diff --git a/.travis.yml b/.travis.yml index ff7272e3e57..4d4cd5dde9f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,3 +22,7 @@ matrix: - env: TARGET_ARCH=ia32 script: './script/cibuild' + +branches: + only: + - master diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c86a6d1c2d2..61a8e474099 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,8 +2,8 @@ :+1::tada: First off, thanks for taking the time to contribute! :tada::+1: -This project adheres to the [Open Code of Conduct][code-of-conduct]. By participating, you are expected to uphold this code. -[code-of-conduct]: http://todogroup.org/opencodeofconduct/#Electron/opensource@github.com +This project adheres to the [Contributor Covenant 1.2](http://contributor-covenant.org/version/1/2/0). +By participating, you are expected to uphold this code. Please report unacceptable behavior to atom@github.com. The following is a set of guidelines for contributing to Electron. These are just guidelines, not rules, use your best judgment and feel free to diff --git a/README.md b/README.md index 764a6abfe9c..8b7d36046a7 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,8 @@ editor](https://github.com/atom/atom). Follow [@ElectronJS](https://twitter.com/electronjs) on Twitter for important announcements. -This project adheres to the [Open Code of Conduct][code-of-conduct]. By participating, you are expected to uphold this code. -[code-of-conduct]: http://todogroup.org/opencodeofconduct/#Electron/opensource@github.com +This project adheres to the [Contributor Covenant 1.2](http://contributor-covenant.org/version/1/2/0). +By participating, you are expected to uphold this code. Please report unacceptable behavior to atom@github.com. ## Downloads diff --git a/atom.gyp b/atom.gyp index 83ea66adbab..d24f193ac2d 100644 --- a/atom.gyp +++ b/atom.gyp @@ -4,7 +4,7 @@ 'product_name%': 'Electron', 'company_name%': 'GitHub, Inc', 'company_abbr%': 'github', - 'version%': '0.29.2', + 'version%': '0.30.4', }, 'includes': [ 'filenames.gypi', diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index fea4deda5b1..c25b3be5f4d 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -17,6 +17,7 @@ #include "atom/browser/atom_browser_main_parts.h" #include "atom/browser/browser.h" #include "atom/browser/api/atom_api_web_contents.h" +#include "atom/common/native_mate_converters/callback.h" #include "atom/common/native_mate_converters/file_path_converter.h" #include "base/command_line.h" #include "base/environment.h" @@ -25,7 +26,6 @@ #include "brightray/browser/brightray_paths.h" #include "content/public/browser/client_certificate_delegate.h" #include "content/public/browser/gpu_data_manager.h" -#include "native_mate/callback.h" #include "native_mate/dictionary.h" #include "native_mate/object_template_builder.h" #include "net/ssl/ssl_cert_request_info.h" diff --git a/atom/browser/api/atom_api_content_tracing.cc b/atom/browser/api/atom_api_content_tracing.cc index 6da450185cd..e404a8f26ca 100644 --- a/atom/browser/api/atom_api_content_tracing.cc +++ b/atom/browser/api/atom_api_content_tracing.cc @@ -5,10 +5,11 @@ #include #include +#include "atom/common/native_mate_converters/callback.h" #include "atom/common/native_mate_converters/file_path_converter.h" #include "base/bind.h" +#include "base/files/file_util.h" #include "content/public/browser/tracing_controller.h" -#include "native_mate/callback.h" #include "native_mate/dictionary.h" #include "atom/common/node_includes.h" @@ -46,6 +47,31 @@ struct Converter { namespace { +using CompletionCallback = base::Callback; + +scoped_refptr GetTraceDataSink( + const base::FilePath& path, const CompletionCallback& callback) { + base::FilePath result_file_path = path; + if (result_file_path.empty() && !base::CreateTemporaryFile(&result_file_path)) + LOG(ERROR) << "Creating temporary file failed"; + + return TracingController::CreateFileSink(result_file_path, + base::Bind(callback, + result_file_path)); +} + +void StopRecording(const base::FilePath& path, + const CompletionCallback& callback) { + TracingController::GetInstance()->DisableRecording( + GetTraceDataSink(path, callback)); +} + +void CaptureMonitoringSnapshot(const base::FilePath& path, + const CompletionCallback& callback) { + TracingController::GetInstance()->CaptureMonitoringSnapshot( + GetTraceDataSink(path, callback)); +} + void Initialize(v8::Local exports, v8::Local unused, v8::Local context, void* priv) { auto controller = base::Unretained(TracingController::GetInstance()); @@ -54,14 +80,12 @@ void Initialize(v8::Local exports, v8::Local unused, &TracingController::GetCategories, controller)); dict.SetMethod("startRecording", base::Bind( &TracingController::EnableRecording, controller)); - dict.SetMethod("stopRecording", base::Bind( - &TracingController::DisableRecording, controller, nullptr)); + dict.SetMethod("stopRecording", &StopRecording); dict.SetMethod("startMonitoring", base::Bind( &TracingController::EnableMonitoring, controller)); dict.SetMethod("stopMonitoring", base::Bind( &TracingController::DisableMonitoring, controller)); - dict.SetMethod("captureMonitoringSnapshot", base::Bind( - &TracingController::CaptureMonitoringSnapshot, controller, nullptr)); + dict.SetMethod("captureMonitoringSnapshot", &CaptureMonitoringSnapshot); dict.SetMethod("getTraceBufferUsage", base::Bind( &TracingController::GetTraceBufferUsage, controller)); dict.SetMethod("setWatchEvent", base::Bind( diff --git a/atom/browser/api/atom_api_cookies.cc b/atom/browser/api/atom_api_cookies.cc index 0363032df87..bf56a8dc13b 100644 --- a/atom/browser/api/atom_api_cookies.cc +++ b/atom/browser/api/atom_api_cookies.cc @@ -4,13 +4,14 @@ #include "atom/browser/api/atom_api_cookies.h" +#include "atom/common/native_mate_converters/callback.h" #include "atom/common/native_mate_converters/gurl_converter.h" #include "atom/common/native_mate_converters/value_converter.h" #include "base/bind.h" #include "base/time/time.h" +#include "base/values.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/browser_thread.h" -#include "native_mate/callback.h" #include "native_mate/dictionary.h" #include "native_mate/object_template_builder.h" #include "net/cookies/cookie_monster.h" @@ -179,8 +180,8 @@ namespace atom { namespace api { -Cookies::Cookies(content::BrowserContext* browser_context) : - browser_context_(browser_context) { +Cookies::Cookies(content::BrowserContext* browser_context) + : request_context_getter_(browser_context->GetRequestContext()) { } Cookies::~Cookies() { @@ -198,11 +199,9 @@ void Cookies::Get(const base::DictionaryValue& options, void Cookies::GetCookiesOnIOThread(scoped_ptr filter, const CookiesCallback& callback) { - net::CookieStore* cookie_store = browser_context_->GetRequestContext() - ->GetURLRequestContext()->cookie_store(); std::string url; filter->GetString("url", &url); - if (!GetCookieListFromStore(cookie_store, url, + if (!GetCookieListFromStore(GetCookieStore(), url, base::Bind(&Cookies::OnGetCookies, base::Unretained(this), Passed(&filter), callback))) { BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, @@ -245,9 +244,7 @@ void Cookies::Remove(const mate::Dictionary& details, void Cookies::RemoveCookiesOnIOThread(const GURL& url, const std::string& name, const CookiesCallback& callback) { - net::CookieStore* cookie_store = browser_context_->GetRequestContext() - ->GetURLRequestContext()->cookie_store(); - cookie_store->DeleteCookieAsync(url, name, + GetCookieStore()->DeleteCookieAsync(url, name, base::Bind(&Cookies::OnRemoveCookies, base::Unretained(this), callback)); } @@ -286,8 +283,6 @@ void Cookies::SetCookiesOnIOThread(scoped_ptr details, const GURL& url, const CookiesCallback& callback) { DCHECK_CURRENTLY_ON(BrowserThread::IO); - net::CookieStore* cookie_store = browser_context_->GetRequestContext() - ->GetURLRequestContext()->cookie_store(); std::string name, value, domain, path; bool secure = false; @@ -308,7 +303,7 @@ void Cookies::SetCookiesOnIOThread(scoped_ptr details, base::Time::FromDoubleT(expiration_date); } - cookie_store->GetCookieMonster()->SetCookieWithDetailsAsync( + GetCookieStore()->GetCookieMonster()->SetCookieWithDetailsAsync( url, name, value, @@ -337,6 +332,10 @@ mate::ObjectTemplateBuilder Cookies::GetObjectTemplateBuilder( .SetMethod("set", &Cookies::Set); } +net::CookieStore* Cookies::GetCookieStore() { + return request_context_getter_->GetURLRequestContext()->cookie_store(); +} + // static mate::Handle Cookies::Create( v8::Isolate* isolate, diff --git a/atom/browser/api/atom_api_cookies.h b/atom/browser/api/atom_api_cookies.h index 61357f05d75..12cf4a22097 100644 --- a/atom/browser/api/atom_api_cookies.h +++ b/atom/browser/api/atom_api_cookies.h @@ -8,17 +8,27 @@ #include #include "base/callback.h" -#include "base/values.h" #include "native_mate/wrappable.h" #include "native_mate/handle.h" -#include "native_mate/dictionary.h" - #include "net/cookies/canonical_cookie.h" +namespace base { +class DictionaryValue; +} + namespace content { class BrowserContext; } +namespace mate { +class Dictionary; +} + +namespace net { +class CookieStore; +class URLRequestContextGetter; +} + namespace atom { namespace api { @@ -60,13 +70,15 @@ class Cookies : public mate::Wrappable { void OnSetCookies(const CookiesCallback& callback, bool set_success); - - // mate::Wrappable implementations: + // mate::Wrappable: mate::ObjectTemplateBuilder GetObjectTemplateBuilder( v8::Isolate* isolate) override; private: - content::BrowserContext* browser_context_; + // Must be called on IO thread. + net::CookieStore* GetCookieStore(); + + scoped_refptr request_context_getter_; DISALLOW_COPY_AND_ASSIGN(Cookies); }; diff --git a/atom/browser/api/atom_api_dialog.cc b/atom/browser/api/atom_api_dialog.cc index b5fd655d6f2..40ee4d0d9b5 100644 --- a/atom/browser/api/atom_api_dialog.cc +++ b/atom/browser/api/atom_api_dialog.cc @@ -10,9 +10,9 @@ #include "atom/browser/native_window.h" #include "atom/browser/ui/file_dialog.h" #include "atom/browser/ui/message_box.h" +#include "atom/common/native_mate_converters/callback.h" #include "atom/common/native_mate_converters/file_path_converter.h" #include "atom/common/native_mate_converters/image_converter.h" -#include "native_mate/callback.h" #include "native_mate/dictionary.h" #include "atom/common/node_includes.h" @@ -42,28 +42,24 @@ namespace { void ShowMessageBox(int type, const std::vector& buttons, int cancel_id, - const std::vector& texts, + int options, + const std::string& title, + const std::string& message, + const std::string& detail, const gfx::ImageSkia& icon, atom::NativeWindow* window, mate::Arguments* args) { - // FIXME We are exceeding the parameters limit of base::Bind here, so we have - // to pass some parameters in an array. We should remove this once we have - // variadic template support in base::Bind. - const std::string& title = texts[0]; - const std::string& message = texts[1]; - const std::string& detail = texts[2]; - v8::Local peek = args->PeekNext(); atom::MessageBoxCallback callback; if (mate::Converter::FromV8(args->isolate(), peek, &callback)) { atom::ShowMessageBox(window, (atom::MessageBoxType)type, buttons, cancel_id, - title, message, detail, icon, callback); + options, title, message, detail, icon, callback); } else { int chosen = atom::ShowMessageBox(window, (atom::MessageBoxType)type, - buttons, cancel_id, title, message, - detail, icon); + buttons, cancel_id, options, title, + message, detail, icon); args->Return(chosen); } } diff --git a/atom/browser/api/atom_api_global_shortcut.cc b/atom/browser/api/atom_api_global_shortcut.cc index 47bebe9d12b..f5a03e4abf9 100644 --- a/atom/browser/api/atom_api_global_shortcut.cc +++ b/atom/browser/api/atom_api_global_shortcut.cc @@ -7,8 +7,8 @@ #include #include "atom/common/native_mate_converters/accelerator_converter.h" +#include "atom/common/native_mate_converters/callback.h" #include "base/stl_util.h" -#include "native_mate/callback.h" #include "native_mate/dictionary.h" #include "atom/common/node_includes.h" diff --git a/atom/browser/api/atom_api_menu.cc b/atom/browser/api/atom_api_menu.cc index 1eb5cb31d11..356a4d4ce49 100644 --- a/atom/browser/api/atom_api_menu.cc +++ b/atom/browser/api/atom_api_menu.cc @@ -6,9 +6,9 @@ #include "atom/browser/native_window.h" #include "atom/common/native_mate_converters/accelerator_converter.h" +#include "atom/common/native_mate_converters/callback.h" #include "atom/common/native_mate_converters/image_converter.h" #include "atom/common/native_mate_converters/string16_converter.h" -#include "native_mate/callback.h" #include "native_mate/constructor.h" #include "native_mate/dictionary.h" #include "native_mate/object_template_builder.h" @@ -20,7 +20,7 @@ namespace atom { namespace api { Menu::Menu() - : model_(new ui::SimpleMenuModel(this)), + : model_(new AtomMenuModel(this)), parent_(NULL) { } diff --git a/atom/browser/api/atom_api_menu.h b/atom/browser/api/atom_api_menu.h index 0c2743878f7..33aafbc45d3 100644 --- a/atom/browser/api/atom_api_menu.h +++ b/atom/browser/api/atom_api_menu.h @@ -8,9 +8,9 @@ #include #include "atom/browser/api/atom_api_window.h" +#include "atom/browser/ui/atom_menu_model.h" #include "base/callback.h" #include "base/memory/scoped_ptr.h" -#include "ui/base/models/simple_menu_model.h" #include "native_mate/wrappable.h" namespace atom { @@ -18,7 +18,7 @@ namespace atom { namespace api { class Menu : public mate::Wrappable, - public ui::SimpleMenuModel::Delegate { + public AtomMenuModel::Delegate { public: static mate::Wrappable* Create(); @@ -33,7 +33,7 @@ class Menu : public mate::Wrappable, static void SendActionToFirstResponder(const std::string& action); #endif - ui::SimpleMenuModel* model() const { return model_.get(); } + AtomMenuModel* model() const { return model_.get(); } protected: Menu(); @@ -42,7 +42,7 @@ class Menu : public mate::Wrappable, // mate::Wrappable: void AfterInit(v8::Isolate* isolate) override; - // ui::SimpleMenuModel::Delegate implementations: + // ui::SimpleMenuModel::Delegate: bool IsCommandIdChecked(int command_id) const override; bool IsCommandIdEnabled(int command_id) const override; bool IsCommandIdVisible(int command_id) const override; @@ -54,7 +54,7 @@ class Menu : public mate::Wrappable, virtual void Popup(Window* window) = 0; virtual void PopupAt(Window* window, int x, int y) = 0; - scoped_ptr model_; + scoped_ptr model_; Menu* parent_; private: @@ -102,9 +102,9 @@ class Menu : public mate::Wrappable, namespace mate { template<> -struct Converter { +struct Converter { static bool FromV8(v8::Isolate* isolate, v8::Local val, - ui::SimpleMenuModel** out) { + atom::AtomMenuModel** out) { // null would be tranfered to NULL. if (val->IsNull()) { *out = nullptr; diff --git a/atom/browser/api/atom_api_protocol.cc b/atom/browser/api/atom_api_protocol.cc index 65ba3039d15..d7d308b306e 100644 --- a/atom/browser/api/atom_api_protocol.cc +++ b/atom/browser/api/atom_api_protocol.cc @@ -7,12 +7,13 @@ #include "atom/browser/atom_browser_client.h" #include "atom/browser/atom_browser_context.h" #include "atom/browser/atom_browser_main_parts.h" +#include "atom/browser/api/atom_api_session.h" #include "atom/browser/net/adapter_request_job.h" #include "atom/browser/net/atom_url_request_job_factory.h" +#include "atom/common/native_mate_converters/callback.h" #include "atom/common/native_mate_converters/file_path_converter.h" #include "atom/common/native_mate_converters/gurl_converter.h" #include "content/public/browser/browser_thread.h" -#include "native_mate/callback.h" #include "native_mate/dictionary.h" #include "net/url_request/url_request_context.h" @@ -62,16 +63,13 @@ class CustomProtocolRequestJob : public AdapterRequestJob { registry_(registry) { } - // AdapterRequestJob: - void GetJobTypeInUI() override { + void GetJobTypeInUI(const Protocol::JsProtocolHandler& callback) { DCHECK_CURRENTLY_ON(BrowserThread::UI); v8::Locker locker(registry_->isolate()); v8::HandleScope handle_scope(registry_->isolate()); // Call the JS handler. - Protocol::JsProtocolHandler callback = - registry_->GetProtocolHandler(request()->url().scheme()); v8::Local result = callback.Run(request()); // Determine the type of the job we are going to create. @@ -130,9 +128,23 @@ class CustomProtocolRequestJob : public AdapterRequestJob { dict.Get("method", &method); dict.Get("referrer", &referrer); + v8::Local value; + mate::Handle session; + scoped_refptr request_context_getter; + // "session" null -> pass nullptr; + // "session" a Session object -> use passed session. + // "session" undefined -> use current session; + if (dict.Get("session", &session)) + request_context_getter = + session->browser_context()->GetRequestContext(); + else if (dict.Get("session", &value) && value->IsNull()) + request_context_getter = nullptr; + else + request_context_getter = registry_->request_context_getter(); + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(&AdapterRequestJob::CreateHttpJobAndStart, GetWeakPtr(), - registry_->browser_context(), url, method, referrer)); + request_context_getter, url, method, referrer)); return; } } @@ -151,6 +163,14 @@ class CustomProtocolRequestJob : public AdapterRequestJob { GetWeakPtr(), net::ERR_NOT_IMPLEMENTED)); } + // AdapterRequestJob: + void GetJobType() override { + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + base::Bind(&CustomProtocolRequestJob::GetJobTypeInUI, + base::Unretained(this), + registry_->GetProtocolHandler(request()->url().scheme()))); + } + private: Protocol* registry_; // Weak, the Protocol class is expected to live forever. }; @@ -211,7 +231,7 @@ std::string ConvertErrorCode(int error_code) { } // namespace Protocol::Protocol(AtomBrowserContext* browser_context) - : browser_context_(browser_context), + : request_context_getter_(browser_context->GetRequestContext()), job_factory_(browser_context->job_factory()) { CHECK(job_factory_); } @@ -335,6 +355,10 @@ int Protocol::InterceptProtocolInIO(const std::string& scheme, const JsProtocolHandler& handler) { DCHECK_CURRENTLY_ON(BrowserThread::IO); + // Force the request context to initialize, otherwise we might have nothing + // to intercept. + request_context_getter_->GetURLRequestContext(); + if (!job_factory_->HasProtocolHandler(scheme)) return ERR_NO_SCHEME; diff --git a/atom/browser/api/atom_api_protocol.h b/atom/browser/api/atom_api_protocol.h index b4d56018baf..4dec17a7434 100644 --- a/atom/browser/api/atom_api_protocol.h +++ b/atom/browser/api/atom_api_protocol.h @@ -16,6 +16,7 @@ namespace net { class URLRequest; +class URLRequestContextGetter; } namespace atom { @@ -46,7 +47,9 @@ class Protocol : public mate::EventEmitter { JsProtocolHandler GetProtocolHandler(const std::string& scheme); - AtomBrowserContext* browser_context() const { return browser_context_; } + net::URLRequestContextGetter* request_context_getter() { + return request_context_getter_.get(); + } protected: explicit Protocol(AtomBrowserContext* browser_context); @@ -94,7 +97,8 @@ class Protocol : public mate::EventEmitter { const JsProtocolHandler& handler); int UninterceptProtocolInIO(const std::string& scheme); - AtomBrowserContext* browser_context_; + scoped_refptr request_context_getter_; + AtomURLRequestJobFactory* job_factory_; ProtocolHandlersMap protocol_handlers_; diff --git a/atom/browser/api/atom_api_session.cc b/atom/browser/api/atom_api_session.cc index 93f8de52dd4..9961d6cc660 100644 --- a/atom/browser/api/atom_api_session.cc +++ b/atom/browser/api/atom_api_session.cc @@ -9,16 +9,22 @@ #include "atom/browser/api/atom_api_cookies.h" #include "atom/browser/atom_browser_context.h" +#include "atom/common/native_mate_converters/callback.h" #include "atom/common/native_mate_converters/gurl_converter.h" -#include "base/thread_task_runner_handle.h" +#include "atom/common/native_mate_converters/file_path_converter.h" +#include "base/files/file_path.h" +#include "base/prefs/pref_service.h" #include "base/strings/string_util.h" +#include "base/thread_task_runner_handle.h" +#include "chrome/common/pref_names.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/storage_partition.h" -#include "native_mate/callback.h" +#include "native_mate/dictionary.h" #include "native_mate/object_template_builder.h" #include "net/base/load_flags.h" #include "net/disk_cache/disk_cache.h" #include "net/proxy/proxy_service.h" +#include "net/proxy/proxy_config_service_fixed.h" #include "net/url_request/url_request_context.h" #include "net/url_request/url_request_context_getter.h" @@ -157,9 +163,10 @@ class ResolveProxyHelper { }; // Runs the callback in UI thread. -void RunCallbackInUI(const net::CompletionCallback& callback, int result) { +template +void RunCallbackInUI(const base::Callback& callback, T... result) { BrowserThread::PostTask( - BrowserThread::UI, FROM_HERE, base::Bind(callback, result)); + BrowserThread::UI, FROM_HERE, base::Bind(callback, result...)); } // Callback of HttpCache::GetBackend. @@ -169,19 +176,19 @@ void OnGetBackend(disk_cache::Backend** backend_ptr, if (result != net::OK) { RunCallbackInUI(callback, result); } else if (backend_ptr && *backend_ptr) { - (*backend_ptr)->DoomAllEntries(base::Bind(&RunCallbackInUI, callback)); + (*backend_ptr)->DoomAllEntries(base::Bind(&RunCallbackInUI, callback)); } else { - RunCallbackInUI(callback, net::ERR_FAILED); + RunCallbackInUI(callback, net::ERR_FAILED); } } -void ClearHttpCacheInIO(content::BrowserContext* browser_context, - const net::CompletionCallback& callback) { - auto request_context = - browser_context->GetRequestContext()->GetURLRequestContext(); +void ClearHttpCacheInIO( + const scoped_refptr& context_getter, + const net::CompletionCallback& callback) { + auto request_context = context_getter->GetURLRequestContext(); auto http_cache = request_context->http_transaction_factory()->GetCache(); if (!http_cache) - RunCallbackInUI(callback, net::ERR_FAILED); + RunCallbackInUI(callback, net::ERR_FAILED); // Call GetBackend and make the backend's ptr accessable in OnGetBackend. using BackendPtr = disk_cache::Backend*; @@ -193,6 +200,16 @@ void ClearHttpCacheInIO(content::BrowserContext* browser_context, on_get_backend.Run(net::OK); } +void SetProxyInIO(net::URLRequestContextGetter* getter, + const std::string& proxy, + const base::Closure& callback) { + net::ProxyConfig config; + config.proxy_rules().ParseFromString(proxy); + auto proxy_service = getter->GetURLRequestContext()->proxy_service(); + proxy_service->ResetConfigService(new net::ProxyConfigServiceFixed(config)); + RunCallbackInUI(callback); +} + } // namespace Session::Session(AtomBrowserContext* browser_context) @@ -210,7 +227,7 @@ void Session::ResolveProxy(const GURL& url, ResolveProxyCallback callback) { void Session::ClearCache(const net::CompletionCallback& callback) { BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(&ClearHttpCacheInIO, - base::Unretained(browser_context_), + make_scoped_refptr(browser_context_->GetRequestContext()), callback)); } @@ -232,6 +249,18 @@ void Session::ClearStorageData(mate::Arguments* args) { base::Time(), base::Time::Max(), callback); } +void Session::SetProxy(const std::string& proxy, + const base::Closure& callback) { + auto getter = browser_context_->GetRequestContext(); + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, + base::Bind(&SetProxyInIO, base::Unretained(getter), proxy, callback)); +} + +void Session::SetDownloadPath(const base::FilePath& path) { + browser_context_->prefs()->SetFilePath( + prefs::kDownloadDefaultDirectory, path); +} + v8::Local Session::Cookies(v8::Isolate* isolate) { if (cookies_.IsEmpty()) { auto handle = atom::api::Cookies::Create(isolate, browser_context_); @@ -246,6 +275,8 @@ mate::ObjectTemplateBuilder Session::GetObjectTemplateBuilder( .SetMethod("resolveProxy", &Session::ResolveProxy) .SetMethod("clearCache", &Session::ClearCache) .SetMethod("clearStorageData", &Session::ClearStorageData) + .SetMethod("setProxy", &Session::SetProxy) + .SetMethod("setDownloadPath", &Session::SetDownloadPath) .SetProperty("cookies", &Session::Cookies); } diff --git a/atom/browser/api/atom_api_session.h b/atom/browser/api/atom_api_session.h index 59e1a938079..b353c61c210 100644 --- a/atom/browser/api/atom_api_session.h +++ b/atom/browser/api/atom_api_session.h @@ -13,6 +13,10 @@ class GURL; +namespace base { +class FilePath; +} + namespace mate { class Arguments; } @@ -31,6 +35,8 @@ class Session: public mate::TrackableObject { static mate::Handle CreateFrom( v8::Isolate* isolate, AtomBrowserContext* browser_context); + AtomBrowserContext* browser_context() const { return browser_context_; } + protected: explicit Session(AtomBrowserContext* browser_context); ~Session(); @@ -43,6 +49,8 @@ class Session: public mate::TrackableObject { void ResolveProxy(const GURL& url, ResolveProxyCallback callback); void ClearCache(const net::CompletionCallback& callback); void ClearStorageData(mate::Arguments* args); + void SetProxy(const std::string& proxy, const base::Closure& callback); + void SetDownloadPath(const base::FilePath& path); v8::Local Cookies(v8::Isolate* isolate); v8::Global cookies_; diff --git a/atom/browser/api/atom_api_tray.cc b/atom/browser/api/atom_api_tray.cc index 649967a2b1e..1382f015a63 100644 --- a/atom/browser/api/atom_api_tray.cc +++ b/atom/browser/api/atom_api_tray.cc @@ -14,6 +14,7 @@ #include "atom/common/native_mate_converters/string16_converter.h" #include "native_mate/constructor.h" #include "native_mate/dictionary.h" +#include "ui/events/event_constants.h" #include "ui/gfx/image/image.h" #include "atom/common/node_includes.h" @@ -40,12 +41,25 @@ mate::Wrappable* Tray::New(v8::Isolate* isolate, const gfx::Image& image) { return new Tray(image); } -void Tray::OnClicked(const gfx::Rect& bounds) { - Emit("clicked", bounds); +void Tray::OnClicked(const gfx::Rect& bounds, int modifiers) { + v8::Locker locker(isolate()); + v8::HandleScope handle_scope(isolate()); + EmitCustomEvent("clicked", + ModifiersToObject(isolate(), modifiers), bounds); } -void Tray::OnDoubleClicked() { - Emit("double-clicked"); +void Tray::OnDoubleClicked(const gfx::Rect& bounds, int modifiers) { + v8::Locker locker(isolate()); + v8::HandleScope handle_scope(isolate()); + EmitCustomEvent("double-clicked", + ModifiersToObject(isolate(), modifiers), bounds); +} + +void Tray::OnRightClicked(const gfx::Rect& bounds, int modifiers) { + v8::Locker locker(isolate()); + v8::HandleScope handle_scope(isolate()); + EmitCustomEvent("right-clicked", + ModifiersToObject(isolate(), modifiers), bounds); } void Tray::OnBalloonShow() { @@ -60,6 +74,10 @@ void Tray::OnBalloonClosed() { Emit("balloon-closed"); } +void Tray::OnDropFiles(const std::vector& files) { + Emit("drop-files", files); +} + bool Tray::IsDestroyed() const { return !tray_icon_; } @@ -102,10 +120,26 @@ void Tray::DisplayBalloon(mate::Arguments* args, tray_icon_->DisplayBalloon(icon, title, content); } +void Tray::PopUpContextMenu(mate::Arguments* args) { + gfx::Point pos; + args->GetNext(&pos); + tray_icon_->PopUpContextMenu(pos); +} + void Tray::SetContextMenu(mate::Arguments* args, Menu* menu) { tray_icon_->SetContextMenu(menu->model()); } +v8::Local Tray::ModifiersToObject(v8::Isolate* isolate, + int modifiers) { + mate::Dictionary obj(isolate, v8::Object::New(isolate)); + obj.Set("shiftKey", static_cast(modifiers & ui::EF_SHIFT_DOWN)); + obj.Set("ctrlKey", static_cast(modifiers & ui::EF_CONTROL_DOWN)); + obj.Set("altKey", static_cast(modifiers & ui::EF_ALT_DOWN)); + obj.Set("metaKey", static_cast(modifiers & ui::EF_COMMAND_DOWN)); + return obj.GetHandle(); +} + // static void Tray::BuildPrototype(v8::Isolate* isolate, v8::Local prototype) { @@ -117,6 +151,7 @@ void Tray::BuildPrototype(v8::Isolate* isolate, .SetMethod("setTitle", &Tray::SetTitle) .SetMethod("setHighlightMode", &Tray::SetHighlightMode) .SetMethod("displayBalloon", &Tray::DisplayBalloon) + .SetMethod("popUpContextMenu", &Tray::PopUpContextMenu) .SetMethod("_setContextMenu", &Tray::SetContextMenu); } diff --git a/atom/browser/api/atom_api_tray.h b/atom/browser/api/atom_api_tray.h index 1a4a498d16b..dc9302597cf 100644 --- a/atom/browser/api/atom_api_tray.h +++ b/atom/browser/api/atom_api_tray.h @@ -6,6 +6,7 @@ #define ATOM_BROWSER_API_ATOM_API_TRAY_H_ #include +#include #include "atom/browser/api/event_emitter.h" #include "atom/browser/ui/tray_icon_observer.h" @@ -41,11 +42,13 @@ class Tray : public mate::EventEmitter, virtual ~Tray(); // TrayIconObserver: - void OnClicked(const gfx::Rect&) override; - void OnDoubleClicked() override; + void OnClicked(const gfx::Rect& bounds, int modifiers) override; + void OnDoubleClicked(const gfx::Rect& bounds, int modifiers) override; + void OnRightClicked(const gfx::Rect& bounds, int modifiers) override; void OnBalloonShow() override; void OnBalloonClicked() override; void OnBalloonClosed() override; + void OnDropFiles(const std::vector& files) override; // mate::Wrappable: bool IsDestroyed() const override; @@ -57,9 +60,12 @@ class Tray : public mate::EventEmitter, void SetTitle(mate::Arguments* args, const std::string& title); void SetHighlightMode(mate::Arguments* args, bool highlight); void DisplayBalloon(mate::Arguments* args, const mate::Dictionary& options); + void PopUpContextMenu(mate::Arguments* args); void SetContextMenu(mate::Arguments* args, Menu* menu); private: + v8::Local ModifiersToObject(v8::Isolate* isolate, int modifiers); + scoped_ptr tray_icon_; DISALLOW_COPY_AND_ASSIGN(Tray); diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 6311e1eaf9a..63707f8dcc2 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -13,7 +13,9 @@ #include "atom/browser/native_window.h" #include "atom/browser/web_view_guest_delegate.h" #include "atom/common/api/api_messages.h" -#include "atom/common/event_emitter_caller.h" +#include "atom/common/api/event_emitter_caller.h" +#include "atom/common/native_mate_converters/callback.h" +#include "atom/common/native_mate_converters/file_path_converter.h" #include "atom/common/native_mate_converters/gfx_converter.h" #include "atom/common/native_mate_converters/gurl_converter.h" #include "atom/common/native_mate_converters/image_converter.h" @@ -36,7 +38,6 @@ #include "content/public/browser/storage_partition.h" #include "content/public/browser/site_instance.h" #include "content/public/browser/web_contents.h" -#include "native_mate/callback.h" #include "native_mate/dictionary.h" #include "native_mate/object_template_builder.h" #include "net/http/http_response_headers.h" @@ -563,12 +564,17 @@ void WebContents::SetUserAgent(const std::string& user_agent) { base::Bind(&SetUserAgentInIO, getter, user_agent)); } +std::string WebContents::GetUserAgent() { + return web_contents()->GetUserAgentOverride(); +} + void WebContents::InsertCSS(const std::string& css) { web_contents()->InsertCSS(css); } -void WebContents::ExecuteJavaScript(const base::string16& code) { - web_contents()->GetMainFrame()->ExecuteJavaScript(code); +void WebContents::ExecuteJavaScript(const base::string16& code, + bool has_user_gesture) { + Send(new AtomViewMsg_ExecuteJavaScript(routing_id(), code, has_user_gesture)); } void WebContents::OpenDevTools(mate::Arguments* args) { @@ -685,6 +691,22 @@ void WebContents::PrintToPDF(const base::DictionaryValue& setting, PrintToPDF(setting, callback); } +void WebContents::AddWorkSpace(const base::FilePath& path) { + if (path.empty()) { + node::ThrowError(isolate(), "path cannot be empty"); + return; + } + DevToolsAddFileSystem(path); +} + +void WebContents::RemoveWorkSpace(const base::FilePath& path) { + if (path.empty()) { + node::ThrowError(isolate(), "path cannot be empty"); + return; + } + DevToolsRemoveFileSystem(path); +} + void WebContents::Undo() { web_contents()->Undo(); } @@ -729,6 +751,14 @@ void WebContents::ReplaceMisspelling(const base::string16& word) { web_contents()->ReplaceMisspelling(word); } +void WebContents::Focus() { + web_contents()->Focus(); +} + +void WebContents::TabTraverse(bool reverse) { + web_contents()->FocusThroughTabTraversal(reverse); +} + bool WebContents::SendIPCMessage(const base::string16& channel, const base::ListValue& args) { return Send(new AtomViewMsg_Message(routing_id(), channel, args)); @@ -767,6 +797,7 @@ mate::ObjectTemplateBuilder WebContents::GetObjectTemplateBuilder( .SetMethod("_goToOffset", &WebContents::GoToOffset) .SetMethod("isCrashed", &WebContents::IsCrashed) .SetMethod("setUserAgent", &WebContents::SetUserAgent) + .SetMethod("getUserAgent", &WebContents::GetUserAgent) .SetMethod("insertCSS", &WebContents::InsertCSS) .SetMethod("_executeJavaScript", &WebContents::ExecuteJavaScript) .SetMethod("openDevTools", &WebContents::OpenDevTools) @@ -787,6 +818,8 @@ mate::ObjectTemplateBuilder WebContents::GetObjectTemplateBuilder( .SetMethod("unselect", &WebContents::Unselect) .SetMethod("replace", &WebContents::Replace) .SetMethod("replaceMisspelling", &WebContents::ReplaceMisspelling) + .SetMethod("focus", &WebContents::Focus) + .SetMethod("tabTraverse", &WebContents::TabTraverse) .SetMethod("_send", &WebContents::SendIPCMessage, true) .SetMethod("setSize", &WebContents::SetSize) .SetMethod("setAllowTransparency", &WebContents::SetAllowTransparency) @@ -797,6 +830,8 @@ mate::ObjectTemplateBuilder WebContents::GetObjectTemplateBuilder( .SetMethod("inspectServiceWorker", &WebContents::InspectServiceWorker) .SetMethod("print", &WebContents::Print) .SetMethod("_printToPDF", &WebContents::PrintToPDF) + .SetMethod("addWorkSpace", &WebContents::AddWorkSpace) + .SetMethod("removeWorkSpace", &WebContents::RemoveWorkSpace) .SetProperty("session", &WebContents::Session) .Build()); diff --git a/atom/browser/api/atom_api_web_contents.h b/atom/browser/api/atom_api_web_contents.h index bb6b22ac84e..2fbc1d89977 100644 --- a/atom/browser/api/atom_api_web_contents.h +++ b/atom/browser/api/atom_api_web_contents.h @@ -37,8 +37,8 @@ class WebContents : public mate::TrackableObject, public content::WebContentsObserver { public: // For node.js callback function type: function(error, buffer) - typedef base::Callback, v8::Local)> - PrintToPDFCallback; + using PrintToPDFCallback = + base::Callback, v8::Local)>; // Create from an existing WebContents. static mate::Handle CreateFrom( @@ -63,8 +63,10 @@ class WebContents : public mate::TrackableObject, void GoToOffset(int offset); bool IsCrashed() const; void SetUserAgent(const std::string& user_agent); + std::string GetUserAgent(); void InsertCSS(const std::string& css); - void ExecuteJavaScript(const base::string16& code); + void ExecuteJavaScript(const base::string16& code, + bool has_user_gesture); void OpenDevTools(mate::Arguments* args); void CloseDevTools(); bool IsDevToolsOpened(); @@ -82,6 +84,10 @@ class WebContents : public mate::TrackableObject, void PrintToPDF(const base::DictionaryValue& setting, const PrintToPDFCallback& callback); + // DevTools workspace api. + void AddWorkSpace(const base::FilePath& path); + void RemoveWorkSpace(const base::FilePath& path); + // Editing commands. void Undo(); void Redo(); @@ -95,6 +101,10 @@ class WebContents : public mate::TrackableObject, void Replace(const base::string16& word); void ReplaceMisspelling(const base::string16& word); + // Focus. + void Focus(); + void TabTraverse(bool reverse); + // Sending messages to browser. bool SendIPCMessage(const base::string16& channel, const base::ListValue& args); diff --git a/atom/browser/api/atom_api_window.cc b/atom/browser/api/atom_api_window.cc index d5bce589113..e6d16fe6fa5 100644 --- a/atom/browser/api/atom_api_window.cc +++ b/atom/browser/api/atom_api_window.cc @@ -8,18 +8,43 @@ #include "atom/browser/api/atom_api_web_contents.h" #include "atom/browser/browser.h" #include "atom/browser/native_window.h" +#include "atom/common/native_mate_converters/callback.h" #include "atom/common/native_mate_converters/gfx_converter.h" #include "atom/common/native_mate_converters/gurl_converter.h" #include "atom/common/native_mate_converters/image_converter.h" #include "atom/common/native_mate_converters/string16_converter.h" #include "content/public/browser/render_process_host.h" -#include "native_mate/callback.h" #include "native_mate/constructor.h" #include "native_mate/dictionary.h" #include "ui/gfx/geometry/rect.h" +#if defined(OS_WIN) +#include "atom/browser/native_window_views.h" +#include "atom/browser/ui/win/taskbar_host.h" +#endif + #include "atom/common/node_includes.h" +#if defined(OS_WIN) +namespace mate { + +template<> +struct Converter { + static bool FromV8(v8::Isolate* isolate, v8::Handle val, + atom::TaskbarHost::ThumbarButton* out) { + mate::Dictionary dict; + if (!ConvertFromV8(isolate, val, &dict)) + return false; + dict.Get("click", &(out->clicked_callback)); + dict.Get("tooltip", &(out->tooltip)); + dict.Get("flags", &out->flags); + return dict.Get("icon", &(out->icon)); + } +}; + +} // namespace mate +#endif + namespace atom { namespace api { @@ -413,6 +438,21 @@ void Window::SetOverlayIcon(const gfx::Image& overlay, window_->SetOverlayIcon(overlay, description); } +bool Window::SetThumbarButtons(mate::Arguments* args) { +#if defined(OS_WIN) + std::vector buttons; + if (!args->GetNext(&buttons)) { + args->ThrowError(); + return false; + } + auto window = static_cast(window_.get()); + return window->taskbar_host().SetThumbarButtons( + window->GetAcceleratedWidget(), buttons); +#else + return false; +#endif +} + void Window::SetMenu(v8::Isolate* isolate, v8::Local value) { mate::Handle menu; if (value->IsObject() && @@ -451,6 +491,12 @@ void Window::ShowDefinitionForSelection() { } #endif +void Window::SetAspectRatio(double aspect_ratio, mate::Arguments* args) { + gfx::Size extra_size; + args->GetNext(&extra_size); + window_->SetAspectRatio(aspect_ratio, extra_size); +} + void Window::SetVisibleOnAllWorkspaces(bool visible) { return window_->SetVisibleOnAllWorkspaces(visible); } @@ -498,6 +544,7 @@ void Window::BuildPrototype(v8::Isolate* isolate, .SetMethod("isMinimized", &Window::IsMinimized) .SetMethod("setFullScreen", &Window::SetFullScreen) .SetMethod("isFullScreen", &Window::IsFullscreen) + .SetMethod("setAspectRatio", &Window::SetAspectRatio) .SetMethod("getBounds", &Window::GetBounds) .SetMethod("setBounds", &Window::SetBounds) .SetMethod("getSize", &Window::GetSize) @@ -531,6 +578,7 @@ void Window::BuildPrototype(v8::Isolate* isolate, .SetMethod("capturePage", &Window::CapturePage) .SetMethod("setProgressBar", &Window::SetProgressBar) .SetMethod("setOverlayIcon", &Window::SetOverlayIcon) + .SetMethod("setThumbarButtons", &Window::SetThumbarButtons) .SetMethod("setMenu", &Window::SetMenu) .SetMethod("setAutoHideMenuBar", &Window::SetAutoHideMenuBar) .SetMethod("isMenuBarAutoHide", &Window::IsMenuBarAutoHide) diff --git a/atom/browser/api/atom_api_window.h b/atom/browser/api/atom_api_window.h index 5cdb49e41bb..de2f093ea3e 100644 --- a/atom/browser/api/atom_api_window.h +++ b/atom/browser/api/atom_api_window.h @@ -11,6 +11,7 @@ #include "base/memory/scoped_ptr.h" #include "ui/gfx/image/image.h" #include "atom/browser/api/trackable_object.h" +#include "atom/browser/native_window.h" #include "atom/browser/native_window_observer.h" #include "native_mate/handle.h" @@ -129,11 +130,13 @@ class Window : public mate::TrackableObject, void SetProgressBar(double progress); void SetOverlayIcon(const gfx::Image& overlay, const std::string& description); + bool SetThumbarButtons(mate::Arguments* args); void SetMenu(v8::Isolate* isolate, v8::Local menu); void SetAutoHideMenuBar(bool auto_hide); bool IsMenuBarAutoHide(); void SetMenuBarVisibility(bool visible); bool IsMenuBarVisible(); + void SetAspectRatio(double aspect_ratio, mate::Arguments* args); #if defined(OS_MACOSX) void ShowDefinitionForSelection(); diff --git a/atom/browser/api/event_emitter.cc b/atom/browser/api/event_emitter.cc index 7337830780f..aa0b6c8781a 100644 --- a/atom/browser/api/event_emitter.cc +++ b/atom/browser/api/event_emitter.cc @@ -37,9 +37,8 @@ v8::Local CreateEventObject(v8::Isolate* isolate) { EventEmitter::EventEmitter() { } -v8::Local EventEmitter::CreateJSEvent(v8::Isolate* isolate, - content::WebContents* sender, - IPC::Message* message) { +v8::Local EventEmitter::CreateJSEvent( + v8::Isolate* isolate, content::WebContents* sender, IPC::Message* message) { v8::Local event; bool use_native_event = sender && message; @@ -54,4 +53,12 @@ v8::Local EventEmitter::CreateJSEvent(v8::Isolate* isolate, return event; } +v8::Local EventEmitter::CreateCustomEvent( + v8::Isolate* isolate, v8::Local custom_event) { + v8::Local event = CreateEventObject(isolate); + event->SetPrototype(custom_event->CreationContext(), custom_event); + mate::Dictionary(isolate, event).Set("sender", GetWrapper(isolate)); + return event; +} + } // namespace mate diff --git a/atom/browser/api/event_emitter.h b/atom/browser/api/event_emitter.h index 6910df3a28d..4fb953b6399 100644 --- a/atom/browser/api/event_emitter.h +++ b/atom/browser/api/event_emitter.h @@ -7,7 +7,7 @@ #include -#include "atom/common/event_emitter_caller.h" +#include "atom/common/api/event_emitter_caller.h" #include "native_mate/wrappable.h" namespace content { @@ -25,6 +25,14 @@ class EventEmitter : public Wrappable { public: typedef std::vector> ValueArray; + // this.emit(name, event, args...); + template + bool EmitCustomEvent(const base::StringPiece& name, + v8::Local event, + const Args&... args) { + return EmitWithEvent(name, CreateCustomEvent(isolate(), event), args...); + } + // this.emit(name, new Event(), args...); template bool Emit(const base::StringPiece& name, const Args&... args) { @@ -37,21 +45,31 @@ class EventEmitter : public Wrappable { content::WebContents* sender, IPC::Message* message, const Args&... args) { - v8::Locker locker(isolate()); - v8::HandleScope handle_scope(isolate()); v8::Local event = CreateJSEvent(isolate(), sender, message); - EmitEvent(isolate(), GetWrapper(isolate()), name, event, args...); - return event->Get( - StringToV8(isolate(), "defaultPrevented"))->BooleanValue(); + return EmitWithEvent(name, event, args...); } protected: EventEmitter(); private: + // this.emit(name, event, args...); + template + bool EmitWithEvent(const base::StringPiece& name, + v8::Local event, + const Args&... args) { + v8::Locker locker(isolate()); + v8::HandleScope handle_scope(isolate()); + EmitEvent(isolate(), GetWrapper(isolate()), name, event, args...); + return event->Get( + StringToV8(isolate(), "defaultPrevented"))->BooleanValue(); + } + v8::Local CreateJSEvent(v8::Isolate* isolate, content::WebContents* sender, IPC::Message* message); + v8::Local CreateCustomEvent( + v8::Isolate* isolate, v8::Local event); DISALLOW_COPY_AND_ASSIGN(EventEmitter); }; diff --git a/atom/browser/api/lib/browser-window.coffee b/atom/browser/api/lib/browser-window.coffee index e7a79d99fcb..68b5932a93b 100644 --- a/atom/browser/api/lib/browser-window.coffee +++ b/atom/browser/api/lib/browser-window.coffee @@ -30,6 +30,15 @@ BrowserWindow::_init = -> @webContents.on 'crashed', => @emit 'crashed' + # Sometimes the webContents doesn't get focus when window is shown, so we have + # to force focusing on webContents in this case. The safest way is to focus it + # when we first start to load URL, if we do it earlier it won't have effect, + # if we do it later we might move focus in the page. + # Though this hack is only needed on OS X when the app is launched from + # Finder, we still do it on all platforms in case of other bugs we don't know. + @webContents.once 'load-url', -> + @focus() + # Redirect focus/blur event to app instance too. @on 'blur', (event) => app.emit 'browser-window-blur', event, this diff --git a/atom/browser/api/lib/content-tracing.coffee b/atom/browser/api/lib/content-tracing.coffee index 774661a1b86..08cd36e4aa5 100644 --- a/atom/browser/api/lib/content-tracing.coffee +++ b/atom/browser/api/lib/content-tracing.coffee @@ -1,7 +1 @@ module.exports = process.atomBinding 'content_tracing' - -# Mirrored from content::TracingController::Options -module.exports.DEFAULT_OPTIONS = 0 -module.exports.ENABLE_SYSTRACE = 1 << 0 -module.exports.ENABLE_SAMPLING = 1 << 1 -module.exports.RECORD_CONTINUOUSLY = 1 << 2 diff --git a/atom/browser/api/lib/dialog.coffee b/atom/browser/api/lib/dialog.coffee index 7a60b7f846e..0843af04282 100644 --- a/atom/browser/api/lib/dialog.coffee +++ b/atom/browser/api/lib/dialog.coffee @@ -11,6 +11,9 @@ fileDialogProperties = messageBoxTypes = ['none', 'info', 'warning', 'error', 'question'] +messageBoxOptions = + noLink: 1 << 0 + parseArgs = (window, options, callback) -> unless window is null or window?.constructor is BrowserWindow # Shift. @@ -101,10 +104,15 @@ module.exports = options.cancelId = i break + flags = if options.noLink then messageBoxOptions.noLink else 0 + binding.showMessageBox messageBoxType, options.buttons, options.cancelId, - [options.title, options.message, options.detail], + flags, + options.title, + options.message, + options.detail, options.icon, window, callback diff --git a/atom/browser/api/lib/navigation-controller.coffee b/atom/browser/api/lib/navigation-controller.coffee index 3c8089d9c6f..a985931f471 100644 --- a/atom/browser/api/lib/navigation-controller.coffee +++ b/atom/browser/api/lib/navigation-controller.coffee @@ -40,6 +40,7 @@ class NavigationController loadUrl: (url, options={}) -> @pendingIndex = -1 @webContents._loadUrl url, options + @webContents.emit 'load-url', url, options getUrl: -> if @currentIndex is -1 diff --git a/atom/browser/api/lib/protocol.coffee b/atom/browser/api/lib/protocol.coffee index 6e22329318b..4a661523509 100644 --- a/atom/browser/api/lib/protocol.coffee +++ b/atom/browser/api/lib/protocol.coffee @@ -59,6 +59,6 @@ class RequestErrorJob protocol.RequestHttpJob = class RequestHttpJob - constructor: ({@url, @method, @referrer}) -> + constructor: ({@session, @url, @method, @referrer}) -> module.exports = protocol diff --git a/atom/browser/api/lib/tray.coffee b/atom/browser/api/lib/tray.coffee index 7d158a9a010..1c225ddd403 100644 --- a/atom/browser/api/lib/tray.coffee +++ b/atom/browser/api/lib/tray.coffee @@ -3,8 +3,12 @@ bindings = process.atomBinding 'tray' Tray = bindings.Tray Tray::__proto__ = EventEmitter.prototype + Tray::setContextMenu = (menu) -> @_setContextMenu menu @menu = menu # Keep a strong reference of menu. +# Keep compatibility with old APIs. +Tray::popContextMenu = Tray::popUpContextMenu + module.exports = Tray diff --git a/atom/browser/api/lib/web-contents.coffee b/atom/browser/api/lib/web-contents.coffee index b2232b72cda..06615e31608 100644 --- a/atom/browser/api/lib/web-contents.coffee +++ b/atom/browser/api/lib/web-contents.coffee @@ -6,6 +6,34 @@ ipc = require 'ipc' nextId = 0 getNextId = -> ++nextId +PDFPageSize = + A4: + custom_display_name: "A4" + height_microns: 297000 + name: "ISO_A4" + is_default: "true" + width_microns: 210000 + A3: + custom_display_name: "A3" + height_microns: 420000 + name: "ISO_A3" + width_microns: 297000 + Legal: + custom_display_name: "Legal" + height_microns: 355600 + name: "NA_LEGAL" + width_microns: 215900 + Letter: + custom_display_name: "Letter" + height_microns: 279400 + name: "NA_LETTER" + width_microns: 215900 + Tabloid: + height_microns: 431800 + name: "NA_LEDGER" + width_microns: 279400 + custom_display_name: "Tabloid" + wrapWebContents = (webContents) -> # webContents is an EventEmitter. webContents.__proto__ = EventEmitter.prototype @@ -18,11 +46,11 @@ wrapWebContents = (webContents) -> # web contents has been loaded. webContents.loaded = false webContents.once 'did-finish-load', -> @loaded = true - webContents.executeJavaScript = (code) -> + webContents.executeJavaScript = (code, hasUserGesture=false) -> if @loaded - @_executeJavaScript code + @_executeJavaScript code, hasUserGesture else - webContents.once 'did-finish-load', @_executeJavaScript.bind(this, code) + webContents.once 'did-finish-load', @_executeJavaScript.bind(this, code, hasUserGesture) # The navigation controller. controller = new NavigationController(webContents) @@ -41,32 +69,27 @@ wrapWebContents = (webContents) -> webContents.printToPDF = (options, callback) -> printingSetting = - pageRage:[], - mediaSize: - height_microns:297000, - is_default:true, - name:"ISO_A4", - width_microns:210000, - custom_display_name:"A4", - landscape:false, - color:2, - headerFooterEnabled:false, - marginsType:0, - isFirstRequest:false, - requestID: getNextId(), - previewModifiable:true, - printToPDF:true, - printWithCloudPrint:false, - printWithPrivet:false, - printWithExtension:false, - deviceName:"Save as PDF", - generateDraftData:true, - fitToPageEnabled:false, - duplex:0, - copies:1, - collate:true, - shouldPrintBackgrounds:false, - shouldPrintSelectionOnly:false + pageRage: [] + mediaSize: {} + landscape: false + color: 2 + headerFooterEnabled: false + marginsType: 0 + isFirstRequest: false + requestID: getNextId() + previewModifiable: true + printToPDF: true + printWithCloudPrint: false + printWithPrivet: false + printWithExtension: false + deviceName: "Save as PDF" + generateDraftData: true + fitToPageEnabled: false + duplex: 0 + copies: 1 + collate: true + shouldPrintBackgrounds: false + shouldPrintSelectionOnly: false if options.landscape printingSetting.landscape = options.landscape @@ -77,6 +100,11 @@ wrapWebContents = (webContents) -> if options.printBackgrounds printingSetting.shouldPrintBackgrounds = options.printBackground + if options.pageSize and PDFPageSize[options.pageSize] + printingSetting.mediaSize = PDFPageSize[options.pageSize] + else + printingSetting.mediaSize = PDFPageSize['A4'] + @_printToPDF printingSetting, callback binding._setWrapWebContents wrapWebContents diff --git a/atom/browser/atom_browser_client.cc b/atom/browser/atom_browser_client.cc index f98f2b2a887..65fd7cd031d 100644 --- a/atom/browser/atom_browser_client.cc +++ b/atom/browser/atom_browser_client.cc @@ -4,6 +4,10 @@ #include "atom/browser/atom_browser_client.h" +#if defined(OS_WIN) +#include +#endif + #include "atom/browser/atom_access_token_store.h" #include "atom/browser/atom_browser_context.h" #include "atom/browser/atom_browser_main_parts.h" @@ -190,10 +194,20 @@ void AtomBrowserClient::AppendExtraCommandLineSwitches( if (process_type != "renderer") return; + // The registered standard schemes. if (!g_custom_schemes.empty()) command_line->AppendSwitchASCII(switches::kRegisterStandardSchemes, g_custom_schemes); +#if defined(OS_WIN) + // Append --app-user-model-id. + PWSTR current_app_id; + if (SUCCEEDED(GetCurrentProcessExplicitAppUserModelID(¤t_app_id))) { + command_line->AppendSwitchNative(switches::kAppUserModelId, current_app_id); + CoTaskMemFree(current_app_id); + } +#endif + NativeWindow* window; WebViewManager::WebViewInfo info; ProcessOwner owner = GetProcessOwner(process_id, &window, &info); diff --git a/atom/browser/atom_browser_context.cc b/atom/browser/atom_browser_context.cc index 1874d5b03b1..f04fbca747e 100644 --- a/atom/browser/atom_browser_context.cc +++ b/atom/browser/atom_browser_context.cc @@ -15,10 +15,13 @@ #include "atom/common/chrome_version.h" #include "atom/common/options_switches.h" #include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/prefs/pref_registry_simple.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/threading/sequenced_worker_pool.h" #include "base/threading/worker_pool.h" +#include "chrome/common/pref_names.h" #include "content/public/browser/browser_thread.h" #include "content/public/common/url_constants.h" #include "content/public/common/user_agent.h" @@ -146,4 +149,11 @@ content::BrowserPluginGuestManager* AtomBrowserContext::GetGuestManager() { return guest_manager_.get(); } +void AtomBrowserContext::RegisterPrefs(PrefRegistrySimple* pref_registry) { + pref_registry->RegisterFilePathPref(prefs::kSelectFileLastDirectory, + base::FilePath()); + pref_registry->RegisterFilePathPref(prefs::kDownloadDefaultDirectory, + base::FilePath()); +} + } // namespace atom diff --git a/atom/browser/atom_browser_context.h b/atom/browser/atom_browser_context.h index 513cc86bca6..c1ff613b8c0 100644 --- a/atom/browser/atom_browser_context.h +++ b/atom/browser/atom_browser_context.h @@ -32,6 +32,9 @@ class AtomBrowserContext : public brightray::BrowserContext { content::DownloadManagerDelegate* GetDownloadManagerDelegate() override; content::BrowserPluginGuestManager* GetGuestManager() override; + // brightray::BrowserContext: + void RegisterPrefs(PrefRegistrySimple* pref_registry) override; + AtomURLRequestJobFactory* job_factory() const { return job_factory_; } private: diff --git a/atom/browser/atom_download_manager_delegate.cc b/atom/browser/atom_download_manager_delegate.cc index 46c4af2dc38..b573a396332 100644 --- a/atom/browser/atom_download_manager_delegate.cc +++ b/atom/browser/atom_download_manager_delegate.cc @@ -6,10 +6,13 @@ #include +#include "atom/browser/atom_browser_context.h" #include "atom/browser/native_window.h" #include "atom/browser/ui/file_dialog.h" #include "base/bind.h" #include "base/files/file_util.h" +#include "base/prefs/pref_service.h" +#include "chrome/common/pref_names.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/download_manager.h" @@ -77,6 +80,11 @@ void AtomDownloadManagerDelegate::OnDownloadPathGenerated( return; } + // Remeber the last selected download directory. + AtomBrowserContext* browser_context = static_cast( + download_manager_->GetBrowserContext()); + browser_context->prefs()->SetFilePath(prefs::kDownloadDefaultDirectory, + path.DirName()); callback.Run(path, content::DownloadItem::TARGET_DISPOSITION_PROMPT, content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, path); @@ -92,9 +100,14 @@ bool AtomDownloadManagerDelegate::DetermineDownloadTarget( const content::DownloadTargetCallback& callback) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); - if (default_download_path_.empty()) { + AtomBrowserContext* browser_context = static_cast( + download_manager_->GetBrowserContext()); + base::FilePath default_download_path = browser_context->prefs()->GetFilePath( + prefs::kDownloadDefaultDirectory); + // If users didn't set download path, use 'Downloads' directory by default. + if (default_download_path.empty()) { auto path = download_manager_->GetBrowserContext()->GetPath(); - default_download_path_ = path.Append(FILE_PATH_LITERAL("Downloads")); + default_download_path = path.Append(FILE_PATH_LITERAL("Downloads")); } if (!download->GetForcedFilePath().empty()) { @@ -118,7 +131,7 @@ bool AtomDownloadManagerDelegate::DetermineDownloadTarget( download->GetContentDisposition(), download->GetSuggestedFilename(), download->GetMimeType(), - default_download_path_, + default_download_path, download_path_callback)); return true; } diff --git a/atom/browser/atom_download_manager_delegate.h b/atom/browser/atom_download_manager_delegate.h index e2d82924329..2df3a7d45a6 100644 --- a/atom/browser/atom_download_manager_delegate.h +++ b/atom/browser/atom_download_manager_delegate.h @@ -47,7 +47,6 @@ class AtomDownloadManagerDelegate : public content::DownloadManagerDelegate { private: content::DownloadManager* download_manager_; - base::FilePath default_download_path_; base::WeakPtrFactory weak_ptr_factory_; DISALLOW_COPY_AND_ASSIGN(AtomDownloadManagerDelegate); diff --git a/atom/browser/common_web_contents_delegate.cc b/atom/browser/common_web_contents_delegate.cc index af429929812..3cef7c6d68c 100644 --- a/atom/browser/common_web_contents_delegate.cc +++ b/atom/browser/common_web_contents_delegate.cc @@ -274,16 +274,21 @@ void CommonWebContentsDelegate::DevToolsAppendToFile( base::Unretained(this), url)); } -void CommonWebContentsDelegate::DevToolsAddFileSystem() { - file_dialog::Filters filters; - base::FilePath default_path; - std::vector paths; - int flag = file_dialog::FILE_DIALOG_OPEN_DIRECTORY; - if (!file_dialog::ShowOpenDialog(owner_window(), "", default_path, - filters, flag, &paths)) - return; +void CommonWebContentsDelegate::DevToolsAddFileSystem( + const base::FilePath& file_system_path) { + base::FilePath path = file_system_path; + if (path.empty()) { + file_dialog::Filters filters; + base::FilePath default_path; + std::vector paths; + int flag = file_dialog::FILE_DIALOG_OPEN_DIRECTORY; + if (!file_dialog::ShowOpenDialog(owner_window(), "", default_path, + filters, flag, &paths)) + return; + + path = paths[0]; + } - base::FilePath path = paths[0]; std::string registered_name; std::string file_system_id = RegisterFileSystem(GetDevToolsWebContents(), path, @@ -313,20 +318,20 @@ void CommonWebContentsDelegate::DevToolsAddFileSystem() { } void CommonWebContentsDelegate::DevToolsRemoveFileSystem( - const std::string& file_system_path) { + const base::FilePath& file_system_path) { if (!web_contents_) return; - base::FilePath path = base::FilePath::FromUTF8Unsafe(file_system_path); - storage::IsolatedContext::GetInstance()->RevokeFileSystemByPath(path); + storage::IsolatedContext::GetInstance()-> + RevokeFileSystemByPath(file_system_path); for (auto it = saved_paths_.begin(); it != saved_paths_.end(); ++it) - if (it->second == path) { + if (it->second == file_system_path) { saved_paths_.erase(it); break; } - base::StringValue file_system_path_value(file_system_path); + base::StringValue file_system_path_value(file_system_path.AsUTF8Unsafe()); web_contents_->CallClientFunction( "DevToolsAPI.fileSystemRemoved", &file_system_path_value, diff --git a/atom/browser/common_web_contents_delegate.h b/atom/browser/common_web_contents_delegate.h index 8c87548951b..e21cd24e910 100644 --- a/atom/browser/common_web_contents_delegate.h +++ b/atom/browser/common_web_contents_delegate.h @@ -80,8 +80,9 @@ class CommonWebContentsDelegate bool save_as) override; void DevToolsAppendToFile(const std::string& url, const std::string& content) override; - void DevToolsAddFileSystem() override; - void DevToolsRemoveFileSystem(const std::string& file_system_path) override; + void DevToolsAddFileSystem(const base::FilePath& path) override; + void DevToolsRemoveFileSystem( + const base::FilePath& file_system_path) override; private: // Callback for when DevToolsSaveToFile has completed. diff --git a/atom/browser/default_app/main.js b/atom/browser/default_app/main.js index 838cb4c2bb2..fd3a6b596b6 100644 --- a/atom/browser/default_app/main.js +++ b/atom/browser/default_app/main.js @@ -237,7 +237,7 @@ app.once('ready', function() { }, { label: 'Toggle &Developer Tools', - accelerator: 'Alt+Ctrl+I', + accelerator: 'Shift+Ctrl+I', click: function() { var focusedWindow = BrowserWindow.getFocusedWindow(); if (focusedWindow) diff --git a/atom/browser/lib/guest-view-manager.coffee b/atom/browser/lib/guest-view-manager.coffee index 4385d389c39..971c15cb1b8 100644 --- a/atom/browser/lib/guest-view-manager.coffee +++ b/atom/browser/lib/guest-view-manager.coffee @@ -3,6 +3,7 @@ webContents = require 'web-contents' webViewManager = null # Doesn't exist in early initialization. supportedWebViewEvents = [ + 'load-commit' 'did-finish-load' 'did-fail-load' 'did-frame-finish-load' diff --git a/atom/browser/lib/guest-window-manager.coffee b/atom/browser/lib/guest-window-manager.coffee index 6f5040ce21d..f92c1a46c05 100644 --- a/atom/browser/lib/guest-window-manager.coffee +++ b/atom/browser/lib/guest-window-manager.coffee @@ -64,3 +64,6 @@ ipc.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPENER_POSTMESSAGE', (event, mess ipc.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', (event, guestId, method, args...) -> BrowserWindow.fromId(guestId)?.webContents?[method] args... + +ipc.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_IS_GUEST_WINDOW', (event) -> + event.returnValue = v8Util.getHiddenValue(event.sender, 'embedder') isnt undefined diff --git a/atom/browser/lib/init.coffee b/atom/browser/lib/init.coffee index 678d7422b0c..069eed8d1b5 100644 --- a/atom/browser/lib/init.coffee +++ b/atom/browser/lib/init.coffee @@ -38,7 +38,7 @@ process.on 'uncaughtException', (error) -> # Show error in GUI. stack = error.stack ? "#{error.name}: #{error.message}" message = "Uncaught Exception:\n#{stack}" - require('dialog').showErrorBox 'A JavaScript error occured in the browser process', message + require('dialog').showErrorBox 'A JavaScript error occurred in the main process', message # Emit 'exit' event on quit. app = require 'app' diff --git a/atom/browser/lib/rpc-server.coffee b/atom/browser/lib/rpc-server.coffee index ab86a0c4551..3eba472570a 100644 --- a/atom/browser/lib/rpc-server.coffee +++ b/atom/browser/lib/rpc-server.coffee @@ -10,6 +10,7 @@ valueToMeta = (sender, value) -> meta.type = 'buffer' if Buffer.isBuffer value meta.type = 'value' if value is null meta.type = 'array' if Array.isArray value + meta.type = 'promise' if value? and value.constructor.name is 'Promise' # Treat the arguments object as array. meta.type = 'array' if meta.type is 'object' and value.callee? and value.length? @@ -29,6 +30,8 @@ valueToMeta = (sender, value) -> meta.members.push {name: prop, type: typeof field} for prop, field of value else if meta.type is 'buffer' meta.value = Array::slice.call value, 0 + else if meta.type is 'promise' + meta.then = valueToMeta(sender, value.then.bind(value)) else meta.type = 'value' meta.value = value @@ -47,6 +50,7 @@ unwrapArgs = (sender, args) -> when 'remote-object' then objectsRegistry.get meta.id when 'array' then unwrapArgs sender, meta.value when 'buffer' then new Buffer(meta.value) + when 'promise' then Promise.resolve(then: metaToValue(meta.then)) when 'object' ret = v8Util.createObjectWithName meta.name for member in meta.members diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index 2a093cc8120..96085846bca 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -8,10 +8,6 @@ #include #include -#if defined(OS_WIN) -#include -#endif - #include "atom/browser/atom_browser_context.h" #include "atom/browser/atom_browser_main_parts.h" #include "atom/browser/window_list.h" @@ -72,6 +68,22 @@ const char* kWebRuntimeFeatures[] = { switches::kPageVisibility, }; +// Convert draggable regions in raw format to SkRegion format. Caller is +// responsible for deleting the returned SkRegion instance. +scoped_ptr DraggableRegionsToSkRegion( + const std::vector& regions) { + scoped_ptr sk_region(new SkRegion); + for (const DraggableRegion& region : regions) { + sk_region->op( + region.bounds.x(), + region.bounds.y(), + region.bounds.right(), + region.bounds.bottom(), + region.draggable ? SkRegion::kUnion_Op : SkRegion::kDifference_Op); + } + return sk_region.Pass(); +} + } // namespace NativeWindow::NativeWindow( @@ -85,6 +97,7 @@ NativeWindow::NativeWindow( node_integration_(true), has_dialog_attached_(false), zoom_factor_(1.0), + aspect_ratio_(0.0), inspectable_web_contents_(inspectable_web_contents), weak_factory_(this) { inspectable_web_contents->GetView()->SetDelegate(this); @@ -246,6 +259,20 @@ bool NativeWindow::IsMenuBarVisible() { return true; } +double NativeWindow::GetAspectRatio() { + return aspect_ratio_; +} + +gfx::Size NativeWindow::GetAspectRatioExtraSize() { + return aspect_ratio_extraSize_; +} + +void NativeWindow::SetAspectRatio(double aspect_ratio, + const gfx::Size& extra_size) { + aspect_ratio_ = aspect_ratio; + aspect_ratio_extraSize_ = extra_size; +} + bool NativeWindow::HasModalDialog() { return has_dialog_attached_; } @@ -369,15 +396,6 @@ void NativeWindow::AppendExtraCommandLineSwitches( command_line->AppendSwitchASCII(switches::kZoomFactor, base::DoubleToString(zoom_factor_)); -#if defined(OS_WIN) - // Append --app-user-model-id. - PWSTR current_app_id; - if (SUCCEEDED(GetCurrentProcessExplicitAppUserModelID(¤t_app_id))) { - command_line->AppendSwitchNative(switches::kAppUserModelId, current_app_id); - CoTaskMemFree(current_app_id); - } -#endif - if (web_preferences_.IsEmpty()) return; @@ -424,6 +442,10 @@ void NativeWindow::OverrideWebkitPrefs(content::WebPreferences* prefs) { prefs->allow_displaying_insecure_content = !b; prefs->allow_running_insecure_content = !b; } + if (web_preferences_.Get("allow-displaying-insecure-content", &b)) + prefs->allow_displaying_insecure_content = b; + if (web_preferences_.Get("allow-running-insecure-content", &b)) + prefs->allow_running_insecure_content = b; if (web_preferences_.Get("extra-plugin-dirs", &list)) { if (content::PluginService::GetInstance()->NPAPIPluginsSupported()) { for (size_t i = 0; i < list.size(); ++i) @@ -500,6 +522,12 @@ void NativeWindow::NotifyWindowLeaveHtmlFullScreen() { OnWindowLeaveHtmlFullScreen()); } +void NativeWindow::NotifyWindowExecuteWindowsCommand( + const std::string& command) { + FOR_EACH_OBSERVER(NativeWindowObserver, observers_, + OnExecuteWindowsCommand(command)); +} + void NativeWindow::DevToolsFocused() { FOR_EACH_OBSERVER(NativeWindowObserver, observers_, OnDevToolsFocus()); } @@ -534,7 +562,7 @@ void NativeWindow::BeforeUnloadDialogCancelled() { void NativeWindow::TitleWasSet(content::NavigationEntry* entry, bool explicit_set) { bool prevent_default = false; - std::string text = base::UTF16ToUTF8(entry->GetTitle()); + std::string text = entry ? base::UTF16ToUTF8(entry->GetTitle()) : ""; FOR_EACH_OBSERVER(NativeWindowObserver, observers_, OnPageTitleUpdated(&prevent_default, text)); @@ -553,6 +581,14 @@ bool NativeWindow::OnMessageReceived(const IPC::Message& message) { return handled; } +void NativeWindow::UpdateDraggableRegions( + const std::vector& regions) { + // Draggable region is not supported for non-frameless window. + if (has_frame_) + return; + draggable_region_ = DraggableRegionsToSkRegion(regions); +} + void NativeWindow::ScheduleUnresponsiveEvent(int ms) { if (!window_unresposive_closure_.IsCancelled()) return; diff --git a/atom/browser/native_window.h b/atom/browser/native_window.h index 0ac7aa50c91..b9294d38c93 100644 --- a/atom/browser/native_window.h +++ b/atom/browser/native_window.h @@ -23,6 +23,8 @@ #include "ui/gfx/image/image.h" #include "ui/gfx/image/image_skia.h" +class SkRegion; + namespace base { class CommandLine; } @@ -57,7 +59,7 @@ struct DraggableRegion; class NativeWindow : public content::WebContentsObserver, public brightray::InspectableWebContentsViewDelegate { public: - typedef base::Callback CapturePageCallback; + using CapturePageCallback = base::Callback; class DialogScope { public: @@ -93,6 +95,7 @@ class NativeWindow : public content::WebContentsObserver, virtual void Close() = 0; virtual void CloseImmediately() = 0; + virtual bool IsClosed() const { return is_closed_; } virtual void Focus(bool focus) = 0; virtual bool IsFocused() = 0; virtual void Show() = 0; @@ -137,14 +140,17 @@ class NativeWindow : public content::WebContentsObserver, virtual void SetMenu(ui::MenuModel* menu); virtual bool HasModalDialog(); virtual gfx::NativeWindow GetNativeWindow() = 0; + + // Taskbar/Dock APIs. virtual void SetProgressBar(double progress) = 0; virtual void SetOverlayIcon(const gfx::Image& overlay, const std::string& description) = 0; + + // Workspace APIs. virtual void SetVisibleOnAllWorkspaces(bool visible) = 0; virtual bool IsVisibleOnAllWorkspaces() = 0; - virtual bool IsClosed() const { return is_closed_; } - + // Webview APIs. virtual void FocusOnWebView(); virtual void BlurWebView(); virtual bool IsWebViewFocused(); @@ -163,6 +169,11 @@ class NativeWindow : public content::WebContentsObserver, virtual void SetMenuBarVisibility(bool visible); virtual bool IsMenuBarVisible(); + // Set the aspect ratio when resizing window. + double GetAspectRatio(); + gfx::Size GetAspectRatioExtraSize(); + void SetAspectRatio(double aspect_ratio, const gfx::Size& extra_size); + base::WeakPtr GetWeakPtr() { return weak_factory_.GetWeakPtr(); } @@ -198,11 +209,11 @@ class NativeWindow : public content::WebContentsObserver, void NotifyWindowLeaveFullScreen(); void NotifyWindowEnterHtmlFullScreen(); void NotifyWindowLeaveHtmlFullScreen(); + void NotifyWindowExecuteWindowsCommand(const std::string& command); void AddObserver(NativeWindowObserver* obs) { observers_.AddObserver(obs); } - void RemoveObserver(NativeWindowObserver* obs) { observers_.RemoveObserver(obs); } @@ -212,6 +223,10 @@ class NativeWindow : public content::WebContentsObserver, } bool has_frame() const { return has_frame_; } + bool transparent() const { return transparent_; } + SkRegion* draggable_region() const { return draggable_region_.get(); } + bool enable_larger_than_screen() const { return enable_larger_than_screen_; } + gfx::ImageSkia icon() const { return icon_; } void set_has_dialog_attached(bool has_dialog_attached) { has_dialog_attached_ = has_dialog_attached; @@ -221,10 +236,6 @@ class NativeWindow : public content::WebContentsObserver, NativeWindow(brightray::InspectableWebContents* inspectable_web_contents, const mate::Dictionary& options); - // Called when the window needs to update its draggable region. - virtual void UpdateDraggableRegions( - const std::vector& regions) = 0; - // brightray::InspectableWebContentsViewDelegate: void DevToolsFocused() override; void DevToolsOpened() override; @@ -236,22 +247,11 @@ class NativeWindow : public content::WebContentsObserver, void TitleWasSet(content::NavigationEntry* entry, bool explicit_set) override; bool OnMessageReceived(const IPC::Message& message) override; - // Whether window has standard frame. - bool has_frame_; - - // Whether window is transparent. - bool transparent_; - - // Whether window can be resized larger than screen. - bool enable_larger_than_screen_; - - // Window icon. - gfx::ImageSkia icon_; - - // Observers of this window. - ObserverList observers_; - private: + // Called when the window needs to update its draggable region. + void UpdateDraggableRegions( + const std::vector& regions); + // Schedule a notification unresponsive event. void ScheduleUnresponsiveEvent(int ms); @@ -263,6 +263,22 @@ class NativeWindow : public content::WebContentsObserver, const SkBitmap& bitmap, content::ReadbackResponse response); + // Whether window has standard frame. + bool has_frame_; + + // Whether window is transparent. + bool transparent_; + + // For custom drag, the whole window is non-draggable and the draggable region + // has to been explicitly provided. + scoped_ptr draggable_region_; // used in custom drag. + + // Whether window can be resized larger than screen. + bool enable_larger_than_screen_; + + // Window icon. + gfx::ImageSkia icon_; + // The windows has been closed. bool is_closed_; @@ -285,9 +301,17 @@ class NativeWindow : public content::WebContentsObserver, // Page's default zoom factor. double zoom_factor_; + // Used to maintain the aspect ratio of a view which is inside of the + // content view. + double aspect_ratio_; + gfx::Size aspect_ratio_extraSize_; + // The page this window is viewing. brightray::InspectableWebContents* inspectable_web_contents_; + // Observers of this window. + ObserverList observers_; + base::WeakPtrFactory weak_factory_; DISALLOW_COPY_AND_ASSIGN(NativeWindow); diff --git a/atom/browser/native_window_mac.h b/atom/browser/native_window_mac.h index 67f4389ff70..e54dc6ad87f 100644 --- a/atom/browser/native_window_mac.h +++ b/atom/browser/native_window_mac.h @@ -11,13 +11,11 @@ #include #include "base/mac/scoped_nsobject.h" -#include "base/memory/scoped_ptr.h" #include "atom/browser/native_window.h" @class AtomNSWindow; @class AtomNSWindowDelegate; @class FullSizeContentView; -class SkRegion; namespace atom { @@ -88,9 +86,6 @@ class NativeWindowMac : public NativeWindow { void ClipWebView(); protected: - void UpdateDraggableRegions( - const std::vector& regions) override; - // NativeWindow: void HandleKeyboardEvent( content::WebContents*, @@ -117,10 +112,6 @@ class NativeWindowMac : public NativeWindow { // The presentation options before entering kiosk mode. NSApplicationPresentationOptions kiosk_options_; - // For custom drag, the whole window is non-draggable and the draggable region - // has to been explicitly provided. - scoped_ptr draggable_region_; // used in custom drag. - // Mouse location since the last mouse event, in screen coordinates. This is // used in custom drag to compute the window movement. NSPoint last_mouse_offset_; diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index c0d358bb8f7..b0649c4a80c 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -20,7 +20,26 @@ #include "content/public/browser/render_widget_host_view.h" #include "native_mate/dictionary.h" -static const CGFloat kAtomWindowCornerRadius = 4.0; +namespace { + +// The radius of rounded corner. +const CGFloat kAtomWindowCornerRadius = 4.0; + +// Prevents window from resizing during the scope. +class ScopedDisableResize { + public: + ScopedDisableResize() { disable_resize_ = true; } + ~ScopedDisableResize() { disable_resize_ = false; } + + static bool IsResizeDisabled() { return disable_resize_; } + + private: + static bool disable_resize_; +}; + +bool ScopedDisableResize::disable_resize_ = false; + +} // namespace @interface NSView (PrivateMethods) - (CGFloat)roundedCornerRadius; @@ -95,6 +114,44 @@ static const CGFloat kAtomWindowCornerRadius = 4.0; shell_->NotifyWindowBlur(); } +- (NSSize)windowWillResize:(NSWindow*)sender toSize:(NSSize)frameSize { + NSSize newSize = frameSize; + double aspectRatio = shell_->GetAspectRatio(); + + if (aspectRatio > 0.0) { + gfx::Size windowSize = shell_->GetSize(); + gfx::Size contentSize = shell_->GetContentSize(); + gfx::Size extraSize = shell_->GetAspectRatioExtraSize(); + + double extraWidthPlusFrame = + windowSize.width() - contentSize.width() + extraSize.width(); + double extraHeightPlusFrame = + windowSize.height() - contentSize.height() + extraSize.height(); + + newSize.width = + roundf((frameSize.height - extraHeightPlusFrame) * aspectRatio + + extraWidthPlusFrame); + + // If the new width is less than the frame size use it as the primary + // constraint. This ensures that the value returned by this method will + // never be larger than the users requested window size. + if (newSize.width <= frameSize.width) { + newSize.height = + roundf((newSize.width - extraWidthPlusFrame) / aspectRatio + + extraHeightPlusFrame); + } else { + newSize.height = + roundf((frameSize.width - extraWidthPlusFrame) / aspectRatio + + extraHeightPlusFrame); + newSize.width = + roundf((newSize.height - extraHeightPlusFrame) * aspectRatio + + extraWidthPlusFrame); + } + } + + return newSize; +} + - (void)windowDidResize:(NSNotification*)notification { if (!shell_->has_frame()) shell_->ClipWebView(); @@ -176,8 +233,12 @@ static const CGFloat kAtomWindowCornerRadius = 4.0; enable_larger_than_screen_ = enable; } -// Enable the window to be larger than screen. - (NSRect)constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen*)screen { + // Resizing is disabled. + if (ScopedDisableResize::IsResizeDisabled()) + return [self frame]; + + // Enable the window to be larger than screen. if (enable_larger_than_screen_) return frameRect; else @@ -280,29 +341,6 @@ static const CGFloat kAtomWindowCornerRadius = 4.0; namespace atom { -namespace { - -// Convert draggable regions in raw format to SkRegion format. Caller is -// responsible for deleting the returned SkRegion instance. -SkRegion* DraggableRegionsToSkRegion( - const std::vector& regions) { - SkRegion* sk_region = new SkRegion; - for (std::vector::const_iterator iter = regions.begin(); - iter != regions.end(); - ++iter) { - const DraggableRegion& region = *iter; - sk_region->op( - region.bounds.x(), - region.bounds.y(), - region.bounds.right(), - region.bounds.bottom(), - region.draggable ? SkRegion::kUnion_Op : SkRegion::kDifference_Op); - } - return sk_region; -} - -} // namespace - NativeWindowMac::NativeWindowMac( brightray::InspectableWebContents* web_contents, const mate::Dictionary& options) @@ -325,7 +363,7 @@ NativeWindowMac::NativeWindowMac( NSUInteger styleMask = NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask; - if (!useStandardWindow || transparent_ || !has_frame_) { + if (!useStandardWindow || transparent() || !has_frame()) { styleMask |= NSTexturedBackgroundWindowMask; } @@ -335,12 +373,12 @@ NativeWindowMac::NativeWindowMac( backing:NSBackingStoreBuffered defer:YES]); [window_ setShell:this]; - [window_ setEnableLargerThanScreen:enable_larger_than_screen_]; + [window_ setEnableLargerThanScreen:enable_larger_than_screen()]; window_delegate_.reset([[AtomNSWindowDelegate alloc] initWithShell:this]); [window_ setDelegate:window_delegate_]; - if (transparent_) { + if (transparent()) { // Make window has transparent background. [window_ setOpaque:NO]; [window_ setHasShadow:NO]; @@ -348,7 +386,7 @@ NativeWindowMac::NativeWindowMac( } // Remove non-transparent corners, see http://git.io/vfonD. - if (!has_frame_) + if (!has_frame()) [window_ setOpaque:NO]; // We will manage window's lifetime ourselves. @@ -357,7 +395,7 @@ NativeWindowMac::NativeWindowMac( // On OS X the initial window size doesn't include window frame. bool use_content_size = false; options.Get(switches::kUseContentSize, &use_content_size); - if (has_frame_ && !use_content_size) + if (!has_frame() || !use_content_size) SetSize(gfx::Size(width, height)); // Enable the NSView to accept first mouse event. @@ -494,6 +532,11 @@ gfx::Rect NativeWindowMac::GetBounds() { } void NativeWindowMac::SetContentSize(const gfx::Size& size) { + if (!has_frame()) { + SetSize(size); + return; + } + NSRect frame_nsrect = [window_ frame]; NSSize frame = frame_nsrect.size; NSSize content = [window_ contentRectForFrameRect:frame_nsrect].size; @@ -507,6 +550,9 @@ void NativeWindowMac::SetContentSize(const gfx::Size& size) { } gfx::Size NativeWindowMac::GetContentSize() { + if (!has_frame()) + return GetSize(); + NSRect bounds = [[window_ contentView] bounds]; return gfx::Size(bounds.size.width, bounds.size.height); } @@ -538,12 +584,15 @@ gfx::Size NativeWindowMac::GetMaximumSize() { } void NativeWindowMac::SetResizable(bool resizable) { + // Change styleMask for frameless causes the window to change size, so we have + // to explicitly disables that. + ScopedDisableResize disable_resize; if (resizable) { [[window_ standardWindowButton:NSWindowZoomButton] setEnabled:YES]; [window_ setStyleMask:[window_ styleMask] | NSResizableWindowMask]; } else { [[window_ standardWindowButton:NSWindowZoomButton] setEnabled:NO]; - [window_ setStyleMask:[window_ styleMask] ^ NSResizableWindowMask]; + [window_ setStyleMask:[window_ styleMask] & (~NSResizableWindowMask)]; } } @@ -565,7 +614,7 @@ void NativeWindowMac::Center() { void NativeWindowMac::SetTitle(const std::string& title) { // We don't want the title to show in transparent window. - if (transparent_) + if (transparent()) return; [window_ setTitle:base::SysUTF8ToNSString(title)]; @@ -701,7 +750,7 @@ bool NativeWindowMac::IsVisibleOnAllWorkspaces() { } bool NativeWindowMac::IsWithinDraggableRegion(NSPoint point) const { - if (!draggable_region_) + if (!draggable_region()) return false; if (!web_contents()) return false; @@ -710,7 +759,7 @@ bool NativeWindowMac::IsWithinDraggableRegion(NSPoint point) const { // |draggable_region_| is stored in local platform-indepdent coordiate system // while |point| is in local Cocoa coordinate system. Do the conversion // to match these two. - return draggable_region_->contains(point.x, webViewHeight - point.y); + return draggable_region()->contains(point.x, webViewHeight - point.y); } void NativeWindowMac::HandleMouseEvent(NSEvent* event) { @@ -730,15 +779,6 @@ void NativeWindowMac::HandleMouseEvent(NSEvent* event) { } } -void NativeWindowMac::UpdateDraggableRegions( - const std::vector& regions) { - // Draggable region is not supported for non-frameless window. - if (has_frame_) - return; - - draggable_region_.reset(DraggableRegionsToSkRegion(regions)); -} - void NativeWindowMac::HandleKeyboardEvent( content::WebContents*, const content::NativeWebKeyboardEvent& event) { @@ -765,7 +805,7 @@ void NativeWindowMac::HandleKeyboardEvent( void NativeWindowMac::InstallView() { NSView* view = inspectable_web_contents()->GetView()->GetNativeView(); - if (has_frame_) { + if (has_frame()) { // Add layer with white background for the contents view. base::scoped_nsobject layer([[CALayer alloc] init]); [layer setBackgroundColor:CGColorGetConstantColor(kCGColorWhite)]; diff --git a/atom/browser/native_window_views.cc b/atom/browser/native_window_views.cc index 1de24bfa267..45697c96683 100644 --- a/atom/browser/native_window_views.cc +++ b/atom/browser/native_window_views.cc @@ -4,10 +4,6 @@ #include "atom/browser/native_window_views.h" -#if defined(OS_WIN) -#include -#endif - #include #include @@ -20,7 +16,6 @@ #include "brightray/browser/inspectable_web_contents_view.h" #include "content/public/browser/native_web_keyboard_event.h" #include "native_mate/dictionary.h" -#include "ui/aura/window.h" #include "ui/aura/window_tree_host.h" #include "ui/base/hit_test.h" #include "ui/gfx/image/image.h" @@ -36,26 +31,20 @@ #include "atom/browser/browser.h" #include "atom/browser/ui/views/global_menu_bar_x11.h" #include "atom/browser/ui/views/frameless_view.h" +#include "atom/browser/ui/views/native_frame_view.h" #include "atom/browser/ui/x/window_state_watcher.h" #include "atom/browser/ui/x/x_window_utils.h" -#include "base/environment.h" -#include "base/nix/xdg_util.h" #include "base/strings/string_util.h" #include "chrome/browser/ui/libgtk2ui/unity_service.h" -#include "dbus/bus.h" -#include "dbus/object_proxy.h" -#include "dbus/message.h" #include "ui/base/x/x11_util.h" #include "ui/gfx/x/x11_types.h" #include "ui/views/window/native_frame_view.h" #elif defined(OS_WIN) #include "atom/browser/ui/views/win_frame_view.h" -#include "base/win/scoped_comptr.h" -#include "base/win/windows_version.h" +#include "atom/browser/ui/win/atom_desktop_window_tree_host_win.h" #include "ui/base/win/shell.h" -#include "ui/gfx/icon_util.h" #include "ui/gfx/win/dpi.h" -#include "ui/views/win/hwnd_util.h" +#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h" #endif namespace atom { @@ -69,42 +58,6 @@ const int kMenuBarHeight = 20; const int kMenuBarHeight = 25; #endif -#if defined(USE_X11) -// Returns true if the bus name "com.canonical.AppMenu.Registrar" is available. -bool ShouldUseGlobalMenuBar() { - dbus::Bus::Options options; - scoped_refptr bus(new dbus::Bus(options)); - - dbus::ObjectProxy* object_proxy = - bus->GetObjectProxy(DBUS_SERVICE_DBUS, dbus::ObjectPath(DBUS_PATH_DBUS)); - dbus::MethodCall method_call(DBUS_INTERFACE_DBUS, "ListNames"); - scoped_ptr response(object_proxy->CallMethodAndBlock( - &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT)); - if (!response) { - bus->ShutdownAndBlock(); - return false; - } - - dbus::MessageReader reader(response.get()); - dbus::MessageReader array_reader(NULL); - if (!reader.PopArray(&array_reader)) { - bus->ShutdownAndBlock(); - return false; - } - while (array_reader.HasMoreData()) { - std::string name; - if (array_reader.PopString(&name) && - name == "com.canonical.AppMenu.Registrar") { - bus->ShutdownAndBlock(); - return true; - } - } - - bus->ShutdownAndBlock(); - return false; -} -#endif - bool IsAltKey(const content::NativeWebKeyboardEvent& event) { #if defined(USE_X11) // 164 and 165 represent VK_LALT and VK_RALT. @@ -183,7 +136,7 @@ const char* AppCommandToString(int command_id) { case APPCOMMAND_DICTATE_OR_COMMAND_CONTROL_TOGGLE: return "dictate-or-command-control-toggle"; default: - return "unkown"; + return "unknown"; } } #endif @@ -231,7 +184,7 @@ NativeWindowViews::NativeWindowViews( options.Get(switches::kResizable, &resizable_); #endif - if (enable_larger_than_screen_) + if (enable_larger_than_screen()) // We need to set a default maximum window size here otherwise Windows // will not allow us to resize the window larger than scree. // Setting directly to INT_MAX somehow doesn't work, so we just devide @@ -251,12 +204,20 @@ NativeWindowViews::NativeWindowViews( params.bounds = bounds; params.delegate = this; params.type = views::Widget::InitParams::TYPE_WINDOW; - params.remove_standard_frame = !has_frame_; + params.remove_standard_frame = !has_frame(); - if (transparent_) + if (transparent()) params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; -#if defined(USE_X11) +#if defined(OS_WIN) + params.native_widget = + new views::DesktopNativeWidgetAura(window_.get()); + atom_desktop_window_tree_host_win_ = new AtomDesktopWindowTreeHostWin( + this, + window_.get(), + static_cast(params.native_widget)); + params.desktop_window_tree_host = atom_desktop_window_tree_host_win_; +#elif defined(USE_X11) std::string name = Browser::Get()->GetName(); // Set WM_WINDOW_ROLE. params.wm_role_name = "browser-window"; @@ -311,24 +272,24 @@ NativeWindowViews::NativeWindowViews( set_background(views::Background::CreateStandardPanelBackground()); AddChildView(web_view_); - if (has_frame_ && + if (has_frame() && options.Get(switches::kUseContentSize, &use_content_size_) && use_content_size_) bounds = ContentBoundsToWindowBounds(bounds); #if defined(OS_WIN) - if (!has_frame_) { + if (!has_frame()) { // Set Window style so that we get a minimize and maximize animation when // frameless. DWORD frame_style = WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_CAPTION; // We should not show a frame for transparent window. - if (transparent_) + if (transparent()) frame_style &= ~(WS_THICKFRAME | WS_CAPTION); ::SetWindowLong(GetAcceleratedWidget(), GWL_STYLE, frame_style); } - if (transparent_) { + if (transparent()) { // Transparent window on Windows has to have WS_EX_COMPOSITED style. LONG ex_style = ::GetWindowLong(GetAcceleratedWidget(), GWL_EXSTYLE); ex_style |= WS_EX_COMPOSITED; @@ -338,14 +299,14 @@ NativeWindowViews::NativeWindowViews( // TODO(zcbenz): This was used to force using native frame on Windows 2003, we // should check whether setting it in InitParams can work. - if (has_frame_) { + if (has_frame()) { window_->set_frame_type(views::Widget::FrameType::FRAME_TYPE_FORCE_NATIVE); window_->FrameTypeChanged(); } // The given window is most likely not rectangular since it uses // transparency and has no standard frame, don't show a shadow for it. - if (transparent_ && !has_frame_) + if (transparent() && !has_frame()) wm::SetShadowType(GetNativeWindow(), wm::SHADOW_TYPE_NONE); window_->UpdateWindowIcon(); @@ -468,7 +429,7 @@ gfx::Rect NativeWindowViews::GetBounds() { } void NativeWindowViews::SetContentSize(const gfx::Size& size) { - if (!has_frame_) { + if (!has_frame()) { NativeWindow::SetSize(size); return; } @@ -479,7 +440,7 @@ void NativeWindowViews::SetContentSize(const gfx::Size& size) { } gfx::Size NativeWindowViews::GetContentSize() { - if (!has_frame_) + if (!has_frame()) return GetSize(); gfx::Size content_size = @@ -491,14 +452,6 @@ gfx::Size NativeWindowViews::GetContentSize() { void NativeWindowViews::SetMinimumSize(const gfx::Size& size) { minimum_size_ = size; - -#if defined(USE_X11) - XSizeHints size_hints; - size_hints.flags = PMinSize; - size_hints.min_width = size.width(); - size_hints.min_height = size.height(); - XSetWMNormalHints(gfx::GetXDisplay(), GetAcceleratedWidget(), &size_hints); -#endif } gfx::Size NativeWindowViews::GetMinimumSize() { @@ -507,14 +460,6 @@ gfx::Size NativeWindowViews::GetMinimumSize() { void NativeWindowViews::SetMaximumSize(const gfx::Size& size) { maximum_size_ = size; - -#if defined(USE_X11) - XSizeHints size_hints; - size_hints.flags = PMaxSize; - size_hints.max_width = size.width(); - size_hints.max_height = size.height(); - XSetWMNormalHints(gfx::GetXDisplay(), GetAcceleratedWidget(), &size_hints); -#endif } gfx::Size NativeWindowViews::GetMaximumSize() { @@ -643,7 +588,7 @@ void NativeWindowViews::SetMenu(ui::MenuModel* menu_model) { #endif // Do not show menu bar in frameless window. - if (!has_frame_) + if (!has_frame()) return; if (!menu_bar_) { @@ -668,24 +613,7 @@ gfx::NativeWindow NativeWindowViews::GetNativeWindow() { void NativeWindowViews::SetProgressBar(double progress) { #if defined(OS_WIN) - if (base::win::GetVersion() < base::win::VERSION_WIN7) - return; - base::win::ScopedComPtr taskbar; - if (FAILED(taskbar.CreateInstance(CLSID_TaskbarList, NULL, - CLSCTX_INPROC_SERVER) || - FAILED(taskbar->HrInit()))) { - return; - } - HWND frame = views::HWNDForNativeWindow(GetNativeWindow()); - if (progress > 1.0) { - taskbar->SetProgressState(frame, TBPF_INDETERMINATE); - } else if (progress < 0) { - taskbar->SetProgressState(frame, TBPF_NOPROGRESS); - } else if (progress >= 0) { - taskbar->SetProgressValue(frame, - static_cast(progress * 100), - 100); - } + taskbar_host_.SetProgressBar(GetAcceleratedWidget(), progress); #elif defined(USE_X11) if (unity::IsRunning()) { unity::SetProgressFraction(progress); @@ -696,22 +624,7 @@ void NativeWindowViews::SetProgressBar(double progress) { void NativeWindowViews::SetOverlayIcon(const gfx::Image& overlay, const std::string& description) { #if defined(OS_WIN) - if (base::win::GetVersion() < base::win::VERSION_WIN7) - return; - - base::win::ScopedComPtr taskbar; - if (FAILED(taskbar.CreateInstance(CLSID_TaskbarList, NULL, - CLSCTX_INPROC_SERVER) || - FAILED(taskbar->HrInit()))) { - return; - } - - HWND frame = views::HWNDForNativeWindow(GetNativeWindow()); - - std::wstring wstr = std::wstring(description.begin(), description.end()); - taskbar->SetOverlayIcon(frame, - IconUtil::CreateHICONFromSkBitmap(overlay.AsBitmap()), - wstr.c_str()); + taskbar_host_.SetOverlayIcon(GetAcceleratedWidget(), overlay, description); #endif } @@ -768,29 +681,6 @@ gfx::AcceleratedWidget NativeWindowViews::GetAcceleratedWidget() { return GetNativeWindow()->GetHost()->GetAcceleratedWidget(); } -void NativeWindowViews::UpdateDraggableRegions( - const std::vector& regions) { - if (has_frame_) - return; - - SkRegion* draggable_region = new SkRegion; - - // By default, the whole window is non-draggable. We need to explicitly - // include those draggable regions. - for (std::vector::const_iterator iter = regions.begin(); - iter != regions.end(); ++iter) { - const DraggableRegion& region = *iter; - draggable_region->op( - region.bounds.x(), - region.bounds.y(), - region.bounds.right(), - region.bounds.bottom(), - region.draggable ? SkRegion::kUnion_Op : SkRegion::kDifference_Op); - } - - draggable_region_.reset(draggable_region); -} - void NativeWindowViews::OnWidgetActivationChanged( views::Widget* widget, bool active) { if (widget != window_.get()) @@ -850,7 +740,7 @@ bool NativeWindowViews::ShouldHandleSystemCommands() const { } gfx::ImageSkia NativeWindowViews::GetWindowAppIcon() { - return icon_; + return icon(); } gfx::ImageSkia NativeWindowViews::GetWindowIcon() { @@ -873,12 +763,12 @@ bool NativeWindowViews::ShouldDescendIntoChildForEventHandling( gfx::NativeView child, const gfx::Point& location) { // App window should claim mouse events that fall within the draggable region. - if (draggable_region_ && - draggable_region_->contains(location.x(), location.y())) + if (draggable_region() && + draggable_region()->contains(location.x(), location.y())) return false; // And the events on border for dragging resizable frameless window. - if (!has_frame_ && CanResize()) { + if (!has_frame() && CanResize()) { FramelessView* frame = static_cast( window_->non_client_view()->frame_view()); return frame->ResizingBorderHitTest(location) == HTNOWHERE; @@ -898,8 +788,8 @@ views::NonClientFrameView* NativeWindowViews::CreateNonClientFrameView( frame_view->Init(this, widget); return frame_view; #else - if (has_frame_) { - return new views::NativeFrameView(widget); + if (has_frame()) { + return new NativeFrameView(this, widget); } else { FramelessView* frame_view = new FramelessView; frame_view->Init(this, widget); @@ -929,10 +819,8 @@ bool NativeWindowViews::ExecuteWindowsCommand(int command_id) { } else if ((command_id & sc_mask) == SC_MAXIMIZE) { NotifyWindowMaximize(); } else { - std::string command = AppCommandToString(command_id & sc_mask); - FOR_EACH_OBSERVER(NativeWindowObserver, - observers_, - OnExecuteWindowsCommand(command)); + std::string command = AppCommandToString(command_id); + NotifyWindowExecuteWindowsCommand(command); } return false; } @@ -950,6 +838,17 @@ void NativeWindowViews::GetDevToolsWindowWMClass( } #endif +#if defined(OS_WIN) +bool NativeWindowViews::PreHandleMSG( + UINT message, WPARAM w_param, LPARAM l_param, LRESULT* result) { + // Handle thumbar button click message. + if (message == WM_COMMAND && HIWORD(w_param) == THBN_CLICKED) + return taskbar_host_.HandleThumbarButtonEvent(LOWORD(w_param)); + else + return false; +} +#endif + void NativeWindowViews::HandleKeyboardEvent( content::WebContents*, const content::NativeWebKeyboardEvent& event) { diff --git a/atom/browser/native_window_views.h b/atom/browser/native_window_views.h index fa7e13c1c33..355f5bd38ef 100644 --- a/atom/browser/native_window_views.h +++ b/atom/browser/native_window_views.h @@ -14,6 +14,11 @@ #include "ui/views/widget/widget_delegate.h" #include "ui/views/widget/widget_observer.h" +#if defined(OS_WIN) +#include "atom/browser/ui/win/message_handler_delegate.h" +#include "atom/browser/ui/win/taskbar_host.h" +#endif + namespace views { class UnhandledKeyboardEventHandler; } @@ -24,7 +29,14 @@ class GlobalMenuBarX11; class MenuBar; class WindowStateWatcher; +#if defined(OS_WIN) +class AtomDesktopWindowTreeHostWin; +#endif + class NativeWindowViews : public NativeWindow, +#if defined(OS_WIN) + public MessageHandlerDelegate, +#endif public views::WidgetDelegateView, public views::WidgetObserver { public: @@ -82,14 +94,13 @@ class NativeWindowViews : public NativeWindow, gfx::AcceleratedWidget GetAcceleratedWidget(); - SkRegion* draggable_region() const { return draggable_region_.get(); } views::Widget* widget() const { return window_.get(); } - private: - // NativeWindow: - void UpdateDraggableRegions( - const std::vector& regions) override; +#if defined(OS_WIN) + TaskbarHost& taskbar_host() { return taskbar_host_; } +#endif + private: // views::WidgetObserver: void OnWidgetActivationChanged( views::Widget* widget, bool active) override; @@ -127,6 +138,12 @@ class NativeWindowViews : public NativeWindow, std::string* name, std::string* class_name) override; #endif +#if defined(OS_WIN) + // MessageHandlerDelegate: + bool PreHandleMSG( + UINT message, WPARAM w_param, LPARAM l_param, LRESULT* result) override; +#endif + // NativeWindow: void HandleKeyboardEvent( content::WebContents*, @@ -159,9 +176,13 @@ class NativeWindowViews : public NativeWindow, // Handles window state events. scoped_ptr window_state_watcher_; #elif defined(OS_WIN) + // Weak ref. + AtomDesktopWindowTreeHostWin* atom_desktop_window_tree_host_win_; // Records window was whether restored from minimized state or maximized // state. bool is_minimized_; + // In charge of running taskbar related APIs. + TaskbarHost taskbar_host_; #endif // Handles unhandled keyboard messages coming back from the renderer process. @@ -177,8 +198,6 @@ class NativeWindowViews : public NativeWindow, gfx::Size maximum_size_; gfx::Size widget_size_; - scoped_ptr draggable_region_; - DISALLOW_COPY_AND_ASSIGN(NativeWindowViews); }; diff --git a/atom/browser/net/adapter_request_job.cc b/atom/browser/net/adapter_request_job.cc index 20be9a70894..ca7dcf2e566 100644 --- a/atom/browser/net/adapter_request_job.cc +++ b/atom/browser/net/adapter_request_job.cc @@ -4,7 +4,6 @@ #include "atom/browser/net/adapter_request_job.h" -#include "atom/browser/atom_browser_context.h" #include "base/threading/sequenced_worker_pool.h" #include "atom/browser/net/url_request_buffer_job.h" #include "atom/browser/net/url_request_fetch_job.h" @@ -28,11 +27,7 @@ AdapterRequestJob::AdapterRequestJob(ProtocolHandler* protocol_handler, void AdapterRequestJob::Start() { DCHECK(!real_job_.get()); - content::BrowserThread::PostTask( - content::BrowserThread::UI, - FROM_HERE, - base::Bind(&AdapterRequestJob::GetJobTypeInUI, - weak_factory_.GetWeakPtr())); + GetJobType(); } void AdapterRequestJob::Kill() { @@ -44,7 +39,11 @@ bool AdapterRequestJob::ReadRawData(net::IOBuffer* buf, int buf_size, int *bytes_read) { DCHECK(!real_job_.get()); - return real_job_->ReadRawData(buf, buf_size, bytes_read); + // Read post-filtered data if available. + if (real_job_->HasFilter()) + return real_job_->Read(buf, buf_size, bytes_read); + else + return real_job_->ReadRawData(buf, buf_size, bytes_read); } bool AdapterRequestJob::IsRedirectResponse(GURL* location, @@ -76,6 +75,11 @@ int AdapterRequestJob::GetResponseCode() const { return real_job_->GetResponseCode(); } +void AdapterRequestJob::GetLoadTimingInfo( + net::LoadTimingInfo* load_timing_info) const { + real_job_->GetLoadTimingInfo(load_timing_info); +} + base::WeakPtr AdapterRequestJob::GetWeakPtr() { return weak_factory_.GetWeakPtr(); } @@ -115,7 +119,7 @@ void AdapterRequestJob::CreateFileJobAndStart(const base::FilePath& path) { } void AdapterRequestJob::CreateHttpJobAndStart( - AtomBrowserContext* browser_context, + scoped_refptr request_context_getter, const GURL& url, const std::string& method, const std::string& referrer) { @@ -124,7 +128,7 @@ void AdapterRequestJob::CreateHttpJobAndStart( return; } - real_job_ = new URLRequestFetchJob(browser_context, request(), + real_job_ = new URLRequestFetchJob(request_context_getter, request(), network_delegate(), url, method, referrer); real_job_->Start(); } @@ -132,10 +136,13 @@ void AdapterRequestJob::CreateHttpJobAndStart( void AdapterRequestJob::CreateJobFromProtocolHandlerAndStart() { real_job_ = protocol_handler_->MaybeCreateJob(request(), network_delegate()); - if (!real_job_.get()) + if (!real_job_.get()) { CreateErrorJobAndStart(net::ERR_NOT_IMPLEMENTED); - else + } else { + // Copy headers from original request. + real_job_->SetExtraRequestHeaders(request()->extra_request_headers()); real_job_->Start(); + } } } // namespace atom diff --git a/atom/browser/net/adapter_request_job.h b/atom/browser/net/adapter_request_job.h index 6aff376f302..afb9d5f55d0 100644 --- a/atom/browser/net/adapter_request_job.h +++ b/atom/browser/net/adapter_request_job.h @@ -10,6 +10,7 @@ #include "base/memory/ref_counted_memory.h" #include "base/memory/weak_ptr.h" #include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_context_getter.h" #include "net/url_request/url_request_job.h" #include "net/url_request/url_request_job_factory.h" #include "v8/include/v8.h" @@ -45,13 +46,15 @@ class AdapterRequestJob : public net::URLRequestJob { bool GetCharset(std::string* charset) override; void GetResponseInfo(net::HttpResponseInfo* info) override; int GetResponseCode() const override; + void GetLoadTimingInfo( + net::LoadTimingInfo* load_timing_info) const override; base::WeakPtr GetWeakPtr(); ProtocolHandler* default_protocol_handler() { return protocol_handler_; } // Override this function to determine which job should be started. - virtual void GetJobTypeInUI() = 0; + virtual void GetJobType() = 0; void CreateErrorJobAndStart(int error_code); void CreateStringJobAndStart(const std::string& mime_type, @@ -61,10 +64,11 @@ class AdapterRequestJob : public net::URLRequestJob { const std::string& charset, scoped_refptr data); void CreateFileJobAndStart(const base::FilePath& path); - void CreateHttpJobAndStart(AtomBrowserContext* browser_context, - const GURL& url, - const std::string& method, - const std::string& referrer); + void CreateHttpJobAndStart( + scoped_refptr request_context_getter, + const GURL& url, + const std::string& method, + const std::string& referrer); void CreateJobFromProtocolHandlerAndStart(); private: diff --git a/atom/browser/net/url_request_fetch_job.cc b/atom/browser/net/url_request_fetch_job.cc index e353ff17087..ee4c67b371e 100644 --- a/atom/browser/net/url_request_fetch_job.cc +++ b/atom/browser/net/url_request_fetch_job.cc @@ -7,13 +7,14 @@ #include #include -#include "atom/browser/atom_browser_context.h" #include "base/strings/string_util.h" +#include "base/thread_task_runner_handle.h" #include "net/base/io_buffer.h" #include "net/base/net_errors.h" #include "net/http/http_response_headers.h" #include "net/url_request/url_fetcher.h" #include "net/url_request/url_fetcher_response_writer.h" +#include "net/url_request/url_request_context_builder.h" #include "net/url_request/url_request_status.h" namespace atom { @@ -74,7 +75,7 @@ class ResponsePiper : public net::URLFetcherResponseWriter { } // namespace URLRequestFetchJob::URLRequestFetchJob( - AtomBrowserContext* browser_context, + scoped_refptr request_context_getter, net::URLRequest* request, net::NetworkDelegate* network_delegate, const GURL& url, @@ -90,7 +91,12 @@ URLRequestFetchJob::URLRequestFetchJob( request_type = GetRequestType(method); fetcher_.reset(net::URLFetcher::Create(url, request_type, this)); - fetcher_->SetRequestContext(browser_context->url_request_context_getter()); + // Use request context if provided else create one. + if (request_context_getter) + fetcher_->SetRequestContext(request_context_getter.get()); + else + fetcher_->SetRequestContext(GetRequestContext()); + fetcher_->SaveResponseWithWriter(make_scoped_ptr(new ResponsePiper(this))); // Use |request|'s referrer if |referrer| is not specified. @@ -101,10 +107,18 @@ URLRequestFetchJob::URLRequestFetchJob( } // Use |request|'s headers. - net::HttpRequestHeaders headers; - if (request->GetFullRequestHeaders(&headers)) { - fetcher_->SetExtraRequestHeaders(headers.ToString()); + fetcher_->SetExtraRequestHeaders(request->extra_request_headers().ToString()); +} + +net::URLRequestContextGetter* URLRequestFetchJob::GetRequestContext() { + if (!url_request_context_getter_.get()) { + auto task_runner = base::ThreadTaskRunnerHandle::Get(); + net::URLRequestContextBuilder builder; + builder.set_proxy_service(net::ProxyService::CreateDirect()); + url_request_context_getter_ = + new net::TrivialURLRequestContextGetter(builder.Build(), task_runner); } + return url_request_context_getter_.get(); } void URLRequestFetchJob::HeadersCompleted() { diff --git a/atom/browser/net/url_request_fetch_job.h b/atom/browser/net/url_request_fetch_job.h index d598e322361..a14e8dd1aae 100644 --- a/atom/browser/net/url_request_fetch_job.h +++ b/atom/browser/net/url_request_fetch_job.h @@ -7,6 +7,7 @@ #include +#include "net/url_request/url_request_context_getter.h" #include "net/url_request/url_fetcher_delegate.h" #include "net/url_request/url_request_job.h" @@ -17,13 +18,14 @@ class AtomBrowserContext; class URLRequestFetchJob : public net::URLRequestJob, public net::URLFetcherDelegate { public: - URLRequestFetchJob(AtomBrowserContext* browser_context, + URLRequestFetchJob(scoped_refptr context_getter, net::URLRequest* request, net::NetworkDelegate* network_delegate, const GURL& url, const std::string& method, const std::string& referrer); + net::URLRequestContextGetter* GetRequestContext(); void HeadersCompleted(); int DataAvailable(net::IOBuffer* buffer, int num_bytes); @@ -41,6 +43,7 @@ class URLRequestFetchJob : public net::URLRequestJob, void OnURLFetchComplete(const net::URLFetcher* source) override; private: + scoped_refptr url_request_context_getter_; scoped_ptr fetcher_; scoped_refptr pending_buffer_; int pending_buffer_size_; diff --git a/atom/browser/resources/mac/Info.plist b/atom/browser/resources/mac/Info.plist index edbeda36e8a..a8c009acaff 100644 --- a/atom/browser/resources/mac/Info.plist +++ b/atom/browser/resources/mac/Info.plist @@ -17,7 +17,7 @@ CFBundleIconFile atom.icns CFBundleVersion - 0.29.2 + 0.30.4 LSMinimumSystemVersion 10.8.0 NSMainNibFile diff --git a/atom/browser/resources/win/atom.rc b/atom/browser/resources/win/atom.rc index 7f6d618d48d..b22d3d88f6c 100644 --- a/atom/browser/resources/win/atom.rc +++ b/atom/browser/resources/win/atom.rc @@ -56,8 +56,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,29,2,0 - PRODUCTVERSION 0,29,2,0 + FILEVERSION 0,30,4,0 + PRODUCTVERSION 0,30,4,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -74,12 +74,12 @@ BEGIN BEGIN VALUE "CompanyName", "GitHub, Inc." VALUE "FileDescription", "Electron" - VALUE "FileVersion", "0.29.2" + VALUE "FileVersion", "0.30.4" VALUE "InternalName", "electron.exe" VALUE "LegalCopyright", "Copyright (C) 2015 GitHub, Inc. All rights reserved." VALUE "OriginalFilename", "electron.exe" VALUE "ProductName", "Electron" - VALUE "ProductVersion", "0.29.2" + VALUE "ProductVersion", "0.30.4" VALUE "SquirrelAwareVersion", "1" END END diff --git a/atom/browser/ui/atom_menu_model.cc b/atom/browser/ui/atom_menu_model.cc new file mode 100644 index 00000000000..7d2d5e1b6a1 --- /dev/null +++ b/atom/browser/ui/atom_menu_model.cc @@ -0,0 +1,22 @@ +// Copyright (c) 2015 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/ui/atom_menu_model.h" + +namespace atom { + +AtomMenuModel::AtomMenuModel(Delegate* delegate) + : ui::SimpleMenuModel(delegate), + delegate_(delegate) { +} + +AtomMenuModel::~AtomMenuModel() { +} + +void AtomMenuModel::MenuClosed() { + ui::SimpleMenuModel::MenuClosed(); + FOR_EACH_OBSERVER(Observer, observers_, MenuClosed()); +} + +} // namespace atom diff --git a/atom/browser/ui/atom_menu_model.h b/atom/browser/ui/atom_menu_model.h new file mode 100644 index 00000000000..42e0e5dbc53 --- /dev/null +++ b/atom/browser/ui/atom_menu_model.h @@ -0,0 +1,47 @@ +// Copyright (c) 2015 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_UI_ATOM_MENU_MODEL_H_ +#define ATOM_BROWSER_UI_ATOM_MENU_MODEL_H_ + +#include "base/observer_list.h" +#include "ui/base/models/simple_menu_model.h" + +namespace atom { + +class AtomMenuModel : public ui::SimpleMenuModel { + public: + class Delegate : public ui::SimpleMenuModel::Delegate { + public: + virtual ~Delegate() {} + }; + + class Observer { + public: + virtual ~Observer() {} + + // Notifies the menu has been closed. + virtual void MenuClosed() {} + }; + + explicit AtomMenuModel(Delegate* delegate); + virtual ~AtomMenuModel(); + + void AddObserver(Observer* obs) { observers_.AddObserver(obs); } + void RemoveObserver(Observer* obs) { observers_.RemoveObserver(obs); } + + // ui::SimpleMenuModel: + void MenuClosed() override; + + private: + Delegate* delegate_; // weak ref. + + ObserverList observers_; + + DISALLOW_COPY_AND_ASSIGN(AtomMenuModel); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_UI_ATOM_MENU_MODEL_H_ diff --git a/atom/browser/ui/cocoa/atom_menu_controller.mm b/atom/browser/ui/cocoa/atom_menu_controller.mm index 176f2db7e14..d799a9f9b64 100644 --- a/atom/browser/ui/cocoa/atom_menu_controller.mm +++ b/atom/browser/ui/cocoa/atom_menu_controller.mm @@ -5,61 +5,15 @@ #import "atom/browser/ui/cocoa/atom_menu_controller.h" +#include "atom/browser/ui/atom_menu_model.h" #include "base/logging.h" #include "base/strings/sys_string_conversions.h" #include "ui/base/accelerators/accelerator.h" #include "ui/base/accelerators/platform_accelerator_cocoa.h" #include "ui/base/l10n/l10n_util_mac.h" -#include "ui/base/models/simple_menu_model.h" +#include "ui/events/cocoa/cocoa_event_utils.h" #include "ui/gfx/image/image.h" -namespace { - -bool isLeftButtonEvent(NSEvent* event) { - NSEventType type = [event type]; - return type == NSLeftMouseDown || - type == NSLeftMouseDragged || - type == NSLeftMouseUp; -} - -bool isRightButtonEvent(NSEvent* event) { - NSEventType type = [event type]; - return type == NSRightMouseDown || - type == NSRightMouseDragged || - type == NSRightMouseUp; -} - -bool isMiddleButtonEvent(NSEvent* event) { - if ([event buttonNumber] != 2) - return false; - - NSEventType type = [event type]; - return type == NSOtherMouseDown || - type == NSOtherMouseDragged || - type == NSOtherMouseUp; -} - -int EventFlagsFromNSEventWithModifiers(NSEvent* event, NSUInteger modifiers) { - int flags = 0; - flags |= (modifiers & NSAlphaShiftKeyMask) ? ui::EF_CAPS_LOCK_DOWN : 0; - flags |= (modifiers & NSShiftKeyMask) ? ui::EF_SHIFT_DOWN : 0; - flags |= (modifiers & NSControlKeyMask) ? ui::EF_CONTROL_DOWN : 0; - flags |= (modifiers & NSAlternateKeyMask) ? ui::EF_ALT_DOWN : 0; - flags |= (modifiers & NSCommandKeyMask) ? ui::EF_COMMAND_DOWN : 0; - flags |= isLeftButtonEvent(event) ? ui::EF_LEFT_MOUSE_BUTTON : 0; - flags |= isRightButtonEvent(event) ? ui::EF_RIGHT_MOUSE_BUTTON : 0; - flags |= isMiddleButtonEvent(event) ? ui::EF_MIDDLE_MOUSE_BUTTON : 0; - return flags; -} - -// Retrieves a bitsum of ui::EventFlags from NSEvent. -int EventFlagsFromNSEvent(NSEvent* event) { - NSUInteger modifiers = [event modifierFlags]; - return EventFlagsFromNSEventWithModifiers(event, modifiers); -} - -} // namespace - @interface AtomMenuController (Private) - (void)addSeparatorToMenu:(NSMenu*)menu atIndex:(int)index; @@ -166,8 +120,7 @@ int EventFlagsFromNSEvent(NSEvent* event) { [item setTarget:nil]; [item setAction:nil]; ui::MenuModel* submenuModel = model->GetSubmenuModelAt(index); - NSMenu* submenu = - [self menuFromModel:(ui::SimpleMenuModel*)submenuModel]; + NSMenu* submenu = [self menuFromModel:submenuModel]; [submenu setTitle:[item title]]; [item setSubmenu:submenu]; @@ -246,8 +199,9 @@ int EventFlagsFromNSEvent(NSEvent* event) { [[sender representedObject] pointerValue]); DCHECK(model); if (model) { - int event_flags = EventFlagsFromNSEvent([NSApp currentEvent]); - model->ActivatedAt(modelIndex, event_flags); + NSEvent* event = [NSApp currentEvent]; + model->ActivatedAt(modelIndex, + ui::EventFlagsFromModifiers([event modifierFlags])); } } diff --git a/atom/browser/ui/file_dialog_mac.mm b/atom/browser/ui/file_dialog_mac.mm index 9cd6c0380be..96d230b1a1e 100644 --- a/atom/browser/ui/file_dialog_mac.mm +++ b/atom/browser/ui/file_dialog_mac.mm @@ -18,25 +18,11 @@ namespace file_dialog { namespace { -CFStringRef CreateUTIFromExtension(const std::string& ext) { - base::ScopedCFTypeRef ext_cf(base::SysUTF8ToCFStringRef(ext)); - return UTTypeCreatePreferredIdentifierForTag( - kUTTagClassFilenameExtension, ext_cf.get(), NULL); -} - void SetAllowedFileTypes(NSSavePanel* dialog, const Filters& filters) { NSMutableSet* file_type_set = [NSMutableSet set]; for (size_t i = 0; i < filters.size(); ++i) { const Filter& filter = filters[i]; for (size_t j = 0; j < filter.second.size(); ++j) { - base::ScopedCFTypeRef uti( - CreateUTIFromExtension(filter.second[j])); - [file_type_set addObject:base::mac::CFToNSCast(uti.get())]; - - // Always allow the extension itself, in case the UTI doesn't map - // back to the original extension correctly. This occurs with dynamic - // UTIs on 10.7 and 10.8. - // See http://crbug.com/148840, http://openradar.me/12316273 base::ScopedCFTypeRef ext_cf( base::SysUTF8ToCFStringRef(filter.second[j])); [file_type_set addObject:base::mac::CFToNSCast(ext_cf.get())]; diff --git a/atom/browser/ui/message_box.h b/atom/browser/ui/message_box.h index 817da2c5699..92052d3de4a 100644 --- a/atom/browser/ui/message_box.h +++ b/atom/browser/ui/message_box.h @@ -27,12 +27,18 @@ enum MessageBoxType { MESSAGE_BOX_TYPE_QUESTION, }; +enum MessageBoxOptions { + MESSAGE_BOX_NONE = 0, + MESSAGE_BOX_NO_LINK = 1 << 0, +}; + typedef base::Callback MessageBoxCallback; int ShowMessageBox(NativeWindow* parent_window, MessageBoxType type, const std::vector& buttons, int cancel_id, + int options, const std::string& title, const std::string& message, const std::string& detail, @@ -42,6 +48,7 @@ void ShowMessageBox(NativeWindow* parent_window, MessageBoxType type, const std::vector& buttons, int cancel_id, + int options, const std::string& title, const std::string& message, const std::string& detail, diff --git a/atom/browser/ui/message_box_gtk.cc b/atom/browser/ui/message_box_gtk.cc index 07695d49845..41682190e60 100644 --- a/atom/browser/ui/message_box_gtk.cc +++ b/atom/browser/ui/message_box_gtk.cc @@ -162,6 +162,7 @@ int ShowMessageBox(NativeWindow* parent, MessageBoxType type, const std::vector& buttons, int cancel_id, + int options, const std::string& title, const std::string& message, const std::string& detail, @@ -174,6 +175,7 @@ void ShowMessageBox(NativeWindow* parent, MessageBoxType type, const std::vector& buttons, int cancel_id, + int options, const std::string& title, const std::string& message, const std::string& detail, diff --git a/atom/browser/ui/message_box_mac.mm b/atom/browser/ui/message_box_mac.mm index 8fe3b7d060c..e518af653da 100644 --- a/atom/browser/ui/message_box_mac.mm +++ b/atom/browser/ui/message_box_mac.mm @@ -95,6 +95,7 @@ int ShowMessageBox(NativeWindow* parent_window, MessageBoxType type, const std::vector& buttons, int cancel_id, + int options, const std::string& title, const std::string& message, const std::string& detail, @@ -127,6 +128,7 @@ void ShowMessageBox(NativeWindow* parent_window, MessageBoxType type, const std::vector& buttons, int cancel_id, + int options, const std::string& title, const std::string& message, const std::string& detail, diff --git a/atom/browser/ui/message_box_win.cc b/atom/browser/ui/message_box_win.cc index dae518deb4d..051f8f5eff6 100644 --- a/atom/browser/ui/message_box_win.cc +++ b/atom/browser/ui/message_box_win.cc @@ -10,6 +10,7 @@ #include #include +#include "atom/browser/browser.h" #include "atom/browser/native_window_views.h" #include "base/callback.h" #include "base/strings/string_util.h" @@ -72,6 +73,7 @@ int ShowMessageBoxUTF16(HWND parent, MessageBoxType type, const std::vector& buttons, int cancel_id, + int options, const base::string16& title, const base::string16& message, const base::string16& detail, @@ -86,7 +88,12 @@ int ShowMessageBoxUTF16(HWND parent, config.hInstance = GetModuleHandle(NULL); config.dwFlags = flags; - if (!title.empty()) + // TaskDialogIndirect doesn't allow empty name, if we set empty title it + // will show "electron.exe" in title. + base::string16 app_name = base::UTF8ToUTF16(Browser::Get()->GetName()); + if (title.empty()) + config.pszWindowTitle = app_name.c_str(); + else config.pszWindowTitle = title.c_str(); base::win::ScopedHICON hicon; @@ -122,11 +129,17 @@ int ShowMessageBoxUTF16(HWND parent, // and custom buttons in pButtons. std::map id_map; std::vector dialog_buttons; - MapToCommonID(buttons, &id_map, &config.dwCommonButtons, &dialog_buttons); + if (options & MESSAGE_BOX_NO_LINK) { + for (size_t i = 0; i < buttons.size(); ++i) + dialog_buttons.push_back({i + kIDStart, buttons[i].c_str()}); + } else { + MapToCommonID(buttons, &id_map, &config.dwCommonButtons, &dialog_buttons); + } if (dialog_buttons.size() > 0) { config.pButtons = &dialog_buttons.front(); config.cButtons = dialog_buttons.size(); - config.dwFlags |= TDF_USE_COMMAND_LINKS; // custom buttons as links. + if (!(options & MESSAGE_BOX_NO_LINK)) + config.dwFlags |= TDF_USE_COMMAND_LINKS; // custom buttons as links. } int id = 0; @@ -144,13 +157,14 @@ void RunMessageBoxInNewThread(base::Thread* thread, MessageBoxType type, const std::vector& buttons, int cancel_id, + int options, const std::string& title, const std::string& message, const std::string& detail, const gfx::ImageSkia& icon, const MessageBoxCallback& callback) { - int result = ShowMessageBox(parent, type, buttons, cancel_id, title, message, - detail, icon); + int result = ShowMessageBox(parent, type, buttons, cancel_id, options, title, + message, detail, icon); content::BrowserThread::PostTask( content::BrowserThread::UI, FROM_HERE, base::Bind(callback, result)); content::BrowserThread::DeleteSoon( @@ -163,6 +177,7 @@ int ShowMessageBox(NativeWindow* parent, MessageBoxType type, const std::vector& buttons, int cancel_id, + int options, const std::string& title, const std::string& message, const std::string& detail, @@ -180,6 +195,7 @@ int ShowMessageBox(NativeWindow* parent, type, utf16_buttons, cancel_id, + options, base::UTF8ToUTF16(title), base::UTF8ToUTF16(message), base::UTF8ToUTF16(detail), @@ -190,6 +206,7 @@ void ShowMessageBox(NativeWindow* parent, MessageBoxType type, const std::vector& buttons, int cancel_id, + int options, const std::string& title, const std::string& message, const std::string& detail, @@ -207,12 +224,12 @@ void ShowMessageBox(NativeWindow* parent, unretained->message_loop()->PostTask( FROM_HERE, base::Bind(&RunMessageBoxInNewThread, base::Unretained(unretained), - parent, type, buttons, cancel_id, title, message, detail, icon, - callback)); + parent, type, buttons, cancel_id, options, title, message, + detail, icon, callback)); } void ShowErrorBox(const base::string16& title, const base::string16& content) { - ShowMessageBoxUTF16(NULL, MESSAGE_BOX_TYPE_ERROR, {}, 0, L"Error", title, + ShowMessageBoxUTF16(NULL, MESSAGE_BOX_TYPE_ERROR, {}, 0, 0, L"Error", title, content, gfx::ImageSkia()); } diff --git a/atom/browser/ui/tray_icon.cc b/atom/browser/ui/tray_icon.cc index a3878f718a6..12c6be2ea74 100644 --- a/atom/browser/ui/tray_icon.cc +++ b/atom/browser/ui/tray_icon.cc @@ -26,12 +26,16 @@ void TrayIcon::DisplayBalloon(const gfx::Image& icon, const base::string16& contents) { } -void TrayIcon::NotifyClicked(const gfx::Rect& bounds) { - FOR_EACH_OBSERVER(TrayIconObserver, observers_, OnClicked(bounds)); +void TrayIcon::PopUpContextMenu(const gfx::Point& pos) { } -void TrayIcon::NotifyDoubleClicked() { - FOR_EACH_OBSERVER(TrayIconObserver, observers_, OnDoubleClicked()); +void TrayIcon::NotifyClicked(const gfx::Rect& bounds, int modifiers) { + FOR_EACH_OBSERVER(TrayIconObserver, observers_, OnClicked(bounds, modifiers)); +} + +void TrayIcon::NotifyDoubleClicked(const gfx::Rect& bounds, int modifiers) { + FOR_EACH_OBSERVER(TrayIconObserver, observers_, + OnDoubleClicked(bounds, modifiers)); } void TrayIcon::NotifyBalloonShow() { @@ -46,4 +50,13 @@ void TrayIcon::NotifyBalloonClosed() { FOR_EACH_OBSERVER(TrayIconObserver, observers_, OnBalloonClosed()); } +void TrayIcon::NotifyRightClicked(const gfx::Rect& bounds, int modifiers) { + FOR_EACH_OBSERVER(TrayIconObserver, observers_, + OnRightClicked(bounds, modifiers)); +} + +void TrayIcon::NotfiyDropFiles(const std::vector& files) { + FOR_EACH_OBSERVER(TrayIconObserver, observers_, OnDropFiles(files)); +} + } // namespace atom diff --git a/atom/browser/ui/tray_icon.h b/atom/browser/ui/tray_icon.h index 7dc67da1bac..55f1c41d19d 100644 --- a/atom/browser/ui/tray_icon.h +++ b/atom/browser/ui/tray_icon.h @@ -6,6 +6,7 @@ #define ATOM_BROWSER_UI_TRAY_ICON_H_ #include +#include #include "atom/browser/ui/tray_icon_observer.h" #include "base/observer_list.h" @@ -46,16 +47,21 @@ class TrayIcon { const base::string16& title, const base::string16& contents); + virtual void PopUpContextMenu(const gfx::Point& pos); + // Set the context menu for this icon. virtual void SetContextMenu(ui::SimpleMenuModel* menu_model) = 0; void AddObserver(TrayIconObserver* obs) { observers_.AddObserver(obs); } void RemoveObserver(TrayIconObserver* obs) { observers_.RemoveObserver(obs); } - void NotifyClicked(const gfx::Rect& = gfx::Rect()); - void NotifyDoubleClicked(); + void NotifyClicked(const gfx::Rect& = gfx::Rect(), int modifiers = 0); + void NotifyDoubleClicked(const gfx::Rect& = gfx::Rect(), int modifiers = 0); void NotifyBalloonShow(); void NotifyBalloonClicked(); void NotifyBalloonClosed(); + void NotifyRightClicked(const gfx::Rect& bounds = gfx::Rect(), + int modifiers = 0); + void NotfiyDropFiles(const std::vector& files); protected: TrayIcon(); diff --git a/atom/browser/ui/tray_icon_cocoa.h b/atom/browser/ui/tray_icon_cocoa.h index 5723cb6b219..7781c93a1c0 100644 --- a/atom/browser/ui/tray_icon_cocoa.h +++ b/atom/browser/ui/tray_icon_cocoa.h @@ -9,15 +9,17 @@ #include +#include "atom/browser/ui/atom_menu_model.h" #include "atom/browser/ui/tray_icon.h" #include "base/mac/scoped_nsobject.h" @class AtomMenuController; -@class StatusItemController; +@class StatusItemView; namespace atom { -class TrayIconCocoa : public TrayIcon { +class TrayIconCocoa : public TrayIcon, + public AtomMenuModel::Observer { public: TrayIconCocoa(); virtual ~TrayIconCocoa(); @@ -27,16 +29,23 @@ class TrayIconCocoa : public TrayIcon { void SetToolTip(const std::string& tool_tip) override; void SetTitle(const std::string& title) override; void SetHighlightMode(bool highlight) override; + void PopUpContextMenu(const gfx::Point& pos) override; void SetContextMenu(ui::SimpleMenuModel* menu_model) override; - private: - base::scoped_nsobject item_; + protected: + // AtomMenuModel::Observer: + void MenuClosed() override; - base::scoped_nsobject controller_; + private: + // Atom custom view for NSStatusItem. + base::scoped_nsobject status_item_view_; // Status menu shown when right-clicking the system icon. base::scoped_nsobject menu_; + // Used for unregistering observer. + AtomMenuModel* menu_model_; // weak ref. + DISALLOW_COPY_AND_ASSIGN(TrayIconCocoa); }; diff --git a/atom/browser/ui/tray_icon_cocoa.mm b/atom/browser/ui/tray_icon_cocoa.mm index f989b9b580e..d69fa8636b4 100644 --- a/atom/browser/ui/tray_icon_cocoa.mm +++ b/atom/browser/ui/tray_icon_cocoa.mm @@ -6,84 +6,333 @@ #include "atom/browser/ui/cocoa/atom_menu_controller.h" #include "base/strings/sys_string_conversions.h" +#include "ui/events/cocoa/cocoa_event_utils.h" #include "ui/gfx/image/image.h" #include "ui/gfx/screen.h" -@interface StatusItemController : NSObject { +namespace { + +// By default, OS X sets 4px to tray image as left and right padding margin. +const CGFloat kHorizontalMargin = 4; +// OS X tends to make the title 2px lower. +const CGFloat kVerticalTitleMargin = 2; + +} // namespace + +@interface StatusItemView : NSView { atom::TrayIconCocoa* trayIcon_; // weak + AtomMenuController* menuController_; // weak + BOOL isHighlightEnable_; + BOOL inMouseEventSequence_; + base::scoped_nsobject image_; + base::scoped_nsobject alternateImage_; + base::scoped_nsobject image_view_; + base::scoped_nsobject title_; + base::scoped_nsobject statusItem_; } -- (id)initWithIcon:(atom::TrayIconCocoa*)icon; -- (void)handleClick:(id)sender; -- (void)handleDoubleClick:(id)sender; -@end // @interface StatusItemController +@end // @interface StatusItemView -@implementation StatusItemController +@implementation StatusItemView -- (id)initWithIcon:(atom::TrayIconCocoa*)icon { +- (id)initWithImage:(NSImage*)image icon:(atom::TrayIconCocoa*)icon { + image_.reset([image copy]); trayIcon_ = icon; + isHighlightEnable_ = YES; + + // Get the initial size. + NSStatusBar* statusBar = [NSStatusBar systemStatusBar]; + NSRect frame = NSMakeRect(0, 0, [self fullWidth], [statusBar thickness]); + + if ((self = [super initWithFrame:frame])) { + // Setup the image view. + NSRect iconFrame = frame; + iconFrame.size.width = [self iconWidth]; + image_view_.reset([[NSImageView alloc] initWithFrame:iconFrame]); + [image_view_ setImageScaling:NSImageScaleNone]; + [image_view_ setImageAlignment:NSImageAlignCenter]; + [self addSubview:image_view_]; + + // Unregister image_view_ as a dragged destination, allows its parent view + // (StatusItemView) handle dragging events. + [image_view_ unregisterDraggedTypes]; + NSArray* types = [NSArray arrayWithObjects:NSFilenamesPboardType, nil]; + [self registerForDraggedTypes:types]; + + // Create the status item. + statusItem_.reset([[[NSStatusBar systemStatusBar] + statusItemWithLength:NSWidth(frame)] retain]); + [statusItem_ setView:self]; + } return self; } -- (void)handleClick:(id)sender { - // Get the frame of the NSStatusItem. - NSRect frame = [NSApp currentEvent].window.frame; +- (void)removeItem { + [[NSStatusBar systemStatusBar] removeStatusItem:statusItem_]; + statusItem_.reset(); +} + +- (void)drawRect:(NSRect)dirtyRect { + // Draw the tray icon and title that align with NSStatusItem, layout: + // ---------------- + // | icon | title | + /// ---------------- + + // Draw background. + BOOL highlight = [self shouldHighlight]; + CGFloat thickness = [[statusItem_ statusBar] thickness]; + NSRect statusItemBounds = NSMakeRect(0, 0, [statusItem_ length], thickness); + [statusItem_ drawStatusBarBackgroundInRect:statusItemBounds + withHighlight:highlight]; + + // Make use of NSImageView to draw the image, which can correctly draw + // template image under dark menu bar. + if (highlight && alternateImage_ && + [image_view_ image] != alternateImage_.get()) { + [image_view_ setImage:alternateImage_]; + } else if ([image_view_ image] != image_.get()) { + [image_view_ setImage:image_]; + } + + if (title_) { + // Highlight the text when icon is highlighted or in dark mode. + highlight |= [self isDarkMode]; + // Draw title. + NSRect titleDrawRect = NSMakeRect( + [self iconWidth], -kVerticalTitleMargin, [self titleWidth], thickness); + [title_ drawInRect:titleDrawRect + withAttributes:[self titleAttributesWithHighlight:highlight]]; + } +} + +- (BOOL)isDarkMode { + NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; + NSString* mode = [defaults stringForKey:@"AppleInterfaceStyle"]; + return mode && [mode isEqualToString:@"Dark"]; +} + +// The width of the full status item. +- (CGFloat)fullWidth { + if (title_) + return [self iconWidth] + [self titleWidth] + kHorizontalMargin; + else + return [self iconWidth]; +} + +// The width of the icon. +- (CGFloat)iconWidth { + CGFloat thickness = [[NSStatusBar systemStatusBar] thickness]; + CGFloat imageHeight = [image_ size].height; + CGFloat imageWidth = [image_ size].width; + CGFloat iconWidth = imageWidth; + if (imageWidth < thickness) { + // Image's width must be larger than menu bar's height. + iconWidth = thickness; + } else { + CGFloat verticalMargin = thickness - imageHeight; + // Image must have same horizontal vertical margin. + if (verticalMargin > 0 && imageWidth != imageHeight) + iconWidth = imageWidth + verticalMargin; + CGFloat horizontalMargin = thickness - imageWidth; + // Image must have at least kHorizontalMargin horizontal margin on each + // side. + if (horizontalMargin < 2 * kHorizontalMargin) + iconWidth = imageWidth + 2 * kHorizontalMargin; + } + return iconWidth; +} + +// The width of the title. +- (CGFloat)titleWidth { + if (!title_) + return 0; + base::scoped_nsobject attributes( + [[NSAttributedString alloc] initWithString:title_ + attributes:[self titleAttributes]]); + return [attributes size].width; +} + +- (NSDictionary*)titleAttributesWithHighlight:(BOOL)highlight { + NSFont* font = [NSFont menuBarFontOfSize:0]; + NSColor* foregroundColor = highlight ? + [NSColor whiteColor] : + [NSColor colorWithRed:0.265625 green:0.25390625 blue:0.234375 alpha:1.0]; + return [NSDictionary dictionaryWithObjectsAndKeys: + font, NSFontAttributeName, + foregroundColor, NSForegroundColorAttributeName, + nil]; +} + +- (NSDictionary*)titleAttributes { + return [self titleAttributesWithHighlight:[self isDarkMode]]; +} + +- (void)setImage:(NSImage*)image { + image_.reset([image copy]); + [self setNeedsDisplay:YES]; +} + +- (void)setAlternateImage:(NSImage*)image { + alternateImage_.reset([image copy]); +} + +- (void)setHighlight:(BOOL)highlight { + isHighlightEnable_ = highlight; +} + +- (void)setTitle:(NSString*)title { + if (title.length > 0) + title_.reset([title copy]); + else + title_.reset(); + [statusItem_ setLength:[self fullWidth]]; + [self setNeedsDisplay:YES]; +} + +- (void)setMenuController:(AtomMenuController*)menu { + menuController_ = menu; +} + +- (void)mouseDown:(NSEvent*)event { + inMouseEventSequence_ = YES; + [self setNeedsDisplay:YES]; +} + +- (void)mouseUp:(NSEvent*)event { + if (!inMouseEventSequence_) { + // If the menu is showing, when user clicked the tray icon, the `mouseDown` + // event will be dissmissed, we need to close the menu at this time. + [self setNeedsDisplay:YES]; + return; + } + inMouseEventSequence_ = NO; + + // Show menu when single clicked on the icon. + if (event.clickCount == 1 && menuController_) + [statusItem_ popUpStatusItemMenu:[menuController_ menu]]; + + // Don't emit click events when menu is showing. + if (menuController_) + return; + + // Single click event. + if (event.clickCount == 1) + trayIcon_->NotifyClicked( + [self getBoundsFromEvent:event], + ui::EventFlagsFromModifiers([event modifierFlags])); + + // Double click event. + if (event.clickCount == 2) + trayIcon_->NotifyDoubleClicked( + [self getBoundsFromEvent:event], + ui::EventFlagsFromModifiers([event modifierFlags])); + + [self setNeedsDisplay:YES]; +} + +- (void)popUpContextMenu { + if (menuController_ && ![menuController_ isMenuOpen]) { + // Redraw the dray icon to show highlight if it is enabled. + [self setNeedsDisplay:YES]; + [statusItem_ popUpStatusItemMenu:[menuController_ menu]]; + // The popUpStatusItemMenu returns only after the showing menu is closed. + // When it returns, we need to redraw the tray icon to not show highlight. + [self setNeedsDisplay:YES]; + } +} + +- (void)rightMouseUp:(NSEvent*)event { + trayIcon_->NotifyRightClicked( + [self getBoundsFromEvent:event], + ui::EventFlagsFromModifiers([event modifierFlags])); +} + +- (NSDragOperation)draggingEntered:(id )sender { + return NSDragOperationCopy; +} + +- (BOOL)performDragOperation:(id )sender { + NSPasteboard* pboard = [sender draggingPasteboard]; + + if ([[pboard types] containsObject:NSFilenamesPboardType]) { + std::vector dropFiles; + NSArray* files = [pboard propertyListForType:NSFilenamesPboardType]; + for (NSString* file in files) + dropFiles.push_back(base::SysNSStringToUTF8(file)); + trayIcon_->NotfiyDropFiles(dropFiles); + return YES; + } + return NO; +} + +- (BOOL)shouldHighlight { + BOOL isMenuOpen = menuController_ && [menuController_ isMenuOpen]; + return isHighlightEnable_ && (inMouseEventSequence_ || isMenuOpen); +} + +- (gfx::Rect)getBoundsFromEvent:(NSEvent*)event { + NSRect frame = event.window.frame; gfx::Rect bounds(frame.origin.x, 0, NSWidth(frame), NSHeight(frame)); - // Flip coordinates to gfx (0,0 in top-left corner) using current screen. NSScreen* screen = [[NSScreen screens] objectAtIndex:0]; bounds.set_y(NSHeight([screen frame]) - NSMaxY(frame)); - - trayIcon_->NotifyClicked(bounds); + return bounds; } - -- (void)handleDoubleClick:(id)sender { - trayIcon_->NotifyDoubleClicked(); -} - @end namespace atom { -TrayIconCocoa::TrayIconCocoa() { - controller_.reset([[StatusItemController alloc] initWithIcon:this]); - - item_.reset([[[NSStatusBar systemStatusBar] - statusItemWithLength:NSVariableStatusItemLength] retain]); - [item_ setEnabled:YES]; - [item_ setTarget:controller_]; - [item_ setAction:@selector(handleClick:)]; - [item_ setDoubleAction:@selector(handleDoubleClick:)]; - [item_ setHighlightMode:YES]; +TrayIconCocoa::TrayIconCocoa() : menu_model_(nullptr) { } TrayIconCocoa::~TrayIconCocoa() { - // Remove the status item from the status bar. - [[NSStatusBar systemStatusBar] removeStatusItem:item_]; + [status_item_view_ removeItem]; + if (menu_model_) + menu_model_->RemoveObserver(this); } void TrayIconCocoa::SetImage(const gfx::Image& image) { - [item_ setImage:image.AsNSImage()]; + if (status_item_view_) { + [status_item_view_ setImage:image.AsNSImage()]; + } else { + status_item_view_.reset( + [[StatusItemView alloc] initWithImage:image.AsNSImage() + icon:this]); + } } void TrayIconCocoa::SetPressedImage(const gfx::Image& image) { - [item_ setAlternateImage:image.AsNSImage()]; + [status_item_view_ setAlternateImage:image.AsNSImage()]; } void TrayIconCocoa::SetToolTip(const std::string& tool_tip) { - [item_ setToolTip:base::SysUTF8ToNSString(tool_tip)]; + [status_item_view_ setToolTip:base::SysUTF8ToNSString(tool_tip)]; } void TrayIconCocoa::SetTitle(const std::string& title) { - [item_ setTitle:base::SysUTF8ToNSString(title)]; + [status_item_view_ setTitle:base::SysUTF8ToNSString(title)]; } void TrayIconCocoa::SetHighlightMode(bool highlight) { - [item_ setHighlightMode:highlight]; + [status_item_view_ setHighlight:highlight]; +} + +void TrayIconCocoa::PopUpContextMenu(const gfx::Point& pos) { + [status_item_view_ popUpContextMenu]; } void TrayIconCocoa::SetContextMenu(ui::SimpleMenuModel* menu_model) { + // Substribe to MenuClosed event. + if (menu_model_) + menu_model_->RemoveObserver(this); + static_cast(menu_model)->AddObserver(this); + + // Create native menu. menu_.reset([[AtomMenuController alloc] initWithModel:menu_model]); - [item_ setMenu:[menu_ menu]]; + [status_item_view_ setMenuController:menu_.get()]; +} + +void TrayIconCocoa::MenuClosed() { + [status_item_view_ setNeedsDisplay:YES]; } // static diff --git a/atom/browser/ui/tray_icon_observer.h b/atom/browser/ui/tray_icon_observer.h index 3a34888b531..fa8090d7d6c 100644 --- a/atom/browser/ui/tray_icon_observer.h +++ b/atom/browser/ui/tray_icon_observer.h @@ -5,6 +5,9 @@ #ifndef ATOM_BROWSER_UI_TRAY_ICON_OBSERVER_H_ #define ATOM_BROWSER_UI_TRAY_ICON_OBSERVER_H_ +#include +#include + namespace gfx { class Rect; } @@ -13,11 +16,13 @@ namespace atom { class TrayIconObserver { public: - virtual void OnClicked(const gfx::Rect&) {} - virtual void OnDoubleClicked() {} + virtual void OnClicked(const gfx::Rect& bounds, int modifiers) {} + virtual void OnDoubleClicked(const gfx::Rect& bounds, int modifiers) {} virtual void OnBalloonShow() {} virtual void OnBalloonClicked() {} virtual void OnBalloonClosed() {} + virtual void OnRightClicked(const gfx::Rect& bounds, int modifiers) {} + virtual void OnDropFiles(const std::vector& files) {} protected: virtual ~TrayIconObserver() {} diff --git a/atom/browser/ui/views/native_frame_view.cc b/atom/browser/ui/views/native_frame_view.cc new file mode 100644 index 00000000000..a434fb43496 --- /dev/null +++ b/atom/browser/ui/views/native_frame_view.cc @@ -0,0 +1,35 @@ +// Copyright (c) 2015 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/ui/views/native_frame_view.h" + +#include "atom/browser/native_window_views.h" + +namespace atom { + +namespace { + +const char kViewClassName[] = "AtomNativeFrameView"; + +} // namespace + +NativeFrameView::NativeFrameView(NativeWindowViews* window, + views::Widget* widget) + : views::NativeFrameView(widget), + window_(window) { +} + +gfx::Size NativeFrameView::GetMinimumSize() const { + return window_->GetMinimumSize(); +} + +gfx::Size NativeFrameView::GetMaximumSize() const { + return window_->GetMaximumSize(); +} + +const char* NativeFrameView::GetClassName() const { + return kViewClassName; +} + +} // namespace atom diff --git a/atom/browser/ui/views/native_frame_view.h b/atom/browser/ui/views/native_frame_view.h new file mode 100644 index 00000000000..acbe9cddc8d --- /dev/null +++ b/atom/browser/ui/views/native_frame_view.h @@ -0,0 +1,34 @@ +// Copyright (c) 2015 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_UI_VIEWS_NATIVE_FRAME_VIEW_H_ +#define ATOM_BROWSER_UI_VIEWS_NATIVE_FRAME_VIEW_H_ + +#include "ui/views/window/native_frame_view.h" + +namespace atom { + +class NativeWindowViews; + +// Like the views::NativeFrameView, but returns the min/max size from the +// NativeWindowViews. +class NativeFrameView : public views::NativeFrameView { + public: + NativeFrameView(NativeWindowViews* window, views::Widget* widget); + + protected: + // views::View: + gfx::Size GetMinimumSize() const override; + gfx::Size GetMaximumSize() const override; + const char* GetClassName() const override; + + private: + NativeWindowViews* window_; // weak ref. + + DISALLOW_COPY_AND_ASSIGN(NativeFrameView); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_UI_VIEWS_NATIVE_FRAME_VIEW_H_ diff --git a/atom/browser/ui/win/atom_desktop_window_tree_host_win.cc b/atom/browser/ui/win/atom_desktop_window_tree_host_win.cc new file mode 100644 index 00000000000..84a6d9aa3e5 --- /dev/null +++ b/atom/browser/ui/win/atom_desktop_window_tree_host_win.cc @@ -0,0 +1,28 @@ +// Copyright (c) 2015 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/ui/win/atom_desktop_window_tree_host_win.h" + +#include "atom/browser/ui/win/message_handler_delegate.h" + +namespace atom { + +AtomDesktopWindowTreeHostWin::AtomDesktopWindowTreeHostWin( + MessageHandlerDelegate* delegate, + views::internal::NativeWidgetDelegate* native_widget_delegate, + views::DesktopNativeWidgetAura* desktop_native_widget_aura) + : views::DesktopWindowTreeHostWin(native_widget_delegate, + desktop_native_widget_aura), + delegate_(delegate) { +} + +AtomDesktopWindowTreeHostWin::~AtomDesktopWindowTreeHostWin() { +} + +bool AtomDesktopWindowTreeHostWin::PreHandleMSG( + UINT message, WPARAM w_param, LPARAM l_param, LRESULT* result) { + return delegate_->PreHandleMSG(message, w_param, l_param, result); +} + +} // namespace atom diff --git a/atom/browser/ui/win/atom_desktop_window_tree_host_win.h b/atom/browser/ui/win/atom_desktop_window_tree_host_win.h new file mode 100644 index 00000000000..47e4cb6aed2 --- /dev/null +++ b/atom/browser/ui/win/atom_desktop_window_tree_host_win.h @@ -0,0 +1,39 @@ +// Copyright (c) 2015 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_UI_WIN_ATOM_DESKTOP_WINDOW_TREE_HOST_WIN_H_ +#define ATOM_BROWSER_UI_WIN_ATOM_DESKTOP_WINDOW_TREE_HOST_WIN_H_ + +#include + +#include + +#include "atom/browser/native_window.h" +#include "ui/views/widget/desktop_aura/desktop_window_tree_host_win.h" + +namespace atom { + +class MessageHandlerDelegate; + +class AtomDesktopWindowTreeHostWin : public views::DesktopWindowTreeHostWin { + public: + AtomDesktopWindowTreeHostWin( + MessageHandlerDelegate* delegate, + views::internal::NativeWidgetDelegate* native_widget_delegate, + views::DesktopNativeWidgetAura* desktop_native_widget_aura); + ~AtomDesktopWindowTreeHostWin() override; + + protected: + bool PreHandleMSG( + UINT message, WPARAM w_param, LPARAM l_param, LRESULT* result) override; + + private: + MessageHandlerDelegate* delegate_; // weak ref + + DISALLOW_COPY_AND_ASSIGN(AtomDesktopWindowTreeHostWin); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_UI_WIN_ATOM_DESKTOP_WINDOW_TREE_HOST_WIN_H_ diff --git a/atom/browser/ui/win/message_handler_delegate.cc b/atom/browser/ui/win/message_handler_delegate.cc new file mode 100644 index 00000000000..791d1fd816d --- /dev/null +++ b/atom/browser/ui/win/message_handler_delegate.cc @@ -0,0 +1,14 @@ +// Copyright (c) 2015 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/ui/win/message_handler_delegate.h" + +namespace atom { + +bool MessageHandlerDelegate::PreHandleMSG( + UINT message, WPARAM w_param, LPARAM l_param, LRESULT* result) { + return false; +} + +} // namespace atom diff --git a/atom/browser/ui/win/message_handler_delegate.h b/atom/browser/ui/win/message_handler_delegate.h new file mode 100644 index 00000000000..d8cfcf7fc43 --- /dev/null +++ b/atom/browser/ui/win/message_handler_delegate.h @@ -0,0 +1,26 @@ +// Copyright (c) 2015 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_UI_WIN_MESSAGE_HANDLER_DELEGATE_H_ +#define ATOM_BROWSER_UI_WIN_MESSAGE_HANDLER_DELEGATE_H_ + +#include + +namespace atom { + +class MessageHandlerDelegate { + public: + // Catch-all message handling and filtering. Called before + // HWNDMessageHandler's built-in handling, which may pre-empt some + // expectations in Views/Aura if messages are consumed. Returns true if the + // message was consumed by the delegate and should not be processed further + // by the HWNDMessageHandler. In this case, |result| is returned. |result| is + // not modified otherwise. + virtual bool PreHandleMSG( + UINT message, WPARAM w_param, LPARAM l_param, LRESULT* result); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_UI_WIN_MESSAGE_HANDLER_DELEGATE_H_ diff --git a/atom/browser/ui/win/notify_icon.cc b/atom/browser/ui/win/notify_icon.cc index 955a047fe1f..4026d9ec4a6 100644 --- a/atom/browser/ui/win/notify_icon.cc +++ b/atom/browser/ui/win/notify_icon.cc @@ -4,7 +4,10 @@ #include "atom/browser/ui/win/notify_icon.h" +#include + #include "atom/browser/ui/win/notify_icon_host.h" +#include "base/md5.h" #include "base/strings/string_number_conversions.h" #include "base/strings/utf_string_conversions.h" #include "base/win/windows_version.h" @@ -25,10 +28,34 @@ NotifyIcon::NotifyIcon(NotifyIconHost* host, icon_id_(id), window_(window), message_id_(message), - menu_model_(NULL) { + menu_model_(NULL), + has_tray_app_id_hash_(false) { + // NB: If we have an App Model ID, we should propagate that to the tray. + // Doing this prevents duplicate items from showing up in the notification + // preferences (i.e. "Always Show / Show notifications only / etc") + PWSTR explicit_app_id; + if (SUCCEEDED(GetCurrentProcessExplicitAppUserModelID(&explicit_app_id))) { + // GUIDs and MD5 hashes are the same length. So convenient! + base::MD5Sum(explicit_app_id, + sizeof(wchar_t) * wcslen(explicit_app_id), + reinterpret_cast(&tray_app_id_hash_)); + + // Set the GUID to version 4 as described in RFC 4122, section 4.4. + // The format of GUID version 4 must be like + // xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx, where y is one of [8, 9, A, B]. + tray_app_id_hash_.Data3 &= 0x0fff; + tray_app_id_hash_.Data3 |= 0x4000; + + // Set y to one of [8, 9, A, B]. + tray_app_id_hash_.Data4[0] = 1; + + has_tray_app_id_hash_ = true; + CoTaskMemFree(explicit_app_id); + } + NOTIFYICONDATA icon_data; InitIconData(&icon_data); - icon_data.uFlags = NIF_MESSAGE; + icon_data.uFlags |= NIF_MESSAGE; icon_data.uCallbackMessage = message_id_; BOOL result = Shell_NotifyIcon(NIM_ADD, &icon_data); // This can happen if the explorer process isn't running when we try to @@ -46,39 +73,34 @@ NotifyIcon::~NotifyIcon() { } void NotifyIcon::HandleClickEvent(const gfx::Point& cursor_pos, - bool left_mouse_click) { - // Pass to the observer if appropriate. + int modifiers, + bool left_mouse_click, + bool double_button_click) { + NOTIFYICONIDENTIFIER icon_id; + memset(&icon_id, 0, sizeof(NOTIFYICONIDENTIFIER)); + icon_id.uID = icon_id_; + icon_id.hWnd = window_; + icon_id.cbSize = sizeof(NOTIFYICONIDENTIFIER); + if (has_tray_app_id_hash_) + memcpy(reinterpret_cast(&icon_id.guidItem), + &tray_app_id_hash_, + sizeof(GUID)); + + RECT rect = { 0 }; + Shell_NotifyIconGetRect(&icon_id, &rect); + if (left_mouse_click) { - NOTIFYICONIDENTIFIER icon_id; - memset(&icon_id, 0, sizeof(NOTIFYICONIDENTIFIER)); - icon_id.uID = icon_id_; - icon_id.hWnd = window_; - icon_id.cbSize = sizeof(NOTIFYICONIDENTIFIER); - - RECT rect = { 0 }; - Shell_NotifyIconGetRect(&icon_id, &rect); - - NotifyClicked(gfx::Rect(rect)); + if (double_button_click) // double left click + NotifyDoubleClicked(gfx::Rect(rect), modifiers); + else // single left click + NotifyClicked(gfx::Rect(rect), modifiers); return; + } else if (!double_button_click) { // single right click + if (menu_model_) + PopUpContextMenu(cursor_pos); + else + NotifyRightClicked(gfx::Rect(rect), modifiers); } - - if (!menu_model_) - return; - - // Set our window as the foreground window, so the context menu closes when - // we click away from it. - if (!SetForegroundWindow(window_)) - return; - - views::MenuRunner menu_runner( - menu_model_, - views::MenuRunner::CONTEXT_MENU | views::MenuRunner::HAS_MNEMONICS); - ignore_result(menu_runner.RunMenuAt( - NULL, - NULL, - gfx::Rect(cursor_pos, gfx::Size()), - views::MENU_ANCHOR_TOPLEFT, - ui::MENU_SOURCE_MOUSE)); } void NotifyIcon::ResetIcon() { @@ -87,7 +109,7 @@ void NotifyIcon::ResetIcon() { // Delete any previously existing icon. Shell_NotifyIcon(NIM_DELETE, &icon_data); InitIconData(&icon_data); - icon_data.uFlags = NIF_MESSAGE; + icon_data.uFlags |= NIF_MESSAGE; icon_data.uCallbackMessage = message_id_; icon_data.hIcon = icon_.Get(); // If we have an image, then set the NIF_ICON flag, which tells @@ -104,7 +126,7 @@ void NotifyIcon::SetImage(const gfx::Image& image) { // Create the icon. NOTIFYICONDATA icon_data; InitIconData(&icon_data); - icon_data.uFlags = NIF_ICON; + icon_data.uFlags |= NIF_ICON; icon_.Set(IconUtil::CreateHICONFromSkBitmap(image.AsBitmap())); icon_data.hIcon = icon_.Get(); BOOL result = Shell_NotifyIcon(NIM_MODIFY, &icon_data); @@ -121,7 +143,7 @@ void NotifyIcon::SetToolTip(const std::string& tool_tip) { // Create the icon. NOTIFYICONDATA icon_data; InitIconData(&icon_data); - icon_data.uFlags = NIF_TIP; + icon_data.uFlags |= NIF_TIP; wcscpy_s(icon_data.szTip, base::UTF8ToUTF16(tool_tip).c_str()); BOOL result = Shell_NotifyIcon(NIM_MODIFY, &icon_data); if (!result) @@ -133,7 +155,7 @@ void NotifyIcon::DisplayBalloon(const gfx::Image& icon, const base::string16& contents) { NOTIFYICONDATA icon_data; InitIconData(&icon_data); - icon_data.uFlags = NIF_INFO; + icon_data.uFlags |= NIF_INFO; icon_data.dwInfoFlags = NIIF_INFO; wcscpy_s(icon_data.szInfoTitle, title.c_str()); wcscpy_s(icon_data.szInfo, contents.c_str()); @@ -151,6 +173,26 @@ void NotifyIcon::DisplayBalloon(const gfx::Image& icon, LOG(WARNING) << "Unable to create status tray balloon."; } +void NotifyIcon::PopUpContextMenu(const gfx::Point& pos) { + // Returns if context menu isn't set. + if (!menu_model_) + return; + // Set our window as the foreground window, so the context menu closes when + // we click away from it. + if (!SetForegroundWindow(window_)) + return; + + views::MenuRunner menu_runner( + menu_model_, + views::MenuRunner::CONTEXT_MENU | views::MenuRunner::HAS_MNEMONICS); + ignore_result(menu_runner.RunMenuAt( + NULL, + NULL, + gfx::Rect(pos, gfx::Size()), + views::MENU_ANCHOR_TOPLEFT, + ui::MENU_SOURCE_MOUSE)); +} + void NotifyIcon::SetContextMenu(ui::SimpleMenuModel* menu_model) { menu_model_ = menu_model; } @@ -160,6 +202,13 @@ void NotifyIcon::InitIconData(NOTIFYICONDATA* icon_data) { icon_data->cbSize = sizeof(NOTIFYICONDATA); icon_data->hWnd = window_; icon_data->uID = icon_id_; + + if (has_tray_app_id_hash_) { + icon_data->uFlags |= NIF_GUID; + memcpy(reinterpret_cast(&icon_data->guidItem), + &tray_app_id_hash_, + sizeof(GUID)); + } } } // namespace atom diff --git a/atom/browser/ui/win/notify_icon.h b/atom/browser/ui/win/notify_icon.h index 12eea1fcf72..136186b689b 100644 --- a/atom/browser/ui/win/notify_icon.h +++ b/atom/browser/ui/win/notify_icon.h @@ -33,7 +33,10 @@ class NotifyIcon : public TrayIcon { // Handles a click event from the user - if |left_button_click| is true and // there is a registered observer, passes the click event to the observer, // otherwise displays the context menu if there is one. - void HandleClickEvent(const gfx::Point& cursor_pos, bool left_button_click); + void HandleClickEvent(const gfx::Point& cursor_pos, + int modifiers, + bool left_button_click, + bool double_button_click); // Re-creates the status tray icon now after the taskbar has been created. void ResetIcon(); @@ -49,6 +52,7 @@ class NotifyIcon : public TrayIcon { void DisplayBalloon(const gfx::Image& icon, const base::string16& title, const base::string16& contents) override; + void PopUpContextMenu(const gfx::Point& pos) override; void SetContextMenu(ui::SimpleMenuModel* menu_model) override; private: @@ -75,6 +79,10 @@ class NotifyIcon : public TrayIcon { // The context menu. ui::SimpleMenuModel* menu_model_; + // A hash of the app model ID + GUID tray_app_id_hash_; + bool has_tray_app_id_hash_; + DISALLOW_COPY_AND_ASSIGN(NotifyIcon); }; diff --git a/atom/browser/ui/win/notify_icon_host.cc b/atom/browser/ui/win/notify_icon_host.cc index 4aac629f248..2c84837e714 100644 --- a/atom/browser/ui/win/notify_icon_host.cc +++ b/atom/browser/ui/win/notify_icon_host.cc @@ -5,13 +5,16 @@ #include "atom/browser/ui/win/notify_icon_host.h" #include +#include #include "atom/browser/ui/win/notify_icon.h" #include "base/bind.h" #include "base/stl_util.h" #include "base/threading/non_thread_safe.h" #include "base/threading/thread.h" +#include "base/win/win_util.h" #include "base/win/wrapped_window_proc.h" +#include "ui/events/event_constants.h" #include "ui/gfx/screen.h" #include "ui/gfx/win/hwnd_util.h" @@ -26,6 +29,24 @@ const UINT kBaseIconId = 2; const wchar_t kNotifyIconHostWindowClass[] = L"Electron_NotifyIconHostWindow"; +bool IsWinPressed() { + return ((::GetKeyState(VK_LWIN) & 0x8000) == 0x8000) || + ((::GetKeyState(VK_RWIN) & 0x8000) == 0x8000); +} + +int GetKeyboardModifers() { + int modifiers = ui::EF_NONE; + if (base::win::IsShiftPressed()) + modifiers |= ui::EF_SHIFT_DOWN; + if (base::win::IsCtrlPressed()) + modifiers |= ui::EF_CONTROL_DOWN; + if (base::win::IsAltPressed()) + modifiers |= ui::EF_ALT_DOWN; + if (IsWinPressed()) + modifiers |= ui::EF_COMMAND_DOWN; + return modifiers; +} + } // namespace NotifyIconHost::NotifyIconHost() @@ -146,12 +167,18 @@ LRESULT CALLBACK NotifyIconHost::WndProc(HWND hwnd, case WM_LBUTTONDOWN: case WM_RBUTTONDOWN: + case WM_LBUTTONDBLCLK: + case WM_RBUTTONDBLCLK: case WM_CONTEXTMENU: // Walk our icons, find which one was clicked on, and invoke its // HandleClickEvent() method. gfx::Point cursor_pos( gfx::Screen::GetNativeScreen()->GetCursorScreenPoint()); - win_icon->HandleClickEvent(cursor_pos, lparam == WM_LBUTTONDOWN); + win_icon->HandleClickEvent( + cursor_pos, + GetKeyboardModifers(), + (lparam == WM_LBUTTONDOWN || lparam == WM_LBUTTONDBLCLK), + (lparam == WM_LBUTTONDBLCLK || lparam == WM_RBUTTONDBLCLK)); return TRUE; } } diff --git a/atom/browser/ui/win/taskbar_host.cc b/atom/browser/ui/win/taskbar_host.cc new file mode 100644 index 00000000000..a8e6ff2926c --- /dev/null +++ b/atom/browser/ui/win/taskbar_host.cc @@ -0,0 +1,165 @@ +// Copyright (c) 2015 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/ui/win/taskbar_host.h" + +#include + +#include "base/stl_util.h" +#include "base/win/scoped_gdi_object.h" +#include "base/strings/utf_string_conversions.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/gfx/icon_util.h" + +namespace atom { + +namespace { + +// From MSDN: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378460(v=vs.85).aspx#thumbbars +// The thumbnail toolbar has a maximum of seven buttons due to the limited room. +const size_t kMaxButtonsCount = 7; + +// The base id of Thumbar button. +const int kButtonIdBase = 40001; + +bool GetThumbarButtonFlags(const std::vector& flags, + THUMBBUTTONFLAGS* out) { + THUMBBUTTONFLAGS result = THBF_ENABLED; // THBF_ENABLED == 0 + for (const auto& flag : flags) { + if (flag == "disabled") + result |= THBF_DISABLED; + else if (flag == "dismissonclick") + result |= THBF_DISMISSONCLICK; + else if (flag == "nobackground") + result |= THBF_NOBACKGROUND; + else if (flag == "hidden") + result |= THBF_HIDDEN; + else if (flag == "noninteractive") + result |= THBF_NONINTERACTIVE; + else + return false; + } + *out = result; + return true; +} + +} // namespace + +TaskbarHost::TaskbarHost() : thumbar_buttons_added_(false) { +} + +TaskbarHost::~TaskbarHost() { +} + +bool TaskbarHost::SetThumbarButtons( + HWND window, const std::vector& buttons) { + if (buttons.size() > kMaxButtonsCount || !InitailizeTaskbar()) + return false; + + callback_map_.clear(); + + // The number of buttons in thumbar can not be changed once it is created, + // so we have to claim kMaxButtonsCount buttons initialy in case users add + // more buttons later. + base::win::ScopedHICON icons[kMaxButtonsCount] = {}; + THUMBBUTTON thumb_buttons[kMaxButtonsCount] = {}; + + for (size_t i = 0; i < kMaxButtonsCount; ++i) { + THUMBBUTTON& thumb_button = thumb_buttons[i]; + + // Set ID. + thumb_button.iId = kButtonIdBase + i; + thumb_button.dwMask = THB_FLAGS; + + if (i >= buttons.size()) { + // This button is used to occupy the place in toolbar, and it does not + // show. + thumb_button.dwFlags = THBF_HIDDEN; + continue; + } + + // This button is user's button. + const ThumbarButton& button = buttons[i]; + + // Generate flags. + thumb_button.dwFlags = THBF_ENABLED; + if (!GetThumbarButtonFlags(button.flags, &thumb_button.dwFlags)) + return false; + + // Set icon. + if (!button.icon.IsEmpty()) { + thumb_button.dwMask |= THB_ICON; + icons[i] = IconUtil::CreateHICONFromSkBitmap(button.icon.AsBitmap()); + thumb_button.hIcon = icons[i].Get(); + } + + // Set tooltip. + if (!button.tooltip.empty()) { + thumb_button.dwMask |= THB_TOOLTIP; + wcscpy_s(thumb_button.szTip, base::UTF8ToUTF16(button.tooltip).c_str()); + } + + // Save callback. + callback_map_[thumb_button.iId] = button.clicked_callback; + } + + // Finally add them to taskbar. + HRESULT r; + if (thumbar_buttons_added_) + r = taskbar_->ThumbBarUpdateButtons(window, kMaxButtonsCount, + thumb_buttons); + else + r = taskbar_->ThumbBarAddButtons(window, kMaxButtonsCount, thumb_buttons); + + thumbar_buttons_added_ = true; + return SUCCEEDED(r); +} + +bool TaskbarHost::SetProgressBar(HWND window, double value) { + if (!InitailizeTaskbar()) + return false; + + HRESULT r; + if (value > 1.0) + r = taskbar_->SetProgressState(window, TBPF_INDETERMINATE); + else if (value < 0) + r = taskbar_->SetProgressState(window, TBPF_NOPROGRESS); + else + r = taskbar_->SetProgressValue(window, static_cast(value * 100), 100); + return SUCCEEDED(r); +} + +bool TaskbarHost::SetOverlayIcon( + HWND window, const gfx::Image& overlay, const std::string& text) { + if (!InitailizeTaskbar()) + return false; + + base::win::ScopedHICON icon( + IconUtil::CreateHICONFromSkBitmap(overlay.AsBitmap())); + return SUCCEEDED( + taskbar_->SetOverlayIcon(window, icon, base::UTF8ToUTF16(text).c_str())); +} + +bool TaskbarHost::HandleThumbarButtonEvent(int button_id) { + if (ContainsKey(callback_map_, button_id)) { + auto callback = callback_map_[button_id]; + if (!callback.is_null()) + callback.Run(); + return true; + } + return false; +} + +bool TaskbarHost::InitailizeTaskbar() { + if (FAILED(taskbar_.CreateInstance(CLSID_TaskbarList, + nullptr, + CLSCTX_INPROC_SERVER)) || + FAILED(taskbar_->HrInit())) { + return false; + } else { + return true; + } +} + +} // namespace atom diff --git a/atom/browser/ui/win/taskbar_host.h b/atom/browser/ui/win/taskbar_host.h new file mode 100644 index 00000000000..185b88a6b5b --- /dev/null +++ b/atom/browser/ui/win/taskbar_host.h @@ -0,0 +1,64 @@ +// Copyright (c) 2015 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_UI_WIN_TASKBAR_HOST_H_ +#define ATOM_BROWSER_UI_WIN_TASKBAR_HOST_H_ + +#include + +#include +#include +#include + +#include "base/callback.h" +#include "base/win/scoped_comptr.h" +#include "ui/gfx/image/image.h" + +namespace atom { + +class TaskbarHost { + public: + struct ThumbarButton { + std::string tooltip; + gfx::Image icon; + std::vector flags; + base::Closure clicked_callback; + }; + + TaskbarHost(); + virtual ~TaskbarHost(); + + // Add or update the buttons in thumbar. + bool SetThumbarButtons( + HWND window, const std::vector& buttons); + + // Set the progress state in taskbar. + bool SetProgressBar(HWND window, double value); + + // Set the overlay icon in taskbar. + bool SetOverlayIcon( + HWND window, const gfx::Image& overlay, const std::string& text); + + // Called by the window that there is a button in thumbar clicked. + bool HandleThumbarButtonEvent(int button_id); + + private: + // Initailize the taskbar object. + bool InitailizeTaskbar(); + + using CallbackMap = std::map; + CallbackMap callback_map_; + + // The COM object of taskbar. + base::win::ScopedComPtr taskbar_; + + // Whether we have already added the buttons to thumbar. + bool thumbar_buttons_added_; + + DISALLOW_COPY_AND_ASSIGN(TaskbarHost); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_UI_WIN_TASKBAR_HOST_H_ diff --git a/atom/browser/ui/x/x_window_utils.cc b/atom/browser/ui/x/x_window_utils.cc index eaef5475b77..a15affb05f1 100644 --- a/atom/browser/ui/x/x_window_utils.cc +++ b/atom/browser/ui/x/x_window_utils.cc @@ -7,6 +7,9 @@ #include #include "base/strings/string_util.h" +#include "dbus/bus.h" +#include "dbus/object_proxy.h" +#include "dbus/message.h" #include "ui/base/x/x11_util.h" namespace atom { @@ -46,4 +49,37 @@ void SetWindowType(::Window xwindow, const std::string& type) { reinterpret_cast(&window_type), 1); } +bool ShouldUseGlobalMenuBar() { + dbus::Bus::Options options; + scoped_refptr bus(new dbus::Bus(options)); + + dbus::ObjectProxy* object_proxy = + bus->GetObjectProxy(DBUS_SERVICE_DBUS, dbus::ObjectPath(DBUS_PATH_DBUS)); + dbus::MethodCall method_call(DBUS_INTERFACE_DBUS, "ListNames"); + scoped_ptr response(object_proxy->CallMethodAndBlock( + &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT)); + if (!response) { + bus->ShutdownAndBlock(); + return false; + } + + dbus::MessageReader reader(response.get()); + dbus::MessageReader array_reader(NULL); + if (!reader.PopArray(&array_reader)) { + bus->ShutdownAndBlock(); + return false; + } + while (array_reader.HasMoreData()) { + std::string name; + if (array_reader.PopString(&name) && + name == "com.canonical.AppMenu.Registrar") { + bus->ShutdownAndBlock(); + return true; + } + } + + bus->ShutdownAndBlock(); + return false; +} + } // namespace atom diff --git a/atom/browser/ui/x/x_window_utils.h b/atom/browser/ui/x/x_window_utils.h index ccf56d1eb9c..16f3ddac6cc 100644 --- a/atom/browser/ui/x/x_window_utils.h +++ b/atom/browser/ui/x/x_window_utils.h @@ -22,6 +22,9 @@ void SetWMSpecState(::Window xwindow, bool enabled, ::Atom state); // Sets the _NET_WM_WINDOW_TYPE of window. void SetWindowType(::Window xwindow, const std::string& type); +// Returns true if the bus name "com.canonical.AppMenu.Registrar" is available. +bool ShouldUseGlobalMenuBar(); + } // namespace atom #endif // ATOM_BROWSER_UI_X_X_WINDOW_UTILS_H_ diff --git a/atom/browser/web_dialog_helper.cc b/atom/browser/web_dialog_helper.cc index f6f23345358..c3d2a1d0f23 100644 --- a/atom/browser/web_dialog_helper.cc +++ b/atom/browser/web_dialog_helper.cc @@ -4,17 +4,66 @@ #include "atom/browser/web_dialog_helper.h" +#include #include +#include "atom/browser/atom_browser_context.h" +#include "atom/browser/native_window.h" #include "atom/browser/ui/file_dialog.h" #include "base/bind.h" #include "base/files/file_enumerator.h" +#include "base/files/file_path.h" +#include "base/prefs/pref_service.h" #include "base/strings/utf_string_conversions.h" +#include "chrome/common/pref_names.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/web_contents.h" #include "content/public/common/file_chooser_file_info.h" +#include "net/base/mime_util.h" #include "ui/shell_dialogs/selected_file_info.h" +namespace { + +file_dialog::Filters GetFileTypesFromAcceptType( + const std::vector& accept_types) { + file_dialog::Filters filters; + if (accept_types.empty()) + return filters; + + std::vector extensions; + + for (const auto& accept_type : accept_types) { + std::string ascii_type = base::UTF16ToASCII(accept_type); + if (ascii_type[0] == '.') { + // If the type starts with a period it is assumed to be a file extension, + // like `.txt`, // so we just have to add it to the list. + base::FilePath::StringType extension( + ascii_type.begin(), ascii_type.end()); + // Skip the first character. + extensions.push_back(extension.substr(1)); + } else { + // For MIME Type, `audio/*, vidio/*, image/* + net::GetExtensionsForMimeType(ascii_type, &extensions); + } + } + + // If no valid exntesion is added, return empty filters. + if (extensions.empty()) + return filters; + + filters.push_back(file_dialog::Filter()); + for (const auto& extension : extensions) { +#if defined(OS_WIN) + filters[0].second.push_back(base::UTF16ToASCII(extension)); +#else + filters[0].second.push_back(extension); +#endif + } + return filters; +} + +} // namespace + namespace atom { WebDialogHelper::WebDialogHelper(NativeWindow* window) @@ -25,15 +74,18 @@ WebDialogHelper::WebDialogHelper(NativeWindow* window) WebDialogHelper::~WebDialogHelper() { } + void WebDialogHelper::RunFileChooser(content::WebContents* web_contents, const content::FileChooserParams& params) { std::vector result; + file_dialog::Filters filters = GetFileTypesFromAcceptType( + params.accept_types); if (params.mode == content::FileChooserParams::Save) { base::FilePath path; if (file_dialog::ShowSaveDialog(window_, base::UTF16ToUTF8(params.title), params.default_file_name, - file_dialog::Filters(), + filters, &path)) { content::FileChooserFileInfo info; info.file_path = path; @@ -56,10 +108,14 @@ void WebDialogHelper::RunFileChooser(content::WebContents* web_contents, } std::vector paths; + AtomBrowserContext* browser_context = static_cast( + window_->web_contents()->GetBrowserContext()); + base::FilePath default_file_path = browser_context->prefs()->GetFilePath( + prefs::kSelectFileLastDirectory).Append(params.default_file_name); if (file_dialog::ShowOpenDialog(window_, base::UTF16ToUTF8(params.title), - params.default_file_name, - file_dialog::Filters(), + default_file_path, + filters, flags, &paths)) { for (auto& path : paths) { @@ -68,6 +124,10 @@ void WebDialogHelper::RunFileChooser(content::WebContents* web_contents, info.display_name = path.BaseName().value(); result.push_back(info); } + if (!paths.empty()) { + browser_context->prefs()->SetFilePath(prefs::kSelectFileLastDirectory, + paths[0].DirName()); + } } } diff --git a/atom/browser/web_view_guest_delegate.cc b/atom/browser/web_view_guest_delegate.cc index 98fa72da22d..38f4da4c1f9 100644 --- a/atom/browser/web_view_guest_delegate.cc +++ b/atom/browser/web_view_guest_delegate.cc @@ -5,7 +5,9 @@ #include "atom/browser/web_view_guest_delegate.h" #include "atom/browser/api/atom_api_web_contents.h" +#include "atom/common/native_mate_converters/gurl_converter.h" #include "content/public/browser/guest_host.h" +#include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/render_widget_host_view.h" @@ -128,6 +130,12 @@ void WebViewGuestDelegate::RenderViewReady() { render_view_host_view->SetBackgroundColor(SK_ColorTRANSPARENT); } +void WebViewGuestDelegate::DidCommitProvisionalLoadForFrame( + content::RenderFrameHost* render_frame_host, + const GURL& url, ui::PageTransition transition_type) { + api_web_contents_->Emit("load-commit", url, !render_frame_host->GetParent()); +} + void WebViewGuestDelegate::DidAttach(int guest_proxy_routing_id) { api_web_contents_->Emit("did-attach"); } diff --git a/atom/browser/web_view_guest_delegate.h b/atom/browser/web_view_guest_delegate.h index 267b5b248af..3312b46104a 100644 --- a/atom/browser/web_view_guest_delegate.h +++ b/atom/browser/web_view_guest_delegate.h @@ -59,6 +59,9 @@ class WebViewGuestDelegate : public content::BrowserPluginGuestDelegate, protected: // content::WebContentsObserver: void RenderViewReady() override; + void DidCommitProvisionalLoadForFrame( + content::RenderFrameHost* render_frame_host, + const GURL& url, ui::PageTransition transition_type) override; // content::BrowserPluginGuestDelegate: void DidAttach(int guest_proxy_routing_id) final; diff --git a/atom/common/api/api_messages.h b/atom/common/api/api_messages.h index eeb26614847..b32df3cef39 100644 --- a/atom/common/api/api_messages.h +++ b/atom/common/api/api_messages.h @@ -34,6 +34,10 @@ IPC_MESSAGE_ROUTED2(AtomViewMsg_Message, base::string16 /* channel */, base::ListValue /* arguments */) +IPC_MESSAGE_ROUTED2(AtomViewMsg_ExecuteJavaScript, + base::string16 /* code */, + bool /* has user gesture */) + // Sent by the renderer when the draggable regions are updated. IPC_MESSAGE_ROUTED1(AtomViewHostMsg_UpdateDraggableRegions, std::vector /* regions */) diff --git a/atom/common/api/atom_api_asar.cc b/atom/common/api/atom_api_asar.cc index 07d7c608d16..24e9ce9b066 100644 --- a/atom/common/api/atom_api_asar.cc +++ b/atom/common/api/atom_api_asar.cc @@ -8,9 +8,9 @@ #include "atom_natives.h" // NOLINT: This file is generated with coffee2c. #include "atom/common/asar/archive.h" +#include "atom/common/native_mate_converters/callback.h" #include "atom/common/native_mate_converters/file_path_converter.h" #include "native_mate/arguments.h" -#include "native_mate/callback.h" #include "native_mate/dictionary.h" #include "native_mate/object_template_builder.h" #include "native_mate/wrappable.h" diff --git a/atom/common/api/atom_api_native_image.cc b/atom/common/api/atom_api_native_image.cc index bd66ad511f7..40a73a7de3f 100644 --- a/atom/common/api/atom_api_native_image.cc +++ b/atom/common/api/atom_api_native_image.cc @@ -78,7 +78,7 @@ bool AddImageSkiaRep(gfx::ImageSkia* image, if (!decoded) return false; - image->AddRepresentation(gfx::ImageSkiaRep(*decoded.release(), scale_factor)); + image->AddRepresentation(gfx::ImageSkiaRep(*decoded, scale_factor)); return true; } @@ -113,7 +113,7 @@ bool PopulateImageSkiaRepsFromPath(gfx::ImageSkia* image, } #if defined(OS_MACOSX) -bool IsTemplateImage(const base::FilePath& path) { +bool IsTemplateFilename(const base::FilePath& path) { return (MatchPattern(path.value(), "*Template.*") || MatchPattern(path.value(), "*Template@*x.*")); } @@ -139,6 +139,7 @@ mate::ObjectTemplateBuilder NativeImage::GetObjectTemplateBuilder( .SetMethod("isEmpty", &NativeImage::IsEmpty) .SetMethod("getSize", &NativeImage::GetSize) .SetMethod("setTemplateImage", &NativeImage::SetTemplateImage) + .SetMethod("isTemplateImage", &NativeImage::IsTemplateImage) .Build()); return mate::ObjectTemplateBuilder( @@ -180,6 +181,10 @@ gfx::Size NativeImage::GetSize() { #if !defined(OS_MACOSX) void NativeImage::SetTemplateImage(bool setAsTemplate) { } + +bool NativeImage::IsTemplateImage() { + return false; +} #endif // static @@ -217,7 +222,7 @@ mate::Handle NativeImage::CreateFromPath( gfx::Image image(image_skia); mate::Handle handle = Create(isolate, image); #if defined(OS_MACOSX) - if (IsTemplateImage(path)) + if (IsTemplateFilename(path)) handle->SetTemplateImage(true); #endif return handle; diff --git a/atom/common/api/atom_api_native_image.h b/atom/common/api/atom_api_native_image.h index 8cb43d06e15..1f0fe946ba5 100644 --- a/atom/common/api/atom_api_native_image.h +++ b/atom/common/api/atom_api_native_image.h @@ -67,6 +67,8 @@ class NativeImage : public mate::Wrappable { // Mark the image as template image. void SetTemplateImage(bool setAsTemplate); + // Determine if the image is a template image. + bool IsTemplateImage(); gfx::Image image_; diff --git a/atom/common/api/atom_api_native_image_mac.mm b/atom/common/api/atom_api_native_image_mac.mm index a80462766bc..ad72d4b1492 100644 --- a/atom/common/api/atom_api_native_image_mac.mm +++ b/atom/common/api/atom_api_native_image_mac.mm @@ -14,6 +14,10 @@ void NativeImage::SetTemplateImage(bool setAsTemplate) { [image_.AsNSImage() setTemplate:setAsTemplate]; } +bool NativeImage::IsTemplateImage() { + return [image_.AsNSImage() isTemplate]; +} + } // namespace api } // namespace atom diff --git a/atom/common/api/atom_bindings.cc b/atom/common/api/atom_bindings.cc index fa4a88d5843..c748647f48d 100644 --- a/atom/common/api/atom_bindings.cc +++ b/atom/common/api/atom_bindings.cc @@ -98,6 +98,10 @@ void AtomBindings::OnCallNextTick(uv_async_t* handle) { if (tick_info->in_tick()) continue; + if (tick_info->length() == 0) { + env->isolate()->RunMicrotasks(); + } + if (tick_info->length() == 0) { tick_info->set_index(0); continue; diff --git a/atom/common/api/event_emitter_caller.cc b/atom/common/api/event_emitter_caller.cc new file mode 100644 index 00000000000..6b0a07ad414 --- /dev/null +++ b/atom/common/api/event_emitter_caller.cc @@ -0,0 +1,32 @@ +// Copyright (c) 2015 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/common/api/event_emitter_caller.h" + +#include "atom/common/api/locker.h" +#include "base/memory/scoped_ptr.h" +#include "third_party/WebKit/public/web/WebScopedMicrotaskSuppression.h" + +#include "atom/common/node_includes.h" + +namespace mate { + +namespace internal { + +v8::Local CallEmitWithArgs(v8::Isolate* isolate, + v8::Local obj, + ValueVector* args) { + // Perform microtask checkpoint after running JavaScript. + scoped_ptr script_scope( + Locker::IsBrowserProcess() ? + nullptr : new blink::WebScopedRunV8Script(isolate)); + // Use node::MakeCallback to call the callback, and it will also run pending + // tasks in Node.js. + return node::MakeCallback( + isolate, obj, "emit", args->size(), &args->front()); +} + +} // namespace internal + +} // namespace mate diff --git a/atom/common/event_emitter_caller.h b/atom/common/api/event_emitter_caller.h similarity index 92% rename from atom/common/event_emitter_caller.h rename to atom/common/api/event_emitter_caller.h index e8ad50a4537..a2567da9d10 100644 --- a/atom/common/event_emitter_caller.h +++ b/atom/common/api/event_emitter_caller.h @@ -2,8 +2,8 @@ // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. -#ifndef ATOM_COMMON_EVENT_EMITTER_CALLER_H_ -#define ATOM_COMMON_EVENT_EMITTER_CALLER_H_ +#ifndef ATOM_COMMON_API_EVENT_EMITTER_CALLER_H_ +#define ATOM_COMMON_API_EVENT_EMITTER_CALLER_H_ #include @@ -50,4 +50,4 @@ v8::Local EmitEvent(v8::Isolate* isolate, } // namespace mate -#endif // ATOM_COMMON_EVENT_EMITTER_CALLER_H_ +#endif // ATOM_COMMON_API_EVENT_EMITTER_CALLER_H_ diff --git a/atom/common/api/lib/callbacks-registry.coffee b/atom/common/api/lib/callbacks-registry.coffee index b549d17c728..8f5eb62916c 100644 --- a/atom/common/api/lib/callbacks-registry.coffee +++ b/atom/common/api/lib/callbacks-registry.coffee @@ -1,3 +1,5 @@ +savedGlobal = global # the "global.global" might be deleted later + module.exports = class CallbacksRegistry constructor: -> @@ -16,10 +18,10 @@ class CallbacksRegistry @callbacks[id] ? -> call: (id, args...) -> - @get(id).call global, args... + @get(id).call savedGlobal, args... apply: (id, args...) -> - @get(id).apply global, args... + @get(id).apply savedGlobal, args... remove: (id) -> delete @callbacks[id] diff --git a/atom/common/api/locker.cc b/atom/common/api/locker.cc new file mode 100644 index 00000000000..fe0b23479a4 --- /dev/null +++ b/atom/common/api/locker.cc @@ -0,0 +1,17 @@ +// 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.chromium file. + +#include "atom/common/api/locker.h" + +namespace mate { + +Locker::Locker(v8::Isolate* isolate) { + if (IsBrowserProcess()) + locker_.reset(new v8::Locker(isolate)); +} + +Locker::~Locker() { +} + +} // namespace mate diff --git a/atom/common/api/locker.h b/atom/common/api/locker.h new file mode 100644 index 00000000000..201217ff625 --- /dev/null +++ b/atom/common/api/locker.h @@ -0,0 +1,34 @@ +// 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.chromium file. + +#ifndef ATOM_COMMON_API_LOCKER_H_ +#define ATOM_COMMON_API_LOCKER_H_ + +#include "base/memory/scoped_ptr.h" +#include "v8/include/v8.h" + +namespace mate { + +// Only lock when lockers are used in current thread. +class Locker { + public: + explicit Locker(v8::Isolate* isolate); + ~Locker(); + + // Returns whether current process is browser process, currently we detect it + // by checking whether current has used V8 Lock, but it might be a bad idea. + static inline bool IsBrowserProcess() { return v8::Locker::IsActive(); } + + private: + void* operator new(size_t size); + void operator delete(void*, size_t); + + scoped_ptr locker_; + + DISALLOW_COPY_AND_ASSIGN(Locker); +}; + +} // namespace mate + +#endif // ATOM_COMMON_API_LOCKER_H_ diff --git a/atom/common/asar/archive.cc b/atom/common/asar/archive.cc index 79b82416cd0..be99530c9c9 100644 --- a/atom/common/asar/archive.cc +++ b/atom/common/asar/archive.cc @@ -54,6 +54,11 @@ bool GetChildNode(const base::DictionaryValue* root, const std::string& name, const base::DictionaryValue* dir, const base::DictionaryValue** out) { + if (name == "") { + *out = root; + return true; + } + const base::DictionaryValue* files = NULL; return GetFilesNode(root, dir, &files) && files->GetDictionaryWithoutPathExpansion(name, out); diff --git a/atom/common/atom_version.h b/atom/common/atom_version.h index f8543df7278..405a104a76f 100644 --- a/atom/common/atom_version.h +++ b/atom/common/atom_version.h @@ -6,8 +6,8 @@ #define ATOM_VERSION_H #define ATOM_MAJOR_VERSION 0 -#define ATOM_MINOR_VERSION 29 -#define ATOM_PATCH_VERSION 2 +#define ATOM_MINOR_VERSION 30 +#define ATOM_PATCH_VERSION 4 #define ATOM_VERSION_IS_RELEASE 1 diff --git a/atom/common/crash_reporter/crash_reporter_win.cc b/atom/common/crash_reporter/crash_reporter_win.cc index a348cf01261..be096da80e2 100644 --- a/atom/common/crash_reporter/crash_reporter_win.cc +++ b/atom/common/crash_reporter/crash_reporter_win.cc @@ -21,6 +21,7 @@ const MINIDUMP_TYPE kSmallDumpType = static_cast( MiniDumpWithProcessThreadData | // Get PEB and TEB. MiniDumpWithUnloadedModules); // Get unloaded modules when available. +const wchar_t kWaitEventFormat[] = L"$1CrashServiceWaitEvent"; const wchar_t kPipeNameFormat[] = L"\\\\.\\pipe\\$1 Crash Service"; } // namespace @@ -47,12 +48,15 @@ void CrashReporterWin::InitBreakpad(const std::string& product_name, base::string16 pipe_name = ReplaceStringPlaceholders( kPipeNameFormat, base::UTF8ToUTF16(product_name), NULL); + base::string16 wait_name = ReplaceStringPlaceholders( + kWaitEventFormat, base::UTF8ToUTF16(product_name), NULL); // Wait until the crash service is started. - HANDLE waiting_event = - ::CreateEventW(NULL, TRUE, FALSE, L"g_atom_shell_crash_service"); - if (waiting_event != INVALID_HANDLE_VALUE) - WaitForSingleObject(waiting_event, 1000); + HANDLE wait_event = ::CreateEventW(NULL, TRUE, FALSE, wait_name.c_str()); + if (wait_event != NULL) { + WaitForSingleObject(wait_event, 1000); + CloseHandle(wait_event); + } // ExceptionHandler() attaches our handler and ~ExceptionHandler() detaches // it, so we must explicitly reset *before* we instantiate our new handler diff --git a/atom/common/crash_reporter/win/crash_service.cc b/atom/common/crash_reporter/win/crash_service.cc index 10e0fdfcc55..d315b0b9419 100644 --- a/atom/common/crash_reporter/win/crash_service.cc +++ b/atom/common/crash_reporter/win/crash_service.cc @@ -14,6 +14,7 @@ #include "base/files/file_util.h" #include "base/logging.h" #include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" #include "base/time/time.h" #include "base/win/windows_version.h" #include "vendor/breakpad/src/client/windows/crash_generation/client_info.h" @@ -24,6 +25,9 @@ namespace breakpad { namespace { +const wchar_t kWaitEventFormat[] = L"$1CrashServiceWaitEvent"; +const wchar_t kClassNameFormat[] = L"$1CrashServiceWindow"; + const wchar_t kTestPipeName[] = L"\\\\.\\pipe\\ChromeCrashServices"; const wchar_t kGoogleReportURL[] = L"https://clients2.google.com/cr/report"; @@ -111,13 +115,18 @@ LRESULT __stdcall CrashSvcWndProc(HWND hwnd, UINT message, // This is the main and only application window. HWND g_top_window = NULL; -bool CreateTopWindow(HINSTANCE instance, bool visible) { +bool CreateTopWindow(HINSTANCE instance, + const base::string16& application_name, + bool visible) { + base::string16 class_name = ReplaceStringPlaceholders( + kClassNameFormat, application_name, NULL); + WNDCLASSEXW wcx = {0}; wcx.cbSize = sizeof(wcx); wcx.style = CS_HREDRAW | CS_VREDRAW; wcx.lpfnWndProc = CrashSvcWndProc; wcx.hInstance = instance; - wcx.lpszClassName = L"crash_svc_class"; + wcx.lpszClassName = class_name.c_str(); ATOM atom = ::RegisterClassExW(&wcx); DWORD style = visible ? WS_POPUPWINDOW | WS_VISIBLE : WS_OVERLAPPED; @@ -193,7 +202,8 @@ CrashService::~CrashService() { delete sender_; } -bool CrashService::Initialize(const base::FilePath& operating_dir, +bool CrashService::Initialize(const base::string16& application_name, + const base::FilePath& operating_dir, const base::FilePath& dumps_path) { using google_breakpad::CrashReportSender; using google_breakpad::CrashGenerationServer; @@ -260,6 +270,7 @@ bool CrashService::Initialize(const base::FilePath& operating_dir, } if (!CreateTopWindow(::GetModuleHandleW(NULL), + application_name, !cmd_line.HasSwitch(kNoWindow))) { LOG(ERROR) << "could not create window"; if (security_attributes.lpSecurityDescriptor) @@ -298,11 +309,10 @@ bool CrashService::Initialize(const base::FilePath& operating_dir, // Create or open an event to signal the browser process that the crash // service is initialized. - HANDLE running_event = - ::CreateEventW(NULL, TRUE, TRUE, L"g_atom_shell_crash_service"); - // If the browser already had the event open, the CreateEvent call did not - // signal it. We need to do it manually. - ::SetEvent(running_event); + base::string16 wait_name = ReplaceStringPlaceholders( + kWaitEventFormat, application_name, NULL); + HANDLE wait_event = ::CreateEventW(NULL, TRUE, FALSE, wait_name.c_str()); + ::SetEvent(wait_event); return true; } diff --git a/atom/common/crash_reporter/win/crash_service.h b/atom/common/crash_reporter/win/crash_service.h index 730e4da3c6b..7195ec2a958 100644 --- a/atom/common/crash_reporter/win/crash_service.h +++ b/atom/common/crash_reporter/win/crash_service.h @@ -37,7 +37,8 @@ class CrashService { // other members in that case. |operating_dir| is where the CrashService // should store breakpad's checkpoint file. |dumps_path| is the directory // where the crash dumps should be stored. - bool Initialize(const base::FilePath& operating_dir, + bool Initialize(const base::string16& application_name, + const base::FilePath& operating_dir, const base::FilePath& dumps_path); // Command line switches: diff --git a/atom/common/crash_reporter/win/crash_service_main.cc b/atom/common/crash_reporter/win/crash_service_main.cc index 0bd72deba9e..7a5eeb10133 100644 --- a/atom/common/crash_reporter/win/crash_service_main.cc +++ b/atom/common/crash_reporter/win/crash_service_main.cc @@ -77,7 +77,8 @@ int Main(const wchar_t* cmd) { cmd_line.AppendSwitchNative("pipe-name", pipe_name); breakpad::CrashService crash_service; - if (!crash_service.Initialize(operating_dir, operating_dir)) + if (!crash_service.Initialize(application_name, operating_dir, + operating_dir)) return 2; VLOG(1) << "Ready to process crash requests"; diff --git a/atom/common/event_emitter_caller.cc b/atom/common/event_emitter_caller.cc deleted file mode 100644 index 85f56a23373..00000000000 --- a/atom/common/event_emitter_caller.cc +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) 2015 GitHub, Inc. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#include "atom/common/event_emitter_caller.h" - -namespace mate { - -namespace internal { - -v8::Local CallEmitWithArgs(v8::Isolate* isolate, - v8::Local obj, - ValueVector* args) { - v8::Local emit_name = StringToSymbol(isolate, "emit"); - v8::Local emit = obj->Get(emit_name); - if (emit.IsEmpty() || !emit->IsFunction()) { - isolate->ThrowException(v8::Exception::TypeError( - StringToV8(isolate, "\"emit\" is not a function"))); - return v8::Undefined(isolate); - } - - v8::MaybeLocal result = emit.As()->Call( - isolate->GetCurrentContext(), obj, args->size(), &args->front()); - if (result.IsEmpty()) { - return v8::Undefined(isolate); - } - - return result.ToLocalChecked(); -} - -} // namespace internal - -} // namespace mate diff --git a/atom/common/native_mate_converters/callback.h b/atom/common/native_mate_converters/callback.h new file mode 100644 index 00000000000..6e51cda79c4 --- /dev/null +++ b/atom/common/native_mate_converters/callback.h @@ -0,0 +1,104 @@ +// Copyright (c) 2015 GitHub, Inc. All rights reserved. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_COMMON_NATIVE_MATE_CONVERTERS_CALLBACK_H_ +#define ATOM_COMMON_NATIVE_MATE_CONVERTERS_CALLBACK_H_ + +#include + +#include "atom/common/api/locker.h" +#include "base/bind.h" +#include "base/callback.h" +#include "native_mate/function_template.h" +#include "native_mate/scoped_persistent.h" +#include "third_party/WebKit/public/web/WebScopedMicrotaskSuppression.h" + +namespace mate { + +namespace internal { + +typedef scoped_refptr > SafeV8Function; + +template +struct V8FunctionInvoker {}; + +template +struct V8FunctionInvoker(ArgTypes...)> { + static v8::Local Go(v8::Isolate* isolate, SafeV8Function function, + ArgTypes... raw) { + Locker locker(isolate); + v8::EscapableHandleScope handle_scope(isolate); + scoped_ptr script_scope( + Locker::IsBrowserProcess() ? + nullptr : new blink::WebScopedRunV8Script(isolate)); + v8::Local holder = function->NewHandle(); + v8::Local context = holder->CreationContext(); + v8::Context::Scope context_scope(context); + std::vector> args = { ConvertToV8(isolate, raw)... }; + v8::Local ret(holder->Call(holder, args.size(), &args.front())); + return handle_scope.Escape(ret); + } +}; + +template +struct V8FunctionInvoker { + static void Go(v8::Isolate* isolate, SafeV8Function function, + ArgTypes... raw) { + Locker locker(isolate); + v8::HandleScope handle_scope(isolate); + scoped_ptr script_scope( + Locker::IsBrowserProcess() ? + nullptr : new blink::WebScopedRunV8Script(isolate)); + v8::Local holder = function->NewHandle(); + v8::Local context = holder->CreationContext(); + v8::Context::Scope context_scope(context); + std::vector> args = { ConvertToV8(isolate, raw)... }; + holder->Call(holder, args.size(), &args.front()); + } +}; + +template +struct V8FunctionInvoker { + static ReturnType Go(v8::Isolate* isolate, SafeV8Function function, + ArgTypes... raw) { + Locker locker(isolate); + v8::HandleScope handle_scope(isolate); + scoped_ptr script_scope( + Locker::IsBrowserProcess() ? + nullptr : new blink::WebScopedRunV8Script(isolate)); + v8::Local holder = function->NewHandle(); + v8::Local context = holder->CreationContext(); + v8::Context::Scope context_scope(context); + ReturnType ret; + std::vector> args = { ConvertToV8(isolate, raw)... }; + v8::Local val(holder->Call(holder, args.size(), &args.front())); + Converter::FromV8(isolate, val, &ret); + return ret; + } +}; + +} // namespace internal + +template +struct Converter > { + static v8::Local ToV8(v8::Isolate* isolate, + const base::Callback& val) { + return CreateFunctionTemplate(isolate, val)->GetFunction(); + } + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + base::Callback* out) { + if (!val->IsFunction()) + return false; + + internal::SafeV8Function function( + new RefCountedPersistent(isolate, val)); + *out = base::Bind(&internal::V8FunctionInvoker::Go, isolate, function); + return true; + } +}; + +} // namespace mate + +#endif // ATOM_COMMON_NATIVE_MATE_CONVERTERS_CALLBACK_H_ diff --git a/atom/common/node_bindings.cc b/atom/common/node_bindings.cc index 8f5dd07428a..304b6a711a5 100644 --- a/atom/common/node_bindings.cc +++ b/atom/common/node_bindings.cc @@ -7,8 +7,9 @@ #include #include +#include "atom/common/api/event_emitter_caller.h" +#include "atom/common/api/locker.h" #include "atom/common/atom_command_line.h" -#include "atom/common/event_emitter_caller.h" #include "atom/common/native_mate_converters/file_path_converter.h" #include "base/command_line.h" #include "base/base_paths.h" @@ -17,8 +18,8 @@ #include "base/path_service.h" #include "content/public/browser/browser_thread.h" #include "content/public/common/content_paths.h" -#include "native_mate/locker.h" #include "native_mate/dictionary.h" +#include "third_party/WebKit/public/web/WebScopedMicrotaskSuppression.h" #include "atom/common/node_includes.h" @@ -227,6 +228,10 @@ void NodeBindings::UvRunOnce() { // Enter node context while dealing with uv events. v8::Context::Scope context_scope(env->context()); + // Perform microtask checkpoint after running JavaScript. + scoped_ptr script_scope( + is_browser_ ? nullptr : new blink::WebScopedRunV8Script(env->isolate())); + // Deal with uv events. int r = uv_run(uv_loop_, UV_RUN_NOWAIT); if (r == 0 || uv_loop_->stop_flag != 0) diff --git a/atom/renderer/api/atom_api_web_frame.cc b/atom/renderer/api/atom_api_web_frame.cc index 7b1666e680c..d2c4ed2fa7b 100644 --- a/atom/renderer/api/atom_api_web_frame.cc +++ b/atom/renderer/api/atom_api_web_frame.cc @@ -4,44 +4,20 @@ #include "atom/renderer/api/atom_api_web_frame.h" -// This defines are required by SchemeRegistry.h. -#define ALWAYS_INLINE inline -#define OS(WTF_FEATURE) (defined WTF_OS_##WTF_FEATURE && WTF_OS_##WTF_FEATURE) // NOLINT -#define USE(WTF_FEATURE) (defined WTF_USE_##WTF_FEATURE && WTF_USE_##WTF_FEATURE) // NOLINT -#define ENABLE(WTF_FEATURE) (defined ENABLE_##WTF_FEATURE && ENABLE_##WTF_FEATURE) // NOLINT - +#include "atom/common/native_mate_converters/callback.h" #include "atom/common/native_mate_converters/gfx_converter.h" #include "atom/common/native_mate_converters/string16_converter.h" #include "atom/renderer/api/atom_api_spell_check_client.h" #include "content/public/renderer/render_frame.h" -#include "native_mate/callback.h" #include "native_mate/dictionary.h" #include "native_mate/object_template_builder.h" #include "third_party/WebKit/public/web/WebDocument.h" #include "third_party/WebKit/public/web/WebLocalFrame.h" +#include "third_party/WebKit/public/web/WebSecurityPolicy.h" #include "third_party/WebKit/public/web/WebView.h" -#include "third_party/WebKit/Source/platform/weborigin/SchemeRegistry.h" #include "atom/common/node_includes.h" -namespace mate { - -template<> -struct Converter { - static bool FromV8(v8::Isolate* isolate, - v8::Local val, - WTF::String* out) { - if (!val->IsString()) - return false; - - v8::String::Value s(val); - *out = WTF::String(reinterpret_cast(*s), s.length()); - return true; - } -}; - -} // namespace mate - namespace atom { namespace api { @@ -106,6 +82,18 @@ void WebFrame::SetSpellCheckProvider(mate::Arguments* args, web_frame_->view()->setSpellCheckClient(spell_check_client_.get()); } +void WebFrame::RegisterURLSchemeAsSecure(const std::string& scheme) { + // Register scheme to secure list (https, wss, data). + blink::WebSecurityPolicy::registerURLSchemeAsSecure( + blink::WebString::fromUTF8(scheme)); +} + +void WebFrame::RegisterURLSchemeAsBypassingCsp(const std::string& scheme) { + // Register scheme to bypass pages's Content Security Policy. + blink::WebSecurityPolicy::registerURLSchemeAsBypassingContentSecurityPolicy( + blink::WebString::fromUTF8(scheme)); +} + mate::ObjectTemplateBuilder WebFrame::GetObjectTemplateBuilder( v8::Isolate* isolate) { return mate::ObjectTemplateBuilder(isolate) @@ -121,7 +109,9 @@ mate::ObjectTemplateBuilder WebFrame::GetObjectTemplateBuilder( .SetMethod("attachGuest", &WebFrame::AttachGuest) .SetMethod("setSpellCheckProvider", &WebFrame::SetSpellCheckProvider) .SetMethod("registerUrlSchemeAsSecure", - &blink::SchemeRegistry::registerURLSchemeAsSecure); + &WebFrame::RegisterURLSchemeAsSecure) + .SetMethod("registerUrlSchemeAsBypassingCsp", + &WebFrame::RegisterURLSchemeAsBypassingCsp); } // static diff --git a/atom/renderer/api/atom_api_web_frame.h b/atom/renderer/api/atom_api_web_frame.h index e57efd45cfb..26b8178e98a 100644 --- a/atom/renderer/api/atom_api_web_frame.h +++ b/atom/renderer/api/atom_api_web_frame.h @@ -54,6 +54,9 @@ class WebFrame : public mate::Wrappable { bool auto_spell_correct_turned_on, v8::Local provider); + void RegisterURLSchemeAsSecure(const std::string& scheme); + void RegisterURLSchemeAsBypassingCsp(const std::string& scheme); + // mate::Wrappable: virtual mate::ObjectTemplateBuilder GetObjectTemplateBuilder( v8::Isolate* isolate); diff --git a/atom/renderer/api/lib/remote.coffee b/atom/renderer/api/lib/remote.coffee index b159125b794..29a24b1789e 100644 --- a/atom/renderer/api/lib/remote.coffee +++ b/atom/renderer/api/lib/remote.coffee @@ -20,6 +20,8 @@ wrapArgs = (args, visited=[]) -> type: 'array', value: wrapArgs(value, visited) else if Buffer.isBuffer value type: 'buffer', value: Array::slice.call(value, 0) + else if value? and value.constructor.name is 'Promise' + type: 'promise', then: valueToMeta(value.then.bind(value)) else if value? and typeof value is 'object' and v8Util.getHiddenValue value, 'atomId' type: 'remote-object', id: v8Util.getHiddenValue value, 'atomId' else if value? and typeof value is 'object' @@ -44,6 +46,7 @@ metaToValue = (meta) -> when 'value' then meta.value when 'array' then (metaToValue(el) for el in meta.members) when 'buffer' then new Buffer(meta.value) + when 'promise' then Promise.resolve(then: metaToValue(meta.then)) when 'error' throw new Error("#{meta.message}\n#{meta.stack}") else diff --git a/atom/renderer/atom_render_view_observer.cc b/atom/renderer/atom_render_view_observer.cc index 02250091a4a..1f199ea445c 100644 --- a/atom/renderer/atom_render_view_observer.cc +++ b/atom/renderer/atom_render_view_observer.cc @@ -11,7 +11,7 @@ #include "atom/common/native_mate_converters/string16_converter.h" #include "atom/common/api/api_messages.h" -#include "atom/common/event_emitter_caller.h" +#include "atom/common/api/event_emitter_caller.h" #include "atom/common/native_mate_converters/value_converter.h" #include "atom/common/options_switches.h" #include "atom/renderer/atom_renderer_client.h" @@ -26,6 +26,8 @@ #include "third_party/WebKit/public/web/WebFrame.h" #include "third_party/WebKit/public/web/WebLocalFrame.h" #include "third_party/WebKit/public/web/WebKit.h" +#include "third_party/WebKit/public/web/WebScopedUserGesture.h" +#include "third_party/WebKit/public/web/WebScriptSource.h" #include "third_party/WebKit/public/web/WebView.h" #include "ui/base/resource/resource_bundle.h" @@ -113,6 +115,8 @@ bool AtomRenderViewObserver::OnMessageReceived(const IPC::Message& message) { bool handled = true; IPC_BEGIN_MESSAGE_MAP(AtomRenderViewObserver, message) IPC_MESSAGE_HANDLER(AtomViewMsg_Message, OnBrowserMessage) + IPC_MESSAGE_HANDLER(AtomViewMsg_ExecuteJavaScript, + OnJavaScriptExecuteRequest) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() @@ -143,4 +147,22 @@ void AtomRenderViewObserver::OnBrowserMessage(const base::string16& channel, } } +void AtomRenderViewObserver::OnJavaScriptExecuteRequest( + const base::string16& code, bool has_user_gesture) { + if (!document_created_) + return; + + if (!render_view()->GetWebView()) + return; + + scoped_ptr gesture( + has_user_gesture ? new blink::WebScopedUserGesture : nullptr); + + v8::Isolate* isolate = blink::mainThreadIsolate(); + v8::HandleScope handle_scope(isolate); + + blink::WebFrame* frame = render_view()->GetWebView()->mainFrame(); + frame->executeScriptAndReturnValue(blink::WebScriptSource(code)); +} + } // namespace atom diff --git a/atom/renderer/atom_render_view_observer.h b/atom/renderer/atom_render_view_observer.h index 4b9d59f3fa0..85a8c159d97 100644 --- a/atom/renderer/atom_render_view_observer.h +++ b/atom/renderer/atom_render_view_observer.h @@ -32,6 +32,8 @@ class AtomRenderViewObserver : public content::RenderViewObserver { void OnBrowserMessage(const base::string16& channel, const base::ListValue& args); + void OnJavaScriptExecuteRequest(const base::string16& code, + bool has_user_gesture); // Weak reference to renderer client. AtomRendererClient* renderer_client_; diff --git a/atom/renderer/guest_view_container.cc b/atom/renderer/guest_view_container.cc index 638df2177e4..7643f72b0e5 100644 --- a/atom/renderer/guest_view_container.cc +++ b/atom/renderer/guest_view_container.cc @@ -7,6 +7,7 @@ #include #include "base/lazy_instance.h" +#include "ui/gfx/geometry/size.h" namespace atom { @@ -51,7 +52,9 @@ void GuestViewContainer::DidResizeElement(const gfx::Size& old_size, if (element_resize_callback_.is_null()) return; - element_resize_callback_.Run(old_size, new_size); + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(element_resize_callback_, old_size, new_size)); } } // namespace atom diff --git a/atom/renderer/lib/override.coffee b/atom/renderer/lib/override.coffee index 85914b116fd..d8c6c0a86d7 100644 --- a/atom/renderer/lib/override.coffee +++ b/atom/renderer/lib/override.coffee @@ -11,6 +11,7 @@ resolveUrl = (url) -> # Window object returned by "window.open". class BrowserWindowProxy constructor: (@guestId) -> + @closed = false ipc.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSED', (guestId) => if guestId is @guestId @closed = true @@ -61,7 +62,7 @@ window.open = (url, frameName='', features='') -> (options[name] = parseInt(options[name], 10) if options[name]?) for name in ints # Inherit the node-integration option of current window. - unless options['node-integration'] + unless options['node-integration']? for arg in process.argv when arg.indexOf('--node-integration=') is 0 options['node-integration'] = arg.substr(-4) is 'true' break @@ -91,7 +92,7 @@ window.prompt = -> throw new Error('prompt() is and will not be supported.') # Simple implementation of postMessage. -unless process.guestInstanceId? +if ipc.sendSync 'ATOM_SHELL_GUEST_WINDOW_MANAGER_IS_GUEST_WINDOW' window.opener = postMessage: (message, targetOrigin='*') -> ipc.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPENER_POSTMESSAGE', message, targetOrigin diff --git a/atom/renderer/lib/web-view/guest-view-internal.coffee b/atom/renderer/lib/web-view/guest-view-internal.coffee index cc0a7c928e3..44db5e304db 100644 --- a/atom/renderer/lib/web-view/guest-view-internal.coffee +++ b/atom/renderer/lib/web-view/guest-view-internal.coffee @@ -4,6 +4,7 @@ webFrame = require 'web-frame' requestId = 0 WEB_VIEW_EVENTS = + 'load-commit': ['url', 'isMainFrame'] 'did-finish-load': [] 'did-fail-load': ['errorCode', 'errorDescription'] 'did-frame-finish-load': ['isMainFrame'] @@ -27,11 +28,12 @@ WEB_VIEW_EVENTS = 'leave-html-full-screen': [] dispatchEvent = (webView, event, args...) -> - throw new Error("Unkown event #{event}") unless WEB_VIEW_EVENTS[event]? + throw new Error("Unknown event #{event}") unless WEB_VIEW_EVENTS[event]? domEvent = new Event(event) for f, i in WEB_VIEW_EVENTS[event] domEvent[f] = args[i] webView.dispatchEvent domEvent + webView.onLoadCommit domEvent if event == 'load-commit' module.exports = registerEvents: (webView, viewInstanceId) -> diff --git a/atom/renderer/lib/web-view/web-view-attributes.coffee b/atom/renderer/lib/web-view/web-view-attributes.coffee index 82229aae57b..acca826a9e3 100644 --- a/atom/renderer/lib/web-view/web-view-attributes.coffee +++ b/atom/renderer/lib/web-view/web-view-attributes.coffee @@ -120,7 +120,7 @@ class SrcAttribute extends WebViewAttribute '' setValueIgnoreMutation: (value) -> - WebViewAttribute::setValueIgnoreMutation value + WebViewAttribute::setValueIgnoreMutation.call(this, value) # takeRecords() is needed to clear queued up src mutations. Without it, it # is possible for this change to get picked up asyncronously by src's # mutation observer |observer|, and then get handled even though we do not diff --git a/atom/renderer/lib/web-view/web-view.coffee b/atom/renderer/lib/web-view/web-view.coffee index 72fee948d13..35fbe17796f 100644 --- a/atom/renderer/lib/web-view/web-view.coffee +++ b/atom/renderer/lib/web-view/web-view.coffee @@ -157,10 +157,10 @@ class WebViewImpl enumerable: true # Updates state upon loadcommit. - onLoadCommit: (@baseUrlForDataUrl, @currentEntryIndex, @entryCount, @processId, url, isTopLevel) -> + onLoadCommit: (webViewEvent) -> oldValue = @webviewNode.getAttribute webViewConstants.ATTRIBUTE_SRC - newValue = url - if isTopLevel and (oldValue != newValue) + newValue = webViewEvent.url + if webViewEvent.isMainFrame and (oldValue != newValue) # Touching the src attribute triggers a navigation. To avoid # triggering a page reload on every guest-initiated navigation, # we do not handle this mutation @@ -269,6 +269,7 @@ registerWebViewElement = -> "goToOffset" "isCrashed" "setUserAgent" + "getUserAgent" "executeJavaScript" "insertCSS" "openDevTools" diff --git a/chromium_src/chrome/common/pref_names.cc b/chromium_src/chrome/common/pref_names.cc new file mode 100644 index 00000000000..3e3a73b9983 --- /dev/null +++ b/chromium_src/chrome/common/pref_names.cc @@ -0,0 +1,12 @@ +// 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/common/pref_names.h" + +namespace prefs { + +const char kSelectFileLastDirectory[] = "selectfile.last_directory"; +const char kDownloadDefaultDirectory[] = "download.default_directory"; + +} // namespace prefs diff --git a/chromium_src/chrome/common/pref_names.h b/chromium_src/chrome/common/pref_names.h index e69de29bb2d..542a2d2c733 100644 --- a/chromium_src/chrome/common/pref_names.h +++ b/chromium_src/chrome/common/pref_names.h @@ -0,0 +1,12 @@ +// 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. + +// Constants for the names of various preferences, for easier changing. + +namespace prefs { + +extern const char kSelectFileLastDirectory[]; +extern const char kDownloadDefaultDirectory[]; + +} // namespace prefs diff --git a/docs/README-es.md b/docs/README-es.md new file mode 100644 index 00000000000..a278de95bd4 --- /dev/null +++ b/docs/README-es.md @@ -0,0 +1,68 @@ +## Guías + +* [Distribución de aplicaciones](tutorial/application-distribution-es.md) +* [Empaquetamiento de aplicaciones](tutorial/application-packaging-es.md) +* [Utilizando módulos nativos](tutorial/using-native-node-modules-es.md) +* [Depurando el proceso principal](tutorial/debugging-main-process-es.md) +* [Utilizando Selenium y WebDriver](tutorial/using-selenium-and-webdriver-es.md) +* [Extensión DevTools](tutorial/devtools-extension-es.md) +* [Utilizando el plugin pepper flash](tutorial/using-pepper-flash-plugin-es.md) + +## Tutoriales + +* [Introducción](tutorial/quick-start.md) +* [Integración con el entorno de escritorio](tutorial/desktop-environment-integration.md) +* [Detección del evento en línea/fuera de línea](tutorial/online-offline-events.md) + +## API + +* [Sinopsis](api/synopsis.md) +* [Proceso](api/process.md) +* [Parámetros CLI soportados (Chrome)](api/chrome-command-line-switches.md) + +Elementos DOM customizados: + +* [Objeto `File`](api/file-object.md) +* [Etiqueta ``](api/web-view-tag.md) +* [Función `window.open`](api/window-open.md) + +Módulos del proceso principal: + +* [app](api/app.md) +* [auto-updater](api/auto-updater.md) +* [browser-window](api/browser-window.md) +* [content-tracing](api/content-tracing.md) +* [dialog](api/dialog.md) +* [global-shortcut](api/global-shortcut.md) +* [ipc (main process)](api/ipc-main-process.md) +* [menu](api/menu.md) +* [menu-item](api/menu-item.md) +* [power-monitor](api/power-monitor.md) +* [power-save-blocker](api/power-save-blocker.md) +* [protocol](api/protocol.md) +* [tray](api/tray.md) + +Módulos del renderer (página web): + +* [ipc (renderer)](api/ipc-renderer.md) +* [remote](api/remote.md) +* [web-frame](api/web-frame.md) + +Módulos de ambos procesos: + +* [clipboard](api/clipboard.md) +* [crash-reporter](api/crash-reporter.md) +* [native-image](api/native-image.md) +* [screen](api/screen.md) +* [shell](api/shell.md) + +## Desarrollo + +* [Guía de estilo](development/coding-style.md) +* [Estructura de directorio](development/source-code-directory-structure.md) +* [Diferencias técnicas con NW.js (anteriormente conocido como node-webkit)](development/atom-shell-vs-node-webkit.md) +* [Sistema de compilación](development/build-system-overview.md) +* [Instrucciones de compilación (Mac)](development/build-instructions-mac.md) +* [Instrucciones de compilación (Windows)](development/build-instructions-windows.md) +* [Instrucciones de compilación (Linux)](development/build-instructions-linux.md) +* [Configurando un servidor de símbolos en el depurador](development/setting-up-symbol-server.md) diff --git a/docs/api/accelerator.md b/docs/api/accelerator.md index a9755ed653b..8858d18e856 100644 --- a/docs/api/accelerator.md +++ b/docs/api/accelerator.md @@ -1,6 +1,6 @@ # Accelerator -An accelerator is string that represents a keyboard shortcut, it can contain +An accelerator is a string that represents a keyboard shortcut. It can contain multiple modifiers and key codes, combined by the `+` character. Examples: @@ -10,7 +10,7 @@ Examples: ## Platform notice -On Linux and Windows, the `Command` key would not have any effect, you can +On Linux and Windows, the `Command` key does not have any effect so use `CommandOrControl` which represents `Command` on OS X and `Control` on Linux and Windows to define some accelerators. diff --git a/docs/api/app.md b/docs/api/app.md index 99576c4e416..8223643a375 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -1,8 +1,8 @@ # app -The `app` module is responsible for controlling the application's life time. +The `app` module is responsible for controlling the application's lifecycle. -The example of quitting the whole application when the last window is closed: +The following example shows how to quit the application when the last window is closed: ```javascript var app = require('app'); @@ -13,26 +13,26 @@ app.on('window-all-closed', function() { ## Event: will-finish-launching -Emitted when application has done basic startup. On Windows and Linux it is the -same with `ready` event, on OS X this event represents the -`applicationWillFinishLaunching` message of `NSApplication`, usually you would -setup listeners to `open-file` and `open-url` events here, and start the crash -reporter and auto updater. +Emitted when the application has finished basic startup. On Windows and Linux, +the `will-finish-launching` event is the same as the `ready` event; on OS X, +this event represents the `applicationWillFinishLaunching` notification of `NSApplication`. +You would usually set up listeners for the `open-file` and `open-url` events here, +and start the crash reporter and auto updater. -Under most cases you should just do everything in `ready` event. +In most cases, you should just do everything in the `ready` event handler. ## Event: ready -Emitted when Electron has done everything initialization. +Emitted when Electron has finished initialization. ## Event: window-all-closed Emitted when all windows have been closed. -This event is only emitted when the application is not going to quit. If a +This event is only emitted when the application is not going to quit. If the user pressed `Cmd + Q`, or the developer called `app.quit()`, Electron would -first try to close all windows and then emit the `will-quit` event, and in -this case the `window-all-closed` would not be emitted. +first try to close all the windows and then emit the `will-quit` event, and in +this case the `window-all-closed` event would not be emitted. ## Event: before-quit @@ -50,23 +50,23 @@ Emitted when all windows have been closed and the application will quit. Calling `event.preventDefault()` will prevent the default behaviour, which is terminating the application. -See description of `window-all-closed` for the differences between `will-quit` -and it. +See the description of the `window-all-closed` event for the differences between the `will-quit` +and `window-all-closed` events. ## Event: quit -Emitted when application is quitting. +Emitted when the application is quitting. ## Event: open-file * `event` Event * `path` String -Emitted when user wants to open a file with the application, it usually happens -when the application is already opened and then OS wants to reuse the -application to open file. But it is also emitted when a file is dropped onto the -dock and the application is not yet running. Make sure to listen to open-file -very early in your application startup to handle this case (even before the +Emitted when the user wants to open a file with the application. The `open-file` event +is usually emitted when the application is already open and the OS wants to reuse the +application to open the file. `open-file` is also emitted when a file is dropped onto the +dock and the application is not yet running. Make sure to listen for the `open-file` +event very early in your application startup to handle this case (even before the `ready` event is emitted). You should call `event.preventDefault()` if you want to handle this event. @@ -76,16 +76,16 @@ You should call `event.preventDefault()` if you want to handle this event. * `event` Event * `url` String -Emitted when user wants to open a URL with the application, this URL scheme +Emitted when the user wants to open a URL with the application. The URL scheme must be registered to be opened by your application. You should call `event.preventDefault()` if you want to handle this event. ## Event: activate-with-no-open-windows -Emitted when the application is activated while there is no opened windows. It -usually happens when user has closed all of application's windows and then -click on the application's dock icon. +Emitted when the application is activated while there are no open windows, which +usually happens when the user has closed all of the application's windows and then +clicks on the application's dock icon. ## Event: browser-window-blur @@ -103,7 +103,7 @@ Emitted when a [browserWindow](browser-window.md) gets focused. ### Event: 'select-certificate' -Emitted when client certificate is requested. +Emitted when a client certificate is requested. * `event` Event * `webContents` [WebContents](browser-window.md#class-webcontents) @@ -120,24 +120,24 @@ app.on('select-certificate', function(event, host, url, list, callback) { }) ``` -`url` corresponds to the navigation entry requesting the client certificate, +`url` corresponds to the navigation entry requesting the client certificate. `callback` needs to be called with an entry filtered from the list. -`event.preventDefault()` prevents from using the first certificate from -the store. +`event.preventDefault()` prevents the application from using the first certificate +from the store. ### Event: 'gpu-process-crashed' -Emitted when the gpu process is crashed. +Emitted when the gpu process crashes. ## app.quit() Try to close all windows. The `before-quit` event will first be emitted. If all windows are successfully closed, the `will-quit` event will be emitted and by -default the application would be terminated. +default the application will terminate. -This method guarantees all `beforeunload` and `unload` handlers are correctly +This method guarantees that all `beforeunload` and `unload` event handlers are correctly executed. It is possible that a window cancels the quitting by returning -`false` in `beforeunload` handler. +`false` in the `beforeunload` event handler. ## app.getAppPath() @@ -148,19 +148,19 @@ Returns the current application directory. * `name` String Retrieves a path to a special directory or file associated with `name`. On -failure an `Error` would throw. +failure an `Error` is thrown. -You can request following paths by the names: +You can request the following paths by the name: * `home`: User's home directory -* `appData`: Per-user application data directory, by default it is pointed to: +* `appData`: Per-user application data directory, which by default points to: * `%APPDATA%` on Windows * `$XDG_CONFIG_HOME` or `~/.config` on Linux * `~/Library/Application Support` on OS X -* `userData`: The directory for storing your app's configuration files, by +* `userData`: The directory for storing your app's configuration files, which by default it is the `appData` directory appended with your app's name -* `cache`: Per-user application cache directory, by default it is pointed to: - * `%APPDATA%` on Window, which doesn't has a universal place for cache +* `cache`: Per-user application cache directory, which by default points to: + * `%APPDATA%` on Windows (which doesn't have a universal cache location) * `$XDG_CACHE_HOME` or `~/.cache` on Linux * `~/Library/Caches` on OS X * `userCache`: The directory for placing your app's caches, by default it is the @@ -175,30 +175,30 @@ You can request following paths by the names: * `name` String * `path` String -Overrides the `path` to a special directory or file associated with `name`. if +Overrides the `path` to a special directory or file associated with `name`. If the path specifies a directory that does not exist, the directory will be -created by this method. On failure an `Error` would throw. +created by this method. On failure an `Error` is thrown. You can only override paths of `name`s defined in `app.getPath`. -By default web pages' cookies and caches will be stored under `userData` -directory, if you want to change this location, you have to override the -`userData` path before the `ready` event of `app` module gets emitted. +By default, web pages' cookies and caches will be stored under the `userData` +directory. If you want to change this location, you have to override the +`userData` path before the `ready` event of the `app` module is emitted. ## app.getVersion() -Returns the version of loaded application, if no version is found in -application's `package.json`, the version of current bundle or executable would -be returned. +Returns the version of the loaded application. If no version is found in the +application's `package.json` file, the version of the current bundle or executable is +returned. ## app.getName() -Returns current application's name, the name in `package.json` would be -used. +Returns the current application's name, which is the name in the application's +`package.json` file. Usually the `name` field of `package.json` is a short lowercased name, according -to the spec of npm modules. So usually you should also specify a `productName` -field, which is your application's full capitalized name, and it will be +to the npm modules spec. You should usually also specify a `productName` +field, which is your application's full capitalized name, and which will be preferred over `name` by Electron. ## app.resolveProxy(url, callback) @@ -206,17 +206,17 @@ preferred over `name` by Electron. * `url` URL * `callback` Function -Resolves the proxy information for `url`, the `callback` would be called with -`callback(proxy)` when the request is done. +Resolves the proxy information for `url`. The `callback` will be called with +`callback(proxy)` when the request is performed. ## app.addRecentDocument(path) * `path` String -Adds `path` to recent documents list. +Adds `path` to the recent documents list. -This list is managed by the system, on Windows you can visit the list from task -bar, and on Mac you can visit it from dock menu. +This list is managed by the OS. On Windows you can visit the list from the task +bar, and on OS X you can visit it from dock menu. ## app.clearRecentDocuments() @@ -226,20 +226,20 @@ Clears the recent documents list. * `tasks` Array - Array of `Task` objects -Adds `tasks` to the [Tasks][tasks] category of JumpList on Windows. +Adds `tasks` to the [Tasks][tasks] category of the JumpList on Windows. -The `tasks` is an array of `Task` objects in following format: +`tasks` is an array of `Task` objects in following format: * `Task` Object * `program` String - Path of the program to execute, usually you should - specify `process.execPath` which opens current program - * `arguments` String - The arguments of command line when `program` is + specify `process.execPath` which opens the current program + * `arguments` String - The command line arguments when `program` is executed * `title` String - The string to be displayed in a JumpList * `description` String - Description of this task * `iconPath` String - The absolute path to an icon to be displayed in a - JumpList, it can be arbitrary resource file that contains an icon, usually - you can specify `process.execPath` to show the icon of the program + JumpList, which can be an arbitrary resource file that contains an icon. You can + usually specify `process.execPath` to show the icon of the program * `iconIndex` Integer - The icon index in the icon file. If an icon file consists of two or more icons, set this value to identify the icon. If an icon file consists of one icon, this value is 0 @@ -255,25 +255,25 @@ to control some low-level Chromium behaviors. ## app.commandLine.appendArgument(value) -Append an argument to Chromium's command line. The argument will quoted properly. +Append an argument to Chromium's command line. The argument will be quoted correctly. **Note:** This will not affect `process.argv`. ## app.dock.bounce([type]) -* `type` String - Can be `critical` or `informational`, the default is +* `type` String - Can be `critical` or `informational`. The default is `informational` When `critical` is passed, the dock icon will bounce until either the application becomes active or the request is canceled. -When `informational` is passed, the dock icon will bounce for one second. The -request, though, remains active until either the application becomes active or +When `informational` is passed, the dock icon will bounce for one second. However, +the request remains active until either the application becomes active or the request is canceled. -An ID representing the request would be returned. +An ID representing the request is returned. -**Note:** This API is only available on Mac. +**Note:** This API is only available on OS X. ## app.dock.cancelBounce(id) @@ -281,7 +281,7 @@ An ID representing the request would be returned. Cancel the bounce of `id`. -**Note:** This API is only available on Mac. +**Note:** This API is only available on OS X. ## app.dock.setBadge(text) @@ -289,33 +289,33 @@ Cancel the bounce of `id`. Sets the string to be displayed in the dock’s badging area. -**Note:** This API is only available on Mac. +**Note:** This API is only available on OS X. ## app.dock.getBadge() Returns the badge string of the dock. -**Note:** This API is only available on Mac. +**Note:** This API is only available on OS X. ## app.dock.hide() Hides the dock icon. -**Note:** This API is only available on Mac. +**Note:** This API is only available on OS X. ## app.dock.show() Shows the dock icon. -**Note:** This API is only available on Mac. +**Note:** This API is only available on OS X. ## app.dock.setMenu(menu) * `menu` Menu -Sets the application [dock menu][dock-menu]. +Sets the application's [dock menu][dock-menu]. -**Note:** This API is only available on Mac. +**Note:** This API is only available on OS X. [dock-menu]:https://developer.apple.com/library/mac/documentation/Carbon/Conceptual/customizing_docktile/concepts/dockconcepts.html#//apple_ref/doc/uid/TP30000986-CH2-TPXREF103 [tasks]:http://msdn.microsoft.com/en-us/library/windows/desktop/dd378460(v=vs.85).aspx#tasks diff --git a/docs/api/auto-updater.md b/docs/api/auto-updater.md index 709d0c0f276..5274fc6c467 100644 --- a/docs/api/auto-updater.md +++ b/docs/api/auto-updater.md @@ -3,9 +3,9 @@ **This module has only been implemented for OS X.** Check out [atom/grunt-electron-installer](https://github.com/atom/grunt-electron-installer) -for building a Windows installer for your app. +to build a Windows installer for your app. -The `auto-updater` module is a simple wrap around the +The `auto-updater` module is a simple wrapper around the [Squirrel.Mac](https://github.com/Squirrel/Squirrel.Mac) framework. Squirrel.Mac requires that your `.app` folder is signed using the @@ -26,11 +26,11 @@ body so that your server has the context it needs in order to supply the most suitable update. The update JSON Squirrel requests should be dynamically generated based on -criteria in the request, and whether an update is required. Squirrel relies -on server side support for determining whether an update is required, see +criteria in the request and whether an update is required. Squirrel relies +on server-side support to determine whether an update is required. See [Server Support](#server-support). -Squirrel's installer is also designed to be fault tolerant, and ensure that any +Squirrel's installer is designed to be fault tolerant and ensures that any updates installed are valid. ## Update Requests @@ -40,11 +40,11 @@ update checking. `Accept: application/json` is added to the request headers because Squirrel is responsible for parsing the response. For the requirements imposed on the responses and the body format of an update -response see [Server Support](#server-support). +response, see [Server Support](#server-support). Your update request must *at least* include a version identifier so that the server can determine whether an update for this specific version is required. It -may also include other identifying criteria such as operating system version or +may also include other identifying criteria, such as operating system version or username, to allow the server to deliver as fine grained an update as you would like. @@ -64,7 +64,7 @@ autoUpdater.setFeedUrl('http://mycompany.com/myapp/latest?version=' + app.getVer Your server should determine whether an update is required based on the [Update Request](#update-requests) your client issues. -If an update is required your server should respond with a status code of +If an update is required, your server should respond with a status code of [200 OK](http://tools.ietf.org/html/rfc2616#section-10.2.1) and include the [update JSON](#update-json-format) in the body. Squirrel **will** download and install this update, even if the version of the update is the same as the @@ -85,33 +85,33 @@ to the update request provided: "url": "http://mycompany.com/myapp/releases/myrelease", "name": "My Release Name", "notes": "Theses are some release notes innit", - "pub_date": "2013-09-18T12:29:53+01:00", + "pub_date": "2013-09-18T12:29:53+01:00" } ``` -The only required key is "url", the others are optional. +The only required key is "url"; the others are optional. Squirrel will request "url" with `Accept: application/zip` and only supports installing ZIP updates. If future update formats are supported their MIME type will be added to the `Accept` header so that your server can return the appropriate format. -`pub_date` if present must be formatted according to ISO 8601. +`pub_date` (if present) must be formatted according to ISO 8601. ## Event: error * `event` Event * `message` String -Emitted when there is an error updating. +Emitted when there is an error while updating. ## Event: checking-for-update -Emitted when checking for update has started. +Emitted when checking if an update has started. ## Event: update-available -Emitted when there is an available update, the update would be downloaded +Emitted when there is an available update. The update is downloaded automatically. ## Event: update-not-available @@ -127,17 +127,17 @@ Emitted when there is no available update. * `updateUrl` String * `quitAndUpdate` Function -Emitted when update has been downloaded, calling `quitAndUpdate()` would restart +Emitted when an update has been downloaded. Calling `quitAndUpdate()` will restart the application and install the update. ## autoUpdater.setFeedUrl(url) * `url` String -Set the `url` and initialize the auto updater. The `url` could not be changed +Set the `url` and initialize the auto updater. The `url` cannot be changed once it is set. ## autoUpdater.checkForUpdates() -Ask the server whether there is an update, you have to call `setFeedUrl` before +Ask the server whether there is an update. You must call `setFeedUrl` before using this API. diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index 61f985ed951..612b3ce4f3f 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -76,7 +76,14 @@ You can also create a window without chrome by using textured window. Defaults to `true`. * `web-preferences` Object - Settings of web page's features * `javascript` Boolean - * `web-security` Boolean + * `web-security` Boolean - When setting `false`, it will disable the same-origin + policy(Usually using testing websites by people), and set `allow_displaying_insecure_content` + and `allow_running_insecure_content` to `true` if these two options are not + set by user. + * `allow-displaying-insecure-content` Boolean - Allow a https page to display + content like image from http URLs. + * `allow-running-insecure-content` Boolean - Allow a https page to run JavaScript, + CSS or plugins from http URLs. * `images` Boolean * `java` Boolean * `text-areas-are-resizable` Boolean @@ -361,6 +368,19 @@ Sets whether the window should be in fullscreen mode. Returns whether the window is in fullscreen mode. +### BrowserWindow.setAspectRatio(aspectRatio[, extraSize]) + +* `aspectRatio` The aspect ratio we want to maintain for some portion of the content view. +* `rect` Object - The extra size to not be included in the aspect ratio to be maintained. + * `width` Integer + * `height` Integer + +This will have a window maintain an aspect ratio. The extra size allows a developer to be able to have space, specifified in pixels, not included within the aspect ratio calculations. This API already takes into account the difference between a window's size and it's content size. + +Consider a normal window with an HD video player and associated controls. Perhaps there are 15 pixels of controls on the left edge, 25 pixels of controls on the right edge and 50 pixels of controls below the player. In order to maintain a 16:9 aspect ratio (standard aspect ratio for HD @1920x1080) within the player itself we would call this function with arguments of 16/9 and [ 40, 50 ]. The second argument doesn't care where the extra width and height are within the content view — only that they exist. Just sum any extra width and height areas you have within the overall content view. + +__Note__: This API is only implemented on OS X. + ### BrowserWindow.setBounds(options) * `options` Object @@ -623,6 +643,39 @@ Sets a 16px overlay onto the current taskbar icon, usually used to convey some s __Note:__ This API is only available on Windows (Windows 7 and above) + +### BrowserWindow.setThumbarButtons(buttons) + +* `buttons` Array + * `button` Object + * `icon` [NativeImage](native-image.md) - The icon showing in thumbnail + toolbar. + * `tooltip` String (optional) - The text of the button's tooltip. + * `flags` Array (optional) - Control specific states and behaviors + of the button. By default, it uses `enabled`. It can include following + Strings: + * `enabled` - The button is active and available to the user. + * `disabled` - The button is disabled. It is present, but has a visual + state that indicates that it will not respond to user action. + * `dismissonclick` - When the button is clicked, the taskbar button's + flyout closes immediately. + * `nobackground` - Do not draw a button border, use only the image. + * `hidden` - The button is not shown to the user. + * `noninteractive` - The button is enabled but not interactive; no + pressed button state is drawn. This value is intended for instances + where the button is used in a notification. + * `click` - Function + +Add a thumbnail toolbar with a specified set of buttons to the thumbnail image +of a window in a taskbar button layout. Returns a `Boolean` object indicates +whether the thumbnail has been added successfully. + +__Note:__ This API is only available on Windows (Windows 7 and above). +The number of buttons in thumbnail toolbar should be no greater than 7 due to +the limited room. Once you setup the thumbnail toolbar, the toolbar cannot be +removed due to the platform's limitation. But you can call the API with an empty +array to clean the buttons. + ### BrowserWindow.showDefinitionForSelection() Shows pop-up dictionary that searches the selected word on the page. @@ -760,7 +813,7 @@ Calling `event.preventDefault()` can prevent creating new windows. * `event` Event * `url` String -Emitted when user or the page wants to start an navigation, it can happen when +Emitted when user or the page wants to start a navigation, it can happen when `window.location` object is changed or user clicks a link in the page. This event will not emit when the navigation is started programmatically with APIs @@ -875,18 +928,27 @@ Whether the renderer process has crashed. Overrides the user agent for this page. +### WebContents.getUserAgent() + +Returns a `String` represents the user agent for this page. + ### WebContents.insertCSS(css) * `css` String Injects CSS into this page. -### WebContents.executeJavaScript(code) +### WebContents.executeJavaScript(code[, userGesture]) * `code` String +* `userGesture` Boolean Evaluates `code` in page. +In browser some HTML APIs like `requestFullScreen` can only be invoked if it +is started by user gesture, by specifying `userGesture` to `true` developers +can ignore this limitation. + ### WebContents.setAudioMuted(muted) + `muted` Boolean @@ -984,6 +1046,12 @@ size. * 0 - default * 1 - none * 2 - minimum + * `pageSize` String - Specify page size of the generated PDF + * `A4` + * `A3` + * `Legal` + * `Letter` + * `Tabloid` * `printBackground` Boolean - Whether to print CSS backgrounds. * `printSelectionOnly` Boolean - Whether to print selection only. * `landscape` Boolean - `true` for landscape, `false` for portrait. @@ -1010,14 +1078,27 @@ win.webContents.on("did-finish-load", function() { // Use default printing options win.webContents.printToPDF({}, function(error, data) { if (error) throw error; - fs.writeFile(dist, data, function(error) { + fs.writeFile("/tmp/print.pdf", data, function(error) { if (err) - alert('write pdf file error', error); + throw error; + console.log("Write PDF successfully."); }) }) }); ``` +### WebContents.addWorkSpace(path) + +* `path` String + +Adds the specified path to devtools workspace. + +### WebContents.removeWorkSpace(path) + +* `path` String + +Removes the specified path from devtools workspace. + ### WebContents.send(channel[, args...]) * `channel` String @@ -1167,3 +1248,46 @@ Clears the session's HTTP cache. * `callback` Function - Called when operation is done Clears the data of web storages. + +### Session.setProxy(config, callback) + +* `config` String +* `callback` Function - Called when operation is done + +Parses the `config` indicating which proxies to use for the session. + +``` +config = scheme-proxies[";"] +scheme-proxies = ["="] +url-scheme = "http" | "https" | "ftp" | "socks" +proxy-uri-list = [","] +proxy-uri = ["://"][":"] + + For example: + "http=foopy:80;ftp=foopy2" -- use HTTP proxy "foopy:80" for http:// + URLs, and HTTP proxy "foopy2:80" for + ftp:// URLs. + "foopy:80" -- use HTTP proxy "foopy:80" for all URLs. + "foopy:80,bar,direct://" -- use HTTP proxy "foopy:80" for all URLs, + failing over to "bar" if "foopy:80" is + unavailable, and after that using no + proxy. + "socks4://foopy" -- use SOCKS v4 proxy "foopy:1080" for all + URLs. + "http=foopy,socks5://bar.com -- use HTTP proxy "foopy" for http URLs, + and fail over to the SOCKS5 proxy + "bar.com" if "foopy" is unavailable. + "http=foopy,direct:// -- use HTTP proxy "foopy" for http URLs, + and use no proxy if "foopy" is + unavailable. + "http=foopy;socks=foopy2 -- use HTTP proxy "foopy" for http URLs, + and use socks4://foopy2 for all other + URLs. +``` + +### Session.setDownloadPath(path) + +* `path` String - The download location + +Sets download saving directory. By default, the download directory will be the +`Downloads` under the respective app folder. diff --git a/docs/api/chrome-command-line-switches.md b/docs/api/chrome-command-line-switches.md index d55dea4c1b5..2f995c99b21 100644 --- a/docs/api/chrome-command-line-switches.md +++ b/docs/api/chrome-command-line-switches.md @@ -1,7 +1,7 @@ # Supported Chrome command line switches -The following command lines switches in Chrome browser are also supported in -Electron, you can use [app.commandLine.appendSwitch][append-switch] to append +This page lists the command line switches used by the Chrome browser that are also supported by +Electron. You can use [app.commandLine.appendSwitch][append-switch] to append them in your app's main script before the [ready][ready] event of [app][app] module is emitted: @@ -17,11 +17,11 @@ app.on('ready', function() { ## --client-certificate=`path` -Sets `path` of client certificate file. +Sets the `path` of client certificate file. ## --ignore-connections-limit=`domains` -Ignore the connections limit for `domains` list seperated by `,`. +Ignore the connections limit for `domains` list separated by `,`. ## --disable-http-cache @@ -29,11 +29,11 @@ Disables the disk cache for HTTP requests. ## --remote-debugging-port=`port` -Enables remote debug over HTTP on the specified `port`. +Enables remote debugging over HTTP on the specified `port`. ## --proxy-server=`address:port` -Uses a specified proxy server, overrides system settings. This switch only +Use a specified proxy server, which overrides the system setting. This switch only affects HTTP and HTTPS requests. ## --proxy-pac-url=`url` @@ -42,12 +42,12 @@ Uses the PAC script at the specified `url`. ## --no-proxy-server -Don't use a proxy server, always make direct connections. Overrides any other +Don't use a proxy server and always make direct connections. Overrides any other proxy server flags that are passed. ## --host-rules=`rules` -Comma-separated list of `rules` that control how hostnames are mapped. +A comma-separated list of `rules` that control how hostnames are mapped. For example: @@ -60,7 +60,7 @@ For example: "www.google.com". These mappings apply to the endpoint host in a net request (the TCP connect -and host resolver in a direct connection, and the `CONNECT` in an http proxy +and host resolver in a direct connection, and the `CONNECT` in an HTTP proxy connection, and the endpoint host in a `SOCKS` proxy connection). ## --host-resolver-rules=`rules` @@ -77,15 +77,15 @@ Ignores certificate related errors. ## --ppapi-flash-path=`path` -Sets `path` of pepper flash plugin. +Sets the `path` of the pepper flash plugin. ## --ppapi-flash-version=`version` -Sets `version` of pepper flash plugin. +Sets the `version` of the pepper flash plugin. ## --log-net-log=`path` -Enables saving net log events and writes them to `path`. +Enables net log events to be saved and writes them to `path`. ## --v=`log_level` @@ -102,7 +102,7 @@ source files `my_module.*` and `foo*.*`. Any pattern containing a forward or backward slash will be tested against the whole pathname and not just the module. E.g. `*/foo/bar/*=2` would change the -logging level for all code in source files under a `foo/bar` directory. +logging level for all code in the source files under a `foo/bar` directory. To disable all chromium related logs and only enable your application logs you can do: diff --git a/docs/api/clipboard.md b/docs/api/clipboard.md index 572e91e9663..b2331b6279c 100644 --- a/docs/api/clipboard.md +++ b/docs/api/clipboard.md @@ -1,14 +1,14 @@ # clipboard -The `clipboard` provides methods to do copy/paste operations. An example of -writing a string to clipboard: +The `clipboard` provides methods to perform copy and paste operations. The following example +shows how to write a string to the clipboard: ```javascript var clipboard = require('clipboard'); clipboard.writeText('Example String'); ``` -On X Window systems, there is also a selection clipboard, to manipulate in it +On X Window systems, there is also a selection clipboard. To manipulate it you need to pass `selection` to each method: ```javascript @@ -21,46 +21,46 @@ console.log(clipboard.readText('selection')); * `type` String -Returns the content in clipboard as plain text. +Returns the content in the clipboard as plain text. ## clipboard.writeText(text[, type]) * `text` String * `type` String -Writes the `text` into clipboard as plain text. +Writes the `text` into the clipboard as plain text. ## clipboard.readHtml([type]) * `type` String -Returns the content in clipboard as markup. +Returns the content in the clipboard as markup. ## clipboard.writeHtml(markup[, type]) * `markup` String * `type` String -Writes the `markup` into clipboard. +Writes `markup` into the clipboard. ## clipboard.readImage([type]) * `type` String -Returns the content in clipboard as [NativeImage](native-image.md). +Returns the content in the clipboard as a [NativeImage](native-image.md). ## clipboard.writeImage(image[, type]) * `image` [NativeImage](native-image.md) * `type` String -Writes the `image` into clipboard. +Writes `image` into the clipboard. ## clipboard.clear([type]) * `type` String -Clears everything in clipboard. +Clears the clipboard. ## clipboard.availableFormats([type]) @@ -71,7 +71,7 @@ Returns an array of supported `format` for the clipboard `type`. * `data` String * `type` String -Returns whether clipboard supports the format of specified `data`. +Returns whether the clipboard supports the format of specified `data`. ```javascript var clipboard = require('clipboard'); @@ -85,7 +85,7 @@ console.log(clipboard.has('

selection

')); * `data` String * `type` String -Reads the `data` in clipboard. +Reads `data` from the clipboard. **Note:** This API is experimental and could be removed in future. @@ -101,4 +101,4 @@ Reads the `data` in clipboard. var clipboard = require('clipboard'); clipboard.write({text: 'test', html: "test"}); ``` -Writes the `data` iinto clipboard. +Writes `data` into clipboard. diff --git a/docs/api/content-tracing.md b/docs/api/content-tracing.md index c7575ba8a23..eab7b15d712 100644 --- a/docs/api/content-tracing.md +++ b/docs/api/content-tracing.md @@ -28,10 +28,10 @@ are reached. Once all child processes have acked to the `getCategories` request, `callback` is invoked with an array of category groups. -## tracing.startRecording(categoryFilter, options, callback) +## tracing.startRecording(categoryFilter, traceOptions, callback) * `categoryFilter` String -* `options` Integer +* `traceOptions` String * `callback` Function Start recording on all processes. @@ -51,9 +51,23 @@ Examples: * `test_MyTest*,test_OtherStuff`, * `"-excluded_category1,-excluded_category2` -`options` controls what kind of tracing is enabled, it could be a OR-ed -combination of `tracing.DEFAULT_OPTIONS`, `tracing.ENABLE_SYSTRACE`, -`tracing.ENABLE_SAMPLING` and `tracing.RECORD_CONTINUOUSLY`. +`traceOptions` controls what kind of tracing is enabled, it is a comma-delimited list. +Possible options are: + +* `record-until-full` +* `record-continuously` +* `trace-to-console` +* `enable-sampling` +* `enable-systrace` + +The first 3 options are trace recoding modes and hence mutually exclusive. +If more than one trace recording modes appear in the `traceOptions` string, +the last one takes precedence. If none of the trace recording mode is specified, +recording mode is `record-until-full`. + +The trace option will first be reset to the default option (record_mode set to +`record-until-full`, enable_sampling and enable_systrace set to false) +before options parsed from `traceOptions` are applied on it. ## tracing.stopRecording(resultFilePath, callback) @@ -75,10 +89,10 @@ Trace data will be written into `resultFilePath` if it is not empty, or into a temporary file. The actual file path will be passed to `callback` if it's not null. -## tracing.startMonitoring(categoryFilter, options, callback) +## tracing.startMonitoring(categoryFilter, traceOptions, callback) * `categoryFilter` String -* `options` Integer +* `traceOptions` String * `callback` Function Start monitoring on all processes. @@ -107,7 +121,7 @@ Get the current monitoring traced data. Child processes typically are caching trace data and only rarely flush and send trace data back to the main process. That is because it may be an expensive -operation to send the trace data over IPC, and we would like to avoid unneeded +operation to send the trace data over IPC, and we would like to avoid unneeded runtime overhead of tracing. So, to end tracing, we must asynchronously ask all child processes to flush any pending trace data. diff --git a/docs/api/crash-reporter.md b/docs/api/crash-reporter.md index ec937e5252a..da35008ea7f 100644 --- a/docs/api/crash-reporter.md +++ b/docs/api/crash-reporter.md @@ -1,6 +1,6 @@ # crash-reporter -An example of automatically submitting crash reporters to a remote server: +The following is an example of automatically submitting a crash report to a remote server: ```javascript crashReporter = require('crash-reporter'); @@ -18,31 +18,30 @@ crashReporter.start({ * `productName` String, default: Electron * `companyName` String, default: GitHub, Inc * `submitUrl` String, default: http://54.249.141.255:1127/post - * URL that crash reports would be sent to as POST + * URL that crash reports will be sent to as POST * `autoSubmit` Boolean, default: true * Send the crash report without user interaction * `ignoreSystemCrashHandler` Boolean, default: false * `extra` Object - * An object you can define which content will be send along with the report. + * An object you can define that will be sent along with the report. * Only string properties are sent correctly. * Nested objects are not supported. -Developers are required to call this method before using other crashReporter APIs. +Developers are required to call this method before using other `crashReporter` APIs. - -**Note:** On OS X, electron uses a new `crashpad` client, which is different -with the `breakpad` on Windows and Linux. To enable crash collection feature, -you are required to call `crashReporter.start` API to initialize `crashpad` in -main process and in each renderer process that you wish to collect crash reports. +**Note:** On OS X, Electron uses a new `crashpad` client, which is different +from `breakpad` on Windows and Linux. To enable the crash collection feature, +you are required to call `crashReporter.start` API to initialize `crashpad` in the +main process and in each renderer process from which you wish to collect crash reports. ## crashReporter.getLastCrashReport() -Returns the date and ID of the last crash report, when there was no crash report -sent or the crash reporter is not started, `null` will be returned. +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. ## crashReporter.getUploadedReports() -Returns all uploaded crash reports, each report contains date and uploaded ID. +Returns all uploaded crash reports. Each report contains the date and uploaded ID. # crash-reporter payload @@ -54,8 +53,8 @@ The crash reporter will send the following data to the `submitUrl` as `POST`: * `process_type` String - e.g. 'renderer' * `ptime` Number * `_version` String - The version in `package.json` -* `_productName` String - The product name in the crashReporter `options` object +* `_productName` String - The product name in the `crashReporter` `options` object * `prod` String - Name of the underlying product. In this case Electron -* `_companyName` String - The company name in the crashReporter `options` object +* `_companyName` String - The company name in the `crashReporter` `options` object * `upload_file_minidump` File - The crashreport as file -* All level one properties of the `extra` object in the crashReporter `options` object +* All level one properties of the `extra` object in the `crashReporter` `options` object diff --git a/docs/api/dialog.md b/docs/api/dialog.md index 9e123b381c5..d9336c0c8a3 100644 --- a/docs/api/dialog.md +++ b/docs/api/dialog.md @@ -70,7 +70,7 @@ will be passed via `callback(filename)` * `browserWindow` BrowserWindow * `options` Object - * `type` String - Can be `"none"`, `"info"`, `"error"`, `"question"` or `"warning"` + * `type` String - Can be `"none"`, `"info"`, `"error"`, `"question"` or `"warning"`. On Windows, "question" displays the same icon as "info", unless if you set an icon using the "icon" option * `buttons` Array - Array of texts for buttons * `title` String - Title of the message box, some platforms will not show it * `message` String - Content of the message box @@ -80,7 +80,12 @@ will be passed via `callback(filename)` instead of clicking the buttons of the dialog. By default it is the index of the buttons that have "cancel" or "no" as label, or 0 if there is no such buttons. On OS X and Windows the index of "Cancel" button will always be - used as `cancelId`, not matter whether it is already specified. + used as `cancelId`, not matter whether it is already specified + * `noLink` Boolean - On Windows Electron would try to figure out which ones of + the `buttons` are common buttons (like "Cancel" or "Yes"), and show the + others as command links in the dialog, this can make the dialog appear in + the style of modern Windows apps. If you don't like this behavior, you can + specify `noLink` to `true` * `callback` Function Shows a message box, it will block until the message box is closed. It returns diff --git a/docs/api/global-shortcut.md b/docs/api/global-shortcut.md index 16399cfbfe4..54da5638d19 100644 --- a/docs/api/global-shortcut.md +++ b/docs/api/global-shortcut.md @@ -1,9 +1,9 @@ # global-shortcut The `global-shortcut` module can register/unregister a global keyboard shortcut -in operating system, so that you can customize the operations for various shortcuts. -Note that the shortcut is global, even if the app does not get focused, it will still work. -You should not use this module until the ready event of app module gets emitted. +with the operating system, so that you can customize the operations for various shortcuts. +Note that the shortcut is global; it will work even if the app does not have the keyboard focus. +You should not use this module until the `ready` event of the app module is emitted. ```javascript var app = require('app'); @@ -37,14 +37,14 @@ app.on('will-quit', function() { * `accelerator` [Accelerator](accelerator.md) * `callback` Function -Registers a global shortcut of `accelerator`, the `callback` would be called when -the registered shortcut is pressed by user. +Registers a global shortcut of `accelerator`. The `callback` is called when +the registered shortcut is pressed by the user. ## globalShortcut.isRegistered(accelerator) * `accelerator` [Accelerator](accelerator.md) -Returns `true` or `false` depending on if the shortcut `accelerator` is registered. +Returns `true` or `false` depending on whether the shortcut `accelerator` is registered. ## globalShortcut.unregister(accelerator) diff --git a/docs/api/menu.md b/docs/api/menu.md index db149a29680..06400d11691 100644 --- a/docs/api/menu.md +++ b/docs/api/menu.md @@ -29,6 +29,8 @@ window.addEventListener('contextmenu', function (e) { Another example of creating the application menu with the simple template API: +**Note to Window and Linux users** the `selector` member of each menu item is a Mac-only [Accelerator option](https://github.com/atom/electron/blob/master/docs/api/accelerator.md). + ```html +``` + +### Utilizando un paquete `asar` como un archivo normal + +En algunas situaciones necesitaremos acceder al paquete `asar` como archivo, por ejemplo, +si necesitaramos verificar la integridad del archivo con un checksum. +Para casos así es posible utilizar el módulo `original-fs`, que provee la API `fs` original: + +```javascript +var originalFs = require('original-fs'); +originalFs.readFileSync('/path/to/example.asar'); +``` + +## Limitaciones de la API Node: + +A pesar de que hemos intentado que los paquetes `asar` funcionen como directorios de la mejor forma posible, +aún existen limitaciones debido a la naturaleza de bajo nivel de la API Node. + +### Los paquetes son de sólo lecutra + +Los paquetes `asar` no pueden ser modificados, por lo cual todas las funciones que modifiquen archivos +no funcionarán. + +## Los directorios del paquete no pueden establecerse como working directories + +A pesar de que los paquetes `asar` son manejados virtualmente como directorios, +estos directorios no existen en el sistema de archivos, por lo cual no es posible establecerlos +como working directory, el uso de la opción `cwd` en algunas APIs podría causar errores. + +### Desempaquetamiento adicional en algunas APIs + +La mayoría de las APIs `fs` pueden leer u obtener información sobre un archivo en un paquete `asar` sin +la necesidad de desempaquetarlo, pero algunas APIs requieren la ruta real. En estos casos Electron extraerá +el archivo a una ruta temporal. Esto agrega un overhead a algunas APIs. + +Las APIs que requieren el desempaquetamiento adicional son: + +* `child_process.execFile` +* `fs.open` +* `fs.openSync` +* `process.dlopen` - Utilizado po `require` en los módulos nativos + +### Información falsa en `fs.stat` + +El objeto `Stats` retornado por `fs.stat` y otras funciones relacionadas, +no es preciso, ya que los archivos del paquete `asar` no existen el sistema de archivos. +La utilización del objeto `Stats` sólo es recomendable para obtener el tamaño del archivo y/o +comprobar el tipo de archivo. + + +## Agregando archivos al paquete `asar` + +Como se menciona arriba, algunas APIs de Node desempaquetarán archivos cuando exista una llamada +que los referencie, además de los problemas de rendimiento que esto podría ocasionar, también +podría accionar alertas falsas en software antivirus. + +Para lidiar con esto, puedes desempaquetar algunos archivos utilizando la opción `--unpack`, +a continuación un ejemplo que excluye las librerías compartidas de los módulos nativos: + +```bash +$ asar pack app app.asar --unpack *.node +``` + +Después de ejecutar este comando, además del archivo `app.asar`, también se creará +un directorio `app.asar.unpacked`, que contendrá los archivos desempaquetados. +Este directorio deberá copiarse junto con el archivo `app.asar` a la hora de distribuir la aplicación. + +[asar]: https://github.com/atom/asar diff --git a/docs/tutorial/application-packaging.md b/docs/tutorial/application-packaging.md index 0741b257244..7568204fb62 100644 --- a/docs/tutorial/application-packaging.md +++ b/docs/tutorial/application-packaging.md @@ -1,6 +1,6 @@ # Application packaging -To protect your app's resources and source code from the users, you can choose +To mitigate [issues](https://github.com/joyent/node/issues/6960) around long path names on Windows, slightly speed up `require` and conceal your source code from cursory inspection you can choose to package your app into an [asar][asar] archive with little changes to your source code. @@ -161,3 +161,4 @@ After running the command, apart from the `app.asar`, there is also an should copy it together with `app.asar` when shipping it to users. [asar]: https://github.com/atom/asar + diff --git a/docs/tutorial/debugging-main-process-es.md b/docs/tutorial/debugging-main-process-es.md new file mode 100644 index 00000000000..1a764036e37 --- /dev/null +++ b/docs/tutorial/debugging-main-process-es.md @@ -0,0 +1,45 @@ +# Depurando el proceso principal + +Los devtools sólo pueden depurar las páginas web (el código del proceso renderer). +Para depurar el código del proceso principal, Electron provee dos opciones para la línea de comandos: `--debug` y `--debug-brk`. + +## Opciones para la línea de comandos + +### `--debug=[port]` + +Esta opción escuchará mensajes del protocolo de depuración V8 en `port`, por defecto `port` es `5858`. + +### `--debug-brk=[port]` + +Similar a `--debug` pero realiza una pausa en la primera línea del script. + +## Utilizando node-inspector para depuración + +__Nota:__ Electron utiliza node v0.11.13, esta versión aún no funciona bien con node-inspector, +el proceso principal podría fallar al inspeccionar el objeto `process`. + +### 1. Iniciar [node-inspector][node-inspector] + +```bash +$ node-inspector +``` + +### 2. Activar el modo de depuración en Electron + +Es posible iniciar Electron con la opción de depuración: + +```bash +$ electron --debug=5858 your/app +``` + +o, pausar el script en la primera línea: + +```bash +$ electron --debug-brk=5858 your/app +``` + +### 3. Cargar la interfaz del depurador + +Abre http://127.0.0.1:8080/debug?ws=127.0.0.1:8080&port=5858 en Chrome. + +[node-inspector]: https://github.com/node-inspector/node-inspector diff --git a/docs/tutorial/desktop-environment-integration-es.md b/docs/tutorial/desktop-environment-integration-es.md new file mode 100644 index 00000000000..f19f36326f3 --- /dev/null +++ b/docs/tutorial/desktop-environment-integration-es.md @@ -0,0 +1,171 @@ +# Integración con el entorno de escritorio + +Los sistemas operativos proveen diferentes características para integrar aplicaciones +en sus entornos de escritorio. Por ejemplo, en Windows, las aplicaciones pueden agregar accesos directos +en la JumpList de la barra de tareas, y en Mac, las aplicaciones pueden agregar un menú personalizado en el dock. + +Esta guía explica cómo integrar tu aplicación en esos entornos de escritorio a través de las APIs de Electron. + +## Documentos recientes (Windows y OS X) + +Windows y OS X proveen un acceso sencillo a la lista de documentos recientes. + +__JumpList:__ + +![JumpList, Archivos recientes](http://i.msdn.microsoft.com/dynimg/IC420538.png) + +__Menú Dock:__ + + + +Para agregar un archivo a la lista de documentos recientes, puedes utilizar: +[app.addRecentDocument][addrecentdocument] API: + +```javascript +var app = require('app'); +app.addRecentDocument('/Users/USERNAME/Desktop/work.type'); +``` + +También puedes utilizar [app.clearRecentDocuments](clearrecentdocuments) para vaciar la lista de documentos recientes: + +```javascript +app.clearRecentDocuments(); +``` + +### Notas sobre Windows + +Para activar esta característica en Windows, tu aplicación debe registrar un handler +para el tipo de archivo que quieres utilizar, de lo contrario el archivo no aparecerá +en la JumpList, aún después de agregarlo. Puedes encontrar más información sobre el proceso de +registrar tu aplicación en [Application Registration][app-registration]. + +Cuando un usuario haga click en un archivo de la JumpList, una nueva instancia de tu aplicación +se iniciará, la ruta del archivo se agregará como un argumento de la línea de comandos. + +### Notas sobre OS X + +Cuando un archivo es solicitado desde el menú de documentos recientes, el evento `open-file` +del módulo `app` será emitido. + +## Menú dock personalizado (OS X) + +OS X permite a los desarrolladores definir un menú personalizado para el dock, +el cual usualmente contiene algunos accesos directos a las características más comunes +de tu aplicación: + +__Menú dock de Terminal.app:__ + + + +Para establecer tu menú dock, puedes utilizar la API `app.dock.setMenu`, la cual sólo está disponible para OSX: + +```javascript +var app = require('app'); +var Menu = require('menu'); +var dockMenu = Menu.buildFromTemplate([ + { label: 'New Window', click: function() { console.log('New Window'); } }, + { label: 'New Window with Settings', submenu: [ + { label: 'Basic' }, + { label: 'Pro'} + ]}, + { label: 'New Command...'} +]); +app.dock.setMenu(dockMenu); +``` + +## Tareas de usuario (Windows) + +En Windows puedes especificar acciones personalizadas en la categoría `Tasks` del JumpList, +tal como menciona MSDN: + + +> Las aplicaciones definen tareas basadas en las características del programa +> y las acciones clave que se esperan de un usuario. Las tareas deben ser +> libres de contexto, es decir, la aplicación no debe encontrarse en ejecución +> para que estas acciones funcionen. También deberían ser las acciones estadísticamente +> más comunes que un usuario normal realizaría en tu aplicación, como por ejemplo, +> redactar un mensaje de correo electrónico, crear un documento en el procesador de textos, +> ejecutar una aplicación en cierto modo, o ejecutar alguno de sus subcomandos. Una aplicación +> no debería popular el menú con características avanzadas que el usuario estándar no necesita +> ni con acciones que sólo se realizan una vez, como por ejemplo, el registro. No utilices +> las tareas para mostrar elementos promocionales como actualizaciones u ofertas especiales. +> +> Es recomendable que la lista de tareas sea estática. Debe mantenerse a pesar +> de los cambios de estado de la aplicación. Aunque exista la posibilidad de variar +> el contenido de la lista dinámicamente, debes considerar que podría ser confuso +> para un usuario que no espera que el destino de la lista cambie. + +__Tareas de Internet Explorer:__ + +![IE](http://i.msdn.microsoft.com/dynimg/IC420539.png) + +A diferencia del menú dock en OS X, el cual es un menú real, las tareas de usuario en Windows +funcionan como accesos directos de aplicación, que al ser clickeados, lanzan el programa +con argumentos específicos. + +Para establecer las tareas de usuario en tu aplicación, puedes utilizar: +[app.setUserTasks][setusertaskstasks] API: + +```javascript +var app = require('app'); +app.setUserTasks([ + { + program: process.execPath, + arguments: '--new-window', + iconPath: process.execPath, + iconIndex: 0, + title: 'New Window', + description: 'Create a new window' + } +]); +``` + +Para purgar la lista de tareas, puedes llamar a `app.setUserTasks` con un array vacío: + +```javascript +app.setUserTasks([]); +``` + +Las tareas de usuario aún serán visibles después de cerrar tu aplicación, por lo cual +el ícono y la ruta del programa deben existir hasta que la aplicación sea desinstalada. + +## Accesos directos en el lanzador Unity (Linux) + +En Unity, es posible agregar algunas entradas personalizadas, modificando el archivo `.desktop`, +ver [Adding shortcuts to a launcher][unity-launcher]. + +__Accesos directos de Audacious:__ + +![audacious](https://help.ubuntu.com/community/UnityLaunchersAndDesktopFiles?action=AttachFile&do=get&target=shortcuts.png) + +## Barra de progreso en la barra de tareas (Windows y Unity) + +En Windows, un botón en la barra de tareas puede utilizarse para mostrar una barra de progreso. Esto permite +que una ventana muestre la información de progreso al usuario, sin que el usuario tenga la ventana de la aplicación activa. + +El entorno de escritorio Unity también posee una característica similar que permite mostrar una barra de progreso en el lanzador. + +__Barra de progreso en un botón de la barra de herramientas:__ + +![Taskbar Progress Bar](https://cloud.githubusercontent.com/assets/639601/5081682/16691fda-6f0e-11e4-9676-49b6418f1264.png) + +__Barra de progreso en el lanzador Unity:__ + +![Unity Launcher](https://cloud.githubusercontent.com/assets/639601/5081747/4a0a589e-6f0f-11e4-803f-91594716a546.png) + +Para establecer la barra de progreso de una ventana, puedes utilizar +[BrowserWindow.setProgressBar][setprogressbar] API: + +```javascript +var window = new BrowserWindow({...}); +window.setProgressBar(0.5); +``` + +[addrecentdocument]: ../api/app.md#appaddrecentdocumentpath +[clearrecentdocuments]: ../api/app.md#appclearrecentdocuments +[setusertaskstasks]: ../api/app.md#appsetusertaskstasks +[setprogressbar]: ../api/browser-window.md#browserwindowsetprogressbarprogress +[setrepresentedfilename]: ../api/browser-window.md#browserwindowsetrepresentedfilenamefilename +[setdocumentedited]: ../api/browser-window.md#browserwindowsetdocumenteditededited +[app-registration]: http://msdn.microsoft.com/en-us/library/windows/desktop/ee872121(v=vs.85).aspx +[unity-launcher]: https://help.ubuntu.com/community/UnityLaunchersAndDesktopFiles#Adding_shortcuts_to_a_launcher diff --git a/docs/tutorial/desktop-environment-integration.md b/docs/tutorial/desktop-environment-integration.md index 0691b15f409..80b4c456938 100644 --- a/docs/tutorial/desktop-environment-integration.md +++ b/docs/tutorial/desktop-environment-integration.md @@ -134,6 +134,59 @@ The user tasks will still show even after your application closes, so the icon and program path specified for a task should exist until your application is uninstalled. +## Thumbnail Toolbars + +On Windows, you can add a thumbnail toolbar with specified buttons in a taskbar +layout of an application window. It provides users a way to access to a particualr +window's command without restoring or activating the window. + +From MSDN, it's illustrated: + +> This toolbar is simply the familiar standard toolbar common control. It has a +> maximum of seven buttons. Each button's ID, image, tooltip, and state are defined +> in a structure, which is then passed to the taskbar. The application can show, +> enable, disable, or hide buttons from the thumbnail toolbar as required by its +> current state. +> +> For example, Windows Media Player might offer standard media transport controls +> such as play, pause, mute, and stop. + +__Thumbnail toolbar of Windows Media Player:__ + +![player](https://i-msdn.sec.s-msft.com/dynimg/IC420540.png) + +You can use [BrowserWindow.setThumbarButtons][setthumbarbuttons] to set thumbnail +toolbar in your application: + +``` +var BrowserWindow = require('browser-window'); +var path = require('path'); +var win = new BrowserWindow({ + width: 800, + height: 600 +}); +win.setThumbarButtons([ + { + tooltip: "button1", + icon: path.join(__dirname, 'button1.png'), + click: function() { console.log("button2 clicked"); } + }, + { + tooltip: "button2", + icon: path.join(__dirname, 'button2.png'), + flags:['enabled', 'dismissonclick'], + click: function() { console.log("button2 clicked."); } + } +]); +``` + +To clean thumbnail toolbar buttons, just call `BrowserWindow.setThumbarButtons` +with an empty array: + +```javascript +win.setThumbarButtons([]); +``` + ## Unity launcher shortcuts (Linux) In Unity, you can add custom entries to its launcher via modifying `.desktop` @@ -199,3 +252,4 @@ window.setDocumentEdited(true); [setdocumentedited]: ../api/browser-window.md#browserwindowsetdocumenteditededited [app-registration]: http://msdn.microsoft.com/en-us/library/windows/desktop/ee872121(v=vs.85).aspx [unity-launcher]: https://help.ubuntu.com/community/UnityLaunchersAndDesktopFiles#Adding_shortcuts_to_a_launcher +[setthumbarbuttons]: ../api/browser-window.md#browserwindowsetthumbarbuttonsbuttons diff --git a/docs/tutorial/devtools-extension-es.md b/docs/tutorial/devtools-extension-es.md new file mode 100644 index 00000000000..f54c3e0eaa8 --- /dev/null +++ b/docs/tutorial/devtools-extension-es.md @@ -0,0 +1,49 @@ +# Extensión DevTools + +Para facilitar la depuración, Electron provee un soporte básico para la extensión +[Chrome DevTools Extension][devtools-extension]. + +Para la mayoría de las extensiones devtools, simplemente puedes descargar el código fuente +y utilizar `BrowserWindow.addDevToolsExtension` para cargarlas, las extensiones cargadas +serán recordadas para que no sea necesario llamar a la función cada vez que creas una ventana. + +Por ejemplo, para usar la extensión [React DevTools Extension](https://github.com/facebook/react-devtools), primero debes descargar el código fuente: + +```bash +$ cd /some-directory +$ git clone --recursive https://github.com/facebook/react-devtools.git +``` + +Luego cargas la aplicación en Electron, abriendo devtools en cualquier ventana, +y ejecutando este código en la consola devtools: + +```javascript +require('remote').require('browser-window').addDevToolsExtension('/some-directory/react-devtools'); +``` + +Para remover una extensión, puedes utilizar `BrowserWindow.removeDevToolsExtension` +especificando el nombre, y esta ya no se cargará la siguiente vez que abras devtools: + +```javascript +require('remote').require('browser-window').removeDevToolsExtension('React Developer Tools'); +``` + +## Formato de las extensiones devtools + +Idealmente todas las extensiones devtools escritas para Chrome pueden ser cargadas por Electron, +pero para ello deben estar en un directorio plano, las extensiones empaquetadas como `crx` +no pueden ser cargadas por Chrome a no ser que halles una forma de extraerlas a un directorio. + +## Páginas en segundo plano (background) + +Electron no soporta la característica de páginas en segundo plano de las extensiones de Chrome, +las extensiones que utilizan esta característica podrían no funcionar. + +## APIs `chrome.*` + +Algunas extensiones utilizan las APIs `chrome.*`, hemos realizado un esfuerzo +para implementar esas APIs en Electron, sin embargo no han sido implementadas en su totalidad. + +Dado que no todas las funciones `chrome.*` han sido implementadas, si la extensión devtools está utilizando otras APIs más allá de `chrome.devtools.*`, es muy probable que no funcione. Puedes reportar fallos en el issue tracker para que podamos agregar soporte a esas APIs. + +[devtools-extension]: https://developer.chrome.com/extensions/devtools diff --git a/docs/tutorial/online-offline-events-es.md b/docs/tutorial/online-offline-events-es.md new file mode 100644 index 00000000000..0e43f9b1610 --- /dev/null +++ b/docs/tutorial/online-offline-events-es.md @@ -0,0 +1,80 @@ +# Detección del evento en línea/fuera de línea + +La detección de estos eventos puede ser implementada en el proceso renderer utilizando las APIs HTML5 estándar, +como en este ejemplo: + +_main.js_ + +```javascript +var app = require('app'); +var BrowserWindow = require('browser-window'); +var onlineStatusWindow; + +app.on('ready', function() { + onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false }); + onlineStatusWindow.loadUrl('file://' + __dirname + '/online-status.html'); +}); +``` + +_online-status.html_ + +```html + + + + + + +``` + +Existen casos en donde necesitas responder a estos eventos desde el proceso principal. +El proceso principal no posee un objeto `navigator`, por lo tanto no puede detectar estos eventos directamente. +Es posible reenviar el evento al proceso principal mediante la utilidad de intercomunicación entre procesos (ipc): + +_main.js_ + +```javascript +var app = require('app'); +var ipc = require('ipc'); +var BrowserWindow = require('browser-window'); +var onlineStatusWindow; + +app.on('ready', function() { + onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false }); + onlineStatusWindow.loadUrl('file://' + __dirname + '/online-status.html'); +}); + +ipc.on('online-status-changed', function(event, status) { + console.log(status); +}); +``` + +_online-status.html_ + +```html + + + + + + +``` diff --git a/docs/tutorial/quick-start-es.md b/docs/tutorial/quick-start-es.md new file mode 100644 index 00000000000..5c3095deb3e --- /dev/null +++ b/docs/tutorial/quick-start-es.md @@ -0,0 +1,154 @@ +# Intro + +## Introducción + +Electron permite la creación de aplicaciones de escritorio utilizando JavaScript puro, a través de un runtime con APIs nativas. Puedes verlo como una variante de io.js, enfocado en aplicaciones de escritorio, en vez de servidores web. + +Esto no significa que Electron sea un binding de librerías GUI para JavaScript. +Electron utiliza páginas web como su GUI, por lo cual puedes verlo como un navegador Chromium mínimo, +controlado por JavaScript. + +### El proceso principal (main process) + +En Electron, el proceso que ejecuta el script `main` del `package.json` se llama __el proceso principal__. +El script que corre en el proceso principal puede crear páginas para mostrar la GUI. + +### El proceso renderer (renderer process) + +Dado que Electron utiliza Chromium para mostrar las páginas web, +también es utilizada la arquitectura multiproceso de Chromium. +Cada página web en Electron se ejecuta en su propio proceso, +el cual es llamado __el proceso renderer__. + +En los navegadores normales, las páginas web usualmente se ejecutan en un entorno +sandbox y no tienen acceso a los recursos nativos. Los usuarios de Electron tienen el poder +de utilizar las APIs de io.js en las páginas web, permitiendo interacciones de bajo nivel con el sistema operativo. + +### Diferencias entre el proceso principal y el proceso renderer + +El proceso principal crea páginas web mediante instancias de `BrowserWindow`. Cada instancia de `BrowserWindow` ejecuta su propia página web y su propio proceso renderer. +Cuando una instancia de `BrowserWindow` es destruida, también su proceso renderer correspondiente acaba. + +El proceso principal gestiona las páginas web y sus correspondientes procesos renderer. +Cada proceso renderer es aislado y sólo considera relevante la página web que corre en él. + +En las páginas web, no está permitido llamar a APIs relacionadas a la GUI nativa +porque la gestión de los recursos GUI nativos es peligrosa, y tiende a que ocurran leaks de memoria. +Si deseas realizar operaciones GUI en una página web, el proceso renderer de la página web debe comunicarse +con el proceso principal, y solicitar a este que realice esas operaciones. + +En Electron, hemos proveído el módulo [ipc](../api/ipc-renderer.md) para la comunicación +entre el proceso principal y el proceso renderer. Y también hay un módulo [remote](../api/remote.md) +para comunicación al estilo RPC. + +## Escribe tu primera aplicación Electron + +Generalmente, una aplicación Electron tendrá la siguiente estructura: + +```text +your-app/ +├── package.json +├── main.js +└── index.html +``` + +El formato de `package.json` es exactamente el mismo que cualquier módulo Node, +y el script especificado en el campo `main` será el script de arranque de tu aplicación, +a ser ejecutado por el proceso principal. Un ejemplo de `package.json` podría verse así: + +```json +{ + "name" : "your-app", + "version" : "0.1.0", + "main" : "main.js" +} +``` + +El `main.js` debería crear las ventanas y gestionar los eventos del sistema, un ejemplo típico sería: +example being: + +```javascript +var app = require('app'); // Módulo para controlar el ciclo de vida de la aplicación. +var BrowserWindow = require('browser-window'); // Módulo para crear uan ventana de navegador. + +// Reportar crashes a nuestro servidor. +require('crash-reporter').start(); + +// Mantener una referencia global al objeto window, si no lo haces, esta ventana +// se cerrará automáticamente cuando el objeto JavaScript sea recolectado (garbage collected): +var mainWindow = null; + +// Salir de todas las ventanas cuando se cierren. +app.on('window-all-closed', function() { + // En OS X es común que las aplicaciones y su barra de menú + // se mantengan activas hasta que el usuario cierre la aplicación + // explícitamente utilizando Cmd + Q + if (process.platform != 'darwin') { + app.quit(); + } +}); + +// Este método será llamado cuando Electron haya finalizado la inicialización +// y esté listo para crear ventanas de navegador. +app.on('ready', function() { + // Crear la ventana. + mainWindow = new BrowserWindow({width: 800, height: 600}); + + // cargar el index.html de nuestra aplicación. + mainWindow.loadUrl('file://' + __dirname + '/index.html'); + + // Desplegar devtools. + mainWindow.openDevTools(); + + // Evento emitido cuando se cierra la ventana. + mainWindow.on('closed', function() { + // Eliminar la referencia del objeto window. + // En el caso de soportar multiples ventanas, es usual almacenar + // los objetos window en un array, este es el momento en el que debes eliminar el elemento correspondiente. + mainWindow = null; + }); +}); +``` + +Finalmente el `index.html` es la página web que mostraremos: + +```html + + + + Hello World! + + +

Hello World!

+ We are using io.js + and Electron . + + +``` + +## Ejecutar la aplicación + +Cuando termines de escribir tu aplicación, puedes distribuirla +siguiendo la [guía de distribución](./application-distribution-es.md) +y luego ejecutar la aplicación empaquetada. También puedes utilizar el binario de Electron +para ejecutar tu aplicación de forma directa. + +En Windows: + +```bash +$ .\electron\electron.exe your-app\ +``` + +En Linux: + +```bash +$ ./electron/electron your-app/ +``` + +En OS X: + +```bash +$ ./Electron.app/Contents/MacOS/Electron your-app/ +``` + +`Electron.app` es parte del paquete de release de Electron, puedes descargarlo [aquí](https://github.com/atom/electron/releases). diff --git a/docs/tutorial/quick-start.md b/docs/tutorial/quick-start.md index 8f723bbcf64..197cf0d3b32 100644 --- a/docs/tutorial/quick-start.md +++ b/docs/tutorial/quick-start.md @@ -4,39 +4,40 @@ Electron enables you to create desktop applications with pure JavaScript by providing a runtime with rich native APIs. You could see it as a variant of the io.js runtime which is focused on desktop applications instead of web servers. -It doesn't mean Electron is a JavaScript binding to GUI libraries. Instead, +This doesn't mean Electron is a JavaScript binding to GUI libraries. Instead, Electron uses web pages as its GUI, so you could also see it as a minimal Chromium browser, controlled by JavaScript. ### Main process In Electron, the process that runs `package.json`'s `main` script is called -__the main process__. The script that runs in the main process, can display GUI by +__the main process__. The script that runs in the main process can display a GUI by creating web pages. ### Renderer process Since Electron uses Chromium for displaying web pages, Chromium's -multi-processes architecture is also used. Each web page in Electron runs in +multi-process architecture is also used. Each web page in Electron runs in its own process, which is called __the renderer process__. -In normal browsers web pages usually run in a sandboxed environment and are not -allowed access to native resources. Electron users however, have the power to use +In normal browsers, web pages usually run in a sandboxed environment and are not +allowed access to native resources. Electron users, however, have the power to use io.js APIs in web pages allowing lower level operating system interactions. ### Differences between main process and renderer process The main process creates web pages by creating `BrowserWindow` instances. Each `BrowserWindow` instance runs the web page in its own renderer process. When a `BrowserWindow` instance is destroyed, the corresponding renderer process -would also be terminated. +is also terminated. The main process manages all web pages and their corresponding renderer -processes, each renderer process is isolated and only cares +processes. Each renderer process is isolated and only cares about the web page running in it. In web pages, it is not allowed to call native GUI related APIs because managing -native GUI resources in web pages is very dangerous and easy to leak resources. -If you want to do GUI operations in web pages, you have to communicate with -the main process to do it there. +native GUI resources in web pages is very dangerous and it is easy to leak resources. +If you want to perform GUI operations in a web page, the renderer process of the web +page must communicate with the main process to request the main process perform those +operations. In Electron, we have provided the [ipc](../api/ipc-renderer.md) module for communication between main process and renderer process. And there is also a @@ -77,20 +78,20 @@ var BrowserWindow = require('browser-window'); // Module to create native brows require('crash-reporter').start(); // Keep a global reference of the window object, if you don't, the window will -// be closed automatically when the javascript object is GCed. +// be closed automatically when the JavaScript object is GCed. var mainWindow = null; // Quit when all windows are closed. app.on('window-all-closed', function() { - // On OSX it is common for applications and their menu bar + // On OS X it is common for applications and their menu bar // to stay active until the user quits explicitly with Cmd + Q if (process.platform != 'darwin') { app.quit(); } }); -// This method will be called when Electron has done everything -// initialization and ready for creating browser windows. +// This method will be called when Electron has finished +// initialization and is ready to create browser windows. app.on('ready', function() { // Create the browser window. mainWindow = new BrowserWindow({width: 800, height: 600}); @@ -129,24 +130,41 @@ Finally the `index.html` is the web page you want to show: ## Run your app -After you're done writing your app, you can create a distribution by -following the [Application distribution](./application-distribution.md) guide -and then execute the packaged app. You can also just use the downloaded -Electron binary to execute your app directly. +Once you've created your initial `main.js`, `index.html`, and `package.json` files, +you'll probably want to try running your app locally to test it and make sure it's +working as expected. -On Windows: +### electron-prebuilt +If you've installed `electron-prebuilt` globally with `npm`, then you need only +run the following in your app's source directory: + +```bash +electron . +``` + +If you've installed it locally, then run: + +```bash +./node_modules/.bin/electron . +``` + +### Manually Downloaded Electron Binary +If you downloaded Electron manually, you can also just use the included +binary to execute your app directly. + +#### Windows ```bash $ .\electron\electron.exe your-app\ ``` -On Linux: +#### Linux ```bash $ ./electron/electron your-app/ ``` -On OS X: +#### OS X ```bash $ ./Electron.app/Contents/MacOS/Electron your-app/ @@ -154,3 +172,8 @@ $ ./Electron.app/Contents/MacOS/Electron your-app/ `Electron.app` here is part of the Electron's release package, you can download it from [here](https://github.com/atom/electron/releases). + +### Run as a distribution +After you're done writing your app, you can create a distribution by +following the [Application distribution](./application-distribution.md) guide +and then executing the packaged app. diff --git a/docs/tutorial/using-native-node-modules-es.md b/docs/tutorial/using-native-node-modules-es.md new file mode 100644 index 00000000000..78409049ad9 --- /dev/null +++ b/docs/tutorial/using-native-node-modules-es.md @@ -0,0 +1,57 @@ +# Utilizando módulos Node nativos + +Los módulos Node nativos son soportados por Electron, pero dado que Electron +está utilizando una versión distinta de V8, debes especificar manualmente la +ubicación de las cabeceras de Electron a la hora de compilar módulos nativos. + +## Compatibilidad de módulos nativos + +A partir de Node v0.11.x han habido cambios vitales en la API de V8. +Es de esperar que los módulos escritos para Node v0.10.x no funcionen con Node v0.11.x. +Electron utiliza Node v.0.11.13 internamente, y por este motivo tiene el mismo problema. + +Para resolver esto, debes usar módulos que soporten Node v0.11.x, +[muchos módulos](https://www.npmjs.org/browse/depended/nan) soportan ambas versiones. +En el caso de los módulos antiguos que sólo soportan Node v0.10.x, debes usar el módulo +[nan](https://github.com/rvagg/nan) para portarlos a v0.11.x. + +## ¿Cómo instalar módulos nativos? + +### La forma fácil + +La forma más sencilla de recompilar módulos nativos es a través del paquete +[`electron-rebuild`](https://github.com/paulcbetts/electron-rebuild), +el cual abstrae y maneja los pasos de descargar las cabeceras y compilar los módulos nativos: + +```sh +npm install --save-dev electron-rebuild + +# Ejecuta esto cada vez que ejecutes npm install +./node_modules/.bin/electron-rebuild +``` + +### La forma node-gyp + +Para compilar módulos Node con las cabeceras de Electron, debes indicar a `node-gyp` +desde dónde descargar las cabeceras y cuál versión usar: + +```bash +$ cd /path-to-module/ +$ HOME=~/.electron-gyp node-gyp rebuild --target=0.29.1 --arch=x64 --dist-url=https://atom.io/download/atom-shell +``` + +Los cambios en `HOME=~/.electron-gyp` fueron para especificar la ruta de las cabeceras. +La opción `--target=0.29.1` es la versión de Electron. La opción `--dist-url=...` especifica +dónde descargar las cabeceras. `--arch=x64` indica que el módulo será compilado para un sistema de 64bit. + +### La forma npm + +También puedes usar `npm` para instalar módulos, los pasos son exactamente igual a otros módulos Node, +con la excepción de que necesitas establecer algunas variables de entorno primero: + +```bash +export npm_config_disturl=https://atom.io/download/atom-shell +export npm_config_target=0.29.1 +export npm_config_arch=x64 +HOME=~/.electron-gyp npm install module-name +``` diff --git a/docs/tutorial/using-pepper-flash-plugin-es.md b/docs/tutorial/using-pepper-flash-plugin-es.md new file mode 100644 index 00000000000..fbb2b6f83aa --- /dev/null +++ b/docs/tutorial/using-pepper-flash-plugin-es.md @@ -0,0 +1,58 @@ +# Utilizando el plugin Pepper Flash + +El plugin Pepper Flash es soportado ahora. Para utilizar pepper flash en Electron, debes especificar la ubicación del plugin manualmente y activarlo en tu aplicación. + +## Preparar una copia del plugin Flash + +En OSX y Linux, el detalle del plugin puede encontrarse accediendo a `chrome://plugins` en el navegador. Su ubicación y versión son útiles para el soporte. También puedes copiarlo a otro lugar. + +## Agrega la opción a Electron + +Puedes agregar la opción `--ppapi-flash-path` y `ppapi-flash-version` o utilizar el método `app.commandLine.appendSwitch` antes del evento ready de la aplicación. +También puedes agregar la opción `plugins` de `browser-window`. Por ejemplo, + +```javascript +var app = require('app'); +var BrowserWindow = require('browser-window'); + +// Report crashes to our server. +require('crash-reporter').start(); + +// Keep a global reference of the window object, if you don't, the window will +// be closed automatically when the javascript object is GCed. +var mainWindow = null; + +// Quit when all windows are closed. +app.on('window-all-closed', function() { + if (process.platform != 'darwin') { + app.quit(); + } +}); + +// Specify flash path. +// On Windows, it might be /path/to/pepflashplayer.dll +// On Mac, /path/to/PepperFlashPlayer.plugin +// On Linux, /path/to/libpepflashplayer.so +app.commandLine.appendSwitch('ppapi-flash-path', '/path/to/libpepflashplayer.so'); + +// Specify flash version, for example, v17.0.0.169 +app.commandLine.appendSwitch('ppapi-flash-version', '17.0.0.169'); + +app.on('ready', function() { + mainWindow = new BrowserWindow({ + 'width': 800, + 'height': 600, + 'web-preferences': { + 'plugins': true + } + }); + mainWindow.loadUrl('file://' + __dirname + '/index.html'); + // Something else +}); +``` + +## Activar el plugin flash en una etiqueta `` +Agrega el atributo `plugins`. +```html + +``` diff --git a/docs/tutorial/using-selenium-and-webdriver-es.md b/docs/tutorial/using-selenium-and-webdriver-es.md new file mode 100644 index 00000000000..7d998905705 --- /dev/null +++ b/docs/tutorial/using-selenium-and-webdriver-es.md @@ -0,0 +1,72 @@ +# Utilizando Selenium y WebDriver + +De [ChromeDriver - WebDriver for Chrome][chrome-driver]: + +> WebDriver es una herramienta de código abierto para automatizar el testing de aplicaciones web +> en varios navegadores. WebDriver provee funciones de navegación, entrada de usuario, +> ejecución de JavaScript, y más. ChromeDriver es un servidor standalone que implementa +> el protocolo de WebDriver para Chromium. Se encuentra en desarrollo por los miembros de +> Chromium y WebDriver. + +En la página de [lanzamientos](https://github.com/atom/electron/releases) de Electron encontrarás paquetes de `chromedriver`. + +## Ajustando parámetros con WebDriverJs + +[WebDriverJs](https://code.google.com/p/selenium/wiki/WebDriverJs) provee +un paquete Node para realizar testing con web driver, lo usaremos como ejemplo. + +### 1. Inicia chrome driver + +Primero necesitas descargar el binario `chromedriver` y ejecutarlo: + +```bash +$ ./chromedriver +Starting ChromeDriver (v2.10.291558) on port 9515 +Only local connections are allowed. +``` + +Recuerda el puerto `9515`, lo utilizaremos después. + +### 2. Instala WebDriverJS + +```bash +$ npm install selenium-webdriver +``` + +### 3. Conecta chrome driver + +El uso de `selenium-webdriver` junto con Electron es básicamente el mismo que el original, +excepto que necesitas especificar manualmente cómo se conectará el chrome driver +y dónde encontrará el binario de Electron: + +```javascript +var webdriver = require('selenium-webdriver'); + +var driver = new webdriver.Builder() + // El puerto "9515" es que abre chrome driver. + .usingServer('http://localhost:9515') + .withCapabilities({chromeOptions: { + // Aquí especificamos la ruta a Electron + binary: '/Path-to-Your-App.app/Contents/MacOS/Atom'}}) + .forBrowser('electron') + .build(); + +driver.get('http://www.google.com'); +driver.findElement(webdriver.By.name('q')).sendKeys('webdriver'); +driver.findElement(webdriver.By.name('btnG')).click(); +driver.wait(function() { + return driver.getTitle().then(function(title) { + return title === 'webdriver - Google Search'; + }); +}, 1000); + +driver.quit(); +``` + +## Workflow + +Para probar tu aplicación sin recompilar Electron, simplemente [copia](https://github.com/atom/electron/blob/master/docs/tutorial/application-distribution.md) las fuentes de tu aplicación en el directorio de recursos de Electron. + +[chrome-driver]: https://sites.google.com/a/chromium.org/chromedriver/ + + diff --git a/filenames.gypi b/filenames.gypi index 837dd3301cc..40af1ebb1fe 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -166,6 +166,8 @@ 'atom/browser/ui/accelerator_util.h', 'atom/browser/ui/accelerator_util_mac.mm', 'atom/browser/ui/accelerator_util_views.cc', + 'atom/browser/ui/atom_menu_model.cc', + 'atom/browser/ui/atom_menu_model.h', 'atom/browser/ui/cocoa/atom_menu_controller.h', 'atom/browser/ui/cocoa/atom_menu_controller.mm', 'atom/browser/ui/cocoa/event_processing_window.h', @@ -196,14 +198,22 @@ 'atom/browser/ui/views/menu_delegate.h', 'atom/browser/ui/views/menu_layout.cc', 'atom/browser/ui/views/menu_layout.h', + 'atom/browser/ui/views/native_frame_view.cc', + 'atom/browser/ui/views/native_frame_view.h', 'atom/browser/ui/views/submenu_button.cc', 'atom/browser/ui/views/submenu_button.h', 'atom/browser/ui/views/win_frame_view.cc', 'atom/browser/ui/views/win_frame_view.h', + 'atom/browser/ui/win/atom_desktop_window_tree_host_win.cc', + 'atom/browser/ui/win/atom_desktop_window_tree_host_win.h', + 'atom/browser/ui/win/message_handler_delegate.cc', + 'atom/browser/ui/win/message_handler_delegate.h', 'atom/browser/ui/win/notify_icon_host.cc', 'atom/browser/ui/win/notify_icon_host.h', 'atom/browser/ui/win/notify_icon.cc', 'atom/browser/ui/win/notify_icon.h', + 'atom/browser/ui/win/taskbar_host.cc', + 'atom/browser/ui/win/taskbar_host.h', 'atom/browser/ui/x/window_state_watcher.cc', 'atom/browser/ui/x/window_state_watcher.h', 'atom/browser/ui/x/x_window_utils.cc', @@ -230,6 +240,10 @@ 'atom/common/api/atom_api_v8_util.cc', 'atom/common/api/atom_bindings.cc', 'atom/common/api/atom_bindings.h', + 'atom/common/api/event_emitter_caller.cc', + 'atom/common/api/event_emitter_caller.h', + 'atom/common/api/locker.cc', + 'atom/common/api/locker.h', 'atom/common/api/object_life_monitor.cc', 'atom/common/api/object_life_monitor.h', 'atom/common/asar/archive.cc', @@ -258,14 +272,13 @@ 'atom/common/crash_reporter/win/crash_service_main.h', 'atom/common/draggable_region.cc', 'atom/common/draggable_region.h', - 'atom/common/event_emitter_caller.cc', - 'atom/common/event_emitter_caller.h', 'atom/common/google_api_key.h', 'atom/common/id_weak_map.cc', 'atom/common/id_weak_map.h', 'atom/common/linux/application_info.cc', 'atom/common/native_mate_converters/accelerator_converter.cc', 'atom/common/native_mate_converters/accelerator_converter.h', + 'atom/common/native_mate_converters/callback.h', 'atom/common/native_mate_converters/file_path_converter.h', 'atom/common/native_mate_converters/gfx_converter.cc', 'atom/common/native_mate_converters/gfx_converter.h', @@ -362,6 +375,8 @@ '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/chrome_utility_messages.h', + 'chromium_src/chrome/common/pref_names.cc', + 'chromium_src/chrome/common/pref_names.h', 'chromium_src/chrome/common/print_messages.cc', 'chromium_src/chrome/common/print_messages.h', 'chromium_src/chrome/common/tts_messages.h', diff --git a/script/lib/config.py b/script/lib/config.py index 82640153a17..1298651b7ed 100644 --- a/script/lib/config.py +++ b/script/lib/config.py @@ -7,8 +7,8 @@ import sys BASE_URL = os.getenv('LIBCHROMIUMCONTENT_MIRROR') or \ - 'http://gh-contractor-zcbenz.s3.amazonaws.com/libchromiumcontent' -LIBCHROMIUMCONTENT_COMMIT = '8c7f5b9adb9372130a9295b7e0fb19355f613cf9' + 'http://github-janky-artifacts.s3.amazonaws.com/libchromiumcontent' +LIBCHROMIUMCONTENT_COMMIT = 'dd51a41b42246b0b5159bfad5e327c8cf10bc585' PLATFORM = { 'cygwin': 'win32', diff --git a/script/upload-checksums.py b/script/upload-checksums.py index c7142512636..776a5debdb6 100755 --- a/script/upload-checksums.py +++ b/script/upload-checksums.py @@ -40,6 +40,7 @@ def get_files_list(version): return [ 'node-{0}.tar.gz'.format(version), 'iojs-{0}.tar.gz'.format(version), + 'iojs-{0}-headers.tar.gz'.format(version), 'node.lib', 'x64/node.lib', 'win-x86/iojs.lib', diff --git a/script/upload-node-headers.py b/script/upload-node-headers.py index 38cb2f7e16f..230b08ed5ac 100755 --- a/script/upload-node-headers.py +++ b/script/upload-node-headers.py @@ -40,11 +40,15 @@ def main(): args = parse_args() node_headers_dir = os.path.join(DIST_DIR, 'node-{0}'.format(args.version)) iojs_headers_dir = os.path.join(DIST_DIR, 'iojs-{0}'.format(args.version)) + iojs2_headers_dir = os.path.join(DIST_DIR, + 'iojs-{0}-headers'.format(args.version)) copy_headers(node_headers_dir) create_header_tarball(node_headers_dir) copy_headers(iojs_headers_dir) create_header_tarball(iojs_headers_dir) + copy_headers(iojs2_headers_dir) + create_header_tarball(iojs2_headers_dir) # Upload node's headers to S3. bucket, access_key, secret_key = s3_config() diff --git a/script/upload.py b/script/upload.py index 9757fe60635..6fc421e6b7a 100755 --- a/script/upload.py +++ b/script/upload.py @@ -90,10 +90,6 @@ def main(): upload_atom_shell(github, release, os.path.join(DIST_DIR, MKSNAPSHOT_NAME)) if PLATFORM == 'win32' and not tag_exists: - # Upload PDBs to Windows symbol server. - execute([sys.executable, - os.path.join(SOURCE_ROOT, 'script', 'upload-windows-pdb.py')]) - # Upload node headers. execute([sys.executable, os.path.join(SOURCE_ROOT, 'script', 'upload-node-headers.py'), diff --git a/spec/api-browser-window-spec.coffee b/spec/api-browser-window-spec.coffee index ce1282439a2..fa7a8ae33a9 100644 --- a/spec/api-browser-window-spec.coffee +++ b/spec/api-browser-window-spec.coffee @@ -4,7 +4,6 @@ path = require 'path' remote = require 'remote' http = require 'http' url = require 'url' -auth = require 'basic-auth' BrowserWindow = remote.require 'browser-window' @@ -117,10 +116,27 @@ describe 'browser-window module', -> assert.equal after[0], size[0] assert.equal after[1], size[1] + it 'works for framless window', -> + w.destroy() + w = new BrowserWindow(show: false, frame: false, width: 400, height: 400) + size = [400, 400] + w.setContentSize size[0], size[1] + after = w.getContentSize() + assert.equal after[0], size[0] + assert.equal after[1], size[1] + describe 'BrowserWindow.fromId(id)', -> it 'returns the window with id', -> assert.equal w.id, BrowserWindow.fromId(w.id).id + describe 'BrowserWindow.setResizable(resizable)', -> + it 'does not change window size for frameless window', -> + w.destroy() + w = new BrowserWindow(show: true, frame: false) + s = w.getSize() + w.setResizable not w.isResizable() + assert.deepEqual s, w.getSize() + describe '"use-content-size" option', -> it 'make window created with content size when used', -> w.destroy() @@ -134,6 +150,16 @@ describe 'browser-window module', -> assert.equal size[0], 400 assert.equal size[1], 400 + it 'works for framless window', -> + w.destroy() + w = new BrowserWindow(show: false, frame: false, width: 400, height: 400, 'use-content-size': true) + contentSize = w.getContentSize() + assert.equal contentSize[0], 400 + assert.equal contentSize[1], 400 + size = w.getSize() + assert.equal size[0], 400 + assert.equal size[1], 400 + describe '"enable-larger-than-screen" option', -> return if process.platform is 'linux' @@ -239,48 +265,3 @@ describe 'browser-window module', -> assert.equal url, 'https://www.github.com/' done() w.loadUrl "file://#{fixtures}/pages/will-navigate.html" - - describe 'dom-ready event', -> - return if isCI and process.platform is 'darwin' - it 'emits when document is loaded', (done) -> - ipc = remote.require 'ipc' - server = http.createServer (req, res) -> - action = url.parse(req.url, true).pathname - if action == '/logo.png' - img = fs.readFileSync(path.join(fixtures, 'assets', 'logo.png')) - res.writeHead(200, {'Content-Type': 'image/png'}) - setTimeout -> - res.end(img, 'binary') - , 2000 - server.close() - server.listen 62542, '127.0.0.1' - ipc.on 'dom-ready', (e, state) -> - ipc.removeAllListeners 'dom-ready' - assert.equal state, 'interactive' - done() - w.webContents.on 'did-finish-load', -> - w.close() - w.loadUrl "file://#{fixtures}/pages/f.html" - - describe 'basic auth', -> - it 'should authenticate with correct credentials', (done) -> - ipc = remote.require 'ipc' - server = http.createServer (req, res) -> - action = url.parse(req.url, true).pathname - if action == '/' - credentials = auth(req) - if credentials.name == 'test' and credentials.pass == 'test' - res.end('Authenticated') - server.close() - else if action == '/jquery.js' - js = fs.readFileSync(path.join(__dirname, 'static', 'jquery-2.0.3.min.js')) - res.writeHead(200, {'Content-Type': 'text/javascript'}) - res.end(js, 'utf-8') - server.listen 62342, '127.0.0.1' - ipc.on 'console-message', (e, message) -> - ipc.removeAllListeners 'console-message' - assert.equal message, 'Authenticated' - done() - w.webContents.on 'did-finish-load', -> - w.close() - w.loadUrl "file://#{fixtures}/pages/basic-auth.html" diff --git a/spec/api-ipc-spec.coffee b/spec/api-ipc-spec.coffee index d7f77b14ce2..7c6148b559b 100644 --- a/spec/api-ipc-spec.coffee +++ b/spec/api-ipc-spec.coffee @@ -52,6 +52,14 @@ describe 'ipc module', -> print_name = remote.require path.join(fixtures, 'module', 'print_name.js') assert.equal print_name.print(buf), 'Buffer' + describe 'remote promise', -> + it 'can be used as promise in each side', (done) -> + promise = remote.require path.join(fixtures, 'module', 'promise.js') + promise.twicePromise(Promise.resolve(1234)) + .then (value) => + assert.equal value, 2468 + done() + describe 'ipc.sender.send', -> it 'should work when sending an object containing id property', (done) -> obj = id: 1, name: 'ly' diff --git a/spec/api-protocol-spec.coffee b/spec/api-protocol-spec.coffee index b65002a6318..4f2fb79da37 100644 --- a/spec/api-protocol-spec.coffee +++ b/spec/api-protocol-spec.coffee @@ -81,6 +81,7 @@ describe 'protocol module', -> it 'returns RequestHttpJob should send respone', (done) -> server = http.createServer (req, res) -> + assert.notEqual req.headers.accept, '' res.writeHead(200, {'Content-Type': 'text/plain'}) res.end('hello') server.close() diff --git a/spec/api-session-spec.coffee b/spec/api-session-spec.coffee index bd7c9bde592..34a08ee50f0 100644 --- a/spec/api-session-spec.coffee +++ b/spec/api-session-spec.coffee @@ -20,8 +20,7 @@ describe 'session module', -> res.end('finished') server.close() - port = remote.process.port - server.listen port, '127.0.0.1', -> + server.listen 0, '127.0.0.1', -> {port} = server.address() w.loadUrl "#{url}:#{port}" w.webContents.on 'did-finish-load', -> diff --git a/spec/asar-spec.coffee b/spec/asar-spec.coffee index 977676a1936..a49417258fe 100644 --- a/spec/asar-spec.coffee +++ b/spec/asar-spec.coffee @@ -85,6 +85,11 @@ describe 'asar package', -> done() describe 'fs.lstatSync', -> + it 'handles path with trailing slash correctly', -> + p = path.join fixtures, 'asar', 'a.asar', 'link2', 'link2', 'file1' + fs.lstatSync p + fs.lstatSync p + '/' + it 'returns information of root', -> p = path.join fixtures, 'asar', 'a.asar' stats = fs.lstatSync p @@ -136,6 +141,10 @@ describe 'asar package', -> assert.throws throws, /ENOENT/ describe 'fs.lstat', -> + it 'handles path with trailing slash correctly', (done) -> + p = path.join fixtures, 'asar', 'a.asar', 'link2', 'link2', 'file1' + fs.lstat p + '/', done + it 'returns information of root', (done) -> p = path.join fixtures, 'asar', 'a.asar' stats = fs.lstat p, (err, stats) -> diff --git a/spec/chromium-spec.coffee b/spec/chromium-spec.coffee index 8ca7e9800fe..704e5bbcac7 100644 --- a/spec/chromium-spec.coffee +++ b/spec/chromium-spec.coffee @@ -3,6 +3,7 @@ http = require 'http' https = require 'https' path = require 'path' ws = require 'ws' +remote = require 'remote' describe 'chromium feature', -> fixtures = path.resolve __dirname, 'fixtures' @@ -36,12 +37,35 @@ describe 'chromium feature', -> describe 'window.open', -> it 'returns a BrowserWindowProxy object', -> b = window.open 'about:blank', 'test', 'show=no' + assert.equal b.closed, false assert.equal b.constructor.name, 'BrowserWindowProxy' b.close() + describe 'window.opener', -> + ipc = remote.require 'ipc' + url = "file://#{fixtures}/pages/window-opener.html" + w = null + + afterEach -> + w?.destroy() + ipc.removeAllListeners 'opener' + + it 'is null for main window', (done) -> + ipc.on 'opener', (event, opener) -> + done(if opener is null then undefined else opener) + BrowserWindow = remote.require 'browser-window' + w = new BrowserWindow(show: false) + w.loadUrl url + + it 'is not null for window opened by window.open', (done) -> + b = window.open url, 'test2', 'show=no' + ipc.on 'opener', (event, opener) -> + b.close() + done(if opener isnt null then undefined else opener) + describe 'creating a Uint8Array under browser side', -> it 'does not crash', -> - RUint8Array = require('remote').getGlobal 'Uint8Array' + RUint8Array = remote.getGlobal 'Uint8Array' new RUint8Array describe 'webgl', -> @@ -111,3 +135,32 @@ describe 'chromium feature', -> else done('user agent is empty') websocket = new WebSocket("ws://127.0.0.1:#{port}") + + describe 'Promise', -> + it 'resolves correctly in Node.js calls', (done) -> + document.registerElement('x-element', { + prototype: Object.create(HTMLElement.prototype, { + createdCallback: { value: -> } + }) + }) + + setImmediate -> + called = false + Promise.resolve().then -> + done(if called then undefined else new Error('wrong sequnce')) + document.createElement 'x-element' + called = true + + it 'resolves correctly in Electron calls', (done) -> + document.registerElement('y-element', { + prototype: Object.create(HTMLElement.prototype, { + createdCallback: { value: -> } + }) + }) + + remote.getGlobal('setImmediate') -> + called = false + Promise.resolve().then -> + done(if called then undefined else new Error('wrong sequnce')) + document.createElement 'y-element' + called = true diff --git a/spec/fixtures/module/promise.js b/spec/fixtures/module/promise.js new file mode 100644 index 00000000000..2e52ed37440 --- /dev/null +++ b/spec/fixtures/module/promise.js @@ -0,0 +1,5 @@ +exports.twicePromise = function (promise) { + return promise.then(function (value) { + return value * 2; + }); +} diff --git a/spec/fixtures/pages/basic-auth.html b/spec/fixtures/pages/basic-auth.html index 81d5bd209ce..aa95546a9c1 100644 --- a/spec/fixtures/pages/basic-auth.html +++ b/spec/fixtures/pages/basic-auth.html @@ -1,16 +1,21 @@ - + diff --git a/spec/fixtures/pages/dom-ready.html b/spec/fixtures/pages/dom-ready.html new file mode 100644 index 00000000000..541852f9ab0 --- /dev/null +++ b/spec/fixtures/pages/dom-ready.html @@ -0,0 +1,9 @@ + + + + + + diff --git a/spec/fixtures/pages/f.html b/spec/fixtures/pages/f.html deleted file mode 100644 index e2003a3ab0d..00000000000 --- a/spec/fixtures/pages/f.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - diff --git a/spec/fixtures/pages/fullscreen.html b/spec/fixtures/pages/fullscreen.html new file mode 100644 index 00000000000..4e5648e0194 --- /dev/null +++ b/spec/fixtures/pages/fullscreen.html @@ -0,0 +1 @@ + diff --git a/spec/fixtures/pages/window-opener.html b/spec/fixtures/pages/window-opener.html new file mode 100644 index 00000000000..0b5ecd556c9 --- /dev/null +++ b/spec/fixtures/pages/window-opener.html @@ -0,0 +1,8 @@ + + + + + + diff --git a/spec/webview-spec.coffee b/spec/webview-spec.coffee index b47c72ac0fb..ba3478fca55 100644 --- a/spec/webview-spec.coffee +++ b/spec/webview-spec.coffee @@ -1,5 +1,6 @@ assert = require 'assert' path = require 'path' +http = require 'http' describe ' tag', -> fixtures = path.join __dirname, 'fixtures' @@ -228,3 +229,51 @@ describe ' tag', -> webview.setAttribute 'nodeintegration', 'on' webview.src = "file://#{fixtures}/pages/history.html" document.body.appendChild webview + + describe 'basic auth', -> + auth = require 'basic-auth' + + it 'should authenticate with correct credentials', (done) -> + message = 'Authenticated' + server = http.createServer (req, res) -> + credentials = auth(req) + if credentials.name == 'test' and credentials.pass == 'test' + res.end(message) + else + res.end('failed') + server.close() + server.listen 0, '127.0.0.1', -> + {port} = server.address() + webview.addEventListener 'ipc-message', (e) -> + assert.equal e.channel, message + done() + webview.src = "file://#{fixtures}/pages/basic-auth.html?port=#{port}" + webview.setAttribute 'nodeintegration', 'on' + document.body.appendChild webview + + describe 'dom-ready event', -> + it 'emits when document is loaded', (done) -> + server = http.createServer (req) -> + # Never respond, so the page never finished loading. + server.listen 0, '127.0.0.1', -> + {port} = server.address() + webview.addEventListener 'dom-ready', -> + done() + webview.src = "file://#{fixtures}/pages/dom-ready.html?port=#{port}" + document.body.appendChild webview + + describe 'executeJavaScript', -> + return unless process.env.TRAVIS is 'true' + + it 'should support user gesture', (done) -> + listener = (e) -> + webview.removeEventListener 'enter-html-full-screen', listener + done() + listener2 = (e) -> + jsScript = 'document.getElementsByTagName("video")[0].webkitRequestFullScreen()' + webview.executeJavaScript jsScript, true + webview.removeEventListener 'did-finish-load', listener2 + webview.addEventListener 'enter-html-full-screen', listener + webview.addEventListener 'did-finish-load', listener2 + webview.src = "file://#{fixtures}/pages/fullscreen.html" + document.body.appendChild webview diff --git a/vendor/brightray b/vendor/brightray index 6328c610413..f4470ee48a7 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit 6328c6104131e623da87f479ea305b83169099b8 +Subproject commit f4470ee48a748888bccba21845bfd65caaa1a6ce diff --git a/vendor/native_mate b/vendor/native_mate index 41cd6d13c9c..67d9eaa215e 160000 --- a/vendor/native_mate +++ b/vendor/native_mate @@ -1 +1 @@ -Subproject commit 41cd6d13c9c9be164f427864277f3cc36b69eb39 +Subproject commit 67d9eaa215e8727d86dc7b1f7a10be8699848f1f