diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index 63ff3caef7307..25b9089d42522 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd @@ -4215,6 +4215,9 @@ Even if you have downloaded files from this website before, the website might ha <message name="IDS_EXTENSION_PROMPT_WARNING_USERS_PRIVATE" desc="Permission string for access to user accounts."> Read and change whitelisted users </message> + <message name="IDS_EXTENSION_PROMPT_WARNING_DISPLAY_SOURCE" desc="Permission string for access to Display Source API."> + Send audio and video to displays on the local network + </message> </if> <!-- Extension/App error messages --> diff --git a/chrome/common/extensions/permissions/chrome_permission_message_rules.cc b/chrome/common/extensions/permissions/chrome_permission_message_rules.cc index 66a4f2001c708..4ef877e718318 100644 --- a/chrome/common/extensions/permissions/chrome_permission_message_rules.cc +++ b/chrome/common/extensions/permissions/chrome_permission_message_rules.cc @@ -633,6 +633,9 @@ ChromePermissionMessageRule::GetAllRules() { {IDS_EXTENSION_PROMPT_WARNING_USERS_PRIVATE, {APIPermission::kUsersPrivate}, {}}, + {IDS_EXTENSION_PROMPT_WARNING_DISPLAY_SOURCE, + {APIPermission::kDisplaySource}, + {}}, }; return std::vector<ChromePermissionMessageRule>( diff --git a/extensions/browser/api/display_source/OWNERS b/extensions/browser/api/display_source/OWNERS new file mode 100644 index 0000000000000..f7e4f6f87513e --- /dev/null +++ b/extensions/browser/api/display_source/OWNERS @@ -0,0 +1,2 @@ +alexander.shalamov@intel.com +mikhail.pozdnyakov@intel.com diff --git a/extensions/browser/api/display_source/display_source_api.cc b/extensions/browser/api/display_source/display_source_api.cc new file mode 100644 index 0000000000000..9b92f0f0c4346 --- /dev/null +++ b/extensions/browser/api/display_source/display_source_api.cc @@ -0,0 +1,97 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/browser/api/display_source/display_source_api.h" +#include "extensions/browser/api/display_source/display_source_connection_delegate_factory.h" +#include "extensions/common/api/display_source.h" + +namespace extensions { + +namespace { + +const char kErrorNotSupported[] = "Not supported"; +const char kErrorInvalidArguments[] = "Invalid arguments"; + +} // namespace + +//////////////////////////////////////////////////////////////////////////////// +// DisplaySourceGetAvailableSinksFunction + +DisplaySourceGetAvailableSinksFunction:: + ~DisplaySourceGetAvailableSinksFunction() {} + +ExtensionFunction::ResponseAction +DisplaySourceGetAvailableSinksFunction::Run() { + DisplaySourceConnectionDelegate* delegate = + DisplaySourceConnectionDelegateFactory::GetForBrowserContext( + browser_context()); + if (!delegate) { + return RespondNow(Error(kErrorNotSupported)); + } + + auto success_callback = base::Bind( + &DisplaySourceGetAvailableSinksFunction::OnGetSinksCompleted, this); + auto failure_callback = base::Bind( + &DisplaySourceGetAvailableSinksFunction::OnGetSinksFailed, this); + delegate->GetAvailableSinks(success_callback, failure_callback); + + return RespondLater(); +} + +void DisplaySourceGetAvailableSinksFunction::OnGetSinksCompleted( + const DisplaySourceSinkInfoList& sinks) { + scoped_ptr<base::ListValue> result = + api::display_source::GetAvailableSinks::Results::Create(sinks); + Respond(ArgumentList(result.Pass())); +} + +void DisplaySourceGetAvailableSinksFunction::OnGetSinksFailed( + const std::string& reason) { + Respond(Error(reason)); +} + +//////////////////////////////////////////////////////////////////////////////// +// DisplaySourceRequestAuthenticationFunction + +DisplaySourceRequestAuthenticationFunction:: + ~DisplaySourceRequestAuthenticationFunction() {} + +ExtensionFunction::ResponseAction +DisplaySourceRequestAuthenticationFunction::Run() { + scoped_ptr<api::display_source::RequestAuthentication::Params> params( + api::display_source::RequestAuthentication::Params::Create(*args_)); + if (!params) { + return RespondNow(Error(kErrorInvalidArguments)); + } + + DisplaySourceConnectionDelegate* delegate = + DisplaySourceConnectionDelegateFactory::GetForBrowserContext( + browser_context()); + if (!delegate) { + return RespondNow(Error(kErrorNotSupported)); + } + + auto success_callback = base::Bind( + &DisplaySourceRequestAuthenticationFunction::OnRequestAuthCompleted, + this); + auto failure_callback = base::Bind( + &DisplaySourceRequestAuthenticationFunction::OnRequestAuthFailed, this); + delegate->RequestAuthentication(params->sink_id, success_callback, + failure_callback); + return RespondLater(); +} + +void DisplaySourceRequestAuthenticationFunction::OnRequestAuthCompleted( + const DisplaySourceAuthInfo& auth_info) { + scoped_ptr<base::ListValue> result = + api::display_source::RequestAuthentication::Results::Create(auth_info); + Respond(ArgumentList(result.Pass())); +} + +void DisplaySourceRequestAuthenticationFunction::OnRequestAuthFailed( + const std::string& reason) { + Respond(Error(reason)); +} + +} // namespace extensions diff --git a/extensions/browser/api/display_source/display_source_api.h b/extensions/browser/api/display_source/display_source_api.h new file mode 100644 index 0000000000000..f1e3ad44c4d19 --- /dev/null +++ b/extensions/browser/api/display_source/display_source_api.h @@ -0,0 +1,52 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_BROWSER_API_DISPLAY_SOURCE_DISPLAY_SOURCE_API_H_ +#define EXTENSIONS_BROWSER_API_DISPLAY_SOURCE_DISPLAY_SOURCE_API_H_ + +#include "base/macros.h" +#include "extensions/browser/api/display_source/display_source_connection_delegate.h" +#include "extensions/browser/extension_function.h" + +namespace extensions { + +class DisplaySourceGetAvailableSinksFunction + : public UIThreadExtensionFunction { + public: + DECLARE_EXTENSION_FUNCTION("displaySource.getAvailableSinks", + DISPLAYSOURCE_GETAVAILABLESINKS); + DisplaySourceGetAvailableSinksFunction() = default; + + protected: + ~DisplaySourceGetAvailableSinksFunction() override; + ResponseAction Run() final; + + private: + void OnGetSinksCompleted(const DisplaySourceSinkInfoList& sinks); + void OnGetSinksFailed(const std::string& reason); + + DISALLOW_COPY_AND_ASSIGN(DisplaySourceGetAvailableSinksFunction); +}; + +class DisplaySourceRequestAuthenticationFunction + : public UIThreadExtensionFunction { + public: + DECLARE_EXTENSION_FUNCTION("displaySource.requestAuthentication", + DISPLAYSOURCE_REQUESTAUTHENTICATION); + DisplaySourceRequestAuthenticationFunction() = default; + + protected: + ~DisplaySourceRequestAuthenticationFunction() override; + ResponseAction Run() final; + + private: + void OnRequestAuthCompleted(const DisplaySourceAuthInfo& auth_info); + void OnRequestAuthFailed(const std::string& reason); + + DISALLOW_COPY_AND_ASSIGN(DisplaySourceRequestAuthenticationFunction); +}; + +} // namespace extensions + +#endif // EXTENSIONS_BROWSER_API_DISPLAY_SOURCE_DISPLAY_SOURCE_API_H_ diff --git a/extensions/browser/api/display_source/display_source_apitest.cc b/extensions/browser/api/display_source/display_source_apitest.cc new file mode 100644 index 0000000000000..9c44868a56869 --- /dev/null +++ b/extensions/browser/api/display_source/display_source_apitest.cc @@ -0,0 +1,99 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/scoped_ptr.h" +#include "content/public/test/test_utils.h" +#include "extensions/browser/api/display_source/display_source_connection_delegate.h" +#include "extensions/browser/api/display_source/display_source_connection_delegate_factory.h" +#include "extensions/shell/test/shell_apitest.h" + +namespace extensions { + +using api::display_source::SinkInfo; +using api::display_source::SinkState; +using api::display_source::SINK_STATE_DISCONNECTED; +using api::display_source::AUTHENTICATION_METHOD_PBC; + +namespace { + +DisplaySourceSinkInfoPtr CreateSinkInfoPtr(int id, + const std::string& name, + SinkState state) { + DisplaySourceSinkInfoPtr ptr(new SinkInfo()); + ptr->id = id; + ptr->name = name; + ptr->state = state; + + return ptr; +} + +} // namespace + +class MockDisplaySourceConnectionDelegate + : public DisplaySourceConnectionDelegate { + public: + DisplaySourceSinkInfoList last_found_sinks() const override { return sinks_; } + const Connection* connection() const override { return nullptr; } + void GetAvailableSinks(const SinkInfoListCallback& sinks_callback, + const FailureCallback& failure_callback) override { + AddSink(CreateSinkInfoPtr(1, "sink 1", SINK_STATE_DISCONNECTED)); + sinks_callback.Run(sinks_); + } + + void RequestAuthentication(int sink_id, + const AuthInfoCallback& auth_info_callback, + const FailureCallback& failure_callback) override { + DisplaySourceAuthInfo info; + info.method = AUTHENTICATION_METHOD_PBC; + auth_info_callback.Run(info); + } + + void Connect(int sink_id, + const DisplaySourceAuthInfo& auth_info, + const base::Closure& connected_callback, + const FailureCallback& failure_callback) override {} + + void Disconnect(const base::Closure& disconnected_callback, + const FailureCallback& failure_callback) override {} + + void StartWatchingSinks() override { + AddSink(CreateSinkInfoPtr(2, "sink 2", SINK_STATE_DISCONNECTED)); + } + + void StopWatchingSinks() override {} + + private: + void AddSink(DisplaySourceSinkInfoPtr sink) { + sinks_.push_back(sink); + FOR_EACH_OBSERVER(DisplaySourceConnectionDelegate::Observer, observers_, + OnSinksUpdated(sinks_)); + } + + DisplaySourceSinkInfoList sinks_; +}; + +class DisplaySourceApiTest : public ShellApiTest { + public: + DisplaySourceApiTest() = default; + + private: + static scoped_ptr<KeyedService> CreateMockDelegate( + content::BrowserContext* profile) { + return make_scoped_ptr<KeyedService>( + new MockDisplaySourceConnectionDelegate()); + } + + void SetUpOnMainThread() override { + ShellApiTest::SetUpOnMainThread(); + DisplaySourceConnectionDelegateFactory::GetInstance()->SetTestingFactory( + browser_context(), &CreateMockDelegate); + content::RunAllPendingInMessageLoop(); + } +}; + +IN_PROC_BROWSER_TEST_F(DisplaySourceApiTest, DisplaySourceExtension) { + ASSERT_TRUE(RunAppTest("api_test/display_source/api")) << message_; +} + +} // namespace extensions diff --git a/extensions/browser/api/display_source/display_source_connection_delegate.cc b/extensions/browser/api/display_source/display_source_connection_delegate.cc new file mode 100644 index 0000000000000..1fd099011537d --- /dev/null +++ b/extensions/browser/api/display_source/display_source_connection_delegate.cc @@ -0,0 +1,25 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/browser/api/display_source/display_source_connection_delegate.h" + +namespace extensions { + +DisplaySourceConnectionDelegate::Connection::Connection() {} + +DisplaySourceConnectionDelegate::Connection::~Connection() {} + +DisplaySourceConnectionDelegate::DisplaySourceConnectionDelegate() {} + +DisplaySourceConnectionDelegate::~DisplaySourceConnectionDelegate() {} + +void DisplaySourceConnectionDelegate::AddObserver(Observer* observer) { + observers_.AddObserver(observer); +} + +void DisplaySourceConnectionDelegate::RemoveObserver(Observer* observer) { + observers_.RemoveObserver(observer); +} + +} // namespace extensions diff --git a/extensions/browser/api/display_source/display_source_connection_delegate.h b/extensions/browser/api/display_source/display_source_connection_delegate.h new file mode 100644 index 0000000000000..a885dacf1a9a0 --- /dev/null +++ b/extensions/browser/api/display_source/display_source_connection_delegate.h @@ -0,0 +1,103 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_BROWSER_API_DISPLAY_SOURCE_DISPLAY_SOURCE_CONNECTION_DELEGATE_H_ +#define EXTENSIONS_BROWSER_API_DISPLAY_SOURCE_DISPLAY_SOURCE_CONNECTION_DELEGATE_H_ + +#include "base/callback.h" +#include "base/observer_list.h" +#include "components/keyed_service/core/keyed_service.h" +#include "extensions/common/api/display_source.h" + +namespace extensions { + +using DisplaySourceSinkInfoPtr = linked_ptr<api::display_source::SinkInfo>; +using DisplaySourceSinkInfoList = std::vector<DisplaySourceSinkInfoPtr>; +using DisplaySourceAuthInfo = api::display_source::AuthenticationInfo; + +// The DisplaySourceConnectionDelegate interface should be implemented +// to provide sinks search and connection functionality for +// 'chrome.displaySource' +// API. +class DisplaySourceConnectionDelegate : public KeyedService { + public: + using AuthInfoCallback = base::Callback<void(const DisplaySourceAuthInfo&)>; + using FailureCallback = base::Callback<void(const std::string&)>; + using SinkInfoListCallback = + base::Callback<void(const DisplaySourceSinkInfoList&)>; + + struct Connection { + Connection(); + ~Connection(); + DisplaySourceSinkInfoPtr connected_sink; + std::string local_ip; + std::string sink_ip; + }; + + class Observer { + public: + // This method is called each tiome the list of available + // sinks is updated whether after 'GetAvailableSinks' call + // or while the implementation is constantly watching the sinks + // (after 'StartWatchingSinks' was called). + virtual void OnSinksUpdated(const DisplaySourceSinkInfoList& sinks) = 0; + + protected: + virtual ~Observer() {} + }; + + DisplaySourceConnectionDelegate(); + ~DisplaySourceConnectionDelegate() override; + + virtual void AddObserver(Observer* observer); + virtual void RemoveObserver(Observer* observer); + + // Returns the list of last found available sinks + // this list may contain outdated data if the delegate + // is not watching the sinks (via 'StartWatchingSinks' + // method). The list is refreshed after 'GetAvailableSinks' + // call. + virtual DisplaySourceSinkInfoList last_found_sinks() const = 0; + + // Returns the Connection object representing the current + // connection to the sink or NULL if there is no curent connection. + virtual const Connection* connection() const = 0; + + // Queries the list of currently available sinks. + virtual void GetAvailableSinks(const SinkInfoListCallback& sinks_callback, + const FailureCallback& failure_callback) = 0; + + // Queries the authentication method required by the sink for connection. + // If the used authentication method requires authentication data to be + // visible on the sink's display (e.g. PIN) the implementation should + // request the sink to show it. + virtual void RequestAuthentication( + int sink_id, + const AuthInfoCallback& auth_info_callback, + const FailureCallback& failure_callback) = 0; + + // Connects to a sink by given id and auth info. + virtual void Connect(int sink_id, + const DisplaySourceAuthInfo& auth_info, + const base::Closure& connected_callback, + const FailureCallback& failure_callback) = 0; + + // Disconnects the current connection to sink, the 'failure_callback' + // is called if there is no current connection. + virtual void Disconnect(const base::Closure& disconnected_callback, + const FailureCallback& failure_callback) = 0; + + // Implementation should start watching the sinks updates. + virtual void StartWatchingSinks() = 0; + + // Implementation should stop watching the sinks updates. + virtual void StopWatchingSinks() = 0; + + protected: + base::ObserverList<Observer> observers_; +}; + +} // namespace extensions + +#endif // EXTENSIONS_BROWSER_API_DISPLAY_SOURCE_DISPLAY_SOURCE_CONNECTION_DELEGATE_H_ diff --git a/extensions/browser/api/display_source/display_source_connection_delegate_factory.cc b/extensions/browser/api/display_source/display_source_connection_delegate_factory.cc new file mode 100644 index 0000000000000..d6c5f27a4030b --- /dev/null +++ b/extensions/browser/api/display_source/display_source_connection_delegate_factory.cc @@ -0,0 +1,60 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/browser/api/display_source/display_source_connection_delegate_factory.h" + +#include "components/keyed_service/content/browser_context_dependency_manager.h" +#include "content/public/browser/browser_thread.h" +#include "extensions/browser/extensions_browser_client.h" + +namespace extensions { + +using content::BrowserContext; + +// static +DisplaySourceConnectionDelegate* +DisplaySourceConnectionDelegateFactory::GetForBrowserContext( + BrowserContext* browser_context) { + return static_cast<DisplaySourceConnectionDelegate*>( + GetInstance()->GetServiceForBrowserContext(browser_context, true)); +} + +// static +DisplaySourceConnectionDelegateFactory* +DisplaySourceConnectionDelegateFactory::GetInstance() { + return base::Singleton<DisplaySourceConnectionDelegateFactory>::get(); +} + +DisplaySourceConnectionDelegateFactory::DisplaySourceConnectionDelegateFactory() + : BrowserContextKeyedServiceFactory( + "DisplaySourceConnectionDelegate", + BrowserContextDependencyManager::GetInstance()) {} + +DisplaySourceConnectionDelegateFactory:: + ~DisplaySourceConnectionDelegateFactory() {} + +KeyedService* DisplaySourceConnectionDelegateFactory::BuildServiceInstanceFor( + BrowserContext* browser_context) const { + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + + DisplaySourceConnectionDelegate* delegate = nullptr; + // TODO(mikhail): Add implementation. + return delegate; +} + +BrowserContext* DisplaySourceConnectionDelegateFactory::GetBrowserContextToUse( + BrowserContext* context) const { + return ExtensionsBrowserClient::Get()->GetOriginalContext(context); +} + +bool DisplaySourceConnectionDelegateFactory:: + ServiceIsCreatedWithBrowserContext() const { + return false; +} + +bool DisplaySourceConnectionDelegateFactory::ServiceIsNULLWhileTesting() const { + return false; +} + +} // namespace extensions diff --git a/extensions/browser/api/display_source/display_source_connection_delegate_factory.h b/extensions/browser/api/display_source/display_source_connection_delegate_factory.h new file mode 100644 index 0000000000000..ec412a9884dbd --- /dev/null +++ b/extensions/browser/api/display_source/display_source_connection_delegate_factory.h @@ -0,0 +1,47 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_BROWSER_API_DISPLAY_SOURCE_DISPLAY_SOURCE_CONNECTION_DELEGATE_FACTORY_H_ +#define EXTENSIONS_BROWSER_API_DISPLAY_SOURCE_DISPLAY_SOURCE_CONNECTION_DELEGATE_FACTORY_H_ + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/singleton.h" +#include "components/keyed_service/content/browser_context_keyed_service_factory.h" +#include "extensions/browser/api/display_source/display_source_connection_delegate.h" + +namespace context { +class BrowserContext; +} + +namespace extensions { + +class DisplaySourceConnectionDelegateFactory + : public BrowserContextKeyedServiceFactory { + public: + static DisplaySourceConnectionDelegate* GetForBrowserContext( + content::BrowserContext* browser_context); + static DisplaySourceConnectionDelegateFactory* GetInstance(); + + private: + friend struct base::DefaultSingletonTraits< + DisplaySourceConnectionDelegateFactory>; + + DisplaySourceConnectionDelegateFactory(); + ~DisplaySourceConnectionDelegateFactory() override; + + // BrowserContextKeyedServiceFactory: + KeyedService* BuildServiceInstanceFor( + content::BrowserContext* browser_context) const override; + content::BrowserContext* GetBrowserContextToUse( + content::BrowserContext* context) const override; + bool ServiceIsCreatedWithBrowserContext() const override; + bool ServiceIsNULLWhileTesting() const override; + + DISALLOW_COPY_AND_ASSIGN(DisplaySourceConnectionDelegateFactory); +}; + +} // namespace extensions + +#endif // EXTENSIONS_BROWSER_API_DISPLAY_SOURCE_DISPLAY_SOURCE_CONNECTION_DELEGATE_FACTORY_H_ diff --git a/extensions/browser/api/display_source/display_source_event_router.cc b/extensions/browser/api/display_source/display_source_event_router.cc new file mode 100644 index 0000000000000..db6f9ab3f1996 --- /dev/null +++ b/extensions/browser/api/display_source/display_source_event_router.cc @@ -0,0 +1,100 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/browser/api/display_source/display_source_event_router.h" + +#include "content/public/browser/browser_context.h" +#include "extensions/browser/api/display_source/display_source_api.h" +#include "extensions/browser/api/display_source/display_source_connection_delegate_factory.h" +#include "extensions/common/api/display_source.h" + +namespace extensions { + +DisplaySourceEventRouter::DisplaySourceEventRouter( + content::BrowserContext* browser_context) + : browser_context_(browser_context), listening_(false) { + EventRouter* event_router = EventRouter::Get(browser_context_); + if (!event_router) + return; + event_router->RegisterObserver( + this, api::display_source::OnSinksUpdated::kEventName); +} + +DisplaySourceEventRouter::~DisplaySourceEventRouter() { + DCHECK(!listening_); +} + +void DisplaySourceEventRouter::Shutdown() { + EventRouter* event_router = EventRouter::Get(browser_context_); + if (event_router) + event_router->UnregisterObserver(this); + + if (!listening_) + return; + listening_ = false; + DisplaySourceConnectionDelegate* delegate = + DisplaySourceConnectionDelegateFactory::GetForBrowserContext( + browser_context_); + if (delegate) + delegate->RemoveObserver(this); +} + +void DisplaySourceEventRouter::OnListenerAdded( + const EventListenerInfo& details) { + StartOrStopListeningForSinksChanges(); +} + +void DisplaySourceEventRouter::OnListenerRemoved( + const EventListenerInfo& details) { + StartOrStopListeningForSinksChanges(); +} + +void DisplaySourceEventRouter::StartOrStopListeningForSinksChanges() { + EventRouter* event_router = EventRouter::Get(browser_context_); + if (!event_router) + return; + + bool should_listen = event_router->HasEventListener( + api::display_source::OnSinksUpdated::kEventName); + if (should_listen && !listening_) { + DisplaySourceConnectionDelegate* delegate = + DisplaySourceConnectionDelegateFactory::GetForBrowserContext( + browser_context_); + if (delegate) { + delegate->AddObserver(this); + delegate->StartWatchingSinks(); + } + } + if (!should_listen && listening_) { + DisplaySourceConnectionDelegate* delegate = + DisplaySourceConnectionDelegateFactory::GetForBrowserContext( + browser_context_); + if (delegate) { + delegate->RemoveObserver(this); + delegate->StopWatchingSinks(); + } + } + + listening_ = should_listen; +} + +void DisplaySourceEventRouter::OnSinksUpdated( + const DisplaySourceSinkInfoList& sinks) { + EventRouter* event_router = EventRouter::Get(browser_context_); + if (!event_router) + return; + scoped_ptr<base::ListValue> args( + api::display_source::OnSinksUpdated::Create(sinks)); + scoped_ptr<Event> sinks_updated_event( + new Event(events::DISPLAY_SOURCE_ON_SINKS_UPDATED, + api::display_source::OnSinksUpdated::kEventName, args.Pass())); + event_router->BroadcastEvent(sinks_updated_event.Pass()); +} + +DisplaySourceEventRouter* DisplaySourceEventRouter::Create( + content::BrowserContext* browser_context) { + return new DisplaySourceEventRouter(browser_context); +} + +} // namespace extensions diff --git a/extensions/browser/api/display_source/display_source_event_router.h b/extensions/browser/api/display_source/display_source_event_router.h new file mode 100644 index 0000000000000..6697fe80dd60a --- /dev/null +++ b/extensions/browser/api/display_source/display_source_event_router.h @@ -0,0 +1,54 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_BROWSER_API_DISPLAY_SOURCE_DISPLAY_SOURCE_EVENT_ROUTER_H_ +#define EXTENSIONS_BROWSER_API_DISPLAY_SOURCE_DISPLAY_SOURCE_EVENT_ROUTER_H_ + +#include "base/macros.h" +#include "components/keyed_service/core/keyed_service.h" +#include "extensions/browser/api/display_source/display_source_connection_delegate.h" +#include "extensions/browser/event_router.h" + +namespace content { +class BrowserContext; +} + +namespace extensions { + +// Observe listeners to 'onSinksUpdated' events. +class DisplaySourceEventRouter + : public KeyedService, + public EventRouter::Observer, + public DisplaySourceConnectionDelegate::Observer { + public: + static DisplaySourceEventRouter* Create( + content::BrowserContext* browser_context); + + ~DisplaySourceEventRouter() override; + + private: + explicit DisplaySourceEventRouter(content::BrowserContext* browser_context); + + // KeyedService overrides: + void Shutdown() override; + + // EventRouter::Observer overrides: + void OnListenerAdded(const EventListenerInfo& details) override; + void OnListenerRemoved(const EventListenerInfo& details) override; + + // DisplaySourceConnectionDelegate::Observer overrides: + void OnSinksUpdated(const DisplaySourceSinkInfoList& sinks) override; + + private: + void StartOrStopListeningForSinksChanges(); + + content::BrowserContext* browser_context_; + bool listening_; + + DISALLOW_COPY_AND_ASSIGN(DisplaySourceEventRouter); +}; + +} // namespace extensions + +#endif // EXTENSIONS_BROWSER_API_DISPLAY_SOURCE_DISPLAY_SOURCE_EVENT_ROUTER_H_ diff --git a/extensions/browser/api/display_source/display_source_event_router_factory.cc b/extensions/browser/api/display_source/display_source_event_router_factory.cc new file mode 100644 index 0000000000000..050159f19f967 --- /dev/null +++ b/extensions/browser/api/display_source/display_source_event_router_factory.cc @@ -0,0 +1,59 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/browser/api/display_source/display_source_event_router_factory.h" + +#include "components/keyed_service/content/browser_context_dependency_manager.h" +#include "content/public/browser/browser_context.h" +#include "extensions/browser/api/display_source/display_source_connection_delegate_factory.h" +#include "extensions/browser/api/display_source/display_source_event_router.h" +#include "extensions/browser/extension_system_provider.h" +#include "extensions/browser/extensions_browser_client.h" + +namespace extensions { + +// static +DisplaySourceEventRouter* DisplaySourceEventRouterFactory::GetForProfile( + content::BrowserContext* context) { + return static_cast<DisplaySourceEventRouter*>( + GetInstance()->GetServiceForBrowserContext(context, true)); +} + +// static +DisplaySourceEventRouterFactory* +DisplaySourceEventRouterFactory::GetInstance() { + return base::Singleton<DisplaySourceEventRouterFactory>::get(); +} + +DisplaySourceEventRouterFactory::DisplaySourceEventRouterFactory() + : BrowserContextKeyedServiceFactory( + "DisplaySourceEventRouter", + BrowserContextDependencyManager::GetInstance()) { + DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory()); + DependsOn(DisplaySourceConnectionDelegateFactory::GetInstance()); +} + +DisplaySourceEventRouterFactory::~DisplaySourceEventRouterFactory() {} + +KeyedService* DisplaySourceEventRouterFactory::BuildServiceInstanceFor( + content::BrowserContext* context) const { + return DisplaySourceEventRouter::Create(context); +} + +content::BrowserContext* +DisplaySourceEventRouterFactory::GetBrowserContextToUse( + content::BrowserContext* context) const { + return ExtensionsBrowserClient::Get()->GetOriginalContext(context); +} + +bool DisplaySourceEventRouterFactory::ServiceIsCreatedWithBrowserContext() + const { + return true; +} + +bool DisplaySourceEventRouterFactory::ServiceIsNULLWhileTesting() const { + return true; +} + +} // namespace extensions diff --git a/extensions/browser/api/display_source/display_source_event_router_factory.h b/extensions/browser/api/display_source/display_source_event_router_factory.h new file mode 100644 index 0000000000000..7fd777f2cb29b --- /dev/null +++ b/extensions/browser/api/display_source/display_source_event_router_factory.h @@ -0,0 +1,48 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_BROWSER_API_DISPLAY_SOURCE_DISPLAY_SOURCE_EVENT_ROUTER_FACTORY_H_ +#define EXTENSIONS_BROWSER_API_DISPLAY_SOURCE_DISPLAY_SOURCE_EVENT_ROUTER_FACTORY_H_ + +#include "base/macros.h" +#include "base/memory/singleton.h" +#include "components/keyed_service/content/browser_context_keyed_service_factory.h" + +namespace extensions { + +class DisplaySourceEventRouter; + +class DisplaySourceEventRouterFactory + : public BrowserContextKeyedServiceFactory { + public: + // Returns the DisplaySourceEventRouter for |profile|, creating it if + // it is not yet created. + static DisplaySourceEventRouter* GetForProfile( + content::BrowserContext* context); + + static DisplaySourceEventRouterFactory* GetInstance(); + + protected: + // BrowserContextKeyedBaseFactory overrides: + content::BrowserContext* GetBrowserContextToUse( + content::BrowserContext* context) const override; + bool ServiceIsCreatedWithBrowserContext() const override; + bool ServiceIsNULLWhileTesting() const override; + + private: + friend struct base::DefaultSingletonTraits<DisplaySourceEventRouterFactory>; + + DisplaySourceEventRouterFactory(); + ~DisplaySourceEventRouterFactory() override; + + // BrowserContextKeyedServiceFactory: + KeyedService* BuildServiceInstanceFor( + content::BrowserContext* profile) const override; + + DISALLOW_COPY_AND_ASSIGN(DisplaySourceEventRouterFactory); +}; + +} // namespace extensions + +#endif // EXTENSIONS_BROWSER_API_DISPLAY_SOURCE_DISPLAY_SOURCE_EVENT_ROUTER_FACTORY_H_ diff --git a/extensions/browser/browser_context_keyed_service_factories.cc b/extensions/browser/browser_context_keyed_service_factories.cc index 8d1061f1f76e1..002c68f796365 100644 --- a/extensions/browser/browser_context_keyed_service_factories.cc +++ b/extensions/browser/browser_context_keyed_service_factories.cc @@ -10,6 +10,7 @@ #include "extensions/browser/api/bluetooth/bluetooth_api.h" #include "extensions/browser/api/bluetooth/bluetooth_private_api.h" #include "extensions/browser/api/bluetooth_socket/bluetooth_socket_event_dispatcher.h" +#include "extensions/browser/api/display_source/display_source_event_router_factory.h" #include "extensions/browser/api/hid/hid_device_manager.h" #include "extensions/browser/api/idle/idle_manager_factory.h" #include "extensions/browser/api/management/management_api.h" @@ -61,6 +62,7 @@ void EnsureBrowserContextKeyedServiceFactoriesBuilt() { api::TCPSocketEventDispatcher::GetFactoryInstance(); api::UDPSocketEventDispatcher::GetFactoryInstance(); DeclarativeUserScriptManagerFactory::GetInstance(); + DisplaySourceEventRouterFactory::GetInstance(); EventRouterFactory::GetInstance(); ExtensionMessageFilter::EnsureShutdownNotifierFactoryBuilt(); ExtensionPrefsFactory::GetInstance(); diff --git a/extensions/browser/extension_event_histogram_value.h b/extensions/browser/extension_event_histogram_value.h index 8a2bf65f29777..6d0b30e4f1db5 100644 --- a/extensions/browser/extension_event_histogram_value.h +++ b/extensions/browser/extension_event_histogram_value.h @@ -410,6 +410,7 @@ enum HistogramValue { EASY_UNLOCK_PRIVATE_ON_CONNECTION_STATUS_CHANGED, EASY_UNLOCK_PRIVATE_ON_DATA_RECEIVED, EASY_UNLOCK_PRIVATE_ON_SEND_COMPLETED, + DISPLAY_SOURCE_ON_SINKS_UPDATED, // Last entry: Add new entries above, then run: // python tools/metrics/histograms/update_extension_histograms.py ENUM_BOUNDARY diff --git a/extensions/browser/extension_function_histogram_value.h b/extensions/browser/extension_function_histogram_value.h index 1faed633fde4c..7820b7f5a706a 100644 --- a/extensions/browser/extension_function_histogram_value.h +++ b/extensions/browser/extension_function_histogram_value.h @@ -1156,6 +1156,8 @@ enum HistogramValue { SETTINGSPRIVATE_SETDEFAULTZOOMPERCENTFUNCTION, BLUETOOTHPRIVATE_CONNECT, BLUETOOTHPRIVATE_FORGETDEVICE, + DISPLAYSOURCE_GETAVAILABLESINKS, + DISPLAYSOURCE_REQUESTAUTHENTICATION, // Last entry: Add new entries above, then run: // python tools/metrics/histograms/update_extension_histograms.py ENUM_BOUNDARY diff --git a/extensions/common/api/_api_features.json b/extensions/common/api/_api_features.json index 540818b0f9090..012d906ac6114 100644 --- a/extensions/common/api/_api_features.json +++ b/extensions/common/api/_api_features.json @@ -127,6 +127,10 @@ "extension_types": ["platform_app"], "contexts": ["blessed_extension"] }, + "displaySource": { + "dependencies": ["permission:displaySource"], + "contexts": ["blessed_extension"] + }, "dns": { "dependencies": ["permission:dns"], "contexts": ["blessed_extension"] diff --git a/extensions/common/api/_permission_features.json b/extensions/common/api/_permission_features.json index c395360a5c86a..ed3e53565ebda 100644 --- a/extensions/common/api/_permission_features.json +++ b/extensions/common/api/_permission_features.json @@ -184,6 +184,10 @@ ] } ], + "displaySource": { + "channel": "dev", + "extension_types": ["extension", "platform_app"] + }, "dns": [ { "channel": "dev", diff --git a/extensions/common/api/display_source.idl b/extensions/common/api/display_source.idl new file mode 100644 index 0000000000000..a3821e3517984 --- /dev/null +++ b/extensions/common/api/display_source.idl @@ -0,0 +1,152 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// The <code>chrome.displaySource</code> API creates a Display +// session using WebMediaStreamTrack as sources. +namespace displaySource { + enum ErrorType { + // Cannot create media pipeline from the given media stream which could be + // appropriate for a Display session (e.g., necessary codecs are missing + // on the platform). + create_media_pipeline_error, + + // A new Display session cannot be started before the existing one is + // terminated. + exceeded_session_limit_error, + + // Could not establish connection to the sink. + establish_connection_error, + + // The capabilities of this Display Source and the connected + // sink do not fit (e.g. the sink cannot play the media content of + // the formats given by the source). + capabilities_negotiation_error, + + // There was an error while packetizing and sending the media content. + media_send_error, + + // The TCP connection with sink has dropped unexpectedly. + connection_error, + + // An unexpected message has arrived from the sink. + unexpected_message_error, + + // The sink became unresponsive. + timeout_error, + + // Unspecified error. + unknown_error + }; + + dictionary ErrorInfo { + ErrorType type; + DOMString? description; + }; + + enum SinkState { + // Connected using this Display Source (i.e., there is an active session) + Connected, + // In process of connection to this Display Source + Connecting, + // Disconnected from this Display Source + Disconnected + }; + + dictionary SinkInfo { + // Id of the sink. It is guaranteed to be unique during the browser session. + long id; + // Human readable name of the sink. + DOMString name; + // State of the sink. + SinkState state; + }; + + enum AuthenticationMethod { + // Push Button Config authentication method. + PBC, + // PIN authentication method. + PIN + }; + + dictionary AuthenticationInfo { + // Authentication method. + AuthenticationMethod method; + // Authentication data (e.g. PIN value). + DOMString? data; + }; + + dictionary StartSessionInfo { + // Id of the sink to connect. + long sinkId; + // Authentication information. + AuthenticationInfo? authenticationInfo; + // The source audio track. + [instanceOf=MediaStreamTrack] object? audioTrack; + // The source audio track. + [instanceOf=MediaStreamTrack] object? videoTrack; + }; + + callback GetSinksCallback = void (SinkInfo[] result); + callback RequestAuthenticationCallback = void (AuthenticationInfo result); + callback TerminateSessionCallback = void (); + + interface Functions { + // Queries the list of the currently available Display sinks. + // + // |callback| : Called when the request is completed. The argument list + // is empty if no available sinks were found. + static void getAvailableSinks(GetSinksCallback callback); + + // Queries authentication data from the sink device. + // + // |sinkId| : Id of the sink + // |callback| : Called when authentication info retrieved from the sink. + // The argument |method| field contains the authentication method required + // by the sink for connection; the |data| field can be null or can contain + // some supplementary data provided by the sink. If authentication info + // cannot be retrieved from the sink the "chrome.runtime.lastError" property + // is defined. + static void requestAuthentication(long sinkId, + RequestAuthenticationCallback callback); + + // Creates a Display session using the provided StartSessionInfo instance. + // The input argument fields must be initialized as described below: + // The |sinkId| must be a valid id of a sink (obtained via + // ‘getAvailableSinks’). + // + // The |audioTrack| or |videoTrack| must be of type MediaStreamTrack. + // Either |audioTrack| or |videoTrack| can be null but not both. This + // means creating a session with only audio or video. + // + // The |authenticationInfo| can be null if no additional authentication data + // are required by the sink; otherwise its |data| field must contain the + // required authentication data (e.g. PIN value) and its |method| field must + // be the same as one obtained from ‘requestAuthentication’. + [nocompile] static void startSession(StartSessionInfo sessionInfo); + + // Terminates the active Display session. + // |sinkId| : Id of the connected sink. + // |callback| : Called when the session is terminated. + [nocompile] static void terminateSession( + long sinkId, optional TerminateSessionCallback callback); + }; + + interface Events { + // Event fired when the available sinks are modified (either their amount + // or properties) + // |sinks| the list of all currently available sinks + static void onSinksUpdated(SinkInfo[] sinks); + // Event fired when the Display session is started. + // |sinkId| Id of the peer sink + [nocompile] static void onSessionStarted(long sinkId); + // Event fired when the Display session is terminated. + // |sinkId| Id of the peer sink + [nocompile] static void onSessionTerminated(long sinkId); + // Event fired when an error occurs. + // |sinkId| Id of the peer sink + // |errorInfo| error description + [nocompile] static void onSessionErrorOccured(long sinkId, + ErrorInfo errorInfo); + }; +}; diff --git a/extensions/common/api/schemas.gypi b/extensions/common/api/schemas.gypi index 63b8fcf74872e..a661132c4de8e 100644 --- a/extensions/common/api/schemas.gypi +++ b/extensions/common/api/schemas.gypi @@ -20,6 +20,7 @@ 'bluetooth_socket.idl', 'cast_channel.idl', 'document_scan.idl', + 'display_source.idl', 'dns.idl', 'events.json', 'extensions_manifest_types.json', diff --git a/extensions/common/permissions/api_permission.h b/extensions/common/permissions/api_permission.h index 4f5b38cedc077..5d46ce91f5142 100644 --- a/extensions/common/permissions/api_permission.h +++ b/extensions/common/permissions/api_permission.h @@ -243,6 +243,7 @@ class APIPermission { kEnterpriseDeviceAttributes, kCertificateProvider, kResourcesPrivate, + kDisplaySource, // Last entry: Add new entries above and ensure to update the // "ExtensionPermission3" enum in tools/metrics/histograms/histograms.xml // (by running update_extension_permission.py). diff --git a/extensions/common/permissions/extensions_api_permissions.cc b/extensions/common/permissions/extensions_api_permissions.cc index c9eb11b5e7944..70a667fe6b412 100644 --- a/extensions/common/permissions/extensions_api_permissions.cc +++ b/extensions/common/permissions/extensions_api_permissions.cc @@ -54,6 +54,7 @@ std::vector<APIPermissionInfo*> ExtensionsAPIPermissions::GetAllPermissions() {APIPermission::kDiagnostics, "diagnostics", APIPermissionInfo::kFlagCannotBeOptional}, + {APIPermission::kDisplaySource, "displaySource"}, {APIPermission::kDns, "dns"}, {APIPermission::kDocumentScan, "documentScan"}, {APIPermission::kExtensionView, diff --git a/extensions/extensions.gypi b/extensions/extensions.gypi index d963eb3defa3a..3f7ad6c6e6422 100644 --- a/extensions/extensions.gypi +++ b/extensions/extensions.gypi @@ -321,6 +321,16 @@ 'browser/api/device_permissions_manager.h', 'browser/api/device_permissions_prompt.cc', 'browser/api/device_permissions_prompt.h', + 'browser/api/display_source/display_source_api.cc', + 'browser/api/display_source/display_source_api.h', + 'browser/api/display_source/display_source_connection_delegate.cc', + 'browser/api/display_source/display_source_connection_delegate.h', + 'browser/api/display_source/display_source_connection_delegate_factory.cc', + 'browser/api/display_source/display_source_connection_delegate_factory.h', + 'browser/api/display_source/display_source_event_router.cc', + 'browser/api/display_source/display_source_event_router_factory.cc', + 'browser/api/display_source/display_source_event_router_factory.h', + 'browser/api/display_source/display_source_event_router.h', 'browser/api/dns/dns_api.cc', 'browser/api/dns/dns_api.h', 'browser/api/dns/host_resolver_wrapper.cc', diff --git a/extensions/extensions_tests.gypi b/extensions/extensions_tests.gypi index aab657003e92d..687586048507b 100644 --- a/extensions/extensions_tests.gypi +++ b/extensions/extensions_tests.gypi @@ -7,6 +7,7 @@ 'extensions_browsertests_sources': [ 'browser/api/audio/audio_apitest.cc', 'browser/api/bluetooth_socket/bluetooth_socket_apitest.cc', + 'browser/api/display_source/display_source_apitest.cc', 'browser/api/dns/dns_apitest.cc', 'browser/api/hid/hid_apitest.cc', 'browser/api/printer_provider/printer_provider_apitest.cc', diff --git a/extensions/test/data/api_test/display_source/api/background.js b/extensions/test/data/api_test/display_source/api/background.js new file mode 100644 index 0000000000000..a54d6b5ea05a1 --- /dev/null +++ b/extensions/test/data/api_test/display_source/api/background.js @@ -0,0 +1,39 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +var testGetAvailableSinks = function() { + var callback = function(sinks) { + chrome.test.assertEq(1, sinks.length); + var sink = sinks[0]; + chrome.test.assertEq(1, sink.id); + chrome.test.assertEq("Disconnected", sink.state); + chrome.test.assertEq("sink 1", sink.name); + chrome.test.succeed("GetAvailableSinks succeded"); + }; + chrome.displaySource.getAvailableSinks(callback); +}; + +var testOnSinksUpdated = function() { + var callback = function(sinks) { + chrome.test.assertEq(2, sinks.length); + var sink = sinks[1]; + chrome.test.assertEq(2, sink.id); + chrome.test.assertEq("Disconnected", sink.state); + chrome.test.assertEq("sink 2", sink.name); + chrome.test.succeed("onSinksUpdated event delivered"); + }; + chrome.displaySource.onSinksUpdated.addListener(callback); +}; + +var testRequestAuthentication = function() { + var callback = function(auth_info) { + chrome.test.assertEq("PBC", auth_info.method); + chrome.test.succeed("RequestAuthentication succeded"); + }; + chrome.displaySource.requestAuthentication(1, callback); +}; + +chrome.test.runTests([testGetAvailableSinks, + testOnSinksUpdated, + testRequestAuthentication]); diff --git a/extensions/test/data/api_test/display_source/api/manifest.json b/extensions/test/data/api_test/display_source/api/manifest.json new file mode 100644 index 0000000000000..c390d3ccabf8e --- /dev/null +++ b/extensions/test/data/api_test/display_source/api/manifest.json @@ -0,0 +1,12 @@ +{ + "name": "chrome.displaySource", + "version": "0.1", + "description": "end-to-end browser test for chrome.displaySource API", + "app": { + "background": { + "scripts": ["background.js"] + } + }, + "permissions": ["displaySource"] +} + diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml index 926604419c8c7..0d99c05574f6b 100644 --- a/tools/metrics/histograms/histograms.xml +++ b/tools/metrics/histograms/histograms.xml @@ -59445,6 +59445,7 @@ http://cs/file:chrome/histograms.xml - but prefer this file for new entries. <int value="389" label="EASY_UNLOCK_PRIVATE_ON_CONNECTION_STATUS_CHANGED"/> <int value="390" label="EASY_UNLOCK_PRIVATE_ON_DATA_RECEIVED"/> <int value="391" label="EASY_UNLOCK_PRIVATE_ON_SEND_COMPLETED"/> + <int value="392" label="DISPLAY_SOURCE_ON_SINKS_UPDATED"/> </enum> <enum name="ExtensionFileWriteResult" type="int"> @@ -60588,6 +60589,8 @@ http://cs/file:chrome/histograms.xml - but prefer this file for new entries. <int value="1095" label="SETTINGSPRIVATE_SETDEFAULTZOOMPERCENTFUNCTION"/> <int value="1096" label="BLUETOOTHPRIVATE_CONNECT"/> <int value="1097" label="BLUETOOTHPRIVATE_FORGETDEVICE"/> + <int value="1098" label="DISPLAYSOURCE_GETAVAILABLESINKS"/> + <int value="1099" label="DISPLAYSOURCE_REQUESTAUTHENTICATION"/> </enum> <enum name="ExtensionInstallCause" type="int">