diff --git a/atom.gyp b/atom.gyp index c9d5f924789f..42069ea84621 100644 --- a/atom.gyp +++ b/atom.gyp @@ -4,7 +4,7 @@ 'product_name%': 'Electron', 'company_name%': 'GitHub, Inc', 'company_abbr%': 'github', - 'version%': '0.33.0', + 'version%': '0.33.3', }, 'includes': [ 'filenames.gypi', diff --git a/atom/browser/api/atom_api_download_item.cc b/atom/browser/api/atom_api_download_item.cc new file mode 100644 index 000000000000..ec4dcd84b285 --- /dev/null +++ b/atom/browser/api/atom_api_download_item.cc @@ -0,0 +1,201 @@ +// 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/api/atom_api_download_item.h" + +#include + +#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 "atom/common/node_includes.h" +#include "base/memory/linked_ptr.h" +#include "base/strings/utf_string_conversions.h" +#include "native_mate/dictionary.h" +#include "net/base/filename_util.h" + +namespace mate { + +template<> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + content::DownloadItem::DownloadState state) { + std::string download_state; + switch (state) { + case content::DownloadItem::COMPLETE: + download_state = "completed"; + break; + case content::DownloadItem::CANCELLED: + download_state = "cancelled"; + break; + case content::DownloadItem::INTERRUPTED: + download_state = "interrupted"; + break; + default: + break; + } + return ConvertToV8(isolate, download_state); + } +}; + +} // namespace mate + +namespace atom { + +namespace api { + +namespace { +// The wrapDownloadItem funtion which is implemented in JavaScript +using WrapDownloadItemCallback = base::Callback)>; +WrapDownloadItemCallback g_wrap_download_item; + +char kDownloadItemSavePathKey[] = "DownloadItemSavePathKey"; + +std::map>> g_download_item_objects; +} // namespace + +DownloadItem::SavePathData::SavePathData(const base::FilePath& path) : + path_(path) { +} + +const base::FilePath& DownloadItem::SavePathData::path() { + return path_; +} + +DownloadItem::DownloadItem(content::DownloadItem* download_item) : + download_item_(download_item) { + download_item_->AddObserver(this); +} + +DownloadItem::~DownloadItem() { + Destroy(); +} + +void DownloadItem::Destroy() { + if (download_item_) { + download_item_->RemoveObserver(this); + auto iter = g_download_item_objects.find(download_item_->GetId()); + if (iter != g_download_item_objects.end()) + g_download_item_objects.erase(iter); + download_item_ = nullptr; + } +} + +bool DownloadItem::IsDestroyed() const { + return download_item_ == nullptr; +} + +void DownloadItem::OnDownloadUpdated(content::DownloadItem* item) { + download_item_->IsDone() ? Emit("done", item->GetState()) : Emit("updated"); +} + +void DownloadItem::OnDownloadDestroyed(content::DownloadItem* download) { + Destroy(); +} + +int64 DownloadItem::GetReceivedBytes() { + return download_item_->GetReceivedBytes(); +} + +int64 DownloadItem::GetTotalBytes() { + return download_item_->GetTotalBytes(); +} + +const GURL& DownloadItem::GetUrl() { + return download_item_->GetURL(); +} + +std::string DownloadItem::GetMimeType() { + return download_item_->GetMimeType(); +} + +bool DownloadItem::HasUserGesture() { + return download_item_->HasUserGesture(); +} + +std::string DownloadItem::GetFilename() { + return base::UTF16ToUTF8(net::GenerateFileName(GetUrl(), + GetContentDisposition(), + std::string(), + download_item_->GetSuggestedFilename(), + GetMimeType(), + std::string()).LossyDisplayName()); +} + +std::string DownloadItem::GetContentDisposition() { + return download_item_->GetContentDisposition(); +} + +void DownloadItem::SetSavePath(const base::FilePath& path) { + download_item_->SetUserData(UserDataKey(), new SavePathData(path)); +} + +void DownloadItem::Pause() { + download_item_->Pause(); +} + +void DownloadItem::Resume() { + download_item_->Resume(); +} + +void DownloadItem::Cancel() { + download_item_->Cancel(true); +} + +mate::ObjectTemplateBuilder DownloadItem::GetObjectTemplateBuilder( + v8::Isolate* isolate) { + return mate::ObjectTemplateBuilder(isolate) + .SetMethod("pause", &DownloadItem::Pause) + .SetMethod("resume", &DownloadItem::Resume) + .SetMethod("cancel", &DownloadItem::Cancel) + .SetMethod("getReceivedBytes", &DownloadItem::GetReceivedBytes) + .SetMethod("getTotalBytes", &DownloadItem::GetTotalBytes) + .SetMethod("getUrl", &DownloadItem::GetUrl) + .SetMethod("getMimeType", &DownloadItem::GetMimeType) + .SetMethod("hasUserGesture", &DownloadItem::HasUserGesture) + .SetMethod("getFilename", &DownloadItem::GetFilename) + .SetMethod("getContentDisposition", &DownloadItem::GetContentDisposition) + .SetMethod("setSavePath", &DownloadItem::SetSavePath); +} + +void SetWrapDownloadItem(const WrapDownloadItemCallback& callback) { + g_wrap_download_item = callback; +} + +void ClearWrapDownloadItem() { + g_wrap_download_item.Reset(); +} + +// static +mate::Handle DownloadItem::Create( + v8::Isolate* isolate, content::DownloadItem* item) { + auto handle = mate::CreateHandle(isolate, new DownloadItem(item)); + g_wrap_download_item.Run(handle.ToV8()); + g_download_item_objects[item->GetId()] = make_linked_ptr( + new v8::Global(isolate, handle.ToV8())); + return handle; +} + +// static +void* DownloadItem::UserDataKey() { + return &kDownloadItemSavePathKey; +} + +} // namespace api + +} // namespace atom + +namespace { + +void Initialize(v8::Local exports, v8::Local unused, + v8::Local context, void* priv) { + v8::Isolate* isolate = context->GetIsolate(); + mate::Dictionary dict(isolate, exports); + dict.SetMethod("_setWrapDownloadItem", &atom::api::SetWrapDownloadItem); + dict.SetMethod("_clearWrapDownloadItem", &atom::api::ClearWrapDownloadItem); +} + +} // namespace + +NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_browser_download_item, Initialize); diff --git a/atom/browser/api/atom_api_download_item.h b/atom/browser/api/atom_api_download_item.h new file mode 100644 index 000000000000..14074a4bed0d --- /dev/null +++ b/atom/browser/api/atom_api_download_item.h @@ -0,0 +1,72 @@ +// 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_API_ATOM_API_DOWNLOAD_ITEM_H_ +#define ATOM_BROWSER_API_ATOM_API_DOWNLOAD_ITEM_H_ + +#include + +#include "atom/browser/api/trackable_object.h" +#include "base/files/file_path.h" +#include "content/public/browser/download_item.h" +#include "native_mate/handle.h" +#include "url/gurl.h" + +namespace atom { + +namespace api { + +class DownloadItem : public mate::EventEmitter, + public content::DownloadItem::Observer { + public: + class SavePathData : public base::SupportsUserData::Data { + public: + explicit SavePathData(const base::FilePath& path); + const base::FilePath& path(); + private: + base::FilePath path_; + }; + + static mate::Handle Create(v8::Isolate* isolate, + content::DownloadItem* item); + static void* UserDataKey(); + + protected: + explicit DownloadItem(content::DownloadItem* download_item); + ~DownloadItem(); + + // Override content::DownloadItem::Observer methods + void OnDownloadUpdated(content::DownloadItem* download) override; + void OnDownloadDestroyed(content::DownloadItem* download) override; + + void Pause(); + void Resume(); + void Cancel(); + int64 GetReceivedBytes(); + int64 GetTotalBytes(); + std::string GetMimeType(); + bool HasUserGesture(); + std::string GetFilename(); + std::string GetContentDisposition(); + const GURL& GetUrl(); + void SetSavePath(const base::FilePath& path); + + private: + // mate::Wrappable: + mate::ObjectTemplateBuilder GetObjectTemplateBuilder( + v8::Isolate* isolate) override; + bool IsDestroyed() const override; + + void Destroy(); + + content::DownloadItem* download_item_; + + DISALLOW_COPY_AND_ASSIGN(DownloadItem); +}; + +} // namespace api + +} // namespace atom + +#endif // ATOM_BROWSER_API_ATOM_API_DOWNLOAD_ITEM_H_ diff --git a/atom/browser/api/atom_api_session.cc b/atom/browser/api/atom_api_session.cc index f07ab8b78045..7d5f75cac09c 100644 --- a/atom/browser/api/atom_api_session.cc +++ b/atom/browser/api/atom_api_session.cc @@ -8,6 +8,7 @@ #include #include "atom/browser/api/atom_api_cookies.h" +#include "atom/browser/api/atom_api_download_item.h" #include "atom/browser/atom_browser_context.h" #include "atom/browser/api/atom_api_web_contents.h" #include "atom/common/native_mate_converters/callback.h" @@ -101,19 +102,6 @@ struct Converter { } }; -template<> -struct Converter { - static v8::Local ToV8(v8::Isolate* isolate, - content::DownloadItem* val) { - mate::Dictionary dict(isolate, v8::Object::New(isolate)); - dict.Set("url", val->GetURL()); - dict.Set("filename", val->GetSuggestedFilename()); - dict.Set("mimeType", val->GetMimeType()); - dict.Set("hasUserGesture", val->HasUserGesture()); - return dict.GetHandle(); - } -}; - } // namespace mate namespace atom { @@ -245,11 +233,12 @@ Session::~Session() { } void Session::OnDownloadCreated(content::DownloadManager* manager, - content::DownloadItem* item) { + content::DownloadItem* item) { auto web_contents = item->GetWebContents(); - bool prevent_default = Emit("will-download", item, - api::WebContents::CreateFrom(isolate(), - web_contents)); + bool prevent_default = Emit( + "will-download", + DownloadItem::Create(isolate(), item), + api::WebContents::CreateFrom(isolate(), web_contents)); if (prevent_default) { item->Cancel(true); item->Remove(); diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 9791a94bb77b..bdeb4ed4f873 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -120,6 +120,33 @@ struct Converter { } }; +template<> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + net::HttpResponseHeaders* headers) { + base::DictionaryValue response_headers; + if (headers) { + void* iter = nullptr; + std::string key; + std::string value; + while (headers->EnumerateHeaderLines(&iter, &key, &value)) { + key = base::StringToLowerASCII(key); + value = base::StringToLowerASCII(value); + if (response_headers.HasKey(key)) { + base::ListValue* values = nullptr; + if (response_headers.GetList(key, &values)) + values->AppendString(value); + } else { + scoped_ptr values(new base::ListValue()); + values->AppendString(value); + response_headers.Set(key, values.Pass()); + } + } + } + return ConvertToV8(isolate, response_headers); + } +}; + } // namespace mate @@ -131,7 +158,7 @@ namespace { v8::Persistent template_; -// The wrapWebContents funtion which is implemented in JavaScript +// The wrapWebContents function which is implemented in JavaScript using WrapWebContentsCallback = base::Callback)>; WrapWebContentsCallback g_wrap_web_contents; @@ -201,7 +228,7 @@ WebContents::WebContents(v8::Isolate* isolate, AttachAsUserData(web_contents); InitWithWebContents(web_contents); - // Save the preferences. + // Save the preferences in C++. base::DictionaryValue web_preferences; mate::ConvertFromV8(isolate, options.GetHandle(), &web_preferences); new WebContentsPreferences(web_contents, &web_preferences); @@ -414,30 +441,6 @@ void WebContents::DidStopLoading() { void WebContents::DidGetResourceResponseStart( const content::ResourceRequestDetails& details) { - v8::Locker locker(isolate()); - v8::HandleScope handle_scope(isolate()); - base::DictionaryValue response_headers; - - net::HttpResponseHeaders* headers = details.headers.get(); - if (!headers) - return; - void* iter = nullptr; - std::string key; - std::string value; - while (headers->EnumerateHeaderLines(&iter, &key, &value)) { - key = base::StringToLowerASCII(key); - value = base::StringToLowerASCII(value); - if (response_headers.HasKey(key)) { - base::ListValue* values = nullptr; - if (response_headers.GetList(key, &values)) - values->AppendString(value); - } else { - scoped_ptr values(new base::ListValue()); - values->AppendString(value); - response_headers.Set(key, values.Pass()); - } - } - Emit("did-get-response-details", details.socket_address.IsEmpty(), details.url, @@ -445,7 +448,7 @@ void WebContents::DidGetResourceResponseStart( details.http_response_code, details.method, details.referrer, - response_headers); + details.headers.get()); } void WebContents::DidGetRedirectForResourceRequest( @@ -454,7 +457,11 @@ void WebContents::DidGetRedirectForResourceRequest( Emit("did-get-redirect-request", details.url, details.new_url, - (details.resource_type == content::RESOURCE_TYPE_MAIN_FRAME)); + (details.resource_type == content::RESOURCE_TYPE_MAIN_FRAME), + details.http_response_code, + details.method, + details.referrer, + details.headers.get()); } void WebContents::DidNavigateMainFrame( @@ -880,6 +887,12 @@ bool WebContents::IsGuest() const { return type_ == WEB_VIEW; } +v8::Local WebContents::GetWebPreferences(v8::Isolate* isolate) { + WebContentsPreferences* web_preferences = + WebContentsPreferences::FromWebContents(web_contents()); + return mate::ConvertToV8(isolate, *web_preferences->web_preferences()); +} + mate::ObjectTemplateBuilder WebContents::GetObjectTemplateBuilder( v8::Isolate* isolate) { if (template_.IsEmpty()) @@ -935,6 +948,7 @@ mate::ObjectTemplateBuilder WebContents::GetObjectTemplateBuilder( .SetMethod("setSize", &WebContents::SetSize) .SetMethod("setAllowTransparency", &WebContents::SetAllowTransparency) .SetMethod("isGuest", &WebContents::IsGuest) + .SetMethod("getWebPreferences", &WebContents::GetWebPreferences) .SetMethod("hasServiceWorker", &WebContents::HasServiceWorker) .SetMethod("unregisterServiceWorker", &WebContents::UnregisterServiceWorker) @@ -988,7 +1002,7 @@ mate::Handle WebContents::CreateFrom( // static mate::Handle WebContents::Create( v8::Isolate* isolate, const mate::Dictionary& options) { - auto handle = mate::CreateHandle(isolate, new WebContents(isolate, options)); + auto handle = mate::CreateHandle(isolate, new WebContents(isolate, options)); g_wrap_web_contents.Run(handle.ToV8()); return handle; } diff --git a/atom/browser/api/atom_api_web_contents.h b/atom/browser/api/atom_api_web_contents.h index c8ea6908bc5b..91750ac6136c 100644 --- a/atom/browser/api/atom_api_web_contents.h +++ b/atom/browser/api/atom_api_web_contents.h @@ -132,6 +132,9 @@ class WebContents : public mate::TrackableObject, void SetAllowTransparency(bool allow); bool IsGuest() const; + // Returns the web preferences of current WebContents. + v8::Local GetWebPreferences(v8::Isolate* isolate); + protected: explicit WebContents(content::WebContents* web_contents); WebContents(v8::Isolate* isolate, const mate::Dictionary& options); diff --git a/atom/browser/api/atom_api_window.cc b/atom/browser/api/atom_api_window.cc index 3a44115da264..4d866d18503c 100644 --- a/atom/browser/api/atom_api_window.cc +++ b/atom/browser/api/atom_api_window.cc @@ -82,6 +82,10 @@ Window::Window(v8::Isolate* isolate, const mate::Dictionary& options) { web_contents_.Reset(isolate, web_contents.ToV8()); api_web_contents_ = web_contents.get(); + // Keep a copy of the options for later use. + mate::Dictionary(isolate, web_contents->GetWrapper(isolate)).Set( + "browserWindowOptions", options); + // Creates BrowserWindow. window_.reset(NativeWindow::Create(web_contents->managed_web_contents(), options)); diff --git a/atom/browser/api/lib/app.coffee b/atom/browser/api/lib/app.coffee index f5b137eb0bb2..18c80dc2b1f4 100644 --- a/atom/browser/api/lib/app.coffee +++ b/atom/browser/api/lib/app.coffee @@ -2,6 +2,7 @@ EventEmitter = require('events').EventEmitter bindings = process.atomBinding 'app' sessionBindings = process.atomBinding 'session' +downloadItemBindings = process.atomBinding 'download_item' app = bindings.app app.__proto__ = EventEmitter.prototype @@ -10,6 +11,15 @@ wrapSession = (session) -> # session is an Event Emitter. session.__proto__ = EventEmitter.prototype +wrapDownloadItem = (download_item) -> + # download_item is an Event Emitter. + download_item.__proto__ = EventEmitter.prototype + # Be compatible with old APIs. + download_item.url = download_item.getUrl() + download_item.filename = download_item.getFilename() + download_item.mimeType = download_item.getMimeType() + download_item.hasUserGesture = download_item.hasUserGesture() + app.setApplicationMenu = (menu) -> require('menu').setApplicationMenu menu @@ -51,5 +61,8 @@ app.on 'activate', (event, hasVisibleWindows) -> @emit 'activate-with-no-open-wi sessionBindings._setWrapSession wrapSession process.once 'exit', sessionBindings._clearWrapSession +downloadItemBindings._setWrapDownloadItem wrapDownloadItem +process.once 'exit', downloadItemBindings._clearWrapDownloadItem + # Only one App object pemitted. module.exports = app diff --git a/atom/browser/atom_browser_context.cc b/atom/browser/atom_browser_context.cc index d1ef09e70ff6..6823fbaee90c 100644 --- a/atom/browser/atom_browser_context.cc +++ b/atom/browser/atom_browser_context.cc @@ -6,6 +6,7 @@ #include "atom/browser/atom_browser_main_parts.h" #include "atom/browser/atom_download_manager_delegate.h" +#include "atom/browser/atom_ssl_config_service.h" #include "atom/browser/browser.h" #include "atom/browser/net/atom_url_request_job_factory.h" #include "atom/browser/net/asar/asar_protocol_handler.h" @@ -156,6 +157,10 @@ content::BrowserPluginGuestManager* AtomBrowserContext::GetGuestManager() { return guest_manager_.get(); } +net::SSLConfigService* AtomBrowserContext::CreateSSLConfigService() { + return new AtomSSLConfigService; +} + void AtomBrowserContext::RegisterPrefs(PrefRegistrySimple* pref_registry) { pref_registry->RegisterFilePathPref(prefs::kSelectFileLastDirectory, base::FilePath()); diff --git a/atom/browser/atom_browser_context.h b/atom/browser/atom_browser_context.h index c99461ad9a86..839359c1ef50 100644 --- a/atom/browser/atom_browser_context.h +++ b/atom/browser/atom_browser_context.h @@ -27,6 +27,7 @@ class AtomBrowserContext : public brightray::BrowserContext { content::URLRequestInterceptorScopedVector* interceptors) override; net::HttpCache::BackendFactory* CreateHttpCacheBackendFactory( const base::FilePath& base_path) override; + net::SSLConfigService* CreateSSLConfigService() override; // content::BrowserContext: content::DownloadManagerDelegate* GetDownloadManagerDelegate() override; diff --git a/atom/browser/atom_download_manager_delegate.cc b/atom/browser/atom_download_manager_delegate.cc index b573a396332f..b6b656682554 100644 --- a/atom/browser/atom_download_manager_delegate.cc +++ b/atom/browser/atom_download_manager_delegate.cc @@ -6,6 +6,7 @@ #include +#include "atom/browser/api/atom_api_download_item.h" #include "atom/browser/atom_browser_context.h" #include "atom/browser/native_window.h" #include "atom/browser/ui/file_dialog.h" @@ -73,18 +74,19 @@ void AtomDownloadManagerDelegate::OnDownloadPathGenerated( if (relay) window = relay->window.get(); - file_dialog::Filters filters; base::FilePath path; - if (!file_dialog::ShowSaveDialog(window, item->GetURL().spec(), default_path, - filters, &path)) { - return; + if (file_dialog::ShowSaveDialog(window, item->GetURL().spec(), default_path, + file_dialog::Filters(), &path)) { + // Remember the last selected download directory. + AtomBrowserContext* browser_context = static_cast( + download_manager_->GetBrowserContext()); + browser_context->prefs()->SetFilePath(prefs::kDownloadDefaultDirectory, + path.DirName()); } - // Remeber the last selected download directory. - AtomBrowserContext* browser_context = static_cast( - download_manager_->GetBrowserContext()); - browser_context->prefs()->SetFilePath(prefs::kDownloadDefaultDirectory, - path.DirName()); + // Running the DownloadTargetCallback with an empty FilePath signals that the + // download should be cancelled. + // If user cancels the file save dialog, run the callback with empty FilePath. callback.Run(path, content::DownloadItem::TARGET_DISPOSITION_PROMPT, content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, path); @@ -100,6 +102,25 @@ bool AtomDownloadManagerDelegate::DetermineDownloadTarget( const content::DownloadTargetCallback& callback) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + if (!download->GetForcedFilePath().empty()) { + callback.Run(download->GetForcedFilePath(), + content::DownloadItem::TARGET_DISPOSITION_OVERWRITE, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + download->GetForcedFilePath()); + return true; + } + base::SupportsUserData::Data* save_path = download->GetUserData( + atom::api::DownloadItem::UserDataKey()); + if (save_path) { + const base::FilePath& default_download_path = + static_cast(save_path)->path(); + callback.Run(default_download_path, + content::DownloadItem::TARGET_DISPOSITION_OVERWRITE, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + default_download_path); + return true; + } + AtomBrowserContext* browser_context = static_cast( download_manager_->GetBrowserContext()); base::FilePath default_download_path = browser_context->prefs()->GetFilePath( @@ -110,14 +131,6 @@ bool AtomDownloadManagerDelegate::DetermineDownloadTarget( default_download_path = path.Append(FILE_PATH_LITERAL("Downloads")); } - if (!download->GetForcedFilePath().empty()) { - callback.Run(download->GetForcedFilePath(), - content::DownloadItem::TARGET_DISPOSITION_OVERWRITE, - content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, - download->GetForcedFilePath()); - return true; - } - CreateDownloadPathCallback download_path_callback = base::Bind(&AtomDownloadManagerDelegate::OnDownloadPathGenerated, weak_ptr_factory_.GetWeakPtr(), diff --git a/atom/browser/atom_ssl_config_service.cc b/atom/browser/atom_ssl_config_service.cc new file mode 100644 index 000000000000..f19dbacf7dd3 --- /dev/null +++ b/atom/browser/atom_ssl_config_service.cc @@ -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. + +#include "atom/browser/atom_ssl_config_service.h" + +#include + +#include "base/command_line.h" +#include "atom/common/options_switches.h" +#include "content/public/browser/browser_thread.h" +#include "net/socket/ssl_client_socket.h" + +namespace atom { + +namespace { + +uint16 GetSSLProtocolVersion(const std::string& version_string) { + uint16 version = 0; // Invalid + if (version_string == "tls1") + version = net::SSL_PROTOCOL_VERSION_TLS1; + else if (version_string == "tls1.1") + version = net::SSL_PROTOCOL_VERSION_TLS1_1; + else if (version_string == "tls1.2") + version = net::SSL_PROTOCOL_VERSION_TLS1_2; + return version; +} + +} // namespace + +AtomSSLConfigService::AtomSSLConfigService() { + auto cmd_line = base::CommandLine::ForCurrentProcess(); + if (cmd_line->HasSwitch(switches::kSSLVersionFallbackMin)) { + auto version_string = + cmd_line->GetSwitchValueASCII(switches::kSSLVersionFallbackMin); + config_.version_fallback_min = GetSSLProtocolVersion(version_string); + } +} + +AtomSSLConfigService::~AtomSSLConfigService() { +} + +void AtomSSLConfigService::GetSSLConfig(net::SSLConfig* config) { + *config = config_; +} + +} // namespace atom diff --git a/atom/browser/atom_ssl_config_service.h b/atom/browser/atom_ssl_config_service.h new file mode 100644 index 000000000000..3fa91c62c438 --- /dev/null +++ b/atom/browser/atom_ssl_config_service.h @@ -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. + +#ifndef ATOM_BROWSER_ATOM_SSL_CONFIG_SERVICE_H_ +#define ATOM_BROWSER_ATOM_SSL_CONFIG_SERVICE_H_ + +#include "net/ssl/ssl_config_service.h" + +namespace atom { + +class AtomSSLConfigService : public net::SSLConfigService { + public: + AtomSSLConfigService(); + ~AtomSSLConfigService() override; + + // net::SSLConfigService: + void GetSSLConfig(net::SSLConfig* config) override; + + private: + net::SSLConfig config_; + + DISALLOW_COPY_AND_ASSIGN(AtomSSLConfigService); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_ATOM_SSL_CONFIG_SERVICE_H_ diff --git a/atom/browser/lib/guest-view-manager.coffee b/atom/browser/lib/guest-view-manager.coffee index 1c05b65b2dc0..455e969812f7 100644 --- a/atom/browser/lib/guest-view-manager.coffee +++ b/atom/browser/lib/guest-view-manager.coffee @@ -84,6 +84,8 @@ createGuest = (embedder, params) -> if params.allowtransparency? @setAllowTransparency params.allowtransparency + guest.allowPopups = params.allowpopups + # Dispatch events to embedder. for event in supportedWebViewEvents do (event) -> diff --git a/atom/browser/lib/guest-window-manager.coffee b/atom/browser/lib/guest-window-manager.coffee index 861b0d10aa31..9a5c0ca349ae 100644 --- a/atom/browser/lib/guest-window-manager.coffee +++ b/atom/browser/lib/guest-window-manager.coffee @@ -4,6 +4,17 @@ BrowserWindow = require 'browser-window' frameToGuest = {} +# Merge |options| with the |embedder|'s window's options. +mergeBrowserWindowOptions = (embedder, options) -> + if embedder.browserWindowOptions? + # Inherit the original options if it is a BrowserWindow. + options.__proto__ = embedder.browserWindowOptions + else + # Or only inherit web-preferences if it is a webview. + options['web-preferences'] ?= {} + options['web-preferences'].__proto__ = embedder.getWebPreferences() + options + # Create a new guest created by |embedder| with |options|. createGuest = (embedder, url, frameName, options) -> guest = frameToGuest[frameName] @@ -40,11 +51,12 @@ createGuest = (embedder, url, frameName, options) -> # Routed window.open messages. ipc.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPEN', (event, args...) -> [url, frameName, options] = args - event.sender.emit 'new-window', event, url, frameName, 'new-window' - if event.sender.isGuest() or event.defaultPrevented + options = mergeBrowserWindowOptions event.sender, options + event.sender.emit 'new-window', event, url, frameName, 'new-window', options + if (event.sender.isGuest() and not event.sender.allowPopups) or event.defaultPrevented event.returnValue = null else - event.returnValue = createGuest event.sender, args... + event.returnValue = createGuest event.sender, url, frameName, options ipc.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', (event, guestId) -> BrowserWindow.fromId(guestId)?.destroy() diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index 4d5f273340ab..c3620bba7052 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -68,6 +68,7 @@ NativeWindow::NativeWindow( const mate::Dictionary& options) : content::WebContentsObserver(inspectable_web_contents->GetWebContents()), has_frame_(true), + force_using_draggable_region_(false), transparent_(false), enable_larger_than_screen_(false), is_closed_(false), @@ -159,8 +160,13 @@ void NativeWindow::InitFromOptions(const mate::Dictionary& options) { // Then show it. bool show = true; options.Get(switches::kShow, &show); - if (show) + if (show) { Show(); + } else { + // When RenderView is created it sets to visible, this is to prevent + // breaking the visibility API. + web_contents()->WasHidden(); + } } void NativeWindow::SetSize(const gfx::Size& size) { @@ -468,7 +474,7 @@ bool NativeWindow::OnMessageReceived(const IPC::Message& message) { void NativeWindow::UpdateDraggableRegions( const std::vector& regions) { // Draggable region is not supported for non-frameless window. - if (has_frame_) + if (has_frame_ && !force_using_draggable_region_) return; draggable_region_ = DraggableRegionsToSkRegion(regions); } diff --git a/atom/browser/native_window.h b/atom/browser/native_window.h index e9a2b9433d13..5c8d8c73b0fe 100644 --- a/atom/browser/native_window.h +++ b/atom/browser/native_window.h @@ -219,6 +219,13 @@ class NativeWindow : public content::WebContentsObserver, bool enable_larger_than_screen() const { return enable_larger_than_screen_; } gfx::ImageSkia icon() const { return icon_; } + bool force_using_draggable_region() const { + return force_using_draggable_region_; + } + void set_force_using_draggable_region(bool force) { + force_using_draggable_region_ = true; + } + void set_has_dialog_attached(bool has_dialog_attached) { has_dialog_attached_ = has_dialog_attached; } @@ -257,6 +264,9 @@ class NativeWindow : public content::WebContentsObserver, // Whether window has standard frame. bool has_frame_; + // Force the window to be aware of draggable regions. + bool force_using_draggable_region_; + // Whether window is transparent. bool transparent_; diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 9555914c899d..f0a685e4d952 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -351,21 +351,19 @@ NativeWindowMac::NativeWindowMac( bool useStandardWindow = true; options.Get(switches::kStandardWindow, &useStandardWindow); + // New title bar styles are available in Yosemite or newer + std::string titleBarStyle; + if (base::mac::IsOSYosemiteOrLater()) + options.Get(switches::kTitleBarStyle, &titleBarStyle); + NSUInteger styleMask = NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask; if (!useStandardWindow || transparent() || !has_frame()) { styleMask |= NSTexturedBackgroundWindowMask; } - - std::string titleBarStyle = "default"; - options.Get(switches::kTitleBarStyle, &titleBarStyle); - - if (base::mac::IsOSYosemiteOrLater()) { - // New title bar styles are available in Yosemite or newer - if ((titleBarStyle == "hidden") || (titleBarStyle == "hidden-inset")) { - styleMask |= NSFullSizeContentViewWindowMask; - styleMask |= NSUnifiedTitleAndToolbarWindowMask; - } + if ((titleBarStyle == "hidden") || (titleBarStyle == "hidden-inset")) { + styleMask |= NSFullSizeContentViewWindowMask; + styleMask |= NSUnifiedTitleAndToolbarWindowMask; } window_.reset([[AtomNSWindow alloc] @@ -393,18 +391,18 @@ NativeWindowMac::NativeWindowMac( // We will manage window's lifetime ourselves. [window_ setReleasedWhenClosed:NO]; - // Configure title bar look on Yosemite or newer - if (base::mac::IsOSYosemiteOrLater()) { - if ((titleBarStyle == "hidden") || (titleBarStyle == "hidden-inset")) { - [window_ setTitlebarAppearsTransparent:YES]; - [window_ setTitleVisibility:NSWindowTitleHidden]; - if (titleBarStyle == "hidden-inset") { - NSToolbar *toolbar = [[NSToolbar alloc] initWithIdentifier:@"titlebarStylingToolbar"]; - toolbar.showsBaselineSeparator = NO; - [window_ setToolbar:toolbar]; - [toolbar release]; - } + // Hide the title bar. + if ((titleBarStyle == "hidden") || (titleBarStyle == "hidden-inset")) { + [window_ setTitlebarAppearsTransparent:YES]; + [window_ setTitleVisibility:NSWindowTitleHidden]; + if (titleBarStyle == "hidden-inset") { + base::scoped_nsobject toolbar( + [[NSToolbar alloc] initWithIdentifier:@"titlebarStylingToolbar"]); + [toolbar setShowsBaselineSeparator:NO]; + [window_ setToolbar:toolbar]; } + // We should be aware of draggable regions when using hidden titlebar. + set_force_using_draggable_region(true); } // On OS X the initial window size doesn't include window frame. @@ -436,6 +434,11 @@ NativeWindowMac::NativeWindowMac( [view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; InstallView(); + + // Install the DraggableRegionView if it is forced to use draggable regions + // for normal window. + if (has_frame() && force_using_draggable_region()) + InstallDraggableRegionView(); } NativeWindowMac::~NativeWindowMac() { @@ -467,6 +470,8 @@ bool NativeWindowMac::IsFocused() { } void NativeWindowMac::Show() { + web_contents()->WasShown(); + // This method is supposed to put focus on window, however if the app does not // have focus then "makeKeyAndOrderFront" will only show the window. [NSApp activateIgnoringOtherApps:YES]; @@ -475,11 +480,13 @@ void NativeWindowMac::Show() { } void NativeWindowMac::ShowInactive() { + web_contents()->WasShown(); [window_ orderFrontRegardless]; } void NativeWindowMac::Hide() { [window_ orderOut:nil]; + web_contents()->WasHidden(); } bool NativeWindowMac::IsVisible() { diff --git a/atom/browser/native_window_views.cc b/atom/browser/native_window_views.cc index 45697c96683c..70707219f3f2 100644 --- a/atom/browser/native_window_views.cc +++ b/atom/browser/native_window_views.cc @@ -338,15 +338,18 @@ bool NativeWindowViews::IsFocused() { } void NativeWindowViews::Show() { + web_contents()->WasShown(); window_->native_widget_private()->ShowWithWindowState(GetRestoredState()); } void NativeWindowViews::ShowInactive() { + web_contents()->WasShown(); window_->ShowInactive(); } void NativeWindowViews::Hide() { window_->Hide(); + web_contents()->WasHidden(); } bool NativeWindowViews::IsVisible() { diff --git a/atom/browser/resources/mac/Info.plist b/atom/browser/resources/mac/Info.plist index 54ba3546a75f..eb39cb35f947 100644 --- a/atom/browser/resources/mac/Info.plist +++ b/atom/browser/resources/mac/Info.plist @@ -17,7 +17,7 @@ CFBundleIconFile atom.icns CFBundleVersion - 0.33.0 + 0.33.3 LSMinimumSystemVersion 10.8.0 NSMainNibFile diff --git a/atom/browser/resources/win/atom.rc b/atom/browser/resources/win/atom.rc index 8863d8be7677..6fba6cf80676 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,33,0,0 - PRODUCTVERSION 0,33,0,0 + FILEVERSION 0,33,3,0 + PRODUCTVERSION 0,33,3,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -74,12 +74,12 @@ BEGIN BEGIN VALUE "CompanyName", "GitHub, Inc." VALUE "FileDescription", "Electron" - VALUE "FileVersion", "0.33.0" + VALUE "FileVersion", "0.33.3" VALUE "InternalName", "electron.exe" VALUE "LegalCopyright", "Copyright (C) 2015 GitHub, Inc. All rights reserved." VALUE "OriginalFilename", "electron.exe" VALUE "ProductName", "Electron" - VALUE "ProductVersion", "0.33.0" + VALUE "ProductVersion", "0.33.3" VALUE "SquirrelAwareVersion", "1" END END diff --git a/atom/browser/ui/win/notify_icon.cc b/atom/browser/ui/win/notify_icon.cc index 4026d9ec4a6b..c88d4c810ef8 100644 --- a/atom/browser/ui/win/notify_icon.cc +++ b/atom/browser/ui/win/notify_icon.cc @@ -4,10 +4,7 @@ #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" @@ -28,31 +25,7 @@ NotifyIcon::NotifyIcon(NotifyIconHost* host, icon_id_(id), window_(window), message_id_(message), - 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); - } - + menu_model_(NULL) { NOTIFYICONDATA icon_data; InitIconData(&icon_data); icon_data.uFlags |= NIF_MESSAGE; @@ -81,10 +54,6 @@ void NotifyIcon::HandleClickEvent(const gfx::Point& cursor_pos, 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); @@ -202,13 +171,6 @@ 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 136186b689b9..d368dec71327 100644 --- a/atom/browser/ui/win/notify_icon.h +++ b/atom/browser/ui/win/notify_icon.h @@ -79,10 +79,6 @@ 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/web_contents_preferences.cc b/atom/browser/web_contents_preferences.cc index db55eddf7e2d..2856598c0bc0 100644 --- a/atom/browser/web_contents_preferences.cc +++ b/atom/browser/web_contents_preferences.cc @@ -40,6 +40,9 @@ WebContentsPreferences::WebContentsPreferences( base::DictionaryValue* web_preferences) { web_preferences_.Swap(web_preferences); web_contents->SetUserData(UserDataKey(), this); + + // The "isGuest" is not a preferences field. + web_preferences_.Remove("isGuest", nullptr); } WebContentsPreferences::~WebContentsPreferences() { @@ -94,7 +97,7 @@ void WebContentsPreferences::AppendExtraCommandLineSwitches( if (base::FilePath(preload).IsAbsolute()) command_line->AppendSwitchNative(switches::kPreloadScript, preload); else - LOG(ERROR) << "preload script must have abosulute path."; + LOG(ERROR) << "preload script must have absolute path."; } else if (web_preferences.GetString(switches::kPreloadUrl, &preload)) { // Translate to file path if there is "preload-url" option. base::FilePath preload_path; diff --git a/atom/browser/web_contents_preferences.h b/atom/browser/web_contents_preferences.h index 83b485f449bc..3e36df021478 100644 --- a/atom/browser/web_contents_preferences.h +++ b/atom/browser/web_contents_preferences.h @@ -37,6 +37,9 @@ class WebContentsPreferences // $.extend(|web_preferences_|, |new_web_preferences|). void Merge(const base::DictionaryValue& new_web_preferences); + // Returns the web preferences. + base::DictionaryValue* web_preferences() { return &web_preferences_; } + private: friend class content::WebContentsUserData; diff --git a/atom/common/asar/archive.cc b/atom/common/asar/archive.cc index 0b2f59ae0f18..969f958956ca 100644 --- a/atom/common/asar/archive.cc +++ b/atom/common/asar/archive.cc @@ -115,6 +115,14 @@ bool FillFileInfoWithNode(Archive::FileInfo* info, Archive::Archive(const base::FilePath& path) : path_(path), file_(path_, base::File::FLAG_OPEN | base::File::FLAG_READ), +#if defined(OS_WIN) + fd_(_open_osfhandle( + reinterpret_cast(file_.GetPlatformFile()), 0)), +#elif defined(OS_POSIX) + fd_(file_.GetPlatformFile()), +#else + fd_(-1), +#endif header_size_(0) { } @@ -271,17 +279,7 @@ bool Archive::CopyFileOut(const base::FilePath& path, base::FilePath* out) { } int Archive::GetFD() const { - if (!file_.IsValid()) - return -1; - -#if defined(OS_WIN) - return - _open_osfhandle(reinterpret_cast(file_.GetPlatformFile()), 0); -#elif defined(OS_POSIX) - return file_.GetPlatformFile(); -#else - return -1; -#endif + return fd_; } } // namespace asar diff --git a/atom/common/asar/archive.h b/atom/common/asar/archive.h index dda7aa78e0c0..f2ff2f76d676 100644 --- a/atom/common/asar/archive.h +++ b/atom/common/asar/archive.h @@ -69,6 +69,7 @@ class Archive { private: base::FilePath path_; base::File file_; + int fd_; uint32 header_size_; scoped_ptr header_; diff --git a/atom/common/atom_version.h b/atom/common/atom_version.h index deb09585434e..4f240347540e 100644 --- a/atom/common/atom_version.h +++ b/atom/common/atom_version.h @@ -7,7 +7,7 @@ #define ATOM_MAJOR_VERSION 0 #define ATOM_MINOR_VERSION 33 -#define ATOM_PATCH_VERSION 0 +#define ATOM_PATCH_VERSION 3 #define ATOM_VERSION_IS_RELEASE 1 diff --git a/atom/common/lib/asar.coffee b/atom/common/lib/asar.coffee index 900a686d44a2..22d0e70b34fa 100644 --- a/atom/common/lib/asar.coffee +++ b/atom/common/lib/asar.coffee @@ -329,7 +329,7 @@ exports.wrapFsWithAsar = (fs) -> buffer = new Buffer(info.size) fd = archive.getFd() - retrun undefined unless fd >= 0 + return undefined unless fd >= 0 fs.readSync fd, buffer, 0, info.size, info.offset buffer.toString 'utf8' diff --git a/atom/common/lib/init.coffee b/atom/common/lib/init.coffee index 4bc3e36986c0..acb635edeaa4 100644 --- a/atom/common/lib/init.coffee +++ b/atom/common/lib/init.coffee @@ -1,8 +1,7 @@ -process = global.process -fs = require 'fs' -path = require 'path' -timers = require 'timers' -Module = require 'module' +fs = require 'fs' +path = require 'path' +timers = require 'timers' +Module = require 'module' process.atomBinding = (name) -> try @@ -37,6 +36,8 @@ wrapWithActivateUvLoop = (func) -> process.activateUvLoop() func.apply this, arguments process.nextTick = wrapWithActivateUvLoop process.nextTick +global.setImmediate = wrapWithActivateUvLoop timers.setImmediate +global.clearImmediate = timers.clearImmediate if process.type is 'browser' # setTimeout needs to update the polling timeout of the event loop, when @@ -45,10 +46,3 @@ if process.type is 'browser' # recalculate the timeout in browser process. global.setTimeout = wrapWithActivateUvLoop timers.setTimeout global.setInterval = wrapWithActivateUvLoop timers.setInterval - global.setImmediate = wrapWithActivateUvLoop timers.setImmediate - global.clearImmediate = wrapWithActivateUvLoop timers.clearImmediate -else - # There are no setImmediate under renderer process by default, so we need to - # manually setup them here. - global.setImmediate = setImmediate - global.clearImmediate = clearImmediate diff --git a/atom/common/node_bindings.cc b/atom/common/node_bindings.cc index 5aec200550ad..2da68854ad14 100644 --- a/atom/common/node_bindings.cc +++ b/atom/common/node_bindings.cc @@ -34,6 +34,7 @@ REFERENCE_MODULE(atom_browser_app); REFERENCE_MODULE(atom_browser_auto_updater); REFERENCE_MODULE(atom_browser_content_tracing); REFERENCE_MODULE(atom_browser_dialog); +REFERENCE_MODULE(atom_browser_download_item); REFERENCE_MODULE(atom_browser_menu); REFERENCE_MODULE(atom_browser_power_monitor); REFERENCE_MODULE(atom_browser_power_save_blocker); diff --git a/atom/common/options_switches.cc b/atom/common/options_switches.cc index c70e1ba4afa6..46687becf84a 100644 --- a/atom/common/options_switches.cc +++ b/atom/common/options_switches.cc @@ -113,6 +113,10 @@ const char kDisableHttpCache[] = "disable-http-cache"; // Register schemes to standard. const char kRegisterStandardSchemes[] = "register-standard-schemes"; +// The minimum SSL/TLS version ("tls1", "tls1.1", or "tls1.2") that +// TLS fallback will accept. +const char kSSLVersionFallbackMin[] = "ssl-version-fallback-min"; + // The browser process app model ID const char kAppUserModelId[] = "app-user-model-id"; diff --git a/atom/common/options_switches.h b/atom/common/options_switches.h index e62f3116661a..16046d19c822 100644 --- a/atom/common/options_switches.h +++ b/atom/common/options_switches.h @@ -59,6 +59,7 @@ extern const char kPageVisibility[]; extern const char kDisableHttpCache[]; extern const char kRegisterStandardSchemes[]; +extern const char kSSLVersionFallbackMin[]; extern const char kAppUserModelId[]; diff --git a/atom/renderer/api/atom_api_web_frame.cc b/atom/renderer/api/atom_api_web_frame.cc index 6e2054453a79..4506658588c2 100644 --- a/atom/renderer/api/atom_api_web_frame.cc +++ b/atom/renderer/api/atom_api_web_frame.cc @@ -98,6 +98,16 @@ void WebFrame::RegisterURLSchemeAsBypassingCsp(const std::string& scheme) { blink::WebString::fromUTF8(scheme)); } +void WebFrame::RegisterURLSchemeAsPrivileged(const std::string& scheme) { + // Register scheme to privileged list (https, wss, data, chrome-extension) + blink::WebString privileged_scheme(blink::WebString::fromUTF8(scheme)); + blink::WebSecurityPolicy::registerURLSchemeAsSecure(privileged_scheme); + blink::WebSecurityPolicy::registerURLSchemeAsBypassingContentSecurityPolicy( + privileged_scheme); + blink::WebSecurityPolicy::registerURLSchemeAsAllowingServiceWorkers( + privileged_scheme); +} + mate::ObjectTemplateBuilder WebFrame::GetObjectTemplateBuilder( v8::Isolate* isolate) { return mate::ObjectTemplateBuilder(isolate) @@ -116,7 +126,9 @@ mate::ObjectTemplateBuilder WebFrame::GetObjectTemplateBuilder( .SetMethod("registerUrlSchemeAsSecure", &WebFrame::RegisterURLSchemeAsSecure) .SetMethod("registerUrlSchemeAsBypassingCsp", - &WebFrame::RegisterURLSchemeAsBypassingCsp); + &WebFrame::RegisterURLSchemeAsBypassingCsp) + .SetMethod("registerUrlSchemeAsPrivileged", + &WebFrame::RegisterURLSchemeAsPrivileged); } // static diff --git a/atom/renderer/api/atom_api_web_frame.h b/atom/renderer/api/atom_api_web_frame.h index f3895353b97c..a3dec6cb7689 100644 --- a/atom/renderer/api/atom_api_web_frame.h +++ b/atom/renderer/api/atom_api_web_frame.h @@ -58,6 +58,7 @@ class WebFrame : public mate::Wrappable { void RegisterURLSchemeAsSecure(const std::string& scheme); void RegisterURLSchemeAsBypassingCsp(const std::string& scheme); + void RegisterURLSchemeAsPrivileged(const std::string& scheme); // mate::Wrappable: virtual mate::ObjectTemplateBuilder GetObjectTemplateBuilder( diff --git a/atom/renderer/api/lib/remote.coffee b/atom/renderer/api/lib/remote.coffee index abd86e7eee0c..00d22ab8cf6d 100644 --- a/atom/renderer/api/lib/remote.coffee +++ b/atom/renderer/api/lib/remote.coffee @@ -1,4 +1,3 @@ -process = global.process ipc = require 'ipc' v8Util = process.atomBinding 'v8_util' CallbacksRegistry = require 'callbacks-registry' diff --git a/atom/renderer/lib/init.coffee b/atom/renderer/lib/init.coffee index 9825f75be928..274c50ec5f39 100644 --- a/atom/renderer/lib/init.coffee +++ b/atom/renderer/lib/init.coffee @@ -1,8 +1,7 @@ -process = global.process -events = require 'events' -path = require 'path' -url = require 'url' -Module = require 'module' +events = require 'events' +path = require 'path' +url = require 'url' +Module = require 'module' # We modified the original process.argv to let node.js load the # atom-renderer.js, we need to restore it here. diff --git a/atom/renderer/lib/override.coffee b/atom/renderer/lib/override.coffee index b60374e04754..5cffdd486d9c 100644 --- a/atom/renderer/lib/override.coffee +++ b/atom/renderer/lib/override.coffee @@ -1,4 +1,3 @@ -process = global.process ipc = require 'ipc' remote = require 'remote' @@ -71,7 +70,6 @@ window.open = (url, frameName='', features='') -> if guestId new BrowserWindowProxy(guestId) else - console.error 'It is not allowed to open new window from this WebContents' null # Use the dialog API to implement alert(). diff --git a/atom/renderer/lib/web-view/guest-view-internal.coffee b/atom/renderer/lib/web-view/guest-view-internal.coffee index b491184fb8d7..2852d1122874 100644 --- a/atom/renderer/lib/web-view/guest-view-internal.coffee +++ b/atom/renderer/lib/web-view/guest-view-internal.coffee @@ -16,7 +16,7 @@ WEB_VIEW_EVENTS = 'did-get-redirect-request': ['oldUrl', 'newUrl', 'isMainFrame'] 'dom-ready': [] 'console-message': ['level', 'message', 'line', 'sourceId'] - 'new-window': ['url', 'frameName', 'disposition'] + 'new-window': ['url', 'frameName', 'disposition', 'options'] 'close': [] 'crashed': [] 'gpu-crashed': [] diff --git a/atom/renderer/lib/web-view/web-view-attributes.coffee b/atom/renderer/lib/web-view/web-view-attributes.coffee index acca826a9e3d..e980e6c96bfc 100644 --- a/atom/renderer/lib/web-view/web-view-attributes.coffee +++ b/atom/renderer/lib/web-view/web-view-attributes.coffee @@ -216,6 +216,7 @@ WebViewImpl::setupWebViewAttributes = -> @attributes[webViewConstants.ATTRIBUTE_NODEINTEGRATION] = new BooleanAttribute(webViewConstants.ATTRIBUTE_NODEINTEGRATION, this) @attributes[webViewConstants.ATTRIBUTE_PLUGINS] = new BooleanAttribute(webViewConstants.ATTRIBUTE_PLUGINS, this) @attributes[webViewConstants.ATTRIBUTE_DISABLEWEBSECURITY] = new BooleanAttribute(webViewConstants.ATTRIBUTE_DISABLEWEBSECURITY, this) + @attributes[webViewConstants.ATTRIBUTE_ALLOWPOPUPS] = new BooleanAttribute(webViewConstants.ATTRIBUTE_ALLOWPOPUPS, this) @attributes[webViewConstants.ATTRIBUTE_PRELOAD] = new PreloadAttribute(this) autosizeAttributes = [ diff --git a/atom/renderer/lib/web-view/web-view-constants.coffee b/atom/renderer/lib/web-view/web-view-constants.coffee index deb678e6a1e8..bfb9376fa7ea 100644 --- a/atom/renderer/lib/web-view/web-view-constants.coffee +++ b/atom/renderer/lib/web-view/web-view-constants.coffee @@ -13,6 +13,7 @@ module.exports = ATTRIBUTE_NODEINTEGRATION: 'nodeintegration' ATTRIBUTE_PLUGINS: 'plugins' ATTRIBUTE_DISABLEWEBSECURITY: 'disablewebsecurity' + ATTRIBUTE_ALLOWPOPUPS: 'allowpopups' ATTRIBUTE_PRELOAD: 'preload' ATTRIBUTE_USERAGENT: 'useragent' diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index f1705c291756..a1870bc66afd 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -665,7 +665,7 @@ Same as `webContents.loadUrl(url[, options])`. Same as `webContents.reload`. -### `win.setMenu(menu)` _OS X_ +### `win.setMenu(menu)` _Linux_ _Windows_ * `menu` Menu diff --git a/docs/api/chrome-command-line-switches.md b/docs/api/chrome-command-line-switches.md index 2f995c99b219..cd633fc0460c 100644 --- a/docs/api/chrome-command-line-switches.md +++ b/docs/api/chrome-command-line-switches.md @@ -87,6 +87,11 @@ Sets the `version` of the pepper flash plugin. Enables net log events to be saved and writes them to `path`. +## --ssl-version-fallback-min=`version` + +Set the minimum SSL/TLS version ("tls1", "tls1.1" or "tls1.2") that TLS +fallback will accept. + ## --v=`log_level` Gives the default maximal active V-logging level; 0 is the default. Normally diff --git a/docs/api/download-item.md b/docs/api/download-item.md new file mode 100644 index 000000000000..53cd56cca9e5 --- /dev/null +++ b/docs/api/download-item.md @@ -0,0 +1,101 @@ +# DownloadItem + +`DownloadItem` is an EventEmitter represents a download item in Electron. It +is used in `will-download` event of `Session` module, and allows users to +control the download item. + +```javascript +// In the main process. +win.webContents.session.on('will-download', function(event, item, webContents) { + // Set the save path, making Electron not to prompt a save dialog. + item.setSavePath('/tmp/save.pdf'); + console.log(item.getMimeType()); + console.log(item.getFilename()); + console.log(item.getTotalBytes()); + item.on('updated', function() { + console.log('Received bytes: ' + item.getReceivedBytes()); + }); + item.on('done', function(e, state) { + if (state == "completed") { + console.log("Download successfully"); + } else { + console.log("Download is cancelled or interrupted that can't be resumed"); + } + }); +``` + +## Events + +### Event: 'updated' + +Emits when the `downloadItem` gets updated. + +### Event: 'done' + +* `event` Event +* `state` String + * `completed` - The download completed successfully. + * `cancelled` - The download has been cancelled. + * `interrupted` - An error broke the connection with the file server. + +Emits when the download is in a terminal state. This includes a completed +download, a cancelled download(via `downloadItem.cancel()`), and interrupted +download that can't be resumed. + +## Methods + +The `downloadItem` object has the following methods: + +### `downloadItem.setSavePath(path)` + +* `path` String - Set the save file path of the download item. + +The API is only available in session's `will-download` callback function. +If user doesn't set the save path via the API, Electron will use the original +routine to determine the save path(Usually prompts a save dialog). + +### `downloadItem.pause()` + +Pauses the download. + +### `downloadItem.resume()` + +Resumes the download that has been paused. + +### `downloadItem.cancel()` + +Cancels the download operation. + +### `downloadItem.getUrl()` + +Returns a `String` represents the origin url where the item is downloaded from. + +### `downloadItem.getMimeType()` + +Returns a `String` represents the mime type. + +### `downloadItem.hasUserGesture()` + +Returns a `Boolean` indicates whether the download has user gesture. + +### `downloadItem.getFilename()` + +Returns a `String` represents the file name of the download item. + +**Note:** The file name is not always the same as the actual one saved in local +disk. If user changes the file name in a prompted download saving dialog, the +actual name of saved file will be different. + +### `downloadItem.getTotalBytes()` + +Returns a `Integer` represents the total size in bytes of the download item. +If the size is unknown, it returns 0. + +### `downloadItem.getReceivedBytes()` + +Returns a `Integer` represents the received bytes of the download item. + +### `downloadItem.getContentDisposition()` + +Returns a `String` represents the Content-Disposition field from the response +header. diff --git a/docs/api/session.md b/docs/api/session.md index 439cf8514e93..64991ab7530c 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -18,11 +18,7 @@ var session = win.webContents.session ### Event: 'will-download' * `event` Event -* `item` Object - * `url` String - * `filename` String - * `mimeType` String - * `hasUserGesture` Boolean +* `item` [DownloadItem](download-item.md) * `webContents` [WebContents](web-contents.md) Fired when Electron is about to download `item` in `webContents`. @@ -32,7 +28,7 @@ Calling `event.preventDefault()` will cancel the download. ```javascript session.on('will-download', function(event, item, webContents) { event.preventDefault(); - require('request')(item.url, function(data) { + require('request')(item.getUrl(), function(data) { require('fs').writeFileSync('/somewhere', data); }); }); diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index e8f72cd24bf2..c2dccc3a876c 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -78,6 +78,10 @@ Returns: * `oldUrl` String * `newUrl` String * `isMainFrame` Boolean +* `httpResponseCode` Integer +* `requestMethod` String +* `referrer` String +* `headers` Object Emitted when a redirect is received while requesting a resource. @@ -107,6 +111,8 @@ Returns: * `frameName` String * `disposition` String - Can be `default`, `foreground-tab`, `background-tab`, `new-window` and `other`. +* `options` Object - The options which will be used for creating the new + `BrowserWindow`. Emitted when the page requests to open a new window for a `url`. It could be requested by `window.open` or an external link like ``. @@ -415,7 +421,7 @@ win.webContents.on("did-finish-load", function() { win.webContents.printToPDF({}, function(error, data) { if (error) throw error; fs.writeFile("/tmp/print.pdf", data, function(error) { - if (err) + if (error) throw error; console.log("Write PDF successfully."); }) diff --git a/docs/api/web-frame.md b/docs/api/web-frame.md index bdf42da3003a..33597543b773 100644 --- a/docs/api/web-frame.md +++ b/docs/api/web-frame.md @@ -83,4 +83,11 @@ attackers. Resources will be loaded from this `scheme` regardless of the current page's Content Security Policy. +### `webFrame.registerUrlSchemeAsPrivileged(scheme)` + +* `scheme` String + +Registers the `scheme` as secure, bypasses content security policy for resources and +allows registering ServiceWorker. + [spellchecker]: https://github.com/atom/node-spellchecker diff --git a/docs/api/web-view-tag.md b/docs/api/web-view-tag.md index 8eb3857ac970..3fda3a98edb5 100644 --- a/docs/api/web-view-tag.md +++ b/docs/api/web-view-tag.md @@ -149,6 +149,14 @@ This value can only be modified before the first navigation, since the session of an active renderer process cannot change. Subsequent attempts to modify the value will fail with a DOM exception. +### `allowpopups` + +```html + +``` + +If "on", the guest page will be allowed to open new windows. + ## Methods The `webview` tag has the following methods: @@ -496,7 +504,9 @@ Returns: * `url` String * `frameName` String * `disposition` String - Can be `default`, `foreground-tab`, `background-tab`, - `new-window` and `other` + `new-window` and `other`. +* `options` Object - The options which should be used for creating the new + `BrowserWindow`. Fired when the guest page attempts to open a new browser window. diff --git a/docs/api/window-open.md b/docs/api/window-open.md index c574d5d036ee..5d298e61e75b 100644 --- a/docs/api/window-open.md +++ b/docs/api/window-open.md @@ -8,6 +8,10 @@ The proxy has limited standard functionality implemented to be compatible with traditional web pages. For full control of the new window you should create a `BrowserWindow` directly. +The newly created `BrowserWindow` will inherit parent window's options by +default, to override inherited options you can set them in the `features` +string. + ### `window.open(url[, frameName][, features])` * `url` String @@ -16,6 +20,9 @@ you should create a `BrowserWindow` directly. Creates a new window and returns an instance of `BrowserWindowProxy` class. +The `features` string follows the format of standard browser, but each feature +has to be a field of `BrowserWindow`'s options. + ### `window.opener.postMessage(message, targetOrigin)` * `message` String diff --git a/docs/tutorial/quick-start.md b/docs/tutorial/quick-start.md index 93cd0ebc5bd9..4ce65a1dc1ae 100644 --- a/docs/tutorial/quick-start.md +++ b/docs/tutorial/quick-start.md @@ -124,6 +124,7 @@ Finally the `index.html` is the web page you want to show: + Hello World! diff --git a/docs/tutorial/using-native-node-modules.md b/docs/tutorial/using-native-node-modules.md index c338494cde2b..0e6477fc4c04 100644 --- a/docs/tutorial/using-native-node-modules.md +++ b/docs/tutorial/using-native-node-modules.md @@ -34,6 +34,19 @@ npm install --save-dev electron-rebuild node ./node_modules/.bin/electron-rebuild ``` +### The npm Way + +You can also use `npm` to install modules. The steps are exactly the same with +Node modules, except that you need to setup some environment variables: + +```bash +export npm_config_disturl=https://atom.io/download/atom-shell +export npm_config_target=0.33.1 +export npm_config_arch=x64 +export npm_config_runtime=electron +HOME=~/.electron-gyp npm install module-name +``` + ### The node-gyp Way To build Node modules with headers of Electron, you need to tell `node-gyp` @@ -48,15 +61,3 @@ The `HOME=~/.electron-gyp` changes where to find development headers. The `--target=0.29.1` is version of Electron. The `--dist-url=...` specifies where to download the headers. The `--arch=x64` says the module is built for 64bit system. - -### The npm Way - -You can also use `npm` to install modules. The steps are exactly the same with -Node modules, except that you need to setup some environment variables: - -```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/filenames.gypi b/filenames.gypi index 99d6bc6d50d3..cb6a2273eae3 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -71,6 +71,8 @@ 'atom/browser/api/atom_api_content_tracing.cc', 'atom/browser/api/atom_api_cookies.cc', 'atom/browser/api/atom_api_cookies.h', + 'atom/browser/api/atom_api_download_item.cc', + 'atom/browser/api/atom_api_download_item.h', 'atom/browser/api/atom_api_dialog.cc', 'atom/browser/api/atom_api_global_shortcut.cc', 'atom/browser/api/atom_api_global_shortcut.h', @@ -129,6 +131,8 @@ 'atom/browser/atom_quota_permission_context.h', 'atom/browser/atom_speech_recognition_manager_delegate.cc', 'atom/browser/atom_speech_recognition_manager_delegate.h', + 'atom/browser/atom_ssl_config_service.cc', + 'atom/browser/atom_ssl_config_service.h', 'atom/browser/bridge_task_runner.cc', 'atom/browser/bridge_task_runner.h', 'atom/browser/browser.cc', diff --git a/script/create-dist.py b/script/create-dist.py index c978641df320..ca7e21642872 100755 --- a/script/create-dist.py +++ b/script/create-dist.py @@ -126,6 +126,8 @@ def copy_chrome_binary(binary): def copy_license(): + shutil.copy2(os.path.join(CHROMIUM_DIR, '..', 'LICENSES.chromium.html'), + DIST_DIR) shutil.copy2(os.path.join(SOURCE_ROOT, 'LICENSE'), DIST_DIR) diff --git a/script/lib/config.py b/script/lib/config.py index 0df68bd773bf..68f216785d60 100644 --- a/script/lib/config.py +++ b/script/lib/config.py @@ -8,7 +8,7 @@ import sys BASE_URL = os.getenv('LIBCHROMIUMCONTENT_MIRROR') or \ 'http://github-janky-artifacts.s3.amazonaws.com/libchromiumcontent' -LIBCHROMIUMCONTENT_COMMIT = '8482fe555913dea3bde8a74f754524e2cfb02bc5' +LIBCHROMIUMCONTENT_COMMIT = '04523758cda2a96d2454f9056fb1fb9a1c1f95f1' PLATFORM = { 'cygwin': 'win32', diff --git a/script/update-external-binaries.py b/script/update-external-binaries.py index 49e73435ab51..fae268ea8cb7 100755 --- a/script/update-external-binaries.py +++ b/script/update-external-binaries.py @@ -8,7 +8,7 @@ from lib.config import get_target_arch from lib.util import safe_mkdir, rm_rf, extract_zip, tempdir, download -VERSION = 'v0.7.0' +VERSION = 'v0.8.0' SOURCE_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) FRAMEWORKS_URL = 'http://github.com/atom/atom-shell-frameworks/releases' \ '/download/' + VERSION diff --git a/spec/api-browser-window-spec.coffee b/spec/api-browser-window-spec.coffee index bb4d782e7ffb..2e4a66b92b9a 100644 --- a/spec/api-browser-window-spec.coffee +++ b/spec/api-browser-window-spec.coffee @@ -294,17 +294,7 @@ describe 'browser-window module', -> w.show() w.minimize() - describe 'will-navigate event', -> - @timeout 10000 - it 'emits when user starts a navigation', (done) -> - url = "file://#{fixtures}/pages/will-navigate.html" - w.webContents.on 'will-navigate', (event, u) -> - event.preventDefault() - assert.equal u, url - done() - w.loadUrl url - - describe 'beginFrameSubscription method', -> + xdescribe 'beginFrameSubscription method', -> it 'subscribes frame updates', (done) -> w.loadUrl "file://#{fixtures}/api/blank.html" w.webContents.beginFrameSubscription (data) -> diff --git a/spec/api-session-spec.coffee b/spec/api-session-spec.coffee index 34a08ee50f09..9e083d27c0f7 100644 --- a/spec/api-session-spec.coffee +++ b/spec/api-session-spec.coffee @@ -2,6 +2,7 @@ assert = require 'assert' remote = require 'remote' http = require 'http' path = require 'path' +fs = require 'fs' app = remote.require 'app' BrowserWindow = remote.require 'browser-window' @@ -72,3 +73,51 @@ describe 'session module', -> quotas: ['persistent'], w.webContents.session.clearStorageData options, -> w.webContents.send 'getcount' + + describe 'DownloadItem', -> + # A 5 MB mock pdf. + mockPDF = new Buffer 1024 * 1024 * 5 + contentDisposition = 'inline; filename="mock.pdf"' + ipc = require 'ipc' + downloadFilePath = path.join fixtures, 'mock.pdf' + downloadServer = http.createServer (req, res) -> + res.writeHead 200, { + 'Content-Length': mockPDF.length, + 'Content-Type': 'application/pdf', + 'Content-Disposition': contentDisposition + } + res.end mockPDF + downloadServer.close() + + it 'can download successfully', (done) -> + downloadServer.listen 0, '127.0.0.1', -> + {port} = downloadServer.address() + ipc.sendSync 'set-download-option', false + w.loadUrl "#{url}:#{port}" + ipc.once 'download-done', (state, url, mimeType, receivedBytes, + totalBytes, disposition, filename) -> + assert.equal state, 'completed' + assert.equal filename, 'mock.pdf' + assert.equal url, "http://127.0.0.1:#{port}/" + assert.equal mimeType, 'application/pdf' + assert.equal receivedBytes, mockPDF.length + assert.equal totalBytes, mockPDF.length + assert.equal disposition, contentDisposition + assert fs.existsSync downloadFilePath + fs.unlinkSync downloadFilePath + done() + + it 'can cancel download', (done) -> + downloadServer.listen 0, '127.0.0.1', -> + {port} = downloadServer.address() + ipc.sendSync 'set-download-option', true + w.loadUrl "#{url}:#{port}/" + ipc.once 'download-done', (state, url, mimeType, receivedBytes, + totalBytes, disposition, filename) -> + assert.equal state, 'cancelled' + assert.equal filename, 'mock.pdf' + assert.equal mimeType, 'application/pdf' + assert.equal receivedBytes, 0 + assert.equal totalBytes, mockPDF.length + assert.equal disposition, contentDisposition + done() diff --git a/spec/asar-spec.coffee b/spec/asar-spec.coffee index 4ef9337f3abe..1e6ee6910369 100644 --- a/spec/asar-spec.coffee +++ b/spec/asar-spec.coffee @@ -8,6 +8,10 @@ describe 'asar package', -> describe 'node api', -> describe 'fs.readFileSync', -> + it 'does not leak fd', -> + for i in [1..10000] + fs.readFileSync(path.join(process.resourcesPath, 'atom.asar', 'renderer', 'api', 'lib', 'ipc.js')) + it 'reads a normal file', -> file1 = path.join fixtures, 'asar', 'a.asar', 'file1' assert.equal fs.readFileSync(file1).toString().trim(), 'file1' diff --git a/spec/chromium-spec.coffee b/spec/chromium-spec.coffee index 2c42cd9a4cf0..a782079026a7 100644 --- a/spec/chromium-spec.coffee +++ b/spec/chromium-spec.coffee @@ -23,6 +23,23 @@ describe 'chromium feature', -> {port} = server.address() $.get "http://127.0.0.1:#{port}" + describe 'document.hidden', -> + BrowserWindow = remote.require 'browser-window' + ipc = remote.require 'ipc' + url = "file://#{fixtures}/pages/document-hidden.html" + w = null + + afterEach -> + w?.destroy() + ipc.removeAllListeners 'hidden' + + it 'is set correctly when window is not shown', (done) -> + ipc.once 'hidden', (event, hidden) -> + assert hidden + done() + w = new BrowserWindow(show:false) + w.loadUrl url + describe 'navigator.webkitGetUserMedia', -> it 'calls its callbacks', (done) -> @timeout 5000 @@ -35,6 +52,8 @@ describe 'chromium feature', -> assert.notEqual navigator.language, '' describe 'window.open', -> + @timeout 10000 + it 'returns a BrowserWindowProxy object', -> b = window.open 'about:blank', '', 'show=no' assert.equal b.closed, false @@ -50,6 +69,16 @@ describe 'chromium feature', -> window.addEventListener 'message', listener b = window.open "file://#{fixtures}/pages/window-opener-node.html", '', 'node-integration=no,show=no' + it 'inherit options of parent window', (done) -> + listener = (event) -> + window.removeEventListener 'message', listener + b.close() + size = remote.getCurrentWindow().getSize() + assert.equal event.data, "size: #{size.width} #{size.height}" + done() + window.addEventListener 'message', listener + b = window.open "file://#{fixtures}/pages/window-open-size.html", '', 'show=no' + describe 'window.opener', -> @timeout 10000 @@ -62,7 +91,7 @@ describe 'chromium feature', -> ipc.removeAllListeners 'opener' it 'is null for main window', (done) -> - ipc.on 'opener', (event, opener) -> + ipc.once 'opener', (event, opener) -> assert.equal opener, null done() BrowserWindow = remote.require 'browser-window' @@ -70,7 +99,7 @@ describe 'chromium feature', -> w.loadUrl url it 'is not null for window opened by window.open', (done) -> - ipc.on 'opener', (event, opener) -> + ipc.once 'opener', (event, opener) -> b.close() done(if opener isnt null then undefined else opener) b = window.open url, '', 'show=no' diff --git a/spec/fixtures/pages/document-hidden.html b/spec/fixtures/pages/document-hidden.html new file mode 100644 index 000000000000..17920bff0793 --- /dev/null +++ b/spec/fixtures/pages/document-hidden.html @@ -0,0 +1,7 @@ + + + + + diff --git a/spec/fixtures/pages/window-open-hide.html b/spec/fixtures/pages/window-open-hide.html new file mode 100644 index 000000000000..d6eb7403ac66 --- /dev/null +++ b/spec/fixtures/pages/window-open-hide.html @@ -0,0 +1,12 @@ + + + + + + diff --git a/spec/fixtures/pages/window-open-size.html b/spec/fixtures/pages/window-open-size.html new file mode 100644 index 000000000000..7b06cfddf551 --- /dev/null +++ b/spec/fixtures/pages/window-open-size.html @@ -0,0 +1,8 @@ + + + + + diff --git a/spec/modules-spec.coffee b/spec/modules-spec.coffee index e7bdac36b3e3..1cdc6cf0ea5c 100644 --- a/spec/modules-spec.coffee +++ b/spec/modules-spec.coffee @@ -22,6 +22,12 @@ describe 'third-party module', -> assert.equal msg, 'ok' done() + describe 'ffi', -> + it 'does not crash', -> + ffi = require 'ffi' + libm = ffi.Library('libm', ceil: [ 'double', [ 'double' ] ]) + assert.equal libm.ceil(1.5), 2 + describe 'q', -> Q = require 'q' diff --git a/spec/node-spec.coffee b/spec/node-spec.coffee index c8d569e01ada..969fc76f41ff 100644 --- a/spec/node-spec.coffee +++ b/spec/node-spec.coffee @@ -65,6 +65,7 @@ describe 'node feature', -> describe 'contexts', -> describe 'setTimeout in fs callback', -> + return if process.env.TRAVIS is 'true' it 'does not crash', (done) -> fs.readFile __filename, -> setTimeout done, 0 diff --git a/spec/package.json b/spec/package.json index a3b0d590effa..8f43b711f196 100644 --- a/spec/package.json +++ b/spec/package.json @@ -5,6 +5,7 @@ "version": "0.1.0", "devDependencies": { "basic-auth": "^1.0.0", + "ffi": "2.0.0", "formidable": "1.0.16", "graceful-fs": "3.0.5", "mocha": "2.1.0", diff --git a/spec/static/main.js b/spec/static/main.js index 38ba7cc089de..5b10bb6d43d7 100644 --- a/spec/static/main.js +++ b/spec/static/main.js @@ -1,6 +1,7 @@ var app = require('app'); var ipc = require('ipc'); var dialog = require('dialog'); +var path = require('path'); var BrowserWindow = require('browser-window'); var window = null; @@ -73,4 +74,27 @@ app.on('ready', function() { }); if (chosen == 0) window.destroy(); }); + + // For session's download test, listen 'will-download' event in browser, and + // reply the result to renderer for verifying + var downloadFilePath = path.join(__dirname, '..', 'fixtures', 'mock.pdf'); + require('ipc').on('set-download-option', function(event, need_cancel) { + window.webContents.session.once('will-download', + function(e, item, webContents) { + item.setSavePath(downloadFilePath); + item.on('done', function(e, state) { + window.webContents.send('download-done', + state, + item.getUrl(), + item.getMimeType(), + item.getReceivedBytes(), + item.getTotalBytes(), + item.getContentDisposition(), + item.getFilename()); + }); + if (need_cancel) + item.cancel(); + }); + event.returnValue = "done"; + }); }); diff --git a/spec/webview-spec.coffee b/spec/webview-spec.coffee index b310b7b129f8..c60af8d74d1f 100644 --- a/spec/webview-spec.coffee +++ b/spec/webview-spec.coffee @@ -86,7 +86,7 @@ describe ' tag', -> it 'preload script can still use "process" in required modules when nodeintegration is off', (done) -> webview.addEventListener 'console-message', (e) -> - assert.equal e.message, 'object function object' + assert.equal e.message, 'object undefined object' done() webview.setAttribute 'preload', "#{fixtures}/module/preload-node-off.js" webview.src = "file://#{fixtures}/api/blank.html" @@ -201,6 +201,26 @@ describe ' tag', -> webview.src = "file://#{fixtures}/pages/partition/one.html" document.body.appendChild webview + describe 'allowpopups attribute', -> + it 'can not open new window when not set', (done) -> + listener = (e) -> + assert.equal e.message, 'null' + webview.removeEventListener 'console-message', listener + done() + webview.addEventListener 'console-message', listener + webview.src = "file://#{fixtures}/pages/window-open-hide.html" + document.body.appendChild webview + + it 'can open new window when set', (done) -> + listener = (e) -> + assert.equal e.message, 'window' + webview.removeEventListener 'console-message', listener + done() + webview.addEventListener 'console-message', listener + webview.setAttribute 'allowpopups', 'on' + webview.src = "file://#{fixtures}/pages/window-open-hide.html" + document.body.appendChild webview + describe 'new-window event', -> it 'emits when window.open is called', (done) -> webview.addEventListener 'new-window', (e) -> diff --git a/vendor/brightray b/vendor/brightray index 25f3a9d0a5b7..8e443520e695 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit 25f3a9d0a5b73ec170a65f4e2e4c9ad91e23fc8c +Subproject commit 8e443520e695674fd26585cfa24a0ec0b6140c27 diff --git a/vendor/node b/vendor/node index aa9c7a2316ba..ac25693ad1d4 160000 --- a/vendor/node +++ b/vendor/node @@ -1 +1 @@ -Subproject commit aa9c7a2316ba7762f1d04d091585695be3e6be22 +Subproject commit ac25693ad1d4c248e69a89147fd3995c3bf6c946