Merge pull request #10 from electron/master

update as upstream
This commit is contained in:
Heilig Benedek 2016-06-11 13:43:25 +02:00 committed by GitHub
commit 70aa706524
108 changed files with 1485 additions and 594 deletions

View file

@ -1 +1 @@
v5.10.0 v6.1.0

View file

@ -110,6 +110,8 @@ int GetPathConstant(const std::string& name) {
return chrome::DIR_USER_PICTURES; return chrome::DIR_USER_PICTURES;
else if (name == "videos") else if (name == "videos")
return chrome::DIR_USER_VIDEOS; return chrome::DIR_USER_VIDEOS;
else if (name == "pepperFlashSystemPlugin")
return chrome::FILE_PEPPER_FLASH_SYSTEM_PLUGIN;
else else
return -1; return -1;
} }
@ -261,13 +263,14 @@ void App::OnContinueUserActivity(
} }
#endif #endif
void App::OnLogin(LoginHandler* login_handler) { void App::OnLogin(LoginHandler* login_handler,
const base::DictionaryValue& request_details) {
v8::Locker locker(isolate()); v8::Locker locker(isolate());
v8::HandleScope handle_scope(isolate()); v8::HandleScope handle_scope(isolate());
bool prevent_default = Emit( bool prevent_default = Emit(
"login", "login",
WebContents::CreateFrom(isolate(), login_handler->GetWebContents()), WebContents::CreateFrom(isolate(), login_handler->GetWebContents()),
login_handler->request(), request_details,
login_handler->auth_info(), login_handler->auth_info(),
base::Bind(&PassLoginInformation, make_scoped_refptr(login_handler))); base::Bind(&PassLoginInformation, make_scoped_refptr(login_handler)));
@ -447,6 +450,15 @@ bool App::Relaunch(mate::Arguments* js_args) {
return relauncher::RelaunchApp(argv); return relauncher::RelaunchApp(argv);
} }
void App::DisableHardwareAcceleration(mate::Arguments* args) {
if (Browser::Get()->is_ready()) {
args->ThrowError("app.disableHardwareAcceleration() can only be called "
"before app is ready");
return;
}
content::GpuDataManager::GetInstance()->DisableHardwareAcceleration();
}
#if defined(USE_NSS_CERTS) #if defined(USE_NSS_CERTS)
void App::ImportCertificate( void App::ImportCertificate(
const base::DictionaryValue& options, const base::DictionaryValue& options,
@ -528,7 +540,9 @@ void App::BuildPrototype(
#endif #endif
.SetMethod("makeSingleInstance", &App::MakeSingleInstance) .SetMethod("makeSingleInstance", &App::MakeSingleInstance)
.SetMethod("releaseSingleInstance", &App::ReleaseSingleInstance) .SetMethod("releaseSingleInstance", &App::ReleaseSingleInstance)
.SetMethod("relaunch", &App::Relaunch); .SetMethod("relaunch", &App::Relaunch)
.SetMethod("disableHardwareAcceleration",
&App::DisableHardwareAcceleration);
} }
} // namespace api } // namespace api

View file

@ -70,7 +70,8 @@ class App : public AtomBrowserClient::Delegate,
void OnActivate(bool has_visible_windows) override; void OnActivate(bool has_visible_windows) override;
void OnWillFinishLaunching() override; void OnWillFinishLaunching() override;
void OnFinishLaunching() 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) #if defined(OS_MACOSX)
void OnContinueUserActivity( void OnContinueUserActivity(
bool* prevent_default, bool* prevent_default,
@ -111,7 +112,7 @@ class App : public AtomBrowserClient::Delegate,
const ProcessSingleton::NotificationCallback& callback); const ProcessSingleton::NotificationCallback& callback);
void ReleaseSingleInstance(); void ReleaseSingleInstance();
bool Relaunch(mate::Arguments* args); bool Relaunch(mate::Arguments* args);
void DisableHardwareAcceleration(mate::Arguments* args);
#if defined(USE_NSS_CERTS) #if defined(USE_NSS_CERTS)
void ImportCertificate(const base::DictionaryValue& options, void ImportCertificate(const base::DictionaryValue& options,
const net::CompletionCallback& callback); const net::CompletionCallback& callback);

View file

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

View file

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

View file

@ -12,8 +12,12 @@
#include "atom/browser/net/url_request_fetch_job.h" #include "atom/browser/net/url_request_fetch_job.h"
#include "atom/browser/net/url_request_string_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/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/node_includes.h"
#include "atom/common/options_switches.h"
#include "base/command_line.h"
#include "base/strings/string_util.h"
#include "content/public/browser/child_process_security_policy.h"
#include "native_mate/dictionary.h" #include "native_mate/dictionary.h"
#include "url/url_util.h" #include "url/url_util.h"
@ -158,21 +162,21 @@ namespace {
void RegisterStandardSchemes( void RegisterStandardSchemes(
const std::vector<std::string>& schemes) { const std::vector<std::string>& schemes) {
for (const auto& scheme : schemes) auto policy = content::ChildProcessSecurityPolicy::GetInstance();
for (const auto& scheme : schemes) {
url::AddStandardScheme(scheme.c_str(), url::SCHEME_WITHOUT_PORT); url::AddStandardScheme(scheme.c_str(), url::SCHEME_WITHOUT_PORT);
} policy->RegisterWebSafeScheme(scheme);
}
mate::Handle<atom::api::Protocol> CreateProtocol(v8::Isolate* isolate) { auto command_line = base::CommandLine::ForCurrentProcess();
auto browser_context = static_cast<atom::AtomBrowserContext*>( command_line->AppendSwitchASCII(atom::switches::kStandardSchemes,
atom::AtomBrowserMainParts::Get()->browser_context()); base::JoinString(schemes, ","));
return atom::api::Protocol::Create(isolate, browser_context);
} }
void Initialize(v8::Local<v8::Object> exports, v8::Local<v8::Value> unused, void Initialize(v8::Local<v8::Object> exports, v8::Local<v8::Value> unused,
v8::Local<v8::Context> context, void* priv) { v8::Local<v8::Context> context, void* priv) {
v8::Isolate* isolate = context->GetIsolate(); v8::Isolate* isolate = context->GetIsolate();
mate::Dictionary dict(isolate, exports); mate::Dictionary dict(isolate, exports);
dict.SetMethod("createProtocolObject", base::Bind(&CreateProtocol, isolate));
dict.SetMethod("registerStandardSchemes", &RegisterStandardSchemes); dict.SetMethod("registerStandardSchemes", &RegisterStandardSchemes);
} }

View file

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

View file

@ -9,6 +9,7 @@
#include "atom/browser/api/atom_api_cookies.h" #include "atom/browser/api/atom_api_cookies.h"
#include "atom/browser/api/atom_api_download_item.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/api/atom_api_web_request.h"
#include "atom/browser/atom_browser_context.h" #include "atom/browser/atom_browser_context.h"
#include "atom/browser/atom_browser_main_parts.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_); 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) { v8::Local<v8::Value> Session::WebRequest(v8::Isolate* isolate) {
if (web_request_.IsEmpty()) { if (web_request_.IsEmpty()) {
auto handle = atom::api::WebRequest::Create(isolate, browser_context()); auto handle = atom::api::WebRequest::Create(isolate, browser_context());
@ -512,6 +521,7 @@ void Session::BuildPrototype(v8::Isolate* isolate,
.SetMethod("allowNTLMCredentialsForDomains", .SetMethod("allowNTLMCredentialsForDomains",
&Session::AllowNTLMCredentialsForDomains) &Session::AllowNTLMCredentialsForDomains)
.SetProperty("cookies", &Session::Cookies) .SetProperty("cookies", &Session::Cookies)
.SetProperty("protocol", &Session::Protocol)
.SetProperty("webRequest", &Session::WebRequest); .SetProperty("webRequest", &Session::WebRequest);
} }

View file

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

View file

@ -389,6 +389,11 @@ void WebContents::ActivateContents(content::WebContents* source) {
Emit("activate"); Emit("activate");
} }
void WebContents::UpdateTargetURL(content::WebContents* source,
const GURL& url) {
Emit("update-target-url", url);
}
bool WebContents::IsPopupOrPanel(const content::WebContents* source) const { bool WebContents::IsPopupOrPanel(const content::WebContents* source) const {
return type_ == BROWSER_WINDOW; return type_ == BROWSER_WINDOW;
} }
@ -739,6 +744,15 @@ int WebContents::GetID() const {
return web_contents()->GetRenderProcessHost()->GetID(); 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 { bool WebContents::Equal(const WebContents* web_contents) const {
return GetID() == web_contents->GetID(); return GetID() == web_contents->GetID();
} }
@ -1089,6 +1103,14 @@ void WebContents::StopFindInPage(content::StopFindAction action) {
web_contents()->StopFinding(action); web_contents()->StopFinding(action);
} }
void WebContents::ShowDefinitionForSelection() {
#if defined(OS_MACOSX)
const auto view = web_contents()->GetRenderWidgetHostView();
if (view)
view->ShowDefinitionForSelection();
#endif
}
void WebContents::Focus() { void WebContents::Focus() {
web_contents()->Focus(); web_contents()->Focus();
} }
@ -1274,6 +1296,7 @@ void WebContents::BuildPrototype(v8::Isolate* isolate,
.SetMethod("endFrameSubscription", &WebContents::EndFrameSubscription) .SetMethod("endFrameSubscription", &WebContents::EndFrameSubscription)
.SetMethod("setSize", &WebContents::SetSize) .SetMethod("setSize", &WebContents::SetSize)
.SetMethod("isGuest", &WebContents::IsGuest) .SetMethod("isGuest", &WebContents::IsGuest)
.SetMethod("getType", &WebContents::GetType)
.SetMethod("getWebPreferences", &WebContents::GetWebPreferences) .SetMethod("getWebPreferences", &WebContents::GetWebPreferences)
.SetMethod("getOwnerBrowserWindow", &WebContents::GetOwnerBrowserWindow) .SetMethod("getOwnerBrowserWindow", &WebContents::GetOwnerBrowserWindow)
.SetMethod("hasServiceWorker", &WebContents::HasServiceWorker) .SetMethod("hasServiceWorker", &WebContents::HasServiceWorker)
@ -1284,6 +1307,8 @@ void WebContents::BuildPrototype(v8::Isolate* isolate,
.SetMethod("_printToPDF", &WebContents::PrintToPDF) .SetMethod("_printToPDF", &WebContents::PrintToPDF)
.SetMethod("addWorkSpace", &WebContents::AddWorkSpace) .SetMethod("addWorkSpace", &WebContents::AddWorkSpace)
.SetMethod("removeWorkSpace", &WebContents::RemoveWorkSpace) .SetMethod("removeWorkSpace", &WebContents::RemoveWorkSpace)
.SetMethod("showDefinitionForSelection",
&WebContents::ShowDefinitionForSelection)
.SetProperty("id", &WebContents::ID) .SetProperty("id", &WebContents::ID)
.SetProperty("session", &WebContents::Session) .SetProperty("session", &WebContents::Session)
.SetProperty("hostWebContents", &WebContents::HostWebContents) .SetProperty("hostWebContents", &WebContents::HostWebContents)
@ -1350,6 +1375,8 @@ void Initialize(v8::Local<v8::Object> exports, v8::Local<v8::Value> unused,
dict.SetMethod("_setWrapWebContents", &atom::api::SetWrapWebContents); dict.SetMethod("_setWrapWebContents", &atom::api::SetWrapWebContents);
dict.SetMethod("fromId", dict.SetMethod("fromId",
&mate::TrackableObject<atom::api::WebContents>::FromWeakMapID); &mate::TrackableObject<atom::api::WebContents>::FromWeakMapID);
dict.SetMethod("getAllWebContents",
&mate::TrackableObject<atom::api::WebContents>::GetAll);
} }
} // namespace } // namespace

View file

@ -59,6 +59,7 @@ class WebContents : public mate::TrackableObject<WebContents>,
v8::Local<v8::ObjectTemplate> prototype); v8::Local<v8::ObjectTemplate> prototype);
int GetID() const; int GetID() const;
std::string GetType() const;
bool Equal(const WebContents* web_contents) const; bool Equal(const WebContents* web_contents) const;
void LoadURL(const GURL& url, const mate::Dictionary& options); void LoadURL(const GURL& url, const mate::Dictionary& options);
void DownloadURL(const GURL& url); void DownloadURL(const GURL& url);
@ -116,6 +117,7 @@ class WebContents : public mate::TrackableObject<WebContents>,
void ReplaceMisspelling(const base::string16& word); void ReplaceMisspelling(const base::string16& word);
uint32_t FindInPage(mate::Arguments* args); uint32_t FindInPage(mate::Arguments* args);
void StopFindInPage(content::StopFindAction action); void StopFindInPage(content::StopFindAction action);
void ShowDefinitionForSelection();
// Focus. // Focus.
void Focus(); void Focus();
@ -182,6 +184,7 @@ class WebContents : public mate::TrackableObject<WebContents>,
const gfx::Rect& pos) override; const gfx::Rect& pos) override;
void CloseContents(content::WebContents* source) override; void CloseContents(content::WebContents* source) override;
void ActivateContents(content::WebContents* contents) override; void ActivateContents(content::WebContents* contents) override;
void UpdateTargetURL(content::WebContents* source, const GURL& url) override;
bool IsPopupOrPanel(const content::WebContents* source) const override; bool IsPopupOrPanel(const content::WebContents* source) const override;
void HandleKeyboardEvent( void HandleKeyboardEvent(
content::WebContents* source, content::WebContents* source,

View file

@ -623,12 +623,6 @@ void Window::UnhookAllWindowMessages() {
} }
#endif #endif
#if defined(OS_MACOSX)
void Window::ShowDefinitionForSelection() {
window_->ShowDefinitionForSelection();
}
#endif
#if defined(TOOLKIT_VIEWS) #if defined(TOOLKIT_VIEWS)
void Window::SetIcon(mate::Handle<NativeImage> icon) { void Window::SetIcon(mate::Handle<NativeImage> icon) {
#if defined(OS_WIN) #if defined(OS_WIN)
@ -760,10 +754,6 @@ void Window::BuildPrototype(v8::Isolate* isolate,
.SetMethod("unhookWindowMessage", &Window::UnhookWindowMessage) .SetMethod("unhookWindowMessage", &Window::UnhookWindowMessage)
.SetMethod("unhookAllWindowMessages", &Window::UnhookAllWindowMessages) .SetMethod("unhookAllWindowMessages", &Window::UnhookAllWindowMessages)
#endif #endif
#if defined(OS_MACOSX)
.SetMethod("showDefinitionForSelection",
&Window::ShowDefinitionForSelection)
#endif
#if defined(TOOLKIT_VIEWS) #if defined(TOOLKIT_VIEWS)
.SetMethod("setIcon", &Window::SetIcon) .SetMethod("setIcon", &Window::SetIcon)
#endif #endif

View file

@ -169,10 +169,6 @@ class Window : public mate::TrackableObject<Window>,
void UnhookAllWindowMessages(); void UnhookAllWindowMessages();
#endif #endif
#if defined(OS_MACOSX)
void ShowDefinitionForSelection();
#endif
#if defined(TOOLKIT_VIEWS) #if defined(TOOLKIT_VIEWS)
void SetIcon(mate::Handle<NativeImage> icon); void SetIcon(mate::Handle<NativeImage> icon);
#endif #endif

View file

@ -169,6 +169,14 @@ void AtomBrowserClient::AppendExtraCommandLineSwitches(
if (process_type != "renderer") if (process_type != "renderer")
return; return;
// Copy following switches to child process.
static const char* const kCommonSwitchNames[] = {
switches::kStandardSchemes,
};
command_line->CopySwitchesFrom(
*base::CommandLine::ForCurrentProcess(),
kCommonSwitchNames, arraysize(kCommonSwitchNames));
// The registered service worker schemes. // The registered service worker schemes.
if (!g_custom_service_worker_schemes.empty()) if (!g_custom_service_worker_schemes.empty())
command_line->AppendSwitchASCII(switches::kRegisterServiceWorkerSchemes, command_line->AppendSwitchASCII(switches::kRegisterServiceWorkerSchemes,

View file

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

View file

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

View file

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

View file

@ -49,7 +49,8 @@ class BrowserObserver {
virtual void OnFinishLaunching() {} virtual void OnFinishLaunching() {}
// The browser requests HTTP login. // 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) #if defined(OS_MACOSX)
// The browser wants to resume a user activity via handoff. (OS X only) // The browser wants to resume a user activity via handoff. (OS X only)

View file

@ -5,6 +5,8 @@
#include "atom/browser/login_handler.h" #include "atom/browser/login_handler.h"
#include "atom/browser/browser.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/browser_thread.h"
#include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_frame_host.h"
#include "content/public/browser/resource_dispatcher_host.h" #include "content/public/browser/resource_dispatcher_host.h"
@ -37,11 +39,18 @@ LoginHandler::LoginHandler(net::AuthChallengeInfo* auth_info,
render_frame_id_(0) { render_frame_id_(0) {
content::ResourceRequestInfo::ForRequest(request_)->GetAssociatedRenderFrame( content::ResourceRequestInfo::ForRequest(request_)->GetAssociatedRenderFrame(
&render_process_host_id_, &render_frame_id_); &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::PostTask(
BrowserThread::UI, FROM_HERE, BrowserThread::UI, FROM_HERE,
base::Bind(&Browser::RequestLogin, base::Bind(&Browser::RequestLogin,
base::Unretained(Browser::Get()), base::Unretained(Browser::Get()),
base::RetainedRef(make_scoped_refptr(this)))); base::RetainedRef(make_scoped_refptr(this)),
base::Passed(&request_details)));
} }
LoginHandler::~LoginHandler() { LoginHandler::~LoginHandler() {

View file

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

View file

@ -10,6 +10,7 @@
#include "atom/browser/atom_browser_context.h" #include "atom/browser/atom_browser_context.h"
#include "atom/browser/atom_browser_main_parts.h" #include "atom/browser/atom_browser_main_parts.h"
#include "atom/browser/browser.h"
#include "atom/browser/window_list.h" #include "atom/browser/window_list.h"
#include "atom/common/api/api_messages.h" #include "atom/common/api/api_messages.h"
#include "atom/common/native_mate_converters/file_path_converter.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. // For normal window, use white as default background.
SetBackgroundColor("#FFFF"); SetBackgroundColor("#FFFF");
} }
std::string title("Electron"); std::string title(Browser::Get()->GetName());
options.Get(options::kTitle, &title); options.Get(options::kTitle, &title);
SetTitle(title); SetTitle(title);
@ -281,9 +282,6 @@ bool NativeWindow::IsDocumentEdited() {
return false; return false;
} }
void NativeWindow::SetIgnoreMouseEvents(bool ignore) {
}
void NativeWindow::SetMenu(ui::MenuModel* menu) { void NativeWindow::SetMenu(ui::MenuModel* menu) {
} }
@ -337,10 +335,6 @@ void NativeWindow::CapturePage(const gfx::Rect& rect,
kBGRA_8888_SkColorType); kBGRA_8888_SkColorType);
} }
void NativeWindow::ShowDefinitionForSelection() {
NOTIMPLEMENTED();
}
void NativeWindow::SetAutoHideMenuBar(bool auto_hide) { void NativeWindow::SetAutoHideMenuBar(bool auto_hide) {
} }

View file

@ -154,7 +154,7 @@ class NativeWindow : public base::SupportsUserData,
virtual std::string GetRepresentedFilename(); virtual std::string GetRepresentedFilename();
virtual void SetDocumentEdited(bool edited); virtual void SetDocumentEdited(bool edited);
virtual bool IsDocumentEdited(); virtual bool IsDocumentEdited();
virtual void SetIgnoreMouseEvents(bool ignore); virtual void SetIgnoreMouseEvents(bool ignore) = 0;
virtual void SetMenu(ui::MenuModel* menu); virtual void SetMenu(ui::MenuModel* menu);
virtual bool HasModalDialog(); virtual bool HasModalDialog();
virtual gfx::NativeWindow GetNativeWindow() = 0; virtual gfx::NativeWindow GetNativeWindow() = 0;
@ -179,9 +179,6 @@ class NativeWindow : public base::SupportsUserData,
virtual void CapturePage(const gfx::Rect& rect, virtual void CapturePage(const gfx::Rect& rect,
const CapturePageCallback& callback); const CapturePageCallback& callback);
// Show popup dictionary.
virtual void ShowDefinitionForSelection();
// Toggle the menu bar. // Toggle the menu bar.
virtual void SetAutoHideMenuBar(bool auto_hide); virtual void SetAutoHideMenuBar(bool auto_hide);
virtual bool IsMenuBarAutoHide(); virtual bool IsMenuBarAutoHide();

View file

@ -83,7 +83,6 @@ class NativeWindowMac : public NativeWindow {
void SetProgressBar(double progress) override; void SetProgressBar(double progress) override;
void SetOverlayIcon(const gfx::Image& overlay, void SetOverlayIcon(const gfx::Image& overlay,
const std::string& description) override; const std::string& description) override;
void ShowDefinitionForSelection() override;
void SetVisibleOnAllWorkspaces(bool visible) override; void SetVisibleOnAllWorkspaces(bool visible) override;
bool IsVisibleOnAllWorkspaces() override; bool IsVisibleOnAllWorkspaces() override;
@ -139,6 +138,9 @@ class NativeWindowMac : public NativeWindow {
// The presentation options before entering kiosk mode. // The presentation options before entering kiosk mode.
NSApplicationPresentationOptions kiosk_options_; NSApplicationPresentationOptions kiosk_options_;
// The window title, for frameless windows we only set title when fullscreen.
std::string title_;
// Force showing the buttons for frameless window. // Force showing the buttons for frameless window.
bool force_show_buttons_; bool force_show_buttons_;

View file

@ -193,10 +193,16 @@ bool ScopedDisableResize::disable_resize_ = false;
- (void)windowDidEnterFullScreen:(NSNotification*)notification { - (void)windowDidEnterFullScreen:(NSNotification*)notification {
shell_->NotifyWindowEnterFullScreen(); shell_->NotifyWindowEnterFullScreen();
// For frameless window we don't set title for normal mode since the title
// bar is expected to be empty, but after entering fullscreen mode we have
// to set one, because title bar is visible here.
NSWindow* window = shell_->GetNativeWindow();
if (shell_->transparent() || !shell_->has_frame())
[window setTitle:base::SysUTF8ToNSString(shell_->GetTitle())];
// Restore the native toolbar immediately after entering fullscreen, if we do // Restore the native toolbar immediately after entering fullscreen, if we do
// this before leaving fullscreen, traffic light buttons will be jumping. // this before leaving fullscreen, traffic light buttons will be jumping.
if (shell_->should_hide_native_toolbar_in_fullscreen()) { if (shell_->should_hide_native_toolbar_in_fullscreen()) {
NSWindow* window = shell_->GetNativeWindow();
base::scoped_nsobject<NSToolbar> toolbar( base::scoped_nsobject<NSToolbar> toolbar(
[[NSToolbar alloc] initWithIdentifier:@"titlebarStylingToolbar"]); [[NSToolbar alloc] initWithIdentifier:@"titlebarStylingToolbar"]);
[toolbar setShowsBaselineSeparator:NO]; [toolbar setShowsBaselineSeparator:NO];
@ -209,6 +215,11 @@ bool ScopedDisableResize::disable_resize_ = false;
} }
- (void)windowWillExitFullScreen:(NSNotification*)notification { - (void)windowWillExitFullScreen:(NSNotification*)notification {
// Restore the title bar to empty.
NSWindow* window = shell_->GetNativeWindow();
if (shell_->transparent() || !shell_->has_frame())
[window setTitle:@""];
// Turn off the style for toolbar. // Turn off the style for toolbar.
if (shell_->should_hide_native_toolbar_in_fullscreen()) if (shell_->should_hide_native_toolbar_in_fullscreen())
shell_->SetStyleMask(false, NSFullSizeContentViewWindowMask); shell_->SetStyleMask(false, NSFullSizeContentViewWindowMask);
@ -513,10 +524,6 @@ NativeWindowMac::NativeWindowMac(
options.Get(options::kDisableAutoHideCursor, &disableAutoHideCursor); options.Get(options::kDisableAutoHideCursor, &disableAutoHideCursor);
[window_ setDisableAutoHideCursor:disableAutoHideCursor]; [window_ setDisableAutoHideCursor:disableAutoHideCursor];
// Disable zoom button if window is not resizable.
if (!maximizable)
SetMaximizable(false);
NSView* view = inspectable_web_contents()->GetView()->GetNativeView(); NSView* view = inspectable_web_contents()->GetView()->GetNativeView();
[view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; [view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
@ -544,6 +551,12 @@ NativeWindowMac::NativeWindowMac(
}]; }];
InstallView(); 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() { NativeWindowMac::~NativeWindowMac() {
@ -781,6 +794,8 @@ void NativeWindowMac::Center() {
} }
void NativeWindowMac::SetTitle(const std::string& title) { void NativeWindowMac::SetTitle(const std::string& title) {
title_ = title;
// We don't want the title to show in transparent or frameless window. // We don't want the title to show in transparent or frameless window.
if (transparent() || !has_frame()) if (transparent() || !has_frame())
return; return;
@ -789,7 +804,7 @@ void NativeWindowMac::SetTitle(const std::string& title) {
} }
std::string NativeWindowMac::GetTitle() { std::string NativeWindowMac::GetTitle() {
return base::SysNSStringToUTF8([window_ title]); return title_;
} }
void NativeWindowMac::FlashFrame(bool flash) { void NativeWindowMac::FlashFrame(bool flash) {
@ -920,15 +935,6 @@ void NativeWindowMac::SetOverlayIcon(const gfx::Image& overlay,
const std::string& description) { const std::string& description) {
} }
void NativeWindowMac::ShowDefinitionForSelection() {
if (!web_contents())
return;
auto rwhv = web_contents()->GetRenderWidgetHostView();
if (!rwhv)
return;
rwhv->ShowDefinitionForSelection();
}
void NativeWindowMac::SetVisibleOnAllWorkspaces(bool visible) { void NativeWindowMac::SetVisibleOnAllWorkspaces(bool visible) {
SetCollectionBehavior(visible, NSWindowCollectionBehaviorCanJoinAllSpaces); SetCollectionBehavior(visible, NSWindowCollectionBehaviorCanJoinAllSpaces);
} }

View file

@ -89,6 +89,21 @@ bool IsAltModifier(const content::NativeWebKeyboardEvent& event) {
(modifiers == (Modifiers::AltKey | Modifiers::IsRight)); (modifiers == (Modifiers::AltKey | Modifiers::IsRight));
} }
#if defined(USE_X11)
int SendClientEvent(XDisplay* display, ::Window window, const char* msg) {
XEvent event = {};
event.xclient.type = ClientMessage;
event.xclient.send_event = True;
event.xclient.message_type = XInternAtom(display, msg, False);
event.xclient.window = window;
event.xclient.format = 32;
XSendEvent(display, DefaultRootWindow(display), False,
SubstructureRedirectMask | SubstructureNotifyMask, &event);
XFlush(display);
return True;
}
#endif
class NativeWindowClientView : public views::ClientView { class NativeWindowClientView : public views::ClientView {
public: public:
NativeWindowClientView(views::Widget* widget, NativeWindowClientView(views::Widget* widget,
@ -298,10 +313,19 @@ void NativeWindowViews::CloseImmediately() {
} }
void NativeWindowViews::Focus(bool focus) { void NativeWindowViews::Focus(bool focus) {
if (focus) if (focus) {
#if defined(OS_WIN)
window_->Activate(); window_->Activate();
else #elif defined(USE_X11)
// The "Activate" implementation of Chromium is not reliable on Linux.
::Window window = GetAcceleratedWidget();
XDisplay* xdisplay = gfx::GetXDisplay();
SendClientEvent(xdisplay, window, "_NET_ACTIVE_WINDOW");
XMapRaised(xdisplay, window);
#endif
} else {
window_->Deactivate(); window_->Deactivate();
}
} }
bool NativeWindowViews::IsFocused() { bool NativeWindowViews::IsFocused() {
@ -648,6 +672,26 @@ bool NativeWindowViews::HasShadow() {
return wm::GetShadowType(GetNativeWindow()) != wm::SHADOW_TYPE_NONE; return wm::GetShadowType(GetNativeWindow()) != wm::SHADOW_TYPE_NONE;
} }
void NativeWindowViews::SetIgnoreMouseEvents(bool ignore) {
#if defined(OS_WIN)
LONG ex_style = ::GetWindowLong(GetAcceleratedWidget(), GWL_EXSTYLE);
if (ignore)
ex_style |= (WS_EX_TRANSPARENT | WS_EX_LAYERED);
else
ex_style &= ~(WS_EX_TRANSPARENT | WS_EX_LAYERED);
::SetWindowLong(GetAcceleratedWidget(), GWL_EXSTYLE, ex_style);
#elif defined(USE_X11)
if (ignore) {
XRectangle r = {0, 0, 1, 1};
XShapeCombineRectangles(gfx::GetXDisplay(), GetAcceleratedWidget(),
ShapeInput, 0, 0, &r, 1, ShapeSet, YXBanded);
} else {
XShapeCombineMask(gfx::GetXDisplay(), GetAcceleratedWidget(),
ShapeInput, 0, 0, None, ShapeSet);
}
#endif
}
void NativeWindowViews::SetMenu(ui::MenuModel* menu_model) { void NativeWindowViews::SetMenu(ui::MenuModel* menu_model) {
if (menu_model == nullptr) { if (menu_model == nullptr) {
// Remove accelerators // Remove accelerators

View file

@ -91,6 +91,7 @@ class NativeWindowViews : public NativeWindow,
void SetBackgroundColor(const std::string& color_name) override; void SetBackgroundColor(const std::string& color_name) override;
void SetHasShadow(bool has_shadow) override; void SetHasShadow(bool has_shadow) override;
bool HasShadow() override; bool HasShadow() override;
void SetIgnoreMouseEvents(bool ignore) override;
void SetMenu(ui::MenuModel* menu_model) override; void SetMenu(ui::MenuModel* menu_model) override;
gfx::NativeWindow GetNativeWindow() override; gfx::NativeWindow GetNativeWindow() override;
void SetOverlayIcon(const gfx::Image& overlay, void SetOverlayIcon(const gfx::Image& overlay,

View file

@ -71,18 +71,13 @@ bool MatchesFilterCondition(net::URLRequest* request,
// Overloaded by multiple types to fill the |details| object. // Overloaded by multiple types to fill the |details| object.
void ToDictionary(base::DictionaryValue* details, net::URLRequest* request) { void ToDictionary(base::DictionaryValue* details, net::URLRequest* request) {
FillRequestDetails(details, request);
details->SetInteger("id", request->identifier()); details->SetInteger("id", request->identifier());
details->SetString("url", request->url().spec());
details->SetString("method", request->method());
details->SetDouble("timestamp", base::Time::Now().ToDoubleT() * 1000); details->SetDouble("timestamp", base::Time::Now().ToDoubleT() * 1000);
auto info = content::ResourceRequestInfo::ForRequest(request); auto info = content::ResourceRequestInfo::ForRequest(request);
details->SetString("resourceType", details->SetString("resourceType",
info ? ResourceTypeToString(info->GetResourceType()) info ? ResourceTypeToString(info->GetResourceType())
: "other"); : "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, void ToDictionary(base::DictionaryValue* details,

View file

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

View file

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

View file

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

View file

@ -56,8 +56,8 @@ END
// //
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,2,1,0 FILEVERSION 1,2,2,0
PRODUCTVERSION 1,2,1,0 PRODUCTVERSION 1,2,2,0
FILEFLAGSMASK 0x3fL FILEFLAGSMASK 0x3fL
#ifdef _DEBUG #ifdef _DEBUG
FILEFLAGS 0x1L FILEFLAGS 0x1L
@ -74,12 +74,12 @@ BEGIN
BEGIN BEGIN
VALUE "CompanyName", "GitHub, Inc." VALUE "CompanyName", "GitHub, Inc."
VALUE "FileDescription", "Electron" VALUE "FileDescription", "Electron"
VALUE "FileVersion", "1.2.1" VALUE "FileVersion", "1.2.2"
VALUE "InternalName", "electron.exe" VALUE "InternalName", "electron.exe"
VALUE "LegalCopyright", "Copyright (C) 2015 GitHub, Inc. All rights reserved." VALUE "LegalCopyright", "Copyright (C) 2015 GitHub, Inc. All rights reserved."
VALUE "OriginalFilename", "electron.exe" VALUE "OriginalFilename", "electron.exe"
VALUE "ProductName", "Electron" VALUE "ProductName", "Electron"
VALUE "ProductVersion", "1.2.1" VALUE "ProductVersion", "1.2.2"
VALUE "SquirrelAwareVersion", "1" VALUE "SquirrelAwareVersion", "1"
END END
END END

View file

@ -33,7 +33,7 @@ Role kRolesMap[] = {
{ @selector(copy:), "copy" }, { @selector(copy:), "copy" },
{ @selector(paste:), "paste" }, { @selector(paste:), "paste" },
{ @selector(delete:), "delete" }, { @selector(delete:), "delete" },
{ @selector(pasteAndMatchStyle:), "paste-and-match-style" }, { @selector(pasteAndMatchStyle:), "pasteandmatchstyle" },
{ @selector(selectAll:), "selectall" }, { @selector(selectAll:), "selectall" },
{ @selector(performMiniaturize:), "minimize" }, { @selector(performMiniaturize:), "minimize" },
{ @selector(performClose:), "close" }, { @selector(performClose:), "close" },

View file

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

View file

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

View file

@ -166,6 +166,13 @@ void WebContentsPreferences::AppendExtraCommandLineSwitches(
command_line->AppendSwitchASCII(::switches::kEnableBlinkFeatures, command_line->AppendSwitchASCII(::switches::kEnableBlinkFeatures,
blink_features); blink_features);
// Disable blink features.
std::string disable_blink_features;
if (web_preferences.GetString(options::kDisableBlinkFeatures,
&disable_blink_features))
command_line->AppendSwitchASCII(::switches::kDisableBlinkFeatures,
disable_blink_features);
// The initial visibility state. // The initial visibility state.
NativeWindow* window = NativeWindow::FromWebContents(web_contents); NativeWindow* window = NativeWindow::FromWebContents(web_contents);

View file

@ -7,7 +7,7 @@
#define ATOM_MAJOR_VERSION 1 #define ATOM_MAJOR_VERSION 1
#define ATOM_MINOR_VERSION 2 #define ATOM_MINOR_VERSION 2
#define ATOM_PATCH_VERSION 1 #define ATOM_PATCH_VERSION 2
#define ATOM_VERSION_IS_RELEASE 1 #define ATOM_VERSION_IS_RELEASE 1

View file

@ -8,7 +8,7 @@
#ifndef ATOM_COMMON_CHROME_VERSION_H_ #ifndef ATOM_COMMON_CHROME_VERSION_H_
#define ATOM_COMMON_CHROME_VERSION_H_ #define ATOM_COMMON_CHROME_VERSION_H_
#define CHROME_VERSION_STRING "51.0.2704.63" #define CHROME_VERSION_STRING "51.0.2704.84"
#define CHROME_VERSION "v" CHROME_VERSION_STRING #define CHROME_VERSION "v" CHROME_VERSION_STRING
#endif // ATOM_COMMON_CHROME_VERSION_H_ #endif // ATOM_COMMON_CHROME_VERSION_H_

View file

@ -22,22 +22,6 @@
namespace mate { 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 // static
v8::Local<v8::Value> Converter<const net::AuthChallengeInfo*>::ToV8( v8::Local<v8::Value> Converter<const net::AuthChallengeInfo*>::ToV8(
v8::Isolate* isolate, const net::AuthChallengeInfo* val) { v8::Isolate* isolate, const net::AuthChallengeInfo* val) {
@ -69,6 +53,19 @@ v8::Local<v8::Value> Converter<scoped_refptr<net::X509Certificate>>::ToV8(
namespace atom { 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, void GetUploadData(base::ListValue* upload_data_list,
const net::URLRequest* request) { const net::URLRequest* request) {
const net::UploadDataStream* upload_data = request->get_upload(); const net::UploadDataStream* upload_data = request->get_upload();

View file

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

View file

@ -107,8 +107,12 @@ const char kOpenerID[] = "openerId";
const char kScrollBounce[] = "scrollBounce"; const char kScrollBounce[] = "scrollBounce";
// Enable blink features. // Enable blink features.
// TODO(kevinsawicki) Rename to enableBlinkFeatures in 2.0
const char kBlinkFeatures[] = "blinkFeatures"; const char kBlinkFeatures[] = "blinkFeatures";
// Disable blink features.
const char kDisableBlinkFeatures[] = "disableBlinkFeatures";
} // namespace options } // namespace options
namespace switches { namespace switches {
@ -125,6 +129,9 @@ const char kPpapiFlashVersion[] = "ppapi-flash-version";
// Disable HTTP cache. // Disable HTTP cache.
const char kDisableHttpCache[] = "disable-http-cache"; const char kDisableHttpCache[] = "disable-http-cache";
// The list of standard schemes.
const char kStandardSchemes[] = "standard-schemes";
// Register schemes to handle service worker. // Register schemes to handle service worker.
const char kRegisterServiceWorkerSchemes[] = "register-service-worker-schemes"; const char kRegisterServiceWorkerSchemes[] = "register-service-worker-schemes";

View file

@ -58,6 +58,7 @@ extern const char kExperimentalCanvasFeatures[];
extern const char kOpenerID[]; extern const char kOpenerID[];
extern const char kScrollBounce[]; extern const char kScrollBounce[];
extern const char kBlinkFeatures[]; extern const char kBlinkFeatures[];
extern const char kDisableBlinkFeatures[];
} // namespace options } // namespace options
@ -70,6 +71,7 @@ extern const char kEnablePlugins[];
extern const char kPpapiFlashPath[]; extern const char kPpapiFlashPath[];
extern const char kPpapiFlashVersion[]; extern const char kPpapiFlashVersion[];
extern const char kDisableHttpCache[]; extern const char kDisableHttpCache[];
extern const char kStandardSchemes[];
extern const char kRegisterServiceWorkerSchemes[]; extern const char kRegisterServiceWorkerSchemes[];
extern const char kSSLVersionFallbackMin[]; extern const char kSSLVersionFallbackMin[];
extern const char kCipherSuiteBlacklist[]; extern const char kCipherSuiteBlacklist[];

View file

@ -122,6 +122,16 @@ bool IsDevToolsExtension(content::RenderFrame* render_frame) {
AtomRendererClient::AtomRendererClient() AtomRendererClient::AtomRendererClient()
: node_bindings_(NodeBindings::Create(false)), : node_bindings_(NodeBindings::Create(false)),
atom_bindings_(new AtomBindings) { atom_bindings_(new AtomBindings) {
// Parse --standard-schemes=scheme1,scheme2
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
std::string custom_schemes = command_line->GetSwitchValueASCII(
switches::kStandardSchemes);
if (!custom_schemes.empty()) {
std::vector<std::string> schemes_list = base::SplitString(
custom_schemes, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
for (const std::string& scheme : schemes_list)
url::AddStandardScheme(scheme.c_str(), url::SCHEME_WITHOUT_PORT);
}
} }
AtomRendererClient::~AtomRendererClient() { AtomRendererClient::~AtomRendererClient() {

View file

@ -8,11 +8,14 @@ const url = require('url')
// Parse command line options. // Parse command line options.
const argv = process.argv.slice(1) const argv = process.argv.slice(1)
const option = { file: null, help: null, version: null, webdriver: null, modules: [] } const option = { file: null, help: null, version: null, abi: null, webdriver: null, modules: [] }
for (let i = 0; i < argv.length; i++) { for (let i = 0; i < argv.length; i++) {
if (argv[i] === '--version' || argv[i] === '-v') { if (argv[i] === '--version' || argv[i] === '-v') {
option.version = true option.version = true
break break
} else if (argv[i] === '--abi') {
option.abi = true
break
} else if (argv[i].match(/^--app=/)) { } else if (argv[i].match(/^--app=/)) {
option.file = argv[i].split('=')[1] option.file = argv[i].split('=')[1]
break break
@ -201,7 +204,7 @@ app.once('ready', () => {
type: 'separator' type: 'separator'
}, },
{ {
label: 'Quit', label: 'Quit ' + app.getName(),
accelerator: 'Command+Q', accelerator: 'Command+Q',
click () { app.quit() } click () { app.quit() }
} }
@ -304,6 +307,9 @@ if (option.file && !option.webdriver) {
} else if (option.version) { } else if (option.version) {
console.log('v' + process.versions.electron) console.log('v' + process.versions.electron)
process.exit(0) process.exit(0)
} else if (option.abi) {
console.log(process.versions.modules)
process.exit(0)
} else if (option.help) { } else if (option.help) {
const helpMessage = `Electron ${process.versions.electron} - Build cross platform desktop apps with JavaScript, HTML, and CSS const helpMessage = `Electron ${process.versions.electron} - Build cross platform desktop apps with JavaScript, HTML, and CSS
@ -321,7 +327,8 @@ if (option.file && !option.webdriver) {
-h, --help Print this usage message. -h, --help Print this usage message.
-i, --interactive Open a REPL to the main process. -i, --interactive Open a REPL to the main process.
-r, --require Module to preload (option can be repeated) -r, --require Module to preload (option can be repeated)
-v, --version Print the version.` -v, --version Print the version.
--abi Print the application binary interface.`
console.log(helpMessage) console.log(helpMessage)
process.exit(0) process.exit(0)
} else if (option.interactive) { } else if (option.interactive) {

View file

@ -12,7 +12,7 @@
`ipcRenderer`는 다음과 같은 이벤트 리스닝 메서드를 가지고 있습니다: `ipcRenderer`는 다음과 같은 이벤트 리스닝 메서드를 가지고 있습니다:
### `ipcMain.on(channel, listener)` ### `ipcRenderer.on(channel, listener)`
* `channel` String * `channel` String
* `listener` Function * `listener` Function
@ -20,7 +20,7 @@
`channel`에 대해 이벤트를 리스닝합니다. 새로운 메시지가 도착하면 `listener` `channel`에 대해 이벤트를 리스닝합니다. 새로운 메시지가 도착하면 `listener`
`listener(event, args...)` 형식으로 호출됩니다. `listener(event, args...)` 형식으로 호출됩니다.
### `ipcMain.once(channel, listener)` ### `ipcRenderer.once(channel, listener)`
* `channel` String * `channel` String
* `listener` Function * `listener` Function
@ -28,7 +28,7 @@
이벤트에 대해 한 번만 작동하는 `listener` 함수를 등록합니다. 이 `listener`는 등록된 이벤트에 대해 한 번만 작동하는 `listener` 함수를 등록합니다. 이 `listener`는 등록된
`channel`에 보내지는 메시지에 한해 호출됩니다. 호출된 이후엔 리스너가 삭제됩니다. `channel`에 보내지는 메시지에 한해 호출됩니다. 호출된 이후엔 리스너가 삭제됩니다.
### `ipcMain.removeListener(channel, listener)` ### `ipcRenderer.removeListener(channel, listener)`
* `channel` String * `channel` String
* `listener` Function * `listener` Function
@ -39,7 +39,7 @@
지정한 `channel`에 대한 리스너를 저장하는 배열에서 지정한 `listener`를 삭제합니다. 지정한 `channel`에 대한 리스너를 저장하는 배열에서 지정한 `listener`를 삭제합니다.
### `ipcMain.removeAllListeners(channel)` ### `ipcRenderer.removeAllListeners(channel)`
* `channel` String (optional) * `channel` String (optional)

View file

@ -76,7 +76,7 @@ exports.withLocalCallback = () => {
```javascript ```javascript
// 렌더러 프로세스 // 렌더러 프로세스
const mapNumbers = require('remote').require('./mapNumbers'); const mapNumbers = require('electron').remote.require('./mapNumbers');
const withRendererCb = mapNumbers.withRendererCallback(x => x + 1); const withRendererCb = mapNumbers.withRendererCallback(x => x + 1);

View file

@ -284,6 +284,15 @@ Returns:
<meta name='theme-color' content='#ff0000'> <meta name='theme-color' content='#ff0000'>
``` ```
### Event: 'update-target-url'
Returns:
* `event` Event
* `url` URL
마우스나 키보드를 사용해 링크에 포커스를 할 때 발생하는 이벤트입니다.
### Event: 'cursor-changed' ### Event: 'cursor-changed'
Returns: Returns:
@ -591,6 +600,12 @@ CSS 코드를 현재 웹 페이지에 삽입합니다.
웹 페이지에서 `replaceMisspelling` 편집 커맨드를 실행합니다. 웹 페이지에서 `replaceMisspelling` 편집 커맨드를 실행합니다.
### `webContents.insertText(text)`
* `text` String
포커스된 요소에 `text`를 삽입합니다.
### `webContents.findInPage(text[, options])` ### `webContents.findInPage(text[, options])`
* `text` String - 찾을 콘텐츠, 반드시 공백이 아니여야 합니다. * `text` String - 찾을 콘텐츠, 반드시 공백이 아니여야 합니다.

View file

@ -117,25 +117,7 @@ MyApp.app/Contents
또한 Electron 소스 코드를 다시 빌드할 때 어플리케이션 이름을 변경할 수 있습니다. 또한 Electron 소스 코드를 다시 빌드할 때 어플리케이션 이름을 변경할 수 있습니다.
`GYP_DEFINES` 환경변수를 사용하여 다음과 같이 다시 빌드할 수 있습니다: `atom.gyp` 파일을 수정하여 다음과 같이 다시 빌드할 수 있습니다:
__Windows__
```bash
> set "GYP_DEFINES=project_name=myapp product_name=MyApp"
> python script\clean.py
> python script\bootstrap.py
> python script\build.py -c R -t myapp
```
__Bash__
```bash
$ export GYP_DEFINES="project_name=myapp product_name=MyApp"
$ script/clean.py
$ script/bootstrap.py
$ script/build.py -c Release -t myapp
```
### grunt-build-atom-shell ### grunt-build-atom-shell
@ -175,16 +157,16 @@ Electron의 개발자로써, Electron을 매우 많은 시나리오에서도 작
3. 다음의 환경 변수들을 설정합니다: 3. 다음의 환경 변수들을 설정합니다:
* `ELECTRON_GITHUB_TOKEN` - GitHub에 릴리즈를 만들 수 있는 토큰. * `ELECTRON_GITHUB_TOKEN` - GitHub에 릴리즈를 만들 수 있는 토큰.
* `ELECTRON_S3_ACCESS_KEY`, `ELECTRON_S3_BUCKET`, `ELECTRON_S3_SECRET_KEY` - * `ELECTRON_S3_ACCESS_KEY`, `ELECTRON_S3_BUCKET`, `ELECTRON_S3_SECRET_KEY` -
node.js 헤더 뿐만 아니라 심볼을 업로드할 장소. node.js 헤더 뿐만 아니라 심볼을 업로드할 장소.
* `ELECTRON_RELEASE` - `true`로 지정하고 업로드 부분이 실행되면, 설정되지 않은 * `ELECTRON_RELEASE` - `true`로 지정하고 업로드 부분이 실행되면, 설정되지 않은
부분을 남기고 `surf-build`가 CI-type 확인을 실행합니다. 모든 pull request를 부분을 남기고 `surf-build`가 CI-type 확인을 실행합니다. 모든 pull request를
실행할 때 적합합니다. 실행할 때 적합합니다.
* `CI` - `true` 또는 다른 것을 지정하면 실패합니다. * `CI` - `true` 또는 다른 것을 지정하면 실패합니다.
* `GITHUB_TOKEN` - `ELECTRON_GITHUB_TOKEN`와 같게 설정 * `GITHUB_TOKEN` - `ELECTRON_GITHUB_TOKEN`와 같게 설정
* `SURF_TEMP` - Windows에선 `C:\Temp`로 설정하면 긴 경로 문제를 해결할 수 있습니다. * `SURF_TEMP` - Windows에선 `C:\Temp`로 설정하면 긴 경로 문제를 해결할 수 있습니다.
* `TARGET_ARCH` - `ia32` 또는 `x64`를 지정. * `TARGET_ARCH` - `ia32` 또는 `x64`를 지정.
4. Electron에 기여를 하는 기여자라면, _반드시_ `script/upload.py`에서 포크를 위해 4. Electron에 기여를 하는 기여자라면, _반드시_ `script/upload.py`에서 포크를 위해
`ELECTRON_REPO`를 설정해야 합니다. (`MYORG/electron`) `ELECTRON_REPO`를 설정해야 합니다. (`MYORG/electron`)

View file

@ -120,6 +120,21 @@ productbuild --component "$APP_PATH" /Applications --sign "$INSTALLER_KEY" "$RES
문서를 참고하여 기본적인 개념을 이해해야 합니다. 그리고 자격(plist) 파일에 문서를 참고하여 기본적인 개념을 이해해야 합니다. 그리고 자격(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를 어플리케이션 서명을 완료한 후 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 [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 [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 [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 [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/ [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/ [ern-tutorial]: https://carouselapps.com/2015/12/15/legally-submit-app-apples-app-store-uses-encryption-obtain-ern/

View file

@ -41,7 +41,7 @@ API를 사용하여 low-level 수준으로 운영체제와 상호작용할 수
프로세스에서 그 작업을 처리할 수 있도록 메인 프로세스와 통신을 해야 합니다. 프로세스에서 그 작업을 처리할 수 있도록 메인 프로세스와 통신을 해야 합니다.
Electron에는 메인 프로세스와 렌더러 프로세스 사이에 통신을 할 수 있도록 Electron에는 메인 프로세스와 렌더러 프로세스 사이에 통신을 할 수 있도록
[ipc](../api/ipc-renderer.md) 모듈을 제공하고 있습니다. [ipcRenderer](../api/ipc-renderer.md)와 [ipcMain](../api/ipc-main.md) 모듈을 제공하고 있습니다.
또는 [remote](../api/remote.md) 모듈을 사용하여 RPC 스타일로 통신할 수도 있습니다. 또는 [remote](../api/remote.md) 모듈을 사용하여 RPC 스타일로 통신할 수도 있습니다.
또한 FAQ에서 [다양한 객체를 공유하는 방법](share-data)도 소개하고 있습니다. 또한 FAQ에서 [다양한 객체를 공유하는 방법](share-data)도 소개하고 있습니다.

View file

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

View file

@ -1,11 +1,11 @@
# Распространение приложения # Распространение приложения
Чтобы разпространять ваше приложение на Electron, папка с вашим приложением Чтобы разпространять ваше приложение на Electron, папка с вашим приложением
должна называться `app` и находиться в папке ресурсов Electron (на OS X это должна называться `app` и находиться в папке ресурсов Electron (в OS X это
`Electron.app/Contents/Resources/`, на Linux и Windows - `resources/`), `Electron.app/Contents/Resources/`, в Linux и Windows - `resources/`),
вот так: вот так:
На OS X: Для OS X:
```text ```text
electron/Electron.app/Contents/Resources/app/ electron/Electron.app/Contents/Resources/app/
@ -14,7 +14,7 @@ electron/Electron.app/Contents/Resources/app/
└── index.html └── index.html
``` ```
На Windows и Linux: Для Windows и Linux:
```text ```text
electron/resources/app electron/resources/app
@ -23,13 +23,13 @@ electron/resources/app
└── index.html └── index.html
``` ```
Затем запустите `Electron.app` (или `electron` на Linux, `electron.exe` на Windows), Затем запустите `Electron.app` (или `electron` в Linux, `electron.exe` в Windows),
и Electron запустится как ваше приложение. Теперь папка `electron` и есть дистрибутив, и Electron запустится как ваше приложение. Теперь папка `electron` и есть дистрибутив,
который вы должны распространять пользователям. который Вы должны распространять пользователям.
## Упаковка вашего приложения в файл ## Упаковка вашего приложения в файл
Если вы не хотите распространять исходные коды вашего проект, вы можете Если Вы `не хотите` распространять исходные коды вашего проекта, Вы можете
упаковать его в архив [asar](https://github.com/atom/asar), чтобы не упаковать его в архив [asar](https://github.com/atom/asar), чтобы не
показывать пользователям исходные коды. показывать пользователям исходные коды.
@ -38,25 +38,26 @@ electron/resources/app
после чего Electron попробует считать ресурсы и запустить архив. после чего Electron попробует считать ресурсы и запустить архив.
На OS X: Для OS X:
```text ```text
electron/Electron.app/Contents/Resources/ electron/Electron.app/Contents/Resources/
└── app.asar └── app.asar
``` ```
На Windows и Linux: Для Windows и Linux:
```text ```text
electron/resources/ electron/resources/
└── app.asar └── app.asar
``` ```
Больше деталей можна найти в [инстуркции по упаковке приложения](application-packaging.md). Больше деталей можно найти в [инстуркции по упаковке приложения](application-packaging.md).
## Ребрендирование скачанных исполняемых файлов ## Ребрендирование скачанных исполняемых файлов
После того, как вы подключили ваше приложение к Electron,
вам наверняка захочеться ребрендировать его перед распространением. После того, как Вы подключили ваше приложение к Electron,
Вам наверняка захочется ребрендировать его перед распространением.
### Windows ### Windows
@ -73,7 +74,7 @@ electron/resources/
* `Electron.app/Contents/Frameworks/Electron Helper.app/Contents/Info.plist` * `Electron.app/Contents/Frameworks/Electron Helper.app/Contents/Info.plist`
Вы таже можете переименовать приложение-помощник, чтобы оно не показывало `Electron Helper`, Вы таже можете переименовать приложение-помощник, чтобы оно не показывало `Electron Helper`,
убедитесь, что вы переименовали его исполняемый файл. убедитесь, что Вы переименовали его исполняемый файл.
Структура переименованного приложения выглядит примерно так: Структура переименованного приложения выглядит примерно так:
@ -104,22 +105,22 @@ MyApp.app/Contents
## Rebranding by Rebuilding Electron from Source ## Rebranding by Rebuilding Electron from Source
Вы также можете ребрендировать Electron изменив имя продукта и собрав его Вы также можете ребрендировать Electron изменив имя продукта и собрав его
из исходных кодов. Чтобы сделать это вам нужно изменить `atom.gyp` и полностью из исходных кодов. Чтобы сделать это Вам нужно изменить `atom.gyp` и полностью
пересобрать Electron. пересобрать Electron.
### grunt-build-atom-shell ### grunt-build-atom-shell
Проверка и пересборка кода Electron довольно сложная задача, так что мы Проверка и пересборка кода Electron довольно сложная задача, так что мы
мы сделали файл-инструкцию для Grunt, который будет делать это автоматически: сделали файл-инструкцию для Grunt, который будет делать это автоматически:
[grunt-build-atom-shell](https://github.com/paulcbetts/grunt-build-atom-shell). [grunt-build-atom-shell](https://github.com/paulcbetts/grunt-build-atom-shell).
Этот файл автоматически просмотрит изменения в `.gyp` фалле, соберёт Этот файл автоматически просмотрит изменения в `.gyp` файле, соберёт
Electron из исходных кодов и пересоберёт модули Node, чтобы всё подходило Electron из исходных кодов и пересоберёт модули Node, чтобы всё подходило
под новое имя. под новое имя.
## Инструменты ## Инструменты
Вы также можете использовать инструменты оттретьих лиц, Вы также можете использовать инструменты от третьих лиц,
которые сделают работу за вас: которые сделают работу за вас:
* [electron-packager](https://github.com/maxogden/electron-packager) * [electron-packager](https://github.com/maxogden/electron-packager)

View file

@ -1,7 +1,7 @@
# Упаковка приложения # Упаковка приложения
Чтобы смягчить [проблемы](https://github.com/joyent/node/issues/6960) с длинными Чтобы смягчить [проблемы](https://github.com/joyent/node/issues/6960) с длинными
именами под Windows, немного ускорить `require` и скрыть ваши исходные коды, вы именами под Windows, немного ускорить `require` и скрыть ваши исходные коды, Вы
можете упаковать его в архив [asar][asar], немного поменяв исходный код. можете упаковать его в архив [asar][asar], немного поменяв исходный код.
## Генерация архива `asar` ## Генерация архива `asar`
@ -25,16 +25,16 @@ $ asar pack your-app app.asar
## Использование архивов `asar` ## Использование архивов `asar`
В Electron есть два вида API: API Node, которые устанавливаются с помощью Node.Js и В Electron есть два вида API: API Node, которые устанавливаются с помощью Node.JS и
веб API, которые предоставляюся Chromium. Оба предоставляют возможность считывать из веб API, которые предоставляюся Chromium. Оба предоставляют возможность считывать из
архивов `asar`. архивов `asar`.
### Node API ### Node API
С специальными патчами в Electron, части Node API вроде `fs.readFile` и `require` Со специальными патчами в Electron, части Node API вроде `fs.readFile` и `require`
считают архивы `asar` виртуальными папками и файлы в них доступны как в обычных. считают архивы `asar` виртуальными папками и файлы в них доступны как в обычных.
Например, у нас есть арихив `example.asar` в `/path/to`: Например, у нас есть архив `example.asar` в `/path/to`:
```bash ```bash
$ asar list /path/to/example.asar $ asar list /path/to/example.asar
@ -60,13 +60,13 @@ const fs = require('fs');
fs.readdirSync('/path/to/example.asar'); fs.readdirSync('/path/to/example.asar');
``` ```
Ичпользуем модуль из архива: Используем модуль из архива:
```javascript ```javascript
require('/path/to/example.asar/dir/module.js'); require('/path/to/example.asar/dir/module.js');
``` ```
Вы также можете показывать веб страницы из архива `asar` через `BrowserWindow`: Вы также можете показывать веб-страницы из архива `asar` через `BrowserWindow`:
```javascript ```javascript
const BrowserWindow = require('electron').BrowserWindow; const BrowserWindow = require('electron').BrowserWindow;
@ -76,7 +76,7 @@ win.loadURL('file:///path/to/example.asar/static/index.html');
### Веб API ### Веб API
На веб страницах файлы запрашиваются с помощью протокола `file:`. Как и в Node API На веб-страницах файлы запрашиваются с помощью протокола `file:`. Как и в Node API
архивы `asar` считаются за директории. архивы `asar` считаются за директории.
Пример получения файла с помощью `$.get`: Пример получения файла с помощью `$.get`:
@ -93,7 +93,7 @@ $.get('file:///path/to/example.asar/file.txt', function(data) {
### Использование архива `asar` в качестве обычного файла ### Использование архива `asar` в качестве обычного файла
Для случаев, когда вам, например, нужно проверить хэш-сумму архива `asar`, Для случаев, когда Вам, например, нужно проверить хэш-сумму архива `asar`,
нужно использовать архив как файл. Для этой цели существует встроенный модуль нужно использовать архив как файл. Для этой цели существует встроенный модуль
`original-fs`, который предоставляет доступ к `fs` без поддежки `asar`: `original-fs`, который предоставляет доступ к `fs` без поддежки `asar`:
@ -101,6 +101,7 @@ $.get('file:///path/to/example.asar/file.txt', function(data) {
var originalFs = require('original-fs'); var originalFs = require('original-fs');
originalFs.readFileSync('/path/to/example.asar'); originalFs.readFileSync('/path/to/example.asar');
``` ```
Вы также можете выставить `process.noAsar` в `true`, чтобы выключить поддержку `asar` Вы также можете выставить `process.noAsar` в `true`, чтобы выключить поддержку `asar`
в модуле `fs`: в модуле `fs`:
@ -112,7 +113,7 @@ fs.readFileSync('/path/to/example.asar');
## Ограничения Node API ## Ограничения Node API
Хотя мы и старались как могли, чтобы сделать `asar` максимально похожим на папки, Хотя мы и старались как могли, чтобы сделать `asar` максимально похожим на папки,
всё ещё существуют некоторые ограничения из-за низкоуровневой натуры Node API. всё ещё существуют некоторые ограничения из-за низкоуровневой архитектуры Node API.
### Архивы только для чтения ### Архивы только для чтения
@ -121,8 +122,8 @@ fs.readFileSync('/path/to/example.asar');
### Нельзя установить рабочую директорию в архиве ### Нельзя установить рабочую директорию в архиве
Хотя архивы `asar` и считаются папками, они ими на самом деле не являются, Хотя архивы `asar` и считаются папками, они ими на самом деле не являются ими,
так что вы не можете установить в них рабочую директорию. Передача так что Вы не можете установить в них рабочую директорию. Передача
архивов `asar` в качестве аргумента `cwd` некоторым API также может вызывать ошибки. архивов `asar` в качестве аргумента `cwd` некоторым API также может вызывать ошибки.
@ -154,18 +155,18 @@ API которым нужна распаковка:
`child_process.spawn` и `child_process.execFile`, но только `execFile` может `child_process.spawn` и `child_process.execFile`, но только `execFile` может
исполнять файлы из архивов `asar`. исполнять файлы из архивов `asar`.
Так вышло потому, что `exec` и `spawn` принимают `команду` а не `файл` как параметр, Так вышло потому, что `exec` и `spawn` принимают `команду`, а не `файл` как параметр,
а `команды` исполняются в оболочке. Нет никакой реальной возможности проверить, а `команды` исполняются в оболочке. Нет никакой реальной возможности проверить,
реален ли файл или находится в архиве `asar`, и даже если мы смогли бы проверить, реален ли файл или находится в архиве `asar`, и даже если мы смогли бы проверить,
то неясно, как земенить путь к файлу без побочных эффектов. то неясно, как земенить путь к файлу без побочных эффектов.
## Добавление распакованых файлов в архив `asar` ## Добавление распакованых файлов в архив `asar`
Как говорилось выше, некоторые API Node будут распаковывать файлв, Как говорилось выше, некоторые API Node будут распаковывать файлы,
чтобы их использовать. Кроме увеличенного потребления ресурсов это также чтобы их использовать. Кроме увеличенного потребления ресурсов это также
может вызвать предупрждения от антивирусов. может вызвать предупреждения от антивирусов.
Чтобы обойти это, вы можете распаковать некоторые файлы, создавая архивы, Чтобы обойти это, Вы можете распаковать некоторые файлы, создавая архивы,
с помощью опции `--unpack`. Пример показывает распаковку нативных модулей: с помощью опции `--unpack`. Пример показывает распаковку нативных модулей:
```bash ```bash

View file

@ -1,7 +1,7 @@
#Руководство по утверждению вашего приложения в App Store #Руководство по утверждению вашего приложения в App Store
Начиная с версии v0.34.0 Electron позволяет вам сформировать данные для App Store к вашему приложению. Начиная с версии v0.34.0 Electron позволяет Вам сформировать данные для App Store к вашему приложению.
Данное руководство представляет собой пошаговую инструкцию по созданию данных файлов. Данное руководство представляет собой пошаговую инструкцию по созданию данных файлов.
Помните, что когда Вы подаете свое приложение на рассмотрение в App Store Вы должны обладать аккаунтом разработчика, Помните, что когда Вы подаете свое приложение на рассмотрение в App Store Вы должны обладать аккаунтом разработчика,
@ -9,9 +9,7 @@
## Как отправить свое приложение на рассмотрение в App Store ## Как отправить свое приложение на рассмотрение в App Store
Последующие шаги подробно описывают, что и в какой последовательности Вам требуется сделать, но не гарантируют что Ваше приложение Последующие шаги подробно описывают, что и в какой последовательности Вам требуется сделать, но не гарантируют, что Ваше приложение будет рассмотрено Apple. Мы также рекомендуем Вам прочитать официальную документацию по оформлению своего приложения и информации к нему, чтобы пройти проверку в App Store.
будет рассмотрено Apple. Мы так же рекомендуем вам прочитать официальную документацию по оформлению своего приложения и информации к нему
чтобы пройти проверку в App Store.
## Получение сертификата ## Получение сертификата
@ -24,8 +22,9 @@
Во-первых, нам нужно подготовить два файла: Во-первых, нам нужно подготовить два файла:
child.plist: `child.plist`:
```xml
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
@ -36,9 +35,11 @@
<true/> <true/>
</dict> </dict>
</plist> </plist>
```
parent.plist: `parent.plist`:
```xml
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
@ -49,8 +50,11 @@
<string>(allow mach-lookup (global-name-regex #"^org.chromium.Chromium.rohitfork.[0-9]+$"))</string> <string>(allow mach-lookup (global-name-regex #"^org.chromium.Chromium.rohitfork.[0-9]+$"))</string>
</dict> </dict>
</plist> </plist>
```
Затем подписываем свое приложение, с помощью специального сценария: Затем подписываем свое приложение, с помощью специального сценария:
```bash
#!/bin/bash #!/bin/bash
# Имя вашего приложения. # Имя вашего приложения.
@ -79,42 +83,45 @@
codesign -s "$APP_KEY" -f --entitlements parent.plist "$APP_PATH" codesign -s "$APP_KEY" -f --entitlements parent.plist "$APP_PATH"
productbuild --component "$APP_PATH" /Applications --sign "$INSTALLER_KEY" "$RESULT_PATH" productbuild --component "$APP_PATH" /Applications --sign "$INSTALLER_KEY" "$RESULT_PATH"
```
Если вы только начали разрабатывать под Mac OS X, то мы желаем Вам прочитать [App SandBox](https://developer.apple.com/library/ios/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/EnablingAppSandbox.html "Ссылка для новичков в разработке приложений для Mac OS X") Если вы только начали разрабатывать под Mac OS X, то мы советуем Вам прочитать [App SandBox](https://developer.apple.com/library/ios/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/EnablingAppSandbox.html "Ссылка для новичков в разработке приложений для Mac OS X")
## Обновление приложения ## Обновление приложения
После того, как Вы подписали свое приложение вы сможете загрузить его в Itunes Connect для обработки, убедитесь, что вы создали [запись](https://developer.apple.com/library/ios/documentation/LanguagesUtilities/Conceptual/iTunesConnect_Guide/Chapters/CreatingiTunesConnectRecord.html "ссылка на показ как создавать запись в Itunes Connect") перед отправкой. После того, как Вы подписали свое приложение Вы сможете загрузить его в Itunes Connect для обработки, убедитесь, что Вы создали [запись](https://developer.apple.com/library/ios/documentation/LanguagesUtilities/Conceptual/iTunesConnect_Guide/Chapters/CreatingiTunesConnectRecord.html "ссылка на показ как создавать запись в Itunes Connect") перед отправкой.
## Объяснение использования 'temporary-exception' ## Объяснение использования 'temporary-exception'
Когда песочница Apple временно исключила ваше приложение, согласно [документации](https://developer.apple.com/library/mac/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/AppSandboxTemporaryExceptionEntitlements.html "Документация по исключениям") вам нужно объяснить насколько это важное исключение: Когда песочница Apple временно исключила ваше приложение, согласно [документации](https://developer.apple.com/library/mac/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/AppSandboxTemporaryExceptionEntitlements.html "Документация по исключениям") Вам нужно объяснить насколько это важное исключение:
>Примечание: если вы временно исключаете свое приложение, обязательно прочитайте и выполните рекомендации по правам на исключение. >Примечание: если Вы временно исключаете свое приложение, обязательно прочитайте и выполните рекомендации по правам на исключение.
>которые предоставляются в Itunes Connect. Самое важное указать почему ваше приложение должно быть исключенно. >которые предоставляются в Itunes Connect. Самое важное указать почему ваше приложение должно быть исключенно.
Вы можете объяснить, что ваше приложение построено на основе браузера Chromium, который использует Mach из-за его мульти-процесс архитектуры. Но есть еще вероятность, что ваше приложение не удалось проверить именно из-за этого. Вы можете объяснить, что ваше приложение построено на основе браузера Chromium, который использует Mach из-за его мультипроцессной архитектуры. Но есть еще вероятность, что ваше приложение не удалось проверить именно из-за этого.
## Отправка приложения на проверку ## Отправка приложения на проверку
Следующие шаги описаны в официальной [документации](https://developer.apple.com/library/ios/documentation/LanguagesUtilities/Conceptual/iTunesConnect_Guide/Chapters/SubmittingTheApp.html "Официальная статья по отправке приложения на проверку") Следующие шаги описаны в официальной [документации](https://developer.apple.com/library/ios/documentation/LanguagesUtilities/Conceptual/iTunesConnect_Guide/Chapters/SubmittingTheApp.html "Официальная статья по отправке приложения на проверку")
#Ограничения в Mac App Store # Ограничения в Mac App Store
Для того чтобы удовлетворить всем просьбам SandBox App Store , некотоыре из модулей были отключены: Для того чтобы удовлетворить всем просьбам SandBox App Store, некоторые из модулей были отключены:
- crashReporter - crashReporter
- autoUpdater - autoUpdater
и следующие проблемы были несколько изменены:
А также следующие проблемы были несколько изменены:
- Захват видео на некоторых машинах может не работать - Захват видео на некоторых машинах может не работать
- Некоторые специалньые возможности могут не работать - Некоторые специальные возможности могут не работать
- Приложения не будут в курсе изменения DNS - Приложения не будут в курсе изменения DNS
Так же из за использования SandBox App Store некоторые возможности могут быть не доступны или ограничены, подробнее о ограничениях
Также из-за использования SandBox App Store некоторые возможности могут быть не доступны или ограничены, подробнее о ограничениях
Вы можете прочитать в [документации](https://developer.apple.com/app-sandboxing/ "Ссылка на ограничения в SandBox AppStore") Вы можете прочитать в [документации](https://developer.apple.com/app-sandboxing/ "Ссылка на ограничения в SandBox AppStore")
# Криптографические алгоритмы котоыре использует Electron # Криптографические алгоритмы которые использует Electron
Смотря в какой стране и городе Вы находитесь Apple может потребовать от Вас задокументировать алгоритмы криптографии котоыре вы используете Смотря в какой стране и городе Вы находитесь, Apple может потребовать от Вас задокументировать алгоритмы криптографии которые Вы используете
и если потребуется то попросит предоставить Вас копию регистрации вашего алгоритма шифрования. и если потребуется, то попросит Вас предоставить копию регистрации вашего алгоритма шифрования.
Electron использует следующие алгоритмы шифрования: Electron использует следующие алгоритмы шифрования:
- AES - NIST SP 800-38A, NIST SP 800-38D, RFC 3394 - AES - NIST SP 800-38A, NIST SP 800-38D, RFC 3394
@ -141,5 +148,5 @@ Electron использует следующие алгоритмы шифров
- RC5 - http://people.csail.mit.edu/rivest/Rivest-rc5rev.pdf - RC5 - http://people.csail.mit.edu/rivest/Rivest-rc5rev.pdf
- RIPEMD - ISO/IEC 10118-3 - RIPEMD - ISO/IEC 10118-3
Если Вы используете необычный алгоритм, то вот статья о том как получить разрешение на использование собственного алгоритма шифрования в Если Вы используете необычный алгоритм, то вот статья о том, как получить разрешение на использование собственного алгоритма шифрования в
рамках закона США - [статья](https://pupeno.com/2015/12/15/legally-submit-app-apples-app-store-uses-encryption-obtain-ern/ "Статья о том как получить разрешение на свой алгоритм шифрования") рамках закона США - [статья](https://pupeno.com/2015/12/15/legally-submit-app-apples-app-store-uses-encryption-obtain-ern/ "Статья о том как получить разрешение на свой алгоритм шифрования")

View file

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

View file

@ -1,15 +1,24 @@
Платформы поддерживаемые Electron: # Платформы поддерживаемые Electron:
#OS X Следующие платформы поддерживаются Electron:
Поддерживает только 64-ых битные OS X. Минимально поддерживаемой версией является OS X 10.9
#Windows ### OS X
Поддерживаются операционные систем Windows 7 и выше, старые операционные системы не поддерживаются (и не работают).
Поддерживаются платформы на x86 и amd64 (64-разрядная) для Windows. Будьте внимательны, что ARM Windows не поддерживается на данный момент. Поддерживает только 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 * Ubuntu 12.04 и выше
- Debian 8 * Fedora 21
* Debian 8

View file

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

View file

@ -1,26 +1,36 @@
# Mac App Store 应用提交向导 # Mac App Store 应用提交向导
自从 v0.34.0, Electron 就允许提交应用包到 Mac App Store 自从 v0.34.0Electron 就允许提交应用包到 Mac App Store
(MAS) . 这个向导提供的信息有 : 如何提交应用和 MAS 构建的限制. (MAS)。这个向导提供的信息有: 如何提交应用和 MAS 构建的限制。
__注意:__ 从 v0.36.0,当应用成为沙箱之后,会有一个 bug 阻止 GPU 进程开启 , 所以在这个 bug 修复之前,建议使用 v0.35.x .更多查看 [issue #3871][issue-3871] . __注意:__ 提交应用到 Mac App Store 需要参加 [Apple Developer
Program][developer-program],这需要额外花费。
__注意:__ 提交应用到 Mac App Store 需要参加 [Apple Developer
Program][developer-program] , 这需要花钱.
## 如何提交 ## 如何提交
下面步骤介绍了一个简单的提交应用到商店方法.然而,这些步骤不能保证你的应用被 Apple 接受;你仍然需要阅读 Apple 的 [Submitting Your App][submitting-your-app] 关于如何满足 Mac App Store 要求的向导. 下面步骤介绍了一个简单的提交应用到商店方法然而,这些步骤不能保证你的应用被 Apple 接受;你仍然需要阅读 Apple 的 [Submitting Your App][submitting-your-app] 关于如何满足 Mac App Store 要求的向导
### 获得证书 ### 获得证书
为了提交应用到商店,首先需要从 Apple 获得一个证书.可以遵循 [existing guides][nwjs-guide]. 为了提交应用到商店,首先需要从 Apple 获得一个证书。可以遵循 [现有向导][nwjs-guide]。
### App 签名 ### 软件签名
获得证书之后,你可以使用 [Application Distribution](application-distribution.md) 打包你的应用, 然后前往提交你的应用.这个步骤基本上和其他程序一样,但是这 key 一个个的标识 Electron 的每个依赖. 获得证书之后,你可以使用 [应用部署](application-distribution.md) 打包你的应用,之后进行提交。
首先你需要准备2个授权文件 . 首先,你需要在软件包内的 `Info.plist` 中增添一项 `ElectronTeamID`
```xml
<plist version="1.0">
<dict>
...
<key>ElectronTeamID</key>
<string>TEAM_ID</string>
</dict>
</plist>
```
之后你需要准备2个授权文件。
`child.plist`: `child.plist`:
@ -46,67 +56,76 @@ Program][developer-program] , 这需要花钱.
<dict> <dict>
<key>com.apple.security.app-sandbox</key> <key>com.apple.security.app-sandbox</key>
<true/> <true/>
<key>com.apple.security.application-groups</key>
<string>TEAM_ID.your.bundle.id</string>
</dict> </dict>
</plist> </plist>
``` ```
然后使用下面的脚本标识你的应用 : 请注意上述 `TEAM_ID` 对应开发者账户的 Team ID`your.bundle.id` 对应软件打包时使用的 Bundle ID。
然后使用下面的脚本签名你的应用:
```bash ```bash
#!/bin/bash #!/bin/bash
# Name of your app. # 应用名称
APP="YourApp" APP="YourApp"
# The path of you app to sign. # 应用路径
APP_PATH="/path/to/YouApp.app" APP_PATH="/path/to/YourApp.app"
# The path to the location you want to put the signed package. # 生成安装包路径
RESULT_PATH="~/Desktop/$APP.pkg" RESULT_PATH="~/Desktop/$APP.pkg"
# The name of certificates you requested. # 开发者应用签名证书
APP_KEY="3rd Party Mac Developer Application: Company Name (APPIDENTITY)" APP_KEY="3rd Party Mac Developer Application: Company Name (APPIDENTITY)"
INSTALLER_KEY="3rd Party Mac Developer Installer: Company Name (APPIDENTITY)" INSTALLER_KEY="3rd Party Mac Developer Installer: Company Name (APPIDENTITY)"
# 授权文件路径
CHILD_PLIST="/path/to/child.plist"
PARENT_PLIST="/path/to/parent.plist"
FRAMEWORKS_PATH="$APP_PATH/Contents/Frameworks" FRAMEWORKS_PATH="$APP_PATH/Contents/Frameworks"
codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Electron Framework.framework/Versions/A" codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/Electron Framework.framework/Versions/A/Electron Framework"
codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper.app/" codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/Electron Framework.framework/Versions/A/Libraries/libffmpeg.dylib"
codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper EH.app/" codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/Electron Framework.framework/Versions/A/Libraries/libnode.dylib"
codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper NP.app/" codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/Electron Framework.framework"
if [ -d "$FRAMEWORKS_PATH/Squirrel.framework/Versions/A" ]; then codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/$APP Helper.app/Contents/MacOS/$APP Helper"
# Signing a non-MAS build. codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/$APP Helper.app/"
codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Mantle.framework/Versions/A" codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/$APP Helper EH.app/Contents/MacOS/$APP Helper EH"
codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/ReactiveCocoa.framework/Versions/A" codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/$APP Helper EH.app/"
codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Squirrel.framework/Versions/A" codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/$APP Helper NP.app/Contents/MacOS/$APP Helper NP"
fi codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/$APP Helper NP.app/"
codesign -fs "$APP_KEY" --entitlements parent.plist "$APP_PATH" codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$APP_PATH/Contents/MacOS/$APP"
codesign -s "$APP_KEY" -f --entitlements "$PARENT_PLIST" "$APP_PATH"
productbuild --component "$APP_PATH" /Applications --sign "$INSTALLER_KEY" "$RESULT_PATH" productbuild --component "$APP_PATH" /Applications --sign "$INSTALLER_KEY" "$RESULT_PATH"
``` ```
如果你是 OS X 下的应用沙箱使用新手,应当仔细阅读 Apple 的 [Enabling App Sandbox][enable-app-sandbox] 来有一点基础,然后向授权文件添加你的应用需要的许可 keys .
如果你是 OS X 下的应用沙箱使用新手,应当仔细阅读 Apple 的 [Enabling App Sandbox][enable-app-sandbox] 了解一些基础,然后在授权文件 (entitlements files) 内添加你的应用需要的许可。
### 上传你的应用并检查提交 ### 上传你的应用并检查提交
在签名应用之后,可以使用应用 Loader 来上传到 iTunes 链接处理 , 确保在上传之前你已经 [created a record][create-record]. 然后你能 [submit your app for review][submit-for-review]. 在签名应用之后,你可以使用 Application Loader 上传软件到 iTunes Connect 进行处理。请确保在上传之前你已经 [创建应用记录][create-record],再 [提交进行审核][submit-for-review]。
## MAS构建限制 ## MAS 构建限制
为了让你的应用沙箱满足所有条件,在 MAS 构建的时候,下面的模块被禁用了 : 为了让你的应用满足沙箱的所有条件,在 MAS 构建的时候,下面的模块已被禁用:
* `crashReporter` * `crashReporter`
* `autoUpdater` * `autoUpdater`
并且下面的行为也改变了: 并且下面的行为也改变了:
* 一些机子的视频采集功能无效. * 一些视频采集功能无效。
* 某些特征不可访问. * 某些辅助功能无法访问。
* Apps 不可识别 DNS 改变. * 应用无法检测 DNS 变化。
也由于应用沙箱的使用方法,应用可以访问的资源被严格限制了 ; 阅读更多信息 [App Sandboxing][app-sandboxing] . 也由于应用沙箱的使用方法,应用可以访问的资源被严格限制了;阅读更多信息 [App Sandboxing][app-sandboxing]。
## Electron 使用的加密算法 ## Electron 使用的加密算法
取决于你所在地方的国家和地区 , Mac App Store 或许需要记录你应用的加密算法 , 甚至要求你提交一个 U.S 加密注册(ERN) 许可的复印件. 取决于你所在地方的国家和地区Mac App Store 或许需要记录你应用的加密算法,甚至要求你提交一个 U.S. 加密注册 (ERN) 许可的复印件。
Electron 使用下列加密算法: Electron 使用下列加密算法
* AES - [NIST SP 800-38A](http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf), [NIST SP 800-38D](http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf), [RFC 3394](http://www.ietf.org/rfc/rfc3394.txt) * AES - [NIST SP 800-38A](http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf), [NIST SP 800-38D](http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf), [RFC 3394](http://www.ietf.org/rfc/rfc3394.txt)
* HMAC - [FIPS 198-1](http://csrc.nist.gov/publications/fips/fips198-1/FIPS-198-1_final.pdf) * HMAC - [FIPS 198-1](http://csrc.nist.gov/publications/fips/fips198-1/FIPS-198-1_final.pdf)
@ -132,9 +151,9 @@ Electron 使用下列加密算法:
* RC5 - http://people.csail.mit.edu/rivest/Rivest-rc5rev.pdf * RC5 - http://people.csail.mit.edu/rivest/Rivest-rc5rev.pdf
* RIPEMD - [ISO/IEC 10118-3](http://webstore.ansi.org/RecordDetail.aspx?sku=ISO%2FIEC%2010118-3:2004) * RIPEMD - [ISO/IEC 10118-3](http://webstore.ansi.org/RecordDetail.aspx?sku=ISO%2FIEC%2010118-3:2004)
如何获取 ERN 许可, 可看这篇文章: [How to legally 如何获取 ERN 许可, 可看这篇文章[How to legally
submit an app to Apples App Store when it uses encryption (or how to obtain an submit an app to Apples App Store when it uses encryption (or how to obtain an
ERN)][ern-tutorial]. ERN)][ern-tutorial]
[developer-program]: https://developer.apple.com/support/compare-memberships/ [developer-program]: https://developer.apple.com/support/compare-memberships/
[submitting-your-app]: https://developer.apple.com/library/mac/documentation/IDEs/Conceptual/AppDistributionGuide/SubmittingYourApp/SubmittingYourApp.html [submitting-your-app]: https://developer.apple.com/library/mac/documentation/IDEs/Conceptual/AppDistributionGuide/SubmittingYourApp/SubmittingYourApp.html

View file

@ -1,7 +1,6 @@
# 快速入门 # 快速入门
## 简介 Electron 可以让你使用纯 JavaScript 调用丰富的原生 APIs 来创造桌面应用。你可以把它看作一个专注于桌面应用的 Node.js 的变体,而不是 Web 服务器。
Electron 可以让你使用纯 JavaScript 调用丰富的原生 APIs 来创造桌面应用。你可以把它看作是专注于桌面应用而不是 web 服务器的io.js 的一个变体。
这不意味着 Electron 是绑定了 GUI 库的 JavaScript。相反Electron 使用 web 页面作为它的 GUI所以你能把它看作成一个被 JavaScript 控制的,精简版的 Chromium 浏览器。 这不意味着 Electron 是绑定了 GUI 库的 JavaScript。相反Electron 使用 web 页面作为它的 GUI所以你能把它看作成一个被 JavaScript 控制的,精简版的 Chromium 浏览器。
@ -14,13 +13,13 @@ Electron 可以让你使用纯 JavaScript 调用丰富的原生 APIs 来创造
在一般浏览器中网页通常会在沙盒环境下运行并且不允许访问原生资源。然而Electron 用户拥有在网页中调用 io.js 的 APIs 的能力,可以与底层操作系统直接交互。 在一般浏览器中网页通常会在沙盒环境下运行并且不允许访问原生资源。然而Electron 用户拥有在网页中调用 io.js 的 APIs 的能力,可以与底层操作系统直接交互。
## 主进程与渲染进程的区别 ## 主进程与渲染进程的区别
主进程使用 BrowserWindow 实例创建页。每个 BrowserWindow 实例都在自己的渲染进程里运行着一个网页。当一个 BrowserWindow 实例被销毁后,相应的渲染进程也会被终止。 主进程使用 `BrowserWindow` 实例创建页。每个 `BrowserWindow` 实例都在自己的渲染进程里运行页。当一个 `BrowserWindow` 实例被销毁后,相应的渲染进程也会被终止。
主进程管理所有页面和与之对应的渲染进程。每个渲染进程都是相互独立的,并且只关心他们自己的页。 主进程管理所有页面和与之对应的渲染进程。每个渲染进程都是相互独立的,并且只关心他们自己的页
由于在页里管理原生 GUI 资源是非常危险而且容易造成资源泄露,所以在页面调用 GUI 相关的 APIs 是不被允许的。如果你想在网页里使用 GUI 操作,其对应的渲染进程必须与主进程进行通讯,请求主进程进行相关的 GUI 操作。 由于在页里管理原生 GUI 资源是非常危险而且容易造成资源泄露,所以在页面调用 GUI 相关的 APIs 是不被允许的。如果你想在网页里使用 GUI 操作,其对应的渲染进程必须与主进程进行通讯,请求主进程进行相关的 GUI 操作。
在 Electron我们提供用于在主进程与渲染进程之间通讯的 [ipc][1] 模块。并且也有一个远程进程调用风格的通讯模块 [remote][2]。 在 Electron我们提供几种方法用于主进程和渲染进程之间的通讯。像 [ipcRenderer][1] 和 [ipcMain][2] 模块用于发送消息, [remote][3] 模块用于 RPC 方式通讯。这些内容都可以在一个 FAQ 中查看 [how to share data between web pages][4]。
# 打造你第一个 Electron 应用 # 打造你第一个 Electron 应用
大体上,一个 Electron 应用的目录结构如下: 大体上,一个 Electron 应用的目录结构如下:
@ -42,53 +41,72 @@ your-app/
`main.js` 应该用于创建窗口和处理系统事件,一个典型的例子如下: `main.js` 应该用于创建窗口和处理系统事件,一个典型的例子如下:
```javascript ```javascript
var app = require('app'); // 控制应用生命周期的模块。 const electron = require('electron');
var BrowserWindow = require('browser-window'); // 创建原生浏览器窗口的模块 // 控制应用生命周期的模块。
const {app} = electron;
// 创建原生浏览器窗口的模块。
const {BrowserWindow} = electron;
// 保持一个对于 window 对象的全局引用,不然,当 JavaScript 被 GC // 保持一个对于 window 对象的全局引用,如果你不这样做
// window 会被自动地关闭 // 当 JavaScript 对象被垃圾回收, window 会被自动地关闭
var mainWindow = null; let mainWindow;
// 当所有窗口被关闭了,退出。 function createWindow() {
app.on('window-all-closed', function() { // 创建浏览器窗口。
// 在 OS X 上,通常用户在明确地按下 Cmd + Q 之前 mainWindow = new BrowserWindow({width: 800, height: 600});
// 应用会保持活动状态
if (process.platform != 'darwin') { // 加载应用的 index.html。
mainWindow.loadURL(`file://${__dirname}/index.html`);
// 启用开发工具。
mainWindow.webContents.openDevTools();
// 当 window 被关闭,这个事件会被触发。
mainWindow.on('closed', () => {
// 取消引用 window 对象,如果你的应用支持多窗口的话,
// 通常会把多个 window 对象存放在一个数组里面,
// 与此同时,你应该删除相应的元素。
mainWindow = null;
});
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow);
// Quit when all windows are closed.
app.on('window-all-closed', () => {
// On OS X it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit(); app.quit();
} }
}); });
// 当 Electron 完成了初始化并且准备创建浏览器窗口的时候 app.on('activate', () => {
// 这个方法就被调用 // On OS X it's common to re-create a window in the app when the
app.on('ready', function() { // dock icon is clicked and there are no other windows open.
// 创建浏览器窗口。 if (mainWindow === null) {
mainWindow = new BrowserWindow({width: 800, height: 600}); createWindow();
}
// 加载应用的 index.html
mainWindow.loadURL('file://' + __dirname + '/index.html');
// 打开开发工具
mainWindow.openDevTools();
// 当 window 被关闭,这个事件会被发出
mainWindow.on('closed', function() {
// 取消引用 window 对象,如果你的应用支持多窗口的话,
// 通常会把多个 window 对象存放在一个数组里面,
// 但这次不是。
mainWindow = null;
});
}); });
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
``` ```
最后,你想展示的 `index.html` 最后,你想展示的 `index.html`
```html ```html
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="UTF-8">
<title>Hello World!</title> <title>Hello World!</title>
</head> </head>
<body> <body>
<h1>Hello World!</h1> <h1>Hello World!</h1>
We are using io.js <script>document.write(process.version)</script> We are using node <script>document.write(process.versions.node)</script>,
Chrome <script>document.write(process.versions.chrome)</script>,
and Electron <script>document.write(process.versions.electron)</script>. and Electron <script>document.write(process.versions.electron)</script>.
</body> </body>
</html> </html>
@ -97,8 +115,9 @@ app.on('ready', function() {
# 运行你的应用 # 运行你的应用
一旦你创建了最初的 `main.js` `index.html``package.json` 这几个文件,你可能会想尝试在本地运行并测试,看看是不是和期望的那样正常运行。 一旦你创建了最初的 `main.js` `index.html``package.json` 这几个文件,你可能会想尝试在本地运行并测试,看看是不是和期望的那样正常运行。
## electron-prebuild ## electron-prebuilt
如果你已经用 `npm` 全局安装了 `electron-prebuilt`,你只需要按照如下方式直接运行你的应用: [electron-prebuilt][5] 是一个 `npm` 模块,包含所使用的 Electron 预编译版本。
如果你已经用 `npm` 全局安装了它,你只需要按照如下方式直接运行你的应用:
```bash ```bash
electron . electron .
``` ```
@ -121,13 +140,32 @@ $ ./electron/electron your-app/
```bash ```bash
$ ./Electron.app/Contents/MacOS/Electron your-app/ $ ./Electron.app/Contents/MacOS/Electron your-app/
``` ```
`Electron.app` 里面是 Electron 发布包,你可以在[这里][3]下载到。 `Electron.app` 里面是 Electron 发布包,你可以在 [这里][6] 下载到。
# 以发行版本运行 # 以发行版本运行
在你完成了你的应用后,你可以按照[应用部署][4]指导发布一个版本,并且以已经打包好的形式运行应用。 在你完成了你的应用后,你可以按照 [应用部署][7] 指导发布一个版本,并且以已经打包好的形式运行应用。
# 参照下面例子
复制并且运行这个库 [electron/electron-quick-start][8]。
[1]: https://github.com/electron/electron/blob/master/docs-translations/zh-CN/api/ipc-main-process.md *注意:*运行时需要你的系统已经安装了 [Git][9] 和 [Node.js][10](包含 [npm][11])。
[2]: https://github.com/electron/electron/blob/master/docs-translations/zh-CN/api/remote.md
[3]: https://github.com/electron/electron/releases ```bash
[4]: https://github.com/electron/electron/blob/master/docs-translations/zh-CN/tutorial/application-distribution.md # Clone the repository
$ git clone https://github.com/electron/electron-quick-start
# Go into the repository
$ cd electron-quick-start
# Install dependencies and run the app
$ npm install && npm start
```
[1]: https://github.com/electron/electron/blob/v1.1.3/docs/api/ipc-renderer.md
[2]: https://github.com/electron/electron/blob/v1.1.3/docs/api/ipc-main.md
[3]: https://github.com/electron/electron/blob/v1.1.3/docs/api/remote.md
[4]: https://github.com/electron/electron/blob/v1.1.3/docs/faq/electron-faq.md#how-to-share-data-between-web-pages
[5]: https://github.com/electron-userland/electron-prebuilt
[6]: https://github.com/electron/electron/releases
[7]: https://github.com/electron/electron/blob/v1.1.3/docs/tutorial/application-distribution.md
[8]: https://github.com/electron/electron-quick-start
[9]: https://git-scm.com/
[10]: https://nodejs.org/en/download/
[11]: https://www.npmjs.com/

View file

@ -346,6 +346,7 @@ You can request the following paths by the name:
* `music` Directory for a user's music. * `music` Directory for a user's music.
* `pictures` Directory for a user's pictures. * `pictures` Directory for a user's pictures.
* `videos` Directory for a user's videos. * `videos` Directory for a user's videos.
* `pepperFlashSystemPlugin` Full path to the system version of the Pepper Flash plugin.
### `app.setPath(name, path)` ### `app.setPath(name, path)`
@ -561,6 +562,12 @@ Imports the certificate in pkcs12 format into the platform certificate store.
`callback` is called with the `result` of import operation, a value of `0` `callback` is called with the `result` of import operation, a value of `0`
indicates success while any other value indicates failure according to chromium [net_error_list](https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h). indicates success while any other value indicates failure according to chromium [net_error_list](https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h).
### `app.disableHardwareAcceleration()`
Disables hardware acceleration for current app.
This method can only be called before app is ready.
### `app.commandLine.appendSwitch(switch[, value])` ### `app.commandLine.appendSwitch(switch[, value])`
Append a switch (with optional `value`) to Chromium's command line. Append a switch (with optional `value`) to Chromium's command line.

View file

@ -175,9 +175,13 @@ The `webPreferences` option is an object that can have following properties:
* `scrollBounce` Boolean - Enables scroll bounce (rubber banding) effect on * `scrollBounce` Boolean - Enables scroll bounce (rubber banding) effect on
OS X. Default is `false`. OS X. Default is `false`.
* `blinkFeatures` String - A list of feature strings separated by `,`, like * `blinkFeatures` String - A list of feature strings separated by `,`, like
`CSSVariables,KeyboardEventKey`. The full list of supported feature strings `CSSVariables,KeyboardEventKey` to enable. The full list of supported feature
can be found in the [setFeatureEnabledFromString][blink-feature-string] strings can be found in the [RuntimeEnabledFeatures.in][blink-feature-string]
function. file.
* `disableBlinkFeatures` String - A list of feature strings separated by `,`,
like `CSSVariables,KeyboardEventKey` to disable. The full list of supported
feature strings can be found in the
[RuntimeEnabledFeatures.in][blink-feature-string] file.
* `defaultFontFamily` Object - Sets the default font for the font-family. * `defaultFontFamily` Object - Sets the default font for the font-family.
* `standard` String - Defaults to `Times New Roman`. * `standard` String - Defaults to `Times New Roman`.
* `serif` String - Defaults to `Times New Roman`. * `serif` String - Defaults to `Times New Roman`.
@ -380,7 +384,11 @@ Find a window according to its ID.
Adds DevTools extension located at `path`, and returns extension's name. Adds DevTools extension located at `path`, and returns extension's name.
The extension will be remembered so you only need to call this API once, this The extension will be remembered so you only need to call this API once, this
API is not for programming use. API is not for programming use. If you try to add an extension that has already
been loaded, this method will not return and instead log a warning to the
console.
Method will also not return if the extension's manifest is missing or incomplete.
### `BrowserWindow.removeDevToolsExtension(name)` ### `BrowserWindow.removeDevToolsExtension(name)`
@ -877,7 +885,7 @@ The `flags` is an array that can include following `String`s:
### `win.showDefinitionForSelection()` _OS X_ ### `win.showDefinitionForSelection()` _OS X_
Shows pop-up dictionary that searches the selected word on the page. Same as `webContents.showDefinitionForSelection()`.
### `win.setIcon(icon)` _Windows_ _Linux_ ### `win.setIcon(icon)` _Windows_ _Linux_
@ -924,10 +932,14 @@ Returns whether the window is visible on all workspaces.
**Note:** This API always returns false on Windows. **Note:** This API always returns false on Windows.
### `win.setIgnoreMouseEvents(ignore)` _OS X_ ### `win.setIgnoreMouseEvents(ignore)`
* `ignore` Boolean * `ignore` Boolean
Ignore all moused events that happened in the window. Makes the window ignore all mouse events.
[blink-feature-string]: https://code.google.com/p/chromium/codesearch#chromium/src/out/Debug/gen/blink/platform/RuntimeEnabledFeatures.cpp&sq=package:chromium&type=cs&l=576 All mouse events happened in this window will be passed to the window below
this window, but if this window has focus, it will still receive keyboard
events.
[blink-feature-string]: https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.in

View file

@ -2,49 +2,70 @@
> Control file downloads from remote sources. > Control file downloads from remote sources.
`DownloadItem` is an EventEmitter that represents a download item in Electron. `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 It is used in `will-download` event of `Session` class, and allows users to
control the download item. control the download item.
```javascript ```javascript
// In the main process. // In the main process.
win.webContents.session.on('will-download', (event, item, webContents) => { win.webContents.session.on('will-download', (event, item, webContents) => {
// Set the save path, making Electron not to prompt a save dialog. // Set the save path, making Electron not to prompt a save dialog.
item.setSavePath('/tmp/save.pdf'); item.setSavePath('/tmp/save.pdf')
console.log(item.getMimeType());
console.log(item.getFilename()); item.on('updated', (event, state) => {
console.log(item.getTotalBytes()); if (state === 'interrupted') {
item.on('updated', () => { console.log('Download is interrupted but can be resumed')
console.log('Received bytes: ' + item.getReceivedBytes()); } else if (state === 'progressing') {
}); if (item.isPaused()) {
item.on('done', (e, state) => { console.log('Download is paused')
if (state === 'completed') { } else {
console.log('Download successfully'); console.log(`Received bytes: ${item.getReceivedBytes()}`)
} else { }
console.log('Download is cancelled or interrupted that can\'t be resumed');
} }
}); })
}); item.once('done', (event, state) => {
if (state === 'completed') {
console.log('Download successfully')
} else {
console.log(`Download failed: ${state}`)
}
})
})
``` ```
## Events ## Events
### Event: 'updated' ### Event: 'updated'
Emits when the `downloadItem` gets updated. Returns:
### Event: 'done'
* `event` Event * `event` Event
* `state` String * `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 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
Emitted when the download is in a terminal state. This includes a completed
download, a cancelled download(via `downloadItem.cancel()`), and interrupted download, a cancelled download(via `downloadItem.cancel()`), and interrupted
download that can't be resumed. 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 ## Methods
The `downloadItem` object has the following 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. Pauses the download.
### `downloadItem.isPaused()`
Returns whether the download is paused.
### `downloadItem.resume()` ### `downloadItem.resume()`
Resumes the download that has been paused. Resumes the download that has been paused.
### `downloadItem.canResume()`
Resumes whether the download can resume.
### `downloadItem.cancel()` ### `downloadItem.cancel()`
Cancels the download operation. 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 Returns a `String` represents the Content-Disposition field from the response
header. 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.

View file

@ -14,8 +14,8 @@ To create a frameless window, you need to set `frame` to `false` in
```javascript ```javascript
const {BrowserWindow} = require('electron'); const {BrowserWindow} = require('electron')
let win = new BrowserWindow({width: 800, height: 600, frame: false}); let win = new BrowserWindow({width: 800, height: 600, frame: false})
``` ```
### Alternatives on OS X ### Alternatives on OS X
@ -28,7 +28,7 @@ the window controls ("traffic lights") for standard window actions.
You can do so by specifying the new `titleBarStyle` option: You can do so by specifying the new `titleBarStyle` option:
```javascript ```javascript
let win = new BrowserWindow({titleBarStyle: 'hidden'}); let win = new BrowserWindow({titleBarStyle: 'hidden'})
``` ```
## Transparent window ## Transparent window
@ -37,7 +37,7 @@ By setting the `transparent` option to `true`, you can also make the frameless
window transparent: window transparent:
```javascript ```javascript
let win = new BrowserWindow({transparent: true, frame: false}); let win = new BrowserWindow({transparent: true, frame: false})
``` ```
### Limitations ### Limitations
@ -59,6 +59,16 @@ let win = new BrowserWindow({transparent: true, frame: false});
Linux. Linux.
* On Mac the native window shadow will not be shown on a transparent window. * On Mac the native window shadow will not be shown on a transparent window.
## Click-through window
To create a click-through window, i.e. making the window ignore all mouse
events, you can call the [win.setIgnoreMouseEvents(ignore)][ignore-mouse-events]
API:
```javascript
win.setIgnoreMouseEvents(true)
```
## Draggable region ## Draggable region
By default, the frameless window is non-draggable. Apps need to specify By default, the frameless window is non-draggable. Apps need to specify
@ -108,3 +118,5 @@ On some platforms, the draggable area will be treated as a non-client frame, so
when you right click on it a system menu will pop up. To make the context menu when you right click on it a system menu will pop up. To make the context menu
behave correctly on all platforms you should never use a custom context menu on behave correctly on all platforms you should never use a custom context menu on
draggable areas. draggable areas.
[ignore-mouse-events]: browser-window.md#winsetignoremouseeventsignore

View file

@ -46,7 +46,9 @@ The `role` property can have following values:
* `cut` * `cut`
* `copy` * `copy`
* `paste` * `paste`
* `pasteandmatchstyle`
* `selectall` * `selectall`
* `delete`
* `minimize` - Minimize current window * `minimize` - Minimize current window
* `close` - Close current window * `close` - Close current window

View file

@ -103,6 +103,12 @@ Creates an empty `nativeImage` instance.
Creates a new `nativeImage` instance from a file located at `path`. Creates a new `nativeImage` instance from a file located at `path`.
```javascript
const nativeImage = require('electron').nativeImage;
let image = nativeImage.createFromPath('/Users/somebody/images/icon.png');
```
### `nativeImage.createFromBuffer(buffer[, scaleFactor])` ### `nativeImage.createFromBuffer(buffer[, scaleFactor])`
* `buffer` [Buffer][buffer] * `buffer` [Buffer][buffer]
@ -121,12 +127,6 @@ Creates a new `nativeImage` instance from `dataURL`.
The following methods are available on instances of `nativeImage`: The following methods are available on instances of `nativeImage`:
```javascript
const nativeImage = require('electron').nativeImage;
let image = nativeImage.createFromPath('/Users/somebody/images/icon.png');
```
### `image.toPng()` ### `image.toPng()`
Returns a [Buffer][buffer] that contains the image's `PNG` encoded data. Returns a [Buffer][buffer] that contains the image's `PNG` encoded data.

View file

@ -80,7 +80,7 @@ exports.withLocalCallback = () => {
```javascript ```javascript
// renderer process // renderer process
const mapNumbers = require('remote').require('./mapNumbers'); const mapNumbers = require('electron').remote.require('./mapNumbers');
const withRendererCb = mapNumbers.withRendererCallback(x => x + 1); const withRendererCb = mapNumbers.withRendererCallback(x => x + 1);

View file

@ -9,44 +9,42 @@ emitted (by invoking or requiring it).
**Note:** In the renderer / DevTools, `window.screen` is a reserved DOM **Note:** In the renderer / DevTools, `window.screen` is a reserved DOM
property, so writing `let {screen} = require('electron')` will not work. property, so writing `let {screen} = require('electron')` will not work.
In our examples below, we use `electronScreen` as the variable name instead.
An example of creating a window that fills the whole screen: An example of creating a window that fills the whole screen:
```javascript ```javascript
const {app, BrowserWindow, screen: electronScreen} = require('electron'); const electron = require('electron')
const {app, BrowserWindow} = electron
let win; let win
app.on('ready', () => { app.on('ready', () => {
const {width, height} = electronScreen.getPrimaryDisplay().workAreaSize; const {width, height} = electron.screen.getPrimaryDisplay().workAreaSize
win = new BrowserWindow({width, height}); win = new BrowserWindow({width, height})
}); });
``` ```
Another example of creating a window in the external display: Another example of creating a window in the external display:
```javascript ```javascript
const {app, BrowserWindow, screen: electronScreen} = require('electron'); const electron = require('electron')
const {app, BrowserWindow} = require('electron')
let win; let win
app.on('ready', () => { app.on('ready', () => {
let displays = electronScreen.getAllDisplays(); let displays = electron.screen.getAllDisplays()
let externalDisplay = null; let externalDisplay = displays.find((display) => {
for (let i in displays) { return display.bounds.x !== 0 || display.bounds.y !== 0
if (displays[i].bounds.x !== 0 || displays[i].bounds.y !== 0) { })
externalDisplay = displays[i];
break;
}
}
if (externalDisplay) { if (externalDisplay) {
win = new BrowserWindow({ win = new BrowserWindow({
x: externalDisplay.bounds.x + 50, x: externalDisplay.bounds.x + 50,
y: externalDisplay.bounds.y + 50 y: externalDisplay.bounds.y + 50
}); })
} }
}); })
``` ```
## The `Display` object ## The `Display` object

View file

@ -206,7 +206,7 @@ Sets the proxy settings.
When `pacScript` and `proxyRules` are provided together, the `proxyRules` When `pacScript` and `proxyRules` are provided together, the `proxyRules`
option is ignored and `pacScript` configuration is applied. option is ignored and `pacScript` configuration is applied.
The `proxyRules` has to follow the rules bellow: The `proxyRules` has to follow the rules below:
``` ```
proxyRules = schemeProxies[";"<schemeProxies>] proxyRules = schemeProxies[";"<schemeProxies>]
@ -547,3 +547,22 @@ The `listener` will be called with `listener(details)` when an error occurs.
* `timestamp` Double * `timestamp` Double
* `fromCache` Boolean * `fromCache` Boolean
* `error` String - The error description. * `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')
})
})

View file

@ -29,12 +29,12 @@ __Platform limitations:__
* When app indicator is used on Linux, the `click` event is ignored. * When app indicator is used on Linux, the `click` event is ignored.
* On Linux in order for changes made to individual `MenuItem`s to take effect, * On Linux in order for changes made to individual `MenuItem`s to take effect,
you have to call `setContextMenu` again. For example: you have to call `setContextMenu` again. For example:
* On Windows it is recommended to use `ICO` icons to get best visual effects.
```javascript ```javascript
contextMenu.items[2].checked = false; contextMenu.items[2].checked = false;
appIcon.setContextMenu(contextMenu); appIcon.setContextMenu(contextMenu);
``` ```
* On Windows it is recommended to use `ICO` icons to get best visual effects.
If you want to keep exact same behaviors on all platforms, you should not If you want to keep exact same behaviors on all platforms, you should not
rely on the `click` event and always attach a context menu to the tray icon. rely on the `click` event and always attach a context menu to the tray icon.

View file

@ -288,6 +288,15 @@ a meta tag:
<meta name='theme-color' content='#ff0000'> <meta name='theme-color' content='#ff0000'>
``` ```
### Event: 'update-target-url'
Returns:
* `event` Event
* `url` String
Emitted when mouse moves over a link or the keyboard moves the focus to a link.
### Event: 'cursor-changed' ### Event: 'cursor-changed'
Returns: Returns:
@ -948,6 +957,10 @@ win.webContents.on('did-finish-load', () => {
}); });
``` ```
### `webContents.showDefinitionForSelection()` _OS X_
Shows pop-up dictionary that searches the selected word on the page.
## Instance Properties ## Instance Properties
`WebContents` objects also have the following properties: `WebContents` objects also have the following properties:

View file

@ -205,7 +205,17 @@ If "on", the guest page will be allowed to open new windows.
A list of strings which specifies the blink features to be enabled separated by `,`. A list of strings which specifies the blink features to be enabled separated by `,`.
The full list of supported feature strings can be found in the The full list of supported feature strings can be found in the
[setFeatureEnabledFromString][blink-feature-string] function. [RuntimeEnabledFeatures.in][blink-feature-string] file.
### `disableblinkfeatures`
```html
<webview src="https://www.github.com/" disableblinkfeatures="PreciseMemoryInfo, CSSVariables"></webview>
```
A list of strings which specifies the blink features to be disabled separated by `,`.
The full list of supported feature strings can be found in the
[RuntimeEnabledFeatures.in][blink-feature-string] file.
## Methods ## Methods
@ -480,6 +490,10 @@ Sends an input `event` to the page.
See [webContents.sendInputEvent](web-contents.md##webcontentssendinputeventevent) See [webContents.sendInputEvent](web-contents.md##webcontentssendinputeventevent)
for detailed description of `event` object. for detailed description of `event` object.
### `<webview>.showDefinitionForSelection()` _OS X_
Shows pop-up dictionary that searches the selected word on the page.
### `<webview>.getWebContents()` ### `<webview>.getWebContents()`
Returns the [WebContents](web-contents.md) associated with this `webview`. Returns the [WebContents](web-contents.md) associated with this `webview`.
@ -782,6 +796,14 @@ Emitted when a page's theme color changes. This is usually due to encountering a
<meta name='theme-color' content='#ff0000'> <meta name='theme-color' content='#ff0000'>
``` ```
### Event: 'update-target-url'
Returns:
* `url` String
Emitted when mouse moves over a link or the keyboard moves the focus to a link.
### Event: 'devtools-opened' ### Event: 'devtools-opened'
Emitted when DevTools is opened. Emitted when DevTools is opened.
@ -794,4 +816,4 @@ Emitted when DevTools is closed.
Emitted when DevTools is focused / opened. Emitted when DevTools is focused / opened.
[blink-feature-string]: https://code.google.com/p/chromium/codesearch#chromium/src/out/Debug/gen/blink/platform/RuntimeEnabledFeatures.cpp&sq=package:chromium&type=cs&l=527 [blink-feature-string]: https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.in

View file

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

View file

@ -30,7 +30,7 @@ use HTML5 APIs which are already available in browsers. Good candidates are
Or you can use the IPC system, which is specific to Electron, to store objects Or you can use the IPC system, which is specific to Electron, to store objects
in the main process as a global variable, and then to access them from the in the main process as a global variable, and then to access them from the
renderers through the `remote` module: renderers through the `remote` property of `electron` module:
```javascript ```javascript
// In the main process. // In the main process.
@ -41,12 +41,12 @@ global.sharedObject = {
```javascript ```javascript
// In page 1. // In page 1.
require('remote').getGlobal('sharedObject').someProperty = 'new value'; require('electron').remote.getGlobal('sharedObject').someProperty = 'new value';
``` ```
```javascript ```javascript
// In page 2. // In page 2.
console.log(require('remote').getGlobal('sharedObject').someProperty); console.log(require('electron').remote.getGlobal('sharedObject').someProperty);
``` ```
## My app's window/tray disappeared after a few minutes. ## My app's window/tray disappeared after a few minutes.

View file

@ -120,6 +120,24 @@ If you are new to app sandboxing under OS X, you should also read through
Apple's [Enabling App Sandbox][enable-app-sandbox] to have a basic idea, then Apple's [Enabling App Sandbox][enable-app-sandbox] to have a basic idea, then
add keys for the permissions needed by your app to the entitlements files. add keys for the permissions needed by your app to the entitlements files.
Apart from manually signing your app, you can also choose to use the
[electron-osx-sign][electron-osx-sign] module to do the job.
#### Sign Native Modules
Native modules used in your app also need to be signed. If using
electron-osx-sign, be sure to include the path to the built binaries in the
argument list:
```bash
electron-osx-sign YourApp.app YourApp.app/Contents/Resources/app/node_modules/nativemodule/build/release/nativemodule
```
Also note that native modules may have intermediate files produced which should
not be included (as they would also need to be signed). If you use
[electron-packager][electron-packager], add `--ignore=.+\.o$` to build step to
ignore these files.
### Upload Your App ### Upload Your App
After signing your app, you can use Application Loader to upload it to iTunes After signing your app, you can use Application Loader to upload it to iTunes
@ -189,6 +207,8 @@ ERN)][ern-tutorial].
[nwjs-guide]: https://github.com/nwjs/nw.js/wiki/Mac-App-Store-%28MAS%29-Submission-Guideline#first-steps [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 [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 [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 [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/ [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/ [ern-tutorial]: https://carouselapps.com/2015/12/15/legally-submit-app-apples-app-store-uses-encryption-obtain-ern/

View file

@ -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 ## Enable Flash Plugin in a `<webview>` Tag
Add `plugins` attribute to `<webview>` tag. Add `plugins` attribute to `<webview>` tag.

View file

@ -5,7 +5,7 @@ Windows Platform. The new `.appx` format does not only enable a number of new
powerful APIs like Cortana or Push Notifications, but through the Windows Store, powerful APIs like Cortana or Push Notifications, but through the Windows Store,
also simplifies installation and updating. also simplifies installation and updating.
Microsoft [developed a tool that compiles Electron apps as `.appx` packages](http://github.com/catalystcode/electron-windows-store), Microsoft [developed a tool that compiles Electron apps as `.appx` packages][electron-windows-store],
enabling developers to use some of the goodies found in the new application enabling developers to use some of the goodies found in the new application
model. This guide explains how to use it - and what the capabilities and model. This guide explains how to use it - and what the capabilities and
limitations of an Electron AppX package are. limitations of an Electron AppX package are.
@ -22,19 +22,17 @@ installation and uninstallation.
In addition, the exe is launched inside the appx model - meaning that it can use In addition, the exe is launched inside the appx model - meaning that it can use
many of the APIs available to the Universal Windows Platform. To gain even more many of the APIs available to the Universal Windows Platform. To gain even more
capabilities, an Electron app can pair up with an invisible UWP app launched capabilities, an Electron app can pair up with an invisible UWP background task
together with the `exe` - sort of launched as a sidekick to run tasks in the launched together with the `exe` - sort of launched as a sidekick to run tasks
background, receive push notifications, or to communicate with other UWP in the background, receive push notifications, or to communicate with other UWP
applications. applications.
To compile any existing Electron app, ensure that you have the following To compile any existing Electron app, ensure that you have the following
requirements: requirements:
* Windows 10 Anniversary Update - Enterprise Edition (This is build 14316 and up * Windows 10 Anniversary Update (until the update is released to the general public,
- as of May 2016, it's part of the Windows Insiders Preview) developers can use the Windows Insider Preview)
* A machine with 64 bit (x64) processor, Hardware-Assisted Virtualization, and * The Windows 10 SDK, [downloadable here][windows-sdk]
Second Level Address Translation (SLAT)
* The Windows 10 SDK, [downloadable here](https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk)
* At least Node 4 (to check, run `node -v`) * At least Node 4 (to check, run `node -v`)
Then, go and install the `electron-windows-store` CLI: Then, go and install the `electron-windows-store` CLI:
@ -43,32 +41,11 @@ Then, go and install the `electron-windows-store` CLI:
npm install -g electron-windows-store npm install -g electron-windows-store
``` ```
## Setup and Preparation ## Step 1: Package Your Electron Application
Before running the CLI for the first time, you will have to setup the "Windows Package the application using [electron-packager][electron-packager] (or a similar tool).
Desktop App Converter". This will take a few minutes, but don't worry - you only Make sure to remove `node_modules` that you don't need in your final application, since
have to do this once. Download and Desktop App Converter from any module you don't actually need will just increase your application's size.
[here](https://www.microsoft.com/en-us/download/details.aspx?id=51691). You will
receive two files: `DesktopAppConverter.zip` and `BaseImage-14316.wim`.
1. Unzip `DesktopAppConverter.zip`. From an elevated PowerShell (opened with
"run as Administrator", ensure that your systems execution policy allows us to
run everything we intend to run by calling `Set-ExecutionPolicy bypass`.
2. Then, run the installation of the Desktop App Converter, passing in the
location of the Windows base Image (downloaded as `BaseImage-14316.wim`), by
calling `.\DesktopAppConverter.ps1 -Setup -BaseImage .\BaseImage-14316.wim`.
3. If running the above command prompts you for a reboot, please restart your
machine and run the above command again after a successful restart.
Once installation succeeded, you can move on to compiling your Electron app.
## Package Your Electron Application
Package the application using
[electron-packager](https://github.com/electron-userland/electron-packager)
(or a similar tool). Make sure to remove `node_modules` that you don't need in
your final application, since any module you don't actually need will just
increase your application's size.
The output should look roughly like this: The output should look roughly like this:
@ -101,7 +78,7 @@ The output should look roughly like this:
└── xinput1_3.dll └── xinput1_3.dll
``` ```
## Running the Command Line Tool ## Step 2: Running electron-windows-store
From an elevated PowerShell (run it "as Administrator"), run From an elevated PowerShell (run it "as Administrator"), run
`electron-windows-store` with the required parameters, passing both the input `electron-windows-store` with the required parameters, passing both the input
@ -130,13 +107,54 @@ Finally, the tool can be used to create a trusted certificate on your computer
to sign the new AppX pacakge. With the signed AppX package, the CLI can also to sign the new AppX pacakge. With the signed AppX package, the CLI can also
automatically install the package on your machine. automatically install the package on your machine.
## Using the AppX Package ## Step 3: Using the AppX Package
Since the Windows Anniversary Update (codenamed Windows Redstone) has not been Since the Windows Anniversary Update (codenamed Windows Redstone) has not been
released to consumers yet, you won't be able to release your app to the Windows released to consumers yet, you won't be able to release your app to the Windows
Store until later this year - but you can already use the `Add-AppxPackage` Store until later this year - but you can already use the `Add-AppxPackage`
[PowerShell Cmdlet to install it on machines](https://technet.microsoft.com/en-us/library/hh856048.aspx) [PowerShell Cmdlet to install it on machines][add-appxpackage]
in developer or enterprise environments. in developer or enterprise environments.
Another important limitation is that the compiled AppX package still contains a Another important limitation is that the compiled AppX package still contains a
win32 executable - and will therefore not run on Xbox, HoloLens, or Phones. win32 executable - and will therefore not run on Xbox, HoloLens, or Phones.
## Optional: Add UWP Features using a BackgroundTask
You can pair your Electron app up with an invisible UWP background task that
gets to make full use of Windows 10 features - like push notifications,
Cortana integration, or live tiles.
To check out how an Electron app that uses a background task to send toast
notifications and live tiles, [check out the Microsoft-provided sample][background-task].
## Optional: Convert using Container Virtualiziation
To generate the AppX package, the `electron-windows-store` CLI uses a template
that should work for most Electron apps. However, if you are using a custom
installer, or should you experience any trouble with the generated package, you
can attempt to create a package using compilation with a Windows Container - in
that mode, the CLI will install and run your application in blank Windows Container
to determine what modifications your application is exactly doing to the operating
system.
Before running the CLI for the, you will have to setup the "Windows Desktop App
Converter". This will take a few minutes, but don't worry - you only have to do
this once. Download and Desktop App Converter from [here][app-converter].
You will receive two files: `DesktopAppConverter.zip` and `BaseImage-14316.wim`.
1. Unzip `DesktopAppConverter.zip`. From an elevated PowerShell (opened with
"run as Administrator", ensure that your systems execution policy allows us to
run everything we intend to run by calling `Set-ExecutionPolicy bypass`.
2. Then, run the installation of the Desktop App Converter, passing in the
location of the Windows base Image (downloaded as `BaseImage-14316.wim`), by
calling `.\DesktopAppConverter.ps1 -Setup -BaseImage .\BaseImage-14316.wim`.
3. If running the above command prompts you for a reboot, please restart your
machine and run the above command again after a successful restart.
Once installation succeeded, you can move on to compiling your Electron app.
[windows-sdk]: https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk
[app-converter]: https://www.microsoft.com/en-us/download/details.aspx?id=51691
[add-appxpackage]: https://technet.microsoft.com/en-us/library/hh856048.aspx
[electron-packager]: https://github.com/electron-userland/electron-packager
[electron-windows-store]: https://github.com/catalystcode/electron-windows-store
[background-task]: https://github.com/felixrieseberg/electron-uwp-background

View file

@ -4,7 +4,7 @@
'product_name%': 'Electron', 'product_name%': 'Electron',
'company_name%': 'GitHub, Inc', 'company_name%': 'GitHub, Inc',
'company_abbr%': 'github', 'company_abbr%': 'github',
'version%': '1.2.1', 'version%': '1.2.2',
}, },
'includes': [ 'includes': [
'filenames.gypi', 'filenames.gypi',

View file

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

View file

@ -151,6 +151,9 @@ Object.assign(BrowserWindow.prototype, {
}, },
inspectServiceWorker () { inspectServiceWorker () {
return this.webContents.inspectServiceWorker() return this.webContents.inspectServiceWorker()
},
showDefinitionForSelection () {
return this.webContents.showDefinitionForSelection()
} }
}) })

View file

@ -11,6 +11,7 @@ rolesMap = {
cut: 'cut', cut: 'cut',
copy: 'copy', copy: 'copy',
paste: 'paste', paste: 'paste',
pasteandmatchstyle: 'pasteAndMatchStyle',
selectall: 'selectAll', selectall: 'selectAll',
minimize: 'minimize', minimize: 'minimize',
close: 'close', close: 'close',

View file

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

View file

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

View file

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

View file

@ -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 renderProcessPreferences = process.atomBinding('render_process_preferences').forAllBrowserWindow()
const fs = require('fs') const fs = require('fs')
@ -20,10 +21,27 @@ const generateExtensionIdFromName = function (name) {
// Create or get manifest object from |srcDirectory|. // Create or get manifest object from |srcDirectory|.
const getManifestFromPath = function (srcDirectory) { const getManifestFromPath = function (srcDirectory) {
const manifest = JSON.parse(fs.readFileSync(path.join(srcDirectory, 'manifest.json'))) let manifest
let manifestContent
try {
manifestContent = fs.readFileSync(path.join(srcDirectory, 'manifest.json'))
} catch (readError) {
console.warn(`Reading ${path.join(srcDirectory, 'manifest.json')} failed.`)
console.warn(readError.stack || readError)
throw readError
}
try {
manifest = JSON.parse(manifestContent)
} catch (parseError) {
console.warn(`Parsing ${path.join(srcDirectory, 'manifest.json')} failed.`)
console.warn(parseError.stack || parseError)
throw parseError
}
if (!manifestNameMap[manifest.name]) { if (!manifestNameMap[manifest.name]) {
const extensionId = generateExtensionIdFromName(manifest.name) const extensionId = generateExtensionIdFromName(manifest.name)
console.log(extensionId)
manifestMap[extensionId] = manifestNameMap[manifest.name] = manifest manifestMap[extensionId] = manifestNameMap[manifest.name] = manifest
Object.assign(manifest, { Object.assign(manifest, {
srcDirectory: srcDirectory, srcDirectory: srcDirectory,
@ -38,6 +56,8 @@ const getManifestFromPath = function (srcDirectory) {
}) })
}) })
return manifest return manifest
} else if (manifest && manifest.name) {
console.warn(`Attempted to load extension "${manifest.name}" that has already been loaded.`)
} }
} }
@ -72,13 +92,13 @@ const removeBackgroundPages = function (manifest) {
} }
// Dispatch tabs events. // Dispatch tabs events.
const hookWindowForTabEvents = function (win) { const hookWebContentsForTabEvents = function (webContents) {
const tabId = win.webContents.id const tabId = webContents.id
for (const page of objectValues(backgroundPages)) { for (const page of objectValues(backgroundPages)) {
page.webContents.sendToAll('CHROME_TABS_ONCREATED', tabId) page.webContents.sendToAll('CHROME_TABS_ONCREATED', tabId)
} }
win.once('closed', () => { webContents.once('destroyed', () => {
for (const page of objectValues(backgroundPages)) { for (const page of objectValues(backgroundPages)) {
page.webContents.sendToAll('CHROME_TABS_ONREMOVED', tabId) page.webContents.sendToAll('CHROME_TABS_ONREMOVED', tabId)
} }
@ -105,6 +125,10 @@ ipcMain.on('CHROME_RUNTIME_CONNECT', function (event, extensionId, connectInfo)
page.webContents.sendToAll(`CHROME_RUNTIME_ONCONNECT_${extensionId}`, event.sender.id, portId, 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) { ipcMain.on('CHROME_RUNTIME_SENDMESSAGE', function (event, extensionId, message) {
const page = backgroundPages[extensionId] const page = backgroundPages[extensionId]
if (!page) { if (!page) {
@ -212,6 +236,15 @@ const loadDevToolsExtensions = function (win, manifests) {
win.devToolsWebContents.executeJavaScript(`DevToolsAPI.addExtensions(${JSON.stringify(extensionInfoArray)})`) 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. // The persistent path of "DevTools Extensions" preference file.
let loadedExtensionsPath = null let loadedExtensionsPath = null
@ -261,10 +294,12 @@ app.once('ready', function () {
} }
}) })
} }
protocol.registerBufferProtocol('chrome-extension', chromeExtensionHandler, function (error) { session.on('session-created', function (ses) {
if (error) { ses.protocol.registerBufferProtocol('chrome-extension', chromeExtensionHandler, function (error) {
console.error(`Unable to register chrome-extension protocol: ${error}`) if (error) {
} console.error(`Unable to register chrome-extension protocol: ${error}`)
}
})
}) })
// Load persisted extensions. // Load persisted extensions.
@ -286,12 +321,15 @@ app.once('ready', function () {
BrowserWindow.addDevToolsExtension = function (srcDirectory) { BrowserWindow.addDevToolsExtension = function (srcDirectory) {
const manifest = getManifestFromPath(srcDirectory) const manifest = getManifestFromPath(srcDirectory)
if (manifest) { if (manifest) {
for (const win of BrowserWindow.getAllWindows()) { for (const webContents of getAllWebContents()) {
loadDevToolsExtensions(win, [manifest]) if (webContents.getType() !== 'remote') {
loadDevToolsExtensions(webContents, [manifest])
}
} }
return manifest.name return manifest.name
} }
} }
BrowserWindow.removeDevToolsExtension = function (name) { BrowserWindow.removeDevToolsExtension = function (name) {
const manifest = manifestNameMap[name] const manifest = manifestNameMap[name]
if (!manifest) return if (!manifest) return
@ -301,14 +339,4 @@ app.once('ready', function () {
delete manifestMap[manifest.extensionId] delete manifestMap[manifest.extensionId]
delete manifestNameMap[name] 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))
})
}
}) })

View file

@ -4,9 +4,9 @@ const ipcMain = require('electron').ipcMain
const webContents = require('electron').webContents const webContents = require('electron').webContents
// Doesn't exist in early initialization. // Doesn't exist in early initialization.
var webViewManager = null let webViewManager = null
var supportedWebViewEvents = [ const supportedWebViewEvents = [
'load-commit', 'load-commit',
'did-finish-load', 'did-finish-load',
'did-fail-load', 'did-fail-load',
@ -36,32 +36,33 @@ var supportedWebViewEvents = [
'media-started-playing', 'media-started-playing',
'media-paused', 'media-paused',
'found-in-page', 'found-in-page',
'did-change-theme-color' 'did-change-theme-color',
'update-target-url'
] ]
var nextInstanceId = 0 let nextInstanceId = 0
var guestInstances = {} const guestInstances = {}
var embedderElementsMap = {} const embedderElementsMap = {}
var reverseEmbedderElementsMap = {} const reverseEmbedderElementsMap = {}
// Moves the last element of array to the first one. // Moves the last element of array to the first one.
var moveLastToFirst = function (list) { const moveLastToFirst = function (list) {
return list.unshift(list.pop()) return list.unshift(list.pop())
} }
// Generate guestInstanceId. // Generate guestInstanceId.
var getNextInstanceId = function () { const getNextInstanceId = function () {
return ++nextInstanceId return ++nextInstanceId
} }
// Create a new guest instance. // Create a new guest instance.
var createGuest = function (embedder, params) { const createGuest = function (embedder, params) {
var destroy, destroyEvents, event, fn, guest, i, id, j, len, len1, listeners
if (webViewManager == null) { if (webViewManager == null) {
webViewManager = process.atomBinding('web_view_manager') webViewManager = process.atomBinding('web_view_manager')
} }
id = getNextInstanceId(embedder)
guest = webContents.create({ const id = getNextInstanceId(embedder)
const guest = webContents.create({
isGuest: true, isGuest: true,
partition: params.partition, partition: params.partition,
embedder: embedder embedder: embedder
@ -72,37 +73,32 @@ var createGuest = function (embedder, params) {
} }
// Destroy guest when the embedder is gone or navigated. // Destroy guest when the embedder is gone or navigated.
destroyEvents = ['will-destroy', 'crashed', 'did-navigate'] const destroyEvents = ['will-destroy', 'crashed', 'did-navigate']
destroy = function () { const destroy = function () {
if (guestInstances[id] != null) { if (guestInstances[id] != null) {
return destroyGuest(embedder, id) destroyGuest(embedder, id)
} }
} }
for (i = 0, len = destroyEvents.length; i < len; i++) { for (const event of destroyEvents) {
event = destroyEvents[i]
embedder.once(event, destroy) embedder.once(event, destroy)
// Users might also listen to the crashed event, so We must ensure the guest // Users might also listen to the crashed event, so we must ensure the guest
// is destroyed before users' listener gets called. It is done by moving our // is destroyed before users' listener gets called. It is done by moving our
// listener to the first one in queue. // listener to the first one in queue.
listeners = embedder._events[event] const listeners = embedder._events[event]
if (Array.isArray(listeners)) { if (Array.isArray(listeners)) {
moveLastToFirst(listeners) moveLastToFirst(listeners)
} }
} }
guest.once('destroyed', function () { guest.once('destroyed', function () {
var j, len1, results for (const event of destroyEvents) {
results = [] embedder.removeListener(event, destroy)
for (j = 0, len1 = destroyEvents.length; j < len1; j++) {
event = destroyEvents[j]
results.push(embedder.removeListener(event, destroy))
} }
return results
}) })
// Init guest web view after attached. // Init guest web view after attached.
guest.once('did-attach', function () { guest.once('did-attach', function () {
var opts let opts
params = this.attachParams params = this.attachParams
delete this.attachParams delete this.attachParams
this.viewInstanceId = params.instanceId this.viewInstanceId = params.instanceId
@ -135,13 +131,12 @@ var createGuest = function (embedder, params) {
}) })
// Dispatch events to embedder. // Dispatch events to embedder.
fn = function (event) { const fn = function (event) {
return guest.on(event, function (_, ...args) { guest.on(event, function (_, ...args) {
embedder.send.apply(embedder, ['ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-' + guest.viewInstanceId, event].concat(args)) embedder.send.apply(embedder, ['ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-' + guest.viewInstanceId, event].concat(args))
}) })
} }
for (j = 0, len1 = supportedWebViewEvents.length; j < len1; j++) { for (const event of supportedWebViewEvents) {
event = supportedWebViewEvents[j]
fn(event) fn(event)
} }
@ -154,12 +149,13 @@ var createGuest = function (embedder, params) {
guest.on('size-changed', function (_, ...args) { guest.on('size-changed', function (_, ...args) {
embedder.send.apply(embedder, ['ELECTRON_GUEST_VIEW_INTERNAL_SIZE_CHANGED-' + guest.viewInstanceId].concat(args)) embedder.send.apply(embedder, ['ELECTRON_GUEST_VIEW_INTERNAL_SIZE_CHANGED-' + guest.viewInstanceId].concat(args))
}) })
return id return id
} }
// Attach the guest to an element of embedder. // Attach the guest to an element of embedder.
var attachGuest = function (embedder, elementInstanceId, guestInstanceId, params) { const attachGuest = function (embedder, elementInstanceId, guestInstanceId, params) {
var guest, key, oldGuestInstanceId, ref1, webPreferences let guest, key, oldGuestInstanceId, ref1, webPreferences
guest = guestInstances[guestInstanceId].guest guest = guestInstances[guestInstanceId].guest
// Destroy the old guest when attaching. // Destroy the old guest when attaching.
@ -181,7 +177,8 @@ var attachGuest = function (embedder, elementInstanceId, guestInstanceId, params
plugins: params.plugins, plugins: params.plugins,
zoomFactor: params.zoomFactor, zoomFactor: params.zoomFactor,
webSecurity: !params.disablewebsecurity, webSecurity: !params.disablewebsecurity,
blinkFeatures: params.blinkfeatures blinkFeatures: params.blinkfeatures,
disableBlinkFeatures: params.disableblinkfeatures
} }
if (params.preload) { if (params.preload) {
@ -194,12 +191,12 @@ var attachGuest = function (embedder, elementInstanceId, guestInstanceId, params
} }
// Destroy an existing guest instance. // Destroy an existing guest instance.
var destroyGuest = function (embedder, id) { const destroyGuest = function (embedder, id) {
var key
webViewManager.removeGuest(embedder, id) webViewManager.removeGuest(embedder, id)
guestInstances[id].guest.destroy() guestInstances[id].guest.destroy()
delete guestInstances[id] delete guestInstances[id]
key = reverseEmbedderElementsMap[id]
const key = reverseEmbedderElementsMap[id]
if (key != null) { if (key != null) {
delete reverseEmbedderElementsMap[id] delete reverseEmbedderElementsMap[id]
return delete embedderElementsMap[key] return delete embedderElementsMap[key]
@ -219,18 +216,18 @@ ipcMain.on('ELECTRON_GUEST_VIEW_MANAGER_DESTROY_GUEST', function (event, id) {
}) })
ipcMain.on('ELECTRON_GUEST_VIEW_MANAGER_SET_SIZE', function (event, id, params) { ipcMain.on('ELECTRON_GUEST_VIEW_MANAGER_SET_SIZE', function (event, id, params) {
var ref1 const guestInstance = guestInstances[id]
return (ref1 = guestInstances[id]) != null ? ref1.guest.setSize(params) : void 0 return guestInstance != null ? guestInstance.guest.setSize(params) : void 0
}) })
// Returns WebContents from its guest id. // Returns WebContents from its guest id.
exports.getGuest = function (id) { exports.getGuest = function (id) {
var ref1 const guestInstance = guestInstances[id]
return (ref1 = guestInstances[id]) != null ? ref1.guest : void 0 return guestInstance != null ? guestInstance.guest : void 0
} }
// Returns the embedder of the guest. // Returns the embedder of the guest.
exports.getEmbedder = function (id) { exports.getEmbedder = function (id) {
var ref1 const guestInstance = guestInstances[id]
return (ref1 = guestInstances[id]) != null ? ref1.embedder : void 0 return guestInstance != null ? guestInstance.embedder : void 0
} }

View file

@ -1,14 +1,13 @@
'use strict' 'use strict'
const ipcMain = require('electron').ipcMain const {BrowserWindow, ipcMain, webContents} = require('electron')
const BrowserWindow = require('electron').BrowserWindow
var hasProp = {}.hasOwnProperty const hasProp = {}.hasOwnProperty
var frameToGuest = {} const frameToGuest = {}
// Copy attribute of |parent| to |child| if it is not defined in |child|. // Copy attribute of |parent| to |child| if it is not defined in |child|.
var mergeOptions = function (child, parent) { const mergeOptions = function (child, parent) {
var key, value let key, value
for (key in parent) { for (key in parent) {
if (!hasProp.call(parent, key)) continue if (!hasProp.call(parent, key)) continue
value = parent[key] value = parent[key]
@ -24,7 +23,7 @@ var mergeOptions = function (child, parent) {
} }
// Merge |options| with the |embedder|'s window's options. // Merge |options| with the |embedder|'s window's options.
var mergeBrowserWindowOptions = function (embedder, options) { const mergeBrowserWindowOptions = function (embedder, options) {
if (embedder.browserWindowOptions != null) { if (embedder.browserWindowOptions != null) {
// Inherit the original options if it is a BrowserWindow. // Inherit the original options if it is a BrowserWindow.
mergeOptions(options, embedder.browserWindowOptions) mergeOptions(options, embedder.browserWindowOptions)
@ -45,9 +44,8 @@ var mergeBrowserWindowOptions = function (embedder, options) {
} }
// Create a new guest created by |embedder| with |options|. // Create a new guest created by |embedder| with |options|.
var createGuest = function (embedder, url, frameName, options) { const createGuest = function (embedder, url, frameName, options) {
var closedByEmbedder, closedByUser, guest, guestId, ref1 let guest = frameToGuest[frameName]
guest = frameToGuest[frameName]
if (frameName && (guest != null)) { if (frameName && (guest != null)) {
guest.loadURL(url) guest.loadURL(url)
return guest.id return guest.id
@ -57,24 +55,26 @@ var createGuest = function (embedder, url, frameName, options) {
if (options.webPreferences == null) { if (options.webPreferences == null) {
options.webPreferences = {} options.webPreferences = {}
} }
options.webPreferences.openerId = (ref1 = BrowserWindow.fromWebContents(embedder)) != null ? ref1.id : void 0 options.webPreferences.openerId = embedder.id
guest = new BrowserWindow(options) guest = new BrowserWindow(options)
guest.loadURL(url) guest.loadURL(url)
// When |embedder| is destroyed we should also destroy attached guest, and if // When |embedder| is destroyed we should also destroy attached guest, and if
// guest is closed by user then we should prevent |embedder| from double // guest is closed by user then we should prevent |embedder| from double
// closing guest. // closing guest.
guestId = guest.id const guestId = guest.webContents.id
closedByEmbedder = function () {
const closedByEmbedder = function () {
guest.removeListener('closed', closedByUser) guest.removeListener('closed', closedByUser)
return guest.destroy() guest.destroy()
} }
closedByUser = function () { const closedByUser = function () {
embedder.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_' + guestId) embedder.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_' + guestId)
return embedder.removeListener('render-view-deleted', closedByEmbedder) embedder.removeListener('render-view-deleted', closedByEmbedder)
} }
embedder.once('render-view-deleted', closedByEmbedder) embedder.once('render-view-deleted', closedByEmbedder)
guest.once('closed', closedByUser) guest.once('closed', closedByUser)
if (frameName) { if (frameName) {
frameToGuest[frameName] = guest frameToGuest[frameName] = guest
guest.frameName = frameName guest.frameName = frameName
@ -82,7 +82,22 @@ var createGuest = function (embedder, url, frameName, options) {
delete frameToGuest[frameName] delete frameToGuest[frameName]
}) })
} }
return guest.id
return guestId
}
const getGuestWindow = function (guestId) {
const guestContents = webContents.fromId(guestId)
if (guestContents == null) return
let guestWindow = BrowserWindow.fromWebContents(guestContents)
if (guestWindow == null) {
const hostContents = guestContents.hostWebContents
if (hostContents != null) {
guestWindow = BrowserWindow.fromWebContents(hostContents)
}
}
return guestWindow
} }
// Routed window.open messages. // Routed window.open messages.
@ -97,28 +112,26 @@ ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', function (event, url, fr
}) })
ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', function (event, guestId) { ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', function (event, guestId) {
var ref1 const guestWindow = getGuestWindow(guestId)
(ref1 = BrowserWindow.fromId(guestId)) != null ? ref1.destroy() : void 0 if (guestWindow != null) guestWindow.destroy()
}) })
ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', function (event, guestId, method, ...args) { ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', function (event, guestId, method, ...args) {
var ref1 const guestWindow = getGuestWindow(guestId)
event.returnValue = (ref1 = BrowserWindow.fromId(guestId)) != null ? ref1[method].apply(ref1, args) : void 0 event.returnValue = guestWindow != null ? guestWindow[method].apply(guestWindow, args) : null
}) })
ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', function (event, guestId, message, targetOrigin, sourceOrigin) { ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', function (event, guestId, message, targetOrigin, sourceOrigin) {
var guestContents, ref1, ref2, sourceId const guestContents = webContents.fromId(guestId)
sourceId = (ref1 = BrowserWindow.fromWebContents(event.sender)) != null ? ref1.id : void 0 if (guestContents == null) return
if (sourceId == null) {
return if (guestContents.getURL().indexOf(targetOrigin) === 0 || targetOrigin === '*') {
} const sourceId = event.sender.id
guestContents = (ref2 = BrowserWindow.fromId(guestId)) != null ? ref2.webContents : void 0 guestContents.send('ELECTRON_GUEST_WINDOW_POSTMESSAGE', sourceId, message, sourceOrigin)
if ((guestContents != null ? guestContents.getURL().indexOf(targetOrigin) : void 0) === 0 || targetOrigin === '*') {
guestContents != null ? guestContents.send('ELECTRON_GUEST_WINDOW_POSTMESSAGE', sourceId, message, sourceOrigin) : void 0
} }
}) })
ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', function (event, guestId, method, ...args) { ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', function (event, guestId, method, ...args) {
var ref1, ref2 const guestContents = webContents.fromId(guestId)
(ref1 = BrowserWindow.fromId(guestId)) != null ? (ref2 = ref1.webContents) != null ? ref2[method].apply(ref2, args) : void 0 : void 0 if (guestContents != null) guestContents[method].apply(guestContents, args)
}) })

View file

@ -99,6 +99,8 @@ exports.injectTo = function (extensionId, isBackgroundPage, context) {
}) })
chrome.runtime = { chrome.runtime = {
id: extensionId,
getURL: function (path) { getURL: function (path) {
return url.format({ return url.format({
protocol: 'chrome-extension', protocol: 'chrome-extension',
@ -181,12 +183,7 @@ exports.injectTo = function (extensionId, isBackgroundPage, context) {
onMessage: chrome.runtime.onMessage onMessage: chrome.runtime.onMessage
} }
chrome.storage = { chrome.storage = require('./extensions/storage.js')
sync: {
get () {},
set () {}
}
}
chrome.pageAction = { chrome.pageAction = {
show () {}, show () {},
@ -197,4 +194,6 @@ exports.injectTo = function (extensionId, isBackgroundPage, context) {
setPopup () {}, setPopup () {},
getPopup () {} getPopup () {}
} }
chrome.i18n = require('./extensions/i18n.js').setup(extensionId)
} }

View file

@ -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)
}
}
}

View file

@ -0,0 +1,59 @@
const getStorage = () => {
const data = window.localStorage.getItem('__chrome.storage.sync__')
if (data != null) {
return JSON.parse(data)
} else {
return {}
}
}
const setStorage = (storage) => {
const json = JSON.stringify(storage)
window.localStorage.setItem('__chrome.storage.sync__', json)
}
module.exports = {
sync: {
get (keys, callback) {
const storage = getStorage()
if (keys == null) return storage
let defaults = {}
switch (typeof keys) {
case 'string':
keys = [keys]
break
case 'object':
if (!Array.isArray(keys)) {
defaults = keys
keys = Object.keys(keys)
}
break
}
if (keys.length === 0) return {}
let items = {}
keys.forEach(function (key) {
var value = storage[key]
if (value == null) value = defaults[key]
items[key] = value
})
setTimeout(function () {
callback(items)
})
},
set (items, callback) {
const storage = getStorage()
Object.keys(items).forEach(function (name) {
storage[name] = items[name]
})
setStorage(storage)
setTimeout(callback)
}
}
}

View file

@ -25,7 +25,13 @@ var BrowserWindowProxy = (function () {
} }
function BrowserWindowProxy (guestId1) { function BrowserWindowProxy (guestId1) {
this.guestId = guestId1 Object.defineProperty(this, 'guestId', {
configurable: false,
enumerable: true,
writeable: false,
value: guestId1
})
this.closed = false this.closed = false
ipcRenderer.once('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_' + this.guestId, () => { ipcRenderer.once('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_' + this.guestId, () => {
BrowserWindowProxy.remove(this.guestId) BrowserWindowProxy.remove(this.guestId)

View file

@ -27,15 +27,16 @@ var WEB_VIEW_EVENTS = {
'crashed': [], 'crashed': [],
'gpu-crashed': [], 'gpu-crashed': [],
'plugin-crashed': ['name', 'version'], 'plugin-crashed': ['name', 'version'],
'media-started-playing': [],
'media-paused': [],
'did-change-theme-color': ['themeColor'],
'destroyed': [], 'destroyed': [],
'page-title-updated': ['title', 'explicitSet'], 'page-title-updated': ['title', 'explicitSet'],
'page-favicon-updated': ['favicons'], 'page-favicon-updated': ['favicons'],
'enter-html-full-screen': [], 'enter-html-full-screen': [],
'leave-html-full-screen': [], 'leave-html-full-screen': [],
'found-in-page': ['result'] 'media-started-playing': [],
'media-paused': [],
'found-in-page': ['result'],
'did-change-theme-color': ['themeColor'],
'update-target-url': ['url']
} }
var DEPRECATED_EVENTS = { var DEPRECATED_EVENTS = {

View file

@ -264,6 +264,13 @@ class BlinkFeaturesAttribute extends WebViewAttribute {
} }
} }
// Attribute that specifies the blink features to be disabled.
class DisableBlinkFeaturesAttribute extends WebViewAttribute {
constructor (webViewImpl) {
super(webViewConstants.ATTRIBUTE_DISABLEBLINKFEATURES, webViewImpl)
}
}
// Sets up all of the webview attributes. // Sets up all of the webview attributes.
WebViewImpl.prototype.setupWebViewAttributes = function () { WebViewImpl.prototype.setupWebViewAttributes = function () {
this.attributes = {} this.attributes = {}
@ -278,6 +285,7 @@ WebViewImpl.prototype.setupWebViewAttributes = function () {
this.attributes[webViewConstants.ATTRIBUTE_ALLOWPOPUPS] = new BooleanAttribute(webViewConstants.ATTRIBUTE_ALLOWPOPUPS, this) this.attributes[webViewConstants.ATTRIBUTE_ALLOWPOPUPS] = new BooleanAttribute(webViewConstants.ATTRIBUTE_ALLOWPOPUPS, this)
this.attributes[webViewConstants.ATTRIBUTE_PRELOAD] = new PreloadAttribute(this) this.attributes[webViewConstants.ATTRIBUTE_PRELOAD] = new PreloadAttribute(this)
this.attributes[webViewConstants.ATTRIBUTE_BLINKFEATURES] = new BlinkFeaturesAttribute(this) this.attributes[webViewConstants.ATTRIBUTE_BLINKFEATURES] = new BlinkFeaturesAttribute(this)
this.attributes[webViewConstants.ATTRIBUTE_DISABLEBLINKFEATURES] = new DisableBlinkFeaturesAttribute(this)
const autosizeAttributes = [webViewConstants.ATTRIBUTE_MAXHEIGHT, webViewConstants.ATTRIBUTE_MAXWIDTH, webViewConstants.ATTRIBUTE_MINHEIGHT, webViewConstants.ATTRIBUTE_MINWIDTH] const autosizeAttributes = [webViewConstants.ATTRIBUTE_MAXHEIGHT, webViewConstants.ATTRIBUTE_MAXWIDTH, webViewConstants.ATTRIBUTE_MINHEIGHT, webViewConstants.ATTRIBUTE_MINWIDTH]
autosizeAttributes.forEach((attribute) => { autosizeAttributes.forEach((attribute) => {

View file

@ -16,6 +16,7 @@ module.exports = {
ATTRIBUTE_PRELOAD: 'preload', ATTRIBUTE_PRELOAD: 'preload',
ATTRIBUTE_USERAGENT: 'useragent', ATTRIBUTE_USERAGENT: 'useragent',
ATTRIBUTE_BLINKFEATURES: 'blinkfeatures', ATTRIBUTE_BLINKFEATURES: 'blinkfeatures',
ATTRIBUTE_DISABLEBLINKFEATURES: 'disableblinkfeatures',
// Internal attribute. // Internal attribute.
ATTRIBUTE_INTERNALINSTANCEID: 'internalinstanceid', ATTRIBUTE_INTERNALINSTANCEID: 'internalinstanceid',

View file

@ -379,7 +379,8 @@ var registerWebViewElement = function () {
'downloadURL', 'downloadURL',
'inspectServiceWorker', 'inspectServiceWorker',
'print', 'print',
'printToPDF' 'printToPDF',
'showDefinitionForSelection'
] ]
nonblockMethods = [ nonblockMethods = [
'insertCSS', 'insertCSS',

View file

@ -1,6 +1,6 @@
{ {
"name": "electron", "name": "electron",
"version": "1.2.1", "version": "1.2.2",
"devDependencies": { "devDependencies": {
"asar": "^0.11.0", "asar": "^0.11.0",
"request": "*", "request": "*",

View file

@ -8,7 +8,7 @@ import sys
BASE_URL = os.getenv('LIBCHROMIUMCONTENT_MIRROR') or \ BASE_URL = os.getenv('LIBCHROMIUMCONTENT_MIRROR') or \
'https://s3.amazonaws.com/github-janky-artifacts/libchromiumcontent' 'https://s3.amazonaws.com/github-janky-artifacts/libchromiumcontent'
LIBCHROMIUMCONTENT_COMMIT = '8f2a0aa0a9dd107e4d574cc9d552781c55c86bab' LIBCHROMIUMCONTENT_COMMIT = '7ae12b2856b81f5bfa1290108f72c3e4b5c13c52'
PLATFORM = { PLATFORM = {
'cygwin': 'win32', 'cygwin': 'win32',

View file

@ -858,12 +858,27 @@ describe('browser-window module', function () {
w.loadURL('about:blank') w.loadURL('about:blank')
}) })
it('throws errors for missing manifest.json files', function () {
assert.throws(function () {
BrowserWindow.addDevToolsExtension(path.join(__dirname, 'does-not-exist'))
}, /ENOENT: no such file or directory/)
})
it('throws errors for invalid manifest.json files', function () {
assert.throws(function () {
BrowserWindow.addDevToolsExtension(path.join(__dirname, 'fixtures', 'devtools-extensions', 'bad-manifest'))
}, /Unexpected token }/)
})
describe('when the devtools is docked', function () { describe('when the devtools is docked', function () {
it('creates the extension', function (done) { it('creates the extension', function (done) {
w.webContents.openDevTools({mode: 'bottom'}) w.webContents.openDevTools({mode: 'bottom'})
ipcMain.once('answer', function (event, message) { ipcMain.once('answer', function (event, message) {
assert.equal(message, 'extension loaded') 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() done()
}) })
}) })
@ -873,14 +888,54 @@ describe('browser-window module', function () {
it('creates the extension', function (done) { it('creates the extension', function (done) {
w.webContents.openDevTools({mode: 'undocked'}) w.webContents.openDevTools({mode: 'undocked'})
ipcMain.once('answer', function (event, message) { ipcMain.once('answer', function (event, message, extensionId) {
assert.equal(message, 'extension loaded') assert.equal(message.runtimeId, 'foo')
assert.equal(message.tabId, w.webContents.id)
done() 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 () { it('serializes the registered extensions on quit', function () {
var extensionName = 'foo' var extensionName = 'foo'
var extensionPath = path.join(__dirname, 'fixtures', 'devtools-extensions', extensionName) var extensionPath = path.join(__dirname, 'fixtures', 'devtools-extensions', extensionName)

View file

@ -919,6 +919,23 @@ describe('protocol module', function () {
}) })
w.loadURL(origin) w.loadURL(origin)
}) })
}),
it('can have fetch working in it', function (done) {
const content = '<html><script>fetch("http://github.com")</script></html>'
const handler = function (request, callback) {
callback({data: content, mimeType: 'text/html'})
}
protocol.registerStringProtocol(standardScheme, handler, function (error) {
if (error) return done(error)
w.webContents.on('crashed', function () {
done('WebContents crashed')
})
w.webContents.on('did-finish-load', function () {
done()
})
w.loadURL(origin)
})
}) })
}) })
}) })

View file

@ -16,8 +16,15 @@ describe('session module', function () {
var fixtures = path.resolve(__dirname, 'fixtures') var fixtures = path.resolve(__dirname, 'fixtures')
var w = null var w = null
var url = 'http://127.0.0.1' 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 () { beforeEach(function () {
if (w != null) {
w.destroy()
}
w = new BrowserWindow({ w = new BrowserWindow({
show: false, show: false,
width: 400, width: 400,
@ -26,7 +33,10 @@ describe('session module', function () {
}) })
afterEach(function () { afterEach(function () {
w.destroy() if (w != null) {
w.destroy()
}
w = null
}) })
describe('session.cookies', function () { 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")
})
})
})
})
})
}) })

View file

@ -1,12 +1,11 @@
const assert = require('assert') const assert = require('assert')
const http = require('http') const http = require('http')
const path = require('path') const path = require('path')
const ws = require('ws') const ws = require('ws')
const url = require('url')
const remote = require('electron').remote const remote = require('electron').remote
const BrowserWindow = remote.require('electron').BrowserWindow const {BrowserWindow, session, webContents} = remote
const session = remote.require('electron').session
const isCI = remote.getGlobal('isCi') const isCI = remote.getGlobal('isCi')
@ -165,6 +164,12 @@ describe('chromium feature', function () {
var b = window.open('about:blank', '', 'show=no') var b = window.open('about:blank', '', 'show=no')
assert.equal(b.closed, false) assert.equal(b.closed, false)
assert.equal(b.constructor.name, 'BrowserWindowProxy') assert.equal(b.constructor.name, 'BrowserWindowProxy')
// Check that guestId is not writeable
assert(b.guestId)
b.guestId = 'anotherValue'
assert.notEqual(b.guestId, 'anoterValue')
b.close() b.close()
}) })
@ -235,7 +240,7 @@ describe('chromium feature', function () {
else else
targetURL = 'file://' + fixtures + '/pages/base-page.html' targetURL = 'file://' + fixtures + '/pages/base-page.html'
b = window.open(targetURL) b = window.open(targetURL)
BrowserWindow.fromId(b.guestId).webContents.once('did-finish-load', function () { webContents.fromId(b.guestId).once('did-finish-load', function () {
assert.equal(b.location, targetURL) assert.equal(b.location, targetURL)
b.close() b.close()
done() done()
@ -246,10 +251,10 @@ describe('chromium feature', function () {
// Load a page that definitely won't redirect // Load a page that definitely won't redirect
var b var b
b = window.open('about:blank') b = window.open('about:blank')
BrowserWindow.fromId(b.guestId).webContents.once('did-finish-load', function () { webContents.fromId(b.guestId).once('did-finish-load', function () {
// When it loads, redirect // When it loads, redirect
b.location = 'file://' + fixtures + '/pages/base-page.html' b.location = 'file://' + fixtures + '/pages/base-page.html'
BrowserWindow.fromId(b.guestId).webContents.once('did-finish-load', function () { webContents.fromId(b.guestId).once('did-finish-load', function () {
// After our second redirect, cleanup and callback // After our second redirect, cleanup and callback
b.close() b.close()
done() done()
@ -308,7 +313,7 @@ describe('chromium feature', function () {
} }
window.addEventListener('message', listener) window.addEventListener('message', listener)
b = window.open('file://' + fixtures + '/pages/window-open-postMessage.html', '', 'show=no') b = window.open('file://' + fixtures + '/pages/window-open-postMessage.html', '', 'show=no')
BrowserWindow.fromId(b.guestId).webContents.once('did-finish-load', function () { webContents.fromId(b.guestId).once('did-finish-load', function () {
b.postMessage('testing', '*') b.postMessage('testing', '*')
}) })
}) })
@ -327,6 +332,25 @@ describe('chromium feature', function () {
window.addEventListener('message', listener) window.addEventListener('message', listener)
b = window.open('file://' + fixtures + '/pages/window-opener-postMessage.html', '', 'show=no') b = window.open('file://' + fixtures + '/pages/window-opener-postMessage.html', '', 'show=no')
}) })
it('supports windows opened from a <webview>', function (done) {
const webview = new WebView()
webview.addEventListener('console-message', function (e) {
webview.remove()
assert.equal(e.message, 'message')
done()
})
webview.allowpopups = true
webview.src = url.format({
pathname: `${fixtures}/pages/webview-opener-postMessage.html`,
protocol: 'file',
query: {
p: `${fixtures}/pages/window-opener-postMessage.html`
},
slashes: true
})
document.body.appendChild(webview)
})
}) })
describe('creating a Uint8Array under browser side', function () { describe('creating a Uint8Array under browser side', function () {

View file

@ -0,0 +1 @@
}

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