diff --git a/atom/browser/api/atom_api_tray.cc b/atom/browser/api/atom_api_tray.cc index e576601449d6..213ddbfd89a0 100644 --- a/atom/browser/api/atom_api_tray.cc +++ b/atom/browser/api/atom_api_tray.cc @@ -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 { + static bool FromV8(v8::Isolate* isolate, v8::Local 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, diff --git a/atom/browser/api/atom_api_tray.h b/atom/browser/api/atom_api_tray.h index 56d851d44e76..1e1bc1307543 100644 --- a/atom/browser/api/atom_api_tray.h +++ b/atom/browser/api/atom_api_tray.h @@ -10,6 +10,7 @@ #include #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, void SetPressedImage(v8::Isolate* isolate, mate::Handle 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); diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 819f0e105d36..d3f9b39dfd27 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -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) diff --git a/atom/browser/api/atom_api_web_contents.h b/atom/browser/api/atom_api_web_contents.h index 631b8ba0503d..641442e1fc1d 100644 --- a/atom/browser/api/atom_api_web_contents.h +++ b/atom/browser/api/atom_api_web_contents.h @@ -129,6 +129,7 @@ class WebContents : public mate::TrackableObject, uint32_t FindInPage(mate::Arguments* args); void StopFindInPage(content::StopFindAction action); void ShowDefinitionForSelection(); + void CopyImageAt(int x, int y); // Focus. void Focus(); diff --git a/atom/browser/api/atom_api_web_contents_mac.mm b/atom/browser/api/atom_api_web_contents_mac.mm index 19246e82ac7f..913951737fe8 100644 --- a/atom/browser/api/atom_api_web_contents_mac.mm +++ b/atom/browser/api/atom_api_web_contents_mac.mm @@ -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 diff --git a/atom/browser/atom_browser_context.cc b/atom/browser/atom_browser_context.cc index a87b29620e80..7ac5dde4ef21 100644 --- a/atom/browser/atom_browser_context.cc +++ b/atom/browser/atom_browser_context.cc @@ -88,6 +88,7 @@ AtomBrowserContext::AtomBrowserContext( use_cache_ = true; options.GetBoolean("cache", &use_cache_); + // Initialize Pref Registry in brightray. InitPrefs(); } diff --git a/atom/browser/atom_download_manager_delegate.cc b/atom/browser/atom_download_manager_delegate.cc index 6d7e3c7172d8..63ca5f661f98 100644 --- a/atom/browser/atom_download_manager_delegate.cc +++ b/atom/browser/atom_download_manager_delegate.cc @@ -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 diff --git a/atom/browser/common_web_contents_delegate_mac.mm b/atom/browser/common_web_contents_delegate_mac.mm index 34df15bb0c2c..c76606137939 100644 --- a/atom/browser/common_web_contents_delegate_mac.mm +++ b/atom/browser/common_web_contents_delegate_mac.mm @@ -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]; } diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index 662835ab921a..d6f8dcf3cb45 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -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_); diff --git a/atom/browser/native_window_views.h b/atom/browser/native_window_views.h index 4dde25b6172f..daca3ae2e717 100644 --- a/atom/browser/native_window_views.h +++ b/atom/browser/native_window_views.h @@ -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_; diff --git a/atom/browser/native_window_views_win.cc b/atom/browser/native_window_views_win.cc index 9af003d55db0..bb6d9858f045 100644 --- a/atom/browser/native_window_views_win.cc +++ b/atom/browser/native_window_views_win.cc @@ -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; } diff --git a/atom/browser/resources/mac/Info.plist b/atom/browser/resources/mac/Info.plist index 5eb6079f6c98..b3f3bc69a4fb 100644 --- a/atom/browser/resources/mac/Info.plist +++ b/atom/browser/resources/mac/Info.plist @@ -17,9 +17,9 @@ CFBundleIconFile electron.icns CFBundleVersion - 1.3.0 + 1.3.1 CFBundleShortVersionString - 1.3.0 + 1.3.1 LSApplicationCategoryType public.app-category.developer-tools LSMinimumSystemVersion diff --git a/atom/browser/resources/win/atom.ico b/atom/browser/resources/win/atom.ico index aa0917755465..004176004f74 100644 Binary files a/atom/browser/resources/win/atom.ico and b/atom/browser/resources/win/atom.ico differ diff --git a/atom/browser/resources/win/atom.rc b/atom/browser/resources/win/atom.rc index 0527a33b32e9..d95e04c07b80 100644 --- a/atom/browser/resources/win/atom.rc +++ b/atom/browser/resources/win/atom.rc @@ -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 diff --git a/atom/browser/ui/tray_icon.cc b/atom/browser/ui/tray_icon.cc index 6e54af92757d..273f5c084391 100644 --- a/atom/browser/ui/tray_icon.cc +++ b/atom/browser/ui/tray_icon.cc @@ -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, diff --git a/atom/browser/ui/tray_icon.h b/atom/browser/ui/tray_icon.h index 78981d076c77..90e81657aae2 100644 --- a/atom/browser/ui/tray_icon.h +++ b/atom/browser/ui/tray_icon.h @@ -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. diff --git a/atom/browser/ui/tray_icon_cocoa.h b/atom/browser/ui/tray_icon_cocoa.h index e9abaa5590e3..fb66a6b3f147 100644 --- a/atom/browser/ui/tray_icon_cocoa.h +++ b/atom/browser/ui/tray_icon_cocoa.h @@ -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; diff --git a/atom/browser/ui/tray_icon_cocoa.mm b/atom/browser/ui/tray_icon_cocoa.mm index 231f1bfb1282..5ac9050b622c 100644 --- a/atom/browser/ui/tray_icon_cocoa.mm +++ b/atom/browser/ui/tray_icon_cocoa.mm @@ -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 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_) - return true; - BOOL isMenuOpen = menuController_ && [menuController_ isMenuOpen]; - return isHighlightEnable_ && (inMouseEventSequence_ || isMenuOpen); + 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 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, diff --git a/atom/common/api/atom_api_shell.cc b/atom/common/api/atom_api_shell.cc index 860787f17c8f..2d9d6e02697a 100644 --- a/atom/common/api/atom_api_shell.cc +++ b/atom/common/api/atom_api_shell.cc @@ -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 { + static bool FromV8(v8::Isolate* isolate, v8::Handle 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 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 exports, v8::Local unused, v8::Local context, void* priv) { mate::Dictionary dict(context->GetIsolate(), exports); @@ -38,6 +121,10 @@ void Initialize(v8::Local exports, v8::Local 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 diff --git a/atom/common/atom_version.h b/atom/common/atom_version.h index 437562f9ca82..ddf222faf13c 100644 --- a/atom/common/atom_version.h +++ b/atom/common/atom_version.h @@ -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 diff --git a/atom/common/crash_reporter/crash_reporter_win.cc b/atom/common/crash_reporter/crash_reporter_win.cc index 7ce61d69719c..3261f9c9c6a3 100644 --- a/atom/common/crash_reporter/crash_reporter_win.cc +++ b/atom/common/crash_reporter/crash_reporter_win.cc @@ -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 } diff --git a/atom/common/crash_reporter/crash_reporter_win.h b/atom/common/crash_reporter/crash_reporter_win.h index 93be8af32cdb..806c3de317b2 100644 --- a/atom/common/crash_reporter/crash_reporter_win.h +++ b/atom/common/crash_reporter/crash_reporter_win.h @@ -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 breakpad_; DISALLOW_COPY_AND_ASSIGN(CrashReporterWin); diff --git a/docs-translations/jp/README.md b/docs-translations/jp/README.md index 734bc8274c52..36d99f518849 100644 --- a/docs-translations/jp/README.md +++ b/docs-translations/jp/README.md @@ -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) # チュートリアル diff --git a/docs-translations/jp/tutorial/testing-on-headless-ci.md b/docs-translations/jp/tutorial/testing-on-headless-ci.md new file mode 100644 index 000000000000..7fba4fef7d18 --- /dev/null +++ b/docs-translations/jp/tutorial/testing-on-headless-ci.md @@ -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 上で動くので、特に設定は必要ありません。 diff --git a/docs-translations/zh-CN/api/browser-window.md b/docs-translations/zh-CN/api/browser-window.md index f2fe697c729c..d28ffc9f45ea 100644 --- a/docs-translations/zh-CN/api/browser-window.md +++ b/docs-translations/zh-CN/api/browser-window.md @@ -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`. diff --git a/docs-translations/zh-CN/development/build-instructions-osx.md b/docs-translations/zh-CN/development/build-instructions-osx.md index d2052c27a54d..f9992c929958 100644 --- a/docs-translations/zh-CN/development/build-instructions-osx.md +++ b/docs-translations/zh-CN/development/build-instructions-osx.md @@ -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 @@ -59,4 +59,4 @@ $ ./script/cpplint.py ```bash $ ./script/test.py -``` \ No newline at end of file +``` diff --git a/docs/README.md b/docs/README.md index 9f4973759453..aec23bf47ec1 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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) diff --git a/docs/api/app.md b/docs/api/app.md index 114ac8c5e09b..1220f36c9f8c 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -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()` diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index f44a7ddeeb93..45060066e3f0 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -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)` diff --git a/docs/api/chrome-command-line-switches.md b/docs/api/chrome-command-line-switches.md index 92b52ff00b38..6bd0537b9b89 100644 --- a/docs/api/chrome-command-line-switches.md +++ b/docs/api/chrome-command-line-switches.md @@ -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', ';*.google.com;*foo.com;1.2.3.4:5678') ``` diff --git a/docs/api/clipboard.md b/docs/api/clipboard.md index 85712bc651d9..bc7e15ee09b0 100644 --- a/docs/api/clipboard.md +++ b/docs/api/clipboard.md @@ -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('

selection

')); +const {clipboard} = require('electron') +console.log(clipboard.has('

selection

')) ``` ### `clipboard.read(data[, type])` _Experimental_ @@ -130,6 +132,7 @@ Reads `data` from the clipboard. * `type` String (optional) ```javascript -clipboard.write({text: 'test', html: "test"}); +const {clipboard} = require('electron') +clipboard.write({text: 'test', html: 'test'}) ``` Writes `data` to the clipboard. diff --git a/docs/api/content-tracing.md b/docs/api/content-tracing.md index d269d602ef59..8cfc7670c6a6 100644 --- a/docs/api/content-tracing.md +++ b/docs/api/content-tracing.md @@ -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 diff --git a/docs/api/crash-reporter.md b/docs/api/crash-reporter.md index bd323de88394..5c84cb4cb113 100644 --- a/docs/api/crash-reporter.md +++ b/docs/api/crash-reporter.md @@ -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 diff --git a/docs/api/desktop-capturer.md b/docs/api/desktop-capturer.md index a72539a7a283..af35b11fdeeb 100644 --- a/docs/api/desktop-capturer.md +++ b/docs/api/desktop-capturer.md @@ -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 `; 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 `, 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 diff --git a/docs/api/dialog.md b/docs/api/dialog.md index d71109e9dfdc..f6bf0fdc19fd 100644 --- a/docs/api/dialog.md +++ b/docs/api/dialog.md @@ -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']}, diff --git a/docs/api/download-item.md b/docs/api/download-item.md index 778f68b7bc36..7ec8fd1896fe 100644 --- a/docs/api/download-item.md +++ b/docs/api/download-item.md @@ -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. diff --git a/docs/api/file-object.md b/docs/api/file-object.md index b1e643c74080..c39a0cf715e6 100644 --- a/docs/api/file-object.md +++ b/docs/api/file-object.md @@ -15,19 +15,19 @@ Example on getting a real path from a dragged-onto-the-app file: ``` diff --git a/docs/api/frameless-window.md b/docs/api/frameless-window.md index b6b5926beef1..1ca64a456f28 100644 --- a/docs/api/frameless-window.md +++ b/docs/api/frameless-window.md @@ -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) ``` diff --git a/docs/api/global-shortcut.md b/docs/api/global-shortcut.md index d9488a7b7fd9..5a2f819ac368 100644 --- a/docs/api/global-shortcut.md +++ b/docs/api/global-shortcut.md @@ -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 diff --git a/docs/api/ipc-main.md b/docs/api/ipc-main.md index 0b7acbfefa0c..6ba9c2656403 100644 --- a/docs/api/ipc-main.md +++ b/docs/api/ipc-main.md @@ -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 diff --git a/docs/api/menu.md b/docs/api/menu.md index 5a405ea12ba2..760942df2491 100644 --- a/docs/api/menu.md +++ b/docs/api/menu.md @@ -15,18 +15,18 @@ the user right clicks the page: ```html ``` @@ -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' - }, + } ] }, { @@ -73,8 +75,8 @@ const template = [ { label: 'Reload', accelerator: 'CmdOrCtrl+R', - click(item, focusedWindow) { - if (focusedWindow) focusedWindow.reload(); + click (item, focusedWindow) { + if (focusedWindow) focusedWindow.reload() } }, { @@ -83,11 +85,10 @@ const template = [ { label: 'Toggle Developer Tools', accelerator: process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I', - click(item, focusedWindow) { - if (focusedWindow) - focusedWindow.webContents.toggleDevTools(); + click (item, focusedWindow) { + 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'}, diff --git a/docs/api/native-image.md b/docs/api/native-image.md index 694193900aff..eed0b5cbaa18 100644 --- a/docs/api/native-image.md +++ b/docs/api/native-image.md @@ -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])` diff --git a/docs/api/power-monitor.md b/docs/api/power-monitor.md index 12c27578e67e..57e9d030be92 100644 --- a/docs/api/power-monitor.md +++ b/docs/api/power-monitor.md @@ -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 diff --git a/docs/api/power-save-blocker.md b/docs/api/power-save-blocker.md index f9e350867a3e..4ad17df53bd2 100644 --- a/docs/api/power-save-blocker.md +++ b/docs/api/power-save-blocker.md @@ -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 diff --git a/docs/api/process.md b/docs/api/process.md index 2f618f240da3..e467f2fc2cdb 100644 --- a/docs/api/process.md +++ b/docs/api/process.md @@ -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 diff --git a/docs/api/protocol.md b/docs/api/protocol.md index bcea37bd224c..97e37d3c4ba2 100644 --- a/docs/api/protocol.md +++ b/docs/api/protocol.md @@ -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('
Response
')}); + callback({mimeType: 'text/html', data: new Buffer('
Response
')}) }, (error) => { - if (error) - console.error('Failed to register protocol'); -}); + if (error) console.error('Failed to register protocol') +}) ``` ### `protocol.registerStringProtocol(scheme, handler[, completion])` diff --git a/docs/api/remote.md b/docs/api/remote.md index aa1b8082bb08..455e7f58a6a5 100644 --- a/docs/api/remote.md +++ b/docs/api/remote.md @@ -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 diff --git a/docs/api/screen.md b/docs/api/screen.md index d5318d8fc02c..191688afd5db 100644 --- a/docs/api/screen.md +++ b/docs/api/screen.md @@ -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') } }) ``` diff --git a/docs/api/session.md b/docs/api/session.md index 527effc4d0b6..3ec443f884fd 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -1,4 +1,4 @@ -# session + # session > Manage browser sessions, cookies, cache, proxy settings, etc. @@ -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 @@ -220,13 +222,13 @@ Emulates network with the given configuration for the `session`. ```javascript // To emulate a GPRS connection with 50kbps throughput and 500 ms latency. window.webContents.session.enableNetworkEmulation({ - latency: 500, - downloadThroughput: 6400, - uploadThroughput: 6400 -}); + 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}) }) ``` diff --git a/docs/api/shell.md b/docs/api/shell.md index a1c01f0fbc77..bad864f28679 100644 --- a/docs/api/shell.md +++ b/docs/api/shell.md @@ -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. diff --git a/docs/api/synopsis.md b/docs/api/synopsis.md index a83ffc41f7b9..8a7fca71b77d 100644 --- a/docs/api/synopsis.md +++ b/docs/api/synopsis.md @@ -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: @@ -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 diff --git a/docs/api/system-preferences.md b/docs/api/system-preferences.md index 586422e56654..8a2af28a295d 100644 --- a/docs/api/system-preferences.md +++ b/docs/api/system-preferences.md @@ -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`) } ``` diff --git a/docs/api/tray.md b/docs/api/tray.md index 5ab68c5e9445..b0333402c047 100644 --- a/docs/api/tray.md +++ b/docs/api/tray.md @@ -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_ diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index fffcc8a47f47..8810e634bb14 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -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', () => { @@ -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'); -} catch(err) { - console.log('Debugger attach failed : ', err); -}; + win.webContents.debugger.attach('1.1') +} catch (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 diff --git a/docs/api/web-frame.md b/docs/api/web-frame.md index 1ad5589bfb4e..31ec91f93f9d 100644 --- a/docs/api/web-frame.md +++ b/docs/api/web-frame.md @@ -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)); + spellCheck (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()` diff --git a/docs/api/web-view-tag.md b/docs/api/web-view-tag.md index e5f6df811d47..8c93bf9fbb8e 100644 --- a/docs/api/web-view-tag.md +++ b/docs/api/web-view-tag.md @@ -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 ``` @@ -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() +}) ``` ### `.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' diff --git a/docs/development/build-instructions-windows.md b/docs/development/build-instructions-windows.md index 7a7e1ee6c6e9..400fa370e8c5 100644 --- a/docs/development/build-instructions-windows.md +++ b/docs/development/build-instructions-windows.md @@ -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: diff --git a/docs/development/debugging-instructions-macos.md b/docs/development/debugging-instructions-macos.md new file mode 100644 index 000000000000..e119db466a19 --- /dev/null +++ b/docs/development/debugging-instructions-macos.md @@ -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, std::__1::allocator > 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 diff --git a/docs/faq.md b/docs/faq.md index 51ebf687a41c..0079d43f4e26 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -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: diff --git a/docs/tutorial/application-packaging.md b/docs/tutorial/application-packaging.md index 07caa681102a..819720122502 100644 --- a/docs/tutorial/application-packaging.md +++ b/docs/tutorial/application-packaging.md @@ -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 ``` @@ -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 diff --git a/docs/tutorial/desktop-environment-integration.md b/docs/tutorial/desktop-environment-integration.md index 413106bb6357..343657092660 100644 --- a/docs/tutorial/desktop-environment-integration.md +++ b/docs/tutorial/desktop-environment-integration.md @@ -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 with Settings', submenu: [ - { label: 'Basic' }, - { label: 'Pro'} + {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); + {label: 'New Command...'} +]) +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, diff --git a/docs/tutorial/devtools-extension.md b/docs/tutorial/devtools-extension.md index 00911a2d2e92..84c8447172ae 100644 --- a/docs/tutorial/devtools-extension.md +++ b/docs/tutorial/devtools-extension.md @@ -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` diff --git a/docs/tutorial/online-offline-events.md b/docs/tutorial/online-offline-events.md index 2b7a6d7edc6a..f89e7fad78e4 100644 --- a/docs/tutorial/online-offline-events.md +++ b/docs/tutorial/online-offline-events.md @@ -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_ @@ -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_ diff --git a/docs/tutorial/quick-start.md b/docs/tutorial/quick-start.md index 6abfd9133e8c..5ae2e81c8b49 100644 --- a/docs/tutorial/quick-start.md +++ b/docs/tutorial/quick-start.md @@ -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() { +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. diff --git a/docs/tutorial/using-pepper-flash-plugin.md b/docs/tutorial/using-pepper-flash-plugin.md index 349d7c0e9f63..4b4fab5867a1 100644 --- a/docs/tutorial/using-pepper-flash-plugin.md +++ b/docs/tutorial/using-pepper-flash-plugin.md @@ -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 diff --git a/docs/tutorial/using-selenium-and-webdriver.md b/docs/tutorial/using-selenium-and-webdriver.md index 50727c7169f4..8e6961697764 100644 --- a/docs/tutorial/using-selenium-and-webdriver.md +++ b/docs/tutorial/using-selenium-and-webdriver.md @@ -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 driver.getTitle().then((title) => { + 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 diff --git a/docs/tutorial/using-widevine-cdm-plugin.md b/docs/tutorial/using-widevine-cdm-plugin.md index b7b37c58a991..512da7a041a1 100644 --- a/docs/tutorial/using-widevine-cdm-plugin.md +++ b/docs/tutorial/using-widevine-cdm-plugin.md @@ -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 diff --git a/electron.gyp b/electron.gyp index 4f8a1056c572..45a0bebc99de 100644 --- a/electron.gyp +++ b/electron.gyp @@ -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', ], diff --git a/lib/browser/api/auto-updater/auto-updater-win.js b/lib/browser/api/auto-updater/auto-updater-win.js index 564e2ff762a2..a010f76dd743 100644 --- a/lib/browser/api/auto-updater/auto-updater-win.js +++ b/lib/browser/api/auto-updater/auto-updater-win.js @@ -1,71 +1,66 @@ '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 () { - if (!this.updateAvailable) { - return this.emitError('No update available, can\'t quit and install') - } - squirrelUpdate.processStart() - return app.quit() -} - -AutoUpdater.prototype.getFeedURL = function () { - return this.updateURL -} - -AutoUpdater.prototype.setFeedURL = function (updateURL, headers) { - this.updateURL = updateURL -} - -AutoUpdater.prototype.checkForUpdates = function () { - if (!this.updateURL) { - return this.emitError('Update URL is not set') - } - if (!squirrelUpdate.supported()) { - return this.emitError('Can not find Squirrel') - } - this.emit('checking-for-update') - squirrelUpdate.download(this.updateURL, (error, update) => { - if (error != null) { - return this.emitError(error) +class AutoUpdater extends EventEmitter { + quitAndInstall () { + if (!this.updateAvailable) { + return this.emitError('No update available, can\'t quit and install') } - if (update == null) { - this.updateAvailable = false - return this.emit('update-not-available') + squirrelUpdate.processStart() + return app.quit() + } + + getFeedURL () { + return this.updateURL + } + + setFeedURL (updateURL, headers) { + this.updateURL = updateURL + } + + checkForUpdates () { + if (!this.updateURL) { + return this.emitError('Update URL is not set') } - this.updateAvailable = true - this.emit('update-available') - squirrelUpdate.update(this.updateURL, (error) => { - var date, releaseNotes, version + if (!squirrelUpdate.supported()) { + return this.emitError('Can not find Squirrel') + } + this.emit('checking-for-update') + squirrelUpdate.download(this.updateURL, (error, update) => { if (error != null) { return this.emitError(error) } - releaseNotes = update.releaseNotes - version = update.version + if (update == null) { + this.updateAvailable = false + return this.emit('update-not-available') + } + this.updateAvailable = true + this.emit('update-available') + squirrelUpdate.update(this.updateURL, (error) => { + var date, releaseNotes, version + if (error != null) { + return this.emitError(error) + } + releaseNotes = update.releaseNotes + version = update.version - // Following information is not available on Windows, so fake them. - date = new Date() - this.emit('update-downloaded', {}, releaseNotes, version, date, this.updateURL, () => { - this.quitAndInstall() + // Following information is not available on Windows, so fake them. + date = new Date() + this.emit('update-downloaded', {}, releaseNotes, version, date, this.updateURL, () => { + this.quitAndInstall() + }) }) }) - }) -} + } -// Private: Emit both error object and message, this is to keep compatibility -// with Old APIs. -AutoUpdater.prototype.emitError = function (message) { - return this.emit('error', new Error(message), message) + // Private: Emit both error object and message, this is to keep compatibility + // with Old APIs. + emitError (message) { + return this.emit('error', new Error(message), message) + } } module.exports = new AutoUpdater() diff --git a/lib/browser/api/menu-item-roles.js b/lib/browser/api/menu-item-roles.js index 6487911764e1..17447ffc66a2 100644 --- a/lib/browser/api/menu-item-roles.js +++ b/lib/browser/api/menu-item-roles.js @@ -7,7 +7,7 @@ const roles = { } }, close: { - label: 'Close', + label: process.platform === 'darwin' ? 'Close Window' : 'Close', accelerator: 'CommandOrControl+W', windowMethod: 'close' }, diff --git a/lib/browser/init.js b/lib/browser/init.js index 68a555ad5858..88377a6978bb 100644 --- a/lib/browser/init.js +++ b/lib/browser/init.js @@ -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}`) } diff --git a/lib/common/asar.js b/lib/common/asar.js index 6adc24e8ded0..fcb26ba97b71 100644 --- a/lib/common/asar.js +++ b/lib/common/asar.js @@ -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) { @@ -595,12 +670,12 @@ // called by `childProcess.{exec,execSync}`, causing // Electron to consider the full command as a single path // to an archive. - [ 'exec', 'execSync' ].forEach(function (functionName) { - var old = childProcess[functionName] + ['exec', 'execSync'].forEach(function (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') } })() diff --git a/package.json b/package.json index 6dab962b19da..3c9f28c1e0f0 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/script/bootstrap.py b/script/bootstrap.py index 198d7d0a7dec..dc70ba6110eb 100755 --- a/script/bootstrap.py +++ b/script/bootstrap.py @@ -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__': diff --git a/script/cibuild b/script/cibuild index 7d025b4e82cd..7f33762c614c 100755 --- a/script/cibuild +++ b/script/cibuild @@ -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'] diff --git a/script/create-dist.py b/script/create-dist.py index ff2f22f4090e..9d37c03108e9 100755 --- a/script/create-dist.py +++ b/script/create-dist.py @@ -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', ], diff --git a/script/update.py b/script/update.py index a67a49e7ab51..2e551f7ad6cf 100755 --- a/script/update.py +++ b/script/update.py @@ -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) diff --git a/spec/api-crash-reporter-spec.js b/spec/api-crash-reporter-spec.js index b4f63fe1ef27..16806c7102c4 100644 --- a/spec/api-crash-reporter-spec.js +++ b/spec/api-crash-reporter-spec.js @@ -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' + }) + }) + }) }) }) diff --git a/spec/api-menu-spec.js b/spec/api-menu-spec.js index b5c707bfb377..4ea6d71cdb5a 100644 --- a/spec/api-menu-spec.js +++ b/spec/api-menu-spec.js @@ -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') diff --git a/spec/api-session-spec.js b/spec/api-session-spec.js index 640cc44551d5..e6f5737b33ba 100644 --- a/spec/api-session-spec.js +++ b/spec/api-session-spec.js @@ -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() }) diff --git a/spec/api-shell-spec.js b/spec/api-shell-spec.js new file mode 100644 index 000000000000..8c9f9833848a --- /dev/null +++ b/spec/api-shell-spec.js @@ -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) + }) + }) +}) diff --git a/spec/asar-spec.js b/spec/asar-spec.js index 376e5ef0805f..81f6ebe2161d 100644 --- a/spec/asar-spec.js +++ b/spec/asar-spec.js @@ -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')) diff --git a/spec/fixtures/assets/shortcut.lnk b/spec/fixtures/assets/shortcut.lnk new file mode 100755 index 000000000000..5f325ca733ea Binary files /dev/null and b/spec/fixtures/assets/shortcut.lnk differ diff --git a/spec/static/main.js b/spec/static/main.js index 5104a0f2525a..8a432532f8e7 100644 --- a/spec/static/main.js +++ b/spec/static/main.js @@ -142,7 +142,8 @@ app.on('ready', function () { item.getReceivedBytes(), item.getTotalBytes(), item.getContentDisposition(), - item.getFilename()) + item.getFilename(), + item.getSavePath()) }) if (needCancel) item.cancel() } diff --git a/vendor/brightray b/vendor/brightray index 75a43dd74af1..230dd2823724 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit 75a43dd74af193b4d081a06eb187886ab6c3c58d +Subproject commit 230dd282372480256006d1beda7b970d1b5c517f