diff --git a/atom/app/atom_content_client.cc b/atom/app/atom_content_client.cc index 954d239b5034..8e2a6c573097 100644 --- a/atom/app/atom_content_client.cc +++ b/atom/app/atom_content_client.cc @@ -44,7 +44,7 @@ content::PepperPluginInfo CreatePepperFlashInfo(const base::FilePath& path, std::vector flash_version_numbers = base::SplitString( version, ".", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); - if (flash_version_numbers.size() < 1) + if (flash_version_numbers.empty()) flash_version_numbers.push_back("11"); // |SplitString()| puts in an empty string given an empty string. :( else if (flash_version_numbers[0].empty()) diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index 56a11daf69bd..709696340274 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -427,7 +427,7 @@ void OnClientCertificateSelected( auto certs = net::X509Certificate::CreateCertificateListFromBytes( data.c_str(), data.length(), net::X509Certificate::FORMAT_AUTO); - if (certs.size() > 0) + if (!certs.empty()) delegate->ContinueWithCertificate(certs[0].get()); } @@ -520,7 +520,7 @@ void App::OnQuit() { int exitCode = AtomBrowserMainParts::Get()->GetExitCode(); Emit("quit", exitCode); - if (process_singleton_.get()) { + if (process_singleton_) { process_singleton_->Cleanup(); process_singleton_.reset(); } @@ -695,7 +695,7 @@ std::string App::GetLocale() { bool App::MakeSingleInstance( const ProcessSingleton::NotificationCallback& callback) { - if (process_singleton_.get()) + if (process_singleton_) return false; base::FilePath user_dir; @@ -716,7 +716,7 @@ bool App::MakeSingleInstance( } void App::ReleaseSingleInstance() { - if (process_singleton_.get()) { + if (process_singleton_) { process_singleton_->Cleanup(); process_singleton_.reset(); } diff --git a/atom/browser/api/atom_api_auto_updater.cc b/atom/browser/api/atom_api_auto_updater.cc index 67079abf2864..276d889006d1 100644 --- a/atom/browser/api/atom_api_auto_updater.cc +++ b/atom/browser/api/atom_api_auto_updater.cc @@ -88,7 +88,7 @@ void AutoUpdater::SetFeedURL(const std::string& url, mate::Arguments* args) { void AutoUpdater::QuitAndInstall() { // If we don't have any window then quitAndInstall immediately. WindowList* window_list = WindowList::GetInstance(); - if (window_list->size() == 0) { + if (window_list->empty()) { auto_updater::AutoUpdater::QuitAndInstall(); return; } diff --git a/atom/browser/api/atom_api_dialog.cc b/atom/browser/api/atom_api_dialog.cc index 326834472d24..cc849e026ea1 100644 --- a/atom/browser/api/atom_api_dialog.cc +++ b/atom/browser/api/atom_api_dialog.cc @@ -78,13 +78,14 @@ void ShowMessageBox(int type, if (mate::Converter::FromV8(args->isolate(), peek, &callback)) { - atom::ShowMessageBox(window, (atom::MessageBoxType)type, buttons, - default_id, cancel_id, options, title, message, detail, - checkbox_label, checkbox_checked, icon, callback); + atom::ShowMessageBox(window, static_cast(type), + buttons, default_id, cancel_id, options, title, + message, detail, checkbox_label, checkbox_checked, + icon, callback); } else { - int chosen = atom::ShowMessageBox(window, (atom::MessageBoxType)type, - buttons, default_id, cancel_id, - options, title, message, detail, icon); + int chosen = atom::ShowMessageBox( + window, static_cast(type), buttons, default_id, + cancel_id, options, title, message, detail, icon); args->Return(chosen); } } diff --git a/atom/browser/api/atom_api_session.cc b/atom/browser/api/atom_api_session.cc index 0c850888e8e7..83d103a631f7 100644 --- a/atom/browser/api/atom_api_session.cc +++ b/atom/browser/api/atom_api_session.cc @@ -233,7 +233,7 @@ class ResolveProxyHelper { public: ResolveProxyHelper(AtomBrowserContext* browser_context, const GURL& url, - Session::ResolveProxyCallback callback) + const Session::ResolveProxyCallback& callback) : callback_(callback), original_thread_(base::ThreadTaskRunnerHandle::Get()) { scoped_refptr context_getter = diff --git a/atom/browser/api/atom_api_url_request.cc b/atom/browser/api/atom_api_url_request.cc index 967ae50a7e1f..d3607e7283cc 100644 --- a/atom/browser/api/atom_api_url_request.cc +++ b/atom/browser/api/atom_api_url_request.cc @@ -8,6 +8,7 @@ #include "atom/browser/net/atom_url_request.h" #include "atom/common/api/event_emitter_caller.h" #include "atom/common/native_mate_converters/callback.h" +#include "atom/common/native_mate_converters/gurl_converter.h" #include "atom/common/native_mate_converters/net_converter.h" #include "atom/common/native_mate_converters/string16_converter.h" #include "atom/common/node_includes.h" @@ -145,6 +146,8 @@ mate::WrappableBase* URLRequest::New(mate::Arguments* args) { dict.Get("method", &method); std::string url; dict.Get("url", &url); + std::string redirect_policy; + dict.Get("redirect", &redirect_policy); std::string partition; mate::Handle session; if (dict.Get("session", &session)) { @@ -156,8 +159,8 @@ mate::WrappableBase* URLRequest::New(mate::Arguments* args) { } auto browser_context = session->browser_context(); auto api_url_request = new URLRequest(args->isolate(), args->GetThis()); - auto atom_url_request = - AtomURLRequest::Create(browser_context, method, url, api_url_request); + auto atom_url_request = AtomURLRequest::Create( + browser_context, method, url, redirect_policy, api_url_request); api_url_request->atom_request_ = atom_url_request; @@ -176,6 +179,7 @@ void URLRequest::BuildPrototype(v8::Isolate* isolate, .SetMethod("setExtraHeader", &URLRequest::SetExtraHeader) .SetMethod("removeExtraHeader", &URLRequest::RemoveExtraHeader) .SetMethod("setChunkedUpload", &URLRequest::SetChunkedUpload) + .SetMethod("followRedirect", &URLRequest::FollowRedirect) .SetMethod("_setLoadFlags", &URLRequest::SetLoadFlags) .SetProperty("notStarted", &URLRequest::NotStarted) .SetProperty("finished", &URLRequest::Finished) @@ -246,6 +250,17 @@ void URLRequest::Cancel() { Close(); } +void URLRequest::FollowRedirect() { + if (request_state_.Canceled() || request_state_.Closed()) { + return; + } + + DCHECK(atom_request_); + if (atom_request_) { + atom_request_->FollowRedirect(); + } +} + bool URLRequest::SetExtraHeader(const std::string& name, const std::string& value) { // Request state must be in the initial non started state. @@ -305,6 +320,24 @@ void URLRequest::SetLoadFlags(int flags) { } } +void URLRequest::OnReceivedRedirect( + int status_code, + const std::string& method, + const GURL& url, + scoped_refptr response_headers) { + if (request_state_.Canceled() || request_state_.Closed()) { + return; + } + + DCHECK(atom_request_); + if (!atom_request_) { + return; + } + + EmitRequestEvent(false, "redirect", status_code, method, url, + response_headers.get()); +} + void URLRequest::OnAuthenticationRequired( scoped_refptr auth_info) { if (request_state_.Canceled() || request_state_.Closed()) { diff --git a/atom/browser/api/atom_api_url_request.h b/atom/browser/api/atom_api_url_request.h index c92ac01961cd..372ac98ac657 100644 --- a/atom/browser/api/atom_api_url_request.h +++ b/atom/browser/api/atom_api_url_request.h @@ -99,6 +99,11 @@ class URLRequest : public mate::EventEmitter { v8::Local prototype); // Methods for reporting events into JavaScript. + void OnReceivedRedirect( + int status_code, + const std::string& method, + const GURL& url, + scoped_refptr response_headers); void OnAuthenticationRequired( scoped_refptr auth_info); void OnResponseStarted( @@ -170,6 +175,7 @@ class URLRequest : public mate::EventEmitter { bool Failed() const; bool Write(scoped_refptr buffer, bool is_last); void Cancel(); + void FollowRedirect(); bool SetExtraHeader(const std::string& name, const std::string& value); void RemoveExtraHeader(const std::string& name); void SetChunkedUpload(bool is_chunked_upload); diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 6ba23b3c9afc..9390777f9c58 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -240,7 +240,7 @@ content::ServiceWorkerContext* GetServiceWorkerContext( } // Called when CapturePage is done. -void OnCapturePageDone(base::Callback callback, +void OnCapturePageDone(const base::Callback& callback, const SkBitmap& bitmap, content::ReadbackResponse response) { callback.Run(gfx::Image::CreateFrom1xBitmap(bitmap)); @@ -411,14 +411,18 @@ WebContents::~WebContents() { if (type_ == WEB_VIEW) guest_delegate_->Destroy(); - // The WebContentsDestroyed will not be called automatically because we - // unsubscribe from webContents before destroying it. So we have to manually - // call it here to make sure "destroyed" event is emitted. RenderViewDeleted(web_contents()->GetRenderViewHost()); - WebContentsDestroyed(); + DestroyWebContents(); } } +void WebContents::DestroyWebContents() { + // This event is only for internal use, which is emitted when WebContents is + // being destroyed. + Emit("will-destroy"); + ResetManagedWebContents(); +} + bool WebContents::DidAddMessageToConsole(content::WebContents* source, int32_t level, const base::string16& message, @@ -919,10 +923,6 @@ bool WebContents::OnMessageReceived(const IPC::Message& message) { // be destroyed on close, and WebContentsDestroyed would be called for it, so // we need to make sure the api::WebContents is also deleted. void WebContents::WebContentsDestroyed() { - // This event is only for internal use, which is emitted when WebContents is - // being destroyed. - Emit("will-destroy"); - // Cleanup relationships with other parts. RemoveFromWeakMap(); diff --git a/atom/browser/api/atom_api_web_contents.h b/atom/browser/api/atom_api_web_contents.h index 2513dc7722a8..c289503b43c9 100644 --- a/atom/browser/api/atom_api_web_contents.h +++ b/atom/browser/api/atom_api_web_contents.h @@ -76,6 +76,9 @@ class WebContents : public mate::TrackableObject, static void BuildPrototype(v8::Isolate* isolate, v8::Local prototype); + // Notifies to destroy any guest web contents before destroying self. + void DestroyWebContents(); + int64_t GetID() const; int GetProcessID() const; Type GetType() const; diff --git a/atom/browser/atom_access_token_store.cc b/atom/browser/atom_access_token_store.cc index aef54dfa0ebc..6a5597ca7780 100644 --- a/atom/browser/atom_access_token_store.cc +++ b/atom/browser/atom_access_token_store.cc @@ -7,11 +7,13 @@ #include #include -#include "atom/browser/atom_browser_context.h" #include "atom/common/google_api_key.h" #include "base/environment.h" #include "content/public/browser/browser_thread.h" #include "device/geolocation/geolocation_provider.h" +#include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_context_builder.h" +#include "net/url_request/url_request_context_getter.h" using content::BrowserThread; @@ -19,51 +21,40 @@ namespace atom { namespace internal { -// Loads access tokens and other necessary data on the UI thread, and -// calls back to the originator on the originating thread. -class TokenLoadingJob : public base::RefCountedThreadSafe { +class GeoURLRequestContextGetter : public net::URLRequestContextGetter { public: - explicit TokenLoadingJob( - const device::AccessTokenStore::LoadAccessTokensCallback& callback) - : callback_(callback), request_context_getter_(nullptr) {} + net::URLRequestContext* GetURLRequestContext() override { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + if (!url_request_context_.get()) { + net::URLRequestContextBuilder builder; + builder.set_proxy_config_service( + net::ProxyService::CreateSystemProxyConfigService( + BrowserThread::GetTaskRunnerForThread(BrowserThread::IO), + BrowserThread::GetTaskRunnerForThread(BrowserThread::FILE))); + url_request_context_ = builder.Build(); + } + return url_request_context_.get(); + } - void Run(AtomBrowserContext* browser_context) { - DCHECK_CURRENTLY_ON(BrowserThread::UI); - request_context_getter_ = browser_context->GetRequestContext(); - std::unique_ptr env(base::Environment::Create()); - if (!env->GetVar("GOOGLE_API_KEY", &api_key_)) - api_key_ = GOOGLEAPIS_API_KEY; - BrowserThread::PostTask( - BrowserThread::IO, FROM_HERE, - base::Bind(&TokenLoadingJob::RespondOnIOThread, this)); + scoped_refptr GetNetworkTaskRunner() + const override { + return BrowserThread::GetTaskRunnerForThread(BrowserThread::IO); } private: - friend class base::RefCountedThreadSafe; + friend class atom::AtomAccessTokenStore; - ~TokenLoadingJob() {} + GeoURLRequestContextGetter() {} + ~GeoURLRequestContextGetter() override {} - void RespondOnIOThread() { - // Equivalent to access_token_map[kGeolocationProviderURL]. - // Somehow base::string16 is causing compilation errors when used in a pair - // of std::map on Linux, this can work around it. - device::AccessTokenStore::AccessTokenMap access_token_map; - std::pair token_pair; - token_pair.first = GURL(GOOGLEAPIS_ENDPOINT + api_key_); - access_token_map.insert(token_pair); - - callback_.Run(access_token_map, request_context_getter_); - } - - device::AccessTokenStore::LoadAccessTokensCallback callback_; - net::URLRequestContextGetter* request_context_getter_; - std::string api_key_; + std::unique_ptr url_request_context_; + DISALLOW_COPY_AND_ASSIGN(GeoURLRequestContextGetter); }; } // namespace internal -AtomAccessTokenStore::AtomAccessTokenStore() { - browser_context_ = AtomBrowserContext::From("", false); +AtomAccessTokenStore::AtomAccessTokenStore() + : request_context_getter_(new internal::GeoURLRequestContextGetter) { device::GeolocationProvider::GetInstance()->UserDidOptIntoLocationServices(); } @@ -72,16 +63,19 @@ AtomAccessTokenStore::~AtomAccessTokenStore() { void AtomAccessTokenStore::LoadAccessTokens( const LoadAccessTokensCallback& callback) { - scoped_refptr job( - new internal::TokenLoadingJob(callback)); - BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, - base::Bind(&AtomAccessTokenStore::RunTokenLoadingJob, - this, base::RetainedRef(job))); -} + std::unique_ptr env(base::Environment::Create()); + std::string api_key; + if (!env->GetVar("GOOGLE_API_KEY", &api_key)) + api_key = GOOGLEAPIS_API_KEY; + // Equivalent to access_token_map[kGeolocationProviderURL]. + // Somehow base::string16 is causing compilation errors when used in a pair + // of std::map on Linux, this can work around it. + device::AccessTokenStore::AccessTokenMap access_token_map; + std::pair token_pair; + token_pair.first = GURL(GOOGLEAPIS_ENDPOINT + api_key); + access_token_map.insert(token_pair); -void AtomAccessTokenStore::RunTokenLoadingJob( - scoped_refptr job) { - job->Run(browser_context_.get()); + callback.Run(access_token_map, request_context_getter_.get()); } void AtomAccessTokenStore::SaveAccessToken(const GURL& server_url, diff --git a/atom/browser/atom_access_token_store.h b/atom/browser/atom_access_token_store.h index 07884e58d60a..820ceddce4fd 100644 --- a/atom/browser/atom_access_token_store.h +++ b/atom/browser/atom_access_token_store.h @@ -9,10 +9,8 @@ namespace atom { -class AtomBrowserContext; - namespace internal { -class TokenLoadingJob; +class GeoURLRequestContextGetter; } class AtomAccessTokenStore : public device::AccessTokenStore { @@ -27,9 +25,7 @@ class AtomAccessTokenStore : public device::AccessTokenStore { const base::string16& access_token) override; private: - void RunTokenLoadingJob(scoped_refptr job); - - scoped_refptr browser_context_; + scoped_refptr request_context_getter_; DISALLOW_COPY_AND_ASSIGN(AtomAccessTokenStore); }; diff --git a/atom/browser/auto_updater_mac.mm b/atom/browser/auto_updater_mac.mm index a6102f3e73fa..3802fef16299 100644 --- a/atom/browser/auto_updater_mac.mm +++ b/atom/browser/auto_updater_mac.mm @@ -27,7 +27,7 @@ namespace { bool g_update_available = false; std::string update_url_ = ""; -} +} // namespace std::string AutoUpdater::GetFeedURL() { return update_url_; diff --git a/atom/browser/browser.cc b/atom/browser/browser.cc index 9b7423cd7d9f..b18ab7b70fd7 100644 --- a/atom/browser/browser.cc +++ b/atom/browser/browser.cc @@ -44,7 +44,7 @@ void Browser::Quit() { return; atom::WindowList* window_list = atom::WindowList::GetInstance(); - if (window_list->size() == 0) + if (window_list->empty()) NotifyAndShutdown(); window_list->CloseAllWindows(); @@ -66,7 +66,7 @@ void Browser::Exit(mate::Arguments* args) { // Must destroy windows before quitting, otherwise bad things can happen. atom::WindowList* window_list = atom::WindowList::GetInstance(); - if (window_list->size() == 0) { + if (window_list->empty()) { Shutdown(); } else { // Unlike Quit(), we do not ask to close window, but destroy the window diff --git a/atom/browser/browser.h b/atom/browser/browser.h index 28103a99c704..78cac65f7e40 100644 --- a/atom/browser/browser.h +++ b/atom/browser/browser.h @@ -102,7 +102,7 @@ class Browser : public WindowListObserver { std::vector args; }; void SetLoginItemSettings(LoginItemSettings settings); - LoginItemSettings GetLoginItemSettings(LoginItemSettings options); + LoginItemSettings GetLoginItemSettings(const LoginItemSettings& options); #if defined(OS_MACOSX) // Hide the application. diff --git a/atom/browser/browser_linux.cc b/atom/browser/browser_linux.cc index 6abfcf5c3481..4dd799dfaf02 100644 --- a/atom/browser/browser_linux.cc +++ b/atom/browser/browser_linux.cc @@ -64,7 +64,7 @@ void Browser::SetLoginItemSettings(LoginItemSettings settings) { } Browser::LoginItemSettings Browser::GetLoginItemSettings( - LoginItemSettings options) { + const LoginItemSettings& options) { return LoginItemSettings(); } diff --git a/atom/browser/browser_mac.mm b/atom/browser/browser_mac.mm index c318cf850701..51604e377cd6 100644 --- a/atom/browser/browser_mac.mm +++ b/atom/browser/browser_mac.mm @@ -64,8 +64,9 @@ bool Browser::RemoveAsDefaultProtocolClient(const std::string& protocol, // On macOS, we can't query the default, but the handlers list seems to put // Apple's defaults first, so we'll use the first option that isn't our bundle CFStringRef other = nil; - for (CFIndex i = 0; i < CFArrayGetCount(bundleList); i++) { - other = (CFStringRef)CFArrayGetValueAtIndex(bundleList, i); + for (CFIndex i = 0; i < CFArrayGetCount(bundleList); ++i) { + other = base::mac::CFCast(CFArrayGetValueAtIndex(bundleList, + i)); if (![identifier isEqualToString: (__bridge NSString *)other]) { break; } @@ -152,7 +153,7 @@ bool Browser::ContinueUserActivity(const std::string& type, } Browser::LoginItemSettings Browser::GetLoginItemSettings( - LoginItemSettings options) { + const LoginItemSettings& options) { LoginItemSettings settings; settings.open_at_login = base::mac::CheckLoginItemStatus( &settings.open_as_hidden); @@ -179,7 +180,7 @@ std::string Browser::GetExecutableFileProductName() const { int Browser::DockBounce(BounceType type) { return [[AtomApplication sharedApplication] - requestUserAttention:(NSRequestUserAttentionType)type]; + requestUserAttention:static_cast(type)]; } void Browser::DockCancelBounce(int request_id) { diff --git a/atom/browser/browser_win.cc b/atom/browser/browser_win.cc index 85990bbc67e3..50a9f9da330e 100644 --- a/atom/browser/browser_win.cc +++ b/atom/browser/browser_win.cc @@ -287,7 +287,7 @@ void Browser::SetLoginItemSettings(LoginItemSettings settings) { } Browser::LoginItemSettings Browser::GetLoginItemSettings( - LoginItemSettings options) { + const LoginItemSettings& options) { LoginItemSettings settings; base::string16 keyPath = L"Software\\Microsoft\\Windows\\CurrentVersion\\Run"; base::win::RegKey key(HKEY_CURRENT_USER, keyPath.c_str(), KEY_ALL_ACCESS); diff --git a/atom/browser/common_web_contents_delegate.cc b/atom/browser/common_web_contents_delegate.cc index 89c1e372558c..f7f9e46250eb 100644 --- a/atom/browser/common_web_contents_delegate.cc +++ b/atom/browser/common_web_contents_delegate.cc @@ -183,7 +183,7 @@ void CommonWebContentsDelegate::SetOwnerWindow( web_contents->SetUserData(relay->key, relay); } -void CommonWebContentsDelegate::DestroyWebContents() { +void CommonWebContentsDelegate::ResetManagedWebContents() { web_contents_.reset(); } @@ -338,7 +338,7 @@ void CommonWebContentsDelegate::DevToolsRequestFileSystems() { } std::vector file_systems; - for (auto file_system_path : file_system_paths) { + for (const auto& file_system_path : file_system_paths) { base::FilePath path = base::FilePath::FromUTF8Unsafe(file_system_path); std::string file_system_id = RegisterFileSystem(GetDevToolsWebContents(), path); diff --git a/atom/browser/common_web_contents_delegate.h b/atom/browser/common_web_contents_delegate.h index c08f8d246cee..27209411c72b 100644 --- a/atom/browser/common_web_contents_delegate.h +++ b/atom/browser/common_web_contents_delegate.h @@ -42,9 +42,6 @@ class CommonWebContentsDelegate void SetOwnerWindow(content::WebContents* web_contents, NativeWindow* owner_window); - // Destroy the managed InspectableWebContents object. - void DestroyWebContents(); - // Returns the WebContents managed by this delegate. content::WebContents* GetWebContents() const; @@ -114,6 +111,9 @@ class CommonWebContentsDelegate std::string* name, std::string* class_name) override; #endif + // Destroy the managed InspectableWebContents object. + void ResetManagedWebContents(); + private: // Callback for when DevToolsSaveToFile has completed. void OnDevToolsSaveToFile(const std::string& url); diff --git a/atom/browser/mac/atom_application_delegate.mm b/atom/browser/mac/atom_application_delegate.mm index 9e245f99078d..4c6a938fba59 100644 --- a/atom/browser/mac/atom_application_delegate.mm +++ b/atom/browser/mac/atom_application_delegate.mm @@ -10,10 +10,6 @@ #include "base/strings/sys_string_conversions.h" #include "base/values.h" -@interface NSWindow (SierraSDK) -@property(class) BOOL allowsAutomaticWindowTabbing; -@end - @implementation AtomApplicationDelegate - (void)setApplicationDockMenu:(atom::AtomMenuModel*)model { @@ -25,10 +21,6 @@ // Don't add the "Enter Full Screen" menu item automatically. [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"NSFullScreenMenuItemEverywhere"]; - // Don't add the "Show Tab Bar" menu item. - if ([NSWindow respondsToSelector:@selector(allowsAutomaticWindowTabbing)]) - NSWindow.allowsAutomaticWindowTabbing = NO; - atom::Browser::Get()->WillFinishLaunching(); } diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index c88469a9c8a1..621e786ddc0d 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -336,6 +336,19 @@ bool ScopedDisableResize::disable_resize_ = false; @end +#if !defined(MAC_OS_X_VERSION_10_12) + +enum { + NSWindowTabbingModeDisallowed = 2 +}; + +@interface NSWindow (SierraSDK) +- (void)setTabbingMode:(NSInteger)mode; +- (void)setTabbingIdentifier:(NSString*)identifier; +@end + +#endif // MAC_OS_X_VERSION_10_12 + @interface AtomNSWindow : EventDispatchingWindow { @private atom::NativeWindowMac* shell_; @@ -682,6 +695,9 @@ NativeWindowMac::NativeWindowMac( options.Get(options::kTitleBarStyle, &title_bar_style_); + std::string tabbingIdentifier; + options.Get(options::kTabbingIdentifier, &tabbingIdentifier); + std::string windowType; options.Get(options::kType, &windowType); @@ -754,6 +770,18 @@ NativeWindowMac::NativeWindowMac( [window_ setOpaque:NO]; } + // Create a tab only if tabbing identifier is specified and window has + // a native title bar. + if (tabbingIdentifier.empty() || transparent() || !has_frame()) { + if ([window_ respondsToSelector:@selector(tabbingMode)]) { + [window_ setTabbingMode:NSWindowTabbingModeDisallowed]; + } + } else { + if ([window_ respondsToSelector:@selector(tabbingIdentifier)]) { + [window_ setTabbingIdentifier:base::SysUTF8ToNSString(tabbingIdentifier)]; + } + } + // We will manage window's lifetime ourselves. [window_ setReleasedWhenClosed:NO]; @@ -1262,7 +1290,7 @@ void NativeWindowMac::SetProgressBar(double progress, const NativeWindow::Progre NSDockTile* dock_tile = [NSApp dockTile]; // For the first time API invoked, we need to create a ContentView in DockTile. - if (dock_tile.contentView == NULL) { + if (dock_tile.contentView == nullptr) { NSImageView* image_view = [[NSImageView alloc] init]; [image_view setImage:[NSApp applicationIconImage]]; [dock_tile setContentView:image_view]; @@ -1358,22 +1386,22 @@ void NativeWindowMac::SetVibrancy(const std::string& type) { // they are available in the minimum SDK version if (type == "selection") { // NSVisualEffectMaterialSelection - vibrancyType = (NSVisualEffectMaterial) 4; + vibrancyType = static_cast(4); } else if (type == "menu") { // NSVisualEffectMaterialMenu - vibrancyType = (NSVisualEffectMaterial) 5; + vibrancyType = static_cast(5); } else if (type == "popover") { // NSVisualEffectMaterialPopover - vibrancyType = (NSVisualEffectMaterial) 6; + vibrancyType = static_cast(6); } else if (type == "sidebar") { // NSVisualEffectMaterialSidebar - vibrancyType = (NSVisualEffectMaterial) 7; + vibrancyType = static_cast(7); } else if (type == "medium-light") { // NSVisualEffectMaterialMediumLight - vibrancyType = (NSVisualEffectMaterial) 8; + vibrancyType = static_cast(8); } else if (type == "ultra-dark") { // NSVisualEffectMaterialUltraDark - vibrancyType = (NSVisualEffectMaterial) 9; + vibrancyType = static_cast(9); } } diff --git a/atom/browser/net/atom_cert_verifier.cc b/atom/browser/net/atom_cert_verifier.cc index 5dee107eb36e..2a2229f19d1f 100644 --- a/atom/browser/net/atom_cert_verifier.cc +++ b/atom/browser/net/atom_cert_verifier.cc @@ -50,7 +50,7 @@ class CertVerifierRequest : public AtomCertVerifier::Request { first_response_(true), weak_ptr_factory_(this) {} - ~CertVerifierRequest() { + ~CertVerifierRequest() override { cert_verifier_->RemoveRequest(params_); default_verifier_request_.reset(); while (!response_list_.empty() && !first_response_) { diff --git a/atom/browser/net/atom_network_delegate.cc b/atom/browser/net/atom_network_delegate.cc index 2ba3f8e35781..3a5b667f06d0 100644 --- a/atom/browser/net/atom_network_delegate.cc +++ b/atom/browser/net/atom_network_delegate.cc @@ -402,7 +402,7 @@ void AtomNetworkDelegate::OnListenerResultInIO( if (!base::ContainsKey(callbacks_, id)) return; - ReadFromResponseObject(*response.get(), out); + ReadFromResponseObject(*response, out); bool cancel = false; response->GetBoolean("cancel", &cancel); diff --git a/atom/browser/net/atom_url_request.cc b/atom/browser/net/atom_url_request.cc index 2c7bb61da0b1..9400f361f11a 100644 --- a/atom/browser/net/atom_url_request.cc +++ b/atom/browser/net/atom_url_request.cc @@ -13,6 +13,7 @@ #include "net/base/io_buffer.h" #include "net/base/load_flags.h" #include "net/base/upload_bytes_element_reader.h" +#include "net/url_request/redirect_info.h" namespace { const int kBufferSize = 4096; @@ -58,6 +59,7 @@ scoped_refptr AtomURLRequest::Create( AtomBrowserContext* browser_context, const std::string& method, const std::string& url, + const std::string& redirect_policy, api::URLRequest* delegate) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); @@ -76,7 +78,7 @@ scoped_refptr AtomURLRequest::Create( if (content::BrowserThread::PostTask( content::BrowserThread::IO, FROM_HERE, base::Bind(&AtomURLRequest::DoInitialize, atom_url_request, - request_context_getter, method, url))) { + request_context_getter, method, url, redirect_policy))) { return atom_url_request; } return nullptr; @@ -93,10 +95,12 @@ void AtomURLRequest::Terminate() { void AtomURLRequest::DoInitialize( scoped_refptr request_context_getter, const std::string& method, - const std::string& url) { + const std::string& url, + const std::string& redirect_policy) { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); DCHECK(request_context_getter); + redirect_policy_ = redirect_policy; request_context_getter_ = request_context_getter; request_context_getter_->AddObserver(this); auto context = request_context_getter_->GetURLRequestContext(); @@ -150,6 +154,13 @@ void AtomURLRequest::Cancel() { base::Bind(&AtomURLRequest::DoCancel, this)); } +void AtomURLRequest::FollowRedirect() { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + content::BrowserThread::PostTask( + content::BrowserThread::IO, FROM_HERE, + base::Bind(&AtomURLRequest::DoFollowRedirect, this)); +} + void AtomURLRequest::SetExtraHeader(const std::string& name, const std::string& value) const { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); @@ -246,6 +257,13 @@ void AtomURLRequest::DoCancel() { DoTerminate(); } +void AtomURLRequest::DoFollowRedirect() { + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + if (request_ && request_->is_redirecting() && redirect_policy_ == "manual") { + request_->FollowDeferredRedirect(); + } +} + void AtomURLRequest::DoSetExtraHeader(const std::string& name, const std::string& value) const { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); @@ -297,6 +315,29 @@ void AtomURLRequest::DoSetLoadFlags(int flags) const { request_->SetLoadFlags(request_->load_flags() | flags); } +void AtomURLRequest::OnReceivedRedirect(net::URLRequest* request, + const net::RedirectInfo& info, + bool* defer_redirect) { + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + if (!request_ || redirect_policy_ == "follow") + return; + + if (redirect_policy_ == "error") { + request->Cancel(); + DoCancelWithError( + "Request cannot follow redirect with the current redirect mode", true); + } else if (redirect_policy_ == "manual") { + *defer_redirect = true; + scoped_refptr response_headers = + request->response_headers(); + content::BrowserThread::PostTask( + content::BrowserThread::UI, FROM_HERE, + base::Bind(&AtomURLRequest::InformDelegateReceivedRedirect, this, + info.status_code, info.new_method, info.new_url, + response_headers)); + } +} + void AtomURLRequest::OnAuthRequired(net::URLRequest* request, net::AuthChallengeInfo* auth_info) { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); @@ -348,6 +389,14 @@ void AtomURLRequest::OnReadCompleted(net::URLRequest* request, int bytes_read) { DCHECK_EQ(request, request_.get()); const auto status = request_->status(); + if (status.error() == bytes_read && + bytes_read == net::ERR_CONTENT_DECODING_INIT_FAILED) { + // When the request job is unable to create a source stream for the + // content encoding, we fail the request. + DoCancelWithError(net::ErrorToString(net::ERR_CONTENT_DECODING_INIT_FAILED), + true); + return; + } bool response_error = false; bool data_ended = false; @@ -399,6 +448,16 @@ bool AtomURLRequest::CopyAndPostBuffer(int bytes_read) { buffer_copy)); } +void AtomURLRequest::InformDelegateReceivedRedirect( + int status_code, + const std::string& method, + const GURL& url, + scoped_refptr response_headers) const { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + if (delegate_) + delegate_->OnReceivedRedirect(status_code, method, url, response_headers); +} + void AtomURLRequest::InformDelegateAuthenticationRequired( scoped_refptr auth_info) const { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); diff --git a/atom/browser/net/atom_url_request.h b/atom/browser/net/atom_url_request.h index db00390b955c..654798d8aac8 100644 --- a/atom/browser/net/atom_url_request.h +++ b/atom/browser/net/atom_url_request.h @@ -30,12 +30,14 @@ class AtomURLRequest : public base::RefCountedThreadSafe, AtomBrowserContext* browser_context, const std::string& method, const std::string& url, + const std::string& redirect_policy, api::URLRequest* delegate); void Terminate(); bool Write(scoped_refptr buffer, bool is_last); void SetChunkedUpload(bool is_chunked_upload); void Cancel(); + void FollowRedirect(); void SetExtraHeader(const std::string& name, const std::string& value) const; void RemoveExtraHeader(const std::string& name) const; void PassLoginInformation(const base::string16& username, @@ -44,6 +46,9 @@ class AtomURLRequest : public base::RefCountedThreadSafe, protected: // Overrides of net::URLRequest::Delegate + void OnReceivedRedirect(net::URLRequest* request, + const net::RedirectInfo& info, + bool* defer_redirect) override; void OnAuthRequired(net::URLRequest* request, net::AuthChallengeInfo* auth_info) override; void OnResponseStarted(net::URLRequest* request) override; @@ -60,11 +65,13 @@ class AtomURLRequest : public base::RefCountedThreadSafe, void DoInitialize(scoped_refptr, const std::string& method, - const std::string& url); + const std::string& url, + const std::string& redirect_policy); void DoTerminate(); void DoWriteBuffer(scoped_refptr buffer, bool is_last); void DoCancel(); + void DoFollowRedirect(); void DoSetExtraHeader(const std::string& name, const std::string& value) const; void DoRemoveExtraHeader(const std::string& name) const; @@ -77,6 +84,11 @@ class AtomURLRequest : public base::RefCountedThreadSafe, void ReadResponse(); bool CopyAndPostBuffer(int bytes_read); + void InformDelegateReceivedRedirect( + int status_code, + const std::string& method, + const GURL& url, + scoped_refptr response_headers) const; void InformDelegateAuthenticationRequired( scoped_refptr auth_info) const; void InformDelegateResponseStarted( @@ -92,6 +104,7 @@ class AtomURLRequest : public base::RefCountedThreadSafe, scoped_refptr request_context_getter_; bool is_chunked_upload_; + std::string redirect_policy_; std::unique_ptr chunked_stream_; std::unique_ptr chunked_stream_writer_; std::vector> diff --git a/atom/browser/node_debugger.cc b/atom/browser/node_debugger.cc index e95369fba190..9fdeb6099e6a 100644 --- a/atom/browser/node_debugger.cc +++ b/atom/browser/node_debugger.cc @@ -164,7 +164,7 @@ void NodeDebugger::DidRead(net::test_server::StreamListenSocket* socket, buffer_.append(data, len); do { - if (buffer_.size() == 0) + if (buffer_.empty()) return; // Read the "Content-Length" header. diff --git a/atom/browser/osr/osr_render_widget_host_view.cc b/atom/browser/osr/osr_render_widget_host_view.cc index 7355f1a0c1e2..2003118f389e 100644 --- a/atom/browser/osr/osr_render_widget_host_view.cc +++ b/atom/browser/osr/osr_render_widget_host_view.cc @@ -851,12 +851,12 @@ void OffScreenRenderWidgetHostView::SetupFrameRate(bool force) { GetCompositor()->vsync_manager()->SetAuthoritativeVSyncInterval( base::TimeDelta::FromMilliseconds(frame_rate_threshold_ms_)); - if (copy_frame_generator_.get()) { + if (copy_frame_generator_) { copy_frame_generator_->set_frame_rate_threshold_ms( frame_rate_threshold_ms_); } - if (begin_frame_timer_.get()) { + if (begin_frame_timer_) { begin_frame_timer_->SetFrameRateThresholdMs(frame_rate_threshold_ms_); } else { begin_frame_timer_.reset(new AtomBeginFrameTimer( @@ -871,7 +871,7 @@ void OffScreenRenderWidgetHostView::Invalidate() { if (software_output_device_) { software_output_device_->OnPaint(bounds_in_pixels); - } else if (copy_frame_generator_.get()) { + } else if (copy_frame_generator_) { copy_frame_generator_->GenerateCopyFrame(true, bounds_in_pixels); } } diff --git a/atom/browser/osr/osr_render_widget_host_view_mac.mm b/atom/browser/osr/osr_render_widget_host_view_mac.mm index 664261947d1d..7cf010ff8e28 100644 --- a/atom/browser/osr/osr_render_widget_host_view_mac.mm +++ b/atom/browser/osr/osr_render_widget_host_view_mac.mm @@ -145,4 +145,4 @@ OffScreenRenderWidgetHostView::GetDelegatedFrameHost() const { return browser_compositor_->GetDelegatedFrameHost(); } -} // namespace +} // namespace atom diff --git a/atom/browser/resources/mac/Info.plist b/atom/browser/resources/mac/Info.plist index 091614ffd428..6fc30bf6a3b3 100644 --- a/atom/browser/resources/mac/Info.plist +++ b/atom/browser/resources/mac/Info.plist @@ -17,9 +17,9 @@ CFBundleIconFile electron.icns CFBundleVersion - 1.6.4 + 1.6.5 CFBundleShortVersionString - 1.6.4 + 1.6.5 LSApplicationCategoryType public.app-category.developer-tools LSMinimumSystemVersion diff --git a/atom/browser/resources/win/atom.rc b/atom/browser/resources/win/atom.rc index 5a2d8ea32ece..34879fe41ec9 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 1,6,4,0 - PRODUCTVERSION 1,6,4,0 + FILEVERSION 1,6,5,0 + PRODUCTVERSION 1,6,5,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -74,12 +74,12 @@ BEGIN BEGIN VALUE "CompanyName", "GitHub, Inc." VALUE "FileDescription", "Electron" - VALUE "FileVersion", "1.6.4" + VALUE "FileVersion", "1.6.5" VALUE "InternalName", "electron.exe" VALUE "LegalCopyright", "Copyright (C) 2015 GitHub, Inc. All rights reserved." VALUE "OriginalFilename", "electron.exe" VALUE "ProductName", "Electron" - VALUE "ProductVersion", "1.6.4" + VALUE "ProductVersion", "1.6.5" VALUE "SquirrelAwareVersion", "1" END END diff --git a/atom/browser/ui/cocoa/atom_menu_controller.mm b/atom/browser/ui/cocoa/atom_menu_controller.mm index b3e293153f96..d0bbf6a153a3 100644 --- a/atom/browser/ui/cocoa/atom_menu_controller.mm +++ b/atom/browser/ui/cocoa/atom_menu_controller.mm @@ -70,7 +70,7 @@ Role kRolesMap[] = { // while its context menu is still open. [self cancel]; - model_ = NULL; + model_ = nullptr; [super dealloc]; } diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index 4952e28876c6..69926684b67a 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -319,7 +319,7 @@ static NSString* const ImageScrubberItemIdentifier = @"scrubber.image.item"; - (void)updateColorPicker:(NSColorPickerTouchBarItem*)item withSettings:(const mate::PersistentDictionary&)settings { std::vector colors; - if (settings.Get("availableColors", &colors) && colors.size() > 0) { + if (settings.Get("availableColors", &colors) && !colors.empty()) { NSColorList* color_list = [[[NSColorList alloc] initWithName:@""] autorelease]; for (size_t i = 0; i < colors.size(); ++i) { [color_list insertColor:[self colorFromHexColorString:colors[i]] @@ -414,7 +414,7 @@ static NSString* const ImageScrubberItemIdentifier = @"scrubber.image.item"; NSMutableArray* generatedItems = [NSMutableArray array]; NSMutableArray* identifiers = [self identifiersFromSettings:items]; - for (NSUInteger i = 0; i < [identifiers count]; i++) { + for (NSUInteger i = 0; i < [identifiers count]; ++i) { if ([identifiers objectAtIndex:i] != NSTouchBarItemIdentifierOtherItemsProxy) { NSTouchBarItem* generatedItem = [self makeItemForIdentifier:[identifiers objectAtIndex:i]]; if (generatedItem) { @@ -474,7 +474,7 @@ static NSString* const ImageScrubberItemIdentifier = @"scrubber.image.item"; settings.Get("segments", &segments); control.segmentCount = segments.size(); - for (int i = 0; i < (int)segments.size(); i++) { + for (size_t i = 0; i < segments.size(); ++i) { std::string label; gfx::Image image; bool enabled = true; @@ -581,7 +581,7 @@ static NSString* const ImageScrubberItemIdentifier = @"scrubber.image.item"; std::vector items; if (!settings.Get("items", &items)) return nil; - if (index >= (long)items.size()) return nil; + if (index >= static_cast(items.size())) return nil; mate::PersistentDictionary item = items[index]; diff --git a/atom/browser/ui/file_dialog_mac.mm b/atom/browser/ui/file_dialog_mac.mm index 70272cbddc8f..80143a987bc7 100644 --- a/atom/browser/ui/file_dialog_mac.mm +++ b/atom/browser/ui/file_dialog_mac.mm @@ -154,7 +154,7 @@ void ShowOpenDialog(const DialogSettings& settings, NSWindow* window = settings.parent_window ? settings.parent_window->GetNativeWindow() : - NULL; + nullptr; [dialog beginSheetModalForWindow:window completionHandler:^(NSInteger chosen) { if (chosen == NSFileHandlingPanelCancelButton) { @@ -193,7 +193,7 @@ void ShowSaveDialog(const DialogSettings& settings, NSWindow* window = settings.parent_window ? settings.parent_window->GetNativeWindow() : - NULL; + nullptr; [dialog beginSheetModalForWindow:window completionHandler:^(NSInteger chosen) { if (chosen == NSFileHandlingPanelCancelButton) { diff --git a/atom/browser/ui/views/submenu_button.cc b/atom/browser/ui/views/submenu_button.cc index b398d84deec8..92f2a791cd09 100644 --- a/atom/browser/ui/views/submenu_button.cc +++ b/atom/browser/ui/views/submenu_button.cc @@ -16,21 +16,10 @@ namespace atom { -namespace { - -// Filter out the "&" in menu label. -base::string16 FilterAccelerator(const base::string16& label) { - base::string16 out; - base::RemoveChars(label, base::ASCIIToUTF16("&").c_str(), &out); - return out; -} - -} // namespace - SubmenuButton::SubmenuButton(const base::string16& title, views::MenuButtonListener* menu_button_listener, const SkColor& background_color) - : views::MenuButton(FilterAccelerator(title), + : views::MenuButton(gfx::RemoveAcceleratorChar(title, '&', NULL, NULL), menu_button_listener, false), accelerator_(0), show_underline_(false), diff --git a/atom/browser/web_contents_permission_helper.cc b/atom/browser/web_contents_permission_helper.cc index 2c79270591b8..ec9e4ad6e941 100644 --- a/atom/browser/web_contents_permission_helper.cc +++ b/atom/browser/web_contents_permission_helper.cc @@ -66,8 +66,9 @@ void WebContentsPermissionHelper::RequestPermission( void WebContentsPermissionHelper::RequestFullscreenPermission( const base::Callback& callback) { - RequestPermission((content::PermissionType)(PermissionType::FULLSCREEN), - callback); + RequestPermission( + static_cast(PermissionType::FULLSCREEN), + callback); } void WebContentsPermissionHelper::RequestMediaAccessPermission( @@ -86,17 +87,17 @@ void WebContentsPermissionHelper::RequestWebNotificationPermission( void WebContentsPermissionHelper::RequestPointerLockPermission( bool user_gesture) { - RequestPermission((content::PermissionType)(PermissionType::POINTER_LOCK), - base::Bind(&OnPointerLockResponse, web_contents_), - user_gesture); + RequestPermission( + static_cast(PermissionType::POINTER_LOCK), + base::Bind(&OnPointerLockResponse, web_contents_), user_gesture); } void WebContentsPermissionHelper::RequestOpenExternalPermission( const base::Callback& callback, bool user_gesture) { - RequestPermission((content::PermissionType)(PermissionType::OPEN_EXTERNAL), - callback, - user_gesture); + RequestPermission( + static_cast(PermissionType::OPEN_EXTERNAL), + callback, user_gesture); } } // namespace atom diff --git a/atom/browser/web_dialog_helper.cc b/atom/browser/web_dialog_helper.cc index 448fccd31f5a..fcd598b1aaea 100644 --- a/atom/browser/web_dialog_helper.cc +++ b/atom/browser/web_dialog_helper.cc @@ -51,7 +51,7 @@ class FileSelectHelper : public base::RefCounted, private: friend class base::RefCounted; - ~FileSelectHelper() {} + ~FileSelectHelper() override {} void OnOpenDialogDone(bool result, const std::vector& paths) { std::vector file_info; diff --git a/atom/browser/window_list.cc b/atom/browser/window_list.cc index b835e07e752a..d627f6d1206b 100644 --- a/atom/browser/window_list.cc +++ b/atom/browser/window_list.cc @@ -46,7 +46,7 @@ void WindowList::RemoveWindow(NativeWindow* window) { for (WindowListObserver& observer : observers_.Get()) observer.OnWindowRemoved(window); - if (windows.size() == 0) { + if (windows.empty()) { for (WindowListObserver& observer : observers_.Get()) observer.OnWindowAllClosed(); } diff --git a/atom/common/atom_version.h b/atom/common/atom_version.h index 73528b45eaae..1054a3dc9f1e 100644 --- a/atom/common/atom_version.h +++ b/atom/common/atom_version.h @@ -7,7 +7,7 @@ #define ATOM_MAJOR_VERSION 1 #define ATOM_MINOR_VERSION 6 -#define ATOM_PATCH_VERSION 4 +#define ATOM_PATCH_VERSION 5 #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 25969dc32eb3..a7908ef30c6b 100644 --- a/atom/common/crash_reporter/crash_reporter_win.cc +++ b/atom/common/crash_reporter/crash_reporter_win.cc @@ -179,7 +179,7 @@ void CrashReporterWin::InitBreakpad(const std::string& product_name, google_breakpad::ExceptionHandler::HANDLER_ALL, kSmallDumpType, pipe_name.c_str(), - GetCustomInfo(product_name, version, company_name))); + GetCustomInfo(product_name, version, company_name, upload_to_server))); if (!breakpad_->IsOutOfProcess()) LOG(ERROR) << "Cannot initialize out-of-process crash handler"; @@ -238,14 +238,19 @@ bool CrashReporterWin::MinidumpCallback(const wchar_t* dump_path, google_breakpad::CustomClientInfo* CrashReporterWin::GetCustomInfo( const std::string& product_name, const std::string& version, - const std::string& company_name) { + const std::string& company_name, + bool upload_to_server) { custom_info_entries_.clear(); - custom_info_entries_.reserve(2 + upload_parameters_.size()); + custom_info_entries_.reserve(3 + upload_parameters_.size()); custom_info_entries_.push_back(google_breakpad::CustomInfoEntry( L"prod", L"Electron")); custom_info_entries_.push_back(google_breakpad::CustomInfoEntry( L"ver", base::UTF8ToWide(version).c_str())); + if (!upload_to_server) { + custom_info_entries_.push_back(google_breakpad::CustomInfoEntry( + L"skip_upload", L"1")); + } for (StringMap::const_iterator iter = upload_parameters_.begin(); iter != upload_parameters_.end(); ++iter) { diff --git a/atom/common/crash_reporter/crash_reporter_win.h b/atom/common/crash_reporter/crash_reporter_win.h index 3fc139aca190..5070df206006 100644 --- a/atom/common/crash_reporter/crash_reporter_win.h +++ b/atom/common/crash_reporter/crash_reporter_win.h @@ -56,7 +56,8 @@ class CrashReporterWin : public CrashReporter { google_breakpad::CustomClientInfo* GetCustomInfo( const std::string& product_name, const std::string& version, - const std::string& company_name); + const std::string& company_name, + bool upload_to_server); // Custom information to be passed to crash handler. std::vector custom_info_entries_; diff --git a/atom/common/crash_reporter/win/crash_service.cc b/atom/common/crash_reporter/win/crash_service.cc index 5782fd72a3fb..a306a567a711 100644 --- a/atom/common/crash_reporter/win/crash_service.cc +++ b/atom/common/crash_reporter/win/crash_service.cc @@ -391,7 +391,7 @@ void CrashService::OnClientDumpRequest(void* context, LOG(ERROR) << "could not write custom info file"; } - if (!self->sender_) + if (!self->sender_ || map.find(L"skip_upload") != map.end()) return; // Send the crash dump using a worker thread. This operation has retry diff --git a/atom/common/native_mate_converters/content_converter.cc b/atom/common/native_mate_converters/content_converter.cc index 72f1011a72c3..7869e37deeea 100644 --- a/atom/common/native_mate_converters/content_converter.cc +++ b/atom/common/native_mate_converters/content_converter.cc @@ -168,11 +168,13 @@ v8::Local Converter::ToV8( break; } - if (val == (content::PermissionType)(PermissionType::POINTER_LOCK)) + if (val == static_cast(PermissionType::POINTER_LOCK)) return StringToV8(isolate, "pointerLock"); - else if (val == (content::PermissionType)(PermissionType::FULLSCREEN)) + else if (val == + static_cast(PermissionType::FULLSCREEN)) return StringToV8(isolate, "fullscreen"); - else if (val == (content::PermissionType)(PermissionType::OPEN_EXTERNAL)) + else if (val == + static_cast(PermissionType::OPEN_EXTERNAL)) return StringToV8(isolate, "openExternal"); return StringToV8(isolate, "unknown"); diff --git a/atom/common/node_bindings.cc b/atom/common/node_bindings.cc index 314a7b9c0330..f4696773dad6 100644 --- a/atom/common/node_bindings.cc +++ b/atom/common/node_bindings.cc @@ -232,6 +232,12 @@ void NodeBindings::RunMessageLoop() { void NodeBindings::UvRunOnce() { node::Environment* env = uv_env(); + // When doing navigation without restarting renderer process, it may happen + // that the node environment is destroyed but the message loop is still there. + // In this case we should not run uv loop. + if (!env) + return; + // Use Locker in browser process. mate::Locker locker(env->isolate()); v8::HandleScope handle_scope(env->isolate()); diff --git a/atom/common/options_switches.cc b/atom/common/options_switches.cc index e1361d223ed3..7613f2988ece 100644 --- a/atom/common/options_switches.cc +++ b/atom/common/options_switches.cc @@ -51,6 +51,9 @@ const char kZoomToPageWidth[] = "zoomToPageWidth"; // The requested title bar style for the window const char kTitleBarStyle[] = "titleBarStyle"; +// Tabbing identifier for the window if native tabs are enabled on macOS. +const char kTabbingIdentifier[] = "tabbingIdentifier"; + // The menu bar is hidden unless "Alt" is pressed. const char kAutoHideMenuBar[] = "autoHideMenuBar"; diff --git a/atom/common/options_switches.h b/atom/common/options_switches.h index 31ebd5902617..6a309c5dcfa7 100644 --- a/atom/common/options_switches.h +++ b/atom/common/options_switches.h @@ -36,6 +36,7 @@ extern const char kAcceptFirstMouse[]; extern const char kUseContentSize[]; extern const char kZoomToPageWidth[]; extern const char kTitleBarStyle[]; +extern const char kTabbingIdentifier[]; extern const char kAutoHideMenuBar[]; extern const char kEnableLargerThanScreen[]; extern const char kDarkTheme[]; diff --git a/atom/common/platform_util_linux.cc b/atom/common/platform_util_linux.cc index 923adbd882be..5c0eaecdd1c2 100644 --- a/atom/common/platform_util_linux.cc +++ b/atom/common/platform_util_linux.cc @@ -7,7 +7,9 @@ #include #include "base/cancelable_callback.h" +#include "base/environment.h" #include "base/files/file_util.h" +#include "base/nix/xdg_util.h" #include "base/process/kill.h" #include "base/process/launch.h" #include "url/gurl.h" @@ -100,7 +102,18 @@ bool MoveItemToTrash(const base::FilePath& full_path) { if (getenv(ELECTRON_TRASH) != NULL) { trash = getenv(ELECTRON_TRASH); } else { - trash = ELECTRON_DEFAULT_TRASH; + // Determine desktop environment and set accordingly. + std::unique_ptr env(base::Environment::Create()); + base::nix::DesktopEnvironment desktop_env( + base::nix::GetDesktopEnvironment(env.get())); + if (desktop_env == base::nix::DESKTOP_ENVIRONMENT_KDE4 || + desktop_env == base::nix::DESKTOP_ENVIRONMENT_KDE5) { + trash = "kioclient5"; + } else if (desktop_env == base::nix::DESKTOP_ENVIRONMENT_KDE3) { + trash = "kioclient"; + } else { + trash = ELECTRON_DEFAULT_TRASH; + } } std::vector argv; diff --git a/atom/common/platform_util_mac.mm b/atom/common/platform_util_mac.mm index aa64678caffe..7259b3b13031 100644 --- a/atom/common/platform_util_mac.mm +++ b/atom/common/platform_util_mac.mm @@ -11,6 +11,7 @@ #include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/logging.h" +#include "base/mac/foundation_util.h" #include "base/mac/mac_logging.h" #include "base/mac/scoped_aedesc.h" #include "base/strings/stringprintf.h" @@ -71,10 +72,10 @@ std::string MessageForOSStatus(OSStatus status, const char* default_message) { // thread safe, including LSGetApplicationForURL (> 10.2) and // NSWorkspace#openURLs. std::string OpenURL(NSURL* ns_url, bool activate) { - CFURLRef openingApp = NULL; - OSStatus status = LSGetApplicationForURL((CFURLRef)ns_url, + CFURLRef openingApp = nullptr; + OSStatus status = LSGetApplicationForURL(base::mac::NSToCFCast(ns_url), kLSRolesAll, - NULL, + nullptr, &openingApp); if (status != noErr) return MessageForOSStatus(status, "Failed to open"); @@ -156,7 +157,7 @@ bool OpenItem(const base::FilePath& full_path) { // Create the list of files (only ever one) to open. base::mac::ScopedAEDesc fileList; - status = AECreateList(NULL, // factoringPtr + status = AECreateList(nullptr, // factoringPtr 0, // factoredSize false, // isRecord fileList.OutPointer()); // resultList @@ -167,7 +168,8 @@ bool OpenItem(const base::FilePath& full_path) { // Add the single path to the file list. C-style cast to avoid both a // static_cast and a const_cast to get across the toll-free bridge. - CFURLRef pathURLRef = (CFURLRef)[NSURL fileURLWithPath:path_string]; + CFURLRef pathURLRef = base::mac::NSToCFCast( + [NSURL fileURLWithPath:path_string]); FSRef pathRef; if (CFURLGetFSRef(pathURLRef, &pathRef)) { status = AEPutPtr(fileList.OutPointer(), // theAEDescList @@ -202,8 +204,8 @@ bool OpenItem(const base::FilePath& full_path) { kAENoReply + kAEAlwaysInteract, // sendMode kAENormalPriority, // sendPriority kAEDefaultTimeout, // timeOutInTicks - NULL, // idleProc - NULL); // filterProc + nullptr, // idleProc + nullptr); // filterProc if (status != noErr) { OSSTATUS_LOG(WARNING, status) << "Could not send AE to Finder in OpenItem()"; diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index 8dd8e04e1ac2..9d015b4f229f 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -9,53 +9,23 @@ #include "atom_natives.h" // NOLINT: This file is generated with js2c -#include "atom/common/api/api_messages.h" #include "atom/common/api/atom_bindings.h" #include "atom/common/api/event_emitter_caller.h" #include "atom/common/asar/asar_util.h" #include "atom/common/atom_constants.h" -#include "atom/common/color_util.h" -#include "atom/common/native_mate_converters/value_converter.h" #include "atom/common/node_bindings.h" #include "atom/common/options_switches.h" #include "atom/renderer/api/atom_api_renderer_ipc.h" #include "atom/renderer/atom_render_view_observer.h" -#include "atom/renderer/content_settings_observer.h" -#include "atom/renderer/guest_view_container.h" #include "atom/renderer/node_array_buffer_bridge.h" -#include "atom/renderer/preferences_manager.h" #include "atom/renderer/web_worker_observer.h" #include "base/command_line.h" -#include "chrome/renderer/media/chrome_key_systems.h" -#include "chrome/renderer/pepper/pepper_helper.h" -#include "chrome/renderer/printing/print_web_view_helper.h" -#include "chrome/renderer/tts_dispatcher.h" -#include "content/public/common/content_constants.h" #include "content/public/renderer/render_frame.h" #include "content/public/renderer/render_frame_observer.h" -#include "content/public/renderer/render_thread.h" -#include "content/public/renderer/render_view.h" -#include "ipc/ipc_message_macros.h" #include "native_mate/dictionary.h" -#include "third_party/WebKit/public/web/WebCustomElement.h" #include "third_party/WebKit/public/web/WebDocument.h" -#include "third_party/WebKit/public/web/WebFrameWidget.h" -#include "third_party/WebKit/public/web/WebKit.h" #include "third_party/WebKit/public/web/WebLocalFrame.h" -#include "third_party/WebKit/public/web/WebPluginParams.h" -#include "third_party/WebKit/public/web/WebRuntimeFeatures.h" #include "third_party/WebKit/public/web/WebScriptSource.h" -#include "third_party/WebKit/public/web/WebSecurityPolicy.h" -#include "third_party/WebKit/public/web/WebView.h" - -#if defined(OS_MACOSX) -#include "base/mac/mac_util.h" -#include "base/strings/sys_string_conversions.h" -#endif - -#if defined(OS_WIN) -#include -#endif #include "atom/common/node_includes.h" @@ -115,7 +85,7 @@ class AtomRenderFrameObserver : public content::RenderFrameObserver { // an argument. std::string bundle(node::isolated_bundle_data, node::isolated_bundle_data + sizeof(node::isolated_bundle_data)); - std::string wrapper = "(function (binding) {\n" + bundle + "\n})"; + std::string wrapper = "(function (binding, require) {\n" + bundle + "\n})"; auto script = v8::Script::Compile( mate::ConvertToV8(isolate, wrapper)->ToString()); auto func = v8::Handle::Cast( @@ -184,35 +154,11 @@ class AtomRenderFrameObserver : public content::RenderFrameObserver { DISALLOW_COPY_AND_ASSIGN(AtomRenderFrameObserver); }; -v8::Local GetRenderProcessPreferences( - const PreferencesManager* preferences_manager, v8::Isolate* isolate) { - if (preferences_manager->preferences()) - return mate::ConvertToV8(isolate, *preferences_manager->preferences()); - else - return v8::Null(isolate); -} - -void AddRenderBindings(v8::Isolate* isolate, - v8::Local process, - const PreferencesManager* preferences_manager) { - mate::Dictionary dict(isolate, process); - dict.SetMethod( - "getRenderProcessPreferences", - base::Bind(GetRenderProcessPreferences, preferences_manager)); -} - bool IsDevToolsExtension(content::RenderFrame* render_frame) { return static_cast(render_frame->GetWebFrame()->document().url()) .SchemeIs("chrome-extension"); } -std::vector ParseSchemesCLISwitch(const char* switch_name) { - base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); - std::string custom_schemes = command_line->GetSwitchValueASCII(switch_name); - return base::SplitString( - custom_schemes, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); -} - } // namespace AtomRendererClient::AtomRendererClient() @@ -221,11 +167,6 @@ AtomRendererClient::AtomRendererClient() atom_bindings_(new AtomBindings(uv_default_loop())) { isolated_world_ = base::CommandLine::ForCurrentProcess()->HasSwitch( switches::kContextIsolation); - // Parse --standard-schemes=scheme1,scheme2 - std::vector standard_schemes_list = - ParseSchemesCLISwitch(switches::kStandardSchemes); - for (const std::string& scheme : standard_schemes_list) - url::AddStandardScheme(scheme.c_str(), url::SCHEME_WITHOUT_PORT); } AtomRendererClient::~AtomRendererClient() { @@ -233,80 +174,19 @@ AtomRendererClient::~AtomRendererClient() { } void AtomRendererClient::RenderThreadStarted() { - blink::WebCustomElement::addEmbedderCustomElementName("webview"); - blink::WebCustomElement::addEmbedderCustomElementName("browserplugin"); - OverrideNodeArrayBuffer(); - - preferences_manager_.reset(new PreferencesManager); - -#if defined(OS_WIN) - // Set ApplicationUserModelID in renderer process. - base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); - base::string16 app_id = - command_line->GetSwitchValueNative(switches::kAppUserModelId); - if (!app_id.empty()) { - SetCurrentProcessExplicitAppUserModelID(app_id.c_str()); - } -#endif - -#if defined(OS_MACOSX) - // Disable rubber banding by default. - base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); - if (!command_line->HasSwitch(switches::kScrollBounce)) { - base::ScopedCFTypeRef key( - base::SysUTF8ToCFStringRef("NSScrollViewRubberbanding")); - base::ScopedCFTypeRef value( - base::SysUTF8ToCFStringRef("false")); - CFPreferencesSetAppValue(key, value, kCFPreferencesCurrentApplication); - CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication); - } -#endif + RendererClientBase::RenderThreadStarted(); } void AtomRendererClient::RenderFrameCreated( content::RenderFrame* render_frame) { - new PepperHelper(render_frame); new AtomRenderFrameObserver(render_frame, this); - new ContentSettingsObserver(render_frame); - new printing::PrintWebViewHelper(render_frame); - - // Allow file scheme to handle service worker by default. - // FIXME(zcbenz): Can this be moved elsewhere? - blink::WebSecurityPolicy::registerURLSchemeAsAllowingServiceWorkers("file"); - - // This is required for widevine plugin detection provided during runtime. - blink::resetPluginCache(); - - // Allow access to file scheme from pdf viewer. - blink::WebSecurityPolicy::addOriginAccessWhitelistEntry( - GURL(kPdfViewerUIOrigin), "file", "", true); - - // Parse --secure-schemes=scheme1,scheme2 - std::vector secure_schemes_list = - ParseSchemesCLISwitch(switches::kSecureSchemes); - for (const std::string& secure_scheme : secure_schemes_list) - blink::WebSecurityPolicy::registerURLSchemeAsSecure( - blink::WebString::fromUTF8(secure_scheme)); + RendererClientBase::RenderFrameCreated(render_frame); } void AtomRendererClient::RenderViewCreated(content::RenderView* render_view) { new AtomRenderViewObserver(render_view, this); - - blink::WebFrameWidget* web_frame_widget = render_view->GetWebFrameWidget(); - if (!web_frame_widget) - return; - - base::CommandLine* cmd = base::CommandLine::ForCurrentProcess(); - if (cmd->HasSwitch(switches::kGuestInstanceID)) { // webview. - web_frame_widget->setBaseBackgroundColor(SK_ColorTRANSPARENT); - } else { // normal window. - // If backgroundColor is specified then use it. - std::string name = cmd->GetSwitchValueASCII(switches::kBackgroundColor); - // Otherwise use white background. - SkColor color = name.empty() ? SK_ColorWHITE : ParseHexColor(name); - web_frame_widget->setBaseBackgroundColor(color); - } + RendererClientBase::RenderViewCreated(render_view); } void AtomRendererClient::DidClearWindowObject( @@ -335,26 +215,6 @@ void AtomRendererClient::RunScriptsAtDocumentEnd( } } -blink::WebSpeechSynthesizer* AtomRendererClient::OverrideSpeechSynthesizer( - blink::WebSpeechSynthesizerClient* client) { - return new TtsDispatcher(client); -} - -bool AtomRendererClient::OverrideCreatePlugin( - content::RenderFrame* render_frame, - blink::WebLocalFrame* frame, - const blink::WebPluginParams& params, - blink::WebPlugin** plugin) { - base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); - if (params.mimeType.utf8() == content::kBrowserPluginMimeType || - params.mimeType.utf8() == kPdfPluginMimeType || - command_line->HasSwitch(switches::kEnablePlugins)) - return false; - - *plugin = nullptr; - return true; -} - void AtomRendererClient::DidCreateScriptContext( v8::Handle context, content::RenderFrame* render_frame) { // Only allow node integration for the main frame, unless it is a devtools @@ -374,8 +234,7 @@ void AtomRendererClient::DidCreateScriptContext( // Add Electron extended APIs. atom_bindings_->BindTo(env->isolate(), env->process_object()); - AddRenderBindings(env->isolate(), env->process_object(), - preferences_manager_.get()); + AddRenderBindings(env->isolate(), env->process_object()); // Load everything. node_bindings_->LoadEnvironment(env); @@ -423,22 +282,6 @@ bool AtomRendererClient::ShouldFork(blink::WebLocalFrame* frame, return http_method == "GET"; } -content::BrowserPluginDelegate* AtomRendererClient::CreateBrowserPluginDelegate( - content::RenderFrame* render_frame, - const std::string& mime_type, - const GURL& original_url) { - if (mime_type == content::kBrowserPluginMimeType) { - return new GuestViewContainer(render_frame); - } else { - return nullptr; - } -} - -void AtomRendererClient::AddSupportedKeySystems( - std::vector>* key_systems) { - AddChromeKeySystems(key_systems); -} - void AtomRendererClient::DidInitializeWorkerContextOnWorkerThread( v8::Local context) { if (base::CommandLine::ForCurrentProcess()->HasSwitch( diff --git a/atom/renderer/atom_renderer_client.h b/atom/renderer/atom_renderer_client.h index af8397854e3e..5a1141d1036d 100644 --- a/atom/renderer/atom_renderer_client.h +++ b/atom/renderer/atom_renderer_client.h @@ -8,15 +8,14 @@ #include #include -#include "content/public/renderer/content_renderer_client.h" +#include "atom/renderer/renderer_client_base.h" namespace atom { class AtomBindings; -class PreferencesManager; class NodeBindings; -class AtomRendererClient : public content::ContentRendererClient { +class AtomRendererClient : public RendererClientBase { public: AtomRendererClient(); virtual ~AtomRendererClient(); @@ -46,25 +45,12 @@ class AtomRendererClient : public content::ContentRendererClient { void RenderViewCreated(content::RenderView*) override; void RunScriptsAtDocumentStart(content::RenderFrame* render_frame) override; void RunScriptsAtDocumentEnd(content::RenderFrame* render_frame) override; - blink::WebSpeechSynthesizer* OverrideSpeechSynthesizer( - blink::WebSpeechSynthesizerClient* client) override; - bool OverrideCreatePlugin(content::RenderFrame* render_frame, - blink::WebLocalFrame* frame, - const blink::WebPluginParams& params, - blink::WebPlugin** plugin) override; bool ShouldFork(blink::WebLocalFrame* frame, const GURL& url, const std::string& http_method, bool is_initial_navigation, bool is_server_redirect, bool* send_referrer) override; - content::BrowserPluginDelegate* CreateBrowserPluginDelegate( - content::RenderFrame* render_frame, - const std::string& mime_type, - const GURL& original_url) override; - void AddSupportedKeySystems( - std::vector>* key_systems) - override; void DidInitializeWorkerContextOnWorkerThread( v8::Local context) override; void WillDestroyWorkerContextOnWorkerThread( @@ -75,7 +61,6 @@ class AtomRendererClient : public content::ContentRendererClient { std::unique_ptr node_bindings_; std::unique_ptr atom_bindings_; - std::unique_ptr preferences_manager_; bool isolated_world_; DISALLOW_COPY_AND_ASSIGN(AtomRendererClient); diff --git a/atom/renderer/atom_sandboxed_renderer_client.cc b/atom/renderer/atom_sandboxed_renderer_client.cc index 7d0dfde8c5ab..80f8cae713a3 100644 --- a/atom/renderer/atom_sandboxed_renderer_client.cc +++ b/atom/renderer/atom_sandboxed_renderer_client.cc @@ -9,6 +9,7 @@ #include "atom_natives.h" // NOLINT: This file is generated with js2c #include "atom/common/api/api_messages.h" +#include "atom/common/api/atom_bindings.h" #include "atom/common/native_mate_converters/string16_converter.h" #include "atom/common/native_mate_converters/v8_value_converter.h" #include "atom/common/native_mate_converters/value_converter.h" @@ -85,6 +86,7 @@ void InitializeBindings(v8::Local binding, auto isolate = context->GetIsolate(); mate::Dictionary b(isolate, binding); b.SetMethod("get", GetBinding); + b.SetMethod("crash", AtomBindings::Crash); } class AtomSandboxedRenderFrameObserver : public content::RenderFrameObserver { @@ -180,12 +182,13 @@ AtomSandboxedRendererClient::~AtomSandboxedRendererClient() { void AtomSandboxedRendererClient::RenderFrameCreated( content::RenderFrame* render_frame) { new AtomSandboxedRenderFrameObserver(render_frame, this); - new printing::PrintWebViewHelper(render_frame); + RendererClientBase::RenderFrameCreated(render_frame); } void AtomSandboxedRendererClient::RenderViewCreated( content::RenderView* render_view) { new AtomSandboxedRenderViewObserver(render_view, this); + RendererClientBase::RenderViewCreated(render_view); } void AtomSandboxedRendererClient::DidCreateScriptContext( @@ -204,7 +207,7 @@ void AtomSandboxedRendererClient::DidCreateScriptContext( std::string preload_bundle_native(node::preload_bundle_data, node::preload_bundle_data + sizeof(node::preload_bundle_data)); std::stringstream ss; - ss << "(function(binding, preloadPath) {\n"; + ss << "(function(binding, preloadPath, require) {\n"; ss << preload_bundle_native << "\n"; ss << "})"; std::string preload_wrapper = ss.str(); @@ -216,6 +219,7 @@ void AtomSandboxedRendererClient::DidCreateScriptContext( // Create and initialize the binding object auto binding = v8::Object::New(isolate); InitializeBindings(binding, context); + AddRenderBindings(isolate, binding); v8::Local args[] = { binding, mate::ConvertToV8(isolate, preload_script) @@ -234,7 +238,7 @@ void AtomSandboxedRendererClient::WillReleaseScriptContext( void AtomSandboxedRendererClient::InvokeIpcCallback( v8::Handle context, - std::string callback_name, + const std::string& callback_name, std::vector> args) { auto isolate = context->GetIsolate(); auto binding_key = mate::ConvertToV8(isolate, kIpcKey)->ToString(); diff --git a/atom/renderer/atom_sandboxed_renderer_client.h b/atom/renderer/atom_sandboxed_renderer_client.h index 0912c6b24bd0..5e0614c9c9ec 100644 --- a/atom/renderer/atom_sandboxed_renderer_client.h +++ b/atom/renderer/atom_sandboxed_renderer_client.h @@ -7,12 +7,11 @@ #include #include -#include "content/public/renderer/content_renderer_client.h" -#include "content/public/renderer/render_frame.h" +#include "atom/renderer/renderer_client_base.h" namespace atom { -class AtomSandboxedRendererClient : public content::ContentRendererClient { +class AtomSandboxedRendererClient : public RendererClientBase { public: AtomSandboxedRendererClient(); virtual ~AtomSandboxedRendererClient(); @@ -22,7 +21,7 @@ class AtomSandboxedRendererClient : public content::ContentRendererClient { void WillReleaseScriptContext( v8::Handle context, content::RenderFrame* render_frame); void InvokeIpcCallback(v8::Handle context, - std::string callback_name, + const std::string& callback_name, std::vector> args); // content::ContentRendererClient: void RenderFrameCreated(content::RenderFrame*) override; diff --git a/atom/renderer/renderer_client_base.cc b/atom/renderer/renderer_client_base.cc new file mode 100644 index 000000000000..3ee72b3c0da2 --- /dev/null +++ b/atom/renderer/renderer_client_base.cc @@ -0,0 +1,190 @@ +// Copyright (c) 2017 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/renderer/renderer_client_base.h" + +#include +#include + +#include "atom/common/atom_constants.h" +#include "atom/common/color_util.h" +#include "atom/common/native_mate_converters/value_converter.h" +#include "atom/common/options_switches.h" +#include "atom/renderer/content_settings_observer.h" +#include "atom/renderer/guest_view_container.h" +#include "atom/renderer/preferences_manager.h" +#include "base/command_line.h" +#include "base/strings/string_split.h" +#include "chrome/renderer/media/chrome_key_systems.h" +#include "chrome/renderer/pepper/pepper_helper.h" +#include "chrome/renderer/printing/print_web_view_helper.h" +#include "chrome/renderer/tts_dispatcher.h" +#include "content/public/common/content_constants.h" +#include "content/public/renderer/render_view.h" +#include "native_mate/dictionary.h" +#include "third_party/WebKit/public/web/WebCustomElement.h" +#include "third_party/WebKit/public/web/WebFrameWidget.h" +#include "third_party/WebKit/public/web/WebKit.h" +#include "third_party/WebKit/public/web/WebPluginParams.h" +#include "third_party/WebKit/public/web/WebSecurityPolicy.h" + +#if defined(OS_MACOSX) +#include "base/mac/mac_util.h" +#include "base/strings/sys_string_conversions.h" +#endif + +#if defined(OS_WIN) +#include +#endif + +namespace atom { + +namespace { + +v8::Local GetRenderProcessPreferences( + const PreferencesManager* preferences_manager, v8::Isolate* isolate) { + if (preferences_manager->preferences()) + return mate::ConvertToV8(isolate, *preferences_manager->preferences()); + else + return v8::Null(isolate); +} + +std::vector ParseSchemesCLISwitch(const char* switch_name) { + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); + std::string custom_schemes = command_line->GetSwitchValueASCII(switch_name); + return base::SplitString( + custom_schemes, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); +} + +} // namespace + +RendererClientBase::RendererClientBase() { + // Parse --standard-schemes=scheme1,scheme2 + std::vector standard_schemes_list = + ParseSchemesCLISwitch(switches::kStandardSchemes); + for (const std::string& scheme : standard_schemes_list) + url::AddStandardScheme(scheme.c_str(), url::SCHEME_WITHOUT_PORT); +} + +RendererClientBase::~RendererClientBase() { +} + +void RendererClientBase::AddRenderBindings( + v8::Isolate* isolate, + v8::Local binding_object) { + mate::Dictionary dict(isolate, binding_object); + dict.SetMethod( + "getRenderProcessPreferences", + base::Bind(GetRenderProcessPreferences, preferences_manager_.get())); +} + +void RendererClientBase::RenderThreadStarted() { + blink::WebCustomElement::addEmbedderCustomElementName("webview"); + blink::WebCustomElement::addEmbedderCustomElementName("browserplugin"); + + preferences_manager_.reset(new PreferencesManager); + +#if defined(OS_WIN) + // Set ApplicationUserModelID in renderer process. + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); + base::string16 app_id = + command_line->GetSwitchValueNative(switches::kAppUserModelId); + if (!app_id.empty()) { + SetCurrentProcessExplicitAppUserModelID(app_id.c_str()); + } +#endif + +#if defined(OS_MACOSX) + // Disable rubber banding by default. + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); + if (!command_line->HasSwitch(switches::kScrollBounce)) { + base::ScopedCFTypeRef key( + base::SysUTF8ToCFStringRef("NSScrollViewRubberbanding")); + base::ScopedCFTypeRef value( + base::SysUTF8ToCFStringRef("false")); + CFPreferencesSetAppValue(key, value, kCFPreferencesCurrentApplication); + CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication); + } +#endif +} + +void RendererClientBase::RenderFrameCreated( + content::RenderFrame* render_frame) { + new PepperHelper(render_frame); + new ContentSettingsObserver(render_frame); + new printing::PrintWebViewHelper(render_frame); + + // Allow file scheme to handle service worker by default. + // FIXME(zcbenz): Can this be moved elsewhere? + blink::WebSecurityPolicy::registerURLSchemeAsAllowingServiceWorkers("file"); + + // This is required for widevine plugin detection provided during runtime. + blink::resetPluginCache(); + + // Allow access to file scheme from pdf viewer. + blink::WebSecurityPolicy::addOriginAccessWhitelistEntry( + GURL(kPdfViewerUIOrigin), "file", "", true); + + // Parse --secure-schemes=scheme1,scheme2 + std::vector secure_schemes_list = + ParseSchemesCLISwitch(switches::kSecureSchemes); + for (const std::string& secure_scheme : secure_schemes_list) + blink::WebSecurityPolicy::registerURLSchemeAsSecure( + blink::WebString::fromUTF8(secure_scheme)); +} + +void RendererClientBase::RenderViewCreated(content::RenderView* render_view) { + blink::WebFrameWidget* web_frame_widget = render_view->GetWebFrameWidget(); + if (!web_frame_widget) + return; + + base::CommandLine* cmd = base::CommandLine::ForCurrentProcess(); + if (cmd->HasSwitch(switches::kGuestInstanceID)) { // webview. + web_frame_widget->setBaseBackgroundColor(SK_ColorTRANSPARENT); + } else { // normal window. + // If backgroundColor is specified then use it. + std::string name = cmd->GetSwitchValueASCII(switches::kBackgroundColor); + // Otherwise use white background. + SkColor color = name.empty() ? SK_ColorWHITE : ParseHexColor(name); + web_frame_widget->setBaseBackgroundColor(color); + } +} + +blink::WebSpeechSynthesizer* RendererClientBase::OverrideSpeechSynthesizer( + blink::WebSpeechSynthesizerClient* client) { + return new TtsDispatcher(client); +} + +bool RendererClientBase::OverrideCreatePlugin( + content::RenderFrame* render_frame, + blink::WebLocalFrame* frame, + const blink::WebPluginParams& params, + blink::WebPlugin** plugin) { + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); + if (params.mimeType.utf8() == content::kBrowserPluginMimeType || + params.mimeType.utf8() == kPdfPluginMimeType || + command_line->HasSwitch(switches::kEnablePlugins)) + return false; + + *plugin = nullptr; + return true; +} + +content::BrowserPluginDelegate* RendererClientBase::CreateBrowserPluginDelegate( + content::RenderFrame* render_frame, + const std::string& mime_type, + const GURL& original_url) { + if (mime_type == content::kBrowserPluginMimeType) { + return new GuestViewContainer(render_frame); + } else { + return nullptr; + } +} + +void RendererClientBase::AddSupportedKeySystems( + std::vector>* key_systems) { + AddChromeKeySystems(key_systems); +} + +} // namespace atom diff --git a/atom/renderer/renderer_client_base.h b/atom/renderer/renderer_client_base.h new file mode 100644 index 000000000000..3a91255c60b1 --- /dev/null +++ b/atom/renderer/renderer_client_base.h @@ -0,0 +1,50 @@ +// Copyright (c) 2017 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_RENDERER_RENDERER_CLIENT_BASE_H_ +#define ATOM_RENDERER_RENDERER_CLIENT_BASE_H_ + +#include +#include + +#include "content/public/renderer/content_renderer_client.h" + +namespace atom { + +class PreferencesManager; + +class RendererClientBase : public content::ContentRendererClient { + public: + RendererClientBase(); + virtual ~RendererClientBase(); + + protected: + void AddRenderBindings(v8::Isolate* isolate, + v8::Local binding_object); + + // content::ContentRendererClient: + void RenderThreadStarted() override; + void RenderFrameCreated(content::RenderFrame*) override; + void RenderViewCreated(content::RenderView*) override; + blink::WebSpeechSynthesizer* OverrideSpeechSynthesizer( + blink::WebSpeechSynthesizerClient* client) override; + bool OverrideCreatePlugin(content::RenderFrame* render_frame, + blink::WebLocalFrame* frame, + const blink::WebPluginParams& params, + blink::WebPlugin** plugin) override; + content::BrowserPluginDelegate* CreateBrowserPluginDelegate( + content::RenderFrame* render_frame, + const std::string& mime_type, + const GURL& original_url) override; + void AddSupportedKeySystems( + std::vector>* key_systems) + override; + + private: + std::unique_ptr preferences_manager_; +}; + +} // namespace atom + +#endif // ATOM_RENDERER_RENDERER_CLIENT_BASE_H_ diff --git a/docs-translations/zh-CN/api/app.md b/docs-translations/zh-CN/api/app.md index 99429119f3f6..04680d548ba5 100644 --- a/docs-translations/zh-CN/api/app.md +++ b/docs-translations/zh-CN/api/app.md @@ -356,6 +356,27 @@ Windows, 使应用的第一个窗口获取焦点. * `pictures` 用户图片目录的路径. * `videos` 用户视频目录的路径. +### `app.getFileIcon(path[, options], callback)` +* `path` String +* `options` Object(可选) + * `size` String + * `small` - 16x16 + * `normal` - 32x32 + * `large` - Linux 为 48x48, Windows 为 32x32, Mac 系统不支持 +* `callback` Function + * `error` Error + * `icon` [NativeImage](native-image.md) + + +获取文件关联的图标. + +在 Windows 系统中, 有2种图标类型: + +- 图标与某些文件扩展名关联, 比如 `.mp3`, `.png`, 等等. +- 图标在文件内部, 比如 `.exe`, `.dll`, `.ico`. + +在 Linux 和 Mac 系统中, 图标取决于应用程序相关文件的 mime 类型 + ### `app.setPath(name, path)` * `name` String diff --git a/docs-translations/zh-CN/api/dialog.md b/docs-translations/zh-CN/api/dialog.md index 702b6ea3e2bf..41ec25672196 100644 --- a/docs-translations/zh-CN/api/dialog.md +++ b/docs-translations/zh-CN/api/dialog.md @@ -107,20 +107,23 @@ console.log(dialog) * `options` Object * `type` String - 可以是 `"none"`, `"info"`, `"error"`, `"question"` 或 `"warning"`. 在 Windows, "question" 与 "info" 展示图标相同, 除非你使用 "icon" 参数. - * `buttons` Array - buttons 内容,数组. - * `defaultId` Integer - 在message box 对话框打开的时候,设置默认button选中,值为在 buttons 数组中的button索引. - * `title` String - message box 的标题,一些平台不显示. - * `message` String - message box 内容. - * `detail` String - 额外信息. - * `icon` [NativeImage](native-image.md) - * `cancelId` Integer - 当用户关闭对话框的时候,不是通过点击对话框的button,就返回值.默认值为对应 "cancel" 或 "no" 标签button 的索引值, 或者如果没有这种button,就返回0. 在 macOS 和 Windows 上, "Cancel" button 的索引值将一直是 `cancelId`, 不管之前是不是特别指出的. - * `noLink` Boolean - 在 Windows ,Electron 将尝试识别哪个button 是普通 button (如 "Cancel" 或 "Yes"), 然后在对话框中以链接命令(command links)方式展现其它的 button . 这能让对话框展示得很炫酷.如果你不喜欢这种效果,你可以设置 `noLink` 为 `true`. - * `callback` Function (可选) - * `response` Number - The index of the button that was clicked + * `buttons` String[]- (可选) - 按钮上文字的数组,在 Windows 系统中,空数组在按钮上会显示 “OK”. + * `defaultId` Integer (可选) - 在 message box 对话框打开的时候,设置默认选中的按钮,值为在 buttons 数组中的索引. + * `title` String (可选) - message box 的标题,一些平台不显示. + * `message` String (可选) - message box 的内容. + * `detail` String (可选)- 额外信息. + * `checkboxLabel` String (可选) - 如果有该参数,message box 中会显示一个 checkbox 复选框,它的勾选状态可以在 `callback` 回调方法中获取。 + * `checkboxChecked` Boolean (可选) - checkbox 的初始值,默认为`false`. + * `icon` [NativeImage](native-image.md)(可选) + * `cancelId` Integer - 当用户不是通过按钮而是使用其他方式关闭对话框时,比如按`Esc`键,就返回该值.默认值为对应 "cancel" 或 "no" 标签 button 的索引值, 如果没有这种 button,就返回0. 该选项在 Windows 上无效. + * `noLink` Boolean(可选) - 在 Windows 系统中,Electron 将尝试识别哪个button 是普通 button (如 "Cancel" 或 "Yes"), 然后在对话框中以链接命令(command links)方式展现其它的 button . 这能让对话框展示得很炫酷.如果你不喜欢这种效果,你可以设置 `noLink` 为 `true`. +* `callback` Function (可选) + * `response` Number - 被点击按钮的索引值。 + * `checkboxChecked` Boolean - 如果设置了 `checkboxLabel` ,会显示 checkbox 的选中状态,否则显示 `false` 返回 `Integer`,如果提供了回调,它会返回点击的按钮的索引或者 undefined 。 -展示 message box, 它会阻塞进程,直到 message box 关闭为止.返回点击按钮的索引值。 +显示 message box 时, 它会阻塞进程,直到 message box 关闭为止.返回点击按钮的索引值。 `browserWindow` 参数允许对话框将自身附加到父窗口,使其成为模态。 diff --git a/docs-translations/zh-CN/api/web-contents.md b/docs-translations/zh-CN/api/web-contents.md index b13628533ee1..dd5c926126c2 100644 --- a/docs-translations/zh-CN/api/web-contents.md +++ b/docs-translations/zh-CN/api/web-contents.md @@ -48,15 +48,14 @@ console.log(webContents) 可使用的进程: [主进程](../tutorial/quick-start.md#main-process) -## 事件 +### 实例事件 -`webContents` 对象可发出下列事件: -### Event: 'did-finish-load' +#### Event: 'did-finish-load' -当导航完成时发出事件,`onload` 事件也完成。 +当导航完成时,发出 `onload` 事件。 -### Event: 'did-fail-load' +#### Event: 'did-fail-load' 返回: @@ -66,9 +65,9 @@ console.log(webContents) * `validatedURL` String * `isMainFrame` Boolean -这个事件类似 `did-finish-load` ,但是是在加载失败或取消加载时发出, 例如, `window.stop()` 请求结束。错误代码的完整列表和它们的含义都可以在 [这里](https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h) 找到。 +这个事件类似 `did-finish-load` ,但是是在加载失败或取消加载时发出, 例如, `window.stop()` 被调用时。错误代码的完整列表和它们的含义都可以在 [这里](https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h) 找到。 -### Event: 'did-frame-finish-load' +#### Event: 'did-frame-finish-load' 返回: @@ -77,15 +76,15 @@ console.log(webContents) 当一个 frame 导航完成的时候发出事件。 -### Event: 'did-start-loading' +#### Event: 'did-start-loading' 当 tab 的spinner 开始 spinning的时候。 -### Event: 'did-stop-loading' +#### Event: 'did-stop-loading' 当 tab 的spinner 结束 spinning的时候。 -### Event: 'did-get-response-details' +#### Event: 'did-get-response-details' 返回: @@ -102,7 +101,7 @@ console.log(webContents) 当有关请求资源的详细信息可用的时候发出事件。 `status` 标识了 socket 链接来下载资源。 -### Event: 'did-get-redirect-request' +#### Event: 'did-get-redirect-request' 返回: @@ -117,7 +116,7 @@ console.log(webContents) 当在请求资源时收到重定向的时候发出事件。 -### Event: 'dom-ready' +#### Event: 'dom-ready' 返回: @@ -125,7 +124,7 @@ console.log(webContents) 当指定 frame 中的 文档加载完成的时候发出事件。 -### Event: 'page-favicon-updated' +#### Event: 'page-favicon-updated' 返回: @@ -134,15 +133,15 @@ console.log(webContents) 当 page 收到图标 url 的时候发出事件。 -### Event: 'new-window' +#### Event: 'new-window' 返回: * `event` Event * `url` String * `frameName` String -* `disposition` String - 可为 `default`、 `foreground-tab`、 `background-tab`、 - `new-window` 和 `other`。 +* `disposition` String - 可为 `default`, `foreground-tab`, `background-tab`, + `new-window`, `save-to-disk` 和 `other` * `options` Object - 创建新的 `BrowserWindow`时使用的参数。 * `additionalFeatures` String[] - 非标准功能(功能未处理    由 Chromium 或 Electron )赋予 `window.open()`。 @@ -168,7 +167,7 @@ myBrowserWindow.webContents.on('new-window', (event, url) => { }) ``` -### Event: 'will-navigate' +#### Event: 'will-navigate' 返回: @@ -183,7 +182,7 @@ myBrowserWindow.webContents.on('new-window', (event, url) => { 调用 `event.preventDefault()` 可以阻止导航。 -### Event: 'did-navigate' +#### Event: 'did-navigate' 返回: @@ -194,22 +193,28 @@ myBrowserWindow.webContents.on('new-window', (event, url) => { 页内跳转时不会发出这个事件,例如点击锚链接或更新 `window.location.hash`。使用 `did-navigate-in-page` 事件可以达到目的。 -### Event: 'did-navigate-in-page' +#### Event: 'did-navigate-in-page' 返回: * `event` Event * `url` String +* `isMainFrame ` Boolean 当页内导航发生的时候发出事件。 当页内导航发生的时候,page 的url 改变,但是不会跳出界面。例如当点击锚链接时或者 DOM 的 `hashchange` 事件发生。 -### Event: 'crashed' +#### Event: 'crashed' + +返回: + +* `event` Event +* `killed` Boolean 当渲染进程崩溃的时候发出事件。 -### Event: 'plugin-crashed' +#### Event: 'plugin-crashed' 返回: @@ -219,54 +224,70 @@ myBrowserWindow.webContents.on('new-window', (event, url) => { 当插件进程崩溃时候发出事件。 -### Event: 'destroyed' +#### Event: 'destroyed' 当 `webContents` 被删除的时候发出事件。 -### Event: 'devtools-opened' +#### Event: 'before-input-event' + +返回: + +* `event` Event +* `input` Object - 属性 + * `type` String - `keyUp` 或者 `keyDown` + * `key` String - [KeyboardEvent.key][keyboardevent] + * `code` String - [KeyboardEvent.code][keyboardevent] + * `isAutoRepeat` Boolean - [KeyboardEvent.repeat][keyboardevent] + * `shift` Boolean - [KeyboardEvent.shiftKey][keyboardevent] + * `control` Boolean - [KeyboardEvent.controlKey][keyboardevent] + * `alt` Boolean - [KeyboardEvent.altKey][keyboardevent] + * `meta` Boolean - [KeyboardEvent.metaKey][keyboardevent] + +在 `keydown` 和 `keyup` 事件触发前发送。调用 `event.preventDefault` 方法可以阻止页面的 `keydown/keyup` 事件。 + +#### Event: 'devtools-opened' 当开发者工具栏打开的时候发出事件。 -### Event: 'devtools-closed' +#### Event: 'devtools-closed' 当开发者工具栏关闭时候发出事件。 -### Event: 'devtools-focused' +#### Event: 'devtools-focused' 当开发者工具栏获得焦点或打开的时候发出事件。 -### Event: 'certificate-error' +#### Event: 'certificate-error' 返回: * `event` Event * `url` URL * `error` String - The error code -* `certificate` Object - * `data` Buffer - PEM encoded data - * `issuerName` String +* `certificate` [Certificate](structures/certificate.md) * `callback` Function + * `isTrusted ` Boolean - 表明证书是否可以被认为是可信的 -当验证证书或 `url` 失败的时候发出事件。 +当验证 `certificate` 或 `url` 失败的时候发出事件。 使用方法类似 [`app` 的 `certificate-error` 事件](app.md#event-certificate-error)。 -### Event: 'select-client-certificate' + +#### Event: 'select-client-certificate' 返回: * `event` Event * `url` URL -* `certificateList` [Objects] - * `data` Buffer - PEM encoded data - * `issuerName` String - Issuer's Common Name +* `certificateList` [Certificate[]](structures/certificate.md) * `callback` Function + * `certificate` [Certificate](structures/certificate.md) - 证书必须来自于指定的列表 当请求客户端证书的时候发出事件。 使用方法类似 [`app` 的 `select-client-certificate` 事件](app.md#event-select-client-certificate)。 -### Event: 'login' +#### Event: 'login' 返回: @@ -282,34 +303,37 @@ myBrowserWindow.webContents.on('new-window', (event, url) => { * `port` Integer * `realm` String * `callback` Function + * `username` String + * `password` String 当 `webContents` 想做基本验证的时候发出事件. 使用方法类似 [the `login` event of `app`](app.md#event-login)。 -### Event: 'found-in-page' +#### Event: 'found-in-page' 返回: * `event` Event * `result` Object * `requestId` Integer - * `finalUpdate` Boolean - 标识是否还有更多的值可以查看。 - * `activeMatchOrdinal` Integer (可选) - 活动匹配位置。 - * `matches` Integer (可选) - 匹配数量。 - * `selectionArea` Object (可选) - 协调首个匹配位置。 + * `activeMatchOrdinal` Integer - 活动匹配位置。 + * `matches` Integer - 匹配数量。 + * `selectionArea` Object - 协调首个匹配位置。 + * `finalUpdate` Boolean + 当使用 [`webContents.findInPage`] 进行页内查找并且找到可用值得时候发出事件。 -### Event: 'media-started-playing' +#### Event: 'media-started-playing' 当媒体开始播放的时候发出事件。 -### Event: 'media-paused' +#### Event: 'media-paused' 当媒体停止播放的时候发出事件。 -### Event: 'did-change-theme-color' +#### Event: 'did-change-theme-color' 当page 的主题色时候发出事件。这通常由于引入了一个 meta 标签: @@ -317,14 +341,29 @@ myBrowserWindow.webContents.on('new-window', (event, url) => { ``` -### Event: 'cursor-changed' +#### Event: 'update-target-url' + +返回: + +* `event` Event +* `url` String + +当鼠标移到一个链接上或键盘焦点移动到一个链接上时发送。 + +#### Event: 'cursor-changed' 返回: * `event` Event * `type` String * `image` NativeImage (可选) -* `scale` Float (可选) +* `scale` Float (可选) 自定义光标的比例 +* `size` Object (可选) - `image`的大小 + * `width` Integer + * `height` Integer +* `hotspot` Object (可选) - 自定义光标热点的坐标 + * `x` Integer - x 坐标 + * `y` Integer - y 坐标 当鼠标的类型发生改变的时候发出事件. `type` 的参数可以是 `default`, `crosshair`, `pointer`, `text`, `wait`, `help`, `e-resize`, `n-resize`, @@ -333,221 +372,428 @@ myBrowserWindow.webContents.on('new-window', (event, url) => { `row-resize`, `m-panning`, `e-panning`, `n-panning`, `ne-panning`, `nw-panning`, `s-panning`, `se-panning`, `sw-panning`, `w-panning`, `move`, `vertical-text`, `cell`, `context-menu`, `alias`, `progress`, `nodrop`, `copy`, `none`, -`not-allowed`, `zoom-in`, `zoom-out`, `grab`, `grabbing`, `custom`。 +`not-allowed`, `zoom-in`, `zoom-out`, `grab`, `grabbing`, `custom`. -如果 `type` 参数值为 `custom`、 `image` 参数会在一个`NativeImage` 中控制自定义鼠标图片,并且 `scale` 会控制图片的缩放比例。 +如果 `type` 参数值为 `custom`、 `image` 参数会在一个`NativeImage` 中控制自定义鼠标图片,并且 `scale` 、`size` 和 `hotspot`会控制自定义光标的属性。 -## 实例方法 +#### Event: 'context-menu' -`webContents` 对象有如下的实例方法: +Returns: -### `webContents.loadURL(url[, options])` +* `event` Event +* `params` Object + * `x` Integer - x 坐标 + * `y` Integer - y 坐标 + * `linkURL` String - 菜单中调用的节点的 URL 链接. + * `linkText` String - 链接的文本。当链接是一个图像时文本可以为空. + * `pageURL` String - 菜单被调用时顶级页面的 URL 链接. + * `frameURL` String - 菜单被调用时子级页面的 URL 链接. + * `srcURL` String - 菜单被调用时元素的原始链接。带有链接的元素可以为图像、音频和视频。 + * `mediaType` String - 菜单被调用时的节点类型。可以为 `none`, `image`, `audio`, `video`, `canvas`, `file` or `plugin`. + * `hasImageContents` Boolean - 菜单中是否含有图像. + * `isEditable` Boolean - 菜单是否可以被编辑. + * `selectionText` String - Text of the selection that the context menu was + invoked on. + * `titleText` String - Title or alt text of the selection that the context + was invoked on. + * `misspelledWord` String - The misspelled word under the cursor, if any. + * `frameCharset` String - The character encoding of the frame on which the + menu was invoked. + * `inputFieldType` String - If the context menu was invoked on an input + field, the type of that field. Possible values are `none`, `plainText`, + `password`, `other`. + * `menuSourceType` String - Input source that invoked the context menu. + Can be `none`, `mouse`, `keyboard`, `touch`, `touchMenu`. + * `mediaFlags` Object - The flags for the media element the context menu was + invoked on. + * `inError` Boolean - Whether the media element has crashed. + * `isPaused` Boolean - Whether the media element is paused. + * `isMuted` Boolean - Whether the media element is muted. + * `hasAudio` Boolean - Whether the media element has audio. + * `isLooping` Boolean - Whether the media element is looping. + * `isControlsVisible` Boolean - Whether the media element's controls are + visible. + * `canToggleControls` Boolean - Whether the media element's controls are + toggleable. + * `canRotate` Boolean - Whether the media element can be rotated. + * `editFlags` Object - These flags indicate whether the renderer believes it + is able to perform the corresponding action. + * `canUndo` Boolean - Whether the renderer believes it can undo. + * `canRedo` Boolean - Whether the renderer believes it can redo. + * `canCut` Boolean - Whether the renderer believes it can cut. + * `canCopy` Boolean - Whether the renderer believes it can copy + * `canPaste` Boolean - Whether the renderer believes it can paste. + * `canDelete` Boolean - Whether the renderer believes it can delete. + * `canSelectAll` Boolean - Whether the renderer believes it can select all. + +Emitted when there is a new context menu that needs to be handled. + +#### Event: 'select-bluetooth-device' + +Returns: + +* `event` Event +* `devices` [BluetoothDevice[]](structures/bluetooth-device.md) +* `callback` Function + * `deviceId` String + +Emitted when bluetooth device needs to be selected on call to +`navigator.bluetooth.requestDevice`. To use `navigator.bluetooth` api +`webBluetooth` should be enabled. If `event.preventDefault` is not called, +first available device will be selected. `callback` should be called with +`deviceId` to be selected, passing empty string to `callback` will +cancel the request. + +```javascript +const {app, webContents} = require('electron') +app.commandLine.appendSwitch('enable-web-bluetooth') + +app.on('ready', () => { + webContents.on('select-bluetooth-device', (event, deviceList, callback) => { + event.preventDefault() + let result = deviceList.find((device) => { + return device.deviceName === 'test' + }) + if (!result) { + callback('') + } else { + callback(result.deviceId) + } + }) +}) +``` + +#### Event: 'paint' + +Returns: + +* `event` Event +* `dirtyRect` [Rectangle](structures/rectangle.md) +* `image` [NativeImage](native-image.md) - The image data of the whole frame. + +Emitted when a new frame is generated. Only the dirty area is passed in the +buffer. + +```javascript +const {BrowserWindow} = require('electron') + +let win = new BrowserWindow({webPreferences: {offscreen: true}}) +win.webContents.on('paint', (event, dirty, image) => { + // updateBitmap(dirty, image.getBitmap()) +}) +win.loadURL('http://github.com') +``` + +#### Event: 'devtools-reload-page' +当 devtools 调试工具页面重新加载时发送 + +#### Event: 'will-attach-webview' + +Returns: + +* `event` Event +* `webPreferences` Object - The web preferences that will be used by the guest + page. This object can be modified to adjust the preferences for the guest + page. +* `params` Object - The other `` parameters such as the `src` URL. + This object can be modified to adjust the parameters of the guest page. + +Emitted when a ``'s web contents is being attached to this web +contents. Calling `event.preventDefault()` will destroy the guest page. + +This event can be used to configure `webPreferences` for the `webContents` +of a `` before it's loaded, and provides the ability to set settings +that can't be set via `` attributes. +### 实例方法 + + +#### `contents.loadURL(url[, options])` * `url` URL * `options` Object (可选) - * `httpReferrer` String - A HTTP Referrer url. + * `httpReferrer` String - HTTP 的 url 链接. * `userAgent` String - 产生请求的用户代理 * `extraHeaders` String - 以 "\n" 分隔的额外头 + * `postData` ([UploadRawData](structures/upload-raw-data.md) | [UploadFile](structures/upload-file.md) | [UploadFileSystem](structures/upload-file-system.md) | [UploadBlob](structures/upload-blob.md))[] - (optional) + * `baseURLForDataURL` String (optional) - 由数据URL加载的文件的基本URL(带尾随路径分隔符)。只有当指定的URL是一个数据URL并且需要加载其他文件时才需要设置。 -在窗口中加载 `url` 、 `url` 必须包含协议前缀, +在窗口中加载 `url`。 `url` 必须包含协议前缀, 比如 `http://` 或 `file://`。如果加载想要忽略 http 缓存,可以使用 `pragma` 头来达到目的。 ```javascript -const options = {'extraHeaders': 'pragma: no-cache\n'} -webContents.loadURL(url, options) +const {webContents} = require('electron') +const options = {extraHeaders: 'pragma: no-cache\n'} +webContents.loadURL('https://github.com', options) ``` -### `webContents.downloadURL(url)` +#### `contents.downloadURL(url)` * `url` URL 初始化一个指定 `url` 的资源下载,不导航跳转。 `session` 的 `will-download` 事件会触发。 -### `webContents.getURL()` +#### `contents.getURL()` -返回当前 page 的 url。 +Returns `String` - 当前页面的 URL. ```javascript -var win = new BrowserWindow({width: 800, height: 600}) +const {BrowserWindow} = require('electron') +let win = new BrowserWindow({width: 800, height: 600}) win.loadURL('http://github.com') -var currentURL = win.webContents.getURL() +let currentURL = win.webContents.getURL() +console.log(currentURL) ``` -### `webContents.getTitle()` +#### `contents.getTitle()` -返回当前 page 的标题。 +Returns `String` - 当前页面的标题. -### `webContents.isLoading()` +#### `contents.isDestroyed()` -返回一个布尔值,标识当前页是否正在加载。 +Returns `Boolean` - 当前的页面是否被销毁了 -### `webContents.isWaitingForResponse()` +#### `contents.isFocused()` -返回一个布尔值,标识当前页是否正在等待主要资源的第一次响应。 +Returns `Boolean` - 焦点是否在当前页面上. -### `webContents.stop()` +#### `contents.isLoading()` + +Returns `Boolean` - 当前页是否正在加载资源。 + +#### `contents.isLoadingMainFrame()` + +Returns `Boolean` - 是否主框架(而不仅是 iframe 或者在帧内)仍在加载中。 + +#### `contents.isWaitingForResponse()` + +Returns `Boolean` - 当前页是否正在等待主要资源的第一次响应。 + +#### `contents.stop()` 停止还为开始的导航。 -### `webContents.reload()` +#### `contents.reload()` 重载当前页。 -### `webContents.reloadIgnoringCache()` +#### `contents.reloadIgnoringCache()` 重载当前页,忽略缓存。 -### `webContents.canGoBack()` +#### `contents.canGoBack()` -返回一个布尔值,标识浏览器是否能回到前一个page。 +Returns `Boolean` - 浏览器是否能回到前一个页面。 -### `webContents.canGoForward()` +#### `contents.canGoForward()` -返回一个布尔值,标识浏览器是否能前往下一个page。 +Returns `Boolean` - 浏览器是否能前往下一个页面。 -### `webContents.canGoToOffset(offset)` +#### `contents.canGoToOffset(offset)` * `offset` Integer -返回一个布尔值,标识浏览器是否能前往指定 `offset` 的page。 +Returns `Boolean` - 页面是否能前往 `offset`。 -### `webContents.clearHistory()` +#### `contents.clearHistory()` 清除导航历史。 -### `webContents.goBack()` +#### `contents.goBack()` -让浏览器回退到前一个page。 +让浏览器回退到前一个页面。 -### `webContents.goForward()` +#### `contents.goForward()` -让浏览器回前往下一个page。 +让浏览器回前往下一个页面。 -### `webContents.goToIndex(index)` +#### `contents.goToIndex(index)` * `index` Integer -让浏览器回前往指定 `index` 的page。 +让浏览器回前往指定 `index` 的页面。 -### `webContents.goToOffset(offset)` +#### `contents.goToOffset(offset)` * `offset` Integer 导航到相对于当前页的偏移位置页。 -### `webContents.isCrashed()` +#### `contents.isCrashed()` -渲染进程是否崩溃。 +Returns `Boolean` - 渲染进程是否崩溃。 -### `webContents.setUserAgent(userAgent)` +#### `contents.setUserAgent(userAgent)` * `userAgent` String 重写本页用户代理。 -### `webContents.getUserAgent()` +#### `contents.getUserAgent()` -返回一个 `String` ,标识本页用户代理信息。 +Returns `String` - 页面的用户代理信息。 -### `webContents.insertCSS(css)` +#### `contents.insertCSS(css)` * `css` String 为当前页插入css。 -### `webContents.executeJavaScript(code[, userGesture, callback])` +#### `contents.executeJavaScript(code[, userGesture, callback])` * `code` String -* `userGesture` Boolean (可选) +* `userGesture` Boolean (可选) - 默认值为 `false` * `callback` Function (可选) - 脚本执行完成后调用的回调函数. - * `result` + * `result` Any -评估 page `代码`。 +Returns `Promise` - 成功了返回 resolves,失败了返回 rejected + +评估页面的 `code` 代码。 浏览器窗口中的一些 HTML API ,例如 `requestFullScreen`,只能被用户手势请求。设置 `userGesture` 为 `true` 可以取消这个限制。 -### `webContents.setAudioMuted(muted)` +如果执行的代码的结果是一个 promise,回调方法将为 promise 的 resolved 的值。我们建议您使用返回的 Promise 来处理导致结果的代码。 + +```js +contents.executeJavaScript('fetch("https://jsonplaceholder.typicode.com/users/1").then(resp => resp.json())', true) + .then((result) => { + console.log(result) // Will be the JSON object from the fetch call + }) +``` +#### `contents.setAudioMuted(muted)` * `muted` Boolean -减缓当前页的 audio 的播放速度。 +设置当前页为静音。 -### `webContents.isAudioMuted()` +#### `contents.isAudioMuted()` -返回一个布尔值,标识当前页是否减缓了 audio 的播放速度。 +Returns `Boolean` - 当前页是否为静音状态 -### `webContents.undo()` +#### `contents.setZoomFactor(factor)` -执行网页的编辑命令 `undo`。 +* `factor` Number - 缩放系数 -### `webContents.redo()` +改变缩放系数为指定的数值。缩放系数是缩放的百分比除以100,比如 300% = 3.0 -执行网页的编辑命令 `redo`。 +#### `contents.getZoomFactor(callback)` -### `webContents.cut()` +* `callback` Function + * `zoomFactor` Number -执行网页的编辑命令 `cut`。 +发送一个请求来获取当前的缩放系数,`callback` 回调方法会在 `callback(zoomFactor)` 中调用. -### `webContents.copy()` +#### `contents.setZoomLevel(level)` -执行网页的编辑命令 `copy`。 +* `level` Number - 缩放等级 -### `webContents.paste()` +改变缩放等级为指定的等级。原始大小为0,每升高或降低一次,代表 20% 的大小缩放。限制为原始大小的 300% 到 50%。 -执行网页的编辑命令 `paste`。 +#### `contents.getZoomLevel(callback)` -### `webContents.pasteAndMatchStyle()` +* `callback` Function + * `zoomLevel` Number -执行网页的编辑命令 `pasteAndMatchStyle`。 +发送一个请求来获取当前的缩放等级,`callback` 回调方法会在 `callback(zoomLevel)` 中调用. +#### `contents.setZoomLevelLimits(minimumLevel, maximumLevel)` -### `webContents.delete()` +* `minimumLevel` Number +* `maximumLevel` Number -执行网页的编辑命令 `delete`。 +**废弃:** 使用 `setVisualZoomLevelLimits` 来代替. 这个方法将在 Electron 2.0 中移除. -### `webContents.selectAll()` +#### `contents.setVisualZoomLevelLimits(minimumLevel, maximumLevel)` -执行网页的编辑命令 `selectAll`。 +* `minimumLevel` Number +* `maximumLevel` Number -### `webContents.unselect()` +设置最大和最小的手势缩放等级。 -执行网页的编辑命令 `unselect`。 +#### `contents.setLayoutZoomLevelLimits(minimumLevel, maximumLevel)` -### `webContents.replace(text)` +* `minimumLevel` Number +* `maximumLevel` Number + +设置最大和最小的 layout-based (i.e. non-visual) zoom level. + +#### `contents.undo()` + +在网页中执行编辑命令 `undo`。 + +#### `contents.redo()` + +在网页中执行编辑命令 `redo`。 + +#### `contents.cut()` + +在网页中执行编辑命令 `cut`。 + +#### `contents.copy()` + +在网页中执行编辑命令 `copy`。 + +#### `contents.copyImageAt(x, y)` + +* `x` Integer +* `y` Integer + +拷贝剪贴板中指定位置的图像. + +#### `contents.paste()` + +在网页中执行编辑命令 `paste`。 + +#### `contents.pasteAndMatchStyle()` + +在网页中执行编辑命令 `pasteAndMatchStyle`。 + +#### `contents.delete()` + +在网页中执行编辑命令 `delete`。 + +#### `contents.selectAll()` + +在网页中执行编辑命令 `selectAll`。 + +#### `contents.unselect()` + +在网页中执行编辑命令 `unselect`。 + +#### `contents.replace(text)` * `text` String -执行网页的编辑命令 `replace`。 +在网页中执行编辑命令 `replace`。 -### `webContents.replaceMisspelling(text)` +#### `contents.replaceMisspelling(text)` * `text` String -执行网页的编辑命令 `replaceMisspelling`。 +在网页中执行编辑命令 `replaceMisspelling`。 -### `webContents.insertText(text)` +#### `contents.insertText(text)` * `text` String 插入 `text` 到获得了焦点的元素。 -### `webContents.findInPage(text[, options])` +#### `contents.findInPage(text[, options])` * `text` String - 查找内容,不能为空。 * `options` Object (可选) - * `forward` Boolean - 是否向前或向后查找,默认为 `true`。 - * `findNext` Boolean - 当前操作是否是第一次查找或下一次查找, + * `forward` Boolean - (可选) 是否向前或向后查找,默认为 `true`。 + * `findNext` Boolean - (可选) 当前操作是否是第一次查找或下一次查找, 默认为 `false`。 - * `matchCase` Boolean - 查找是否区分大小写, + * `matchCase` Boolean - (可选) 查找是否区分大小写, 默认为 `false`。 - * `wordStart` Boolean -是否仅以首字母查找, + * `wordStart` Boolean - (可选) 是否仅以首字母查找, 默认为 `false`。 - * `medialCapitalAsWordStart` Boolean - 是否结合 `wordStart`,如果匹配是大写字母开头,后面接小写字母或无字母,那么就接受这个词中匹配。接受几个其它的合成词匹配,默认为 `false`。 + * `medialCapitalAsWordStart` Boolean - (可选) 是否结合 `wordStart`,如果匹配是大写字母开头,后面接小写字母或无字母,那么就接受这个词中匹配。接受几个其它的合成词匹配,默认为 `false`。 发起请求,在网页中查找所有与 `text` 相匹配的项,并且返回一个 `Integer` 来表示这个请求用的请求 Id。这个请求结果可以通过订阅 [`found-in-page`](web-contents.md#event-found-in-page) 事件来取得。 -### `webContents.stopFindInPage(action)` +#### `contents.stopFindInPage(action)` * `action` String - 指定一个行为来接替停止 [`webContents.findInPage`] 请求。 @@ -558,47 +804,62 @@ var currentURL = win.webContents.getURL() 使用给定的 `action` 来为 `webContents` 停止任何 `findInPage` 请求。 ```javascript -webContents.on('found-in-page', function (event, result) { +const {webContents} = require('electron') +webContents.on('found-in-page', (event, result) => { if (result.finalUpdate) webContents.stopFindInPage('clearSelection') }) const requestId = webContents.findInPage('api') +console.log(requestId) ``` -### `webContents.hasServiceWorker(callback)` +#### `contents.capturePage([rect, ]callback)` + +* `rect` [Rectangle](structures/rectangle.md) (optional) - 页面被捕捉的区域 +* `callback` Function + * `image` [NativeImage](native-image.md) + +捕捉页面 `rect` 区域的快照。在完成后 `callback` 方法会通过 `callback(image)` 调用 。`image` 是 [NativeImage](native-image.md) 的实例。省略 `rect` 参数会捕捉整个页面的可视区域。 + +#### `contents.hasServiceWorker(callback)` * `callback` Function - + * `hasWorker` Boolean + 检查是否有任何 ServiceWorker 注册了,并且返回一个布尔值,来作为 `callback`响应的标识。 -### `webContents.unregisterServiceWorker(callback)` +#### `contents.unregisterServiceWorker(callback)` * `callback` Function - + * `success` Boolean + 如果存在任何 ServiceWorker,则全部注销,并且当JS承诺执行行或JS拒绝执行而失败的时候,返回一个布尔值,它标识了相应的 `callback`。 -### `webContents.print([options])` +#### `contents.print([options])` * `options` Object (可选) * `silent` Boolean - 不需要请求用户的打印设置. 默认为 `false`。 * `printBackground` Boolean - 打印背景和网页图片. 默认为 `false`。 -打印窗口的网页。当设置 `silent` 为 `false` 的时候,Electron 将使用系统默认的打印机和打印方式来打印。 +打印窗口的网页。当设置 `silent` 为 `true` 的时候,Electron 将使用系统默认的打印机和打印方式来打印。 在网页中调用 `window.print()` 和 调用 `webContents.print({silent: false, printBackground: false})`具有相同的作用。 -**注意:** 在 Windows,打印 API 依赖于 `pdf.dll`。如果你的应用不使用任何的打印,你可以安全删除 `pdf.dll` 来减少二进制文件的size。 +使用 `page-break-before: always; ` CSS 的样式将强制打印到一个新的页面. -### `webContents.printToPDF(options, callback)` +#### `contents.printToPDF(options, callback)` * `options` Object * `marginsType` Integer - 指定使用的 margin type。默认 margin 使用 0,无 margin 使用 1,最小化 margin 使用 2。 * `pageSize` String - 指定生成的PDF文件的page size. 可以是 `A3`、 - `A4`、 `A5`、 `Legal`、`Letter` 和 `Tabloid`。 + `A4`、 `A5`、 `Legal`、`Letter` 和 `Tabloid`。或者是一个包含 `height` + 和 `width` 的对象,单位是微米。 * `printBackground` Boolean - 是否打印 css 背景。 * `printSelectionOnly` Boolean - 是否只打印选中的部分。 * `landscape` Boolean - landscape 为 `true`, portrait 为 `false`。 * `callback` Function + * `error` Error + * `data` Buffer 打印窗口的网页为 pdf,使用 Chromium 预览打印的自定义设置。 @@ -615,18 +876,22 @@ const requestId = webContents.findInPage('api') } ``` +使用 `page-break-before: always; ` CSS 的样式将强制打印到一个新的页面. + +一个 `webContents.printToPDF` 的示例: + ```javascript -const BrowserWindow = require('electron').BrowserWindow +const {BrowserWindow} = require('electron') const fs = require('fs') -var win = new BrowserWindow({width: 800, height: 600}) +let win = new BrowserWindow({width: 800, height: 600}) win.loadURL('http://github.com') -win.webContents.on('did-finish-load', function () { +win.webContents.on('did-finish-load', () => { // Use default printing options - win.webContents.printToPDF({}, function (error, data) { + win.webContents.printToPDF({}, (error, data) => { if (error) throw error - fs.writeFile('/tmp/print.pdf', data, function (error) { + fs.writeFile('/tmp/print.pdf', data, (error) => { if (error) throw error console.log('Write PDF successfully.') }) @@ -634,59 +899,61 @@ win.webContents.on('did-finish-load', function () { }) ``` -### `webContents.addWorkSpace(path)` +#### `contents.addWorkSpace(path)` * `path` String 添加指定的路径给开发者工具栏的 workspace。必须在 DevTools 创建之后使用它: ```javascript -mainWindow.webContents.on('devtools-opened', function () { - mainWindow.webContents.addWorkSpace(__dirname) +const {BrowserWindow} = require('electron') +let win = new BrowserWindow() +win.webContents.on('devtools-opened', () => { + win.webContents.addWorkSpace(__dirname) }) ``` -### `webContents.removeWorkSpace(path)` +#### `contents.removeWorkSpace(path)` * `path` String 从开发者工具栏的 workspace 删除指定的路径。 -### `webContents.openDevTools([options])` +#### `contents.openDevTools([options])` * `options` Object (可选) - * `detach` Boolean - 在一个新窗口打开开发者工具栏 + * `mode` String - 打开开发者工具的状态属性。可以为 `right`, `bottom`, `undocked`, `detach`。默认值为上一次使用时的状态。在`undocked`模式可以把工具栏放回来,`detach`模式不可以。 打开开发者工具栏。 -### `webContents.closeDevTools()` +#### `contents.closeDevTools()` 关闭开发者工具栏。 -### `webContents.isDevToolsOpened()` +#### `contents.isDevToolsOpened()` -返回布尔值,开发者工具栏是否打开。 +Returns `Boolean` - 开发者工具栏是否打开。 -### `webContents.isDevToolsFocused()` +#### `contents.isDevToolsFocused()` -返回布尔值,开发者工具栏视图是否获得焦点。 +Returns `Boolean` - 开发者工具栏视图是否获得焦点。 -### `webContents.toggleDevTools()` +#### `contents.toggleDevTools()` Toggles 开发者工具。 -### `webContents.inspectElement(x, y)` +#### `contents.inspectElement(x, y)` * `x` Integer * `y` Integer 在 (`x`, `y`) 开始检测元素。 -### `webContents.inspectServiceWorker()` +#### `contents.inspectServiceWorker()` 为 service worker 上下文打开开发者工具栏。 -### `webContents.send(channel[, arg1][, arg2][, ...])` +#### `contents.send(channel[, arg1][, arg2][, ...])` * `channel` String * `arg` (可选) @@ -695,16 +962,18 @@ Toggles 开发者工具。 渲染进程可以通过使用 `ipcRenderer` 监听 `channel` 来处理消息。 -例子,从主进程向渲染进程发送消息: +从主进程向渲染进程发送消息的示例: ```javascript // 主进程. -var window = null -app.on('ready', function () { - window = new BrowserWindow({width: 800, height: 600}) - window.loadURL(`file://${__dirname}/index.html`) - window.webContents.on('did-finish-load', function () { - window.webContents.send('ping', 'whoooooooh!') +const {app, BrowserWindow} = require('electron') +let win = null + +app.on('ready', () => { + win = new BrowserWindow({width: 800, height: 600}) + win.loadURL(`file://${__dirname}/index.html`) + win.webContents.on('did-finish-load', () => { + win.webContents.send('ping', 'whoooooooh!') }) }) ``` @@ -714,31 +983,31 @@ app.on('ready', function () { ``` -### `webContents.enableDeviceEmulation(parameters)` +#### `contents.enableDeviceEmulation(parameters)` -`parameters` Object, properties: +`parameters` Object, 属性为: * `screenPosition` String - 指定需要模拟的屏幕 (默认 : `desktop`) - * `desktop` - * `mobile` -* `screenSize` Object - 设置模拟屏幕 size (screenPosition == mobile) + * `desktop` - 桌面屏幕模式 + * `mobile` - 手机屏幕模式 +* `screenSize` Object - 设置模拟屏幕的尺寸 (screenPosition == mobile) * `width` Integer - 设置模拟屏幕 width * `height` Integer - 设置模拟屏幕 height -* `viewPosition` Object - 在屏幕放置 view +* `viewPosition` Object - 屏幕中可视区域的位置 (screenPosition == mobile) (默认: `{x: 0, y: 0}`) * `x` Integer - 设置偏移左上角的x轴 * `y` Integer - 设置偏移左上角的y轴 -* `deviceScaleFactor` Integer - 设置设备比例因子 (如果为0,默认为原始屏幕比例) (默认: `0`) -* `viewSize` Object - 设置模拟视图 size (空表示不覆盖) +* `deviceScaleFactor` Integer - 设置设备缩放比例系数 (如果为0,默认为原始屏幕比例) (默认: `0`) +* `viewSize` Object - 设置模拟视图的尺寸 (空表示不覆盖) * `width` Integer - 设置模拟视图 width * `height` Integer - 设置模拟视图 height * `fitToView` Boolean - 如果有必要的话,是否把模拟视图按比例缩放来适应可用空间 (默认: `false`) @@ -749,32 +1018,30 @@ app.on('ready', function () { 使用给定的参数来开启设备模拟。 -### `webContents.disableDeviceEmulation()` +#### `contents.disableDeviceEmulation()` -使用 `webContents.enableDeviceEmulation` 关闭设备模拟。 +关闭模拟器,使用 `webContents.enableDeviceEmulation` 来启用。 -### `webContents.sendInputEvent(event)` +#### `contents.sendInputEvent(event)` * `event` Object * `type` String (**必需**) - 事件类型,可以是 `mouseDown`, `mouseUp`, `mouseEnter`, `mouseLeave`, `contextMenu`, `mouseWheel`, `mouseMove`, `keyDown`, `keyUp`, `char`. - * `modifiers` Array - 事件的 modifiers 数组, 可以是 - include `shift`, `control`, `alt`, `meta`, `isKeypad`, `isAutoRepeat`, + * `modifiers` String[] - 事件的 modifiers 数组, 可以包含 `shift`, `control`, `alt`, `meta`, `isKeypad`, `isAutoRepeat`, `leftButtonDown`, `middleButtonDown`, `rightButtonDown`, `capsLock`, `numLock`, `left`, `right`. -向 page 发送一个输入 `event`。 +向页面发送一个输入 `event`。 对键盘事件来说,`event` 对象还有如下属性: -* `keyCode` String (**必需**) - 特点是将作为键盘事件发送。可用的 key codes [Accelerator](accelerator.md)。 - +* `keyCode` String (**必需**) - 将字符串作为键盘事件发送。可用的 key codes [Accelerator](accelerator.md)。 对鼠标事件来说,`event` 对象还有如下属性: -* `x` Integer (**required**) -* `y` Integer (**required**) +* `x` Integer (**必需**) +* `y` Integer (**必需**) * `button` String - button 按下, 可以是 `left`, `middle`, `right` * `globalX` Integer * `globalY` Integer @@ -793,122 +1060,151 @@ app.on('ready', function () { * `hasPreciseScrollingDeltas` Boolean * `canScroll` Boolean -### `webContents.beginFrameSubscription(callback)` - +#### `contents.beginFrameSubscription(callback)` +* `onlyDirty` Boolean (可选) - 默认值为 `false` * `callback` Function - + * `frameBuffer` Buffer + * `dirtyRect` [Rectangle](structures/rectangle.md) + 开始订阅 提交 事件和捕获数据帧,当有提交事件时,使用 `callback(frameBuffer)` 调用 `callback`。 `frameBuffer` 是一个包含原始像素数据的 `Buffer`,像素数据是按照 32bit BGRA 格式有效存储的,但是实际情况是取决于处理器的字节顺序的(大多数的处理器是存放小端序的,如果是在大端序的处理器上,数据是 32bit ARGB 格式)。 -### `webContents.endFrameSubscription()` +`dirtyRect` 脏区域是一个包含 `x, y, width, height` 属性的对象,它们描述了一个页面中的重绘区域。如果 `onlyDirty` 被设置为`true`, `frameBuffer` 将只包含重绘区域。`onlyDirty` 的默认值为 `false`. + +#### `contents.endFrameSubscription()` 停止订阅帧提交事件。 -### `webContents.savePage(fullPath, saveType, callback)` +#### `contents.startDrag(item)` + +* `item` Object + * `file` String 或 `files` Array - 要拖拽的文件(可以为多个)的路径。 + * `icon` [NativeImage](native-image.md) - 在 macOS 中图标不能为空. + +设置 `item` 作为当前拖拽操作的对象。`file` 是作为拖拽文件的绝对路径。`icon` 是拖拽时光标下面显示的图像。 + +#### `contents.savePage(fullPath, saveType, callback)` * `fullPath` String - 文件的完整路径. * `saveType` String - 指定保存类型. * `HTMLOnly` - 只保存html. * `HTMLComplete` - 保存整个 page 内容. * `MHTML` - 保存完整的 html 为 MHTML. -* `callback` Function - `function(error) {}`. +* `callback` Function - `(error) => {}`. * `error` Error -如果保存界面过程初始化成功,返回 true。 +Returns `Boolean` - 如果保存界面过程初始化成功,返回 true。 ```javascript +const {BrowserWindow} = require('electron') +let win = new BrowserWindow() + win.loadURL('https://github.com') -win.webContents.on('did-finish-load', function () { - win.webContents.savePage('/tmp/test.html', 'HTMLComplete', function (error) { +win.webContents.on('did-finish-load', () => { + win.webContents.savePage('/tmp/test.html', 'HTMLComplete', (error) => { if (!error) console.log('Save page successfully') }) }) ``` -## 实例属性 +#### `contents.showDefinitionForSelection()` _macOS_ + +在页面上显示搜索的弹窗。 + +#### `contents.setSize(options)` + +设置页面的大小。This is only supported for `` guest contents. + +* `options` Object + * `normal` Object (可选) - Normal size of the page. This can be used in + combination with the [`disableguestresize`](web-view-tag.md#disableguestresize) + attribute to manually resize the webview guest contents. + * `width` Integer + * `height` Integer + +#### `contents.isOffscreen()` + +Returns `Boolean` - 表明是否设置了 *offscreen rendering*. + +#### `contents.startPainting()` + +如果设置了 *offscreen rendering* 并且没有绘制,开始绘制. + +#### `contents.stopPainting()` + +如果设置了 *offscreen rendering* 并且绘制了,停止绘制. + +#### `contents.isPainting()` + +Returns `Boolean` - 如果设置了 *offscreen rendering* ,返回当前是否正在绘制. + +#### `contents.setFrameRate(fps)` + +* `fps` Integer + +如果设置了 *offscreen rendering*,设置帧频为制定数值。有效范围为1-60. + +#### `contents.getFrameRate()` + +Returns `Integer` - 如果设置了 *offscreen rendering*,返回当前的帧频 + +#### `contents.invalidate()` + +全部重新绘制整个页面的内容。 + +如果设置了*offscreen rendering* ,让页面失效并且生成一个新的`'paint'`事件 + +#### `contents.getWebRTCIPHandlingPolicy()` + +Returns `String` - Returns the WebRTC IP Handling Policy. + +#### `contents.setWebRTCIPHandlingPolicy(policy)` + +* `policy` String - Specify the WebRTC IP Handling Policy. + * `default` - Exposes user's public and local IPs. This is the default + behavior. When this policy is used, WebRTC has the right to enumerate all + interfaces and bind them to discover public interfaces. + * `default_public_interface_only` - Exposes user's public IP, but does not + expose user's local IP. When this policy is used, WebRTC should only use the + default route used by http. This doesn't expose any local addresses. + * `default_public_and_private_interfaces` - Exposes user's public and local + IPs. When this policy is used, WebRTC should only use the default route used + by http. This also exposes the associated default private address. Default + route is the route chosen by the OS on a multi-homed endpoint. + * `disable_non_proxied_udp` - Does not expose public or local IPs. When this + policy is used, WebRTC should only use TCP to contact peers or servers unless + the proxy server supports UDP. + +Setting the WebRTC IP handling policy allows you to control which IPs are +exposed via WebRTC. See [BrowserLeaks](https://browserleaks.com/webrtc) for +more details. + +### 实例属性 `WebContents` 对象也有下列属性: -### `webContents.session` +#### `contents.id` + +表明 WebContents 唯一标示的整数 + +#### `contents.session` 返回这个 `webContents` 使用的 [session](session.md) 对象。 -### `webContents.hostWebContents` +#### `contents.hostWebContents` -返回这个 `webContents` 的父 `webContents`。 +返回这个 `webContents` 的父 [`WebContents`](web-contents.md)。 -### `webContents.devToolsWebContents` +#### `contents.devToolsWebContents` 获取这个 `WebContents` 的开发者工具栏的 `WebContents`。 **注意:** 用户不可保存这个对象,因为当开发者工具栏关闭的时候它的值为 `null`。 -### `webContents.debugger` +#### `contents.debugger` -调试 API 为 [remote debugging protocol][rdp] 提供交替传送。 +webContents 的 [Debugger](debugger.md) 实例. -```javascript -try { - win.webContents.debugger.attach('1.1') -} catch (err) { - console.log('Debugger attach failed : ', err) -} - -win.webContents.debugger.on('detach', function (event, reason) { - console.log('Debugger detached due to : ', reason) -}) - -win.webContents.debugger.on('message', function (event, method, params) { - if (method === 'Network.requestWillBeSent') { - if (params.request.url === 'https://www.github.com') { - win.webContents.debugger.detach() - } - } -}) - -win.webContents.debugger.sendCommand('Network.enable') -``` - -#### `webContents.debugger.attach([protocolVersion])` - -* `protocolVersion` String (可选) - 请求调试协议版本。 - -添加 `webContents` 调试。 - -#### `webContents.debugger.isAttached()` - -返回一个布尔值,标识是否已经给 `webContents` 添加了调试。 - -#### `webContents.debugger.detach()` - -删除 `webContents` 调试。 - -#### `webContents.debugger.sendCommand(method[, commandParams, callback])` - -* `method` String - 方法名, 应该是由远程调试协议定义的方法。 -* `commandParams` Object (可选) - 请求参数为 JSON 对象。 -* `callback` Function (可选) - Response - * `error` Object - 错误消息,标识命令失败。 - * `result` Object - 回复在远程调试协议中由 'returns'属性定义的命令描述。 - -发送给定命令给调试目标。 - -### Event: 'detach' - -* `event` Event -* `reason` String - 拆分调试器原因。 - -在调试 session 结束时发出事件.这在 `webContents` 关闭时或 `webContents` 请求开发者工具栏时发生。 - -### Event: 'message' - -* `event` Event -* `method` String - 方法名。 -* `params` Object - 在远程调试协议中由 'parameters' 属性定义的事件参数。 - -每当调试目标发出事件时发出。 - -[rdp]: https://developer.chrome.com/devtools/docs/debugger-protocol -[`webContents.findInPage`]: web-contents.md#webcontentsfindinpagetext-options +[keyboardevent]: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent diff --git a/docs-translations/zh-CN/tutorial/electron-versioning.md b/docs-translations/zh-CN/tutorial/electron-versioning.md index 18bd3735404f..7027347b3927 100644 --- a/docs-translations/zh-CN/tutorial/electron-versioning.md +++ b/docs-translations/zh-CN/tutorial/electron-versioning.md @@ -5,7 +5,7 @@ 版本号使用参照以下规则: * 主要版本: 适用于 Electron API 的突破性变更 - 如果您从 `0.37.0` 升级到 `1.0.0`, 您将需要升级您的应用程序。 -* 次要版本: 适用于 Chrome 主要版本 和 Node 次要版本升级; 或重大的 Electron 变动 - 如果您从 `0.37.0` 升级到 `1.0.0`, 您的应用程序仍然可以正常运行, 但你可能需要解决一些小幅的变动。 -* 补丁版本: 适用于新功能的添加和 bug 修复 - 如果您从 `0.37.0` 升级到 `1.0.0`, 你的应用程序仍然像之前一样正常运行。 +* 次要版本: 适用于 Chrome 主要版本 和 Node 次要版本升级; 或重大的 Electron 变动 - 如果您从 `1.0.0` 升级到 `1.1.0`, 您的应用程序仍然可以正常运行, 但你可能需要解决一些小幅的变动。 +* 补丁版本: 适用于新功能的添加和 bug 修复 - 如果您从 `1.0.0` 升级到 `1.0.1`, 你的应用程序仍然像之前一样正常运行。 如果你使用 `electron` 或 `electron-prebuilt`,我们建议您设置固定的版本号(如 1.1.0 而不是 ^1.1.0),以确保Electron的所有升级都是由您(开发人员)进行的手动操作。 diff --git a/docs/README.md b/docs/README.md index 56a423f491de..24b23f92a760 100644 --- a/docs/README.md +++ b/docs/README.md @@ -103,3 +103,6 @@ an issue: * [Debug Instructions (Windows)](development/debug-instructions-windows.md) * [Setting Up Symbol Server in debugger](development/setting-up-symbol-server.md) * [Documentation Styleguide](styleguide.md) +* [Updating Chrome](development/updating-chrome.md) +* [Chromium Development](development/chromium-development.md) +* [V8 Development](development/v8-development.md) diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index a21033df4019..2eacb865adbb 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -211,6 +211,9 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. width of the web page when zoomed, `false` will cause it to zoom to the width of the screen. This will also affect the behavior when calling `maximize()` directly. Default is `false`. + * `tabbingIdentifier` String (optional) - Tab group name, allows opening the + window as a native tab on macOS 10.12+. Windows with the same tabbing + identifier will be grouped together. * `webPreferences` Object (optional) - Settings of web page's features. * `devTools` Boolean (optional) - Whether to enable DevTools. If it is set to `false`, can not use `BrowserWindow.webContents.openDevTools()` to open DevTools. Default is `true`. * `nodeIntegration` Boolean (optional) - Whether node integration is enabled. Default @@ -225,6 +228,13 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. When node integration is turned off, the preload script can reintroduce Node global symbols back to the global scope. See example [here](process.md#event-loaded). + * `sandbox` Boolean (optional) - If set, this will sandbox the renderer + associated with the window, making it compatible with the Chromium + OS-level sandbox and disabling the Node.js engine. This is not the same as + the `nodeIntegration` option and the APIs available to the preload script + are more limited. Read more about the option [here](sandbox-option.md). + **Note:** This option is currently experimental and may change or be + removed in future Electron releases. * `session` [Session](session.md#class-session) (optional) - Sets the session used by the page. Instead of passing the Session object directly, you can also choose to use the `partition` option instead, which accepts a partition string. When @@ -282,7 +292,6 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. window. Defaults to `false`. See the [offscreen rendering tutorial](../tutorial/offscreen-rendering.md) for more details. - * `sandbox` Boolean (optional) - Whether to enable Chromium OS-level sandbox. * `contextIsolation` Boolean (optional) - Whether to run Electron APIs and the specified `preload` script in a separate JavaScript context. Defaults to `false`. The context that the `preload` script runs in will still diff --git a/docs/api/client-request.md b/docs/api/client-request.md index 0b722f2f09d3..9821b44261a5 100644 --- a/docs/api/client-request.md +++ b/docs/api/client-request.md @@ -29,6 +29,11 @@ the hostname and the port number 'hostname:port' * `hostname` String (optional) - The server host name. * `port` Integer (optional) - The server's listening port number. * `path` String (optional) - The path part of the request URL. + * `redirect` String (optional) - The redirect mode for this request. Should be +one of `follow`, `error` or `manual`. Defaults to `follow`. When mode is `error`, +any redirection will be aborted. When mode is `manual` the redirection will be +deferred until [`request.followRedirect`](#requestfollowRedirect) is invoked. Listen for the [`redirect`](#event-redirect) event in +this mode to get more details about the redirect request. `options` properties such as `protocol`, `host`, `hostname`, `port` and `path` strictly follow the Node.js model as described in the @@ -65,6 +70,8 @@ Returns: * `port` Integer * `realm` String * `callback` Function + * `username` String + * `password` String Emitted when an authenticating proxy is asking for user credentials. @@ -119,6 +126,19 @@ Emitted as the last event in the HTTP request-response transaction. The `close` event indicates that no more events will be emitted on either the `request` or `response` objects. + +#### Event: 'redirect' + +Returns: + +* `statusCode` Integer +* `method` String +* `redirectUrl` String +* `responseHeaders` Object + +Emitted when there is redirection and the mode is `manual`. Calling +[`request.followRedirect`](#requestfollowRedirect) will continue with the redirection. + ### Instance Properties #### `request.chunkedEncoding` @@ -138,17 +158,18 @@ internally buffered inside Electron process memory. #### `request.setHeader(name, value)` * `name` String - An extra HTTP header name. -* `value` String - An extra HTTP header value. +* `value` Object - An extra HTTP header value. Adds an extra HTTP header. The header name will issued as it is without lowercasing. It can be called only before first write. Calling this method after -the first write will throw an error. +the first write will throw an error. If the passed value is not a `String`, its +`toString()` method will be called to obtain the final value. #### `request.getHeader(name)` * `name` String - Specify an extra header name. -Returns String - The value of a previously set extra header name. +Returns Object - The value of a previously set extra header name. #### `request.removeHeader(name)` @@ -190,3 +211,7 @@ Cancels an ongoing HTTP transaction. If the request has already emitted the `close` event, the abort operation will have no effect. Otherwise an ongoing event will emit `abort` and `close` events. Additionally, if there is an ongoing response object,it will emit the `aborted` event. + +#### `request.followRedirect()` + +Continues any deferred redirection request when the redirection mode is `manual`. diff --git a/docs/api/crash-reporter.md b/docs/api/crash-reporter.md index 98bf0f3662f2..2f0848c1193e 100644 --- a/docs/api/crash-reporter.md +++ b/docs/api/crash-reporter.md @@ -40,7 +40,7 @@ The `crashReporter` module has the following methods: * `companyName` String (optional) * `submitURL` String - URL that crash reports will be sent to as POST. * `productName` String (optional) - Defaults to `app.getName()`. - * `uploadToServer` Boolean (optional) _Linux_ _macOS_ - Whether crash reports should be sent to the server + * `uploadToServer` Boolean (optional) - Whether crash reports should be sent to the server Default is `true`. * `ignoreSystemCrashHandler` Boolean (optional) - Default is `false`. * `extra` Object (optional) - An object you can define that will be sent along with the diff --git a/docs/api/desktop-capturer.md b/docs/api/desktop-capturer.md index 85755dc03ef9..f552753445d4 100644 --- a/docs/api/desktop-capturer.md +++ b/docs/api/desktop-capturer.md @@ -60,8 +60,9 @@ The `desktopCapturer` module has the following methods: * `options` Object * `types` String[] - An array of Strings that lists the types of desktop sources to be captured, available types are `screen` and `window`. - * `thumbnailSize` Object (optional) - The suggested size that the media source - thumbnail should be scaled to, defaults to `{width: 150, height: 150}`. + * `thumbnailSize` Object (optional) - The size that the media source thumbnail should be scaled to. + * `width` Integer - Default is `150` + * `height` Integer - Default is `150` * `callback` Function * `error` Error * `sources` [DesktopCapturerSource[]](structures/desktop-capturer-source.md) diff --git a/docs/api/dialog.md b/docs/api/dialog.md index 230930abd948..8885c315144b 100644 --- a/docs/api/dialog.md +++ b/docs/api/dialog.md @@ -46,16 +46,8 @@ The `dialog` module has the following methods: * `noResolveAliases` - Disable the automatic alias (symlink) path resolution. Selected aliases will now return the alias path instead of their target path. _macOS_ - * `normalizeAccessKeys` Boolean (optional) - Normalize the keyboard access keys - across platforms. Default is `false`. Enabling this assumes `&` is used in - the button labels for the placement of the keyboard shortcut access key - and labels will be converted so they work correctly on each platform, `&` - characters are removed on macOS, converted to `_` on Linux, and left - untouched on Windows. For example, a button label of `Vie&w` will be - converted to `Vie_w` on Linux and `View` on macOS and can be selected - via `Alt-W` on Windows and Linux. - * `message` String (optional) _macOS_ - Message to display above input - boxes. + * `message` String (optional) _macOS_ - Message to display above input + boxes. * `callback` Function (optional) * `filePaths` String[] - An array of file paths chosen by the user @@ -147,6 +139,14 @@ will be passed via `callback(filename)` 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 set `noLink` to `true`. + * `normalizeAccessKeys` Boolean (optional) - Normalize the keyboard access keys + across platforms. Default is `false`. Enabling this assumes `&` is used in + the button labels for the placement of the keyboard shortcut access key + and labels will be converted so they work correctly on each platform, `&` + characters are removed on macOS, converted to `_` on Linux, and left + untouched on Windows. For example, a button label of `Vie&w` will be + converted to `Vie_w` on Linux and `View` on macOS and can be selected + via `Alt-W` on Windows and Linux. * `callback` Function (optional) * `response` Number - The index of the button that was clicked * `checkboxChecked` Boolean - The checked state of the checkbox if diff --git a/docs/api/menu-item.md b/docs/api/menu-item.md index 1c12d9fdacd1..494bdb74f196 100644 --- a/docs/api/menu-item.md +++ b/docs/api/menu-item.md @@ -15,7 +15,7 @@ See [`Menu`](menu.md) for examples. * `browserWindow` BrowserWindow * `event` Event * `role` String (optional) - Define the action of the menu item, when specified the - `click` property will be ignored. + `click` property will be ignored. See [roles](#roles). * `type` String (optional) - Can be `normal`, `separator`, `submenu`, `checkbox` or `radio`. * `label` String - (optional) @@ -36,12 +36,16 @@ See [`Menu`](menu.md) for examples. * `position` String (optional) - This field allows fine-grained definition of the specific location within a given menu. +### Roles + +Roles allow menu items to have predefined behaviors. + It is best to specify `role` for any menu item that matches a standard role, rather than trying to manually implement the behavior in a `click` function. The built-in `role` behavior will give the best native experience. -The `label` and `accelerator` are optional when using a `role` and will default -to appropriate values for each platform. +The `label` and `accelerator` values are optional when using a `role` and will +default to appropriate values for each platform. The `role` property can have following values: @@ -63,8 +67,10 @@ The `role` property can have following values: * `resetzoom` - Reset the focused page's zoom level to the original size * `zoomin` - Zoom in the focused page by 10% * `zoomout` - Zoom out the focused page by 10% +* `editMenu` - Whole default "Edit" menu (Undo, Copy, etc.) +* `windowMenu` - Whole default "Window" menu (Minimize, Close, etc.) -On macOS `role` can also have following additional values: +The following additional roles are avaiable on macOS: * `about` - Map to the `orderFrontStandardAboutPanel` action * `hide` - Map to the `hide` action @@ -78,8 +84,8 @@ On macOS `role` can also have following additional values: * `help` - The submenu is a "Help" menu * `services` - The submenu is a "Services" menu -When specifying `role` on macOS, `label` and `accelerator` are the only options -that will affect the MenuItem. All other options will be ignored. +When specifying a `role` on macOS, `label` and `accelerator` are the only +options that will affect the menu item. All other options will be ignored. ### Instance Properties diff --git a/docs/api/menu.md b/docs/api/menu.md index cd6817625a35..42724e1d4afc 100644 --- a/docs/api/menu.md +++ b/docs/api/menu.md @@ -30,8 +30,8 @@ Returns `Menu` - The application menu, if set, or `null`, if not set. * `action` String Sends the `action` to the first responder of application. This is used for -emulating default Cocoa menu behaviors, usually you would just use the -`role` property of `MenuItem`. +emulating default macOS menu behaviors. Usually you would just use the +[`role`](menu-item.md#roles) property of a [`MenuItem`](menu-item.md). See the [macOS Cocoa Event Handling Guide](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/EventOverview/EventArchitecture/EventArchitecture.html#//apple_ref/doc/uid/10000060i-CH3-SW7) for more information on macOS' native actions. @@ -115,76 +115,36 @@ const template = [ { label: 'Edit', submenu: [ - { - role: 'undo' - }, - { - role: 'redo' - }, - { - type: 'separator' - }, - { - role: 'cut' - }, - { - role: 'copy' - }, - { - role: 'paste' - }, - { - role: 'pasteandmatchstyle' - }, - { - role: 'delete' - }, - { - role: 'selectall' - } + {role: 'undo'}, + {role: 'redo'}, + {type: 'separator'}, + {role: 'cut'}, + {role: 'copy'}, + {role: 'paste'}, + {role: 'pasteandmatchstyle'}, + {role: 'delete'}, + {role: 'selectall'} ] }, { label: 'View', submenu: [ - { - role: 'reload' - }, - { - role: 'forcereload' - }, - { - role: 'toggledevtools' - }, - { - type: 'separator' - }, - { - role: 'resetzoom' - }, - { - role: 'zoomin' - }, - { - role: 'zoomout' - }, - { - type: 'separator' - }, - { - role: 'togglefullscreen' - } + {role: 'reload'}, + {role: 'forcereload'}, + {role: 'toggledevtools'}, + {type: 'separator'}, + {role: 'resetzoom'}, + {role: 'zoomin'}, + {role: 'zoomout'}, + {type: 'separator'}, + {role: 'togglefullscreen'} ] }, { role: 'window', submenu: [ - { - role: 'minimize' - }, - { - role: 'close' - } + {role: 'minimize'}, + {role: 'close'} ] }, { @@ -202,76 +162,37 @@ if (process.platform === 'darwin') { template.unshift({ label: app.getName(), submenu: [ - { - role: 'about' - }, - { - type: 'separator' - }, - { - role: 'services', - submenu: [] - }, - { - type: 'separator' - }, - { - role: 'hide' - }, - { - role: 'hideothers' - }, - { - role: 'unhide' - }, - { - type: 'separator' - }, - { - role: 'quit' - } + {role: 'about'}, + {type: 'separator'}, + {role: 'services', submenu: []}, + {type: 'separator'}, + {role: 'hide'}, + {role: 'hideothers'}, + {role: 'unhide'}, + {type: 'separator'}, + {role: 'quit'} ] }) - // Edit menu. + + // Edit menu template[1].submenu.push( - { - type: 'separator' - }, + {type: 'separator'}, { label: 'Speech', submenu: [ - { - role: 'startspeaking' - }, - { - role: 'stopspeaking' - } + {role: 'startspeaking'}, + {role: 'stopspeaking'} ] } ) - // Window menu. + + // Window menu template[3].submenu = [ - { - label: 'Close', - accelerator: 'CmdOrCtrl+W', - role: 'close' - }, - { - label: 'Minimize', - accelerator: 'CmdOrCtrl+M', - role: 'minimize' - }, - { - label: 'Zoom', - role: 'zoom' - }, - { - type: 'separator' - }, - { - label: 'Bring All to Front', - role: 'front' - } + {role: 'close'}, + {role: 'minimize'}, + {role: 'zoom'}, + {type: 'separator'}, + {role: 'front'} ] } diff --git a/docs/api/process.md b/docs/api/process.md index 951abc7df1af..84320ca98bb0 100644 --- a/docs/api/process.md +++ b/docs/api/process.md @@ -32,38 +32,38 @@ process.once('loaded', () => { ### `process.noAsar` -Setting this to `true` can disable the support for `asar` archives in Node's -built-in modules. +A `Boolean` that controls ASAR support inside your application. Setting this to `true` +will disable the support for `asar` archives in Node's built-in modules. ### `process.type` -Current process's type, can be `"browser"` (i.e. main process) or `"renderer"`. +A `String` representing the current process's type, can be `"browser"` (i.e. main process) or `"renderer"`. ### `process.versions.electron` -Electron's version string. +A `String` representing Electron's version string. ### `process.versions.chrome` -Chrome's version string. +A `String` representing Chrome's version string. ### `process.resourcesPath` -Path to the resources directory. +A `String` representing the path to the resources directory. ### `process.mas` -For Mac App Store build, this property is `true`, for other builds it is +A `Boolean`. For Mac App Store build, this property is `true`, for other builds it is `undefined`. ### `process.windowsStore` -If the app is running as a Windows Store app (appx), this property is `true`, +A `Boolean`. If the app is running as a Windows Store app (appx), this property is `true`, for otherwise it is `undefined`. ### `process.defaultApp` -When app is started by being passed as parameter to the default app, this +A `Boolean`. When app is started by being passed as parameter to the default app, this property is `true` in the main process, otherwise it is `undefined`. ## Methods diff --git a/docs/api/remote.md b/docs/api/remote.md index 0bed3ad9b511..2abb2a843110 100644 --- a/docs/api/remote.md +++ b/docs/api/remote.md @@ -141,6 +141,36 @@ The `remote` module has the following methods: * `module` String Returns `any` - The object returned by `require(module)` in the main process. +Modules specified by their relative path will resolve relative to the entrypoint +of the main process. + +e.g. + +``` +project/ +├── main +│   ├── foo.js +│   └── index.js +├── package.json +└── renderer + └── index.js +``` + +```js +// main process: main/index.js +const {app} = require('electron') +app.on('ready', () => { /* ... */ }) +``` + +```js +// some relative module: main/foo.js +module.exports = 'bar' +``` + +```js +// renderer process: renderer/index.js +const foo = require('electron').remote.require('./foo') // bar +``` ### `remote.getCurrentWindow()` diff --git a/docs/api/sandbox-option.md b/docs/api/sandbox-option.md new file mode 100644 index 000000000000..9598e47257d1 --- /dev/null +++ b/docs/api/sandbox-option.md @@ -0,0 +1,195 @@ +# `sandbox` Option + +> Create a browser window with a renderer that can run inside Chromium OS sandbox. With this +option enabled, the renderer must communicate via IPC to the main process in order to access node APIs. +However, in order to enable the Chromium OS sandbox, electron must be run with the `--enable-sandbox` +command line argument. + +One of the key security features of Chromium is that all blink rendering/JavaScript +code is executed within a sandbox. This sandbox uses OS-specific features to ensure +that exploits in the renderer process cannot harm the system. + +In other words, when the sandbox is enabled, the renderers can only make changes +to the system by delegating tasks to the main process via IPC. +[Here's](https://www.chromium.org/developers/design-documents/sandbox) more +information about the sandbox. + +Since a major feature in electron is the ability to run node.js in the +renderer process (making it easier to develop desktop applications using web +technologies), the sandbox is disabled by electron. This is because +most node.js APIs require system access. `require()` for example, is not +possible without file system permissions, which are not available in a sandboxed +environment. + +Usually this is not a problem for desktop applications since the code is always +trusted, but it makes electron less secure than chromium for displaying +untrusted web content. For applications that require more security, the +`sandbox` flag will force electron to spawn a classic chromium renderer that is +compatible with the sandbox. + +A sandboxed renderer doesn't have a node.js environment running and doesn't +expose node.js JavaScript APIs to client code. The only exception is the preload script, +which has access to a subset of the electron renderer API. + +Another difference is that sandboxed renderers don't modify any of the default +JavaScript APIs. Consequently, some APIs such as `window.open` will work as they +do in chromium (i.e. they do not return a `BrowserWindowProxy`). + +## Example + +To create a sandboxed window, simply pass `sandbox: true` to `webPreferences`: + +```js +let win +app.on('ready', () => { + win = new BrowserWindow({ + webPreferences: { + sandbox: true + } + }) + w.loadURL('http://google.com') +}) +``` + +In the above code the `BrowserWindow` that was created has node.js disabled and can communicate +only via IPC. The use of this option stops electron from creating a node.js runtime in the renderer. Also, +within this new window `window.open` follows the native behaviour (by default electron creates a `BrowserWindow` +and returns a proxy to this via `window.open`). + +It is important to note that this option alone won't enable the OS-enforced sandbox. To enable this feature, the +`--enable-sandbox` command-line argument must be passed to electron, which will +force `sandbox: true` for all `BrowserWindow` instances. + + +```js +let win +app.on('ready', () => { + // no need to pass `sandbox: true` since `--enable-sandbox` was enabled. + win = new BrowserWindow() + w.loadURL('http://google.com') +}) +``` + +Note that it is not enough to call +`app.commandLine.appendSwitch('--enable-sandbox')`, as electron/node startup +code runs after it is possible to make changes to chromium sandbox settings. The +switch must be passed to electron on the command-line: + +``` +electron --enable-sandbox app.js +``` + +It is not possible to have the OS sandbox active only for some renderers, if +`--enable-sandbox` is enabled, normal electron windows cannot be created. + +If you need to mix sandboxed and non-sandboxed renderers in one application, +simply omit the `--enable-sandbox` argument. Without this argument, windows +created with `sandbox: true` will still have node.js disabled and communicate +only via IPC, which by itself is already a gain from security POV. + +## Preload + +An app can make customizations to sandboxed renderers using a preload script. +Here's an example: + +```js +let win +app.on('ready', () => { + win = new BrowserWindow({ + webPreferences: { + sandbox: true, + preload: 'preload.js' + } + }) + w.loadURL('http://google.com') +}) +``` + +and preload.js: + +```js +// This file is loaded whenever a javascript context is created. It runs in a +// private scope that can access a subset of electron renderer APIs. We must be +// careful to not leak any objects into the global scope! +const fs = require('fs') +const {ipcRenderer} = require('electron') + +// read a configuration file using the `fs` module +const buf = fs.readFileSync('allowed-popup-urls.json') +const allowedUrls = JSON.parse(buf.toString('utf8')) + +const defaultWindowOpen = window.open + +function customWindowOpen (url, ...args) { + if (allowedUrls.indexOf(url) === -1) { + ipcRenderer.sendSync('blocked-popup-notification', location.origin, url) + return null + } + return defaultWindowOpen(url, ...args) +} + +window.open = customWindowOpen +``` + +Important things to notice in the preload script: + +- Even though the sandboxed renderer doesn't have node.js running, it still has + access to a limited node-like environment: `Buffer`, `process`, `setImmediate` + and `require` are available. +- The preload script can indirectly access all APIs from the main process through the + `remote` and `ipcRenderer` modules. This is how `fs` (used above) and other + modules are implemented: They are proxies to remote counterparts in the main + process. +- The preload script must be contained in a single script, but it is possible to have + complex preload code composed with multiple modules by using a tool like + browserify, as explained below. In fact, browserify is already used by + electron to provide a node-like environment to the preload script. + +To create a browserify bundle and use it as a preload script, something like +the following should be used: + + browserify preload/index.js \ + -x electron \ + -x fs \ + --insert-global-vars=__filename,__dirname -o preload.js + +The `-x` flag should be used with any required module that is already exposed in +the preload scope, and tells browserify to use the enclosing `require` function +for it. `--insert-global-vars` will ensure that `process`, `Buffer` and +`setImmediate` are also taken from the enclosing scope(normally browserify +injects code for those). + +Currently the `require` function provided in the preload scope exposes the +following modules: + +- `child_process` +- `electron` (crashReporter, remote and ipcRenderer) +- `fs` +- `os` +- `timers` +- `url` + +More may be added as needed to expose more electron APIs in the sandbox, but any +module in the main process can already be used through +`electron.remote.require`. + +## Status + +Please use the `sandbox` option with care, as it is still an experimental +feature. We are still not aware of the security implications of exposing some +electron renderer APIs to the preload script, but here are some things to +consider before rendering untrusted content: + +- A preload script can accidentaly leak privileged APIs to untrusted code. +- Some bug in V8 engine may allow malicious code to access the renderer preload + APIs, effectively granting full access to the system through the `remote` + module. + +Since rendering untrusted content in electron is still uncharted territory, +the APIs exposed to the sandbox preload script should be considered more +unstable than the rest of electron APIs, and may have breaking changes to fix +security issues. + +One planned enhancement that should greatly increase security is to block IPC +messages from sandboxed renderers by default, allowing the main process to +explicitly define a set of messages the renderer is allowed to send. diff --git a/docs/api/touch-bar-scrubber.md b/docs/api/touch-bar-scrubber.md index 370c98beed1d..31f65d8770cb 100644 --- a/docs/api/touch-bar-scrubber.md +++ b/docs/api/touch-bar-scrubber.md @@ -9,9 +9,9 @@ Process: [Main](../tutorial/quick-start.md#main-process) * `options` Object * `items` [ScrubberItem[]](structures/scrubber-item.md) - An array of items to place in this scrubber * `select` Function - Called when the user taps an item that was not the last tapped item - * `selectedIndex` - The index of the item the user selected + * `selectedIndex` Integer - The index of the item the user selected * `highlight` Function - Called when the user taps any item - * `highlightedIndex` - The index of the item the user touched + * `highlightedIndex` Integer - The index of the item the user touched * `selectedStyle` String - Selected item style. Defaults to `null`. * `overlayStyle` String - Selected overlay item style. Defaults to `null`. * `showArrowButtons` Boolean - Defaults to `false`. diff --git a/docs/api/touch-bar-segmented-control.md b/docs/api/touch-bar-segmented-control.md index 5c8a0de7eba1..42be09079082 100644 --- a/docs/api/touch-bar-segmented-control.md +++ b/docs/api/touch-bar-segmented-control.md @@ -8,18 +8,23 @@ Process: [Main](../tutorial/quick-start.md#main-process) * `options` Object * `segmentStyle` String - (Optional) Style of the segments: - * `automatic` - Default - * `rounded` - * `textured-rounded` - * `round-rect` - * `textured-square` - * `capsule` - * `small-square` - * `separated` + * `automatic` - Default. The appearance of the segmented control is + automatically determined based on the type of window in which the control + is displayed and the position within the window. + * `rounded` - The control is displayed using the rounded style. + * `textured-rounded` - The control is displayed using the textured rounded + style. + * `round-rect` - The control is displayed using the round rect style. + * `textured-square` - The control is displayed using the textured square + style. + * `capsule` - The control is displayed using the capsule style + * `small-square` - The control is displayed using the small square style. + * `separated` - The segments in the control are displayed very close to each + other but not touching. * `segments` [SegmentedControlSegment[]](structures/segmented-control-segment.md) - An array of segments to place in this control * `selectedIndex` Integer (Optional) - The index of the currently selected segment, will update automatically with user interaction * `change` Function - Called when the user selects a new segment - * `selectedIndex` - The index of the segment the user selected + * `selectedIndex` Integer - The index of the segment the user selected ### Instance Properties diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index b91f6d9edef5..87530234a282 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -336,6 +336,7 @@ Returns: * `activeMatchOrdinal` Integer - Position of the active match. * `matches` Integer - Number of Matches. * `selectionArea` Object - Coordinates of first match region. + * `finalUpdate` Boolean Emitted when a result is available for [`webContents.findInPage`] request. @@ -1247,7 +1248,7 @@ one through the `'paint'` event. #### `contents.getWebRTCIPHandlingPolicy()` -* Returns `String` - Returns the WebRTC IP Handling Policy. +Returns `String` - Returns the WebRTC IP Handling Policy. #### `contents.setWebRTCIPHandlingPolicy(policy)` diff --git a/docs/api/web-frame.md b/docs/api/web-frame.md index 25ae3480f938..abe8e4d91626 100644 --- a/docs/api/web-frame.md +++ b/docs/api/web-frame.md @@ -136,6 +136,9 @@ Inserts `text` to the focused element. * `callback` Function (optional) - Called after script has been executed. * `result` Any +Returns `Promise` - A promise that resolves with the result of the executed code +or is rejected if the result of the code is a rejected promise. + Evaluates `code` in page. In the browser window some HTML APIs like `requestFullScreen` can only be diff --git a/docs/api/webview-tag.md b/docs/api/webview-tag.md index 5efc225a1d65..9c09c8ef730e 100644 --- a/docs/api/webview-tag.md +++ b/docs/api/webview-tag.md @@ -749,6 +749,7 @@ Returns: * `activeMatchOrdinal` Integer - Position of the active match. * `matches` Integer - Number of Matches. * `selectionArea` Object - Coordinates of first match region. + * `finalUpdate` Boolean Fired when a result is available for [`webview.findInPage`](webview-tag.md#webviewtagfindinpage) request. diff --git a/docs/development/chromium-development.md b/docs/development/chromium-development.md new file mode 100644 index 000000000000..23506ae918fa --- /dev/null +++ b/docs/development/chromium-development.md @@ -0,0 +1,14 @@ +# Chromium Development + +> A collection of resources for learning about Chromium and tracking its development + +- [chromiumdev](https://chromiumdev-slack.herokuapp.com) on Slack +- [@ChromiumDev](https://twitter.com/ChromiumDev) on Twitter +- [@googlechrome](https://twitter.com/googlechrome) on Twitter +- [Blog](https://blog.chromium.org) +- [Code Search](https://cs.chromium.org/) +- [Source Code](https://cs.chromium.org/chromium/src/) +- [Development Calendar and Release Info](https://www.chromium.org/developers/calendar) +- [Discussion Groups](http://www.chromium.org/developers/discussion-groups) + +See also [V8 Development](v8-development.md) diff --git a/docs/development/upgrading-chrome.md b/docs/development/upgrading-chrome.md index 5cb9337cd09c..6e47a8b4f521 100644 --- a/docs/development/upgrading-chrome.md +++ b/docs/development/upgrading-chrome.md @@ -45,6 +45,46 @@ Chrome/Node API changes. - 64-bit Linux - ARM Linux +## Verify ffmpeg Support + +Electron ships with a version of `ffmpeg` that includes proprietary codecs by +default. A version without these codecs is built and distributed with each +release as well. Each Chrome upgrade should verify that switching this version is +still supported. + +You can verify Electron's support for multiple `ffmpeg` builds by loading the +following page. It should work with the default `ffmpeg` library distributed +with Electron and not work with the `ffmpeg` library built without proprietary +codecs. + +```html + + + + + Proprietary Codec Check + + +

Checking if Electron is using proprietary codecs by loading video from http://www.quirksmode.org/html5/videos/big_buck_bunny.mp4

+

+ + + + +``` + ## Links - [Chrome Release Schedule](https://www.chromium.org/developers/calendar) diff --git a/docs/development/v8-development.md b/docs/development/v8-development.md new file mode 100644 index 000000000000..76d13299ca7e --- /dev/null +++ b/docs/development/v8-development.md @@ -0,0 +1,11 @@ +# V8 Development + +> A collection of resources for learning and using V8 + +* [V8 Tracing](https://github.com/v8/v8/wiki/Tracing-V8) +* [V8 Profiler](https://github.com/v8/v8/wiki/V8-Profiler) - Profiler combinations which are useful for profiling: `--prof`, `--trace-ic`, `--trace-opt`, `--trace-deopt`, `--print-bytecode`, `--print-opt-code` +* [V8 Interpreter Design](https://docs.google.com/document/d/11T2CRex9hXxoJwbYqVQ32yIPMh0uouUZLdyrtmMoL44/edit?ts=56f27d9d#heading=h.6jz9dj3bnr8t) +* [Optimizing compiler](https://github.com/v8/v8/wiki/TurboFan) +* [V8 GDB Debugging](https://github.com/v8/v8/wiki/GDB-JIT-Interface) + +See also [Chromium Development](chromium-development.md) diff --git a/docs/tutorial/electron-versioning.md b/docs/tutorial/electron-versioning.md index cae99344a657..3f6c9ef90d43 100644 --- a/docs/tutorial/electron-versioning.md +++ b/docs/tutorial/electron-versioning.md @@ -1,21 +1,55 @@ # Electron Versioning -If you are a seasoned Node developer, you are surely aware of `semver` - and -might be used to giving your dependency management systems only rough guidelines -rather than fixed version numbers. Due to the hard dependency on Node and -Chromium, Electron is in a slightly more difficult position and does not follow -semver. You should therefore always reference a specific version of Electron. +If you've been using Node and npm for a while, you are probably aware of [Semantic Versioning], or SemVer for short. It's a convention for specifying version numbers for software that helps communicate intentions to the users of your software. -Version numbers are bumped using the following rules: +## Overview of Semantic Versioning -* Major: For breaking changes in Electron's API - if you upgrade from `0.37.0` - to `1.0.0`, you will have to update your app. -* Minor: For major Chrome and minor Node upgrades; or significant Electron - changes - if you upgrade from `1.0.0` to `1.1.0`, your app is supposed to +Semantic versions are always made up of three numbers: + +``` +major.minor.patch +``` + +Semantic version numbers are bumped (incremented) using the following rules: + +* **Major** is for changes that break backwards compatibility. +* **Minor** is for new features that don't break backwards compatibility. +* **Patch** is for bug fixes and other minor changes. + +A simple mnemonic for remembering this scheme is as follows: + +``` +breaking.feature.fix +``` + +## Electron Versioning + +Due to its dependency on Node and Chromium, it is not possible for the Electron +project to adhere to a SemVer policy. **You should therefore always +reference a specific version of Electron.** + +Electron version numbers are bumped using the following rules: + +* **Major** is for breaking changes in Electron's API. If you upgrade from `0.37.0` + to `1.0.0`, you will have to make changes to your app. +* **Minor** is for major Chrome and minor Node upgrades, or significant Electron + changes. If you upgrade from `1.5.0` to `1.6.0`, your app is supposed to still work, but you might have to work around small changes. -* Patch: For new features and bug fixes - if you upgrade from `1.0.0` to - `1.0.1`, your app will continue to work as-is. +* **Patch** is for new features and bug fixes. If you upgrade from `1.6.2` to + `1.6.3`, your app will continue to work as-is. -If you are using `electron` or `electron-prebuilt`, we recommend that you set a fixed version -number (`1.1.0` instead of `^1.1.0`) to ensure that all upgrades of Electron are -a manual operation made by you, the developer. +We recommend that you set a fixed version when installing Electron from npm: + +```sh +npm install electron --save-exact --save-dev +``` + +The `--save-exact` flag will add `electron` to your `package.json` file without +using a `^` or `~`, e.g. `1.6.2` instead of `^1.6.2`. This practice ensures that +all upgrades of Electron are a manual operation made by you, the developer. + +Alternatively, you can use the `~` prefix in your SemVer range, like `~1.6.2`. +This will lock your major and minor version, but allow new patch versions to +be installed. + +[Semantic Versioning]: http://semver.org diff --git a/electron.gyp b/electron.gyp index 15336a4e6bf3..3dec2ee8a072 100644 --- a/electron.gyp +++ b/electron.gyp @@ -4,7 +4,7 @@ 'product_name%': 'Electron', 'company_name%': 'GitHub, Inc', 'company_abbr%': 'github', - 'version%': '1.6.4', + 'version%': '1.6.5', 'js2c_input_dir': '<(SHARED_INTERMEDIATE_DIR)/js2c', }, 'includes': [ @@ -441,7 +441,13 @@ 'sandbox_args': [ './lib/sandboxed_renderer/init.js', '-r', - './lib/sandboxed_renderer/api/exports/electron.js:electron' + './lib/sandboxed_renderer/api/exports/electron.js:electron', + '-r', + './lib/sandboxed_renderer/api/exports/fs.js:fs', + '-r', + './lib/sandboxed_renderer/api/exports/os.js:os', + '-r', + './lib/sandboxed_renderer/api/exports/child_process.js:child_process' ], 'isolated_args': [ 'lib/isolated_renderer/init.js', diff --git a/filenames.gypi b/filenames.gypi index 3b557fcb88a4..935d7033153a 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -480,6 +480,8 @@ 'atom/renderer/node_array_buffer_bridge.h', 'atom/renderer/preferences_manager.cc', 'atom/renderer/preferences_manager.h', + 'atom/renderer/renderer_client_base.cc', + 'atom/renderer/renderer_client_base.h', 'atom/renderer/web_worker_observer.cc', 'atom/renderer/web_worker_observer.h', 'atom/utility/atom_content_utility_client.cc', diff --git a/lib/browser/api/menu-item-roles.js b/lib/browser/api/menu-item-roles.js index 8a78670267ae..af0d35b4a56a 100644 --- a/lib/browser/api/menu-item-roles.js +++ b/lib/browser/api/menu-item-roles.js @@ -154,6 +154,68 @@ const roles = { webContents.setZoomLevel(zoomLevel - 0.5) }) } + }, + // Edit submenu (should fit both Mac & Windows) + editMenu: { + label: 'Edit', + submenu: [ + { + role: 'undo' + }, + { + role: 'redo' + }, + { + type: 'separator' + }, + { + role: 'cut' + }, + { + role: 'copy' + }, + { + role: 'paste' + }, + + process.platform === 'darwin' ? { + role: 'pasteandmatchstyle' + } : null, + + { + role: 'delete' + }, + + process.platform === 'win32' ? { + type: 'separator' + } : null, + + { + role: 'selectall' + } + ] + }, + + // Window submenu should be used for Mac only + windowMenu: { + label: 'Window', + submenu: [ + { + role: 'minimize' + }, + { + role: 'close' + }, + + process.platform === 'darwin' ? { + type: 'separator' + } : null, + + process.platform === 'darwin' ? { + role: 'front' + } : null + + ] } } @@ -176,6 +238,19 @@ exports.getDefaultAccelerator = (role) => { if (roles.hasOwnProperty(role)) return roles[role].accelerator } +exports.getDefaultSubmenu = (role) => { + if (!roles.hasOwnProperty(role)) return + + let {submenu} = roles[role] + + // remove null items from within the submenu + if (Array.isArray(submenu)) { + submenu = submenu.filter((item) => item != null) + } + + return submenu +} + exports.execute = (role, focusedWindow, focusedWebContents) => { if (!canExecuteRole(role)) return false diff --git a/lib/browser/api/menu-item.js b/lib/browser/api/menu-item.js index 98b8e9980e28..e95226d3b364 100644 --- a/lib/browser/api/menu-item.js +++ b/lib/browser/api/menu-item.js @@ -11,7 +11,7 @@ const MenuItem = function (options) { for (let key in options) { if (!(key in this)) this[key] = options[key] } - + this.submenu = this.submenu || roles.getDefaultSubmenu(this.role) if (this.submenu != null && this.submenu.constructor !== Menu) { this.submenu = Menu.buildFromTemplate(this.submenu) } diff --git a/lib/browser/api/net.js b/lib/browser/api/net.js index 10d903919e9d..d32f6946eee5 100644 --- a/lib/browser/api/net.js +++ b/lib/browser/api/net.js @@ -156,9 +156,15 @@ class ClientRequest extends EventEmitter { urlStr = url.format(urlObj) } + const redirectPolicy = options.redirect || 'follow' + if (!['follow', 'error', 'manual'].includes(redirectPolicy)) { + throw new Error('redirect mode should be one of follow, error or manual') + } + let urlRequestOptions = { method: method, - url: urlStr + url: urlStr, + redirect: redirectPolicy } if (options.session) { if (options.session instanceof Session) { @@ -240,7 +246,7 @@ class ClientRequest extends EventEmitter { if (typeof name !== 'string') { throw new TypeError('`name` should be a string in setHeader(name, value).') } - if (value === undefined) { + if (value == null) { throw new Error('`value` required in setHeader("' + name + '", value).') } if (!this.urlRequest.notStarted) { @@ -249,11 +255,11 @@ class ClientRequest extends EventEmitter { const key = name.toLowerCase() this.extraHeaders[key] = value - this.urlRequest.setExtraHeader(name, value) + this.urlRequest.setExtraHeader(name, value.toString()) } getHeader (name) { - if (arguments.length < 1) { + if (name == null) { throw new Error('`name` is required for getHeader(name).') } @@ -266,7 +272,7 @@ class ClientRequest extends EventEmitter { } removeHeader (name) { - if (arguments.length < 1) { + if (name == null) { throw new Error('`name` is required for removeHeader(name).') } @@ -339,6 +345,10 @@ class ClientRequest extends EventEmitter { return this._write(data, encoding, callback, true) } + followRedirect () { + this.urlRequest.followRedirect() + } + abort () { this.urlRequest.cancel() } diff --git a/lib/renderer/api/remote.js b/lib/renderer/api/remote.js index b3d3d2ab17cc..f65d61790ace 100644 --- a/lib/renderer/api/remote.js +++ b/lib/renderer/api/remote.js @@ -139,7 +139,12 @@ const setObjectMembers = function (ref, object, metaId, members) { // Only set setter when it is writable. if (member.writable) { descriptor.set = function (value) { - ipcRenderer.sendSync('ELECTRON_BROWSER_MEMBER_SET', metaId, member.name, value) + const meta = ipcRenderer.sendSync('ELECTRON_BROWSER_MEMBER_SET', metaId, member.name, value) + // Meta will be non-null when a setter error occurred so parse it + // to a value so it gets re-thrown. + if (meta != null) { + metaToValue(meta) + } return value } } diff --git a/lib/sandboxed_renderer/api/exports/child_process.js b/lib/sandboxed_renderer/api/exports/child_process.js new file mode 100644 index 000000000000..ff39e96a120e --- /dev/null +++ b/lib/sandboxed_renderer/api/exports/child_process.js @@ -0,0 +1 @@ +module.exports = require('electron').remote.require('child_process') diff --git a/lib/sandboxed_renderer/api/exports/electron.js b/lib/sandboxed_renderer/api/exports/electron.js index 2a1d34123116..02f23be3966d 100644 --- a/lib/sandboxed_renderer/api/exports/electron.js +++ b/lib/sandboxed_renderer/api/exports/electron.js @@ -11,6 +11,12 @@ Object.defineProperties(exports, { return require('../../../renderer/api/remote') } }, + crashReporter: { + enumerable: true, + get: function () { + return require('../../../common/api/crash-reporter') + } + }, CallbacksRegistry: { get: function () { return require('../../../common/api/callbacks-registry') diff --git a/lib/sandboxed_renderer/api/exports/fs.js b/lib/sandboxed_renderer/api/exports/fs.js new file mode 100644 index 000000000000..7342908e59a9 --- /dev/null +++ b/lib/sandboxed_renderer/api/exports/fs.js @@ -0,0 +1 @@ +module.exports = require('electron').remote.require('fs') diff --git a/lib/sandboxed_renderer/api/exports/os.js b/lib/sandboxed_renderer/api/exports/os.js new file mode 100644 index 000000000000..ecd0d38a63a6 --- /dev/null +++ b/lib/sandboxed_renderer/api/exports/os.js @@ -0,0 +1 @@ +module.exports = require('electron').remote.require('os') diff --git a/lib/sandboxed_renderer/init.js b/lib/sandboxed_renderer/init.js index 7f2d252865a8..46b74ee7b042 100644 --- a/lib/sandboxed_renderer/init.js +++ b/lib/sandboxed_renderer/init.js @@ -21,23 +21,23 @@ for (let prop of Object.keys(events.EventEmitter.prototype)) { Object.setPrototypeOf(process, events.EventEmitter.prototype) const electron = require('electron') +const fs = require('fs') const preloadModules = new Map([ - ['electron', electron] + ['child_process', require('child_process')], + ['electron', electron], + ['fs', fs], + ['os', require('os')], + ['url', require('url')], + ['timers', require('timers')] ]) -const extraModules = [ - 'fs' -] -for (let extraModule of extraModules) { - preloadModules.set(extraModule, electron.remote.require(extraModule)) -} - -// Fetch the preload script using the "fs" module proxy. -let preloadSrc = preloadModules.get('fs').readFileSync(preloadPath).toString() +const preloadSrc = fs.readFileSync(preloadPath).toString() // Pass different process object to the preload script(which should not have // access to things like `process.atomBinding`). const preloadProcess = new events.EventEmitter() +preloadProcess.platform = electron.remote.process.platform +preloadProcess.crash = () => binding.crash() process.on('exit', () => preloadProcess.emit('exit')) // This is the `require` function that will be visible to the preload script @@ -67,12 +67,13 @@ function preloadRequire (module) { // and any `require('electron')` calls in `preload.js` will work as expected // since browserify won't try to include `electron` in the bundle, falling back // to the `preloadRequire` function above. -let preloadWrapperSrc = `(function(require, process, Buffer, global) { +const preloadWrapperSrc = `(function(require, process, Buffer, global, setImmediate) { ${preloadSrc} })` // eval in window scope: // http://www.ecma-international.org/ecma-262/5.1/#sec-10.4.2 const geval = eval -let preloadFn = geval(preloadWrapperSrc) -preloadFn(preloadRequire, preloadProcess, Buffer, global) +const preloadFn = geval(preloadWrapperSrc) +const {setImmediate} = require('timers') +preloadFn(preloadRequire, preloadProcess, Buffer, global, setImmediate) diff --git a/package.json b/package.json index d321d7831f05..015be4a12e09 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "electron", - "version": "1.6.4", + "version": "1.6.5", "devDependencies": { "asar": "^0.11.0", "browserify": "^13.1.0", diff --git a/script/cibuild b/script/cibuild index 92dec9db2dee..dc2d9d081045 100755 --- a/script/cibuild +++ b/script/cibuild @@ -91,6 +91,7 @@ def main(): run_script('build.py', ['-c', 'D']) if PLATFORM == 'win32' or target_arch == 'x64': run_script('test.py', ['--ci']) + run_script('verify-ffmpeg.py') def run_script(script, args=[]): diff --git a/script/create-dist.py b/script/create-dist.py index 35a109cfa3f5..3d4b62624eee 100755 --- a/script/create-dist.py +++ b/script/create-dist.py @@ -1,5 +1,6 @@ #!/usr/bin/env python +import argparse import glob import os import re @@ -87,7 +88,9 @@ def main(): copy_chrome_binary('mksnapshot') copy_license() - if PLATFORM != 'win32': + args = parse_args() + + if PLATFORM != 'win32' and not args.no_api_docs: create_api_json_schema() if PLATFORM == 'linux': @@ -242,5 +245,13 @@ def create_symbols_zip(): make_zip(os.path.join(DIST_DIR, pdb_name), pdbs + licenses, []) +def parse_args(): + parser = argparse.ArgumentParser(description='Create Electron Distribution') + parser.add_argument('--no_api_docs', + action='store_true', + help='Skip generating the Electron API Documentation!') + return parser.parse_args() + + if __name__ == '__main__': sys.exit(main()) diff --git a/script/lib/config.py b/script/lib/config.py index fc87d083750c..d033d8a41c6a 100644 --- a/script/lib/config.py +++ b/script/lib/config.py @@ -9,7 +9,7 @@ import sys BASE_URL = os.getenv('LIBCHROMIUMCONTENT_MIRROR') or \ 'https://s3.amazonaws.com/github-janky-artifacts/libchromiumcontent' LIBCHROMIUMCONTENT_COMMIT = os.getenv('LIBCHROMIUMCONTENT_COMMIT') or \ - '44448acf6a21024b9adb7140ffef6402a509f8bf' + '8d551064d2b3d11f89ce8d5c4610f34e0408bad8' PLATFORM = { 'cygwin': 'win32', diff --git a/script/verify-ffmpeg.py b/script/verify-ffmpeg.py new file mode 100755 index 000000000000..9d9dba067030 --- /dev/null +++ b/script/verify-ffmpeg.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python + +import os +import shutil +import subprocess +import sys + +from lib.util import electron_gyp, rm_rf + + +SOURCE_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) +FFMPEG_LIBCC_PATH = os.path.join(SOURCE_ROOT, 'vendor', 'brightray', 'vendor', + 'download', 'libchromiumcontent', 'ffmpeg') + +PROJECT_NAME = electron_gyp()['project_name%'] +PRODUCT_NAME = electron_gyp()['product_name%'] + + +def main(): + os.chdir(SOURCE_ROOT) + + if len(sys.argv) == 2 and sys.argv[1] == '-R': + config = 'R' + else: + config = 'D' + + app_path = create_app_copy(config) + + if sys.platform == 'darwin': + electron = os.path.join(app_path, 'Contents', 'MacOS', PRODUCT_NAME) + ffmpeg_name = 'libffmpeg.dylib' + ffmpeg_app_path = os.path.join(app_path, 'Contents', 'Frameworks', + '{0} Framework.framework'.format(PROJECT_NAME), + 'Libraries') + elif sys.platform == 'win32': + electron = os.path.join(app_path, '{0}.exe'.format(PROJECT_NAME)) + ffmpeg_app_path = app_path + ffmpeg_name = 'ffmpeg.dll' + else: + electron = os.path.join(app_path, PROJECT_NAME) + ffmpeg_app_path = app_path + ffmpeg_name = 'libffmpeg.so' + + # Copy ffmpeg without proprietary codecs into app + shutil.copy(os.path.join(FFMPEG_LIBCC_PATH, ffmpeg_name), ffmpeg_app_path) + + returncode = 0 + try: + test_path = os.path.join('spec', 'fixtures', 'no-proprietary-codecs.js') + subprocess.check_call([electron, test_path] + sys.argv[1:]) + except subprocess.CalledProcessError as e: + returncode = e.returncode + except KeyboardInterrupt: + returncode = 0 + + return returncode + + +# Create copy of app to install ffmpeg library without proprietary codecs into +def create_app_copy(config): + initial_app_path = os.path.join(SOURCE_ROOT, 'out', config) + app_path = os.path.join(SOURCE_ROOT, 'out', config + '-no-proprietary-codecs') + + if sys.platform == 'darwin': + app_name = '{0}.app'.format(PRODUCT_NAME) + initial_app_path = os.path.join(initial_app_path, app_name) + app_path = os.path.join(app_path, app_name) + + rm_rf(app_path) + shutil.copytree(initial_app_path, app_path, symlinks=True) + return app_path + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/spec/api-browser-window-spec.js b/spec/api-browser-window-spec.js index 04c3be453cc1..98898c1eed92 100644 --- a/spec/api-browser-window-spec.js +++ b/spec/api-browser-window-spec.js @@ -692,7 +692,7 @@ describe('BrowserWindow module', function () { }) }) - describe('"title-bar-style" option', function () { + describe('"titleBarStyle" option', function () { if (process.platform !== 'darwin') { return } @@ -772,6 +772,20 @@ describe('BrowserWindow module', function () { }) }) + describe('"tabbingIdentifier" option', function () { + it('can be set on a window', function () { + w.destroy() + w = new BrowserWindow({ + tabbingIdentifier: 'group1' + }) + w.destroy() + w = new BrowserWindow({ + tabbingIdentifier: 'group2', + frame: false + }) + }) + }) + describe('"web-preferences" option', function () { afterEach(function () { ipcMain.removeAllListeners('answer') diff --git a/spec/api-crash-reporter-spec.js b/spec/api-crash-reporter-spec.js index c269cfe1324b..8a51b5ef2db1 100644 --- a/spec/api-crash-reporter-spec.js +++ b/spec/api-crash-reporter-spec.js @@ -1,5 +1,6 @@ const assert = require('assert') const childProcess = require('child_process') +const fs = require('fs') const http = require('http') const multiparty = require('multiparty') const path = require('path') @@ -11,85 +12,183 @@ const {remote} = require('electron') const {app, BrowserWindow, crashReporter} = remote.require('electron') describe('crashReporter module', function () { - var fixtures = path.resolve(__dirname, 'fixtures') - var w = null - var originalTempDirectory = null - var tempDirectory = null - - beforeEach(function () { - w = new BrowserWindow({ - show: false - }) - tempDirectory = temp.mkdirSync('electronCrashReporterSpec-') - originalTempDirectory = app.getPath('temp') - app.setPath('temp', tempDirectory) - }) - - afterEach(function () { - app.setPath('temp', originalTempDirectory) - return closeWindow(w).then(function () { w = null }) - }) - if (process.mas) { return } + var fixtures = path.resolve(__dirname, 'fixtures') + const generateSpecs = (description, browserWindowOpts) => { + describe(description, function () { + var w = null + var originalTempDirectory = null + var tempDirectory = null - it('should send minidump when renderer crashes', function (done) { - if (process.env.APPVEYOR === 'True') return done() - if (process.env.TRAVIS === 'true') return done() + beforeEach(function () { + w = new BrowserWindow(Object.assign({ + show: false + }, browserWindowOpts)) + tempDirectory = temp.mkdirSync('electronCrashReporterSpec-') + originalTempDirectory = app.getPath('temp') + app.setPath('temp', tempDirectory) + }) - this.timeout(120000) + afterEach(function () { + app.setPath('temp', originalTempDirectory) + return closeWindow(w).then(function () { w = null }) + }) - startServer({ - callback (port) { - const crashUrl = url.format({ - protocol: 'file', - pathname: path.join(fixtures, 'api', 'crash.html'), - search: '?port=' + port + it('should send minidump when renderer crashes', function (done) { + if (process.env.APPVEYOR === 'True') return done() + if (process.env.TRAVIS === 'true') return done() + + this.timeout(120000) + + startServer({ + callback (port) { + const crashUrl = url.format({ + protocol: 'file', + pathname: path.join(fixtures, 'api', 'crash.html'), + search: '?port=' + port + }) + w.loadURL(crashUrl) + }, + processType: 'renderer', + done: done }) - w.loadURL(crashUrl) - }, - processType: 'renderer', - done: done - }) - }) + }) - it('should send minidump when node processes crash', function (done) { - if (process.env.APPVEYOR === 'True') return done() - if (process.env.TRAVIS === 'true') return done() + it('should send minidump when node processes crash', function (done) { + if (process.env.APPVEYOR === 'True') return done() + if (process.env.TRAVIS === 'true') return done() - this.timeout(120000) + this.timeout(120000) - startServer({ - callback (port) { - const crashesDir = path.join(app.getPath('temp'), `${app.getName()} Crashes`) - const version = app.getVersion() - const crashPath = path.join(fixtures, 'module', 'crash.js') - childProcess.fork(crashPath, [port, version, crashesDir], {silent: true}) - }, - processType: 'browser', - done: done - }) - }) - - it('should send minidump with updated extra parameters', function (done) { - if (process.env.APPVEYOR === 'True') return done() - if (process.env.TRAVIS === 'true') return done() - - this.timeout(10000) - - startServer({ - callback (port) { - const crashUrl = url.format({ - protocol: 'file', - pathname: path.join(fixtures, 'api', 'crash-restart.html'), - search: '?port=' + port + startServer({ + callback (port) { + const crashesDir = path.join(app.getPath('temp'), `${app.getName()} Crashes`) + const version = app.getVersion() + const crashPath = path.join(fixtures, 'module', 'crash.js') + childProcess.fork(crashPath, [port, version, crashesDir], {silent: true}) + }, + processType: 'browser', + done: done }) - w.loadURL(crashUrl) - }, - processType: 'renderer', - done: done + }) + + it('should not send minidump if uploadToServer is false', function (done) { + this.timeout(120000) + + if (process.platform === 'darwin') { + crashReporter.setUploadToServer(false) + } + + let server + let dumpFile + let crashesDir + const testDone = (uploaded) => { + if (uploaded) { + return done(new Error('fail')) + } + server.close() + if (process.platform === 'darwin') { + crashReporter.setUploadToServer(true) + } + assert(fs.existsSync(dumpFile)) + fs.unlinkSync(dumpFile) + done() + } + + let pollInterval + const pollDumpFile = () => { + fs.readdir(crashesDir, (err, files) => { + if (err) { + return + } + const dumps = files.filter((file) => /\.dmp$/.test(file)) + if (!dumps.length) { + return + } + assert.equal(1, dumps.length) + dumpFile = path.join(crashesDir, dumps[0]) + clearInterval(pollInterval) + // dump file should not be deleted when not uploading, so we wait + // 500 ms and assert it still exists in `testDone` + setTimeout(testDone, 500) + }) + } + + remote.ipcMain.once('set-crash-directory', (event, dir) => { + if (process.platform === 'linux') { + crashesDir = dir + } else { + crashesDir = crashReporter.getCrashesDirectory() + if (process.platform === 'darwin') { + // crashpad uses an extra subdirectory + crashesDir = path.join(crashesDir, 'completed') + } + } + + // Before starting, remove all dump files in the crash directory. + // This is required because: + // - mac crashpad not seem to allow changing the crash directory after + // the first "start" call. + // - Other tests in this suite may leave dumps there. + // - We want to verify in `testDone` that a dump file is created and + // not deleted. + fs.readdir(crashesDir, (err, files) => { + if (!err) { + for (const file of files) { + if (/\.dmp$/.test(file)) { + fs.unlinkSync(path.join(crashesDir, file)) + } + } + } + event.returnValue = null // allow the renderer to crash + pollInterval = setInterval(pollDumpFile, 100) + }) + }) + + server = startServer({ + callback (port) { + const crashUrl = url.format({ + protocol: 'file', + pathname: path.join(fixtures, 'api', 'crash.html'), + search: `?port=${port}&skipUpload=1` + }) + w.loadURL(crashUrl) + }, + processType: 'renderer', + done: testDone.bind(null, true) + }) + }) + + it('should send minidump with updated extra parameters', function (done) { + if (process.env.APPVEYOR === 'True') return done() + if (process.env.TRAVIS === 'true') return done() + + this.timeout(10000) + + startServer({ + callback (port) { + const crashUrl = url.format({ + protocol: 'file', + pathname: path.join(fixtures, 'api', 'crash-restart.html'), + search: '?port=' + port + }) + w.loadURL(crashUrl) + }, + processType: 'renderer', + done: done + }) + }) }) + } + + generateSpecs('without sandbox', {}) + generateSpecs('with sandbox ', { + webPreferences: { + sandbox: true, + preload: path.join(fixtures, 'module', 'preload-sandbox.js') + } }) describe('.start(options)', function () { @@ -204,4 +303,5 @@ const startServer = ({callback, processType, done}) => { } callback(port) }) + return server } diff --git a/spec/api-ipc-spec.js b/spec/api-ipc-spec.js index 47f2eef21685..c5e38e83e4db 100644 --- a/spec/api-ipc-spec.js +++ b/spec/api-ipc-spec.js @@ -187,6 +187,18 @@ describe('ipc module', function () { property.property = 1127 }) + it('rethrows errors getting/setting properties', function () { + const foo = remote.require(path.join(fixtures, 'module', 'error-properties.js')) + + assert.throws(function () { + foo.bar + }, /getting error/) + + assert.throws(function () { + foo.bar = 'test' + }, /setting error/) + }) + it('can construct an object from its member', function () { var call = remote.require(path.join(fixtures, 'module', 'call.js')) var obj = new call.constructor() diff --git a/spec/api-menu-spec.js b/spec/api-menu-spec.js index 176f1eafae52..9b1f3de36906 100644 --- a/spec/api-menu-spec.js +++ b/spec/api-menu-spec.js @@ -455,6 +455,57 @@ describe('menu module', function () { }) }) + describe('MenuItem editMenu', function () { + it('includes a default submenu layout when submenu is empty', function () { + var item = new MenuItem({role: 'editMenu'}) + assert.equal(item.label, 'Edit') + assert.equal(item.submenu.items[0].role, 'undo') + assert.equal(item.submenu.items[1].role, 'redo') + assert.equal(item.submenu.items[2].type, 'separator') + assert.equal(item.submenu.items[3].role, 'cut') + assert.equal(item.submenu.items[4].role, 'copy') + assert.equal(item.submenu.items[5].role, 'paste') + + if (process.platform === 'darwin') { + assert.equal(item.submenu.items[6].role, 'pasteandmatchstyle') + assert.equal(item.submenu.items[7].role, 'delete') + assert.equal(item.submenu.items[8].role, 'selectall') + } + + if (process.platform === 'win32') { + assert.equal(item.submenu.items[6].role, 'delete') + assert.equal(item.submenu.items[7].type, 'separator') + assert.equal(item.submenu.items[8].role, 'selectall') + } + }) + + it('overrides default layout when submenu is specified', function () { + var item = new MenuItem({role: 'editMenu', submenu: [{role: 'close'}]}) + assert.equal(item.label, 'Edit') + assert.equal(item.submenu.items[0].role, 'close') + }) + }) + + describe('MenuItem windowMenu', function () { + it('includes a default submenu layout when submenu is empty', function () { + var item = new MenuItem({role: 'windowMenu'}) + assert.equal(item.label, 'Window') + assert.equal(item.submenu.items[0].role, 'minimize') + assert.equal(item.submenu.items[1].role, 'close') + + if (process.platform === 'darwin') { + assert.equal(item.submenu.items[2].type, 'separator') + assert.equal(item.submenu.items[3].role, 'front') + } + }) + + it('overrides default layout when submenu is specified', function () { + var item = new MenuItem({role: 'windowMenu', submenu: [{role: 'copy'}]}) + assert.equal(item.label, 'Window') + assert.equal(item.submenu.items[0].role, 'copy') + }) + }) + describe('MenuItem with custom properties in constructor', function () { it('preserves the custom properties', function () { var template = [{ diff --git a/spec/api-net-spec.js b/spec/api-net-spec.js index 49fe2edadcce..dc024553a858 100644 --- a/spec/api-net-spec.js +++ b/spec/api-net-spec.js @@ -364,6 +364,49 @@ describe('net module', function () { urlRequest.end() }) + it('should be able to set a non-string object as a header value', function (done) { + const requestUrl = '/requestUrl' + const customHeaderName = 'Some-Integer-Value' + const customHeaderValue = 900 + server.on('request', function (request, response) { + switch (request.url) { + case requestUrl: + assert.equal(request.headers[customHeaderName.toLowerCase()], + customHeaderValue.toString()) + response.statusCode = 200 + response.statusMessage = 'OK' + response.end() + break + default: + assert.equal(request.url, requestUrl) + } + }) + const urlRequest = net.request({ + method: 'GET', + url: `${server.url}${requestUrl}` + }) + urlRequest.on('response', function (response) { + const statusCode = response.statusCode + assert.equal(statusCode, 200) + response.pause() + response.on('end', function () { + done() + }) + response.resume() + }) + urlRequest.setHeader(customHeaderName, customHeaderValue) + assert.equal(urlRequest.getHeader(customHeaderName), + customHeaderValue) + assert.equal(urlRequest.getHeader(customHeaderName.toLowerCase()), + customHeaderValue) + urlRequest.write('') + assert.equal(urlRequest.getHeader(customHeaderName), + customHeaderValue) + assert.equal(urlRequest.getHeader(customHeaderName.toLowerCase()), + customHeaderValue) + urlRequest.end() + }) + it('should not be able to set a custom HTTP request header after first write', function (done) { const requestUrl = '/requestUrl' const customHeaderName = 'Some-Custom-Header-Name' @@ -906,6 +949,217 @@ describe('net module', function () { urlRequest.end() }) + it('should throw if given an invalid redirect mode', function () { + const requestUrl = '/requestUrl' + const options = { + url: `${server.url}${requestUrl}`, + redirect: 'custom' + } + assert.throws(function () { + net.request(options) + }, 'redirect mode should be one of follow, error or manual') + }) + + it('should throw when calling getHeader without a name', function () { + assert.throws(function () { + net.request({url: `${server.url}/requestUrl`}).getHeader() + }, /`name` is required for getHeader\(name\)\./) + + assert.throws(function () { + net.request({url: `${server.url}/requestUrl`}).getHeader(null) + }, /`name` is required for getHeader\(name\)\./) + }) + + it('should throw when calling removeHeader without a name', function () { + assert.throws(function () { + net.request({url: `${server.url}/requestUrl`}).removeHeader() + }, /`name` is required for removeHeader\(name\)\./) + + assert.throws(function () { + net.request({url: `${server.url}/requestUrl`}).removeHeader(null) + }, /`name` is required for removeHeader\(name\)\./) + }) + + it('should follow redirect when no redirect mode is provided', function (done) { + const requestUrl = '/301' + server.on('request', function (request, response) { + switch (request.url) { + case '/301': + response.statusCode = '301' + response.setHeader('Location', '/200') + response.end() + break + case '/200': + response.statusCode = '200' + response.end() + break + default: + assert(false) + } + }) + const urlRequest = net.request({ + url: `${server.url}${requestUrl}` + }) + urlRequest.on('response', function (response) { + assert.equal(response.statusCode, 200) + done() + }) + urlRequest.end() + }) + + it('should follow redirect chain when no redirect mode is provided', function (done) { + const requestUrl = '/redirectChain' + server.on('request', function (request, response) { + switch (request.url) { + case '/redirectChain': + response.statusCode = '301' + response.setHeader('Location', '/301') + response.end() + break + case '/301': + response.statusCode = '301' + response.setHeader('Location', '/200') + response.end() + break + case '/200': + response.statusCode = '200' + response.end() + break + default: + assert(false) + } + }) + const urlRequest = net.request({ + url: `${server.url}${requestUrl}` + }) + urlRequest.on('response', function (response) { + assert.equal(response.statusCode, 200) + done() + }) + urlRequest.end() + }) + + it('should not follow redirect when mode is error', function (done) { + const requestUrl = '/301' + server.on('request', function (request, response) { + switch (request.url) { + case '/301': + response.statusCode = '301' + response.setHeader('Location', '/200') + response.end() + break + case '/200': + response.statusCode = '200' + response.end() + break + default: + assert(false) + } + }) + const urlRequest = net.request({ + url: `${server.url}${requestUrl}`, + redirect: 'error' + }) + urlRequest.on('error', function (error) { + assert.equal(error.message, 'Request cannot follow redirect with the current redirect mode') + }) + urlRequest.on('close', function () { + done() + }) + urlRequest.end() + }) + + it('should allow follow redirect when mode is manual', function (done) { + const requestUrl = '/redirectChain' + let redirectCount = 0 + server.on('request', function (request, response) { + switch (request.url) { + case '/redirectChain': + response.statusCode = '301' + response.setHeader('Location', '/301') + response.end() + break + case '/301': + response.statusCode = '301' + response.setHeader('Location', '/200') + response.end() + break + case '/200': + response.statusCode = '200' + response.end() + break + default: + assert(false) + } + }) + const urlRequest = net.request({ + url: `${server.url}${requestUrl}`, + redirect: 'manual' + }) + urlRequest.on('response', function (response) { + assert.equal(response.statusCode, 200) + assert.equal(redirectCount, 2) + done() + }) + urlRequest.on('redirect', function (status, method, url) { + if (url === `${server.url}/301` || url === `${server.url}/200`) { + redirectCount += 1 + urlRequest.followRedirect() + } + }) + urlRequest.end() + }) + + it('should allow cancelling redirect when mode is manual', function (done) { + const requestUrl = '/redirectChain' + let redirectCount = 0 + server.on('request', function (request, response) { + switch (request.url) { + case '/redirectChain': + response.statusCode = '301' + response.setHeader('Location', '/redirect/1') + response.end() + break + case '/redirect/1': + response.statusCode = '200' + response.setHeader('Location', '/redirect/2') + response.end() + break + case '/redirect/2': + response.statusCode = '200' + response.end() + break + default: + assert(false) + } + }) + const urlRequest = net.request({ + url: `${server.url}${requestUrl}`, + redirect: 'manual' + }) + urlRequest.on('response', function (response) { + assert.equal(response.statusCode, 200) + response.pause() + response.on('data', function (chunk) { + }) + response.on('end', function () { + urlRequest.abort() + }) + response.resume() + }) + urlRequest.on('close', function () { + assert.equal(redirectCount, 1) + done() + }) + urlRequest.on('redirect', function (status, method, url) { + if (url === `${server.url}/redirect/1`) { + redirectCount += 1 + urlRequest.followRedirect() + } + }) + urlRequest.end() + }) + it('should throw if given an invalid session option', function (done) { const requestUrl = '/requestUrl' try { diff --git a/spec/api-session-spec.js b/spec/api-session-spec.js index f5a4cbf56c2a..c34cdfdab0ff 100644 --- a/spec/api-session-spec.js +++ b/spec/api-session-spec.js @@ -226,7 +226,7 @@ describe('session module', function () { it('clears localstorage data', function (done) { ipcMain.on('count', function (event, count) { ipcMain.removeAllListeners('count') - assert(!count) + assert.equal(count, 0) done() }) w.loadURL('file://' + path.join(fixtures, 'api', 'localstorage.html')) diff --git a/spec/fixtures/api/crash.html b/spec/fixtures/api/crash.html index 2b1cea6e9af3..6f013a2c7ba5 100644 --- a/spec/fixtures/api/crash.html +++ b/spec/fixtures/api/crash.html @@ -1,20 +1,24 @@ diff --git a/spec/fixtures/module/error-properties.js b/spec/fixtures/module/error-properties.js new file mode 100644 index 000000000000..c3a1e3b3a7f6 --- /dev/null +++ b/spec/fixtures/module/error-properties.js @@ -0,0 +1,11 @@ +class Foo { + set bar (value) { + throw new Error('setting error') + } + + get bar () { + throw new Error('getting error') + } +} + +module.exports = new Foo() diff --git a/spec/fixtures/module/preload-sandbox.js b/spec/fixtures/module/preload-sandbox.js index 39a8704e1329..15d6e06a862c 100644 --- a/spec/fixtures/module/preload-sandbox.js +++ b/spec/fixtures/module/preload-sandbox.js @@ -1,6 +1,8 @@ (function () { + const {setImmediate} = require('timers') const {ipcRenderer} = require('electron') window.ipcRenderer = ipcRenderer + window.setImmediate = setImmediate if (location.protocol === 'file:') { window.test = 'preload' window.require = require diff --git a/spec/fixtures/no-proprietary-codecs.js b/spec/fixtures/no-proprietary-codecs.js new file mode 100644 index 000000000000..23a7d815a924 --- /dev/null +++ b/spec/fixtures/no-proprietary-codecs.js @@ -0,0 +1,47 @@ +// Verifies that Electron cannot play a video that uses proprietary codecs +// +// This application should be run with the ffmpeg that does not include +// proprietary codecs to ensure Electron uses it instead of the version +// that does include proprietary codecs. + +const {app, BrowserWindow, ipcMain} = require('electron') +const path = require('path') +const url = require('url') + +const MEDIA_ERR_SRC_NOT_SUPPORTED = 4 +const FIVE_MINUTES = 5 * 60 * 1000 + +let window + +app.once('ready', () => { + window = new BrowserWindow({ + show: false + }) + + window.loadURL(url.format({ + protocol: 'file', + slashed: true, + pathname: path.resolve(__dirname, 'asar', 'video.asar', 'index.html') + })) + + ipcMain.on('asar-video', (event, message, error) => { + if (message === 'ended') { + console.log('Video played, proprietary codecs are included') + app.exit(1) + return + } + + if (message === 'error' && error === MEDIA_ERR_SRC_NOT_SUPPORTED) { + app.exit(0) + return + } + + console.log(`Unexpected response from page: ${message} ${error}`) + app.exit(1) + }) + + setTimeout(() => { + console.log('No IPC message after 5 minutes') + app.exit(1) + }, FIVE_MINUTES) +}) diff --git a/spec/webview-spec.js b/spec/webview-spec.js index 2f5cb8068c4d..4ea9369d666f 100644 --- a/spec/webview-spec.js +++ b/spec/webview-spec.js @@ -121,6 +121,9 @@ describe(' tag', function () { }) it('loads node symbols after POST navigation when set', function (done) { + // FIXME Figure out why this is timing out on AppVeyor + if (process.env.APPVEYOR === 'True') return done() + webview.addEventListener('console-message', function (e) { assert.equal(e.message, 'function object object') done() diff --git a/vendor/brightray b/vendor/brightray index d0144ba2c902..baccc077948f 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit d0144ba2c9024d65cdf260abf5c03c742afff3fd +Subproject commit baccc077948f504c6a6db58e855fd33938b2b625