diff options
Diffstat (limited to 'chrome')
23 files changed, 1179 insertions, 0 deletions
diff --git a/chrome/browser/DEPS b/chrome/browser/DEPS index 797e41f..6b4f26e 100644 --- a/chrome/browser/DEPS +++ b/chrome/browser/DEPS @@ -20,6 +20,7 @@ include_rules = [ "+components/cdm/browser", "+components/cloud_devices/common", "+components/content_settings", + "+components/copresence", "+components/data_reduction_proxy", "+components/dom_distiller", "+components/domain_reliability", diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd index d2b7ed8..0e975f7 100644 --- a/chrome/browser/browser_resources.grd +++ b/chrome/browser/browser_resources.grd @@ -436,6 +436,7 @@ <include name="IDR_HANGUL_MANIFEST" file="resources\chromeos\input_method\hangul_manifest.json" type="BINDATA" /> <include name="IDR_BRAILLE_MANIFEST" file="resources\chromeos\braille_ime\manifest.json" type="BINDATA" /> </if> + <include name="IDR_WHISPERNET_PROXY_MANIFEST" file="resources\whispernet_proxy\manifest.json" type="BINDATA" /> </includes> </release> </grit> diff --git a/chrome/browser/copresence/OWNERS b/chrome/browser/copresence/OWNERS new file mode 100644 index 0000000..2bc8d07 --- /dev/null +++ b/chrome/browser/copresence/OWNERS @@ -0,0 +1,3 @@ +rkc@chromium.org +ckehoe@chromium.org +xiyuan@chromium.org diff --git a/chrome/browser/copresence/chrome_whispernet_client.cc b/chrome/browser/copresence/chrome_whispernet_client.cc new file mode 100644 index 0000000..3fa8bbb --- /dev/null +++ b/chrome/browser/copresence/chrome_whispernet_client.cc @@ -0,0 +1,200 @@ +// Copyright 2014 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 "chrome/browser/copresence/chrome_whispernet_client.h" + +#include "chrome/browser/extensions/api/copresence_private/copresence_private_api.h" +#include "chrome/browser/extensions/component_loader.h" +#include "chrome/browser/extensions/extension_service.h" +#include "chrome/common/extensions/api/copresence_private.h" +#include "content/public/browser/browser_context.h" +#include "extensions/browser/event_router.h" +#include "extensions/browser/extension_system.h" +#include "grit/browser_resources.h" + +namespace copresence { + +// Once the CL for the copresence component lands, these constants will be +// need to be picked up from components/copresence/copresence_constants.h +const int kDefaultRepetitions = 5; +const float kDefaultSampleRate = 48000.0; +const int kDefaultBitsPerSample = 16; +const float kDefaultCarrierFrequency = 18500.0; +const int kDefaultChannels = 2; + +} // namespace copresence + +// static +const char ChromeWhispernetClient::kWhispernetProxyExtensionId[] = + "bpfmnplchembfbdgieamdodgaencleal"; + +// Public: + +ChromeWhispernetClient::ChromeWhispernetClient( + content::BrowserContext* browser_context) + : browser_context_(browser_context), extension_loaded_(false) { +} + +ChromeWhispernetClient::~ChromeWhispernetClient() { +} + +void ChromeWhispernetClient::Initialize(const SuccessCallback& init_callback) { + DVLOG(3) << "Initializing whispernet proxy client."; + init_callback_ = init_callback; + + extensions::ExtensionSystem* es = + extensions::ExtensionSystem::Get(browser_context_); + DCHECK(es); + ExtensionService* service = es->extension_service(); + DCHECK(service); + extensions::ComponentLoader* loader = service->component_loader(); + DCHECK(loader); + + // This callback is cancelled in Shutdown(). + extension_loaded_callback_ = base::Bind( + &ChromeWhispernetClient::OnExtensionLoaded, base::Unretained(this)); + + if (!loader->Exists(kWhispernetProxyExtensionId)) { + DVLOG(3) << "Loading Whispernet proxy."; + loader->Add(IDR_WHISPERNET_PROXY_MANIFEST, + base::FilePath(FILE_PATH_LITERAL("whispernet_proxy"))); + } else { + init_callback_.Run(true); + } +} + +void ChromeWhispernetClient::Shutdown() { + extension_loaded_callback_.Reset(); + init_callback_.Reset(); + tokens_callback_.Reset(); + samples_callback_.Reset(); + db_callback_.Reset(); +} + +// Fire an event to request a token encode. +void ChromeWhispernetClient::EncodeToken(const std::string& token) { + DCHECK(extension_loaded_); + DCHECK(browser_context_); + DCHECK(extensions::EventRouter::Get(browser_context_)); + + scoped_ptr<extensions::Event> event(new extensions::Event( + extensions::api::copresence_private::OnEncodeTokenRequest::kEventName, + extensions::api::copresence_private::OnEncodeTokenRequest::Create(token), + browser_context_)); + + extensions::EventRouter::Get(browser_context_) + ->DispatchEventToExtension(kWhispernetProxyExtensionId, event.Pass()); +} + +// Fire an event to request a decode for the given samples. +void ChromeWhispernetClient::DecodeSamples(const std::string& samples) { + DCHECK(extension_loaded_); + DCHECK(browser_context_); + DCHECK(extensions::EventRouter::Get(browser_context_)); + + scoped_ptr<extensions::Event> event(new extensions::Event( + extensions::api::copresence_private::OnDecodeSamplesRequest::kEventName, + extensions::api::copresence_private::OnDecodeSamplesRequest::Create( + samples), + browser_context_)); + + extensions::EventRouter::Get(browser_context_) + ->DispatchEventToExtension(kWhispernetProxyExtensionId, event.Pass()); +} + +void ChromeWhispernetClient::DetectBroadcast() { + DCHECK(extension_loaded_); + DCHECK(browser_context_); + DCHECK(extensions::EventRouter::Get(browser_context_)); + + scoped_ptr<extensions::Event> event(new extensions::Event( + extensions::api::copresence_private::OnDetectBroadcastRequest::kEventName, + make_scoped_ptr(new base::ListValue()), + browser_context_)); + + extensions::EventRouter::Get(browser_context_) + ->DispatchEventToExtension(kWhispernetProxyExtensionId, event.Pass()); +} + +void ChromeWhispernetClient::RegisterTokensCallback( + const TokensCallback& tokens_callback) { + tokens_callback_ = tokens_callback; +} + +void ChromeWhispernetClient::RegisterSamplesCallback( + const SamplesCallback& samples_callback) { + samples_callback_ = samples_callback; +} + +void ChromeWhispernetClient::RegisterDetectBroadcastCallback( + const SuccessCallback& db_callback) { + db_callback_ = db_callback; +} + +ChromeWhispernetClient::TokensCallback +ChromeWhispernetClient::GetTokensCallback() { + return tokens_callback_; +} + +ChromeWhispernetClient::SamplesCallback +ChromeWhispernetClient::GetSamplesCallback() { + return samples_callback_; +} + +ChromeWhispernetClient::SuccessCallback +ChromeWhispernetClient::GetDetectBroadcastCallback() { + return db_callback_; +} + +ChromeWhispernetClient::SuccessCallback +ChromeWhispernetClient::GetInitializedCallback() { + return extension_loaded_callback_; +} + +// Private: + +// Fire an event to initialize whispernet with the given parameters. +void ChromeWhispernetClient::InitializeWhispernet( + const extensions::api::copresence_private::AudioParameters& params) { + DCHECK(browser_context_); + DCHECK(extensions::EventRouter::Get(browser_context_)); + + scoped_ptr<extensions::Event> event(new extensions::Event( + extensions::api::copresence_private::OnInitialize::kEventName, + extensions::api::copresence_private::OnInitialize::Create(params), + browser_context_)); + + extensions::EventRouter::Get(browser_context_) + ->DispatchEventToExtension(kWhispernetProxyExtensionId, event.Pass()); +} + +void ChromeWhispernetClient::OnExtensionLoaded(bool success) { + if (extension_loaded_) { + if (!init_callback_.is_null()) + init_callback_.Run(success); + return; + } + + // Our extension just got loaded, initialize whispernet. + extension_loaded_ = true; + + // This will fire another OnExtensionLoaded call once the initialization is + // done, which means we've initialized for realz, hence call the init + // callback. + + // At this point, we have the same parameters for record and play. This + // may change later though (ongoing discussion with research). + extensions::api::copresence_private::AudioParameters params; + params.play.sample_rate = copresence::kDefaultSampleRate; + params.play.bits_per_sample = copresence::kDefaultBitsPerSample; + params.play.carrier_frequency = copresence::kDefaultCarrierFrequency; + params.play.repetitions = copresence::kDefaultRepetitions; + + params.record.sample_rate = copresence::kDefaultSampleRate; + params.record.bits_per_sample = copresence::kDefaultBitsPerSample; + params.record.carrier_frequency = copresence::kDefaultCarrierFrequency; + params.record.channels = copresence::kDefaultChannels; + + InitializeWhispernet(params); +} diff --git a/chrome/browser/copresence/chrome_whispernet_client.h b/chrome/browser/copresence/chrome_whispernet_client.h new file mode 100644 index 0000000..a644a1a --- /dev/null +++ b/chrome/browser/copresence/chrome_whispernet_client.h @@ -0,0 +1,84 @@ +// Copyright 2014 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 CHROME_BROWSER_COPRESENCE_CHROME_WHISPERNET_CLIENT_H_ +#define CHROME_BROWSER_COPRESENCE_CHROME_WHISPERNET_CLIENT_H_ + +#include <string> +#include <vector> + +#include "base/callback.h" +#include "base/macros.h" +#include "components/copresence/public/whispernet_client.h" + +namespace content { +class BrowserContext; +} + +namespace extensions { +namespace api { +namespace copresence_private { +struct AudioParameters; +} +} +} + +namespace media { +class AudioBusRefCounted; +} + +// This class is responsible for communication with our ledger_proxy extension +// that talks to the whispernet audio library. +class ChromeWhispernetClient : public copresence::WhispernetClient { + public: + // The browser context needs to outlive this class. + explicit ChromeWhispernetClient(content::BrowserContext* browser_context); + virtual ~ChromeWhispernetClient(); + + // WhispernetClient overrides: + virtual void Initialize(const SuccessCallback& init_callback) OVERRIDE; + virtual void Shutdown() OVERRIDE; + + virtual void EncodeToken(const std::string& token) OVERRIDE; + virtual void DecodeSamples(const std::string& samples) OVERRIDE; + virtual void DetectBroadcast() OVERRIDE; + + virtual void RegisterTokensCallback( + const TokensCallback& tokens_callback) OVERRIDE; + virtual void RegisterSamplesCallback( + const SamplesCallback& samples_callback) OVERRIDE; + virtual void RegisterDetectBroadcastCallback( + const SuccessCallback& db_callback) OVERRIDE; + + virtual TokensCallback GetTokensCallback() OVERRIDE; + virtual SamplesCallback GetSamplesCallback() OVERRIDE; + virtual SuccessCallback GetDetectBroadcastCallback() OVERRIDE; + virtual SuccessCallback GetInitializedCallback() OVERRIDE; + + static const char kWhispernetProxyExtensionId[]; + + private: + // Fire an event to initialize whispernet with the given parameters. + void InitializeWhispernet( + const extensions::api::copresence_private::AudioParameters& params); + + // This gets called twice; once when the proxy extension loads, the second + // time when we have initialized the proxy extension's encoder and decoder. + void OnExtensionLoaded(bool success); + + content::BrowserContext* browser_context_; + + SuccessCallback extension_loaded_callback_; + SuccessCallback init_callback_; + + TokensCallback tokens_callback_; + SamplesCallback samples_callback_; + SuccessCallback db_callback_; + + bool extension_loaded_; + + DISALLOW_COPY_AND_ASSIGN(ChromeWhispernetClient); +}; + +#endif // CHROME_BROWSER_COPRESENCE_CHROME_WHISPERNET_CLIENT_H_ diff --git a/chrome/browser/copresence/chrome_whispernet_client_browsertest.cc b/chrome/browser/copresence/chrome_whispernet_client_browsertest.cc new file mode 100644 index 0000000..469a42d --- /dev/null +++ b/chrome/browser/copresence/chrome_whispernet_client_browsertest.cc @@ -0,0 +1,179 @@ +// Copyright 2014 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 "chrome/browser/copresence/chrome_whispernet_client.h" + +#include <cstdlib> +#include <string> + +#include "base/bind.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/run_loop.h" +#include "base/stl_util.h" +#include "chrome/browser/extensions/api/copresence_private/copresence_private_api.h" +#include "chrome/browser/extensions/extension_browsertest.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/test/base/in_process_browser_test.h" +#include "media/base/audio_bus.h" + +namespace { + +// Copied from src/components/copresence/mediums/audio/audio_recorder.cc +std::string AudioBusToString(scoped_refptr<media::AudioBusRefCounted> source) { + std::string buffer; + buffer.resize(source->frames() * source->channels() * sizeof(float)); + float* buffer_view = reinterpret_cast<float*>(string_as_array(&buffer)); + + const int channels = source->channels(); + for (int ch = 0; ch < channels; ++ch) { + for (int si = 0, di = ch; si < source->frames(); ++si, di += channels) + buffer_view[di] = source->channel(ch)[si]; + } + + return buffer; +} + +} // namespace + +class ChromeWhispernetClientTest : public ExtensionBrowserTest { + public: + ChromeWhispernetClientTest() : initialized_(false) {} + + virtual ~ChromeWhispernetClientTest() { + if (client_) + extensions::SetWhispernetClientForTesting(NULL); + } + + void InitializeWhispernet() { + run_loop_.reset(new base::RunLoop()); + client_.reset(new ChromeWhispernetClient(browser()->profile())); + extensions::SetWhispernetClientForTesting(client_.get()); + + client_->Initialize(base::Bind(&ChromeWhispernetClientTest::InitCallback, + base::Unretained(this))); + run_loop_->Run(); + + EXPECT_TRUE(initialized_); + } + + void EncodeTokenAndSaveSamples() { + ASSERT_TRUE(client_); + + // This is the base64 encoding for 000000. + const std::string kZeroToken = "MDAwMDAw"; + + run_loop_.reset(new base::RunLoop()); + client_->RegisterSamplesCallback(base::Bind( + &ChromeWhispernetClientTest::SamplesCallback, base::Unretained(this))); + expected_token_ = kZeroToken; + + client_->EncodeToken(kZeroToken); + run_loop_->Run(); + + EXPECT_GT(saved_samples_->frames(), 0); + } + + void DecodeSamplesAndVerifyToken() { + ASSERT_TRUE(client_); + + const std::string kZeroToken = "MDAwMDAw"; + + run_loop_.reset(new base::RunLoop()); + client_->RegisterTokensCallback(base::Bind( + &ChromeWhispernetClientTest::TokensCallback, base::Unretained(this))); + expected_token_ = kZeroToken; + + ASSERT_GT(saved_samples_->frames(), 0); + + // Convert our single channel samples to two channel. Decode samples + // expects 2 channel data. + scoped_refptr<media::AudioBusRefCounted> samples_bus = + media::AudioBusRefCounted::Create(2, saved_samples_->frames()); + memcpy(samples_bus->channel(0), + saved_samples_->channel(0), + sizeof(float) * saved_samples_->frames()); + memcpy(samples_bus->channel(1), + saved_samples_->channel(0), + sizeof(float) * saved_samples_->frames()); + + client_->DecodeSamples(AudioBusToString(samples_bus)); + run_loop_->Run(); + } + + void DetectBroadcast() { + ASSERT_TRUE(client_); + + run_loop_.reset(new base::RunLoop()); + client_->RegisterDetectBroadcastCallback( + base::Bind(&ChromeWhispernetClientTest::DetectBroadcastCallback, + base::Unretained(this))); + client_->DetectBroadcast(); + run_loop_->Run(); + } + + protected: + void InitCallback(bool success) { + EXPECT_TRUE(success); + initialized_ = true; + ASSERT_TRUE(run_loop_); + run_loop_->Quit(); + } + + void SamplesCallback( + const std::string& token, + const scoped_refptr<media::AudioBusRefCounted>& samples) { + EXPECT_EQ(expected_token_, token); + saved_samples_ = samples; + ASSERT_TRUE(run_loop_); + run_loop_->Quit(); + } + + void TokensCallback(const std::vector<std::string>& tokens) { + ASSERT_TRUE(run_loop_); + run_loop_->Quit(); + + EXPECT_EQ(expected_token_, tokens[0]); + } + + void DetectBroadcastCallback(bool success) { + EXPECT_TRUE(success); + ASSERT_TRUE(run_loop_); + run_loop_->Quit(); + } + + private: + scoped_ptr<base::RunLoop> run_loop_; + scoped_ptr<ChromeWhispernetClient> client_; + + std::string expected_token_; + scoped_refptr<media::AudioBusRefCounted> saved_samples_; + + bool initialized_; + + DISALLOW_COPY_AND_ASSIGN(ChromeWhispernetClientTest); +}; + +IN_PROC_BROWSER_TEST_F(ChromeWhispernetClientTest, Initialize) { + InitializeWhispernet(); +} + +IN_PROC_BROWSER_TEST_F(ChromeWhispernetClientTest, EncodeToken) { + InitializeWhispernet(); + EncodeTokenAndSaveSamples(); +} + +IN_PROC_BROWSER_TEST_F(ChromeWhispernetClientTest, DecodeSamples) { + InitializeWhispernet(); + EncodeTokenAndSaveSamples(); + DecodeSamplesAndVerifyToken(); +} + +IN_PROC_BROWSER_TEST_F(ChromeWhispernetClientTest, DetectBroadcast) { + InitializeWhispernet(); + EncodeTokenAndSaveSamples(); + DecodeSamplesAndVerifyToken(); + DetectBroadcast(); +} diff --git a/chrome/browser/extensions/api/copresence_private/copresence_private_api.cc b/chrome/browser/extensions/api/copresence_private/copresence_private_api.cc new file mode 100644 index 0000000..2dcc52e --- /dev/null +++ b/chrome/browser/extensions/api/copresence_private/copresence_private_api.cc @@ -0,0 +1,105 @@ +// Copyright 2014 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 "chrome/browser/extensions/api/copresence_private/copresence_private_api.h" + +#include "base/lazy_instance.h" +#include "base/stl_util.h" +#include "chrome/browser/copresence/chrome_whispernet_client.h" +#include "chrome/common/extensions/api/copresence_private.h" +#include "components/copresence/public/whispernet_client.h" +#include "media/base/audio_bus.h" + +namespace extensions { + +// This code is only for testing while we don't have the rest of the +// CopresenceAPI service which will actually give us the whispernet client. +// Once we add that code, both the g_whispernet_client and the +// GetWhispernetClient function will go away, to be replaced by the +// GetWhispernetClient function that will fetch our active whispernet client +// from the CopresenceAPI profile keyed service. +copresence::WhispernetClient* g_whispernet_client = NULL; + +// Copresence Private functions. + +copresence::WhispernetClient* CopresencePrivateFunction::GetWhispernetClient() { + // This is temporary code, this needs to be replaced by the real + // GetWhispernetClient code from c/b/e/api/copresence/copresence_util.h + DCHECK(g_whispernet_client); + return g_whispernet_client; +} + +// CopresenceSendFoundFunction implementation: +ExtensionFunction::ResponseAction CopresencePrivateSendFoundFunction::Run() { + if (!GetWhispernetClient() || + GetWhispernetClient()->GetTokensCallback().is_null()) { + return RespondNow(NoArguments()); + } + + scoped_ptr<api::copresence_private::SendFound::Params> params( + api::copresence_private::SendFound::Params::Create(*args_)); + EXTENSION_FUNCTION_VALIDATE(params.get()); + GetWhispernetClient()->GetTokensCallback().Run(params->tokens); + return RespondNow(NoArguments()); +} + +// CopresenceSendEncodedFunction implementation: +ExtensionFunction::ResponseAction CopresencePrivateSendSamplesFunction::Run() { + if (!GetWhispernetClient() || + GetWhispernetClient()->GetSamplesCallback().is_null()) { + return RespondNow(NoArguments()); + } + + scoped_ptr<api::copresence_private::SendSamples::Params> params( + api::copresence_private::SendSamples::Params::Create(*args_)); + EXTENSION_FUNCTION_VALIDATE(params.get()); + + scoped_refptr<media::AudioBusRefCounted> samples = + media::AudioBusRefCounted::Create(1, + params->samples.size() / sizeof(float)); + + memcpy(samples->channel(0), + string_as_array(¶ms->samples), + params->samples.size()); + + GetWhispernetClient()->GetSamplesCallback().Run(params->token, samples); + return RespondNow(NoArguments()); +} + +// CopresenceSendDetectFunction implementation: +ExtensionFunction::ResponseAction CopresencePrivateSendDetectFunction::Run() { + if (!GetWhispernetClient() || + GetWhispernetClient()->GetDetectBroadcastCallback().is_null()) { + return RespondNow(NoArguments()); + } + + scoped_ptr<api::copresence_private::SendDetect::Params> params( + api::copresence_private::SendDetect::Params::Create(*args_)); + EXTENSION_FUNCTION_VALIDATE(params.get()); + + GetWhispernetClient()->GetDetectBroadcastCallback().Run(params->detected); + return RespondNow(NoArguments()); +} + +// CopresenceSendInitializedFunction implementation: +ExtensionFunction::ResponseAction +CopresencePrivateSendInitializedFunction::Run() { + if (!GetWhispernetClient() || + GetWhispernetClient()->GetInitializedCallback().is_null()) { + return RespondNow(NoArguments()); + } + + scoped_ptr<api::copresence_private::SendInitialized::Params> params( + api::copresence_private::SendInitialized::Params::Create(*args_)); + EXTENSION_FUNCTION_VALIDATE(params.get()); + + GetWhispernetClient()->GetInitializedCallback().Run(params->success); + return RespondNow(NoArguments()); +} + +void SetWhispernetClientForTesting(copresence::WhispernetClient* client) { + g_whispernet_client = client; +} + +} // namespace extensions diff --git a/chrome/browser/extensions/api/copresence_private/copresence_private_api.h b/chrome/browser/extensions/api/copresence_private/copresence_private_api.h new file mode 100644 index 0000000..2eb0265 --- /dev/null +++ b/chrome/browser/extensions/api/copresence_private/copresence_private_api.h @@ -0,0 +1,69 @@ +// Copyright 2014 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 CHROME_BROWSER_EXTENSIONS_API_COPRESENCE_PRIVATE_COPRESENCE_PRIVATE_API_H_ +#define CHROME_BROWSER_EXTENSIONS_API_COPRESENCE_PRIVATE_COPRESENCE_PRIVATE_API_H_ + +#include "chrome/browser/extensions/chrome_extension_function.h" + +namespace copresence { +class WhispernetClient; +} + +namespace extensions { + +class CopresencePrivateFunction : public ChromeUIThreadExtensionFunction { + protected: + copresence::WhispernetClient* GetWhispernetClient(); + virtual ~CopresencePrivateFunction() {} +}; + +class CopresencePrivateSendFoundFunction : public CopresencePrivateFunction { + public: + DECLARE_EXTENSION_FUNCTION("copresencePrivate.sendFound", + COPRESENCEPRIVATE_SENDFOUND); + + protected: + virtual ~CopresencePrivateSendFoundFunction() {} + virtual ExtensionFunction::ResponseAction Run() OVERRIDE; +}; + +class CopresencePrivateSendSamplesFunction : public CopresencePrivateFunction { + public: + DECLARE_EXTENSION_FUNCTION("copresencePrivate.sendSamples", + COPRESENCEPRIVATE_SENDSAMPLES); + + protected: + virtual ~CopresencePrivateSendSamplesFunction() {} + virtual ExtensionFunction::ResponseAction Run() OVERRIDE; +}; + +class CopresencePrivateSendDetectFunction : public CopresencePrivateFunction { + public: + DECLARE_EXTENSION_FUNCTION("copresencePrivate.sendDetect", + COPRESENCEPRIVATE_SENDDETECT); + + protected: + virtual ~CopresencePrivateSendDetectFunction() {} + virtual ExtensionFunction::ResponseAction Run() OVERRIDE; +}; + +class CopresencePrivateSendInitializedFunction + : public CopresencePrivateFunction { + public: + DECLARE_EXTENSION_FUNCTION("copresencePrivate.sendInitialized", + COPRESENCEPRIVATE_SENDINITIALIZED); + + protected: + virtual ~CopresencePrivateSendInitializedFunction() {} + virtual ExtensionFunction::ResponseAction Run() OVERRIDE; +}; + +// This will go away once we check in the code for the CopresenceAPI BCK +// service which lets us inject a whispernet client. +void SetWhispernetClientForTesting(copresence::WhispernetClient* client); + +} // namespace extensions + +#endif // CHROME_BROWSER_EXTENSIONS_API_COPRESENCE_PRIVATE_COPRESENCE_PRIVATE_API_H_ diff --git a/chrome/browser/resources/component_extension_resources.grd b/chrome/browser/resources/component_extension_resources.grd index 8c6d093d..c2608f2 100644 --- a/chrome/browser/resources/component_extension_resources.grd +++ b/chrome/browser/resources/component_extension_resources.grd @@ -172,6 +172,17 @@ <include name="IDR_LEDGER_LEDGER_PROXY_NMF" file="../extensions/api/ledger/ledger_proxy/ledger_proxy.nmf.js" type="BINDATA" /> <include name="IDR_LEDGER_LEDGER_PROXY_PEXE" file="../extensions/api/ledger/ledger_proxy/ledger_proxy_pnacl.pexe.js" type="BINDATA" /> </if> + <include name="IDR_WHISPERNET_PROXY_BACKGROUND_HTML" file="whispernet_proxy/background.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" /> + <include name="IDR_WHISPERNET_PROXY_INIT_JS" file="whispernet_proxy/js/init.js" type="BINDATA" /> + <include name="IDR_WHISPERNET_PROXY_NACL_JS" file="whispernet_proxy/js/nacl.js" type="BINDATA" /> + <include name="IDR_WHISPERNET_PROXY_WRAPPER_JS" file="whispernet_proxy/js/wrapper.js" type="BINDATA" /> + <!-- The next two files have a .png extension since grit for some reason + won't compile these files in if they are named .nmf/.pexe or + anything remotely similar to their actual extension. Naming these + .js or .html breaks all kinds of presubmit checks, hence .png seems + to be the only viable option at the moment. --> + <include name="IDR_WHISPERNET_PROXY_WHISPERNET_PROXY_PROXY_NMF" file="whispernet_proxy/whispernet_proxy.nmf.png" type="BINDATA" /> + <include name="IDR_WHISPERNET_PROXY_WHISPERNET_PROXY_PROXY_PEXE" file="whispernet_proxy/whispernet_proxy_pnacl.pexe.png" type="BINDATA" /> </includes> </release> </grit> diff --git a/chrome/browser/resources/whispernet_proxy/OWNERS b/chrome/browser/resources/whispernet_proxy/OWNERS new file mode 100644 index 0000000..2bc8d07 --- /dev/null +++ b/chrome/browser/resources/whispernet_proxy/OWNERS @@ -0,0 +1,3 @@ +rkc@chromium.org +ckehoe@chromium.org +xiyuan@chromium.org diff --git a/chrome/browser/resources/whispernet_proxy/background.html b/chrome/browser/resources/whispernet_proxy/background.html new file mode 100644 index 0000000..06a8476 --- /dev/null +++ b/chrome/browser/resources/whispernet_proxy/background.html @@ -0,0 +1,11 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<script src="js/nacl.js"></script> +<script src="js/wrapper.js"></script> +<script src="js/init.js"></script> +</head> +<body> +</body> +</html> diff --git a/chrome/browser/resources/whispernet_proxy/js/init.js b/chrome/browser/resources/whispernet_proxy/js/init.js new file mode 100644 index 0000000..e346f206 --- /dev/null +++ b/chrome/browser/resources/whispernet_proxy/js/init.js @@ -0,0 +1,104 @@ +// Copyright 2014 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. + +'use strict'; + +// Globals holding our encoder and decoder. We will never have more than one +// Global variable that will be used to access this Nacl bridge. +var whispernetNacl = null; + +// copy of an encoder or a decoder at a time. +var whisperEncoder = null; +var whisperDecoder = null; + +/** + * Initialize the whispernet encoder and decoder. + * @param {Object} audioParams Audio parameters used to initialize the encoder + * and decoder. + */ +function initialize(audioParams) { + if (!whispernetNacl) { + chrome.copresencePrivate.sendInitialized(false); + return; + } + + console.log('init: creating encoder!'); + whisperEncoder = new WhisperEncoder(audioParams.play, whispernetNacl); + whisperEncoder.setAudioDataCallback(chrome.copresencePrivate.sendSamples); + + console.log('init: creating decoder!'); + whisperDecoder = new WhisperDecoder(audioParams.record, whispernetNacl); + whisperDecoder.setReceiveCallback(chrome.copresencePrivate.sendFound); + whisperDecoder.onDetectBroadcast(chrome.copresencePrivate.sendDetect); + + chrome.copresencePrivate.sendInitialized(true); +} + +/** + * Sends a request to whispernet to encode a token. + * @param {string} token Token to encode. This needs to be a base64 string. + */ +function encodeTokenRequest(token) { + if (whisperEncoder) { + whisperEncoder.encode(atob(token), true); + } else { + console.error('encodeTokenRequest: Whisper not initialized!'); + } +} + +/** + * Sends a request to whispernet to decode samples. + * @param {ArrayBuffer} samples Array of samples to decode. + */ +function decodeSamplesRequest(samples) { + if (whisperDecoder) { + whisperDecoder.processSamples(samples); + } else { + console.error('decodeSamplesRequest: Whisper not initialized!'); + } +} + +/** + * Sends a request to whispernet to detect broadcast. + */ +function detectBroadcastRequest() { + if (whisperDecoder) { + whisperDecoder.detectBroadcast(); + } else { + console.error('detectBroadcastRequest: Whisper not initialized!'); + } +} + +/** + * Initialize our listerners and signal that the extension is loaded. + */ +function onWhispernetLoaded() { + console.log('init: Nacl ready!'); + + // Setup all the listeners for the private API. + chrome.copresencePrivate.onInitialize.addListener(initialize); + chrome.copresencePrivate.onEncodeTokenRequest.addListener(encodeTokenRequest); + chrome.copresencePrivate.onDecodeSamplesRequest.addListener( + decodeSamplesRequest); + chrome.copresencePrivate.onDetectBroadcastRequest.addListener( + detectBroadcastRequest); + + // This first initialized is sent to indicate that the library is loaded. + // Every other time, it will be sent only when Chrome wants to reinitialize + // the encoder and decoder. + chrome.copresencePrivate.sendInitialized(true); +} + +/** + * Initialize the whispernet Nacl bridge. + */ +function initWhispernet() { + console.log('init: Starting Nacl bridge.'); + // TODO(rkc): Figure out how to embed the .nmf and the .pexe into component + // resources without having to rename them to .js. + whispernetNacl = new NaclBridge('whispernet_proxy.nmf.png', + onWhispernetLoaded); +} + +window.addEventListener('DOMContentLoaded', initWhispernet); diff --git a/chrome/browser/resources/whispernet_proxy/js/nacl.js b/chrome/browser/resources/whispernet_proxy/js/nacl.js new file mode 100644 index 0000000..6505728 --- /dev/null +++ b/chrome/browser/resources/whispernet_proxy/js/nacl.js @@ -0,0 +1,102 @@ +// Copyright 2014 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. + +'use strict'; + +/** + * Constructor for the Nacl bridge to the whispernet wrapper. + * @param {string} nmf The relative path to the nmf containing the location of + * the whispernet Nacl wrapper. + * @param {function()} readyCallback Callback to be called once we've loaded the + * whispernet wrapper. + */ +function NaclBridge(nmf, readyCallback) { + this.readyCallback_ = readyCallback; + this.callbacks_ = []; + this.isEnabled_ = false; + this.naclId_ = this.loadNacl_(nmf); +} + +/** + * Method to send generic byte data to the whispernet wrapper. + * @param {string} data Raw data to send to the whispernet wrapper. + */ +NaclBridge.prototype.send = function(data) { + if (this.isEnabled_) { + this.embed_.postMessage(data); + } else { + console.error('Whisper Nacl Bridge not initialized!'); + } +}; + +/** + * Method to add a listener to Nacl messages received by this bridge. + * @param {function(Event)} messageCallback Callback to receive the messsage. + */ +NaclBridge.prototype.addListener = function(messageCallback) { + this.callbacks_.push(messageCallback); +}; + +/** + * Method that receives Nacl messages and forwards them to registered + * callbacks. + * @param {Event} e Event from the whispernet wrapper. + * @private + */ +NaclBridge.prototype.onMessage_ = function(e) { + if (this.isEnabled_) { + this.callbacks_.forEach(function(callback) { + callback(e); + }); + } +}; + +/** + * Injects the <embed> for this nacl manifest URL, generating a unique ID. + * @param {string} manifestUrl Url to the nacl manifest to load. + * @return {number} generated ID. + * @private + */ +NaclBridge.prototype.loadNacl_ = function(manifestUrl) { + var id = 'nacl-' + Math.floor(Math.random() * 10000); + this.embed_ = document.createElement('embed'); + this.embed_.name = 'nacl_module'; + this.embed_.width = 1; + this.embed_.height = 1; + this.embed_.src = manifestUrl; + this.embed_.id = id; + this.embed_.type = 'application/x-pnacl'; + + // Wait for the element to load and callback. + this.embed_.addEventListener('load', this.onNaclReady_.bind(this)); + this.embed_.addEventListener('error', this.onNaclError_.bind(this)); + + // Inject the embed string into the page. + document.body.appendChild(this.embed_); + + // Listen for messages from the NaCl module. + window.addEventListener('message', this.onMessage_.bind(this), true); + return id; +}; + +/** + * Callback that is called when the Whispernet wrapper is loaded and forward + * that status to the callback registered with us in the constructor. + * @private + */ +NaclBridge.prototype.onNaclReady_ = function() { + if (this.readyCallback_) + this.readyCallback_(); + this.isEnabled_ = true; +}; + +/** + * Callback that handles Nacl errors. + * @param {string} msg Error string. + * @private + */ +NaclBridge.prototype.onNaclError_ = function(msg) { + // TODO(rkc): Handle error from NaCl better. + console.error('NaCl error', msg); +}; diff --git a/chrome/browser/resources/whispernet_proxy/js/wrapper.js b/chrome/browser/resources/whispernet_proxy/js/wrapper.js new file mode 100644 index 0000000..3613fd9 --- /dev/null +++ b/chrome/browser/resources/whispernet_proxy/js/wrapper.js @@ -0,0 +1,224 @@ +// Copyright 2014 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. + +'use strict'; + +/** + * Function to convert an array of bytes to a base64 string + * TODO(rkc): Change this to use a Uint8array instead of a string. + * @param {string} bytes String containing the bytes we want to convert. + * @return {string} String containing the base64 representation. + */ +function bytesToBase64(bytes) { + var bstr = ''; + for (var i = 0; i < bytes.length; ++i) + bstr += String.fromCharCode(bytes[i]); + return btoa(bstr); +} + +/** + * Function to convert a string to an array of bytes. + * @param {string} str String to convert. + * @return {Array} Array containing the string. + */ +function stringToArray(str) { + var buffer = []; + for (var i = 0; i < str.length; ++i) + buffer[i] = str.charCodeAt(i); + return buffer; +} + +/** + * Creates a whispernet encoder. + * @constructor + * @param {Object} params Dictionary of parameters used to initialize the + * whispernet encoder. + * @param {Object} whisperNacl The NaclBridge object to use to communicate with + * the whispernet wrapper. + */ +function WhisperEncoder(params, whisperNacl) { + params = params || {}; + this.repetitions_ = params.repetitions || 3; + + this.whisperNacl_ = whisperNacl; + this.whisperNacl_.addListener(this.onNaclMessage_.bind(this)); + + var symbolCoder = {}; + symbolCoder.sample_rate = params.sampleRate || 48000.0; + symbolCoder.upsampling_factor = params.bitsPerSample || 16; + symbolCoder.desired_carrier_frequency = params.carrierFrequency || 18500.0; + symbolCoder.bits_per_symbol = 4; + symbolCoder.min_cycles_per_frame = 4; + symbolCoder.baseband_decimation_factor = 4; + + var msg = { + type: 'initialize_encoder', + symbol_coder: symbolCoder, + encoder_params: { + bytes_per_token: 6, + include_parity_symbol: true, + single_sideband: true + } + }; + this.whisperNacl_.send(JSON.stringify(msg)); +} + +/** + * Method to encode a token. + * @param {string} token Token to encode. + * @param {boolean} raw Whether we should return the encoded samples in raw + * format or as a Wave file. + */ +WhisperEncoder.prototype.encode = function(token, raw) { + var msg = { + type: 'encode_token', + // Trying to send the token in binary form to Nacl doesn't work correctly. + // We end up with the correct string + a bunch of extra characters. This is + // true of returning a binary string too; hence we communicate back and + // forth by converting the bytes into an array of integers. + token: stringToArray(token), + repetitions: this.repetitions_, + return_raw_samples: raw + }; + this.whisperNacl_.send(JSON.stringify(msg)); +}; + +/** + * Method to set the callback for encoded audio data received from the encoder + * when we finish encoding a token. + * @param {function(string, ArrayBuffer)} callback Callback which will receive + * the audio samples. + */ +WhisperEncoder.prototype.setAudioDataCallback = function(callback) { + this.audioDataCallback_ = callback; +}; + +/** + * Method to handle messages from the whispernet NaCl wrapper. + * @param {Event} e Event from the whispernet wrapper. + * @private + */ +WhisperEncoder.prototype.onNaclMessage_ = function(e) { + var msg = e.data; + if (msg.type == 'encode_token_response') { + this.audioDataCallback_(bytesToBase64(msg.token), msg.samples); + } +}; + +/** + * Creates a whispernet decoder. + * @constructor + * @param {Object} params Dictionary of parameters used to initialize the + * whispernet decoder. + * @param {Object} whisperNacl The NaclBridge object to use to communicate with + * the whispernet wrapper. + */ +function WhisperDecoder(params, whisperNacl) { + params = params || {}; + + this.whisperNacl_ = whisperNacl; + this.whisperNacl_.addListener(this.onNaclMessage_.bind(this)); + + var msg = { + type: 'initialize_decoder', + num_channels: params.channels, + symbol_coder: { + sample_rate: params.sampleRate || 48000.0, + upsampling_factor: params.bitsPerSample || 16, + desired_carrier_frequency: params.carrierFrequency || 18500.0, + bits_per_symbol: 4, + min_cycles_per_frame: 4, + baseband_decimation_factor: 4 + }, + decoder_params: { + bytes_per_token: 6, + include_parity_symbol: true, + max_candidates: 1, + broadcaster_stopped_threshold_in_seconds: 10 + }, + acquisition_params: { + max_buffer_duration_in_seconds: 3 + } + }; + this.whisperNacl_.send(JSON.stringify(msg)); +} + +/** + * Method to request the decoder to wipe its internal buffer. + */ +WhisperDecoder.prototype.wipeDecoder = function() { + var msg = { + type: 'wipe_decode_buffer' + }; + this.whisperNacl_.send(JSON.stringify(msg)); +}; + +/** + * Method to request the decoder to detect a broadcast. + */ +WhisperDecoder.prototype.detectBroadcast = function() { + var msg = { + type: 'detect_broadcast' + }; + this.whisperNacl_.send(JSON.stringify(msg)); +}; + +/** + * Method to request the decoder to process samples. + * @param {ArrayBuffer} samples Array of samples to process. + */ +WhisperDecoder.prototype.processSamples = function(samples) { + // For sample processing, the Nacl module doesn't expect any frills in the + // message, just send the samples directly. + this.whisperNacl_.send(samples); +}; + +/** + * Method to set the callback for decoded tokens received from the decoder. + * @param {function(!Array.string)} callback Callback to receive the list of + * decoded tokens. + */ +WhisperDecoder.prototype.setReceiveCallback = function(callback) { + this.tokenCallback_ = callback; +}; + +/** + * Method to set the callback for receiving the detect callback status received + * from the decoder. + * @param {function()} callback Callback to set to receive the detect broadcast + * status. + */ +WhisperDecoder.prototype.onDetectBroadcast = function(callback) { + this.detectBroadcastCallback_ = callback; +}; + +/** + * Method to handle messages from the whispernet NaCl wrapper. + * @param {Event} e Event from the whispernet wrapper. + * @private + */ +WhisperDecoder.prototype.onNaclMessage_ = function(e) { + var msg = e.data; + if (msg.type == 'decode_tokens_response') { + this.handleCandidates_(JSON.parse(msg.tokens)); + } else if (msg.type == 'detect_broadcast_response') { + this.detectBroadcastCallback_(msg.detected); + } +}; + +/** + * Method to receive tokens from the decoder and process and forward them to the + * token callback registered with us. + * @param {!Array.string} candidates Array of token candidates. + * @private + */ +WhisperDecoder.prototype.handleCandidates_ = function(candidates) { + if (!this.tokenCallback_ || !candidates || candidates.length == 0) + return; + + var returnCandidates = []; + for (var i = 0; i < candidates.length; ++i) + returnCandidates[i] = bytesToBase64(candidates[i]); + this.tokenCallback_(returnCandidates); +}; diff --git a/chrome/browser/resources/whispernet_proxy/manifest.json b/chrome/browser/resources/whispernet_proxy/manifest.json new file mode 100644 index 0000000..6b15ef9 --- /dev/null +++ b/chrome/browser/resources/whispernet_proxy/manifest.json @@ -0,0 +1,13 @@ +{ + // chrome-extension://bpfmnplchembfbdgieamdodgaencleal/ + "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxY7MIAZJdh8y5zmU3kRtxMtOWwHl/Yu+c700YQncON0OaqNLcWD/vrPBsMW/4SkkHIBtJDUmLAzPfXyUtbxYo3Vi3E7xJ028BN0Z+KMhPMvFYnHiS4NlFTRDHvQbWi7P+4bvOvS8RZe1VBgSQMhaJSsa6zzE+UNRPsiWNOaX2O08N3oyxo1Z2SU8Q9eE283NOgY8fVg8TJFVqGHPmbcblpM2A+dk7YFBIT1dFemRxJFW2DdLoCXojrNWXqzYstj/muuefRTgnNmwlCjCeDyK6R6x71GRTUnkVOiBgETgsTfOJRvKQCy65/EENZdX/u4e1LM1PWq0ePJ+xMwEXfuHtQIDAQAB", + "name": "Whispernet Proxy", + "version": "1.0", + "manifest_version": 2, + "description": "Proxy whispernet calls.", + "permissions": [ "copresencePrivate" ], + "background": { + "page": "background.html", + "persistent": true + } +} diff --git a/chrome/chrome_browser_extensions.gypi b/chrome/chrome_browser_extensions.gypi index ed1ce27..58098be 100644 --- a/chrome/chrome_browser_extensions.gypi +++ b/chrome/chrome_browser_extensions.gypi @@ -353,6 +353,8 @@ 'browser/apps/shortcut_manager.h', 'browser/apps/shortcut_manager_factory.cc', 'browser/apps/shortcut_manager_factory.h', + 'browser/copresence/chrome_whispernet_client.cc', + 'browser/copresence/chrome_whispernet_client.h', 'browser/extensions/active_script_controller.cc', 'browser/extensions/active_script_controller.h', 'browser/extensions/active_tab_permission_granter.cc', @@ -483,6 +485,8 @@ 'browser/extensions/api/cookies/cookies_api_constants.h', 'browser/extensions/api/cookies/cookies_helpers.cc', 'browser/extensions/api/cookies/cookies_helpers.h', + 'browser/extensions/api/copresence_private/copresence_private_api.cc', + 'browser/extensions/api/copresence_private/copresence_private_api.h', 'browser/extensions/api/debugger/debugger_api.cc', 'browser/extensions/api/debugger/debugger_api.h', 'browser/extensions/api/debugger/debugger_api_constants.cc', diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 37e8e82..3f30b4e 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -1026,6 +1026,7 @@ 'browser/chromeos/ui/idle_logout_dialog_view_browsertest.cc', 'browser/collected_cookies_browsertest.cc', 'browser/content_settings/content_settings_browsertest.cc', + 'browser/copresence/chrome_whispernet_client_browsertest.cc', 'browser/crash_recovery_browsertest.cc', 'browser/custom_handlers/protocol_handler_registry_browsertest.cc', 'browser/devtools/device/adb/adb_client_socket_browsertest.cc', diff --git a/chrome/common/extensions/api/_api_features.json b/chrome/common/extensions/api/_api_features.json index 0280fca..cd7b197 100644 --- a/chrome/common/extensions/api/_api_features.json +++ b/chrome/common/extensions/api/_api_features.json @@ -230,6 +230,10 @@ "dependencies": ["permission:cookies"], "contexts": ["blessed_extension"] }, + "copresencePrivate": { + "dependencies": ["permission:copresencePrivate"], + "contexts": ["blessed_extension"] + }, "debugger": { "dependencies": ["permission:debugger"], "contexts": ["blessed_extension"] diff --git a/chrome/common/extensions/api/_permission_features.json b/chrome/common/extensions/api/_permission_features.json index a3e8662..b313858 100644 --- a/chrome/common/extensions/api/_permission_features.json +++ b/chrome/common/extensions/api/_permission_features.json @@ -313,6 +313,14 @@ "channel": "stable", "extension_types": ["extension", "legacy_packaged_app"] }, + "copresencePrivate": { + "channel": "stable", + "extension_types": ["extension"], + "location": "component", + "whitelist": [ + "AFA728615D3A82D4017BDECEE86978543194D198" // Whispernet Proxy + ] + }, "diagnostics": [ { "channel": "dev", diff --git a/chrome/common/extensions/api/api.gyp b/chrome/common/extensions/api/api.gyp index d995e1a..522aba2 100644 --- a/chrome/common/extensions/api/api.gyp +++ b/chrome/common/extensions/api/api.gyp @@ -43,6 +43,7 @@ 'context_menus_internal.json', 'context_menus.json', 'cookies.json', + 'copresence_private.idl', 'debugger.json', 'desktop_capture.json', 'developer_private.idl', diff --git a/chrome/common/extensions/api/copresence_private.idl b/chrome/common/extensions/api/copresence_private.idl new file mode 100644 index 0000000..1f9634a --- /dev/null +++ b/chrome/common/extensions/api/copresence_private.idl @@ -0,0 +1,49 @@ +// Copyright 2014 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. + +// Use the <code>chrome.copresencePrivate</code> API to interface with Chrome +// from the whispernet_proxy extension. +namespace copresencePrivate { + + dictionary PlayParameters { + double sampleRate; + long bitsPerSample; + double carrierFrequency; + long repetitions; + }; + + dictionary RecordParameters { + double sampleRate; + long bitsPerSample; + long channels; + double carrierFrequency; + }; + + dictionary AudioParameters { + PlayParameters play; + RecordParameters record; + }; + + interface Functions { + // Send a boolean indicating whether our initialization was successful. + static void sendInitialized(boolean success); + // Sends an array of found tokens to Chrome. + static void sendFound(DOMString[] tokens); + // Send an array buffer of samples encoded for the specified token. + static void sendSamples(DOMString token, ArrayBuffer samples); + // Send a boolean indicating whether we detected a broadcast or not. + static void sendDetect(boolean detected); + }; + + interface Events { + // Fired to request initialization of the whisper.net library. + static void onInitialize(AudioParameters audioParams); + // Fired to request encoding of the given token. + static void onEncodeTokenRequest(DOMString base64Token); + // Fired when we have new samples to decode. + static void onDecodeSamplesRequest(ArrayBuffer audioSamples); + // Fired to request a DetectBroadcast. + static void onDetectBroadcastRequest(); + }; +}; diff --git a/chrome/common/extensions/permissions/chrome_api_permissions.cc b/chrome/common/extensions/permissions/chrome_api_permissions.cc index 64258b8..81a3346 100644 --- a/chrome/common/extensions/permissions/chrome_api_permissions.cc +++ b/chrome/common/extensions/permissions/chrome_api_permissions.cc @@ -109,6 +109,7 @@ std::vector<APIPermissionInfo*> ChromeAPIPermissions::GetAllPermissions() PermissionMessage::kContentSettings}, {APIPermission::kContextMenus, "contextMenus"}, {APIPermission::kCookie, "cookies"}, + {APIPermission::kCopresencePrivate, "copresencePrivate"}, {APIPermission::kEnterprisePlatformKeys, "enterprise.platformKeys"}, {APIPermission::kFileBrowserHandler, "fileBrowserHandler", APIPermissionInfo::kFlagCannotBeOptional}, diff --git a/chrome/common/extensions/permissions/permission_set_unittest.cc b/chrome/common/extensions/permissions/permission_set_unittest.cc index 3c5a3c5..908d597 100644 --- a/chrome/common/extensions/permissions/permission_set_unittest.cc +++ b/chrome/common/extensions/permissions/permission_set_unittest.cc @@ -644,6 +644,7 @@ TEST(PermissionsTest, PermissionMessages) { skip.insert(APIPermission::kBrowsingData); skip.insert(APIPermission::kCastStreaming); skip.insert(APIPermission::kContextMenus); + skip.insert(APIPermission::kCopresencePrivate); skip.insert(APIPermission::kDiagnostics); skip.insert(APIPermission::kDns); skip.insert(APIPermission::kDownloadsShelf); |