Merge remote-tracking branch 'upstream/master' into speedup-gpu

This commit is contained in:
gellert 2016-08-01 12:00:34 +02:00
commit 439ad94afe
85 changed files with 1592 additions and 757 deletions

View file

@ -8,7 +8,6 @@
#include "atom/browser/api/atom_api_menu.h"
#include "atom/browser/browser.h"
#include "atom/browser/ui/tray_icon.h"
#include "atom/common/api/atom_api_native_image.h"
#include "atom/common/native_mate_converters/gfx_converter.h"
#include "atom/common/native_mate_converters/image_converter.h"
@ -18,6 +17,45 @@
#include "native_mate/dictionary.h"
#include "ui/gfx/image/image.h"
namespace mate {
template<>
struct Converter<atom::TrayIcon::HighlightMode> {
static bool FromV8(v8::Isolate* isolate, v8::Local<v8::Value> val,
atom::TrayIcon::HighlightMode* out) {
std::string mode;
if (ConvertFromV8(isolate, val, &mode)) {
if (mode == "always") {
*out = atom::TrayIcon::HighlightMode::ALWAYS;
return true;
}
if (mode == "selection") {
*out = atom::TrayIcon::HighlightMode::SELECTION;
return true;
}
if (mode == "never") {
*out = atom::TrayIcon::HighlightMode::NEVER;
return true;
}
}
// Support old boolean parameter
// TODO(kevinsawicki): Remove in 2.0, deprecate before then with warnings
bool highlight;
if (ConvertFromV8(isolate, val, &highlight)) {
if (highlight)
*out = atom::TrayIcon::HighlightMode::SELECTION;
else
*out = atom::TrayIcon::HighlightMode::NEVER;
return true;
}
return false;
}
};
} // namespace mate
namespace atom {
namespace api {
@ -117,8 +155,8 @@ void Tray::SetTitle(const std::string& title) {
tray_icon_->SetTitle(title);
}
void Tray::SetHighlightMode(bool highlight) {
tray_icon_->SetHighlightMode(highlight);
void Tray::SetHighlightMode(TrayIcon::HighlightMode mode) {
tray_icon_->SetHighlightMode(mode);
}
void Tray::DisplayBalloon(mate::Arguments* args,

View file

@ -10,6 +10,7 @@
#include <vector>
#include "atom/browser/api/trackable_object.h"
#include "atom/browser/ui/tray_icon.h"
#include "atom/browser/ui/tray_icon_observer.h"
#include "native_mate/handle.h"
@ -62,7 +63,7 @@ class Tray : public mate::TrackableObject<Tray>,
void SetPressedImage(v8::Isolate* isolate, mate::Handle<NativeImage> image);
void SetToolTip(const std::string& tool_tip);
void SetTitle(const std::string& title);
void SetHighlightMode(bool highlight);
void SetHighlightMode(TrayIcon::HighlightMode mode);
void DisplayBalloon(mate::Arguments* args, const mate::Dictionary& options);
void PopUpContextMenu(mate::Arguments* args);
void SetContextMenu(v8::Isolate* isolate, mate::Handle<Menu> menu);

View file

@ -772,7 +772,7 @@ bool WebContents::OnMessageReceived(const IPC::Message& message) {
}
// There are three ways of destroying a webContents:
// 1. call webContents.destory();
// 1. call webContents.destroy();
// 2. garbage collection;
// 3. user closes the window of webContents;
// For webview only #1 will happen, for BrowserWindow both #1 and #3 may
@ -1175,6 +1175,12 @@ void WebContents::ShowDefinitionForSelection() {
#endif
}
void WebContents::CopyImageAt(int x, int y) {
const auto host = web_contents()->GetRenderViewHost();
if (host)
host->CopyImageAt(x, y);
}
void WebContents::Focus() {
web_contents()->Focus();
}
@ -1518,6 +1524,7 @@ void WebContents::BuildPrototype(v8::Isolate* isolate,
.SetMethod("removeWorkSpace", &WebContents::RemoveWorkSpace)
.SetMethod("showDefinitionForSelection",
&WebContents::ShowDefinitionForSelection)
.SetMethod("copyImageAt", &WebContents::CopyImageAt)
.SetMethod("capturePage", &WebContents::CapturePage)
.SetMethod("isFocused", &WebContents::IsFocused)
.SetMethod("isOffscreen", &WebContents::IsOffScreen)

View file

@ -129,6 +129,7 @@ class WebContents : public mate::TrackableObject<WebContents>,
uint32_t FindInPage(mate::Arguments* args);
void StopFindInPage(content::StopFindAction action);
void ShowDefinitionForSelection();
void CopyImageAt(int x, int y);
// Focus.
void Focus();

View file

@ -13,6 +13,9 @@ namespace atom {
namespace api {
bool WebContents::IsFocused() const {
auto view = web_contents()->GetRenderWidgetHostView();
if (!view) return false;
if (GetType() != BACKGROUND_PAGE) {
auto window = web_contents()->GetTopLevelNativeWindow();
// On Mac the render widget host view does not lose focus when the window
@ -21,8 +24,7 @@ bool WebContents::IsFocused() const {
return false;
}
auto view = web_contents()->GetRenderWidgetHostView();
return view && view->HasFocus();
return view->HasFocus();
}
} // namespace api

View file

@ -88,6 +88,7 @@ AtomBrowserContext::AtomBrowserContext(
use_cache_ = true;
options.GetBoolean("cache", &use_cache_);
// Initialize Pref Registry in brightray.
InitPrefs();
}

View file

@ -85,6 +85,14 @@ void AtomDownloadManagerDelegate::OnDownloadPathGenerated(
download_manager_->GetBrowserContext());
browser_context->prefs()->SetFilePath(prefs::kDownloadDefaultDirectory,
path.DirName());
v8::Isolate* isolate = v8::Isolate::GetCurrent();
v8::Locker locker(isolate);
v8::HandleScope handle_scope(isolate);
api::DownloadItem* download_item = api::DownloadItem::FromWrappedClass(
isolate, item);
if (download_item)
download_item->SetSavePath(path);
}
// Running the DownloadTargetCallback with an empty FilePath signals that the

View file

@ -32,7 +32,8 @@ void CommonWebContentsDelegate::HandleKeyboardEvent(
[[NSApp mainMenu] performKeyEquivalent:event.os_event])
return;
if (event.os_event.window)
if (event.os_event.window &&
[event.os_event.window isKindOfClass:[EventDispatchingWindow class]])
[event.os_event.window redispatchKeyEvent:event.os_event];
}

View file

@ -40,6 +40,11 @@
#include "ui/gfx/geometry/size_conversions.h"
#include "ui/gl/gpu_switching_manager.h"
#if defined(OS_LINUX) || defined(OS_WIN)
#include "content/public/common/renderer_preferences.h"
#include "ui/gfx/font_render_params.h"
#endif
DEFINE_WEB_CONTENTS_USER_DATA_KEY(atom::NativeWindowRelay);
namespace atom {
@ -67,6 +72,20 @@ NativeWindow::NativeWindow(
if (parent)
options.Get("modal", &is_modal_);
#if defined(OS_LINUX) || defined(OS_WIN)
auto* prefs = web_contents()->GetMutableRendererPrefs();
// Update font settings.
CR_DEFINE_STATIC_LOCAL(const gfx::FontRenderParams, params,
(gfx::GetFontRenderParams(gfx::FontRenderParamsQuery(), nullptr)));
prefs->should_antialias_text = params.antialiasing;
prefs->use_subpixel_positioning = params.subpixel_positioning;
prefs->hinting = params.hinting;
prefs->use_autohinter = params.autohinter;
prefs->use_bitmaps = params.use_bitmaps;
prefs->subpixel_rendering = params.subpixel_rendering;
#endif
// Tell the content module to initialize renderer widget with transparent
// mode.
ui::GpuSwitchingManager::SetTransparent(transparent_);

View file

@ -214,6 +214,16 @@ class NativeWindowViews : public NativeWindow,
// fullscreen), so we restore it correctly.
gfx::Rect last_normal_bounds_;
// last_normal_bounds_ may or may not require update on WM_MOVE. When a
// window is maximized, it is moved (WM_MOVE) to maximum size first and then
// sized (WM_SIZE). In this case, last_normal_bounds_ should not update. We
// keep last_normal_bounds_candidate_ as a candidate which will become valid
// last_normal_bounds_ if the moves are consecutive with no WM_SIZE event in
// between.
gfx::Rect last_normal_bounds_candidate_;
bool consecutive_moves_;
// In charge of running taskbar related APIs.
TaskbarHost taskbar_host_;

View file

@ -110,18 +110,26 @@ bool NativeWindowViews::PreHandleMSG(
if (HIWORD(w_param) == THBN_CLICKED)
return taskbar_host_.HandleThumbarButtonEvent(LOWORD(w_param));
return false;
case WM_SIZE:
case WM_SIZE: {
consecutive_moves_ = false;
// Handle window state change.
HandleSizeEvent(w_param, l_param);
return false;
}
case WM_MOVING: {
if (!movable_)
::GetWindowRect(GetAcceleratedWidget(), (LPRECT)l_param);
return false;
}
case WM_MOVE: {
if (last_window_state_ == ui::SHOW_STATE_NORMAL) {
if (consecutive_moves_)
last_normal_bounds_ = last_normal_bounds_candidate_;
last_normal_bounds_candidate_ = GetBounds();
consecutive_moves_ = true;
}
return false;
}
default:
return false;
}

View file

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 368 KiB

Before After
Before After

View file

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

View file

@ -18,7 +18,7 @@ void TrayIcon::SetPressedImage(ImageType image) {
void TrayIcon::SetTitle(const std::string& title) {
}
void TrayIcon::SetHighlightMode(bool highlight) {
void TrayIcon::SetHighlightMode(TrayIcon::HighlightMode mode) {
}
void TrayIcon::DisplayBalloon(ImageType icon,

View file

@ -43,9 +43,13 @@ class TrayIcon {
// only works on macOS.
virtual void SetTitle(const std::string& title);
// Sets whether the status icon is highlighted when it is clicked. This only
// works on macOS.
virtual void SetHighlightMode(bool highlight);
// Sets the status icon highlight mode. This only works on macOS.
enum HighlightMode {
ALWAYS, // Always highlight the tray icon
NEVER, // Never highlight the tray icon
SELECTION // Highlight the tray icon when clicked or the menu is opened
};
virtual void SetHighlightMode(HighlightMode mode);
// Displays a notification balloon with the specified contents.
// Depending on the platform it might not appear by the icon tray.

View file

@ -27,7 +27,7 @@ class TrayIconCocoa : public TrayIcon,
void SetPressedImage(const gfx::Image& image) override;
void SetToolTip(const std::string& tool_tip) override;
void SetTitle(const std::string& title) override;
void SetHighlightMode(bool highlight) override;
void SetHighlightMode(TrayIcon::HighlightMode mode) override;
void PopUpContextMenu(const gfx::Point& pos,
AtomMenuModel* menu_model) override;
void SetContextMenu(AtomMenuModel* menu_model) override;

View file

@ -23,7 +23,7 @@ const CGFloat kVerticalTitleMargin = 2;
@interface StatusItemView : NSView {
atom::TrayIconCocoa* trayIcon_; // weak
AtomMenuController* menuController_; // weak
BOOL isHighlightEnable_;
atom::TrayIcon::HighlightMode highlight_mode_;
BOOL forceHighlight_;
BOOL inMouseEventSequence_;
base::scoped_nsobject<NSImage> image_;
@ -39,7 +39,7 @@ const CGFloat kVerticalTitleMargin = 2;
- (id)initWithImage:(NSImage*)image icon:(atom::TrayIconCocoa*)icon {
image_.reset([image copy]);
trayIcon_ = icon;
isHighlightEnable_ = YES;
highlight_mode_ = atom::TrayIcon::HighlightMode::SELECTION;
forceHighlight_ = NO;
inMouseEventSequence_ = NO;
@ -192,8 +192,9 @@ const CGFloat kVerticalTitleMargin = 2;
alternateImage_.reset([image copy]);
}
- (void)setHighlight:(BOOL)highlight {
isHighlightEnable_ = highlight;
- (void)setHighlight:(atom::TrayIcon::HighlightMode)mode {
highlight_mode_ = mode;
[self setNeedsDisplay:YES];
}
- (void)setTitle:(NSString*)title {
@ -328,10 +329,15 @@ const CGFloat kVerticalTitleMargin = 2;
}
- (BOOL)shouldHighlight {
if (isHighlightEnable_ && forceHighlight_)
switch (highlight_mode_) {
case atom::TrayIcon::HighlightMode::ALWAYS:
return true;
case atom::TrayIcon::HighlightMode::NEVER:
return false;
case atom::TrayIcon::HighlightMode::SELECTION:
BOOL isMenuOpen = menuController_ && [menuController_ isMenuOpen];
return isHighlightEnable_ && (inMouseEventSequence_ || isMenuOpen);
return forceHighlight_ || inMouseEventSequence_ || isMenuOpen;
}
}
@end
@ -369,8 +375,8 @@ void TrayIconCocoa::SetTitle(const std::string& title) {
[status_item_view_ setTitle:base::SysUTF8ToNSString(title)];
}
void TrayIconCocoa::SetHighlightMode(bool highlight) {
[status_item_view_ setHighlight:highlight];
void TrayIconCocoa::SetHighlightMode(TrayIcon::HighlightMode mode) {
[status_item_view_ setHighlight:mode];
}
void TrayIconCocoa::PopUpContextMenu(const gfx::Point& pos,

View file

@ -11,6 +11,34 @@
#include "atom/common/node_includes.h"
#include "native_mate/dictionary.h"
#if defined(OS_WIN)
#include "base/win/scoped_com_initializer.h"
#include "base/win/shortcut.h"
namespace mate {
template<>
struct Converter<base::win::ShortcutOperation> {
static bool FromV8(v8::Isolate* isolate, v8::Handle<v8::Value> val,
base::win::ShortcutOperation* out) {
std::string operation;
if (!ConvertFromV8(isolate, val, & operation))
return false;
if (operation.empty() || operation == "create")
*out = base::win::SHORTCUT_CREATE_ALWAYS;
else if (operation == "update")
*out = base::win::SHORTCUT_UPDATE_EXISTING;
else if (operation == "replace")
*out = base::win::SHORTCUT_REPLACE_EXISTING;
else
return false;
return true;
}
};
} // namespace mate
#endif
namespace {
bool OpenExternal(
@ -30,6 +58,61 @@ bool OpenExternal(
return platform_util::OpenExternal(url, activate);
}
#if defined(OS_WIN)
bool WriteShortcutLink(const base::FilePath& shortcut_path,
mate::Arguments* args) {
base::win::ShortcutOperation operation = base::win::SHORTCUT_CREATE_ALWAYS;
args->GetNext(&operation);
mate::Dictionary options = mate::Dictionary::CreateEmpty(args->isolate());
if (!args->GetNext(&options)) {
args->ThrowError();
return false;
}
base::win::ShortcutProperties properties;
base::FilePath path;
base::string16 str;
int index;
if (options.Get("target", &path))
properties.set_target(path);
if (options.Get("cwd", &path))
properties.set_working_dir(path);
if (options.Get("args", &str))
properties.set_arguments(str);
if (options.Get("description", &str))
properties.set_description(str);
if (options.Get("icon", &path) && options.Get("iconIndex", &index))
properties.set_icon(path, index);
if (options.Get("appUserModelId", &str))
properties.set_app_id(str);
base::win::ScopedCOMInitializer com_initializer;
return base::win::CreateOrUpdateShortcutLink(
shortcut_path, properties, operation);
}
v8::Local<v8::Value> ReadShortcutLink(mate::Arguments* args,
const base::FilePath& path) {
using base::win::ShortcutProperties;
mate::Dictionary options = mate::Dictionary::CreateEmpty(args->isolate());
base::win::ScopedCOMInitializer com_initializer;
base::win::ShortcutProperties properties;
if (!base::win::ResolveShortcutProperties(
path, ShortcutProperties::PROPERTIES_ALL, &properties)) {
args->ThrowError("Failed to read shortcut link");
return v8::Null(args->isolate());
}
options.Set("target", properties.target);
options.Set("cwd", properties.working_dir);
options.Set("args", properties.arguments);
options.Set("description", properties.description);
options.Set("icon", properties.icon);
options.Set("iconIndex", properties.icon_index);
options.Set("appUserModelId", properties.app_id);
return options.GetHandle();
}
#endif
void Initialize(v8::Local<v8::Object> exports, v8::Local<v8::Value> unused,
v8::Local<v8::Context> context, void* priv) {
mate::Dictionary dict(context->GetIsolate(), exports);
@ -38,6 +121,10 @@ void Initialize(v8::Local<v8::Object> exports, v8::Local<v8::Value> unused,
dict.SetMethod("openExternal", &OpenExternal);
dict.SetMethod("moveItemToTrash", &platform_util::MoveItemToTrash);
dict.SetMethod("beep", &platform_util::Beep);
#if defined(OS_WIN)
dict.SetMethod("writeShortcutLink", &WriteShortcutLink);
dict.SetMethod("readShortcutLink", &ReadShortcutLink);
#endif
}
} // namespace

View file

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

View file

@ -138,7 +138,8 @@ void UnregisterNonABICompliantCodeRange(void* start) {
} // namespace
CrashReporterWin::CrashReporterWin()
: skip_system_crash_handler_(false) {
: skip_system_crash_handler_(false),
code_range_registered_(false) {
}
CrashReporterWin::~CrashReporterWin() {
@ -189,19 +190,20 @@ void CrashReporterWin::InitBreakpad(const std::string& product_name,
LOG(ERROR) << "Cannot initialize out-of-process crash handler";
#ifdef _WIN64
bool registered = false;
// Hook up V8 to breakpad.
{
if (!code_range_registered_) {
code_range_registered_ = true;
// gin::Debug::SetCodeRangeCreatedCallback only runs the callback when
// Isolate is just created, so we have to manually run following code here.
void* code_range = nullptr;
size_t size = 0;
v8::Isolate::GetCurrent()->GetCodeRange(&code_range, &size);
if (code_range && size)
registered = RegisterNonABICompliantCodeRange(code_range, size);
if (code_range && size &&
RegisterNonABICompliantCodeRange(code_range, size)) {
gin::Debug::SetCodeRangeDeletedCallback(
UnregisterNonABICompliantCodeRange);
}
}
if (registered)
gin::Debug::SetCodeRangeDeletedCallback(UnregisterNonABICompliantCodeRange);
#endif
}

View file

@ -62,6 +62,7 @@ class CrashReporterWin : public CrashReporter {
google_breakpad::CustomClientInfo custom_info_;
bool skip_system_crash_handler_;
bool code_range_registered_;
std::unique_ptr<google_breakpad::ExceptionHandler> breakpad_;
DISALLOW_COPY_AND_ASSIGN(CrashReporterWin);

View file

@ -25,7 +25,7 @@ _リンクになっていないリストは未翻訳のものです。_
* [DevTools エクステンション](tutorial/devtools-extension.md)
* [Pepper Flashプラグインを使用する](tutorial/using-pepper-flash-plugin.md)
* [Widevine CDMプラグインを使用する](tutorial/using-widevine-cdm-plugin.md)
* Testing on Headless CI Systems (Travis, Jenkins) (tutorial/testing-on-headless-ci.md)
* [継続的インテグレーションシステムによるテスト(Travis, Jenkins)](tutorial/testing-on-headless-ci.md)
# チュートリアル

View file

@ -0,0 +1,43 @@
# 継続的インテグレーションシステムによるテスト(Travis CI, Jenkins)
Electron は Chromium を元にしているので、実行にはディスプレイドライバーが必要です。Chromium がディスプレイドライバーを見つけられないと、Electron は起動に失敗してテストの実行ができません。Travis や Circle、 Jenkins などの継続的インテグレーションシステムで Electron アプリをテストするにはちょっとした設定が必要です。端的に言うと仮想ディスプレイドライバーを使用します。
## 仮想ディスプレイサーバーの設定
まず [Xvfb](https://en.wikipedia.org/wiki/Xvfb) をインストールします(リンク先は英語)。Xvfb は X Window System のプロトコルを実装した仮想フレームバッファです。描画系の操作をスクリーン表示無しにメモリ内で行ってくれます。
その後 xvfb の仮想スクリーンを作成して、環境変数 `$DISPLAY` に作成した仮想スクリーンを指定します。Electron の Chromium は自動で`$DISPLAY`を見るので、アプリ側の設定は必要ありません。この手順は Paul Betts 氏が公開しているツール [xvfb-maybe](https://github.com/paulcbetts/xvfb-maybe) によって自動化されています。テスト実行コマンドの前に `xvfb-maybe` を追加するだけで、現在のシステムが必要とする xvfb の設定を自動で行ってくれます。xvfb の設定が必要ない Windows や macOS では何もしません。
```
## Windows や macOS では以下のコマンドは electron-mocha をただ起動するだけです。
## Linux でかつ GUI 環境でない場合、以下のコマンドは
## xvfb-run electron-mocha ./test/*.js と同等になります。
xvfb-maybe electron-mocha ./test/*.js
```
### Travis CI
Travis では `.travis.yml` を以下のようにするといいでしょう。
```yml
addons:
apt:
packages:
- xvfb
install:
- export DISPLAY=':99.0'
- Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
```
### Jenkins
Jenkins では [Xvfb plugin](https://wiki.jenkins-ci.org/display/JENKINS/Xvfb+Plugin) という便利なプラグインが利用可能です(リンク先は英語)。
### Circle CI
Circle CI では、なんと既に xvfb 及び `$DISPLAY` が設定されているので、[何の設定も必要ありません](https://circleci.com/docs/environment#browsers)(リンク先は英語)。
### AppVeyor
AppVeyor は Selenium や Chromium、Electron などをサポートしている Windows 上で動くので、特に設定は必要ありません。

View file

@ -47,7 +47,7 @@ win.show();
* `maximizable` Boolean - 窗口是否可以最大化. 在 Linux 上无效. 默认为 `true`.
* `closable` Boolean - 窗口是否可以关闭. 在 Linux 上无效. 默认为 `true`.
* `alwaysOnTop` Boolean - 窗口是否总是显示在其他窗口之前. 在 Linux 上无效. 默认为 `false`.
* `fullscreen` Boolean - 窗口是否可以全屏幕. 当明确设置值为When `false` ,全屏化按钮将会隐藏,在 macOS 将禁用. 默认 `false`.
* `fullscreen` Boolean - 窗口是否可以全屏幕. 当明确设置值为 `false` ,全屏化按钮将会隐藏,在 macOS 将禁用. 默认 `false`.
* `fullscreenable` Boolean - 在 macOS 上,全屏化按钮是否可用,默认为 `true`.
* `skipTaskbar` Boolean - 是否在任务栏中显示窗口. 默认是`false`.
* `kiosk` Boolean - kiosk 方式. 默认为 `false`.

View file

@ -8,7 +8,7 @@
* [Xcode](https://developer.apple.com/technologies/tools/) >= 5.1
* [node.js](http://nodejs.org) (外部)
如果你通过 Homebrew 使用 Python 下载,需要安装下面的 Python 模块:
如果你目前使用的Python是通过 Homebrew 安装的则你还需要安装如下Python模块:
* pyobjc

View file

@ -93,5 +93,6 @@ an issue:
* [Build Instructions (macOS)](development/build-instructions-osx.md)
* [Build Instructions (Windows)](development/build-instructions-windows.md)
* [Build Instructions (Linux)](development/build-instructions-linux.md)
* [Debug Instructions (Windows)](development/debug-instructions-windows.md)
* [Debug Instructions (macOS)](development/debug-instructions-windows.md)
* [Debug Instructions (Windows)](development/debug-instructions-macos.md)
* [Setting Up Symbol Server in debugger](development/setting-up-symbol-server.md)

View file

@ -6,10 +6,10 @@ The following example shows how to quit the application when the last window is
closed:
```javascript
const {app} = require('electron');
const {app} = require('electron')
app.on('window-all-closed', () => {
app.quit();
});
app.quit()
})
```
## Events
@ -192,15 +192,17 @@ certificate you should prevent the default behavior with
`event.preventDefault()` and call `callback(true)`.
```javascript
const {app} = require('electron')
app.on('certificate-error', (event, webContents, url, error, certificate, callback) => {
if (url === 'https://github.com') {
// Verification logic.
event.preventDefault();
callback(true);
event.preventDefault()
callback(true)
} else {
callback(false);
callback(false)
}
});
})
```
### Event: 'select-client-certificate'
@ -228,10 +230,12 @@ and `callback` needs to be called with an entry filtered from the list. Using
certificate from the store.
```javascript
const {app} = require('electron')
app.on('select-client-certificate', (event, webContents, url, list, callback) => {
event.preventDefault();
callback(list[0]);
});
event.preventDefault()
callback(list[0])
})
```
### Event: 'login'
@ -259,10 +263,12 @@ should prevent the default behavior with `event.preventDefault()` and call
`callback(username, password)` with the credentials.
```javascript
const {app} = require('electron')
app.on('login', (event, webContents, request, authInfo, callback) => {
event.preventDefault();
callback('username', 'secret');
});
event.preventDefault()
callback('username', 'secret')
})
```
### Event: 'gpu-process-crashed'
@ -331,6 +337,8 @@ An example of restarting current instance immediately and adding a new command
line argument to the new instance:
```javascript
const {app} = require('electron')
app.relaunch({args: process.argv.slice(1) + ['--relaunch']})
app.exit(0)
```
@ -537,24 +545,24 @@ An example of activating the window of primary instance when a second instance
starts:
```javascript
let myWindow = null;
const {app} = require('electron')
let myWindow = null
const shouldQuit = app.makeSingleInstance((commandLine, workingDirectory) => {
// Someone tried to run a second instance, we should focus our window.
if (myWindow) {
if (myWindow.isMinimized()) myWindow.restore();
myWindow.focus();
if (myWindow.isMinimized()) myWindow.restore()
myWindow.focus()
}
});
})
if (shouldQuit) {
app.quit();
return;
app.quit()
}
// Create myWindow, load the rest of the app, etc...
app.on('ready', () => {
});
})
```
### `app.releaseSingleInstance()`

View file

@ -6,8 +6,8 @@
// In the main process.
const {BrowserWindow} = require('electron')
// Or in the renderer process.
const {BrowserWindow} = require('electron').remote
// Or use `remote` from the renderer process.
// const {BrowserWindow} = require('electron').remote
let win = new BrowserWindow({width: 800, height: 600})
win.on('closed', () => {
@ -35,6 +35,7 @@ process has done drawing for the first time, showing window after this event
will have no visual flash:
```javascript
const {BrowserWindow} = require('electron')
let win = new BrowserWindow({show: false})
win.once('ready-to-show', () => {
win.show()
@ -52,6 +53,8 @@ the app feel slow. In this case, it is recommended to show the window
immediately, and use a `backgroundColor` close to your app's background:
```javascript
const {BrowserWindow} = require('electron')
let win = new BrowserWindow({backgroundColor: '#2e2c29'})
win.loadURL('https://github.com')
```
@ -64,8 +67,12 @@ to set `backgroundColor` to make app feel more native.
By using `parent` option, you can create child windows:
```javascript
const {BrowserWindow} = require('electron')
let top = new BrowserWindow()
let child = new BrowserWindow({parent: top})
child.show()
top.show()
```
The `child` window will always show on top of the `top` window.
@ -76,6 +83,8 @@ A modal window is a child window that disables parent window, to create a modal
window, you have to set both `parent` and `modal` options:
```javascript
const {BrowserWindow} = require('electron')
let child = new BrowserWindow({parent: top, modal: true, show: false})
child.loadURL('https://github.com')
child.once('ready-to-show', () => {
@ -310,14 +319,14 @@ close. For example:
```javascript
window.onbeforeunload = (e) => {
console.log('I do not want to be closed');
console.log('I do not want to be closed')
// Unlike usual browsers that a message box will be prompted to users, returning
// a non-void value will silently cancel the close.
// It is recommended to use the dialog API to let the user confirm closing the
// application.
e.returnValue = false;
};
e.returnValue = false
}
```
#### Event: 'closed'
@ -416,12 +425,14 @@ Commands are lowercased, underscores are replaced with hyphens, and the
e.g. `APPCOMMAND_BROWSER_BACKWARD` is emitted as `browser-backward`.
```javascript
someWindow.on('app-command', (e, cmd) => {
const {BrowserWindow} = require('electron')
let win = new BrowserWindow()
win.on('app-command', (e, cmd) => {
// Navigate the window back when the user hits their mouse back button
if (cmd === 'browser-backward' && someWindow.webContents.canGoBack()) {
someWindow.webContents.goBack();
if (cmd === 'browser-backward' && win.webContents.canGoBack()) {
win.webContents.goBack()
}
});
})
```
#### Event: 'scroll-touch-begin' _macOS_
@ -498,7 +509,10 @@ an Object containing `name` and `version` properties.
To check if a DevTools extension is installed you can run the following:
```javascript
const {BrowserWindow} = require('electron')
let installed = BrowserWindow.getDevToolsExtensions().hasOwnProperty('devtron')
console.log(installed)
```
**Note:** This API cannot be called before the `ready` event of the `app` module
@ -509,8 +523,10 @@ is emitted.
Objects created with `new BrowserWindow` have the following properties:
```javascript
const {BrowserWindow} = require('electron')
// In this example `win` is our instance
let win = new BrowserWindow({width: 800, height: 600});
let win = new BrowserWindow({width: 800, height: 600})
win.loadURL('https://github.com')
```
#### `win.webContents`
@ -613,7 +629,7 @@ Returns a boolean, whether the window is in fullscreen mode.
#### `win.setAspectRatio(aspectRatio[, extraSize])` _macOS_
* `aspectRatio` The aspect ratio to maintain for some portion of the
* `aspectRatio` Float - The aspect ratio to maintain for some portion of the
content view.
* `extraSize` Object (optional) - The extra size not to be included while
maintaining the aspect ratio.
@ -806,13 +822,19 @@ window.
#### `win.setSheetOffset(offsetY[, offsetX])` _macOS_
* `offsetY` Float
* `offsetX` Float (optional)
Changes the attachment point for sheets on macOS. By default, sheets are
attached just below the window frame, but you may want to display them beneath
a HTML-rendered toolbar. For example:
```javascript
let toolbarRect = document.getElementById('toolbar').getBoundingClientRect();
win.setSheetOffset(toolbarRect.height);
const {BrowserWindow} = require('electron')
let win = new BrowserWindow()
let toolbarRect = document.getElementById('toolbar').getBoundingClientRect()
win.setSheetOffset(toolbarRect.height)
```
#### `win.flashFrame(flag)`

View file

@ -7,13 +7,13 @@ your app's main script before the [ready][ready] event of the [app][app] module
is emitted:
```javascript
const {app} = require('electron');
app.commandLine.appendSwitch('remote-debugging-port', '8315');
app.commandLine.appendSwitch('host-rules', 'MAP * 127.0.0.1');
const {app} = require('electron')
app.commandLine.appendSwitch('remote-debugging-port', '8315')
app.commandLine.appendSwitch('host-rules', 'MAP * 127.0.0.1')
app.on('ready', () => {
// Your code here
});
})
```
## --ignore-connections-limit=`domains`
@ -57,6 +57,7 @@ list of hosts. This flag has an effect only if used in tandem with
For example:
```javascript
const {app} = require('electron')
app.commandLine.appendSwitch('proxy-bypass-list', '<local>;*.google.com;*foo.com;1.2.3.4:5678')
```

View file

@ -5,16 +5,17 @@
The following example shows how to write a string to the clipboard:
```javascript
const {clipboard} = require('electron');
clipboard.writeText('Example String');
const {clipboard} = require('electron')
clipboard.writeText('Example String')
```
On X Window systems, there is also a selection clipboard. To manipulate it
you need to pass `selection` to each method:
```javascript
clipboard.writeText('Example String', 'selection');
console.log(clipboard.readText('selection'));
const {clipboard} = require('electron')
clipboard.writeText('Example String', 'selection')
console.log(clipboard.readText('selection'))
```
## Methods
@ -109,7 +110,8 @@ Returns an array of supported formats for the clipboard `type`.
Returns whether the clipboard supports the format of specified `data`.
```javascript
console.log(clipboard.has('<p>selection</p>'));
const {clipboard} = require('electron')
console.log(clipboard.has('<p>selection</p>'))
```
### `clipboard.read(data[, type])` _Experimental_
@ -130,6 +132,7 @@ Reads `data` from the clipboard.
* `type` String (optional)
```javascript
clipboard.write({text: 'test', html: "<b>test</b>"});
const {clipboard} = require('electron')
clipboard.write({text: 'test', html: '<b>test</b>'})
```
Writes `data` to the clipboard.

View file

@ -8,22 +8,22 @@ This module does not include a web interface so you need to open
result.
```javascript
const {contentTracing} = require('electron');
const {contentTracing} = require('electron')
const options = {
categoryFilter: '*',
traceOptions: 'record-until-full,enable-sampling'
};
}
contentTracing.startRecording(options, () => {
console.log('Tracing started');
console.log('Tracing started')
setTimeout(() => {
contentTracing.stopRecording('', (path) => {
console.log('Tracing data recorded to ' + path);
});
}, 5000);
});
console.log('Tracing data recorded to ' + path)
})
}, 5000)
})
```
## Methods

View file

@ -6,14 +6,14 @@ The following is an example of automatically submitting a crash report to a
remote server:
```javascript
const {crashReporter} = require('electron');
const {crashReporter} = require('electron')
crashReporter.start({
productName: 'YourName',
companyName: 'YourCompany',
submitURL: 'https://your-domain.com/url-to-submit',
autoSubmit: true
});
})
```
For setting up a server to accept and process crash reports, you can use

View file

@ -1,14 +1,17 @@
# desktopCapturer
> List `getUserMedia` sources for capturing audio, video, and images from a
microphone, camera, or screen.
> Access information about media sources that can be used to capture audio and
> video from the desktop using the [`navigator.webkitGetUserMedia`] API.
The following example shows how to capture video from a desktop window whose
title is `Electron`:
```javascript
// In the renderer process.
const {desktopCapturer} = require('electron');
const {desktopCapturer} = require('electron')
desktopCapturer.getSources({types: ['window', 'screen']}, (error, sources) => {
if (error) throw error;
if (error) throw error
for (let i = 0; i < sources.length; ++i) {
if (sources[i].name === 'Electron') {
navigator.webkitGetUserMedia({
@ -23,29 +26,28 @@ desktopCapturer.getSources({types: ['window', 'screen']}, (error, sources) => {
maxHeight: 720
}
}
}, gotStream, getUserMediaError);
return;
}, handleStream, handleError)
return
}
}
});
})
function gotStream(stream) {
document.querySelector('video').src = URL.createObjectURL(stream);
function handleStream (stream) {
document.querySelector('video').src = URL.createObjectURL(stream)
}
function getUserMediaError(e) {
console.log('getUserMediaError');
function handleError (e) {
console.log(e)
}
```
When creating a constraints object for the `navigator.webkitGetUserMedia` call,
if you are using a source from `desktopCapturer` your `chromeMediaSource` must
be set to `"desktop"` and your `audio` must be set to `false`.
To capture video from a source provided by `desktopCapturer` the constraints
passed to [`navigator.webkitGetUserMedia`] must include
`chromeMediaSource: 'desktop'`, and `audio: false`.
If you wish to
capture the audio and video from the entire desktop you can set
`chromeMediaSource` to `"screen"` and `audio` to `true`. When using this method
you cannot specify a `chromeMediaSourceId`.
To capture both audio and video from the entire desktop the constraints passed
to [`navigator.webkitGetUserMedia`] must include `chromeMediaSource: 'screen'`,
and `audio: true`, but should not include a `chromeMediaSourceId` constraint.
## Methods
@ -56,24 +58,28 @@ The `desktopCapturer` module has the following methods:
* `options` Object
* `types` Array - An array of String that lists the types of desktop sources
to be captured, available types are `screen` and `window`.
* `thumbnailSize` Object (optional) - The suggested size that thumbnail should
be scaled, it is `{width: 150, height: 150}` by default.
* `thumbnailSize` Object (optional) - The suggested size that the media source
thumbnail should be scaled to, defaults to `{width: 150, height: 150}`.
* `callback` Function
Starts a request to get all desktop sources, `callback` will be called with
`callback(error, sources)` when the request is completed.
Starts gathering information about all available desktop media sources,
and calls `callback(error, sources)` when finished.
The `sources` is an array of `Source` objects, each `Source` represents a
captured screen or individual window, and has following properties:
`sources` is an array of `Source` objects, each `Source` represents a
screen or an individual window that can be captured, and has the following
properties:
* `id` String - The id of the captured window or screen used in
`navigator.webkitGetUserMedia`. The format looks like `window:XX` or
`screen:XX` where `XX` is a random generated number.
* `name` String - The described name of the capturing screen or window. If the
source is a screen, the name will be `Entire Screen` or `Screen <index>`; if
it is a window, the name will be the window's title.
* `thumbnail` [NativeImage](native-image.md) - A thumbnail native image.
* `id` String - The identifier of a window or screen that can be used as a
`chromeMediaSourceId` constraint when calling
[`navigator.webkitGetUserMedia`]. The format of the identifier will be
`window:XX` or `screen:XX`, where `XX` is a random generated number.
* `name` String - A screen source will be named either `Entire Screen` or
`Screen <index>`, while the name of a window source will match the window
title.
* `thumbnail` [NativeImage](native-image.md) - A thumbnail image. **Note:**
There is no guarantee that the size of the thumbnail is the same as the
`thumnbailSize` specified in the `options` passed to
`desktopCapturer.getSources`. The actual size depends on the scale of the
screen or window.
**Note:** There is no guarantee that the size of `source.thumbnail` is always
the same as the `thumnbailSize` in `options`. It also depends on the scale of
the screen or window.
[`navigator.webkitGetUserMedia`]: https://developer.mozilla.org/en/docs/Web/API/Navigator/getUserMedia

View file

@ -5,17 +5,16 @@
An example of showing a dialog to select multiple files and directories:
```javascript
let win = ...; // BrowserWindow in which to show the dialog
const {dialog} = require('electron');
console.log(dialog.showOpenDialog({properties: ['openFile', 'openDirectory', 'multiSelections']}));
const {dialog} = require('electron')
console.log(dialog.showOpenDialog({properties: ['openFile', 'openDirectory', 'multiSelections']}))
```
The Dialog is opened from Electron's main thread. If you want to use the dialog
object from a renderer process, remember to access it using the remote:
```javascript
const {dialog} = require('electron').remote;
const {dialog} = require('electron').remote
console.log(dialog)
```
## Methods
@ -42,7 +41,7 @@ otherwise it returns `undefined`.
The `filters` specifies an array of file types that can be displayed or
selected when you want to limit the user to a specific type. For example:
```javascript
```
{
filters: [
{name: 'Images', extensions: ['jpg', 'png', 'gif']},

View file

@ -8,6 +8,8 @@ control the download item.
```javascript
// In the main process.
const {BrowserWindow} = require('electron')
let win = new BrowserWindow()
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')
@ -78,6 +80,12 @@ The API is only available in session's `will-download` callback function.
If user doesn't set the save path via the API, Electron will use the original
routine to determine the save path(Usually prompts a save dialog).
### `downloadItem.getSavePath()`
Returns the save path of the download item. This will be either the path
set via `downloadItem.setSavePath(path)` or the path selected from the shown
save dialog.
### `downloadItem.pause()`
Pauses the download.

View file

@ -15,19 +15,19 @@ Example on getting a real path from a dragged-onto-the-app file:
</div>
<script>
const holder = document.getElementById('holder');
const holder = document.getElementById('holder')
holder.ondragover = () => {
return false;
};
}
holder.ondragleave = holder.ondragend = () => {
return false;
};
}
holder.ondrop = (e) => {
e.preventDefault();
e.preventDefault()
for (let f of e.dataTransfer.files) {
console.log('File(s) you dragged here: ', f.path);
console.log('File(s) you dragged here: ', f.path)
}
return false;
};
}
</script>
```

View file

@ -16,6 +16,7 @@ To create a frameless window, you need to set `frame` to `false` in
```javascript
const {BrowserWindow} = require('electron')
let win = new BrowserWindow({width: 800, height: 600, frame: false})
win.show()
```
### Alternatives on macOS
@ -28,7 +29,9 @@ the window controls ("traffic lights") for standard window actions.
You can do so by specifying the new `titleBarStyle` option:
```javascript
const {BrowserWindow} = require('electron')
let win = new BrowserWindow({titleBarStyle: 'hidden'})
win.show()
```
## Transparent window
@ -37,7 +40,9 @@ By setting the `transparent` option to `true`, you can also make the frameless
window transparent:
```javascript
const {BrowserWindow} = require('electron')
let win = new BrowserWindow({transparent: true, frame: false})
win.show()
```
### Limitations
@ -66,6 +71,8 @@ events, you can call the [win.setIgnoreMouseEvents(ignore)][ignore-mouse-events]
API:
```javascript
const {BrowserWindow} = require('electron')
let win = new BrowserWindow()
win.setIgnoreMouseEvents(true)
```

View file

@ -11,29 +11,29 @@ not have the keyboard focus. You should not use this module until the `ready`
event of the app module is emitted.
```javascript
const {app, globalShortcut} = require('electron');
const {app, globalShortcut} = require('electron')
app.on('ready', () => {
// Register a 'CommandOrControl+X' shortcut listener.
const ret = globalShortcut.register('CommandOrControl+X', () => {
console.log('CommandOrControl+X is pressed');
});
console.log('CommandOrControl+X is pressed')
})
if (!ret) {
console.log('registration failed');
console.log('registration failed')
}
// Check whether a shortcut is registered.
console.log(globalShortcut.isRegistered('CommandOrControl+X'));
});
console.log(globalShortcut.isRegistered('CommandOrControl+X'))
})
app.on('will-quit', () => {
// Unregister a shortcut.
globalShortcut.unregister('CommandOrControl+X');
globalShortcut.unregister('CommandOrControl+X')
// Unregister all shortcuts.
globalShortcut.unregisterAll();
});
globalShortcut.unregisterAll()
})
```
## Methods

View file

@ -23,27 +23,27 @@ processes:
```javascript
// In main process.
const {ipcMain} = require('electron');
const {ipcMain} = require('electron')
ipcMain.on('asynchronous-message', (event, arg) => {
console.log(arg); // prints "ping"
event.sender.send('asynchronous-reply', 'pong');
});
console.log(arg) // prints "ping"
event.sender.send('asynchronous-reply', 'pong')
})
ipcMain.on('synchronous-message', (event, arg) => {
console.log(arg); // prints "ping"
event.returnValue = 'pong';
});
console.log(arg) // prints "ping"
event.returnValue = 'pong'
})
```
```javascript
// In renderer process (web page).
const {ipcRenderer} = require('electron');
console.log(ipcRenderer.sendSync('synchronous-message', 'ping')); // prints "pong"
const {ipcRenderer} = require('electron')
console.log(ipcRenderer.sendSync('synchronous-message', 'ping')) // prints "pong"
ipcRenderer.on('asynchronous-reply', (event, arg) => {
console.log(arg); // prints "pong"
});
ipcRenderer.send('asynchronous-message', 'ping');
console.log(arg) // prints "pong"
})
ipcRenderer.send('asynchronous-message', 'ping')
```
## Listening for Messages

View file

@ -15,18 +15,18 @@ the user right clicks the page:
```html
<!-- index.html -->
<script>
const {remote} = require('electron');
const {remote} = require('electron')
const {Menu, MenuItem} = remote;
const menu = new Menu();
menu.append(new MenuItem({label: 'MenuItem1', click() { console.log('item 1 clicked'); }}));
menu.append(new MenuItem({type: 'separator'}));
menu.append(new MenuItem({label: 'MenuItem2', type: 'checkbox', checked: true}));
const menu = new Menu()
menu.append(new MenuItem({label: 'MenuItem1', click() { console.log('item 1 clicked') }}))
menu.append(new MenuItem({type: 'separator'}))
menu.append(new MenuItem({label: 'MenuItem2', type: 'checkbox', checked: true}))
window.addEventListener('contextmenu', (e) => {
e.preventDefault();
menu.popup(remote.getCurrentWindow());
}, false);
e.preventDefault()
menu.popup(remote.getCurrentWindow())
}, false)
</script>
```
@ -34,6 +34,8 @@ An example of creating the application menu in the render process with the
simple template API:
```javascript
const {Menu} = require('electron')
const template = [
{
label: 'Edit',
@ -64,7 +66,7 @@ const template = [
},
{
role: 'selectall'
},
}
]
},
{
@ -74,7 +76,7 @@ const template = [
label: 'Reload',
accelerator: 'CmdOrCtrl+R',
click (item, focusedWindow) {
if (focusedWindow) focusedWindow.reload();
if (focusedWindow) focusedWindow.reload()
}
},
{
@ -84,10 +86,9 @@ const template = [
label: 'Toggle Developer Tools',
accelerator: process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I',
click (item, focusedWindow) {
if (focusedWindow)
focusedWindow.webContents.toggleDevTools();
if (focusedWindow) focusedWindow.webContents.toggleDevTools()
}
}
},
]
},
{
@ -98,7 +99,7 @@ const template = [
},
{
role: 'close'
},
}
]
},
{
@ -106,14 +107,14 @@ const template = [
submenu: [
{
label: 'Learn More',
click() { require('electron').shell.openExternal('http://electron.atom.io'); }
},
click () { require('electron').shell.openExternal('http://electron.atom.io') }
}
]
}
]
},
];
if (process.platform === 'darwin') {
const name = require('electron').remote.app.getName();
const name = require('electron').remote.app.getName()
template.unshift({
label: name,
submenu: [
@ -144,9 +145,9 @@ if (process.platform === 'darwin') {
},
{
role: 'quit'
},
}
]
});
})
// Window menu.
template[3].submenu = [
{
@ -170,11 +171,11 @@ if (process.platform === 'darwin') {
label: 'Bring All to Front',
role: 'front'
}
];
]
}
const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
const menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)
```
## Class: Menu
@ -227,16 +228,14 @@ The `menu` object has the following instance methods:
#### `menu.popup([browserWindow, x, y, positioningItem])`
* `browserWindow` BrowserWindow (optional) - Default is `null`.
* `x` Number (optional) - Default is -1.
* `y` Number (**required** if `x` is used) - Default is -1.
* `browserWindow` BrowserWindow (optional) - Default is `BrowserWindow.getFocusedWindow()`.
* `x` Number (optional) - Default is the current mouse cursor position.
* `y` Number (**required** if `x` is used) - Default is the current mouse cursor position.
* `positioningItem` Number (optional) _macOS_ - The index of the menu item to
be positioned under the mouse cursor at the specified coordinates. Default is
-1.
Pops up this menu as a context menu in the `browserWindow`. You can optionally
provide a `x, y` coordinate to place the menu at, otherwise it will be placed
at the current mouse cursor position.
Pops up this menu as a context menu in the `browserWindow`.
#### `menu.append(menuItem)`
@ -322,7 +321,7 @@ the first item.
Template:
```javascript
```
[
{label: '4', id: '4'},
{label: '5', id: '5'},
@ -344,7 +343,7 @@ Menu:
Template:
```javascript
```
[
{label: 'a', position: 'endof=letters'},
{label: '1', position: 'endof=numbers'},

View file

@ -9,15 +9,20 @@ For example, when creating a tray or setting a window's icon, you can pass an
image file path as a `String`:
```javascript
const {BrowserWindow, Tray} = require('electron')
const appIcon = new Tray('/Users/somebody/images/icon.png')
let win = new BrowserWindow({icon: '/Users/somebody/images/window.png'})
console.log(appIcon, win)
```
Or read the image from the clipboard which returns a `nativeImage`:
```javascript
const {clipboard, Tray} = require('electron')
const image = clipboard.readImage()
const appIcon = new Tray(image)
console.log(appIcon)
```
## Supported Formats
@ -30,6 +35,8 @@ quality it is recommended to include at least the following sizes in the icon:
* 16x16
* 32x32
* 40x40
* 48x48
* 64x64
* 256x256
@ -55,7 +62,9 @@ images/
```javascript
const {Tray} = require('electron')
let appIcon = new Tray('/Users/somebody/images/icon.png')
console.log(appIcon)
```
Following suffixes for DPI are also supported:
@ -105,8 +114,10 @@ Creates an empty `NativeImage` instance.
Creates a new `NativeImage` instance from a file located at `path`.
```javascript
const {nativeImage} = require('electron')
const nativeImage = require('electron').nativeImage
let image = nativeImage.createFromPath('/Users/somebody/images/icon.png')
console.log(image)
```
### `nativeImage.createFromBuffer(buffer[, scaleFactor])`

View file

@ -8,11 +8,13 @@ event of the `app` module is emitted.
For example:
```javascript
const {app} = require('electron')
app.on('ready', () => {
require('electron').powerMonitor.on('suspend', () => {
console.log('The system is going to sleep');
});
});
console.log('The system is going to sleep')
})
})
```
## Events

View file

@ -5,12 +5,12 @@
For example:
```javascript
const {powerSaveBlocker} = require('electron');
const {powerSaveBlocker} = require('electron')
const id = powerSaveBlocker.start('prevent-display-sleep');
console.log(powerSaveBlocker.isStarted(id));
const id = powerSaveBlocker.start('prevent-display-sleep')
console.log(powerSaveBlocker.isStarted(id))
powerSaveBlocker.stop(id);
powerSaveBlocker.stop(id)
```
## Methods

View file

@ -16,12 +16,12 @@ the global scope when node integration is turned off:
```javascript
// preload.js
const _setImmediate = setImmediate;
const _clearImmediate = clearImmediate;
const _setImmediate = setImmediate
const _clearImmediate = clearImmediate
process.once('loaded', () => {
global.setImmediate = _setImmediate;
global.clearImmediate = _clearImmediate;
});
global.setImmediate = _setImmediate
global.clearImmediate = _clearImmediate
})
```
## Properties

View file

@ -6,19 +6,19 @@ An example of implementing a protocol that has the same effect as the
`file://` protocol:
```javascript
const {app, protocol} = require('electron');
const path = require('path');
const {app, protocol} = require('electron')
const path = require('path')
app.on('ready', () => {
protocol.registerFileProtocol('atom', (request, callback) => {
const url = request.url.substr(7);
callback({path: path.normalize(__dirname + '/' + url)});
const url = request.url.substr(7)
callback({path: path.normalize(`${__dirname}/${url}`)})
}, (error) => {
if (error)
console.error('Failed to register protocol');
});
});
if (error) console.error('Failed to register protocol')
})
})
```
**Note:** All methods unless specified can only be used after the `ready` event
of the `app` module gets emitted.
@ -52,10 +52,12 @@ So if you want to register a custom protocol to replace the `http` protocol, you
have to register it as standard scheme:
```javascript
protocol.registerStandardSchemes(['atom']);
const {app, protocol} = require('electron')
protocol.registerStandardSchemes(['atom'])
app.on('ready', () => {
protocol.registerHttpProtocol('atom', ...);
});
protocol.registerHttpProtocol('atom', '...')
})
```
**Note:** This method can only be used before the `ready` event of the `app`
@ -119,12 +121,13 @@ should be called with either a `Buffer` object or an object that has the `data`,
Example:
```javascript
const {protocol} = require('electron')
protocol.registerBufferProtocol('atom', (request, callback) => {
callback({mimeType: 'text/html', data: new Buffer('<h5>Response</h5>')});
callback({mimeType: 'text/html', data: new Buffer('<h5>Response</h5>')})
}, (error) => {
if (error)
console.error('Failed to register protocol');
});
if (error) console.error('Failed to register protocol')
})
```
### `protocol.registerStringProtocol(scheme, handler[, completion])`

View file

@ -14,10 +14,9 @@ similar to Java's [RMI][rmi]. An example of creating a browser window from a
renderer process:
```javascript
const {BrowserWindow} = require('electron').remote;
let win = new BrowserWindow({width: 800, height: 600});
win.loadURL('https://github.com');
const {BrowserWindow} = require('electron').remote
let win = new BrowserWindow({width: 800, height: 600})
win.loadURL('https://github.com')
```
**Note:** for the reverse (access the renderer process from the main process),
@ -70,23 +69,22 @@ For instance you can't use a function from the renderer process in an
```javascript
// main process mapNumbers.js
exports.withRendererCallback = (mapper) => {
return [1,2,3].map(mapper);
};
return [1, 2, 3].map(mapper)
}
exports.withLocalCallback = () => {
return [1,2,3].map(x => x + 1);
};
return [1, 2, 3].map(x => x + 1)
}
```
```javascript
// renderer process
const mapNumbers = require('electron').remote.require('./mapNumbers');
const mapNumbers = require('electron').remote.require('./mapNumbers')
const withRendererCb = mapNumbers.withRendererCallback(x => x + 1)
const withLocalCb = mapNumbers.withLocalCallback()
const withRendererCb = mapNumbers.withRendererCallback(x => x + 1);
const withLocalCb = mapNumbers.withLocalCallback();
console.log(withRendererCb, withLocalCb); // [undefined, undefined, undefined], [2, 3, 4]
console.log(withRendererCb, withLocalCb)
// [undefined, undefined, undefined], [2, 3, 4]
```
As you can see, the renderer callback's synchronous return value was not as
@ -100,9 +98,9 @@ For example, the following code seems innocent at first glance. It installs a
callback for the `close` event on a remote object:
```javascript
remote.getCurrentWindow().on('close', () => {
// blabla...
});
require('electron').remote.getCurrentWindow().on('close', () => {
// window was closed...
})
```
But remember the callback is referenced by the main process until you
@ -124,7 +122,8 @@ The built-in modules in the main process are added as getters in the `remote`
module, so you can use them directly like the `electron` module.
```javascript
const app = remote.app;
const app = require('electron').remote.app
console.log(app)
```
## Methods

View file

@ -21,7 +21,8 @@ let win
app.on('ready', () => {
const {width, height} = electron.screen.getPrimaryDisplay().workAreaSize
win = new BrowserWindow({width, height})
});
win.loadURL('https://github.com')
})
```
Another example of creating a window in the external display:
@ -43,6 +44,7 @@ app.on('ready', () => {
x: externalDisplay.bounds.x + 50,
y: externalDisplay.bounds.y + 50
})
win.loadURL('https://github.com')
}
})
```

View file

@ -8,12 +8,13 @@ You can also access the `session` of existing pages by using the `session`
property of [`WebContents`](web-contents.md), or from the `session` module.
```javascript
const {session, BrowserWindow} = require('electron')
const {BrowserWindow} = require('electron')
let win = new BrowserWindow({width: 800, height: 600})
win.loadURL('http://github.com')
const ses = win.webContents.session
console.log(ses.getUserAgent())
```
## Methods
@ -52,9 +53,9 @@ Returns the default session object of the app.
You can create a `Session` object in the `session` module:
```javascript
const session = require('electron').session;
const ses = session.fromPartition('persist:name');
const {session} = require('electron')
const ses = session.fromPartition('persist:name')
console.log(ses.getUserAgent())
```
### Instance Events
@ -73,12 +74,13 @@ Calling `event.preventDefault()` will cancel the download and `item` will not be
available from next tick of the process.
```javascript
const {session} = require('electron')
session.defaultSession.on('will-download', (event, item, webContents) => {
event.preventDefault();
event.preventDefault()
require('request')(item.getURL(), (data) => {
require('fs').writeFileSync('/somewhere', data);
});
});
require('fs').writeFileSync('/somewhere', data)
})
})
```
### Instance Methods
@ -223,10 +225,10 @@ window.webContents.session.enableNetworkEmulation({
latency: 500,
downloadThroughput: 6400,
uploadThroughput: 6400
});
})
// To emulate a network outage.
window.webContents.session.enableNetworkEmulation({offline: true});
window.webContents.session.enableNetworkEmulation({offline: true})
```
#### `ses.disableNetworkEmulation()`
@ -247,12 +249,12 @@ Calling `setCertificateVerifyProc(null)` will revert back to default certificate
verify proc.
```javascript
myWindow.webContents.session.setCertificateVerifyProc((hostname, cert, callback) => {
if (hostname === 'github.com')
callback(true);
else
callback(false);
});
const {BrowserWindow} = require('electron')
let win = new BrowserWindow()
win.webContents.session.setCertificateVerifyProc((hostname, cert, callback) => {
callback(hostname === 'github.com')
})
```
#### `ses.setPermissionRequestHandler(handler)`
@ -267,16 +269,14 @@ Sets the handler which can be used to respond to permission requests for the `se
Calling `callback(true)` will allow the permission and `callback(false)` will reject it.
```javascript
session.fromPartition(partition).setPermissionRequestHandler((webContents, permission, callback) => {
if (webContents.getURL() === host) {
if (permission === 'notifications') {
callback(false); // denied.
return;
}
const {session} = require('electron')
session.fromPartition('some-partition').setPermissionRequestHandler((webContents, permission, callback) => {
if (webContents.getURL() === 'some-host' && permission === 'notifications') {
return callback(false) // denied.
}
callback(true);
});
callback(true)
})
```
#### `ses.clearHostResolverCache([callback])`
@ -294,6 +294,7 @@ Dynamically sets whether to always send credentials for HTTP NTLM or Negotiate
authentication.
```javascript
const {session} = require('electron')
// consider any url ending with `example.com`, `foobar.com`, `baz`
// for integrated authentication.
session.defaultSession.allowNTLMCredentialsForDomains('*example.com, *foobar.com, *baz')
@ -340,13 +341,12 @@ const {app, session} = require('electron')
const path = require('path')
app.on('ready', function () {
const protocol = session.fromPartition(partitionName).protocol
const protocol = session.fromPartition('some-partition').protocol
protocol.registerFileProtocol('atom', function (request, callback) {
var url = request.url.substr(7)
callback({path: path.normalize(__dirname + '/' + url)})
callback({path: path.normalize(`${__dirname}/${url}`)})
}, function (error) {
if (error)
console.error('Failed to register protocol')
if (error) console.error('Failed to register protocol')
})
})
```
@ -359,22 +359,23 @@ The `Cookies` class gives you ability to query and modify cookies. Instances of
For example:
```javascript
const {session} = require('electron')
// Query all cookies.
session.defaultSession.cookies.get({}, (error, cookies) => {
console.log(cookies)
console.log(error, cookies)
})
// Query all cookies associated with a specific url.
session.defaultSession.cookies.get({url: 'http://www.github.com'}, (error, cookies) => {
console.log(cookies)
console.log(error, cookies)
})
// Set a cookie with the given cookie data;
// may overwrite equivalent cookies if they exist.
const cookie = {url: 'http://www.github.com', name: 'dummy_name', value: 'dummy'}
session.defaultSession.cookies.set(cookie, (error) => {
if (error)
console.error(error)
if (error) console.error(error)
})
```
@ -464,13 +465,15 @@ called with an `response` object when `listener` has done its work.
An example of adding `User-Agent` header for requests:
```javascript
const {session} = require('electron')
// Modify the user agent for all requests to the following urls.
const filter = {
urls: ['https://*.github.com/*', '*://electron.github.io']
}
session.defaultSession.webRequest.onBeforeSendHeaders(filter, (details, callback) => {
details.requestHeaders['User-Agent'] = "MyAgent"
details.requestHeaders['User-Agent'] = 'MyAgent'
callback({cancel: false, requestHeaders: details.requestHeaders})
})
```

View file

@ -7,9 +7,9 @@ The `shell` module provides functions related to desktop integration.
An example of opening a URL in the user's default browser:
```javascript
const {shell} = require('electron');
const {shell} = require('electron')
shell.openExternal('https://github.com');
shell.openExternal('https://github.com')
```
## Methods
@ -48,3 +48,37 @@ Move the given file to trash and returns a boolean status for the operation.
### `shell.beep()`
Play the beep sound.
### `shell.writeShortcutLink(shortcutPath[, operation], options)` _Windows_
* `shortcutPath` String
* `operation` String (optional) - Default is `create`, can be one of followings:
* `create` - Creates a new shortcut, overwriting if necessary.
* `update` - Updates specified properties only on an existing shortcut.
* `replace` - Overwrites an existing shortcut, fails if the shortcut doesn't
exist.
* `options` Object
* `target` String - The target to launch from this shortcut.
* `cwd` String (optional) - The target to launch from this shortcut. Default
is empty.
* `args` String (optional) - The arguments to be applied to `target` when
launching from this shortcut. Default is empty.
* `description` String (optional) - The description of the shortcut. Default
is empty.
* `icon` String (optional) - The path to the icon, can be a DLL or EXE. `icon`
and `iconIndex` have to be set together. Default is empty, which uses the
target's icon.
* `iconIndex` Integer (optional) - The resource ID of icon when `icon` is a
DLL or EXE. Default is 0.
* `appUserModelId` String (optional) - The Application User Model ID. Default
is empty.
Creates or updates a shortcut link at `shortcutPath`. On success `true` is
returned, otherwise `false` is returned.
### `shell.readShortcutLink(shortcutPath)` _Windows_
Resolves the shortcut link at `shortcutPath`, an object is returned with the
fields described in the `options` of `shell.writeShortcutLink`.
An exception will be thrown when any error happens.

View file

@ -19,14 +19,13 @@ scripts to be able to use those modules.
The main process script is just like a normal Node.js script:
```javascript
const {app, BrowserWindow} = require('electron');
let win = null;
const {app, BrowserWindow} = require('electron')
let win = null
app.on('ready', () => {
win = new BrowserWindow({width: 800, height: 600});
win.loadURL('https://github.com');
});
win = new BrowserWindow({width: 800, height: 600})
win.loadURL('https://github.com')
})
```
The renderer process is no different than a normal web page, except for the
@ -37,8 +36,8 @@ extra ability to use node modules:
<html>
<body>
<script>
const {app} = require('electron').remote;
console.log(app.getVersion());
const {app} = require('electron').remote
console.log(app.getVersion())
</script>
</body>
</html>
@ -53,23 +52,43 @@ As of 0.37, you can use
built-in modules.
```javascript
const {app, BrowserWindow} = require('electron');
const {app, BrowserWindow} = require('electron')
let win
app.on('ready', () => {
win = new BrowserWindow()
win.loadURL('https://github.com')
})
```
If you need the entire `electron` module, you can require it and then using
destructuring to access the individual modules from `electron`.
```javascript
const electron = require('electron');
const {app, BrowserWindow} = electron;
const electron = require('electron')
const {app, BrowserWindow} = electron
let win
app.on('ready', () => {
win = new BrowserWindow()
win.loadURL('https://github.com')
})
```
This is equivalent to the following code:
```javascript
const electron = require('electron');
const app = electron.app;
const BrowserWindow = electron.BrowserWindow;
const electron = require('electron')
const app = electron.app
const BrowserWindow = electron.BrowserWindow
let win
app.on('ready', () => {
win = new BrowserWindow()
win.loadURL('https://github.com')
})
```
[gui]: https://en.wikipedia.org/wiki/Graphical_user_interface

View file

@ -3,8 +3,8 @@
> Get system preferences.
```javascript
const {systemPreferences} = require('electron');
console.log(systemPreferences.isDarkMode());
const {systemPreferences} = require('electron')
console.log(systemPreferences.isDarkMode())
```
## Methods
@ -79,23 +79,24 @@ An example of using it to determine if you should create a transparent window or
not (transparent windows won't work correctly when DWM composition is disabled):
```javascript
let browserOptions = {width: 1000, height: 800};
const {BrowserWindow, systemPreferences} = require('electron')
let browserOptions = {width: 1000, height: 800}
// Make the window transparent only if the platform supports it.
if (process.platform !== 'win32' || systemPreferences.isAeroGlassEnabled()) {
browserOptions.transparent = true;
browserOptions.frame = false;
browserOptions.transparent = true
browserOptions.frame = false
}
// Create the window.
let win = new BrowserWindow(browserOptions);
let win = new BrowserWindow(browserOptions)
// Navigate.
if (browserOptions.transparent) {
win.loadURL('file://' + __dirname + '/index.html');
win.loadURL(`file://${__dirname}/index.html`)
} else {
// No transparency, so we load a fallback that uses basic styles.
win.loadURL('file://' + __dirname + '/fallback.html');
win.loadURL(`file://${__dirname}/fallback.html`)
}
```

View file

@ -13,7 +13,7 @@ app.on('ready', () => {
{label: 'Item2', type: 'radio'},
{label: 'Item3', type: 'radio', checked: true},
{label: 'Item4', type: 'radio'}
]);
])
tray.setToolTip('This is my application.')
tray.setContextMenu(contextMenu)
})
@ -31,8 +31,18 @@ __Platform limitations:__
you have to call `setContextMenu` again. For example:
```javascript
contextMenu.items[2].checked = false;
appIcon.setContextMenu(contextMenu);
const {Menu, Tray} = require('electron')
const appIcon = new Tray('/path/to/my/icon')
const contextMenu = Menu.buildFromTemplate([
{label: 'Item1', type: 'radio'},
{label: 'Item2', type: 'radio'}
])
// Make a change to the context menu
contextMenu.items[2].checked = false
// Call this again for Linux because we modified the context menu
appIcon.setContextMenu(contextMenu)
```
* On Windows it is recommended to use `ICO` icons to get best visual effects.
@ -173,12 +183,36 @@ Sets the hover text for this tray icon.
Sets the title displayed aside of the tray icon in the status bar.
#### `tray.setHighlightMode(highlight)` _macOS_
#### `tray.setHighlightMode(mode)` _macOS_
* `highlight` Boolean
* `mode` String highlight mode with one of the following values:
* `'selection'` - Highlight the tray icon when it is clicked and also when
its context menu is open. This is the default.
* `'always'` - Always highlight the tray icon.
* `'never'` - Never highlight the tray icon.
Sets whether the tray icon's background becomes highlighted (in blue)
when the tray icon is clicked. Defaults to true.
Sets when the tray's icon background becomes highlighted (in blue).
**Note:** You can use `highlightMode` with a [`BrowserWindow`](browser-window.md)
by toggling between `'never'` and `'always'` modes when the window visibility
changes.
```js
const {BrowserWindow, Tray} = require('electron')
const win = new BrowserWindow({width: 800, height: 600})
const tray = new Tray('/path/to/my/icon')
tray.on('click', () => {
win.isVisible() ? win.hide() : win.show()
})
win.on('show', () => {
tray.setHighlightMode('always')
})
win.on('hide', () => {
tray.setHighlightMode('never')
})
```
#### `tray.displayBalloon(options)` _Windows_

View file

@ -9,12 +9,13 @@ the [`BrowserWindow`](browser-window.md) object. An example of accessing the
`webContents` object:
```javascript
const {BrowserWindow} = require('electron');
const {BrowserWindow} = require('electron')
let win = new BrowserWindow({width: 800, height: 1500});
win.loadURL('http://github.com');
let win = new BrowserWindow({width: 800, height: 1500})
win.loadURL('http://github.com')
let contents = win.webContents;
let contents = win.webContents
console.log(contents)
```
## Methods
@ -22,7 +23,8 @@ let contents = win.webContents;
These methods can be accessed from the `webContents` module:
```js
const {webContents} = require('electron');
const {webContents} = require('electron')
console.log(webContents)
```
### `webContents.getAllWebContents()`
@ -437,6 +439,7 @@ first available device will be selected. `callback` should be called with
cancel the request.
```javascript
const {app, webContents} = require('electron')
app.commandLine.appendSwitch('enable-web-bluetooth')
app.on('ready', () => {
@ -508,8 +511,9 @@ e.g. the `http://` or `file://`. If the load should bypass http cache then
use the `pragma` header to achieve it.
```javascript
const options = {extraHeaders: 'pragma: no-cache\n'};
webContents.loadURL(url, options);
const {webContents} = require('electron')
const options = {extraHeaders: 'pragma: no-cache\n'}
webContents.loadURL('https://github.com', options)
```
#### `contents.downloadURL(url)`
@ -524,10 +528,12 @@ Initiates a download of the resource at `url` without navigating. The
Returns URL of the current web page.
```javascript
let win = new BrowserWindow({width: 800, height: 600});
win.loadURL('http://github.com');
const {BrowserWindow} = require('electron')
let win = new BrowserWindow({width: 800, height: 600})
win.loadURL('http://github.com')
let currentURL = win.webContents.getURL();
let currentURL = win.webContents.getURL()
console.log(currentURL)
```
#### `contents.getTitle()`
@ -661,6 +667,13 @@ Executes the editing command `cut` in web page.
Executes the editing command `copy` in web page.
### `contents.copyImageAt(x, y)`
* `x` Integer
* `y` Integer
Copy the image at the given position to the clipboard.
#### `contents.paste()`
Executes the editing command `paste` in web page.
@ -731,12 +744,13 @@ the request can be obtained by subscribing to
Stops any `findInPage` request for the `webContents` with the provided `action`.
```javascript
const {webContents} = require('electron')
webContents.on('found-in-page', (event, result) => {
if (result.finalUpdate)
webContents.stopFindInPage('clearSelection');
});
if (result.finalUpdate) webContents.stopFindInPage('clearSelection')
})
const requestId = webContents.findInPage('api');
const requestId = webContents.findInPage('api')
console.log(requestId)
```
#### `contents.capturePage([rect, ]callback)`
@ -802,7 +816,7 @@ The `callback` will be called with `callback(error, data)` on completion. The
By default, an empty `options` will be regarded as:
```javascript
```
{
marginsType: 0,
printBackground: false,
@ -814,23 +828,22 @@ By default, an empty `options` will be regarded as:
An example of `webContents.printToPDF`:
```javascript
const {BrowserWindow} = require('electron');
const fs = require('fs');
const {BrowserWindow} = require('electron')
const fs = require('fs')
let win = new BrowserWindow({width: 800, height: 600});
win.loadURL('http://github.com');
let win = new BrowserWindow({width: 800, height: 600})
win.loadURL('http://github.com')
win.webContents.on('did-finish-load', () => {
// Use default printing options
win.webContents.printToPDF({}, (error, data) => {
if (error) throw error;
if (error) throw error
fs.writeFile('/tmp/print.pdf', data, (error) => {
if (error)
throw error;
console.log('Write PDF successfully.');
});
});
});
if (error) throw error
console.log('Write PDF successfully.')
})
})
})
```
#### `contents.addWorkSpace(path)`
@ -841,9 +854,11 @@ Adds the specified path to DevTools workspace. Must be used after DevTools
creation:
```javascript
const {BrowserWindow} = require('electron')
let win = new BrowserWindow()
win.webContents.on('devtools-opened', () => {
win.webContents.addWorkSpace(__dirname);
});
win.webContents.addWorkSpace(__dirname)
})
```
#### `contents.removeWorkSpace(path)`
@ -904,15 +919,16 @@ An example of sending messages from the main process to the renderer process:
```javascript
// In the main process.
let win = null;
const {app, BrowserWindow} = require('electron')
let win = null
app.on('ready', () => {
win = new BrowserWindow({width: 800, height: 600});
win.loadURL(`file://${__dirname}/index.html`);
win = new BrowserWindow({width: 800, height: 600})
win.loadURL(`file://${__dirname}/index.html`)
win.webContents.on('did-finish-load', () => {
win.webContents.send('ping', 'whoooooooh!');
});
});
win.webContents.send('ping', 'whoooooooh!')
})
})
```
```html
@ -921,8 +937,8 @@ app.on('ready', () => {
<body>
<script>
require('electron').ipcRenderer.on('ping', (event, message) => {
console.log(message); // Prints "whoooooooh!"
});
console.log(message) // Prints 'whoooooooh!'
})
</script>
</body>
</html>
@ -943,7 +959,7 @@ app.on('ready', () => {
(screenPosition == mobile) (default: `{x: 0, y: 0}`)
* `x` Integer - Set the x axis offset from top left corner
* `y` Integer - Set the y axis offset from top left corner
* `deviceScaleFactor` Integer - Set the device scale factor (if zero defaults to
* `deviceScaleFactor` Float - Set the device scale factor (if zero defaults to
original device scale factor) (default: `0`)
* `viewSize` Object - Set the emulated view size (empty means no override)
* `width` Integer - Set the emulated view width
@ -1051,14 +1067,16 @@ the cursor when dragging.
Returns true if the process of saving page has been initiated successfully.
```javascript
win.loadURL('https://github.com');
const {BrowserWindow} = require('electron')
let win = new BrowserWindow()
win.loadURL('https://github.com')
win.webContents.on('did-finish-load', () => {
win.webContents.savePage('/tmp/test.html', 'HTMLComplete', (error) => {
if (!error)
console.log('Save page successfully');
});
});
if (!error) console.log('Save page successfully')
})
})
```
#### `contents.showDefinitionForSelection()` _macOS_
@ -1120,24 +1138,28 @@ Get the debugger instance for this webContents.
Debugger API serves as an alternate transport for [remote debugging protocol][rdp].
```javascript
const {BrowserWindow} = require('electron')
let win = new BrowserWindow()
try {
win.webContents.debugger.attach('1.1');
win.webContents.debugger.attach('1.1')
} catch (err) {
console.log('Debugger attach failed : ', err);
};
console.log('Debugger attach failed : ', err)
}
win.webContents.debugger.on('detach', (event, reason) => {
console.log('Debugger detached due to : ', reason);
});
console.log('Debugger detached due to : ', reason)
})
win.webContents.debugger.on('message', (event, method, params) => {
if (method === 'Network.requestWillBeSent') {
if (params.request.url === 'https://www.github.com')
win.webContents.debugger.detach();
if (params.request.url === 'https://www.github.com') {
win.webContents.debugger.detach()
}
});
}
})
win.webContents.debugger.sendCommand('Network.enable');
win.webContents.debugger.sendCommand('Network.enable')
```
### Instance Methods

View file

@ -5,9 +5,9 @@
An example of zooming current page to 200%.
```javascript
const {webFrame} = require('electron');
const {webFrame} = require('electron')
webFrame.setZoomFactor(2);
webFrame.setZoomFactor(2)
```
## Methods
@ -58,11 +58,12 @@ whether the word passed is correctly spelled.
An example of using [node-spellchecker][spellchecker] as provider:
```javascript
const {webFrame} = require('electron')
webFrame.setSpellCheckProvider('en-US', true, {
spellCheck (text) {
return !(require('spellchecker').isMisspelled(text));
return !(require('spellchecker').isMisspelled(text))
}
});
})
```
### `webFrame.registerURLSchemeAsSecure(scheme)`
@ -112,12 +113,13 @@ Returns an object describing usage information of Blink's internal memory
caches.
```javascript
const {webFrame} = require('electron')
console.log(webFrame.getResourceUsage())
```
This will generate:
```javascript
```
{
images: {
count: 22,
@ -130,8 +132,8 @@ This will generate:
cssStyleSheets: { /* same with "images" */ },
xslStyleSheets: { /* same with "images" */ },
fonts: { /* same with "images" */ },
other: { /* same with "images" */ },
}
other: { /* same with "images" */ }
})
```
### `webFrame.clearCache()`

View file

@ -12,7 +12,7 @@ app. It doesn't have the same permissions as your web page and all interactions
between your app and embedded content will be asynchronous. This keeps your app
safe from the embedded content.
For security purpose, `webview` can only be used in `BrowserWindow`s that have
For security purposes, `webview` can only be used in `BrowserWindow`s that have
`nodeIntegration` enabled.
## Example
@ -35,20 +35,20 @@ and displays a "loading..." message during the load time:
```html
<script>
onload = () => {
const webview = document.getElementById('foo');
const indicator = document.querySelector('.indicator');
const webview = document.getElementById('foo')
const indicator = document.querySelector('.indicator')
const loadstart = () => {
indicator.innerText = 'loading...';
};
indicator.innerText = 'loading...'
}
const loadstop = () => {
indicator.innerText = '';
};
indicator.innerText = ''
}
webview.addEventListener('did-start-loading', loadstart);
webview.addEventListener('did-stop-loading', loadstop);
};
webview.addEventListener('did-start-loading', loadstart)
webview.addEventListener('did-stop-loading', loadstop)
}
</script>
```
@ -223,9 +223,10 @@ The `webview` tag has the following methods:
**Example**
```javascript
const webview = document.getElementById('foo')
webview.addEventListener('dom-ready', () => {
webview.openDevTools();
});
webview.openDevTools()
})
```
### `<webview>.loadURL(url[, options])`
@ -618,9 +619,10 @@ The following example code forwards all log messages to the embedder's console
without regard for log level or other properties.
```javascript
const webview = document.getElementById('foo')
webview.addEventListener('console-message', (e) => {
console.log('Guest page logged a message:', e.message);
});
console.log('Guest page logged a message:', e.message)
})
```
### Event: 'found-in-page'
@ -638,12 +640,13 @@ Fired when a result is available for
[`webview.findInPage`](web-view-tag.md#webviewtagfindinpage) request.
```javascript
const webview = document.getElementById('foo')
webview.addEventListener('found-in-page', (e) => {
if (e.result.finalUpdate)
webview.stopFindInPage('keepSelection');
});
if (e.result.finalUpdate) webview.stopFindInPage('keepSelection')
})
const requestId = webview.findInPage('test');
const requestId = webview.findInPage('test')
console.log(requestId)
```
### Event: 'new-window'
@ -662,14 +665,15 @@ Fired when the guest page attempts to open a new browser window.
The following example code opens the new url in system's default browser.
```javascript
const {shell} = require('electron');
const {shell} = require('electron')
const webview = document.getElementById('foo')
webview.addEventListener('new-window', (e) => {
const protocol = require('url').parse(e.url).protocol;
const protocol = require('url').parse(e.url).protocol
if (protocol === 'http:' || protocol === 'https:') {
shell.openExternal(e.url);
shell.openExternal(e.url)
}
});
})
```
### Event: 'will-navigate'
@ -722,9 +726,10 @@ The following example code navigates the `webview` to `about:blank` when the
guest attempts to close itself.
```javascript
const webview = document.getElementById('foo')
webview.addEventListener('close', () => {
webview.src = 'about:blank';
});
webview.src = 'about:blank'
})
```
### Event: 'ipc-message'
@ -741,19 +746,20 @@ between guest page and embedder page:
```javascript
// In embedder page.
const webview = document.getElementById('foo')
webview.addEventListener('ipc-message', (event) => {
console.log(event.channel);
console.log(event.channel)
// Prints "pong"
});
webview.send('ping');
})
webview.send('ping')
```
```javascript
// In guest page.
const {ipcRenderer} = require('electron');
const {ipcRenderer} = require('electron')
ipcRenderer.on('ping', () => {
ipcRenderer.sendToHost('pong');
});
ipcRenderer.sendToHost('pong')
})
```
### Event: 'crashed'

View file

@ -58,17 +58,25 @@ $ python script\build.py -c D
After building is done, you can find `electron.exe` under `out\D` (debug
target) or under `out\R` (release target).
## 64bit Build
## 32bit Build
To build for the 64bit target, you need to pass `--target_arch=x64` when running
the bootstrap script:
To build for the 32bit target, you need to pass `--target_arch=ia32` when
running the bootstrap script:
```powershell
$ python script\bootstrap.py -v --target_arch=x64
$ python script\bootstrap.py -v --target_arch=ia32
```
The other building steps are exactly the same.
## Visual Studio project
To generate a Visual Studio project, you can pass the `--msvs` parameter:
```powershell
$ python script\bootstrap.py --msvs
```
## Tests
Test your changes conform to the project coding style using:

View file

@ -0,0 +1,125 @@
# Debugging on macOS
If you experience crashes or issues in Electron that you believe are not caused
by your JavaScript application, but instead by Electron itself, debugging can
be a little bit tricky, especially for developers not used to native/C++
debugging. However, using lldb, and the Electron source code, it is fairly easy
to enable step-through debugging with breakpoints inside Electron's source code.
## Requirements
* **A debug build of Electron**: The easiest way is usually building it
yourself, using the tools and prerequisites listed in the
[build instructions for macOS](build-instructions-osx.md). While you can
easily attach to and debug Electron as you can download it directly, you will
find that it is heavily optimized, making debugging substantially more
difficult: The debugger will not be able to show you the content of all
variables and the execution path can seem strange because of inlining,
tail calls, and other compiler optimizations.
* **Xcode**: In addition to Xcode, also install the Xcode command line tools.
They include LLDB, the default debugger in Xcode on Mac OS X. It supports
debugging C, Objective-C and C++ on the desktop and iOS devices and simulator.
## Attaching to and Debugging Electron
To start a debugging session, open up Terminal and start `lldb`, passing a debug
build of Electron as a parameter.
```bash
$ lldb ./out/D/Electron.app
(lldb) target create "./out/D/Electron.app"
Current executable set to './out/D/Electron.app' (x86_64).
```
### Setting Breakpoints
LLDB is a powerful tool and supports multiple strategies for code inspection. For
this basic introduction, let's assume that you're calling a command from JavaScript
that isn't behaving correctly - so you'd like to break on that command's C++
counterpart inside the Electron source.
Relevant code files can be found in `./atom/` as well as in Brightray, found in
`./vendor/brightray/browser` and `./vendor/brightray/common`. If you're hardcore,
you can also debug Chromium directly, which is obviously found in `chromium_src`.
Let's assume that you want to debug `app.setName()`, which is defined in `browser.cc`
as `Browser::SetName()`. Set the breakpoint using the `breakpoint` command, specifying
file and line to break on:
```bash
(lldb) breakpoint set --file browser.cc --line 117
Breakpoint 1: where = Electron Framework`atom::Browser::SetName(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&) + 20 at browser.cc:118, address = 0x000000000015fdb4
```
Then, start Electron:
```bash
(lldb) run
```
The app will immediately be paused, since Electron sets the app's name on launch:
```bash
(lldb) run
Process 25244 launched: '/Users/fr/Code/electron/out/D/Electron.app/Contents/MacOS/Electron' (x86_64)
Process 25244 stopped
* thread #1: tid = 0x839a4c, 0x0000000100162db4 Electron Framework`atom::Browser::SetName(this=0x0000000108b14f20, name="Electron") + 20 at browser.cc:118, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x0000000100162db4 Electron Framework`atom::Browser::SetName(this=0x0000000108b14f20, name="Electron") + 20 at browser.cc:118
115 }
116
117 void Browser::SetName(const std::string& name) {
-> 118 name_override_ = name;
119 }
120
121 int Browser::GetBadgeCount() {
(lldb)
```
To show the arguments and local variables for the current frame, run `frame variable` (or `fr v`),
which will show you that the app is currently setting the name to "Electron".
```bash
(lldb) frame variable
(atom::Browser *) this = 0x0000000108b14f20
(const string &) name = "Electron": {
[...]
}
```
To do a source level single step in the currently selected thread, execute `step` (or `s`).
This would take you into into `name_override_.empty()`. To proceed and do a step over,
run `next` (or `n`).
```bash
(lldb) step
Process 25244 stopped
* thread #1: tid = 0x839a4c, 0x0000000100162dcc Electron Framework`atom::Browser::SetName(this=0x0000000108b14f20, name="Electron") + 44 at browser.cc:119, queue = 'com.apple.main-thread', stop reason = step in
frame #0: 0x0000000100162dcc Electron Framework`atom::Browser::SetName(this=0x0000000108b14f20, name="Electron") + 44 at browser.cc:119
116
117 void Browser::SetName(const std::string& name) {
118 name_override_ = name;
-> 119 }
120
121 int Browser::GetBadgeCount() {
122 return badge_count_;
```
To finish debugging at this point, run `process continue`. You can also continue until a certain
line is hit in this thread (`thread until 100`). This command will run the thread in the current
frame till it reaches line 100 in this frame or stops if it leaves the current frame.
Now, if you open up Electron's developer tools and call `setName`, you will once again hit the
breakpoint.
### Further Reading
LLDB is a powerful tool with a great documentation. To learn more about it, consider
Apple's debugging documentation, for instance the [LLDB Command Structure Reference][lldb-command-structure]
or the introduction to [Using LLDB as a Standalone Debugger][lldb-standalone].
You can also check out LLDB's fantastic [manual and tutorial][lldb-tutorial], which
will explain more complex debugging scenarios.
[lldb-command-structure]: https://developer.apple.com/library/mac/documentation/IDEs/Conceptual/gdb_to_lldb_transition_guide/document/lldb-basics.html#//apple_ref/doc/uid/TP40012917-CH2-SW2
[lldb-standalone]: https://developer.apple.com/library/mac/documentation/IDEs/Conceptual/gdb_to_lldb_transition_guide/document/lldb-terminal-workflow-tutorial.html
[lldb-tutorial]: http://lldb.llvm.org/tutorial.html

View file

@ -36,17 +36,17 @@ renderers through the `remote` property of `electron` module:
// In the main process.
global.sharedObject = {
someProperty: 'default value'
};
}
```
```javascript
// In page 1.
require('electron').remote.getGlobal('sharedObject').someProperty = 'new value';
require('electron').remote.getGlobal('sharedObject').someProperty = 'new value'
```
```javascript
// In page 2.
console.log(require('electron').remote.getGlobal('sharedObject').someProperty);
console.log(require('electron').remote.getGlobal('sharedObject').someProperty)
```
## My app's window/tray disappeared after a few minutes.
@ -63,18 +63,22 @@ If you want a quick fix, you can make the variables global by changing your
code from this:
```javascript
const {app, Tray} = require('electron')
app.on('ready', () => {
const tray = new Tray('/path/to/icon.png');
});
const tray = new Tray('/path/to/icon.png')
tray.setTitle('hello world')
})
```
to this:
```javascript
let tray = null;
const {app, Tray} = require('electron')
let tray = null
app.on('ready', () => {
tray = new Tray('/path/to/icon.png');
});
tray = new Tray('/path/to/icon.png')
tray.setTitle('hello world')
})
```
## I can not use jQuery/RequireJS/Meteor/AngularJS in Electron.
@ -87,11 +91,13 @@ To solve this, you can turn off node integration in Electron:
```javascript
// In the main process.
const {BrowserWindow} = require('electron')
let win = new BrowserWindow({
webPreferences: {
nodeIntegration: false
}
});
})
win.show()
```
But if you want to keep the abilities of using Node.js and Electron APIs, you
@ -114,7 +120,7 @@ delete window.module;
When using Electron's built-in module you might encounter an error like this:
```
> require('electron').webFrame.setZoomFactor(1.0);
> require('electron').webFrame.setZoomFactor(1.0)
Uncaught TypeError: Cannot read property 'setZoomLevel' of undefined
```
@ -125,7 +131,7 @@ To verify whether you are using the correct built-in module, you can print the
path of the `electron` module:
```javascript
console.log(require.resolve('electron'));
console.log(require.resolve('electron'))
```
and then check if it is in the following form:

View file

@ -51,29 +51,29 @@ $ asar list /path/to/example.asar
Read a file in the `asar` archive:
```javascript
const fs = require('fs');
fs.readFileSync('/path/to/example.asar/file.txt');
const fs = require('fs')
fs.readFileSync('/path/to/example.asar/file.txt')
```
List all files under the root of the archive:
```javascript
const fs = require('fs');
fs.readdirSync('/path/to/example.asar');
const fs = require('fs')
fs.readdirSync('/path/to/example.asar')
```
Use a module from the archive:
```javascript
require('/path/to/example.asar/dir/module.js');
require('/path/to/example.asar/dir/module.js')
```
You can also display a web page in an `asar` archive with `BrowserWindow`:
```javascript
const {BrowserWindow} = require('electron');
let win = new BrowserWindow({width: 800, height: 600});
win.loadURL('file:///path/to/example.asar/static/index.html');
const {BrowserWindow} = require('electron')
let win = new BrowserWindow({width: 800, height: 600})
win.loadURL('file:///path/to/example.asar/static/index.html')
```
### Web API
@ -85,10 +85,10 @@ For example, to get a file with `$.get`:
```html
<script>
let $ = require('./jquery.min.js');
let $ = require('./jquery.min.js')
$.get('file:///path/to/example.asar/file.txt', (data) => {
console.log(data);
});
console.log(data)
})
</script>
```
@ -99,16 +99,17 @@ content of an `asar` archive as a file. For this purpose you can use the built-i
`original-fs` module which provides original `fs` APIs without `asar` support:
```javascript
const originalFs = require('original-fs');
originalFs.readFileSync('/path/to/example.asar');
const originalFs = require('original-fs')
originalFs.readFileSync('/path/to/example.asar')
```
You can also set `process.noAsar` to `true` to disable the support for `asar` in
the `fs` module:
```javascript
process.noAsar = true;
fs.readFileSync('/path/to/example.asar');
const fs = require('fs')
process.noAsar = true
fs.readFileSync('/path/to/example.asar')
```
## Limitations of the Node API

View file

@ -20,11 +20,11 @@ the currently running operating system's native notification APIs to display it.
```javascript
let myNotification = new Notification('Title', {
body: 'Lorem Ipsum Dolor Sit Amet'
});
})
myNotification.onclick = () => {
console.log('Notification clicked');
};
console.log('Notification clicked')
}
```
While code and user experience across operating systems are similar, there
@ -75,14 +75,16 @@ To add a file to recent documents, you can use the
[app.addRecentDocument][addrecentdocument] API:
```javascript
app.addRecentDocument('/Users/USERNAME/Desktop/work.type');
const {app} = require('electron')
app.addRecentDocument('/Users/USERNAME/Desktop/work.type')
```
And you can use [app.clearRecentDocuments][clearrecentdocuments] API to empty
the recent documents list:
```javascript
app.clearRecentDocuments();
const {app} = require('electron')
app.clearRecentDocuments()
```
### Windows Notes
@ -113,19 +115,17 @@ To set your custom dock menu, you can use the `app.dock.setMenu` API, which is
only available on macOS:
```javascript
const electron = require('electron');
const app = electron.app;
const Menu = electron.Menu;
const {app, Menu} = require('electron')
const dockMenu = Menu.buildFromTemplate([
{ label: 'New Window', click() { console.log('New Window'); } },
{label: 'New Window', click () { console.log('New Window') }},
{label: 'New Window with Settings', submenu: [
{label: 'Basic'},
{label: 'Pro'}
]},
{label: 'New Command...'}
]);
app.dock.setMenu(dockMenu);
])
app.dock.setMenu(dockMenu)
```
## User Tasks (Windows)
@ -162,6 +162,7 @@ To set user tasks for your application, you can use
[app.setUserTasks][setusertaskstasks] API:
```javascript
const {app} = require('electron')
app.setUserTasks([
{
program: process.execPath,
@ -171,13 +172,14 @@ app.setUserTasks([
title: 'New Window',
description: 'Create a new window'
}
]);
])
```
To clean your tasks list, just call `app.setUserTasks` with an empty array:
```javascript
app.setUserTasks([]);
const {app} = require('electron')
app.setUserTasks([])
```
The user tasks will still show even after your application closes, so the icon
@ -209,34 +211,36 @@ You can use [BrowserWindow.setThumbarButtons][setthumbarbuttons] to set
thumbnail toolbar in your application:
```javascript
const {BrowserWindow} = require('electron');
const path = require('path');
const {BrowserWindow} = require('electron')
const path = require('path')
let win = new BrowserWindow({
width: 800,
height: 600
});
})
win.setThumbarButtons([
{
tooltip: 'button1',
icon: path.join(__dirname, 'button1.png'),
click() { console.log('button1 clicked'); }
click () { console.log('button1 clicked') }
},
{
tooltip: 'button2',
icon: path.join(__dirname, 'button2.png'),
flags: ['enabled', 'dismissonclick'],
click() { console.log('button2 clicked.'); }
click () { console.log('button2 clicked.') }
}
]);
])
```
To clean thumbnail toolbar buttons, just call `BrowserWindow.setThumbarButtons`
with an empty array:
```javascript
win.setThumbarButtons([]);
const {BrowserWindow} = require('electron')
let win = new BrowserWindow()
win.setThumbarButtons([])
```
## Unity Launcher Shortcuts (Linux)
@ -267,8 +271,9 @@ To set the progress bar for a Window, you can use the
[BrowserWindow.setProgressBar][setprogressbar] API:
```javascript
let win = new BrowserWindow({...});
win.setProgressBar(0.5);
const {BrowserWindow} = require('electron')
let win = new BrowserWindow()
win.setProgressBar(0.5)
```
## Icon Overlays in Taskbar (Windows)
@ -294,8 +299,9 @@ To set the overlay icon for a window, you can use the
[BrowserWindow.setOverlayIcon][setoverlayicon] API:
```javascript
let win = new BrowserWindow({...});
win.setOverlayIcon('path/to/overlay.png', 'Description for overlay');
const {BrowserWindow} = require('electron')
let win = new BrowserWindow()
win.setOverlayIcon('path/to/overlay.png', 'Description for overlay')
```
## Represented File of Window (macOS)
@ -316,9 +322,10 @@ To set the represented file of window, you can use the
[BrowserWindow.setDocumentEdited][setdocumentedited] APIs:
```javascript
let win = new BrowserWindow({...});
win.setRepresentedFilename('/etc/passwd');
win.setDocumentEdited(true);
const {BrowserWindow} = require('electron')
let win = new BrowserWindow()
win.setRepresentedFilename('/etc/passwd')
win.setDocumentEdited(true)
```
## Dragging files out of the window
@ -342,6 +349,7 @@ In web page:
In the main process:
```javascript
const {ipcMain} = require('electron')
ipcMain.on('ondragstart', (event, filePath) => {
event.sender.startDrag({
file: filePath,

View file

@ -29,7 +29,10 @@ Using the [React Developer Tools][react-devtools] as example:
* on macOS it is `~/Library/Application Support/Google/Chrome/Default/Extensions`.
1. Pass the location of the extension to `BrowserWindow.addDevToolsExtension`
API, for the React Developer Tools, it is something like:
`~/Library/Application Support/Google/Chrome/Default/Extensions/fmkadmapgofadopljbjfkapdkoienihi/0.14.10_0`
`~/Library/Application Support/Google/Chrome/Default/Extensions/fmkadmapgofadopljbjfkapdkoienihi/0.15.0_0`
**Note:** The `BrowserWindow.addDevToolsExtension` API cannot be called before the
ready event of the app module is emitted.
The name of the extension is returned by `BrowserWindow.addDevToolsExtension`,
and you can pass the name of the extension to the `BrowserWindow.removeDevToolsExtension`

View file

@ -6,16 +6,14 @@ using standard HTML5 APIs, as shown in the following example.
_main.js_
```javascript
const electron = require('electron');
const app = electron.app;
const BrowserWindow = electron.BrowserWindow;
const {app, BrowserWindow} = require('electron')
let onlineStatusWindow;
let onlineStatusWindow
app.on('ready', () => {
onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false });
onlineStatusWindow.loadURL(`file://${__dirname}/online-status.html`);
});
onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false })
onlineStatusWindow.loadURL(`file://${__dirname}/online-status.html`)
})
```
_online-status.html_
@ -26,13 +24,13 @@ _online-status.html_
<body>
<script>
const alertOnlineStatus = () => {
window.alert(navigator.onLine ? 'online' : 'offline');
};
window.alert(navigator.onLine ? 'online' : 'offline')
}
window.addEventListener('online', alertOnlineStatus);
window.addEventListener('offline', alertOnlineStatus);
window.addEventListener('online', alertOnlineStatus)
window.addEventListener('offline', alertOnlineStatus)
alertOnlineStatus();
alertOnlineStatus()
</script>
</body>
</html>
@ -47,21 +45,17 @@ to the main process and handled as needed, as shown in the following example.
_main.js_
```javascript
const electron = require('electron');
const app = electron.app;
const ipcMain = electron.ipcMain;
const BrowserWindow = electron.BrowserWindow;
let onlineStatusWindow;
const {app, BrowserWindow, ipcMain} = require('electron')
let onlineStatusWindow
app.on('ready', () => {
onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false });
onlineStatusWindow.loadURL(`file://${__dirname}/online-status.html`);
});
onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false })
onlineStatusWindow.loadURL(`file://${__dirname}/online-status.html`)
})
ipcMain.on('online-status-changed', (event, status) => {
console.log(status);
});
console.log(status)
})
```
_online-status.html_
@ -71,15 +65,15 @@ _online-status.html_
<html>
<body>
<script>
const {ipcRenderer} = require('electron');
const {ipcRenderer} = require('electron')
const updateOnlineStatus = () => {
ipcRenderer.send('online-status-changed', navigator.onLine ? 'online' : 'offline');
};
ipcRenderer.send('online-status-changed', navigator.onLine ? 'online' : 'offline')
}
window.addEventListener('online', updateOnlineStatus);
window.addEventListener('offline', updateOnlineStatus);
window.addEventListener('online', updateOnlineStatus)
window.addEventListener('offline', updateOnlineStatus)
updateOnlineStatus();
updateOnlineStatus()
</script>
</body>
</html>

View file

@ -80,56 +80,52 @@ The `main.js` should create windows and handle system events, a typical
example being:
```javascript
const electron = require('electron');
// Module to control application life.
const {app} = electron;
// Module to create native browser window.
const {BrowserWindow} = electron;
const {app, BrowserWindow} = require('electron')
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win;
let win
function createWindow () {
// Create the browser window.
win = new BrowserWindow({width: 800, height: 600});
win = new BrowserWindow({width: 800, height: 600})
// and load the index.html of the app.
win.loadURL(`file://${__dirname}/index.html`);
win.loadURL(`file://${__dirname}/index.html`)
// Open the DevTools.
win.webContents.openDevTools();
win.webContents.openDevTools()
// Emitted when the window is closed.
win.on('closed', () => {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
win = null;
});
win = 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);
app.on('ready', createWindow)
// Quit when all windows are closed.
app.on('window-all-closed', () => {
// On macOS 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()
}
});
})
app.on('activate', () => {
// On macOS 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) {
createWindow();
createWindow()
}
});
})
// 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.

View file

@ -20,6 +20,9 @@ before the app ready event. Also, turn on `plugins` option of `BrowserWindow`.
For example:
```javascript
const {app, BrowserWindow} = require('electron')
const path = require('path')
// Specify flash path, supposing it is placed in the same directory with main.js.
let pluginName
switch (process.platform) {
@ -39,7 +42,7 @@ app.commandLine.appendSwitch('ppapi-flash-path', path.join(__dirname, pluginName
app.commandLine.appendSwitch('ppapi-flash-version', '17.0.0.169')
app.on('ready', () => {
win = new BrowserWindow({
let win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
@ -48,7 +51,7 @@ app.on('ready', () => {
})
win.loadURL(`file://${__dirname}/index.html`)
// Something else
});
})
```
You can also try loading the system wide Pepper Flash plugin instead of shipping

View file

@ -79,7 +79,7 @@ upstream, except that you have to manually specify how to connect chrome driver
and where to find Electron's binary:
```javascript
const webdriver = require('selenium-webdriver');
const webdriver = require('selenium-webdriver')
const driver = new webdriver.Builder()
// The "9515" is the port opened by chrome driver.
@ -87,22 +87,22 @@ const driver = new webdriver.Builder()
.withCapabilities({
chromeOptions: {
// Here is the path to your Electron binary.
binary: '/Path-to-Your-App.app/Contents/MacOS/Electron',
binary: '/Path-to-Your-App.app/Contents/MacOS/Electron'
}
})
.forBrowser('electron')
.build();
.build()
driver.get('http://www.google.com');
driver.findElement(webdriver.By.name('q')).sendKeys('webdriver');
driver.findElement(webdriver.By.name('btnG')).click();
driver.get('http://www.google.com')
driver.findElement(webdriver.By.name('q')).sendKeys('webdriver')
driver.findElement(webdriver.By.name('btnG')).click()
driver.wait(() => {
return driver.getTitle().then((title) => {
return title === 'webdriver - Google Search';
});
}, 1000);
return title === 'webdriver - Google Search'
})
}, 1000)
driver.quit();
driver.quit()
```
## Setting up with WebdriverIO
@ -132,7 +132,7 @@ $ npm install webdriverio
### 3. Connect to chrome driver
```javascript
const webdriverio = require('webdriverio');
const webdriverio = require('webdriverio')
const options = {
host: 'localhost', // Use localhost as chrome driver server
port: 9515, // "9515" is the port opened by chrome driver.
@ -143,9 +143,9 @@ const options = {
args: [/* cli arguments */] // Optional, perhaps 'app=' + /path/to/your/app/
}
}
};
}
let client = webdriverio.remote(options);
let client = webdriverio.remote(options)
client
.init()
@ -153,9 +153,9 @@ client
.setValue('#q', 'webdriverio')
.click('#btnG')
.getTitle().then((title) => {
console.log('Title was: ' + title);
console.log('Title was: ' + title)
})
.end();
.end()
```
## Workflow

View file

@ -51,23 +51,26 @@ enabled.
Example code:
```javascript
const {app, BrowserWindow} = require('electron')
// You have to pass the filename of `widevinecdmadapter` here, it is
// * `widevinecdmadapter.plugin` on macOS,
// * `libwidevinecdmadapter.so` on Linux,
// * `widevinecdmadapter.dll` on Windows.
app.commandLine.appendSwitch('widevine-cdm-path', '/path/to/widevinecdmadapter.plugin');
app.commandLine.appendSwitch('widevine-cdm-path', '/path/to/widevinecdmadapter.plugin')
// The version of plugin can be got from `chrome://plugins` page in Chrome.
app.commandLine.appendSwitch('widevine-cdm-version', '1.4.8.866');
app.commandLine.appendSwitch('widevine-cdm-version', '1.4.8.866')
let win = null;
let win = null
app.on('ready', () => {
win = new BrowserWindow({
webPreferences: {
// The `plugins` have to be enabled.
plugins: true
}
});
});
})
win.show()
})
```
## Verifying the plugin

View file

@ -4,7 +4,7 @@
'product_name%': 'Electron',
'company_name%': 'GitHub, Inc',
'company_abbr%': 'github',
'version%': '1.3.0',
'version%': '1.3.1',
},
'includes': [
'filenames.gypi',
@ -191,7 +191,11 @@
'<@(copied_libraries)',
'<(libchromiumcontent_dir)/locales',
'<(libchromiumcontent_dir)/icudtl.dat',
'<(libchromiumcontent_dir)/blink_image_resources_200_percent.pak',
'<(libchromiumcontent_dir)/content_resources_200_percent.pak',
'<(libchromiumcontent_dir)/content_shell.pak',
'<(libchromiumcontent_dir)/ui_resources_200_percent.pak',
'<(libchromiumcontent_dir)/views_resources_200_percent.pak',
'<(libchromiumcontent_dir)/natives_blob.bin',
'<(libchromiumcontent_dir)/snapshot_blob.bin',
],

View file

@ -1,17 +1,11 @@
'use strict'
const app = require('electron').app
const EventEmitter = require('events').EventEmitter
const {app} = require('electron')
const {EventEmitter} = require('events')
const squirrelUpdate = require('./squirrel-update-win')
const util = require('util')
function AutoUpdater () {
EventEmitter.call(this)
}
util.inherits(AutoUpdater, EventEmitter)
AutoUpdater.prototype.quitAndInstall = function () {
class AutoUpdater extends EventEmitter {
quitAndInstall () {
if (!this.updateAvailable) {
return this.emitError('No update available, can\'t quit and install')
}
@ -19,15 +13,15 @@ AutoUpdater.prototype.quitAndInstall = function () {
return app.quit()
}
AutoUpdater.prototype.getFeedURL = function () {
getFeedURL () {
return this.updateURL
}
AutoUpdater.prototype.setFeedURL = function (updateURL, headers) {
setFeedURL (updateURL, headers) {
this.updateURL = updateURL
}
AutoUpdater.prototype.checkForUpdates = function () {
checkForUpdates () {
if (!this.updateURL) {
return this.emitError('Update URL is not set')
}
@ -64,8 +58,9 @@ AutoUpdater.prototype.checkForUpdates = function () {
// Private: Emit both error object and message, this is to keep compatibility
// with Old APIs.
AutoUpdater.prototype.emitError = function (message) {
emitError (message) {
return this.emit('error', new Error(message), message)
}
}
module.exports = new AutoUpdater()

View file

@ -7,7 +7,7 @@ const roles = {
}
},
close: {
label: 'Close',
label: process.platform === 'darwin' ? 'Close Window' : 'Close',
accelerator: 'CommandOrControl+W',
windowMethod: 'close'
},

View file

@ -93,8 +93,8 @@ if (process.platform === 'win32') {
if (fs.statSyncNoException(updateDotExe)) {
var packageDir = path.dirname(path.resolve(updateDotExe))
var packageName = path.basename(packageDir)
var exeName = path.basename(process.execPath).replace(/\.exe$/i, '')
var packageName = path.basename(packageDir).replace(/\s/g, '')
var exeName = path.basename(process.execPath).replace(/\.exe$/i, '').replace(/\s/g, '')
app.setAppUserModelId(`com.squirrel.${packageName}.${exeName}`)
}

View file

@ -4,14 +4,13 @@
const path = require('path')
const util = require('util')
var hasProp = {}.hasOwnProperty
const hasProp = {}.hasOwnProperty
// Cache asar archive objects.
var cachedArchives = {}
const cachedArchives = {}
var getOrCreateArchive = function (p) {
var archive
archive = cachedArchives[p]
const getOrCreateArchive = function (p) {
let archive = cachedArchives[p]
if (archive != null) {
return archive
}
@ -25,31 +24,33 @@
// Clean cache on quit.
process.on('exit', function () {
var archive, p
for (p in cachedArchives) {
for (let p in cachedArchives) {
if (!hasProp.call(cachedArchives, p)) continue
archive = cachedArchives[p]
archive.destroy()
cachedArchives[p].destroy()
}
})
// Separate asar package's path from full path.
var splitPath = function (p) {
var index
const splitPath = function (p) {
// shortcut to disable asar.
if (process.noAsar) {
return [false]
}
if (Buffer.isBuffer(p)) {
p = p.toString()
}
if (typeof p !== 'string') {
return [false]
}
if (p.substr(-5) === '.asar') {
return [true, p, '']
}
p = path.normalize(p)
index = p.lastIndexOf('.asar' + path.sep)
const index = p.lastIndexOf('.asar' + path.sep)
if (index === -1) {
return [false]
}
@ -57,15 +58,15 @@
}
// Convert asar archive's Stats object to fs's Stats object.
var nextInode = 0
let nextInode = 0
var uid = process.getuid != null ? process.getuid() : 0
const uid = process.getuid != null ? process.getuid() : 0
var gid = process.getgid != null ? process.getgid() : 0
const gid = process.getgid != null ? process.getgid() : 0
var fakeTime = new Date()
const fakeTime = new Date()
var asarStatsToFsStats = function (stats) {
const asarStatsToFsStats = function (stats) {
return {
dev: 1,
ino: ++nextInode,
@ -104,97 +105,110 @@
}
// Create a ENOENT error.
var notFoundError = function (asarPath, filePath, callback) {
var error
error = new Error(`ENOENT, ${filePath} not found in ${asarPath}`)
const notFoundError = function (asarPath, filePath, callback) {
const error = new Error(`ENOENT, ${filePath} not found in ${asarPath}`)
error.code = 'ENOENT'
error.errno = -2
if (typeof callback !== 'function') {
throw error
}
return process.nextTick(function () {
return callback(error)
process.nextTick(function () {
callback(error)
})
}
// Create a ENOTDIR error.
var notDirError = function (callback) {
var error
error = new Error('ENOTDIR, not a directory')
const notDirError = function (callback) {
const error = new Error('ENOTDIR, not a directory')
error.code = 'ENOTDIR'
error.errno = -20
if (typeof callback !== 'function') {
throw error
}
return process.nextTick(function () {
return callback(error)
process.nextTick(function () {
callback(error)
})
}
// Create a EACCES error.
const accessError = function (asarPath, filePath, callback) {
const error = new Error(`EACCES: permission denied, access '${filePath}'`)
error.code = 'EACCES'
error.errno = -13
if (typeof callback !== 'function') {
throw error
}
process.nextTick(function () {
callback(error)
})
}
// Create invalid archive error.
var invalidArchiveError = function (asarPath, callback) {
var error
error = new Error(`Invalid package ${asarPath}`)
const invalidArchiveError = function (asarPath, callback) {
const error = new Error(`Invalid package ${asarPath}`)
if (typeof callback !== 'function') {
throw error
}
return process.nextTick(function () {
return callback(error)
process.nextTick(function () {
callback(error)
})
}
// Override APIs that rely on passing file path instead of content to C++.
var overrideAPISync = function (module, name, arg) {
var old
const overrideAPISync = function (module, name, arg) {
if (arg == null) {
arg = 0
}
old = module[name]
const old = module[name]
module[name] = function () {
var archive, newPath, p
p = arguments[arg]
const p = arguments[arg]
const [isAsar, asarPath, filePath] = splitPath(p)
if (!isAsar) {
return old.apply(this, arguments)
}
archive = getOrCreateArchive(asarPath)
const archive = getOrCreateArchive(asarPath)
if (!archive) {
invalidArchiveError(asarPath)
}
newPath = archive.copyFileOut(filePath)
const newPath = archive.copyFileOut(filePath)
if (!newPath) {
notFoundError(asarPath, filePath)
}
arguments[arg] = newPath
return old.apply(this, arguments)
}
}
var overrideAPI = function (module, name, arg) {
var old
const overrideAPI = function (module, name, arg) {
if (arg == null) {
arg = 0
}
old = module[name]
const old = module[name]
module[name] = function () {
var archive, callback, newPath, p
p = arguments[arg]
const p = arguments[arg]
const [isAsar, asarPath, filePath] = splitPath(p)
if (!isAsar) {
return old.apply(this, arguments)
}
callback = arguments[arguments.length - 1]
const callback = arguments[arguments.length - 1]
if (typeof callback !== 'function') {
return overrideAPISync(module, name, arg)
}
archive = getOrCreateArchive(asarPath)
const archive = getOrCreateArchive(asarPath)
if (!archive) {
return invalidArchiveError(asarPath, callback)
}
newPath = archive.copyFileOut(filePath)
const newPath = archive.copyFileOut(filePath)
if (!newPath) {
return notFoundError(asarPath, filePath, callback)
}
arguments[arg] = newPath
return old.apply(this, arguments)
}
@ -202,61 +216,58 @@
// Override fs APIs.
exports.wrapFsWithAsar = function (fs) {
var exists, existsSync, internalModuleReadFile, internalModuleStat, lstat, lstatSync, mkdir, mkdirSync, readFile, readFileSync, readdir, readdirSync, realpath, realpathSync, stat, statSync, statSyncNoException, logFDs, logASARAccess
logFDs = {}
logASARAccess = function (asarPath, filePath, offset) {
const logFDs = {}
const logASARAccess = function (asarPath, filePath, offset) {
if (!process.env.ELECTRON_LOG_ASAR_READS) {
return
}
if (!logFDs[asarPath]) {
var logFilename, logPath
const path = require('path')
logFilename = path.basename(asarPath, '.asar') + '-access-log.txt'
logPath = path.join(require('os').tmpdir(), logFilename)
const logFilename = path.basename(asarPath, '.asar') + '-access-log.txt'
const logPath = path.join(require('os').tmpdir(), logFilename)
logFDs[asarPath] = fs.openSync(logPath, 'a')
console.log('Logging ' + asarPath + ' access to ' + logPath)
}
fs.writeSync(logFDs[asarPath], offset + ': ' + filePath + '\n')
}
lstatSync = fs.lstatSync
const {lstatSync} = fs
fs.lstatSync = function (p) {
var archive, stats
const [isAsar, asarPath, filePath] = splitPath(p)
if (!isAsar) {
return lstatSync(p)
}
archive = getOrCreateArchive(asarPath)
const archive = getOrCreateArchive(asarPath)
if (!archive) {
invalidArchiveError(asarPath)
}
stats = archive.stat(filePath)
const stats = archive.stat(filePath)
if (!stats) {
notFoundError(asarPath, filePath)
}
return asarStatsToFsStats(stats)
}
lstat = fs.lstat
const {lstat} = fs
fs.lstat = function (p, callback) {
var archive, stats
const [isAsar, asarPath, filePath] = splitPath(p)
if (!isAsar) {
return lstat(p, callback)
}
archive = getOrCreateArchive(asarPath)
const archive = getOrCreateArchive(asarPath)
if (!archive) {
return invalidArchiveError(asarPath, callback)
}
stats = getOrCreateArchive(asarPath).stat(filePath)
const stats = getOrCreateArchive(asarPath).stat(filePath)
if (!stats) {
return notFoundError(asarPath, filePath, callback)
}
return process.nextTick(function () {
return callback(null, asarStatsToFsStats(stats))
process.nextTick(function () {
callback(null, asarStatsToFsStats(stats))
})
}
statSync = fs.statSync
const {statSync} = fs
fs.statSync = function (p) {
const [isAsar] = splitPath(p)
if (!isAsar) {
@ -266,7 +277,8 @@
// Do not distinguish links for now.
return fs.lstatSync(p)
}
stat = fs.stat
const {stat} = fs
fs.stat = function (p, callback) {
const [isAsar] = splitPath(p)
if (!isAsar) {
@ -274,47 +286,47 @@
}
// Do not distinguish links for now.
return process.nextTick(function () {
return fs.lstat(p, callback)
process.nextTick(function () {
fs.lstat(p, callback)
})
}
statSyncNoException = fs.statSyncNoException
const {statSyncNoException} = fs
fs.statSyncNoException = function (p) {
var archive, stats
const [isAsar, asarPath, filePath] = splitPath(p)
if (!isAsar) {
return statSyncNoException(p)
}
archive = getOrCreateArchive(asarPath)
const archive = getOrCreateArchive(asarPath)
if (!archive) {
return false
}
stats = archive.stat(filePath)
const stats = archive.stat(filePath)
if (!stats) {
return false
}
return asarStatsToFsStats(stats)
}
realpathSync = fs.realpathSync
const {realpathSync} = fs
fs.realpathSync = function (p) {
var archive, real
const [isAsar, asarPath, filePath] = splitPath(p)
if (!isAsar) {
return realpathSync.apply(this, arguments)
}
archive = getOrCreateArchive(asarPath)
const archive = getOrCreateArchive(asarPath)
if (!archive) {
invalidArchiveError(asarPath)
}
real = archive.realpath(filePath)
const real = archive.realpath(filePath)
if (real === false) {
notFoundError(asarPath, filePath)
}
return path.join(realpathSync(asarPath), real)
}
realpath = fs.realpath
const {realpath} = fs
fs.realpath = function (p, cache, callback) {
var archive, real
const [isAsar, asarPath, filePath] = splitPath(p)
if (!isAsar) {
return realpath.apply(this, arguments)
@ -323,11 +335,11 @@
callback = cache
cache = void 0
}
archive = getOrCreateArchive(asarPath)
const archive = getOrCreateArchive(asarPath)
if (!archive) {
return invalidArchiveError(asarPath, callback)
}
real = archive.realpath(filePath)
const real = archive.realpath(filePath)
if (real === false) {
return notFoundError(asarPath, filePath, callback)
}
@ -338,37 +350,101 @@
return callback(null, path.join(p, real))
})
}
exists = fs.exists
const {exists} = fs
fs.exists = function (p, callback) {
var archive
const [isAsar, asarPath, filePath] = splitPath(p)
if (!isAsar) {
return exists(p, callback)
}
archive = getOrCreateArchive(asarPath)
const archive = getOrCreateArchive(asarPath)
if (!archive) {
return invalidArchiveError(asarPath, callback)
}
return process.nextTick(function () {
return callback(archive.stat(filePath) !== false)
process.nextTick(function () {
callback(archive.stat(filePath) !== false)
})
}
existsSync = fs.existsSync
const {existsSync} = fs
fs.existsSync = function (p) {
var archive
const [isAsar, asarPath, filePath] = splitPath(p)
if (!isAsar) {
return existsSync(p)
}
archive = getOrCreateArchive(asarPath)
const archive = getOrCreateArchive(asarPath)
if (!archive) {
return false
}
return archive.stat(filePath) !== false
}
readFile = fs.readFile
const {access} = fs
fs.access = function (p, mode, callback) {
const [isAsar, asarPath, filePath] = splitPath(p)
if (!isAsar) {
return access.apply(this, arguments)
}
if (typeof mode === 'function') {
callback = mode
mode = fs.constants.F_OK
}
const archive = getOrCreateArchive(asarPath)
if (!archive) {
return invalidArchiveError(asarPath, callback)
}
const info = archive.getFileInfo(filePath)
if (!info) {
return notFoundError(asarPath, filePath, callback)
}
if (info.unpacked) {
const realPath = archive.copyFileOut(filePath)
return fs.access(realPath, mode, callback)
}
const stats = getOrCreateArchive(asarPath).stat(filePath)
if (!stats) {
return notFoundError(asarPath, filePath, callback)
}
if (mode & fs.constants.W_OK) {
return accessError(asarPath, filePath, callback)
}
process.nextTick(function () {
callback()
})
}
const {accessSync} = fs
fs.accessSync = function (p, mode) {
const [isAsar, asarPath, filePath] = splitPath(p)
if (!isAsar) {
return accessSync.apply(this, arguments)
}
if (mode == null) {
mode = fs.constants.F_OK
}
const archive = getOrCreateArchive(asarPath)
if (!archive) {
invalidArchiveError(asarPath)
}
const info = archive.getFileInfo(filePath)
if (!info) {
notFoundError(asarPath, filePath)
}
if (info.unpacked) {
const realPath = archive.copyFileOut(filePath)
return fs.accessSync(realPath, mode)
}
const stats = getOrCreateArchive(asarPath).stat(filePath)
if (!stats) {
notFoundError(asarPath, filePath)
}
if (mode & fs.constants.W_OK) {
accessError(asarPath, filePath)
}
}
const {readFile} = fs
fs.readFile = function (p, options, callback) {
var archive, buffer, encoding, fd, info, realPath
const [isAsar, asarPath, filePath] = splitPath(p)
if (!isAsar) {
return readFile.apply(this, arguments)
@ -377,21 +453,21 @@
callback = options
options = void 0
}
archive = getOrCreateArchive(asarPath)
const archive = getOrCreateArchive(asarPath)
if (!archive) {
return invalidArchiveError(asarPath, callback)
}
info = archive.getFileInfo(filePath)
const info = archive.getFileInfo(filePath)
if (!info) {
return notFoundError(asarPath, filePath, callback)
}
if (info.size === 0) {
return process.nextTick(function () {
return callback(null, new Buffer(0))
callback(null, new Buffer(0))
})
}
if (info.unpacked) {
realPath = archive.copyFileOut(filePath)
const realPath = archive.copyFileOut(filePath)
return fs.readFile(realPath, options, callback)
}
if (!options) {
@ -405,31 +481,29 @@
} else if (!util.isObject(options)) {
throw new TypeError('Bad arguments')
}
encoding = options.encoding
buffer = new Buffer(info.size)
fd = archive.getFd()
const {encoding} = options
const buffer = new Buffer(info.size)
const fd = archive.getFd()
if (!(fd >= 0)) {
return notFoundError(asarPath, filePath, callback)
}
logASARAccess(asarPath, filePath, info.offset)
return fs.read(fd, buffer, 0, info.size, info.offset, function (error) {
return callback(error, encoding ? buffer.toString(encoding) : buffer)
fs.read(fd, buffer, 0, info.size, info.offset, function (error) {
callback(error, encoding ? buffer.toString(encoding) : buffer)
})
}
readFileSync = fs.readFileSync
fs.readFileSync = function (p, opts) {
// this allows v8 to optimize this function
var archive, buffer, encoding, fd, info, options, realPath
options = opts
const {readFileSync} = fs
fs.readFileSync = function (p, options) {
const [isAsar, asarPath, filePath] = splitPath(p)
if (!isAsar) {
return readFileSync.apply(this, arguments)
}
archive = getOrCreateArchive(asarPath)
const archive = getOrCreateArchive(asarPath)
if (!archive) {
invalidArchiveError(asarPath)
}
info = archive.getFileInfo(filePath)
const info = archive.getFileInfo(filePath)
if (!info) {
notFoundError(asarPath, filePath)
}
@ -441,7 +515,7 @@
}
}
if (info.unpacked) {
realPath = archive.copyFileOut(filePath)
const realPath = archive.copyFileOut(filePath)
return fs.readFileSync(realPath, options)
}
if (!options) {
@ -455,9 +529,9 @@
} else if (!util.isObject(options)) {
throw new TypeError('Bad arguments')
}
encoding = options.encoding
buffer = new Buffer(info.size)
fd = archive.getFd()
const {encoding} = options
const buffer = new Buffer(info.size)
const fd = archive.getFd()
if (!(fd >= 0)) {
notFoundError(asarPath, filePath)
}
@ -469,89 +543,89 @@
return buffer
}
}
readdir = fs.readdir
const {readdir} = fs
fs.readdir = function (p, callback) {
var archive, files
const [isAsar, asarPath, filePath] = splitPath(p)
if (!isAsar) {
return readdir.apply(this, arguments)
}
archive = getOrCreateArchive(asarPath)
const archive = getOrCreateArchive(asarPath)
if (!archive) {
return invalidArchiveError(asarPath, callback)
}
files = archive.readdir(filePath)
const files = archive.readdir(filePath)
if (!files) {
return notFoundError(asarPath, filePath, callback)
}
return process.nextTick(function () {
return callback(null, files)
process.nextTick(function () {
callback(null, files)
})
}
readdirSync = fs.readdirSync
const {readdirSync} = fs
fs.readdirSync = function (p) {
var archive, files
const [isAsar, asarPath, filePath] = splitPath(p)
if (!isAsar) {
return readdirSync.apply(this, arguments)
}
archive = getOrCreateArchive(asarPath)
const archive = getOrCreateArchive(asarPath)
if (!archive) {
invalidArchiveError(asarPath)
}
files = archive.readdir(filePath)
const files = archive.readdir(filePath)
if (!files) {
notFoundError(asarPath, filePath)
}
return files
}
internalModuleReadFile = process.binding('fs').internalModuleReadFile
const {internalModuleReadFile} = process.binding('fs')
process.binding('fs').internalModuleReadFile = function (p) {
var archive, buffer, fd, info, realPath
const [isAsar, asarPath, filePath] = splitPath(p)
if (!isAsar) {
return internalModuleReadFile(p)
}
archive = getOrCreateArchive(asarPath)
const archive = getOrCreateArchive(asarPath)
if (!archive) {
return void 0
return
}
info = archive.getFileInfo(filePath)
const info = archive.getFileInfo(filePath)
if (!info) {
return void 0
return
}
if (info.size === 0) {
return ''
}
if (info.unpacked) {
realPath = archive.copyFileOut(filePath)
const realPath = archive.copyFileOut(filePath)
return fs.readFileSync(realPath, {
encoding: 'utf8'
})
}
buffer = new Buffer(info.size)
fd = archive.getFd()
const buffer = new Buffer(info.size)
const fd = archive.getFd()
if (!(fd >= 0)) {
return void 0
return
}
logASARAccess(asarPath, filePath, info.offset)
fs.readSync(fd, buffer, 0, info.size, info.offset)
return buffer.toString('utf8')
}
internalModuleStat = process.binding('fs').internalModuleStat
const {internalModuleStat} = process.binding('fs')
process.binding('fs').internalModuleStat = function (p) {
var archive, stats
const [isAsar, asarPath, filePath] = splitPath(p)
if (!isAsar) {
return internalModuleStat(p)
}
archive = getOrCreateArchive(asarPath)
const archive = getOrCreateArchive(asarPath)
// -ENOENT
if (!archive) {
return -34
}
stats = archive.stat(filePath)
const stats = archive.stat(filePath)
// -ENOENT
if (!stats) {
@ -569,7 +643,7 @@
// This is to work around the recursive looping bug of mkdirp since it is
// widely used.
if (process.platform === 'win32') {
mkdir = fs.mkdir
const {mkdir} = fs
fs.mkdir = function (p, mode, callback) {
if (typeof mode === 'function') {
callback = mode
@ -578,9 +652,10 @@
if (isAsar && filePath.length) {
return notDirError(callback)
}
return mkdir(p, mode, callback)
mkdir(p, mode, callback)
}
mkdirSync = fs.mkdirSync
const {mkdirSync} = fs
fs.mkdirSync = function (p, mode) {
const [isAsar, , filePath] = splitPath(p)
if (isAsar && filePath.length) {
@ -596,11 +671,11 @@
// Electron to consider the full command as a single path
// to an archive.
['exec', 'execSync'].forEach(function (functionName) {
var old = childProcess[functionName]
const old = childProcess[functionName]
childProcess[functionName] = function () {
var processNoAsarOriginalValue = process.noAsar
const processNoAsarOriginalValue = process.noAsar
process.noAsar = true
var result = old.apply(this, arguments)
const result = old.apply(this, arguments)
process.noAsar = processNoAsarOriginalValue
return result
}
@ -611,6 +686,6 @@
overrideAPISync(process, 'dlopen', 1)
overrideAPISync(require('module')._extensions, '.node', 1)
overrideAPISync(fs, 'openSync')
return overrideAPISync(childProcess, 'execFileSync')
overrideAPISync(childProcess, 'execFileSync')
}
})()

View file

@ -1,10 +1,11 @@
{
"name": "electron",
"version": "1.3.0",
"version": "1.3.1",
"devDependencies": {
"asar": "^0.11.0",
"request": "*",
"standard": "^7.1.2"
"standard": "^7.1.2",
"standard-markdown": "^1.1.1"
},
"optionalDependencies": {
"runas": "^3.0.0"
@ -23,9 +24,10 @@
"scripts": {
"bootstrap": "python ./script/bootstrap.py",
"build": "python ./script/build.py -c D",
"lint": "npm run lint-js && npm run lint-cpp",
"lint": "npm run lint-js && npm run lint-cpp && npm run lint-docs",
"lint-js": "standard && cd spec && standard",
"lint-cpp": "python ./script/cpplint.py",
"lint-docs": "standard-markdown docs",
"preinstall": "node -e 'process.exit(0)'",
"repl": "python ./script/start.py --interactive",
"start": "python ./script/start.py",

View file

@ -65,7 +65,7 @@ def main():
create_chrome_version_h()
touch_config_gypi()
run_update(defines, args.disable_clang, args.clang_dir)
run_update(defines, args.msvs)
update_electron_modules('spec', args.target_arch)
@ -86,6 +86,8 @@ def parse_args():
action='store_true',
help='Run non-interactively by assuming "yes" to all ' \
'prompts.')
parser.add_argument('--msvs', action='store_true',
help='Generate Visual Studio project')
parser.add_argument('--target_arch', default=get_target_arch(),
help='Manually specify the arch to build for')
parser.add_argument('--clang_dir', default='', help='Path to clang binaries')
@ -249,14 +251,14 @@ def touch_config_gypi():
f.write(content)
def run_update(defines, disable_clang, clang_dir):
env = os.environ.copy()
if not disable_clang and clang_dir == '':
# Build with prebuilt clang.
set_clang_env(env)
def run_update(defines, msvs):
args = [sys.executable, os.path.join(SOURCE_ROOT, 'script', 'update.py')]
if defines:
args += ['--defines', defines]
if msvs:
args += ['--msvs']
update = os.path.join(SOURCE_ROOT, 'script', 'update.py')
execute_stdout([sys.executable, update, '--defines', defines], env)
execute_stdout(args)
if __name__ == '__main__':

View file

@ -35,6 +35,13 @@ def main():
if os.environ.has_key('JANKY_SHA1'):
setup_nodenv()
# Ignore the CXX and CC env in CI.
try:
del os.environ['CC']
del os.environ['CXX']
except KeyError:
pass
target_arch = 'x64'
if os.environ.has_key('TARGET_ARCH'):
target_arch = os.environ['TARGET_ARCH']

View file

@ -52,6 +52,10 @@ TARGET_BINARIES = {
'icudtl.dat',
'libffmpeg.so',
'libnode.so',
'blink_image_resources_200_percent.pak',
'content_resources_200_percent.pak',
'ui_resources_200_percent.pak',
'views_resources_200_percent.pak',
'natives_blob.bin',
'snapshot_blob.bin',
],

View file

@ -28,6 +28,8 @@ def parse_args():
parser = argparse.ArgumentParser(description='Update build configurations')
parser.add_argument('--defines', default='',
help='The definetions passed to gyp')
parser.add_argument('--msvs', action='store_true',
help='Generate Visual Studio project')
return parser.parse_args()
@ -86,7 +88,11 @@ def run_gyp(target_arch, component):
if define:
defines += ['-D' + define]
return subprocess.call([python, gyp, '-f', 'ninja', '--depth', '.',
generator = 'ninja'
if args.msvs:
generator = 'msvs-ninja'
return subprocess.call([python, gyp, '-f', generator, '--depth', '.',
'electron.gyp', '-Icommon.gypi'] + defines, env=env)

View file

@ -88,5 +88,19 @@ describe('crash-reporter module', function () {
})
}, /companyName is a required option to crashReporter\.start/)
})
it('can be called multiple times', function () {
assert.doesNotThrow(function () {
crashReporter.start({
companyName: 'Umbrella Corporation',
submitURL: 'http://127.0.0.1/crashes'
})
crashReporter.start({
companyName: 'Umbrella Corporation 2',
submitURL: 'http://127.0.0.1/more-crashes'
})
})
})
})
})

View file

@ -399,7 +399,7 @@ describe('menu module', function () {
describe('MenuItem role', function () {
it('includes a default label and accelerator', function () {
var item = new MenuItem({role: 'close'})
assert.equal(item.label, 'Close')
assert.equal(item.label, process.platform === 'darwin' ? 'Close Window' : 'Close')
assert.equal(item.accelerator, undefined)
assert.equal(item.getDefaultRoleAccelerator(), 'CommandOrControl+W')

View file

@ -239,9 +239,10 @@ describe('session module', function () {
res.end(mockPDF)
downloadServer.close()
})
var assertDownload = function (event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename, port) {
var assertDownload = function (event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename, port, savePath) {
assert.equal(state, 'completed')
assert.equal(filename, 'mock.pdf')
assert.equal(savePath, path.join(__dirname, 'fixtures', 'mock.pdf'))
assert.equal(url, 'http://127.0.0.1:' + port + '/')
assert.equal(mimeType, 'application/pdf')
assert.equal(receivedBytes, mockPDF.length)
@ -256,8 +257,8 @@ describe('session module', function () {
var port = downloadServer.address().port
ipcRenderer.sendSync('set-download-option', false, false)
w.loadURL(url + ':' + port)
ipcRenderer.once('download-done', function (event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename) {
assertDownload(event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename, port)
ipcRenderer.once('download-done', function (event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename, savePath) {
assertDownload(event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename, port, savePath)
done()
})
})
@ -272,8 +273,8 @@ describe('session module', function () {
webview.addEventListener('did-finish-load', function () {
webview.downloadURL(url + ':' + port + '/')
})
ipcRenderer.once('download-done', function (event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename) {
assertDownload(event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename, port)
ipcRenderer.once('download-done', function (event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename, savePath) {
assertDownload(event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename, port, savePath)
document.body.removeChild(webview)
done()
})

77
spec/api-shell-spec.js Normal file
View file

@ -0,0 +1,77 @@
const assert = require('assert')
const fs = require('fs')
const path = require('path')
const os = require('os')
const {shell} = require('electron')
describe('shell module', function () {
if (process.platform !== 'win32') return
const fixtures = path.resolve(__dirname, 'fixtures')
const shortcutOptions = {
target: 'C:\\target',
description: 'description',
cwd: 'cwd',
args: 'args',
appUserModelId: 'appUserModelId',
icon: 'icon',
iconIndex: 1
}
describe('shell.readShortcutLink(shortcutPath)', function () {
it('throws when failed', function () {
assert.throws(function () {
shell.readShortcutLink('not-exist')
}, /Failed to read shortcut link/)
})
it('reads all properties of a shortcut', function () {
const shortcut = shell.readShortcutLink(path.join(fixtures, 'assets', 'shortcut.lnk'))
assert.deepEqual(shortcut, shortcutOptions)
})
})
describe('shell.writeShortcutLink(shortcutPath[, operation], options)', function () {
const tmpShortcut = path.join(os.tmpdir(), `${Date.now()}.lnk`)
afterEach(function () {
fs.unlinkSync(tmpShortcut)
})
it('writes the shortcut', function () {
assert.equal(shell.writeShortcutLink(tmpShortcut, {target: 'C:\\'}), true)
assert.equal(fs.existsSync(tmpShortcut), true)
})
it('correctly sets the fields', function () {
assert.equal(shell.writeShortcutLink(tmpShortcut, shortcutOptions), true)
assert.deepEqual(shell.readShortcutLink(tmpShortcut), shortcutOptions)
})
it('updates the shortcut', function () {
assert.equal(shell.writeShortcutLink(tmpShortcut, 'update', shortcutOptions), false)
assert.equal(shell.writeShortcutLink(tmpShortcut, 'create', shortcutOptions), true)
assert.deepEqual(shell.readShortcutLink(tmpShortcut), shortcutOptions)
const change = {target: 'D:\\'}
assert.equal(shell.writeShortcutLink(tmpShortcut, 'update', change), true)
assert.deepEqual(shell.readShortcutLink(tmpShortcut), Object.assign(shortcutOptions, change))
})
it('replaces the shortcut', function () {
assert.equal(shell.writeShortcutLink(tmpShortcut, 'replace', shortcutOptions), false)
assert.equal(shell.writeShortcutLink(tmpShortcut, 'create', shortcutOptions), true)
assert.deepEqual(shell.readShortcutLink(tmpShortcut), shortcutOptions)
const change = {
target: 'D:\\',
description: 'description2',
cwd: 'cwd2',
args: 'args2',
appUserModelId: 'appUserModelId2',
icon: 'icon2',
iconIndex: 2
}
assert.equal(shell.writeShortcutLink(tmpShortcut, 'replace', change), true)
assert.deepEqual(shell.readShortcutLink(tmpShortcut), change)
})
})
})

View file

@ -13,6 +13,11 @@ describe('asar package', function () {
var fixtures = path.join(__dirname, 'fixtures')
describe('node api', function () {
it('supports paths specified as a Buffer', function () {
var file = new Buffer(path.join(fixtures, 'asar', 'a.asar', 'file1'))
assert.equal(fs.existsSync(file), true)
})
describe('fs.readFileSync', function () {
it('does not leak fd', function () {
var readCalls = 1
@ -534,6 +539,70 @@ describe('asar package', function () {
})
})
describe('fs.access', function () {
it('accesses a normal file', function (done) {
var p = path.join(fixtures, 'asar', 'a.asar', 'file1')
fs.access(p, function (err) {
assert(err == null)
done()
})
})
it('throws an error when called with write mode', function (done) {
var p = path.join(fixtures, 'asar', 'a.asar', 'file1')
fs.access(p, fs.constants.R_OK | fs.constants.W_OK, function (err) {
assert.equal(err.code, 'EACCES')
done()
})
})
it('throws an error when called on non-existent file', function (done) {
var p = path.join(fixtures, 'asar', 'a.asar', 'not-exist')
fs.access(p, function (err) {
assert.equal(err.code, 'ENOENT')
done()
})
})
it('allows write mode for unpacked files', function (done) {
var p = path.join(fixtures, 'asar', 'unpack.asar', 'a.txt')
fs.access(p, fs.constants.R_OK | fs.constants.W_OK, function (err) {
assert(err == null)
done()
})
})
})
describe('fs.accessSync', function () {
it('accesses a normal file', function () {
var p = path.join(fixtures, 'asar', 'a.asar', 'file1')
assert.doesNotThrow(function () {
fs.accessSync(p)
})
})
it('throws an error when called with write mode', function () {
var p = path.join(fixtures, 'asar', 'a.asar', 'file1')
assert.throws(function () {
fs.accessSync(p, fs.constants.R_OK | fs.constants.W_OK)
}, /EACCES/)
})
it('throws an error when called on non-existent file', function () {
var p = path.join(fixtures, 'asar', 'a.asar', 'not-exist')
assert.throws(function () {
fs.accessSync(p)
}, /ENOENT/)
})
it('allows write mode for unpacked files', function () {
var p = path.join(fixtures, 'asar', 'unpack.asar', 'a.txt')
assert.doesNotThrow(function () {
fs.accessSync(p, fs.constants.R_OK | fs.constants.W_OK)
})
})
})
describe('child_process.fork', function () {
it('opens a normal js file', function (done) {
var child = ChildProcess.fork(path.join(fixtures, 'asar', 'a.asar', 'ping.js'))

BIN
spec/fixtures/assets/shortcut.lnk vendored Executable file

Binary file not shown.

View file

@ -142,7 +142,8 @@ app.on('ready', function () {
item.getReceivedBytes(),
item.getTotalBytes(),
item.getContentDisposition(),
item.getFilename())
item.getFilename(),
item.getSavePath())
})
if (needCancel) item.cancel()
}

2
vendor/brightray vendored

@ -1 +1 @@
Subproject commit 75a43dd74af193b4d081a06eb187886ab6c3c58d
Subproject commit 230dd282372480256006d1beda7b970d1b5c517f