mirror of
https://github.com/electron/electron.git
synced 2026-02-19 03:14:51 -05:00
Compare commits
124 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b763d81d79 | ||
|
|
7521454161 | ||
|
|
36f0583077 | ||
|
|
96391dbb3b | ||
|
|
d5cf2e1244 | ||
|
|
9a665eb5fb | ||
|
|
6b16695ba3 | ||
|
|
ac714a1d02 | ||
|
|
e5e94fce02 | ||
|
|
f65a7983c6 | ||
|
|
d1f5d20098 | ||
|
|
2f67cd5b35 | ||
|
|
3fd0ac6d44 | ||
|
|
4a6cad7ba5 | ||
|
|
eccede0c0d | ||
|
|
9ed83e7512 | ||
|
|
b3ed83055c | ||
|
|
2360f3eb11 | ||
|
|
4175e947bb | ||
|
|
1cbcd05ab7 | ||
|
|
40c7e767ef | ||
|
|
66c5a8362a | ||
|
|
d6ba1421fa | ||
|
|
ccad8ec125 | ||
|
|
d2bff97199 | ||
|
|
46a1ce8117 | ||
|
|
042f24c5b6 | ||
|
|
c3624116ae | ||
|
|
492397b815 | ||
|
|
3a0b72e5dc | ||
|
|
914939c793 | ||
|
|
b8874913f2 | ||
|
|
dc959414a3 | ||
|
|
bcdc4435b4 | ||
|
|
303da32dd3 | ||
|
|
8fd91cc35b | ||
|
|
3001c76483 | ||
|
|
28a9963ca7 | ||
|
|
b5f290d8d2 | ||
|
|
4574a21ed3 | ||
|
|
7aaaa24da7 | ||
|
|
ded4a94a92 | ||
|
|
c6fd15e641 | ||
|
|
36965a6b4e | ||
|
|
53c3e01af6 | ||
|
|
d2571f3a9e | ||
|
|
e1a8050eab | ||
|
|
7b1f5a9cea | ||
|
|
5b9393c173 | ||
|
|
98180568f2 | ||
|
|
f005ac8d8b | ||
|
|
a0d824ccf5 | ||
|
|
ee1529587c | ||
|
|
5e665c1d38 | ||
|
|
61d1df2b43 | ||
|
|
6c4ee66165 | ||
|
|
84b014577f | ||
|
|
84ec42463b | ||
|
|
01c8f698ee | ||
|
|
f53a9c1268 | ||
|
|
56c545f679 | ||
|
|
34c1a53441 | ||
|
|
8d330f7dde | ||
|
|
3c0d90eca8 | ||
|
|
9d30245fb4 | ||
|
|
0efccf45bc | ||
|
|
4e0d4c4785 | ||
|
|
6d313b48f2 | ||
|
|
330e8abd16 | ||
|
|
ffd8c36f4f | ||
|
|
efa12608e0 | ||
|
|
969ac4ced1 | ||
|
|
a2d77352e5 | ||
|
|
55c48efb90 | ||
|
|
7a285cd0ea | ||
|
|
415fbfaf41 | ||
|
|
1a41e196e8 | ||
|
|
4f63509ebd | ||
|
|
45a554f305 | ||
|
|
29a0bc23c4 | ||
|
|
f7508f17c5 | ||
|
|
d2538cd3b1 | ||
|
|
fae52d8e4a | ||
|
|
064f198162 | ||
|
|
fd2a9cb056 | ||
|
|
b4c27eeaa1 | ||
|
|
93b4d20c59 | ||
|
|
c647bf5d27 | ||
|
|
cbca75d184 | ||
|
|
8054fc83ac | ||
|
|
0b7680aa14 | ||
|
|
668e85dd7c | ||
|
|
e253c9bfe6 | ||
|
|
c00d3536d1 | ||
|
|
0710d69acd | ||
|
|
8a25cfcadc | ||
|
|
6865206ea7 | ||
|
|
abb1a09f16 | ||
|
|
a184e37f25 | ||
|
|
3f68d69c40 | ||
|
|
0dd8fd57de | ||
|
|
27ff31899b | ||
|
|
e61c8543f1 | ||
|
|
f774303923 | ||
|
|
39f26838ef | ||
|
|
1098d0f414 | ||
|
|
ca6f494ba6 | ||
|
|
7f6c2372f8 | ||
|
|
20a6be8962 | ||
|
|
28c19dad8f | ||
|
|
ada60a938a | ||
|
|
0ee1f51883 | ||
|
|
56276d2102 | ||
|
|
90407259a6 | ||
|
|
939e65d262 | ||
|
|
6a797f2199 | ||
|
|
4b5afb5ccf | ||
|
|
cf079f6c43 | ||
|
|
0659093dfa | ||
|
|
fb8bde0094 | ||
|
|
f747a66109 | ||
|
|
67ac6648c4 | ||
|
|
84f1dc7f8c | ||
|
|
7c55db280b |
File diff suppressed because it is too large
Load Diff
12
DEPS
12
DEPS
@@ -10,9 +10,9 @@ gclient_gn_args = [
|
||||
|
||||
vars = {
|
||||
'chromium_version':
|
||||
'69.0.3497.106',
|
||||
'69.0.3497.128',
|
||||
'node_version':
|
||||
'4d44266b78256449dd6ae86e419e3ec07257b569',
|
||||
'8bc5d171a0873c0ba49f9433798bc8b67399788c',
|
||||
|
||||
'boto_version': 'f7574aa6cc2c819430c1f05e9a1a1a666ef8169b',
|
||||
'pyyaml_version': '3.12',
|
||||
@@ -107,7 +107,7 @@ hooks = [
|
||||
'action': [
|
||||
'python',
|
||||
'-c',
|
||||
'import os; os.chdir("src"); os.chdir("electron"); os.system("npm install")',
|
||||
'import os, subprocess; os.chdir(os.path.join("src", "electron")); subprocess.check_call(["python", "script/lib/npm.py", "install"]);',
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -117,7 +117,7 @@ hooks = [
|
||||
'action': [
|
||||
'python',
|
||||
'-c',
|
||||
'import os; os.chdir("src"); os.chdir("electron"); os.chdir("vendor"); os.chdir("boto"); os.system("python setup.py build");',
|
||||
'import os, subprocess; os.chdir(os.path.join("src", "electron", "vendor", "boto")); subprocess.check_call(["python", "setup.py", "build"]);',
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -127,9 +127,9 @@ hooks = [
|
||||
'action': [
|
||||
'python',
|
||||
'-c',
|
||||
'import os; os.chdir("src"); os.chdir("electron"); os.chdir("vendor"); os.chdir("requests"); os.system("python setup.py build");',
|
||||
'import os, subprocess; os.chdir(os.path.join("src", "electron", "vendor", "requests")); subprocess.check_call(["python", "setup.py", "build"]);',
|
||||
],
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
recursedeps = [
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "atom/browser/browser.h"
|
||||
#include "atom/common/atom_version.h"
|
||||
#include "atom/common/chrome_version.h"
|
||||
#include "atom/common/options_switches.h"
|
||||
@@ -14,8 +15,10 @@
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/strings/string_split.h"
|
||||
#include "base/strings/string_util.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "content/public/common/content_constants.h"
|
||||
#include "content/public/common/content_switches.h"
|
||||
#include "content/public/common/pepper_plugin_info.h"
|
||||
#include "content/public/common/user_agent.h"
|
||||
#include "electron/buildflags/buildflags.h"
|
||||
@@ -173,6 +176,27 @@ void ConvertStringWithSeparatorToVector(std::vector<std::string>* vec,
|
||||
base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
|
||||
}
|
||||
|
||||
std::string RemoveWhitespace(const std::string& str) {
|
||||
std::string trimmed;
|
||||
if (base::RemoveChars(str, " ", &trimmed))
|
||||
return trimmed;
|
||||
else
|
||||
return str;
|
||||
}
|
||||
|
||||
bool IsBrowserProcess() {
|
||||
const base::CommandLine* command_line =
|
||||
base::CommandLine::ForCurrentProcess();
|
||||
std::string process_type =
|
||||
command_line->GetSwitchValueASCII(::switches::kProcessType);
|
||||
return process_type.empty();
|
||||
}
|
||||
|
||||
std::string BuildDefaultUserAgent() {
|
||||
return "Chrome/" CHROME_VERSION_STRING " " ATOM_PRODUCT_NAME
|
||||
"/" ATOM_VERSION_STRING;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
AtomContentClient::AtomContentClient() {}
|
||||
@@ -184,9 +208,35 @@ std::string AtomContentClient::GetProduct() const {
|
||||
}
|
||||
|
||||
std::string AtomContentClient::GetUserAgent() const {
|
||||
return content::BuildUserAgentFromProduct("Chrome/" CHROME_VERSION_STRING
|
||||
" " ATOM_PRODUCT_NAME
|
||||
"/" ATOM_VERSION_STRING);
|
||||
if (IsBrowserProcess()) {
|
||||
if (user_agent_override_.empty()) {
|
||||
auto* browser = Browser::Get();
|
||||
std::string name = RemoveWhitespace(browser->GetName());
|
||||
std::string user_agent;
|
||||
if (name == ATOM_PRODUCT_NAME) {
|
||||
user_agent = BuildDefaultUserAgent();
|
||||
} else {
|
||||
user_agent = base::StringPrintf(
|
||||
"%s/%s Chrome/%s " ATOM_PRODUCT_NAME "/" ATOM_VERSION_STRING,
|
||||
name.c_str(), browser->GetVersion().c_str(), CHROME_VERSION_STRING);
|
||||
}
|
||||
return content::BuildUserAgentFromProduct(user_agent);
|
||||
}
|
||||
return user_agent_override_;
|
||||
}
|
||||
// In a renderer process the user agent should be provided on the CLI
|
||||
// If it's not we just fallback to the default one, this should never happen
|
||||
// but we want to handle it gracefully
|
||||
const base::CommandLine* command_line =
|
||||
base::CommandLine::ForCurrentProcess();
|
||||
std::string cli_user_agent = command_line->GetSwitchValueASCII("user-agent");
|
||||
if (cli_user_agent.empty())
|
||||
return BuildDefaultUserAgent();
|
||||
return cli_user_agent;
|
||||
}
|
||||
|
||||
void AtomContentClient::SetUserAgent(const std::string& user_agent) {
|
||||
user_agent_override_ = user_agent;
|
||||
}
|
||||
|
||||
base::string16 AtomContentClient::GetLocalizedString(int message_id) const {
|
||||
|
||||
@@ -18,10 +18,12 @@ class AtomContentClient : public brightray::ContentClient {
|
||||
AtomContentClient();
|
||||
~AtomContentClient() override;
|
||||
|
||||
std::string GetUserAgent() const override;
|
||||
void SetUserAgent(const std::string& user_agent);
|
||||
|
||||
protected:
|
||||
// content::ContentClient:
|
||||
std::string GetProduct() const override;
|
||||
std::string GetUserAgent() const override;
|
||||
base::string16 GetLocalizedString(int message_id) const override;
|
||||
void AddAdditionalSchemes(Schemes* schemes) override;
|
||||
void AddPepperPlugins(
|
||||
@@ -31,6 +33,8 @@ class AtomContentClient : public brightray::ContentClient {
|
||||
std::vector<media::CdmHostFilePath>* cdm_host_file_paths) override;
|
||||
|
||||
private:
|
||||
std::string user_agent_override_ = "";
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(AtomContentClient);
|
||||
};
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "atom/app/atom_content_client.h"
|
||||
#include "atom/browser/api/atom_api_menu.h"
|
||||
#include "atom/browser/api/atom_api_session.h"
|
||||
#include "atom/browser/api/atom_api_web_contents.h"
|
||||
@@ -43,6 +44,7 @@
|
||||
#include "content/public/browser/client_certificate_delegate.h"
|
||||
#include "content/public/browser/gpu_data_manager.h"
|
||||
#include "content/public/browser/render_frame_host.h"
|
||||
#include "content/public/common/content_client.h"
|
||||
#include "content/public/common/content_switches.h"
|
||||
#include "media/audio/audio_manager.h"
|
||||
#include "native_mate/object_template_builder.h"
|
||||
@@ -1236,6 +1238,18 @@ void App::EnableMixedSandbox(mate::Arguments* args) {
|
||||
command_line->AppendSwitch(switches::kEnableMixedSandbox);
|
||||
}
|
||||
|
||||
void App::SetUserAgentFallback(const std::string& user_agent) {
|
||||
AtomContentClient* client =
|
||||
static_cast<AtomContentClient*>(content::GetContentClient());
|
||||
client->SetUserAgent(user_agent);
|
||||
}
|
||||
|
||||
std::string App::GetUserAgentFallback() {
|
||||
AtomContentClient* client =
|
||||
static_cast<AtomContentClient*>(content::GetContentClient());
|
||||
return client->GetUserAgent();
|
||||
}
|
||||
|
||||
#if defined(OS_MACOSX)
|
||||
bool App::MoveToApplicationsFolder(mate::Arguments* args) {
|
||||
return ui::cocoa::AtomBundleMover::Move(args);
|
||||
@@ -1342,6 +1356,8 @@ void App::BuildPrototype(v8::Isolate* isolate,
|
||||
.SetMethod("startAccessingSecurityScopedResource",
|
||||
&App::StartAccessingSecurityScopedResource)
|
||||
#endif
|
||||
.SetProperty("userAgentFallback", &App::GetUserAgentFallback,
|
||||
&App::SetUserAgentFallback)
|
||||
.SetMethod("enableSandbox", &App::EnableSandbox)
|
||||
.SetMethod("enableMixedSandbox", &App::EnableMixedSandbox);
|
||||
}
|
||||
|
||||
@@ -205,6 +205,8 @@ class App : public AtomBrowserClient::Delegate,
|
||||
const std::string& info_type);
|
||||
void EnableSandbox(mate::Arguments* args);
|
||||
void EnableMixedSandbox(mate::Arguments* args);
|
||||
void SetUserAgentFallback(const std::string& user_agent);
|
||||
std::string GetUserAgentFallback();
|
||||
|
||||
#if defined(OS_MACOSX)
|
||||
bool MoveToApplicationsFolder(mate::Arguments* args);
|
||||
|
||||
@@ -1184,6 +1184,9 @@ void WebContents::LoadURL(const GURL& url, const mate::Dictionary& options) {
|
||||
params.transition_type = ui::PAGE_TRANSITION_TYPED;
|
||||
params.should_clear_history_list = true;
|
||||
params.override_user_agent = content::NavigationController::UA_OVERRIDE_TRUE;
|
||||
// Discord non-committed entries to ensure that we don't re-use a pending
|
||||
// entry
|
||||
web_contents()->GetController().DiscardNonCommittedEntries();
|
||||
web_contents()->GetController().LoadURLWithParams(params);
|
||||
|
||||
// Set the background color of RenderWidgetHostView.
|
||||
@@ -1455,7 +1458,7 @@ bool WebContents::IsDOMReady() const {
|
||||
|
||||
#if BUILDFLAG(ENABLE_PRINTING)
|
||||
void WebContents::Print(mate::Arguments* args) {
|
||||
bool silent, print_background = false;
|
||||
bool silent = false, print_background = false;
|
||||
base::string16 device_name;
|
||||
mate::Dictionary options = mate::Dictionary::CreateEmpty(args->isolate());
|
||||
base::DictionaryValue settings;
|
||||
|
||||
@@ -17,12 +17,15 @@ namespace mate {
|
||||
StreamSubscriber::StreamSubscriber(
|
||||
v8::Isolate* isolate,
|
||||
v8::Local<v8::Object> emitter,
|
||||
base::WeakPtr<atom::URLRequestStreamJob> url_job)
|
||||
: isolate_(isolate),
|
||||
base::WeakPtr<atom::URLRequestStreamJob> url_job,
|
||||
scoped_refptr<base::SequencedTaskRunner> ui_task_runner)
|
||||
: base::RefCountedDeleteOnSequence<StreamSubscriber>(ui_task_runner),
|
||||
isolate_(isolate),
|
||||
emitter_(isolate, emitter),
|
||||
url_job_(url_job),
|
||||
weak_factory_(this) {
|
||||
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
||||
DCHECK(ui_task_runner->RunsTasksInCurrentSequence());
|
||||
|
||||
auto weak_self = weak_factory_.GetWeakPtr();
|
||||
On("data", base::Bind(&StreamSubscriber::OnData, weak_self));
|
||||
On("end", base::Bind(&StreamSubscriber::OnEnd, weak_self));
|
||||
@@ -30,13 +33,12 @@ StreamSubscriber::StreamSubscriber(
|
||||
}
|
||||
|
||||
StreamSubscriber::~StreamSubscriber() {
|
||||
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
||||
RemoveAllListeners();
|
||||
}
|
||||
|
||||
void StreamSubscriber::On(const std::string& event,
|
||||
EventCallback&& callback) { // NOLINT
|
||||
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
||||
DCHECK(owning_task_runner()->RunsTasksInCurrentSequence());
|
||||
DCHECK(js_handlers_.find(event) == js_handlers_.end());
|
||||
|
||||
v8::Locker locker(isolate_);
|
||||
@@ -50,7 +52,7 @@ void StreamSubscriber::On(const std::string& event,
|
||||
}
|
||||
|
||||
void StreamSubscriber::Off(const std::string& event) {
|
||||
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
||||
DCHECK(owning_task_runner()->RunsTasksInCurrentSequence());
|
||||
DCHECK(js_handlers_.find(event) != js_handlers_.end());
|
||||
|
||||
v8::Locker locker(isolate_);
|
||||
@@ -96,6 +98,7 @@ void StreamSubscriber::OnError(mate::Arguments* args) {
|
||||
}
|
||||
|
||||
void StreamSubscriber::RemoveAllListeners() {
|
||||
DCHECK(owning_task_runner()->RunsTasksInCurrentSequence());
|
||||
v8::Locker locker(isolate_);
|
||||
v8::Isolate::Scope isolate_scope(isolate_);
|
||||
v8::HandleScope handle_scope(isolate_);
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
#include <vector>
|
||||
|
||||
#include "base/callback.h"
|
||||
#include "base/memory/ref_counted.h"
|
||||
#include "base/memory/ref_counted_delete_on_sequence.h"
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "content/public/browser/browser_thread.h"
|
||||
#include "v8/include/v8.h"
|
||||
@@ -23,17 +25,25 @@ namespace mate {
|
||||
|
||||
class Arguments;
|
||||
|
||||
class StreamSubscriber {
|
||||
class StreamSubscriber
|
||||
: public base::RefCountedDeleteOnSequence<StreamSubscriber> {
|
||||
public:
|
||||
REQUIRE_ADOPTION_FOR_REFCOUNTED_TYPE();
|
||||
|
||||
StreamSubscriber(v8::Isolate* isolate,
|
||||
v8::Local<v8::Object> emitter,
|
||||
base::WeakPtr<atom::URLRequestStreamJob> url_job);
|
||||
~StreamSubscriber();
|
||||
base::WeakPtr<atom::URLRequestStreamJob> url_job,
|
||||
scoped_refptr<base::SequencedTaskRunner> ui_task_runner);
|
||||
|
||||
private:
|
||||
friend class base::DeleteHelper<StreamSubscriber>;
|
||||
friend class base::RefCountedDeleteOnSequence<StreamSubscriber>;
|
||||
|
||||
using JSHandlersMap = std::map<std::string, v8::Global<v8::Value>>;
|
||||
using EventCallback = base::Callback<void(mate::Arguments* args)>;
|
||||
|
||||
~StreamSubscriber();
|
||||
|
||||
void On(const std::string& event, EventCallback&& callback); // NOLINT
|
||||
void Off(const std::string& event);
|
||||
|
||||
|
||||
@@ -90,6 +90,10 @@
|
||||
#include "components/services/pdf_compositor/public/interfaces/pdf_compositor.mojom.h"
|
||||
#endif // BUILDFLAG(ENABLE_PRINTING)
|
||||
|
||||
#if BUILDFLAG(ENABLE_TTS)
|
||||
#include "chrome/browser/speech/tts_message_filter.h"
|
||||
#endif // BUILDFLAG(ENABLE_TTS)
|
||||
|
||||
using content::BrowserThread;
|
||||
|
||||
namespace atom {
|
||||
@@ -222,6 +226,10 @@ void AtomBrowserClient::RenderProcessWillLaunch(
|
||||
process_id, host->GetBrowserContext()));
|
||||
#endif
|
||||
|
||||
#if BUILDFLAG(ENABLE_TTS)
|
||||
host->AddFilter(new TtsMessageFilter(host->GetBrowserContext()));
|
||||
#endif
|
||||
|
||||
ProcessPreferences prefs;
|
||||
auto* web_preferences =
|
||||
WebContentsPreferences::From(GetWebContentsFromProcessID(process_id));
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "atom/app/atom_content_client.h"
|
||||
#include "atom/browser/atom_blob_reader.h"
|
||||
#include "atom/browser/atom_browser_main_parts.h"
|
||||
#include "atom/browser/atom_download_manager_delegate.h"
|
||||
@@ -42,6 +43,7 @@
|
||||
#include "components/proxy_config/proxy_config_pref_names.h"
|
||||
#include "content/browser/blob_storage/chrome_blob_storage_context.h"
|
||||
#include "content/public/browser/storage_partition.h"
|
||||
#include "content/public/common/content_client.h"
|
||||
#include "content/public/common/user_agent.h"
|
||||
#include "net/base/escape.h"
|
||||
|
||||
@@ -51,14 +53,6 @@ namespace atom {
|
||||
|
||||
namespace {
|
||||
|
||||
std::string RemoveWhitespace(const std::string& str) {
|
||||
std::string trimmed;
|
||||
if (base::RemoveChars(str, " ", &trimmed))
|
||||
return trimmed;
|
||||
else
|
||||
return str;
|
||||
}
|
||||
|
||||
// Convert string to lower case and escape it.
|
||||
std::string MakePartitionName(const std::string& input) {
|
||||
return net::EscapePath(base::ToLowerASCII(input));
|
||||
@@ -78,19 +72,9 @@ AtomBrowserContext::AtomBrowserContext(const std::string& partition,
|
||||
storage_policy_(new SpecialStoragePolicy),
|
||||
in_memory_(in_memory),
|
||||
weak_factory_(this) {
|
||||
// Construct user agent string.
|
||||
Browser* browser = Browser::Get();
|
||||
std::string name = RemoveWhitespace(browser->GetName());
|
||||
std::string user_agent;
|
||||
if (name == ATOM_PRODUCT_NAME) {
|
||||
user_agent = "Chrome/" CHROME_VERSION_STRING " " ATOM_PRODUCT_NAME
|
||||
"/" ATOM_VERSION_STRING;
|
||||
} else {
|
||||
user_agent = base::StringPrintf(
|
||||
"%s/%s Chrome/%s " ATOM_PRODUCT_NAME "/" ATOM_VERSION_STRING,
|
||||
name.c_str(), browser->GetVersion().c_str(), CHROME_VERSION_STRING);
|
||||
}
|
||||
user_agent_ = content::BuildUserAgentFromProduct(user_agent);
|
||||
AtomContentClient* client =
|
||||
static_cast<AtomContentClient*>(content::GetContentClient());
|
||||
user_agent_ = client->GetUserAgent();
|
||||
|
||||
// Read options.
|
||||
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
|
||||
|
||||
@@ -88,19 +88,53 @@ void AtomDownloadManagerDelegate::OnDownloadPathGenerated(
|
||||
if (relay)
|
||||
window = relay->GetNativeWindow();
|
||||
|
||||
auto* web_preferences = WebContentsPreferences::From(web_contents);
|
||||
bool offscreen =
|
||||
!web_preferences || web_preferences->IsEnabled(options::kOffscreen);
|
||||
|
||||
// Show save dialog if save path was not set already on item
|
||||
base::FilePath path;
|
||||
GetItemSavePath(item, &path);
|
||||
// Show save dialog if save path was not set already on item
|
||||
file_dialog::DialogSettings settings;
|
||||
settings.parent_window = window;
|
||||
settings.force_detached = offscreen;
|
||||
settings.title = item->GetURL().spec();
|
||||
settings.default_path = default_path;
|
||||
if (path.empty() && file_dialog::ShowSaveDialog(settings, &path)) {
|
||||
if (path.empty()) {
|
||||
file_dialog::DialogSettings settings;
|
||||
settings.parent_window = window;
|
||||
settings.title = item->GetURL().spec();
|
||||
settings.default_path = default_path;
|
||||
|
||||
auto* web_preferences = WebContentsPreferences::From(web_contents);
|
||||
const bool offscreen =
|
||||
!web_preferences || web_preferences->IsEnabled(options::kOffscreen);
|
||||
settings.force_detached = offscreen;
|
||||
|
||||
auto dialog_callback =
|
||||
base::Bind(&AtomDownloadManagerDelegate::OnDownloadSaveDialogDone,
|
||||
base::Unretained(this), download_id, callback);
|
||||
file_dialog::ShowSaveDialog(settings, dialog_callback);
|
||||
} else {
|
||||
callback.Run(path, download::DownloadItem::TARGET_DISPOSITION_PROMPT,
|
||||
download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, path,
|
||||
download::DOWNLOAD_INTERRUPT_REASON_NONE);
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(MAS_BUILD)
|
||||
void AtomDownloadManagerDelegate::OnDownloadSaveDialogDone(
|
||||
uint32_t download_id,
|
||||
const content::DownloadTargetCallback& download_callback,
|
||||
bool result,
|
||||
const base::FilePath& path,
|
||||
const std::string& bookmark)
|
||||
#else
|
||||
void AtomDownloadManagerDelegate::OnDownloadSaveDialogDone(
|
||||
uint32_t download_id,
|
||||
const content::DownloadTargetCallback& download_callback,
|
||||
bool result,
|
||||
const base::FilePath& path)
|
||||
#endif
|
||||
{
|
||||
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
||||
|
||||
auto* item = download_manager_->GetDownload(download_id);
|
||||
if (!item)
|
||||
return;
|
||||
|
||||
if (result) {
|
||||
// Remember the last selected download directory.
|
||||
AtomBrowserContext* browser_context = static_cast<AtomBrowserContext*>(
|
||||
download_manager_->GetBrowserContext());
|
||||
@@ -117,12 +151,16 @@ void AtomDownloadManagerDelegate::OnDownloadPathGenerated(
|
||||
}
|
||||
|
||||
// Running the DownloadTargetCallback with an empty FilePath signals that the
|
||||
// download should be cancelled.
|
||||
// If user cancels the file save dialog, run the callback with empty FilePath.
|
||||
callback.Run(path, download::DownloadItem::TARGET_DISPOSITION_PROMPT,
|
||||
download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, path,
|
||||
path.empty() ? download::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED
|
||||
: download::DOWNLOAD_INTERRUPT_REASON_NONE);
|
||||
// download should be cancelled. If user cancels the file save dialog, run
|
||||
// the callback with empty FilePath.
|
||||
const base::FilePath download_path = result ? path : base::FilePath();
|
||||
const auto interrupt_reason =
|
||||
download_path.empty() ? download::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED
|
||||
: download::DOWNLOAD_INTERRUPT_REASON_NONE;
|
||||
download_callback.Run(download_path,
|
||||
download::DownloadItem::TARGET_DISPOSITION_PROMPT,
|
||||
download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
|
||||
download_path, interrupt_reason);
|
||||
}
|
||||
|
||||
void AtomDownloadManagerDelegate::Shutdown() {
|
||||
|
||||
@@ -24,10 +24,6 @@ class AtomDownloadManagerDelegate : public content::DownloadManagerDelegate {
|
||||
explicit AtomDownloadManagerDelegate(content::DownloadManager* manager);
|
||||
~AtomDownloadManagerDelegate() override;
|
||||
|
||||
void OnDownloadPathGenerated(uint32_t download_id,
|
||||
const content::DownloadTargetCallback& callback,
|
||||
const base::FilePath& default_path);
|
||||
|
||||
// content::DownloadManagerDelegate:
|
||||
void Shutdown() override;
|
||||
bool DetermineDownloadTarget(
|
||||
@@ -42,6 +38,25 @@ class AtomDownloadManagerDelegate : public content::DownloadManagerDelegate {
|
||||
// Get the save path set on the associated api::DownloadItem object
|
||||
void GetItemSavePath(download::DownloadItem* item, base::FilePath* path);
|
||||
|
||||
void OnDownloadPathGenerated(uint32_t download_id,
|
||||
const content::DownloadTargetCallback& callback,
|
||||
const base::FilePath& default_path);
|
||||
|
||||
#if defined(MAS_BUILD)
|
||||
void OnDownloadSaveDialogDone(
|
||||
uint32_t download_id,
|
||||
const content::DownloadTargetCallback& download_callback,
|
||||
bool result,
|
||||
const base::FilePath& path,
|
||||
const std::string& bookmark);
|
||||
#else
|
||||
void OnDownloadSaveDialogDone(
|
||||
uint32_t download_id,
|
||||
const content::DownloadTargetCallback& download_callback,
|
||||
bool result,
|
||||
const base::FilePath& path);
|
||||
#endif
|
||||
|
||||
content::DownloadManager* download_manager_;
|
||||
base::WeakPtrFactory<AtomDownloadManagerDelegate> weak_ptr_factory_;
|
||||
|
||||
|
||||
@@ -48,6 +48,8 @@ network::mojom::HttpAuthDynamicParamsPtr CreateHttpAuthDynamicParams(
|
||||
command_line.GetSwitchValueASCII(switches::kAuthServerWhitelist);
|
||||
auth_dynamic_params->delegate_whitelist = command_line.GetSwitchValueASCII(
|
||||
switches::kAuthNegotiateDelegateWhitelist);
|
||||
auth_dynamic_params->enable_negotiate_port =
|
||||
command_line.HasSwitch(atom::switches::kEnableAuthNegotiatePort);
|
||||
|
||||
return auth_dynamic_params;
|
||||
}
|
||||
|
||||
@@ -585,7 +585,12 @@ void NativeWindowMac::Hide() {
|
||||
}
|
||||
|
||||
bool NativeWindowMac::IsVisible() {
|
||||
return [window_ isVisible];
|
||||
bool occluded = [window_ occlusionState] == NSWindowOcclusionStateVisible;
|
||||
|
||||
// For a window to be visible, it must be visible to the user in the
|
||||
// foreground of the app, which means that it should not be minimized or
|
||||
// occluded
|
||||
return [window_ isVisible] && !occluded && !IsMinimized();
|
||||
}
|
||||
|
||||
bool NativeWindowMac::IsEnabled() {
|
||||
@@ -1118,17 +1123,23 @@ void NativeWindowMac::SetProgressBar(double progress,
|
||||
const NativeWindow::ProgressState state) {
|
||||
NSDockTile* dock_tile = [NSApp dockTile];
|
||||
|
||||
// Sometimes macOS would install a default contentView for dock, we must
|
||||
// verify whether NSProgressIndicator has been installed.
|
||||
bool first_time = !dock_tile.contentView ||
|
||||
[[dock_tile.contentView subviews] count] == 0 ||
|
||||
![[[dock_tile.contentView subviews] lastObject]
|
||||
isKindOfClass:[NSProgressIndicator class]];
|
||||
|
||||
// For the first time API invoked, we need to create a ContentView in
|
||||
// DockTile.
|
||||
if (dock_tile.contentView == nullptr) {
|
||||
NSImageView* image_view = [[NSImageView alloc] init];
|
||||
if (first_time) {
|
||||
NSImageView* image_view = [[[NSImageView alloc] init] autorelease];
|
||||
[image_view setImage:[NSApp applicationIconImage]];
|
||||
[dock_tile setContentView:image_view];
|
||||
}
|
||||
|
||||
if ([[dock_tile.contentView subviews] count] == 0) {
|
||||
NSProgressIndicator* progress_indicator = [[AtomProgressBar alloc]
|
||||
initWithFrame:NSMakeRect(0.0f, 0.0f, dock_tile.size.width, 15.0)];
|
||||
NSRect frame = NSMakeRect(0.0f, 0.0f, dock_tile.size.width, 15.0);
|
||||
NSProgressIndicator* progress_indicator =
|
||||
[[[AtomProgressBar alloc] initWithFrame:frame] autorelease];
|
||||
[progress_indicator setStyle:NSProgressIndicatorBarStyle];
|
||||
[progress_indicator setIndeterminate:NO];
|
||||
[progress_indicator setBezeled:YES];
|
||||
@@ -1139,7 +1150,7 @@ void NativeWindowMac::SetProgressBar(double progress,
|
||||
}
|
||||
|
||||
NSProgressIndicator* progress_indicator = static_cast<NSProgressIndicator*>(
|
||||
[[[dock_tile contentView] subviews] objectAtIndex:0]);
|
||||
[[[dock_tile contentView] subviews] lastObject]);
|
||||
if (progress < 0) {
|
||||
[progress_indicator setHidden:YES];
|
||||
} else if (progress > 1) {
|
||||
|
||||
@@ -349,6 +349,9 @@ void NativeWindowViews::Show() {
|
||||
|
||||
widget()->native_widget_private()->ShowWithWindowState(GetRestoredState());
|
||||
|
||||
// explicitly focus the window
|
||||
widget()->Activate();
|
||||
|
||||
NotifyWindowShow();
|
||||
|
||||
#if defined(USE_X11)
|
||||
|
||||
@@ -13,14 +13,16 @@
|
||||
#include "atom/common/api/event_emitter_caller.h"
|
||||
#include "atom/common/atom_constants.h"
|
||||
#include "atom/common/native_mate_converters/net_converter.h"
|
||||
#include "atom/common/node_includes.h"
|
||||
#include "base/strings/string_number_conversions.h"
|
||||
#include "base/strings/string_util.h"
|
||||
#include "base/threading/thread_task_runner_handle.h"
|
||||
#include "base/time/time.h"
|
||||
#include "native_mate/dictionary.h"
|
||||
#include "net/base/net_errors.h"
|
||||
#include "net/filter/gzip_source_stream.h"
|
||||
|
||||
#include "atom/common/node_includes.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
namespace {
|
||||
@@ -82,14 +84,14 @@ void BeforeStartInUI(base::WeakPtr<URLRequestStreamJob> job,
|
||||
return;
|
||||
}
|
||||
|
||||
auto subscriber = std::make_unique<mate::StreamSubscriber>(
|
||||
args->isolate(), data.GetHandle(), job);
|
||||
auto subscriber = base::MakeRefCounted<mate::StreamSubscriber>(
|
||||
args->isolate(), data.GetHandle(), job,
|
||||
base::ThreadTaskRunnerHandle::Get());
|
||||
|
||||
content::BrowserThread::PostTask(
|
||||
content::BrowserThread::IO, FROM_HERE,
|
||||
base::BindOnce(&URLRequestStreamJob::StartAsync, job,
|
||||
std::move(subscriber), base::RetainedRef(response_headers),
|
||||
ended, error));
|
||||
base::BindOnce(&URLRequestStreamJob::StartAsync, job, subscriber,
|
||||
base::RetainedRef(response_headers), ended, error));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -104,10 +106,7 @@ URLRequestStreamJob::URLRequestStreamJob(net::URLRequest* request,
|
||||
weak_factory_(this) {}
|
||||
|
||||
URLRequestStreamJob::~URLRequestStreamJob() {
|
||||
if (subscriber_) {
|
||||
content::BrowserThread::DeleteSoon(content::BrowserThread::UI, FROM_HERE,
|
||||
std::move(subscriber_));
|
||||
}
|
||||
DCHECK(!subscriber_ || subscriber_->HasOneRef());
|
||||
}
|
||||
|
||||
void URLRequestStreamJob::Start() {
|
||||
@@ -121,7 +120,7 @@ void URLRequestStreamJob::Start() {
|
||||
}
|
||||
|
||||
void URLRequestStreamJob::StartAsync(
|
||||
std::unique_ptr<mate::StreamSubscriber> subscriber,
|
||||
scoped_refptr<mate::StreamSubscriber> subscriber,
|
||||
scoped_refptr<net::HttpResponseHeaders> response_headers,
|
||||
bool ended,
|
||||
int error) {
|
||||
@@ -133,12 +132,13 @@ void URLRequestStreamJob::StartAsync(
|
||||
|
||||
ended_ = ended;
|
||||
response_headers_ = response_headers;
|
||||
subscriber_ = std::move(subscriber);
|
||||
subscriber_ = subscriber;
|
||||
request_start_time_ = base::TimeTicks::Now();
|
||||
NotifyHeadersComplete();
|
||||
}
|
||||
|
||||
void URLRequestStreamJob::OnData(std::vector<char>&& buffer) { // NOLINT
|
||||
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
|
||||
if (write_buffer_.empty()) {
|
||||
// Quick branch without copying.
|
||||
write_buffer_ = std::move(buffer);
|
||||
@@ -173,7 +173,7 @@ void URLRequestStreamJob::OnError(int error) {
|
||||
int URLRequestStreamJob::ReadRawData(net::IOBuffer* dest, int dest_size) {
|
||||
response_start_time_ = base::TimeTicks::Now();
|
||||
|
||||
if (ended_)
|
||||
if (ended_ && write_buffer_.empty())
|
||||
return 0;
|
||||
|
||||
// When write_buffer_ is empty, there is no data valable yet, we have to save
|
||||
@@ -191,12 +191,13 @@ int URLRequestStreamJob::ReadRawData(net::IOBuffer* dest, int dest_size) {
|
||||
}
|
||||
|
||||
void URLRequestStreamJob::DoneReading() {
|
||||
content::BrowserThread::DeleteSoon(content::BrowserThread::UI, FROM_HERE,
|
||||
std::move(subscriber_));
|
||||
write_buffer_.clear();
|
||||
}
|
||||
|
||||
void URLRequestStreamJob::DoneReadingRedirectResponse() {
|
||||
if (subscriber_) {
|
||||
subscriber_ = nullptr;
|
||||
}
|
||||
DoneReading();
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ class URLRequestStreamJob : public JsAsker, public net::URLRequestJob {
|
||||
net::NetworkDelegate* network_delegate);
|
||||
~URLRequestStreamJob() override;
|
||||
|
||||
void StartAsync(std::unique_ptr<mate::StreamSubscriber> subscriber,
|
||||
void StartAsync(scoped_refptr<mate::StreamSubscriber> subscriber,
|
||||
scoped_refptr<net::HttpResponseHeaders> response_headers,
|
||||
bool ended,
|
||||
int error);
|
||||
@@ -62,7 +62,7 @@ class URLRequestStreamJob : public JsAsker, public net::URLRequestJob {
|
||||
base::TimeTicks request_start_time_;
|
||||
base::TimeTicks response_start_time_;
|
||||
scoped_refptr<net::HttpResponseHeaders> response_headers_;
|
||||
std::unique_ptr<mate::StreamSubscriber> subscriber_;
|
||||
scoped_refptr<mate::StreamSubscriber> subscriber_;
|
||||
|
||||
base::WeakPtrFactory<URLRequestStreamJob> weak_factory_;
|
||||
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>electron.icns</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>4.0.2</string>
|
||||
<string>4.2.2</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>4.0.2</string>
|
||||
<string>4.2.2</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.developer-tools</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
|
||||
@@ -50,8 +50,8 @@ END
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 4,0,2,0
|
||||
PRODUCTVERSION 4,0,2,0
|
||||
FILEVERSION 4,2,2,0
|
||||
PRODUCTVERSION 4,2,2,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -68,12 +68,12 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "GitHub, Inc."
|
||||
VALUE "FileDescription", "Electron"
|
||||
VALUE "FileVersion", "4.0.2"
|
||||
VALUE "FileVersion", "4.2.2"
|
||||
VALUE "InternalName", "electron.exe"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2015 GitHub, Inc. All rights reserved."
|
||||
VALUE "OriginalFilename", "electron.exe"
|
||||
VALUE "ProductName", "Electron"
|
||||
VALUE "ProductVersion", "4.0.2"
|
||||
VALUE "ProductVersion", "4.2.2"
|
||||
VALUE "SquirrelAwareVersion", "1"
|
||||
END
|
||||
END
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "ui/base/l10n/l10n_util_mac.h"
|
||||
#include "ui/events/cocoa/cocoa_event_utils.h"
|
||||
#include "ui/gfx/image/image.h"
|
||||
#include "ui/strings/grit/ui_strings.h"
|
||||
|
||||
using content::BrowserThread;
|
||||
|
||||
@@ -56,6 +57,29 @@ Role kRolesMap[] = {
|
||||
{@selector(clearRecentDocuments:), "clearrecentdocuments"},
|
||||
};
|
||||
|
||||
// Called when adding a submenu to the menu and checks if the submenu, via its
|
||||
// |model|, has visible child items.
|
||||
bool MenuHasVisibleItems(const atom::AtomMenuModel* model) {
|
||||
int count = model->GetItemCount();
|
||||
for (int index = 0; index < count; index++) {
|
||||
if (model->IsVisibleAt(index))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Called when an empty submenu is created. This inserts a menu item labeled
|
||||
// "(empty)" into the submenu. Matches Windows behavior.
|
||||
NSMenu* MakeEmptySubmenu() {
|
||||
base::scoped_nsobject<NSMenu> submenu([[NSMenu alloc] initWithTitle:@""]);
|
||||
NSString* empty_menu_title =
|
||||
l10n_util::GetNSString(IDS_APP_MENU_EMPTY_SUBMENU);
|
||||
|
||||
[submenu addItemWithTitle:empty_menu_title action:NULL keyEquivalent:@""];
|
||||
[[submenu itemAtIndex:0] setEnabled:NO];
|
||||
return submenu.autorelease();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Menu item is located for ease of removing it from the parent owner
|
||||
@@ -218,13 +242,21 @@ static base::scoped_nsobject<NSMenu> recentDocumentsMenuSwap_;
|
||||
NSMenu* submenu = [[NSMenu alloc] initWithTitle:label];
|
||||
[item setSubmenu:submenu];
|
||||
[NSApp setServicesMenu:submenu];
|
||||
} else if (type == atom::AtomMenuModel::TYPE_SUBMENU) {
|
||||
} else if (type == atom::AtomMenuModel::TYPE_SUBMENU &&
|
||||
model->IsVisibleAt(index)) {
|
||||
// We need to specifically check that the submenu top-level item has been
|
||||
// enabled as it's not validated by validateUserInterfaceItem
|
||||
if (!model->IsEnabledAt(index))
|
||||
[item setEnabled:NO];
|
||||
|
||||
// Recursively build a submenu from the sub-model at this index.
|
||||
[item setTarget:nil];
|
||||
[item setAction:nil];
|
||||
atom::AtomMenuModel* submenuModel =
|
||||
static_cast<atom::AtomMenuModel*>(model->GetSubmenuModelAt(index));
|
||||
NSMenu* submenu = [self menuFromModel:submenuModel];
|
||||
NSMenu* submenu = MenuHasVisibleItems(submenuModel)
|
||||
? [self menuFromModel:submenuModel]
|
||||
: MakeEmptySubmenu();
|
||||
[submenu setTitle:[item title]];
|
||||
[item setSubmenu:submenu];
|
||||
|
||||
|
||||
@@ -623,9 +623,14 @@ static NSString* const ImageScrubberItemIdentifier = @"scrubber.image.item";
|
||||
segments[i].Get("enabled", &enabled);
|
||||
if (segments[i].Get("label", &label)) {
|
||||
[control setLabel:base::SysUTF8ToNSString(label) forSegment:i];
|
||||
} else if (segments[i].Get("icon", &image)) {
|
||||
} else {
|
||||
[control setLabel:@"" forSegment:i];
|
||||
}
|
||||
if (segments[i].Get("icon", &image)) {
|
||||
[control setImage:image.AsNSImage() forSegment:i];
|
||||
[control setImageScaling:NSImageScaleProportionallyUpOrDown forSegment:i];
|
||||
} else {
|
||||
[control setImage:nil forSegment:i];
|
||||
}
|
||||
[control setEnabled:enabled forSegment:i];
|
||||
}
|
||||
|
||||
@@ -284,7 +284,7 @@ bool ShowOpenDialog(const DialogSettings& settings,
|
||||
|
||||
void OpenDialogCompletion(int chosen,
|
||||
NSOpenPanel* dialog,
|
||||
const DialogSettings& settings,
|
||||
bool security_scoped_bookmarks,
|
||||
const OpenDialogCallback& callback) {
|
||||
if (chosen == NSFileHandlingPanelCancelButton) {
|
||||
#if defined(MAS_BUILD)
|
||||
@@ -297,7 +297,7 @@ void OpenDialogCompletion(int chosen,
|
||||
std::vector<base::FilePath> paths;
|
||||
#if defined(MAS_BUILD)
|
||||
std::vector<std::string> bookmarks;
|
||||
if (settings.security_scoped_bookmarks) {
|
||||
if (security_scoped_bookmarks) {
|
||||
ReadDialogPathsWithBookmarks(dialog, &paths, &bookmarks);
|
||||
} else {
|
||||
ReadDialogPaths(dialog, &paths);
|
||||
@@ -320,17 +320,21 @@ void ShowOpenDialog(const DialogSettings& settings,
|
||||
// Duplicate the callback object here since c is a reference and gcd would
|
||||
// only store the pointer, by duplication we can force gcd to store a copy.
|
||||
__block OpenDialogCallback callback = c;
|
||||
// Capture the value of the security_scoped_bookmarks settings flag
|
||||
// and pass it to the completion handler.
|
||||
bool security_scoped_bookmarks = settings.security_scoped_bookmarks;
|
||||
|
||||
if (!settings.parent_window || !settings.parent_window->GetNativeWindow() ||
|
||||
settings.force_detached) {
|
||||
[dialog beginWithCompletionHandler:^(NSInteger chosen) {
|
||||
OpenDialogCompletion(chosen, dialog, settings, callback);
|
||||
OpenDialogCompletion(chosen, dialog, security_scoped_bookmarks, callback);
|
||||
}];
|
||||
} else {
|
||||
NSWindow* window = settings.parent_window->GetNativeWindow();
|
||||
[dialog beginSheetModalForWindow:window
|
||||
completionHandler:^(NSInteger chosen) {
|
||||
OpenDialogCompletion(chosen, dialog, settings, callback);
|
||||
OpenDialogCompletion(chosen, dialog,
|
||||
security_scoped_bookmarks, callback);
|
||||
}];
|
||||
}
|
||||
}
|
||||
@@ -351,7 +355,7 @@ bool ShowSaveDialog(const DialogSettings& settings, base::FilePath* path) {
|
||||
|
||||
void SaveDialogCompletion(int chosen,
|
||||
NSSavePanel* dialog,
|
||||
const DialogSettings& settings,
|
||||
bool security_scoped_bookmarks,
|
||||
const SaveDialogCallback& callback) {
|
||||
if (chosen == NSFileHandlingPanelCancelButton) {
|
||||
#if defined(MAS_BUILD)
|
||||
@@ -363,7 +367,7 @@ void SaveDialogCompletion(int chosen,
|
||||
std::string path = base::SysNSStringToUTF8([[dialog URL] path]);
|
||||
#if defined(MAS_BUILD)
|
||||
std::string bookmark;
|
||||
if (settings.security_scoped_bookmarks) {
|
||||
if (security_scoped_bookmarks) {
|
||||
bookmark = GetBookmarkDataFromNSURL([dialog URL]);
|
||||
}
|
||||
callback.Run(true, base::FilePath(path), bookmark);
|
||||
@@ -381,17 +385,19 @@ void ShowSaveDialog(const DialogSettings& settings,
|
||||
[dialog setCanSelectHiddenExtension:YES];
|
||||
|
||||
__block SaveDialogCallback callback = c;
|
||||
bool security_scoped_bookmarks = settings.security_scoped_bookmarks;
|
||||
|
||||
if (!settings.parent_window || !settings.parent_window->GetNativeWindow() ||
|
||||
settings.force_detached) {
|
||||
[dialog beginWithCompletionHandler:^(NSInteger chosen) {
|
||||
SaveDialogCompletion(chosen, dialog, settings, callback);
|
||||
SaveDialogCompletion(chosen, dialog, security_scoped_bookmarks, callback);
|
||||
}];
|
||||
} else {
|
||||
NSWindow* window = settings.parent_window->GetNativeWindow();
|
||||
[dialog beginSheetModalForWindow:window
|
||||
completionHandler:^(NSInteger chosen) {
|
||||
SaveDialogCompletion(chosen, dialog, settings, callback);
|
||||
SaveDialogCompletion(chosen, dialog,
|
||||
security_scoped_bookmarks, callback);
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "cc/base/switches.h"
|
||||
#include "content/public/browser/render_frame_host.h"
|
||||
#include "content/public/browser/render_process_host.h"
|
||||
#include "content/public/common/content_client.h"
|
||||
#include "content/public/common/content_switches.h"
|
||||
#include "content/public/common/web_preferences.h"
|
||||
#include "native_mate/dictionary.h"
|
||||
@@ -229,6 +230,9 @@ WebContentsPreferences* WebContentsPreferences::From(
|
||||
|
||||
void WebContentsPreferences::AppendCommandLineSwitches(
|
||||
base::CommandLine* command_line) {
|
||||
// Append UA Override
|
||||
command_line->AppendSwitchASCII("user-agent",
|
||||
content::GetContentClient()->GetUserAgent());
|
||||
// Check if plugins are enabled.
|
||||
if (IsEnabled(options::kPlugins))
|
||||
command_line->AppendSwitch(switches::kEnablePlugins);
|
||||
|
||||
@@ -81,49 +81,71 @@ float GetScaleFactorFromOptions(mate::Arguments* args) {
|
||||
return scale_factor;
|
||||
}
|
||||
|
||||
bool AddImageSkiaRep(gfx::ImageSkia* image,
|
||||
const unsigned char* data,
|
||||
size_t size,
|
||||
int width,
|
||||
int height,
|
||||
double scale_factor) {
|
||||
auto decoded = std::make_unique<SkBitmap>();
|
||||
bool AddImageSkiaRepFromPNG(gfx::ImageSkia* image,
|
||||
const unsigned char* data,
|
||||
size_t size,
|
||||
double scale_factor) {
|
||||
SkBitmap bitmap;
|
||||
if (!gfx::PNGCodec::Decode(data, size, &bitmap))
|
||||
return false;
|
||||
|
||||
// Try PNG first.
|
||||
if (!gfx::PNGCodec::Decode(data, size, decoded.get())) {
|
||||
// Try JPEG.
|
||||
decoded = gfx::JPEGCodec::Decode(data, size);
|
||||
if (decoded) {
|
||||
// `JPEGCodec::Decode()` doesn't tell `SkBitmap` instance it creates
|
||||
// that all of its pixels are opaque, that's why the bitmap gets
|
||||
// an alpha type `kPremul_SkAlphaType` instead of `kOpaque_SkAlphaType`.
|
||||
// Let's fix it here.
|
||||
// TODO(alexeykuzmin): This workaround should be removed
|
||||
// when the `JPEGCodec::Decode()` code is fixed.
|
||||
// See https://github.com/electron/electron/issues/11294.
|
||||
decoded->setAlphaType(SkAlphaType::kOpaque_SkAlphaType);
|
||||
}
|
||||
}
|
||||
|
||||
if (!decoded) {
|
||||
// Try Bitmap
|
||||
if (width > 0 && height > 0) {
|
||||
decoded.reset(new SkBitmap);
|
||||
decoded->allocN32Pixels(width, height, false);
|
||||
decoded->setPixels(
|
||||
const_cast<void*>(reinterpret_cast<const void*>(data)));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
image->AddRepresentation(gfx::ImageSkiaRep(*decoded, scale_factor));
|
||||
image->AddRepresentation(gfx::ImageSkiaRep(bitmap, scale_factor));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AddImageSkiaRep(gfx::ImageSkia* image,
|
||||
const base::FilePath& path,
|
||||
double scale_factor) {
|
||||
bool AddImageSkiaRepFromJPEG(gfx::ImageSkia* image,
|
||||
const unsigned char* data,
|
||||
size_t size,
|
||||
double scale_factor) {
|
||||
auto bitmap = gfx::JPEGCodec::Decode(data, size);
|
||||
if (!bitmap)
|
||||
return false;
|
||||
|
||||
// `JPEGCodec::Decode()` doesn't tell `SkBitmap` instance it creates
|
||||
// that all of its pixels are opaque, that's why the bitmap gets
|
||||
// an alpha type `kPremul_SkAlphaType` instead of `kOpaque_SkAlphaType`.
|
||||
// Let's fix it here.
|
||||
// TODO(alexeykuzmin): This workaround should be removed
|
||||
// when the `JPEGCodec::Decode()` code is fixed.
|
||||
// See https://github.com/electron/electron/issues/11294.
|
||||
bitmap->setAlphaType(SkAlphaType::kOpaque_SkAlphaType);
|
||||
|
||||
image->AddRepresentation(gfx::ImageSkiaRep(*bitmap, scale_factor));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AddImageSkiaRepFromBuffer(gfx::ImageSkia* image,
|
||||
const unsigned char* data,
|
||||
size_t size,
|
||||
int width,
|
||||
int height,
|
||||
double scale_factor) {
|
||||
// Try PNG first.
|
||||
if (AddImageSkiaRepFromPNG(image, data, size, scale_factor))
|
||||
return true;
|
||||
|
||||
// Try JPEG second.
|
||||
if (AddImageSkiaRepFromJPEG(image, data, size, scale_factor))
|
||||
return true;
|
||||
|
||||
if (width == 0 || height == 0)
|
||||
return false;
|
||||
|
||||
auto info = SkImageInfo::MakeN32(width, height, kPremul_SkAlphaType);
|
||||
if (size < info.computeMinByteSize())
|
||||
return false;
|
||||
|
||||
SkBitmap bitmap;
|
||||
bitmap.allocN32Pixels(width, height, false);
|
||||
bitmap.writePixels({info, data, bitmap.rowBytes()});
|
||||
|
||||
image->AddRepresentation(gfx::ImageSkiaRep(bitmap, scale_factor));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AddImageSkiaRepFromPath(gfx::ImageSkia* image,
|
||||
const base::FilePath& path,
|
||||
double scale_factor) {
|
||||
std::string file_contents;
|
||||
{
|
||||
base::ThreadRestrictions::ScopedAllowIO allow_io;
|
||||
@@ -134,7 +156,8 @@ bool AddImageSkiaRep(gfx::ImageSkia* image,
|
||||
const unsigned char* data =
|
||||
reinterpret_cast<const unsigned char*>(file_contents.data());
|
||||
size_t size = file_contents.size();
|
||||
return AddImageSkiaRep(image, data, size, 0, 0, scale_factor);
|
||||
|
||||
return AddImageSkiaRepFromBuffer(image, data, size, 0, 0, scale_factor);
|
||||
}
|
||||
|
||||
bool PopulateImageSkiaRepsFromPath(gfx::ImageSkia* image,
|
||||
@@ -143,12 +166,12 @@ bool PopulateImageSkiaRepsFromPath(gfx::ImageSkia* image,
|
||||
std::string filename(path.BaseName().RemoveExtension().AsUTF8Unsafe());
|
||||
if (base::MatchPattern(filename, "*@*x"))
|
||||
// Don't search for other representations if the DPI has been specified.
|
||||
return AddImageSkiaRep(image, path, GetScaleFactorFromPath(path));
|
||||
return AddImageSkiaRepFromPath(image, path, GetScaleFactorFromPath(path));
|
||||
else
|
||||
succeed |= AddImageSkiaRep(image, path, 1.0f);
|
||||
succeed |= AddImageSkiaRepFromPath(image, path, 1.0f);
|
||||
|
||||
for (const ScaleFactorPair& pair : kScaleFactorPairs)
|
||||
succeed |= AddImageSkiaRep(
|
||||
succeed |= AddImageSkiaRepFromPath(
|
||||
image, path.InsertBeforeExtensionASCII(pair.name), pair.scale);
|
||||
return succeed;
|
||||
}
|
||||
@@ -423,19 +446,20 @@ void NativeImage::AddRepresentation(const mate::Dictionary& options) {
|
||||
v8::Local<v8::Value> buffer;
|
||||
GURL url;
|
||||
if (options.Get("buffer", &buffer) && node::Buffer::HasInstance(buffer)) {
|
||||
AddImageSkiaRep(
|
||||
&image_skia,
|
||||
reinterpret_cast<unsigned char*>(node::Buffer::Data(buffer)),
|
||||
node::Buffer::Length(buffer), width, height, scale_factor);
|
||||
skia_rep_added = true;
|
||||
auto* data = reinterpret_cast<unsigned char*>(node::Buffer::Data(buffer));
|
||||
auto size = node::Buffer::Length(buffer);
|
||||
skia_rep_added = AddImageSkiaRepFromBuffer(&image_skia, data, size, width,
|
||||
height, scale_factor);
|
||||
} else if (options.Get("dataURL", &url)) {
|
||||
std::string mime_type, charset, data;
|
||||
if (net::DataURL::Parse(url, &mime_type, &charset, &data)) {
|
||||
if (mime_type == "image/png" || mime_type == "image/jpeg") {
|
||||
AddImageSkiaRep(&image_skia,
|
||||
reinterpret_cast<const unsigned char*>(data.c_str()),
|
||||
data.size(), width, height, scale_factor);
|
||||
skia_rep_added = true;
|
||||
auto* data_ptr = reinterpret_cast<const unsigned char*>(data.c_str());
|
||||
if (mime_type == "image/png") {
|
||||
skia_rep_added = AddImageSkiaRepFromPNG(&image_skia, data_ptr,
|
||||
data.size(), scale_factor);
|
||||
} else if (mime_type == "image/jpeg") {
|
||||
skia_rep_added = AddImageSkiaRepFromJPEG(&image_skia, data_ptr,
|
||||
data.size(), scale_factor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -509,6 +533,11 @@ mate::Handle<NativeImage> NativeImage::CreateFromPath(
|
||||
mate::Handle<NativeImage> NativeImage::CreateFromBuffer(
|
||||
mate::Arguments* args,
|
||||
v8::Local<v8::Value> buffer) {
|
||||
if (!node::Buffer::HasInstance(buffer)) {
|
||||
args->ThrowError("buffer must be a node Buffer");
|
||||
return mate::Handle<NativeImage>();
|
||||
}
|
||||
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
double scale_factor = 1.;
|
||||
@@ -521,9 +550,9 @@ mate::Handle<NativeImage> NativeImage::CreateFromBuffer(
|
||||
}
|
||||
|
||||
gfx::ImageSkia image_skia;
|
||||
AddImageSkiaRep(&image_skia,
|
||||
reinterpret_cast<unsigned char*>(node::Buffer::Data(buffer)),
|
||||
node::Buffer::Length(buffer), width, height, scale_factor);
|
||||
AddImageSkiaRepFromBuffer(
|
||||
&image_skia, reinterpret_cast<unsigned char*>(node::Buffer::Data(buffer)),
|
||||
node::Buffer::Length(buffer), width, height, scale_factor);
|
||||
return Create(args->isolate(), gfx::Image(image_skia));
|
||||
}
|
||||
|
||||
|
||||
@@ -7,21 +7,31 @@
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "atom/browser/browser.h"
|
||||
#include "atom/common/api/locker.h"
|
||||
#include "atom/common/atom_version.h"
|
||||
#include "atom/common/chrome_version.h"
|
||||
#include "atom/common/heap_snapshot.h"
|
||||
#include "atom/common/native_mate_converters/file_path_converter.h"
|
||||
#include "atom/common/native_mate_converters/string16_converter.h"
|
||||
#include "atom/common/node_includes.h"
|
||||
#include "atom/common/promise_util.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/process/process_handle.h"
|
||||
#include "base/process/process_info.h"
|
||||
#include "base/process/process_metrics_iocounters.h"
|
||||
#include "base/sys_info.h"
|
||||
#include "base/threading/thread_restrictions.h"
|
||||
#include "brightray/common/application_info.h"
|
||||
#include "native_mate/dictionary.h"
|
||||
#include "services/resource_coordinator/public/cpp/memory_instrumentation/global_memory_dump.h"
|
||||
#include "services/resource_coordinator/public/cpp/memory_instrumentation/memory_instrumentation.h"
|
||||
|
||||
// Must be the last in the includes list, otherwise the definition of chromium
|
||||
// macros conflicts with node macros.
|
||||
#include "atom/common/node_includes.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
@@ -61,6 +71,7 @@ void AtomBindings::BindTo(v8::Isolate* isolate, v8::Local<v8::Object> process) {
|
||||
dict.SetMethod("getHeapStatistics", &GetHeapStatistics);
|
||||
dict.SetMethod("getCreationTime", &GetCreationTime);
|
||||
dict.SetMethod("getSystemMemoryInfo", &GetSystemMemoryInfo);
|
||||
dict.SetMethod("getProcessMemoryInfo", &GetProcessMemoryInfo);
|
||||
dict.SetMethod("getCPUUsage", base::Bind(&AtomBindings::GetCPUUsage,
|
||||
base::Unretained(metrics_.get())));
|
||||
dict.SetMethod("getIOCounters", &GetIOCounters);
|
||||
@@ -209,6 +220,67 @@ v8::Local<v8::Value> AtomBindings::GetSystemMemoryInfo(v8::Isolate* isolate,
|
||||
return dict.GetHandle();
|
||||
}
|
||||
|
||||
// static
|
||||
v8::Local<v8::Promise> AtomBindings::GetProcessMemoryInfo(
|
||||
v8::Isolate* isolate) {
|
||||
scoped_refptr<util::Promise> promise = new util::Promise(isolate);
|
||||
|
||||
if (mate::Locker::IsBrowserProcess() && !Browser::Get()->is_ready()) {
|
||||
promise->RejectWithErrorMessage(
|
||||
"Memory Info is available only after app ready");
|
||||
return promise->GetHandle();
|
||||
}
|
||||
|
||||
v8::Global<v8::Context> context(isolate, isolate->GetCurrentContext());
|
||||
memory_instrumentation::MemoryInstrumentation::GetInstance()
|
||||
->RequestGlobalDumpForPid(base::GetCurrentProcId(),
|
||||
std::vector<std::string>(),
|
||||
base::Bind(&AtomBindings::DidReceiveMemoryDump,
|
||||
std::move(context), promise));
|
||||
return promise->GetHandle();
|
||||
}
|
||||
|
||||
// static
|
||||
void AtomBindings::DidReceiveMemoryDump(
|
||||
const v8::Global<v8::Context>& context,
|
||||
scoped_refptr<util::Promise> promise,
|
||||
bool success,
|
||||
std::unique_ptr<memory_instrumentation::GlobalMemoryDump> global_dump) {
|
||||
v8::Isolate* isolate = promise->isolate();
|
||||
mate::Locker locker(isolate);
|
||||
v8::HandleScope handle_scope(isolate);
|
||||
v8::MicrotasksScope script_scope(isolate,
|
||||
v8::MicrotasksScope::kRunMicrotasks);
|
||||
v8::Context::Scope context_scope(
|
||||
v8::Local<v8::Context>::New(isolate, context));
|
||||
|
||||
if (!success) {
|
||||
promise->RejectWithErrorMessage("Failed to create memory dump");
|
||||
return;
|
||||
}
|
||||
|
||||
bool resolved = false;
|
||||
for (const memory_instrumentation::GlobalMemoryDump::ProcessDump& dump :
|
||||
global_dump->process_dumps()) {
|
||||
if (base::GetCurrentProcId() == dump.pid()) {
|
||||
mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate);
|
||||
const auto& osdump = dump.os_dump();
|
||||
#if defined(OS_LINUX) || defined(OS_WIN)
|
||||
dict.Set("residentSet", osdump.resident_set_kb);
|
||||
#endif
|
||||
dict.Set("private", osdump.private_footprint_kb);
|
||||
dict.Set("shared", osdump.shared_footprint_kb);
|
||||
promise->Resolve(dict.GetHandle());
|
||||
resolved = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!resolved) {
|
||||
promise->RejectWithErrorMessage(
|
||||
R"(Failed to find current process memory details in memory dump)");
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
v8::Local<v8::Value> AtomBindings::GetCPUUsage(base::ProcessMetrics* metrics,
|
||||
v8::Isolate* isolate) {
|
||||
|
||||
@@ -10,18 +10,27 @@
|
||||
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/macros.h"
|
||||
#include "base/memory/scoped_refptr.h"
|
||||
#include "base/process/process_metrics.h"
|
||||
#include "base/strings/string16.h"
|
||||
#include "native_mate/arguments.h"
|
||||
#include "uv.h" // NOLINT(build/include)
|
||||
#include "v8/include/v8.h"
|
||||
|
||||
namespace memory_instrumentation {
|
||||
class GlobalMemoryDump;
|
||||
}
|
||||
|
||||
namespace node {
|
||||
class Environment;
|
||||
}
|
||||
|
||||
namespace atom {
|
||||
|
||||
namespace util {
|
||||
class Promise;
|
||||
}
|
||||
|
||||
class AtomBindings {
|
||||
public:
|
||||
explicit AtomBindings(uv_loop_t* loop);
|
||||
@@ -41,6 +50,7 @@ class AtomBindings {
|
||||
static v8::Local<v8::Value> GetCreationTime(v8::Isolate* isolate);
|
||||
static v8::Local<v8::Value> GetSystemMemoryInfo(v8::Isolate* isolate,
|
||||
mate::Arguments* args);
|
||||
static v8::Local<v8::Promise> GetProcessMemoryInfo(v8::Isolate* isolate);
|
||||
static v8::Local<v8::Value> GetCPUUsage(base::ProcessMetrics* metrics,
|
||||
v8::Isolate* isolate);
|
||||
static v8::Local<v8::Value> GetIOCounters(v8::Isolate* isolate);
|
||||
@@ -52,6 +62,12 @@ class AtomBindings {
|
||||
|
||||
static void OnCallNextTick(uv_async_t* handle);
|
||||
|
||||
static void DidReceiveMemoryDump(
|
||||
const v8::Global<v8::Context>& context,
|
||||
scoped_refptr<util::Promise> promise,
|
||||
bool success,
|
||||
std::unique_ptr<memory_instrumentation::GlobalMemoryDump> dump);
|
||||
|
||||
uv_async_t call_next_tick_async_;
|
||||
std::list<node::Environment*> pending_next_ticks_;
|
||||
std::unique_ptr<base::ProcessMetrics> metrics_;
|
||||
|
||||
@@ -39,6 +39,10 @@ bool IsPrintingEnabled() {
|
||||
return BUILDFLAG(ENABLE_PRINTING);
|
||||
}
|
||||
|
||||
bool IsTtsEnabled() {
|
||||
return BUILDFLAG(ENABLE_TTS);
|
||||
}
|
||||
|
||||
void Initialize(v8::Local<v8::Object> exports,
|
||||
v8::Local<v8::Value> unused,
|
||||
v8::Local<v8::Context> context,
|
||||
@@ -52,6 +56,7 @@ void Initialize(v8::Local<v8::Object> exports,
|
||||
&IsFakeLocationProviderEnabled);
|
||||
dict.SetMethod("isViewApiEnabled", &IsViewApiEnabled);
|
||||
dict.SetMethod("isPrintingEnabled", &IsPrintingEnabled);
|
||||
dict.SetMethod("isTtsEnabled", &IsTtsEnabled);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#define ATOM_COMMON_ATOM_VERSION_H_
|
||||
|
||||
#define ATOM_MAJOR_VERSION 4
|
||||
#define ATOM_MINOR_VERSION 0
|
||||
#define ATOM_MINOR_VERSION 2
|
||||
#define ATOM_PATCH_VERSION 2
|
||||
// clang-format off
|
||||
// #define ATOM_PRE_RELEASE_VERSION
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#ifndef ATOM_COMMON_CHROME_VERSION_H_
|
||||
#define ATOM_COMMON_CHROME_VERSION_H_
|
||||
|
||||
#define CHROME_VERSION_STRING "69.0.3497.106"
|
||||
#define CHROME_VERSION_STRING "69.0.3497.128"
|
||||
#define CHROME_VERSION "v" CHROME_VERSION_STRING
|
||||
|
||||
#endif // ATOM_COMMON_CHROME_VERSION_H_
|
||||
|
||||
@@ -93,8 +93,13 @@ bool RegisterNonABICompliantCodeRange(void* start, size_t size_in_bytes) {
|
||||
|
||||
// All addresses are 32bit relative offsets to start.
|
||||
record->runtime_function.BeginAddress = 0;
|
||||
#if defined(_M_ARM64)
|
||||
record->runtime_function.FunctionLength =
|
||||
base::checked_cast<DWORD>(size_in_bytes);
|
||||
#else
|
||||
record->runtime_function.EndAddress =
|
||||
base::checked_cast<DWORD>(size_in_bytes);
|
||||
#endif
|
||||
record->runtime_function.UnwindData =
|
||||
offsetof(ExceptionHandlerRecord, unwind_info);
|
||||
|
||||
|
||||
@@ -181,24 +181,45 @@ bool Converter<net::HttpResponseHeaders*>::FromV8(
|
||||
if (!val->IsObject()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto addHeaderFromValue = [&isolate, &out](
|
||||
const std::string& key,
|
||||
const v8::Local<v8::Value>& localVal) {
|
||||
auto context = isolate->GetCurrentContext();
|
||||
v8::Local<v8::String> localStrVal;
|
||||
if (!localVal->ToString(context).ToLocal(&localStrVal)) {
|
||||
return false;
|
||||
}
|
||||
std::string value;
|
||||
mate::ConvertFromV8(isolate, localStrVal, &value);
|
||||
out->AddHeader(key + ": " + value);
|
||||
return true;
|
||||
};
|
||||
|
||||
auto context = isolate->GetCurrentContext();
|
||||
auto headers = v8::Local<v8::Object>::Cast(val);
|
||||
auto keys = headers->GetOwnPropertyNames();
|
||||
for (uint32_t i = 0; i < keys->Length(); i++) {
|
||||
v8::Local<v8::String> key, value;
|
||||
if (!keys->Get(i)->ToString(context).ToLocal(&key)) {
|
||||
v8::Local<v8::String> keyVal;
|
||||
if (!keys->Get(i)->ToString(context).ToLocal(&keyVal)) {
|
||||
return false;
|
||||
}
|
||||
if (!headers->Get(key)->ToString(context).ToLocal(&value)) {
|
||||
return false;
|
||||
std::string key;
|
||||
mate::ConvertFromV8(isolate, keyVal, &key);
|
||||
|
||||
auto localVal = headers->Get(keyVal);
|
||||
if (localVal->IsArray()) {
|
||||
auto values = v8::Local<v8::Array>::Cast(localVal);
|
||||
for (uint32_t j = 0; j < values->Length(); j++) {
|
||||
if (!addHeaderFromValue(key, values->Get(j))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!addHeaderFromValue(key, localVal)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
v8::String::Utf8Value key_utf8(key);
|
||||
v8::String::Utf8Value value_utf8(value);
|
||||
std::string k(*key_utf8, key_utf8.length());
|
||||
std::string v(*value_utf8, value_utf8.length());
|
||||
std::ostringstream tmp;
|
||||
tmp << k << ": " << v;
|
||||
out->AddHeader(tmp.str());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -247,6 +247,9 @@ const char kAuthServerWhitelist[] = "auth-server-whitelist";
|
||||
const char kAuthNegotiateDelegateWhitelist[] =
|
||||
"auth-negotiate-delegate-whitelist";
|
||||
|
||||
// If set, include the port in generated Kerberos SPNs.
|
||||
const char kEnableAuthNegotiatePort[] = "enable-auth-negotiate-port";
|
||||
|
||||
} // namespace switches
|
||||
|
||||
} // namespace atom
|
||||
|
||||
@@ -118,6 +118,7 @@ extern const char kDiskCacheSize[];
|
||||
extern const char kIgnoreConnectionsLimit[];
|
||||
extern const char kAuthServerWhitelist[];
|
||||
extern const char kAuthNegotiateDelegateWhitelist[];
|
||||
extern const char kEnableAuthNegotiatePort[];
|
||||
|
||||
} // namespace switches
|
||||
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/renderer/api/atom_api_web_frame.h"
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "atom/common/api/api_messages.h"
|
||||
#include "atom/common/api/event_emitter_caller.h"
|
||||
@@ -109,15 +112,15 @@ class ScriptExecutionCallback : public blink::WebScriptExecutionCallback {
|
||||
DISALLOW_COPY_AND_ASSIGN(ScriptExecutionCallback);
|
||||
};
|
||||
|
||||
class FrameSpellChecker : public content::RenderFrameVisitor {
|
||||
class FrameSetSpellChecker : public content::RenderFrameVisitor {
|
||||
public:
|
||||
explicit FrameSpellChecker(SpellCheckClient* spell_check_client,
|
||||
content::RenderFrame* main_frame)
|
||||
: spell_check_client_(spell_check_client), main_frame_(main_frame) {}
|
||||
~FrameSpellChecker() override {
|
||||
spell_check_client_ = nullptr;
|
||||
main_frame_ = nullptr;
|
||||
FrameSetSpellChecker(SpellCheckClient* spell_check_client,
|
||||
content::RenderFrame* main_frame)
|
||||
: spell_check_client_(spell_check_client), main_frame_(main_frame) {
|
||||
content::RenderFrame::ForEach(this);
|
||||
main_frame->GetWebFrame()->SetSpellCheckPanelHostClient(spell_check_client);
|
||||
}
|
||||
|
||||
bool Visit(content::RenderFrame* render_frame) override {
|
||||
auto* view = render_frame->GetRenderView();
|
||||
if (view->GetMainRenderFrame() == main_frame_ ||
|
||||
@@ -130,73 +133,130 @@ class FrameSpellChecker : public content::RenderFrameVisitor {
|
||||
private:
|
||||
SpellCheckClient* spell_check_client_;
|
||||
content::RenderFrame* main_frame_;
|
||||
DISALLOW_COPY_AND_ASSIGN(FrameSpellChecker);
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(FrameSetSpellChecker);
|
||||
};
|
||||
|
||||
class SpellCheckerHolder : public content::RenderFrameObserver {
|
||||
public:
|
||||
// Find existing holder for the |render_frame|.
|
||||
static SpellCheckerHolder* FromRenderFrame(
|
||||
content::RenderFrame* render_frame) {
|
||||
for (auto* holder : instances_) {
|
||||
if (holder->render_frame() == render_frame)
|
||||
return holder;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SpellCheckerHolder(content::RenderFrame* render_frame,
|
||||
std::unique_ptr<SpellCheckClient> spell_check_client)
|
||||
: content::RenderFrameObserver(render_frame),
|
||||
spell_check_client_(std::move(spell_check_client)) {
|
||||
DCHECK(!FromRenderFrame(render_frame));
|
||||
instances_.insert(this);
|
||||
}
|
||||
|
||||
~SpellCheckerHolder() final { instances_.erase(this); }
|
||||
|
||||
void UnsetAndDestroy() {
|
||||
FrameSetSpellChecker set_spell_checker(nullptr, render_frame());
|
||||
delete this;
|
||||
}
|
||||
|
||||
// RenderFrameObserver implementation.
|
||||
void OnDestruct() final {
|
||||
// Since we delete this in WillReleaseScriptContext, this method is unlikely
|
||||
// to be called, but override anyway since I'm not sure if there are some
|
||||
// corner cases.
|
||||
//
|
||||
// Note that while there are two "delete this", it is totally fine as the
|
||||
// observer unsubscribes automatically in destructor and the other one won't
|
||||
// be called.
|
||||
//
|
||||
// Also note that we should not call UnsetAndDestroy here, as the render
|
||||
// frame is going to be destroyed.
|
||||
delete this;
|
||||
}
|
||||
|
||||
void WillReleaseScriptContext(v8::Local<v8::Context> context,
|
||||
int world_id) final {
|
||||
// Unset spell checker when the script context is going to be released, as
|
||||
// the spell check implementation lives there.
|
||||
UnsetAndDestroy();
|
||||
}
|
||||
|
||||
private:
|
||||
static std::set<SpellCheckerHolder*> instances_;
|
||||
|
||||
std::unique_ptr<SpellCheckClient> spell_check_client_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
WebFrame::WebFrame(v8::Isolate* isolate)
|
||||
: web_frame_(blink::WebLocalFrame::FrameForCurrentContext()) {
|
||||
Init(isolate);
|
||||
// static
|
||||
std::set<SpellCheckerHolder*> SpellCheckerHolder::instances_;
|
||||
|
||||
void SetName(v8::Local<v8::Value> window, const std::string& name) {
|
||||
GetRenderFrame(window)->GetWebFrame()->SetName(
|
||||
blink::WebString::FromUTF8(name));
|
||||
}
|
||||
|
||||
WebFrame::WebFrame(v8::Isolate* isolate, blink::WebLocalFrame* blink_frame)
|
||||
: web_frame_(blink_frame) {
|
||||
Init(isolate);
|
||||
}
|
||||
|
||||
WebFrame::~WebFrame() {}
|
||||
|
||||
void WebFrame::SetName(const std::string& name) {
|
||||
web_frame_->SetName(blink::WebString::FromUTF8(name));
|
||||
}
|
||||
|
||||
double WebFrame::SetZoomLevel(double level) {
|
||||
double SetZoomLevel(v8::Local<v8::Value> window, double level) {
|
||||
double result = 0.0;
|
||||
content::RenderFrame* render_frame =
|
||||
content::RenderFrame::FromWebFrame(web_frame_);
|
||||
content::RenderFrame* render_frame = GetRenderFrame(window);
|
||||
render_frame->Send(new AtomFrameHostMsg_SetTemporaryZoomLevel(
|
||||
render_frame->GetRoutingID(), level, &result));
|
||||
return result;
|
||||
}
|
||||
|
||||
double WebFrame::GetZoomLevel() const {
|
||||
double GetZoomLevel(v8::Local<v8::Value> window) {
|
||||
double result = 0.0;
|
||||
content::RenderFrame* render_frame =
|
||||
content::RenderFrame::FromWebFrame(web_frame_);
|
||||
content::RenderFrame* render_frame = GetRenderFrame(window);
|
||||
render_frame->Send(
|
||||
new AtomFrameHostMsg_GetZoomLevel(render_frame->GetRoutingID(), &result));
|
||||
return result;
|
||||
}
|
||||
|
||||
double WebFrame::SetZoomFactor(double factor) {
|
||||
double SetZoomFactor(v8::Local<v8::Value> window, double factor) {
|
||||
return blink::WebView::ZoomLevelToZoomFactor(
|
||||
SetZoomLevel(blink::WebView::ZoomFactorToZoomLevel(factor)));
|
||||
SetZoomLevel(window, blink::WebView::ZoomFactorToZoomLevel(factor)));
|
||||
}
|
||||
|
||||
double WebFrame::GetZoomFactor() const {
|
||||
return blink::WebView::ZoomLevelToZoomFactor(GetZoomLevel());
|
||||
double GetZoomFactor(v8::Local<v8::Value> window) {
|
||||
return blink::WebView::ZoomLevelToZoomFactor(GetZoomLevel(window));
|
||||
}
|
||||
|
||||
void WebFrame::SetVisualZoomLevelLimits(double min_level, double max_level) {
|
||||
web_frame_->View()->SetDefaultPageScaleLimits(min_level, max_level);
|
||||
web_frame_->View()->SetIgnoreViewportTagScaleLimits(true);
|
||||
void SetVisualZoomLevelLimits(v8::Local<v8::Value> window,
|
||||
double min_level,
|
||||
double max_level) {
|
||||
blink::WebFrame* web_frame = GetRenderFrame(window)->GetWebFrame();
|
||||
web_frame->View()->SetDefaultPageScaleLimits(min_level, max_level);
|
||||
web_frame->View()->SetIgnoreViewportTagScaleLimits(true);
|
||||
}
|
||||
|
||||
void WebFrame::SetLayoutZoomLevelLimits(double min_level, double max_level) {
|
||||
web_frame_->View()->ZoomLimitsChanged(min_level, max_level);
|
||||
void SetLayoutZoomLevelLimits(v8::Local<v8::Value> window,
|
||||
double min_level,
|
||||
double max_level) {
|
||||
blink::WebFrame* web_frame = GetRenderFrame(window)->GetWebFrame();
|
||||
web_frame->View()->ZoomLimitsChanged(min_level, max_level);
|
||||
}
|
||||
|
||||
v8::Local<v8::Value> WebFrame::RegisterEmbedderCustomElement(
|
||||
v8::Local<v8::Value> RegisterEmbedderCustomElement(
|
||||
v8::Local<v8::Value> window,
|
||||
v8::Local<v8::Object> context,
|
||||
const base::string16& name,
|
||||
v8::Local<v8::Object> options) {
|
||||
v8::Context::Scope context_scope(context->CreationContext());
|
||||
return web_frame_->GetDocument().RegisterEmbedderCustomElement(
|
||||
blink::WebString::FromUTF16(name), options);
|
||||
return GetRenderFrame(context)
|
||||
->GetWebFrame()
|
||||
->GetDocument()
|
||||
.RegisterEmbedderCustomElement(blink::WebString::FromUTF16(name),
|
||||
options);
|
||||
}
|
||||
|
||||
int WebFrame::GetWebFrameId(v8::Local<v8::Value> content_window) {
|
||||
int GetWebFrameId(v8::Local<v8::Value> window,
|
||||
v8::Local<v8::Value> content_window) {
|
||||
// Get the WebLocalFrame before (possibly) executing any user-space JS while
|
||||
// getting the |params|. We track the status of the RenderFrame via an
|
||||
// observer in case it is deleted during user code execution.
|
||||
@@ -215,34 +275,42 @@ int WebFrame::GetWebFrameId(v8::Local<v8::Value> content_window) {
|
||||
return render_frame->GetRoutingID();
|
||||
}
|
||||
|
||||
void WebFrame::SetSpellCheckProvider(mate::Arguments* args,
|
||||
const std::string& language,
|
||||
bool auto_spell_correct_turned_on,
|
||||
v8::Local<v8::Object> provider) {
|
||||
void SetSpellCheckProvider(mate::Arguments* args,
|
||||
v8::Local<v8::Value> window,
|
||||
const std::string& language,
|
||||
bool auto_spell_correct_turned_on,
|
||||
v8::Local<v8::Object> provider) {
|
||||
if (!provider->Has(mate::StringToV8(args->isolate(), "spellCheck"))) {
|
||||
args->ThrowError("\"spellCheck\" has to be defined");
|
||||
return;
|
||||
}
|
||||
|
||||
auto client = std::make_unique<SpellCheckClient>(
|
||||
language, auto_spell_correct_turned_on, args->isolate(), provider);
|
||||
// Remove the old client.
|
||||
content::RenderFrame* render_frame = GetRenderFrame(window);
|
||||
auto* existing = SpellCheckerHolder::FromRenderFrame(render_frame);
|
||||
if (existing)
|
||||
existing->UnsetAndDestroy();
|
||||
|
||||
// Set spellchecker for all live frames in the same process or
|
||||
// in the sandbox mode for all live sub frames to this WebFrame.
|
||||
FrameSpellChecker spell_checker(
|
||||
client.get(), content::RenderFrame::FromWebFrame(web_frame_));
|
||||
content::RenderFrame::ForEach(&spell_checker);
|
||||
spell_check_client_.swap(client);
|
||||
web_frame_->SetSpellCheckPanelHostClient(spell_check_client_.get());
|
||||
auto spell_check_client = std::make_unique<SpellCheckClient>(
|
||||
language, auto_spell_correct_turned_on, args->isolate(), provider);
|
||||
FrameSetSpellChecker spell_checker(spell_check_client.get(), render_frame);
|
||||
|
||||
// Attach the spell checker to RenderFrame.
|
||||
new SpellCheckerHolder(render_frame, std::move(spell_check_client));
|
||||
}
|
||||
|
||||
void WebFrame::RegisterURLSchemeAsBypassingCSP(const std::string& scheme) {
|
||||
void RegisterURLSchemeAsBypassingCSP(v8::Local<v8::Value> window,
|
||||
const std::string& scheme) {
|
||||
// Register scheme to bypass pages's Content Security Policy.
|
||||
blink::SchemeRegistry::RegisterURLSchemeAsBypassingContentSecurityPolicy(
|
||||
WTF::String::FromUTF8(scheme.data(), scheme.length()));
|
||||
}
|
||||
|
||||
void WebFrame::RegisterURLSchemeAsPrivileged(const std::string& scheme,
|
||||
mate::Arguments* args) {
|
||||
void RegisterURLSchemeAsPrivileged(v8::Local<v8::Value> window,
|
||||
const std::string& scheme,
|
||||
mate::Arguments* args) {
|
||||
// TODO(deepak1556): blink::SchemeRegistry methods should be called
|
||||
// before any renderer threads are created. Fixing this would break
|
||||
// current api. Change it with 2.0.
|
||||
@@ -253,7 +321,7 @@ void WebFrame::RegisterURLSchemeAsPrivileged(const std::string& scheme,
|
||||
bool allowServiceWorkers = true;
|
||||
bool supportFetchAPI = true;
|
||||
bool corsEnabled = true;
|
||||
if (args->Length() == 2) {
|
||||
if (args->Length() == 3) {
|
||||
mate::Dictionary options;
|
||||
if (args->GetNext(&options)) {
|
||||
options.Get("secure", &secure);
|
||||
@@ -283,30 +351,42 @@ void WebFrame::RegisterURLSchemeAsPrivileged(const std::string& scheme,
|
||||
}
|
||||
}
|
||||
|
||||
void WebFrame::InsertText(const std::string& text) {
|
||||
web_frame_->FrameWidget()->GetActiveWebInputMethodController()->CommitText(
|
||||
blink::WebString::FromUTF8(text),
|
||||
blink::WebVector<blink::WebImeTextSpan>(), blink::WebRange(), 0);
|
||||
void InsertText(v8::Local<v8::Value> window, const std::string& text) {
|
||||
blink::WebFrame* web_frame = GetRenderFrame(window)->GetWebFrame();
|
||||
if (web_frame->IsWebLocalFrame()) {
|
||||
web_frame->ToWebLocalFrame()
|
||||
->FrameWidget()
|
||||
->GetActiveWebInputMethodController()
|
||||
->CommitText(blink::WebString::FromUTF8(text),
|
||||
blink::WebVector<blink::WebImeTextSpan>(),
|
||||
blink::WebRange(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
void WebFrame::InsertCSS(const std::string& css) {
|
||||
web_frame_->GetDocument().InsertStyleSheet(blink::WebString::FromUTF8(css));
|
||||
void InsertCSS(v8::Local<v8::Value> window, const std::string& css) {
|
||||
blink::WebFrame* web_frame = GetRenderFrame(window)->GetWebFrame();
|
||||
if (web_frame->IsWebLocalFrame()) {
|
||||
web_frame->ToWebLocalFrame()->GetDocument().InsertStyleSheet(
|
||||
blink::WebString::FromUTF8(css));
|
||||
}
|
||||
}
|
||||
|
||||
void WebFrame::ExecuteJavaScript(const base::string16& code,
|
||||
mate::Arguments* args) {
|
||||
void ExecuteJavaScript(v8::Local<v8::Value> window,
|
||||
const base::string16& code,
|
||||
mate::Arguments* args) {
|
||||
bool has_user_gesture = false;
|
||||
args->GetNext(&has_user_gesture);
|
||||
ScriptExecutionCallback::CompletionCallback completion_callback;
|
||||
args->GetNext(&completion_callback);
|
||||
std::unique_ptr<blink::WebScriptExecutionCallback> callback(
|
||||
new ScriptExecutionCallback(completion_callback));
|
||||
web_frame_->RequestExecuteScriptAndReturnValue(
|
||||
GetRenderFrame(window)->GetWebFrame()->RequestExecuteScriptAndReturnValue(
|
||||
blink::WebScriptSource(blink::WebString::FromUTF16(code)),
|
||||
has_user_gesture, callback.release());
|
||||
}
|
||||
|
||||
void WebFrame::ExecuteJavaScriptInIsolatedWorld(
|
||||
void ExecuteJavaScriptInIsolatedWorld(
|
||||
v8::Local<v8::Value> window,
|
||||
int world_id,
|
||||
const std::vector<mate::Dictionary>& scripts,
|
||||
mate::Arguments* args) {
|
||||
@@ -341,186 +421,132 @@ void WebFrame::ExecuteJavaScriptInIsolatedWorld(
|
||||
std::unique_ptr<blink::WebScriptExecutionCallback> callback(
|
||||
new ScriptExecutionCallback(completion_callback));
|
||||
|
||||
web_frame_->RequestExecuteScriptInIsolatedWorld(
|
||||
GetRenderFrame(window)->GetWebFrame()->RequestExecuteScriptInIsolatedWorld(
|
||||
world_id, &sources.front(), sources.size(), has_user_gesture,
|
||||
scriptExecutionType, callback.release());
|
||||
}
|
||||
|
||||
void WebFrame::SetIsolatedWorldSecurityOrigin(int world_id,
|
||||
const std::string& origin_url) {
|
||||
web_frame_->SetIsolatedWorldSecurityOrigin(
|
||||
void SetIsolatedWorldSecurityOrigin(v8::Local<v8::Value> window,
|
||||
int world_id,
|
||||
const std::string& origin_url) {
|
||||
GetRenderFrame(window)->GetWebFrame()->SetIsolatedWorldSecurityOrigin(
|
||||
world_id, blink::WebSecurityOrigin::CreateFromString(
|
||||
blink::WebString::FromUTF8(origin_url)));
|
||||
}
|
||||
|
||||
void WebFrame::SetIsolatedWorldContentSecurityPolicy(
|
||||
int world_id,
|
||||
const std::string& security_policy) {
|
||||
web_frame_->SetIsolatedWorldContentSecurityPolicy(
|
||||
void SetIsolatedWorldContentSecurityPolicy(v8::Local<v8::Value> window,
|
||||
int world_id,
|
||||
const std::string& security_policy) {
|
||||
GetRenderFrame(window)->GetWebFrame()->SetIsolatedWorldContentSecurityPolicy(
|
||||
world_id, blink::WebString::FromUTF8(security_policy));
|
||||
}
|
||||
|
||||
void WebFrame::SetIsolatedWorldHumanReadableName(int world_id,
|
||||
const std::string& name) {
|
||||
web_frame_->SetIsolatedWorldHumanReadableName(
|
||||
void SetIsolatedWorldHumanReadableName(v8::Local<v8::Value> window,
|
||||
int world_id,
|
||||
const std::string& name) {
|
||||
GetRenderFrame(window)->GetWebFrame()->SetIsolatedWorldHumanReadableName(
|
||||
world_id, blink::WebString::FromUTF8(name));
|
||||
}
|
||||
|
||||
// static
|
||||
mate::Handle<WebFrame> WebFrame::Create(v8::Isolate* isolate) {
|
||||
return mate::CreateHandle(isolate, new WebFrame(isolate));
|
||||
}
|
||||
|
||||
blink::WebCache::ResourceTypeStats WebFrame::GetResourceUsage(
|
||||
v8::Isolate* isolate) {
|
||||
blink::WebCache::ResourceTypeStats GetResourceUsage(v8::Isolate* isolate) {
|
||||
blink::WebCache::ResourceTypeStats stats;
|
||||
blink::WebCache::GetResourceTypeStats(&stats);
|
||||
return stats;
|
||||
}
|
||||
|
||||
void WebFrame::ClearCache(v8::Isolate* isolate) {
|
||||
void ClearCache(v8::Isolate* isolate) {
|
||||
isolate->IdleNotificationDeadline(0.5);
|
||||
blink::WebCache::Clear();
|
||||
base::MemoryPressureListener::NotifyMemoryPressure(
|
||||
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL);
|
||||
}
|
||||
|
||||
v8::Local<v8::Value> WebFrame::Opener() const {
|
||||
blink::WebFrame* frame = web_frame_->Opener();
|
||||
if (frame && frame->IsWebLocalFrame())
|
||||
return mate::CreateHandle(isolate(),
|
||||
new WebFrame(isolate(), frame->ToWebLocalFrame()))
|
||||
.ToV8();
|
||||
else
|
||||
return v8::Null(isolate());
|
||||
}
|
||||
|
||||
v8::Local<v8::Value> WebFrame::Parent() const {
|
||||
blink::WebFrame* frame = web_frame_->Parent();
|
||||
if (frame && frame->IsWebLocalFrame())
|
||||
return mate::CreateHandle(isolate(),
|
||||
new WebFrame(isolate(), frame->ToWebLocalFrame()))
|
||||
.ToV8();
|
||||
else
|
||||
return v8::Null(isolate());
|
||||
}
|
||||
|
||||
v8::Local<v8::Value> WebFrame::Top() const {
|
||||
blink::WebFrame* frame = web_frame_->Top();
|
||||
if (frame && frame->IsWebLocalFrame())
|
||||
return mate::CreateHandle(isolate(),
|
||||
new WebFrame(isolate(), frame->ToWebLocalFrame()))
|
||||
.ToV8();
|
||||
else
|
||||
return v8::Null(isolate());
|
||||
}
|
||||
|
||||
v8::Local<v8::Value> WebFrame::FirstChild() const {
|
||||
blink::WebFrame* frame = web_frame_->FirstChild();
|
||||
if (frame && frame->IsWebLocalFrame())
|
||||
return mate::CreateHandle(isolate(),
|
||||
new WebFrame(isolate(), frame->ToWebLocalFrame()))
|
||||
.ToV8();
|
||||
else
|
||||
return v8::Null(isolate());
|
||||
}
|
||||
|
||||
v8::Local<v8::Value> WebFrame::NextSibling() const {
|
||||
blink::WebFrame* frame = web_frame_->NextSibling();
|
||||
if (frame && frame->IsWebLocalFrame())
|
||||
return mate::CreateHandle(isolate(),
|
||||
new WebFrame(isolate(), frame->ToWebLocalFrame()))
|
||||
.ToV8();
|
||||
else
|
||||
return v8::Null(isolate());
|
||||
}
|
||||
|
||||
v8::Local<v8::Value> WebFrame::GetFrameForSelector(
|
||||
const std::string& selector) const {
|
||||
blink::WebElement element = web_frame_->GetDocument().QuerySelector(
|
||||
blink::WebString::FromUTF8(selector));
|
||||
blink::WebLocalFrame* element_frame =
|
||||
blink::WebLocalFrame::FromFrameOwnerElement(element);
|
||||
if (element_frame)
|
||||
return mate::CreateHandle(isolate(), new WebFrame(isolate(), element_frame))
|
||||
.ToV8();
|
||||
else
|
||||
return v8::Null(isolate());
|
||||
}
|
||||
|
||||
v8::Local<v8::Value> WebFrame::FindFrameByName(const std::string& name) const {
|
||||
blink::WebLocalFrame* local_frame =
|
||||
web_frame_->FindFrameByName(blink::WebString::FromUTF8(name))
|
||||
->ToWebLocalFrame();
|
||||
if (local_frame)
|
||||
return mate::CreateHandle(isolate(), new WebFrame(isolate(), local_frame))
|
||||
.ToV8();
|
||||
else
|
||||
return v8::Null(isolate());
|
||||
}
|
||||
|
||||
v8::Local<v8::Value> WebFrame::FindFrameByRoutingId(int routing_id) const {
|
||||
v8::Local<v8::Value> FindFrameByRoutingId(v8::Isolate* isolate,
|
||||
v8::Local<v8::Value> window,
|
||||
int routing_id) {
|
||||
content::RenderFrame* render_frame =
|
||||
content::RenderFrame::FromRoutingID(routing_id);
|
||||
blink::WebLocalFrame* local_frame = nullptr;
|
||||
if (render_frame)
|
||||
local_frame = render_frame->GetWebFrame();
|
||||
if (local_frame)
|
||||
return mate::CreateHandle(isolate(), new WebFrame(isolate(), local_frame))
|
||||
.ToV8();
|
||||
return render_frame->GetWebFrame()->MainWorldScriptContext()->Global();
|
||||
else
|
||||
return v8::Null(isolate());
|
||||
return v8::Null(isolate);
|
||||
}
|
||||
|
||||
v8::Local<v8::Value> WebFrame::RoutingId() const {
|
||||
int routing_id = content::RenderFrame::GetRoutingIdForWebFrame(web_frame_);
|
||||
return v8::Number::New(isolate(), routing_id);
|
||||
v8::Local<v8::Value> GetOpener(v8::Isolate* isolate,
|
||||
v8::Local<v8::Value> window) {
|
||||
blink::WebFrame* frame = GetRenderFrame(window)->GetWebFrame()->Opener();
|
||||
if (frame && frame->IsWebLocalFrame())
|
||||
return frame->ToWebLocalFrame()->MainWorldScriptContext()->Global();
|
||||
else
|
||||
return v8::Null(isolate);
|
||||
}
|
||||
|
||||
// static
|
||||
void WebFrame::BuildPrototype(v8::Isolate* isolate,
|
||||
v8::Local<v8::FunctionTemplate> prototype) {
|
||||
prototype->SetClassName(mate::StringToV8(isolate, "WebFrame"));
|
||||
mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate())
|
||||
.SetMethod("setName", &WebFrame::SetName)
|
||||
.SetMethod("setZoomLevel", &WebFrame::SetZoomLevel)
|
||||
.SetMethod("getZoomLevel", &WebFrame::GetZoomLevel)
|
||||
.SetMethod("setZoomFactor", &WebFrame::SetZoomFactor)
|
||||
.SetMethod("getZoomFactor", &WebFrame::GetZoomFactor)
|
||||
.SetMethod("setVisualZoomLevelLimits",
|
||||
&WebFrame::SetVisualZoomLevelLimits)
|
||||
.SetMethod("setLayoutZoomLevelLimits",
|
||||
&WebFrame::SetLayoutZoomLevelLimits)
|
||||
.SetMethod("registerEmbedderCustomElement",
|
||||
&WebFrame::RegisterEmbedderCustomElement)
|
||||
.SetMethod("getWebFrameId", &WebFrame::GetWebFrameId)
|
||||
.SetMethod("setSpellCheckProvider", &WebFrame::SetSpellCheckProvider)
|
||||
.SetMethod("registerURLSchemeAsBypassingCSP",
|
||||
&WebFrame::RegisterURLSchemeAsBypassingCSP)
|
||||
.SetMethod("registerURLSchemeAsPrivileged",
|
||||
&WebFrame::RegisterURLSchemeAsPrivileged)
|
||||
.SetMethod("insertText", &WebFrame::InsertText)
|
||||
.SetMethod("insertCSS", &WebFrame::InsertCSS)
|
||||
.SetMethod("executeJavaScript", &WebFrame::ExecuteJavaScript)
|
||||
.SetMethod("executeJavaScriptInIsolatedWorld",
|
||||
&WebFrame::ExecuteJavaScriptInIsolatedWorld)
|
||||
.SetMethod("setIsolatedWorldSecurityOrigin",
|
||||
&WebFrame::SetIsolatedWorldSecurityOrigin)
|
||||
.SetMethod("setIsolatedWorldContentSecurityPolicy",
|
||||
&WebFrame::SetIsolatedWorldContentSecurityPolicy)
|
||||
.SetMethod("setIsolatedWorldHumanReadableName",
|
||||
&WebFrame::SetIsolatedWorldHumanReadableName)
|
||||
.SetMethod("getResourceUsage", &WebFrame::GetResourceUsage)
|
||||
.SetMethod("clearCache", &WebFrame::ClearCache)
|
||||
.SetMethod("getFrameForSelector", &WebFrame::GetFrameForSelector)
|
||||
.SetMethod("findFrameByName", &WebFrame::FindFrameByName)
|
||||
.SetProperty("opener", &WebFrame::Opener)
|
||||
.SetProperty("parent", &WebFrame::Parent)
|
||||
.SetProperty("top", &WebFrame::Top)
|
||||
.SetProperty("firstChild", &WebFrame::FirstChild)
|
||||
.SetProperty("nextSibling", &WebFrame::NextSibling)
|
||||
.SetProperty("routingId", &WebFrame::RoutingId)
|
||||
.SetMethod("findFrameByRoutingId", &WebFrame::FindFrameByRoutingId);
|
||||
// Don't name it as GetParent, Windows has API with same name.
|
||||
v8::Local<v8::Value> GetFrameParent(v8::Isolate* isolate,
|
||||
v8::Local<v8::Value> window) {
|
||||
blink::WebFrame* frame = GetRenderFrame(window)->GetWebFrame()->Parent();
|
||||
if (frame && frame->IsWebLocalFrame())
|
||||
return frame->ToWebLocalFrame()->MainWorldScriptContext()->Global();
|
||||
else
|
||||
return v8::Null(isolate);
|
||||
}
|
||||
|
||||
v8::Local<v8::Value> GetTop(v8::Isolate* isolate, v8::Local<v8::Value> window) {
|
||||
blink::WebFrame* frame = GetRenderFrame(window)->GetWebFrame()->Top();
|
||||
if (frame && frame->IsWebLocalFrame())
|
||||
return frame->ToWebLocalFrame()->MainWorldScriptContext()->Global();
|
||||
else
|
||||
return v8::Null(isolate);
|
||||
}
|
||||
|
||||
v8::Local<v8::Value> GetFirstChild(v8::Isolate* isolate,
|
||||
v8::Local<v8::Value> window) {
|
||||
blink::WebFrame* frame = GetRenderFrame(window)->GetWebFrame()->FirstChild();
|
||||
if (frame && frame->IsWebLocalFrame())
|
||||
return frame->ToWebLocalFrame()->MainWorldScriptContext()->Global();
|
||||
else
|
||||
return v8::Null(isolate);
|
||||
}
|
||||
|
||||
v8::Local<v8::Value> GetNextSibling(v8::Isolate* isolate,
|
||||
v8::Local<v8::Value> window) {
|
||||
blink::WebFrame* frame = GetRenderFrame(window)->GetWebFrame()->NextSibling();
|
||||
if (frame && frame->IsWebLocalFrame())
|
||||
return frame->ToWebLocalFrame()->MainWorldScriptContext()->Global();
|
||||
else
|
||||
return v8::Null(isolate);
|
||||
}
|
||||
|
||||
v8::Local<v8::Value> GetFrameForSelector(v8::Isolate* isolate,
|
||||
v8::Local<v8::Value> window,
|
||||
const std::string& selector) {
|
||||
blink::WebElement element =
|
||||
GetRenderFrame(window)->GetWebFrame()->GetDocument().QuerySelector(
|
||||
blink::WebString::FromUTF8(selector));
|
||||
if (element.IsNull()) // not found
|
||||
return v8::Null(isolate);
|
||||
|
||||
blink::WebFrame* frame = blink::WebFrame::FromFrameOwnerElement(element);
|
||||
if (frame && frame->IsWebLocalFrame())
|
||||
return frame->ToWebLocalFrame()->MainWorldScriptContext()->Global();
|
||||
else
|
||||
return v8::Null(isolate);
|
||||
}
|
||||
|
||||
v8::Local<v8::Value> FindFrameByName(v8::Isolate* isolate,
|
||||
v8::Local<v8::Value> window,
|
||||
const std::string& name) {
|
||||
blink::WebFrame* frame =
|
||||
GetRenderFrame(window)->GetWebFrame()->FindFrameByName(
|
||||
blink::WebString::FromUTF8(name));
|
||||
if (frame && frame->IsWebLocalFrame())
|
||||
return frame->ToWebLocalFrame()->MainWorldScriptContext()->Global();
|
||||
else
|
||||
return v8::Null(isolate);
|
||||
}
|
||||
|
||||
int GetRoutingId(v8::Local<v8::Value> window) {
|
||||
return GetRenderFrame(window)->GetRoutingID();
|
||||
}
|
||||
|
||||
} // namespace api
|
||||
@@ -529,16 +555,51 @@ void WebFrame::BuildPrototype(v8::Isolate* isolate,
|
||||
|
||||
namespace {
|
||||
|
||||
using atom::api::WebFrame;
|
||||
|
||||
void Initialize(v8::Local<v8::Object> exports,
|
||||
v8::Local<v8::Value> unused,
|
||||
v8::Local<v8::Context> context,
|
||||
void* priv) {
|
||||
using namespace atom::api; // NOLINT(build/namespaces)
|
||||
|
||||
v8::Isolate* isolate = context->GetIsolate();
|
||||
mate::Dictionary dict(isolate, exports);
|
||||
dict.Set("webFrame", WebFrame::Create(isolate));
|
||||
dict.Set("WebFrame", WebFrame::GetConstructor(isolate)->GetFunction());
|
||||
dict.SetMethod("setName", &SetName);
|
||||
dict.SetMethod("setZoomLevel", &SetZoomLevel);
|
||||
dict.SetMethod("getZoomLevel", &GetZoomLevel);
|
||||
dict.SetMethod("setZoomFactor", &SetZoomFactor);
|
||||
dict.SetMethod("getZoomFactor", &GetZoomFactor);
|
||||
dict.SetMethod("setVisualZoomLevelLimits", &SetVisualZoomLevelLimits);
|
||||
dict.SetMethod("setLayoutZoomLevelLimits", &SetLayoutZoomLevelLimits);
|
||||
dict.SetMethod("registerEmbedderCustomElement",
|
||||
&RegisterEmbedderCustomElement);
|
||||
dict.SetMethod("getWebFrameId", &GetWebFrameId);
|
||||
dict.SetMethod("setSpellCheckProvider", &SetSpellCheckProvider);
|
||||
dict.SetMethod("registerURLSchemeAsBypassingCSP",
|
||||
&RegisterURLSchemeAsBypassingCSP);
|
||||
dict.SetMethod("registerURLSchemeAsPrivileged",
|
||||
&RegisterURLSchemeAsPrivileged);
|
||||
dict.SetMethod("insertText", &InsertText);
|
||||
dict.SetMethod("insertCSS", &InsertCSS);
|
||||
dict.SetMethod("executeJavaScript", &ExecuteJavaScript);
|
||||
dict.SetMethod("executeJavaScriptInIsolatedWorld",
|
||||
&ExecuteJavaScriptInIsolatedWorld);
|
||||
dict.SetMethod("setIsolatedWorldSecurityOrigin",
|
||||
&SetIsolatedWorldSecurityOrigin);
|
||||
dict.SetMethod("setIsolatedWorldContentSecurityPolicy",
|
||||
&SetIsolatedWorldContentSecurityPolicy);
|
||||
dict.SetMethod("setIsolatedWorldHumanReadableName",
|
||||
&SetIsolatedWorldHumanReadableName);
|
||||
dict.SetMethod("getResourceUsage", &GetResourceUsage);
|
||||
dict.SetMethod("clearCache", &ClearCache);
|
||||
dict.SetMethod("_findFrameByRoutingId", &FindFrameByRoutingId);
|
||||
dict.SetMethod("_getFrameForSelector", &GetFrameForSelector);
|
||||
dict.SetMethod("_findFrameByName", &FindFrameByName);
|
||||
dict.SetMethod("_getOpener", &GetOpener);
|
||||
dict.SetMethod("_getParent", &GetFrameParent);
|
||||
dict.SetMethod("_getTop", &GetTop);
|
||||
dict.SetMethod("_getFirstChild", &GetFirstChild);
|
||||
dict.SetMethod("_getNextSibling", &GetNextSibling);
|
||||
dict.SetMethod("_getRoutingId", &GetRoutingId);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
// Copyright (c) 2014 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_RENDERER_API_ATOM_API_WEB_FRAME_H_
|
||||
#define ATOM_RENDERER_API_ATOM_API_WEB_FRAME_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "native_mate/handle.h"
|
||||
#include "native_mate/wrappable.h"
|
||||
#include "third_party/blink/public/platform/web_cache.h"
|
||||
|
||||
namespace blink {
|
||||
class WebLocalFrame;
|
||||
}
|
||||
|
||||
namespace mate {
|
||||
class Dictionary;
|
||||
class Arguments;
|
||||
} // namespace mate
|
||||
|
||||
namespace atom {
|
||||
|
||||
namespace api {
|
||||
|
||||
class SpellCheckClient;
|
||||
|
||||
class WebFrame : public mate::Wrappable<WebFrame> {
|
||||
public:
|
||||
static mate::Handle<WebFrame> Create(v8::Isolate* isolate);
|
||||
|
||||
static void BuildPrototype(v8::Isolate* isolate,
|
||||
v8::Local<v8::FunctionTemplate> prototype);
|
||||
|
||||
private:
|
||||
explicit WebFrame(v8::Isolate* isolate);
|
||||
explicit WebFrame(v8::Isolate* isolate, blink::WebLocalFrame* blink_frame);
|
||||
~WebFrame() override;
|
||||
|
||||
void SetName(const std::string& name);
|
||||
|
||||
double SetZoomLevel(double level);
|
||||
double GetZoomLevel() const;
|
||||
double SetZoomFactor(double factor);
|
||||
double GetZoomFactor() const;
|
||||
|
||||
void SetVisualZoomLevelLimits(double min_level, double max_level);
|
||||
void SetLayoutZoomLevelLimits(double min_level, double max_level);
|
||||
|
||||
v8::Local<v8::Value> RegisterEmbedderCustomElement(
|
||||
v8::Local<v8::Object> context,
|
||||
const base::string16& name,
|
||||
v8::Local<v8::Object> options);
|
||||
int GetWebFrameId(v8::Local<v8::Value> content_window);
|
||||
|
||||
// Set the provider that will be used by SpellCheckClient for spell check.
|
||||
void SetSpellCheckProvider(mate::Arguments* args,
|
||||
const std::string& language,
|
||||
bool auto_spell_correct_turned_on,
|
||||
v8::Local<v8::Object> provider);
|
||||
|
||||
void RegisterURLSchemeAsBypassingCSP(const std::string& scheme);
|
||||
void RegisterURLSchemeAsPrivileged(const std::string& scheme,
|
||||
mate::Arguments* args);
|
||||
|
||||
// Editing.
|
||||
void InsertText(const std::string& text);
|
||||
void InsertCSS(const std::string& css);
|
||||
|
||||
// Executing scripts.
|
||||
void ExecuteJavaScript(const base::string16& code, mate::Arguments* args);
|
||||
void ExecuteJavaScriptInIsolatedWorld(
|
||||
int world_id,
|
||||
const std::vector<mate::Dictionary>& scripts,
|
||||
mate::Arguments* args);
|
||||
|
||||
// Isolated world related methods
|
||||
void SetIsolatedWorldSecurityOrigin(int world_id,
|
||||
const std::string& origin_url);
|
||||
void SetIsolatedWorldContentSecurityPolicy(
|
||||
int world_id,
|
||||
const std::string& security_policy);
|
||||
void SetIsolatedWorldHumanReadableName(int world_id, const std::string& name);
|
||||
|
||||
// Resource related methods
|
||||
blink::WebCache::ResourceTypeStats GetResourceUsage(v8::Isolate* isolate);
|
||||
void ClearCache(v8::Isolate* isolate);
|
||||
|
||||
// Frame navigation
|
||||
v8::Local<v8::Value> Opener() const;
|
||||
v8::Local<v8::Value> Parent() const;
|
||||
v8::Local<v8::Value> Top() const;
|
||||
v8::Local<v8::Value> FirstChild() const;
|
||||
v8::Local<v8::Value> NextSibling() const;
|
||||
v8::Local<v8::Value> GetFrameForSelector(const std::string& selector) const;
|
||||
v8::Local<v8::Value> FindFrameByName(const std::string& name) const;
|
||||
v8::Local<v8::Value> FindFrameByRoutingId(int routing_id) const;
|
||||
v8::Local<v8::Value> RoutingId() const;
|
||||
|
||||
std::unique_ptr<SpellCheckClient> spell_check_client_;
|
||||
|
||||
blink::WebLocalFrame* web_frame_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(WebFrame);
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // ATOM_RENDERER_API_ATOM_API_WEB_FRAME_H_
|
||||
@@ -216,7 +216,7 @@ void AtomRenderFrameObserver::OnTakeHeapSnapshot(
|
||||
args.AppendBoolean(success);
|
||||
|
||||
render_frame_->Send(new AtomFrameHostMsg_Message(
|
||||
render_frame_->GetRoutingID(), "ipc-message", args));
|
||||
render_frame_->GetRoutingID(), "ipc-internal-message", args));
|
||||
}
|
||||
|
||||
void AtomRenderFrameObserver::EmitIPCEvent(blink::WebLocalFrame* frame,
|
||||
|
||||
@@ -158,6 +158,8 @@ void AtomSandboxedRendererClient::InitializeBindings(
|
||||
process.SetMethod("hang", AtomBindings::Hang);
|
||||
process.SetMethod("getHeapStatistics", &AtomBindings::GetHeapStatistics);
|
||||
process.SetMethod("getSystemMemoryInfo", &AtomBindings::GetSystemMemoryInfo);
|
||||
process.SetMethod("getProcessMemoryInfo",
|
||||
&AtomBindings::GetProcessMemoryInfo);
|
||||
process.SetMethod(
|
||||
"getCPUUsage",
|
||||
base::Bind(&AtomBindings::GetCPUUsage, base::Unretained(metrics_.get())));
|
||||
@@ -230,6 +232,8 @@ void AtomSandboxedRendererClient::DidCreateScriptContext(
|
||||
// Execute the function with proper arguments
|
||||
ignore_result(
|
||||
func->Call(context, v8::Null(isolate), node::arraysize(args), args));
|
||||
|
||||
InvokeIpcCallback(context, "onLoaded", std::vector<v8::Local<v8::Value>>());
|
||||
}
|
||||
|
||||
void AtomSandboxedRendererClient::WillReleaseScriptContext(
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
#include "base/command_line.h"
|
||||
#include "base/strings/string_split.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "chrome/renderer/tts_dispatcher.h"
|
||||
#include "content/public/common/content_constants.h"
|
||||
#include "content/public/common/content_switches.h"
|
||||
#include "content/public/renderer/render_frame.h"
|
||||
@@ -56,6 +55,10 @@
|
||||
#include "components/printing/renderer/print_render_frame_helper.h"
|
||||
#endif // BUILDFLAG(ENABLE_PRINTING)
|
||||
|
||||
#if BUILDFLAG(ENABLE_TTS)
|
||||
#include "chrome/renderer/tts_dispatcher.h"
|
||||
#endif // BUILDFLAG(ENABLE_TTS)
|
||||
|
||||
namespace atom {
|
||||
|
||||
namespace {
|
||||
@@ -228,7 +231,11 @@ void RendererClientBase::DidClearWindowObject(
|
||||
std::unique_ptr<blink::WebSpeechSynthesizer>
|
||||
RendererClientBase::OverrideSpeechSynthesizer(
|
||||
blink::WebSpeechSynthesizerClient* client) {
|
||||
#if BUILDFLAG(ENABLE_TTS)
|
||||
return std::make_unique<TtsDispatcher>(client);
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool RendererClientBase::OverrideCreatePlugin(
|
||||
|
||||
@@ -13,8 +13,6 @@
|
||||
|
||||
namespace brightray {
|
||||
|
||||
int g_identifier_ = 1;
|
||||
|
||||
CocoaNotification::CocoaNotification(NotificationDelegate* delegate,
|
||||
NotificationPresenter* presenter)
|
||||
: Notification(delegate, presenter) {}
|
||||
@@ -29,7 +27,9 @@ void CocoaNotification::Show(const NotificationOptions& options) {
|
||||
notification_.reset([[NSUserNotification alloc] init]);
|
||||
|
||||
NSString* identifier =
|
||||
[NSString stringWithFormat:@"ElectronNotification%d", g_identifier_++];
|
||||
[NSString stringWithFormat:@"%@:notification:%@",
|
||||
[[NSBundle mainBundle] bundleIdentifier],
|
||||
[[[NSUUID alloc] init] UUIDString]];
|
||||
|
||||
[notification_ setTitle:base::SysUTF16ToNSString(options.title)];
|
||||
[notification_ setSubtitle:base::SysUTF16ToNSString(options.subtitle)];
|
||||
@@ -115,6 +115,8 @@ void CocoaNotification::Dismiss() {
|
||||
NotificationDismissed();
|
||||
|
||||
this->LogAction("dismissed");
|
||||
|
||||
notification_.reset(nil);
|
||||
}
|
||||
|
||||
void CocoaNotification::NotificationDisplayed() {
|
||||
|
||||
@@ -20,10 +20,6 @@ std::string GetProductInternal() {
|
||||
GetApplicationVersion().c_str());
|
||||
}
|
||||
|
||||
std::string GetBrightrayUserAgent() {
|
||||
return content::BuildUserAgentFromProduct(GetProductInternal());
|
||||
}
|
||||
|
||||
ContentClient::ContentClient() {}
|
||||
|
||||
ContentClient::~ContentClient() {}
|
||||
@@ -32,10 +28,6 @@ std::string ContentClient::GetProduct() const {
|
||||
return GetProductInternal();
|
||||
}
|
||||
|
||||
std::string ContentClient::GetUserAgent() const {
|
||||
return GetBrightrayUserAgent();
|
||||
}
|
||||
|
||||
base::string16 ContentClient::GetLocalizedString(int message_id) const {
|
||||
return l10n_util::GetStringUTF16(message_id);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ class ContentClient : public content::ContentClient {
|
||||
|
||||
private:
|
||||
std::string GetProduct() const override;
|
||||
std::string GetUserAgent() const override;
|
||||
base::string16 GetLocalizedString(int message_id) const override;
|
||||
base::StringPiece GetDataResource(int resource_id,
|
||||
ui::ScaleFactor) const override;
|
||||
|
||||
@@ -12,5 +12,6 @@ proprietary_codecs = true
|
||||
ffmpeg_branding = "Chrome"
|
||||
|
||||
enable_basic_printing = true
|
||||
angle_enable_vulkan_validation_layers = false
|
||||
|
||||
is_cfi = false
|
||||
|
||||
@@ -15,6 +15,7 @@ buildflag_header("buildflags") {
|
||||
"ENABLE_VIEW_API=$enable_view_api",
|
||||
"ENABLE_PEPPER_FLASH=$enable_pepper_flash",
|
||||
"ENABLE_PDF_VIEWER=$enable_pdf_viewer",
|
||||
"ENABLE_TTS=$enable_tts",
|
||||
"OVERRIDE_LOCATION_PROVIDER=$enable_fake_location_provider",
|
||||
]
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ declare_args() {
|
||||
|
||||
enable_pdf_viewer = false
|
||||
|
||||
enable_tts = true
|
||||
|
||||
# Provide a fake location provider for mocking
|
||||
# the geolocation responses. Disable it if you
|
||||
# need to test with chromium's location provider.
|
||||
|
||||
@@ -99,4 +99,28 @@ source_set("chrome") {
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
if (enable_tts) {
|
||||
sources += [
|
||||
"//chrome/browser/speech/tts_controller.h",
|
||||
"//chrome/browser/speech/tts_controller_impl.cc",
|
||||
"//chrome/browser/speech/tts_controller_impl.h",
|
||||
"//chrome/browser/speech/tts_mac.mm",
|
||||
"//chrome/browser/speech/tts_message_filter.cc",
|
||||
"//chrome/browser/speech/tts_message_filter.h",
|
||||
"//chrome/browser/speech/tts_platform.cc",
|
||||
"//chrome/browser/speech/tts_platform.h",
|
||||
"//chrome/browser/speech/tts_win.cc",
|
||||
"//chrome/common/tts_messages.h",
|
||||
"//chrome/common/tts_utterance_request.cc",
|
||||
"//chrome/common/tts_utterance_request.h",
|
||||
"//chrome/renderer/tts_dispatcher.cc",
|
||||
"//chrome/renderer/tts_dispatcher.h",
|
||||
]
|
||||
|
||||
if (is_linux) {
|
||||
sources += [ "//chrome/browser/speech/tts_linux.cc" ]
|
||||
deps += [ "//third_party/speech-dispatcher" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,336 +0,0 @@
|
||||
// Copyright (c) 2012 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_SPEECH_TTS_CONTROLLER_H_
|
||||
#define CHROME_BROWSER_SPEECH_TTS_CONTROLLER_H_
|
||||
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/memory/singleton.h"
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "url/gurl.h"
|
||||
|
||||
class Utterance;
|
||||
class TtsPlatformImpl;
|
||||
|
||||
namespace base {
|
||||
class Value;
|
||||
}
|
||||
|
||||
namespace content {
|
||||
class BrowserContext;
|
||||
}
|
||||
|
||||
// Events sent back from the TTS engine indicating the progress.
|
||||
enum TtsEventType {
|
||||
TTS_EVENT_START,
|
||||
TTS_EVENT_END,
|
||||
TTS_EVENT_WORD,
|
||||
TTS_EVENT_SENTENCE,
|
||||
TTS_EVENT_MARKER,
|
||||
TTS_EVENT_INTERRUPTED,
|
||||
TTS_EVENT_CANCELLED,
|
||||
TTS_EVENT_ERROR,
|
||||
TTS_EVENT_PAUSE,
|
||||
TTS_EVENT_RESUME
|
||||
};
|
||||
|
||||
enum TtsGenderType { TTS_GENDER_NONE, TTS_GENDER_MALE, TTS_GENDER_FEMALE };
|
||||
|
||||
// Returns true if this event type is one that indicates an utterance
|
||||
// is finished and can be destroyed.
|
||||
bool IsFinalTtsEventType(TtsEventType event_type);
|
||||
|
||||
// The continuous parameters that apply to a given utterance.
|
||||
struct UtteranceContinuousParameters {
|
||||
UtteranceContinuousParameters();
|
||||
|
||||
double rate;
|
||||
double pitch;
|
||||
double volume;
|
||||
};
|
||||
|
||||
// Information about one voice.
|
||||
struct VoiceData {
|
||||
VoiceData();
|
||||
VoiceData(const VoiceData&);
|
||||
~VoiceData();
|
||||
|
||||
std::string name;
|
||||
std::string lang;
|
||||
TtsGenderType gender;
|
||||
std::string extension_id;
|
||||
std::set<TtsEventType> events;
|
||||
|
||||
// If true, the synthesis engine is a remote network resource.
|
||||
// It may be higher latency and may incur bandwidth costs.
|
||||
bool remote;
|
||||
|
||||
// If true, this is implemented by this platform's subclass of
|
||||
// TtsPlatformImpl. If false, this is implemented by an extension.
|
||||
bool native;
|
||||
std::string native_voice_identifier;
|
||||
};
|
||||
|
||||
// Interface that delegates TTS requests to user-installed extensions.
|
||||
class TtsEngineDelegate {
|
||||
public:
|
||||
virtual ~TtsEngineDelegate() {}
|
||||
|
||||
// Return a list of all available voices registered.
|
||||
virtual void GetVoices(content::BrowserContext* browser_context,
|
||||
std::vector<VoiceData>* out_voices) = 0;
|
||||
|
||||
// Speak the given utterance by sending an event to the given TTS engine.
|
||||
virtual void Speak(Utterance* utterance, const VoiceData& voice) = 0;
|
||||
|
||||
// Stop speaking the given utterance by sending an event to the target
|
||||
// associated with this utterance.
|
||||
virtual void Stop(Utterance* utterance) = 0;
|
||||
|
||||
// Pause in the middle of speaking this utterance.
|
||||
virtual void Pause(Utterance* utterance) = 0;
|
||||
|
||||
// Resume speaking this utterance.
|
||||
virtual void Resume(Utterance* utterance) = 0;
|
||||
|
||||
// Load the built-in component extension for ChromeOS.
|
||||
virtual bool LoadBuiltInTtsExtension(
|
||||
content::BrowserContext* browser_context) = 0;
|
||||
};
|
||||
|
||||
// Class that wants to receive events on utterances.
|
||||
class UtteranceEventDelegate {
|
||||
public:
|
||||
virtual ~UtteranceEventDelegate() {}
|
||||
virtual void OnTtsEvent(Utterance* utterance,
|
||||
TtsEventType event_type,
|
||||
int char_index,
|
||||
const std::string& error_message) = 0;
|
||||
};
|
||||
|
||||
// Class that wants to be notified when the set of
|
||||
// voices has changed.
|
||||
class VoicesChangedDelegate {
|
||||
public:
|
||||
virtual ~VoicesChangedDelegate() {}
|
||||
virtual void OnVoicesChanged() = 0;
|
||||
};
|
||||
|
||||
// One speech utterance.
|
||||
class Utterance {
|
||||
public:
|
||||
// Construct an utterance given a profile and a completion task to call
|
||||
// when the utterance is done speaking. Before speaking this utterance,
|
||||
// its other parameters like text, rate, pitch, etc. should all be set.
|
||||
explicit Utterance(content::BrowserContext* browser_context);
|
||||
~Utterance();
|
||||
|
||||
// Sends an event to the delegate. If the event type is TTS_EVENT_END
|
||||
// or TTS_EVENT_ERROR, deletes the utterance. If |char_index| is -1,
|
||||
// uses the last good value.
|
||||
void OnTtsEvent(TtsEventType event_type,
|
||||
int char_index,
|
||||
const std::string& error_message);
|
||||
|
||||
// Finish an utterance without sending an event to the delegate.
|
||||
void Finish();
|
||||
|
||||
// Getters and setters for the text to speak and other speech options.
|
||||
void set_text(const std::string& text) { text_ = text; }
|
||||
const std::string& text() const { return text_; }
|
||||
|
||||
void set_options(const base::Value* options);
|
||||
const base::Value* options() const { return options_.get(); }
|
||||
|
||||
void set_src_extension_id(const std::string& src_extension_id) {
|
||||
src_extension_id_ = src_extension_id;
|
||||
}
|
||||
const std::string& src_extension_id() { return src_extension_id_; }
|
||||
|
||||
void set_src_id(int src_id) { src_id_ = src_id; }
|
||||
int src_id() { return src_id_; }
|
||||
|
||||
void set_src_url(const GURL& src_url) { src_url_ = src_url; }
|
||||
const GURL& src_url() { return src_url_; }
|
||||
|
||||
void set_voice_name(const std::string& voice_name) {
|
||||
voice_name_ = voice_name;
|
||||
}
|
||||
const std::string& voice_name() const { return voice_name_; }
|
||||
|
||||
void set_lang(const std::string& lang) { lang_ = lang; }
|
||||
const std::string& lang() const { return lang_; }
|
||||
|
||||
void set_gender(TtsGenderType gender) { gender_ = gender; }
|
||||
TtsGenderType gender() const { return gender_; }
|
||||
|
||||
void set_continuous_parameters(const UtteranceContinuousParameters& params) {
|
||||
continuous_parameters_ = params;
|
||||
}
|
||||
const UtteranceContinuousParameters& continuous_parameters() {
|
||||
return continuous_parameters_;
|
||||
}
|
||||
|
||||
void set_can_enqueue(bool can_enqueue) { can_enqueue_ = can_enqueue; }
|
||||
bool can_enqueue() const { return can_enqueue_; }
|
||||
|
||||
void set_required_event_types(const std::set<TtsEventType>& types) {
|
||||
required_event_types_ = types;
|
||||
}
|
||||
const std::set<TtsEventType>& required_event_types() const {
|
||||
return required_event_types_;
|
||||
}
|
||||
|
||||
void set_desired_event_types(const std::set<TtsEventType>& types) {
|
||||
desired_event_types_ = types;
|
||||
}
|
||||
const std::set<TtsEventType>& desired_event_types() const {
|
||||
return desired_event_types_;
|
||||
}
|
||||
|
||||
const std::string& extension_id() const { return extension_id_; }
|
||||
void set_extension_id(const std::string& extension_id) {
|
||||
extension_id_ = extension_id;
|
||||
}
|
||||
|
||||
UtteranceEventDelegate* event_delegate() const {
|
||||
return event_delegate_.get();
|
||||
}
|
||||
void set_event_delegate(
|
||||
base::WeakPtr<UtteranceEventDelegate> event_delegate) {
|
||||
event_delegate_ = event_delegate;
|
||||
}
|
||||
|
||||
// Getters and setters for internal state.
|
||||
content::BrowserContext* browser_context() const { return browser_context_; }
|
||||
int id() const { return id_; }
|
||||
bool finished() const { return finished_; }
|
||||
|
||||
private:
|
||||
// The BrowserContext that initiated this utterance.
|
||||
content::BrowserContext* browser_context_;
|
||||
|
||||
// The extension ID of the extension providing TTS for this utterance, or
|
||||
// empty if native TTS is being used.
|
||||
std::string extension_id_;
|
||||
|
||||
// The unique ID of this utterance, used to associate callback functions
|
||||
// with utterances.
|
||||
int id_;
|
||||
|
||||
// The id of the next utterance, so we can associate requests with
|
||||
// responses.
|
||||
static int next_utterance_id_;
|
||||
|
||||
// The text to speak.
|
||||
std::string text_;
|
||||
|
||||
// The full options arg passed to tts.speak, which may include fields
|
||||
// other than the ones we explicitly parse, below.
|
||||
std::unique_ptr<base::Value> options_;
|
||||
|
||||
// The extension ID of the extension that called speak() and should
|
||||
// receive events.
|
||||
std::string src_extension_id_;
|
||||
|
||||
// The source extension's ID of this utterance, so that it can associate
|
||||
// events with the appropriate callback.
|
||||
int src_id_;
|
||||
|
||||
// The URL of the page where the source extension called speak.
|
||||
GURL src_url_;
|
||||
|
||||
// The delegate to be called when an utterance event is fired.
|
||||
base::WeakPtr<UtteranceEventDelegate> event_delegate_;
|
||||
|
||||
// The parsed options.
|
||||
std::string voice_name_;
|
||||
std::string lang_;
|
||||
TtsGenderType gender_;
|
||||
UtteranceContinuousParameters continuous_parameters_;
|
||||
bool can_enqueue_;
|
||||
std::set<TtsEventType> required_event_types_;
|
||||
std::set<TtsEventType> desired_event_types_;
|
||||
|
||||
// The index of the current char being spoken.
|
||||
int char_index_;
|
||||
|
||||
// True if this utterance received an event indicating it's done.
|
||||
bool finished_;
|
||||
};
|
||||
|
||||
// Singleton class that manages text-to-speech for the TTS and TTS engine
|
||||
// extension APIs, maintaining a queue of pending utterances and keeping
|
||||
// track of all state.
|
||||
class TtsController {
|
||||
public:
|
||||
// Get the single instance of this class.
|
||||
static TtsController* GetInstance();
|
||||
|
||||
// Returns true if we're currently speaking an utterance.
|
||||
virtual bool IsSpeaking() = 0;
|
||||
|
||||
// Speak the given utterance. If the utterance's can_enqueue flag is true
|
||||
// and another utterance is in progress, adds it to the end of the queue.
|
||||
// Otherwise, interrupts any current utterance and speaks this one
|
||||
// immediately.
|
||||
virtual void SpeakOrEnqueue(Utterance* utterance) = 0;
|
||||
|
||||
// Stop all utterances and flush the queue. Implies leaving pause mode
|
||||
// as well.
|
||||
virtual void Stop() = 0;
|
||||
|
||||
// Pause the speech queue. Some engines may support pausing in the middle
|
||||
// of an utterance.
|
||||
virtual void Pause() = 0;
|
||||
|
||||
// Resume speaking.
|
||||
virtual void Resume() = 0;
|
||||
|
||||
// Handle events received from the speech engine. Events are forwarded to
|
||||
// the callback function, and in addition, completion and error events
|
||||
// trigger finishing the current utterance and starting the next one, if
|
||||
// any.
|
||||
virtual void OnTtsEvent(int utterance_id,
|
||||
TtsEventType event_type,
|
||||
int char_index,
|
||||
const std::string& error_message) = 0;
|
||||
|
||||
// Return a list of all available voices, including the native voice,
|
||||
// if supported, and all voices registered by extensions.
|
||||
virtual void GetVoices(content::BrowserContext* browser_context,
|
||||
std::vector<VoiceData>* out_voices) = 0;
|
||||
|
||||
// Called by the extension system or platform implementation when the
|
||||
// list of voices may have changed and should be re-queried.
|
||||
virtual void VoicesChanged() = 0;
|
||||
|
||||
// Add a delegate that wants to be notified when the set of voices changes.
|
||||
virtual void AddVoicesChangedDelegate(VoicesChangedDelegate* delegate) = 0;
|
||||
|
||||
// Remove delegate that wants to be notified when the set of voices changes.
|
||||
virtual void RemoveVoicesChangedDelegate(VoicesChangedDelegate* delegate) = 0;
|
||||
|
||||
// Set the delegate that processes TTS requests with user-installed
|
||||
// extensions.
|
||||
virtual void SetTtsEngineDelegate(TtsEngineDelegate* delegate) = 0;
|
||||
|
||||
// Get the delegate that processes TTS requests with user-installed
|
||||
// extensions.
|
||||
virtual TtsEngineDelegate* GetTtsEngineDelegate() = 0;
|
||||
|
||||
// For unit testing.
|
||||
virtual void SetPlatformImpl(TtsPlatformImpl* platform_impl) = 0;
|
||||
virtual int QueueSize() = 0;
|
||||
|
||||
protected:
|
||||
virtual ~TtsController() {}
|
||||
};
|
||||
|
||||
#endif // CHROME_BROWSER_SPEECH_TTS_CONTROLLER_H_
|
||||
@@ -1,447 +0,0 @@
|
||||
// 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/speech/tts_controller_impl.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/values.h"
|
||||
#include "chrome/browser/browser_process.h"
|
||||
#include "chrome/browser/speech/tts_platform.h"
|
||||
|
||||
namespace {
|
||||
// A value to be used to indicate that there is no char index available.
|
||||
const int kInvalidCharIndex = -1;
|
||||
|
||||
// Given a language/region code of the form 'fr-FR', returns just the basic
|
||||
// language portion, e.g. 'fr'.
|
||||
std::string TrimLanguageCode(std::string lang) {
|
||||
if (lang.size() >= 5 && lang[2] == '-')
|
||||
return lang.substr(0, 2);
|
||||
else
|
||||
return lang;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool IsFinalTtsEventType(TtsEventType event_type) {
|
||||
return (event_type == TTS_EVENT_END || event_type == TTS_EVENT_INTERRUPTED ||
|
||||
event_type == TTS_EVENT_CANCELLED || event_type == TTS_EVENT_ERROR);
|
||||
}
|
||||
|
||||
//
|
||||
// UtteranceContinuousParameters
|
||||
//
|
||||
|
||||
UtteranceContinuousParameters::UtteranceContinuousParameters()
|
||||
: rate(-1), pitch(-1), volume(-1) {}
|
||||
|
||||
//
|
||||
// VoiceData
|
||||
//
|
||||
|
||||
VoiceData::VoiceData()
|
||||
: gender(TTS_GENDER_NONE), remote(false), native(false) {}
|
||||
|
||||
VoiceData::VoiceData(const VoiceData&) = default;
|
||||
|
||||
VoiceData::~VoiceData() = default;
|
||||
|
||||
//
|
||||
// Utterance
|
||||
//
|
||||
|
||||
// static
|
||||
int Utterance::next_utterance_id_ = 0;
|
||||
|
||||
Utterance::Utterance(content::BrowserContext* browser_context)
|
||||
: browser_context_(browser_context),
|
||||
id_(next_utterance_id_++),
|
||||
src_id_(-1),
|
||||
gender_(TTS_GENDER_NONE),
|
||||
can_enqueue_(false),
|
||||
char_index_(0),
|
||||
finished_(false) {
|
||||
options_.reset(new base::DictionaryValue());
|
||||
}
|
||||
|
||||
Utterance::~Utterance() {
|
||||
DCHECK(finished_);
|
||||
}
|
||||
|
||||
void Utterance::OnTtsEvent(TtsEventType event_type,
|
||||
int char_index,
|
||||
const std::string& error_message) {
|
||||
if (char_index >= 0)
|
||||
char_index_ = char_index;
|
||||
if (IsFinalTtsEventType(event_type))
|
||||
finished_ = true;
|
||||
|
||||
if (event_delegate_)
|
||||
event_delegate_->OnTtsEvent(this, event_type, char_index, error_message);
|
||||
if (finished_)
|
||||
event_delegate_.reset();
|
||||
}
|
||||
|
||||
void Utterance::Finish() {
|
||||
finished_ = true;
|
||||
}
|
||||
|
||||
void Utterance::set_options(const base::Value* options) {
|
||||
options_.reset(options->DeepCopy());
|
||||
}
|
||||
|
||||
TtsController* TtsController::GetInstance() {
|
||||
return TtsControllerImpl::GetInstance();
|
||||
}
|
||||
|
||||
//
|
||||
// TtsControllerImpl
|
||||
//
|
||||
|
||||
// static
|
||||
TtsControllerImpl* TtsControllerImpl::GetInstance() {
|
||||
return base::Singleton<TtsControllerImpl>::get();
|
||||
}
|
||||
|
||||
TtsControllerImpl::TtsControllerImpl()
|
||||
: current_utterance_(NULL),
|
||||
paused_(false),
|
||||
platform_impl_(NULL),
|
||||
tts_engine_delegate_(NULL) {}
|
||||
|
||||
TtsControllerImpl::~TtsControllerImpl() {
|
||||
if (current_utterance_) {
|
||||
current_utterance_->Finish();
|
||||
delete current_utterance_;
|
||||
}
|
||||
|
||||
// Clear any queued utterances too.
|
||||
ClearUtteranceQueue(false); // Don't sent events.
|
||||
}
|
||||
|
||||
void TtsControllerImpl::SpeakOrEnqueue(Utterance* utterance) {
|
||||
// If we're paused and we get an utterance that can't be queued,
|
||||
// flush the queue but stay in the paused state.
|
||||
if (paused_ && !utterance->can_enqueue()) {
|
||||
Stop();
|
||||
paused_ = true;
|
||||
delete utterance;
|
||||
return;
|
||||
}
|
||||
|
||||
if (paused_ || (IsSpeaking() && utterance->can_enqueue())) {
|
||||
utterance_queue_.push(utterance);
|
||||
} else {
|
||||
Stop();
|
||||
SpeakNow(utterance);
|
||||
}
|
||||
}
|
||||
|
||||
void TtsControllerImpl::SpeakNow(Utterance* utterance) {
|
||||
// Ensure we have all built-in voices loaded. This is a no-op if already
|
||||
// loaded.
|
||||
bool loaded_built_in =
|
||||
GetPlatformImpl()->LoadBuiltInTtsExtension(utterance->browser_context());
|
||||
|
||||
// Get all available voices and try to find a matching voice.
|
||||
std::vector<VoiceData> voices;
|
||||
GetVoices(utterance->browser_context(), &voices);
|
||||
int index = GetMatchingVoice(utterance, voices);
|
||||
|
||||
VoiceData voice;
|
||||
if (index != -1) {
|
||||
// Select the matching voice.
|
||||
voice = voices[index];
|
||||
} else {
|
||||
// However, if no match was found on a platform without native tts voices,
|
||||
// attempt to get a voice based only on the current locale without respect
|
||||
// to any supplied voice names.
|
||||
std::vector<VoiceData> native_voices;
|
||||
|
||||
if (GetPlatformImpl()->PlatformImplAvailable())
|
||||
GetPlatformImpl()->GetVoices(&native_voices);
|
||||
|
||||
if (native_voices.empty() && !voices.empty()) {
|
||||
// TODO(dtseng): Notify extension caller of an error.
|
||||
utterance->set_voice_name("");
|
||||
// TODO(gaochun): Replace the global variable g_browser_process with
|
||||
// GetContentClient()->browser() to eliminate the dependency of browser
|
||||
// once TTS implementation was moved to content.
|
||||
utterance->set_lang(g_browser_process->GetApplicationLocale());
|
||||
index = GetMatchingVoice(utterance, voices);
|
||||
|
||||
// If even that fails, just take the first available voice.
|
||||
if (index == -1)
|
||||
index = 0;
|
||||
voice = voices[index];
|
||||
} else {
|
||||
// Otherwise, simply give native voices a chance to handle this utterance.
|
||||
voice.native = true;
|
||||
}
|
||||
}
|
||||
|
||||
GetPlatformImpl()->WillSpeakUtteranceWithVoice(utterance, voice);
|
||||
|
||||
if (!voice.native) {
|
||||
#if !defined(OS_ANDROID)
|
||||
DCHECK(!voice.extension_id.empty());
|
||||
current_utterance_ = utterance;
|
||||
utterance->set_extension_id(voice.extension_id);
|
||||
if (tts_engine_delegate_)
|
||||
tts_engine_delegate_->Speak(utterance, voice);
|
||||
bool sends_end_event =
|
||||
voice.events.find(TTS_EVENT_END) != voice.events.end();
|
||||
if (!sends_end_event) {
|
||||
utterance->Finish();
|
||||
delete utterance;
|
||||
current_utterance_ = NULL;
|
||||
SpeakNextUtterance();
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
// It's possible for certain platforms to send start events immediately
|
||||
// during |speak|.
|
||||
current_utterance_ = utterance;
|
||||
GetPlatformImpl()->clear_error();
|
||||
bool success = GetPlatformImpl()->Speak(utterance->id(), utterance->text(),
|
||||
utterance->lang(), voice,
|
||||
utterance->continuous_parameters());
|
||||
if (!success)
|
||||
current_utterance_ = NULL;
|
||||
|
||||
// If the native voice wasn't able to process this speech, see if
|
||||
// the browser has built-in TTS that isn't loaded yet.
|
||||
if (!success && loaded_built_in) {
|
||||
utterance_queue_.push(utterance);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
utterance->OnTtsEvent(TTS_EVENT_ERROR, kInvalidCharIndex,
|
||||
GetPlatformImpl()->error());
|
||||
delete utterance;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TtsControllerImpl::Stop() {
|
||||
paused_ = false;
|
||||
if (current_utterance_ && !current_utterance_->extension_id().empty()) {
|
||||
#if !defined(OS_ANDROID)
|
||||
if (tts_engine_delegate_)
|
||||
tts_engine_delegate_->Stop(current_utterance_);
|
||||
#endif
|
||||
} else {
|
||||
GetPlatformImpl()->clear_error();
|
||||
GetPlatformImpl()->StopSpeaking();
|
||||
}
|
||||
|
||||
if (current_utterance_)
|
||||
current_utterance_->OnTtsEvent(TTS_EVENT_INTERRUPTED, kInvalidCharIndex,
|
||||
std::string());
|
||||
FinishCurrentUtterance();
|
||||
ClearUtteranceQueue(true); // Send events.
|
||||
}
|
||||
|
||||
void TtsControllerImpl::Pause() {
|
||||
paused_ = true;
|
||||
if (current_utterance_ && !current_utterance_->extension_id().empty()) {
|
||||
#if !defined(OS_ANDROID)
|
||||
if (tts_engine_delegate_)
|
||||
tts_engine_delegate_->Pause(current_utterance_);
|
||||
#endif
|
||||
} else if (current_utterance_) {
|
||||
GetPlatformImpl()->clear_error();
|
||||
GetPlatformImpl()->Pause();
|
||||
}
|
||||
}
|
||||
|
||||
void TtsControllerImpl::Resume() {
|
||||
paused_ = false;
|
||||
if (current_utterance_ && !current_utterance_->extension_id().empty()) {
|
||||
#if !defined(OS_ANDROID)
|
||||
if (tts_engine_delegate_)
|
||||
tts_engine_delegate_->Resume(current_utterance_);
|
||||
#endif
|
||||
} else if (current_utterance_) {
|
||||
GetPlatformImpl()->clear_error();
|
||||
GetPlatformImpl()->Resume();
|
||||
} else {
|
||||
SpeakNextUtterance();
|
||||
}
|
||||
}
|
||||
|
||||
void TtsControllerImpl::OnTtsEvent(int utterance_id,
|
||||
TtsEventType event_type,
|
||||
int char_index,
|
||||
const std::string& error_message) {
|
||||
// We may sometimes receive completion callbacks "late", after we've
|
||||
// already finished the utterance (for example because another utterance
|
||||
// interrupted or we got a call to Stop). This is normal and we can
|
||||
// safely just ignore these events.
|
||||
if (!current_utterance_ || utterance_id != current_utterance_->id()) {
|
||||
return;
|
||||
}
|
||||
current_utterance_->OnTtsEvent(event_type, char_index, error_message);
|
||||
if (current_utterance_->finished()) {
|
||||
FinishCurrentUtterance();
|
||||
SpeakNextUtterance();
|
||||
}
|
||||
}
|
||||
|
||||
void TtsControllerImpl::GetVoices(content::BrowserContext* browser_context,
|
||||
std::vector<VoiceData>* out_voices) {
|
||||
#if !defined(OS_ANDROID)
|
||||
if (browser_context && tts_engine_delegate_)
|
||||
tts_engine_delegate_->GetVoices(browser_context, out_voices);
|
||||
#endif
|
||||
|
||||
TtsPlatformImpl* platform_impl = GetPlatformImpl();
|
||||
if (platform_impl) {
|
||||
// Ensure we have all built-in voices loaded. This is a no-op if already
|
||||
// loaded.
|
||||
platform_impl->LoadBuiltInTtsExtension(browser_context);
|
||||
if (platform_impl->PlatformImplAvailable())
|
||||
platform_impl->GetVoices(out_voices);
|
||||
}
|
||||
}
|
||||
|
||||
bool TtsControllerImpl::IsSpeaking() {
|
||||
return current_utterance_ != NULL || GetPlatformImpl()->IsSpeaking();
|
||||
}
|
||||
|
||||
void TtsControllerImpl::FinishCurrentUtterance() {
|
||||
if (current_utterance_) {
|
||||
if (!current_utterance_->finished())
|
||||
current_utterance_->OnTtsEvent(TTS_EVENT_INTERRUPTED, kInvalidCharIndex,
|
||||
std::string());
|
||||
delete current_utterance_;
|
||||
current_utterance_ = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void TtsControllerImpl::SpeakNextUtterance() {
|
||||
if (paused_)
|
||||
return;
|
||||
|
||||
// Start speaking the next utterance in the queue. Keep trying in case
|
||||
// one fails but there are still more in the queue to try.
|
||||
while (!utterance_queue_.empty() && !current_utterance_) {
|
||||
Utterance* utterance = utterance_queue_.front();
|
||||
utterance_queue_.pop();
|
||||
SpeakNow(utterance);
|
||||
}
|
||||
}
|
||||
|
||||
void TtsControllerImpl::ClearUtteranceQueue(bool send_events) {
|
||||
while (!utterance_queue_.empty()) {
|
||||
Utterance* utterance = utterance_queue_.front();
|
||||
utterance_queue_.pop();
|
||||
if (send_events)
|
||||
utterance->OnTtsEvent(TTS_EVENT_CANCELLED, kInvalidCharIndex,
|
||||
std::string());
|
||||
else
|
||||
utterance->Finish();
|
||||
delete utterance;
|
||||
}
|
||||
}
|
||||
|
||||
void TtsControllerImpl::SetPlatformImpl(TtsPlatformImpl* platform_impl) {
|
||||
platform_impl_ = platform_impl;
|
||||
}
|
||||
|
||||
int TtsControllerImpl::QueueSize() {
|
||||
return static_cast<int>(utterance_queue_.size());
|
||||
}
|
||||
|
||||
TtsPlatformImpl* TtsControllerImpl::GetPlatformImpl() {
|
||||
if (!platform_impl_)
|
||||
platform_impl_ = TtsPlatformImpl::GetInstance();
|
||||
return platform_impl_;
|
||||
}
|
||||
|
||||
int TtsControllerImpl::GetMatchingVoice(const Utterance* utterance,
|
||||
std::vector<VoiceData>& voices) {
|
||||
// Make two passes: the first time, do strict language matching
|
||||
// ('fr-FR' does not match 'fr-CA'). The second time, do prefix
|
||||
// language matching ('fr-FR' matches 'fr' and 'fr-CA')
|
||||
for (int pass = 0; pass < 2; ++pass) {
|
||||
for (size_t i = 0; i < voices.size(); ++i) {
|
||||
const VoiceData& voice = voices[i];
|
||||
|
||||
if (!utterance->extension_id().empty() &&
|
||||
utterance->extension_id() != voice.extension_id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!voice.name.empty() && !utterance->voice_name().empty() &&
|
||||
voice.name != utterance->voice_name()) {
|
||||
continue;
|
||||
}
|
||||
if (!voice.lang.empty() && !utterance->lang().empty()) {
|
||||
std::string voice_lang = voice.lang;
|
||||
std::string utterance_lang = utterance->lang();
|
||||
if (pass == 1) {
|
||||
voice_lang = TrimLanguageCode(voice_lang);
|
||||
utterance_lang = TrimLanguageCode(utterance_lang);
|
||||
}
|
||||
if (voice_lang != utterance_lang) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (voice.gender != TTS_GENDER_NONE &&
|
||||
utterance->gender() != TTS_GENDER_NONE &&
|
||||
voice.gender != utterance->gender()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (utterance->required_event_types().size() > 0) {
|
||||
bool has_all_required_event_types = true;
|
||||
for (std::set<TtsEventType>::const_iterator iter =
|
||||
utterance->required_event_types().begin();
|
||||
iter != utterance->required_event_types().end(); ++iter) {
|
||||
if (voice.events.find(*iter) == voice.events.end()) {
|
||||
has_all_required_event_types = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!has_all_required_event_types)
|
||||
continue;
|
||||
}
|
||||
|
||||
return static_cast<int>(i);
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void TtsControllerImpl::VoicesChanged() {
|
||||
for (std::set<VoicesChangedDelegate*>::iterator iter =
|
||||
voices_changed_delegates_.begin();
|
||||
iter != voices_changed_delegates_.end(); ++iter) {
|
||||
(*iter)->OnVoicesChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void TtsControllerImpl::AddVoicesChangedDelegate(
|
||||
VoicesChangedDelegate* delegate) {
|
||||
voices_changed_delegates_.insert(delegate);
|
||||
}
|
||||
|
||||
void TtsControllerImpl::RemoveVoicesChangedDelegate(
|
||||
VoicesChangedDelegate* delegate) {
|
||||
voices_changed_delegates_.erase(delegate);
|
||||
}
|
||||
|
||||
void TtsControllerImpl::SetTtsEngineDelegate(TtsEngineDelegate* delegate) {
|
||||
tts_engine_delegate_ = delegate;
|
||||
}
|
||||
|
||||
TtsEngineDelegate* TtsControllerImpl::GetTtsEngineDelegate() {
|
||||
return tts_engine_delegate_;
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
// 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_SPEECH_TTS_CONTROLLER_IMPL_H_
|
||||
#define CHROME_BROWSER_SPEECH_TTS_CONTROLLER_IMPL_H_
|
||||
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/memory/singleton.h"
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "chrome/browser/speech/tts_controller.h"
|
||||
#include "url/gurl.h"
|
||||
|
||||
namespace content {
|
||||
class BrowserContext;
|
||||
}
|
||||
|
||||
// Singleton class that manages text-to-speech for the TTS and TTS engine
|
||||
// extension APIs, maintaining a queue of pending utterances and keeping
|
||||
// track of all state.
|
||||
class TtsControllerImpl : public TtsController {
|
||||
public:
|
||||
// Get the single instance of this class.
|
||||
static TtsControllerImpl* GetInstance();
|
||||
|
||||
// TtsController methods
|
||||
bool IsSpeaking() override;
|
||||
void SpeakOrEnqueue(Utterance* utterance) override;
|
||||
void Stop() override;
|
||||
void Pause() override;
|
||||
void Resume() override;
|
||||
void OnTtsEvent(int utterance_id,
|
||||
TtsEventType event_type,
|
||||
int char_index,
|
||||
const std::string& error_message) override;
|
||||
void GetVoices(content::BrowserContext* browser_context,
|
||||
std::vector<VoiceData>* out_voices) override;
|
||||
void VoicesChanged() override;
|
||||
void AddVoicesChangedDelegate(VoicesChangedDelegate* delegate) override;
|
||||
void RemoveVoicesChangedDelegate(VoicesChangedDelegate* delegate) override;
|
||||
void SetTtsEngineDelegate(TtsEngineDelegate* delegate) override;
|
||||
TtsEngineDelegate* GetTtsEngineDelegate() override;
|
||||
void SetPlatformImpl(TtsPlatformImpl* platform_impl) override;
|
||||
int QueueSize() override;
|
||||
|
||||
protected:
|
||||
TtsControllerImpl();
|
||||
~TtsControllerImpl() override;
|
||||
|
||||
private:
|
||||
// Get the platform TTS implementation (or injected mock).
|
||||
TtsPlatformImpl* GetPlatformImpl();
|
||||
|
||||
// Start speaking the given utterance. Will either take ownership of
|
||||
// |utterance| or delete it if there's an error. Returns true on success.
|
||||
void SpeakNow(Utterance* utterance);
|
||||
|
||||
// Clear the utterance queue. If send_events is true, will send
|
||||
// TTS_EVENT_CANCELLED events on each one.
|
||||
void ClearUtteranceQueue(bool send_events);
|
||||
|
||||
// Finalize and delete the current utterance.
|
||||
void FinishCurrentUtterance();
|
||||
|
||||
// Start speaking the next utterance in the queue.
|
||||
void SpeakNextUtterance();
|
||||
|
||||
// Given an utterance and a vector of voices, return the
|
||||
// index of the voice that best matches the utterance.
|
||||
int GetMatchingVoice(const Utterance* utterance,
|
||||
std::vector<VoiceData>& voices);
|
||||
|
||||
friend struct base::DefaultSingletonTraits<TtsControllerImpl>;
|
||||
|
||||
// The current utterance being spoken.
|
||||
Utterance* current_utterance_;
|
||||
|
||||
// Whether the queue is paused or not.
|
||||
bool paused_;
|
||||
|
||||
// A queue of utterances to speak after the current one finishes.
|
||||
std::queue<Utterance*> utterance_queue_;
|
||||
|
||||
// A set of delegates that want to be notified when the voices change.
|
||||
std::set<VoicesChangedDelegate*> voices_changed_delegates_;
|
||||
|
||||
// A pointer to the platform implementation of text-to-speech, for
|
||||
// dependency injection.
|
||||
TtsPlatformImpl* platform_impl_;
|
||||
|
||||
// The delegate that processes TTS requests with user-installed extensions.
|
||||
TtsEngineDelegate* tts_engine_delegate_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(TtsControllerImpl);
|
||||
};
|
||||
|
||||
#endif // CHROME_BROWSER_SPEECH_TTS_CONTROLLER_IMPL_H_
|
||||
@@ -1,349 +0,0 @@
|
||||
// Copyright (c) 2012 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 <math.h>
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
||||
#include "base/command_line.h"
|
||||
#include "base/debug/leak_annotations.h"
|
||||
#include "base/memory/singleton.h"
|
||||
#include "base/synchronization/lock.h"
|
||||
#include "base/task_scheduler/post_task.h"
|
||||
#include "chrome/browser/speech/tts_platform.h"
|
||||
#include "content/public/browser/browser_thread.h"
|
||||
#include "content/public/common/content_switches.h"
|
||||
|
||||
#include "library_loaders/libspeechd.h"
|
||||
|
||||
using content::BrowserThread;
|
||||
|
||||
namespace {
|
||||
|
||||
const char kNotSupportedError[] =
|
||||
"Native speech synthesis not supported on this platform.";
|
||||
|
||||
struct SPDChromeVoice {
|
||||
std::string name;
|
||||
std::string module;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
class TtsPlatformImplLinux : public TtsPlatformImpl {
|
||||
public:
|
||||
bool PlatformImplAvailable() override;
|
||||
bool Speak(int utterance_id,
|
||||
const std::string& utterance,
|
||||
const std::string& lang,
|
||||
const VoiceData& voice,
|
||||
const UtteranceContinuousParameters& params) override;
|
||||
bool StopSpeaking() override;
|
||||
void Pause() override;
|
||||
void Resume() override;
|
||||
bool IsSpeaking() override;
|
||||
void GetVoices(std::vector<VoiceData>* out_voices) override;
|
||||
|
||||
void OnSpeechEvent(SPDNotificationType type);
|
||||
|
||||
// Get the single instance of this class.
|
||||
static TtsPlatformImplLinux* GetInstance();
|
||||
|
||||
private:
|
||||
TtsPlatformImplLinux();
|
||||
~TtsPlatformImplLinux() override;
|
||||
|
||||
// Initiate the connection with the speech dispatcher.
|
||||
void Initialize();
|
||||
|
||||
// Resets the connection with speech dispatcher.
|
||||
void Reset();
|
||||
|
||||
static void NotificationCallback(size_t msg_id,
|
||||
size_t client_id,
|
||||
SPDNotificationType type);
|
||||
|
||||
static void IndexMarkCallback(size_t msg_id,
|
||||
size_t client_id,
|
||||
SPDNotificationType state,
|
||||
char* index_mark);
|
||||
|
||||
static SPDNotificationType current_notification_;
|
||||
|
||||
base::Lock initialization_lock_;
|
||||
LibSpeechdLoader libspeechd_loader_;
|
||||
SPDConnection* conn_;
|
||||
|
||||
// These apply to the current utterance only.
|
||||
std::string utterance_;
|
||||
int utterance_id_;
|
||||
|
||||
// Map a string composed of a voicename and module to the voicename. Used to
|
||||
// uniquely identify a voice across all available modules.
|
||||
std::unique_ptr<std::map<std::string, SPDChromeVoice>> all_native_voices_;
|
||||
|
||||
friend struct base::DefaultSingletonTraits<TtsPlatformImplLinux>;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(TtsPlatformImplLinux);
|
||||
};
|
||||
|
||||
// static
|
||||
SPDNotificationType TtsPlatformImplLinux::current_notification_ = SPD_EVENT_END;
|
||||
|
||||
TtsPlatformImplLinux::TtsPlatformImplLinux() : utterance_id_(0) {
|
||||
const base::CommandLine& command_line =
|
||||
*base::CommandLine::ForCurrentProcess();
|
||||
if (!command_line.HasSwitch(switches::kEnableSpeechDispatcher))
|
||||
return;
|
||||
|
||||
base::PostTaskWithTraits(
|
||||
FROM_HERE, {base::MayBlock(), base::TaskPriority::BACKGROUND},
|
||||
base::Bind(&TtsPlatformImplLinux::Initialize, base::Unretained(this)));
|
||||
}
|
||||
|
||||
void TtsPlatformImplLinux::Initialize() {
|
||||
base::AutoLock lock(initialization_lock_);
|
||||
|
||||
if (!libspeechd_loader_.Load("libspeechd.so.2"))
|
||||
return;
|
||||
|
||||
{
|
||||
// spd_open has memory leaks which are hard to suppress.
|
||||
// http://crbug.com/317360
|
||||
ANNOTATE_SCOPED_MEMORY_LEAK;
|
||||
conn_ = libspeechd_loader_.spd_open("chrome", "extension_api", NULL,
|
||||
SPD_MODE_THREADED);
|
||||
}
|
||||
if (!conn_)
|
||||
return;
|
||||
|
||||
// Register callbacks for all events.
|
||||
conn_->callback_begin = conn_->callback_end = conn_->callback_cancel =
|
||||
conn_->callback_pause = conn_->callback_resume = &NotificationCallback;
|
||||
|
||||
conn_->callback_im = &IndexMarkCallback;
|
||||
|
||||
libspeechd_loader_.spd_set_notification_on(conn_, SPD_BEGIN);
|
||||
libspeechd_loader_.spd_set_notification_on(conn_, SPD_END);
|
||||
libspeechd_loader_.spd_set_notification_on(conn_, SPD_CANCEL);
|
||||
libspeechd_loader_.spd_set_notification_on(conn_, SPD_PAUSE);
|
||||
libspeechd_loader_.spd_set_notification_on(conn_, SPD_RESUME);
|
||||
}
|
||||
|
||||
TtsPlatformImplLinux::~TtsPlatformImplLinux() {
|
||||
base::AutoLock lock(initialization_lock_);
|
||||
if (conn_) {
|
||||
libspeechd_loader_.spd_close(conn_);
|
||||
conn_ = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void TtsPlatformImplLinux::Reset() {
|
||||
base::AutoLock lock(initialization_lock_);
|
||||
if (conn_)
|
||||
libspeechd_loader_.spd_close(conn_);
|
||||
conn_ = libspeechd_loader_.spd_open("chrome", "extension_api", NULL,
|
||||
SPD_MODE_THREADED);
|
||||
}
|
||||
|
||||
bool TtsPlatformImplLinux::PlatformImplAvailable() {
|
||||
if (!initialization_lock_.Try())
|
||||
return false;
|
||||
bool result = libspeechd_loader_.loaded() && (conn_ != NULL);
|
||||
initialization_lock_.Release();
|
||||
return result;
|
||||
}
|
||||
|
||||
bool TtsPlatformImplLinux::Speak(int utterance_id,
|
||||
const std::string& utterance,
|
||||
const std::string& lang,
|
||||
const VoiceData& voice,
|
||||
const UtteranceContinuousParameters& params) {
|
||||
if (!PlatformImplAvailable()) {
|
||||
error_ = kNotSupportedError;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Speech dispatcher's speech params are around 3x at either limit.
|
||||
float rate = params.rate > 3 ? 3 : params.rate;
|
||||
rate = params.rate < 0.334 ? 0.334 : rate;
|
||||
float pitch = params.pitch > 3 ? 3 : params.pitch;
|
||||
pitch = params.pitch < 0.334 ? 0.334 : pitch;
|
||||
|
||||
std::map<std::string, SPDChromeVoice>::iterator it =
|
||||
all_native_voices_->find(voice.name);
|
||||
if (it != all_native_voices_->end()) {
|
||||
libspeechd_loader_.spd_set_output_module(conn_, it->second.module.c_str());
|
||||
libspeechd_loader_.spd_set_synthesis_voice(conn_, it->second.name.c_str());
|
||||
}
|
||||
|
||||
// Map our multiplicative range to Speech Dispatcher's linear range.
|
||||
// .334 = -100.
|
||||
// 3 = 100.
|
||||
libspeechd_loader_.spd_set_voice_rate(conn_, 100 * log10(rate) / log10(3));
|
||||
libspeechd_loader_.spd_set_voice_pitch(conn_, 100 * log10(pitch) / log10(3));
|
||||
|
||||
// Support languages other than the default
|
||||
if (!lang.empty())
|
||||
libspeechd_loader_.spd_set_language(conn_, lang.c_str());
|
||||
|
||||
utterance_ = utterance;
|
||||
utterance_id_ = utterance_id;
|
||||
|
||||
if (libspeechd_loader_.spd_say(conn_, SPD_TEXT, utterance.c_str()) == -1) {
|
||||
Reset();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TtsPlatformImplLinux::StopSpeaking() {
|
||||
if (!PlatformImplAvailable())
|
||||
return false;
|
||||
if (libspeechd_loader_.spd_stop(conn_) == -1) {
|
||||
Reset();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void TtsPlatformImplLinux::Pause() {
|
||||
if (!PlatformImplAvailable())
|
||||
return;
|
||||
libspeechd_loader_.spd_pause(conn_);
|
||||
}
|
||||
|
||||
void TtsPlatformImplLinux::Resume() {
|
||||
if (!PlatformImplAvailable())
|
||||
return;
|
||||
libspeechd_loader_.spd_resume(conn_);
|
||||
}
|
||||
|
||||
bool TtsPlatformImplLinux::IsSpeaking() {
|
||||
return current_notification_ == SPD_EVENT_BEGIN;
|
||||
}
|
||||
|
||||
void TtsPlatformImplLinux::GetVoices(std::vector<VoiceData>* out_voices) {
|
||||
if (!all_native_voices_.get()) {
|
||||
all_native_voices_.reset(new std::map<std::string, SPDChromeVoice>());
|
||||
char** modules = libspeechd_loader_.spd_list_modules(conn_);
|
||||
if (!modules)
|
||||
return;
|
||||
for (int i = 0; modules[i]; i++) {
|
||||
char* module = modules[i];
|
||||
libspeechd_loader_.spd_set_output_module(conn_, module);
|
||||
SPDVoice** native_voices =
|
||||
libspeechd_loader_.spd_list_synthesis_voices(conn_);
|
||||
if (!native_voices) {
|
||||
free(module);
|
||||
continue;
|
||||
}
|
||||
for (int j = 0; native_voices[j]; j++) {
|
||||
SPDVoice* native_voice = native_voices[j];
|
||||
SPDChromeVoice native_data;
|
||||
native_data.name = native_voice->name;
|
||||
native_data.module = module;
|
||||
std::string key;
|
||||
key.append(native_data.name);
|
||||
key.append(" ");
|
||||
key.append(native_data.module);
|
||||
all_native_voices_->insert(
|
||||
std::pair<std::string, SPDChromeVoice>(key, native_data));
|
||||
free(native_voices[j]);
|
||||
}
|
||||
free(modules[i]);
|
||||
}
|
||||
}
|
||||
|
||||
for (std::map<std::string, SPDChromeVoice>::iterator it =
|
||||
all_native_voices_->begin();
|
||||
it != all_native_voices_->end(); it++) {
|
||||
out_voices->push_back(VoiceData());
|
||||
VoiceData& voice = out_voices->back();
|
||||
voice.native = true;
|
||||
voice.name = it->first;
|
||||
voice.events.insert(TTS_EVENT_START);
|
||||
voice.events.insert(TTS_EVENT_END);
|
||||
voice.events.insert(TTS_EVENT_CANCELLED);
|
||||
voice.events.insert(TTS_EVENT_MARKER);
|
||||
voice.events.insert(TTS_EVENT_PAUSE);
|
||||
voice.events.insert(TTS_EVENT_RESUME);
|
||||
}
|
||||
}
|
||||
|
||||
void TtsPlatformImplLinux::OnSpeechEvent(SPDNotificationType type) {
|
||||
TtsController* controller = TtsController::GetInstance();
|
||||
switch (type) {
|
||||
case SPD_EVENT_BEGIN:
|
||||
controller->OnTtsEvent(utterance_id_, TTS_EVENT_START, 0, std::string());
|
||||
break;
|
||||
case SPD_EVENT_RESUME:
|
||||
controller->OnTtsEvent(utterance_id_, TTS_EVENT_RESUME, 0, std::string());
|
||||
break;
|
||||
case SPD_EVENT_END:
|
||||
controller->OnTtsEvent(utterance_id_, TTS_EVENT_END, utterance_.size(),
|
||||
std::string());
|
||||
break;
|
||||
case SPD_EVENT_PAUSE:
|
||||
controller->OnTtsEvent(utterance_id_, TTS_EVENT_PAUSE, utterance_.size(),
|
||||
std::string());
|
||||
break;
|
||||
case SPD_EVENT_CANCEL:
|
||||
controller->OnTtsEvent(utterance_id_, TTS_EVENT_CANCELLED, 0,
|
||||
std::string());
|
||||
break;
|
||||
case SPD_EVENT_INDEX_MARK:
|
||||
controller->OnTtsEvent(utterance_id_, TTS_EVENT_MARKER, 0, std::string());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
void TtsPlatformImplLinux::NotificationCallback(size_t msg_id,
|
||||
size_t client_id,
|
||||
SPDNotificationType type) {
|
||||
// We run Speech Dispatcher in threaded mode, so these callbacks should always
|
||||
// be in a separate thread.
|
||||
if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
|
||||
current_notification_ = type;
|
||||
BrowserThread::PostTask(
|
||||
BrowserThread::UI, FROM_HERE,
|
||||
base::Bind(&TtsPlatformImplLinux::OnSpeechEvent,
|
||||
base::Unretained(TtsPlatformImplLinux::GetInstance()),
|
||||
type));
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
void TtsPlatformImplLinux::IndexMarkCallback(size_t msg_id,
|
||||
size_t client_id,
|
||||
SPDNotificationType state,
|
||||
char* index_mark) {
|
||||
// TODO(dtseng): index_mark appears to specify an index type supplied by a
|
||||
// client. Need to explore how this is used before hooking it up with existing
|
||||
// word, sentence events.
|
||||
// We run Speech Dispatcher in threaded mode, so these callbacks should always
|
||||
// be in a separate thread.
|
||||
if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
|
||||
current_notification_ = state;
|
||||
BrowserThread::PostTask(
|
||||
BrowserThread::UI, FROM_HERE,
|
||||
base::Bind(&TtsPlatformImplLinux::OnSpeechEvent,
|
||||
base::Unretained(TtsPlatformImplLinux::GetInstance()),
|
||||
state));
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
TtsPlatformImplLinux* TtsPlatformImplLinux::GetInstance() {
|
||||
return base::Singleton<
|
||||
TtsPlatformImplLinux,
|
||||
base::LeakySingletonTraits<TtsPlatformImplLinux>>::get();
|
||||
}
|
||||
|
||||
// static
|
||||
TtsPlatformImpl* TtsPlatformImpl::GetInstance() {
|
||||
return TtsPlatformImplLinux::GetInstance();
|
||||
}
|
||||
@@ -1,344 +0,0 @@
|
||||
// Copyright (c) 2012 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 <string>
|
||||
|
||||
#include "base/mac/scoped_nsobject.h"
|
||||
#include "base/memory/singleton.h"
|
||||
#include "base/strings/sys_string_conversions.h"
|
||||
#include "base/values.h"
|
||||
#include "chrome/browser/speech/tts_controller.h"
|
||||
#include "chrome/browser/speech/tts_platform.h"
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
class TtsPlatformImplMac;
|
||||
|
||||
@interface ChromeTtsDelegate : NSObject <NSSpeechSynthesizerDelegate> {
|
||||
@private
|
||||
TtsPlatformImplMac* ttsImplMac_; // weak.
|
||||
}
|
||||
|
||||
- (id)initWithPlatformImplMac:(TtsPlatformImplMac*)ttsImplMac;
|
||||
|
||||
@end
|
||||
|
||||
// Subclass of NSSpeechSynthesizer that takes an utterance
|
||||
// string on initialization, retains it and only allows it
|
||||
// to be spoken once.
|
||||
//
|
||||
// We construct a new NSSpeechSynthesizer for each utterance, for
|
||||
// two reasons:
|
||||
// 1. To associate delegate callbacks with a particular utterance,
|
||||
// without assuming anything undocumented about the protocol.
|
||||
// 2. To work around http://openradar.appspot.com/radar?id=2854403,
|
||||
// where Nuance voices don't retain the utterance string and
|
||||
// crash when trying to call willSpeakWord.
|
||||
@interface SingleUseSpeechSynthesizer : NSSpeechSynthesizer {
|
||||
@private
|
||||
base::scoped_nsobject<NSString> utterance_;
|
||||
bool didSpeak_;
|
||||
}
|
||||
|
||||
- (id)initWithUtterance:(NSString*)utterance;
|
||||
- (bool)startSpeakingRetainedUtterance;
|
||||
- (bool)startSpeakingString:(NSString*)utterance;
|
||||
|
||||
@end
|
||||
|
||||
class TtsPlatformImplMac : public TtsPlatformImpl {
|
||||
public:
|
||||
bool PlatformImplAvailable() override { return true; }
|
||||
|
||||
bool Speak(int utterance_id,
|
||||
const std::string& utterance,
|
||||
const std::string& lang,
|
||||
const VoiceData& voice,
|
||||
const UtteranceContinuousParameters& params) override;
|
||||
|
||||
bool StopSpeaking() override;
|
||||
|
||||
void Pause() override;
|
||||
|
||||
void Resume() override;
|
||||
|
||||
bool IsSpeaking() override;
|
||||
|
||||
void GetVoices(std::vector<VoiceData>* out_voices) override;
|
||||
|
||||
// Called by ChromeTtsDelegate when we get a callback from the
|
||||
// native speech engine.
|
||||
void OnSpeechEvent(NSSpeechSynthesizer* sender,
|
||||
TtsEventType event_type,
|
||||
int char_index,
|
||||
const std::string& error_message);
|
||||
|
||||
// Get the single instance of this class.
|
||||
static TtsPlatformImplMac* GetInstance();
|
||||
|
||||
private:
|
||||
TtsPlatformImplMac();
|
||||
~TtsPlatformImplMac() override;
|
||||
|
||||
base::scoped_nsobject<SingleUseSpeechSynthesizer> speech_synthesizer_;
|
||||
base::scoped_nsobject<ChromeTtsDelegate> delegate_;
|
||||
int utterance_id_;
|
||||
std::string utterance_;
|
||||
int last_char_index_;
|
||||
bool paused_;
|
||||
|
||||
friend struct base::DefaultSingletonTraits<TtsPlatformImplMac>;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(TtsPlatformImplMac);
|
||||
};
|
||||
|
||||
// static
|
||||
TtsPlatformImpl* TtsPlatformImpl::GetInstance() {
|
||||
return TtsPlatformImplMac::GetInstance();
|
||||
}
|
||||
|
||||
bool TtsPlatformImplMac::Speak(int utterance_id,
|
||||
const std::string& utterance,
|
||||
const std::string& lang,
|
||||
const VoiceData& voice,
|
||||
const UtteranceContinuousParameters& params) {
|
||||
// TODO: convert SSML to SAPI xml. http://crbug.com/88072
|
||||
utterance_ = utterance;
|
||||
paused_ = false;
|
||||
|
||||
NSString* utterance_nsstring =
|
||||
[NSString stringWithUTF8String:utterance_.c_str()];
|
||||
|
||||
// Deliberately construct a new speech synthesizer every time Speak is
|
||||
// called, otherwise there's no way to know whether calls to the delegate
|
||||
// apply to the current utterance or a previous utterance. In
|
||||
// experimentation, the overhead of constructing and destructing a
|
||||
// NSSpeechSynthesizer is minimal.
|
||||
speech_synthesizer_.reset([[SingleUseSpeechSynthesizer alloc]
|
||||
initWithUtterance:utterance_nsstring]);
|
||||
[speech_synthesizer_ setDelegate:delegate_];
|
||||
|
||||
if (!voice.native_voice_identifier.empty()) {
|
||||
NSString* native_voice_identifier =
|
||||
[NSString stringWithUTF8String:voice.native_voice_identifier.c_str()];
|
||||
[speech_synthesizer_ setVoice:native_voice_identifier];
|
||||
}
|
||||
|
||||
utterance_id_ = utterance_id;
|
||||
|
||||
// TODO: support languages other than the default: crbug.com/88059
|
||||
|
||||
if (params.rate >= 0.0) {
|
||||
// The TTS api defines rate via words per minute. Let 200 be the default.
|
||||
[speech_synthesizer_ setObject:[NSNumber numberWithInt:params.rate * 200]
|
||||
forProperty:NSSpeechRateProperty
|
||||
error:nil];
|
||||
}
|
||||
|
||||
if (params.pitch >= 0.0) {
|
||||
// The input is a float from 0.0 to 2.0, with 1.0 being the default.
|
||||
// Get the default pitch for this voice and modulate it by 50% - 150%.
|
||||
NSError* errorCode;
|
||||
NSNumber* defaultPitchObj =
|
||||
[speech_synthesizer_ objectForProperty:NSSpeechPitchBaseProperty
|
||||
error:&errorCode];
|
||||
int defaultPitch = defaultPitchObj ? [defaultPitchObj intValue] : 48;
|
||||
int newPitch = static_cast<int>(defaultPitch * (0.5 * params.pitch + 0.5));
|
||||
[speech_synthesizer_ setObject:[NSNumber numberWithInt:newPitch]
|
||||
forProperty:NSSpeechPitchBaseProperty
|
||||
error:nil];
|
||||
}
|
||||
|
||||
if (params.volume >= 0.0) {
|
||||
[speech_synthesizer_ setObject:[NSNumber numberWithFloat:params.volume]
|
||||
forProperty:NSSpeechVolumeProperty
|
||||
error:nil];
|
||||
}
|
||||
|
||||
bool success = [speech_synthesizer_ startSpeakingRetainedUtterance];
|
||||
if (success) {
|
||||
TtsController* controller = TtsController::GetInstance();
|
||||
controller->OnTtsEvent(utterance_id_, TTS_EVENT_START, 0, "");
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool TtsPlatformImplMac::StopSpeaking() {
|
||||
if (speech_synthesizer_.get()) {
|
||||
[speech_synthesizer_ stopSpeaking];
|
||||
speech_synthesizer_.reset(nil);
|
||||
}
|
||||
paused_ = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void TtsPlatformImplMac::Pause() {
|
||||
if (speech_synthesizer_.get() && utterance_id_ && !paused_) {
|
||||
[speech_synthesizer_ pauseSpeakingAtBoundary:NSSpeechImmediateBoundary];
|
||||
paused_ = true;
|
||||
TtsController::GetInstance()->OnTtsEvent(utterance_id_, TTS_EVENT_PAUSE,
|
||||
last_char_index_, "");
|
||||
}
|
||||
}
|
||||
|
||||
void TtsPlatformImplMac::Resume() {
|
||||
if (speech_synthesizer_.get() && utterance_id_ && paused_) {
|
||||
[speech_synthesizer_ continueSpeaking];
|
||||
paused_ = false;
|
||||
TtsController::GetInstance()->OnTtsEvent(utterance_id_, TTS_EVENT_RESUME,
|
||||
last_char_index_, "");
|
||||
}
|
||||
}
|
||||
|
||||
bool TtsPlatformImplMac::IsSpeaking() {
|
||||
if (speech_synthesizer_)
|
||||
return [speech_synthesizer_ isSpeaking];
|
||||
return false;
|
||||
}
|
||||
|
||||
void TtsPlatformImplMac::GetVoices(std::vector<VoiceData>* outVoices) {
|
||||
NSArray* voices = [NSSpeechSynthesizer availableVoices];
|
||||
|
||||
// Create a new temporary array of the available voices with
|
||||
// the default voice first.
|
||||
NSMutableArray* orderedVoices =
|
||||
[NSMutableArray arrayWithCapacity:[voices count]];
|
||||
NSString* defaultVoice = [NSSpeechSynthesizer defaultVoice];
|
||||
if (defaultVoice) {
|
||||
[orderedVoices addObject:defaultVoice];
|
||||
}
|
||||
for (NSString* voiceIdentifier in voices) {
|
||||
if (![voiceIdentifier isEqualToString:defaultVoice])
|
||||
[orderedVoices addObject:voiceIdentifier];
|
||||
}
|
||||
|
||||
for (NSString* voiceIdentifier in orderedVoices) {
|
||||
outVoices->push_back(VoiceData());
|
||||
VoiceData& data = outVoices->back();
|
||||
|
||||
NSDictionary* attributes =
|
||||
[NSSpeechSynthesizer attributesForVoice:voiceIdentifier];
|
||||
NSString* name = [attributes objectForKey:NSVoiceName];
|
||||
NSString* gender = [attributes objectForKey:NSVoiceGender];
|
||||
NSString* localeIdentifier =
|
||||
[attributes objectForKey:NSVoiceLocaleIdentifier];
|
||||
|
||||
data.native = true;
|
||||
data.native_voice_identifier = base::SysNSStringToUTF8(voiceIdentifier);
|
||||
data.name = base::SysNSStringToUTF8(name);
|
||||
|
||||
NSDictionary* localeComponents =
|
||||
[NSLocale componentsFromLocaleIdentifier:localeIdentifier];
|
||||
NSString* language = [localeComponents objectForKey:NSLocaleLanguageCode];
|
||||
NSString* country = [localeComponents objectForKey:NSLocaleCountryCode];
|
||||
if (language && country) {
|
||||
data.lang =
|
||||
[[NSString stringWithFormat:@"%@-%@", language, country] UTF8String];
|
||||
} else {
|
||||
data.lang = base::SysNSStringToUTF8(language);
|
||||
}
|
||||
if ([gender isEqualToString:NSVoiceGenderMale])
|
||||
data.gender = TTS_GENDER_MALE;
|
||||
else if ([gender isEqualToString:NSVoiceGenderFemale])
|
||||
data.gender = TTS_GENDER_FEMALE;
|
||||
else
|
||||
data.gender = TTS_GENDER_NONE;
|
||||
data.events.insert(TTS_EVENT_START);
|
||||
data.events.insert(TTS_EVENT_END);
|
||||
data.events.insert(TTS_EVENT_WORD);
|
||||
data.events.insert(TTS_EVENT_ERROR);
|
||||
data.events.insert(TTS_EVENT_CANCELLED);
|
||||
data.events.insert(TTS_EVENT_INTERRUPTED);
|
||||
data.events.insert(TTS_EVENT_PAUSE);
|
||||
data.events.insert(TTS_EVENT_RESUME);
|
||||
}
|
||||
}
|
||||
|
||||
void TtsPlatformImplMac::OnSpeechEvent(NSSpeechSynthesizer* sender,
|
||||
TtsEventType event_type,
|
||||
int char_index,
|
||||
const std::string& error_message) {
|
||||
// Don't send events from an utterance that's already completed.
|
||||
// This depends on the fact that we construct a new NSSpeechSynthesizer
|
||||
// each time we call Speak.
|
||||
if (sender != speech_synthesizer_.get())
|
||||
return;
|
||||
|
||||
if (event_type == TTS_EVENT_END)
|
||||
char_index = utterance_.size();
|
||||
TtsController* controller = TtsController::GetInstance();
|
||||
controller->OnTtsEvent(utterance_id_, event_type, char_index, error_message);
|
||||
last_char_index_ = char_index;
|
||||
}
|
||||
|
||||
TtsPlatformImplMac::TtsPlatformImplMac() {
|
||||
utterance_id_ = -1;
|
||||
paused_ = false;
|
||||
|
||||
delegate_.reset([[ChromeTtsDelegate alloc] initWithPlatformImplMac:this]);
|
||||
}
|
||||
|
||||
TtsPlatformImplMac::~TtsPlatformImplMac() {}
|
||||
|
||||
// static
|
||||
TtsPlatformImplMac* TtsPlatformImplMac::GetInstance() {
|
||||
return base::Singleton<TtsPlatformImplMac>::get();
|
||||
}
|
||||
|
||||
@implementation ChromeTtsDelegate
|
||||
|
||||
- (id)initWithPlatformImplMac:(TtsPlatformImplMac*)ttsImplMac {
|
||||
if ((self = [super init])) {
|
||||
ttsImplMac_ = ttsImplMac;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)speechSynthesizer:(NSSpeechSynthesizer*)sender
|
||||
didFinishSpeaking:(BOOL)finished_speaking {
|
||||
ttsImplMac_->OnSpeechEvent(sender, TTS_EVENT_END, 0, "");
|
||||
}
|
||||
|
||||
- (void)speechSynthesizer:(NSSpeechSynthesizer*)sender
|
||||
willSpeakWord:(NSRange)character_range
|
||||
ofString:(NSString*)string {
|
||||
ttsImplMac_->OnSpeechEvent(sender, TTS_EVENT_WORD, character_range.location,
|
||||
"");
|
||||
}
|
||||
|
||||
- (void)speechSynthesizer:(NSSpeechSynthesizer*)sender
|
||||
didEncounterErrorAtIndex:(NSUInteger)character_index
|
||||
ofString:(NSString*)string
|
||||
message:(NSString*)message {
|
||||
std::string message_utf8 = base::SysNSStringToUTF8(message);
|
||||
ttsImplMac_->OnSpeechEvent(sender, TTS_EVENT_ERROR, character_index,
|
||||
message_utf8);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation SingleUseSpeechSynthesizer
|
||||
|
||||
- (id)initWithUtterance:(NSString*)utterance {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
utterance_.reset([utterance retain]);
|
||||
didSpeak_ = false;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (bool)startSpeakingRetainedUtterance {
|
||||
CHECK(!didSpeak_);
|
||||
CHECK(utterance_);
|
||||
didSpeak_ = true;
|
||||
return [super startSpeakingString:utterance_];
|
||||
}
|
||||
|
||||
- (bool)startSpeakingString:(NSString*)utterance {
|
||||
CHECK(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,176 +0,0 @@
|
||||
// Copyright (c) 2013 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/speech/tts_message_filter.h"
|
||||
|
||||
#include "base/bind.h"
|
||||
#include "base/logging.h"
|
||||
#include "content/public/browser/browser_context.h"
|
||||
#include "content/public/browser/render_process_host.h"
|
||||
|
||||
using content::BrowserThread;
|
||||
|
||||
TtsMessageFilter::TtsMessageFilter(int render_process_id,
|
||||
content::BrowserContext* browser_context)
|
||||
: BrowserMessageFilter(TtsMsgStart),
|
||||
render_process_id_(render_process_id),
|
||||
browser_context_(browser_context),
|
||||
weak_ptr_factory_(this) {
|
||||
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
||||
TtsController::GetInstance()->AddVoicesChangedDelegate(this);
|
||||
|
||||
// Balanced in OnChannelClosingInUIThread() to keep the ref-count be non-zero
|
||||
// until all WeakPtr's are invalidated.
|
||||
AddRef();
|
||||
}
|
||||
|
||||
void TtsMessageFilter::OverrideThreadForMessage(const IPC::Message& message,
|
||||
BrowserThread::ID* thread) {
|
||||
switch (message.type()) {
|
||||
case TtsHostMsg_InitializeVoiceList::ID:
|
||||
case TtsHostMsg_Speak::ID:
|
||||
case TtsHostMsg_Pause::ID:
|
||||
case TtsHostMsg_Resume::ID:
|
||||
case TtsHostMsg_Cancel::ID:
|
||||
*thread = BrowserThread::UI;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool TtsMessageFilter::OnMessageReceived(const IPC::Message& message) {
|
||||
bool handled = true;
|
||||
IPC_BEGIN_MESSAGE_MAP(TtsMessageFilter, message)
|
||||
IPC_MESSAGE_HANDLER(TtsHostMsg_InitializeVoiceList, OnInitializeVoiceList)
|
||||
IPC_MESSAGE_HANDLER(TtsHostMsg_Speak, OnSpeak)
|
||||
IPC_MESSAGE_HANDLER(TtsHostMsg_Pause, OnPause)
|
||||
IPC_MESSAGE_HANDLER(TtsHostMsg_Resume, OnResume)
|
||||
IPC_MESSAGE_HANDLER(TtsHostMsg_Cancel, OnCancel)
|
||||
IPC_MESSAGE_UNHANDLED(handled = false)
|
||||
IPC_END_MESSAGE_MAP()
|
||||
return handled;
|
||||
}
|
||||
|
||||
void TtsMessageFilter::OnChannelClosing() {
|
||||
BrowserThread::PostTask(
|
||||
BrowserThread::UI, FROM_HERE,
|
||||
base::Bind(&TtsMessageFilter::OnChannelClosingInUIThread, this));
|
||||
}
|
||||
|
||||
void TtsMessageFilter::OnDestruct() const {
|
||||
BrowserThread::DeleteOnUIThread::Destruct(this);
|
||||
}
|
||||
|
||||
void TtsMessageFilter::OnInitializeVoiceList() {
|
||||
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
||||
TtsController* tts_controller = TtsController::GetInstance();
|
||||
std::vector<VoiceData> voices;
|
||||
tts_controller->GetVoices(browser_context_, &voices);
|
||||
|
||||
std::vector<TtsVoice> out_voices;
|
||||
out_voices.resize(voices.size());
|
||||
for (size_t i = 0; i < voices.size(); ++i) {
|
||||
TtsVoice& out_voice = out_voices[i];
|
||||
out_voice.voice_uri = voices[i].name;
|
||||
out_voice.name = voices[i].name;
|
||||
out_voice.lang = voices[i].lang;
|
||||
out_voice.local_service = !voices[i].remote;
|
||||
out_voice.is_default = (i == 0);
|
||||
}
|
||||
Send(new TtsMsg_SetVoiceList(out_voices));
|
||||
}
|
||||
|
||||
void TtsMessageFilter::OnSpeak(const TtsUtteranceRequest& request) {
|
||||
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
||||
|
||||
std::unique_ptr<Utterance> utterance(new Utterance(browser_context_));
|
||||
utterance->set_src_id(request.id);
|
||||
utterance->set_text(request.text);
|
||||
utterance->set_lang(request.lang);
|
||||
utterance->set_voice_name(request.voice);
|
||||
utterance->set_can_enqueue(true);
|
||||
|
||||
UtteranceContinuousParameters params;
|
||||
params.rate = request.rate;
|
||||
params.pitch = request.pitch;
|
||||
params.volume = request.volume;
|
||||
utterance->set_continuous_parameters(params);
|
||||
|
||||
utterance->set_event_delegate(weak_ptr_factory_.GetWeakPtr());
|
||||
|
||||
TtsController::GetInstance()->SpeakOrEnqueue(utterance.release());
|
||||
}
|
||||
|
||||
void TtsMessageFilter::OnPause() {
|
||||
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
||||
TtsController::GetInstance()->Pause();
|
||||
}
|
||||
|
||||
void TtsMessageFilter::OnResume() {
|
||||
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
||||
TtsController::GetInstance()->Resume();
|
||||
}
|
||||
|
||||
void TtsMessageFilter::OnCancel() {
|
||||
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
||||
TtsController::GetInstance()->Stop();
|
||||
}
|
||||
|
||||
void TtsMessageFilter::OnTtsEvent(Utterance* utterance,
|
||||
TtsEventType event_type,
|
||||
int char_index,
|
||||
const std::string& error_message) {
|
||||
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
||||
switch (event_type) {
|
||||
case TTS_EVENT_START:
|
||||
Send(new TtsMsg_DidStartSpeaking(utterance->src_id()));
|
||||
break;
|
||||
case TTS_EVENT_END:
|
||||
Send(new TtsMsg_DidFinishSpeaking(utterance->src_id()));
|
||||
break;
|
||||
case TTS_EVENT_WORD:
|
||||
Send(new TtsMsg_WordBoundary(utterance->src_id(), char_index));
|
||||
break;
|
||||
case TTS_EVENT_SENTENCE:
|
||||
Send(new TtsMsg_SentenceBoundary(utterance->src_id(), char_index));
|
||||
break;
|
||||
case TTS_EVENT_MARKER:
|
||||
Send(new TtsMsg_MarkerEvent(utterance->src_id(), char_index));
|
||||
break;
|
||||
case TTS_EVENT_INTERRUPTED:
|
||||
Send(new TtsMsg_WasInterrupted(utterance->src_id()));
|
||||
break;
|
||||
case TTS_EVENT_CANCELLED:
|
||||
Send(new TtsMsg_WasCancelled(utterance->src_id()));
|
||||
break;
|
||||
case TTS_EVENT_ERROR:
|
||||
Send(
|
||||
new TtsMsg_SpeakingErrorOccurred(utterance->src_id(), error_message));
|
||||
break;
|
||||
case TTS_EVENT_PAUSE:
|
||||
Send(new TtsMsg_DidPauseSpeaking(utterance->src_id()));
|
||||
break;
|
||||
case TTS_EVENT_RESUME:
|
||||
Send(new TtsMsg_DidResumeSpeaking(utterance->src_id()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void TtsMessageFilter::OnVoicesChanged() {
|
||||
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
||||
OnInitializeVoiceList();
|
||||
}
|
||||
|
||||
void TtsMessageFilter::OnChannelClosingInUIThread() {
|
||||
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
||||
TtsController::GetInstance()->RemoveVoicesChangedDelegate(this);
|
||||
|
||||
weak_ptr_factory_.InvalidateWeakPtrs();
|
||||
Release(); // Balanced in TtsMessageFilter().
|
||||
}
|
||||
|
||||
TtsMessageFilter::~TtsMessageFilter() {
|
||||
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
||||
DCHECK(!weak_ptr_factory_.HasWeakPtrs());
|
||||
TtsController::GetInstance()->RemoveVoicesChangedDelegate(this);
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
// Copyright (c) 2013 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_SPEECH_TTS_MESSAGE_FILTER_H_
|
||||
#define CHROME_BROWSER_SPEECH_TTS_MESSAGE_FILTER_H_
|
||||
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "chrome/browser/speech/tts_controller.h"
|
||||
#include "chrome/common/tts_messages.h"
|
||||
#include "content/public/browser/browser_message_filter.h"
|
||||
|
||||
namespace content {
|
||||
class BrowserContext;
|
||||
}
|
||||
|
||||
class TtsMessageFilter : public content::BrowserMessageFilter,
|
||||
public UtteranceEventDelegate,
|
||||
public VoicesChangedDelegate {
|
||||
public:
|
||||
explicit TtsMessageFilter(int render_process_id,
|
||||
content::BrowserContext* browser_context);
|
||||
|
||||
// content::BrowserMessageFilter implementation.
|
||||
void OverrideThreadForMessage(const IPC::Message& message,
|
||||
content::BrowserThread::ID* thread) override;
|
||||
bool OnMessageReceived(const IPC::Message& message) override;
|
||||
void OnChannelClosing() override;
|
||||
void OnDestruct() const override;
|
||||
|
||||
// UtteranceEventDelegate implementation.
|
||||
void OnTtsEvent(Utterance* utterance,
|
||||
TtsEventType event_type,
|
||||
int char_index,
|
||||
const std::string& error_message) override;
|
||||
|
||||
// VoicesChangedDelegate implementation.
|
||||
void OnVoicesChanged() override;
|
||||
|
||||
private:
|
||||
friend class content::BrowserThread;
|
||||
friend class base::DeleteHelper<TtsMessageFilter>;
|
||||
|
||||
~TtsMessageFilter() override;
|
||||
|
||||
void OnInitializeVoiceList();
|
||||
void OnSpeak(const TtsUtteranceRequest& utterance);
|
||||
void OnPause();
|
||||
void OnResume();
|
||||
void OnCancel();
|
||||
|
||||
void OnChannelClosingInUIThread();
|
||||
|
||||
int render_process_id_;
|
||||
content::BrowserContext* browser_context_;
|
||||
|
||||
base::WeakPtrFactory<TtsMessageFilter> weak_ptr_factory_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(TtsMessageFilter);
|
||||
};
|
||||
|
||||
#endif // CHROME_BROWSER_SPEECH_TTS_MESSAGE_FILTER_H_
|
||||
@@ -1,28 +0,0 @@
|
||||
// Copyright (c) 2012 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/speech/tts_platform.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
bool TtsPlatformImpl::LoadBuiltInTtsExtension(
|
||||
content::BrowserContext* browser_context) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string TtsPlatformImpl::error() {
|
||||
return error_;
|
||||
}
|
||||
|
||||
void TtsPlatformImpl::clear_error() {
|
||||
error_ = std::string();
|
||||
}
|
||||
|
||||
void TtsPlatformImpl::set_error(const std::string& error) {
|
||||
error_ = error;
|
||||
}
|
||||
|
||||
void TtsPlatformImpl::WillSpeakUtteranceWithVoice(const Utterance* utterance,
|
||||
const VoiceData& voice_data) {
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
// Copyright (c) 2012 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_SPEECH_TTS_PLATFORM_H_
|
||||
#define CHROME_BROWSER_SPEECH_TTS_PLATFORM_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "chrome/browser/speech/tts_controller.h"
|
||||
|
||||
// Abstract class that defines the native platform TTS interface,
|
||||
// subclassed by specific implementations on Win, Mac, etc.
|
||||
class TtsPlatformImpl {
|
||||
public:
|
||||
static TtsPlatformImpl* GetInstance();
|
||||
|
||||
// Returns true if this platform implementation is supported and available.
|
||||
virtual bool PlatformImplAvailable() = 0;
|
||||
|
||||
// Some platforms may provide a built-in TTS extension. Returns true
|
||||
// if the extension was not previously loaded and is now loading, and
|
||||
// false if it's already loaded or if there's no extension to load.
|
||||
// Will call TtsController::RetrySpeakingQueuedUtterances when
|
||||
// the extension finishes loading.
|
||||
virtual bool LoadBuiltInTtsExtension(
|
||||
content::BrowserContext* browser_context);
|
||||
|
||||
// Speak the given utterance with the given parameters if possible,
|
||||
// and return true on success. Utterance will always be nonempty.
|
||||
// If rate, pitch, or volume are -1.0, they will be ignored.
|
||||
//
|
||||
// The TtsController will only try to speak one utterance at
|
||||
// a time. If it wants to interrupt speech, it will always call Stop
|
||||
// before speaking again.
|
||||
virtual bool Speak(int utterance_id,
|
||||
const std::string& utterance,
|
||||
const std::string& lang,
|
||||
const VoiceData& voice,
|
||||
const UtteranceContinuousParameters& params) = 0;
|
||||
|
||||
// Stop speaking immediately and return true on success.
|
||||
virtual bool StopSpeaking() = 0;
|
||||
|
||||
// Returns whether any speech is on going.
|
||||
virtual bool IsSpeaking() = 0;
|
||||
|
||||
// Append information about voices provided by this platform implementation
|
||||
// to |out_voices|.
|
||||
virtual void GetVoices(std::vector<VoiceData>* out_voices) = 0;
|
||||
|
||||
// Pause the current utterance, if any, until a call to Resume,
|
||||
// Speak, or StopSpeaking.
|
||||
virtual void Pause() = 0;
|
||||
|
||||
// Resume speaking the current utterance, if it was paused.
|
||||
virtual void Resume() = 0;
|
||||
|
||||
// Allows the platform to monitor speech commands and the voices used
|
||||
// for each one.
|
||||
virtual void WillSpeakUtteranceWithVoice(const Utterance* utterance,
|
||||
const VoiceData& voice_data);
|
||||
|
||||
virtual std::string error();
|
||||
virtual void clear_error();
|
||||
virtual void set_error(const std::string& error);
|
||||
|
||||
protected:
|
||||
TtsPlatformImpl() {}
|
||||
|
||||
// On some platforms this may be a leaky singleton - do not rely on the
|
||||
// destructor being called! http://crbug.com/122026
|
||||
virtual ~TtsPlatformImpl() {}
|
||||
|
||||
std::string error_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(TtsPlatformImpl);
|
||||
};
|
||||
|
||||
#endif // CHROME_BROWSER_SPEECH_TTS_PLATFORM_H_
|
||||
@@ -1,311 +0,0 @@
|
||||
// Copyright (c) 2012 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 <math.h>
|
||||
#include <objbase.h>
|
||||
#include <sapi.h>
|
||||
#include <sphelper.h>
|
||||
#include <wrl/client.h>
|
||||
|
||||
#include "base/memory/singleton.h"
|
||||
#include "base/strings/string_number_conversions.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "base/values.h"
|
||||
#include "base/win/scoped_co_mem.h"
|
||||
#include "chrome/browser/speech/tts_controller.h"
|
||||
#include "chrome/browser/speech/tts_platform.h"
|
||||
|
||||
namespace {
|
||||
// ISpObjectToken key and value names.
|
||||
const wchar_t kAttributesKey[] = L"Attributes";
|
||||
const wchar_t kGenderValue[] = L"Gender";
|
||||
const wchar_t kLanguageValue[] = L"Language";
|
||||
} // anonymous namespace.
|
||||
|
||||
class TtsPlatformImplWin : public TtsPlatformImpl {
|
||||
public:
|
||||
bool PlatformImplAvailable() override { return true; }
|
||||
|
||||
bool Speak(int utterance_id,
|
||||
const std::string& utterance,
|
||||
const std::string& lang,
|
||||
const VoiceData& voice,
|
||||
const UtteranceContinuousParameters& params) override;
|
||||
|
||||
bool StopSpeaking() override;
|
||||
|
||||
void Pause() override;
|
||||
|
||||
void Resume() override;
|
||||
|
||||
bool IsSpeaking() override;
|
||||
|
||||
void GetVoices(std::vector<VoiceData>* out_voices) override;
|
||||
|
||||
// Get the single instance of this class.
|
||||
static TtsPlatformImplWin* GetInstance();
|
||||
|
||||
static void __stdcall SpeechEventCallback(WPARAM w_param, LPARAM l_param);
|
||||
|
||||
private:
|
||||
TtsPlatformImplWin();
|
||||
~TtsPlatformImplWin() override {}
|
||||
|
||||
void OnSpeechEvent();
|
||||
void SetVoiceFromName(const std::string& name);
|
||||
Microsoft::WRL::ComPtr<ISpVoice> speech_synthesizer_;
|
||||
|
||||
// These apply to the current utterance only.
|
||||
std::wstring utterance_;
|
||||
int utterance_id_;
|
||||
int prefix_len_;
|
||||
ULONG stream_number_;
|
||||
int char_position_;
|
||||
bool paused_;
|
||||
std::string last_voice_name_;
|
||||
friend struct base::DefaultSingletonTraits<TtsPlatformImplWin>;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(TtsPlatformImplWin);
|
||||
};
|
||||
|
||||
// static
|
||||
TtsPlatformImpl* TtsPlatformImpl::GetInstance() {
|
||||
return TtsPlatformImplWin::GetInstance();
|
||||
}
|
||||
|
||||
bool TtsPlatformImplWin::Speak(int utterance_id,
|
||||
const std::string& src_utterance,
|
||||
const std::string& lang,
|
||||
const VoiceData& voice,
|
||||
const UtteranceContinuousParameters& params) {
|
||||
std::wstring prefix;
|
||||
std::wstring suffix;
|
||||
|
||||
if (!speech_synthesizer_.Get())
|
||||
return false;
|
||||
SetVoiceFromName(voice.name);
|
||||
if (params.rate >= 0.0) {
|
||||
// Map our multiplicative range of 0.1x to 10.0x onto Microsoft's
|
||||
// linear range of -10 to 10:
|
||||
// 0.1 -> -10
|
||||
// 1.0 -> 0
|
||||
// 10.0 -> 10
|
||||
speech_synthesizer_->SetRate(static_cast<int32_t>(10 * log10(params.rate)));
|
||||
}
|
||||
|
||||
if (params.pitch >= 0.0) {
|
||||
// The TTS api allows a range of -10 to 10 for speech pitch.
|
||||
// TODO(dtseng): cleanup if we ever use any other properties that
|
||||
// require xml.
|
||||
std::wstring pitch_value =
|
||||
base::IntToString16(static_cast<int>(params.pitch * 10 - 10));
|
||||
prefix = L"<pitch absmiddle=\"" + pitch_value + L"\">";
|
||||
suffix = L"</pitch>";
|
||||
}
|
||||
|
||||
if (params.volume >= 0.0) {
|
||||
// The TTS api allows a range of 0 to 100 for speech volume.
|
||||
speech_synthesizer_->SetVolume(static_cast<uint16_t>(params.volume * 100));
|
||||
}
|
||||
|
||||
// TODO(dmazzoni): convert SSML to SAPI xml. http://crbug.com/88072
|
||||
|
||||
utterance_ = base::UTF8ToWide(src_utterance);
|
||||
utterance_id_ = utterance_id;
|
||||
char_position_ = 0;
|
||||
std::wstring merged_utterance = prefix + utterance_ + suffix;
|
||||
prefix_len_ = prefix.size();
|
||||
|
||||
HRESULT result = speech_synthesizer_->Speak(merged_utterance.c_str(),
|
||||
SPF_ASYNC, &stream_number_);
|
||||
return (result == S_OK);
|
||||
}
|
||||
|
||||
bool TtsPlatformImplWin::StopSpeaking() {
|
||||
if (speech_synthesizer_.Get()) {
|
||||
// Clear the stream number so that any further events relating to this
|
||||
// utterance are ignored.
|
||||
stream_number_ = 0;
|
||||
|
||||
if (IsSpeaking()) {
|
||||
// Stop speech by speaking the empty string with the purge flag.
|
||||
speech_synthesizer_->Speak(L"", SPF_ASYNC | SPF_PURGEBEFORESPEAK, NULL);
|
||||
}
|
||||
if (paused_) {
|
||||
speech_synthesizer_->Resume();
|
||||
paused_ = false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void TtsPlatformImplWin::Pause() {
|
||||
if (speech_synthesizer_.Get() && utterance_id_ && !paused_) {
|
||||
speech_synthesizer_->Pause();
|
||||
paused_ = true;
|
||||
TtsController::GetInstance()->OnTtsEvent(utterance_id_, TTS_EVENT_PAUSE,
|
||||
char_position_, "");
|
||||
}
|
||||
}
|
||||
|
||||
void TtsPlatformImplWin::Resume() {
|
||||
if (speech_synthesizer_.Get() && utterance_id_ && paused_) {
|
||||
speech_synthesizer_->Resume();
|
||||
paused_ = false;
|
||||
TtsController::GetInstance()->OnTtsEvent(utterance_id_, TTS_EVENT_RESUME,
|
||||
char_position_, "");
|
||||
}
|
||||
}
|
||||
|
||||
bool TtsPlatformImplWin::IsSpeaking() {
|
||||
if (speech_synthesizer_.Get()) {
|
||||
SPVOICESTATUS status;
|
||||
HRESULT result = speech_synthesizer_->GetStatus(&status, NULL);
|
||||
if (result == S_OK) {
|
||||
if (status.dwRunningState == 0 || // 0 == waiting to speak
|
||||
status.dwRunningState == SPRS_IS_SPEAKING) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void TtsPlatformImplWin::GetVoices(std::vector<VoiceData>* out_voices) {
|
||||
Microsoft::WRL::ComPtr<IEnumSpObjectTokens> voice_tokens;
|
||||
unsigned long voice_count;
|
||||
if (S_OK !=
|
||||
SpEnumTokens(SPCAT_VOICES, NULL, NULL, voice_tokens.GetAddressOf()))
|
||||
return;
|
||||
if (S_OK != voice_tokens->GetCount(&voice_count))
|
||||
return;
|
||||
for (unsigned i = 0; i < voice_count; i++) {
|
||||
VoiceData voice;
|
||||
Microsoft::WRL::ComPtr<ISpObjectToken> voice_token;
|
||||
if (S_OK != voice_tokens->Next(1, voice_token.GetAddressOf(), NULL))
|
||||
return;
|
||||
base::win::ScopedCoMem<WCHAR> description;
|
||||
if (S_OK != SpGetDescription(voice_token.Get(), &description))
|
||||
continue;
|
||||
voice.name = base::WideToUTF8(description.get());
|
||||
Microsoft::WRL::ComPtr<ISpDataKey> attributes;
|
||||
if (S_OK != voice_token->OpenKey(kAttributesKey, attributes.GetAddressOf()))
|
||||
continue;
|
||||
base::win::ScopedCoMem<WCHAR> gender;
|
||||
if (S_OK == attributes->GetStringValue(kGenderValue, &gender)) {
|
||||
if (0 == _wcsicmp(gender.get(), L"male"))
|
||||
voice.gender = TTS_GENDER_MALE;
|
||||
else if (0 == _wcsicmp(gender.get(), L"female"))
|
||||
voice.gender = TTS_GENDER_FEMALE;
|
||||
}
|
||||
base::win::ScopedCoMem<WCHAR> language;
|
||||
if (S_OK == attributes->GetStringValue(kLanguageValue, &language)) {
|
||||
int lcid_value;
|
||||
base::HexStringToInt(base::WideToUTF8(language.get()), &lcid_value);
|
||||
LCID lcid = MAKELCID(lcid_value, SORT_DEFAULT);
|
||||
WCHAR locale_name[LOCALE_NAME_MAX_LENGTH] = {0};
|
||||
LCIDToLocaleName(lcid, locale_name, LOCALE_NAME_MAX_LENGTH, 0);
|
||||
voice.lang = base::WideToUTF8(locale_name);
|
||||
}
|
||||
voice.native = true;
|
||||
voice.events.insert(TTS_EVENT_START);
|
||||
voice.events.insert(TTS_EVENT_END);
|
||||
voice.events.insert(TTS_EVENT_MARKER);
|
||||
voice.events.insert(TTS_EVENT_WORD);
|
||||
voice.events.insert(TTS_EVENT_SENTENCE);
|
||||
voice.events.insert(TTS_EVENT_PAUSE);
|
||||
voice.events.insert(TTS_EVENT_RESUME);
|
||||
out_voices->push_back(voice);
|
||||
}
|
||||
}
|
||||
|
||||
void TtsPlatformImplWin::OnSpeechEvent() {
|
||||
TtsController* controller = TtsController::GetInstance();
|
||||
SPEVENT event;
|
||||
while (S_OK == speech_synthesizer_->GetEvents(1, &event, NULL)) {
|
||||
if (event.ulStreamNum != stream_number_)
|
||||
continue;
|
||||
|
||||
switch (event.eEventId) {
|
||||
case SPEI_START_INPUT_STREAM:
|
||||
controller->OnTtsEvent(utterance_id_, TTS_EVENT_START, 0,
|
||||
std::string());
|
||||
break;
|
||||
case SPEI_END_INPUT_STREAM:
|
||||
char_position_ = utterance_.size();
|
||||
controller->OnTtsEvent(utterance_id_, TTS_EVENT_END, char_position_,
|
||||
std::string());
|
||||
break;
|
||||
case SPEI_TTS_BOOKMARK:
|
||||
controller->OnTtsEvent(utterance_id_, TTS_EVENT_MARKER, char_position_,
|
||||
std::string());
|
||||
break;
|
||||
case SPEI_WORD_BOUNDARY:
|
||||
char_position_ = static_cast<ULONG>(event.lParam) - prefix_len_;
|
||||
controller->OnTtsEvent(utterance_id_, TTS_EVENT_WORD, char_position_,
|
||||
std::string());
|
||||
break;
|
||||
case SPEI_SENTENCE_BOUNDARY:
|
||||
char_position_ = static_cast<ULONG>(event.lParam) - prefix_len_;
|
||||
controller->OnTtsEvent(utterance_id_, TTS_EVENT_SENTENCE,
|
||||
char_position_, std::string());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
void TtsPlatformImplWin::SetVoiceFromName(const std::string& name) {
|
||||
if (name.empty() || name == last_voice_name_)
|
||||
return;
|
||||
last_voice_name_ = name;
|
||||
Microsoft::WRL::ComPtr<IEnumSpObjectTokens> voice_tokens;
|
||||
unsigned long voice_count;
|
||||
if (S_OK !=
|
||||
SpEnumTokens(SPCAT_VOICES, NULL, NULL, voice_tokens.GetAddressOf()))
|
||||
return;
|
||||
if (S_OK != voice_tokens->GetCount(&voice_count))
|
||||
return;
|
||||
for (unsigned i = 0; i < voice_count; i++) {
|
||||
Microsoft::WRL::ComPtr<ISpObjectToken> voice_token;
|
||||
if (S_OK != voice_tokens->Next(1, voice_token.GetAddressOf(), NULL))
|
||||
return;
|
||||
base::win::ScopedCoMem<WCHAR> description;
|
||||
if (S_OK != SpGetDescription(voice_token.Get(), &description))
|
||||
continue;
|
||||
if (name == base::WideToUTF8(description.get())) {
|
||||
speech_synthesizer_->SetVoice(voice_token.Get());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
TtsPlatformImplWin::TtsPlatformImplWin()
|
||||
: utterance_id_(0),
|
||||
prefix_len_(0),
|
||||
stream_number_(0),
|
||||
char_position_(0),
|
||||
paused_(false) {
|
||||
::CoCreateInstance(CLSID_SpVoice, nullptr, CLSCTX_ALL,
|
||||
IID_PPV_ARGS(&speech_synthesizer_));
|
||||
if (speech_synthesizer_.Get()) {
|
||||
ULONGLONG event_mask =
|
||||
SPFEI(SPEI_START_INPUT_STREAM) | SPFEI(SPEI_TTS_BOOKMARK) |
|
||||
SPFEI(SPEI_WORD_BOUNDARY) | SPFEI(SPEI_SENTENCE_BOUNDARY) |
|
||||
SPFEI(SPEI_END_INPUT_STREAM);
|
||||
speech_synthesizer_->SetInterest(event_mask, event_mask);
|
||||
speech_synthesizer_->SetNotifyCallbackFunction(
|
||||
TtsPlatformImplWin::SpeechEventCallback, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
TtsPlatformImplWin* TtsPlatformImplWin::GetInstance() {
|
||||
return base::Singleton<TtsPlatformImplWin,
|
||||
base::LeakySingletonTraits<TtsPlatformImplWin>>::get();
|
||||
}
|
||||
|
||||
// static
|
||||
void TtsPlatformImplWin::SpeechEventCallback(WPARAM w_param, LPARAM l_param) {
|
||||
GetInstance()->OnSpeechEvent();
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
// Copyright (c) 2013 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.
|
||||
|
||||
// Multiply-included message file, hence no include guard.
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "chrome/common/tts_utterance_request.h"
|
||||
#include "ipc/ipc_message_macros.h"
|
||||
#include "ipc/ipc_param_traits.h"
|
||||
|
||||
#define IPC_MESSAGE_START TtsMsgStart
|
||||
|
||||
IPC_STRUCT_TRAITS_BEGIN(TtsUtteranceRequest)
|
||||
IPC_STRUCT_TRAITS_MEMBER(id)
|
||||
IPC_STRUCT_TRAITS_MEMBER(text)
|
||||
IPC_STRUCT_TRAITS_MEMBER(lang)
|
||||
IPC_STRUCT_TRAITS_MEMBER(voice)
|
||||
IPC_STRUCT_TRAITS_MEMBER(volume)
|
||||
IPC_STRUCT_TRAITS_MEMBER(rate)
|
||||
IPC_STRUCT_TRAITS_MEMBER(pitch)
|
||||
IPC_STRUCT_TRAITS_END()
|
||||
|
||||
IPC_STRUCT_TRAITS_BEGIN(TtsVoice)
|
||||
IPC_STRUCT_TRAITS_MEMBER(voice_uri)
|
||||
IPC_STRUCT_TRAITS_MEMBER(name)
|
||||
IPC_STRUCT_TRAITS_MEMBER(lang)
|
||||
IPC_STRUCT_TRAITS_MEMBER(local_service)
|
||||
IPC_STRUCT_TRAITS_MEMBER(is_default)
|
||||
IPC_STRUCT_TRAITS_END()
|
||||
|
||||
// Renderer -> Browser messages.
|
||||
|
||||
IPC_MESSAGE_CONTROL0(TtsHostMsg_InitializeVoiceList)
|
||||
IPC_MESSAGE_CONTROL1(TtsHostMsg_Speak, TtsUtteranceRequest)
|
||||
IPC_MESSAGE_CONTROL0(TtsHostMsg_Pause)
|
||||
IPC_MESSAGE_CONTROL0(TtsHostMsg_Resume)
|
||||
IPC_MESSAGE_CONTROL0(TtsHostMsg_Cancel)
|
||||
|
||||
// Browser -> Renderer messages.
|
||||
|
||||
IPC_MESSAGE_CONTROL1(TtsMsg_SetVoiceList, std::vector<TtsVoice>)
|
||||
IPC_MESSAGE_CONTROL1(TtsMsg_DidStartSpeaking, int /* utterance id */)
|
||||
IPC_MESSAGE_CONTROL1(TtsMsg_DidFinishSpeaking, int /* utterance id */)
|
||||
IPC_MESSAGE_CONTROL1(TtsMsg_DidPauseSpeaking, int /* utterance id */)
|
||||
IPC_MESSAGE_CONTROL1(TtsMsg_DidResumeSpeaking, int /* utterance id */)
|
||||
IPC_MESSAGE_CONTROL2(TtsMsg_WordBoundary,
|
||||
int /* utterance id */,
|
||||
int /* char index */)
|
||||
IPC_MESSAGE_CONTROL2(TtsMsg_SentenceBoundary,
|
||||
int /* utterance id */,
|
||||
int /* char index */)
|
||||
IPC_MESSAGE_CONTROL2(TtsMsg_MarkerEvent,
|
||||
int /* utterance id */,
|
||||
int /* char index */)
|
||||
IPC_MESSAGE_CONTROL1(TtsMsg_WasInterrupted, int /* utterance id */)
|
||||
IPC_MESSAGE_CONTROL1(TtsMsg_WasCancelled, int /* utterance id */)
|
||||
IPC_MESSAGE_CONTROL2(TtsMsg_SpeakingErrorOccurred,
|
||||
int /* utterance id */,
|
||||
std::string /* error message */)
|
||||
@@ -1,20 +0,0 @@
|
||||
// Copyright (c) 2013 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/common/tts_utterance_request.h"
|
||||
|
||||
TtsUtteranceRequest::TtsUtteranceRequest()
|
||||
: id(0), volume(1.0), rate(1.0), pitch(1.0) {}
|
||||
|
||||
TtsUtteranceRequest::~TtsUtteranceRequest() {}
|
||||
|
||||
TtsVoice::TtsVoice() : local_service(true), is_default(false) {}
|
||||
|
||||
TtsVoice::TtsVoice(const TtsVoice&) = default;
|
||||
|
||||
TtsVoice::~TtsVoice() {}
|
||||
|
||||
TtsUtteranceResponse::TtsUtteranceResponse() : id(0) {}
|
||||
|
||||
TtsUtteranceResponse::~TtsUtteranceResponse() {}
|
||||
@@ -1,45 +0,0 @@
|
||||
// Copyright (c) 2013 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_COMMON_TTS_UTTERANCE_REQUEST_H_
|
||||
#define CHROME_COMMON_TTS_UTTERANCE_REQUEST_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "base/macros.h"
|
||||
#include "base/strings/string16.h"
|
||||
|
||||
struct TtsUtteranceRequest {
|
||||
TtsUtteranceRequest();
|
||||
~TtsUtteranceRequest();
|
||||
|
||||
int id;
|
||||
std::string text;
|
||||
std::string lang;
|
||||
std::string voice;
|
||||
float volume;
|
||||
float rate;
|
||||
float pitch;
|
||||
};
|
||||
|
||||
struct TtsVoice {
|
||||
TtsVoice();
|
||||
TtsVoice(const TtsVoice&);
|
||||
~TtsVoice();
|
||||
|
||||
std::string voice_uri;
|
||||
std::string name;
|
||||
std::string lang;
|
||||
bool local_service;
|
||||
bool is_default;
|
||||
};
|
||||
|
||||
struct TtsUtteranceResponse {
|
||||
TtsUtteranceResponse();
|
||||
~TtsUtteranceResponse();
|
||||
|
||||
int id;
|
||||
};
|
||||
|
||||
#endif // CHROME_COMMON_TTS_UTTERANCE_REQUEST_H_
|
||||
@@ -1,198 +0,0 @@
|
||||
// Copyright (c) 2013 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/renderer/tts_dispatcher.h"
|
||||
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "chrome/common/tts_messages.h"
|
||||
#include "chrome/common/tts_utterance_request.h"
|
||||
#include "content/public/renderer/render_thread.h"
|
||||
#include "third_party/blink/public/platform/web_speech_synthesis_utterance.h"
|
||||
#include "third_party/blink/public/platform/web_speech_synthesis_voice.h"
|
||||
#include "third_party/blink/public/platform/web_string.h"
|
||||
#include "third_party/blink/public/platform/web_vector.h"
|
||||
|
||||
using blink::WebSpeechSynthesisUtterance;
|
||||
using blink::WebSpeechSynthesisVoice;
|
||||
using blink::WebSpeechSynthesizerClient;
|
||||
using blink::WebString;
|
||||
using blink::WebVector;
|
||||
using content::RenderThread;
|
||||
|
||||
int TtsDispatcher::next_utterance_id_ = 1;
|
||||
|
||||
TtsDispatcher::TtsDispatcher(WebSpeechSynthesizerClient* client)
|
||||
: synthesizer_client_(client) {
|
||||
RenderThread::Get()->AddObserver(this);
|
||||
}
|
||||
|
||||
TtsDispatcher::~TtsDispatcher() {
|
||||
RenderThread::Get()->RemoveObserver(this);
|
||||
}
|
||||
|
||||
bool TtsDispatcher::OnControlMessageReceived(const IPC::Message& message) {
|
||||
IPC_BEGIN_MESSAGE_MAP(TtsDispatcher, message)
|
||||
IPC_MESSAGE_HANDLER(TtsMsg_SetVoiceList, OnSetVoiceList)
|
||||
IPC_MESSAGE_HANDLER(TtsMsg_DidStartSpeaking, OnDidStartSpeaking)
|
||||
IPC_MESSAGE_HANDLER(TtsMsg_DidFinishSpeaking, OnDidFinishSpeaking)
|
||||
IPC_MESSAGE_HANDLER(TtsMsg_DidPauseSpeaking, OnDidPauseSpeaking)
|
||||
IPC_MESSAGE_HANDLER(TtsMsg_DidResumeSpeaking, OnDidResumeSpeaking)
|
||||
IPC_MESSAGE_HANDLER(TtsMsg_WordBoundary, OnWordBoundary)
|
||||
IPC_MESSAGE_HANDLER(TtsMsg_SentenceBoundary, OnSentenceBoundary)
|
||||
IPC_MESSAGE_HANDLER(TtsMsg_MarkerEvent, OnMarkerEvent)
|
||||
IPC_MESSAGE_HANDLER(TtsMsg_WasInterrupted, OnWasInterrupted)
|
||||
IPC_MESSAGE_HANDLER(TtsMsg_WasCancelled, OnWasCancelled)
|
||||
IPC_MESSAGE_HANDLER(TtsMsg_SpeakingErrorOccurred, OnSpeakingErrorOccurred)
|
||||
IPC_END_MESSAGE_MAP()
|
||||
|
||||
// Always return false because there may be multiple TtsDispatchers
|
||||
// and we want them all to have a chance to handle this message.
|
||||
return false;
|
||||
}
|
||||
|
||||
void TtsDispatcher::UpdateVoiceList() {
|
||||
RenderThread::Get()->Send(new TtsHostMsg_InitializeVoiceList());
|
||||
}
|
||||
|
||||
void TtsDispatcher::Speak(const WebSpeechSynthesisUtterance& web_utterance) {
|
||||
int id = next_utterance_id_++;
|
||||
|
||||
utterance_id_map_[id] = web_utterance;
|
||||
|
||||
TtsUtteranceRequest utterance;
|
||||
utterance.id = id;
|
||||
utterance.text = web_utterance.GetText().Utf8();
|
||||
utterance.lang = web_utterance.Lang().Utf8();
|
||||
utterance.voice = web_utterance.Voice().Utf8();
|
||||
utterance.volume = web_utterance.Volume();
|
||||
utterance.rate = web_utterance.Rate();
|
||||
utterance.pitch = web_utterance.Pitch();
|
||||
RenderThread::Get()->Send(new TtsHostMsg_Speak(utterance));
|
||||
}
|
||||
|
||||
void TtsDispatcher::Pause() {
|
||||
RenderThread::Get()->Send(new TtsHostMsg_Pause());
|
||||
}
|
||||
|
||||
void TtsDispatcher::Resume() {
|
||||
RenderThread::Get()->Send(new TtsHostMsg_Resume());
|
||||
}
|
||||
|
||||
void TtsDispatcher::Cancel() {
|
||||
RenderThread::Get()->Send(new TtsHostMsg_Cancel());
|
||||
}
|
||||
|
||||
WebSpeechSynthesisUtterance TtsDispatcher::FindUtterance(int utterance_id) {
|
||||
base::hash_map<int, WebSpeechSynthesisUtterance>::const_iterator iter =
|
||||
utterance_id_map_.find(utterance_id);
|
||||
if (iter == utterance_id_map_.end())
|
||||
return WebSpeechSynthesisUtterance();
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
void TtsDispatcher::OnSetVoiceList(const std::vector<TtsVoice>& voices) {
|
||||
WebVector<WebSpeechSynthesisVoice> out_voices(voices.size());
|
||||
for (size_t i = 0; i < voices.size(); ++i) {
|
||||
out_voices[i] = WebSpeechSynthesisVoice();
|
||||
out_voices[i].SetVoiceURI(WebString::FromUTF8(voices[i].voice_uri));
|
||||
out_voices[i].SetName(WebString::FromUTF8(voices[i].name));
|
||||
out_voices[i].SetLanguage(WebString::FromUTF8(voices[i].lang));
|
||||
out_voices[i].SetIsLocalService(voices[i].local_service);
|
||||
out_voices[i].SetIsDefault(voices[i].is_default);
|
||||
}
|
||||
synthesizer_client_->SetVoiceList(out_voices);
|
||||
}
|
||||
|
||||
void TtsDispatcher::OnDidStartSpeaking(int utterance_id) {
|
||||
if (utterance_id_map_.find(utterance_id) == utterance_id_map_.end())
|
||||
return;
|
||||
|
||||
WebSpeechSynthesisUtterance utterance = FindUtterance(utterance_id);
|
||||
if (utterance.IsNull())
|
||||
return;
|
||||
|
||||
synthesizer_client_->DidStartSpeaking(utterance);
|
||||
}
|
||||
|
||||
void TtsDispatcher::OnDidFinishSpeaking(int utterance_id) {
|
||||
WebSpeechSynthesisUtterance utterance = FindUtterance(utterance_id);
|
||||
if (utterance.IsNull())
|
||||
return;
|
||||
|
||||
synthesizer_client_->DidFinishSpeaking(utterance);
|
||||
utterance_id_map_.erase(utterance_id);
|
||||
}
|
||||
|
||||
void TtsDispatcher::OnDidPauseSpeaking(int utterance_id) {
|
||||
WebSpeechSynthesisUtterance utterance = FindUtterance(utterance_id);
|
||||
if (utterance.IsNull())
|
||||
return;
|
||||
|
||||
synthesizer_client_->DidPauseSpeaking(utterance);
|
||||
}
|
||||
|
||||
void TtsDispatcher::OnDidResumeSpeaking(int utterance_id) {
|
||||
WebSpeechSynthesisUtterance utterance = FindUtterance(utterance_id);
|
||||
if (utterance.IsNull())
|
||||
return;
|
||||
|
||||
synthesizer_client_->DidResumeSpeaking(utterance);
|
||||
}
|
||||
|
||||
void TtsDispatcher::OnWordBoundary(int utterance_id, int char_index) {
|
||||
CHECK(char_index >= 0);
|
||||
|
||||
WebSpeechSynthesisUtterance utterance = FindUtterance(utterance_id);
|
||||
if (utterance.IsNull())
|
||||
return;
|
||||
|
||||
synthesizer_client_->WordBoundaryEventOccurred(
|
||||
utterance, static_cast<unsigned>(char_index));
|
||||
}
|
||||
|
||||
void TtsDispatcher::OnSentenceBoundary(int utterance_id, int char_index) {
|
||||
CHECK(char_index >= 0);
|
||||
|
||||
WebSpeechSynthesisUtterance utterance = FindUtterance(utterance_id);
|
||||
if (utterance.IsNull())
|
||||
return;
|
||||
|
||||
synthesizer_client_->SentenceBoundaryEventOccurred(
|
||||
utterance, static_cast<unsigned>(char_index));
|
||||
}
|
||||
|
||||
void TtsDispatcher::OnMarkerEvent(int utterance_id, int char_index) {
|
||||
// Not supported yet.
|
||||
}
|
||||
|
||||
void TtsDispatcher::OnWasInterrupted(int utterance_id) {
|
||||
WebSpeechSynthesisUtterance utterance = FindUtterance(utterance_id);
|
||||
if (utterance.IsNull())
|
||||
return;
|
||||
|
||||
// The web speech API doesn't support "interrupted".
|
||||
synthesizer_client_->DidFinishSpeaking(utterance);
|
||||
utterance_id_map_.erase(utterance_id);
|
||||
}
|
||||
|
||||
void TtsDispatcher::OnWasCancelled(int utterance_id) {
|
||||
WebSpeechSynthesisUtterance utterance = FindUtterance(utterance_id);
|
||||
if (utterance.IsNull())
|
||||
return;
|
||||
|
||||
// The web speech API doesn't support "cancelled".
|
||||
synthesizer_client_->DidFinishSpeaking(utterance);
|
||||
utterance_id_map_.erase(utterance_id);
|
||||
}
|
||||
|
||||
void TtsDispatcher::OnSpeakingErrorOccurred(int utterance_id,
|
||||
const std::string& error_message) {
|
||||
WebSpeechSynthesisUtterance utterance = FindUtterance(utterance_id);
|
||||
if (utterance.IsNull())
|
||||
return;
|
||||
|
||||
// The web speech API doesn't support an error message.
|
||||
synthesizer_client_->SpeakingErrorOccurred(utterance);
|
||||
utterance_id_map_.erase(utterance_id);
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
// Copyright (c) 2013 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_RENDERER_TTS_DISPATCHER_H_
|
||||
#define CHROME_RENDERER_TTS_DISPATCHER_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "base/containers/hash_tables.h"
|
||||
#include "content/public/renderer/render_thread_observer.h"
|
||||
#include "third_party/blink/public/platform/web_speech_synthesizer.h"
|
||||
#include "third_party/blink/public/platform/web_speech_synthesizer_client.h"
|
||||
|
||||
namespace IPC {
|
||||
class Message;
|
||||
}
|
||||
|
||||
struct TtsVoice;
|
||||
|
||||
// TtsDispatcher is a delegate for methods used by Blink for speech synthesis
|
||||
// APIs. It's the complement of TtsDispatcherHost (owned by RenderViewHost).
|
||||
// Each TtsDispatcher is owned by the WebSpeechSynthesizerClient in Blink;
|
||||
// it registers itself to listen to IPC upon construction and unregisters
|
||||
// itself when deleted. There can be multiple TtsDispatchers alive at once,
|
||||
// so each one routes IPC messages to its WebSpeechSynthesizerClient only if
|
||||
// the utterance id (which is globally unique) matches.
|
||||
class TtsDispatcher : public blink::WebSpeechSynthesizer,
|
||||
public content::RenderThreadObserver {
|
||||
public:
|
||||
explicit TtsDispatcher(blink::WebSpeechSynthesizerClient* client);
|
||||
~TtsDispatcher() override;
|
||||
|
||||
private:
|
||||
// RenderProcessObserver override.
|
||||
bool OnControlMessageReceived(const IPC::Message& message) override;
|
||||
|
||||
// blink::WebSpeechSynthesizer implementation.
|
||||
void UpdateVoiceList() override;
|
||||
void Speak(const blink::WebSpeechSynthesisUtterance& utterance) override;
|
||||
void Pause() override;
|
||||
void Resume() override;
|
||||
void Cancel() override;
|
||||
|
||||
blink::WebSpeechSynthesisUtterance FindUtterance(int utterance_id);
|
||||
|
||||
void OnSetVoiceList(const std::vector<TtsVoice>& voices);
|
||||
void OnDidStartSpeaking(int utterance_id);
|
||||
void OnDidFinishSpeaking(int utterance_id);
|
||||
void OnDidPauseSpeaking(int utterance_id);
|
||||
void OnDidResumeSpeaking(int utterance_id);
|
||||
void OnWordBoundary(int utterance_id, int char_index);
|
||||
void OnSentenceBoundary(int utterance_id, int char_index);
|
||||
void OnMarkerEvent(int utterance_id, int char_index);
|
||||
void OnWasInterrupted(int utterance_id);
|
||||
void OnWasCancelled(int utterance_id);
|
||||
void OnSpeakingErrorOccurred(int utterance_id,
|
||||
const std::string& error_message);
|
||||
|
||||
// The WebKit client class that we use to send events back to the JS world.
|
||||
// Weak reference, this will be valid as long as this object exists.
|
||||
blink::WebSpeechSynthesizerClient* synthesizer_client_;
|
||||
|
||||
// Next utterance id, used to map response IPCs to utterance objects.
|
||||
static int next_utterance_id_;
|
||||
|
||||
// Map from id to utterance objects.
|
||||
base::hash_map<int, blink::WebSpeechSynthesisUtterance> utterance_id_map_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(TtsDispatcher);
|
||||
};
|
||||
|
||||
#endif // CHROME_RENDERER_TTS_DISPATCHER_H_
|
||||
@@ -1,52 +0,0 @@
|
||||
// This is generated file. Do not modify directly.
|
||||
// Path to the code generator:
|
||||
// tools/generate_library_loader/generate_library_loader.py .
|
||||
|
||||
#ifndef LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H
|
||||
#define LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H
|
||||
|
||||
#include "third_party/speech-dispatcher/libspeechd.h"
|
||||
#define LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN
|
||||
|
||||
#include <string>
|
||||
|
||||
class LibSpeechdLoader {
|
||||
public:
|
||||
LibSpeechdLoader();
|
||||
~LibSpeechdLoader();
|
||||
|
||||
bool Load(const std::string& library_name)
|
||||
__attribute__((warn_unused_result));
|
||||
|
||||
bool loaded() const { return loaded_; }
|
||||
|
||||
decltype(&::spd_open) spd_open;
|
||||
decltype(&::spd_say) spd_say;
|
||||
decltype(&::spd_stop) spd_stop;
|
||||
decltype(&::spd_close) spd_close;
|
||||
decltype(&::spd_pause) spd_pause;
|
||||
decltype(&::spd_resume) spd_resume;
|
||||
decltype(&::spd_set_notification_on) spd_set_notification_on;
|
||||
decltype(&::spd_set_voice_rate) spd_set_voice_rate;
|
||||
decltype(&::spd_set_voice_pitch) spd_set_voice_pitch;
|
||||
decltype(&::spd_list_synthesis_voices) spd_list_synthesis_voices;
|
||||
decltype(&::spd_set_synthesis_voice) spd_set_synthesis_voice;
|
||||
decltype(&::spd_list_modules) spd_list_modules;
|
||||
decltype(&::spd_set_output_module) spd_set_output_module;
|
||||
decltype(&::spd_set_language) spd_set_language;
|
||||
|
||||
private:
|
||||
void CleanUp(bool unload);
|
||||
|
||||
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN)
|
||||
void* library_;
|
||||
#endif
|
||||
|
||||
bool loaded_;
|
||||
|
||||
// Disallow copy constructor and assignment operator.
|
||||
LibSpeechdLoader(const LibSpeechdLoader&);
|
||||
void operator=(const LibSpeechdLoader&);
|
||||
};
|
||||
|
||||
#endif // LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H
|
||||
@@ -1,252 +0,0 @@
|
||||
// This is generated file. Do not modify directly.
|
||||
// Path to the code generator:
|
||||
// tools/generate_library_loader/generate_library_loader.py .
|
||||
|
||||
#include "library_loaders/libspeechd.h"
|
||||
|
||||
#include <dlfcn.h>
|
||||
|
||||
// Put these sanity checks here so that they fire at most once
|
||||
// (to avoid cluttering the build output).
|
||||
#if !defined( \
|
||||
LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN) && \
|
||||
!defined( \
|
||||
LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED)
|
||||
#error neither LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN nor LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED defined
|
||||
#endif
|
||||
#if defined( \
|
||||
LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN) && \
|
||||
defined( \
|
||||
LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED)
|
||||
#error both LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN and LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED defined
|
||||
#endif
|
||||
|
||||
LibSpeechdLoader::LibSpeechdLoader() : loaded_(false) {}
|
||||
|
||||
LibSpeechdLoader::~LibSpeechdLoader() {
|
||||
CleanUp(loaded_);
|
||||
}
|
||||
|
||||
bool LibSpeechdLoader::Load(const std::string& library_name) {
|
||||
if (loaded_)
|
||||
return false;
|
||||
|
||||
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN)
|
||||
library_ = dlopen(library_name.c_str(), RTLD_LAZY);
|
||||
if (!library_)
|
||||
return false;
|
||||
#endif
|
||||
|
||||
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN)
|
||||
spd_open =
|
||||
reinterpret_cast<decltype(this->spd_open)>(dlsym(library_, "spd_open"));
|
||||
#endif
|
||||
#if defined( \
|
||||
LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED)
|
||||
spd_open = &::spd_open;
|
||||
#endif
|
||||
if (!spd_open) {
|
||||
CleanUp(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN)
|
||||
spd_say =
|
||||
reinterpret_cast<decltype(this->spd_say)>(dlsym(library_, "spd_say"));
|
||||
#endif
|
||||
#if defined( \
|
||||
LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED)
|
||||
spd_say = &::spd_say;
|
||||
#endif
|
||||
if (!spd_say) {
|
||||
CleanUp(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN)
|
||||
spd_stop =
|
||||
reinterpret_cast<decltype(this->spd_stop)>(dlsym(library_, "spd_stop"));
|
||||
#endif
|
||||
#if defined( \
|
||||
LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED)
|
||||
spd_stop = &::spd_stop;
|
||||
#endif
|
||||
if (!spd_stop) {
|
||||
CleanUp(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN)
|
||||
spd_close =
|
||||
reinterpret_cast<decltype(this->spd_close)>(dlsym(library_, "spd_close"));
|
||||
#endif
|
||||
#if defined( \
|
||||
LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED)
|
||||
spd_close = &::spd_close;
|
||||
#endif
|
||||
if (!spd_close) {
|
||||
CleanUp(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN)
|
||||
spd_pause =
|
||||
reinterpret_cast<decltype(this->spd_pause)>(dlsym(library_, "spd_pause"));
|
||||
#endif
|
||||
#if defined( \
|
||||
LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED)
|
||||
spd_pause = &::spd_pause;
|
||||
#endif
|
||||
if (!spd_pause) {
|
||||
CleanUp(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN)
|
||||
spd_resume = reinterpret_cast<decltype(this->spd_resume)>(
|
||||
dlsym(library_, "spd_resume"));
|
||||
#endif
|
||||
#if defined( \
|
||||
LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED)
|
||||
spd_resume = &::spd_resume;
|
||||
#endif
|
||||
if (!spd_resume) {
|
||||
CleanUp(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN)
|
||||
spd_set_notification_on =
|
||||
reinterpret_cast<decltype(this->spd_set_notification_on)>(
|
||||
dlsym(library_, "spd_set_notification_on"));
|
||||
#endif
|
||||
#if defined( \
|
||||
LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED)
|
||||
spd_set_notification_on = &::spd_set_notification_on;
|
||||
#endif
|
||||
if (!spd_set_notification_on) {
|
||||
CleanUp(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN)
|
||||
spd_set_voice_rate = reinterpret_cast<decltype(this->spd_set_voice_rate)>(
|
||||
dlsym(library_, "spd_set_voice_rate"));
|
||||
#endif
|
||||
#if defined( \
|
||||
LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED)
|
||||
spd_set_voice_rate = &::spd_set_voice_rate;
|
||||
#endif
|
||||
if (!spd_set_voice_rate) {
|
||||
CleanUp(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN)
|
||||
spd_set_voice_pitch = reinterpret_cast<decltype(this->spd_set_voice_pitch)>(
|
||||
dlsym(library_, "spd_set_voice_pitch"));
|
||||
#endif
|
||||
#if defined( \
|
||||
LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED)
|
||||
spd_set_voice_pitch = &::spd_set_voice_pitch;
|
||||
#endif
|
||||
if (!spd_set_voice_pitch) {
|
||||
CleanUp(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN)
|
||||
spd_list_synthesis_voices =
|
||||
reinterpret_cast<decltype(this->spd_list_synthesis_voices)>(
|
||||
dlsym(library_, "spd_list_synthesis_voices"));
|
||||
#endif
|
||||
#if defined( \
|
||||
LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED)
|
||||
spd_list_synthesis_voices = &::spd_list_synthesis_voices;
|
||||
#endif
|
||||
if (!spd_list_synthesis_voices) {
|
||||
CleanUp(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN)
|
||||
spd_set_synthesis_voice =
|
||||
reinterpret_cast<decltype(this->spd_set_synthesis_voice)>(
|
||||
dlsym(library_, "spd_set_synthesis_voice"));
|
||||
#endif
|
||||
#if defined( \
|
||||
LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED)
|
||||
spd_set_synthesis_voice = &::spd_set_synthesis_voice;
|
||||
#endif
|
||||
if (!spd_set_synthesis_voice) {
|
||||
CleanUp(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN)
|
||||
spd_list_modules = reinterpret_cast<decltype(this->spd_list_modules)>(
|
||||
dlsym(library_, "spd_list_modules"));
|
||||
#endif
|
||||
#if defined( \
|
||||
LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED)
|
||||
spd_list_modules = &::spd_list_modules;
|
||||
#endif
|
||||
if (!spd_list_modules) {
|
||||
CleanUp(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN)
|
||||
spd_set_output_module =
|
||||
reinterpret_cast<decltype(this->spd_set_output_module)>(
|
||||
dlsym(library_, "spd_set_output_module"));
|
||||
#endif
|
||||
#if defined( \
|
||||
LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED)
|
||||
spd_set_output_module = &::spd_set_output_module;
|
||||
#endif
|
||||
if (!spd_set_output_module) {
|
||||
CleanUp(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN)
|
||||
spd_set_language = reinterpret_cast<decltype(this->spd_set_language)>(
|
||||
dlsym(library_, "spd_set_language"));
|
||||
#endif
|
||||
#if defined( \
|
||||
LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED)
|
||||
spd_set_language = &::spd_set_language;
|
||||
#endif
|
||||
if (!spd_set_language) {
|
||||
CleanUp(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
loaded_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void LibSpeechdLoader::CleanUp(bool unload) {
|
||||
#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN)
|
||||
if (unload) {
|
||||
dlclose(library_);
|
||||
library_ = NULL;
|
||||
}
|
||||
#endif
|
||||
loaded_ = false;
|
||||
spd_open = NULL;
|
||||
spd_say = NULL;
|
||||
spd_stop = NULL;
|
||||
spd_close = NULL;
|
||||
spd_pause = NULL;
|
||||
spd_resume = NULL;
|
||||
spd_set_notification_on = NULL;
|
||||
spd_set_voice_rate = NULL;
|
||||
spd_set_voice_pitch = NULL;
|
||||
spd_list_synthesis_voices = NULL;
|
||||
spd_set_synthesis_voice = NULL;
|
||||
spd_list_modules = NULL;
|
||||
spd_set_output_module = NULL;
|
||||
spd_set_language = NULL;
|
||||
}
|
||||
@@ -422,6 +422,52 @@ Emitted when `remote.getGlobal()` is called in the renderer process of `webConte
|
||||
Calling `event.preventDefault()` will prevent the global from being returned.
|
||||
Custom value can be returned by setting `event.returnValue`.
|
||||
|
||||
### Event: 'remote-get-builtin'
|
||||
|
||||
Returns:
|
||||
|
||||
* `event` Event
|
||||
* `webContents` [WebContents](web-contents.md)
|
||||
* `moduleName` String
|
||||
|
||||
Emitted when `remote.getBuiltin()` is called in the renderer process of `webContents`.
|
||||
Calling `event.preventDefault()` will prevent the module from being returned.
|
||||
Custom value can be returned by setting `event.returnValue`.
|
||||
|
||||
### Event: 'remote-get-current-window'
|
||||
|
||||
Returns:
|
||||
|
||||
* `event` Event
|
||||
* `webContents` [WebContents](web-contents.md)
|
||||
|
||||
Emitted when `remote.getCurrentWindow()` is called in the renderer process of `webContents`.
|
||||
Calling `event.preventDefault()` will prevent the object from being returned.
|
||||
Custom value can be returned by setting `event.returnValue`.
|
||||
|
||||
### Event: 'remote-get-current-web-contents'
|
||||
|
||||
Returns:
|
||||
|
||||
* `event` Event
|
||||
* `webContents` [WebContents](web-contents.md)
|
||||
|
||||
Emitted when `remote.getCurrentWebContents()` is called in the renderer process of `webContents`.
|
||||
Calling `event.preventDefault()` will prevent the object from being returned.
|
||||
Custom value can be returned by setting `event.returnValue`.
|
||||
|
||||
### Event: 'remote-get-guest-web-contents'
|
||||
|
||||
Returns:
|
||||
|
||||
* `event` Event
|
||||
* `webContents` [WebContents](web-contents.md)
|
||||
* `guestWebContents` [WebContents](web-contents.md)
|
||||
|
||||
Emitted when `<webview>.getWebContents()` is called in the renderer process of `webContents`.
|
||||
Calling `event.preventDefault()` will prevent the object from being returned.
|
||||
Custom value can be returned by setting `event.returnValue`.
|
||||
|
||||
## Methods
|
||||
|
||||
The `app` object has the following methods:
|
||||
@@ -1215,6 +1261,15 @@ Sets the `image` associated with this dock icon.
|
||||
|
||||
## Properties
|
||||
|
||||
### `app.userAgentFallback`
|
||||
|
||||
A `String` which is the user agent string Electron will use as a global fallback.
|
||||
|
||||
This is the user agent that will be used when no user agent is set at the
|
||||
`webContents` or `session` level. Useful for ensuring your entire
|
||||
app has the same user agent. Set to a custom value as early as possible
|
||||
in your apps initialization to ensure that your overridden value is used.
|
||||
|
||||
### `app.isPackaged`
|
||||
|
||||
A `Boolean` property that returns `true` if the app is packaged, `false` otherwise. For many apps, this property can be used to distinguish development and production environments.
|
||||
|
||||
@@ -79,7 +79,7 @@ with `callback(error, cookies)` on complete.
|
||||
* `url` String - The url to associate the cookie with.
|
||||
* `name` String (optional) - The name of the cookie. Empty by default if omitted.
|
||||
* `value` String (optional) - The value of the cookie. Empty by default if omitted.
|
||||
* `domain` String (optional) - The domain of the cookie. Empty by default if omitted.
|
||||
* `domain` String (optional) - The domain of the cookie; this will be normalized with a preceding dot so that it's also valid for subdomains. Empty by default if omitted.
|
||||
* `path` String (optional) - The path of the cookie. Empty by default if omitted.
|
||||
* `secure` Boolean (optional) - Whether the cookie should be marked as Secure. Defaults to
|
||||
false.
|
||||
|
||||
@@ -95,8 +95,7 @@ them will get reported without `companyName`, `productName` or any of the `extra
|
||||
|
||||
Returns [`CrashReport`](structures/crash-report.md):
|
||||
|
||||
Returns the date and ID of the last crash report. If no crash reports have been
|
||||
sent or the crash reporter has not been started, `null` is returned.
|
||||
Returns the date and ID of the last crash report. Only crash reports that have been uploaded will be returned; even if a crash report is present on disk it will not be returned until it is uploaded. In the case that there are no uploaded reports, `null` is returned.
|
||||
|
||||
### `crashReporter.getUploadedReports()`
|
||||
|
||||
|
||||
@@ -92,3 +92,9 @@ objects, each `DesktopCapturerSource` represents a screen or an individual windo
|
||||
captured.
|
||||
|
||||
[`navigator.mediaDevices.getUserMedia`]: https://developer.mozilla.org/en/docs/Web/API/MediaDevices/getUserMedia
|
||||
|
||||
### Caveats
|
||||
|
||||
`navigator.mediaDevices.getUserMedia` does not work on macOS for audio capture due to a fundamental limitation whereby apps that want to access the system's audio require a [signed kernel extension](https://developer.apple.com/library/archive/documentation/Security/Conceptual/System_Integrity_Protection_Guide/KernelExtensions/KernelExtensions.html). Chromium, and by extension Electron, does not provide this.
|
||||
|
||||
It is possible to circumvent this limitation by capturing system audio with another macOS app like Soundflower and passing it through a virtual audio input device. This virtual device can then be queried with `navigator.mediaDevices.getUserMedia`.
|
||||
@@ -55,7 +55,7 @@ The `dialog` module has the following methods:
|
||||
* `filePaths` String[] - An array of file paths chosen by the user
|
||||
* `bookmarks` String[] _macOS_ _mas_ - An array matching the `filePaths` array of base64 encoded strings which contains security scoped bookmark data. `securityScopedBookmarks` must be enabled for this to be populated.
|
||||
|
||||
Returns `String[]`, an array of file paths chosen by the user,
|
||||
Returns `String[] | undefined`, an array of file paths chosen by the user,
|
||||
if the callback is provided it returns `undefined`.
|
||||
|
||||
The `browserWindow` argument allows the dialog to attach itself to a parent window, making it modal.
|
||||
@@ -106,8 +106,8 @@ shown.
|
||||
* `filename` String
|
||||
* `bookmark` String _macOS_ _mas_ - Base64 encoded string which contains the security scoped bookmark data for the saved file. `securityScopedBookmarks` must be enabled for this to be present.
|
||||
|
||||
Returns `String`, the path of the file chosen by the user,
|
||||
if a callback is provided it returns `undefined`.
|
||||
Returns `String | undefined`, the path of the file chosen by the user,
|
||||
if a callback is provided or the dialog is cancelled it returns `undefined`.
|
||||
|
||||
The `browserWindow` argument allows the dialog to attach itself to a parent window, making it modal.
|
||||
|
||||
|
||||
@@ -253,7 +253,7 @@ const { remote } = require('electron')
|
||||
const { Menu, MenuItem } = remote
|
||||
|
||||
const menu = new Menu()
|
||||
menu.append(new MenuItem({ label: 'MenuItem1', click() { console.log('item 1 clicked') } })))
|
||||
menu.append(new MenuItem({ label: 'MenuItem1', click() { console.log('item 1 clicked') } }))
|
||||
menu.append(new MenuItem({ type: 'separator' }))
|
||||
menu.append(new MenuItem({ label: 'MenuItem2', type: 'checkbox', checked: true }))
|
||||
|
||||
|
||||
@@ -185,6 +185,12 @@ The `hslShift` is applied to the image with the following rules
|
||||
This means that `[-1, 0, 1]` will make the image completely white and
|
||||
`[-1, 1, 0]` will make the image completely black.
|
||||
|
||||
In some cases, the `NSImageName` doesn't match its string representation; one example of this is `NSFolderImageName`, whose string representation would actually be `NSFolder`. Therefore, you'll need to determine the correct string representation for your image before passing it in. This can be done with the following:
|
||||
|
||||
`echo -e '#import <Cocoa/Cocoa.h>\nint main() { NSLog(@"%@", SYSTEM_IMAGE_NAME); }' | clang -otest -x objective-c -framework Cocoa - && ./test`
|
||||
|
||||
where `SYSTEM_IMAGE_NAME` should be replaced with any value from [this list](https://developer.apple.com/documentation/appkit/nsimagename?language=objc).
|
||||
|
||||
## Class: NativeImage
|
||||
|
||||
> Natively wrap images such as tray, dock, and application icons.
|
||||
|
||||
@@ -14,6 +14,7 @@ In sandboxed renderers the `process` object contains only a subset of the APIs:
|
||||
- `crash()`
|
||||
- `hang()`
|
||||
- `getHeapStatistics()`
|
||||
- `getProcessMemoryInfo()`
|
||||
- `getSystemMemoryInfo()`
|
||||
- `getCPUUsage()`
|
||||
- `getIOCounters()`
|
||||
@@ -157,6 +158,27 @@ Returns `Object`:
|
||||
|
||||
Returns an object with V8 heap statistics. Note that all statistics are reported in Kilobytes.
|
||||
|
||||
### `process.getProcessMemoryInfo()`
|
||||
|
||||
Returns `Object`:
|
||||
|
||||
* `residentSet` Integer _Linux_ and _Windows_ - The amount of memory
|
||||
currently pinned to actual physical RAM in Kilobytes.
|
||||
* `private` Integer - The amount of memory not shared by other processes, such as
|
||||
JS heap or HTML content in Kilobytes.
|
||||
* `shared` Integer - The amount of memory shared between processes, typically
|
||||
memory consumed by the Electron code itself in Kilobytes.
|
||||
|
||||
Returns an object giving memory usage statistics about the current process. Note
|
||||
that all statistics are reported in Kilobytes.
|
||||
This api should be called after app ready.
|
||||
|
||||
Chromium does not provide `residentSet` value for macOS. This is because macOS
|
||||
performs in-memory compression of pages that haven't been recently used. As a
|
||||
result the resident set size value is not what one would expect. `private` memory
|
||||
is more representative of the actual pre-compression memory usage of the process
|
||||
on macOS.
|
||||
|
||||
### `process.getSystemMemoryInfo()`
|
||||
|
||||
Returns `Object`:
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
* `name` String - The name of the cookie.
|
||||
* `value` String - The value of the cookie.
|
||||
* `domain` String (optional) - The domain of the cookie.
|
||||
* `hostOnly` Boolean (optional) - Whether the cookie is a host-only cookie.
|
||||
* `domain` String (optional) - The domain of the cookie; this will be normalized with a preceding dot so that it's also valid for subdomains.
|
||||
* `hostOnly` Boolean (optional) - Whether the cookie is a host-only cookie; this will only be `true` if no domain was passed.
|
||||
* `path` String (optional) - The path of the cookie.
|
||||
* `secure` Boolean (optional) - Whether the cookie is marked as secure.
|
||||
* `httpOnly` Boolean (optional) - Whether the cookie is marked as HTTP only.
|
||||
|
||||
@@ -138,11 +138,16 @@ new [`BrowserWindow`](browser-window.md). If you call `event.preventDefault()` a
|
||||
instance, failing to do so may result in unexpected behavior. For example:
|
||||
|
||||
```javascript
|
||||
myBrowserWindow.webContents.on('new-window', (event, url) => {
|
||||
myBrowserWindow.webContents.on('new-window', (event, url, frameName, disposition, options) => {
|
||||
event.preventDefault()
|
||||
const win = new BrowserWindow({ show: false })
|
||||
const win = new BrowserWindow({
|
||||
webContents: options.webContents, // use existing webContents if provided
|
||||
show: false
|
||||
})
|
||||
win.once('ready-to-show', () => win.show())
|
||||
win.loadURL(url)
|
||||
if (!options.webContents) {
|
||||
win.loadURL(url) // existing webContents will be navigated automatically
|
||||
}
|
||||
event.newGuest = win
|
||||
})
|
||||
```
|
||||
@@ -685,6 +690,48 @@ Emitted when `remote.getGlobal()` is called in the renderer process.
|
||||
Calling `event.preventDefault()` will prevent the global from being returned.
|
||||
Custom value can be returned by setting `event.returnValue`.
|
||||
|
||||
#### Event: 'remote-get-builtin'
|
||||
|
||||
Returns:
|
||||
|
||||
* `event` Event
|
||||
* `moduleName` String
|
||||
|
||||
Emitted when `remote.getBuiltin()` is called in the renderer process.
|
||||
Calling `event.preventDefault()` will prevent the module from being returned.
|
||||
Custom value can be returned by setting `event.returnValue`.
|
||||
|
||||
#### Event: 'remote-get-current-window'
|
||||
|
||||
Returns:
|
||||
|
||||
* `event` Event
|
||||
|
||||
Emitted when `remote.getCurrentWindow()` is called in the renderer process.
|
||||
Calling `event.preventDefault()` will prevent the object from being returned.
|
||||
Custom value can be returned by setting `event.returnValue`.
|
||||
|
||||
#### Event: 'remote-get-current-web-contents'
|
||||
|
||||
Returns:
|
||||
|
||||
* `event` Event
|
||||
|
||||
Emitted when `remote.getCurrentWebContents()` is called in the renderer process.
|
||||
Calling `event.preventDefault()` will prevent the object from being returned.
|
||||
Custom value can be returned by setting `event.returnValue`.
|
||||
|
||||
#### Event: 'remote-get-guest-web-contents'
|
||||
|
||||
Returns:
|
||||
|
||||
* `event` Event
|
||||
* `guestWebContents` [WebContents](web-contents.md)
|
||||
|
||||
Emitted when `<webview>.getWebContents()` is called in the renderer process.
|
||||
Calling `event.preventDefault()` will prevent the object from being returned.
|
||||
Custom value can be returned by setting `event.returnValue`.
|
||||
|
||||
### Instance Methods
|
||||
|
||||
#### `contents.loadURL(url[, options])`
|
||||
@@ -1540,6 +1587,10 @@ Takes a V8 heap snapshot and saves it to `filePath`.
|
||||
Controls whether or not this WebContents will throttle animations and timers
|
||||
when the page becomes backgrounded. This also affects the Page Visibility API.
|
||||
|
||||
#### `contents.getType()`
|
||||
|
||||
Returns `String` - the type of the webContent. Can be `backgroundPage`, `window`, `browserView`, `remote`, `webview` or `offscreen`.
|
||||
|
||||
### Instance Properties
|
||||
|
||||
#### `contents.id`
|
||||
|
||||
@@ -32,7 +32,7 @@ const filter = {
|
||||
|
||||
session.defaultSession.webRequest.onBeforeSendHeaders(filter, (details, callback) => {
|
||||
details.requestHeaders['User-Agent'] = 'MyAgent'
|
||||
callback({ cancel: false, requestHeaders: details.requestHeaders })
|
||||
callback({ requestHeaders: details.requestHeaders })
|
||||
})
|
||||
```
|
||||
|
||||
@@ -73,25 +73,24 @@ The `callback` has to be called with an `response` object.
|
||||
* `urls` String[] - Array of URL patterns that will be used to filter out the
|
||||
requests that do not match the URL patterns.
|
||||
* `listener` Function
|
||||
* `details` Object
|
||||
* `id` Integer
|
||||
* `url` String
|
||||
* `method` String
|
||||
* `webContentsId` Integer (optional)
|
||||
* `resourceType` String
|
||||
* `timestamp` Double
|
||||
* `requestHeaders` Object
|
||||
* `callback` Function
|
||||
* `response` Object
|
||||
* `cancel` Boolean (optional)
|
||||
* `requestHeaders` Object (optional) - When provided, request will be made
|
||||
with these headers.
|
||||
|
||||
The `listener` will be called with `listener(details, callback)` before sending
|
||||
an HTTP request, once the request headers are available. This may occur after a
|
||||
TCP connection is made to the server, but before any http data is sent.
|
||||
|
||||
* `details` Object
|
||||
* `id` Integer
|
||||
* `url` String
|
||||
* `method` String
|
||||
* `webContentsId` Integer (optional)
|
||||
* `resourceType` String
|
||||
* `timestamp` Double
|
||||
* `requestHeaders` Object
|
||||
* `callback` Function
|
||||
* `response` Object
|
||||
* `cancel` Boolean (optional)
|
||||
* `requestHeaders` Object (optional) - When provided, request will be made
|
||||
with these headers.
|
||||
|
||||
The `callback` has to be called with an `response` object.
|
||||
|
||||
#### `webRequest.onSendHeaders([filter, ]listener)`
|
||||
@@ -119,29 +118,28 @@ response are visible by the time this listener is fired.
|
||||
* `urls` String[] - Array of URL patterns that will be used to filter out the
|
||||
requests that do not match the URL patterns.
|
||||
* `listener` Function
|
||||
* `details` Object
|
||||
* `id` Integer
|
||||
* `url` String
|
||||
* `method` String
|
||||
* `webContentsId` Integer (optional)
|
||||
* `resourceType` String
|
||||
* `timestamp` Double
|
||||
* `statusLine` String
|
||||
* `statusCode` Integer
|
||||
* `responseHeaders` Object
|
||||
* `callback` Function
|
||||
* `response` Object
|
||||
* `cancel` Boolean (optional)
|
||||
* `responseHeaders` Object (optional) - When provided, the server is assumed
|
||||
to have responded with these headers.
|
||||
* `statusLine` String (optional) - Should be provided when overriding
|
||||
`responseHeaders` to change header status otherwise original response
|
||||
header's status will be used.
|
||||
|
||||
The `listener` will be called with `listener(details, callback)` when HTTP
|
||||
response headers of a request have been received.
|
||||
|
||||
* `details` Object
|
||||
* `id` Integer
|
||||
* `url` String
|
||||
* `method` String
|
||||
* `webContentsId` Integer (optional)
|
||||
* `resourceType` String
|
||||
* `timestamp` Double
|
||||
* `statusLine` String
|
||||
* `statusCode` Integer
|
||||
* `responseHeaders` Object
|
||||
* `callback` Function
|
||||
* `response` Object
|
||||
* `cancel` Boolean
|
||||
* `responseHeaders` Object (optional) - When provided, the server is assumed
|
||||
to have responded with these headers.
|
||||
* `statusLine` String (optional) - Should be provided when overriding
|
||||
`responseHeaders` to change header status otherwise original response
|
||||
header's status will be used.
|
||||
|
||||
The `callback` has to be called with an `response` object.
|
||||
|
||||
#### `webRequest.onResponseStarted([filter, ]listener)`
|
||||
@@ -202,6 +200,7 @@ redirect is about to occur.
|
||||
* `method` String
|
||||
* `webContentsId` Integer (optional)
|
||||
* `resourceType` String
|
||||
* `referrer` String
|
||||
* `timestamp` Double
|
||||
* `responseHeaders` Object
|
||||
* `fromCache` Boolean
|
||||
|
||||
@@ -866,10 +866,6 @@ ipcRenderer.on('ping', () => {
|
||||
|
||||
Fired when the renderer process is crashed.
|
||||
|
||||
### Event: 'gpu-crashed'
|
||||
|
||||
Fired when the gpu process is crashed.
|
||||
|
||||
### Event: 'plugin-crashed'
|
||||
|
||||
Returns:
|
||||
|
||||
@@ -41,6 +41,7 @@ filenames = {
|
||||
"lib/browser/api/web-contents.js",
|
||||
"lib/browser/api/web-contents-view.js",
|
||||
"lib/browser/chrome-extension.js",
|
||||
"lib/browser/crash-reporter-init.js",
|
||||
"lib/browser/guest-view-manager.js",
|
||||
"lib/browser/guest-window-manager.js",
|
||||
"lib/browser/init.js",
|
||||
@@ -559,7 +560,6 @@ filenames = {
|
||||
"atom/renderer/api/atom_api_spell_check_client.cc",
|
||||
"atom/renderer/api/atom_api_spell_check_client.h",
|
||||
"atom/renderer/api/atom_api_web_frame.cc",
|
||||
"atom/renderer/api/atom_api_web_frame.h",
|
||||
"atom/renderer/atom_autofill_agent.cc",
|
||||
"atom/renderer/atom_autofill_agent.h",
|
||||
"atom/renderer/atom_render_frame_observer.cc",
|
||||
@@ -598,31 +598,14 @@ filenames = {
|
||||
"chromium_src/chrome/browser/process_singleton_posix.cc",
|
||||
"chromium_src/chrome/browser/process_singleton_win.cc",
|
||||
"chromium_src/chrome/browser/process_singleton.h",
|
||||
"chromium_src/chrome/browser/speech/tts_controller.h",
|
||||
"chromium_src/chrome/browser/speech/tts_controller_impl.cc",
|
||||
"chromium_src/chrome/browser/speech/tts_controller_impl.h",
|
||||
"chromium_src/chrome/browser/speech/tts_linux.cc",
|
||||
"chromium_src/chrome/browser/speech/tts_mac.mm",
|
||||
"chromium_src/chrome/browser/speech/tts_message_filter.cc",
|
||||
"chromium_src/chrome/browser/speech/tts_message_filter.h",
|
||||
"chromium_src/chrome/browser/speech/tts_platform.cc",
|
||||
"chromium_src/chrome/browser/speech/tts_platform.h",
|
||||
"chromium_src/chrome/browser/speech/tts_win.cc",
|
||||
"chromium_src/chrome/browser/ui/browser_dialogs.h",
|
||||
"chromium_src/chrome/browser/ui/cocoa/color_chooser_mac.mm",
|
||||
"chromium_src/chrome/browser/ui/views/color_chooser_aura.cc",
|
||||
"chromium_src/chrome/browser/ui/views/color_chooser_aura.h",
|
||||
"chromium_src/chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.cc",
|
||||
"chromium_src/chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.h",
|
||||
"chromium_src/chrome/common/tts_messages.h",
|
||||
"chromium_src/chrome/common/tts_utterance_request.cc",
|
||||
"chromium_src/chrome/common/tts_utterance_request.h",
|
||||
"chromium_src/chrome/renderer/tts_dispatcher.cc",
|
||||
"chromium_src/chrome/renderer/tts_dispatcher.h",
|
||||
"chromium_src/chrome/renderer/spellchecker/spellcheck_worditerator.cc",
|
||||
"chromium_src/chrome/renderer/spellchecker/spellcheck_worditerator.h",
|
||||
"chromium_src/library_loaders/libspeechd_loader.cc",
|
||||
"chromium_src/library_loaders/libspeechd.h",
|
||||
]
|
||||
|
||||
lib_sources_nss = [
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
const electron = require('electron')
|
||||
const { WebContentsView, TopLevelWindow } = electron
|
||||
const { BrowserWindow } = process.atomBinding('window')
|
||||
const v8Util = process.atomBinding('v8_util')
|
||||
const ipcMain = require('@electron/internal/browser/ipc-main-internal')
|
||||
|
||||
Object.setPrototypeOf(BrowserWindow.prototype, TopLevelWindow.prototype)
|
||||
|
||||
@@ -27,69 +25,6 @@ BrowserWindow.prototype._init = function () {
|
||||
nativeSetBounds.call(this, bounds, ...opts)
|
||||
}
|
||||
|
||||
// Make new windows requested by links behave like "window.open"
|
||||
this.webContents.on('-new-window', (event, url, frameName, disposition,
|
||||
additionalFeatures, postData,
|
||||
referrer) => {
|
||||
const options = {
|
||||
show: true,
|
||||
width: 800,
|
||||
height: 600
|
||||
}
|
||||
ipcMain.emit('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN',
|
||||
event, url, referrer, frameName, disposition,
|
||||
options, additionalFeatures, postData)
|
||||
})
|
||||
|
||||
this.webContents.on('-web-contents-created', (event, webContents, url,
|
||||
frameName) => {
|
||||
v8Util.setHiddenValue(webContents, 'url-framename', { url, frameName })
|
||||
})
|
||||
|
||||
// Create a new browser window for the native implementation of
|
||||
// "window.open", used in sandbox and nativeWindowOpen mode
|
||||
this.webContents.on('-add-new-contents', (event, webContents, disposition,
|
||||
userGesture, left, top, width,
|
||||
height) => {
|
||||
const urlFrameName = v8Util.getHiddenValue(webContents, 'url-framename')
|
||||
if ((disposition !== 'foreground-tab' && disposition !== 'new-window' &&
|
||||
disposition !== 'background-tab') || !urlFrameName) {
|
||||
event.preventDefault()
|
||||
return
|
||||
}
|
||||
|
||||
if (webContents.getLastWebPreferences().nodeIntegration === true) {
|
||||
const message =
|
||||
'Enabling Node.js integration in child windows opened with the ' +
|
||||
'"nativeWindowOpen" option will cause memory leaks, please turn off ' +
|
||||
'the "nodeIntegration" option.\\n' +
|
||||
'From 5.x child windows opened with the "nativeWindowOpen" option ' +
|
||||
'will always have Node.js integration disabled.\\n' +
|
||||
'See https://github.com/electron/electron/pull/15076 for more.'
|
||||
// console is only available after DOM is created.
|
||||
const printWarning = () => this.webContents.executeJavaScript(`console.warn('${message}')`)
|
||||
if (this.webContents.isDomReady()) {
|
||||
printWarning()
|
||||
} else {
|
||||
this.webContents.once('dom-ready', printWarning)
|
||||
}
|
||||
}
|
||||
|
||||
const { url, frameName } = urlFrameName
|
||||
v8Util.deleteHiddenValue(webContents, 'url-framename')
|
||||
const options = {
|
||||
show: true,
|
||||
x: left,
|
||||
y: top,
|
||||
width: width || 800,
|
||||
height: height || 600,
|
||||
webContents: webContents
|
||||
}
|
||||
const referrer = { url: '', policy: 'default' }
|
||||
ipcMain.emit('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN',
|
||||
event, url, referrer, frameName, disposition, options)
|
||||
})
|
||||
|
||||
// window.resizeTo(...)
|
||||
// window.moveTo(...)
|
||||
this.webContents.on('move', (event, size) => {
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
'use strict'
|
||||
|
||||
const CrashReporter = require('@electron/internal/common/crash-reporter')
|
||||
const ipcMain = require('@electron/internal/browser/ipc-main-internal')
|
||||
const { crashReporterInit } = require('@electron/internal/browser/crash-reporter-init')
|
||||
|
||||
class CrashReporterMain extends CrashReporter {
|
||||
sendSync (channel, ...args) {
|
||||
const event = {}
|
||||
ipcMain.emit(channel, event, ...args)
|
||||
return event.returnValue
|
||||
init (options) {
|
||||
return crashReporterInit(options)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -263,7 +263,8 @@ module.exports = {
|
||||
|
||||
// Choose a default button to get selected when dialog is cancelled.
|
||||
if (cancelId == null) {
|
||||
cancelId = 0
|
||||
// If the defaultId is set to 0, ensure the cancel button is a different index (1)
|
||||
cancelId = (defaultId === 0 && buttons.length > 1) ? 1 : 0
|
||||
for (let i = 0; i < buttons.length; i++) {
|
||||
const text = buttons[i].toLowerCase()
|
||||
if (text === 'cancel' || text === 'no') {
|
||||
|
||||
@@ -109,6 +109,12 @@ Menu.prototype.insert = function (pos, item) {
|
||||
throw new TypeError('Invalid item')
|
||||
}
|
||||
|
||||
if (pos < 0) {
|
||||
throw new RangeError(`Position ${pos} cannot be less than 0`)
|
||||
} else if (pos > this.getItemCount()) {
|
||||
throw new RangeError(`Position ${pos} cannot be greater than the total MenuItem count`)
|
||||
}
|
||||
|
||||
// insert item depending on its type
|
||||
insertItemByType.call(this, item, pos)
|
||||
|
||||
|
||||
@@ -10,8 +10,12 @@ Object.setPrototypeOf(module.exports, new Proxy({}, {
|
||||
if (!app.isReady()) return
|
||||
|
||||
const netLog = session.defaultSession.netLog
|
||||
|
||||
if (!Object.getPrototypeOf(netLog).hasOwnProperty(property)) return
|
||||
|
||||
// check for properties on the prototype chain that aren't functions
|
||||
if (typeof netLog[property] !== 'function') return netLog[property]
|
||||
|
||||
// Returning a native function directly would throw error.
|
||||
return (...args) => netLog[property](...args)
|
||||
},
|
||||
|
||||
@@ -5,6 +5,7 @@ const { EventEmitter } = require('events')
|
||||
const electron = require('electron')
|
||||
const path = require('path')
|
||||
const url = require('url')
|
||||
const v8Util = process.atomBinding('v8_util')
|
||||
const { app, ipcMain, session, NavigationController, deprecate } = electron
|
||||
|
||||
const ipcMainInternal = require('@electron/internal/browser/ipc-main-internal')
|
||||
@@ -199,7 +200,7 @@ WebContents.prototype.executeJavaScript = function (code, hasUserGesture, callba
|
||||
WebContents.prototype.takeHeapSnapshot = function (filePath) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const channel = `ELECTRON_TAKE_HEAP_SNAPSHOT_RESULT_${getNextId()}`
|
||||
ipcMain.once(channel, (event, success) => {
|
||||
ipcMainInternal.once(channel, (event, success) => {
|
||||
if (success) {
|
||||
resolve()
|
||||
} else {
|
||||
@@ -207,7 +208,7 @@ WebContents.prototype.takeHeapSnapshot = function (filePath) {
|
||||
}
|
||||
})
|
||||
if (!this._takeHeapSnapshot(filePath, channel)) {
|
||||
ipcMain.emit(channel, false)
|
||||
ipcMainInternal.emit(channel, false)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -262,7 +263,7 @@ WebContents.prototype.printToPDF = function (options, callback) {
|
||||
|
||||
WebContents.prototype.print = function (...args) {
|
||||
if (features.isPrintingEnabled()) {
|
||||
this._print(args)
|
||||
this._print(...args)
|
||||
} else {
|
||||
console.error('Error: Printing feature is disabled.')
|
||||
}
|
||||
@@ -320,6 +321,17 @@ WebContents.prototype.findInPage = function (text, options = {}) {
|
||||
return this._findInPage(text, options)
|
||||
}
|
||||
|
||||
const safeProtocols = new Set([
|
||||
'chrome-devtools:',
|
||||
'chrome-extension:'
|
||||
])
|
||||
|
||||
const isWebContentsTrusted = function (contents) {
|
||||
const pageURL = contents._getURL()
|
||||
const { protocol } = url.parse(pageURL)
|
||||
return safeProtocols.has(protocol)
|
||||
}
|
||||
|
||||
// Add JavaScript wrappers for WebContents class.
|
||||
WebContents.prototype._init = function () {
|
||||
// The navigation controller.
|
||||
@@ -368,13 +380,22 @@ WebContents.prototype._init = function () {
|
||||
})
|
||||
})
|
||||
|
||||
this.on('remote-require', (event, ...args) => {
|
||||
app.emit('remote-require', event, this, ...args)
|
||||
})
|
||||
const forwardedEvents = [
|
||||
'remote-require',
|
||||
'remote-get-global',
|
||||
'remote-get-builtin',
|
||||
'remote-get-current-window',
|
||||
'remote-get-current-web-contents',
|
||||
'remote-get-guest-web-contents'
|
||||
]
|
||||
|
||||
this.on('remote-get-global', (event, ...args) => {
|
||||
app.emit('remote-get-global', event, this, ...args)
|
||||
})
|
||||
for (const eventName of forwardedEvents) {
|
||||
this.on(eventName, (event, ...args) => {
|
||||
if (!isWebContentsTrusted(event.sender)) {
|
||||
app.emit(eventName, event, this, ...args)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
deprecate.event(this, 'did-get-response-details', '-did-get-response-details')
|
||||
deprecate.event(this, 'did-get-redirect-request', '-did-get-redirect-request')
|
||||
@@ -384,6 +405,72 @@ WebContents.prototype._init = function () {
|
||||
this.reload()
|
||||
})
|
||||
|
||||
// Handle window.open for BrowserWindow and BrowserView.
|
||||
if (['browserView', 'window'].includes(this.getType())) {
|
||||
// Make new windows requested by links behave like "window.open"
|
||||
this.webContents.on('-new-window', (event, url, frameName, disposition,
|
||||
additionalFeatures, postData,
|
||||
referrer) => {
|
||||
const options = {
|
||||
show: true,
|
||||
width: 800,
|
||||
height: 600
|
||||
}
|
||||
ipcMainInternal.emit('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN',
|
||||
event, url, referrer, frameName, disposition,
|
||||
options, additionalFeatures, postData)
|
||||
})
|
||||
|
||||
this.webContents.on('-web-contents-created', (event, webContents, url,
|
||||
frameName) => {
|
||||
v8Util.setHiddenValue(webContents, 'url-framename', { url, frameName })
|
||||
})
|
||||
|
||||
// Create a new browser window for the native implementation of
|
||||
// "window.open", used in sandbox and nativeWindowOpen mode
|
||||
this.webContents.on('-add-new-contents', (event, webContents, disposition,
|
||||
userGesture, left, top, width,
|
||||
height) => {
|
||||
const urlFrameName = v8Util.getHiddenValue(webContents, 'url-framename')
|
||||
if ((disposition !== 'foreground-tab' && disposition !== 'new-window' &&
|
||||
disposition !== 'background-tab') || !urlFrameName) {
|
||||
event.preventDefault()
|
||||
return
|
||||
}
|
||||
|
||||
if (webContents.getLastWebPreferences().nodeIntegration === true) {
|
||||
const message =
|
||||
'Enabling Node.js integration in child windows opened with the ' +
|
||||
'"nativeWindowOpen" option will cause memory leaks, please turn off ' +
|
||||
'the "nodeIntegration" option.\\n' +
|
||||
'From 5.x child windows opened with the "nativeWindowOpen" option ' +
|
||||
'will always have Node.js integration disabled.\\n' +
|
||||
'See https://github.com/electron/electron/pull/15076 for more.'
|
||||
// console is only available after DOM is created.
|
||||
const printWarning = () => this.webContents.executeJavaScript(`console.warn('${message}')`)
|
||||
if (this.webContents.isDomReady()) {
|
||||
printWarning()
|
||||
} else {
|
||||
this.webContents.once('dom-ready', printWarning)
|
||||
}
|
||||
}
|
||||
|
||||
const { url, frameName } = urlFrameName
|
||||
v8Util.deleteHiddenValue(webContents, 'url-framename')
|
||||
const options = {
|
||||
show: true,
|
||||
x: left,
|
||||
y: top,
|
||||
width: width || 800,
|
||||
height: height || 600,
|
||||
webContents
|
||||
}
|
||||
const referrer = { url: '', policy: 'default' }
|
||||
ipcMainInternal.emit('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN',
|
||||
event, url, referrer, frameName, disposition, options)
|
||||
})
|
||||
}
|
||||
|
||||
app.emit('web-contents-created', {}, this)
|
||||
}
|
||||
|
||||
|
||||
46
lib/browser/crash-reporter-init.js
Normal file
46
lib/browser/crash-reporter-init.js
Normal file
@@ -0,0 +1,46 @@
|
||||
'use strict'
|
||||
|
||||
const { app } = require('electron')
|
||||
const cp = require('child_process')
|
||||
const os = require('os')
|
||||
const path = require('path')
|
||||
|
||||
const getTempDirectory = function () {
|
||||
try {
|
||||
return app.getPath('temp')
|
||||
} catch (error) {
|
||||
return os.tmpdir()
|
||||
}
|
||||
}
|
||||
|
||||
exports.crashReporterInit = function (options) {
|
||||
const productName = options.productName || app.getName()
|
||||
const crashesDirectory = path.join(getTempDirectory(), `${productName} Crashes`)
|
||||
let crashServicePid
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
const env = {
|
||||
ELECTRON_INTERNAL_CRASH_SERVICE: 1
|
||||
}
|
||||
const args = [
|
||||
'--reporter-url=' + options.submitURL,
|
||||
'--application-name=' + productName,
|
||||
'--crashes-directory=' + crashesDirectory,
|
||||
'--v=1'
|
||||
]
|
||||
|
||||
const crashServiceProcess = cp.spawn(process.helperExecPath, args, {
|
||||
env,
|
||||
detached: true
|
||||
})
|
||||
|
||||
crashServicePid = crashServiceProcess.pid
|
||||
}
|
||||
|
||||
return {
|
||||
productName,
|
||||
crashesDirectory,
|
||||
crashServicePid,
|
||||
appVersion: app.getVersion()
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,11 @@ const { webContents } = require('electron')
|
||||
const ipcMain = require('@electron/internal/browser/ipc-main-internal')
|
||||
const parseFeaturesString = require('@electron/internal/common/parse-features-string')
|
||||
const errorUtils = require('@electron/internal/common/error-utils')
|
||||
const { syncMethods, asyncMethods } = require('@electron/internal/common/web-view-methods')
|
||||
const {
|
||||
syncMethods,
|
||||
asyncCallbackMethods,
|
||||
asyncPromiseMethods
|
||||
} = require('@electron/internal/common/web-view-methods')
|
||||
|
||||
// Doesn't exist in early initialization.
|
||||
let webViewManager = null
|
||||
@@ -32,7 +36,6 @@ const supportedWebViewEvents = [
|
||||
'focus-change',
|
||||
'close',
|
||||
'crashed',
|
||||
'gpu-crashed',
|
||||
'plugin-crashed',
|
||||
'destroyed',
|
||||
'page-title-updated',
|
||||
@@ -391,7 +394,7 @@ ipcMain.on('ELECTRON_GUEST_VIEW_MANAGER_FOCUS_CHANGE', function (event, focus, g
|
||||
handleMessage('ELECTRON_GUEST_VIEW_MANAGER_ASYNC_CALL', function (event, requestId, guestInstanceId, method, args, hasCallback) {
|
||||
new Promise(resolve => {
|
||||
const guest = getGuestForWebContents(guestInstanceId, event.sender)
|
||||
if (!asyncMethods.has(method)) {
|
||||
if (!asyncCallbackMethods.has(method) && !asyncPromiseMethods.has(method)) {
|
||||
throw new Error(`Invalid method: ${method}`)
|
||||
}
|
||||
if (hasCallback) {
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
'use strict'
|
||||
|
||||
const { spawn } = require('child_process')
|
||||
const electron = require('electron')
|
||||
const { EventEmitter } = require('events')
|
||||
const fs = require('fs')
|
||||
const os = require('os')
|
||||
const path = require('path')
|
||||
const v8Util = process.atomBinding('v8_util')
|
||||
const eventBinding = process.atomBinding('event')
|
||||
|
||||
const { isPromise } = electron
|
||||
|
||||
const { crashReporterInit } = require('@electron/internal/browser/crash-reporter-init')
|
||||
const ipcMain = require('@electron/internal/browser/ipc-main-internal')
|
||||
const objectsRegistry = require('@electron/internal/browser/objects-registry')
|
||||
const guestViewManager = require('@electron/internal/browser/guest-view-manager')
|
||||
@@ -296,42 +294,75 @@ handleRemoteCommand('ELECTRON_BROWSER_REQUIRE', function (event, contextId, modu
|
||||
const customEvent = eventBinding.createWithSender(event.sender)
|
||||
event.sender.emit('remote-require', customEvent, moduleName)
|
||||
|
||||
if (customEvent.defaultPrevented) {
|
||||
if (typeof customEvent.returnValue === 'undefined') {
|
||||
throw new Error(`Invalid module: ${moduleName}`)
|
||||
if (customEvent.returnValue === undefined) {
|
||||
if (customEvent.defaultPrevented) {
|
||||
throw new Error(`Blocked remote.require('${moduleName}')`)
|
||||
} else {
|
||||
customEvent.returnValue = process.mainModule.require(moduleName)
|
||||
}
|
||||
} else {
|
||||
customEvent.returnValue = process.mainModule.require(moduleName)
|
||||
}
|
||||
|
||||
return valueToMeta(event.sender, contextId, customEvent.returnValue)
|
||||
})
|
||||
|
||||
handleRemoteCommand('ELECTRON_BROWSER_GET_BUILTIN', function (event, contextId, module) {
|
||||
return valueToMeta(event.sender, contextId, electron[module])
|
||||
handleRemoteCommand('ELECTRON_BROWSER_GET_BUILTIN', function (event, contextId, moduleName) {
|
||||
const customEvent = eventBinding.createWithSender(event.sender)
|
||||
event.sender.emit('remote-get-builtin', customEvent, moduleName)
|
||||
|
||||
if (customEvent.returnValue === undefined) {
|
||||
if (customEvent.defaultPrevented) {
|
||||
throw new Error(`Blocked remote.getBuiltin('${moduleName}')`)
|
||||
} else {
|
||||
customEvent.returnValue = electron[moduleName]
|
||||
}
|
||||
}
|
||||
|
||||
return valueToMeta(event.sender, contextId, customEvent.returnValue)
|
||||
})
|
||||
|
||||
handleRemoteCommand('ELECTRON_BROWSER_GLOBAL', function (event, contextId, globalName) {
|
||||
const customEvent = eventBinding.createWithSender(event.sender)
|
||||
event.sender.emit('remote-get-global', customEvent, globalName)
|
||||
|
||||
if (customEvent.defaultPrevented) {
|
||||
if (typeof customEvent.returnValue === 'undefined') {
|
||||
throw new Error(`Invalid global: ${globalName}`)
|
||||
if (customEvent.returnValue === undefined) {
|
||||
if (customEvent.defaultPrevented) {
|
||||
throw new Error(`Blocked remote.getGlobal('${globalName}')`)
|
||||
} else {
|
||||
customEvent.returnValue = global[globalName]
|
||||
}
|
||||
} else {
|
||||
customEvent.returnValue = global[globalName]
|
||||
}
|
||||
|
||||
return valueToMeta(event.sender, contextId, customEvent.returnValue)
|
||||
})
|
||||
|
||||
handleRemoteCommand('ELECTRON_BROWSER_CURRENT_WINDOW', function (event, contextId) {
|
||||
return valueToMeta(event.sender, contextId, event.sender.getOwnerBrowserWindow())
|
||||
const customEvent = eventBinding.createWithSender(event.sender)
|
||||
event.sender.emit('remote-get-current-window', customEvent)
|
||||
|
||||
if (customEvent.returnValue === undefined) {
|
||||
if (customEvent.defaultPrevented) {
|
||||
throw new Error('Blocked remote.getCurrentWindow()')
|
||||
} else {
|
||||
customEvent.returnValue = event.sender.getOwnerBrowserWindow()
|
||||
}
|
||||
}
|
||||
|
||||
return valueToMeta(event.sender, contextId, customEvent.returnValue)
|
||||
})
|
||||
|
||||
handleRemoteCommand('ELECTRON_BROWSER_CURRENT_WEB_CONTENTS', function (event, contextId) {
|
||||
return valueToMeta(event.sender, contextId, event.sender)
|
||||
const customEvent = eventBinding.createWithSender(event.sender)
|
||||
event.sender.emit('remote-get-current-web-contents', customEvent)
|
||||
|
||||
if (customEvent.returnValue === undefined) {
|
||||
if (customEvent.defaultPrevented) {
|
||||
throw new Error('Blocked remote.getCurrentWebContents()')
|
||||
} else {
|
||||
customEvent.returnValue = event.sender
|
||||
}
|
||||
}
|
||||
|
||||
return valueToMeta(event.sender, contextId, customEvent.returnValue)
|
||||
})
|
||||
|
||||
handleRemoteCommand('ELECTRON_BROWSER_CONSTRUCTOR', function (event, contextId, id, args) {
|
||||
@@ -411,7 +442,19 @@ handleRemoteCommand('ELECTRON_BROWSER_CONTEXT_RELEASE', (event, contextId) => {
|
||||
|
||||
handleRemoteCommand('ELECTRON_BROWSER_GUEST_WEB_CONTENTS', function (event, contextId, guestInstanceId) {
|
||||
const guest = guestViewManager.getGuestForWebContents(guestInstanceId, event.sender)
|
||||
return valueToMeta(event.sender, contextId, guest)
|
||||
|
||||
const customEvent = eventBinding.createWithSender(event.sender)
|
||||
event.sender.emit('remote-get-guest-web-contents', customEvent, guest)
|
||||
|
||||
if (customEvent.returnValue === undefined) {
|
||||
if (customEvent.defaultPrevented) {
|
||||
throw new Error(`Blocked remote.getGuestForWebContents()`)
|
||||
} else {
|
||||
customEvent.returnValue = guest
|
||||
}
|
||||
}
|
||||
|
||||
return valueToMeta(event.sender, contextId, customEvent.returnValue)
|
||||
})
|
||||
|
||||
// Implements window.close()
|
||||
@@ -423,46 +466,6 @@ ipcMain.on('ELECTRON_BROWSER_WINDOW_CLOSE', function (event) {
|
||||
event.returnValue = null
|
||||
})
|
||||
|
||||
const getTempDirectory = function () {
|
||||
try {
|
||||
return electron.app.getPath('temp')
|
||||
} catch (error) {
|
||||
return os.tmpdir()
|
||||
}
|
||||
}
|
||||
|
||||
const crashReporterInit = function (options) {
|
||||
const productName = options.productName || electron.app.getName()
|
||||
const crashesDirectory = path.join(getTempDirectory(), `${productName} Crashes`)
|
||||
let crashServicePid
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
const env = {
|
||||
ELECTRON_INTERNAL_CRASH_SERVICE: 1
|
||||
}
|
||||
const args = [
|
||||
'--reporter-url=' + options.submitURL,
|
||||
'--application-name=' + productName,
|
||||
'--crashes-directory=' + crashesDirectory,
|
||||
'--v=1'
|
||||
]
|
||||
|
||||
const crashServiceProcess = spawn(process.helperExecPath, args, {
|
||||
env,
|
||||
detached: true
|
||||
})
|
||||
|
||||
crashServicePid = crashServiceProcess.pid
|
||||
}
|
||||
|
||||
return {
|
||||
productName,
|
||||
crashesDirectory,
|
||||
crashServicePid,
|
||||
appVersion: electron.app.getVersion()
|
||||
}
|
||||
}
|
||||
|
||||
const setReturnValue = function (event, getValue) {
|
||||
try {
|
||||
event.returnValue = [null, getValue()]
|
||||
@@ -487,20 +490,27 @@ ipcMain.on('ELECTRON_BROWSER_CLIPBOARD_WRITE_FIND_TEXT', function (event, text)
|
||||
setReturnValue(event, () => electron.clipboard.writeFindText(text))
|
||||
})
|
||||
|
||||
ipcMain.on('ELECTRON_BROWSER_SANDBOX_LOAD', function (event) {
|
||||
const preloadPath = event.sender._getPreloadPath()
|
||||
const getPreloadScript = function (preloadPath) {
|
||||
let preloadSrc = null
|
||||
let preloadError = null
|
||||
if (preloadPath) {
|
||||
try {
|
||||
preloadSrc = fs.readFileSync(preloadPath).toString()
|
||||
} catch (err) {
|
||||
preloadError = { stack: err ? err.stack : (new Error(`Failed to load "${preloadPath}"`)).stack }
|
||||
preloadError = errorUtils.serialize(err)
|
||||
}
|
||||
}
|
||||
return { preloadPath, preloadSrc, preloadError }
|
||||
}
|
||||
|
||||
ipcMain.on('ELECTRON_BROWSER_SANDBOX_LOAD', function (event) {
|
||||
const preloadPaths = [
|
||||
...(event.sender.session ? event.sender.session.getPreloads() : []),
|
||||
event.sender._getPreloadPath()
|
||||
]
|
||||
|
||||
event.returnValue = {
|
||||
preloadSrc,
|
||||
preloadError,
|
||||
preloadScripts: preloadPaths.map(path => getPreloadScript(path)),
|
||||
isRemoteModuleEnabled: event.sender._isRemoteModuleEnabled(),
|
||||
process: {
|
||||
arch: process.arch,
|
||||
|
||||
@@ -2,28 +2,16 @@
|
||||
|
||||
const binding = process.atomBinding('crash_reporter')
|
||||
|
||||
const errorUtils = require('@electron/internal/common/error-utils')
|
||||
|
||||
class CrashReporter {
|
||||
contructor () {
|
||||
this.productName = null
|
||||
this.crashesDirectory = null
|
||||
}
|
||||
|
||||
sendSync (channel, ...args) {
|
||||
init (options) {
|
||||
throw new Error('Not implemented')
|
||||
}
|
||||
|
||||
invoke (command, ...args) {
|
||||
const [ error, result ] = this.sendSync(command, ...args)
|
||||
|
||||
if (error) {
|
||||
throw errorUtils.deserialize(error)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
start (options) {
|
||||
if (options == null) options = {}
|
||||
|
||||
@@ -51,7 +39,7 @@ class CrashReporter {
|
||||
throw new Error('submitURL is a required option to crashReporter.start')
|
||||
}
|
||||
|
||||
const ret = this.invoke('ELECTRON_CRASH_REPORTER_INIT', {
|
||||
const ret = this.init({
|
||||
submitURL,
|
||||
productName
|
||||
})
|
||||
|
||||
@@ -50,18 +50,20 @@ exports.syncMethods = new Set([
|
||||
'setZoomLevel'
|
||||
])
|
||||
|
||||
exports.asyncMethods = new Set([
|
||||
exports.asyncCallbackMethods = new Set([
|
||||
'insertCSS',
|
||||
'insertText',
|
||||
'send',
|
||||
'sendInputEvent',
|
||||
'setLayoutZoomLevelLimits',
|
||||
'setVisualZoomLevelLimits',
|
||||
// with callback
|
||||
'capturePage',
|
||||
'executeJavaScript',
|
||||
'getZoomFactor',
|
||||
'getZoomLevel',
|
||||
'print',
|
||||
'printToPDF'
|
||||
])
|
||||
|
||||
exports.asyncPromiseMethods = new Set([
|
||||
'capturePage',
|
||||
'executeJavaScript'
|
||||
])
|
||||
|
||||
@@ -2,10 +2,21 @@
|
||||
|
||||
const CrashReporter = require('@electron/internal/common/crash-reporter')
|
||||
const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal')
|
||||
const errorUtils = require('@electron/internal/common/error-utils')
|
||||
|
||||
const invoke = function (command, ...args) {
|
||||
const [ error, result ] = ipcRenderer.sendSync(command, ...args)
|
||||
|
||||
if (error) {
|
||||
throw errorUtils.deserialize(error)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
class CrashReporterRenderer extends CrashReporter {
|
||||
sendSync (channel, ...args) {
|
||||
return ipcRenderer.sendSync(channel, ...args)
|
||||
init (options) {
|
||||
return invoke('ELECTRON_CRASH_REPORTER_INIT', options)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,67 @@
|
||||
'use strict'
|
||||
|
||||
const { EventEmitter } = require('events')
|
||||
const { webFrame, WebFrame } = process.atomBinding('web_frame')
|
||||
const binding = process.atomBinding('web_frame')
|
||||
|
||||
// WebFrame is an EventEmitter.
|
||||
Object.setPrototypeOf(WebFrame.prototype, EventEmitter.prototype)
|
||||
EventEmitter.call(webFrame)
|
||||
class WebFrame extends EventEmitter {
|
||||
constructor (context) {
|
||||
super()
|
||||
|
||||
// Lots of webview would subscribe to webFrame's events.
|
||||
webFrame.setMaxListeners(0)
|
||||
this.context = context
|
||||
// Lots of webview would subscribe to webFrame's events.
|
||||
this.setMaxListeners(0)
|
||||
}
|
||||
|
||||
module.exports = webFrame
|
||||
findFrameByRoutingId (...args) {
|
||||
return getWebFrame(binding._findFrameByRoutingId(this.context, ...args))
|
||||
}
|
||||
|
||||
getFrameForSelector (...args) {
|
||||
return getWebFrame(binding._getFrameForSelector(this.context, ...args))
|
||||
}
|
||||
|
||||
findFrameByName (...args) {
|
||||
return getWebFrame(binding._findFrameByName(this.context, ...args))
|
||||
}
|
||||
|
||||
get opener () {
|
||||
return getWebFrame(binding._getOpener(this.context))
|
||||
}
|
||||
|
||||
get parent () {
|
||||
return getWebFrame(binding._getParent(this.context))
|
||||
}
|
||||
|
||||
get top () {
|
||||
return getWebFrame(binding._getTop(this.context))
|
||||
}
|
||||
|
||||
get firstChild () {
|
||||
return getWebFrame(binding._getFirstChild(this.context))
|
||||
}
|
||||
|
||||
get nextSibling () {
|
||||
return getWebFrame(binding._getNextSibling(this.context))
|
||||
}
|
||||
|
||||
get routingId () {
|
||||
return binding._getRoutingId(this.context)
|
||||
}
|
||||
}
|
||||
|
||||
// Populate the methods.
|
||||
for (const name in binding) {
|
||||
if (!name.startsWith('_')) { // some methods are manully populated above
|
||||
WebFrame.prototype[name] = function (...args) {
|
||||
return binding[name](this.context, ...args)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to return WebFrame or null depending on context.
|
||||
// TODO(zcbenz): Consider returning same WebFrame for the same context.
|
||||
function getWebFrame (context) {
|
||||
return context ? new WebFrame(context) : null
|
||||
}
|
||||
|
||||
module.exports = new WebFrame(window)
|
||||
|
||||
@@ -4,8 +4,6 @@ const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal')
|
||||
const Event = require('@electron/internal/renderer/extensions/event')
|
||||
const url = require('url')
|
||||
|
||||
let nextId = 0
|
||||
|
||||
class Tab {
|
||||
constructor (tabId) {
|
||||
this.id = tabId
|
||||
@@ -146,14 +144,12 @@ exports.injectTo = function (extensionId, isBackgroundPage, context) {
|
||||
}
|
||||
|
||||
chrome.tabs = {
|
||||
executeScript (tabId, details, callback) {
|
||||
const requestId = ++nextId
|
||||
ipcRenderer.once(`CHROME_TABS_EXECUTESCRIPT_RESULT_${requestId}`, (event, result) => {
|
||||
// Disabled due to false positive in StandardJS
|
||||
// eslint-disable-next-line standard/no-callback-literal
|
||||
callback([event.result])
|
||||
})
|
||||
ipcRenderer.send('CHROME_TABS_EXECUTESCRIPT', requestId, tabId, extensionId, details)
|
||||
executeScript (tabId, details, resultCallback) {
|
||||
if (resultCallback) {
|
||||
ipcRenderer.once(`CHROME_TABS_EXECUTESCRIPT_RESULT_${originResultID}`, (event, result) => resultCallback([result]))
|
||||
}
|
||||
ipcRenderer.send('CHROME_TABS_EXECUTESCRIPT', originResultID, tabId, extensionId, details)
|
||||
originResultID++
|
||||
},
|
||||
|
||||
sendMessage (tabId, message, options, responseCallback) {
|
||||
|
||||
@@ -28,7 +28,6 @@ const WEB_VIEW_EVENTS = {
|
||||
'focus-change': ['focus', 'guestInstanceId'],
|
||||
'close': [],
|
||||
'crashed': [],
|
||||
'gpu-crashed': [],
|
||||
'plugin-crashed': ['name', 'version'],
|
||||
'destroyed': [],
|
||||
'page-title-updated': ['title', 'explicitSet'],
|
||||
|
||||
@@ -7,7 +7,11 @@ const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal')
|
||||
const guestViewInternal = require('@electron/internal/renderer/web-view/guest-view-internal')
|
||||
const webViewConstants = require('@electron/internal/renderer/web-view/web-view-constants')
|
||||
const errorUtils = require('@electron/internal/common/error-utils')
|
||||
const { syncMethods, asyncMethods } = require('@electron/internal/common/web-view-methods')
|
||||
const {
|
||||
syncMethods,
|
||||
asyncCallbackMethods,
|
||||
asyncPromiseMethods
|
||||
} = require('@electron/internal/common/web-view-methods')
|
||||
|
||||
// ID generator.
|
||||
let nextId = 0
|
||||
@@ -268,10 +272,36 @@ const registerWebViewElement = (window) => {
|
||||
ipcRenderer.send('ELECTRON_GUEST_VIEW_MANAGER_ASYNC_CALL', requestId, getGuestInstanceId(this), method, args, callback != null)
|
||||
}
|
||||
}
|
||||
for (const method of asyncMethods) {
|
||||
|
||||
for (const method of asyncCallbackMethods) {
|
||||
proto[method] = createNonBlockHandler(method)
|
||||
}
|
||||
|
||||
const createPromiseHandler = function (method) {
|
||||
return function (...args) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const callback = (typeof args[args.length - 1] === 'function') ? args.pop() : null
|
||||
const requestId = getNextId()
|
||||
|
||||
ipcRenderer.once(`ELECTRON_GUEST_VIEW_MANAGER_ASYNC_CALL_RESPONSE_${requestId}`, function (event, error, result) {
|
||||
if (error == null) {
|
||||
if (callback) {
|
||||
callback(result)
|
||||
}
|
||||
resolve(result)
|
||||
} else {
|
||||
reject(errorUtils.deserialize(error))
|
||||
}
|
||||
})
|
||||
ipcRenderer.send('ELECTRON_GUEST_VIEW_MANAGER_ASYNC_CALL', requestId, getGuestInstanceId(this), method, args, callback != null)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for (const method of asyncPromiseMethods) {
|
||||
proto[method] = createPromiseHandler(method)
|
||||
}
|
||||
|
||||
// WebContents associated with this webview.
|
||||
proto.getWebContents = function () {
|
||||
const { getRemoteForUsage } = require('@electron/internal/renderer/remote')
|
||||
|
||||
@@ -58,12 +58,16 @@ ipcNative.onMessage = function (channel, args, senderId) {
|
||||
electron.ipcRenderer.emit(channel, { sender: electron.ipcRenderer, senderId }, ...args)
|
||||
}
|
||||
|
||||
ipcNative.onLoaded = function () {
|
||||
process.emit('loaded')
|
||||
}
|
||||
|
||||
ipcNative.onExit = function () {
|
||||
process.emit('exit')
|
||||
}
|
||||
|
||||
const {
|
||||
preloadSrc, preloadError, isRemoteModuleEnabled, process: processProps
|
||||
preloadScripts, isRemoteModuleEnabled, process: processProps
|
||||
} = ipcRenderer.sendSync('ELECTRON_BROWSER_SANDBOX_LOAD')
|
||||
|
||||
const makePropertyNonConfigurable = function (object, name) {
|
||||
@@ -90,6 +94,7 @@ Object.assign(preloadProcess, processProps)
|
||||
Object.assign(process, binding.process)
|
||||
Object.assign(process, processProps)
|
||||
|
||||
process.on('loaded', () => preloadProcess.emit('loaded'))
|
||||
process.on('exit', () => preloadProcess.emit('exit'))
|
||||
|
||||
// This is the `require` function that will be visible to the preload script
|
||||
@@ -100,7 +105,7 @@ function preloadRequire (module) {
|
||||
if (remoteModules.has(module)) {
|
||||
return require(module)
|
||||
}
|
||||
throw new Error('module not found')
|
||||
throw new Error(`module not found: ${module}`)
|
||||
}
|
||||
|
||||
switch (window.location.protocol) {
|
||||
@@ -126,6 +131,8 @@ if (!process.guestInstanceId && preloadProcess.argv.includes('--webview-tag=true
|
||||
require('@electron/internal/renderer/web-view/web-view').setupWebView(window)
|
||||
}
|
||||
|
||||
const errorUtils = require('@electron/internal/common/error-utils')
|
||||
|
||||
// Wrap the script into a function executed in global scope. It won't have
|
||||
// access to the current scope, so we'll expose a few objects as arguments:
|
||||
//
|
||||
@@ -145,7 +152,7 @@ if (!process.guestInstanceId && preloadProcess.argv.includes('--webview-tag=true
|
||||
// and any `require('electron')` calls in `preload.js` will work as expected
|
||||
// since browserify won't try to include `electron` in the bundle, falling back
|
||||
// to the `preloadRequire` function above.
|
||||
if (preloadSrc) {
|
||||
function runPreloadScript (preloadSrc) {
|
||||
const preloadWrapperSrc = `(function(require, process, Buffer, global, setImmediate, clearImmediate) {
|
||||
${preloadSrc}
|
||||
})`
|
||||
@@ -153,9 +160,21 @@ if (preloadSrc) {
|
||||
// eval in window scope
|
||||
const preloadFn = binding.createPreloadScript(preloadWrapperSrc)
|
||||
const { setImmediate, clearImmediate } = require('timers')
|
||||
|
||||
preloadFn(preloadRequire, preloadProcess, Buffer, global, setImmediate, clearImmediate)
|
||||
} else if (preloadError) {
|
||||
console.error(preloadError.stack)
|
||||
}
|
||||
|
||||
for (const { preloadPath, preloadSrc, preloadError } of preloadScripts) {
|
||||
try {
|
||||
if (preloadSrc) {
|
||||
runPreloadScript(preloadSrc)
|
||||
} else if (preloadError) {
|
||||
throw errorUtils.deserialize(preloadError)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Unable to load preload script: ${preloadPath}`)
|
||||
console.error(`${error}`)
|
||||
}
|
||||
}
|
||||
|
||||
// Warn about security issues
|
||||
|
||||
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "electron",
|
||||
"version": "4.0.2",
|
||||
"version": "4.2.2",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "electron",
|
||||
"version": "4.0.2",
|
||||
"version": "4.2.2",
|
||||
"repository": "https://github.com/electron/electron",
|
||||
"description": "Build cross platform desktop apps with JavaScript, HTML, and CSS",
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1 +1,6 @@
|
||||
implement_ssl_get_tlsext_status_type.patch
|
||||
expose_ripemd160.patch
|
||||
expose_aes-cfb.patch
|
||||
sync_sorted_ciphers.patch
|
||||
handle_pub_key_null_in_ec_key_set_public_key.patch
|
||||
add_a_compatibility_evp_ciph_ocb_mode_value.patch
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: David Benjamin <davidben@google.com>
|
||||
Date: Sun, 14 Oct 2018 11:01:40 -0500
|
||||
Subject: Add a compatibility EVP_CIPH_OCB_MODE value.
|
||||
|
||||
Node references it these days. Also replace the no-op modes with negative
|
||||
numbers rather than zero. Stream ciphers like RC4 report a "mode" of zero, so
|
||||
code comparing the mode to a dummy value will get confused.
|
||||
|
||||
(I came across https://github.com/nodejs/node/pull/23635, though we'd have run
|
||||
into it sooner or later anyway. Better to just define the value and avoid ifdef
|
||||
proliferation.)
|
||||
|
||||
Change-Id: I223f25663e138480ad83f35aa16f5218f1425563
|
||||
Reviewed-on: https://boringssl-review.googlesource.com/c/32464
|
||||
Reviewed-by: Adam Langley <agl@google.com>
|
||||
Commit-Queue: Adam Langley <agl@google.com>
|
||||
CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
|
||||
|
||||
diff --git a/include/openssl/cipher.h b/include/openssl/cipher.h
|
||||
index e2ab9449275a62ee8a93bd48284b39e8df88a14f..7d4d78b3730022fb61ae63c6d3a86a61cb0c91e2 100644
|
||||
--- a/include/openssl/cipher.h
|
||||
+++ b/include/openssl/cipher.h
|
||||
@@ -425,8 +425,9 @@ OPENSSL_EXPORT const EVP_CIPHER *EVP_aes_256_cfb128(void);
|
||||
|
||||
// The following flags do nothing and are included only to make it easier to
|
||||
// compile code with BoringSSL.
|
||||
-#define EVP_CIPH_CCM_MODE 0
|
||||
-#define EVP_CIPH_WRAP_MODE 0
|
||||
+#define EVP_CIPH_CCM_MODE (-1)
|
||||
+#define EVP_CIPH_OCB_MODE (-2)
|
||||
+#define EVP_CIPH_WRAP_MODE (-3)
|
||||
#define EVP_CIPHER_CTX_FLAG_WRAP_ALLOW 0
|
||||
|
||||
// EVP_CIPHER_CTX_set_flags does nothing.
|
||||
84
patches/common/boringssl/expose_aes-cfb.patch
Normal file
84
patches/common/boringssl/expose_aes-cfb.patch
Normal file
@@ -0,0 +1,84 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Jeremy Apthorp <nornagon@nornagon.net>
|
||||
Date: Fri, 18 Jan 2019 14:23:28 -0800
|
||||
Subject: expose aes-{128,256}-cfb
|
||||
|
||||
|
||||
diff --git a/crypto/cipher_extra/cipher_extra.c b/crypto/cipher_extra/cipher_extra.c
|
||||
index 1b23ad32f8cff2a00512ba58d24b47b628e7920c..be7ef07b2c188a76890deb0f305cf92fcc57a64e 100644
|
||||
--- a/crypto/cipher_extra/cipher_extra.c
|
||||
+++ b/crypto/cipher_extra/cipher_extra.c
|
||||
@@ -101,10 +101,14 @@ const EVP_CIPHER *EVP_get_cipherbyname(const char *name) {
|
||||
return EVP_des_ede3_cbc();
|
||||
} else if (OPENSSL_strcasecmp(name, "aes-128-cbc") == 0) {
|
||||
return EVP_aes_128_cbc();
|
||||
+ } else if (OPENSSL_strcasecmp(name, "aes-128-cfb") == 0) {
|
||||
+ return EVP_aes_128_cfb128();
|
||||
} else if (OPENSSL_strcasecmp(name, "aes-192-cbc") == 0) {
|
||||
return EVP_aes_192_cbc();
|
||||
} else if (OPENSSL_strcasecmp(name, "aes-256-cbc") == 0) {
|
||||
return EVP_aes_256_cbc();
|
||||
+ } else if (OPENSSL_strcasecmp(name, "aes-256-cfb") == 0) {
|
||||
+ return EVP_aes_256_cfb128();
|
||||
} else if (OPENSSL_strcasecmp(name, "aes-128-ctr") == 0) {
|
||||
return EVP_aes_128_ctr();
|
||||
} else if (OPENSSL_strcasecmp(name, "aes-192-ctr") == 0) {
|
||||
diff --git a/decrepit/cfb/cfb.c b/decrepit/cfb/cfb.c
|
||||
index d3a176163303a202baeb1f95727c6ed3525439d6..21d108a7b73d454aa6b0e324df4b67088d60302a 100644
|
||||
--- a/decrepit/cfb/cfb.c
|
||||
+++ b/decrepit/cfb/cfb.c
|
||||
@@ -57,4 +57,12 @@ static const EVP_CIPHER aes_128_cfb128 = {
|
||||
NULL /* cleanup */, NULL /* ctrl */,
|
||||
};
|
||||
|
||||
+static const EVP_CIPHER aes_256_cfb128 = {
|
||||
+ NID_aes_128_cfb128, 1 /* block_size */, 32 /* key_size */,
|
||||
+ 16 /* iv_len */, sizeof(EVP_CFB_CTX), EVP_CIPH_CFB_MODE,
|
||||
+ NULL /* app_data */, aes_cfb_init_key, aes_cfb128_cipher,
|
||||
+ NULL /* cleanup */, NULL /* ctrl */,
|
||||
+};
|
||||
+
|
||||
const EVP_CIPHER *EVP_aes_128_cfb128(void) { return &aes_128_cfb128; }
|
||||
+const EVP_CIPHER *EVP_aes_256_cfb128(void) { return &aes_256_cfb128; }
|
||||
diff --git a/decrepit/evp/evp_do_all.c b/decrepit/evp/evp_do_all.c
|
||||
index acc4719b7e9c4c4461fc6142f2ae9156b407915b..8b008a401ec2f2d0673f6876609dd5786cace4c2 100644
|
||||
--- a/decrepit/evp/evp_do_all.c
|
||||
+++ b/decrepit/evp/evp_do_all.c
|
||||
@@ -20,10 +20,12 @@ void EVP_CIPHER_do_all_sorted(void (*callback)(const EVP_CIPHER *cipher,
|
||||
const char *unused, void *arg),
|
||||
void *arg) {
|
||||
callback(EVP_aes_128_cbc(), "AES-128-CBC", NULL, arg);
|
||||
+ callback(EVP_aes_128_cfb128(), "AES-128-CFB", NULL, arg);
|
||||
callback(EVP_aes_128_ctr(), "AES-128-CTR", NULL, arg);
|
||||
callback(EVP_aes_128_ecb(), "AES-128-ECB", NULL, arg);
|
||||
callback(EVP_aes_128_ofb(), "AES-128-OFB", NULL, arg);
|
||||
callback(EVP_aes_256_cbc(), "AES-256-CBC", NULL, arg);
|
||||
+ callback(EVP_aes_256_cfb128(), "AES-256-CFB", NULL, arg);
|
||||
callback(EVP_aes_256_ctr(), "AES-256-CTR", NULL, arg);
|
||||
callback(EVP_aes_256_ecb(), "AES-256-ECB", NULL, arg);
|
||||
callback(EVP_aes_256_ofb(), "AES-256-OFB", NULL, arg);
|
||||
@@ -38,10 +40,12 @@ void EVP_CIPHER_do_all_sorted(void (*callback)(const EVP_CIPHER *cipher,
|
||||
|
||||
// OpenSSL returns everything twice, the second time in lower case.
|
||||
callback(EVP_aes_128_cbc(), "aes-128-cbc", NULL, arg);
|
||||
+ callback(EVP_aes_128_cfb128(), "aes-128-cfb", NULL, arg);
|
||||
callback(EVP_aes_128_ctr(), "aes-128-ctr", NULL, arg);
|
||||
callback(EVP_aes_128_ecb(), "aes-128-ecb", NULL, arg);
|
||||
callback(EVP_aes_128_ofb(), "aes-128-ofb", NULL, arg);
|
||||
callback(EVP_aes_256_cbc(), "aes-256-cbc", NULL, arg);
|
||||
+ callback(EVP_aes_256_cfb128(), "aes-256-cfb", NULL, arg);
|
||||
callback(EVP_aes_256_ctr(), "aes-256-ctr", NULL, arg);
|
||||
callback(EVP_aes_256_ecb(), "aes-256-ecb", NULL, arg);
|
||||
callback(EVP_aes_256_ofb(), "aes-256-ofb", NULL, arg);
|
||||
diff --git a/include/openssl/cipher.h b/include/openssl/cipher.h
|
||||
index 7d99d49ba7ae2d8a4eb80681cbd9b41eee86bac7..e2ab9449275a62ee8a93bd48284b39e8df88a14f 100644
|
||||
--- a/include/openssl/cipher.h
|
||||
+++ b/include/openssl/cipher.h
|
||||
@@ -421,6 +421,7 @@ OPENSSL_EXPORT const EVP_CIPHER *EVP_aes_192_ofb(void);
|
||||
|
||||
// EVP_aes_128_cfb128 is only available in decrepit.
|
||||
OPENSSL_EXPORT const EVP_CIPHER *EVP_aes_128_cfb128(void);
|
||||
+OPENSSL_EXPORT const EVP_CIPHER *EVP_aes_256_cfb128(void);
|
||||
|
||||
// The following flags do nothing and are included only to make it easier to
|
||||
// compile code with BoringSSL.
|
||||
95
patches/common/boringssl/expose_ripemd160.patch
Normal file
95
patches/common/boringssl/expose_ripemd160.patch
Normal file
@@ -0,0 +1,95 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Jeremy Apthorp <nornagon@nornagon.net>
|
||||
Date: Fri, 18 Jan 2019 13:56:52 -0800
|
||||
Subject: expose ripemd160
|
||||
|
||||
This adds references to the decrepit/ module from non-decrepit source,
|
||||
which is not allowed in upstream. Until upstream has a way to interface
|
||||
with node.js that allows exposing additional digests without patching,
|
||||
this patch is required to provide ripemd160 support in the nodejs crypto
|
||||
module.
|
||||
|
||||
diff --git a/crypto/digest_extra/digest_extra.c b/crypto/digest_extra/digest_extra.c
|
||||
index 4b4bb38135e6089eaf6f47afda0199567a2397ef..43b7eca808b82a032055f56ce726ce4f38c5f2c5 100644
|
||||
--- a/crypto/digest_extra/digest_extra.c
|
||||
+++ b/crypto/digest_extra/digest_extra.c
|
||||
@@ -81,6 +81,7 @@ static const struct nid_to_digest nid_to_digest_mapping[] = {
|
||||
{NID_sha384, EVP_sha384, SN_sha384, LN_sha384},
|
||||
{NID_sha512, EVP_sha512, SN_sha512, LN_sha512},
|
||||
{NID_md5_sha1, EVP_md5_sha1, SN_md5_sha1, LN_md5_sha1},
|
||||
+ {NID_ripemd160, EVP_ripemd160, SN_ripemd160, LN_ripemd160},
|
||||
// As a remnant of signing |EVP_MD|s, OpenSSL returned the corresponding
|
||||
// hash function when given a signature OID. To avoid unintended lax parsing
|
||||
// of hash OIDs, this is no longer supported for lookup by OID or NID.
|
||||
diff --git a/crypto/fipsmodule/digest/digests.c b/crypto/fipsmodule/digest/digests.c
|
||||
index f2fa349c2b32ae88766624af3109ece4b1d69909..bcaed59c5401bef071acba9b9919d9069e3ccd4d 100644
|
||||
--- a/crypto/fipsmodule/digest/digests.c
|
||||
+++ b/crypto/fipsmodule/digest/digests.c
|
||||
@@ -63,6 +63,7 @@
|
||||
#include <openssl/md5.h>
|
||||
#include <openssl/nid.h>
|
||||
#include <openssl/sha.h>
|
||||
+#include <openssl/ripemd.h>
|
||||
|
||||
#include "internal.h"
|
||||
#include "../delocate.h"
|
||||
@@ -277,4 +278,27 @@ DEFINE_METHOD_FUNCTION(EVP_MD, EVP_md5_sha1) {
|
||||
out->ctx_size = sizeof(MD5_SHA1_CTX);
|
||||
}
|
||||
|
||||
+static void ripemd160_init(EVP_MD_CTX *ctx) {
|
||||
+ CHECK(RIPEMD160_Init(ctx->md_data));
|
||||
+}
|
||||
+
|
||||
+static void ripemd160_update(EVP_MD_CTX *ctx, const void *data, size_t count) {
|
||||
+ CHECK(RIPEMD160_Update(ctx->md_data, data, count));
|
||||
+}
|
||||
+
|
||||
+static void ripemd160_final(EVP_MD_CTX *ctx, uint8_t *md) {
|
||||
+ CHECK(RIPEMD160_Final(md, ctx->md_data));
|
||||
+}
|
||||
+
|
||||
+DEFINE_METHOD_FUNCTION(EVP_MD, EVP_ripemd160) {
|
||||
+ out->type = NID_ripemd160;
|
||||
+ out->md_size = RIPEMD160_DIGEST_LENGTH;
|
||||
+ out->flags = 0;
|
||||
+ out->init = ripemd160_init;
|
||||
+ out->update = ripemd160_update;
|
||||
+ out->final = ripemd160_final;
|
||||
+ out->block_size = 64;
|
||||
+ out->ctx_size = sizeof(RIPEMD160_CTX);
|
||||
+}
|
||||
+
|
||||
#undef CHECK
|
||||
diff --git a/decrepit/evp/evp_do_all.c b/decrepit/evp/evp_do_all.c
|
||||
index 38b8f9f78f76050174096740596ac59a0fe18757..acc4719b7e9c4c4461fc6142f2ae9156b407915b 100644
|
||||
--- a/decrepit/evp/evp_do_all.c
|
||||
+++ b/decrepit/evp/evp_do_all.c
|
||||
@@ -66,6 +66,7 @@ void EVP_MD_do_all_sorted(void (*callback)(const EVP_MD *cipher,
|
||||
callback(EVP_sha256(), "SHA256", NULL, arg);
|
||||
callback(EVP_sha384(), "SHA384", NULL, arg);
|
||||
callback(EVP_sha512(), "SHA512", NULL, arg);
|
||||
+ callback(EVP_ripemd160(), "RIPEMD160", NULL, arg);
|
||||
|
||||
callback(EVP_md4(), "md4", NULL, arg);
|
||||
callback(EVP_md5(), "md5", NULL, arg);
|
||||
@@ -74,4 +75,5 @@ void EVP_MD_do_all_sorted(void (*callback)(const EVP_MD *cipher,
|
||||
callback(EVP_sha256(), "sha256", NULL, arg);
|
||||
callback(EVP_sha384(), "sha384", NULL, arg);
|
||||
callback(EVP_sha512(), "sha512", NULL, arg);
|
||||
+ callback(EVP_ripemd160(), "ripemd160", NULL, arg);
|
||||
}
|
||||
diff --git a/include/openssl/digest.h b/include/openssl/digest.h
|
||||
index 4077d902a07c215659ed61b54a468231536d70ee..f15df35d16402256fa00263e2c2e71d55ce67d1a 100644
|
||||
--- a/include/openssl/digest.h
|
||||
+++ b/include/openssl/digest.h
|
||||
@@ -88,6 +88,9 @@ OPENSSL_EXPORT const EVP_MD *EVP_sha512(void);
|
||||
// MD5 and SHA-1, as used in TLS 1.1 and below.
|
||||
OPENSSL_EXPORT const EVP_MD *EVP_md5_sha1(void);
|
||||
|
||||
+// EVP_ripemd160 is in decrepit and not available by default.
|
||||
+OPENSSL_EXPORT const EVP_MD *EVP_ripemd160(void);
|
||||
+
|
||||
// EVP_get_digestbynid returns an |EVP_MD| for the given NID, or NULL if no
|
||||
// such digest is known.
|
||||
OPENSSL_EXPORT const EVP_MD *EVP_get_digestbynid(int nid);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user