Merge branch 'master' into chrome-storage-sync

This commit is contained in:
Jessica Lord 2016-06-09 16:35:00 -07:00
commit f121f46a24
50 changed files with 605 additions and 192 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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
Add `plugins` attribute to `<webview>` tag.

View file

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

View file

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

View file

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

View file

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

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

View file

@ -194,4 +194,6 @@ exports.injectTo = function (extensionId, isBackgroundPage, context) {
setPopup () {},
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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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