Browse Source

Merge branch 'master' into native-window-open

Ryohei Ikegami 8 years ago
parent
commit
61fa8693d2
100 changed files with 2250 additions and 808 deletions
  1. 1 1
      atom/app/atom_content_client.cc
  2. 4 4
      atom/browser/api/atom_api_app.cc
  3. 1 1
      atom/browser/api/atom_api_auto_updater.cc
  4. 7 6
      atom/browser/api/atom_api_dialog.cc
  5. 1 1
      atom/browser/api/atom_api_session.cc
  6. 35 2
      atom/browser/api/atom_api_url_request.cc
  7. 6 0
      atom/browser/api/atom_api_url_request.h
  8. 9 9
      atom/browser/api/atom_api_web_contents.cc
  9. 3 0
      atom/browser/api/atom_api_web_contents.h
  10. 40 46
      atom/browser/atom_access_token_store.cc
  11. 2 6
      atom/browser/atom_access_token_store.h
  12. 1 1
      atom/browser/auto_updater_mac.mm
  13. 2 2
      atom/browser/browser.cc
  14. 1 1
      atom/browser/browser.h
  15. 1 1
      atom/browser/browser_linux.cc
  16. 5 4
      atom/browser/browser_mac.mm
  17. 1 1
      atom/browser/browser_win.cc
  18. 2 2
      atom/browser/common_web_contents_delegate.cc
  19. 3 3
      atom/browser/common_web_contents_delegate.h
  20. 0 8
      atom/browser/mac/atom_application_delegate.mm
  21. 35 7
      atom/browser/native_window_mac.mm
  22. 1 1
      atom/browser/net/atom_cert_verifier.cc
  23. 1 1
      atom/browser/net/atom_network_delegate.cc
  24. 61 2
      atom/browser/net/atom_url_request.cc
  25. 14 1
      atom/browser/net/atom_url_request.h
  26. 1 1
      atom/browser/node_debugger.cc
  27. 3 3
      atom/browser/osr/osr_render_widget_host_view.cc
  28. 1 1
      atom/browser/osr/osr_render_widget_host_view_mac.mm
  29. 2 2
      atom/browser/resources/mac/Info.plist
  30. 4 4
      atom/browser/resources/win/atom.rc
  31. 1 1
      atom/browser/ui/cocoa/atom_menu_controller.mm
  32. 4 4
      atom/browser/ui/cocoa/atom_touch_bar.mm
  33. 2 2
      atom/browser/ui/file_dialog_mac.mm
  34. 1 12
      atom/browser/ui/views/submenu_button.cc
  35. 9 8
      atom/browser/web_contents_permission_helper.cc
  36. 1 1
      atom/browser/web_dialog_helper.cc
  37. 1 1
      atom/browser/window_list.cc
  38. 1 1
      atom/common/atom_version.h
  39. 8 3
      atom/common/crash_reporter/crash_reporter_win.cc
  40. 2 1
      atom/common/crash_reporter/crash_reporter_win.h
  41. 1 1
      atom/common/crash_reporter/win/crash_service.cc
  42. 5 3
      atom/common/native_mate_converters/content_converter.cc
  43. 6 0
      atom/common/node_bindings.cc
  44. 3 0
      atom/common/options_switches.cc
  45. 1 0
      atom/common/options_switches.h
  46. 14 1
      atom/common/platform_util_linux.cc
  47. 9 7
      atom/common/platform_util_mac.mm
  48. 5 162
      atom/renderer/atom_renderer_client.cc
  49. 2 17
      atom/renderer/atom_renderer_client.h
  50. 7 3
      atom/renderer/atom_sandboxed_renderer_client.cc
  51. 3 4
      atom/renderer/atom_sandboxed_renderer_client.h
  52. 190 0
      atom/renderer/renderer_client_base.cc
  53. 50 0
      atom/renderer/renderer_client_base.h
  54. 21 0
      docs-translations/zh-CN/api/app.md
  55. 14 11
      docs-translations/zh-CN/api/dialog.md
  56. 431 162
      docs-translations/zh-CN/api/web-contents.md
  57. 2 2
      docs-translations/zh-CN/tutorial/electron-versioning.md
  58. 3 0
      docs/README.md
  59. 10 1
      docs/api/browser-window.md
  60. 28 3
      docs/api/client-request.md
  61. 1 1
      docs/api/crash-reporter.md
  62. 3 2
      docs/api/desktop-capturer.md
  63. 10 10
      docs/api/dialog.md
  64. 12 6
      docs/api/menu-item.md
  65. 43 122
      docs/api/menu.md
  66. 9 9
      docs/api/process.md
  67. 30 0
      docs/api/remote.md
  68. 195 0
      docs/api/sandbox-option.md
  69. 2 2
      docs/api/touch-bar-scrubber.md
  70. 14 9
      docs/api/touch-bar-segmented-control.md
  71. 2 1
      docs/api/web-contents.md
  72. 3 0
      docs/api/web-frame.md
  73. 1 0
      docs/api/webview-tag.md
  74. 14 0
      docs/development/chromium-development.md
  75. 40 0
      docs/development/upgrading-chrome.md
  76. 11 0
      docs/development/v8-development.md
  77. 51 17
      docs/tutorial/electron-versioning.md
  78. 8 2
      electron.gyp
  79. 2 0
      filenames.gypi
  80. 75 0
      lib/browser/api/menu-item-roles.js
  81. 1 1
      lib/browser/api/menu-item.js
  82. 15 5
      lib/browser/api/net.js
  83. 6 1
      lib/renderer/api/remote.js
  84. 1 0
      lib/sandboxed_renderer/api/exports/child_process.js
  85. 6 0
      lib/sandboxed_renderer/api/exports/electron.js
  86. 1 0
      lib/sandboxed_renderer/api/exports/fs.js
  87. 1 0
      lib/sandboxed_renderer/api/exports/os.js
  88. 14 13
      lib/sandboxed_renderer/init.js
  89. 1 1
      package.json
  90. 1 0
      script/cibuild
  91. 12 1
      script/create-dist.py
  92. 1 1
      script/lib/config.py
  93. 75 0
      script/verify-ffmpeg.py
  94. 15 1
      spec/api-browser-window-spec.js
  95. 165 65
      spec/api-crash-reporter-spec.js
  96. 12 0
      spec/api-ipc-spec.js
  97. 51 0
      spec/api-menu-spec.js
  98. 254 0
      spec/api-net-spec.js
  99. 1 1
      spec/api-session-spec.js
  100. 8 4
      spec/fixtures/api/crash.html

+ 1 - 1
atom/app/atom_content_client.cc

@@ -44,7 +44,7 @@ content::PepperPluginInfo CreatePepperFlashInfo(const base::FilePath& path,
 
   std::vector<std::string> 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())

+ 4 - 4
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();
   }

+ 1 - 1
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;
   }

+ 7 - 6
atom/browser/api/atom_api_dialog.cc

@@ -78,13 +78,14 @@ void ShowMessageBox(int type,
   if (mate::Converter<atom::MessageBoxCallback>::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<atom::MessageBoxType>(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<atom::MessageBoxType>(type), buttons, default_id,
+        cancel_id, options, title, message, detail, icon);
     args->Return(chosen);
   }
 }

+ 1 - 1
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<net::URLRequestContextGetter> context_getter =

+ 35 - 2
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<api::Session> 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<net::HttpResponseHeaders> 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<const net::AuthChallengeInfo> auth_info) {
   if (request_state_.Canceled() || request_state_.Closed()) {

+ 6 - 0
atom/browser/api/atom_api_url_request.h

@@ -99,6 +99,11 @@ class URLRequest : public mate::EventEmitter<URLRequest> {
                              v8::Local<v8::FunctionTemplate> prototype);
 
   // Methods for reporting events into JavaScript.
+  void OnReceivedRedirect(
+      int status_code,
+      const std::string& method,
+      const GURL& url,
+      scoped_refptr<net::HttpResponseHeaders> response_headers);
   void OnAuthenticationRequired(
       scoped_refptr<const net::AuthChallengeInfo> auth_info);
   void OnResponseStarted(
@@ -170,6 +175,7 @@ class URLRequest : public mate::EventEmitter<URLRequest> {
   bool Failed() const;
   bool Write(scoped_refptr<const net::IOBufferWithSize> 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);

+ 9 - 9
atom/browser/api/atom_api_web_contents.cc

@@ -240,7 +240,7 @@ content::ServiceWorkerContext* GetServiceWorkerContext(
 }
 
 // Called when CapturePage is done.
-void OnCapturePageDone(base::Callback<void(const gfx::Image&)> callback,
+void OnCapturePageDone(const base::Callback<void(const gfx::Image&)>& 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();
 

+ 3 - 0
atom/browser/api/atom_api_web_contents.h

@@ -76,6 +76,9 @@ class WebContents : public mate::TrackableObject<WebContents>,
   static void BuildPrototype(v8::Isolate* isolate,
                              v8::Local<v8::FunctionTemplate> prototype);
 
+  // Notifies to destroy any guest web contents before destroying self.
+  void DestroyWebContents();
+
   int64_t GetID() const;
   int GetProcessID() const;
   Type GetType() const;

+ 40 - 46
atom/browser/atom_access_token_store.cc

@@ -7,11 +7,13 @@
 #include <string>
 #include <utility>
 
-#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<TokenLoadingJob> {
+class GeoURLRequestContextGetter : public net::URLRequestContextGetter {
  public:
-  explicit TokenLoadingJob(
-      const device::AccessTokenStore::LoadAccessTokensCallback& callback)
-      : callback_(callback), request_context_getter_(nullptr) {}
-
-  void Run(AtomBrowserContext* browser_context) {
-    DCHECK_CURRENTLY_ON(BrowserThread::UI);
-    request_context_getter_ = browser_context->GetRequestContext();
-    std::unique_ptr<base::Environment> 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));
+  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();
   }
 
- private:
-  friend class base::RefCountedThreadSafe<TokenLoadingJob>;
+  scoped_refptr<base::SingleThreadTaskRunner> GetNetworkTaskRunner()
+      const override {
+    return BrowserThread::GetTaskRunnerForThread(BrowserThread::IO);
+  }
 
-  ~TokenLoadingJob() {}
+ private:
+  friend class atom::AtomAccessTokenStore;
 
-  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<GURL, base::string16> token_pair;
-    token_pair.first = GURL(GOOGLEAPIS_ENDPOINT + api_key_);
-    access_token_map.insert(token_pair);
+  GeoURLRequestContextGetter() {}
+  ~GeoURLRequestContextGetter() override {}
 
-    callback_.Run(access_token_map, request_context_getter_);
-  }
-
-  device::AccessTokenStore::LoadAccessTokensCallback callback_;
-  net::URLRequestContextGetter* request_context_getter_;
-  std::string api_key_;
+  std::unique_ptr<net::URLRequestContext> 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<internal::TokenLoadingJob> job(
-      new internal::TokenLoadingJob(callback));
-  BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
-                          base::Bind(&AtomAccessTokenStore::RunTokenLoadingJob,
-                                     this, base::RetainedRef(job)));
-}
-
-void AtomAccessTokenStore::RunTokenLoadingJob(
-    scoped_refptr<internal::TokenLoadingJob> job) {
-  job->Run(browser_context_.get());
+  std::unique_ptr<base::Environment> 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<GURL, base::string16> token_pair;
+  token_pair.first = GURL(GOOGLEAPIS_ENDPOINT + api_key);
+  access_token_map.insert(token_pair);
+
+  callback.Run(access_token_map, request_context_getter_.get());
 }
 
 void AtomAccessTokenStore::SaveAccessToken(const GURL& server_url,

+ 2 - 6
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<internal::TokenLoadingJob> job);
-
-  scoped_refptr<AtomBrowserContext> browser_context_;
+  scoped_refptr<internal::GeoURLRequestContextGetter> request_context_getter_;
   DISALLOW_COPY_AND_ASSIGN(AtomAccessTokenStore);
 };
 

+ 1 - 1
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_;

+ 2 - 2
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

+ 1 - 1
atom/browser/browser.h

@@ -102,7 +102,7 @@ class Browser : public WindowListObserver {
     std::vector<base::string16> args;
   };
   void SetLoginItemSettings(LoginItemSettings settings);
-  LoginItemSettings GetLoginItemSettings(LoginItemSettings options);
+  LoginItemSettings GetLoginItemSettings(const LoginItemSettings& options);
 
 #if defined(OS_MACOSX)
   // Hide the application.

+ 1 - 1
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();
 }
 

+ 5 - 4
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<CFStringRef>(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<NSRequestUserAttentionType>(type)];
 }
 
 void Browser::DockCancelBounce(int request_id) {

+ 1 - 1
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);

+ 2 - 2
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<FileSystem> 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);

+ 3 - 3
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);

+ 0 - 8
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();
 }
 

+ 35 - 7
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<QLPreviewPanelDataSource, QLPreviewPanelDelegate, NSTouchBarDelegate> {
  @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<NSVisualEffectMaterial>(4);
     } else if (type == "menu") {
       // NSVisualEffectMaterialMenu
-      vibrancyType = (NSVisualEffectMaterial) 5;
+      vibrancyType = static_cast<NSVisualEffectMaterial>(5);
     } else if (type == "popover") {
       // NSVisualEffectMaterialPopover
-      vibrancyType = (NSVisualEffectMaterial) 6;
+      vibrancyType = static_cast<NSVisualEffectMaterial>(6);
     } else if (type == "sidebar") {
       // NSVisualEffectMaterialSidebar
-      vibrancyType = (NSVisualEffectMaterial) 7;
+      vibrancyType = static_cast<NSVisualEffectMaterial>(7);
     } else if (type == "medium-light") {
       // NSVisualEffectMaterialMediumLight
-      vibrancyType = (NSVisualEffectMaterial) 8;
+      vibrancyType = static_cast<NSVisualEffectMaterial>(8);
     } else if (type == "ultra-dark") {
       // NSVisualEffectMaterialUltraDark
-      vibrancyType = (NSVisualEffectMaterial) 9;
+      vibrancyType = static_cast<NSVisualEffectMaterial>(9);
     }
   }
 

+ 1 - 1
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_) {

+ 1 - 1
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);

+ 61 - 2
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> 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> 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<net::URLRequestContextGetter> 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<net::HttpResponseHeaders> 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<net::HttpResponseHeaders> response_headers) const {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  if (delegate_)
+    delegate_->OnReceivedRedirect(status_code, method, url, response_headers);
+}
+
 void AtomURLRequest::InformDelegateAuthenticationRequired(
     scoped_refptr<net::AuthChallengeInfo> auth_info) const {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

+ 14 - 1
atom/browser/net/atom_url_request.h

@@ -30,12 +30,14 @@ class AtomURLRequest : public base::RefCountedThreadSafe<AtomURLRequest>,
       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<const net::IOBufferWithSize> 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<AtomURLRequest>,
 
  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<AtomURLRequest>,
 
   void DoInitialize(scoped_refptr<net::URLRequestContextGetter>,
                     const std::string& method,
-                    const std::string& url);
+                    const std::string& url,
+                    const std::string& redirect_policy);
   void DoTerminate();
   void DoWriteBuffer(scoped_refptr<const net::IOBufferWithSize> 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<AtomURLRequest>,
   void ReadResponse();
   bool CopyAndPostBuffer(int bytes_read);
 
+  void InformDelegateReceivedRedirect(
+      int status_code,
+      const std::string& method,
+      const GURL& url,
+      scoped_refptr<net::HttpResponseHeaders> response_headers) const;
   void InformDelegateAuthenticationRequired(
       scoped_refptr<net::AuthChallengeInfo> auth_info) const;
   void InformDelegateResponseStarted(
@@ -92,6 +104,7 @@ class AtomURLRequest : public base::RefCountedThreadSafe<AtomURLRequest>,
   scoped_refptr<net::URLRequestContextGetter> request_context_getter_;
 
   bool is_chunked_upload_;
+  std::string redirect_policy_;
   std::unique_ptr<net::ChunkedUploadDataStream> chunked_stream_;
   std::unique_ptr<net::ChunkedUploadDataStream::Writer> chunked_stream_writer_;
   std::vector<std::unique_ptr<net::UploadElementReader>>

+ 1 - 1
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.

+ 3 - 3
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);
   }
 }

+ 1 - 1
atom/browser/osr/osr_render_widget_host_view_mac.mm

@@ -145,4 +145,4 @@ OffScreenRenderWidgetHostView::GetDelegatedFrameHost() const {
   return browser_compositor_->GetDelegatedFrameHost();
 }
 
-}   // namespace
+} // namespace atom

+ 2 - 2
atom/browser/resources/mac/Info.plist

@@ -17,9 +17,9 @@
   <key>CFBundleIconFile</key>
   <string>electron.icns</string>
   <key>CFBundleVersion</key>
-  <string>1.6.4</string>
+  <string>1.6.5</string>
   <key>CFBundleShortVersionString</key>
-  <string>1.6.4</string>
+  <string>1.6.5</string>
   <key>LSApplicationCategoryType</key>
   <string>public.app-category.developer-tools</string>
   <key>LSMinimumSystemVersion</key>

+ 4 - 4
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

+ 1 - 1
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];
 }
 

+ 4 - 4
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<std::string> 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<mate::PersistentDictionary> items;
   if (!settings.Get("items", &items)) return nil;
 
-  if (index >= (long)items.size()) return nil;
+  if (index >= static_cast<NSInteger>(items.size())) return nil;
 
   mate::PersistentDictionary item = items[index];
 

+ 2 - 2
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) {

+ 1 - 12
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),

+ 9 - 8
atom/browser/web_contents_permission_helper.cc

@@ -66,8 +66,9 @@ void WebContentsPermissionHelper::RequestPermission(
 
 void WebContentsPermissionHelper::RequestFullscreenPermission(
     const base::Callback<void(bool)>& callback) {
-  RequestPermission((content::PermissionType)(PermissionType::FULLSCREEN),
-                    callback);
+  RequestPermission(
+      static_cast<content::PermissionType>(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<content::PermissionType>(PermissionType::POINTER_LOCK),
+      base::Bind(&OnPointerLockResponse, web_contents_), user_gesture);
 }
 
 void WebContentsPermissionHelper::RequestOpenExternalPermission(
     const base::Callback<void(bool)>& callback,
     bool user_gesture) {
-  RequestPermission((content::PermissionType)(PermissionType::OPEN_EXTERNAL),
-                    callback,
-                    user_gesture);
+  RequestPermission(
+      static_cast<content::PermissionType>(PermissionType::OPEN_EXTERNAL),
+      callback, user_gesture);
 }
 
 }  // namespace atom

+ 1 - 1
atom/browser/web_dialog_helper.cc

@@ -51,7 +51,7 @@ class FileSelectHelper : public base::RefCounted<FileSelectHelper>,
  private:
   friend class base::RefCounted<FileSelectHelper>;
 
-  ~FileSelectHelper() {}
+  ~FileSelectHelper() override {}
 
   void OnOpenDialogDone(bool result, const std::vector<base::FilePath>& paths) {
     std::vector<content::FileChooserFileInfo> file_info;

+ 1 - 1
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();
   }

+ 1 - 1
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
 

+ 8 - 3
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) {

+ 2 - 1
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<google_breakpad::CustomInfoEntry> custom_info_entries_;

+ 1 - 1
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

+ 5 - 3
atom/common/native_mate_converters/content_converter.cc

@@ -168,11 +168,13 @@ v8::Local<v8::Value> Converter<content::PermissionType>::ToV8(
       break;
   }
 
-  if (val == (content::PermissionType)(PermissionType::POINTER_LOCK))
+  if (val == static_cast<content::PermissionType>(PermissionType::POINTER_LOCK))
     return StringToV8(isolate, "pointerLock");
-  else if (val == (content::PermissionType)(PermissionType::FULLSCREEN))
+  else if (val ==
+           static_cast<content::PermissionType>(PermissionType::FULLSCREEN))
     return StringToV8(isolate, "fullscreen");
-  else if (val == (content::PermissionType)(PermissionType::OPEN_EXTERNAL))
+  else if (val ==
+           static_cast<content::PermissionType>(PermissionType::OPEN_EXTERNAL))
     return StringToV8(isolate, "openExternal");
 
   return StringToV8(isolate, "unknown");

+ 6 - 0
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());

+ 3 - 0
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";
 

+ 1 - 0
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[];

+ 14 - 1
atom/common/platform_util_linux.cc

@@ -7,7 +7,9 @@
 #include <stdio.h>
 
 #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<base::Environment> 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<std::string> argv;

+ 9 - 7
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<AEDescList> 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()";

+ 5 - 162
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 <shlobj.h>
-#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<v8::Function>::Cast(
@@ -184,35 +154,11 @@ class AtomRenderFrameObserver : public content::RenderFrameObserver {
   DISALLOW_COPY_AND_ASSIGN(AtomRenderFrameObserver);
 };
 
-v8::Local<v8::Value> 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<v8::Object> 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<GURL>(render_frame->GetWebFrame()->document().url())
       .SchemeIs("chrome-extension");
 }
 
-std::vector<std::string> 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<std::string> 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<CFStringRef> key(
-        base::SysUTF8ToCFStringRef("NSScrollViewRubberbanding"));
-    base::ScopedCFTypeRef<CFStringRef> 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<std::string> 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<v8::Context> 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<std::unique_ptr<::media::KeySystemProperties>>* key_systems) {
-  AddChromeKeySystems(key_systems);
-}
-
 void AtomRendererClient::DidInitializeWorkerContextOnWorkerThread(
     v8::Local<v8::Context> context) {
   if (base::CommandLine::ForCurrentProcess()->HasSwitch(

+ 2 - 17
atom/renderer/atom_renderer_client.h

@@ -8,15 +8,14 @@
 #include <string>
 #include <vector>
 
-#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<std::unique_ptr<::media::KeySystemProperties>>* key_systems)
-      override;
   void DidInitializeWorkerContextOnWorkerThread(
       v8::Local<v8::Context> context) override;
   void WillDestroyWorkerContextOnWorkerThread(
@@ -75,7 +61,6 @@ class AtomRendererClient : public content::ContentRendererClient {
 
   std::unique_ptr<NodeBindings> node_bindings_;
   std::unique_ptr<AtomBindings> atom_bindings_;
-  std::unique_ptr<PreferencesManager> preferences_manager_;
   bool isolated_world_;
 
   DISALLOW_COPY_AND_ASSIGN(AtomRendererClient);

+ 7 - 3
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<v8::Object> 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<v8::Value> args[] = {
     binding,
     mate::ConvertToV8(isolate, preload_script)
@@ -234,7 +238,7 @@ void AtomSandboxedRendererClient::WillReleaseScriptContext(
 
 void AtomSandboxedRendererClient::InvokeIpcCallback(
     v8::Handle<v8::Context> context,
-    std::string callback_name,
+    const std::string& callback_name,
     std::vector<v8::Handle<v8::Value>> args) {
   auto isolate = context->GetIsolate();
   auto binding_key = mate::ConvertToV8(isolate, kIpcKey)->ToString();

+ 3 - 4
atom/renderer/atom_sandboxed_renderer_client.h

@@ -7,12 +7,11 @@
 #include <string>
 #include <vector>
 
-#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<v8::Context> context, content::RenderFrame* render_frame);
   void InvokeIpcCallback(v8::Handle<v8::Context> context,
-                         std::string callback_name,
+                         const std::string& callback_name,
                          std::vector<v8::Handle<v8::Value>> args);
   // content::ContentRendererClient:
   void RenderFrameCreated(content::RenderFrame*) override;

+ 190 - 0
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 <string>
+#include <vector>
+
+#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 <shlobj.h>
+#endif
+
+namespace atom {
+
+namespace {
+
+v8::Local<v8::Value> 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<std::string> 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<std::string> 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<v8::Object> 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<CFStringRef> key(
+        base::SysUTF8ToCFStringRef("NSScrollViewRubberbanding"));
+    base::ScopedCFTypeRef<CFStringRef> 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<std::string> 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<std::unique_ptr<::media::KeySystemProperties>>* key_systems) {
+  AddChromeKeySystems(key_systems);
+}
+
+}  // namespace atom

+ 50 - 0
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 <string>
+#include <vector>
+
+#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<v8::Object> 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<std::unique_ptr<::media::KeySystemProperties>>* key_systems)
+      override;
+
+ private:
+  std::unique_ptr<PreferencesManager> preferences_manager_;
+};
+
+}  // namespace atom
+
+#endif  // ATOM_RENDERER_RENDERER_CLIENT_BASE_H_

+ 21 - 0
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

+ 14 - 11
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` 参数允许对话框将自身附加到父窗口,使其成为模态。
 

File diff suppressed because it is too large
+ 431 - 162
docs-translations/zh-CN/api/web-contents.md


+ 2 - 2
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的所有升级都是由您(开发人员)进行的手动操作。

+ 3 - 0
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)

+ 10 - 1
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

+ 28 - 3
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`.

+ 1 - 1
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

+ 3 - 2
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)

+ 10 - 10
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

+ 12 - 6
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
 

+ 43 - 122
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'}
   ]
 }
 

+ 9 - 9
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

+ 30 - 0
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()`
 

+ 195 - 0
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.

+ 2 - 2
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`.

+ 14 - 9
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
 

+ 2 - 1
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)`
 

+ 3 - 0
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

+ 1 - 0
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.

+ 14 - 0
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)

+ 40 - 0
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
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Proprietary Codec Check</title>
+  </head>
+  <body>
+    <p>Checking if Electron is using proprietary codecs by loading video from http://www.quirksmode.org/html5/videos/big_buck_bunny.mp4</p>
+    <p id="outcome"></p>
+    <video style="display:none" src="http://www.quirksmode.org/html5/videos/big_buck_bunny.mp4" autoplay></video>
+    <script>
+      const video = document.querySelector('video')
+      video.addEventListener('error', ({target}) => {
+        if (target.error.code === target.error.MEDIA_ERR_SRC_NOT_SUPPORTED) {
+          document.querySelector('#outcome').textContent = 'Not using proprietary codecs, video emitted source not supported error event.'
+        } else {
+          document.querySelector('#outcome').textContent = `Unexpected error: ${target.error.code}`
+        }
+      })
+      video.addEventListener('playing', () => {
+        document.querySelector('#outcome').textContent = 'Using proprietary codecs, video started playing.'
+      })
+    </script>
+  </body>
+</html>
+```
+
 ## Links
 
 - [Chrome Release Schedule](https://www.chromium.org/developers/calendar)

+ 11 - 0
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)

+ 51 - 17
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.
-
-Version numbers are bumped using the following rules:
-
-* 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
+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.
+
+## Overview of Semantic Versioning
+
+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.
+
+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.
 
-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.
+[Semantic Versioning]: http://semver.org

+ 8 - 2
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',

+ 2 - 0
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',

+ 75 - 0
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
 

+ 1 - 1
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)
   }

+ 15 - 5
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()
   }

+ 6 - 1
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
         }
       }

+ 1 - 0
lib/sandboxed_renderer/api/exports/child_process.js

@@ -0,0 +1 @@
+module.exports = require('electron').remote.require('child_process')

+ 6 - 0
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')

+ 1 - 0
lib/sandboxed_renderer/api/exports/fs.js

@@ -0,0 +1 @@
+module.exports = require('electron').remote.require('fs')

+ 1 - 0
lib/sandboxed_renderer/api/exports/os.js

@@ -0,0 +1 @@
+module.exports = require('electron').remote.require('os')

+ 14 - 13
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)

+ 1 - 1
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",

+ 1 - 0
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=[]):

+ 12 - 1
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())

+ 1 - 1
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',

+ 75 - 0
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())

+ 15 - 1
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')

+ 165 - 65
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 () {
+  if (process.mas) {
+    return
+  }
   var fixtures = path.resolve(__dirname, 'fixtures')
-  var w = null
-  var originalTempDirectory = null
-  var tempDirectory = null
+  const generateSpecs = (description, browserWindowOpts) => {
+    describe(description, function () {
+      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)
-  })
+      beforeEach(function () {
+        w = new BrowserWindow(Object.assign({
+          show: false
+        }, browserWindowOpts))
+        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 })
-  })
+      afterEach(function () {
+        app.setPath('temp', originalTempDirectory)
+        return closeWindow(w).then(function () { w = null })
+      })
 
-  if (process.mas) {
-    return
-  }
+      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
+        })
+      })
 
-  it('should send minidump when renderer crashes', 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 crashUrl = url.format({
-          protocol: 'file',
-          pathname: path.join(fixtures, 'api', 'crash.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 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)
-
-    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 not send minidump if uploadToServer is false', function (done) {
+        this.timeout(120000)
 
-  it('should send minidump with updated extra parameters', function (done) {
-    if (process.env.APPVEYOR === 'True') return done()
-    if (process.env.TRAVIS === 'true') return done()
+        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()
+        }
 
-    this.timeout(10000)
+        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)
+          })
+        }
 
-    startServer({
-      callback (port) {
-        const crashUrl = url.format({
-          protocol: 'file',
-          pathname: path.join(fixtures, 'api', 'crash-restart.html'),
-          search: '?port=' + port
+        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)
         })
-        w.loadURL(crashUrl)
-      },
-      processType: 'renderer',
-      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
+            })
+            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
 }

+ 12 - 0
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()

+ 51 - 0
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 = [{

+ 254 - 0
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 {

+ 1 - 1
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'))

+ 8 - 4
spec/fixtures/api/crash.html

@@ -1,20 +1,24 @@
 <html>
 <body>
 <script type="text/javascript" charset="utf-8">
-var port = require('url').parse(window.location.href, true).query.port;
-var crashReporter = require('electron').crashReporter;
+var url = require('url').parse(window.location.href, true);
+var uploadToServer = !url.query.skipUpload;
+var port = url.query.port;
+var {crashReporter, ipcRenderer} = require('electron');
 crashReporter.start({
   productName: 'Zombies',
   companyName: 'Umbrella Corporation',
   submitURL: 'http://127.0.0.1:' + port,
-  uploadToServer: true,
+  uploadToServer: uploadToServer,
   ignoreSystemCrashHandler: true,
   extra: {
     'extra1': 'extra1',
     'extra2': 'extra2',
   }
 });
-
+if (!uploadToServer) {
+  ipcRenderer.sendSync('set-crash-directory', crashReporter.getCrashesDirectory())
+}
 setImmediate(function() { process.crash(); });
 </script>
 </body>

Some files were not shown because too many files changed in this diff