Browse Source

Merge branch 'master' into chrome-storage-sync

Jessica Lord 8 years ago
parent
commit
f121f46a24
50 changed files with 601 additions and 188 deletions
  1. 5 2
      atom/browser/api/atom_api_app.cc
  2. 2 1
      atom/browser/api/atom_api_app.h
  3. 24 1
      atom/browser/api/atom_api_download_item.cc
  4. 4 0
      atom/browser/api/atom_api_download_item.h
  5. 1 8
      atom/browser/api/atom_api_protocol.cc
  6. 7 3
      atom/browser/api/atom_api_protocol.h
  7. 10 0
      atom/browser/api/atom_api_session.cc
  8. 2 0
      atom/browser/api/atom_api_session.h
  9. 12 0
      atom/browser/api/atom_api_web_contents.cc
  10. 1 0
      atom/browser/api/atom_api_web_contents.h
  11. 5 2
      atom/browser/atom_resource_dispatcher_host_delegate.cc
  12. 6 2
      atom/browser/browser.cc
  13. 3 1
      atom/browser/browser.h
  14. 2 1
      atom/browser/browser_observer.h
  15. 10 1
      atom/browser/login_handler.cc
  16. 0 1
      atom/browser/login_handler.h
  17. 2 1
      atom/browser/native_window.cc
  18. 6 4
      atom/browser/native_window_mac.mm
  19. 1 6
      atom/browser/net/atom_network_delegate.cc
  20. 2 2
      atom/browser/net/js_asker.cc
  21. 7 3
      atom/browser/net/js_asker.h
  22. 4 4
      atom/browser/ui/win/taskbar_host.cc
  23. 2 2
      atom/browser/ui/win/taskbar_host.h
  24. 13 16
      atom/common/native_mate_converters/net_converter.cc
  25. 4 6
      atom/common/native_mate_converters/net_converter.h
  26. 17 0
      docs-translations/ko-KR/tutorial/mac-app-store-submission-guide.md
  27. 12 12
      docs-translations/ru-RU/README.md
  28. 30 30
      docs-translations/ru-RU/tutorial/quick-start.md
  29. 20 11
      docs-translations/ru-RU/tutorial/supported-platforms.md
  30. 1 1
      docs-translations/zh-CN/api/app.md
  31. 9 9
      docs-translations/zh-CN/tutorial/quick-start.md
  32. 1 0
      docs/api/app.md
  33. 59 19
      docs/api/download-item.md
  34. 19 0
      docs/api/session.md
  35. 1 1
      docs/development/build-system-overview.md
  36. 2 0
      docs/tutorial/using-pepper-flash-plugin.md
  37. 2 1
      filenames.gypi
  38. 3 3
      lib/browser/api/protocol.js
  39. 6 2
      lib/browser/api/session.js
  40. 11 9
      lib/browser/api/web-contents.js
  41. 29 20
      lib/browser/chrome-extension.js
  42. 2 0
      lib/renderer/chrome-api.js
  43. 84 0
      lib/renderer/extensions/i18n.js
  44. 43 1
      spec/api-browser-window-spec.js
  45. 50 1
      spec/api-session-spec.js
  46. 10 0
      spec/fixtures/devtools-extensions/foo/_locales/en/messages.json
  47. 2 0
      spec/fixtures/devtools-extensions/foo/index.html
  48. 2 1
      spec/fixtures/devtools-extensions/foo/manifest.json
  49. 30 0
      spec/fixtures/pages/webview-devtools.html
  50. 21 0
      spec/webview-spec.js

+ 5 - 2
atom/browser/api/atom_api_app.cc

@@ -110,6 +110,8 @@ int GetPathConstant(const std::string& name) {
     return chrome::DIR_USER_PICTURES;
   else if (name == "videos")
     return chrome::DIR_USER_VIDEOS;
+  else if (name == "pepperFlashSystemPlugin")
+    return chrome::FILE_PEPPER_FLASH_SYSTEM_PLUGIN;
   else
     return -1;
 }
@@ -261,13 +263,14 @@ void App::OnContinueUserActivity(
 }
 #endif
 
-void App::OnLogin(LoginHandler* login_handler) {
+void App::OnLogin(LoginHandler* login_handler,
+                  const base::DictionaryValue& request_details) {
   v8::Locker locker(isolate());
   v8::HandleScope handle_scope(isolate());
   bool prevent_default = Emit(
       "login",
       WebContents::CreateFrom(isolate(), login_handler->GetWebContents()),
-      login_handler->request(),
+      request_details,
       login_handler->auth_info(),
       base::Bind(&PassLoginInformation, make_scoped_refptr(login_handler)));
 

+ 2 - 1
atom/browser/api/atom_api_app.h

@@ -70,7 +70,8 @@ class App : public AtomBrowserClient::Delegate,
   void OnActivate(bool has_visible_windows) override;
   void OnWillFinishLaunching() override;
   void OnFinishLaunching() override;
-  void OnLogin(LoginHandler* login_handler) override;
+  void OnLogin(LoginHandler* login_handler,
+               const base::DictionaryValue& request_details) override;
 #if defined(OS_MACOSX)
   void OnContinueUserActivity(
       bool* prevent_default,

+ 24 - 1
atom/browser/api/atom_api_download_item.cc

@@ -25,6 +25,9 @@ struct Converter<content::DownloadItem::DownloadState> {
                                    content::DownloadItem::DownloadState state) {
     std::string download_state;
     switch (state) {
+      case content::DownloadItem::IN_PROGRESS:
+        download_state = "progressing";
+        break;
       case content::DownloadItem::COMPLETE:
         download_state = "completed";
         break;
@@ -85,7 +88,7 @@ void DownloadItem::OnDownloadUpdated(content::DownloadItem* item) {
     // Destroy the item once item is downloaded.
     base::MessageLoop::current()->PostTask(FROM_HERE, GetDestroyClosure());
   } else {
-    Emit("updated");
+    Emit("updated", item->GetState());
   }
 }
 
@@ -99,10 +102,18 @@ void DownloadItem::Pause() {
   download_item_->Pause();
 }
 
+bool DownloadItem::IsPaused() const {
+  return download_item_->IsPaused();
+}
+
 void DownloadItem::Resume() {
   download_item_->Resume();
 }
 
+bool DownloadItem::CanResume() const {
+  return download_item_->CanResume();
+}
+
 void DownloadItem::Cancel() {
   download_item_->Cancel(true);
   download_item_->Remove();
@@ -141,6 +152,14 @@ const GURL& DownloadItem::GetURL() const {
   return download_item_->GetURL();
 }
 
+content::DownloadItem::DownloadState DownloadItem::GetState() const {
+  return download_item_->GetState();
+}
+
+bool DownloadItem::IsDone() const {
+  return download_item_->IsDone();
+}
+
 void DownloadItem::SetSavePath(const base::FilePath& path) {
   save_path_ = path;
 }
@@ -155,7 +174,9 @@ void DownloadItem::BuildPrototype(v8::Isolate* isolate,
   mate::ObjectTemplateBuilder(isolate, prototype)
       .MakeDestroyable()
       .SetMethod("pause", &DownloadItem::Pause)
+      .SetMethod("isPaused", &DownloadItem::IsPaused)
       .SetMethod("resume", &DownloadItem::Resume)
+      .SetMethod("canResume", &DownloadItem::CanResume)
       .SetMethod("cancel", &DownloadItem::Cancel)
       .SetMethod("getReceivedBytes", &DownloadItem::GetReceivedBytes)
       .SetMethod("getTotalBytes", &DownloadItem::GetTotalBytes)
@@ -164,6 +185,8 @@ void DownloadItem::BuildPrototype(v8::Isolate* isolate,
       .SetMethod("getFilename", &DownloadItem::GetFilename)
       .SetMethod("getContentDisposition", &DownloadItem::GetContentDisposition)
       .SetMethod("getURL", &DownloadItem::GetURL)
+      .SetMethod("getState", &DownloadItem::GetState)
+      .SetMethod("isDone", &DownloadItem::IsDone)
       .SetMethod("setSavePath", &DownloadItem::SetSavePath)
       .SetMethod("getSavePath", &DownloadItem::GetSavePath);
 }

+ 4 - 0
atom/browser/api/atom_api_download_item.h

@@ -27,7 +27,9 @@ class DownloadItem : public mate::TrackableObject<DownloadItem>,
                              v8::Local<v8::ObjectTemplate> prototype);
 
   void Pause();
+  bool IsPaused() const;
   void Resume();
+  bool CanResume() const;
   void Cancel();
   int64_t GetReceivedBytes() const;
   int64_t GetTotalBytes() const;
@@ -36,6 +38,8 @@ class DownloadItem : public mate::TrackableObject<DownloadItem>,
   std::string GetFilename() const;
   std::string GetContentDisposition() const;
   const GURL& GetURL() const;
+  content::DownloadItem::DownloadState GetState() const;
+  bool IsDone() const;
   void SetSavePath(const base::FilePath& path);
   base::FilePath GetSavePath() const;
 

+ 1 - 8
atom/browser/api/atom_api_protocol.cc

@@ -12,7 +12,7 @@
 #include "atom/browser/net/url_request_fetch_job.h"
 #include "atom/browser/net/url_request_string_job.h"
 #include "atom/common/native_mate_converters/callback.h"
-#include "atom/common/native_mate_converters/net_converter.h"
+#include "atom/common/native_mate_converters/value_converter.h"
 #include "atom/common/node_includes.h"
 #include "atom/common/options_switches.h"
 #include "base/command_line.h"
@@ -173,17 +173,10 @@ void RegisterStandardSchemes(
                                   base::JoinString(schemes, ","));
 }
 
-mate::Handle<atom::api::Protocol> CreateProtocol(v8::Isolate* isolate) {
-  auto browser_context = static_cast<atom::AtomBrowserContext*>(
-      atom::AtomBrowserMainParts::Get()->browser_context());
-  return atom::api::Protocol::Create(isolate, browser_context);
-}
-
 void Initialize(v8::Local<v8::Object> exports, v8::Local<v8::Value> unused,
                 v8::Local<v8::Context> context, void* priv) {
   v8::Isolate* isolate = context->GetIsolate();
   mate::Dictionary dict(isolate, exports);
-  dict.SetMethod("createProtocolObject", base::Bind(&CreateProtocol, isolate));
   dict.SetMethod("registerStandardSchemes", &RegisterStandardSchemes);
 }
 

+ 7 - 3
atom/browser/api/atom_api_protocol.h

@@ -9,6 +9,7 @@
 #include <map>
 #include <vector>
 
+#include "atom/browser/api/trackable_object.h"
 #include "atom/browser/net/atom_url_request_job_factory.h"
 #include "base/callback.h"
 #include "base/containers/scoped_ptr_hash_map.h"
@@ -16,7 +17,10 @@
 #include "native_mate/arguments.h"
 #include "native_mate/dictionary.h"
 #include "native_mate/handle.h"
-#include "native_mate/wrappable.h"
+
+namespace base {
+class DictionaryValue;
+}
 
 namespace net {
 class URLRequest;
@@ -30,10 +34,10 @@ class AtomURLRequestJobFactory;
 
 namespace api {
 
-class Protocol : public mate::Wrappable<Protocol> {
+class Protocol : public mate::TrackableObject<Protocol> {
  public:
   using Handler =
-      base::Callback<void(const net::URLRequest*, v8::Local<v8::Value>)>;
+      base::Callback<void(const base::DictionaryValue&, v8::Local<v8::Value>)>;
   using CompletionCallback = base::Callback<void(v8::Local<v8::Value>)>;
   using BooleanCallback = base::Callback<void(bool)>;
 

+ 10 - 0
atom/browser/api/atom_api_session.cc

@@ -9,6 +9,7 @@
 
 #include "atom/browser/api/atom_api_cookies.h"
 #include "atom/browser/api/atom_api_download_item.h"
+#include "atom/browser/api/atom_api_protocol.h"
 #include "atom/browser/api/atom_api_web_request.h"
 #include "atom/browser/atom_browser_context.h"
 #include "atom/browser/atom_browser_main_parts.h"
@@ -462,6 +463,14 @@ v8::Local<v8::Value> Session::Cookies(v8::Isolate* isolate) {
   return v8::Local<v8::Value>::New(isolate, cookies_);
 }
 
+v8::Local<v8::Value> Session::Protocol(v8::Isolate* isolate) {
+  if (protocol_.IsEmpty()) {
+    auto handle = atom::api::Protocol::Create(isolate, browser_context());
+    protocol_.Reset(isolate, handle.ToV8());
+  }
+  return v8::Local<v8::Value>::New(isolate, protocol_);
+}
+
 v8::Local<v8::Value> Session::WebRequest(v8::Isolate* isolate) {
   if (web_request_.IsEmpty()) {
     auto handle = atom::api::WebRequest::Create(isolate, browser_context());
@@ -512,6 +521,7 @@ void Session::BuildPrototype(v8::Isolate* isolate,
       .SetMethod("allowNTLMCredentialsForDomains",
                  &Session::AllowNTLMCredentialsForDomains)
       .SetProperty("cookies", &Session::Cookies)
+      .SetProperty("protocol", &Session::Protocol)
       .SetProperty("webRequest", &Session::WebRequest);
 }
 

+ 2 - 0
atom/browser/api/atom_api_session.h

@@ -81,10 +81,12 @@ class Session: public mate::TrackableObject<Session>,
   void ClearHostResolverCache(mate::Arguments* args);
   void AllowNTLMCredentialsForDomains(const std::string& domains);
   v8::Local<v8::Value> Cookies(v8::Isolate* isolate);
+  v8::Local<v8::Value> Protocol(v8::Isolate* isolate);
   v8::Local<v8::Value> WebRequest(v8::Isolate* isolate);
 
   // Cached object.
   v8::Global<v8::Value> cookies_;
+  v8::Global<v8::Value> protocol_;
   v8::Global<v8::Value> web_request_;
 
   // The X-DevTools-Emulate-Network-Conditions-Client-Id.

+ 12 - 0
atom/browser/api/atom_api_web_contents.cc

@@ -744,6 +744,15 @@ int WebContents::GetID() const {
   return web_contents()->GetRenderProcessHost()->GetID();
 }
 
+std::string WebContents::GetType() const {
+  switch (type_) {
+    case BROWSER_WINDOW: return "window";
+    case WEB_VIEW: return "webview";
+    case REMOTE: return "remote";
+    default: return "";
+  }
+}
+
 bool WebContents::Equal(const WebContents* web_contents) const {
   return GetID() == web_contents->GetID();
 }
@@ -1287,6 +1296,7 @@ void WebContents::BuildPrototype(v8::Isolate* isolate,
       .SetMethod("endFrameSubscription", &WebContents::EndFrameSubscription)
       .SetMethod("setSize", &WebContents::SetSize)
       .SetMethod("isGuest", &WebContents::IsGuest)
+      .SetMethod("getType", &WebContents::GetType)
       .SetMethod("getWebPreferences", &WebContents::GetWebPreferences)
       .SetMethod("getOwnerBrowserWindow", &WebContents::GetOwnerBrowserWindow)
       .SetMethod("hasServiceWorker", &WebContents::HasServiceWorker)
@@ -1365,6 +1375,8 @@ void Initialize(v8::Local<v8::Object> exports, v8::Local<v8::Value> unused,
   dict.SetMethod("_setWrapWebContents", &atom::api::SetWrapWebContents);
   dict.SetMethod("fromId",
                  &mate::TrackableObject<atom::api::WebContents>::FromWeakMapID);
+  dict.SetMethod("getAllWebContents",
+                 &mate::TrackableObject<atom::api::WebContents>::GetAll);
 }
 
 }  // namespace

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

@@ -59,6 +59,7 @@ class WebContents : public mate::TrackableObject<WebContents>,
                              v8::Local<v8::ObjectTemplate> prototype);
 
   int GetID() const;
+  std::string GetType() const;
   bool Equal(const WebContents* web_contents) const;
   void LoadURL(const GURL& url, const mate::Dictionary& options);
   void DownloadURL(const GURL& url);

+ 5 - 2
atom/browser/atom_resource_dispatcher_host_delegate.cc

@@ -31,10 +31,13 @@ void HandleExternalProtocolInUI(
   if (!web_contents)
     return;
 
-  GURL escaped_url(net::EscapeExternalHandlerValue(url.spec()));
-  auto callback = base::Bind(&OnOpenExternal, escaped_url);
   auto permission_helper =
       WebContentsPermissionHelper::FromWebContents(web_contents);
+  if (!permission_helper)
+    return;
+
+  GURL escaped_url(net::EscapeExternalHandlerValue(url.spec()));
+  auto callback = base::Bind(&OnOpenExternal, escaped_url);
   permission_helper->RequestOpenExternalPermission(callback, has_user_gesture);
 }
 

+ 6 - 2
atom/browser/browser.cc

@@ -151,8 +151,12 @@ void Browser::DidFinishLaunching() {
   FOR_EACH_OBSERVER(BrowserObserver, observers_, OnFinishLaunching());
 }
 
-void Browser::RequestLogin(LoginHandler* login_handler) {
-  FOR_EACH_OBSERVER(BrowserObserver, observers_, OnLogin(login_handler));
+void Browser::RequestLogin(
+    LoginHandler* login_handler,
+    std::unique_ptr<base::DictionaryValue> request_details) {
+  FOR_EACH_OBSERVER(BrowserObserver,
+                    observers_,
+                    OnLogin(login_handler, *(request_details.get())));
 }
 
 void Browser::NotifyAndShutdown() {

+ 3 - 1
atom/browser/browser.h

@@ -21,6 +21,7 @@
 #endif
 
 namespace base {
+class DictionaryValue;
 class FilePath;
 }
 
@@ -165,7 +166,8 @@ class Browser : public WindowListObserver {
   void DidFinishLaunching();
 
   // Request basic auth login.
-  void RequestLogin(LoginHandler* login_handler);
+  void RequestLogin(LoginHandler* login_handler,
+                    std::unique_ptr<base::DictionaryValue> request_details);
 
   void AddObserver(BrowserObserver* obs) {
     observers_.AddObserver(obs);

+ 2 - 1
atom/browser/browser_observer.h

@@ -49,7 +49,8 @@ class BrowserObserver {
   virtual void OnFinishLaunching() {}
 
   // The browser requests HTTP login.
-  virtual void OnLogin(LoginHandler* login_handler) {}
+  virtual void OnLogin(LoginHandler* login_handler,
+                       const base::DictionaryValue& request_details) {}
 
 #if defined(OS_MACOSX)
   // The browser wants to resume a user activity via handoff. (OS X only)

+ 10 - 1
atom/browser/login_handler.cc

@@ -5,6 +5,8 @@
 #include "atom/browser/login_handler.h"
 
 #include "atom/browser/browser.h"
+#include "atom/common/native_mate_converters/net_converter.h"
+#include "base/values.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/resource_dispatcher_host.h"
@@ -37,11 +39,18 @@ LoginHandler::LoginHandler(net::AuthChallengeInfo* auth_info,
       render_frame_id_(0) {
   content::ResourceRequestInfo::ForRequest(request_)->GetAssociatedRenderFrame(
       &render_process_host_id_,  &render_frame_id_);
+
+  // Fill request details on IO thread.
+  std::unique_ptr<base::DictionaryValue> request_details(
+      new base::DictionaryValue);
+  FillRequestDetails(request_details.get(), request_);
+
   BrowserThread::PostTask(
       BrowserThread::UI, FROM_HERE,
       base::Bind(&Browser::RequestLogin,
                  base::Unretained(Browser::Get()),
-                 base::RetainedRef(make_scoped_refptr(this))));
+                 base::RetainedRef(make_scoped_refptr(this)),
+                 base::Passed(&request_details)));
 }
 
 LoginHandler::~LoginHandler() {

+ 0 - 1
atom/browser/login_handler.h

@@ -36,7 +36,6 @@ class LoginHandler : public content::ResourceDispatcherHostLoginDelegate {
   void Login(const base::string16& username, const base::string16& password);
 
   const net::AuthChallengeInfo* auth_info() const { return auth_info_.get(); }
-  const net::URLRequest* request() const { return request_; }
 
  protected:
   ~LoginHandler() override;

+ 2 - 1
atom/browser/native_window.cc

@@ -10,6 +10,7 @@
 
 #include "atom/browser/atom_browser_context.h"
 #include "atom/browser/atom_browser_main_parts.h"
+#include "atom/browser/browser.h"
 #include "atom/browser/window_list.h"
 #include "atom/common/api/api_messages.h"
 #include "atom/common/native_mate_converters/file_path_converter.h"
@@ -166,7 +167,7 @@ void NativeWindow::InitFromOptions(const mate::Dictionary& options) {
     // For normal window, use white as default background.
     SetBackgroundColor("#FFFF");
   }
-  std::string title("Electron");
+  std::string title(Browser::Get()->GetName());
   options.Get(options::kTitle, &title);
   SetTitle(title);
 

+ 6 - 4
atom/browser/native_window_mac.mm

@@ -524,10 +524,6 @@ NativeWindowMac::NativeWindowMac(
   options.Get(options::kDisableAutoHideCursor, &disableAutoHideCursor);
   [window_ setDisableAutoHideCursor:disableAutoHideCursor];
 
-  // Disable zoom button if window is not resizable.
-  if (!maximizable)
-    SetMaximizable(false);
-
   NSView* view = inspectable_web_contents()->GetView()->GetNativeView();
   [view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
 
@@ -555,6 +551,12 @@ NativeWindowMac::NativeWindowMac(
   }];
 
   InstallView();
+
+  // Disable zoom button if window is not resizable.
+  // Set maximizable state last to ensure zoom button does not get reset
+  // by calls to other APIs.
+  if (!maximizable)
+    SetMaximizable(false);
 }
 
 NativeWindowMac::~NativeWindowMac() {

+ 1 - 6
atom/browser/net/atom_network_delegate.cc

@@ -71,18 +71,13 @@ bool MatchesFilterCondition(net::URLRequest* request,
 
 // Overloaded by multiple types to fill the |details| object.
 void ToDictionary(base::DictionaryValue* details, net::URLRequest* request) {
+  FillRequestDetails(details, request);
   details->SetInteger("id", request->identifier());
-  details->SetString("url", request->url().spec());
-  details->SetString("method", request->method());
   details->SetDouble("timestamp", base::Time::Now().ToDoubleT() * 1000);
   auto info = content::ResourceRequestInfo::ForRequest(request);
   details->SetString("resourceType",
                      info ? ResourceTypeToString(info->GetResourceType())
                           : "other");
-  std::unique_ptr<base::ListValue> list(new base::ListValue);
-  GetUploadData(list.get(), request);
-  if (!list->empty())
-    details->Set("uploadData", std::move(list));
 }
 
 void ToDictionary(base::DictionaryValue* details,

+ 2 - 2
atom/browser/net/js_asker.cc

@@ -44,7 +44,7 @@ void HandlerCallback(const BeforeStartCallback& before_start,
 
 void AskForOptions(v8::Isolate* isolate,
                    const JavaScriptHandler& handler,
-                   net::URLRequest* request,
+                   std::unique_ptr<base::DictionaryValue> request_details,
                    const BeforeStartCallback& before_start,
                    const ResponseCallback& callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
@@ -53,7 +53,7 @@ void AskForOptions(v8::Isolate* isolate,
   v8::Local<v8::Context> context = isolate->GetCurrentContext();
   v8::Context::Scope context_scope(context);
   handler.Run(
-      request,
+      *(request_details.get()),
       mate::ConvertToV8(isolate,
                         base::Bind(&HandlerCallback, before_start, callback)));
 }

+ 7 - 3
atom/browser/net/js_asker.h

@@ -5,6 +5,7 @@
 #ifndef ATOM_BROWSER_NET_JS_ASKER_H_
 #define ATOM_BROWSER_NET_JS_ASKER_H_
 
+#include "atom/common/native_mate_converters/net_converter.h"
 #include "base/callback.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
@@ -19,7 +20,7 @@
 namespace atom {
 
 using JavaScriptHandler =
-    base::Callback<void(const net::URLRequest*, v8::Local<v8::Value>)>;
+    base::Callback<void(const base::DictionaryValue&, v8::Local<v8::Value>)>;
 
 namespace internal {
 
@@ -31,7 +32,7 @@ using ResponseCallback =
 // Ask handler for options in UI thread.
 void AskForOptions(v8::Isolate* isolate,
                    const JavaScriptHandler& handler,
-                   net::URLRequest* request,
+                   std::unique_ptr<base::DictionaryValue> request_details,
                    const BeforeStartCallback& before_start,
                    const ResponseCallback& callback);
 
@@ -67,12 +68,15 @@ class JsAsker : public RequestJob {
  private:
   // RequestJob:
   void Start() override {
+    std::unique_ptr<base::DictionaryValue> request_details(
+        new base::DictionaryValue);
+    FillRequestDetails(request_details.get(), RequestJob::request());
     content::BrowserThread::PostTask(
         content::BrowserThread::UI, FROM_HERE,
         base::Bind(&internal::AskForOptions,
                    isolate_,
                    handler_,
-                   RequestJob::request(),
+                   base::Passed(&request_details),
                    base::Bind(&JsAsker::BeforeStartInUI,
                               weak_factory_.GetWeakPtr()),
                    base::Bind(&JsAsker::OnResponse,

+ 4 - 4
atom/browser/ui/win/taskbar_host.cc

@@ -54,7 +54,7 @@ TaskbarHost::~TaskbarHost() {
 
 bool TaskbarHost::SetThumbarButtons(
     HWND window, const std::vector<ThumbarButton>& buttons) {
-  if (buttons.size() > kMaxButtonsCount || !InitailizeTaskbar())
+  if (buttons.size() > kMaxButtonsCount || !InitializeTaskbar())
     return false;
 
   callback_map_.clear();
@@ -118,7 +118,7 @@ bool TaskbarHost::SetThumbarButtons(
 }
 
 bool TaskbarHost::SetProgressBar(HWND window, double value) {
-  if (!InitailizeTaskbar())
+  if (!InitializeTaskbar())
     return false;
 
   HRESULT r;
@@ -133,7 +133,7 @@ bool TaskbarHost::SetProgressBar(HWND window, double value) {
 
 bool TaskbarHost::SetOverlayIcon(
     HWND window, const gfx::Image& overlay, const std::string& text) {
-  if (!InitailizeTaskbar())
+  if (!InitializeTaskbar())
     return false;
 
   base::win::ScopedHICON icon(
@@ -152,7 +152,7 @@ bool TaskbarHost::HandleThumbarButtonEvent(int button_id) {
   return false;
 }
 
-bool TaskbarHost::InitailizeTaskbar() {
+bool TaskbarHost::InitializeTaskbar() {
   if (FAILED(taskbar_.CreateInstance(CLSID_TaskbarList,
                                      nullptr,
                                      CLSCTX_INPROC_SERVER)) ||

+ 2 - 2
atom/browser/ui/win/taskbar_host.h

@@ -44,8 +44,8 @@ class TaskbarHost {
   bool HandleThumbarButtonEvent(int button_id);
 
  private:
-  // Initailize the taskbar object.
-  bool InitailizeTaskbar();
+  // Initialize the taskbar object.
+  bool InitializeTaskbar();
 
   using CallbackMap = std::map<int, base::Closure>;
   CallbackMap callback_map_;

+ 13 - 16
atom/common/native_mate_converters/net_converter.cc

@@ -22,22 +22,6 @@
 
 namespace mate {
 
-// static
-v8::Local<v8::Value> Converter<const net::URLRequest*>::ToV8(
-    v8::Isolate* isolate, const net::URLRequest* val) {
-  std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
-  dict->SetString("method", val->method());
-  std::string url;
-  if (!val->url_chain().empty()) url = val->url().spec();
-  dict->SetStringWithoutPathExpansion("url", url);
-  dict->SetString("referrer", val->referrer());
-  std::unique_ptr<base::ListValue> list(new base::ListValue);
-  atom::GetUploadData(list.get(), val);
-  if (!list->empty())
-    dict->Set("uploadData", std::move(list));
-  return mate::ConvertToV8(isolate, *(dict.get()));
-}
-
 // static
 v8::Local<v8::Value> Converter<const net::AuthChallengeInfo*>::ToV8(
     v8::Isolate* isolate, const net::AuthChallengeInfo* val) {
@@ -69,6 +53,19 @@ v8::Local<v8::Value> Converter<scoped_refptr<net::X509Certificate>>::ToV8(
 
 namespace atom {
 
+void FillRequestDetails(base::DictionaryValue* details,
+                        const net::URLRequest* request) {
+  details->SetString("method", request->method());
+  std::string url;
+  if (!request->url_chain().empty()) url = request->url().spec();
+  details->SetStringWithoutPathExpansion("url", url);
+  details->SetString("referrer", request->referrer());
+  std::unique_ptr<base::ListValue> list(new base::ListValue);
+  GetUploadData(list.get(), request);
+  if (!list->empty())
+    details->Set("uploadData", std::move(list));
+}
+
 void GetUploadData(base::ListValue* upload_data_list,
                    const net::URLRequest* request) {
   const net::UploadDataStream* upload_data = request->get_upload();

+ 4 - 6
atom/common/native_mate_converters/net_converter.h

@@ -9,6 +9,7 @@
 #include "native_mate/converter.h"
 
 namespace base {
+class DictionaryValue;
 class ListValue;
 }
 
@@ -20,12 +21,6 @@ class X509Certificate;
 
 namespace mate {
 
-template<>
-struct Converter<const net::URLRequest*> {
-  static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
-                                   const net::URLRequest* val);
-};
-
 template<>
 struct Converter<const net::AuthChallengeInfo*> {
   static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
@@ -42,6 +37,9 @@ struct Converter<scoped_refptr<net::X509Certificate>> {
 
 namespace atom {
 
+void FillRequestDetails(base::DictionaryValue* details,
+                        const net::URLRequest* request);
+
 void GetUploadData(base::ListValue* upload_data_list,
                    const net::URLRequest* request);
 

+ 17 - 0
docs-translations/ko-KR/tutorial/mac-app-store-submission-guide.md

@@ -120,6 +120,21 @@ productbuild --component "$APP_PATH" /Applications --sign "$INSTALLER_KEY" "$RES
 문서를 참고하여 기본적인 개념을 이해해야 합니다. 그리고 자격(plist) 파일에
 어플리케이션에서 요구하는 권한의 키를 추가합니다.
 
+그 외에 [electron-osx-sign][electron-osx-sign] 모듈을 이용해서 직접 서명할 수도 있습니다.
+
+#### 네이티브 모듈 서명하기
+
+앱 내부에서 사용한 네이티브 모듈들도 서명이 필요합니다.
+electron-osx-sign 을 사용한다면, 앱 실행 인수 목록에 경로를 반드시 지정해야 합니다.
+
+```bash
+electron-osx-sign YourApp.app YourApp.app/Contents/Resources/app/node_modules/nativemodule/build/release/nativemodule
+```
+
+참고할 점은 네이티브 모듈이 의도하지 않았지만 오브젝트 파일(.o)을 포함하는 경우도 있습니다.
+이 경우 오브젝트 파일들의 서명을 해야할 수도 있습니다.
+[electron-packager][electron-packager]를 사용한다면, 빌드 과정에 `--ignore=.+\.o$` 코드를 추가해 해당 파일을 무시해줍시다.
+
 ### 어플리케이션 업로드
 
 어플리케이션 서명을 완료한 후 iTunes Connect에 업로드하기 위해 Application Loader를
@@ -190,6 +205,8 @@ ERN의 승인을 얻는 방법은, 다음 글을 참고하는 것이 좋습니
 [nwjs-guide]: https://github.com/nwjs/nw.js/wiki/Mac-App-Store-%28MAS%29-Submission-Guideline#first-steps
 [enable-app-sandbox]: https://developer.apple.com/library/ios/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/EnablingAppSandbox.html
 [create-record]: https://developer.apple.com/library/ios/documentation/LanguagesUtilities/Conceptual/iTunesConnect_Guide/Chapters/CreatingiTunesConnectRecord.html
+[electron-osx-sign]: https://github.com/electron-userland/electron-osx-sign
+[electron-packager]: https://github.com/electron-userland/electron-packager
 [submit-for-review]: https://developer.apple.com/library/ios/documentation/LanguagesUtilities/Conceptual/iTunesConnect_Guide/Chapters/SubmittingTheApp.html
 [app-sandboxing]: https://developer.apple.com/app-sandboxing/
 [ern-tutorial]: https://carouselapps.com/2015/12/15/legally-submit-app-apples-app-store-uses-encryption-obtain-ern/

+ 12 - 12
docs-translations/ru-RU/README.md

@@ -1,10 +1,10 @@
-Пожалуйста, убедитесь, что вы используете документацию, которые соответствует вашей версии Electron.
-Номер версии должен быть частью адреса страницы. Если это не так, вы
-возможно, используете документацию ветки разработки, которая может содержать изменения api, 
+Пожалуйста, убедитесь, что Вы используете документацию, которая соответствует вашей версии Electron.
+Номер версии должен быть частью адреса страницы. Если это не так, Вы
+возможно, используете документацию ветки разработки, которая может содержать изменения api,
 которые не совместимы с вашей версией Electron. Если это так,
 Вы можете переключиться на другую версию документации в списке
-[доступные версии](http://electron.atom.io/docs/) на atom.io, или 
-если вы используете интерфейс GitHub, откройте список "переключение ветки/тега" и
+[доступные версии](http://electron.atom.io/docs/) на [atom.io](atom.io), или
+если Вы используете интерфейс GitHub, откройте список "переключение ветки/тега" и
 выберите тег, который соответствует вашей версии.
 
 ## Руководства
@@ -13,7 +13,7 @@
 * [Application Distribution](tutorial/application-distribution.md)
 * [Mac App Store Submission Guide](tutorial/mac-app-store-submission-guide.md)
 * [Application Packaging](tutorial/application-packaging.md)
-* [Using Native Node Modules](tutorial/using-native-node-modules.md)
+* [Использование нативных модулей NodeJS](tutorial/using-native-node-modules.md)
 * [Отладка главного процесса](tutorial/debugging-main-process.md)
 * [Использование Selenium и WebDriver](tutorial/using-selenium-and-webdriver.md)
 * [DevTools Extension](tutorial/devtools-extension.md)
@@ -22,8 +22,8 @@
 ## Учебники
 
 * [Быстрый старт](tutorial/quick-start.md)
-* [Desktop Environment Integration](tutorial/desktop-environment-integration.md)
-* [Online/Offline Event Detection](tutorial/online-offline-events.md)
+* [Интеграция рабочего окружения](tutorial/desktop-environment-integration.md)
+* [Определение Онлайн/Оффлайн состояния](tutorial/online-offline-events.md)
 
 ## API References
 
@@ -37,7 +37,7 @@
 * [`<webview>` Tag](api/web-view-tag.md)
 * [`window.open` Function](api/window-open.md)
 
-### Modules for the Main Process:
+### Модули для Main Process:
 
 * [app](api/app.md)
 * [autoUpdater](api/auto-updater.md)
@@ -61,7 +61,7 @@
 * [remote](api/remote.md)
 * [webFrame](api/web-frame.md)
 
-### Modules for Both Processes:
+### Модули для обоих процессов:
 
 * [clipboard](api/clipboard.md)
 * [crashReporter](api/crash-reporter.md)
@@ -72,11 +72,11 @@
 ## Разработка
 
 * [Стиль кодирования](development/coding-style.md)
-* [Source Code Directory Structure](development/source-code-directory-structure.md)
+* [Структура папок с исходным кодом](development/source-code-directory-structure.md)
 * [Technical Differences to NW.js (formerly node-webkit)](development/atom-shell-vs-node-webkit.md)
 * [Обзор системы сборки](development/build-system-overview.md)
 * [Инструкции по сборке (OS X)](development/build-instructions-osx.md)
 * [Инструкции по сборке (Windows)](development/build-instructions-windows.md)
 * [Инструкции по сборке (Linux)](development/build-instructions-linux.md)
 * [Настройка сервера символов для отладчика](development/setting-up-symbol-server.md)
- 
+

+ 30 - 30
docs-translations/ru-RU/tutorial/quick-start.md

@@ -1,11 +1,11 @@
 # Быстрый старт
 
-Electron позволяет вам делать приложения для рабочего стола на чистом JavaScript,
-предоставляя среду с богатым API. Можете представлять его как Node.js, который
-ориентирован на рабочий стол, а не веб сервера.
+Electron позволяет Вам делать приложения для рабочего стола на чистом JavaScript,
+предоставляя среду с богатым API. Можете представлять его как Node.js приложение, которое
+ориентировано для рабочего стола, а не для веб сервера.
 
-Это, однако, не значит, что Electron — лишь привязки к GUI билиотекам. На деле
-Electron использует веб-страницы как интерфейс, так что вы можете считать его
+Однако это не значит, что Electron — лишь привязка к GUI билиотекам. На деле
+Electron использует веб-страницы как интерфейс, так что Вы можете считать его
 небольшим Chroumium браузером, который контролируется с помощью JavaScript.
 
 ### Главный процесс
@@ -18,11 +18,11 @@ __главным процессом__. Скрипт, который работа
 
 Так как Electron использует Chromium для показа веб-страниц,
 мульти-процессовая архитектура показа страниц Chromium тоже используется.
-Каждая веб-страницы в Electron работает в своём собственном процессе,
+Каждая веб-страница в Electron работает в своём собственном процессе,
 который называется  __процесс-рендерер__.
 
 В обычных браузерах веб-страницы обычно запускаются в "песочнице" и им недоступны
-реальные ресурсы компьютера. Пользователи Electron же могут использовать API
+реальные ресурсы компьютера. Пользователи Electron напротив могут использовать API
 Node.js на страницах, что допускает более низкоуровневую работу с операционной системой.
 
 ### Разница мужду главным процессом и процессом-рендерером
@@ -43,11 +43,11 @@ Node.js на страницах, что допускает более низко
 В Electron есть несолько способов общения между процессам. Например, модули
 [`ipcRenderer`](../api/ipc-renderer.md) и [`ipcMain`](../api/ipc-main.md) используются
 для отправки сообщений, а [remote](../api/remote.md) - для коммуникации в RPC стиле.
ЧАВО также есть пункт о том, [как разделять информацию между страницами][share-data]
FAQ также есть пункт о том, [как разделять информацию между страницами][share-data]
 
 ## Первое приложение на Electron
 
-Как правило, приложение Electron структурировано следующим образом::
+Как правило, приложение Electron структурировано следующим образом:
 
 ```text
 your-app/
@@ -56,9 +56,9 @@ your-app/
 └── index.html
 ```
 
-Формат `package.json` точно такой же, как у модулей Node и сприпт, объявленый
+Формат `package.json` точно такой же, как у модулей Node и скрипт, объявленый
 как `main`, будет выполняться при запуске вашего приложения, работая в
-главном процессе. Например, ваш `package.json` может выглядеть вот так:
+главном процессе. Например, Ваш `package.json` может выглядеть вот так:
 
 ```json
 {
@@ -82,12 +82,12 @@ const app = electron.app
 // Модуль, создающий окно приложения.
 const BrowserWindow = electron.BrowserWindow
 
-// Удерживайте глобальное обращение к объекту окна, если вы так не сделаете, то
+// Удерживайте глобальное обращение к объекту окна, если Вы так не сделаете, то
 // окно само закроется после того, как объект будет собран сборщиком мусора.
 let mainWindow
 
 function createWindow () {
-  // Создаём окно браузера.
+  // Создаём окно браузера
   mainWindow = new BrowserWindow({width: 800, height: 600})
 
   // и загружаем index.html приложения.
@@ -98,9 +98,9 @@ function createWindow () {
 
   // Будет выполнено, когда пользователь закроет окно
   mainWindow.on('closed', function () {
-    //Убрать обращение на объект окна, обычно стоит хранить окна в массиве,
-    //если ваше приложение поддерживает несколько, сейчас стоит удалить
-    //соответствующий элемент.
+    // Убрать обращение на объект окна, обычно стоит хранить окна в массиве,
+    // если ваше приложение поддерживает несколько, сейчас стоит удалить
+    // соответствующий элемент.
     mainWindow = null
   })
 }
@@ -114,7 +114,7 @@ app.on('ready', createWindow)
 // Выйти, если все окна закрыты
 app.on('window-all-closed', function () {
   //На OS X приложение и его строка меню обычно остаются активными,
-  //пока пользователь не завершит их с помощью Cmd + Q.
+  //пока пользователь не завершит их с помощью `Cmd + Q`.
   if (process.platform !== 'darwin') {
     app.quit()
   }
@@ -129,12 +129,12 @@ app.on('activate', function () {
   }
 })
 
-//В этот файл вы можете включить остальной код вашего главного процесса.
+//В этот файл Вы можете включить остальной код вашего главного процесса.
 //Вы также можете разложить его по отдельным файлам и подключить с помощью require.
 
 ```
 
-Наконец, `index.html`, страница, которую вы хотите показать:
+Наконец, `index.html`, страница, которую Вы хотите показать:
 
 ```html
 <!DOCTYPE html>
@@ -154,22 +154,22 @@ app.on('activate', function () {
 
 ## Запуск вашего приложения
 
-Когда вы создали `main.js`, `index.html` и `package.json` вас скорее всего захочется
-запустить ваше приложение, чтобы проверить, что оно работает так, как надо.
+После того как Вы создали `main.js`, `index.html` и `package.json` Вам скорее всего захочется
+запустить приложение, чтобы проверить, что оно работает так, как надо.
 
 ### electron-prebuilt
 
 [`electron-prebuilt`](https://github.com/electron-userland/electron-prebuilt) — `npm` модуль,
 который содержит прекомпилированную версию Electron.
 
-Если вы установили Electron глобально через `npm`, то вам нужно будет всего лишь
+Если вы установили Electron глобально через `npm`, то Вам нужно будет всего лишь
 запустить сдедующее в папке вашего проекта:
 
 ```bash
 electron .
 ```
 
-Если вы установили Electron локально, то выполните это:
+Если Вы установили Electron локально, то выполните это:
 
 ```bash
 ./node_modules/.bin/electron .
@@ -177,7 +177,7 @@ electron .
 
 ### Исполняемые файлы Electron, скачанные вручную
 
-Если вы скачали Electron вручную, то вы можете использовать
+Если Вы скачали Electron вручную, то Вы можете использовать
 исполняемые файлы прямо в папке вашего проекта.
 
 #### Windows
@@ -198,27 +198,27 @@ $ ./electron/electron your-app/
 $ ./Electron.app/Contents/MacOS/Electron your-app/
 ```
 
-`Electron.app` — часть реализного пакета Electron, вы можете скачать его
+`Electron.app` — часть реализного пакета Electron, Вы можете скачать его
 [тут](https://github.com/electron/electron/releases).
 
 ### Запустить как дистрибутив
 
-Когда вы закончили написание вашего приложения, вы можете создать
+Когда Вы закончили написание вашего приложения, Вы можете создать
 дистрибутив, следуя инструкциям [отсюда](./application-distribution.md) и
 затем запустить полученное приложение.
 
 ### Попробуйте этот пример
 
-Скопируйте и запустите этот обучающий код, ичпользуя репозиторий [`atom/electron-quick-start`](https://github.com/electron/electron-quick-start)
+Скопируйте и запустите этот обучающий код, используя репозиторий [`atom/electron-quick-start`](https://github.com/electron/electron-quick-start)
 
 **Заметка**: Для запуска требуется [Git](https://git-scm.com) и [Node.js](https://nodejs.org/en/download/) (который включает в себя [npm](https://npmjs.org)).
 
 ```bash
-# Склонируйте репозиторий
+# Клонируем репозиторий
 $ git clone https://github.com/electron/electron-quick-start
-# Перейдите в папку репозитория
+# Переходим в папку скачанного репозитория
 $ cd electron-quick-start
-# Установите зависимости и запустите
+# Устанавливаем зависимости и запускаем
 $ npm install && npm start
 ```
 

+ 20 - 11
docs-translations/ru-RU/tutorial/supported-platforms.md

@@ -1,15 +1,24 @@
-Платформы поддерживаемые Electron:
+# Платформы поддерживаемые Electron:
 
-#OS X
-Поддерживает только 64-ых битные OS X. Минимально поддерживаемой версией является OS X 10.9
+Следующие платформы поддерживаются Electron:
 
-#Windows
-Поддерживаются операционные систем Windows 7 и выше, старые операционные системы не поддерживаются (и не работают).
-Поддерживаются платформы на x86 и amd64 (64-разрядная) для Windows. Будьте внимательны, что ARM Windows не поддерживается на данный момент.
+### OS X
+
+Поддерживает только 64-x битные OS X. Минимально поддерживаемой версией является OS X 10.9
+
+### Windows
+
+Поддерживаются операционные системы Windows 7 и выше, старые операционные системы не поддерживаются (и не работают).
+
+Поддерживаются бинарники `x86` и `amd64` (x64) для Windows. Будьте внимательны, `ARM` версия Windows не поддерживается на данный момент.
+
+### Linux
+
+Подготовленные бинарники Electron для `ia32`(`i686`) и `x64`(`amd64`) архитектур сделаны на Ubuntu 12.04,
+`arm` бинарник сделан на ARM v7 с hard-float ABI и NEON для Debian Wheezy.
 
-#Linux
-Поддерживает архитектуры ia32(i686 в) и x64(amd64).
 Гарантированно будет работать в дистрибутивах:
-- Ubuntu 12.04 и выше
-- Fedora 21
-- Debian 8
+
+* Ubuntu 12.04 и выше
+* Fedora 21
+* Debian 8

+ 1 - 1
docs-translations/zh-CN/api/app.md

@@ -32,7 +32,7 @@ app.on('window-all-closed', function() {
 
 当所有的窗口都被关闭时触发。
 
-这个时间仅在应用还没有退出时才能触发。 如果用户按下了 `Cmd + Q`,
+这个事件仅在应用还没有退出时才能触发。 如果用户按下了 `Cmd + Q`,
 或者开发者调用了 `app.quit()` ,Electron 将会先尝试关闭所有的窗口再触发 `will-quit` 事件,
 在这种情况下 `window-all-closed` 不会被触发。
 

+ 9 - 9
docs-translations/zh-CN/tutorial/quick-start.md

@@ -49,24 +49,24 @@ const {BrowserWindow} = electron;
 
 // 保持一个对于 window 对象的全局引用,如果你不这样做,
 // 当 JavaScript 对象被垃圾回收, window 会被自动地关闭
-let win;
+let mainWindow;
 
 function createWindow() {
   // 创建浏览器窗口。
-  win = new BrowserWindow({width: 800, height: 600});
+  mainWindow = new BrowserWindow({width: 800, height: 600});
 
   // 加载应用的 index.html。
-  win.loadURL(`file://${__dirname}/index.html`);
+  mainWindow.loadURL(`file://${__dirname}/index.html`);
 
   // 启用开发工具。
-  win.webContents.openDevTools();
+  mainWindow.webContents.openDevTools();
 
   // 当 window 被关闭,这个事件会被触发。
-  win.on('closed', () => {
+  mainWindow.on('closed', () => {
     // 取消引用 window 对象,如果你的应用支持多窗口的话,
     // 通常会把多个 window 对象存放在一个数组里面,
     // 与此同时,你应该删除相应的元素。
-    win = null;
+    mainWindow = null;
   });
 }
 
@@ -87,7 +87,7 @@ app.on('window-all-closed', () => {
 app.on('activate', () => {
   // On OS X it's common to re-create a window in the app when the
   // dock icon is clicked and there are no other windows open.
-  if (win === null) {
+  if (mainWindow === null) {
     createWindow();
   }
 });
@@ -146,7 +146,7 @@ $ ./Electron.app/Contents/MacOS/Electron your-app/
 在你完成了你的应用后,你可以按照 [应用部署][7] 指导发布一个版本,并且以已经打包好的形式运行应用。
 
 # 参照下面例子
-复制并且运行这个库 [atom/electron-quick-start][8]。
+复制并且运行这个库 [electron/electron-quick-start][8]。
 
 *注意:*运行时需要你的系统已经安装了 [Git][9] 和 [Node.js][10](包含 [npm][11])。
 
@@ -168,4 +168,4 @@ $ npm install && npm start
   [8]: https://github.com/electron/electron-quick-start
   [9]: https://git-scm.com/
   [10]: https://nodejs.org/en/download/
-  [11]: https://www.npmjs.com/
+  [11]: https://www.npmjs.com/

+ 1 - 0
docs/api/app.md

@@ -346,6 +346,7 @@ You can request the following paths by the name:
 * `music` Directory for a user's music.
 * `pictures` Directory for a user's pictures.
 * `videos` Directory for a user's videos.
+* `pepperFlashSystemPlugin`  Full path to the system version of the Pepper Flash plugin.
 
 ### `app.setPath(name, path)`
 

+ 59 - 19
docs/api/download-item.md

@@ -2,49 +2,70 @@
 
 > Control file downloads from remote sources.
 
-`DownloadItem` is an EventEmitter that represents a download item in Electron.
-It is used in `will-download` event of `Session` module, and allows users to
+`DownloadItem` is an `EventEmitter` that represents a download item in Electron.
+It is used in `will-download` event of `Session` class, and allows users to
 control the download item.
 
 ```javascript
 // In the main process.
 win.webContents.session.on('will-download', (event, item, webContents) => {
   // Set the save path, making Electron not to prompt a save dialog.
-  item.setSavePath('/tmp/save.pdf');
-  console.log(item.getMimeType());
-  console.log(item.getFilename());
-  console.log(item.getTotalBytes());
-  item.on('updated', () => {
-    console.log('Received bytes: ' + item.getReceivedBytes());
-  });
-  item.on('done', (e, state) => {
+  item.setSavePath('/tmp/save.pdf')
+
+  item.on('updated', (event, state) => {
+    if (state === 'interrupted') {
+      console.log('Download is interrupted but can be resumed')
+    } else if (state === 'progressing') {
+      if (item.isPaused()) {
+        console.log('Download is paused')
+      } else {
+        console.log(`Received bytes: ${item.getReceivedBytes()}`)
+      }
+    }
+  })
+  item.once('done', (event, state) => {
     if (state === 'completed') {
-      console.log('Download successfully');
+      console.log('Download successfully')
     } else {
-      console.log('Download is cancelled or interrupted that can\'t be resumed');
+      console.log(`Download failed: ${state}`)
     }
-  });
-});
+  })
+})
 ```
 
 ## Events
 
 ### Event: 'updated'
 
-Emits when the `downloadItem` gets updated.
+Returns:
+
+* `event` Event
+* `state` String
+
+Emitted when the download has been updated and is not done.
+
+The `state` can be one of following:
+
+* `progressing` - The download is in-progress.
+* `interrupted` - The download has interrupted and can be resumed.
 
 ### Event: 'done'
 
+Returns:
+
 * `event` Event
 * `state` String
-  * `completed` - The download completed successfully.
-  * `cancelled` - The download has been cancelled.
-  * `interrupted` - An error broke the connection with the file server.
 
-Emits when the download is in a terminal state. This includes a completed
+Emitted when the download is in a terminal state. This includes a completed
 download, a cancelled download(via `downloadItem.cancel()`), and interrupted
 download that can't be resumed.
 
+The `state` can be one of following:
+
+* `completed` - The download completed successfully.
+* `cancelled` - The download has been cancelled.
+* `interrupted` - The download has interrupted and can not resume.
+
 ## Methods
 
 The `downloadItem` object has the following methods:
@@ -61,10 +82,18 @@ routine to determine the save path(Usually prompts a save dialog).
 
 Pauses the download.
 
+### `downloadItem.isPaused()`
+
+Returns whether the download is paused.
+
 ### `downloadItem.resume()`
 
 Resumes the download that has been paused.
 
+### `downloadItem.canResume()`
+
+Resumes whether the download can resume.
+
 ### `downloadItem.cancel()`
 
 Cancels the download operation.
@@ -102,3 +131,14 @@ Returns a `Integer` represents the received bytes of the download item.
 
 Returns a `String` represents the Content-Disposition field from the response
 header.
+
+### `downloadItem.getState()`
+
+Returns current state as `String`.
+
+Possible values are:
+
+* `progressing` - The download is in-progress.
+* `completed` - The download completed successfully.
+* `cancelled` - The download has been cancelled.
+* `interrupted` - The download has interrupted.

+ 19 - 0
docs/api/session.md

@@ -547,3 +547,22 @@ The `listener` will be called with `listener(details)` when an error occurs.
   * `timestamp` Double
   * `fromCache` Boolean
   * `error` String - The error description.
+
+#### `ses.protocol`
+
+Returns an instance of [protocol](protocol.md) module for this session.
+
+```javascript
+const {app, session} = require('electron')
+const path = require('path')
+
+app.on('ready', function () {
+  const protocol = session.fromPartition(partitionName).protocol
+  protocol.registerFileProtocol('atom', function (request, callback) {
+    var url = request.url.substr(7)
+    callback({path: path.normalize(__dirname + '/' + url)})
+  }, function (error) {
+    if (error)
+      console.error('Failed to register protocol')
+  })
+})

+ 1 - 1
docs/development/build-system-overview.md

@@ -8,7 +8,7 @@ be found in the `.gyp` and `.gypi` files.
 
 Following `gyp` files contain the main rules for building Electron:
 
-* `atom.gyp` defines how Electron itself is built.
+* `electron.gyp` defines how Electron itself is built.
 * `common.gypi` adjusts the build configurations of Node to make it build
   together with Chromium.
 * `vendor/brightray/brightray.gyp` defines how `brightray` is built and

+ 2 - 0
docs/tutorial/using-pepper-flash-plugin.md

@@ -41,6 +41,8 @@ app.on('ready', () => {
 });
 ```
 
+The path to the system wide Pepper Flash plugin can also be obtained by calling `app.getPath('pepperFlashSystemPlugin')`.
+
 ## Enable Flash Plugin in a `<webview>` Tag
 
 Add `plugins` attribute to `<webview>` tag.

+ 2 - 1
filenames.gypi

@@ -63,7 +63,8 @@
       'lib/renderer/api/remote.js',
       'lib/renderer/api/screen.js',
       'lib/renderer/api/web-frame.js',
-      'lib/renderer/extensions/storage.js'
+      'lib/renderer/extensions/i18n.js',
+      'lib/renderer/extensions/storage.js',
     ],
     'js2c_sources': [
       'lib/common/asar.js',

+ 3 - 3
lib/browser/api/protocol.js

@@ -1,5 +1,5 @@
-const {app} = require('electron')
-const {createProtocolObject, registerStandardSchemes} = process.atomBinding('protocol')
+const {app, session} = require('electron')
+const {registerStandardSchemes} = process.atomBinding('protocol')
 
 exports.registerStandardSchemes = function (schemes) {
   if (app.isReady()) {
@@ -10,7 +10,7 @@ exports.registerStandardSchemes = function (schemes) {
 }
 
 app.once('ready', function () {
-  let protocol = createProtocolObject()
+  let protocol = session.defaultSession.protocol
   for (let method in protocol) {
     exports[method] = protocol[method].bind(protocol)
   }

+ 6 - 2
lib/browser/api/session.js

@@ -3,6 +3,7 @@ const electron = require('electron')
 const bindings = process.atomBinding('session')
 
 const PERSIST_PREFIX = 'persist:'
+const Session = new EventEmitter()
 
 // Wrapper of binding.fromPartition that checks for ready event.
 const fromPartition = function (partition, persist) {
@@ -14,7 +15,7 @@ const fromPartition = function (partition, persist) {
 }
 
 // Returns the Session from |partition| string.
-exports.fromPartition = function (partition = '') {
+Session.fromPartition = function (partition = '') {
   if (partition === '') return exports.defaultSession
 
   if (partition.startsWith(PERSIST_PREFIX)) {
@@ -25,7 +26,7 @@ exports.fromPartition = function (partition = '') {
 }
 
 // Returns the default session.
-Object.defineProperty(exports, 'defaultSession', {
+Object.defineProperty(Session, 'defaultSession', {
   enumerable: true,
   get: function () {
     return fromPartition('', false)
@@ -35,6 +36,9 @@ Object.defineProperty(exports, 'defaultSession', {
 const wrapSession = function (session) {
   // Session is an EventEmitter.
   Object.setPrototypeOf(session, EventEmitter.prototype)
+  Session.emit('session-created', session)
 }
 
 bindings._setWrapSession(wrapSession)
+
+module.exports = Session

+ 11 - 9
lib/browser/api/web-contents.js

@@ -10,6 +10,14 @@ session
 const binding = process.atomBinding('web_contents')
 const debuggerBinding = process.atomBinding('debugger')
 
+const WebContents = new EventEmitter()
+WebContents.create = (options = {}) => {
+  return binding.create(options)
+}
+WebContents.fromId = (id) => {
+  return binding.fromId(id)
+}
+
 let nextId = 0
 const getNextId = function () {
   return ++nextId
@@ -223,6 +231,8 @@ const wrapWebContents = function (webContents) {
 
     this._printToPDF(printingSetting, callback)
   }
+
+  WebContents.emit('web-contents-created', webContents)
 }
 
 binding._setWrapWebContents(wrapWebContents)
@@ -235,12 +245,4 @@ const wrapDebugger = function (webContentsDebugger) {
 
 debuggerBinding._setWrapDebugger(wrapDebugger)
 
-module.exports = {
-  create (options = {}) {
-    return binding.create(options)
-  },
-
-  fromId (id) {
-    return binding.fromId(id)
-  }
-}
+module.exports = WebContents

+ 29 - 20
lib/browser/chrome-extension.js

@@ -1,4 +1,5 @@
-const {app, ipcMain, protocol, webContents, BrowserWindow} = require('electron')
+const {app, ipcMain, session, webContents, BrowserWindow} = require('electron')
+const {getAllWebContents} = process.atomBinding('web_contents')
 const renderProcessPreferences = process.atomBinding('render_process_preferences').forAllBrowserWindow()
 
 const fs = require('fs')
@@ -81,13 +82,13 @@ const removeBackgroundPages = function (manifest) {
 }
 
 // Dispatch tabs events.
-const hookWindowForTabEvents = function (win) {
-  const tabId = win.webContents.id
+const hookWebContentsForTabEvents = function (webContents) {
+  const tabId = webContents.id
   for (const page of objectValues(backgroundPages)) {
     page.webContents.sendToAll('CHROME_TABS_ONCREATED', tabId)
   }
 
-  win.once('closed', () => {
+  webContents.once('destroyed', () => {
     for (const page of objectValues(backgroundPages)) {
       page.webContents.sendToAll('CHROME_TABS_ONREMOVED', tabId)
     }
@@ -114,6 +115,10 @@ ipcMain.on('CHROME_RUNTIME_CONNECT', function (event, extensionId, connectInfo)
   page.webContents.sendToAll(`CHROME_RUNTIME_ONCONNECT_${extensionId}`, event.sender.id, portId, connectInfo)
 })
 
+ipcMain.on('CHROME_I18N_MANIFEST', function (event, extensionId) {
+  event.returnValue = manifestMap[extensionId]
+})
+
 ipcMain.on('CHROME_RUNTIME_SENDMESSAGE', function (event, extensionId, message) {
   const page = backgroundPages[extensionId]
   if (!page) {
@@ -221,6 +226,15 @@ const loadDevToolsExtensions = function (win, manifests) {
   win.devToolsWebContents.executeJavaScript(`DevToolsAPI.addExtensions(${JSON.stringify(extensionInfoArray)})`)
 }
 
+webContents.on('web-contents-created', function (webContents) {
+  if (webContents.getType() === 'remote') return
+
+  hookWebContentsForTabEvents(webContents)
+  webContents.on('devtools-opened', function () {
+    loadDevToolsExtensions(webContents, objectValues(manifestMap))
+  })
+})
+
 // The persistent path of "DevTools Extensions" preference file.
 let loadedExtensionsPath = null
 
@@ -270,10 +284,12 @@ app.once('ready', function () {
       }
     })
   }
-  protocol.registerBufferProtocol('chrome-extension', chromeExtensionHandler, function (error) {
-    if (error) {
-      console.error(`Unable to register chrome-extension protocol: ${error}`)
-    }
+  session.on('session-created', function (ses) {
+    ses.protocol.registerBufferProtocol('chrome-extension', chromeExtensionHandler, function (error) {
+      if (error) {
+        console.error(`Unable to register chrome-extension protocol: ${error}`)
+      }
+    })
   })
 
   // Load persisted extensions.
@@ -295,12 +311,15 @@ app.once('ready', function () {
   BrowserWindow.addDevToolsExtension = function (srcDirectory) {
     const manifest = getManifestFromPath(srcDirectory)
     if (manifest) {
-      for (const win of BrowserWindow.getAllWindows()) {
-        loadDevToolsExtensions(win, [manifest])
+      for (const webContents of getAllWebContents()) {
+        if (webContents.getType() !== 'remote') {
+          loadDevToolsExtensions(webContents, [manifest])
+        }
       }
       return manifest.name
     }
   }
+
   BrowserWindow.removeDevToolsExtension = function (name) {
     const manifest = manifestNameMap[name]
     if (!manifest) return
@@ -310,14 +329,4 @@ app.once('ready', function () {
     delete manifestMap[manifest.extensionId]
     delete manifestNameMap[name]
   }
-
-  // Load extensions automatically when devtools is opened.
-  const init = BrowserWindow.prototype._init
-  BrowserWindow.prototype._init = function () {
-    init.call(this)
-    hookWindowForTabEvents(this)
-    this.webContents.on('devtools-opened', () => {
-      loadDevToolsExtensions(this, objectValues(manifestMap))
-    })
-  }
 })

+ 2 - 0
lib/renderer/chrome-api.js

@@ -194,4 +194,6 @@ exports.injectTo = function (extensionId, isBackgroundPage, context) {
     setPopup () {},
     getPopup () {}
   }
+
+  chrome.i18n = require('./extensions/i18n.js').setup(extensionId)
 }

+ 84 - 0
lib/renderer/extensions/i18n.js

@@ -0,0 +1,84 @@
+// Implementation of chrome.i18n.getMessage
+// https://developer.chrome.com/extensions/i18n#method-getMessage
+//
+// Does not implement predefined messages:
+// https://developer.chrome.com/extensions/i18n#overview-predefined
+
+const {ipcRenderer} = require('electron')
+const fs = require('fs')
+const path = require('path')
+
+let metadata
+
+const getExtensionMetadata = (extensionId) => {
+  if (!metadata) {
+    metadata = ipcRenderer.sendSync('CHROME_I18N_MANIFEST', extensionId)
+  }
+  return metadata
+}
+
+const getMessagesPath = (extensionId, language) => {
+  const metadata = getExtensionMetadata(extensionId)
+  const defaultLocale = metadata.default_locale || 'en'
+  const localesDirectory = path.join(metadata.srcDirectory, '_locales')
+  let messagesPath = path.join(localesDirectory, language, 'messages.json')
+  if (!fs.statSyncNoException(messagesPath)) {
+    messagesPath = path.join(localesDirectory, defaultLocale, 'messages.json')
+  }
+  return messagesPath
+}
+
+const getMessages = (extensionId, language) => {
+  try {
+    const messagesPath = getMessagesPath(extensionId, language)
+    return JSON.parse(fs.readFileSync(messagesPath)) || {}
+  } catch (error) {
+    return {}
+  }
+}
+
+const getLanguage = () => {
+  return navigator.language.replace(/-.*$/, '').toLowerCase()
+}
+
+const replaceNumberedSubstitutions = (message, substitutions) => {
+  return message.replace(/\$(\d+)/, (_, number) => {
+    const index = parseInt(number, 10) - 1
+    return substitutions[index] || ''
+  })
+}
+
+const replacePlaceholders = (message, placeholders, substitutions) => {
+  if (typeof substitutions === 'string') {
+    substitutions = [substitutions]
+  }
+  if (!Array.isArray(substitutions)) {
+    substitutions = []
+  }
+
+  if (placeholders) {
+    Object.keys(placeholders).forEach((name) => {
+      let {content} = placeholders[name]
+      content = replaceNumberedSubstitutions(content, substitutions)
+      message = message.replace(new RegExp(`\\$${name}\\$`, 'gi'), content)
+    })
+  }
+
+  return replaceNumberedSubstitutions(message, substitutions)
+}
+
+const getMessage = (extensionId, messageName, substitutions) => {
+  const messages = getMessages(extensionId, getLanguage())
+  if (messages.hasOwnProperty(messageName)) {
+    const {message, placeholders} = messages[messageName]
+    return replacePlaceholders(message, placeholders, substitutions)
+  }
+}
+
+exports.setup = (extensionId) => {
+  return {
+    getMessage (messageName, substitutions) {
+      return getMessage(extensionId, messageName, substitutions)
+    }
+  }
+}

+ 43 - 1
spec/api-browser-window-spec.js

@@ -859,11 +859,13 @@ describe('browser-window module', function () {
       })
 
       describe('when the devtools is docked', function () {
-        it('creates the extension', function (done) {
+        it.only('creates the extension', function (done) {
           w.webContents.openDevTools({mode: 'bottom'})
 
           ipcMain.once('answer', function (event, message) {
             assert.equal(message.runtimeId, 'foo')
+            assert.equal(message.tabId, w.webContents.id)
+            assert.equal(message.i18nString, 'foo - bar (baz)')
             assert.deepEqual(message.storageItems, {foo: 'bar'})
             done()
           })
@@ -876,12 +878,52 @@ describe('browser-window module', function () {
 
           ipcMain.once('answer', function (event, message, extensionId) {
             assert.equal(message.runtimeId, 'foo')
+            assert.equal(message.tabId, w.webContents.id)
             done()
           })
         })
       })
     })
 
+    it('works when used with partitions', function (done) {
+      this.timeout(10000)
+
+      if (w != null) {
+        w.destroy()
+      }
+      w = new BrowserWindow({
+        show: false,
+        webPreferences: {
+          partition: 'temp'
+        }
+      })
+
+      var extensionPath = path.join(__dirname, 'fixtures', 'devtools-extensions', 'foo')
+      BrowserWindow.removeDevToolsExtension('foo')
+      BrowserWindow.addDevToolsExtension(extensionPath)
+
+      w.webContents.on('devtools-opened', function () {
+        var showPanelIntevalId = setInterval(function () {
+          if (w && w.devToolsWebContents) {
+            w.devToolsWebContents.executeJavaScript('(' + (function () {
+              var lastPanelId = WebInspector.inspectorView._tabbedPane._tabs.peekLast().id
+              WebInspector.inspectorView.showPanel(lastPanelId)
+            }).toString() + ')()')
+          } else {
+            clearInterval(showPanelIntevalId)
+          }
+        }, 100)
+      })
+
+      w.loadURL('about:blank')
+      w.webContents.openDevTools({mode: 'bottom'})
+
+      ipcMain.once('answer', function (event, message) {
+        assert.equal(message.runtimeId, 'foo')
+        done()
+      })
+    })
+
     it('serializes the registered extensions on quit', function () {
       var extensionName = 'foo'
       var extensionPath = path.join(__dirname, 'fixtures', 'devtools-extensions', extensionName)

+ 50 - 1
spec/api-session-spec.js

@@ -16,8 +16,15 @@ describe('session module', function () {
   var fixtures = path.resolve(__dirname, 'fixtures')
   var w = null
   var url = 'http://127.0.0.1'
+  var partitionName = 'temp'
+  var protocolName = 'sp'
+  const tempProtocol = session.fromPartition(partitionName).protocol
+  const protocol = session.defaultSession.protocol
 
   beforeEach(function () {
+    if (w != null) {
+      w.destroy()
+    }
     w = new BrowserWindow({
       show: false,
       width: 400,
@@ -26,7 +33,10 @@ describe('session module', function () {
   })
 
   afterEach(function () {
-    w.destroy()
+    if (w != null) {
+      w.destroy()
+    }
+    w = null
   })
 
   describe('session.cookies', function () {
@@ -262,4 +272,43 @@ describe('session module', function () {
       })
     })
   })
+
+  describe('session.protocol', function () {
+    beforeEach(function () {
+      if (w != null) {
+        w.destroy()
+      }
+      w = new BrowserWindow({
+        show: false,
+        width: 400,
+        height: 400,
+        webPreferences: {
+          partition: partitionName
+        }
+      })
+    })
+
+    it('handles requests from a partition', function (done) {
+      var handler = function (error, callback) {
+        callback({
+          data: 'test'
+        })
+      }
+      tempProtocol.registerStringProtocol(protocolName, handler, function (error) {
+        if (error) {
+          return done(error)
+        }
+        protocol.isProtocolHandled(protocolName, function (result) {
+          assert.equal(result, false)
+          tempProtocol.isProtocolHandled(protocolName, function (result) {
+            assert.equal(result, true)
+            w.webContents.on('did-finish-load', function () {
+              done()
+            })
+            w.loadURL(protocolName + "://fake-host")
+          })
+        })
+      })
+    })
+  })
 })

+ 10 - 0
spec/fixtures/devtools-extensions/foo/_locales/en/messages.json

@@ -0,0 +1,10 @@
+{
+  "foo": {
+    "message": "foo - $BAZ$ ($2)",
+    "placeholders": {
+      "baz": {
+        "content": "$1"
+      }
+    }
+  }
+}

+ 2 - 0
spec/fixtures/devtools-extensions/foo/index.html

@@ -13,6 +13,8 @@
       testStorage(function (items) {
         var message = JSON.stringify({
           runtimeId: chrome.runtime.id,
+          tabId: chrome.devtools.inspectedWindow.tabId,
+          i18nString: chrome.i18n.getMessage('foo', ['bar', 'baz']),
           storageItems: items
         })
         var sendMessage = `require('electron').ipcRenderer.send('answer', ${message})`

+ 2 - 1
spec/fixtures/devtools-extensions/foo/manifest.json

@@ -1,5 +1,6 @@
 {
   "name": "foo",
   "version": "1.0",
-  "devtools_page": "foo.html"
+  "devtools_page": "foo.html",
+  "default_locale": "en"
 }

+ 30 - 0
spec/fixtures/pages/webview-devtools.html

@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+  </head>
+  <body>
+    <webview nodeintegration src="./a.html"></webview>
+    <script>
+        var wv = document.querySelector('webview')
+        wv.addEventListener('dom-ready', () => {
+          const webContents = wv.getWebContents()
+          webContents.on('devtools-opened', function () {
+            var showPanelIntevalId = setInterval(function () {
+              if (webContents.devToolsWebContents) {
+                webContents.devToolsWebContents.executeJavaScript('(' + (function () {
+                  var lastPanelId = WebInspector.inspectorView._tabbedPane._tabs.peekLast().id
+                  WebInspector.inspectorView.showPanel(lastPanelId)
+                }).toString() + ')()')
+              } else {
+                clearInterval(showPanelIntevalId)
+              }
+            }, 100)
+          })
+
+          wv.openDevTools()
+        })
+      </script>
+    </script>
+  </body>
+</html>

+ 21 - 0
spec/webview-spec.js

@@ -912,4 +912,25 @@ describe('<webview> tag', function () {
 
     w.loadURL('file://' + fixtures + '/pages/webview-visibilitychange.html')
   })
+
+  it('loads devtools extensions registered on the parent window', function (done) {
+    this.timeout(10000)
+
+    w = new BrowserWindow({
+      show: false
+    })
+
+    BrowserWindow.removeDevToolsExtension('foo')
+
+    var extensionPath = path.join(__dirname, 'fixtures', 'devtools-extensions', 'foo')
+    BrowserWindow.addDevToolsExtension(extensionPath)
+
+    w.loadURL('file://' + fixtures + '/pages/webview-devtools.html')
+
+    ipcMain.once('answer', function (event, message) {
+      assert.equal(message.runtimeId, 'foo')
+      assert.notEqual(message.tabId, w.webContents.id)
+      done()
+    })
+  })
 })