From 1945771f37ae213176fb5e6ab21223f237d99c6a Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Mon, 30 Jan 2017 10:49:17 -0800 Subject: [PATCH 001/925] Add SECURITY.md --- SECURITY.md | 9 +++++++++ docs/tutorial/security.md | 5 +++++ 2 files changed, 14 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000000..ff2f101842 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,9 @@ +# Reporting Security Issues + +The Electron team and community take security bugs in Electron seriously. We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions. + +To report a security issue, email [electron@github.com](mailto:electron@github.com) and include the word "SECURITY" in the subject line. + +The Electron team will send a response indicating the next steps in handling your report. After the initial reply to your report, the security team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance. + +Report security bugs in third-party modules to the person or team maintaining the module. You can also report a vulnerability through the [Node Security Project](https://nodesecurity.io/report). diff --git a/docs/tutorial/security.md b/docs/tutorial/security.md index 6962f79256..356ebf68f5 100644 --- a/docs/tutorial/security.md +++ b/docs/tutorial/security.md @@ -20,6 +20,11 @@ display primarily local content (or trusted, secure remote content without Node integration) – if your application executes code from an online source, it is your responsibility to ensure that the code is not malicious. +## Disclosing Security Vulnerabilities + +For information on how to properly disclose an Electron vulnerability, +see [SECURITY.md](https://github.com/electron/electron/tree/master/SECURITY.md) + ## Chromium Security Issues and Upgrades While Electron strives to support new versions of Chromium as soon as possible, From f0882a505820e4ae774e5f5f654a5c16cdf14ae0 Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Mon, 30 Jan 2017 10:57:53 -0800 Subject: [PATCH 002/925] use consistent headings --- docs/tutorial/security.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/security.md b/docs/tutorial/security.md index 356ebf68f5..47fe218f55 100644 --- a/docs/tutorial/security.md +++ b/docs/tutorial/security.md @@ -20,7 +20,7 @@ display primarily local content (or trusted, secure remote content without Node integration) – if your application executes code from an online source, it is your responsibility to ensure that the code is not malicious. -## Disclosing Security Vulnerabilities +## Reporting Security Issues For information on how to properly disclose an Electron vulnerability, see [SECURITY.md](https://github.com/electron/electron/tree/master/SECURITY.md) From b8e12a0d21a4950b72d73ca7ff117a6058bdda80 Mon Sep 17 00:00:00 2001 From: Jacob Groundwater Date: Mon, 6 Feb 2017 14:57:04 -0800 Subject: [PATCH 003/925] Update ISSUE_TEMPLATE.md --- ISSUE_TEMPLATE.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md index 7552ddadc2..1dac63b5c7 100644 --- a/ISSUE_TEMPLATE.md +++ b/ISSUE_TEMPLATE.md @@ -19,5 +19,16 @@ Thanks for opening an issue! A few things to keep in mind: ### How to reproduce + +Your best chance of getting the bug fixed quickly is to provide a REPOSITORY that can be cloned and run. + +If you provide a URL, please list the commands required to clone/setup/run your repo e.g. + + $ git clone $YOUR_URL -b $BRANCH + $ npm install + $ npm start || electron . + +You can fork https://github.com/electron/electron-quick-start and submit the branch with your changes + +--> From 852519a826dda90ee911adb0a3548d2fe4b31ea2 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 6 Feb 2017 11:20:07 -0800 Subject: [PATCH 004/925] Bump v1.6.0 --- atom/browser/resources/mac/Info.plist | 4 ++-- atom/browser/resources/win/atom.rc | 8 ++++---- atom/common/atom_version.h | 4 ++-- electron.gyp | 2 +- package.json | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/atom/browser/resources/mac/Info.plist b/atom/browser/resources/mac/Info.plist index 55007cd987..e0655b6fa4 100644 --- a/atom/browser/resources/mac/Info.plist +++ b/atom/browser/resources/mac/Info.plist @@ -17,9 +17,9 @@ CFBundleIconFile electron.icns CFBundleVersion - 1.5.1 + 1.6.0 CFBundleShortVersionString - 1.5.1 + 1.6.0 LSApplicationCategoryType public.app-category.developer-tools LSMinimumSystemVersion diff --git a/atom/browser/resources/win/atom.rc b/atom/browser/resources/win/atom.rc index 4220927a91..8202be5565 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,5,1,0 - PRODUCTVERSION 1,5,1,0 + FILEVERSION 1,6,0,0 + PRODUCTVERSION 1,6,0,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -74,12 +74,12 @@ BEGIN BEGIN VALUE "CompanyName", "GitHub, Inc." VALUE "FileDescription", "Electron" - VALUE "FileVersion", "1.5.1" + VALUE "FileVersion", "1.6.0" VALUE "InternalName", "electron.exe" VALUE "LegalCopyright", "Copyright (C) 2015 GitHub, Inc. All rights reserved." VALUE "OriginalFilename", "electron.exe" VALUE "ProductName", "Electron" - VALUE "ProductVersion", "1.5.1" + VALUE "ProductVersion", "1.6.0" VALUE "SquirrelAwareVersion", "1" END END diff --git a/atom/common/atom_version.h b/atom/common/atom_version.h index 4355205398..8a865c8cd2 100644 --- a/atom/common/atom_version.h +++ b/atom/common/atom_version.h @@ -6,8 +6,8 @@ #define ATOM_COMMON_ATOM_VERSION_H_ #define ATOM_MAJOR_VERSION 1 -#define ATOM_MINOR_VERSION 5 -#define ATOM_PATCH_VERSION 1 +#define ATOM_MINOR_VERSION 6 +#define ATOM_PATCH_VERSION 0 #define ATOM_VERSION_IS_RELEASE 1 diff --git a/electron.gyp b/electron.gyp index 39c1f19045..adf38cfbe5 100644 --- a/electron.gyp +++ b/electron.gyp @@ -4,7 +4,7 @@ 'product_name%': 'Electron', 'company_name%': 'GitHub, Inc', 'company_abbr%': 'github', - 'version%': '1.5.1', + 'version%': '1.6.0', 'js2c_input_dir': '<(SHARED_INTERMEDIATE_DIR)/js2c', }, 'includes': [ diff --git a/package.json b/package.json index 4265611d87..b4f942ae2a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "electron", - "version": "1.5.1", + "version": "1.6.0", "devDependencies": { "asar": "^0.11.0", "browserify": "^13.1.0", From 84c180ac69816e5aa5f4363c0f4df46b4a8a1779 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 7 Feb 2017 17:07:43 +0900 Subject: [PATCH 005/925] Simplify the trick used for referencing symbols --- atom/node/osfhandle.cc | 38 ++++++++++++++++++++++++++-- common.gypi | 56 +++--------------------------------------- 2 files changed, 39 insertions(+), 55 deletions(-) diff --git a/atom/node/osfhandle.cc b/atom/node/osfhandle.cc index cb3dab6139..b0ac8c1ce3 100644 --- a/atom/node/osfhandle.cc +++ b/atom/node/osfhandle.cc @@ -6,6 +6,22 @@ #include +#define U_I18N_IMPLEMENTATION + +#include "third_party/icu/source/common/unicode/ubidi.h" +#include "third_party/icu/source/common/unicode/uchar.h" +#include "third_party/icu/source/common/unicode/uidna.h" +#include "third_party/icu/source/common/unicode/unistr.h" +#include "third_party/icu/source/common/unicode/unorm.h" +#include "third_party/icu/source/common/unicode/urename.h" +#include "third_party/icu/source/common/unicode/ustring.h" +#include "third_party/icu/source/i18n/unicode/measfmt.h" +#include "third_party/icu/source/i18n/unicode/translit.h" +#include "third_party/icu/source/i18n/unicode/ucsdet.h" +#include "third_party/icu/source/i18n/unicode/ulocdata.h" +#include "third_party/icu/source/i18n/unicode/uregex.h" +#include "third_party/icu/source/i18n/unicode/uspoof.h" +#include "third_party/icu/source/i18n/unicode/usearch.h" #include "v8-profiler.h" #include "v8-inspector.h" @@ -21,12 +37,30 @@ int close(int fd) { void ReferenceSymbols() { // Following symbols are used by electron.exe but got stripped by compiler, - // for some reason, adding them to ForceSymbolReferences does not work, - // probably because of VC++ bugs. + // by using the symbols we can force compiler to keep the objects in node.dll, + // thus electron.exe can link with the exported symbols. + + // v8_profiler symbols: v8::TracingCpuProfiler::Create(nullptr); + // v8_inspector symbols: reinterpret_cast(nullptr)-> canDispatchMethod(v8_inspector::StringView()); reinterpret_cast(nullptr)->unmuteMetrics(0); + // icu symbols: + u_errorName(U_ZERO_ERROR); + ubidi_setPara(nullptr, nullptr, 0, 0, nullptr, nullptr); + ucsdet_getName(nullptr, nullptr); + uidna_openUTS46(UIDNA_CHECK_BIDI, nullptr); + ulocdata_close(nullptr); + unorm_normalize(nullptr, 0, UNORM_NFC, 0, nullptr, 0, nullptr); + uregex_matches(nullptr, 0, nullptr); + uspoof_open(nullptr); + usearch_setPattern(nullptr, nullptr, 0, nullptr); + usearch_setPattern(nullptr, nullptr, 0, nullptr); + UMeasureFormatWidth width = UMEASFMT_WIDTH_WIDE; + UErrorCode status = U_ZERO_ERROR; + icu::MeasureFormat format(icu::Locale::getRoot(), width, status); + reinterpret_cast(nullptr)->clone(); } } // namespace node diff --git a/common.gypi b/common.gypi index ca143b1897..7c1bf366aa 100644 --- a/common.gypi +++ b/common.gypi @@ -129,6 +129,9 @@ }], ['_target_name=="node"', { 'include_dirs': [ + '<(libchromiumcontent_src_dir)', + '<(libchromiumcontent_src_dir)/third_party/icu/source/common', + '<(libchromiumcontent_src_dir)/third_party/icu/source/i18n', '<(libchromiumcontent_src_dir)/v8', '<(libchromiumcontent_src_dir)/v8/include', ], @@ -155,59 +158,6 @@ '-ldbghelp.lib', '-lshlwapi.lib', ], - # Force referencing symbols of ICU and v8_inspector to make sure - # they are included in the final DLL. - 'conditions': [ - ['libchromiumcontent_component==0', { - 'variables': { - 'conditions': [ - ['target_arch=="ia32"', { - 'reference_symbols': [ - # ICU symbols: - '_u_errorName_58', - '_ubidi_setPara_58', - '_ucsdet_getName_58', - '_uidna_openUTS46_58', - '_ulocdata_close_58', - '_unorm_normalize_58', - '_uregex_matches_58', - '_uscript_getCode_58', - '_uspoof_open_58', - '_usearch_setPattern_58', - '?createInstance@Transliterator@icu_58@@SAPAV12@ABVUnicodeString@2@W4UTransDirection@@AAW4UErrorCode@@@Z', - '??0MeasureFormat@icu_58@@QAE@ABVLocale@1@W4UMeasureFormatWidth@@AAW4UErrorCode@@@Z', - ], - }, { - 'reference_symbols': [ - # ICU symbols: - 'u_errorName_58', - 'ubidi_setPara_58', - 'ucsdet_getName_58', - 'uidna_openUTS46_58', - 'ulocdata_close_58', - 'unorm_normalize_58', - 'uregex_matches_58', - 'uspoof_open_58', - 'usearch_setPattern_58', - '?createInstance@Transliterator@icu_58@@SAPEAV12@AEBVUnicodeString@2@W4UTransDirection@@AEAW4UErrorCode@@@Z', - '??0MeasureFormat@icu_58@@QEAA@AEBVLocale@1@W4UMeasureFormatWidth@@AEAW4UErrorCode@@@Z', - # v8_inspector symbols: - '?DOM@ReasonEnum@Paused@API@Debugger@protocol@v8_inspector@@3PEBDEB', - '?canDispatchMethod@V8InspectorSession@v8_inspector@@SA_NAEBVStringView@2@@Z', - ], - }], - ], - }, - 'msvs_settings': { - 'VCLinkerTool': { - # There is nothing like "whole-archive" on Windows, so we - # have to manually force some objets files to be included - # by referencing them. - 'ForceSymbolReferences': [ '<@(reference_symbols)' ], # '/INCLUDE' - }, - }, - }], - ], }], ['OS=="linux" and libchromiumcontent_component==0', { # Prevent the linker from stripping symbols. From 42f65c52fb7cc72e734c62daeb88b276451d52e4 Mon Sep 17 00:00:00 2001 From: Tan Wang Leng Date: Tue, 31 Jan 2017 10:43:12 +0800 Subject: [PATCH 006/925] :bug: Fix webpreferences not accepting numeric options The webpreferences attribute values are parsed as strings instead of numbers. Therefore, a conversion is required. --- atom/browser/web_contents_preferences.cc | 26 +++++++++++++++++++++--- atom/browser/web_contents_preferences.h | 4 ++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/atom/browser/web_contents_preferences.cc b/atom/browser/web_contents_preferences.cc index 5afa3070f0..96722a9a9b 100644 --- a/atom/browser/web_contents_preferences.cc +++ b/atom/browser/web_contents_preferences.cc @@ -5,6 +5,7 @@ #include "atom/browser/web_contents_preferences.h" #include +#include #include #include @@ -215,6 +216,25 @@ bool WebContentsPreferences::IsSandboxed(content::WebContents* web_contents) { return sandboxed; } +// static +bool WebContentsPreferences::ConvertValueToIntegerFromString( + WebContentsPreferences* pref, std::string attributeName, int* intValue) { + + // if it is already an integer, no conversion needed + if (pref->web_preferences_.GetInteger(attributeName, intValue)) { + return true; + } + + std::string stringValue; + + if (pref->web_preferences_.GetString(attributeName, &stringValue)) { + std::stringstream(stringValue) >> *intValue; + return true; + } + + return false; +} + // static void WebContentsPreferences::OverrideWebkitPrefs( content::WebContents* web_contents, content::WebPreferences* prefs) { @@ -254,11 +274,11 @@ void WebContentsPreferences::OverrideWebkitPrefs( prefs->fantasy_font_family_map[content::kCommonScript] = font; } int size; - if (self->web_preferences_.GetInteger("defaultFontSize", &size)) + if (ConvertValueToIntegerFromString(self, "defaultFontSize", &size)) prefs->default_font_size = size; - if (self->web_preferences_.GetInteger("defaultMonospaceFontSize", &size)) + if (ConvertValueToIntegerFromString(self, "defaultMonospaceFontSize", &size)) prefs->default_fixed_font_size = size; - if (self->web_preferences_.GetInteger("minimumFontSize", &size)) + if (ConvertValueToIntegerFromString(self, "minimumFontSize", &size)) prefs->minimum_font_size = size; std::string encoding; if (self->web_preferences_.GetString("defaultEncoding", &encoding)) diff --git a/atom/browser/web_contents_preferences.h b/atom/browser/web_contents_preferences.h index 3a04ea9eea..7fa1944408 100644 --- a/atom/browser/web_contents_preferences.h +++ b/atom/browser/web_contents_preferences.h @@ -5,6 +5,7 @@ #ifndef ATOM_BROWSER_WEB_CONTENTS_PREFERENCES_H_ #define ATOM_BROWSER_WEB_CONTENTS_PREFERENCES_H_ +#include #include #include "base/values.h" @@ -38,6 +39,9 @@ class WebContentsPreferences static bool IsSandboxed(content::WebContents* web_contents); + static bool ConvertValueToIntegerFromString( + WebContentsPreferences* pref, std::string attributeName, int* intValue); + // Modify the WebPreferences according to |web_contents|'s preferences. static void OverrideWebkitPrefs( content::WebContents* web_contents, content::WebPreferences* prefs); From 7ec88d16a6f2fedd707b01340ee508bd36af9ede Mon Sep 17 00:00:00 2001 From: Tan Wang Leng Date: Sat, 4 Feb 2017 09:42:52 +0800 Subject: [PATCH 007/925] Use base::StringToInt() instead of std::stringstream --- atom/browser/web_contents_preferences.cc | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/atom/browser/web_contents_preferences.cc b/atom/browser/web_contents_preferences.cc index 96722a9a9b..cfb33de14e 100644 --- a/atom/browser/web_contents_preferences.cc +++ b/atom/browser/web_contents_preferences.cc @@ -5,7 +5,6 @@ #include "atom/browser/web_contents_preferences.h" #include -#include #include #include @@ -225,11 +224,10 @@ bool WebContentsPreferences::ConvertValueToIntegerFromString( return true; } - std::string stringValue; + base::string16 stringValue; if (pref->web_preferences_.GetString(attributeName, &stringValue)) { - std::stringstream(stringValue) >> *intValue; - return true; + return base::StringToInt(stringValue, intValue); } return false; From 4de637779bed9d0ed6c35c4c87c8f2d2567a009d Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 7 Feb 2017 09:13:42 -0800 Subject: [PATCH 008/925] Make int converter helper an instance method --- atom/browser/web_contents_preferences.cc | 37 ++++++++++-------------- atom/browser/web_contents_preferences.h | 6 ++-- 2 files changed, 19 insertions(+), 24 deletions(-) diff --git a/atom/browser/web_contents_preferences.cc b/atom/browser/web_contents_preferences.cc index cfb33de14e..1182e2859f 100644 --- a/atom/browser/web_contents_preferences.cc +++ b/atom/browser/web_contents_preferences.cc @@ -215,24 +215,6 @@ bool WebContentsPreferences::IsSandboxed(content::WebContents* web_contents) { return sandboxed; } -// static -bool WebContentsPreferences::ConvertValueToIntegerFromString( - WebContentsPreferences* pref, std::string attributeName, int* intValue) { - - // if it is already an integer, no conversion needed - if (pref->web_preferences_.GetInteger(attributeName, intValue)) { - return true; - } - - base::string16 stringValue; - - if (pref->web_preferences_.GetString(attributeName, &stringValue)) { - return base::StringToInt(stringValue, intValue); - } - - return false; -} - // static void WebContentsPreferences::OverrideWebkitPrefs( content::WebContents* web_contents, content::WebPreferences* prefs) { @@ -272,15 +254,28 @@ void WebContentsPreferences::OverrideWebkitPrefs( prefs->fantasy_font_family_map[content::kCommonScript] = font; } int size; - if (ConvertValueToIntegerFromString(self, "defaultFontSize", &size)) + if (self->GetInteger("defaultFontSize", &size)) prefs->default_font_size = size; - if (ConvertValueToIntegerFromString(self, "defaultMonospaceFontSize", &size)) + if (self->GetInteger("defaultMonospaceFontSize", &size)) prefs->default_fixed_font_size = size; - if (ConvertValueToIntegerFromString(self, "minimumFontSize", &size)) + if (self->GetInteger("minimumFontSize", &size)) prefs->minimum_font_size = size; std::string encoding; if (self->web_preferences_.GetString("defaultEncoding", &encoding)) prefs->default_encoding = encoding; } +bool WebContentsPreferences::GetInteger(const std::string& attributeName, + int* intValue) { + // if it is already an integer, no conversion needed + if (web_preferences_.GetInteger(attributeName, intValue)) + return true; + + base::string16 stringValue; + if (web_preferences_.GetString(attributeName, &stringValue)) + return base::StringToInt(stringValue, intValue); + + return false; +} + } // namespace atom diff --git a/atom/browser/web_contents_preferences.h b/atom/browser/web_contents_preferences.h index 7fa1944408..f6e44c51b1 100644 --- a/atom/browser/web_contents_preferences.h +++ b/atom/browser/web_contents_preferences.h @@ -39,9 +39,6 @@ class WebContentsPreferences static bool IsSandboxed(content::WebContents* web_contents); - static bool ConvertValueToIntegerFromString( - WebContentsPreferences* pref, std::string attributeName, int* intValue); - // Modify the WebPreferences according to |web_contents|'s preferences. static void OverrideWebkitPrefs( content::WebContents* web_contents, content::WebPreferences* prefs); @@ -64,6 +61,9 @@ class WebContentsPreferences content::WebContents* web_contents_; base::DictionaryValue web_preferences_; + // Get preferences value as integer possibly coercing it from a string + bool GetInteger(const std::string& attributeName, int* intValue); + DISALLOW_COPY_AND_ASSIGN(WebContentsPreferences); }; From 0f98c9d3fbdc523b4056f444bf4e5f6ce6b722a7 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 7 Feb 2017 09:32:40 -0800 Subject: [PATCH 009/925] Use at least OS version checks --- atom/browser/native_window_mac.mm | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index e3cf300bba..b463d2067c 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -228,7 +228,7 @@ bool ScopedDisableResize::disable_resize_ = false; - (void)windowWillEnterFullScreen:(NSNotification*)notification { // Hide the native toolbar before entering fullscreen, so there is no visual // artifacts. - if (base::mac::IsOS10_10() && + if (base::mac::IsAtLeastOS10_10() && shell_->title_bar_style() == atom::NativeWindowMac::HIDDEN_INSET) { NSWindow* window = shell_->GetNativeWindow(); [window setToolbar:nil]; @@ -243,7 +243,7 @@ bool ScopedDisableResize::disable_resize_ = false; // have to set one, because title bar is visible here. NSWindow* window = shell_->GetNativeWindow(); if ((shell_->transparent() || !shell_->has_frame()) && - base::mac::IsOS10_10() && + base::mac::IsAtLeastOS10_10() && // FIXME(zcbenz): Showing titlebar for hiddenInset window is weird under // fullscreen mode. shell_->title_bar_style() != atom::NativeWindowMac::HIDDEN_INSET) { @@ -252,7 +252,7 @@ bool ScopedDisableResize::disable_resize_ = false; // Restore the native toolbar immediately after entering fullscreen, if we do // this before leaving fullscreen, traffic light buttons will be jumping. - if (base::mac::IsOS10_10() && + if (base::mac::IsAtLeastOS10_10() && shell_->title_bar_style() == atom::NativeWindowMac::HIDDEN_INSET) { base::scoped_nsobject toolbar( [[NSToolbar alloc] initWithIdentifier:@"titlebarStylingToolbar"]); @@ -269,13 +269,13 @@ bool ScopedDisableResize::disable_resize_ = false; // Restore the titlebar visibility. NSWindow* window = shell_->GetNativeWindow(); if ((shell_->transparent() || !shell_->has_frame()) && - base::mac::IsOS10_10() && + base::mac::IsAtLeastOS10_10() && shell_->title_bar_style() != atom::NativeWindowMac::HIDDEN_INSET) { [window setTitleVisibility:NSWindowTitleHidden]; } // Turn off the style for toolbar. - if (base::mac::IsOS10_10() && + if (base::mac::IsAtLeastOS10_10() && shell_->title_bar_style() == atom::NativeWindowMac::HIDDEN_INSET) { shell_->SetStyleMask(false, NSFullSizeContentViewWindowMask); } @@ -712,7 +712,7 @@ NativeWindowMac::NativeWindowMac( [window_ setDisableKeyOrMainWindow:YES]; if (transparent() || !has_frame()) { - if (base::mac::IsOS10_10()) { + if (base::mac::IsAtLeastOS10_10()) { // Don't show title bar. [window_ setTitleVisibility:NSWindowTitleHidden]; } @@ -725,7 +725,7 @@ NativeWindowMac::NativeWindowMac( // Hide the title bar. if (title_bar_style_ == HIDDEN_INSET) { - if (base::mac::IsOS10_10()) { + if (base::mac::IsAtLeastOS10_10()) { [window_ setTitlebarAppearsTransparent:YES]; base::scoped_nsobject toolbar( [[NSToolbar alloc] initWithIdentifier:@"titlebarStylingToolbar"]); @@ -1061,7 +1061,7 @@ void NativeWindowMac::SetAlwaysOnTop(bool top, const std::string& level, int windowLevel = NSNormalWindowLevel; CGWindowLevel maxWindowLevel = CGWindowLevelForKey(kCGMaximumWindowLevelKey); CGWindowLevel minWindowLevel = CGWindowLevelForKey(kCGMinimumWindowLevelKey); - + if (top) { if (level == "floating") { windowLevel = NSFloatingWindowLevel; @@ -1104,7 +1104,7 @@ void NativeWindowMac::Center() { void NativeWindowMac::SetTitle(const std::string& title) { // For macOS <= 10.9, the setTitleVisibility API is not available, we have // to avoid calling setTitle for frameless window. - if (!base::mac::IsOS10_10() && (transparent() || !has_frame())) + if (!base::mac::IsAtLeastOS10_10() && (transparent() || !has_frame())) return; [window_ setTitle:base::SysUTF8ToNSString(title)]; @@ -1314,7 +1314,7 @@ void NativeWindowMac::SetVibrancy(const std::string& type) { vibrancyType = NSVisualEffectMaterialTitlebar; } - if (base::mac::IsOS10_11()) { + if (base::mac::IsAtLeastOS10_11()) { // TODO(kevinsawicki): Use NSVisualEffectMaterial* constants directly once // they are available in the minimum SDK version if (type == "selection") { From be5b90717961ba0bbd7dac0762b791a2b7c1b2af Mon Sep 17 00:00:00 2001 From: Yury Solovyov Date: Sat, 29 Oct 2016 14:02:33 +0300 Subject: [PATCH 010/925] WIP --- atom/common/api/atom_api_native_image.cc | 8 ++++++++ atom/common/api/atom_api_native_image.h | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/atom/common/api/atom_api_native_image.cc b/atom/common/api/atom_api_native_image.cc index 0c174941db..30d4b124a1 100644 --- a/atom/common/api/atom_api_native_image.cc +++ b/atom/common/api/atom_api_native_image.cc @@ -301,6 +301,12 @@ gfx::Size NativeImage::GetSize() { return image_.Size(); } +void NativeImage::CreateFromFileIcon(v8::Isolate* isolate, + const base::FilePath& path, + const IconLoadedCallback& callback) { + callback.Run(CreateEmpty(isolate)); +} + float NativeImage::GetAspectRatio() { gfx::Size size = GetSize(); if (size.IsEmpty()) @@ -514,6 +520,8 @@ void Initialize(v8::Local exports, v8::Local unused, mate::Dictionary dict(context->GetIsolate(), exports); dict.SetMethod("createEmpty", &atom::api::NativeImage::CreateEmpty); dict.SetMethod("createFromPath", &atom::api::NativeImage::CreateFromPath); + dict.SetMethod("createFromFileIcon", + &atom::api::NativeImage::CreateFromFileIcon); dict.SetMethod("createFromBuffer", &atom::api::NativeImage::CreateFromBuffer); dict.SetMethod("createFromDataURL", &atom::api::NativeImage::CreateFromDataURL); diff --git a/atom/common/api/atom_api_native_image.h b/atom/common/api/atom_api_native_image.h index ee1b5f5d4b..251776b893 100644 --- a/atom/common/api/atom_api_native_image.h +++ b/atom/common/api/atom_api_native_image.h @@ -38,6 +38,7 @@ namespace atom { namespace api { class NativeImage : public mate::Wrappable { +using IconLoadedCallback = base::Callback)>; public: static mate::Handle CreateEmpty(v8::Isolate* isolate); static mate::Handle Create( @@ -52,6 +53,9 @@ class NativeImage : public mate::Wrappable { mate::Arguments* args, v8::Local buffer); static mate::Handle CreateFromDataURL( v8::Isolate* isolate, const GURL& url); + static void CreateFromFileIcon(v8::Isolate* isolate, + const base::FilePath& path, + const IconLoadedCallback& callback); static void BuildPrototype(v8::Isolate* isolate, v8::Local prototype); From 1d24a3a1751ea3c5020a636e9c6020eb2376dabe Mon Sep 17 00:00:00 2001 From: Yury Solovyov Date: Sat, 29 Oct 2016 14:38:23 +0300 Subject: [PATCH 011/925] Add callback converters --- atom/common/api/atom_api_native_image.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/atom/common/api/atom_api_native_image.cc b/atom/common/api/atom_api_native_image.cc index 30d4b124a1..c9e11f7619 100644 --- a/atom/common/api/atom_api_native_image.cc +++ b/atom/common/api/atom_api_native_image.cc @@ -12,6 +12,7 @@ #include "atom/common/native_mate_converters/gfx_converter.h" #include "atom/common/native_mate_converters/gurl_converter.h" #include "atom/common/native_mate_converters/value_converter.h" +#include "atom/common/native_mate_converters/callback.h" #include "base/base64.h" #include "base/files/file_util.h" #include "base/strings/pattern.h" From 8e4ed664d9be988f73bce78ef36b87bb9f07e944 Mon Sep 17 00:00:00 2001 From: Yury Solovyov Date: Sat, 29 Oct 2016 15:35:50 +0300 Subject: [PATCH 012/925] Add icon fetching sources --- atom/common/api/atom_api_native_image.cc | 3 +- chromium_src/chrome/browser/icon_loader.cc | 49 ++++++ chromium_src/chrome/browser/icon_loader.h | 105 +++++++++++ chromium_src/chrome/browser/icon_manager.cc | 148 ++++++++++++++++ chromium_src/chrome/browser/icon_manager.h | 121 +++++++++++++ .../browser/ui/webui/fileicon_source.cc | 164 ++++++++++++++++++ .../chrome/browser/ui/webui/fileicon_source.h | 73 ++++++++ filenames.gypi | 6 + 8 files changed, 668 insertions(+), 1 deletion(-) create mode 100644 chromium_src/chrome/browser/icon_loader.cc create mode 100644 chromium_src/chrome/browser/icon_loader.h create mode 100644 chromium_src/chrome/browser/icon_manager.cc create mode 100644 chromium_src/chrome/browser/icon_manager.h create mode 100644 chromium_src/chrome/browser/ui/webui/fileicon_source.cc create mode 100644 chromium_src/chrome/browser/ui/webui/fileicon_source.h diff --git a/atom/common/api/atom_api_native_image.cc b/atom/common/api/atom_api_native_image.cc index c9e11f7619..6fd34153d3 100644 --- a/atom/common/api/atom_api_native_image.cc +++ b/atom/common/api/atom_api_native_image.cc @@ -8,15 +8,16 @@ #include #include "atom/common/asar/asar_util.h" +#include "atom/common/native_mate_converters/callback.h" #include "atom/common/native_mate_converters/file_path_converter.h" #include "atom/common/native_mate_converters/gfx_converter.h" #include "atom/common/native_mate_converters/gurl_converter.h" #include "atom/common/native_mate_converters/value_converter.h" -#include "atom/common/native_mate_converters/callback.h" #include "base/base64.h" #include "base/files/file_util.h" #include "base/strings/pattern.h" #include "base/strings/string_util.h" +#include "chrome/browser/ui/webui/fileicon_source.h" #include "native_mate/dictionary.h" #include "native_mate/object_template_builder.h" #include "net/base/data_url.h" diff --git a/chromium_src/chrome/browser/icon_loader.cc b/chromium_src/chrome/browser/icon_loader.cc new file mode 100644 index 0000000000..979ee5772c --- /dev/null +++ b/chromium_src/chrome/browser/icon_loader.cc @@ -0,0 +1,49 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/icon_loader.h" + +#include "base/bind.h" +#include "base/threading/thread_task_runner_handle.h" +#include "content/public/browser/browser_thread.h" + +using content::BrowserThread; + +IconLoader::IconLoader(const base::FilePath& file_path, + IconSize size, + Delegate* delegate) + : target_task_runner_(NULL), + file_path_(file_path), + icon_size_(size), + delegate_(delegate) {} + +IconLoader::~IconLoader() { +} + +void IconLoader::Start() { + target_task_runner_ = base::ThreadTaskRunnerHandle::Get(); + + BrowserThread::PostTaskAndReply(BrowserThread::FILE, FROM_HERE, + base::Bind(&IconLoader::ReadGroup, this), + base::Bind(&IconLoader::OnReadGroup, this)); +} + +void IconLoader::ReadGroup() { + group_ = ReadGroupIDFromFilepath(file_path_); +} + +void IconLoader::OnReadGroup() { + if (IsIconMutableFromFilepath(file_path_) || + !delegate_->OnGroupLoaded(this, group_)) { + BrowserThread::PostTask(ReadIconThreadID(), FROM_HERE, + base::Bind(&IconLoader::ReadIcon, this)); + } +} + +void IconLoader::NotifyDelegate() { + // If the delegate takes ownership of the Image, release it from the scoped + // pointer. + if (delegate_->OnImageLoaded(this, image_.get(), group_)) + ignore_result(image_.release()); // Can't ignore return value. +} diff --git a/chromium_src/chrome/browser/icon_loader.h b/chromium_src/chrome/browser/icon_loader.h new file mode 100644 index 0000000000..33301acd82 --- /dev/null +++ b/chromium_src/chrome/browser/icon_loader.h @@ -0,0 +1,105 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_ICON_LOADER_H_ +#define CHROME_BROWSER_ICON_LOADER_H_ + +#include +#include + +#include "base/files/file_path.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/single_thread_task_runner.h" +#include "build/build_config.h" +#include "content/public/browser/browser_thread.h" +#include "ui/gfx/image/image.h" + +#if defined(OS_WIN) +// On Windows, we group files by their extension, with several exceptions: +// .dll, .exe, .ico. See IconManager.h for explanation. +typedef std::wstring IconGroupID; +#elif defined(OS_POSIX) +// On POSIX, we group files by MIME type. +typedef std::string IconGroupID; +#endif + +//////////////////////////////////////////////////////////////////////////////// +// +// A facility to read a file containing an icon asynchronously in the IO +// thread. Returns the icon in the form of an ImageSkia. +// +//////////////////////////////////////////////////////////////////////////////// +class IconLoader : public base::RefCountedThreadSafe { + public: + enum IconSize { + SMALL = 0, // 16x16 + NORMAL, // 32x32 + LARGE, // Windows: 32x32, Linux: 48x48, Mac: Unsupported + ALL, // All sizes available + }; + + class Delegate { + public: + // Invoked when an icon group has been read, but before the icon data + // is read. If the icon is already cached, this method should call and + // return the results of OnImageLoaded with the cached image. + virtual bool OnGroupLoaded(IconLoader* source, + const IconGroupID& group) = 0; + // Invoked when an icon has been read. |source| is the IconLoader. If the + // icon has been successfully loaded, result is non-null. This method must + // return true if it is taking ownership of the returned image. + virtual bool OnImageLoaded(IconLoader* source, + gfx::Image* result, + const IconGroupID& group) = 0; + + protected: + virtual ~Delegate() {} + }; + + IconLoader(const base::FilePath& file_path, + IconSize size, + Delegate* delegate); + + // Start reading the icon on the file thread. + void Start(); + + private: + friend class base::RefCountedThreadSafe; + + virtual ~IconLoader(); + + // Get the identifying string for the given file. The implementation + // is in icon_loader_[platform].cc. + static IconGroupID ReadGroupIDFromFilepath(const base::FilePath& path); + + // Some icons (exe's on windows) can change as they're loaded. + static bool IsIconMutableFromFilepath(const base::FilePath& path); + + // The thread ReadIcon() should be called on. + static content::BrowserThread::ID ReadIconThreadID(); + + void ReadGroup(); + void OnReadGroup(); + void ReadIcon(); + + void NotifyDelegate(); + + // The task runner object of the thread in which we notify the delegate. + scoped_refptr target_task_runner_; + + base::FilePath file_path_; + + IconGroupID group_; + + IconSize icon_size_; + + std::unique_ptr image_; + + Delegate* delegate_; + + DISALLOW_COPY_AND_ASSIGN(IconLoader); +}; + +#endif // CHROME_BROWSER_ICON_LOADER_H_ diff --git a/chromium_src/chrome/browser/icon_manager.cc b/chromium_src/chrome/browser/icon_manager.cc new file mode 100644 index 0000000000..6596ab3112 --- /dev/null +++ b/chromium_src/chrome/browser/icon_manager.cc @@ -0,0 +1,148 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/icon_manager.h" + +#include +#include + +#include "base/bind.h" +#include "base/stl_util.h" +#include "base/task_runner.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/skia/include/core/SkCanvas.h" + +namespace { + +void RunCallbackIfNotCanceled( + const base::CancelableTaskTracker::IsCanceledCallback& is_canceled, + const IconManager::IconRequestCallback& callback, + gfx::Image* image) { + if (is_canceled.Run()) + return; + callback.Run(image); +} + +} // namespace + +struct IconManager::ClientRequest { + IconRequestCallback callback; + base::FilePath file_path; + IconLoader::IconSize size; +}; + +IconManager::IconManager() { +} + +IconManager::~IconManager() { + base::STLDeleteValues(&icon_cache_); +} + +gfx::Image* IconManager::LookupIconFromFilepath(const base::FilePath& file_name, + IconLoader::IconSize size) { + GroupMap::iterator it = group_cache_.find(file_name); + if (it != group_cache_.end()) + return LookupIconFromGroup(it->second, size); + + return NULL; +} + +gfx::Image* IconManager::LookupIconFromGroup(const IconGroupID& group, + IconLoader::IconSize size) { + IconMap::iterator it = icon_cache_.find(CacheKey(group, size)); + if (it != icon_cache_.end()) + return it->second; + + return NULL; +} + +base::CancelableTaskTracker::TaskId IconManager::LoadIcon( + const base::FilePath& file_name, + IconLoader::IconSize size, + const IconRequestCallback& callback, + base::CancelableTaskTracker* tracker) { + IconLoader* loader = new IconLoader(file_name, size, this); + loader->AddRef(); + loader->Start(); + + base::CancelableTaskTracker::IsCanceledCallback is_canceled; + base::CancelableTaskTracker::TaskId id = + tracker->NewTrackedTaskId(&is_canceled); + IconRequestCallback callback_runner = base::Bind( + &RunCallbackIfNotCanceled, is_canceled, callback); + + ClientRequest client_request = { callback_runner, file_name, size }; + requests_[loader] = client_request; + return id; +} + +// IconLoader::Delegate implementation ----------------------------------------- + +bool IconManager::OnGroupLoaded(IconLoader* loader, + const IconGroupID& group) { + ClientRequests::iterator rit = requests_.find(loader); + if (rit == requests_.end()) { + NOTREACHED(); + return false; + } + + gfx::Image* result = LookupIconFromGroup(group, rit->second.size); + if (!result) { + return false; + } + + return OnImageLoaded(loader, result, group); +} + +bool IconManager::OnImageLoaded( + IconLoader* loader, gfx::Image* result, const IconGroupID& group) { + ClientRequests::iterator rit = requests_.find(loader); + + // Balances the AddRef() in LoadIcon(). + loader->Release(); + + // Look up our client state. + if (rit == requests_.end()) { + NOTREACHED(); + return false; // Return false to indicate result should be deleted. + } + + const ClientRequest& client_request = rit->second; + + // Cache the bitmap. Watch out: |result| may be NULL to indicate a current + // failure. We assume that if we have an entry in |icon_cache_| + // it must not be NULL. + CacheKey key(group, client_request.size); + IconMap::iterator it = icon_cache_.find(key); + if (it != icon_cache_.end()) { + if (!result) { + delete it->second; + icon_cache_.erase(it); + } else if (result != it->second) { + it->second->SwapRepresentations(result); + delete result; + result = it->second; + } + } else if (result) { + icon_cache_[key] = result; + } + + group_cache_[client_request.file_path] = group; + + // Inform our client that the request has completed. + client_request.callback.Run(result); + requests_.erase(rit); + + return true; // Indicates we took ownership of result. +} + +IconManager::CacheKey::CacheKey(const IconGroupID& group, + IconLoader::IconSize size) + : group(group), + size(size) { +} + +bool IconManager::CacheKey::operator<(const CacheKey &other) const { + return std::tie(group, size) < std::tie(other.group, other.size); +} diff --git a/chromium_src/chrome/browser/icon_manager.h b/chromium_src/chrome/browser/icon_manager.h new file mode 100644 index 0000000000..0b5ec07d41 --- /dev/null +++ b/chromium_src/chrome/browser/icon_manager.h @@ -0,0 +1,121 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Class for finding and caching Windows explorer icons. The IconManager +// lives on the UI thread but performs icon extraction work on the file thread +// to avoid blocking the UI thread with potentially expensive COM and disk +// operations. +// +// Terminology +// +// Windows files have icons associated with them that can be of two types: +// 1. "Per class": the icon used for this file is used for all files with the +// same file extension or class. Examples are PDF or MP3 files, which use +// the same icon for all files of that type. +// 2. "Per instance": the icon used for this file is embedded in the file +// itself and is unique. Executable files are typically "per instance". +// +// Files that end in the following extensions are considered "per instance": +// .exe +// .dll +// .ico +// The IconManager will do explicit icon loads on the full path of these files +// and cache the results per file. All other file types will be looked up by +// file extension and the results will be cached per extension. That way, all +// .mp3 files will share one icon, but all .exe files will have their own icon. +// +// POSIX files don't have associated icons. We query the OS by the file's +// mime type. +// +// The IconManager can be queried in two ways: +// 1. A quick, synchronous check of its caches which does not touch the disk: +// IconManager::LookupIcon() +// 2. An asynchronous icon load from a file on the file thread: +// IconManager::LoadIcon() +// +// When using the second (asychronous) method, callers must supply a callback +// which will be run once the icon has been extracted. The icon manager will +// cache the results of the icon extraction so that subsequent lookups will be +// fast. +// +// Icon bitmaps returned should be treated as const since they may be referenced +// by other clients. Make a copy of the icon if you need to modify it. + +#ifndef CHROME_BROWSER_ICON_MANAGER_H_ +#define CHROME_BROWSER_ICON_MANAGER_H_ + +#include + +#include "base/files/file_path.h" +#include "base/macros.h" +#include "base/task/cancelable_task_tracker.h" +#include "chrome/browser/icon_loader.h" +#include "ui/gfx/image/image.h" + +class IconManager : public IconLoader::Delegate { + public: + IconManager(); + ~IconManager() override; + + // Synchronous call to examine the internal caches for the icon. Returns the + // icon if we have already loaded it, NULL if we don't have it and must load + // it via 'LoadIcon'. The returned bitmap is owned by the IconManager and must + // not be free'd by the caller. If the caller needs to modify the icon, it + // must make a copy and modify the copy. + gfx::Image* LookupIconFromFilepath(const base::FilePath& file_name, + IconLoader::IconSize size); + + typedef base::Callback IconRequestCallback; + + // Asynchronous call to lookup and return the icon associated with file. The + // work is done on the file thread, with the callbacks running on the thread + // this function is called. + // + // Note: + // 1. This does *not* check the cache. + // 2. The returned bitmap pointer is *not* owned by callback. So callback + // should never keep it or delete it. + // 3. The gfx::Image pointer passed to the callback may be NULL if decoding + // failed. + base::CancelableTaskTracker::TaskId LoadIcon( + const base::FilePath& file_name, + IconLoader::IconSize size, + const IconRequestCallback& callback, + base::CancelableTaskTracker* tracker); + + // IconLoader::Delegate interface. + bool OnGroupLoaded(IconLoader* loader, const IconGroupID& group) override; + bool OnImageLoaded(IconLoader* loader, + gfx::Image* result, + const IconGroupID& group) override; + + private: + struct CacheKey { + CacheKey(const IconGroupID& group, IconLoader::IconSize size); + + // Used as a key in the map below, so we need this comparator. + bool operator<(const CacheKey &other) const; + + IconGroupID group; + IconLoader::IconSize size; + }; + + gfx::Image* LookupIconFromGroup(const IconGroupID& group, + IconLoader::IconSize size); + + typedef std::map IconMap; + IconMap icon_cache_; + + typedef std::map GroupMap; + GroupMap group_cache_; + + // Asynchronous requests that have not yet been completed. + struct ClientRequest; + typedef std::map ClientRequests; + ClientRequests requests_; + + DISALLOW_COPY_AND_ASSIGN(IconManager); +}; + +#endif // CHROME_BROWSER_ICON_MANAGER_H_ diff --git a/chromium_src/chrome/browser/ui/webui/fileicon_source.cc b/chromium_src/chrome/browser/ui/webui/fileicon_source.cc new file mode 100644 index 0000000000..8659346b58 --- /dev/null +++ b/chromium_src/chrome/browser/ui/webui/fileicon_source.cc @@ -0,0 +1,164 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/ui/webui/fileicon_source.h" + +#include "base/bind.h" +#include "base/callback.h" +#include "base/files/file_path.h" +#include "base/memory/ref_counted_memory.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/string_split.h" +#include "base/strings/utf_string_conversions.h" +#include "chrome/browser/browser_process.h" +#include "net/base/escape.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/base/webui/web_ui_util.h" +#include "ui/gfx/codec/png_codec.h" +#include "ui/gfx/image/image.h" +#include "ui/gfx/image/image_skia.h" +#include "url/gurl.h" + +namespace { + +typedef std::map QueryIconSizeMap; + +// The path used in internal URLs to file icon data. +const char kFileIconPath[] = "fileicon"; + +// URL parameter specifying icon size. +const char kIconSize[] = "iconsize"; + +// URL parameter specifying scale factor. +const char kScaleFactor[] = "scale"; + +// Assuming the url is of the form '/path?query', convert the path portion into +// a FilePath and return the resulting |file_path| and |query|. The path +// portion may have been encoded using encodeURIComponent(). +void GetFilePathAndQuery(const std::string& url, + base::FilePath* file_path, + std::string* query) { + // We receive the url with chrome://fileicon/ stripped but GURL expects it. + const GURL gurl("chrome://fileicon/" + url); + std::string path = net::UnescapeURLComponent( + gurl.path().substr(1), + net::UnescapeRule::URL_SPECIAL_CHARS_EXCEPT_PATH_SEPARATORS | + net::UnescapeRule::PATH_SEPARATORS | net::UnescapeRule::SPACES); + + *file_path = base::FilePath::FromUTF8Unsafe(path); + *file_path = file_path->NormalizePathSeparators(); + query->assign(gurl.query()); +} + +IconLoader::IconSize SizeStringToIconSize(const std::string& size_string) { + if (size_string == "small") return IconLoader::SMALL; + if (size_string == "large") return IconLoader::LARGE; + // We default to NORMAL if we don't recognize the size_string. Including + // size_string=="normal". + return IconLoader::NORMAL; +} + +// Simple parser for data on the query. +void ParseQueryParams(const std::string& query, + float* scale_factor, + IconLoader::IconSize* icon_size) { + base::StringPairs parameters; + if (icon_size) + *icon_size = IconLoader::NORMAL; + if (scale_factor) + *scale_factor = 1.0f; + base::SplitStringIntoKeyValuePairs(query, '=', '&', ¶meters); + for (base::StringPairs::const_iterator iter = parameters.begin(); + iter != parameters.end(); ++iter) { + if (icon_size && iter->first == kIconSize) + *icon_size = SizeStringToIconSize(iter->second); + else if (scale_factor && iter->first == kScaleFactor) + webui::ParseScaleFactor(iter->second, scale_factor); + } +} + +} // namespace + +FileIconSource::IconRequestDetails::IconRequestDetails() : scale_factor(1.0f) { +} + +FileIconSource::IconRequestDetails::IconRequestDetails( + const IconRequestDetails& other) = default; + +FileIconSource::IconRequestDetails::~IconRequestDetails() { +} + +FileIconSource::FileIconSource() {} + +FileIconSource::~FileIconSource() {} + +void FileIconSource::FetchFileIcon( + const base::FilePath& path, + float scale_factor, + IconLoader::IconSize icon_size, + const content::URLDataSource::GotDataCallback& callback) { + IconManager* im = g_browser_process->icon_manager(); + gfx::Image* icon = im->LookupIconFromFilepath(path, icon_size); + + if (icon) { + scoped_refptr icon_data(new base::RefCountedBytes); + gfx::PNGCodec::EncodeBGRASkBitmap( + icon->ToImageSkia()->GetRepresentation(scale_factor).sk_bitmap(), + false, + &icon_data->data()); + + callback.Run(icon_data.get()); + } else { + // Attach the ChromeURLDataManager request ID to the history request. + IconRequestDetails details; + details.callback = callback; + details.scale_factor = scale_factor; + + // Icon was not in cache, go fetch it slowly. + im->LoadIcon(path, + icon_size, + base::Bind(&FileIconSource::OnFileIconDataAvailable, + base::Unretained(this), details), + &cancelable_task_tracker_); + } +} + +std::string FileIconSource::GetSource() const { + return kFileIconPath; +} + +// void FileIconSource::StartDataRequest( +// const std::string& url_path, +// const content::ResourceRequestInfo::WebContentsGetter& wc_getter, +// const content::URLDataSource::GotDataCallback& callback) { +// std::string query; +// base::FilePath file_path; +// IconLoader::IconSize icon_size; +// float scale_factor = 1.0f; +// GetFilePathAndQuery(url_path, &file_path, &query); +// ParseQueryParams(query, &scale_factor, &icon_size); +// FetchFileIcon(file_path, scale_factor, icon_size, callback); +// } + +std::string FileIconSource::GetMimeType(const std::string&) const { + // Rely on image decoder inferring the correct type. + return std::string(); +} + +void FileIconSource::OnFileIconDataAvailable(const IconRequestDetails& details, + gfx::Image* icon) { + if (icon) { + scoped_refptr icon_data(new base::RefCountedBytes); + gfx::PNGCodec::EncodeBGRASkBitmap( + icon->ToImageSkia()->GetRepresentation( + details.scale_factor).sk_bitmap(), + false, + &icon_data->data()); + + details.callback.Run(icon_data.get()); + } else { + // TODO(glen): send a dummy icon. + details.callback.Run(NULL); + } +} diff --git a/chromium_src/chrome/browser/ui/webui/fileicon_source.h b/chromium_src/chrome/browser/ui/webui/fileicon_source.h new file mode 100644 index 0000000000..7fd6671b60 --- /dev/null +++ b/chromium_src/chrome/browser/ui/webui/fileicon_source.h @@ -0,0 +1,73 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_UI_WEBUI_FILEICON_SOURCE_H_ +#define CHROME_BROWSER_UI_WEBUI_FILEICON_SOURCE_H_ + +#include + +#include "base/files/file_path.h" +#include "base/macros.h" +#include "base/task/cancelable_task_tracker.h" +#include "chrome/browser/icon_manager.h" +#include "content/public/browser/url_data_source.h" + +namespace gfx { +class Image; +} + +namespace content { +class ResourceRequestInfo; +} + +// FileIconSource is the gateway between network-level chrome: +// requests for favicons and the history backend that serves these. +class FileIconSource : public content::URLDataSource { + public: + FileIconSource(); + + // content::URLDataSource implementation. + std::string GetSource() const override; + // void StartDataRequest( + // const std::string& path, + // const content::ResourceRequestInfo::WebContentsGetter& wc_getter, + // const content::URLDataSource::GotDataCallback& callback) override; + std::string GetMimeType(const std::string&) const override; + + protected: + ~FileIconSource() override; + + // Once the |path| and |icon_size| has been determined from the request, this + // function is called to perform the actual fetch. Declared as virtual for + // testing. + virtual void FetchFileIcon( + const base::FilePath& path, + float scale_factor, + IconLoader::IconSize icon_size, + const content::URLDataSource::GotDataCallback& callback); + + private: + // Contains the necessary information for completing an icon fetch request. + struct IconRequestDetails { + IconRequestDetails(); + IconRequestDetails(const IconRequestDetails& other); + ~IconRequestDetails(); + + // The callback to run with the response. + content::URLDataSource::GotDataCallback callback; + + // The requested scale factor to respond with. + float scale_factor; + }; + + // Called when favicon data is available from the history backend. + void OnFileIconDataAvailable(const IconRequestDetails& details, + gfx::Image* icon); + + // Tracks tasks requesting file icons. + base::CancelableTaskTracker cancelable_task_tracker_; + + DISALLOW_COPY_AND_ASSIGN(FileIconSource); +}; +#endif // CHROME_BROWSER_UI_WEBUI_FILEICON_SOURCE_H_ diff --git a/filenames.gypi b/filenames.gypi index 636cbb6063..65e74b5ee6 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -475,6 +475,10 @@ 'chromium_src/chrome/browser/chrome_process_finder_win.cc', 'chromium_src/chrome/browser/chrome_process_finder_win.h', 'chromium_src/chrome/browser/chrome_notification_types.h', + 'chromium_src/chrome/browser/icon_loader.cc', + 'chromium_src/chrome/browser/icon_loader.h', + 'chromium_src/chrome/browser/icon_manager.cc', + 'chromium_src/chrome/browser/icon_manager.h', 'chromium_src/chrome/browser/extensions/global_shortcut_listener.cc', 'chromium_src/chrome/browser/extensions/global_shortcut_listener.h', 'chromium_src/chrome/browser/extensions/global_shortcut_listener_mac.mm', @@ -543,6 +547,8 @@ 'chromium_src/chrome/browser/ui/views/color_chooser_aura.h', 'chromium_src/chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.cc', 'chromium_src/chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.h', + 'chromium_src/chrome/browser/ui/webui/fileicon_source.cc', + 'chromium_src/chrome/browser/ui/webui/fileicon_source.h', 'chromium_src/chrome/common/chrome_constants.cc', 'chromium_src/chrome/common/chrome_constants.h', 'chromium_src/chrome/common/chrome_paths.cc', From d118fed5c2de11c185dcff122f6e00c5111bc8c9 Mon Sep 17 00:00:00 2001 From: Yury Solovyov Date: Sat, 29 Oct 2016 20:42:27 +0300 Subject: [PATCH 013/925] Try my own class --- atom/common/api/atom_api_native_image.cc | 13 +- atom/common/api/atom_api_native_image.h | 5 +- atom/common/fileicon_fetcher.cc | 76 ++++++++ atom/common/fileicon_fetcher.h | 48 +++++ .../browser/ui/webui/fileicon_source.cc | 164 ------------------ .../chrome/browser/ui/webui/fileicon_source.h | 73 -------- filenames.gypi | 4 +- 7 files changed, 141 insertions(+), 242 deletions(-) create mode 100644 atom/common/fileicon_fetcher.cc create mode 100644 atom/common/fileicon_fetcher.h delete mode 100644 chromium_src/chrome/browser/ui/webui/fileicon_source.cc delete mode 100644 chromium_src/chrome/browser/ui/webui/fileicon_source.h diff --git a/atom/common/api/atom_api_native_image.cc b/atom/common/api/atom_api_native_image.cc index 6fd34153d3..3f6f830e14 100644 --- a/atom/common/api/atom_api_native_image.cc +++ b/atom/common/api/atom_api_native_image.cc @@ -17,7 +17,7 @@ #include "base/files/file_util.h" #include "base/strings/pattern.h" #include "base/strings/string_util.h" -#include "chrome/browser/ui/webui/fileicon_source.h" +#include "atom/common/fileicon_fetcher.h" #include "native_mate/dictionary.h" #include "native_mate/object_template_builder.h" #include "net/base/data_url.h" @@ -306,7 +306,16 @@ gfx::Size NativeImage::GetSize() { void NativeImage::CreateFromFileIcon(v8::Isolate* isolate, const base::FilePath& path, const IconLoadedCallback& callback) { - callback.Run(CreateEmpty(isolate)); + IconLoader::IconSize icon_size = IconLoader::IconSize::NORMAL; + float scale_factor = 1.0f; + auto onready = base::Bind(&NativeImage::OnIconLoaded, isolate, callback); + FileIconFetcher::FetchFileIcon(path, scale_factor, icon_size, onready); +} + +void NativeImage::OnIconLoaded(v8::Isolate* isolate, + const IconLoadedCallback& callback, + gfx::Image& image) { + callback.Run(Create(isolate, image)); } float NativeImage::GetAspectRatio() { diff --git a/atom/common/api/atom_api_native_image.h b/atom/common/api/atom_api_native_image.h index 251776b893..d0474835eb 100644 --- a/atom/common/api/atom_api_native_image.h +++ b/atom/common/api/atom_api_native_image.h @@ -38,7 +38,7 @@ namespace atom { namespace api { class NativeImage : public mate::Wrappable { -using IconLoadedCallback = base::Callback)>; + using IconLoadedCallback = base::Callback)>; public: static mate::Handle CreateEmpty(v8::Isolate* isolate); static mate::Handle Create( @@ -90,6 +90,9 @@ using IconLoadedCallback = base::Callback)>; gfx::Size GetSize(); float GetAspectRatio(); + void OnIconLoaded(v8::Isolate* isolate, + const IconLoadedCallback& callback, + gfx::Image& image); // Mark the image as template image. void SetTemplateImage(bool setAsTemplate); // Determine if the image is a template image. diff --git a/atom/common/fileicon_fetcher.cc b/atom/common/fileicon_fetcher.cc new file mode 100644 index 0000000000..43a864a086 --- /dev/null +++ b/atom/common/fileicon_fetcher.cc @@ -0,0 +1,76 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "chrome/browser/ui/webui/fileicon_source.h" + +#include "base/bind.h" +#include "base/callback.h" +#include "base/files/file_path.h" +#include "base/memory/ref_counted_memory.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/string_split.h" +#include "base/strings/utf_string_conversions.h" +#include "chrome/browser/browser_process.h" +#include "net/base/escape.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/base/webui/web_ui_util.h" +#include "ui/gfx/codec/png_codec.h" +#include "ui/gfx/image/image.h" +#include "ui/gfx/image/image_skia.h" +#include "url/gurl.h" + +void FileIconFetcher::FetchFileIcon(const base::FilePath& path, + float scale_factor, + IconLoader::IconSize icon_size, + const IconFetchedCallback& callback) { + IconManager* im = g_browser_process->icon_manager(); + gfx::Image* icon = im->LookupIconFromFilepath(path, icon_size); + + if (icon) { + scoped_refptr icon_data(new base::RefCountedBytes); + gfx::PNGCodec::EncodeBGRASkBitmap( + icon->ToImageSkia()->GetRepresentation(scale_factor).sk_bitmap(), + false, + &icon_data->data()); + + callback.Run(icon_data.get()); + } else { + // Attach the ChromeURLDataManager request ID to the history request. + IconRequestDetails details; + details.callback = callback; + details.scale_factor = scale_factor; + + // Icon was not in cache, go fetch it slowly. + im->LoadIcon(path, + icon_size, + base::Bind(&FileIconFetcher::OnFileIconDataAvailable, details), + &cancelable_task_tracker_); + } +} + +void FileIconFetcher::OnFileIconDataAvailable(const IconRequestDetails& details, + gfx::Image* icon) { + if (icon) { + scoped_refptr icon_data(new base::RefCountedBytes); + gfx::PNGCodec::EncodeBGRASkBitmap( + icon->ToImageSkia()->GetRepresentation( + details.scale_factor).sk_bitmap(), + false, + &icon_data->data()); + + details.callback.Run(icon_data.get()); + } else { + details.callback.Run(NULL); + } +} + + +FileIconFetcher::IconRequestDetails::IconRequestDetails() : scale_factor(1.0f) { +} + +FileIconFetcher::IconRequestDetails::IconRequestDetails( + const IconRequestDetails& other) = default; + +FileIconFetcher::IconRequestDetails::~IconRequestDetails() { +} diff --git a/atom/common/fileicon_fetcher.h b/atom/common/fileicon_fetcher.h new file mode 100644 index 0000000000..217058d6a9 --- /dev/null +++ b/atom/common/fileicon_fetcher.h @@ -0,0 +1,48 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_COMMON_API_FILEICON_FETCHER_H_ +#define ATOM_COMMON_API_FILEICON_FETCHER_H_ + +#include + +#include "base/files/file_path.h" +#include "base/macros.h" +#include "base/task/cancelable_task_tracker.h" +#include "chrome/browser/icon_manager.h" +#include "content/public/browser/url_data_source.h" + +namespace gfx { +class Image; +} + +class FileIconFetcher { + using IconFetchedCallback = base::Callback; + public: + static void FetchFileIcon(const base::FilePath& path, + float scale_factor, + IconLoader::IconSize icon_size, + const IconFetchedCallback& callback); + + private: + struct IconRequestDetails { + IconRequestDetails(); + IconRequestDetails(const IconRequestDetails& other); + ~IconRequestDetails(); + + // The callback to run with the response. + IconFetchedCallback callback; + + // The requested scale factor to respond with. + float scale_factor; + }; + // Called when favicon data is available from the history backend. + static void OnFileIconDataAvailable(const IconRequestDetails& details, + gfx::Image* icon); + + // Tracks tasks requesting file icons. + base::CancelableTaskTracker cancelable_task_tracker_; +}; + +#endif // ATOM_COMMON_API_FILEICON_FETCHER_H_ diff --git a/chromium_src/chrome/browser/ui/webui/fileicon_source.cc b/chromium_src/chrome/browser/ui/webui/fileicon_source.cc deleted file mode 100644 index 8659346b58..0000000000 --- a/chromium_src/chrome/browser/ui/webui/fileicon_source.cc +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "chrome/browser/ui/webui/fileicon_source.h" - -#include "base/bind.h" -#include "base/callback.h" -#include "base/files/file_path.h" -#include "base/memory/ref_counted_memory.h" -#include "base/message_loop/message_loop.h" -#include "base/strings/string_split.h" -#include "base/strings/utf_string_conversions.h" -#include "chrome/browser/browser_process.h" -#include "net/base/escape.h" -#include "third_party/skia/include/core/SkBitmap.h" -#include "ui/base/webui/web_ui_util.h" -#include "ui/gfx/codec/png_codec.h" -#include "ui/gfx/image/image.h" -#include "ui/gfx/image/image_skia.h" -#include "url/gurl.h" - -namespace { - -typedef std::map QueryIconSizeMap; - -// The path used in internal URLs to file icon data. -const char kFileIconPath[] = "fileicon"; - -// URL parameter specifying icon size. -const char kIconSize[] = "iconsize"; - -// URL parameter specifying scale factor. -const char kScaleFactor[] = "scale"; - -// Assuming the url is of the form '/path?query', convert the path portion into -// a FilePath and return the resulting |file_path| and |query|. The path -// portion may have been encoded using encodeURIComponent(). -void GetFilePathAndQuery(const std::string& url, - base::FilePath* file_path, - std::string* query) { - // We receive the url with chrome://fileicon/ stripped but GURL expects it. - const GURL gurl("chrome://fileicon/" + url); - std::string path = net::UnescapeURLComponent( - gurl.path().substr(1), - net::UnescapeRule::URL_SPECIAL_CHARS_EXCEPT_PATH_SEPARATORS | - net::UnescapeRule::PATH_SEPARATORS | net::UnescapeRule::SPACES); - - *file_path = base::FilePath::FromUTF8Unsafe(path); - *file_path = file_path->NormalizePathSeparators(); - query->assign(gurl.query()); -} - -IconLoader::IconSize SizeStringToIconSize(const std::string& size_string) { - if (size_string == "small") return IconLoader::SMALL; - if (size_string == "large") return IconLoader::LARGE; - // We default to NORMAL if we don't recognize the size_string. Including - // size_string=="normal". - return IconLoader::NORMAL; -} - -// Simple parser for data on the query. -void ParseQueryParams(const std::string& query, - float* scale_factor, - IconLoader::IconSize* icon_size) { - base::StringPairs parameters; - if (icon_size) - *icon_size = IconLoader::NORMAL; - if (scale_factor) - *scale_factor = 1.0f; - base::SplitStringIntoKeyValuePairs(query, '=', '&', ¶meters); - for (base::StringPairs::const_iterator iter = parameters.begin(); - iter != parameters.end(); ++iter) { - if (icon_size && iter->first == kIconSize) - *icon_size = SizeStringToIconSize(iter->second); - else if (scale_factor && iter->first == kScaleFactor) - webui::ParseScaleFactor(iter->second, scale_factor); - } -} - -} // namespace - -FileIconSource::IconRequestDetails::IconRequestDetails() : scale_factor(1.0f) { -} - -FileIconSource::IconRequestDetails::IconRequestDetails( - const IconRequestDetails& other) = default; - -FileIconSource::IconRequestDetails::~IconRequestDetails() { -} - -FileIconSource::FileIconSource() {} - -FileIconSource::~FileIconSource() {} - -void FileIconSource::FetchFileIcon( - const base::FilePath& path, - float scale_factor, - IconLoader::IconSize icon_size, - const content::URLDataSource::GotDataCallback& callback) { - IconManager* im = g_browser_process->icon_manager(); - gfx::Image* icon = im->LookupIconFromFilepath(path, icon_size); - - if (icon) { - scoped_refptr icon_data(new base::RefCountedBytes); - gfx::PNGCodec::EncodeBGRASkBitmap( - icon->ToImageSkia()->GetRepresentation(scale_factor).sk_bitmap(), - false, - &icon_data->data()); - - callback.Run(icon_data.get()); - } else { - // Attach the ChromeURLDataManager request ID to the history request. - IconRequestDetails details; - details.callback = callback; - details.scale_factor = scale_factor; - - // Icon was not in cache, go fetch it slowly. - im->LoadIcon(path, - icon_size, - base::Bind(&FileIconSource::OnFileIconDataAvailable, - base::Unretained(this), details), - &cancelable_task_tracker_); - } -} - -std::string FileIconSource::GetSource() const { - return kFileIconPath; -} - -// void FileIconSource::StartDataRequest( -// const std::string& url_path, -// const content::ResourceRequestInfo::WebContentsGetter& wc_getter, -// const content::URLDataSource::GotDataCallback& callback) { -// std::string query; -// base::FilePath file_path; -// IconLoader::IconSize icon_size; -// float scale_factor = 1.0f; -// GetFilePathAndQuery(url_path, &file_path, &query); -// ParseQueryParams(query, &scale_factor, &icon_size); -// FetchFileIcon(file_path, scale_factor, icon_size, callback); -// } - -std::string FileIconSource::GetMimeType(const std::string&) const { - // Rely on image decoder inferring the correct type. - return std::string(); -} - -void FileIconSource::OnFileIconDataAvailable(const IconRequestDetails& details, - gfx::Image* icon) { - if (icon) { - scoped_refptr icon_data(new base::RefCountedBytes); - gfx::PNGCodec::EncodeBGRASkBitmap( - icon->ToImageSkia()->GetRepresentation( - details.scale_factor).sk_bitmap(), - false, - &icon_data->data()); - - details.callback.Run(icon_data.get()); - } else { - // TODO(glen): send a dummy icon. - details.callback.Run(NULL); - } -} diff --git a/chromium_src/chrome/browser/ui/webui/fileicon_source.h b/chromium_src/chrome/browser/ui/webui/fileicon_source.h deleted file mode 100644 index 7fd6671b60..0000000000 --- a/chromium_src/chrome/browser/ui/webui/fileicon_source.h +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef CHROME_BROWSER_UI_WEBUI_FILEICON_SOURCE_H_ -#define CHROME_BROWSER_UI_WEBUI_FILEICON_SOURCE_H_ - -#include - -#include "base/files/file_path.h" -#include "base/macros.h" -#include "base/task/cancelable_task_tracker.h" -#include "chrome/browser/icon_manager.h" -#include "content/public/browser/url_data_source.h" - -namespace gfx { -class Image; -} - -namespace content { -class ResourceRequestInfo; -} - -// FileIconSource is the gateway between network-level chrome: -// requests for favicons and the history backend that serves these. -class FileIconSource : public content::URLDataSource { - public: - FileIconSource(); - - // content::URLDataSource implementation. - std::string GetSource() const override; - // void StartDataRequest( - // const std::string& path, - // const content::ResourceRequestInfo::WebContentsGetter& wc_getter, - // const content::URLDataSource::GotDataCallback& callback) override; - std::string GetMimeType(const std::string&) const override; - - protected: - ~FileIconSource() override; - - // Once the |path| and |icon_size| has been determined from the request, this - // function is called to perform the actual fetch. Declared as virtual for - // testing. - virtual void FetchFileIcon( - const base::FilePath& path, - float scale_factor, - IconLoader::IconSize icon_size, - const content::URLDataSource::GotDataCallback& callback); - - private: - // Contains the necessary information for completing an icon fetch request. - struct IconRequestDetails { - IconRequestDetails(); - IconRequestDetails(const IconRequestDetails& other); - ~IconRequestDetails(); - - // The callback to run with the response. - content::URLDataSource::GotDataCallback callback; - - // The requested scale factor to respond with. - float scale_factor; - }; - - // Called when favicon data is available from the history backend. - void OnFileIconDataAvailable(const IconRequestDetails& details, - gfx::Image* icon); - - // Tracks tasks requesting file icons. - base::CancelableTaskTracker cancelable_task_tracker_; - - DISALLOW_COPY_AND_ASSIGN(FileIconSource); -}; -#endif // CHROME_BROWSER_UI_WEBUI_FILEICON_SOURCE_H_ diff --git a/filenames.gypi b/filenames.gypi index 65e74b5ee6..232fd66666 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -404,6 +404,8 @@ 'atom/common/crash_reporter/win/crash_service_main.h', 'atom/common/draggable_region.cc', 'atom/common/draggable_region.h', + 'atom/common/fileicon_fetcher.cc', + 'atom/common/fileicon_fetcher.h', 'atom/common/google_api_key.h', 'atom/common/key_weak_map.h', 'atom/common/keyboard_util.cc', @@ -547,8 +549,6 @@ 'chromium_src/chrome/browser/ui/views/color_chooser_aura.h', 'chromium_src/chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.cc', 'chromium_src/chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.h', - 'chromium_src/chrome/browser/ui/webui/fileicon_source.cc', - 'chromium_src/chrome/browser/ui/webui/fileicon_source.h', 'chromium_src/chrome/common/chrome_constants.cc', 'chromium_src/chrome/common/chrome_constants.h', 'chromium_src/chrome/common/chrome_paths.cc', From eb889b9b868af5d3b82b920509b673561538699b Mon Sep 17 00:00:00 2001 From: Yury Solovyov Date: Sun, 30 Oct 2016 14:56:22 +0300 Subject: [PATCH 014/925] Get it compiling, linking till fails though --- atom/common/api/atom_api_native_image.cc | 4 +++- atom/common/api/atom_api_native_image.h | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/atom/common/api/atom_api_native_image.cc b/atom/common/api/atom_api_native_image.cc index 3f6f830e14..33c38eac00 100644 --- a/atom/common/api/atom_api_native_image.cc +++ b/atom/common/api/atom_api_native_image.cc @@ -308,7 +308,9 @@ void NativeImage::CreateFromFileIcon(v8::Isolate* isolate, const IconLoadedCallback& callback) { IconLoader::IconSize icon_size = IconLoader::IconSize::NORMAL; float scale_factor = 1.0f; - auto onready = base::Bind(&NativeImage::OnIconLoaded, isolate, callback); + auto onready = base::Bind(&NativeImage::OnIconLoaded, + base::Unretained(isolate), + callback); FileIconFetcher::FetchFileIcon(path, scale_factor, icon_size, onready); } diff --git a/atom/common/api/atom_api_native_image.h b/atom/common/api/atom_api_native_image.h index d0474835eb..2b8cbf7a2e 100644 --- a/atom/common/api/atom_api_native_image.h +++ b/atom/common/api/atom_api_native_image.h @@ -90,7 +90,7 @@ class NativeImage : public mate::Wrappable { gfx::Size GetSize(); float GetAspectRatio(); - void OnIconLoaded(v8::Isolate* isolate, + static void OnIconLoaded(v8::Isolate* isolate, const IconLoadedCallback& callback, gfx::Image& image); // Mark the image as template image. From 602aba872327df664b53b506c70fcba7107977bd Mon Sep 17 00:00:00 2001 From: Yury Solovyov Date: Sun, 30 Oct 2016 15:31:30 +0300 Subject: [PATCH 015/925] Include proper header --- atom/common/fileicon_fetcher.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atom/common/fileicon_fetcher.cc b/atom/common/fileicon_fetcher.cc index 43a864a086..a0b3b73808 100644 --- a/atom/common/fileicon_fetcher.cc +++ b/atom/common/fileicon_fetcher.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. -#include "chrome/browser/ui/webui/fileicon_source.h" +#include "atom/common/fileicon_fetcher.h" #include "base/bind.h" #include "base/callback.h" From 1b3cd87fc965acf0c41e74f25a78faa48221dc95 Mon Sep 17 00:00:00 2001 From: Yury Solovyov Date: Sun, 30 Oct 2016 17:10:09 +0300 Subject: [PATCH 016/925] Add icon manager to browser process --- atom/common/fileicon_fetcher.h | 2 +- chromium_src/chrome/browser/browser_process.cc | 7 +++++++ chromium_src/chrome/browser/browser_process.h | 4 ++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/atom/common/fileicon_fetcher.h b/atom/common/fileicon_fetcher.h index 217058d6a9..f8b0024126 100644 --- a/atom/common/fileicon_fetcher.h +++ b/atom/common/fileicon_fetcher.h @@ -42,7 +42,7 @@ class FileIconFetcher { gfx::Image* icon); // Tracks tasks requesting file icons. - base::CancelableTaskTracker cancelable_task_tracker_; + static base::CancelableTaskTracker cancelable_task_tracker_; }; #endif // ATOM_COMMON_API_FILEICON_FETCHER_H_ diff --git a/chromium_src/chrome/browser/browser_process.cc b/chromium_src/chrome/browser/browser_process.cc index a38d55f871..bed32469a6 100644 --- a/chromium_src/chrome/browser/browser_process.cc +++ b/chromium_src/chrome/browser/browser_process.cc @@ -5,6 +5,7 @@ #include "chrome/browser/browser_process.h" #include "chrome/browser/printing/print_job_manager.h" +#include "chrome/browser/icon_manager.h" #include "ui/base/l10n/l10n_util.h" BrowserProcess* g_browser_process = NULL; @@ -12,6 +13,8 @@ BrowserProcess* g_browser_process = NULL; BrowserProcess::BrowserProcess() : print_job_manager_(new printing::PrintJobManager) { g_browser_process = this; + + icon_manager_.reset(new IconManager); } BrowserProcess::~BrowserProcess() { @@ -25,3 +28,7 @@ std::string BrowserProcess::GetApplicationLocale() { printing::PrintJobManager* BrowserProcess::print_job_manager() { return print_job_manager_.get(); } + +IconManager* BrowserProcess::icon_manager() { + return icon_manager_.get(); +} diff --git a/chromium_src/chrome/browser/browser_process.h b/chromium_src/chrome/browser/browser_process.h index 1459ca31a6..9f28045c38 100644 --- a/chromium_src/chrome/browser/browser_process.h +++ b/chromium_src/chrome/browser/browser_process.h @@ -19,6 +19,8 @@ namespace printing { class PrintJobManager; } +class IconManager; + // NOT THREAD SAFE, call only from the main thread. // These functions shouldn't return NULL unless otherwise noted. class BrowserProcess { @@ -29,9 +31,11 @@ class BrowserProcess { std::string GetApplicationLocale(); printing::PrintJobManager* print_job_manager(); + IconManager* icon_manager(); private: std::unique_ptr print_job_manager_; + std::unique_ptr icon_manager_; DISALLOW_COPY_AND_ASSIGN(BrowserProcess); }; From b25b14164285405612b0c858c3596b6e7e9580d8 Mon Sep 17 00:00:00 2001 From: Robo Date: Thu, 3 Nov 2016 00:27:16 +0530 Subject: [PATCH 017/925] create iconmanager as singleton class and cleanup code (#1) * create iconmanager as singleton class and cleanup code --- atom/browser/api/atom_api_app.cc | 42 +++++++++- atom/browser/api/atom_api_app.h | 6 ++ atom/common/api/atom_api_native_image.cc | 20 ----- atom/common/api/atom_api_native_image.h | 7 -- atom/common/fileicon_fetcher.cc | 76 ------------------- atom/common/fileicon_fetcher.h | 48 ------------ .../chrome/browser/browser_process.cc | 7 -- chromium_src/chrome/browser/browser_process.h | 2 - chromium_src/chrome/browser/icon_loader.cc | 9 +-- .../chrome/browser/icon_loader_auralinux.cc | 55 ++++++++++++++ .../chrome/browser/icon_loader_mac.mm | 62 +++++++++++++++ .../chrome/browser/icon_loader_win.cc | 75 ++++++++++++++++++ chromium_src/chrome/browser/icon_manager.cc | 54 +++++-------- chromium_src/chrome/browser/icon_manager.h | 21 ++--- filenames.gypi | 7 +- 15 files changed, 275 insertions(+), 216 deletions(-) delete mode 100644 atom/common/fileicon_fetcher.cc delete mode 100644 atom/common/fileicon_fetcher.h create mode 100644 chromium_src/chrome/browser/icon_loader_auralinux.cc create mode 100644 chromium_src/chrome/browser/icon_loader_mac.mm create mode 100644 chromium_src/chrome/browser/icon_loader_win.cc diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index caaee9d25c..5f878aace1 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -30,6 +30,7 @@ #include "base/path_service.h" #include "base/strings/string_util.h" #include "brightray/browser/brightray_paths.h" +#include "chrome/browser/icon_manager.h" #include "chrome/common/chrome_paths.h" #include "content/public/browser/browser_accessibility_state.h" #include "content/public/browser/client_certificate_delegate.h" @@ -314,6 +315,26 @@ struct Converter { } }; +template <> +struct Converter { + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + IconLoader::IconSize* out) { + std::string icon_size_string; + if (!ConvertFromV8(isolate, val, &icon_size_string)) + return false; + if (icon_size_string == "small") + *out = IconLoader::IconSize::SMALL; + else if (icon_size_string == "normal") + *out = IconLoader::IconSize::NORMAL; + else if (icon_size_string == "large") + *out = IconLoader::IconSize::LARGE; + else + return false; + return true; + } +}; + template<> struct Converter { static bool FromV8(v8::Isolate* isolate, v8::Local val, @@ -462,6 +483,11 @@ int ImportIntoCertStore( } #endif +void OnIconDataAvailable(const App::FileIconCallback& callback, + gfx::Image* icon) { + callback.Run(icon ? *icon : gfx::Image()); +} + } // namespace App::App(v8::Isolate* isolate) { @@ -841,6 +867,19 @@ JumpListResult App::SetJumpList(v8::Local val, } #endif // defined(OS_WIN) +void App::GetFileIcon(const base::FilePath& path, + IconLoader::IconSize icon_size, + const FileIconCallback& callback) { + IconManager* icon_manager = IconManager::GetInstance(); + gfx::Image* icon = icon_manager->LookupIconFromFilepath(path, icon_size); + if (icon) { + callback.Run(*icon); + } else { + icon_manager->LoadIcon(path, icon_size, + base::Bind(&OnIconDataAvailable, callback)); + } +} + // static mate::Handle App::Create(v8::Isolate* isolate) { return mate::CreateHandle(isolate, new App(isolate)); @@ -909,7 +948,8 @@ void App::BuildPrototype( .SetMethod("isAccessibilitySupportEnabled", &App::IsAccessibilitySupportEnabled) .SetMethod("disableHardwareAcceleration", - &App::DisableHardwareAcceleration); + &App::DisableHardwareAcceleration) + .SetMethod("getFileIcon", &App::GetFileIcon); } } // namespace api diff --git a/atom/browser/api/atom_api_app.h b/atom/browser/api/atom_api_app.h index c7e62927c5..b42b201fff 100644 --- a/atom/browser/api/atom_api_app.h +++ b/atom/browser/api/atom_api_app.h @@ -13,6 +13,7 @@ #include "atom/browser/browser.h" #include "atom/browser/browser_observer.h" #include "atom/common/native_mate_converters/callback.h" +#include "chrome/browser/icon_loader.h" #include "chrome/browser/process_singleton.h" #include "content/public/browser/gpu_data_manager_observer.h" #include "native_mate/handle.h" @@ -43,6 +44,8 @@ class App : public AtomBrowserClient::Delegate, public BrowserObserver, public content::GpuDataManagerObserver { public: + using FileIconCallback = base::Callback; + static mate::Handle Create(v8::Isolate* isolate); static void BuildPrototype(v8::Isolate* isolate, @@ -129,6 +132,9 @@ class App : public AtomBrowserClient::Delegate, void ImportCertificate(const base::DictionaryValue& options, const net::CompletionCallback& callback); #endif + void GetFileIcon(const base::FilePath& path, + IconLoader::IconSize icon_size, + const FileIconCallback& callback); #if defined(OS_WIN) // Get the current Jump List settings. diff --git a/atom/common/api/atom_api_native_image.cc b/atom/common/api/atom_api_native_image.cc index 33c38eac00..1d4e20e3cf 100644 --- a/atom/common/api/atom_api_native_image.cc +++ b/atom/common/api/atom_api_native_image.cc @@ -17,7 +17,6 @@ #include "base/files/file_util.h" #include "base/strings/pattern.h" #include "base/strings/string_util.h" -#include "atom/common/fileicon_fetcher.h" #include "native_mate/dictionary.h" #include "native_mate/object_template_builder.h" #include "net/base/data_url.h" @@ -303,23 +302,6 @@ gfx::Size NativeImage::GetSize() { return image_.Size(); } -void NativeImage::CreateFromFileIcon(v8::Isolate* isolate, - const base::FilePath& path, - const IconLoadedCallback& callback) { - IconLoader::IconSize icon_size = IconLoader::IconSize::NORMAL; - float scale_factor = 1.0f; - auto onready = base::Bind(&NativeImage::OnIconLoaded, - base::Unretained(isolate), - callback); - FileIconFetcher::FetchFileIcon(path, scale_factor, icon_size, onready); -} - -void NativeImage::OnIconLoaded(v8::Isolate* isolate, - const IconLoadedCallback& callback, - gfx::Image& image) { - callback.Run(Create(isolate, image)); -} - float NativeImage::GetAspectRatio() { gfx::Size size = GetSize(); if (size.IsEmpty()) @@ -533,8 +515,6 @@ void Initialize(v8::Local exports, v8::Local unused, mate::Dictionary dict(context->GetIsolate(), exports); dict.SetMethod("createEmpty", &atom::api::NativeImage::CreateEmpty); dict.SetMethod("createFromPath", &atom::api::NativeImage::CreateFromPath); - dict.SetMethod("createFromFileIcon", - &atom::api::NativeImage::CreateFromFileIcon); dict.SetMethod("createFromBuffer", &atom::api::NativeImage::CreateFromBuffer); dict.SetMethod("createFromDataURL", &atom::api::NativeImage::CreateFromDataURL); diff --git a/atom/common/api/atom_api_native_image.h b/atom/common/api/atom_api_native_image.h index 2b8cbf7a2e..ee1b5f5d4b 100644 --- a/atom/common/api/atom_api_native_image.h +++ b/atom/common/api/atom_api_native_image.h @@ -38,7 +38,6 @@ namespace atom { namespace api { class NativeImage : public mate::Wrappable { - using IconLoadedCallback = base::Callback)>; public: static mate::Handle CreateEmpty(v8::Isolate* isolate); static mate::Handle Create( @@ -53,9 +52,6 @@ class NativeImage : public mate::Wrappable { mate::Arguments* args, v8::Local buffer); static mate::Handle CreateFromDataURL( v8::Isolate* isolate, const GURL& url); - static void CreateFromFileIcon(v8::Isolate* isolate, - const base::FilePath& path, - const IconLoadedCallback& callback); static void BuildPrototype(v8::Isolate* isolate, v8::Local prototype); @@ -90,9 +86,6 @@ class NativeImage : public mate::Wrappable { gfx::Size GetSize(); float GetAspectRatio(); - static void OnIconLoaded(v8::Isolate* isolate, - const IconLoadedCallback& callback, - gfx::Image& image); // Mark the image as template image. void SetTemplateImage(bool setAsTemplate); // Determine if the image is a template image. diff --git a/atom/common/fileicon_fetcher.cc b/atom/common/fileicon_fetcher.cc deleted file mode 100644 index a0b3b73808..0000000000 --- a/atom/common/fileicon_fetcher.cc +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) 2016 GitHub, Inc. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#include "atom/common/fileicon_fetcher.h" - -#include "base/bind.h" -#include "base/callback.h" -#include "base/files/file_path.h" -#include "base/memory/ref_counted_memory.h" -#include "base/message_loop/message_loop.h" -#include "base/strings/string_split.h" -#include "base/strings/utf_string_conversions.h" -#include "chrome/browser/browser_process.h" -#include "net/base/escape.h" -#include "third_party/skia/include/core/SkBitmap.h" -#include "ui/base/webui/web_ui_util.h" -#include "ui/gfx/codec/png_codec.h" -#include "ui/gfx/image/image.h" -#include "ui/gfx/image/image_skia.h" -#include "url/gurl.h" - -void FileIconFetcher::FetchFileIcon(const base::FilePath& path, - float scale_factor, - IconLoader::IconSize icon_size, - const IconFetchedCallback& callback) { - IconManager* im = g_browser_process->icon_manager(); - gfx::Image* icon = im->LookupIconFromFilepath(path, icon_size); - - if (icon) { - scoped_refptr icon_data(new base::RefCountedBytes); - gfx::PNGCodec::EncodeBGRASkBitmap( - icon->ToImageSkia()->GetRepresentation(scale_factor).sk_bitmap(), - false, - &icon_data->data()); - - callback.Run(icon_data.get()); - } else { - // Attach the ChromeURLDataManager request ID to the history request. - IconRequestDetails details; - details.callback = callback; - details.scale_factor = scale_factor; - - // Icon was not in cache, go fetch it slowly. - im->LoadIcon(path, - icon_size, - base::Bind(&FileIconFetcher::OnFileIconDataAvailable, details), - &cancelable_task_tracker_); - } -} - -void FileIconFetcher::OnFileIconDataAvailable(const IconRequestDetails& details, - gfx::Image* icon) { - if (icon) { - scoped_refptr icon_data(new base::RefCountedBytes); - gfx::PNGCodec::EncodeBGRASkBitmap( - icon->ToImageSkia()->GetRepresentation( - details.scale_factor).sk_bitmap(), - false, - &icon_data->data()); - - details.callback.Run(icon_data.get()); - } else { - details.callback.Run(NULL); - } -} - - -FileIconFetcher::IconRequestDetails::IconRequestDetails() : scale_factor(1.0f) { -} - -FileIconFetcher::IconRequestDetails::IconRequestDetails( - const IconRequestDetails& other) = default; - -FileIconFetcher::IconRequestDetails::~IconRequestDetails() { -} diff --git a/atom/common/fileicon_fetcher.h b/atom/common/fileicon_fetcher.h deleted file mode 100644 index f8b0024126..0000000000 --- a/atom/common/fileicon_fetcher.h +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) 2016 GitHub, Inc. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#ifndef ATOM_COMMON_API_FILEICON_FETCHER_H_ -#define ATOM_COMMON_API_FILEICON_FETCHER_H_ - -#include - -#include "base/files/file_path.h" -#include "base/macros.h" -#include "base/task/cancelable_task_tracker.h" -#include "chrome/browser/icon_manager.h" -#include "content/public/browser/url_data_source.h" - -namespace gfx { -class Image; -} - -class FileIconFetcher { - using IconFetchedCallback = base::Callback; - public: - static void FetchFileIcon(const base::FilePath& path, - float scale_factor, - IconLoader::IconSize icon_size, - const IconFetchedCallback& callback); - - private: - struct IconRequestDetails { - IconRequestDetails(); - IconRequestDetails(const IconRequestDetails& other); - ~IconRequestDetails(); - - // The callback to run with the response. - IconFetchedCallback callback; - - // The requested scale factor to respond with. - float scale_factor; - }; - // Called when favicon data is available from the history backend. - static void OnFileIconDataAvailable(const IconRequestDetails& details, - gfx::Image* icon); - - // Tracks tasks requesting file icons. - static base::CancelableTaskTracker cancelable_task_tracker_; -}; - -#endif // ATOM_COMMON_API_FILEICON_FETCHER_H_ diff --git a/chromium_src/chrome/browser/browser_process.cc b/chromium_src/chrome/browser/browser_process.cc index bed32469a6..a38d55f871 100644 --- a/chromium_src/chrome/browser/browser_process.cc +++ b/chromium_src/chrome/browser/browser_process.cc @@ -5,7 +5,6 @@ #include "chrome/browser/browser_process.h" #include "chrome/browser/printing/print_job_manager.h" -#include "chrome/browser/icon_manager.h" #include "ui/base/l10n/l10n_util.h" BrowserProcess* g_browser_process = NULL; @@ -13,8 +12,6 @@ BrowserProcess* g_browser_process = NULL; BrowserProcess::BrowserProcess() : print_job_manager_(new printing::PrintJobManager) { g_browser_process = this; - - icon_manager_.reset(new IconManager); } BrowserProcess::~BrowserProcess() { @@ -28,7 +25,3 @@ std::string BrowserProcess::GetApplicationLocale() { printing::PrintJobManager* BrowserProcess::print_job_manager() { return print_job_manager_.get(); } - -IconManager* BrowserProcess::icon_manager() { - return icon_manager_.get(); -} diff --git a/chromium_src/chrome/browser/browser_process.h b/chromium_src/chrome/browser/browser_process.h index 9f28045c38..a21371efb2 100644 --- a/chromium_src/chrome/browser/browser_process.h +++ b/chromium_src/chrome/browser/browser_process.h @@ -31,11 +31,9 @@ class BrowserProcess { std::string GetApplicationLocale(); printing::PrintJobManager* print_job_manager(); - IconManager* icon_manager(); private: std::unique_ptr print_job_manager_; - std::unique_ptr icon_manager_; DISALLOW_COPY_AND_ASSIGN(BrowserProcess); }; diff --git a/chromium_src/chrome/browser/icon_loader.cc b/chromium_src/chrome/browser/icon_loader.cc index 979ee5772c..afc5d8016e 100644 --- a/chromium_src/chrome/browser/icon_loader.cc +++ b/chromium_src/chrome/browser/icon_loader.cc @@ -18,15 +18,14 @@ IconLoader::IconLoader(const base::FilePath& file_path, icon_size_(size), delegate_(delegate) {} -IconLoader::~IconLoader() { -} +IconLoader::~IconLoader() {} void IconLoader::Start() { target_task_runner_ = base::ThreadTaskRunnerHandle::Get(); BrowserThread::PostTaskAndReply(BrowserThread::FILE, FROM_HERE, - base::Bind(&IconLoader::ReadGroup, this), - base::Bind(&IconLoader::OnReadGroup, this)); + base::Bind(&IconLoader::ReadGroup, this), + base::Bind(&IconLoader::OnReadGroup, this)); } void IconLoader::ReadGroup() { @@ -37,7 +36,7 @@ void IconLoader::OnReadGroup() { if (IsIconMutableFromFilepath(file_path_) || !delegate_->OnGroupLoaded(this, group_)) { BrowserThread::PostTask(ReadIconThreadID(), FROM_HERE, - base::Bind(&IconLoader::ReadIcon, this)); + base::Bind(&IconLoader::ReadIcon, this)); } } diff --git a/chromium_src/chrome/browser/icon_loader_auralinux.cc b/chromium_src/chrome/browser/icon_loader_auralinux.cc new file mode 100644 index 0000000000..2a1b0d868f --- /dev/null +++ b/chromium_src/chrome/browser/icon_loader_auralinux.cc @@ -0,0 +1,55 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/icon_loader.h" + +#include "base/bind.h" +#include "base/message_loop/message_loop.h" +#include "base/nix/mime_util_xdg.h" +#include "ui/views/linux_ui/linux_ui.h" + +// static +IconGroupID IconLoader::ReadGroupIDFromFilepath( + const base::FilePath& filepath) { + return base::nix::GetFileMimeType(filepath); +} + +// static +bool IconLoader::IsIconMutableFromFilepath(const base::FilePath&) { + return false; +} + +// static +content::BrowserThread::ID IconLoader::ReadIconThreadID() { + // ReadIcon() calls into views::LinuxUI and GTK2 code, so it must be on the UI + // thread. + return content::BrowserThread::UI; +} + +void IconLoader::ReadIcon() { + int size_pixels = 0; + switch (icon_size_) { + case IconLoader::SMALL: + size_pixels = 16; + break; + case IconLoader::NORMAL: + size_pixels = 32; + break; + case IconLoader::LARGE: + size_pixels = 48; + break; + default: + NOTREACHED(); + } + + views::LinuxUI* ui = views::LinuxUI::instance(); + if (ui) { + gfx::Image image = ui->GetIconForContentType(group_, size_pixels); + if (!image.IsEmpty()) + image_.reset(new gfx::Image(image)); + } + + target_task_runner_->PostTask(FROM_HERE, + base::Bind(&IconLoader::NotifyDelegate, this)); +} diff --git a/chromium_src/chrome/browser/icon_loader_mac.mm b/chromium_src/chrome/browser/icon_loader_mac.mm new file mode 100644 index 0000000000..a9dd42566b --- /dev/null +++ b/chromium_src/chrome/browser/icon_loader_mac.mm @@ -0,0 +1,62 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/icon_loader.h" + +#import + +#include "base/bind.h" +#include "base/files/file_path.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/sys_string_conversions.h" +#include "base/threading/thread.h" +#include "ui/gfx/image/image_skia.h" +#include "ui/gfx/image/image_skia_util_mac.h" + +// static +IconGroupID IconLoader::ReadGroupIDFromFilepath( + const base::FilePath& filepath) { + return filepath.Extension(); +} + +// static +bool IconLoader::IsIconMutableFromFilepath(const base::FilePath&) { + return false; +} + +// static +content::BrowserThread::ID IconLoader::ReadIconThreadID() { + return content::BrowserThread::FILE; +} + +void IconLoader::ReadIcon() { + NSString* group = base::SysUTF8ToNSString(group_); + NSWorkspace* workspace = [NSWorkspace sharedWorkspace]; + NSImage* icon = [workspace iconForFileType:group]; + + if (icon_size_ == ALL) { + // The NSImage already has all sizes. + image_.reset(new gfx::Image([icon retain])); + } else { + NSSize size = NSZeroSize; + switch (icon_size_) { + case IconLoader::SMALL: + size = NSMakeSize(16, 16); + break; + case IconLoader::NORMAL: + size = NSMakeSize(32, 32); + break; + default: + NOTREACHED(); + } + gfx::ImageSkia image_skia(gfx::ImageSkiaFromResizedNSImage(icon, size)); + if (!image_skia.isNull()) { + image_skia.MakeThreadSafe(); + image_.reset(new gfx::Image(image_skia)); + } + } + + target_task_runner_->PostTask(FROM_HERE, + base::Bind(&IconLoader::NotifyDelegate, this)); +} diff --git a/chromium_src/chrome/browser/icon_loader_win.cc b/chromium_src/chrome/browser/icon_loader_win.cc new file mode 100644 index 0000000000..079c1f4828 --- /dev/null +++ b/chromium_src/chrome/browser/icon_loader_win.cc @@ -0,0 +1,75 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/icon_loader.h" + +#include +#include + +#include "base/bind.h" +#include "base/message_loop/message_loop.h" +#include "base/threading/thread.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/display/win/dpi.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/icon_util.h" +#include "ui/gfx/image/image_skia.h" + +// static +IconGroupID IconLoader::ReadGroupIDFromFilepath( + const base::FilePath& filepath) { + if (!IsIconMutableFromFilepath(filepath)) + return filepath.Extension(); + return filepath.value(); +} + +// static +bool IconLoader::IsIconMutableFromFilepath(const base::FilePath& filepath) { + return filepath.MatchesExtension(L".exe") || + filepath.MatchesExtension(L".dll") || + filepath.MatchesExtension(L".ico"); +} + +// static +content::BrowserThread::ID IconLoader::ReadIconThreadID() { + return content::BrowserThread::FILE; +} + +void IconLoader::ReadIcon() { + int size = 0; + switch (icon_size_) { + case IconLoader::SMALL: + size = SHGFI_SMALLICON; + break; + case IconLoader::NORMAL: + size = 0; + break; + case IconLoader::LARGE: + size = SHGFI_LARGEICON; + break; + default: + NOTREACHED(); + } + + image_.reset(); + + SHFILEINFO file_info = {0}; + if (SHGetFileInfo(group_.c_str(), FILE_ATTRIBUTE_NORMAL, &file_info, + sizeof(SHFILEINFO), + SHGFI_ICON | size | SHGFI_USEFILEATTRIBUTES)) { + std::unique_ptr bitmap( + IconUtil::CreateSkBitmapFromHICON(file_info.hIcon)); + if (bitmap.get()) { + gfx::ImageSkia image_skia( + gfx::ImageSkiaRep(*bitmap, display::win::GetDPIScale())); + image_skia.MakeThreadSafe(); + image_.reset(new gfx::Image(image_skia)); + DestroyIcon(file_info.hIcon); + } + } + + // Always notify the delegate, regardless of success. + target_task_runner_->PostTask(FROM_HERE, + base::Bind(&IconLoader::NotifyDelegate, this)); +} diff --git a/chromium_src/chrome/browser/icon_manager.cc b/chromium_src/chrome/browser/icon_manager.cc index 6596ab3112..17e362c534 100644 --- a/chromium_src/chrome/browser/icon_manager.cc +++ b/chromium_src/chrome/browser/icon_manager.cc @@ -13,30 +13,21 @@ #include "third_party/skia/include/core/SkBitmap.h" #include "third_party/skia/include/core/SkCanvas.h" -namespace { - -void RunCallbackIfNotCanceled( - const base::CancelableTaskTracker::IsCanceledCallback& is_canceled, - const IconManager::IconRequestCallback& callback, - gfx::Image* image) { - if (is_canceled.Run()) - return; - callback.Run(image); -} - -} // namespace - struct IconManager::ClientRequest { IconRequestCallback callback; base::FilePath file_path; IconLoader::IconSize size; }; -IconManager::IconManager() { +// static +IconManager* IconManager::GetInstance() { + return base::Singleton::get(); } +IconManager::IconManager() {} + IconManager::~IconManager() { - base::STLDeleteValues(&icon_cache_); + STLDeleteValues(&icon_cache_); } gfx::Image* IconManager::LookupIconFromFilepath(const base::FilePath& file_name, @@ -54,33 +45,23 @@ gfx::Image* IconManager::LookupIconFromGroup(const IconGroupID& group, if (it != icon_cache_.end()) return it->second; - return NULL; + return nullptr; } -base::CancelableTaskTracker::TaskId IconManager::LoadIcon( - const base::FilePath& file_name, - IconLoader::IconSize size, - const IconRequestCallback& callback, - base::CancelableTaskTracker* tracker) { +void IconManager::LoadIcon(const base::FilePath& file_name, + IconLoader::IconSize size, + const IconRequestCallback& callback) { IconLoader* loader = new IconLoader(file_name, size, this); loader->AddRef(); loader->Start(); - base::CancelableTaskTracker::IsCanceledCallback is_canceled; - base::CancelableTaskTracker::TaskId id = - tracker->NewTrackedTaskId(&is_canceled); - IconRequestCallback callback_runner = base::Bind( - &RunCallbackIfNotCanceled, is_canceled, callback); - - ClientRequest client_request = { callback_runner, file_name, size }; + ClientRequest client_request = {callback, file_name, size}; requests_[loader] = client_request; - return id; } // IconLoader::Delegate implementation ----------------------------------------- -bool IconManager::OnGroupLoaded(IconLoader* loader, - const IconGroupID& group) { +bool IconManager::OnGroupLoaded(IconLoader* loader, const IconGroupID& group) { ClientRequests::iterator rit = requests_.find(loader); if (rit == requests_.end()) { NOTREACHED(); @@ -95,8 +76,9 @@ bool IconManager::OnGroupLoaded(IconLoader* loader, return OnImageLoaded(loader, result, group); } -bool IconManager::OnImageLoaded( - IconLoader* loader, gfx::Image* result, const IconGroupID& group) { +bool IconManager::OnImageLoaded(IconLoader* loader, + gfx::Image* result, + const IconGroupID& group) { ClientRequests::iterator rit = requests_.find(loader); // Balances the AddRef() in LoadIcon(). @@ -139,10 +121,8 @@ bool IconManager::OnImageLoaded( IconManager::CacheKey::CacheKey(const IconGroupID& group, IconLoader::IconSize size) - : group(group), - size(size) { -} + : group(group), size(size) {} -bool IconManager::CacheKey::operator<(const CacheKey &other) const { +bool IconManager::CacheKey::operator<(const CacheKey& other) const { return std::tie(group, size) < std::tie(other.group, other.size); } diff --git a/chromium_src/chrome/browser/icon_manager.h b/chromium_src/chrome/browser/icon_manager.h index 0b5ec07d41..dd6de82527 100644 --- a/chromium_src/chrome/browser/icon_manager.h +++ b/chromium_src/chrome/browser/icon_manager.h @@ -49,15 +49,13 @@ #include "base/files/file_path.h" #include "base/macros.h" -#include "base/task/cancelable_task_tracker.h" +#include "base/memory/singleton.h" #include "chrome/browser/icon_loader.h" #include "ui/gfx/image/image.h" class IconManager : public IconLoader::Delegate { public: - IconManager(); - ~IconManager() override; - + static IconManager* GetInstance(); // Synchronous call to examine the internal caches for the icon. Returns the // icon if we have already loaded it, NULL if we don't have it and must load // it via 'LoadIcon'. The returned bitmap is owned by the IconManager and must @@ -78,11 +76,9 @@ class IconManager : public IconLoader::Delegate { // should never keep it or delete it. // 3. The gfx::Image pointer passed to the callback may be NULL if decoding // failed. - base::CancelableTaskTracker::TaskId LoadIcon( - const base::FilePath& file_name, - IconLoader::IconSize size, - const IconRequestCallback& callback, - base::CancelableTaskTracker* tracker); + void LoadIcon(const base::FilePath& file_name, + IconLoader::IconSize size, + const IconRequestCallback& callback); // IconLoader::Delegate interface. bool OnGroupLoaded(IconLoader* loader, const IconGroupID& group) override; @@ -91,11 +87,16 @@ class IconManager : public IconLoader::Delegate { const IconGroupID& group) override; private: + friend struct base::DefaultSingletonTraits; + + IconManager(); + ~IconManager() override; + struct CacheKey { CacheKey(const IconGroupID& group, IconLoader::IconSize size); // Used as a key in the map below, so we need this comparator. - bool operator<(const CacheKey &other) const; + bool operator<(const CacheKey& other) const; IconGroupID group; IconLoader::IconSize size; diff --git a/filenames.gypi b/filenames.gypi index 232fd66666..74cef968cd 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -404,8 +404,6 @@ 'atom/common/crash_reporter/win/crash_service_main.h', 'atom/common/draggable_region.cc', 'atom/common/draggable_region.h', - 'atom/common/fileicon_fetcher.cc', - 'atom/common/fileicon_fetcher.h', 'atom/common/google_api_key.h', 'atom/common/key_weak_map.h', 'atom/common/keyboard_util.cc', @@ -476,11 +474,14 @@ 'chromium_src/chrome/browser/browser_process.h', 'chromium_src/chrome/browser/chrome_process_finder_win.cc', 'chromium_src/chrome/browser/chrome_process_finder_win.h', - 'chromium_src/chrome/browser/chrome_notification_types.h', + 'chromium_src/chrome/browser/icon_loader_auralinux.cc', + 'chromium_src/chrome/browser/icon_loader_mac.mm', + 'chromium_src/chrome/browser/icon_loader_win.cc', 'chromium_src/chrome/browser/icon_loader.cc', 'chromium_src/chrome/browser/icon_loader.h', 'chromium_src/chrome/browser/icon_manager.cc', 'chromium_src/chrome/browser/icon_manager.h', + 'chromium_src/chrome/browser/chrome_notification_types.h', 'chromium_src/chrome/browser/extensions/global_shortcut_listener.cc', 'chromium_src/chrome/browser/extensions/global_shortcut_listener.h', 'chromium_src/chrome/browser/extensions/global_shortcut_listener_mac.mm', From ff3aaa55f7cb303443cb511f70518b06708460ce Mon Sep 17 00:00:00 2001 From: deepak1556 Date: Thu, 3 Nov 2016 10:47:03 +0530 Subject: [PATCH 018/925] define icon loader for liunx as separate target --- electron.gyp | 1 + filenames.gypi | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/electron.gyp b/electron.gyp index adf38cfbe5..a657d2d1c5 100644 --- a/electron.gyp +++ b/electron.gyp @@ -328,6 +328,7 @@ }], # OS=="mac" and mas_build==1 ['OS=="linux"', { 'sources': [ + '<@(lib_sources_linux)', '<@(lib_sources_nss)', ], 'link_settings': { diff --git a/filenames.gypi b/filenames.gypi index 74cef968cd..5b16beaa91 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -474,7 +474,6 @@ 'chromium_src/chrome/browser/browser_process.h', 'chromium_src/chrome/browser/chrome_process_finder_win.cc', 'chromium_src/chrome/browser/chrome_process_finder_win.h', - 'chromium_src/chrome/browser/icon_loader_auralinux.cc', 'chromium_src/chrome/browser/icon_loader_mac.mm', 'chromium_src/chrome/browser/icon_loader_win.cc', 'chromium_src/chrome/browser/icon_loader.cc', @@ -608,6 +607,9 @@ '<@(native_mate_files)', '<(SHARED_INTERMEDIATE_DIR)/atom_natives.h', ], + 'lib_sources_linux': [ + 'chromium_src/chrome/browser/icon_loader_auralinux.cc', + ], 'lib_sources_nss': [ 'chromium_src/chrome/browser/certificate_manager_model.cc', 'chromium_src/chrome/browser/certificate_manager_model.h', From bec671bac83c23dcf6b74bc6de582aa363c8b900 Mon Sep 17 00:00:00 2001 From: Yury Solovyov Date: Thu, 3 Nov 2016 21:04:56 +0300 Subject: [PATCH 019/925] Make size optional --- atom/browser/api/atom_api_app.cc | 15 +++++++++++++-- atom/browser/api/atom_api_app.h | 3 +-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index 5f878aace1..f5d9201f84 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -868,8 +868,19 @@ JumpListResult App::SetJumpList(v8::Local val, #endif // defined(OS_WIN) void App::GetFileIcon(const base::FilePath& path, - IconLoader::IconSize icon_size, - const FileIconCallback& callback) { + mate::Arguments* args) { + IconLoader::IconSize icon_size; + FileIconCallback callback; + + if (!args->GetNext(&icon_size)) { + icon_size = IconLoader::IconSize::NORMAL; + } + + if (!args->GetNext(&callback)) { + args->ThrowError(); + return; + } + IconManager* icon_manager = IconManager::GetInstance(); gfx::Image* icon = icon_manager->LookupIconFromFilepath(path, icon_size); if (icon) { diff --git a/atom/browser/api/atom_api_app.h b/atom/browser/api/atom_api_app.h index b42b201fff..7d2f6f752b 100644 --- a/atom/browser/api/atom_api_app.h +++ b/atom/browser/api/atom_api_app.h @@ -133,8 +133,7 @@ class App : public AtomBrowserClient::Delegate, const net::CompletionCallback& callback); #endif void GetFileIcon(const base::FilePath& path, - IconLoader::IconSize icon_size, - const FileIconCallback& callback); + mate::Arguments* args); #if defined(OS_WIN) // Get the current Jump List settings. From 05cb26a1743fbe11d2218c13764c4fc3658d729c Mon Sep 17 00:00:00 2001 From: Yury Solovyov Date: Thu, 3 Nov 2016 21:23:40 +0300 Subject: [PATCH 020/925] Use object for options --- atom/browser/api/atom_api_app.cc | 36 +++++++++++++------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index f5d9201f84..c6b50a48e9 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -315,26 +315,6 @@ struct Converter { } }; -template <> -struct Converter { - static bool FromV8(v8::Isolate* isolate, - v8::Local val, - IconLoader::IconSize* out) { - std::string icon_size_string; - if (!ConvertFromV8(isolate, val, &icon_size_string)) - return false; - if (icon_size_string == "small") - *out = IconLoader::IconSize::SMALL; - else if (icon_size_string == "normal") - *out = IconLoader::IconSize::NORMAL; - else if (icon_size_string == "large") - *out = IconLoader::IconSize::LARGE; - else - return false; - return true; - } -}; - template<> struct Converter { static bool FromV8(v8::Isolate* isolate, v8::Local val, @@ -356,6 +336,15 @@ namespace api { namespace { +IconLoader::IconSize GetIconSizeByString(std::string size) { + if (size == "small") { + return IconLoader::IconSize::SMALL; + } else if (size == "large") { + return IconLoader::IconSize::LARGE; + } + return IconLoader::IconSize::NORMAL; +}; + // Return the path constant from string. int GetPathConstant(const std::string& name) { if (name == "appData") @@ -869,11 +858,16 @@ JumpListResult App::SetJumpList(v8::Local val, void App::GetFileIcon(const base::FilePath& path, mate::Arguments* args) { + base::DictionaryValue options; IconLoader::IconSize icon_size; FileIconCallback callback; - if (!args->GetNext(&icon_size)) { + if (!args->GetNext(&options)) { icon_size = IconLoader::IconSize::NORMAL; + } else { + std::string icon_size_string; + options.GetString("size", &icon_size_string); + icon_size = GetIconSizeByString(icon_size_string); } if (!args->GetNext(&callback)) { From fe99b255c425591dc5fc5729eb2f9f684320e205 Mon Sep 17 00:00:00 2001 From: Yury Solovyov Date: Thu, 3 Nov 2016 21:38:57 +0300 Subject: [PATCH 021/925] Add docs --- docs/api/app.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/api/app.md b/docs/api/app.md index 83e071618d..8398f4c0a7 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -401,6 +401,14 @@ You can request the following paths by the name: * `videos` Directory for a user's videos. * `pepperFlashSystemPlugin` Full path to the system version of the Pepper Flash plugin. +### `app.getFileIcon(path, [options], callback)` + +* `path` String +* `options` Object (optional) + * `size` String - Defaults to `normal`. Can be `small`, `normal`, `large` +* `callback` Function + * `Icon` [NativeImage](native-image.md) + ### `app.setPath(name, path)` * `name` String From 3d47c9b71df7df1b65c330f2110e059a87b4237e Mon Sep 17 00:00:00 2001 From: Yury Solovyov Date: Thu, 3 Nov 2016 21:43:53 +0300 Subject: [PATCH 022/925] Fix lint --- atom/browser/api/atom_api_app.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index c6b50a48e9..4b59914993 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -343,7 +343,7 @@ IconLoader::IconSize GetIconSizeByString(std::string size) { return IconLoader::IconSize::LARGE; } return IconLoader::IconSize::NORMAL; -}; +} // Return the path constant from string. int GetPathConstant(const std::string& name) { From 2e85ff1f57b9d4cca28a89f3c955c5c34d6ac55c Mon Sep 17 00:00:00 2001 From: deepak1556 Date: Fri, 4 Nov 2016 12:29:18 +0530 Subject: [PATCH 023/925] Fix code style --- atom/browser/api/atom_api_app.cc | 6 +++--- atom/common/api/atom_api_native_image.cc | 1 - chromium_src/chrome/browser/browser_process.h | 2 -- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index 4b59914993..cabe751a04 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -336,7 +336,7 @@ namespace api { namespace { -IconLoader::IconSize GetIconSizeByString(std::string size) { +IconLoader::IconSize GetIconSizeByString(const std::string& size) { if (size == "small") { return IconLoader::IconSize::SMALL; } else if (size == "large") { @@ -858,7 +858,7 @@ JumpListResult App::SetJumpList(v8::Local val, void App::GetFileIcon(const base::FilePath& path, mate::Arguments* args) { - base::DictionaryValue options; + mate::Dictionary options; IconLoader::IconSize icon_size; FileIconCallback callback; @@ -866,7 +866,7 @@ void App::GetFileIcon(const base::FilePath& path, icon_size = IconLoader::IconSize::NORMAL; } else { std::string icon_size_string; - options.GetString("size", &icon_size_string); + options.Get("size", &icon_size_string); icon_size = GetIconSizeByString(icon_size_string); } diff --git a/atom/common/api/atom_api_native_image.cc b/atom/common/api/atom_api_native_image.cc index 1d4e20e3cf..0c174941db 100644 --- a/atom/common/api/atom_api_native_image.cc +++ b/atom/common/api/atom_api_native_image.cc @@ -8,7 +8,6 @@ #include #include "atom/common/asar/asar_util.h" -#include "atom/common/native_mate_converters/callback.h" #include "atom/common/native_mate_converters/file_path_converter.h" #include "atom/common/native_mate_converters/gfx_converter.h" #include "atom/common/native_mate_converters/gurl_converter.h" diff --git a/chromium_src/chrome/browser/browser_process.h b/chromium_src/chrome/browser/browser_process.h index a21371efb2..1459ca31a6 100644 --- a/chromium_src/chrome/browser/browser_process.h +++ b/chromium_src/chrome/browser/browser_process.h @@ -19,8 +19,6 @@ namespace printing { class PrintJobManager; } -class IconManager; - // NOT THREAD SAFE, call only from the main thread. // These functions shouldn't return NULL unless otherwise noted. class BrowserProcess { From 1b4ee6e0d87c5a881083966510ab62fd9beead58 Mon Sep 17 00:00:00 2001 From: Yury Date: Sat, 5 Nov 2016 21:23:49 +0300 Subject: [PATCH 024/925] Image from icon node-style callback (#2) * Try implementing node-style callbacks * Add locker and handle scope --- atom/browser/api/atom_api_app.cc | 16 ++++++++++++++-- atom/browser/api/atom_api_app.h | 3 ++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index cabe751a04..7358960685 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -474,7 +474,16 @@ int ImportIntoCertStore( void OnIconDataAvailable(const App::FileIconCallback& callback, gfx::Image* icon) { - callback.Run(icon ? *icon : gfx::Image()); + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + if (icon && !icon->IsEmpty()) { + v8::Locker locker(isolate); + v8::HandleScope handle_scope(isolate); + callback.Run(v8::Null(isolate), *icon); + } else { + v8::Local error_message = + v8::String::NewFromUtf8(isolate, "Failed to get file icon."); + callback.Run(v8::Exception::Error(error_message), gfx::Image()); + } } } // namespace @@ -878,7 +887,10 @@ void App::GetFileIcon(const base::FilePath& path, IconManager* icon_manager = IconManager::GetInstance(); gfx::Image* icon = icon_manager->LookupIconFromFilepath(path, icon_size); if (icon) { - callback.Run(*icon); + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::Locker locker(isolate); + v8::HandleScope handle_scope(isolate); + callback.Run(v8::Null(isolate), *icon); } else { icon_manager->LoadIcon(path, icon_size, base::Bind(&OnIconDataAvailable, callback)); diff --git a/atom/browser/api/atom_api_app.h b/atom/browser/api/atom_api_app.h index 7d2f6f752b..b89761ede5 100644 --- a/atom/browser/api/atom_api_app.h +++ b/atom/browser/api/atom_api_app.h @@ -12,6 +12,7 @@ #include "atom/browser/atom_browser_client.h" #include "atom/browser/browser.h" #include "atom/browser/browser_observer.h" +#include "atom/common/api/atom_api_native_image.h" #include "atom/common/native_mate_converters/callback.h" #include "chrome/browser/icon_loader.h" #include "chrome/browser/process_singleton.h" @@ -44,7 +45,7 @@ class App : public AtomBrowserClient::Delegate, public BrowserObserver, public content::GpuDataManagerObserver { public: - using FileIconCallback = base::Callback; + using FileIconCallback = base::Callback, const gfx::Image&)>; static mate::Handle Create(v8::Isolate* isolate); From 5794138ed43d76f25291428bf25f37dd6b12ed7b Mon Sep 17 00:00:00 2001 From: Yury Solovyov Date: Sat, 5 Nov 2016 21:55:23 +0300 Subject: [PATCH 025/925] Normalize path --- atom/browser/api/atom_api_app.cc | 7 +++++-- atom/browser/api/atom_api_app.h | 3 ++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index 7358960685..9beff98805 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -871,6 +871,8 @@ void App::GetFileIcon(const base::FilePath& path, IconLoader::IconSize icon_size; FileIconCallback callback; + base::FilePath normalized_path = path.NormalizePathSeparators(); + if (!args->GetNext(&options)) { icon_size = IconLoader::IconSize::NORMAL; } else { @@ -885,14 +887,15 @@ void App::GetFileIcon(const base::FilePath& path, } IconManager* icon_manager = IconManager::GetInstance(); - gfx::Image* icon = icon_manager->LookupIconFromFilepath(path, icon_size); + gfx::Image* icon = icon_manager->LookupIconFromFilepath(normalized_path, + icon_size); if (icon) { v8::Isolate* isolate = v8::Isolate::GetCurrent(); v8::Locker locker(isolate); v8::HandleScope handle_scope(isolate); callback.Run(v8::Null(isolate), *icon); } else { - icon_manager->LoadIcon(path, icon_size, + icon_manager->LoadIcon(normalized_path, icon_size, base::Bind(&OnIconDataAvailable, callback)); } } diff --git a/atom/browser/api/atom_api_app.h b/atom/browser/api/atom_api_app.h index b89761ede5..b051e4e5fa 100644 --- a/atom/browser/api/atom_api_app.h +++ b/atom/browser/api/atom_api_app.h @@ -45,7 +45,8 @@ class App : public AtomBrowserClient::Delegate, public BrowserObserver, public content::GpuDataManagerObserver { public: - using FileIconCallback = base::Callback, const gfx::Image&)>; + using FileIconCallback = base::Callback, + const gfx::Image&)>; static mate::Handle Create(v8::Isolate* isolate); From 11ef2c539b73250ebfdadd48d580532377756d23 Mon Sep 17 00:00:00 2001 From: Yury Solovyov Date: Sat, 5 Nov 2016 22:04:38 +0300 Subject: [PATCH 026/925] Update docs --- docs/api/app.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/api/app.md b/docs/api/app.md index 8398f4c0a7..8acccb126f 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -407,7 +407,8 @@ You can request the following paths by the name: * `options` Object (optional) * `size` String - Defaults to `normal`. Can be `small`, `normal`, `large` * `callback` Function - * `Icon` [NativeImage](native-image.md) + * `error` `Error` + * `icon` [NativeImage](native-image.md) ### `app.setPath(name, path)` From c2bf5bb986d1af57448b659a2ba9cb21211dbffd Mon Sep 17 00:00:00 2001 From: Yury Solovyov Date: Sun, 6 Nov 2016 12:58:01 +0300 Subject: [PATCH 027/925] Put locker and handle scope to the top of the function. Remove unneeded header --- atom/browser/api/atom_api_app.cc | 12 +++++++----- atom/browser/api/atom_api_app.h | 1 - 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index 9beff98805..7bb797468a 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -475,9 +475,10 @@ int ImportIntoCertStore( void OnIconDataAvailable(const App::FileIconCallback& callback, gfx::Image* icon) { v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::Locker locker(isolate); + v8::HandleScope handle_scope(isolate); + if (icon && !icon->IsEmpty()) { - v8::Locker locker(isolate); - v8::HandleScope handle_scope(isolate); callback.Run(v8::Null(isolate), *icon); } else { v8::Local error_message = @@ -871,6 +872,10 @@ void App::GetFileIcon(const base::FilePath& path, IconLoader::IconSize icon_size; FileIconCallback callback; + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::Locker locker(isolate); + v8::HandleScope handle_scope(isolate); + base::FilePath normalized_path = path.NormalizePathSeparators(); if (!args->GetNext(&options)) { @@ -890,9 +895,6 @@ void App::GetFileIcon(const base::FilePath& path, gfx::Image* icon = icon_manager->LookupIconFromFilepath(normalized_path, icon_size); if (icon) { - v8::Isolate* isolate = v8::Isolate::GetCurrent(); - v8::Locker locker(isolate); - v8::HandleScope handle_scope(isolate); callback.Run(v8::Null(isolate), *icon); } else { icon_manager->LoadIcon(normalized_path, icon_size, diff --git a/atom/browser/api/atom_api_app.h b/atom/browser/api/atom_api_app.h index b051e4e5fa..1d8e5804e5 100644 --- a/atom/browser/api/atom_api_app.h +++ b/atom/browser/api/atom_api_app.h @@ -12,7 +12,6 @@ #include "atom/browser/atom_browser_client.h" #include "atom/browser/browser.h" #include "atom/browser/browser_observer.h" -#include "atom/common/api/atom_api_native_image.h" #include "atom/common/native_mate_converters/callback.h" #include "chrome/browser/icon_loader.h" #include "chrome/browser/process_singleton.h" From 00748889f93455ac5f1401844da241dab01f0b58 Mon Sep 17 00:00:00 2001 From: Yury Solovyov Date: Sun, 6 Nov 2016 13:59:17 +0300 Subject: [PATCH 028/925] Add tests --- spec/api-app-spec.js | 58 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/spec/api-app-spec.js b/spec/api-app-spec.js index 085522a0cc..1dc9fae100 100644 --- a/spec/api-app-spec.js +++ b/spec/api-app-spec.js @@ -456,4 +456,62 @@ describe('app module', function () { assert.equal(app.isDefaultProtocolClient(protocol), false) }) }) + + describe('getFileIcon() API', function() { + const iconPath = path.join(process.cwd(), 'fixtures/assets/icon.ico') + const sizes = { + small: 16, + normal: 32, + large: 48 + }; + + it('fetches non-empty icon', function(done) { + app.getFileIcon(iconPath, function(err, icon) { + assert.equal(err, null) + assert.equal(icon.isEmpty(), false) + done() + }) + }) + + it('fetches normal size by default', function(done) { + app.getFileIcon(iconPath, function(err, icon) { + const size = icon.getSize() + assert.equal(size.height, sizes.normal) + assert.equal(size.width, sizes.normal) + done() + }) + }) + + describe('size option', function() { + it('fetches small icons', function(done) { + app.getFileIcon(iconPath, { size: 'small' }, function(err, icon) { + const size = icon.getSize() + assert.equal(size.height, sizes.small) + assert.equal(size.width, sizes.small) + done() + }) + }) + + it('fetches normal icons', function(done) { + app.getFileIcon(iconPath, { size: 'normal' }, function(err, icon) { + const size = icon.getSize() + assert.equal(size.height, sizes.normal) + assert.equal(size.width, sizes.normal) + done() + }) + }) + + it('fetches large icons', function(done) { + if (process.platform === 'darwin') { + done() // macOS does not support large icons + } + app.getFileIcon(iconPath, { size: 'normal' }, function(err, icon) { + const size = icon.getSize() + assert.equal(size.height, sizes.normal) + assert.equal(size.width, sizes.normal) + done() + }) + }) + }) + }) }) From 1aa4fcae08b9737187df9e0cdcc2c583bd7b80f1 Mon Sep 17 00:00:00 2001 From: Yury Solovyov Date: Sun, 6 Nov 2016 14:03:16 +0300 Subject: [PATCH 029/925] Lint tests --- spec/api-app-spec.js | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/spec/api-app-spec.js b/spec/api-app-spec.js index 1dc9fae100..9212e9711b 100644 --- a/spec/api-app-spec.js +++ b/spec/api-app-spec.js @@ -457,56 +457,60 @@ describe('app module', function () { }) }) - describe('getFileIcon() API', function() { + describe('getFileIcon() API', function () { const iconPath = path.join(process.cwd(), 'fixtures/assets/icon.ico') const sizes = { small: 16, normal: 32, large: 48 - }; + } - it('fetches non-empty icon', function(done) { - app.getFileIcon(iconPath, function(err, icon) { + it('fetches non-empty icon', function (done) { + app.getFileIcon(iconPath, function (err, icon) { assert.equal(err, null) assert.equal(icon.isEmpty(), false) done() }) }) - it('fetches normal size by default', function(done) { - app.getFileIcon(iconPath, function(err, icon) { + it('fetches normal size by default', function (done) { + app.getFileIcon(iconPath, function (err, icon) { const size = icon.getSize() + assert.equal(err, null) assert.equal(size.height, sizes.normal) assert.equal(size.width, sizes.normal) done() }) }) - describe('size option', function() { - it('fetches small icons', function(done) { - app.getFileIcon(iconPath, { size: 'small' }, function(err, icon) { + describe('size option', function () { + it('fetches small icons', function (done) { + app.getFileIcon(iconPath, { size: 'small' }, function (err, icon) { const size = icon.getSize() + assert.equal(err, null) assert.equal(size.height, sizes.small) assert.equal(size.width, sizes.small) done() }) }) - it('fetches normal icons', function(done) { - app.getFileIcon(iconPath, { size: 'normal' }, function(err, icon) { + it('fetches normal icons', function (done) { + app.getFileIcon(iconPath, { size: 'normal' }, function (err, icon) { const size = icon.getSize() + assert.equal(err, null) assert.equal(size.height, sizes.normal) assert.equal(size.width, sizes.normal) done() }) }) - it('fetches large icons', function(done) { + it('fetches large icons', function (done) { if (process.platform === 'darwin') { done() // macOS does not support large icons } - app.getFileIcon(iconPath, { size: 'normal' }, function(err, icon) { + app.getFileIcon(iconPath, { size: 'normal' }, function (err, icon) { const size = icon.getSize() + assert.equal(err, null) assert.equal(size.height, sizes.normal) assert.equal(size.width, sizes.normal) done() From c36cdb858025836938702e85b1dbcc8525b07cd5 Mon Sep 17 00:00:00 2001 From: Yury Solovyov Date: Sun, 6 Nov 2016 14:29:45 +0300 Subject: [PATCH 030/925] Properly skip large size test on macOS --- spec/api-app-spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/api-app-spec.js b/spec/api-app-spec.js index 9212e9711b..2508efe388 100644 --- a/spec/api-app-spec.js +++ b/spec/api-app-spec.js @@ -506,7 +506,7 @@ describe('app module', function () { it('fetches large icons', function (done) { if (process.platform === 'darwin') { - done() // macOS does not support large icons + return this.skip() // macOS does not support large icons } app.getFileIcon(iconPath, { size: 'normal' }, function (err, icon) { const size = icon.getSize() From 29452364f30a06d762c633f18f2f849b00cc1dd6 Mon Sep 17 00:00:00 2001 From: Yury Solovyov Date: Sun, 6 Nov 2016 15:21:21 +0300 Subject: [PATCH 031/925] Use isolate() method to get isolate --- atom/browser/api/atom_api_app.cc | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index 7bb797468a..47ce814c6e 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -472,9 +472,9 @@ int ImportIntoCertStore( } #endif -void OnIconDataAvailable(const App::FileIconCallback& callback, +void OnIconDataAvailable(v8::Isolate* isolate, + const App::FileIconCallback& callback, gfx::Image* icon) { - v8::Isolate* isolate = v8::Isolate::GetCurrent(); v8::Locker locker(isolate); v8::HandleScope handle_scope(isolate); @@ -872,9 +872,8 @@ void App::GetFileIcon(const base::FilePath& path, IconLoader::IconSize icon_size; FileIconCallback callback; - v8::Isolate* isolate = v8::Isolate::GetCurrent(); - v8::Locker locker(isolate); - v8::HandleScope handle_scope(isolate); + v8::Locker locker(isolate()); + v8::HandleScope handle_scope(isolate()); base::FilePath normalized_path = path.NormalizePathSeparators(); @@ -895,10 +894,11 @@ void App::GetFileIcon(const base::FilePath& path, gfx::Image* icon = icon_manager->LookupIconFromFilepath(normalized_path, icon_size); if (icon) { - callback.Run(v8::Null(isolate), *icon); + callback.Run(v8::Null(isolate()), *icon); } else { icon_manager->LoadIcon(normalized_path, icon_size, - base::Bind(&OnIconDataAvailable, callback)); + base::Bind(&OnIconDataAvailable, isolate(), + callback)); } } From 2b60df3d8bbb5b5ac1b8df2efc5b5ab8e225b319 Mon Sep 17 00:00:00 2001 From: Yury Solovyov Date: Sun, 6 Nov 2016 17:10:33 +0300 Subject: [PATCH 032/925] Fix some feedback --- docs/api/app.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/app.md b/docs/api/app.md index 8acccb126f..5bd12f361d 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -405,7 +405,7 @@ You can request the following paths by the name: * `path` String * `options` Object (optional) - * `size` String - Defaults to `normal`. Can be `small`, `normal`, `large` + * `size` String - Can be `small`, `normal`, `large`. Except for `large` on _macOS_ * `callback` Function * `error` `Error` * `icon` [NativeImage](native-image.md) From bcf0964c618637b24e54ecfe87d178fd1bc79a41 Mon Sep 17 00:00:00 2001 From: Yury Solovyov Date: Thu, 10 Nov 2016 21:34:30 +0300 Subject: [PATCH 033/925] Fix more review --- atom/browser/api/atom_api_app.cc | 2 +- docs/api/app.md | 8 +++++++- spec/api-app-spec.js | 8 ++++---- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index 47ce814c6e..904a63a5d0 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -886,7 +886,7 @@ void App::GetFileIcon(const base::FilePath& path, } if (!args->GetNext(&callback)) { - args->ThrowError(); + args->ThrowError("Missing required callback function"); return; } diff --git a/docs/api/app.md b/docs/api/app.md index 5bd12f361d..e825add47e 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -405,11 +405,17 @@ You can request the following paths by the name: * `path` String * `options` Object (optional) - * `size` String - Can be `small`, `normal`, `large`. Except for `large` on _macOS_ + * `size` String - Can be `small`, `normal`, `large`. The `large` size is not supported on _macOS_ * `callback` Function * `error` `Error` * `icon` [NativeImage](native-image.md) +Fetches associated icon for using OS rules for handling icons. +On _Windows_, there a 2 kinds of icons: +- icons associated with certain file extension - `.mp3`, `.png`, etc. +- icons inside file itself, like `.exe`, `.dll`, `.ico`. +On _Linux_ and _macOS_, icons depend on application associated with file mime type. + ### `app.setPath(name, path)` * `name` String diff --git a/spec/api-app-spec.js b/spec/api-app-spec.js index 2508efe388..f158cc851a 100644 --- a/spec/api-app-spec.js +++ b/spec/api-app-spec.js @@ -458,7 +458,7 @@ describe('app module', function () { }) describe('getFileIcon() API', function () { - const iconPath = path.join(process.cwd(), 'fixtures/assets/icon.ico') + const iconPath = path.join(__dirname, 'fixtures/assets/icon.ico') const sizes = { small: 16, normal: 32, @@ -484,7 +484,7 @@ describe('app module', function () { }) describe('size option', function () { - it('fetches small icons', function (done) { + it('fetches small icon', function (done) { app.getFileIcon(iconPath, { size: 'small' }, function (err, icon) { const size = icon.getSize() assert.equal(err, null) @@ -494,7 +494,7 @@ describe('app module', function () { }) }) - it('fetches normal icons', function (done) { + it('fetches normal icon', function (done) { app.getFileIcon(iconPath, { size: 'normal' }, function (err, icon) { const size = icon.getSize() assert.equal(err, null) @@ -504,7 +504,7 @@ describe('app module', function () { }) }) - it('fetches large icons', function (done) { + it('fetches large icon', function (done) { if (process.platform === 'darwin') { return this.skip() // macOS does not support large icons } From c810e64fdafa0f4de4bd5864bda337f5f3ea2904 Mon Sep 17 00:00:00 2001 From: Yury Solovyov Date: Thu, 10 Nov 2016 22:00:58 +0300 Subject: [PATCH 034/925] Fix doc lint --- docs/api/app.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/app.md b/docs/api/app.md index e825add47e..5285800551 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -407,7 +407,7 @@ You can request the following paths by the name: * `options` Object (optional) * `size` String - Can be `small`, `normal`, `large`. The `large` size is not supported on _macOS_ * `callback` Function - * `error` `Error` + * `error` Error * `icon` [NativeImage](native-image.md) Fetches associated icon for using OS rules for handling icons. From a9dae243b4df683c6cd07e0ee38364200e4a47c1 Mon Sep 17 00:00:00 2001 From: Yury Solovyov Date: Fri, 11 Nov 2016 09:32:41 +0300 Subject: [PATCH 035/925] Fix large icon spec --- spec/api-app-spec.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/api-app-spec.js b/spec/api-app-spec.js index f158cc851a..bf5e767d79 100644 --- a/spec/api-app-spec.js +++ b/spec/api-app-spec.js @@ -508,11 +508,11 @@ describe('app module', function () { if (process.platform === 'darwin') { return this.skip() // macOS does not support large icons } - app.getFileIcon(iconPath, { size: 'normal' }, function (err, icon) { + app.getFileIcon(iconPath, { size: 'large' }, function (err, icon) { const size = icon.getSize() assert.equal(err, null) - assert.equal(size.height, sizes.normal) - assert.equal(size.width, sizes.normal) + assert.equal(size.height, sizes.large) + assert.equal(size.width, sizes.large) done() }) }) From ee6677645038b10ca855ba5bc3aa197949e048b4 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 7 Feb 2017 10:15:46 -0800 Subject: [PATCH 036/925] Update IconManager for Chrome 56 upgrade --- chromium_src/chrome/browser/icon_manager.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chromium_src/chrome/browser/icon_manager.cc b/chromium_src/chrome/browser/icon_manager.cc index 17e362c534..95073fcdd7 100644 --- a/chromium_src/chrome/browser/icon_manager.cc +++ b/chromium_src/chrome/browser/icon_manager.cc @@ -27,7 +27,7 @@ IconManager* IconManager::GetInstance() { IconManager::IconManager() {} IconManager::~IconManager() { - STLDeleteValues(&icon_cache_); + base::STLDeleteValues(&icon_cache_); } gfx::Image* IconManager::LookupIconFromFilepath(const base::FilePath& file_name, From 683a758daba7f66884897f33fd54aa143ea6f337 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 7 Feb 2017 10:16:09 -0800 Subject: [PATCH 037/925] Call done instead of skip --- spec/api-app-spec.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/api-app-spec.js b/spec/api-app-spec.js index bf5e767d79..8d754bf65e 100644 --- a/spec/api-app-spec.js +++ b/spec/api-app-spec.js @@ -505,9 +505,9 @@ describe('app module', function () { }) it('fetches large icon', function (done) { - if (process.platform === 'darwin') { - return this.skip() // macOS does not support large icons - } + // macOS does not support large icons + if (process.platform === 'darwin') return done() + app.getFileIcon(iconPath, { size: 'large' }, function (err, icon) { const size = icon.getSize() assert.equal(err, null) From dddc6aec497d461eb8ab9c98bca650bd5079ce2d Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 7 Feb 2017 10:21:20 -0800 Subject: [PATCH 038/925] Tweak spec descriptions --- spec/api-app-spec.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/api-app-spec.js b/spec/api-app-spec.js index 8d754bf65e..71e1df8789 100644 --- a/spec/api-app-spec.js +++ b/spec/api-app-spec.js @@ -465,7 +465,7 @@ describe('app module', function () { large: 48 } - it('fetches non-empty icon', function (done) { + it('fetches a non-empty icon', function (done) { app.getFileIcon(iconPath, function (err, icon) { assert.equal(err, null) assert.equal(icon.isEmpty(), false) @@ -473,7 +473,7 @@ describe('app module', function () { }) }) - it('fetches normal size by default', function (done) { + it('fetches normal icon size by default', function (done) { app.getFileIcon(iconPath, function (err, icon) { const size = icon.getSize() assert.equal(err, null) @@ -484,7 +484,7 @@ describe('app module', function () { }) describe('size option', function () { - it('fetches small icon', function (done) { + it('fetches a small icon', function (done) { app.getFileIcon(iconPath, { size: 'small' }, function (err, icon) { const size = icon.getSize() assert.equal(err, null) @@ -494,7 +494,7 @@ describe('app module', function () { }) }) - it('fetches normal icon', function (done) { + it('fetches a normal icon', function (done) { app.getFileIcon(iconPath, { size: 'normal' }, function (err, icon) { const size = icon.getSize() assert.equal(err, null) @@ -504,7 +504,7 @@ describe('app module', function () { }) }) - it('fetches large icon', function (done) { + it('fetches a large icon', function (done) { // macOS does not support large icons if (process.platform === 'darwin') return done() From a4277bb61699a009c7a00197c61ba623d42c9559 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 7 Feb 2017 10:33:44 -0800 Subject: [PATCH 039/925] Document sizes --- docs/api/app.md | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/docs/api/app.md b/docs/api/app.md index 5285800551..6e159d1dc6 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -405,16 +405,23 @@ You can request the following paths by the name: * `path` String * `options` Object (optional) - * `size` String - Can be `small`, `normal`, `large`. The `large` size is not supported on _macOS_ + * `size` String + * `small` - 16x16 + * `normal` - 32x32 + * `large` - 48x48. This size is not supported on _macOS_. * `callback` Function * `error` Error * `icon` [NativeImage](native-image.md) -Fetches associated icon for using OS rules for handling icons. +Fetches a path's associated icon. + On _Windows_, there a 2 kinds of icons: -- icons associated with certain file extension - `.mp3`, `.png`, etc. -- icons inside file itself, like `.exe`, `.dll`, `.ico`. -On _Linux_ and _macOS_, icons depend on application associated with file mime type. + +- Icons associated with certain file extensions, like `.mp3`, `.png`, etc. +- Icons inside the file itself, like `.exe`, `.dll`, `.ico`. + +On _Linux_ and _macOS_, icons depend on the application associated with file +mime type. ### `app.setPath(name, path)` From fc1b7431c758e88a5f9cd0b99f7f97fc35fe6d20 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 7 Feb 2017 10:35:31 -0800 Subject: [PATCH 040/925] Tweak optional syntax --- docs/api/app.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/app.md b/docs/api/app.md index 6e159d1dc6..25a609cb24 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -401,7 +401,7 @@ You can request the following paths by the name: * `videos` Directory for a user's videos. * `pepperFlashSystemPlugin` Full path to the system version of the Pepper Flash plugin. -### `app.getFileIcon(path, [options], callback)` +### `app.getFileIcon(path[, options], callback)` * `path` String * `options` Object (optional) From 82ac4ddf19a8cafcb5cce43a47b58e0da3c93087 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 7 Feb 2017 11:20:27 -0800 Subject: [PATCH 041/925] Large is only 48x48 on Linux --- docs/api/app.md | 2 +- spec/api-app-spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api/app.md b/docs/api/app.md index 25a609cb24..d34daeddde 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -408,7 +408,7 @@ You can request the following paths by the name: * `size` String * `small` - 16x16 * `normal` - 32x32 - * `large` - 48x48. This size is not supported on _macOS_. + * `large` - 48x48 on _Linux_, 32x32 on _Windows_, unsupported on _macOS_. * `callback` Function * `error` Error * `icon` [NativeImage](native-image.md) diff --git a/spec/api-app-spec.js b/spec/api-app-spec.js index 71e1df8789..9bfd77173c 100644 --- a/spec/api-app-spec.js +++ b/spec/api-app-spec.js @@ -462,7 +462,7 @@ describe('app module', function () { const sizes = { small: 16, normal: 32, - large: 48 + large: process.platform === 'win32' ? 32 : 48 } it('fetches a non-empty icon', function (done) { From ba03f040173332ebe2f0752ee9ddb9c6827de0ca Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 7 Feb 2017 18:24:35 -0800 Subject: [PATCH 042/925] Disable getFileIcon specs on Linux CI --- spec/api-app-spec.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/spec/api-app-spec.js b/spec/api-app-spec.js index 9bfd77173c..301d6ae4bf 100644 --- a/spec/api-app-spec.js +++ b/spec/api-app-spec.js @@ -9,6 +9,8 @@ const {closeWindow} = require('./window-helpers') const {app, BrowserWindow, ipcMain} = remote +const isCI = remote.getGlobal('isCi') + describe('electron module', function () { it('does not expose internal modules to require', function () { assert.throws(function () { @@ -458,6 +460,9 @@ describe('app module', function () { }) describe('getFileIcon() API', function () { + // FIXME Get these specs running on Linux CI + if (process.platform === 'linux' && isCI) return + const iconPath = path.join(__dirname, 'fixtures/assets/icon.ico') const sizes = { small: 16, From 0748f4fef21297940edfbf32b2865135b9b52eb4 Mon Sep 17 00:00:00 2001 From: dengyaolong Date: Wed, 8 Feb 2017 12:38:08 +0800 Subject: [PATCH 043/925] complete download-item translation docs --- docs-translations/zh-CN/api/download-item.md | 28 +++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/docs-translations/zh-CN/api/download-item.md b/docs-translations/zh-CN/api/download-item.md index 3e53bbf6ca..d35605bbb1 100644 --- a/docs-translations/zh-CN/api/download-item.md +++ b/docs-translations/zh-CN/api/download-item.md @@ -37,15 +37,20 @@ win.webContents.session.on('will-download', (event, item, webContents) => { ### 事件: 'updated' -当`downloadItem`获得更新时触发。 +* `event` Event +* `state` String + * `progressing` - 下载中。 + * `interrupted` - 下载被中断且可恢复。 + +当`downloadItem`获得更新且未终止时触发。 ### 事件: 'done' * `event` Event * `state` String - * `completed` - 下载成功完成。 + * `completed` - 下载成功。 * `cancelled` - 下载被取消。 - * `interrupted` - 与文件服务器错误的中断连接。 + * `interrupted` - 下载被中断且不可恢复。 当下载处于一个终止状态时触发。这包括了一个完成的下载,一个被取消的下载(via `downloadItem.cancel()`), 和一个被意外中断的下载(无法恢复)。 @@ -65,10 +70,18 @@ win.webContents.session.on('will-download', (event, item, webContents) => { 暂停下载。 +### `downloadItem.isPause()` + +返回一个`Boolean`表示是否暂定下载。 + ### `downloadItem.resume()` 恢复被暂停的下载。 +### `downloadItem.canResume()` + +返回一个`Boolean`表示是否可以恢复被暂停的下载。 + ### `downloadItem.cancel()` 取消下载操作。 @@ -103,3 +116,12 @@ win.webContents.session.on('will-download', (event, item, webContents) => { ### `downloadItem.getContentDisposition()` 以`String`形式返回响应头(response header)中的`Content-Disposition`域。 + +### `downloadItem.getState()` + +以`String`形式返回该下载项的目前状态。 + +* `progressing` - 下载中。 +* `completed` - 下载成功。 +* `cancelled` - 下载被取消。 +* `interrupted` - 下载被中断。 From 148898fb757ef5ebd165f82e12fa95c2a3d04c27 Mon Sep 17 00:00:00 2001 From: Gary Wilber Date: Tue, 7 Feb 2017 23:03:42 -0800 Subject: [PATCH 044/925] Implement invalidate for non-offscreen mode --- atom/browser/api/atom_api_web_contents.cc | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 9d5c8f8684..3605f806ae 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -1489,13 +1489,20 @@ int WebContents::GetFrameRate() const { } void WebContents::Invalidate() { - if (!IsOffScreen()) - return; - - auto* osr_rwhv = static_cast( - web_contents()->GetRenderWidgetHostView()); - if (osr_rwhv) - osr_rwhv->Invalidate(); + if (IsOffScreen()) { + auto* osr_rwhv = static_cast( + web_contents()->GetRenderWidgetHostView()); + if (osr_rwhv) + osr_rwhv->Invalidate(); + } + else { + const auto ownerWindow = owner_window(); + const auto nativeWindow = ownerWindow ? ownerWindow->GetNativeWindow() : nullptr; + if (nativeWindow) { + const gfx::Rect& bounds = nativeWindow->bounds(); + nativeWindow->SchedulePaintInRect(gfx::Rect(0, 0, bounds.width(), bounds.height())); + } + } } v8::Local WebContents::GetWebPreferences(v8::Isolate* isolate) { From ed44b32ff58348275b3e7c2d4888e9ddf31aa4d2 Mon Sep 17 00:00:00 2001 From: Gary Wilber Date: Tue, 7 Feb 2017 23:08:03 -0800 Subject: [PATCH 045/925] Convert indentation to spaces --- atom/browser/api/atom_api_web_contents.cc | 28 +++++++++++------------ 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 3605f806ae..a5384f83d2 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -1489,20 +1489,20 @@ int WebContents::GetFrameRate() const { } void WebContents::Invalidate() { - if (IsOffScreen()) { - auto* osr_rwhv = static_cast( - web_contents()->GetRenderWidgetHostView()); - if (osr_rwhv) - osr_rwhv->Invalidate(); - } - else { - const auto ownerWindow = owner_window(); - const auto nativeWindow = ownerWindow ? ownerWindow->GetNativeWindow() : nullptr; - if (nativeWindow) { - const gfx::Rect& bounds = nativeWindow->bounds(); - nativeWindow->SchedulePaintInRect(gfx::Rect(0, 0, bounds.width(), bounds.height())); - } - } + if (IsOffScreen()) { + auto* osr_rwhv = static_cast( + web_contents()->GetRenderWidgetHostView()); + if (osr_rwhv) + osr_rwhv->Invalidate(); + } + else { + const auto ownerWindow = owner_window(); + const auto nativeWindow = ownerWindow ? ownerWindow->GetNativeWindow() : nullptr; + if (nativeWindow) { + const gfx::Rect& bounds = nativeWindow->bounds(); + nativeWindow->SchedulePaintInRect(gfx::Rect(0, 0, bounds.width(), bounds.height())); + } + } } v8::Local WebContents::GetWebPreferences(v8::Isolate* isolate) { From 11e1f6b56c15a7ea1edf964bbbc27e8c2e734627 Mon Sep 17 00:00:00 2001 From: Gary Wilber Date: Wed, 8 Feb 2017 00:05:16 -0800 Subject: [PATCH 046/925] Fix lint errors --- atom/browser/api/atom_api_web_contents.cc | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index a5384f83d2..5ae6186a04 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -1494,13 +1494,14 @@ void WebContents::Invalidate() { web_contents()->GetRenderWidgetHostView()); if (osr_rwhv) osr_rwhv->Invalidate(); - } - else { + } else { const auto ownerWindow = owner_window(); - const auto nativeWindow = ownerWindow ? ownerWindow->GetNativeWindow() : nullptr; + const auto nativeWindow = ownerWindow ? ownerWindow->GetNativeWindow() : + nullptr; if (nativeWindow) { const gfx::Rect& bounds = nativeWindow->bounds(); - nativeWindow->SchedulePaintInRect(gfx::Rect(0, 0, bounds.width(), bounds.height())); + nativeWindow->SchedulePaintInRect( + gfx::Rect(0, 0, bounds.width(), bounds.height())); } } } From 84f8bfbdefb3307b0488898be1a1de5d0c1ef1f9 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 7 Feb 2017 08:48:01 -0800 Subject: [PATCH 047/925] Add spec for registering/requiring Coffeescript --- spec/fixtures/module/test.coffee | 1 + spec/modules-spec.js | 9 +++++++++ 2 files changed, 10 insertions(+) create mode 100644 spec/fixtures/module/test.coffee diff --git a/spec/fixtures/module/test.coffee b/spec/fixtures/module/test.coffee new file mode 100644 index 0000000000..6552b3396f --- /dev/null +++ b/spec/fixtures/module/test.coffee @@ -0,0 +1 @@ +module.exports = yes diff --git a/spec/modules-spec.js b/spec/modules-spec.js index 92771b2f53..0bbc5290c5 100644 --- a/spec/modules-spec.js +++ b/spec/modules-spec.js @@ -48,6 +48,15 @@ describe('third-party module', function () { }) }) + describe('coffee-script', function () { + it('can be registered and used to require .coffee files', function () { + assert.doesNotThrow(function () { + require('coffee-script').register() + assert.strictEqual(require('./fixtures/module/test.coffee'), true) + }) + }) + }) + describe('global variables', function () { describe('process', function () { it('can be declared in a module', function () { From 090255c3f43fc1d721edba8d8b404f387d89e3e1 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 7 Feb 2017 08:52:11 -0800 Subject: [PATCH 048/925] Upgrade node for require wrapper `this` fix --- vendor/node | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/node b/vendor/node index be4f9967b2..b64908216a 160000 --- a/vendor/node +++ b/vendor/node @@ -1 +1 @@ -Subproject commit be4f9967b2c5e8ce78647f37d44928e028e9f750 +Subproject commit b64908216a6d5f84862b2b947985a513a6521cb3 From c1773a2c9412cc9b2b67a739e5dc17b09440d50a Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 7 Feb 2017 08:55:32 -0800 Subject: [PATCH 049/925] Add coffee-script dependency to specs --- spec/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/package.json b/spec/package.json index 46aa8404c6..1211b3ea8b 100644 --- a/spec/package.json +++ b/spec/package.json @@ -5,6 +5,7 @@ "version": "0.1.0", "devDependencies": { "basic-auth": "^1.0.4", + "coffee-script": "^1.12.3", "graceful-fs": "^4.1.9", "mkdirp": "^0.5.1", "mocha": "^3.1.0", From e9226aa3541adfa52e9aa54d64d842c158cf1af0 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 8 Feb 2017 09:02:46 -0800 Subject: [PATCH 050/925] Assert test.coffee outside of doesNotThrow --- spec/modules-spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/modules-spec.js b/spec/modules-spec.js index 0bbc5290c5..b8c02a2e3e 100644 --- a/spec/modules-spec.js +++ b/spec/modules-spec.js @@ -52,8 +52,8 @@ describe('third-party module', function () { it('can be registered and used to require .coffee files', function () { assert.doesNotThrow(function () { require('coffee-script').register() - assert.strictEqual(require('./fixtures/module/test.coffee'), true) }) + assert.strictEqual(require('./fixtures/module/test.coffee'), true) }) }) From e29b64a18ae03df38beec6550d28ad64d57987b1 Mon Sep 17 00:00:00 2001 From: Greg Nolle Date: Wed, 9 Nov 2016 13:05:46 +0000 Subject: [PATCH 051/925] modify CertVerifier Class * respond to multiple similar verification requests. * accept net error result as callback response. --- atom/browser/api/atom_api_session.cc | 2 +- atom/browser/net/atom_cert_verifier.cc | 167 +++++++++++++++++++++---- atom/browser/net/atom_cert_verifier.h | 22 +++- docs/api/session.md | 8 +- lib/browser/api/session.js | 21 +++- lib/browser/rpc-server.js | 1 + lib/renderer/api/remote.js | 3 +- spec/api-session-spec.js | 8 +- 8 files changed, 199 insertions(+), 33 deletions(-) diff --git a/atom/browser/api/atom_api_session.cc b/atom/browser/api/atom_api_session.cc index 45bb9c2cf3..e3b55cb37c 100644 --- a/atom/browser/api/atom_api_session.cc +++ b/atom/browser/api/atom_api_session.cc @@ -738,7 +738,7 @@ void Session::BuildPrototype(v8::Isolate* isolate, .SetMethod("setDownloadPath", &Session::SetDownloadPath) .SetMethod("enableNetworkEmulation", &Session::EnableNetworkEmulation) .SetMethod("disableNetworkEmulation", &Session::DisableNetworkEmulation) - .SetMethod("setCertificateVerifyProc", &Session::SetCertVerifyProc) + .SetMethod("_setCertificateVerifyProc", &Session::SetCertVerifyProc) .SetMethod("setPermissionRequestHandler", &Session::SetPermissionRequestHandler) .SetMethod("clearHostResolverCache", &Session::ClearHostResolverCache) diff --git a/atom/browser/net/atom_cert_verifier.cc b/atom/browser/net/atom_cert_verifier.cc index 61c7439e27..ec7b8755fc 100644 --- a/atom/browser/net/atom_cert_verifier.cc +++ b/atom/browser/net/atom_cert_verifier.cc @@ -7,6 +7,9 @@ #include "atom/browser/browser.h" #include "atom/browser/net/atom_ct_delegate.h" #include "atom/common/native_mate_converters/net_converter.h" +#include "base/containers/linked_list.h" +#include "base/memory/ptr_util.h" +#include "base/memory/weak_ptr.h" #include "content/public/browser/browser_thread.h" #include "net/base/net_errors.h" #include "net/cert/cert_verify_result.h" @@ -19,17 +22,119 @@ namespace atom { namespace { -void OnResult( - net::CertVerifyResult* verify_result, - const net::CompletionCallback& callback, - bool result) { - BrowserThread::PostTask( - BrowserThread::IO, FROM_HERE, - base::Bind(callback, result ? net::OK : net::ERR_FAILED)); -} +class Response : public base::LinkNode { + public: + Response(net::CertVerifyResult* verify_result, + const net::CompletionCallback& callback) + : verify_result_(verify_result), callback_(callback) {} + net::CertVerifyResult* verify_result() { return verify_result_; } + net::CompletionCallback callback() { return callback_; } + + private: + net::CertVerifyResult* verify_result_; + net::CompletionCallback callback_; + + DISALLOW_COPY_AND_ASSIGN(Response); +}; } // namespace +class CertVerifierRequest : public AtomCertVerifier::Request { + public: + CertVerifierRequest(const AtomCertVerifier::RequestParams& params, + AtomCertVerifier* cert_verifier) + : params_(params), + cert_verifier_(cert_verifier), + error_(net::ERR_IO_PENDING), + custom_response_(net::ERR_IO_PENDING), + first_response_(true), + weak_ptr_factory_(this) {} + + ~CertVerifierRequest() { + cert_verifier_->RemoveRequest(params_); + default_verifier_request_.reset(); + while (!response_list_.empty() && !first_response_) { + base::LinkNode* response_node = response_list_.head(); + response_node->RemoveFromList(); + Response* response = response_node->value(); + RunResponse(response); + } + cert_verifier_ = nullptr; + weak_ptr_factory_.InvalidateWeakPtrs(); + } + + void RunResponse(Response* response) { + if (custom_response_ == net::ERR_ABORTED) { + *(response->verify_result()) = result_; + response->callback().Run(error_); + } else { + response->verify_result()->Reset(); + response->verify_result()->verified_cert = params_.certificate(); + cert_verifier_->ct_delegate()->AddCTExcludedHost(params_.hostname()); + response->callback().Run(custom_response_); + } + delete response; + } + + void Start(net::CRLSet* crl_set, + const net::BoundNetLog& net_log) { + int error = cert_verifier_->default_verifier()->Verify( + params_, crl_set, &result_, + base::Bind(&CertVerifierRequest::OnDefaultVerificationDone, + weak_ptr_factory_.GetWeakPtr()), + &default_verifier_request_, net_log); + if (error != net::ERR_IO_PENDING) + OnDefaultVerificationDone(error); + } + + void OnDefaultVerificationDone(int error) { + error_ = error; + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(cert_verifier_->verify_proc(), params_.hostname(), + params_.certificate(), net::ErrorToString(error), + base::Bind(&CertVerifierRequest::OnResponseInUI, + weak_ptr_factory_.GetWeakPtr()))); + } + + void OnResponseInUI(int result) { + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, + base::Bind(&CertVerifierRequest::NotifyResponseInIO, + weak_ptr_factory_.GetWeakPtr(), result)); + } + + void NotifyResponseInIO(int result) { + custom_response_ = result; + first_response_ = false; + // Responding to first request in the list will initiate destruction of + // the class, respond to others in the list inside destructor. + base::LinkNode* response_node = response_list_.head(); + response_node->RemoveFromList(); + Response* response = response_node->value(); + RunResponse(response); + } + + void AddResponseListener(net::CertVerifyResult* verify_result, + const net::CompletionCallback& callback) { + response_list_.Append(new Response(verify_result, callback)); + } + + const AtomCertVerifier::RequestParams& params() const { return params_; } + + private: + using ResponseList = base::LinkedList; + + const AtomCertVerifier::RequestParams params_; + AtomCertVerifier* cert_verifier_; + int error_; + int custom_response_; + bool first_response_; + ResponseList response_list_; + net::CertVerifyResult result_; + std::unique_ptr default_verifier_request_; + base::WeakPtrFactory weak_ptr_factory_; +}; + AtomCertVerifier::AtomCertVerifier(AtomCTDelegate* ct_delegate) : default_cert_verifier_(net::CertVerifier::CreateDefault()), ct_delegate_(ct_delegate) {} @@ -51,23 +156,43 @@ int AtomCertVerifier::Verify( if (verify_proc_.is_null()) { ct_delegate_->ClearCTExcludedHostsList(); - return default_cert_verifier_->Verify( - params, crl_set, verify_result, callback, out_req, net_log); + return default_cert_verifier_->Verify(params, crl_set, verify_result, + callback, out_req, net_log); + } else { + CertVerifierRequest* request = FindRequest(params); + if (!request) { + out_req->reset(); + std::unique_ptr new_request = + base::MakeUnique(params, this); + new_request->Start(crl_set, net_log); + request = new_request.get(); + *out_req = std::move(new_request); + inflight_requests_[params] = request; + } + request->AddResponseListener(verify_result, callback); + + return net::ERR_IO_PENDING; } - - verify_result->Reset(); - verify_result->verified_cert = params.certificate(); - ct_delegate_->AddCTExcludedHost(params.hostname()); - - BrowserThread::PostTask( - BrowserThread::UI, FROM_HERE, - base::Bind(verify_proc_, params.hostname(), params.certificate(), - base::Bind(OnResult, verify_result, callback))); - return net::ERR_IO_PENDING; } bool AtomCertVerifier::SupportsOCSPStapling() { - return true; + if (verify_proc_.is_null()) + return default_cert_verifier_->SupportsOCSPStapling(); + return false; +} + +void AtomCertVerifier::RemoveRequest(const RequestParams& params) { + auto it = inflight_requests_.find(params); + if (it != inflight_requests_.end()) + inflight_requests_.erase(it); +} + +CertVerifierRequest* AtomCertVerifier::FindRequest( + const RequestParams& params) { + auto it = inflight_requests_.find(params); + if (it != inflight_requests_.end()) + return it->second; + return nullptr; } } // namespace atom diff --git a/atom/browser/net/atom_cert_verifier.h b/atom/browser/net/atom_cert_verifier.h index e321f7d3d9..ff60908ee6 100644 --- a/atom/browser/net/atom_cert_verifier.h +++ b/atom/browser/net/atom_cert_verifier.h @@ -5,6 +5,7 @@ #ifndef ATOM_BROWSER_NET_ATOM_CERT_VERIFIER_H_ #define ATOM_BROWSER_NET_ATOM_CERT_VERIFIER_H_ +#include #include #include @@ -13,19 +14,26 @@ namespace atom { class AtomCTDelegate; +class CertVerifierRequest; class AtomCertVerifier : public net::CertVerifier { public: explicit AtomCertVerifier(AtomCTDelegate* ct_delegate); virtual ~AtomCertVerifier(); - using VerifyProc = - base::Callback, - const base::Callback&)>; + using VerifyProc = base::Callback, + const std::string& default_result, + const net::CompletionCallback&)>; void SetVerifyProc(const VerifyProc& proc); + const VerifyProc verify_proc() const { return verify_proc_; } + AtomCTDelegate* ct_delegate() const { return ct_delegate_; } + net::CertVerifier* default_verifier() const { + return default_cert_verifier_.get(); + } + protected: // net::CertVerifier: int Verify(const RequestParams& params, @@ -37,6 +45,12 @@ class AtomCertVerifier : public net::CertVerifier { bool SupportsOCSPStapling() override; private: + friend class CertVerifierRequest; + + void RemoveRequest(const RequestParams& params); + CertVerifierRequest* FindRequest(const RequestParams& params); + + std::map inflight_requests_; VerifyProc verify_proc_; std::unique_ptr default_cert_verifier_; AtomCTDelegate* ct_delegate_; diff --git a/docs/api/session.md b/docs/api/session.md index 58de08d39a..5155fabaed 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -252,8 +252,14 @@ the original network configuration. * `proc` Function * `hostname` String * `certificate` [Certificate](structures/certificate.md) + * `error` String - Verification result from chromium. * `callback` Function - * `isTrusted` Boolean - Determines if the certificate should be trusted + * `verificationResult` Integer - Value can be one of certificate error codes + from [here](https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h). + Apart from the certificate error codes, the following special codes can be used. + * `0` - Indicates success and disables Certificate Transperancy verification. + * `-2` - Indicates failure. + * `-3` - Uses the verification result from chromium. Sets the certificate verify proc for `session`, the `proc` will be called with `proc(hostname, certificate, callback)` whenever a server certificate diff --git a/lib/browser/api/session.js b/lib/browser/api/session.js index ac47244db3..bf25029952 100644 --- a/lib/browser/api/session.js +++ b/lib/browser/api/session.js @@ -1,5 +1,5 @@ const {EventEmitter} = require('events') -const {app} = require('electron') +const {app, deprecate} = require('electron') const {fromPartition, Session, Cookies} = process.atomBinding('session') // Public API. @@ -20,3 +20,22 @@ Object.setPrototypeOf(Cookies.prototype, EventEmitter.prototype) Session.prototype._init = function () { app.emit('session-created', this) } + +// Remove after 2.0 +Session.prototype.setCertificateVerifyProc = function (verifyProc) { + if (!verifyProc) { + this._setCertificateVerifyProc(null) + return + } + if (verifyProc.length <= 3) { + deprecate.warn('setCertificateVerifyproc(hostname, certificate, callback)', + 'setCertificateVerifyproc(hostname, certificate, error, callback)') + this._setCertificateVerifyProc((hostname, certificate, error, cb) => { + verifyProc(hostname, certificate, (result) => { + cb(result ? 0 : -2) + }) + }) + } else { + this._setCertificateVerifyProc(verifyProc) + } +} diff --git a/lib/browser/rpc-server.js b/lib/browser/rpc-server.js index 1dac516459..2cafc93439 100644 --- a/lib/browser/rpc-server.js +++ b/lib/browser/rpc-server.js @@ -222,6 +222,7 @@ const unwrapArgs = function (sender, args) { removeRemoteListenersAndLogWarning(meta, args, callIntoRenderer) } } + Object.defineProperty(callIntoRenderer, 'length', { value: meta.length }) v8Util.setRemoteCallbackFreer(callIntoRenderer, meta.id, sender) rendererFunctions.set(objectId, callIntoRenderer) diff --git a/lib/renderer/api/remote.js b/lib/renderer/api/remote.js index c3c8e3b89c..8258a91a66 100644 --- a/lib/renderer/api/remote.js +++ b/lib/renderer/api/remote.js @@ -79,7 +79,8 @@ const wrapArgs = function (args, visited) { return { type: 'function', id: callbacksRegistry.add(value), - location: v8Util.getHiddenValue(value, 'location') + location: v8Util.getHiddenValue(value, 'location'), + length: value.length } } else { return { diff --git a/spec/api-session-spec.js b/spec/api-session-spec.js index 1f31ff7123..6b907b9bf2 100644 --- a/spec/api-session-spec.js +++ b/spec/api-session-spec.js @@ -557,8 +557,8 @@ describe('session module', function () { }) it('accepts the request when the callback is called with true', function (done) { - session.defaultSession.setCertificateVerifyProc(function (hostname, certificate, callback) { - callback(true) + session.defaultSession.setCertificateVerifyProc(function (hostname, certificate, error, callback) { + callback(0) }) w.webContents.once('did-finish-load', function () { @@ -569,7 +569,7 @@ describe('session module', function () { }) it('rejects the request when the callback is called with false', function (done) { - session.defaultSession.setCertificateVerifyProc(function (hostname, certificate, callback) { + session.defaultSession.setCertificateVerifyProc(function (hostname, certificate, error, callback) { assert.equal(hostname, '127.0.0.1') assert.equal(certificate.issuerName, 'Intermediate CA') assert.equal(certificate.subjectName, 'localhost') @@ -580,7 +580,7 @@ describe('session module', function () { assert.equal(certificate.issuerCert.issuerCert.issuer.commonName, 'Root CA') assert.equal(certificate.issuerCert.issuerCert.subject.commonName, 'Root CA') assert.equal(certificate.issuerCert.issuerCert.issuerCert, undefined) - callback(false) + callback(-2) }) var url = `https://127.0.0.1:${server.address().port}` From 37db8040991c3c3238b0524ff33b43f29625b96f Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 7 Feb 2017 13:58:05 -0800 Subject: [PATCH 052/925] Use NetLogWithSource since BoundNetLog no longer exists --- atom/browser/net/atom_cert_verifier.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atom/browser/net/atom_cert_verifier.cc b/atom/browser/net/atom_cert_verifier.cc index ec7b8755fc..b5382dd623 100644 --- a/atom/browser/net/atom_cert_verifier.cc +++ b/atom/browser/net/atom_cert_verifier.cc @@ -77,7 +77,7 @@ class CertVerifierRequest : public AtomCertVerifier::Request { } void Start(net::CRLSet* crl_set, - const net::BoundNetLog& net_log) { + const net::NetLogWithSource& net_log) { int error = cert_verifier_->default_verifier()->Verify( params_, crl_set, &result_, base::Bind(&CertVerifierRequest::OnDefaultVerificationDone, From 6b56dfd94b9dccb88b0e58ae03dacd5079cdb995 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 7 Feb 2017 15:24:49 -0800 Subject: [PATCH 053/925] Add spec for remote callback length --- spec/api-ipc-spec.js | 7 +++++++ spec/fixtures/module/function-with-args.js | 3 +++ 2 files changed, 10 insertions(+) create mode 100644 spec/fixtures/module/function-with-args.js diff --git a/spec/api-ipc-spec.js b/spec/api-ipc-spec.js index b1ca29c6a1..372855619d 100644 --- a/spec/api-ipc-spec.js +++ b/spec/api-ipc-spec.js @@ -85,6 +85,13 @@ describe('ipc module', function () { assert.equal(foo.baz(), 123) }) + it('includes the length of functions specified as arguments', function () { + var a = remote.require(path.join(fixtures, 'module', 'function-with-args.js')) + assert.equal(a(function (a, b, c, d, f) {}), 5) + assert.equal(a((a) => {}), 1) + assert.equal(a((...args) => {}), 0) + }) + it('handles circular references in arrays and objects', function () { var a = remote.require(path.join(fixtures, 'module', 'circular.js')) diff --git a/spec/fixtures/module/function-with-args.js b/spec/fixtures/module/function-with-args.js new file mode 100644 index 0000000000..ed636e5988 --- /dev/null +++ b/spec/fixtures/module/function-with-args.js @@ -0,0 +1,3 @@ +module.exports = function (cb) { + return cb.length +} From 9c134e7bf39c955f610f7633374f76bbc34bd899 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 7 Feb 2017 15:39:18 -0800 Subject: [PATCH 054/925] Assert certificate error --- spec/api-session-spec.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/api-session-spec.js b/spec/api-session-spec.js index 6b907b9bf2..a8773ca629 100644 --- a/spec/api-session-spec.js +++ b/spec/api-session-spec.js @@ -558,6 +558,7 @@ describe('session module', function () { it('accepts the request when the callback is called with true', function (done) { session.defaultSession.setCertificateVerifyProc(function (hostname, certificate, error, callback) { + assert.equal(error, 'net::ERR_CERT_AUTHORITY_INVALID') callback(0) }) @@ -580,6 +581,7 @@ describe('session module', function () { assert.equal(certificate.issuerCert.issuerCert.issuer.commonName, 'Root CA') assert.equal(certificate.issuerCert.issuerCert.subject.commonName, 'Root CA') assert.equal(certificate.issuerCert.issuerCert.issuerCert, undefined) + assert.equal(error, 'net::ERR_CERT_AUTHORITY_INVALID') callback(-2) }) From 5245d42d15482d3cc0b4b0dc06d4768cf6e08c01 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 7 Feb 2017 15:44:56 -0800 Subject: [PATCH 055/925] Only document deprecation for now --- docs/tutorial/planned-breaking-changes.md | 13 +++++++++++++ lib/browser/api/session.js | 12 +++--------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/docs/tutorial/planned-breaking-changes.md b/docs/tutorial/planned-breaking-changes.md index e2d0e3a939..a9ffd981b1 100644 --- a/docs/tutorial/planned-breaking-changes.md +++ b/docs/tutorial/planned-breaking-changes.md @@ -91,6 +91,19 @@ process.versions.electron read-only properties for consistency with the other `process.versions` properties set by Node. +## `session` + +```js +// Deprecated +ses.setCertificateVerifyProc(function (hostname, certificate, callback) { + callback(true) +}) +// Replace with +ses.setCertificateVerifyProc(function (hostname, certificate, error, callback) { + callback(0) +}) +``` + ## `Tray` ```js diff --git a/lib/browser/api/session.js b/lib/browser/api/session.js index bf25029952..8c1c856258 100644 --- a/lib/browser/api/session.js +++ b/lib/browser/api/session.js @@ -1,5 +1,5 @@ const {EventEmitter} = require('events') -const {app, deprecate} = require('electron') +const {app} = require('electron') const {fromPartition, Session, Cookies} = process.atomBinding('session') // Public API. @@ -21,15 +21,9 @@ Session.prototype._init = function () { app.emit('session-created', this) } -// Remove after 2.0 Session.prototype.setCertificateVerifyProc = function (verifyProc) { - if (!verifyProc) { - this._setCertificateVerifyProc(null) - return - } - if (verifyProc.length <= 3) { - deprecate.warn('setCertificateVerifyproc(hostname, certificate, callback)', - 'setCertificateVerifyproc(hostname, certificate, error, callback)') + if (verifyProc != null && verifyProc.length <= 3) { + // TODO(kevinsawicki): Remove in 2.0, deprecate before then with warnings this._setCertificateVerifyProc((hostname, certificate, error, cb) => { verifyProc(hostname, certificate, (result) => { cb(result ? 0 : -2) From 70178adb6e6d10b2083b720a3902e676af1bd50a Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 7 Feb 2017 16:35:37 -0800 Subject: [PATCH 056/925] Use object for verification request --- atom/browser/api/atom_api_session.cc | 12 ++++++++++++ atom/browser/net/atom_cert_verifier.cc | 8 ++++++-- atom/browser/net/atom_cert_verifier.h | 10 +++++++--- docs/api/session.md | 13 +++++++------ docs/tutorial/planned-breaking-changes.md | 2 +- lib/browser/api/session.js | 4 ++-- spec/api-session-spec.js | 21 +++++++++++++++++---- 7 files changed, 52 insertions(+), 18 deletions(-) diff --git a/atom/browser/api/atom_api_session.cc b/atom/browser/api/atom_api_session.cc index e3b55cb37c..44166b553b 100644 --- a/atom/browser/api/atom_api_session.cc +++ b/atom/browser/api/atom_api_session.cc @@ -204,6 +204,18 @@ struct Converter { } }; +template<> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + atom::VerifyRequest val) { + mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate); + dict.Set("hostname", val.hostname); + dict.Set("certificate", val.certificate); + dict.Set("verificationResult", val.default_result); + return dict.GetHandle(); + } +}; + } // namespace mate namespace atom { diff --git a/atom/browser/net/atom_cert_verifier.cc b/atom/browser/net/atom_cert_verifier.cc index b5382dd623..7ac003eadf 100644 --- a/atom/browser/net/atom_cert_verifier.cc +++ b/atom/browser/net/atom_cert_verifier.cc @@ -89,10 +89,14 @@ class CertVerifierRequest : public AtomCertVerifier::Request { void OnDefaultVerificationDone(int error) { error_ = error; + VerifyRequest request = { + params_.hostname(), + net::ErrorToString(error), + params_.certificate() + }; BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, - base::Bind(cert_verifier_->verify_proc(), params_.hostname(), - params_.certificate(), net::ErrorToString(error), + base::Bind(cert_verifier_->verify_proc(), request, base::Bind(&CertVerifierRequest::OnResponseInUI, weak_ptr_factory_.GetWeakPtr()))); } diff --git a/atom/browser/net/atom_cert_verifier.h b/atom/browser/net/atom_cert_verifier.h index ff60908ee6..b2346553b9 100644 --- a/atom/browser/net/atom_cert_verifier.h +++ b/atom/browser/net/atom_cert_verifier.h @@ -16,14 +16,18 @@ namespace atom { class AtomCTDelegate; class CertVerifierRequest; +struct VerifyRequest { + std::string hostname; + std::string default_result; + scoped_refptr certificate; +}; + class AtomCertVerifier : public net::CertVerifier { public: explicit AtomCertVerifier(AtomCTDelegate* ct_delegate); virtual ~AtomCertVerifier(); - using VerifyProc = base::Callback, - const std::string& default_result, + using VerifyProc = base::Callback; void SetVerifyProc(const VerifyProc& proc); diff --git a/docs/api/session.md b/docs/api/session.md index 5155fabaed..d0814dfdb3 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -250,9 +250,10 @@ the original network configuration. #### `ses.setCertificateVerifyProc(proc)` * `proc` Function - * `hostname` String - * `certificate` [Certificate](structures/certificate.md) - * `error` String - Verification result from chromium. + * `request` Object + * `hostname` String + * `certificate` [Certificate](structures/certificate.md) + * `error` String - Verification result from chromium. * `callback` Function * `verificationResult` Integer - Value can be one of certificate error codes from [here](https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h). @@ -262,9 +263,9 @@ the original network configuration. * `-3` - Uses the verification result from chromium. Sets the certificate verify proc for `session`, the `proc` will be called with -`proc(hostname, certificate, callback)` whenever a server certificate -verification is requested. Calling `callback(true)` accepts the certificate, -calling `callback(false)` rejects it. +`proc(request, callback)` whenever a server certificate +verification is requested. Calling `callback(0)` accepts the certificate, +calling `callback(-2)` rejects it. Calling `setCertificateVerifyProc(null)` will revert back to default certificate verify proc. diff --git a/docs/tutorial/planned-breaking-changes.md b/docs/tutorial/planned-breaking-changes.md index a9ffd981b1..57505d0b66 100644 --- a/docs/tutorial/planned-breaking-changes.md +++ b/docs/tutorial/planned-breaking-changes.md @@ -99,7 +99,7 @@ ses.setCertificateVerifyProc(function (hostname, certificate, callback) { callback(true) }) // Replace with -ses.setCertificateVerifyProc(function (hostname, certificate, error, callback) { +ses.setCertificateVerifyProc(function (request, callback) { callback(0) }) ``` diff --git a/lib/browser/api/session.js b/lib/browser/api/session.js index 8c1c856258..33f3b47dee 100644 --- a/lib/browser/api/session.js +++ b/lib/browser/api/session.js @@ -22,9 +22,9 @@ Session.prototype._init = function () { } Session.prototype.setCertificateVerifyProc = function (verifyProc) { - if (verifyProc != null && verifyProc.length <= 3) { + if (verifyProc != null && verifyProc.length > 2) { // TODO(kevinsawicki): Remove in 2.0, deprecate before then with warnings - this._setCertificateVerifyProc((hostname, certificate, error, cb) => { + this._setCertificateVerifyProc(({hostname, certificate, verificationResult}, cb) => { verifyProc(hostname, certificate, (result) => { cb(result ? 0 : -2) }) diff --git a/spec/api-session-spec.js b/spec/api-session-spec.js index a8773ca629..4d6fc64c4d 100644 --- a/spec/api-session-spec.js +++ b/spec/api-session-spec.js @@ -557,8 +557,8 @@ describe('session module', function () { }) it('accepts the request when the callback is called with true', function (done) { - session.defaultSession.setCertificateVerifyProc(function (hostname, certificate, error, callback) { - assert.equal(error, 'net::ERR_CERT_AUTHORITY_INVALID') + session.defaultSession.setCertificateVerifyProc(function ({hostname, certificate, verificationResult}, callback) { + assert.equal(verificationResult, 'net::ERR_CERT_AUTHORITY_INVALID') callback(0) }) @@ -569,8 +569,21 @@ describe('session module', function () { w.loadURL(`https://127.0.0.1:${server.address().port}`) }) + it('supports the old function signature', function (done) { + session.defaultSession.setCertificateVerifyProc(function (hostname, certificate, callback) { + assert.equal(hostname, '127.0.0.1') + callback(true) + }) + + w.webContents.once('did-finish-load', function () { + assert.equal(w.webContents.getTitle(), 'hello') + done() + }) + w.loadURL(`https://127.0.0.1:${server.address().port}`) + }) + it('rejects the request when the callback is called with false', function (done) { - session.defaultSession.setCertificateVerifyProc(function (hostname, certificate, error, callback) { + session.defaultSession.setCertificateVerifyProc(function ({hostname, certificate, verificationResult}, callback) { assert.equal(hostname, '127.0.0.1') assert.equal(certificate.issuerName, 'Intermediate CA') assert.equal(certificate.subjectName, 'localhost') @@ -581,7 +594,7 @@ describe('session module', function () { assert.equal(certificate.issuerCert.issuerCert.issuer.commonName, 'Root CA') assert.equal(certificate.issuerCert.issuerCert.subject.commonName, 'Root CA') assert.equal(certificate.issuerCert.issuerCert.issuerCert, undefined) - assert.equal(error, 'net::ERR_CERT_AUTHORITY_INVALID') + assert.equal(verificationResult, 'net::ERR_CERT_AUTHORITY_INVALID') callback(-2) }) From 1e581d68149eaa4101bcc3714d46ae3959bae531 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 8 Feb 2017 10:34:07 -0800 Subject: [PATCH 057/925] Use unique_ptr for passing VerifyRequestParams --- atom/browser/api/atom_api_session.cc | 4 ++-- atom/browser/net/atom_cert_verifier.cc | 23 +++++++++++++++-------- atom/browser/net/atom_cert_verifier.h | 4 ++-- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/atom/browser/api/atom_api_session.cc b/atom/browser/api/atom_api_session.cc index 44166b553b..0c850888e8 100644 --- a/atom/browser/api/atom_api_session.cc +++ b/atom/browser/api/atom_api_session.cc @@ -205,9 +205,9 @@ struct Converter { }; template<> -struct Converter { +struct Converter { static v8::Local ToV8(v8::Isolate* isolate, - atom::VerifyRequest val) { + atom::VerifyRequestParams val) { mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate); dict.Set("hostname", val.hostname); dict.Set("certificate", val.certificate); diff --git a/atom/browser/net/atom_cert_verifier.cc b/atom/browser/net/atom_cert_verifier.cc index 7ac003eadf..5dee107eb3 100644 --- a/atom/browser/net/atom_cert_verifier.cc +++ b/atom/browser/net/atom_cert_verifier.cc @@ -89,16 +89,23 @@ class CertVerifierRequest : public AtomCertVerifier::Request { void OnDefaultVerificationDone(int error) { error_ = error; - VerifyRequest request = { - params_.hostname(), - net::ErrorToString(error), - params_.certificate() - }; + std::unique_ptr request(new VerifyRequestParams()); + request->hostname = params_.hostname(); + request->default_result = net::ErrorToString(error); + request->certificate = params_.certificate(); BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, - base::Bind(cert_verifier_->verify_proc(), request, - base::Bind(&CertVerifierRequest::OnResponseInUI, - weak_ptr_factory_.GetWeakPtr()))); + base::Bind(&CertVerifierRequest::OnVerifyRequestInUI, + weak_ptr_factory_.GetWeakPtr(), + cert_verifier_->verify_proc(), + base::Passed(&request))); + } + + void OnVerifyRequestInUI(const AtomCertVerifier::VerifyProc& verify_proc, + std::unique_ptr request) { + verify_proc.Run(*(request.get()), + base::Bind(&CertVerifierRequest::OnResponseInUI, + weak_ptr_factory_.GetWeakPtr())); } void OnResponseInUI(int result) { diff --git a/atom/browser/net/atom_cert_verifier.h b/atom/browser/net/atom_cert_verifier.h index b2346553b9..09fa0f2778 100644 --- a/atom/browser/net/atom_cert_verifier.h +++ b/atom/browser/net/atom_cert_verifier.h @@ -16,7 +16,7 @@ namespace atom { class AtomCTDelegate; class CertVerifierRequest; -struct VerifyRequest { +struct VerifyRequestParams { std::string hostname; std::string default_result; scoped_refptr certificate; @@ -27,7 +27,7 @@ class AtomCertVerifier : public net::CertVerifier { explicit AtomCertVerifier(AtomCTDelegate* ct_delegate); virtual ~AtomCertVerifier(); - using VerifyProc = base::Callback; void SetVerifyProc(const VerifyProc& proc); From 18e15a1e53cc9efc1b5e99295c84a0ca3a1b130d Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 8 Feb 2017 12:51:24 -0800 Subject: [PATCH 058/925] Add spec for rejecting using old signature --- spec/api-session-spec.js | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/spec/api-session-spec.js b/spec/api-session-spec.js index 4d6fc64c4d..f8e0e27dc3 100644 --- a/spec/api-session-spec.js +++ b/spec/api-session-spec.js @@ -569,17 +569,33 @@ describe('session module', function () { w.loadURL(`https://127.0.0.1:${server.address().port}`) }) - it('supports the old function signature', function (done) { - session.defaultSession.setCertificateVerifyProc(function (hostname, certificate, callback) { - assert.equal(hostname, '127.0.0.1') - callback(true) + describe('deprecated function signature', function () { + it('supports accepting the request', function (done) { + session.defaultSession.setCertificateVerifyProc(function (hostname, certificate, callback) { + assert.equal(hostname, '127.0.0.1') + callback(true) + }) + + w.webContents.once('did-finish-load', function () { + assert.equal(w.webContents.getTitle(), 'hello') + done() + }) + w.loadURL(`https://127.0.0.1:${server.address().port}`) }) - w.webContents.once('did-finish-load', function () { - assert.equal(w.webContents.getTitle(), 'hello') - done() + it('supports rejecting the request', function (done) { + session.defaultSession.setCertificateVerifyProc(function (hostname, certificate, callback) { + assert.equal(hostname, '127.0.0.1') + callback(false) + }) + + var url = `https://127.0.0.1:${server.address().port}` + w.webContents.once('did-finish-load', function () { + assert.equal(w.webContents.getTitle(), url) + done() + }) + w.loadURL(url) }) - w.loadURL(`https://127.0.0.1:${server.address().port}`) }) it('rejects the request when the callback is called with false', function (done) { From 22b9c6e53960b5d6c3def62a0d71af5208fc9933 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 8 Feb 2017 13:33:07 -0800 Subject: [PATCH 059/925] Allow multiple verification results --- spec/api-session-spec.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/api-session-spec.js b/spec/api-session-spec.js index f8e0e27dc3..1a7bf3538b 100644 --- a/spec/api-session-spec.js +++ b/spec/api-session-spec.js @@ -556,9 +556,9 @@ describe('session module', function () { server.close() }) - it('accepts the request when the callback is called with true', function (done) { + it('accepts the request when the callback is called with 0', function (done) { session.defaultSession.setCertificateVerifyProc(function ({hostname, certificate, verificationResult}, callback) { - assert.equal(verificationResult, 'net::ERR_CERT_AUTHORITY_INVALID') + assert(['net::ERR_CERT_AUTHORITY_INVALID', 'net::ERR_CERT_COMMON_NAME_INVALID'].includes(verificationResult), verificationResult) callback(0) }) @@ -598,7 +598,7 @@ describe('session module', function () { }) }) - it('rejects the request when the callback is called with false', function (done) { + it('rejects the request when the callback is called with -2', function (done) { session.defaultSession.setCertificateVerifyProc(function ({hostname, certificate, verificationResult}, callback) { assert.equal(hostname, '127.0.0.1') assert.equal(certificate.issuerName, 'Intermediate CA') @@ -610,7 +610,7 @@ describe('session module', function () { assert.equal(certificate.issuerCert.issuerCert.issuer.commonName, 'Root CA') assert.equal(certificate.issuerCert.issuerCert.subject.commonName, 'Root CA') assert.equal(certificate.issuerCert.issuerCert.issuerCert, undefined) - assert.equal(verificationResult, 'net::ERR_CERT_AUTHORITY_INVALID') + assert(['net::ERR_CERT_AUTHORITY_INVALID', 'net::ERR_CERT_COMMON_NAME_INVALID'].includes(verificationResult), verificationResult) callback(-2) }) From 5d35d2476dfc43f117bad55838541f29fffe55d0 Mon Sep 17 00:00:00 2001 From: Weiqiang Lin <23leo@163.com> Date: Thu, 9 Feb 2017 15:24:29 +0800 Subject: [PATCH 060/925] window.open Function to zh-cn --- docs-translations/zh-CN/api/window-open.md | 57 ++++++---------------- 1 file changed, 15 insertions(+), 42 deletions(-) diff --git a/docs-translations/zh-CN/api/window-open.md b/docs-translations/zh-CN/api/window-open.md index 069e2c3522..3ef1b59f1a 100644 --- a/docs-translations/zh-CN/api/window-open.md +++ b/docs-translations/zh-CN/api/window-open.md @@ -1,11 +1,13 @@ # `window.open` 函数 -当在界面中使用 `window.open` 来创建一个新的窗口时候,将会创建一个 `BrowserWindow` 的实例,并且将返回一个标识,这个界面通过标识来对这个新的窗口进行有限的控制. +> 通过链接打开一个新窗口。 -这个标识对传统的web界面来说,通过它能对子窗口进行有限的功能性兼容控制. -想要完全的控制这个窗口,可以直接创建一个 `BrowserWindow` . +当在界面中使用 `window.open` 来创建一个新的窗口时候,将会创建一个 `BrowserWindow` 的实例,并且将返回一个标识,这个界面通过标识来对这个新的窗口进行有限的控制。 -新创建的 `BrowserWindow` 默认为继承父窗口的属性参数,想重写属性的话可以在 `features` 中设置他们. +这个标识对传统的web界面来说,通过它能对子窗口进行有限的功能性兼容控制。 +想要完全的控制这个窗口,可以直接创建一个 `BrowserWindow`。 + +新创建的 `BrowserWindow` 默认为继承父窗口的属性参数,想重写属性的话可以在 `features` 中设置他们。 ### `window.open(url[, frameName][, features])` @@ -13,48 +15,19 @@ * `frameName` String (可选) * `features` String (可选) -创建一个新的window并且返回一个 `BrowserWindowProxy` 类的实例. +创建一个新的 window 并且返回一个 [`BrowserWindowProxy`](browser-window-proxy.md) 类的实例。 - `features` 遵循标准浏览器的格式,但是每个feature 应该作为 `BrowserWindow` 参数的一个字段. + `features` 遵循标准浏览器的格式,但是每个 feature 应该作为 `BrowserWindow` 参数的一个字段。 + +**注意:** +* 如果在父窗口禁用 Node integration,那么在新打开的 `window` 中将始终禁用。 +* 非标准功能(不由 Chromium 或 Electron 处理)的 +   `features` 将被传递给任何注册的 `webContent` 的 `new-window` 事件 +   在 `additionalFeatures` 参数的处理程序。 ### `window.opener.postMessage(message, targetOrigin)` * `message` String * `targetOrigin` String -通过指定位置或用 `*` 来代替没有明确位置来向父窗口发送信息. - -## Class: BrowserWindowProxy - -`BrowserWindowProxy` 由`window.open` 创建返回,并且提供了对子窗口的有限功能性控制. - -### `BrowserWindowProxy.blur()` - -子窗口的失去焦点. -### `BrowserWindowProxy.close()` - -强行关闭子窗口,忽略卸载事件. - -### `BrowserWindowProxy.closed` - -在子窗口关闭之后恢复正常. - -### `BrowserWindowProxy.eval(code)` - -* `code` String - -评估子窗口的代码. - -### `BrowserWindowProxy.focus()` - -子窗口获得焦点(让其显示在最前). - -### `BrowserWindowProxy.postMessage(message, targetOrigin)` - -* `message` String -* `targetOrigin` String - - -通过指定位置或用 `*` 来代替没有明确位置来向子窗口发送信息. - -除了这些方法,子窗口还可以无特性和使用单一方法来实现 `window.opener` 对象. \ No newline at end of file +通过指定位置或用 `*` 来代替没有明确位置来向父窗口发送信息。 From 6adf1640e589e243d23b3e87b2efafca5cf39b3b Mon Sep 17 00:00:00 2001 From: Weiqiang Lin <23leo@163.com> Date: Thu, 9 Feb 2017 15:51:48 +0800 Subject: [PATCH 061/925] autoUpdater to zh-CN --- docs-translations/zh-CN/api/auto-updater.md | 51 ++++++++++++++++++--- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/docs-translations/zh-CN/api/auto-updater.md b/docs-translations/zh-CN/api/auto-updater.md index 845b4c24d9..75a816484b 100644 --- a/docs-translations/zh-CN/api/auto-updater.md +++ b/docs-translations/zh-CN/api/auto-updater.md @@ -1,6 +1,20 @@ # autoUpdater -这个模块提供了一个到 `Squirrel` 自动更新框架的接口。 +> 使应用程序能够自动更新。 + +进程: [Main](../glossary.md#main-process) + +`autoUpdater` 模块提供了来自 +[Squirrel](https://github.com/Squirrel) 框架的一个接口。 + +你可以快速启动多平台发布服务器来分发您的 +应用程序通过使用下列项目中的一个: + +- [nuts][nuts]: *针对你的应用程序的智能发布服务器,使用 GitHub 作为后端。 使用 Squirrel 自动更新(Mac 和 Windows)* +- [electron-release-server][electron-release-server]: *一个功能齐全, + electron 应用的自托管发布服务器,兼容 auto-updater* +- [squirrel-updates-server][squirrel-updates-server]: *对于使用 GitHub 版本的 Squirrel.Mac 和 Squirrel.Windows 的一个简单的 node.js 服务器* +- [squirrel-release-server][squirrel-release-server]: *一个简单的 Squirrel.Windows 的 PHP 应用程序,它从文件夹读取更新,并支持增量更新* ## 平台相关的提示 @@ -9,11 +23,21 @@ ### macOS 在 macOS 上,`autoUpdater` 模块依靠的是内置的 [Squirrel.Mac][squirrel-mac],这意味着你不需要依靠其他的设置就能使用。关于 -更新服务器的配置,你可以通过阅读 [Server Support][server-support] 这篇文章来了解。 +更新服务器的配置,你可以通过阅读 [Server Support][server-support] 这篇文章来了解。注意 [App +Transport Security](https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW35) (ATS) 适用于所有请求作为的一部分 +更新过程。需要禁用 ATS 的应用程序可以在应用程序的 plist 添加 +`NSAllowsArbitraryLoads` 属性。 + +**注意:** 你的应用程序必须签署 macOS 自动更新。 +这是 `Squirrel.Mac` 的要求。 ### Windows -在 Windows 上,你必须使用安装程序将你的应用装到用户的计算机上,所以比较推荐的方法是用 [grunt-electron-installer][installer] 这个模块来自动生成一个 Windows 安装向导。 +在 Windows 上,你必须使用安装程序将你的应用装到用户的计算机上,所以比较推荐的方法是用 [electron-winstaller][installer-lib], [electron-builder][electron-builder-lib] 或者 [grunt-electron-installer][installer] 模块来自动生成一个 Windows 安装向导。 + +当使用 [electron-winstaller][installer-lib] 或者 [electron-builder][electron-builder-lib] + +当使用 [electron-winstaller][installer-lib] 或者 [electron-builder][electron-builder-lib] 确保你不尝试更新你的应用程序 [the first time it runs](https://github.com/electron/windows-installer#handling-squirrel-events)(另见 [this issue for more info](https://github.com/electron/electron/issues/7155))。 建议使用 [electron-squirrel-startup](https://github.com/mongodb-js/electron-squirrel-startup) 获取应用程序的桌面快捷方式。 Squirrel 自动生成的安装向导会生成一个带 [Application User Model ID][app-user-model-id] 的快捷方式。 Application User Model ID 的格式是 `com.squirrel.PACKAGE_ID.YOUR_EXE_WITHOUT_DOT_EXE`, 比如 @@ -67,11 +91,16 @@ Linux 下没有任何的自动更新支持,所以我们推荐用各个 Linux `autoUpdater` 对象有以下的方法: -### `autoUpdater.setFeedURL(url)` +### `autoUpdater.setFeedURL(url[, requestHeaders])` * `url` String +* `requestHeaders` Object _macOS_ (optional) - HTTP请求头。 -设置检查更新的 `url`,并且初始化自动更新。这个 `url` 一旦设置就无法更改。 +设置检查更新的 `url`,并且初始化自动更新。 + +### `autoUpdater.getFeedURL()` + +返回 `String` - 当前更新提要 URL。 ### `autoUpdater.checkForUpdates()` @@ -81,8 +110,18 @@ Linux 下没有任何的自动更新支持,所以我们推荐用各个 Linux 在下载完成后,重启当前的应用并且安装更新。这个方法应该仅在 `update-downloaded` 事件触发后被调用。 +**注意:** `autoUpdater.quitAndInstall()` 将先关闭所有应用程序窗口 +并且只在 `app` 上发出 `before-quit` 事件。这不同于 +从正常退出的事件序列。 + [squirrel-mac]: https://github.com/Squirrel/Squirrel.Mac [server-support]: https://github.com/Squirrel/Squirrel.Mac#server-support [squirrel-windows]: https://github.com/Squirrel/Squirrel.Windows -[installer]: https://github.com/atom/grunt-electron-installer +[installer]: https://github.com/electron/grunt-electron-installer +[installer-lib]: https://github.com/electron/windows-installer +[electron-builder-lib]: https://github.com/electron-userland/electron-builder [app-user-model-id]: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx +[electron-release-server]: https://github.com/ArekSredzki/electron-release-server +[squirrel-updates-server]: https://github.com/Aluxian/squirrel-updates-server +[nuts]: https://github.com/GitbookIO/nuts +[squirrel-release-server]: https://github.com/Arcath/squirrel-release-server From 90492df1cbbe1ad8535c4b4befada4ee1b354a25 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 8 Feb 2017 14:24:56 +0900 Subject: [PATCH 062/925] Fix exception when executing create-dist directly --- script/create-dist.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/script/create-dist.py b/script/create-dist.py index f777d7a6ee..4aa67eacd6 100755 --- a/script/create-dist.py +++ b/script/create-dist.py @@ -132,9 +132,13 @@ def copy_license(): shutil.copy2(os.path.join(SOURCE_ROOT, 'LICENSE'), DIST_DIR) def create_api_json_schema(): + node_bin_dir = os.path.join(SOURCE_ROOT, 'node_modules', '.bin') + env = os.environ.copy() + env['PATH'] = os.path.pathsep.join([node_bin_dir, env['PATH']]) outfile = os.path.relpath(os.path.join(DIST_DIR, 'electron-api.json')) execute(['electron-docs-linter', 'docs', '--outfile={0}'.format(outfile), - '--version={}'.format(ELECTRON_VERSION.replace('v', ''))]) + '--version={}'.format(ELECTRON_VERSION.replace('v', ''))], + env=env) def strip_binaries(): for binary in TARGET_BINARIES[PLATFORM]: From a3c5eafb16bf905c690b0fe347e1ec7fe4fa53f7 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 8 Feb 2017 14:28:34 +0900 Subject: [PATCH 063/925] Update libchromiumcontent to include debug symbols --- script/lib/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/lib/config.py b/script/lib/config.py index e6d149353c..96c6591a54 100644 --- a/script/lib/config.py +++ b/script/lib/config.py @@ -9,7 +9,7 @@ import sys BASE_URL = os.getenv('LIBCHROMIUMCONTENT_MIRROR') or \ 'https://s3.amazonaws.com/github-janky-artifacts/libchromiumcontent' LIBCHROMIUMCONTENT_COMMIT = os.getenv('LIBCHROMIUMCONTENT_COMMIT') or \ - 'e0da1e9caa7c8f3da3519963a9ea32abba43c7c8' + 'ea20b8dfe0a7fad61bb4917404950ddcd2224588' PLATFORM = { 'cygwin': 'win32', From f949f83a578ed9ed6c53f936cdd9446fbb322a30 Mon Sep 17 00:00:00 2001 From: Weiqiang Lin <23leo@163.com> Date: Thu, 9 Feb 2017 16:17:38 +0800 Subject: [PATCH 064/925] BrowserWindow to zh-CN --- docs-translations/zh-CN/api/browser-window.md | 119 ++++++++++++++++-- 1 file changed, 107 insertions(+), 12 deletions(-) diff --git a/docs-translations/zh-CN/api/browser-window.md b/docs-translations/zh-CN/api/browser-window.md index 9faa908503..2487133c4f 100644 --- a/docs-translations/zh-CN/api/browser-window.md +++ b/docs-translations/zh-CN/api/browser-window.md @@ -1,32 +1,122 @@ # BrowserWindow - `BrowserWindow` 类让你有创建一个浏览器窗口的权力。例如: +> 创建和控制浏览器窗口。 + +进程: [Main](../glossary.md#main-process) ```javascript // In the main process. -const BrowserWindow = require('electron').BrowserWindow +const {BrowserWindow} = require('electron') -// Or in the renderer process. -// const BrowserWindow = require('electron').remote.BrowserWindow +// Or use `remote` from the renderer process. +// const {BrowserWindow} = require('electron').remote -var win = new BrowserWindow({ width: 800, height: 600, show: false }) -win.on('closed', function () { +let win = new BrowserWindow({width: 800, height: 600}) +win.on('closed', () => { win = null }) +// Load a remote URL win.loadURL('https://github.com') -win.show() + +// Or load a local HTML file +win.loadURL(`file://${__dirname}/app/index.html`) ``` -你也可以不通过chrome创建窗口,使用 -[Frameless Window](frameless-window.md) API. +## Frameless window + +不通过chrome创建窗口,或者一个任意形状的透明窗口, +你可以使用 [Frameless Window](frameless-window.md) API。 + +## Showing window gracefully + +When loading a page in the window directly, users may see the page load incrementally, which is not a good experience for a native app. To make the window display +without visual flash, there are two solutions for different situations. + +## 优雅地显示窗口 + +当在窗口中直接加载页面时,用户可能会看到增量加载页面,这不是一个好的原生应用程序的体验。使窗口显示 +没有可视闪烁,有两种解决方案适用于不同的情况。 + +### 使用 `ready-to-show` 事件 + +在加载页面时,进程第一次完成绘制时,渲染器会发出 `ready-to-show` 事件 +,在此事件后显示窗口将没有可视闪烁: + +```javascript +const {BrowserWindow} = require('electron') +let win = new BrowserWindow({show: false}) +win.once('ready-to-show', () => { + win.show() +}) +``` + +这个事件通常发生在 `did-finish-load` 事件之后,但是 +页面有许多远程资源,它可能会在 `did-finish-load` 之前发出 +事件。 + +### 设置 `backgroundColor` + +对于一个复杂的应用程序,`ready-to-show` 事件可能发出太晚,使 +应用程序感觉缓慢。在这种情况下,建议立即显示窗口, +并使用接近应用程序的背景 `backgroundColor`: + +```javascript +const {BrowserWindow} = require('electron') + +let win = new BrowserWindow({backgroundColor: '#2e2c29'}) +win.loadURL('https://github.com') +``` + +请注意,即使是使用 `ready-to-show` 事件的应用程序,仍建议使用 +设置 `backgroundColor` 使应用程序感觉更原生。 + +## Parent 和 child 窗口 + +使用 `parent` 选项,你可以创建 child 窗口: + +```javascript +const {BrowserWindow} = require('electron') + +let top = new BrowserWindow() +let child = new BrowserWindow({parent: top}) +child.show() +top.show() +``` + +`child` 窗口将总是显示在 `top` 窗口的顶部。 + +### Modal 窗口 + +模态窗口是禁用父窗口的子窗口,以创建模态 +窗口,你必须设置 `parent` 和 `modal` 选项: + +```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', () => { + child.show() +}) +``` + +### 平台通知 + +* 在 macOS 上,modal 窗口将显示为附加到父窗口的工作表。 +* 在 macOS 上,子窗口将保持与父窗口的相对位置 +   当父窗口移动时,而在 Windows 和 Linux 子窗口不会 +   移动。 +* 在Windows上,不支持动态更改父窗口。 +* 在Linux上,模态窗口的类型将更改为 `dialog`。 +* 在Linux上,许多桌面环境不支持隐藏模态窗口。 ## Class: BrowserWindow `BrowserWindow` 是一个 -[EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter). +[EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter)。 -通过 `options` 可以创建一个具有本质属性的 `BrowserWindow` . +通过 `options` 可以创建一个具有原生属性的 `BrowserWindow`。 ### `new BrowserWindow([options])` @@ -748,4 +838,9 @@ windows上句柄类型为 `HWND` ,macOS `NSView*` , Linux `Window`. 忽略窗口的所有鼠标事件. -[blink-feature-string]: https://code.google.com/p/chromium/codesearch#chromium/src/out/Debug/gen/blink/platform/RuntimeEnabledFeatures.cpp&sq=package:chromium&type=cs&l=527 + +[blink-feature-string]: https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.in +[quick-look]: https://en.wikipedia.org/wiki/Quick_Look +[vibrancy-docs]: https://developer.apple.com/reference/appkit/nsvisualeffectview?language=objc +[window-levels]: https://developer.apple.com/reference/appkit/nswindow/1664726-window_levels +[chrome-content-scripts]: https://developer.chrome.com/extensions/content_scripts#execution-environment From db4ad8c6925df63b0fde2774f64092b31ad1021d Mon Sep 17 00:00:00 2001 From: Weiqiang Lin <23leo@163.com> Date: Thu, 9 Feb 2017 16:57:41 +0800 Subject: [PATCH 065/925] contentTracing to zh-CN --- .../zh-CN/api/content-tracing.md | 81 +++++++++++-------- 1 file changed, 47 insertions(+), 34 deletions(-) diff --git a/docs-translations/zh-CN/api/content-tracing.md b/docs-translations/zh-CN/api/content-tracing.md index f30a616b3f..6b675d09a7 100644 --- a/docs-translations/zh-CN/api/content-tracing.md +++ b/docs-translations/zh-CN/api/content-tracing.md @@ -1,29 +1,38 @@ # contentTracing -`content-tracing` 模块是用来收集由底层的Chromium content 模块 产生的搜索数据. 这个模块不具备web接口,所有需要我们在chrome浏览器中添加 `chrome://tracing/` 来加载生成文件从而查看结果. +> 从 Chromium 的 content 模块收集跟踪数据,以查找性能 +瓶颈和运行缓慢的原因。 + +进程: [Main](../glossary.md#main-process) + +这个模块不具备web接口,所有需要我们在 Chrome 浏览器中打开 `chrome://tracing/` 来加载生成文件从而查看结果。 + +**注意:** 你不应该使用这个模块,直到应用程序发出 `ready` 事件。 ```javascript -const contentTracing = require('electron').contentTracing +const {app, contentTracing} = require('electron') -const options = { - categoryFilter: '*', - traceOptions: 'record-until-full,enable-sampling' -} +app.on('ready', () => { + const options = { + categoryFilter: '*', + traceOptions: 'record-until-full,enable-sampling' + } -contentTracing.startRecording(options, function () { - console.log('Tracing started') + contentTracing.startRecording(options, () => { + console.log('Tracing started') - setTimeout(function () { - contentTracing.stopRecording('', function (path) { - console.log('Tracing data recorded to ' + path) - }) - }, 5000) + setTimeout(() => { + contentTracing.stopRecording('', (path) => { + console.log('Tracing data recorded to ' + path) + }) + }, 5000) + }) }) ``` ## 方法 - `content-tracing` 模块的方法如下: + `content-tracing` 模块的方法如下: ### `contentTracing.getCategories(callback)` @@ -31,7 +40,7 @@ contentTracing.startRecording(options, function () { 获得一组分类组. 分类组可以更改为新的代码路径。 -一旦所有的子进程都接受到了`getCategories`方法请求, 分类组将调用 `callback`. +一旦所有的子进程都接受到了 `getCategories` 方法请求, 分类组将调用 `callback`。 ### `contentTracing.startRecording(options, callback)` @@ -40,11 +49,11 @@ contentTracing.startRecording(options, function () { * `traceOptions` String * `callback` Function -开始向所有进程进行记录.(recording) +开始向所有进程进行记录。 -一旦收到可以开始记录的请求,记录将会立马启动并且在子进程是异步记录听的. 当所有的子进程都收到 `startRecording` 请求的时候,`callback` 将会被调用. +一旦收到可以开始记录的请求,记录将会立马启动并且在子进程是异步记录听的. 当所有的子进程都收到 `startRecording` 请求的时候,`callback` 将会被调用。 -`categoryFilter`是一个过滤器,它用来控制那些分类组应该被用来查找.过滤器应当有一个可选的 `-` 前缀来排除匹配的分类组.不允许同一个列表既是包含又是排斥. +`categoryFilter`是一个过滤器,它用来控制那些分类组应该被用来查找.过滤器应当有一个可选的 `-` 前缀来排除匹配的分类组。不允许同一个列表既是包含又是排斥。 例子: @@ -52,7 +61,7 @@ contentTracing.startRecording(options, function () { * `test_MyTest*,test_OtherStuff`, * `"-excluded_category1,-excluded_category2` -`traceOptions` 控制着哪种查找应该被启动,这是一个用逗号分隔的列表.可用参数如下: +`traceOptions` 控制着哪种查找应该被启动,这是一个用逗号分隔的列表。可用参数如下: * `record-until-full` * `record-continuously` @@ -60,25 +69,26 @@ contentTracing.startRecording(options, function () { * `enable-sampling` * `enable-systrace` -前3个参数是来查找记录模块,并且以后都互斥.如果在`traceOptions` 中超过一个跟踪 +前3个参数是来查找记录模块,并且以后都互斥。如果在 `traceOptions` 中超过一个跟踪 记录模式,那最后一个的优先级最高.如果没有指明跟踪 -记录模式,那么它默认为 `record-until-full`. +记录模式,那么它默认为 `record-until-full`。 在 `traceOptions` 中的参数被解析应用之前,查找参数初始化默认为 (`record_mode` 设置为 -`record-until-full`, `enable_sampling` 和 `enable_systrace` 设置为 `false`). +`record-until-full`, `enable_sampling` 和 `enable_systrace` 设置为 `false`)。 ### `contentTracing.stopRecording(resultFilePath, callback)` * `resultFilePath` String * `callback` Function + * `resultFilePath` String -停止对所有子进程的记录. +停止对所有子进程的记录。 -子进程通常缓存查找数据,并且仅仅将数据截取和发送给主进程.这有利于在通过 IPC 发送查找数据之前减小查找时的运行开销,这样做很有价值.因此,发送查找数据,我们应当异步通知所有子进程来截取任何待查找的数据. +子进程通常缓存查找数据,并且仅仅将数据截取和发送给主进程。这有利于在通过 IPC 发送查找数据之前减小查找时的运行开销,这样做很有价值.因此,发送查找数据,我们应当异步通知所有子进程来截取任何待查找的数据。 -一旦所有子进程接收到了 `stopRecording` 请求,将调用 `callback` ,并且返回一个包含查找数据的文件. +一旦所有子进程接收到了 `stopRecording` 请求,将调用 `callback` ,并且返回一个包含查找数据的文件。 -如果 `resultFilePath` 不为空,那么将把查找数据写入其中,否则写入一个临时文件.实际文件路径如果不为空,则将调用 `callback` . +如果 `resultFilePath` 不为空,那么将把查找数据写入其中,否则写入一个临时文件。实际文件路径如果不为空,则将调用 `callback`。 ### `contentTracing.startMonitoring(options, callback)` @@ -87,31 +97,34 @@ contentTracing.startRecording(options, function () { * `traceOptions` String * `callback` Function -开始向所有进程进行监听.(monitoring) +开始向所有进程进行监听。 -一旦收到可以开始监听的请求,记录将会立马启动并且在子进程是异步记监听的. 当所有的子进程都收到 `startMonitoring` 请求的时候,`callback` 将会被调用. +一旦收到可以开始监听的请求,记录将会立马启动并且在子进程是异步记监听的。当所有的子进程都收到 `startMonitoring` 请求的时候,`callback` 将会被调用。 ### `contentTracing.stopMonitoring(callback)` * `callback` Function -停止对所有子进程的监听. +停止对所有子进程的监听。 -一旦所有子进程接收到了 `stopMonitoring` 请求,将调用 `callback` . +一旦所有子进程接收到了 `stopMonitoring` 请求,将调用 `callback`。 ### `contentTracing.captureMonitoringSnapshot(resultFilePath, callback)` * `resultFilePath` String * `callback` Function + * `resultFilePath` String -获取当前监听的查找数据. +获取当前监听的查找数据。 -子进程通常缓存查找数据,并且仅仅将数据截取和发送给主进程.因为如果直接通过 IPC 来发送查找数据的代价昂贵,我们宁愿避免不必要的查找运行开销.因此,为了停止查找,我们应当异步通知所有子进程来截取任何待查找的数据. +子进程通常缓存查找数据,并且仅仅将数据截取和发送给主进程.因为如果直接通过 IPC 来发送查找数据的代价昂贵,我们宁愿避免不必要的查找运行开销。因此,为了停止查找,我们应当异步通知所有子进程来截取任何待查找的数据。 -一旦所有子进程接收到了 `captureMonitoringSnapshot` 请求,将调用 `callback` ,并且返回一个包含查找数据的文件. +一旦所有子进程接收到了 `captureMonitoringSnapshot` 请求,将调用 `callback` ,并且返回一个包含查找数据的文件。 ### `contentTracing.getTraceBufferUsage(callback)` * `callback` Function + * `value` Number + * `percentage` Number -通过查找 buffer 进程来获取百分比最大使用量.当确定了TraceBufferUsage 的值确定的时候,就调用 `callback` . +通过查找 buffer 进程来获取百分比最大使用量.当确定了TraceBufferUsage 的值确定的时候,就调用 `callback`。 From 4a78490a6a17074b5911f1c94b9a72de7ca1998a Mon Sep 17 00:00:00 2001 From: Weiqiang Lin <23leo@163.com> Date: Thu, 9 Feb 2017 17:01:30 +0800 Subject: [PATCH 066/925] dialog to zh-CN --- docs-translations/zh-CN/api/dialog.md | 108 +++++++++++++++++++------- 1 file changed, 79 insertions(+), 29 deletions(-) diff --git a/docs-translations/zh-CN/api/dialog.md b/docs-translations/zh-CN/api/dialog.md index 366d7b8ff3..702b6ea3e2 100644 --- a/docs-translations/zh-CN/api/dialog.md +++ b/docs-translations/zh-CN/api/dialog.md @@ -1,6 +1,8 @@ # dialog -`dialog` 模块提供了api来展示原生的系统对话框,例如打开文件框,alert框,所以web应用可以给用户带来跟系统应用相同的体验. +> 显示用于打开和保存文件,alert框等的原生的系统对话框 + +进程: [Main](../glossary.md#main-process) 对话框例子,展示了选择文件和目录: @@ -9,62 +11,95 @@ const {dialog} = require('electron') console.log(dialog.showOpenDialog({properties: ['openFile', 'openDirectory', 'multiSelections']})) ``` -**macOS 上的注意事项**: 如果你想像sheets一样展示对话框,只需要在`browserWindow` 参数中提供一个 `BrowserWindow` 的引用对象. +对话框从 Electron 的主线程打开。如果要从渲染器进程使用对话框 +对象,记得使用 remote 访问它: + +```javascript +const {dialog} = require('electron').remote +console.log(dialog) +``` ## 方法 -`dialog` 模块有以下方法: +`dialog` 模块有以下方法: ### `dialog.showOpenDialog([browserWindow, ]options[, callback])` * `browserWindow` BrowserWindow (可选) * `options` Object - * `title` String - * `defaultPath` String - * `filters` Array - * `properties` Array - 包含了对话框的特性值, 可以包含 `openFile`, `openDirectory`, `multiSelections` and - `createDirectory` + * `title` String (可选) + * `defaultPath` String (可选) + * `buttonLabel` String (可选) - Custom label for the confirmation button, when + left empty the default label will be used. + * `filters` [FileFilter[]](structures/file-filter.md) (optional) + * `properties` String[] (可选) - Contains which features the dialog should + use. The following values are supported: + * `openFile` - Allow files to be selected. + * `openDirectory` - Allow directories to be selected. + * `multiSelections` - Allow multiple paths to be selected. + * `showHiddenFiles` - Show hidden files in dialog. + * `createDirectory` _macOS_ - Allow creating new directories from dialog. + * `promptToCreate` _Windows_ - Prompt for creation if the file path entered + in the dialog does not exist. This does not actually create the file at + the path but allows non-existent paths to be returned that should be + created by the application. + * `normalizeAccessKeys` Boolean (可选) - Normalize the keyboard access keys + across platforms. Default is `false`. Enabling this assumes `&` is used in + the button labels for the placement of the keyboard shortcut access key + and labels will be converted so they work correctly on each platform, `&` + characters are removed on macOS, converted to `_` on Linux, and left + untouched on Windows. For example, a button label of `Vie&w` will be + converted to `Vie_w` on Linux and `View` on macOS and can be selected + via `Alt-W` on Windows and Linux. * `callback` Function (可选) + * `filePaths` String[] - An array of file paths chosen by the user -成功使用这个方法的话,就返回一个可供用户选择的文件路径数组,失败返回 `undefined`. +成功使用这个方法的话,就返回一个可供用户选择的文件路径数组,失败返回 `undefined`。 -`filters` 当需要限定用户的行为的时候,指定一个文件数组给用户展示或选择. 例如: +`browserWindow` 参数允许对话框将自身附加到父窗口,使其成为模态。 + +`filters` 当需要限定用户的行为的时候,指定一个文件数组给用户展示或选择。例如: ```javascript { filters: [ - { name: 'Images', extensions: ['jpg', 'png', 'gif'] }, - { name: 'Movies', extensions: ['mkv', 'avi', 'mp4'] }, - { name: 'Custom File Type', extensions: ['as'] }, - { name: 'All Files', extensions: ['*'] } + {name: 'Images', extensions: ['jpg', 'png', 'gif']}, + {name: 'Movies', extensions: ['mkv', 'avi', 'mp4']}, + {name: 'Custom File Type', extensions: ['as']}, + {name: 'All Files', extensions: ['*']} ] } ``` -`extensions` 数组应当只包含扩展名,不应该包含通配符或'.'号 (例如 -`'png'` 正确,但是 `'.png'` 和 `'*.png'` 不正确). 展示全部文件的话, 使用 -`'*'` 通配符 (不支持其他通配符). +`extensions` 数组应当只包含扩展名,不应该包含通配符或 '.' 号 (例如 +`'png'` 正确,但是 `'.png'` 和 `'*.png'` 不正确)。展示全部文件的话,使用 +`'*'` 通配符 (不支持其他通配符)。 -如果 `callback` 被调用, 将异步调用 API ,并且结果将用过 `callback(filenames)` 展示. +如果 `callback` 被调用,将异步调用 API ,并且结果将用过 `callback(filenames)` 展示。 **注意:** 在 Windows 和 Linux ,一个打开的 dialog 不能既是文件选择框又是目录选择框, 所以如果在这些平台上设置 `properties` 的值为 -`['openFile', 'openDirectory']` , 将展示一个目录选择框. +`['openFile', 'openDirectory']` ,将展示一个目录选择框。 ### `dialog.showSaveDialog([browserWindow, ]options[, callback])` * `browserWindow` BrowserWindow (可选) * `options` Object - * `title` String - * `defaultPath` String - * `filters` Array + * `title` String (可选) + * `defaultPath` String (可选) + * `buttonLabel` String (可选) - Custom label for the confirmation button, when + left empty the default label will be used. + * `filters` [FileFilter[]](structures/file-filter.md) (optional) * `callback` Function (可选) + * `filename` String -成功使用这个方法的话,就返回一个可供用户选择的文件路径数组,失败返回 `undefined`. +成功使用这个方法的话,就返回一个可供用户选择的文件路径数组,失败返回 `undefined`。 + +`browserWindow` 参数允许对话框将自身附加到父窗口,使其成为模态。 `filters` 指定展示一个文件类型数组, 例子 -`dialog.showOpenDialog` . +`dialog.showOpenDialog` 。 -如果 `callback` 被调用, 将异步调用 API ,并且结果将用过 `callback(filenames)` 展示. +如果 `callback` 被调用, 将异步调用 API ,并且结果将用过 `callback(filenames)` 展示。 ### `dialog.showMessageBox([browserWindow, ]options[, callback])` @@ -80,14 +115,29 @@ console.log(dialog.showOpenDialog({properties: ['openFile', 'openDirectory', 'mu * `icon` [NativeImage](native-image.md) * `cancelId` Integer - 当用户关闭对话框的时候,不是通过点击对话框的button,就返回值.默认值为对应 "cancel" 或 "no" 标签button 的索引值, 或者如果没有这种button,就返回0. 在 macOS 和 Windows 上, "Cancel" button 的索引值将一直是 `cancelId`, 不管之前是不是特别指出的. * `noLink` Boolean - 在 Windows ,Electron 将尝试识别哪个button 是普通 button (如 "Cancel" 或 "Yes"), 然后在对话框中以链接命令(command links)方式展现其它的 button . 这能让对话框展示得很炫酷.如果你不喜欢这种效果,你可以设置 `noLink` 为 `true`. -* `callback` Function + * `callback` Function (可选) + * `response` Number - The index of the button that was clicked -展示 message box, 它会阻塞进程,直到 message box 关闭为止.返回点击按钮的索引值. +返回 `Integer`,如果提供了回调,它会返回点击的按钮的索引或者 undefined 。 -如果 `callback` 被调用, 将异步调用 API ,并且结果将用过 `callback(response)` 展示. +展示 message box, 它会阻塞进程,直到 message box 关闭为止.返回点击按钮的索引值。 + +`browserWindow` 参数允许对话框将自身附加到父窗口,使其成为模态。 + +如果 `callback` 被调用, 将异步调用 API ,并且结果将用过 `callback(response)` 展示。 ### `dialog.showErrorBox(title, content)` +* `title` String - 错误框中的标题 +* `content` String - 错误框中的内容 + 展示一个传统的包含错误信息的对话框. -在 `app` 模块触发 `ready` 事件之前,这个 api 可以被安全调用,通常它被用来在启动的早期阶段报告错误. 在 Linux 上,如果在 `app` 模块触发 `ready` 事件之前调用,message 将会被触发显示stderr,并且没有实际GUI 框显示. +在 `app` 模块触发 `ready` 事件之前,这个 api 可以被安全调用,通常它被用来在启动的早期阶段报告错误. 在 Linux 上,如果在 `app` 模块触发 `ready` 事件之前调用,message 将会被触发显示 stderr ,并且没有实际 GUI 框显示. + +## Sheets + +在 macOS 上,如果你想像 sheets 一样展示对话框,只需要在`browserWindow` 参数中提供一个 `BrowserWindow` 的引用对象.,如果没有则为模态窗口。 + +你可以调用 `BrowserWindow.getCurrentWindow().setSheetOffset(offset)` 来改变 +sheets 的窗口框架的偏移量。 From 8f542b1d6954d503b38ebbdb496fbb8af59ee6a4 Mon Sep 17 00:00:00 2001 From: Weiqiang Lin <23leo@163.com> Date: Thu, 9 Feb 2017 17:08:58 +0800 Subject: [PATCH 067/925] globalShortcut to zh-CN --- .../zh-CN/api/global-shortcut.md | 47 ++++++++++++------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/docs-translations/zh-CN/api/global-shortcut.md b/docs-translations/zh-CN/api/global-shortcut.md index c8da091a2d..15037c73c0 100644 --- a/docs-translations/zh-CN/api/global-shortcut.md +++ b/docs-translations/zh-CN/api/global-shortcut.md @@ -1,18 +1,21 @@ -# global-shortcut +# globalShortcut -`global-shortcut` 模块可以便捷的为您设置(注册/注销)各种自定义操作的快捷键. +> 当应用程序没有键盘焦点时检测键盘事件。 -**Note**: 使用此模块注册的快捷键是系统全局的(QQ截图那种), 不要在应用模块(app module)响应 `ready` -消息前使用此模块(注册快捷键). +进程: [Main](../glossary.md#main-process) + +`globalShortcut` 模块可以便捷的为你设置(注册/注销)各种自定义操作的快捷键。 + +**注意:** 使用此模块注册的快捷键是系统全局的(QQ截图那种), 不要在应用模块(app module)响应 `ready` +消息前使用此模块(注册快捷键)。 ```javascript -var app = require('app') -var globalShortcut = require('global-shortcut') +const {app, globalShortcut} = require('electron') -app.on('ready', function () { - // Register a 'ctrl+x' shortcut listener. - var ret = globalShortcut.register('ctrl+x', function () { - console.log('ctrl+x is pressed') +app.on('ready', () => { + // Register a 'CommandOrControl+X' shortcut listener. + const ret = globalShortcut.register('CommandOrControl+X', () => { + console.log('CommandOrControl+X is pressed') }) if (!ret) { @@ -20,12 +23,12 @@ app.on('ready', function () { } // Check whether a shortcut is registered. - console.log(globalShortcut.isRegistered('ctrl+x')) + console.log(globalShortcut.isRegistered('CommandOrControl+X')) }) -app.on('will-quit', function () { +app.on('will-quit', () => { // Unregister a shortcut. - globalShortcut.unregister('ctrl+x') + globalShortcut.unregister('CommandOrControl+X') // Unregister all shortcuts. globalShortcut.unregisterAll() @@ -34,27 +37,35 @@ app.on('will-quit', function () { ## Methods -`global-shortcut` 模块包含以下函数: +`globalShortcut` 模块包含以下函数: ### `globalShortcut.register(accelerator, callback)` * `accelerator` [Accelerator](accelerator.md) * `callback` Function -注册 `accelerator` 快捷键. 当用户按下注册的快捷键时将会调用 `callback` 函数. +注册 `accelerator` 快捷键。当用户按下注册的快捷键时将会调用 `callback` 函数。 + +当 accelerator 已经被其他应用程序占用时,此调用将 +默默地失败。这种行为是操作系统的意图,因为它们没有 +想要应用程序争取全局快捷键。 ### `globalShortcut.isRegistered(accelerator)` * `accelerator` [Accelerator](accelerator.md) -查询 `accelerator` 快捷键是否已经被注册过了,将会返回 `true`(已被注册) 或 `false`(未注册). +返回 `Boolean` - 查询 `accelerator` 快捷键是否已经被注册过了,将会返回 `true` 或 `false`。 + +当 accelerator 已经被其他应用程序占用时,此调用将 +默默地失败。这种行为是操作系统的意图,因为它们没有 +想要应用程序争取全局快捷键。 ### `globalShortcut.unregister(accelerator)` * `accelerator` [Accelerator](accelerator.md) -注销全局快捷键 `accelerator`. +注销全局快捷键 `accelerator`。 ### `globalShortcut.unregisterAll()` -注销本应用注册的所有全局快捷键. +注销本应用程序注册的所有全局快捷键。 From c7b639e28297dddc63108ed01570329171b0eb06 Mon Sep 17 00:00:00 2001 From: Weiqiang Lin <23leo@163.com> Date: Thu, 9 Feb 2017 17:16:27 +0800 Subject: [PATCH 068/925] ipcMain to zh-CN --- docs-translations/zh-CN/api/ipc-main.md | 46 ++++++++++++++----------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/docs-translations/zh-CN/api/ipc-main.md b/docs-translations/zh-CN/api/ipc-main.md index c3b4b90eb3..e84a9f4f4e 100644 --- a/docs-translations/zh-CN/api/ipc-main.md +++ b/docs-translations/zh-CN/api/ipc-main.md @@ -1,28 +1,32 @@ # ipcMain +> 从主进程到渲染器进程异步通信。 + +进程:[Main](../glossary.md#main-process) + `ipcMain` 模块是类 -[EventEmitter](https://nodejs.org/api/events.html) 的实例.当在主进程中使用它的时候,它控制着由渲染进程(web page)发送过来的异步或同步消息.从渲染进程发送过来的消息将触发事件. +[EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter) 的实例。当在主进程中使用它的时候,它控制着由渲染进程(web page)发送过来的异步或同步消息。从渲染进程发送过来的消息将触发事件。 ## 发送消息 -同样也可以从主进程向渲染进程发送消息,查看更多 [webContents.send][web-contents-send] . +同样也可以从主进程向渲染进程发送消息,查看更多 [webContents.send][web-contents-send] 。 -* 发送消息,事件名为 `channel`. -* 回应同步消息, 你可以设置 `event.returnValue`. +* 发送消息,事件名为 `channel`。 +* 回应同步消息, 你可以设置 `event.returnValue`。 * 回应异步消息, 你可以使用 - `event.sender.send(...)`. + `event.sender.send(...)`。 -一个例子,在主进程和渲染进程之间发送和处理消息: +一个例子,在主进程和渲染进程之间发送和处理消息: ```javascript // In main process. -const ipcMain = require('electron').ipcMain -ipcMain.on('asynchronous-message', function (event, arg) { +const {ipcMain} = require('electron') +ipcMain.on('asynchronous-message', (event, arg) => { console.log(arg) // prints "ping" event.sender.send('asynchronous-reply', 'pong') }) -ipcMain.on('synchronous-message', function (event, arg) { +ipcMain.on('synchronous-message', (event, arg) => { console.log(arg) // prints "ping" event.returnValue = 'pong' }) @@ -30,56 +34,56 @@ ipcMain.on('synchronous-message', function (event, arg) { ```javascript // In renderer process (web page). -const ipcRenderer = require('electron').ipcRenderer +const {ipcRenderer} = require('electron') console.log(ipcRenderer.sendSync('synchronous-message', 'ping')) // prints "pong" -ipcRenderer.on('asynchronous-reply', function (event, arg) { +ipcRenderer.on('asynchronous-reply', (event, arg) => { console.log(arg) // prints "pong" }) ipcRenderer.send('asynchronous-message', 'ping') ``` -## 监听消息 +## 方法 -`ipcMain` 模块有如下监听事件方法: +`ipcMain` 模块有如下监听事件方法: ### `ipcMain.on(channel, listener)` * `channel` String * `listener` Function -监听 `channel`, 当新消息到达,将通过 `listener(event, args...)` 调用 `listener`. +监听 `channel`, 当新消息到达,将通过 `listener(event, args...)` 调用 `listener`。 ### `ipcMain.once(channel, listener)` * `channel` String * `listener` Function -为事件添加一个一次性用的`listener` 函数.这个 `listener` 只有在下次的消息到达 `channel` 时被请求调用,之后就被删除了. +为事件添加一个一次性用的`listener` 函数。这个 `listener` 只有在下次的消息到达 `channel` 时被请求调用,之后就被删除了。 ### `ipcMain.removeListener(channel, listener)` * `channel` String * `listener` Function -为特定的 `channel` 从监听队列中删除特定的 `listener` 监听者. +为特定的 `channel` 从监听队列中删除特定的 `listener` 监听者。 ### `ipcMain.removeAllListeners([channel])` * `channel` String (可选) -删除所有监听者,或特指的 `channel` 的所有监听者. +删除所有监听者,或特指的 `channel` 的所有监听者。 ## 事件对象 -传递给 `callback` 的 `event` 对象有如下方法: +传递给 `callback` 的 `event` 对象有如下方法: ### `event.returnValue` -将此设置为在一个同步消息中返回的值. +将此设置为在一个同步消息中返回的值。 ### `event.sender` -返回发送消息的 `webContents` ,你可以调用 `event.sender.send` 来回复异步消息,更多信息 [webContents.send][web-contents-send]. +返回发送消息的 `webContents` ,你可以调用 `event.sender.send` 来回复异步消息,更多信息 [webContents.send][web-contents-send]。 -[web-contents-send]: web-contents.md#webcontentssendchannel-arg1-arg2- \ No newline at end of file +[web-contents-send]: web-contents.md#webcontentssendchannel-arg1-arg2- From eba65eb4d30f40b6d0ea3db32117ac36194720d4 Mon Sep 17 00:00:00 2001 From: Weiqiang Lin <23leo@163.com> Date: Thu, 9 Feb 2017 17:34:45 +0800 Subject: [PATCH 069/925] Class: Menu to zh-CN --- docs-translations/zh-CN/api/menu.md | 481 +++++++++++++++------------- 1 file changed, 253 insertions(+), 228 deletions(-) diff --git a/docs-translations/zh-CN/api/menu.md b/docs-translations/zh-CN/api/menu.md index 07736d4bbd..0baddba1da 100644 --- a/docs-translations/zh-CN/api/menu.md +++ b/docs-translations/zh-CN/api/menu.md @@ -1,215 +1,31 @@ -# 菜单 +# 类:菜单 -`menu` 类可以用来创建原生菜单,它可用作应用菜单和 -[context 菜单](https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/PopupGuide/ContextMenus). +> 创建原生的应用菜单和 context 菜单。 -这个模块是一个主进程的模块,并且可以通过 `remote` 模块给渲染进程调用. - -每个菜单有一个或几个菜单项 [menu items](menu-item.md),并且每个菜单项可以有子菜单. - -下面这个例子是在网页(渲染进程)中通过 [remote](remote.md) 模块动态创建的菜单,并且右键显示: - -```html - - -``` - -例子,在渲染进程中使用模板api创建应用菜单: - -```javascript -var template = [ - { - label: 'Edit', - submenu: [ - { - label: 'Undo', - accelerator: 'CmdOrCtrl+Z', - role: 'undo' - }, - { - label: 'Redo', - accelerator: 'Shift+CmdOrCtrl+Z', - role: 'redo' - }, - { - type: 'separator' - }, - { - label: 'Cut', - accelerator: 'CmdOrCtrl+X', - role: 'cut' - }, - { - label: 'Copy', - accelerator: 'CmdOrCtrl+C', - role: 'copy' - }, - { - label: 'Paste', - accelerator: 'CmdOrCtrl+V', - role: 'paste' - }, - { - label: 'Select All', - accelerator: 'CmdOrCtrl+A', - role: 'selectall' - } - ] - }, - { - label: 'View', - submenu: [ - { - label: 'Reload', - accelerator: 'CmdOrCtrl+R', - click: function (item, focusedWindow) { - if (focusedWindow) focusedWindow.reload() - } - }, - { - label: 'Toggle Full Screen', - accelerator: (function () { - return (process.platform === 'darwin') ? 'Ctrl+Command+F' : 'F11' - })(), - click: function (item, focusedWindow) { - if (focusedWindow) focusedWindow.setFullScreen(!focusedWindow.isFullScreen()) - } - }, - { - label: 'Toggle Developer Tools', - accelerator: (function () { - if (process.platform === 'darwin') { - return 'Alt+Command+I' - } else { - return 'Ctrl+Shift+I' - } - })(), - click: function (item, focusedWindow) { - if (focusedWindow) focusedWindow.toggleDevTools() - } - } - ] - }, - { - label: 'Window', - role: 'window', - submenu: [ - { - label: 'Minimize', - accelerator: 'CmdOrCtrl+M', - role: 'minimize' - }, - { - label: 'Close', - accelerator: 'CmdOrCtrl+W', - role: 'close' - } - ] - }, - { - label: 'Help', - role: 'help', - submenu: [ - { - label: 'Learn More', - click: function () { require('electron').shell.openExternal('http://electron.atom.io') } - } - ] - } -] - -if (process.platform === 'darwin') { - var name = require('electron').remote.app.getName() - template.unshift({ - label: name, - submenu: [ - { - label: 'About ' + name, - role: 'about' - }, - { - type: 'separator' - }, - { - label: 'Services', - role: 'services', - submenu: [] - }, - { - type: 'separator' - }, - { - label: 'Hide ' + name, - accelerator: 'Command+H', - role: 'hide' - }, - { - label: 'Hide Others', - accelerator: 'Command+Alt+H', - role: 'hideothers' - }, - { - label: 'Show All', - role: 'unhide' - }, - { - type: 'separator' - }, - { - label: 'Quit', - accelerator: 'Command+Q', - click: function () { app.quit() } - } - ] - }) - // Window menu. - template[3].submenu.push( - { - type: 'separator' - }, - { - label: 'Bring All to Front', - role: 'front' - } - ) -} - -var menu = Menu.buildFromTemplate(template) -Menu.setApplicationMenu(menu) -``` - -## 类: Menu +进程: [Main](../glossary.md#main-process) ### `new Menu()` -创建一个新的菜单. +创建一个新的菜单。 -## 方法 +### 静态方法 -`菜单` 类有如下方法: +`菜单` 类有如下静态方法: -### `Menu.setApplicationMenu(menu)` +#### `Menu.setApplicationMenu(menu)` * `menu` Menu -在 macOS 上设置应用菜单 `menu` . -在windows 和 linux,是为每个窗口都在其顶部设置菜单 `menu`. +在 macOS 上设置应用菜单 `menu`。 +在 windows 和 linux,是为每个窗口都在其顶部设置菜单 `menu`。 -### `Menu.sendActionToFirstResponder(action)` _macOS_ +**注意:** 这个API必须在 `app` 模块的 `ready` 事件后调用。 + +#### `Menu.getApplicationMenu()` + +返回 `Menu` - 应用程序菜单,设置、 `null` 、或未设置。 + +#### `Menu.sendActionToFirstResponder(action)` _macOS_ * `action` String @@ -217,19 +33,21 @@ Menu.setApplicationMenu(menu) 查看更多 macOS 的原生 action [macOS Cocoa Event Handling Guide](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/EventOverview/EventArchitecture/EventArchitecture.html#//apple_ref/doc/uid/10000060i-CH3-SW7) . -### `Menu.buildFromTemplate(template)` +#### `Menu.buildFromTemplate(template)` -* `template` Array +* `template` MenuItemConstructorOptions[] -一般来说,`template` 只是用来创建 [MenuItem](menu-item.md) 的数组 `参数` . +返回 `Menu` -你也可以向 `template` 元素添加其它东西,并且他们会变成已经有的菜单项的属性. +一般来说,`template` 只是用来创建 [MenuItem](menu-item.md) 的数组 `参数`。 -##实例方法 +你也可以向 `template` 元素添加其它东西,并且他们会变成已经有的菜单项的属性。 + +### 实例方法 `menu` 对象有如下实例方法 -### `menu.popup([browserWindow, x, y, positioningItem])` +#### `menu.popup([browserWindow, x, y, positioningItem])` * `browserWindow` BrowserWindow (可选) - 默认为 `null`. * `x` Number (可选) - 默认为 -1. @@ -239,31 +57,239 @@ Menu.setApplicationMenu(menu) 在 `browserWindow` 中弹出 context menu .你可以选择性地提供指定的 `x, y` 来设置菜单应该放在哪里,否则它将默认地放在当前鼠标的位置. -### `menu.append(menuItem)` +#### `menu.append(menuItem)` * `menuItem` MenuItem -添加菜单项. +添加菜单项。 -### `menu.insert(pos, menuItem)` +#### `menu.insert(pos, menuItem)` * `pos` Integer * `menuItem` MenuItem -在制定位置添加菜单项. +在指定位置添加菜单项。 -### `menu.items()` +### 实例属性 -获取一个菜单项数组. +`menu` 对象拥有以下属性: + +#### `menu.items()` + +获取一个菜单项数组。 + +## 例子 + +`Menu` 类只能在主进程中使用,但你也可以 +在渲染过程中通过 [`remote`](remote.md) 模块使用它。 + +### 主进程 + +在主进程中创建应用程序菜单的示例 +简单模板API: + +```javascript +const {app, Menu} = require('electron') + +const template = [ + { + label: 'Edit', + submenu: [ + { + role: 'undo' + }, + { + role: 'redo' + }, + { + type: 'separator' + }, + { + role: 'cut' + }, + { + role: 'copy' + }, + { + role: 'paste' + }, + { + role: 'pasteandmatchstyle' + }, + { + role: 'delete' + }, + { + role: 'selectall' + } + ] + }, + { + label: 'View', + submenu: [ + { + role: 'reload' + }, + { + role: 'forcereload' + }, + { + role: 'toggledevtools' + }, + { + type: 'separator' + }, + { + role: 'resetzoom' + }, + { + role: 'zoomin' + }, + { + role: 'zoomout' + }, + { + type: 'separator' + }, + { + role: 'togglefullscreen' + } + ] + }, + { + role: 'window', + submenu: [ + { + role: 'minimize' + }, + { + role: 'close' + } + ] + }, + { + role: 'help', + submenu: [ + { + label: 'Learn More', + click () { require('electron').shell.openExternal('http://electron.atom.io') } + } + ] + } +] + +if (process.platform === 'darwin') { + template.unshift({ + label: app.getName(), + submenu: [ + { + role: 'about' + }, + { + type: 'separator' + }, + { + role: 'services', + submenu: [] + }, + { + type: 'separator' + }, + { + role: 'hide' + }, + { + role: 'hideothers' + }, + { + role: 'unhide' + }, + { + type: 'separator' + }, + { + role: 'quit' + } + ] + }) + // Edit menu. + template[1].submenu.push( + { + type: 'separator' + }, + { + label: 'Speech', + submenu: [ + { + role: 'startspeaking' + }, + { + role: 'stopspeaking' + } + ] + } + ) + // Window menu. + template[3].submenu = [ + { + label: 'Close', + accelerator: 'CmdOrCtrl+W', + role: 'close' + }, + { + label: 'Minimize', + accelerator: 'CmdOrCtrl+M', + role: 'minimize' + }, + { + label: 'Zoom', + role: 'zoom' + }, + { + type: 'separator' + }, + { + label: 'Bring All to Front', + role: 'front' + } + ] +} + +const menu = Menu.buildFromTemplate(template) +Menu.setApplicationMenu(menu) +``` + +### 渲染进程 + +以下是通过使用 [`remote`](remote.md) 模块在网页(渲染进程)中动态创建菜单的示例 +,并在用户点击右键时显示: + +```html + + +``` ## macOS Application 上的菜单的注意事项 -相对于windows 和 linux, macOS 上的应用菜单是完全不同的style,这里是一些注意事项,来让你的菜单项更原生化. +相对于 windows 和 linux, macOS 上的应用菜单是完全不同的 style,这里是一些注意事项,来让你的菜单项更原生化。 ### 标准菜单 -在 macOS 上,有很多系统定义的标准菜单,例如 `Services` and -`Windows` 菜单.为了让你的应用更标准化,你可以为你的菜单的 `role` 设置值,然后 electron 将会识别他们并且让你的菜单更标准: +在 macOS 上,有很多系统定义的标准菜单,例如 `Services` and +`Windows` 菜单.为了让你的应用更标准化,你可以为你的菜单的 `role` 设置值,然后 Electron 将会识别他们并且让你的菜单更标准: * `window` * `help` @@ -271,29 +297,29 @@ Menu.setApplicationMenu(menu) ### 标准菜单项行为 -macOS 为一些菜单项提供了标准的行为方法,例如 `About xxx`, -`Hide xxx`, and `Hide Others`. 为了让你的菜单项的行为更标准化,你应该为菜单项设置 `role` 属性. +macOS 为一些菜单项提供了标准的行为方法,例如 `About xxx`, +`Hide xxx`,和 `Hide Others`. 为了让你的菜单项的行为更标准化,你应该为菜单项设置 `role` 属性。 ### 主菜单名 -在 macOS ,无论你设置的什么标签,应用菜单的第一个菜单项的标签始终未你的应用名字.想要改变它的话,你必须通过修改应用绑定的 `Info.plist` 文件来修改应用名字.更多信息参考[About Information -Property List Files][AboutInformationPropertyListFiles] . +在 macOS ,无论你设置的什么标签,应用菜单的第一个菜单项的标签始终未你的应用名字。想要改变它的话,你必须通过修改应用绑定的 `Info.plist` 文件来修改应用名字,更多信息参考 [About Information +Property List Files][AboutInformationPropertyListFiles] 。 ## 为制定浏览器窗口设置菜单 (*Linux* *Windows*) -浏览器窗口的[`setMenu` 方法][setMenu] 能够设置菜单为特定浏览器窗口的类型. +浏览器窗口的 [`setMenu` 方法][setMenu] 能够设置菜单为特定浏览器窗口的类型。 ## 菜单项位置 -当通过 `Menu.buildFromTemplate` 创建菜单的时候,你可以使用 `position` and `id` 来放置菜单项. +当通过 `Menu.buildFromTemplate` 创建菜单的时候,你可以使用 `position` and `id` 来放置菜单项。 -`MenuItem` 的属性 `position` 格式为 `[placement]=[id]`,`placement` 取值为 `before`, `after`, 或 `endof` 和 `id`, `id` 是菜单已经存在的菜单项的唯一 ID: +`MenuItem` 的属性 `position` 格式为 `[placement]=[id]`,`placement` 取值为 `before`, `after`, 或 `endof` 和 `id`, `id` 是菜单已经存在的菜单项的唯一 ID: -* `before` - 在对应引用id菜单项之前插入. 如果引用的菜单项不存在,则将其插在菜单末尾. -* `after` - 在对应引用id菜单项之后插入. 如果引用的菜单项不存在,则将其插在菜单末尾. -* `endof` - 在逻辑上包含对应引用id菜单项的集合末尾插入. 如果引用的菜单项不存在, 则将使用给定的id创建一个新的集合,并且这个菜单项将插入. +* `before` - 在对应引用id菜单项之前插入。如果引用的菜单项不存在,则将其插在菜单末尾。 +* `after` - 在对应引用id菜单项之后插入。如果引用的菜单项不存在,则将其插在菜单末尾。 +* `endof` - 在逻辑上包含对应引用id菜单项的集合末尾插入。如果引用的菜单项不存在, 则将使用给定的id创建一个新的集合,并且这个菜单项将插入。 -当一个菜档项插入成功了,所有的没有插入的菜单项将一个接一个地在后面插入.所以如果你想在同一个位置插入一组菜单项,只需要为这组菜单项的第一个指定位置. +当一个菜档项插入成功了,所有的没有插入的菜单项将一个接一个地在后面插入。所以如果你想在同一个位置插入一组菜单项,只需要为这组菜单项的第一个指定位置。 ### 例子 @@ -346,5 +372,4 @@ Property List Files][AboutInformationPropertyListFiles] . ``` [AboutInformationPropertyListFiles]: https://developer.apple.com/library/ios/documentation/general/Reference/InfoPlistKeyReference/Articles/AboutInformationPropertyListFiles.html -[setMenu]: -https://github.com/electron/electron/blob/master/docs/api/browser-window.md#winsetmenumenu-linux-windows +[setMenu]: https://github.com/electron/electron/blob/master/docs/api/browser-window.md#winsetmenumenu-linux-windows From e267a0dd4194e626889cc24d49fd6b115f5bc96b Mon Sep 17 00:00:00 2001 From: Weiqiang Lin <23leo@163.com> Date: Thu, 9 Feb 2017 17:57:27 +0800 Subject: [PATCH 070/925] Class: MenuItem to zh-CN --- docs-translations/zh-CN/api/menu-item.md | 93 ++++++++++++++++++------ 1 file changed, 71 insertions(+), 22 deletions(-) diff --git a/docs-translations/zh-CN/api/menu-item.md b/docs-translations/zh-CN/api/menu-item.md index 6d5d59d934..c2091674a5 100644 --- a/docs-translations/zh-CN/api/menu-item.md +++ b/docs-translations/zh-CN/api/menu-item.md @@ -1,29 +1,40 @@ # 菜单项 -菜单项模块允许你向应用或[menu][1]添加选项。 -查看[menu][1]例子。 +## 类:菜单项 -## 类:MenuItem -使用下面的方法创建一个新的 `MenuItem` +> 向原生的应用菜单和 context 菜单添加菜单项。 + +进程: [Main](../glossary.md#main-process) + +查看 [`Menu`](menu.md) 的示。 + +### `new MenuItem(options)` -###new MenuItem(options) * `options` Object - * `click` Function - 当菜单项被点击的时候,使用 `click(menuItem,browserWindow)` 调用 - * `role` String - 定义菜单项的行为,在指定 `click` 属性时将会被忽略 - * `type` String - 取值 `normal`,`separator`,`checkbox`or`radio` - * `label` String - * `sublabel` String - * `accelerator` [Accelerator][2] - * `icon` [NativeImage][3] - * `enabled` Boolean - * `visible` Boolean - * `checked` Boolean - * `submenu` Menu - 应当作为 `submenu` 菜单项的特定类型,当它作为 `type: 'submenu'` 菜单项的特定类型时可以忽略。如果它的值不是 `Menu`,将自动转为 `Menu.buildFromTemplate`。 - * `id` String - 标志一个菜单的唯一性。如果被定义使用,它将被用作这个菜单项的参考位置属性。 - * `position` String - 定义给定的菜单的具体指定位置信息。 + * `click` Function (可选) - 当菜单项被点击的时候,使用 `click(menuItem,browserWindow)` 调用。 + * `menuItem` MenuItem + * `browserWindow` BrowserWindow + * `event` Event + * `role` String (可选) - 定义菜单项的行为,在指定 `click` 属性时将会被忽略。 + * `type` String (可选) - 取值 `normal`, `separator`, `submenu`, `checkbox` or `radio`。 + * `label` String - (可选) + * `sublabel` String - (可选) + * `accelerator` [Accelerator](accelerator.md) (可选) + * `icon` ([NativeImage](native-image.md) | String) (可选) + * `enabled` Boolean (可选) - 如果为 false,菜单项将显示为灰色不可点击。 + unclickable. + * `visible` Boolean (可选) - 如果为 false,菜单项将完全隐藏。 + * `checked` Boolean (可选) - 只为 `checkbox` 或 `radio` 类型的菜单项。 + * `submenu` (MenuItemConstructorOptions[] | Menu) (可选) - 应当作为 `submenu` 菜单项的特定类型,当它作为 `type: 'submenu'` 菜单项的特定类型时可以忽略。如果它的值不是 `Menu`,将自动转为 `Menu.buildFromTemplate`。 + * `id` String (可选) - 标志一个菜单的唯一性。如果被定义使用,它将被用作这个菜单项的参考位置属性。 + * `position` String (可选) - 定义菜单的具体指定位置信息。 -在创建菜单项时,如果有匹配的方法,建议指定 `role` 属性,不需要人为操作它的行为,这样菜单使用可以给用户最好的体验。 +在创建菜单项时,如果有匹配的方法,建议指定 `role` 属性, +而不是试图手动实现在一个 `click` 函数中的行为。 +内置的 `role` 行为将提供最好的原生体验。 +当使用 `role' 时,`label' 和 `accelerator` 是可选的,默认为 +到每个平台的适当值。 `role`属性值可以为: @@ -32,9 +43,19 @@ * `cut` * `copy` * `paste` +* `pasteandmatchstyle` * `selectall` +* `delete` * `minimize` - 最小化当前窗口 * `close` - 关闭当前窗口 +* `quit`- 退出应用程序 +* `reload` - 正常重新加载当前窗口 +* `forcereload` - 忽略缓存并重新加载当前窗口 +* `toggledevtools` - 在当前窗口中切换开发者工具 +* `togglefullscreen`- 在当前窗口中切换全屏模式 +* `resetzoom` - 将对焦页面的缩放级别重置为原始大小 +* `zoomin` - 将聚焦页面缩小10% +* `zoomout` - 将聚焦页面放大10% 在 macOS 上,`role` 还可以有以下值: @@ -42,16 +63,44 @@ * `hide` - 匹配 `hide` 行为 * `hideothers` - 匹配 `hideOtherApplications` 行为 * `unhide` - 匹配 `unhideAllApplications` 行为 +* `startspeaking` - 匹配 `startSpeaking` 行为 +* `stopspeaking` - 匹配 `stopSpeaking` 行为 * `front` - 匹配 `arrangeInFront` 行为 +* `zoom` - 匹配 `performZoom` 行为 * `window` - "Window" 菜单项 * `help` - "Help" 菜单项 * `services` - "Services" 菜单项 +当在 macOS 上指定 `role' 时,`label` 和 `accelerator` 是影响MenuItem的唯一的选项 +所有其他选项将被忽略。 +### 实例属性 +`MenuItem` 对象拥有以下属性: +#### `menuItem.enabled` +一个布尔值表示是否启用该项,此属性可以动态改变。 - [1]:https://github.com/heyunjiang/electron/blob/master/docs-translations/zh-CN/api/menu.md - [2]:https://github.com/heyunjiang/electron/blob/master/docs/api/accelerator.md - [3]:https://github.com/heyunjiang/electron/blob/master/docs/api/native-image.md \ No newline at end of file +#### `menuItem.visible` + +一个布尔值表示是否可见,此属性可以动态改变。 + +#### `menuItem.checked` + +一个布尔值表示是否选中该项,此属性可以动态改变。 + +`checkbox` 菜单项将在选中和未选中切换 `checked` 属性。 + +`radio` 菜单项将在选中切换 `checked` 属性,并且 +将关闭同一菜单中所有相邻项目的属性。 + +您可以为其他行为添加一个 `click` 函数。 + +#### `menuItem.label` + +一个表示菜单项可见标签的字符串 + +#### `menuItem.click` + +当 MenuItem 接收到点击事件时触发的函数 From 1d612a12a1960f2f0c539faa68fae7523f583f25 Mon Sep 17 00:00:00 2001 From: Tan Wang Leng Date: Wed, 1 Feb 2017 23:34:21 +0800 Subject: [PATCH 071/925] :apple: Add additional options for Mac's save dialog Support additional attributes available in macOS's NSSavePanel: message, nameFieldLabel and showsTagField --- atom/browser/api/atom_api_dialog.cc | 9 ++++-- .../browser/atom_download_manager_delegate.cc | 1 + atom/browser/common_web_contents_delegate.cc | 2 +- atom/browser/ui/file_dialog.h | 6 ++++ atom/browser/ui/file_dialog_gtk.cc | 3 ++ atom/browser/ui/file_dialog_mac.mm | 31 ++++++++++++++++--- atom/browser/ui/file_dialog_win.cc | 6 ++++ atom/browser/web_dialog_helper.cc | 3 ++ lib/browser/api/dialog.js | 20 +++++++++++- 9 files changed, 72 insertions(+), 9 deletions(-) diff --git a/atom/browser/api/atom_api_dialog.cc b/atom/browser/api/atom_api_dialog.cc index 5d853e2d59..1efb78d72d 100644 --- a/atom/browser/api/atom_api_dialog.cc +++ b/atom/browser/api/atom_api_dialog.cc @@ -92,6 +92,9 @@ void ShowSaveDialog(const std::string& title, const std::string& button_label, const base::FilePath& default_path, const file_dialog::Filters& filters, + const std::string& message, + const std::string& name_field_label, + const bool& shows_tag_field, atom::NativeWindow* window, mate::Arguments* args) { v8::Local peek = args->PeekNext(); @@ -100,11 +103,13 @@ void ShowSaveDialog(const std::string& title, peek, &callback)) { file_dialog::ShowSaveDialog(window, title, button_label, default_path, - filters, callback); + filters, message, name_field_label, + shows_tag_field, callback); } else { base::FilePath path; if (file_dialog::ShowSaveDialog(window, title, button_label, default_path, - filters, &path)) + filters, message, name_field_label, + shows_tag_field, &path)) args->Return(path); } } diff --git a/atom/browser/atom_download_manager_delegate.cc b/atom/browser/atom_download_manager_delegate.cc index 0213216697..b92e3e18bc 100644 --- a/atom/browser/atom_download_manager_delegate.cc +++ b/atom/browser/atom_download_manager_delegate.cc @@ -93,6 +93,7 @@ void AtomDownloadManagerDelegate::OnDownloadPathGenerated( if (path.empty() && file_dialog::ShowSaveDialog(window, item->GetURL().spec(), "", default_path, file_dialog::Filters(), + "", "", false, &path)) { // Remember the last selected download directory. AtomBrowserContext* browser_context = static_cast( diff --git a/atom/browser/common_web_contents_delegate.cc b/atom/browser/common_web_contents_delegate.cc index ed0fd5e4ba..c3cca231c3 100644 --- a/atom/browser/common_web_contents_delegate.cc +++ b/atom/browser/common_web_contents_delegate.cc @@ -297,7 +297,7 @@ void CommonWebContentsDelegate::DevToolsSaveToFile( file_dialog::Filters filters; base::FilePath default_path(base::FilePath::FromUTF8Unsafe(url)); if (!file_dialog::ShowSaveDialog(owner_window(), url, "", default_path, - filters, &path)) { + filters, "", "", false, &path)) { base::StringValue url_value(url); web_contents_->CallClientFunction( "DevToolsAPI.canceledSaveURL", &url_value, nullptr, nullptr); diff --git a/atom/browser/ui/file_dialog.h b/atom/browser/ui/file_dialog.h index a8703bebf8..8b2d7f0d99 100644 --- a/atom/browser/ui/file_dialog.h +++ b/atom/browser/ui/file_dialog.h @@ -58,6 +58,9 @@ bool ShowSaveDialog(atom::NativeWindow* parent_window, const std::string& button_label, const base::FilePath& default_path, const Filters& filters, + const std::string& message, + const std::string& name_field_label, + const bool& shows_tag_field, base::FilePath* path); void ShowSaveDialog(atom::NativeWindow* parent_window, @@ -65,6 +68,9 @@ void ShowSaveDialog(atom::NativeWindow* parent_window, const std::string& button_label, const base::FilePath& default_path, const Filters& filters, + const std::string& message, + const std::string& name_field_label, + const bool& shows_tag_field, const SaveDialogCallback& callback); } // namespace file_dialog diff --git a/atom/browser/ui/file_dialog_gtk.cc b/atom/browser/ui/file_dialog_gtk.cc index 18f53eba6f..c39281d8f6 100644 --- a/atom/browser/ui/file_dialog_gtk.cc +++ b/atom/browser/ui/file_dialog_gtk.cc @@ -275,6 +275,9 @@ bool ShowSaveDialog(atom::NativeWindow* parent_window, const std::string& button_label, const base::FilePath& default_path, const Filters& filters, + const std::string& message, + const std::string& name_field_label, + const bool& shows_tag_field, base::FilePath* path) { FileChooserDialog save_dialog(GTK_FILE_CHOOSER_ACTION_SAVE, parent_window, title, button_label, default_path, filters); diff --git a/atom/browser/ui/file_dialog_mac.mm b/atom/browser/ui/file_dialog_mac.mm index 9492fe90ee..ee07d2af00 100644 --- a/atom/browser/ui/file_dialog_mac.mm +++ b/atom/browser/ui/file_dialog_mac.mm @@ -47,13 +47,24 @@ void SetupDialog(NSSavePanel* dialog, const std::string& title, const std::string& button_label, const base::FilePath& default_path, - const Filters& filters) { + const Filters& filters, + const std::string& message, + const std::string& name_field_label, + const bool& shows_tag_field) { if (!title.empty()) [dialog setTitle:base::SysUTF8ToNSString(title)]; if (!button_label.empty()) [dialog setPrompt:base::SysUTF8ToNSString(button_label)]; + if (!message.empty()) + [dialog setMessage:base::SysUTF8ToNSString(message)]; + + if (!name_field_label.empty()) + [dialog setNameFieldLabel:base::SysUTF8ToNSString(name_field_label)]; + + [dialog setShowsTagField:shows_tag_field]; + NSString* default_dir = nil; NSString* default_filename = nil; if (!default_path.empty()) { @@ -127,7 +138,8 @@ bool ShowOpenDialog(atom::NativeWindow* parent_window, DCHECK(paths); NSOpenPanel* dialog = [NSOpenPanel openPanel]; - SetupDialog(dialog, title, button_label, default_path, filters); +// TODO yamgent: Fix this + SetupDialog(dialog, title, button_label, default_path, filters, "", "", false); SetupDialogForProperties(dialog, properties); int chosen = RunModalDialog(dialog, parent_window); @@ -147,7 +159,8 @@ void ShowOpenDialog(atom::NativeWindow* parent_window, const OpenDialogCallback& c) { NSOpenPanel* dialog = [NSOpenPanel openPanel]; - SetupDialog(dialog, title, button_label, default_path, filters); +// TODO yamgent: Fix this + SetupDialog(dialog, title, button_label, default_path, filters, "", "", false); SetupDialogForProperties(dialog, properties); // Duplicate the callback object here since c is a reference and gcd would @@ -172,11 +185,15 @@ bool ShowSaveDialog(atom::NativeWindow* parent_window, const std::string& button_label, const base::FilePath& default_path, const Filters& filters, + const std::string& message, + const std::string& name_field_label, + const bool& shows_tag_field, base::FilePath* path) { DCHECK(path); NSSavePanel* dialog = [NSSavePanel savePanel]; - SetupDialog(dialog, title, button_label, default_path, filters); + SetupDialog(dialog, title, button_label, default_path, filters, message, + name_field_label, shows_tag_field); int chosen = RunModalDialog(dialog, parent_window); if (chosen == NSFileHandlingPanelCancelButton || ![[dialog URL] isFileURL]) @@ -191,10 +208,14 @@ void ShowSaveDialog(atom::NativeWindow* parent_window, const std::string& button_label, const base::FilePath& default_path, const Filters& filters, + const std::string& message, + const std::string& name_field_label, + const bool& shows_tag_field, const SaveDialogCallback& c) { NSSavePanel* dialog = [NSSavePanel savePanel]; - SetupDialog(dialog, title, button_label, default_path, filters); + SetupDialog(dialog, title, button_label, default_path, filters, message, + name_field_label, shows_tag_field); [dialog setCanSelectHiddenExtension:YES]; __block SaveDialogCallback callback = c; diff --git a/atom/browser/ui/file_dialog_win.cc b/atom/browser/ui/file_dialog_win.cc index 8e973432f5..2c80175624 100644 --- a/atom/browser/ui/file_dialog_win.cc +++ b/atom/browser/ui/file_dialog_win.cc @@ -268,6 +268,9 @@ bool ShowSaveDialog(atom::NativeWindow* parent_window, const std::string& button_label, const base::FilePath& default_path, const Filters& filters, + const std::string& message, + const std::string& name_field_label, + const bool& shows_tag_field, base::FilePath* path) { FileDialog save_dialog( default_path, title, button_label, filters, @@ -289,6 +292,9 @@ void ShowSaveDialog(atom::NativeWindow* parent, const std::string& button_label, const base::FilePath& default_path, const Filters& filters, + const std::string& message, + const std::string& name_field_label, + const bool& shows_tag_field, const SaveDialogCallback& callback) { RunState run_state; if (!CreateDialogThread(&run_state)) { diff --git a/atom/browser/web_dialog_helper.cc b/atom/browser/web_dialog_helper.cc index e3942f1f71..e5bd46672f 100644 --- a/atom/browser/web_dialog_helper.cc +++ b/atom/browser/web_dialog_helper.cc @@ -90,6 +90,9 @@ void WebDialogHelper::RunFileChooser( "", params.default_file_name, filters, + "", + "", + false, &path)) { content::FileChooserFileInfo info; info.file_path = path; diff --git a/lib/browser/api/dialog.js b/lib/browser/api/dialog.js index 85572b3b4f..4088431c15 100644 --- a/lib/browser/api/dialog.js +++ b/lib/browser/api/dialog.js @@ -136,7 +136,8 @@ module.exports = { } } - let {buttonLabel, defaultPath, filters, title} = options + let {buttonLabel, defaultPath, filters, title, message, nameFieldLabel, + showsTagField} = options if (title == null) { title = '' @@ -160,10 +161,27 @@ module.exports = { filters = [] } + if (message == null) { + message = '' + } else if (typeof message !== 'string') { + throw new TypeError('Message must be a string') + } + + if (nameFieldLabel == null) { + nameFieldLabel = '' + } else if (typeof nameFieldLabel !== 'string') { + throw new TypeError('Name field label must be a string') + } + + if (showsTagField == null) { + showsTagField = false + } + const wrappedCallback = typeof callback === 'function' ? function (success, result) { return callback(success ? result : void 0) } : null return binding.showSaveDialog(title, buttonLabel, defaultPath, filters, + message, nameFieldLabel, showsTagField, window, wrappedCallback) }, From cc22149bebbc86c47734348153ac9672bc9f941b Mon Sep 17 00:00:00 2001 From: Tan Wang Leng Date: Wed, 1 Feb 2017 23:36:06 +0800 Subject: [PATCH 072/925] :memo: Update docs for new attributes in showSaveDialog() --- docs/api/dialog.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/api/dialog.md b/docs/api/dialog.md index 00a7e92ef1..2b85ee34e5 100644 --- a/docs/api/dialog.md +++ b/docs/api/dialog.md @@ -94,6 +94,10 @@ shown. * `buttonLabel` String (optional) - Custom label for the confirmation button, when left empty the default label will be used. * `filters` [FileFilter[]](structures/file-filter.md) (optional) + * `message` (optional) _macOS_ + * `nameFieldLabel` String (optional) _macOS_ - Custom label for the string displayed + in front of the filename text field. + * `showsTagField` Boolean (optional) _macOS_ * `callback` Function (optional) * `filename` String From 36209ddd90a1a54534eee88ef2d98da4758bcc77 Mon Sep 17 00:00:00 2001 From: Tan Wang Leng Date: Thu, 2 Feb 2017 00:01:01 +0800 Subject: [PATCH 073/925] :apple: Add additional options for Mac's open dialog Support an additional attributes available in macOS's NSOpenPanel: message. --- atom/browser/api/atom_api_dialog.cc | 5 +++-- atom/browser/common_web_contents_delegate.cc | 2 +- atom/browser/ui/file_dialog.h | 2 ++ atom/browser/ui/file_dialog_gtk.cc | 2 ++ atom/browser/ui/file_dialog_mac.mm | 12 ++++++++---- atom/browser/ui/file_dialog_win.cc | 2 ++ atom/browser/web_dialog_helper.cc | 1 + lib/browser/api/dialog.js | 12 ++++++++++-- 8 files changed, 29 insertions(+), 9 deletions(-) diff --git a/atom/browser/api/atom_api_dialog.cc b/atom/browser/api/atom_api_dialog.cc index 1efb78d72d..d99480712d 100644 --- a/atom/browser/api/atom_api_dialog.cc +++ b/atom/browser/api/atom_api_dialog.cc @@ -71,6 +71,7 @@ void ShowOpenDialog(const std::string& title, const base::FilePath& default_path, const file_dialog::Filters& filters, int properties, + const std::string& message, atom::NativeWindow* window, mate::Arguments* args) { v8::Local peek = args->PeekNext(); @@ -79,11 +80,11 @@ void ShowOpenDialog(const std::string& title, peek, &callback)) { file_dialog::ShowOpenDialog(window, title, button_label, default_path, - filters, properties, callback); + filters, properties, message, callback); } else { std::vector paths; if (file_dialog::ShowOpenDialog(window, title, button_label, default_path, - filters, properties, &paths)) + filters, properties, message, &paths)) args->Return(paths); } } diff --git a/atom/browser/common_web_contents_delegate.cc b/atom/browser/common_web_contents_delegate.cc index c3cca231c3..5b3961464c 100644 --- a/atom/browser/common_web_contents_delegate.cc +++ b/atom/browser/common_web_contents_delegate.cc @@ -363,7 +363,7 @@ void CommonWebContentsDelegate::DevToolsAddFileSystem( std::vector paths; int flag = file_dialog::FILE_DIALOG_OPEN_DIRECTORY; if (!file_dialog::ShowOpenDialog(owner_window(), "", "", default_path, - filters, flag, &paths)) + filters, flag, "", &paths)) return; path = paths[0]; diff --git a/atom/browser/ui/file_dialog.h b/atom/browser/ui/file_dialog.h index 8b2d7f0d99..4759e503b2 100644 --- a/atom/browser/ui/file_dialog.h +++ b/atom/browser/ui/file_dialog.h @@ -43,6 +43,7 @@ bool ShowOpenDialog(atom::NativeWindow* parent_window, const base::FilePath& default_path, const Filters& filters, int properties, + const std::string& message, std::vector* paths); void ShowOpenDialog(atom::NativeWindow* parent_window, @@ -51,6 +52,7 @@ void ShowOpenDialog(atom::NativeWindow* parent_window, const base::FilePath& default_path, const Filters& filters, int properties, + const std::string& message, const OpenDialogCallback& callback); bool ShowSaveDialog(atom::NativeWindow* parent_window, diff --git a/atom/browser/ui/file_dialog_gtk.cc b/atom/browser/ui/file_dialog_gtk.cc index c39281d8f6..db79ceb171 100644 --- a/atom/browser/ui/file_dialog_gtk.cc +++ b/atom/browser/ui/file_dialog_gtk.cc @@ -236,6 +236,7 @@ bool ShowOpenDialog(atom::NativeWindow* parent_window, const base::FilePath& default_path, const Filters& filters, int properties, + const std::string& message, std::vector* paths) { GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN; if (properties & FILE_DIALOG_OPEN_DIRECTORY) @@ -260,6 +261,7 @@ void ShowOpenDialog(atom::NativeWindow* parent_window, const base::FilePath& default_path, const Filters& filters, int properties, + const std::string& message, const OpenDialogCallback& callback) { GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN; if (properties & FILE_DIALOG_OPEN_DIRECTORY) diff --git a/atom/browser/ui/file_dialog_mac.mm b/atom/browser/ui/file_dialog_mac.mm index ee07d2af00..8c88531bd1 100644 --- a/atom/browser/ui/file_dialog_mac.mm +++ b/atom/browser/ui/file_dialog_mac.mm @@ -134,12 +134,14 @@ bool ShowOpenDialog(atom::NativeWindow* parent_window, const base::FilePath& default_path, const Filters& filters, int properties, + const std::string& message, std::vector* paths) { DCHECK(paths); NSOpenPanel* dialog = [NSOpenPanel openPanel]; -// TODO yamgent: Fix this - SetupDialog(dialog, title, button_label, default_path, filters, "", "", false); + SetupDialog(dialog, title, button_label, default_path, filters, message, + // NSOpenPanel does not support name_field_label and shows_tag_field + "", false); SetupDialogForProperties(dialog, properties); int chosen = RunModalDialog(dialog, parent_window); @@ -156,11 +158,13 @@ void ShowOpenDialog(atom::NativeWindow* parent_window, const base::FilePath& default_path, const Filters& filters, int properties, + const std::string& message, const OpenDialogCallback& c) { NSOpenPanel* dialog = [NSOpenPanel openPanel]; -// TODO yamgent: Fix this - SetupDialog(dialog, title, button_label, default_path, filters, "", "", false); + SetupDialog(dialog, title, button_label, default_path, filters, message, + // NSOpenPanel does not support name_field_label and shows_tag_field + "", false); SetupDialogForProperties(dialog, properties); // Duplicate the callback object here since c is a reference and gcd would diff --git a/atom/browser/ui/file_dialog_win.cc b/atom/browser/ui/file_dialog_win.cc index 2c80175624..dc28acb348 100644 --- a/atom/browser/ui/file_dialog_win.cc +++ b/atom/browser/ui/file_dialog_win.cc @@ -198,6 +198,7 @@ bool ShowOpenDialog(atom::NativeWindow* parent_window, const base::FilePath& default_path, const Filters& filters, int properties, + const std::string& message, std::vector* paths) { int options = FOS_FORCEFILESYSTEM | FOS_FILEMUSTEXIST; if (properties & FILE_DIALOG_OPEN_DIRECTORY) @@ -250,6 +251,7 @@ void ShowOpenDialog(atom::NativeWindow* parent, const base::FilePath& default_path, const Filters& filters, int properties, + const std::string& message, const OpenDialogCallback& callback) { RunState run_state; if (!CreateDialogThread(&run_state)) { diff --git a/atom/browser/web_dialog_helper.cc b/atom/browser/web_dialog_helper.cc index e5bd46672f..0e28b07525 100644 --- a/atom/browser/web_dialog_helper.cc +++ b/atom/browser/web_dialog_helper.cc @@ -125,6 +125,7 @@ void WebDialogHelper::RunFileChooser( default_file_path, filters, flags, + "", &paths)) { for (auto& path : paths) { content::FileChooserFileInfo info; diff --git a/lib/browser/api/dialog.js b/lib/browser/api/dialog.js index 4088431c15..73a519cc20 100644 --- a/lib/browser/api/dialog.js +++ b/lib/browser/api/dialog.js @@ -81,7 +81,8 @@ module.exports = { } } - let {buttonLabel, defaultPath, filters, properties, title} = options + let {buttonLabel, defaultPath, filters, properties, title, + message} = options if (properties == null) { properties = ['openFile'] @@ -118,11 +119,18 @@ module.exports = { filters = [] } + if (message == null) { + message = '' + } else if (typeof message !== 'string') { + throw new TypeError('Message must be a string') + } + const wrappedCallback = typeof callback === 'function' ? function (success, result) { return callback(success ? result : void 0) } : null return binding.showOpenDialog(title, buttonLabel, defaultPath, filters, - dialogProperties, window, wrappedCallback) + dialogProperties, message, window, + wrappedCallback) }, showSaveDialog: function (...args) { From 8843fef06587f6e8a91cb9c97cdae4b41deddc87 Mon Sep 17 00:00:00 2001 From: Tan Wang Leng Date: Thu, 2 Feb 2017 00:01:19 +0800 Subject: [PATCH 074/925] :memo: Update docs for new attributes in showOpenDialog() --- docs/api/dialog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/api/dialog.md b/docs/api/dialog.md index 2b85ee34e5..1bdb5f5cc7 100644 --- a/docs/api/dialog.md +++ b/docs/api/dialog.md @@ -51,6 +51,7 @@ The `dialog` module has the following methods: untouched on Windows. For example, a button label of `Vie&w` will be converted to `Vie_w` on Linux and `View` on macOS and can be selected via `Alt-W` on Windows and Linux. + * `message` (optional) _macOS_ * `callback` Function (optional) * `filePaths` String[] - An array of file paths chosen by the user From 1bf8270c0d61a6df8a9071485520a2972db00a4e Mon Sep 17 00:00:00 2001 From: Tan Wang Leng Date: Thu, 2 Feb 2017 20:21:20 +0800 Subject: [PATCH 075/925] :checkered_flag: Fix dialog method calls for Windows --- atom/browser/ui/file_dialog_win.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/atom/browser/ui/file_dialog_win.cc b/atom/browser/ui/file_dialog_win.cc index dc28acb348..365d28cbac 100644 --- a/atom/browser/ui/file_dialog_win.cc +++ b/atom/browser/ui/file_dialog_win.cc @@ -169,7 +169,7 @@ void RunOpenDialogInNewThread(const RunState& run_state, const OpenDialogCallback& callback) { std::vector paths; bool result = ShowOpenDialog(parent, title, button_label, default_path, - filters, properties, &paths); + filters, properties, "", &paths); run_state.ui_task_runner->PostTask(FROM_HERE, base::Bind(callback, result, paths)); run_state.ui_task_runner->DeleteSoon(FROM_HERE, run_state.dialog_thread); @@ -184,7 +184,7 @@ void RunSaveDialogInNewThread(const RunState& run_state, const SaveDialogCallback& callback) { base::FilePath path; bool result = ShowSaveDialog(parent, title, button_label, default_path, - filters, &path); + filters, "", "", false, &path); run_state.ui_task_runner->PostTask(FROM_HERE, base::Bind(callback, result, path)); run_state.ui_task_runner->DeleteSoon(FROM_HERE, run_state.dialog_thread); From b0487b7d8431bf38d351f38f940bf9d468bc8423 Mon Sep 17 00:00:00 2001 From: Tan Wang Leng Date: Thu, 2 Feb 2017 21:01:02 +0800 Subject: [PATCH 076/925] Fix wrong signature for gtk's ShowSaveDialog() --- atom/browser/ui/file_dialog_gtk.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/atom/browser/ui/file_dialog_gtk.cc b/atom/browser/ui/file_dialog_gtk.cc index db79ceb171..54e32cdf7d 100644 --- a/atom/browser/ui/file_dialog_gtk.cc +++ b/atom/browser/ui/file_dialog_gtk.cc @@ -298,6 +298,9 @@ void ShowSaveDialog(atom::NativeWindow* parent_window, const std::string& button_label, const base::FilePath& default_path, const Filters& filters, + const std::string& message, + const std::string& name_field_label, + const bool& shows_tag_field, const SaveDialogCallback& callback) { FileChooserDialog* save_dialog = new FileChooserDialog( GTK_FILE_CHOOSER_ACTION_SAVE, parent_window, title, button_label, From 9423143211bc2368a8db188c9381927215228c86 Mon Sep 17 00:00:00 2001 From: Tan Wang Leng Date: Thu, 9 Feb 2017 21:01:40 +0800 Subject: [PATCH 077/925] Change the default value of showsTagField to true The default value of showsTagField in macOS's NSSavePanel is true. Therefore, in order to follow the standard behavior and not break backwards-compatibility, let's change the default value of showsTagField to true. Reference: https://developer.apple.com/reference/appkit/nssavepanel/1525589-showstagfield?language=objc --- lib/browser/api/dialog.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/browser/api/dialog.js b/lib/browser/api/dialog.js index 73a519cc20..e046eadc16 100644 --- a/lib/browser/api/dialog.js +++ b/lib/browser/api/dialog.js @@ -182,7 +182,7 @@ module.exports = { } if (showsTagField == null) { - showsTagField = false + showsTagField = true } const wrappedCallback = typeof callback === 'function' ? function (success, result) { From 72723646dd7719f57cf13eb1ea5b9d448f868f99 Mon Sep 17 00:00:00 2001 From: Tan Wang Leng Date: Thu, 9 Feb 2017 21:23:02 +0800 Subject: [PATCH 078/925] Add tests to check errors in parameters --- spec/api-dialog-spec.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/spec/api-dialog-spec.js b/spec/api-dialog-spec.js index 9b90b0353d..a07548e953 100644 --- a/spec/api-dialog-spec.js +++ b/spec/api-dialog-spec.js @@ -19,6 +19,10 @@ describe('dialog module', () => { assert.throws(() => { dialog.showOpenDialog({defaultPath: {}}) }, /Default path must be a string/) + + assert.throws(() => { + dialog.showOpenDialog({message: {}}) + }, /Message must be a string/) }) }) @@ -35,6 +39,14 @@ describe('dialog module', () => { assert.throws(() => { dialog.showSaveDialog({defaultPath: {}}) }, /Default path must be a string/) + + assert.throws(() => { + dialog.showSaveDialog({message: {}}) + }, /Message must be a string/) + + assert.throws(() => { + dialog.showSaveDialog({nameFieldLabel: {}}) + }, /Name field label must be a string/) }) }) From a4a71a1dc985dd8a48b3bcfb871bf65140885353 Mon Sep 17 00:00:00 2001 From: Tan Wang Leng Date: Thu, 9 Feb 2017 21:33:33 +0800 Subject: [PATCH 079/925] Change qualifier of ShowSaveDialog() parameter The normal convention in the codebase is to not use references or 'const' for primitives like 'bool' and 'int'. --- atom/browser/api/atom_api_dialog.cc | 2 +- atom/browser/ui/file_dialog.h | 4 ++-- atom/browser/ui/file_dialog_gtk.cc | 4 ++-- atom/browser/ui/file_dialog_mac.mm | 4 ++-- atom/browser/ui/file_dialog_win.cc | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/atom/browser/api/atom_api_dialog.cc b/atom/browser/api/atom_api_dialog.cc index d99480712d..5f0b7fec5f 100644 --- a/atom/browser/api/atom_api_dialog.cc +++ b/atom/browser/api/atom_api_dialog.cc @@ -95,7 +95,7 @@ void ShowSaveDialog(const std::string& title, const file_dialog::Filters& filters, const std::string& message, const std::string& name_field_label, - const bool& shows_tag_field, + bool shows_tag_field, atom::NativeWindow* window, mate::Arguments* args) { v8::Local peek = args->PeekNext(); diff --git a/atom/browser/ui/file_dialog.h b/atom/browser/ui/file_dialog.h index 4759e503b2..546722b4e6 100644 --- a/atom/browser/ui/file_dialog.h +++ b/atom/browser/ui/file_dialog.h @@ -62,7 +62,7 @@ bool ShowSaveDialog(atom::NativeWindow* parent_window, const Filters& filters, const std::string& message, const std::string& name_field_label, - const bool& shows_tag_field, + bool shows_tag_field, base::FilePath* path); void ShowSaveDialog(atom::NativeWindow* parent_window, @@ -72,7 +72,7 @@ void ShowSaveDialog(atom::NativeWindow* parent_window, const Filters& filters, const std::string& message, const std::string& name_field_label, - const bool& shows_tag_field, + bool shows_tag_field, const SaveDialogCallback& callback); } // namespace file_dialog diff --git a/atom/browser/ui/file_dialog_gtk.cc b/atom/browser/ui/file_dialog_gtk.cc index 54e32cdf7d..f4aaa38037 100644 --- a/atom/browser/ui/file_dialog_gtk.cc +++ b/atom/browser/ui/file_dialog_gtk.cc @@ -279,7 +279,7 @@ bool ShowSaveDialog(atom::NativeWindow* parent_window, const Filters& filters, const std::string& message, const std::string& name_field_label, - const bool& shows_tag_field, + bool shows_tag_field, base::FilePath* path) { FileChooserDialog save_dialog(GTK_FILE_CHOOSER_ACTION_SAVE, parent_window, title, button_label, default_path, filters); @@ -300,7 +300,7 @@ void ShowSaveDialog(atom::NativeWindow* parent_window, const Filters& filters, const std::string& message, const std::string& name_field_label, - const bool& shows_tag_field, + bool shows_tag_field, const SaveDialogCallback& callback) { FileChooserDialog* save_dialog = new FileChooserDialog( GTK_FILE_CHOOSER_ACTION_SAVE, parent_window, title, button_label, diff --git a/atom/browser/ui/file_dialog_mac.mm b/atom/browser/ui/file_dialog_mac.mm index 8c88531bd1..e916f8d23d 100644 --- a/atom/browser/ui/file_dialog_mac.mm +++ b/atom/browser/ui/file_dialog_mac.mm @@ -191,7 +191,7 @@ bool ShowSaveDialog(atom::NativeWindow* parent_window, const Filters& filters, const std::string& message, const std::string& name_field_label, - const bool& shows_tag_field, + bool shows_tag_field, base::FilePath* path) { DCHECK(path); NSSavePanel* dialog = [NSSavePanel savePanel]; @@ -214,7 +214,7 @@ void ShowSaveDialog(atom::NativeWindow* parent_window, const Filters& filters, const std::string& message, const std::string& name_field_label, - const bool& shows_tag_field, + bool shows_tag_field, const SaveDialogCallback& c) { NSSavePanel* dialog = [NSSavePanel savePanel]; diff --git a/atom/browser/ui/file_dialog_win.cc b/atom/browser/ui/file_dialog_win.cc index 365d28cbac..7b5a1f4897 100644 --- a/atom/browser/ui/file_dialog_win.cc +++ b/atom/browser/ui/file_dialog_win.cc @@ -272,7 +272,7 @@ bool ShowSaveDialog(atom::NativeWindow* parent_window, const Filters& filters, const std::string& message, const std::string& name_field_label, - const bool& shows_tag_field, + bool shows_tag_field, base::FilePath* path) { FileDialog save_dialog( default_path, title, button_label, filters, @@ -296,7 +296,7 @@ void ShowSaveDialog(atom::NativeWindow* parent, const Filters& filters, const std::string& message, const std::string& name_field_label, - const bool& shows_tag_field, + bool shows_tag_field, const SaveDialogCallback& callback) { RunState run_state; if (!CreateDialogThread(&run_state)) { From 347dc835b584917db72eb46d189f7a7a6cad0aff Mon Sep 17 00:00:00 2001 From: Tan Wang Leng Date: Thu, 9 Feb 2017 21:47:26 +0800 Subject: [PATCH 080/925] Fix code formatting --- atom/browser/ui/file_dialog_mac.mm | 10 ++++------ lib/browser/api/dialog.js | 3 +-- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/atom/browser/ui/file_dialog_mac.mm b/atom/browser/ui/file_dialog_mac.mm index e916f8d23d..70d5694562 100644 --- a/atom/browser/ui/file_dialog_mac.mm +++ b/atom/browser/ui/file_dialog_mac.mm @@ -139,9 +139,8 @@ bool ShowOpenDialog(atom::NativeWindow* parent_window, DCHECK(paths); NSOpenPanel* dialog = [NSOpenPanel openPanel]; - SetupDialog(dialog, title, button_label, default_path, filters, message, - // NSOpenPanel does not support name_field_label and shows_tag_field - "", false); + // NSOpenPanel does not support name_field_label and shows_tag_field + SetupDialog(dialog, title, button_label, default_path, filters, message, "", false); SetupDialogForProperties(dialog, properties); int chosen = RunModalDialog(dialog, parent_window); @@ -162,9 +161,8 @@ void ShowOpenDialog(atom::NativeWindow* parent_window, const OpenDialogCallback& c) { NSOpenPanel* dialog = [NSOpenPanel openPanel]; - SetupDialog(dialog, title, button_label, default_path, filters, message, - // NSOpenPanel does not support name_field_label and shows_tag_field - "", false); + // NSOpenPanel does not support name_field_label and shows_tag_field + SetupDialog(dialog, title, button_label, default_path, filters, message, "", false); SetupDialogForProperties(dialog, properties); // Duplicate the callback object here since c is a reference and gcd would diff --git a/lib/browser/api/dialog.js b/lib/browser/api/dialog.js index e046eadc16..b000e46e15 100644 --- a/lib/browser/api/dialog.js +++ b/lib/browser/api/dialog.js @@ -81,8 +81,7 @@ module.exports = { } } - let {buttonLabel, defaultPath, filters, properties, title, - message} = options + let {buttonLabel, defaultPath, filters, properties, title, message} = options if (properties == null) { properties = ['openFile'] From c8c11e68c6c519789ba686783c3d653d5f790a51 Mon Sep 17 00:00:00 2001 From: Birunthan Mohanathas Date: Mon, 6 Feb 2017 15:35:36 +0000 Subject: [PATCH 081/925] Add support for checkbox with dialog.showMessageBox This adds the `checkboxLabel` and `checkboxChecked` options to display a checkbox in the message box. Fixes #6048. --- atom/browser/api/atom_api_dialog.cc | 6 +- .../browser/atom_javascript_dialog_manager.cc | 15 ++-- atom/browser/atom_javascript_dialog_manager.h | 3 +- atom/browser/ui/message_box.h | 5 +- atom/browser/ui/message_box_gtk.cc | 45 ++++++++-- atom/browser/ui/message_box_mac.mm | 25 ++++-- atom/browser/ui/message_box_win.cc | 89 +++++++++++++------ docs/api/dialog.md | 7 ++ lib/browser/api/dialog.js | 17 +++- spec/api-dialog-spec.js | 4 + 10 files changed, 155 insertions(+), 61 deletions(-) diff --git a/atom/browser/api/atom_api_dialog.cc b/atom/browser/api/atom_api_dialog.cc index 5d853e2d59..41cad524ea 100644 --- a/atom/browser/api/atom_api_dialog.cc +++ b/atom/browser/api/atom_api_dialog.cc @@ -47,6 +47,8 @@ void ShowMessageBox(int type, const std::string& title, const std::string& message, const std::string& detail, + const std::string& checkbox_label, + bool checkbox_checked, const gfx::ImageSkia& icon, atom::NativeWindow* window, mate::Arguments* args) { @@ -56,8 +58,8 @@ void ShowMessageBox(int type, peek, &callback)) { atom::ShowMessageBox(window, (atom::MessageBoxType)type, buttons, - default_id, cancel_id, options, title, - message, detail, icon, callback); + default_id, cancel_id, options, title, message, detail, + checkbox_label, checkbox_checked, icon, callback); } else { int chosen = atom::ShowMessageBox(window, (atom::MessageBoxType)type, buttons, default_id, cancel_id, diff --git a/atom/browser/atom_javascript_dialog_manager.cc b/atom/browser/atom_javascript_dialog_manager.cc index 24197915e6..f874a99db5 100644 --- a/atom/browser/atom_javascript_dialog_manager.cc +++ b/atom/browser/atom_javascript_dialog_manager.cc @@ -38,14 +38,9 @@ void AtomJavaScriptDialogManager::RunJavaScriptDialog( } atom::ShowMessageBox(NativeWindow::FromWebContents(web_contents), - atom::MessageBoxType::MESSAGE_BOX_TYPE_NONE, - buttons, - -1, - 0, - atom::MessageBoxOptions::MESSAGE_BOX_NONE, - "", - base::UTF16ToUTF8(message_text), - "", + atom::MessageBoxType::MESSAGE_BOX_TYPE_NONE, buttons, -1, + 0, atom::MessageBoxOptions::MESSAGE_BOX_NONE, "", + base::UTF16ToUTF8(message_text), "", "", false, gfx::ImageSkia(), base::Bind(&OnMessageBoxCallback, callback)); } @@ -66,7 +61,9 @@ void AtomJavaScriptDialogManager::CancelDialogs( // static void AtomJavaScriptDialogManager::OnMessageBoxCallback( - const DialogClosedCallback& callback, int code) { + const DialogClosedCallback& callback, + int code, + bool checkbox_checked) { callback.Run(code == 0, base::string16()); } diff --git a/atom/browser/atom_javascript_dialog_manager.h b/atom/browser/atom_javascript_dialog_manager.h index e5bb6114bc..01cc76248c 100644 --- a/atom/browser/atom_javascript_dialog_manager.h +++ b/atom/browser/atom_javascript_dialog_manager.h @@ -32,7 +32,8 @@ class AtomJavaScriptDialogManager : public content::JavaScriptDialogManager { private: static void OnMessageBoxCallback(const DialogClosedCallback& callback, - int code); + int code, + bool checkbox_checked); }; } // namespace atom diff --git a/atom/browser/ui/message_box.h b/atom/browser/ui/message_box.h index 16eef3c463..6c826719ee 100644 --- a/atom/browser/ui/message_box.h +++ b/atom/browser/ui/message_box.h @@ -32,7 +32,8 @@ enum MessageBoxOptions { MESSAGE_BOX_NO_LINK = 1 << 0, }; -typedef base::Callback MessageBoxCallback; +typedef base::Callback + MessageBoxCallback; int ShowMessageBox(NativeWindow* parent_window, MessageBoxType type, @@ -54,6 +55,8 @@ void ShowMessageBox(NativeWindow* parent_window, const std::string& title, const std::string& message, const std::string& detail, + const std::string& checkbox_label, + bool checkbox_checked, const gfx::ImageSkia& icon, const MessageBoxCallback& callback); diff --git a/atom/browser/ui/message_box_gtk.cc b/atom/browser/ui/message_box_gtk.cc index 0717d4d06d..40d23f19eb 100644 --- a/atom/browser/ui/message_box_gtk.cc +++ b/atom/browser/ui/message_box_gtk.cc @@ -36,8 +36,11 @@ class GtkMessageBox : public NativeWindowObserver { const std::string& title, const std::string& message, const std::string& detail, + const std::string& checkbox_label, + bool checkbox_checked, const gfx::ImageSkia& icon) : cancel_id_(cancel_id), + checkbox_checked_(false), parent_(static_cast(parent_window)) { // Create dialog. dialog_ = gtk_message_dialog_new( @@ -68,6 +71,18 @@ class GtkMessageBox : public NativeWindowObserver { g_object_unref(pixbuf); } + if (!checkbox_label.empty()) { + GtkWidget* message_area = + gtk_message_dialog_get_message_area(GTK_MESSAGE_DIALOG(dialog_)); + GtkWidget* check_button = + gtk_check_button_new_with_label(checkbox_label.c_str()); + g_signal_connect(check_button, "toggled", + G_CALLBACK(OnCheckboxToggledThunk), this); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check_button), + checkbox_checked); + gtk_container_add(GTK_CONTAINER(message_area), check_button); + } + // Add buttons. for (size_t i = 0; i < buttons.size(); ++i) { GtkWidget* button = gtk_dialog_add_button( @@ -154,6 +169,7 @@ class GtkMessageBox : public NativeWindowObserver { } CHROMEGTK_CALLBACK_1(GtkMessageBox, void, OnResponseDialog, int); + CHROMEGTK_CALLBACK_1(GtkMessageBox, void, OnCheckboxToggled, gpointer); private: atom::UnresponsiveSuppressor unresponsive_suppressor_; @@ -161,6 +177,8 @@ class GtkMessageBox : public NativeWindowObserver { // The id to return when the dialog is closed without pressing buttons. int cancel_id_; + bool checkbox_checked_; + NativeWindowViews* parent_; GtkWidget* dialog_; MessageBoxCallback callback_; @@ -172,12 +190,16 @@ void GtkMessageBox::OnResponseDialog(GtkWidget* widget, int response) { gtk_widget_hide(dialog_); if (response < 0) - callback_.Run(cancel_id_); + callback_.Run(cancel_id_, checkbox_checked_); else - callback_.Run(response); + callback_.Run(response, checkbox_checked_); delete this; } +void GtkMessageBox::OnCheckboxToggled(GtkWidget* widget, gpointer data) { + checkbox_checked_ = GTK_TOGGLE_BUTTON(widget)->active; +} + } // namespace int ShowMessageBox(NativeWindow* parent, @@ -190,8 +212,9 @@ int ShowMessageBox(NativeWindow* parent, const std::string& message, const std::string& detail, const gfx::ImageSkia& icon) { - return GtkMessageBox(parent, type, buttons, default_id, cancel_id, - title, message, detail, icon).RunSynchronous(); + return GtkMessageBox(parent, type, buttons, default_id, cancel_id, title, + message, detail, "", false, icon) + .RunSynchronous(); } void ShowMessageBox(NativeWindow* parent, @@ -203,18 +226,22 @@ void ShowMessageBox(NativeWindow* parent, const std::string& title, const std::string& message, const std::string& detail, + const std::string& checkbox_label, + bool checkbox_checked, const gfx::ImageSkia& icon, const MessageBoxCallback& callback) { - (new GtkMessageBox(parent, type, buttons, default_id, cancel_id, - title, message, detail, icon))->RunAsynchronous(callback); + (new GtkMessageBox(parent, type, buttons, default_id, cancel_id, title, + message, detail, checkbox_label, checkbox_checked, icon)) + ->RunAsynchronous(callback); } void ShowErrorBox(const base::string16& title, const base::string16& content) { if (Browser::Get()->is_ready()) { - GtkMessageBox(nullptr, MESSAGE_BOX_TYPE_ERROR, { "OK" }, -1, 0, "Error", + GtkMessageBox(nullptr, MESSAGE_BOX_TYPE_ERROR, {"OK"}, -1, 0, "Error", base::UTF16ToUTF8(title).c_str(), - base::UTF16ToUTF8(content).c_str(), - gfx::ImageSkia()).RunSynchronous(); + base::UTF16ToUTF8(content).c_str(), "", false, + gfx::ImageSkia()) + .RunSynchronous(); } else { fprintf(stderr, ANSI_TEXT_BOLD ANSI_BACKGROUND_GRAY diff --git a/atom/browser/ui/message_box_mac.mm b/atom/browser/ui/message_box_mac.mm index 8fd28e2446..0d03eefc20 100644 --- a/atom/browser/ui/message_box_mac.mm +++ b/atom/browser/ui/message_box_mac.mm @@ -39,7 +39,7 @@ - (void)alertDidEnd:(NSAlert*)alert returnCode:(NSInteger)returnCode contextInfo:(void*)contextInfo { - callback_.Run(returnCode); + callback_.Run(returnCode, alert.suppressionButton.state == NSOnState); [alert_ release]; [self release]; @@ -60,6 +60,8 @@ NSAlert* CreateNSAlert(NativeWindow* parent_window, const std::string& title, const std::string& message, const std::string& detail, + const std::string& checkbox_label, + bool checkbox_checked, const gfx::ImageSkia& icon) { // Ignore the title; it's the window title on other platforms and ignorable. NSAlert* alert = [[NSAlert alloc] init]; @@ -95,6 +97,12 @@ NSAlert* CreateNSAlert(NativeWindow* parent_window, [[ns_buttons objectAtIndex:default_id] setKeyEquivalent:@"\r"]; } + if (!checkbox_label.empty()) { + alert.showsSuppressionButton = YES; + alert.suppressionButton.title = base::SysUTF8ToNSString(checkbox_label); + alert.suppressionButton.state = checkbox_checked ? NSOnState : NSOffState; + } + if (!icon.isNull()) { NSImage* image = skia::SkBitmapToNSImageWithColorSpace( *icon.bitmap(), base::mac::GetGenericRGBColorSpace()); @@ -104,7 +112,7 @@ NSAlert* CreateNSAlert(NativeWindow* parent_window, return alert; } -void SetReturnCode(int* ret_code, int result) { +void SetReturnCode(int* ret_code, int result, bool checkbox_checked) { *ret_code = result; } @@ -120,9 +128,8 @@ int ShowMessageBox(NativeWindow* parent_window, const std::string& message, const std::string& detail, const gfx::ImageSkia& icon) { - NSAlert* alert = CreateNSAlert( - parent_window, type, buttons, default_id, title, message, - detail, icon); + NSAlert* alert = CreateNSAlert(parent_window, type, buttons, default_id, + title, message, detail, "", false, icon); // Use runModal for synchronous alert without parent, since we don't have a // window to wait for. @@ -154,11 +161,13 @@ void ShowMessageBox(NativeWindow* parent_window, const std::string& title, const std::string& message, const std::string& detail, + const std::string& checkbox_label, + bool checkbox_checked, const gfx::ImageSkia& icon, const MessageBoxCallback& callback) { - NSAlert* alert = CreateNSAlert( - parent_window, type, buttons, default_id, title, message, - detail, icon); + NSAlert* alert = + CreateNSAlert(parent_window, type, buttons, default_id, title, message, + detail, checkbox_label, checkbox_checked, icon); ModalDelegate* delegate = [[ModalDelegate alloc] initWithCallback:callback andAlert:alert callEndModal:false]; diff --git a/atom/browser/ui/message_box_win.cc b/atom/browser/ui/message_box_win.cc index b6777fb1da..844a057ae6 100644 --- a/atom/browser/ui/message_box_win.cc +++ b/atom/browser/ui/message_box_win.cc @@ -72,7 +72,7 @@ void MapToCommonID(const std::vector& buttons, } } -int ShowMessageBoxUTF16(HWND parent, +int ShowTaskDialogUTF16(NativeWindow* parent, MessageBoxType type, const std::vector& buttons, int default_id, @@ -81,6 +81,8 @@ int ShowMessageBoxUTF16(HWND parent, const base::string16& title, const base::string16& message, const base::string16& detail, + const base::string16& checkbox_label, + bool* checkbox_checked, const gfx::ImageSkia& icon) { TASKDIALOG_FLAGS flags = TDF_SIZE_TO_CONTENT | // Show all content. @@ -88,10 +90,14 @@ int ShowMessageBoxUTF16(HWND parent, TASKDIALOGCONFIG config = { 0 }; config.cbSize = sizeof(config); - config.hwndParent = parent; config.hInstance = GetModuleHandle(NULL); config.dwFlags = flags; + if (parent) { + config.hwndParent = + static_cast(parent)->GetAcceleratedWidget(); + } + if (default_id > 0) config.nDefaultButton = kIDStart + default_id; @@ -132,6 +138,14 @@ int ShowMessageBoxUTF16(HWND parent, config.pszContent = detail.c_str(); } + if (!checkbox_label.empty()) { + config.pszVerificationText = checkbox_label.c_str(); + + if (checkbox_checked && *checkbox_checked) { + config.dwFlags |= TDF_VERIFICATION_FLAG_CHECKED; + } + } + // Iterate through the buttons, put common buttons in dwCommonButtons // and custom buttons in pButtons. std::map id_map; @@ -151,7 +165,12 @@ int ShowMessageBoxUTF16(HWND parent, } int id = 0; - TaskDialogIndirect(&config, &id, NULL, NULL); + BOOL verificationFlagChecked = FALSE; + TaskDialogIndirect(&config, &id, nullptr, &verificationFlagChecked); + if (checkbox_checked) { + *checkbox_checked = verificationFlagChecked; + } + if (id_map.find(id) != id_map.end()) // common button. return id_map[id]; else if (id >= kIDStart) // custom button. @@ -160,6 +179,29 @@ int ShowMessageBoxUTF16(HWND parent, return cancel_id; } +int ShowTaskDialogUTF8(NativeWindow* parent, + MessageBoxType type, + const std::vector& buttons, + int default_id, + int cancel_id, + int options, + const std::string& title, + const std::string& message, + const std::string& detail, + const std::string& checkbox_label, + bool* checkbox_checked, + const gfx::ImageSkia& icon) { + std::vector utf16_buttons; + for (const auto& button : buttons) + utf16_buttons.push_back(base::UTF8ToUTF16(button)); + + return ShowTaskDialogUTF16( + parent, type, utf16_buttons, default_id, cancel_id, options, + base::UTF8ToUTF16(title), base::UTF8ToUTF16(message), + base::UTF8ToUTF16(detail), base::UTF8ToUTF16(checkbox_label), + checkbox_checked, icon); +} + void RunMessageBoxInNewThread(base::Thread* thread, NativeWindow* parent, MessageBoxType type, @@ -170,12 +212,16 @@ void RunMessageBoxInNewThread(base::Thread* thread, const std::string& title, const std::string& message, const std::string& detail, + const std::string& checkbox_label, + bool checkbox_checked, const gfx::ImageSkia& icon, const MessageBoxCallback& callback) { - int result = ShowMessageBox(parent, type, buttons, default_id, - cancel_id, options, title, message, detail, icon); + int result = ShowTaskDialogUTF8(parent, type, buttons, default_id, cancel_id, + options, title, message, detail, + checkbox_label, &checkbox_checked, icon); content::BrowserThread::PostTask( - content::BrowserThread::UI, FROM_HERE, base::Bind(callback, result)); + content::BrowserThread::UI, FROM_HERE, + base::Bind(callback, result, checkbox_checked)); content::BrowserThread::DeleteSoon( content::BrowserThread::UI, FROM_HERE, thread); } @@ -192,25 +238,9 @@ int ShowMessageBox(NativeWindow* parent, const std::string& message, const std::string& detail, const gfx::ImageSkia& icon) { - std::vector utf16_buttons; - for (const auto& button : buttons) - utf16_buttons.push_back(base::UTF8ToUTF16(button)); - - HWND hwnd_parent = parent ? - static_cast(parent)->GetAcceleratedWidget() : - NULL; - atom::UnresponsiveSuppressor suppressor; - return ShowMessageBoxUTF16(hwnd_parent, - type, - utf16_buttons, - default_id, - cancel_id, - options, - base::UTF8ToUTF16(title), - base::UTF8ToUTF16(message), - base::UTF8ToUTF16(detail), - icon); + return ShowTaskDialogUTF8(parent, type, buttons, default_id, cancel_id, + options, title, message, detail, "", nullptr, icon); } void ShowMessageBox(NativeWindow* parent, @@ -222,13 +252,15 @@ void ShowMessageBox(NativeWindow* parent, const std::string& title, const std::string& message, const std::string& detail, + const std::string& checkbox_label, + bool checkbox_checked, const gfx::ImageSkia& icon, const MessageBoxCallback& callback) { std::unique_ptr thread( new base::Thread(ATOM_PRODUCT_NAME "MessageBoxThread")); thread->init_com_with_mta(false); if (!thread->Start()) { - callback.Run(cancel_id); + callback.Run(cancel_id, checkbox_checked); return; } @@ -237,13 +269,14 @@ void ShowMessageBox(NativeWindow* parent, FROM_HERE, base::Bind(&RunMessageBoxInNewThread, base::Unretained(unretained), parent, type, buttons, default_id, cancel_id, options, title, - message, detail, icon, callback)); + message, detail, checkbox_label, checkbox_checked, icon, + callback)); } void ShowErrorBox(const base::string16& title, const base::string16& content) { atom::UnresponsiveSuppressor suppressor; - ShowMessageBoxUTF16(NULL, MESSAGE_BOX_TYPE_ERROR, {}, -1, 0, 0, L"Error", - title, content, gfx::ImageSkia()); + ShowTaskDialogUTF16(nullptr, MESSAGE_BOX_TYPE_ERROR, {}, -1, 0, 0, L"Error", + title, content, L"", nullptr, gfx::ImageSkia()); } } // namespace atom diff --git a/docs/api/dialog.md b/docs/api/dialog.md index 00a7e92ef1..6a7cdb225b 100644 --- a/docs/api/dialog.md +++ b/docs/api/dialog.md @@ -122,6 +122,11 @@ will be passed via `callback(filename)` * `title` String (optional) - Title of the message box, some platforms will not show it. * `message` String - Content of the message box. * `detail` String (optional) - Extra information of the message. + * `checkboxLabel` String (optional) - If provided, the message box will + include a checkbox with the given label. The checkbox state can be + inspected only when using `callback`. + * `checkboxChecked` Boolean (optional) - Initial checked state of the + checkbox. `false` by default. * `icon` [NativeImage](native-image.md) (optional) * `cancelId` Integer (optional) - The value will be returned when user cancels the dialog instead of clicking the buttons of the dialog. By default it is the index @@ -135,6 +140,8 @@ will be passed via `callback(filename)` set `noLink` to `true`. * `callback` Function (optional) * `response` Number - The index of the button that was clicked + * `checkboxChecked` Boolean - The checked state of the checkbox if + `checkboxLabel` was set. Otherwise `false`. Returns `Integer`, the index of the clicked button, if a callback is provided it returns undefined. diff --git a/lib/browser/api/dialog.js b/lib/browser/api/dialog.js index 85572b3b4f..d0266d912d 100644 --- a/lib/browser/api/dialog.js +++ b/lib/browser/api/dialog.js @@ -178,7 +178,10 @@ module.exports = { } } - let {buttons, cancelId, defaultId, detail, icon, message, title, type} = options + let { + buttons, cancelId, checkboxLabel, checkboxChecked, defaultId, detail, + icon, message, title, type + } = options if (type == null) { type = 'none' @@ -217,6 +220,14 @@ module.exports = { throw new TypeError('Detail must be a string') } + checkboxChecked = !!checkboxChecked + + if (checkboxLabel == null) { + checkboxLabel = '' + } else if (typeof checkboxLabel !== 'string') { + throw new TypeError('checkboxLabel must be a string') + } + if (icon == null) { icon = null } @@ -239,8 +250,8 @@ module.exports = { const flags = options.noLink ? messageBoxOptions.noLink : 0 return binding.showMessageBox(messageBoxType, buttons, defaultId, cancelId, - flags, title, message, detail, icon, window, - callback) + flags, title, message, detail, checkboxLabel, + checkboxChecked, icon, window, callback) }, showErrorBox: function (...args) { diff --git a/spec/api-dialog-spec.js b/spec/api-dialog-spec.js index 9b90b0353d..966cbc4bee 100644 --- a/spec/api-dialog-spec.js +++ b/spec/api-dialog-spec.js @@ -59,6 +59,10 @@ describe('dialog module', () => { assert.throws(() => { dialog.showMessageBox({detail: 3.14}) }, /Detail must be a string/) + + assert.throws(() => { + dialog.showMessageBox({checkboxLabel: false}) + }, /checkboxLabel must be a string/) }) }) From 7994acf0eab6c38a4e1020ed93ac89b2c3f201ac Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 9 Feb 2017 09:49:09 -0800 Subject: [PATCH 082/925] Use CHROMEGTK_CALLBACK_0 and ignore gpointer data --- atom/browser/ui/message_box_gtk.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/atom/browser/ui/message_box_gtk.cc b/atom/browser/ui/message_box_gtk.cc index 40d23f19eb..a7bbe51ecf 100644 --- a/atom/browser/ui/message_box_gtk.cc +++ b/atom/browser/ui/message_box_gtk.cc @@ -169,7 +169,7 @@ class GtkMessageBox : public NativeWindowObserver { } CHROMEGTK_CALLBACK_1(GtkMessageBox, void, OnResponseDialog, int); - CHROMEGTK_CALLBACK_1(GtkMessageBox, void, OnCheckboxToggled, gpointer); + CHROMEGTK_CALLBACK_0(GtkMessageBox, void, OnCheckboxToggled); private: atom::UnresponsiveSuppressor unresponsive_suppressor_; @@ -196,7 +196,7 @@ void GtkMessageBox::OnResponseDialog(GtkWidget* widget, int response) { delete this; } -void GtkMessageBox::OnCheckboxToggled(GtkWidget* widget, gpointer data) { +void GtkMessageBox::OnCheckboxToggled(GtkWidget* widget) { checkbox_checked_ = GTK_TOGGLE_BUTTON(widget)->active; } From 867bb5a94e9778db1cc05f2ab41b761f9030b389 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 7 Feb 2017 17:32:58 -0800 Subject: [PATCH 083/925] Add DialogSettings helper struct --- atom/browser/api/atom_api_dialog.cc | 43 +++++---- .../browser/atom_download_manager_delegate.cc | 9 +- atom/browser/common_web_contents_delegate.cc | 18 ++-- atom/browser/ui/file_dialog.h | 35 +++---- atom/browser/ui/file_dialog_gtk.cc | 76 +++++----------- atom/browser/ui/file_dialog_mac.mm | 78 +++++++--------- atom/browser/ui/file_dialog_win.cc | 91 ++++++------------- atom/browser/web_dialog_helper.cc | 27 +++--- lib/browser/api/dialog.js | 9 +- 9 files changed, 151 insertions(+), 235 deletions(-) diff --git a/atom/browser/api/atom_api_dialog.cc b/atom/browser/api/atom_api_dialog.cc index 41cad524ea..61999e1c70 100644 --- a/atom/browser/api/atom_api_dialog.cc +++ b/atom/browser/api/atom_api_dialog.cc @@ -35,6 +35,24 @@ struct Converter { } }; +template<> +struct Converter { + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + file_dialog::DialogSettings* out) { + mate::Dictionary dict; + if (!ConvertFromV8(isolate, val, &dict)) + return false; + dict.Get("window", &(out->parent_window)); + dict.Get("title", &(out->title)); + dict.Get("buttonLabel", &(out->button_label)); + dict.Get("defaultPath", &(out->default_path)); + dict.Get("filters", &(out->filters)); + dict.Get("properties", &(out->properties)); + return true; + } +}; + } // namespace mate namespace { @@ -68,45 +86,32 @@ void ShowMessageBox(int type, } } -void ShowOpenDialog(const std::string& title, - const std::string& button_label, - const base::FilePath& default_path, - const file_dialog::Filters& filters, - int properties, - atom::NativeWindow* window, +void ShowOpenDialog(const file_dialog::DialogSettings& settings, mate::Arguments* args) { v8::Local peek = args->PeekNext(); file_dialog::OpenDialogCallback callback; if (mate::Converter::FromV8(args->isolate(), peek, &callback)) { - file_dialog::ShowOpenDialog(window, title, button_label, default_path, - filters, properties, callback); + file_dialog::ShowOpenDialog(settings, callback); } else { std::vector paths; - if (file_dialog::ShowOpenDialog(window, title, button_label, default_path, - filters, properties, &paths)) + if (file_dialog::ShowOpenDialog(settings, &paths)) args->Return(paths); } } -void ShowSaveDialog(const std::string& title, - const std::string& button_label, - const base::FilePath& default_path, - const file_dialog::Filters& filters, - atom::NativeWindow* window, +void ShowSaveDialog(const file_dialog::DialogSettings& settings, mate::Arguments* args) { v8::Local peek = args->PeekNext(); file_dialog::SaveDialogCallback callback; if (mate::Converter::FromV8(args->isolate(), peek, &callback)) { - file_dialog::ShowSaveDialog(window, title, button_label, default_path, - filters, callback); + file_dialog::ShowSaveDialog(settings, callback); } else { base::FilePath path; - if (file_dialog::ShowSaveDialog(window, title, button_label, default_path, - filters, &path)) + if (file_dialog::ShowSaveDialog(settings, &path)) args->Return(path); } } diff --git a/atom/browser/atom_download_manager_delegate.cc b/atom/browser/atom_download_manager_delegate.cc index 0213216697..ccbc43416a 100644 --- a/atom/browser/atom_download_manager_delegate.cc +++ b/atom/browser/atom_download_manager_delegate.cc @@ -90,10 +90,11 @@ void AtomDownloadManagerDelegate::OnDownloadPathGenerated( base::FilePath path; GetItemSavePath(item, &path); // Show save dialog if save path was not set already on item - if (path.empty() && file_dialog::ShowSaveDialog(window, item->GetURL().spec(), - "", default_path, - file_dialog::Filters(), - &path)) { + file_dialog::DialogSettings settings; + settings.parent_window = window; + settings.title = item->GetURL().spec(); + settings.default_path = default_path; + if (path.empty() && file_dialog::ShowSaveDialog(settings, &path)) { // Remember the last selected download directory. AtomBrowserContext* browser_context = static_cast( download_manager_->GetBrowserContext()); diff --git a/atom/browser/common_web_contents_delegate.cc b/atom/browser/common_web_contents_delegate.cc index ed0fd5e4ba..89c1e37255 100644 --- a/atom/browser/common_web_contents_delegate.cc +++ b/atom/browser/common_web_contents_delegate.cc @@ -294,10 +294,11 @@ void CommonWebContentsDelegate::DevToolsSaveToFile( if (it != saved_files_.end() && !save_as) { path = it->second; } else { - file_dialog::Filters filters; - base::FilePath default_path(base::FilePath::FromUTF8Unsafe(url)); - if (!file_dialog::ShowSaveDialog(owner_window(), url, "", default_path, - filters, &path)) { + file_dialog::DialogSettings settings; + settings.parent_window = owner_window(); + settings.title = url; + settings.default_path = base::FilePath::FromUTF8Unsafe(url); + if (!file_dialog::ShowSaveDialog(settings, &path)) { base::StringValue url_value(url); web_contents_->CallClientFunction( "DevToolsAPI.canceledSaveURL", &url_value, nullptr, nullptr); @@ -358,12 +359,11 @@ void CommonWebContentsDelegate::DevToolsAddFileSystem( const base::FilePath& file_system_path) { base::FilePath path = file_system_path; if (path.empty()) { - file_dialog::Filters filters; - base::FilePath default_path; std::vector paths; - int flag = file_dialog::FILE_DIALOG_OPEN_DIRECTORY; - if (!file_dialog::ShowOpenDialog(owner_window(), "", "", default_path, - filters, flag, &paths)) + file_dialog::DialogSettings settings; + settings.parent_window = owner_window(); + settings.properties = file_dialog::FILE_DIALOG_OPEN_DIRECTORY; + if (!file_dialog::ShowOpenDialog(settings, &paths)) return; path = paths[0]; diff --git a/atom/browser/ui/file_dialog.h b/atom/browser/ui/file_dialog.h index a8703bebf8..95fffe6ebb 100644 --- a/atom/browser/ui/file_dialog.h +++ b/atom/browser/ui/file_dialog.h @@ -37,34 +37,25 @@ typedef base::Callback SaveDialogCallback; -bool ShowOpenDialog(atom::NativeWindow* parent_window, - const std::string& title, - const std::string& button_label, - const base::FilePath& default_path, - const Filters& filters, - int properties, +struct DialogSettings { + atom::NativeWindow* parent_window = nullptr; + std::string title; + std::string button_label; + base::FilePath default_path; + Filters filters; + int properties = 0; +}; + +bool ShowOpenDialog(const DialogSettings& settings, std::vector* paths); -void ShowOpenDialog(atom::NativeWindow* parent_window, - const std::string& title, - const std::string& button_label, - const base::FilePath& default_path, - const Filters& filters, - int properties, +void ShowOpenDialog(const DialogSettings& settings, const OpenDialogCallback& callback); -bool ShowSaveDialog(atom::NativeWindow* parent_window, - const std::string& title, - const std::string& button_label, - const base::FilePath& default_path, - const Filters& filters, +bool ShowSaveDialog(const DialogSettings& settings, base::FilePath* path); -void ShowSaveDialog(atom::NativeWindow* parent_window, - const std::string& title, - const std::string& button_label, - const base::FilePath& default_path, - const Filters& filters, +void ShowSaveDialog(const DialogSettings& settings, const SaveDialogCallback& callback); } // namespace file_dialog diff --git a/atom/browser/ui/file_dialog_gtk.cc b/atom/browser/ui/file_dialog_gtk.cc index 18f53eba6f..c35d2d9d36 100644 --- a/atom/browser/ui/file_dialog_gtk.cc +++ b/atom/browser/ui/file_dialog_gtk.cc @@ -36,24 +36,20 @@ void OnFileFilterDataDestroyed(std::string* file_extension) { class FileChooserDialog { public: FileChooserDialog(GtkFileChooserAction action, - atom::NativeWindow* parent_window, - const std::string& title, - const std::string& button_label, - const base::FilePath& default_path, - const Filters& filters) - : parent_(static_cast(parent_window)), - filters_(filters) { + const DialogSettings& settings) + : parent_(static_cast(settings.parent_window)), + filters_(settings.filters) { const char* confirm_text = GTK_STOCK_OK; - if (!button_label.empty()) - confirm_text = button_label.c_str(); + if (!settings.button_label.empty()) + confirm_text = settings.button_label.c_str(); else if (action == GTK_FILE_CHOOSER_ACTION_SAVE) confirm_text = GTK_STOCK_SAVE; else if (action == GTK_FILE_CHOOSER_ACTION_OPEN) confirm_text = GTK_STOCK_OPEN; dialog_ = gtk_file_chooser_dialog_new( - title.c_str(), + settings.title.c_str(), NULL, action, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, @@ -71,20 +67,20 @@ class FileChooserDialog { if (action != GTK_FILE_CHOOSER_ACTION_OPEN) gtk_file_chooser_set_create_folders(GTK_FILE_CHOOSER(dialog_), TRUE); - if (!default_path.empty()) { - if (base::DirectoryExists(default_path)) { + if (!settings.default_path.empty()) { + if (base::DirectoryExists(settings.default_path)) { gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog_), - default_path.value().c_str()); + settings.default_path.value().c_str()); } else { gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog_), - default_path.DirName().value().c_str()); + settings.default_path.DirName().value().c_str()); gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog_), - default_path.BaseName().value().c_str()); + settings.default_path.BaseName().value().c_str()); } } - if (!filters.empty()) - AddFilters(filters); + if (!settings.filters.empty()) + AddFilters(settings.filters); } ~FileChooserDialog() { @@ -230,19 +226,13 @@ base::FilePath FileChooserDialog::AddExtensionForFilename( } // namespace -bool ShowOpenDialog(atom::NativeWindow* parent_window, - const std::string& title, - const std::string& button_label, - const base::FilePath& default_path, - const Filters& filters, - int properties, +bool ShowOpenDialog(const DialogSettings& settings, std::vector* paths) { GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN; - if (properties & FILE_DIALOG_OPEN_DIRECTORY) + if (settings.properties & FILE_DIALOG_OPEN_DIRECTORY) action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER; - FileChooserDialog open_dialog(action, parent_window, title, button_label, - default_path, filters); - open_dialog.SetupProperties(properties); + FileChooserDialog open_dialog(action, settings); + open_dialog.SetupProperties(settings.properties); gtk_widget_show_all(open_dialog.dialog()); int response = gtk_dialog_run(GTK_DIALOG(open_dialog.dialog())); @@ -254,30 +244,19 @@ bool ShowOpenDialog(atom::NativeWindow* parent_window, } } -void ShowOpenDialog(atom::NativeWindow* parent_window, - const std::string& title, - const std::string& button_label, - const base::FilePath& default_path, - const Filters& filters, - int properties, +void ShowOpenDialog(const DialogSettings& settings, const OpenDialogCallback& callback) { GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN; - if (properties & FILE_DIALOG_OPEN_DIRECTORY) + if (settings.properties & FILE_DIALOG_OPEN_DIRECTORY) action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER; - FileChooserDialog* open_dialog = new FileChooserDialog( - action, parent_window, title, button_label, default_path, filters); - open_dialog->SetupProperties(properties); + FileChooserDialog* open_dialog = new FileChooserDialog(action, settings); + open_dialog->SetupProperties(settings.properties); open_dialog->RunOpenAsynchronous(callback); } -bool ShowSaveDialog(atom::NativeWindow* parent_window, - const std::string& title, - const std::string& button_label, - const base::FilePath& default_path, - const Filters& filters, +bool ShowSaveDialog(const DialogSettings& settings, base::FilePath* path) { - FileChooserDialog save_dialog(GTK_FILE_CHOOSER_ACTION_SAVE, parent_window, - title, button_label, default_path, filters); + FileChooserDialog save_dialog(GTK_FILE_CHOOSER_ACTION_SAVE, settings); gtk_widget_show_all(save_dialog.dialog()); int response = gtk_dialog_run(GTK_DIALOG(save_dialog.dialog())); if (response == GTK_RESPONSE_ACCEPT) { @@ -288,15 +267,10 @@ bool ShowSaveDialog(atom::NativeWindow* parent_window, } } -void ShowSaveDialog(atom::NativeWindow* parent_window, - const std::string& title, - const std::string& button_label, - const base::FilePath& default_path, - const Filters& filters, +void ShowSaveDialog(const DialogSettings& settings, const SaveDialogCallback& callback) { FileChooserDialog* save_dialog = new FileChooserDialog( - GTK_FILE_CHOOSER_ACTION_SAVE, parent_window, title, button_label, - default_path, filters); + GTK_FILE_CHOOSER_ACTION_SAVE, settings); save_dialog->RunSaveAsynchronous(callback); } diff --git a/atom/browser/ui/file_dialog_mac.mm b/atom/browser/ui/file_dialog_mac.mm index 9492fe90ee..a3cf358e95 100644 --- a/atom/browser/ui/file_dialog_mac.mm +++ b/atom/browser/ui/file_dialog_mac.mm @@ -44,25 +44,23 @@ void SetAllowedFileTypes(NSSavePanel* dialog, const Filters& filters) { } void SetupDialog(NSSavePanel* dialog, - const std::string& title, - const std::string& button_label, - const base::FilePath& default_path, - const Filters& filters) { - if (!title.empty()) - [dialog setTitle:base::SysUTF8ToNSString(title)]; + const DialogSettings& settings) { + if (!settings.title.empty()) + [dialog setTitle:base::SysUTF8ToNSString(settings.title)]; - if (!button_label.empty()) - [dialog setPrompt:base::SysUTF8ToNSString(button_label)]; + if (!settings.button_label.empty()) + [dialog setPrompt:base::SysUTF8ToNSString(settings.button_label)]; NSString* default_dir = nil; NSString* default_filename = nil; - if (!default_path.empty()) { - if (base::DirectoryExists(default_path)) { - default_dir = base::SysUTF8ToNSString(default_path.value()); + if (!settings.default_path.empty()) { + if (base::DirectoryExists(settings.default_path)) { + default_dir = base::SysUTF8ToNSString(settings.default_path.value()); } else { - default_dir = base::SysUTF8ToNSString(default_path.DirName().value()); + default_dir = + base::SysUTF8ToNSString(settings.default_path.DirName().value()); default_filename = - base::SysUTF8ToNSString(default_path.BaseName().value()); + base::SysUTF8ToNSString(settings.default_path.BaseName().value()); } } @@ -71,10 +69,10 @@ void SetupDialog(NSSavePanel* dialog, if (default_filename) [dialog setNameFieldStringValue:default_filename]; - if (filters.empty()) + if (settings.filters.empty()) [dialog setAllowsOtherFileTypes:YES]; else - SetAllowedFileTypes(dialog, filters); + SetAllowedFileTypes(dialog, settings.filters); } void SetupDialogForProperties(NSOpenPanel* dialog, int properties) { @@ -117,20 +115,15 @@ void ReadDialogPaths(NSOpenPanel* dialog, std::vector* paths) { } // namespace -bool ShowOpenDialog(atom::NativeWindow* parent_window, - const std::string& title, - const std::string& button_label, - const base::FilePath& default_path, - const Filters& filters, - int properties, +bool ShowOpenDialog(const DialogSettings& settings, std::vector* paths) { DCHECK(paths); NSOpenPanel* dialog = [NSOpenPanel openPanel]; - SetupDialog(dialog, title, button_label, default_path, filters); - SetupDialogForProperties(dialog, properties); + SetupDialog(dialog, settings); + SetupDialogForProperties(dialog, settings.properties); - int chosen = RunModalDialog(dialog, parent_window); + int chosen = RunModalDialog(dialog, settings.parent_window); if (chosen == NSFileHandlingPanelCancelButton) return false; @@ -138,23 +131,20 @@ bool ShowOpenDialog(atom::NativeWindow* parent_window, return true; } -void ShowOpenDialog(atom::NativeWindow* parent_window, - const std::string& title, - const std::string& button_label, - const base::FilePath& default_path, - const Filters& filters, - int properties, +void ShowOpenDialog(const DialogSettings& settings, const OpenDialogCallback& c) { NSOpenPanel* dialog = [NSOpenPanel openPanel]; - SetupDialog(dialog, title, button_label, default_path, filters); - SetupDialogForProperties(dialog, properties); + SetupDialog(dialog, settings); + SetupDialogForProperties(dialog, settings.properties); // Duplicate the callback object here since c is a reference and gcd would // only store the pointer, by duplication we can force gcd to store a copy. __block OpenDialogCallback callback = c; - NSWindow* window = parent_window ? parent_window->GetNativeWindow() : NULL; + NSWindow* window = settings.parent_window ? + settings.parent_window->GetNativeWindow() : + NULL; [dialog beginSheetModalForWindow:window completionHandler:^(NSInteger chosen) { if (chosen == NSFileHandlingPanelCancelButton) { @@ -167,18 +157,14 @@ void ShowOpenDialog(atom::NativeWindow* parent_window, }]; } -bool ShowSaveDialog(atom::NativeWindow* parent_window, - const std::string& title, - const std::string& button_label, - const base::FilePath& default_path, - const Filters& filters, +bool ShowSaveDialog(const DialogSettings& settings, base::FilePath* path) { DCHECK(path); NSSavePanel* dialog = [NSSavePanel savePanel]; - SetupDialog(dialog, title, button_label, default_path, filters); + SetupDialog(dialog, settings); - int chosen = RunModalDialog(dialog, parent_window); + int chosen = RunModalDialog(dialog, settings.parent_window); if (chosen == NSFileHandlingPanelCancelButton || ![[dialog URL] isFileURL]) return false; @@ -186,20 +172,18 @@ bool ShowSaveDialog(atom::NativeWindow* parent_window, return true; } -void ShowSaveDialog(atom::NativeWindow* parent_window, - const std::string& title, - const std::string& button_label, - const base::FilePath& default_path, - const Filters& filters, +void ShowSaveDialog(const DialogSettings& settings, const SaveDialogCallback& c) { NSSavePanel* dialog = [NSSavePanel savePanel]; - SetupDialog(dialog, title, button_label, default_path, filters); + SetupDialog(dialog, settings); [dialog setCanSelectHiddenExtension:YES]; __block SaveDialogCallback callback = c; - NSWindow* window = parent_window ? parent_window->GetNativeWindow() : NULL; + NSWindow* window = settings.parent_window ? + settings.parent_window->GetNativeWindow() : + NULL; [dialog beginSheetModalForWindow:window completionHandler:^(NSInteger chosen) { if (chosen == NSFileHandlingPanelCancelButton) { diff --git a/atom/browser/ui/file_dialog_win.cc b/atom/browser/ui/file_dialog_win.cc index 8e973432f5..eaaac5e39e 100644 --- a/atom/browser/ui/file_dialog_win.cc +++ b/atom/browser/ui/file_dialog_win.cc @@ -66,26 +66,24 @@ void ConvertFilters(const Filters& filters, template class FileDialog { public: - FileDialog(const base::FilePath& default_path, - const std::string& title, - const std::string& button_label, - const Filters& filters, int options) { + FileDialog(const DialogSettings& settings, int options) { std::wstring file_part; - if (!IsDirectory(default_path)) - file_part = default_path.BaseName().value(); + if (!IsDirectory(settings.default_path)) + file_part = settings.default_path.BaseName().value(); std::vector buffer; std::vector filterspec; - ConvertFilters(filters, &buffer, &filterspec); + ConvertFilters(settings.filters, &buffer, &filterspec); dialog_.reset(new T(file_part.c_str(), options, NULL, filterspec.data(), filterspec.size())); - if (!title.empty()) - GetPtr()->SetTitle(base::UTF8ToUTF16(title).c_str()); + if (!settings.title.empty()) + GetPtr()->SetTitle(base::UTF8ToUTF16(settings.title).c_str()); - if (!button_label.empty()) - GetPtr()->SetOkButtonLabel(base::UTF8ToUTF16(button_label).c_str()); + if (!settings.button_label.empty()) + GetPtr()->SetOkButtonLabel( + base::UTF8ToUTF16(settings.button_label).c_str()); // By default, *.* will be added to the file name if file type is "*.*". In // Electron, we disable it to make a better experience. @@ -107,7 +105,7 @@ class FileDialog { } } - SetDefaultFolder(default_path); + SetDefaultFolder(settings.default_path); } bool Show(atom::NativeWindow* parent_window) { @@ -160,31 +158,20 @@ bool CreateDialogThread(RunState* run_state) { } void RunOpenDialogInNewThread(const RunState& run_state, - atom::NativeWindow* parent, - const std::string& title, - const std::string& button_label, - const base::FilePath& default_path, - const Filters& filters, - int properties, + const DialogSettings& settings, const OpenDialogCallback& callback) { std::vector paths; - bool result = ShowOpenDialog(parent, title, button_label, default_path, - filters, properties, &paths); + bool result = ShowOpenDialog(settings, &paths); run_state.ui_task_runner->PostTask(FROM_HERE, base::Bind(callback, result, paths)); run_state.ui_task_runner->DeleteSoon(FROM_HERE, run_state.dialog_thread); } void RunSaveDialogInNewThread(const RunState& run_state, - atom::NativeWindow* parent, - const std::string& title, - const std::string& button_label, - const base::FilePath& default_path, - const Filters& filters, + const DialogSettings& settings, const SaveDialogCallback& callback) { base::FilePath path; - bool result = ShowSaveDialog(parent, title, button_label, default_path, - filters, &path); + bool result = ShowSaveDialog(settings, &path); run_state.ui_task_runner->PostTask(FROM_HERE, base::Bind(callback, result, path)); run_state.ui_task_runner->DeleteSoon(FROM_HERE, run_state.dialog_thread); @@ -192,26 +179,20 @@ void RunSaveDialogInNewThread(const RunState& run_state, } // namespace -bool ShowOpenDialog(atom::NativeWindow* parent_window, - const std::string& title, - const std::string& button_label, - const base::FilePath& default_path, - const Filters& filters, - int properties, +bool ShowOpenDialog(const DialogSettings& settings, std::vector* paths) { int options = FOS_FORCEFILESYSTEM | FOS_FILEMUSTEXIST; - if (properties & FILE_DIALOG_OPEN_DIRECTORY) + if (settings.properties & FILE_DIALOG_OPEN_DIRECTORY) options |= FOS_PICKFOLDERS; - if (properties & FILE_DIALOG_MULTI_SELECTIONS) + if (settings.properties & FILE_DIALOG_MULTI_SELECTIONS) options |= FOS_ALLOWMULTISELECT; - if (properties & FILE_DIALOG_SHOW_HIDDEN_FILES) + if (settings.properties & FILE_DIALOG_SHOW_HIDDEN_FILES) options |= FOS_FORCESHOWHIDDEN; - if (properties & FILE_DIALOG_PROMPT_TO_CREATE) + if (settings.properties & FILE_DIALOG_PROMPT_TO_CREATE) options |= FOS_CREATEPROMPT; - FileDialog open_dialog( - default_path, title, button_label, filters, options); - if (!open_dialog.Show(parent_window)) + FileDialog open_dialog(settings, options); + if (!open_dialog.Show(settings.parent_window)) return false; ATL::CComPtr items; @@ -244,12 +225,7 @@ bool ShowOpenDialog(atom::NativeWindow* parent_window, return true; } -void ShowOpenDialog(atom::NativeWindow* parent, - const std::string& title, - const std::string& button_label, - const base::FilePath& default_path, - const Filters& filters, - int properties, +void ShowOpenDialog(const DialogSettings& settings, const OpenDialogCallback& callback) { RunState run_state; if (!CreateDialogThread(&run_state)) { @@ -259,20 +235,14 @@ void ShowOpenDialog(atom::NativeWindow* parent, run_state.dialog_thread->task_runner()->PostTask( FROM_HERE, - base::Bind(&RunOpenDialogInNewThread, run_state, parent, title, - button_label, default_path, filters, properties, callback)); + base::Bind(&RunOpenDialogInNewThread, run_state, settings, callback)); } -bool ShowSaveDialog(atom::NativeWindow* parent_window, - const std::string& title, - const std::string& button_label, - const base::FilePath& default_path, - const Filters& filters, +bool ShowSaveDialog(const DialogSettings& settings, base::FilePath* path) { FileDialog save_dialog( - default_path, title, button_label, filters, - FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST | FOS_OVERWRITEPROMPT); - if (!save_dialog.Show(parent_window)) + settings, FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST | FOS_OVERWRITEPROMPT); + if (!save_dialog.Show(settings.parent_window)) return false; wchar_t buffer[MAX_PATH]; @@ -284,11 +254,7 @@ bool ShowSaveDialog(atom::NativeWindow* parent_window, return true; } -void ShowSaveDialog(atom::NativeWindow* parent, - const std::string& title, - const std::string& button_label, - const base::FilePath& default_path, - const Filters& filters, +void ShowSaveDialog(const DialogSettings& settings, const SaveDialogCallback& callback) { RunState run_state; if (!CreateDialogThread(&run_state)) { @@ -298,8 +264,7 @@ void ShowSaveDialog(atom::NativeWindow* parent, run_state.dialog_thread->task_runner()->PostTask( FROM_HERE, - base::Bind(&RunSaveDialogInNewThread, run_state, parent, title, - button_label, default_path, filters, callback)); + base::Bind(&RunSaveDialogInNewThread, run_state, settings, callback)); } } // namespace file_dialog diff --git a/atom/browser/web_dialog_helper.cc b/atom/browser/web_dialog_helper.cc index e3942f1f71..af7cc27674 100644 --- a/atom/browser/web_dialog_helper.cc +++ b/atom/browser/web_dialog_helper.cc @@ -81,16 +81,16 @@ void WebDialogHelper::RunFileChooser( content::RenderFrameHost* render_frame_host, const content::FileChooserParams& params) { std::vector result; - file_dialog::Filters filters = GetFileTypesFromAcceptType( - params.accept_types); + + file_dialog::DialogSettings settings; + settings.filters = GetFileTypesFromAcceptType(params.accept_types); + settings.parent_window = window_; + settings.title = base::UTF16ToUTF8(params.title); + if (params.mode == content::FileChooserParams::Save) { base::FilePath path; - if (file_dialog::ShowSaveDialog(window_, - base::UTF16ToUTF8(params.title), - "", - params.default_file_name, - filters, - &path)) { + settings.default_path = params.default_file_name; + if (file_dialog::ShowSaveDialog(settings, &path)) { content::FileChooserFileInfo info; info.file_path = path; info.display_name = path.BaseName().value(); @@ -114,15 +114,10 @@ void WebDialogHelper::RunFileChooser( std::vector paths; AtomBrowserContext* browser_context = static_cast( window_->web_contents()->GetBrowserContext()); - base::FilePath default_file_path = browser_context->prefs()->GetFilePath( + settings.default_path = browser_context->prefs()->GetFilePath( prefs::kSelectFileLastDirectory).Append(params.default_file_name); - if (file_dialog::ShowOpenDialog(window_, - base::UTF16ToUTF8(params.title), - "", - default_file_path, - filters, - flags, - &paths)) { + settings.properties = flags; + if (file_dialog::ShowOpenDialog(settings, &paths)) { for (auto& path : paths) { content::FileChooserFileInfo info; info.file_path = path; diff --git a/lib/browser/api/dialog.js b/lib/browser/api/dialog.js index d0266d912d..8f1aaf7e14 100644 --- a/lib/browser/api/dialog.js +++ b/lib/browser/api/dialog.js @@ -121,8 +121,9 @@ module.exports = { const wrappedCallback = typeof callback === 'function' ? function (success, result) { return callback(success ? result : void 0) } : null - return binding.showOpenDialog(title, buttonLabel, defaultPath, filters, - dialogProperties, window, wrappedCallback) + const settings = {title, buttonLabel, defaultPath, filters, window} + settings.properties = dialogProperties + return binding.showOpenDialog(settings, wrappedCallback) }, showSaveDialog: function (...args) { @@ -163,8 +164,8 @@ module.exports = { const wrappedCallback = typeof callback === 'function' ? function (success, result) { return callback(success ? result : void 0) } : null - return binding.showSaveDialog(title, buttonLabel, defaultPath, filters, - window, wrappedCallback) + const settings = {title, buttonLabel, defaultPath, filters, window} + return binding.showSaveDialog(settings, wrappedCallback) }, showMessageBox: function (...args) { From 35654d872b544699a5f065a66371c6f652513bbb Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 9 Feb 2017 11:29:10 -0800 Subject: [PATCH 084/925] Document new settings --- docs/api/dialog.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/api/dialog.md b/docs/api/dialog.md index 10326731aa..75edc26bdc 100644 --- a/docs/api/dialog.md +++ b/docs/api/dialog.md @@ -51,7 +51,8 @@ The `dialog` module has the following methods: untouched on Windows. For example, a button label of `Vie&w` will be converted to `Vie_w` on Linux and `View` on macOS and can be selected via `Alt-W` on Windows and Linux. - * `message` (optional) _macOS_ + * `message` String (optional) _macOS_ - Message to display above input + boxes. * `callback` Function (optional) * `filePaths` String[] - An array of file paths chosen by the user @@ -95,10 +96,11 @@ shown. * `buttonLabel` String (optional) - Custom label for the confirmation button, when left empty the default label will be used. * `filters` [FileFilter[]](structures/file-filter.md) (optional) - * `message` (optional) _macOS_ - * `nameFieldLabel` String (optional) _macOS_ - Custom label for the string displayed - in front of the filename text field. - * `showsTagField` Boolean (optional) _macOS_ + * `message` String (optional) _macOS_ - Message to display above text fields. + * `nameFieldLabel` String (optional) _macOS_ - Custom label for the text + displayed in front of the filename text field. + * `showsTagField` Boolean (optional) _macOS_ - Show the tags input box, + defaults to `true`. * `callback` Function (optional) * `filename` String From 5130ad24eb81f3e08db938e6549282a7c04d2bee Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 9 Feb 2017 11:30:17 -0800 Subject: [PATCH 085/925] :art: --- lib/browser/api/dialog.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/browser/api/dialog.js b/lib/browser/api/dialog.js index 2bee42eb41..285ca72901 100644 --- a/lib/browser/api/dialog.js +++ b/lib/browser/api/dialog.js @@ -143,8 +143,7 @@ module.exports = { } } - let {buttonLabel, defaultPath, filters, title, message, nameFieldLabel, - showsTagField} = options + let {buttonLabel, defaultPath, filters, title, message, nameFieldLabel, showsTagField} = options if (title == null) { title = '' From 706b9f6cbf9d0532b21fe9b3d00fbc47555749be Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 3 Feb 2017 12:21:46 -0800 Subject: [PATCH 086/925] Add new-webview specs --- spec/static/main.js | 10 ++++++++++ spec/webview-spec.js | 27 ++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/spec/static/main.js b/spec/static/main.js index f29bd00e68..735e7e8b5f 100644 --- a/spec/static/main.js +++ b/spec/static/main.js @@ -250,6 +250,16 @@ ipcMain.on('prevent-next-new-window', (event, id) => { webContents.fromId(id).once('new-window', event => event.preventDefault()) }) +ipcMain.on('prevent-next-new-webview', (event) => { + event.sender.once('new-webview', event => event.preventDefault()) +}) + +ipcMain.on('disable-node-on-next-new-webview', (event, id) => { + event.sender.once('new-webview', (event, guest, webPreferences) => { + webPreferences.nodeIntegration = false + }) +}) + ipcMain.on('try-emit-web-contents-event', (event, id, eventName) => { const consoleWarn = console.warn let warningMessage = null diff --git a/spec/webview-spec.js b/spec/webview-spec.js index 9af3e01a4c..02354eb863 100644 --- a/spec/webview-spec.js +++ b/spec/webview-spec.js @@ -2,7 +2,7 @@ const assert = require('assert') const path = require('path') const http = require('http') const url = require('url') -const {remote} = require('electron') +const {ipcRenderer, remote} = require('electron') const {app, session, getGuestWebContents, ipcMain, BrowserWindow, webContents} = remote const {closeWindow} = require('./window-helpers') @@ -1100,6 +1100,31 @@ describe(' tag', function () { w.loadURL('file://' + fixtures + '/pages/webview-visibilitychange.html') }) + describe('new-webview event', () => { + it('supports changing the web preferences', (done) => { + ipcRenderer.send('disable-node-on-next-new-webview') + webview.addEventListener('console-message', (event) => { + assert.equal(event.message, 'undefined undefined undefined undefined') + done() + }) + webview.setAttribute('nodeintegration', 'yes') + webview.src = 'file://' + fixtures + '/pages/c.html' + document.body.appendChild(webview) + }) + + it('supports preventing a webview from being created', (done) => { + ipcRenderer.send('prevent-next-new-webview') + webview.addEventListener('destroyed', () => { + done() + }) + webview.src = 'file://' + fixtures + '/pages/c.html' + document.body.appendChild(webview) + }) + }) + + it('emits a new-webview event when first attached that ', () => { + }) + it('loads devtools extensions registered on the parent window', function (done) { w = new BrowserWindow({ show: false From 7eda8fbdfb3811aef70d22b7bf8e0704d5bf47af Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 3 Feb 2017 12:46:22 -0800 Subject: [PATCH 087/925] Add new-webview event that can be prevented --- lib/browser/guest-view-manager.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/browser/guest-view-manager.js b/lib/browser/guest-view-manager.js index 500ed37bbb..95e4e0d47f 100644 --- a/lib/browser/guest-view-manager.js +++ b/lib/browser/guest-view-manager.js @@ -147,7 +147,8 @@ const createGuest = function (embedder, params) { } // Attach the guest to an element of embedder. -const attachGuest = function (embedder, elementInstanceId, guestInstanceId, params) { +const attachGuest = function (event, elementInstanceId, guestInstanceId, params) { + const embedder = event.sender // Destroy the old guest when attaching. const key = `${embedder.getId()}-${elementInstanceId}` const oldGuestInstanceId = embedderElementsMap[key] @@ -204,6 +205,14 @@ const attachGuest = function (embedder, elementInstanceId, guestInstanceId, para if (params.preload) { webPreferences.preloadURL = params.preload } + + event.sender.emit('new-webview', event, guest, webPreferences) + if (event.defaultPrevented) { + if (guest.viewInstanceId == null) guest.viewInstanceId = params.instanceId + destroyGuest(embedder, guestInstanceId) + return + } + webViewManager.addGuest(guestInstanceId, elementInstanceId, embedder, guest, webPreferences) guest.attachParams = params embedderElementsMap[key] = guestInstanceId @@ -276,7 +285,7 @@ ipcMain.on('ELECTRON_GUEST_VIEW_MANAGER_CREATE_GUEST', function (event, params, }) ipcMain.on('ELECTRON_GUEST_VIEW_MANAGER_ATTACH_GUEST', function (event, elementInstanceId, guestInstanceId, params) { - attachGuest(event.sender, elementInstanceId, guestInstanceId, params) + attachGuest(event, elementInstanceId, guestInstanceId, params) }) ipcMain.on('ELECTRON_GUEST_VIEW_MANAGER_DESTROY_GUEST', function (event, guestInstanceId) { From 46f1a49594467256d33328e2254aecb3a12c579e Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 3 Feb 2017 12:55:37 -0800 Subject: [PATCH 088/925] new-webview -> will-attach-webview --- lib/browser/guest-view-manager.js | 2 +- spec/static/main.js | 8 ++++---- spec/webview-spec.js | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/browser/guest-view-manager.js b/lib/browser/guest-view-manager.js index 95e4e0d47f..5a47a52747 100644 --- a/lib/browser/guest-view-manager.js +++ b/lib/browser/guest-view-manager.js @@ -206,7 +206,7 @@ const attachGuest = function (event, elementInstanceId, guestInstanceId, params) webPreferences.preloadURL = params.preload } - event.sender.emit('new-webview', event, guest, webPreferences) + event.sender.emit('will-attach-webview', event, guest, webPreferences) if (event.defaultPrevented) { if (guest.viewInstanceId == null) guest.viewInstanceId = params.instanceId destroyGuest(embedder, guestInstanceId) diff --git a/spec/static/main.js b/spec/static/main.js index 735e7e8b5f..0e89fad20d 100644 --- a/spec/static/main.js +++ b/spec/static/main.js @@ -250,12 +250,12 @@ ipcMain.on('prevent-next-new-window', (event, id) => { webContents.fromId(id).once('new-window', event => event.preventDefault()) }) -ipcMain.on('prevent-next-new-webview', (event) => { - event.sender.once('new-webview', event => event.preventDefault()) +ipcMain.on('prevent-next-will-attach-webview', (event) => { + event.sender.once('will-attach-webview', event => event.preventDefault()) }) -ipcMain.on('disable-node-on-next-new-webview', (event, id) => { - event.sender.once('new-webview', (event, guest, webPreferences) => { +ipcMain.on('disable-node-on-next-will-attach-webview', (event, id) => { + event.sender.once('will-attach-webview', (event, guest, webPreferences) => { webPreferences.nodeIntegration = false }) }) diff --git a/spec/webview-spec.js b/spec/webview-spec.js index 02354eb863..fb360db781 100644 --- a/spec/webview-spec.js +++ b/spec/webview-spec.js @@ -1100,9 +1100,9 @@ describe(' tag', function () { w.loadURL('file://' + fixtures + '/pages/webview-visibilitychange.html') }) - describe('new-webview event', () => { + describe('will-attach-webview event', () => { it('supports changing the web preferences', (done) => { - ipcRenderer.send('disable-node-on-next-new-webview') + ipcRenderer.send('disable-node-on-next-will-attach-webview') webview.addEventListener('console-message', (event) => { assert.equal(event.message, 'undefined undefined undefined undefined') done() @@ -1113,7 +1113,7 @@ describe(' tag', function () { }) it('supports preventing a webview from being created', (done) => { - ipcRenderer.send('prevent-next-new-webview') + ipcRenderer.send('prevent-next-will-attach-webview') webview.addEventListener('destroyed', () => { done() }) From 792f3c3030af9bd0f20b6a14bab58039f9232baa Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 3 Feb 2017 12:58:04 -0800 Subject: [PATCH 089/925] Document will-attach-webview --- docs/api/web-contents.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index 38e5808594..4e4f6171b7 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -502,6 +502,18 @@ win.loadURL('http://github.com') Emitted when the devtools window instructs the webContents to reload +#### Event: 'will-attach-webview' + +Returns: + +* `event` Event +* `guest` WebContents - The contents of the guest page. +* `webPreferences` Object - The web preferences that will be used by the guest + page. This object can be modified to adjust the preferences for the guest + page. + +Calling `event.preventDefault()` will destroy the guest page. + ### Instance Methods #### `contents.loadURL(url[, options])` From 8b0d3a256794f0fda3d630830687f69ccc268859 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 3 Feb 2017 13:58:07 -0800 Subject: [PATCH 090/925] Emit attach params as well in event --- docs/api/web-contents.md | 5 ++++- lib/browser/guest-view-manager.js | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index 4e4f6171b7..192e12e401 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -511,8 +511,11 @@ Returns: * `webPreferences` Object - The web preferences that will be used by the guest page. This object can be modified to adjust the preferences for the guest page. +* `params` Object - The other `` parameters such as the `src` URL. + This object can be modified to adjust the parameters of the guest page. -Calling `event.preventDefault()` will destroy the guest page. +Emitted when a ``'s web contents is being attached to this web +contents. Calling `event.preventDefault()` will destroy the guest page. ### Instance Methods diff --git a/lib/browser/guest-view-manager.js b/lib/browser/guest-view-manager.js index 5a47a52747..6b8b614470 100644 --- a/lib/browser/guest-view-manager.js +++ b/lib/browser/guest-view-manager.js @@ -206,7 +206,7 @@ const attachGuest = function (event, elementInstanceId, guestInstanceId, params) webPreferences.preloadURL = params.preload } - event.sender.emit('will-attach-webview', event, guest, webPreferences) + event.sender.emit('will-attach-webview', event, guest, webPreferences, params) if (event.defaultPrevented) { if (guest.viewInstanceId == null) guest.viewInstanceId = params.instanceId destroyGuest(embedder, guestInstanceId) From acff2f6baf52d9ae0fb25ce7c87eb1b410a64405 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 3 Feb 2017 14:01:39 -0800 Subject: [PATCH 091/925] Adjust src from will-attach-webview --- spec/static/main.js | 3 ++- spec/webview-spec.js | 5 +---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/spec/static/main.js b/spec/static/main.js index 0e89fad20d..0763ba2ecf 100644 --- a/spec/static/main.js +++ b/spec/static/main.js @@ -255,7 +255,8 @@ ipcMain.on('prevent-next-will-attach-webview', (event) => { }) ipcMain.on('disable-node-on-next-will-attach-webview', (event, id) => { - event.sender.once('will-attach-webview', (event, guest, webPreferences) => { + event.sender.once('will-attach-webview', (event, guest, webPreferences, params) => { + params.src = `file://${path.join(__dirname, '..', 'fixtures', 'pages', 'c.html')}` webPreferences.nodeIntegration = false }) }) diff --git a/spec/webview-spec.js b/spec/webview-spec.js index fb360db781..02207a4b26 100644 --- a/spec/webview-spec.js +++ b/spec/webview-spec.js @@ -1108,7 +1108,7 @@ describe(' tag', function () { done() }) webview.setAttribute('nodeintegration', 'yes') - webview.src = 'file://' + fixtures + '/pages/c.html' + webview.src = 'file://' + fixtures + '/pages/a.html' document.body.appendChild(webview) }) @@ -1122,9 +1122,6 @@ describe(' tag', function () { }) }) - it('emits a new-webview event when first attached that ', () => { - }) - it('loads devtools extensions registered on the parent window', function (done) { w = new BrowserWindow({ show: false From a7c050107b3b8cda5acf8c961cfe85e1dd274056 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 9 Feb 2017 11:47:40 -0800 Subject: [PATCH 092/925] Remove guest from will-attach-webview event --- docs/api/web-contents.md | 1 - lib/browser/guest-view-manager.js | 2 +- spec/static/main.js | 2 +- spec/webview-spec.js | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index 192e12e401..d30a34f8ec 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -507,7 +507,6 @@ Emitted when the devtools window instructs the webContents to reload Returns: * `event` Event -* `guest` WebContents - The contents of the guest page. * `webPreferences` Object - The web preferences that will be used by the guest page. This object can be modified to adjust the preferences for the guest page. diff --git a/lib/browser/guest-view-manager.js b/lib/browser/guest-view-manager.js index 6b8b614470..9eb81b89ae 100644 --- a/lib/browser/guest-view-manager.js +++ b/lib/browser/guest-view-manager.js @@ -206,7 +206,7 @@ const attachGuest = function (event, elementInstanceId, guestInstanceId, params) webPreferences.preloadURL = params.preload } - event.sender.emit('will-attach-webview', event, guest, webPreferences, params) + event.sender.emit('will-attach-webview', event, webPreferences, params) if (event.defaultPrevented) { if (guest.viewInstanceId == null) guest.viewInstanceId = params.instanceId destroyGuest(embedder, guestInstanceId) diff --git a/spec/static/main.js b/spec/static/main.js index 0763ba2ecf..1eafd78503 100644 --- a/spec/static/main.js +++ b/spec/static/main.js @@ -255,7 +255,7 @@ ipcMain.on('prevent-next-will-attach-webview', (event) => { }) ipcMain.on('disable-node-on-next-will-attach-webview', (event, id) => { - event.sender.once('will-attach-webview', (event, guest, webPreferences, params) => { + event.sender.once('will-attach-webview', (event, webPreferences, params) => { params.src = `file://${path.join(__dirname, '..', 'fixtures', 'pages', 'c.html')}` webPreferences.nodeIntegration = false }) diff --git a/spec/webview-spec.js b/spec/webview-spec.js index 02207a4b26..174b1e3962 100644 --- a/spec/webview-spec.js +++ b/spec/webview-spec.js @@ -1100,7 +1100,7 @@ describe(' tag', function () { w.loadURL('file://' + fixtures + '/pages/webview-visibilitychange.html') }) - describe('will-attach-webview event', () => { + describe.only('will-attach-webview event', () => { it('supports changing the web preferences', (done) => { ipcRenderer.send('disable-node-on-next-will-attach-webview') webview.addEventListener('console-message', (event) => { From 941f6c63e399d488ba82061d78f3d9ff4f568208 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 9 Feb 2017 11:48:45 -0800 Subject: [PATCH 093/925] :art: Use embedder var for clarity --- lib/browser/guest-view-manager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/browser/guest-view-manager.js b/lib/browser/guest-view-manager.js index 9eb81b89ae..f1a460271b 100644 --- a/lib/browser/guest-view-manager.js +++ b/lib/browser/guest-view-manager.js @@ -206,7 +206,7 @@ const attachGuest = function (event, elementInstanceId, guestInstanceId, params) webPreferences.preloadURL = params.preload } - event.sender.emit('will-attach-webview', event, webPreferences, params) + embedder.emit('will-attach-webview', event, webPreferences, params) if (event.defaultPrevented) { if (guest.viewInstanceId == null) guest.viewInstanceId = params.instanceId destroyGuest(embedder, guestInstanceId) From 81addbdcbf927373aa433b30a47631ffb28a2811 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 9 Feb 2017 11:49:14 -0800 Subject: [PATCH 094/925] Remove only call --- spec/webview-spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/webview-spec.js b/spec/webview-spec.js index 174b1e3962..02207a4b26 100644 --- a/spec/webview-spec.js +++ b/spec/webview-spec.js @@ -1100,7 +1100,7 @@ describe(' tag', function () { w.loadURL('file://' + fixtures + '/pages/webview-visibilitychange.html') }) - describe.only('will-attach-webview event', () => { + describe('will-attach-webview event', () => { it('supports changing the web preferences', (done) => { ipcRenderer.send('disable-node-on-next-will-attach-webview') webview.addEventListener('console-message', (event) => { From e8f0331aeb81b0f42bfd8af111d3c3d0b6381c1a Mon Sep 17 00:00:00 2001 From: Vadim Macagon Date: Fri, 10 Feb 2017 10:33:33 +0700 Subject: [PATCH 095/925] Improve docs for `will-attach-webview` event --- docs/api/web-contents.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index d30a34f8ec..781436509f 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -516,6 +516,10 @@ Returns: Emitted when a ``'s web contents is being attached to this web contents. Calling `event.preventDefault()` will destroy the guest page. +This event can be used to configure `webPreferences` for the `webContents` +of a `` before it's loaded, and provides the ability to set settings +that can't be set via `` attributes. + ### Instance Methods #### `contents.loadURL(url[, options])` From 197ad915b72f3c88eb0818d433c64b3e20a4b4bd Mon Sep 17 00:00:00 2001 From: Vadim Macagon Date: Fri, 10 Feb 2017 17:57:55 +0700 Subject: [PATCH 096/925] [ci skip] Improve docs for `new-window` event This should help clarify the purpose of `event.newGuest` as per the recent discussion in #7407. --- docs/api/web-contents.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index d30a34f8ec..6f43ccbbce 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -157,9 +157,20 @@ requested by `window.open` or an external link like ``. By default a new `BrowserWindow` will be created for the `url`. -Calling `event.preventDefault()` will prevent creating new windows. In such case, the -`event.newGuest` may be set with a reference to a `BrowserWindow` instance to make it -used by the Electron's runtime. +Calling `event.preventDefault()` will prevent Electron from automatically creating a +new `BrowserWindow`. If you call `event.preventDefault()` and manually create a new +`BrowserWindow` then you must set `event.newGuest` to reference the new `BrowserWindow` +instance, failing to do so may result in unexpected behavior. For example: + +```javascript +myBrowserWindow.webContents.on('new-window', event => { + event.preventDefault() + const win = new BrowserWindow({ show: false }) + win.once('ready-to-show', () => win.show()) + win.loadURL(event.url) + event.newGuest = win +}); +``` #### Event: 'will-navigate' From 035d9df29bc5aefc4b5174c09b44949e0da06c8b Mon Sep 17 00:00:00 2001 From: Vadim Macagon Date: Fri, 10 Feb 2017 18:02:03 +0700 Subject: [PATCH 097/925] Fix example code --- docs/api/web-contents.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index 6f43ccbbce..8bbacd60ea 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -163,11 +163,11 @@ new `BrowserWindow`. If you call `event.preventDefault()` and manually create a instance, failing to do so may result in unexpected behavior. For example: ```javascript -myBrowserWindow.webContents.on('new-window', event => { +myBrowserWindow.webContents.on('new-window', (event, url) => { event.preventDefault() const win = new BrowserWindow({ show: false }) win.once('ready-to-show', () => win.show()) - win.loadURL(event.url) + win.loadURL(url) event.newGuest = win }); ``` From 19d7f0733e77a57a5bf5743917c7d6f9abc10b50 Mon Sep 17 00:00:00 2001 From: Catalin Ionut Fratila Date: Fri, 10 Feb 2017 14:26:53 +0100 Subject: [PATCH 098/925] create-dist: Adding argument to supress api docs generation. --- script/create-dist.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/script/create-dist.py b/script/create-dist.py index 4aa67eacd6..f9d590259c 100755 --- a/script/create-dist.py +++ b/script/create-dist.py @@ -1,5 +1,6 @@ #!/usr/bin/env python +import argparse import glob import os import re @@ -86,7 +87,9 @@ def main(): copy_chrome_binary('mksnapshot') copy_license() - if PLATFORM != 'win32': + args = parse_args() + + if PLATFORM != 'win32' and not args.no_api_docs: create_api_json_schema() if PLATFORM == 'linux': @@ -241,5 +244,13 @@ def create_symbols_zip(): make_zip(os.path.join(DIST_DIR, pdb_name), pdbs + licenses, []) +def parse_args(): + parser = argparse.ArgumentParser(description='Create Electron Distribution') + parser.add_argument('--no_api_docs', + action='store_true', + help='Skip generating the Electron API Documentation!') + return parser.parse_args() + + if __name__ == '__main__': sys.exit(main()) From e85d2f00dd9ba2b57a480f926583c1fe1ea6b6bd Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 10 Feb 2017 08:49:13 -0800 Subject: [PATCH 099/925] Fix linting errors around spacing and semicolons --- docs/api/web-contents.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index 8bbacd60ea..959b6fd6e3 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -164,12 +164,12 @@ instance, failing to do so may result in unexpected behavior. For example: ```javascript myBrowserWindow.webContents.on('new-window', (event, url) => { - event.preventDefault() - const win = new BrowserWindow({ show: false }) - win.once('ready-to-show', () => win.show()) - win.loadURL(url) - event.newGuest = win -}); + event.preventDefault() + const win = new BrowserWindow({show: false}) + win.once('ready-to-show', () => win.show()) + win.loadURL(url) + event.newGuest = win +}) ``` #### Event: 'will-navigate' From 888dc5702f705715c41911e7e7d080b6a082bb2c Mon Sep 17 00:00:00 2001 From: ThePooE Date: Wed, 18 Jan 2017 14:44:19 +0700 Subject: [PATCH 100/925] :memo: Thai: add prelude translation to README.md --- docs-translations/th-TH/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs-translations/th-TH/README.md b/docs-translations/th-TH/README.md index 432c2168d1..97f460f70e 100644 --- a/docs-translations/th-TH/README.md +++ b/docs-translations/th-TH/README.md @@ -1,3 +1,5 @@ +กรุณาตรวจสอบว่าคุณกำลังใช้คู่มือที่ตรงกับเวอร์ชั่นของ Electon ของคุณด้วย ตัวเลขเวอร์ชั่นจะมีบอกใน URL ของหน้าเพจ ถ้าไม่มีหมายความว่าคุณอาจจะใช้เอกสารของ development branch ที่ API อาจจะมีการเปลี่ยนแปลง ซึ่งไม่สามารถใช้ร่วมกับ Electron เวอร์ชั่นที่คุณใช้อยู่ได้ เพื่อที่จะดูเอกสารเวอร์ชั่นเก่า [คุณสามารถที่จะดูแท็ก](https://github.com/electron/electron/tree/v1.4.0) ใน GitHub โดยการที่คลิกที่ "เปลี่ยน branches/tags" แล้วเลือกแท็กที่ตรงกับเวอร์ชั่นของคุณ + ## คู่มือ * [แพลตฟอร์มที่รองรับ](tutorial/supported-platforms.md) From 7fdfaa13e75463c2ac817d1f3f2bc30bbdc54ec8 Mon Sep 17 00:00:00 2001 From: ThePooE Date: Wed, 18 Jan 2017 15:05:43 +0700 Subject: [PATCH 101/925] :memo: Thai: fully translated README.md --- docs-translations/th-TH/README.md | 57 ++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/docs-translations/th-TH/README.md b/docs-translations/th-TH/README.md index 97f460f70e..3527338048 100644 --- a/docs-translations/th-TH/README.md +++ b/docs-translations/th-TH/README.md @@ -1,28 +1,42 @@ -กรุณาตรวจสอบว่าคุณกำลังใช้คู่มือที่ตรงกับเวอร์ชั่นของ Electon ของคุณด้วย ตัวเลขเวอร์ชั่นจะมีบอกใน URL ของหน้าเพจ ถ้าไม่มีหมายความว่าคุณอาจจะใช้เอกสารของ development branch ที่ API อาจจะมีการเปลี่ยนแปลง ซึ่งไม่สามารถใช้ร่วมกับ Electron เวอร์ชั่นที่คุณใช้อยู่ได้ เพื่อที่จะดูเอกสารเวอร์ชั่นเก่า [คุณสามารถที่จะดูแท็ก](https://github.com/electron/electron/tree/v1.4.0) ใน GitHub โดยการที่คลิกที่ "เปลี่ยน branches/tags" แล้วเลือกแท็กที่ตรงกับเวอร์ชั่นของคุณ +กรุณาตรวจสอบว่าคุณกำลังใช้คู่มือที่ตรงกับเวอร์ชั่นของ Electon ของคุณด้วย ตัวเลขเวอร์ชั่นจะมีบอกใน URL ของหน้าเพจ ถ้าไม่มีหมายความว่าคุณอาจจะใช้เอกสารของ development branch ที่ API อาจจะมีการเปลี่ยนแปลง ซึ่งไม่สามารถใช้ร่วมกับ Electron เวอร์ชั่นที่คุณใช้อยู่ได้ เพื่อที่จะดูเอกสารเวอร์ชั่นเก่า [คุณสามารถที่จะดูแท็ก](https://github.com/electron/electron/tree/v1.4.0) ใน GitHub โดยการที่คลิกที่ "เรียกดูตามกิ่ง/แท็ก" แล้วเลือกแท็กที่ตรงกับเวอร์ชั่นของคุณ -## คู่มือ +## คำถามที่ถูกถามบ่อย +รวบรวมคำถามที่ถูกถามบ่อย กรุณาอ่านก่อนเปิด issue: + +* [คำถามที่ถูกถามบ่อยเกี่ยวกับ Electron](faq/electron-faq.md) + +## คู่มือ + +* [คำศัพท์เฉพาะ](glossary.md) * [แพลตฟอร์มที่รองรับ](tutorial/supported-platforms.md) +* [ความปลอดภัย](tutorial/security.md) * [การเผยแพร่แอปพลิเคชัน](tutorial/application-distribution.md) * [แนวทางการส่งแอปเข้า Mac App Store](tutorial/mac-app-store-submission-guide.md) +* [คู่มือ Windows Store](tutorial/mac-app-store-submission-guide.md) * [การบรรจุแอปพลิเคชัน](tutorial/application-packaging.md) * [การใช้โมดูลของ Node](tutorial/using-native-node-modules.md) * [การหาข้อผิดพลาดในกระบวนการหลัก](tutorial/debugging-main-process.md) * [การใช้งาน Selenium และ WebDriver](tutorial/using-selenium-and-webdriver.md) * [ส่วนเสริมของ DevTools](tutorial/devtools-extension.md) * [การใช้งานส่วนเสริม Pepper Flash](tutorial/using-pepper-flash-plugin.md) +* [การใช้งานส่วนเสริม Widevine CDM Plugin](tutorial/using-pepper-flash-plugin.md) +* [การทดสอบบน CI (Travis, Jenkins)](tutorial/testing-on-headless-ci.md) +* [การเลนเดอร์นอกหน้าต่าง] (tutorial/offscreen-rendering.md) ## แนะนำ * [เริ่มต้นอย่างคราวๆ](tutorial/quick-start.md) * [การร่วมกันของสภาพแวดล้อมบนเดสทอป](tutorial/desktop-environment-integration.md) * [การตรวจจับเหตุการณ์ออนไลน์หรือออฟไลน์](tutorial/online-offline-events.md) +* [REPL](tutorial/repl.md) ## แหล่งอ้างอิงของ API * [สรุปความ](api/synopsis.md) * [โปรเซสออบเจค](api/process.md) * [คำสั่งสำหรับเปลี่ยนแปลงค่าของ Chrome ที่รองรับ](api/chrome-command-line-switches.md) +* [Variables สภาพแวดล้อม](api/environment-variables.md) ### การปรับแต่ง DOM: @@ -33,43 +47,48 @@ ### โมดูลสำหรับกระบวนการหลัก : * [app](api/app.md) -* [auto-updater](api/auto-updater.md) -* [browser-window](api/browser-window.md) -* [content-tracing](api/content-tracing.md) +* [autoUpdater](api/auto-updater.md) +* [BrowserWindow](api/browser-window.md) +* [contentTracing](api/content-tracing.md) * [dialog](api/dialog.md) -* [global-shortcut](api/global-shortcut.md) -* [ipc (main process)](api/ipc-main-process.md) -* [menu](api/menu.md) -* [menu-item](api/menu-item.md) -* [power-monitor](api/power-monitor.md) -* [power-save-blocker](api/power-save-blocker.md) +* [globalShortcut](api/global-shortcut.md) +* [ipcMain](api/ipc-main.md) +* [Menu](api/menu.md) +* [MenuItem](api/menu-item.md) +* [powerMonitor](api/power-monitor.md) +* [powerSaveBlocker](api/power-save-blocker.md) * [protocol](api/protocol.md) * [session](api/session.md) -* [web-contents](api/web-contents.md) -* [tray](api/tray.md) +* [systemPreferences](api/system-preferences.md) +* [Tray](api/tray.md) +* [webContents](api/web-contents.md) ### โมดูลสำหรับกระบวนการ Renderer (เว็บเพจ): -* [ipc (renderer)](api/ipc-renderer.md) +* [desktopCapturer](api/desktop-capturer.md) +* [ipcRenderer](api/ipc-renderer.md) * [remote](api/remote.md) -* [web-frame](api/web-frame.md) +* [webFrame](api/web-frame.md) -### Modules for Both Processes: +### โมดูลสำหรับทั้งสองกระบวนการ: * [clipboard](api/clipboard.md) -* [crash-reporter](api/crash-reporter.md) -* [native-image](api/native-image.md) +* [crashReporter](api/crash-reporter.md) +* [nativeImage](api/native-image.md) * [screen](api/screen.md) * [shell](api/shell.md) ## การพัฒนา * [ลักษณะการเขียนโค้ด](development/coding-style.md) +* [การใช้ clang-format สำหรับโค้ด C++](development/clang-format.md) * [โครงสร้างไดเรคทอรี่ของซอร์สโค้ด](development/source-code-directory-structure.md) * [ความแตกต่างทางเทคนิคจาก NW.js (หรือ node-webkit)](development/atom-shell-vs-node-webkit.md) * [ภาพรวมการสร้างระบบ](development/build-system-overview.md) * [ขั้นตอนการสร้าง (macOS)](development/build-instructions-osx.md) * [ขั้นตอนการสร้าง (Windows)](development/build-instructions-windows.md) * [ขั้นตอนการสร้าง (Linux)](development/build-instructions-linux.md) -* [Setting Up Symbol Server in debugger](development/setting-up-symbol-server.md) +* [ขั้นตอนการแก้บัค (macOS)](development/debugging-instructions-macos.md) +* [ขั้นตอนการแก้บัค (Windows)](development/debug-instructions-windows.md) * [การติดตั้งเซิร์ฟเวอร์ Symbol Server ใน debugger](development/setting-up-symbol-server.md) +* [ลักษณะการแก้เอกสาร](styleguide.md) From e008d86e765de4826b8feeba56156b177b2033ef Mon Sep 17 00:00:00 2001 From: ThePooE Date: Thu, 19 Jan 2017 14:10:31 +0700 Subject: [PATCH 102/925] :memo: Thai: added electron-faq.md --- docs-translations/th-TH/faq/electron-faq.md | 142 ++++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 docs-translations/th-TH/faq/electron-faq.md diff --git a/docs-translations/th-TH/faq/electron-faq.md b/docs-translations/th-TH/faq/electron-faq.md new file mode 100644 index 0000000000..4a4ddc0a84 --- /dev/null +++ b/docs-translations/th-TH/faq/electron-faq.md @@ -0,0 +1,142 @@ +# คำถามที่ถูกถามบ่อยเกี่ยวกับ Electron (Electron FAQ) + +## เมื่อไหร่ Electron จะอัพเกรดไปเวอร์ชั่นล่าสุดของ Chrome ? + +โดยส่วยมาก Chrome เวอร์ชั่นใน Electron จะโดนอัพเกรดโดยประมาณหนี่งถึงสองอาทิตย์หลังจากมีเวอร์ชั่นใหม่ของ Chrome ที่เสถียร + +Chrome เวอร์ชั่นที่เสถียรเท่านั้นที่จะถูกใช้ ถ้ามีการแก้บัคที่สำคัญในช่องทางเบต้าหรือพัฒนา เราจะนำมันเข้ามาใช้ด้วย + +สำหรับข้อมูลเพิ่มเติม โปรดดูที่ [บทนำความปลอดภัย](tutorial/security.md) + +## เมื่อไหร่ Electron จะอัพเกรดไปเวอร์ชั่นล่าสุดของ Node.js ? + +เมื่อเวอร์ชั่นใหม่ของ Node.js ถูกปล่อยออกมา เราจะรอโดยประมาณหนึ่งเดือนก่อนที่จะอัพเกรด Node.js ที่อยู่ใน Electron เพื่อที่ว่าเราจะได้ลดความเสี่ยงถึงผลกระทบของบัคใน Node.js เวอร์ชั่นใหม่ซึ่งเกิดขึ้นบ่อยมาก + +ความสามารถใหม่ของ Node.js โดยส่วนมากจะมากับการอัพเกรด V8 เนื่องจาก Electron นั้นใช้ V8 ที่มาพร้อมกับ Chrome browser อยู่แล้ว ทำให้ Electron มีความสามารถใหม่ของ JavaScript ที่มาพร้อมกับ Node.js เวอร์ชั่นใหม่อยู่แล้ว + +## วิธีการแบ่งข้อมูลระหว่างเว็ปเพจ + +ในการที่จะแบ่งข้อมูลนั้น (the renderer processes) วิธีการที่เรียบง่ายที่สุดคือการใช้ APIs ของ HTML5 ซี่งใช้ได้อยู่แล้วในเว็ปบราวเซอร์ ทางเลือกอื่นๆที่ดีคือ [Storage API][storage], [`localStorage`][local-storage], +[`sessionStorage`][session-storage], และ [IndexedDB][indexed-db]. + +หรือคุณจะสามารถใช้ระบบ IPC ซึ่งสำหรับ Electron มันจะเก็บ objects ในโปรเซสหลักในรูปของ global variable แล้วจึงเรียกมันจากตัว renderer ผ่านทาง `remote` ของ `electron` โมดูล + +```javascript +// ในโปรเซสหลัก +global.sharedObject = { + someProperty: 'default value' +} +``` + +```javascript +// ในเพจหนึ่ง +require('electron').remote.getGlobal('sharedObject').someProperty = 'new value' +``` + +```javascript +// ในเพจสอง +console.log(require('electron').remote.getGlobal('sharedObject').someProperty) +``` + +## หน้าต่างของแอพฉันหายไปหลังจากไม่กี่นาที + +เหตุการณ์นี้เกิดขึ้นมื่อ variable ที่ใช้เก็บค่าหน้าต่างโดนหน่วยความจำเก็บกวาด + +* [การจัดการหน่วยความจำ][memory-management] +* [ขอบเขตของตัวแปร][variable-scope] + +วิธีการแก้แบบรวดเร็ว: เปลี่ยนตัวแปรให้เป็น global ด้วยการเปลี่ยนจากโค้ดนี้ + +```javascript +const {app, Tray} = require('electron') +app.on('ready', () => { + const tray = new Tray('/path/to/icon.png') + tray.setTitle('hello world') +}) +``` + +เป็น : + +```javascript +const {app, Tray} = require('electron') +let tray = null +app.on('ready', () => { + tray = new Tray('/path/to/icon.png') + tray.setTitle('hello world') +}) +``` + +## ไม่สามารถใช้ jQuery/RequireJS/Meteor/AngularJS ใน Electron + +เนื่องจากการรวบรวม Node.js เข้าไปใน Electron จึงทำให้เกิดการใส่อักขระเพิ่มเตืมลงไปใน DOM เช่น `module`, `export`, `require` + +มันทำให้เกิดปัญหากับ library อื่นๆที่ต้องการจะใช้อักขระตัวเดียวกัน + +ในการแก้ปัญหานี้ คุณจะต้องปิด node ใน Electron: + +```javascript +// ในโปรเซสหลัก +const {BrowserWindow} = require('electron') +let win = new BrowserWindow({ + webPreferences: { + nodeIntegration: false + } +}) +win.show() +``` + +แต่ถ้าคุณยังต้องการที่จะใช้ Node.js และ API ของ Electron คุณจะค้องเปลี่ยนชื่อของอักขระในเพจดังนี้ : + +```html + + + + +``` + +## `require('electron').xxx` is undefined. + +ในตอนที่คุณใช้โมดูลที่มาพร้อมกับ Electron คุณอาจจะเจอปัญหาดังกล่าว: + +``` +> require('electron').webFrame.setZoomFactor(1.0); +Uncaught TypeError: Cannot read property 'setZoomLevel' of undefined +``` + +มันเปิดมาจากคุณได้ลง [module npm `electron`][electron-module] ในเครื่องไม่ว่าจะเป็น locally หรือ globally ซึ่งมันจะทับโมดูลที่มาพร้อมกับ Electron + +เพื่อตรวจสอบว่าคุณกำลังใช้โมดูลที่ถูกต้อง คุณสามารถที่จะส่ง command ที่จะปริ้น path ของ `electron` ได้: + +```javascript +console.log(require.resolve('electron')) +``` + +แล้วก้เช็คว่าผลลัพท์อยู่ในรูปของ: + +``` +"/path/to/Electron.app/Contents/Resources/atom.asar/renderer/api/lib/exports/electron.js" +``` + +ถ้าผลลัพท์ที่ได้จากการส่ง command อยู่ในรูปแบบ `node_modules/electron/index.js` คุณจะต้องลบโมดูล `electron` ใน npm หรือไม่ก็เปลี่ยนชื่อมัน + +```bash +npm uninstall electron +npm uninstall -g electron +``` + +ถ้าหากว่าคุณกำลังใช้โมดูลที่มาพร้อมกับ Electron แล้วยังเกิดข้อผิดผลาดดังกล่าว มีความเป็นไปได้สูงว่าคุณกำลังใช้โมดูลในโปรเซสที่ผิด + +ยกตัวอย่างเช่น `electron.app` จะสามารถใช้ได้ในโปรเซสหลักเท่านั้น แต่ว่าในขณะเดียวกัน `electron.webFrame` นั้นใช้ได้ในโปรเซส renderer เท่านั้น + +[memory-management]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management +[variable-scope]: https://msdn.microsoft.com/library/bzt2dkta(v=vs.94).aspx +[electron-module]: https://www.npmjs.com/package/electron +[storage]: https://developer.mozilla.org/en-US/docs/Web/API/Storage +[local-storage]: https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage +[session-storage]: https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage +[indexed-db]: https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API From a78cd79a58c221aeefd0fdeffd687d43304872b9 Mon Sep 17 00:00:00 2001 From: ThePooE Date: Thu, 19 Jan 2017 14:16:06 +0700 Subject: [PATCH 103/925] :memo: Thai: add link from README.md to the translated version --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7d6bfe7433..cb3583afdf 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ contains documents describing how to build and contribute to Electron. - [Simplified Chinese](https://github.com/electron/electron/tree/master/docs-translations/zh-CN) - [Traditional Chinese](https://github.com/electron/electron/tree/master/docs-translations/zh-TW) - [Turkish](https://github.com/electron/electron/tree/master/docs-translations/tr-TR) +- [Thai](https://github.com/electron/electron/tree/master/docs-Translations/th-TH) - [Ukrainian](https://github.com/electron/electron/tree/master/docs-translations/uk-UA) - [Russian](https://github.com/electron/electron/tree/master/docs-translations/ru-RU) - [French](https://github.com/electron/electron/tree/master/docs-translations/fr-FR) From 9ce93e6cac9ecba2c1b4fb4c24d113a2273e6c8d Mon Sep 17 00:00:00 2001 From: ThePooE Date: Thu, 19 Jan 2017 14:37:00 +0700 Subject: [PATCH 104/925] :memo: Thai: add English placeholders for files to be translated --- docs-translations/th-TH/README.md | 2 +- docs-translations/th-TH/api/accelerator.md | 71 + docs-translations/th-TH/api/app.md | 908 ++++++++++++ docs-translations/th-TH/api/auto-updater.md | 139 ++ .../th-TH/api/browser-window-proxy.md | 53 + docs-translations/th-TH/api/browser-window.md | 1272 +++++++++++++++++ .../th-TH/api/chrome-command-line-switches.md | 189 +++ docs-translations/th-TH/api/client-request.md | 192 +++ docs-translations/th-TH/api/clipboard.md | 169 +++ .../th-TH/api/content-tracing.md | 173 +++ docs-translations/th-TH/api/cookies.md | 106 ++ docs-translations/th-TH/api/crash-reporter.md | 112 ++ docs-translations/th-TH/api/debugger.md | 83 ++ .../th-TH/api/desktop-capturer.md | 76 + docs-translations/th-TH/api/dialog.md | 161 +++ docs-translations/th-TH/api/download-item.md | 168 +++ .../th-TH/api/environment-variables.md | 87 ++ docs-translations/th-TH/api/file-object.md | 33 + .../th-TH/api/frameless-window.md | 143 ++ .../th-TH/api/global-shortcut.md | 75 + .../th-TH/api/incoming-message.md | 74 + docs-translations/th-TH/api/ipc-main.md | 99 ++ docs-translations/th-TH/api/ipc-renderer.md | 81 ++ docs-translations/th-TH/api/locales.md | 139 ++ docs-translations/th-TH/api/menu-item.md | 116 ++ docs-translations/th-TH/api/menu.md | 403 ++++++ docs-translations/th-TH/api/native-image.md | 256 ++++ docs-translations/th-TH/api/net.md | 71 + docs-translations/th-TH/api/power-monitor.md | 41 + .../th-TH/api/power-save-blocker.md | 56 + docs-translations/th-TH/api/process.md | 118 ++ docs-translations/th-TH/api/protocol.md | 297 ++++ docs-translations/th-TH/api/remote.md | 169 +++ docs-translations/th-TH/api/screen.md | 122 ++ docs-translations/th-TH/api/session.md | 402 ++++++ docs-translations/th-TH/api/shell.md | 86 ++ .../th-TH/api/structures/bluetooth-device.md | 4 + .../api/structures/certificate-principal.md | 8 + .../th-TH/api/structures/certificate.md | 12 + .../th-TH/api/structures/cookie.md | 14 + .../th-TH/api/structures/crash-report.md | 4 + .../api/structures/desktop-capturer-source.md | 14 + .../th-TH/api/structures/display.md | 19 + .../th-TH/api/structures/file-filter.md | 4 + .../api/structures/jump-list-category.md | 21 + .../th-TH/api/structures/jump-list-item.md | 28 + .../api/structures/memory-usage-details.md | 8 + .../th-TH/api/structures/mime-typed-buffer.md | 4 + .../th-TH/api/structures/rectangle.md | 6 + .../structures/remove-client-certificate.md | 5 + .../th-TH/api/structures/remove-password.md | 15 + .../th-TH/api/structures/shortcut-details.md | 15 + .../th-TH/api/structures/task.md | 14 + .../th-TH/api/structures/thumbar-button.md | 21 + .../th-TH/api/structures/upload-blob.md | 4 + .../th-TH/api/structures/upload-data.md | 6 + .../api/structures/upload-file-system.md | 9 + .../th-TH/api/structures/upload-file.md | 9 + .../th-TH/api/structures/upload-raw-data.md | 4 + docs-translations/th-TH/api/synopsis.md | 95 ++ .../th-TH/api/system-preferences.md | 239 ++++ docs-translations/th-TH/api/tray.md | 247 ++++ docs-translations/th-TH/api/web-contents.md | 1241 ++++++++++++++++ docs-translations/th-TH/api/web-frame.md | 193 +++ docs-translations/th-TH/api/web-request.md | 205 +++ docs-translations/th-TH/api/webview-tag.md | 910 ++++++++++++ docs-translations/th-TH/api/window-open.md | 43 + .../development/atom-shell-vs-node-webkit.md | 52 + .../development/build-instructions-linux.md | 204 +++ .../development/build-instructions-osx.md | 64 + .../development/build-instructions-windows.md | 146 ++ .../development/build-system-overview.md | 122 ++ .../th-TH/development/clang-format.md | 34 + .../th-TH/development/coding-style.md | 55 + .../development/debug-instructions-windows.md | 93 ++ .../debugging-instructions-macos.md | 125 ++ .../th-TH/development/releasing.md | 101 ++ .../development/setting-up-symbol-server.md | 56 + .../source-code-directory-structure.md | 93 ++ .../th-TH/{faq/electron-faq.md => faq.md} | 0 docs-translations/th-TH/glossary.md | 153 ++ docs-translations/th-TH/styleguide.md | 246 ++++ docs-translations/th-TH/tutorial/about.md | 57 + .../th-TH/tutorial/accessibility.md | 33 + .../tutorial/application-distribution.md | 176 +++ .../th-TH/tutorial/application-packaging.md | 185 +++ .../debugging-main-process-node-inspector.md | 131 ++ .../tutorial/debugging-main-process-vscode.md | 34 + .../th-TH/tutorial/debugging-main-process.md | 33 + .../desktop-environment-integration.md | 399 ++++++ .../th-TH/tutorial/devtools-extension.md | 66 + .../th-TH/tutorial/electron-versioning.md | 21 + .../mac-app-store-submission-guide.md | 266 ++++ .../th-TH/tutorial/offscreen-rendering.md | 57 + .../th-TH/tutorial/online-offline-events.md | 90 ++ .../tutorial/planned-breaking-changes.md | 159 +++ .../th-TH/tutorial/quick-start.md | 244 ++++ docs-translations/th-TH/tutorial/repl.md | 26 + docs-translations/th-TH/tutorial/security.md | 96 ++ .../th-TH/tutorial/supported-platforms.md | 31 + .../th-TH/tutorial/testing-on-headless-ci.md | 60 + .../tutorial/using-native-node-modules.md | 104 ++ .../tutorial/using-pepper-flash-plugin.md | 82 ++ .../tutorial/using-selenium-and-webdriver.md | 172 +++ .../tutorial/using-widevine-cdm-plugin.md | 85 ++ .../th-TH/tutorial/windows-store-guide.md | 161 +++ 106 files changed, 14474 insertions(+), 1 deletion(-) create mode 100644 docs-translations/th-TH/api/accelerator.md create mode 100644 docs-translations/th-TH/api/app.md create mode 100644 docs-translations/th-TH/api/auto-updater.md create mode 100644 docs-translations/th-TH/api/browser-window-proxy.md create mode 100644 docs-translations/th-TH/api/browser-window.md create mode 100644 docs-translations/th-TH/api/chrome-command-line-switches.md create mode 100644 docs-translations/th-TH/api/client-request.md create mode 100644 docs-translations/th-TH/api/clipboard.md create mode 100644 docs-translations/th-TH/api/content-tracing.md create mode 100644 docs-translations/th-TH/api/cookies.md create mode 100644 docs-translations/th-TH/api/crash-reporter.md create mode 100644 docs-translations/th-TH/api/debugger.md create mode 100644 docs-translations/th-TH/api/desktop-capturer.md create mode 100644 docs-translations/th-TH/api/dialog.md create mode 100644 docs-translations/th-TH/api/download-item.md create mode 100644 docs-translations/th-TH/api/environment-variables.md create mode 100644 docs-translations/th-TH/api/file-object.md create mode 100644 docs-translations/th-TH/api/frameless-window.md create mode 100644 docs-translations/th-TH/api/global-shortcut.md create mode 100644 docs-translations/th-TH/api/incoming-message.md create mode 100644 docs-translations/th-TH/api/ipc-main.md create mode 100644 docs-translations/th-TH/api/ipc-renderer.md create mode 100644 docs-translations/th-TH/api/locales.md create mode 100644 docs-translations/th-TH/api/menu-item.md create mode 100644 docs-translations/th-TH/api/menu.md create mode 100644 docs-translations/th-TH/api/native-image.md create mode 100644 docs-translations/th-TH/api/net.md create mode 100644 docs-translations/th-TH/api/power-monitor.md create mode 100644 docs-translations/th-TH/api/power-save-blocker.md create mode 100644 docs-translations/th-TH/api/process.md create mode 100644 docs-translations/th-TH/api/protocol.md create mode 100644 docs-translations/th-TH/api/remote.md create mode 100644 docs-translations/th-TH/api/screen.md create mode 100644 docs-translations/th-TH/api/session.md create mode 100644 docs-translations/th-TH/api/shell.md create mode 100644 docs-translations/th-TH/api/structures/bluetooth-device.md create mode 100644 docs-translations/th-TH/api/structures/certificate-principal.md create mode 100644 docs-translations/th-TH/api/structures/certificate.md create mode 100644 docs-translations/th-TH/api/structures/cookie.md create mode 100644 docs-translations/th-TH/api/structures/crash-report.md create mode 100644 docs-translations/th-TH/api/structures/desktop-capturer-source.md create mode 100644 docs-translations/th-TH/api/structures/display.md create mode 100644 docs-translations/th-TH/api/structures/file-filter.md create mode 100644 docs-translations/th-TH/api/structures/jump-list-category.md create mode 100644 docs-translations/th-TH/api/structures/jump-list-item.md create mode 100644 docs-translations/th-TH/api/structures/memory-usage-details.md create mode 100644 docs-translations/th-TH/api/structures/mime-typed-buffer.md create mode 100644 docs-translations/th-TH/api/structures/rectangle.md create mode 100644 docs-translations/th-TH/api/structures/remove-client-certificate.md create mode 100644 docs-translations/th-TH/api/structures/remove-password.md create mode 100644 docs-translations/th-TH/api/structures/shortcut-details.md create mode 100644 docs-translations/th-TH/api/structures/task.md create mode 100644 docs-translations/th-TH/api/structures/thumbar-button.md create mode 100644 docs-translations/th-TH/api/structures/upload-blob.md create mode 100644 docs-translations/th-TH/api/structures/upload-data.md create mode 100644 docs-translations/th-TH/api/structures/upload-file-system.md create mode 100644 docs-translations/th-TH/api/structures/upload-file.md create mode 100644 docs-translations/th-TH/api/structures/upload-raw-data.md create mode 100644 docs-translations/th-TH/api/synopsis.md create mode 100644 docs-translations/th-TH/api/system-preferences.md create mode 100644 docs-translations/th-TH/api/tray.md create mode 100644 docs-translations/th-TH/api/web-contents.md create mode 100644 docs-translations/th-TH/api/web-frame.md create mode 100644 docs-translations/th-TH/api/web-request.md create mode 100644 docs-translations/th-TH/api/webview-tag.md create mode 100644 docs-translations/th-TH/api/window-open.md create mode 100644 docs-translations/th-TH/development/atom-shell-vs-node-webkit.md create mode 100644 docs-translations/th-TH/development/build-instructions-linux.md create mode 100644 docs-translations/th-TH/development/build-instructions-osx.md create mode 100644 docs-translations/th-TH/development/build-instructions-windows.md create mode 100644 docs-translations/th-TH/development/build-system-overview.md create mode 100644 docs-translations/th-TH/development/clang-format.md create mode 100644 docs-translations/th-TH/development/coding-style.md create mode 100644 docs-translations/th-TH/development/debug-instructions-windows.md create mode 100644 docs-translations/th-TH/development/debugging-instructions-macos.md create mode 100644 docs-translations/th-TH/development/releasing.md create mode 100644 docs-translations/th-TH/development/setting-up-symbol-server.md create mode 100644 docs-translations/th-TH/development/source-code-directory-structure.md rename docs-translations/th-TH/{faq/electron-faq.md => faq.md} (100%) create mode 100644 docs-translations/th-TH/glossary.md create mode 100644 docs-translations/th-TH/styleguide.md create mode 100644 docs-translations/th-TH/tutorial/about.md create mode 100644 docs-translations/th-TH/tutorial/accessibility.md create mode 100644 docs-translations/th-TH/tutorial/application-distribution.md create mode 100644 docs-translations/th-TH/tutorial/application-packaging.md create mode 100644 docs-translations/th-TH/tutorial/debugging-main-process-node-inspector.md create mode 100644 docs-translations/th-TH/tutorial/debugging-main-process-vscode.md create mode 100644 docs-translations/th-TH/tutorial/debugging-main-process.md create mode 100644 docs-translations/th-TH/tutorial/desktop-environment-integration.md create mode 100644 docs-translations/th-TH/tutorial/devtools-extension.md create mode 100644 docs-translations/th-TH/tutorial/electron-versioning.md create mode 100644 docs-translations/th-TH/tutorial/mac-app-store-submission-guide.md create mode 100644 docs-translations/th-TH/tutorial/offscreen-rendering.md create mode 100644 docs-translations/th-TH/tutorial/online-offline-events.md create mode 100644 docs-translations/th-TH/tutorial/planned-breaking-changes.md create mode 100644 docs-translations/th-TH/tutorial/quick-start.md create mode 100644 docs-translations/th-TH/tutorial/repl.md create mode 100644 docs-translations/th-TH/tutorial/security.md create mode 100644 docs-translations/th-TH/tutorial/supported-platforms.md create mode 100644 docs-translations/th-TH/tutorial/testing-on-headless-ci.md create mode 100644 docs-translations/th-TH/tutorial/using-native-node-modules.md create mode 100644 docs-translations/th-TH/tutorial/using-pepper-flash-plugin.md create mode 100644 docs-translations/th-TH/tutorial/using-selenium-and-webdriver.md create mode 100644 docs-translations/th-TH/tutorial/using-widevine-cdm-plugin.md create mode 100644 docs-translations/th-TH/tutorial/windows-store-guide.md diff --git a/docs-translations/th-TH/README.md b/docs-translations/th-TH/README.md index 3527338048..97a6f3f868 100644 --- a/docs-translations/th-TH/README.md +++ b/docs-translations/th-TH/README.md @@ -4,7 +4,7 @@ รวบรวมคำถามที่ถูกถามบ่อย กรุณาอ่านก่อนเปิด issue: -* [คำถามที่ถูกถามบ่อยเกี่ยวกับ Electron](faq/electron-faq.md) +* [คำถามที่ถูกถามบ่อยเกี่ยวกับ Electron](faq.md) ## คู่มือ diff --git a/docs-translations/th-TH/api/accelerator.md b/docs-translations/th-TH/api/accelerator.md new file mode 100644 index 0000000000..f90e409043 --- /dev/null +++ b/docs-translations/th-TH/api/accelerator.md @@ -0,0 +1,71 @@ +# Accelerator + +> Define keyboard shortcuts. + +Accelerators are Strings that can contain multiple modifiers and key codes, +combined by the `+` character, and are used to define keyboard shortcuts +throughout your application. + +Examples: + +* `CommandOrControl+A` +* `CommandOrControl+Shift+Z` + +Shortcuts are registered with the [`globalShortcut`](global-shortcut.md) module +using the [`register`](global-shortcut.md#globalshortcutregisteraccelerator-callback) +method, i.e. + +```javascript +const {app, globalShortcut} = require('electron') + +app.on('ready', () => { + // Register a 'CommandOrControl+Y' shortcut listener. + globalShortcut.register('CommandOrControl+Y', () => { + // Do stuff when Y and either Command/Control is pressed. + }) +}) +``` + +## Platform notice + +On Linux and Windows, the `Command` key does not have any effect so +use `CommandOrControl` which represents `Command` on macOS and `Control` on +Linux and Windows to define some accelerators. + +Use `Alt` instead of `Option`. The `Option` key only exists on macOS, whereas +the `Alt` key is available on all platforms. + +The `Super` key is mapped to the `Windows` key on Windows and Linux and +`Cmd` on macOS. + +## Available modifiers + +* `Command` (or `Cmd` for short) +* `Control` (or `Ctrl` for short) +* `CommandOrControl` (or `CmdOrCtrl` for short) +* `Alt` +* `Option` +* `AltGr` +* `Shift` +* `Super` + +## Available key codes + +* `0` to `9` +* `A` to `Z` +* `F1` to `F24` +* Punctuations like `~`, `!`, `@`, `#`, `$`, etc. +* `Plus` +* `Space` +* `Tab` +* `Backspace` +* `Delete` +* `Insert` +* `Return` (or `Enter` as alias) +* `Up`, `Down`, `Left` and `Right` +* `Home` and `End` +* `PageUp` and `PageDown` +* `Escape` (or `Esc` for short) +* `VolumeUp`, `VolumeDown` and `VolumeMute` +* `MediaNextTrack`, `MediaPreviousTrack`, `MediaStop` and `MediaPlayPause` +* `PrintScreen` diff --git a/docs-translations/th-TH/api/app.md b/docs-translations/th-TH/api/app.md new file mode 100644 index 0000000000..3c7670655a --- /dev/null +++ b/docs-translations/th-TH/api/app.md @@ -0,0 +1,908 @@ +# app + +> Control your application's event lifecycle. + +Process: [Main](../glossary.md#main-process) + +The following example shows how to quit the application when the last window is +closed: + +```javascript +const {app} = require('electron') +app.on('window-all-closed', () => { + app.quit() +}) +``` + +## Events + +The `app` object emits the following events: + +### Event: 'will-finish-launching' + +Emitted when the application has finished basic startup. On Windows and Linux, +the `will-finish-launching` event is the same as the `ready` event; on macOS, +this event represents the `applicationWillFinishLaunching` notification of +`NSApplication`. You would usually set up listeners for the `open-file` and +`open-url` events here, and start the crash reporter and auto updater. + +In most cases, you should just do everything in the `ready` event handler. + +### Event: 'ready' + +Returns: + +* `launchInfo` Object _macOS_ + +Emitted when Electron has finished initializing. On macOS, `launchInfo` holds +the `userInfo` of the `NSUserNotification` that was used to open the application, +if it was launched from Notification Center. You can call `app.isReady()` to +check if this event has already fired. + +### Event: 'window-all-closed' + +Emitted when all windows have been closed. + +If you do not subscribe to this event and all windows are closed, the default +behavior is to quit the app; however, if you subscribe, you control whether the +app quits or not. If the user pressed `Cmd + Q`, or the developer called +`app.quit()`, Electron will first try to close all the windows and then emit the +`will-quit` event, and in this case the `window-all-closed` event would not be +emitted. + +### Event: 'before-quit' + +Returns: + +* `event` Event + +Emitted before the application starts closing its windows. +Calling `event.preventDefault()` will prevent the default behaviour, which is +terminating the application. + +**Note:** If application quit was initiated by `autoUpdater.quitAndInstall()` +then `before-quit` is emitted *after* emitting `close` event on all windows and +closing them. + +### Event: 'will-quit' + +Returns: + +* `event` Event + +Emitted when all windows have been closed and the application will quit. +Calling `event.preventDefault()` will prevent the default behaviour, which is +terminating the application. + +See the description of the `window-all-closed` event for the differences between +the `will-quit` and `window-all-closed` events. + +### Event: 'quit' + +Returns: + +* `event` Event +* `exitCode` Integer + +Emitted when the application is quitting. + +### Event: 'open-file' _macOS_ + +Returns: + +* `event` Event +* `path` String + +Emitted when the user wants to open a file with the application. The `open-file` +event is usually emitted when the application is already open and the OS wants +to reuse the application to open the file. `open-file` is also emitted when a +file is dropped onto the dock and the application is not yet running. Make sure +to listen for the `open-file` event very early in your application startup to +handle this case (even before the `ready` event is emitted). + +You should call `event.preventDefault()` if you want to handle this event. + +On Windows, you have to parse `process.argv` (in the main process) to get the +filepath. + +### Event: 'open-url' _macOS_ + +Returns: + +* `event` Event +* `url` String + +Emitted when the user wants to open a URL with the application. Your application's +`Info.plist` file must define the url scheme within the `CFBundleURLTypes` key, and +set `NSPrincipalClass` to `AtomApplication`. + +You should call `event.preventDefault()` if you want to handle this event. + +### Event: 'activate' _macOS_ + +Returns: + +* `event` Event +* `hasVisibleWindows` Boolean + +Emitted when the application is activated, which usually happens when the user +clicks on the application's dock icon. + +### Event: 'continue-activity' _macOS_ + +Returns: + +* `event` Event +* `type` String - A string identifying the activity. Maps to + [`NSUserActivity.activityType`][activity-type]. +* `userInfo` Object - Contains app-specific state stored by the activity on + another device. + +Emitted during [Handoff][handoff] when an activity from a different device wants +to be resumed. You should call `event.preventDefault()` if you want to handle +this event. + +A user activity can be continued only in an app that has the same developer Team +ID as the activity's source app and that supports the activity's type. +Supported activity types are specified in the app's `Info.plist` under the +`NSUserActivityTypes` key. + +### Event: 'browser-window-blur' + +Returns: + +* `event` Event +* `window` BrowserWindow + +Emitted when a [browserWindow](browser-window.md) gets blurred. + +### Event: 'browser-window-focus' + +Returns: + +* `event` Event +* `window` BrowserWindow + +Emitted when a [browserWindow](browser-window.md) gets focused. + +### Event: 'browser-window-created' + +Returns: + +* `event` Event +* `window` BrowserWindow + +Emitted when a new [browserWindow](browser-window.md) is created. + +### Event: 'web-contents-created' + +Returns: + +* `event` Event +* `webContents` WebContents + +Emitted when a new [webContents](web-contents.md) is created. + +### Event: 'certificate-error' + +Returns: + +* `event` Event +* `webContents` [WebContents](web-contents.md) +* `url` String +* `error` String - The error code +* `certificate` [Certificate](structures/certificate.md) +* `callback` Function + * `isTrusted` Boolean - Whether to consider the certificate as trusted + +Emitted when failed to verify the `certificate` for `url`, to trust the +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) + } else { + callback(false) + } +}) +``` + +### Event: 'select-client-certificate' + +Returns: + +* `event` Event +* `webContents` [WebContents](web-contents.md) +* `url` URL +* `certificateList` [Certificate[]](structures/certificate.md) +* `callback` Function + * `certificate` [Certificate](structures/certificate.md) (optional) + +Emitted when a client certificate is requested. + +The `url` corresponds to the navigation entry requesting the client certificate +and `callback` can be called with an entry filtered from the list. Using +`event.preventDefault()` prevents the application from using the first +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: 'login' + +Returns: + +* `event` Event +* `webContents` [WebContents](web-contents.md) +* `request` Object + * `method` String + * `url` URL + * `referrer` URL +* `authInfo` Object + * `isProxy` Boolean + * `scheme` String + * `host` String + * `port` Integer + * `realm` String +* `callback` Function + * `username` String + * `password` String + +Emitted when `webContents` wants to do basic auth. + +The default behavior is to cancel all authentications, to override this you +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: 'gpu-process-crashed' + +Returns: + +* `event` Event +* `killed` Boolean + +Emitted when the gpu process crashes or is killed. + +### Event: 'accessibility-support-changed' _macOS_ _Windows_ + +Returns: + +* `event` Event +* `accessibilitySupportEnabled` Boolean - `true` when Chrome's accessibility + support is enabled, `false` otherwise. + +Emitted when Chrome's accessibility support changes. This event fires when +assistive technologies, such as screen readers, are enabled or disabled. +See https://www.chromium.org/developers/design-documents/accessibility for more +details. + +## Methods + +The `app` object has the following methods: + +**Note:** Some methods are only available on specific operating systems and are +labeled as such. + +### `app.quit()` + +Try to close all windows. The `before-quit` event will be emitted first. If all +windows are successfully closed, the `will-quit` event will be emitted and by +default the application will terminate. + +This method guarantees that all `beforeunload` and `unload` event handlers are +correctly executed. It is possible that a window cancels the quitting by +returning `false` in the `beforeunload` event handler. + +### `app.exit([exitCode])` + +* `exitCode` Integer (optional) + +Exits immediately with `exitCode`. `exitCode` defaults to 0. + +All windows will be closed immediately without asking user and the `before-quit` +and `will-quit` events will not be emitted. + +### `app.relaunch([options])` + +* `options` Object (optional) + * `args` String[] - (optional) + * `execPath` String (optional) + +Relaunches the app when current instance exits. + +By default the new instance will use the same working directory and command line +arguments with current instance. When `args` is specified, the `args` will be +passed as command line arguments instead. When `execPath` is specified, the +`execPath` will be executed for relaunch instead of current app. + +Note that this method does not quit the app when executed, you have to call +`app.quit` or `app.exit` after calling `app.relaunch` to make the app restart. + +When `app.relaunch` is called for multiple times, multiple instances will be +started after current instance exited. + +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).concat(['--relaunch'])}) +app.exit(0) +``` + +### `app.isReady()` + +Returns `Boolean` - `true` if Electron has finished initializing, `false` otherwise. + +### `app.focus()` + +On Linux, focuses on the first visible window. On macOS, makes the application +the active app. On Windows, focuses on the application's first window. + +### `app.hide()` _macOS_ + +Hides all application windows without minimizing them. + +### `app.show()` _macOS_ + +Shows application windows after they were hidden. Does not automatically focus +them. + +### `app.getAppPath()` + +Returns `String` - The current application directory. + +### `app.getPath(name)` + +* `name` String + +Returns `String` - A path to a special directory or file associated with `name`. On +failure an `Error` is thrown. + +You can request the following paths by the name: + +* `home` User's home directory. +* `appData` Per-user application data directory, which by default points to: + * `%APPDATA%` on Windows + * `$XDG_CONFIG_HOME` or `~/.config` on Linux + * `~/Library/Application Support` on macOS +* `userData` The directory for storing your app's configuration files, which by + default it is the `appData` directory appended with your app's name. +* `temp` Temporary directory. +* `exe` The current executable file. +* `module` The `libchromiumcontent` library. +* `desktop` The current user's Desktop directory. +* `documents` Directory for a user's "My Documents". +* `downloads` Directory for a user's downloads. +* `music` Directory for a user's music. +* `pictures` Directory for a user's pictures. +* `videos` Directory for a user's videos. +* `pepperFlashSystemPlugin` Full path to the system version of the Pepper Flash plugin. + +### `app.setPath(name, path)` + +* `name` String +* `path` String + +Overrides the `path` to a special directory or file associated with `name`. If +the path specifies a directory that does not exist, the directory will be +created by this method. On failure an `Error` is thrown. + +You can only override paths of a `name` defined in `app.getPath`. + +By default, web pages' cookies and caches will be stored under the `userData` +directory. If you want to change this location, you have to override the +`userData` path before the `ready` event of the `app` module is emitted. + +### `app.getVersion()` + +Returns `String` - The version of the loaded application. If no version is found in the +application's `package.json` file, the version of the current bundle or +executable is returned. + +### `app.getName()` + +Returns `String` - The current application's name, which is the name in the application's +`package.json` file. + +Usually the `name` field of `package.json` is a short lowercased name, according +to the npm modules spec. You should usually also specify a `productName` +field, which is your application's full capitalized name, and which will be +preferred over `name` by Electron. + +### `app.setName(name)` + +* `name` String + +Overrides the current application's name. + +### `app.getLocale()` + +Returns `String` - The current application locale. Possible return values are documented +[here](locales.md). + +**Note:** When distributing your packaged app, you have to also ship the +`locales` folder. + +**Note:** On Windows you have to call it after the `ready` events gets emitted. + +### `app.addRecentDocument(path)` _macOS_ _Windows_ + +* `path` String + +Adds `path` to the recent documents list. + +This list is managed by the OS. On Windows you can visit the list from the task +bar, and on macOS you can visit it from dock menu. + +### `app.clearRecentDocuments()` _macOS_ _Windows_ + +Clears the recent documents list. + +### `app.setAsDefaultProtocolClient(protocol[, path, args])` _macOS_ _Windows_ + +* `protocol` String - The name of your protocol, without `://`. If you want your + app to handle `electron://` links, call this method with `electron` as the + parameter. +* `path` String (optional) _Windows_ - Defaults to `process.execPath` +* `args` String[] (optional) _Windows_ - Defaults to an empty array + +Returns `Boolean` - Whether the call succeeded. + +This method sets the current executable as the default handler for a protocol +(aka URI scheme). It allows you to integrate your app deeper into the operating +system. Once registered, all links with `your-protocol://` will be opened with +the current executable. The whole link, including protocol, will be passed to +your application as a parameter. + +On Windows you can provide optional parameters path, the path to your executable, +and args, an array of arguments to be passed to your executable when it launches. + +**Note:** On macOS, you can only register protocols that have been added to +your app's `info.plist`, which can not be modified at runtime. You can however +change the file with a simple text editor or script during build time. +Please refer to [Apple's documentation][CFBundleURLTypes] for details. + +The API uses the Windows Registry and LSSetDefaultHandlerForURLScheme internally. + +### `app.removeAsDefaultProtocolClient(protocol[, path, args])` _macOS_ _Windows_ + +* `protocol` String - The name of your protocol, without `://`. +* `path` String (optional) _Windows_ - Defaults to `process.execPath` +* `args` String[] (optional) _Windows_ - Defaults to an empty array + +Returns `Boolean` - Whether the call succeeded. + +This method checks if the current executable as the default handler for a +protocol (aka URI scheme). If so, it will remove the app as the default handler. + + +### `app.isDefaultProtocolClient(protocol[, path, args])` _macOS_ _Windows_ + +* `protocol` String - The name of your protocol, without `://`. +* `path` String (optional) _Windows_ - Defaults to `process.execPath` +* `args` String[] (optional) _Windows_ - Defaults to an empty array + +Returns `Boolean` + +This method checks if the current executable is the default handler for a protocol +(aka URI scheme). If so, it will return true. Otherwise, it will return false. + +**Note:** On macOS, you can use this method to check if the app has been +registered as the default protocol handler for a protocol. You can also verify +this by checking `~/Library/Preferences/com.apple.LaunchServices.plist` on the +macOS machine. Please refer to +[Apple's documentation][LSCopyDefaultHandlerForURLScheme] for details. + +The API uses the Windows Registry and LSCopyDefaultHandlerForURLScheme internally. + +### `app.setUserTasks(tasks)` _Windows_ + +* `tasks` [Task[]](structures/task.md) - Array of `Task` objects + +Adds `tasks` to the [Tasks][tasks] category of the JumpList on Windows. + +`tasks` is an array of [`Task`](structures/task.md) objects. + +Returns `Boolean` - Whether the call succeeded. + +**Note:** If you'd like to customize the Jump List even more use +`app.setJumpList(categories)` instead. + +### `app.getJumpListSettings()` _Windows_ + +Returns `Object`: + +* `minItems` Integer - The minimum number of items that will be shown in the + Jump List (for a more detailed description of this value see the + [MSDN docs][JumpListBeginListMSDN]). +* `removedItems` [JumpListItem[]](structures/jump-list-item.md) - Array of `JumpListItem` objects that correspond to + items that the user has explicitly removed from custom categories in the + Jump List. These items must not be re-added to the Jump List in the **next** + call to `app.setJumpList()`, Windows will not display any custom category + that contains any of the removed items. + +### `app.setJumpList(categories)` _Windows_ + +* `categories` [JumpListCategory[]](structures/jump-list-category.md) or `null` - Array of `JumpListCategory` objects. + +Sets or removes a custom Jump List for the application, and returns one of the +following strings: + +* `ok` - Nothing went wrong. +* `error` - One or more errors occurred, enable runtime logging to figure out + the likely cause. +* `invalidSeparatorError` - An attempt was made to add a separator to a + custom category in the Jump List. Separators are only allowed in the + standard `Tasks` category. +* `fileTypeRegistrationError` - An attempt was made to add a file link to + the Jump List for a file type the app isn't registered to handle. +* `customCategoryAccessDeniedError` - Custom categories can't be added to the + Jump List due to user privacy or group policy settings. + +If `categories` is `null` the previously set custom Jump List (if any) will be +replaced by the standard Jump List for the app (managed by Windows). + +**Note:** If a `JumpListCategory` object has neither the `type` nor the `name` +property set then its `type` is assumed to be `tasks`. If the `name` property +is set but the `type` property is omitted then the `type` is assumed to be +`custom`. + +**Note:** Users can remove items from custom categories, and Windows will not +allow a removed item to be added back into a custom category until **after** +the next successful call to `app.setJumpList(categories)`. Any attempt to +re-add a removed item to a custom category earlier than that will result in the +entire custom category being omitted from the Jump List. The list of removed +items can be obtained using `app.getJumpListSettings()`. + +Here's a very simple example of creating a custom Jump List: + +```javascript +const {app} = require('electron') + +app.setJumpList([ + { + type: 'custom', + name: 'Recent Projects', + items: [ + { type: 'file', path: 'C:\\Projects\\project1.proj' }, + { type: 'file', path: 'C:\\Projects\\project2.proj' } + ] + }, + { // has a name so `type` is assumed to be "custom" + name: 'Tools', + items: [ + { + type: 'task', + title: 'Tool A', + program: process.execPath, + args: '--run-tool-a', + icon: process.execPath, + iconIndex: 0, + description: 'Runs Tool A' + }, + { + type: 'task', + title: 'Tool B', + program: process.execPath, + args: '--run-tool-b', + icon: process.execPath, + iconIndex: 0, + description: 'Runs Tool B' + } + ] + }, + { type: 'frequent' }, + { // has no name and no type so `type` is assumed to be "tasks" + items: [ + { + type: 'task', + title: 'New Project', + program: process.execPath, + args: '--new-project', + description: 'Create a new project.' + }, + { type: 'separator' }, + { + type: 'task', + title: 'Recover Project', + program: process.execPath, + args: '--recover-project', + description: 'Recover Project' + } + ] + } +]) +``` + +### `app.makeSingleInstance(callback)` + +* `callback` Function + * `argv` String[] - An array of the second instance's command line arguments + * `workingDirectory` String - The second instance's working directory + +This method makes your application a Single Instance Application - instead of +allowing multiple instances of your app to run, this will ensure that only a +single instance of your app is running, and other instances signal this +instance and exit. + +`callback` will be called with `callback(argv, workingDirectory)` when a second +instance has been executed. `argv` is an Array of the second instance's command +line arguments, and `workingDirectory` is its current working directory. Usually +applications respond to this by making their primary window focused and +non-minimized. + +The `callback` is guaranteed to be executed after the `ready` event of `app` +gets emitted. + +This method returns `false` if your process is the primary instance of the +application and your app should continue loading. And returns `true` if your +process has sent its parameters to another instance, and you should immediately +quit. + +On macOS the system enforces single instance automatically when users try to open +a second instance of your app in Finder, and the `open-file` and `open-url` +events will be emitted for that. However when users start your app in command +line the system's single instance mechanism will be bypassed and you have to +use this method to ensure single instance. + +An example of activating the window of primary instance when a second instance +starts: + +```javascript +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 (shouldQuit) { + app.quit() +} + +// Create myWindow, load the rest of the app, etc... +app.on('ready', () => { +}) +``` + +### `app.releaseSingleInstance()` + +Releases all locks that were created by `makeSingleInstance`. This will allow +multiple instances of the application to once again run side by side. + +### `app.setUserActivity(type, userInfo[, webpageURL])` _macOS_ + +* `type` String - Uniquely identifies the activity. Maps to + [`NSUserActivity.activityType`][activity-type]. +* `userInfo` Object - App-specific state to store for use by another device. +* `webpageURL` String (optional) - The webpage to load in a browser if no suitable app is + installed on the resuming device. The scheme must be `http` or `https`. + +Creates an `NSUserActivity` and sets it as the current activity. The activity +is eligible for [Handoff][handoff] to another device afterward. + +### `app.getCurrentActivityType()` _macOS_ + +Returns `String` - The type of the currently running activity. + +### `app.setAppUserModelId(id)` _Windows_ + +* `id` String + +Changes the [Application User Model ID][app-user-model-id] to `id`. + +### `app.importCertificate(options, callback)` _LINUX_ + +* `options` Object + * `certificate` String - Path for the pkcs12 file. + * `password` String - Passphrase for the certificate. +* `callback` Function + * `result` Integer - Result of import. + +Imports the certificate in pkcs12 format into the platform certificate store. +`callback` is called with the `result` of import operation, a value of `0` +indicates success while any other value indicates failure according to chromium [net_error_list](https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h). + +### `app.disableHardwareAcceleration()` + +Disables hardware acceleration for current app. + +This method can only be called before app is ready. + +### `app.setBadgeCount(count)` _Linux_ _macOS_ + +* `count` Integer + +Returns `Boolean` - Whether the call succeeded. + +Sets the counter badge for current app. Setting the count to `0` will hide the +badge. + +On macOS it shows on the dock icon. On Linux it only works for Unity launcher, + +**Note:** Unity launcher requires the exsistence of a `.desktop` file to work, +for more information please read [Desktop Environment Integration][unity-requiremnt]. + +### `app.getBadgeCount()` _Linux_ _macOS_ + +Returns `Integer` - The current value displayed in the counter badge. + +### `app.isUnityRunning()` _Linux_ + +Returns `Boolean` - Whether the current desktop environment is Unity launcher. + +### `app.getLoginItemSettings()` _macOS_ _Windows_ + +Returns `Object`: + +* `openAtLogin` Boolean - `true` if the app is set to open at login. +* `openAsHidden` Boolean - `true` if the app is set to open as hidden at login. + This setting is only supported on macOS. +* `wasOpenedAtLogin` Boolean - `true` if the app was opened at login + automatically. This setting is only supported on macOS. +* `wasOpenedAsHidden` Boolean - `true` if the app was opened as a hidden login + item. This indicates that the app should not open any windows at startup. + This setting is only supported on macOS. +* `restoreState` Boolean - `true` if the app was opened as a login item that + should restore the state from the previous session. This indicates that the + app should restore the windows that were open the last time the app was + closed. This setting is only supported on macOS. + +**Note:** This API has no effect on +[MAS builds][mas-builds]. + +### `app.setLoginItemSettings(settings)` _macOS_ _Windows_ + +* `settings` Object + * `openAtLogin` Boolean (optional) - `true` to open the app at login, `false` to remove + the app as a login item. Defaults to `false`. + * `openAsHidden` Boolean (optional) - `true` to open the app as hidden. Defaults to + `false`. The user can edit this setting from the System Preferences so + `app.getLoginItemStatus().wasOpenedAsHidden` should be checked when the app + is opened to know the current value. This setting is only supported on + macOS. + +Set the app's login item settings. + +**Note:** This API has no effect on +[MAS builds][mas-builds]. + +### `app.isAccessibilitySupportEnabled()` _macOS_ _Windows_ + +Returns `Boolean` - `true` if Chrome's accessibility support is enabled, +`false` otherwise. This API will return `true` if the use of assistive +technologies, such as screen readers, has been detected. See +https://www.chromium.org/developers/design-documents/accessibility for more +details. + +### `app.setAboutPanelOptions(options)` _macOS_ + +* `options` Object + * `applicationName` String (optional) - The app's name. + * `applicationVersion` String (optional) - The app's version. + * `copyright` String (optional) - Copyright information. + * `credits` String (optional) - Credit information. + * `version` String (optional) - The app's build version number. + +Set the about panel options. This will override the values defined in the app's +`.plist` file. See the [Apple docs][about-panel-options] for more details. + +### `app.commandLine.appendSwitch(switch[, value])` + +* `switch` String - A command-line switch +* `value` String (optional) - A value for the given switch + +Append a switch (with optional `value`) to Chromium's command line. + +**Note:** This will not affect `process.argv`, and is mainly used by developers +to control some low-level Chromium behaviors. + +### `app.commandLine.appendArgument(value)` + +* `value` String - The argument to append to the command line + +Append an argument to Chromium's command line. The argument will be quoted +correctly. + +**Note:** This will not affect `process.argv`. + +### `app.dock.bounce([type])` _macOS_ + +* `type` String (optional) - Can be `critical` or `informational`. The default is + `informational` + +When `critical` is passed, the dock icon will bounce until either the +application becomes active or the request is canceled. + +When `informational` is passed, the dock icon will bounce for one second. +However, the request remains active until either the application becomes active +or the request is canceled. + +Returns `Integer` an ID representing the request. + +### `app.dock.cancelBounce(id)` _macOS_ + +* `id` Integer + +Cancel the bounce of `id`. + +### `app.dock.downloadFinished(filePath)` _macOS_ + +* `filePath` String + +Bounces the Downloads stack if the filePath is inside the Downloads folder. + +### `app.dock.setBadge(text)` _macOS_ + +* `text` String + +Sets the string to be displayed in the dock’s badging area. + +### `app.dock.getBadge()` _macOS_ + +Returns `String` - The badge string of the dock. + +### `app.dock.hide()` _macOS_ + +Hides the dock icon. + +### `app.dock.show()` _macOS_ + +Shows the dock icon. + +### `app.dock.isVisible()` _macOS_ + +Returns `Boolean` - Whether the dock icon is visible. +The `app.dock.show()` call is asynchronous so this method might not +return true immediately after that call. + +### `app.dock.setMenu(menu)` _macOS_ + +* `menu` [Menu](menu.md) + +Sets the application's [dock menu][dock-menu]. + +### `app.dock.setIcon(image)` _macOS_ + +* `image` ([NativeImage](native-image.md) | String) + +Sets the `image` associated with this dock icon. + +[dock-menu]:https://developer.apple.com/library/mac/documentation/Carbon/Conceptual/customizing_docktile/concepts/dockconcepts.html#//apple_ref/doc/uid/TP30000986-CH2-TPXREF103 +[tasks]:http://msdn.microsoft.com/en-us/library/windows/desktop/dd378460(v=vs.85).aspx#tasks +[app-user-model-id]: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx +[CFBundleURLTypes]: https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/TP40009249-102207-TPXREF115 +[LSCopyDefaultHandlerForURLScheme]: https://developer.apple.com/library/mac/documentation/Carbon/Reference/LaunchServicesReference/#//apple_ref/c/func/LSCopyDefaultHandlerForURLScheme +[handoff]: https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/Handoff/HandoffFundamentals/HandoffFundamentals.html +[activity-type]: https://developer.apple.com/library/ios/documentation/Foundation/Reference/NSUserActivity_Class/index.html#//apple_ref/occ/instp/NSUserActivity/activityType +[unity-requiremnt]: ../tutorial/desktop-environment-integration.md#unity-launcher-shortcuts-linux +[mas-builds]: ../tutorial/mac-app-store-submission-guide.md +[JumpListBeginListMSDN]: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378398(v=vs.85).aspx +[about-panel-options]: https://developer.apple.com/reference/appkit/nsapplication/1428479-orderfrontstandardaboutpanelwith?language=objc diff --git a/docs-translations/th-TH/api/auto-updater.md b/docs-translations/th-TH/api/auto-updater.md new file mode 100644 index 0000000000..9c743a0376 --- /dev/null +++ b/docs-translations/th-TH/api/auto-updater.md @@ -0,0 +1,139 @@ +# autoUpdater + +> Enable apps to automatically update themselves. + +Process: [Main](../glossary.md#main-process) + +The `autoUpdater` module provides an interface for the +[Squirrel](https://github.com/Squirrel) framework. + +You can quickly launch a multi-platform release server for distributing your +application by using one of these projects: + +- [nuts][nuts]: *A smart release server for your applications, using GitHub as a backend. Auto-updates with Squirrel (Mac & Windows)* +- [electron-release-server][electron-release-server]: *A fully featured, + self-hosted release server for electron applications, compatible with + auto-updater* +- [squirrel-updates-server][squirrel-updates-server]: *A simple node.js server + for Squirrel.Mac and Squirrel.Windows which uses GitHub releases* +- [squirrel-release-server][squirrel-release-server]: *A simple PHP application for Squirrel.Windows which reads updates from a folder. Supports delta updates.* + +## Platform notices + +Though `autoUpdater` provides a uniform API for different platforms, there are +still some subtle differences on each platform. + +### macOS + +On macOS, the `autoUpdater` module is built upon [Squirrel.Mac][squirrel-mac], +meaning you don't need any special setup to make it work. For server-side +requirements, you can read [Server Support][server-support]. Note that [App +Transport Security](https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW35) (ATS) applies to all requests made as part of the +update process. Apps that need to disable ATS can add the +`NSAllowsArbitraryLoads` key to their app's plist. + +**Note:** Your application must be signed for automatic updates on macOS. +This is a requirement of `Squirrel.Mac`. + +### Windows + +On Windows, you have to install your app into a user's machine before you can +use the `autoUpdater`, so it is recommended that you use the +[electron-winstaller][installer-lib], [electron-builder][electron-builder-lib] or the [grunt-electron-installer][installer] package to generate a Windows installer. + +When using [electron-winstaller][installer-lib] or [electron-builder][electron-builder-lib] make sure you do not try to update your app [the first time it runs](https://github.com/electron/windows-installer#handling-squirrel-events) (Also see [this issue for more info](https://github.com/electron/electron/issues/7155)). It's also recommended to use [electron-squirrel-startup](https://github.com/mongodb-js/electron-squirrel-startup) to get desktop shortcuts for your app. + +The installer generated with Squirrel will create a shortcut icon with an +[Application User Model ID][app-user-model-id] in the format of +`com.squirrel.PACKAGE_ID.YOUR_EXE_WITHOUT_DOT_EXE`, examples are +`com.squirrel.slack.Slack` and `com.squirrel.code.Code`. You have to use the +same ID for your app with `app.setAppUserModelId` API, otherwise Windows will +not be able to pin your app properly in task bar. + +The server-side setup is also different from macOS. You can read the documents of +[Squirrel.Windows][squirrel-windows] to get more details. + +### Linux + +There is no built-in support for auto-updater on Linux, so it is recommended to +use the distribution's package manager to update your app. + +## Events + +The `autoUpdater` object emits the following events: + +### Event: 'error' + +Returns: + +* `error` Error + +Emitted when there is an error while updating. + +### Event: 'checking-for-update' + +Emitted when checking if an update has started. + +### Event: 'update-available' + +Emitted when there is an available update. The update is downloaded +automatically. + +### Event: 'update-not-available' + +Emitted when there is no available update. + +### Event: 'update-downloaded' + +Returns: + +* `event` Event +* `releaseNotes` String +* `releaseName` String +* `releaseDate` Date +* `updateURL` String + +Emitted when an update has been downloaded. + +On Windows only `releaseName` is available. + +## Methods + +The `autoUpdater` object has the following methods: + +### `autoUpdater.setFeedURL(url[, requestHeaders])` + +* `url` String +* `requestHeaders` Object _macOS_ (optional) - HTTP request headers. + +Sets the `url` and initialize the auto updater. + +### `autoUpdater.getFeedURL()` + +Returns `String` - The current update feed URL. + +### `autoUpdater.checkForUpdates()` + +Asks the server whether there is an update. You must call `setFeedURL` before +using this API. + +### `autoUpdater.quitAndInstall()` + +Restarts the app and installs the update after it has been downloaded. It +should only be called after `update-downloaded` has been emitted. + +**Note:** `autoUpdater.quitAndInstall()` will close all application windows +first and only emit `before-quit` event on `app` after that. This is different +from the normal quit event sequence. + +[squirrel-mac]: https://github.com/Squirrel/Squirrel.Mac +[server-support]: https://github.com/Squirrel/Squirrel.Mac#server-support +[squirrel-windows]: https://github.com/Squirrel/Squirrel.Windows +[installer]: https://github.com/electron/grunt-electron-installer +[installer-lib]: https://github.com/electron/windows-installer +[electron-builder-lib]: https://github.com/electron-userland/electron-builder +[app-user-model-id]: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx +[electron-release-server]: https://github.com/ArekSredzki/electron-release-server +[squirrel-updates-server]: https://github.com/Aluxian/squirrel-updates-server +[nuts]: https://github.com/GitbookIO/nuts +[squirrel-release-server]: https://github.com/Arcath/squirrel-release-server diff --git a/docs-translations/th-TH/api/browser-window-proxy.md b/docs-translations/th-TH/api/browser-window-proxy.md new file mode 100644 index 0000000000..2bf5a15f74 --- /dev/null +++ b/docs-translations/th-TH/api/browser-window-proxy.md @@ -0,0 +1,53 @@ +## Class: BrowserWindowProxy + +> Manipulate the child browser window + +Process: [Renderer](../glossary.md#renderer-process) + +The `BrowserWindowProxy` object is returned from `window.open` and provides +limited functionality with the child window. + +### Instance Methods + +The `BrowserWindowProxy` object has the following instance methods: + +#### `win.blur()` + +Removes focus from the child window. + +#### `win.close()` + +Forcefully closes the child window without calling its unload event. + +#### `win.eval(code)` + +* `code` String + +Evaluates the code in the child window. + +#### `win.focus()` + +Focuses the child window (brings the window to front). + +#### `win.print()` + +Invokes the print dialog on the child window. + +#### `win.postMessage(message, targetOrigin)` + +* `message` String +* `targetOrigin` String + +Sends a message to the child window with the specified origin or `*` for no +origin preference. + +In addition to these methods, the child window implements `window.opener` object +with no properties and a single method. + +### Instance Properties + +The `BrowserWindowProxy` object has the following instance properties: + +#### `win.closed` + +A Boolean that is set to true after the child window gets closed. diff --git a/docs-translations/th-TH/api/browser-window.md b/docs-translations/th-TH/api/browser-window.md new file mode 100644 index 0000000000..306a365a58 --- /dev/null +++ b/docs-translations/th-TH/api/browser-window.md @@ -0,0 +1,1272 @@ +# BrowserWindow + +> Create and control browser windows. + +Process: [Main](../glossary.md#main-process) + +```javascript +// In the main process. +const {BrowserWindow} = require('electron') + +// Or use `remote` from the renderer process. +// const {BrowserWindow} = require('electron').remote + +let win = new BrowserWindow({width: 800, height: 600}) +win.on('closed', () => { + win = null +}) + +// Load a remote URL +win.loadURL('https://github.com') + +// Or load a local HTML file +win.loadURL(`file://${__dirname}/app/index.html`) +``` + +## Frameless window + +To create a window without chrome, or a transparent window in arbitrary shape, +you can use the [Frameless Window](frameless-window.md) API. + +## Showing window gracefully + +When loading a page in the window directly, users may see the page load incrementally, which is not a good experience for a native app. To make the window display +without visual flash, there are two solutions for different situations. + +### Using `ready-to-show` event + +While loading the page, the `ready-to-show` event will be emitted when renderer +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() +}) +``` + +This is event is usually emitted after the `did-finish-load` event, but for +pages with many remote resources, it may be emitted before the `did-finish-load` +event. + +### Setting `backgroundColor` + +For a complex app, the `ready-to-show` event could be emitted too late, making +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') +``` + +Note that even for apps that use `ready-to-show` event, it is still recommended +to set `backgroundColor` to make app feel more native. + +## Parent and child windows + +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. + +### Modal windows + +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', () => { + child.show() +}) +``` + +### Platform notices + +* On macOS modal windows will be displayed as sheets attached to the parent window. +* On macOS the child windows will keep the relative position to parent window + when parent window moves, while on Windows and Linux child windows will not + move. +* On Windows it is not supported to change parent window dynamically. +* On Linux the type of modal windows will be changed to `dialog`. +* On Linux many desktop environments do not support hiding a modal window. + +## Class: BrowserWindow + +> Create and control browser windows. + +Process: [Main](../glossary.md#main-process) + +`BrowserWindow` is an +[EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter). + +It creates a new `BrowserWindow` with native properties as set by the `options`. + +### `new BrowserWindow([options])` + +* `options` Object (optional) + * `width` Integer (optional) - Window's width in pixels. Default is `800`. + * `height` Integer (optional) - Window's height in pixels. Default is `600`. + * `x` Integer (optional) (**required** if y is used) - Window's left offset from screen. + Default is to center the window. + * `y` Integer (optional) (**required** if x is used) - Window's top offset from screen. + Default is to center the window. + * `useContentSize` Boolean (optional) - The `width` and `height` would be used as web + page's size, which means the actual window's size will include window + frame's size and be slightly larger. Default is `false`. + * `center` Boolean (optional) - Show window in the center of the screen. + * `minWidth` Integer (optional) - Window's minimum width. Default is `0`. + * `minHeight` Integer (optional) - Window's minimum height. Default is `0`. + * `maxWidth` Integer (optional) - Window's maximum width. Default is no limit. + * `maxHeight` Integer (optional) - Window's maximum height. Default is no limit. + * `resizable` Boolean (optional) - Whether window is resizable. Default is `true`. + * `movable` Boolean (optional) - Whether window is movable. This is not implemented + on Linux. Default is `true`. + * `minimizable` Boolean (optional) - Whether window is minimizable. This is not + implemented on Linux. Default is `true`. + * `maximizable` Boolean (optional) - Whether window is maximizable. This is not + implemented on Linux. Default is `true`. + * `closable` Boolean (optional) - Whether window is closable. This is not implemented + on Linux. Default is `true`. + * `focusable` Boolean (optional) - Whether the window can be focused. Default is + `true`. On Windows setting `focusable: false` also implies setting + `skipTaskbar: true`. On Linux setting `focusable: false` makes the window + stop interacting with wm, so the window will always stay on top in all + workspaces. + * `alwaysOnTop` Boolean (optional) - Whether the window should always stay on top of + other windows. Default is `false`. + * `fullscreen` Boolean (optional) - Whether the window should show in fullscreen. When + explicitly set to `false` the fullscreen button will be hidden or disabled + on macOS. Default is `false`. + * `fullscreenable` Boolean (optional) - Whether the window can be put into fullscreen + mode. On macOS, also whether the maximize/zoom button should toggle full + screen mode or maximize window. Default is `true`. + * `skipTaskbar` Boolean (optional) - Whether to show the window in taskbar. Default is + `false`. + * `kiosk` Boolean (optional) - The kiosk mode. Default is `false`. + * `title` String (optional) - Default window title. Default is `"Electron"`. + * `icon` ([NativeImage](native-image.md) | String) (optional) - The window icon. On Windows it is + recommended to use `ICO` icons to get best visual effects, you can also + leave it undefined so the executable's icon will be used. + * `show` Boolean (optional) - Whether window should be shown when created. Default is + `true`. + * `frame` Boolean (optional) - Specify `false` to create a + [Frameless Window](frameless-window.md). Default is `true`. + * `parent` BrowserWindow (optional) - Specify parent window. Default is `null`. + * `modal` Boolean (optional) - Whether this is a modal window. This only works when the + window is a child window. Default is `false`. + * `acceptFirstMouse` Boolean (optional) - Whether the web view accepts a single + mouse-down event that simultaneously activates the window. Default is + `false`. + * `disableAutoHideCursor` Boolean (optional) - Whether to hide cursor when typing. + Default is `false`. + * `autoHideMenuBar` Boolean (optional) - Auto hide the menu bar unless the `Alt` + key is pressed. Default is `false`. + * `enableLargerThanScreen` Boolean (optional) - Enable the window to be resized larger + than screen. Default is `false`. + * `backgroundColor` String (optional) - Window's background color as Hexadecimal value, + like `#66CD00` or `#FFF` or `#80FFFFFF` (alpha is supported). Default is + `#FFF` (white). + * `hasShadow` Boolean (optional) - Whether window should have a shadow. This is only + implemented on macOS. Default is `true`. + * `darkTheme` Boolean (optional) - Forces using dark theme for the window, only works on + some GTK+3 desktop environments. Default is `false`. + * `transparent` Boolean (optional) - Makes the window [transparent](frameless-window.md). + Default is `false`. + * `type` String (optional) - The type of window, default is normal window. See more about + this below. + * `titleBarStyle` String (optional) - The style of window title bar. Default is `default`. Possible values are: + * `default` - Results in the standard gray opaque Mac title + bar. + * `hidden` - Results in a hidden title bar and a full size content window, yet + the title bar still has the standard window controls ("traffic lights") in + the top left. + * `hidden-inset` - Results in a hidden title bar with an alternative look + where the traffic light buttons are slightly more inset from the window edge. + * `thickFrame` Boolean (optional) - Use `WS_THICKFRAME` style for frameless windows on + Windows, which adds standard window frame. Setting it to `false` will remove + window shadow and window animations. Default is `true`. + * `vibrancy` String (optional) - Add a type of vibrancy effect to the window, only on + macOS. Can be `appearance-based`, `light`, `dark`, `titlebar`, `selection`, + `menu`, `popover`, `sidebar`, `medium-light` or `ultra-dark`. + * `zoomToPageWidth` Boolean (optional) - Controls the behavior on macOS when + option-clicking the green stoplight button on the toolbar or by clicking the + Window > Zoom menu item. If `true`, the window will grow to the preferred + width of the web page when zoomed, `false` will cause it to zoom to the + width of the screen. This will also affect the behavior when calling + `maximize()` directly. Default is `false`. + * `webPreferences` Object (optional) - Settings of web page's features. + * `devTools` Boolean (optional) - Whether to enable DevTools. If it is set to `false`, can not use `BrowserWindow.webContents.openDevTools()` to open DevTools. Default is `true`. + * `nodeIntegration` Boolean (optional) - Whether node integration is enabled. Default + is `true`. + * `preload` String (optional) - Specifies a script that will be loaded before other + scripts run in the page. This script will always have access to node APIs + no matter whether node integration is turned on or off. The value should + be the absolute file path to the script. + When node integration is turned off, the preload script can reintroduce + Node global symbols back to the global scope. See example + [here](process.md#event-loaded). + * `session` [Session](session.md#class-session) (optional) - Sets the session used by the + page. Instead of passing the Session object directly, you can also choose to + use the `partition` option instead, which accepts a partition string. When + both `session` and `partition` are provided, `session` will be preferred. + Default is the default session. + * `partition` String (optional) - Sets the session used by the page according to the + session's partition string. If `partition` starts with `persist:`, the page + will use a persistent session available to all pages in the app with the + same `partition`. If there is no `persist:` prefix, the page will use an + in-memory session. By assigning the same `partition`, multiple pages can share + the same session. Default is the default session. + * `zoomFactor` Number (optional) - The default zoom factor of the page, `3.0` represents + `300%`. Default is `1.0`. + * `javascript` Boolean (optional) - Enables JavaScript support. Default is `true`. + * `webSecurity` Boolean (optional) - When `false`, it will disable the + same-origin policy (usually using testing websites by people), and set + `allowDisplayingInsecureContent` and `allowRunningInsecureContent` to + `true` if these two options are not set by user. Default is `true`. + * `allowDisplayingInsecureContent` Boolean (optional) - Allow an https page to display + content like images from http URLs. Default is `false`. + * `allowRunningInsecureContent` Boolean (optional) - Allow an https page to run + JavaScript, CSS or plugins from http URLs. Default is `false`. + * `images` Boolean (optional) - Enables image support. Default is `true`. + * `textAreasAreResizable` Boolean (optional) - Make TextArea elements resizable. Default + is `true`. + * `webgl` Boolean (optional) - Enables WebGL support. Default is `true`. + * `webaudio` Boolean (optional) - Enables WebAudio support. Default is `true`. + * `plugins` Boolean (optional) - Whether plugins should be enabled. Default is `false`. + * `experimentalFeatures` Boolean (optional) - Enables Chromium's experimental features. + Default is `false`. + * `experimentalCanvasFeatures` Boolean (optional) - Enables Chromium's experimental + canvas features. Default is `false`. + * `scrollBounce` Boolean (optional) - Enables scroll bounce (rubber banding) effect on + macOS. Default is `false`. + * `blinkFeatures` String (optional) - A list of feature strings separated by `,`, like + `CSSVariables,KeyboardEventKey` to enable. The full list of supported feature + strings can be found in the [RuntimeEnabledFeatures.in][blink-feature-string] + file. + * `disableBlinkFeatures` String (optional) - A list of feature strings separated by `,`, + like `CSSVariables,KeyboardEventKey` to disable. The full list of supported + feature strings can be found in the + [RuntimeEnabledFeatures.in][blink-feature-string] file. + * `defaultFontFamily` Object (optional) - Sets the default font for the font-family. + * `standard` String (optional) - Defaults to `Times New Roman`. + * `serif` String (optional) - Defaults to `Times New Roman`. + * `sansSerif` String (optional) - Defaults to `Arial`. + * `monospace` String (optional) - Defaults to `Courier New`. + * `cursive` String (optional) - Defaults to `Script`. + * `fantasy` String (optional) - Defaults to `Impact`. + * `defaultFontSize` Integer (optional) - Defaults to `16`. + * `defaultMonospaceFontSize` Integer (optional) - Defaults to `13`. + * `minimumFontSize` Integer (optional) - Defaults to `0`. + * `defaultEncoding` String (optional) - Defaults to `ISO-8859-1`. + * `backgroundThrottling` Boolean (optional) - Whether to throttle animations and timers + when the page becomes background. Defaults to `true`. + * `offscreen` Boolean (optional) - Whether to enable offscreen rendering for the browser + window. Defaults to `false`. See the + [offscreen rendering tutorial](../tutorial/offscreen-rendering.md) for + more details. + * `sandbox` Boolean (optional) - Whether to enable Chromium OS-level sandbox. + * `contextIsolation` Boolean (optional) - Whether to run Electron APIs and + the specified `preload` script in a separate JavaScript context. Defaults + to `false`. The context that the `preload` script runs in will still + have full access to the `document` and `window` globals but it will use + its own set of JavaScript builtins (`Array`, `Object`, `JSON`, etc.) + and will be isolated from any changes made to the global environment + by the loaded page. The Electron API will only be available in the + `preload` script and not the loaded page. This option should be used when + loading potentially untrusted remote content to ensure the loaded content + cannot tamper with the `preload` script and any Electron APIs being used. + This option uses the same technique used by [Chrome Content Scripts][chrome-content-scripts]. + You can access this context in the dev tools by selecting the + 'Electron Isolated Context' entry in the combo box at the top of the + Console tab. **Note:** This option is currently experimental and may + change or be removed in future Electron releases. + +When setting minimum or maximum window size with `minWidth`/`maxWidth`/ +`minHeight`/`maxHeight`, it only constrains the users. It won't prevent you from +passing a size that does not follow size constraints to `setBounds`/`setSize` or +to the constructor of `BrowserWindow`. + +The possible values and behaviors of the `type` option are platform dependent. +Possible values are: + +* On Linux, possible types are `desktop`, `dock`, `toolbar`, `splash`, + `notification`. +* On macOS, possible types are `desktop`, `textured`. + * The `textured` type adds metal gradient appearance + (`NSTexturedBackgroundWindowMask`). + * The `desktop` type places the window at the desktop background window level + (`kCGDesktopWindowLevel - 1`). Note that desktop window will not receive + focus, keyboard or mouse events, but you can use `globalShortcut` to receive + input sparingly. +* On Windows, possible type is `toolbar`. + +### Instance Events + +Objects created with `new BrowserWindow` emit the following events: + +**Note:** Some events are only available on specific operating systems and are +labeled as such. + +#### Event: 'page-title-updated' + +Returns: + +* `event` Event +* `title` String + +Emitted when the document changed its title, calling `event.preventDefault()` +will prevent the native window's title from changing. + +#### Event: 'close' + +Returns: + +* `event` Event + +Emitted when the window is going to be closed. It's emitted before the +`beforeunload` and `unload` event of the DOM. Calling `event.preventDefault()` +will cancel the close. + +Usually you would want to use the `beforeunload` handler to decide whether the +window should be closed, which will also be called when the window is +reloaded. In Electron, returning any value other than `undefined` would cancel the +close. For example: + +```javascript +window.onbeforeunload = (e) => { + 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 +} +``` + +#### Event: 'closed' + +Emitted when the window is closed. After you have received this event you should +remove the reference to the window and avoid using it any more. + +#### Event: 'unresponsive' + +Emitted when the web page becomes unresponsive. + +#### Event: 'responsive' + +Emitted when the unresponsive web page becomes responsive again. + +#### Event: 'blur' + +Emitted when the window loses focus. + +#### Event: 'focus' + +Emitted when the window gains focus. + +#### Event: 'show' + +Emitted when the window is shown. + +#### Event: 'hide' + +Emitted when the window is hidden. + +#### Event: 'ready-to-show' + +Emitted when the web page has been rendered and window can be displayed without +a visual flash. + +#### Event: 'maximize' + +Emitted when window is maximized. + +#### Event: 'unmaximize' + +Emitted when the window exits from a maximized state. + +#### Event: 'minimize' + +Emitted when the window is minimized. + +#### Event: 'restore' + +Emitted when the window is restored from a minimized state. + +#### Event: 'resize' + +Emitted when the window is being resized. + +#### Event: 'move' + +Emitted when the window is being moved to a new position. + +__Note__: On macOS this event is just an alias of `moved`. + +#### Event: 'moved' _macOS_ + +Emitted once when the window is moved to a new position. + +#### Event: 'enter-full-screen' + +Emitted when the window enters a full-screen state. + +#### Event: 'leave-full-screen' + +Emitted when the window leaves a full-screen state. + +#### Event: 'enter-html-full-screen' + +Emitted when the window enters a full-screen state triggered by HTML API. + +#### Event: 'leave-html-full-screen' + +Emitted when the window leaves a full-screen state triggered by HTML API. + +#### Event: 'app-command' _Windows_ + +Returns: + +* `event` Event +* `command` String + +Emitted when an [App Command](https://msdn.microsoft.com/en-us/library/windows/desktop/ms646275(v=vs.85).aspx) +is invoked. These are typically related to keyboard media keys or browser +commands, as well as the "Back" button built into some mice on Windows. + +Commands are lowercased, underscores are replaced with hyphens, and the +`APPCOMMAND_` prefix is stripped off. +e.g. `APPCOMMAND_BROWSER_BACKWARD` is emitted as `browser-backward`. + +```javascript +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' && win.webContents.canGoBack()) { + win.webContents.goBack() + } +}) +``` + +#### Event: 'scroll-touch-begin' _macOS_ + +Emitted when scroll wheel event phase has begun. + +#### Event: 'scroll-touch-end' _macOS_ + +Emitted when scroll wheel event phase has ended. + +#### Event: 'scroll-touch-edge' _macOS_ + +Emitted when scroll wheel event phase filed upon reaching the edge of element. + +#### Event: 'swipe' _macOS_ + +Returns: + +* `event` Event +* `direction` String + +Emitted on 3-finger swipe. Possible directions are `up`, `right`, `down`, `left`. + +### Static Methods + +The `BrowserWindow` class has the following static methods: + +#### `BrowserWindow.getAllWindows()` + +Returns `BrowserWindow[]` - An array of all opened browser windows. + +#### `BrowserWindow.getFocusedWindow()` + +Returns `BrowserWindow` - The window that is focused in this application, otherwise returns `null`. + +#### `BrowserWindow.fromWebContents(webContents)` + +* `webContents` [WebContents](web-contents.md) + +Returns `BrowserWindow` - The window that owns the given `webContents`. + +#### `BrowserWindow.fromId(id)` + +* `id` Integer + +Returns `BrowserWindow` - The window with the given `id`. + +#### `BrowserWindow.addDevToolsExtension(path)` + +* `path` String + +Adds DevTools extension located at `path`, and returns extension's name. + +The extension will be remembered so you only need to call this API once, this +API is not for programming use. If you try to add an extension that has already +been loaded, this method will not return and instead log a warning to the +console. + +The method will also not return if the extension's manifest is missing or incomplete. + +**Note:** This API cannot be called before the `ready` event of the `app` module +is emitted. + +#### `BrowserWindow.removeDevToolsExtension(name)` + +* `name` String + +Remove a DevTools extension by name. + +**Note:** This API cannot be called before the `ready` event of the `app` module +is emitted. + +#### `BrowserWindow.getDevToolsExtensions()` + +Returns `Object` - The keys are the extension names and each value is +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 +is emitted. + +### Instance Properties + +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}) +win.loadURL('https://github.com') +``` + +#### `win.webContents` + +A `WebContents` object this window owns. All web page related events and +operations will be done via it. + +See the [`webContents` documentation](web-contents.md) for its methods and +events. + +#### `win.id` + +A `Integer` representing the unique ID of the window. + +### Instance Methods + +Objects created with `new BrowserWindow` have the following instance methods: + +**Note:** Some methods are only available on specific operating systems and are +labeled as such. + +#### `win.destroy()` + +Force closing the window, the `unload` and `beforeunload` event won't be emitted +for the web page, and `close` event will also not be emitted +for this window, but it guarantees the `closed` event will be emitted. + +#### `win.close()` + +Try to close the window. This has the same effect as a user manually clicking +the close button of the window. The web page may cancel the close though. See +the [close event](#event-close). + +#### `win.focus()` + +Focuses on the window. + +#### `win.blur()` + +Removes focus from the window. + +#### `win.isFocused()` + +Returns `Boolean` - Whether the window is focused. + +#### `win.isDestroyed()` + +Returns `Boolean` - Whether the window is destroyed. + +#### `win.show()` + +Shows and gives focus to the window. + +#### `win.showInactive()` + +Shows the window but doesn't focus on it. + +#### `win.hide()` + +Hides the window. + +#### `win.isVisible()` + +Returns `Boolean` - Whether the window is visible to the user. + +#### `win.isModal()` + +Returns `Boolean` - Whether current window is a modal window. + +#### `win.maximize()` + +Maximizes the window. + +#### `win.unmaximize()` + +Unmaximizes the window. + +#### `win.isMaximized()` + +Returns `Boolean` - Whether the window is maximized. + +#### `win.minimize()` + +Minimizes the window. On some platforms the minimized window will be shown in +the Dock. + +#### `win.restore()` + +Restores the window from minimized state to its previous state. + +#### `win.isMinimized()` + +Returns `Boolean` - Whether the window is minimized. + +#### `win.setFullScreen(flag)` + +* `flag` Boolean + +Sets whether the window should be in fullscreen mode. + +#### `win.isFullScreen()` + +Returns `Boolean` - Whether the window is in fullscreen mode. + +#### `win.setAspectRatio(aspectRatio[, extraSize])` _macOS_ + +* `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. + * `width` Integer + * `height` Integer + +This will make a window maintain an aspect ratio. The extra size allows a +developer to have space, specified in pixels, not included within the aspect +ratio calculations. This API already takes into account the difference between a +window's size and its content size. + +Consider a normal window with an HD video player and associated controls. +Perhaps there are 15 pixels of controls on the left edge, 25 pixels of controls +on the right edge and 50 pixels of controls below the player. In order to +maintain a 16:9 aspect ratio (standard aspect ratio for HD @1920x1080) within +the player itself we would call this function with arguments of 16/9 and +[ 40, 50 ]. The second argument doesn't care where the extra width and height +are within the content view--only that they exist. Just sum any extra width and +height areas you have within the overall content view. + +#### `win.previewFile(path[, displayName])` _macOS_ + +* `path` String - The absolute path to the file to preview with QuickLook. This + is important as Quick Look uses the file name and file extension on the path + to determine the content type of the file to open. +* `displayName` String (Optional) - The name of the file to display on the + Quick Look modal view. This is purely visual and does not affect the content + type of the file. Defaults to `path`. + +Uses [Quick Look][quick-look] to preview a file at a given path. + +#### `win.closeFilePreview()` _macOS_ + +Closes the currently open [Quick Look][quick-look] panel. + +#### `win.setBounds(bounds[, animate])` + +* `bounds` [Rectangle](structures/rectangle.md) +* `animate` Boolean (optional) _macOS_ + +Resizes and moves the window to the supplied bounds + +#### `win.getBounds()` + +Returns [`Rectangle`](structures/rectangle.md) + +#### `win.setContentBounds(bounds[, animate])` + +* `bounds` [Rectangle](structures/rectangle.md) +* `animate` Boolean (optional) _macOS_ + +Resizes and moves the window's client area (e.g. the web page) to +the supplied bounds. + +#### `win.getContentBounds()` + +Returns [`Rectangle`](structures/rectangle.md) + +#### `win.setSize(width, height[, animate])` + +* `width` Integer +* `height` Integer +* `animate` Boolean (optional) _macOS_ + +Resizes the window to `width` and `height`. + +#### `win.getSize()` + +Returns `Integer[]` - Contains the window's width and height. + +#### `win.setContentSize(width, height[, animate])` + +* `width` Integer +* `height` Integer +* `animate` Boolean (optional) _macOS_ + +Resizes the window's client area (e.g. the web page) to `width` and `height`. + +#### `win.getContentSize()` + +Returns `Integer[]` - Contains the window's client area's width and height. + +#### `win.setMinimumSize(width, height)` + +* `width` Integer +* `height` Integer + +Sets the minimum size of window to `width` and `height`. + +#### `win.getMinimumSize()` + +Returns `Integer[]` - Contains the window's minimum width and height. + +#### `win.setMaximumSize(width, height)` + +* `width` Integer +* `height` Integer + +Sets the maximum size of window to `width` and `height`. + +#### `win.getMaximumSize()` + +Returns `Integer[]` - Contains the window's maximum width and height. + +#### `win.setResizable(resizable)` + +* `resizable` Boolean + +Sets whether the window can be manually resized by user. + +#### `win.isResizable()` + +Returns `Boolean` - Whether the window can be manually resized by user. + +#### `win.setMovable(movable)` _macOS_ _Windows_ + +* `movable` Boolean + +Sets whether the window can be moved by user. On Linux does nothing. + +#### `win.isMovable()` _macOS_ _Windows_ + +Returns `Boolean` - Whether the window can be moved by user. + +On Linux always returns `true`. + +#### `win.setMinimizable(minimizable)` _macOS_ _Windows_ + +* `minimizable` Boolean + +Sets whether the window can be manually minimized by user. On Linux does +nothing. + +#### `win.isMinimizable()` _macOS_ _Windows_ + +Returns `Boolean` - Whether the window can be manually minimized by user + +On Linux always returns `true`. + +#### `win.setMaximizable(maximizable)` _macOS_ _Windows_ + +* `maximizable` Boolean + +Sets whether the window can be manually maximized by user. On Linux does +nothing. + +#### `win.isMaximizable()` _macOS_ _Windows_ + +Returns `Boolean` - Whether the window can be manually maximized by user. + +On Linux always returns `true`. + +#### `win.setFullScreenable(fullscreenable)` + +* `fullscreenable` Boolean + +Sets whether the maximize/zoom window button toggles fullscreen mode or +maximizes the window. + +#### `win.isFullScreenable()` + +Returns `Boolean` - Whether the maximize/zoom window button toggles fullscreen mode or +maximizes the window. + +#### `win.setClosable(closable)` _macOS_ _Windows_ + +* `closable` Boolean + +Sets whether the window can be manually closed by user. On Linux does nothing. + +#### `win.isClosable()` _macOS_ _Windows_ + +Returns `Boolean` - Whether the window can be manually closed by user. + +On Linux always returns `true`. + +#### `win.setAlwaysOnTop(flag[, level])` + +* `flag` Boolean +* `level` String (optional) _macOS_ - Values include `normal`, `floating`, + `torn-off-menu`, `modal-panel`, `main-menu`, `status`, `pop-up-menu`, + `screen-saver`, and ~~`dock`~~ (Deprecated). The default is `floating`. See the + [macOS docs][window-levels] for more details. + +Sets whether the window should show always on top of other windows. After +setting this, the window is still a normal window, not a toolbox window which +can not be focused on. + +#### `win.isAlwaysOnTop()` + +Returns `Boolean` - Whether the window is always on top of other windows. + +#### `win.center()` + +Moves window to the center of the screen. + +#### `win.setPosition(x, y[, animate])` + +* `x` Integer +* `y` Integer +* `animate` Boolean (optional) _macOS_ + +Moves window to `x` and `y`. + +#### `win.getPosition()` + +Returns `Integer[]` - Contains the window's current position. + +#### `win.setTitle(title)` + +* `title` String + +Changes the title of native window to `title`. + +#### `win.getTitle()` + +Returns `String` - The title of the native window. + +**Note:** The title of web page can be different from the title of the native +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 +const {BrowserWindow} = require('electron') +let win = new BrowserWindow() + +let toolbarRect = document.getElementById('toolbar').getBoundingClientRect() +win.setSheetOffset(toolbarRect.height) +``` + +#### `win.flashFrame(flag)` + +* `flag` Boolean + +Starts or stops flashing the window to attract user's attention. + +#### `win.setSkipTaskbar(skip)` + +* `skip` Boolean + +Makes the window not show in the taskbar. + +#### `win.setKiosk(flag)` + +* `flag` Boolean + +Enters or leaves the kiosk mode. + +#### `win.isKiosk()` + +Returns `Boolean` - Whether the window is in kiosk mode. + +#### `win.getNativeWindowHandle()` + +Returns `Buffer` - The platform-specific handle of the window. + +The native type of the handle is `HWND` on Windows, `NSView*` on macOS, and +`Window` (`unsigned long`) on Linux. + +#### `win.hookWindowMessage(message, callback)` _Windows_ + +* `message` Integer +* `callback` Function + +Hooks a windows message. The `callback` is called when +the message is received in the WndProc. + +#### `win.isWindowMessageHooked(message)` _Windows_ + +* `message` Integer + +Returns `Boolean` - `true` or `false` depending on whether the message is hooked. + +#### `win.unhookWindowMessage(message)` _Windows_ + +* `message` Integer + +Unhook the window message. + +#### `win.unhookAllWindowMessages()` _Windows_ + +Unhooks all of the window messages. + +#### `win.setRepresentedFilename(filename)` _macOS_ + +* `filename` String + +Sets the pathname of the file the window represents, and the icon of the file +will show in window's title bar. + +#### `win.getRepresentedFilename()` _macOS_ + +Returns `String` - The pathname of the file the window represents. + +#### `win.setDocumentEdited(edited)` _macOS_ + +* `edited` Boolean + +Specifies whether the window’s document has been edited, and the icon in title +bar will become gray when set to `true`. + +#### `win.isDocumentEdited()` _macOS_ + +Returns `Boolean` - Whether the window's document has been edited. + +#### `win.focusOnWebView()` + +#### `win.blurWebView()` + +#### `win.capturePage([rect, ]callback)` + +* `rect` [Rectangle](structures/rectangle.md) (optional) - The bounds to capture +* `callback` Function + * `image` [NativeImage](native-image.md) + +Same as `webContents.capturePage([rect, ]callback)`. + +#### `win.loadURL(url[, options])` + +* `url` String +* `options` Object (optional) + * `httpReferrer` String (optional) - A HTTP Referrer url. + * `userAgent` String (optional) - A user agent originating the request. + * `extraHeaders` String (optional) - Extra headers separated by "\n" + * `postData` ([UploadRawData](structures/upload-raw-data.md) | [UploadFile](structures/upload-file.md) | [UploadFileSystem](structures/upload-file-system.md) | [UploadBlob](structures/upload-blob.md))[] - (optional) + +Same as `webContents.loadURL(url[, options])`. + +The `url` can be a remote address (e.g. `http://`) or a path to a local +HTML file using the `file://` protocol. + +To ensure that file URLs are properly formatted, it is recommended to use +Node's [`url.format`](https://nodejs.org/api/url.html#url_url_format_urlobject) +method: + +```javascript +let url = require('url').format({ + protocol: 'file', + slashes: true, + pathname: require('path').join(__dirname, 'index.html') +}) + +win.loadURL(url) +``` + +You can load a URL using a `POST` request with URL-encoded data by doing +the following: + +```javascript +win.loadURL('http://localhost:8000/post', { + postData: [{ + type: 'rawData', + bytes: Buffer.from('hello=world') + }], + extraHeaders: 'Content-Type: application/x-www-form-urlencoded' +}) +``` + +#### `win.reload()` + +Same as `webContents.reload`. + +#### `win.setMenu(menu)` _Linux_ _Windows_ + +* `menu` Menu + +Sets the `menu` as the window's menu bar, setting it to `null` will remove the +menu bar. + +#### `win.setProgressBar(progress[, options])` + +* `progress` Double +* `options` Object (optional) + * `mode` String _Windows_ - Mode for the progress bar. Can be `none`, `normal`, `indeterminate`, `error`, or `paused`. + +Sets progress value in progress bar. Valid range is [0, 1.0]. + +Remove progress bar when progress < 0; +Change to indeterminate mode when progress > 1. + +On Linux platform, only supports Unity desktop environment, you need to specify +the `*.desktop` file name to `desktopName` field in `package.json`. By default, +it will assume `app.getName().desktop`. + +On Windows, a mode can be passed. Accepted values are `none`, `normal`, +`indeterminate`, `error`, and `paused`. If you call `setProgressBar` without a +mode set (but with a value within the valid range), `normal` will be assumed. + +#### `win.setOverlayIcon(overlay, description)` _Windows_ + +* `overlay` [NativeImage](native-image.md) - the icon to display on the bottom +right corner of the taskbar icon. If this parameter is `null`, the overlay is +cleared +* `description` String - a description that will be provided to Accessibility +screen readers + +Sets a 16 x 16 pixel overlay onto the current taskbar icon, usually used to +convey some sort of application status or to passively notify the user. + +#### `win.setHasShadow(hasShadow)` _macOS_ + +* `hasShadow` Boolean + +Sets whether the window should have a shadow. On Windows and Linux does +nothing. + +#### `win.hasShadow()` _macOS_ + +Returns `Boolean` - Whether the window has a shadow. + +On Windows and Linux always returns +`true`. + +#### `win.setThumbarButtons(buttons)` _Windows_ + +* `buttons` [ThumbarButton[]](structures/thumbar-button.md) + +Returns `Boolean` - Whether the buttons were added successfully + +Add a thumbnail toolbar with a specified set of buttons to the thumbnail image +of a window in a taskbar button layout. Returns a `Boolean` object indicates +whether the thumbnail has been added successfully. + +The number of buttons in thumbnail toolbar should be no greater than 7 due to +the limited room. Once you setup the thumbnail toolbar, the toolbar cannot be +removed due to the platform's limitation. But you can call the API with an empty +array to clean the buttons. + +The `buttons` is an array of `Button` objects: + +* `Button` Object + * `icon` [NativeImage](native-image.md) - The icon showing in thumbnail + toolbar. + * `click` Function + * `tooltip` String (optional) - The text of the button's tooltip. + * `flags` String[] (optional) - Control specific states and behaviors of the + button. By default, it is `['enabled']`. + +The `flags` is an array that can include following `String`s: + +* `enabled` - The button is active and available to the user. +* `disabled` - The button is disabled. It is present, but has a visual state + indicating it will not respond to user action. +* `dismissonclick` - When the button is clicked, the thumbnail window closes + immediately. +* `nobackground` - Do not draw a button border, use only the image. +* `hidden` - The button is not shown to the user. +* `noninteractive` - The button is enabled but not interactive; no pressed + button state is drawn. This value is intended for instances where the button + is used in a notification. + +#### `win.setThumbnailClip(region)` _Windows_ + +* `region` [Rectangle](structures/rectangle.md) - Region of the window + +Sets the region of the window to show as the thumbnail image displayed when +hovering over the window in the taskbar. You can reset the thumbnail to be +the entire window by specifying an empty region: +`{x: 0, y: 0, width: 0, height: 0}`. + +#### `win.setThumbnailToolTip(toolTip)` _Windows_ + +* `toolTip` String + +Sets the toolTip that is displayed when hovering over the window thumbnail +in the taskbar. + +#### `win.setAppDetails(options)` _Windows_ + +* `options` Object + * `appId` String (optional) - Window's [App User Model ID](https://msdn.microsoft.com/en-us/library/windows/desktop/dd391569(v=vs.85).aspx). + It has to be set, otherwise the other options will have no effect. + * `appIconPath` String (optional) - Window's [Relaunch Icon](https://msdn.microsoft.com/en-us/library/windows/desktop/dd391573(v=vs.85).aspx). + * `appIconIndex` Integer (optional) - Index of the icon in `appIconPath`. + Ignored when `appIconPath` is not set. Default is `0`. + * `relaunchCommand` String (optional) - Window's [Relaunch Command](https://msdn.microsoft.com/en-us/library/windows/desktop/dd391571(v=vs.85).aspx). + * `relaunchDisplayName` String (optional) - Window's [Relaunch Display Name](https://msdn.microsoft.com/en-us/library/windows/desktop/dd391572(v=vs.85).aspx). + +Sets the properties for the window's taskbar button. + +**Note:** `relaunchCommand` and `relaunchDisplayName` must always be set +together. If one of those properties is not set, then neither will be used. + +#### `win.showDefinitionForSelection()` _macOS_ + +Same as `webContents.showDefinitionForSelection()`. + +#### `win.setIcon(icon)` _Windows_ _Linux_ + +* `icon` [NativeImage](native-image.md) + +Changes window icon. + +#### `win.setAutoHideMenuBar(hide)` + +* `hide` Boolean + +Sets whether the window menu bar should hide itself automatically. Once set the +menu bar will only show when users press the single `Alt` key. + +If the menu bar is already visible, calling `setAutoHideMenuBar(true)` won't +hide it immediately. + +#### `win.isMenuBarAutoHide()` + +Returns `Boolean` - Whether menu bar automatically hides itself. + +#### `win.setMenuBarVisibility(visible)` _Windows_ _Linux_ + +* `visible` Boolean + +Sets whether the menu bar should be visible. If the menu bar is auto-hide, users +can still bring up the menu bar by pressing the single `Alt` key. + +#### `win.isMenuBarVisible()` + +Returns `Boolean` - Whether the menu bar is visible. + +#### `win.setVisibleOnAllWorkspaces(visible)` + +* `visible` Boolean + +Sets whether the window should be visible on all workspaces. + +**Note:** This API does nothing on Windows. + +#### `win.isVisibleOnAllWorkspaces()` + +Returns `Boolean` - Whether the window is visible on all workspaces. + +**Note:** This API always returns false on Windows. + +#### `win.setIgnoreMouseEvents(ignore)` + +* `ignore` Boolean + +Makes the window ignore all mouse events. + +All mouse events happened in this window will be passed to the window below +this window, but if this window has focus, it will still receive keyboard +events. + +#### `win.setContentProtection(enable)` _macOS_ _Windows_ + +* `enable` Boolean + +Prevents the window contents from being captured by other apps. + +On macOS it sets the NSWindow's sharingType to NSWindowSharingNone. +On Windows it calls SetWindowDisplayAffinity with `WDA_MONITOR`. + +#### `win.setFocusable(focusable)` _Windows_ + +* `focusable` Boolean + +Changes whether the window can be focused. + +#### `win.setParentWindow(parent)` _Linux_ _macOS_ + +* `parent` BrowserWindow + +Sets `parent` as current window's parent window, passing `null` will turn +current window into a top-level window. + +#### `win.getParentWindow()` + +Returns `BrowserWindow` - The parent window. + +#### `win.getChildWindows()` + +Returns `BrowserWindow[]` - All child windows. + +#### `win.setAutoHideCursor(autoHide)` _macOS_ + +* `autoHide` Boolean + +Controls whether to hide cursor when typing. + +#### `win.setVibrancy(type)` _macOS_ + +* `type` String - Can be `appearance-based`, `light`, `dark`, `titlebar`, + `selection`, `menu`, `popover`, `sidebar`, `medium-light` or `ultra-dark`. See + the [macOS documentation][vibrancy-docs] for more details. + +Adds a vibrancy effect to the browser window. Passing `null` or an empty string +will remove the vibrancy effect on the window. + +[blink-feature-string]: https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.in +[quick-look]: https://en.wikipedia.org/wiki/Quick_Look +[vibrancy-docs]: https://developer.apple.com/reference/appkit/nsvisualeffectview?language=objc +[window-levels]: https://developer.apple.com/reference/appkit/nswindow/1664726-window_levels +[chrome-content-scripts]: https://developer.chrome.com/extensions/content_scripts#execution-environment diff --git a/docs-translations/th-TH/api/chrome-command-line-switches.md b/docs-translations/th-TH/api/chrome-command-line-switches.md new file mode 100644 index 0000000000..7920fe5e20 --- /dev/null +++ b/docs-translations/th-TH/api/chrome-command-line-switches.md @@ -0,0 +1,189 @@ +# Supported Chrome Command Line Switches + +> Command line switches supported by Electron. + +You can use [app.commandLine.appendSwitch][append-switch] to append them in +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') + +app.on('ready', () => { + // Your code here +}) +``` + +## --ignore-connections-limit=`domains` + +Ignore the connections limit for `domains` list separated by `,`. + +## --disable-http-cache + +Disables the disk cache for HTTP requests. + +## --disable-http2 + +Disable HTTP/2 and SPDY/3.1 protocols. + +## --debug=`port` and --debug-brk=`port` + +Debug-related flags, see the [Debugging the Main Process][debugging-main-process] guide for details. + +## --remote-debugging-port=`port` + +Enables remote debugging over HTTP on the specified `port`. + +## --js-flags=`flags` + +Specifies the flags passed to the Node JS engine. It has to be passed when starting +Electron if you want to enable the `flags` in the main process. + +```bash +$ electron --js-flags="--harmony_proxies --harmony_collections" your-app +``` + +See the [Node documentation][node-cli] or run `node --help` in your terminal for a list of available flags. Additionally, run `node --v8-options` to see a list of flags that specifically refer to Node's V8 JavaScript engine. + +## --proxy-server=`address:port` + +Use a specified proxy server, which overrides the system setting. This switch +only affects requests with HTTP protocol, including HTTPS and WebSocket +requests. It is also noteworthy that not all proxy servers support HTTPS and +WebSocket requests. + +## --proxy-bypass-list=`hosts` + +Instructs Electron to bypass the proxy server for the given semi-colon-separated +list of hosts. This flag has an effect only if used in tandem with +`--proxy-server`. + +For example: + +```javascript +const {app} = require('electron') +app.commandLine.appendSwitch('proxy-bypass-list', ';*.google.com;*foo.com;1.2.3.4:5678') +``` + +Will use the proxy server for all hosts except for local addresses (`localhost`, +`127.0.0.1` etc.), `google.com` subdomains, hosts that contain the suffix +`foo.com` and anything at `1.2.3.4:5678`. + +## --proxy-pac-url=`url` + +Uses the PAC script at the specified `url`. + +## --no-proxy-server + +Don't use a proxy server and always make direct connections. Overrides any other +proxy server flags that are passed. + +## --host-rules=`rules` + +A comma-separated list of `rules` that control how hostnames are mapped. + +For example: + +* `MAP * 127.0.0.1` Forces all hostnames to be mapped to 127.0.0.1 +* `MAP *.google.com proxy` Forces all google.com subdomains to be resolved to + "proxy". +* `MAP test.com [::1]:77` Forces "test.com" to resolve to IPv6 loopback. Will + also force the port of the resulting socket address to be 77. +* `MAP * baz, EXCLUDE www.google.com` Remaps everything to "baz", except for + "www.google.com". + +These mappings apply to the endpoint host in a net request (the TCP connect +and host resolver in a direct connection, and the `CONNECT` in an HTTP proxy +connection, and the endpoint host in a `SOCKS` proxy connection). + +## --host-resolver-rules=`rules` + +Like `--host-rules` but these `rules` only apply to the host resolver. + +## --auth-server-whitelist=`url` + +A comma-separated list of servers for which integrated authentication is enabled. + +For example: + +``` +--auth-server-whitelist='*example.com, *foobar.com, *baz' +``` + +then any `url` ending with `example.com`, `foobar.com`, `baz` will be considered +for integrated authentication. Without `*` prefix the url has to match exactly. + +## --auth-negotiate-delegate-whitelist=`url` + +A comma-separated list of servers for which delegation of user credentials is required. +Without `*` prefix the url has to match exactly. + +## --ignore-certificate-errors + +Ignores certificate related errors. + +## --ppapi-flash-path=`path` + +Sets the `path` of the pepper flash plugin. + +## --ppapi-flash-version=`version` + +Sets the `version` of the pepper flash plugin. + +## --log-net-log=`path` + +Enables net log events to be saved and writes them to `path`. + +## --ssl-version-fallback-min=`version` + +Sets the minimum SSL/TLS version (`tls1`, `tls1.1` or `tls1.2`) that TLS +fallback will accept. + +## --cipher-suite-blacklist=`cipher_suites` + +Specifies comma-separated list of SSL cipher suites to disable. + +## --disable-renderer-backgrounding + +Prevents Chromium from lowering the priority of invisible pages' renderer +processes. + +This flag is global to all renderer processes, if you only want to disable +throttling in one window, you can take the hack of +[playing silent audio][play-silent-audio]. + +## --enable-logging + +Prints Chromium's logging into console. + +This switch can not be used in `app.commandLine.appendSwitch` since it is parsed +earlier than user's app is loaded, but you can set the `ELECTRON_ENABLE_LOGGING` +environment variable to achieve the same effect. + +## --v=`log_level` + +Gives the default maximal active V-logging level; 0 is the default. Normally +positive values are used for V-logging levels. + +This switch only works when `--enable-logging` is also passed. + +## --vmodule=`pattern` + +Gives the per-module maximal V-logging levels to override the value given by +`--v`. E.g. `my_module=2,foo*=3` would change the logging level for all code in +source files `my_module.*` and `foo*.*`. + +Any pattern containing a forward or backward slash will be tested against the +whole pathname and not just the module. E.g. `*/foo/bar/*=2` would change the +logging level for all code in the source files under a `foo/bar` directory. + +This switch only works when `--enable-logging` is also passed. + +[app]: app.md +[append-switch]: app.md#appcommandlineappendswitchswitch-value +[ready]: app.md#event-ready +[play-silent-audio]: https://github.com/atom/atom/pull/9485/files +[debugging-main-process]: ../tutorial/debugging-main-process.md +[node-cli]: https://nodejs.org/api/cli.html diff --git a/docs-translations/th-TH/api/client-request.md b/docs-translations/th-TH/api/client-request.md new file mode 100644 index 0000000000..0b722f2f09 --- /dev/null +++ b/docs-translations/th-TH/api/client-request.md @@ -0,0 +1,192 @@ +## Class: ClientRequest + +> Make HTTP/HTTPS requests. + +Process: [Main](../glossary.md#main-process) + +`ClientRequest` implements the [Writable Stream](https://nodejs.org/api/stream.html#stream_writable_streams) +interface and is therefore an [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter). + +### `new ClientRequest(options)` + +* `options` (Object | String) - If `options` is a String, it is interpreted as +the request URL. If it is an object, it is expected to fully specify an HTTP request via the +following properties: + * `method` String (optional) - The HTTP request method. Defaults to the GET +method. + * `url` String (optional) - The request URL. Must be provided in the absolute +form with the protocol scheme specified as http or https. + * `session` Object (optional) - The [`Session`](session.md) instance with +which the request is associated. + * `partition` String (optional) - The name of the [`partition`](session.md) + with which the request is associated. Defaults to the empty string. The +`session` option prevails on `partition`. Thus if a `session` is explicitly +specified, `partition` is ignored. + * `protocol` String (optional) - The protocol scheme in the form 'scheme:'. +Currently supported values are 'http:' or 'https:'. Defaults to 'http:'. + * `host` String (optional) - The server host provided as a concatenation of +the hostname and the port number 'hostname:port' + * `hostname` String (optional) - The server host name. + * `port` Integer (optional) - The server's listening port number. + * `path` String (optional) - The path part of the request URL. + +`options` properties such as `protocol`, `host`, `hostname`, `port` and `path` +strictly follow the Node.js model as described in the +[URL](https://nodejs.org/api/url.html) module. + +For instance, we could have created the same request to 'github.com' as follows: + +```JavaScript +const request = net.request({ + method: 'GET', + protocol: 'https:', + hostname: 'github.com', + port: 443, + path: '/' +}) +``` + +### Instance Events + +#### Event: 'response' + +Returns: + +* `response` IncomingMessage - An object representing the HTTP response message. + +#### Event: 'login' + +Returns: + +* `authInfo` Object + * `isProxy` Boolean + * `scheme` String + * `host` String + * `port` Integer + * `realm` String +* `callback` Function + +Emitted when an authenticating proxy is asking for user credentials. + +The `callback` function is expected to be called back with user credentials: + +* `username` String +* `password` String + +```JavaScript +request.on('login', (authInfo, callback) => { + callback('username', 'password') +}) +``` +Providing empty credentials will cancel the request and report an authentication +error on the response object: + +```JavaScript +request.on('response', (response) => { + console.log(`STATUS: ${response.statusCode}`); + response.on('error', (error) => { + console.log(`ERROR: ${JSON.stringify(error)}`) + }) +}) +request.on('login', (authInfo, callback) => { + callback() +}) +``` + +#### Event: 'finish' + +Emitted just after the last chunk of the `request`'s data has been written into +the `request` object. + +#### Event: 'abort' + +Emitted when the `request` is aborted. The `abort` event will not be fired if +the `request` is already closed. + +#### Event: 'error' + +Returns: + +* `error` Error - an error object providing some information about the failure. + +Emitted when the `net` module fails to issue a network request. Typically when +the `request` object emits an `error` event, a `close` event will subsequently +follow and no response object will be provided. + +#### Event: 'close' + +Emitted as the last event in the HTTP request-response transaction. The `close` +event indicates that no more events will be emitted on either the `request` or +`response` objects. + +### Instance Properties + +#### `request.chunkedEncoding` + +A Boolean specifying whether the request will use HTTP chunked transfer encoding +or not. Defaults to false. The property is readable and writable, however it can +be set only before the first write operation as the HTTP headers are not yet put +on the wire. Trying to set the `chunkedEncoding` property after the first write +will throw an error. + +Using chunked encoding is strongly recommended if you need to send a large +request body as data will be streamed in small chunks instead of being +internally buffered inside Electron process memory. + +### Instance Methods + +#### `request.setHeader(name, value)` + +* `name` String - An extra HTTP header name. +* `value` String - An extra HTTP header value. + +Adds an extra HTTP header. The header name will issued as it is without +lowercasing. It can be called only before first write. Calling this method after +the first write will throw an error. + +#### `request.getHeader(name)` + +* `name` String - Specify an extra header name. + +Returns String - The value of a previously set extra header name. + +#### `request.removeHeader(name)` + +* `name` String - Specify an extra header name. + +Removes a previously set extra header name. This method can be called only +before first write. Trying to call it after the first write will throw an error. + +#### `request.write(chunk[, encoding][, callback])` + +* `chunk` (String | Buffer) - A chunk of the request body's data. If it is a +string, it is converted into a Buffer using the specified encoding. +* `encoding` String (optional) - Used to convert string chunks into Buffer +objects. Defaults to 'utf-8'. +* `callback` Function (optional) - Called after the write operation ends. + +`callback` is essentially a dummy function introduced in the purpose of keeping +similarity with the Node.js API. It is called asynchronously in the next tick +after `chunk` content have been delivered to the Chromium networking layer. +Contrary to the Node.js implementation, it is not guaranteed that `chunk` +content have been flushed on the wire before `callback` is called. + +Adds a chunk of data to the request body. The first write operation may cause +the request headers to be issued on the wire. After the first write operation, +it is not allowed to add or remove a custom header. + +#### `request.end([chunk][, encoding][, callback])` + +* `chunk` (String | Buffer) (optional) +* `encoding` String (optional) +* `callback` Function (optional) + +Sends the last chunk of the request data. Subsequent write or end operations +will not be allowed. The `finish` event is emitted just after the end operation. + +#### `request.abort()` + +Cancels an ongoing HTTP transaction. If the request has already emitted the +`close` event, the abort operation will have no effect. Otherwise an ongoing +event will emit `abort` and `close` events. Additionally, if there is an ongoing +response object,it will emit the `aborted` event. diff --git a/docs-translations/th-TH/api/clipboard.md b/docs-translations/th-TH/api/clipboard.md new file mode 100644 index 0000000000..932a1da7b2 --- /dev/null +++ b/docs-translations/th-TH/api/clipboard.md @@ -0,0 +1,169 @@ +# clipboard + +> Perform copy and paste operations on the system clipboard. + +Process: [Main](../glossary.md#main-process), [Renderer](../glossary.md#renderer-process) + +The following example shows how to write a string to the clipboard: + +```javascript +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 +const {clipboard} = require('electron') +clipboard.writeText('Example String', 'selection') +console.log(clipboard.readText('selection')) +``` + +## Methods + +The `clipboard` module has the following methods: + +**Note:** Experimental APIs are marked as such and could be removed in future. + +### `clipboard.readText([type])` + +* `type` String (optional) + +Returns `String` - The content in the clipboard as plain text. + +### `clipboard.writeText(text[, type])` + +* `text` String +* `type` String (optional) + +Writes the `text` into the clipboard as plain text. + +### `clipboard.readHTML([type])` + +* `type` String (optional) + +Returns `String` - The content in the clipboard as markup. + +### `clipboard.writeHTML(markup[, type])` + +* `markup` String +* `type` String (optional) + +Writes `markup` to the clipboard. + +### `clipboard.readImage([type])` + +* `type` String (optional) + +Returns [`NativeImage`](native-image.md) - The image content in the clipboard. + +### `clipboard.writeImage(image[, type])` + +* `image` [NativeImage](native-image.md) +* `type` String (optional) + +Writes `image` to the clipboard. + +### `clipboard.readRTF([type])` + +* `type` String (optional) + +Returns `String` - The content in the clipboard as RTF. + +### `clipboard.writeRTF(text[, type])` + +* `text` String +* `type` String (optional) + +Writes the `text` into the clipboard in RTF. + +### `clipboard.readBookmark()` _macOS_ _Windows_ + +Returns `Object`: + +* `title` String +* `url` String + +Returns an Object containing `title` and `url` keys representing the bookmark in +the clipboard. The `title` and `url` values will be empty strings when the +bookmark is unavailable. + +### `clipboard.writeBookmark(title, url[, type])` _macOS_ _Windows_ + +* `title` String +* `url` String +* `type` String (optional) + +Writes the `title` and `url` into the clipboard as a bookmark. + +**Note:** Most apps on Windows don't support pasting bookmarks into them so +you can use `clipboard.write` to write both a bookmark and fallback text to the +clipboard. + +```js +clipboard.write({ + text: 'http://electron.atom.io', + bookmark: 'Electron Homepage' +}) +``` + +### `clipboard.readFindText()` _macOS_ + +Returns `String` - The text on the find pasteboard. This method uses synchronous +IPC when called from the renderer process. The cached value is reread from the +find pasteboard whenever the application is activated. + +### `clipboard.writeFindText(text)` _macOS_ + +* `text` String + +Writes the `text` into the find pasteboard as plain text. This method uses +synchronous IPC when called from the renderer process. + +### `clipboard.clear([type])` + +* `type` String (optional) + +Clears the clipboard content. + +### `clipboard.availableFormats([type])` + +* `type` String (optional) + +Returns `String[]` - An array of supported formats for the clipboard `type`. + +### `clipboard.has(data[, type])` _Experimental_ + +* `data` String +* `type` String (optional) + +Returns `Boolean` - Whether the clipboard supports the format of specified `data`. + +```javascript +const {clipboard} = require('electron') +console.log(clipboard.has('

selection

')) +``` + +### `clipboard.read(data[, type])` _Experimental_ + +* `data` String +* `type` String (optional) + +Returns `String` - Reads `data` from the clipboard. + +### `clipboard.write(data[, type])` + +* `data` Object + * `text` String (optional) + * `html` String (optional) + * `image` [NativeImage](native-image.md) (optional) + * `rtf` String (optional) + * `bookmark` String (optional) - The title of the url at `text`. +* `type` String (optional) + +```javascript +const {clipboard} = require('electron') +clipboard.write({text: 'test', html: 'test'}) +``` +Writes `data` to the clipboard. diff --git a/docs-translations/th-TH/api/content-tracing.md b/docs-translations/th-TH/api/content-tracing.md new file mode 100644 index 0000000000..a1c76d8807 --- /dev/null +++ b/docs-translations/th-TH/api/content-tracing.md @@ -0,0 +1,173 @@ +# contentTracing + +> Collect tracing data from Chromium's content module for finding performance +bottlenecks and slow operations. + +Process: [Main](../glossary.md#main-process) + +This module does not include a web interface so you need to open +`chrome://tracing/` in a Chrome browser and load the generated file to view the +result. + +```javascript +const {contentTracing} = require('electron') + +const options = { + categoryFilter: '*', + traceOptions: 'record-until-full,enable-sampling' +} + +contentTracing.startRecording(options, () => { + console.log('Tracing started') + + setTimeout(() => { + contentTracing.stopRecording('', (path) => { + console.log('Tracing data recorded to ' + path) + }) + }, 5000) +}) +``` + +## Methods + +The `contentTracing` module has the following methods: + +### `contentTracing.getCategories(callback)` + +* `callback` Function + * `categories` String[] + +Get a set of category groups. The category groups can change as new code paths +are reached. + +Once all child processes have acknowledged the `getCategories` request the +`callback` is invoked with an array of category groups. + +### `contentTracing.startRecording(options, callback)` + +* `options` Object + * `categoryFilter` String + * `traceOptions` String +* `callback` Function + +Start recording on all processes. + +Recording begins immediately locally and asynchronously on child processes +as soon as they receive the EnableRecording request. The `callback` will be +called once all child processes have acknowledged the `startRecording` request. + +`categoryFilter` is a filter to control what category groups should be +traced. A filter can have an optional `-` prefix to exclude category groups +that contain a matching category. Having both included and excluded +category patterns in the same list is not supported. + +Examples: + +* `test_MyTest*`, +* `test_MyTest*,test_OtherStuff`, +* `"-excluded_category1,-excluded_category2` + +`traceOptions` controls what kind of tracing is enabled, it is a comma-delimited +list. Possible options are: + +* `record-until-full` +* `record-continuously` +* `trace-to-console` +* `enable-sampling` +* `enable-systrace` + +The first 3 options are trace recoding modes and hence mutually exclusive. +If more than one trace recording modes appear in the `traceOptions` string, +the last one takes precedence. If none of the trace recording modes are +specified, recording mode is `record-until-full`. + +The trace option will first be reset to the default option (`record_mode` set to +`record-until-full`, `enable_sampling` and `enable_systrace` set to `false`) +before options parsed from `traceOptions` are applied on it. + +### `contentTracing.stopRecording(resultFilePath, callback)` + +* `resultFilePath` String +* `callback` Function + * `resultFilePath` String + +Stop recording on all processes. + +Child processes typically cache trace data and only rarely flush and send +trace data back to the main process. This helps to minimize the runtime overhead +of tracing since sending trace data over IPC can be an expensive operation. So, +to end tracing, we must asynchronously ask all child processes to flush any +pending trace data. + +Once all child processes have acknowledged the `stopRecording` request, +`callback` will be called with a file that contains the traced data. + +Trace data will be written into `resultFilePath` if it is not empty or into a +temporary file. The actual file path will be passed to `callback` if it's not +`null`. + +### `contentTracing.startMonitoring(options, callback)` + +* `options` Object + * `categoryFilter` String + * `traceOptions` String +* `callback` Function + +Start monitoring on all processes. + +Monitoring begins immediately locally and asynchronously on child processes as +soon as they receive the `startMonitoring` request. + +Once all child processes have acknowledged the `startMonitoring` request the +`callback` will be called. + +### `contentTracing.stopMonitoring(callback)` + +* `callback` Function + +Stop monitoring on all processes. + +Once all child processes have acknowledged the `stopMonitoring` request the +`callback` is called. + +### `contentTracing.captureMonitoringSnapshot(resultFilePath, callback)` + +* `resultFilePath` String +* `callback` Function + * `resultFilePath` String + +Get the current monitoring traced data. + +Child processes typically cache trace data and only rarely flush and send +trace data back to the main process. This is because it may be an expensive +operation to send the trace data over IPC and we would like to avoid unneeded +runtime overhead from tracing. So, to end tracing, we must asynchronously ask +all child processes to flush any pending trace data. + +Once all child processes have acknowledged the `captureMonitoringSnapshot` +request the `callback` will be called with a file that contains the traced data. + + +### `contentTracing.getTraceBufferUsage(callback)` + +* `callback` Function + * `value` Number + * `percentage` Number + +Get the maximum usage across processes of trace buffer as a percentage of the +full state. When the TraceBufferUsage value is determined the `callback` is +called. + +### `contentTracing.setWatchEvent(categoryName, eventName, callback)` + +* `categoryName` String +* `eventName` String +* `callback` Function + +`callback` will be called every time the given event occurs on any +process. + +### `contentTracing.cancelWatchEvent()` + +Cancel the watch event. This may lead to a race condition with the watch event +callback if tracing is enabled. diff --git a/docs-translations/th-TH/api/cookies.md b/docs-translations/th-TH/api/cookies.md new file mode 100644 index 0000000000..8e64202967 --- /dev/null +++ b/docs-translations/th-TH/api/cookies.md @@ -0,0 +1,106 @@ +## Class: Cookies + +> Query and modify a session's cookies. + +Process: [Main](../glossary.md#main-process) + +Instances of the `Cookies` class are accessed by using `cookies` property of +a `Session`. + +For example: + +```javascript +const {session} = require('electron') + +// Query all cookies. +session.defaultSession.cookies.get({}, (error, 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(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) +}) +``` + +### Instance Events + +The following events are available on instances of `Cookies`: + +#### Event: 'changed' + +* `event` Event +* `cookie` [Cookie](structures/cookie.md) - The cookie that was changed +* `cause` String - The cause of the change with one of the following values: + * `explicit` - The cookie was changed directly by a consumer's action. + * `overwrite` - The cookie was automatically removed due to an insert + operation that overwrote it. + * `expired` - The cookie was automatically removed as it expired. + * `evicted` - The cookie was automatically evicted during garbage collection. + * `expired-overwrite` - The cookie was overwritten with an already-expired + expiration date. +* `removed` Boolean - `true` if the cookie was removed, `false` otherwise. + +Emitted when a cookie is changed because it was added, edited, removed, or +expired. + +### Instance Methods + +The following methods are available on instances of `Cookies`: + +#### `cookies.get(filter, callback)` + +* `filter` Object + * `url` String (optional) - Retrieves cookies which are associated with + `url`. Empty implies retrieving cookies of all urls. + * `name` String (optional) - Filters cookies by name. + * `domain` String (optional) - Retrieves cookies whose domains match or are + subdomains of `domains` + * `path` String (optional) - Retrieves cookies whose path matches `path`. + * `secure` Boolean (optional) - Filters cookies by their Secure property. + * `session` Boolean (optional) - Filters out session or persistent cookies. +* `callback` Function + * `error` Error + * `cookies` Cookies[] + +Sends a request to get all cookies matching `details`, `callback` will be called +with `callback(error, cookies)` on complete. + +`cookies` is an Array of [`cookie`](structures/cookie.md) objects. + +#### `cookies.set(details, callback)` + +* `details` Object + * `url` String - The url to associate the cookie with. + * `name` String (optional) - The name of the cookie. Empty by default if omitted. + * `value` String (optional) - The value of the cookie. Empty by default if omitted. + * `domain` String (optional) - The domain of the cookie. Empty by default if omitted. + * `path` String (optional) - The path of the cookie. Empty by default if omitted. + * `secure` Boolean (optional) - Whether the cookie should be marked as Secure. Defaults to + false. + * `httpOnly` Boolean (optional) - Whether the cookie should be marked as HTTP only. + Defaults to false. + * `expirationDate` Double (optional) - The expiration date of the cookie as the number of + seconds since the UNIX epoch. If omitted then the cookie becomes a session + cookie and will not be retained between sessions. +* `callback` Function + * `error` Error + +Sets a cookie with `details`, `callback` will be called with `callback(error)` +on complete. + +#### `cookies.remove(url, name, callback)` + +* `url` String - The URL associated with the cookie. +* `name` String - The name of cookie to remove. +* `callback` Function + +Removes the cookies matching `url` and `name`, `callback` will called with +`callback()` on complete. diff --git a/docs-translations/th-TH/api/crash-reporter.md b/docs-translations/th-TH/api/crash-reporter.md new file mode 100644 index 0000000000..737918571c --- /dev/null +++ b/docs-translations/th-TH/api/crash-reporter.md @@ -0,0 +1,112 @@ +# crashReporter + +> Submit crash reports to a remote server. + +Process: [Main](../glossary.md#main-process), [Renderer](../glossary.md#renderer-process) + +The following is an example of automatically submitting a crash report to a +remote server: + +```javascript +const {crashReporter} = require('electron') + +crashReporter.start({ + productName: 'YourName', + companyName: 'YourCompany', + submitURL: 'https://your-domain.com/url-to-submit', + uploadToServer: true +}) +``` + +For setting up a server to accept and process crash reports, you can use +following projects: + +* [socorro](https://github.com/mozilla/socorro) +* [mini-breakpad-server](https://github.com/electron/mini-breakpad-server) + +Crash reports are saved locally in an application-specific temp directory folder. +For a `productName` of `YourName`, crash reports will be stored in a folder +named `YourName Crashes` inside the temp directory. You can customize this temp +directory location for your app by calling the `app.setPath('temp', '/my/custom/temp')` +API before starting the crash reporter. + +## Methods + +The `crashReporter` module has the following methods: + +### `crashReporter.start(options)` + +* `options` Object + * `companyName` String (optional) + * `submitURL` String - URL that crash reports will be sent to as POST. + * `productName` String (optional) - Defaults to `app.getName()`. + * `uploadToServer` Boolean (optional) _macOS_ - Whether crash reports should be sent to the server + Default is `true`. + * `ignoreSystemCrashHandler` Boolean (optional) - Default is `false`. + * `extra` Object (optional) - An object you can define that will be sent along with the + report. Only string properties are sent correctly, Nested objects are not + supported. + +You are required to call this method before using any other `crashReporter` APIs +and in each process (main/renderer) from which you want to collect crash reports. +You can pass different options to `crashReporter.start` when calling from different processes. + +**Note:** On Windows and Linux, Electron uses `breakpad` for crash collection and reporting. +Crashes can be collected from the main and renderer process, but not from the child processes +created via the `child_process` module. + +**Note:** On macOS, Electron uses a new `crashpad` client for crash collection and reporting. +Crashes can be collected from the main, renderer and any of the child processes created via the `child_process` module. +If you want to enable crash reporting, initializing `crashpad` from the main process using `crashReporter.start` is required +regardless of which process you want to collect crashes from. Once initialized this way, the crashpad handler collects +crashes from all processes. You still have to call `crashReporter.start` from the renderer process, otherwise crashes from +renderer processes will get reported without `companyName`, `productName` or any of the `extra` information. + +### `crashReporter.getLastCrashReport()` + +Returns [`CrashReport`](structures/crash-report.md): + +Returns the date and ID of the last crash report. If no crash reports have been +sent or the crash reporter has not been started, `null` is returned. + +### `crashReporter.getUploadedReports()` + +Returns [`CrashReport[]`](structures/crash-report.md): + +Returns all uploaded crash reports. Each report contains the date and uploaded +ID. + +### `crashReporter.getUploadToServer()` _macOS_ + +Returns `Boolean` - Whether reports should be submitted to the server. Set through +the `start` method or `setUploadToServer`. + +**Note:** This API can only be called from the main process. + +### `crashReporter.setUploadToServer(uploadToServer)` _macOS_ + +* `uploadToServer` Boolean _macOS_ - Whether reports should be submitted to the server + +This would normally be controlled by user preferences. This has no effect if +called before `start` is called. + +**Note:** This API can only be called from the main process. + +## Crash Report Payload + +The crash reporter will send the following data to the `submitURL` as +a `multipart/form-data` `POST`: + +* `ver` String - The version of Electron. +* `platform` String - e.g. 'win32'. +* `process_type` String - e.g. 'renderer'. +* `guid` String - e.g. '5e1286fc-da97-479e-918b-6bfb0c3d1c72' +* `_version` String - The version in `package.json`. +* `_productName` String - The product name in the `crashReporter` `options` + object. +* `prod` String - Name of the underlying product. In this case Electron. +* `_companyName` String - The company name in the `crashReporter` `options` + object. +* `upload_file_minidump` File - The crash report in the format of `minidump`. +* All level one properties of the `extra` object in the `crashReporter` + `options` object. diff --git a/docs-translations/th-TH/api/debugger.md b/docs-translations/th-TH/api/debugger.md new file mode 100644 index 0000000000..1be33833ed --- /dev/null +++ b/docs-translations/th-TH/api/debugger.md @@ -0,0 +1,83 @@ +## Class: Debugger + +> An alternate transport for Chrome's remote debugging protocol. + +Process: [Main](../glossary.md#main-process) + +Chrome Developer Tools has a [special binding][rdp] available at JavaScript +runtime that allows interacting with pages and instrumenting them. + +```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.on('detach', (event, 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() + } + } +}) + +win.webContents.debugger.sendCommand('Network.enable') +``` + +### Instance Methods + +#### `debugger.attach([protocolVersion])` + +* `protocolVersion` String (optional) - Requested debugging protocol version. + +Attaches the debugger to the `webContents`. + +#### `debugger.isAttached()` + +Returns `Boolean` - Whether a debugger is attached to the `webContents`. + +#### `debugger.detach()` + +Detaches the debugger from the `webContents`. + +#### `debugger.sendCommand(method[, commandParams, callback])` + +* `method` String - Method name, should be one of the methods defined by the + remote debugging protocol. +* `commandParams` Object (optional) - JSON object with request parameters. +* `callback` Function (optional) - Response + * `error` Object - Error message indicating the failure of the command. + * `result` Any - Response defined by the 'returns' attribute of + the command description in the remote debugging protocol. + +Send given command to the debugging target. + +### Instance Events + +#### Event: 'detach' + +* `event` Event +* `reason` String - Reason for detaching debugger. + +Emitted when debugging session is terminated. This happens either when +`webContents` is closed or devtools is invoked for the attached `webContents`. + +#### Event: 'message' + +* `event` Event +* `method` String - Method name. +* `params` Object - Event parameters defined by the 'parameters' + attribute in the remote debugging protocol. + +Emitted whenever debugging target issues instrumentation event. + +[rdp]: https://developer.chrome.com/devtools/docs/debugger-protocol +[`webContents.findInPage`]: web-contents.md#contentsfindinpagetext-options diff --git a/docs-translations/th-TH/api/desktop-capturer.md b/docs-translations/th-TH/api/desktop-capturer.md new file mode 100644 index 0000000000..85755dc03e --- /dev/null +++ b/docs-translations/th-TH/api/desktop-capturer.md @@ -0,0 +1,76 @@ +# desktopCapturer + +> Access information about media sources that can be used to capture audio and +> video from the desktop using the [`navigator.webkitGetUserMedia`] API. + +Process: [Renderer](../glossary.md#renderer-process) + +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') + +desktopCapturer.getSources({types: ['window', 'screen']}, (error, sources) => { + if (error) throw error + for (let i = 0; i < sources.length; ++i) { + if (sources[i].name === 'Electron') { + navigator.webkitGetUserMedia({ + audio: false, + video: { + mandatory: { + chromeMediaSource: 'desktop', + chromeMediaSourceId: sources[i].id, + minWidth: 1280, + maxWidth: 1280, + minHeight: 720, + maxHeight: 720 + } + } + }, handleStream, handleError) + return + } + } +}) + +function handleStream (stream) { + document.querySelector('video').src = URL.createObjectURL(stream) +} + +function handleError (e) { + console.log(e) +} +``` + +To capture video from a source provided by `desktopCapturer` the constraints +passed to [`navigator.webkitGetUserMedia`] must include +`chromeMediaSource: 'desktop'`, and `audio: false`. + +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 + +The `desktopCapturer` module has the following methods: + +### `desktopCapturer.getSources(options, callback)` + +* `options` Object + * `types` String[] - An array of Strings that lists the types of desktop sources + to be captured, available types are `screen` and `window`. + * `thumbnailSize` Object (optional) - The suggested size that the media source + thumbnail should be scaled to, defaults to `{width: 150, height: 150}`. +* `callback` Function + * `error` Error + * `sources` [DesktopCapturerSource[]](structures/desktop-capturer-source.md) + +Starts gathering information about all available desktop media sources, +and calls `callback(error, sources)` when finished. + +`sources` is an array of [`DesktopCapturerSource`](structures/desktop-capturer-source.md) +objects, each `DesktopCapturerSource` represents a screen or an individual window that can be +captured. + +[`navigator.webkitGetUserMedia`]: https://developer.mozilla.org/en/docs/Web/API/Navigator/getUserMedia diff --git a/docs-translations/th-TH/api/dialog.md b/docs-translations/th-TH/api/dialog.md new file mode 100644 index 0000000000..18f1fc6b07 --- /dev/null +++ b/docs-translations/th-TH/api/dialog.md @@ -0,0 +1,161 @@ +# dialog + +> Display native system dialogs for opening and saving files, alerting, etc. + +Process: [Main](../glossary.md#main-process) + +An example of showing a dialog to select multiple files and directories: + +```javascript +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 +console.log(dialog) +``` + +## Methods + +The `dialog` module has the following methods: + +### `dialog.showOpenDialog([browserWindow, ]options[, callback])` + +* `browserWindow` BrowserWindow (optional) +* `options` Object + * `title` String (optional) + * `defaultPath` String (optional) + * `buttonLabel` String (optional) - Custom label for the confirmation button, when + left empty the default label will be used. + * `filters` [FileFilter[]](structures/file-filter.md) (optional) + * `properties` String[] (optional) - Contains which features the dialog should use, can + contain `openFile`, `openDirectory`, `multiSelections`, `createDirectory` + and `showHiddenFiles`. + * `normalizeAccessKeys` Boolean (optional) - Normalize the keyboard access keys + across platforms. Default is `false`. Enabling this assumes `&` is used in + the button labels for the placement of the keyboard shortcut access key + and labels will be converted so they work correctly on each platform, `&` + characters are removed on macOS, converted to `_` on Linux, and left + untouched on Windows. For example, a button label of `Vie&w` will be + converted to `Vie_w` on Linux and `View` on macOS and can be selected + via `Alt-W` on Windows and Linux. +* `callback` Function (optional) + * `filePaths` String[] - An array of file paths chosen by the user + +Returns `String[]`, an array of file paths chosen by the user, +if the callback is provided it returns `undefined`. + +The `browserWindow` argument allows the dialog to attach itself to a parent window, making it modal. + +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']}, + {name: 'Movies', extensions: ['mkv', 'avi', 'mp4']}, + {name: 'Custom File Type', extensions: ['as']}, + {name: 'All Files', extensions: ['*']} + ] +} +``` + +The `extensions` array should contain extensions without wildcards or dots (e.g. +`'png'` is good but `'.png'` and `'*.png'` are bad). To show all files, use the +`'*'` wildcard (no other wildcard is supported). + +If a `callback` is passed, the API call will be asynchronous and the result +will be passed via `callback(filenames)` + +**Note:** On Windows and Linux an open dialog can not be both a file selector +and a directory selector, so if you set `properties` to +`['openFile', 'openDirectory']` on these platforms, a directory selector will be +shown. + +### `dialog.showSaveDialog([browserWindow, ]options[, callback])` + +* `browserWindow` BrowserWindow (optional) +* `options` Object + * `title` String (optional) + * `defaultPath` String (optional) + * `buttonLabel` String (optional) - Custom label for the confirmation button, when + left empty the default label will be used. + * `filters` [FileFilter[]](structures/file-filter.md) (optional) +* `callback` Function (optional) + * `filename` String + +Returns `String`, the path of the file chosen by the user, +if a callback is provided it returns `undefined`. + +The `browserWindow` argument allows the dialog to attach itself to a parent window, making it modal. + +The `filters` specifies an array of file types that can be displayed, see +`dialog.showOpenDialog` for an example. + +If a `callback` is passed, the API call will be asynchronous and the result +will be passed via `callback(filename)` + +### `dialog.showMessageBox([browserWindow, ]options[, callback])` + +* `browserWindow` BrowserWindow (optional) +* `options` Object + * `type` String (optional) - Can be `"none"`, `"info"`, `"error"`, `"question"` or + `"warning"`. On Windows, "question" displays the same icon as "info", unless + you set an icon using the "icon" option. + * `buttons` String[] (optional) - Array of texts for buttons. On Windows, an empty array + will result in one button labeled "OK". + * `defaultId` Integer (optional) - Index of the button in the buttons array which will + be selected by default when the message box opens. + * `title` String (optional) - Title of the message box, some platforms will not show it. + * `message` String - Content of the message box. + * `detail` String (optional) - Extra information of the message. + * `icon` [NativeImage](native-image.md) (optional) + * `cancelId` Integer (optional) - The value will be returned when user cancels the dialog + instead of clicking the buttons of the dialog. By default it is the index + of the buttons that have "cancel" or "no" as label, or 0 if there is no such + buttons. On macOS and Windows the index of the "Cancel" button will always + be used as `cancelId` even if it is specified. + * `noLink` Boolean (optional) - On Windows Electron will try to figure out which one of + the `buttons` are common buttons (like "Cancel" or "Yes"), and show the + others as command links in the dialog. This can make the dialog appear in + the style of modern Windows apps. If you don't like this behavior, you can + set `noLink` to `true`. +* `callback` Function (optional) + * `response` Number - The index of the button that was clicked + +Returns `Integer`, the index of the clicked button, if a callback is provided +it returns undefined. + +Shows a message box, it will block the process until the message box is closed. +It returns the index of the clicked button. + +The `browserWindow` argument allows the dialog to attach itself to a parent window, making it modal. + +If a `callback` is passed, the API call will be asynchronous and the result +will be passed via `callback(response)`. + +### `dialog.showErrorBox(title, content)` + +* `title` String - The title to display in the error box +* `content` String - The text content to display in the error box + +Displays a modal dialog that shows an error message. + +This API can be called safely before the `ready` event the `app` module emits, +it is usually used to report errors in early stage of startup. If called +before the app `ready`event on Linux, the message will be emitted to stderr, +and no GUI dialog will appear. + +## Sheets + +On macOS, dialogs are presented as sheets attached to a window if you provide +a `BrowserWindow` reference in the `browserWindow` parameter, or modals if no +window is provided. + +You can call `BrowserWindow.getCurrentWindow().setSheetOffset(offset)` to change +the offset from the window frame where sheets are attached. diff --git a/docs-translations/th-TH/api/download-item.md b/docs-translations/th-TH/api/download-item.md new file mode 100644 index 0000000000..6603b619aa --- /dev/null +++ b/docs-translations/th-TH/api/download-item.md @@ -0,0 +1,168 @@ +## Class: DownloadItem + +> Control file downloads from remote sources. + +Process: [Main](../glossary.md#main-process) + +`DownloadItem` is an `EventEmitter` that represents a download item in Electron. +It is used in `will-download` event of `Session` class, and allows users to +control the download item. + +```javascript +// In the main process. +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') + + item.on('updated', (event, state) => { + if (state === 'interrupted') { + console.log('Download is interrupted but can be resumed') + } else if (state === 'progressing') { + if (item.isPaused()) { + console.log('Download is paused') + } else { + console.log(`Received bytes: ${item.getReceivedBytes()}`) + } + } + }) + item.once('done', (event, state) => { + if (state === 'completed') { + console.log('Download successfully') + } else { + console.log(`Download failed: ${state}`) + } + }) +}) +``` + +### Instance Events + +#### Event: 'updated' + +Returns: + +* `event` Event +* `state` String + +Emitted when the download has been updated and is not done. + +The `state` can be one of following: + +* `progressing` - The download is in-progress. +* `interrupted` - The download has interrupted and can be resumed. + +#### Event: 'done' + +Returns: + +* `event` Event +* `state` String + +Emitted when the download is in a terminal state. This includes a completed +download, a cancelled download (via `downloadItem.cancel()`), and interrupted +download that can't be resumed. + +The `state` can be one of following: + +* `completed` - The download completed successfully. +* `cancelled` - The download has been cancelled. +* `interrupted` - The download has interrupted and can not resume. + +### Instance Methods + +The `downloadItem` object has the following methods: + +#### `downloadItem.setSavePath(path)` + +* `path` String - Set the save file path of the download item. + +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 `String` - 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. + +#### `downloadItem.isPaused()` + +Returns `Boolean` - Whether the download is paused. + +#### `downloadItem.resume()` + +Resumes the download that has been paused. + +#### `downloadItem.canResume()` + +Resumes `Boolean` - Whether the download can resume. + +#### `downloadItem.cancel()` + +Cancels the download operation. + +#### `downloadItem.getURL()` + +Returns `String` - The origin url where the item is downloaded from. + +#### `downloadItem.getMimeType()` + +Returns `String` - The files mime type. + +#### `downloadItem.hasUserGesture()` + +Returns `Boolean` - Whether the download has user gesture. + +#### `downloadItem.getFilename()` + +Returns `String` - The file name of the download item. + +**Note:** The file name is not always the same as the actual one saved in local +disk. If user changes the file name in a prompted download saving dialog, the +actual name of saved file will be different. + +#### `downloadItem.getTotalBytes()` + +Returns `Integer` - The total size in bytes of the download item. + +If the size is unknown, it returns 0. + +#### `downloadItem.getReceivedBytes()` + +Returns `Integer` - The received bytes of the download item. + +#### `downloadItem.getContentDisposition()` + +Returns `String` - The Content-Disposition field from the response +header. + +#### `downloadItem.getState()` + +Returns `String` - The current state. Can be `progressing`, `completed`, `cancelled` or `interrupted`. + +**Note:** The following methods are useful specifically to resume a +`cancelled` item when session is restarted. + +#### `downloadItem.getURLChain()` + +Returns `String[]` - The complete url chain of the item including any redirects. + +#### `downloadItem.getLastModifiedTime()` + +Returns `String` - Last-Modified header value. + +#### `downloadItem.getETag()` + +Returns `String` - ETag header value. + +#### `downloadItem.getStartTime()` + +Returns `Double` - Number of seconds since the UNIX epoch when the download was +started. diff --git a/docs-translations/th-TH/api/environment-variables.md b/docs-translations/th-TH/api/environment-variables.md new file mode 100644 index 0000000000..83a233e085 --- /dev/null +++ b/docs-translations/th-TH/api/environment-variables.md @@ -0,0 +1,87 @@ +# Environment Variables + +> Control application configuration and behavior without changing code. + +Certain Electron behaviors are controlled by environment variables because they +are initialized earlier than the command line flags and the app's code. + +POSIX shell example: + +```bash +$ export ELECTRON_ENABLE_LOGGING=true +$ electron +``` + +Windows console example: + +```powershell +> set ELECTRON_ENABLE_LOGGING=true +> electron +``` + +## Production Variables + +The following environment variables are intended primarily for use at runtime +in packaged Electron applications. + +### `GOOGLE_API_KEY` + +Electron includes a hardcoded API key for making requests to Google's geocoding +webservice. Because this API key is included in every version of Electron, it +often exceeds its usage quota. To work around this, you can supply your own +Google API key in the environment. Place the following code in your main process +file, before opening any browser windows that will make geocoding requests: + +```javascript +process.env.GOOGLE_API_KEY = 'YOUR_KEY_HERE' +``` + +For instructions on how to acquire a Google API key, visit [this page](https://www.chromium.org/developers/how-tos/api-keys). + +By default, a newly generated Google API key may not be allowed to make +geocoding requests. To enable geocoding requests, visit [this page](https://console.developers.google.com/apis/api/geolocation/overview). + +### `ELECTRON_NO_ASAR` + +Disables ASAR support. This variable is only supported in forked child processes +and spawned child processes that set `ELECTRON_RUN_AS_NODE`. + +### `ELECTRON_RUN_AS_NODE` + +Starts the process as a normal Node.js process. + +### `ELECTRON_NO_ATTACH_CONSOLE` _Windows_ + +Don't attach to the current console session. + +### `ELECTRON_FORCE_WINDOW_MENU_BAR` _Linux_ + +Don't use the global menu bar on Linux. + +## Development Variables + +The following environment variables are intended primarily for development and +debugging purposes. + + +### `ELECTRON_ENABLE_LOGGING` + +Prints Chrome's internal logging to the console. + +### `ELECTRON_LOG_ASAR_READS` + +When Electron reads from an ASAR file, log the read offset and file path to +the system `tmpdir`. The resulting file can be provided to the ASAR module +to optimize file ordering. + +### `ELECTRON_ENABLE_STACK_DUMPING` + +Prints the stack trace to the console when Electron crashes. + +This environment variable will not work if the `crashReporter` is started. + +### `ELECTRON_DEFAULT_ERROR_MODE` _Windows_ + +Shows the Windows's crash dialog when Electron crashes. + +This environment variable will not work if the `crashReporter` is started. diff --git a/docs-translations/th-TH/api/file-object.md b/docs-translations/th-TH/api/file-object.md new file mode 100644 index 0000000000..d451513c02 --- /dev/null +++ b/docs-translations/th-TH/api/file-object.md @@ -0,0 +1,33 @@ +# `File` Object + +> Use the HTML5 `File` API to work natively with files on the filesystem. + +The DOM's File interface provides abstraction around native files in order to +let users work on native files directly with the HTML5 file API. Electron has +added a `path` attribute to the `File` interface which exposes the file's real +path on filesystem. + +Example of getting a real path from a dragged-onto-the-app file: + +```html +
+ Drag your file here +
+ + +``` diff --git a/docs-translations/th-TH/api/frameless-window.md b/docs-translations/th-TH/api/frameless-window.md new file mode 100644 index 0000000000..3ef5cdc93e --- /dev/null +++ b/docs-translations/th-TH/api/frameless-window.md @@ -0,0 +1,143 @@ +# Frameless Window + +> Open a window without toolbars, borders, or other graphical "chrome". + +A frameless window is a window that has no +[chrome](https://developer.mozilla.org/en-US/docs/Glossary/Chrome), the parts of +the window, like toolbars, that are not a part of the web page. These are +options on the [`BrowserWindow`](browser-window.md) class. + +## Create a frameless window + +To create a frameless window, you need to set `frame` to `false` in +[BrowserWindow](browser-window.md)'s `options`: + + +```javascript +const {BrowserWindow} = require('electron') +let win = new BrowserWindow({width: 800, height: 600, frame: false}) +win.show() +``` + +### Alternatives on macOS + +On macOS 10.9 Mavericks and newer, there's an alternative way to specify +a chromeless window. Instead of setting `frame` to `false` which disables +both the titlebar and window controls, you may want to have the title bar +hidden and your content extend to the full window size, yet still preserve +the window controls ("traffic lights") for standard window actions. +You can do so by specifying the new `titleBarStyle` option: + +#### `hidden` + +Results in a hidden title bar and a full size content window, yet the title bar still has the standard window controls (“traffic lights”) in the top left. + +```javascript +const {BrowserWindow} = require('electron') +let win = new BrowserWindow({titleBarStyle: 'hidden'}) +win.show() +``` + +#### `hidden-inset` + +Results in a hidden title bar with an alternative look where the traffic light buttons are slightly more inset from the window edge. + +```javascript +const {BrowserWindow} = require('electron') +let win = new BrowserWindow({titleBarStyle: 'hidden-inset'}) +win.show() +``` + +## Transparent window + +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 + +* You can not click through the transparent area. We are going to introduce an + API to set window shape to solve this, see + [our issue](https://github.com/electron/electron/issues/1335) for details. +* Transparent windows are not resizable. Setting `resizable` to `true` may make + a transparent window stop working on some platforms. +* The `blur` filter only applies to the web page, so there is no way to apply + blur effect to the content below the window (i.e. other applications open on + the user's system). +* On Windows operating systems, transparent windows will not work when DWM is + disabled. +* On Linux users have to put `--enable-transparent-visuals --disable-gpu` in + the command line to disable GPU and allow ARGB to make transparent window, + this is caused by an upstream bug that [alpha channel doesn't work on some + NVidia drivers](https://code.google.com/p/chromium/issues/detail?id=369209) on + Linux. +* On Mac the native window shadow will not be shown on a transparent window. + +## Click-through window + +To create a click-through window, i.e. making the window ignore all mouse +events, you can call the [win.setIgnoreMouseEvents(ignore)][ignore-mouse-events] +API: + +```javascript +const {BrowserWindow} = require('electron') +let win = new BrowserWindow() +win.setIgnoreMouseEvents(true) +``` + +## Draggable region + +By default, the frameless window is non-draggable. Apps need to specify +`-webkit-app-region: drag` in CSS to tell Electron which regions are draggable +(like the OS's standard titlebar), and apps can also use +`-webkit-app-region: no-drag` to exclude the non-draggable area from the + draggable region. Note that only rectangular shapes are currently supported. + +To make the whole window draggable, you can add `-webkit-app-region: drag` as +`body`'s style: + +```html + + +``` + +And note that if you have made the whole window draggable, you must also mark +buttons as non-draggable, otherwise it would be impossible for users to click on +them: + +```css +button { + -webkit-app-region: no-drag; +} +``` + +If you're setting just a custom titlebar as draggable, you also need to make all +buttons in titlebar non-draggable. + +## Text selection + +In a frameless window the dragging behaviour may conflict with selecting text. +For example, when you drag the titlebar you may accidentally select the text on +the titlebar. To prevent this, you need to disable text selection within a +draggable area like this: + +```css +.titlebar { + -webkit-user-select: none; + -webkit-app-region: drag; +} +``` + +## Context menu + +On some platforms, the draggable area will be treated as a non-client frame, so +when you right click on it a system menu will pop up. To make the context menu +behave correctly on all platforms you should never use a custom context menu on +draggable areas. + +[ignore-mouse-events]: browser-window.md#winsetignoremouseeventsignore diff --git a/docs-translations/th-TH/api/global-shortcut.md b/docs-translations/th-TH/api/global-shortcut.md new file mode 100644 index 0000000000..d0f48a6f80 --- /dev/null +++ b/docs-translations/th-TH/api/global-shortcut.md @@ -0,0 +1,75 @@ +# globalShortcut + +> Detect keyboard events when the application does not have keyboard focus. + +Process: [Main](../glossary.md#main-process) + +The `globalShortcut` module can register/unregister a global keyboard shortcut +with the operating system so that you can customize the operations for various +shortcuts. + +**Note:** The shortcut is global; it will work even if the app does +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') + +app.on('ready', () => { + // Register a 'CommandOrControl+X' shortcut listener. + const ret = globalShortcut.register('CommandOrControl+X', () => { + console.log('CommandOrControl+X is pressed') + }) + + if (!ret) { + console.log('registration failed') + } + + // Check whether a shortcut is registered. + console.log(globalShortcut.isRegistered('CommandOrControl+X')) +}) + +app.on('will-quit', () => { + // Unregister a shortcut. + globalShortcut.unregister('CommandOrControl+X') + + // Unregister all shortcuts. + globalShortcut.unregisterAll() +}) +``` + +## Methods + +The `globalShortcut` module has the following methods: + +### `globalShortcut.register(accelerator, callback)` + +* `accelerator` [Accelerator](accelerator.md) +* `callback` Function + +Registers a global shortcut of `accelerator`. The `callback` is called when +the registered shortcut is pressed by the user. + +When the accelerator is already taken by other applications, this call will +silently fail. This behavior is intended by operating systems, since they don't +want applications to fight for global shortcuts. + +### `globalShortcut.isRegistered(accelerator)` + +* `accelerator` [Accelerator](accelerator.md) + +Returns `Boolean` - Whether this application has registered `accelerator`. + +When the accelerator is already taken by other applications, this call will +still return `false`. This behavior is intended by operating systems, since they +don't want applications to fight for global shortcuts. + +### `globalShortcut.unregister(accelerator)` + +* `accelerator` [Accelerator](accelerator.md) + +Unregisters the global shortcut of `accelerator`. + +### `globalShortcut.unregisterAll()` + +Unregisters all of the global shortcuts. diff --git a/docs-translations/th-TH/api/incoming-message.md b/docs-translations/th-TH/api/incoming-message.md new file mode 100644 index 0000000000..f047aaa7e4 --- /dev/null +++ b/docs-translations/th-TH/api/incoming-message.md @@ -0,0 +1,74 @@ +## Class: IncomingMessage + +> Handle responses to HTTP/HTTPS requests. + +Process: [Main](../glossary.md#main-process) + +`IncomingMessage` implements the [Readable Stream](https://nodejs.org/api/stream.html#stream_readable_streams) +interface and is therefore an [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter). + +### Instance Events + +#### Event: 'data' + +Returns: + +* `chunk` Buffer - A chunk of response body's data. + +The `data` event is the usual method of transferring response data into +applicative code. + +#### Event: 'end' + +Indicates that response body has ended. + +#### Event: 'aborted' + +Emitted when a request has been canceled during an ongoing HTTP transaction. + +#### Event: 'error' + +Returns: + +`error` Error - Typically holds an error string identifying failure root cause. + +Emitted when an error was encountered while streaming response data events. For +instance, if the server closes the underlying while the response is still +streaming, an `error` event will be emitted on the response object and a `close` +event will subsequently follow on the request object. + +### Instance Properties + +An `IncomingMessage` instance has the following readable properties: + +#### `response.statusCode` + +An Integer indicating the HTTP response status code. + +#### `response.statusMessage` + +A String representing the HTTP status message. + +#### `response.headers` + +An Object representing the response HTTP headers. The `headers` object is +formatted as follows: + +* All header names are lowercased. +* Each header name produces an array-valued property on the headers object. +* Each header value is pushed into the array associated with its header name. + +#### `response.httpVersion` + +A String indicating the HTTP protocol version number. Typical values are '1.0' +or '1.1'. Additionally `httpVersionMajor` and `httpVersionMinor` are two +Integer-valued readable properties that return respectively the HTTP major and +minor version numbers. + +#### `response.httpVersionMajor` + +An Integer indicating the HTTP protocol major version number. + +#### `response.httpVersionMinor` + +An Integer indicating the HTTP protocol minor version number. diff --git a/docs-translations/th-TH/api/ipc-main.md b/docs-translations/th-TH/api/ipc-main.md new file mode 100644 index 0000000000..57c57b5a4e --- /dev/null +++ b/docs-translations/th-TH/api/ipc-main.md @@ -0,0 +1,99 @@ +# ipcMain + +> Communicate asynchronously from the main process to renderer processes. + +Process: [Main](../glossary.md#main-process) + +The `ipcMain` module is an instance of the +[EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter) class. When used in the main +process, it handles asynchronous and synchronous messages sent from a renderer +process (web page). Messages sent from a renderer will be emitted to this +module. + +## Sending Messages + +It is also possible to send messages from the main process to the renderer +process, see [webContents.send][web-contents-send] for more information. + +* When sending a message, the event name is the `channel`. +* To reply a synchronous message, you need to set `event.returnValue`. +* To send an asynchronous back to the sender, you can use + `event.sender.send(...)`. + +An example of sending and handling messages between the render and main +processes: + +```javascript +// In main process. +const {ipcMain} = require('electron') +ipcMain.on('asynchronous-message', (event, arg) => { + 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' +}) +``` + +```javascript +// In renderer process (web page). +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') +``` + +## Methods + +The `ipcMain` module has the following method to listen for events: + +### `ipcMain.on(channel, listener)` + +* `channel` String +* `listener` Function + +Listens to `channel`, when a new message arrives `listener` would be called with +`listener(event, args...)`. + +### `ipcMain.once(channel, listener)` + +* `channel` String +* `listener` Function + +Adds a one time `listener` function for the event. This `listener` is invoked +only the next time a message is sent to `channel`, after which it is removed. + +### `ipcMain.removeListener(channel, listener)` + +* `channel` String +* `listener` Function + +Removes the specified `listener` from the listener array for the specified +`channel`. + +### `ipcMain.removeAllListeners([channel])` + +* `channel` String (optional) + +Removes all listeners, or those of the specified `channel`. + +## Event object + +The `event` object passed to the `callback` has the following methods: + +### `event.returnValue` + +Set this to the value to be returned in a synchronous message. + +### `event.sender` + +Returns the `webContents` that sent the message, you can call +`event.sender.send` to reply to the asynchronous message, see +[webContents.send][web-contents-send] for more information. + +[web-contents-send]: web-contents.md#webcontentssendchannel-arg1-arg2- diff --git a/docs-translations/th-TH/api/ipc-renderer.md b/docs-translations/th-TH/api/ipc-renderer.md new file mode 100644 index 0000000000..de67648764 --- /dev/null +++ b/docs-translations/th-TH/api/ipc-renderer.md @@ -0,0 +1,81 @@ +# ipcRenderer + +> Communicate asynchronously from a renderer process to the main process. + +Process: [Renderer](../glossary.md#renderer-process) + +The `ipcRenderer` module is an instance of the +[EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter) class. It provides a few +methods so you can send synchronous and asynchronous messages from the render +process (web page) to the main process. You can also receive replies from the +main process. + +See [ipcMain](ipc-main.md) for code examples. + +## Methods + +The `ipcRenderer` module has the following method to listen for events and send messages: + +### `ipcRenderer.on(channel, listener)` + +* `channel` String +* `listener` Function + +Listens to `channel`, when a new message arrives `listener` would be called with +`listener(event, args...)`. + +### `ipcRenderer.once(channel, listener)` + +* `channel` String +* `listener` Function + +Adds a one time `listener` function for the event. This `listener` is invoked +only the next time a message is sent to `channel`, after which it is removed. + +### `ipcRenderer.removeListener(channel, listener)` + +* `channel` String +* `listener` Function + +Removes the specified `listener` from the listener array for the specified +`channel`. + +### `ipcRenderer.removeAllListeners([channel])` + +* `channel` String (optional) + +Removes all listeners, or those of the specified `channel`. + +### `ipcRenderer.send(channel[, arg1][, arg2][, ...])` + +* `channel` String +* `...args` any[] + +Send a message to the main process asynchronously via `channel`, you can also +send arbitrary arguments. Arguments will be serialized in JSON internally and +hence no functions or prototype chain will be included. + +The main process handles it by listening for `channel` with `ipcMain` module. + +### `ipcRenderer.sendSync(channel[, arg1][, arg2][, ...])` + +* `channel` String +* `...args` any[] + +Send a message to the main process synchronously via `channel`, you can also +send arbitrary arguments. Arguments will be serialized in JSON internally and +hence no functions or prototype chain will be included. + +The main process handles it by listening for `channel` with `ipcMain` module, +and replies by setting `event.returnValue`. + +**Note:** Sending a synchronous message will block the whole renderer process, +unless you know what you are doing you should never use it. + +### `ipcRenderer.sendToHost(channel[, arg1][, arg2][, ...])` + +* `channel` String +* `...args` any[] + +Like `ipcRenderer.send` but the event will be sent to the `` element in +the host page instead of the main process. diff --git a/docs-translations/th-TH/api/locales.md b/docs-translations/th-TH/api/locales.md new file mode 100644 index 0000000000..e8af957b26 --- /dev/null +++ b/docs-translations/th-TH/api/locales.md @@ -0,0 +1,139 @@ +# Locales + +> Locale values returned by `app.getLocale()`. + +Electron uses Chromium's `l10n_util` library to fetch the locale. Possible +values are listed below: + +| Language Code | Language Name | +|---------------|---------------| +| af | Afrikaans | +| an | Aragonese | +| ar-AE | Arabic (U.A.E.) | +| ar-IQ | Arabic (Iraq) | +| ar | Arabic (Standard) | +| ar-BH | Arabic (Bahrain) | +| ar-DZ | Arabic (Algeria) | +| ar-EG | Arabic (Egypt) | +| ar-JO | Arabic (Jordan) | +| ar-KW | Arabic (Kuwait) | +| ar-LB | Arabic (Lebanon) | +| ar-LY | Arabic (Libya) | +| ar-MA | Arabic (Morocco) | +| ar-OM | Arabic (Oman) | +| ar-QA | Arabic (Qatar) | +| ar-SA | Arabic (Saudi Arabia) | +| ar-SY | Arabic (Syria) | +| ar-TN | Arabic (Tunisia) | +| ar-YE | Arabic (Yemen) | +| as | Assamese | +| ast | Asturian | +| az | Azerbaijani | +| be | Belarusian | +| bg | Bulgarian | +| bg | Bulgarian | +| bn | Bengali | +| br | Breton | +| bs | Bosnian | +| ca | Catalan | +| ce | Chechen | +| ch | Chamorro | +| co | Corsican | +| cr | Cree | +| cs | Czech | +| cv | Chuvash | +| da | Danish | +| de | German (Standard) | +| de-AT | German (Austria) | +| de-CH | German (Switzerland) | +| de-DE | German (Germany) | +| de-LI | German (Liechtenstein) | +| de-LU | German (Luxembourg) | +| el | Greek | +| en-AU | English (Australia) | +| en-BZ | English (Belize) | +| en | English | +| en-CA | English (Canada) | +| en-GB | English (United Kingdom) | +| en-IE | English (Ireland) | +| en-JM | English (Jamaica) | +| en-NZ | English (New Zealand) | +| en-PH | English (Philippines) | +| en-TT | English (Trinidad & Tobago) | +| en-US | English (United States) | +| en-ZA | English (South Africa) | +| en-ZW | English (Zimbabwe) | +| eo | Esperanto | +| et | Estonian | +| eu | Basque | +| fa | Persian | +| fa | Farsi | +| fa-IR | Persian/Iran | +| fi | Finnish | +| fj | Fijian | +| fo | Faeroese | +| fr-CH | French (Switzerland) | +| fr-FR | French (France) | +| fr-LU | French (Luxembourg) | +| fr-MC | French (Monaco) | +| fr | French (Standard) | +| fr-BE | French (Belgium) | +| fr-CA | French (Canada) | +| fur | Friulian | +| fy | Frisian | +| ga | Irish | +| gd-IE | Gaelic (Irish) | +| gd | Gaelic (Scots) | +| gl | Galacian | +| gu | Gujurati | +| he | Hebrew | +| hi | Hindi | +| hr | Croatian | +| ht | Haitian | +| hu | Hungarian | +| hy | Armenian | +| id | Indonesian | +| is | Icelandic | +| it-CH | Italian (Switzerland) | +| it | Italian (Standard) | +| iu | Inuktitut | +| ja | Japanese | +| ka | Georgian | +| kk | Kazakh | +| km | Khmer | +| kn | Kannada | +| ko | Korean | +| ko-KP | Korean (North Korea) | +| ko-KR | Korean (South Korea) | +| ks | Kashmiri | +| ky | Kirghiz | +| la | Latin | +| lb | Luxembourgish | +| lt | Lithuanian | +| lv | Latvian | +| mi | Maori | +| mk | FYRO Macedonian | +| ml | Malayalam | +| mo | Moldavian | +| mr | Marathi | +| ms | Malay | +| mt | Maltese | +| my | Burmese | +| nb | Norwegian (Bokmal) | +| ne | Nepali | +| ng | Ndonga | +| nl | Dutch (Standard) | +| nl-BE | Dutch (Belgian) | +| nn | Norwegian (Nynorsk) | +| no | Norwegian | +| nv | Navajo | +| oc | Occitan | +| om | Oromo | +| or | Oriya | +| sq | Albanian | +| tlh | Klingon | +| zh-TW | Chinese (Taiwan) | +| zh | Chinese | +| zh-CN | Chinese (PRC) | +| zh-HK | Chinese (Hong Kong) | +| zh-SG | Chinese (Singapore) | diff --git a/docs-translations/th-TH/api/menu-item.md b/docs-translations/th-TH/api/menu-item.md new file mode 100644 index 0000000000..920589c41d --- /dev/null +++ b/docs-translations/th-TH/api/menu-item.md @@ -0,0 +1,116 @@ +## Class: MenuItem + +> Add items to native application menus and context menus. + +Process: [Main](../glossary.md#main-process) + +See [`Menu`](menu.md) for examples. + +### `new MenuItem(options)` + +* `options` Object + * `click` Function (optional) - Will be called with + `click(menuItem, browserWindow, event)` when the menu item is clicked. + * `menuItem` MenuItem + * `browserWindow` BrowserWindow + * `event` Event + * `role` String (optional) - Define the action of the menu item, when specified the + `click` property will be ignored. + * `type` String (optional) - Can be `normal`, `separator`, `submenu`, `checkbox` or + `radio`. + * `label` String - (optional) + * `sublabel` String - (optional) + * `accelerator` [Accelerator](accelerator.md) (optional) + * `icon` ([NativeImage](native-image.md) | String) (optional) + * `enabled` Boolean (optional) - If false, the menu item will be greyed out and + unclickable. + * `visible` Boolean (optional) - If false, the menu item will be entirely hidden. + * `checked` Boolean (optional) - Should only be specified for `checkbox` or `radio` type + menu items. + * `submenu` (MenuItemConstructorOptions[] | Menu) (optional) - Should be specified for `submenu` type menu items. If + `submenu` is specified, the `type: 'submenu'` can be omitted. If the value + is not a `Menu` then it will be automatically converted to one using + `Menu.buildFromTemplate`. + * `id` String (optional) - Unique within a single menu. If defined then it can be used + as a reference to this item by the position attribute. + * `position` String (optional) - This field allows fine-grained definition of the + specific location within a given menu. + +It is best to specify `role` for any menu item that matches a standard role, +rather than trying to manually implement the behavior in a `click` function. +The built-in `role` behavior will give the best native experience. + +The `label` and `accelerator` are optional when using a `role` and will default +to appropriate values for each platform. + +The `role` property can have following values: + +* `undo` +* `redo` +* `cut` +* `copy` +* `paste` +* `pasteandmatchstyle` +* `selectall` +* `delete` +* `minimize` - Minimize current window +* `close` - Close current window +* `quit`- Quit the application +* `reload` - Reload the current window +* `toggledevtools` - Toggle developer tools in the current window +* `togglefullscreen`- Toggle full screen mode on the current window +* `resetzoom` - Reset the focused page's zoom level to the original size +* `zoomin` - Zoom in the focused page by 10% +* `zoomout` - Zoom out the focused page by 10% + +On macOS `role` can also have following additional values: + +* `about` - Map to the `orderFrontStandardAboutPanel` action +* `hide` - Map to the `hide` action +* `hideothers` - Map to the `hideOtherApplications` action +* `unhide` - Map to the `unhideAllApplications` action +* `startspeaking` - Map to the `startSpeaking` action +* `stopspeaking` - Map to the `stopSpeaking` action +* `front` - Map to the `arrangeInFront` action +* `zoom` - Map to the `performZoom` action +* `window` - The submenu is a "Window" menu +* `help` - The submenu is a "Help" menu +* `services` - The submenu is a "Services" menu + +When specifying `role` on macOS, `label` and `accelerator` are the only options +that will affect the MenuItem. All other options will be ignored. + +### Instance Properties + +The following properties are available on instances of `MenuItem`: + +#### `menuItem.enabled` + +A Boolean indicating whether the item is enabled, this property can be +dynamically changed. + +#### `menuItem.visible` + +A Boolean indicating whether the item is visible, this property can be +dynamically changed. + +#### `menuItem.checked` + +A Boolean indicating whether the item is checked, this property can be +dynamically changed. + +A `checkbox` menu item will toggle the `checked` property on and off when +selected. + +A `radio` menu item will turn on its `checked` property when clicked, and +will turn off that property for all adjacent items in the same menu. + +You can add a `click` function for additional behavior. + +#### `menuItem.label` + +A String representing the menu items visible label + +#### `menuItem.click` + +A Function that is fired when the MenuItem recieves a click event diff --git a/docs-translations/th-TH/api/menu.md b/docs-translations/th-TH/api/menu.md new file mode 100644 index 0000000000..64ab3afafb --- /dev/null +++ b/docs-translations/th-TH/api/menu.md @@ -0,0 +1,403 @@ +## Class: Menu + +> Create native application menus and context menus. + +Process: [Main](../glossary.md#main-process) + +### `new Menu()` + +Creates a new menu. + +### Static Methods + +The `menu` class has the following static methods: + +#### `Menu.setApplicationMenu(menu)` + +* `menu` Menu + +Sets `menu` as the application menu on macOS. On Windows and Linux, the `menu` +will be set as each window's top menu. + +**Note:** This API has to be called after the `ready` event of `app` module. + +#### `Menu.getApplicationMenu()` + +Returns `Menu` - The application menu, if set, or `null`, if not set. + +#### `Menu.sendActionToFirstResponder(action)` _macOS_ + +* `action` String + +Sends the `action` to the first responder of application. This is used for +emulating default Cocoa menu behaviors, usually you would just use the +`role` property of `MenuItem`. + +See the [macOS Cocoa Event Handling Guide](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/EventOverview/EventArchitecture/EventArchitecture.html#//apple_ref/doc/uid/10000060i-CH3-SW7) +for more information on macOS' native actions. + +#### `Menu.buildFromTemplate(template)` + +* `template` MenuItemConstructorOptions[] + +Returns `Menu` + +Generally, the `template` is just an array of `options` for constructing a +[MenuItem](menu-item.md). The usage can be referenced above. + +You can also attach other fields to the element of the `template` and they +will become properties of the constructed menu items. + +### Instance Methods + +The `menu` object has the following instance methods: + +#### `menu.popup([browserWindow, x, y, positioningItem])` + +* `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`. + +#### `menu.append(menuItem)` + +* `menuItem` MenuItem + +Appends the `menuItem` to the menu. + +#### `menu.insert(pos, menuItem)` + +* `pos` Integer +* `menuItem` MenuItem + +Inserts the `menuItem` to the `pos` position of the menu. + +### Instance Properties + +`menu` objects also have the following properties: + +#### `menu.items` + +A MenuItem[] array containing the menu's items. + +Each `Menu` consists of multiple [`MenuItem`](menu-item.md)s and each `MenuItem` +can have a submenu. + +## Examples + +The `Menu` class is only available in the main process, but you can also use it +in the render process via the [`remote`](remote.md) module. + +### Main process + +An example of creating the application menu in the main process with the +simple template API: + +```javascript +const {app, Menu} = require('electron') + +const template = [ + { + label: 'Edit', + submenu: [ + { + role: 'undo' + }, + { + role: 'redo' + }, + { + type: 'separator' + }, + { + role: 'cut' + }, + { + role: 'copy' + }, + { + role: 'paste' + }, + { + role: 'pasteandmatchstyle' + }, + { + role: 'delete' + }, + { + role: 'selectall' + } + ] + }, + { + label: 'View', + submenu: [ + { + role: 'reload' + }, + { + role: 'toggledevtools' + }, + { + type: 'separator' + }, + { + role: 'resetzoom' + }, + { + role: 'zoomin' + }, + { + role: 'zoomout' + }, + { + type: 'separator' + }, + { + role: 'togglefullscreen' + } + ] + }, + { + role: 'window', + submenu: [ + { + role: 'minimize' + }, + { + role: 'close' + } + ] + }, + { + role: 'help', + submenu: [ + { + label: 'Learn More', + click () { require('electron').shell.openExternal('http://electron.atom.io') } + } + ] + } +] + +if (process.platform === 'darwin') { + template.unshift({ + label: app.getName(), + submenu: [ + { + role: 'about' + }, + { + type: 'separator' + }, + { + role: 'services', + submenu: [] + }, + { + type: 'separator' + }, + { + role: 'hide' + }, + { + role: 'hideothers' + }, + { + role: 'unhide' + }, + { + type: 'separator' + }, + { + role: 'quit' + } + ] + }) + // Edit menu. + template[1].submenu.push( + { + type: 'separator' + }, + { + label: 'Speech', + submenu: [ + { + role: 'startspeaking' + }, + { + role: 'stopspeaking' + } + ] + } + ) + // Window menu. + template[3].submenu = [ + { + label: 'Close', + accelerator: 'CmdOrCtrl+W', + role: 'close' + }, + { + label: 'Minimize', + accelerator: 'CmdOrCtrl+M', + role: 'minimize' + }, + { + label: 'Zoom', + role: 'zoom' + }, + { + type: 'separator' + }, + { + label: 'Bring All to Front', + role: 'front' + } + ] +} + +const menu = Menu.buildFromTemplate(template) +Menu.setApplicationMenu(menu) +``` + +### Render process + +Below is an example of creating a menu dynamically in a web page +(render process) by using the [`remote`](remote.md) module, and showing it when +the user right clicks the page: + +```html + + +``` + + +## Notes on macOS Application Menu + +macOS has a completely different style of application menu from Windows and +Linux. Here are some notes on making your app's menu more native-like. + +### Standard Menus + +On macOS there are many system-defined standard menus, like the `Services` and +`Windows` menus. To make your menu a standard menu, you should set your menu's +`role` to one of following and Electron will recognize them and make them +become standard menus: + +* `window` +* `help` +* `services` + +### Standard Menu Item Actions + +macOS has provided standard actions for some menu items, like `About xxx`, +`Hide xxx`, and `Hide Others`. To set the action of a menu item to a standard +action, you should set the `role` attribute of the menu item. + +### Main Menu's Name + +On macOS the label of the application menu's first item is always your app's +name, no matter what label you set. To change it, modify your app bundle's +`Info.plist` file. See +[About Information Property List Files][AboutInformationPropertyListFiles] +for more information. + +## Setting Menu for Specific Browser Window (*Linux* *Windows*) + +The [`setMenu` method][setMenu] of browser windows can set the menu of certain +browser windows. + +## Menu Item Position + +You can make use of `position` and `id` to control how the item will be placed +when building a menu with `Menu.buildFromTemplate`. + +The `position` attribute of `MenuItem` has the form `[placement]=[id]`, where +`placement` is one of `before`, `after`, or `endof` and `id` is the unique ID of +an existing item in the menu: + +* `before` - Inserts this item before the id referenced item. If the + referenced item doesn't exist the item will be inserted at the end of + the menu. +* `after` - Inserts this item after id referenced item. If the referenced + item doesn't exist the item will be inserted at the end of the menu. +* `endof` - Inserts this item at the end of the logical group containing + the id referenced item (groups are created by separator items). If + the referenced item doesn't exist, a new separator group is created with + the given id and this item is inserted after that separator. + +When an item is positioned, all un-positioned items are inserted after +it until a new item is positioned. So if you want to position a group of +menu items in the same location you only need to specify a position for +the first item. + +### Examples + +Template: + +```javascript +[ + {label: '4', id: '4'}, + {label: '5', id: '5'}, + {label: '1', id: '1', position: 'before=4'}, + {label: '2', id: '2'}, + {label: '3', id: '3'} +] +``` + +Menu: + +``` +- 1 +- 2 +- 3 +- 4 +- 5 +``` + +Template: + +```javascript +[ + {label: 'a', position: 'endof=letters'}, + {label: '1', position: 'endof=numbers'}, + {label: 'b', position: 'endof=letters'}, + {label: '2', position: 'endof=numbers'}, + {label: 'c', position: 'endof=letters'}, + {label: '3', position: 'endof=numbers'} +] +``` + +Menu: + +``` +- --- +- a +- b +- c +- --- +- 1 +- 2 +- 3 +``` + +[AboutInformationPropertyListFiles]: https://developer.apple.com/library/ios/documentation/general/Reference/InfoPlistKeyReference/Articles/AboutInformationPropertyListFiles.html +[setMenu]: https://github.com/electron/electron/blob/master/docs/api/browser-window.md#winsetmenumenu-linux-windows diff --git a/docs-translations/th-TH/api/native-image.md b/docs-translations/th-TH/api/native-image.md new file mode 100644 index 0000000000..cc910cbb8a --- /dev/null +++ b/docs-translations/th-TH/api/native-image.md @@ -0,0 +1,256 @@ +# nativeImage + +> Create tray, dock, and application icons using PNG or JPG files. + +Process: [Main](../glossary.md#main-process), [Renderer](../glossary.md#renderer-process) + +In Electron, for the APIs that take images, you can pass either file paths or +`NativeImage` instances. An empty image will be used when `null` is passed. + +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 + +Currently `PNG` and `JPEG` image formats are supported. `PNG` is recommended +because of its support for transparency and lossless compression. + +On Windows, you can also load `ICO` icons from file paths. For best visual +quality it is recommended to include at least the following sizes in the: + +* Small icon + * 16x16 (100% DPI scale) + * 20x20 (125% DPI scale) + * 24x24 (150% DPI scale) + * 32x32 (200% DPI scale) +* Large icon + * 32x32 (100% DPI scale) + * 40x40 (125% DPI scale) + * 48x48 (150% DPI scale) + * 64x64 (200% DPI scale) +* 256x256 + +Check the *Size requirements* section in [this article][icons]. + +[icons]:https://msdn.microsoft.com/en-us/library/windows/desktop/dn742485(v=vs.85).aspx + +## High Resolution Image + +On platforms that have high-DPI support such as Apple Retina displays, you can +append `@2x` after image's base filename to mark it as a high resolution image. + +For example if `icon.png` is a normal image that has standard resolution, then +`icon@2x.png` will be treated as a high resolution image that has double DPI +density. + +If you want to support displays with different DPI densities at the same time, +you can put images with different sizes in the same folder and use the filename +without DPI suffixes. For example: + +```text +images/ +├── icon.png +├── icon@2x.png +└── icon@3x.png +``` + + +```javascript +const {Tray} = require('electron') +let appIcon = new Tray('/Users/somebody/images/icon.png') +console.log(appIcon) +``` + +Following suffixes for DPI are also supported: + +* `@1x` +* `@1.25x` +* `@1.33x` +* `@1.4x` +* `@1.5x` +* `@1.8x` +* `@2x` +* `@2.5x` +* `@3x` +* `@4x` +* `@5x` + +## Template Image + +Template images consist of black and clear colors (and an alpha channel). +Template images are not intended to be used as standalone images and are usually +mixed with other content to create the desired final appearance. + +The most common case is to use template images for a menu bar icon so it can +adapt to both light and dark menu bars. + +**Note:** Template image is only supported on macOS. + +To mark an image as a template image, its filename should end with the word +`Template`. For example: + +* `xxxTemplate.png` +* `xxxTemplate@2x.png` + +## Methods + +The `nativeImage` module has the following methods, all of which return +an instance of the `NativeImage` class: + +### `nativeImage.createEmpty()` + +Returns `NativeImage` + +Creates an empty `NativeImage` instance. + +### `nativeImage.createFromPath(path)` + +* `path` String + +Returns `NativeImage` + +Creates a new `NativeImage` instance from a file located at `path`. This method +returns an empty image if the `path` does not exist, cannot be read, or is not +a valid image. + +```javascript +const nativeImage = require('electron').nativeImage + +let image = nativeImage.createFromPath('/Users/somebody/images/icon.png') +console.log(image) +``` + +### `nativeImage.createFromBuffer(buffer[, options])` + +* `buffer` [Buffer][buffer] +* `options` Object (optional) + * `width` Integer (optional) - Required for bitmap buffers. + * `height` Integer (optional) - Required for bitmap buffers. + * `scaleFactor` Double (optional) - Defaults to 1.0. + +Returns `NativeImage` + +Creates a new `NativeImage` instance from `buffer`. + +### `nativeImage.createFromDataURL(dataURL)` + +* `dataURL` String + +Creates a new `NativeImage` instance from `dataURL`. + +## Class: NativeImage + +> Natively wrap images such as tray, dock, and application icons. + +Process: [Main](../glossary.md#main-process), [Renderer](../glossary.md#renderer-process) + +### Instance Methods + +The following methods are available on instances of the `NativeImage` class: + +#### `image.toPNG()` + +Returns `Buffer` - A [Buffer][buffer] that contains the image's `PNG` encoded data. + +#### `image.toJPEG(quality)` + +* `quality` Integer (**required**) - Between 0 - 100. + +Returns `Buffer` - A [Buffer][buffer] that contains the image's `JPEG` encoded data. + +#### `image.toBitmap()` + +Returns `Buffer` - A [Buffer][buffer] that contains a copy of the image's raw bitmap pixel +data. + +#### `image.toDataURL()` + +Returns `String` - The data URL of the image. + +#### `image.getBitmap()` + +Returns `Buffer` - A [Buffer][buffer] that contains the image's raw bitmap pixel data. + +The difference between `getBitmap()` and `toBitmap()` is, `getBitmap()` does not +copy the bitmap data, so you have to use the returned Buffer immediately in +current event loop tick, otherwise the data might be changed or destroyed. + +#### `image.getNativeHandle()` _macOS_ + +Returns `Buffer` - A [Buffer][buffer] that stores C pointer to underlying native handle of +the image. On macOS, a pointer to `NSImage` instance would be returned. + +Notice that the returned pointer is a weak pointer to the underlying native +image instead of a copy, so you _must_ ensure that the associated +`nativeImage` instance is kept around. + +#### `image.isEmpty()` + +Returns `Boolean` - Whether the image is empty. + +#### `image.getSize()` + +Returns `Object`: + +* `width` Integer +* `height` Integer + +#### `image.setTemplateImage(option)` + +* `option` Boolean + +Marks the image as a template image. + +#### `image.isTemplateImage()` + +Returns `Boolean` - Whether the image is a template image. + +#### `image.crop(rect)` + +* `rect` Object - The area of the image to crop + * `x` Integer + * `y` Integer + * `width` Integer + * `height` Integer + +Returns `NativeImage` - The cropped image. + +#### `image.resize(options)` + +* `options` Object + * `width` Integer (optional) + * `height` Integer (optional) + * `quality` String (optional) - The desired quality of the resize image. + Possible values are `good`, `better` or `best`. The default is `best`. + These values express a desired quality/speed tradeoff. They are translated + into an algorithm-specific method that depends on the capabilities + (CPU, GPU) of the underlying platform. It is possible for all three methods + to be mapped to the same algorithm on a given platform. + +Returns `NativeImage` - The resized image. + +If only the `height` or the `width` are specified then the current aspect ratio +will be preserved in the resized image. + +#### `image.getAspectRatio()` + +Returns `Float` - The image's aspect ratio. + +[buffer]: https://nodejs.org/api/buffer.html#buffer_class_buffer diff --git a/docs-translations/th-TH/api/net.md b/docs-translations/th-TH/api/net.md new file mode 100644 index 0000000000..e5e5961714 --- /dev/null +++ b/docs-translations/th-TH/api/net.md @@ -0,0 +1,71 @@ +# net + +> Issue HTTP/HTTPS requests using Chromium's native networking library + +Process: [Main](../glossary.md#main-process) + +The `net` module is a client-side API for issuing HTTP(S) requests. It is +similar to the [HTTP](https://nodejs.org/api/http.html) and +[HTTPS](https://nodejs.org/api/https.html) modules of Node.js but uses +Chromium's native networking library instead of the Node.js implementation, +offering better support for web proxies. + +The following is a non-exhaustive list of why you may consider using the `net` +module instead of the native Node.js modules: + +* Automatic management of system proxy configuration, support of the wpad + protocol and proxy pac configuration files. +* Automatic tunneling of HTTPS requests. +* Support for authenticating proxies using basic, digest, NTLM, Kerberos or + negotiate authentication schemes. +* Support for traffic monitoring proxies: Fiddler-like proxies used for access + control and monitoring. + +The `net` module API has been specifically designed to mimic, as closely as +possible, the familiar Node.js API. The API components including classes, +methods, properties and event names are similar to those commonly used in +Node.js. + +For instance, the following example quickly shows how the `net` API might be +used: + +```javascript +const {app} = require('electron') +app.on('ready', () => { + const {net} = require('electron') + const request = net.request('https://github.com') + request.on('response', (response) => { + console.log(`STATUS: ${response.statusCode}`) + console.log(`HEADERS: ${JSON.stringify(response.headers)}`) + response.on('data', (chunk) => { + console.log(`BODY: ${chunk}`) + }) + response.on('end', () => { + console.log('No more data in response.') + }) + }) + request.end() +}) +``` + +By the way, it is almost identical to how you would normally use the +[HTTP](https://nodejs.org/api/http.html)/[HTTPS](https://nodejs.org/api/https.html) +modules of Node.js + +The `net` API can be used only after the application emits the `ready` event. +Trying to use the module before the `ready` event will throw an error. + +## Methods + +The `net` module has the following methods: + +### `net.request(options)` + +* `options` (Object | String) - The `ClientRequest` constructor options. + +Returns [`ClientRequest`](./client-request.md) + +Creates a [`ClientRequest`](./client-request.md) instance using the provided +`options` which are directly forwarded to the `ClientRequest` constructor. +The `net.request` method would be used to issue both secure and insecure HTTP +requests according to the specified protocol scheme in the `options` object. diff --git a/docs-translations/th-TH/api/power-monitor.md b/docs-translations/th-TH/api/power-monitor.md new file mode 100644 index 0000000000..0c012c64ac --- /dev/null +++ b/docs-translations/th-TH/api/power-monitor.md @@ -0,0 +1,41 @@ +# powerMonitor + +> Monitor power state changes. + +Process: [Main](../glossary.md#main-process) + +You cannot require or use this module until the `ready` event of the `app` +module is emitted. + +For example: + +```javascript +const electron = require('electron') +const {app} = electron + +app.on('ready', () => { + electron.powerMonitor.on('suspend', () => { + console.log('The system is going to sleep') + }) +}) +``` + +## Events + +The `powerMonitor` module emits the following events: + +### Event: 'suspend' + +Emitted when the system is suspending. + +### Event: 'resume' + +Emitted when system is resuming. + +### Event: 'on-ac' _Windows_ + +Emitted when the system changes to AC power. + +### Event: 'on-battery' _Windows_ + +Emitted when system changes to battery power. diff --git a/docs-translations/th-TH/api/power-save-blocker.md b/docs-translations/th-TH/api/power-save-blocker.md new file mode 100644 index 0000000000..f78ec5d999 --- /dev/null +++ b/docs-translations/th-TH/api/power-save-blocker.md @@ -0,0 +1,56 @@ +# powerSaveBlocker + +> Block the system from entering low-power (sleep) mode. + +Process: [Main](../glossary.md#main-process) + +For example: + +```javascript +const {powerSaveBlocker} = require('electron') + +const id = powerSaveBlocker.start('prevent-display-sleep') +console.log(powerSaveBlocker.isStarted(id)) + +powerSaveBlocker.stop(id) +``` + +## Methods + +The `powerSaveBlocker` module has the following methods: + +### `powerSaveBlocker.start(type)` + +* `type` String - Power save blocker type. + * `prevent-app-suspension` - Prevent the application from being suspended. + Keeps system active but allows screen to be turned off. Example use cases: + downloading a file or playing audio. + * `prevent-display-sleep` - Prevent the display from going to sleep. Keeps + system and screen active. Example use case: playing video. + +Returns `Integer` - The blocker ID that is assigned to this power blocker + +Starts preventing the system from entering lower-power mode. Returns an integer +identifying the power save blocker. + +**Note:** `prevent-display-sleep` has higher precedence over +`prevent-app-suspension`. Only the highest precedence type takes effect. In +other words, `prevent-display-sleep` always takes precedence over +`prevent-app-suspension`. + +For example, an API calling A requests for `prevent-app-suspension`, and +another calling B requests for `prevent-display-sleep`. `prevent-display-sleep` +will be used until B stops its request. After that, `prevent-app-suspension` +is used. + +### `powerSaveBlocker.stop(id)` + +* `id` Integer - The power save blocker id returned by `powerSaveBlocker.start`. + +Stops the specified power save blocker. + +### `powerSaveBlocker.isStarted(id)` + +* `id` Integer - The power save blocker id returned by `powerSaveBlocker.start`. + +Returns `Boolean` - Whether the corresponding `powerSaveBlocker` has started. diff --git a/docs-translations/th-TH/api/process.md b/docs-translations/th-TH/api/process.md new file mode 100644 index 0000000000..951abc7df1 --- /dev/null +++ b/docs-translations/th-TH/api/process.md @@ -0,0 +1,118 @@ +# process + +> Extensions to process object. + +Process: [Main](../glossary.md#main-process), [Renderer](../glossary.md#renderer-process) + +Electron's `process` object is extended from the +[Node.js `process` object](https://nodejs.org/api/process.html). +It adds the following events, properties, and methods: + +## Events + +### Event: 'loaded' + +Emitted when Electron has loaded its internal initialization script and is +beginning to load the web page or the main script. + +It can be used by the preload script to add removed Node global symbols back to +the global scope when node integration is turned off: + +```javascript +// preload.js +const _setImmediate = setImmediate +const _clearImmediate = clearImmediate +process.once('loaded', () => { + global.setImmediate = _setImmediate + global.clearImmediate = _clearImmediate +}) +``` + +## Properties + +### `process.noAsar` + +Setting this to `true` can disable the support for `asar` archives in Node's +built-in modules. + +### `process.type` + +Current process's type, can be `"browser"` (i.e. main process) or `"renderer"`. + +### `process.versions.electron` + +Electron's version string. + +### `process.versions.chrome` + +Chrome's version string. + +### `process.resourcesPath` + +Path to the resources directory. + +### `process.mas` + +For Mac App Store build, this property is `true`, for other builds it is +`undefined`. + +### `process.windowsStore` + +If the app is running as a Windows Store app (appx), this property is `true`, +for otherwise it is `undefined`. + +### `process.defaultApp` + +When app is started by being passed as parameter to the default app, this +property is `true` in the main process, otherwise it is `undefined`. + +## Methods + +The `process` object has the following method: + +### `process.crash()` + +Causes the main thread of the current process crash. + +### `process.hang()` + +Causes the main thread of the current process hang. + +### `process.setFdLimit(maxDescriptors)` _macOS_ _Linux_ + +* `maxDescriptors` Integer + +Sets the file descriptor soft limit to `maxDescriptors` or the OS hard +limit, whichever is lower for the current process. + +### `process.getProcessMemoryInfo()` + +Returns `Object`: + +* `workingSetSize` Integer - The amount of memory currently pinned to actual physical + RAM. +* `peakWorkingSetSize` Integer - The maximum amount of memory that has ever been pinned + to actual physical RAM. +* `privateBytes` Integer - The amount of memory not shared by other processes, such as + JS heap or HTML content. +* `sharedBytes` Integer - The amount of memory shared between processes, typically + memory consumed by the Electron code itself + +Returns an object giving memory usage statistics about the current process. Note +that all statistics are reported in Kilobytes. + +### `process.getSystemMemoryInfo()` + +Returns `Object`: + +* `total` Integer - The total amount of physical memory in Kilobytes available to the + system. +* `free` Integer - The total amount of memory not being used by applications or disk + cache. +* `swapTotal` Integer - The total amount of swap memory in Kilobytes available to the + system. _Windows_ _Linux_ +* `swapFree` Integer - The free amount of swap memory in Kilobytes available to the + system. _Windows_ _Linux_ + +Returns an object giving memory usage statistics about the entire system. Note +that all statistics are reported in Kilobytes. diff --git a/docs-translations/th-TH/api/protocol.md b/docs-translations/th-TH/api/protocol.md new file mode 100644 index 0000000000..3de153db48 --- /dev/null +++ b/docs-translations/th-TH/api/protocol.md @@ -0,0 +1,297 @@ +# protocol + +> Register a custom protocol and intercept existing protocol requests. + +Process: [Main](../glossary.md#main-process) + +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') + +app.on('ready', () => { + protocol.registerFileProtocol('atom', (request, callback) => { + const url = request.url.substr(7) + callback({path: path.normalize(`${__dirname}/${url}`)}) + }, (error) => { + 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. + +## Methods + +The `protocol` module has the following methods: + +### `protocol.registerStandardSchemes(schemes[, options])` + +* `schemes` String[] - Custom schemes to be registered as standard schemes. +* `options` Object (optional) + * `secure` Boolean (optional) - `true` to register the scheme as secure. + Default `false`. + +A standard scheme adheres to what RFC 3986 calls [generic URI +syntax](https://tools.ietf.org/html/rfc3986#section-3). For example `http` and +`https` are standard schemes, while `file` is not. + +Registering a scheme as standard, will allow relative and absolute resources to +be resolved correctly when served. Otherwise the scheme will behave like the +`file` protocol, but without the ability to resolve relative URLs. + +For example when you load following page with custom protocol without +registering it as standard scheme, the image will not be loaded because +non-standard schemes can not recognize relative URLs: + +```html + + + +``` + +Registering a scheme as standard will allow access to files through the +[FileSystem API][file-system-api]. Otherwise the renderer will throw a security +error for the scheme. + +By default web storage apis (localStorage, sessionStorage, webSQL, indexedDB, cookies) +are disabled for non standard schemes. So in general if you want to register a +custom protocol to replace the `http` protocol, you have to register it as a standard scheme: + +```javascript +const {app, protocol} = require('electron') + +protocol.registerStandardSchemes(['atom']) +app.on('ready', () => { + protocol.registerHttpProtocol('atom', '...') +}) +``` + +**Note:** This method can only be used before the `ready` event of the `app` +module gets emitted. + +### `protocol.registerServiceWorkerSchemes(schemes)` + +* `schemes` String[] - Custom schemes to be registered to handle service workers. + +### `protocol.registerFileProtocol(scheme, handler[, completion])` + +* `scheme` String +* `handler` Function + * `request` Object + * `url` String + * `referrer` String + * `method` String + * `uploadData` [UploadData[]](structures/upload-data.md) + * `callback` Function + * `filePath` String (optional) +* `completion` Function (optional) + * `error` Error + +Registers a protocol of `scheme` that will send the file as a response. The +`handler` will be called with `handler(request, callback)` when a `request` is +going to be created with `scheme`. `completion` will be called with +`completion(null)` when `scheme` is successfully registered or +`completion(error)` when failed. + +To handle the `request`, the `callback` should be called with either the file's +path or an object that has a `path` property, e.g. `callback(filePath)` or +`callback({path: filePath})`. + +When `callback` is called with nothing, a number, or an object that has an +`error` property, the `request` will fail with the `error` number you +specified. For the available error numbers you can use, please see the +[net error list][net-error]. + +By default the `scheme` is treated like `http:`, which is parsed differently +than protocols that follow the "generic URI syntax" like `file:`, so you +probably want to call `protocol.registerStandardSchemes` to have your scheme +treated as a standard scheme. + +### `protocol.registerBufferProtocol(scheme, handler[, completion])` + +* `scheme` String +* `handler` Function + * `request` Object + * `url` String + * `referrer` String + * `method` String + * `uploadData` [UploadData[]](structures/upload-data.md) + * `callback` Function + * `buffer` (Buffer | [MimeTypedBuffer](structures/mime-typed-buffer.md)) (optional) +* `completion` Function (optional) + * `error` Error + +Registers a protocol of `scheme` that will send a `Buffer` as a response. + +The usage is the same with `registerFileProtocol`, except that the `callback` +should be called with either a `Buffer` object or an object that has the `data`, +`mimeType`, and `charset` properties. + +Example: + +```javascript +const {protocol} = require('electron') + +protocol.registerBufferProtocol('atom', (request, callback) => { + callback({mimeType: 'text/html', data: new Buffer('
Response
')}) +}, (error) => { + if (error) console.error('Failed to register protocol') +}) +``` + +### `protocol.registerStringProtocol(scheme, handler[, completion])` + +* `scheme` String +* `handler` Function + * `request` Object + * `url` String + * `referrer` String + * `method` String + * `uploadData` [UploadData[]](structures/upload-data.md) + * `callback` Function + * `data` String (optional) +* `completion` Function (optional) + * `error` Error + +Registers a protocol of `scheme` that will send a `String` as a response. + +The usage is the same with `registerFileProtocol`, except that the `callback` +should be called with either a `String` or an object that has the `data`, +`mimeType`, and `charset` properties. + +### `protocol.registerHttpProtocol(scheme, handler[, completion])` + +* `scheme` String +* `handler` Function + * `request` Object + * `url` String + * `referrer` String + * `method` String + * `uploadData` [UploadData[]](structures/upload-data.md) + * `callback` Function + * `redirectRequest` Object + * `url` String + * `method` String + * `session` Object (optional) + * `uploadData` Object (optional) + * `contentType` String - MIME type of the content. + * `data` String - Content to be sent. +* `completion` Function (optional) + * `error` Error + +Registers a protocol of `scheme` that will send an HTTP request as a response. + +The usage is the same with `registerFileProtocol`, except that the `callback` +should be called with a `redirectRequest` object that has the `url`, `method`, +`referrer`, `uploadData` and `session` properties. + +By default the HTTP request will reuse the current session. If you want the +request to have a different session you should set `session` to `null`. + +For POST requests the `uploadData` object must be provided. + +### `protocol.unregisterProtocol(scheme[, completion])` + +* `scheme` String +* `completion` Function (optional) + * `error` Error + +Unregisters the custom protocol of `scheme`. + +### `protocol.isProtocolHandled(scheme, callback)` + +* `scheme` String +* `callback` Function + * `error` Error + +The `callback` will be called with a boolean that indicates whether there is +already a handler for `scheme`. + +### `protocol.interceptFileProtocol(scheme, handler[, completion])` + +* `scheme` String +* `handler` Function + * `request` Object + * `url` String + * `referrer` String + * `method` String + * `uploadData` [UploadData[]](structures/upload-data.md) + * `callback` Function + * `filePath` String +* `completion` Function (optional) + * `error` Error + +Intercepts `scheme` protocol and uses `handler` as the protocol's new handler +which sends a file as a response. + +### `protocol.interceptStringProtocol(scheme, handler[, completion])` + +* `scheme` String +* `handler` Function + * `request` Object + * `url` String + * `referrer` String + * `method` String + * `uploadData` [UploadData[]](structures/upload-data.md) + * `callback` Function + * `data` String (optional) +* `completion` Function (optional) + * `error` Error + +Intercepts `scheme` protocol and uses `handler` as the protocol's new handler +which sends a `String` as a response. + +### `protocol.interceptBufferProtocol(scheme, handler[, completion])` + +* `scheme` String +* `handler` Function + * `request` Object + * `url` String + * `referrer` String + * `method` String + * `uploadData` [UploadData[]](structures/upload-data.md) + * `callback` Function + * `buffer` Buffer (optional) +* `completion` Function (optional) + * `error` Error + +Intercepts `scheme` protocol and uses `handler` as the protocol's new handler +which sends a `Buffer` as a response. + +### `protocol.interceptHttpProtocol(scheme, handler[, completion])` + +* `scheme` String +* `handler` Function + * `request` Object + * `url` String + * `referrer` String + * `method` String + * `uploadData` [UploadData[]](structures/upload-data.md) + * `callback` Function + * `redirectRequest` Object + * `url` String + * `method` String + * `session` Object (optional) + * `uploadData` Object (optional) + * `contentType` String - MIME type of the content. + * `data` String - Content to be sent. +* `completion` Function (optional) + * `error` Error + +Intercepts `scheme` protocol and uses `handler` as the protocol's new handler +which sends a new HTTP request as a response. + +### `protocol.uninterceptProtocol(scheme[, completion])` + +* `scheme` String +* `completion` Function (optional) + * `error` Error + +Remove the interceptor installed for `scheme` and restore its original handler. + +[net-error]: https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h +[file-system-api]: https://developer.mozilla.org/en-US/docs/Web/API/LocalFileSystem diff --git a/docs-translations/th-TH/api/remote.md b/docs-translations/th-TH/api/remote.md new file mode 100644 index 0000000000..0bed3ad9b5 --- /dev/null +++ b/docs-translations/th-TH/api/remote.md @@ -0,0 +1,169 @@ +# remote + +> Use main process modules from the renderer process. + +Process: [Renderer](../glossary.md#renderer-process) + +The `remote` module provides a simple way to do inter-process communication +(IPC) between the renderer process (web page) and the main process. + +In Electron, GUI-related modules (such as `dialog`, `menu` etc.) are only +available in the main process, not in the renderer process. In order to use them +from the renderer process, the `ipc` module is necessary to send inter-process +messages to the main process. With the `remote` module, you can invoke methods +of the main process object without explicitly sending inter-process messages, +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') +``` + +**Note:** For the reverse (access the renderer process from the main process), +you can use [webContents.executeJavascript](web-contents.md#contentsexecutejavascriptcode-usergesture-callback). + +## Remote Objects + +Each object (including functions) returned by the `remote` module represents an +object in the main process (we call it a remote object or remote function). +When you invoke methods of a remote object, call a remote function, or create +a new object with the remote constructor (function), you are actually sending +synchronous inter-process messages. + +In the example above, both `BrowserWindow` and `win` were remote objects and +`new BrowserWindow` didn't create a `BrowserWindow` object in the renderer +process. Instead, it created a `BrowserWindow` object in the main process and +returned the corresponding remote object in the renderer process, namely the +`win` object. + +**Note:** Only [enumerable properties][enumerable-properties] which are present +when the remote object is first referenced are accessible via remote. + +**Note:** Arrays and Buffers are copied over IPC when accessed via the `remote` +module. Modifying them in the renderer process does not modify them in the main +process and vice versa. + +## Lifetime of Remote Objects + +Electron makes sure that as long as the remote object in the renderer process +lives (in other words, has not been garbage collected), the corresponding object +in the main process will not be released. When the remote object has been +garbage collected, the corresponding object in the main process will be +dereferenced. + +If the remote object is leaked in the renderer process (e.g. stored in a map but +never freed), the corresponding object in the main process will also be leaked, +so you should be very careful not to leak remote objects. + +Primary value types like strings and numbers, however, are sent by copy. + +## Passing callbacks to the main process + +Code in the main process can accept callbacks from the renderer - for instance +the `remote` module - but you should be extremely careful when using this +feature. + +First, in order to avoid deadlocks, the callbacks passed to the main process +are called asynchronously. You should not expect the main process to +get the return value of the passed callbacks. + +For instance you can't use a function from the renderer process in an +`Array.map` called in the main process: + +```javascript +// main process mapNumbers.js +exports.withRendererCallback = (mapper) => { + return [1, 2, 3].map(mapper) +} + +exports.withLocalCallback = () => { + return [1, 2, 3].map(x => x + 1) +} +``` + +```javascript +// renderer process +const mapNumbers = require('electron').remote.require('./mapNumbers') +const withRendererCb = mapNumbers.withRendererCallback(x => x + 1) +const withLocalCb = mapNumbers.withLocalCallback() + +console.log(withRendererCb, withLocalCb) +// [undefined, undefined, undefined], [2, 3, 4] +``` + +As you can see, the renderer callback's synchronous return value was not as +expected, and didn't match the return value of an identical callback that lives +in the main process. + +Second, the callbacks passed to the main process will persist until the +main process garbage-collects them. + +For example, the following code seems innocent at first glance. It installs a +callback for the `close` event on a remote object: + +```javascript +require('electron').remote.getCurrentWindow().on('close', () => { + // window was closed... +}) +``` + +But remember the callback is referenced by the main process until you +explicitly uninstall it. If you do not, each time you reload your window the +callback will be installed again, leaking one callback for each restart. + +To make things worse, since the context of previously installed callbacks has +been released, exceptions will be raised in the main process when the `close` +event is emitted. + +To avoid this problem, ensure you clean up any references to renderer callbacks +passed to the main process. This involves cleaning up event handlers, or +ensuring the main process is explicitly told to deference callbacks that came +from a renderer process that is exiting. + +## Accessing built-in modules in the main process + +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 = require('electron').remote.app +console.log(app) +``` + +## Methods + +The `remote` module has the following methods: + +### `remote.require(module)` + +* `module` String + +Returns `any` - The object returned by `require(module)` in the main process. + +### `remote.getCurrentWindow()` + +Returns [`BrowserWindow`](browser-window.md) - The window to which this web page +belongs. + +### `remote.getCurrentWebContents()` + +Returns [`WebContents`](web-contents.md) - The web contents of this web page. + +### `remote.getGlobal(name)` + +* `name` String + +Returns `any` - The global variable of `name` (e.g. `global[name]`) in the main +process. + +## Properties + +### `remote.process` + +The `process` object in the main process. This is the same as +`remote.getGlobal('process')` but is cached. + +[rmi]: http://en.wikipedia.org/wiki/Java_remote_method_invocation +[enumerable-properties]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties diff --git a/docs-translations/th-TH/api/screen.md b/docs-translations/th-TH/api/screen.md new file mode 100644 index 0000000000..9704f88134 --- /dev/null +++ b/docs-translations/th-TH/api/screen.md @@ -0,0 +1,122 @@ +# screen + +> Retrieve information about screen size, displays, cursor position, etc. + +Process: [Main](../glossary.md#main-process), [Renderer](../glossary.md#renderer-process) + +You cannot require or use this module until the `ready` event of the `app` +module is emitted. + +`screen` is an [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter). + +**Note:** In the renderer / DevTools, `window.screen` is a reserved DOM +property, so writing `let {screen} = require('electron')` will not work. + +An example of creating a window that fills the whole screen: + +```javascript +const electron = require('electron') +const {app, BrowserWindow} = electron + +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: + +```javascript +const electron = require('electron') +const {app, BrowserWindow} = require('electron') + +let win + +app.on('ready', () => { + let displays = electron.screen.getAllDisplays() + let externalDisplay = displays.find((display) => { + return display.bounds.x !== 0 || display.bounds.y !== 0 + }) + + if (externalDisplay) { + win = new BrowserWindow({ + x: externalDisplay.bounds.x + 50, + y: externalDisplay.bounds.y + 50 + }) + win.loadURL('https://github.com') + } +}) +``` + +## Events + +The `screen` module emits the following events: + +### Event: 'display-added' + +Returns: + +* `event` Event +* `newDisplay` [Display](structures/display.md) + +Emitted when `newDisplay` has been added. + +### Event: 'display-removed' + +Returns: + +* `event` Event +* `oldDisplay` [Display](structures/display.md) + +Emitted when `oldDisplay` has been removed. + +### Event: 'display-metrics-changed' + +Returns: + +* `event` Event +* `display` [Display](structures/display.md) +* `changedMetrics` String[] + +Emitted when one or more metrics change in a `display`. The `changedMetrics` is +an array of strings that describe the changes. Possible changes are `bounds`, +`workArea`, `scaleFactor` and `rotation`. + +## Methods + +The `screen` module has the following methods: + +### `screen.getCursorScreenPoint()` + +Returns `Object`: + +* `x` Integer +* `y` Integer + +The current absolute position of the mouse pointer. + +### `screen.getPrimaryDisplay()` + +Returns [`Display`](structures/display.md) - The primary display. + +### `screen.getAllDisplays()` + +Returns [`Display[]`](structures/display.md) - An array of displays that are currently available. + +### `screen.getDisplayNearestPoint(point)` + +* `point` Object + * `x` Integer + * `y` Integer + +Returns [`Display`](structures/display.md) - The display nearest the specified point. + +### `screen.getDisplayMatching(rect)` + +* `rect` [Rectangle](structures/rectangle.md) + +Returns [`Display`](structures/display.md) - The display that most closely +intersects the provided bounds. diff --git a/docs-translations/th-TH/api/session.md b/docs-translations/th-TH/api/session.md new file mode 100644 index 0000000000..d50274e22e --- /dev/null +++ b/docs-translations/th-TH/api/session.md @@ -0,0 +1,402 @@ +# session + +> Manage browser sessions, cookies, cache, proxy settings, etc. + +Process: [Main](../glossary.md#main-process) + +The `session` module can be used to create new `Session` objects. + +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 {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 + +The `session` module has the following methods: + +### `session.fromPartition(partition[, options])` + +* `partition` String +* `options` Object + * `cache` Boolean - Whether to enable cache. + +Returns `Session` - A session instance from `partition` string. When there is an existing +`Session` with the same `partition`, it will be returned; othewise a new +`Session` instance will be created with `options`. + +If `partition` starts with `persist:`, the page will use a persistent session +available to all pages in the app with the same `partition`. if there is no +`persist:` prefix, the page will use an in-memory session. If the `partition` is +empty then default session of the app will be returned. + +To create a `Session` with `options`, you have to ensure the `Session` with the +`partition` has never been used before. There is no way to change the `options` +of an existing `Session` object. + +## Properties + +The `session` module has the following properties: + +### `session.defaultSession` + +A `Session` object, the default session object of the app. + +## Class: Session + +> Get and set properties of a session. + +Process: [Main](../glossary.md#main-process) + +You can create a `Session` object in the `session` module: + +```javascript +const {session} = require('electron') +const ses = session.fromPartition('persist:name') +console.log(ses.getUserAgent()) +``` + +### Instance Events + +The following events are available on instances of `Session`: + +#### Event: 'will-download' + +* `event` Event +* `item` [DownloadItem](download-item.md) +* `webContents` [WebContents](web-contents.md) + +Emitted when Electron is about to download `item` in `webContents`. + +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() + require('request')(item.getURL(), (data) => { + require('fs').writeFileSync('/somewhere', data) + }) +}) +``` + +### Instance Methods + +The following methods are available on instances of `Session`: + +#### `ses.getCacheSize(callback)` + +* `callback` Function + * `size` Integer - Cache size used in bytes. + +Returns the session's current cache size. + +#### `ses.clearCache(callback)` + +* `callback` Function - Called when operation is done + +Clears the session’s HTTP cache. + +#### `ses.clearStorageData([options, callback])` + +* `options` Object (optional) + * `origin` String - Should follow `window.location.origin`’s representation + `scheme://host:port`. + * `storages` String[] - The types of storages to clear, can contain: + `appcache`, `cookies`, `filesystem`, `indexdb`, `localstorage`, + `shadercache`, `websql`, `serviceworkers` + * `quotas` String[] - The types of quotas to clear, can contain: + `temporary`, `persistent`, `syncable`. +* `callback` Function (optional) - Called when operation is done. + +Clears the data of web storages. + +#### `ses.flushStorageData()` + +Writes any unwritten DOMStorage data to disk. + +#### `ses.setProxy(config, callback)` + +* `config` Object + * `pacScript` String - The URL associated with the PAC file. + * `proxyRules` String - Rules indicating which proxies to use. + * `proxyBypassRules` String - Rules indicating which URLs should + bypass the proxy settings. +* `callback` Function - Called when operation is done. + +Sets the proxy settings. + +When `pacScript` and `proxyRules` are provided together, the `proxyRules` +option is ignored and `pacScript` configuration is applied. + +The `proxyRules` has to follow the rules below: + +``` +proxyRules = schemeProxies[";"] +schemeProxies = ["="] +urlScheme = "http" | "https" | "ftp" | "socks" +proxyURIList = [","] +proxyURL = ["://"][":"] +``` + +For example: + +* `http=foopy:80;ftp=foopy2` - Use HTTP proxy `foopy:80` for `http://` URLs, and + HTTP proxy `foopy2:80` for `ftp://` URLs. +* `foopy:80` - Use HTTP proxy `foopy:80` for all URLs. +* `foopy:80,bar,direct://` - Use HTTP proxy `foopy:80` for all URLs, failing + over to `bar` if `foopy:80` is unavailable, and after that using no proxy. +* `socks4://foopy` - Use SOCKS v4 proxy `foopy:1080` for all URLs. +* `http=foopy,socks5://bar.com` - Use HTTP proxy `foopy` for http URLs, and fail + over to the SOCKS5 proxy `bar.com` if `foopy` is unavailable. +* `http=foopy,direct://` - Use HTTP proxy `foopy` for http URLs, and use no + proxy if `foopy` is unavailable. +* `http=foopy;socks=foopy2` - Use HTTP proxy `foopy` for http URLs, and use + `socks4://foopy2` for all other URLs. + +The `proxyBypassRules` is a comma separated list of rules described below: + +* `[ URL_SCHEME "://" ] HOSTNAME_PATTERN [ ":" ]` + + Match all hostnames that match the pattern HOSTNAME_PATTERN. + + Examples: + "foobar.com", "*foobar.com", "*.foobar.com", "*foobar.com:99", + "https://x.*.y.com:99" + + * `"." HOSTNAME_SUFFIX_PATTERN [ ":" PORT ]` + + Match a particular domain suffix. + + Examples: + ".google.com", ".com", "http://.google.com" + +* `[ SCHEME "://" ] IP_LITERAL [ ":" PORT ]` + + Match URLs which are IP address literals. + + Examples: + "127.0.1", "[0:0::1]", "[::1]", "http://[::1]:99" + +* `IP_LITERAL "/" PREFIX_LENGHT_IN_BITS` + + Match any URL that is to an IP literal that falls between the + given range. IP range is specified using CIDR notation. + + Examples: + "192.168.1.1/16", "fefe:13::abc/33". + +* `` + + Match local addresses. The meaning of `` is whether the + host matches one of: "127.0.0.1", "::1", "localhost". + +#### `ses.resolveProxy(url, callback)` + +* `url` URL +* `callback` Function + * `proxy` Object + +Resolves the proxy information for `url`. The `callback` will be called with +`callback(proxy)` when the request is performed. + +#### `ses.setDownloadPath(path)` + +* `path` String - The download location + +Sets download saving directory. By default, the download directory will be the +`Downloads` under the respective app folder. + +#### `ses.enableNetworkEmulation(options)` + +* `options` Object + * `offline` Boolean (optional) - Whether to emulate network outage. Defaults + to false. + * `latency` Double (optional) - RTT in ms. Defaults to 0 which will disable + latency throttling. + * `downloadThroughput` Double (optional) - Download rate in Bps. Defaults to 0 + which will disable download throttling. + * `uploadThroughput` Double (optional) - Upload rate in Bps. Defaults to 0 + which will disable upload throttling. + +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 +}) + +// To emulate a network outage. +window.webContents.session.enableNetworkEmulation({offline: true}) +``` + +#### `ses.disableNetworkEmulation()` + +Disables any network emulation already active for the `session`. Resets to +the original network configuration. + +#### `ses.setCertificateVerifyProc(proc)` + +* `proc` Function + * `hostname` String + * `certificate` [Certificate](structures/certificate.md) + * `callback` Function + * `isTrusted` Boolean - Determines if the certificate should be trusted + +Sets the certificate verify proc for `session`, the `proc` will be called with +`proc(hostname, certificate, callback)` whenever a server certificate +verification is requested. Calling `callback(true)` accepts the certificate, +calling `callback(false)` rejects it. + +Calling `setCertificateVerifyProc(null)` will revert back to default certificate +verify proc. + +```javascript +const {BrowserWindow} = require('electron') +let win = new BrowserWindow() + +win.webContents.session.setCertificateVerifyProc((hostname, cert, callback) => { + callback(hostname === 'github.com') +}) +``` + +#### `ses.setPermissionRequestHandler(handler)` + +* `handler` Function + * `webContents` Object - [WebContents](web-contents.md) requesting the permission. + * `permission` String - Enum of 'media', 'geolocation', 'notifications', 'midiSysex', + 'pointerLock', 'fullscreen', 'openExternal'. + * `callback` Function + * `permissionGranted` Boolean - Allow or deny the permission + +Sets the handler which can be used to respond to permission requests for the `session`. +Calling `callback(true)` will allow the permission and `callback(false)` will reject it. + +```javascript +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) +}) +``` + +#### `ses.clearHostResolverCache([callback])` + +* `callback` Function (optional) - Called when operation is done. + +Clears the host resolver cache. + +#### `ses.allowNTLMCredentialsForDomains(domains)` + +* `domains` String - A comma-seperated list of servers for which + integrated authentication is enabled. + +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') + +// consider all urls for integrated authentication. +session.defaultSession.allowNTLMCredentialsForDomains('*') +``` + +#### `ses.setUserAgent(userAgent[, acceptLanguages])` + +* `userAgent` String +* `acceptLanguages` String (optional) + +Overrides the `userAgent` and `acceptLanguages` for this session. + +The `acceptLanguages` must a comma separated ordered list of language codes, for +example `"en-US,fr,de,ko,zh-CN,ja"`. + +This doesn't affect existing `WebContents`, and each `WebContents` can use +`webContents.setUserAgent` to override the session-wide user agent. + +#### `ses.getUserAgent()` + +Returns `String` - The user agent for this session. + +#### `ses.getBlobData(identifier, callback)` + +* `identifier` String - Valid UUID. +* `callback` Function + * `result` Buffer - Blob data. + +Returns `Blob` - The blob data associated with the `identifier`. + +#### `ses.createInterruptedDownload(options)` + +* `options` Object + * `path` String - Absolute path of the download. + * `urlChain` String[] - Complete URL chain for the download. + * `mimeType` String (optional) + * `offset` Integer - Start range for the download. + * `length` Integer - Total length of the download. + * `lastModified` String - Last-Modified header value. + * `eTag` String - ETag header value. + * `startTime` Double (optional) - Time when download was started in + number of seconds since UNIX epoch. + +Allows resuming `cancelled` or `interrupted` downloads from previous `Session`. +The API will generate a [DownloadItem](download-item.md) that can be accessed with the [will-download](#event-will-download) +event. The [DownloadItem](download-item.md) will not have any `WebContents` associated with it and +the initial state will be `interrupted`. The download will start only when the +`resume` API is called on the [DownloadItem](download-item.md). + +#### `ses.clearAuthCache(options[, callback])` + +* `options` ([RemovePassword](structures/remove-password.md) | [RemoveClientCertificate](structures/remove-client-certificate.md)) +* `callback` Function (optional) - Called when operation is done + +Clears the session’s HTTP authentication cache. + +### Instance Properties + +The following properties are available on instances of `Session`: + +#### `ses.cookies` + +A Cookies object for this session. + +#### `ses.webRequest` + +A WebRequest object for this session. + +#### `ses.protocol` + +A Protocol object (an instance of [protocol](protocol.md) module) for this session. + +```javascript +const {app, session} = require('electron') +const path = require('path') + +app.on('ready', function () { + const protocol = session.fromPartition('some-partition').protocol + protocol.registerFileProtocol('atom', function (request, callback) { + var url = request.url.substr(7) + callback({path: path.normalize(`${__dirname}/${url}`)}) + }, function (error) { + if (error) console.error('Failed to register protocol') + }) +}) +``` diff --git a/docs-translations/th-TH/api/shell.md b/docs-translations/th-TH/api/shell.md new file mode 100644 index 0000000000..ae30de76f8 --- /dev/null +++ b/docs-translations/th-TH/api/shell.md @@ -0,0 +1,86 @@ +# shell + +> Manage files and URLs using their default applications. + +Process: [Main](../glossary.md#main-process), [Renderer](../glossary.md#renderer-process) + +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') + +shell.openExternal('https://github.com') +``` + +## Methods + +The `shell` module has the following methods: + +### `shell.showItemInFolder(fullPath)` + +* `fullPath` String + +Returns `Boolean` - Whether the item was successfully shown + +Show the given file in a file manager. If possible, select the file. + +### `shell.openItem(fullPath)` + +* `fullPath` String + +Returns `Boolean` - Whether the item was successfully opened. + +Open the given file in the desktop's default manner. + +### `shell.openExternal(url[, options, callback])` + +* `url` String +* `options` Object (optional) _macOS_ + * `activate` Boolean - `true` to bring the opened application to the + foreground. The default is `true`. +* `callback` Function (optional) - If specified will perform the open asynchronously. _macOS_ + * `error` Error + +Returns `Boolean` - Whether an application was available to open the URL. +If callback is specified, always returns true. + +Open the given external protocol URL in the desktop's default manner. (For +example, mailto: URLs in the user's default mail agent). + +### `shell.moveItemToTrash(fullPath)` + +* `fullPath` String + +Returns `Boolean` - Whether the item was successfully moved to the trash + +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 following: + * `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` [ShortcutDetails](structures/shortcut-details.md) + +Returns `Boolean` - Whether the shortcut was created successfully + +Creates or updates a shortcut link at `shortcutPath`. + +### `shell.readShortcutLink(shortcutPath)` _Windows_ + +* `shortcutPath` String + +Returns [`ShortcutDetails`](structures/shortcut-details.md) + +Resolves the shortcut link at `shortcutPath`. + +An exception will be thrown when any error happens. diff --git a/docs-translations/th-TH/api/structures/bluetooth-device.md b/docs-translations/th-TH/api/structures/bluetooth-device.md new file mode 100644 index 0000000000..33d3bb51f9 --- /dev/null +++ b/docs-translations/th-TH/api/structures/bluetooth-device.md @@ -0,0 +1,4 @@ +# BluetoothDevice Object + +* `deviceName` String +* `deviceId` String diff --git a/docs-translations/th-TH/api/structures/certificate-principal.md b/docs-translations/th-TH/api/structures/certificate-principal.md new file mode 100644 index 0000000000..12c46382aa --- /dev/null +++ b/docs-translations/th-TH/api/structures/certificate-principal.md @@ -0,0 +1,8 @@ +# CertificatePrincipal Object + +* `commonName` String - Common Name +* `organizations` String[] - Organization names +* `organizationUnits` String[] - Organization Unit names +* `locality` String - Locality +* `state` String - State or province +* `country` String - Country or region diff --git a/docs-translations/th-TH/api/structures/certificate.md b/docs-translations/th-TH/api/structures/certificate.md new file mode 100644 index 0000000000..3c521b2d36 --- /dev/null +++ b/docs-translations/th-TH/api/structures/certificate.md @@ -0,0 +1,12 @@ +# Certificate Object + +* `data` String - PEM encoded data +* `issuer` [CertificatePrincipal](certificate-principal.md) - Issuer principal +* `issuerName` String - Issuer's Common Name +* `issuerCert` Certificate - Issuer certificate (if not self-signed) +* `subject` [CertificatePrincipal](certificate-principal.md) - Subject principal +* `subjectName` String - Subject's Common Name +* `serialNumber` String - Hex value represented string +* `validStart` Number - Start date of the certificate being valid in seconds +* `validExpiry` Number - End date of the certificate being valid in seconds +* `fingerprint` String - Fingerprint of the certificate diff --git a/docs-translations/th-TH/api/structures/cookie.md b/docs-translations/th-TH/api/structures/cookie.md new file mode 100644 index 0000000000..677840cf82 --- /dev/null +++ b/docs-translations/th-TH/api/structures/cookie.md @@ -0,0 +1,14 @@ +# Cookie Object + +* `name` String - The name of the cookie. +* `value` String - The value of the cookie. +* `domain` String (optional) - The domain of the cookie. +* `hostOnly` Boolean (optional) - Whether the cookie is a host-only cookie. +* `path` String (optional) - The path of the cookie. +* `secure` Boolean (optional) - Whether the cookie is marked as secure. +* `httpOnly` Boolean (optional) - Whether the cookie is marked as HTTP only. +* `session` Boolean (optional) - Whether the cookie is a session cookie or a persistent + cookie with an expiration date. +* `expirationDate` Double (optional) - The expiration date of the cookie as + the number of seconds since the UNIX epoch. Not provided for session + cookies. diff --git a/docs-translations/th-TH/api/structures/crash-report.md b/docs-translations/th-TH/api/structures/crash-report.md new file mode 100644 index 0000000000..b26d4adeca --- /dev/null +++ b/docs-translations/th-TH/api/structures/crash-report.md @@ -0,0 +1,4 @@ +# CrashReport Object + +* `date` String +* `ID` Integer \ No newline at end of file diff --git a/docs-translations/th-TH/api/structures/desktop-capturer-source.md b/docs-translations/th-TH/api/structures/desktop-capturer-source.md new file mode 100644 index 0000000000..3ebe4ce49c --- /dev/null +++ b/docs-translations/th-TH/api/structures/desktop-capturer-source.md @@ -0,0 +1,14 @@ +# DesktopCapturerSource Object + +* `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 + `thumbnailSize` specified in the `options` passed to + `desktopCapturer.getSources`. The actual size depends on the scale of the + screen or window. diff --git a/docs-translations/th-TH/api/structures/display.md b/docs-translations/th-TH/api/structures/display.md new file mode 100644 index 0000000000..d702b35a1b --- /dev/null +++ b/docs-translations/th-TH/api/structures/display.md @@ -0,0 +1,19 @@ +# Display Object + +* `id` Number - Unique identifier associated with the display. +* `rotation` Number - Can be 0, 90, 180, 270, represents screen rotation in + clock-wise degrees. +* `scaleFactor` Number - Output device's pixel scale factor. +* `touchSupport` String - Can be `available`, `unavailable`, `unknown`. +* `bounds` [Rectangle](rectangle.md) +* `size` Object + * `height` Number + * `width` Number +* `workArea` [Rectangle](rectangle.md) +* `workAreaSize` Object + * `height` Number + * `width` Number + +The `Display` object represents a physical display connected to the system. A +fake `Display` may exist on a headless system, or a `Display` may correspond to +a remote, virtual display. diff --git a/docs-translations/th-TH/api/structures/file-filter.md b/docs-translations/th-TH/api/structures/file-filter.md new file mode 100644 index 0000000000..014350a60f --- /dev/null +++ b/docs-translations/th-TH/api/structures/file-filter.md @@ -0,0 +1,4 @@ +# FileFilter Object + +* `name` String +* `extensions` String[] diff --git a/docs-translations/th-TH/api/structures/jump-list-category.md b/docs-translations/th-TH/api/structures/jump-list-category.md new file mode 100644 index 0000000000..07627e78c9 --- /dev/null +++ b/docs-translations/th-TH/api/structures/jump-list-category.md @@ -0,0 +1,21 @@ +# JumpListCategory Object + +* `type` String (optional) - One of the following: + * `tasks` - Items in this category will be placed into the standard `Tasks` + category. There can be only one such category, and it will always be + displayed at the bottom of the Jump List. + * `frequent` - Displays a list of files frequently opened by the app, the + name of the category and its items are set by Windows. + * `recent` - Displays a list of files recently opened by the app, the name + of the category and its items are set by Windows. Items may be added to + this category indirectly using `app.addRecentDocument(path)`. + * `custom` - Displays tasks or file links, `name` must be set by the app. +* `name` String (optional) - Must be set if `type` is `custom`, otherwise it should be + omitted. +* `items` JumpListItem[] (optional) - Array of [`JumpListItem`](jump-list-item.md) objects if `type` is `tasks` or + `custom`, otherwise it should be omitted. + +**Note:** If a `JumpListCategory` object has neither the `type` nor the `name` +property set then its `type` is assumed to be `tasks`. If the `name` property +is set but the `type` property is omitted then the `type` is assumed to be +`custom`. diff --git a/docs-translations/th-TH/api/structures/jump-list-item.md b/docs-translations/th-TH/api/structures/jump-list-item.md new file mode 100644 index 0000000000..f17d72e0a4 --- /dev/null +++ b/docs-translations/th-TH/api/structures/jump-list-item.md @@ -0,0 +1,28 @@ +# JumpListItem Object + +* `type` String (optional) - One of the following: + * `task` - A task will launch an app with specific arguments. + * `separator` - Can be used to separate items in the standard `Tasks` + category. + * `file` - A file link will open a file using the app that created the + Jump List, for this to work the app must be registered as a handler for + the file type (though it doesn't have to be the default handler). +* `path` String (optional) - Path of the file to open, should only be set if `type` is + `file`. +* `program` String (optional) - Path of the program to execute, usually you should + specify `process.execPath` which opens the current program. Should only be + set if `type` is `task`. +* `args` String (optional) - The command line arguments when `program` is executed. Should + only be set if `type` is `task`. +* `title` String (optional) - The text to be displayed for the item in the Jump List. + Should only be set if `type` is `task`. +* `description` String (optional) - Description of the task (displayed in a tooltip). + Should only be set if `type` is `task`. +* `iconPath` String (optional) - The absolute path to an icon to be displayed in a + Jump List, which can be an arbitrary resource file that contains an icon + (e.g. `.ico`, `.exe`, `.dll`). You can usually specify `process.execPath` to + show the program icon. +* `iconIndex` Number (optional) - The index of the icon in the resource file. If a + resource file contains multiple icons this value can be used to specify the + zero-based index of the icon that should be displayed for this task. If a + resource file contains only one icon, this property should be set to zero. diff --git a/docs-translations/th-TH/api/structures/memory-usage-details.md b/docs-translations/th-TH/api/structures/memory-usage-details.md new file mode 100644 index 0000000000..228a445290 --- /dev/null +++ b/docs-translations/th-TH/api/structures/memory-usage-details.md @@ -0,0 +1,8 @@ +# MemoryUsageDetails Object + +* `count` Number +* `size` Number +* `liveSize` Number +* `decodedSize` Number +* `purgedSize` Number +* `purgeableSize` Number diff --git a/docs-translations/th-TH/api/structures/mime-typed-buffer.md b/docs-translations/th-TH/api/structures/mime-typed-buffer.md new file mode 100644 index 0000000000..dc1a20d28f --- /dev/null +++ b/docs-translations/th-TH/api/structures/mime-typed-buffer.md @@ -0,0 +1,4 @@ +# MimeTypedBuffer Object + +* `mimeType` String - The mimeType of the Buffer that you are sending +* `buffer` Buffer - The actual Buffer content diff --git a/docs-translations/th-TH/api/structures/rectangle.md b/docs-translations/th-TH/api/structures/rectangle.md new file mode 100644 index 0000000000..0cd000699e --- /dev/null +++ b/docs-translations/th-TH/api/structures/rectangle.md @@ -0,0 +1,6 @@ +# Rectangle Object + +* `x` Number - The x coordinate of the origin of the rectangle +* `y` Number - The y coordinate of the origin of the rectangle +* `width` Number +* `height` Number diff --git a/docs-translations/th-TH/api/structures/remove-client-certificate.md b/docs-translations/th-TH/api/structures/remove-client-certificate.md new file mode 100644 index 0000000000..7ec853f163 --- /dev/null +++ b/docs-translations/th-TH/api/structures/remove-client-certificate.md @@ -0,0 +1,5 @@ +# RemoveClientCertificate Object + +* `type` String - `clientCertificate`. +* `origin` String - Origin of the server whose associated client certificate + must be removed from the cache. diff --git a/docs-translations/th-TH/api/structures/remove-password.md b/docs-translations/th-TH/api/structures/remove-password.md new file mode 100644 index 0000000000..28a9ed8ae1 --- /dev/null +++ b/docs-translations/th-TH/api/structures/remove-password.md @@ -0,0 +1,15 @@ +# RemovePassword Object + +* `type` String - `password`. +* `origin` String (optional) - When provided, the authentication info + related to the origin will only be removed otherwise the entire cache + will be cleared. +* `scheme` String (optional) - Scheme of the authentication. + Can be `basic`, `digest`, `ntlm`, `negotiate`. Must be provided if + removing by `origin`. +* `realm` String (optional) - Realm of the authentication. Must be provided if + removing by `origin`. +* `username` String (optional) - Credentials of the authentication. Must be + provided if removing by `origin`. +* `password` String (optional) - Credentials of the authentication. Must be + provided if removing by `origin`. diff --git a/docs-translations/th-TH/api/structures/shortcut-details.md b/docs-translations/th-TH/api/structures/shortcut-details.md new file mode 100644 index 0000000000..e7b272d099 --- /dev/null +++ b/docs-translations/th-TH/api/structures/shortcut-details.md @@ -0,0 +1,15 @@ +# ShortcutDetails Object + +* `target` String - The target to launch from this shortcut. +* `cwd` String (optional) - The working directory. 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` Number (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. diff --git a/docs-translations/th-TH/api/structures/task.md b/docs-translations/th-TH/api/structures/task.md new file mode 100644 index 0000000000..61a28de879 --- /dev/null +++ b/docs-translations/th-TH/api/structures/task.md @@ -0,0 +1,14 @@ +# Task Object + +* `program` String - Path of the program to execute, usually you should + specify `process.execPath` which opens the current program. +* `arguments` String - The command line arguments when `program` is + executed. +* `title` String - The string to be displayed in a JumpList. +* `description` String - Description of this task. +* `iconPath` String - The absolute path to an icon to be displayed in a + JumpList, which can be an arbitrary resource file that contains an icon. You + can usually specify `process.execPath` to show the icon of the program. +* `iconIndex` Number - The icon index in the icon file. If an icon file + consists of two or more icons, set this value to identify the icon. If an + icon file consists of one icon, this value is 0. diff --git a/docs-translations/th-TH/api/structures/thumbar-button.md b/docs-translations/th-TH/api/structures/thumbar-button.md new file mode 100644 index 0000000000..259195852a --- /dev/null +++ b/docs-translations/th-TH/api/structures/thumbar-button.md @@ -0,0 +1,21 @@ +# ThumbarButton Object + +* `icon` [NativeImage](../native-image.md) - The icon showing in thumbnail + toolbar. +* `click` Function +* `tooltip` String (optional) - The text of the button's tooltip. +* `flags` String[] (optional) - Control specific states and behaviors of the + button. By default, it is `['enabled']`. + +The `flags` is an array that can include following `String`s: + +* `enabled` - The button is active and available to the user. +* `disabled` - The button is disabled. It is present, but has a visual state + indicating it will not respond to user action. +* `dismissonclick` - When the button is clicked, the thumbnail window closes + immediately. +* `nobackground` - Do not draw a button border, use only the image. +* `hidden` - The button is not shown to the user. +* `noninteractive` - The button is enabled but not interactive; no pressed + button state is drawn. This value is intended for instances where the button + is used in a notification. diff --git a/docs-translations/th-TH/api/structures/upload-blob.md b/docs-translations/th-TH/api/structures/upload-blob.md new file mode 100644 index 0000000000..be93cacb49 --- /dev/null +++ b/docs-translations/th-TH/api/structures/upload-blob.md @@ -0,0 +1,4 @@ +# UploadBlob Object + +* `type` String - `blob`. +* `blobUUID` String - UUID of blob data to upload. diff --git a/docs-translations/th-TH/api/structures/upload-data.md b/docs-translations/th-TH/api/structures/upload-data.md new file mode 100644 index 0000000000..8e5c07725a --- /dev/null +++ b/docs-translations/th-TH/api/structures/upload-data.md @@ -0,0 +1,6 @@ +# UploadData Object + +* `bytes` Buffer - Content being sent. +* `file` String - Path of file being uploaded. +* `blobUUID` String - UUID of blob data. Use [ses.getBlobData](../session.md#sesgetblobdataidentifier-callback) method + to retrieve the data. diff --git a/docs-translations/th-TH/api/structures/upload-file-system.md b/docs-translations/th-TH/api/structures/upload-file-system.md new file mode 100644 index 0000000000..d62b0a8ba7 --- /dev/null +++ b/docs-translations/th-TH/api/structures/upload-file-system.md @@ -0,0 +1,9 @@ +# UploadFileSystem Object + +* `type` String - `fileSystem`. +* `filsSystemURL` String - FileSystem url to read data for upload. +* `offset` Integer - Defaults to `0`. +* `length` Integer - Number of bytes to read from `offset`. + Defaults to `0`. +* `modificationTime` Double - Last Modification time in + number of seconds sine the UNIX epoch. diff --git a/docs-translations/th-TH/api/structures/upload-file.md b/docs-translations/th-TH/api/structures/upload-file.md new file mode 100644 index 0000000000..8a21973014 --- /dev/null +++ b/docs-translations/th-TH/api/structures/upload-file.md @@ -0,0 +1,9 @@ +# UploadFile Object + +* `type` String - `file`. +* `filePath` String - Path of file to be uploaded. +* `offset` Integer - Defaults to `0`. +* `length` Integer - Number of bytes to read from `offset`. + Defaults to `0`. +* `modificationTime` Double - Last Modification time in + number of seconds sine the UNIX epoch. diff --git a/docs-translations/th-TH/api/structures/upload-raw-data.md b/docs-translations/th-TH/api/structures/upload-raw-data.md new file mode 100644 index 0000000000..4fe162311f --- /dev/null +++ b/docs-translations/th-TH/api/structures/upload-raw-data.md @@ -0,0 +1,4 @@ +# UploadRawData Object + +* `type` String - `rawData`. +* `bytes` Buffer - Data to be uploaded. diff --git a/docs-translations/th-TH/api/synopsis.md b/docs-translations/th-TH/api/synopsis.md new file mode 100644 index 0000000000..71ca497cc2 --- /dev/null +++ b/docs-translations/th-TH/api/synopsis.md @@ -0,0 +1,95 @@ +# Synopsis + +> How to use Node.js and Electron APIs. + +All of [Node.js's built-in modules](https://nodejs.org/api/) are available in +Electron and third-party node modules also fully supported as well (including +the [native modules](../tutorial/using-native-node-modules.md)). + +Electron also provides some extra built-in modules for developing native +desktop applications. Some modules are only available in the main process, some +are only available in the renderer process (web page), and some can be used in +both processes. + +The basic rule is: if a module is [GUI][gui] or low-level system related, then +it should be only available in the main process. You need to be familiar with +the concept of [main process vs. renderer process](../tutorial/quick-start.md#main-process) +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 + +app.on('ready', () => { + 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 +extra ability to use node modules: + +```html + + + + + + +``` + +To run your app, read [Run your app](../tutorial/quick-start.md#run-your-app). + +## Destructuring assignment + +As of 0.37, you can use +[destructuring assignment][destructuring-assignment] to make it easier to use +built-in modules. + +```javascript +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 + +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 +let win + +app.on('ready', () => { + win = new BrowserWindow() + win.loadURL('https://github.com') +}) +``` + +[gui]: https://en.wikipedia.org/wiki/Graphical_user_interface +[destructuring-assignment]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment diff --git a/docs-translations/th-TH/api/system-preferences.md b/docs-translations/th-TH/api/system-preferences.md new file mode 100644 index 0000000000..243c4a04af --- /dev/null +++ b/docs-translations/th-TH/api/system-preferences.md @@ -0,0 +1,239 @@ +# systemPreferences + +> Get system preferences. + +Process: [Main](../glossary.md#main-process) + +```javascript +const {systemPreferences} = require('electron') +console.log(systemPreferences.isDarkMode()) +``` + +## Events + +The `systemPreferences` object emits the following events: + +### Event: 'accent-color-changed' _Windows_ + +Returns: + +* `event` Event +* `newColor` String - The new RGBA color the user assigned to be their system + accent color. + +### Event: 'color-changed' _Windows_ + +Returns: + +* `event` Event + +### Event: 'inverted-color-scheme-changed' _Windows_ + +Returns: + +* `event` Event +* `invertedColorScheme` Boolean - `true` if an inverted color scheme, such as + a high contrast theme, is being used, `false` otherwise. + +## Methods + +### `systemPreferences.isDarkMode()` _macOS_ + +Returns `Boolean` - Whether the system is in Dark Mode. + +### `systemPreferences.isSwipeTrackingFromScrollEventsEnabled()` _macOS_ + +Returns `Boolean` - Whether the Swipe between pages setting is on. + +### `systemPreferences.postNotification(event, userInfo)` _macOS_ + +* `event` String +* `userInfo` Object + +Posts `event` as native notifications of macOS. The `userInfo` is an Object +that contains the user information dictionary sent along with the notification. + +### `systemPreferences.postLocalNotification(event, userInfo)` _macOS_ + +* `event` String +* `userInfo` Object + +Posts `event` as native notifications of macOS. The `userInfo` is an Object +that contains the user information dictionary sent along with the notification. + +### `systemPreferences.subscribeNotification(event, callback)` _macOS_ + +* `event` String +* `callback` Function + * `event` String + * `userInfo` Object + +Subscribes to native notifications of macOS, `callback` will be called with +`callback(event, userInfo)` when the corresponding `event` happens. The +`userInfo` is an Object that contains the user information dictionary sent +along with the notification. + +The `id` of the subscriber is returned, which can be used to unsubscribe the +`event`. + +Under the hood this API subscribes to `NSDistributedNotificationCenter`, +example values of `event` are: + +* `AppleInterfaceThemeChangedNotification` +* `AppleAquaColorVariantChanged` +* `AppleColorPreferencesChangedNotification` +* `AppleShowScrollBarsSettingChanged` + +### `systemPreferences.unsubscribeNotification(id)` _macOS_ + +* `id` Integer + +Removes the subscriber with `id`. + +### `systemPreferences.subscribeLocalNotification(event, callback)` _macOS_ + +* `event` String +* `callback` Function + * `event` String + * `userInfo` Object + +Same as `subscribeNotification`, but uses `NSNotificationCenter` for local defaults. +This is necessary for events such as `NSUserDefaultsDidChangeNotification` + +### `systemPreferences.unsubscribeLocalNotification(id)` _macOS_ + +* `id` Integer + +Same as `unsubscribeNotification`, but removes the subscriber from `NSNotificationCenter`. + +### `systemPreferences.getUserDefault(key, type)` _macOS_ + +* `key` String +* `type` String - Can be `string`, `boolean`, `integer`, `float`, `double`, + `url`, `array`, `dictionary` + +Get the value of `key` in system preferences. + +This API uses `NSUserDefaults` on macOS. Some popular `key` and `type`s are: + +* `AppleInterfaceStyle`: `string` +* `AppleAquaColorVariant`: `integer` +* `AppleHighlightColor`: `string` +* `AppleShowScrollBars`: `string` +* `NSNavRecentPlaces`: `array` +* `NSPreferredWebServices`: `dictionary` +* `NSUserDictionaryReplacementItems`: `array` + +### `systemPreferences.setUserDefault(key, type, value)` _macOS_ + +* `key` String +* `type` String - See [`getUserDefault`][#systempreferencesgetuserdefaultkey-type-macos] +* `value` String + +Set the value of `key` in system preferences. + +Note that `type` should match actual type of `value`. An exception is thrown +if they don't. + +This API uses `NSUserDefaults` on macOS. Some popular `key` and `type`s are: + +* `ApplePressAndHoldEnabled`: `boolean` + +### `systemPreferences.isAeroGlassEnabled()` _Windows_ + +This method returns `true` if [DWM composition][dwm-composition] (Aero Glass) is +enabled, and `false` otherwise. + +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 +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 +} + +// Create the window. +let win = new BrowserWindow(browserOptions) + +// Navigate. +if (browserOptions.transparent) { + win.loadURL(`file://${__dirname}/index.html`) +} else { + // No transparency, so we load a fallback that uses basic styles. + win.loadURL(`file://${__dirname}/fallback.html`) +} +``` + +[dwm-composition]:https://msdn.microsoft.com/en-us/library/windows/desktop/aa969540.aspx + +### `systemPreferences.getAccentColor()` _Windows_ + +Returns `String` - The users current system wide accent color preference in RGBA +hexadecimal form. + +```js +const color = systemPreferences.getAccentColor() // `"aabbccdd"` +const red = color.substr(0, 2) // "aa" +const green = color.substr(2, 2) // "bb" +const blue = color.substr(4, 2) // "cc" +const alpha = color.substr(6, 2) // "dd" +``` + +### `systemPreferences.getColor(color)` _Windows_ + +* `color` String - One of the following values: + * `3d-dark-shadow` - Dark shadow for three-dimensional display elements. + * `3d-face` - Face color for three-dimensional display elements and for dialog + box backgrounds. + * `3d-highlight` - Highlight color for three-dimensional display elements. + * `3d-light` - Light color for three-dimensional display elements. + * `3d-shadow` - Shadow color for three-dimensional display elements. + * `active-border` - Active window border. + * `active-caption` - Active window title bar. Specifies the left side color in + the color gradient of an active window's title bar if the gradient effect is + enabled. + * `active-caption-gradient` - Right side color in the color gradient of an + active window's title bar. + * `app-workspace` - Background color of multiple document interface (MDI) + applications. + * `button-text` - Text on push buttons. + * `caption-text` - Text in caption, size box, and scroll bar arrow box. + * `desktop` - Desktop background color. + * `disabled-text` - Grayed (disabled) text. + * `highlight` - Item(s) selected in a control. + * `highlight-text` - Text of item(s) selected in a control. + * `hotlight` - Color for a hyperlink or hot-tracked item. + * `inactive-border` - Inactive window border. + * `inactive-caption` - Inactive window caption. Specifies the left side color + in the color gradient of an inactive window's title bar if the gradient + effect is enabled. + * `inactive-caption-gradient` - Right side color in the color gradient of an + inactive window's title bar. + * `inactive-caption-text` - Color of text in an inactive caption. + * `info-background` - Background color for tooltip controls. + * `info-text` - Text color for tooltip controls. + * `menu` - Menu background. + * `menu-highlight` - The color used to highlight menu items when the menu + appears as a flat menu. + * `menubar` - The background color for the menu bar when menus appear as flat + menus. + * `menu-text` - Text in menus. + * `scrollbar` - Scroll bar gray area. + * `window` - Window background. + * `window-frame` - Window frame. + * `window-text` - Text in windows. + +Returns `String` - The system color setting in RGB hexadecimal form (`#ABCDEF`). +See the [Windows docs][windows-colors] for more details. + +### `systemPreferences.isInvertedColorScheme()` _Windows_ + +Returns `Boolean` - `true` if an inverted color scheme, such as a high contrast +theme, is active, `false` otherwise. + +[windows-colors]:https://msdn.microsoft.com/en-us/library/windows/desktop/ms724371(v=vs.85).aspx diff --git a/docs-translations/th-TH/api/tray.md b/docs-translations/th-TH/api/tray.md new file mode 100644 index 0000000000..0be9702986 --- /dev/null +++ b/docs-translations/th-TH/api/tray.md @@ -0,0 +1,247 @@ +## Class: Tray + +> Add icons and context menus to the system's notification area. + +Process: [Main](../glossary.md#main-process) + +`Tray` is an [EventEmitter][event-emitter]. + +```javascript +const {app, Menu, Tray} = require('electron') + +let tray = null +app.on('ready', () => { + tray = new Tray('/path/to/my/icon') + const contextMenu = Menu.buildFromTemplate([ + {label: 'Item1', type: 'radio'}, + {label: 'Item2', type: 'radio'}, + {label: 'Item3', type: 'radio', checked: true}, + {label: 'Item4', type: 'radio'} + ]) + tray.setToolTip('This is my application.') + tray.setContextMenu(contextMenu) +}) +``` + +__Platform limitations:__ + +* On Linux the app indicator will be used if it is supported, otherwise + `GtkStatusIcon` will be used instead. +* On Linux distributions that only have app indicator support, you have to + install `libappindicator1` to make the tray icon work. +* App indicator will only be shown when it has a context menu. +* When app indicator is used on Linux, the `click` event is ignored. +* On Linux in order for changes made to individual `MenuItem`s to take effect, + you have to call `setContextMenu` again. For example: + +```javascript +const {app, Menu, Tray} = require('electron') + +let appIcon = null +app.on('ready', () => { + 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[1].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. + +If you want to keep exact same behaviors on all platforms, you should not +rely on the `click` event and always attach a context menu to the tray icon. + + +### `new Tray(image)` + +* `image` ([NativeImage](native-image.md) | String) + +Creates a new tray icon associated with the `image`. + +### Instance Events + +The `Tray` module emits the following events: + +#### Event: 'click' + +* `event` Event + * `altKey` Boolean + * `shiftKey` Boolean + * `ctrlKey` Boolean + * `metaKey` Boolean +* `bounds` [Rectangle](structures/rectangle.md) - The bounds of tray icon + +Emitted when the tray icon is clicked. + +#### Event: 'right-click' _macOS_ _Windows_ + +* `event` Event + * `altKey` Boolean + * `shiftKey` Boolean + * `ctrlKey` Boolean + * `metaKey` Boolean +* `bounds` [Rectangle](structures/rectangle.md) - The bounds of tray icon + +Emitted when the tray icon is right clicked. + +#### Event: 'double-click' _macOS_ _Windows_ + +* `event` Event + * `altKey` Boolean + * `shiftKey` Boolean + * `ctrlKey` Boolean + * `metaKey` Boolean +* `bounds` [Rectangle](structures/rectangle.md) - The bounds of tray icon + +Emitted when the tray icon is double clicked. + +#### Event: 'balloon-show' _Windows_ + +Emitted when the tray balloon shows. + +#### Event: 'balloon-click' _Windows_ + +Emitted when the tray balloon is clicked. + +#### Event: 'balloon-closed' _Windows_ + +Emitted when the tray balloon is closed because of timeout or user manually +closes it. + +#### Event: 'drop' _macOS_ + +Emitted when any dragged items are dropped on the tray icon. + +#### Event: 'drop-files' _macOS_ + +* `event` Event +* `files` String[] - The paths of the dropped files. + +Emitted when dragged files are dropped in the tray icon. + +#### Event: 'drop-text' _macOS_ + +* `event` Event +* `text` String - the dropped text string + +Emitted when dragged text is dropped in the tray icon. + +#### Event: 'drag-enter' _macOS_ + +Emitted when a drag operation enters the tray icon. + +#### Event: 'drag-leave' _macOS_ + +Emitted when a drag operation exits the tray icon. + +#### Event: 'drag-end' _macOS_ + +Emitted when a drag operation ends on the tray or ends at another location. + +### Instance Methods + +The `Tray` class has the following methods: + +#### `tray.destroy()` + +Destroys the tray icon immediately. + +#### `tray.setImage(image)` + +* `image` ([NativeImage](native-image.md) | String) + +Sets the `image` associated with this tray icon. + +#### `tray.setPressedImage(image)` _macOS_ + +* `image` [NativeImage](native-image.md) + +Sets the `image` associated with this tray icon when pressed on macOS. + +#### `tray.setToolTip(toolTip)` + +* `toolTip` String + +Sets the hover text for this tray icon. + +#### `tray.setTitle(title)` _macOS_ + +* `title` String + +Sets the title displayed aside of the tray icon in the status bar. + +#### `tray.setHighlightMode(mode)` _macOS_ + +* `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 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. + +```javascript +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_ + +* `options` Object + * `icon` ([NativeImage](native-image.md) | String) - (optional) + * `title` String - (optional) + * `content` String - (optional) + +Displays a tray balloon. + +#### `tray.popUpContextMenu([menu, position])` _macOS_ _Windows_ + +* `menu` Menu (optional) +* `position` Object (optional) - The pop up position. + * `x` Integer + * `y` Integer + +Pops up the context menu of the tray icon. When `menu` is passed, the `menu` will +be shown instead of the tray icon's context menu. + +The `position` is only available on Windows, and it is (0, 0) by default. + +#### `tray.setContextMenu(menu)` + +* `menu` Menu + +Sets the context menu for this icon. + +#### `tray.getBounds()` _macOS_ _Windows_ + +Returns [`Rectangle`](structures/rectangle.md) + +The `bounds` of this tray icon as `Object`. + +#### `tray.isDestroyed()` + +Returns `Boolean` - Whether the tray icon is destroyed. + +[event-emitter]: https://nodejs.org/api/events.html#events_class_eventemitter diff --git a/docs-translations/th-TH/api/web-contents.md b/docs-translations/th-TH/api/web-contents.md new file mode 100644 index 0000000000..77cd2d4c6b --- /dev/null +++ b/docs-translations/th-TH/api/web-contents.md @@ -0,0 +1,1241 @@ +# webContents + +> Render and control web pages. + +Process: [Main](../glossary.md#main-process) + +`webContents` is an +[EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter). +It is responsible for rendering and controlling a web page and is a property of +the [`BrowserWindow`](browser-window.md) object. An example of accessing the +`webContents` object: + +```javascript +const {BrowserWindow} = require('electron') + +let win = new BrowserWindow({width: 800, height: 1500}) +win.loadURL('http://github.com') + +let contents = win.webContents +console.log(contents) +``` + +## Methods + +These methods can be accessed from the `webContents` module: + +```javascript +const {webContents} = require('electron') +console.log(webContents) +``` + +### `webContents.getAllWebContents()` + +Returns `WebContents[]` - An array of all `WebContents` instances. This will contain web contents +for all windows, webviews, opened devtools, and devtools extension background pages. + +### `webContents.getFocusedWebContents()` + +Returns `WebContents` - The web contents that is focused in this application, otherwise +returns `null`. + +### `webContents.fromId(id)` + +* `id` Integer + +Returns `WebContents` - A WebContents instance with the given ID. + +## Class: WebContents + +> Render and control the contents of a BrowserWindow instance. + +Process: [Main](../glossary.md#main-process) + +### Instance Events + +#### Event: 'did-finish-load' + +Emitted when the navigation is done, i.e. the spinner of the tab has stopped +spinning, and the `onload` event was dispatched. + +#### Event: 'did-fail-load' + +Returns: + +* `event` Event +* `errorCode` Integer +* `errorDescription` String +* `validatedURL` String +* `isMainFrame` Boolean + +This event is like `did-finish-load` but emitted when the load failed or was +cancelled, e.g. `window.stop()` is invoked. +The full list of error codes and their meaning is available [here](https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h). +Note that redirect responses will emit `errorCode` -3; you may want to ignore +that error explicitly. + +#### Event: 'did-frame-finish-load' + +Returns: + +* `event` Event +* `isMainFrame` Boolean + +Emitted when a frame has done navigation. + +#### Event: 'did-start-loading' + +Corresponds to the points in time when the spinner of the tab started spinning. + +#### Event: 'did-stop-loading' + +Corresponds to the points in time when the spinner of the tab stopped spinning. + +#### Event: 'did-get-response-details' + +Returns: + +* `event` Event +* `status` Boolean +* `newURL` String +* `originalURL` String +* `httpResponseCode` Integer +* `requestMethod` String +* `referrer` String +* `headers` Object +* `resourceType` String + +Emitted when details regarding a requested resource are available. +`status` indicates the socket connection to download the resource. + +#### Event: 'did-get-redirect-request' + +Returns: + +* `event` Event +* `oldURL` String +* `newURL` String +* `isMainFrame` Boolean +* `httpResponseCode` Integer +* `requestMethod` String +* `referrer` String +* `headers` Object + +Emitted when a redirect is received while requesting a resource. + +#### Event: 'dom-ready' + +Returns: + +* `event` Event + +Emitted when the document in the given frame is loaded. + +#### Event: 'page-favicon-updated' + +Returns: + +* `event` Event +* `favicons` String[] - Array of URLs + +Emitted when page receives favicon urls. + +#### Event: 'new-window' + +Returns: + +* `event` Event +* `url` String +* `frameName` String +* `disposition` String - Can be `default`, `foreground-tab`, `background-tab`, + `new-window`, `save-to-disk` and `other`. +* `options` Object - The options which will be used for creating the new + `BrowserWindow`. +* `additionalFeatures` String[] - The non-standard features (features not handled + by Chromium or Electron) given to `window.open()`. + +Emitted when the page requests to open a new window for a `url`. It could be +requested by `window.open` or an external link like `
`. + +By default a new `BrowserWindow` will be created for the `url`. + +Calling `event.preventDefault()` will prevent creating new windows. In such case, the +`event.newGuest` may be set with a reference to a `BrowserWindow` instance to make it +used by the Electron's runtime. + +#### Event: 'will-navigate' + +Returns: + +* `event` Event +* `url` String + +Emitted when a user or the page wants to start navigation. It can happen when +the `window.location` object is changed or a user clicks a link in the page. + +This event will not emit when the navigation is started programmatically with +APIs like `webContents.loadURL` and `webContents.back`. + +It is also not emitted for in-page navigations, such as clicking anchor links +or updating the `window.location.hash`. Use `did-navigate-in-page` event for +this purpose. + +Calling `event.preventDefault()` will prevent the navigation. + +#### Event: 'did-navigate' + +Returns: + +* `event` Event +* `url` String + +Emitted when a navigation is done. + +This event is not emitted for in-page navigations, such as clicking anchor links +or updating the `window.location.hash`. Use `did-navigate-in-page` event for +this purpose. + +#### Event: 'did-navigate-in-page' + +Returns: + +* `event` Event +* `url` String +* `isMainFrame` Boolean + +Emitted when an in-page navigation happened. + +When in-page navigation happens, the page URL changes but does not cause +navigation outside of the page. Examples of this occurring are when anchor links +are clicked or when the DOM `hashchange` event is triggered. + +#### Event: 'crashed' + +Returns: + +* `event` Event +* `killed` Boolean + +Emitted when the renderer process crashes or is killed. + +#### Event: 'plugin-crashed' + +Returns: + +* `event` Event +* `name` String +* `version` String + +Emitted when a plugin process has crashed. + +#### Event: 'destroyed' + +Emitted when `webContents` is destroyed. + +#### Event: 'before-input-event' + +Returns: + +* `event` Event +* `input` Object - Input properties + * `type` String - Either `keyUp` or `keyDown` + * `key` String - Equivalent to [KeyboardEvent.key][keyboardevent] + * `isAutoRepeat` Boolean - Equivalent to [KeyboardEvent.repeat][keyboardevent] + * `shift` Boolean - Equivalent to [KeyboardEvent.shiftKey][keyboardevent] + * `control` Boolean - Equivalent to [KeyboardEvent.controlKey][keyboardevent] + * `alt` Boolean - Equivalent to [KeyboardEvent.altKey][keyboardevent] + * `meta` Boolean - Equivalent to [KeyboardEvent.metaKey][keyboardevent] + +Emitted before dispatching the `keydown` and `keyup` events in the page. +Calling `event.preventDefault` will prevent the page `keydown`/`keyup` events +from being dispatched. + +#### Event: 'devtools-opened' + +Emitted when DevTools is opened. + +#### Event: 'devtools-closed' + +Emitted when DevTools is closed. + +#### Event: 'devtools-focused' + +Emitted when DevTools is focused / opened. + +#### Event: 'certificate-error' + +Returns: + +* `event` Event +* `url` String +* `error` String - The error code +* `certificate` [Certificate](structures/certificate.md) +* `callback` Function + * `isTrusted` Boolean - Indicates whether the certificate can be considered trusted + +Emitted when failed to verify the `certificate` for `url`. + +The usage is the same with [the `certificate-error` event of +`app`](app.md#event-certificate-error). + +#### Event: 'select-client-certificate' + +Returns: + +* `event` Event +* `url` URL +* `certificateList` [Certificate[]](structures/certificate.md) +* `callback` Function + * `certificate` [Certificate](structures/certificate.md) - Must be a certificate from the given list + +Emitted when a client certificate is requested. + +The usage is the same with [the `select-client-certificate` event of +`app`](app.md#event-select-client-certificate). + +#### Event: 'login' + +Returns: + +* `event` Event +* `request` Object + * `method` String + * `url` URL + * `referrer` URL +* `authInfo` Object + * `isProxy` Boolean + * `scheme` String + * `host` String + * `port` Integer + * `realm` String +* `callback` Function + * `username` String + * `password` String + +Emitted when `webContents` wants to do basic auth. + +The usage is the same with [the `login` event of `app`](app.md#event-login). + +#### Event: 'found-in-page' + +Returns: + +* `event` Event +* `result` Object + * `requestId` Integer + * `activeMatchOrdinal` Integer - Position of the active match. + * `matches` Integer - Number of Matches. + * `selectionArea` Object - Coordinates of first match region. + +Emitted when a result is available for +[`webContents.findInPage`] request. + +#### Event: 'media-started-playing' + +Emitted when media starts playing. + +#### Event: 'media-paused' + +Emitted when media is paused or done playing. + +#### Event: 'did-change-theme-color' + +Emitted when a page's theme color changes. This is usually due to encountering +a meta tag: + +```html + +``` + +#### Event: 'update-target-url' + +Returns: + +* `event` Event +* `url` String + +Emitted when mouse moves over a link or the keyboard moves the focus to a link. + +#### Event: 'cursor-changed' + +Returns: + +* `event` Event +* `type` String +* `image` NativeImage (optional) +* `scale` Float (optional) - scaling factor for the custom cursor +* `size` Object (optional) - the size of the `image` + * `width` Integer + * `height` Integer +* `hotspot` Object (optional) - coordinates of the custom cursor's hotspot + * `x` Integer - x coordinate + * `y` Integer - y coordinate + +Emitted when the cursor's type changes. The `type` parameter can be `default`, +`crosshair`, `pointer`, `text`, `wait`, `help`, `e-resize`, `n-resize`, +`ne-resize`, `nw-resize`, `s-resize`, `se-resize`, `sw-resize`, `w-resize`, +`ns-resize`, `ew-resize`, `nesw-resize`, `nwse-resize`, `col-resize`, +`row-resize`, `m-panning`, `e-panning`, `n-panning`, `ne-panning`, `nw-panning`, +`s-panning`, `se-panning`, `sw-panning`, `w-panning`, `move`, `vertical-text`, +`cell`, `context-menu`, `alias`, `progress`, `nodrop`, `copy`, `none`, +`not-allowed`, `zoom-in`, `zoom-out`, `grab`, `grabbing`, `custom`. + +If the `type` parameter is `custom`, the `image` parameter will hold the custom +cursor image in a `NativeImage`, and `scale`, `size` and `hotspot` will hold +additional information about the custom cursor. + +#### Event: 'context-menu' + +Returns: + +* `event` Event +* `params` Object + * `x` Integer - x coordinate + * `y` Integer - y coordinate + * `linkURL` String - URL of the link that encloses the node the context menu + was invoked on. + * `linkText` String - Text associated with the link. May be an empty + string if the contents of the link are an image. + * `pageURL` String - URL of the top level page that the context menu was + invoked on. + * `frameURL` String - URL of the subframe that the context menu was invoked + on. + * `srcURL` String - Source URL for the element that the context menu + was invoked on. Elements with source URLs are images, audio and video. + * `mediaType` String - Type of the node the context menu was invoked on. Can + be `none`, `image`, `audio`, `video`, `canvas`, `file` or `plugin`. + * `hasImageContents` Boolean - Whether the context menu was invoked on an image + which has non-empty contents. + * `isEditable` Boolean - Whether the context is editable. + * `selectionText` String - Text of the selection that the context menu was + invoked on. + * `titleText` String - Title or alt text of the selection that the context + was invoked on. + * `misspelledWord` String - The misspelled word under the cursor, if any. + * `frameCharset` String - The character encoding of the frame on which the + menu was invoked. + * `inputFieldType` String - If the context menu was invoked on an input + field, the type of that field. Possible values are `none`, `plainText`, + `password`, `other`. + * `menuSourceType` String - Input source that invoked the context menu. + Can be `none`, `mouse`, `keyboard`, `touch`, `touchMenu`. + * `mediaFlags` Object - The flags for the media element the context menu was + invoked on. + * `inError` Boolean - Whether the media element has crashed. + * `isPaused` Boolean - Whether the media element is paused. + * `isMuted` Boolean - Whether the media element is muted. + * `hasAudio` Boolean - Whether the media element has audio. + * `isLooping` Boolean - Whether the media element is looping. + * `isControlsVisible` Boolean - Whether the media element's controls are + visible. + * `canToggleControls` Boolean - Whether the media element's controls are + toggleable. + * `canRotate` Boolean - Whether the media element can be rotated. + * `editFlags` Object - These flags indicate whether the renderer believes it + is able to perform the corresponding action. + * `canUndo` Boolean - Whether the renderer believes it can undo. + * `canRedo` Boolean - Whether the renderer believes it can redo. + * `canCut` Boolean - Whether the renderer believes it can cut. + * `canCopy` Boolean - Whether the renderer believes it can copy + * `canPaste` Boolean - Whether the renderer believes it can paste. + * `canDelete` Boolean - Whether the renderer believes it can delete. + * `canSelectAll` Boolean - Whether the renderer believes it can select all. + +Emitted when there is a new context menu that needs to be handled. + +#### Event: 'select-bluetooth-device' + +Returns: + +* `event` Event +* `devices` [BluetoothDevice[]](structures/bluetooth-device.md) +* `callback` Function + * `deviceId` String + +Emitted when bluetooth device needs to be selected on call to +`navigator.bluetooth.requestDevice`. To use `navigator.bluetooth` api +`webBluetooth` should be enabled. If `event.preventDefault` is not called, +first available device will be selected. `callback` should be called with +`deviceId` to be selected, passing empty string to `callback` will +cancel the request. + +```javascript +const {app, webContents} = require('electron') +app.commandLine.appendSwitch('enable-web-bluetooth') + +app.on('ready', () => { + webContents.on('select-bluetooth-device', (event, deviceList, callback) => { + event.preventDefault() + let result = deviceList.find((device) => { + return device.deviceName === 'test' + }) + if (!result) { + callback('') + } else { + callback(result.deviceId) + } + }) +}) +``` + +#### Event: 'paint' + +Returns: + +* `event` Event +* `dirtyRect` [Rectangle](structures/rectangle.md) +* `image` [NativeImage](native-image.md) - The image data of the whole frame. + +Emitted when a new frame is generated. Only the dirty area is passed in the +buffer. + +```javascript +const {BrowserWindow} = require('electron') + +let win = new BrowserWindow({webPreferences: {offscreen: true}}) +win.webContents.on('paint', (event, dirty, image) => { + // updateBitmap(dirty, image.getBitmap()) +}) +win.loadURL('http://github.com') +``` + +#### Event: 'devtools-reload-page' + +Emitted when the devtools window instructs the webContents to reload + +### Instance Methods + +#### `contents.loadURL(url[, options])` + +* `url` String +* `options` Object (optional) + * `httpReferrer` String (optional) - A HTTP Referrer url. + * `userAgent` String (optional) - A user agent originating the request. + * `extraHeaders` String (optional) - Extra headers separated by "\n" + * `postData` ([UploadRawData](structures/upload-raw-data.md) | [UploadFile](structures/upload-file.md) | [UploadFileSystem](structures/upload-file-system.md) | [UploadBlob](structures/upload-blob.md))[] - (optional) + +Loads the `url` in the window. The `url` must contain the protocol prefix, +e.g. the `http://` or `file://`. If the load should bypass http cache then +use the `pragma` header to achieve it. + +```javascript +const {webContents} = require('electron') +const options = {extraHeaders: 'pragma: no-cache\n'} +webContents.loadURL('https://github.com', options) +``` + +#### `contents.downloadURL(url)` + +* `url` String + +Initiates a download of the resource at `url` without navigating. The +`will-download` event of `session` will be triggered. + +#### `contents.getURL()` + +Returns `String` - The URL of the current web page. + +```javascript +const {BrowserWindow} = require('electron') +let win = new BrowserWindow({width: 800, height: 600}) +win.loadURL('http://github.com') + +let currentURL = win.webContents.getURL() +console.log(currentURL) +``` + +#### `contents.getTitle()` + +Returns `String` - The title of the current web page. + +#### `contents.isDestroyed()` + +Returns `Boolean` - Whether the web page is destroyed. + +#### `contents.isFocused()` + +Returns `Boolean` - Whether the web page is focused. + +#### `contents.isLoading()` + +Returns `Boolean` - Whether web page is still loading resources. + +#### `contents.isLoadingMainFrame()` + +Returns `Boolean` - Whether the main frame (and not just iframes or frames within it) is +still loading. + +#### `contents.isWaitingForResponse()` + +Returns `Boolean` - Whether the web page is waiting for a first-response from the main +resource of the page. + +#### `contents.stop()` + +Stops any pending navigation. + +#### `contents.reload()` + +Reloads the current web page. + +#### `contents.reloadIgnoringCache()` + +Reloads current page and ignores cache. + +#### `contents.canGoBack()` + +Returns `Boolean` - Whether the browser can go back to previous web page. + +#### `contents.canGoForward()` + +Returns `Boolean` - Whether the browser can go forward to next web page. + +#### `contents.canGoToOffset(offset)` + +* `offset` Integer + +Returns `Boolean` - Whether the web page can go to `offset`. + +#### `contents.clearHistory()` + +Clears the navigation history. + +#### `contents.goBack()` + +Makes the browser go back a web page. + +#### `contents.goForward()` + +Makes the browser go forward a web page. + +#### `contents.goToIndex(index)` + +* `index` Integer + +Navigates browser to the specified absolute web page index. + +#### `contents.goToOffset(offset)` + +* `offset` Integer + +Navigates to the specified offset from the "current entry". + +#### `contents.isCrashed()` + +Returns `Boolean` - Whether the renderer process has crashed. + +#### `contents.setUserAgent(userAgent)` + +* `userAgent` String + +Overrides the user agent for this web page. + +#### `contents.getUserAgent()` + +Returns `String` - The user agent for this web page. + +#### `contents.insertCSS(css)` + +* `css` String + +Injects CSS into the current web page. + +#### `contents.executeJavaScript(code[, userGesture, callback])` + +* `code` String +* `userGesture` Boolean (optional) +* `callback` Function (optional) - Called after script has been executed. + * `result` Any + +Returns `Promise` - A promise that resolves with the result of the executed code +or is rejected if the result of the code is a rejected promise. + +Evaluates `code` in page. + +In the browser window some HTML APIs like `requestFullScreen` can only be +invoked by a gesture from the user. Setting `userGesture` to `true` will remove +this limitation. + +If the result of the executed code is a promise the callback result will be the +resolved value of the promise. We recommend that you use the returned Promise +to handle code that results in a Promise. + +```js +contents.executeJavaScript('fetch("https://jsonplaceholder.typicode.com/users/1").then(resp => resp.json())', true) + .then((result) => { + console.log(result) // Will be the JSON object from the fetch call + }) +``` + +#### `contents.setAudioMuted(muted)` + +* `muted` Boolean + +Mute the audio on the current web page. + +#### `contents.isAudioMuted()` + +Returns `Boolean` - Whether this page has been muted. + +#### `contents.setZoomFactor(factor)` + +* `factor` Number - Zoom factor. + +Changes the zoom factor to the specified factor. Zoom factor is +zoom percent divided by 100, so 300% = 3.0. + +#### `contents.getZoomFactor(callback)` + +* `callback` Function + * `zoomFactor` Number + +Sends a request to get current zoom factor, the `callback` will be called with +`callback(zoomFactor)`. + +#### `contents.setZoomLevel(level)` + +* `level` Number - Zoom level + +Changes the zoom level to the specified level. The original size is 0 and each +increment above or below represents zooming 20% larger or smaller to default +limits of 300% and 50% of original size, respectively. + +#### `contents.getZoomLevel(callback)` + +* `callback` Function + * `zoomLevel` Number + +Sends a request to get current zoom level, the `callback` will be called with +`callback(zoomLevel)`. + +#### `contents.setZoomLevelLimits(minimumLevel, maximumLevel)` + +* `minimumLevel` Number +* `maximumLevel` Number + +**Deprecated:** Call `setVisualZoomLevelLimits` instead to set the visual zoom +level limits. This method will be removed in Electron 2.0. + +#### `contents.setVisualZoomLevelLimits(minimumLevel, maximumLevel)` + +* `minimumLevel` Number +* `maximumLevel` Number + +Sets the maximum and minimum pinch-to-zoom level. + +#### `contents.setLayoutZoomLevelLimits(minimumLevel, maximumLevel)` + +* `minimumLevel` Number +* `maximumLevel` Number + +Sets the maximum and minimum layout-based (i.e. non-visual) zoom level. + +#### `contents.undo()` + +Executes the editing command `undo` in web page. + +#### `contents.redo()` + +Executes the editing command `redo` in web page. + +#### `contents.cut()` + +Executes the editing command `cut` in web page. + +#### `contents.copy()` + +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. + +#### `contents.pasteAndMatchStyle()` + +Executes the editing command `pasteAndMatchStyle` in web page. + +#### `contents.delete()` + +Executes the editing command `delete` in web page. + +#### `contents.selectAll()` + +Executes the editing command `selectAll` in web page. + +#### `contents.unselect()` + +Executes the editing command `unselect` in web page. + +#### `contents.replace(text)` + +* `text` String + +Executes the editing command `replace` in web page. + +#### `contents.replaceMisspelling(text)` + +* `text` String + +Executes the editing command `replaceMisspelling` in web page. + +#### `contents.insertText(text)` + +* `text` String + +Inserts `text` to the focused element. + +#### `contents.findInPage(text[, options])` + +* `text` String - Content to be searched, must not be empty. +* `options` Object (optional) + * `forward` Boolean - (optional) Whether to search forward or backward, defaults to `true`. + * `findNext` Boolean - (optional) Whether the operation is first request or a follow up, + defaults to `false`. + * `matchCase` Boolean - (optional) Whether search should be case-sensitive, + defaults to `false`. + * `wordStart` Boolean - (optional) Whether to look only at the start of words. + defaults to `false`. + * `medialCapitalAsWordStart` Boolean - (optional) When combined with `wordStart`, + accepts a match in the middle of a word if the match begins with an + uppercase letter followed by a lowercase or non-letter. + Accepts several other intra-word matches, defaults to `false`. + +Starts a request to find all matches for the `text` in the web page and returns +an `Integer` representing the request id used for the request. The result of +the request can be obtained by subscribing to +[`found-in-page`](web-contents.md#event-found-in-page) event. + +#### `contents.stopFindInPage(action)` + +* `action` String - Specifies the action to take place when ending + [`webContents.findInPage`] request. + * `clearSelection` - Clear the selection. + * `keepSelection` - Translate the selection into a normal selection. + * `activateSelection` - Focus and click the selection node. + +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') +}) + +const requestId = webContents.findInPage('api') +console.log(requestId) +``` + +#### `contents.capturePage([rect, ]callback)` + +* `rect` [Rectangle](structures/rectangle.md) (optional) - The area of the page to be captured +* `callback` Function + * `image` [NativeImage](native-image.md) + +Captures a snapshot of the page within `rect`. Upon completion `callback` will +be called with `callback(image)`. The `image` is an instance of +[NativeImage](native-image.md) that stores data of the snapshot. Omitting +`rect` will capture the whole visible page. + +#### `contents.hasServiceWorker(callback)` + +* `callback` Function + * `hasWorker` Boolean + +Checks if any ServiceWorker is registered and returns a boolean as +response to `callback`. + +#### `contents.unregisterServiceWorker(callback)` + +* `callback` Function + * `success` Boolean + +Unregisters any ServiceWorker if present and returns a boolean as +response to `callback` when the JS promise is fulfilled or false +when the JS promise is rejected. + +#### `contents.print([options])` + +* `options` Object (optional) + * `silent` Boolean - Don't ask user for print settings. Default is `false`. + * `printBackground` Boolean - Also prints the background color and image of + the web page. Default is `false`. + +Prints window's web page. When `silent` is set to `true`, Electron will pick +up system's default printer and default settings for printing. + +Calling `window.print()` in web page is equivalent to calling +`webContents.print({silent: false, printBackground: false})`. + +Use `page-break-before: always; ` CSS style to force to print to a new page. + +#### `contents.printToPDF(options, callback)` + +* `options` Object + * `marginsType` Integer - (optional) Specifies the type of margins to use. Uses 0 for + default margin, 1 for no margin, and 2 for minimum margin. + * `pageSize` String - (optional) Specify page size of the generated PDF. Can be `A3`, + `A4`, `A5`, `Legal`, `Letter`, `Tabloid` or an Object containing `height` + and `width` in microns. + * `printBackground` Boolean - (optional) Whether to print CSS backgrounds. + * `printSelectionOnly` Boolean - (optional) Whether to print selection only. + * `landscape` Boolean - (optional) `true` for landscape, `false` for portrait. +* `callback` Function + * `error` Error + * `data` Buffer + +Prints window's web page as PDF with Chromium's preview printing custom +settings. + +The `callback` will be called with `callback(error, data)` on completion. The +`data` is a `Buffer` that contains the generated PDF data. + +The `landscape` will be ignored if `@page` CSS at-rule is used in the web page. + +By default, an empty `options` will be regarded as: + +```javascript +{ + marginsType: 0, + printBackground: false, + printSelectionOnly: false, + landscape: false +} +``` + +Use `page-break-before: always; ` CSS style to force to print to a new page. + +An example of `webContents.printToPDF`: + +```javascript +const {BrowserWindow} = require('electron') +const fs = require('fs') + +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 + fs.writeFile('/tmp/print.pdf', data, (error) => { + if (error) throw error + console.log('Write PDF successfully.') + }) + }) +}) +``` + +#### `contents.addWorkSpace(path)` + +* `path` String + +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) +}) +``` + +#### `contents.removeWorkSpace(path)` + +* `path` String + +Removes the specified path from DevTools workspace. + +#### `contents.openDevTools([options])` + +* `options` Object (optional) + * `mode` String - Opens the devtools with specified dock state, can be + `right`, `bottom`, `undocked`, `detach`. Defaults to last used dock state. + In `undocked` mode it's possible to dock back. In `detach` mode it's not. + +Opens the devtools. + +#### `contents.closeDevTools()` + +Closes the devtools. + +#### `contents.isDevToolsOpened()` + +Returns `Boolean` - Whether the devtools is opened. + +#### `contents.isDevToolsFocused()` + +Returns `Boolean` - Whether the devtools view is focused . + +#### `contents.toggleDevTools()` + +Toggles the developer tools. + +#### `contents.inspectElement(x, y)` + +* `x` Integer +* `y` Integer + +Starts inspecting element at position (`x`, `y`). + +#### `contents.inspectServiceWorker()` + +Opens the developer tools for the service worker context. + +#### `contents.send(channel[, arg1][, arg2][, ...])` + +* `channel` String +* `...args` any[] + +Send an asynchronous message to renderer process via `channel`, you can also +send arbitrary arguments. Arguments will be serialized in JSON internally and +hence no functions or prototype chain will be included. + +The renderer process can handle the message by listening to `channel` with the +`ipcRenderer` module. + +An example of sending messages from the main process to the renderer process: + +```javascript +// In the main process. +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.webContents.on('did-finish-load', () => { + win.webContents.send('ping', 'whoooooooh!') + }) +}) +``` + +```html + + + + + + +``` + +#### `contents.enableDeviceEmulation(parameters)` + +* `parameters` Object + * `screenPosition` String - Specify the screen type to emulate + (default: `desktop`) + * `desktop` - Desktop screen type + * `mobile` - Mobile screen type + * `screenSize` Object - Set the emulated screen size (screenPosition == mobile) + * `width` Integer - Set the emulated screen width + * `height` Integer - Set the emulated screen height + * `viewPosition` Object - Position the view on the screen + (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 + original device scale factor) (default: `0`) + * `viewSize` Object - Set the emulated view size (empty means no override) + * `width` Integer - Set the emulated view width + * `height` Integer - Set the emulated view height + * `fitToView` Boolean - Whether emulated view should be scaled down if + necessary to fit into available space (default: `false`) + * `offset` Object - Offset of the emulated view inside available space (not in + fit to view mode) (default: `{x: 0, y: 0}`) + * `x` Float - Set the x axis offset from top left corner + * `y` Float - Set the y axis offset from top left corner + * `scale` Float - Scale of emulated view inside available space (not in fit to + view mode) (default: `1`) + +Enable device emulation with the given parameters. + +#### `contents.disableDeviceEmulation()` + +Disable device emulation enabled by `webContents.enableDeviceEmulation`. + +#### `contents.sendInputEvent(event)` + +* `event` Object + * `type` String (**required**) - The type of the event, can be `mouseDown`, + `mouseUp`, `mouseEnter`, `mouseLeave`, `contextMenu`, `mouseWheel`, + `mouseMove`, `keyDown`, `keyUp`, `char`. + * `modifiers` String[] - An array of modifiers of the event, can + include `shift`, `control`, `alt`, `meta`, `isKeypad`, `isAutoRepeat`, + `leftButtonDown`, `middleButtonDown`, `rightButtonDown`, `capsLock`, + `numLock`, `left`, `right`. + +Sends an input `event` to the page. + +For keyboard events, the `event` object also have following properties: + +* `keyCode` String (**required**) - The character that will be sent + as the keyboard event. Should only use the valid key codes in + [Accelerator](accelerator.md). + +For mouse events, the `event` object also have following properties: + +* `x` Integer (**required**) +* `y` Integer (**required**) +* `button` String - The button pressed, can be `left`, `middle`, `right` +* `globalX` Integer +* `globalY` Integer +* `movementX` Integer +* `movementY` Integer +* `clickCount` Integer + +For the `mouseWheel` event, the `event` object also have following properties: + +* `deltaX` Integer +* `deltaY` Integer +* `wheelTicksX` Integer +* `wheelTicksY` Integer +* `accelerationRatioX` Integer +* `accelerationRatioY` Integer +* `hasPreciseScrollingDeltas` Boolean +* `canScroll` Boolean + +#### `contents.beginFrameSubscription([onlyDirty ,]callback)` + +* `onlyDirty` Boolean (optional) - Defaults to `false` +* `callback` Function + * `frameBuffer` Buffer + * `dirtyRect` [Rectangle](structures/rectangle.md) + +Begin subscribing for presentation events and captured frames, the `callback` +will be called with `callback(frameBuffer, dirtyRect)` when there is a +presentation event. + +The `frameBuffer` is a `Buffer` that contains raw pixel data. On most machines, +the pixel data is effectively stored in 32bit BGRA format, but the actual +representation depends on the endianness of the processor (most modern +processors are little-endian, on machines with big-endian processors the data +is in 32bit ARGB format). + +The `dirtyRect` is an object with `x, y, width, height` properties that +describes which part of the page was repainted. If `onlyDirty` is set to +`true`, `frameBuffer` will only contain the repainted area. `onlyDirty` +defaults to `false`. + +#### `contents.endFrameSubscription()` + +End subscribing for frame presentation events. + +#### `contents.startDrag(item)` + +* `item` Object + * `file` String + * `icon` [NativeImage](native-image.md) + +Sets the `item` as dragging item for current drag-drop operation, `file` is the +absolute path of the file to be dragged, and `icon` is the image showing under +the cursor when dragging. + +#### `contents.savePage(fullPath, saveType, callback)` + +* `fullPath` String - The full file path. +* `saveType` String - Specify the save type. + * `HTMLOnly` - Save only the HTML of the page. + * `HTMLComplete` - Save complete-html page. + * `MHTML` - Save complete-html page as MHTML. +* `callback` Function - `(error) => {}`. + * `error` Error + +Returns true if the process of saving page has been initiated successfully. + +```javascript +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') + }) +}) +``` + +#### `contents.showDefinitionForSelection()` _macOS_ + +Shows pop-up dictionary that searches the selected word on the page. + +#### `contents.setSize(options)` + +Set the size of the page. This is only supported for `` guest contents. + +* `options` Object + * `normal` Object (optional) - Normal size of the page. This can be used in + combination with the [`disableguestresize`](web-view-tag.md#disableguestresize) + attribute to manually resize the webview guest contents. + * `width` Integer + * `height` Integer + +#### `contents.isOffscreen()` + +Returns `Boolean` - Indicates whether *offscreen rendering* is enabled. + +#### `contents.startPainting()` + +If *offscreen rendering* is enabled and not painting, start painting. + +#### `contents.stopPainting()` + +If *offscreen rendering* is enabled and painting, stop painting. + +#### `contents.isPainting()` + +Returns `Boolean` - If *offscreen rendering* is enabled returns whether it is currently painting. + +#### `contents.setFrameRate(fps)` + +* `fps` Integer + +If *offscreen rendering* is enabled sets the frame rate to the specified number. +Only values between 1 and 60 are accepted. + +#### `contents.getFrameRate()` + +Returns `Integer` - If *offscreen rendering* is enabled returns the current frame rate. + +#### `contents.invalidate()` + +If *offscreen rendering* is enabled invalidates the frame and generates a new +one through the `'paint'` event. + +### Instance Properties + +#### `contents.id` + +A Integer representing the unique ID of this WebContents. + +#### `contents.session` + +A Session object ([session](session.md)) used by this webContents. + +#### `contents.hostWebContents` + +A [`WebContents`](web-contents.md) instance that might own this `WebContents`. + +#### `contents.devToolsWebContents` + +A `WebContents` of DevTools for this `WebContents`. + +**Note:** Users should never store this object because it may become `null` +when the DevTools has been closed. + +#### `contents.debugger` + +A [Debugger](debugger.md) instance for this webContents. + +[keyboardevent]: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent diff --git a/docs-translations/th-TH/api/web-frame.md b/docs-translations/th-TH/api/web-frame.md new file mode 100644 index 0000000000..a8a430c7c5 --- /dev/null +++ b/docs-translations/th-TH/api/web-frame.md @@ -0,0 +1,193 @@ +# webFrame + +> Customize the rendering of the current web page. + +Process: [Renderer](../glossary.md#renderer-process) + +An example of zooming current page to 200%. + +```javascript +const {webFrame} = require('electron') + +webFrame.setZoomFactor(2) +``` + +## Methods + +The `webFrame` module has the following methods: + +### `webFrame.setZoomFactor(factor)` + +* `factor` Number - Zoom factor. + +Changes the zoom factor to the specified factor. Zoom factor is +zoom percent divided by 100, so 300% = 3.0. + +### `webFrame.getZoomFactor()` + +Returns `Number` - The current zoom factor. + +### `webFrame.setZoomLevel(level)` + +* `level` Number - Zoom level + +Changes the zoom level to the specified level. The original size is 0 and each +increment above or below represents zooming 20% larger or smaller to default +limits of 300% and 50% of original size, respectively. + +### `webFrame.getZoomLevel()` + +Returns `Number` - The current zoom level. + +### `webFrame.setZoomLevelLimits(minimumLevel, maximumLevel)` + +* `minimumLevel` Number +* `maximumLevel` Number + +**Deprecated:** Call `setVisualZoomLevelLimits` instead to set the visual zoom +level limits. This method will be removed in Electron 2.0. + +### `webFrame.setVisualZoomLevelLimits(minimumLevel, maximumLevel)` + +* `minimumLevel` Number +* `maximumLevel` Number + +Sets the maximum and minimum pinch-to-zoom level. + +### `webFrame.setLayoutZoomLevelLimits(minimumLevel, maximumLevel)` + +* `minimumLevel` Number +* `maximumLevel` Number + +Sets the maximum and minimum layout-based (i.e. non-visual) zoom level. + +### `webFrame.setSpellCheckProvider(language, autoCorrectWord, provider)` + +* `language` String +* `autoCorrectWord` Boolean +* `provider` Object + * `spellCheck` Function - Returns `Boolean` + * `text` String + +Sets a provider for spell checking in input fields and text areas. + +The `provider` must be an object that has a `spellCheck` method that returns +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)) + } +}) +``` + +### `webFrame.registerURLSchemeAsSecure(scheme)` + +* `scheme` String + +Registers the `scheme` as secure scheme. + +Secure schemes do not trigger mixed content warnings. For example, `https` and +`data` are secure schemes because they cannot be corrupted by active network +attackers. + +### `webFrame.registerURLSchemeAsBypassingCSP(scheme)` + +* `scheme` String + +Resources will be loaded from this `scheme` regardless of the current page's +Content Security Policy. + +### `webFrame.registerURLSchemeAsPrivileged(scheme[, options])` + +* `scheme` String +* `options` Object (optional) + * `secure` Boolean - (optional) Default true. + * `bypassCSP` Boolean - (optional) Default true. + * `allowServiceWorkers` Boolean - (optional) Default true. + * `supportFetchAPI` Boolean - (optional) Default true. + * `corsEnabled` Boolean - (optional) Default true. + +Registers the `scheme` as secure, bypasses content security policy for resources, +allows registering ServiceWorker and supports fetch API. + +Specify an option with the value of `false` to omit it from the registration. +An example of registering a privileged scheme, without bypassing Content Security Policy: + +```javascript +const {webFrame} = require('electron') +webFrame.registerURLSchemeAsPrivileged('foo', { bypassCSP: false }) +``` + +### `webFrame.insertText(text)` + +* `text` String + +Inserts `text` to the focused element. + +### `webFrame.executeJavaScript(code[, userGesture, callback])` + +* `code` String +* `userGesture` Boolean (optional) - Default is `false`. +* `callback` Function (optional) - Called after script has been executed. + * `result` Any + +Evaluates `code` in page. + +In the browser window some HTML APIs like `requestFullScreen` can only be +invoked by a gesture from the user. Setting `userGesture` to `true` will remove +this limitation. + +### `webFrame.getResourceUsage()` + +Returns `Object`: + +* `images` [MemoryUsageDetails](structures/memory-usage-details.md) +* `cssStyleSheets` [MemoryUsageDetails](structures/memory-usage-details.md) +* `xslStyleSheets` [MemoryUsageDetails](structures/memory-usage-details.md) +* `fonts` [MemoryUsageDetails](structures/memory-usage-details.md) +* `other` [MemoryUsageDetails](structures/memory-usage-details.md) + +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, + size: 2549, + liveSize: 2542, + decodedSize: 478, + purgedSize: 0, + purgeableSize: 0 + }, + cssStyleSheets: { /* same with "images" */ }, + xslStyleSheets: { /* same with "images" */ }, + fonts: { /* same with "images" */ }, + other: { /* same with "images" */ } +} +``` + +### `webFrame.clearCache()` + +Attempts to free memory that is no longer being used (like images from a +previous navigation). + +Note that blindly calling this method probably makes Electron slower since it +will have to refill these emptied caches, you should only call it if an event +in your app has occurred that makes you think your page is actually using less +memory (i.e. you have navigated from a super heavy page to a mostly empty one, +and intend to stay there). + +[spellchecker]: https://github.com/atom/node-spellchecker diff --git a/docs-translations/th-TH/api/web-request.md b/docs-translations/th-TH/api/web-request.md new file mode 100644 index 0000000000..6da98d81f1 --- /dev/null +++ b/docs-translations/th-TH/api/web-request.md @@ -0,0 +1,205 @@ +## Class: WebRequest + +> Intercept and modify the contents of a request at various stages of its lifetime. + +Process: [Main](../glossary.md#main-process) + +Instances of the `WebRequest` class are accessed by using the `webRequest` +property of a `Session`. + +The methods of `WebRequest` accept an optional `filter` and a `listener`. The +`listener` will be called with `listener(details)` when the API's event has +happened. The `details` object describes the request. Passing `null` +as `listener` will unsubscribe from the event. + +The `filter` object has a `urls` property which is an Array of URL +patterns that will be used to filter out the requests that do not match the URL +patterns. If the `filter` is omitted then all requests will be matched. + +For certain events the `listener` is passed with a `callback`, which should be +called with a `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' + callback({cancel: false, requestHeaders: details.requestHeaders}) +}) +``` + +### Instance Methods + +The following methods are available on instances of `WebRequest`: + +#### `webRequest.onBeforeRequest([filter, ]listener)` + +* `filter` Object +* `listener` Function + * `details` Object + * `id` Integer + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `uploadData` [UploadData[]](structures/upload-data.md) + * `callback` Function + * `response` Object + * `cancel` Boolean (optional) + * `redirectURL` String (optional) - The original request is prevented from + being sent or completed and is instead redirected to the given URL. + +The `listener` will be called with `listener(details, callback)` when a request +is about to occur. + +The `uploadData` is an array of `UploadData` objects. + +The `callback` has to be called with an `response` object. + +#### `webRequest.onBeforeSendHeaders([filter, ]listener)` + +* `filter` Object +* `listener` Function + +The `listener` will be called with `listener(details, callback)` before sending +an HTTP request, once the request headers are available. This may occur after a +TCP connection is made to the server, but before any http data is sent. + +* `details` Object + * `id` Integer + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `requestHeaders` Object +* `callback` Function + * `response` Object + * `cancel` Boolean (optional) + * `requestHeaders` Object (optional) - When provided, request will be made + with these headers. + +The `callback` has to be called with an `response` object. + +#### `webRequest.onSendHeaders([filter, ]listener)` + +* `filter` Object +* `listener` Function + * `details` Object + * `id` Integer + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `requestHeaders` Object + +The `listener` will be called with `listener(details)` just before a request is +going to be sent to the server, modifications of previous `onBeforeSendHeaders` +response are visible by the time this listener is fired. + +#### `webRequest.onHeadersReceived([filter, ]listener)` + +* `filter` Object +* `listener` Function + +The `listener` will be called with `listener(details, callback)` when HTTP +response headers of a request have been received. + +* `details` Object + * `id` String + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `statusLine` String + * `statusCode` Integer + * `responseHeaders` Object +* `callback` Function + * `response` Object + * `cancel` Boolean + * `responseHeaders` Object (optional) - When provided, the server is assumed + to have responded with these headers. + * `statusLine` String (optional) - Should be provided when overriding + `responseHeaders` to change header status otherwise original response + header's status will be used. + +The `callback` has to be called with an `response` object. + +#### `webRequest.onResponseStarted([filter, ]listener)` + +* `filter` Object +* `listener` Function + * `details` Object + * `id` Integer + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `responseHeaders` Object + * `fromCache` Boolean - Indicates whether the response was fetched from disk + cache. + * `statusCode` Integer + * `statusLine` String + +The `listener` will be called with `listener(details)` when first byte of the +response body is received. For HTTP requests, this means that the status line +and response headers are available. + +#### `webRequest.onBeforeRedirect([filter, ]listener)` + +* `filter` Object +* `listener` Function + * `details` Object + * `id` String + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `redirectURL` String + * `statusCode` Integer + * `ip` String (optional) - The server IP address that the request was + actually sent to. + * `fromCache` Boolean + * `responseHeaders` Object + +The `listener` will be called with `listener(details)` when a server initiated +redirect is about to occur. + +#### `webRequest.onCompleted([filter, ]listener)` + +* `filter` Object +* `listener` Function + * `details` Object + * `id` Integer + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `responseHeaders` Object + * `fromCache` Boolean + * `statusCode` Integer + * `statusLine` String + +The `listener` will be called with `listener(details)` when a request is +completed. + +#### `webRequest.onErrorOccurred([filter, ]listener)` + +* `filter` Object +* `listener` Function + * `details` Object + * `id` Integer + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `fromCache` Boolean + * `error` String - The error description. + +The `listener` will be called with `listener(details)` when an error occurs. diff --git a/docs-translations/th-TH/api/webview-tag.md b/docs-translations/th-TH/api/webview-tag.md new file mode 100644 index 0000000000..fbf5d6d844 --- /dev/null +++ b/docs-translations/th-TH/api/webview-tag.md @@ -0,0 +1,910 @@ +# `` Tag + +> Display external web content in an isolated frame and process. + +Use the `webview` tag to embed 'guest' content (such as web pages) in your +Electron app. The guest content is contained within the `webview` container. +An embedded page within your app controls how the guest content is laid out and +rendered. + +Unlike an `iframe`, the `webview` runs in a separate process than your +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 purposes, `webview` can only be used in `BrowserWindow`s that have +`nodeIntegration` enabled. + +## Example + +To embed a web page in your app, add the `webview` tag to your app's embedder +page (this is the app page that will display the guest content). In its simplest +form, the `webview` tag includes the `src` of the web page and css styles that +control the appearance of the `webview` container: + +```html + +``` + +If you want to control the guest content in any way, you can write JavaScript +that listens for `webview` events and responds to those events using the +`webview` methods. Here's sample code with two event listeners: one that listens +for the web page to start loading, the other for the web page to stop loading, +and displays a "loading..." message during the load time: + +```html + +``` + +## CSS Styling Notes + +Please note that the `webview` tag's style uses `display:flex;` internally to +ensure the child `object` element fills the full height and width of its `webview` +container when used with traditional and flexbox layouts (since v0.36.11). Please +do not overwrite the default `display:flex;` CSS property, unless specifying +`display:inline-flex;` for inline layout. + +`webview` has issues being hidden using the `hidden` attribute or using `display: none;`. +It can cause unusual rendering behaviour within its child `browserplugin` object +and the web page is reloaded, when the `webview` is un-hidden, as opposed to just +becoming visible again. The recommended approach is to hide the `webview` using +CSS by zeroing the `width` & `height` and allowing the element to shrink to the 0px +dimensions via `flex`. + +```html + +``` + +## Tag Attributes + +The `webview` tag has the following attributes: + +### `src` + +```html + +``` + +Returns the visible URL. Writing to this attribute initiates top-level +navigation. + +Assigning `src` its own value will reload the current page. + +The `src` attribute can also accept data URLs, such as +`data:text/plain,Hello, world!`. + +### `autosize` + +```html + +``` + +If "on", the `webview` container will automatically resize within the +bounds specified by the attributes `minwidth`, `minheight`, `maxwidth`, and +`maxheight`. These constraints do not impact the `webview` unless `autosize` is +enabled. When `autosize` is enabled, the `webview` container size cannot be less +than the minimum values or greater than the maximum. + +### `nodeintegration` + +```html + +``` + +If "on", the guest page in `webview` will have node integration and can use node +APIs like `require` and `process` to access low level system resources. + +### `plugins` + +```html + +``` + +If "on", the guest page in `webview` will be able to use browser plugins. + +### `preload` + +```html + +``` + +Specifies a script that will be loaded before other scripts run in the guest +page. The protocol of script's URL must be either `file:` or `asar:`, because it +will be loaded by `require` in guest page under the hood. + +When the guest page doesn't have node integration this script will still have +access to all Node APIs, but global objects injected by Node will be deleted +after this script has finished executing. + +### `httpreferrer` + +```html + +``` + +Sets the referrer URL for the guest page. + +### `useragent` + +```html + +``` + +Sets the user agent for the guest page before the page is navigated to. Once the +page is loaded, use the `setUserAgent` method to change the user agent. + +### `disablewebsecurity` + +```html + +``` + +If "on", the guest page will have web security disabled. + +### `partition` + +```html + + +``` + +Sets the session used by the page. If `partition` starts with `persist:`, the +page will use a persistent session available to all pages in the app with the +same `partition`. if there is no `persist:` prefix, the page will use an +in-memory session. By assigning the same `partition`, multiple pages can share +the same session. If the `partition` is unset then default session of the app +will be used. + +This value can only be modified before the first navigation, since the session +of an active renderer process cannot change. Subsequent attempts to modify the +value will fail with a DOM exception. + +### `allowpopups` + +```html + +``` + +If "on", the guest page will be allowed to open new windows. + +### `webpreferences` + +```html + +``` + +A list of strings which specifies the web preferences to be set on the webview, separated by `,`. +The full list of supported preference strings can be found in [BrowserWindow](browser-window.md#new-browserwindowoptions). + +The string follows the same format as the features string in `window.open`. +A name by itself is given a `true` boolean value. +A preference can be set to another value by including an `=`, followed by the value. +Special values `yes` and `1` are interpreted as `true`, while `no` and `0` are interpreted as `false`. + +### `blinkfeatures` + +```html + +``` + +A list of strings which specifies the blink features to be enabled separated by `,`. +The full list of supported feature strings can be found in the +[RuntimeEnabledFeatures.in][blink-feature-string] file. + +### `disableblinkfeatures` + +```html + +``` + +A list of strings which specifies the blink features to be disabled separated by `,`. +The full list of supported feature strings can be found in the +[RuntimeEnabledFeatures.in][blink-feature-string] file. + +### `guestinstance` + +```html + +``` + +A value that links the webview to a specific webContents. When a webview +first loads a new webContents is created and this attribute is set to its +instance identifier. Setting this attribute on a new or existing webview +connects it to the existing webContents that currently renders in a different +webview. + +The existing webview will see the `destroy` event and will then create a new +webContents when a new url is loaded. + +### `disableguestresize` + +```html + +``` + +Prevents the webview contents from resizing when the webview element itself is +resized. + +This can be used in combination with +[`webContents.setSize`](web-contents.md#contentssetsizeoptions) to manually +resize the webview contents in reaction to a window size change. This can +make resizing faster compared to relying on the webview element bounds to +automatically resize the contents. + +```javascript +const {webContents} = require('electron') + +// We assume that `win` points to a `BrowserWindow` instance containing a +// `` with `disableguestresize`. + +win.on('resize', () => { + const [width, height] = win.getContentSize() + for (let wc of webContents.getAllWebContents()) { + // Check if `wc` belongs to a webview in the `win` window. + if (wc.hostWebContents && + wc.hostWebContents.id === win.webContents.id) { + wc.setSize({ + normal: { + width: width, + height: height + } + }) + } + } +}) +``` + +## Methods + +The `webview` tag has the following methods: + +**Note:** The webview element must be loaded before using the methods. + +**Example** + +```javascript +const webview = document.getElementById('foo') +webview.addEventListener('dom-ready', () => { + webview.openDevTools() +}) +``` + +### `.loadURL(url[, options])` + +* `url` URL +* `options` Object (optional) + * `httpReferrer` String (optional) - A HTTP Referrer url. + * `userAgent` String (optional) - A user agent originating the request. + * `extraHeaders` String (optional) - Extra headers separated by "\n" + * `postData` ([UploadRawData](structures/upload-raw-data.md) | [UploadFile](structures/upload-file.md) | [UploadFileSystem](structures/upload-file-system.md) | [UploadBlob](structures/upload-blob.md))[] - (optional) + +Loads the `url` in the webview, the `url` must contain the protocol prefix, +e.g. the `http://` or `file://`. + +### `.getURL()` + +Returns `String` - The URL of guest page. + +### `.getTitle()` + +Returns `String` - The title of guest page. + +### `.isLoading()` + +Returns `Boolean` - Whether guest page is still loading resources. + +### `.isWaitingForResponse()` + +Returns `Boolean` - Whether the guest page is waiting for a first-response for the +main resource of the page. + +### `.stop()` + +Stops any pending navigation. + +### `.reload()` + +Reloads the guest page. + +### `.reloadIgnoringCache()` + +Reloads the guest page and ignores cache. + +### `.canGoBack()` + +Returns `Boolean` - Whether the guest page can go back. + +### `.canGoForward()` + +Returns `Boolean` - Whether the guest page can go forward. + +### `.canGoToOffset(offset)` + +* `offset` Integer + +Returns `Boolean` - Whether the guest page can go to `offset`. + +### `.clearHistory()` + +Clears the navigation history. + +### `.goBack()` + +Makes the guest page go back. + +### `.goForward()` + +Makes the guest page go forward. + +### `.goToIndex(index)` + +* `index` Integer + +Navigates to the specified absolute index. + +### `.goToOffset(offset)` + +* `offset` Integer + +Navigates to the specified offset from the "current entry". + +### `.isCrashed()` + +Returns `Boolean` - Whether the renderer process has crashed. + +### `.setUserAgent(userAgent)` + +* `userAgent` String + +Overrides the user agent for the guest page. + +### `.getUserAgent()` + +Returns `String` - The user agent for guest page. + +### `.insertCSS(css)` + +* `css` String + +Injects CSS into the guest page. + +### `.executeJavaScript(code, userGesture, callback)` + +* `code` String +* `userGesture` Boolean - Default `false`. +* `callback` Function (optional) - Called after script has been executed. + * `result` Any + +Evaluates `code` in page. If `userGesture` is set, it will create the user +gesture context in the page. HTML APIs like `requestFullScreen`, which require +user action, can take advantage of this option for automation. + +### `.openDevTools()` + +Opens a DevTools window for guest page. + +### `.closeDevTools()` + +Closes the DevTools window of guest page. + +### `.isDevToolsOpened()` + +Returns `Boolean` - Whether guest page has a DevTools window attached. + +### `.isDevToolsFocused()` + +Returns `Boolean` - Whether DevTools window of guest page is focused. + +### `.inspectElement(x, y)` + +* `x` Integer +* `y` Integer + +Starts inspecting element at position (`x`, `y`) of guest page. + +### `.inspectServiceWorker()` + +Opens the DevTools for the service worker context present in the guest page. + +### `.setAudioMuted(muted)` + +* `muted` Boolean + +Set guest page muted. + +### `.isAudioMuted()` + +Returns `Boolean` - Whether guest page has been muted. + +### `.undo()` + +Executes editing command `undo` in page. + +### `.redo()` + +Executes editing command `redo` in page. + +### `.cut()` + +Executes editing command `cut` in page. + +### `.copy()` + +Executes editing command `copy` in page. + +### `.paste()` + +Executes editing command `paste` in page. + +### `.pasteAndMatchStyle()` + +Executes editing command `pasteAndMatchStyle` in page. + +### `.delete()` + +Executes editing command `delete` in page. + +### `.selectAll()` + +Executes editing command `selectAll` in page. + +### `.unselect()` + +Executes editing command `unselect` in page. + +### `.replace(text)` + +* `text` String + +Executes editing command `replace` in page. + +### `.replaceMisspelling(text)` + +* `text` String + +Executes editing command `replaceMisspelling` in page. + +### `.insertText(text)` + +* `text` String + +Inserts `text` to the focused element. + +### `.findInPage(text[, options])` + +* `text` String - Content to be searched, must not be empty. +* `options` Object (optional) + * `forward` Boolean - Whether to search forward or backward, defaults to `true`. + * `findNext` Boolean - Whether the operation is first request or a follow up, + defaults to `false`. + * `matchCase` Boolean - Whether search should be case-sensitive, + defaults to `false`. + * `wordStart` Boolean - Whether to look only at the start of words. + defaults to `false`. + * `medialCapitalAsWordStart` Boolean - When combined with `wordStart`, + accepts a match in the middle of a word if the match begins with an + uppercase letter followed by a lowercase or non-letter. + Accepts several other intra-word matches, defaults to `false`. + +Starts a request to find all matches for the `text` in the web page and returns an `Integer` +representing the request id used for the request. The result of the request can be +obtained by subscribing to [`found-in-page`](webview-tag.md#event-found-in-page) event. + +### `.stopFindInPage(action)` + +* `action` String - Specifies the action to take place when ending + [`.findInPage`](webview-tag.md#webviewtagfindinpage) request. + * `clearSelection` - Clear the selection. + * `keepSelection` - Translate the selection into a normal selection. + * `activateSelection` - Focus and click the selection node. + +Stops any `findInPage` request for the `webview` with the provided `action`. + +### `.print([options])` + +Prints `webview`'s web page. Same as `webContents.print([options])`. + +### `.printToPDF(options, callback)` + +Prints `webview`'s web page as PDF, Same as `webContents.printToPDF(options, callback)`. + +### `.capturePage([rect, ]callback)` + +Captures a snapshot of the `webview`'s page. Same as `webContents.capturePage([rect, ]callback)`. + +### `.send(channel[, arg1][, arg2][, ...])` + +* `channel` String +* `arg` (optional) + +Send an asynchronous message to renderer process via `channel`, you can also +send arbitrary arguments. The renderer process can handle the message by +listening to the `channel` event with the `ipcRenderer` module. + +See [webContents.send](web-contents.md#webcontentssendchannel-args) for +examples. + +### `.sendInputEvent(event)` + +* `event` Object + +Sends an input `event` to the page. + +See [webContents.sendInputEvent](web-contents.md#webcontentssendinputeventevent) +for detailed description of `event` object. + +### `.setZoomFactor(factor)` + +* `factor` Number - Zoom factor. + +Changes the zoom factor to the specified factor. Zoom factor is +zoom percent divided by 100, so 300% = 3.0. + +### `.setZoomLevel(level)` + +* `level` Number - Zoom level + +Changes the zoom level to the specified level. The original size is 0 and each +increment above or below represents zooming 20% larger or smaller to default +limits of 300% and 50% of original size, respectively. + +### `.showDefinitionForSelection()` _macOS_ + +Shows pop-up dictionary that searches the selected word on the page. + +### `.getWebContents()` + +Returns [`WebContents`](web-contents.md) - The web contents associated with +this `webview`. + +## DOM events + +The following DOM events are available to the `webview` tag: + +### Event: 'load-commit' + +Returns: + +* `url` String +* `isMainFrame` Boolean + +Fired when a load has committed. This includes navigation within the current +document as well as subframe document-level loads, but does not include +asynchronous resource loads. + +### Event: 'did-finish-load' + +Fired when the navigation is done, i.e. the spinner of the tab will stop +spinning, and the `onload` event is dispatched. + +### Event: 'did-fail-load' + +Returns: + +* `errorCode` Integer +* `errorDescription` String +* `validatedURL` String +* `isMainFrame` Boolean + +This event is like `did-finish-load`, but fired when the load failed or was +cancelled, e.g. `window.stop()` is invoked. + +### Event: 'did-frame-finish-load' + +Returns: + +* `isMainFrame` Boolean + +Fired when a frame has done navigation. + +### Event: 'did-start-loading' + +Corresponds to the points in time when the spinner of the tab starts spinning. + +### Event: 'did-stop-loading' + +Corresponds to the points in time when the spinner of the tab stops spinning. + +### Event: 'did-get-response-details' + +Returns: + +* `status` Boolean +* `newURL` String +* `originalURL` String +* `httpResponseCode` Integer +* `requestMethod` String +* `referrer` String +* `headers` Object +* `resourceType` String + +Fired when details regarding a requested resource is available. +`status` indicates socket connection to download the resource. + +### Event: 'did-get-redirect-request' + +Returns: + +* `oldURL` String +* `newURL` String +* `isMainFrame` Boolean + +Fired when a redirect was received while requesting a resource. + +### Event: 'dom-ready' + +Fired when document in the given frame is loaded. + +### Event: 'page-title-updated' + +Returns: + +* `title` String +* `explicitSet` Boolean + +Fired when page title is set during navigation. `explicitSet` is false when +title is synthesized from file url. + +### Event: 'page-favicon-updated' + +Returns: + +* `favicons` String[] - Array of URLs. + +Fired when page receives favicon urls. + +### Event: 'enter-html-full-screen' + +Fired when page enters fullscreen triggered by HTML API. + +### Event: 'leave-html-full-screen' + +Fired when page leaves fullscreen triggered by HTML API. + +### Event: 'console-message' + +Returns: + +* `level` Integer +* `message` String +* `line` Integer +* `sourceId` String + +Fired when the guest window logs a console message. + +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) +}) +``` + +### Event: 'found-in-page' + +Returns: + +* `result` Object + * `requestId` Integer + * `activeMatchOrdinal` Integer - Position of the active match. + * `matches` Integer - Number of Matches. + * `selectionArea` Object - Coordinates of first match region. + +Fired when a result is available for +[`webview.findInPage`](webview-tag.md#webviewtagfindinpage) request. + +```javascript +const webview = document.getElementById('foo') +webview.addEventListener('found-in-page', (e) => { + webview.stopFindInPage('keepSelection') +}) + +const requestId = webview.findInPage('test') +console.log(requestId) +``` + +### Event: 'new-window' + +Returns: + +* `url` String +* `frameName` String +* `disposition` String - Can be `default`, `foreground-tab`, `background-tab`, + `new-window`, `save-to-disk` and `other`. +* `options` Object - The options which should be used for creating the new + `BrowserWindow`. + +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 webview = document.getElementById('foo') + +webview.addEventListener('new-window', (e) => { + const protocol = require('url').parse(e.url).protocol + if (protocol === 'http:' || protocol === 'https:') { + shell.openExternal(e.url) + } +}) +``` + +### Event: 'will-navigate' + +Returns: + +* `url` String + +Emitted when a user or the page wants to start navigation. It can happen when +the `window.location` object is changed or a user clicks a link in the page. + +This event will not emit when the navigation is started programmatically with +APIs like `.loadURL` and `.back`. + +It is also not emitted during in-page navigation, such as clicking anchor links +or updating the `window.location.hash`. Use `did-navigate-in-page` event for +this purpose. + +Calling `event.preventDefault()` does __NOT__ have any effect. + +### Event: 'did-navigate' + +Returns: + +* `url` String + +Emitted when a navigation is done. + +This event is not emitted for in-page navigations, such as clicking anchor links +or updating the `window.location.hash`. Use `did-navigate-in-page` event for +this purpose. + +### Event: 'did-navigate-in-page' + +Returns: + +* `isMainFrame` Boolean +* `url` String + +Emitted when an in-page navigation happened. + +When in-page navigation happens, the page URL changes but does not cause +navigation outside of the page. Examples of this occurring are when anchor links +are clicked or when the DOM `hashchange` event is triggered. + +### Event: 'close' + +Fired when the guest page attempts to close itself. + +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' +}) +``` + +### Event: 'ipc-message' + +Returns: + +* `channel` String +* `args` Array + +Fired when the guest page has sent an asynchronous message to embedder page. + +With `sendToHost` method and `ipc-message` event you can easily communicate +between guest page and embedder page: + +```javascript +// In embedder page. +const webview = document.getElementById('foo') +webview.addEventListener('ipc-message', (event) => { + console.log(event.channel) + // Prints "pong" +}) +webview.send('ping') +``` + +```javascript +// In guest page. +const {ipcRenderer} = require('electron') +ipcRenderer.on('ping', () => { + ipcRenderer.sendToHost('pong') +}) +``` + +### Event: 'crashed' + +Fired when the renderer process is crashed. + +### Event: 'gpu-crashed' + +Fired when the gpu process is crashed. + +### Event: 'plugin-crashed' + +Returns: + +* `name` String +* `version` String + +Fired when a plugin process is crashed. + +### Event: 'destroyed' + +Fired when the WebContents is destroyed. + +### Event: 'media-started-playing' + +Emitted when media starts playing. + +### Event: 'media-paused' + +Emitted when media is paused or done playing. + +### Event: 'did-change-theme-color' + +Returns: + +* `themeColor` String + +Emitted when a page's theme color changes. This is usually due to encountering a meta tag: + +```html + +``` + +### Event: 'update-target-url' + +Returns: + +* `url` String + +Emitted when mouse moves over a link or the keyboard moves the focus to a link. + +### Event: 'devtools-opened' + +Emitted when DevTools is opened. + +### Event: 'devtools-closed' + +Emitted when DevTools is closed. + +### Event: 'devtools-focused' + +Emitted when DevTools is focused / opened. + +[blink-feature-string]: https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.in diff --git a/docs-translations/th-TH/api/window-open.md b/docs-translations/th-TH/api/window-open.md new file mode 100644 index 0000000000..56216f551a --- /dev/null +++ b/docs-translations/th-TH/api/window-open.md @@ -0,0 +1,43 @@ +# `window.open` Function + +> Open a new window and load a URL. + +When `window.open` is called to create a new window in a web page, a new instance +of `BrowserWindow` will be created for the `url` and a proxy will be returned +to `window.open` to let the page have limited control over it. + +The proxy has limited standard functionality implemented to be +compatible with traditional web pages. For full control of the new window +you should create a `BrowserWindow` directly. + +The newly created `BrowserWindow` will inherit the parent window's options by +default. To override inherited options you can set them in the `features` +string. + +### `window.open(url[, frameName][, features])` + +* `url` String +* `frameName` String (optional) +* `features` String (optional) + +Returns [`BrowserWindowProxy`](browser-window-proxy.md) - Creates a new window +and returns an instance of `BrowserWindowProxy` class. + +The `features` string follows the format of standard browser, but each feature +has to be a field of `BrowserWindow`'s options. + +**Notes:** + +* Node integration will always be disabled in the opened `window` if it is + disabled on the parent window. +* Non-standard features (that are not handled by Chromium or Electron) given in + `features` will be passed to any registered `webContent`'s `new-window` event + handler in the `additionalFeatures` argument. + +### `window.opener.postMessage(message, targetOrigin)` + +* `message` String +* `targetOrigin` String + +Sends a message to the parent window with the specified origin or `*` for no +origin preference. diff --git a/docs-translations/th-TH/development/atom-shell-vs-node-webkit.md b/docs-translations/th-TH/development/atom-shell-vs-node-webkit.md new file mode 100644 index 0000000000..0912a8d66e --- /dev/null +++ b/docs-translations/th-TH/development/atom-shell-vs-node-webkit.md @@ -0,0 +1,52 @@ +# Technical Differences Between Electron and NW.js (formerly node-webkit) + +__Note: Electron was previously named Atom Shell.__ + +Like NW.js, Electron provides a platform to write desktop applications +with JavaScript and HTML and has Node integration to grant access to the low +level system from web pages. + +But there are also fundamental differences between the two projects that make +Electron a completely separate product from NW.js: + +__1. Entry of Application__ + +In NW.js the main entry point of an application is a web page. You specify a +main page URL in the `package.json` and it is opened in a browser window as +the application's main window. + +In Electron, the entry point is a JavaScript script. Instead of +providing a URL directly, you manually create a browser window and load +an HTML file using the API. You also need to listen to window events +to decide when to quit the application. + +Electron works more like the Node.js runtime. Electron's APIs are lower level +so you can use it for browser testing in place of [PhantomJS](http://phantomjs.org/). + +__2. Build System__ + +In order to avoid the complexity of building all of Chromium, Electron uses [`libchromiumcontent`](https://github.com/brightray/libchromiumcontent) to access +Chromium's Content API. `libchromiumcontent` is a single shared library that +includes the Chromium Content module and all of its dependencies. Users don't +need a powerful machine to build Electron. + +__3. Node Integration__ + +In NW.js, the Node integration in web pages requires patching Chromium to +work, while in Electron we chose a different way to integrate the libuv loop +with each platform's message loop to avoid hacking Chromium. See the +[`node_bindings`][node-bindings] code for how that was done. + +__4. Multi-context__ + +If you are an experienced NW.js user, you should be familiar with the +concept of Node context and web context. These concepts were invented because +of how NW.js was implemented. + +By using the [multi-context](http://strongloop.com/strongblog/whats-new-node-js-v0-12-multiple-context-execution/) +feature of Node, Electron doesn't introduce a new JavaScript context in web +pages. + +Note: NW.js has optionally supported multi-context since 0.13. + +[node-bindings]: https://github.com/electron/electron/tree/master/atom/common diff --git a/docs-translations/th-TH/development/build-instructions-linux.md b/docs-translations/th-TH/development/build-instructions-linux.md new file mode 100644 index 0000000000..42b7730a84 --- /dev/null +++ b/docs-translations/th-TH/development/build-instructions-linux.md @@ -0,0 +1,204 @@ +# Build Instructions (Linux) + +Follow the guidelines below for building Electron on Linux. + +## Prerequisites + +* At least 25GB disk space and 8GB RAM. +* Python 2.7.x. Some distributions like CentOS 6.x still use Python 2.6.x + so you may need to check your Python version with `python -V`. +* Node.js. There are various ways to install Node. You can download + source code from [Node.js](http://nodejs.org) and compile from source. + Doing so permits installing Node on your own home directory as a standard user. + Or try repositories such as [NodeSource](https://nodesource.com/blog/nodejs-v012-iojs-and-the-nodesource-linux-repositories). +* Clang 3.4 or later. +* Development headers of GTK+ and libnotify. + +On Ubuntu, install the following libraries: + +```bash +$ sudo apt-get install build-essential clang libdbus-1-dev libgtk2.0-dev \ + libnotify-dev libgnome-keyring-dev libgconf2-dev \ + libasound2-dev libcap-dev libcups2-dev libxtst-dev \ + libxss1 libnss3-dev gcc-multilib g++-multilib curl \ + gperf bison +``` + +On RHEL / CentOS, install the following libraries: + +```bash +$ sudo yum install clang dbus-devel gtk2-devel libnotify-devel \ + libgnome-keyring-devel xorg-x11-server-utils libcap-devel \ + cups-devel libXtst-devel alsa-lib-devel libXrandr-devel \ + GConf2-devel nss-devel +``` + +On Fedora, install the following libraries: + +```bash +$ sudo dnf install clang dbus-devel gtk2-devel libnotify-devel \ + libgnome-keyring-devel xorg-x11-server-utils libcap-devel \ + cups-devel libXtst-devel alsa-lib-devel libXrandr-devel \ + GConf2-devel nss-devel +``` + +Other distributions may offer similar packages for installation via package +managers such as pacman. Or one can compile from source code. + +## Getting the Code + +```bash +$ git clone https://github.com/electron/electron.git +``` + +## Bootstrapping + +The bootstrap script will download all necessary build dependencies and create +the build project files. You must have Python 2.7.x for the script to succeed. +Downloading certain files can take a long time. Notice that we are using +`ninja` to build Electron so there is no `Makefile` generated. + +```bash +$ cd electron +$ ./script/bootstrap.py -v +``` + +### Cross compilation + +If you want to build for an `arm` target you should also install the following +dependencies: + +```bash +$ sudo apt-get install libc6-dev-armhf-cross linux-libc-dev-armhf-cross \ + g++-arm-linux-gnueabihf +``` + +And to cross compile for `arm` or `ia32` targets, you should pass the +`--target_arch` parameter to the `bootstrap.py` script: + +```bash +$ ./script/bootstrap.py -v --target_arch=arm +``` + +## Building + +If you would like to build both `Release` and `Debug` targets: + +```bash +$ ./script/build.py +``` + +This script will cause a very large Electron executable to be placed in +the directory `out/R`. The file size is in excess of 1.3 gigabytes. This +happens because the Release target binary contains debugging symbols. +To reduce the file size, run the `create-dist.py` script: + +```bash +$ ./script/create-dist.py +``` + +This will put a working distribution with much smaller file sizes in +the `dist` directory. After running the create-dist.py script, you +may want to remove the 1.3+ gigabyte binary which is still in `out/R`. + +You can also build the `Debug` target only: + +```bash +$ ./script/build.py -c D +``` + +After building is done, you can find the `electron` debug binary under `out/D`. + +## Cleaning + +To clean the build files: + +```bash +$ npm run clean +``` + +## Troubleshooting + +### Error While Loading Shared Libraries: libtinfo.so.5 + +Prebulit `clang` will try to link to `libtinfo.so.5`. Depending on the host +architecture, symlink to appropriate `libncurses`: + +```bash +$ sudo ln -s /usr/lib/libncurses.so.5 /usr/lib/libtinfo.so.5 +``` + +## Tests + +See [Build System Overview: Tests](build-system-overview.md#tests) + +## Advanced topics + +The default building configuration is targeted for major desktop Linux +distributions, to build for a specific distribution or device, following +information may help you. + +### Building `libchromiumcontent` locally + +To avoid using the prebuilt binaries of `libchromiumcontent`, you can pass the +`--build_libchromiumcontent` switch to `bootstrap.py` script: + +```bash +$ ./script/bootstrap.py -v --build_libchromiumcontent +``` + +Note that by default the `shared_library` configuration is not built, so you can +only build `Release` version of Electron if you use this mode: + +```bash +$ ./script/build.py -c R +``` + +### Using system `clang` instead of downloaded `clang` binaries + +By default Electron is built with prebuilt `clang` binaries provided by Chromium +project. If for some reason you want to build with the `clang` installed in your +system, you can call `bootstrap.py` with `--clang_dir=` switch. By passing +it the build script will assume the `clang` binaries reside in `/bin/`. + +For example if you installed `clang` under `/user/local/bin/clang`: + +```bash +$ ./script/bootstrap.py -v --build_libchromiumcontent --clang_dir /usr/local +$ ./script/build.py -c R +``` + +### Using other compilers other than `clang` + +To build Electron with compilers like `g++`, you first need to disable `clang` +with `--disable_clang` switch first, and then set `CC` and `CXX` environment +variables to the ones you want. + +For example building with GCC toolchain: + +```bash +$ env CC=gcc CXX=g++ ./script/bootstrap.py -v --build_libchromiumcontent --disable_clang +$ ./script/build.py -c R +``` + +### Environment variables + +Apart from `CC` and `CXX`, you can also set following environment variables to +custom the building configurations: + +* `CPPFLAGS` +* `CPPFLAGS_host` +* `CFLAGS` +* `CFLAGS_host` +* `CXXFLAGS` +* `CXXFLAGS_host` +* `AR` +* `AR_host` +* `CC` +* `CC_host` +* `CXX` +* `CXX_host` +* `LDFLAGS` + +The environment variables have to be set when executing the `bootstrap.py` +script, it won't work in the `build.py` script. diff --git a/docs-translations/th-TH/development/build-instructions-osx.md b/docs-translations/th-TH/development/build-instructions-osx.md new file mode 100644 index 0000000000..2bf706dba8 --- /dev/null +++ b/docs-translations/th-TH/development/build-instructions-osx.md @@ -0,0 +1,64 @@ +# Build Instructions (macOS) + +Follow the guidelines below for building Electron on macOS. + +## Prerequisites + +* macOS >= 10.8 +* [Xcode](https://developer.apple.com/technologies/tools/) >= 5.1 +* [node.js](http://nodejs.org) (external) + +If you are using the Python downloaded by Homebrew, you also need to install +the following Python modules: + +* [pyobjc](https://pythonhosted.org/pyobjc/install.html) + +## Getting the Code + +```bash +$ git clone https://github.com/electron/electron +``` + +## Bootstrapping + +The bootstrap script will download all necessary build dependencies and create +the build project files. Notice that we're using [ninja](https://ninja-build.org/) +to build Electron so there is no Xcode project generated. + +```bash +$ cd electron +$ ./script/bootstrap.py -v +``` + +## Building + +Build both `Release` and `Debug` targets: + +```bash +$ ./script/build.py +``` + +You can also only build the `Debug` target: + +```bash +$ ./script/build.py -c D +``` + +After building is done, you can find `Electron.app` under `out/D`. + +## 32bit Support + +Electron can only be built for a 64bit target on macOS and there is no plan to +support 32bit macOS in the future. + +## Cleaning + +To clean the build files: + +```bash +$ npm run clean +``` + +## Tests + +See [Build System Overview: Tests](build-system-overview.md#tests) diff --git a/docs-translations/th-TH/development/build-instructions-windows.md b/docs-translations/th-TH/development/build-instructions-windows.md new file mode 100644 index 0000000000..a506bb2959 --- /dev/null +++ b/docs-translations/th-TH/development/build-instructions-windows.md @@ -0,0 +1,146 @@ +# Build Instructions (Windows) + +Follow the guidelines below for building Electron on Windows. + +## Prerequisites + +* Windows 7 / Server 2008 R2 or higher +* Visual Studio 2015 - [download VS 2015 Community Edition for + free](https://www.visualstudio.com/en-us/products/visual-studio-community-vs.aspx) +* [Python 2.7](http://www.python.org/download/releases/2.7/) +* [Node.js](http://nodejs.org/download/) +* [Git](http://git-scm.com) + +If you don't currently have a Windows installation, +[dev.microsoftedge.com](https://developer.microsoft.com/en-us/microsoft-edge/tools/vms/) +has timebombed versions of Windows that you can use to build Electron. + +Building Electron is done entirely with command-line scripts and cannot be done +with Visual Studio. You can develop Electron with any editor but support for +building with Visual Studio will come in the future. + +**Note:** Even though Visual Studio is not used for building, it's still +**required** because we need the build toolchains it provides. + +**Note:** While older versions of Electron required Visual Studio 2013, Electron 1.1 and later does require Visual Studio 2015. + +## Getting the Code + +```powershell +$ git clone https://github.com/electron/electron.git +``` + +## Bootstrapping + +The bootstrap script will download all necessary build dependencies and create +the build project files. Notice that we're using `ninja` to build Electron so +there is no Visual Studio project generated. + +```powershell +$ cd electron +$ python script\bootstrap.py -v +``` + +## Building + +Build both Release and Debug targets: + +```powershell +$ python script\build.py +``` + +You can also only build the Debug target: + +```powershell +$ 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). + +## 32bit Build + +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=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 +``` + +## Cleaning + +To clean the build files: + +```powershell +$ npm run clean +``` + +## Tests + +See [Build System Overview: Tests](build-system-overview.md#tests) + +## Troubleshooting + +### Command xxxx not found + +If you encountered an error like `Command xxxx not found`, you may try to use +the `VS2015 Command Prompt` console to execute the build scripts. + +### Fatal internal compiler error: C1001 + +Make sure you have the latest Visual Studio update installed. + +### Assertion failed: ((handle))->activecnt >= 0 + +If building under Cygwin, you may see `bootstrap.py` failed with following +error: + +``` +Assertion failed: ((handle))->activecnt >= 0, file src\win\pipe.c, line 1430 + +Traceback (most recent call last): + File "script/bootstrap.py", line 87, in + sys.exit(main()) + File "script/bootstrap.py", line 22, in main + update_node_modules('.') + File "script/bootstrap.py", line 56, in update_node_modules + execute([NPM, 'install']) + File "/home/zcbenz/codes/raven/script/lib/util.py", line 118, in execute + raise e +subprocess.CalledProcessError: Command '['npm.cmd', 'install']' returned non-zero exit status 3 +``` + +This is caused by a bug when using Cygwin Python and Win32 Node together. The +solution is to use the Win32 Python to execute the bootstrap script (assuming +you have installed Python under `C:\Python27`): + +```powershell +$ /cygdrive/c/Python27/python.exe script/bootstrap.py +``` + +### LNK1181: cannot open input file 'kernel32.lib' + +Try reinstalling 32bit Node.js. + +### Error: ENOENT, stat 'C:\Users\USERNAME\AppData\Roaming\npm' + +Simply making that directory [should fix the problem](http://stackoverflow.com/a/25095327/102704): + +```powershell +$ mkdir ~\AppData\Roaming\npm +``` + +### node-gyp is not recognized as an internal or external command + +You may get this error if you are using Git Bash for building, you should use +PowerShell or VS2015 Command Prompt instead. diff --git a/docs-translations/th-TH/development/build-system-overview.md b/docs-translations/th-TH/development/build-system-overview.md new file mode 100644 index 0000000000..df12df6ae9 --- /dev/null +++ b/docs-translations/th-TH/development/build-system-overview.md @@ -0,0 +1,122 @@ +# Build System Overview + +Electron uses [gyp](https://gyp.gsrc.io/) for project generation and +[ninja](https://ninja-build.org/) for building. Project configurations can +be found in the `.gyp` and `.gypi` files. + +## Gyp Files + +Following `gyp` files contain the main rules for building Electron: + +* `electron.gyp` defines how Electron itself is built. +* `common.gypi` adjusts the build configurations of Node to make it build + together with Chromium. +* `vendor/brightray/brightray.gyp` defines how `brightray` is built and + includes the default configurations for linking with Chromium. +* `vendor/brightray/brightray.gypi` includes general build configurations about + building. + +## Component Build + +Since Chromium is quite a large project, the final linking stage can take +quite a few minutes, which makes it hard for development. In order to solve +this, Chromium introduced the "component build", which builds each component as +a separate shared library, making linking very quick but sacrificing file size +and performance. + +In Electron we took a very similar approach: for `Debug` builds, the binary +will be linked to a shared library version of Chromium's components to achieve +fast linking time; for `Release` builds, the binary will be linked to the static +library versions, so we can have the best possible binary size and performance. + +## Minimal Bootstrapping + +All of Chromium's prebuilt binaries (`libchromiumcontent`) are downloaded when +running the bootstrap script. By default both static libraries and shared +libraries will be downloaded and the final size should be between 800MB and 2GB +depending on the platform. + +By default, `libchromiumcontent` is downloaded from Amazon Web Services. +If the `LIBCHROMIUMCONTENT_MIRROR` environment variable is set, the bootstrap +script will download from it. +[`libchromiumcontent-qiniu-mirror`](https://github.com/hokein/libchromiumcontent-qiniu-mirror) +is a mirror for `libchromiumcontent`. If you have trouble in accessing AWS, you +can switch the download address to it via +`export LIBCHROMIUMCONTENT_MIRROR=http://7xk3d2.dl1.z0.glb.clouddn.com/` + +If you only want to build Electron quickly for testing or development, you +can download just the shared library versions by passing the `--dev` parameter: + +```bash +$ ./script/bootstrap.py --dev +$ ./script/build.py -c D +``` + +## Two-Phase Project Generation + +Electron links with different sets of libraries in `Release` and `Debug` +builds. `gyp`, however, doesn't support configuring different link settings for +different configurations. + +To work around this Electron uses a `gyp` variable +`libchromiumcontent_component` to control which link settings to use and only +generates one target when running `gyp`. + +## Target Names + +Unlike most projects that use `Release` and `Debug` as target names, Electron +uses `R` and `D` instead. This is because `gyp` randomly crashes if there is +only one `Release` or `Debug` build configuration defined, and Electron only has +to generate one target at a time as stated above. + +This only affects developers, if you are just building Electron for rebranding +you are not affected. + +## Tests + +Test your changes conform to the project coding style using: + +```bash +$ npm run lint +``` + +Test functionality using: + +```bash +$ npm test +``` + +Whenever you make changes to Electron source code, you'll need to re-run the +build before the tests: + +```bash +$ npm run build && npm test +``` + +You can make the test suite run faster by isolating the specific test or block +you're currently working on using Mocha's +[exclusive tests](https://mochajs.org/#exclusive-tests) feature. Just append +`.only` to any `describe` or `it` function call: + +```js +describe.only('some feature', function () { + // ... only tests in this block will be run +}) +``` + +Alternatively, you can use mocha's `grep` option to only run tests matching the +given regular expression pattern: + +```sh +$ npm test -- --grep child_process +``` + +Tests that include native modules (e.g. `runas`) can't be executed with the +debug build (see [#2558](https://github.com/electron/electron/issues/2558) for +details), but they will work with the release build. + +To run the tests with the release build use: + +```bash +$ npm test -- -R +``` diff --git a/docs-translations/th-TH/development/clang-format.md b/docs-translations/th-TH/development/clang-format.md new file mode 100644 index 0000000000..7ad261a2de --- /dev/null +++ b/docs-translations/th-TH/development/clang-format.md @@ -0,0 +1,34 @@ +# Using clang-format on C++ Code + +[`clang-format`](http://clang.llvm.org/docs/ClangFormat.html) is a tool to +automatically format C/C++/Objective-C code, so that developers don't need to +worry about style issues during code reviews. + +It is highly recommended to format your changed C++ code before opening pull +requests, which will save you and the reviewers' time. + +You can install `clang-format` and `git-clang-format` via +`npm install -g clang-format`. + +To automatically format a file according to Electron C++ code style, simply run +`clang-format -i path/to/electron/file.cc`. It should work on macOS/Linux/Windows. + +The workflow to format your changed code: + +1. Make codes changes in Electron repository. +2. Run `git add your_changed_file.cc`. +3. Run `git-clang-format`, and you will probably see modifications in + `your_changed_file.cc`, these modifications are generated from `clang-format`. +4. Run `git add your_changed_file.cc`, and commit your change. +5. Now the branch is ready to be opened as a pull request. + +If you want to format the changed code on your latest git commit (HEAD), you can +run `git-clang-format HEAD~1`. See `git-clang-format -h` for more details. + +## Editor Integration + +You can also integrate `clang-format` directly into your favorite editors. +For further guidance on setting up editor integration, see these pages: + + * [Atom](https://atom.io/packages/clang-format) + * [Vim & Emacs](http://clang.llvm.org/docs/ClangFormat.html#vim-integration) diff --git a/docs-translations/th-TH/development/coding-style.md b/docs-translations/th-TH/development/coding-style.md new file mode 100644 index 0000000000..503496ba3f --- /dev/null +++ b/docs-translations/th-TH/development/coding-style.md @@ -0,0 +1,55 @@ +# Coding Style + +These are the style guidelines for coding in Electron. + +You can run `npm run lint` to show any style issues detected by `cpplint` and +`eslint`. + +## C++ and Python + +For C++ and Python, we follow Chromium's [Coding +Style](http://www.chromium.org/developers/coding-style). You can use +[clang-format](clang-format.md) to format the C++ code automatically. There is +also a script `script/cpplint.py` to check whether all files conform. + +The Python version we are using now is Python 2.7. + +The C++ code uses a lot of Chromium's abstractions and types, so it's +recommended to get acquainted with them. A good place to start is +Chromium's [Important Abstractions and Data Structures](https://www.chromium.org/developers/coding-style/important-abstractions-and-data-structures) +document. The document mentions some special types, scoped types (that +automatically release their memory when going out of scope), logging mechanisms +etc. + +## JavaScript + +* Write [standard](http://npm.im/standard) JavaScript style. +* File names should be concatenated with `-` instead of `_`, e.g. + `file-name.js` rather than `file_name.js`, because in + [github/atom](https://github.com/github/atom) module names are usually in + the `module-name` form. This rule only applies to `.js` files. +* Use newer ES6/ES2015 syntax where appropriate + * [`const`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const) + for requires and other constants + * [`let`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let) + for defining variables + * [Arrow functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) + instead of `function () { }` + * [Template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) + instead of string concatenation using `+` + +## Naming Things + +Electron APIs uses the same capitalization scheme as Node.js: + +- When the module itself is a class like `BrowserWindow`, use `CamelCase`. +- When the module is a set of APIs, like `globalShortcut`, use `mixedCase`. +- When the API is a property of object, and it is complex enough to be in a + separate chapter like `win.webContents`, use `mixedCase`. +- For other non-module APIs, use natural titles, like ` Tag` or + `Process Object`. + +When creating a new API, it is preferred to use getters and setters instead of +jQuery's one-function style. For example, `.getText()` and `.setText(text)` +are preferred to `.text([text])`. There is a +[discussion](https://github.com/electron/electron/issues/46) on this. diff --git a/docs-translations/th-TH/development/debug-instructions-windows.md b/docs-translations/th-TH/development/debug-instructions-windows.md new file mode 100644 index 0000000000..b2d3347393 --- /dev/null +++ b/docs-translations/th-TH/development/debug-instructions-windows.md @@ -0,0 +1,93 @@ +# Debugging on Windows + +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 Visual Studio, GitHub's hosted Electron Symbol Server, +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 Windows](build-instructions-windows.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. + +* **Visual Studio with C++ Tools**: The free community editions of Visual + Studio 2013 and Visual Studio 2015 both work. Once installed, + [configure Visual Studio to use GitHub's Electron Symbol server](setting-up-symbol-server.md). + It will enable Visual Studio to gain a better understanding of what happens + inside Electron, making it easier to present variables in a human-readable + format. + +* **ProcMon**: The [free SysInternals tool][sys-internals] allows you to inspect + a processes parameters, file handles, and registry operations. + +## Attaching to and Debugging Electron + +To start a debugging session, open up PowerShell/CMD and execute your debug +build of Electron, using the application to open as a parameter. + +```powershell +$ ./out/D/electron.exe ~/my-electron-app/ +``` + +### Setting Breakpoints + +Then, open up Visual Studio. Electron is not built with Visual Studio and hence +does not contain a project file - you can however open up the source code files +"As File", meaning that Visual Studio will open them up by themselves. You can +still set breakpoints - Visual Studio will automatically figure out that the +source code matches the code running in the attached process and break +accordingly. + +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`. + +### Attaching + +You can attach the Visual Studio debugger to a running process on a local or +remote computer. After the process is running, click Debug / Attach to Process +(or press `CTRL+ALT+P`) to open the "Attach to Process" dialog box. You can use +this capability to debug apps that are running on a local or remote computer, +debug multiple processes simultaneously. + +If Electron is running under a different user account, select the +`Show processes from all users` check box. Notice that depending on how many +BrowserWindows your app opened, you will see multiple processes. A typical +one-window app will result in Visual Studio presenting you with two +`Electron.exe` entries - one for the main process and one for the renderer +process. Since the list only gives you names, there's currently no reliable +way of figuring out which is which. + +### Which Process Should I Attach to? + +Code executed within the main process (that is, code found in or eventually run +by your main JavaScript file) as well as code called using the remote +(`require('electron').remote`) will run inside the main process, while other +code will execute inside its respective renderer process. + +You can be attached to multiple programs when you are debugging, but only one +program is active in the debugger at any time. You can set the active program +in the `Debug Location` toolbar or the `Processes window`. + +## Using ProcMon to Observe a Process + +While Visual Studio is fantastic for inspecting specific code paths, ProcMon's +strength is really in observing everything your application is doing with the +operating system - it captures File, Registry, Network, Process, and Profiling +details of processes. It attempts to log **all** events occurring and can be +quite overwhelming, but if you seek to understand what and how your application +is doing to the operating system, it can be a valuable resource. + +For an introduction to ProcMon's basic and advanced debugging features, go check +out [this video tutorial][procmon-instructions] provided by Microsoft. + +[sys-internals]: https://technet.microsoft.com/en-us/sysinternals/processmonitor.aspx +[procmon-instructions]: https://channel9.msdn.com/shows/defrag-tools/defrag-tools-4-process-monitor diff --git a/docs-translations/th-TH/development/debugging-instructions-macos.md b/docs-translations/th-TH/development/debugging-instructions-macos.md new file mode 100644 index 0000000000..e119db466a --- /dev/null +++ b/docs-translations/th-TH/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-translations/th-TH/development/releasing.md b/docs-translations/th-TH/development/releasing.md new file mode 100644 index 0000000000..111a10ea52 --- /dev/null +++ b/docs-translations/th-TH/development/releasing.md @@ -0,0 +1,101 @@ +# Releasing + +This document describes the process for releasing a new version of Electron. + +## Compile release notes + +The current process is to maintain a local file, keeping track of notable changes as pull requests are merged. For examples of how to format the notes, see previous releases on [the releases page]. + +## Create a temporary branch + +Create a new branch from `master` named `release`. + +```sh +git checkout master +git pull +git checkout -b release +``` + +This branch is created as a precaution to prevent any merged PRs from sneaking into a release between the time the temporary release branch is created and the CI builds are complete. + +## Bump the version + +Run the `bump-version` script, passing `major`, `minor`, or `patch` as an argument: + +```sh +npm run bump-version -- patch +git push origin HEAD +``` + +This will bump the version number in several files. See [this bump commit] for an example. + +Most releases will be `patch` level. Upgrades to Chrome or other major changes should use `minor`. For more info, see [electron-versioning]. + +## Edit the release draft + +1. Visit [the releases page] and you'll see a new draft release with placeholder release notes. +1. Edit the release and add release notes. +1. Click 'Save draft'. **Do not click 'Publish release'!** +1. Wait for all the builds to pass. :hourglass_flowing_sand: + +## Merge temporary branch + +Merge the temporary back into master, without creating a merge commit: + +```sh +git merge release master --no-commit +git push origin master +``` + +If this fails, rebase with master and rebuild: + +```sh +git pull +git checkout release +git rebase master +git push origin HEAD +``` + +## Run local debug build + +Run local debug build to verify that you are actually building the version you want. Sometimes you thought you were doing a release for a new version, but you're actually not. + +```sh +npm run build +npm start +``` + +Verify the window is displaying the current updated version. + +## Set environment variables + +You'll need to set the following environment variables to publish a release. Ask another team member for these credentials. + +- `ELECTRON_S3_BUCKET` +- `ELECTRON_S3_ACCESS_KEY` +- `ELECTRON_S3_SECRET_KEY` +- `ELECTRON_GITHUB_TOKEN` - A personal access token with "repo" scope. + +You will only need to do this once. + +## Publish the release + +This script will download the binaries and generate the node headers and the .lib linker used on Windows by node-gyp to build native modules. + +```sh +npm run release +``` + +Note: Many distributions of Python still ship with old HTTPS certificates. You may see a `InsecureRequestWarning`, but it can be disregarded. + +## Delete the temporary branch + +```sh +git checkout master +git branch -D release # delete local branch +git push origin :release # delete remote branch +``` + +[the releases page]: https://github.com/electron/electron/releases +[this bump commit]: https://github.com/electron/electron/commit/78ec1b8f89b3886b856377a1756a51617bc33f5a +[electron-versioning]: /docs/tutorial/electron-versioning.md diff --git a/docs-translations/th-TH/development/setting-up-symbol-server.md b/docs-translations/th-TH/development/setting-up-symbol-server.md new file mode 100644 index 0000000000..098fd2a5df --- /dev/null +++ b/docs-translations/th-TH/development/setting-up-symbol-server.md @@ -0,0 +1,56 @@ +# Setting Up Symbol Server in Debugger + +Debug symbols allow you to have better debugging sessions. They have information +about the functions contained in executables and dynamic libraries and provide +you with information to get clean call stacks. A Symbol Server allows the +debugger to load the correct symbols, binaries and sources automatically without +forcing users to download large debugging files. The server functions like +[Microsoft's symbol server](http://support.microsoft.com/kb/311503) so the +documentation there can be useful. + +Note that because released Electron builds are heavily optimized, debugging is +not always easy. 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. The only workaround is to build an +unoptimized local build. + +The official symbol server URL for Electron is +https://electron-symbols.githubapp.com. +You cannot visit this URL directly, you must add it to the symbol path of your +debugging tool. In the examples below, a local cache directory is used to avoid +repeatedly fetching the PDB from the server. Replace `c:\code\symbols` with an +appropriate cache directory on your machine. + +## Using the Symbol Server in Windbg + +The Windbg symbol path is configured with a string value delimited with asterisk +characters. To use only the Electron symbol server, add the following entry to +your symbol path (**Note:** you can replace `c:\code\symbols` with any writable +directory on your computer, if you'd prefer a different location for downloaded +symbols): + +``` +SRV*c:\code\symbols\*https://electron-symbols.githubapp.com +``` + +Set this string as `_NT_SYMBOL_PATH` in the environment, using the Windbg menus, +or by typing the `.sympath` command. If you would like to get symbols from +Microsoft's symbol server as well, you should list that first: + +``` +SRV*c:\code\symbols\*http://msdl.microsoft.com/download/symbols;SRV*c:\code\symbols\*https://electron-symbols.githubapp.com +``` + +## Using the symbol server in Visual Studio + + + + +## Troubleshooting: Symbols will not load + +Type the following commands in Windbg to print why symbols are not loading: + +``` +> !sym noisy +> .reload /f electron.exe +``` diff --git a/docs-translations/th-TH/development/source-code-directory-structure.md b/docs-translations/th-TH/development/source-code-directory-structure.md new file mode 100644 index 0000000000..cc74fe6942 --- /dev/null +++ b/docs-translations/th-TH/development/source-code-directory-structure.md @@ -0,0 +1,93 @@ +# Source Code Directory Structure + +The source code of Electron is separated into a few parts, mostly +following Chromium on the separation conventions. + +You may need to become familiar with [Chromium's multi-process +architecture](http://dev.chromium.org/developers/design-documents/multi-process-architecture) +to understand the source code better. + +## Structure of Source Code + +``` +Electron +├── atom/ - C++ source code. +| ├── app/ - System entry code. +| ├── browser/ - The frontend including the main window, UI, and all of the +| | main process things. This talks to the renderer to manage web pages. +| | ├── ui/ - Implementation of UI stuff for different platforms. +| | | ├── cocoa/ - Cocoa specific source code. +| | | ├── win/ - Windows GUI specific source code. +| | | └── x/ - X11 specific source code. +| | ├── api/ - The implementation of the main process APIs. +| | ├── net/ - Network related code. +| | ├── mac/ - Mac specific Objective-C source code. +| | └── resources/ - Icons, platform-dependent files, etc. +| ├── renderer/ - Code that runs in renderer process. +| | └── api/ - The implementation of renderer process APIs. +| └── common/ - Code that used by both the main and renderer processes, +| including some utility functions and code to integrate node's message +| loop into Chromium's message loop. +| └── api/ - The implementation of common APIs, and foundations of +| Electron's built-in modules. +├── chromium_src/ - Source code that copied from Chromium. +├── default_app/ - The default page to show when Electron is started without +| providing an app. +├── docs/ - Documentations. +├── lib/ - JavaScript source code. +| ├── browser/ - Javascript main process initialization code. +| | └── api/ - Javascript API implementation. +| ├── common/ - JavaScript used by both the main and renderer processes +| | └── api/ - Javascript API implementation. +| └── renderer/ - Javascript renderer process initialization code. +| └── api/ - Javascript API implementation. +├── spec/ - Automatic tests. +├── electron.gyp - Building rules of Electron. +└── common.gypi - Compiler specific settings and building rules for other + components like `node` and `breakpad`. +``` + +## Structure of Other Directories + +* **script** - Scripts used for development purpose like building, packaging, + testing, etc. +* **tools** - Helper scripts used by gyp files, unlike `script`, scripts put + here should never be invoked by users directly. +* **vendor** - Source code of third party dependencies, we didn't use + `third_party` as name because it would confuse it with the same directory in + Chromium's source code tree. +* **node_modules** - Third party node modules used for building. +* **out** - Temporary output directory of `ninja`. +* **dist** - Temporary directory created by `script/create-dist.py` script + when creating a distribution. +* **external_binaries** - Downloaded binaries of third-party frameworks which + do not support building with `gyp`. + +## Keeping Git Submodules Up to Date + +The Electron repository has a few vendored dependencies, found in the +[/vendor][vendor] directory. Occasionally you might see a message like this +when running `git status`: + +```sh +$ git status + + modified: vendor/brightray (new commits) + modified: vendor/node (new commits) +``` + +To update these vendored dependencies, run the following command: + +```sh +git submodule update --init --recursive +``` + +If you find yourself running this command often, you can create an alias for it +in your `~/.gitconfig` file: + +``` +[alias] + su = submodule update --init --recursive +``` + +[vendor]: https://github.com/electron/electron/tree/master/vendor diff --git a/docs-translations/th-TH/faq/electron-faq.md b/docs-translations/th-TH/faq.md similarity index 100% rename from docs-translations/th-TH/faq/electron-faq.md rename to docs-translations/th-TH/faq.md diff --git a/docs-translations/th-TH/glossary.md b/docs-translations/th-TH/glossary.md new file mode 100644 index 0000000000..6019bddeee --- /dev/null +++ b/docs-translations/th-TH/glossary.md @@ -0,0 +1,153 @@ +# Glossary + +This page defines some terminology that is commonly used in Electron development. + +### ASAR + +ASAR stands for Atom Shell Archive Format. An [asar][asar] archive is a simple +`tar`-like format that concatenates files into a single file. Electron can read +arbitrary files from it without unpacking the whole file. + +The ASAR format was created primarily to improve performance on Windows... TODO + +### Brightray + +[Brightray][brightray] is a static library that makes [libchromiumcontent] +easier to use in applications. It was created specifically for Electron, but can +be used to enable Chromium's renderer in native apps that are not based on +Electron. + +Brightray is a low-level dependency of Electron that does not concern the +majority of Electron users. + +### DMG + +An Apple Disk Image is a packaging format used by macOS. DMG files are +commonly used for distributing application "installers". [electron-builder] +supports `dmg` as a build target. + +### IPC + +IPC stands for Inter-Process Communication. Electron uses IPC to send +serialized JSON messages between the [main] and [renderer] processes. + +### libchromiumcontent + +A single, shared library that includes the Chromium Content module and all its +dependencies (e.g., Blink, [V8], etc.). + +### main process + +The main process, commonly a file named `main.js`, is the entry point to every +Electron app. It controls the life of the app, from open to close. It also +manages native elements such as the Menu, Menu Bar, Dock, Tray, etc. The +main process is responsible for creating each new renderer process in the app. +The full Node API is built in. + +Every app's main process file is specified in the `main` property in +`package.json`. This is how `electron .` knows what file to execute at startup. + +See also: [process](#process), [renderer process](#renderer-process) + +### MAS + +Acronym for Apple's Mac App Store. For details on submitting your app to the +MAS, see the [Mac App Store Submission Guide]. + +### native modules + +Native modules (also called [addons] in +Node.js) are modules written in C or C++ that can be loaded into Node.js or +Electron using the require() function, and used just as if they were an +ordinary Node.js module. They are used primarily to provide an interface +between JavaScript running in Node.js and C/C++ libraries. + +Native Node modules are supported by Electron, but since Electron is very +likely to use a different V8 version from the Node binary installed in your +system, you have to manually specify the location of Electron’s headers when +building native modules. + +See also [Using Native Node Modules]. + +## NSIS + +Nullsoft Scriptable Install System is a script-driven Installer +authoring tool for Microsoft Windows. It is released under a combination of +free software licenses, and is a widely-used alternative to commercial +proprietary products like InstallShield. [electron-builder] supports NSIS +as a build target. + +### process + +A process is an instance of a computer program that is being executed. Electron +apps that make use of the [main] and one or many [renderer] process are +actually running several programs simultaneously. + +In Node.js and Electron, each running process has a `process` object. This +object is a global that provides information about, and control over, the +current process. As a global, it is always available to applications without +using require(). + +See also: [main process](#main-process), [renderer process](#renderer-process) + +### renderer process + +The renderer process is a browser window in your app. Unlike the main process, +there can be multiple of these and each is run in a separate process. +They can also be hidden. + +In normal browsers, web pages usually run in a sandboxed environment and are not +allowed access to native resources. Electron users, however, have the power to +use Node.js APIs in web pages allowing lower level operating system +interactions. + +See also: [process](#process), [main process](#main-process) + +### Squirrel + +Squirrel is an open-source framework that enables Electron apps to update +automatically as new versions are released. See the [autoUpdater] API for +info about getting started with Squirrel. + +### userland + +This term originated in the Unix community, where "userland" or "userspace" +referred to programs that run outside of the operating system kernel. More +recently, the term has been popularized in the Node and npm community to +distinguish between the features available in "Node core" versus packages +published to the npm registry by the much larger "user" community. + +Like Node, Electron is focused on having a small set of APIs that provide +all the necessary primitives for developing multi-platform desktop applications. +This design philosophy allows Electron to remain a flexible tool without being +overly prescriptive about how it should be used. Userland enables users to +create and share tools that provide additional functionality on top of what is +available in "core". + +### V8 + +V8 is Google's open source JavaScript engine. It is written in C++ and is +used in Google Chrome, the open source browser from Google. V8 can run +standalone, or can be embedded into any C++ application. + +### webview + +`webview` tags are used to embed 'guest' content (such as external web pages) in +your Electron app. They are similar to `iframe`s, but differ in that each +webview runs in a separate process. 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. + +[addons]: https://nodejs.org/api/addons.html +[asar]: https://github.com/electron/asar +[autoUpdater]: api/auto-updater.md +[brightray]: https://github.com/electron/brightray +[electron-builder]: https://github.com/electron-userland/electron-builder +[libchromiumcontent]: #libchromiumcontent +[Mac App Store Submission Guide]: tutorials/mac-app-store-submission-guide.md +[main]: #main-process +[renderer]: #renderer-process +[Using Native Node Modules]: tutorial/using-native-node-modules.md +[userland]: #userland +[V8]: #v8 diff --git a/docs-translations/th-TH/styleguide.md b/docs-translations/th-TH/styleguide.md new file mode 100644 index 0000000000..8f59c8a341 --- /dev/null +++ b/docs-translations/th-TH/styleguide.md @@ -0,0 +1,246 @@ +# Electron Documentation Styleguide + +These are the guidelines for writing Electron documentation. + +## Titles + +* Each page must have a single `#`-level title at the top. +* Chapters in the same page must have `##`-level titles. +* Sub-chapters need to increase the number of `#` in the title according to + their nesting depth. +* All words in the page's title must be capitalized, except for conjunctions + like "of" and "and" . +* Only the first word of a chapter title must be capitalized. + +Using `Quick Start` as example: + +```markdown +# Quick Start + +... + +## Main process + +... + +## Renderer process + +... + +## Run your app + +... + +### Run as a distribution + +... + +### Manually downloaded Electron binary + +... +``` + +For API references, there are exceptions to this rule. + +## Markdown rules + +* Use `bash` instead of `cmd` in code blocks (due to the syntax highlighter). +* Lines should be wrapped at 80 columns. +* No nesting lists more than 2 levels (due to the markdown renderer). +* All `js` and `javascript` code blocks are linted with +[standard-markdown](http://npm.im/standard-markdown). + +## Picking words + +* Use "will" over "would" when describing outcomes. +* Prefer "in the ___ process" over "on". + +## API references + +The following rules only apply to the documentation of APIs. + +### Page title + +Each page must use the actual object name returned by `require('electron')` +as the title, such as `BrowserWindow`, `autoUpdater`, and `session`. + +Under the page tile must be a one-line description starting with `>`. + +Using `session` as example: + +```markdown +# session + +> Manage browser sessions, cookies, cache, proxy settings, etc. +``` + +### Module methods and events + +For modules that are not classes, their methods and events must be listed under +the `## Methods` and `## Events` chapters. + +Using `autoUpdater` as an example: + +```markdown +# autoUpdater + +## Events + +### Event: 'error' + +## Methods + +### `autoUpdater.setFeedURL(url[, requestHeaders])` +``` + +### Classes + +* API classes or classes that are part of modules must be listed under a + `## Class: TheClassName` chapter. +* One page can have multiple classes. +* Constructors must be listed with `###`-level titles. +* [Static Methods](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/static) must be listed under a `### Static Methods` chapter. +* [Instance Methods](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes#Prototype_methods) must be listed under an `### Instance Methods` chapter. +* All methods that have a return value must start their description with "Returns `[TYPE]` - Return description" + * If the method returns an `Object`, its structure can be specified using a colon followed by a newline then an unordered list of properties in the same style as function parameters. +* Instance Events must be listed under an `### Instance Events` chapter. +* Instance Properties must be listed under an `### Instance Properties` chapter. + * Instance properties must start with "A [Property Type] ..." + +Using the `Session` and `Cookies` classes as an example: + +```markdown +# session + +## Methods + +### session.fromPartition(partition) + +## Properties + +### session.defaultSession + +## Class: Session + +### Instance Events + +#### Event: 'will-download' + +### Instance Methods + +#### `ses.getCacheSize(callback)` + +### Instance Properties + +#### `ses.cookies` + +## Class: Cookies + +### Instance Methods + +#### `cookies.get(filter, callback)` +``` + +### Methods + +The methods chapter must be in the following form: + +```markdown +### `objectName.methodName(required[, optional]))` + +* `required` String - A parameter description. +* `optional` Integer (optional) - Another parameter description. + +... +``` + +The title can be `###` or `####`-levels depending on whether it is a method of +a module or a class. + +For modules, the `objectName` is the module's name. For classes, it must be the +name of the instance of the class, and must not be the same as the module's +name. + +For example, the methods of the `Session` class under the `session` module must +use `ses` as the `objectName`. + +The optional arguments are notated by square brackets `[]` surrounding the optional argument +as well as the comma required if this optional argument follows another +argument: + +``` +required[, optional] +``` + +Below the method is more detailed information on each of the arguments. The type +of argument is notated by either the common types: + +* [`String`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) +* [`Number`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number) +* [`Object`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) +* [`Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) +* [`Boolean`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean) +* Or a custom type like Electron's [`WebContent`](api/web-contents.md) + +If an argument or a method is unique to certain platforms, those platforms are +denoted using a space-delimited italicized list following the datatype. Values +can be `macOS`, `Windows`, or `Linux`. + +```markdown +* `animate` Boolean (optional) _macOS_ _Windows_ - Animate the thing. +``` + +`Array` type arguments must specify what elements the array may include in +the description below. + +The description for `Function` type arguments should make it clear how it may be +called and list the types of the parameters that will be passed to it. + +### Events + +The events chapter must be in following form: + +```markdown +### Event: 'wake-up' + +Returns: + +* `time` String + +... +``` + +The title can be `###` or `####`-levels depending on whether it is an event of +a module or a class. + +The arguments of an event follow the same rules as methods. + +### Properties + +The properties chapter must be in following form: + +```markdown +### session.defaultSession + +... +``` + +The title can be `###` or `####`-levels depending on whether it is a property of +a module or a class. + +## Documentation Translations + +Translations of the Electron docs are located within the `docs-translations` +directory. + +To add another set (or partial set): + +* Create a subdirectory named by language abbreviation. +* Translate the files. +* Update the `README.md` within your language directory to link to the files + you have translated. +* Add a link to your translation directory on the main Electron + [README](https://github.com/electron/electron#documentation-translations). + +Note that the files under `docs-translations` must only include the translated +ones, the original English files should not be copied there. diff --git a/docs-translations/th-TH/tutorial/about.md b/docs-translations/th-TH/tutorial/about.md new file mode 100644 index 0000000000..0344c9a1f9 --- /dev/null +++ b/docs-translations/th-TH/tutorial/about.md @@ -0,0 +1,57 @@ +# About Electron + +[Electron](http://electron.atom.io) is an open source library developed by GitHub for building cross-platform desktop applications with HTML, CSS, and JavaScript. Electron accomplishes this by combining [Chromium](https://www.chromium.org/Home) and [Node.js](https://nodejs.org) into a single runtime and apps can be packaged for Mac, Windows, and Linux. + +Electron began in 2013 as the framework on which [Atom](https://atom.io), GitHub's hackable text editor, would be built. The two were open sourced in the Spring of 2014. + +It has since become a popular tool used by open source developers, startups, and established companies. [See who is building on Electron](/apps). + +Read on to learn more about the contributors and releases of Electron or get started building with Electron in the [Quick Start Guide](quick-start.md). + +## Core Team and Contributors + +Electron is maintained by a team at GitHub as well as a group of [active contributors](https://github.com/electron/electron/graphs/contributors) from the community. Some of the contributors are individuals and some work at larger companies who are developing on Electron. We're happy to add frequent contributors to the project as maintainers. Read more about [contributing to Electron](https://github.com/electron/electron/blob/master/CONTRIBUTING.md). + +## Releases + +[Electron releases](https://github.com/electron/electron/releases) frequently. We release when there are significant bug fixes, new APIs or are updating versions of Chromium or Node.js. + +### Updating Dependencies + +Electron's version of Chromium is usually updated within one or two weeks after a new stable Chromium version is released, depending on the effort involved in the upgrade. + +When a new version of Node.js is released, Electron usually waits about a month before upgrading in order to bring in a more stable version. + +In Electron, Node.js and Chromium share a single V8 instance—usually the version that Chromium is using. Most of the time this _just works_ but sometimes it means patching Node.js. + + +### Versioning + +Due to the hard dependency on Node.js and Chromium, Electron is in a tricky versioning position and [does not follow `semver`](http://semver.org). You should therefore always reference a specific version of Electron. [Read more about Electron's versioning](http://electron.atom.io/docs/tutorial/electron-versioning/) or see the [versions currently in use](https://electron.atom.io/#electron-versions). + +### LTS + +Long term support of older versions of Electron does not currently exist. If your current version of Electron works for you, you can stay on it for as long as you'd like. If you want to make use of new features as they come in you should upgrade to a newer version. + +A major update came with version `v1.0.0`. If you're not yet using this version, you should [read more about the `v1.0.0` changes](http://electron.atom.io/blog/2016/05/11/electron-1-0). + +## Core Philosophy + +In order to keep Electron small (file size) and sustainable (the spread of dependencies and APIs) the project limits the scope of the core project. + +For instance, Electron uses just the rendering library from Chromium rather than all of Chromium. This makes it easier to upgrade Chromium but also means some browser features found in Google Chrome do not exist in Electron. + +New features added to Electron should primarily be native APIs. If a feature can be its own Node.js module, it probably should be. See the [Electron tools built by the community](http://electron.atom.io/community). + +## History + +Below are milestones in Electron's history. + +| :calendar: | :tada: | +| --- | --- | +| **April 2013**| [Atom Shell is started](https://github.com/electron/electron/commit/6ef8875b1e93787fa9759f602e7880f28e8e6b45).| +| **May 2014** | [Atom Shell is open sourced](http://blog.atom.io/2014/05/06/atom-is-now-open-source.html). | +| **April 2015** | [Atom Shell is re-named Electron](https://github.com/electron/electron/pull/1389). | +| **May 2016** | [Electron releases `v1.0.0`](http://electron.atom.io/blog/2016/05/11/electron-1-0).| +| **May 2016** | [Electron apps compatible with Mac App Store](http://electron.atom.io/docs/tutorial/mac-app-store-submission-guide).| +| **August 2016** | [Windows Store support for Electron apps](http://electron.atom.io/docs/tutorial/windows-store-guide).| diff --git a/docs-translations/th-TH/tutorial/accessibility.md b/docs-translations/th-TH/tutorial/accessibility.md new file mode 100644 index 0000000000..22b9704bdb --- /dev/null +++ b/docs-translations/th-TH/tutorial/accessibility.md @@ -0,0 +1,33 @@ +# Accessibility + +Making accessible applications is important and we're happy to introduce new functionality to [Devtron](http://electron.atom.io/devtron) and [Spectron](http://electron.atom.io/spectron) that gives developers the opportunity to make their apps better for everyone. + +--- + +Accessibility concerns in Electron applications are similar to those of websites because they're both ultimately HTML. With Electron apps, however, you can't use the online resources for accessibility audits because your app doesn't have a URL to point the auditor to. + +These new features bring those auditing tools to your Electron app. You can choose to add audits to your tests with Spectron or use them within DevTools with Devtron. Read on for a summary of the tools or checkout our [accessibility documentation](http://electron.atom.io/docs/tutorial/accessibility) for more information. + +### Spectron + +In the testing framework Spectron, you can now audit each window and `` tag in your application. For example: + +```javascript +app.client.auditAccessibility().then(function (audit) { + if (audit.failed) { + console.error(audit.message) + } +}) +``` + +You can read more about this feature in [Spectron's documentation](https://github.com/electron/spectron#accessibility-testing). + +### Devtron + +In Devtron, there is a new accessibility tab which will allow you to audit a page in your app, sort and filter the results. + +![devtron screenshot](https://cloud.githubusercontent.com/assets/1305617/17156618/9f9bcd72-533f-11e6-880d-389115f40a2a.png) + +Both of these tools are using the [Accessibility Developer Tools](https://github.com/GoogleChrome/accessibility-developer-tools) library built by Google for Chrome. You can learn more about the accessibility audit rules this library uses on that [repository's wiki](https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules). + +If you know of other great accessibility tools for Electron, add them to the [accessibility documentation](http://electron.atom.io/docs/tutorial/accessibility) with a pull request. diff --git a/docs-translations/th-TH/tutorial/application-distribution.md b/docs-translations/th-TH/tutorial/application-distribution.md new file mode 100644 index 0000000000..139a9114c7 --- /dev/null +++ b/docs-translations/th-TH/tutorial/application-distribution.md @@ -0,0 +1,176 @@ +# Application Distribution + +To distribute your app with Electron, you need to download Electron's [prebuilt +binaries](https://github.com/electron/electron/releases). Next, the folder +containing your app should be named `app` and placed in Electron's resources +directory as shown in the following examples. Note that the location of +Electron's prebuilt binaries is indicated with `electron/` in the examples +below. + +On macOS: + +```text +electron/Electron.app/Contents/Resources/app/ +├── package.json +├── main.js +└── index.html +``` + +On Windows and Linux: + +```text +electron/resources/app +├── package.json +├── main.js +└── index.html +``` + +Then execute `Electron.app` (or `electron` on Linux, `electron.exe` on Windows), +and Electron will start as your app. The `electron` directory will then be +your distribution to deliver to final users. + +## Packaging Your App into a File + +Apart from shipping your app by copying all of its source files, you can also +package your app into an [asar](https://github.com/electron/asar) archive to avoid +exposing your app's source code to users. + +To use an `asar` archive to replace the `app` folder, you need to rename the +archive to `app.asar`, and put it under Electron's resources directory like +below, and Electron will then try to read the archive and start from it. + +On macOS: + +```text +electron/Electron.app/Contents/Resources/ +└── app.asar +``` + +On Windows and Linux: + +```text +electron/resources/ +└── app.asar +``` + +More details can be found in [Application packaging](application-packaging.md). + +## Rebranding with Downloaded Binaries + +After bundling your app into Electron, you will want to rebrand Electron +before distributing it to users. + +### Windows + +You can rename `electron.exe` to any name you like, and edit its icon and other +information with tools like [rcedit](https://github.com/atom/rcedit). + +### macOS + +You can rename `Electron.app` to any name you want, and you also have to rename +the `CFBundleDisplayName`, `CFBundleIdentifier` and `CFBundleName` fields in the +following files: + +* `Electron.app/Contents/Info.plist` +* `Electron.app/Contents/Frameworks/Electron Helper.app/Contents/Info.plist` + +You can also rename the helper app to avoid showing `Electron Helper` in the +Activity Monitor, but make sure you have renamed the helper app's executable +file's name. + +The structure of a renamed app would be like: + +``` +MyApp.app/Contents +├── Info.plist +├── MacOS/ +│   └── MyApp +└── Frameworks/ + ├── MyApp Helper EH.app + | ├── Info.plist + | └── MacOS/ + |    └── MyApp Helper EH + ├── MyApp Helper NP.app + | ├── Info.plist + | └── MacOS/ + |    └── MyApp Helper NP + └── MyApp Helper.app + ├── Info.plist + └── MacOS/ +    └── MyApp Helper +``` + +### Linux + +You can rename the `electron` executable to any name you like. + +## Packaging Tools + +Apart from packaging your app manually, you can also choose to use third party +packaging tools to do the work for you: + +* [electron-builder](https://github.com/electron-userland/electron-builder) +* [electron-packager](https://github.com/electron-userland/electron-packager) + +## Rebranding by Rebuilding Electron from Source + +It is also possible to rebrand Electron by changing the product name and +building it from source. To do this you need to modify the `atom.gyp` file and +have a clean rebuild. + +### grunt-build-atom-shell + +Manually checking out Electron's code and rebuilding could be complicated, so +a Grunt task has been created that will handle this automatically: +[grunt-build-atom-shell](https://github.com/paulcbetts/grunt-build-atom-shell). + +This task will automatically handle editing the `.gyp` file, building from +source, then rebuilding your app's native Node modules to match the new +executable name. + +### Creating a Custom Electron Fork + +Creating a custom fork of Electron is almost certainly not something you will +need to do in order to build your app, even for "Production Level" applications. +Using a tool such as `electron-packager` or `electron-builder` will allow you to +"Rebrand" Electron without having to do these steps. + +You need to fork Electron when you have custom C++ code that you have patched +directly into Electron, that either cannot be upstreamed, or has been rejected +from the official version. As maintainers of Electron, we very much would like +to make your scenario work, so please try as hard as you can to get your changes +into the official version of Electron, it will be much much easier on you, and +we appreciate your help. + +#### Creating a Custom Release with surf-build + +1. Install [Surf](https://github.com/surf-build/surf), via npm: + `npm install -g surf-build@latest` + +2. Create a new S3 bucket and create the following empty directory structure: + + ``` + - atom-shell/ + - symbols/ + - dist/ + ``` + +3. Set the following Environment Variables: + + * `ELECTRON_GITHUB_TOKEN` - a token that can create releases on GitHub + * `ELECTRON_S3_ACCESS_KEY`, `ELECTRON_S3_BUCKET`, `ELECTRON_S3_SECRET_KEY` - + the place where you'll upload node.js headers as well as symbols + * `ELECTRON_RELEASE` - Set to `true` and the upload part will run, leave unset + and `surf-build` will just do CI-type checks, appropriate to run for every + pull request. + * `CI` - Set to `true` or else it will fail + * `GITHUB_TOKEN` - set it to the same as `ELECTRON_GITHUB_TOKEN` + * `SURF_TEMP` - set to `C:\Temp` on Windows to prevent path too long issues + * `TARGET_ARCH` - set to `ia32` or `x64` + +4. In `script/upload.py`, you _must_ set `ELECTRON_REPO` to your fork (`MYORG/electron`), + especially if you are a contributor to Electron proper. + +5. `surf-build -r https://github.com/MYORG/electron -s YOUR_COMMIT -n 'surf-PLATFORM-ARCH'` + +6. Wait a very, very long time for the build to complete. diff --git a/docs-translations/th-TH/tutorial/application-packaging.md b/docs-translations/th-TH/tutorial/application-packaging.md new file mode 100644 index 0000000000..8197201225 --- /dev/null +++ b/docs-translations/th-TH/tutorial/application-packaging.md @@ -0,0 +1,185 @@ +# Application Packaging + +To mitigate [issues](https://github.com/joyent/node/issues/6960) around long +path names on Windows, slightly speed up `require` and conceal your source code +from cursory inspection, you can choose to package your app into an [asar][asar] +archive with little changes to your source code. + +## Generating `asar` Archive + +An [asar][asar] archive is a simple tar-like format that concatenates files +into a single file. Electron can read arbitrary files from it without unpacking +the whole file. + +Steps to package your app into an `asar` archive: + +### 1. Install the asar Utility + +```bash +$ npm install -g asar +``` + +### 2. Package with `asar pack` + +```bash +$ asar pack your-app app.asar +``` + +## Using `asar` Archives + +In Electron there are two sets of APIs: Node APIs provided by Node.js and Web +APIs provided by Chromium. Both APIs support reading files from `asar` archives. + +### Node API + +With special patches in Electron, Node APIs like `fs.readFile` and `require` +treat `asar` archives as virtual directories, and the files in it as normal +files in the filesystem. + +For example, suppose we have an `example.asar` archive under `/path/to`: + +```bash +$ asar list /path/to/example.asar +/app.js +/file.txt +/dir/module.js +/static/index.html +/static/main.css +/static/jquery.min.js +``` + +Read a file in the `asar` archive: + +```javascript +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') +``` + +Use a module from the archive: + +```javascript +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') +``` + +### Web API + +In a web page, files in an archive can be requested with the `file:` protocol. +Like the Node API, `asar` archives are treated as directories. + +For example, to get a file with `$.get`: + +```html + +``` + +### Treating an `asar` Archive as a Normal File + +For some cases like verifying the `asar` archive's checksum, we need to read the +content of an `asar` archive as a file. For this purpose you can use the built-in +`original-fs` module which provides original `fs` APIs without `asar` support: + +```javascript +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 +const fs = require('fs') +process.noAsar = true +fs.readFileSync('/path/to/example.asar') +``` + +## Limitations of the Node API + +Even though we tried hard to make `asar` archives in the Node API work like +directories as much as possible, there are still limitations due to the +low-level nature of the Node API. + +### Archives Are Read-only + +The archives can not be modified so all Node APIs that can modify files will not +work with `asar` archives. + +### Working Directory Can Not Be Set to Directories in Archive + +Though `asar` archives are treated as directories, there are no actual +directories in the filesystem, so you can never set the working directory to +directories in `asar` archives. Passing them as the `cwd` option of some APIs +will also cause errors. + +### Extra Unpacking on Some APIs + +Most `fs` APIs can read a file or get a file's information from `asar` archives +without unpacking, but for some APIs that rely on passing the real file path to +underlying system calls, Electron will extract the needed file into a +temporary file and pass the path of the temporary file to the APIs to make them +work. This adds a little overhead for those APIs. + +APIs that requires extra unpacking are: + +* `child_process.execFile` +* `child_process.execFileSync` +* `fs.open` +* `fs.openSync` +* `process.dlopen` - Used by `require` on native modules + +### Fake Stat Information of `fs.stat` + +The `Stats` object returned by `fs.stat` and its friends on files in `asar` +archives is generated by guessing, because those files do not exist on the +filesystem. So you should not trust the `Stats` object except for getting file +size and checking file type. + +### Executing Binaries Inside `asar` Archive + +There are Node APIs that can execute binaries like `child_process.exec`, +`child_process.spawn` and `child_process.execFile`, but only `execFile` is +supported to execute binaries inside `asar` archive. + +This is because `exec` and `spawn` accept `command` instead of `file` as input, +and `command`s are executed under shell. There is no reliable way to determine +whether a command uses a file in asar archive, and even if we do, we can not be +sure whether we can replace the path in command without side effects. + +## Adding Unpacked Files in `asar` Archive + +As stated above, some Node APIs will unpack the file to filesystem when +calling, apart from the performance issues, it could also lead to false alerts +of virus scanners. + +To work around this, you can unpack some files creating archives by using the +`--unpack` option, an example of excluding shared libraries of native modules +is: + +```bash +$ asar pack app app.asar --unpack *.node +``` + +After running the command, apart from the `app.asar`, there is also an +`app.asar.unpacked` folder generated which contains the unpacked files, you +should copy it together with `app.asar` when shipping it to users. + +[asar]: https://github.com/electron/asar diff --git a/docs-translations/th-TH/tutorial/debugging-main-process-node-inspector.md b/docs-translations/th-TH/tutorial/debugging-main-process-node-inspector.md new file mode 100644 index 0000000000..2c42ef3139 --- /dev/null +++ b/docs-translations/th-TH/tutorial/debugging-main-process-node-inspector.md @@ -0,0 +1,131 @@ +# Debugging the Main Process in node-inspector + +[`node-inspector`][node-inspector] provides a familiar DevTools GUI that can +be used in Chrome to debug Electron's main process, however, because +`node-inspector` relies on some native Node modules they must be rebuilt to +target the version of Electron you wish to debug. You can either rebuild +the `node-inspector` dependencies yourself, or let +[`electron-inspector`][electron-inspector] do it for you, both approaches are +covered in this document. + +**Note**: At the time of writing the latest release of `node-inspector` +(0.12.8) can't be rebuilt to target Electron 1.3.0 or later without patching +one of its dependencies. If you use `electron-inspector` it will take care of +this for you. + + +## Use `electron-inspector` for Debugging + +### 1. Install the [node-gyp required tools][node-gyp-required-tools] + +### 2. Install [`electron-rebuild`][electron-rebuild], if you haven't done so already. + +```shell +npm install electron-rebuild --save-dev +``` + +### 3. Install [`electron-inspector`][electron-inspector] + +```shell +npm install electron-inspector --save-dev +``` + +### 4. Start Electron + +Launch Electron with the `--debug` switch: + +```shell +electron --debug=5858 your/app +``` + +or, to pause execution on the first line of JavaScript: + +```shell +electron --debug-brk=5858 your/app +``` + +### 5. Start electron-inspector + +On macOS / Linux: + +```shell +node_modules/.bin/electron-inspector +``` + +On Windows: + +```shell +node_modules\\.bin\\electron-inspector +``` + +`electron-inspector` will need to rebuild `node-inspector` dependencies on the +first run, and any time you change your Electron version. The rebuild process +may require an internet connection to download Node headers and libs, and may +take a few minutes. + +### 6. Load the debugger UI + +Open http://127.0.0.1:8080/debug?ws=127.0.0.1:8080&port=5858 in the Chrome +browser. You may have to click pause if starting with `--debug-brk` to force +the UI to update. + + +## Use `node-inspector` for Debugging + +### 1. Install the [node-gyp required tools][node-gyp-required-tools] + +### 2. Install [`node-inspector`][node-inspector] + +```bash +$ npm install node-inspector +``` + +### 3. Install [`node-pre-gyp`][node-pre-gyp] + +```bash +$ npm install node-pre-gyp +``` + +### 4. Recompile the `node-inspector` `v8` modules for Electron + +**Note:** Update the target argument to be your Electron version number + +```bash +$ node_modules/.bin/node-pre-gyp --target=1.2.5 --runtime=electron --fallback-to-build --directory node_modules/v8-debug/ --dist-url=https://atom.io/download/atom-shell reinstall +$ node_modules/.bin/node-pre-gyp --target=1.2.5 --runtime=electron --fallback-to-build --directory node_modules/v8-profiler/ --dist-url=https://atom.io/download/atom-shell reinstall +``` + +See also [How to install native modules][how-to-install-native-modules]. + +### 5. Enable debug mode for Electron + +You can either start Electron with a debug flag like: + +```bash +$ electron --debug=5858 your/app +``` + +or, to pause your script on the first line: + +```bash +$ electron --debug-brk=5858 your/app +``` + +### 6. Start the [`node-inspector`][node-inspector] server using Electron + +```bash +$ ELECTRON_RUN_AS_NODE=true path/to/electron.exe node_modules/node-inspector/bin/inspector.js +``` + +### 7. Load the debugger UI + +Open http://127.0.0.1:8080/debug?ws=127.0.0.1:8080&port=5858 in the Chrome +browser. You may have to click pause if starting with `--debug-brk` to see the +entry line. + +[electron-inspector]: https://github.com/enlight/electron-inspector +[electron-rebuild]: https://github.com/electron/electron-rebuild +[node-inspector]: https://github.com/node-inspector/node-inspector +[node-pre-gyp]: https://github.com/mapbox/node-pre-gyp +[node-gyp-required-tools]: https://github.com/nodejs/node-gyp#installation +[how-to-install-native-modules]: using-native-node-modules.md#how-to-install-native-modules diff --git a/docs-translations/th-TH/tutorial/debugging-main-process-vscode.md b/docs-translations/th-TH/tutorial/debugging-main-process-vscode.md new file mode 100644 index 0000000000..55b525ad79 --- /dev/null +++ b/docs-translations/th-TH/tutorial/debugging-main-process-vscode.md @@ -0,0 +1,34 @@ +# Debugging the Main Process in VSCode + +### 1. Open an Electron project in VSCode. + +```bash +$ git clone git@github.com:electron/electron-quick-start.git +$ code electron-quick-start +``` + +### 2. Add a file `.vscode/launch.json` with the following configuration: + +```json +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Debug Main Process", + "type": "node", + "request": "launch", + "cwd": "${workspaceRoot}", + "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron", + "program": "${workspaceRoot}/main.js" + } + ] +} +``` + +**Note:** For Windows, use `"${workspaceRoot}/node_modules/.bin/electron.cmd"` for `runtimeExecutable`. + +### 3. Debugging + +Set some breakpoints in `main.js`, and start debugging in the [Debug View](https://code.visualstudio.com/docs/editor/debugging). You should be able to hit the breakpoints. + +Here is a pre-configured project that you can download and directly debug in VSCode: https://github.com/octref/vscode-electron-debug/tree/master/electron-quick-start diff --git a/docs-translations/th-TH/tutorial/debugging-main-process.md b/docs-translations/th-TH/tutorial/debugging-main-process.md new file mode 100644 index 0000000000..fb5759e499 --- /dev/null +++ b/docs-translations/th-TH/tutorial/debugging-main-process.md @@ -0,0 +1,33 @@ +# Debugging the Main Process + +The DevTools in an Electron browser window can only debug JavaScript that's +executed in that window (i.e. the web pages). To debug JavaScript that's +executed in the main process you will need to use an external debugger and +launch Electron with the `--debug` or `--debug-brk` switch. + +## Command Line Switches + +Use one of the following command line switches to enable debugging of the main +process: + +### `--debug=[port]` + +Electron will listen for V8 debugger protocol messages on the specified `port`, +an external debugger will need to connect on this port. The default `port` is +`5858`. + +```shell +electron --debug=5858 your/app +``` + +### `--debug-brk=[port]` + +Like `--debug` but pauses execution on the first line of JavaScript. + +## External Debuggers + +You will need to use a debugger that supports the V8 debugger protocol, +the following guides should help you to get started: + +- [Debugging the Main Process in VSCode](debugging-main-process-vscode.md) +- [Debugging the Main Process in node-inspector](debugging-main-process-node-inspector.md) diff --git a/docs-translations/th-TH/tutorial/desktop-environment-integration.md b/docs-translations/th-TH/tutorial/desktop-environment-integration.md new file mode 100644 index 0000000000..cbe1021c58 --- /dev/null +++ b/docs-translations/th-TH/tutorial/desktop-environment-integration.md @@ -0,0 +1,399 @@ +# Desktop Environment Integration + +Different operating systems provide different features for integrating desktop +applications into their desktop environments. For example, on Windows, +applications can put shortcuts in the JumpList of task bar, and on Mac, +applications can put a custom menu in the dock menu. + +This guide explains how to integrate your application into those desktop +environments with Electron APIs. + +## Notifications (Windows, Linux, macOS) + +All three operating systems provide means for applications to send notifications +to the user. Electron conveniently allows developers to send notifications with +the [HTML5 Notification API](https://notifications.spec.whatwg.org/), using +the currently running operating system's native notification APIs to display it. + +**Note:** Since this is an HTML5 API it is only available in the renderer process. + +```javascript +let myNotification = new Notification('Title', { + body: 'Lorem Ipsum Dolor Sit Amet' +}) + +myNotification.onclick = () => { + console.log('Notification clicked') +} +``` + +While code and user experience across operating systems are similar, there +are fine differences. + +### Windows + +* On Windows 10, notifications "just work". +* On Windows 8.1 and Windows 8, a shortcut to your app, with a [Application User +Model ID][app-user-model-id], must be installed to the Start screen. Note, +however, that it does not need to be pinned to the Start screen. +* On Windows 7, notifications are not supported. You can however send +"balloon notifications" using the [Tray API][tray-balloon]. + +Furthermore, the maximum length for the notification body is 250 characters, +with the Windows team recommending that notifications should be kept to 200 +characters. + +### Linux + +Notifications are sent using `libnotify`, it can show notifications on any +desktop environment that follows [Desktop Notifications +Specification][notification-spec], including Cinnamon, Enlightenment, Unity, +GNOME, KDE. + +### macOS + +Notifications are straight-forward on macOS, you should however be aware of +[Apple's Human Interface guidelines regarding notifications](https://developer.apple.com/library/mac/documentation/UserExperience/Conceptual/OSXHIGuidelines/NotificationCenter.html). + +Note that notifications are limited to 256 bytes in size - and will be truncated +if you exceed that limit. + +## Recent documents (Windows & macOS) + +Windows and macOS provide easy access to a list of recent documents opened by +the application via JumpList or dock menu, respectively. + +__JumpList:__ + +![JumpList Recent Files](http://i.msdn.microsoft.com/dynimg/IC420538.png) + +__Application dock menu:__ + + + +To add a file to recent documents, you can use the +[app.addRecentDocument][addrecentdocument] API: + +```javascript +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 +const {app} = require('electron') +app.clearRecentDocuments() +``` + +### Windows Notes + +In order to be able to use this feature on Windows, your application has to be +registered as a handler of the file type of the document, otherwise the file +won't appear in JumpList even after you have added it. You can find everything +on registering your application in [Application Registration][app-registration]. + +When a user clicks a file from the JumpList, a new instance of your application +will be started with the path of the file added as a command line argument. + +### macOS Notes + +When a file is requested from the recent documents menu, the `open-file` event +of `app` module will be emitted for it. + +## Custom Dock Menu (macOS) + +macOS enables developers to specify a custom menu for the dock, which usually +contains some shortcuts for commonly used features of your application: + +__Dock menu of Terminal.app:__ + + + +To set your custom dock menu, you can use the `app.dock.setMenu` API, which is +only available on macOS: + +```javascript +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 Command...'} +]) +app.dock.setMenu(dockMenu) +``` + +## User Tasks (Windows) + +On Windows you can specify custom actions in the `Tasks` category of JumpList, +as quoted from MSDN: + +> Applications define tasks based on both the program's features and the key +> things a user is expected to do with them. Tasks should be context-free, in +> that the application does not need to be running for them to work. They +> should also be the statistically most common actions that a normal user would +> perform in an application, such as compose an email message or open the +> calendar in a mail program, create a new document in a word processor, launch +> an application in a certain mode, or launch one of its subcommands. An +> application should not clutter the menu with advanced features that standard +> users won't need or one-time actions such as registration. Do not use tasks +> for promotional items such as upgrades or special offers. +> +> It is strongly recommended that the task list be static. It should remain the +> same regardless of the state or status of the application. While it is +> possible to vary the list dynamically, you should consider that this could +> confuse the user who does not expect that portion of the destination list to +> change. + +__Tasks of Internet Explorer:__ + +![IE](http://i.msdn.microsoft.com/dynimg/IC420539.png) + +Unlike the dock menu in macOS which is a real menu, user tasks in Windows work +like application shortcuts such that when user clicks a task, a program will be +executed with specified arguments. + +To set user tasks for your application, you can use +[app.setUserTasks][setusertaskstasks] API: + +```javascript +const {app} = require('electron') +app.setUserTasks([ + { + program: process.execPath, + arguments: '--new-window', + iconPath: process.execPath, + iconIndex: 0, + title: 'New Window', + description: 'Create a new window' + } +]) +``` + +To clean your tasks list, just call `app.setUserTasks` with an empty array: + +```javascript +const {app} = require('electron') +app.setUserTasks([]) +``` + +The user tasks will still show even after your application closes, so the icon +and program path specified for a task should exist until your application is +uninstalled. + +## Thumbnail Toolbars + +On Windows you can add a thumbnail toolbar with specified buttons in a taskbar +layout of an application window. It provides users a way to access to a +particular window's command without restoring or activating the window. + +From MSDN, it's illustrated: + +> This toolbar is simply the familiar standard toolbar common control. It has a +> maximum of seven buttons. Each button's ID, image, tooltip, and state are defined +> in a structure, which is then passed to the taskbar. The application can show, +> enable, disable, or hide buttons from the thumbnail toolbar as required by its +> current state. +> +> For example, Windows Media Player might offer standard media transport controls +> such as play, pause, mute, and stop. + +__Thumbnail toolbar of Windows Media Player:__ + +![player](https://i-msdn.sec.s-msft.com/dynimg/IC420540.png) + +You can use [BrowserWindow.setThumbarButtons][setthumbarbuttons] to set +thumbnail toolbar in your application: + +```javascript +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') } + }, + { + tooltip: 'button2', + icon: path.join(__dirname, 'button2.png'), + flags: ['enabled', 'dismissonclick'], + click () { console.log('button2 clicked.') } + } +]) +``` + +To clean thumbnail toolbar buttons, just call `BrowserWindow.setThumbarButtons` +with an empty array: + +```javascript +const {BrowserWindow} = require('electron') +let win = new BrowserWindow() +win.setThumbarButtons([]) +``` + +## Unity Launcher Shortcuts (Linux) + +In Unity, you can add custom entries to its launcher via modifying the +`.desktop` file, see [Adding Shortcuts to a Launcher][unity-launcher]. + +__Launcher shortcuts of Audacious:__ + +![audacious](https://help.ubuntu.com/community/UnityLaunchersAndDesktopFiles?action=AttachFile&do=get&target=shortcuts.png) + +## Progress Bar in Taskbar (Windows, macOS, Unity) + +On Windows a taskbar button can be used to display a progress bar. This enables +a window to provide progress information to the user without the user having to +switch to the window itself. + +On macOS the progress bar will be displayed as a part of the dock icon. + +The Unity DE also has a similar feature that allows you to specify the progress +bar in the launcher. + +__Progress bar in taskbar button:__ + +![Taskbar Progress Bar](https://cloud.githubusercontent.com/assets/639601/5081682/16691fda-6f0e-11e4-9676-49b6418f1264.png) + +To set the progress bar for a Window, you can use the +[BrowserWindow.setProgressBar][setprogressbar] API: + +```javascript +const {BrowserWindow} = require('electron') +let win = new BrowserWindow() +win.setProgressBar(0.5) +``` + +## Icon Overlays in Taskbar (Windows) + +On Windows a taskbar button can use a small overlay to display application +status, as quoted from MSDN: + +> Icon overlays serve as a contextual notification of status, and are intended +> to negate the need for a separate notification area status icon to communicate +> that information to the user. For instance, the new mail status in Microsoft +> Outlook, currently shown in the notification area, can now be indicated +> through an overlay on the taskbar button. Again, you must decide during your +> development cycle which method is best for your application. Overlay icons are +> intended to supply important, long-standing status or notifications such as +> network status, messenger status, or new mail. The user should not be +> presented with constantly changing overlays or animations. + +__Overlay on taskbar button:__ + +![Overlay on taskbar button](https://i-msdn.sec.s-msft.com/dynimg/IC420441.png) + +To set the overlay icon for a window, you can use the +[BrowserWindow.setOverlayIcon][setoverlayicon] API: + +```javascript +const {BrowserWindow} = require('electron') +let win = new BrowserWindow() +win.setOverlayIcon('path/to/overlay.png', 'Description for overlay') +``` + +## Flash Frame (Windows) + +On Windows you can highlight the taskbar button to get the user's attention. +This is similar to bouncing the dock icon on macOS. +From the MSDN reference documentation: + +> Typically, a window is flashed to inform the user that the window requires +> attention but that it does not currently have the keyboard focus. + +To flash the BrowserWindow taskbar button, you can use the +[BrowserWindow.flashFrame][flashframe] API: + +```javascript +const {BrowserWindow} = require('electron') +let win = new BrowserWindow() +win.once('focus', () => win.flashFrame(false)) +win.flashFrame(true) +``` + +Don't forget to call the `flashFrame` method with `false` to turn off the flash. In +the above example, it is called when the window comes into focus, but you might +use a timeout or some other event to disable it. + +## Represented File of Window (macOS) + +On macOS a window can set its represented file, so the file's icon can show in +the title bar and when users Command-Click or Control-Click on the title a path +popup will show. + +You can also set the edited state of a window so that the file icon can indicate +whether the document in this window has been modified. + +__Represented file popup menu:__ + + + +To set the represented file of window, you can use the +[BrowserWindow.setRepresentedFilename][setrepresentedfilename] and +[BrowserWindow.setDocumentEdited][setdocumentedited] APIs: + +```javascript +const {BrowserWindow} = require('electron') +let win = new BrowserWindow() +win.setRepresentedFilename('/etc/passwd') +win.setDocumentEdited(true) +``` + +## Dragging files out of the window + +For certain kinds of apps that manipulate on files, it is important to be able +to drag files from Electron to other apps. To implement this feature in your +app, you need to call `webContents.startDrag(item)` API on `ondragstart` event. + +In web page: + +```html +item + +``` + +In the main process: + +```javascript +const {ipcMain} = require('electron') +ipcMain.on('ondragstart', (event, filePath) => { + event.sender.startDrag({ + file: filePath, + icon: '/path/to/icon.png' + }) +}) +``` + +[addrecentdocument]: ../api/app.md#appaddrecentdocumentpath-os-x-windows +[clearrecentdocuments]: ../api/app.md#appclearrecentdocuments-os-x-windows +[setusertaskstasks]: ../api/app.md#appsetusertaskstasks-windows +[setprogressbar]: ../api/browser-window.md#winsetprogressbarprogress +[setoverlayicon]: ../api/browser-window.md#winsetoverlayiconoverlay-description-windows-7 +[setrepresentedfilename]: ../api/browser-window.md#winsetrepresentedfilenamefilename-os-x +[setdocumentedited]: ../api/browser-window.md#winsetdocumenteditededited-os-x +[app-registration]: http://msdn.microsoft.com/en-us/library/windows/desktop/ee872121(v=vs.85).aspx +[unity-launcher]: https://help.ubuntu.com/community/UnityLaunchersAndDesktopFiles#Adding_shortcuts_to_a_launcher +[setthumbarbuttons]: ../api/browser-window.md#winsetthumbarbuttonsbuttons-windows-7 +[tray-balloon]: ../api/tray.md#traydisplayballoonoptions-windows +[app-user-model-id]: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx +[notification-spec]: https://developer.gnome.org/notification-spec/ +[flashframe]: ../api/browser-window.md#winflashframeflag diff --git a/docs-translations/th-TH/tutorial/devtools-extension.md b/docs-translations/th-TH/tutorial/devtools-extension.md new file mode 100644 index 0000000000..ac43916a7e --- /dev/null +++ b/docs-translations/th-TH/tutorial/devtools-extension.md @@ -0,0 +1,66 @@ +# DevTools Extension + +Electron supports the [Chrome DevTools Extension][devtools-extension], which can +be used to extend the ability of devtools for debugging popular web frameworks. + +## How to load a DevTools Extension + +This document outlines the process for manually loading an extension. +You may also try +[electron-devtools-installer](https://github.com/GPMDP/electron-devtools-installer), +a third-party tool that downloads extensions directly from the Chrome WebStore. + +To load an extension in Electron, you need to download it in Chrome browser, +locate its filesystem path, and then load it by calling the +`BrowserWindow.addDevToolsExtension(extension)` API. + +Using the [React Developer Tools][react-devtools] as example: + +1. Install it in Chrome browser. +1. Navigate to `chrome://extensions`, and find its extension ID, which is a hash + string like `fmkadmapgofadopljbjfkapdkoienihi`. +1. Find out filesystem location used by Chrome for storing extensions: + * on Windows it is `%LOCALAPPDATA%\Google\Chrome\User Data\Default\Extensions`; + * on Linux it could be: + * `~/.config/google-chrome/Default/Extensions/` + * `~/.config/google-chrome-beta/Default/Extensions/` + * `~/.config/google-chrome-canary/Default/Extensions/` + * `~/.config/chromium/Default/Extensions/` + * 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.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` +API to unload it. + +## Supported DevTools Extensions + +Electron only supports a limited set of `chrome.*` APIs, so some extensions +using unsupported `chrome.*` APIs for chrome extension features may not work. +Following Devtools Extensions are tested and guaranteed to work in Electron: + +* [Ember Inspector](https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi) +* [React Developer Tools](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi) +* [Backbone Debugger](https://chrome.google.com/webstore/detail/backbone-debugger/bhljhndlimiafopmmhjlgfpnnchjjbhd) +* [jQuery Debugger](https://chrome.google.com/webstore/detail/jquery-debugger/dbhhnnnpaeobfddmlalhnehgclcmjimi) +* [AngularJS Batarang](https://chrome.google.com/webstore/detail/angularjs-batarang/ighdmehidhipcmcojjgiloacoafjmpfk) +* [Vue.js devtools](https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd) +* [Cerebral Debugger](http://www.cerebraljs.com/documentation/the_debugger) +* [Redux DevTools Extension](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd) + +### What should I do if a DevTools Extension is not working? + +First please make sure the extension is still being maintained, some extensions +can not even work for recent versions of Chrome browser, and we are not able to +do anything for them. + +Then file a bug at Electron's issues list, and describe which part of the +extension is not working as expected. + +[devtools-extension]: https://developer.chrome.com/extensions/devtools +[react-devtools]: https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi diff --git a/docs-translations/th-TH/tutorial/electron-versioning.md b/docs-translations/th-TH/tutorial/electron-versioning.md new file mode 100644 index 0000000000..cae99344a6 --- /dev/null +++ b/docs-translations/th-TH/tutorial/electron-versioning.md @@ -0,0 +1,21 @@ +# Electron Versioning + +If you are a seasoned Node developer, you are surely aware of `semver` - and +might be used to giving your dependency management systems only rough guidelines +rather than fixed version numbers. Due to the hard dependency on Node and +Chromium, Electron is in a slightly more difficult position and does not follow +semver. You should therefore always reference a specific version of Electron. + +Version numbers are bumped using the following rules: + +* Major: For breaking changes in Electron's API - if you upgrade from `0.37.0` + to `1.0.0`, you will have to update your app. +* Minor: For major Chrome and minor Node upgrades; or significant Electron + changes - if you upgrade from `1.0.0` to `1.1.0`, your app is supposed to + still work, but you might have to work around small changes. +* Patch: For new features and bug fixes - if you upgrade from `1.0.0` to + `1.0.1`, your app will continue to work as-is. + +If you are using `electron` or `electron-prebuilt`, we recommend that you set a fixed version +number (`1.1.0` instead of `^1.1.0`) to ensure that all upgrades of Electron are +a manual operation made by you, the developer. diff --git a/docs-translations/th-TH/tutorial/mac-app-store-submission-guide.md b/docs-translations/th-TH/tutorial/mac-app-store-submission-guide.md new file mode 100644 index 0000000000..4551935d6c --- /dev/null +++ b/docs-translations/th-TH/tutorial/mac-app-store-submission-guide.md @@ -0,0 +1,266 @@ +# Mac App Store Submission Guide + +Since v0.34.0, Electron allows submitting packaged apps to the Mac App Store +(MAS). This guide provides information on: how to submit your app and the +limitations of the MAS build. + +**Note:** Submitting an app to Mac App Store requires enrolling [Apple Developer +Program][developer-program], which costs money. + +## How to Submit Your App + +The following steps introduce a simple way to submit your app to Mac App Store. +However, these steps do not ensure your app will be approved by Apple; you +still need to read Apple's [Submitting Your App][submitting-your-app] guide on +how to meet the Mac App Store requirements. + +### Get Certificate + +To submit your app to the Mac App Store, you first must get a certificate from +Apple. You can follow these [existing guides][nwjs-guide] on web. + +### Get Team ID + +Before signing your app, you need to know the Team ID of your account. To locate +your Team ID, Sign in to [Apple Developer Center](https://developer.apple.com/account/), +and click Membership in the sidebar. Your Team ID appears in the Membership +Information section under the team name. + +### Sign Your App + +After finishing the preparation work, you can package your app by following +[Application Distribution](application-distribution.md), and then proceed to +signing your app. + +First, you have to add a `ElectronTeamID` key to your app's `Info.plist`, which +has your Team ID as value: + +```xml + + + ... + ElectronTeamID + TEAM_ID + + +``` + +Then, you need to prepare two entitlements files. + +`child.plist`: + +```xml + + + + + com.apple.security.app-sandbox + + com.apple.security.inherit + + + +``` + +`parent.plist`: + +```xml + + + + + com.apple.security.app-sandbox + + com.apple.security.application-groups + TEAM_ID.your.bundle.id + + +``` + +You have to replace `TEAM_ID` with your Team ID, and replace `your.bundle.id` +with the Bundle ID of your app. + +And then sign your app with the following script: + +```bash +#!/bin/bash + +# Name of your app. +APP="YourApp" +# The path of your app to sign. +APP_PATH="/path/to/YourApp.app" +# The path to the location you want to put the signed package. +RESULT_PATH="~/Desktop/$APP.pkg" +# The name of certificates you requested. +APP_KEY="3rd Party Mac Developer Application: Company Name (APPIDENTITY)" +INSTALLER_KEY="3rd Party Mac Developer Installer: Company Name (APPIDENTITY)" +# The path of your plist files. +CHILD_PLIST="/path/to/child.plist" +PARENT_PLIST="/path/to/parent.plist" + +FRAMEWORKS_PATH="$APP_PATH/Contents/Frameworks" + +codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/Electron Framework.framework/Versions/A/Electron Framework" +codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/Electron Framework.framework/Versions/A/Libraries/libffmpeg.dylib" +codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/Electron Framework.framework/Versions/A/Libraries/libnode.dylib" +codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/Electron Framework.framework" +codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/$APP Helper.app/Contents/MacOS/$APP Helper" +codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/$APP Helper.app/" +codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/$APP Helper EH.app/Contents/MacOS/$APP Helper EH" +codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/$APP Helper EH.app/" +codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/$APP Helper NP.app/Contents/MacOS/$APP Helper NP" +codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/$APP Helper NP.app/" +codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$APP_PATH/Contents/MacOS/$APP" +codesign -s "$APP_KEY" -f --entitlements "$PARENT_PLIST" "$APP_PATH" + +productbuild --component "$APP_PATH" /Applications --sign "$INSTALLER_KEY" "$RESULT_PATH" +``` + +If you are new to app sandboxing under macOS, you should also read through +Apple's [Enabling App Sandbox][enable-app-sandbox] to have a basic idea, then +add keys for the permissions needed by your app to the entitlements files. + +Apart from manually signing your app, you can also choose to use the +[electron-osx-sign][electron-osx-sign] module to do the job. + +#### Sign Native Modules + +Native modules used in your app also need to be signed. If using +electron-osx-sign, be sure to include the path to the built binaries in the +argument list: + +```bash +electron-osx-sign YourApp.app YourApp.app/Contents/Resources/app/node_modules/nativemodule/build/release/nativemodule +``` + +Also note that native modules may have intermediate files produced which should +not be included (as they would also need to be signed). If you use +[electron-packager][electron-packager] before version 8.1.0, add +`--ignore=.+\.o$` to your build step to ignore these files. Versions 8.1.0 and +later ignores those files by default. + +### Upload Your App + +After signing your app, you can use Application Loader to upload it to iTunes +Connect for processing, making sure you have [created a record][create-record] +before uploading. + +### Submit Your App for Review + +After these steps, you can [submit your app for review][submit-for-review]. + +## Limitations of MAS Build + +In order to satisfy all requirements for app sandboxing, the following modules +have been disabled in the MAS build: + +* `crashReporter` +* `autoUpdater` + +and the following behaviors have been changed: + +* Video capture may not work for some machines. +* Certain accessibility features may not work. +* Apps will not be aware of DNS changes. +* APIs for launching apps at login are disabled. See +https://github.com/electron/electron/issues/7312#issuecomment-249479237 + +Also, due to the usage of app sandboxing, the resources which can be accessed by +the app are strictly limited; you can read [App Sandboxing][app-sandboxing] for +more information. + +### Additional Entitlements + +Depending on which Electron APIs your app uses, you may need to add additional +entitlements to your `parent.plist` file to be able to use these APIs from your +app's Mac App Store build. + +#### Network Access + +Enable outgoing network connections to allow your app to connect to a server: + +```xml +com.apple.security.network.client + +``` + +Enable incoming network connections to allow your app to open a network +listening socket: + +```xml +com.apple.security.network.server + +``` + +See the [Enabling Network Access documentation][network-access] for more +details. + +#### dialog.showOpenDialog + +```xml +com.apple.security.files.user-selected.read-only + +``` + +See the [Enabling User-Selected File Access documentation][user-selected] for +more details. + +#### dialog.showSaveDialog + +```xml +com.apple.security.files.user-selected.read-write + +``` + +See the [Enabling User-Selected File Access documentation][user-selected] for +more details. + +## Cryptographic Algorithms Used by Electron + +Depending on the country and region you are located, Mac App Store may require +documenting the cryptographic algorithms used in your app, and even ask you to +submit a copy of U.S. Encryption Registration (ERN) approval. + +Electron uses following cryptographic algorithms: + +* AES - [NIST SP 800-38A](http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf), [NIST SP 800-38D](http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf), [RFC 3394](http://www.ietf.org/rfc/rfc3394.txt) +* HMAC - [FIPS 198-1](http://csrc.nist.gov/publications/fips/fips198-1/FIPS-198-1_final.pdf) +* ECDSA - ANS X9.62–2005 +* ECDH - ANS X9.63–2001 +* HKDF - [NIST SP 800-56C](http://csrc.nist.gov/publications/nistpubs/800-56C/SP-800-56C.pdf) +* PBKDF2 - [RFC 2898](https://tools.ietf.org/html/rfc2898) +* RSA - [RFC 3447](http://www.ietf.org/rfc/rfc3447) +* SHA - [FIPS 180-4](http://csrc.nist.gov/publications/fips/fips180-4/fips-180-4.pdf) +* Blowfish - https://www.schneier.com/cryptography/blowfish/ +* CAST - [RFC 2144](https://tools.ietf.org/html/rfc2144), [RFC 2612](https://tools.ietf.org/html/rfc2612) +* DES - [FIPS 46-3](http://csrc.nist.gov/publications/fips/fips46-3/fips46-3.pdf) +* DH - [RFC 2631](https://tools.ietf.org/html/rfc2631) +* DSA - [ANSI X9.30](http://webstore.ansi.org/RecordDetail.aspx?sku=ANSI+X9.30-1%3A1997) +* EC - [SEC 1](http://www.secg.org/sec1-v2.pdf) +* IDEA - "On the Design and Security of Block Ciphers" book by X. Lai +* MD2 - [RFC 1319](http://tools.ietf.org/html/rfc1319) +* MD4 - [RFC 6150](https://tools.ietf.org/html/rfc6150) +* MD5 - [RFC 1321](https://tools.ietf.org/html/rfc1321) +* MDC2 - [ISO/IEC 10118-2](https://www.openssl.org/docs/manmaster/crypto/mdc2.html) +* RC2 - [RFC 2268](https://tools.ietf.org/html/rfc2268) +* RC4 - [RFC 4345](https://tools.ietf.org/html/rfc4345) +* RC5 - http://people.csail.mit.edu/rivest/Rivest-rc5rev.pdf +* RIPEMD - [ISO/IEC 10118-3](http://webstore.ansi.org/RecordDetail.aspx?sku=ISO%2FIEC%2010118-3:2004) + +On how to get the ERN approval, you can reference the article: [How to legally +submit an app to Apple’s App Store when it uses encryption (or how to obtain an +ERN)][ern-tutorial]. + +[developer-program]: https://developer.apple.com/support/compare-memberships/ +[submitting-your-app]: https://developer.apple.com/library/mac/documentation/IDEs/Conceptual/AppDistributionGuide/SubmittingYourApp/SubmittingYourApp.html +[nwjs-guide]: https://github.com/nwjs/nw.js/wiki/Mac-App-Store-%28MAS%29-Submission-Guideline#first-steps +[enable-app-sandbox]: https://developer.apple.com/library/ios/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/EnablingAppSandbox.html +[create-record]: https://developer.apple.com/library/ios/documentation/LanguagesUtilities/Conceptual/iTunesConnect_Guide/Chapters/CreatingiTunesConnectRecord.html +[electron-osx-sign]: https://github.com/electron-userland/electron-osx-sign +[electron-packager]: https://github.com/electron-userland/electron-packager +[submit-for-review]: https://developer.apple.com/library/ios/documentation/LanguagesUtilities/Conceptual/iTunesConnect_Guide/Chapters/SubmittingTheApp.html +[app-sandboxing]: https://developer.apple.com/app-sandboxing/ +[ern-tutorial]: https://carouselapps.com/2015/12/15/legally-submit-app-apples-app-store-uses-encryption-obtain-ern/ +[temporary-exception]: https://developer.apple.com/library/mac/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/AppSandboxTemporaryExceptionEntitlements.html +[user-selected]: https://developer.apple.com/library/mac/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/EnablingAppSandbox.html#//apple_ref/doc/uid/TP40011195-CH4-SW6 +[network-access]: https://developer.apple.com/library/ios/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/EnablingAppSandbox.html#//apple_ref/doc/uid/TP40011195-CH4-SW9 diff --git a/docs-translations/th-TH/tutorial/offscreen-rendering.md b/docs-translations/th-TH/tutorial/offscreen-rendering.md new file mode 100644 index 0000000000..4c3024bdc6 --- /dev/null +++ b/docs-translations/th-TH/tutorial/offscreen-rendering.md @@ -0,0 +1,57 @@ +# Offscreen Rendering + +Offscreen rendering lets you obtain the content of a browser window in a bitmap, +so it can be rendered anywhere, for example on a texture in a 3D scene. The +offscreen rendering in Electron uses a similar approach than the [Chromium +Embedded Framework](https://bitbucket.org/chromiumembedded/cef) project. + +Two modes of rendering can be used and only the dirty area is passed in the +`'paint'` event to be more efficient. The rendering can be stopped, continued +and the frame rate can be set. The specified frame rate is a top limit value, +when there is nothing happening on a webpage, no frames are generated. The +maximum frame rate is 60, because above that there is no benefit, just +performance loss. + +**Note:** An offscreen window is always created as a [Frameless Window](../api/frameless-window.md). + +## Two modes of rendering + +### GPU accelerated + +GPU accelerated rendering means that the GPU is used for composition. Because of +that the frame has to be copied from the GPU which requires more performance, +thus this mode is quite a bit slower than the other one. The benefit of this +mode that WebGL and 3D CSS animations are supported. + +### Software output device + +This mode uses a software output device for rendering in the CPU, so the frame +generation is much faster, thus this mode is preferred over the GPU accelerated +one. + +To enable this mode GPU acceleration has to be disabled by calling the +[`app.disableHardwareAcceleration()`][disablehardwareacceleration] API. + +## Usage + +``` javascript +const {app, BrowserWindow} = require('electron') + +app.disableHardwareAcceleration() + +let win +app.once('ready', () => { + win = new BrowserWindow({ + webPreferences: { + offscreen: true + } + }) + win.loadURL('http://github.com') + win.webContents.on('paint', (event, dirty, image) => { + // updateBitmap(dirty, image.getBitmap()) + }) + win.webContents.setFrameRate(30) +}) +``` + +[disablehardwareacceleration]: ../api/app.md#appdisablehardwareacceleration diff --git a/docs-translations/th-TH/tutorial/online-offline-events.md b/docs-translations/th-TH/tutorial/online-offline-events.md new file mode 100644 index 0000000000..bc9f7ecc94 --- /dev/null +++ b/docs-translations/th-TH/tutorial/online-offline-events.md @@ -0,0 +1,90 @@ +# Online/Offline Event Detection + +Online and offline event detection can be implemented in the renderer process +using standard HTML5 APIs, as shown in the following example. + +_main.js_ + +```javascript +const {app, BrowserWindow} = require('electron') + +let onlineStatusWindow + +app.on('ready', () => { + onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false }) + onlineStatusWindow.loadURL(`file://${__dirname}/online-status.html`) +}) +``` + +_online-status.html_ + +```html + + + + + + +``` + +There may be instances where you want to respond to these events in the +main process as well. The main process however does not have a +`navigator` object and thus cannot detect these events directly. Using +Electron's inter-process communication utilities, the events can be forwarded +to the main process and handled as needed, as shown in the following example. + +_main.js_ + +```javascript +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`) +}) + +ipcMain.on('online-status-changed', (event, status) => { + console.log(status) +}) +``` + +_online-status.html_ + +```html + + + + + + +``` + +**NOTE:** If Electron is not able to connect to a local area network (LAN) or +a router, it is considered offline; all other conditions return `true`. +So while you can assume that Electron is offline when `navigator.onLine` +returns a `false` value, you cannot assume that a `true` value necessarily +means that Electron can access the internet. You could be getting false +positives, such as in cases where the computer is running a virtualization +software that has virtual ethernet adapters that are always "connected." +Therefore, if you really want to determine the internet access status of Electron, +you should develop additional means for checking. diff --git a/docs-translations/th-TH/tutorial/planned-breaking-changes.md b/docs-translations/th-TH/tutorial/planned-breaking-changes.md new file mode 100644 index 0000000000..e2d0e3a939 --- /dev/null +++ b/docs-translations/th-TH/tutorial/planned-breaking-changes.md @@ -0,0 +1,159 @@ +# Planned Breaking API Changes + +The following list includes the APIs that will be removed in Electron 2.0. + +There is no timetable for when this release will occur but deprecation +warnings will be added at least 90 days beforehand. + +## `BrowserWindow` + +```js +// Deprecated +let optionsA = {webPreferences: {blinkFeatures: ''}} +let windowA = new BrowserWindow(optionsA) +// Replace with +let optionsB = {webPreferences: {enableBlinkFeatures: ''}} +let windowB = new BrowserWindow(optionsB) +``` + +## `clipboard` + +```js +// Deprecated +clipboard.readRtf() +// Replace with +clipboard.readRTF() + +// Deprecated +clipboard.writeRtf() +// Replace with +clipboard.writeRTF() + +// Deprecated +clipboard.readHtml() +// Replace with +clipboard.readHTML() + +// Deprecated +clipboard.writeHtml() +// Replace with +clipboard.writeHTML() +``` + +## `crashReporter` + +```js +// Deprecated +crashReporter.start({ + companyName: 'Crashly', + submitURL: 'https://crash.server.com', + autoSubmit: true +}) +// Replace with +crashReporter.start({ + companyName: 'Crashly', + submitURL: 'https://crash.server.com', + uploadToServer: true +}) +``` + +## `nativeImage` + +```js +// Deprecated +nativeImage.toPng() +// Replace with +nativeImage.toPNG() + +// Deprecated +nativeImage.toJpeg() +// Replace with +nativeImage.toJPEG() + +// Deprecated +nativeImage.createFromBuffer(buffer, 1.0) +// Replace with +nativeImage.createFromBuffer(buffer, { + scaleFactor: 1.0 +}) +``` + +## `process` + +```js +// Deprecated +process.versions['atom-shell'] +// Replace with +process.versions.electron +``` + +* `process.versions.electron` and `process.version.chrome` will be made + read-only properties for consistency with the other `process.versions` + properties set by Node. + +## `Tray` + +```js +// Deprecated +tray.setHighlightMode(true) +// Replace with +tray.setHighlightMode('on') + +// Deprecated +tray.setHighlightMode(false) +// Replace with +tray.setHighlightMode('off') +``` + +## `webContents` + +```js +// Deprecated +webContents.openDevTools({detach: true}) +// Replace with +webContents.openDevTools({mode: 'detach'}) +``` + +```js +// Deprecated +webContents.setZoomLevelLimits(1, 2) +// Replace with +webContents.setVisualZoomLevelLimits(1, 2) +``` + +## `webFrame` + +```js +// Deprecated +webFrame.setZoomLevelLimits(1, 2) +// Replace with +webFrame.setVisualZoomLevelLimits(1, 2) + +// Deprecated +webFrame.registerURLSchemeAsSecure('app') +// Replace with +protocol.registerStandardSchemes(['app'], {secure: true}) + +// Deprecated +webFrame.registerURLSchemeAsPrivileged('app', {secure: true}) +// Replace with +protocol.registerStandardSchemes(['app'], {secure: true}) +``` + +## `` + +```js +// Deprecated +webview.setZoomLevelLimits(1, 2) +// Replace with +webview.setVisualZoomLevelLimits(1, 2) +``` + +## Node Headers URL + +This is the URL specified as `disturl` in a `.npmrc` file or as the `--dist-url` +command line flag when building native Node modules. + +Deprecated: https://atom.io/download/atom-shell + +Replace with: https://atom.io/download/electron diff --git a/docs-translations/th-TH/tutorial/quick-start.md b/docs-translations/th-TH/tutorial/quick-start.md new file mode 100644 index 0000000000..d95957a48c --- /dev/null +++ b/docs-translations/th-TH/tutorial/quick-start.md @@ -0,0 +1,244 @@ +# Quick Start + +Electron enables you to create desktop applications with pure JavaScript by +providing a runtime with rich native (operating system) APIs. You could see it +as a variant of the Node.js runtime that is focused on desktop applications +instead of web servers. + +This doesn't mean Electron is a JavaScript binding to graphical user interface +(GUI) libraries. Instead, Electron uses web pages as its GUI, so you could also +see it as a minimal Chromium browser, controlled by JavaScript. + +### Main Process + +In Electron, the process that runs `package.json`'s `main` script is called +__the main process__. The script that runs in the main process can display a GUI +by creating web pages. + +### Renderer Process + +Since Electron uses Chromium for displaying web pages, Chromium's +multi-process architecture is also used. Each web page in Electron runs in +its own process, which is called __the renderer process__. + +In normal browsers, web pages usually run in a sandboxed environment and are not +allowed access to native resources. Electron users, however, have the power to +use Node.js APIs in web pages allowing lower level operating system +interactions. + +### Differences Between Main Process and Renderer Process + +The main process creates web pages by creating `BrowserWindow` instances. Each +`BrowserWindow` instance runs the web page in its own renderer process. When a +`BrowserWindow` instance is destroyed, the corresponding renderer process +is also terminated. + +The main process manages all web pages and their corresponding renderer +processes. Each renderer process is isolated and only cares about the web page +running in it. + +In web pages, calling native GUI related APIs is not allowed because managing +native GUI resources in web pages is very dangerous and it is easy to leak +resources. If you want to perform GUI operations in a web page, the renderer +process of the web page must communicate with the main process to request that +the main process perform those operations. + +In Electron, we have several ways to communicate between the main process and +renderer processes. Like [`ipcRenderer`](../api/ipc-renderer.md) and +[`ipcMain`](../api/ipc-main.md) modules for sending messages, and the +[remote](../api/remote.md) module for RPC style communication. There is also +an FAQ entry on [how to share data between web pages][share-data]. + +## Write your First Electron App + +Generally, an Electron app is structured like this: + +```text +your-app/ +├── package.json +├── main.js +└── index.html +``` + +The format of `package.json` is exactly the same as that of Node's modules, and +the script specified by the `main` field is the startup script of your app, +which will run the main process. An example of your `package.json` might look +like this: + +```json +{ + "name" : "your-app", + "version" : "0.1.0", + "main" : "main.js" +} +``` + +__Note__: If the `main` field is not present in `package.json`, Electron will +attempt to load an `index.js`. + +The `main.js` should create windows and handle system events, a typical +example being: + +```javascript +const {app, BrowserWindow} = require('electron') +const path = require('path') +const url = require('url') + +// 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 + +function createWindow () { + // Create the browser window. + win = new BrowserWindow({width: 800, height: 600}) + + // and load the index.html of the app. + win.loadURL(url.format({ + pathname: path.join(__dirname, 'index.html'), + protocol: 'file:', + slashes: true + })) + + // Open the DevTools. + 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 + }) +} + +// This method will be called when Electron has finished +// initialization and is ready to create browser windows. +// Some APIs can only be used after this event occurs. +app.on('ready', createWindow) + +// Quit when all windows are closed. +app.on('window-all-closed', () => { + // On 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.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() + } +}) + +// 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. +``` + +Finally the `index.html` is the web page you want to show: + +```html + + + + + Hello World! + + +

Hello World!

+ We are using node , + Chrome , + and Electron . + + +``` + +## Run your app + +Once you've created your initial `main.js`, `index.html`, and `package.json` files, +you'll probably want to try running your app locally to test it and make sure it's +working as expected. + +### `electron` + +[`electron`](https://github.com/electron-userland/electron-prebuilt) is +an `npm` module that contains pre-compiled versions of Electron. + +If you've installed it globally with `npm`, then you will only need to run the +following in your app's source directory: + +```bash +electron . +``` + +If you've installed it locally, then run: + +#### macOS / Linux + +```bash +$ ./node_modules/.bin/electron . +``` + +#### Windows + +```bash +$ .\node_modules\.bin\electron . +``` + +### Manually Downloaded Electron Binary + +If you downloaded Electron manually, you can also use the included +binary to execute your app directly. + +#### Windows + +```bash +$ .\electron\electron.exe your-app\ +``` + +#### Linux + +```bash +$ ./electron/electron your-app/ +``` + +#### macOS + +```bash +$ ./Electron.app/Contents/MacOS/Electron your-app/ +``` + +`Electron.app` here is part of the Electron's release package, you can download +it from [here](https://github.com/electron/electron/releases). + +### Run as a distribution + +After you're done writing your app, you can create a distribution by +following the [Application Distribution](./application-distribution.md) guide +and then executing the packaged app. + +### Try this Example + +Clone and run the code in this tutorial by using the [`electron/electron-quick-start`](https://github.com/electron/electron-quick-start) +repository. + +**Note**: Running this requires [Git](https://git-scm.com) and [Node.js](https://nodejs.org/en/download/) (which includes [npm](https://npmjs.org)) on your system. + +```bash +# Clone the repository +$ git clone https://github.com/electron/electron-quick-start +# Go into the repository +$ cd electron-quick-start +# Install dependencies +$ npm install +# Run the app +$ npm start +``` + +For more example apps, see the +[list of boilerplates](http://electron.atom.io/community/#boilerplates) +created by the awesome electron community. + +[share-data]: ../faq.md#how-to-share-data-between-web-pages diff --git a/docs-translations/th-TH/tutorial/repl.md b/docs-translations/th-TH/tutorial/repl.md new file mode 100644 index 0000000000..4f37c3a625 --- /dev/null +++ b/docs-translations/th-TH/tutorial/repl.md @@ -0,0 +1,26 @@ +# REPL + +Read-Eval-Print-Loop (REPL) is a simple, interactive computer programming +environment that takes single user inputs (i.e. single expressions), evaluates +them, and returns the result to the user. + +The `repl` module provides a REPL implementation that can be accessed using: + +* Assuming you have `electron` or `electron-prebuilt` installed as a local + project dependency: + + ```sh + ./node_modules/.bin/electron --interactive + ``` +* Assuming you have `electron` or `electron-prebuilt` installed globally: + + ```sh + electron --interactive + ``` + +This only creates a REPL for the main process. You can use the Console +tab of the Dev Tools to get a REPL for the renderer processes. + +**Note:** `electron --interactive` is not available on Windows. + +More information can be found in the [Node.js REPL docs](https://nodejs.org/dist/latest/docs/api/repl.html). diff --git a/docs-translations/th-TH/tutorial/security.md b/docs-translations/th-TH/tutorial/security.md new file mode 100644 index 0000000000..b0ebf8dc9b --- /dev/null +++ b/docs-translations/th-TH/tutorial/security.md @@ -0,0 +1,96 @@ +# Security, Native Capabilities, and Your Responsibility + +As web developers, we usually enjoy the strong security net of the browser - the +risks associated with the code we write are relatively small. Our websites are +granted limited powers in a sandbox, and we trust that our users enjoy a browser +built by a large team of engineers that is able to quickly respond to newly +discovered security threats. + +When working with Electron, it is important to understand that Electron is not +a web browser. It allows you to build feature-rich desktop applications with +familiar web technologies, but your code wields much greater power. JavaScript +can access the filesystem, user shell, and more. This allows you to build +high quality native applications, but the inherent security risks scale with the +additional powers granted to your code. + +With that in mind, be aware that displaying arbitrary content from untrusted +sources poses a severe security risk that Electron is not intended to handle. +In fact, the most popular Electron apps (Atom, Slack, Visual Studio Code, etc) +display primarily local content (or trusted, secure remote content without Node +integration) – if your application executes code from an online source, it is +your responsibility to ensure that the code is not malicious. + +## Chromium Security Issues and Upgrades + +While Electron strives to support new versions of Chromium as soon as possible, +developers should be aware that upgrading is a serious undertaking - involving +hand-editing dozens or even hundreds of files. Given the resources and +contributions available today, Electron will often not be on the very latest +version of Chromium, lagging behind by either days or weeks. + +We feel that our current system of updating the Chromium component strikes an +appropriate balance between the resources we have available and the needs of the +majority of applications built on top of the framework. We definitely are +interested in hearing more about specific use cases from the people that build +things on top of Electron. Pull requests and contributions supporting this +effort are always very welcome. + +## Ignoring Above Advice + +A security issue exists whenever you receive code from a remote destination and +execute it locally. As an example, consider a remote website being displayed +inside a browser window. If an attacker somehow manages to change said content +(either by attacking the source directly, or by sitting between your app and +the actual destination), they will be able to execute native code on the user's +machine. + +> :warning: Under no circumstances should you load and execute remote code with +Node integration enabled. Instead, use only local files (packaged together with +your application) to execute Node code. To display remote content, use the +`webview` tag and make sure to disable the `nodeIntegration`. + +#### Checklist + +This is not bulletproof, but at the least, you should attempt the following: + +* Only display secure (https) content +* Disable the Node integration in all renderers that display remote content + (setting `nodeIntegration` to `false` in `webPreferences`) +* Enable context isolation in all rendererers that display remote content + (setting `contextIsolation` to `true` in `webPreferences`) +* Do not disable `webSecurity`. Disabling it will disable the same-origin policy. +* Define a [`Content-Security-Policy`](http://www.html5rocks.com/en/tutorials/security/content-security-policy/) +, and use restrictive rules (i.e. `script-src 'self'`) +* [Override and disable `eval`](https://github.com/nylas/N1/blob/0abc5d5defcdb057120d726b271933425b75b415/static/index.js#L6-L8) +, which allows strings to be executed as code. +* Do not set `allowDisplayingInsecureContent` to true. +* Do not set `allowRunningInsecureContent` to true. +* Do not enable `experimentalFeatures` or `experimentalCanvasFeatures` unless + you know what you're doing. +* Do not use `blinkFeatures` unless you know what you're doing. +* WebViews: Do not add the `nodeintegration` attribute. +* WebViews: Do not use `disablewebsecurity` +* WebViews: Do not use `allowpopups` +* WebViews: Do not use `insertCSS` or `executeJavaScript` with remote CSS/JS. + +Again, this list merely minimizes the risk, it does not remove it. If your goal +is to display a website, a browser will be a more secure option. + +## Buffer Global + +Node's [Buffer](https://nodejs.org/api/buffer.html) class is currently available +as a global even when the `nodeintegration` attribute is not added. You can +delete this in your app by doing the following in your `preload` script: + +```js +delete global.Buffer +``` + +Deleting it may break Node modules used in your preload script and app since +many libraries expect it to be a global instead of requiring it directly via: + +```js +const {Buffer} = require('buffer') +``` + +The `Buffer` global may be removed in future major versions of Electron. diff --git a/docs-translations/th-TH/tutorial/supported-platforms.md b/docs-translations/th-TH/tutorial/supported-platforms.md new file mode 100644 index 0000000000..7474cec466 --- /dev/null +++ b/docs-translations/th-TH/tutorial/supported-platforms.md @@ -0,0 +1,31 @@ +# Supported Platforms + +Following platforms are supported by Electron: + +### macOS + +Only 64bit binaries are provided for macOS, and the minimum macOS version +supported is macOS 10.9. + +### Windows + +Windows 7 and later are supported, older operating systems are not supported +(and do not work). + +Both `ia32` (`x86`) and `x64` (`amd64`) binaries are provided for Windows. +Please note, the `ARM` version of Windows is not supported for now. + +### Linux + +The prebuilt `ia32` (`i686`) and `x64` (`amd64`) binaries of Electron are built on +Ubuntu 12.04, the `arm` binary is built against ARM v7 with hard-float ABI and +NEON for Debian Wheezy. + +Whether the prebuilt binary can run on a distribution depends on whether the +distribution includes the libraries that Electron is linked to on the building +platform, so only Ubuntu 12.04 is guaranteed to work, but following platforms +are also verified to be able to run the prebuilt binaries of Electron: + +* Ubuntu 12.04 and later +* Fedora 21 +* Debian 8 diff --git a/docs-translations/th-TH/tutorial/testing-on-headless-ci.md b/docs-translations/th-TH/tutorial/testing-on-headless-ci.md new file mode 100644 index 0000000000..557c358b12 --- /dev/null +++ b/docs-translations/th-TH/tutorial/testing-on-headless-ci.md @@ -0,0 +1,60 @@ +# Testing on Headless CI Systems (Travis CI, Jenkins) + +Being based on Chromium, Electron requires a display driver to function. +If Chromium can't find a display driver, Electron will simply fail to launch - +and therefore not executing any of your tests, regardless of how you are running +them. Testing Electron-based apps on Travis, Circle, Jenkins or similar Systems +requires therefore a little bit of configuration. In essence, we need to use +a virtual display driver. + +## Configuring the Virtual Display Server + +First, install [Xvfb](https://en.wikipedia.org/wiki/Xvfb). +It's a virtual framebuffer, implementing the X11 display server protocol - +it performs all graphical operations in memory without showing any screen output, +which is exactly what we need. + +Then, create a virtual xvfb screen and export an environment variable +called DISPLAY that points to it. Chromium in Electron will automatically look +for `$DISPLAY`, so no further configuration of your app is required. +This step can be automated with Paul Betts's +[xvfb-maybe](https://github.com/paulcbetts/xvfb-maybe): Prepend your test +commands with `xvfb-maybe` and the little tool will automatically configure +xvfb, if required by the current system. On Windows or macOS, it will simply +do nothing. + +``` +## On Windows or macOS, this just invokes electron-mocha +## On Linux, if we are in a headless environment, this will be equivalent +## to xvfb-run electron-mocha ./test/*.js +xvfb-maybe electron-mocha ./test/*.js +``` + +### Travis CI + +On Travis, your `.travis.yml` should look roughly like this: + +```yml +addons: + apt: + packages: + - xvfb + +install: + - export DISPLAY=':99.0' + - Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & +``` + +### Jenkins + +For Jenkins, a [Xvfb plugin is available](https://wiki.jenkins-ci.org/display/JENKINS/Xvfb+Plugin). + +### Circle CI + +Circle CI is awesome and has xvfb and `$DISPLAY` +[already setup, so no further configuration is required](https://circleci.com/docs/environment#browsers). + +### AppVeyor + +AppVeyor runs on Windows, supporting Selenium, Chromium, Electron and similar +tools out of the box - no configuration is required. diff --git a/docs-translations/th-TH/tutorial/using-native-node-modules.md b/docs-translations/th-TH/tutorial/using-native-node-modules.md new file mode 100644 index 0000000000..51b256ab10 --- /dev/null +++ b/docs-translations/th-TH/tutorial/using-native-node-modules.md @@ -0,0 +1,104 @@ +# Using Native Node Modules + +The native Node modules are supported by Electron, but since Electron is very +likely to use a different V8 version from the Node binary installed in your +system, you have to manually specify the location of Electron's headers when +building native modules. + +## How to install native modules + +Three ways to install native modules: + +### Using `npm` + +By setting a few environment variables, you can use `npm` to install modules +directly. + +An example of installing all dependencies for Electron: + +```bash +# Electron's version. +export npm_config_target=1.2.3 +# The architecture of Electron, can be ia32 or x64. +export npm_config_arch=x64 +export npm_config_target_arch=x64 +# Download headers for Electron. +export npm_config_disturl=https://atom.io/download/electron +# Tell node-pre-gyp that we are building for Electron. +export npm_config_runtime=electron +# Tell node-pre-gyp to build module from source code. +export npm_config_build_from_source=true +# Install all dependencies, and store cache to ~/.electron-gyp. +HOME=~/.electron-gyp npm install +``` + +### Installing modules and rebuilding for Electron + +You can also choose to install modules like other Node projects, and then +rebuild the modules for Electron with the [`electron-rebuild`][electron-rebuild] +package. This module can get the version of Electron and handle the manual steps +of downloading headers and building native modules for your app. + +An example of installing `electron-rebuild` and then rebuild modules with it: + +```bash +npm install --save-dev electron-rebuild + +# Every time you run "npm install", run this: +./node_modules/.bin/electron-rebuild + +# On Windows if you have trouble, try: +.\node_modules\.bin\electron-rebuild.cmd +``` + +### Manually building for Electron + +If you are a developer developing a native module and want to test it against +Electron, you might want to rebuild the module for Electron manually. You can +use `node-gyp` directly to build for Electron: + +```bash +cd /path-to-module/ +HOME=~/.electron-gyp node-gyp rebuild --target=1.2.3 --arch=x64 --dist-url=https://atom.io/download/electron +``` + +The `HOME=~/.electron-gyp` changes where to find development headers. The +`--target=1.2.3` is version of Electron. The `--dist-url=...` specifies +where to download the headers. The `--arch=x64` says the module is built for +64bit system. + +## Troubleshooting + +If you installed a native module and found it was not working, you need to check +following things: + +* The architecture of module has to match Electron's architecture (ia32 or x64). +* After you upgraded Electron, you usually need to rebuild the modules. +* When in doubt, run `electron-rebuild` first. + +## Modules that rely on `prebuild` + +[`prebuild`](https://github.com/mafintosh/prebuild) provides a way to easily +publish native Node modules with prebuilt binaries for multiple versions of Node +and Electron. + +If modules provide binaries for the usage in Electron, make sure to omit +`--build-from-source` and the `npm_config_build_from_source` environment +variable in order to take full advantage of the prebuilt binaries. + +## Modules that rely on `node-pre-gyp` + +The [`node-pre-gyp` tool][node-pre-gyp] provides a way to deploy native Node +modules with prebuilt binaries, and many popular modules are using it. + +Usually those modules work fine under Electron, but sometimes when Electron uses +a newer version of V8 than Node, and there are ABI changes, bad things may +happen. So in general it is recommended to always build native modules from +source code. + +If you are following the `npm` way of installing modules, then this is done +by default, if not, you have to pass `--build-from-source` to `npm`, or set the +`npm_config_build_from_source` environment variable. + +[electron-rebuild]: https://github.com/paulcbetts/electron-rebuild +[node-pre-gyp]: https://github.com/mapbox/node-pre-gyp diff --git a/docs-translations/th-TH/tutorial/using-pepper-flash-plugin.md b/docs-translations/th-TH/tutorial/using-pepper-flash-plugin.md new file mode 100644 index 0000000000..839f00b520 --- /dev/null +++ b/docs-translations/th-TH/tutorial/using-pepper-flash-plugin.md @@ -0,0 +1,82 @@ +# Using Pepper Flash Plugin + +Electron supports the Pepper Flash plugin. To use the Pepper Flash plugin in +Electron, you should manually specify the location of the Pepper Flash plugin +and then enable it in your application. + +## Prepare a Copy of Flash Plugin + +On macOS and Linux, the details of the Pepper Flash plugin can be found by +navigating to `chrome://plugins` in the Chrome browser. Its location and version +are useful for Electron's Pepper Flash support. You can also copy it to another +location. + +## Add Electron Switch + +You can directly add `--ppapi-flash-path` and `--ppapi-flash-version` to the +Electron command line or by using the `app.commandLine.appendSwitch` method +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) { + case 'win32': + pluginName = 'pepflashplayer.dll' + break + case 'darwin': + pluginName = 'PepperFlashPlayer.plugin' + break + case 'linux': + pluginName = 'libpepflashplayer.so' + break +} +app.commandLine.appendSwitch('ppapi-flash-path', path.join(__dirname, pluginName)) + +// Optional: Specify flash version, for example, v17.0.0.169 +app.commandLine.appendSwitch('ppapi-flash-version', '17.0.0.169') + +app.on('ready', () => { + let win = new BrowserWindow({ + width: 800, + height: 600, + webPreferences: { + plugins: true + } + }) + win.loadURL(`file://${__dirname}/index.html`) + // Something else +}) +``` + +You can also try loading the system wide Pepper Flash plugin instead of shipping +the plugins yourself, its path can be received by calling +`app.getPath('pepperFlashSystemPlugin')`. + +## Enable Flash Plugin in a `` Tag + +Add `plugins` attribute to `` tag. + +```html + +``` + +## Troubleshooting + +You can check if Pepper Flash plugin was loaded by inspecting +`navigator.plugins` in the console of devtools (although you can't know if the +plugin's path is correct). + +The architecture of Pepper Flash plugin has to match Electron's one. On Windows, +a common error is to use 32bit version of Flash plugin against 64bit version of +Electron. + +On Windows the path passed to `--ppapi-flash-path` has to use `\` as path +delimiter, using POSIX-style paths will not work. + +For some operations, such as streaming media using RTMP, it is necessary to grant wider permissions to players’ `.swf` files. One way of accomplishing this, is to use [nw-flash-trust](https://github.com/szwacz/nw-flash-trust). diff --git a/docs-translations/th-TH/tutorial/using-selenium-and-webdriver.md b/docs-translations/th-TH/tutorial/using-selenium-and-webdriver.md new file mode 100644 index 0000000000..464d1ce99f --- /dev/null +++ b/docs-translations/th-TH/tutorial/using-selenium-and-webdriver.md @@ -0,0 +1,172 @@ +# Using Selenium and WebDriver + +From [ChromeDriver - WebDriver for Chrome][chrome-driver]: + +> WebDriver is an open source tool for automated testing of web apps across many +> browsers. It provides capabilities for navigating to web pages, user input, +> JavaScript execution, and more. ChromeDriver is a standalone server which +> implements WebDriver's wire protocol for Chromium. It is being developed by +> members of the Chromium and WebDriver teams. + +## Setting up Spectron + +[Spectron][spectron] is the officially supported ChromeDriver testing framework +for Electron. It is built on top of [WebdriverIO](http://webdriver.io/) and +has helpers to access Electron APIs in your tests and bundles ChromeDriver. + +```bash +$ npm install --save-dev spectron +``` + +```javascript +// A simple test to verify a visible window is opened with a title +var Application = require('spectron').Application +var assert = require('assert') + +var app = new Application({ + path: '/Applications/MyApp.app/Contents/MacOS/MyApp' +}) + +app.start().then(function () { + // Check if the window is visible + return app.browserWindow.isVisible() +}).then(function (isVisible) { + // Verify the window is visible + assert.equal(isVisible, true) +}).then(function () { + // Get the window's title + return app.client.getTitle() +}).then(function (title) { + // Verify the window's title + assert.equal(title, 'My App') +}).catch(function (error) { + // Log any failures + console.error('Test failed', error.message) +}).then(function () { + // Stop the application + return app.stop() +}) +``` + +## Setting up with WebDriverJs + +[WebDriverJs](https://code.google.com/p/selenium/wiki/WebDriverJs) provides +a Node package for testing with web driver, we will use it as an example. + +### 1. Start ChromeDriver + +First you need to download the `chromedriver` binary, and run it: + +```bash +$ npm install electron-chromedriver +$ ./node_modules/.bin/chromedriver +Starting ChromeDriver (v2.10.291558) on port 9515 +Only local connections are allowed. +``` + +Remember the port number `9515`, which will be used later + +### 2. Install WebDriverJS + +```bash +$ npm install selenium-webdriver +``` + +### 3. Connect to ChromeDriver + +The usage of `selenium-webdriver` with Electron is basically the same with +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 driver = new webdriver.Builder() + // The "9515" is the port opened by chrome driver. + .usingServer('http://localhost:9515') + .withCapabilities({ + chromeOptions: { + // Here is the path to your Electron binary. + binary: '/Path-to-Your-App.app/Contents/MacOS/Electron' + } + }) + .forBrowser('electron') + .build() + +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) + +driver.quit() +``` + +## Setting up with WebdriverIO + +[WebdriverIO](http://webdriver.io/) provides a Node package for testing with web +driver. + +### 1. Start ChromeDriver + +First you need to download the `chromedriver` binary, and run it: + +```bash +$ npm install electron-chromedriver +$ ./node_modules/.bin/chromedriver --url-base=wd/hub --port=9515 +Starting ChromeDriver (v2.10.291558) on port 9515 +Only local connections are allowed. +``` + +Remember the port number `9515`, which will be used later + +### 2. Install WebdriverIO + +```bash +$ npm install webdriverio +``` + +### 3. Connect to chrome driver + +```javascript +const webdriverio = require('webdriverio') +const options = { + host: 'localhost', // Use localhost as chrome driver server + port: 9515, // "9515" is the port opened by chrome driver. + desiredCapabilities: { + browserName: 'chrome', + chromeOptions: { + binary: '/Path-to-Your-App/electron', // Path to your Electron binary. + args: [/* cli arguments */] // Optional, perhaps 'app=' + /path/to/your/app/ + } + } +} + +let client = webdriverio.remote(options) + +client + .init() + .url('http://google.com') + .setValue('#q', 'webdriverio') + .click('#btnG') + .getTitle().then((title) => { + console.log('Title was: ' + title) + }) + .end() +``` + +## Workflow + +To test your application without rebuilding Electron, simply +[place](https://github.com/electron/electron/blob/master/docs/tutorial/application-distribution.md) +your app source into Electron's resource directory. + +Alternatively, pass an argument to run with your electron binary that points to +your app's folder. This eliminates the need to copy-paste your app into +Electron's resource directory. + +[chrome-driver]: https://sites.google.com/a/chromium.org/chromedriver/ +[spectron]: http://electron.atom.io/spectron diff --git a/docs-translations/th-TH/tutorial/using-widevine-cdm-plugin.md b/docs-translations/th-TH/tutorial/using-widevine-cdm-plugin.md new file mode 100644 index 0000000000..512da7a041 --- /dev/null +++ b/docs-translations/th-TH/tutorial/using-widevine-cdm-plugin.md @@ -0,0 +1,85 @@ +# Using Widevine CDM Plugin + +In Electron you can use the Widevine CDM plugin shipped with Chrome browser. + +## Getting the plugin + +Electron doesn't ship with the Widevine CDM plugin for license reasons, to get +it, you need to install the official Chrome browser first, which should match +the architecture and Chrome version of the Electron build you use. + +**Note:** The major version of Chrome browser has to be the same with the Chrome +version used by Electron, otherwise the plugin will not work even though +`navigator.plugins` would show it has been loaded. + +### Windows & macOS + +Open `chrome://components/` in Chrome browser, find `WidevineCdm` and make +sure it is up to date, then you can find all the plugin binaries from the +`APP_DATA/Google/Chrome/WidevineCDM/VERSION/_platform_specific/PLATFORM_ARCH/` +directory. + +`APP_DATA` is system's location for storing app data, on Windows it is +`%LOCALAPPDATA%`, on macOS it is `~/Library/Application Support`. `VERSION` is +Widevine CDM plugin's version string, like `1.4.8.866`. `PLATFORM` is `mac` or +`win`. `ARCH` is `x86` or `x64`. + +On Windows the required binaries are `widevinecdm.dll` and +`widevinecdmadapter.dll`, on macOS they are `libwidevinecdm.dylib` and +`widevinecdmadapter.plugin`. You can copy them to anywhere you like, but they +have to be put together. + +### Linux + +On Linux the plugin binaries are shipped together with Chrome browser, you can +find them under `/opt/google/chrome`, the filenames are `libwidevinecdm.so` and +`libwidevinecdmadapter.so`. + +## Using the plugin + +After getting the plugin files, you should pass the `widevinecdmadapter`'s path +to Electron with `--widevine-cdm-path` command line switch, and the plugin's +version with `--widevine-cdm-version` switch. + +**Note:** Though only the `widevinecdmadapter` binary is passed to Electron, the +`widevinecdm` binary has to be put aside it. + +The command line switches have to be passed before the `ready` event of `app` +module gets emitted, and the page that uses this plugin must have plugin +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') +// The version of plugin can be got from `chrome://plugins` page in Chrome. +app.commandLine.appendSwitch('widevine-cdm-version', '1.4.8.866') + +let win = null +app.on('ready', () => { + win = new BrowserWindow({ + webPreferences: { + // The `plugins` have to be enabled. + plugins: true + } + }) + win.show() +}) +``` + +## Verifying the plugin + +To verify whether the plugin works, you can use following ways: + +* Open devtools and check whether `navigator.plugins` includes the Widevine +CDM plugin. +* Open https://shaka-player-demo.appspot.com/ and load a manifest that uses +`Widevine`. +* Open http://www.dash-player.com/demo/drm-test-area/, check whether the page +says `bitdash uses Widevine in your browser`, then play the video. diff --git a/docs-translations/th-TH/tutorial/windows-store-guide.md b/docs-translations/th-TH/tutorial/windows-store-guide.md new file mode 100644 index 0000000000..2075691baa --- /dev/null +++ b/docs-translations/th-TH/tutorial/windows-store-guide.md @@ -0,0 +1,161 @@ +# Windows Store Guide + +With Windows 8, the good old win32 executable got a new sibling: The Universal +Windows Platform. The new `.appx` format does not only enable a number of new +powerful APIs like Cortana or Push Notifications, but through the Windows Store, +also simplifies installation and updating. + +Microsoft [developed a tool that compiles Electron apps as `.appx` packages][electron-windows-store], +enabling developers to use some of the goodies found in the new application +model. This guide explains how to use it - and what the capabilities and +limitations of an Electron AppX package are. + +## Background and Requirements + +Windows 10 "Anniversary Update" is able to run win32 `.exe` binaries by +launching them together with a virtualized filesystem and registry. Both are +created during compilation by running app and installer inside a Windows +Container, allowing Windows to identify exactly which modifications to the +operating system are done during installation. Pairing the executable with a +virtual filesystem and a virtual registry allows Windows to enable one-click +installation and uninstallation. + +In addition, the exe is launched inside the appx model - meaning that it can use +many of the APIs available to the Universal Windows Platform. To gain even more +capabilities, an Electron app can pair up with an invisible UWP background task +launched together with the `exe` - sort of launched as a sidekick to run tasks +in the background, receive push notifications, or to communicate with other UWP +applications. + +To compile any existing Electron app, ensure that you have the following +requirements: + +* Windows 10 with Anniversary Update (released August 2nd, 2016) +* The Windows 10 SDK, [downloadable here][windows-sdk] +* At least Node 4 (to check, run `node -v`) + +Then, go and install the `electron-windows-store` CLI: + +``` +npm install -g electron-windows-store +``` + +## Step 1: Package Your Electron Application + +Package the application using [electron-packager][electron-packager] (or a similar tool). +Make sure to remove `node_modules` that you don't need in your final application, since +any module you don't actually need will just increase your application's size. + +The output should look roughly like this: + +``` +├── Ghost.exe +├── LICENSE +├── content_resources_200_percent.pak +├── content_shell.pak +├── d3dcompiler_47.dll +├── ffmpeg.dll +├── icudtl.dat +├── libEGL.dll +├── libGLESv2.dll +├── locales +│   ├── am.pak +│   ├── ar.pak +│   ├── [...] +├── natives_blob.bin +├── node.dll +├── resources +│   ├── app +│   └── atom.asar +├── snapshot_blob.bin +├── squirrel.exe +├── ui_resources_200_percent.pak +└── xinput1_3.dll +``` + +## Step 2: Running electron-windows-store + +From an elevated PowerShell (run it "as Administrator"), run +`electron-windows-store` with the required parameters, passing both the input +and output directories, the app's name and version, and confirmation that +`node_modules` should be flattened. + +``` +electron-windows-store ` + --input-directory C:\myelectronapp ` + --output-directory C:\output\myelectronapp ` + --flatten true ` + --package-version 1.0.0.0 ` + --package-name myelectronapp +``` + +Once executed, the tool goes to work: It accepts your Electron app as an input, +flattening the `node_modules`. Then, it archives your application as `app.zip`. +Using an installer and a Windows Container, the tool creates an "expanded" AppX +package - including the Windows Application Manifest (`AppXManifest.xml`) as +well as the virtual file system and the virtual registry inside your output +folder. + +Once the expanded AppX files are created, the tool uses the Windows App Packager +(`MakeAppx.exe`) to create a single-file AppX package from those files on disk. +Finally, the tool can be used to create a trusted certificate on your computer +to sign the new AppX package. With the signed AppX package, the CLI can also +automatically install the package on your machine. + +## Step 3: Using the AppX Package + +In order to run your package, your users will need Windows 10 with the so-called +"Anniversary Update" - details on how to update Windows can be found [here][how-to-update]. + +In opposition to traditional UWP apps, packaged apps currently need to undergo a +manual verification process, for which you can apply [here][centennial-campaigns]. +In the meantime, all users will be able to just install your package by double-clicking it, +so a submission to the store might not be necessary if you're simply looking for an +easier installation method. In managed environments (usually enterprises), the +`Add-AppxPackage` [PowerShell Cmdlet can be used to install it in an automated fashion][add-appxpackage]. + +Another important limitation is that the compiled AppX package still contains a +win32 executable - and will therefore not run on Xbox, HoloLens, or Phones. + +## Optional: Add UWP Features using a BackgroundTask +You can pair your Electron app up with an invisible UWP background task that +gets to make full use of Windows 10 features - like push notifications, +Cortana integration, or live tiles. + +To check out how an Electron app that uses a background task to send toast +notifications and live tiles, [check out the Microsoft-provided sample][background-task]. + +## Optional: Convert using Container Virtualiziation + +To generate the AppX package, the `electron-windows-store` CLI uses a template +that should work for most Electron apps. However, if you are using a custom +installer, or should you experience any trouble with the generated package, you +can attempt to create a package using compilation with a Windows Container - in +that mode, the CLI will install and run your application in blank Windows Container +to determine what modifications your application is exactly doing to the operating +system. + +Before running the CLI for the, you will have to setup the "Windows Desktop App +Converter". This will take a few minutes, but don't worry - you only have to do +this once. Download and Desktop App Converter from [here][app-converter]. +You will receive two files: `DesktopAppConverter.zip` and `BaseImage-14316.wim`. + +1. Unzip `DesktopAppConverter.zip`. From an elevated PowerShell (opened with + "run as Administrator", ensure that your systems execution policy allows us to + run everything we intend to run by calling `Set-ExecutionPolicy bypass`. +2. Then, run the installation of the Desktop App Converter, passing in the + location of the Windows base Image (downloaded as `BaseImage-14316.wim`), by + calling `.\DesktopAppConverter.ps1 -Setup -BaseImage .\BaseImage-14316.wim`. +3. If running the above command prompts you for a reboot, please restart your + machine and run the above command again after a successful restart. + +Once installation succeeded, you can move on to compiling your Electron app. + +[windows-sdk]: https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk +[app-converter]: https://www.microsoft.com/en-us/download/details.aspx?id=51691 +[add-appxpackage]: https://technet.microsoft.com/en-us/library/hh856048.aspx +[electron-packager]: https://github.com/electron-userland/electron-packager +[electron-windows-store]: https://github.com/catalystcode/electron-windows-store +[background-task]: https://github.com/felixrieseberg/electron-uwp-background +[centennial-campaigns]: https://developer.microsoft.com/en-us/windows/projects/campaigns/desktop-bridge +[how-to-update]: https://blogs.windows.com/windowsexperience/2016/08/02/how-to-get-the-windows-10-anniversary-update From f4ea70d2c25ae132ae79237c6dadf4a5b93bdf32 Mon Sep 17 00:00:00 2001 From: wit543 Date: Thu, 19 Jan 2017 15:05:32 +0700 Subject: [PATCH 105/925] Update about.md --- docs-translations/th-TH/tutorial/about.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs-translations/th-TH/tutorial/about.md b/docs-translations/th-TH/tutorial/about.md index 0344c9a1f9..6f7e3cf236 100644 --- a/docs-translations/th-TH/tutorial/about.md +++ b/docs-translations/th-TH/tutorial/about.md @@ -1,6 +1,6 @@ -# About Electron +# เกี่ยวกับ Electron -[Electron](http://electron.atom.io) is an open source library developed by GitHub for building cross-platform desktop applications with HTML, CSS, and JavaScript. Electron accomplishes this by combining [Chromium](https://www.chromium.org/Home) and [Node.js](https://nodejs.org) into a single runtime and apps can be packaged for Mac, Windows, and Linux. +[Electron](http://electron.atom.io) เป็นโอเพ่นซอร์สไลบรารี พัฒนา โดย GitHub สำหรับการสร้างการใช้งานเดสก์ทอปข้ามแพลตฟอร์มกับ HTML, CSS และ JavaScript Electron เกิดขึ้นได้โดยการ่วม [Chromium](https://www.chromium.org/Home) กับ [Node.js](https://nodejs.org) เข้าด้วยกันเป็นหนึงruntimeและปพลิเคชันสามารถบรรจุสำหรับ Mac, Windows และ Linux Electron began in 2013 as the framework on which [Atom](https://atom.io), GitHub's hackable text editor, would be built. The two were open sourced in the Spring of 2014. @@ -22,7 +22,7 @@ Electron's version of Chromium is usually updated within one or two weeks after When a new version of Node.js is released, Electron usually waits about a month before upgrading in order to bring in a more stable version. -In Electron, Node.js and Chromium share a single V8 instance—usually the version that Chromium is using. Most of the time this _just works_ but sometimes it means patching Node.js. +ใน Electron, Node.js and Chromium share a single V8 instance—usually the version that Chromium is using. Most of the time this _just works_ but sometimes it means patching Node.js. ### Versioning From 0f55468042cf2c5c387b32171dec4f9cc9075cc0 Mon Sep 17 00:00:00 2001 From: wit543 Date: Thu, 19 Jan 2017 15:18:41 +0700 Subject: [PATCH 106/925] =?UTF-8?q?=F0=9F=93=9D=20Thai:=20translate=20the?= =?UTF-8?q?=20header=20++?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit translate the header and opening statement --- docs-translations/th-TH/tutorial/quick-start.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs-translations/th-TH/tutorial/quick-start.md b/docs-translations/th-TH/tutorial/quick-start.md index d95957a48c..57e726ef52 100644 --- a/docs-translations/th-TH/tutorial/quick-start.md +++ b/docs-translations/th-TH/tutorial/quick-start.md @@ -1,7 +1,7 @@ -# Quick Start +# การเริ่มต้นอย่างรวดเร็ว -Electron enables you to create desktop applications with pure JavaScript by -providing a runtime with rich native (operating system) APIs. You could see it +Electronช่วยให้คุณสามารถสร้างโปรแกรมคอมพิวเตอร์ด้วย JavaScript โดย +ให้รันไทม์ที่สอดคล้องกับ APIs ระดับระบบปฏิบัติการ You could see it as a variant of the Node.js runtime that is focused on desktop applications instead of web servers. From d3bf36e12a48e05b96a7be7261054cec6d8374c5 Mon Sep 17 00:00:00 2001 From: ThePooE Date: Thu, 19 Jan 2017 17:42:51 +0700 Subject: [PATCH 107/925] :memo: Thai: translated styleguide.md --- docs-translations/th-TH/styleguide.md | 167 +++++++++++--------------- 1 file changed, 67 insertions(+), 100 deletions(-) diff --git a/docs-translations/th-TH/styleguide.md b/docs-translations/th-TH/styleguide.md index 8f59c8a341..85caeea5dd 100644 --- a/docs-translations/th-TH/styleguide.md +++ b/docs-translations/th-TH/styleguide.md @@ -1,85 +1,69 @@ -# Electron Documentation Styleguide +# ลักษณะเอกสาร Electron -These are the guidelines for writing Electron documentation. +นี้คือกฎในการเขียนเอกสารประกอบ Electron -## Titles +## หัวข้อ -* Each page must have a single `#`-level title at the top. -* Chapters in the same page must have `##`-level titles. -* Sub-chapters need to increase the number of `#` in the title according to - their nesting depth. -* All words in the page's title must be capitalized, except for conjunctions - like "of" and "and" . -* Only the first word of a chapter title must be capitalized. +* ทุกๆเพจจะมี `#` อยู่ข้างบนสุดของเอกสาร +* ทุกๆบทจะต้องมี `##` ในหัวข้อ +* ทุกๆบทย่อยจะเพิ่ม `#` ลงไปในหัวข้อตามความลึกของบทย่อย -Using `Quick Start` as example: +ยกตัวอย่างเช่น `การเริ่มต้น`: ```markdown -# Quick Start +# การเริ่มต้น ... -## Main process +## โปรเซสหลัก ... -## Renderer process +## โปรเซส render ... -## Run your app +## การรันแอพ ... -### Run as a distribution - -... - -### Manually downloaded Electron binary - -... ``` -For API references, there are exceptions to this rule. +สำหรับการอ้างอิงของ API จะไม่ใช้กฎนี้ -## Markdown rules +## กฎของ Markdown -* Use `bash` instead of `cmd` in code blocks (due to the syntax highlighter). -* Lines should be wrapped at 80 columns. -* No nesting lists more than 2 levels (due to the markdown renderer). -* All `js` and `javascript` code blocks are linted with +* ใช้ `bash` แทน `cmd` ในการเขียน code blocks (เพราะตัวไฮไลท์ syntax) +* บรรทัดควรที่จะเริ่มต้นใหม่ที่ 80 คอลัมน์ +* ไม่ควรใส่ลิสต์ที่มีมากกว่าสองชั้น (เพราะตัว render ของ markdown) +* ทุก `js` แลพ `javascript` code blocks จะไฮไลท์ด้วย [standard-markdown](http://npm.im/standard-markdown). -## Picking words +## การใช้คำ -* Use "will" over "would" when describing outcomes. -* Prefer "in the ___ process" over "on". +* ใช้ 'จะ' แทนที่ 'ควรจะ' ตอนอธิบายผลลัทธ์ -## API references +## การอ้างอิงของ API -The following rules only apply to the documentation of APIs. +กฎดังต่อไปนี้จะใช้สำหรับเอกสาร API เท่านั้น -### Page title +### หัวข้อเพจ -Each page must use the actual object name returned by `require('electron')` -as the title, such as `BrowserWindow`, `autoUpdater`, and `session`. +ทุกๆเพจจะใช้ชื่อของ object ที่ได้รับจาก `require('electron')` เป็นหัวข้อ อาทิเช่น `BrowserWindow`, `autoUpdater` และ `session` -Under the page tile must be a one-line description starting with `>`. - -Using `session` as example: +ข้างล่างหัวข้อจะเป็นคำอธิบายหนึ่งบรรทัดเริ่มต้นด้วย `>` ```markdown # session -> Manage browser sessions, cookies, cache, proxy settings, etc. +> คำอธิบาย ``` -### Module methods and events +### โมดูล methods และ events -For modules that are not classes, their methods and events must be listed under -the `## Methods` and `## Events` chapters. +สำหรับโมดูลที่ไม่ใช่คราสนั้น methods และ events ของมันจะต้องอยู่ในลิสต์ภายใต้บท `## Methods` และ `## Events` -Using `autoUpdater` as an example: +ยกตัวอย่างเช่น `autoUpdater`: ```markdown # autoUpdater @@ -93,21 +77,20 @@ Using `autoUpdater` as an example: ### `autoUpdater.setFeedURL(url[, requestHeaders])` ``` -### Classes +### คราส (Classes) -* API classes or classes that are part of modules must be listed under a - `## Class: TheClassName` chapter. -* One page can have multiple classes. -* Constructors must be listed with `###`-level titles. -* [Static Methods](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/static) must be listed under a `### Static Methods` chapter. -* [Instance Methods](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes#Prototype_methods) must be listed under an `### Instance Methods` chapter. -* All methods that have a return value must start their description with "Returns `[TYPE]` - Return description" - * If the method returns an `Object`, its structure can be specified using a colon followed by a newline then an unordered list of properties in the same style as function parameters. -* Instance Events must be listed under an `### Instance Events` chapter. -* Instance Properties must be listed under an `### Instance Properties` chapter. - * Instance properties must start with "A [Property Type] ..." +* คราส API หรือ คราสที่เป็นส่วนของโมดูลจะต้องอยู่ในบท `## Class: TheClassName` +* หนึ่งเพจสามารถมีหลายคราสได้ +* Constructors จะต้องอยู่ใน `###` +* [Static Methods](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/static) จะต้องอยู่ในบทของ `### Static Method` +* [Instance Methods](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes#Prototype_methods) จะต้องอยู่ในบทของ `### Instance Methods` +* ทุกๆ methods ที่มีค่า รีเทิร์น(return) จะต้องเริ่มต้นคำอธิบายด้วย "Returns `[TYPE]` - คำอธิบายค่ารีเทิร์น" + * ถ้า method รีเทิร์น `Object` โครงสร้างจองมันจะอธิบายได้ด้วยการใช้ ','(โคล่อน) ตามด้วยบรรทัดใหม่ จากนั้นก็ ลิสต์ของคุณสมบัติที่ไม่เรียงกัน (เหมือนกับ parametres ของฟังค์ชั่น) +* Instance ของ Events จะต้องอยู่ในบทของ `### Instance Events` +* Instance ของ Properties จะต้องอยู่ในบทของ `### Instance Properties` + * จะเริ่มต้นด้วย "A [Type ของ คุณสมบัติ]" -Using the `Session` and `Cookies` classes as an example: +ยกตัวอย่างเช่น `Session` และ `Cookies` คราส: ```markdown # session @@ -143,7 +126,7 @@ Using the `Session` and `Cookies` classes as an example: ### Methods -The methods chapter must be in the following form: +บท methods จะอยู่ในรูปแบบดังนี้: ```markdown ### `objectName.methodName(required[, optional]))` @@ -154,51 +137,41 @@ The methods chapter must be in the following form: ... ``` -The title can be `###` or `####`-levels depending on whether it is a method of -a module or a class. +หัวข้อจะสามารถอยู่ในรูปของ `###` หรือ `####` (ตามว่าเป็น method ของโมดูลหรือของคราส) -For modules, the `objectName` is the module's name. For classes, it must be the -name of the instance of the class, and must not be the same as the module's -name. +สำหรับโมดูล `objectName` คือชื่อของโมดูล แต่ว่าสำหรับคราสนั้น ชื่อจะต้องเป็น instance ของคราสและจะต้องไม่ซ้ำกับชื่อโมดูล -For example, the methods of the `Session` class under the `session` module must -use `ses` as the `objectName`. +ยกตัวอย่างเช่น method ของคราส `Session` ที่อยู่ใน `session` โมดูลจะต้องใช้ `ses` เหมือนกับ `objectName`. -The optional arguments are notated by square brackets `[]` surrounding the optional argument -as well as the comma required if this optional argument follows another -argument: +หากมี arguments เพิ่มเตินนั้นจะได้รับการบันทึกภายใน `[]` รวมถึงคอมม่ถ้าหากว่า arguement นี้ตามด้วย argument เสริมอื่นๆ ``` required[, optional] ``` -Below the method is more detailed information on each of the arguments. The type -of argument is notated by either the common types: +ข้างล่าง method จะเป็นข้อคาวมโดยละเอียดเกี่ยวกับ arguments อื่นๆ, ชนิค (Type) ของ argument จะโดนบันทีกตาม type ทั่วไป * [`String`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) * [`Number`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number) * [`Object`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) * [`Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) * [`Boolean`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean) -* Or a custom type like Electron's [`WebContent`](api/web-contents.md) +* หรือ type พิเศษเช่น Electron [`WebContent`](api/web-contents.md) -If an argument or a method is unique to certain platforms, those platforms are -denoted using a space-delimited italicized list following the datatype. Values -can be `macOS`, `Windows`, or `Linux`. +หาก argument หรือ method มีเฉพาะเจาะจงกับ platform นั้น ให้ใช้ตัว italic (เอียง) ลิสต์ตาม datatype +โดยที่ตัวลิสต์นั้นจะจำกัดอยู่ที่ `macOS`, `Windows`, หรือ `Linux` เท่านั้น ```markdown -* `animate` Boolean (optional) _macOS_ _Windows_ - Animate the thing. +* `animate` Boolean (optional) _macOS_ _Windows_ - เล่นแอนนิเมชัั่น. ``` -`Array` type arguments must specify what elements the array may include in -the description below. +Arguments ชนิด `Array` จะต้องบอกว่า array นั้นจะใส่อะไรลงไปตามข้อมูลด้านล่าง -The description for `Function` type arguments should make it clear how it may be -called and list the types of the parameters that will be passed to it. +ข้อความอธิบาย arguments ของ ฟังค์ชั่น (function) จะต้องบอกเจาะจงถึงวิธีการใช้งานและชนิดของ parametres ที่ฟังค์ชั่นนั้นรับ -### Events +### อีเว้น (Events) -The events chapter must be in following form: +บทอีเว้นจะต้องอยู่ในรูปแบบดังนี้: ```markdown ### Event: 'wake-up' @@ -210,14 +183,13 @@ Returns: ... ``` -The title can be `###` or `####`-levels depending on whether it is an event of -a module or a class. +หัวข้อจะสามารถอยู่ในรูปของ `###` หรือ `####` (ตามว่าเป็นอีเว้นของโมดูลหรือของคราส) -The arguments of an event follow the same rules as methods. +แบบเอกสาร arguments ของอีเว้นจะใช้กฎเดียวกับ methods -### Properties +### คุณสมบัติ (Properties) -The properties chapter must be in following form: +บทคุณสมบัติจะต้องอยู่ในรูปแบบดังนี้: ```markdown ### session.defaultSession @@ -225,22 +197,17 @@ The properties chapter must be in following form: ... ``` -The title can be `###` or `####`-levels depending on whether it is a property of -a module or a class. +หัวข้อจะสามารถอยู่ในรูปของ `###` หรือ `####` (ตามว่าเป็นคุณสมบัติของโมดูลหรือของคราส) -## Documentation Translations +## การแปลเอกสาร -Translations of the Electron docs are located within the `docs-translations` -directory. +เอกสารของ Electron ที่ถูกแปลแล้ว จะอยู่ในโฟลเดอร์ `docs-translation` -To add another set (or partial set): +ในการสร้างเซ็ตของภาษาใหม่ (เพิ่มในบางส่วน) -* Create a subdirectory named by language abbreviation. -* Translate the files. -* Update the `README.md` within your language directory to link to the files - you have translated. -* Add a link to your translation directory on the main Electron - [README](https://github.com/electron/electron#documentation-translations). +* สร้างที่อยู่ย่อยตามตัวย่อของภาษา +* แปลภาษา +* อัพเดท `README.md` ภายในเพื่อเป็นสารลิงค์ไฟล์ต่างๆที่แปลแล้ว +* เพิ่มลิ้งค์ลง [README](https://github.com/electron/electron#documentation-translations) หลักของ Electron -Note that the files under `docs-translations` must only include the translated -ones, the original English files should not be copied there. +โน้ต: ไฟล์ที่อยู่ใน `docs-translations` จะมีแค่ที่แปลแล้วเท่านั้น ไฟล์ต้นตำรับภาษาอังกริษไม่ควรจะโดนก้อปปี้ลงไปด้วย From 43b09b7360d221ad67c10ab99a592dff7cc7ff45 Mon Sep 17 00:00:00 2001 From: ThePooE Date: Fri, 20 Jan 2017 02:35:50 +0700 Subject: [PATCH 108/925] :memo: Thai: translated glossary.md --- docs-translations/th-TH/glossary.md | 165 ++++++++++++++-------------- 1 file changed, 81 insertions(+), 84 deletions(-) diff --git a/docs-translations/th-TH/glossary.md b/docs-translations/th-TH/glossary.md index 6019bddeee..4b3e7ec76b 100644 --- a/docs-translations/th-TH/glossary.md +++ b/docs-translations/th-TH/glossary.md @@ -1,143 +1,140 @@ -# Glossary +# อภิธานศัพท์ (Glossary) -This page defines some terminology that is commonly used in Electron development. +เพจนี้จะบ่งบอกคำศัพท์ที่ถูกใช่บ่อยๆในการพัฒนา Electron ### ASAR -ASAR stands for Atom Shell Archive Format. An [asar][asar] archive is a simple -`tar`-like format that concatenates files into a single file. Electron can read -arbitrary files from it without unpacking the whole file. +[ASAR (Atom Shell Archive Format)][asar] เป็น extension ของไฟล์ที่มีลักษณะความคล้ายครีง `tar` ที่รวมหลายไฟล์ลงไปในไฟล์เดียว Electron สามารถอ่านไฟล์ได้เลยโดยที่ไม่ต้อง unpack ไฟล์ -The ASAR format was created primarily to improve performance on Windows... TODO +กำลังทำ: ไฟล์ประเภท ASAR นั้น สร้างมาเพื่อเพิ่มประสิทธิถาพบนระบบปฎิบัติการ Windows ### Brightray -[Brightray][brightray] is a static library that makes [libchromiumcontent] -easier to use in applications. It was created specifically for Electron, but can -be used to enable Chromium's renderer in native apps that are not based on -Electron. +[Brightray][brightray] เป็น static library ที่ทำให้ [libchromiumcontent] ใช้ได้ง่ายขึ้นในแอพพิเคชั่น +มันถูกสร้างขึ้นมาเพื่อ Electron โดยเฉพาะ แต่ว่ามันสามารถเปิดใช้ในแอพที่ใช้ Chromium's render ซึ่งไม่ถูกสร้างจาก Electron ได้ -Brightray is a low-level dependency of Electron that does not concern the -majority of Electron users. +Brightray เป็น dependency ของ Electron ที่ไม่ค่อยได้ใช้งานสำหรับผู้ใช้ Electron ทั่วไป ### DMG -An Apple Disk Image is a packaging format used by macOS. DMG files are -commonly used for distributing application "installers". [electron-builder] -supports `dmg` as a build target. +DMG เป็นรูปแบบไฟล์ที่ใช้กับ Apple Disk Image + +โดยส่วนมากไฟล์ DMG จะถูกใช้ในการแจกแจงตัวติดตั้งของแอพพิเคชั่น +คุณสามารถใช้ [electron-builder] ในการสร้างไฟล์ขึ้นมาได้ ### IPC -IPC stands for Inter-Process Communication. Electron uses IPC to send -serialized JSON messages between the [main] and [renderer] processes. +IPC ย่อมาจาก Inter-Process Communication ซึ่ง Electron ใช้ IPC +ในการที่จะส่งข้อความ JSON ระหว่าง [โปรเซสหลัก][main] และ [ตัวเรนเดอร์][renderer] ### libchromiumcontent -A single, shared library that includes the Chromium Content module and all its -dependencies (e.g., Blink, [V8], etc.). +เป็น library รวมที่มี Chromium Content โมดูลและ dependencies ต่างๆเข้าไปด้วย +(อาทิเช่น Blink, [V8], ฯลฯ) -### main process +### โปรเซสหลัก -The main process, commonly a file named `main.js`, is the entry point to every -Electron app. It controls the life of the app, from open to close. It also -manages native elements such as the Menu, Menu Bar, Dock, Tray, etc. The -main process is responsible for creating each new renderer process in the app. -The full Node API is built in. +โปรเซสหลักในส่วนมากจะเป็นไฟล์ `main.js` ซึ่งเป็นจุดเริ่มต้นของทุกๆแอพของ Electron +โปรเซสหลักเป็นตัวที่กำหนดการทำงานของแอพตั้งแต่ต้นจนจบ มันจะควบคุมองค์ประกอบของแอพเช่น +Menu, Menu Bar, Dock, Tray, ฯลฯ โปรเซสหลักนั้นจะรับผิดชอบการสร้างตัวเรนเดอร์ใหม่ในแอพ -Every app's main process file is specified in the `main` property in -`package.json`. This is how `electron .` knows what file to execute at startup. +Node API นั้นมาพร้อมกับ Electron -See also: [process](#process), [renderer process](#renderer-process) +`package.json` จะสามารถใช้ระบุโปรเซสหลักของทุกๆแอพบน Electron ได้ นี้คือเหตุผลที่ `electron .` รู้ว่าไฟล์ไหนควรจะรันตอนเริ่มต้น + +เพิ่มเติม: [process](#process), [renderer process](#renderer-process) ### MAS -Acronym for Apple's Mac App Store. For details on submitting your app to the -MAS, see the [Mac App Store Submission Guide]. +MAS เป็นตัวย่อของ Mac App Store ในการที่จะส่งไฟล์ลง MAS นั้นโปรดดู [การแนะนำการส่ง Mac App Store](Mac App Store Submission Guide) ### native modules -Native modules (also called [addons] in -Node.js) are modules written in C or C++ that can be loaded into Node.js or -Electron using the require() function, and used just as if they were an -ordinary Node.js module. They are used primarily to provide an interface -between JavaScript running in Node.js and C/C++ libraries. +Native โมดูล (อีกชื่อคือ [addons]) คือโมดูลที่เขียนด้วย C หรือ C++ +ที่สามารถจะโหลดอยู่ใน Node.js หรือ Electron โดยการใช้ฟังค์ชั่น require() +และใช้แบบเดียวกับโมดูล Node.js ธรรมดา -Native Node modules are supported by Electron, but since Electron is very -likely to use a different V8 version from the Node binary installed in your -system, you have to manually specify the location of Electron’s headers when -building native modules. +โดยส่วนมากมันถูกใช้เป็นอินเตอร์เฟสระหว่าง JavaScript และ Node.js ผ่านทาง C/C++ -See also [Using Native Node Modules]. +Native Node โมดูลจะได้รับการสนับสนุนจาก Electron +แต่ว่ามีความเป็นไปได้ว่า Electron นั้นจะใช้ V8 คนละเวอร์ชั่นกับ Node ที่มีอยู่ในระบบ +คุณจะต้องเป็นคนเลือก Electron's header ด้วยตนเอง + +เพิ่มเติม: [วิธีการใช้ Native Node โมดูล][Using Native Node Modules] ## NSIS -Nullsoft Scriptable Install System is a script-driven Installer -authoring tool for Microsoft Windows. It is released under a combination of -free software licenses, and is a widely-used alternative to commercial -proprietary products like InstallShield. [electron-builder] supports NSIS -as a build target. +NSIS ย่อมาจาก Nullsoft Scriptable Install System คือตัวติดตั้งที่ขับเคลื่อนด้วยสคริปท์ +สำหรับระบบปฎิบัติการ Windows มันได้รับการจดลิขสิทธ์สำหรับใช้ทุกๆคน +มันถูกใช้แทนสินค้าเชิงพาณิชย์เช่น InstallShield +[electron-builder] นั้นรองรับการสร้าง NSIS ### process -A process is an instance of a computer program that is being executed. Electron -apps that make use of the [main] and one or many [renderer] process are -actually running several programs simultaneously. +โปรเซสคือ instance ของโปรแกรมที่กำลังทำงานอยู่ +Electron แอพนั้นใช้ [โปรเซสหลัก][main] และ [ตัวเรนเดอร์][renderer] หนึ่งตัวขึ้นไปที่รัน +หลายๆโปรเกรมพร้อมๆกัน -In Node.js and Electron, each running process has a `process` object. This -object is a global that provides information about, and control over, the -current process. As a global, it is always available to applications without -using require(). +ใน Node.js และ Electron นั้น แต่ละโปรเซสที่กำลังรันอยู่จะมี `process` object +Object ตัวนี้เป็น global ที่ให้ข้อมูลและการควบคุมของตัวโปรเซสนั้นๆ -See also: [main process](#main-process), [renderer process](#renderer-process) +เพราะว่ามันเป็น global ดังนั้นจึงไม่จำเป็นที่จะเรียก `require()` เพื่อใช้งาน + +เพิ่มเติม: [โปรเซสหลัก](#main-process), [ตัวเรนเดอร์](#renderer-process) ### renderer process -The renderer process is a browser window in your app. Unlike the main process, -there can be multiple of these and each is run in a separate process. -They can also be hidden. +ตัวเรนเดอร์ คือ หน้าต่างบราวเซอร์ของแอพพิเคชั่นของคุณ +มันแตกต่างจากโปรเซสหลักโดยที่มันจะสามารถมีอยู่พร้อมกันหลายตัวได้ +และแต่ละตัวนั้นใช้ โปรเซส ที่ต่างกันออกไป +นอกจากนั้นมันยังสามารถซ่อนได้อีกด้วย -In normal browsers, web pages usually run in a sandboxed environment and are not -allowed access to native resources. Electron users, however, have the power to -use Node.js APIs in web pages allowing lower level operating system -interactions. +ใน บราวเซอร์ ทั่วๆไป เว็ปเพจจะได้รับการรันด้วยสภาพแวดล้อมจำกัดและจะไม่สามารถใช้ทรัพยากร native ได้ -See also: [process](#process), [main process](#main-process) +แต่ว่าสำหับผู้ใช้ Electron นั้นเราสามารถให้ +เว็ปเพจมีการปฏิสัมพันธ์กับระบบปฎิบัติการได้โดยผ่านทาง API ของ Node.js + +เพิ่มเติม: [โปรเซสหลัก](#main-process), [ตัวเรนเดอร์](#renderer-process) ### Squirrel -Squirrel is an open-source framework that enables Electron apps to update -automatically as new versions are released. See the [autoUpdater] API for -info about getting started with Squirrel. +Squirrel เป็น open-source framework ที่ทำให้แอพ Electron นั้น +อัพเดทได้อย่างอัตโนมัดเมื่อมีเวอร์ชั่นใหม่เข้ามา + +เพิ่มเติม: [autoUpdater] API เพื่อการใช้งาน Squirrel เบื้องต้น ### userland -This term originated in the Unix community, where "userland" or "userspace" -referred to programs that run outside of the operating system kernel. More -recently, the term has been popularized in the Node and npm community to -distinguish between the features available in "Node core" versus packages -published to the npm registry by the much larger "user" community. +userland แต่แรกทีมาจากสังคม unix ซึ่งหมายถึงโปรแกรมที่รันข้างนอก kernel ของระบบปฎิบัติการ +ภายหลังนี้ "userland" ได้รับความสนใจจากสังคม Node และ npm เพื่อใช้ในการอธิบายความแตกต่าง +ของ feature ที่พร้อมไช้งานใน "node core" กับ แพ็คเกจที่ได้รับการแจกจ่ายถายใน npm registry +ซึ่งมาจากเหล่าคนใช้ที่กว้างขวาง -Like Node, Electron is focused on having a small set of APIs that provide -all the necessary primitives for developing multi-platform desktop applications. -This design philosophy allows Electron to remain a flexible tool without being -overly prescriptive about how it should be used. Userland enables users to -create and share tools that provide additional functionality on top of what is -available in "core". +Electron นั้นมีความคล้ายคลึงกับ Node ตรงที่มี API ที่ไม่เยอะแต่ว่ามีความสามารถเพียงพอ +ในการพัฒนา multi-platform แอพพิเคชั่น + +ด้วยปรัชญาการออกแบบนี้เองที่ทำให้ Electron นั้นเป็นเครื่องมือที่มีความยืดหยุ่นโดยที่ไม่กำหนดให้ผู้ใช้ +ใช้งานได้เพียงตามที่ออกแบบไว้ + +Userland ได้เปิดโอกาสให้ผู้ใช้สามารถสร้างและแบ่งปันเครื่องมือนอกเหนือจากอะไรก็ตามที่มีอยู่ใน "core" ### V8 -V8 is Google's open source JavaScript engine. It is written in C++ and is -used in Google Chrome, the open source browser from Google. V8 can run -standalone, or can be embedded into any C++ application. +V8 เป็น JavaScripe engine ของ Google ที่เป็น open source +มันถูกเขียนขึ้นด้วย C++ และถูกใช้ใน Google Chrome + +Google Chrome เป็น open source บราวส์เซอร์ของ Google + +V8 สามารถรันแยกเองต่างหากได้ หรือจะสามารถนำไปใช้กับแอพพิเคชั่น C++ อะไรก็ได้ ### webview -`webview` tags are used to embed 'guest' content (such as external web pages) in -your Electron app. They are similar to `iframe`s, but differ in that each -webview runs in a separate process. 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. +`webview` เป็นแท็กที่ใช้ในการใส่ข้อมูลสำหรับคนใช้ทั่วไป (เช่นเว็ปภายนอก) ใน Electron แอพของคุณ +มันมีความคล้ายครึงกับ `iframe` แต่ว่าต่างกันโดยที่ webview รันโดยโปรเซสคนละตัว +มันไม่มี permission เหมือนกับเว็ปเพจของคุณและการมีปฏิสัมพันธ์ระกว่าง แอพของคุณ กับ สิ่งที่ฝังอยู่จะเป็นไปโดยราบลื่นโดยที่ไม่ต้องซิ้งค์ (asynchronous) + +ด้วยเหตุนี้เองทำให้ แอพของคุณปลอกภัยจากสิ่งที่ถูกฝัง + [addons]: https://nodejs.org/api/addons.html [asar]: https://github.com/electron/asar From 45a1ffef8f36b4e49d78713aef8393cd775a1f1f Mon Sep 17 00:00:00 2001 From: ThePooE Date: Fri, 20 Jan 2017 02:38:12 +0700 Subject: [PATCH 109/925] :memo: Thai: fix small line breaking, word warpping in glossary.md --- docs-translations/th-TH/glossary.md | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/docs-translations/th-TH/glossary.md b/docs-translations/th-TH/glossary.md index 4b3e7ec76b..9164f75ef1 100644 --- a/docs-translations/th-TH/glossary.md +++ b/docs-translations/th-TH/glossary.md @@ -4,14 +4,17 @@ ### ASAR -[ASAR (Atom Shell Archive Format)][asar] เป็น extension ของไฟล์ที่มีลักษณะความคล้ายครีง `tar` ที่รวมหลายไฟล์ลงไปในไฟล์เดียว Electron สามารถอ่านไฟล์ได้เลยโดยที่ไม่ต้อง unpack ไฟล์ +[ASAR (Atom Shell Archive Format)][asar] เป็น extension ของไฟล์ +มันมีลักษณะความคล้ายครีง `tar` ที่รวมหลายไฟล์ลงไปในไฟล์เดียว +Electron สามารถอ่านไฟล์ ASAR ได้เลยโดยที่ไม่ต้อง unpack กำลังทำ: ไฟล์ประเภท ASAR นั้น สร้างมาเพื่อเพิ่มประสิทธิถาพบนระบบปฎิบัติการ Windows ### Brightray -[Brightray][brightray] เป็น static library ที่ทำให้ [libchromiumcontent] ใช้ได้ง่ายขึ้นในแอพพิเคชั่น -มันถูกสร้างขึ้นมาเพื่อ Electron โดยเฉพาะ แต่ว่ามันสามารถเปิดใช้ในแอพที่ใช้ Chromium's render ซึ่งไม่ถูกสร้างจาก Electron ได้ +[Brightray][brightray] เป็น static library ที่ทำให้ [libchromiumcontent] ใช้ได้ง่ายขึ้น +ในแอพพิเคชั่น มันถูกสร้างขึ้นมาเพื่อ Electron โดยเฉพาะ +แต่ว่ามันสามารถเปิดใช้ในแอพที่ใช้ Chromium's render ซึ่งไม่ถูกสร้างจาก Electron ได้ Brightray เป็น dependency ของ Electron ที่ไม่ค่อยได้ใช้งานสำหรับผู้ใช้ Electron ทั่วไป @@ -40,13 +43,15 @@ Menu, Menu Bar, Dock, Tray, ฯลฯ โปรเซสหลักนั้น Node API นั้นมาพร้อมกับ Electron -`package.json` จะสามารถใช้ระบุโปรเซสหลักของทุกๆแอพบน Electron ได้ นี้คือเหตุผลที่ `electron .` รู้ว่าไฟล์ไหนควรจะรันตอนเริ่มต้น +`package.json` จะสามารถใช้ระบุโปรเซสหลักของทุกๆแอพบน Electron ได้ +นี้คือเหตุผลที่ `electron .` รู้ว่าไฟล์ไหนควรจะรันตอนเริ่มต้น เพิ่มเติม: [process](#process), [renderer process](#renderer-process) ### MAS -MAS เป็นตัวย่อของ Mac App Store ในการที่จะส่งไฟล์ลง MAS นั้นโปรดดู [การแนะนำการส่ง Mac App Store](Mac App Store Submission Guide) +MAS เป็นตัวย่อของ Mac App Store +ในการที่จะส่งไฟล์ลง MAS นั้นโปรดดู [การแนะนำการส่ง Mac App Store](Mac App Store Submission Guide) ### native modules @@ -89,7 +94,8 @@ Object ตัวนี้เป็น global ที่ให้ข้อมู และแต่ละตัวนั้นใช้ โปรเซส ที่ต่างกันออกไป นอกจากนั้นมันยังสามารถซ่อนได้อีกด้วย -ใน บราวเซอร์ ทั่วๆไป เว็ปเพจจะได้รับการรันด้วยสภาพแวดล้อมจำกัดและจะไม่สามารถใช้ทรัพยากร native ได้ +ใน บราวเซอร์ ทั่วๆไป +เว็ปเพจจะได้รับการรันด้วยสภาพแวดล้อมจำกัดและจะไม่สามารถใช้ทรัพยากร native ได้ แต่ว่าสำหับผู้ใช้ Electron นั้นเราสามารถให้ เว็ปเพจมีการปฏิสัมพันธ์กับระบบปฎิบัติการได้โดยผ่านทาง API ของ Node.js @@ -105,7 +111,7 @@ Squirrel เป็น open-source framework ที่ทำให้แอพ El ### userland -userland แต่แรกทีมาจากสังคม unix ซึ่งหมายถึงโปรแกรมที่รันข้างนอก kernel ของระบบปฎิบัติการ +"Userland" แต่แรกทีมาจากสังคม unix ซึ่งหมายถึงโปรแกรมที่รันข้างนอก kernel ของระบบปฎิบัติการ ภายหลังนี้ "userland" ได้รับความสนใจจากสังคม Node และ npm เพื่อใช้ในการอธิบายความแตกต่าง ของ feature ที่พร้อมไช้งานใน "node core" กับ แพ็คเกจที่ได้รับการแจกจ่ายถายใน npm registry ซึ่งมาจากเหล่าคนใช้ที่กว้างขวาง @@ -131,7 +137,8 @@ V8 สามารถรันแยกเองต่างหากได้ `webview` เป็นแท็กที่ใช้ในการใส่ข้อมูลสำหรับคนใช้ทั่วไป (เช่นเว็ปภายนอก) ใน Electron แอพของคุณ มันมีความคล้ายครึงกับ `iframe` แต่ว่าต่างกันโดยที่ webview รันโดยโปรเซสคนละตัว -มันไม่มี permission เหมือนกับเว็ปเพจของคุณและการมีปฏิสัมพันธ์ระกว่าง แอพของคุณ กับ สิ่งที่ฝังอยู่จะเป็นไปโดยราบลื่นโดยที่ไม่ต้องซิ้งค์ (asynchronous) +มันไม่มี permission เหมือนกับเว็ปเพจของคุณและการมีปฏิสัมพันธ์ระหว่าง +แอพของคุงกับสิ่งที่ฝังอยู่จะเป็นไปโดยราบลื่นโดยที่ไม่ต้องซิ้งค์ (asynchronous) ด้วยเหตุนี้เองทำให้ แอพของคุณปลอกภัยจากสิ่งที่ถูกฝัง From c49363f521d812501df01df92178b000399c92be Mon Sep 17 00:00:00 2001 From: ThePooE Date: Sat, 21 Jan 2017 16:16:32 +0700 Subject: [PATCH 110/925] :memo: Thai: translated accessibility.md --- .../th-TH/tutorial/accessibility.md | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/docs-translations/th-TH/tutorial/accessibility.md b/docs-translations/th-TH/tutorial/accessibility.md index 22b9704bdb..b1687e552b 100644 --- a/docs-translations/th-TH/tutorial/accessibility.md +++ b/docs-translations/th-TH/tutorial/accessibility.md @@ -1,33 +1,40 @@ -# Accessibility +# การเข้าถึง (Accessibility) -Making accessible applications is important and we're happy to introduce new functionality to [Devtron](http://electron.atom.io/devtron) and [Spectron](http://electron.atom.io/spectron) that gives developers the opportunity to make their apps better for everyone. +การที่จะทำให้แอพพิเคชั่นนั้นเข้าถึงได้เป็นเรื่องที่สำคัญมาก และ เรามีความสุขที่จะต้อนรับความสามารถใหม่ของเราสู่ [Devtron](http://electron.atom.io/devtron) และ [Spectron](http://electron.atom.io/spectron) ซึ่งได้ให้โอกาสผู้พัฒนาในการที่จะสร้างแอพพิเคชั่นที่ดีขึ้นเพื่อทุกๆคน --- -Accessibility concerns in Electron applications are similar to those of websites because they're both ultimately HTML. With Electron apps, however, you can't use the online resources for accessibility audits because your app doesn't have a URL to point the auditor to. +ความกังวลเกี่ยวกับการเข้าถึงของแอพพิเคชั่น Electron นั้นมีความคล้ายครึงกับความกังวลของเว็ปไซต์ เพราะว่าทั้งสองนั้นเป็น HTML ด้วยกัน ในขณะเดียวกันนั้น แต่ว่าในแอพ Electron คุณไม่สามารถใช้ทรัพยากรณ์ออนไลน์ได้เพราะว่าแอพของคุณนั้นไม่มี URL ที่สามารถเข้าถึงได้ -These new features bring those auditing tools to your Electron app. You can choose to add audits to your tests with Spectron or use them within DevTools with Devtron. Read on for a summary of the tools or checkout our [accessibility documentation](http://electron.atom.io/docs/tutorial/accessibility) for more information. +ความสามารถใหม่ๆนี้นำอุปกรณ์การแก้ไขต่างๆเข้ามาใส่แอพ Electron ของคุณ คุณสามารถเลือกที่จะแก้ไขบททดสองของคุณได้ด้วย Spectron หรือว่าใช้มันใน DevTools ด้วย Devtron + +กรุณาอ่านต่อเพื่อบทสรุปของอุปกรณ์หรือดู [เอกสารการเข้าถึง](http://electron.atom.io/docs/tutorial/accessibility) ของเราสำหรับข้อมูลเพิ่มเติม ### Spectron -In the testing framework Spectron, you can now audit each window and `` tag in your application. For example: +ในการทดสอบเฟรมเวิร์ค Spectron นั้น +คุณจะใช้วิธีการแก้ไขทุกๆหน้าต่าง และ แท็ก `` ในแอพพิเคชั่นของคุณได้ + +ยกตัวอย่างเช่น: ```javascript -app.client.auditAccessibility().then(function (audit) { - if (audit.failed) { - console.error(audit.message) - } +app.client.autidAccessibility().then(function (audit) { + if (audit.failed) { + console.error(audit.message) + } }) ``` -You can read more about this feature in [Spectron's documentation](https://github.com/electron/spectron#accessibility-testing). +คุณสามารถอ่านข้อมูลเพิ่มเติมสำหรับได้ที่ [เอกสาร Spectron](https://github.com/electron/spectron#accessibility-testing) ### Devtron -In Devtron, there is a new accessibility tab which will allow you to audit a page in your app, sort and filter the results. +ใน Devtron นั้น จะมีแท็ปการเข้าถึง ซึ่งจะทำให้คุณสามารถจัดการเพจในแอพของคุณได้ ![devtron screenshot](https://cloud.githubusercontent.com/assets/1305617/17156618/9f9bcd72-533f-11e6-880d-389115f40a2a.png) -Both of these tools are using the [Accessibility Developer Tools](https://github.com/GoogleChrome/accessibility-developer-tools) library built by Google for Chrome. You can learn more about the accessibility audit rules this library uses on that [repository's wiki](https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules). +ทั้งสองเครื่องมือใช้ [Accessibility Developer Tools](https://github.com/GoogleChrome/accessibility-developer-tools) ซึ่งเป็น library ที่สร้างขึ้นโดย Google เพื่อ Chrome -If you know of other great accessibility tools for Electron, add them to the [accessibility documentation](http://electron.atom.io/docs/tutorial/accessibility) with a pull request. +คุณสามารถศึกษาเพิ่มเติมเกี่ยวกับมันได้ที่ [รีโปนี้](https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules) + +ถ้าคุณมีความรู้เกี่ยวกับอุปกรณือื่นๆที่สามารถใช้กับ Electron ได้ โปรดใส่มันเพิ่มใน [เอกสารการเข้าถึง](http://electron.atom.io/docs/tutorial/accessibility) ด้วยการขอดึงจาก Electron (pull request) From 9dd960fc972f61d9b344ff017da6e1916c2a3b8d Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 10 Feb 2017 12:06:06 -0800 Subject: [PATCH 111/925] Remove untranslated files --- docs-translations/th-TH/api/accelerator.md | 71 - docs-translations/th-TH/api/app.md | 908 ------------ docs-translations/th-TH/api/auto-updater.md | 139 -- .../th-TH/api/browser-window-proxy.md | 53 - docs-translations/th-TH/api/browser-window.md | 1272 ----------------- .../th-TH/api/chrome-command-line-switches.md | 189 --- docs-translations/th-TH/api/client-request.md | 192 --- docs-translations/th-TH/api/clipboard.md | 169 --- .../th-TH/api/content-tracing.md | 173 --- docs-translations/th-TH/api/cookies.md | 106 -- docs-translations/th-TH/api/crash-reporter.md | 112 -- docs-translations/th-TH/api/debugger.md | 83 -- .../th-TH/api/desktop-capturer.md | 76 - docs-translations/th-TH/api/dialog.md | 161 --- docs-translations/th-TH/api/download-item.md | 168 --- .../th-TH/api/environment-variables.md | 87 -- docs-translations/th-TH/api/file-object.md | 33 - .../th-TH/api/frameless-window.md | 143 -- .../th-TH/api/global-shortcut.md | 75 - .../th-TH/api/incoming-message.md | 74 - docs-translations/th-TH/api/ipc-main.md | 99 -- docs-translations/th-TH/api/ipc-renderer.md | 81 -- docs-translations/th-TH/api/locales.md | 139 -- docs-translations/th-TH/api/menu-item.md | 116 -- docs-translations/th-TH/api/menu.md | 403 ------ docs-translations/th-TH/api/native-image.md | 256 ---- docs-translations/th-TH/api/net.md | 71 - docs-translations/th-TH/api/power-monitor.md | 41 - .../th-TH/api/power-save-blocker.md | 56 - docs-translations/th-TH/api/process.md | 118 -- docs-translations/th-TH/api/protocol.md | 297 ---- docs-translations/th-TH/api/remote.md | 169 --- docs-translations/th-TH/api/screen.md | 122 -- docs-translations/th-TH/api/session.md | 402 ------ docs-translations/th-TH/api/shell.md | 86 -- .../th-TH/api/structures/bluetooth-device.md | 4 - .../api/structures/certificate-principal.md | 8 - .../th-TH/api/structures/certificate.md | 12 - .../th-TH/api/structures/cookie.md | 14 - .../th-TH/api/structures/crash-report.md | 4 - .../api/structures/desktop-capturer-source.md | 14 - .../th-TH/api/structures/display.md | 19 - .../th-TH/api/structures/file-filter.md | 4 - .../api/structures/jump-list-category.md | 21 - .../th-TH/api/structures/jump-list-item.md | 28 - .../api/structures/memory-usage-details.md | 8 - .../th-TH/api/structures/mime-typed-buffer.md | 4 - .../th-TH/api/structures/rectangle.md | 6 - .../structures/remove-client-certificate.md | 5 - .../th-TH/api/structures/remove-password.md | 15 - .../th-TH/api/structures/shortcut-details.md | 15 - .../th-TH/api/structures/task.md | 14 - .../th-TH/api/structures/thumbar-button.md | 21 - .../th-TH/api/structures/upload-blob.md | 4 - .../th-TH/api/structures/upload-data.md | 6 - .../api/structures/upload-file-system.md | 9 - .../th-TH/api/structures/upload-file.md | 9 - .../th-TH/api/structures/upload-raw-data.md | 4 - docs-translations/th-TH/api/synopsis.md | 95 -- .../th-TH/api/system-preferences.md | 239 ---- docs-translations/th-TH/api/tray.md | 247 ---- docs-translations/th-TH/api/web-contents.md | 1241 ---------------- docs-translations/th-TH/api/web-frame.md | 193 --- docs-translations/th-TH/api/web-request.md | 205 --- docs-translations/th-TH/api/webview-tag.md | 910 ------------ docs-translations/th-TH/api/window-open.md | 43 - .../development/atom-shell-vs-node-webkit.md | 52 - .../development/build-instructions-linux.md | 204 --- .../development/build-instructions-osx.md | 64 - .../development/build-instructions-windows.md | 146 -- .../development/build-system-overview.md | 122 -- .../th-TH/development/clang-format.md | 34 - .../th-TH/development/coding-style.md | 55 - .../development/debug-instructions-windows.md | 93 -- .../debugging-instructions-macos.md | 125 -- .../th-TH/development/releasing.md | 101 -- .../development/setting-up-symbol-server.md | 56 - .../source-code-directory-structure.md | 93 -- .../tutorial/application-distribution.md | 176 --- .../th-TH/tutorial/application-packaging.md | 185 --- .../debugging-main-process-node-inspector.md | 131 -- .../tutorial/debugging-main-process-vscode.md | 34 - .../th-TH/tutorial/debugging-main-process.md | 33 - .../desktop-environment-integration.md | 399 ------ .../th-TH/tutorial/devtools-extension.md | 66 - .../th-TH/tutorial/electron-versioning.md | 21 - .../mac-app-store-submission-guide.md | 266 ---- .../th-TH/tutorial/offscreen-rendering.md | 57 - .../th-TH/tutorial/online-offline-events.md | 90 -- .../tutorial/planned-breaking-changes.md | 159 --- docs-translations/th-TH/tutorial/repl.md | 26 - docs-translations/th-TH/tutorial/security.md | 96 -- .../th-TH/tutorial/supported-platforms.md | 31 - .../th-TH/tutorial/testing-on-headless-ci.md | 60 - .../tutorial/using-native-node-modules.md | 104 -- .../tutorial/using-pepper-flash-plugin.md | 82 -- .../tutorial/using-selenium-and-webdriver.md | 172 --- .../tutorial/using-widevine-cdm-plugin.md | 85 -- .../th-TH/tutorial/windows-store-guide.md | 161 --- 99 files changed, 13740 deletions(-) delete mode 100644 docs-translations/th-TH/api/accelerator.md delete mode 100644 docs-translations/th-TH/api/app.md delete mode 100644 docs-translations/th-TH/api/auto-updater.md delete mode 100644 docs-translations/th-TH/api/browser-window-proxy.md delete mode 100644 docs-translations/th-TH/api/browser-window.md delete mode 100644 docs-translations/th-TH/api/chrome-command-line-switches.md delete mode 100644 docs-translations/th-TH/api/client-request.md delete mode 100644 docs-translations/th-TH/api/clipboard.md delete mode 100644 docs-translations/th-TH/api/content-tracing.md delete mode 100644 docs-translations/th-TH/api/cookies.md delete mode 100644 docs-translations/th-TH/api/crash-reporter.md delete mode 100644 docs-translations/th-TH/api/debugger.md delete mode 100644 docs-translations/th-TH/api/desktop-capturer.md delete mode 100644 docs-translations/th-TH/api/dialog.md delete mode 100644 docs-translations/th-TH/api/download-item.md delete mode 100644 docs-translations/th-TH/api/environment-variables.md delete mode 100644 docs-translations/th-TH/api/file-object.md delete mode 100644 docs-translations/th-TH/api/frameless-window.md delete mode 100644 docs-translations/th-TH/api/global-shortcut.md delete mode 100644 docs-translations/th-TH/api/incoming-message.md delete mode 100644 docs-translations/th-TH/api/ipc-main.md delete mode 100644 docs-translations/th-TH/api/ipc-renderer.md delete mode 100644 docs-translations/th-TH/api/locales.md delete mode 100644 docs-translations/th-TH/api/menu-item.md delete mode 100644 docs-translations/th-TH/api/menu.md delete mode 100644 docs-translations/th-TH/api/native-image.md delete mode 100644 docs-translations/th-TH/api/net.md delete mode 100644 docs-translations/th-TH/api/power-monitor.md delete mode 100644 docs-translations/th-TH/api/power-save-blocker.md delete mode 100644 docs-translations/th-TH/api/process.md delete mode 100644 docs-translations/th-TH/api/protocol.md delete mode 100644 docs-translations/th-TH/api/remote.md delete mode 100644 docs-translations/th-TH/api/screen.md delete mode 100644 docs-translations/th-TH/api/session.md delete mode 100644 docs-translations/th-TH/api/shell.md delete mode 100644 docs-translations/th-TH/api/structures/bluetooth-device.md delete mode 100644 docs-translations/th-TH/api/structures/certificate-principal.md delete mode 100644 docs-translations/th-TH/api/structures/certificate.md delete mode 100644 docs-translations/th-TH/api/structures/cookie.md delete mode 100644 docs-translations/th-TH/api/structures/crash-report.md delete mode 100644 docs-translations/th-TH/api/structures/desktop-capturer-source.md delete mode 100644 docs-translations/th-TH/api/structures/display.md delete mode 100644 docs-translations/th-TH/api/structures/file-filter.md delete mode 100644 docs-translations/th-TH/api/structures/jump-list-category.md delete mode 100644 docs-translations/th-TH/api/structures/jump-list-item.md delete mode 100644 docs-translations/th-TH/api/structures/memory-usage-details.md delete mode 100644 docs-translations/th-TH/api/structures/mime-typed-buffer.md delete mode 100644 docs-translations/th-TH/api/structures/rectangle.md delete mode 100644 docs-translations/th-TH/api/structures/remove-client-certificate.md delete mode 100644 docs-translations/th-TH/api/structures/remove-password.md delete mode 100644 docs-translations/th-TH/api/structures/shortcut-details.md delete mode 100644 docs-translations/th-TH/api/structures/task.md delete mode 100644 docs-translations/th-TH/api/structures/thumbar-button.md delete mode 100644 docs-translations/th-TH/api/structures/upload-blob.md delete mode 100644 docs-translations/th-TH/api/structures/upload-data.md delete mode 100644 docs-translations/th-TH/api/structures/upload-file-system.md delete mode 100644 docs-translations/th-TH/api/structures/upload-file.md delete mode 100644 docs-translations/th-TH/api/structures/upload-raw-data.md delete mode 100644 docs-translations/th-TH/api/synopsis.md delete mode 100644 docs-translations/th-TH/api/system-preferences.md delete mode 100644 docs-translations/th-TH/api/tray.md delete mode 100644 docs-translations/th-TH/api/web-contents.md delete mode 100644 docs-translations/th-TH/api/web-frame.md delete mode 100644 docs-translations/th-TH/api/web-request.md delete mode 100644 docs-translations/th-TH/api/webview-tag.md delete mode 100644 docs-translations/th-TH/api/window-open.md delete mode 100644 docs-translations/th-TH/development/atom-shell-vs-node-webkit.md delete mode 100644 docs-translations/th-TH/development/build-instructions-linux.md delete mode 100644 docs-translations/th-TH/development/build-instructions-osx.md delete mode 100644 docs-translations/th-TH/development/build-instructions-windows.md delete mode 100644 docs-translations/th-TH/development/build-system-overview.md delete mode 100644 docs-translations/th-TH/development/clang-format.md delete mode 100644 docs-translations/th-TH/development/coding-style.md delete mode 100644 docs-translations/th-TH/development/debug-instructions-windows.md delete mode 100644 docs-translations/th-TH/development/debugging-instructions-macos.md delete mode 100644 docs-translations/th-TH/development/releasing.md delete mode 100644 docs-translations/th-TH/development/setting-up-symbol-server.md delete mode 100644 docs-translations/th-TH/development/source-code-directory-structure.md delete mode 100644 docs-translations/th-TH/tutorial/application-distribution.md delete mode 100644 docs-translations/th-TH/tutorial/application-packaging.md delete mode 100644 docs-translations/th-TH/tutorial/debugging-main-process-node-inspector.md delete mode 100644 docs-translations/th-TH/tutorial/debugging-main-process-vscode.md delete mode 100644 docs-translations/th-TH/tutorial/debugging-main-process.md delete mode 100644 docs-translations/th-TH/tutorial/desktop-environment-integration.md delete mode 100644 docs-translations/th-TH/tutorial/devtools-extension.md delete mode 100644 docs-translations/th-TH/tutorial/electron-versioning.md delete mode 100644 docs-translations/th-TH/tutorial/mac-app-store-submission-guide.md delete mode 100644 docs-translations/th-TH/tutorial/offscreen-rendering.md delete mode 100644 docs-translations/th-TH/tutorial/online-offline-events.md delete mode 100644 docs-translations/th-TH/tutorial/planned-breaking-changes.md delete mode 100644 docs-translations/th-TH/tutorial/repl.md delete mode 100644 docs-translations/th-TH/tutorial/security.md delete mode 100644 docs-translations/th-TH/tutorial/supported-platforms.md delete mode 100644 docs-translations/th-TH/tutorial/testing-on-headless-ci.md delete mode 100644 docs-translations/th-TH/tutorial/using-native-node-modules.md delete mode 100644 docs-translations/th-TH/tutorial/using-pepper-flash-plugin.md delete mode 100644 docs-translations/th-TH/tutorial/using-selenium-and-webdriver.md delete mode 100644 docs-translations/th-TH/tutorial/using-widevine-cdm-plugin.md delete mode 100644 docs-translations/th-TH/tutorial/windows-store-guide.md diff --git a/docs-translations/th-TH/api/accelerator.md b/docs-translations/th-TH/api/accelerator.md deleted file mode 100644 index f90e409043..0000000000 --- a/docs-translations/th-TH/api/accelerator.md +++ /dev/null @@ -1,71 +0,0 @@ -# Accelerator - -> Define keyboard shortcuts. - -Accelerators are Strings that can contain multiple modifiers and key codes, -combined by the `+` character, and are used to define keyboard shortcuts -throughout your application. - -Examples: - -* `CommandOrControl+A` -* `CommandOrControl+Shift+Z` - -Shortcuts are registered with the [`globalShortcut`](global-shortcut.md) module -using the [`register`](global-shortcut.md#globalshortcutregisteraccelerator-callback) -method, i.e. - -```javascript -const {app, globalShortcut} = require('electron') - -app.on('ready', () => { - // Register a 'CommandOrControl+Y' shortcut listener. - globalShortcut.register('CommandOrControl+Y', () => { - // Do stuff when Y and either Command/Control is pressed. - }) -}) -``` - -## Platform notice - -On Linux and Windows, the `Command` key does not have any effect so -use `CommandOrControl` which represents `Command` on macOS and `Control` on -Linux and Windows to define some accelerators. - -Use `Alt` instead of `Option`. The `Option` key only exists on macOS, whereas -the `Alt` key is available on all platforms. - -The `Super` key is mapped to the `Windows` key on Windows and Linux and -`Cmd` on macOS. - -## Available modifiers - -* `Command` (or `Cmd` for short) -* `Control` (or `Ctrl` for short) -* `CommandOrControl` (or `CmdOrCtrl` for short) -* `Alt` -* `Option` -* `AltGr` -* `Shift` -* `Super` - -## Available key codes - -* `0` to `9` -* `A` to `Z` -* `F1` to `F24` -* Punctuations like `~`, `!`, `@`, `#`, `$`, etc. -* `Plus` -* `Space` -* `Tab` -* `Backspace` -* `Delete` -* `Insert` -* `Return` (or `Enter` as alias) -* `Up`, `Down`, `Left` and `Right` -* `Home` and `End` -* `PageUp` and `PageDown` -* `Escape` (or `Esc` for short) -* `VolumeUp`, `VolumeDown` and `VolumeMute` -* `MediaNextTrack`, `MediaPreviousTrack`, `MediaStop` and `MediaPlayPause` -* `PrintScreen` diff --git a/docs-translations/th-TH/api/app.md b/docs-translations/th-TH/api/app.md deleted file mode 100644 index 3c7670655a..0000000000 --- a/docs-translations/th-TH/api/app.md +++ /dev/null @@ -1,908 +0,0 @@ -# app - -> Control your application's event lifecycle. - -Process: [Main](../glossary.md#main-process) - -The following example shows how to quit the application when the last window is -closed: - -```javascript -const {app} = require('electron') -app.on('window-all-closed', () => { - app.quit() -}) -``` - -## Events - -The `app` object emits the following events: - -### Event: 'will-finish-launching' - -Emitted when the application has finished basic startup. On Windows and Linux, -the `will-finish-launching` event is the same as the `ready` event; on macOS, -this event represents the `applicationWillFinishLaunching` notification of -`NSApplication`. You would usually set up listeners for the `open-file` and -`open-url` events here, and start the crash reporter and auto updater. - -In most cases, you should just do everything in the `ready` event handler. - -### Event: 'ready' - -Returns: - -* `launchInfo` Object _macOS_ - -Emitted when Electron has finished initializing. On macOS, `launchInfo` holds -the `userInfo` of the `NSUserNotification` that was used to open the application, -if it was launched from Notification Center. You can call `app.isReady()` to -check if this event has already fired. - -### Event: 'window-all-closed' - -Emitted when all windows have been closed. - -If you do not subscribe to this event and all windows are closed, the default -behavior is to quit the app; however, if you subscribe, you control whether the -app quits or not. If the user pressed `Cmd + Q`, or the developer called -`app.quit()`, Electron will first try to close all the windows and then emit the -`will-quit` event, and in this case the `window-all-closed` event would not be -emitted. - -### Event: 'before-quit' - -Returns: - -* `event` Event - -Emitted before the application starts closing its windows. -Calling `event.preventDefault()` will prevent the default behaviour, which is -terminating the application. - -**Note:** If application quit was initiated by `autoUpdater.quitAndInstall()` -then `before-quit` is emitted *after* emitting `close` event on all windows and -closing them. - -### Event: 'will-quit' - -Returns: - -* `event` Event - -Emitted when all windows have been closed and the application will quit. -Calling `event.preventDefault()` will prevent the default behaviour, which is -terminating the application. - -See the description of the `window-all-closed` event for the differences between -the `will-quit` and `window-all-closed` events. - -### Event: 'quit' - -Returns: - -* `event` Event -* `exitCode` Integer - -Emitted when the application is quitting. - -### Event: 'open-file' _macOS_ - -Returns: - -* `event` Event -* `path` String - -Emitted when the user wants to open a file with the application. The `open-file` -event is usually emitted when the application is already open and the OS wants -to reuse the application to open the file. `open-file` is also emitted when a -file is dropped onto the dock and the application is not yet running. Make sure -to listen for the `open-file` event very early in your application startup to -handle this case (even before the `ready` event is emitted). - -You should call `event.preventDefault()` if you want to handle this event. - -On Windows, you have to parse `process.argv` (in the main process) to get the -filepath. - -### Event: 'open-url' _macOS_ - -Returns: - -* `event` Event -* `url` String - -Emitted when the user wants to open a URL with the application. Your application's -`Info.plist` file must define the url scheme within the `CFBundleURLTypes` key, and -set `NSPrincipalClass` to `AtomApplication`. - -You should call `event.preventDefault()` if you want to handle this event. - -### Event: 'activate' _macOS_ - -Returns: - -* `event` Event -* `hasVisibleWindows` Boolean - -Emitted when the application is activated, which usually happens when the user -clicks on the application's dock icon. - -### Event: 'continue-activity' _macOS_ - -Returns: - -* `event` Event -* `type` String - A string identifying the activity. Maps to - [`NSUserActivity.activityType`][activity-type]. -* `userInfo` Object - Contains app-specific state stored by the activity on - another device. - -Emitted during [Handoff][handoff] when an activity from a different device wants -to be resumed. You should call `event.preventDefault()` if you want to handle -this event. - -A user activity can be continued only in an app that has the same developer Team -ID as the activity's source app and that supports the activity's type. -Supported activity types are specified in the app's `Info.plist` under the -`NSUserActivityTypes` key. - -### Event: 'browser-window-blur' - -Returns: - -* `event` Event -* `window` BrowserWindow - -Emitted when a [browserWindow](browser-window.md) gets blurred. - -### Event: 'browser-window-focus' - -Returns: - -* `event` Event -* `window` BrowserWindow - -Emitted when a [browserWindow](browser-window.md) gets focused. - -### Event: 'browser-window-created' - -Returns: - -* `event` Event -* `window` BrowserWindow - -Emitted when a new [browserWindow](browser-window.md) is created. - -### Event: 'web-contents-created' - -Returns: - -* `event` Event -* `webContents` WebContents - -Emitted when a new [webContents](web-contents.md) is created. - -### Event: 'certificate-error' - -Returns: - -* `event` Event -* `webContents` [WebContents](web-contents.md) -* `url` String -* `error` String - The error code -* `certificate` [Certificate](structures/certificate.md) -* `callback` Function - * `isTrusted` Boolean - Whether to consider the certificate as trusted - -Emitted when failed to verify the `certificate` for `url`, to trust the -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) - } else { - callback(false) - } -}) -``` - -### Event: 'select-client-certificate' - -Returns: - -* `event` Event -* `webContents` [WebContents](web-contents.md) -* `url` URL -* `certificateList` [Certificate[]](structures/certificate.md) -* `callback` Function - * `certificate` [Certificate](structures/certificate.md) (optional) - -Emitted when a client certificate is requested. - -The `url` corresponds to the navigation entry requesting the client certificate -and `callback` can be called with an entry filtered from the list. Using -`event.preventDefault()` prevents the application from using the first -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: 'login' - -Returns: - -* `event` Event -* `webContents` [WebContents](web-contents.md) -* `request` Object - * `method` String - * `url` URL - * `referrer` URL -* `authInfo` Object - * `isProxy` Boolean - * `scheme` String - * `host` String - * `port` Integer - * `realm` String -* `callback` Function - * `username` String - * `password` String - -Emitted when `webContents` wants to do basic auth. - -The default behavior is to cancel all authentications, to override this you -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: 'gpu-process-crashed' - -Returns: - -* `event` Event -* `killed` Boolean - -Emitted when the gpu process crashes or is killed. - -### Event: 'accessibility-support-changed' _macOS_ _Windows_ - -Returns: - -* `event` Event -* `accessibilitySupportEnabled` Boolean - `true` when Chrome's accessibility - support is enabled, `false` otherwise. - -Emitted when Chrome's accessibility support changes. This event fires when -assistive technologies, such as screen readers, are enabled or disabled. -See https://www.chromium.org/developers/design-documents/accessibility for more -details. - -## Methods - -The `app` object has the following methods: - -**Note:** Some methods are only available on specific operating systems and are -labeled as such. - -### `app.quit()` - -Try to close all windows. The `before-quit` event will be emitted first. If all -windows are successfully closed, the `will-quit` event will be emitted and by -default the application will terminate. - -This method guarantees that all `beforeunload` and `unload` event handlers are -correctly executed. It is possible that a window cancels the quitting by -returning `false` in the `beforeunload` event handler. - -### `app.exit([exitCode])` - -* `exitCode` Integer (optional) - -Exits immediately with `exitCode`. `exitCode` defaults to 0. - -All windows will be closed immediately without asking user and the `before-quit` -and `will-quit` events will not be emitted. - -### `app.relaunch([options])` - -* `options` Object (optional) - * `args` String[] - (optional) - * `execPath` String (optional) - -Relaunches the app when current instance exits. - -By default the new instance will use the same working directory and command line -arguments with current instance. When `args` is specified, the `args` will be -passed as command line arguments instead. When `execPath` is specified, the -`execPath` will be executed for relaunch instead of current app. - -Note that this method does not quit the app when executed, you have to call -`app.quit` or `app.exit` after calling `app.relaunch` to make the app restart. - -When `app.relaunch` is called for multiple times, multiple instances will be -started after current instance exited. - -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).concat(['--relaunch'])}) -app.exit(0) -``` - -### `app.isReady()` - -Returns `Boolean` - `true` if Electron has finished initializing, `false` otherwise. - -### `app.focus()` - -On Linux, focuses on the first visible window. On macOS, makes the application -the active app. On Windows, focuses on the application's first window. - -### `app.hide()` _macOS_ - -Hides all application windows without minimizing them. - -### `app.show()` _macOS_ - -Shows application windows after they were hidden. Does not automatically focus -them. - -### `app.getAppPath()` - -Returns `String` - The current application directory. - -### `app.getPath(name)` - -* `name` String - -Returns `String` - A path to a special directory or file associated with `name`. On -failure an `Error` is thrown. - -You can request the following paths by the name: - -* `home` User's home directory. -* `appData` Per-user application data directory, which by default points to: - * `%APPDATA%` on Windows - * `$XDG_CONFIG_HOME` or `~/.config` on Linux - * `~/Library/Application Support` on macOS -* `userData` The directory for storing your app's configuration files, which by - default it is the `appData` directory appended with your app's name. -* `temp` Temporary directory. -* `exe` The current executable file. -* `module` The `libchromiumcontent` library. -* `desktop` The current user's Desktop directory. -* `documents` Directory for a user's "My Documents". -* `downloads` Directory for a user's downloads. -* `music` Directory for a user's music. -* `pictures` Directory for a user's pictures. -* `videos` Directory for a user's videos. -* `pepperFlashSystemPlugin` Full path to the system version of the Pepper Flash plugin. - -### `app.setPath(name, path)` - -* `name` String -* `path` String - -Overrides the `path` to a special directory or file associated with `name`. If -the path specifies a directory that does not exist, the directory will be -created by this method. On failure an `Error` is thrown. - -You can only override paths of a `name` defined in `app.getPath`. - -By default, web pages' cookies and caches will be stored under the `userData` -directory. If you want to change this location, you have to override the -`userData` path before the `ready` event of the `app` module is emitted. - -### `app.getVersion()` - -Returns `String` - The version of the loaded application. If no version is found in the -application's `package.json` file, the version of the current bundle or -executable is returned. - -### `app.getName()` - -Returns `String` - The current application's name, which is the name in the application's -`package.json` file. - -Usually the `name` field of `package.json` is a short lowercased name, according -to the npm modules spec. You should usually also specify a `productName` -field, which is your application's full capitalized name, and which will be -preferred over `name` by Electron. - -### `app.setName(name)` - -* `name` String - -Overrides the current application's name. - -### `app.getLocale()` - -Returns `String` - The current application locale. Possible return values are documented -[here](locales.md). - -**Note:** When distributing your packaged app, you have to also ship the -`locales` folder. - -**Note:** On Windows you have to call it after the `ready` events gets emitted. - -### `app.addRecentDocument(path)` _macOS_ _Windows_ - -* `path` String - -Adds `path` to the recent documents list. - -This list is managed by the OS. On Windows you can visit the list from the task -bar, and on macOS you can visit it from dock menu. - -### `app.clearRecentDocuments()` _macOS_ _Windows_ - -Clears the recent documents list. - -### `app.setAsDefaultProtocolClient(protocol[, path, args])` _macOS_ _Windows_ - -* `protocol` String - The name of your protocol, without `://`. If you want your - app to handle `electron://` links, call this method with `electron` as the - parameter. -* `path` String (optional) _Windows_ - Defaults to `process.execPath` -* `args` String[] (optional) _Windows_ - Defaults to an empty array - -Returns `Boolean` - Whether the call succeeded. - -This method sets the current executable as the default handler for a protocol -(aka URI scheme). It allows you to integrate your app deeper into the operating -system. Once registered, all links with `your-protocol://` will be opened with -the current executable. The whole link, including protocol, will be passed to -your application as a parameter. - -On Windows you can provide optional parameters path, the path to your executable, -and args, an array of arguments to be passed to your executable when it launches. - -**Note:** On macOS, you can only register protocols that have been added to -your app's `info.plist`, which can not be modified at runtime. You can however -change the file with a simple text editor or script during build time. -Please refer to [Apple's documentation][CFBundleURLTypes] for details. - -The API uses the Windows Registry and LSSetDefaultHandlerForURLScheme internally. - -### `app.removeAsDefaultProtocolClient(protocol[, path, args])` _macOS_ _Windows_ - -* `protocol` String - The name of your protocol, without `://`. -* `path` String (optional) _Windows_ - Defaults to `process.execPath` -* `args` String[] (optional) _Windows_ - Defaults to an empty array - -Returns `Boolean` - Whether the call succeeded. - -This method checks if the current executable as the default handler for a -protocol (aka URI scheme). If so, it will remove the app as the default handler. - - -### `app.isDefaultProtocolClient(protocol[, path, args])` _macOS_ _Windows_ - -* `protocol` String - The name of your protocol, without `://`. -* `path` String (optional) _Windows_ - Defaults to `process.execPath` -* `args` String[] (optional) _Windows_ - Defaults to an empty array - -Returns `Boolean` - -This method checks if the current executable is the default handler for a protocol -(aka URI scheme). If so, it will return true. Otherwise, it will return false. - -**Note:** On macOS, you can use this method to check if the app has been -registered as the default protocol handler for a protocol. You can also verify -this by checking `~/Library/Preferences/com.apple.LaunchServices.plist` on the -macOS machine. Please refer to -[Apple's documentation][LSCopyDefaultHandlerForURLScheme] for details. - -The API uses the Windows Registry and LSCopyDefaultHandlerForURLScheme internally. - -### `app.setUserTasks(tasks)` _Windows_ - -* `tasks` [Task[]](structures/task.md) - Array of `Task` objects - -Adds `tasks` to the [Tasks][tasks] category of the JumpList on Windows. - -`tasks` is an array of [`Task`](structures/task.md) objects. - -Returns `Boolean` - Whether the call succeeded. - -**Note:** If you'd like to customize the Jump List even more use -`app.setJumpList(categories)` instead. - -### `app.getJumpListSettings()` _Windows_ - -Returns `Object`: - -* `minItems` Integer - The minimum number of items that will be shown in the - Jump List (for a more detailed description of this value see the - [MSDN docs][JumpListBeginListMSDN]). -* `removedItems` [JumpListItem[]](structures/jump-list-item.md) - Array of `JumpListItem` objects that correspond to - items that the user has explicitly removed from custom categories in the - Jump List. These items must not be re-added to the Jump List in the **next** - call to `app.setJumpList()`, Windows will not display any custom category - that contains any of the removed items. - -### `app.setJumpList(categories)` _Windows_ - -* `categories` [JumpListCategory[]](structures/jump-list-category.md) or `null` - Array of `JumpListCategory` objects. - -Sets or removes a custom Jump List for the application, and returns one of the -following strings: - -* `ok` - Nothing went wrong. -* `error` - One or more errors occurred, enable runtime logging to figure out - the likely cause. -* `invalidSeparatorError` - An attempt was made to add a separator to a - custom category in the Jump List. Separators are only allowed in the - standard `Tasks` category. -* `fileTypeRegistrationError` - An attempt was made to add a file link to - the Jump List for a file type the app isn't registered to handle. -* `customCategoryAccessDeniedError` - Custom categories can't be added to the - Jump List due to user privacy or group policy settings. - -If `categories` is `null` the previously set custom Jump List (if any) will be -replaced by the standard Jump List for the app (managed by Windows). - -**Note:** If a `JumpListCategory` object has neither the `type` nor the `name` -property set then its `type` is assumed to be `tasks`. If the `name` property -is set but the `type` property is omitted then the `type` is assumed to be -`custom`. - -**Note:** Users can remove items from custom categories, and Windows will not -allow a removed item to be added back into a custom category until **after** -the next successful call to `app.setJumpList(categories)`. Any attempt to -re-add a removed item to a custom category earlier than that will result in the -entire custom category being omitted from the Jump List. The list of removed -items can be obtained using `app.getJumpListSettings()`. - -Here's a very simple example of creating a custom Jump List: - -```javascript -const {app} = require('electron') - -app.setJumpList([ - { - type: 'custom', - name: 'Recent Projects', - items: [ - { type: 'file', path: 'C:\\Projects\\project1.proj' }, - { type: 'file', path: 'C:\\Projects\\project2.proj' } - ] - }, - { // has a name so `type` is assumed to be "custom" - name: 'Tools', - items: [ - { - type: 'task', - title: 'Tool A', - program: process.execPath, - args: '--run-tool-a', - icon: process.execPath, - iconIndex: 0, - description: 'Runs Tool A' - }, - { - type: 'task', - title: 'Tool B', - program: process.execPath, - args: '--run-tool-b', - icon: process.execPath, - iconIndex: 0, - description: 'Runs Tool B' - } - ] - }, - { type: 'frequent' }, - { // has no name and no type so `type` is assumed to be "tasks" - items: [ - { - type: 'task', - title: 'New Project', - program: process.execPath, - args: '--new-project', - description: 'Create a new project.' - }, - { type: 'separator' }, - { - type: 'task', - title: 'Recover Project', - program: process.execPath, - args: '--recover-project', - description: 'Recover Project' - } - ] - } -]) -``` - -### `app.makeSingleInstance(callback)` - -* `callback` Function - * `argv` String[] - An array of the second instance's command line arguments - * `workingDirectory` String - The second instance's working directory - -This method makes your application a Single Instance Application - instead of -allowing multiple instances of your app to run, this will ensure that only a -single instance of your app is running, and other instances signal this -instance and exit. - -`callback` will be called with `callback(argv, workingDirectory)` when a second -instance has been executed. `argv` is an Array of the second instance's command -line arguments, and `workingDirectory` is its current working directory. Usually -applications respond to this by making their primary window focused and -non-minimized. - -The `callback` is guaranteed to be executed after the `ready` event of `app` -gets emitted. - -This method returns `false` if your process is the primary instance of the -application and your app should continue loading. And returns `true` if your -process has sent its parameters to another instance, and you should immediately -quit. - -On macOS the system enforces single instance automatically when users try to open -a second instance of your app in Finder, and the `open-file` and `open-url` -events will be emitted for that. However when users start your app in command -line the system's single instance mechanism will be bypassed and you have to -use this method to ensure single instance. - -An example of activating the window of primary instance when a second instance -starts: - -```javascript -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 (shouldQuit) { - app.quit() -} - -// Create myWindow, load the rest of the app, etc... -app.on('ready', () => { -}) -``` - -### `app.releaseSingleInstance()` - -Releases all locks that were created by `makeSingleInstance`. This will allow -multiple instances of the application to once again run side by side. - -### `app.setUserActivity(type, userInfo[, webpageURL])` _macOS_ - -* `type` String - Uniquely identifies the activity. Maps to - [`NSUserActivity.activityType`][activity-type]. -* `userInfo` Object - App-specific state to store for use by another device. -* `webpageURL` String (optional) - The webpage to load in a browser if no suitable app is - installed on the resuming device. The scheme must be `http` or `https`. - -Creates an `NSUserActivity` and sets it as the current activity. The activity -is eligible for [Handoff][handoff] to another device afterward. - -### `app.getCurrentActivityType()` _macOS_ - -Returns `String` - The type of the currently running activity. - -### `app.setAppUserModelId(id)` _Windows_ - -* `id` String - -Changes the [Application User Model ID][app-user-model-id] to `id`. - -### `app.importCertificate(options, callback)` _LINUX_ - -* `options` Object - * `certificate` String - Path for the pkcs12 file. - * `password` String - Passphrase for the certificate. -* `callback` Function - * `result` Integer - Result of import. - -Imports the certificate in pkcs12 format into the platform certificate store. -`callback` is called with the `result` of import operation, a value of `0` -indicates success while any other value indicates failure according to chromium [net_error_list](https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h). - -### `app.disableHardwareAcceleration()` - -Disables hardware acceleration for current app. - -This method can only be called before app is ready. - -### `app.setBadgeCount(count)` _Linux_ _macOS_ - -* `count` Integer - -Returns `Boolean` - Whether the call succeeded. - -Sets the counter badge for current app. Setting the count to `0` will hide the -badge. - -On macOS it shows on the dock icon. On Linux it only works for Unity launcher, - -**Note:** Unity launcher requires the exsistence of a `.desktop` file to work, -for more information please read [Desktop Environment Integration][unity-requiremnt]. - -### `app.getBadgeCount()` _Linux_ _macOS_ - -Returns `Integer` - The current value displayed in the counter badge. - -### `app.isUnityRunning()` _Linux_ - -Returns `Boolean` - Whether the current desktop environment is Unity launcher. - -### `app.getLoginItemSettings()` _macOS_ _Windows_ - -Returns `Object`: - -* `openAtLogin` Boolean - `true` if the app is set to open at login. -* `openAsHidden` Boolean - `true` if the app is set to open as hidden at login. - This setting is only supported on macOS. -* `wasOpenedAtLogin` Boolean - `true` if the app was opened at login - automatically. This setting is only supported on macOS. -* `wasOpenedAsHidden` Boolean - `true` if the app was opened as a hidden login - item. This indicates that the app should not open any windows at startup. - This setting is only supported on macOS. -* `restoreState` Boolean - `true` if the app was opened as a login item that - should restore the state from the previous session. This indicates that the - app should restore the windows that were open the last time the app was - closed. This setting is only supported on macOS. - -**Note:** This API has no effect on -[MAS builds][mas-builds]. - -### `app.setLoginItemSettings(settings)` _macOS_ _Windows_ - -* `settings` Object - * `openAtLogin` Boolean (optional) - `true` to open the app at login, `false` to remove - the app as a login item. Defaults to `false`. - * `openAsHidden` Boolean (optional) - `true` to open the app as hidden. Defaults to - `false`. The user can edit this setting from the System Preferences so - `app.getLoginItemStatus().wasOpenedAsHidden` should be checked when the app - is opened to know the current value. This setting is only supported on - macOS. - -Set the app's login item settings. - -**Note:** This API has no effect on -[MAS builds][mas-builds]. - -### `app.isAccessibilitySupportEnabled()` _macOS_ _Windows_ - -Returns `Boolean` - `true` if Chrome's accessibility support is enabled, -`false` otherwise. This API will return `true` if the use of assistive -technologies, such as screen readers, has been detected. See -https://www.chromium.org/developers/design-documents/accessibility for more -details. - -### `app.setAboutPanelOptions(options)` _macOS_ - -* `options` Object - * `applicationName` String (optional) - The app's name. - * `applicationVersion` String (optional) - The app's version. - * `copyright` String (optional) - Copyright information. - * `credits` String (optional) - Credit information. - * `version` String (optional) - The app's build version number. - -Set the about panel options. This will override the values defined in the app's -`.plist` file. See the [Apple docs][about-panel-options] for more details. - -### `app.commandLine.appendSwitch(switch[, value])` - -* `switch` String - A command-line switch -* `value` String (optional) - A value for the given switch - -Append a switch (with optional `value`) to Chromium's command line. - -**Note:** This will not affect `process.argv`, and is mainly used by developers -to control some low-level Chromium behaviors. - -### `app.commandLine.appendArgument(value)` - -* `value` String - The argument to append to the command line - -Append an argument to Chromium's command line. The argument will be quoted -correctly. - -**Note:** This will not affect `process.argv`. - -### `app.dock.bounce([type])` _macOS_ - -* `type` String (optional) - Can be `critical` or `informational`. The default is - `informational` - -When `critical` is passed, the dock icon will bounce until either the -application becomes active or the request is canceled. - -When `informational` is passed, the dock icon will bounce for one second. -However, the request remains active until either the application becomes active -or the request is canceled. - -Returns `Integer` an ID representing the request. - -### `app.dock.cancelBounce(id)` _macOS_ - -* `id` Integer - -Cancel the bounce of `id`. - -### `app.dock.downloadFinished(filePath)` _macOS_ - -* `filePath` String - -Bounces the Downloads stack if the filePath is inside the Downloads folder. - -### `app.dock.setBadge(text)` _macOS_ - -* `text` String - -Sets the string to be displayed in the dock’s badging area. - -### `app.dock.getBadge()` _macOS_ - -Returns `String` - The badge string of the dock. - -### `app.dock.hide()` _macOS_ - -Hides the dock icon. - -### `app.dock.show()` _macOS_ - -Shows the dock icon. - -### `app.dock.isVisible()` _macOS_ - -Returns `Boolean` - Whether the dock icon is visible. -The `app.dock.show()` call is asynchronous so this method might not -return true immediately after that call. - -### `app.dock.setMenu(menu)` _macOS_ - -* `menu` [Menu](menu.md) - -Sets the application's [dock menu][dock-menu]. - -### `app.dock.setIcon(image)` _macOS_ - -* `image` ([NativeImage](native-image.md) | String) - -Sets the `image` associated with this dock icon. - -[dock-menu]:https://developer.apple.com/library/mac/documentation/Carbon/Conceptual/customizing_docktile/concepts/dockconcepts.html#//apple_ref/doc/uid/TP30000986-CH2-TPXREF103 -[tasks]:http://msdn.microsoft.com/en-us/library/windows/desktop/dd378460(v=vs.85).aspx#tasks -[app-user-model-id]: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx -[CFBundleURLTypes]: https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/TP40009249-102207-TPXREF115 -[LSCopyDefaultHandlerForURLScheme]: https://developer.apple.com/library/mac/documentation/Carbon/Reference/LaunchServicesReference/#//apple_ref/c/func/LSCopyDefaultHandlerForURLScheme -[handoff]: https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/Handoff/HandoffFundamentals/HandoffFundamentals.html -[activity-type]: https://developer.apple.com/library/ios/documentation/Foundation/Reference/NSUserActivity_Class/index.html#//apple_ref/occ/instp/NSUserActivity/activityType -[unity-requiremnt]: ../tutorial/desktop-environment-integration.md#unity-launcher-shortcuts-linux -[mas-builds]: ../tutorial/mac-app-store-submission-guide.md -[JumpListBeginListMSDN]: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378398(v=vs.85).aspx -[about-panel-options]: https://developer.apple.com/reference/appkit/nsapplication/1428479-orderfrontstandardaboutpanelwith?language=objc diff --git a/docs-translations/th-TH/api/auto-updater.md b/docs-translations/th-TH/api/auto-updater.md deleted file mode 100644 index 9c743a0376..0000000000 --- a/docs-translations/th-TH/api/auto-updater.md +++ /dev/null @@ -1,139 +0,0 @@ -# autoUpdater - -> Enable apps to automatically update themselves. - -Process: [Main](../glossary.md#main-process) - -The `autoUpdater` module provides an interface for the -[Squirrel](https://github.com/Squirrel) framework. - -You can quickly launch a multi-platform release server for distributing your -application by using one of these projects: - -- [nuts][nuts]: *A smart release server for your applications, using GitHub as a backend. Auto-updates with Squirrel (Mac & Windows)* -- [electron-release-server][electron-release-server]: *A fully featured, - self-hosted release server for electron applications, compatible with - auto-updater* -- [squirrel-updates-server][squirrel-updates-server]: *A simple node.js server - for Squirrel.Mac and Squirrel.Windows which uses GitHub releases* -- [squirrel-release-server][squirrel-release-server]: *A simple PHP application for Squirrel.Windows which reads updates from a folder. Supports delta updates.* - -## Platform notices - -Though `autoUpdater` provides a uniform API for different platforms, there are -still some subtle differences on each platform. - -### macOS - -On macOS, the `autoUpdater` module is built upon [Squirrel.Mac][squirrel-mac], -meaning you don't need any special setup to make it work. For server-side -requirements, you can read [Server Support][server-support]. Note that [App -Transport Security](https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW35) (ATS) applies to all requests made as part of the -update process. Apps that need to disable ATS can add the -`NSAllowsArbitraryLoads` key to their app's plist. - -**Note:** Your application must be signed for automatic updates on macOS. -This is a requirement of `Squirrel.Mac`. - -### Windows - -On Windows, you have to install your app into a user's machine before you can -use the `autoUpdater`, so it is recommended that you use the -[electron-winstaller][installer-lib], [electron-builder][electron-builder-lib] or the [grunt-electron-installer][installer] package to generate a Windows installer. - -When using [electron-winstaller][installer-lib] or [electron-builder][electron-builder-lib] make sure you do not try to update your app [the first time it runs](https://github.com/electron/windows-installer#handling-squirrel-events) (Also see [this issue for more info](https://github.com/electron/electron/issues/7155)). It's also recommended to use [electron-squirrel-startup](https://github.com/mongodb-js/electron-squirrel-startup) to get desktop shortcuts for your app. - -The installer generated with Squirrel will create a shortcut icon with an -[Application User Model ID][app-user-model-id] in the format of -`com.squirrel.PACKAGE_ID.YOUR_EXE_WITHOUT_DOT_EXE`, examples are -`com.squirrel.slack.Slack` and `com.squirrel.code.Code`. You have to use the -same ID for your app with `app.setAppUserModelId` API, otherwise Windows will -not be able to pin your app properly in task bar. - -The server-side setup is also different from macOS. You can read the documents of -[Squirrel.Windows][squirrel-windows] to get more details. - -### Linux - -There is no built-in support for auto-updater on Linux, so it is recommended to -use the distribution's package manager to update your app. - -## Events - -The `autoUpdater` object emits the following events: - -### Event: 'error' - -Returns: - -* `error` Error - -Emitted when there is an error while updating. - -### Event: 'checking-for-update' - -Emitted when checking if an update has started. - -### Event: 'update-available' - -Emitted when there is an available update. The update is downloaded -automatically. - -### Event: 'update-not-available' - -Emitted when there is no available update. - -### Event: 'update-downloaded' - -Returns: - -* `event` Event -* `releaseNotes` String -* `releaseName` String -* `releaseDate` Date -* `updateURL` String - -Emitted when an update has been downloaded. - -On Windows only `releaseName` is available. - -## Methods - -The `autoUpdater` object has the following methods: - -### `autoUpdater.setFeedURL(url[, requestHeaders])` - -* `url` String -* `requestHeaders` Object _macOS_ (optional) - HTTP request headers. - -Sets the `url` and initialize the auto updater. - -### `autoUpdater.getFeedURL()` - -Returns `String` - The current update feed URL. - -### `autoUpdater.checkForUpdates()` - -Asks the server whether there is an update. You must call `setFeedURL` before -using this API. - -### `autoUpdater.quitAndInstall()` - -Restarts the app and installs the update after it has been downloaded. It -should only be called after `update-downloaded` has been emitted. - -**Note:** `autoUpdater.quitAndInstall()` will close all application windows -first and only emit `before-quit` event on `app` after that. This is different -from the normal quit event sequence. - -[squirrel-mac]: https://github.com/Squirrel/Squirrel.Mac -[server-support]: https://github.com/Squirrel/Squirrel.Mac#server-support -[squirrel-windows]: https://github.com/Squirrel/Squirrel.Windows -[installer]: https://github.com/electron/grunt-electron-installer -[installer-lib]: https://github.com/electron/windows-installer -[electron-builder-lib]: https://github.com/electron-userland/electron-builder -[app-user-model-id]: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx -[electron-release-server]: https://github.com/ArekSredzki/electron-release-server -[squirrel-updates-server]: https://github.com/Aluxian/squirrel-updates-server -[nuts]: https://github.com/GitbookIO/nuts -[squirrel-release-server]: https://github.com/Arcath/squirrel-release-server diff --git a/docs-translations/th-TH/api/browser-window-proxy.md b/docs-translations/th-TH/api/browser-window-proxy.md deleted file mode 100644 index 2bf5a15f74..0000000000 --- a/docs-translations/th-TH/api/browser-window-proxy.md +++ /dev/null @@ -1,53 +0,0 @@ -## Class: BrowserWindowProxy - -> Manipulate the child browser window - -Process: [Renderer](../glossary.md#renderer-process) - -The `BrowserWindowProxy` object is returned from `window.open` and provides -limited functionality with the child window. - -### Instance Methods - -The `BrowserWindowProxy` object has the following instance methods: - -#### `win.blur()` - -Removes focus from the child window. - -#### `win.close()` - -Forcefully closes the child window without calling its unload event. - -#### `win.eval(code)` - -* `code` String - -Evaluates the code in the child window. - -#### `win.focus()` - -Focuses the child window (brings the window to front). - -#### `win.print()` - -Invokes the print dialog on the child window. - -#### `win.postMessage(message, targetOrigin)` - -* `message` String -* `targetOrigin` String - -Sends a message to the child window with the specified origin or `*` for no -origin preference. - -In addition to these methods, the child window implements `window.opener` object -with no properties and a single method. - -### Instance Properties - -The `BrowserWindowProxy` object has the following instance properties: - -#### `win.closed` - -A Boolean that is set to true after the child window gets closed. diff --git a/docs-translations/th-TH/api/browser-window.md b/docs-translations/th-TH/api/browser-window.md deleted file mode 100644 index 306a365a58..0000000000 --- a/docs-translations/th-TH/api/browser-window.md +++ /dev/null @@ -1,1272 +0,0 @@ -# BrowserWindow - -> Create and control browser windows. - -Process: [Main](../glossary.md#main-process) - -```javascript -// In the main process. -const {BrowserWindow} = require('electron') - -// Or use `remote` from the renderer process. -// const {BrowserWindow} = require('electron').remote - -let win = new BrowserWindow({width: 800, height: 600}) -win.on('closed', () => { - win = null -}) - -// Load a remote URL -win.loadURL('https://github.com') - -// Or load a local HTML file -win.loadURL(`file://${__dirname}/app/index.html`) -``` - -## Frameless window - -To create a window without chrome, or a transparent window in arbitrary shape, -you can use the [Frameless Window](frameless-window.md) API. - -## Showing window gracefully - -When loading a page in the window directly, users may see the page load incrementally, which is not a good experience for a native app. To make the window display -without visual flash, there are two solutions for different situations. - -### Using `ready-to-show` event - -While loading the page, the `ready-to-show` event will be emitted when renderer -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() -}) -``` - -This is event is usually emitted after the `did-finish-load` event, but for -pages with many remote resources, it may be emitted before the `did-finish-load` -event. - -### Setting `backgroundColor` - -For a complex app, the `ready-to-show` event could be emitted too late, making -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') -``` - -Note that even for apps that use `ready-to-show` event, it is still recommended -to set `backgroundColor` to make app feel more native. - -## Parent and child windows - -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. - -### Modal windows - -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', () => { - child.show() -}) -``` - -### Platform notices - -* On macOS modal windows will be displayed as sheets attached to the parent window. -* On macOS the child windows will keep the relative position to parent window - when parent window moves, while on Windows and Linux child windows will not - move. -* On Windows it is not supported to change parent window dynamically. -* On Linux the type of modal windows will be changed to `dialog`. -* On Linux many desktop environments do not support hiding a modal window. - -## Class: BrowserWindow - -> Create and control browser windows. - -Process: [Main](../glossary.md#main-process) - -`BrowserWindow` is an -[EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter). - -It creates a new `BrowserWindow` with native properties as set by the `options`. - -### `new BrowserWindow([options])` - -* `options` Object (optional) - * `width` Integer (optional) - Window's width in pixels. Default is `800`. - * `height` Integer (optional) - Window's height in pixels. Default is `600`. - * `x` Integer (optional) (**required** if y is used) - Window's left offset from screen. - Default is to center the window. - * `y` Integer (optional) (**required** if x is used) - Window's top offset from screen. - Default is to center the window. - * `useContentSize` Boolean (optional) - The `width` and `height` would be used as web - page's size, which means the actual window's size will include window - frame's size and be slightly larger. Default is `false`. - * `center` Boolean (optional) - Show window in the center of the screen. - * `minWidth` Integer (optional) - Window's minimum width. Default is `0`. - * `minHeight` Integer (optional) - Window's minimum height. Default is `0`. - * `maxWidth` Integer (optional) - Window's maximum width. Default is no limit. - * `maxHeight` Integer (optional) - Window's maximum height. Default is no limit. - * `resizable` Boolean (optional) - Whether window is resizable. Default is `true`. - * `movable` Boolean (optional) - Whether window is movable. This is not implemented - on Linux. Default is `true`. - * `minimizable` Boolean (optional) - Whether window is minimizable. This is not - implemented on Linux. Default is `true`. - * `maximizable` Boolean (optional) - Whether window is maximizable. This is not - implemented on Linux. Default is `true`. - * `closable` Boolean (optional) - Whether window is closable. This is not implemented - on Linux. Default is `true`. - * `focusable` Boolean (optional) - Whether the window can be focused. Default is - `true`. On Windows setting `focusable: false` also implies setting - `skipTaskbar: true`. On Linux setting `focusable: false` makes the window - stop interacting with wm, so the window will always stay on top in all - workspaces. - * `alwaysOnTop` Boolean (optional) - Whether the window should always stay on top of - other windows. Default is `false`. - * `fullscreen` Boolean (optional) - Whether the window should show in fullscreen. When - explicitly set to `false` the fullscreen button will be hidden or disabled - on macOS. Default is `false`. - * `fullscreenable` Boolean (optional) - Whether the window can be put into fullscreen - mode. On macOS, also whether the maximize/zoom button should toggle full - screen mode or maximize window. Default is `true`. - * `skipTaskbar` Boolean (optional) - Whether to show the window in taskbar. Default is - `false`. - * `kiosk` Boolean (optional) - The kiosk mode. Default is `false`. - * `title` String (optional) - Default window title. Default is `"Electron"`. - * `icon` ([NativeImage](native-image.md) | String) (optional) - The window icon. On Windows it is - recommended to use `ICO` icons to get best visual effects, you can also - leave it undefined so the executable's icon will be used. - * `show` Boolean (optional) - Whether window should be shown when created. Default is - `true`. - * `frame` Boolean (optional) - Specify `false` to create a - [Frameless Window](frameless-window.md). Default is `true`. - * `parent` BrowserWindow (optional) - Specify parent window. Default is `null`. - * `modal` Boolean (optional) - Whether this is a modal window. This only works when the - window is a child window. Default is `false`. - * `acceptFirstMouse` Boolean (optional) - Whether the web view accepts a single - mouse-down event that simultaneously activates the window. Default is - `false`. - * `disableAutoHideCursor` Boolean (optional) - Whether to hide cursor when typing. - Default is `false`. - * `autoHideMenuBar` Boolean (optional) - Auto hide the menu bar unless the `Alt` - key is pressed. Default is `false`. - * `enableLargerThanScreen` Boolean (optional) - Enable the window to be resized larger - than screen. Default is `false`. - * `backgroundColor` String (optional) - Window's background color as Hexadecimal value, - like `#66CD00` or `#FFF` or `#80FFFFFF` (alpha is supported). Default is - `#FFF` (white). - * `hasShadow` Boolean (optional) - Whether window should have a shadow. This is only - implemented on macOS. Default is `true`. - * `darkTheme` Boolean (optional) - Forces using dark theme for the window, only works on - some GTK+3 desktop environments. Default is `false`. - * `transparent` Boolean (optional) - Makes the window [transparent](frameless-window.md). - Default is `false`. - * `type` String (optional) - The type of window, default is normal window. See more about - this below. - * `titleBarStyle` String (optional) - The style of window title bar. Default is `default`. Possible values are: - * `default` - Results in the standard gray opaque Mac title - bar. - * `hidden` - Results in a hidden title bar and a full size content window, yet - the title bar still has the standard window controls ("traffic lights") in - the top left. - * `hidden-inset` - Results in a hidden title bar with an alternative look - where the traffic light buttons are slightly more inset from the window edge. - * `thickFrame` Boolean (optional) - Use `WS_THICKFRAME` style for frameless windows on - Windows, which adds standard window frame. Setting it to `false` will remove - window shadow and window animations. Default is `true`. - * `vibrancy` String (optional) - Add a type of vibrancy effect to the window, only on - macOS. Can be `appearance-based`, `light`, `dark`, `titlebar`, `selection`, - `menu`, `popover`, `sidebar`, `medium-light` or `ultra-dark`. - * `zoomToPageWidth` Boolean (optional) - Controls the behavior on macOS when - option-clicking the green stoplight button on the toolbar or by clicking the - Window > Zoom menu item. If `true`, the window will grow to the preferred - width of the web page when zoomed, `false` will cause it to zoom to the - width of the screen. This will also affect the behavior when calling - `maximize()` directly. Default is `false`. - * `webPreferences` Object (optional) - Settings of web page's features. - * `devTools` Boolean (optional) - Whether to enable DevTools. If it is set to `false`, can not use `BrowserWindow.webContents.openDevTools()` to open DevTools. Default is `true`. - * `nodeIntegration` Boolean (optional) - Whether node integration is enabled. Default - is `true`. - * `preload` String (optional) - Specifies a script that will be loaded before other - scripts run in the page. This script will always have access to node APIs - no matter whether node integration is turned on or off. The value should - be the absolute file path to the script. - When node integration is turned off, the preload script can reintroduce - Node global symbols back to the global scope. See example - [here](process.md#event-loaded). - * `session` [Session](session.md#class-session) (optional) - Sets the session used by the - page. Instead of passing the Session object directly, you can also choose to - use the `partition` option instead, which accepts a partition string. When - both `session` and `partition` are provided, `session` will be preferred. - Default is the default session. - * `partition` String (optional) - Sets the session used by the page according to the - session's partition string. If `partition` starts with `persist:`, the page - will use a persistent session available to all pages in the app with the - same `partition`. If there is no `persist:` prefix, the page will use an - in-memory session. By assigning the same `partition`, multiple pages can share - the same session. Default is the default session. - * `zoomFactor` Number (optional) - The default zoom factor of the page, `3.0` represents - `300%`. Default is `1.0`. - * `javascript` Boolean (optional) - Enables JavaScript support. Default is `true`. - * `webSecurity` Boolean (optional) - When `false`, it will disable the - same-origin policy (usually using testing websites by people), and set - `allowDisplayingInsecureContent` and `allowRunningInsecureContent` to - `true` if these two options are not set by user. Default is `true`. - * `allowDisplayingInsecureContent` Boolean (optional) - Allow an https page to display - content like images from http URLs. Default is `false`. - * `allowRunningInsecureContent` Boolean (optional) - Allow an https page to run - JavaScript, CSS or plugins from http URLs. Default is `false`. - * `images` Boolean (optional) - Enables image support. Default is `true`. - * `textAreasAreResizable` Boolean (optional) - Make TextArea elements resizable. Default - is `true`. - * `webgl` Boolean (optional) - Enables WebGL support. Default is `true`. - * `webaudio` Boolean (optional) - Enables WebAudio support. Default is `true`. - * `plugins` Boolean (optional) - Whether plugins should be enabled. Default is `false`. - * `experimentalFeatures` Boolean (optional) - Enables Chromium's experimental features. - Default is `false`. - * `experimentalCanvasFeatures` Boolean (optional) - Enables Chromium's experimental - canvas features. Default is `false`. - * `scrollBounce` Boolean (optional) - Enables scroll bounce (rubber banding) effect on - macOS. Default is `false`. - * `blinkFeatures` String (optional) - A list of feature strings separated by `,`, like - `CSSVariables,KeyboardEventKey` to enable. The full list of supported feature - strings can be found in the [RuntimeEnabledFeatures.in][blink-feature-string] - file. - * `disableBlinkFeatures` String (optional) - A list of feature strings separated by `,`, - like `CSSVariables,KeyboardEventKey` to disable. The full list of supported - feature strings can be found in the - [RuntimeEnabledFeatures.in][blink-feature-string] file. - * `defaultFontFamily` Object (optional) - Sets the default font for the font-family. - * `standard` String (optional) - Defaults to `Times New Roman`. - * `serif` String (optional) - Defaults to `Times New Roman`. - * `sansSerif` String (optional) - Defaults to `Arial`. - * `monospace` String (optional) - Defaults to `Courier New`. - * `cursive` String (optional) - Defaults to `Script`. - * `fantasy` String (optional) - Defaults to `Impact`. - * `defaultFontSize` Integer (optional) - Defaults to `16`. - * `defaultMonospaceFontSize` Integer (optional) - Defaults to `13`. - * `minimumFontSize` Integer (optional) - Defaults to `0`. - * `defaultEncoding` String (optional) - Defaults to `ISO-8859-1`. - * `backgroundThrottling` Boolean (optional) - Whether to throttle animations and timers - when the page becomes background. Defaults to `true`. - * `offscreen` Boolean (optional) - Whether to enable offscreen rendering for the browser - window. Defaults to `false`. See the - [offscreen rendering tutorial](../tutorial/offscreen-rendering.md) for - more details. - * `sandbox` Boolean (optional) - Whether to enable Chromium OS-level sandbox. - * `contextIsolation` Boolean (optional) - Whether to run Electron APIs and - the specified `preload` script in a separate JavaScript context. Defaults - to `false`. The context that the `preload` script runs in will still - have full access to the `document` and `window` globals but it will use - its own set of JavaScript builtins (`Array`, `Object`, `JSON`, etc.) - and will be isolated from any changes made to the global environment - by the loaded page. The Electron API will only be available in the - `preload` script and not the loaded page. This option should be used when - loading potentially untrusted remote content to ensure the loaded content - cannot tamper with the `preload` script and any Electron APIs being used. - This option uses the same technique used by [Chrome Content Scripts][chrome-content-scripts]. - You can access this context in the dev tools by selecting the - 'Electron Isolated Context' entry in the combo box at the top of the - Console tab. **Note:** This option is currently experimental and may - change or be removed in future Electron releases. - -When setting minimum or maximum window size with `minWidth`/`maxWidth`/ -`minHeight`/`maxHeight`, it only constrains the users. It won't prevent you from -passing a size that does not follow size constraints to `setBounds`/`setSize` or -to the constructor of `BrowserWindow`. - -The possible values and behaviors of the `type` option are platform dependent. -Possible values are: - -* On Linux, possible types are `desktop`, `dock`, `toolbar`, `splash`, - `notification`. -* On macOS, possible types are `desktop`, `textured`. - * The `textured` type adds metal gradient appearance - (`NSTexturedBackgroundWindowMask`). - * The `desktop` type places the window at the desktop background window level - (`kCGDesktopWindowLevel - 1`). Note that desktop window will not receive - focus, keyboard or mouse events, but you can use `globalShortcut` to receive - input sparingly. -* On Windows, possible type is `toolbar`. - -### Instance Events - -Objects created with `new BrowserWindow` emit the following events: - -**Note:** Some events are only available on specific operating systems and are -labeled as such. - -#### Event: 'page-title-updated' - -Returns: - -* `event` Event -* `title` String - -Emitted when the document changed its title, calling `event.preventDefault()` -will prevent the native window's title from changing. - -#### Event: 'close' - -Returns: - -* `event` Event - -Emitted when the window is going to be closed. It's emitted before the -`beforeunload` and `unload` event of the DOM. Calling `event.preventDefault()` -will cancel the close. - -Usually you would want to use the `beforeunload` handler to decide whether the -window should be closed, which will also be called when the window is -reloaded. In Electron, returning any value other than `undefined` would cancel the -close. For example: - -```javascript -window.onbeforeunload = (e) => { - 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 -} -``` - -#### Event: 'closed' - -Emitted when the window is closed. After you have received this event you should -remove the reference to the window and avoid using it any more. - -#### Event: 'unresponsive' - -Emitted when the web page becomes unresponsive. - -#### Event: 'responsive' - -Emitted when the unresponsive web page becomes responsive again. - -#### Event: 'blur' - -Emitted when the window loses focus. - -#### Event: 'focus' - -Emitted when the window gains focus. - -#### Event: 'show' - -Emitted when the window is shown. - -#### Event: 'hide' - -Emitted when the window is hidden. - -#### Event: 'ready-to-show' - -Emitted when the web page has been rendered and window can be displayed without -a visual flash. - -#### Event: 'maximize' - -Emitted when window is maximized. - -#### Event: 'unmaximize' - -Emitted when the window exits from a maximized state. - -#### Event: 'minimize' - -Emitted when the window is minimized. - -#### Event: 'restore' - -Emitted when the window is restored from a minimized state. - -#### Event: 'resize' - -Emitted when the window is being resized. - -#### Event: 'move' - -Emitted when the window is being moved to a new position. - -__Note__: On macOS this event is just an alias of `moved`. - -#### Event: 'moved' _macOS_ - -Emitted once when the window is moved to a new position. - -#### Event: 'enter-full-screen' - -Emitted when the window enters a full-screen state. - -#### Event: 'leave-full-screen' - -Emitted when the window leaves a full-screen state. - -#### Event: 'enter-html-full-screen' - -Emitted when the window enters a full-screen state triggered by HTML API. - -#### Event: 'leave-html-full-screen' - -Emitted when the window leaves a full-screen state triggered by HTML API. - -#### Event: 'app-command' _Windows_ - -Returns: - -* `event` Event -* `command` String - -Emitted when an [App Command](https://msdn.microsoft.com/en-us/library/windows/desktop/ms646275(v=vs.85).aspx) -is invoked. These are typically related to keyboard media keys or browser -commands, as well as the "Back" button built into some mice on Windows. - -Commands are lowercased, underscores are replaced with hyphens, and the -`APPCOMMAND_` prefix is stripped off. -e.g. `APPCOMMAND_BROWSER_BACKWARD` is emitted as `browser-backward`. - -```javascript -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' && win.webContents.canGoBack()) { - win.webContents.goBack() - } -}) -``` - -#### Event: 'scroll-touch-begin' _macOS_ - -Emitted when scroll wheel event phase has begun. - -#### Event: 'scroll-touch-end' _macOS_ - -Emitted when scroll wheel event phase has ended. - -#### Event: 'scroll-touch-edge' _macOS_ - -Emitted when scroll wheel event phase filed upon reaching the edge of element. - -#### Event: 'swipe' _macOS_ - -Returns: - -* `event` Event -* `direction` String - -Emitted on 3-finger swipe. Possible directions are `up`, `right`, `down`, `left`. - -### Static Methods - -The `BrowserWindow` class has the following static methods: - -#### `BrowserWindow.getAllWindows()` - -Returns `BrowserWindow[]` - An array of all opened browser windows. - -#### `BrowserWindow.getFocusedWindow()` - -Returns `BrowserWindow` - The window that is focused in this application, otherwise returns `null`. - -#### `BrowserWindow.fromWebContents(webContents)` - -* `webContents` [WebContents](web-contents.md) - -Returns `BrowserWindow` - The window that owns the given `webContents`. - -#### `BrowserWindow.fromId(id)` - -* `id` Integer - -Returns `BrowserWindow` - The window with the given `id`. - -#### `BrowserWindow.addDevToolsExtension(path)` - -* `path` String - -Adds DevTools extension located at `path`, and returns extension's name. - -The extension will be remembered so you only need to call this API once, this -API is not for programming use. If you try to add an extension that has already -been loaded, this method will not return and instead log a warning to the -console. - -The method will also not return if the extension's manifest is missing or incomplete. - -**Note:** This API cannot be called before the `ready` event of the `app` module -is emitted. - -#### `BrowserWindow.removeDevToolsExtension(name)` - -* `name` String - -Remove a DevTools extension by name. - -**Note:** This API cannot be called before the `ready` event of the `app` module -is emitted. - -#### `BrowserWindow.getDevToolsExtensions()` - -Returns `Object` - The keys are the extension names and each value is -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 -is emitted. - -### Instance Properties - -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}) -win.loadURL('https://github.com') -``` - -#### `win.webContents` - -A `WebContents` object this window owns. All web page related events and -operations will be done via it. - -See the [`webContents` documentation](web-contents.md) for its methods and -events. - -#### `win.id` - -A `Integer` representing the unique ID of the window. - -### Instance Methods - -Objects created with `new BrowserWindow` have the following instance methods: - -**Note:** Some methods are only available on specific operating systems and are -labeled as such. - -#### `win.destroy()` - -Force closing the window, the `unload` and `beforeunload` event won't be emitted -for the web page, and `close` event will also not be emitted -for this window, but it guarantees the `closed` event will be emitted. - -#### `win.close()` - -Try to close the window. This has the same effect as a user manually clicking -the close button of the window. The web page may cancel the close though. See -the [close event](#event-close). - -#### `win.focus()` - -Focuses on the window. - -#### `win.blur()` - -Removes focus from the window. - -#### `win.isFocused()` - -Returns `Boolean` - Whether the window is focused. - -#### `win.isDestroyed()` - -Returns `Boolean` - Whether the window is destroyed. - -#### `win.show()` - -Shows and gives focus to the window. - -#### `win.showInactive()` - -Shows the window but doesn't focus on it. - -#### `win.hide()` - -Hides the window. - -#### `win.isVisible()` - -Returns `Boolean` - Whether the window is visible to the user. - -#### `win.isModal()` - -Returns `Boolean` - Whether current window is a modal window. - -#### `win.maximize()` - -Maximizes the window. - -#### `win.unmaximize()` - -Unmaximizes the window. - -#### `win.isMaximized()` - -Returns `Boolean` - Whether the window is maximized. - -#### `win.minimize()` - -Minimizes the window. On some platforms the minimized window will be shown in -the Dock. - -#### `win.restore()` - -Restores the window from minimized state to its previous state. - -#### `win.isMinimized()` - -Returns `Boolean` - Whether the window is minimized. - -#### `win.setFullScreen(flag)` - -* `flag` Boolean - -Sets whether the window should be in fullscreen mode. - -#### `win.isFullScreen()` - -Returns `Boolean` - Whether the window is in fullscreen mode. - -#### `win.setAspectRatio(aspectRatio[, extraSize])` _macOS_ - -* `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. - * `width` Integer - * `height` Integer - -This will make a window maintain an aspect ratio. The extra size allows a -developer to have space, specified in pixels, not included within the aspect -ratio calculations. This API already takes into account the difference between a -window's size and its content size. - -Consider a normal window with an HD video player and associated controls. -Perhaps there are 15 pixels of controls on the left edge, 25 pixels of controls -on the right edge and 50 pixels of controls below the player. In order to -maintain a 16:9 aspect ratio (standard aspect ratio for HD @1920x1080) within -the player itself we would call this function with arguments of 16/9 and -[ 40, 50 ]. The second argument doesn't care where the extra width and height -are within the content view--only that they exist. Just sum any extra width and -height areas you have within the overall content view. - -#### `win.previewFile(path[, displayName])` _macOS_ - -* `path` String - The absolute path to the file to preview with QuickLook. This - is important as Quick Look uses the file name and file extension on the path - to determine the content type of the file to open. -* `displayName` String (Optional) - The name of the file to display on the - Quick Look modal view. This is purely visual and does not affect the content - type of the file. Defaults to `path`. - -Uses [Quick Look][quick-look] to preview a file at a given path. - -#### `win.closeFilePreview()` _macOS_ - -Closes the currently open [Quick Look][quick-look] panel. - -#### `win.setBounds(bounds[, animate])` - -* `bounds` [Rectangle](structures/rectangle.md) -* `animate` Boolean (optional) _macOS_ - -Resizes and moves the window to the supplied bounds - -#### `win.getBounds()` - -Returns [`Rectangle`](structures/rectangle.md) - -#### `win.setContentBounds(bounds[, animate])` - -* `bounds` [Rectangle](structures/rectangle.md) -* `animate` Boolean (optional) _macOS_ - -Resizes and moves the window's client area (e.g. the web page) to -the supplied bounds. - -#### `win.getContentBounds()` - -Returns [`Rectangle`](structures/rectangle.md) - -#### `win.setSize(width, height[, animate])` - -* `width` Integer -* `height` Integer -* `animate` Boolean (optional) _macOS_ - -Resizes the window to `width` and `height`. - -#### `win.getSize()` - -Returns `Integer[]` - Contains the window's width and height. - -#### `win.setContentSize(width, height[, animate])` - -* `width` Integer -* `height` Integer -* `animate` Boolean (optional) _macOS_ - -Resizes the window's client area (e.g. the web page) to `width` and `height`. - -#### `win.getContentSize()` - -Returns `Integer[]` - Contains the window's client area's width and height. - -#### `win.setMinimumSize(width, height)` - -* `width` Integer -* `height` Integer - -Sets the minimum size of window to `width` and `height`. - -#### `win.getMinimumSize()` - -Returns `Integer[]` - Contains the window's minimum width and height. - -#### `win.setMaximumSize(width, height)` - -* `width` Integer -* `height` Integer - -Sets the maximum size of window to `width` and `height`. - -#### `win.getMaximumSize()` - -Returns `Integer[]` - Contains the window's maximum width and height. - -#### `win.setResizable(resizable)` - -* `resizable` Boolean - -Sets whether the window can be manually resized by user. - -#### `win.isResizable()` - -Returns `Boolean` - Whether the window can be manually resized by user. - -#### `win.setMovable(movable)` _macOS_ _Windows_ - -* `movable` Boolean - -Sets whether the window can be moved by user. On Linux does nothing. - -#### `win.isMovable()` _macOS_ _Windows_ - -Returns `Boolean` - Whether the window can be moved by user. - -On Linux always returns `true`. - -#### `win.setMinimizable(minimizable)` _macOS_ _Windows_ - -* `minimizable` Boolean - -Sets whether the window can be manually minimized by user. On Linux does -nothing. - -#### `win.isMinimizable()` _macOS_ _Windows_ - -Returns `Boolean` - Whether the window can be manually minimized by user - -On Linux always returns `true`. - -#### `win.setMaximizable(maximizable)` _macOS_ _Windows_ - -* `maximizable` Boolean - -Sets whether the window can be manually maximized by user. On Linux does -nothing. - -#### `win.isMaximizable()` _macOS_ _Windows_ - -Returns `Boolean` - Whether the window can be manually maximized by user. - -On Linux always returns `true`. - -#### `win.setFullScreenable(fullscreenable)` - -* `fullscreenable` Boolean - -Sets whether the maximize/zoom window button toggles fullscreen mode or -maximizes the window. - -#### `win.isFullScreenable()` - -Returns `Boolean` - Whether the maximize/zoom window button toggles fullscreen mode or -maximizes the window. - -#### `win.setClosable(closable)` _macOS_ _Windows_ - -* `closable` Boolean - -Sets whether the window can be manually closed by user. On Linux does nothing. - -#### `win.isClosable()` _macOS_ _Windows_ - -Returns `Boolean` - Whether the window can be manually closed by user. - -On Linux always returns `true`. - -#### `win.setAlwaysOnTop(flag[, level])` - -* `flag` Boolean -* `level` String (optional) _macOS_ - Values include `normal`, `floating`, - `torn-off-menu`, `modal-panel`, `main-menu`, `status`, `pop-up-menu`, - `screen-saver`, and ~~`dock`~~ (Deprecated). The default is `floating`. See the - [macOS docs][window-levels] for more details. - -Sets whether the window should show always on top of other windows. After -setting this, the window is still a normal window, not a toolbox window which -can not be focused on. - -#### `win.isAlwaysOnTop()` - -Returns `Boolean` - Whether the window is always on top of other windows. - -#### `win.center()` - -Moves window to the center of the screen. - -#### `win.setPosition(x, y[, animate])` - -* `x` Integer -* `y` Integer -* `animate` Boolean (optional) _macOS_ - -Moves window to `x` and `y`. - -#### `win.getPosition()` - -Returns `Integer[]` - Contains the window's current position. - -#### `win.setTitle(title)` - -* `title` String - -Changes the title of native window to `title`. - -#### `win.getTitle()` - -Returns `String` - The title of the native window. - -**Note:** The title of web page can be different from the title of the native -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 -const {BrowserWindow} = require('electron') -let win = new BrowserWindow() - -let toolbarRect = document.getElementById('toolbar').getBoundingClientRect() -win.setSheetOffset(toolbarRect.height) -``` - -#### `win.flashFrame(flag)` - -* `flag` Boolean - -Starts or stops flashing the window to attract user's attention. - -#### `win.setSkipTaskbar(skip)` - -* `skip` Boolean - -Makes the window not show in the taskbar. - -#### `win.setKiosk(flag)` - -* `flag` Boolean - -Enters or leaves the kiosk mode. - -#### `win.isKiosk()` - -Returns `Boolean` - Whether the window is in kiosk mode. - -#### `win.getNativeWindowHandle()` - -Returns `Buffer` - The platform-specific handle of the window. - -The native type of the handle is `HWND` on Windows, `NSView*` on macOS, and -`Window` (`unsigned long`) on Linux. - -#### `win.hookWindowMessage(message, callback)` _Windows_ - -* `message` Integer -* `callback` Function - -Hooks a windows message. The `callback` is called when -the message is received in the WndProc. - -#### `win.isWindowMessageHooked(message)` _Windows_ - -* `message` Integer - -Returns `Boolean` - `true` or `false` depending on whether the message is hooked. - -#### `win.unhookWindowMessage(message)` _Windows_ - -* `message` Integer - -Unhook the window message. - -#### `win.unhookAllWindowMessages()` _Windows_ - -Unhooks all of the window messages. - -#### `win.setRepresentedFilename(filename)` _macOS_ - -* `filename` String - -Sets the pathname of the file the window represents, and the icon of the file -will show in window's title bar. - -#### `win.getRepresentedFilename()` _macOS_ - -Returns `String` - The pathname of the file the window represents. - -#### `win.setDocumentEdited(edited)` _macOS_ - -* `edited` Boolean - -Specifies whether the window’s document has been edited, and the icon in title -bar will become gray when set to `true`. - -#### `win.isDocumentEdited()` _macOS_ - -Returns `Boolean` - Whether the window's document has been edited. - -#### `win.focusOnWebView()` - -#### `win.blurWebView()` - -#### `win.capturePage([rect, ]callback)` - -* `rect` [Rectangle](structures/rectangle.md) (optional) - The bounds to capture -* `callback` Function - * `image` [NativeImage](native-image.md) - -Same as `webContents.capturePage([rect, ]callback)`. - -#### `win.loadURL(url[, options])` - -* `url` String -* `options` Object (optional) - * `httpReferrer` String (optional) - A HTTP Referrer url. - * `userAgent` String (optional) - A user agent originating the request. - * `extraHeaders` String (optional) - Extra headers separated by "\n" - * `postData` ([UploadRawData](structures/upload-raw-data.md) | [UploadFile](structures/upload-file.md) | [UploadFileSystem](structures/upload-file-system.md) | [UploadBlob](structures/upload-blob.md))[] - (optional) - -Same as `webContents.loadURL(url[, options])`. - -The `url` can be a remote address (e.g. `http://`) or a path to a local -HTML file using the `file://` protocol. - -To ensure that file URLs are properly formatted, it is recommended to use -Node's [`url.format`](https://nodejs.org/api/url.html#url_url_format_urlobject) -method: - -```javascript -let url = require('url').format({ - protocol: 'file', - slashes: true, - pathname: require('path').join(__dirname, 'index.html') -}) - -win.loadURL(url) -``` - -You can load a URL using a `POST` request with URL-encoded data by doing -the following: - -```javascript -win.loadURL('http://localhost:8000/post', { - postData: [{ - type: 'rawData', - bytes: Buffer.from('hello=world') - }], - extraHeaders: 'Content-Type: application/x-www-form-urlencoded' -}) -``` - -#### `win.reload()` - -Same as `webContents.reload`. - -#### `win.setMenu(menu)` _Linux_ _Windows_ - -* `menu` Menu - -Sets the `menu` as the window's menu bar, setting it to `null` will remove the -menu bar. - -#### `win.setProgressBar(progress[, options])` - -* `progress` Double -* `options` Object (optional) - * `mode` String _Windows_ - Mode for the progress bar. Can be `none`, `normal`, `indeterminate`, `error`, or `paused`. - -Sets progress value in progress bar. Valid range is [0, 1.0]. - -Remove progress bar when progress < 0; -Change to indeterminate mode when progress > 1. - -On Linux platform, only supports Unity desktop environment, you need to specify -the `*.desktop` file name to `desktopName` field in `package.json`. By default, -it will assume `app.getName().desktop`. - -On Windows, a mode can be passed. Accepted values are `none`, `normal`, -`indeterminate`, `error`, and `paused`. If you call `setProgressBar` without a -mode set (but with a value within the valid range), `normal` will be assumed. - -#### `win.setOverlayIcon(overlay, description)` _Windows_ - -* `overlay` [NativeImage](native-image.md) - the icon to display on the bottom -right corner of the taskbar icon. If this parameter is `null`, the overlay is -cleared -* `description` String - a description that will be provided to Accessibility -screen readers - -Sets a 16 x 16 pixel overlay onto the current taskbar icon, usually used to -convey some sort of application status or to passively notify the user. - -#### `win.setHasShadow(hasShadow)` _macOS_ - -* `hasShadow` Boolean - -Sets whether the window should have a shadow. On Windows and Linux does -nothing. - -#### `win.hasShadow()` _macOS_ - -Returns `Boolean` - Whether the window has a shadow. - -On Windows and Linux always returns -`true`. - -#### `win.setThumbarButtons(buttons)` _Windows_ - -* `buttons` [ThumbarButton[]](structures/thumbar-button.md) - -Returns `Boolean` - Whether the buttons were added successfully - -Add a thumbnail toolbar with a specified set of buttons to the thumbnail image -of a window in a taskbar button layout. Returns a `Boolean` object indicates -whether the thumbnail has been added successfully. - -The number of buttons in thumbnail toolbar should be no greater than 7 due to -the limited room. Once you setup the thumbnail toolbar, the toolbar cannot be -removed due to the platform's limitation. But you can call the API with an empty -array to clean the buttons. - -The `buttons` is an array of `Button` objects: - -* `Button` Object - * `icon` [NativeImage](native-image.md) - The icon showing in thumbnail - toolbar. - * `click` Function - * `tooltip` String (optional) - The text of the button's tooltip. - * `flags` String[] (optional) - Control specific states and behaviors of the - button. By default, it is `['enabled']`. - -The `flags` is an array that can include following `String`s: - -* `enabled` - The button is active and available to the user. -* `disabled` - The button is disabled. It is present, but has a visual state - indicating it will not respond to user action. -* `dismissonclick` - When the button is clicked, the thumbnail window closes - immediately. -* `nobackground` - Do not draw a button border, use only the image. -* `hidden` - The button is not shown to the user. -* `noninteractive` - The button is enabled but not interactive; no pressed - button state is drawn. This value is intended for instances where the button - is used in a notification. - -#### `win.setThumbnailClip(region)` _Windows_ - -* `region` [Rectangle](structures/rectangle.md) - Region of the window - -Sets the region of the window to show as the thumbnail image displayed when -hovering over the window in the taskbar. You can reset the thumbnail to be -the entire window by specifying an empty region: -`{x: 0, y: 0, width: 0, height: 0}`. - -#### `win.setThumbnailToolTip(toolTip)` _Windows_ - -* `toolTip` String - -Sets the toolTip that is displayed when hovering over the window thumbnail -in the taskbar. - -#### `win.setAppDetails(options)` _Windows_ - -* `options` Object - * `appId` String (optional) - Window's [App User Model ID](https://msdn.microsoft.com/en-us/library/windows/desktop/dd391569(v=vs.85).aspx). - It has to be set, otherwise the other options will have no effect. - * `appIconPath` String (optional) - Window's [Relaunch Icon](https://msdn.microsoft.com/en-us/library/windows/desktop/dd391573(v=vs.85).aspx). - * `appIconIndex` Integer (optional) - Index of the icon in `appIconPath`. - Ignored when `appIconPath` is not set. Default is `0`. - * `relaunchCommand` String (optional) - Window's [Relaunch Command](https://msdn.microsoft.com/en-us/library/windows/desktop/dd391571(v=vs.85).aspx). - * `relaunchDisplayName` String (optional) - Window's [Relaunch Display Name](https://msdn.microsoft.com/en-us/library/windows/desktop/dd391572(v=vs.85).aspx). - -Sets the properties for the window's taskbar button. - -**Note:** `relaunchCommand` and `relaunchDisplayName` must always be set -together. If one of those properties is not set, then neither will be used. - -#### `win.showDefinitionForSelection()` _macOS_ - -Same as `webContents.showDefinitionForSelection()`. - -#### `win.setIcon(icon)` _Windows_ _Linux_ - -* `icon` [NativeImage](native-image.md) - -Changes window icon. - -#### `win.setAutoHideMenuBar(hide)` - -* `hide` Boolean - -Sets whether the window menu bar should hide itself automatically. Once set the -menu bar will only show when users press the single `Alt` key. - -If the menu bar is already visible, calling `setAutoHideMenuBar(true)` won't -hide it immediately. - -#### `win.isMenuBarAutoHide()` - -Returns `Boolean` - Whether menu bar automatically hides itself. - -#### `win.setMenuBarVisibility(visible)` _Windows_ _Linux_ - -* `visible` Boolean - -Sets whether the menu bar should be visible. If the menu bar is auto-hide, users -can still bring up the menu bar by pressing the single `Alt` key. - -#### `win.isMenuBarVisible()` - -Returns `Boolean` - Whether the menu bar is visible. - -#### `win.setVisibleOnAllWorkspaces(visible)` - -* `visible` Boolean - -Sets whether the window should be visible on all workspaces. - -**Note:** This API does nothing on Windows. - -#### `win.isVisibleOnAllWorkspaces()` - -Returns `Boolean` - Whether the window is visible on all workspaces. - -**Note:** This API always returns false on Windows. - -#### `win.setIgnoreMouseEvents(ignore)` - -* `ignore` Boolean - -Makes the window ignore all mouse events. - -All mouse events happened in this window will be passed to the window below -this window, but if this window has focus, it will still receive keyboard -events. - -#### `win.setContentProtection(enable)` _macOS_ _Windows_ - -* `enable` Boolean - -Prevents the window contents from being captured by other apps. - -On macOS it sets the NSWindow's sharingType to NSWindowSharingNone. -On Windows it calls SetWindowDisplayAffinity with `WDA_MONITOR`. - -#### `win.setFocusable(focusable)` _Windows_ - -* `focusable` Boolean - -Changes whether the window can be focused. - -#### `win.setParentWindow(parent)` _Linux_ _macOS_ - -* `parent` BrowserWindow - -Sets `parent` as current window's parent window, passing `null` will turn -current window into a top-level window. - -#### `win.getParentWindow()` - -Returns `BrowserWindow` - The parent window. - -#### `win.getChildWindows()` - -Returns `BrowserWindow[]` - All child windows. - -#### `win.setAutoHideCursor(autoHide)` _macOS_ - -* `autoHide` Boolean - -Controls whether to hide cursor when typing. - -#### `win.setVibrancy(type)` _macOS_ - -* `type` String - Can be `appearance-based`, `light`, `dark`, `titlebar`, - `selection`, `menu`, `popover`, `sidebar`, `medium-light` or `ultra-dark`. See - the [macOS documentation][vibrancy-docs] for more details. - -Adds a vibrancy effect to the browser window. Passing `null` or an empty string -will remove the vibrancy effect on the window. - -[blink-feature-string]: https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.in -[quick-look]: https://en.wikipedia.org/wiki/Quick_Look -[vibrancy-docs]: https://developer.apple.com/reference/appkit/nsvisualeffectview?language=objc -[window-levels]: https://developer.apple.com/reference/appkit/nswindow/1664726-window_levels -[chrome-content-scripts]: https://developer.chrome.com/extensions/content_scripts#execution-environment diff --git a/docs-translations/th-TH/api/chrome-command-line-switches.md b/docs-translations/th-TH/api/chrome-command-line-switches.md deleted file mode 100644 index 7920fe5e20..0000000000 --- a/docs-translations/th-TH/api/chrome-command-line-switches.md +++ /dev/null @@ -1,189 +0,0 @@ -# Supported Chrome Command Line Switches - -> Command line switches supported by Electron. - -You can use [app.commandLine.appendSwitch][append-switch] to append them in -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') - -app.on('ready', () => { - // Your code here -}) -``` - -## --ignore-connections-limit=`domains` - -Ignore the connections limit for `domains` list separated by `,`. - -## --disable-http-cache - -Disables the disk cache for HTTP requests. - -## --disable-http2 - -Disable HTTP/2 and SPDY/3.1 protocols. - -## --debug=`port` and --debug-brk=`port` - -Debug-related flags, see the [Debugging the Main Process][debugging-main-process] guide for details. - -## --remote-debugging-port=`port` - -Enables remote debugging over HTTP on the specified `port`. - -## --js-flags=`flags` - -Specifies the flags passed to the Node JS engine. It has to be passed when starting -Electron if you want to enable the `flags` in the main process. - -```bash -$ electron --js-flags="--harmony_proxies --harmony_collections" your-app -``` - -See the [Node documentation][node-cli] or run `node --help` in your terminal for a list of available flags. Additionally, run `node --v8-options` to see a list of flags that specifically refer to Node's V8 JavaScript engine. - -## --proxy-server=`address:port` - -Use a specified proxy server, which overrides the system setting. This switch -only affects requests with HTTP protocol, including HTTPS and WebSocket -requests. It is also noteworthy that not all proxy servers support HTTPS and -WebSocket requests. - -## --proxy-bypass-list=`hosts` - -Instructs Electron to bypass the proxy server for the given semi-colon-separated -list of hosts. This flag has an effect only if used in tandem with -`--proxy-server`. - -For example: - -```javascript -const {app} = require('electron') -app.commandLine.appendSwitch('proxy-bypass-list', ';*.google.com;*foo.com;1.2.3.4:5678') -``` - -Will use the proxy server for all hosts except for local addresses (`localhost`, -`127.0.0.1` etc.), `google.com` subdomains, hosts that contain the suffix -`foo.com` and anything at `1.2.3.4:5678`. - -## --proxy-pac-url=`url` - -Uses the PAC script at the specified `url`. - -## --no-proxy-server - -Don't use a proxy server and always make direct connections. Overrides any other -proxy server flags that are passed. - -## --host-rules=`rules` - -A comma-separated list of `rules` that control how hostnames are mapped. - -For example: - -* `MAP * 127.0.0.1` Forces all hostnames to be mapped to 127.0.0.1 -* `MAP *.google.com proxy` Forces all google.com subdomains to be resolved to - "proxy". -* `MAP test.com [::1]:77` Forces "test.com" to resolve to IPv6 loopback. Will - also force the port of the resulting socket address to be 77. -* `MAP * baz, EXCLUDE www.google.com` Remaps everything to "baz", except for - "www.google.com". - -These mappings apply to the endpoint host in a net request (the TCP connect -and host resolver in a direct connection, and the `CONNECT` in an HTTP proxy -connection, and the endpoint host in a `SOCKS` proxy connection). - -## --host-resolver-rules=`rules` - -Like `--host-rules` but these `rules` only apply to the host resolver. - -## --auth-server-whitelist=`url` - -A comma-separated list of servers for which integrated authentication is enabled. - -For example: - -``` ---auth-server-whitelist='*example.com, *foobar.com, *baz' -``` - -then any `url` ending with `example.com`, `foobar.com`, `baz` will be considered -for integrated authentication. Without `*` prefix the url has to match exactly. - -## --auth-negotiate-delegate-whitelist=`url` - -A comma-separated list of servers for which delegation of user credentials is required. -Without `*` prefix the url has to match exactly. - -## --ignore-certificate-errors - -Ignores certificate related errors. - -## --ppapi-flash-path=`path` - -Sets the `path` of the pepper flash plugin. - -## --ppapi-flash-version=`version` - -Sets the `version` of the pepper flash plugin. - -## --log-net-log=`path` - -Enables net log events to be saved and writes them to `path`. - -## --ssl-version-fallback-min=`version` - -Sets the minimum SSL/TLS version (`tls1`, `tls1.1` or `tls1.2`) that TLS -fallback will accept. - -## --cipher-suite-blacklist=`cipher_suites` - -Specifies comma-separated list of SSL cipher suites to disable. - -## --disable-renderer-backgrounding - -Prevents Chromium from lowering the priority of invisible pages' renderer -processes. - -This flag is global to all renderer processes, if you only want to disable -throttling in one window, you can take the hack of -[playing silent audio][play-silent-audio]. - -## --enable-logging - -Prints Chromium's logging into console. - -This switch can not be used in `app.commandLine.appendSwitch` since it is parsed -earlier than user's app is loaded, but you can set the `ELECTRON_ENABLE_LOGGING` -environment variable to achieve the same effect. - -## --v=`log_level` - -Gives the default maximal active V-logging level; 0 is the default. Normally -positive values are used for V-logging levels. - -This switch only works when `--enable-logging` is also passed. - -## --vmodule=`pattern` - -Gives the per-module maximal V-logging levels to override the value given by -`--v`. E.g. `my_module=2,foo*=3` would change the logging level for all code in -source files `my_module.*` and `foo*.*`. - -Any pattern containing a forward or backward slash will be tested against the -whole pathname and not just the module. E.g. `*/foo/bar/*=2` would change the -logging level for all code in the source files under a `foo/bar` directory. - -This switch only works when `--enable-logging` is also passed. - -[app]: app.md -[append-switch]: app.md#appcommandlineappendswitchswitch-value -[ready]: app.md#event-ready -[play-silent-audio]: https://github.com/atom/atom/pull/9485/files -[debugging-main-process]: ../tutorial/debugging-main-process.md -[node-cli]: https://nodejs.org/api/cli.html diff --git a/docs-translations/th-TH/api/client-request.md b/docs-translations/th-TH/api/client-request.md deleted file mode 100644 index 0b722f2f09..0000000000 --- a/docs-translations/th-TH/api/client-request.md +++ /dev/null @@ -1,192 +0,0 @@ -## Class: ClientRequest - -> Make HTTP/HTTPS requests. - -Process: [Main](../glossary.md#main-process) - -`ClientRequest` implements the [Writable Stream](https://nodejs.org/api/stream.html#stream_writable_streams) -interface and is therefore an [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter). - -### `new ClientRequest(options)` - -* `options` (Object | String) - If `options` is a String, it is interpreted as -the request URL. If it is an object, it is expected to fully specify an HTTP request via the -following properties: - * `method` String (optional) - The HTTP request method. Defaults to the GET -method. - * `url` String (optional) - The request URL. Must be provided in the absolute -form with the protocol scheme specified as http or https. - * `session` Object (optional) - The [`Session`](session.md) instance with -which the request is associated. - * `partition` String (optional) - The name of the [`partition`](session.md) - with which the request is associated. Defaults to the empty string. The -`session` option prevails on `partition`. Thus if a `session` is explicitly -specified, `partition` is ignored. - * `protocol` String (optional) - The protocol scheme in the form 'scheme:'. -Currently supported values are 'http:' or 'https:'. Defaults to 'http:'. - * `host` String (optional) - The server host provided as a concatenation of -the hostname and the port number 'hostname:port' - * `hostname` String (optional) - The server host name. - * `port` Integer (optional) - The server's listening port number. - * `path` String (optional) - The path part of the request URL. - -`options` properties such as `protocol`, `host`, `hostname`, `port` and `path` -strictly follow the Node.js model as described in the -[URL](https://nodejs.org/api/url.html) module. - -For instance, we could have created the same request to 'github.com' as follows: - -```JavaScript -const request = net.request({ - method: 'GET', - protocol: 'https:', - hostname: 'github.com', - port: 443, - path: '/' -}) -``` - -### Instance Events - -#### Event: 'response' - -Returns: - -* `response` IncomingMessage - An object representing the HTTP response message. - -#### Event: 'login' - -Returns: - -* `authInfo` Object - * `isProxy` Boolean - * `scheme` String - * `host` String - * `port` Integer - * `realm` String -* `callback` Function - -Emitted when an authenticating proxy is asking for user credentials. - -The `callback` function is expected to be called back with user credentials: - -* `username` String -* `password` String - -```JavaScript -request.on('login', (authInfo, callback) => { - callback('username', 'password') -}) -``` -Providing empty credentials will cancel the request and report an authentication -error on the response object: - -```JavaScript -request.on('response', (response) => { - console.log(`STATUS: ${response.statusCode}`); - response.on('error', (error) => { - console.log(`ERROR: ${JSON.stringify(error)}`) - }) -}) -request.on('login', (authInfo, callback) => { - callback() -}) -``` - -#### Event: 'finish' - -Emitted just after the last chunk of the `request`'s data has been written into -the `request` object. - -#### Event: 'abort' - -Emitted when the `request` is aborted. The `abort` event will not be fired if -the `request` is already closed. - -#### Event: 'error' - -Returns: - -* `error` Error - an error object providing some information about the failure. - -Emitted when the `net` module fails to issue a network request. Typically when -the `request` object emits an `error` event, a `close` event will subsequently -follow and no response object will be provided. - -#### Event: 'close' - -Emitted as the last event in the HTTP request-response transaction. The `close` -event indicates that no more events will be emitted on either the `request` or -`response` objects. - -### Instance Properties - -#### `request.chunkedEncoding` - -A Boolean specifying whether the request will use HTTP chunked transfer encoding -or not. Defaults to false. The property is readable and writable, however it can -be set only before the first write operation as the HTTP headers are not yet put -on the wire. Trying to set the `chunkedEncoding` property after the first write -will throw an error. - -Using chunked encoding is strongly recommended if you need to send a large -request body as data will be streamed in small chunks instead of being -internally buffered inside Electron process memory. - -### Instance Methods - -#### `request.setHeader(name, value)` - -* `name` String - An extra HTTP header name. -* `value` String - An extra HTTP header value. - -Adds an extra HTTP header. The header name will issued as it is without -lowercasing. It can be called only before first write. Calling this method after -the first write will throw an error. - -#### `request.getHeader(name)` - -* `name` String - Specify an extra header name. - -Returns String - The value of a previously set extra header name. - -#### `request.removeHeader(name)` - -* `name` String - Specify an extra header name. - -Removes a previously set extra header name. This method can be called only -before first write. Trying to call it after the first write will throw an error. - -#### `request.write(chunk[, encoding][, callback])` - -* `chunk` (String | Buffer) - A chunk of the request body's data. If it is a -string, it is converted into a Buffer using the specified encoding. -* `encoding` String (optional) - Used to convert string chunks into Buffer -objects. Defaults to 'utf-8'. -* `callback` Function (optional) - Called after the write operation ends. - -`callback` is essentially a dummy function introduced in the purpose of keeping -similarity with the Node.js API. It is called asynchronously in the next tick -after `chunk` content have been delivered to the Chromium networking layer. -Contrary to the Node.js implementation, it is not guaranteed that `chunk` -content have been flushed on the wire before `callback` is called. - -Adds a chunk of data to the request body. The first write operation may cause -the request headers to be issued on the wire. After the first write operation, -it is not allowed to add or remove a custom header. - -#### `request.end([chunk][, encoding][, callback])` - -* `chunk` (String | Buffer) (optional) -* `encoding` String (optional) -* `callback` Function (optional) - -Sends the last chunk of the request data. Subsequent write or end operations -will not be allowed. The `finish` event is emitted just after the end operation. - -#### `request.abort()` - -Cancels an ongoing HTTP transaction. If the request has already emitted the -`close` event, the abort operation will have no effect. Otherwise an ongoing -event will emit `abort` and `close` events. Additionally, if there is an ongoing -response object,it will emit the `aborted` event. diff --git a/docs-translations/th-TH/api/clipboard.md b/docs-translations/th-TH/api/clipboard.md deleted file mode 100644 index 932a1da7b2..0000000000 --- a/docs-translations/th-TH/api/clipboard.md +++ /dev/null @@ -1,169 +0,0 @@ -# clipboard - -> Perform copy and paste operations on the system clipboard. - -Process: [Main](../glossary.md#main-process), [Renderer](../glossary.md#renderer-process) - -The following example shows how to write a string to the clipboard: - -```javascript -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 -const {clipboard} = require('electron') -clipboard.writeText('Example String', 'selection') -console.log(clipboard.readText('selection')) -``` - -## Methods - -The `clipboard` module has the following methods: - -**Note:** Experimental APIs are marked as such and could be removed in future. - -### `clipboard.readText([type])` - -* `type` String (optional) - -Returns `String` - The content in the clipboard as plain text. - -### `clipboard.writeText(text[, type])` - -* `text` String -* `type` String (optional) - -Writes the `text` into the clipboard as plain text. - -### `clipboard.readHTML([type])` - -* `type` String (optional) - -Returns `String` - The content in the clipboard as markup. - -### `clipboard.writeHTML(markup[, type])` - -* `markup` String -* `type` String (optional) - -Writes `markup` to the clipboard. - -### `clipboard.readImage([type])` - -* `type` String (optional) - -Returns [`NativeImage`](native-image.md) - The image content in the clipboard. - -### `clipboard.writeImage(image[, type])` - -* `image` [NativeImage](native-image.md) -* `type` String (optional) - -Writes `image` to the clipboard. - -### `clipboard.readRTF([type])` - -* `type` String (optional) - -Returns `String` - The content in the clipboard as RTF. - -### `clipboard.writeRTF(text[, type])` - -* `text` String -* `type` String (optional) - -Writes the `text` into the clipboard in RTF. - -### `clipboard.readBookmark()` _macOS_ _Windows_ - -Returns `Object`: - -* `title` String -* `url` String - -Returns an Object containing `title` and `url` keys representing the bookmark in -the clipboard. The `title` and `url` values will be empty strings when the -bookmark is unavailable. - -### `clipboard.writeBookmark(title, url[, type])` _macOS_ _Windows_ - -* `title` String -* `url` String -* `type` String (optional) - -Writes the `title` and `url` into the clipboard as a bookmark. - -**Note:** Most apps on Windows don't support pasting bookmarks into them so -you can use `clipboard.write` to write both a bookmark and fallback text to the -clipboard. - -```js -clipboard.write({ - text: 'http://electron.atom.io', - bookmark: 'Electron Homepage' -}) -``` - -### `clipboard.readFindText()` _macOS_ - -Returns `String` - The text on the find pasteboard. This method uses synchronous -IPC when called from the renderer process. The cached value is reread from the -find pasteboard whenever the application is activated. - -### `clipboard.writeFindText(text)` _macOS_ - -* `text` String - -Writes the `text` into the find pasteboard as plain text. This method uses -synchronous IPC when called from the renderer process. - -### `clipboard.clear([type])` - -* `type` String (optional) - -Clears the clipboard content. - -### `clipboard.availableFormats([type])` - -* `type` String (optional) - -Returns `String[]` - An array of supported formats for the clipboard `type`. - -### `clipboard.has(data[, type])` _Experimental_ - -* `data` String -* `type` String (optional) - -Returns `Boolean` - Whether the clipboard supports the format of specified `data`. - -```javascript -const {clipboard} = require('electron') -console.log(clipboard.has('

selection

')) -``` - -### `clipboard.read(data[, type])` _Experimental_ - -* `data` String -* `type` String (optional) - -Returns `String` - Reads `data` from the clipboard. - -### `clipboard.write(data[, type])` - -* `data` Object - * `text` String (optional) - * `html` String (optional) - * `image` [NativeImage](native-image.md) (optional) - * `rtf` String (optional) - * `bookmark` String (optional) - The title of the url at `text`. -* `type` String (optional) - -```javascript -const {clipboard} = require('electron') -clipboard.write({text: 'test', html: 'test'}) -``` -Writes `data` to the clipboard. diff --git a/docs-translations/th-TH/api/content-tracing.md b/docs-translations/th-TH/api/content-tracing.md deleted file mode 100644 index a1c76d8807..0000000000 --- a/docs-translations/th-TH/api/content-tracing.md +++ /dev/null @@ -1,173 +0,0 @@ -# contentTracing - -> Collect tracing data from Chromium's content module for finding performance -bottlenecks and slow operations. - -Process: [Main](../glossary.md#main-process) - -This module does not include a web interface so you need to open -`chrome://tracing/` in a Chrome browser and load the generated file to view the -result. - -```javascript -const {contentTracing} = require('electron') - -const options = { - categoryFilter: '*', - traceOptions: 'record-until-full,enable-sampling' -} - -contentTracing.startRecording(options, () => { - console.log('Tracing started') - - setTimeout(() => { - contentTracing.stopRecording('', (path) => { - console.log('Tracing data recorded to ' + path) - }) - }, 5000) -}) -``` - -## Methods - -The `contentTracing` module has the following methods: - -### `contentTracing.getCategories(callback)` - -* `callback` Function - * `categories` String[] - -Get a set of category groups. The category groups can change as new code paths -are reached. - -Once all child processes have acknowledged the `getCategories` request the -`callback` is invoked with an array of category groups. - -### `contentTracing.startRecording(options, callback)` - -* `options` Object - * `categoryFilter` String - * `traceOptions` String -* `callback` Function - -Start recording on all processes. - -Recording begins immediately locally and asynchronously on child processes -as soon as they receive the EnableRecording request. The `callback` will be -called once all child processes have acknowledged the `startRecording` request. - -`categoryFilter` is a filter to control what category groups should be -traced. A filter can have an optional `-` prefix to exclude category groups -that contain a matching category. Having both included and excluded -category patterns in the same list is not supported. - -Examples: - -* `test_MyTest*`, -* `test_MyTest*,test_OtherStuff`, -* `"-excluded_category1,-excluded_category2` - -`traceOptions` controls what kind of tracing is enabled, it is a comma-delimited -list. Possible options are: - -* `record-until-full` -* `record-continuously` -* `trace-to-console` -* `enable-sampling` -* `enable-systrace` - -The first 3 options are trace recoding modes and hence mutually exclusive. -If more than one trace recording modes appear in the `traceOptions` string, -the last one takes precedence. If none of the trace recording modes are -specified, recording mode is `record-until-full`. - -The trace option will first be reset to the default option (`record_mode` set to -`record-until-full`, `enable_sampling` and `enable_systrace` set to `false`) -before options parsed from `traceOptions` are applied on it. - -### `contentTracing.stopRecording(resultFilePath, callback)` - -* `resultFilePath` String -* `callback` Function - * `resultFilePath` String - -Stop recording on all processes. - -Child processes typically cache trace data and only rarely flush and send -trace data back to the main process. This helps to minimize the runtime overhead -of tracing since sending trace data over IPC can be an expensive operation. So, -to end tracing, we must asynchronously ask all child processes to flush any -pending trace data. - -Once all child processes have acknowledged the `stopRecording` request, -`callback` will be called with a file that contains the traced data. - -Trace data will be written into `resultFilePath` if it is not empty or into a -temporary file. The actual file path will be passed to `callback` if it's not -`null`. - -### `contentTracing.startMonitoring(options, callback)` - -* `options` Object - * `categoryFilter` String - * `traceOptions` String -* `callback` Function - -Start monitoring on all processes. - -Monitoring begins immediately locally and asynchronously on child processes as -soon as they receive the `startMonitoring` request. - -Once all child processes have acknowledged the `startMonitoring` request the -`callback` will be called. - -### `contentTracing.stopMonitoring(callback)` - -* `callback` Function - -Stop monitoring on all processes. - -Once all child processes have acknowledged the `stopMonitoring` request the -`callback` is called. - -### `contentTracing.captureMonitoringSnapshot(resultFilePath, callback)` - -* `resultFilePath` String -* `callback` Function - * `resultFilePath` String - -Get the current monitoring traced data. - -Child processes typically cache trace data and only rarely flush and send -trace data back to the main process. This is because it may be an expensive -operation to send the trace data over IPC and we would like to avoid unneeded -runtime overhead from tracing. So, to end tracing, we must asynchronously ask -all child processes to flush any pending trace data. - -Once all child processes have acknowledged the `captureMonitoringSnapshot` -request the `callback` will be called with a file that contains the traced data. - - -### `contentTracing.getTraceBufferUsage(callback)` - -* `callback` Function - * `value` Number - * `percentage` Number - -Get the maximum usage across processes of trace buffer as a percentage of the -full state. When the TraceBufferUsage value is determined the `callback` is -called. - -### `contentTracing.setWatchEvent(categoryName, eventName, callback)` - -* `categoryName` String -* `eventName` String -* `callback` Function - -`callback` will be called every time the given event occurs on any -process. - -### `contentTracing.cancelWatchEvent()` - -Cancel the watch event. This may lead to a race condition with the watch event -callback if tracing is enabled. diff --git a/docs-translations/th-TH/api/cookies.md b/docs-translations/th-TH/api/cookies.md deleted file mode 100644 index 8e64202967..0000000000 --- a/docs-translations/th-TH/api/cookies.md +++ /dev/null @@ -1,106 +0,0 @@ -## Class: Cookies - -> Query and modify a session's cookies. - -Process: [Main](../glossary.md#main-process) - -Instances of the `Cookies` class are accessed by using `cookies` property of -a `Session`. - -For example: - -```javascript -const {session} = require('electron') - -// Query all cookies. -session.defaultSession.cookies.get({}, (error, 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(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) -}) -``` - -### Instance Events - -The following events are available on instances of `Cookies`: - -#### Event: 'changed' - -* `event` Event -* `cookie` [Cookie](structures/cookie.md) - The cookie that was changed -* `cause` String - The cause of the change with one of the following values: - * `explicit` - The cookie was changed directly by a consumer's action. - * `overwrite` - The cookie was automatically removed due to an insert - operation that overwrote it. - * `expired` - The cookie was automatically removed as it expired. - * `evicted` - The cookie was automatically evicted during garbage collection. - * `expired-overwrite` - The cookie was overwritten with an already-expired - expiration date. -* `removed` Boolean - `true` if the cookie was removed, `false` otherwise. - -Emitted when a cookie is changed because it was added, edited, removed, or -expired. - -### Instance Methods - -The following methods are available on instances of `Cookies`: - -#### `cookies.get(filter, callback)` - -* `filter` Object - * `url` String (optional) - Retrieves cookies which are associated with - `url`. Empty implies retrieving cookies of all urls. - * `name` String (optional) - Filters cookies by name. - * `domain` String (optional) - Retrieves cookies whose domains match or are - subdomains of `domains` - * `path` String (optional) - Retrieves cookies whose path matches `path`. - * `secure` Boolean (optional) - Filters cookies by their Secure property. - * `session` Boolean (optional) - Filters out session or persistent cookies. -* `callback` Function - * `error` Error - * `cookies` Cookies[] - -Sends a request to get all cookies matching `details`, `callback` will be called -with `callback(error, cookies)` on complete. - -`cookies` is an Array of [`cookie`](structures/cookie.md) objects. - -#### `cookies.set(details, callback)` - -* `details` Object - * `url` String - The url to associate the cookie with. - * `name` String (optional) - The name of the cookie. Empty by default if omitted. - * `value` String (optional) - The value of the cookie. Empty by default if omitted. - * `domain` String (optional) - The domain of the cookie. Empty by default if omitted. - * `path` String (optional) - The path of the cookie. Empty by default if omitted. - * `secure` Boolean (optional) - Whether the cookie should be marked as Secure. Defaults to - false. - * `httpOnly` Boolean (optional) - Whether the cookie should be marked as HTTP only. - Defaults to false. - * `expirationDate` Double (optional) - The expiration date of the cookie as the number of - seconds since the UNIX epoch. If omitted then the cookie becomes a session - cookie and will not be retained between sessions. -* `callback` Function - * `error` Error - -Sets a cookie with `details`, `callback` will be called with `callback(error)` -on complete. - -#### `cookies.remove(url, name, callback)` - -* `url` String - The URL associated with the cookie. -* `name` String - The name of cookie to remove. -* `callback` Function - -Removes the cookies matching `url` and `name`, `callback` will called with -`callback()` on complete. diff --git a/docs-translations/th-TH/api/crash-reporter.md b/docs-translations/th-TH/api/crash-reporter.md deleted file mode 100644 index 737918571c..0000000000 --- a/docs-translations/th-TH/api/crash-reporter.md +++ /dev/null @@ -1,112 +0,0 @@ -# crashReporter - -> Submit crash reports to a remote server. - -Process: [Main](../glossary.md#main-process), [Renderer](../glossary.md#renderer-process) - -The following is an example of automatically submitting a crash report to a -remote server: - -```javascript -const {crashReporter} = require('electron') - -crashReporter.start({ - productName: 'YourName', - companyName: 'YourCompany', - submitURL: 'https://your-domain.com/url-to-submit', - uploadToServer: true -}) -``` - -For setting up a server to accept and process crash reports, you can use -following projects: - -* [socorro](https://github.com/mozilla/socorro) -* [mini-breakpad-server](https://github.com/electron/mini-breakpad-server) - -Crash reports are saved locally in an application-specific temp directory folder. -For a `productName` of `YourName`, crash reports will be stored in a folder -named `YourName Crashes` inside the temp directory. You can customize this temp -directory location for your app by calling the `app.setPath('temp', '/my/custom/temp')` -API before starting the crash reporter. - -## Methods - -The `crashReporter` module has the following methods: - -### `crashReporter.start(options)` - -* `options` Object - * `companyName` String (optional) - * `submitURL` String - URL that crash reports will be sent to as POST. - * `productName` String (optional) - Defaults to `app.getName()`. - * `uploadToServer` Boolean (optional) _macOS_ - Whether crash reports should be sent to the server - Default is `true`. - * `ignoreSystemCrashHandler` Boolean (optional) - Default is `false`. - * `extra` Object (optional) - An object you can define that will be sent along with the - report. Only string properties are sent correctly, Nested objects are not - supported. - -You are required to call this method before using any other `crashReporter` APIs -and in each process (main/renderer) from which you want to collect crash reports. -You can pass different options to `crashReporter.start` when calling from different processes. - -**Note:** On Windows and Linux, Electron uses `breakpad` for crash collection and reporting. -Crashes can be collected from the main and renderer process, but not from the child processes -created via the `child_process` module. - -**Note:** On macOS, Electron uses a new `crashpad` client for crash collection and reporting. -Crashes can be collected from the main, renderer and any of the child processes created via the `child_process` module. -If you want to enable crash reporting, initializing `crashpad` from the main process using `crashReporter.start` is required -regardless of which process you want to collect crashes from. Once initialized this way, the crashpad handler collects -crashes from all processes. You still have to call `crashReporter.start` from the renderer process, otherwise crashes from -renderer processes will get reported without `companyName`, `productName` or any of the `extra` information. - -### `crashReporter.getLastCrashReport()` - -Returns [`CrashReport`](structures/crash-report.md): - -Returns the date and ID of the last crash report. If no crash reports have been -sent or the crash reporter has not been started, `null` is returned. - -### `crashReporter.getUploadedReports()` - -Returns [`CrashReport[]`](structures/crash-report.md): - -Returns all uploaded crash reports. Each report contains the date and uploaded -ID. - -### `crashReporter.getUploadToServer()` _macOS_ - -Returns `Boolean` - Whether reports should be submitted to the server. Set through -the `start` method or `setUploadToServer`. - -**Note:** This API can only be called from the main process. - -### `crashReporter.setUploadToServer(uploadToServer)` _macOS_ - -* `uploadToServer` Boolean _macOS_ - Whether reports should be submitted to the server - -This would normally be controlled by user preferences. This has no effect if -called before `start` is called. - -**Note:** This API can only be called from the main process. - -## Crash Report Payload - -The crash reporter will send the following data to the `submitURL` as -a `multipart/form-data` `POST`: - -* `ver` String - The version of Electron. -* `platform` String - e.g. 'win32'. -* `process_type` String - e.g. 'renderer'. -* `guid` String - e.g. '5e1286fc-da97-479e-918b-6bfb0c3d1c72' -* `_version` String - The version in `package.json`. -* `_productName` String - The product name in the `crashReporter` `options` - object. -* `prod` String - Name of the underlying product. In this case Electron. -* `_companyName` String - The company name in the `crashReporter` `options` - object. -* `upload_file_minidump` File - The crash report in the format of `minidump`. -* All level one properties of the `extra` object in the `crashReporter` - `options` object. diff --git a/docs-translations/th-TH/api/debugger.md b/docs-translations/th-TH/api/debugger.md deleted file mode 100644 index 1be33833ed..0000000000 --- a/docs-translations/th-TH/api/debugger.md +++ /dev/null @@ -1,83 +0,0 @@ -## Class: Debugger - -> An alternate transport for Chrome's remote debugging protocol. - -Process: [Main](../glossary.md#main-process) - -Chrome Developer Tools has a [special binding][rdp] available at JavaScript -runtime that allows interacting with pages and instrumenting them. - -```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.on('detach', (event, 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() - } - } -}) - -win.webContents.debugger.sendCommand('Network.enable') -``` - -### Instance Methods - -#### `debugger.attach([protocolVersion])` - -* `protocolVersion` String (optional) - Requested debugging protocol version. - -Attaches the debugger to the `webContents`. - -#### `debugger.isAttached()` - -Returns `Boolean` - Whether a debugger is attached to the `webContents`. - -#### `debugger.detach()` - -Detaches the debugger from the `webContents`. - -#### `debugger.sendCommand(method[, commandParams, callback])` - -* `method` String - Method name, should be one of the methods defined by the - remote debugging protocol. -* `commandParams` Object (optional) - JSON object with request parameters. -* `callback` Function (optional) - Response - * `error` Object - Error message indicating the failure of the command. - * `result` Any - Response defined by the 'returns' attribute of - the command description in the remote debugging protocol. - -Send given command to the debugging target. - -### Instance Events - -#### Event: 'detach' - -* `event` Event -* `reason` String - Reason for detaching debugger. - -Emitted when debugging session is terminated. This happens either when -`webContents` is closed or devtools is invoked for the attached `webContents`. - -#### Event: 'message' - -* `event` Event -* `method` String - Method name. -* `params` Object - Event parameters defined by the 'parameters' - attribute in the remote debugging protocol. - -Emitted whenever debugging target issues instrumentation event. - -[rdp]: https://developer.chrome.com/devtools/docs/debugger-protocol -[`webContents.findInPage`]: web-contents.md#contentsfindinpagetext-options diff --git a/docs-translations/th-TH/api/desktop-capturer.md b/docs-translations/th-TH/api/desktop-capturer.md deleted file mode 100644 index 85755dc03e..0000000000 --- a/docs-translations/th-TH/api/desktop-capturer.md +++ /dev/null @@ -1,76 +0,0 @@ -# desktopCapturer - -> Access information about media sources that can be used to capture audio and -> video from the desktop using the [`navigator.webkitGetUserMedia`] API. - -Process: [Renderer](../glossary.md#renderer-process) - -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') - -desktopCapturer.getSources({types: ['window', 'screen']}, (error, sources) => { - if (error) throw error - for (let i = 0; i < sources.length; ++i) { - if (sources[i].name === 'Electron') { - navigator.webkitGetUserMedia({ - audio: false, - video: { - mandatory: { - chromeMediaSource: 'desktop', - chromeMediaSourceId: sources[i].id, - minWidth: 1280, - maxWidth: 1280, - minHeight: 720, - maxHeight: 720 - } - } - }, handleStream, handleError) - return - } - } -}) - -function handleStream (stream) { - document.querySelector('video').src = URL.createObjectURL(stream) -} - -function handleError (e) { - console.log(e) -} -``` - -To capture video from a source provided by `desktopCapturer` the constraints -passed to [`navigator.webkitGetUserMedia`] must include -`chromeMediaSource: 'desktop'`, and `audio: false`. - -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 - -The `desktopCapturer` module has the following methods: - -### `desktopCapturer.getSources(options, callback)` - -* `options` Object - * `types` String[] - An array of Strings that lists the types of desktop sources - to be captured, available types are `screen` and `window`. - * `thumbnailSize` Object (optional) - The suggested size that the media source - thumbnail should be scaled to, defaults to `{width: 150, height: 150}`. -* `callback` Function - * `error` Error - * `sources` [DesktopCapturerSource[]](structures/desktop-capturer-source.md) - -Starts gathering information about all available desktop media sources, -and calls `callback(error, sources)` when finished. - -`sources` is an array of [`DesktopCapturerSource`](structures/desktop-capturer-source.md) -objects, each `DesktopCapturerSource` represents a screen or an individual window that can be -captured. - -[`navigator.webkitGetUserMedia`]: https://developer.mozilla.org/en/docs/Web/API/Navigator/getUserMedia diff --git a/docs-translations/th-TH/api/dialog.md b/docs-translations/th-TH/api/dialog.md deleted file mode 100644 index 18f1fc6b07..0000000000 --- a/docs-translations/th-TH/api/dialog.md +++ /dev/null @@ -1,161 +0,0 @@ -# dialog - -> Display native system dialogs for opening and saving files, alerting, etc. - -Process: [Main](../glossary.md#main-process) - -An example of showing a dialog to select multiple files and directories: - -```javascript -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 -console.log(dialog) -``` - -## Methods - -The `dialog` module has the following methods: - -### `dialog.showOpenDialog([browserWindow, ]options[, callback])` - -* `browserWindow` BrowserWindow (optional) -* `options` Object - * `title` String (optional) - * `defaultPath` String (optional) - * `buttonLabel` String (optional) - Custom label for the confirmation button, when - left empty the default label will be used. - * `filters` [FileFilter[]](structures/file-filter.md) (optional) - * `properties` String[] (optional) - Contains which features the dialog should use, can - contain `openFile`, `openDirectory`, `multiSelections`, `createDirectory` - and `showHiddenFiles`. - * `normalizeAccessKeys` Boolean (optional) - Normalize the keyboard access keys - across platforms. Default is `false`. Enabling this assumes `&` is used in - the button labels for the placement of the keyboard shortcut access key - and labels will be converted so they work correctly on each platform, `&` - characters are removed on macOS, converted to `_` on Linux, and left - untouched on Windows. For example, a button label of `Vie&w` will be - converted to `Vie_w` on Linux and `View` on macOS and can be selected - via `Alt-W` on Windows and Linux. -* `callback` Function (optional) - * `filePaths` String[] - An array of file paths chosen by the user - -Returns `String[]`, an array of file paths chosen by the user, -if the callback is provided it returns `undefined`. - -The `browserWindow` argument allows the dialog to attach itself to a parent window, making it modal. - -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']}, - {name: 'Movies', extensions: ['mkv', 'avi', 'mp4']}, - {name: 'Custom File Type', extensions: ['as']}, - {name: 'All Files', extensions: ['*']} - ] -} -``` - -The `extensions` array should contain extensions without wildcards or dots (e.g. -`'png'` is good but `'.png'` and `'*.png'` are bad). To show all files, use the -`'*'` wildcard (no other wildcard is supported). - -If a `callback` is passed, the API call will be asynchronous and the result -will be passed via `callback(filenames)` - -**Note:** On Windows and Linux an open dialog can not be both a file selector -and a directory selector, so if you set `properties` to -`['openFile', 'openDirectory']` on these platforms, a directory selector will be -shown. - -### `dialog.showSaveDialog([browserWindow, ]options[, callback])` - -* `browserWindow` BrowserWindow (optional) -* `options` Object - * `title` String (optional) - * `defaultPath` String (optional) - * `buttonLabel` String (optional) - Custom label for the confirmation button, when - left empty the default label will be used. - * `filters` [FileFilter[]](structures/file-filter.md) (optional) -* `callback` Function (optional) - * `filename` String - -Returns `String`, the path of the file chosen by the user, -if a callback is provided it returns `undefined`. - -The `browserWindow` argument allows the dialog to attach itself to a parent window, making it modal. - -The `filters` specifies an array of file types that can be displayed, see -`dialog.showOpenDialog` for an example. - -If a `callback` is passed, the API call will be asynchronous and the result -will be passed via `callback(filename)` - -### `dialog.showMessageBox([browserWindow, ]options[, callback])` - -* `browserWindow` BrowserWindow (optional) -* `options` Object - * `type` String (optional) - Can be `"none"`, `"info"`, `"error"`, `"question"` or - `"warning"`. On Windows, "question" displays the same icon as "info", unless - you set an icon using the "icon" option. - * `buttons` String[] (optional) - Array of texts for buttons. On Windows, an empty array - will result in one button labeled "OK". - * `defaultId` Integer (optional) - Index of the button in the buttons array which will - be selected by default when the message box opens. - * `title` String (optional) - Title of the message box, some platforms will not show it. - * `message` String - Content of the message box. - * `detail` String (optional) - Extra information of the message. - * `icon` [NativeImage](native-image.md) (optional) - * `cancelId` Integer (optional) - The value will be returned when user cancels the dialog - instead of clicking the buttons of the dialog. By default it is the index - of the buttons that have "cancel" or "no" as label, or 0 if there is no such - buttons. On macOS and Windows the index of the "Cancel" button will always - be used as `cancelId` even if it is specified. - * `noLink` Boolean (optional) - On Windows Electron will try to figure out which one of - the `buttons` are common buttons (like "Cancel" or "Yes"), and show the - others as command links in the dialog. This can make the dialog appear in - the style of modern Windows apps. If you don't like this behavior, you can - set `noLink` to `true`. -* `callback` Function (optional) - * `response` Number - The index of the button that was clicked - -Returns `Integer`, the index of the clicked button, if a callback is provided -it returns undefined. - -Shows a message box, it will block the process until the message box is closed. -It returns the index of the clicked button. - -The `browserWindow` argument allows the dialog to attach itself to a parent window, making it modal. - -If a `callback` is passed, the API call will be asynchronous and the result -will be passed via `callback(response)`. - -### `dialog.showErrorBox(title, content)` - -* `title` String - The title to display in the error box -* `content` String - The text content to display in the error box - -Displays a modal dialog that shows an error message. - -This API can be called safely before the `ready` event the `app` module emits, -it is usually used to report errors in early stage of startup. If called -before the app `ready`event on Linux, the message will be emitted to stderr, -and no GUI dialog will appear. - -## Sheets - -On macOS, dialogs are presented as sheets attached to a window if you provide -a `BrowserWindow` reference in the `browserWindow` parameter, or modals if no -window is provided. - -You can call `BrowserWindow.getCurrentWindow().setSheetOffset(offset)` to change -the offset from the window frame where sheets are attached. diff --git a/docs-translations/th-TH/api/download-item.md b/docs-translations/th-TH/api/download-item.md deleted file mode 100644 index 6603b619aa..0000000000 --- a/docs-translations/th-TH/api/download-item.md +++ /dev/null @@ -1,168 +0,0 @@ -## Class: DownloadItem - -> Control file downloads from remote sources. - -Process: [Main](../glossary.md#main-process) - -`DownloadItem` is an `EventEmitter` that represents a download item in Electron. -It is used in `will-download` event of `Session` class, and allows users to -control the download item. - -```javascript -// In the main process. -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') - - item.on('updated', (event, state) => { - if (state === 'interrupted') { - console.log('Download is interrupted but can be resumed') - } else if (state === 'progressing') { - if (item.isPaused()) { - console.log('Download is paused') - } else { - console.log(`Received bytes: ${item.getReceivedBytes()}`) - } - } - }) - item.once('done', (event, state) => { - if (state === 'completed') { - console.log('Download successfully') - } else { - console.log(`Download failed: ${state}`) - } - }) -}) -``` - -### Instance Events - -#### Event: 'updated' - -Returns: - -* `event` Event -* `state` String - -Emitted when the download has been updated and is not done. - -The `state` can be one of following: - -* `progressing` - The download is in-progress. -* `interrupted` - The download has interrupted and can be resumed. - -#### Event: 'done' - -Returns: - -* `event` Event -* `state` String - -Emitted when the download is in a terminal state. This includes a completed -download, a cancelled download (via `downloadItem.cancel()`), and interrupted -download that can't be resumed. - -The `state` can be one of following: - -* `completed` - The download completed successfully. -* `cancelled` - The download has been cancelled. -* `interrupted` - The download has interrupted and can not resume. - -### Instance Methods - -The `downloadItem` object has the following methods: - -#### `downloadItem.setSavePath(path)` - -* `path` String - Set the save file path of the download item. - -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 `String` - 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. - -#### `downloadItem.isPaused()` - -Returns `Boolean` - Whether the download is paused. - -#### `downloadItem.resume()` - -Resumes the download that has been paused. - -#### `downloadItem.canResume()` - -Resumes `Boolean` - Whether the download can resume. - -#### `downloadItem.cancel()` - -Cancels the download operation. - -#### `downloadItem.getURL()` - -Returns `String` - The origin url where the item is downloaded from. - -#### `downloadItem.getMimeType()` - -Returns `String` - The files mime type. - -#### `downloadItem.hasUserGesture()` - -Returns `Boolean` - Whether the download has user gesture. - -#### `downloadItem.getFilename()` - -Returns `String` - The file name of the download item. - -**Note:** The file name is not always the same as the actual one saved in local -disk. If user changes the file name in a prompted download saving dialog, the -actual name of saved file will be different. - -#### `downloadItem.getTotalBytes()` - -Returns `Integer` - The total size in bytes of the download item. - -If the size is unknown, it returns 0. - -#### `downloadItem.getReceivedBytes()` - -Returns `Integer` - The received bytes of the download item. - -#### `downloadItem.getContentDisposition()` - -Returns `String` - The Content-Disposition field from the response -header. - -#### `downloadItem.getState()` - -Returns `String` - The current state. Can be `progressing`, `completed`, `cancelled` or `interrupted`. - -**Note:** The following methods are useful specifically to resume a -`cancelled` item when session is restarted. - -#### `downloadItem.getURLChain()` - -Returns `String[]` - The complete url chain of the item including any redirects. - -#### `downloadItem.getLastModifiedTime()` - -Returns `String` - Last-Modified header value. - -#### `downloadItem.getETag()` - -Returns `String` - ETag header value. - -#### `downloadItem.getStartTime()` - -Returns `Double` - Number of seconds since the UNIX epoch when the download was -started. diff --git a/docs-translations/th-TH/api/environment-variables.md b/docs-translations/th-TH/api/environment-variables.md deleted file mode 100644 index 83a233e085..0000000000 --- a/docs-translations/th-TH/api/environment-variables.md +++ /dev/null @@ -1,87 +0,0 @@ -# Environment Variables - -> Control application configuration and behavior without changing code. - -Certain Electron behaviors are controlled by environment variables because they -are initialized earlier than the command line flags and the app's code. - -POSIX shell example: - -```bash -$ export ELECTRON_ENABLE_LOGGING=true -$ electron -``` - -Windows console example: - -```powershell -> set ELECTRON_ENABLE_LOGGING=true -> electron -``` - -## Production Variables - -The following environment variables are intended primarily for use at runtime -in packaged Electron applications. - -### `GOOGLE_API_KEY` - -Electron includes a hardcoded API key for making requests to Google's geocoding -webservice. Because this API key is included in every version of Electron, it -often exceeds its usage quota. To work around this, you can supply your own -Google API key in the environment. Place the following code in your main process -file, before opening any browser windows that will make geocoding requests: - -```javascript -process.env.GOOGLE_API_KEY = 'YOUR_KEY_HERE' -``` - -For instructions on how to acquire a Google API key, visit [this page](https://www.chromium.org/developers/how-tos/api-keys). - -By default, a newly generated Google API key may not be allowed to make -geocoding requests. To enable geocoding requests, visit [this page](https://console.developers.google.com/apis/api/geolocation/overview). - -### `ELECTRON_NO_ASAR` - -Disables ASAR support. This variable is only supported in forked child processes -and spawned child processes that set `ELECTRON_RUN_AS_NODE`. - -### `ELECTRON_RUN_AS_NODE` - -Starts the process as a normal Node.js process. - -### `ELECTRON_NO_ATTACH_CONSOLE` _Windows_ - -Don't attach to the current console session. - -### `ELECTRON_FORCE_WINDOW_MENU_BAR` _Linux_ - -Don't use the global menu bar on Linux. - -## Development Variables - -The following environment variables are intended primarily for development and -debugging purposes. - - -### `ELECTRON_ENABLE_LOGGING` - -Prints Chrome's internal logging to the console. - -### `ELECTRON_LOG_ASAR_READS` - -When Electron reads from an ASAR file, log the read offset and file path to -the system `tmpdir`. The resulting file can be provided to the ASAR module -to optimize file ordering. - -### `ELECTRON_ENABLE_STACK_DUMPING` - -Prints the stack trace to the console when Electron crashes. - -This environment variable will not work if the `crashReporter` is started. - -### `ELECTRON_DEFAULT_ERROR_MODE` _Windows_ - -Shows the Windows's crash dialog when Electron crashes. - -This environment variable will not work if the `crashReporter` is started. diff --git a/docs-translations/th-TH/api/file-object.md b/docs-translations/th-TH/api/file-object.md deleted file mode 100644 index d451513c02..0000000000 --- a/docs-translations/th-TH/api/file-object.md +++ /dev/null @@ -1,33 +0,0 @@ -# `File` Object - -> Use the HTML5 `File` API to work natively with files on the filesystem. - -The DOM's File interface provides abstraction around native files in order to -let users work on native files directly with the HTML5 file API. Electron has -added a `path` attribute to the `File` interface which exposes the file's real -path on filesystem. - -Example of getting a real path from a dragged-onto-the-app file: - -```html -
- Drag your file here -
- - -``` diff --git a/docs-translations/th-TH/api/frameless-window.md b/docs-translations/th-TH/api/frameless-window.md deleted file mode 100644 index 3ef5cdc93e..0000000000 --- a/docs-translations/th-TH/api/frameless-window.md +++ /dev/null @@ -1,143 +0,0 @@ -# Frameless Window - -> Open a window without toolbars, borders, or other graphical "chrome". - -A frameless window is a window that has no -[chrome](https://developer.mozilla.org/en-US/docs/Glossary/Chrome), the parts of -the window, like toolbars, that are not a part of the web page. These are -options on the [`BrowserWindow`](browser-window.md) class. - -## Create a frameless window - -To create a frameless window, you need to set `frame` to `false` in -[BrowserWindow](browser-window.md)'s `options`: - - -```javascript -const {BrowserWindow} = require('electron') -let win = new BrowserWindow({width: 800, height: 600, frame: false}) -win.show() -``` - -### Alternatives on macOS - -On macOS 10.9 Mavericks and newer, there's an alternative way to specify -a chromeless window. Instead of setting `frame` to `false` which disables -both the titlebar and window controls, you may want to have the title bar -hidden and your content extend to the full window size, yet still preserve -the window controls ("traffic lights") for standard window actions. -You can do so by specifying the new `titleBarStyle` option: - -#### `hidden` - -Results in a hidden title bar and a full size content window, yet the title bar still has the standard window controls (“traffic lights”) in the top left. - -```javascript -const {BrowserWindow} = require('electron') -let win = new BrowserWindow({titleBarStyle: 'hidden'}) -win.show() -``` - -#### `hidden-inset` - -Results in a hidden title bar with an alternative look where the traffic light buttons are slightly more inset from the window edge. - -```javascript -const {BrowserWindow} = require('electron') -let win = new BrowserWindow({titleBarStyle: 'hidden-inset'}) -win.show() -``` - -## Transparent window - -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 - -* You can not click through the transparent area. We are going to introduce an - API to set window shape to solve this, see - [our issue](https://github.com/electron/electron/issues/1335) for details. -* Transparent windows are not resizable. Setting `resizable` to `true` may make - a transparent window stop working on some platforms. -* The `blur` filter only applies to the web page, so there is no way to apply - blur effect to the content below the window (i.e. other applications open on - the user's system). -* On Windows operating systems, transparent windows will not work when DWM is - disabled. -* On Linux users have to put `--enable-transparent-visuals --disable-gpu` in - the command line to disable GPU and allow ARGB to make transparent window, - this is caused by an upstream bug that [alpha channel doesn't work on some - NVidia drivers](https://code.google.com/p/chromium/issues/detail?id=369209) on - Linux. -* On Mac the native window shadow will not be shown on a transparent window. - -## Click-through window - -To create a click-through window, i.e. making the window ignore all mouse -events, you can call the [win.setIgnoreMouseEvents(ignore)][ignore-mouse-events] -API: - -```javascript -const {BrowserWindow} = require('electron') -let win = new BrowserWindow() -win.setIgnoreMouseEvents(true) -``` - -## Draggable region - -By default, the frameless window is non-draggable. Apps need to specify -`-webkit-app-region: drag` in CSS to tell Electron which regions are draggable -(like the OS's standard titlebar), and apps can also use -`-webkit-app-region: no-drag` to exclude the non-draggable area from the - draggable region. Note that only rectangular shapes are currently supported. - -To make the whole window draggable, you can add `-webkit-app-region: drag` as -`body`'s style: - -```html - - -``` - -And note that if you have made the whole window draggable, you must also mark -buttons as non-draggable, otherwise it would be impossible for users to click on -them: - -```css -button { - -webkit-app-region: no-drag; -} -``` - -If you're setting just a custom titlebar as draggable, you also need to make all -buttons in titlebar non-draggable. - -## Text selection - -In a frameless window the dragging behaviour may conflict with selecting text. -For example, when you drag the titlebar you may accidentally select the text on -the titlebar. To prevent this, you need to disable text selection within a -draggable area like this: - -```css -.titlebar { - -webkit-user-select: none; - -webkit-app-region: drag; -} -``` - -## Context menu - -On some platforms, the draggable area will be treated as a non-client frame, so -when you right click on it a system menu will pop up. To make the context menu -behave correctly on all platforms you should never use a custom context menu on -draggable areas. - -[ignore-mouse-events]: browser-window.md#winsetignoremouseeventsignore diff --git a/docs-translations/th-TH/api/global-shortcut.md b/docs-translations/th-TH/api/global-shortcut.md deleted file mode 100644 index d0f48a6f80..0000000000 --- a/docs-translations/th-TH/api/global-shortcut.md +++ /dev/null @@ -1,75 +0,0 @@ -# globalShortcut - -> Detect keyboard events when the application does not have keyboard focus. - -Process: [Main](../glossary.md#main-process) - -The `globalShortcut` module can register/unregister a global keyboard shortcut -with the operating system so that you can customize the operations for various -shortcuts. - -**Note:** The shortcut is global; it will work even if the app does -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') - -app.on('ready', () => { - // Register a 'CommandOrControl+X' shortcut listener. - const ret = globalShortcut.register('CommandOrControl+X', () => { - console.log('CommandOrControl+X is pressed') - }) - - if (!ret) { - console.log('registration failed') - } - - // Check whether a shortcut is registered. - console.log(globalShortcut.isRegistered('CommandOrControl+X')) -}) - -app.on('will-quit', () => { - // Unregister a shortcut. - globalShortcut.unregister('CommandOrControl+X') - - // Unregister all shortcuts. - globalShortcut.unregisterAll() -}) -``` - -## Methods - -The `globalShortcut` module has the following methods: - -### `globalShortcut.register(accelerator, callback)` - -* `accelerator` [Accelerator](accelerator.md) -* `callback` Function - -Registers a global shortcut of `accelerator`. The `callback` is called when -the registered shortcut is pressed by the user. - -When the accelerator is already taken by other applications, this call will -silently fail. This behavior is intended by operating systems, since they don't -want applications to fight for global shortcuts. - -### `globalShortcut.isRegistered(accelerator)` - -* `accelerator` [Accelerator](accelerator.md) - -Returns `Boolean` - Whether this application has registered `accelerator`. - -When the accelerator is already taken by other applications, this call will -still return `false`. This behavior is intended by operating systems, since they -don't want applications to fight for global shortcuts. - -### `globalShortcut.unregister(accelerator)` - -* `accelerator` [Accelerator](accelerator.md) - -Unregisters the global shortcut of `accelerator`. - -### `globalShortcut.unregisterAll()` - -Unregisters all of the global shortcuts. diff --git a/docs-translations/th-TH/api/incoming-message.md b/docs-translations/th-TH/api/incoming-message.md deleted file mode 100644 index f047aaa7e4..0000000000 --- a/docs-translations/th-TH/api/incoming-message.md +++ /dev/null @@ -1,74 +0,0 @@ -## Class: IncomingMessage - -> Handle responses to HTTP/HTTPS requests. - -Process: [Main](../glossary.md#main-process) - -`IncomingMessage` implements the [Readable Stream](https://nodejs.org/api/stream.html#stream_readable_streams) -interface and is therefore an [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter). - -### Instance Events - -#### Event: 'data' - -Returns: - -* `chunk` Buffer - A chunk of response body's data. - -The `data` event is the usual method of transferring response data into -applicative code. - -#### Event: 'end' - -Indicates that response body has ended. - -#### Event: 'aborted' - -Emitted when a request has been canceled during an ongoing HTTP transaction. - -#### Event: 'error' - -Returns: - -`error` Error - Typically holds an error string identifying failure root cause. - -Emitted when an error was encountered while streaming response data events. For -instance, if the server closes the underlying while the response is still -streaming, an `error` event will be emitted on the response object and a `close` -event will subsequently follow on the request object. - -### Instance Properties - -An `IncomingMessage` instance has the following readable properties: - -#### `response.statusCode` - -An Integer indicating the HTTP response status code. - -#### `response.statusMessage` - -A String representing the HTTP status message. - -#### `response.headers` - -An Object representing the response HTTP headers. The `headers` object is -formatted as follows: - -* All header names are lowercased. -* Each header name produces an array-valued property on the headers object. -* Each header value is pushed into the array associated with its header name. - -#### `response.httpVersion` - -A String indicating the HTTP protocol version number. Typical values are '1.0' -or '1.1'. Additionally `httpVersionMajor` and `httpVersionMinor` are two -Integer-valued readable properties that return respectively the HTTP major and -minor version numbers. - -#### `response.httpVersionMajor` - -An Integer indicating the HTTP protocol major version number. - -#### `response.httpVersionMinor` - -An Integer indicating the HTTP protocol minor version number. diff --git a/docs-translations/th-TH/api/ipc-main.md b/docs-translations/th-TH/api/ipc-main.md deleted file mode 100644 index 57c57b5a4e..0000000000 --- a/docs-translations/th-TH/api/ipc-main.md +++ /dev/null @@ -1,99 +0,0 @@ -# ipcMain - -> Communicate asynchronously from the main process to renderer processes. - -Process: [Main](../glossary.md#main-process) - -The `ipcMain` module is an instance of the -[EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter) class. When used in the main -process, it handles asynchronous and synchronous messages sent from a renderer -process (web page). Messages sent from a renderer will be emitted to this -module. - -## Sending Messages - -It is also possible to send messages from the main process to the renderer -process, see [webContents.send][web-contents-send] for more information. - -* When sending a message, the event name is the `channel`. -* To reply a synchronous message, you need to set `event.returnValue`. -* To send an asynchronous back to the sender, you can use - `event.sender.send(...)`. - -An example of sending and handling messages between the render and main -processes: - -```javascript -// In main process. -const {ipcMain} = require('electron') -ipcMain.on('asynchronous-message', (event, arg) => { - 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' -}) -``` - -```javascript -// In renderer process (web page). -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') -``` - -## Methods - -The `ipcMain` module has the following method to listen for events: - -### `ipcMain.on(channel, listener)` - -* `channel` String -* `listener` Function - -Listens to `channel`, when a new message arrives `listener` would be called with -`listener(event, args...)`. - -### `ipcMain.once(channel, listener)` - -* `channel` String -* `listener` Function - -Adds a one time `listener` function for the event. This `listener` is invoked -only the next time a message is sent to `channel`, after which it is removed. - -### `ipcMain.removeListener(channel, listener)` - -* `channel` String -* `listener` Function - -Removes the specified `listener` from the listener array for the specified -`channel`. - -### `ipcMain.removeAllListeners([channel])` - -* `channel` String (optional) - -Removes all listeners, or those of the specified `channel`. - -## Event object - -The `event` object passed to the `callback` has the following methods: - -### `event.returnValue` - -Set this to the value to be returned in a synchronous message. - -### `event.sender` - -Returns the `webContents` that sent the message, you can call -`event.sender.send` to reply to the asynchronous message, see -[webContents.send][web-contents-send] for more information. - -[web-contents-send]: web-contents.md#webcontentssendchannel-arg1-arg2- diff --git a/docs-translations/th-TH/api/ipc-renderer.md b/docs-translations/th-TH/api/ipc-renderer.md deleted file mode 100644 index de67648764..0000000000 --- a/docs-translations/th-TH/api/ipc-renderer.md +++ /dev/null @@ -1,81 +0,0 @@ -# ipcRenderer - -> Communicate asynchronously from a renderer process to the main process. - -Process: [Renderer](../glossary.md#renderer-process) - -The `ipcRenderer` module is an instance of the -[EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter) class. It provides a few -methods so you can send synchronous and asynchronous messages from the render -process (web page) to the main process. You can also receive replies from the -main process. - -See [ipcMain](ipc-main.md) for code examples. - -## Methods - -The `ipcRenderer` module has the following method to listen for events and send messages: - -### `ipcRenderer.on(channel, listener)` - -* `channel` String -* `listener` Function - -Listens to `channel`, when a new message arrives `listener` would be called with -`listener(event, args...)`. - -### `ipcRenderer.once(channel, listener)` - -* `channel` String -* `listener` Function - -Adds a one time `listener` function for the event. This `listener` is invoked -only the next time a message is sent to `channel`, after which it is removed. - -### `ipcRenderer.removeListener(channel, listener)` - -* `channel` String -* `listener` Function - -Removes the specified `listener` from the listener array for the specified -`channel`. - -### `ipcRenderer.removeAllListeners([channel])` - -* `channel` String (optional) - -Removes all listeners, or those of the specified `channel`. - -### `ipcRenderer.send(channel[, arg1][, arg2][, ...])` - -* `channel` String -* `...args` any[] - -Send a message to the main process asynchronously via `channel`, you can also -send arbitrary arguments. Arguments will be serialized in JSON internally and -hence no functions or prototype chain will be included. - -The main process handles it by listening for `channel` with `ipcMain` module. - -### `ipcRenderer.sendSync(channel[, arg1][, arg2][, ...])` - -* `channel` String -* `...args` any[] - -Send a message to the main process synchronously via `channel`, you can also -send arbitrary arguments. Arguments will be serialized in JSON internally and -hence no functions or prototype chain will be included. - -The main process handles it by listening for `channel` with `ipcMain` module, -and replies by setting `event.returnValue`. - -**Note:** Sending a synchronous message will block the whole renderer process, -unless you know what you are doing you should never use it. - -### `ipcRenderer.sendToHost(channel[, arg1][, arg2][, ...])` - -* `channel` String -* `...args` any[] - -Like `ipcRenderer.send` but the event will be sent to the `` element in -the host page instead of the main process. diff --git a/docs-translations/th-TH/api/locales.md b/docs-translations/th-TH/api/locales.md deleted file mode 100644 index e8af957b26..0000000000 --- a/docs-translations/th-TH/api/locales.md +++ /dev/null @@ -1,139 +0,0 @@ -# Locales - -> Locale values returned by `app.getLocale()`. - -Electron uses Chromium's `l10n_util` library to fetch the locale. Possible -values are listed below: - -| Language Code | Language Name | -|---------------|---------------| -| af | Afrikaans | -| an | Aragonese | -| ar-AE | Arabic (U.A.E.) | -| ar-IQ | Arabic (Iraq) | -| ar | Arabic (Standard) | -| ar-BH | Arabic (Bahrain) | -| ar-DZ | Arabic (Algeria) | -| ar-EG | Arabic (Egypt) | -| ar-JO | Arabic (Jordan) | -| ar-KW | Arabic (Kuwait) | -| ar-LB | Arabic (Lebanon) | -| ar-LY | Arabic (Libya) | -| ar-MA | Arabic (Morocco) | -| ar-OM | Arabic (Oman) | -| ar-QA | Arabic (Qatar) | -| ar-SA | Arabic (Saudi Arabia) | -| ar-SY | Arabic (Syria) | -| ar-TN | Arabic (Tunisia) | -| ar-YE | Arabic (Yemen) | -| as | Assamese | -| ast | Asturian | -| az | Azerbaijani | -| be | Belarusian | -| bg | Bulgarian | -| bg | Bulgarian | -| bn | Bengali | -| br | Breton | -| bs | Bosnian | -| ca | Catalan | -| ce | Chechen | -| ch | Chamorro | -| co | Corsican | -| cr | Cree | -| cs | Czech | -| cv | Chuvash | -| da | Danish | -| de | German (Standard) | -| de-AT | German (Austria) | -| de-CH | German (Switzerland) | -| de-DE | German (Germany) | -| de-LI | German (Liechtenstein) | -| de-LU | German (Luxembourg) | -| el | Greek | -| en-AU | English (Australia) | -| en-BZ | English (Belize) | -| en | English | -| en-CA | English (Canada) | -| en-GB | English (United Kingdom) | -| en-IE | English (Ireland) | -| en-JM | English (Jamaica) | -| en-NZ | English (New Zealand) | -| en-PH | English (Philippines) | -| en-TT | English (Trinidad & Tobago) | -| en-US | English (United States) | -| en-ZA | English (South Africa) | -| en-ZW | English (Zimbabwe) | -| eo | Esperanto | -| et | Estonian | -| eu | Basque | -| fa | Persian | -| fa | Farsi | -| fa-IR | Persian/Iran | -| fi | Finnish | -| fj | Fijian | -| fo | Faeroese | -| fr-CH | French (Switzerland) | -| fr-FR | French (France) | -| fr-LU | French (Luxembourg) | -| fr-MC | French (Monaco) | -| fr | French (Standard) | -| fr-BE | French (Belgium) | -| fr-CA | French (Canada) | -| fur | Friulian | -| fy | Frisian | -| ga | Irish | -| gd-IE | Gaelic (Irish) | -| gd | Gaelic (Scots) | -| gl | Galacian | -| gu | Gujurati | -| he | Hebrew | -| hi | Hindi | -| hr | Croatian | -| ht | Haitian | -| hu | Hungarian | -| hy | Armenian | -| id | Indonesian | -| is | Icelandic | -| it-CH | Italian (Switzerland) | -| it | Italian (Standard) | -| iu | Inuktitut | -| ja | Japanese | -| ka | Georgian | -| kk | Kazakh | -| km | Khmer | -| kn | Kannada | -| ko | Korean | -| ko-KP | Korean (North Korea) | -| ko-KR | Korean (South Korea) | -| ks | Kashmiri | -| ky | Kirghiz | -| la | Latin | -| lb | Luxembourgish | -| lt | Lithuanian | -| lv | Latvian | -| mi | Maori | -| mk | FYRO Macedonian | -| ml | Malayalam | -| mo | Moldavian | -| mr | Marathi | -| ms | Malay | -| mt | Maltese | -| my | Burmese | -| nb | Norwegian (Bokmal) | -| ne | Nepali | -| ng | Ndonga | -| nl | Dutch (Standard) | -| nl-BE | Dutch (Belgian) | -| nn | Norwegian (Nynorsk) | -| no | Norwegian | -| nv | Navajo | -| oc | Occitan | -| om | Oromo | -| or | Oriya | -| sq | Albanian | -| tlh | Klingon | -| zh-TW | Chinese (Taiwan) | -| zh | Chinese | -| zh-CN | Chinese (PRC) | -| zh-HK | Chinese (Hong Kong) | -| zh-SG | Chinese (Singapore) | diff --git a/docs-translations/th-TH/api/menu-item.md b/docs-translations/th-TH/api/menu-item.md deleted file mode 100644 index 920589c41d..0000000000 --- a/docs-translations/th-TH/api/menu-item.md +++ /dev/null @@ -1,116 +0,0 @@ -## Class: MenuItem - -> Add items to native application menus and context menus. - -Process: [Main](../glossary.md#main-process) - -See [`Menu`](menu.md) for examples. - -### `new MenuItem(options)` - -* `options` Object - * `click` Function (optional) - Will be called with - `click(menuItem, browserWindow, event)` when the menu item is clicked. - * `menuItem` MenuItem - * `browserWindow` BrowserWindow - * `event` Event - * `role` String (optional) - Define the action of the menu item, when specified the - `click` property will be ignored. - * `type` String (optional) - Can be `normal`, `separator`, `submenu`, `checkbox` or - `radio`. - * `label` String - (optional) - * `sublabel` String - (optional) - * `accelerator` [Accelerator](accelerator.md) (optional) - * `icon` ([NativeImage](native-image.md) | String) (optional) - * `enabled` Boolean (optional) - If false, the menu item will be greyed out and - unclickable. - * `visible` Boolean (optional) - If false, the menu item will be entirely hidden. - * `checked` Boolean (optional) - Should only be specified for `checkbox` or `radio` type - menu items. - * `submenu` (MenuItemConstructorOptions[] | Menu) (optional) - Should be specified for `submenu` type menu items. If - `submenu` is specified, the `type: 'submenu'` can be omitted. If the value - is not a `Menu` then it will be automatically converted to one using - `Menu.buildFromTemplate`. - * `id` String (optional) - Unique within a single menu. If defined then it can be used - as a reference to this item by the position attribute. - * `position` String (optional) - This field allows fine-grained definition of the - specific location within a given menu. - -It is best to specify `role` for any menu item that matches a standard role, -rather than trying to manually implement the behavior in a `click` function. -The built-in `role` behavior will give the best native experience. - -The `label` and `accelerator` are optional when using a `role` and will default -to appropriate values for each platform. - -The `role` property can have following values: - -* `undo` -* `redo` -* `cut` -* `copy` -* `paste` -* `pasteandmatchstyle` -* `selectall` -* `delete` -* `minimize` - Minimize current window -* `close` - Close current window -* `quit`- Quit the application -* `reload` - Reload the current window -* `toggledevtools` - Toggle developer tools in the current window -* `togglefullscreen`- Toggle full screen mode on the current window -* `resetzoom` - Reset the focused page's zoom level to the original size -* `zoomin` - Zoom in the focused page by 10% -* `zoomout` - Zoom out the focused page by 10% - -On macOS `role` can also have following additional values: - -* `about` - Map to the `orderFrontStandardAboutPanel` action -* `hide` - Map to the `hide` action -* `hideothers` - Map to the `hideOtherApplications` action -* `unhide` - Map to the `unhideAllApplications` action -* `startspeaking` - Map to the `startSpeaking` action -* `stopspeaking` - Map to the `stopSpeaking` action -* `front` - Map to the `arrangeInFront` action -* `zoom` - Map to the `performZoom` action -* `window` - The submenu is a "Window" menu -* `help` - The submenu is a "Help" menu -* `services` - The submenu is a "Services" menu - -When specifying `role` on macOS, `label` and `accelerator` are the only options -that will affect the MenuItem. All other options will be ignored. - -### Instance Properties - -The following properties are available on instances of `MenuItem`: - -#### `menuItem.enabled` - -A Boolean indicating whether the item is enabled, this property can be -dynamically changed. - -#### `menuItem.visible` - -A Boolean indicating whether the item is visible, this property can be -dynamically changed. - -#### `menuItem.checked` - -A Boolean indicating whether the item is checked, this property can be -dynamically changed. - -A `checkbox` menu item will toggle the `checked` property on and off when -selected. - -A `radio` menu item will turn on its `checked` property when clicked, and -will turn off that property for all adjacent items in the same menu. - -You can add a `click` function for additional behavior. - -#### `menuItem.label` - -A String representing the menu items visible label - -#### `menuItem.click` - -A Function that is fired when the MenuItem recieves a click event diff --git a/docs-translations/th-TH/api/menu.md b/docs-translations/th-TH/api/menu.md deleted file mode 100644 index 64ab3afafb..0000000000 --- a/docs-translations/th-TH/api/menu.md +++ /dev/null @@ -1,403 +0,0 @@ -## Class: Menu - -> Create native application menus and context menus. - -Process: [Main](../glossary.md#main-process) - -### `new Menu()` - -Creates a new menu. - -### Static Methods - -The `menu` class has the following static methods: - -#### `Menu.setApplicationMenu(menu)` - -* `menu` Menu - -Sets `menu` as the application menu on macOS. On Windows and Linux, the `menu` -will be set as each window's top menu. - -**Note:** This API has to be called after the `ready` event of `app` module. - -#### `Menu.getApplicationMenu()` - -Returns `Menu` - The application menu, if set, or `null`, if not set. - -#### `Menu.sendActionToFirstResponder(action)` _macOS_ - -* `action` String - -Sends the `action` to the first responder of application. This is used for -emulating default Cocoa menu behaviors, usually you would just use the -`role` property of `MenuItem`. - -See the [macOS Cocoa Event Handling Guide](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/EventOverview/EventArchitecture/EventArchitecture.html#//apple_ref/doc/uid/10000060i-CH3-SW7) -for more information on macOS' native actions. - -#### `Menu.buildFromTemplate(template)` - -* `template` MenuItemConstructorOptions[] - -Returns `Menu` - -Generally, the `template` is just an array of `options` for constructing a -[MenuItem](menu-item.md). The usage can be referenced above. - -You can also attach other fields to the element of the `template` and they -will become properties of the constructed menu items. - -### Instance Methods - -The `menu` object has the following instance methods: - -#### `menu.popup([browserWindow, x, y, positioningItem])` - -* `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`. - -#### `menu.append(menuItem)` - -* `menuItem` MenuItem - -Appends the `menuItem` to the menu. - -#### `menu.insert(pos, menuItem)` - -* `pos` Integer -* `menuItem` MenuItem - -Inserts the `menuItem` to the `pos` position of the menu. - -### Instance Properties - -`menu` objects also have the following properties: - -#### `menu.items` - -A MenuItem[] array containing the menu's items. - -Each `Menu` consists of multiple [`MenuItem`](menu-item.md)s and each `MenuItem` -can have a submenu. - -## Examples - -The `Menu` class is only available in the main process, but you can also use it -in the render process via the [`remote`](remote.md) module. - -### Main process - -An example of creating the application menu in the main process with the -simple template API: - -```javascript -const {app, Menu} = require('electron') - -const template = [ - { - label: 'Edit', - submenu: [ - { - role: 'undo' - }, - { - role: 'redo' - }, - { - type: 'separator' - }, - { - role: 'cut' - }, - { - role: 'copy' - }, - { - role: 'paste' - }, - { - role: 'pasteandmatchstyle' - }, - { - role: 'delete' - }, - { - role: 'selectall' - } - ] - }, - { - label: 'View', - submenu: [ - { - role: 'reload' - }, - { - role: 'toggledevtools' - }, - { - type: 'separator' - }, - { - role: 'resetzoom' - }, - { - role: 'zoomin' - }, - { - role: 'zoomout' - }, - { - type: 'separator' - }, - { - role: 'togglefullscreen' - } - ] - }, - { - role: 'window', - submenu: [ - { - role: 'minimize' - }, - { - role: 'close' - } - ] - }, - { - role: 'help', - submenu: [ - { - label: 'Learn More', - click () { require('electron').shell.openExternal('http://electron.atom.io') } - } - ] - } -] - -if (process.platform === 'darwin') { - template.unshift({ - label: app.getName(), - submenu: [ - { - role: 'about' - }, - { - type: 'separator' - }, - { - role: 'services', - submenu: [] - }, - { - type: 'separator' - }, - { - role: 'hide' - }, - { - role: 'hideothers' - }, - { - role: 'unhide' - }, - { - type: 'separator' - }, - { - role: 'quit' - } - ] - }) - // Edit menu. - template[1].submenu.push( - { - type: 'separator' - }, - { - label: 'Speech', - submenu: [ - { - role: 'startspeaking' - }, - { - role: 'stopspeaking' - } - ] - } - ) - // Window menu. - template[3].submenu = [ - { - label: 'Close', - accelerator: 'CmdOrCtrl+W', - role: 'close' - }, - { - label: 'Minimize', - accelerator: 'CmdOrCtrl+M', - role: 'minimize' - }, - { - label: 'Zoom', - role: 'zoom' - }, - { - type: 'separator' - }, - { - label: 'Bring All to Front', - role: 'front' - } - ] -} - -const menu = Menu.buildFromTemplate(template) -Menu.setApplicationMenu(menu) -``` - -### Render process - -Below is an example of creating a menu dynamically in a web page -(render process) by using the [`remote`](remote.md) module, and showing it when -the user right clicks the page: - -```html - - -``` - - -## Notes on macOS Application Menu - -macOS has a completely different style of application menu from Windows and -Linux. Here are some notes on making your app's menu more native-like. - -### Standard Menus - -On macOS there are many system-defined standard menus, like the `Services` and -`Windows` menus. To make your menu a standard menu, you should set your menu's -`role` to one of following and Electron will recognize them and make them -become standard menus: - -* `window` -* `help` -* `services` - -### Standard Menu Item Actions - -macOS has provided standard actions for some menu items, like `About xxx`, -`Hide xxx`, and `Hide Others`. To set the action of a menu item to a standard -action, you should set the `role` attribute of the menu item. - -### Main Menu's Name - -On macOS the label of the application menu's first item is always your app's -name, no matter what label you set. To change it, modify your app bundle's -`Info.plist` file. See -[About Information Property List Files][AboutInformationPropertyListFiles] -for more information. - -## Setting Menu for Specific Browser Window (*Linux* *Windows*) - -The [`setMenu` method][setMenu] of browser windows can set the menu of certain -browser windows. - -## Menu Item Position - -You can make use of `position` and `id` to control how the item will be placed -when building a menu with `Menu.buildFromTemplate`. - -The `position` attribute of `MenuItem` has the form `[placement]=[id]`, where -`placement` is one of `before`, `after`, or `endof` and `id` is the unique ID of -an existing item in the menu: - -* `before` - Inserts this item before the id referenced item. If the - referenced item doesn't exist the item will be inserted at the end of - the menu. -* `after` - Inserts this item after id referenced item. If the referenced - item doesn't exist the item will be inserted at the end of the menu. -* `endof` - Inserts this item at the end of the logical group containing - the id referenced item (groups are created by separator items). If - the referenced item doesn't exist, a new separator group is created with - the given id and this item is inserted after that separator. - -When an item is positioned, all un-positioned items are inserted after -it until a new item is positioned. So if you want to position a group of -menu items in the same location you only need to specify a position for -the first item. - -### Examples - -Template: - -```javascript -[ - {label: '4', id: '4'}, - {label: '5', id: '5'}, - {label: '1', id: '1', position: 'before=4'}, - {label: '2', id: '2'}, - {label: '3', id: '3'} -] -``` - -Menu: - -``` -- 1 -- 2 -- 3 -- 4 -- 5 -``` - -Template: - -```javascript -[ - {label: 'a', position: 'endof=letters'}, - {label: '1', position: 'endof=numbers'}, - {label: 'b', position: 'endof=letters'}, - {label: '2', position: 'endof=numbers'}, - {label: 'c', position: 'endof=letters'}, - {label: '3', position: 'endof=numbers'} -] -``` - -Menu: - -``` -- --- -- a -- b -- c -- --- -- 1 -- 2 -- 3 -``` - -[AboutInformationPropertyListFiles]: https://developer.apple.com/library/ios/documentation/general/Reference/InfoPlistKeyReference/Articles/AboutInformationPropertyListFiles.html -[setMenu]: https://github.com/electron/electron/blob/master/docs/api/browser-window.md#winsetmenumenu-linux-windows diff --git a/docs-translations/th-TH/api/native-image.md b/docs-translations/th-TH/api/native-image.md deleted file mode 100644 index cc910cbb8a..0000000000 --- a/docs-translations/th-TH/api/native-image.md +++ /dev/null @@ -1,256 +0,0 @@ -# nativeImage - -> Create tray, dock, and application icons using PNG or JPG files. - -Process: [Main](../glossary.md#main-process), [Renderer](../glossary.md#renderer-process) - -In Electron, for the APIs that take images, you can pass either file paths or -`NativeImage` instances. An empty image will be used when `null` is passed. - -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 - -Currently `PNG` and `JPEG` image formats are supported. `PNG` is recommended -because of its support for transparency and lossless compression. - -On Windows, you can also load `ICO` icons from file paths. For best visual -quality it is recommended to include at least the following sizes in the: - -* Small icon - * 16x16 (100% DPI scale) - * 20x20 (125% DPI scale) - * 24x24 (150% DPI scale) - * 32x32 (200% DPI scale) -* Large icon - * 32x32 (100% DPI scale) - * 40x40 (125% DPI scale) - * 48x48 (150% DPI scale) - * 64x64 (200% DPI scale) -* 256x256 - -Check the *Size requirements* section in [this article][icons]. - -[icons]:https://msdn.microsoft.com/en-us/library/windows/desktop/dn742485(v=vs.85).aspx - -## High Resolution Image - -On platforms that have high-DPI support such as Apple Retina displays, you can -append `@2x` after image's base filename to mark it as a high resolution image. - -For example if `icon.png` is a normal image that has standard resolution, then -`icon@2x.png` will be treated as a high resolution image that has double DPI -density. - -If you want to support displays with different DPI densities at the same time, -you can put images with different sizes in the same folder and use the filename -without DPI suffixes. For example: - -```text -images/ -├── icon.png -├── icon@2x.png -└── icon@3x.png -``` - - -```javascript -const {Tray} = require('electron') -let appIcon = new Tray('/Users/somebody/images/icon.png') -console.log(appIcon) -``` - -Following suffixes for DPI are also supported: - -* `@1x` -* `@1.25x` -* `@1.33x` -* `@1.4x` -* `@1.5x` -* `@1.8x` -* `@2x` -* `@2.5x` -* `@3x` -* `@4x` -* `@5x` - -## Template Image - -Template images consist of black and clear colors (and an alpha channel). -Template images are not intended to be used as standalone images and are usually -mixed with other content to create the desired final appearance. - -The most common case is to use template images for a menu bar icon so it can -adapt to both light and dark menu bars. - -**Note:** Template image is only supported on macOS. - -To mark an image as a template image, its filename should end with the word -`Template`. For example: - -* `xxxTemplate.png` -* `xxxTemplate@2x.png` - -## Methods - -The `nativeImage` module has the following methods, all of which return -an instance of the `NativeImage` class: - -### `nativeImage.createEmpty()` - -Returns `NativeImage` - -Creates an empty `NativeImage` instance. - -### `nativeImage.createFromPath(path)` - -* `path` String - -Returns `NativeImage` - -Creates a new `NativeImage` instance from a file located at `path`. This method -returns an empty image if the `path` does not exist, cannot be read, or is not -a valid image. - -```javascript -const nativeImage = require('electron').nativeImage - -let image = nativeImage.createFromPath('/Users/somebody/images/icon.png') -console.log(image) -``` - -### `nativeImage.createFromBuffer(buffer[, options])` - -* `buffer` [Buffer][buffer] -* `options` Object (optional) - * `width` Integer (optional) - Required for bitmap buffers. - * `height` Integer (optional) - Required for bitmap buffers. - * `scaleFactor` Double (optional) - Defaults to 1.0. - -Returns `NativeImage` - -Creates a new `NativeImage` instance from `buffer`. - -### `nativeImage.createFromDataURL(dataURL)` - -* `dataURL` String - -Creates a new `NativeImage` instance from `dataURL`. - -## Class: NativeImage - -> Natively wrap images such as tray, dock, and application icons. - -Process: [Main](../glossary.md#main-process), [Renderer](../glossary.md#renderer-process) - -### Instance Methods - -The following methods are available on instances of the `NativeImage` class: - -#### `image.toPNG()` - -Returns `Buffer` - A [Buffer][buffer] that contains the image's `PNG` encoded data. - -#### `image.toJPEG(quality)` - -* `quality` Integer (**required**) - Between 0 - 100. - -Returns `Buffer` - A [Buffer][buffer] that contains the image's `JPEG` encoded data. - -#### `image.toBitmap()` - -Returns `Buffer` - A [Buffer][buffer] that contains a copy of the image's raw bitmap pixel -data. - -#### `image.toDataURL()` - -Returns `String` - The data URL of the image. - -#### `image.getBitmap()` - -Returns `Buffer` - A [Buffer][buffer] that contains the image's raw bitmap pixel data. - -The difference between `getBitmap()` and `toBitmap()` is, `getBitmap()` does not -copy the bitmap data, so you have to use the returned Buffer immediately in -current event loop tick, otherwise the data might be changed or destroyed. - -#### `image.getNativeHandle()` _macOS_ - -Returns `Buffer` - A [Buffer][buffer] that stores C pointer to underlying native handle of -the image. On macOS, a pointer to `NSImage` instance would be returned. - -Notice that the returned pointer is a weak pointer to the underlying native -image instead of a copy, so you _must_ ensure that the associated -`nativeImage` instance is kept around. - -#### `image.isEmpty()` - -Returns `Boolean` - Whether the image is empty. - -#### `image.getSize()` - -Returns `Object`: - -* `width` Integer -* `height` Integer - -#### `image.setTemplateImage(option)` - -* `option` Boolean - -Marks the image as a template image. - -#### `image.isTemplateImage()` - -Returns `Boolean` - Whether the image is a template image. - -#### `image.crop(rect)` - -* `rect` Object - The area of the image to crop - * `x` Integer - * `y` Integer - * `width` Integer - * `height` Integer - -Returns `NativeImage` - The cropped image. - -#### `image.resize(options)` - -* `options` Object - * `width` Integer (optional) - * `height` Integer (optional) - * `quality` String (optional) - The desired quality of the resize image. - Possible values are `good`, `better` or `best`. The default is `best`. - These values express a desired quality/speed tradeoff. They are translated - into an algorithm-specific method that depends on the capabilities - (CPU, GPU) of the underlying platform. It is possible for all three methods - to be mapped to the same algorithm on a given platform. - -Returns `NativeImage` - The resized image. - -If only the `height` or the `width` are specified then the current aspect ratio -will be preserved in the resized image. - -#### `image.getAspectRatio()` - -Returns `Float` - The image's aspect ratio. - -[buffer]: https://nodejs.org/api/buffer.html#buffer_class_buffer diff --git a/docs-translations/th-TH/api/net.md b/docs-translations/th-TH/api/net.md deleted file mode 100644 index e5e5961714..0000000000 --- a/docs-translations/th-TH/api/net.md +++ /dev/null @@ -1,71 +0,0 @@ -# net - -> Issue HTTP/HTTPS requests using Chromium's native networking library - -Process: [Main](../glossary.md#main-process) - -The `net` module is a client-side API for issuing HTTP(S) requests. It is -similar to the [HTTP](https://nodejs.org/api/http.html) and -[HTTPS](https://nodejs.org/api/https.html) modules of Node.js but uses -Chromium's native networking library instead of the Node.js implementation, -offering better support for web proxies. - -The following is a non-exhaustive list of why you may consider using the `net` -module instead of the native Node.js modules: - -* Automatic management of system proxy configuration, support of the wpad - protocol and proxy pac configuration files. -* Automatic tunneling of HTTPS requests. -* Support for authenticating proxies using basic, digest, NTLM, Kerberos or - negotiate authentication schemes. -* Support for traffic monitoring proxies: Fiddler-like proxies used for access - control and monitoring. - -The `net` module API has been specifically designed to mimic, as closely as -possible, the familiar Node.js API. The API components including classes, -methods, properties and event names are similar to those commonly used in -Node.js. - -For instance, the following example quickly shows how the `net` API might be -used: - -```javascript -const {app} = require('electron') -app.on('ready', () => { - const {net} = require('electron') - const request = net.request('https://github.com') - request.on('response', (response) => { - console.log(`STATUS: ${response.statusCode}`) - console.log(`HEADERS: ${JSON.stringify(response.headers)}`) - response.on('data', (chunk) => { - console.log(`BODY: ${chunk}`) - }) - response.on('end', () => { - console.log('No more data in response.') - }) - }) - request.end() -}) -``` - -By the way, it is almost identical to how you would normally use the -[HTTP](https://nodejs.org/api/http.html)/[HTTPS](https://nodejs.org/api/https.html) -modules of Node.js - -The `net` API can be used only after the application emits the `ready` event. -Trying to use the module before the `ready` event will throw an error. - -## Methods - -The `net` module has the following methods: - -### `net.request(options)` - -* `options` (Object | String) - The `ClientRequest` constructor options. - -Returns [`ClientRequest`](./client-request.md) - -Creates a [`ClientRequest`](./client-request.md) instance using the provided -`options` which are directly forwarded to the `ClientRequest` constructor. -The `net.request` method would be used to issue both secure and insecure HTTP -requests according to the specified protocol scheme in the `options` object. diff --git a/docs-translations/th-TH/api/power-monitor.md b/docs-translations/th-TH/api/power-monitor.md deleted file mode 100644 index 0c012c64ac..0000000000 --- a/docs-translations/th-TH/api/power-monitor.md +++ /dev/null @@ -1,41 +0,0 @@ -# powerMonitor - -> Monitor power state changes. - -Process: [Main](../glossary.md#main-process) - -You cannot require or use this module until the `ready` event of the `app` -module is emitted. - -For example: - -```javascript -const electron = require('electron') -const {app} = electron - -app.on('ready', () => { - electron.powerMonitor.on('suspend', () => { - console.log('The system is going to sleep') - }) -}) -``` - -## Events - -The `powerMonitor` module emits the following events: - -### Event: 'suspend' - -Emitted when the system is suspending. - -### Event: 'resume' - -Emitted when system is resuming. - -### Event: 'on-ac' _Windows_ - -Emitted when the system changes to AC power. - -### Event: 'on-battery' _Windows_ - -Emitted when system changes to battery power. diff --git a/docs-translations/th-TH/api/power-save-blocker.md b/docs-translations/th-TH/api/power-save-blocker.md deleted file mode 100644 index f78ec5d999..0000000000 --- a/docs-translations/th-TH/api/power-save-blocker.md +++ /dev/null @@ -1,56 +0,0 @@ -# powerSaveBlocker - -> Block the system from entering low-power (sleep) mode. - -Process: [Main](../glossary.md#main-process) - -For example: - -```javascript -const {powerSaveBlocker} = require('electron') - -const id = powerSaveBlocker.start('prevent-display-sleep') -console.log(powerSaveBlocker.isStarted(id)) - -powerSaveBlocker.stop(id) -``` - -## Methods - -The `powerSaveBlocker` module has the following methods: - -### `powerSaveBlocker.start(type)` - -* `type` String - Power save blocker type. - * `prevent-app-suspension` - Prevent the application from being suspended. - Keeps system active but allows screen to be turned off. Example use cases: - downloading a file or playing audio. - * `prevent-display-sleep` - Prevent the display from going to sleep. Keeps - system and screen active. Example use case: playing video. - -Returns `Integer` - The blocker ID that is assigned to this power blocker - -Starts preventing the system from entering lower-power mode. Returns an integer -identifying the power save blocker. - -**Note:** `prevent-display-sleep` has higher precedence over -`prevent-app-suspension`. Only the highest precedence type takes effect. In -other words, `prevent-display-sleep` always takes precedence over -`prevent-app-suspension`. - -For example, an API calling A requests for `prevent-app-suspension`, and -another calling B requests for `prevent-display-sleep`. `prevent-display-sleep` -will be used until B stops its request. After that, `prevent-app-suspension` -is used. - -### `powerSaveBlocker.stop(id)` - -* `id` Integer - The power save blocker id returned by `powerSaveBlocker.start`. - -Stops the specified power save blocker. - -### `powerSaveBlocker.isStarted(id)` - -* `id` Integer - The power save blocker id returned by `powerSaveBlocker.start`. - -Returns `Boolean` - Whether the corresponding `powerSaveBlocker` has started. diff --git a/docs-translations/th-TH/api/process.md b/docs-translations/th-TH/api/process.md deleted file mode 100644 index 951abc7df1..0000000000 --- a/docs-translations/th-TH/api/process.md +++ /dev/null @@ -1,118 +0,0 @@ -# process - -> Extensions to process object. - -Process: [Main](../glossary.md#main-process), [Renderer](../glossary.md#renderer-process) - -Electron's `process` object is extended from the -[Node.js `process` object](https://nodejs.org/api/process.html). -It adds the following events, properties, and methods: - -## Events - -### Event: 'loaded' - -Emitted when Electron has loaded its internal initialization script and is -beginning to load the web page or the main script. - -It can be used by the preload script to add removed Node global symbols back to -the global scope when node integration is turned off: - -```javascript -// preload.js -const _setImmediate = setImmediate -const _clearImmediate = clearImmediate -process.once('loaded', () => { - global.setImmediate = _setImmediate - global.clearImmediate = _clearImmediate -}) -``` - -## Properties - -### `process.noAsar` - -Setting this to `true` can disable the support for `asar` archives in Node's -built-in modules. - -### `process.type` - -Current process's type, can be `"browser"` (i.e. main process) or `"renderer"`. - -### `process.versions.electron` - -Electron's version string. - -### `process.versions.chrome` - -Chrome's version string. - -### `process.resourcesPath` - -Path to the resources directory. - -### `process.mas` - -For Mac App Store build, this property is `true`, for other builds it is -`undefined`. - -### `process.windowsStore` - -If the app is running as a Windows Store app (appx), this property is `true`, -for otherwise it is `undefined`. - -### `process.defaultApp` - -When app is started by being passed as parameter to the default app, this -property is `true` in the main process, otherwise it is `undefined`. - -## Methods - -The `process` object has the following method: - -### `process.crash()` - -Causes the main thread of the current process crash. - -### `process.hang()` - -Causes the main thread of the current process hang. - -### `process.setFdLimit(maxDescriptors)` _macOS_ _Linux_ - -* `maxDescriptors` Integer - -Sets the file descriptor soft limit to `maxDescriptors` or the OS hard -limit, whichever is lower for the current process. - -### `process.getProcessMemoryInfo()` - -Returns `Object`: - -* `workingSetSize` Integer - The amount of memory currently pinned to actual physical - RAM. -* `peakWorkingSetSize` Integer - The maximum amount of memory that has ever been pinned - to actual physical RAM. -* `privateBytes` Integer - The amount of memory not shared by other processes, such as - JS heap or HTML content. -* `sharedBytes` Integer - The amount of memory shared between processes, typically - memory consumed by the Electron code itself - -Returns an object giving memory usage statistics about the current process. Note -that all statistics are reported in Kilobytes. - -### `process.getSystemMemoryInfo()` - -Returns `Object`: - -* `total` Integer - The total amount of physical memory in Kilobytes available to the - system. -* `free` Integer - The total amount of memory not being used by applications or disk - cache. -* `swapTotal` Integer - The total amount of swap memory in Kilobytes available to the - system. _Windows_ _Linux_ -* `swapFree` Integer - The free amount of swap memory in Kilobytes available to the - system. _Windows_ _Linux_ - -Returns an object giving memory usage statistics about the entire system. Note -that all statistics are reported in Kilobytes. diff --git a/docs-translations/th-TH/api/protocol.md b/docs-translations/th-TH/api/protocol.md deleted file mode 100644 index 3de153db48..0000000000 --- a/docs-translations/th-TH/api/protocol.md +++ /dev/null @@ -1,297 +0,0 @@ -# protocol - -> Register a custom protocol and intercept existing protocol requests. - -Process: [Main](../glossary.md#main-process) - -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') - -app.on('ready', () => { - protocol.registerFileProtocol('atom', (request, callback) => { - const url = request.url.substr(7) - callback({path: path.normalize(`${__dirname}/${url}`)}) - }, (error) => { - 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. - -## Methods - -The `protocol` module has the following methods: - -### `protocol.registerStandardSchemes(schemes[, options])` - -* `schemes` String[] - Custom schemes to be registered as standard schemes. -* `options` Object (optional) - * `secure` Boolean (optional) - `true` to register the scheme as secure. - Default `false`. - -A standard scheme adheres to what RFC 3986 calls [generic URI -syntax](https://tools.ietf.org/html/rfc3986#section-3). For example `http` and -`https` are standard schemes, while `file` is not. - -Registering a scheme as standard, will allow relative and absolute resources to -be resolved correctly when served. Otherwise the scheme will behave like the -`file` protocol, but without the ability to resolve relative URLs. - -For example when you load following page with custom protocol without -registering it as standard scheme, the image will not be loaded because -non-standard schemes can not recognize relative URLs: - -```html - - - -``` - -Registering a scheme as standard will allow access to files through the -[FileSystem API][file-system-api]. Otherwise the renderer will throw a security -error for the scheme. - -By default web storage apis (localStorage, sessionStorage, webSQL, indexedDB, cookies) -are disabled for non standard schemes. So in general if you want to register a -custom protocol to replace the `http` protocol, you have to register it as a standard scheme: - -```javascript -const {app, protocol} = require('electron') - -protocol.registerStandardSchemes(['atom']) -app.on('ready', () => { - protocol.registerHttpProtocol('atom', '...') -}) -``` - -**Note:** This method can only be used before the `ready` event of the `app` -module gets emitted. - -### `protocol.registerServiceWorkerSchemes(schemes)` - -* `schemes` String[] - Custom schemes to be registered to handle service workers. - -### `protocol.registerFileProtocol(scheme, handler[, completion])` - -* `scheme` String -* `handler` Function - * `request` Object - * `url` String - * `referrer` String - * `method` String - * `uploadData` [UploadData[]](structures/upload-data.md) - * `callback` Function - * `filePath` String (optional) -* `completion` Function (optional) - * `error` Error - -Registers a protocol of `scheme` that will send the file as a response. The -`handler` will be called with `handler(request, callback)` when a `request` is -going to be created with `scheme`. `completion` will be called with -`completion(null)` when `scheme` is successfully registered or -`completion(error)` when failed. - -To handle the `request`, the `callback` should be called with either the file's -path or an object that has a `path` property, e.g. `callback(filePath)` or -`callback({path: filePath})`. - -When `callback` is called with nothing, a number, or an object that has an -`error` property, the `request` will fail with the `error` number you -specified. For the available error numbers you can use, please see the -[net error list][net-error]. - -By default the `scheme` is treated like `http:`, which is parsed differently -than protocols that follow the "generic URI syntax" like `file:`, so you -probably want to call `protocol.registerStandardSchemes` to have your scheme -treated as a standard scheme. - -### `protocol.registerBufferProtocol(scheme, handler[, completion])` - -* `scheme` String -* `handler` Function - * `request` Object - * `url` String - * `referrer` String - * `method` String - * `uploadData` [UploadData[]](structures/upload-data.md) - * `callback` Function - * `buffer` (Buffer | [MimeTypedBuffer](structures/mime-typed-buffer.md)) (optional) -* `completion` Function (optional) - * `error` Error - -Registers a protocol of `scheme` that will send a `Buffer` as a response. - -The usage is the same with `registerFileProtocol`, except that the `callback` -should be called with either a `Buffer` object or an object that has the `data`, -`mimeType`, and `charset` properties. - -Example: - -```javascript -const {protocol} = require('electron') - -protocol.registerBufferProtocol('atom', (request, callback) => { - callback({mimeType: 'text/html', data: new Buffer('
Response
')}) -}, (error) => { - if (error) console.error('Failed to register protocol') -}) -``` - -### `protocol.registerStringProtocol(scheme, handler[, completion])` - -* `scheme` String -* `handler` Function - * `request` Object - * `url` String - * `referrer` String - * `method` String - * `uploadData` [UploadData[]](structures/upload-data.md) - * `callback` Function - * `data` String (optional) -* `completion` Function (optional) - * `error` Error - -Registers a protocol of `scheme` that will send a `String` as a response. - -The usage is the same with `registerFileProtocol`, except that the `callback` -should be called with either a `String` or an object that has the `data`, -`mimeType`, and `charset` properties. - -### `protocol.registerHttpProtocol(scheme, handler[, completion])` - -* `scheme` String -* `handler` Function - * `request` Object - * `url` String - * `referrer` String - * `method` String - * `uploadData` [UploadData[]](structures/upload-data.md) - * `callback` Function - * `redirectRequest` Object - * `url` String - * `method` String - * `session` Object (optional) - * `uploadData` Object (optional) - * `contentType` String - MIME type of the content. - * `data` String - Content to be sent. -* `completion` Function (optional) - * `error` Error - -Registers a protocol of `scheme` that will send an HTTP request as a response. - -The usage is the same with `registerFileProtocol`, except that the `callback` -should be called with a `redirectRequest` object that has the `url`, `method`, -`referrer`, `uploadData` and `session` properties. - -By default the HTTP request will reuse the current session. If you want the -request to have a different session you should set `session` to `null`. - -For POST requests the `uploadData` object must be provided. - -### `protocol.unregisterProtocol(scheme[, completion])` - -* `scheme` String -* `completion` Function (optional) - * `error` Error - -Unregisters the custom protocol of `scheme`. - -### `protocol.isProtocolHandled(scheme, callback)` - -* `scheme` String -* `callback` Function - * `error` Error - -The `callback` will be called with a boolean that indicates whether there is -already a handler for `scheme`. - -### `protocol.interceptFileProtocol(scheme, handler[, completion])` - -* `scheme` String -* `handler` Function - * `request` Object - * `url` String - * `referrer` String - * `method` String - * `uploadData` [UploadData[]](structures/upload-data.md) - * `callback` Function - * `filePath` String -* `completion` Function (optional) - * `error` Error - -Intercepts `scheme` protocol and uses `handler` as the protocol's new handler -which sends a file as a response. - -### `protocol.interceptStringProtocol(scheme, handler[, completion])` - -* `scheme` String -* `handler` Function - * `request` Object - * `url` String - * `referrer` String - * `method` String - * `uploadData` [UploadData[]](structures/upload-data.md) - * `callback` Function - * `data` String (optional) -* `completion` Function (optional) - * `error` Error - -Intercepts `scheme` protocol and uses `handler` as the protocol's new handler -which sends a `String` as a response. - -### `protocol.interceptBufferProtocol(scheme, handler[, completion])` - -* `scheme` String -* `handler` Function - * `request` Object - * `url` String - * `referrer` String - * `method` String - * `uploadData` [UploadData[]](structures/upload-data.md) - * `callback` Function - * `buffer` Buffer (optional) -* `completion` Function (optional) - * `error` Error - -Intercepts `scheme` protocol and uses `handler` as the protocol's new handler -which sends a `Buffer` as a response. - -### `protocol.interceptHttpProtocol(scheme, handler[, completion])` - -* `scheme` String -* `handler` Function - * `request` Object - * `url` String - * `referrer` String - * `method` String - * `uploadData` [UploadData[]](structures/upload-data.md) - * `callback` Function - * `redirectRequest` Object - * `url` String - * `method` String - * `session` Object (optional) - * `uploadData` Object (optional) - * `contentType` String - MIME type of the content. - * `data` String - Content to be sent. -* `completion` Function (optional) - * `error` Error - -Intercepts `scheme` protocol and uses `handler` as the protocol's new handler -which sends a new HTTP request as a response. - -### `protocol.uninterceptProtocol(scheme[, completion])` - -* `scheme` String -* `completion` Function (optional) - * `error` Error - -Remove the interceptor installed for `scheme` and restore its original handler. - -[net-error]: https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h -[file-system-api]: https://developer.mozilla.org/en-US/docs/Web/API/LocalFileSystem diff --git a/docs-translations/th-TH/api/remote.md b/docs-translations/th-TH/api/remote.md deleted file mode 100644 index 0bed3ad9b5..0000000000 --- a/docs-translations/th-TH/api/remote.md +++ /dev/null @@ -1,169 +0,0 @@ -# remote - -> Use main process modules from the renderer process. - -Process: [Renderer](../glossary.md#renderer-process) - -The `remote` module provides a simple way to do inter-process communication -(IPC) between the renderer process (web page) and the main process. - -In Electron, GUI-related modules (such as `dialog`, `menu` etc.) are only -available in the main process, not in the renderer process. In order to use them -from the renderer process, the `ipc` module is necessary to send inter-process -messages to the main process. With the `remote` module, you can invoke methods -of the main process object without explicitly sending inter-process messages, -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') -``` - -**Note:** For the reverse (access the renderer process from the main process), -you can use [webContents.executeJavascript](web-contents.md#contentsexecutejavascriptcode-usergesture-callback). - -## Remote Objects - -Each object (including functions) returned by the `remote` module represents an -object in the main process (we call it a remote object or remote function). -When you invoke methods of a remote object, call a remote function, or create -a new object with the remote constructor (function), you are actually sending -synchronous inter-process messages. - -In the example above, both `BrowserWindow` and `win` were remote objects and -`new BrowserWindow` didn't create a `BrowserWindow` object in the renderer -process. Instead, it created a `BrowserWindow` object in the main process and -returned the corresponding remote object in the renderer process, namely the -`win` object. - -**Note:** Only [enumerable properties][enumerable-properties] which are present -when the remote object is first referenced are accessible via remote. - -**Note:** Arrays and Buffers are copied over IPC when accessed via the `remote` -module. Modifying them in the renderer process does not modify them in the main -process and vice versa. - -## Lifetime of Remote Objects - -Electron makes sure that as long as the remote object in the renderer process -lives (in other words, has not been garbage collected), the corresponding object -in the main process will not be released. When the remote object has been -garbage collected, the corresponding object in the main process will be -dereferenced. - -If the remote object is leaked in the renderer process (e.g. stored in a map but -never freed), the corresponding object in the main process will also be leaked, -so you should be very careful not to leak remote objects. - -Primary value types like strings and numbers, however, are sent by copy. - -## Passing callbacks to the main process - -Code in the main process can accept callbacks from the renderer - for instance -the `remote` module - but you should be extremely careful when using this -feature. - -First, in order to avoid deadlocks, the callbacks passed to the main process -are called asynchronously. You should not expect the main process to -get the return value of the passed callbacks. - -For instance you can't use a function from the renderer process in an -`Array.map` called in the main process: - -```javascript -// main process mapNumbers.js -exports.withRendererCallback = (mapper) => { - return [1, 2, 3].map(mapper) -} - -exports.withLocalCallback = () => { - return [1, 2, 3].map(x => x + 1) -} -``` - -```javascript -// renderer process -const mapNumbers = require('electron').remote.require('./mapNumbers') -const withRendererCb = mapNumbers.withRendererCallback(x => x + 1) -const withLocalCb = mapNumbers.withLocalCallback() - -console.log(withRendererCb, withLocalCb) -// [undefined, undefined, undefined], [2, 3, 4] -``` - -As you can see, the renderer callback's synchronous return value was not as -expected, and didn't match the return value of an identical callback that lives -in the main process. - -Second, the callbacks passed to the main process will persist until the -main process garbage-collects them. - -For example, the following code seems innocent at first glance. It installs a -callback for the `close` event on a remote object: - -```javascript -require('electron').remote.getCurrentWindow().on('close', () => { - // window was closed... -}) -``` - -But remember the callback is referenced by the main process until you -explicitly uninstall it. If you do not, each time you reload your window the -callback will be installed again, leaking one callback for each restart. - -To make things worse, since the context of previously installed callbacks has -been released, exceptions will be raised in the main process when the `close` -event is emitted. - -To avoid this problem, ensure you clean up any references to renderer callbacks -passed to the main process. This involves cleaning up event handlers, or -ensuring the main process is explicitly told to deference callbacks that came -from a renderer process that is exiting. - -## Accessing built-in modules in the main process - -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 = require('electron').remote.app -console.log(app) -``` - -## Methods - -The `remote` module has the following methods: - -### `remote.require(module)` - -* `module` String - -Returns `any` - The object returned by `require(module)` in the main process. - -### `remote.getCurrentWindow()` - -Returns [`BrowserWindow`](browser-window.md) - The window to which this web page -belongs. - -### `remote.getCurrentWebContents()` - -Returns [`WebContents`](web-contents.md) - The web contents of this web page. - -### `remote.getGlobal(name)` - -* `name` String - -Returns `any` - The global variable of `name` (e.g. `global[name]`) in the main -process. - -## Properties - -### `remote.process` - -The `process` object in the main process. This is the same as -`remote.getGlobal('process')` but is cached. - -[rmi]: http://en.wikipedia.org/wiki/Java_remote_method_invocation -[enumerable-properties]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties diff --git a/docs-translations/th-TH/api/screen.md b/docs-translations/th-TH/api/screen.md deleted file mode 100644 index 9704f88134..0000000000 --- a/docs-translations/th-TH/api/screen.md +++ /dev/null @@ -1,122 +0,0 @@ -# screen - -> Retrieve information about screen size, displays, cursor position, etc. - -Process: [Main](../glossary.md#main-process), [Renderer](../glossary.md#renderer-process) - -You cannot require or use this module until the `ready` event of the `app` -module is emitted. - -`screen` is an [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter). - -**Note:** In the renderer / DevTools, `window.screen` is a reserved DOM -property, so writing `let {screen} = require('electron')` will not work. - -An example of creating a window that fills the whole screen: - -```javascript -const electron = require('electron') -const {app, BrowserWindow} = electron - -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: - -```javascript -const electron = require('electron') -const {app, BrowserWindow} = require('electron') - -let win - -app.on('ready', () => { - let displays = electron.screen.getAllDisplays() - let externalDisplay = displays.find((display) => { - return display.bounds.x !== 0 || display.bounds.y !== 0 - }) - - if (externalDisplay) { - win = new BrowserWindow({ - x: externalDisplay.bounds.x + 50, - y: externalDisplay.bounds.y + 50 - }) - win.loadURL('https://github.com') - } -}) -``` - -## Events - -The `screen` module emits the following events: - -### Event: 'display-added' - -Returns: - -* `event` Event -* `newDisplay` [Display](structures/display.md) - -Emitted when `newDisplay` has been added. - -### Event: 'display-removed' - -Returns: - -* `event` Event -* `oldDisplay` [Display](structures/display.md) - -Emitted when `oldDisplay` has been removed. - -### Event: 'display-metrics-changed' - -Returns: - -* `event` Event -* `display` [Display](structures/display.md) -* `changedMetrics` String[] - -Emitted when one or more metrics change in a `display`. The `changedMetrics` is -an array of strings that describe the changes. Possible changes are `bounds`, -`workArea`, `scaleFactor` and `rotation`. - -## Methods - -The `screen` module has the following methods: - -### `screen.getCursorScreenPoint()` - -Returns `Object`: - -* `x` Integer -* `y` Integer - -The current absolute position of the mouse pointer. - -### `screen.getPrimaryDisplay()` - -Returns [`Display`](structures/display.md) - The primary display. - -### `screen.getAllDisplays()` - -Returns [`Display[]`](structures/display.md) - An array of displays that are currently available. - -### `screen.getDisplayNearestPoint(point)` - -* `point` Object - * `x` Integer - * `y` Integer - -Returns [`Display`](structures/display.md) - The display nearest the specified point. - -### `screen.getDisplayMatching(rect)` - -* `rect` [Rectangle](structures/rectangle.md) - -Returns [`Display`](structures/display.md) - The display that most closely -intersects the provided bounds. diff --git a/docs-translations/th-TH/api/session.md b/docs-translations/th-TH/api/session.md deleted file mode 100644 index d50274e22e..0000000000 --- a/docs-translations/th-TH/api/session.md +++ /dev/null @@ -1,402 +0,0 @@ -# session - -> Manage browser sessions, cookies, cache, proxy settings, etc. - -Process: [Main](../glossary.md#main-process) - -The `session` module can be used to create new `Session` objects. - -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 {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 - -The `session` module has the following methods: - -### `session.fromPartition(partition[, options])` - -* `partition` String -* `options` Object - * `cache` Boolean - Whether to enable cache. - -Returns `Session` - A session instance from `partition` string. When there is an existing -`Session` with the same `partition`, it will be returned; othewise a new -`Session` instance will be created with `options`. - -If `partition` starts with `persist:`, the page will use a persistent session -available to all pages in the app with the same `partition`. if there is no -`persist:` prefix, the page will use an in-memory session. If the `partition` is -empty then default session of the app will be returned. - -To create a `Session` with `options`, you have to ensure the `Session` with the -`partition` has never been used before. There is no way to change the `options` -of an existing `Session` object. - -## Properties - -The `session` module has the following properties: - -### `session.defaultSession` - -A `Session` object, the default session object of the app. - -## Class: Session - -> Get and set properties of a session. - -Process: [Main](../glossary.md#main-process) - -You can create a `Session` object in the `session` module: - -```javascript -const {session} = require('electron') -const ses = session.fromPartition('persist:name') -console.log(ses.getUserAgent()) -``` - -### Instance Events - -The following events are available on instances of `Session`: - -#### Event: 'will-download' - -* `event` Event -* `item` [DownloadItem](download-item.md) -* `webContents` [WebContents](web-contents.md) - -Emitted when Electron is about to download `item` in `webContents`. - -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() - require('request')(item.getURL(), (data) => { - require('fs').writeFileSync('/somewhere', data) - }) -}) -``` - -### Instance Methods - -The following methods are available on instances of `Session`: - -#### `ses.getCacheSize(callback)` - -* `callback` Function - * `size` Integer - Cache size used in bytes. - -Returns the session's current cache size. - -#### `ses.clearCache(callback)` - -* `callback` Function - Called when operation is done - -Clears the session’s HTTP cache. - -#### `ses.clearStorageData([options, callback])` - -* `options` Object (optional) - * `origin` String - Should follow `window.location.origin`’s representation - `scheme://host:port`. - * `storages` String[] - The types of storages to clear, can contain: - `appcache`, `cookies`, `filesystem`, `indexdb`, `localstorage`, - `shadercache`, `websql`, `serviceworkers` - * `quotas` String[] - The types of quotas to clear, can contain: - `temporary`, `persistent`, `syncable`. -* `callback` Function (optional) - Called when operation is done. - -Clears the data of web storages. - -#### `ses.flushStorageData()` - -Writes any unwritten DOMStorage data to disk. - -#### `ses.setProxy(config, callback)` - -* `config` Object - * `pacScript` String - The URL associated with the PAC file. - * `proxyRules` String - Rules indicating which proxies to use. - * `proxyBypassRules` String - Rules indicating which URLs should - bypass the proxy settings. -* `callback` Function - Called when operation is done. - -Sets the proxy settings. - -When `pacScript` and `proxyRules` are provided together, the `proxyRules` -option is ignored and `pacScript` configuration is applied. - -The `proxyRules` has to follow the rules below: - -``` -proxyRules = schemeProxies[";"] -schemeProxies = ["="] -urlScheme = "http" | "https" | "ftp" | "socks" -proxyURIList = [","] -proxyURL = ["://"][":"] -``` - -For example: - -* `http=foopy:80;ftp=foopy2` - Use HTTP proxy `foopy:80` for `http://` URLs, and - HTTP proxy `foopy2:80` for `ftp://` URLs. -* `foopy:80` - Use HTTP proxy `foopy:80` for all URLs. -* `foopy:80,bar,direct://` - Use HTTP proxy `foopy:80` for all URLs, failing - over to `bar` if `foopy:80` is unavailable, and after that using no proxy. -* `socks4://foopy` - Use SOCKS v4 proxy `foopy:1080` for all URLs. -* `http=foopy,socks5://bar.com` - Use HTTP proxy `foopy` for http URLs, and fail - over to the SOCKS5 proxy `bar.com` if `foopy` is unavailable. -* `http=foopy,direct://` - Use HTTP proxy `foopy` for http URLs, and use no - proxy if `foopy` is unavailable. -* `http=foopy;socks=foopy2` - Use HTTP proxy `foopy` for http URLs, and use - `socks4://foopy2` for all other URLs. - -The `proxyBypassRules` is a comma separated list of rules described below: - -* `[ URL_SCHEME "://" ] HOSTNAME_PATTERN [ ":" ]` - - Match all hostnames that match the pattern HOSTNAME_PATTERN. - - Examples: - "foobar.com", "*foobar.com", "*.foobar.com", "*foobar.com:99", - "https://x.*.y.com:99" - - * `"." HOSTNAME_SUFFIX_PATTERN [ ":" PORT ]` - - Match a particular domain suffix. - - Examples: - ".google.com", ".com", "http://.google.com" - -* `[ SCHEME "://" ] IP_LITERAL [ ":" PORT ]` - - Match URLs which are IP address literals. - - Examples: - "127.0.1", "[0:0::1]", "[::1]", "http://[::1]:99" - -* `IP_LITERAL "/" PREFIX_LENGHT_IN_BITS` - - Match any URL that is to an IP literal that falls between the - given range. IP range is specified using CIDR notation. - - Examples: - "192.168.1.1/16", "fefe:13::abc/33". - -* `` - - Match local addresses. The meaning of `` is whether the - host matches one of: "127.0.0.1", "::1", "localhost". - -#### `ses.resolveProxy(url, callback)` - -* `url` URL -* `callback` Function - * `proxy` Object - -Resolves the proxy information for `url`. The `callback` will be called with -`callback(proxy)` when the request is performed. - -#### `ses.setDownloadPath(path)` - -* `path` String - The download location - -Sets download saving directory. By default, the download directory will be the -`Downloads` under the respective app folder. - -#### `ses.enableNetworkEmulation(options)` - -* `options` Object - * `offline` Boolean (optional) - Whether to emulate network outage. Defaults - to false. - * `latency` Double (optional) - RTT in ms. Defaults to 0 which will disable - latency throttling. - * `downloadThroughput` Double (optional) - Download rate in Bps. Defaults to 0 - which will disable download throttling. - * `uploadThroughput` Double (optional) - Upload rate in Bps. Defaults to 0 - which will disable upload throttling. - -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 -}) - -// To emulate a network outage. -window.webContents.session.enableNetworkEmulation({offline: true}) -``` - -#### `ses.disableNetworkEmulation()` - -Disables any network emulation already active for the `session`. Resets to -the original network configuration. - -#### `ses.setCertificateVerifyProc(proc)` - -* `proc` Function - * `hostname` String - * `certificate` [Certificate](structures/certificate.md) - * `callback` Function - * `isTrusted` Boolean - Determines if the certificate should be trusted - -Sets the certificate verify proc for `session`, the `proc` will be called with -`proc(hostname, certificate, callback)` whenever a server certificate -verification is requested. Calling `callback(true)` accepts the certificate, -calling `callback(false)` rejects it. - -Calling `setCertificateVerifyProc(null)` will revert back to default certificate -verify proc. - -```javascript -const {BrowserWindow} = require('electron') -let win = new BrowserWindow() - -win.webContents.session.setCertificateVerifyProc((hostname, cert, callback) => { - callback(hostname === 'github.com') -}) -``` - -#### `ses.setPermissionRequestHandler(handler)` - -* `handler` Function - * `webContents` Object - [WebContents](web-contents.md) requesting the permission. - * `permission` String - Enum of 'media', 'geolocation', 'notifications', 'midiSysex', - 'pointerLock', 'fullscreen', 'openExternal'. - * `callback` Function - * `permissionGranted` Boolean - Allow or deny the permission - -Sets the handler which can be used to respond to permission requests for the `session`. -Calling `callback(true)` will allow the permission and `callback(false)` will reject it. - -```javascript -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) -}) -``` - -#### `ses.clearHostResolverCache([callback])` - -* `callback` Function (optional) - Called when operation is done. - -Clears the host resolver cache. - -#### `ses.allowNTLMCredentialsForDomains(domains)` - -* `domains` String - A comma-seperated list of servers for which - integrated authentication is enabled. - -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') - -// consider all urls for integrated authentication. -session.defaultSession.allowNTLMCredentialsForDomains('*') -``` - -#### `ses.setUserAgent(userAgent[, acceptLanguages])` - -* `userAgent` String -* `acceptLanguages` String (optional) - -Overrides the `userAgent` and `acceptLanguages` for this session. - -The `acceptLanguages` must a comma separated ordered list of language codes, for -example `"en-US,fr,de,ko,zh-CN,ja"`. - -This doesn't affect existing `WebContents`, and each `WebContents` can use -`webContents.setUserAgent` to override the session-wide user agent. - -#### `ses.getUserAgent()` - -Returns `String` - The user agent for this session. - -#### `ses.getBlobData(identifier, callback)` - -* `identifier` String - Valid UUID. -* `callback` Function - * `result` Buffer - Blob data. - -Returns `Blob` - The blob data associated with the `identifier`. - -#### `ses.createInterruptedDownload(options)` - -* `options` Object - * `path` String - Absolute path of the download. - * `urlChain` String[] - Complete URL chain for the download. - * `mimeType` String (optional) - * `offset` Integer - Start range for the download. - * `length` Integer - Total length of the download. - * `lastModified` String - Last-Modified header value. - * `eTag` String - ETag header value. - * `startTime` Double (optional) - Time when download was started in - number of seconds since UNIX epoch. - -Allows resuming `cancelled` or `interrupted` downloads from previous `Session`. -The API will generate a [DownloadItem](download-item.md) that can be accessed with the [will-download](#event-will-download) -event. The [DownloadItem](download-item.md) will not have any `WebContents` associated with it and -the initial state will be `interrupted`. The download will start only when the -`resume` API is called on the [DownloadItem](download-item.md). - -#### `ses.clearAuthCache(options[, callback])` - -* `options` ([RemovePassword](structures/remove-password.md) | [RemoveClientCertificate](structures/remove-client-certificate.md)) -* `callback` Function (optional) - Called when operation is done - -Clears the session’s HTTP authentication cache. - -### Instance Properties - -The following properties are available on instances of `Session`: - -#### `ses.cookies` - -A Cookies object for this session. - -#### `ses.webRequest` - -A WebRequest object for this session. - -#### `ses.protocol` - -A Protocol object (an instance of [protocol](protocol.md) module) for this session. - -```javascript -const {app, session} = require('electron') -const path = require('path') - -app.on('ready', function () { - const protocol = session.fromPartition('some-partition').protocol - protocol.registerFileProtocol('atom', function (request, callback) { - var url = request.url.substr(7) - callback({path: path.normalize(`${__dirname}/${url}`)}) - }, function (error) { - if (error) console.error('Failed to register protocol') - }) -}) -``` diff --git a/docs-translations/th-TH/api/shell.md b/docs-translations/th-TH/api/shell.md deleted file mode 100644 index ae30de76f8..0000000000 --- a/docs-translations/th-TH/api/shell.md +++ /dev/null @@ -1,86 +0,0 @@ -# shell - -> Manage files and URLs using their default applications. - -Process: [Main](../glossary.md#main-process), [Renderer](../glossary.md#renderer-process) - -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') - -shell.openExternal('https://github.com') -``` - -## Methods - -The `shell` module has the following methods: - -### `shell.showItemInFolder(fullPath)` - -* `fullPath` String - -Returns `Boolean` - Whether the item was successfully shown - -Show the given file in a file manager. If possible, select the file. - -### `shell.openItem(fullPath)` - -* `fullPath` String - -Returns `Boolean` - Whether the item was successfully opened. - -Open the given file in the desktop's default manner. - -### `shell.openExternal(url[, options, callback])` - -* `url` String -* `options` Object (optional) _macOS_ - * `activate` Boolean - `true` to bring the opened application to the - foreground. The default is `true`. -* `callback` Function (optional) - If specified will perform the open asynchronously. _macOS_ - * `error` Error - -Returns `Boolean` - Whether an application was available to open the URL. -If callback is specified, always returns true. - -Open the given external protocol URL in the desktop's default manner. (For -example, mailto: URLs in the user's default mail agent). - -### `shell.moveItemToTrash(fullPath)` - -* `fullPath` String - -Returns `Boolean` - Whether the item was successfully moved to the trash - -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 following: - * `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` [ShortcutDetails](structures/shortcut-details.md) - -Returns `Boolean` - Whether the shortcut was created successfully - -Creates or updates a shortcut link at `shortcutPath`. - -### `shell.readShortcutLink(shortcutPath)` _Windows_ - -* `shortcutPath` String - -Returns [`ShortcutDetails`](structures/shortcut-details.md) - -Resolves the shortcut link at `shortcutPath`. - -An exception will be thrown when any error happens. diff --git a/docs-translations/th-TH/api/structures/bluetooth-device.md b/docs-translations/th-TH/api/structures/bluetooth-device.md deleted file mode 100644 index 33d3bb51f9..0000000000 --- a/docs-translations/th-TH/api/structures/bluetooth-device.md +++ /dev/null @@ -1,4 +0,0 @@ -# BluetoothDevice Object - -* `deviceName` String -* `deviceId` String diff --git a/docs-translations/th-TH/api/structures/certificate-principal.md b/docs-translations/th-TH/api/structures/certificate-principal.md deleted file mode 100644 index 12c46382aa..0000000000 --- a/docs-translations/th-TH/api/structures/certificate-principal.md +++ /dev/null @@ -1,8 +0,0 @@ -# CertificatePrincipal Object - -* `commonName` String - Common Name -* `organizations` String[] - Organization names -* `organizationUnits` String[] - Organization Unit names -* `locality` String - Locality -* `state` String - State or province -* `country` String - Country or region diff --git a/docs-translations/th-TH/api/structures/certificate.md b/docs-translations/th-TH/api/structures/certificate.md deleted file mode 100644 index 3c521b2d36..0000000000 --- a/docs-translations/th-TH/api/structures/certificate.md +++ /dev/null @@ -1,12 +0,0 @@ -# Certificate Object - -* `data` String - PEM encoded data -* `issuer` [CertificatePrincipal](certificate-principal.md) - Issuer principal -* `issuerName` String - Issuer's Common Name -* `issuerCert` Certificate - Issuer certificate (if not self-signed) -* `subject` [CertificatePrincipal](certificate-principal.md) - Subject principal -* `subjectName` String - Subject's Common Name -* `serialNumber` String - Hex value represented string -* `validStart` Number - Start date of the certificate being valid in seconds -* `validExpiry` Number - End date of the certificate being valid in seconds -* `fingerprint` String - Fingerprint of the certificate diff --git a/docs-translations/th-TH/api/structures/cookie.md b/docs-translations/th-TH/api/structures/cookie.md deleted file mode 100644 index 677840cf82..0000000000 --- a/docs-translations/th-TH/api/structures/cookie.md +++ /dev/null @@ -1,14 +0,0 @@ -# Cookie Object - -* `name` String - The name of the cookie. -* `value` String - The value of the cookie. -* `domain` String (optional) - The domain of the cookie. -* `hostOnly` Boolean (optional) - Whether the cookie is a host-only cookie. -* `path` String (optional) - The path of the cookie. -* `secure` Boolean (optional) - Whether the cookie is marked as secure. -* `httpOnly` Boolean (optional) - Whether the cookie is marked as HTTP only. -* `session` Boolean (optional) - Whether the cookie is a session cookie or a persistent - cookie with an expiration date. -* `expirationDate` Double (optional) - The expiration date of the cookie as - the number of seconds since the UNIX epoch. Not provided for session - cookies. diff --git a/docs-translations/th-TH/api/structures/crash-report.md b/docs-translations/th-TH/api/structures/crash-report.md deleted file mode 100644 index b26d4adeca..0000000000 --- a/docs-translations/th-TH/api/structures/crash-report.md +++ /dev/null @@ -1,4 +0,0 @@ -# CrashReport Object - -* `date` String -* `ID` Integer \ No newline at end of file diff --git a/docs-translations/th-TH/api/structures/desktop-capturer-source.md b/docs-translations/th-TH/api/structures/desktop-capturer-source.md deleted file mode 100644 index 3ebe4ce49c..0000000000 --- a/docs-translations/th-TH/api/structures/desktop-capturer-source.md +++ /dev/null @@ -1,14 +0,0 @@ -# DesktopCapturerSource Object - -* `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 - `thumbnailSize` specified in the `options` passed to - `desktopCapturer.getSources`. The actual size depends on the scale of the - screen or window. diff --git a/docs-translations/th-TH/api/structures/display.md b/docs-translations/th-TH/api/structures/display.md deleted file mode 100644 index d702b35a1b..0000000000 --- a/docs-translations/th-TH/api/structures/display.md +++ /dev/null @@ -1,19 +0,0 @@ -# Display Object - -* `id` Number - Unique identifier associated with the display. -* `rotation` Number - Can be 0, 90, 180, 270, represents screen rotation in - clock-wise degrees. -* `scaleFactor` Number - Output device's pixel scale factor. -* `touchSupport` String - Can be `available`, `unavailable`, `unknown`. -* `bounds` [Rectangle](rectangle.md) -* `size` Object - * `height` Number - * `width` Number -* `workArea` [Rectangle](rectangle.md) -* `workAreaSize` Object - * `height` Number - * `width` Number - -The `Display` object represents a physical display connected to the system. A -fake `Display` may exist on a headless system, or a `Display` may correspond to -a remote, virtual display. diff --git a/docs-translations/th-TH/api/structures/file-filter.md b/docs-translations/th-TH/api/structures/file-filter.md deleted file mode 100644 index 014350a60f..0000000000 --- a/docs-translations/th-TH/api/structures/file-filter.md +++ /dev/null @@ -1,4 +0,0 @@ -# FileFilter Object - -* `name` String -* `extensions` String[] diff --git a/docs-translations/th-TH/api/structures/jump-list-category.md b/docs-translations/th-TH/api/structures/jump-list-category.md deleted file mode 100644 index 07627e78c9..0000000000 --- a/docs-translations/th-TH/api/structures/jump-list-category.md +++ /dev/null @@ -1,21 +0,0 @@ -# JumpListCategory Object - -* `type` String (optional) - One of the following: - * `tasks` - Items in this category will be placed into the standard `Tasks` - category. There can be only one such category, and it will always be - displayed at the bottom of the Jump List. - * `frequent` - Displays a list of files frequently opened by the app, the - name of the category and its items are set by Windows. - * `recent` - Displays a list of files recently opened by the app, the name - of the category and its items are set by Windows. Items may be added to - this category indirectly using `app.addRecentDocument(path)`. - * `custom` - Displays tasks or file links, `name` must be set by the app. -* `name` String (optional) - Must be set if `type` is `custom`, otherwise it should be - omitted. -* `items` JumpListItem[] (optional) - Array of [`JumpListItem`](jump-list-item.md) objects if `type` is `tasks` or - `custom`, otherwise it should be omitted. - -**Note:** If a `JumpListCategory` object has neither the `type` nor the `name` -property set then its `type` is assumed to be `tasks`. If the `name` property -is set but the `type` property is omitted then the `type` is assumed to be -`custom`. diff --git a/docs-translations/th-TH/api/structures/jump-list-item.md b/docs-translations/th-TH/api/structures/jump-list-item.md deleted file mode 100644 index f17d72e0a4..0000000000 --- a/docs-translations/th-TH/api/structures/jump-list-item.md +++ /dev/null @@ -1,28 +0,0 @@ -# JumpListItem Object - -* `type` String (optional) - One of the following: - * `task` - A task will launch an app with specific arguments. - * `separator` - Can be used to separate items in the standard `Tasks` - category. - * `file` - A file link will open a file using the app that created the - Jump List, for this to work the app must be registered as a handler for - the file type (though it doesn't have to be the default handler). -* `path` String (optional) - Path of the file to open, should only be set if `type` is - `file`. -* `program` String (optional) - Path of the program to execute, usually you should - specify `process.execPath` which opens the current program. Should only be - set if `type` is `task`. -* `args` String (optional) - The command line arguments when `program` is executed. Should - only be set if `type` is `task`. -* `title` String (optional) - The text to be displayed for the item in the Jump List. - Should only be set if `type` is `task`. -* `description` String (optional) - Description of the task (displayed in a tooltip). - Should only be set if `type` is `task`. -* `iconPath` String (optional) - The absolute path to an icon to be displayed in a - Jump List, which can be an arbitrary resource file that contains an icon - (e.g. `.ico`, `.exe`, `.dll`). You can usually specify `process.execPath` to - show the program icon. -* `iconIndex` Number (optional) - The index of the icon in the resource file. If a - resource file contains multiple icons this value can be used to specify the - zero-based index of the icon that should be displayed for this task. If a - resource file contains only one icon, this property should be set to zero. diff --git a/docs-translations/th-TH/api/structures/memory-usage-details.md b/docs-translations/th-TH/api/structures/memory-usage-details.md deleted file mode 100644 index 228a445290..0000000000 --- a/docs-translations/th-TH/api/structures/memory-usage-details.md +++ /dev/null @@ -1,8 +0,0 @@ -# MemoryUsageDetails Object - -* `count` Number -* `size` Number -* `liveSize` Number -* `decodedSize` Number -* `purgedSize` Number -* `purgeableSize` Number diff --git a/docs-translations/th-TH/api/structures/mime-typed-buffer.md b/docs-translations/th-TH/api/structures/mime-typed-buffer.md deleted file mode 100644 index dc1a20d28f..0000000000 --- a/docs-translations/th-TH/api/structures/mime-typed-buffer.md +++ /dev/null @@ -1,4 +0,0 @@ -# MimeTypedBuffer Object - -* `mimeType` String - The mimeType of the Buffer that you are sending -* `buffer` Buffer - The actual Buffer content diff --git a/docs-translations/th-TH/api/structures/rectangle.md b/docs-translations/th-TH/api/structures/rectangle.md deleted file mode 100644 index 0cd000699e..0000000000 --- a/docs-translations/th-TH/api/structures/rectangle.md +++ /dev/null @@ -1,6 +0,0 @@ -# Rectangle Object - -* `x` Number - The x coordinate of the origin of the rectangle -* `y` Number - The y coordinate of the origin of the rectangle -* `width` Number -* `height` Number diff --git a/docs-translations/th-TH/api/structures/remove-client-certificate.md b/docs-translations/th-TH/api/structures/remove-client-certificate.md deleted file mode 100644 index 7ec853f163..0000000000 --- a/docs-translations/th-TH/api/structures/remove-client-certificate.md +++ /dev/null @@ -1,5 +0,0 @@ -# RemoveClientCertificate Object - -* `type` String - `clientCertificate`. -* `origin` String - Origin of the server whose associated client certificate - must be removed from the cache. diff --git a/docs-translations/th-TH/api/structures/remove-password.md b/docs-translations/th-TH/api/structures/remove-password.md deleted file mode 100644 index 28a9ed8ae1..0000000000 --- a/docs-translations/th-TH/api/structures/remove-password.md +++ /dev/null @@ -1,15 +0,0 @@ -# RemovePassword Object - -* `type` String - `password`. -* `origin` String (optional) - When provided, the authentication info - related to the origin will only be removed otherwise the entire cache - will be cleared. -* `scheme` String (optional) - Scheme of the authentication. - Can be `basic`, `digest`, `ntlm`, `negotiate`. Must be provided if - removing by `origin`. -* `realm` String (optional) - Realm of the authentication. Must be provided if - removing by `origin`. -* `username` String (optional) - Credentials of the authentication. Must be - provided if removing by `origin`. -* `password` String (optional) - Credentials of the authentication. Must be - provided if removing by `origin`. diff --git a/docs-translations/th-TH/api/structures/shortcut-details.md b/docs-translations/th-TH/api/structures/shortcut-details.md deleted file mode 100644 index e7b272d099..0000000000 --- a/docs-translations/th-TH/api/structures/shortcut-details.md +++ /dev/null @@ -1,15 +0,0 @@ -# ShortcutDetails Object - -* `target` String - The target to launch from this shortcut. -* `cwd` String (optional) - The working directory. 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` Number (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. diff --git a/docs-translations/th-TH/api/structures/task.md b/docs-translations/th-TH/api/structures/task.md deleted file mode 100644 index 61a28de879..0000000000 --- a/docs-translations/th-TH/api/structures/task.md +++ /dev/null @@ -1,14 +0,0 @@ -# Task Object - -* `program` String - Path of the program to execute, usually you should - specify `process.execPath` which opens the current program. -* `arguments` String - The command line arguments when `program` is - executed. -* `title` String - The string to be displayed in a JumpList. -* `description` String - Description of this task. -* `iconPath` String - The absolute path to an icon to be displayed in a - JumpList, which can be an arbitrary resource file that contains an icon. You - can usually specify `process.execPath` to show the icon of the program. -* `iconIndex` Number - The icon index in the icon file. If an icon file - consists of two or more icons, set this value to identify the icon. If an - icon file consists of one icon, this value is 0. diff --git a/docs-translations/th-TH/api/structures/thumbar-button.md b/docs-translations/th-TH/api/structures/thumbar-button.md deleted file mode 100644 index 259195852a..0000000000 --- a/docs-translations/th-TH/api/structures/thumbar-button.md +++ /dev/null @@ -1,21 +0,0 @@ -# ThumbarButton Object - -* `icon` [NativeImage](../native-image.md) - The icon showing in thumbnail - toolbar. -* `click` Function -* `tooltip` String (optional) - The text of the button's tooltip. -* `flags` String[] (optional) - Control specific states and behaviors of the - button. By default, it is `['enabled']`. - -The `flags` is an array that can include following `String`s: - -* `enabled` - The button is active and available to the user. -* `disabled` - The button is disabled. It is present, but has a visual state - indicating it will not respond to user action. -* `dismissonclick` - When the button is clicked, the thumbnail window closes - immediately. -* `nobackground` - Do not draw a button border, use only the image. -* `hidden` - The button is not shown to the user. -* `noninteractive` - The button is enabled but not interactive; no pressed - button state is drawn. This value is intended for instances where the button - is used in a notification. diff --git a/docs-translations/th-TH/api/structures/upload-blob.md b/docs-translations/th-TH/api/structures/upload-blob.md deleted file mode 100644 index be93cacb49..0000000000 --- a/docs-translations/th-TH/api/structures/upload-blob.md +++ /dev/null @@ -1,4 +0,0 @@ -# UploadBlob Object - -* `type` String - `blob`. -* `blobUUID` String - UUID of blob data to upload. diff --git a/docs-translations/th-TH/api/structures/upload-data.md b/docs-translations/th-TH/api/structures/upload-data.md deleted file mode 100644 index 8e5c07725a..0000000000 --- a/docs-translations/th-TH/api/structures/upload-data.md +++ /dev/null @@ -1,6 +0,0 @@ -# UploadData Object - -* `bytes` Buffer - Content being sent. -* `file` String - Path of file being uploaded. -* `blobUUID` String - UUID of blob data. Use [ses.getBlobData](../session.md#sesgetblobdataidentifier-callback) method - to retrieve the data. diff --git a/docs-translations/th-TH/api/structures/upload-file-system.md b/docs-translations/th-TH/api/structures/upload-file-system.md deleted file mode 100644 index d62b0a8ba7..0000000000 --- a/docs-translations/th-TH/api/structures/upload-file-system.md +++ /dev/null @@ -1,9 +0,0 @@ -# UploadFileSystem Object - -* `type` String - `fileSystem`. -* `filsSystemURL` String - FileSystem url to read data for upload. -* `offset` Integer - Defaults to `0`. -* `length` Integer - Number of bytes to read from `offset`. - Defaults to `0`. -* `modificationTime` Double - Last Modification time in - number of seconds sine the UNIX epoch. diff --git a/docs-translations/th-TH/api/structures/upload-file.md b/docs-translations/th-TH/api/structures/upload-file.md deleted file mode 100644 index 8a21973014..0000000000 --- a/docs-translations/th-TH/api/structures/upload-file.md +++ /dev/null @@ -1,9 +0,0 @@ -# UploadFile Object - -* `type` String - `file`. -* `filePath` String - Path of file to be uploaded. -* `offset` Integer - Defaults to `0`. -* `length` Integer - Number of bytes to read from `offset`. - Defaults to `0`. -* `modificationTime` Double - Last Modification time in - number of seconds sine the UNIX epoch. diff --git a/docs-translations/th-TH/api/structures/upload-raw-data.md b/docs-translations/th-TH/api/structures/upload-raw-data.md deleted file mode 100644 index 4fe162311f..0000000000 --- a/docs-translations/th-TH/api/structures/upload-raw-data.md +++ /dev/null @@ -1,4 +0,0 @@ -# UploadRawData Object - -* `type` String - `rawData`. -* `bytes` Buffer - Data to be uploaded. diff --git a/docs-translations/th-TH/api/synopsis.md b/docs-translations/th-TH/api/synopsis.md deleted file mode 100644 index 71ca497cc2..0000000000 --- a/docs-translations/th-TH/api/synopsis.md +++ /dev/null @@ -1,95 +0,0 @@ -# Synopsis - -> How to use Node.js and Electron APIs. - -All of [Node.js's built-in modules](https://nodejs.org/api/) are available in -Electron and third-party node modules also fully supported as well (including -the [native modules](../tutorial/using-native-node-modules.md)). - -Electron also provides some extra built-in modules for developing native -desktop applications. Some modules are only available in the main process, some -are only available in the renderer process (web page), and some can be used in -both processes. - -The basic rule is: if a module is [GUI][gui] or low-level system related, then -it should be only available in the main process. You need to be familiar with -the concept of [main process vs. renderer process](../tutorial/quick-start.md#main-process) -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 - -app.on('ready', () => { - 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 -extra ability to use node modules: - -```html - - - - - - -``` - -To run your app, read [Run your app](../tutorial/quick-start.md#run-your-app). - -## Destructuring assignment - -As of 0.37, you can use -[destructuring assignment][destructuring-assignment] to make it easier to use -built-in modules. - -```javascript -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 - -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 -let win - -app.on('ready', () => { - win = new BrowserWindow() - win.loadURL('https://github.com') -}) -``` - -[gui]: https://en.wikipedia.org/wiki/Graphical_user_interface -[destructuring-assignment]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment diff --git a/docs-translations/th-TH/api/system-preferences.md b/docs-translations/th-TH/api/system-preferences.md deleted file mode 100644 index 243c4a04af..0000000000 --- a/docs-translations/th-TH/api/system-preferences.md +++ /dev/null @@ -1,239 +0,0 @@ -# systemPreferences - -> Get system preferences. - -Process: [Main](../glossary.md#main-process) - -```javascript -const {systemPreferences} = require('electron') -console.log(systemPreferences.isDarkMode()) -``` - -## Events - -The `systemPreferences` object emits the following events: - -### Event: 'accent-color-changed' _Windows_ - -Returns: - -* `event` Event -* `newColor` String - The new RGBA color the user assigned to be their system - accent color. - -### Event: 'color-changed' _Windows_ - -Returns: - -* `event` Event - -### Event: 'inverted-color-scheme-changed' _Windows_ - -Returns: - -* `event` Event -* `invertedColorScheme` Boolean - `true` if an inverted color scheme, such as - a high contrast theme, is being used, `false` otherwise. - -## Methods - -### `systemPreferences.isDarkMode()` _macOS_ - -Returns `Boolean` - Whether the system is in Dark Mode. - -### `systemPreferences.isSwipeTrackingFromScrollEventsEnabled()` _macOS_ - -Returns `Boolean` - Whether the Swipe between pages setting is on. - -### `systemPreferences.postNotification(event, userInfo)` _macOS_ - -* `event` String -* `userInfo` Object - -Posts `event` as native notifications of macOS. The `userInfo` is an Object -that contains the user information dictionary sent along with the notification. - -### `systemPreferences.postLocalNotification(event, userInfo)` _macOS_ - -* `event` String -* `userInfo` Object - -Posts `event` as native notifications of macOS. The `userInfo` is an Object -that contains the user information dictionary sent along with the notification. - -### `systemPreferences.subscribeNotification(event, callback)` _macOS_ - -* `event` String -* `callback` Function - * `event` String - * `userInfo` Object - -Subscribes to native notifications of macOS, `callback` will be called with -`callback(event, userInfo)` when the corresponding `event` happens. The -`userInfo` is an Object that contains the user information dictionary sent -along with the notification. - -The `id` of the subscriber is returned, which can be used to unsubscribe the -`event`. - -Under the hood this API subscribes to `NSDistributedNotificationCenter`, -example values of `event` are: - -* `AppleInterfaceThemeChangedNotification` -* `AppleAquaColorVariantChanged` -* `AppleColorPreferencesChangedNotification` -* `AppleShowScrollBarsSettingChanged` - -### `systemPreferences.unsubscribeNotification(id)` _macOS_ - -* `id` Integer - -Removes the subscriber with `id`. - -### `systemPreferences.subscribeLocalNotification(event, callback)` _macOS_ - -* `event` String -* `callback` Function - * `event` String - * `userInfo` Object - -Same as `subscribeNotification`, but uses `NSNotificationCenter` for local defaults. -This is necessary for events such as `NSUserDefaultsDidChangeNotification` - -### `systemPreferences.unsubscribeLocalNotification(id)` _macOS_ - -* `id` Integer - -Same as `unsubscribeNotification`, but removes the subscriber from `NSNotificationCenter`. - -### `systemPreferences.getUserDefault(key, type)` _macOS_ - -* `key` String -* `type` String - Can be `string`, `boolean`, `integer`, `float`, `double`, - `url`, `array`, `dictionary` - -Get the value of `key` in system preferences. - -This API uses `NSUserDefaults` on macOS. Some popular `key` and `type`s are: - -* `AppleInterfaceStyle`: `string` -* `AppleAquaColorVariant`: `integer` -* `AppleHighlightColor`: `string` -* `AppleShowScrollBars`: `string` -* `NSNavRecentPlaces`: `array` -* `NSPreferredWebServices`: `dictionary` -* `NSUserDictionaryReplacementItems`: `array` - -### `systemPreferences.setUserDefault(key, type, value)` _macOS_ - -* `key` String -* `type` String - See [`getUserDefault`][#systempreferencesgetuserdefaultkey-type-macos] -* `value` String - -Set the value of `key` in system preferences. - -Note that `type` should match actual type of `value`. An exception is thrown -if they don't. - -This API uses `NSUserDefaults` on macOS. Some popular `key` and `type`s are: - -* `ApplePressAndHoldEnabled`: `boolean` - -### `systemPreferences.isAeroGlassEnabled()` _Windows_ - -This method returns `true` if [DWM composition][dwm-composition] (Aero Glass) is -enabled, and `false` otherwise. - -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 -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 -} - -// Create the window. -let win = new BrowserWindow(browserOptions) - -// Navigate. -if (browserOptions.transparent) { - win.loadURL(`file://${__dirname}/index.html`) -} else { - // No transparency, so we load a fallback that uses basic styles. - win.loadURL(`file://${__dirname}/fallback.html`) -} -``` - -[dwm-composition]:https://msdn.microsoft.com/en-us/library/windows/desktop/aa969540.aspx - -### `systemPreferences.getAccentColor()` _Windows_ - -Returns `String` - The users current system wide accent color preference in RGBA -hexadecimal form. - -```js -const color = systemPreferences.getAccentColor() // `"aabbccdd"` -const red = color.substr(0, 2) // "aa" -const green = color.substr(2, 2) // "bb" -const blue = color.substr(4, 2) // "cc" -const alpha = color.substr(6, 2) // "dd" -``` - -### `systemPreferences.getColor(color)` _Windows_ - -* `color` String - One of the following values: - * `3d-dark-shadow` - Dark shadow for three-dimensional display elements. - * `3d-face` - Face color for three-dimensional display elements and for dialog - box backgrounds. - * `3d-highlight` - Highlight color for three-dimensional display elements. - * `3d-light` - Light color for three-dimensional display elements. - * `3d-shadow` - Shadow color for three-dimensional display elements. - * `active-border` - Active window border. - * `active-caption` - Active window title bar. Specifies the left side color in - the color gradient of an active window's title bar if the gradient effect is - enabled. - * `active-caption-gradient` - Right side color in the color gradient of an - active window's title bar. - * `app-workspace` - Background color of multiple document interface (MDI) - applications. - * `button-text` - Text on push buttons. - * `caption-text` - Text in caption, size box, and scroll bar arrow box. - * `desktop` - Desktop background color. - * `disabled-text` - Grayed (disabled) text. - * `highlight` - Item(s) selected in a control. - * `highlight-text` - Text of item(s) selected in a control. - * `hotlight` - Color for a hyperlink or hot-tracked item. - * `inactive-border` - Inactive window border. - * `inactive-caption` - Inactive window caption. Specifies the left side color - in the color gradient of an inactive window's title bar if the gradient - effect is enabled. - * `inactive-caption-gradient` - Right side color in the color gradient of an - inactive window's title bar. - * `inactive-caption-text` - Color of text in an inactive caption. - * `info-background` - Background color for tooltip controls. - * `info-text` - Text color for tooltip controls. - * `menu` - Menu background. - * `menu-highlight` - The color used to highlight menu items when the menu - appears as a flat menu. - * `menubar` - The background color for the menu bar when menus appear as flat - menus. - * `menu-text` - Text in menus. - * `scrollbar` - Scroll bar gray area. - * `window` - Window background. - * `window-frame` - Window frame. - * `window-text` - Text in windows. - -Returns `String` - The system color setting in RGB hexadecimal form (`#ABCDEF`). -See the [Windows docs][windows-colors] for more details. - -### `systemPreferences.isInvertedColorScheme()` _Windows_ - -Returns `Boolean` - `true` if an inverted color scheme, such as a high contrast -theme, is active, `false` otherwise. - -[windows-colors]:https://msdn.microsoft.com/en-us/library/windows/desktop/ms724371(v=vs.85).aspx diff --git a/docs-translations/th-TH/api/tray.md b/docs-translations/th-TH/api/tray.md deleted file mode 100644 index 0be9702986..0000000000 --- a/docs-translations/th-TH/api/tray.md +++ /dev/null @@ -1,247 +0,0 @@ -## Class: Tray - -> Add icons and context menus to the system's notification area. - -Process: [Main](../glossary.md#main-process) - -`Tray` is an [EventEmitter][event-emitter]. - -```javascript -const {app, Menu, Tray} = require('electron') - -let tray = null -app.on('ready', () => { - tray = new Tray('/path/to/my/icon') - const contextMenu = Menu.buildFromTemplate([ - {label: 'Item1', type: 'radio'}, - {label: 'Item2', type: 'radio'}, - {label: 'Item3', type: 'radio', checked: true}, - {label: 'Item4', type: 'radio'} - ]) - tray.setToolTip('This is my application.') - tray.setContextMenu(contextMenu) -}) -``` - -__Platform limitations:__ - -* On Linux the app indicator will be used if it is supported, otherwise - `GtkStatusIcon` will be used instead. -* On Linux distributions that only have app indicator support, you have to - install `libappindicator1` to make the tray icon work. -* App indicator will only be shown when it has a context menu. -* When app indicator is used on Linux, the `click` event is ignored. -* On Linux in order for changes made to individual `MenuItem`s to take effect, - you have to call `setContextMenu` again. For example: - -```javascript -const {app, Menu, Tray} = require('electron') - -let appIcon = null -app.on('ready', () => { - 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[1].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. - -If you want to keep exact same behaviors on all platforms, you should not -rely on the `click` event and always attach a context menu to the tray icon. - - -### `new Tray(image)` - -* `image` ([NativeImage](native-image.md) | String) - -Creates a new tray icon associated with the `image`. - -### Instance Events - -The `Tray` module emits the following events: - -#### Event: 'click' - -* `event` Event - * `altKey` Boolean - * `shiftKey` Boolean - * `ctrlKey` Boolean - * `metaKey` Boolean -* `bounds` [Rectangle](structures/rectangle.md) - The bounds of tray icon - -Emitted when the tray icon is clicked. - -#### Event: 'right-click' _macOS_ _Windows_ - -* `event` Event - * `altKey` Boolean - * `shiftKey` Boolean - * `ctrlKey` Boolean - * `metaKey` Boolean -* `bounds` [Rectangle](structures/rectangle.md) - The bounds of tray icon - -Emitted when the tray icon is right clicked. - -#### Event: 'double-click' _macOS_ _Windows_ - -* `event` Event - * `altKey` Boolean - * `shiftKey` Boolean - * `ctrlKey` Boolean - * `metaKey` Boolean -* `bounds` [Rectangle](structures/rectangle.md) - The bounds of tray icon - -Emitted when the tray icon is double clicked. - -#### Event: 'balloon-show' _Windows_ - -Emitted when the tray balloon shows. - -#### Event: 'balloon-click' _Windows_ - -Emitted when the tray balloon is clicked. - -#### Event: 'balloon-closed' _Windows_ - -Emitted when the tray balloon is closed because of timeout or user manually -closes it. - -#### Event: 'drop' _macOS_ - -Emitted when any dragged items are dropped on the tray icon. - -#### Event: 'drop-files' _macOS_ - -* `event` Event -* `files` String[] - The paths of the dropped files. - -Emitted when dragged files are dropped in the tray icon. - -#### Event: 'drop-text' _macOS_ - -* `event` Event -* `text` String - the dropped text string - -Emitted when dragged text is dropped in the tray icon. - -#### Event: 'drag-enter' _macOS_ - -Emitted when a drag operation enters the tray icon. - -#### Event: 'drag-leave' _macOS_ - -Emitted when a drag operation exits the tray icon. - -#### Event: 'drag-end' _macOS_ - -Emitted when a drag operation ends on the tray or ends at another location. - -### Instance Methods - -The `Tray` class has the following methods: - -#### `tray.destroy()` - -Destroys the tray icon immediately. - -#### `tray.setImage(image)` - -* `image` ([NativeImage](native-image.md) | String) - -Sets the `image` associated with this tray icon. - -#### `tray.setPressedImage(image)` _macOS_ - -* `image` [NativeImage](native-image.md) - -Sets the `image` associated with this tray icon when pressed on macOS. - -#### `tray.setToolTip(toolTip)` - -* `toolTip` String - -Sets the hover text for this tray icon. - -#### `tray.setTitle(title)` _macOS_ - -* `title` String - -Sets the title displayed aside of the tray icon in the status bar. - -#### `tray.setHighlightMode(mode)` _macOS_ - -* `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 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. - -```javascript -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_ - -* `options` Object - * `icon` ([NativeImage](native-image.md) | String) - (optional) - * `title` String - (optional) - * `content` String - (optional) - -Displays a tray balloon. - -#### `tray.popUpContextMenu([menu, position])` _macOS_ _Windows_ - -* `menu` Menu (optional) -* `position` Object (optional) - The pop up position. - * `x` Integer - * `y` Integer - -Pops up the context menu of the tray icon. When `menu` is passed, the `menu` will -be shown instead of the tray icon's context menu. - -The `position` is only available on Windows, and it is (0, 0) by default. - -#### `tray.setContextMenu(menu)` - -* `menu` Menu - -Sets the context menu for this icon. - -#### `tray.getBounds()` _macOS_ _Windows_ - -Returns [`Rectangle`](structures/rectangle.md) - -The `bounds` of this tray icon as `Object`. - -#### `tray.isDestroyed()` - -Returns `Boolean` - Whether the tray icon is destroyed. - -[event-emitter]: https://nodejs.org/api/events.html#events_class_eventemitter diff --git a/docs-translations/th-TH/api/web-contents.md b/docs-translations/th-TH/api/web-contents.md deleted file mode 100644 index 77cd2d4c6b..0000000000 --- a/docs-translations/th-TH/api/web-contents.md +++ /dev/null @@ -1,1241 +0,0 @@ -# webContents - -> Render and control web pages. - -Process: [Main](../glossary.md#main-process) - -`webContents` is an -[EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter). -It is responsible for rendering and controlling a web page and is a property of -the [`BrowserWindow`](browser-window.md) object. An example of accessing the -`webContents` object: - -```javascript -const {BrowserWindow} = require('electron') - -let win = new BrowserWindow({width: 800, height: 1500}) -win.loadURL('http://github.com') - -let contents = win.webContents -console.log(contents) -``` - -## Methods - -These methods can be accessed from the `webContents` module: - -```javascript -const {webContents} = require('electron') -console.log(webContents) -``` - -### `webContents.getAllWebContents()` - -Returns `WebContents[]` - An array of all `WebContents` instances. This will contain web contents -for all windows, webviews, opened devtools, and devtools extension background pages. - -### `webContents.getFocusedWebContents()` - -Returns `WebContents` - The web contents that is focused in this application, otherwise -returns `null`. - -### `webContents.fromId(id)` - -* `id` Integer - -Returns `WebContents` - A WebContents instance with the given ID. - -## Class: WebContents - -> Render and control the contents of a BrowserWindow instance. - -Process: [Main](../glossary.md#main-process) - -### Instance Events - -#### Event: 'did-finish-load' - -Emitted when the navigation is done, i.e. the spinner of the tab has stopped -spinning, and the `onload` event was dispatched. - -#### Event: 'did-fail-load' - -Returns: - -* `event` Event -* `errorCode` Integer -* `errorDescription` String -* `validatedURL` String -* `isMainFrame` Boolean - -This event is like `did-finish-load` but emitted when the load failed or was -cancelled, e.g. `window.stop()` is invoked. -The full list of error codes and their meaning is available [here](https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h). -Note that redirect responses will emit `errorCode` -3; you may want to ignore -that error explicitly. - -#### Event: 'did-frame-finish-load' - -Returns: - -* `event` Event -* `isMainFrame` Boolean - -Emitted when a frame has done navigation. - -#### Event: 'did-start-loading' - -Corresponds to the points in time when the spinner of the tab started spinning. - -#### Event: 'did-stop-loading' - -Corresponds to the points in time when the spinner of the tab stopped spinning. - -#### Event: 'did-get-response-details' - -Returns: - -* `event` Event -* `status` Boolean -* `newURL` String -* `originalURL` String -* `httpResponseCode` Integer -* `requestMethod` String -* `referrer` String -* `headers` Object -* `resourceType` String - -Emitted when details regarding a requested resource are available. -`status` indicates the socket connection to download the resource. - -#### Event: 'did-get-redirect-request' - -Returns: - -* `event` Event -* `oldURL` String -* `newURL` String -* `isMainFrame` Boolean -* `httpResponseCode` Integer -* `requestMethod` String -* `referrer` String -* `headers` Object - -Emitted when a redirect is received while requesting a resource. - -#### Event: 'dom-ready' - -Returns: - -* `event` Event - -Emitted when the document in the given frame is loaded. - -#### Event: 'page-favicon-updated' - -Returns: - -* `event` Event -* `favicons` String[] - Array of URLs - -Emitted when page receives favicon urls. - -#### Event: 'new-window' - -Returns: - -* `event` Event -* `url` String -* `frameName` String -* `disposition` String - Can be `default`, `foreground-tab`, `background-tab`, - `new-window`, `save-to-disk` and `other`. -* `options` Object - The options which will be used for creating the new - `BrowserWindow`. -* `additionalFeatures` String[] - The non-standard features (features not handled - by Chromium or Electron) given to `window.open()`. - -Emitted when the page requests to open a new window for a `url`. It could be -requested by `window.open` or an external link like ``. - -By default a new `BrowserWindow` will be created for the `url`. - -Calling `event.preventDefault()` will prevent creating new windows. In such case, the -`event.newGuest` may be set with a reference to a `BrowserWindow` instance to make it -used by the Electron's runtime. - -#### Event: 'will-navigate' - -Returns: - -* `event` Event -* `url` String - -Emitted when a user or the page wants to start navigation. It can happen when -the `window.location` object is changed or a user clicks a link in the page. - -This event will not emit when the navigation is started programmatically with -APIs like `webContents.loadURL` and `webContents.back`. - -It is also not emitted for in-page navigations, such as clicking anchor links -or updating the `window.location.hash`. Use `did-navigate-in-page` event for -this purpose. - -Calling `event.preventDefault()` will prevent the navigation. - -#### Event: 'did-navigate' - -Returns: - -* `event` Event -* `url` String - -Emitted when a navigation is done. - -This event is not emitted for in-page navigations, such as clicking anchor links -or updating the `window.location.hash`. Use `did-navigate-in-page` event for -this purpose. - -#### Event: 'did-navigate-in-page' - -Returns: - -* `event` Event -* `url` String -* `isMainFrame` Boolean - -Emitted when an in-page navigation happened. - -When in-page navigation happens, the page URL changes but does not cause -navigation outside of the page. Examples of this occurring are when anchor links -are clicked or when the DOM `hashchange` event is triggered. - -#### Event: 'crashed' - -Returns: - -* `event` Event -* `killed` Boolean - -Emitted when the renderer process crashes or is killed. - -#### Event: 'plugin-crashed' - -Returns: - -* `event` Event -* `name` String -* `version` String - -Emitted when a plugin process has crashed. - -#### Event: 'destroyed' - -Emitted when `webContents` is destroyed. - -#### Event: 'before-input-event' - -Returns: - -* `event` Event -* `input` Object - Input properties - * `type` String - Either `keyUp` or `keyDown` - * `key` String - Equivalent to [KeyboardEvent.key][keyboardevent] - * `isAutoRepeat` Boolean - Equivalent to [KeyboardEvent.repeat][keyboardevent] - * `shift` Boolean - Equivalent to [KeyboardEvent.shiftKey][keyboardevent] - * `control` Boolean - Equivalent to [KeyboardEvent.controlKey][keyboardevent] - * `alt` Boolean - Equivalent to [KeyboardEvent.altKey][keyboardevent] - * `meta` Boolean - Equivalent to [KeyboardEvent.metaKey][keyboardevent] - -Emitted before dispatching the `keydown` and `keyup` events in the page. -Calling `event.preventDefault` will prevent the page `keydown`/`keyup` events -from being dispatched. - -#### Event: 'devtools-opened' - -Emitted when DevTools is opened. - -#### Event: 'devtools-closed' - -Emitted when DevTools is closed. - -#### Event: 'devtools-focused' - -Emitted when DevTools is focused / opened. - -#### Event: 'certificate-error' - -Returns: - -* `event` Event -* `url` String -* `error` String - The error code -* `certificate` [Certificate](structures/certificate.md) -* `callback` Function - * `isTrusted` Boolean - Indicates whether the certificate can be considered trusted - -Emitted when failed to verify the `certificate` for `url`. - -The usage is the same with [the `certificate-error` event of -`app`](app.md#event-certificate-error). - -#### Event: 'select-client-certificate' - -Returns: - -* `event` Event -* `url` URL -* `certificateList` [Certificate[]](structures/certificate.md) -* `callback` Function - * `certificate` [Certificate](structures/certificate.md) - Must be a certificate from the given list - -Emitted when a client certificate is requested. - -The usage is the same with [the `select-client-certificate` event of -`app`](app.md#event-select-client-certificate). - -#### Event: 'login' - -Returns: - -* `event` Event -* `request` Object - * `method` String - * `url` URL - * `referrer` URL -* `authInfo` Object - * `isProxy` Boolean - * `scheme` String - * `host` String - * `port` Integer - * `realm` String -* `callback` Function - * `username` String - * `password` String - -Emitted when `webContents` wants to do basic auth. - -The usage is the same with [the `login` event of `app`](app.md#event-login). - -#### Event: 'found-in-page' - -Returns: - -* `event` Event -* `result` Object - * `requestId` Integer - * `activeMatchOrdinal` Integer - Position of the active match. - * `matches` Integer - Number of Matches. - * `selectionArea` Object - Coordinates of first match region. - -Emitted when a result is available for -[`webContents.findInPage`] request. - -#### Event: 'media-started-playing' - -Emitted when media starts playing. - -#### Event: 'media-paused' - -Emitted when media is paused or done playing. - -#### Event: 'did-change-theme-color' - -Emitted when a page's theme color changes. This is usually due to encountering -a meta tag: - -```html - -``` - -#### Event: 'update-target-url' - -Returns: - -* `event` Event -* `url` String - -Emitted when mouse moves over a link or the keyboard moves the focus to a link. - -#### Event: 'cursor-changed' - -Returns: - -* `event` Event -* `type` String -* `image` NativeImage (optional) -* `scale` Float (optional) - scaling factor for the custom cursor -* `size` Object (optional) - the size of the `image` - * `width` Integer - * `height` Integer -* `hotspot` Object (optional) - coordinates of the custom cursor's hotspot - * `x` Integer - x coordinate - * `y` Integer - y coordinate - -Emitted when the cursor's type changes. The `type` parameter can be `default`, -`crosshair`, `pointer`, `text`, `wait`, `help`, `e-resize`, `n-resize`, -`ne-resize`, `nw-resize`, `s-resize`, `se-resize`, `sw-resize`, `w-resize`, -`ns-resize`, `ew-resize`, `nesw-resize`, `nwse-resize`, `col-resize`, -`row-resize`, `m-panning`, `e-panning`, `n-panning`, `ne-panning`, `nw-panning`, -`s-panning`, `se-panning`, `sw-panning`, `w-panning`, `move`, `vertical-text`, -`cell`, `context-menu`, `alias`, `progress`, `nodrop`, `copy`, `none`, -`not-allowed`, `zoom-in`, `zoom-out`, `grab`, `grabbing`, `custom`. - -If the `type` parameter is `custom`, the `image` parameter will hold the custom -cursor image in a `NativeImage`, and `scale`, `size` and `hotspot` will hold -additional information about the custom cursor. - -#### Event: 'context-menu' - -Returns: - -* `event` Event -* `params` Object - * `x` Integer - x coordinate - * `y` Integer - y coordinate - * `linkURL` String - URL of the link that encloses the node the context menu - was invoked on. - * `linkText` String - Text associated with the link. May be an empty - string if the contents of the link are an image. - * `pageURL` String - URL of the top level page that the context menu was - invoked on. - * `frameURL` String - URL of the subframe that the context menu was invoked - on. - * `srcURL` String - Source URL for the element that the context menu - was invoked on. Elements with source URLs are images, audio and video. - * `mediaType` String - Type of the node the context menu was invoked on. Can - be `none`, `image`, `audio`, `video`, `canvas`, `file` or `plugin`. - * `hasImageContents` Boolean - Whether the context menu was invoked on an image - which has non-empty contents. - * `isEditable` Boolean - Whether the context is editable. - * `selectionText` String - Text of the selection that the context menu was - invoked on. - * `titleText` String - Title or alt text of the selection that the context - was invoked on. - * `misspelledWord` String - The misspelled word under the cursor, if any. - * `frameCharset` String - The character encoding of the frame on which the - menu was invoked. - * `inputFieldType` String - If the context menu was invoked on an input - field, the type of that field. Possible values are `none`, `plainText`, - `password`, `other`. - * `menuSourceType` String - Input source that invoked the context menu. - Can be `none`, `mouse`, `keyboard`, `touch`, `touchMenu`. - * `mediaFlags` Object - The flags for the media element the context menu was - invoked on. - * `inError` Boolean - Whether the media element has crashed. - * `isPaused` Boolean - Whether the media element is paused. - * `isMuted` Boolean - Whether the media element is muted. - * `hasAudio` Boolean - Whether the media element has audio. - * `isLooping` Boolean - Whether the media element is looping. - * `isControlsVisible` Boolean - Whether the media element's controls are - visible. - * `canToggleControls` Boolean - Whether the media element's controls are - toggleable. - * `canRotate` Boolean - Whether the media element can be rotated. - * `editFlags` Object - These flags indicate whether the renderer believes it - is able to perform the corresponding action. - * `canUndo` Boolean - Whether the renderer believes it can undo. - * `canRedo` Boolean - Whether the renderer believes it can redo. - * `canCut` Boolean - Whether the renderer believes it can cut. - * `canCopy` Boolean - Whether the renderer believes it can copy - * `canPaste` Boolean - Whether the renderer believes it can paste. - * `canDelete` Boolean - Whether the renderer believes it can delete. - * `canSelectAll` Boolean - Whether the renderer believes it can select all. - -Emitted when there is a new context menu that needs to be handled. - -#### Event: 'select-bluetooth-device' - -Returns: - -* `event` Event -* `devices` [BluetoothDevice[]](structures/bluetooth-device.md) -* `callback` Function - * `deviceId` String - -Emitted when bluetooth device needs to be selected on call to -`navigator.bluetooth.requestDevice`. To use `navigator.bluetooth` api -`webBluetooth` should be enabled. If `event.preventDefault` is not called, -first available device will be selected. `callback` should be called with -`deviceId` to be selected, passing empty string to `callback` will -cancel the request. - -```javascript -const {app, webContents} = require('electron') -app.commandLine.appendSwitch('enable-web-bluetooth') - -app.on('ready', () => { - webContents.on('select-bluetooth-device', (event, deviceList, callback) => { - event.preventDefault() - let result = deviceList.find((device) => { - return device.deviceName === 'test' - }) - if (!result) { - callback('') - } else { - callback(result.deviceId) - } - }) -}) -``` - -#### Event: 'paint' - -Returns: - -* `event` Event -* `dirtyRect` [Rectangle](structures/rectangle.md) -* `image` [NativeImage](native-image.md) - The image data of the whole frame. - -Emitted when a new frame is generated. Only the dirty area is passed in the -buffer. - -```javascript -const {BrowserWindow} = require('electron') - -let win = new BrowserWindow({webPreferences: {offscreen: true}}) -win.webContents.on('paint', (event, dirty, image) => { - // updateBitmap(dirty, image.getBitmap()) -}) -win.loadURL('http://github.com') -``` - -#### Event: 'devtools-reload-page' - -Emitted when the devtools window instructs the webContents to reload - -### Instance Methods - -#### `contents.loadURL(url[, options])` - -* `url` String -* `options` Object (optional) - * `httpReferrer` String (optional) - A HTTP Referrer url. - * `userAgent` String (optional) - A user agent originating the request. - * `extraHeaders` String (optional) - Extra headers separated by "\n" - * `postData` ([UploadRawData](structures/upload-raw-data.md) | [UploadFile](structures/upload-file.md) | [UploadFileSystem](structures/upload-file-system.md) | [UploadBlob](structures/upload-blob.md))[] - (optional) - -Loads the `url` in the window. The `url` must contain the protocol prefix, -e.g. the `http://` or `file://`. If the load should bypass http cache then -use the `pragma` header to achieve it. - -```javascript -const {webContents} = require('electron') -const options = {extraHeaders: 'pragma: no-cache\n'} -webContents.loadURL('https://github.com', options) -``` - -#### `contents.downloadURL(url)` - -* `url` String - -Initiates a download of the resource at `url` without navigating. The -`will-download` event of `session` will be triggered. - -#### `contents.getURL()` - -Returns `String` - The URL of the current web page. - -```javascript -const {BrowserWindow} = require('electron') -let win = new BrowserWindow({width: 800, height: 600}) -win.loadURL('http://github.com') - -let currentURL = win.webContents.getURL() -console.log(currentURL) -``` - -#### `contents.getTitle()` - -Returns `String` - The title of the current web page. - -#### `contents.isDestroyed()` - -Returns `Boolean` - Whether the web page is destroyed. - -#### `contents.isFocused()` - -Returns `Boolean` - Whether the web page is focused. - -#### `contents.isLoading()` - -Returns `Boolean` - Whether web page is still loading resources. - -#### `contents.isLoadingMainFrame()` - -Returns `Boolean` - Whether the main frame (and not just iframes or frames within it) is -still loading. - -#### `contents.isWaitingForResponse()` - -Returns `Boolean` - Whether the web page is waiting for a first-response from the main -resource of the page. - -#### `contents.stop()` - -Stops any pending navigation. - -#### `contents.reload()` - -Reloads the current web page. - -#### `contents.reloadIgnoringCache()` - -Reloads current page and ignores cache. - -#### `contents.canGoBack()` - -Returns `Boolean` - Whether the browser can go back to previous web page. - -#### `contents.canGoForward()` - -Returns `Boolean` - Whether the browser can go forward to next web page. - -#### `contents.canGoToOffset(offset)` - -* `offset` Integer - -Returns `Boolean` - Whether the web page can go to `offset`. - -#### `contents.clearHistory()` - -Clears the navigation history. - -#### `contents.goBack()` - -Makes the browser go back a web page. - -#### `contents.goForward()` - -Makes the browser go forward a web page. - -#### `contents.goToIndex(index)` - -* `index` Integer - -Navigates browser to the specified absolute web page index. - -#### `contents.goToOffset(offset)` - -* `offset` Integer - -Navigates to the specified offset from the "current entry". - -#### `contents.isCrashed()` - -Returns `Boolean` - Whether the renderer process has crashed. - -#### `contents.setUserAgent(userAgent)` - -* `userAgent` String - -Overrides the user agent for this web page. - -#### `contents.getUserAgent()` - -Returns `String` - The user agent for this web page. - -#### `contents.insertCSS(css)` - -* `css` String - -Injects CSS into the current web page. - -#### `contents.executeJavaScript(code[, userGesture, callback])` - -* `code` String -* `userGesture` Boolean (optional) -* `callback` Function (optional) - Called after script has been executed. - * `result` Any - -Returns `Promise` - A promise that resolves with the result of the executed code -or is rejected if the result of the code is a rejected promise. - -Evaluates `code` in page. - -In the browser window some HTML APIs like `requestFullScreen` can only be -invoked by a gesture from the user. Setting `userGesture` to `true` will remove -this limitation. - -If the result of the executed code is a promise the callback result will be the -resolved value of the promise. We recommend that you use the returned Promise -to handle code that results in a Promise. - -```js -contents.executeJavaScript('fetch("https://jsonplaceholder.typicode.com/users/1").then(resp => resp.json())', true) - .then((result) => { - console.log(result) // Will be the JSON object from the fetch call - }) -``` - -#### `contents.setAudioMuted(muted)` - -* `muted` Boolean - -Mute the audio on the current web page. - -#### `contents.isAudioMuted()` - -Returns `Boolean` - Whether this page has been muted. - -#### `contents.setZoomFactor(factor)` - -* `factor` Number - Zoom factor. - -Changes the zoom factor to the specified factor. Zoom factor is -zoom percent divided by 100, so 300% = 3.0. - -#### `contents.getZoomFactor(callback)` - -* `callback` Function - * `zoomFactor` Number - -Sends a request to get current zoom factor, the `callback` will be called with -`callback(zoomFactor)`. - -#### `contents.setZoomLevel(level)` - -* `level` Number - Zoom level - -Changes the zoom level to the specified level. The original size is 0 and each -increment above or below represents zooming 20% larger or smaller to default -limits of 300% and 50% of original size, respectively. - -#### `contents.getZoomLevel(callback)` - -* `callback` Function - * `zoomLevel` Number - -Sends a request to get current zoom level, the `callback` will be called with -`callback(zoomLevel)`. - -#### `contents.setZoomLevelLimits(minimumLevel, maximumLevel)` - -* `minimumLevel` Number -* `maximumLevel` Number - -**Deprecated:** Call `setVisualZoomLevelLimits` instead to set the visual zoom -level limits. This method will be removed in Electron 2.0. - -#### `contents.setVisualZoomLevelLimits(minimumLevel, maximumLevel)` - -* `minimumLevel` Number -* `maximumLevel` Number - -Sets the maximum and minimum pinch-to-zoom level. - -#### `contents.setLayoutZoomLevelLimits(minimumLevel, maximumLevel)` - -* `minimumLevel` Number -* `maximumLevel` Number - -Sets the maximum and minimum layout-based (i.e. non-visual) zoom level. - -#### `contents.undo()` - -Executes the editing command `undo` in web page. - -#### `contents.redo()` - -Executes the editing command `redo` in web page. - -#### `contents.cut()` - -Executes the editing command `cut` in web page. - -#### `contents.copy()` - -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. - -#### `contents.pasteAndMatchStyle()` - -Executes the editing command `pasteAndMatchStyle` in web page. - -#### `contents.delete()` - -Executes the editing command `delete` in web page. - -#### `contents.selectAll()` - -Executes the editing command `selectAll` in web page. - -#### `contents.unselect()` - -Executes the editing command `unselect` in web page. - -#### `contents.replace(text)` - -* `text` String - -Executes the editing command `replace` in web page. - -#### `contents.replaceMisspelling(text)` - -* `text` String - -Executes the editing command `replaceMisspelling` in web page. - -#### `contents.insertText(text)` - -* `text` String - -Inserts `text` to the focused element. - -#### `contents.findInPage(text[, options])` - -* `text` String - Content to be searched, must not be empty. -* `options` Object (optional) - * `forward` Boolean - (optional) Whether to search forward or backward, defaults to `true`. - * `findNext` Boolean - (optional) Whether the operation is first request or a follow up, - defaults to `false`. - * `matchCase` Boolean - (optional) Whether search should be case-sensitive, - defaults to `false`. - * `wordStart` Boolean - (optional) Whether to look only at the start of words. - defaults to `false`. - * `medialCapitalAsWordStart` Boolean - (optional) When combined with `wordStart`, - accepts a match in the middle of a word if the match begins with an - uppercase letter followed by a lowercase or non-letter. - Accepts several other intra-word matches, defaults to `false`. - -Starts a request to find all matches for the `text` in the web page and returns -an `Integer` representing the request id used for the request. The result of -the request can be obtained by subscribing to -[`found-in-page`](web-contents.md#event-found-in-page) event. - -#### `contents.stopFindInPage(action)` - -* `action` String - Specifies the action to take place when ending - [`webContents.findInPage`] request. - * `clearSelection` - Clear the selection. - * `keepSelection` - Translate the selection into a normal selection. - * `activateSelection` - Focus and click the selection node. - -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') -}) - -const requestId = webContents.findInPage('api') -console.log(requestId) -``` - -#### `contents.capturePage([rect, ]callback)` - -* `rect` [Rectangle](structures/rectangle.md) (optional) - The area of the page to be captured -* `callback` Function - * `image` [NativeImage](native-image.md) - -Captures a snapshot of the page within `rect`. Upon completion `callback` will -be called with `callback(image)`. The `image` is an instance of -[NativeImage](native-image.md) that stores data of the snapshot. Omitting -`rect` will capture the whole visible page. - -#### `contents.hasServiceWorker(callback)` - -* `callback` Function - * `hasWorker` Boolean - -Checks if any ServiceWorker is registered and returns a boolean as -response to `callback`. - -#### `contents.unregisterServiceWorker(callback)` - -* `callback` Function - * `success` Boolean - -Unregisters any ServiceWorker if present and returns a boolean as -response to `callback` when the JS promise is fulfilled or false -when the JS promise is rejected. - -#### `contents.print([options])` - -* `options` Object (optional) - * `silent` Boolean - Don't ask user for print settings. Default is `false`. - * `printBackground` Boolean - Also prints the background color and image of - the web page. Default is `false`. - -Prints window's web page. When `silent` is set to `true`, Electron will pick -up system's default printer and default settings for printing. - -Calling `window.print()` in web page is equivalent to calling -`webContents.print({silent: false, printBackground: false})`. - -Use `page-break-before: always; ` CSS style to force to print to a new page. - -#### `contents.printToPDF(options, callback)` - -* `options` Object - * `marginsType` Integer - (optional) Specifies the type of margins to use. Uses 0 for - default margin, 1 for no margin, and 2 for minimum margin. - * `pageSize` String - (optional) Specify page size of the generated PDF. Can be `A3`, - `A4`, `A5`, `Legal`, `Letter`, `Tabloid` or an Object containing `height` - and `width` in microns. - * `printBackground` Boolean - (optional) Whether to print CSS backgrounds. - * `printSelectionOnly` Boolean - (optional) Whether to print selection only. - * `landscape` Boolean - (optional) `true` for landscape, `false` for portrait. -* `callback` Function - * `error` Error - * `data` Buffer - -Prints window's web page as PDF with Chromium's preview printing custom -settings. - -The `callback` will be called with `callback(error, data)` on completion. The -`data` is a `Buffer` that contains the generated PDF data. - -The `landscape` will be ignored if `@page` CSS at-rule is used in the web page. - -By default, an empty `options` will be regarded as: - -```javascript -{ - marginsType: 0, - printBackground: false, - printSelectionOnly: false, - landscape: false -} -``` - -Use `page-break-before: always; ` CSS style to force to print to a new page. - -An example of `webContents.printToPDF`: - -```javascript -const {BrowserWindow} = require('electron') -const fs = require('fs') - -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 - fs.writeFile('/tmp/print.pdf', data, (error) => { - if (error) throw error - console.log('Write PDF successfully.') - }) - }) -}) -``` - -#### `contents.addWorkSpace(path)` - -* `path` String - -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) -}) -``` - -#### `contents.removeWorkSpace(path)` - -* `path` String - -Removes the specified path from DevTools workspace. - -#### `contents.openDevTools([options])` - -* `options` Object (optional) - * `mode` String - Opens the devtools with specified dock state, can be - `right`, `bottom`, `undocked`, `detach`. Defaults to last used dock state. - In `undocked` mode it's possible to dock back. In `detach` mode it's not. - -Opens the devtools. - -#### `contents.closeDevTools()` - -Closes the devtools. - -#### `contents.isDevToolsOpened()` - -Returns `Boolean` - Whether the devtools is opened. - -#### `contents.isDevToolsFocused()` - -Returns `Boolean` - Whether the devtools view is focused . - -#### `contents.toggleDevTools()` - -Toggles the developer tools. - -#### `contents.inspectElement(x, y)` - -* `x` Integer -* `y` Integer - -Starts inspecting element at position (`x`, `y`). - -#### `contents.inspectServiceWorker()` - -Opens the developer tools for the service worker context. - -#### `contents.send(channel[, arg1][, arg2][, ...])` - -* `channel` String -* `...args` any[] - -Send an asynchronous message to renderer process via `channel`, you can also -send arbitrary arguments. Arguments will be serialized in JSON internally and -hence no functions or prototype chain will be included. - -The renderer process can handle the message by listening to `channel` with the -`ipcRenderer` module. - -An example of sending messages from the main process to the renderer process: - -```javascript -// In the main process. -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.webContents.on('did-finish-load', () => { - win.webContents.send('ping', 'whoooooooh!') - }) -}) -``` - -```html - - - - - - -``` - -#### `contents.enableDeviceEmulation(parameters)` - -* `parameters` Object - * `screenPosition` String - Specify the screen type to emulate - (default: `desktop`) - * `desktop` - Desktop screen type - * `mobile` - Mobile screen type - * `screenSize` Object - Set the emulated screen size (screenPosition == mobile) - * `width` Integer - Set the emulated screen width - * `height` Integer - Set the emulated screen height - * `viewPosition` Object - Position the view on the screen - (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 - original device scale factor) (default: `0`) - * `viewSize` Object - Set the emulated view size (empty means no override) - * `width` Integer - Set the emulated view width - * `height` Integer - Set the emulated view height - * `fitToView` Boolean - Whether emulated view should be scaled down if - necessary to fit into available space (default: `false`) - * `offset` Object - Offset of the emulated view inside available space (not in - fit to view mode) (default: `{x: 0, y: 0}`) - * `x` Float - Set the x axis offset from top left corner - * `y` Float - Set the y axis offset from top left corner - * `scale` Float - Scale of emulated view inside available space (not in fit to - view mode) (default: `1`) - -Enable device emulation with the given parameters. - -#### `contents.disableDeviceEmulation()` - -Disable device emulation enabled by `webContents.enableDeviceEmulation`. - -#### `contents.sendInputEvent(event)` - -* `event` Object - * `type` String (**required**) - The type of the event, can be `mouseDown`, - `mouseUp`, `mouseEnter`, `mouseLeave`, `contextMenu`, `mouseWheel`, - `mouseMove`, `keyDown`, `keyUp`, `char`. - * `modifiers` String[] - An array of modifiers of the event, can - include `shift`, `control`, `alt`, `meta`, `isKeypad`, `isAutoRepeat`, - `leftButtonDown`, `middleButtonDown`, `rightButtonDown`, `capsLock`, - `numLock`, `left`, `right`. - -Sends an input `event` to the page. - -For keyboard events, the `event` object also have following properties: - -* `keyCode` String (**required**) - The character that will be sent - as the keyboard event. Should only use the valid key codes in - [Accelerator](accelerator.md). - -For mouse events, the `event` object also have following properties: - -* `x` Integer (**required**) -* `y` Integer (**required**) -* `button` String - The button pressed, can be `left`, `middle`, `right` -* `globalX` Integer -* `globalY` Integer -* `movementX` Integer -* `movementY` Integer -* `clickCount` Integer - -For the `mouseWheel` event, the `event` object also have following properties: - -* `deltaX` Integer -* `deltaY` Integer -* `wheelTicksX` Integer -* `wheelTicksY` Integer -* `accelerationRatioX` Integer -* `accelerationRatioY` Integer -* `hasPreciseScrollingDeltas` Boolean -* `canScroll` Boolean - -#### `contents.beginFrameSubscription([onlyDirty ,]callback)` - -* `onlyDirty` Boolean (optional) - Defaults to `false` -* `callback` Function - * `frameBuffer` Buffer - * `dirtyRect` [Rectangle](structures/rectangle.md) - -Begin subscribing for presentation events and captured frames, the `callback` -will be called with `callback(frameBuffer, dirtyRect)` when there is a -presentation event. - -The `frameBuffer` is a `Buffer` that contains raw pixel data. On most machines, -the pixel data is effectively stored in 32bit BGRA format, but the actual -representation depends on the endianness of the processor (most modern -processors are little-endian, on machines with big-endian processors the data -is in 32bit ARGB format). - -The `dirtyRect` is an object with `x, y, width, height` properties that -describes which part of the page was repainted. If `onlyDirty` is set to -`true`, `frameBuffer` will only contain the repainted area. `onlyDirty` -defaults to `false`. - -#### `contents.endFrameSubscription()` - -End subscribing for frame presentation events. - -#### `contents.startDrag(item)` - -* `item` Object - * `file` String - * `icon` [NativeImage](native-image.md) - -Sets the `item` as dragging item for current drag-drop operation, `file` is the -absolute path of the file to be dragged, and `icon` is the image showing under -the cursor when dragging. - -#### `contents.savePage(fullPath, saveType, callback)` - -* `fullPath` String - The full file path. -* `saveType` String - Specify the save type. - * `HTMLOnly` - Save only the HTML of the page. - * `HTMLComplete` - Save complete-html page. - * `MHTML` - Save complete-html page as MHTML. -* `callback` Function - `(error) => {}`. - * `error` Error - -Returns true if the process of saving page has been initiated successfully. - -```javascript -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') - }) -}) -``` - -#### `contents.showDefinitionForSelection()` _macOS_ - -Shows pop-up dictionary that searches the selected word on the page. - -#### `contents.setSize(options)` - -Set the size of the page. This is only supported for `` guest contents. - -* `options` Object - * `normal` Object (optional) - Normal size of the page. This can be used in - combination with the [`disableguestresize`](web-view-tag.md#disableguestresize) - attribute to manually resize the webview guest contents. - * `width` Integer - * `height` Integer - -#### `contents.isOffscreen()` - -Returns `Boolean` - Indicates whether *offscreen rendering* is enabled. - -#### `contents.startPainting()` - -If *offscreen rendering* is enabled and not painting, start painting. - -#### `contents.stopPainting()` - -If *offscreen rendering* is enabled and painting, stop painting. - -#### `contents.isPainting()` - -Returns `Boolean` - If *offscreen rendering* is enabled returns whether it is currently painting. - -#### `contents.setFrameRate(fps)` - -* `fps` Integer - -If *offscreen rendering* is enabled sets the frame rate to the specified number. -Only values between 1 and 60 are accepted. - -#### `contents.getFrameRate()` - -Returns `Integer` - If *offscreen rendering* is enabled returns the current frame rate. - -#### `contents.invalidate()` - -If *offscreen rendering* is enabled invalidates the frame and generates a new -one through the `'paint'` event. - -### Instance Properties - -#### `contents.id` - -A Integer representing the unique ID of this WebContents. - -#### `contents.session` - -A Session object ([session](session.md)) used by this webContents. - -#### `contents.hostWebContents` - -A [`WebContents`](web-contents.md) instance that might own this `WebContents`. - -#### `contents.devToolsWebContents` - -A `WebContents` of DevTools for this `WebContents`. - -**Note:** Users should never store this object because it may become `null` -when the DevTools has been closed. - -#### `contents.debugger` - -A [Debugger](debugger.md) instance for this webContents. - -[keyboardevent]: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent diff --git a/docs-translations/th-TH/api/web-frame.md b/docs-translations/th-TH/api/web-frame.md deleted file mode 100644 index a8a430c7c5..0000000000 --- a/docs-translations/th-TH/api/web-frame.md +++ /dev/null @@ -1,193 +0,0 @@ -# webFrame - -> Customize the rendering of the current web page. - -Process: [Renderer](../glossary.md#renderer-process) - -An example of zooming current page to 200%. - -```javascript -const {webFrame} = require('electron') - -webFrame.setZoomFactor(2) -``` - -## Methods - -The `webFrame` module has the following methods: - -### `webFrame.setZoomFactor(factor)` - -* `factor` Number - Zoom factor. - -Changes the zoom factor to the specified factor. Zoom factor is -zoom percent divided by 100, so 300% = 3.0. - -### `webFrame.getZoomFactor()` - -Returns `Number` - The current zoom factor. - -### `webFrame.setZoomLevel(level)` - -* `level` Number - Zoom level - -Changes the zoom level to the specified level. The original size is 0 and each -increment above or below represents zooming 20% larger or smaller to default -limits of 300% and 50% of original size, respectively. - -### `webFrame.getZoomLevel()` - -Returns `Number` - The current zoom level. - -### `webFrame.setZoomLevelLimits(minimumLevel, maximumLevel)` - -* `minimumLevel` Number -* `maximumLevel` Number - -**Deprecated:** Call `setVisualZoomLevelLimits` instead to set the visual zoom -level limits. This method will be removed in Electron 2.0. - -### `webFrame.setVisualZoomLevelLimits(minimumLevel, maximumLevel)` - -* `minimumLevel` Number -* `maximumLevel` Number - -Sets the maximum and minimum pinch-to-zoom level. - -### `webFrame.setLayoutZoomLevelLimits(minimumLevel, maximumLevel)` - -* `minimumLevel` Number -* `maximumLevel` Number - -Sets the maximum and minimum layout-based (i.e. non-visual) zoom level. - -### `webFrame.setSpellCheckProvider(language, autoCorrectWord, provider)` - -* `language` String -* `autoCorrectWord` Boolean -* `provider` Object - * `spellCheck` Function - Returns `Boolean` - * `text` String - -Sets a provider for spell checking in input fields and text areas. - -The `provider` must be an object that has a `spellCheck` method that returns -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)) - } -}) -``` - -### `webFrame.registerURLSchemeAsSecure(scheme)` - -* `scheme` String - -Registers the `scheme` as secure scheme. - -Secure schemes do not trigger mixed content warnings. For example, `https` and -`data` are secure schemes because they cannot be corrupted by active network -attackers. - -### `webFrame.registerURLSchemeAsBypassingCSP(scheme)` - -* `scheme` String - -Resources will be loaded from this `scheme` regardless of the current page's -Content Security Policy. - -### `webFrame.registerURLSchemeAsPrivileged(scheme[, options])` - -* `scheme` String -* `options` Object (optional) - * `secure` Boolean - (optional) Default true. - * `bypassCSP` Boolean - (optional) Default true. - * `allowServiceWorkers` Boolean - (optional) Default true. - * `supportFetchAPI` Boolean - (optional) Default true. - * `corsEnabled` Boolean - (optional) Default true. - -Registers the `scheme` as secure, bypasses content security policy for resources, -allows registering ServiceWorker and supports fetch API. - -Specify an option with the value of `false` to omit it from the registration. -An example of registering a privileged scheme, without bypassing Content Security Policy: - -```javascript -const {webFrame} = require('electron') -webFrame.registerURLSchemeAsPrivileged('foo', { bypassCSP: false }) -``` - -### `webFrame.insertText(text)` - -* `text` String - -Inserts `text` to the focused element. - -### `webFrame.executeJavaScript(code[, userGesture, callback])` - -* `code` String -* `userGesture` Boolean (optional) - Default is `false`. -* `callback` Function (optional) - Called after script has been executed. - * `result` Any - -Evaluates `code` in page. - -In the browser window some HTML APIs like `requestFullScreen` can only be -invoked by a gesture from the user. Setting `userGesture` to `true` will remove -this limitation. - -### `webFrame.getResourceUsage()` - -Returns `Object`: - -* `images` [MemoryUsageDetails](structures/memory-usage-details.md) -* `cssStyleSheets` [MemoryUsageDetails](structures/memory-usage-details.md) -* `xslStyleSheets` [MemoryUsageDetails](structures/memory-usage-details.md) -* `fonts` [MemoryUsageDetails](structures/memory-usage-details.md) -* `other` [MemoryUsageDetails](structures/memory-usage-details.md) - -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, - size: 2549, - liveSize: 2542, - decodedSize: 478, - purgedSize: 0, - purgeableSize: 0 - }, - cssStyleSheets: { /* same with "images" */ }, - xslStyleSheets: { /* same with "images" */ }, - fonts: { /* same with "images" */ }, - other: { /* same with "images" */ } -} -``` - -### `webFrame.clearCache()` - -Attempts to free memory that is no longer being used (like images from a -previous navigation). - -Note that blindly calling this method probably makes Electron slower since it -will have to refill these emptied caches, you should only call it if an event -in your app has occurred that makes you think your page is actually using less -memory (i.e. you have navigated from a super heavy page to a mostly empty one, -and intend to stay there). - -[spellchecker]: https://github.com/atom/node-spellchecker diff --git a/docs-translations/th-TH/api/web-request.md b/docs-translations/th-TH/api/web-request.md deleted file mode 100644 index 6da98d81f1..0000000000 --- a/docs-translations/th-TH/api/web-request.md +++ /dev/null @@ -1,205 +0,0 @@ -## Class: WebRequest - -> Intercept and modify the contents of a request at various stages of its lifetime. - -Process: [Main](../glossary.md#main-process) - -Instances of the `WebRequest` class are accessed by using the `webRequest` -property of a `Session`. - -The methods of `WebRequest` accept an optional `filter` and a `listener`. The -`listener` will be called with `listener(details)` when the API's event has -happened. The `details` object describes the request. Passing `null` -as `listener` will unsubscribe from the event. - -The `filter` object has a `urls` property which is an Array of URL -patterns that will be used to filter out the requests that do not match the URL -patterns. If the `filter` is omitted then all requests will be matched. - -For certain events the `listener` is passed with a `callback`, which should be -called with a `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' - callback({cancel: false, requestHeaders: details.requestHeaders}) -}) -``` - -### Instance Methods - -The following methods are available on instances of `WebRequest`: - -#### `webRequest.onBeforeRequest([filter, ]listener)` - -* `filter` Object -* `listener` Function - * `details` Object - * `id` Integer - * `url` String - * `method` String - * `resourceType` String - * `timestamp` Double - * `uploadData` [UploadData[]](structures/upload-data.md) - * `callback` Function - * `response` Object - * `cancel` Boolean (optional) - * `redirectURL` String (optional) - The original request is prevented from - being sent or completed and is instead redirected to the given URL. - -The `listener` will be called with `listener(details, callback)` when a request -is about to occur. - -The `uploadData` is an array of `UploadData` objects. - -The `callback` has to be called with an `response` object. - -#### `webRequest.onBeforeSendHeaders([filter, ]listener)` - -* `filter` Object -* `listener` Function - -The `listener` will be called with `listener(details, callback)` before sending -an HTTP request, once the request headers are available. This may occur after a -TCP connection is made to the server, but before any http data is sent. - -* `details` Object - * `id` Integer - * `url` String - * `method` String - * `resourceType` String - * `timestamp` Double - * `requestHeaders` Object -* `callback` Function - * `response` Object - * `cancel` Boolean (optional) - * `requestHeaders` Object (optional) - When provided, request will be made - with these headers. - -The `callback` has to be called with an `response` object. - -#### `webRequest.onSendHeaders([filter, ]listener)` - -* `filter` Object -* `listener` Function - * `details` Object - * `id` Integer - * `url` String - * `method` String - * `resourceType` String - * `timestamp` Double - * `requestHeaders` Object - -The `listener` will be called with `listener(details)` just before a request is -going to be sent to the server, modifications of previous `onBeforeSendHeaders` -response are visible by the time this listener is fired. - -#### `webRequest.onHeadersReceived([filter, ]listener)` - -* `filter` Object -* `listener` Function - -The `listener` will be called with `listener(details, callback)` when HTTP -response headers of a request have been received. - -* `details` Object - * `id` String - * `url` String - * `method` String - * `resourceType` String - * `timestamp` Double - * `statusLine` String - * `statusCode` Integer - * `responseHeaders` Object -* `callback` Function - * `response` Object - * `cancel` Boolean - * `responseHeaders` Object (optional) - When provided, the server is assumed - to have responded with these headers. - * `statusLine` String (optional) - Should be provided when overriding - `responseHeaders` to change header status otherwise original response - header's status will be used. - -The `callback` has to be called with an `response` object. - -#### `webRequest.onResponseStarted([filter, ]listener)` - -* `filter` Object -* `listener` Function - * `details` Object - * `id` Integer - * `url` String - * `method` String - * `resourceType` String - * `timestamp` Double - * `responseHeaders` Object - * `fromCache` Boolean - Indicates whether the response was fetched from disk - cache. - * `statusCode` Integer - * `statusLine` String - -The `listener` will be called with `listener(details)` when first byte of the -response body is received. For HTTP requests, this means that the status line -and response headers are available. - -#### `webRequest.onBeforeRedirect([filter, ]listener)` - -* `filter` Object -* `listener` Function - * `details` Object - * `id` String - * `url` String - * `method` String - * `resourceType` String - * `timestamp` Double - * `redirectURL` String - * `statusCode` Integer - * `ip` String (optional) - The server IP address that the request was - actually sent to. - * `fromCache` Boolean - * `responseHeaders` Object - -The `listener` will be called with `listener(details)` when a server initiated -redirect is about to occur. - -#### `webRequest.onCompleted([filter, ]listener)` - -* `filter` Object -* `listener` Function - * `details` Object - * `id` Integer - * `url` String - * `method` String - * `resourceType` String - * `timestamp` Double - * `responseHeaders` Object - * `fromCache` Boolean - * `statusCode` Integer - * `statusLine` String - -The `listener` will be called with `listener(details)` when a request is -completed. - -#### `webRequest.onErrorOccurred([filter, ]listener)` - -* `filter` Object -* `listener` Function - * `details` Object - * `id` Integer - * `url` String - * `method` String - * `resourceType` String - * `timestamp` Double - * `fromCache` Boolean - * `error` String - The error description. - -The `listener` will be called with `listener(details)` when an error occurs. diff --git a/docs-translations/th-TH/api/webview-tag.md b/docs-translations/th-TH/api/webview-tag.md deleted file mode 100644 index fbf5d6d844..0000000000 --- a/docs-translations/th-TH/api/webview-tag.md +++ /dev/null @@ -1,910 +0,0 @@ -# `` Tag - -> Display external web content in an isolated frame and process. - -Use the `webview` tag to embed 'guest' content (such as web pages) in your -Electron app. The guest content is contained within the `webview` container. -An embedded page within your app controls how the guest content is laid out and -rendered. - -Unlike an `iframe`, the `webview` runs in a separate process than your -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 purposes, `webview` can only be used in `BrowserWindow`s that have -`nodeIntegration` enabled. - -## Example - -To embed a web page in your app, add the `webview` tag to your app's embedder -page (this is the app page that will display the guest content). In its simplest -form, the `webview` tag includes the `src` of the web page and css styles that -control the appearance of the `webview` container: - -```html - -``` - -If you want to control the guest content in any way, you can write JavaScript -that listens for `webview` events and responds to those events using the -`webview` methods. Here's sample code with two event listeners: one that listens -for the web page to start loading, the other for the web page to stop loading, -and displays a "loading..." message during the load time: - -```html - -``` - -## CSS Styling Notes - -Please note that the `webview` tag's style uses `display:flex;` internally to -ensure the child `object` element fills the full height and width of its `webview` -container when used with traditional and flexbox layouts (since v0.36.11). Please -do not overwrite the default `display:flex;` CSS property, unless specifying -`display:inline-flex;` for inline layout. - -`webview` has issues being hidden using the `hidden` attribute or using `display: none;`. -It can cause unusual rendering behaviour within its child `browserplugin` object -and the web page is reloaded, when the `webview` is un-hidden, as opposed to just -becoming visible again. The recommended approach is to hide the `webview` using -CSS by zeroing the `width` & `height` and allowing the element to shrink to the 0px -dimensions via `flex`. - -```html - -``` - -## Tag Attributes - -The `webview` tag has the following attributes: - -### `src` - -```html - -``` - -Returns the visible URL. Writing to this attribute initiates top-level -navigation. - -Assigning `src` its own value will reload the current page. - -The `src` attribute can also accept data URLs, such as -`data:text/plain,Hello, world!`. - -### `autosize` - -```html - -``` - -If "on", the `webview` container will automatically resize within the -bounds specified by the attributes `minwidth`, `minheight`, `maxwidth`, and -`maxheight`. These constraints do not impact the `webview` unless `autosize` is -enabled. When `autosize` is enabled, the `webview` container size cannot be less -than the minimum values or greater than the maximum. - -### `nodeintegration` - -```html - -``` - -If "on", the guest page in `webview` will have node integration and can use node -APIs like `require` and `process` to access low level system resources. - -### `plugins` - -```html - -``` - -If "on", the guest page in `webview` will be able to use browser plugins. - -### `preload` - -```html - -``` - -Specifies a script that will be loaded before other scripts run in the guest -page. The protocol of script's URL must be either `file:` or `asar:`, because it -will be loaded by `require` in guest page under the hood. - -When the guest page doesn't have node integration this script will still have -access to all Node APIs, but global objects injected by Node will be deleted -after this script has finished executing. - -### `httpreferrer` - -```html - -``` - -Sets the referrer URL for the guest page. - -### `useragent` - -```html - -``` - -Sets the user agent for the guest page before the page is navigated to. Once the -page is loaded, use the `setUserAgent` method to change the user agent. - -### `disablewebsecurity` - -```html - -``` - -If "on", the guest page will have web security disabled. - -### `partition` - -```html - - -``` - -Sets the session used by the page. If `partition` starts with `persist:`, the -page will use a persistent session available to all pages in the app with the -same `partition`. if there is no `persist:` prefix, the page will use an -in-memory session. By assigning the same `partition`, multiple pages can share -the same session. If the `partition` is unset then default session of the app -will be used. - -This value can only be modified before the first navigation, since the session -of an active renderer process cannot change. Subsequent attempts to modify the -value will fail with a DOM exception. - -### `allowpopups` - -```html - -``` - -If "on", the guest page will be allowed to open new windows. - -### `webpreferences` - -```html - -``` - -A list of strings which specifies the web preferences to be set on the webview, separated by `,`. -The full list of supported preference strings can be found in [BrowserWindow](browser-window.md#new-browserwindowoptions). - -The string follows the same format as the features string in `window.open`. -A name by itself is given a `true` boolean value. -A preference can be set to another value by including an `=`, followed by the value. -Special values `yes` and `1` are interpreted as `true`, while `no` and `0` are interpreted as `false`. - -### `blinkfeatures` - -```html - -``` - -A list of strings which specifies the blink features to be enabled separated by `,`. -The full list of supported feature strings can be found in the -[RuntimeEnabledFeatures.in][blink-feature-string] file. - -### `disableblinkfeatures` - -```html - -``` - -A list of strings which specifies the blink features to be disabled separated by `,`. -The full list of supported feature strings can be found in the -[RuntimeEnabledFeatures.in][blink-feature-string] file. - -### `guestinstance` - -```html - -``` - -A value that links the webview to a specific webContents. When a webview -first loads a new webContents is created and this attribute is set to its -instance identifier. Setting this attribute on a new or existing webview -connects it to the existing webContents that currently renders in a different -webview. - -The existing webview will see the `destroy` event and will then create a new -webContents when a new url is loaded. - -### `disableguestresize` - -```html - -``` - -Prevents the webview contents from resizing when the webview element itself is -resized. - -This can be used in combination with -[`webContents.setSize`](web-contents.md#contentssetsizeoptions) to manually -resize the webview contents in reaction to a window size change. This can -make resizing faster compared to relying on the webview element bounds to -automatically resize the contents. - -```javascript -const {webContents} = require('electron') - -// We assume that `win` points to a `BrowserWindow` instance containing a -// `` with `disableguestresize`. - -win.on('resize', () => { - const [width, height] = win.getContentSize() - for (let wc of webContents.getAllWebContents()) { - // Check if `wc` belongs to a webview in the `win` window. - if (wc.hostWebContents && - wc.hostWebContents.id === win.webContents.id) { - wc.setSize({ - normal: { - width: width, - height: height - } - }) - } - } -}) -``` - -## Methods - -The `webview` tag has the following methods: - -**Note:** The webview element must be loaded before using the methods. - -**Example** - -```javascript -const webview = document.getElementById('foo') -webview.addEventListener('dom-ready', () => { - webview.openDevTools() -}) -``` - -### `.loadURL(url[, options])` - -* `url` URL -* `options` Object (optional) - * `httpReferrer` String (optional) - A HTTP Referrer url. - * `userAgent` String (optional) - A user agent originating the request. - * `extraHeaders` String (optional) - Extra headers separated by "\n" - * `postData` ([UploadRawData](structures/upload-raw-data.md) | [UploadFile](structures/upload-file.md) | [UploadFileSystem](structures/upload-file-system.md) | [UploadBlob](structures/upload-blob.md))[] - (optional) - -Loads the `url` in the webview, the `url` must contain the protocol prefix, -e.g. the `http://` or `file://`. - -### `.getURL()` - -Returns `String` - The URL of guest page. - -### `.getTitle()` - -Returns `String` - The title of guest page. - -### `.isLoading()` - -Returns `Boolean` - Whether guest page is still loading resources. - -### `.isWaitingForResponse()` - -Returns `Boolean` - Whether the guest page is waiting for a first-response for the -main resource of the page. - -### `.stop()` - -Stops any pending navigation. - -### `.reload()` - -Reloads the guest page. - -### `.reloadIgnoringCache()` - -Reloads the guest page and ignores cache. - -### `.canGoBack()` - -Returns `Boolean` - Whether the guest page can go back. - -### `.canGoForward()` - -Returns `Boolean` - Whether the guest page can go forward. - -### `.canGoToOffset(offset)` - -* `offset` Integer - -Returns `Boolean` - Whether the guest page can go to `offset`. - -### `.clearHistory()` - -Clears the navigation history. - -### `.goBack()` - -Makes the guest page go back. - -### `.goForward()` - -Makes the guest page go forward. - -### `.goToIndex(index)` - -* `index` Integer - -Navigates to the specified absolute index. - -### `.goToOffset(offset)` - -* `offset` Integer - -Navigates to the specified offset from the "current entry". - -### `.isCrashed()` - -Returns `Boolean` - Whether the renderer process has crashed. - -### `.setUserAgent(userAgent)` - -* `userAgent` String - -Overrides the user agent for the guest page. - -### `.getUserAgent()` - -Returns `String` - The user agent for guest page. - -### `.insertCSS(css)` - -* `css` String - -Injects CSS into the guest page. - -### `.executeJavaScript(code, userGesture, callback)` - -* `code` String -* `userGesture` Boolean - Default `false`. -* `callback` Function (optional) - Called after script has been executed. - * `result` Any - -Evaluates `code` in page. If `userGesture` is set, it will create the user -gesture context in the page. HTML APIs like `requestFullScreen`, which require -user action, can take advantage of this option for automation. - -### `.openDevTools()` - -Opens a DevTools window for guest page. - -### `.closeDevTools()` - -Closes the DevTools window of guest page. - -### `.isDevToolsOpened()` - -Returns `Boolean` - Whether guest page has a DevTools window attached. - -### `.isDevToolsFocused()` - -Returns `Boolean` - Whether DevTools window of guest page is focused. - -### `.inspectElement(x, y)` - -* `x` Integer -* `y` Integer - -Starts inspecting element at position (`x`, `y`) of guest page. - -### `.inspectServiceWorker()` - -Opens the DevTools for the service worker context present in the guest page. - -### `.setAudioMuted(muted)` - -* `muted` Boolean - -Set guest page muted. - -### `.isAudioMuted()` - -Returns `Boolean` - Whether guest page has been muted. - -### `.undo()` - -Executes editing command `undo` in page. - -### `.redo()` - -Executes editing command `redo` in page. - -### `.cut()` - -Executes editing command `cut` in page. - -### `.copy()` - -Executes editing command `copy` in page. - -### `.paste()` - -Executes editing command `paste` in page. - -### `.pasteAndMatchStyle()` - -Executes editing command `pasteAndMatchStyle` in page. - -### `.delete()` - -Executes editing command `delete` in page. - -### `.selectAll()` - -Executes editing command `selectAll` in page. - -### `.unselect()` - -Executes editing command `unselect` in page. - -### `.replace(text)` - -* `text` String - -Executes editing command `replace` in page. - -### `.replaceMisspelling(text)` - -* `text` String - -Executes editing command `replaceMisspelling` in page. - -### `.insertText(text)` - -* `text` String - -Inserts `text` to the focused element. - -### `.findInPage(text[, options])` - -* `text` String - Content to be searched, must not be empty. -* `options` Object (optional) - * `forward` Boolean - Whether to search forward or backward, defaults to `true`. - * `findNext` Boolean - Whether the operation is first request or a follow up, - defaults to `false`. - * `matchCase` Boolean - Whether search should be case-sensitive, - defaults to `false`. - * `wordStart` Boolean - Whether to look only at the start of words. - defaults to `false`. - * `medialCapitalAsWordStart` Boolean - When combined with `wordStart`, - accepts a match in the middle of a word if the match begins with an - uppercase letter followed by a lowercase or non-letter. - Accepts several other intra-word matches, defaults to `false`. - -Starts a request to find all matches for the `text` in the web page and returns an `Integer` -representing the request id used for the request. The result of the request can be -obtained by subscribing to [`found-in-page`](webview-tag.md#event-found-in-page) event. - -### `.stopFindInPage(action)` - -* `action` String - Specifies the action to take place when ending - [`.findInPage`](webview-tag.md#webviewtagfindinpage) request. - * `clearSelection` - Clear the selection. - * `keepSelection` - Translate the selection into a normal selection. - * `activateSelection` - Focus and click the selection node. - -Stops any `findInPage` request for the `webview` with the provided `action`. - -### `.print([options])` - -Prints `webview`'s web page. Same as `webContents.print([options])`. - -### `.printToPDF(options, callback)` - -Prints `webview`'s web page as PDF, Same as `webContents.printToPDF(options, callback)`. - -### `.capturePage([rect, ]callback)` - -Captures a snapshot of the `webview`'s page. Same as `webContents.capturePage([rect, ]callback)`. - -### `.send(channel[, arg1][, arg2][, ...])` - -* `channel` String -* `arg` (optional) - -Send an asynchronous message to renderer process via `channel`, you can also -send arbitrary arguments. The renderer process can handle the message by -listening to the `channel` event with the `ipcRenderer` module. - -See [webContents.send](web-contents.md#webcontentssendchannel-args) for -examples. - -### `.sendInputEvent(event)` - -* `event` Object - -Sends an input `event` to the page. - -See [webContents.sendInputEvent](web-contents.md#webcontentssendinputeventevent) -for detailed description of `event` object. - -### `.setZoomFactor(factor)` - -* `factor` Number - Zoom factor. - -Changes the zoom factor to the specified factor. Zoom factor is -zoom percent divided by 100, so 300% = 3.0. - -### `.setZoomLevel(level)` - -* `level` Number - Zoom level - -Changes the zoom level to the specified level. The original size is 0 and each -increment above or below represents zooming 20% larger or smaller to default -limits of 300% and 50% of original size, respectively. - -### `.showDefinitionForSelection()` _macOS_ - -Shows pop-up dictionary that searches the selected word on the page. - -### `.getWebContents()` - -Returns [`WebContents`](web-contents.md) - The web contents associated with -this `webview`. - -## DOM events - -The following DOM events are available to the `webview` tag: - -### Event: 'load-commit' - -Returns: - -* `url` String -* `isMainFrame` Boolean - -Fired when a load has committed. This includes navigation within the current -document as well as subframe document-level loads, but does not include -asynchronous resource loads. - -### Event: 'did-finish-load' - -Fired when the navigation is done, i.e. the spinner of the tab will stop -spinning, and the `onload` event is dispatched. - -### Event: 'did-fail-load' - -Returns: - -* `errorCode` Integer -* `errorDescription` String -* `validatedURL` String -* `isMainFrame` Boolean - -This event is like `did-finish-load`, but fired when the load failed or was -cancelled, e.g. `window.stop()` is invoked. - -### Event: 'did-frame-finish-load' - -Returns: - -* `isMainFrame` Boolean - -Fired when a frame has done navigation. - -### Event: 'did-start-loading' - -Corresponds to the points in time when the spinner of the tab starts spinning. - -### Event: 'did-stop-loading' - -Corresponds to the points in time when the spinner of the tab stops spinning. - -### Event: 'did-get-response-details' - -Returns: - -* `status` Boolean -* `newURL` String -* `originalURL` String -* `httpResponseCode` Integer -* `requestMethod` String -* `referrer` String -* `headers` Object -* `resourceType` String - -Fired when details regarding a requested resource is available. -`status` indicates socket connection to download the resource. - -### Event: 'did-get-redirect-request' - -Returns: - -* `oldURL` String -* `newURL` String -* `isMainFrame` Boolean - -Fired when a redirect was received while requesting a resource. - -### Event: 'dom-ready' - -Fired when document in the given frame is loaded. - -### Event: 'page-title-updated' - -Returns: - -* `title` String -* `explicitSet` Boolean - -Fired when page title is set during navigation. `explicitSet` is false when -title is synthesized from file url. - -### Event: 'page-favicon-updated' - -Returns: - -* `favicons` String[] - Array of URLs. - -Fired when page receives favicon urls. - -### Event: 'enter-html-full-screen' - -Fired when page enters fullscreen triggered by HTML API. - -### Event: 'leave-html-full-screen' - -Fired when page leaves fullscreen triggered by HTML API. - -### Event: 'console-message' - -Returns: - -* `level` Integer -* `message` String -* `line` Integer -* `sourceId` String - -Fired when the guest window logs a console message. - -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) -}) -``` - -### Event: 'found-in-page' - -Returns: - -* `result` Object - * `requestId` Integer - * `activeMatchOrdinal` Integer - Position of the active match. - * `matches` Integer - Number of Matches. - * `selectionArea` Object - Coordinates of first match region. - -Fired when a result is available for -[`webview.findInPage`](webview-tag.md#webviewtagfindinpage) request. - -```javascript -const webview = document.getElementById('foo') -webview.addEventListener('found-in-page', (e) => { - webview.stopFindInPage('keepSelection') -}) - -const requestId = webview.findInPage('test') -console.log(requestId) -``` - -### Event: 'new-window' - -Returns: - -* `url` String -* `frameName` String -* `disposition` String - Can be `default`, `foreground-tab`, `background-tab`, - `new-window`, `save-to-disk` and `other`. -* `options` Object - The options which should be used for creating the new - `BrowserWindow`. - -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 webview = document.getElementById('foo') - -webview.addEventListener('new-window', (e) => { - const protocol = require('url').parse(e.url).protocol - if (protocol === 'http:' || protocol === 'https:') { - shell.openExternal(e.url) - } -}) -``` - -### Event: 'will-navigate' - -Returns: - -* `url` String - -Emitted when a user or the page wants to start navigation. It can happen when -the `window.location` object is changed or a user clicks a link in the page. - -This event will not emit when the navigation is started programmatically with -APIs like `.loadURL` and `.back`. - -It is also not emitted during in-page navigation, such as clicking anchor links -or updating the `window.location.hash`. Use `did-navigate-in-page` event for -this purpose. - -Calling `event.preventDefault()` does __NOT__ have any effect. - -### Event: 'did-navigate' - -Returns: - -* `url` String - -Emitted when a navigation is done. - -This event is not emitted for in-page navigations, such as clicking anchor links -or updating the `window.location.hash`. Use `did-navigate-in-page` event for -this purpose. - -### Event: 'did-navigate-in-page' - -Returns: - -* `isMainFrame` Boolean -* `url` String - -Emitted when an in-page navigation happened. - -When in-page navigation happens, the page URL changes but does not cause -navigation outside of the page. Examples of this occurring are when anchor links -are clicked or when the DOM `hashchange` event is triggered. - -### Event: 'close' - -Fired when the guest page attempts to close itself. - -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' -}) -``` - -### Event: 'ipc-message' - -Returns: - -* `channel` String -* `args` Array - -Fired when the guest page has sent an asynchronous message to embedder page. - -With `sendToHost` method and `ipc-message` event you can easily communicate -between guest page and embedder page: - -```javascript -// In embedder page. -const webview = document.getElementById('foo') -webview.addEventListener('ipc-message', (event) => { - console.log(event.channel) - // Prints "pong" -}) -webview.send('ping') -``` - -```javascript -// In guest page. -const {ipcRenderer} = require('electron') -ipcRenderer.on('ping', () => { - ipcRenderer.sendToHost('pong') -}) -``` - -### Event: 'crashed' - -Fired when the renderer process is crashed. - -### Event: 'gpu-crashed' - -Fired when the gpu process is crashed. - -### Event: 'plugin-crashed' - -Returns: - -* `name` String -* `version` String - -Fired when a plugin process is crashed. - -### Event: 'destroyed' - -Fired when the WebContents is destroyed. - -### Event: 'media-started-playing' - -Emitted when media starts playing. - -### Event: 'media-paused' - -Emitted when media is paused or done playing. - -### Event: 'did-change-theme-color' - -Returns: - -* `themeColor` String - -Emitted when a page's theme color changes. This is usually due to encountering a meta tag: - -```html - -``` - -### Event: 'update-target-url' - -Returns: - -* `url` String - -Emitted when mouse moves over a link or the keyboard moves the focus to a link. - -### Event: 'devtools-opened' - -Emitted when DevTools is opened. - -### Event: 'devtools-closed' - -Emitted when DevTools is closed. - -### Event: 'devtools-focused' - -Emitted when DevTools is focused / opened. - -[blink-feature-string]: https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.in diff --git a/docs-translations/th-TH/api/window-open.md b/docs-translations/th-TH/api/window-open.md deleted file mode 100644 index 56216f551a..0000000000 --- a/docs-translations/th-TH/api/window-open.md +++ /dev/null @@ -1,43 +0,0 @@ -# `window.open` Function - -> Open a new window and load a URL. - -When `window.open` is called to create a new window in a web page, a new instance -of `BrowserWindow` will be created for the `url` and a proxy will be returned -to `window.open` to let the page have limited control over it. - -The proxy has limited standard functionality implemented to be -compatible with traditional web pages. For full control of the new window -you should create a `BrowserWindow` directly. - -The newly created `BrowserWindow` will inherit the parent window's options by -default. To override inherited options you can set them in the `features` -string. - -### `window.open(url[, frameName][, features])` - -* `url` String -* `frameName` String (optional) -* `features` String (optional) - -Returns [`BrowserWindowProxy`](browser-window-proxy.md) - Creates a new window -and returns an instance of `BrowserWindowProxy` class. - -The `features` string follows the format of standard browser, but each feature -has to be a field of `BrowserWindow`'s options. - -**Notes:** - -* Node integration will always be disabled in the opened `window` if it is - disabled on the parent window. -* Non-standard features (that are not handled by Chromium or Electron) given in - `features` will be passed to any registered `webContent`'s `new-window` event - handler in the `additionalFeatures` argument. - -### `window.opener.postMessage(message, targetOrigin)` - -* `message` String -* `targetOrigin` String - -Sends a message to the parent window with the specified origin or `*` for no -origin preference. diff --git a/docs-translations/th-TH/development/atom-shell-vs-node-webkit.md b/docs-translations/th-TH/development/atom-shell-vs-node-webkit.md deleted file mode 100644 index 0912a8d66e..0000000000 --- a/docs-translations/th-TH/development/atom-shell-vs-node-webkit.md +++ /dev/null @@ -1,52 +0,0 @@ -# Technical Differences Between Electron and NW.js (formerly node-webkit) - -__Note: Electron was previously named Atom Shell.__ - -Like NW.js, Electron provides a platform to write desktop applications -with JavaScript and HTML and has Node integration to grant access to the low -level system from web pages. - -But there are also fundamental differences between the two projects that make -Electron a completely separate product from NW.js: - -__1. Entry of Application__ - -In NW.js the main entry point of an application is a web page. You specify a -main page URL in the `package.json` and it is opened in a browser window as -the application's main window. - -In Electron, the entry point is a JavaScript script. Instead of -providing a URL directly, you manually create a browser window and load -an HTML file using the API. You also need to listen to window events -to decide when to quit the application. - -Electron works more like the Node.js runtime. Electron's APIs are lower level -so you can use it for browser testing in place of [PhantomJS](http://phantomjs.org/). - -__2. Build System__ - -In order to avoid the complexity of building all of Chromium, Electron uses [`libchromiumcontent`](https://github.com/brightray/libchromiumcontent) to access -Chromium's Content API. `libchromiumcontent` is a single shared library that -includes the Chromium Content module and all of its dependencies. Users don't -need a powerful machine to build Electron. - -__3. Node Integration__ - -In NW.js, the Node integration in web pages requires patching Chromium to -work, while in Electron we chose a different way to integrate the libuv loop -with each platform's message loop to avoid hacking Chromium. See the -[`node_bindings`][node-bindings] code for how that was done. - -__4. Multi-context__ - -If you are an experienced NW.js user, you should be familiar with the -concept of Node context and web context. These concepts were invented because -of how NW.js was implemented. - -By using the [multi-context](http://strongloop.com/strongblog/whats-new-node-js-v0-12-multiple-context-execution/) -feature of Node, Electron doesn't introduce a new JavaScript context in web -pages. - -Note: NW.js has optionally supported multi-context since 0.13. - -[node-bindings]: https://github.com/electron/electron/tree/master/atom/common diff --git a/docs-translations/th-TH/development/build-instructions-linux.md b/docs-translations/th-TH/development/build-instructions-linux.md deleted file mode 100644 index 42b7730a84..0000000000 --- a/docs-translations/th-TH/development/build-instructions-linux.md +++ /dev/null @@ -1,204 +0,0 @@ -# Build Instructions (Linux) - -Follow the guidelines below for building Electron on Linux. - -## Prerequisites - -* At least 25GB disk space and 8GB RAM. -* Python 2.7.x. Some distributions like CentOS 6.x still use Python 2.6.x - so you may need to check your Python version with `python -V`. -* Node.js. There are various ways to install Node. You can download - source code from [Node.js](http://nodejs.org) and compile from source. - Doing so permits installing Node on your own home directory as a standard user. - Or try repositories such as [NodeSource](https://nodesource.com/blog/nodejs-v012-iojs-and-the-nodesource-linux-repositories). -* Clang 3.4 or later. -* Development headers of GTK+ and libnotify. - -On Ubuntu, install the following libraries: - -```bash -$ sudo apt-get install build-essential clang libdbus-1-dev libgtk2.0-dev \ - libnotify-dev libgnome-keyring-dev libgconf2-dev \ - libasound2-dev libcap-dev libcups2-dev libxtst-dev \ - libxss1 libnss3-dev gcc-multilib g++-multilib curl \ - gperf bison -``` - -On RHEL / CentOS, install the following libraries: - -```bash -$ sudo yum install clang dbus-devel gtk2-devel libnotify-devel \ - libgnome-keyring-devel xorg-x11-server-utils libcap-devel \ - cups-devel libXtst-devel alsa-lib-devel libXrandr-devel \ - GConf2-devel nss-devel -``` - -On Fedora, install the following libraries: - -```bash -$ sudo dnf install clang dbus-devel gtk2-devel libnotify-devel \ - libgnome-keyring-devel xorg-x11-server-utils libcap-devel \ - cups-devel libXtst-devel alsa-lib-devel libXrandr-devel \ - GConf2-devel nss-devel -``` - -Other distributions may offer similar packages for installation via package -managers such as pacman. Or one can compile from source code. - -## Getting the Code - -```bash -$ git clone https://github.com/electron/electron.git -``` - -## Bootstrapping - -The bootstrap script will download all necessary build dependencies and create -the build project files. You must have Python 2.7.x for the script to succeed. -Downloading certain files can take a long time. Notice that we are using -`ninja` to build Electron so there is no `Makefile` generated. - -```bash -$ cd electron -$ ./script/bootstrap.py -v -``` - -### Cross compilation - -If you want to build for an `arm` target you should also install the following -dependencies: - -```bash -$ sudo apt-get install libc6-dev-armhf-cross linux-libc-dev-armhf-cross \ - g++-arm-linux-gnueabihf -``` - -And to cross compile for `arm` or `ia32` targets, you should pass the -`--target_arch` parameter to the `bootstrap.py` script: - -```bash -$ ./script/bootstrap.py -v --target_arch=arm -``` - -## Building - -If you would like to build both `Release` and `Debug` targets: - -```bash -$ ./script/build.py -``` - -This script will cause a very large Electron executable to be placed in -the directory `out/R`. The file size is in excess of 1.3 gigabytes. This -happens because the Release target binary contains debugging symbols. -To reduce the file size, run the `create-dist.py` script: - -```bash -$ ./script/create-dist.py -``` - -This will put a working distribution with much smaller file sizes in -the `dist` directory. After running the create-dist.py script, you -may want to remove the 1.3+ gigabyte binary which is still in `out/R`. - -You can also build the `Debug` target only: - -```bash -$ ./script/build.py -c D -``` - -After building is done, you can find the `electron` debug binary under `out/D`. - -## Cleaning - -To clean the build files: - -```bash -$ npm run clean -``` - -## Troubleshooting - -### Error While Loading Shared Libraries: libtinfo.so.5 - -Prebulit `clang` will try to link to `libtinfo.so.5`. Depending on the host -architecture, symlink to appropriate `libncurses`: - -```bash -$ sudo ln -s /usr/lib/libncurses.so.5 /usr/lib/libtinfo.so.5 -``` - -## Tests - -See [Build System Overview: Tests](build-system-overview.md#tests) - -## Advanced topics - -The default building configuration is targeted for major desktop Linux -distributions, to build for a specific distribution or device, following -information may help you. - -### Building `libchromiumcontent` locally - -To avoid using the prebuilt binaries of `libchromiumcontent`, you can pass the -`--build_libchromiumcontent` switch to `bootstrap.py` script: - -```bash -$ ./script/bootstrap.py -v --build_libchromiumcontent -``` - -Note that by default the `shared_library` configuration is not built, so you can -only build `Release` version of Electron if you use this mode: - -```bash -$ ./script/build.py -c R -``` - -### Using system `clang` instead of downloaded `clang` binaries - -By default Electron is built with prebuilt `clang` binaries provided by Chromium -project. If for some reason you want to build with the `clang` installed in your -system, you can call `bootstrap.py` with `--clang_dir=` switch. By passing -it the build script will assume the `clang` binaries reside in `/bin/`. - -For example if you installed `clang` under `/user/local/bin/clang`: - -```bash -$ ./script/bootstrap.py -v --build_libchromiumcontent --clang_dir /usr/local -$ ./script/build.py -c R -``` - -### Using other compilers other than `clang` - -To build Electron with compilers like `g++`, you first need to disable `clang` -with `--disable_clang` switch first, and then set `CC` and `CXX` environment -variables to the ones you want. - -For example building with GCC toolchain: - -```bash -$ env CC=gcc CXX=g++ ./script/bootstrap.py -v --build_libchromiumcontent --disable_clang -$ ./script/build.py -c R -``` - -### Environment variables - -Apart from `CC` and `CXX`, you can also set following environment variables to -custom the building configurations: - -* `CPPFLAGS` -* `CPPFLAGS_host` -* `CFLAGS` -* `CFLAGS_host` -* `CXXFLAGS` -* `CXXFLAGS_host` -* `AR` -* `AR_host` -* `CC` -* `CC_host` -* `CXX` -* `CXX_host` -* `LDFLAGS` - -The environment variables have to be set when executing the `bootstrap.py` -script, it won't work in the `build.py` script. diff --git a/docs-translations/th-TH/development/build-instructions-osx.md b/docs-translations/th-TH/development/build-instructions-osx.md deleted file mode 100644 index 2bf706dba8..0000000000 --- a/docs-translations/th-TH/development/build-instructions-osx.md +++ /dev/null @@ -1,64 +0,0 @@ -# Build Instructions (macOS) - -Follow the guidelines below for building Electron on macOS. - -## Prerequisites - -* macOS >= 10.8 -* [Xcode](https://developer.apple.com/technologies/tools/) >= 5.1 -* [node.js](http://nodejs.org) (external) - -If you are using the Python downloaded by Homebrew, you also need to install -the following Python modules: - -* [pyobjc](https://pythonhosted.org/pyobjc/install.html) - -## Getting the Code - -```bash -$ git clone https://github.com/electron/electron -``` - -## Bootstrapping - -The bootstrap script will download all necessary build dependencies and create -the build project files. Notice that we're using [ninja](https://ninja-build.org/) -to build Electron so there is no Xcode project generated. - -```bash -$ cd electron -$ ./script/bootstrap.py -v -``` - -## Building - -Build both `Release` and `Debug` targets: - -```bash -$ ./script/build.py -``` - -You can also only build the `Debug` target: - -```bash -$ ./script/build.py -c D -``` - -After building is done, you can find `Electron.app` under `out/D`. - -## 32bit Support - -Electron can only be built for a 64bit target on macOS and there is no plan to -support 32bit macOS in the future. - -## Cleaning - -To clean the build files: - -```bash -$ npm run clean -``` - -## Tests - -See [Build System Overview: Tests](build-system-overview.md#tests) diff --git a/docs-translations/th-TH/development/build-instructions-windows.md b/docs-translations/th-TH/development/build-instructions-windows.md deleted file mode 100644 index a506bb2959..0000000000 --- a/docs-translations/th-TH/development/build-instructions-windows.md +++ /dev/null @@ -1,146 +0,0 @@ -# Build Instructions (Windows) - -Follow the guidelines below for building Electron on Windows. - -## Prerequisites - -* Windows 7 / Server 2008 R2 or higher -* Visual Studio 2015 - [download VS 2015 Community Edition for - free](https://www.visualstudio.com/en-us/products/visual-studio-community-vs.aspx) -* [Python 2.7](http://www.python.org/download/releases/2.7/) -* [Node.js](http://nodejs.org/download/) -* [Git](http://git-scm.com) - -If you don't currently have a Windows installation, -[dev.microsoftedge.com](https://developer.microsoft.com/en-us/microsoft-edge/tools/vms/) -has timebombed versions of Windows that you can use to build Electron. - -Building Electron is done entirely with command-line scripts and cannot be done -with Visual Studio. You can develop Electron with any editor but support for -building with Visual Studio will come in the future. - -**Note:** Even though Visual Studio is not used for building, it's still -**required** because we need the build toolchains it provides. - -**Note:** While older versions of Electron required Visual Studio 2013, Electron 1.1 and later does require Visual Studio 2015. - -## Getting the Code - -```powershell -$ git clone https://github.com/electron/electron.git -``` - -## Bootstrapping - -The bootstrap script will download all necessary build dependencies and create -the build project files. Notice that we're using `ninja` to build Electron so -there is no Visual Studio project generated. - -```powershell -$ cd electron -$ python script\bootstrap.py -v -``` - -## Building - -Build both Release and Debug targets: - -```powershell -$ python script\build.py -``` - -You can also only build the Debug target: - -```powershell -$ 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). - -## 32bit Build - -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=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 -``` - -## Cleaning - -To clean the build files: - -```powershell -$ npm run clean -``` - -## Tests - -See [Build System Overview: Tests](build-system-overview.md#tests) - -## Troubleshooting - -### Command xxxx not found - -If you encountered an error like `Command xxxx not found`, you may try to use -the `VS2015 Command Prompt` console to execute the build scripts. - -### Fatal internal compiler error: C1001 - -Make sure you have the latest Visual Studio update installed. - -### Assertion failed: ((handle))->activecnt >= 0 - -If building under Cygwin, you may see `bootstrap.py` failed with following -error: - -``` -Assertion failed: ((handle))->activecnt >= 0, file src\win\pipe.c, line 1430 - -Traceback (most recent call last): - File "script/bootstrap.py", line 87, in - sys.exit(main()) - File "script/bootstrap.py", line 22, in main - update_node_modules('.') - File "script/bootstrap.py", line 56, in update_node_modules - execute([NPM, 'install']) - File "/home/zcbenz/codes/raven/script/lib/util.py", line 118, in execute - raise e -subprocess.CalledProcessError: Command '['npm.cmd', 'install']' returned non-zero exit status 3 -``` - -This is caused by a bug when using Cygwin Python and Win32 Node together. The -solution is to use the Win32 Python to execute the bootstrap script (assuming -you have installed Python under `C:\Python27`): - -```powershell -$ /cygdrive/c/Python27/python.exe script/bootstrap.py -``` - -### LNK1181: cannot open input file 'kernel32.lib' - -Try reinstalling 32bit Node.js. - -### Error: ENOENT, stat 'C:\Users\USERNAME\AppData\Roaming\npm' - -Simply making that directory [should fix the problem](http://stackoverflow.com/a/25095327/102704): - -```powershell -$ mkdir ~\AppData\Roaming\npm -``` - -### node-gyp is not recognized as an internal or external command - -You may get this error if you are using Git Bash for building, you should use -PowerShell or VS2015 Command Prompt instead. diff --git a/docs-translations/th-TH/development/build-system-overview.md b/docs-translations/th-TH/development/build-system-overview.md deleted file mode 100644 index df12df6ae9..0000000000 --- a/docs-translations/th-TH/development/build-system-overview.md +++ /dev/null @@ -1,122 +0,0 @@ -# Build System Overview - -Electron uses [gyp](https://gyp.gsrc.io/) for project generation and -[ninja](https://ninja-build.org/) for building. Project configurations can -be found in the `.gyp` and `.gypi` files. - -## Gyp Files - -Following `gyp` files contain the main rules for building Electron: - -* `electron.gyp` defines how Electron itself is built. -* `common.gypi` adjusts the build configurations of Node to make it build - together with Chromium. -* `vendor/brightray/brightray.gyp` defines how `brightray` is built and - includes the default configurations for linking with Chromium. -* `vendor/brightray/brightray.gypi` includes general build configurations about - building. - -## Component Build - -Since Chromium is quite a large project, the final linking stage can take -quite a few minutes, which makes it hard for development. In order to solve -this, Chromium introduced the "component build", which builds each component as -a separate shared library, making linking very quick but sacrificing file size -and performance. - -In Electron we took a very similar approach: for `Debug` builds, the binary -will be linked to a shared library version of Chromium's components to achieve -fast linking time; for `Release` builds, the binary will be linked to the static -library versions, so we can have the best possible binary size and performance. - -## Minimal Bootstrapping - -All of Chromium's prebuilt binaries (`libchromiumcontent`) are downloaded when -running the bootstrap script. By default both static libraries and shared -libraries will be downloaded and the final size should be between 800MB and 2GB -depending on the platform. - -By default, `libchromiumcontent` is downloaded from Amazon Web Services. -If the `LIBCHROMIUMCONTENT_MIRROR` environment variable is set, the bootstrap -script will download from it. -[`libchromiumcontent-qiniu-mirror`](https://github.com/hokein/libchromiumcontent-qiniu-mirror) -is a mirror for `libchromiumcontent`. If you have trouble in accessing AWS, you -can switch the download address to it via -`export LIBCHROMIUMCONTENT_MIRROR=http://7xk3d2.dl1.z0.glb.clouddn.com/` - -If you only want to build Electron quickly for testing or development, you -can download just the shared library versions by passing the `--dev` parameter: - -```bash -$ ./script/bootstrap.py --dev -$ ./script/build.py -c D -``` - -## Two-Phase Project Generation - -Electron links with different sets of libraries in `Release` and `Debug` -builds. `gyp`, however, doesn't support configuring different link settings for -different configurations. - -To work around this Electron uses a `gyp` variable -`libchromiumcontent_component` to control which link settings to use and only -generates one target when running `gyp`. - -## Target Names - -Unlike most projects that use `Release` and `Debug` as target names, Electron -uses `R` and `D` instead. This is because `gyp` randomly crashes if there is -only one `Release` or `Debug` build configuration defined, and Electron only has -to generate one target at a time as stated above. - -This only affects developers, if you are just building Electron for rebranding -you are not affected. - -## Tests - -Test your changes conform to the project coding style using: - -```bash -$ npm run lint -``` - -Test functionality using: - -```bash -$ npm test -``` - -Whenever you make changes to Electron source code, you'll need to re-run the -build before the tests: - -```bash -$ npm run build && npm test -``` - -You can make the test suite run faster by isolating the specific test or block -you're currently working on using Mocha's -[exclusive tests](https://mochajs.org/#exclusive-tests) feature. Just append -`.only` to any `describe` or `it` function call: - -```js -describe.only('some feature', function () { - // ... only tests in this block will be run -}) -``` - -Alternatively, you can use mocha's `grep` option to only run tests matching the -given regular expression pattern: - -```sh -$ npm test -- --grep child_process -``` - -Tests that include native modules (e.g. `runas`) can't be executed with the -debug build (see [#2558](https://github.com/electron/electron/issues/2558) for -details), but they will work with the release build. - -To run the tests with the release build use: - -```bash -$ npm test -- -R -``` diff --git a/docs-translations/th-TH/development/clang-format.md b/docs-translations/th-TH/development/clang-format.md deleted file mode 100644 index 7ad261a2de..0000000000 --- a/docs-translations/th-TH/development/clang-format.md +++ /dev/null @@ -1,34 +0,0 @@ -# Using clang-format on C++ Code - -[`clang-format`](http://clang.llvm.org/docs/ClangFormat.html) is a tool to -automatically format C/C++/Objective-C code, so that developers don't need to -worry about style issues during code reviews. - -It is highly recommended to format your changed C++ code before opening pull -requests, which will save you and the reviewers' time. - -You can install `clang-format` and `git-clang-format` via -`npm install -g clang-format`. - -To automatically format a file according to Electron C++ code style, simply run -`clang-format -i path/to/electron/file.cc`. It should work on macOS/Linux/Windows. - -The workflow to format your changed code: - -1. Make codes changes in Electron repository. -2. Run `git add your_changed_file.cc`. -3. Run `git-clang-format`, and you will probably see modifications in - `your_changed_file.cc`, these modifications are generated from `clang-format`. -4. Run `git add your_changed_file.cc`, and commit your change. -5. Now the branch is ready to be opened as a pull request. - -If you want to format the changed code on your latest git commit (HEAD), you can -run `git-clang-format HEAD~1`. See `git-clang-format -h` for more details. - -## Editor Integration - -You can also integrate `clang-format` directly into your favorite editors. -For further guidance on setting up editor integration, see these pages: - - * [Atom](https://atom.io/packages/clang-format) - * [Vim & Emacs](http://clang.llvm.org/docs/ClangFormat.html#vim-integration) diff --git a/docs-translations/th-TH/development/coding-style.md b/docs-translations/th-TH/development/coding-style.md deleted file mode 100644 index 503496ba3f..0000000000 --- a/docs-translations/th-TH/development/coding-style.md +++ /dev/null @@ -1,55 +0,0 @@ -# Coding Style - -These are the style guidelines for coding in Electron. - -You can run `npm run lint` to show any style issues detected by `cpplint` and -`eslint`. - -## C++ and Python - -For C++ and Python, we follow Chromium's [Coding -Style](http://www.chromium.org/developers/coding-style). You can use -[clang-format](clang-format.md) to format the C++ code automatically. There is -also a script `script/cpplint.py` to check whether all files conform. - -The Python version we are using now is Python 2.7. - -The C++ code uses a lot of Chromium's abstractions and types, so it's -recommended to get acquainted with them. A good place to start is -Chromium's [Important Abstractions and Data Structures](https://www.chromium.org/developers/coding-style/important-abstractions-and-data-structures) -document. The document mentions some special types, scoped types (that -automatically release their memory when going out of scope), logging mechanisms -etc. - -## JavaScript - -* Write [standard](http://npm.im/standard) JavaScript style. -* File names should be concatenated with `-` instead of `_`, e.g. - `file-name.js` rather than `file_name.js`, because in - [github/atom](https://github.com/github/atom) module names are usually in - the `module-name` form. This rule only applies to `.js` files. -* Use newer ES6/ES2015 syntax where appropriate - * [`const`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const) - for requires and other constants - * [`let`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let) - for defining variables - * [Arrow functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) - instead of `function () { }` - * [Template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) - instead of string concatenation using `+` - -## Naming Things - -Electron APIs uses the same capitalization scheme as Node.js: - -- When the module itself is a class like `BrowserWindow`, use `CamelCase`. -- When the module is a set of APIs, like `globalShortcut`, use `mixedCase`. -- When the API is a property of object, and it is complex enough to be in a - separate chapter like `win.webContents`, use `mixedCase`. -- For other non-module APIs, use natural titles, like ` Tag` or - `Process Object`. - -When creating a new API, it is preferred to use getters and setters instead of -jQuery's one-function style. For example, `.getText()` and `.setText(text)` -are preferred to `.text([text])`. There is a -[discussion](https://github.com/electron/electron/issues/46) on this. diff --git a/docs-translations/th-TH/development/debug-instructions-windows.md b/docs-translations/th-TH/development/debug-instructions-windows.md deleted file mode 100644 index b2d3347393..0000000000 --- a/docs-translations/th-TH/development/debug-instructions-windows.md +++ /dev/null @@ -1,93 +0,0 @@ -# Debugging on Windows - -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 Visual Studio, GitHub's hosted Electron Symbol Server, -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 Windows](build-instructions-windows.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. - -* **Visual Studio with C++ Tools**: The free community editions of Visual - Studio 2013 and Visual Studio 2015 both work. Once installed, - [configure Visual Studio to use GitHub's Electron Symbol server](setting-up-symbol-server.md). - It will enable Visual Studio to gain a better understanding of what happens - inside Electron, making it easier to present variables in a human-readable - format. - -* **ProcMon**: The [free SysInternals tool][sys-internals] allows you to inspect - a processes parameters, file handles, and registry operations. - -## Attaching to and Debugging Electron - -To start a debugging session, open up PowerShell/CMD and execute your debug -build of Electron, using the application to open as a parameter. - -```powershell -$ ./out/D/electron.exe ~/my-electron-app/ -``` - -### Setting Breakpoints - -Then, open up Visual Studio. Electron is not built with Visual Studio and hence -does not contain a project file - you can however open up the source code files -"As File", meaning that Visual Studio will open them up by themselves. You can -still set breakpoints - Visual Studio will automatically figure out that the -source code matches the code running in the attached process and break -accordingly. - -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`. - -### Attaching - -You can attach the Visual Studio debugger to a running process on a local or -remote computer. After the process is running, click Debug / Attach to Process -(or press `CTRL+ALT+P`) to open the "Attach to Process" dialog box. You can use -this capability to debug apps that are running on a local or remote computer, -debug multiple processes simultaneously. - -If Electron is running under a different user account, select the -`Show processes from all users` check box. Notice that depending on how many -BrowserWindows your app opened, you will see multiple processes. A typical -one-window app will result in Visual Studio presenting you with two -`Electron.exe` entries - one for the main process and one for the renderer -process. Since the list only gives you names, there's currently no reliable -way of figuring out which is which. - -### Which Process Should I Attach to? - -Code executed within the main process (that is, code found in or eventually run -by your main JavaScript file) as well as code called using the remote -(`require('electron').remote`) will run inside the main process, while other -code will execute inside its respective renderer process. - -You can be attached to multiple programs when you are debugging, but only one -program is active in the debugger at any time. You can set the active program -in the `Debug Location` toolbar or the `Processes window`. - -## Using ProcMon to Observe a Process - -While Visual Studio is fantastic for inspecting specific code paths, ProcMon's -strength is really in observing everything your application is doing with the -operating system - it captures File, Registry, Network, Process, and Profiling -details of processes. It attempts to log **all** events occurring and can be -quite overwhelming, but if you seek to understand what and how your application -is doing to the operating system, it can be a valuable resource. - -For an introduction to ProcMon's basic and advanced debugging features, go check -out [this video tutorial][procmon-instructions] provided by Microsoft. - -[sys-internals]: https://technet.microsoft.com/en-us/sysinternals/processmonitor.aspx -[procmon-instructions]: https://channel9.msdn.com/shows/defrag-tools/defrag-tools-4-process-monitor diff --git a/docs-translations/th-TH/development/debugging-instructions-macos.md b/docs-translations/th-TH/development/debugging-instructions-macos.md deleted file mode 100644 index e119db466a..0000000000 --- a/docs-translations/th-TH/development/debugging-instructions-macos.md +++ /dev/null @@ -1,125 +0,0 @@ -# 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-translations/th-TH/development/releasing.md b/docs-translations/th-TH/development/releasing.md deleted file mode 100644 index 111a10ea52..0000000000 --- a/docs-translations/th-TH/development/releasing.md +++ /dev/null @@ -1,101 +0,0 @@ -# Releasing - -This document describes the process for releasing a new version of Electron. - -## Compile release notes - -The current process is to maintain a local file, keeping track of notable changes as pull requests are merged. For examples of how to format the notes, see previous releases on [the releases page]. - -## Create a temporary branch - -Create a new branch from `master` named `release`. - -```sh -git checkout master -git pull -git checkout -b release -``` - -This branch is created as a precaution to prevent any merged PRs from sneaking into a release between the time the temporary release branch is created and the CI builds are complete. - -## Bump the version - -Run the `bump-version` script, passing `major`, `minor`, or `patch` as an argument: - -```sh -npm run bump-version -- patch -git push origin HEAD -``` - -This will bump the version number in several files. See [this bump commit] for an example. - -Most releases will be `patch` level. Upgrades to Chrome or other major changes should use `minor`. For more info, see [electron-versioning]. - -## Edit the release draft - -1. Visit [the releases page] and you'll see a new draft release with placeholder release notes. -1. Edit the release and add release notes. -1. Click 'Save draft'. **Do not click 'Publish release'!** -1. Wait for all the builds to pass. :hourglass_flowing_sand: - -## Merge temporary branch - -Merge the temporary back into master, without creating a merge commit: - -```sh -git merge release master --no-commit -git push origin master -``` - -If this fails, rebase with master and rebuild: - -```sh -git pull -git checkout release -git rebase master -git push origin HEAD -``` - -## Run local debug build - -Run local debug build to verify that you are actually building the version you want. Sometimes you thought you were doing a release for a new version, but you're actually not. - -```sh -npm run build -npm start -``` - -Verify the window is displaying the current updated version. - -## Set environment variables - -You'll need to set the following environment variables to publish a release. Ask another team member for these credentials. - -- `ELECTRON_S3_BUCKET` -- `ELECTRON_S3_ACCESS_KEY` -- `ELECTRON_S3_SECRET_KEY` -- `ELECTRON_GITHUB_TOKEN` - A personal access token with "repo" scope. - -You will only need to do this once. - -## Publish the release - -This script will download the binaries and generate the node headers and the .lib linker used on Windows by node-gyp to build native modules. - -```sh -npm run release -``` - -Note: Many distributions of Python still ship with old HTTPS certificates. You may see a `InsecureRequestWarning`, but it can be disregarded. - -## Delete the temporary branch - -```sh -git checkout master -git branch -D release # delete local branch -git push origin :release # delete remote branch -``` - -[the releases page]: https://github.com/electron/electron/releases -[this bump commit]: https://github.com/electron/electron/commit/78ec1b8f89b3886b856377a1756a51617bc33f5a -[electron-versioning]: /docs/tutorial/electron-versioning.md diff --git a/docs-translations/th-TH/development/setting-up-symbol-server.md b/docs-translations/th-TH/development/setting-up-symbol-server.md deleted file mode 100644 index 098fd2a5df..0000000000 --- a/docs-translations/th-TH/development/setting-up-symbol-server.md +++ /dev/null @@ -1,56 +0,0 @@ -# Setting Up Symbol Server in Debugger - -Debug symbols allow you to have better debugging sessions. They have information -about the functions contained in executables and dynamic libraries and provide -you with information to get clean call stacks. A Symbol Server allows the -debugger to load the correct symbols, binaries and sources automatically without -forcing users to download large debugging files. The server functions like -[Microsoft's symbol server](http://support.microsoft.com/kb/311503) so the -documentation there can be useful. - -Note that because released Electron builds are heavily optimized, debugging is -not always easy. 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. The only workaround is to build an -unoptimized local build. - -The official symbol server URL for Electron is -https://electron-symbols.githubapp.com. -You cannot visit this URL directly, you must add it to the symbol path of your -debugging tool. In the examples below, a local cache directory is used to avoid -repeatedly fetching the PDB from the server. Replace `c:\code\symbols` with an -appropriate cache directory on your machine. - -## Using the Symbol Server in Windbg - -The Windbg symbol path is configured with a string value delimited with asterisk -characters. To use only the Electron symbol server, add the following entry to -your symbol path (**Note:** you can replace `c:\code\symbols` with any writable -directory on your computer, if you'd prefer a different location for downloaded -symbols): - -``` -SRV*c:\code\symbols\*https://electron-symbols.githubapp.com -``` - -Set this string as `_NT_SYMBOL_PATH` in the environment, using the Windbg menus, -or by typing the `.sympath` command. If you would like to get symbols from -Microsoft's symbol server as well, you should list that first: - -``` -SRV*c:\code\symbols\*http://msdl.microsoft.com/download/symbols;SRV*c:\code\symbols\*https://electron-symbols.githubapp.com -``` - -## Using the symbol server in Visual Studio - - - - -## Troubleshooting: Symbols will not load - -Type the following commands in Windbg to print why symbols are not loading: - -``` -> !sym noisy -> .reload /f electron.exe -``` diff --git a/docs-translations/th-TH/development/source-code-directory-structure.md b/docs-translations/th-TH/development/source-code-directory-structure.md deleted file mode 100644 index cc74fe6942..0000000000 --- a/docs-translations/th-TH/development/source-code-directory-structure.md +++ /dev/null @@ -1,93 +0,0 @@ -# Source Code Directory Structure - -The source code of Electron is separated into a few parts, mostly -following Chromium on the separation conventions. - -You may need to become familiar with [Chromium's multi-process -architecture](http://dev.chromium.org/developers/design-documents/multi-process-architecture) -to understand the source code better. - -## Structure of Source Code - -``` -Electron -├── atom/ - C++ source code. -| ├── app/ - System entry code. -| ├── browser/ - The frontend including the main window, UI, and all of the -| | main process things. This talks to the renderer to manage web pages. -| | ├── ui/ - Implementation of UI stuff for different platforms. -| | | ├── cocoa/ - Cocoa specific source code. -| | | ├── win/ - Windows GUI specific source code. -| | | └── x/ - X11 specific source code. -| | ├── api/ - The implementation of the main process APIs. -| | ├── net/ - Network related code. -| | ├── mac/ - Mac specific Objective-C source code. -| | └── resources/ - Icons, platform-dependent files, etc. -| ├── renderer/ - Code that runs in renderer process. -| | └── api/ - The implementation of renderer process APIs. -| └── common/ - Code that used by both the main and renderer processes, -| including some utility functions and code to integrate node's message -| loop into Chromium's message loop. -| └── api/ - The implementation of common APIs, and foundations of -| Electron's built-in modules. -├── chromium_src/ - Source code that copied from Chromium. -├── default_app/ - The default page to show when Electron is started without -| providing an app. -├── docs/ - Documentations. -├── lib/ - JavaScript source code. -| ├── browser/ - Javascript main process initialization code. -| | └── api/ - Javascript API implementation. -| ├── common/ - JavaScript used by both the main and renderer processes -| | └── api/ - Javascript API implementation. -| └── renderer/ - Javascript renderer process initialization code. -| └── api/ - Javascript API implementation. -├── spec/ - Automatic tests. -├── electron.gyp - Building rules of Electron. -└── common.gypi - Compiler specific settings and building rules for other - components like `node` and `breakpad`. -``` - -## Structure of Other Directories - -* **script** - Scripts used for development purpose like building, packaging, - testing, etc. -* **tools** - Helper scripts used by gyp files, unlike `script`, scripts put - here should never be invoked by users directly. -* **vendor** - Source code of third party dependencies, we didn't use - `third_party` as name because it would confuse it with the same directory in - Chromium's source code tree. -* **node_modules** - Third party node modules used for building. -* **out** - Temporary output directory of `ninja`. -* **dist** - Temporary directory created by `script/create-dist.py` script - when creating a distribution. -* **external_binaries** - Downloaded binaries of third-party frameworks which - do not support building with `gyp`. - -## Keeping Git Submodules Up to Date - -The Electron repository has a few vendored dependencies, found in the -[/vendor][vendor] directory. Occasionally you might see a message like this -when running `git status`: - -```sh -$ git status - - modified: vendor/brightray (new commits) - modified: vendor/node (new commits) -``` - -To update these vendored dependencies, run the following command: - -```sh -git submodule update --init --recursive -``` - -If you find yourself running this command often, you can create an alias for it -in your `~/.gitconfig` file: - -``` -[alias] - su = submodule update --init --recursive -``` - -[vendor]: https://github.com/electron/electron/tree/master/vendor diff --git a/docs-translations/th-TH/tutorial/application-distribution.md b/docs-translations/th-TH/tutorial/application-distribution.md deleted file mode 100644 index 139a9114c7..0000000000 --- a/docs-translations/th-TH/tutorial/application-distribution.md +++ /dev/null @@ -1,176 +0,0 @@ -# Application Distribution - -To distribute your app with Electron, you need to download Electron's [prebuilt -binaries](https://github.com/electron/electron/releases). Next, the folder -containing your app should be named `app` and placed in Electron's resources -directory as shown in the following examples. Note that the location of -Electron's prebuilt binaries is indicated with `electron/` in the examples -below. - -On macOS: - -```text -electron/Electron.app/Contents/Resources/app/ -├── package.json -├── main.js -└── index.html -``` - -On Windows and Linux: - -```text -electron/resources/app -├── package.json -├── main.js -└── index.html -``` - -Then execute `Electron.app` (or `electron` on Linux, `electron.exe` on Windows), -and Electron will start as your app. The `electron` directory will then be -your distribution to deliver to final users. - -## Packaging Your App into a File - -Apart from shipping your app by copying all of its source files, you can also -package your app into an [asar](https://github.com/electron/asar) archive to avoid -exposing your app's source code to users. - -To use an `asar` archive to replace the `app` folder, you need to rename the -archive to `app.asar`, and put it under Electron's resources directory like -below, and Electron will then try to read the archive and start from it. - -On macOS: - -```text -electron/Electron.app/Contents/Resources/ -└── app.asar -``` - -On Windows and Linux: - -```text -electron/resources/ -└── app.asar -``` - -More details can be found in [Application packaging](application-packaging.md). - -## Rebranding with Downloaded Binaries - -After bundling your app into Electron, you will want to rebrand Electron -before distributing it to users. - -### Windows - -You can rename `electron.exe` to any name you like, and edit its icon and other -information with tools like [rcedit](https://github.com/atom/rcedit). - -### macOS - -You can rename `Electron.app` to any name you want, and you also have to rename -the `CFBundleDisplayName`, `CFBundleIdentifier` and `CFBundleName` fields in the -following files: - -* `Electron.app/Contents/Info.plist` -* `Electron.app/Contents/Frameworks/Electron Helper.app/Contents/Info.plist` - -You can also rename the helper app to avoid showing `Electron Helper` in the -Activity Monitor, but make sure you have renamed the helper app's executable -file's name. - -The structure of a renamed app would be like: - -``` -MyApp.app/Contents -├── Info.plist -├── MacOS/ -│   └── MyApp -└── Frameworks/ - ├── MyApp Helper EH.app - | ├── Info.plist - | └── MacOS/ - |    └── MyApp Helper EH - ├── MyApp Helper NP.app - | ├── Info.plist - | └── MacOS/ - |    └── MyApp Helper NP - └── MyApp Helper.app - ├── Info.plist - └── MacOS/ -    └── MyApp Helper -``` - -### Linux - -You can rename the `electron` executable to any name you like. - -## Packaging Tools - -Apart from packaging your app manually, you can also choose to use third party -packaging tools to do the work for you: - -* [electron-builder](https://github.com/electron-userland/electron-builder) -* [electron-packager](https://github.com/electron-userland/electron-packager) - -## Rebranding by Rebuilding Electron from Source - -It is also possible to rebrand Electron by changing the product name and -building it from source. To do this you need to modify the `atom.gyp` file and -have a clean rebuild. - -### grunt-build-atom-shell - -Manually checking out Electron's code and rebuilding could be complicated, so -a Grunt task has been created that will handle this automatically: -[grunt-build-atom-shell](https://github.com/paulcbetts/grunt-build-atom-shell). - -This task will automatically handle editing the `.gyp` file, building from -source, then rebuilding your app's native Node modules to match the new -executable name. - -### Creating a Custom Electron Fork - -Creating a custom fork of Electron is almost certainly not something you will -need to do in order to build your app, even for "Production Level" applications. -Using a tool such as `electron-packager` or `electron-builder` will allow you to -"Rebrand" Electron without having to do these steps. - -You need to fork Electron when you have custom C++ code that you have patched -directly into Electron, that either cannot be upstreamed, or has been rejected -from the official version. As maintainers of Electron, we very much would like -to make your scenario work, so please try as hard as you can to get your changes -into the official version of Electron, it will be much much easier on you, and -we appreciate your help. - -#### Creating a Custom Release with surf-build - -1. Install [Surf](https://github.com/surf-build/surf), via npm: - `npm install -g surf-build@latest` - -2. Create a new S3 bucket and create the following empty directory structure: - - ``` - - atom-shell/ - - symbols/ - - dist/ - ``` - -3. Set the following Environment Variables: - - * `ELECTRON_GITHUB_TOKEN` - a token that can create releases on GitHub - * `ELECTRON_S3_ACCESS_KEY`, `ELECTRON_S3_BUCKET`, `ELECTRON_S3_SECRET_KEY` - - the place where you'll upload node.js headers as well as symbols - * `ELECTRON_RELEASE` - Set to `true` and the upload part will run, leave unset - and `surf-build` will just do CI-type checks, appropriate to run for every - pull request. - * `CI` - Set to `true` or else it will fail - * `GITHUB_TOKEN` - set it to the same as `ELECTRON_GITHUB_TOKEN` - * `SURF_TEMP` - set to `C:\Temp` on Windows to prevent path too long issues - * `TARGET_ARCH` - set to `ia32` or `x64` - -4. In `script/upload.py`, you _must_ set `ELECTRON_REPO` to your fork (`MYORG/electron`), - especially if you are a contributor to Electron proper. - -5. `surf-build -r https://github.com/MYORG/electron -s YOUR_COMMIT -n 'surf-PLATFORM-ARCH'` - -6. Wait a very, very long time for the build to complete. diff --git a/docs-translations/th-TH/tutorial/application-packaging.md b/docs-translations/th-TH/tutorial/application-packaging.md deleted file mode 100644 index 8197201225..0000000000 --- a/docs-translations/th-TH/tutorial/application-packaging.md +++ /dev/null @@ -1,185 +0,0 @@ -# Application Packaging - -To mitigate [issues](https://github.com/joyent/node/issues/6960) around long -path names on Windows, slightly speed up `require` and conceal your source code -from cursory inspection, you can choose to package your app into an [asar][asar] -archive with little changes to your source code. - -## Generating `asar` Archive - -An [asar][asar] archive is a simple tar-like format that concatenates files -into a single file. Electron can read arbitrary files from it without unpacking -the whole file. - -Steps to package your app into an `asar` archive: - -### 1. Install the asar Utility - -```bash -$ npm install -g asar -``` - -### 2. Package with `asar pack` - -```bash -$ asar pack your-app app.asar -``` - -## Using `asar` Archives - -In Electron there are two sets of APIs: Node APIs provided by Node.js and Web -APIs provided by Chromium. Both APIs support reading files from `asar` archives. - -### Node API - -With special patches in Electron, Node APIs like `fs.readFile` and `require` -treat `asar` archives as virtual directories, and the files in it as normal -files in the filesystem. - -For example, suppose we have an `example.asar` archive under `/path/to`: - -```bash -$ asar list /path/to/example.asar -/app.js -/file.txt -/dir/module.js -/static/index.html -/static/main.css -/static/jquery.min.js -``` - -Read a file in the `asar` archive: - -```javascript -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') -``` - -Use a module from the archive: - -```javascript -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') -``` - -### Web API - -In a web page, files in an archive can be requested with the `file:` protocol. -Like the Node API, `asar` archives are treated as directories. - -For example, to get a file with `$.get`: - -```html - -``` - -### Treating an `asar` Archive as a Normal File - -For some cases like verifying the `asar` archive's checksum, we need to read the -content of an `asar` archive as a file. For this purpose you can use the built-in -`original-fs` module which provides original `fs` APIs without `asar` support: - -```javascript -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 -const fs = require('fs') -process.noAsar = true -fs.readFileSync('/path/to/example.asar') -``` - -## Limitations of the Node API - -Even though we tried hard to make `asar` archives in the Node API work like -directories as much as possible, there are still limitations due to the -low-level nature of the Node API. - -### Archives Are Read-only - -The archives can not be modified so all Node APIs that can modify files will not -work with `asar` archives. - -### Working Directory Can Not Be Set to Directories in Archive - -Though `asar` archives are treated as directories, there are no actual -directories in the filesystem, so you can never set the working directory to -directories in `asar` archives. Passing them as the `cwd` option of some APIs -will also cause errors. - -### Extra Unpacking on Some APIs - -Most `fs` APIs can read a file or get a file's information from `asar` archives -without unpacking, but for some APIs that rely on passing the real file path to -underlying system calls, Electron will extract the needed file into a -temporary file and pass the path of the temporary file to the APIs to make them -work. This adds a little overhead for those APIs. - -APIs that requires extra unpacking are: - -* `child_process.execFile` -* `child_process.execFileSync` -* `fs.open` -* `fs.openSync` -* `process.dlopen` - Used by `require` on native modules - -### Fake Stat Information of `fs.stat` - -The `Stats` object returned by `fs.stat` and its friends on files in `asar` -archives is generated by guessing, because those files do not exist on the -filesystem. So you should not trust the `Stats` object except for getting file -size and checking file type. - -### Executing Binaries Inside `asar` Archive - -There are Node APIs that can execute binaries like `child_process.exec`, -`child_process.spawn` and `child_process.execFile`, but only `execFile` is -supported to execute binaries inside `asar` archive. - -This is because `exec` and `spawn` accept `command` instead of `file` as input, -and `command`s are executed under shell. There is no reliable way to determine -whether a command uses a file in asar archive, and even if we do, we can not be -sure whether we can replace the path in command without side effects. - -## Adding Unpacked Files in `asar` Archive - -As stated above, some Node APIs will unpack the file to filesystem when -calling, apart from the performance issues, it could also lead to false alerts -of virus scanners. - -To work around this, you can unpack some files creating archives by using the -`--unpack` option, an example of excluding shared libraries of native modules -is: - -```bash -$ asar pack app app.asar --unpack *.node -``` - -After running the command, apart from the `app.asar`, there is also an -`app.asar.unpacked` folder generated which contains the unpacked files, you -should copy it together with `app.asar` when shipping it to users. - -[asar]: https://github.com/electron/asar diff --git a/docs-translations/th-TH/tutorial/debugging-main-process-node-inspector.md b/docs-translations/th-TH/tutorial/debugging-main-process-node-inspector.md deleted file mode 100644 index 2c42ef3139..0000000000 --- a/docs-translations/th-TH/tutorial/debugging-main-process-node-inspector.md +++ /dev/null @@ -1,131 +0,0 @@ -# Debugging the Main Process in node-inspector - -[`node-inspector`][node-inspector] provides a familiar DevTools GUI that can -be used in Chrome to debug Electron's main process, however, because -`node-inspector` relies on some native Node modules they must be rebuilt to -target the version of Electron you wish to debug. You can either rebuild -the `node-inspector` dependencies yourself, or let -[`electron-inspector`][electron-inspector] do it for you, both approaches are -covered in this document. - -**Note**: At the time of writing the latest release of `node-inspector` -(0.12.8) can't be rebuilt to target Electron 1.3.0 or later without patching -one of its dependencies. If you use `electron-inspector` it will take care of -this for you. - - -## Use `electron-inspector` for Debugging - -### 1. Install the [node-gyp required tools][node-gyp-required-tools] - -### 2. Install [`electron-rebuild`][electron-rebuild], if you haven't done so already. - -```shell -npm install electron-rebuild --save-dev -``` - -### 3. Install [`electron-inspector`][electron-inspector] - -```shell -npm install electron-inspector --save-dev -``` - -### 4. Start Electron - -Launch Electron with the `--debug` switch: - -```shell -electron --debug=5858 your/app -``` - -or, to pause execution on the first line of JavaScript: - -```shell -electron --debug-brk=5858 your/app -``` - -### 5. Start electron-inspector - -On macOS / Linux: - -```shell -node_modules/.bin/electron-inspector -``` - -On Windows: - -```shell -node_modules\\.bin\\electron-inspector -``` - -`electron-inspector` will need to rebuild `node-inspector` dependencies on the -first run, and any time you change your Electron version. The rebuild process -may require an internet connection to download Node headers and libs, and may -take a few minutes. - -### 6. Load the debugger UI - -Open http://127.0.0.1:8080/debug?ws=127.0.0.1:8080&port=5858 in the Chrome -browser. You may have to click pause if starting with `--debug-brk` to force -the UI to update. - - -## Use `node-inspector` for Debugging - -### 1. Install the [node-gyp required tools][node-gyp-required-tools] - -### 2. Install [`node-inspector`][node-inspector] - -```bash -$ npm install node-inspector -``` - -### 3. Install [`node-pre-gyp`][node-pre-gyp] - -```bash -$ npm install node-pre-gyp -``` - -### 4. Recompile the `node-inspector` `v8` modules for Electron - -**Note:** Update the target argument to be your Electron version number - -```bash -$ node_modules/.bin/node-pre-gyp --target=1.2.5 --runtime=electron --fallback-to-build --directory node_modules/v8-debug/ --dist-url=https://atom.io/download/atom-shell reinstall -$ node_modules/.bin/node-pre-gyp --target=1.2.5 --runtime=electron --fallback-to-build --directory node_modules/v8-profiler/ --dist-url=https://atom.io/download/atom-shell reinstall -``` - -See also [How to install native modules][how-to-install-native-modules]. - -### 5. Enable debug mode for Electron - -You can either start Electron with a debug flag like: - -```bash -$ electron --debug=5858 your/app -``` - -or, to pause your script on the first line: - -```bash -$ electron --debug-brk=5858 your/app -``` - -### 6. Start the [`node-inspector`][node-inspector] server using Electron - -```bash -$ ELECTRON_RUN_AS_NODE=true path/to/electron.exe node_modules/node-inspector/bin/inspector.js -``` - -### 7. Load the debugger UI - -Open http://127.0.0.1:8080/debug?ws=127.0.0.1:8080&port=5858 in the Chrome -browser. You may have to click pause if starting with `--debug-brk` to see the -entry line. - -[electron-inspector]: https://github.com/enlight/electron-inspector -[electron-rebuild]: https://github.com/electron/electron-rebuild -[node-inspector]: https://github.com/node-inspector/node-inspector -[node-pre-gyp]: https://github.com/mapbox/node-pre-gyp -[node-gyp-required-tools]: https://github.com/nodejs/node-gyp#installation -[how-to-install-native-modules]: using-native-node-modules.md#how-to-install-native-modules diff --git a/docs-translations/th-TH/tutorial/debugging-main-process-vscode.md b/docs-translations/th-TH/tutorial/debugging-main-process-vscode.md deleted file mode 100644 index 55b525ad79..0000000000 --- a/docs-translations/th-TH/tutorial/debugging-main-process-vscode.md +++ /dev/null @@ -1,34 +0,0 @@ -# Debugging the Main Process in VSCode - -### 1. Open an Electron project in VSCode. - -```bash -$ git clone git@github.com:electron/electron-quick-start.git -$ code electron-quick-start -``` - -### 2. Add a file `.vscode/launch.json` with the following configuration: - -```json -{ - "version": "0.2.0", - "configurations": [ - { - "name": "Debug Main Process", - "type": "node", - "request": "launch", - "cwd": "${workspaceRoot}", - "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron", - "program": "${workspaceRoot}/main.js" - } - ] -} -``` - -**Note:** For Windows, use `"${workspaceRoot}/node_modules/.bin/electron.cmd"` for `runtimeExecutable`. - -### 3. Debugging - -Set some breakpoints in `main.js`, and start debugging in the [Debug View](https://code.visualstudio.com/docs/editor/debugging). You should be able to hit the breakpoints. - -Here is a pre-configured project that you can download and directly debug in VSCode: https://github.com/octref/vscode-electron-debug/tree/master/electron-quick-start diff --git a/docs-translations/th-TH/tutorial/debugging-main-process.md b/docs-translations/th-TH/tutorial/debugging-main-process.md deleted file mode 100644 index fb5759e499..0000000000 --- a/docs-translations/th-TH/tutorial/debugging-main-process.md +++ /dev/null @@ -1,33 +0,0 @@ -# Debugging the Main Process - -The DevTools in an Electron browser window can only debug JavaScript that's -executed in that window (i.e. the web pages). To debug JavaScript that's -executed in the main process you will need to use an external debugger and -launch Electron with the `--debug` or `--debug-brk` switch. - -## Command Line Switches - -Use one of the following command line switches to enable debugging of the main -process: - -### `--debug=[port]` - -Electron will listen for V8 debugger protocol messages on the specified `port`, -an external debugger will need to connect on this port. The default `port` is -`5858`. - -```shell -electron --debug=5858 your/app -``` - -### `--debug-brk=[port]` - -Like `--debug` but pauses execution on the first line of JavaScript. - -## External Debuggers - -You will need to use a debugger that supports the V8 debugger protocol, -the following guides should help you to get started: - -- [Debugging the Main Process in VSCode](debugging-main-process-vscode.md) -- [Debugging the Main Process in node-inspector](debugging-main-process-node-inspector.md) diff --git a/docs-translations/th-TH/tutorial/desktop-environment-integration.md b/docs-translations/th-TH/tutorial/desktop-environment-integration.md deleted file mode 100644 index cbe1021c58..0000000000 --- a/docs-translations/th-TH/tutorial/desktop-environment-integration.md +++ /dev/null @@ -1,399 +0,0 @@ -# Desktop Environment Integration - -Different operating systems provide different features for integrating desktop -applications into their desktop environments. For example, on Windows, -applications can put shortcuts in the JumpList of task bar, and on Mac, -applications can put a custom menu in the dock menu. - -This guide explains how to integrate your application into those desktop -environments with Electron APIs. - -## Notifications (Windows, Linux, macOS) - -All three operating systems provide means for applications to send notifications -to the user. Electron conveniently allows developers to send notifications with -the [HTML5 Notification API](https://notifications.spec.whatwg.org/), using -the currently running operating system's native notification APIs to display it. - -**Note:** Since this is an HTML5 API it is only available in the renderer process. - -```javascript -let myNotification = new Notification('Title', { - body: 'Lorem Ipsum Dolor Sit Amet' -}) - -myNotification.onclick = () => { - console.log('Notification clicked') -} -``` - -While code and user experience across operating systems are similar, there -are fine differences. - -### Windows - -* On Windows 10, notifications "just work". -* On Windows 8.1 and Windows 8, a shortcut to your app, with a [Application User -Model ID][app-user-model-id], must be installed to the Start screen. Note, -however, that it does not need to be pinned to the Start screen. -* On Windows 7, notifications are not supported. You can however send -"balloon notifications" using the [Tray API][tray-balloon]. - -Furthermore, the maximum length for the notification body is 250 characters, -with the Windows team recommending that notifications should be kept to 200 -characters. - -### Linux - -Notifications are sent using `libnotify`, it can show notifications on any -desktop environment that follows [Desktop Notifications -Specification][notification-spec], including Cinnamon, Enlightenment, Unity, -GNOME, KDE. - -### macOS - -Notifications are straight-forward on macOS, you should however be aware of -[Apple's Human Interface guidelines regarding notifications](https://developer.apple.com/library/mac/documentation/UserExperience/Conceptual/OSXHIGuidelines/NotificationCenter.html). - -Note that notifications are limited to 256 bytes in size - and will be truncated -if you exceed that limit. - -## Recent documents (Windows & macOS) - -Windows and macOS provide easy access to a list of recent documents opened by -the application via JumpList or dock menu, respectively. - -__JumpList:__ - -![JumpList Recent Files](http://i.msdn.microsoft.com/dynimg/IC420538.png) - -__Application dock menu:__ - - - -To add a file to recent documents, you can use the -[app.addRecentDocument][addrecentdocument] API: - -```javascript -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 -const {app} = require('electron') -app.clearRecentDocuments() -``` - -### Windows Notes - -In order to be able to use this feature on Windows, your application has to be -registered as a handler of the file type of the document, otherwise the file -won't appear in JumpList even after you have added it. You can find everything -on registering your application in [Application Registration][app-registration]. - -When a user clicks a file from the JumpList, a new instance of your application -will be started with the path of the file added as a command line argument. - -### macOS Notes - -When a file is requested from the recent documents menu, the `open-file` event -of `app` module will be emitted for it. - -## Custom Dock Menu (macOS) - -macOS enables developers to specify a custom menu for the dock, which usually -contains some shortcuts for commonly used features of your application: - -__Dock menu of Terminal.app:__ - - - -To set your custom dock menu, you can use the `app.dock.setMenu` API, which is -only available on macOS: - -```javascript -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 Command...'} -]) -app.dock.setMenu(dockMenu) -``` - -## User Tasks (Windows) - -On Windows you can specify custom actions in the `Tasks` category of JumpList, -as quoted from MSDN: - -> Applications define tasks based on both the program's features and the key -> things a user is expected to do with them. Tasks should be context-free, in -> that the application does not need to be running for them to work. They -> should also be the statistically most common actions that a normal user would -> perform in an application, such as compose an email message or open the -> calendar in a mail program, create a new document in a word processor, launch -> an application in a certain mode, or launch one of its subcommands. An -> application should not clutter the menu with advanced features that standard -> users won't need or one-time actions such as registration. Do not use tasks -> for promotional items such as upgrades or special offers. -> -> It is strongly recommended that the task list be static. It should remain the -> same regardless of the state or status of the application. While it is -> possible to vary the list dynamically, you should consider that this could -> confuse the user who does not expect that portion of the destination list to -> change. - -__Tasks of Internet Explorer:__ - -![IE](http://i.msdn.microsoft.com/dynimg/IC420539.png) - -Unlike the dock menu in macOS which is a real menu, user tasks in Windows work -like application shortcuts such that when user clicks a task, a program will be -executed with specified arguments. - -To set user tasks for your application, you can use -[app.setUserTasks][setusertaskstasks] API: - -```javascript -const {app} = require('electron') -app.setUserTasks([ - { - program: process.execPath, - arguments: '--new-window', - iconPath: process.execPath, - iconIndex: 0, - title: 'New Window', - description: 'Create a new window' - } -]) -``` - -To clean your tasks list, just call `app.setUserTasks` with an empty array: - -```javascript -const {app} = require('electron') -app.setUserTasks([]) -``` - -The user tasks will still show even after your application closes, so the icon -and program path specified for a task should exist until your application is -uninstalled. - -## Thumbnail Toolbars - -On Windows you can add a thumbnail toolbar with specified buttons in a taskbar -layout of an application window. It provides users a way to access to a -particular window's command without restoring or activating the window. - -From MSDN, it's illustrated: - -> This toolbar is simply the familiar standard toolbar common control. It has a -> maximum of seven buttons. Each button's ID, image, tooltip, and state are defined -> in a structure, which is then passed to the taskbar. The application can show, -> enable, disable, or hide buttons from the thumbnail toolbar as required by its -> current state. -> -> For example, Windows Media Player might offer standard media transport controls -> such as play, pause, mute, and stop. - -__Thumbnail toolbar of Windows Media Player:__ - -![player](https://i-msdn.sec.s-msft.com/dynimg/IC420540.png) - -You can use [BrowserWindow.setThumbarButtons][setthumbarbuttons] to set -thumbnail toolbar in your application: - -```javascript -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') } - }, - { - tooltip: 'button2', - icon: path.join(__dirname, 'button2.png'), - flags: ['enabled', 'dismissonclick'], - click () { console.log('button2 clicked.') } - } -]) -``` - -To clean thumbnail toolbar buttons, just call `BrowserWindow.setThumbarButtons` -with an empty array: - -```javascript -const {BrowserWindow} = require('electron') -let win = new BrowserWindow() -win.setThumbarButtons([]) -``` - -## Unity Launcher Shortcuts (Linux) - -In Unity, you can add custom entries to its launcher via modifying the -`.desktop` file, see [Adding Shortcuts to a Launcher][unity-launcher]. - -__Launcher shortcuts of Audacious:__ - -![audacious](https://help.ubuntu.com/community/UnityLaunchersAndDesktopFiles?action=AttachFile&do=get&target=shortcuts.png) - -## Progress Bar in Taskbar (Windows, macOS, Unity) - -On Windows a taskbar button can be used to display a progress bar. This enables -a window to provide progress information to the user without the user having to -switch to the window itself. - -On macOS the progress bar will be displayed as a part of the dock icon. - -The Unity DE also has a similar feature that allows you to specify the progress -bar in the launcher. - -__Progress bar in taskbar button:__ - -![Taskbar Progress Bar](https://cloud.githubusercontent.com/assets/639601/5081682/16691fda-6f0e-11e4-9676-49b6418f1264.png) - -To set the progress bar for a Window, you can use the -[BrowserWindow.setProgressBar][setprogressbar] API: - -```javascript -const {BrowserWindow} = require('electron') -let win = new BrowserWindow() -win.setProgressBar(0.5) -``` - -## Icon Overlays in Taskbar (Windows) - -On Windows a taskbar button can use a small overlay to display application -status, as quoted from MSDN: - -> Icon overlays serve as a contextual notification of status, and are intended -> to negate the need for a separate notification area status icon to communicate -> that information to the user. For instance, the new mail status in Microsoft -> Outlook, currently shown in the notification area, can now be indicated -> through an overlay on the taskbar button. Again, you must decide during your -> development cycle which method is best for your application. Overlay icons are -> intended to supply important, long-standing status or notifications such as -> network status, messenger status, or new mail. The user should not be -> presented with constantly changing overlays or animations. - -__Overlay on taskbar button:__ - -![Overlay on taskbar button](https://i-msdn.sec.s-msft.com/dynimg/IC420441.png) - -To set the overlay icon for a window, you can use the -[BrowserWindow.setOverlayIcon][setoverlayicon] API: - -```javascript -const {BrowserWindow} = require('electron') -let win = new BrowserWindow() -win.setOverlayIcon('path/to/overlay.png', 'Description for overlay') -``` - -## Flash Frame (Windows) - -On Windows you can highlight the taskbar button to get the user's attention. -This is similar to bouncing the dock icon on macOS. -From the MSDN reference documentation: - -> Typically, a window is flashed to inform the user that the window requires -> attention but that it does not currently have the keyboard focus. - -To flash the BrowserWindow taskbar button, you can use the -[BrowserWindow.flashFrame][flashframe] API: - -```javascript -const {BrowserWindow} = require('electron') -let win = new BrowserWindow() -win.once('focus', () => win.flashFrame(false)) -win.flashFrame(true) -``` - -Don't forget to call the `flashFrame` method with `false` to turn off the flash. In -the above example, it is called when the window comes into focus, but you might -use a timeout or some other event to disable it. - -## Represented File of Window (macOS) - -On macOS a window can set its represented file, so the file's icon can show in -the title bar and when users Command-Click or Control-Click on the title a path -popup will show. - -You can also set the edited state of a window so that the file icon can indicate -whether the document in this window has been modified. - -__Represented file popup menu:__ - - - -To set the represented file of window, you can use the -[BrowserWindow.setRepresentedFilename][setrepresentedfilename] and -[BrowserWindow.setDocumentEdited][setdocumentedited] APIs: - -```javascript -const {BrowserWindow} = require('electron') -let win = new BrowserWindow() -win.setRepresentedFilename('/etc/passwd') -win.setDocumentEdited(true) -``` - -## Dragging files out of the window - -For certain kinds of apps that manipulate on files, it is important to be able -to drag files from Electron to other apps. To implement this feature in your -app, you need to call `webContents.startDrag(item)` API on `ondragstart` event. - -In web page: - -```html -item - -``` - -In the main process: - -```javascript -const {ipcMain} = require('electron') -ipcMain.on('ondragstart', (event, filePath) => { - event.sender.startDrag({ - file: filePath, - icon: '/path/to/icon.png' - }) -}) -``` - -[addrecentdocument]: ../api/app.md#appaddrecentdocumentpath-os-x-windows -[clearrecentdocuments]: ../api/app.md#appclearrecentdocuments-os-x-windows -[setusertaskstasks]: ../api/app.md#appsetusertaskstasks-windows -[setprogressbar]: ../api/browser-window.md#winsetprogressbarprogress -[setoverlayicon]: ../api/browser-window.md#winsetoverlayiconoverlay-description-windows-7 -[setrepresentedfilename]: ../api/browser-window.md#winsetrepresentedfilenamefilename-os-x -[setdocumentedited]: ../api/browser-window.md#winsetdocumenteditededited-os-x -[app-registration]: http://msdn.microsoft.com/en-us/library/windows/desktop/ee872121(v=vs.85).aspx -[unity-launcher]: https://help.ubuntu.com/community/UnityLaunchersAndDesktopFiles#Adding_shortcuts_to_a_launcher -[setthumbarbuttons]: ../api/browser-window.md#winsetthumbarbuttonsbuttons-windows-7 -[tray-balloon]: ../api/tray.md#traydisplayballoonoptions-windows -[app-user-model-id]: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx -[notification-spec]: https://developer.gnome.org/notification-spec/ -[flashframe]: ../api/browser-window.md#winflashframeflag diff --git a/docs-translations/th-TH/tutorial/devtools-extension.md b/docs-translations/th-TH/tutorial/devtools-extension.md deleted file mode 100644 index ac43916a7e..0000000000 --- a/docs-translations/th-TH/tutorial/devtools-extension.md +++ /dev/null @@ -1,66 +0,0 @@ -# DevTools Extension - -Electron supports the [Chrome DevTools Extension][devtools-extension], which can -be used to extend the ability of devtools for debugging popular web frameworks. - -## How to load a DevTools Extension - -This document outlines the process for manually loading an extension. -You may also try -[electron-devtools-installer](https://github.com/GPMDP/electron-devtools-installer), -a third-party tool that downloads extensions directly from the Chrome WebStore. - -To load an extension in Electron, you need to download it in Chrome browser, -locate its filesystem path, and then load it by calling the -`BrowserWindow.addDevToolsExtension(extension)` API. - -Using the [React Developer Tools][react-devtools] as example: - -1. Install it in Chrome browser. -1. Navigate to `chrome://extensions`, and find its extension ID, which is a hash - string like `fmkadmapgofadopljbjfkapdkoienihi`. -1. Find out filesystem location used by Chrome for storing extensions: - * on Windows it is `%LOCALAPPDATA%\Google\Chrome\User Data\Default\Extensions`; - * on Linux it could be: - * `~/.config/google-chrome/Default/Extensions/` - * `~/.config/google-chrome-beta/Default/Extensions/` - * `~/.config/google-chrome-canary/Default/Extensions/` - * `~/.config/chromium/Default/Extensions/` - * 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.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` -API to unload it. - -## Supported DevTools Extensions - -Electron only supports a limited set of `chrome.*` APIs, so some extensions -using unsupported `chrome.*` APIs for chrome extension features may not work. -Following Devtools Extensions are tested and guaranteed to work in Electron: - -* [Ember Inspector](https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi) -* [React Developer Tools](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi) -* [Backbone Debugger](https://chrome.google.com/webstore/detail/backbone-debugger/bhljhndlimiafopmmhjlgfpnnchjjbhd) -* [jQuery Debugger](https://chrome.google.com/webstore/detail/jquery-debugger/dbhhnnnpaeobfddmlalhnehgclcmjimi) -* [AngularJS Batarang](https://chrome.google.com/webstore/detail/angularjs-batarang/ighdmehidhipcmcojjgiloacoafjmpfk) -* [Vue.js devtools](https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd) -* [Cerebral Debugger](http://www.cerebraljs.com/documentation/the_debugger) -* [Redux DevTools Extension](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd) - -### What should I do if a DevTools Extension is not working? - -First please make sure the extension is still being maintained, some extensions -can not even work for recent versions of Chrome browser, and we are not able to -do anything for them. - -Then file a bug at Electron's issues list, and describe which part of the -extension is not working as expected. - -[devtools-extension]: https://developer.chrome.com/extensions/devtools -[react-devtools]: https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi diff --git a/docs-translations/th-TH/tutorial/electron-versioning.md b/docs-translations/th-TH/tutorial/electron-versioning.md deleted file mode 100644 index cae99344a6..0000000000 --- a/docs-translations/th-TH/tutorial/electron-versioning.md +++ /dev/null @@ -1,21 +0,0 @@ -# Electron Versioning - -If you are a seasoned Node developer, you are surely aware of `semver` - and -might be used to giving your dependency management systems only rough guidelines -rather than fixed version numbers. Due to the hard dependency on Node and -Chromium, Electron is in a slightly more difficult position and does not follow -semver. You should therefore always reference a specific version of Electron. - -Version numbers are bumped using the following rules: - -* Major: For breaking changes in Electron's API - if you upgrade from `0.37.0` - to `1.0.0`, you will have to update your app. -* Minor: For major Chrome and minor Node upgrades; or significant Electron - changes - if you upgrade from `1.0.0` to `1.1.0`, your app is supposed to - still work, but you might have to work around small changes. -* Patch: For new features and bug fixes - if you upgrade from `1.0.0` to - `1.0.1`, your app will continue to work as-is. - -If you are using `electron` or `electron-prebuilt`, we recommend that you set a fixed version -number (`1.1.0` instead of `^1.1.0`) to ensure that all upgrades of Electron are -a manual operation made by you, the developer. diff --git a/docs-translations/th-TH/tutorial/mac-app-store-submission-guide.md b/docs-translations/th-TH/tutorial/mac-app-store-submission-guide.md deleted file mode 100644 index 4551935d6c..0000000000 --- a/docs-translations/th-TH/tutorial/mac-app-store-submission-guide.md +++ /dev/null @@ -1,266 +0,0 @@ -# Mac App Store Submission Guide - -Since v0.34.0, Electron allows submitting packaged apps to the Mac App Store -(MAS). This guide provides information on: how to submit your app and the -limitations of the MAS build. - -**Note:** Submitting an app to Mac App Store requires enrolling [Apple Developer -Program][developer-program], which costs money. - -## How to Submit Your App - -The following steps introduce a simple way to submit your app to Mac App Store. -However, these steps do not ensure your app will be approved by Apple; you -still need to read Apple's [Submitting Your App][submitting-your-app] guide on -how to meet the Mac App Store requirements. - -### Get Certificate - -To submit your app to the Mac App Store, you first must get a certificate from -Apple. You can follow these [existing guides][nwjs-guide] on web. - -### Get Team ID - -Before signing your app, you need to know the Team ID of your account. To locate -your Team ID, Sign in to [Apple Developer Center](https://developer.apple.com/account/), -and click Membership in the sidebar. Your Team ID appears in the Membership -Information section under the team name. - -### Sign Your App - -After finishing the preparation work, you can package your app by following -[Application Distribution](application-distribution.md), and then proceed to -signing your app. - -First, you have to add a `ElectronTeamID` key to your app's `Info.plist`, which -has your Team ID as value: - -```xml - - - ... - ElectronTeamID - TEAM_ID - - -``` - -Then, you need to prepare two entitlements files. - -`child.plist`: - -```xml - - - - - com.apple.security.app-sandbox - - com.apple.security.inherit - - - -``` - -`parent.plist`: - -```xml - - - - - com.apple.security.app-sandbox - - com.apple.security.application-groups - TEAM_ID.your.bundle.id - - -``` - -You have to replace `TEAM_ID` with your Team ID, and replace `your.bundle.id` -with the Bundle ID of your app. - -And then sign your app with the following script: - -```bash -#!/bin/bash - -# Name of your app. -APP="YourApp" -# The path of your app to sign. -APP_PATH="/path/to/YourApp.app" -# The path to the location you want to put the signed package. -RESULT_PATH="~/Desktop/$APP.pkg" -# The name of certificates you requested. -APP_KEY="3rd Party Mac Developer Application: Company Name (APPIDENTITY)" -INSTALLER_KEY="3rd Party Mac Developer Installer: Company Name (APPIDENTITY)" -# The path of your plist files. -CHILD_PLIST="/path/to/child.plist" -PARENT_PLIST="/path/to/parent.plist" - -FRAMEWORKS_PATH="$APP_PATH/Contents/Frameworks" - -codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/Electron Framework.framework/Versions/A/Electron Framework" -codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/Electron Framework.framework/Versions/A/Libraries/libffmpeg.dylib" -codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/Electron Framework.framework/Versions/A/Libraries/libnode.dylib" -codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/Electron Framework.framework" -codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/$APP Helper.app/Contents/MacOS/$APP Helper" -codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/$APP Helper.app/" -codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/$APP Helper EH.app/Contents/MacOS/$APP Helper EH" -codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/$APP Helper EH.app/" -codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/$APP Helper NP.app/Contents/MacOS/$APP Helper NP" -codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/$APP Helper NP.app/" -codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$APP_PATH/Contents/MacOS/$APP" -codesign -s "$APP_KEY" -f --entitlements "$PARENT_PLIST" "$APP_PATH" - -productbuild --component "$APP_PATH" /Applications --sign "$INSTALLER_KEY" "$RESULT_PATH" -``` - -If you are new to app sandboxing under macOS, you should also read through -Apple's [Enabling App Sandbox][enable-app-sandbox] to have a basic idea, then -add keys for the permissions needed by your app to the entitlements files. - -Apart from manually signing your app, you can also choose to use the -[electron-osx-sign][electron-osx-sign] module to do the job. - -#### Sign Native Modules - -Native modules used in your app also need to be signed. If using -electron-osx-sign, be sure to include the path to the built binaries in the -argument list: - -```bash -electron-osx-sign YourApp.app YourApp.app/Contents/Resources/app/node_modules/nativemodule/build/release/nativemodule -``` - -Also note that native modules may have intermediate files produced which should -not be included (as they would also need to be signed). If you use -[electron-packager][electron-packager] before version 8.1.0, add -`--ignore=.+\.o$` to your build step to ignore these files. Versions 8.1.0 and -later ignores those files by default. - -### Upload Your App - -After signing your app, you can use Application Loader to upload it to iTunes -Connect for processing, making sure you have [created a record][create-record] -before uploading. - -### Submit Your App for Review - -After these steps, you can [submit your app for review][submit-for-review]. - -## Limitations of MAS Build - -In order to satisfy all requirements for app sandboxing, the following modules -have been disabled in the MAS build: - -* `crashReporter` -* `autoUpdater` - -and the following behaviors have been changed: - -* Video capture may not work for some machines. -* Certain accessibility features may not work. -* Apps will not be aware of DNS changes. -* APIs for launching apps at login are disabled. See -https://github.com/electron/electron/issues/7312#issuecomment-249479237 - -Also, due to the usage of app sandboxing, the resources which can be accessed by -the app are strictly limited; you can read [App Sandboxing][app-sandboxing] for -more information. - -### Additional Entitlements - -Depending on which Electron APIs your app uses, you may need to add additional -entitlements to your `parent.plist` file to be able to use these APIs from your -app's Mac App Store build. - -#### Network Access - -Enable outgoing network connections to allow your app to connect to a server: - -```xml -com.apple.security.network.client - -``` - -Enable incoming network connections to allow your app to open a network -listening socket: - -```xml -com.apple.security.network.server - -``` - -See the [Enabling Network Access documentation][network-access] for more -details. - -#### dialog.showOpenDialog - -```xml -com.apple.security.files.user-selected.read-only - -``` - -See the [Enabling User-Selected File Access documentation][user-selected] for -more details. - -#### dialog.showSaveDialog - -```xml -com.apple.security.files.user-selected.read-write - -``` - -See the [Enabling User-Selected File Access documentation][user-selected] for -more details. - -## Cryptographic Algorithms Used by Electron - -Depending on the country and region you are located, Mac App Store may require -documenting the cryptographic algorithms used in your app, and even ask you to -submit a copy of U.S. Encryption Registration (ERN) approval. - -Electron uses following cryptographic algorithms: - -* AES - [NIST SP 800-38A](http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf), [NIST SP 800-38D](http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf), [RFC 3394](http://www.ietf.org/rfc/rfc3394.txt) -* HMAC - [FIPS 198-1](http://csrc.nist.gov/publications/fips/fips198-1/FIPS-198-1_final.pdf) -* ECDSA - ANS X9.62–2005 -* ECDH - ANS X9.63–2001 -* HKDF - [NIST SP 800-56C](http://csrc.nist.gov/publications/nistpubs/800-56C/SP-800-56C.pdf) -* PBKDF2 - [RFC 2898](https://tools.ietf.org/html/rfc2898) -* RSA - [RFC 3447](http://www.ietf.org/rfc/rfc3447) -* SHA - [FIPS 180-4](http://csrc.nist.gov/publications/fips/fips180-4/fips-180-4.pdf) -* Blowfish - https://www.schneier.com/cryptography/blowfish/ -* CAST - [RFC 2144](https://tools.ietf.org/html/rfc2144), [RFC 2612](https://tools.ietf.org/html/rfc2612) -* DES - [FIPS 46-3](http://csrc.nist.gov/publications/fips/fips46-3/fips46-3.pdf) -* DH - [RFC 2631](https://tools.ietf.org/html/rfc2631) -* DSA - [ANSI X9.30](http://webstore.ansi.org/RecordDetail.aspx?sku=ANSI+X9.30-1%3A1997) -* EC - [SEC 1](http://www.secg.org/sec1-v2.pdf) -* IDEA - "On the Design and Security of Block Ciphers" book by X. Lai -* MD2 - [RFC 1319](http://tools.ietf.org/html/rfc1319) -* MD4 - [RFC 6150](https://tools.ietf.org/html/rfc6150) -* MD5 - [RFC 1321](https://tools.ietf.org/html/rfc1321) -* MDC2 - [ISO/IEC 10118-2](https://www.openssl.org/docs/manmaster/crypto/mdc2.html) -* RC2 - [RFC 2268](https://tools.ietf.org/html/rfc2268) -* RC4 - [RFC 4345](https://tools.ietf.org/html/rfc4345) -* RC5 - http://people.csail.mit.edu/rivest/Rivest-rc5rev.pdf -* RIPEMD - [ISO/IEC 10118-3](http://webstore.ansi.org/RecordDetail.aspx?sku=ISO%2FIEC%2010118-3:2004) - -On how to get the ERN approval, you can reference the article: [How to legally -submit an app to Apple’s App Store when it uses encryption (or how to obtain an -ERN)][ern-tutorial]. - -[developer-program]: https://developer.apple.com/support/compare-memberships/ -[submitting-your-app]: https://developer.apple.com/library/mac/documentation/IDEs/Conceptual/AppDistributionGuide/SubmittingYourApp/SubmittingYourApp.html -[nwjs-guide]: https://github.com/nwjs/nw.js/wiki/Mac-App-Store-%28MAS%29-Submission-Guideline#first-steps -[enable-app-sandbox]: https://developer.apple.com/library/ios/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/EnablingAppSandbox.html -[create-record]: https://developer.apple.com/library/ios/documentation/LanguagesUtilities/Conceptual/iTunesConnect_Guide/Chapters/CreatingiTunesConnectRecord.html -[electron-osx-sign]: https://github.com/electron-userland/electron-osx-sign -[electron-packager]: https://github.com/electron-userland/electron-packager -[submit-for-review]: https://developer.apple.com/library/ios/documentation/LanguagesUtilities/Conceptual/iTunesConnect_Guide/Chapters/SubmittingTheApp.html -[app-sandboxing]: https://developer.apple.com/app-sandboxing/ -[ern-tutorial]: https://carouselapps.com/2015/12/15/legally-submit-app-apples-app-store-uses-encryption-obtain-ern/ -[temporary-exception]: https://developer.apple.com/library/mac/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/AppSandboxTemporaryExceptionEntitlements.html -[user-selected]: https://developer.apple.com/library/mac/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/EnablingAppSandbox.html#//apple_ref/doc/uid/TP40011195-CH4-SW6 -[network-access]: https://developer.apple.com/library/ios/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/EnablingAppSandbox.html#//apple_ref/doc/uid/TP40011195-CH4-SW9 diff --git a/docs-translations/th-TH/tutorial/offscreen-rendering.md b/docs-translations/th-TH/tutorial/offscreen-rendering.md deleted file mode 100644 index 4c3024bdc6..0000000000 --- a/docs-translations/th-TH/tutorial/offscreen-rendering.md +++ /dev/null @@ -1,57 +0,0 @@ -# Offscreen Rendering - -Offscreen rendering lets you obtain the content of a browser window in a bitmap, -so it can be rendered anywhere, for example on a texture in a 3D scene. The -offscreen rendering in Electron uses a similar approach than the [Chromium -Embedded Framework](https://bitbucket.org/chromiumembedded/cef) project. - -Two modes of rendering can be used and only the dirty area is passed in the -`'paint'` event to be more efficient. The rendering can be stopped, continued -and the frame rate can be set. The specified frame rate is a top limit value, -when there is nothing happening on a webpage, no frames are generated. The -maximum frame rate is 60, because above that there is no benefit, just -performance loss. - -**Note:** An offscreen window is always created as a [Frameless Window](../api/frameless-window.md). - -## Two modes of rendering - -### GPU accelerated - -GPU accelerated rendering means that the GPU is used for composition. Because of -that the frame has to be copied from the GPU which requires more performance, -thus this mode is quite a bit slower than the other one. The benefit of this -mode that WebGL and 3D CSS animations are supported. - -### Software output device - -This mode uses a software output device for rendering in the CPU, so the frame -generation is much faster, thus this mode is preferred over the GPU accelerated -one. - -To enable this mode GPU acceleration has to be disabled by calling the -[`app.disableHardwareAcceleration()`][disablehardwareacceleration] API. - -## Usage - -``` javascript -const {app, BrowserWindow} = require('electron') - -app.disableHardwareAcceleration() - -let win -app.once('ready', () => { - win = new BrowserWindow({ - webPreferences: { - offscreen: true - } - }) - win.loadURL('http://github.com') - win.webContents.on('paint', (event, dirty, image) => { - // updateBitmap(dirty, image.getBitmap()) - }) - win.webContents.setFrameRate(30) -}) -``` - -[disablehardwareacceleration]: ../api/app.md#appdisablehardwareacceleration diff --git a/docs-translations/th-TH/tutorial/online-offline-events.md b/docs-translations/th-TH/tutorial/online-offline-events.md deleted file mode 100644 index bc9f7ecc94..0000000000 --- a/docs-translations/th-TH/tutorial/online-offline-events.md +++ /dev/null @@ -1,90 +0,0 @@ -# Online/Offline Event Detection - -Online and offline event detection can be implemented in the renderer process -using standard HTML5 APIs, as shown in the following example. - -_main.js_ - -```javascript -const {app, BrowserWindow} = require('electron') - -let onlineStatusWindow - -app.on('ready', () => { - onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false }) - onlineStatusWindow.loadURL(`file://${__dirname}/online-status.html`) -}) -``` - -_online-status.html_ - -```html - - - - - - -``` - -There may be instances where you want to respond to these events in the -main process as well. The main process however does not have a -`navigator` object and thus cannot detect these events directly. Using -Electron's inter-process communication utilities, the events can be forwarded -to the main process and handled as needed, as shown in the following example. - -_main.js_ - -```javascript -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`) -}) - -ipcMain.on('online-status-changed', (event, status) => { - console.log(status) -}) -``` - -_online-status.html_ - -```html - - - - - - -``` - -**NOTE:** If Electron is not able to connect to a local area network (LAN) or -a router, it is considered offline; all other conditions return `true`. -So while you can assume that Electron is offline when `navigator.onLine` -returns a `false` value, you cannot assume that a `true` value necessarily -means that Electron can access the internet. You could be getting false -positives, such as in cases where the computer is running a virtualization -software that has virtual ethernet adapters that are always "connected." -Therefore, if you really want to determine the internet access status of Electron, -you should develop additional means for checking. diff --git a/docs-translations/th-TH/tutorial/planned-breaking-changes.md b/docs-translations/th-TH/tutorial/planned-breaking-changes.md deleted file mode 100644 index e2d0e3a939..0000000000 --- a/docs-translations/th-TH/tutorial/planned-breaking-changes.md +++ /dev/null @@ -1,159 +0,0 @@ -# Planned Breaking API Changes - -The following list includes the APIs that will be removed in Electron 2.0. - -There is no timetable for when this release will occur but deprecation -warnings will be added at least 90 days beforehand. - -## `BrowserWindow` - -```js -// Deprecated -let optionsA = {webPreferences: {blinkFeatures: ''}} -let windowA = new BrowserWindow(optionsA) -// Replace with -let optionsB = {webPreferences: {enableBlinkFeatures: ''}} -let windowB = new BrowserWindow(optionsB) -``` - -## `clipboard` - -```js -// Deprecated -clipboard.readRtf() -// Replace with -clipboard.readRTF() - -// Deprecated -clipboard.writeRtf() -// Replace with -clipboard.writeRTF() - -// Deprecated -clipboard.readHtml() -// Replace with -clipboard.readHTML() - -// Deprecated -clipboard.writeHtml() -// Replace with -clipboard.writeHTML() -``` - -## `crashReporter` - -```js -// Deprecated -crashReporter.start({ - companyName: 'Crashly', - submitURL: 'https://crash.server.com', - autoSubmit: true -}) -// Replace with -crashReporter.start({ - companyName: 'Crashly', - submitURL: 'https://crash.server.com', - uploadToServer: true -}) -``` - -## `nativeImage` - -```js -// Deprecated -nativeImage.toPng() -// Replace with -nativeImage.toPNG() - -// Deprecated -nativeImage.toJpeg() -// Replace with -nativeImage.toJPEG() - -// Deprecated -nativeImage.createFromBuffer(buffer, 1.0) -// Replace with -nativeImage.createFromBuffer(buffer, { - scaleFactor: 1.0 -}) -``` - -## `process` - -```js -// Deprecated -process.versions['atom-shell'] -// Replace with -process.versions.electron -``` - -* `process.versions.electron` and `process.version.chrome` will be made - read-only properties for consistency with the other `process.versions` - properties set by Node. - -## `Tray` - -```js -// Deprecated -tray.setHighlightMode(true) -// Replace with -tray.setHighlightMode('on') - -// Deprecated -tray.setHighlightMode(false) -// Replace with -tray.setHighlightMode('off') -``` - -## `webContents` - -```js -// Deprecated -webContents.openDevTools({detach: true}) -// Replace with -webContents.openDevTools({mode: 'detach'}) -``` - -```js -// Deprecated -webContents.setZoomLevelLimits(1, 2) -// Replace with -webContents.setVisualZoomLevelLimits(1, 2) -``` - -## `webFrame` - -```js -// Deprecated -webFrame.setZoomLevelLimits(1, 2) -// Replace with -webFrame.setVisualZoomLevelLimits(1, 2) - -// Deprecated -webFrame.registerURLSchemeAsSecure('app') -// Replace with -protocol.registerStandardSchemes(['app'], {secure: true}) - -// Deprecated -webFrame.registerURLSchemeAsPrivileged('app', {secure: true}) -// Replace with -protocol.registerStandardSchemes(['app'], {secure: true}) -``` - -## `` - -```js -// Deprecated -webview.setZoomLevelLimits(1, 2) -// Replace with -webview.setVisualZoomLevelLimits(1, 2) -``` - -## Node Headers URL - -This is the URL specified as `disturl` in a `.npmrc` file or as the `--dist-url` -command line flag when building native Node modules. - -Deprecated: https://atom.io/download/atom-shell - -Replace with: https://atom.io/download/electron diff --git a/docs-translations/th-TH/tutorial/repl.md b/docs-translations/th-TH/tutorial/repl.md deleted file mode 100644 index 4f37c3a625..0000000000 --- a/docs-translations/th-TH/tutorial/repl.md +++ /dev/null @@ -1,26 +0,0 @@ -# REPL - -Read-Eval-Print-Loop (REPL) is a simple, interactive computer programming -environment that takes single user inputs (i.e. single expressions), evaluates -them, and returns the result to the user. - -The `repl` module provides a REPL implementation that can be accessed using: - -* Assuming you have `electron` or `electron-prebuilt` installed as a local - project dependency: - - ```sh - ./node_modules/.bin/electron --interactive - ``` -* Assuming you have `electron` or `electron-prebuilt` installed globally: - - ```sh - electron --interactive - ``` - -This only creates a REPL for the main process. You can use the Console -tab of the Dev Tools to get a REPL for the renderer processes. - -**Note:** `electron --interactive` is not available on Windows. - -More information can be found in the [Node.js REPL docs](https://nodejs.org/dist/latest/docs/api/repl.html). diff --git a/docs-translations/th-TH/tutorial/security.md b/docs-translations/th-TH/tutorial/security.md deleted file mode 100644 index b0ebf8dc9b..0000000000 --- a/docs-translations/th-TH/tutorial/security.md +++ /dev/null @@ -1,96 +0,0 @@ -# Security, Native Capabilities, and Your Responsibility - -As web developers, we usually enjoy the strong security net of the browser - the -risks associated with the code we write are relatively small. Our websites are -granted limited powers in a sandbox, and we trust that our users enjoy a browser -built by a large team of engineers that is able to quickly respond to newly -discovered security threats. - -When working with Electron, it is important to understand that Electron is not -a web browser. It allows you to build feature-rich desktop applications with -familiar web technologies, but your code wields much greater power. JavaScript -can access the filesystem, user shell, and more. This allows you to build -high quality native applications, but the inherent security risks scale with the -additional powers granted to your code. - -With that in mind, be aware that displaying arbitrary content from untrusted -sources poses a severe security risk that Electron is not intended to handle. -In fact, the most popular Electron apps (Atom, Slack, Visual Studio Code, etc) -display primarily local content (or trusted, secure remote content without Node -integration) – if your application executes code from an online source, it is -your responsibility to ensure that the code is not malicious. - -## Chromium Security Issues and Upgrades - -While Electron strives to support new versions of Chromium as soon as possible, -developers should be aware that upgrading is a serious undertaking - involving -hand-editing dozens or even hundreds of files. Given the resources and -contributions available today, Electron will often not be on the very latest -version of Chromium, lagging behind by either days or weeks. - -We feel that our current system of updating the Chromium component strikes an -appropriate balance between the resources we have available and the needs of the -majority of applications built on top of the framework. We definitely are -interested in hearing more about specific use cases from the people that build -things on top of Electron. Pull requests and contributions supporting this -effort are always very welcome. - -## Ignoring Above Advice - -A security issue exists whenever you receive code from a remote destination and -execute it locally. As an example, consider a remote website being displayed -inside a browser window. If an attacker somehow manages to change said content -(either by attacking the source directly, or by sitting between your app and -the actual destination), they will be able to execute native code on the user's -machine. - -> :warning: Under no circumstances should you load and execute remote code with -Node integration enabled. Instead, use only local files (packaged together with -your application) to execute Node code. To display remote content, use the -`webview` tag and make sure to disable the `nodeIntegration`. - -#### Checklist - -This is not bulletproof, but at the least, you should attempt the following: - -* Only display secure (https) content -* Disable the Node integration in all renderers that display remote content - (setting `nodeIntegration` to `false` in `webPreferences`) -* Enable context isolation in all rendererers that display remote content - (setting `contextIsolation` to `true` in `webPreferences`) -* Do not disable `webSecurity`. Disabling it will disable the same-origin policy. -* Define a [`Content-Security-Policy`](http://www.html5rocks.com/en/tutorials/security/content-security-policy/) -, and use restrictive rules (i.e. `script-src 'self'`) -* [Override and disable `eval`](https://github.com/nylas/N1/blob/0abc5d5defcdb057120d726b271933425b75b415/static/index.js#L6-L8) -, which allows strings to be executed as code. -* Do not set `allowDisplayingInsecureContent` to true. -* Do not set `allowRunningInsecureContent` to true. -* Do not enable `experimentalFeatures` or `experimentalCanvasFeatures` unless - you know what you're doing. -* Do not use `blinkFeatures` unless you know what you're doing. -* WebViews: Do not add the `nodeintegration` attribute. -* WebViews: Do not use `disablewebsecurity` -* WebViews: Do not use `allowpopups` -* WebViews: Do not use `insertCSS` or `executeJavaScript` with remote CSS/JS. - -Again, this list merely minimizes the risk, it does not remove it. If your goal -is to display a website, a browser will be a more secure option. - -## Buffer Global - -Node's [Buffer](https://nodejs.org/api/buffer.html) class is currently available -as a global even when the `nodeintegration` attribute is not added. You can -delete this in your app by doing the following in your `preload` script: - -```js -delete global.Buffer -``` - -Deleting it may break Node modules used in your preload script and app since -many libraries expect it to be a global instead of requiring it directly via: - -```js -const {Buffer} = require('buffer') -``` - -The `Buffer` global may be removed in future major versions of Electron. diff --git a/docs-translations/th-TH/tutorial/supported-platforms.md b/docs-translations/th-TH/tutorial/supported-platforms.md deleted file mode 100644 index 7474cec466..0000000000 --- a/docs-translations/th-TH/tutorial/supported-platforms.md +++ /dev/null @@ -1,31 +0,0 @@ -# Supported Platforms - -Following platforms are supported by Electron: - -### macOS - -Only 64bit binaries are provided for macOS, and the minimum macOS version -supported is macOS 10.9. - -### Windows - -Windows 7 and later are supported, older operating systems are not supported -(and do not work). - -Both `ia32` (`x86`) and `x64` (`amd64`) binaries are provided for Windows. -Please note, the `ARM` version of Windows is not supported for now. - -### Linux - -The prebuilt `ia32` (`i686`) and `x64` (`amd64`) binaries of Electron are built on -Ubuntu 12.04, the `arm` binary is built against ARM v7 with hard-float ABI and -NEON for Debian Wheezy. - -Whether the prebuilt binary can run on a distribution depends on whether the -distribution includes the libraries that Electron is linked to on the building -platform, so only Ubuntu 12.04 is guaranteed to work, but following platforms -are also verified to be able to run the prebuilt binaries of Electron: - -* Ubuntu 12.04 and later -* Fedora 21 -* Debian 8 diff --git a/docs-translations/th-TH/tutorial/testing-on-headless-ci.md b/docs-translations/th-TH/tutorial/testing-on-headless-ci.md deleted file mode 100644 index 557c358b12..0000000000 --- a/docs-translations/th-TH/tutorial/testing-on-headless-ci.md +++ /dev/null @@ -1,60 +0,0 @@ -# Testing on Headless CI Systems (Travis CI, Jenkins) - -Being based on Chromium, Electron requires a display driver to function. -If Chromium can't find a display driver, Electron will simply fail to launch - -and therefore not executing any of your tests, regardless of how you are running -them. Testing Electron-based apps on Travis, Circle, Jenkins or similar Systems -requires therefore a little bit of configuration. In essence, we need to use -a virtual display driver. - -## Configuring the Virtual Display Server - -First, install [Xvfb](https://en.wikipedia.org/wiki/Xvfb). -It's a virtual framebuffer, implementing the X11 display server protocol - -it performs all graphical operations in memory without showing any screen output, -which is exactly what we need. - -Then, create a virtual xvfb screen and export an environment variable -called DISPLAY that points to it. Chromium in Electron will automatically look -for `$DISPLAY`, so no further configuration of your app is required. -This step can be automated with Paul Betts's -[xvfb-maybe](https://github.com/paulcbetts/xvfb-maybe): Prepend your test -commands with `xvfb-maybe` and the little tool will automatically configure -xvfb, if required by the current system. On Windows or macOS, it will simply -do nothing. - -``` -## On Windows or macOS, this just invokes electron-mocha -## On Linux, if we are in a headless environment, this will be equivalent -## to xvfb-run electron-mocha ./test/*.js -xvfb-maybe electron-mocha ./test/*.js -``` - -### Travis CI - -On Travis, your `.travis.yml` should look roughly like this: - -```yml -addons: - apt: - packages: - - xvfb - -install: - - export DISPLAY=':99.0' - - Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & -``` - -### Jenkins - -For Jenkins, a [Xvfb plugin is available](https://wiki.jenkins-ci.org/display/JENKINS/Xvfb+Plugin). - -### Circle CI - -Circle CI is awesome and has xvfb and `$DISPLAY` -[already setup, so no further configuration is required](https://circleci.com/docs/environment#browsers). - -### AppVeyor - -AppVeyor runs on Windows, supporting Selenium, Chromium, Electron and similar -tools out of the box - no configuration is required. diff --git a/docs-translations/th-TH/tutorial/using-native-node-modules.md b/docs-translations/th-TH/tutorial/using-native-node-modules.md deleted file mode 100644 index 51b256ab10..0000000000 --- a/docs-translations/th-TH/tutorial/using-native-node-modules.md +++ /dev/null @@ -1,104 +0,0 @@ -# Using Native Node Modules - -The native Node modules are supported by Electron, but since Electron is very -likely to use a different V8 version from the Node binary installed in your -system, you have to manually specify the location of Electron's headers when -building native modules. - -## How to install native modules - -Three ways to install native modules: - -### Using `npm` - -By setting a few environment variables, you can use `npm` to install modules -directly. - -An example of installing all dependencies for Electron: - -```bash -# Electron's version. -export npm_config_target=1.2.3 -# The architecture of Electron, can be ia32 or x64. -export npm_config_arch=x64 -export npm_config_target_arch=x64 -# Download headers for Electron. -export npm_config_disturl=https://atom.io/download/electron -# Tell node-pre-gyp that we are building for Electron. -export npm_config_runtime=electron -# Tell node-pre-gyp to build module from source code. -export npm_config_build_from_source=true -# Install all dependencies, and store cache to ~/.electron-gyp. -HOME=~/.electron-gyp npm install -``` - -### Installing modules and rebuilding for Electron - -You can also choose to install modules like other Node projects, and then -rebuild the modules for Electron with the [`electron-rebuild`][electron-rebuild] -package. This module can get the version of Electron and handle the manual steps -of downloading headers and building native modules for your app. - -An example of installing `electron-rebuild` and then rebuild modules with it: - -```bash -npm install --save-dev electron-rebuild - -# Every time you run "npm install", run this: -./node_modules/.bin/electron-rebuild - -# On Windows if you have trouble, try: -.\node_modules\.bin\electron-rebuild.cmd -``` - -### Manually building for Electron - -If you are a developer developing a native module and want to test it against -Electron, you might want to rebuild the module for Electron manually. You can -use `node-gyp` directly to build for Electron: - -```bash -cd /path-to-module/ -HOME=~/.electron-gyp node-gyp rebuild --target=1.2.3 --arch=x64 --dist-url=https://atom.io/download/electron -``` - -The `HOME=~/.electron-gyp` changes where to find development headers. The -`--target=1.2.3` is version of Electron. The `--dist-url=...` specifies -where to download the headers. The `--arch=x64` says the module is built for -64bit system. - -## Troubleshooting - -If you installed a native module and found it was not working, you need to check -following things: - -* The architecture of module has to match Electron's architecture (ia32 or x64). -* After you upgraded Electron, you usually need to rebuild the modules. -* When in doubt, run `electron-rebuild` first. - -## Modules that rely on `prebuild` - -[`prebuild`](https://github.com/mafintosh/prebuild) provides a way to easily -publish native Node modules with prebuilt binaries for multiple versions of Node -and Electron. - -If modules provide binaries for the usage in Electron, make sure to omit -`--build-from-source` and the `npm_config_build_from_source` environment -variable in order to take full advantage of the prebuilt binaries. - -## Modules that rely on `node-pre-gyp` - -The [`node-pre-gyp` tool][node-pre-gyp] provides a way to deploy native Node -modules with prebuilt binaries, and many popular modules are using it. - -Usually those modules work fine under Electron, but sometimes when Electron uses -a newer version of V8 than Node, and there are ABI changes, bad things may -happen. So in general it is recommended to always build native modules from -source code. - -If you are following the `npm` way of installing modules, then this is done -by default, if not, you have to pass `--build-from-source` to `npm`, or set the -`npm_config_build_from_source` environment variable. - -[electron-rebuild]: https://github.com/paulcbetts/electron-rebuild -[node-pre-gyp]: https://github.com/mapbox/node-pre-gyp diff --git a/docs-translations/th-TH/tutorial/using-pepper-flash-plugin.md b/docs-translations/th-TH/tutorial/using-pepper-flash-plugin.md deleted file mode 100644 index 839f00b520..0000000000 --- a/docs-translations/th-TH/tutorial/using-pepper-flash-plugin.md +++ /dev/null @@ -1,82 +0,0 @@ -# Using Pepper Flash Plugin - -Electron supports the Pepper Flash plugin. To use the Pepper Flash plugin in -Electron, you should manually specify the location of the Pepper Flash plugin -and then enable it in your application. - -## Prepare a Copy of Flash Plugin - -On macOS and Linux, the details of the Pepper Flash plugin can be found by -navigating to `chrome://plugins` in the Chrome browser. Its location and version -are useful for Electron's Pepper Flash support. You can also copy it to another -location. - -## Add Electron Switch - -You can directly add `--ppapi-flash-path` and `--ppapi-flash-version` to the -Electron command line or by using the `app.commandLine.appendSwitch` method -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) { - case 'win32': - pluginName = 'pepflashplayer.dll' - break - case 'darwin': - pluginName = 'PepperFlashPlayer.plugin' - break - case 'linux': - pluginName = 'libpepflashplayer.so' - break -} -app.commandLine.appendSwitch('ppapi-flash-path', path.join(__dirname, pluginName)) - -// Optional: Specify flash version, for example, v17.0.0.169 -app.commandLine.appendSwitch('ppapi-flash-version', '17.0.0.169') - -app.on('ready', () => { - let win = new BrowserWindow({ - width: 800, - height: 600, - webPreferences: { - plugins: true - } - }) - win.loadURL(`file://${__dirname}/index.html`) - // Something else -}) -``` - -You can also try loading the system wide Pepper Flash plugin instead of shipping -the plugins yourself, its path can be received by calling -`app.getPath('pepperFlashSystemPlugin')`. - -## Enable Flash Plugin in a `` Tag - -Add `plugins` attribute to `` tag. - -```html - -``` - -## Troubleshooting - -You can check if Pepper Flash plugin was loaded by inspecting -`navigator.plugins` in the console of devtools (although you can't know if the -plugin's path is correct). - -The architecture of Pepper Flash plugin has to match Electron's one. On Windows, -a common error is to use 32bit version of Flash plugin against 64bit version of -Electron. - -On Windows the path passed to `--ppapi-flash-path` has to use `\` as path -delimiter, using POSIX-style paths will not work. - -For some operations, such as streaming media using RTMP, it is necessary to grant wider permissions to players’ `.swf` files. One way of accomplishing this, is to use [nw-flash-trust](https://github.com/szwacz/nw-flash-trust). diff --git a/docs-translations/th-TH/tutorial/using-selenium-and-webdriver.md b/docs-translations/th-TH/tutorial/using-selenium-and-webdriver.md deleted file mode 100644 index 464d1ce99f..0000000000 --- a/docs-translations/th-TH/tutorial/using-selenium-and-webdriver.md +++ /dev/null @@ -1,172 +0,0 @@ -# Using Selenium and WebDriver - -From [ChromeDriver - WebDriver for Chrome][chrome-driver]: - -> WebDriver is an open source tool for automated testing of web apps across many -> browsers. It provides capabilities for navigating to web pages, user input, -> JavaScript execution, and more. ChromeDriver is a standalone server which -> implements WebDriver's wire protocol for Chromium. It is being developed by -> members of the Chromium and WebDriver teams. - -## Setting up Spectron - -[Spectron][spectron] is the officially supported ChromeDriver testing framework -for Electron. It is built on top of [WebdriverIO](http://webdriver.io/) and -has helpers to access Electron APIs in your tests and bundles ChromeDriver. - -```bash -$ npm install --save-dev spectron -``` - -```javascript -// A simple test to verify a visible window is opened with a title -var Application = require('spectron').Application -var assert = require('assert') - -var app = new Application({ - path: '/Applications/MyApp.app/Contents/MacOS/MyApp' -}) - -app.start().then(function () { - // Check if the window is visible - return app.browserWindow.isVisible() -}).then(function (isVisible) { - // Verify the window is visible - assert.equal(isVisible, true) -}).then(function () { - // Get the window's title - return app.client.getTitle() -}).then(function (title) { - // Verify the window's title - assert.equal(title, 'My App') -}).catch(function (error) { - // Log any failures - console.error('Test failed', error.message) -}).then(function () { - // Stop the application - return app.stop() -}) -``` - -## Setting up with WebDriverJs - -[WebDriverJs](https://code.google.com/p/selenium/wiki/WebDriverJs) provides -a Node package for testing with web driver, we will use it as an example. - -### 1. Start ChromeDriver - -First you need to download the `chromedriver` binary, and run it: - -```bash -$ npm install electron-chromedriver -$ ./node_modules/.bin/chromedriver -Starting ChromeDriver (v2.10.291558) on port 9515 -Only local connections are allowed. -``` - -Remember the port number `9515`, which will be used later - -### 2. Install WebDriverJS - -```bash -$ npm install selenium-webdriver -``` - -### 3. Connect to ChromeDriver - -The usage of `selenium-webdriver` with Electron is basically the same with -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 driver = new webdriver.Builder() - // The "9515" is the port opened by chrome driver. - .usingServer('http://localhost:9515') - .withCapabilities({ - chromeOptions: { - // Here is the path to your Electron binary. - binary: '/Path-to-Your-App.app/Contents/MacOS/Electron' - } - }) - .forBrowser('electron') - .build() - -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) - -driver.quit() -``` - -## Setting up with WebdriverIO - -[WebdriverIO](http://webdriver.io/) provides a Node package for testing with web -driver. - -### 1. Start ChromeDriver - -First you need to download the `chromedriver` binary, and run it: - -```bash -$ npm install electron-chromedriver -$ ./node_modules/.bin/chromedriver --url-base=wd/hub --port=9515 -Starting ChromeDriver (v2.10.291558) on port 9515 -Only local connections are allowed. -``` - -Remember the port number `9515`, which will be used later - -### 2. Install WebdriverIO - -```bash -$ npm install webdriverio -``` - -### 3. Connect to chrome driver - -```javascript -const webdriverio = require('webdriverio') -const options = { - host: 'localhost', // Use localhost as chrome driver server - port: 9515, // "9515" is the port opened by chrome driver. - desiredCapabilities: { - browserName: 'chrome', - chromeOptions: { - binary: '/Path-to-Your-App/electron', // Path to your Electron binary. - args: [/* cli arguments */] // Optional, perhaps 'app=' + /path/to/your/app/ - } - } -} - -let client = webdriverio.remote(options) - -client - .init() - .url('http://google.com') - .setValue('#q', 'webdriverio') - .click('#btnG') - .getTitle().then((title) => { - console.log('Title was: ' + title) - }) - .end() -``` - -## Workflow - -To test your application without rebuilding Electron, simply -[place](https://github.com/electron/electron/blob/master/docs/tutorial/application-distribution.md) -your app source into Electron's resource directory. - -Alternatively, pass an argument to run with your electron binary that points to -your app's folder. This eliminates the need to copy-paste your app into -Electron's resource directory. - -[chrome-driver]: https://sites.google.com/a/chromium.org/chromedriver/ -[spectron]: http://electron.atom.io/spectron diff --git a/docs-translations/th-TH/tutorial/using-widevine-cdm-plugin.md b/docs-translations/th-TH/tutorial/using-widevine-cdm-plugin.md deleted file mode 100644 index 512da7a041..0000000000 --- a/docs-translations/th-TH/tutorial/using-widevine-cdm-plugin.md +++ /dev/null @@ -1,85 +0,0 @@ -# Using Widevine CDM Plugin - -In Electron you can use the Widevine CDM plugin shipped with Chrome browser. - -## Getting the plugin - -Electron doesn't ship with the Widevine CDM plugin for license reasons, to get -it, you need to install the official Chrome browser first, which should match -the architecture and Chrome version of the Electron build you use. - -**Note:** The major version of Chrome browser has to be the same with the Chrome -version used by Electron, otherwise the plugin will not work even though -`navigator.plugins` would show it has been loaded. - -### Windows & macOS - -Open `chrome://components/` in Chrome browser, find `WidevineCdm` and make -sure it is up to date, then you can find all the plugin binaries from the -`APP_DATA/Google/Chrome/WidevineCDM/VERSION/_platform_specific/PLATFORM_ARCH/` -directory. - -`APP_DATA` is system's location for storing app data, on Windows it is -`%LOCALAPPDATA%`, on macOS it is `~/Library/Application Support`. `VERSION` is -Widevine CDM plugin's version string, like `1.4.8.866`. `PLATFORM` is `mac` or -`win`. `ARCH` is `x86` or `x64`. - -On Windows the required binaries are `widevinecdm.dll` and -`widevinecdmadapter.dll`, on macOS they are `libwidevinecdm.dylib` and -`widevinecdmadapter.plugin`. You can copy them to anywhere you like, but they -have to be put together. - -### Linux - -On Linux the plugin binaries are shipped together with Chrome browser, you can -find them under `/opt/google/chrome`, the filenames are `libwidevinecdm.so` and -`libwidevinecdmadapter.so`. - -## Using the plugin - -After getting the plugin files, you should pass the `widevinecdmadapter`'s path -to Electron with `--widevine-cdm-path` command line switch, and the plugin's -version with `--widevine-cdm-version` switch. - -**Note:** Though only the `widevinecdmadapter` binary is passed to Electron, the -`widevinecdm` binary has to be put aside it. - -The command line switches have to be passed before the `ready` event of `app` -module gets emitted, and the page that uses this plugin must have plugin -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') -// The version of plugin can be got from `chrome://plugins` page in Chrome. -app.commandLine.appendSwitch('widevine-cdm-version', '1.4.8.866') - -let win = null -app.on('ready', () => { - win = new BrowserWindow({ - webPreferences: { - // The `plugins` have to be enabled. - plugins: true - } - }) - win.show() -}) -``` - -## Verifying the plugin - -To verify whether the plugin works, you can use following ways: - -* Open devtools and check whether `navigator.plugins` includes the Widevine -CDM plugin. -* Open https://shaka-player-demo.appspot.com/ and load a manifest that uses -`Widevine`. -* Open http://www.dash-player.com/demo/drm-test-area/, check whether the page -says `bitdash uses Widevine in your browser`, then play the video. diff --git a/docs-translations/th-TH/tutorial/windows-store-guide.md b/docs-translations/th-TH/tutorial/windows-store-guide.md deleted file mode 100644 index 2075691baa..0000000000 --- a/docs-translations/th-TH/tutorial/windows-store-guide.md +++ /dev/null @@ -1,161 +0,0 @@ -# Windows Store Guide - -With Windows 8, the good old win32 executable got a new sibling: The Universal -Windows Platform. The new `.appx` format does not only enable a number of new -powerful APIs like Cortana or Push Notifications, but through the Windows Store, -also simplifies installation and updating. - -Microsoft [developed a tool that compiles Electron apps as `.appx` packages][electron-windows-store], -enabling developers to use some of the goodies found in the new application -model. This guide explains how to use it - and what the capabilities and -limitations of an Electron AppX package are. - -## Background and Requirements - -Windows 10 "Anniversary Update" is able to run win32 `.exe` binaries by -launching them together with a virtualized filesystem and registry. Both are -created during compilation by running app and installer inside a Windows -Container, allowing Windows to identify exactly which modifications to the -operating system are done during installation. Pairing the executable with a -virtual filesystem and a virtual registry allows Windows to enable one-click -installation and uninstallation. - -In addition, the exe is launched inside the appx model - meaning that it can use -many of the APIs available to the Universal Windows Platform. To gain even more -capabilities, an Electron app can pair up with an invisible UWP background task -launched together with the `exe` - sort of launched as a sidekick to run tasks -in the background, receive push notifications, or to communicate with other UWP -applications. - -To compile any existing Electron app, ensure that you have the following -requirements: - -* Windows 10 with Anniversary Update (released August 2nd, 2016) -* The Windows 10 SDK, [downloadable here][windows-sdk] -* At least Node 4 (to check, run `node -v`) - -Then, go and install the `electron-windows-store` CLI: - -``` -npm install -g electron-windows-store -``` - -## Step 1: Package Your Electron Application - -Package the application using [electron-packager][electron-packager] (or a similar tool). -Make sure to remove `node_modules` that you don't need in your final application, since -any module you don't actually need will just increase your application's size. - -The output should look roughly like this: - -``` -├── Ghost.exe -├── LICENSE -├── content_resources_200_percent.pak -├── content_shell.pak -├── d3dcompiler_47.dll -├── ffmpeg.dll -├── icudtl.dat -├── libEGL.dll -├── libGLESv2.dll -├── locales -│   ├── am.pak -│   ├── ar.pak -│   ├── [...] -├── natives_blob.bin -├── node.dll -├── resources -│   ├── app -│   └── atom.asar -├── snapshot_blob.bin -├── squirrel.exe -├── ui_resources_200_percent.pak -└── xinput1_3.dll -``` - -## Step 2: Running electron-windows-store - -From an elevated PowerShell (run it "as Administrator"), run -`electron-windows-store` with the required parameters, passing both the input -and output directories, the app's name and version, and confirmation that -`node_modules` should be flattened. - -``` -electron-windows-store ` - --input-directory C:\myelectronapp ` - --output-directory C:\output\myelectronapp ` - --flatten true ` - --package-version 1.0.0.0 ` - --package-name myelectronapp -``` - -Once executed, the tool goes to work: It accepts your Electron app as an input, -flattening the `node_modules`. Then, it archives your application as `app.zip`. -Using an installer and a Windows Container, the tool creates an "expanded" AppX -package - including the Windows Application Manifest (`AppXManifest.xml`) as -well as the virtual file system and the virtual registry inside your output -folder. - -Once the expanded AppX files are created, the tool uses the Windows App Packager -(`MakeAppx.exe`) to create a single-file AppX package from those files on disk. -Finally, the tool can be used to create a trusted certificate on your computer -to sign the new AppX package. With the signed AppX package, the CLI can also -automatically install the package on your machine. - -## Step 3: Using the AppX Package - -In order to run your package, your users will need Windows 10 with the so-called -"Anniversary Update" - details on how to update Windows can be found [here][how-to-update]. - -In opposition to traditional UWP apps, packaged apps currently need to undergo a -manual verification process, for which you can apply [here][centennial-campaigns]. -In the meantime, all users will be able to just install your package by double-clicking it, -so a submission to the store might not be necessary if you're simply looking for an -easier installation method. In managed environments (usually enterprises), the -`Add-AppxPackage` [PowerShell Cmdlet can be used to install it in an automated fashion][add-appxpackage]. - -Another important limitation is that the compiled AppX package still contains a -win32 executable - and will therefore not run on Xbox, HoloLens, or Phones. - -## Optional: Add UWP Features using a BackgroundTask -You can pair your Electron app up with an invisible UWP background task that -gets to make full use of Windows 10 features - like push notifications, -Cortana integration, or live tiles. - -To check out how an Electron app that uses a background task to send toast -notifications and live tiles, [check out the Microsoft-provided sample][background-task]. - -## Optional: Convert using Container Virtualiziation - -To generate the AppX package, the `electron-windows-store` CLI uses a template -that should work for most Electron apps. However, if you are using a custom -installer, or should you experience any trouble with the generated package, you -can attempt to create a package using compilation with a Windows Container - in -that mode, the CLI will install and run your application in blank Windows Container -to determine what modifications your application is exactly doing to the operating -system. - -Before running the CLI for the, you will have to setup the "Windows Desktop App -Converter". This will take a few minutes, but don't worry - you only have to do -this once. Download and Desktop App Converter from [here][app-converter]. -You will receive two files: `DesktopAppConverter.zip` and `BaseImage-14316.wim`. - -1. Unzip `DesktopAppConverter.zip`. From an elevated PowerShell (opened with - "run as Administrator", ensure that your systems execution policy allows us to - run everything we intend to run by calling `Set-ExecutionPolicy bypass`. -2. Then, run the installation of the Desktop App Converter, passing in the - location of the Windows base Image (downloaded as `BaseImage-14316.wim`), by - calling `.\DesktopAppConverter.ps1 -Setup -BaseImage .\BaseImage-14316.wim`. -3. If running the above command prompts you for a reboot, please restart your - machine and run the above command again after a successful restart. - -Once installation succeeded, you can move on to compiling your Electron app. - -[windows-sdk]: https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk -[app-converter]: https://www.microsoft.com/en-us/download/details.aspx?id=51691 -[add-appxpackage]: https://technet.microsoft.com/en-us/library/hh856048.aspx -[electron-packager]: https://github.com/electron-userland/electron-packager -[electron-windows-store]: https://github.com/catalystcode/electron-windows-store -[background-task]: https://github.com/felixrieseberg/electron-uwp-background -[centennial-campaigns]: https://developer.microsoft.com/en-us/windows/projects/campaigns/desktop-bridge -[how-to-update]: https://blogs.windows.com/windowsexperience/2016/08/02/how-to-get-the-windows-10-anniversary-update From 4d7d72256dab53d05e3df43c2a41979e927009a5 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 10 Feb 2017 12:18:24 -0800 Subject: [PATCH 112/925] Remove doc linter warnings --- docs-translations/th-TH/tutorial/accessibility.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs-translations/th-TH/tutorial/accessibility.md b/docs-translations/th-TH/tutorial/accessibility.md index b1687e552b..527a57e7ed 100644 --- a/docs-translations/th-TH/tutorial/accessibility.md +++ b/docs-translations/th-TH/tutorial/accessibility.md @@ -12,16 +12,16 @@ ### Spectron -ในการทดสอบเฟรมเวิร์ค Spectron นั้น +ในการทดสอบเฟรมเวิร์ค Spectron นั้น คุณจะใช้วิธีการแก้ไขทุกๆหน้าต่าง และ แท็ก `` ในแอพพิเคชั่นของคุณได้ ยกตัวอย่างเช่น: ```javascript app.client.autidAccessibility().then(function (audit) { - if (audit.failed) { - console.error(audit.message) - } + if (audit.failed) { + console.error(audit.message) + } }) ``` @@ -29,11 +29,11 @@ app.client.autidAccessibility().then(function (audit) { ### Devtron -ใน Devtron นั้น จะมีแท็ปการเข้าถึง ซึ่งจะทำให้คุณสามารถจัดการเพจในแอพของคุณได้ +ใน Devtron นั้น จะมีแท็ปการเข้าถึง ซึ่งจะทำให้คุณสามารถจัดการเพจในแอพของคุณได้ ![devtron screenshot](https://cloud.githubusercontent.com/assets/1305617/17156618/9f9bcd72-533f-11e6-880d-389115f40a2a.png) -ทั้งสองเครื่องมือใช้ [Accessibility Developer Tools](https://github.com/GoogleChrome/accessibility-developer-tools) ซึ่งเป็น library ที่สร้างขึ้นโดย Google เพื่อ Chrome +ทั้งสองเครื่องมือใช้ [Accessibility Developer Tools](https://github.com/GoogleChrome/accessibility-developer-tools) ซึ่งเป็น library ที่สร้างขึ้นโดย Google เพื่อ Chrome คุณสามารถศึกษาเพิ่มเติมเกี่ยวกับมันได้ที่ [รีโปนี้](https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules) From b27c94368d72896ec9a09fa6c37170d7f57fd9d0 Mon Sep 17 00:00:00 2001 From: deepak1556 Date: Sun, 29 Jan 2017 19:43:20 +0530 Subject: [PATCH 113/925] webContents: set zoom levels with HostZoomMap --- atom/browser/api/atom_api_web_contents.cc | 23 +++++++++++++++++++++++ atom/browser/api/atom_api_web_contents.h | 6 ++++++ lib/browser/api/web-contents.js | 7 +------ 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 9d5c8f8684..74d5da416a 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -49,6 +49,7 @@ #include "content/browser/web_contents/web_contents_impl.h" #include "content/common/view_messages.h" #include "content/public/browser/favicon_status.h" +#include "content/public/browser/host_zoom_map.h" #include "content/public/browser/native_web_keyboard_event.h" #include "content/public/browser/navigation_details.h" #include "content/public/browser/navigation_entry.h" @@ -1498,6 +1499,24 @@ void WebContents::Invalidate() { osr_rwhv->Invalidate(); } +void WebContents::SetZoomLevel(double level) { + content::HostZoomMap::SetZoomLevel(web_contents(), level); +} + +double WebContents::GetZoomLevel() { + return content::HostZoomMap::GetZoomLevel(web_contents()); +} + +void WebContents::SetZoomFactor(double factor) { + auto level = content::ZoomFactorToZoomLevel(factor); + SetZoomLevel(level); +} + +double WebContents::GetZoomFactor() { + auto level = GetZoomLevel(); + return content::ZoomLevelToZoomFactor(level); +} + v8::Local WebContents::GetWebPreferences(v8::Isolate* isolate) { WebContentsPreferences* web_preferences = WebContentsPreferences::FromWebContents(web_contents()); @@ -1626,6 +1645,10 @@ void WebContents::BuildPrototype(v8::Isolate* isolate, .SetMethod("setFrameRate", &WebContents::SetFrameRate) .SetMethod("getFrameRate", &WebContents::GetFrameRate) .SetMethod("invalidate", &WebContents::Invalidate) + .SetMethod("setZoomLevel", &WebContents::SetZoomLevel) + .SetMethod("getZoomLevel", &WebContents::GetZoomLevel) + .SetMethod("setZoomFactor", &WebContents::SetZoomFactor) + .SetMethod("getZoomFactor", &WebContents::GetZoomFactor) .SetMethod("getType", &WebContents::GetType) .SetMethod("getWebPreferences", &WebContents::GetWebPreferences) .SetMethod("getOwnerBrowserWindow", &WebContents::GetOwnerBrowserWindow) diff --git a/atom/browser/api/atom_api_web_contents.h b/atom/browser/api/atom_api_web_contents.h index 81472ccd97..f245040e9f 100644 --- a/atom/browser/api/atom_api_web_contents.h +++ b/atom/browser/api/atom_api_web_contents.h @@ -174,6 +174,12 @@ class WebContents : public mate::TrackableObject, int GetFrameRate() const; void Invalidate(); + // Methods for zoom handling. + void SetZoomLevel(double level); + double GetZoomLevel(); + void SetZoomFactor(double factor); + double GetZoomFactor(); + // Callback triggered on permission response. void OnEnterFullscreenModeForTab(content::WebContents* source, const GURL& origin, diff --git a/lib/browser/api/web-contents.js b/lib/browser/api/web-contents.js index a92ef7603b..c94bd768c0 100644 --- a/lib/browser/api/web-contents.js +++ b/lib/browser/api/web-contents.js @@ -105,15 +105,10 @@ const webFrameMethods = [ 'insertText', 'setLayoutZoomLevelLimits', 'setVisualZoomLevelLimits', - 'setZoomFactor', - 'setZoomLevel', // TODO(kevinsawicki): Remove in 2.0, deprecate before then with warnings 'setZoomLevelLimits' ] -const webFrameMethodsWithResult = [ - 'getZoomFactor', - 'getZoomLevel' -] +const webFrameMethodsWithResult = [] const asyncWebFrameMethods = function (requestId, method, callback, ...args) { return new Promise((resolve, reject) => { From 07794a58aa53da4eee29ce4821b094423803fd6b Mon Sep 17 00:00:00 2001 From: deepak1556 Date: Mon, 30 Jan 2017 00:38:28 +0530 Subject: [PATCH 114/925] use zoom factor webpreference option when required. * When setzoomlevel is not called for the host. * When there is no zoom preference for the host. --- atom/browser/api/atom_api_web_contents.cc | 35 ++++++++++++++++++++-- atom/browser/api/atom_api_web_contents.h | 8 +++++ atom/browser/web_contents_preferences.cc | 7 ----- atom/renderer/atom_render_view_observer.cc | 12 -------- 4 files changed, 41 insertions(+), 21 deletions(-) diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 74d5da416a..fe73164d8b 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -68,6 +68,7 @@ #include "content/public/common/context_menu_params.h" #include "native_mate/dictionary.h" #include "native_mate/object_template_builder.h" +#include "net/base/url_util.h" #include "net/url_request/url_request_context.h" #include "third_party/WebKit/public/platform/WebInputEvent.h" #include "third_party/WebKit/public/web/WebFindOptions.h" @@ -252,7 +253,8 @@ WebContents::WebContents(v8::Isolate* isolate, type_(type), request_id_(0), background_throttling_(true), - enable_devtools_(true) { + enable_devtools_(true), + zoom_factor_(content::kMinimumZoomFactor) { if (type == REMOTE) { web_contents->SetUserAgentOverride(GetBrowserContext()->GetUserAgent()); @@ -272,9 +274,11 @@ WebContents::WebContents(v8::Isolate* isolate, type_(BROWSER_WINDOW), request_id_(0), background_throttling_(true), - enable_devtools_(true) { + enable_devtools_(true), + zoom_factor_(content::kMinimumZoomFactor) { // Read options. options.Get("backgroundThrottling", &background_throttling_); + options.Get("zoomFactor", &zoom_factor_); // FIXME(zcbenz): We should read "type" parameter for better design, but // on Windows we have encountered a compiler bug that if we read "type" @@ -734,6 +738,8 @@ void WebContents::DidFinishNavigation( auto url = navigation_handle->GetURL(); bool is_in_page = navigation_handle->IsSamePage(); if (is_main_frame && !is_in_page) { + // Set initial zoom factor if needed. + SetZoomFactorIfNeeded(url); Emit("did-navigate", url); } else if (is_in_page) { Emit("did-navigate-in-page", url, is_main_frame); @@ -1500,6 +1506,15 @@ void WebContents::Invalidate() { } void WebContents::SetZoomLevel(double level) { + auto factor = content::ZoomLevelToZoomFactor(level); + if (!content::ZoomValuesEqual(zoom_factor_, factor)) { + content::NavigationEntry* entry = + web_contents()->GetController().GetLastCommittedEntry(); + if (entry) { + std::string host = net::GetHostOrSpecFromURL(entry->GetURL()); + host_zoom_factor_[host] = factor; + } + } content::HostZoomMap::SetZoomLevel(web_contents(), level); } @@ -1517,6 +1532,22 @@ double WebContents::GetZoomFactor() { return content::ZoomLevelToZoomFactor(level); } +void WebContents::SetZoomFactorIfNeeded(const GURL& url) { + if (zoom_factor_ == content::kMinimumZoomFactor) + return; + + std::string host = net::GetHostOrSpecFromURL(url); + double zoom_factor = zoom_factor_; + auto it = host_zoom_factor_.find(host); + if (it != host_zoom_factor_.end()) + zoom_factor = it->second; + auto level = content::ZoomFactorToZoomLevel(zoom_factor); + if (content::ZoomValuesEqual(level, GetZoomLevel())) + return; + + SetZoomLevel(level); +} + v8::Local WebContents::GetWebPreferences(v8::Isolate* isolate) { WebContentsPreferences* web_preferences = WebContentsPreferences::FromWebContents(web_contents()); diff --git a/atom/browser/api/atom_api_web_contents.h b/atom/browser/api/atom_api_web_contents.h index f245040e9f..be93c4c131 100644 --- a/atom/browser/api/atom_api_web_contents.h +++ b/atom/browser/api/atom_api_web_contents.h @@ -349,11 +349,16 @@ class WebContents : public mate::TrackableObject, const base::ListValue& args, IPC::Message* message); + // Called after committing a navigation, to set the zoom + // factor. + void SetZoomFactorIfNeeded(const GURL& url); + v8::Global session_; v8::Global devtools_web_contents_; v8::Global debugger_; std::unique_ptr guest_delegate_; + std::map host_zoom_factor_; // The host webcontents that may contain this webcontents. WebContents* embedder_; @@ -370,6 +375,9 @@ class WebContents : public mate::TrackableObject, // Whether to enable devtools. bool enable_devtools_; + // Initial zoom factor. + double zoom_factor_; + DISALLOW_COPY_AND_ASSIGN(WebContents); }; diff --git a/atom/browser/web_contents_preferences.cc b/atom/browser/web_contents_preferences.cc index 1182e2859f..c6527dd6f6 100644 --- a/atom/browser/web_contents_preferences.cc +++ b/atom/browser/web_contents_preferences.cc @@ -130,13 +130,6 @@ void WebContentsPreferences::AppendExtraCommandLineSwitches( if (web_preferences.GetString(options::kBackgroundColor, &color)) command_line->AppendSwitchASCII(switches::kBackgroundColor, color); - // The zoom factor. - double zoom_factor = 1.0; - if (web_preferences.GetDouble(options::kZoomFactor, &zoom_factor) && - zoom_factor != 1.0) - command_line->AppendSwitchASCII(switches::kZoomFactor, - base::DoubleToString(zoom_factor)); - // --guest-instance-id, which is used to identify guest WebContents. int guest_instance_id = 0; if (web_preferences.GetInteger(options::kGuestInstanceID, &guest_instance_id)) diff --git a/atom/renderer/atom_render_view_observer.cc b/atom/renderer/atom_render_view_observer.cc index b96c6ea672..a68238ba4c 100644 --- a/atom/renderer/atom_render_view_observer.cc +++ b/atom/renderer/atom_render_view_observer.cc @@ -14,7 +14,6 @@ #include "atom/common/api/event_emitter_caller.h" #include "atom/common/native_mate_converters/value_converter.h" #include "atom/common/node_includes.h" -#include "atom/common/options_switches.h" #include "atom/renderer/atom_renderer_client.h" #include "base/command_line.h" #include "base/strings/string_number_conversions.h" @@ -117,17 +116,6 @@ void AtomRenderViewObserver::EmitIPCEvent(blink::WebFrame* frame, void AtomRenderViewObserver::DidCreateDocumentElement( blink::WebLocalFrame* frame) { document_created_ = true; - - // Read --zoom-factor from command line. - std::string zoom_factor_str = base::CommandLine::ForCurrentProcess()-> - GetSwitchValueASCII(switches::kZoomFactor); - if (zoom_factor_str.empty()) - return; - double zoom_factor; - if (!base::StringToDouble(zoom_factor_str, &zoom_factor)) - return; - double zoom_level = blink::WebView::zoomFactorToZoomLevel(zoom_factor); - frame->view()->setZoomLevel(zoom_level); } void AtomRenderViewObserver::DraggableRegionsChanged(blink::WebFrame* frame) { From 63c0e4cbb1865b493e6b049cf1316901e8042c65 Mon Sep 17 00:00:00 2001 From: deepak1556 Date: Mon, 30 Jan 2017 16:48:40 +0530 Subject: [PATCH 115/925] Add ZoomController to manager zoom changes for webcontents --- atom/browser/api/atom_api_web_contents.cc | 59 +++----- atom/browser/api/atom_api_web_contents.h | 14 +- atom/browser/api/atom_api_web_view_manager.cc | 7 + atom/browser/web_contents_zoom_controller.cc | 139 ++++++++++++++++++ atom/browser/web_contents_zoom_controller.h | 73 +++++++++ atom/browser/web_view_guest_delegate.cc | 13 ++ atom/browser/web_view_guest_delegate.h | 12 +- filenames.gypi | 2 + lib/browser/guest-view-manager.js | 2 +- lib/renderer/web-view/web-view.js | 1 - 10 files changed, 269 insertions(+), 53 deletions(-) create mode 100644 atom/browser/web_contents_zoom_controller.cc create mode 100644 atom/browser/web_contents_zoom_controller.h diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index fe73164d8b..8e513923de 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -22,6 +22,7 @@ #include "atom/browser/ui/drag_util.h" #include "atom/browser/web_contents_permission_helper.h" #include "atom/browser/web_contents_preferences.h" +#include "atom/browser/web_contents_zoom_controller.h" #include "atom/browser/web_view_guest_delegate.h" #include "atom/common/api/api_messages.h" #include "atom/common/api/event_emitter_caller.h" @@ -49,7 +50,6 @@ #include "content/browser/web_contents/web_contents_impl.h" #include "content/common/view_messages.h" #include "content/public/browser/favicon_status.h" -#include "content/public/browser/host_zoom_map.h" #include "content/public/browser/native_web_keyboard_event.h" #include "content/public/browser/navigation_details.h" #include "content/public/browser/navigation_entry.h" @@ -68,7 +68,6 @@ #include "content/public/common/context_menu_params.h" #include "native_mate/dictionary.h" #include "native_mate/object_template_builder.h" -#include "net/base/url_util.h" #include "net/url_request/url_request_context.h" #include "third_party/WebKit/public/platform/WebInputEvent.h" #include "third_party/WebKit/public/web/WebFindOptions.h" @@ -249,13 +248,12 @@ WebContents::WebContents(v8::Isolate* isolate, content::WebContents* web_contents, Type type) : content::WebContentsObserver(web_contents), + zoom_controller_(nullptr), embedder_(nullptr), type_(type), request_id_(0), background_throttling_(true), - enable_devtools_(true), - zoom_factor_(content::kMinimumZoomFactor) { - + enable_devtools_(true) { if (type == REMOTE) { web_contents->SetUserAgentOverride(GetBrowserContext()->GetUserAgent()); Init(isolate); @@ -268,17 +266,15 @@ WebContents::WebContents(v8::Isolate* isolate, } } -WebContents::WebContents(v8::Isolate* isolate, - const mate::Dictionary& options) - : embedder_(nullptr), +WebContents::WebContents(v8::Isolate* isolate, const mate::Dictionary& options) + : zoom_controller_(nullptr), + embedder_(nullptr), type_(BROWSER_WINDOW), request_id_(0), background_throttling_(true), - enable_devtools_(true), - zoom_factor_(content::kMinimumZoomFactor) { + enable_devtools_(true) { // Read options. options.Get("backgroundThrottling", &background_throttling_); - options.Get("zoomFactor", &zoom_factor_); // FIXME(zcbenz): We should read "type" parameter for better design, but // on Windows we have encountered a compiler bug that if we read "type" @@ -350,10 +346,16 @@ void WebContents::InitWithSessionAndOptions(v8::Isolate* isolate, // Save the preferences in C++. new WebContentsPreferences(web_contents, options); - // Intialize permission helper. + // Initialize permission helper. WebContentsPermissionHelper::CreateForWebContents(web_contents); - // Intialize security state client. + // Initialize security state client. SecurityStateTabHelper::CreateForWebContents(web_contents); + // Initialize zoom controller. + WebContentsZoomController::CreateForWebContents(web_contents); + zoom_controller_ = WebContentsZoomController::FromWebContents(web_contents); + double zoom_factor; + if (options.Get("zoomFactor", &zoom_factor)) + zoom_controller_->SetDefaultZoomFactor(zoom_factor); web_contents->SetUserAgentOverride(GetBrowserContext()->GetUserAgent()); @@ -738,8 +740,6 @@ void WebContents::DidFinishNavigation( auto url = navigation_handle->GetURL(); bool is_in_page = navigation_handle->IsSamePage(); if (is_main_frame && !is_in_page) { - // Set initial zoom factor if needed. - SetZoomFactorIfNeeded(url); Emit("did-navigate", url); } else if (is_in_page) { Emit("did-navigate-in-page", url, is_main_frame); @@ -1506,20 +1506,11 @@ void WebContents::Invalidate() { } void WebContents::SetZoomLevel(double level) { - auto factor = content::ZoomLevelToZoomFactor(level); - if (!content::ZoomValuesEqual(zoom_factor_, factor)) { - content::NavigationEntry* entry = - web_contents()->GetController().GetLastCommittedEntry(); - if (entry) { - std::string host = net::GetHostOrSpecFromURL(entry->GetURL()); - host_zoom_factor_[host] = factor; - } - } - content::HostZoomMap::SetZoomLevel(web_contents(), level); + zoom_controller_->SetZoomLevel(level); } double WebContents::GetZoomLevel() { - return content::HostZoomMap::GetZoomLevel(web_contents()); + return zoom_controller_->GetZoomLevel(); } void WebContents::SetZoomFactor(double factor) { @@ -1532,22 +1523,6 @@ double WebContents::GetZoomFactor() { return content::ZoomLevelToZoomFactor(level); } -void WebContents::SetZoomFactorIfNeeded(const GURL& url) { - if (zoom_factor_ == content::kMinimumZoomFactor) - return; - - std::string host = net::GetHostOrSpecFromURL(url); - double zoom_factor = zoom_factor_; - auto it = host_zoom_factor_.find(host); - if (it != host_zoom_factor_.end()) - zoom_factor = it->second; - auto level = content::ZoomFactorToZoomLevel(zoom_factor); - if (content::ZoomValuesEqual(level, GetZoomLevel())) - return; - - SetZoomLevel(level); -} - v8::Local WebContents::GetWebPreferences(v8::Isolate* isolate) { WebContentsPreferences* web_preferences = WebContentsPreferences::FromWebContents(web_contents()); diff --git a/atom/browser/api/atom_api_web_contents.h b/atom/browser/api/atom_api_web_contents.h index be93c4c131..c65560a63d 100644 --- a/atom/browser/api/atom_api_web_contents.h +++ b/atom/browser/api/atom_api_web_contents.h @@ -39,6 +39,7 @@ namespace atom { struct SetSizeParams; class AtomBrowserContext; +class WebContentsZoomController; class WebViewGuestDelegate; namespace api { @@ -206,6 +207,8 @@ class WebContents : public mate::TrackableObject, v8::Local DevToolsWebContents(v8::Isolate* isolate); v8::Local Debugger(v8::Isolate* isolate); + WebContentsZoomController* GetZoomController() { return zoom_controller_; } + protected: WebContents(v8::Isolate* isolate, content::WebContents* web_contents, @@ -349,20 +352,18 @@ class WebContents : public mate::TrackableObject, const base::ListValue& args, IPC::Message* message); - // Called after committing a navigation, to set the zoom - // factor. - void SetZoomFactorIfNeeded(const GURL& url); - v8::Global session_; v8::Global devtools_web_contents_; v8::Global debugger_; std::unique_ptr guest_delegate_; - std::map host_zoom_factor_; // The host webcontents that may contain this webcontents. WebContents* embedder_; + // The zoom controller for this webContents. + WebContentsZoomController* zoom_controller_; + // The type of current WebContents. Type type_; @@ -375,9 +376,6 @@ class WebContents : public mate::TrackableObject, // Whether to enable devtools. bool enable_devtools_; - // Initial zoom factor. - double zoom_factor_; - DISALLOW_COPY_AND_ASSIGN(WebContents); }; diff --git a/atom/browser/api/atom_api_web_view_manager.cc b/atom/browser/api/atom_api_web_view_manager.cc index 1586c3a10d..33205b80c6 100644 --- a/atom/browser/api/atom_api_web_view_manager.cc +++ b/atom/browser/api/atom_api_web_view_manager.cc @@ -3,6 +3,7 @@ // found in the LICENSE file. #include "atom/browser/web_contents_preferences.h" +#include "atom/browser/web_contents_zoom_controller.h" #include "atom/browser/web_view_manager.h" #include "atom/common/native_mate_converters/content_converter.h" #include "atom/common/native_mate_converters/value_converter.h" @@ -24,6 +25,12 @@ void AddGuest(int guest_instance_id, manager->AddGuest(guest_instance_id, element_instance_id, embedder, guest_web_contents); + double zoom_factor; + if (options.GetDouble("zoomFactor", &zoom_factor)) { + atom::WebContentsZoomController::FromWebContents(guest_web_contents) + ->SetDefaultZoomFactor(zoom_factor); + } + WebContentsPreferences::FromWebContents(guest_web_contents)->Merge(options); } diff --git a/atom/browser/web_contents_zoom_controller.cc b/atom/browser/web_contents_zoom_controller.cc new file mode 100644 index 0000000000..d0a8975eae --- /dev/null +++ b/atom/browser/web_contents_zoom_controller.cc @@ -0,0 +1,139 @@ +// Copyright (c) 2017 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/web_contents_zoom_controller.h" + +#include "content/public/browser/navigation_details.h" +#include "content/public/browser/navigation_entry.h" +#include "content/public/browser/navigation_handle.h" +#include "content/public/browser/render_process_host.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/web_contents.h" +#include "content/public/common/page_type.h" +#include "content/public/common/page_zoom.h" +#include "net/base/url_util.h" + +DEFINE_WEB_CONTENTS_USER_DATA_KEY(atom::WebContentsZoomController); + +namespace atom { + +WebContentsZoomController::WebContentsZoomController( + content::WebContents* web_contents) + : content::WebContentsObserver(web_contents) { + default_zoom_factor_ = content::kEpsilon; + host_zoom_map_ = content::HostZoomMap::GetForWebContents(web_contents); + zoom_subscription_ = host_zoom_map_->AddZoomLevelChangedCallback(base::Bind( + &WebContentsZoomController::OnZoomLevelChanged, base::Unretained(this))); +} + +WebContentsZoomController::~WebContentsZoomController() {} + +void WebContentsZoomController::AddObserver( + WebContentsZoomController::Observer* observer) { + observers_.AddObserver(observer); +} + +void WebContentsZoomController::RemoveObserver( + WebContentsZoomController::Observer* observer) { + observers_.RemoveObserver(observer); +} + +void WebContentsZoomController::SetZoomLevel(double level) { + if (!web_contents()->GetRenderViewHost()->IsRenderViewLive() || + content::ZoomValuesEqual(GetZoomLevel(), level)) + return; + auto new_zoom_factor = content::ZoomLevelToZoomFactor(level); + content::NavigationEntry* entry = + web_contents()->GetController().GetLastCommittedEntry(); + if (entry) { + std::string host = net::GetHostOrSpecFromURL(entry->GetURL()); + // When new zoom level varies from kZoomFactor, it takes preference. + if (!content::ZoomValuesEqual(GetDefaultZoomFactor(), new_zoom_factor)) + host_zoom_factor_[host] = new_zoom_factor; + content::HostZoomMap::SetZoomLevel(web_contents(), level); + // Notify observers of zoom level changes. + FOR_EACH_OBSERVER(WebContentsZoomController::Observer, observers_, + OnZoomLevelChanged(web_contents(), level)); + } +} + +double WebContentsZoomController::GetZoomLevel() { + return content::HostZoomMap::GetZoomLevel(web_contents()); +} + +void WebContentsZoomController::SetDefaultZoomFactor(double factor) { + default_zoom_factor_ = factor; +} + +double WebContentsZoomController::GetDefaultZoomFactor() { + return default_zoom_factor_; +} + +void WebContentsZoomController::DidFinishNavigation( + content::NavigationHandle* navigation_handle) { + if (!navigation_handle->IsInMainFrame() || !navigation_handle->HasCommitted()) + return; + + if (navigation_handle->IsErrorPage()) { + content::HostZoomMap::SendErrorPageZoomLevelRefresh(web_contents()); + return; + } + + if (!navigation_handle->IsSamePage()) + SetZoomFactorOnNavigationIfNeeded(navigation_handle->GetURL()); +} + +void WebContentsZoomController::WebContentsDestroyed() { + observers_.Clear(); + host_zoom_factor_.clear(); +} + +void WebContentsZoomController::RenderFrameHostChanged( + content::RenderFrameHost* old_host, + content::RenderFrameHost* new_host) { + // If our associated HostZoomMap changes, update our event subscription. + content::HostZoomMap* new_host_zoom_map = + content::HostZoomMap::GetForWebContents(web_contents()); + if (new_host_zoom_map == host_zoom_map_) + return; + + host_zoom_map_ = new_host_zoom_map; + zoom_subscription_ = host_zoom_map_->AddZoomLevelChangedCallback(base::Bind( + &WebContentsZoomController::OnZoomLevelChanged, base::Unretained(this))); +} + +void WebContentsZoomController::SetZoomFactorOnNavigationIfNeeded( + const GURL& url) { + if (content::ZoomValuesEqual(GetDefaultZoomFactor(), content::kEpsilon)) + return; + + // When kZoomFactor is available, it takes precedence over + // pref store values but if the host has zoom factor set explicitly + // then it takes precendence. + // pref store < kZoomFactor < setZoomLevel + std::string host = net::GetHostOrSpecFromURL(url); + double zoom_factor = GetDefaultZoomFactor(); + auto it = host_zoom_factor_.find(host); + if (it != host_zoom_factor_.end()) + zoom_factor = it->second; + auto level = content::ZoomFactorToZoomLevel(zoom_factor); + if (content::ZoomValuesEqual(level, GetZoomLevel())) + return; + + SetZoomLevel(level); +} + +void WebContentsZoomController::OnZoomLevelChanged( + const content::HostZoomMap::ZoomLevelChange& change) { + if (change.mode == content::HostZoomMap::ZOOM_CHANGED_FOR_HOST) { + auto it = host_zoom_factor_.find(change.host); + if (it == host_zoom_factor_.end()) + return; + host_zoom_factor_.insert( + it, std::make_pair(change.host, + content::ZoomLevelToZoomFactor(change.zoom_level))); + } +} + +} // namespace atom diff --git a/atom/browser/web_contents_zoom_controller.h b/atom/browser/web_contents_zoom_controller.h new file mode 100644 index 0000000000..d109ccd8d8 --- /dev/null +++ b/atom/browser/web_contents_zoom_controller.h @@ -0,0 +1,73 @@ +// Copyright (c) 2017 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_WEB_CONTENTS_ZOOM_CONTROLLER_H_ +#define ATOM_BROWSER_WEB_CONTENTS_ZOOM_CONTROLLER_H_ + +#include "content/public/browser/host_zoom_map.h" +#include "content/public/browser/web_contents_observer.h" +#include "content/public/browser/web_contents_user_data.h" + +namespace atom { + +// Manages the zoom changes of WebContents. +class WebContentsZoomController + : public content::WebContentsObserver, + public content::WebContentsUserData { + public: + class Observer { + public: + virtual void OnZoomLevelChanged(content::WebContents* web_contents, + double level) {} + + protected: + virtual ~Observer() {} + }; + + explicit WebContentsZoomController(content::WebContents* web_contents); + ~WebContentsZoomController() override; + + void AddObserver(Observer* observer); + void RemoveObserver(Observer* observer); + + // Methods for managing zoom levels. + void SetZoomLevel(double level); + double GetZoomLevel(); + void SetDefaultZoomFactor(double factor); + double GetDefaultZoomFactor(); + + protected: + // content::WebContentsObserver: + void DidFinishNavigation(content::NavigationHandle* handle) override; + void WebContentsDestroyed() override; + void RenderFrameHostChanged(content::RenderFrameHost* old_host, + content::RenderFrameHost* new_host) override; + + private: + friend class content::WebContentsUserData; + + // Called after a navigation has committed to set default zoom factor. + void SetZoomFactorOnNavigationIfNeeded(const GURL& url); + + // Track zoom changes of a host in other instances of a partition. + void OnZoomLevelChanged(const content::HostZoomMap::ZoomLevelChange& change); + + // kZoomFactor. + double default_zoom_factor_; + + // Map between zoom factor and hosts in this webContent. + std::map host_zoom_factor_; + + base::ObserverList observers_; + + content::HostZoomMap* host_zoom_map_; + + std::unique_ptr zoom_subscription_; + + DISALLOW_COPY_AND_ASSIGN(WebContentsZoomController); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_WEB_CONTENTS_ZOOM_CONTROLLER_H_ diff --git a/atom/browser/web_view_guest_delegate.cc b/atom/browser/web_view_guest_delegate.cc index cfb3264963..85b034d02a 100644 --- a/atom/browser/web_view_guest_delegate.cc +++ b/atom/browser/web_view_guest_delegate.cc @@ -39,7 +39,9 @@ void WebViewGuestDelegate::Initialize(api::WebContents* api_web_contents) { void WebViewGuestDelegate::Destroy() { // Give the content module an opportunity to perform some cleanup. + embedder_zoom_controller_->RemoveObserver(this); guest_host_->WillDestroy(); + embedder_zoom_controller_ = nullptr; guest_host_ = nullptr; } @@ -107,6 +109,9 @@ void WebViewGuestDelegate::DidFinishNavigation( void WebViewGuestDelegate::DidAttach(int guest_proxy_routing_id) { api_web_contents_->Emit("did-attach"); + embedder_zoom_controller_ = + WebContentsZoomController::FromWebContents(embedder_web_contents_); + embedder_zoom_controller_->AddObserver(this); } content::WebContents* WebViewGuestDelegate::GetOwnerWebContents() const { @@ -134,6 +139,14 @@ void WebViewGuestDelegate::WillAttach( completion_callback.Run(); } +void WebViewGuestDelegate::OnZoomLevelChanged( + content::WebContents* web_contents, + double level) { + if (web_contents == GetOwnerWebContents()) { + api_web_contents_->GetZoomController()->SetZoomLevel(level); + } +} + void WebViewGuestDelegate::GuestSizeChangedDueToAutoSize( const gfx::Size& old_size, const gfx::Size& new_size) { api_web_contents_->Emit("size-changed", diff --git a/atom/browser/web_view_guest_delegate.h b/atom/browser/web_view_guest_delegate.h index eade31234c..4fc000fb06 100644 --- a/atom/browser/web_view_guest_delegate.h +++ b/atom/browser/web_view_guest_delegate.h @@ -5,6 +5,7 @@ #ifndef ATOM_BROWSER_WEB_VIEW_GUEST_DELEGATE_H_ #define ATOM_BROWSER_WEB_VIEW_GUEST_DELEGATE_H_ +#include "atom/browser/web_contents_zoom_controller.h" #include "content/public/browser/browser_plugin_guest_delegate.h" #include "content/public/browser/web_contents_observer.h" @@ -31,7 +32,8 @@ struct SetSizeParams { }; class WebViewGuestDelegate : public content::BrowserPluginGuestDelegate, - public content::WebContentsObserver { + public content::WebContentsObserver, + public WebContentsZoomController::Observer { public: WebViewGuestDelegate(); ~WebViewGuestDelegate() override; @@ -63,6 +65,10 @@ class WebViewGuestDelegate : public content::BrowserPluginGuestDelegate, content::RenderWidgetHost* GetOwnerRenderWidgetHost() override; content::SiteInstance* GetOwnerSiteInstance() override; + // WebContentsZoomController::Observer: + void OnZoomLevelChanged(content::WebContents* web_contents, + double level) override; + private: // This method is invoked when the contents auto-resized to give the container // an opportunity to match it if it wishes. @@ -78,6 +84,10 @@ class WebViewGuestDelegate : public content::BrowserPluginGuestDelegate, // The WebContents that attaches this guest view. content::WebContents* embedder_web_contents_; + // The zoom controller of the embedder that is used + // to subscribe for zoom changes. + WebContentsZoomController* embedder_zoom_controller_; + // The size of the container element. gfx::Size element_size_; diff --git a/filenames.gypi b/filenames.gypi index 5b16beaa91..d3dc7ed22c 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -341,6 +341,8 @@ 'atom/browser/web_contents_permission_helper.h', 'atom/browser/web_contents_preferences.cc', 'atom/browser/web_contents_preferences.h', + 'atom/browser/web_contents_zoom_controller.cc', + 'atom/browser/web_contents_zoom_controller.h', 'atom/browser/web_dialog_helper.cc', 'atom/browser/web_dialog_helper.h', 'atom/browser/web_view_guest_delegate.cc', diff --git a/lib/browser/guest-view-manager.js b/lib/browser/guest-view-manager.js index f1a460271b..e61b2574ad 100644 --- a/lib/browser/guest-view-manager.js +++ b/lib/browser/guest-view-manager.js @@ -184,7 +184,7 @@ const attachGuest = function (event, elementInstanceId, guestInstanceId, params) guestInstanceId: guestInstanceId, nodeIntegration: params.nodeintegration != null ? params.nodeintegration : false, plugins: params.plugins, - zoomFactor: params.zoomFactor, + zoomFactor: embedder.getZoomFactor(), webSecurity: !params.disablewebsecurity, blinkFeatures: params.blinkfeatures, disableBlinkFeatures: params.disableblinkfeatures diff --git a/lib/renderer/web-view/web-view.js b/lib/renderer/web-view/web-view.js index ba8ae32d46..9800bf9949 100644 --- a/lib/renderer/web-view/web-view.js +++ b/lib/renderer/web-view/web-view.js @@ -231,7 +231,6 @@ class WebViewImpl { const params = { instanceId: this.viewInstanceId, userAgentOverride: this.userAgentOverride, - zoomFactor: webFrame.getZoomFactor() } for (const attributeName in this.attributes) { if (hasProp.call(this.attributes, attributeName)) { From e3fe3cc490dc3c28b3b44434958dd0851e933b4c Mon Sep 17 00:00:00 2001 From: deepak1556 Date: Mon, 30 Jan 2017 22:36:50 +0530 Subject: [PATCH 116/925] map webframe zoom api to use HostZoomMap --- atom/browser/api/atom_api_web_contents.cc | 17 ++++++++ atom/browser/api/atom_api_web_contents.h | 8 ++++ atom/browser/web_contents_zoom_controller.cc | 44 +++++++++++++++++++- atom/browser/web_contents_zoom_controller.h | 11 ++++- atom/browser/web_view_guest_delegate.cc | 13 +++++- atom/browser/web_view_guest_delegate.h | 3 +- atom/common/api/api_messages.h | 8 ++++ atom/renderer/api/atom_api_web_frame.cc | 17 ++++++-- lib/renderer/web-view/web-view.js | 10 +---- 9 files changed, 113 insertions(+), 18 deletions(-) diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 8e513923de..8d012a6b5d 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -819,6 +819,10 @@ bool WebContents::OnMessageReceived(const IPC::Message& message) { IPC_MESSAGE_HANDLER(AtomViewHostMsg_Message, OnRendererMessage) IPC_MESSAGE_HANDLER_DELAY_REPLY(AtomViewHostMsg_Message_Sync, OnRendererMessageSync) + IPC_MESSAGE_HANDLER_DELAY_REPLY(AtomViewHostMsg_SetTemporaryZoomLevel, + OnSetTemporaryZoomLevel) + IPC_MESSAGE_HANDLER_DELAY_REPLY(AtomViewHostMsg_GetZoomLevel, + OnGetZoomLevel) IPC_MESSAGE_HANDLER_CODE(ViewHostMsg_SetCursor, OnCursorChange, handled = false) IPC_MESSAGE_UNHANDLED(handled = false) @@ -1523,6 +1527,19 @@ double WebContents::GetZoomFactor() { return content::ZoomLevelToZoomFactor(level); } +void WebContents::OnSetTemporaryZoomLevel(double level, + IPC::Message* reply_msg) { + zoom_controller_->SetTemporaryZoomLevel(level); + double new_level = zoom_controller_->GetTemporaryZoomLevel(); + AtomViewHostMsg_SetTemporaryZoomLevel::WriteReplyParams(reply_msg, new_level); + Send(reply_msg); +} + +void WebContents::OnGetZoomLevel(IPC::Message* reply_msg) { + AtomViewHostMsg_GetZoomLevel::WriteReplyParams(reply_msg, GetZoomLevel()); + Send(reply_msg); +} + v8::Local WebContents::GetWebPreferences(v8::Isolate* isolate) { WebContentsPreferences* web_preferences = WebContentsPreferences::FromWebContents(web_contents()); diff --git a/atom/browser/api/atom_api_web_contents.h b/atom/browser/api/atom_api_web_contents.h index c65560a63d..a37fb8a91f 100644 --- a/atom/browser/api/atom_api_web_contents.h +++ b/atom/browser/api/atom_api_web_contents.h @@ -352,6 +352,14 @@ class WebContents : public mate::TrackableObject, const base::ListValue& args, IPC::Message* message); + // Called when received a synchronous message from renderer to + // set temporary zoom level. + void OnSetTemporaryZoomLevel(double level, IPC::Message* reply_msg); + + // Called when received a synchronous message from renderer to + // get the zoom level. + void OnGetZoomLevel(IPC::Message* reply_msg); + v8::Global session_; v8::Global devtools_web_contents_; v8::Global debugger_; diff --git a/atom/browser/web_contents_zoom_controller.cc b/atom/browser/web_contents_zoom_controller.cc index d0a8975eae..2b297f3eb7 100644 --- a/atom/browser/web_contents_zoom_controller.cc +++ b/atom/browser/web_contents_zoom_controller.cc @@ -22,6 +22,7 @@ WebContentsZoomController::WebContentsZoomController( content::WebContents* web_contents) : content::WebContentsObserver(web_contents) { default_zoom_factor_ = content::kEpsilon; + temporary_zoom_level_ = content::kEpsilon; host_zoom_map_ = content::HostZoomMap::GetForWebContents(web_contents); zoom_subscription_ = host_zoom_map_->AddZoomLevelChangedCallback(base::Bind( &WebContentsZoomController::OnZoomLevelChanged, base::Unretained(this))); @@ -43,6 +44,11 @@ void WebContentsZoomController::SetZoomLevel(double level) { if (!web_contents()->GetRenderViewHost()->IsRenderViewLive() || content::ZoomValuesEqual(GetZoomLevel(), level)) return; + + if (!content::ZoomValuesEqual(GetTemporaryZoomLevel(), content::kEpsilon)) { + temporary_zoom_level_ = content::kEpsilon; + } + auto new_zoom_factor = content::ZoomLevelToZoomFactor(level); content::NavigationEntry* entry = web_contents()->GetController().GetLastCommittedEntry(); @@ -54,7 +60,7 @@ void WebContentsZoomController::SetZoomLevel(double level) { content::HostZoomMap::SetZoomLevel(web_contents(), level); // Notify observers of zoom level changes. FOR_EACH_OBSERVER(WebContentsZoomController::Observer, observers_, - OnZoomLevelChanged(web_contents(), level)); + OnZoomLevelChanged(web_contents(), level, false)); } } @@ -70,6 +76,34 @@ double WebContentsZoomController::GetDefaultZoomFactor() { return default_zoom_factor_; } +bool WebContentsZoomController::UsesTemporaryZoomLevel() { + return !content::ZoomValuesEqual(temporary_zoom_level_, content::kEpsilon); +} + +double WebContentsZoomController::GetTemporaryZoomLevel() { + return temporary_zoom_level_; +} + +void WebContentsZoomController::SetTemporaryZoomLevel(double level) { + int render_process_id = web_contents()->GetRenderProcessHost()->GetID(); + int render_view_id = web_contents()->GetRenderViewHost()->GetRoutingID(); + host_zoom_map_->SetTemporaryZoomLevel(render_process_id, render_view_id, + level); + temporary_zoom_level_ = level; + // Notify observers of zoom level changes. + FOR_EACH_OBSERVER(WebContentsZoomController::Observer, observers_, + OnZoomLevelChanged(web_contents(), level, true)); +} + +void WebContentsZoomController::DidStartNavigation( + content::NavigationHandle* navigation_handle) { + if (!navigation_handle->IsInMainFrame() || navigation_handle->IsSamePage()) + return; + int render_process_id = web_contents()->GetRenderProcessHost()->GetID(); + int render_view_id = web_contents()->GetRenderViewHost()->GetRoutingID(); + host_zoom_map_->ClearTemporaryZoomLevel(render_process_id, render_view_id); +} + void WebContentsZoomController::DidFinishNavigation( content::NavigationHandle* navigation_handle) { if (!navigation_handle->IsInMainFrame() || !navigation_handle->HasCommitted()) @@ -108,6 +142,14 @@ void WebContentsZoomController::SetZoomFactorOnNavigationIfNeeded( if (content::ZoomValuesEqual(GetDefaultZoomFactor(), content::kEpsilon)) return; + if (!content::ZoomValuesEqual(GetTemporaryZoomLevel(), content::kEpsilon)) { + FOR_EACH_OBSERVER( + WebContentsZoomController::Observer, observers_, + OnZoomLevelChanged(web_contents(), GetTemporaryZoomLevel(), true)); + temporary_zoom_level_ = content::kEpsilon; + return; + } + // When kZoomFactor is available, it takes precedence over // pref store values but if the host has zoom factor set explicitly // then it takes precendence. diff --git a/atom/browser/web_contents_zoom_controller.h b/atom/browser/web_contents_zoom_controller.h index d109ccd8d8..32cc8be0cc 100644 --- a/atom/browser/web_contents_zoom_controller.h +++ b/atom/browser/web_contents_zoom_controller.h @@ -5,6 +5,9 @@ #ifndef ATOM_BROWSER_WEB_CONTENTS_ZOOM_CONTROLLER_H_ #define ATOM_BROWSER_WEB_CONTENTS_ZOOM_CONTROLLER_H_ +#include +#include + #include "content/public/browser/host_zoom_map.h" #include "content/public/browser/web_contents_observer.h" #include "content/public/browser/web_contents_user_data.h" @@ -19,7 +22,8 @@ class WebContentsZoomController class Observer { public: virtual void OnZoomLevelChanged(content::WebContents* web_contents, - double level) {} + double level, + bool is_temporary) {} protected: virtual ~Observer() {} @@ -36,9 +40,13 @@ class WebContentsZoomController double GetZoomLevel(); void SetDefaultZoomFactor(double factor); double GetDefaultZoomFactor(); + void SetTemporaryZoomLevel(double level); + bool UsesTemporaryZoomLevel(); + double GetTemporaryZoomLevel(); protected: // content::WebContentsObserver: + void DidStartNavigation(content::NavigationHandle* handle) override; void DidFinishNavigation(content::NavigationHandle* handle) override; void WebContentsDestroyed() override; void RenderFrameHostChanged(content::RenderFrameHost* old_host, @@ -55,6 +63,7 @@ class WebContentsZoomController // kZoomFactor. double default_zoom_factor_; + double temporary_zoom_level_; // Map between zoom factor and hosts in this webContent. std::map host_zoom_factor_; diff --git a/atom/browser/web_view_guest_delegate.cc b/atom/browser/web_view_guest_delegate.cc index 85b034d02a..a239633238 100644 --- a/atom/browser/web_view_guest_delegate.cc +++ b/atom/browser/web_view_guest_delegate.cc @@ -112,6 +112,10 @@ void WebViewGuestDelegate::DidAttach(int guest_proxy_routing_id) { embedder_zoom_controller_ = WebContentsZoomController::FromWebContents(embedder_web_contents_); embedder_zoom_controller_->AddObserver(this); + if (embedder_zoom_controller_->UsesTemporaryZoomLevel()) { + double level = embedder_zoom_controller_->GetTemporaryZoomLevel(); + api_web_contents_->GetZoomController()->SetTemporaryZoomLevel(level); + } } content::WebContents* WebViewGuestDelegate::GetOwnerWebContents() const { @@ -141,9 +145,14 @@ void WebViewGuestDelegate::WillAttach( void WebViewGuestDelegate::OnZoomLevelChanged( content::WebContents* web_contents, - double level) { + double level, + bool is_temporary) { if (web_contents == GetOwnerWebContents()) { - api_web_contents_->GetZoomController()->SetZoomLevel(level); + if (is_temporary) { + api_web_contents_->GetZoomController()->SetTemporaryZoomLevel(level); + } else { + api_web_contents_->GetZoomController()->SetZoomLevel(level); + } } } diff --git a/atom/browser/web_view_guest_delegate.h b/atom/browser/web_view_guest_delegate.h index 4fc000fb06..329b7ec317 100644 --- a/atom/browser/web_view_guest_delegate.h +++ b/atom/browser/web_view_guest_delegate.h @@ -67,7 +67,8 @@ class WebViewGuestDelegate : public content::BrowserPluginGuestDelegate, // WebContentsZoomController::Observer: void OnZoomLevelChanged(content::WebContents* web_contents, - double level) override; + double level, + bool is_temporary) override; private: // This method is invoked when the contents auto-resized to give the container diff --git a/atom/common/api/api_messages.h b/atom/common/api/api_messages.h index ab27d5a251..ef945d9eeb 100644 --- a/atom/common/api/api_messages.h +++ b/atom/common/api/api_messages.h @@ -41,3 +41,11 @@ IPC_MESSAGE_ROUTED1(AtomViewHostMsg_UpdateDraggableRegions, // Update renderer process preferences. IPC_MESSAGE_CONTROL1(AtomMsg_UpdatePreferences, base::ListValue) + +// Sent by renderer to set the temporary zoom level. +IPC_SYNC_MESSAGE_ROUTED1_1(AtomViewHostMsg_SetTemporaryZoomLevel, + double /* zoom level */, + double /* result */) + +// Sent by renderer to get the zoom level. +IPC_SYNC_MESSAGE_ROUTED0_1(AtomViewHostMsg_GetZoomLevel, double /* result */) diff --git a/atom/renderer/api/atom_api_web_frame.cc b/atom/renderer/api/atom_api_web_frame.cc index c750a455de..cc2d28ca96 100644 --- a/atom/renderer/api/atom_api_web_frame.cc +++ b/atom/renderer/api/atom_api_web_frame.cc @@ -4,6 +4,7 @@ #include "atom/renderer/api/atom_api_web_frame.h" +#include "atom/common/api/api_messages.h" #include "atom/common/api/event_emitter_caller.h" #include "atom/common/native_mate_converters/blink_converter.h" #include "atom/common/native_mate_converters/callback.h" @@ -72,13 +73,21 @@ void WebFrame::SetName(const std::string& name) { } double WebFrame::SetZoomLevel(double level) { - double ret = web_frame_->view()->setZoomLevel(level); - mate::EmitEvent(isolate(), GetWrapper(), "zoom-level-changed", ret); - return ret; + double result; + content::RenderView* render_view = + content::RenderView::FromWebView(web_frame_->view()); + render_view->Send(new AtomViewHostMsg_SetTemporaryZoomLevel( + render_view->GetRoutingID(), level, &result)); + return result; } double WebFrame::GetZoomLevel() const { - return web_frame_->view()->zoomLevel(); + double result; + content::RenderView* render_view = + content::RenderView::FromWebView(web_frame_->view()); + render_view->Send( + new AtomViewHostMsg_GetZoomLevel(render_view->GetRoutingID(), &result)); + return result; } double WebFrame::SetZoomFactor(double factor) { diff --git a/lib/renderer/web-view/web-view.js b/lib/renderer/web-view/web-view.js index 9800bf9949..c98b02d274 100644 --- a/lib/renderer/web-view/web-view.js +++ b/lib/renderer/web-view/web-view.js @@ -34,12 +34,6 @@ class WebViewImpl { this.viewInstanceId = getNextId() shadowRoot.appendChild(this.browserPluginNode) - // Subscribe to host's zoom level changes. - this.onZoomLevelChanged = (zoomLevel) => { - this.webviewNode.setZoomLevel(zoomLevel) - } - webFrame.on('zoom-level-changed', this.onZoomLevelChanged) - this.onVisibilityChanged = (event, visibilityState) => { this.webviewNode.send('ELECTRON_RENDERER_WINDOW_VISIBILITY_CHANGE', visibilityState) } @@ -56,8 +50,6 @@ class WebViewImpl { // Resets some state upon reattaching element to the DOM. reset () { - // Unlisten the zoom-level-changed event. - webFrame.removeListener('zoom-level-changed', this.onZoomLevelChanged) ipcRenderer.removeListener('ELECTRON_RENDERER_WINDOW_VISIBILITY_CHANGE', this.onVisibilityChanged) // If guestInstanceId is defined then the has navigated and has @@ -230,7 +222,7 @@ class WebViewImpl { buildParams () { const params = { instanceId: this.viewInstanceId, - userAgentOverride: this.userAgentOverride, + userAgentOverride: this.userAgentOverride } for (const attributeName in this.attributes) { if (hasProp.call(this.attributes, attributeName)) { From cbeaa6be87b4a676440956233557cac28d984d4f Mon Sep 17 00:00:00 2001 From: deepak1556 Date: Tue, 31 Jan 2017 14:45:45 +0530 Subject: [PATCH 117/925] simply handling temporary zoom levels of webview --- atom/browser/api/atom_api_web_contents.cc | 2 +- atom/browser/web_contents_zoom_controller.cc | 60 +++++++++++--------- atom/browser/web_contents_zoom_controller.h | 9 ++- atom/browser/web_view_guest_delegate.cc | 9 +-- 4 files changed, 45 insertions(+), 35 deletions(-) diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 8d012a6b5d..0865ea0e5f 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -1530,7 +1530,7 @@ double WebContents::GetZoomFactor() { void WebContents::OnSetTemporaryZoomLevel(double level, IPC::Message* reply_msg) { zoom_controller_->SetTemporaryZoomLevel(level); - double new_level = zoom_controller_->GetTemporaryZoomLevel(); + double new_level = zoom_controller_->GetZoomLevel(); AtomViewHostMsg_SetTemporaryZoomLevel::WriteReplyParams(reply_msg, new_level); Send(reply_msg); } diff --git a/atom/browser/web_contents_zoom_controller.cc b/atom/browser/web_contents_zoom_controller.cc index 2b297f3eb7..a40b683092 100644 --- a/atom/browser/web_contents_zoom_controller.cc +++ b/atom/browser/web_contents_zoom_controller.cc @@ -7,6 +7,7 @@ #include "content/public/browser/navigation_details.h" #include "content/public/browser/navigation_entry.h" #include "content/public/browser/navigation_handle.h" +#include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/web_contents.h" @@ -20,15 +21,19 @@ namespace atom { WebContentsZoomController::WebContentsZoomController( content::WebContents* web_contents) - : content::WebContentsObserver(web_contents) { + : old_process_id_(-1), + old_view_id_(-1), + embedder_zoom_controller_(nullptr), + content::WebContentsObserver(web_contents) { default_zoom_factor_ = content::kEpsilon; - temporary_zoom_level_ = content::kEpsilon; host_zoom_map_ = content::HostZoomMap::GetForWebContents(web_contents); zoom_subscription_ = host_zoom_map_->AddZoomLevelChangedCallback(base::Bind( &WebContentsZoomController::OnZoomLevelChanged, base::Unretained(this))); } -WebContentsZoomController::~WebContentsZoomController() {} +WebContentsZoomController::~WebContentsZoomController() { + embedder_zoom_controller_ = nullptr; +} void WebContentsZoomController::AddObserver( WebContentsZoomController::Observer* observer) { @@ -40,13 +45,21 @@ void WebContentsZoomController::RemoveObserver( observers_.RemoveObserver(observer); } +void WebContentsZoomController::SetEmbedderZoomController( + WebContentsZoomController* controller) { + embedder_zoom_controller_ = controller; +} + void WebContentsZoomController::SetZoomLevel(double level) { if (!web_contents()->GetRenderViewHost()->IsRenderViewLive() || content::ZoomValuesEqual(GetZoomLevel(), level)) return; - if (!content::ZoomValuesEqual(GetTemporaryZoomLevel(), content::kEpsilon)) { - temporary_zoom_level_ = content::kEpsilon; + int render_process_id = web_contents()->GetRenderProcessHost()->GetID(); + int render_view_id = web_contents()->GetRenderViewHost()->GetRoutingID(); + if (host_zoom_map_->UsesTemporaryZoomLevel(render_process_id, + render_view_id)) { + host_zoom_map_->ClearTemporaryZoomLevel(render_process_id, render_view_id); } auto new_zoom_factor = content::ZoomLevelToZoomFactor(level); @@ -76,32 +89,20 @@ double WebContentsZoomController::GetDefaultZoomFactor() { return default_zoom_factor_; } -bool WebContentsZoomController::UsesTemporaryZoomLevel() { - return !content::ZoomValuesEqual(temporary_zoom_level_, content::kEpsilon); -} - -double WebContentsZoomController::GetTemporaryZoomLevel() { - return temporary_zoom_level_; -} - void WebContentsZoomController::SetTemporaryZoomLevel(double level) { - int render_process_id = web_contents()->GetRenderProcessHost()->GetID(); - int render_view_id = web_contents()->GetRenderViewHost()->GetRoutingID(); - host_zoom_map_->SetTemporaryZoomLevel(render_process_id, render_view_id, - level); - temporary_zoom_level_ = level; + old_process_id_ = web_contents()->GetRenderProcessHost()->GetID(); + old_view_id_ = web_contents()->GetRenderViewHost()->GetRoutingID(); + host_zoom_map_->SetTemporaryZoomLevel(old_process_id_, old_view_id_, level); // Notify observers of zoom level changes. FOR_EACH_OBSERVER(WebContentsZoomController::Observer, observers_, OnZoomLevelChanged(web_contents(), level, true)); } -void WebContentsZoomController::DidStartNavigation( - content::NavigationHandle* navigation_handle) { - if (!navigation_handle->IsInMainFrame() || navigation_handle->IsSamePage()) - return; +bool WebContentsZoomController::UsesTemporaryZoomLevel() { int render_process_id = web_contents()->GetRenderProcessHost()->GetID(); int render_view_id = web_contents()->GetRenderViewHost()->GetRoutingID(); - host_zoom_map_->ClearTemporaryZoomLevel(render_process_id, render_view_id); + return host_zoom_map_->UsesTemporaryZoomLevel(render_process_id, + render_view_id); } void WebContentsZoomController::DidFinishNavigation( @@ -142,11 +143,14 @@ void WebContentsZoomController::SetZoomFactorOnNavigationIfNeeded( if (content::ZoomValuesEqual(GetDefaultZoomFactor(), content::kEpsilon)) return; - if (!content::ZoomValuesEqual(GetTemporaryZoomLevel(), content::kEpsilon)) { - FOR_EACH_OBSERVER( - WebContentsZoomController::Observer, observers_, - OnZoomLevelChanged(web_contents(), GetTemporaryZoomLevel(), true)); - temporary_zoom_level_ = content::kEpsilon; + if (host_zoom_map_->UsesTemporaryZoomLevel(old_process_id_, old_view_id_)) { + host_zoom_map_->ClearTemporaryZoomLevel(old_process_id_, old_view_id_); + } + + if (embedder_zoom_controller_ && + embedder_zoom_controller_->UsesTemporaryZoomLevel()) { + double level = embedder_zoom_controller_->GetZoomLevel(); + SetTemporaryZoomLevel(level); return; } diff --git a/atom/browser/web_contents_zoom_controller.h b/atom/browser/web_contents_zoom_controller.h index 32cc8be0cc..11db8745b3 100644 --- a/atom/browser/web_contents_zoom_controller.h +++ b/atom/browser/web_contents_zoom_controller.h @@ -35,6 +35,8 @@ class WebContentsZoomController void AddObserver(Observer* observer); void RemoveObserver(Observer* observer); + void SetEmbedderZoomController(WebContentsZoomController* controller); + // Methods for managing zoom levels. void SetZoomLevel(double level); double GetZoomLevel(); @@ -42,11 +44,9 @@ class WebContentsZoomController double GetDefaultZoomFactor(); void SetTemporaryZoomLevel(double level); bool UsesTemporaryZoomLevel(); - double GetTemporaryZoomLevel(); protected: // content::WebContentsObserver: - void DidStartNavigation(content::NavigationHandle* handle) override; void DidFinishNavigation(content::NavigationHandle* handle) override; void WebContentsDestroyed() override; void RenderFrameHostChanged(content::RenderFrameHost* old_host, @@ -65,6 +65,11 @@ class WebContentsZoomController double default_zoom_factor_; double temporary_zoom_level_; + int old_process_id_; + int old_view_id_; + + WebContentsZoomController* embedder_zoom_controller_; + // Map between zoom factor and hosts in this webContent. std::map host_zoom_factor_; diff --git a/atom/browser/web_view_guest_delegate.cc b/atom/browser/web_view_guest_delegate.cc index a239633238..e8b1aee0ce 100644 --- a/atom/browser/web_view_guest_delegate.cc +++ b/atom/browser/web_view_guest_delegate.cc @@ -111,11 +111,9 @@ void WebViewGuestDelegate::DidAttach(int guest_proxy_routing_id) { api_web_contents_->Emit("did-attach"); embedder_zoom_controller_ = WebContentsZoomController::FromWebContents(embedder_web_contents_); + auto zoom_controller = api_web_contents_->GetZoomController(); embedder_zoom_controller_->AddObserver(this); - if (embedder_zoom_controller_->UsesTemporaryZoomLevel()) { - double level = embedder_zoom_controller_->GetTemporaryZoomLevel(); - api_web_contents_->GetZoomController()->SetTemporaryZoomLevel(level); - } + zoom_controller->SetEmbedderZoomController(embedder_zoom_controller_); } content::WebContents* WebViewGuestDelegate::GetOwnerWebContents() const { @@ -153,6 +151,9 @@ void WebViewGuestDelegate::OnZoomLevelChanged( } else { api_web_contents_->GetZoomController()->SetZoomLevel(level); } + // Change the default zoom factor to match the embedders' new zoom level. + double zoom_factor = content::ZoomLevelToZoomFactor(level); + api_web_contents_->GetZoomController()->SetDefaultZoomFactor(zoom_factor); } } From 7a0aff2bae6124beeec8b2f2ea0698f91fcd7efe Mon Sep 17 00:00:00 2001 From: deepak1556 Date: Tue, 31 Jan 2017 14:55:48 +0530 Subject: [PATCH 118/925] code cleanup --- atom/browser/api/atom_api_web_contents.cc | 8 ++++---- atom/browser/api/atom_api_web_view_manager.cc | 3 ++- atom/browser/web_contents_zoom_controller.cc | 14 ++++++-------- atom/browser/web_view_guest_delegate.cc | 12 +++++++----- atom/common/options_switches.cc | 1 - atom/common/options_switches.h | 1 - atom/renderer/api/atom_api_web_frame.cc | 4 ++-- 7 files changed, 21 insertions(+), 22 deletions(-) diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 0865ea0e5f..ae040be5c4 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -248,8 +248,8 @@ WebContents::WebContents(v8::Isolate* isolate, content::WebContents* web_contents, Type type) : content::WebContentsObserver(web_contents), - zoom_controller_(nullptr), embedder_(nullptr), + zoom_controller_(nullptr), type_(type), request_id_(0), background_throttling_(true), @@ -267,8 +267,8 @@ WebContents::WebContents(v8::Isolate* isolate, } WebContents::WebContents(v8::Isolate* isolate, const mate::Dictionary& options) - : zoom_controller_(nullptr), - embedder_(nullptr), + : embedder_(nullptr), + zoom_controller_(nullptr), type_(BROWSER_WINDOW), request_id_(0), background_throttling_(true), @@ -354,7 +354,7 @@ void WebContents::InitWithSessionAndOptions(v8::Isolate* isolate, WebContentsZoomController::CreateForWebContents(web_contents); zoom_controller_ = WebContentsZoomController::FromWebContents(web_contents); double zoom_factor; - if (options.Get("zoomFactor", &zoom_factor)) + if (options.Get(options::kZoomFactor, &zoom_factor)) zoom_controller_->SetDefaultZoomFactor(zoom_factor); web_contents->SetUserAgentOverride(GetBrowserContext()->GetUserAgent()); diff --git a/atom/browser/api/atom_api_web_view_manager.cc b/atom/browser/api/atom_api_web_view_manager.cc index 33205b80c6..961cb03220 100644 --- a/atom/browser/api/atom_api_web_view_manager.cc +++ b/atom/browser/api/atom_api_web_view_manager.cc @@ -8,6 +8,7 @@ #include "atom/common/native_mate_converters/content_converter.h" #include "atom/common/native_mate_converters/value_converter.h" #include "atom/common/node_includes.h" +#include "atom/common/options_switches.h" #include "content/public/browser/browser_context.h" #include "native_mate/dictionary.h" @@ -26,7 +27,7 @@ void AddGuest(int guest_instance_id, guest_web_contents); double zoom_factor; - if (options.GetDouble("zoomFactor", &zoom_factor)) { + if (options.GetDouble(atom::options::kZoomFactor, &zoom_factor)) { atom::WebContentsZoomController::FromWebContents(guest_web_contents) ->SetDefaultZoomFactor(zoom_factor); } diff --git a/atom/browser/web_contents_zoom_controller.cc b/atom/browser/web_contents_zoom_controller.cc index a40b683092..92eb30fac8 100644 --- a/atom/browser/web_contents_zoom_controller.cc +++ b/atom/browser/web_contents_zoom_controller.cc @@ -7,7 +7,6 @@ #include "content/public/browser/navigation_details.h" #include "content/public/browser/navigation_entry.h" #include "content/public/browser/navigation_handle.h" -#include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/web_contents.h" @@ -31,9 +30,7 @@ WebContentsZoomController::WebContentsZoomController( &WebContentsZoomController::OnZoomLevelChanged, base::Unretained(this))); } -WebContentsZoomController::~WebContentsZoomController() { - embedder_zoom_controller_ = nullptr; -} +WebContentsZoomController::~WebContentsZoomController() {} void WebContentsZoomController::AddObserver( WebContentsZoomController::Observer* observer) { @@ -72,8 +69,8 @@ void WebContentsZoomController::SetZoomLevel(double level) { host_zoom_factor_[host] = new_zoom_factor; content::HostZoomMap::SetZoomLevel(web_contents(), level); // Notify observers of zoom level changes. - FOR_EACH_OBSERVER(WebContentsZoomController::Observer, observers_, - OnZoomLevelChanged(web_contents(), level, false)); + for (Observer& observer : observers_) + observer.OnZoomLevelChanged(web_contents(), level, false); } } @@ -94,8 +91,8 @@ void WebContentsZoomController::SetTemporaryZoomLevel(double level) { old_view_id_ = web_contents()->GetRenderViewHost()->GetRoutingID(); host_zoom_map_->SetTemporaryZoomLevel(old_process_id_, old_view_id_, level); // Notify observers of zoom level changes. - FOR_EACH_OBSERVER(WebContentsZoomController::Observer, observers_, - OnZoomLevelChanged(web_contents(), level, true)); + for (Observer& observer : observers_) + observer.OnZoomLevelChanged(web_contents(), level, true); } bool WebContentsZoomController::UsesTemporaryZoomLevel() { @@ -122,6 +119,7 @@ void WebContentsZoomController::DidFinishNavigation( void WebContentsZoomController::WebContentsDestroyed() { observers_.Clear(); host_zoom_factor_.clear(); + embedder_zoom_controller_ = nullptr; } void WebContentsZoomController::RenderFrameHostChanged( diff --git a/atom/browser/web_view_guest_delegate.cc b/atom/browser/web_view_guest_delegate.cc index e8b1aee0ce..21b917ac61 100644 --- a/atom/browser/web_view_guest_delegate.cc +++ b/atom/browser/web_view_guest_delegate.cc @@ -23,11 +23,11 @@ const int kDefaultHeight = 300; } // namespace WebViewGuestDelegate::WebViewGuestDelegate() - : guest_host_(nullptr), + : embedder_zoom_controller_(nullptr), + guest_host_(nullptr), auto_size_enabled_(false), is_full_page_plugin_(false), - api_web_contents_(nullptr) { -} + api_web_contents_(nullptr) {} WebViewGuestDelegate::~WebViewGuestDelegate() { } @@ -39,9 +39,11 @@ void WebViewGuestDelegate::Initialize(api::WebContents* api_web_contents) { void WebViewGuestDelegate::Destroy() { // Give the content module an opportunity to perform some cleanup. - embedder_zoom_controller_->RemoveObserver(this); + if (embedder_zoom_controller_) { + embedder_zoom_controller_->RemoveObserver(this); + embedder_zoom_controller_ = nullptr; + } guest_host_->WillDestroy(); - embedder_zoom_controller_ = nullptr; guest_host_ = nullptr; } diff --git a/atom/common/options_switches.cc b/atom/common/options_switches.cc index 4729a28127..6db479ceb7 100644 --- a/atom/common/options_switches.cc +++ b/atom/common/options_switches.cc @@ -155,7 +155,6 @@ const char kAppUserModelId[] = "app-user-model-id"; // The command line switch versions of the options. const char kBackgroundColor[] = "background-color"; -const char kZoomFactor[] = "zoom-factor"; const char kPreloadScript[] = "preload"; const char kPreloadURL[] = "preload-url"; const char kNodeIntegration[] = "node-integration"; diff --git a/atom/common/options_switches.h b/atom/common/options_switches.h index c81ab529cf..abe3856cd8 100644 --- a/atom/common/options_switches.h +++ b/atom/common/options_switches.h @@ -81,7 +81,6 @@ extern const char kSecureSchemes[]; extern const char kAppUserModelId[]; extern const char kBackgroundColor[]; -extern const char kZoomFactor[]; extern const char kPreloadScript[]; extern const char kPreloadURL[]; extern const char kNodeIntegration[]; diff --git a/atom/renderer/api/atom_api_web_frame.cc b/atom/renderer/api/atom_api_web_frame.cc index cc2d28ca96..f103b89391 100644 --- a/atom/renderer/api/atom_api_web_frame.cc +++ b/atom/renderer/api/atom_api_web_frame.cc @@ -73,7 +73,7 @@ void WebFrame::SetName(const std::string& name) { } double WebFrame::SetZoomLevel(double level) { - double result; + double result = 0.0; content::RenderView* render_view = content::RenderView::FromWebView(web_frame_->view()); render_view->Send(new AtomViewHostMsg_SetTemporaryZoomLevel( @@ -82,7 +82,7 @@ double WebFrame::SetZoomLevel(double level) { } double WebFrame::GetZoomLevel() const { - double result; + double result = 0.0; content::RenderView* render_view = content::RenderView::FromWebView(web_frame_->view()); render_view->Send( From 5efa34ce6da708c29006b606c468f6d4da085d52 Mon Sep 17 00:00:00 2001 From: Fabian Zeindl Date: Sun, 12 Feb 2017 10:03:33 +0100 Subject: [PATCH 119/925] Update web-contents.md --- docs/api/web-contents.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index 1c35eab44c..a303150046 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -1180,7 +1180,7 @@ the cursor when dragging. * `callback` Function - `(error) => {}`. * `error` Error -Returns true if the process of saving page has been initiated successfully. +Returns `Boolean` - true if the process of saving page has been initiated successfully. ```javascript const {BrowserWindow} = require('electron') From 46c65ae5fc8413d1a0275ef70b28eefe7c27ce33 Mon Sep 17 00:00:00 2001 From: Fabian Zeindl Date: Sun, 12 Feb 2017 10:06:24 +0100 Subject: [PATCH 120/925] Update session.md --- docs/api/session.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/session.md b/docs/api/session.md index d0814dfdb3..f04d45195f 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -98,7 +98,7 @@ The following methods are available on instances of `Session`: * `callback` Function * `size` Integer - Cache size used in bytes. -Returns the session's current cache size. +Callback is invoked with the session's current cache size. #### `ses.clearCache(callback)` From 50fbd5266a1c5078a33220d8ba751593967c9c0e Mon Sep 17 00:00:00 2001 From: Weiqiang Lin <23leo@163.com> Date: Sun, 12 Feb 2017 22:00:09 +0800 Subject: [PATCH 121/925] powerMonitor to zh-CN --- docs-translations/zh-CN/api/power-monitor.md | 26 +++++++++++--------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/docs-translations/zh-CN/api/power-monitor.md b/docs-translations/zh-CN/api/power-monitor.md index 7395a8b3b4..39bbaa6efe 100644 --- a/docs-translations/zh-CN/api/power-monitor.md +++ b/docs-translations/zh-CN/api/power-monitor.md @@ -1,12 +1,19 @@ # powerMonitor -`power-monitor`模块是用来监听能源区改变的.只能在主进程中使用.在 `app` 模块的 `ready` 事件触发之后就不能使用这个模块了. +> 监视电源状态更改。 + +进程: [Main](../glossary.md#main-process) + +在 `app` 模块的 `ready` 事件触发之后就不能使用这个模块了。 例如: ```javascript -app.on('ready', function () { - require('electron').powerMonitor.on('suspend', function () { +const electron = require('electron') +const {app} = electron + +app.on('ready', () => { + electron.powerMonitor.on('suspend', () => { console.log('The system is going to sleep') }) }) @@ -14,23 +21,20 @@ app.on('ready', function () { ## 事件 -`power-monitor` 模块可以触发下列事件: +`powerMonitor` 模块可以触发下列事件: ### Event: 'suspend' -在系统挂起的时候触发. +在系统挂起的时候触发。 ### Event: 'resume' -在系统恢复继续工作的时候触发. -Emitted when system is resuming. +在系统恢复继续工作的时候触发。 ### Event: 'on-ac' -在系统使用交流电的时候触发. -Emitted when the system changes to AC power. +在系统使用交流电的时候触发。 ### Event: 'on-battery' -在系统使用电池电源的时候触发. -Emitted when system changes to battery power. \ No newline at end of file +在系统使用电池电源的时候触发。 From 6a0738aa88b21cae5955adb9f671d4d7324e5c9e Mon Sep 17 00:00:00 2001 From: Weiqiang Lin <23leo@163.com> Date: Sun, 12 Feb 2017 22:07:24 +0800 Subject: [PATCH 122/925] powerSaveBlocker to zh-CN --- .../zh-CN/api/power-save-blocker.md | 44 ++++++++++--------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/docs-translations/zh-CN/api/power-save-blocker.md b/docs-translations/zh-CN/api/power-save-blocker.md index 67b1396d86..da6ffbdb80 100644 --- a/docs-translations/zh-CN/api/power-save-blocker.md +++ b/docs-translations/zh-CN/api/power-save-blocker.md @@ -1,13 +1,15 @@ # powerSaveBlocker -`powerSaveBlocker` 模块是用来阻止应用系统进入睡眠模式的,因此这允许应用保持系统和屏幕继续工作. +> 阻止系统进入低功耗(睡眠)模式。 + +进程: [Main](../glossary.md#main-process) 例如: ```javascript -const powerSaveBlocker = require('electron').powerSaveBlocker +const {powerSaveBlocker} = require('electron') -var id = powerSaveBlocker.start('prevent-display-sleep') +const id = powerSaveBlocker.start('prevent-display-sleep') console.log(powerSaveBlocker.isStarted(id)) powerSaveBlocker.stop(id) @@ -15,34 +17,36 @@ powerSaveBlocker.stop(id) ## 方法 -`powerSaveBlocker` 模块有如下方法: +`powerSaveBlocker` 模块有如下方法: ### `powerSaveBlocker.start(type)` -* `type` String - 强行保存阻塞类型. - * `prevent-app-suspension` - 阻止应用挂起. - 保持系统活跃,但是允许屏幕不亮. 用例: - 下载文件或者播放音频. - * `prevent-display-sleep`- 阻止应用进入休眠. 保持系统和屏幕活跃,屏幕一直亮. 用例: 播放音频. +* `type` String - 强行保存阻塞类型。 + * `prevent-app-suspension` - 阻止应用挂起。 + 保持系统活跃,但是允许屏幕不亮。例如: + 下载文件或者播放音频。 + * `prevent-display-sleep`- 阻止应用进入休眠。保持系统和屏幕活跃,屏幕一直亮。例如:播放音频。 -开始阻止系统进入睡眠模式.返回一个整数,这个整数标识了保持活跃的blocker. +返回 `Integer` - 分配给此阻断器的 blocker ID -**注意:** `prevent-display-sleep` 有更高的优先级 -`prevent-app-suspension`. 只有最高优先级生效. 换句话说, `prevent-display-sleep` 优先级永远高于 -`prevent-app-suspension`. +开始阻止系统进入睡眠模式。返回一个整数,这个整数标识了保持活跃的blocker ID。 -例如, A 请求调用了 `prevent-app-suspension`, B请求调用了 `prevent-display-sleep`. `prevent-display-sleep` -将一直工作,直到B停止调用. 在那之后, `prevent-app-suspension` -才起效. +**注意:** `prevent-display-sleep` 有更高的优先级 +`prevent-app-suspension`。只有最高优先级生效,换句话说, `prevent-display-sleep` 优先级永远高于 +`prevent-app-suspension`。 + +例如, A 请求调用了 `prevent-app-suspension`,B请求调用了 `prevent-display-sleep`。`prevent-display-sleep` +将一直工作,直到B停止调用。在那之后,`prevent-app-suspension` +才起效。 ### `powerSaveBlocker.stop(id)` -* `id` Integer - 通过 `powerSaveBlocker.start` 返回的保持活跃的 blocker id. +* `id` Integer - 通过 `powerSaveBlocker.start` 返回保持活跃的 blocker id. -让指定blocker 停止活跃. +让指定 blocker 停止活跃。 ### `powerSaveBlocker.isStarted(id)` -* `id` Integer - 通过 `powerSaveBlocker.start` 返回的保持活跃的 blocker id. +* `id` Integer - 通过 `powerSaveBlocker.start` 返回保持活跃的 blocker id. -返回 boolean, 是否对应的 `powerSaveBlocker` 已经启动. \ No newline at end of file +返回 boolean,对应的 `powerSaveBlocker` 是否已经启动。 From 9fd9b39448f24dc84efeea1076205d09a22f0522 Mon Sep 17 00:00:00 2001 From: Weiqiang Lin <23leo@163.com> Date: Sun, 12 Feb 2017 22:35:03 +0800 Subject: [PATCH 123/925] protocol to -zh-CN --- docs-translations/zh-CN/api/protocol.md | 217 +++++++++++++++++------- 1 file changed, 157 insertions(+), 60 deletions(-) diff --git a/docs-translations/zh-CN/api/protocol.md b/docs-translations/zh-CN/api/protocol.md index 44c5a0a05f..e188bc0fcd 100644 --- a/docs-translations/zh-CN/api/protocol.md +++ b/docs-translations/zh-CN/api/protocol.md @@ -1,8 +1,10 @@ # 协议 -`protocol` 模块可以注册一个自定义协议,或者使用一个已经存在的协议. +> 注册一个自定义协议,或者使用一个已经存在的协议。 -例子,使用一个与 `file://` 功能相似的协议 : +进程: [Main](../glossary.md#main-process) + +例如使用一个与 `file://` 功能相似的协议: ```javascript const {app, protocol} = require('electron') @@ -18,70 +20,112 @@ app.on('ready', () => { }) ``` -**注意:** 这个模块只有在 `app` 模块的 `ready` 事件触发之后才可使用. +**注意:** 这个模块只有在 `app` 模块的 `ready` 事件触发之后才可使用。 ## 方法 -`protocol` 模块有如下方法: +`protocol` 模块有如下方法: -### `protocol.registerStandardSchemes(schemes)` +### `protocol.registerStandardSchemes(schemes[, options])` -* `schemes` Array - 将一个自定义的方案注册为标准的方案. +* `schemes` String[] - 将一个自定义的方案注册为标准的方案。 +* `options` Object (可选) + * `secure` Boolean (可选) - `true` 将方案注册为安全。 + 默认值 `false`。 一个标准的 `scheme` 遵循 RFC 3986 的 -[generic URI syntax](https://tools.ietf.org/html/rfc3986#section-3) 标准. 这包含了 `file:` 和 `filesystem:`. +[generic URI syntax](https://tools.ietf.org/html/rfc3986#section-3) 标准。例如 `http` 和 +`https` 是标准方案,而 `file` 不是。 + +注册一个 `scheme` 作为标准,将允许相对和绝对的资源 +在服务时正确解析。 否则该方案将表现得像 +`file` 协议,但无法解析相对 URLs。 + +例如,当你加载以下页面与自定义协议无法 +注册为标准 `scheme`,图像将不会加载,因为 +非标准方案无法识别相对 URLs: + +```html + + + +``` + +注册方案作为标准将允许通过访问文件 +[FileSystem API][file-system-api]。 否则渲染器将抛出一个安全性 +错误。 + +默认情况下 web storage apis(localStorage,sessionStorage,webSQL,indexedDB,cookies) +对于非标准方案禁用。所以一般来说如果你想注册一个 +自定义协议替换 `http` 协议,您必须将其注册为标准方案: + +```javascript +const {app, protocol} = require('electron') + +protocol.registerStandardSchemes(['atom']) +app.on('ready', () => { + protocol.registerHttpProtocol('atom', '...') +}) +``` + +**注意:** 这个方法只有在 `app` 模块的 `ready` 事件触发之后才可使用。 ### `protocol.registerServiceWorkerSchemes(schemes)` -* `schemes` Array - 将一个自定义的方案注册为处理 service workers. +* `schemes` String[] - 将一个自定义的方案注册为处理 service workers。 ### `protocol.registerFileProtocol(scheme, handler[, completion])` * `scheme` String * `handler` Function + * `request` Object + * `url` String + * `referrer` String + * `method` String + * `uploadData` [UploadData[]](structures/upload-data.md) + * `callback` Function + * `filePath` String (可选) * `completion` Function (可选) + * `error` Error -注册一个协议,用来发送响应文件.当通过这个协议来发起一个请求的时候,将使用 `handler(request, callback)` 来调用 -`handler` .当 `scheme` 被成功注册或者完成(错误)时失败,将使用 `completion(null)` 调用 `completion`. - -* `request` Object - * `url` String - * `referrer` String - * `method` String - * `uploadData` Array (可选) -* `callback` Function - -`uploadData` 是一个 `data` 对象数组: - -* `data` Object - * `bytes` Buffer - 被发送的内容. - * `file` String - 上传的文件路径. +注册一个协议,用来发送响应文件。当通过这个协议来发起一个请求的时候,将使用 `handler(request,callback)` 来调用 +`handler`。当 `scheme` 被成功注册或者完成(错误)时失败,将使用 `completion(null)` 调用 `completion`。 为了处理请求,调用 `callback` 时需要使用文件路径或者一个带 `path` 参数的对象, 例如 `callback(filePath)` 或 -`callback({path: filePath})`. +`callback({path: filePath})`。 -当不使用任何参数调用 `callback` 时,你可以指定一个数字或一个带有 `error` 参数的对象,来标识 `request` 失败.你可以使用的 error number 可以参考 -[net error list][net-error]. +当不使用任何参数调用 `callback` 时,你可以指定一个数字或一个带有 `error` 参数的对象,来标识 `request` 失败。你可以使用的 error number 可以参考 +[net error list][net-error]。 -默认 `scheme` 会被注册为一个 `http:` 协议,它与遵循 "generic URI syntax" 规则的协议解析不同,例如 `file:` ,所以你或许应该调用 `protocol.registerStandardSchemes` 来创建一个标准的 scheme. +默认 `scheme` 会被注册为一个 `http:` 协议,它与遵循 "generic URI syntax" 规则的协议解析不同,例如 `file:`,所以你或许应该调用 `protocol.registerStandardSchemes` 来创建一个标准的 scheme。 ### `protocol.registerBufferProtocol(scheme, handler[, completion])` * `scheme` String * `handler` Function + * `request` Object + * `url` String + * `referrer` String + * `method` String + * `uploadData` [UploadData[]](structures/upload-data.md) + * `callback` Function + * `buffer` (Buffer | [MimeTypedBuffer](structures/mime-typed-buffer.md)) (optional) * `completion` Function (可选) + * `error` Error -注册一个 `scheme` 协议,用来发送响应 `Buffer` . +注册一个 `scheme` 协议,用来发送响应 `Buffer`。 -这个方法的用法类似 `registerFileProtocol`,除非使用一个 `Buffer` 对象,或一个有 `data`, -`mimeType`, 和 `charset` 属性的对象来调用 `callback` . +这个方法的用法类似 `registerFileProtocol`,除非使用一个 `Buffer` 对象,或一个有 `data`、 +`mimeType` 和 `charset` 属性的对象来调用 `callback`。 例子: ```javascript -protocol.registerBufferProtocol('atom', function (request, callback) { +const {protocol} = require('electron') + +protocol.registerBufferProtocol('atom', (request, callback) => { callback({mimeType: 'text/html', data: new Buffer('
Response
')}) -}, function (error) { +}, (error) => { if (error) console.error('Failed to register protocol') }) ``` @@ -90,90 +134,143 @@ protocol.registerBufferProtocol('atom', function (request, callback) { * `scheme` String * `handler` Function + * `request` Object + * `url` String + * `referrer` String + * `method` String + * `uploadData` [UploadData[]](structures/upload-data.md) + * `callback` Function + * `data` String (可选) * `completion` Function (可选) + * `error` Error -注册一个 `scheme` 协议,用来发送响应 `String` . +注册一个 `scheme` 协议,用来发送响应 `String`。 -这个方法的用法类似 `registerFileProtocol`,除非使用一个 `String` 对象,或一个有 `data`, -`mimeType`, 和 `charset` 属性的对象来调用 `callback` . +这个方法的用法类似 `registerFileProtocol`,除非使用一个 `String` 对象,或一个有 `data`、 +`mimeType` 和 `charset` 属性的对象来调用 `callback`。 ### `protocol.registerHttpProtocol(scheme, handler[, completion])` * `scheme` String * `handler` Function + * `request` Object + * `url` String + * `referrer` String + * `method` String + * `uploadData` [UploadData[]](structures/upload-data.md) + * `callback` Function + * `redirectRequest` Object + * `url` String + * `method` String + * `session` Object (可选) + * `uploadData` Object (可选) + * `contentType` String - MIME type of the content. + * `data` String - Content to be sent. * `completion` Function (可选) + * `error` Error 注册一个 `scheme` 协议,用来发送 HTTP 请求作为响应. -这个方法的用法类似 `registerFileProtocol`,除非使用一个 `redirectRequest` 对象,或一个有 `url`, `method`, -`referrer`, `uploadData` 和 `session` 属性的对象来调用 `callback` . +这个方法的用法类似 `registerFileProtocol`,除非使用一个 `redirectRequest` 对象,或一个有 `url`、 `method`、 +`referrer`、 `uploadData` 和 `session` 属性的对象来调用 `callback`。 -* `redirectRequest` Object - * `url` String - * `method` String - * `session` Object (可选) - * `uploadData` Object (可选) +默认这个 HTTP 请求会使用当前 session。如果你想使用不同的session值,你应该设置 `session` 为 `null`。 -默认这个 HTTP 请求会使用当前 session .如果你想使用不同的session值,你应该设置 `session` 为 `null`. - -POST 请求应当包含 `uploadData` 对象. - -* `uploadData` object - * `contentType` String - 内容的 MIME type. - * `data` String - 被发送的内容. +POST 请求应当包含 `uploadData` 对象。 ### `protocol.unregisterProtocol(scheme[, completion])` * `scheme` String * `completion` Function (可选) + * `error` Error -注销自定义协议 `scheme`. +注销自定义协议 `scheme`。 ### `protocol.isProtocolHandled(scheme, callback)` * `scheme` String * `callback` Function + * `error` Error -将使用一个布尔值来调用 `callback` ,这个布尔值标识了是否已经存在 `scheme` 的句柄了. +将使用一个布尔值来调用 `callback` ,这个布尔值标识了是否已经存在 `scheme` 的句柄了。 ### `protocol.interceptFileProtocol(scheme, handler[, completion])` * `scheme` String * `handler` Function + * `request` Object + * `url` String + * `referrer` String + * `method` String + * `uploadData` [UploadData[]](structures/upload-data.md) + * `callback` Function + * `filePath` String * `completion` Function (可选) + * `error` Error -拦截 `scheme` 协议并且使用 `handler` 作为协议的新的句柄来发送响应文件. +拦截 `scheme` 协议并且使用 `handler` 作为协议的新的句柄来发送响应文件。 ### `protocol.interceptStringProtocol(scheme, handler[, completion])` * `scheme` String * `handler` Function + * `request` Object + * `url` String + * `referrer` String + * `method` String + * `uploadData` [UploadData[]](structures/upload-data.md) + * `callback` Function + * `data` String (可选) * `completion` Function (可选) + * `error` Error -拦截 `scheme` 协议并且使用 `handler` 作为协议的新的句柄来发送响应 `String`. +拦截 `scheme` 协议并且使用 `handler` 作为协议的新的句柄来发送响应 `String`。 ### `protocol.interceptBufferProtocol(scheme, handler[, completion])` * `scheme` String * `handler` Function + * `request` Object + * `url` String + * `referrer` String + * `method` String + * `uploadData` [UploadData[]](structures/upload-data.md) + * `callback` Function + * `buffer` Buffer (可选) * `completion` Function (可选) + * `error` Error -拦截 `scheme` 协议并且使用 `handler` 作为协议的新的句柄来发送响应 `Buffer`. +拦截 `scheme` 协议并且使用 `handler` 作为协议的新的句柄来发送响应 `Buffer`。 ### `protocol.interceptHttpProtocol(scheme, handler[, completion])` * `scheme` String * `handler` Function -* `completion` Function (optional) + * `request` Object + * `url` String + * `referrer` String + * `method` String + * `uploadData` [UploadData[]](structures/upload-data.md) + * `callback` Function + * `redirectRequest` Object + * `url` String + * `method` String + * `session` Object (可选) + * `uploadData` Object (可选) + * `contentType` String - MIME type of the content. + * `data` String - Content to be sent. +* `completion` Function (可选) + * `error` Error -拦截 `scheme` 协议并且使用 `handler` 作为协议的新的句柄来发送新的响应 HTTP 请求. -Intercepts `scheme` protocol and uses `handler` as the protocol's new handler -which sends a new HTTP request as a response. +拦截 `scheme` 协议并且使用 `handler` 作为协议的新的句柄来发送新的响应 HTTP 请求。 ### `protocol.uninterceptProtocol(scheme[, completion])` * `scheme` String -* `completion` Function -取消对 `scheme` 的拦截,使用它的原始句柄进行处理. +* `completion` Function (可选) + * `error` Error + +取消对 `scheme` 的拦截,使用它的原始句柄进行处理。 [net-error]: https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h +[file-system-api]: https://developer.mozilla.org/en-US/docs/Web/API/LocalFileSystem From c9dd59eb865c40357d2b9d862a53befe8a7693b2 Mon Sep 17 00:00:00 2001 From: Weiqiang Lin <23leo@163.com> Date: Sun, 12 Feb 2017 22:36:08 +0800 Subject: [PATCH 124/925] session to zh-CN --- docs-translations/zh-CN/api/session.md | 57 +++++++++++++++++--------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/docs-translations/zh-CN/api/session.md b/docs-translations/zh-CN/api/session.md index 927b9c16ab..be02c26bc2 100644 --- a/docs-translations/zh-CN/api/session.md +++ b/docs-translations/zh-CN/api/session.md @@ -1,46 +1,64 @@ # session -`session` 模块可以用来创建一个新的 `Session` 对象. +> 管理浏览器会话,Cookie,缓存,代理设置等。 -你也可以通过使用 [`webContents`](web-contents.md) 的属性 `session` 来使用一个已有页面的 `session` ,`webContents` 是[`BrowserWindow`](browser-window.md) 的属性. +进程: [Main](../glossary.md#main-process) + +`session` 模块可以用来创建一个新的 `Session` 对象。 + +你也可以通过使用 [`webContents`](web-contents.md) 的属性 `session` 来使用一个已有页面的 `session` ,`webContents` 是[`BrowserWindow`](browser-window.md) 的属性。 ```javascript -const BrowserWindow = require('electron').BrowserWindow +const {BrowserWindow} = require('electron') -var win = new BrowserWindow({ width: 800, height: 600 }) +let win = new BrowserWindow({width: 800, height: 600}) win.loadURL('http://github.com') -var ses = win.webContents.session +const ses = win.webContents.session +console.log(ses.getUserAgent()) ``` ## 方法 -`session` 模块有如下方法: +`session` 模块有如下方法: -### session.fromPartition(partition) +### `session.fromPartition(partition[, options])` * `partition` String +* `options` Object + * `cache` Boolean - 是否启用缓存。 -从字符串 `partition` 返回一个新的 `Session` 实例. +从字符串 `partition` 返回一个新的 `Session` 实例。 -如果 `partition` 以 `persist:` 开头,那么这个page将使用一个持久的 session,这个 session 将对应用的所有 page 可用.如果没前缀,这个 page 将使用一个历史 session.如果 `partition` 为空,那么将返回应用的默认 session . +返回 `Session` - 一个来自 `partition` 字符串的会话实例。当存在时 +`Session` 与同一个 `partition`,它会被返回;否则一个新 +`Session` 实例将使用 `options` 创建。 + +如果 `partition` 以 `persist:` 开头,那么这个 page 将使用一个持久的 session,这个 session 将对应用的所有 page 可用。如果没前缀,这个 page 将使用一个历史 session。如果 `partition` 为空,那么将返回应用的默认 session。 + +要用 `options` 创建一个 `Session`,你必须确保 `Session` 与 +`partition` 从来没有被使用过。没有办法改变现有 `Session` 对象的 `options'。 ## 属性 -`session` 模块有如下属性: +`session` 模块有如下属性: ### session.defaultSession -返回应用的默认 session 对象. +返回应用的默认 `Session` 对象。 ## Class: Session -可以在 `session` 模块中创建一个 `Session` 对象 : +> 获取和设置会话的属性。 + +进程: [Main](../glossary.md#main-process) + +可以在 `session` 模块中创建一个 `Session` 对象: ```javascript -const session = require('electron').session - -var ses = session.fromPartition('persist:name') +const {session} = require('electron') +const ses = session.fromPartition('persist:name') +console.log(ses.getUserAgent()) ``` ### 实例事件 @@ -55,12 +73,13 @@ var ses = session.fromPartition('persist:name') 当 Electron 将要从 `webContents` 下载 `item` 时触发. -调用 `event.preventDefault()` 可以取消下载,并且在进程的下个 tick中,这个 `item` 也不可用. +调用 `event.preventDefault()` 可以取消下载,并且在进程的下个 tick 中,这个 `item` 也不可用。 ```javascript -session.defaultSession.on('will-download', function (event, item, webContents) { +const {session} = require('electron') +session.defaultSession.on('will-download', (event, item, webContents) => { event.preventDefault() - require('request')(item.getURL(), function (data) { + require('request')(item.getURL(), (data) => { require('fs').writeFileSync('/somewhere', data) }) }) @@ -68,7 +87,7 @@ session.defaultSession.on('will-download', function (event, item, webContents) { ### 实例方法 -实例 `Session` 有以下方法: +实例 `Session` 有以下方法: #### `ses.cookies` From dddb6458dae65f1ffd8dc77026abef7a85b0fb52 Mon Sep 17 00:00:00 2001 From: Weiqiang Lin <23leo@163.com> Date: Sun, 12 Feb 2017 22:38:24 +0800 Subject: [PATCH 125/925] systemPreferences to zh-CN --- docs-translations/zh-CN/api/system-preferences.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs-translations/zh-CN/api/system-preferences.md b/docs-translations/zh-CN/api/system-preferences.md index 7182e33e76..7ac9b5a7c0 100644 --- a/docs-translations/zh-CN/api/system-preferences.md +++ b/docs-translations/zh-CN/api/system-preferences.md @@ -1,6 +1,6 @@ # systemPreferences -> 获取系统偏好设置. +> 获取系统偏好设置。 可使用的进程: [主进程](../tutorial/quick-start.md#main-process) @@ -15,20 +15,20 @@ console.log(systemPreferences.isDarkMode()) ### Event: 'accent-color-changed' _Windows_ -返回: +返回: * `event` Event * `newColor` String - 用户给系统颜色设置的新的 RGBA 色值。 ### Event: 'color-changed' _Windows_ -返回: +返回: * `event` Event ### Event: 'inverted-color-scheme-changed' _Windows_ -返回: +返回: * `event` Event * `invertedColorScheme` Boolean - 如果一个反色的配色方案正在被使用,比如一个高对比度的主题,则返回 `true` ,否则返回 `false` 。 @@ -37,7 +37,7 @@ console.log(systemPreferences.isDarkMode()) ### `systemPreferences.isDarkMode()` _macOS_ -返回 `Boolean` - 系统是否处于深色模式. +返回 `Boolean` - 系统是否处于深色模式。 ### `systemPreferences.isSwipeTrackingFromScrollEventsEnabled()` _macOS_ From b7551b5d8496f67838d7c3ded687178d25a9022e Mon Sep 17 00:00:00 2001 From: Weiqiang Lin <23leo@163.com> Date: Sun, 12 Feb 2017 22:51:36 +0800 Subject: [PATCH 126/925] Class: Tray to zh-CN --- docs-translations/zh-CN/api/tray.md | 236 +++++++++++++++------------- 1 file changed, 131 insertions(+), 105 deletions(-) diff --git a/docs-translations/zh-CN/api/tray.md b/docs-translations/zh-CN/api/tray.md index 80b8dc12f7..8ecbcac7b5 100644 --- a/docs-translations/zh-CN/api/tray.md +++ b/docs-translations/zh-CN/api/tray.md @@ -1,205 +1,231 @@ -# Tray +# Class:Tray -> 通过 `Tray` 向系统的通知区添加一个带有右键菜单的图标. +> 将图标和上下文菜单添加到系统的通知区域。 + +可使用的进程: [主进程](../tutorial/quick-start.md#main-process) + +`Tray` 是一个 [事件发出者][event-emitter]。 ```javascript -const electron = require('electron') -const app = electron.app -const Menu = electron.Menu -const Tray = electron.Tray +const {app, Menu, Tray} = require('electron') -var appIcon = null -app.on('ready', function () { - appIcon = new Tray('/path/to/my/icon') - var contextMenu = Menu.buildFromTemplate([ - { label: 'Item1', type: 'radio' }, - { label: 'Item2', type: 'radio' }, - { label: 'Item3', type: 'radio', checked: true }, - { label: 'Item4', type: 'radio' } +let tray = null +app.on('ready', () => { + tray = new Tray('/path/to/my/icon') + const contextMenu = Menu.buildFromTemplate([ + {label: 'Item1', type: 'radio'}, + {label: 'Item2', type: 'radio'}, + {label: 'Item3', type: 'radio', checked: true}, + {label: 'Item4', type: 'radio'} ]) - appIcon.setToolTip('This is my application.') - appIcon.setContextMenu(contextMenu) + tray.setToolTip('This is my application.') + tray.setContextMenu(contextMenu) }) - ``` __平台限制:__ -* 在 Linux, 如果支持应用指示器则使用它,否则使用 `GtkStatusIcon` 代替. -* 在 Linux ,配置了只有有了应用指示器的支持, 你必须安装 `libappindicator1` 来让 tray icon 执行. -* 应用指示器只有在它拥有 context menu 时才会显示. -* 当在linux 上使用了应用指示器,将忽略点击事件. -* 在 Linux,为了让单独的 `MenuItem` 起效,需要再次调用 `setContextMenu` .例如: +* 在 Linux, 如果支持应用指示器则使用它,否则使用 `GtkStatusIcon` 代替。 +* 在 Linux ,配置了只有有了应用指示器的支持, 你必须安装 `libappindicator1` 来让 tray icon 执行。 +* 应用指示器只有在它拥有 context menu 时才会显示。 +* 当在linux 上使用了应用指示器,将忽略点击事件。 +* 在 Linux,为了让单独的 `MenuItem` 起效,需要再次调用 `setContextMenu` 。例如: ```javascript -contextMenu.items[2].checked = false -appIcon.setContextMenu(contextMenu) +const {app, Menu, Tray} = require('electron') + +let appIcon = null +app.on('ready', () => { + 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[1].checked = false + + // Call this again for Linux because we modified the context menu + appIcon.setContextMenu(contextMenu) +}) ``` -如果想在所有平台保持完全相同的行为,不应该依赖点击事件,而是一直将一个 context menu 添加到 tray icon. -## Class: Tray - -`Tray` 是一个 [事件发出者][event-emitter]. +如果想在所有平台保持完全相同的行为,不应该依赖点击事件,而是一直将一个 context menu 添加到 tray icon。 ### `new Tray(image)` -* `image` [NativeImage](native-image.md) +* `image` ([NativeImage](native-image.md) | String) -创建一个与 `image` 相关的 icon. +创建一个与 `image` 相关的 icon。 -## 事件 +### Instance Events -`Tray` 模块可发出下列事件: +`Tray` 模块可发出下列事件: -**注意:** 一些事件只能在特定的os中运行,已经标明. - -### Event: 'click' +#### Event: 'click' * `event` Event * `altKey` Boolean * `shiftKey` Boolean * `ctrlKey` Boolean * `metaKey` Boolean -* `bounds` Object - tray icon 的 bounds. - * `x` Integer - * `y` Integer - * `width` Integer - * `height` Integer +* `bounds` [Rectangle](structures/rectangle.md) - tray icon 的 bounds -当tray icon被点击的时候发出事件. +当 tray icon 被点击的时候发出事件。 -__注意:__ `bounds` 只在 macOS 和 Windows 上起效. - -### Event: 'right-click' _macOS_ _Windows_ +#### Event: 'right-click' _macOS_ _Windows_ * `event` Event * `altKey` Boolean * `shiftKey` Boolean * `ctrlKey` Boolean * `metaKey` Boolean -* `bounds` Object - tray icon 的 bounds. - * `x` Integer - * `y` Integer - * `width` Integer - * `height` Integer +* `bounds` [Rectangle](structures/rectangle.md) - tray icon 的 bounds -当tray icon被鼠标右键点击的时候发出事件. +当 tray icon 被鼠标右键点击的时候发出事件。 -### Event: 'double-click' _macOS_ _Windows_ +#### Event: 'double-click' _macOS_ _Windows_ * `event` Event * `altKey` Boolean * `shiftKey` Boolean * `ctrlKey` Boolean * `metaKey` Boolean -* `bounds` Object - tray icon 的 bounds. - * `x` Integer - * `y` Integer - * `width` Integer - * `height` Integer +* `bounds` [Rectangle](structures/rectangle.md) - tray icon 的 bounds -当tray icon被双击的时候发出事件. +当 tray icon 被双击的时候发出事件。 -### Event: 'balloon-show' _Windows_ +#### Event: 'balloon-show' _Windows_ -当tray 气泡显示的时候发出事件. +当 tray 气泡显示的时候发出事件。 -### Event: 'balloon-click' _Windows_ +#### Event: 'balloon-click' _Windows_ -当tray 气泡被点击的时候发出事件. +当 tray 气泡被点击的时候发出事件。 -### Event: 'balloon-closed' _Windows_ +#### Event: 'balloon-closed' _Windows_ -当tray 气泡关闭的时候发出事件,因为超时或人为关闭. +当 tray 气泡关闭的时候发出事件,因为超时或人为关闭。 -### Event: 'drop' _macOS_ +#### Event: 'drop' _macOS_ -当tray icon上的任何可拖动项被删除的时候发出事件. +当 tray icon 上的任何可拖动项被删除的时候发出事件。 -### Event: 'drop-files' _macOS_ +#### Event: 'drop-files' _macOS_ * `event` * `files` Array - 已删除文件的路径. -当tray icon上的可拖动文件被删除的时候发出事件. +当 tray icon 上的可拖动文件被删除的时候发出事件。 -### Event: 'drag-enter' _macOS_ +#### Event: 'drag-enter' _macOS_ -当一个拖动操作进入tray icon的时候发出事件. +当一个拖动操作进入 tray icon 的时候发出事件。 -### Event: 'drag-leave' _macOS_ +#### Event: 'drag-leave' _macOS_ -当一个拖动操作离开tray icon的时候发出事件. -Emitted when a drag operation exits the tray icon. +当一个拖动操作离开 tray icon 的时候发出事件。 -### Event: 'drag-end' _macOS_ +#### Event: 'drag-end' _macOS_ -当一个拖动操作在tray icon上或其它地方停止拖动的时候发出事件. +当一个拖动操作在 tray icon 上或其它地方停止拖动的时候发出事件。 -## 方法 +### 方法 -`Tray` 模块有以下方法: +`Tray` 模块有以下方法: -**Note:** 一些方法只能在特定的os中运行,已经标明. +#### `tray.destroy()` -### `Tray.destroy()` +立刻删除 tray icon。 -立刻删除 tray icon. +#### `tray.setImage(image)` -### `Tray.setImage(image)` +* `image` ([NativeImage](native-image.md) | String) + +让 `image` 与 tray icon 关联起来。 + +#### `tray.setPressedImage(image)` _macOS_ * `image` [NativeImage](native-image.md) -让 `image` 与 tray icon 关联起来. +当在 macOS 上按压 tray icon 的时候,让 `image` 与 tray icon 关联起来。 -### `Tray.setPressedImage(image)` _macOS_ - -* `image` [NativeImage](native-image.md) - -当在 macOS 上按压 tray icon 的时候, 让 `image` 与 tray icon 关联起来. - -### `Tray.setToolTip(toolTip)` +#### `tray.setToolTip(toolTip)` * `toolTip` String -为 tray icon 设置 hover text. +为 tray icon 设置 hover text。 -### `Tray.setTitle(title)` _macOS_ +#### `tray.setTitle(title)` _macOS_ * `title` String -在状态栏沿着 tray icon 设置标题. +在状态栏沿着 tray icon 设置标题。 -### `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. -当 tray icon 被点击的时候,是否设置它的背景色变为高亮(blue).默认为 true. +设置 tray icon 的背景色变为高亮(blue)。 -### `Tray.displayBalloon(options)` _Windows_ +**注意:** 你可以使用 `highlightMode` 和一个 [`BrowserWindow`](browser-window.md) +通过在窗口可见性时切换 `'never'` 和 `'always'` 模式变化。 + +```javascript +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_ * `options` Object - * `icon` [NativeImage](native-image.md) - * `title` String - * `content` String + * `icon` ([NativeImage](native-image.md) | String) - (可选) + * `title` String - (可选) + * `content` String - (可选) -展示一个 tray balloon. +展示一个 tray balloon。 -### `Tray.popUpContextMenu([menu, position])` _macOS_ _Windows_ +#### `tray.popUpContextMenu([menu, position])` _macOS_ _Windows_ * `menu` Menu (optional) * `position` Object (可选) - 上托位置. * `x` Integer * `y` Integer -从 tray icon 上托出 context menu . 当划过 `menu` 的时候, `menu` 显示,代替 tray 的 context menu . +从 tray icon 上托出 context menu。当划过 `menu` 的时候, `menu` 显示,代替 tray 的 context menu。 -`position` 只在 windows 上可用,默认为 (0, 0) . +`position` 只在 windows 上可用,默认为 (0, 0)。 -### `Tray.setContextMenu(menu)` +#### `tray.setContextMenu(menu)` * `menu` Menu -为这个 icon 设置 context menu . +为这个 icon 设置 context menu。 -[event-emitter]: http://nodejs.org/api/events.html#events_class_events_eventemitter +#### `tray.getBounds()` _macOS_ _Windows_ + +返回 [`Rectangle`](structures/rectangle.md) + +这个 tray icon 的 `bounds` 对象。 + +#### `tray.isDestroyed()` + +返回 `Boolean` - tray icon 是否销毁。 + +[event-emitter]: https://nodejs.org/api/events.html#events_class_eventemitter From e262f6c63ebbf0baf86670de9bb69274dd648cc1 Mon Sep 17 00:00:00 2001 From: Weiqiang Lin <23leo@163.com> Date: Sun, 12 Feb 2017 23:12:33 +0800 Subject: [PATCH 127/925] webContents to zh-CN --- docs-translations/zh-CN/api/web-contents.md | 409 +++++++++++--------- 1 file changed, 230 insertions(+), 179 deletions(-) diff --git a/docs-translations/zh-CN/api/web-contents.md b/docs-translations/zh-CN/api/web-contents.md index 838fefc5cb..b13628533e 100644 --- a/docs-translations/zh-CN/api/web-contents.md +++ b/docs-translations/zh-CN/api/web-contents.md @@ -1,30 +1,64 @@ # webContents +> 渲染和控制网页。 + +可使用的进程: [主进程](../tutorial/quick-start.md#main-process) + `webContents` 是一个 [事件发出者](http://nodejs.org/api/events.html#events_class_events_eventemitter). - 它负责渲染并控制网页,也是 [`BrowserWindow`](browser-window.md) 对象的属性.一个使用 `webContents` 的例子: ```javascript -const BrowserWindow = require('electron').BrowserWindow +const {BrowserWindow} = require('electron') -var win = new BrowserWindow({width: 800, height: 1500}) +let win = new BrowserWindow({width: 800, height: 1500}) win.loadURL('http://github.com') -var webContents = win.webContents +let contents = win.webContents +console.log(contents) ``` +## 方法 + +这些方法可以从 `webContents` 模块访问: + +```javascript +const {webContents} = require('electron') +console.log(webContents) +``` + +### `webContents.getAllWebContents()` + +返回 `WebContents[]` - 所有 `WebContents` 实例的数组。这将包含Web内容 +适用于所有 windows,webviews,打开的 devtools 和 devtools 扩展背景页面。 + +### `webContents.getFocusedWebContents()` + +返回 `WebContents` - 在此应用程序中焦点的 Web 内容,否则返回`null`。 + +### `webContents.fromId(id)` + +* `id` Integer + +返回 `WebContents` - 一个给定 ID 的 WebContents 实例。 + +## Class: WebContents + +> 渲染和控制浏览器窗口实例的内容。 + +可使用的进程: [主进程](../tutorial/quick-start.md#main-process) + ## 事件 -`webContents` 对象可发出下列事件: +`webContents` 对象可发出下列事件: ### Event: 'did-finish-load' -当导航完成时发出事件,`onload` 事件也完成. +当导航完成时发出事件,`onload` 事件也完成。 ### Event: 'did-fail-load' -返回: +返回: * `event` Event * `errorCode` Integer @@ -32,28 +66,28 @@ var webContents = win.webContents * `validatedURL` String * `isMainFrame` Boolean -这个事件类似 `did-finish-load` ,但是是在加载失败或取消加载时发出, 例如, `window.stop()` 请求结束.错误代码的完整列表和它们的含义都可以在 [这里](https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h) 找到. +这个事件类似 `did-finish-load` ,但是是在加载失败或取消加载时发出, 例如, `window.stop()` 请求结束。错误代码的完整列表和它们的含义都可以在 [这里](https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h) 找到。 ### Event: 'did-frame-finish-load' -返回: +返回: * `event` Event * `isMainFrame` Boolean -当一个 frame 导航完成的时候发出事件. +当一个 frame 导航完成的时候发出事件。 ### Event: 'did-start-loading' -当 tab 的spinner 开始 spinning的时候. +当 tab 的spinner 开始 spinning的时候。 ### Event: 'did-stop-loading' -当 tab 的spinner 结束 spinning的时候. +当 tab 的spinner 结束 spinning的时候。 ### Event: 'did-get-response-details' -返回: +返回: * `event` Event * `status` Boolean @@ -65,12 +99,12 @@ var webContents = win.webContents * `headers` Object * `resourceType` String -当有关请求资源的详细信息可用的时候发出事件. -`status` 标识了 socket链接来下载资源. +当有关请求资源的详细信息可用的时候发出事件。 +`status` 标识了 socket 链接来下载资源。 ### Event: 'did-get-redirect-request' -返回: +返回: * `event` Event * `oldURL` String @@ -81,112 +115,129 @@ var webContents = win.webContents * `referrer` String * `headers` Object -当在请求资源时收到重定向的时候发出事件. +当在请求资源时收到重定向的时候发出事件。 ### Event: 'dom-ready' -返回: +返回: * `event` Event -当指定 frame 中的 文档加载完成的时候发出事件. +当指定 frame 中的 文档加载完成的时候发出事件。 ### Event: 'page-favicon-updated' -返回: +返回: * `event` Event * `favicons` Array - Array of URLs -当 page 收到图标 url 的时候发出事件. +当 page 收到图标 url 的时候发出事件。 ### Event: 'new-window' -返回: +返回: * `event` Event * `url` String * `frameName` String -* `disposition` String - 可为 `default`, `foreground-tab`, `background-tab`, - `new-window` 和 `other`. -* `options` Object - 创建新的 `BrowserWindow`时使用的参数. +* `disposition` String - 可为 `default`、 `foreground-tab`、 `background-tab`、 + `new-window` 和 `other`。 +* `options` Object - 创建新的 `BrowserWindow`时使用的参数。 +* `additionalFeatures` String[] - 非标准功能(功能未处理 +   由 Chromium 或 Electron )赋予 `window.open()`。 -当 page 请求打开指定 url 窗口的时候发出事件.这可以是通过 `window.open` 或一个外部连接如 `` 发出的请求. +当 page 请求打开指定 url 窗口的时候发出事件.这可以是通过 `window.open` 或一个外部连接如 `` 发出的请求。 -默认指定 `url` 的 `BrowserWindow` 会被创建. +默认指定 `url` 的 `BrowserWindow` 会被创建。 -调用 `event.preventDefault()` 可以用来阻止打开窗口. +调用 `event.preventDefault()` 可以用来阻止打开窗口。 + +调用 `event.preventDefault()` 将阻止 Electron 自动创建 +新 `BrowserWindow`。 如果调用 `event.preventDefault()` 并手动创建一个新的 +`BrowserWindow`,那么你必须设置 `event.newGuest` 来引用新的 `BrowserWindow` +实例,如果不这样做可能会导致意外的行为。例如: + +```javascript +myBrowserWindow.webContents.on('new-window', (event, url) => { + event.preventDefault() + const win = new BrowserWindow({show: false}) + win.once('ready-to-show', () => win.show()) + win.loadURL(url) + event.newGuest = win +}) +``` ### Event: 'will-navigate' -返回: +返回: * `event` Event * `url` String -当用户或 page 想要开始导航的时候发出事件.它可在当 `window.location` 对象改变或用户点击 page 中的链接的时候发生. +当用户或 page 想要开始导航的时候发出事件。它可在当 `window.location` 对象改变或用户点击 page 中的链接的时候发生。 -当使用 api(如 `webContents.loadURL` 和 `webContents.back`) 以编程方式来启动导航的时候,这个事件将不会发出. +当使用 api(如 `webContents.loadURL` 和 `webContents.back`) 以编程方式来启动导航的时候,这个事件将不会发出。 -它也不会在页内跳转发生, 例如点击锚链接或更新 `window.location.hash`.使用 `did-navigate-in-page` 事件可以达到目的. +它也不会在页内跳转发生,例如点击锚链接或更新 `window.location.hash`。使用 `did-navigate-in-page` 事件可以达到目的。 -调用 `event.preventDefault()` 可以阻止导航. +调用 `event.preventDefault()` 可以阻止导航。 ### Event: 'did-navigate' -返回: +返回: * `event` Event * `url` String -当一个导航结束时候发出事件. +当一个导航结束时候发出事件。 -页内跳转时不会发出这个事件,例如点击锚链接或更新 `window.location.hash`.使用 `did-navigate-in-page` 事件可以达到目的. +页内跳转时不会发出这个事件,例如点击锚链接或更新 `window.location.hash`。使用 `did-navigate-in-page` 事件可以达到目的。 ### Event: 'did-navigate-in-page' -返回: +返回: * `event` Event * `url` String -当页内导航发生的时候发出事件. +当页内导航发生的时候发出事件。 -当页内导航发生的时候,page 的url 改变,但是不会跳出界面.例如当点击锚链接时或者 DOM 的 `hashchange` 事件发生. +当页内导航发生的时候,page 的url 改变,但是不会跳出界面。例如当点击锚链接时或者 DOM 的 `hashchange` 事件发生。 ### Event: 'crashed' -当渲染进程崩溃的时候发出事件. +当渲染进程崩溃的时候发出事件。 ### Event: 'plugin-crashed' -返回: +返回: * `event` Event * `name` String * `version` String -当插件进程崩溃时候发出事件. +当插件进程崩溃时候发出事件。 ### Event: 'destroyed' -当 `webContents` 被删除的时候发出事件. +当 `webContents` 被删除的时候发出事件。 ### Event: 'devtools-opened' -当开发者工具栏打开的时候发出事件. +当开发者工具栏打开的时候发出事件。 ### Event: 'devtools-closed' -当开发者工具栏关闭时候发出事件. +当开发者工具栏关闭时候发出事件。 ### Event: 'devtools-focused' -当开发者工具栏获得焦点或打开的时候发出事件. +当开发者工具栏获得焦点或打开的时候发出事件。 ### Event: 'certificate-error' -返回: +返回: * `event` Event * `url` URL @@ -196,13 +247,13 @@ var webContents = win.webContents * `issuerName` String * `callback` Function -当验证证书或 `url` 失败的时候发出事件. +当验证证书或 `url` 失败的时候发出事件。 -使用方法类似 [`app` 的 `certificate-error` 事件](app.md#event-certificate-error). +使用方法类似 [`app` 的 `certificate-error` 事件](app.md#event-certificate-error)。 ### Event: 'select-client-certificate' -返回: +返回: * `event` Event * `url` URL @@ -211,13 +262,13 @@ var webContents = win.webContents * `issuerName` String - Issuer's Common Name * `callback` Function -当请求客户端证书的时候发出事件. +当请求客户端证书的时候发出事件。 -使用方法类似 [`app` 的 `select-client-certificate` 事件](app.md#event-select-client-certificate). +使用方法类似 [`app` 的 `select-client-certificate` 事件](app.md#event-select-client-certificate)。 ### Event: 'login' -返回: +返回: * `event` Event * `request` Object @@ -234,33 +285,33 @@ var webContents = win.webContents 当 `webContents` 想做基本验证的时候发出事件. -使用方法类似 [the `login` event of `app`](app.md#event-login). +使用方法类似 [the `login` event of `app`](app.md#event-login)。 ### Event: 'found-in-page' -返回: +返回: * `event` Event * `result` Object * `requestId` Integer - * `finalUpdate` Boolean - 标识是否还有更多的值可以查看. - * `activeMatchOrdinal` Integer (可选) - 活动匹配位置 - * `matches` Integer (可选) - 匹配数量. - * `selectionArea` Object (可选) - 协调首个匹配位置. + * `finalUpdate` Boolean - 标识是否还有更多的值可以查看。 + * `activeMatchOrdinal` Integer (可选) - 活动匹配位置。 + * `matches` Integer (可选) - 匹配数量。 + * `selectionArea` Object (可选) - 协调首个匹配位置。 -当使用 [`webContents.findInPage`] 进行页内查找并且找到可用值得时候发出事件. +当使用 [`webContents.findInPage`] 进行页内查找并且找到可用值得时候发出事件。 ### Event: 'media-started-playing' -当媒体开始播放的时候发出事件. +当媒体开始播放的时候发出事件。 ### Event: 'media-paused' -当媒体停止播放的时候发出事件. +当媒体停止播放的时候发出事件。 ### Event: 'did-change-theme-color' -当page 的主题色时候发出事件.这通常由于引入了一个 meta 标签 : +当page 的主题色时候发出事件。这通常由于引入了一个 meta 标签: ```html @@ -268,7 +319,7 @@ var webContents = win.webContents ### Event: 'cursor-changed' -返回: +返回: * `event` Event * `type` String @@ -282,9 +333,9 @@ var webContents = win.webContents `row-resize`, `m-panning`, `e-panning`, `n-panning`, `ne-panning`, `nw-panning`, `s-panning`, `se-panning`, `sw-panning`, `w-panning`, `move`, `vertical-text`, `cell`, `context-menu`, `alias`, `progress`, `nodrop`, `copy`, `none`, -`not-allowed`, `zoom-in`, `zoom-out`, `grab`, `grabbing`, `custom`. +`not-allowed`, `zoom-in`, `zoom-out`, `grab`, `grabbing`, `custom`。 -如果 `type` 参数值为 `custom`, `image` 参数会在一个`NativeImage` 中控制自定义鼠标图片, 并且 `scale` 会控制图片的缩放比例. +如果 `type` 参数值为 `custom`、 `image` 参数会在一个`NativeImage` 中控制自定义鼠标图片,并且 `scale` 会控制图片的缩放比例。 ## 实例方法 @@ -298,8 +349,8 @@ var webContents = win.webContents * `userAgent` String - 产生请求的用户代理 * `extraHeaders` String - 以 "\n" 分隔的额外头 -在窗口中加载 `url` , `url` 必须包含协议前缀, -比如 `http://` 或 `file://`. 如果加载想要忽略 http 缓存,可以使用 `pragma` 头来达到目的. +在窗口中加载 `url` 、 `url` 必须包含协议前缀, +比如 `http://` 或 `file://`。如果加载想要忽略 http 缓存,可以使用 `pragma` 头来达到目的。 ```javascript const options = {'extraHeaders': 'pragma: no-cache\n'} @@ -310,11 +361,11 @@ webContents.loadURL(url, options) * `url` URL -初始化一个指定 `url` 的资源下载,不导航跳转. `session` 的 `will-download` 事件会触发. +初始化一个指定 `url` 的资源下载,不导航跳转。 `session` 的 `will-download` 事件会触发。 ### `webContents.getURL()` -返回当前page 的 url. +返回当前 page 的 url。 ```javascript var win = new BrowserWindow({width: 800, height: 600}) @@ -325,85 +376,85 @@ var currentURL = win.webContents.getURL() ### `webContents.getTitle()` -返回当前page 的 标题. +返回当前 page 的标题。 ### `webContents.isLoading()` -返回一个布尔值,标识当前页是否正在加载. +返回一个布尔值,标识当前页是否正在加载。 ### `webContents.isWaitingForResponse()` -返回一个布尔值,标识当前页是否正在等待主要资源的第一次响应. +返回一个布尔值,标识当前页是否正在等待主要资源的第一次响应。 ### `webContents.stop()` -停止还为开始的导航. +停止还为开始的导航。 ### `webContents.reload()` -重载当前页. +重载当前页。 ### `webContents.reloadIgnoringCache()` -重载当前页,忽略缓存. +重载当前页,忽略缓存。 ### `webContents.canGoBack()` -返回一个布尔值,标识浏览器是否能回到前一个page. +返回一个布尔值,标识浏览器是否能回到前一个page。 ### `webContents.canGoForward()` -返回一个布尔值,标识浏览器是否能前往下一个page. +返回一个布尔值,标识浏览器是否能前往下一个page。 ### `webContents.canGoToOffset(offset)` * `offset` Integer -返回一个布尔值,标识浏览器是否能前往指定 `offset` 的page. +返回一个布尔值,标识浏览器是否能前往指定 `offset` 的page。 ### `webContents.clearHistory()` -清除导航历史. +清除导航历史。 ### `webContents.goBack()` -让浏览器回退到前一个page. +让浏览器回退到前一个page。 ### `webContents.goForward()` -让浏览器回前往下一个page. +让浏览器回前往下一个page。 ### `webContents.goToIndex(index)` * `index` Integer -让浏览器回前往指定 `index` 的page. +让浏览器回前往指定 `index` 的page。 ### `webContents.goToOffset(offset)` * `offset` Integer -导航到相对于当前页的偏移位置页. +导航到相对于当前页的偏移位置页。 ### `webContents.isCrashed()` -渲染进程是否崩溃. +渲染进程是否崩溃。 ### `webContents.setUserAgent(userAgent)` * `userAgent` String -重写本页用户代理. +重写本页用户代理。 ### `webContents.getUserAgent()` -返回一个 `String` ,标识本页用户代理信息. +返回一个 `String` ,标识本页用户代理信息。 ### `webContents.insertCSS(css)` * `css` String -为当前页插入css. +为当前页插入css。 ### `webContents.executeJavaScript(code[, userGesture, callback])` @@ -412,99 +463,99 @@ var currentURL = win.webContents.getURL() * `callback` Function (可选) - 脚本执行完成后调用的回调函数. * `result` -评估 page `代码`. +评估 page `代码`。 -浏览器窗口中的一些 HTML API ,例如 `requestFullScreen`,只能被用户手势请求.设置 `userGesture` 为 `true` 可以取消这个限制. +浏览器窗口中的一些 HTML API ,例如 `requestFullScreen`,只能被用户手势请求。设置 `userGesture` 为 `true` 可以取消这个限制。 ### `webContents.setAudioMuted(muted)` * `muted` Boolean -减缓当前页的 audio 的播放速度. +减缓当前页的 audio 的播放速度。 ### `webContents.isAudioMuted()` -返回一个布尔值,标识当前页是否减缓了 audio 的播放速度. +返回一个布尔值,标识当前页是否减缓了 audio 的播放速度。 ### `webContents.undo()` -执行网页的编辑命令 `undo` . +执行网页的编辑命令 `undo`。 ### `webContents.redo()` -执行网页的编辑命令 `redo` . +执行网页的编辑命令 `redo`。 ### `webContents.cut()` -执行网页的编辑命令 `cut` . +执行网页的编辑命令 `cut`。 ### `webContents.copy()` -执行网页的编辑命令 `copy` . +执行网页的编辑命令 `copy`。 ### `webContents.paste()` -执行网页的编辑命令 `paste` . +执行网页的编辑命令 `paste`。 ### `webContents.pasteAndMatchStyle()` -执行网页的编辑命令 `pasteAndMatchStyle` . +执行网页的编辑命令 `pasteAndMatchStyle`。 ### `webContents.delete()` -执行网页的编辑命令 `delete` . +执行网页的编辑命令 `delete`。 ### `webContents.selectAll()` -执行网页的编辑命令 `selectAll` . +执行网页的编辑命令 `selectAll`。 ### `webContents.unselect()` -执行网页的编辑命令 `unselect` . +执行网页的编辑命令 `unselect`。 ### `webContents.replace(text)` * `text` String -执行网页的编辑命令 `replace` . +执行网页的编辑命令 `replace`。 ### `webContents.replaceMisspelling(text)` * `text` String -执行网页的编辑命令 `replaceMisspelling` . +执行网页的编辑命令 `replaceMisspelling`。 ### `webContents.insertText(text)` * `text` String -插入 `text` 到获得了焦点的元素. +插入 `text` 到获得了焦点的元素。 ### `webContents.findInPage(text[, options])` -* `text` String - 查找内容, 不能为空. +* `text` String - 查找内容,不能为空。 * `options` Object (可选) - * `forward` Boolean - 是否向前或向后查找, 默认为 `true`. - * `findNext` Boolean - 当前操作是否是第一次查找或下一次查找, - 默认为 `false`. - * `matchCase` Boolean - 查找是否区分大小写, - 默认为 `false`. - * `wordStart` Boolean -是否仅以首字母查找. - 默认为 `false`. - * `medialCapitalAsWordStart` Boolean - 是否结合 `wordStart`,如果匹配是大写字母开头,后面接小写字母或无字母,那么就接受这个词中匹配.接受几个其它的合成词匹配, 默认为 `false`. + * `forward` Boolean - 是否向前或向后查找,默认为 `true`。 + * `findNext` Boolean - 当前操作是否是第一次查找或下一次查找, + 默认为 `false`。 + * `matchCase` Boolean - 查找是否区分大小写, + 默认为 `false`。 + * `wordStart` Boolean -是否仅以首字母查找, + 默认为 `false`。 + * `medialCapitalAsWordStart` Boolean - 是否结合 `wordStart`,如果匹配是大写字母开头,后面接小写字母或无字母,那么就接受这个词中匹配。接受几个其它的合成词匹配,默认为 `false`。 -发起请求,在网页中查找所有与 `text` 相匹配的项,并且返回一个 `Integer` 来表示这个请求用的请求Id.这个请求结果可以通过订阅 - [`found-in-page`](web-contents.md#event-found-in-page) 事件来取得. +发起请求,在网页中查找所有与 `text` 相匹配的项,并且返回一个 `Integer` 来表示这个请求用的请求 Id。这个请求结果可以通过订阅 + [`found-in-page`](web-contents.md#event-found-in-page) 事件来取得。 ### `webContents.stopFindInPage(action)` * `action` String - 指定一个行为来接替停止 - [`webContents.findInPage`] 请求. - * `clearSelection` - 转变为一个普通的 selection. - * `keepSelection` - 清除 selection. - * `activateSelection` - 获取焦点并点击 selection node. + [`webContents.findInPage`] 请求。 + * `clearSelection` - 转变为一个普通的 selection。 + * `keepSelection` - 清除 selection。 + * `activateSelection` - 获取焦点并点击 selection node。 -使用给定的 `action` 来为 `webContents` 停止任何 `findInPage` 请求. +使用给定的 `action` 来为 `webContents` 停止任何 `findInPage` 请求。 ```javascript webContents.on('found-in-page', function (event, result) { @@ -518,42 +569,42 @@ const requestId = webContents.findInPage('api') * `callback` Function -检查是否有任何 ServiceWorker 注册了,并且返回一个布尔值,来作为 `callback`响应的标识. +检查是否有任何 ServiceWorker 注册了,并且返回一个布尔值,来作为 `callback`响应的标识。 ### `webContents.unregisterServiceWorker(callback)` * `callback` Function -如果存在任何 ServiceWorker ,则全部注销,并且当JS承诺执行行或JS拒绝执行而失败的时候,返回一个布尔值,它标识了相应的 `callback`. +如果存在任何 ServiceWorker,则全部注销,并且当JS承诺执行行或JS拒绝执行而失败的时候,返回一个布尔值,它标识了相应的 `callback`。 ### `webContents.print([options])` * `options` Object (可选) - * `silent` Boolean - 不需要请求用户的打印设置. 默认为 `false`. - * `printBackground` Boolean - 打印背景和网页图片. 默认为 `false`. + * `silent` Boolean - 不需要请求用户的打印设置. 默认为 `false`。 + * `printBackground` Boolean - 打印背景和网页图片. 默认为 `false`。 -打印窗口的网页. 当设置 `silent` 为 `false` 的时候,Electron 将使用系统默认的打印机和打印方式来打印. +打印窗口的网页。当设置 `silent` 为 `false` 的时候,Electron 将使用系统默认的打印机和打印方式来打印。 -在网页中调用 `window.print()` 和 调用 `webContents.print({silent: false, printBackground: false})`具有相同的作用. +在网页中调用 `window.print()` 和 调用 `webContents.print({silent: false, printBackground: false})`具有相同的作用。 -**注意:** 在 Windows, 打印 API 依赖于 `pdf.dll`. 如果你的应用不使用任何的打印, 你可以安全删除 `pdf.dll` 来减少二进制文件的size. +**注意:** 在 Windows,打印 API 依赖于 `pdf.dll`。如果你的应用不使用任何的打印,你可以安全删除 `pdf.dll` 来减少二进制文件的size。 ### `webContents.printToPDF(options, callback)` * `options` Object - * `marginsType` Integer - 指定使用的 margin type. 默认 margin 使用 0, 无 margin 使用 1, 最小化 margin 使用 2. - * `pageSize` String - 指定生成的PDF文件的page size. 可以是 `A3`, - `A4`, `A5`, `Legal`, `Letter` 和 `Tabloid`. - * `printBackground` Boolean - 是否打印 css 背景. - * `printSelectionOnly` Boolean - 是否只打印选中的部分. - * `landscape` Boolean - landscape 为 `true`, portrait 为 `false`. + * `marginsType` Integer - 指定使用的 margin type。默认 margin 使用 0,无 margin 使用 1,最小化 margin 使用 2。 + * `pageSize` String - 指定生成的PDF文件的page size. 可以是 `A3`、 + `A4`、 `A5`、 `Legal`、`Letter` 和 `Tabloid`。 + * `printBackground` Boolean - 是否打印 css 背景。 + * `printSelectionOnly` Boolean - 是否只打印选中的部分。 + * `landscape` Boolean - landscape 为 `true`, portrait 为 `false`。 * `callback` Function -打印窗口的网页为 pdf ,使用 Chromium 预览打印的自定义设置. +打印窗口的网页为 pdf,使用 Chromium 预览打印的自定义设置。 -完成时使用 `callback(error, data)` 调用 `callback` . `data` 是一个 `Buffer` ,包含了生成的 pdf 数据. +完成时使用 `callback(error, data)` 调用 `callback` 。 `data` 是一个 `Buffer` ,包含了生成的 pdf 数据、 -默认,空的 `options` 被视为 : +默认,空的 `options` 被视为: ```javascript { @@ -587,7 +638,7 @@ win.webContents.on('did-finish-load', function () { * `path` String -添加指定的路径给开发者工具栏的 workspace.必须在 DevTools 创建之后使用它 : +添加指定的路径给开发者工具栏的 workspace。必须在 DevTools 创建之后使用它: ```javascript mainWindow.webContents.on('devtools-opened', function () { @@ -599,52 +650,52 @@ mainWindow.webContents.on('devtools-opened', function () { * `path` String -从开发者工具栏的 workspace 删除指定的路径. +从开发者工具栏的 workspace 删除指定的路径。 ### `webContents.openDevTools([options])` * `options` Object (可选) * `detach` Boolean - 在一个新窗口打开开发者工具栏 -打开开发者工具栏. +打开开发者工具栏。 ### `webContents.closeDevTools()` -关闭开发者工具栏. +关闭开发者工具栏。 ### `webContents.isDevToolsOpened()` -返回布尔值,开发者工具栏是否打开. +返回布尔值,开发者工具栏是否打开。 ### `webContents.isDevToolsFocused()` -返回布尔值,开发者工具栏视图是否获得焦点. +返回布尔值,开发者工具栏视图是否获得焦点。 ### `webContents.toggleDevTools()` -Toggles 开发者工具. +Toggles 开发者工具。 ### `webContents.inspectElement(x, y)` * `x` Integer * `y` Integer -在 (`x`, `y`) 开始检测元素. +在 (`x`, `y`) 开始检测元素。 ### `webContents.inspectServiceWorker()` -为 service worker 上下文打开开发者工具栏. +为 service worker 上下文打开开发者工具栏。 ### `webContents.send(channel[, arg1][, arg2][, ...])` * `channel` String * `arg` (可选) -通过 `channel` 发送异步消息给渲染进程,你也可发送任意的参数.参数应该在 JSON 内部序列化,并且此后没有函数或原形链被包括了. +通过 `channel` 发送异步消息给渲染进程,你也可发送任意的参数。参数应该在 JSON 内部序列化,并且此后没有函数或原形链被包括了。 -渲染进程可以通过使用 `ipcRenderer` 监听 `channel` 来处理消息. +渲染进程可以通过使用 `ipcRenderer` 监听 `channel` 来处理消息。 -例子,从主进程向渲染进程发送消息 : +例子,从主进程向渲染进程发送消息: ```javascript // 主进程. @@ -696,11 +747,11 @@ app.on('ready', function () { * `y` Float - 设置相对左上角的y轴偏移值 * `scale` Float - 可用空间内的模拟视图偏移 (不在适应视图模式) (默认: `1`) -使用给定的参数来开启设备模拟. +使用给定的参数来开启设备模拟。 ### `webContents.disableDeviceEmulation()` -使用 `webContents.enableDeviceEmulation` 关闭设备模拟. +使用 `webContents.enableDeviceEmulation` 关闭设备模拟。 ### `webContents.sendInputEvent(event)` @@ -713,14 +764,14 @@ app.on('ready', function () { `leftButtonDown`, `middleButtonDown`, `rightButtonDown`, `capsLock`, `numLock`, `left`, `right`. -向 page 发送一个输入 `event` . +向 page 发送一个输入 `event`。 -对键盘事件来说,`event` 对象还有如下属性 : +对键盘事件来说,`event` 对象还有如下属性: -* `keyCode` String (**必需**) - 特点是将作为键盘事件发送. 可用的 key codes [Accelerator](accelerator.md). +* `keyCode` String (**必需**) - 特点是将作为键盘事件发送。可用的 key codes [Accelerator](accelerator.md)。 -对鼠标事件来说,`event` 对象还有如下属性 : +对鼠标事件来说,`event` 对象还有如下属性: * `x` Integer (**required**) * `y` Integer (**required**) @@ -731,7 +782,7 @@ app.on('ready', function () { * `movementY` Integer * `clickCount` Integer -对鼠标滚轮事件来说,`event` 对象还有如下属性 : +对鼠标滚轮事件来说,`event` 对象还有如下属性: * `deltaX` Integer * `deltaY` Integer @@ -746,13 +797,13 @@ app.on('ready', function () { * `callback` Function -开始订阅 提交 事件和捕获数据帧,当有 提交 事件时,使用 `callback(frameBuffer)` 调用 `callback`. +开始订阅 提交 事件和捕获数据帧,当有提交事件时,使用 `callback(frameBuffer)` 调用 `callback`。 -`frameBuffer` 是一个包含原始像素数据的 `Buffer`,像素数据是按照 32bit BGRA 格式有效存储的,但是实际情况是取决于处理器的字节顺序的(大多数的处理器是存放小端序的,如果是在大端序的处理器上,数据是 32bit ARGB 格式). +`frameBuffer` 是一个包含原始像素数据的 `Buffer`,像素数据是按照 32bit BGRA 格式有效存储的,但是实际情况是取决于处理器的字节顺序的(大多数的处理器是存放小端序的,如果是在大端序的处理器上,数据是 32bit ARGB 格式)。 ### `webContents.endFrameSubscription()` -停止订阅帧提交事件. +停止订阅帧提交事件。 ### `webContents.savePage(fullPath, saveType, callback)` @@ -764,7 +815,7 @@ app.on('ready', function () { * `callback` Function - `function(error) {}`. * `error` Error -如果保存界面过程初始化成功,返回 true. +如果保存界面过程初始化成功,返回 true。 ```javascript win.loadURL('https://github.com') @@ -778,25 +829,25 @@ win.webContents.on('did-finish-load', function () { ## 实例属性 -`WebContents` 对象也有下列属性: +`WebContents` 对象也有下列属性: ### `webContents.session` -返回这个 `webContents` 使用的 [session](session.md) 对象. +返回这个 `webContents` 使用的 [session](session.md) 对象。 ### `webContents.hostWebContents` -返回这个 `webContents` 的父 `webContents` . +返回这个 `webContents` 的父 `webContents`。 ### `webContents.devToolsWebContents` -获取这个 `WebContents` 的开发者工具栏的 `WebContents` . +获取这个 `WebContents` 的开发者工具栏的 `WebContents`。 -**注意:** 用户不可保存这个对象,因为当开发者工具栏关闭的时候它的值为 `null` . +**注意:** 用户不可保存这个对象,因为当开发者工具栏关闭的时候它的值为 `null`。 ### `webContents.debugger` -调试 API 为 [remote debugging protocol][rdp] 提供交替传送. +调试 API 为 [remote debugging protocol][rdp] 提供交替传送。 ```javascript try { @@ -822,42 +873,42 @@ win.webContents.debugger.sendCommand('Network.enable') #### `webContents.debugger.attach([protocolVersion])` -* `protocolVersion` String (可选) - 请求调试协议版本. +* `protocolVersion` String (可选) - 请求调试协议版本。 -添加 `webContents` 调试. +添加 `webContents` 调试。 #### `webContents.debugger.isAttached()` -返回一个布尔值,标识是否已经给 `webContents` 添加了调试. +返回一个布尔值,标识是否已经给 `webContents` 添加了调试。 #### `webContents.debugger.detach()` -删除 `webContents` 调试. +删除 `webContents` 调试。 #### `webContents.debugger.sendCommand(method[, commandParams, callback])` -* `method` String - 方法名, 应该是由远程调试协议定义的方法. -* `commandParams` Object (可选) - 请求参数为 JSON 对象. +* `method` String - 方法名, 应该是由远程调试协议定义的方法。 +* `commandParams` Object (可选) - 请求参数为 JSON 对象。 * `callback` Function (可选) - Response - * `error` Object - 错误消息,标识命令失败. - * `result` Object - 回复在远程调试协议中由 'returns'属性定义的命令描述. + * `error` Object - 错误消息,标识命令失败。 + * `result` Object - 回复在远程调试协议中由 'returns'属性定义的命令描述。 -发送给定命令给调试目标. +发送给定命令给调试目标。 ### Event: 'detach' * `event` Event -* `reason` String - 拆分调试器原因. +* `reason` String - 拆分调试器原因。 -在调试 session 结束时发出事件.这在 `webContents` 关闭时或 `webContents` 请求开发者工具栏时发生. +在调试 session 结束时发出事件.这在 `webContents` 关闭时或 `webContents` 请求开发者工具栏时发生。 ### Event: 'message' * `event` Event -* `method` String - 方法名. -* `params` Object - 在远程调试协议中由 'parameters' 属性定义的事件参数. +* `method` String - 方法名。 +* `params` Object - 在远程调试协议中由 'parameters' 属性定义的事件参数。 -每当调试目标发出事件时发出. +每当调试目标发出事件时发出。 [rdp]: https://developer.chrome.com/devtools/docs/debugger-protocol [`webContents.findInPage`]: web-contents.md#webcontentsfindinpagetext-options From 51d5706b48454d08766fa74b009090b7e2f5322e Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Mon, 13 Feb 2017 16:45:43 +0900 Subject: [PATCH 128/925] Update libchromiumcontent: fix usage of private API in MAS build --- script/lib/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/lib/config.py b/script/lib/config.py index 96c6591a54..d3f2c33d4c 100644 --- a/script/lib/config.py +++ b/script/lib/config.py @@ -9,7 +9,7 @@ import sys BASE_URL = os.getenv('LIBCHROMIUMCONTENT_MIRROR') or \ 'https://s3.amazonaws.com/github-janky-artifacts/libchromiumcontent' LIBCHROMIUMCONTENT_COMMIT = os.getenv('LIBCHROMIUMCONTENT_COMMIT') or \ - 'ea20b8dfe0a7fad61bb4917404950ddcd2224588' + '4f5b89374df7ee69095b9f7d50b30fb46ddd7407' PLATFORM = { 'cygwin': 'win32', From 9b8e4eb6cbcbd8aba71f40969989a9b85af8acf6 Mon Sep 17 00:00:00 2001 From: PoppinL Date: Mon, 13 Feb 2017 16:14:49 +0800 Subject: [PATCH 129/925] Fix typo Fix typo --- docs/api/browser-window.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index 370902be99..dd477e8c16 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -47,7 +47,7 @@ win.once('ready-to-show', () => { }) ``` -This is event is usually emitted after the `did-finish-load` event, but for +This event is usually emitted after the `did-finish-load` event, but for pages with many remote resources, it may be emitted before the `did-finish-load` event. From 16382162f8ed2fc591120c345e75d1356fc4a70a Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Mon, 13 Feb 2017 19:27:21 +0900 Subject: [PATCH 130/925] Update brightray for electron/brightray#275. --- vendor/brightray | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/brightray b/vendor/brightray index 509fd37d89..adb27d1c70 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit 509fd37d890ce24d4b1ef0790e7d5e1382d27d0c +Subproject commit adb27d1c703eed84d115906e2e8813dc05185033 From e45d8079b8b74b0e5e89b360df9d3120ad1da724 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Sun, 12 Feb 2017 15:23:27 +1100 Subject: [PATCH 131/925] Add support for a noResolveAliases property --- atom/browser/ui/file_dialog.h | 13 +++++++------ atom/browser/ui/file_dialog_mac.mm | 2 ++ lib/browser/api/dialog.js | 3 ++- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/atom/browser/ui/file_dialog.h b/atom/browser/ui/file_dialog.h index e9734adeb6..6f33c46f20 100644 --- a/atom/browser/ui/file_dialog.h +++ b/atom/browser/ui/file_dialog.h @@ -23,12 +23,13 @@ typedef std::pair > Filter; typedef std::vector Filters; enum FileDialogProperty { - FILE_DIALOG_OPEN_FILE = 1 << 0, - FILE_DIALOG_OPEN_DIRECTORY = 1 << 1, - FILE_DIALOG_MULTI_SELECTIONS = 1 << 2, - FILE_DIALOG_CREATE_DIRECTORY = 1 << 3, - FILE_DIALOG_SHOW_HIDDEN_FILES = 1 << 4, - FILE_DIALOG_PROMPT_TO_CREATE = 1 << 5, + FILE_DIALOG_OPEN_FILE = 1 << 0, + FILE_DIALOG_OPEN_DIRECTORY = 1 << 1, + FILE_DIALOG_MULTI_SELECTIONS = 1 << 2, + FILE_DIALOG_CREATE_DIRECTORY = 1 << 3, + FILE_DIALOG_SHOW_HIDDEN_FILES = 1 << 4, + FILE_DIALOG_PROMPT_TO_CREATE = 1 << 5, + FILE_DIALOG_NO_RESOLVE_ALIASES = 1 << 6, }; typedef base::Callback Date: Sun, 12 Feb 2017 15:25:38 +1100 Subject: [PATCH 132/925] Document the noResolveAliases property --- docs/api/dialog.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/api/dialog.md b/docs/api/dialog.md index 75edc26bdc..60a184a1e1 100644 --- a/docs/api/dialog.md +++ b/docs/api/dialog.md @@ -43,6 +43,9 @@ The `dialog` module has the following methods: in the dialog does not exist. This does not actually create the file at the path but allows non-existent paths to be returned that should be created by the application. + * `noResolveAliases` _macOS_ - Disable the automatic alias (symlink) path + resolution. Selected aliases will now return the alias path instead of + their target path. * `normalizeAccessKeys` Boolean (optional) - Normalize the keyboard access keys across platforms. Default is `false`. Enabling this assumes `&` is used in the button labels for the placement of the keyboard shortcut access key From 17fe5efa13960b17903eb1a1e92d5af6296cab5d Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Sun, 12 Feb 2017 15:27:19 +1100 Subject: [PATCH 133/925] Fix linting --- lib/browser/api/dialog.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/browser/api/dialog.js b/lib/browser/api/dialog.js index 15f3b8e3ae..834a6a6d7d 100644 --- a/lib/browser/api/dialog.js +++ b/lib/browser/api/dialog.js @@ -11,7 +11,7 @@ const fileDialogProperties = { createDirectory: 1 << 3, showHiddenFiles: 1 << 4, promptToCreate: 1 << 5, - noResolveAliases: 1 << 6, + noResolveAliases: 1 << 6 } const messageBoxTypes = ['none', 'info', 'warning', 'error', 'question'] From a746651d1cea692ffc57209bb87d2794c726de23 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 13 Feb 2017 08:23:49 -0800 Subject: [PATCH 134/925] Use dialog setter for consistency with other properties --- atom/browser/ui/file_dialog_mac.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atom/browser/ui/file_dialog_mac.mm b/atom/browser/ui/file_dialog_mac.mm index 4fac811414..70272cbddc 100644 --- a/atom/browser/ui/file_dialog_mac.mm +++ b/atom/browser/ui/file_dialog_mac.mm @@ -94,7 +94,7 @@ void SetupDialogForProperties(NSOpenPanel* dialog, int properties) { if (properties & FILE_DIALOG_SHOW_HIDDEN_FILES) [dialog setShowsHiddenFiles:YES]; if (properties & FILE_DIALOG_NO_RESOLVE_ALIASES) - dialog.resolvesAliases = false; + [dialog setResolvesAliases:NO]; } // Run modal dialog with parent window and return user's choice. From d7ab142e1084fed5cfe0eae79d62bb12a134df8b Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 13 Feb 2017 09:09:42 -0800 Subject: [PATCH 135/925] Call content::WebContentsObserver first --- atom/browser/web_contents_zoom_controller.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/atom/browser/web_contents_zoom_controller.cc b/atom/browser/web_contents_zoom_controller.cc index 92eb30fac8..cb293705c9 100644 --- a/atom/browser/web_contents_zoom_controller.cc +++ b/atom/browser/web_contents_zoom_controller.cc @@ -20,10 +20,10 @@ namespace atom { WebContentsZoomController::WebContentsZoomController( content::WebContents* web_contents) - : old_process_id_(-1), + : content::WebContentsObserver(web_contents), + old_process_id_(-1), old_view_id_(-1), - embedder_zoom_controller_(nullptr), - content::WebContentsObserver(web_contents) { + embedder_zoom_controller_(nullptr) { default_zoom_factor_ = content::kEpsilon; host_zoom_map_ = content::HostZoomMap::GetForWebContents(web_contents); zoom_subscription_ = host_zoom_map_->AddZoomLevelChangedCallback(base::Bind( From f19924bcb04a2e5b690edc271152f8bce5367ec6 Mon Sep 17 00:00:00 2001 From: Gary Wilber Date: Mon, 13 Feb 2017 19:41:24 -0800 Subject: [PATCH 136/925] Add Invalidate method to NativeWindow and add Mac implementation --- atom/browser/api/atom_api_web_contents.cc | 8 ++------ atom/browser/native_window.h | 1 + atom/browser/native_window_mac.h | 1 + atom/browser/native_window_mac.mm | 5 +++++ atom/browser/native_window_views.cc | 6 ++++++ atom/browser/native_window_views.h | 1 + 6 files changed, 16 insertions(+), 6 deletions(-) diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 5ae6186a04..c90437372d 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -1496,12 +1496,8 @@ void WebContents::Invalidate() { osr_rwhv->Invalidate(); } else { const auto ownerWindow = owner_window(); - const auto nativeWindow = ownerWindow ? ownerWindow->GetNativeWindow() : - nullptr; - if (nativeWindow) { - const gfx::Rect& bounds = nativeWindow->bounds(); - nativeWindow->SchedulePaintInRect( - gfx::Rect(0, 0, bounds.width(), bounds.height())); + if (ownerWindow) { + ownerWindow->Invalidate(); } } } diff --git a/atom/browser/native_window.h b/atom/browser/native_window.h index 6f2d525469..aa5e7e0c71 100644 --- a/atom/browser/native_window.h +++ b/atom/browser/native_window.h @@ -124,6 +124,7 @@ class NativeWindow : public base::SupportsUserData, std::string* error = nullptr) = 0; virtual bool IsAlwaysOnTop() = 0; virtual void Center() = 0; + virtual void Invalidate() = 0; virtual void SetTitle(const std::string& title) = 0; virtual std::string GetTitle() = 0; virtual void FlashFrame(bool flash) = 0; diff --git a/atom/browser/native_window_mac.h b/atom/browser/native_window_mac.h index 186051200a..2beb55c029 100644 --- a/atom/browser/native_window_mac.h +++ b/atom/browser/native_window_mac.h @@ -71,6 +71,7 @@ class NativeWindowMac : public NativeWindow, int relativeLevel, std::string* error) override; bool IsAlwaysOnTop() override; void Center() override; + void Invalidate() override; void SetTitle(const std::string& title) override; std::string GetTitle() override; void FlashFrame(bool flash) override; diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index b463d2067c..cef80ceabf 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -1101,6 +1101,11 @@ void NativeWindowMac::Center() { [window_ center]; } +void NativeWindowMac::Invalidate() { + [window_ flushWindow]; + [[window_ contentView] setNeedsDisplay:TRUE]; +} + void NativeWindowMac::SetTitle(const std::string& title) { // For macOS <= 10.9, the setTitleVisibility API is not available, we have // to avoid calling setTitle for frameless window. diff --git a/atom/browser/native_window_views.cc b/atom/browser/native_window_views.cc index 3270add583..64ed0611f3 100644 --- a/atom/browser/native_window_views.cc +++ b/atom/browser/native_window_views.cc @@ -695,6 +695,12 @@ void NativeWindowViews::Center() { window_->CenterWindow(GetSize()); } +void NativeWindowViews::Invalidate() { + const gfx::Rect& bounds = GetBounds(); + window_->SchedulePaintInRect( + gfx::Rect(0, 0, bounds.width(), bounds.height())); +} + void NativeWindowViews::SetTitle(const std::string& title) { title_ = title; window_->UpdateWindowTitle(); diff --git a/atom/browser/native_window_views.h b/atom/browser/native_window_views.h index 7d983b89a5..a7f02fb272 100644 --- a/atom/browser/native_window_views.h +++ b/atom/browser/native_window_views.h @@ -90,6 +90,7 @@ class NativeWindowViews : public NativeWindow, int relativeLevel, std::string* error) override; bool IsAlwaysOnTop() override; void Center() override; + void Invalidate() override; void SetTitle(const std::string& title) override; std::string GetTitle() override; void FlashFrame(bool flash) override; From d56c1909dfdbd3df2f3f0560d154b87fe394692b Mon Sep 17 00:00:00 2001 From: liusi Date: Tue, 14 Feb 2017 14:39:33 +0800 Subject: [PATCH 137/925] only emit `download-item done` event once. --- atom/browser/api/atom_api_download_item.cc | 10 ++++++---- atom/browser/api/atom_api_download_item.h | 1 + 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/atom/browser/api/atom_api_download_item.cc b/atom/browser/api/atom_api_download_item.cc index ccb99b6f60..80e37ba965 100644 --- a/atom/browser/api/atom_api_download_item.cc +++ b/atom/browser/api/atom_api_download_item.cc @@ -58,7 +58,7 @@ std::map> g_download_item_objects; DownloadItem::DownloadItem(v8::Isolate* isolate, content::DownloadItem* download_item) - : download_item_(download_item) { + : download_item_(download_item), done_emitted_(false) { download_item_->AddObserver(this); Init(isolate); AttachAsUserData(download_item); @@ -76,14 +76,16 @@ DownloadItem::~DownloadItem() { } void DownloadItem::OnDownloadUpdated(content::DownloadItem* item) { - if (download_item_->IsDone()) { + if (!download_item_->IsDone()) { + Emit("updated", item->GetState()); + + } else if (!done_emitted_) { Emit("done", item->GetState()); + done_emitted_ = true; // Destroy the item once item is downloaded. base::ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, GetDestroyClosure()); - } else { - Emit("updated", item->GetState()); } } diff --git a/atom/browser/api/atom_api_download_item.h b/atom/browser/api/atom_api_download_item.h index fbc74b1c81..8840a4cff4 100644 --- a/atom/browser/api/atom_api_download_item.h +++ b/atom/browser/api/atom_api_download_item.h @@ -59,6 +59,7 @@ class DownloadItem : public mate::TrackableObject, private: base::FilePath save_path_; content::DownloadItem* download_item_; + bool done_emitted_; DISALLOW_COPY_AND_ASSIGN(DownloadItem); }; From bad695491111b5988d08131828db4847a42300b1 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 14 Feb 2017 17:23:20 +0900 Subject: [PATCH 138/925] Disable the ActiveVerifier --- atom/app/atom_main_delegate.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/atom/app/atom_main_delegate.cc b/atom/app/atom_main_delegate.cc index e4015e57be..1490b89013 100644 --- a/atom/app/atom_main_delegate.cc +++ b/atom/app/atom_main_delegate.cc @@ -102,6 +102,9 @@ bool AtomMainDelegate::BasicStartupComplete(int* exit_code) { #if defined(OS_WIN) // Ignore invalid parameter errors. _set_invalid_parameter_handler(InvalidParameterHandler); + // Disable the ActiveVerifier, which is used by Chrome to track possible + // bugs, but no use in Electron. + base::win::DisableHandleVerifier(); #endif return brightray::MainDelegate::BasicStartupComplete(exit_code); From 1f0bc7fe865f53e05c4885fb1e8152846521cf52 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 10 Feb 2017 09:40:46 -0800 Subject: [PATCH 139/925] Upgrade brigthray for context crash fix --- vendor/brightray | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/brightray b/vendor/brightray index adb27d1c70..eccdb0f1cb 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit adb27d1c703eed84d115906e2e8813dc05185033 +Subproject commit eccdb0f1cb0fe8feaeeaf1b1a1a1cc2c3f3c7126 From 70010fdb8eb483777cdcea74b22d8133e01eb2d6 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 6 Feb 2017 13:54:41 -0800 Subject: [PATCH 140/925] Add spec for Buffer global --- spec/api-browser-window-spec.js | 5 +++-- spec/fixtures/module/preload-node-off.js | 2 +- spec/fixtures/module/preload.js | 2 +- spec/fixtures/module/send-later.js | 2 +- spec/webview-spec.js | 10 +++++----- 5 files changed, 11 insertions(+), 10 deletions(-) diff --git a/spec/api-browser-window-spec.js b/spec/api-browser-window-spec.js index 81d512212c..354befdb3f 100644 --- a/spec/api-browser-window-spec.js +++ b/spec/api-browser-window-spec.js @@ -801,8 +801,9 @@ describe('BrowserWindow module', function () { describe('"node-integration" option', function () { it('disables node integration when specified to false', function (done) { var preload = path.join(fixtures, 'module', 'send-later.js') - ipcMain.once('answer', function (event, test) { - assert.equal(test, 'undefined') + ipcMain.once('answer', function (event, typeofProcess, typeofBuffer) { + assert.equal(typeofProcess, 'undefined') + assert.equal(typeofBuffer, 'undefined') done() }) w.destroy() diff --git a/spec/fixtures/module/preload-node-off.js b/spec/fixtures/module/preload-node-off.js index 54fe343a9c..452ee41b68 100644 --- a/spec/fixtures/module/preload-node-off.js +++ b/spec/fixtures/module/preload-node-off.js @@ -1,6 +1,6 @@ setImmediate(function () { try { - console.log([typeof process, typeof setImmediate, typeof global].join(' ')) + console.log([typeof process, typeof setImmediate, typeof global, typeof Buffer].join(' ')) } catch (e) { console.log(e.message) } diff --git a/spec/fixtures/module/preload.js b/spec/fixtures/module/preload.js index 39c8b11fbe..e9dad8ac99 100644 --- a/spec/fixtures/module/preload.js +++ b/spec/fixtures/module/preload.js @@ -1 +1 @@ -console.log([typeof require, typeof module, typeof process].join(' ')) +console.log([typeof require, typeof module, typeof process, typeof Buffer].join(' ')) diff --git a/spec/fixtures/module/send-later.js b/spec/fixtures/module/send-later.js index 8eb16f72f8..2d3156faaa 100644 --- a/spec/fixtures/module/send-later.js +++ b/spec/fixtures/module/send-later.js @@ -1,4 +1,4 @@ var ipcRenderer = require('electron').ipcRenderer window.onload = function () { - ipcRenderer.send('answer', typeof window.process) + ipcRenderer.send('answer', typeof window.process, typeof window.Buffer) } diff --git a/spec/webview-spec.js b/spec/webview-spec.js index 02207a4b26..39d10f1526 100644 --- a/spec/webview-spec.js +++ b/spec/webview-spec.js @@ -171,7 +171,7 @@ describe(' tag', function () { describe('preload attribute', function () { it('loads the script before other scripts in window', function (done) { var listener = function (e) { - assert.equal(e.message, 'function object object') + assert.equal(e.message, 'function object object function') webview.removeEventListener('console-message', listener) done() } @@ -181,9 +181,9 @@ describe(' tag', function () { document.body.appendChild(webview) }) - it('preload script can still use "process" in required modules when nodeintegration is off', function (done) { + it('preload script can still use "process" and "Buffer" in required modules when nodeintegration is off', function (done) { webview.addEventListener('console-message', function (e) { - assert.equal(e.message, 'object undefined object') + assert.equal(e.message, 'object undefined object function') done() }) webview.setAttribute('preload', fixtures + '/module/preload-node-off.js') @@ -212,7 +212,7 @@ describe(' tag', function () { it('works without script tag in page', function (done) { var listener = function (e) { - assert.equal(e.message, 'function object object') + assert.equal(e.message, 'function object object function') webview.removeEventListener('console-message', listener) done() } @@ -224,7 +224,7 @@ describe(' tag', function () { it('resolves relative URLs', function (done) { var listener = function (e) { - assert.equal(e.message, 'function object object') + assert.equal(e.message, 'function object object function') webview.removeEventListener('console-message', listener) done() } From 7e138083efa10a6a0188bfeceadfb0c58ad3a13a Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 6 Feb 2017 15:08:08 -0800 Subject: [PATCH 141/925] Add declared const Buffer spec --- spec/fixtures/module/declare-buffer.js | 2 ++ spec/modules-spec.js | 6 ++++++ 2 files changed, 8 insertions(+) create mode 100644 spec/fixtures/module/declare-buffer.js diff --git a/spec/fixtures/module/declare-buffer.js b/spec/fixtures/module/declare-buffer.js new file mode 100644 index 0000000000..9a054a24b5 --- /dev/null +++ b/spec/fixtures/module/declare-buffer.js @@ -0,0 +1,2 @@ +const Buffer = 'declared Buffer' +module.exports = Buffer diff --git a/spec/modules-spec.js b/spec/modules-spec.js index b8c02a2e3e..5f82717527 100644 --- a/spec/modules-spec.js +++ b/spec/modules-spec.js @@ -69,6 +69,12 @@ describe('third-party module', function () { assert.strictEqual(require('./fixtures/module/declare-global'), 'declared global') }) }) + + describe('Buffer', function () { + it('can be declared in a module', function () { + assert.strictEqual(require('./fixtures/module/declare-buffer'), 'declared Buffer') + }) + }) }) }) From 1933a4fc9f3e6dc5ec829d1ee36f54a8760252f6 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 6 Feb 2017 15:01:10 -0800 Subject: [PATCH 142/925] Remove global Buffer mention now handled as require wrapper --- docs/tutorial/security.md | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/docs/tutorial/security.md b/docs/tutorial/security.md index c62fc9f387..628edd84ed 100644 --- a/docs/tutorial/security.md +++ b/docs/tutorial/security.md @@ -80,22 +80,3 @@ This is not bulletproof, but at the least, you should attempt the following: Again, this list merely minimizes the risk, it does not remove it. If your goal is to display a website, a browser will be a more secure option. - -## Buffer Global - -Node's [Buffer](https://nodejs.org/api/buffer.html) class is currently available -as a global even when the `nodeintegration` attribute is not added. You can -delete this in your app by doing the following in your `preload` script: - -```js -delete global.Buffer -``` - -Deleting it may break Node modules used in your preload script and app since -many libraries expect it to be a global instead of requiring it directly via: - -```js -const {Buffer} = require('buffer') -``` - -The `Buffer` global may be removed in future major versions of Electron. From 2a18e3f9ea96e8c16c610c6558047b3af1109e92 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 13 Feb 2017 08:48:39 -0800 Subject: [PATCH 143/925] Remove Buffer global on load --- lib/renderer/init.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/renderer/init.js b/lib/renderer/init.js index 02c71b5fa0..d9a05592e9 100644 --- a/lib/renderer/init.js +++ b/lib/renderer/init.js @@ -127,6 +127,7 @@ if (nodeIntegration === 'true') { // Delete Node's symbols after the Environment has been loaded. process.once('loaded', function () { delete global.process + delete global.Buffer delete global.setImmediate delete global.clearImmediate delete global.global From 8e09fcd2a9df604d5907d66f55b361db26af2fdf Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 13 Feb 2017 08:54:00 -0800 Subject: [PATCH 144/925] Upgrade node for require wrapper Buffer change --- vendor/node | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/node b/vendor/node index b64908216a..9eeeb53aa6 160000 --- a/vendor/node +++ b/vendor/node @@ -1 +1 @@ -Subproject commit b64908216a6d5f84862b2b947985a513a6521cb3 +Subproject commit 9eeeb53aa697a54561ffc6c94f06b3ac5963f398 From fdc1d3d7637706e14f7fa41d2f3050a1efce52b5 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 13 Feb 2017 09:33:11 -0800 Subject: [PATCH 145/925] Add Buffer typeof assertion --- spec/webview-spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/webview-spec.js b/spec/webview-spec.js index 39d10f1526..21a5401840 100644 --- a/spec/webview-spec.js +++ b/spec/webview-spec.js @@ -318,7 +318,7 @@ describe(' tag', function () { it('does not break preload script', function (done) { var listener = function (e) { - assert.equal(e.message, 'function object object') + assert.equal(e.message, 'function object object function') webview.removeEventListener('console-message', listener) done() } From c54ae66fb93766bc538b72a3d155fa934226f17f Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 13 Feb 2017 11:24:47 -0800 Subject: [PATCH 146/925] Call OnWindowScrollTouchEnd instead of Edge --- atom/browser/native_window.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index 1ef70f2c73..eae68c37f5 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -531,7 +531,7 @@ void NativeWindow::NotifyWindowScrollTouchBegin() { void NativeWindow::NotifyWindowScrollTouchEnd() { for (NativeWindowObserver& observer : observers_) - observer.OnWindowScrollTouchEdge(); + observer.OnWindowScrollTouchEnd(); } void NativeWindow::NotifyWindowScrollTouchEdge() { From 3e18d8baf564943664951fd385bb23296a302e95 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 2 Feb 2017 13:00:00 -0800 Subject: [PATCH 147/925] Remove _ prefix on binding methods --- atom/common/api/atom_api_crash_reporter.cc | 6 +++--- lib/common/api/crash-reporter.js | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/atom/common/api/atom_api_crash_reporter.cc b/atom/common/api/atom_api_crash_reporter.cc index aaaf200b32..57f880ff19 100644 --- a/atom/common/api/atom_api_crash_reporter.cc +++ b/atom/common/api/atom_api_crash_reporter.cc @@ -38,11 +38,11 @@ void Initialize(v8::Local exports, v8::Local unused, auto report = base::Unretained(CrashReporter::GetInstance()); dict.SetMethod("start", base::Bind(&CrashReporter::Start, report)); - dict.SetMethod("_getUploadedReports", + dict.SetMethod("getUploadedReports", base::Bind(&CrashReporter::GetUploadedReports, report)); - dict.SetMethod("_setUploadToServer", + dict.SetMethod("setUploadToServer", base::Bind(&CrashReporter::SetUploadToServer, report)); - dict.SetMethod("_getUploadToServer", + dict.SetMethod("getUploadToServer", base::Bind(&CrashReporter::GetUploadToServer, report)); } diff --git a/lib/common/api/crash-reporter.js b/lib/common/api/crash-reporter.js index c5a462cf51..3579a8c375 100644 --- a/lib/common/api/crash-reporter.js +++ b/lib/common/api/crash-reporter.js @@ -104,7 +104,7 @@ class CrashReporter { getUploadToServer () { if (process.type === 'browser') { - return binding._getUploadToServer() + return binding.getUploadToServer() } else { throw new Error('getUploadToServer can only be called from the main process') } @@ -112,7 +112,7 @@ class CrashReporter { setUploadToServer (uploadToServer) { if (process.type === 'browser') { - return binding._setUploadToServer(uploadToServer) + return binding.setUploadToServer(uploadToServer) } else { throw new Error('setUploadToServer can only be called from the main process') } From cfe3ae234b2d820b971ec28b9dc299f59bf95cdd Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 2 Feb 2017 13:01:49 -0800 Subject: [PATCH 148/925] Correct , -> . typo --- docs/api/crash-reporter.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api/crash-reporter.md b/docs/api/crash-reporter.md index 3e93de4a25..164ee7d749 100644 --- a/docs/api/crash-reporter.md +++ b/docs/api/crash-reporter.md @@ -44,14 +44,14 @@ The `crashReporter` module has the following methods: Default is `true`. * `ignoreSystemCrashHandler` Boolean (optional) - Default is `false`. * `extra` Object (optional) - An object you can define that will be sent along with the - report. Only string properties are sent correctly, Nested objects are not + report. Only string properties are sent correctly. Nested objects are not supported. You are required to call this method before using any other `crashReporter` APIs and in each process (main/renderer) from which you want to collect crash reports. You can pass different options to `crashReporter.start` when calling from different processes. -**Note** Child processes created via the `child_process` module will not have access to the Electron modules. +**Note** Child processes created via the `child_process` module will not have access to the Electron modules. Therefore, to collect crash reports from them, use `process.crashReporter.start` instead. Pass the same options as above along with an additional one called `crashesDirectory` that should point to a directory to store the crash reports temporarily. You can test this out by calling `process.crash()` to crash the child process. From ba975d552a8adbf1699d38fc28e8659218fb6faf Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 2 Feb 2017 14:23:21 -0800 Subject: [PATCH 149/925] Add setExtraParameter support on macOS --- atom/common/api/atom_api_crash_reporter.cc | 13 +++++++------ atom/common/crash_reporter/crash_reporter.cc | 4 ++++ atom/common/crash_reporter/crash_reporter.h | 2 ++ atom/common/crash_reporter/crash_reporter_mac.h | 2 ++ atom/common/crash_reporter/crash_reporter_mac.mm | 8 ++++++++ lib/common/api/crash-reporter.js | 4 ++++ 6 files changed, 27 insertions(+), 6 deletions(-) diff --git a/atom/common/api/atom_api_crash_reporter.cc b/atom/common/api/atom_api_crash_reporter.cc index 57f880ff19..6820e7a792 100644 --- a/atom/common/api/atom_api_crash_reporter.cc +++ b/atom/common/api/atom_api_crash_reporter.cc @@ -35,15 +35,16 @@ namespace { void Initialize(v8::Local exports, v8::Local unused, v8::Local context, void* priv) { mate::Dictionary dict(context->GetIsolate(), exports); - auto report = base::Unretained(CrashReporter::GetInstance()); - dict.SetMethod("start", - base::Bind(&CrashReporter::Start, report)); + auto reporter = base::Unretained(CrashReporter::GetInstance()); + dict.SetMethod("start", base::Bind(&CrashReporter::Start, reporter)); + dict.SetMethod("setExtraParameter", + base::Bind(&CrashReporter::SetExtraParameter, reporter)); dict.SetMethod("getUploadedReports", - base::Bind(&CrashReporter::GetUploadedReports, report)); + base::Bind(&CrashReporter::GetUploadedReports, reporter)); dict.SetMethod("setUploadToServer", - base::Bind(&CrashReporter::SetUploadToServer, report)); + base::Bind(&CrashReporter::SetUploadToServer, reporter)); dict.SetMethod("getUploadToServer", - base::Bind(&CrashReporter::GetUploadToServer, report)); + base::Bind(&CrashReporter::GetUploadToServer, reporter)); } } // namespace diff --git a/atom/common/crash_reporter/crash_reporter.cc b/atom/common/crash_reporter/crash_reporter.cc index f8a5f5e29e..bc3176275f 100644 --- a/atom/common/crash_reporter/crash_reporter.cc +++ b/atom/common/crash_reporter/crash_reporter.cc @@ -86,6 +86,10 @@ void CrashReporter::InitBreakpad(const std::string& product_name, void CrashReporter::SetUploadParameters() { } +void CrashReporter::SetExtraParameter(const std::string& key, + const std::string& value) { +} + #if defined(OS_MACOSX) && defined(MAS_BUILD) // static CrashReporter* CrashReporter::GetInstance() { diff --git a/atom/common/crash_reporter/crash_reporter.h b/atom/common/crash_reporter/crash_reporter.h index c564527109..cd93f171c0 100644 --- a/atom/common/crash_reporter/crash_reporter.h +++ b/atom/common/crash_reporter/crash_reporter.h @@ -37,6 +37,8 @@ class CrashReporter { virtual void SetUploadToServer(bool upload_to_server); virtual bool GetUploadToServer(); + virtual void SetExtraParameter(const std::string& key, + const std::string& value); protected: CrashReporter(); diff --git a/atom/common/crash_reporter/crash_reporter_mac.h b/atom/common/crash_reporter/crash_reporter_mac.h index 9066f2017b..8d1b1d0de1 100644 --- a/atom/common/crash_reporter/crash_reporter_mac.h +++ b/atom/common/crash_reporter/crash_reporter_mac.h @@ -34,6 +34,8 @@ class CrashReporterMac : public CrashReporter { void SetUploadParameters() override; void SetUploadToServer(bool upload_to_server) override; bool GetUploadToServer() override; + void SetExtraParameter(const std::string& key, + const std::string& value) override; private: friend struct base::DefaultSingletonTraits; diff --git a/atom/common/crash_reporter/crash_reporter_mac.mm b/atom/common/crash_reporter/crash_reporter_mac.mm index cdaa5d2f52..0e9b20d6f6 100644 --- a/atom/common/crash_reporter/crash_reporter_mac.mm +++ b/atom/common/crash_reporter/crash_reporter_mac.mm @@ -100,6 +100,14 @@ void CrashReporterMac::SetCrashKeyValue(const base::StringPiece& key, simple_string_dictionary_->SetKeyValue(key.data(), value.data()); } +void CrashReporterMac::SetExtraParameter(const std::string& key, + const std::string& value) { + if (simple_string_dictionary_) + SetCrashKeyValue(key, value); + else + upload_parameters_[key] = value; +} + std::vector CrashReporterMac::GetUploadedReports(const base::FilePath& crashes_dir) { std::vector uploaded_reports; diff --git a/lib/common/api/crash-reporter.js b/lib/common/api/crash-reporter.js index 3579a8c375..f168251cc1 100644 --- a/lib/common/api/crash-reporter.js +++ b/lib/common/api/crash-reporter.js @@ -117,6 +117,10 @@ class CrashReporter { throw new Error('setUploadToServer can only be called from the main process') } } + + setExtraParameter (key, value) { + binding.setExtraParameter(key, value) + } } module.exports = new CrashReporter() From 9a434af03e34d2aa325f93af6a0e702235dabaa4 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 6 Feb 2017 16:31:20 -0800 Subject: [PATCH 150/925] Clear crash keys in InitBreakpad --- atom/common/crash_reporter/crash_reporter_linux.cc | 10 ++++++---- atom/common/crash_reporter/crash_reporter_linux.h | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/atom/common/crash_reporter/crash_reporter_linux.cc b/atom/common/crash_reporter/crash_reporter_linux.cc index 50e1c5ec86..9020977716 100644 --- a/atom/common/crash_reporter/crash_reporter_linux.cc +++ b/atom/common/crash_reporter/crash_reporter_linux.cc @@ -64,13 +64,15 @@ void CrashReporterLinux::InitBreakpad(const std::string& product_name, bool skip_system_crash_handler) { EnableCrashDumping(crashes_dir); - crash_keys_.SetKeyValue("prod", ATOM_PRODUCT_NAME); - crash_keys_.SetKeyValue("ver", version.c_str()); + crash_keys_.reset(new CrashKeyStorage()); + + crash_keys_->SetKeyValue("prod", ATOM_PRODUCT_NAME); + crash_keys_->SetKeyValue("ver", version.c_str()); upload_url_ = submit_url; for (StringMap::const_iterator iter = upload_parameters_.begin(); iter != upload_parameters_.end(); ++iter) - crash_keys_.SetKeyValue(iter->first.c_str(), iter->second.c_str()); + crash_keys_->SetKeyValue(iter->first.c_str(), iter->second.c_str()); } void CrashReporterLinux::SetUploadParameters() { @@ -120,7 +122,7 @@ bool CrashReporterLinux::CrashDone(const MinidumpDescriptor& minidump, info.oom_size = base::g_oom_size; info.pid = self->pid_; info.upload_url = self->upload_url_.c_str(); - info.crash_keys = &self->crash_keys_; + info.crash_keys = self->crash_keys_.get(); HandleCrashDump(info); return true; } diff --git a/atom/common/crash_reporter/crash_reporter_linux.h b/atom/common/crash_reporter/crash_reporter_linux.h index 846e1f1b0a..997caf1c27 100644 --- a/atom/common/crash_reporter/crash_reporter_linux.h +++ b/atom/common/crash_reporter/crash_reporter_linux.h @@ -49,7 +49,7 @@ class CrashReporterLinux : public CrashReporter { const bool succeeded); std::unique_ptr breakpad_; - CrashKeyStorage crash_keys_; + std::unique_ptr crash_keys_; uint64_t process_start_time_; pid_t pid_; From 41fef3999c276b640d3e67a1fad1acad9ba2bcad Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 9 Feb 2017 12:47:02 -0800 Subject: [PATCH 151/925] Document setExtraParameter --- docs/api/crash-reporter.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/api/crash-reporter.md b/docs/api/crash-reporter.md index 164ee7d749..096276a3ff 100644 --- a/docs/api/crash-reporter.md +++ b/docs/api/crash-reporter.md @@ -60,6 +60,10 @@ reports temporarily. You can test this out by calling `process.crash()` to crash This will start the process that will monitor and send the crash reports. Replace `submitURL`, `productName` and `crashesDirectory` with appropriate values. +**Note:** If you need send additional/updated `extra` parameters after your +first call `start` you can call `setExtraParameter` on macOS or call `start` +again with the new/updated `extra` parameters on Linux and Windows. + ```js const args = [ `--reporter-url=${submitURL}`, @@ -111,6 +115,18 @@ called before `start` is called. **Note:** This API can only be called from the main process. +### `crashReporter.setExtraParameter(key, value)` _macOS_ + +* `key` String - Parameter key. +* `value` String - Parameter value. + +Set an extra data to set be sent with the crash report. The values specified +here will be sent in addition to any values set via the `extra` option to +the `start` API. This API is only available on macOS, if you need to +add additional extra parameters on Linux and Windows after your first call to +`start` you can call `start` again with the updated `extra` options for the +parameters to send. + ## Crash Report Payload The crash reporter will send the following data to the `submitURL` as From 3b7207da9fb47a638561920ed30fd73288398def Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 9 Feb 2017 13:05:23 -0800 Subject: [PATCH 152/925] Support removing extra parameters --- atom/common/api/atom_api_crash_reporter.cc | 11 +++++++++-- atom/common/crash_reporter/crash_reporter.cc | 3 +++ atom/common/crash_reporter/crash_reporter.h | 1 + atom/common/crash_reporter/crash_reporter_mac.h | 1 + atom/common/crash_reporter/crash_reporter_mac.mm | 7 +++++++ docs/api/crash-reporter.md | 3 ++- 6 files changed, 23 insertions(+), 3 deletions(-) diff --git a/atom/common/api/atom_api_crash_reporter.cc b/atom/common/api/atom_api_crash_reporter.cc index 6820e7a792..0edd787e55 100644 --- a/atom/common/api/atom_api_crash_reporter.cc +++ b/atom/common/api/atom_api_crash_reporter.cc @@ -31,14 +31,21 @@ struct Converter { namespace { +void SetExtraParameter(const std::string& key, mate::Arguments* args) { + std::string value; + if (args->GetNext(&value)) + CrashReporter::GetInstance()->SetExtraParameter(key, value); + else + CrashReporter::GetInstance()->RemoveExtraParameter(key); +} + void Initialize(v8::Local exports, v8::Local unused, v8::Local context, void* priv) { mate::Dictionary dict(context->GetIsolate(), exports); auto reporter = base::Unretained(CrashReporter::GetInstance()); dict.SetMethod("start", base::Bind(&CrashReporter::Start, reporter)); - dict.SetMethod("setExtraParameter", - base::Bind(&CrashReporter::SetExtraParameter, reporter)); + dict.SetMethod("setExtraParameter", &SetExtraParameter); dict.SetMethod("getUploadedReports", base::Bind(&CrashReporter::GetUploadedReports, reporter)); dict.SetMethod("setUploadToServer", diff --git a/atom/common/crash_reporter/crash_reporter.cc b/atom/common/crash_reporter/crash_reporter.cc index bc3176275f..d901f83fa4 100644 --- a/atom/common/crash_reporter/crash_reporter.cc +++ b/atom/common/crash_reporter/crash_reporter.cc @@ -90,6 +90,9 @@ void CrashReporter::SetExtraParameter(const std::string& key, const std::string& value) { } +void CrashReporter::RemoveExtraParameter(const std::string& key) { +} + #if defined(OS_MACOSX) && defined(MAS_BUILD) // static CrashReporter* CrashReporter::GetInstance() { diff --git a/atom/common/crash_reporter/crash_reporter.h b/atom/common/crash_reporter/crash_reporter.h index cd93f171c0..2bdcd9b02d 100644 --- a/atom/common/crash_reporter/crash_reporter.h +++ b/atom/common/crash_reporter/crash_reporter.h @@ -39,6 +39,7 @@ class CrashReporter { virtual bool GetUploadToServer(); virtual void SetExtraParameter(const std::string& key, const std::string& value); + virtual void RemoveExtraParameter(const std::string& key); protected: CrashReporter(); diff --git a/atom/common/crash_reporter/crash_reporter_mac.h b/atom/common/crash_reporter/crash_reporter_mac.h index 8d1b1d0de1..583c99a86e 100644 --- a/atom/common/crash_reporter/crash_reporter_mac.h +++ b/atom/common/crash_reporter/crash_reporter_mac.h @@ -36,6 +36,7 @@ class CrashReporterMac : public CrashReporter { bool GetUploadToServer() override; void SetExtraParameter(const std::string& key, const std::string& value) override; + void RemoveExtraParameter(const std::string& key) override; private: friend struct base::DefaultSingletonTraits; diff --git a/atom/common/crash_reporter/crash_reporter_mac.mm b/atom/common/crash_reporter/crash_reporter_mac.mm index 0e9b20d6f6..4b59be5dfc 100644 --- a/atom/common/crash_reporter/crash_reporter_mac.mm +++ b/atom/common/crash_reporter/crash_reporter_mac.mm @@ -108,6 +108,13 @@ void CrashReporterMac::SetExtraParameter(const std::string& key, upload_parameters_[key] = value; } +void CrashReporterMac::RemoveExtraParameter(const std::string& key) { + if (simple_string_dictionary_) + simple_string_dictionary_->RemoveKey(key.data()); + else + upload_parameters_.erase(key); +} + std::vector CrashReporterMac::GetUploadedReports(const base::FilePath& crashes_dir) { std::vector uploaded_reports; diff --git a/docs/api/crash-reporter.md b/docs/api/crash-reporter.md index 096276a3ff..f4ed2d2ce0 100644 --- a/docs/api/crash-reporter.md +++ b/docs/api/crash-reporter.md @@ -118,7 +118,8 @@ called before `start` is called. ### `crashReporter.setExtraParameter(key, value)` _macOS_ * `key` String - Parameter key. -* `value` String - Parameter value. +* `value` String - Parameter value. Specifying `null` or `undefined` will + remove the key from the extra parameters. Set an extra data to set be sent with the crash report. The values specified here will be sent in addition to any values set via the `extra` option to From f3756ccf2962c243fbe67f20c4a642fc2bf7f41f Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 13 Feb 2017 10:07:10 -0800 Subject: [PATCH 153/925] Run crash reporter specs on all platforms --- spec/api-crash-reporter-spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/api-crash-reporter-spec.js b/spec/api-crash-reporter-spec.js index 0871670de3..876e2f0996 100644 --- a/spec/api-crash-reporter-spec.js +++ b/spec/api-crash-reporter-spec.js @@ -35,7 +35,7 @@ describe('crashReporter module', function () { } it('should send minidump when renderer crashes', function (done) { - if (process.platform !== 'darwin') return done() + if (process.env.APPVEYOR === 'True') return done() if (process.env.TRAVIS === 'true') return done() this.timeout(120000) @@ -55,7 +55,7 @@ describe('crashReporter module', function () { }) it('should send minidump when node processes crash', function (done) { - if (process.platform !== 'darwin') return done() + if (process.env.APPVEYOR === 'True') return done() if (process.env.TRAVIS === 'true') return done() this.timeout(120000) From a4bbad03c26d90c369163e5939ab9ea9b6a9ca90 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 13 Feb 2017 10:08:43 -0800 Subject: [PATCH 154/925] autoSubmit -> uploadToServer --- spec/api-crash-reporter-spec.js | 2 +- spec/fixtures/api/crash.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/api-crash-reporter-spec.js b/spec/api-crash-reporter-spec.js index 876e2f0996..8e9ba64673 100644 --- a/spec/api-crash-reporter-spec.js +++ b/spec/api-crash-reporter-spec.js @@ -111,7 +111,7 @@ describe('crashReporter module', function () { crashReporter.start({ companyName: 'Umbrella Corporation', submitURL: 'http://127.0.0.1/crashes', - autoSubmit: true + uploadToServer: true }) assert.equal(crashReporter.getUploadToServer(), true) crashReporter.setUploadToServer(false) diff --git a/spec/fixtures/api/crash.html b/spec/fixtures/api/crash.html index c1fb621426..2b1cea6e9a 100644 --- a/spec/fixtures/api/crash.html +++ b/spec/fixtures/api/crash.html @@ -7,7 +7,7 @@ crashReporter.start({ productName: 'Zombies', companyName: 'Umbrella Corporation', submitURL: 'http://127.0.0.1:' + port, - autoSubmit: true, + uploadToServer: true, ignoreSystemCrashHandler: true, extra: { 'extra1': 'extra1', From b51adf8ae54f7951f1e5d71d889b0a0df65b9a9d Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 13 Feb 2017 10:46:19 -0800 Subject: [PATCH 155/925] Add spec for updated extra parameters --- lib/common/api/crash-reporter.js | 2 +- spec/api-crash-reporter-spec.js | 22 +++++++++++++ spec/fixtures/api/crash-restart.html | 46 ++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 spec/fixtures/api/crash-restart.html diff --git a/lib/common/api/crash-reporter.js b/lib/common/api/crash-reporter.js index f168251cc1..658622e8f5 100644 --- a/lib/common/api/crash-reporter.js +++ b/lib/common/api/crash-reporter.js @@ -75,7 +75,7 @@ class CrashReporter { } getUploadedReports () { - return binding._getUploadedReports(this.getCrashesDirectory()) + return binding.getUploadedReports(this.getCrashesDirectory()) } getCrashesDirectory () { diff --git a/spec/api-crash-reporter-spec.js b/spec/api-crash-reporter-spec.js index 8e9ba64673..c269cfe132 100644 --- a/spec/api-crash-reporter-spec.js +++ b/spec/api-crash-reporter-spec.js @@ -72,6 +72,26 @@ describe('crashReporter module', function () { }) }) + it('should send minidump with updated extra parameters', function (done) { + if (process.env.APPVEYOR === 'True') return done() + if (process.env.TRAVIS === 'true') return done() + + this.timeout(10000) + + startServer({ + callback (port) { + const crashUrl = url.format({ + protocol: 'file', + pathname: path.join(fixtures, 'api', 'crash-restart.html'), + search: '?port=' + port + }) + w.loadURL(crashUrl) + }, + processType: 'renderer', + done: done + }) + }) + describe('.start(options)', function () { it('requires that the companyName and submitURL options be specified', function () { assert.throws(function () { @@ -155,6 +175,7 @@ const startServer = ({callback, processType, done}) => { assert.equal(fields.platform, process.platform) assert.equal(fields.extra1, 'extra1') assert.equal(fields.extra2, 'extra2') + assert.equal(fields.extra3, undefined) assert.equal(fields._productName, 'Zombies') assert.equal(fields._companyName, 'Umbrella Corporation') assert.equal(fields._version, app.getVersion()) @@ -165,6 +186,7 @@ const startServer = ({callback, processType, done}) => { assert.equal(crashReporter.getLastCrashReport().id, reportId) assert.notEqual(crashReporter.getUploadedReports().length, 0) assert.equal(crashReporter.getUploadedReports()[0].id, reportId) + req.socket.destroy() done() }, done) }) diff --git a/spec/fixtures/api/crash-restart.html b/spec/fixtures/api/crash-restart.html new file mode 100644 index 0000000000..1877596d1c --- /dev/null +++ b/spec/fixtures/api/crash-restart.html @@ -0,0 +1,46 @@ + + + + + From 07173cdd9dbb4877075386427df38d6db94f1353 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 13 Feb 2017 10:48:04 -0800 Subject: [PATCH 156/925] Tweak setExtraParameter description --- docs/api/crash-reporter.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/api/crash-reporter.md b/docs/api/crash-reporter.md index f4ed2d2ce0..818aa8e7b5 100644 --- a/docs/api/crash-reporter.md +++ b/docs/api/crash-reporter.md @@ -121,12 +121,11 @@ called before `start` is called. * `value` String - Parameter value. Specifying `null` or `undefined` will remove the key from the extra parameters. -Set an extra data to set be sent with the crash report. The values specified -here will be sent in addition to any values set via the `extra` option to -the `start` API. This API is only available on macOS, if you need to -add additional extra parameters on Linux and Windows after your first call to -`start` you can call `start` again with the updated `extra` options for the -parameters to send. +Set an extra parameter to set be sent with the crash report. The values +specified here will be sent in addition to any values set via the `extra` option +when `start` was called. This API is only available on macOS, if you need to +add/update extra parameters on Linux and Windows after your first call to +`start` you can call `start` again with the updated `extra` options. ## Crash Report Payload From af7702d0f2f3d76c3e350203b6f31db27931e8e1 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 13 Feb 2017 10:52:40 -0800 Subject: [PATCH 157/925] :art: --- spec/fixtures/api/crash-restart.html | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/fixtures/api/crash-restart.html b/spec/fixtures/api/crash-restart.html index 1877596d1c..4a96baeaed 100644 --- a/spec/fixtures/api/crash-restart.html +++ b/spec/fixtures/api/crash-restart.html @@ -12,9 +12,9 @@ crashReporter.start({ uploadToServer: true, ignoreSystemCrashHandler: true, extra: { - 'extra1': 'extra1', - 'extra2':'initial', - 'extra3': 'extra3' + extra1: 'extra1', + extra2: 'initial', + extra3: 'extra3' } }) @@ -30,8 +30,8 @@ setImmediate(() => { uploadToServer: true, ignoreSystemCrashHandler: true, extra: { - 'extra1': 'extra1', - 'extra2': 'extra2' + extra1: 'extra1', + extra2: 'extra2' } }) } From 272a0824e87236b7c63e4a11722f780814e4daf8 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 13 Feb 2017 11:15:40 -0800 Subject: [PATCH 158/925] Fix darwin platform name --- spec/fixtures/api/crash-restart.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/fixtures/api/crash-restart.html b/spec/fixtures/api/crash-restart.html index 4a96baeaed..2f55c539bb 100644 --- a/spec/fixtures/api/crash-restart.html +++ b/spec/fixtures/api/crash-restart.html @@ -19,7 +19,7 @@ crashReporter.start({ }) setImmediate(() => { - if (process.platform === 'darwin2') { + if (process.platform === 'darwin') { crashReporter.setExtraParameter('extra2', 'extra2') crashReporter.setExtraParameter('extra3', null) } else { From 82f452568cfd7f66df210079b4686a8a1382d5dc Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 14 Feb 2017 11:09:15 -0800 Subject: [PATCH 159/925] :art: Minor formatting tweaks --- atom/browser/api/atom_api_web_contents.cc | 3 +-- atom/browser/native_window_mac.mm | 2 +- atom/browser/native_window_views.cc | 3 +-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index c90437372d..bd57bc217f 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -1496,9 +1496,8 @@ void WebContents::Invalidate() { osr_rwhv->Invalidate(); } else { const auto ownerWindow = owner_window(); - if (ownerWindow) { + if (ownerWindow) ownerWindow->Invalidate(); - } } } diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index cef80ceabf..8b1eff27c4 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -1103,7 +1103,7 @@ void NativeWindowMac::Center() { void NativeWindowMac::Invalidate() { [window_ flushWindow]; - [[window_ contentView] setNeedsDisplay:TRUE]; + [[window_ contentView] setNeedsDisplay:YES]; } void NativeWindowMac::SetTitle(const std::string& title) { diff --git a/atom/browser/native_window_views.cc b/atom/browser/native_window_views.cc index 64ed0611f3..4b64367bdf 100644 --- a/atom/browser/native_window_views.cc +++ b/atom/browser/native_window_views.cc @@ -697,8 +697,7 @@ void NativeWindowViews::Center() { void NativeWindowViews::Invalidate() { const gfx::Rect& bounds = GetBounds(); - window_->SchedulePaintInRect( - gfx::Rect(0, 0, bounds.width(), bounds.height())); + window_->SchedulePaintInRect(gfx::Rect(GetBounds().size())); } void NativeWindowViews::SetTitle(const std::string& title) { From 5cb664868636e95f6acf4689cac4203f00dc3667 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 14 Feb 2017 11:12:30 -0800 Subject: [PATCH 160/925] Use underscore variable name for consistency --- atom/browser/api/atom_api_web_contents.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index bd57bc217f..5834e4c7b3 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -1495,9 +1495,9 @@ void WebContents::Invalidate() { if (osr_rwhv) osr_rwhv->Invalidate(); } else { - const auto ownerWindow = owner_window(); - if (ownerWindow) - ownerWindow->Invalidate(); + const auto owner_window = owner_window(); + if (owner_window) + owner_window->Invalidate(); } } From 643cb5d6def5ef7e2864424bbb806696b36a5f79 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 14 Feb 2017 11:13:23 -0800 Subject: [PATCH 161/925] Remove unused variable --- atom/browser/native_window_views.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/atom/browser/native_window_views.cc b/atom/browser/native_window_views.cc index 4b64367bdf..771caf583d 100644 --- a/atom/browser/native_window_views.cc +++ b/atom/browser/native_window_views.cc @@ -696,7 +696,6 @@ void NativeWindowViews::Center() { } void NativeWindowViews::Invalidate() { - const gfx::Rect& bounds = GetBounds(); window_->SchedulePaintInRect(gfx::Rect(GetBounds().size())); } From 52d989f9081cd72e6f815a26551be79a81e15537 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 14 Feb 2017 11:17:39 -0800 Subject: [PATCH 162/925] Update docs for non-offscreen invalidate --- docs/api/web-contents.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index 38e5808594..7c636aee39 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -1210,6 +1210,8 @@ Returns `Integer` - If *offscreen rendering* is enabled returns the current fram #### `contents.invalidate()` +Schedules a full repaint of the window this web contents is in. + If *offscreen rendering* is enabled invalidates the frame and generates a new one through the `'paint'` event. From 36e6501c447469813f5625c9c8500be442b4117b Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 14 Feb 2017 12:30:23 -0800 Subject: [PATCH 163/925] Rename variable for compiler error --- atom/browser/api/atom_api_web_contents.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index afd7cda4aa..3fc22438ab 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -1506,9 +1506,9 @@ void WebContents::Invalidate() { if (osr_rwhv) osr_rwhv->Invalidate(); } else { - const auto owner_window = owner_window(); - if (owner_window) - owner_window->Invalidate(); + const auto window = owner_window(); + if (window) + window->Invalidate(); } } From b3ab426a5ed5513293ed546980c5ea0eba4aab39 Mon Sep 17 00:00:00 2001 From: Jonas Zhang <106856363@qq.com> Date: Wed, 15 Feb 2017 14:20:48 +0800 Subject: [PATCH 164/925] Optimize zh-CN language logic and grammar Optimize zh-CN language logic and grammar. --- .../zh-CN/tutorial/supported-platforms.md | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/docs-translations/zh-CN/tutorial/supported-platforms.md b/docs-translations/zh-CN/tutorial/supported-platforms.md index 42c180dbaf..27e8976aa0 100644 --- a/docs-translations/zh-CN/tutorial/supported-platforms.md +++ b/docs-translations/zh-CN/tutorial/supported-platforms.md @@ -1,26 +1,24 @@ # 支持的平台 -以下的平台是 Electron 目前支持的: +目前 Electron 支持以下平台: ### macOS -对于 macOS 系统仅有64位的二进制文档,支持的最低版本是 macOS 10.9。 +对于 macOS 仅提供64位版本,并且只支持 macOS 10.9 或更高版本。 ### Windows -仅支持 Windows 7 及其以后的版本,之前的版本中是不能工作的。 +仅支持 Windows 7 或更高版本。 -对于 Windows 提供 `ia32` (x86) 和 `amd64` (x64) 版本的二进制文件。需要注意的是 `ARM` 版本的 Windows 目前尚不支持。 +对于 Windows 提供 `ia32` (x86) 和 `amd64` (x64) 版本。需要注意的是 `ARM` 版本的 Windows 目前尚不支持。 ### Linux -预编译的 `ia32` (`i686`) 和 `x64` (`amd64`) 版本 Electron 二进制文件都是在 -Ubuntu 12.04 下编译的,`arm` 版的二进制文件是在 ARM v7(硬浮点 ABI 与 +Electron 的 `ia32` (`i686`) 和 `x64` (`amd64`) 预编译版本均是在Ubuntu 12.04 下编译的,`arm` 版的二进制文件是在 ARM v7(硬浮点 ABI 与 Debian Wheezy 版本的 NEON)下完成的。 -预编译二进制文件是否能够运行,取决于其中是否包括了编译平台链接的库,所以只有 Ubuntu 12.04 -可以保证正常工作,但是以下的平台也被证实可以运行 Electron 的预编译版本: +预编译版本是否能够正常运行,取决于其中是否包含了编译平台的链接库。所以只有 Ubuntu 12.04 是可以保证能正常运行的,并且以下平台也被证实可以正常运行 Electron 的预编译版本: -* Ubuntu 12.04 及更新 +* Ubuntu 12.04 或更高版本 * Fedora 21 * Debian 8 From f79ce38a5727b89625020d054db1265ef0a87eca Mon Sep 17 00:00:00 2001 From: DemoPark <106856363@qq.com> Date: Wed, 15 Feb 2017 17:21:20 +0800 Subject: [PATCH 165/925] add zh-CN translations for electron-versioning.md add zh-CN translations for 'electron-versioning.md' --- docs-translations/zh-CN/README.md | 2 +- .../zh-CN/tutorial/electron-versioning.md | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 docs-translations/zh-CN/tutorial/electron-versioning.md diff --git a/docs-translations/zh-CN/README.md b/docs-translations/zh-CN/README.md index 64c89cdcc5..b5bd420c58 100644 --- a/docs-translations/zh-CN/README.md +++ b/docs-translations/zh-CN/README.md @@ -13,7 +13,7 @@ * [术语表](glossary.md) * [支持平台](tutorial/supported-platforms.md) * [安全性](tutorial/security.md) 未翻译 -* [Electron 版本管理](tutorial/electron-versioning.md) 未翻译 +* [Electron 版本管理](tutorial/electron-versioning.md) * [分发应用](tutorial/application-distribution.md) * [提交应用到 Mac App Store](tutorial/mac-app-store-submission-guide.md) * [Windows 商店提交指引](tutorial/windows-store-guide.md) diff --git a/docs-translations/zh-CN/tutorial/electron-versioning.md b/docs-translations/zh-CN/tutorial/electron-versioning.md new file mode 100644 index 0000000000..18bd373540 --- /dev/null +++ b/docs-translations/zh-CN/tutorial/electron-versioning.md @@ -0,0 +1,11 @@ +# Electron 版本管理 + +如果你是一个经验丰富的Node开发人员,你肯定知道`semver` - 然而这里给你的依赖管理系统可能只有粗略的指导建议而不是固定的版本号。由于对 Node 和 Chromium 的硬性依赖,Electron 处于一个稍微复杂的境地,而且不遵循semver。因此,您应该始终引用特定版本的Electron。 + +版本号使用参照以下规则: + +* 主要版本: 适用于 Electron API 的突破性变更 - 如果您从 `0.37.0` 升级到 `1.0.0`, 您将需要升级您的应用程序。 +* 次要版本: 适用于 Chrome 主要版本 和 Node 次要版本升级; 或重大的 Electron 变动 - 如果您从 `0.37.0` 升级到 `1.0.0`, 您的应用程序仍然可以正常运行, 但你可能需要解决一些小幅的变动。 +* 补丁版本: 适用于新功能的添加和 bug 修复 - 如果您从 `0.37.0` 升级到 `1.0.0`, 你的应用程序仍然像之前一样正常运行。 + +如果你使用 `electron` 或 `electron-prebuilt`,我们建议您设置固定的版本号(如 1.1.0 而不是 ^1.1.0),以确保Electron的所有升级都是由您(开发人员)进行的手动操作。 From 1170ecb2695801e82567e58c97a000d11d78e5fd Mon Sep 17 00:00:00 2001 From: DemoPark <106856363@qq.com> Date: Wed, 15 Feb 2017 17:45:52 +0800 Subject: [PATCH 166/925] add zh-CN translations for offscreen-rendering.md add zh-CN translations for offscreen-rendering.md --- docs-translations/zh-CN/README.md | 2 +- .../zh-CN/tutorial/offscreen-rendering.md | 44 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 docs-translations/zh-CN/tutorial/offscreen-rendering.md diff --git a/docs-translations/zh-CN/README.md b/docs-translations/zh-CN/README.md index b5bd420c58..04167acf62 100644 --- a/docs-translations/zh-CN/README.md +++ b/docs-translations/zh-CN/README.md @@ -25,7 +25,7 @@ * [使用 Pepper Flash 插件](tutorial/using-pepper-flash-plugin.md) * [使用 Widevine CDM 插件](tutorial/using-widevine-cdm-plugin.md) * [通过自动化持续集成系统(CI)进行测试 (Travis, Jenkins)](tutorial/testing-on-headless-ci.md) 未翻译 -* [离屏渲染](tutorial/offscreen-rendering.md) 未翻译 +* [离屏渲染](tutorial/offscreen-rendering.md) ## 教程 diff --git a/docs-translations/zh-CN/tutorial/offscreen-rendering.md b/docs-translations/zh-CN/tutorial/offscreen-rendering.md new file mode 100644 index 0000000000..bff3f939fb --- /dev/null +++ b/docs-translations/zh-CN/tutorial/offscreen-rendering.md @@ -0,0 +1,44 @@ +# 离屏渲染 + +离线渲染允许您在位图中获取浏览器窗口的内容,因此可以在任何地方渲染,例如在3D场景中的纹理。Electron中的离屏渲染使用与 [Chromium +Embedded Framework](https://bitbucket.org/chromiumembedded/cef) 项目类似的方法。 + +可以使用两种渲染模式,并且只有脏区通过 `'paint'` 事件才能更高效。渲染可以停止、继续,并且可以设置帧速率。 指定的帧速率是上限值,当网页上没有发生任何事件时,不会生成任何帧。 最大帧速率是60,因为再高没有好处,而且损失性能。 + +**注意:** 屏幕窗口始终创建为 [Frameless Window](../api/frameless-window.md). + +## 两种渲染模式 + +### GPU加速 + +GPU加速渲染意味着使用GPU用于合成。因为帧必须从需要更多性能的GPU中复制,因此这种模式比另一个模式慢得多。这种模式的优点是支持WebGL和3D CSS动画。 + +### 软件输出设备 + +此模式使用软件输出设备在CPU中渲染,因此帧生成速度更快,因此此模式优先于GPU加速模式。 + +要启用此模式,必须通过调用 [`app.disableHardwareAcceleration()`][disablehardwareacceleration] API 来禁用GPU加速。 + +## 使用 + +``` javascript +const {app, BrowserWindow} = require('electron') + +app.disableHardwareAcceleration() + +let win +app.once('ready', () => { + win = new BrowserWindow({ + webPreferences: { + offscreen: true + } + }) + win.loadURL('http://github.com') + win.webContents.on('paint', (event, dirty, image) => { + // updateBitmap(dirty, image.getBitmap()) + }) + win.webContents.setFrameRate(30) +}) +``` + +[disablehardwareacceleration]: ../api/app.md#appdisablehardwareacceleration From a0fc5ae63db713d0fbe55b97ad66cf3e3ef055b1 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 14 Feb 2017 10:11:17 -0800 Subject: [PATCH 167/925] Use electron version in Chromedriver asset name --- script/create-dist.py | 5 ++--- script/lib/config.py | 3 --- script/upload.py | 6 +++--- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/script/create-dist.py b/script/create-dist.py index 4aa67eacd6..5a63b20707 100755 --- a/script/create-dist.py +++ b/script/create-dist.py @@ -9,8 +9,7 @@ import sys import stat from lib.config import LIBCHROMIUMCONTENT_COMMIT, BASE_URL, PLATFORM, \ - get_target_arch, get_chromedriver_version, \ - get_zip_name + get_target_arch, get_zip_name from lib.util import scoped_cwd, rm_rf, get_electron_version, make_zip, \ execute, electron_gyp @@ -94,7 +93,7 @@ def main(): create_version() create_dist_zip() - create_chrome_binary_zip('chromedriver', get_chromedriver_version()) + create_chrome_binary_zip('chromedriver', ELECTRON_VERSION) create_chrome_binary_zip('mksnapshot', ELECTRON_VERSION) create_ffmpeg_zip() create_symbols_zip() diff --git a/script/lib/config.py b/script/lib/config.py index d3f2c33d4c..ee47030cf9 100644 --- a/script/lib/config.py +++ b/script/lib/config.py @@ -42,9 +42,6 @@ def get_target_arch(): return 'x64' -def get_chromedriver_version(): - return 'v2.21' - def get_env_var(name): value = os.environ.get('ELECTRON_' + name, '') if not value: diff --git a/script/upload.py b/script/upload.py index 2ceffbe1dd..8b4cdfcbdf 100755 --- a/script/upload.py +++ b/script/upload.py @@ -10,8 +10,8 @@ import sys import tempfile from io import StringIO -from lib.config import PLATFORM, get_target_arch, get_chromedriver_version, \ - get_env_var, s3_config, get_zip_name +from lib.config import PLATFORM, get_target_arch, get_env_var, s3_config, \ + get_zip_name from lib.util import electron_gyp, execute, get_electron_version, \ parse_version, scoped_cwd, s3put from lib.github import GitHub @@ -91,7 +91,7 @@ def main(): # Upload chromedriver and mksnapshot for minor version update. if parse_version(args.version)[2] == '0': - chromedriver = get_zip_name('chromedriver', get_chromedriver_version()) + chromedriver = get_zip_name('chromedriver', ELECTRON_VERSION) upload_electron(github, release, os.path.join(DIST_DIR, chromedriver)) mksnapshot = get_zip_name('mksnapshot', ELECTRON_VERSION) upload_electron(github, release, os.path.join(DIST_DIR, mksnapshot)) From be79417a032602502b4ccb294b2655ea9c8b0c40 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 13 Feb 2017 15:21:55 -0800 Subject: [PATCH 168/925] Include CanFocus on Windows and map to state --- atom/browser/native_window_views.cc | 11 +++---- atom/browser/native_window_views.h | 2 ++ atom/browser/native_window_views_win.cc | 4 +++ .../ui/win/atom_desktop_native_widget_aura.cc | 20 +++++++++++++ .../ui/win/atom_desktop_native_widget_aura.h | 29 +++++++++++++++++++ filenames.gypi | 2 ++ 6 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 atom/browser/ui/win/atom_desktop_native_widget_aura.cc create mode 100644 atom/browser/ui/win/atom_desktop_native_widget_aura.h diff --git a/atom/browser/native_window_views.cc b/atom/browser/native_window_views.cc index 771caf583d..a883a66cbb 100644 --- a/atom/browser/native_window_views.cc +++ b/atom/browser/native_window_views.cc @@ -48,6 +48,7 @@ #include "ui/views/window/native_frame_view.h" #elif defined(OS_WIN) #include "atom/browser/ui/views/win_frame_view.h" +#include "atom/browser/ui/win/atom_desktop_native_widget_aura.h" #include "atom/browser/ui/win/atom_desktop_window_tree_host_win.h" #include "skia/ext/skia_utils_win.h" #include "ui/base/win/shell.h" @@ -148,7 +149,8 @@ NativeWindowViews::NativeWindowViews( resizable_(true), maximizable_(true), minimizable_(true), - fullscreenable_(true) { + fullscreenable_(true), + focusable_(true) { options.Get(options::kTitle, &title_); options.Get(options::kAutoHideMenuBar, &menu_bar_autohide_); @@ -196,16 +198,14 @@ NativeWindowViews::NativeWindowViews( if (transparent() && !has_frame()) params.shadow_type = views::Widget::InitParams::SHADOW_TYPE_NONE; - bool focusable; - if (options.Get(options::kFocusable, &focusable) && !focusable) + if (options.Get(options::kFocusable, &focusable_) && !focusable_) params.activatable = views::Widget::InitParams::ACTIVATABLE_NO; #if defined(OS_WIN) if (parent) params.parent = parent->GetNativeWindow(); - params.native_widget = - new views::DesktopNativeWidgetAura(window_.get()); + params.native_widget = new AtomDeskopNativeWidgetAura(window_.get(), this); atom_desktop_window_tree_host_win_ = new AtomDesktopWindowTreeHostWin( this, window_.get(), @@ -806,6 +806,7 @@ void NativeWindowViews::SetContentProtection(bool enable) { } void NativeWindowViews::SetFocusable(bool focusable) { + focusable_ = focusable; #if defined(OS_WIN) LONG ex_style = ::GetWindowLong(GetAcceleratedWidget(), GWL_EXSTYLE); if (focusable) diff --git a/atom/browser/native_window_views.h b/atom/browser/native_window_views.h index a7f02fb272..032c78f1d1 100644 --- a/atom/browser/native_window_views.h +++ b/atom/browser/native_window_views.h @@ -130,6 +130,7 @@ class NativeWindowViews : public NativeWindow, #if defined(OS_WIN) TaskbarHost& taskbar_host() { return taskbar_host_; } + bool CanFocus(); #endif private: @@ -262,6 +263,7 @@ class NativeWindowViews : public NativeWindow, bool maximizable_; bool minimizable_; bool fullscreenable_; + bool focusable_; std::string title_; gfx::Size widget_size_; diff --git a/atom/browser/native_window_views_win.cc b/atom/browser/native_window_views_win.cc index 1b523e90b8..ea6fc8004a 100644 --- a/atom/browser/native_window_views_win.cc +++ b/atom/browser/native_window_views_win.cc @@ -202,4 +202,8 @@ void NativeWindowViews::HandleSizeEvent(WPARAM w_param, LPARAM l_param) { } } +bool NativeWindowViews::CanFocus() { + return focusable_ && IsVisible(); +} + } // namespace atom diff --git a/atom/browser/ui/win/atom_desktop_native_widget_aura.cc b/atom/browser/ui/win/atom_desktop_native_widget_aura.cc new file mode 100644 index 0000000000..812e2636bf --- /dev/null +++ b/atom/browser/ui/win/atom_desktop_native_widget_aura.cc @@ -0,0 +1,20 @@ +// Copyright (c) 2017 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/ui/win/atom_desktop_native_widget_aura.h" + +namespace atom { + +AtomDeskopNativeWidgetAura::AtomDeskopNativeWidgetAura( + views::internal::NativeWidgetDelegate* delegate, + NativeWindowViews* window) + : views::DesktopNativeWidgetAura(delegate), + window_(window) { +} + +bool AtomDeskopNativeWidgetAura::CanFocus() { + return window_->CanFocus(); +} + +} // namespace atom diff --git a/atom/browser/ui/win/atom_desktop_native_widget_aura.h b/atom/browser/ui/win/atom_desktop_native_widget_aura.h new file mode 100644 index 0000000000..7048a47c19 --- /dev/null +++ b/atom/browser/ui/win/atom_desktop_native_widget_aura.h @@ -0,0 +1,29 @@ +// Copyright (c) 2017 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_UI_WIN_ATOM_DESKTOP_NATIVE_WIDGET_AURA_H_ +#define ATOM_BROWSER_UI_WIN_ATOM_DESKTOP_NATIVE_WIDGET_AURA_H_ + +#include "atom/browser/native_window_views.h" +#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h" + +namespace atom { + +class AtomDeskopNativeWidgetAura : public views::DesktopNativeWidgetAura { + public: + AtomDeskopNativeWidgetAura(views::internal::NativeWidgetDelegate* delegate, + NativeWindowViews* window); + + // aura::WindowDelegate + bool CanFocus() override; + + private: + NativeWindowViews* window_; + + DISALLOW_COPY_AND_ASSIGN(AtomDeskopNativeWidgetAura); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_UI_WIN_ATOM_DESKTOP_NATIVE_WIDGET_AURA_H_ diff --git a/filenames.gypi b/filenames.gypi index d3dc7ed22c..00297becef 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -317,6 +317,8 @@ 'atom/browser/ui/views/submenu_button.h', 'atom/browser/ui/views/win_frame_view.cc', 'atom/browser/ui/views/win_frame_view.h', + 'atom/browser/ui/win/atom_desktop_native_widget_aura.cc', + 'atom/browser/ui/win/atom_desktop_native_widget_aura.h', 'atom/browser/ui/win/atom_desktop_window_tree_host_win.cc', 'atom/browser/ui/win/atom_desktop_window_tree_host_win.h', 'atom/browser/ui/win/jump_list.cc', From 35908ac398bb11f9644dbd28d445a9874843a79a Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 13 Feb 2017 18:11:37 -0800 Subject: [PATCH 169/925] Add webContents.focus() spec --- spec/api-web-contents-spec.js | 12 +++++++++++ spec/fixtures/pages/focus-web-contents.html | 24 +++++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 spec/fixtures/pages/focus-web-contents.html diff --git a/spec/api-web-contents-spec.js b/spec/api-web-contents-spec.js index f80e04ac7c..07faa93e61 100644 --- a/spec/api-web-contents-spec.js +++ b/spec/api-web-contents-spec.js @@ -308,4 +308,16 @@ describe('webContents module', function () { } }) }) + + describe('focus()', function () { + it('focuses the parent window', function (done) { + ipcMain.once('answer', (event, visible, focused) => { + assert.equal(visible, true) + assert.equal(focused, true) + done() + }) + w.show() + w.loadURL('file://' + path.join(__dirname, 'fixtures', 'pages', 'focus-web-contents.html')) + }) + }) }) diff --git a/spec/fixtures/pages/focus-web-contents.html b/spec/fixtures/pages/focus-web-contents.html new file mode 100644 index 0000000000..8411439a80 --- /dev/null +++ b/spec/fixtures/pages/focus-web-contents.html @@ -0,0 +1,24 @@ + + + + + + + + + + + From 86007fe61dcb980f602670f99f3ebbe88d5c06e7 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 13 Feb 2017 18:22:57 -0800 Subject: [PATCH 170/925] Update blur parent window spec --- spec/api-web-contents-spec.js | 16 +++++++++------- spec/fixtures/pages/focus-web-contents.html | 16 ++++++++-------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/spec/api-web-contents-spec.js b/spec/api-web-contents-spec.js index 07faa93e61..0faaafbcda 100644 --- a/spec/api-web-contents-spec.js +++ b/spec/api-web-contents-spec.js @@ -310,14 +310,16 @@ describe('webContents module', function () { }) describe('focus()', function () { - it('focuses the parent window', function (done) { - ipcMain.once('answer', (event, visible, focused) => { - assert.equal(visible, true) - assert.equal(focused, true) - done() + describe('when the web contents is hidden', function () { + it('does not blur the focused window', function (done) { + ipcMain.once('answer', (event, parentFocused, childFocused) => { + assert.equal(parentFocused, true) + assert.equal(childFocused, false) + done() + }) + w.show() + w.loadURL('file://' + path.join(__dirname, 'fixtures', 'pages', 'focus-web-contents.html')) }) - w.show() - w.loadURL('file://' + path.join(__dirname, 'fixtures', 'pages', 'focus-web-contents.html')) }) }) }) diff --git a/spec/fixtures/pages/focus-web-contents.html b/spec/fixtures/pages/focus-web-contents.html index 8411439a80..83675bc1d3 100644 --- a/spec/fixtures/pages/focus-web-contents.html +++ b/spec/fixtures/pages/focus-web-contents.html @@ -6,16 +6,16 @@ From bda8af8dd3037e126dddd590de6b8ff087573637 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 14 Feb 2017 12:52:19 -0800 Subject: [PATCH 171/925] Just use visible state in CanFocus delegate --- atom/browser/native_window_views.cc | 7 +++---- atom/browser/native_window_views.h | 2 -- atom/browser/native_window_views_win.cc | 4 ---- atom/browser/ui/win/atom_desktop_native_widget_aura.cc | 2 +- 4 files changed, 4 insertions(+), 11 deletions(-) diff --git a/atom/browser/native_window_views.cc b/atom/browser/native_window_views.cc index a883a66cbb..c2c6212153 100644 --- a/atom/browser/native_window_views.cc +++ b/atom/browser/native_window_views.cc @@ -149,8 +149,7 @@ NativeWindowViews::NativeWindowViews( resizable_(true), maximizable_(true), minimizable_(true), - fullscreenable_(true), - focusable_(true) { + fullscreenable_(true) { options.Get(options::kTitle, &title_); options.Get(options::kAutoHideMenuBar, &menu_bar_autohide_); @@ -198,7 +197,8 @@ NativeWindowViews::NativeWindowViews( if (transparent() && !has_frame()) params.shadow_type = views::Widget::InitParams::SHADOW_TYPE_NONE; - if (options.Get(options::kFocusable, &focusable_) && !focusable_) + bool focusable; + if (options.Get(options::kFocusable, &focusable) && !focusable) params.activatable = views::Widget::InitParams::ACTIVATABLE_NO; #if defined(OS_WIN) @@ -806,7 +806,6 @@ void NativeWindowViews::SetContentProtection(bool enable) { } void NativeWindowViews::SetFocusable(bool focusable) { - focusable_ = focusable; #if defined(OS_WIN) LONG ex_style = ::GetWindowLong(GetAcceleratedWidget(), GWL_EXSTYLE); if (focusable) diff --git a/atom/browser/native_window_views.h b/atom/browser/native_window_views.h index 032c78f1d1..a7f02fb272 100644 --- a/atom/browser/native_window_views.h +++ b/atom/browser/native_window_views.h @@ -130,7 +130,6 @@ class NativeWindowViews : public NativeWindow, #if defined(OS_WIN) TaskbarHost& taskbar_host() { return taskbar_host_; } - bool CanFocus(); #endif private: @@ -263,7 +262,6 @@ class NativeWindowViews : public NativeWindow, bool maximizable_; bool minimizable_; bool fullscreenable_; - bool focusable_; std::string title_; gfx::Size widget_size_; diff --git a/atom/browser/native_window_views_win.cc b/atom/browser/native_window_views_win.cc index ea6fc8004a..1b523e90b8 100644 --- a/atom/browser/native_window_views_win.cc +++ b/atom/browser/native_window_views_win.cc @@ -202,8 +202,4 @@ void NativeWindowViews::HandleSizeEvent(WPARAM w_param, LPARAM l_param) { } } -bool NativeWindowViews::CanFocus() { - return focusable_ && IsVisible(); -} - } // namespace atom diff --git a/atom/browser/ui/win/atom_desktop_native_widget_aura.cc b/atom/browser/ui/win/atom_desktop_native_widget_aura.cc index 812e2636bf..4e2cc60bff 100644 --- a/atom/browser/ui/win/atom_desktop_native_widget_aura.cc +++ b/atom/browser/ui/win/atom_desktop_native_widget_aura.cc @@ -14,7 +14,7 @@ AtomDeskopNativeWidgetAura::AtomDeskopNativeWidgetAura( } bool AtomDeskopNativeWidgetAura::CanFocus() { - return window_->CanFocus(); + return window_->IsVisible(); } } // namespace atom From db79f4f450c86a358e8963fc8f9c028936599b47 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 14 Feb 2017 13:09:24 -0800 Subject: [PATCH 172/925] Implement Activate instead of CanFocus --- atom/browser/native_window_views.cc | 2 +- atom/browser/ui/win/atom_desktop_native_widget_aura.cc | 7 ++++--- atom/browser/ui/win/atom_desktop_native_widget_aura.h | 10 +++++----- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/atom/browser/native_window_views.cc b/atom/browser/native_window_views.cc index c2c6212153..ad85cb0169 100644 --- a/atom/browser/native_window_views.cc +++ b/atom/browser/native_window_views.cc @@ -205,7 +205,7 @@ NativeWindowViews::NativeWindowViews( if (parent) params.parent = parent->GetNativeWindow(); - params.native_widget = new AtomDeskopNativeWidgetAura(window_.get(), this); + params.native_widget = new AtomDesktopNativeWidgetAura(window_.get(), this); atom_desktop_window_tree_host_win_ = new AtomDesktopWindowTreeHostWin( this, window_.get(), diff --git a/atom/browser/ui/win/atom_desktop_native_widget_aura.cc b/atom/browser/ui/win/atom_desktop_native_widget_aura.cc index 4e2cc60bff..2986fdf8ae 100644 --- a/atom/browser/ui/win/atom_desktop_native_widget_aura.cc +++ b/atom/browser/ui/win/atom_desktop_native_widget_aura.cc @@ -6,15 +6,16 @@ namespace atom { -AtomDeskopNativeWidgetAura::AtomDeskopNativeWidgetAura( +AtomDesktopNativeWidgetAura::AtomDesktopNativeWidgetAura( views::internal::NativeWidgetDelegate* delegate, NativeWindowViews* window) : views::DesktopNativeWidgetAura(delegate), window_(window) { } -bool AtomDeskopNativeWidgetAura::CanFocus() { - return window_->IsVisible(); +void AtomDesktopNativeWidgetAura::Activate() { + if (window_->IsVisible()) + views::DesktopNativeWidgetAura::Activate(); } } // namespace atom diff --git a/atom/browser/ui/win/atom_desktop_native_widget_aura.h b/atom/browser/ui/win/atom_desktop_native_widget_aura.h index 7048a47c19..a41d48d73f 100644 --- a/atom/browser/ui/win/atom_desktop_native_widget_aura.h +++ b/atom/browser/ui/win/atom_desktop_native_widget_aura.h @@ -10,18 +10,18 @@ namespace atom { -class AtomDeskopNativeWidgetAura : public views::DesktopNativeWidgetAura { +class AtomDesktopNativeWidgetAura : public views::DesktopNativeWidgetAura { public: - AtomDeskopNativeWidgetAura(views::internal::NativeWidgetDelegate* delegate, + AtomDesktopNativeWidgetAura(views::internal::NativeWidgetDelegate* delegate, NativeWindowViews* window); - // aura::WindowDelegate - bool CanFocus() override; + // internal::NativeWidgetPrivate: + void Activate() override; private: NativeWindowViews* window_; - DISALLOW_COPY_AND_ASSIGN(AtomDeskopNativeWidgetAura); + DISALLOW_COPY_AND_ASSIGN(AtomDesktopNativeWidgetAura); }; } // namespace atom From 52801c4a41b6852503fe1802733ac016a080034d Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 14 Feb 2017 13:14:35 -0800 Subject: [PATCH 173/925] Use internal IsVisible method --- atom/browser/native_window_views.cc | 2 +- .../browser/ui/win/atom_desktop_native_widget_aura.cc | 11 ++++++----- atom/browser/ui/win/atom_desktop_native_widget_aura.h | 5 +---- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/atom/browser/native_window_views.cc b/atom/browser/native_window_views.cc index ad85cb0169..b0722cbc4a 100644 --- a/atom/browser/native_window_views.cc +++ b/atom/browser/native_window_views.cc @@ -205,7 +205,7 @@ NativeWindowViews::NativeWindowViews( if (parent) params.parent = parent->GetNativeWindow(); - params.native_widget = new AtomDesktopNativeWidgetAura(window_.get(), this); + params.native_widget = new AtomDesktopNativeWidgetAura(window_.get()); atom_desktop_window_tree_host_win_ = new AtomDesktopWindowTreeHostWin( this, window_.get(), diff --git a/atom/browser/ui/win/atom_desktop_native_widget_aura.cc b/atom/browser/ui/win/atom_desktop_native_widget_aura.cc index 2986fdf8ae..e0cd68608a 100644 --- a/atom/browser/ui/win/atom_desktop_native_widget_aura.cc +++ b/atom/browser/ui/win/atom_desktop_native_widget_aura.cc @@ -7,14 +7,15 @@ namespace atom { AtomDesktopNativeWidgetAura::AtomDesktopNativeWidgetAura( - views::internal::NativeWidgetDelegate* delegate, - NativeWindowViews* window) - : views::DesktopNativeWidgetAura(delegate), - window_(window) { + views::internal::NativeWidgetDelegate* delegate) + : views::DesktopNativeWidgetAura(delegate) { } void AtomDesktopNativeWidgetAura::Activate() { - if (window_->IsVisible()) + // Activate can cause the focused window to be blurred so only + // call when the window being activated is visible. This prevents + // hidden windows from blurring the focused window when created. + if (IsVisible()) views::DesktopNativeWidgetAura::Activate(); } diff --git a/atom/browser/ui/win/atom_desktop_native_widget_aura.h b/atom/browser/ui/win/atom_desktop_native_widget_aura.h index a41d48d73f..30da757ec1 100644 --- a/atom/browser/ui/win/atom_desktop_native_widget_aura.h +++ b/atom/browser/ui/win/atom_desktop_native_widget_aura.h @@ -12,15 +12,12 @@ namespace atom { class AtomDesktopNativeWidgetAura : public views::DesktopNativeWidgetAura { public: - AtomDesktopNativeWidgetAura(views::internal::NativeWidgetDelegate* delegate, - NativeWindowViews* window); + AtomDesktopNativeWidgetAura(views::internal::NativeWidgetDelegate* delegate); // internal::NativeWidgetPrivate: void Activate() override; private: - NativeWindowViews* window_; - DISALLOW_COPY_AND_ASSIGN(AtomDesktopNativeWidgetAura); }; From 70849de8c4c23b191f467c28cb43d36f62691bd2 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 14 Feb 2017 13:18:06 -0800 Subject: [PATCH 174/925] Mark constructor as explicit --- atom/browser/ui/win/atom_desktop_native_widget_aura.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/atom/browser/ui/win/atom_desktop_native_widget_aura.h b/atom/browser/ui/win/atom_desktop_native_widget_aura.h index 30da757ec1..b5a6c0933d 100644 --- a/atom/browser/ui/win/atom_desktop_native_widget_aura.h +++ b/atom/browser/ui/win/atom_desktop_native_widget_aura.h @@ -12,7 +12,8 @@ namespace atom { class AtomDesktopNativeWidgetAura : public views::DesktopNativeWidgetAura { public: - AtomDesktopNativeWidgetAura(views::internal::NativeWidgetDelegate* delegate); + explicit AtomDesktopNativeWidgetAura( + views::internal::NativeWidgetDelegate* delegate); // internal::NativeWidgetPrivate: void Activate() override; From d97585d06dca54f7140e407ad48ce725bb71024b Mon Sep 17 00:00:00 2001 From: Eric Brumer Date: Wed, 15 Feb 2017 19:02:11 -0800 Subject: [PATCH 175/925] Allow favicon.png to be on the root of any drive letter --- spec/webview-spec.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/spec/webview-spec.js b/spec/webview-spec.js index 21a5401840..f25d949171 100644 --- a/spec/webview-spec.js +++ b/spec/webview-spec.js @@ -523,8 +523,11 @@ describe(' tag', function () { it('emits when favicon urls are received', function (done) { webview.addEventListener('page-favicon-updated', function (e) { assert.equal(e.favicons.length, 2) - var pageUrl = process.platform === 'win32' ? 'file:///C:/favicon.png' : 'file:///favicon.png' - assert.equal(e.favicons[0], pageUrl) + if (process.platform === 'win32') { + assert(/^file:\/\/\/[a-zA-Z]:\/favicon.png$/.test(e.favicons[0])) + } else { + assert.equal(e.favicons[0], 'file:///favicon.png') + } done() }) webview.src = 'file://' + fixtures + '/pages/a.html' From 6dd18678a9db5ab2891e6afd3ea1ab36fdd21f8e Mon Sep 17 00:00:00 2001 From: DemoPark <106856363@qq.com> Date: Thu, 16 Feb 2017 16:31:55 +0800 Subject: [PATCH 176/925] add translations files for zh-CN add zh-CN translations file - security.md add zh-CN translations file - repl.md add zh-CN translations file - windows-store-guide.md update zh-CN translations file - glossary.md update zh-CN translations file - README.md --- docs-translations/zh-CN/README.md | 6 +- docs-translations/zh-CN/glossary.md | 2 +- docs-translations/zh-CN/tutorial/repl.md | 22 ++++ docs-translations/zh-CN/tutorial/security.md | 46 +++++++ .../zh-CN/tutorial/windows-store-guide.md | 114 ++++++++++++++++++ 5 files changed, 186 insertions(+), 4 deletions(-) create mode 100644 docs-translations/zh-CN/tutorial/repl.md create mode 100644 docs-translations/zh-CN/tutorial/security.md create mode 100644 docs-translations/zh-CN/tutorial/windows-store-guide.md diff --git a/docs-translations/zh-CN/README.md b/docs-translations/zh-CN/README.md index 04167acf62..fba7f3007f 100644 --- a/docs-translations/zh-CN/README.md +++ b/docs-translations/zh-CN/README.md @@ -12,7 +12,7 @@ * [术语表](glossary.md) * [支持平台](tutorial/supported-platforms.md) -* [安全性](tutorial/security.md) 未翻译 +* [安全性](tutorial/security.md) * [Electron 版本管理](tutorial/electron-versioning.md) * [分发应用](tutorial/application-distribution.md) * [提交应用到 Mac App Store](tutorial/mac-app-store-submission-guide.md) @@ -24,7 +24,7 @@ * [使用开发人员工具扩展](tutorial/devtools-extension.md) * [使用 Pepper Flash 插件](tutorial/using-pepper-flash-plugin.md) * [使用 Widevine CDM 插件](tutorial/using-widevine-cdm-plugin.md) -* [通过自动化持续集成系统(CI)进行测试 (Travis, Jenkins)](tutorial/testing-on-headless-ci.md) 未翻译 +* [通过自动化持续集成系统(CI)进行测试 (Travis, Jenkins)](tutorial/testing-on-headless-ci.md) * [离屏渲染](tutorial/offscreen-rendering.md) ## 教程 @@ -32,7 +32,7 @@ * [快速入门](tutorial/quick-start.md) * [桌面环境集成](tutorial/desktop-environment-integration.md) * [在线/离线事件探测](tutorial/online-offline-events.md) -* [应答式编译器(REPL)](tutorial/repl.md) 未翻译 +* [交互式解释器(REPL)](tutorial/repl.md) ## API文档 diff --git a/docs-translations/zh-CN/glossary.md b/docs-translations/zh-CN/glossary.md index 37ca43d9fb..69465b537d 100644 --- a/docs-translations/zh-CN/glossary.md +++ b/docs-translations/zh-CN/glossary.md @@ -1,4 +1,4 @@ -# Glossary +# 术语表 这篇文档说明了一些经常在 Electron 开发中使用的专业术语。 diff --git a/docs-translations/zh-CN/tutorial/repl.md b/docs-translations/zh-CN/tutorial/repl.md new file mode 100644 index 0000000000..c23a4fc951 --- /dev/null +++ b/docs-translations/zh-CN/tutorial/repl.md @@ -0,0 +1,22 @@ +# 交互式解释器 (REPL) + +读取(Read)-运算(Eval)-输出(Print)-循环(Loop) (REPL) 是很简单的, 交互式的计算机编程环境,它采用单个用户输入,运算并返回结果给用户。 + +在这里 `repl` 模块提供了一个 REPL 的实现, 可以这样使用: + +* 如果你的 `electron` 或 `electron-prebuilt` 已经安装为本地项目依赖项: + + ```sh + ./node_modules/.bin/electron --interactive + ``` +* 如果你的 `electron` 或 `electron-prebuilt` 已经为全局方式安装: + + ```sh + electron --interactive + ``` + +这里只会为主进程创建一个REPL。 您可以使用 Dev Tools 的“控制台”选项卡来为渲染器进程获取一个REPL。 + +**注意:** `electron --interactive` 在 Windows 上不可用. + +更多的内容可以在这里找到 [Node.js REPL docs](https://nodejs.org/dist/latest/docs/api/repl.html). diff --git a/docs-translations/zh-CN/tutorial/security.md b/docs-translations/zh-CN/tutorial/security.md new file mode 100644 index 0000000000..b546f5c5ae --- /dev/null +++ b/docs-translations/zh-CN/tutorial/security.md @@ -0,0 +1,46 @@ +# 安全,本地功能和你的责任 + +作为 web 开发人员,我们通常喜欢网络安全性更强大的浏览器 - 与我们编写的代码相关的风险相对较小。我们的网站在沙箱中获得有限的权限,我们相信我们的用户可以享受由大量工程师团队构建的浏览器,能够快速响应新发现的安全威胁。 + +当使用 Electron 时,要知道 Electron 不是一个 Web 浏览器很重要。它允许您使用熟悉的 Web 技术构建功能丰富的桌面应用程序,但是您的代码具有更强大的功能。 JavaScript 可以访问文件系统,用户 shell 等。这允许您构建更高质量的本机应用程序,但是内在的安全风险会随着授予您的代码的额外权力而增加。 + +考虑到这一点,请注意,在 Electron 不任何处理的情况下显示来自不受信任的来源的任何内容将带来了严重的安全风险。事实上,最流行的 Electron 应用程序(Atom,Slack,Visual Studio Code 等)主要显示本地内容(或没有 Node 集成的可信安全远程内容) - 如果您的应用程序从在线源执行代码,那么您有责任确保代码不是恶意的。 + +## 报告安全问题 + +有关如何正确上报 Electron 漏洞的信息,参阅 [SECURITY.md](https://github.com/electron/electron/tree/master/SECURITY.md) + +## Chromium 安全问题和升级 + +尽管 Electron 努力尽快支持新版本的 Chromium,但开发人员应该意识到,升级是一项严肃的工作 - 涉及手动编辑几十个甚至几百个文件。 考虑到当前的资源和贡献,Electron 通常不会是最新版本的 Chromium,总是落后于一两天或几周。 + +我们认为,我们当前的更新 Chromium 组件的系统在我们可用的资源和构建在框架之上的大多数应用程序的需求之间取得了适当的平衡。 我们绝对有兴趣听听更多关于在 Electron 上构建事物的人的具体用例。 非常欢迎提出请求并且捐助支持我们的努力。 + +## 除了以上建议 + +每当您从远程目标收到代码并在本地执行它时,就会存在安全问题。 举个例子,比如在浏览器窗口内显示的远程网站。 如果攻击者以某种方式设法改变所述内容(通过直接攻击源或者通过在应用和实际目的地之间进行攻击),他们将能够在用户的机器上执行本地代码。 + +> :警告: 在任何情况下都不应该在启用了 Node 集成时加载并执行远程代码. 反而应该只使用本地文件(与应用程序一起打包)来执行 Node 代码。要显示远程内容, 应使用 `webview` 标签并确保禁用了 `nodeIntegration`. + +#### 检查列表 + +这并不是万无一失的,但至少,你应该尝试以下内容: + +* 只显示安全的内容(https) +* 在显示远程内容的所有渲染器中禁用 Node 集成 (在 `webPreferences` 中设置 `nodeIntegration` 为 `false`) +* 在显示远程内容的所有渲染器中启用上下文隔离 (在 `webPreferences` 中设置 `contextIsolation` 为 `true`) +* 在所有加载远程内容的会话中使用 `ses.setPermissionRequestHandler()` . +* 不要禁用 `webSecurity`. 禁用它将禁用同源策略. +* 定义一个 [`Content-Security-Policy`](http://www.html5rocks.com/en/tutorials/security/content-security-policy/) +, 并使用限制规则 (即: `script-src 'self'`) +* 覆盖并禁用 [`eval`](https://github.com/nylas/N1/blob/0abc5d5defcdb057120d726b271933425b75b415/static/index.js#L6-L8) +, 它允许字符串作为代码执行. +* 不要设置 `allowRunningInsecureContent` 为 `true`. +* 不要启用 `experimentalFeatures` 或 `experimentalCanvasFeatures` 除非你知道你在做什么. +* 不要使用 `blinkFeatures` 除非你知道你在做什么. +* WebViews: 不要填加 `nodeintegration` 属性. +* WebViews: 不要使用 `disablewebsecurity` +* WebViews: 不要使用 `allowpopups` +* WebViews: 不要使用 `insertCSS` 或 `executeJavaScript` 操作远程 CSS/JS. + +强调一下,这份列表只是将风险降到最低,并不会完全屏蔽风险。 如果您的目的是展示一个网站,浏览器将是一个更安全的选择。 \ No newline at end of file diff --git a/docs-translations/zh-CN/tutorial/windows-store-guide.md b/docs-translations/zh-CN/tutorial/windows-store-guide.md new file mode 100644 index 0000000000..67cd39e5a5 --- /dev/null +++ b/docs-translations/zh-CN/tutorial/windows-store-guide.md @@ -0,0 +1,114 @@ +# Windows商店指南 + +在 Windows 8 中, 一些不错的旧 win32 程序迎来了一个新朋友: 通用Windows平台(UWP)。 新的 `.appx` 格式不仅启用了许多新的强大的 API,如 Cortana 或推送通知,而且通过Windows 应用商店,也同时简化了安装和更新。 + +Microsoft 开发了一个工具,将 Electron 应用程序[编译为 `.appx` 软件包][electron-windows-store],使开发人员能够使用新应用程序模型中的一些好东西。 本指南解释了如何使用它 - 以及 Electron AppX 包的功能和限制。 + +## 背景和要求 + +Windows 10 的 "周年更新" 能够运行 win32 `.exe` 程序并且它们的虚拟化文件系统和注册表跟随一起启动。 两者都是通过在 Windows 容器中运行应用程序和安装器编译后创建的,允许 Windows 在安装过程中正确识别操作系统进行了哪些修改。 将可执行文件和虚拟文件系统与虚拟注册表配对, 允许 Windows 启用一键安装和卸载。 + +此外,exe 在 appx 模型内启动 - 这意味着它可以使用通用 Windows 平台可用的许多 API。 为了获得更多的功能,Electron 应用程序可以与一个看不见的 UWP 后台任务配合使用,它与 `exe` 一起启动,作为后台运行任务的接收器,接收推送通知或与其他 UWP 应用程序通信 。 + +要编译任何现有的 Electron 应用程序,请确保满足以下要求: + +* Windows 10及周年更新 (2016年8月2日发布的) +* Windows 10 SDK, [这里下载][windows-sdk] +* 最新的 Node 4 (运行 `node -v` 来确认) + +然后, 安装 `electron-windows-store` CLI: + +``` +npm install -g electron-windows-store +``` + +## 步骤 1: 打包你的 Electron 应用程序 + +打包应用程序使用 [electron-packager][electron-packager] (或类似工具). 确保在最终的应用程序中删除不需要的 `node_modules`, 因为这些你不需要模块只会额外增加你的应用程序的大小. + +结构输出应该看起来大致像这样: + +``` +├── Ghost.exe +├── LICENSE +├── content_resources_200_percent.pak +├── content_shell.pak +├── d3dcompiler_47.dll +├── ffmpeg.dll +├── icudtl.dat +├── libEGL.dll +├── libGLESv2.dll +├── locales +│   ├── am.pak +│   ├── ar.pak +│   ├── [...] +├── natives_blob.bin +├── node.dll +├── resources +│   ├── app +│   └── atom.asar +├── snapshot_blob.bin +├── squirrel.exe +├── ui_resources_200_percent.pak +└── xinput1_3.dll +``` + +## 步骤 2: 运行 electron-windows-store + +从提权的 PowerShell(用管理员身份运行它)中,以所需的参数运行 `electron-windows-store`,传递输入和输出目录,应用程序的名称和版本,以及确认`node_modules`应该是扁平的。 + + +``` +electron-windows-store ` + --input-directory C:\myelectronapp ` + --output-directory C:\output\myelectronapp ` + --flatten true ` + --package-version 1.0.0.0 ` + --package-name myelectronapp +``` + +一旦执行,工具就开始工作:它接受您的 Electron 应用程序作为输入,展平 `node_modules`。 然后,它将应用程序归档为 `app.zip`。 使用安装程序和 Windows 容器,该工具创建一个“扩展的” AppX 包 - 包括 Windows 应用程序清单 (`AppXManifest.xml`)以及虚拟文件系统和输出文件夹中的虚拟注册表。 + +当创建扩展的 AppX 文件后,该工具使用 Windows App Packager(`MakeAppx.exe`)将磁盘上的这些文件创建为单文件 AppX 包。 最后,该工具可用于在计算机上创建可信证书,以签署新的 AppX 包。 使用签名的 AppX 软件包,CLI也可以自动在您的计算机上安装软件包。 + + +## 步骤 3: 使用 AppX 包 + +为了运行您的软件包,您的用户将需要将 Windows 10 安装“周年纪念更新” - 有关如何更新Windows的详细信息可以在[这里][how-to-update]找到 + +与传统的UWP应用程序不同,打包应用程序目前需要进行手动验证过程,您可以在[这里][centennial-campaigns]申请. +在此期间,所有用户都能够通过双击安装包来安装您的程序,所以如果您只是寻找一个更简单的安装方法,可能不需要提交到商店。 + +在受管理的环境中(通常是企业), `Add-AppxPackage` PowerShell Cmdlet 可用于以[自动方式安装][add-appxpackage]它。 + +另一个重要的限制是编译的 AppX 包仍然包含一个 win32 可执行文件,因此不会在 Xbox,HoloLens 或 Phones 中运行。 + +## 可选: 使用 BackgroundTask 添加 UWP 功能 + +您可以将 Electron 应用程序与不可见的 UWP 后台任务配对,以充分利用 Windows 10 功能,如推送通知,Cortana 集成或活动磁贴。 + +如何使用 Electron 应用程序通过后台任务发送 Toast 通知和活动磁贴,请查看[微软提供的案例][background-task]. + + +## 可选: 使用容器虚拟化进行转换 + +要生成 AppX 包,`electron-windows-store` CLI 使用的模板应该适用于大多数 Electron 应用程序。 但是,如果您使用自定义安装程序,或者您遇到生成的包的任何问题,您可以尝试使用 Windows 容器编译创建包 - 在该模式下,CLI 将在空 Windows 容器中安装和运行应用程序,以确定应用程序正在对操作系统进行哪些修改。 + +在运行 CLI 之前,您必须设置 “Windows Desktop App Converter” 。 这将需要几分钟,但不要担心 - 你只需要这样做一次。 从这里下载 [Desktop App Converter][app-converter] + +您将得到两个文件: `DesktopAppConverter.zip` 和 `BaseImage-14316.wim`. + +1. 解压 `DesktopAppConverter.zip`. 打开提权的 PowerShell (用"以管理员权限运行"打开, 确保您的系统执行策略允许我们通过调用 `Set-ExecutionPolicy bypass` 来运行我们想要运行的一切). +2. 然后, 通过调用 `.\DesktopAppConverter.ps1 -Setup -BaseImage .\BaseImage-14316.wim`, 运行 Desktop App Converter 安装,并传递 Windows 基本映像的位置 (下载的 `BaseImage-14316.wim`). +3. 如果运行以上命令提示您重新启动,请重新启动计算机,并在成功重新启动后再次运行上述命令。 + +当安装成功后,您可以继续编译你的 Electron 应用程序。 + +[windows-sdk]: https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk +[app-converter]: https://www.microsoft.com/en-us/download/details.aspx?id=51691 +[add-appxpackage]: https://technet.microsoft.com/en-us/library/hh856048.aspx +[electron-packager]: https://github.com/electron-userland/electron-packager +[electron-windows-store]: https://github.com/catalystcode/electron-windows-store +[background-task]: https://github.com/felixrieseberg/electron-uwp-background +[centennial-campaigns]: https://developer.microsoft.com/en-us/windows/projects/campaigns/desktop-bridge +[how-to-update]: https://blogs.windows.com/windowsexperience/2016/08/02/how-to-get-the-windows-10-anniversary-update From 7cb101b56658d4ce4cf8d26f229b8824a30a8364 Mon Sep 17 00:00:00 2001 From: Eric Brumer Date: Thu, 16 Feb 2017 09:17:37 -0800 Subject: [PATCH 177/925] Case insensitive filepath --- spec/webview-spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/webview-spec.js b/spec/webview-spec.js index f25d949171..16e38f2d2d 100644 --- a/spec/webview-spec.js +++ b/spec/webview-spec.js @@ -524,7 +524,7 @@ describe(' tag', function () { webview.addEventListener('page-favicon-updated', function (e) { assert.equal(e.favicons.length, 2) if (process.platform === 'win32') { - assert(/^file:\/\/\/[a-zA-Z]:\/favicon.png$/.test(e.favicons[0])) + assert(/^file:\/\/\/[a-zA-Z]:\/favicon.png$/i.test(e.favicons[0])) } else { assert.equal(e.favicons[0], 'file:///favicon.png') } From 87e606627c7d2c04e51cfc71918967bc8c749ff4 Mon Sep 17 00:00:00 2001 From: Eric Brumer Date: Thu, 16 Feb 2017 09:26:20 -0800 Subject: [PATCH 178/925] Smaller regex now that it's case insensitive --- spec/webview-spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/webview-spec.js b/spec/webview-spec.js index 16e38f2d2d..c6b69310a8 100644 --- a/spec/webview-spec.js +++ b/spec/webview-spec.js @@ -524,7 +524,7 @@ describe(' tag', function () { webview.addEventListener('page-favicon-updated', function (e) { assert.equal(e.favicons.length, 2) if (process.platform === 'win32') { - assert(/^file:\/\/\/[a-zA-Z]:\/favicon.png$/i.test(e.favicons[0])) + assert(/^file:\/\/\/[A-Z]:\/favicon.png$/i.test(e.favicons[0])) } else { assert.equal(e.favicons[0], 'file:///favicon.png') } From 977abc645872f3638c3710128c08a964b734a5f6 Mon Sep 17 00:00:00 2001 From: Yury Solovyov Date: Thu, 16 Feb 2017 22:19:19 +0300 Subject: [PATCH 179/925] Update icon loading API implementation --- atom/browser/api/atom_api_app.cc | 11 +- atom/browser/api/atom_api_app.h | 5 +- chromium_src/chrome/browser/icon_loader.cc | 48 +++--- chromium_src/chrome/browser/icon_loader.h | 75 ++++----- .../chrome/browser/icon_loader_auralinux.cc | 16 +- .../chrome/browser/icon_loader_mac.mm | 16 +- .../chrome/browser/icon_loader_win.cc | 35 ++--- chromium_src/chrome/browser/icon_manager.cc | 143 +++++++----------- chromium_src/chrome/browser/icon_manager.h | 70 ++++----- 9 files changed, 176 insertions(+), 243 deletions(-) diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index 904a63a5d0..b8cc49f576 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -890,15 +890,18 @@ void App::GetFileIcon(const base::FilePath& path, return; } - IconManager* icon_manager = IconManager::GetInstance(); - gfx::Image* icon = icon_manager->LookupIconFromFilepath(normalized_path, + if (!icon_manager_.get()) { + icon_manager_.reset(new IconManager()); + } + + gfx::Image* icon = icon_manager_->LookupIconFromFilepath(normalized_path, icon_size); if (icon) { callback.Run(v8::Null(isolate()), *icon); } else { - icon_manager->LoadIcon(normalized_path, icon_size, + icon_manager_->LoadIcon(normalized_path, icon_size, base::Bind(&OnIconDataAvailable, isolate(), - callback)); + callback), &cancelable_task_tracker_); } } diff --git a/atom/browser/api/atom_api_app.h b/atom/browser/api/atom_api_app.h index 1d8e5804e5..0f7b138cb9 100644 --- a/atom/browser/api/atom_api_app.h +++ b/atom/browser/api/atom_api_app.h @@ -13,7 +13,8 @@ #include "atom/browser/browser.h" #include "atom/browser/browser_observer.h" #include "atom/common/native_mate_converters/callback.h" -#include "chrome/browser/icon_loader.h" +#include "base/task/cancelable_task_tracker.h" +#include "chrome/browser/icon_manager.h" #include "chrome/browser/process_singleton.h" #include "content/public/browser/gpu_data_manager_observer.h" #include "native_mate/handle.h" @@ -129,6 +130,8 @@ class App : public AtomBrowserClient::Delegate, void DisableHardwareAcceleration(mate::Arguments* args); bool IsAccessibilitySupportEnabled(); Browser::LoginItemSettings GetLoginItemSettings(mate::Arguments* args); + base::CancelableTaskTracker cancelable_task_tracker_; + std::unique_ptr icon_manager_; #if defined(USE_NSS_CERTS) void ImportCertificate(const base::DictionaryValue& options, const net::CompletionCallback& callback); diff --git a/chromium_src/chrome/browser/icon_loader.cc b/chromium_src/chrome/browser/icon_loader.cc index afc5d8016e..a0cca6379d 100644 --- a/chromium_src/chrome/browser/icon_loader.cc +++ b/chromium_src/chrome/browser/icon_loader.cc @@ -3,46 +3,40 @@ // found in the LICENSE file. #include "chrome/browser/icon_loader.h" - #include "base/bind.h" #include "base/threading/thread_task_runner_handle.h" #include "content/public/browser/browser_thread.h" using content::BrowserThread; -IconLoader::IconLoader(const base::FilePath& file_path, - IconSize size, - Delegate* delegate) - : target_task_runner_(NULL), - file_path_(file_path), - icon_size_(size), - delegate_(delegate) {} - -IconLoader::~IconLoader() {} +// static +IconLoader* IconLoader::Create(const base::FilePath& file_path, + IconSize size, + IconLoadedCallback callback) { + return new IconLoader(file_path, size, callback); +} void IconLoader::Start() { target_task_runner_ = base::ThreadTaskRunnerHandle::Get(); - - BrowserThread::PostTaskAndReply(BrowserThread::FILE, FROM_HERE, - base::Bind(&IconLoader::ReadGroup, this), - base::Bind(&IconLoader::OnReadGroup, this)); + BrowserThread::PostTaskAndReply( + BrowserThread::FILE, FROM_HERE, + base::Bind(&IconLoader::ReadGroup, base::Unretained(this)), + base::Bind(&IconLoader::OnReadGroup, base::Unretained(this))); } +IconLoader::IconLoader(const base::FilePath& file_path, + IconSize size, + IconLoadedCallback callback) + : file_path_(file_path), icon_size_(size), callback_(callback) {} + +IconLoader::~IconLoader() {} + void IconLoader::ReadGroup() { - group_ = ReadGroupIDFromFilepath(file_path_); + group_ = GroupForFilepath(file_path_); } void IconLoader::OnReadGroup() { - if (IsIconMutableFromFilepath(file_path_) || - !delegate_->OnGroupLoaded(this, group_)) { - BrowserThread::PostTask(ReadIconThreadID(), FROM_HERE, - base::Bind(&IconLoader::ReadIcon, this)); - } -} - -void IconLoader::NotifyDelegate() { - // If the delegate takes ownership of the Image, release it from the scoped - // pointer. - if (delegate_->OnImageLoaded(this, image_.get(), group_)) - ignore_result(image_.release()); // Can't ignore return value. + BrowserThread::PostTask( + ReadIconThreadID(), FROM_HERE, + base::Bind(&IconLoader::ReadIcon, base::Unretained(this))); } diff --git a/chromium_src/chrome/browser/icon_loader.h b/chromium_src/chrome/browser/icon_loader.h index 33301acd82..50d9ef4541 100644 --- a/chromium_src/chrome/browser/icon_loader.h +++ b/chromium_src/chrome/browser/icon_loader.h @@ -8,31 +8,30 @@ #include #include +#include "base/callback.h" #include "base/files/file_path.h" #include "base/macros.h" -#include "base/memory/ref_counted.h" #include "base/single_thread_task_runner.h" #include "build/build_config.h" #include "content/public/browser/browser_thread.h" #include "ui/gfx/image/image.h" -#if defined(OS_WIN) -// On Windows, we group files by their extension, with several exceptions: -// .dll, .exe, .ico. See IconManager.h for explanation. -typedef std::wstring IconGroupID; -#elif defined(OS_POSIX) -// On POSIX, we group files by MIME type. -typedef std::string IconGroupID; -#endif - //////////////////////////////////////////////////////////////////////////////// // // A facility to read a file containing an icon asynchronously in the IO // thread. Returns the icon in the form of an ImageSkia. // //////////////////////////////////////////////////////////////////////////////// -class IconLoader : public base::RefCountedThreadSafe { +class IconLoader { public: + // An IconGroup is a class of files that all share the same icon. For all + // platforms but Windows, and for most files on Windows, it is the file type + // (e.g. all .mp3 files share an icon, all .html files share an icon). On + // Windows, for certain file types (.exe, .dll, etc), each file of that type + // is assumed to have a unique icon. In that case, each of those files is a + // group to itself. + using IconGroup = base::FilePath::StringType; + enum IconSize { SMALL = 0, // 16x16 NORMAL, // 32x32 @@ -40,42 +39,32 @@ class IconLoader : public base::RefCountedThreadSafe { ALL, // All sizes available }; - class Delegate { - public: - // Invoked when an icon group has been read, but before the icon data - // is read. If the icon is already cached, this method should call and - // return the results of OnImageLoaded with the cached image. - virtual bool OnGroupLoaded(IconLoader* source, - const IconGroupID& group) = 0; - // Invoked when an icon has been read. |source| is the IconLoader. If the - // icon has been successfully loaded, result is non-null. This method must - // return true if it is taking ownership of the returned image. - virtual bool OnImageLoaded(IconLoader* source, - gfx::Image* result, - const IconGroupID& group) = 0; + // The callback invoked when an icon has been read. The parameters are: + // - The icon that was loaded, or null if there was a failure to load it. + // - The determined group from the original requested path. + using IconLoadedCallback = + base::Callback, const IconGroup&)>; - protected: - virtual ~Delegate() {} - }; + // Creates an IconLoader, which owns itself. If the IconLoader might outlive + // the caller, be sure to use a weak pointer in the |callback|. + static IconLoader* Create(const base::FilePath& file_path, + IconSize size, + IconLoadedCallback callback); - IconLoader(const base::FilePath& file_path, - IconSize size, - Delegate* delegate); - - // Start reading the icon on the file thread. + // Starts the process of reading the icon. When the reading of the icon is + // complete, the IconLoadedCallback callback will be fulfilled, and the + // IconLoader will delete itself. void Start(); private: - friend class base::RefCountedThreadSafe; + IconLoader(const base::FilePath& file_path, + IconSize size, + IconLoadedCallback callback); - virtual ~IconLoader(); + ~IconLoader(); - // Get the identifying string for the given file. The implementation - // is in icon_loader_[platform].cc. - static IconGroupID ReadGroupIDFromFilepath(const base::FilePath& path); - - // Some icons (exe's on windows) can change as they're loaded. - static bool IsIconMutableFromFilepath(const base::FilePath& path); + // Given a file path, get the group for the given file. + static IconGroup GroupForFilepath(const base::FilePath& file_path); // The thread ReadIcon() should be called on. static content::BrowserThread::ID ReadIconThreadID(); @@ -84,20 +73,18 @@ class IconLoader : public base::RefCountedThreadSafe { void OnReadGroup(); void ReadIcon(); - void NotifyDelegate(); - // The task runner object of the thread in which we notify the delegate. scoped_refptr target_task_runner_; base::FilePath file_path_; - IconGroupID group_; + IconGroup group_; IconSize icon_size_; std::unique_ptr image_; - Delegate* delegate_; + IconLoadedCallback callback_; DISALLOW_COPY_AND_ASSIGN(IconLoader); }; diff --git a/chromium_src/chrome/browser/icon_loader_auralinux.cc b/chromium_src/chrome/browser/icon_loader_auralinux.cc index 2a1b0d868f..449d05771d 100644 --- a/chromium_src/chrome/browser/icon_loader_auralinux.cc +++ b/chromium_src/chrome/browser/icon_loader_auralinux.cc @@ -10,14 +10,9 @@ #include "ui/views/linux_ui/linux_ui.h" // static -IconGroupID IconLoader::ReadGroupIDFromFilepath( - const base::FilePath& filepath) { - return base::nix::GetFileMimeType(filepath); -} - -// static -bool IconLoader::IsIconMutableFromFilepath(const base::FilePath&) { - return false; +IconLoader::IconGroup IconLoader::GroupForFilepath( + const base::FilePath& file_path) { + return base::nix::GetFileMimeType(file_path); } // static @@ -50,6 +45,7 @@ void IconLoader::ReadIcon() { image_.reset(new gfx::Image(image)); } - target_task_runner_->PostTask(FROM_HERE, - base::Bind(&IconLoader::NotifyDelegate, this)); + target_task_runner_->PostTask( + FROM_HERE, base::Bind(callback_, base::Passed(&image_), group_)); + delete this; } diff --git a/chromium_src/chrome/browser/icon_loader_mac.mm b/chromium_src/chrome/browser/icon_loader_mac.mm index a9dd42566b..dd94e9fe7e 100644 --- a/chromium_src/chrome/browser/icon_loader_mac.mm +++ b/chromium_src/chrome/browser/icon_loader_mac.mm @@ -15,14 +15,9 @@ #include "ui/gfx/image/image_skia_util_mac.h" // static -IconGroupID IconLoader::ReadGroupIDFromFilepath( - const base::FilePath& filepath) { - return filepath.Extension(); -} - -// static -bool IconLoader::IsIconMutableFromFilepath(const base::FilePath&) { - return false; +IconLoader::IconGroup IconLoader::GroupForFilepath( + const base::FilePath& file_path) { + return file_path.Extension(); } // static @@ -57,6 +52,7 @@ void IconLoader::ReadIcon() { } } - target_task_runner_->PostTask(FROM_HERE, - base::Bind(&IconLoader::NotifyDelegate, this)); + target_task_runner_->PostTask( + FROM_HERE, base::Bind(callback_, base::Passed(&image_), group_)); + delete this; } diff --git a/chromium_src/chrome/browser/icon_loader_win.cc b/chromium_src/chrome/browser/icon_loader_win.cc index 079c1f4828..279c819cc4 100644 --- a/chromium_src/chrome/browser/icon_loader_win.cc +++ b/chromium_src/chrome/browser/icon_loader_win.cc @@ -17,18 +17,15 @@ #include "ui/gfx/image/image_skia.h" // static -IconGroupID IconLoader::ReadGroupIDFromFilepath( - const base::FilePath& filepath) { - if (!IsIconMutableFromFilepath(filepath)) - return filepath.Extension(); - return filepath.value(); -} +IconLoader::IconGroup IconLoader::GroupForFilepath( + const base::FilePath& file_path) { + if (file_path.MatchesExtension(L".exe") || + file_path.MatchesExtension(L".dll") || + file_path.MatchesExtension(L".ico")) { + return file_path.value(); + } -// static -bool IconLoader::IsIconMutableFromFilepath(const base::FilePath& filepath) { - return filepath.MatchesExtension(L".exe") || - filepath.MatchesExtension(L".dll") || - filepath.MatchesExtension(L".ico"); + return file_path.Extension(); } // static @@ -54,22 +51,22 @@ void IconLoader::ReadIcon() { image_.reset(); - SHFILEINFO file_info = {0}; + SHFILEINFO file_info = { 0 }; if (SHGetFileInfo(group_.c_str(), FILE_ATTRIBUTE_NORMAL, &file_info, - sizeof(SHFILEINFO), - SHGFI_ICON | size | SHGFI_USEFILEATTRIBUTES)) { + sizeof(SHFILEINFO), + SHGFI_ICON | size | SHGFI_USEFILEATTRIBUTES)) { std::unique_ptr bitmap( IconUtil::CreateSkBitmapFromHICON(file_info.hIcon)); if (bitmap.get()) { - gfx::ImageSkia image_skia( - gfx::ImageSkiaRep(*bitmap, display::win::GetDPIScale())); + gfx::ImageSkia image_skia(gfx::ImageSkiaRep(*bitmap, + display::win::GetDPIScale())); image_skia.MakeThreadSafe(); image_.reset(new gfx::Image(image_skia)); DestroyIcon(file_info.hIcon); } } - // Always notify the delegate, regardless of success. - target_task_runner_->PostTask(FROM_HERE, - base::Bind(&IconLoader::NotifyDelegate, this)); + target_task_runner_->PostTask( + FROM_HERE, base::Bind(callback_, base::Passed(&image_), group_)); + delete this; } diff --git a/chromium_src/chrome/browser/icon_manager.cc b/chromium_src/chrome/browser/icon_manager.cc index 95073fcdd7..7b20ef323a 100644 --- a/chromium_src/chrome/browser/icon_manager.cc +++ b/chromium_src/chrome/browser/icon_manager.cc @@ -8,121 +8,86 @@ #include #include "base/bind.h" -#include "base/stl_util.h" #include "base/task_runner.h" #include "third_party/skia/include/core/SkBitmap.h" #include "third_party/skia/include/core/SkCanvas.h" -struct IconManager::ClientRequest { - IconRequestCallback callback; - base::FilePath file_path; - IconLoader::IconSize size; -}; +namespace { -// static -IconManager* IconManager::GetInstance() { - return base::Singleton::get(); +void RunCallbackIfNotCanceled( + const base::CancelableTaskTracker::IsCanceledCallback& is_canceled, + const IconManager::IconRequestCallback& callback, + gfx::Image* image) { + if (is_canceled.Run()) + return; + callback.Run(image); } -IconManager::IconManager() {} +} // namespace + +IconManager::IconManager() : weak_factory_(this) {} IconManager::~IconManager() { - base::STLDeleteValues(&icon_cache_); } -gfx::Image* IconManager::LookupIconFromFilepath(const base::FilePath& file_name, +gfx::Image* IconManager::LookupIconFromFilepath(const base::FilePath& file_path, IconLoader::IconSize size) { - GroupMap::iterator it = group_cache_.find(file_name); - if (it != group_cache_.end()) - return LookupIconFromGroup(it->second, size); + auto group_it = group_cache_.find(file_path); + if (group_it == group_cache_.end()) + return nullptr; - return NULL; + CacheKey key(group_it->second, size); + auto icon_it = icon_cache_.find(key); + if (icon_it == icon_cache_.end()) + return nullptr; + + return icon_it->second.get(); } -gfx::Image* IconManager::LookupIconFromGroup(const IconGroupID& group, - IconLoader::IconSize size) { - IconMap::iterator it = icon_cache_.find(CacheKey(group, size)); - if (it != icon_cache_.end()) - return it->second; +base::CancelableTaskTracker::TaskId IconManager::LoadIcon( + const base::FilePath& file_path, + IconLoader::IconSize size, + const IconRequestCallback& callback, + base::CancelableTaskTracker* tracker) { + base::CancelableTaskTracker::IsCanceledCallback is_canceled; + base::CancelableTaskTracker::TaskId id = + tracker->NewTrackedTaskId(&is_canceled); + IconRequestCallback callback_runner = base::Bind( + &RunCallbackIfNotCanceled, is_canceled, callback); - return nullptr; -} - -void IconManager::LoadIcon(const base::FilePath& file_name, - IconLoader::IconSize size, - const IconRequestCallback& callback) { - IconLoader* loader = new IconLoader(file_name, size, this); - loader->AddRef(); + IconLoader* loader = IconLoader::Create( + file_path, size, + base::Bind(&IconManager::OnIconLoaded, weak_factory_.GetWeakPtr(), + callback_runner, file_path, size)); loader->Start(); - ClientRequest client_request = {callback, file_name, size}; - requests_[loader] = client_request; + return id; } -// IconLoader::Delegate implementation ----------------------------------------- - -bool IconManager::OnGroupLoaded(IconLoader* loader, const IconGroupID& group) { - ClientRequests::iterator rit = requests_.find(loader); - if (rit == requests_.end()) { - NOTREACHED(); - return false; +void IconManager::OnIconLoaded(IconRequestCallback callback, + base::FilePath file_path, + IconLoader::IconSize size, + std::unique_ptr result, + const IconLoader::IconGroup& group) { + // Cache the bitmap. Watch out: |result| may be null, which indicates a + // failure. We assume that if we have an entry in |icon_cache_| it must not be + // null. + CacheKey key(group, size); + if (result) { + callback.Run(result.get()); + icon_cache_[key] = std::move(result); + } else { + callback.Run(nullptr); + icon_cache_.erase(key); } - gfx::Image* result = LookupIconFromGroup(group, rit->second.size); - if (!result) { - return false; - } - - return OnImageLoaded(loader, result, group); + group_cache_[file_path] = group; } -bool IconManager::OnImageLoaded(IconLoader* loader, - gfx::Image* result, - const IconGroupID& group) { - ClientRequests::iterator rit = requests_.find(loader); - - // Balances the AddRef() in LoadIcon(). - loader->Release(); - - // Look up our client state. - if (rit == requests_.end()) { - NOTREACHED(); - return false; // Return false to indicate result should be deleted. - } - - const ClientRequest& client_request = rit->second; - - // Cache the bitmap. Watch out: |result| may be NULL to indicate a current - // failure. We assume that if we have an entry in |icon_cache_| - // it must not be NULL. - CacheKey key(group, client_request.size); - IconMap::iterator it = icon_cache_.find(key); - if (it != icon_cache_.end()) { - if (!result) { - delete it->second; - icon_cache_.erase(it); - } else if (result != it->second) { - it->second->SwapRepresentations(result); - delete result; - result = it->second; - } - } else if (result) { - icon_cache_[key] = result; - } - - group_cache_[client_request.file_path] = group; - - // Inform our client that the request has completed. - client_request.callback.Run(result); - requests_.erase(rit); - - return true; // Indicates we took ownership of result. -} - -IconManager::CacheKey::CacheKey(const IconGroupID& group, +IconManager::CacheKey::CacheKey(const IconLoader::IconGroup& group, IconLoader::IconSize size) : group(group), size(size) {} -bool IconManager::CacheKey::operator<(const CacheKey& other) const { +bool IconManager::CacheKey::operator<(const CacheKey &other) const { return std::tie(group, size) < std::tie(other.group, other.size); } diff --git a/chromium_src/chrome/browser/icon_manager.h b/chromium_src/chrome/browser/icon_manager.h index dd6de82527..5cda41d469 100644 --- a/chromium_src/chrome/browser/icon_manager.h +++ b/chromium_src/chrome/browser/icon_manager.h @@ -34,7 +34,7 @@ // 2. An asynchronous icon load from a file on the file thread: // IconManager::LoadIcon() // -// When using the second (asychronous) method, callers must supply a callback +// When using the second (asynchronous) method, callers must supply a callback // which will be run once the icon has been extracted. The icon manager will // cache the results of the icon extraction so that subsequent lookups will be // fast. @@ -46,25 +46,29 @@ #define CHROME_BROWSER_ICON_MANAGER_H_ #include +#include #include "base/files/file_path.h" #include "base/macros.h" -#include "base/memory/singleton.h" +#include "base/memory/weak_ptr.h" +#include "base/task/cancelable_task_tracker.h" #include "chrome/browser/icon_loader.h" #include "ui/gfx/image/image.h" -class IconManager : public IconLoader::Delegate { +class IconManager { public: - static IconManager* GetInstance(); + IconManager(); + ~IconManager(); + // Synchronous call to examine the internal caches for the icon. Returns the - // icon if we have already loaded it, NULL if we don't have it and must load - // it via 'LoadIcon'. The returned bitmap is owned by the IconManager and must - // not be free'd by the caller. If the caller needs to modify the icon, it - // must make a copy and modify the copy. - gfx::Image* LookupIconFromFilepath(const base::FilePath& file_name, + // icon if we have already loaded it, or null if we don't have it and must + // load it via LoadIcon(). The returned bitmap is owned by the IconManager and + // must not be free'd by the caller. If the caller needs to modify the icon, + // it must make a copy and modify the copy. + gfx::Image* LookupIconFromFilepath(const base::FilePath& file_path, IconLoader::IconSize size); - typedef base::Callback IconRequestCallback; + using IconRequestCallback = base::Callback; // Asynchronous call to lookup and return the icon associated with file. The // work is done on the file thread, with the callbacks running on the thread @@ -74,47 +78,35 @@ class IconManager : public IconLoader::Delegate { // 1. This does *not* check the cache. // 2. The returned bitmap pointer is *not* owned by callback. So callback // should never keep it or delete it. - // 3. The gfx::Image pointer passed to the callback may be NULL if decoding + // 3. The gfx::Image pointer passed to the callback will be null if decoding // failed. - void LoadIcon(const base::FilePath& file_name, - IconLoader::IconSize size, - const IconRequestCallback& callback); - - // IconLoader::Delegate interface. - bool OnGroupLoaded(IconLoader* loader, const IconGroupID& group) override; - bool OnImageLoaded(IconLoader* loader, - gfx::Image* result, - const IconGroupID& group) override; + base::CancelableTaskTracker::TaskId LoadIcon( + const base::FilePath& file_name, + IconLoader::IconSize size, + const IconRequestCallback& callback, + base::CancelableTaskTracker* tracker); private: - friend struct base::DefaultSingletonTraits; - - IconManager(); - ~IconManager() override; + void OnIconLoaded(IconRequestCallback callback, + base::FilePath file_path, + IconLoader::IconSize size, + std::unique_ptr result, + const IconLoader::IconGroup& group); struct CacheKey { - CacheKey(const IconGroupID& group, IconLoader::IconSize size); + CacheKey(const IconLoader::IconGroup& group, IconLoader::IconSize size); // Used as a key in the map below, so we need this comparator. - bool operator<(const CacheKey& other) const; + bool operator<(const CacheKey &other) const; - IconGroupID group; + IconLoader::IconGroup group; IconLoader::IconSize size; }; - gfx::Image* LookupIconFromGroup(const IconGroupID& group, - IconLoader::IconSize size); + std::map group_cache_; + std::map> icon_cache_; - typedef std::map IconMap; - IconMap icon_cache_; - - typedef std::map GroupMap; - GroupMap group_cache_; - - // Asynchronous requests that have not yet been completed. - struct ClientRequest; - typedef std::map ClientRequests; - ClientRequests requests_; + base::WeakPtrFactory weak_factory_; DISALLOW_COPY_AND_ASSIGN(IconManager); }; From 5687f8b3b712d296e3fcd2f71a405383e33eb115 Mon Sep 17 00:00:00 2001 From: deepak1556 Date: Fri, 17 Feb 2017 14:03:46 +0530 Subject: [PATCH 180/925] Destroy icon manager after file thread is destroyed --- atom/browser/api/atom_api_app.cc | 17 ++++++++--------- atom/browser/api/atom_api_app.h | 5 +++-- chromium_src/chrome/browser/browser_process.cc | 10 +++++++++- chromium_src/chrome/browser/browser_process.h | 4 ++++ 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index b8cc49f576..56a11daf69 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -30,6 +30,7 @@ #include "base/path_service.h" #include "base/strings/string_util.h" #include "brightray/browser/brightray_paths.h" +#include "chrome/browser/browser_process.h" #include "chrome/browser/icon_manager.h" #include "chrome/common/chrome_paths.h" #include "content/public/browser/browser_accessibility_state.h" @@ -890,18 +891,16 @@ void App::GetFileIcon(const base::FilePath& path, return; } - if (!icon_manager_.get()) { - icon_manager_.reset(new IconManager()); - } - - gfx::Image* icon = icon_manager_->LookupIconFromFilepath(normalized_path, - icon_size); + auto icon_manager = g_browser_process->GetIconManager(); + gfx::Image* icon = + icon_manager->LookupIconFromFilepath(normalized_path, icon_size); if (icon) { callback.Run(v8::Null(isolate()), *icon); } else { - icon_manager_->LoadIcon(normalized_path, icon_size, - base::Bind(&OnIconDataAvailable, isolate(), - callback), &cancelable_task_tracker_); + icon_manager->LoadIcon( + normalized_path, icon_size, + base::Bind(&OnIconDataAvailable, isolate(), callback), + &cancelable_task_tracker_); } } diff --git a/atom/browser/api/atom_api_app.h b/atom/browser/api/atom_api_app.h index 0f7b138cb9..8b276f334d 100644 --- a/atom/browser/api/atom_api_app.h +++ b/atom/browser/api/atom_api_app.h @@ -130,8 +130,6 @@ class App : public AtomBrowserClient::Delegate, void DisableHardwareAcceleration(mate::Arguments* args); bool IsAccessibilitySupportEnabled(); Browser::LoginItemSettings GetLoginItemSettings(mate::Arguments* args); - base::CancelableTaskTracker cancelable_task_tracker_; - std::unique_ptr icon_manager_; #if defined(USE_NSS_CERTS) void ImportCertificate(const base::DictionaryValue& options, const net::CompletionCallback& callback); @@ -153,6 +151,9 @@ class App : public AtomBrowserClient::Delegate, std::unique_ptr certificate_manager_model_; #endif + // Tracks tasks requesting file icons. + base::CancelableTaskTracker cancelable_task_tracker_; + DISALLOW_COPY_AND_ASSIGN(App); }; diff --git a/chromium_src/chrome/browser/browser_process.cc b/chromium_src/chrome/browser/browser_process.cc index a38d55f871..d37478396e 100644 --- a/chromium_src/chrome/browser/browser_process.cc +++ b/chromium_src/chrome/browser/browser_process.cc @@ -4,13 +4,15 @@ #include "chrome/browser/browser_process.h" +#include "chrome/browser/icon_manager.h" #include "chrome/browser/printing/print_job_manager.h" #include "ui/base/l10n/l10n_util.h" BrowserProcess* g_browser_process = NULL; BrowserProcess::BrowserProcess() - : print_job_manager_(new printing::PrintJobManager) { + : print_job_manager_(new printing::PrintJobManager), + icon_manager_(new IconManager) { g_browser_process = this; } @@ -22,6 +24,12 @@ std::string BrowserProcess::GetApplicationLocale() { return l10n_util::GetApplicationLocale(""); } +IconManager* BrowserProcess::GetIconManager() { + if (!icon_manager_.get()) + icon_manager_.reset(new IconManager); + return icon_manager_.get(); +} + printing::PrintJobManager* BrowserProcess::print_job_manager() { return print_job_manager_.get(); } diff --git a/chromium_src/chrome/browser/browser_process.h b/chromium_src/chrome/browser/browser_process.h index 1459ca31a6..1c1156f452 100644 --- a/chromium_src/chrome/browser/browser_process.h +++ b/chromium_src/chrome/browser/browser_process.h @@ -15,6 +15,8 @@ #include "base/macros.h" +class IconManager; + namespace printing { class PrintJobManager; } @@ -27,11 +29,13 @@ class BrowserProcess { ~BrowserProcess(); std::string GetApplicationLocale(); + IconManager* GetIconManager(); printing::PrintJobManager* print_job_manager(); private: std::unique_ptr print_job_manager_; + std::unique_ptr icon_manager_; DISALLOW_COPY_AND_ASSIGN(BrowserProcess); }; From 529b84ff9cae0e12d0c4031a6d253e5d34fe94b3 Mon Sep 17 00:00:00 2001 From: DemoPark <106856363@qq.com> Date: Fri, 17 Feb 2017 17:28:55 +0800 Subject: [PATCH 181/925] add translations files for zh-CN add zh-CN translations file - clang-format.md add zh-CN translations file - styleguide.md update zh-CN translations file - README.md --- docs-translations/zh-CN/README.md | 4 +- .../zh-CN/development/clang-format.md | 27 +++ docs-translations/zh-CN/styleguide.md | 224 ++++++++++++++++++ 3 files changed, 253 insertions(+), 2 deletions(-) create mode 100644 docs-translations/zh-CN/development/clang-format.md create mode 100644 docs-translations/zh-CN/styleguide.md diff --git a/docs-translations/zh-CN/README.md b/docs-translations/zh-CN/README.md index fba7f3007f..c9de0bff13 100644 --- a/docs-translations/zh-CN/README.md +++ b/docs-translations/zh-CN/README.md @@ -85,7 +85,7 @@ ## 开发 * [代码规范](development/coding-style.md) -* [在 C++ 代码中使用 clang格式化工具](development/clang-format.md) 未翻译 +* [在 C++ 代码中使用 clang-format 工具](development/clang-format.md) * [源码目录结构](development/source-code-directory-structure.md) * [与 NW.js(原 node-webkit)在技术上的差异](development/atom-shell-vs-node-webkit.md) * [构建系统概览](development/build-system-overview.md) @@ -95,4 +95,4 @@ * [调试步骤 (macOS)](development/debug-instructions-macos.md) 未翻译 * [调试步骤 (Windows)](development/debug-instructions-windows.md) 未翻译 * [在调试中使用 Symbol Server](development/setting-up-symbol-server.md) -* [文档风格规范](styleguide.md) 未翻译 +* [文档风格指南](styleguide.md) diff --git a/docs-translations/zh-CN/development/clang-format.md b/docs-translations/zh-CN/development/clang-format.md new file mode 100644 index 0000000000..46e4bf97ec --- /dev/null +++ b/docs-translations/zh-CN/development/clang-format.md @@ -0,0 +1,27 @@ +# 在 C++ 代码中使用 clang-format 工具 + +[`clang-format`](http://clang.llvm.org/docs/ClangFormat.html) 是一个自动格式化 C/C++/Objective-C 代码的工具, 可以让开发人员不需要担心代码审查期间的样式问题. + +强烈建议在发起请求之前格式化已更改的 C++ 代码,这将节省您和审阅者的时间. + +你可以通过 `npm install -g clang-format` 安装 `clang-format` 和 `git-clang-format`. + +根据 Electron C++ 代码样式自动格式化文件, 只要运行 `clang-format -i path/to/electron/file.cc` 即可. 它应该能够在 macOS/Linux/Windows 上运行. + +格式化已更改代码的工作流: + +1. 在 Electron 存储库中更改代码. +2. 运行 `git add your_changed_file.cc`. +3. 运行 `git-clang-format`, 然后你将可能会看到修改后的 `your_changed_file.cc`, 这些修改是从 `clang-format` 生成的. +4. 运行 `git add your_changed_file.cc`, 并提交你的修改. +5. 现在准备好的分支推送请求已经被打开. + +如果你想在你最新的 git commit(HEAD)中格式化更改的代码, 你可以运行 `git-clang-format HEAD~1`. 通过 `git-clang-format -h` 可以获得更多详情. + +## 编辑器集成 + +您还可以将 `clang-format` 直接集成到您喜欢的编辑器中, +有关设置编辑器集成的更多指导,请参阅这些页面: + + * [Atom](https://atom.io/packages/clang-format) + * [Vim & Emacs](http://clang.llvm.org/docs/ClangFormat.html#vim-integration) diff --git a/docs-translations/zh-CN/styleguide.md b/docs-translations/zh-CN/styleguide.md new file mode 100644 index 0000000000..74f888ace0 --- /dev/null +++ b/docs-translations/zh-CN/styleguide.md @@ -0,0 +1,224 @@ +# Electron 文档风格指南 + +这里是一些编写 Electron 文档的指南. + +## 标题 + +* 每个页面顶部必须有一个单独的 `#` 级标题。 +* 同一页面中的章节必须有 `##` 级标题。 +* 子章节需要根据它们的嵌套深度增加标题中的 `#` 数量。 +* 页面标题中的所有单词首字母都必须大写,除了 “of” 和 “and” 之类的连接词。 +* 只有章节标题的第一个单词首字母必须大写. + +举一个 `Quick Start` 的例子: + +```markdown +# Quick Start + +... + +## Main process + +... + +## Renderer process + +... + +## Run your app + +... + +### Run as a distribution + +... + +### Manually downloaded Electron binary + +... +``` + +对于 API 参考, 可以例外于这些规则. + +## Markdown 规则 + +* 在代码块中使用 `bash` 而不是 `cmd`(由于语法高亮问题). +* 行长度应该控制在80列内. +* 列表嵌套不超出2级 (由于 Markdown 渲染问题). +* 所有的 `js` 和 `javascript` 代码块均被标记为 +[standard-markdown](http://npm.im/standard-markdown). + +## 用词选择 + +* 在描述结果时,使用 “will” 而不是 “would”。 +* 首选 "in the ___ process" 而不是 "on". + +## API 参考 + +以下规则仅适用于 API 的文档。 + +### 页面标题 + +每个页面必须使用由 `require('electron')` 返回的实际对象名称作为标题,例如 `BrowserWindow`,`autoUpdater` 和 `session`。 + +在页面标题下必须是以 `>` 开头的单行描述。 + +举一个 `session` 的例子: + +```markdown +# session + +> Manage browser sessions, cookies, cache, proxy settings, etc. +``` + +### 模块方法和事件 + +对于非类的模块,它们的方法和事件必须在 `## Methods` 和 `## Events` 章节中列出。 + +举一个 `autoUpdater` 的例子: + +```markdown +# autoUpdater + +## Events + +### Event: 'error' + +## Methods + +### `autoUpdater.setFeedURL(url[, requestHeaders])` +``` + +### 类 + +* API 类或作为模块一部分的类必须在 `## Class: TheClassName` 章节中列出. +* 一个页面可以有多个类. +* 构造函数必须用 `###` 级标题列出. +* [静态方法](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/static) 必须在 `### Static Methods` 章节中列出. +* [实例方法](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes#Prototype_methods) 必须在 `### Instance Methods` 章节中列出. +* 所有具有返回值的方法必须用 "Returns `[TYPE]` - Return description" 的形式描述. + * 如果该方法返回一个 `Object`,则可以使用冒号后跟换行符,然后使用与函数参数相同样式的属性的无序列表来指定其结构. +* 实例事件必须在 `### Instance Events` 章节中列出. +* 实例属性必须在 `### Instance Properties` 章节中列出. + * 实例属性必须以 "A [Property Type] ..." 开始描述. + +这里用 `Session` 和 `Cookies` 类作为例子: + +```markdown +# session + +## Methods + +### session.fromPartition(partition) + +## Properties + +### session.defaultSession + +## Class: Session + +### Instance Events + +#### Event: 'will-download' + +### Instance Methods + +#### `ses.getCacheSize(callback)` + +### Instance Properties + +#### `ses.cookies` + +## Class: Cookies + +### Instance Methods + +#### `cookies.get(filter, callback)` +``` + +### 方法 + +方法章节必须采用以下形式: + +```markdown +### `objectName.methodName(required[, optional]))` + +* `required` String - A parameter description. +* `optional` Integer (optional) - Another parameter description. + +... +``` + +标题可以是 `###` 级别或 `####` 级别,具体取决于它是模块还是类的方法。 + +对于模块,`objectName` 是模块的名称。 对于类,它必须是类的实例的名称,并且不能与模块的名称相同。 + +例如,`session` 模块下的 `Session` 类的方法必须使用 `ses` 作为 `objectName` 。 + +可选参数由围绕可选参数的方括号 `[]` 表示,并且如果此可选参数跟随另一个参数,则需要逗号: + +``` +required[, optional] +``` + +下面的方法是每个参数更加详细的信息。 参数的类型由常见类型表示: + +* [`String`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) +* [`Number`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number) +* [`Object`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) +* [`Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) +* [`Boolean`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean) +* 或自定义类型,就像 Electron 的 [`WebContent`](api/web-contents.md) + +如果参数或方法对某些平台是唯一的,那么这些平台将使用数据类型后面的空格分隔的斜体列表来表示。 值可以是 `macOS`,`Windows` 或 `Linux` + +```markdown +* `animate` Boolean (optional) _macOS_ _Windows_ - Animate the thing. +``` + +`Array` 类型的参数, 必须在指定数组下面的描述中描述可能包含的元素. + +`Function` 类型参数的描述应该清楚描述它是如何被调用的,并列出将被传递给它的参数的类型. + +### 事件 + +事件章节必须采用以下形式: + +```markdown +### Event: 'wake-up' + +Returns: + +* `time` String + +... +``` + +标题可以是 `###` 级别或 `####` 级别,具体取决于它是模块还是类的事件。 + +事件的参数遵循与方法相同的规则. + +### 属性 + +属性章节必须采用以下形式: + +```markdown +### session.defaultSession + +... +``` + +标题可以是 `###` 级别或 `####` 级别,具体取决于它是模块还是类的属性。 + +## 文档翻译 + +Electron 文档的翻译文件位于 `docs-translations` 目录中. + +如要添加另一个设定集(或部分设定集): + +* 创建以语言缩写命名的子目录。 +* 翻译文件。 +* 更新您的语言目录中的 `README.md` 文件以链接到已翻译的文件。 +* 在 Electron 的主 [README](https://github.com/electron/electron#documentation-translations) 上添加到翻译目录的链接。 + +请注意,`docs-translations` 下的文件只能包含已被翻译的文件,不应将原始英语文件复制到那里。 From e60601898de837b2214324a70a27d641e31ab404 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 17 Feb 2017 09:51:28 -0800 Subject: [PATCH 182/925] Upgrade native_mate --- vendor/native_mate | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/native_mate b/vendor/native_mate index b5e5de626c..ad0fd82566 160000 --- a/vendor/native_mate +++ b/vendor/native_mate @@ -1 +1 @@ -Subproject commit b5e5de626c6a57e44c7e6448d8bbaaac475d493c +Subproject commit ad0fd825663932ee3fa29ff935dfec99933bdd8c From 203a920c6458595f7ad70ccc37193cb617533958 Mon Sep 17 00:00:00 2001 From: deepak1556 Date: Thu, 16 Feb 2017 03:44:24 +0530 Subject: [PATCH 183/925] fix signature of zoom getter apis --- atom/browser/api/atom_api_web_contents.cc | 4 ++-- lib/browser/api/web-contents.js | 20 ++++++++++++++++++++ lib/browser/guest-view-manager.js | 18 ++++++++++-------- 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 3fc22438ab..89e7a7d65b 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -1672,9 +1672,9 @@ void WebContents::BuildPrototype(v8::Isolate* isolate, .SetMethod("getFrameRate", &WebContents::GetFrameRate) .SetMethod("invalidate", &WebContents::Invalidate) .SetMethod("setZoomLevel", &WebContents::SetZoomLevel) - .SetMethod("getZoomLevel", &WebContents::GetZoomLevel) + .SetMethod("_getZoomLevel", &WebContents::GetZoomLevel) .SetMethod("setZoomFactor", &WebContents::SetZoomFactor) - .SetMethod("getZoomFactor", &WebContents::GetZoomFactor) + .SetMethod("_getZoomFactor", &WebContents::GetZoomFactor) .SetMethod("getType", &WebContents::GetType) .SetMethod("getWebPreferences", &WebContents::GetWebPreferences) .SetMethod("getOwnerBrowserWindow", &WebContents::GetOwnerBrowserWindow) diff --git a/lib/browser/api/web-contents.js b/lib/browser/api/web-contents.js index c94bd768c0..c43a288239 100644 --- a/lib/browser/api/web-contents.js +++ b/lib/browser/api/web-contents.js @@ -206,6 +206,26 @@ WebContents.prototype.printToPDF = function (options, callback) { this._printToPDF(printingSetting, callback) } +WebContents.prototype.getZoomLevel = function (callback) { + if (typeof callback !== 'function') { + throw new Error('Must pass function as an argument') + } + process.nextTick(() => { + const zoomLevel = this._getZoomLevel() + callback(zoomLevel) + }) +} + +WebContents.prototype.getZoomFactor = function (callback) { + if (typeof callback !== 'function') { + throw new Error('Must pass function as an argument') + } + process.nextTick(() => { + const zoomFactor = this._getZoomFactor() + callback(zoomFactor) + }) +} + // Add JavaScript wrappers for WebContents class. WebContents.prototype._init = function () { // The navigation controller. diff --git a/lib/browser/guest-view-manager.js b/lib/browser/guest-view-manager.js index e61b2574ad..83a56827b2 100644 --- a/lib/browser/guest-view-manager.js +++ b/lib/browser/guest-view-manager.js @@ -184,7 +184,6 @@ const attachGuest = function (event, elementInstanceId, guestInstanceId, params) guestInstanceId: guestInstanceId, nodeIntegration: params.nodeintegration != null ? params.nodeintegration : false, plugins: params.plugins, - zoomFactor: embedder.getZoomFactor(), webSecurity: !params.disablewebsecurity, blinkFeatures: params.blinkfeatures, disableBlinkFeatures: params.disableblinkfeatures @@ -213,15 +212,18 @@ const attachGuest = function (event, elementInstanceId, guestInstanceId, params) return } - webViewManager.addGuest(guestInstanceId, elementInstanceId, embedder, guest, webPreferences) - guest.attachParams = params - embedderElementsMap[key] = guestInstanceId + embedder.getZoomFactor(function (zoomFactor) { + webPreferences.zoomFactor = zoomFactor + webViewManager.addGuest(guestInstanceId, elementInstanceId, embedder, guest, webPreferences) + guest.attachParams = params + embedderElementsMap[key] = guestInstanceId - guest.setEmbedder(embedder) - guestInstance.embedder = embedder - guestInstance.elementInstanceId = elementInstanceId + guest.setEmbedder(embedder) + guestInstance.embedder = embedder + guestInstance.elementInstanceId = elementInstanceId - watchEmbedder(embedder) + watchEmbedder(embedder) + }) } // Destroy an existing guest instance. From 763b9ab8d8aaa93087048db8a282edc5b2f4f102 Mon Sep 17 00:00:00 2001 From: deepak1556 Date: Thu, 16 Feb 2017 03:44:40 +0530 Subject: [PATCH 184/925] add zoom spec --- spec/api-web-contents-spec.js | 123 +++++++++++++++++++++++++++++++++- spec/static/main.js | 3 +- 2 files changed, 124 insertions(+), 2 deletions(-) diff --git a/spec/api-web-contents-spec.js b/spec/api-web-contents-spec.js index 0faaafbcda..c9538e8480 100644 --- a/spec/api-web-contents-spec.js +++ b/spec/api-web-contents-spec.js @@ -1,11 +1,12 @@ 'use strict' const assert = require('assert') +const http = require('http') const path = require('path') const {closeWindow} = require('./window-helpers') const {ipcRenderer, remote} = require('electron') -const {BrowserWindow, webContents, ipcMain} = remote +const {BrowserWindow, webContents, ipcMain, session} = remote const isCi = remote.getGlobal('isCi') @@ -322,4 +323,124 @@ describe('webContents module', function () { }) }) }) + + describe('zoom api', () => { + const zoomScheme = remote.getGlobal('zoomScheme') + const hostZoomMap = { + 'host1': 0.3, + 'host2': 0.7, + 'host3': 0.2 + } + + before((done) => { + let protocol = session.defaultSession.protocol + protocol.registerStringProtocol(zoomScheme, (request, callback) => { + let response = `` + callback({data: response, mimeType: 'text/html'}) + }, (error) => done(error)) + }) + + after((done) => { + let protocol = session.defaultSession.protocol + protocol.unregisterProtocol(zoomScheme, (error) => done(error)) + }) + + it('can set the correct zoom level', (done) => { + w.loadURL('about:blank') + w.webContents.on('did-finish-load', () => { + w.webContents.getZoomLevel((zoomLevel) => { + assert.equal(zoomLevel, 0.0) + w.webContents.setZoomLevel(0.5) + w.webContents.getZoomLevel((zoomLevel) => { + assert.equal(zoomLevel, 0.5) + w.webContents.setZoomLevel(0) + done() + }) + }) + }) + }) + + it('can persist zoom level across navigation', (done) => { + let finalNavigation = false + ipcMain.on('set-zoom', (e, host) => { + let zoomLevel = hostZoomMap[host] + if (!finalNavigation) { + w.webContents.setZoomLevel(zoomLevel) + } + e.sender.send(`${host}-zoom-set`) + }) + ipcMain.on('host1-zoom-level', (e, zoomLevel) => { + let expectedZoomLevel = hostZoomMap['host1'] + assert.equal(zoomLevel, expectedZoomLevel) + if (finalNavigation) { + done() + } else { + w.loadURL(`${zoomScheme}://host2`) + } + }) + ipcMain.once('host2-zoom-level', (e, zoomLevel) => { + let expectedZoomLevel = hostZoomMap['host2'] + assert.equal(zoomLevel, expectedZoomLevel) + finalNavigation = true + w.webContents.goBack() + }) + w.loadURL(`${zoomScheme}://host1`) + }) + + it('can propagate zoom level across same session', (done) => { + const w2 = new BrowserWindow({ + show: false + }) + w2.webContents.on('did-finish-load', () => { + w.webContents.getZoomLevel((zoomLevel1) => { + assert.equal(zoomLevel1, hostZoomMap['host3']) + w2.webContents.getZoomLevel((zoomLevel2) => { + assert.equal(zoomLevel1, zoomLevel2) + w2.setClosable(true) + w2.close() + done() + }) + }) + }) + w.webContents.on('did-finish-load', () => { + w.webContents.setZoomLevel(hostZoomMap['host3']) + w2.loadURL(`${zoomScheme}://host3`) + }) + w.loadURL(`${zoomScheme}://host3`) + }) + + it('can persist when it contains iframe', (done) => { + const server = http.createServer(function (req, res) { + setTimeout(() => { + res.end() + }, 2000) + }) + server.listen(0, '127.0.0.1', function () { + const url = 'http://127.0.0.1:' + server.address().port + const content = `` + w.webContents.on('did-frame-finish-load', (e, isMainFrame) => { + if (!isMainFrame) { + w.webContents.getZoomLevel((zoomLevel) => { + assert.equal(zoomLevel, 2.0) + w.webContents.setZoomLevel(0) + server.close() + done() + }) + } + }) + w.webContents.on('dom-ready', () => { + w.webContents.setZoomLevel(2.0) + }) + w.loadURL(`data:text/html,${content}`) + }) + }) + }) }) diff --git a/spec/static/main.js b/spec/static/main.js index 1eafd78503..c36e86aeae 100644 --- a/spec/static/main.js +++ b/spec/static/main.js @@ -93,7 +93,8 @@ if (global.isCi) { // Register app as standard scheme. global.standardScheme = 'app' -protocol.registerStandardSchemes([global.standardScheme], { secure: true }) +global.zoomScheme = 'zoom' +protocol.registerStandardSchemes([global.standardScheme, global.zoomScheme], { secure: true }) app.on('window-all-closed', function () { app.quit() From 71fd3e807caa40a43b9d453518f37f182021966c Mon Sep 17 00:00:00 2001 From: deepak1556 Date: Thu, 16 Feb 2017 04:03:47 +0530 Subject: [PATCH 185/925] use sync zoom api with guest view manager webpreferences --- lib/browser/guest-view-manager.js | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/lib/browser/guest-view-manager.js b/lib/browser/guest-view-manager.js index 83a56827b2..721da5acc4 100644 --- a/lib/browser/guest-view-manager.js +++ b/lib/browser/guest-view-manager.js @@ -184,6 +184,7 @@ const attachGuest = function (event, elementInstanceId, guestInstanceId, params) guestInstanceId: guestInstanceId, nodeIntegration: params.nodeintegration != null ? params.nodeintegration : false, plugins: params.plugins, + zoomFactor: embedder._getZoomFactor(), webSecurity: !params.disablewebsecurity, blinkFeatures: params.blinkfeatures, disableBlinkFeatures: params.disableblinkfeatures @@ -212,18 +213,15 @@ const attachGuest = function (event, elementInstanceId, guestInstanceId, params) return } - embedder.getZoomFactor(function (zoomFactor) { - webPreferences.zoomFactor = zoomFactor - webViewManager.addGuest(guestInstanceId, elementInstanceId, embedder, guest, webPreferences) - guest.attachParams = params - embedderElementsMap[key] = guestInstanceId + webViewManager.addGuest(guestInstanceId, elementInstanceId, embedder, guest, webPreferences) + guest.attachParams = params + embedderElementsMap[key] = guestInstanceId - guest.setEmbedder(embedder) - guestInstance.embedder = embedder - guestInstance.elementInstanceId = elementInstanceId + guest.setEmbedder(embedder) + guestInstance.embedder = embedder + guestInstance.elementInstanceId = elementInstanceId - watchEmbedder(embedder) - }) + watchEmbedder(embedder) } // Destroy an existing guest instance. From dfc2e6c4a8d42643aae09a0710fe52ca5d6ccd32 Mon Sep 17 00:00:00 2001 From: deepak1556 Date: Thu, 16 Feb 2017 15:26:33 +0530 Subject: [PATCH 186/925] add temporary zoom api spec --- spec/api-web-contents-spec.js | 84 ++++++++++++++++++++++++++ spec/fixtures/pages/webframe-zoom.html | 9 +++ 2 files changed, 93 insertions(+) create mode 100644 spec/fixtures/pages/webframe-zoom.html diff --git a/spec/api-web-contents-spec.js b/spec/api-web-contents-spec.js index c9538e8480..ea5f88d14e 100644 --- a/spec/api-web-contents-spec.js +++ b/spec/api-web-contents-spec.js @@ -417,6 +417,41 @@ describe('webContents module', function () { w.loadURL(`${zoomScheme}://host3`) }) + it('cannot propagate zoom level across different session', (done) => { + const w2 = new BrowserWindow({ + show: false, + webPreferences: { + partition: 'temp' + } + }) + let protocol = w2.webContents.session.protocol + protocol.registerStringProtocol(zoomScheme, (request, callback) => { + callback('hello') + }, (error) => { + if (error) return done(error) + w2.webContents.on('did-finish-load', () => { + w.webContents.getZoomLevel((zoomLevel1) => { + assert.equal(zoomLevel1, hostZoomMap['host3']) + w2.webContents.getZoomLevel((zoomLevel2) => { + assert.equal(zoomLevel2, 0) + assert.notEqual(zoomLevel1, zoomLevel2) + protocol.unregisterProtocol(zoomScheme, (error) => { + if (error) return done(error) + w2.setClosable(true) + w2.close() + done() + }) + }) + }) + }) + w.webContents.on('did-finish-load', () => { + w.webContents.setZoomLevel(hostZoomMap['host3']) + w2.loadURL(`${zoomScheme}://host3`) + }) + w.loadURL(`${zoomScheme}://host3`) + }) + }) + it('can persist when it contains iframe', (done) => { const server = http.createServer(function (req, res) { setTimeout(() => { @@ -442,5 +477,54 @@ describe('webContents module', function () { w.loadURL(`data:text/html,${content}`) }) }) + + it('cannot propagate when used with webframe', (done) => { + let finalZoomLevel = 0 + const w2 = new BrowserWindow({ + show: false + }) + w2.webContents.on('did-finish-load', () => { + w.webContents.getZoomLevel((zoomLevel1) => { + assert.equal(zoomLevel1, finalZoomLevel) + w2.webContents.getZoomLevel((zoomLevel2) => { + assert.equal(zoomLevel2, 0) + assert.notEqual(zoomLevel1, zoomLevel2) + w2.setClosable(true) + w2.close() + done() + }) + }) + }) + ipcMain.once('temporary-zoom-set', (e, zoomLevel) => { + w2.loadURL(`file://${fixtures}/pages/a.html`) + finalZoomLevel = zoomLevel + }) + w.loadURL(`file://${fixtures}/pages/webframe-zoom.html`) + }) + + it('cannot persist zoom level after navigation with webFrame', (done) => { + let initialNavigation = true + const source = ` + const {ipcRenderer, webFrame} = require('electron') + webFrame.setZoomLevel(0.6) + ipcRenderer.send('zoom-level-set', webFrame.getZoomLevel()) + ` + w.webContents.on('did-finish-load', () => { + if (initialNavigation) { + w.webContents.executeJavaScript(source, () => {}) + } else { + w.webContents.getZoomLevel((zoomLevel) => { + assert.equal(zoomLevel, 0) + done() + }) + } + }) + ipcMain.once('zoom-level-set', (e, zoomLevel) => { + assert.equal(zoomLevel, 0.6) + w.loadURL(`file://${fixtures}/pages/b.html`) + initialNavigation = false + }) + w.loadURL(`file://${fixtures}/pages/a.html`) + }) }) }) diff --git a/spec/fixtures/pages/webframe-zoom.html b/spec/fixtures/pages/webframe-zoom.html new file mode 100644 index 0000000000..81006aad64 --- /dev/null +++ b/spec/fixtures/pages/webframe-zoom.html @@ -0,0 +1,9 @@ + + + + + From c71b44048cd4859b4244b219d19b039c79d9f720 Mon Sep 17 00:00:00 2001 From: deepak1556 Date: Fri, 17 Feb 2017 10:29:52 +0530 Subject: [PATCH 187/925] address review comments --- spec/api-web-contents-spec.js | 44 +++++++++++++++++------------------ 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/spec/api-web-contents-spec.js b/spec/api-web-contents-spec.js index ea5f88d14e..d7a894cce4 100644 --- a/spec/api-web-contents-spec.js +++ b/spec/api-web-contents-spec.js @@ -327,29 +327,29 @@ describe('webContents module', function () { describe('zoom api', () => { const zoomScheme = remote.getGlobal('zoomScheme') const hostZoomMap = { - 'host1': 0.3, - 'host2': 0.7, - 'host3': 0.2 + host1: 0.3, + host2: 0.7, + host3: 0.2 } before((done) => { - let protocol = session.defaultSession.protocol + const protocol = session.defaultSession.protocol protocol.registerStringProtocol(zoomScheme, (request, callback) => { - let response = `` + ` callback({data: response, mimeType: 'text/html'}) }, (error) => done(error)) }) after((done) => { - let protocol = session.defaultSession.protocol + const protocol = session.defaultSession.protocol protocol.unregisterProtocol(zoomScheme, (error) => done(error)) }) @@ -371,14 +371,14 @@ describe('webContents module', function () { it('can persist zoom level across navigation', (done) => { let finalNavigation = false ipcMain.on('set-zoom', (e, host) => { - let zoomLevel = hostZoomMap[host] + const zoomLevel = hostZoomMap[host] if (!finalNavigation) { w.webContents.setZoomLevel(zoomLevel) } e.sender.send(`${host}-zoom-set`) }) ipcMain.on('host1-zoom-level', (e, zoomLevel) => { - let expectedZoomLevel = hostZoomMap['host1'] + const expectedZoomLevel = hostZoomMap.host1 assert.equal(zoomLevel, expectedZoomLevel) if (finalNavigation) { done() @@ -387,7 +387,7 @@ describe('webContents module', function () { } }) ipcMain.once('host2-zoom-level', (e, zoomLevel) => { - let expectedZoomLevel = hostZoomMap['host2'] + const expectedZoomLevel = hostZoomMap.host2 assert.equal(zoomLevel, expectedZoomLevel) finalNavigation = true w.webContents.goBack() @@ -401,7 +401,7 @@ describe('webContents module', function () { }) w2.webContents.on('did-finish-load', () => { w.webContents.getZoomLevel((zoomLevel1) => { - assert.equal(zoomLevel1, hostZoomMap['host3']) + assert.equal(zoomLevel1, hostZoomMap.host3) w2.webContents.getZoomLevel((zoomLevel2) => { assert.equal(zoomLevel1, zoomLevel2) w2.setClosable(true) @@ -411,7 +411,7 @@ describe('webContents module', function () { }) }) w.webContents.on('did-finish-load', () => { - w.webContents.setZoomLevel(hostZoomMap['host3']) + w.webContents.setZoomLevel(hostZoomMap.host3) w2.loadURL(`${zoomScheme}://host3`) }) w.loadURL(`${zoomScheme}://host3`) @@ -424,14 +424,14 @@ describe('webContents module', function () { partition: 'temp' } }) - let protocol = w2.webContents.session.protocol + const protocol = w2.webContents.session.protocol protocol.registerStringProtocol(zoomScheme, (request, callback) => { callback('hello') }, (error) => { if (error) return done(error) w2.webContents.on('did-finish-load', () => { w.webContents.getZoomLevel((zoomLevel1) => { - assert.equal(zoomLevel1, hostZoomMap['host3']) + assert.equal(zoomLevel1, hostZoomMap.host3) w2.webContents.getZoomLevel((zoomLevel2) => { assert.equal(zoomLevel2, 0) assert.notEqual(zoomLevel1, zoomLevel2) @@ -445,7 +445,7 @@ describe('webContents module', function () { }) }) w.webContents.on('did-finish-load', () => { - w.webContents.setZoomLevel(hostZoomMap['host3']) + w.webContents.setZoomLevel(hostZoomMap.host3) w2.loadURL(`${zoomScheme}://host3`) }) w.loadURL(`${zoomScheme}://host3`) @@ -456,7 +456,7 @@ describe('webContents module', function () { const server = http.createServer(function (req, res) { setTimeout(() => { res.end() - }, 2000) + }, 200) }) server.listen(0, '127.0.0.1', function () { const url = 'http://127.0.0.1:' + server.address().port From 403e7681c1a4eed4e3aaa7fc1753275761a919bd Mon Sep 17 00:00:00 2001 From: deepak1556 Date: Fri, 17 Feb 2017 21:18:30 +0530 Subject: [PATCH 188/925] add basic webview zoom specs --- lib/renderer/web-view/web-view.js | 8 +-- .../pages/webview-custom-zoom-level.html | 23 ++++++++ spec/fixtures/pages/zoom-factor.html | 2 +- spec/webview-spec.js | 52 +++++++++++++------ 4 files changed, 66 insertions(+), 19 deletions(-) create mode 100644 spec/fixtures/pages/webview-custom-zoom-level.html diff --git a/lib/renderer/web-view/web-view.js b/lib/renderer/web-view/web-view.js index c98b02d274..5e5a10cb00 100644 --- a/lib/renderer/web-view/web-view.js +++ b/lib/renderer/web-view/web-view.js @@ -374,7 +374,11 @@ var registerWebViewElement = function () { 'print', 'printToPDF', 'showDefinitionForSelection', - 'capturePage' + 'capturePage', + 'setZoomFactor', + 'setZoomLevel', + 'getZoomLevel', + 'getZoomFactor' ] const nonblockMethods = [ 'insertCSS', @@ -383,8 +387,6 @@ var registerWebViewElement = function () { 'sendInputEvent', 'setLayoutZoomLevelLimits', 'setVisualZoomLevelLimits', - 'setZoomFactor', - 'setZoomLevel', // TODO(kevinsawicki): Remove in 2.0, deprecate before then with warnings 'setZoomLevelLimits' ] diff --git a/spec/fixtures/pages/webview-custom-zoom-level.html b/spec/fixtures/pages/webview-custom-zoom-level.html new file mode 100644 index 0000000000..0598623a66 --- /dev/null +++ b/spec/fixtures/pages/webview-custom-zoom-level.html @@ -0,0 +1,23 @@ + + + + + + diff --git a/spec/fixtures/pages/zoom-factor.html b/spec/fixtures/pages/zoom-factor.html index b9f8f988ca..4c04e38f3a 100644 --- a/spec/fixtures/pages/zoom-factor.html +++ b/spec/fixtures/pages/zoom-factor.html @@ -2,7 +2,7 @@ diff --git a/spec/webview-spec.js b/spec/webview-spec.js index c6b69310a8..40e6a8bffd 100644 --- a/spec/webview-spec.js +++ b/spec/webview-spec.js @@ -1067,21 +1067,6 @@ describe(' tag', function () { }) }) - it('inherits the zoomFactor of the parent window', function (done) { - w = new BrowserWindow({ - show: false, - webPreferences: { - zoomFactor: 1.2 - } - }) - ipcMain.once('pong', function (event, zoomFactor, zoomLevel) { - assert.equal(zoomFactor, 1.2) - assert.equal(zoomLevel, 1) - done() - }) - w.loadURL('file://' + fixtures + '/pages/webview-zoom-factor.html') - }) - it('inherits the parent window visibility state and receives visibilitychange events', function (done) { w = new BrowserWindow({ show: false @@ -1540,4 +1525,41 @@ describe(' tag', function () { }) }) }) + + describe('zoom behavior', () => { + it('inherits the zoomFactor of the parent window', (done) => { + w = new BrowserWindow({ + show: false, + webPreferences: { + zoomFactor: 1.2 + } + }) + ipcMain.once('webview-zoom-level', (event, zoomFactor, zoomLevel) => { + assert.equal(zoomFactor, 1.2) + assert.equal(zoomLevel, 1) + done() + }) + w.loadURL(`file://${fixtures}/pages/webview-zoom-factor.html`) + }) + + it('inherits the zoomLevel of parent window on navigation', (done) => { + w = new BrowserWindow({ + show: false, + webPreferences: { + zoomFactor: 1.2 + } + }) + ipcMain.on('webview-zoom-level', (event, zoomLevel, zoomFactor, final) => { + if (!final) { + assert.equal(zoomFactor, 1.44) + assert.equal(zoomLevel, 2.0) + } else { + assert.equal(zoomFactor, 1.2) + assert.equal(zoomLevel, 1) + done() + } + }) + w.loadURL(`file://${fixtures}/pages/webview-custom-zoom-level.html`) + }) + }) }) From 0c022fdc3692fe563d3b79cb8759dab51b845d52 Mon Sep 17 00:00:00 2001 From: deepak1556 Date: Fri, 17 Feb 2017 23:24:24 +0530 Subject: [PATCH 189/925] set zoom changes for in page navigaitons --- atom/browser/web_contents_zoom_controller.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/atom/browser/web_contents_zoom_controller.cc b/atom/browser/web_contents_zoom_controller.cc index cb293705c9..1bd4f5a9e4 100644 --- a/atom/browser/web_contents_zoom_controller.cc +++ b/atom/browser/web_contents_zoom_controller.cc @@ -112,8 +112,7 @@ void WebContentsZoomController::DidFinishNavigation( return; } - if (!navigation_handle->IsSamePage()) - SetZoomFactorOnNavigationIfNeeded(navigation_handle->GetURL()); + SetZoomFactorOnNavigationIfNeeded(navigation_handle->GetURL()); } void WebContentsZoomController::WebContentsDestroyed() { From a57af31b99f6412b5441b6cd2dbd5e5855d8f165 Mon Sep 17 00:00:00 2001 From: deepak1556 Date: Fri, 17 Feb 2017 23:28:29 +0530 Subject: [PATCH 190/925] add zoom specs based on standard protocols --- spec/api-web-contents-spec.js | 6 +-- .../pages/webview-custom-zoom-level.html | 13 +++--- .../pages/webview-in-page-navigate.html | 32 +++++++++++++++ spec/fixtures/pages/zoom-factor.html | 2 +- spec/webview-spec.js | 40 ++++++++++++++++--- 5 files changed, 79 insertions(+), 14 deletions(-) create mode 100644 spec/fixtures/pages/webview-in-page-navigate.html diff --git a/spec/api-web-contents-spec.js b/spec/api-web-contents-spec.js index d7a894cce4..448d2556a1 100644 --- a/spec/api-web-contents-spec.js +++ b/spec/api-web-contents-spec.js @@ -496,7 +496,7 @@ describe('webContents module', function () { }) }) ipcMain.once('temporary-zoom-set', (e, zoomLevel) => { - w2.loadURL(`file://${fixtures}/pages/a.html`) + w2.loadURL(`file://${fixtures}/pages/c.html`) finalZoomLevel = zoomLevel }) w.loadURL(`file://${fixtures}/pages/webframe-zoom.html`) @@ -521,10 +521,10 @@ describe('webContents module', function () { }) ipcMain.once('zoom-level-set', (e, zoomLevel) => { assert.equal(zoomLevel, 0.6) - w.loadURL(`file://${fixtures}/pages/b.html`) + w.loadURL(`file://${fixtures}/pages/d.html`) initialNavigation = false }) - w.loadURL(`file://${fixtures}/pages/a.html`) + w.loadURL(`file://${fixtures}/pages/c.html`) }) }) }) diff --git a/spec/fixtures/pages/webview-custom-zoom-level.html b/spec/fixtures/pages/webview-custom-zoom-level.html index 0598623a66..24f246fdd2 100644 --- a/spec/fixtures/pages/webview-custom-zoom-level.html +++ b/spec/fixtures/pages/webview-custom-zoom-level.html @@ -1,20 +1,23 @@ - + + diff --git a/spec/fixtures/pages/zoom-factor.html b/spec/fixtures/pages/zoom-factor.html index 4c04e38f3a..c27b5ea495 100644 --- a/spec/fixtures/pages/zoom-factor.html +++ b/spec/fixtures/pages/zoom-factor.html @@ -2,7 +2,7 @@ diff --git a/spec/webview-spec.js b/spec/webview-spec.js index 40e6a8bffd..027a3a9520 100644 --- a/spec/webview-spec.js +++ b/spec/webview-spec.js @@ -1527,6 +1527,21 @@ describe(' tag', function () { }) describe('zoom behavior', () => { + const zoomScheme = remote.getGlobal('zoomScheme') + const webviewSession = session.fromPartition('webview-temp') + + before((done) => { + const protocol = webviewSession.protocol + protocol.registerStringProtocol(zoomScheme, (request, callback) => { + callback('hello') + }, (error) => done(error)) + }) + + after((done) => { + const protocol = webviewSession.protocol + protocol.unregisterProtocol(zoomScheme, (error) => done(error)) + }) + it('inherits the zoomFactor of the parent window', (done) => { w = new BrowserWindow({ show: false, @@ -1534,7 +1549,7 @@ describe(' tag', function () { zoomFactor: 1.2 } }) - ipcMain.once('webview-zoom-level', (event, zoomFactor, zoomLevel) => { + ipcMain.once('webview-parent-zoom-level', (event, zoomFactor, zoomLevel) => { assert.equal(zoomFactor, 1.2) assert.equal(zoomLevel, 1) done() @@ -1542,24 +1557,39 @@ describe(' tag', function () { w.loadURL(`file://${fixtures}/pages/webview-zoom-factor.html`) }) - it('inherits the zoomLevel of parent window on navigation', (done) => { + it('maintains zoom level on navigation', (done) => { w = new BrowserWindow({ show: false, webPreferences: { zoomFactor: 1.2 } }) - ipcMain.on('webview-zoom-level', (event, zoomLevel, zoomFactor, final) => { - if (!final) { + ipcMain.on('webview-zoom-level', (event, zoomLevel, zoomFactor, newHost, final) => { + if (!newHost) { assert.equal(zoomFactor, 1.44) assert.equal(zoomLevel, 2.0) } else { assert.equal(zoomFactor, 1.2) assert.equal(zoomLevel, 1) - done() } + if (final) done() }) w.loadURL(`file://${fixtures}/pages/webview-custom-zoom-level.html`) }) + + it('maintains zoom level when navigating within same page', (done) => { + w = new BrowserWindow({ + show: false, + webPreferences: { + zoomFactor: 1.2 + } + }) + ipcMain.on('webview-zoom-in-page', (event, zoomLevel, zoomFactor, final) => { + assert.equal(zoomFactor, 1.44) + assert.equal(zoomLevel, 2.0) + if (final) done() + }) + w.loadURL(`file://${fixtures}/pages/webview-in-page-navigate.html`) + }) }) }) From a1ede4a4b081b9cd71edcf602a4ec6259771b5e8 Mon Sep 17 00:00:00 2001 From: gellert Date: Sat, 18 Feb 2017 20:25:03 +0100 Subject: [PATCH 191/925] fixes os version check of vibrancy --- atom/browser/native_window_mac.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 8b1eff27c4..acbef9906a 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -1280,7 +1280,7 @@ void NativeWindowMac::SetAutoHideCursor(bool auto_hide) { } void NativeWindowMac::SetVibrancy(const std::string& type) { - if (!base::mac::IsOS10_10()) return; + if (!base::mac::IsAtLeastOS10_10()) return; NSView* vibrant_view = [window_ vibrantView]; From c78d7fc1259ec4c8dbca193cd2e5cdc845f4e4fb Mon Sep 17 00:00:00 2001 From: DemoPark <106856363@qq.com> Date: Sun, 19 Feb 2017 21:38:37 +0800 Subject: [PATCH 192/925] add translations files for zh-CN add zh-CN translations file - debug-instructions-windows.md add zh-CN translations file - debugging-instructions-macos.md update zh-CN translations file - README.md --- docs-translations/zh-CN/README.md | 4 +- .../development/debug-instructions-windows.md | 46 +++++++++ .../debugging-instructions-macos.md | 97 +++++++++++++++++++ 3 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 docs-translations/zh-CN/development/debug-instructions-windows.md create mode 100644 docs-translations/zh-CN/development/debugging-instructions-macos.md diff --git a/docs-translations/zh-CN/README.md b/docs-translations/zh-CN/README.md index c9de0bff13..8d1fd8500d 100644 --- a/docs-translations/zh-CN/README.md +++ b/docs-translations/zh-CN/README.md @@ -92,7 +92,7 @@ * [构建步骤(macOS)](development/build-instructions-osx.md) * [构建步骤(Windows)](development/build-instructions-windows.md) * [构建步骤(Linux)](development/build-instructions-linux.md) -* [调试步骤 (macOS)](development/debug-instructions-macos.md) 未翻译 -* [调试步骤 (Windows)](development/debug-instructions-windows.md) 未翻译 +* [调试步骤 (macOS)](development/debug-instructions-macos.md) +* [调试步骤 (Windows)](development/debug-instructions-windows.md) * [在调试中使用 Symbol Server](development/setting-up-symbol-server.md) * [文档风格指南](styleguide.md) diff --git a/docs-translations/zh-CN/development/debug-instructions-windows.md b/docs-translations/zh-CN/development/debug-instructions-windows.md new file mode 100644 index 0000000000..0afa03463c --- /dev/null +++ b/docs-translations/zh-CN/development/debug-instructions-windows.md @@ -0,0 +1,46 @@ +# 在 Windows 中调试 + +如果你在 Electron 中遇到问题或者引起崩溃,你认为它不是由你的JavaScript应用程序引起的,而是由 Electron 本身引起的。调试可能有点棘手,特别是对于不习惯 native/C++ 调试的开发人员。 然而,使用 Visual Studio,GitHub托管的 Electron Symbol Server 和Electron 源代码,在 Electron 的源代码中启用断点调试是相当容易的。 + +## 要求 + +* **Electron 的调试版本**: 最简单的方法是自己构建它,使用 [Windows 的构建说明](build-instructions-windows.md) 中列出的工具和先决条件要求。虽然你可以轻松地附加和调试 Electron,因为你可以直接下载它,你会发现,由于大量的优化,使调试实质上更加困难:调试器将无法向您显示所有变量的内容,并且执行路径可能看起来很奇怪,这是因为内联,尾部调用和其他编译器优化。 + +* **Visual Studio 与 C++ 工具**: Visual Studio 2013 和 Visual Studio 2015 的免费社区版本都可以使用。 安装之后, [配置 Visual Studio 使用 GitHub 的 Electron Symbol 服务器](setting-up-symbol-server.md). 它将使 Visual Studio 能够更好地理解 Electron 中发生的事情,从而更容易以人类可读的格式呈现变量。 + +* **ProcMon**: [免费的 SysInternals 工具][sys-internals] 允许您检查进程参数,文件句柄和注册表操作。 + +## 附加并调试 Electron + +要启动调试会话,请打开 PowerShell/CMD 并执行 Electron 的调试版本,使用应用程序作为参数打开。 + +```powershell +$ ./out/D/electron.exe ~/my-electron-app/ +``` + +### 设置断点 + +然后,打开 Visual Studio。 Electron 不是使用 Visual Studio 构建的,因此不包含项目文件 - 但是您可以打开源代码文件 "As File",这意味着 Visual Studio 将自己打开它们。 您仍然可以设置断点 - Visual Studio 将自动确定源代码与附加过程中运行的代码相匹配,并相应地中断。 + +相关的代码文件可以在 `./atom/` 以及 Brightray 中找到, 找到 `./vendor/brightray/browser` 和 `./vendor/brightray/common`. 如果是内核,你也可以直接调试 Chromium,这显然在 `chromium_src` 中。 + +### 附加 + +您可以将 Visual Studio 调试器附加到本地或远程计算机上正在运行的进程。 进程运行后,单击 调试 / 附加 到进程(或按下 `CTRL+ALT+P`)打开“附加到进程”对话框。 您可以使用此功能调试在本地或远程计算机上运行的应用程序,同时调试多个进程。 + +如果Electron在不同的用户帐户下运行,请选中“显示所有用户的进程”复选框。 请注意,根据您的应用程序打开的浏览器窗口数量,您将看到多个进程。 典型的单窗口应用程序将导致 Visual Studio 向您提供两个 `Electron.exe` 条目 - 一个用于主进程,一个用于渲染器进程。 因为列表只给你的名字,目前没有可靠的方法来弄清楚哪个是。 + +### 我应该附加哪个进程? + +在主进程内部执行的代码(即在主 JavaScript 文件中找到或最终运行的代码)以及使用远程代码调用的代码(`require('electron').remote`)将在主进程内运行,而其他代码将在其相应的渲染器进程内执行。 + +您可以在调试时附加到多个程序,但在任何时候只有一个程序在调试器中处于活动状态。 您可以在 `调试位置` 工具栏或 `进程窗口` 中设置活动程序。 + +## 使用 ProcMon 观察进程 + +虽然 Visual Studio 非常适合检查特定的代码路径,但 ProcMon 的优势在于它可以监视应用程序对操作系统的所有操作 - 捕获进程的文件,注册表,网络,进程和分析详细信息。 它试图记录发生的 **所有** 事件,并且可能是相当压倒性的,而且果你想了解你的应用程序对操作系统做什么和如何做,它则是一个很有价值的资源。 + +有关 ProcMon 的基本和高级调试功能的介绍,请查看Microsoft提供的[视频教程][procmon-instructions]。 + +[sys-internals]: https://technet.microsoft.com/en-us/sysinternals/processmonitor.aspx +[procmon-instructions]: https://channel9.msdn.com/shows/defrag-tools/defrag-tools-4-process-monitor diff --git a/docs-translations/zh-CN/development/debugging-instructions-macos.md b/docs-translations/zh-CN/development/debugging-instructions-macos.md new file mode 100644 index 0000000000..0a7929e57e --- /dev/null +++ b/docs-translations/zh-CN/development/debugging-instructions-macos.md @@ -0,0 +1,97 @@ +# 在 macOS 中调试 + +如果你在 Electron 中遇到问题或者引起崩溃,你认为它不是由你的JavaScript应用程序引起的,而是由 Electron 本身引起的。调试可能有点棘手,特别是对于不习惯 native/C++ 调试的开发人员。 然而,使用 lldb 和 Electron 源代码,可以在 Electron 的源代码中使用断点启用逐步调试,这是相当容易的。 + +## 要求 + +* **Electron 的调试版本**: 最简单的方法是自己构建它,使用 [macOS 的构建说明](build-instructions-osx.md) 中列出的工具和先决条件要求。 虽然你可以轻松地附加和调试 Electron,因为你可以直接下载它,你会发现,由于大量的优化,使调试实质上更加困难:调试器将无法向您显示所有变量的内容,并且执行路径可能看起来很奇怪,这是因为内联,尾部调用和其他编译器优化。 + +* **Xcode**: 除了 Xcode,还安装 Xcode 命令行工具。它们包括 LLDB,在 Mac OS X 的 Xcode 中的默认调试器。它支持在桌面和iOS设备和模拟器上调试 C,Objective-C 和 C++。 + +## 附加并调试 Electron + +要启动调试会话,打开命令行并启动 `lldb` ,并传递一个调试版本的 Electron 作为参数。 + +```bash +$ lldb ./out/D/Electron.app +(lldb) target create "./out/D/Electron.app" +Current executable set to './out/D/Electron.app' (x86_64). +``` + +### 设置断点 + +LLDB是一个强大的工具,支持进行多种策略的代码检查。 在这做一个基本的介绍,让我们假设你从 JavaScript 调用一个不正常的命令 - 所以你想打断该命令的 C++ 对应的 Electron 源。 + +相关的代码文件可以在 `./atom/` 以及 Brightray 中找到, 找到 `./vendor/brightray/browser` 和 `./vendor/brightray/common`. 如果是内核,你也可以直接调试 Chromium,这显然在 `chromium_src` 中。 + +让我们假设你想调试 `app.setName()`, 在 `browser.cc` 中定义为 `Browser::SetName()`. 使用 `breakpoint` 命令进行断点,指定文件和断点位置:: + +```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 +``` + +然后, 启动 Electron: + +```bash +(lldb) run +``` + +应用程式会立即暂停,因为 Electron 会在启动时设定应用程序名称: + +```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) +``` + +显示当前帧的参数和局部变量, 运行 `frame variable` (或 `fr v`), 这将显示你的应用程序当前设置名称为 “Electron”. + + +```bash +(lldb) frame variable +(atom::Browser *) this = 0x0000000108b14f20 +(const string &) name = "Electron": { + [...] +} +``` + +在当前选择的线程中执行源级单步执行, 执行 `step` (或 `s`). 这将带你进入 `name_override_.empty()`。 继续前进,步过,运行 `next` (或 `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_; +``` + +要完成此时的调试,运行 `process continue`。 你也可以继续,直到这个线程中的某一行被命中(`线程直到100`)。 此命令将在当前帧中运行线程,直到它到达此帧中的行100,或者如果它离开当前帧,则停止。 + +现在,如果你打开 Electron 的开发工具并调用 `setName`,你将再次命中断点。 + +### 进一步阅读 +LLDB是一个强大的工具,有一个庞大的文档。 要了解更多信息,请参考 Apple 的调试文档, 例如 [LLDB Command Structure Reference][lldb-command-structure] +或 [Using LLDB as a Standalone Debugger][lldb-standalone]. + +你也可以查看LLDB的 [manual and tutorial][lldb-tutorial], 这将解释更复杂的调试场景. + +[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 From 92092bed4114455f491f9994178ec6e8c65a452d Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 21 Feb 2017 08:49:50 -0800 Subject: [PATCH 193/925] Bump v1.6.1 --- atom/browser/resources/mac/Info.plist | 4 ++-- atom/browser/resources/win/atom.rc | 8 ++++---- atom/common/atom_version.h | 2 +- electron.gyp | 2 +- package.json | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/atom/browser/resources/mac/Info.plist b/atom/browser/resources/mac/Info.plist index e0655b6fa4..ed10c6fc37 100644 --- a/atom/browser/resources/mac/Info.plist +++ b/atom/browser/resources/mac/Info.plist @@ -17,9 +17,9 @@ CFBundleIconFile electron.icns CFBundleVersion - 1.6.0 + 1.6.1 CFBundleShortVersionString - 1.6.0 + 1.6.1 LSApplicationCategoryType public.app-category.developer-tools LSMinimumSystemVersion diff --git a/atom/browser/resources/win/atom.rc b/atom/browser/resources/win/atom.rc index 8202be5565..abda92004f 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,6,0,0 - PRODUCTVERSION 1,6,0,0 + FILEVERSION 1,6,1,0 + PRODUCTVERSION 1,6,1,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -74,12 +74,12 @@ BEGIN BEGIN VALUE "CompanyName", "GitHub, Inc." VALUE "FileDescription", "Electron" - VALUE "FileVersion", "1.6.0" + VALUE "FileVersion", "1.6.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.6.0" + VALUE "ProductVersion", "1.6.1" VALUE "SquirrelAwareVersion", "1" END END diff --git a/atom/common/atom_version.h b/atom/common/atom_version.h index 8a865c8cd2..9b824c0364 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 6 -#define ATOM_PATCH_VERSION 0 +#define ATOM_PATCH_VERSION 1 #define ATOM_VERSION_IS_RELEASE 1 diff --git a/electron.gyp b/electron.gyp index a657d2d1c5..9a31b0edba 100644 --- a/electron.gyp +++ b/electron.gyp @@ -4,7 +4,7 @@ 'product_name%': 'Electron', 'company_name%': 'GitHub, Inc', 'company_abbr%': 'github', - 'version%': '1.6.0', + 'version%': '1.6.1', 'js2c_input_dir': '<(SHARED_INTERMEDIATE_DIR)/js2c', }, 'includes': [ diff --git a/package.json b/package.json index b4f942ae2a..e880e8ce75 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "electron", - "version": "1.6.0", + "version": "1.6.1", "devDependencies": { "asar": "^0.11.0", "browserify": "^13.1.0", From e5d82edf5d529706f924ec8c9c579fda2de15b61 Mon Sep 17 00:00:00 2001 From: Lasse Jacobs Date: Tue, 21 Feb 2017 14:52:15 -0800 Subject: [PATCH 194/925] :memo: Add glossary.md to dutch translations part1 [ci skip] Add glossary.md with the first 8 topics. --- docs-translations/nl/glossary.md | 58 ++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 docs-translations/nl/glossary.md diff --git a/docs-translations/nl/glossary.md b/docs-translations/nl/glossary.md new file mode 100644 index 0000000000..3af61d4c5b --- /dev/null +++ b/docs-translations/nl/glossary.md @@ -0,0 +1,58 @@ +# Woordenlijst + +Deze pagina defineerd bepaalde terminologie die veel gebruikt wordt binnen Electron's ontwikkeling. + +### ASAR + +ASAR staat voor "Atom Shell Archive Format". Een [asar][asar] archief is een simpel `tar`-achtig formaat die bestanden samenvoegt in een enkel bestand. Electron kan er willekeurige bestanden uit lezen zonder het hele bestand uit te pakken. + +De ASAR-indeling is in de eerste plaats gemaakt om de prestaties op Windows te verbeteren ... TODO + +### Brightray + +[Brightray][brightray] is een statische bibliotheek die[libchromiumcontent] gemakkelijker in applicaties te gebruiken maakt. Het werd speciaal gemaakt voor Electron, maar kan ook worden gebruikt om Chromium's renderer in applicaties die niet op Electron gebaseerd zijn in te schakelen. + +Brightray is een laag-niveau afhankelijkheid van Electron, wat geen betrekking heeft op de meerderheid van Electron's gebruikers. + +### DMG + +Een Apple Disk Image is een pakket formaat gebruikt door macOS. DMG bestanden worden gebruikt voor het verspreiden van programma installeerders. [electron-builder] ondersteunt `dmg` als een bouw doelwit. + +### IPC + +IPC staat voor "Inter-Process Communicatie". Electron gebruikt IPC om geseralizeerde JSON berichten te sturen tussen het [hoofd] en het [renderer] proces. + +### libchromiumcontent + +Een enkele, gedeelde bibliotheek die de Chromium inhouds module en al zijn afhankelijkheden(e.g., Blink, [V8], etc.) bevat. + +### main process + +Het hoofd proces, vaak een bestand genoemd `main.js`, is het toeganspunt voor elke Electron applicatie. Het controleert de levensduur van de app, van open naar dicht. Het beheert ook de basis elementen zoals het menu, menubalk, etc. Het hoofd proces is verantwoordelijk voor het maken van elk nieuw renderer proces in de app. De volledige Node API is ingebouwd. +Elke app's hoofd proces is opgegeven in het `main` attribuut in de `package.json`. Dit is hoe `electron .` weet welk bestand het moet uitvoeren bij het opstarten. + +Zie ook: [process](#process), [renderer process](#renderer-process) + +### MAS + +Acroniem voor Apple's Mac App Store. Voor meer informatie over het indienen van uw app op de MAS, zie [Mac App Store Submission Guide]. + +### native modules + +Native modules (ook wel [addons] in Node.js) zijn modules geschreven in C of C++ die kunnen ingeladen worden in Node.js of Electron met de `require()` functie, en gebruikt worden net alsof ze een gewone Node.js module zijn. Ze worden voornamelijk gebruikt om een een interface te voorzien tussen JavaScript die in Node.js loopt en C/C++. Native Node modules zijn ondersteund door Electron, maar aangezien Electron zeer waarschijnlijk een andere versie van V8 gebruikt dan uit de Node binaire geinstalleerd op uw systeem, moet u de locatie van Electron headers handmatig opgeven bij het bouwen van native modules. + +Zie ook: [Using Native Node Modules]. + + +[addons]: https://nodejs.org/api/addons.html +[asar]: https://github.com/electron/asar +[autoUpdater]: api/auto-updater.md +[brightray]: https://github.com/electron/brightray +[electron-builder]: https://github.com/electron-userland/electron-builder +[libchromiumcontent]: #libchromiumcontent +[Mac App Store Submission Guide]: tutorials/mac-app-store-submission-guide.md +[main]: #main-process +[renderer]: #renderer-process +[Using Native Node Modules]: tutorial/using-native-node-modules.md +[userland]: #userland +[V8]: #v8 From c4280612c12bae2aad75dbb748af7c816388a1b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Herrmann?= Date: Wed, 22 Feb 2017 11:49:14 +0100 Subject: [PATCH 195/925] update setCertificateVerifyProc example setCertificateVerifyProc example should reflect new api --- docs/api/session.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/api/session.md b/docs/api/session.md index f04d45195f..aeda16d38b 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -274,8 +274,13 @@ verify proc. const {BrowserWindow} = require('electron') let win = new BrowserWindow() -win.webContents.session.setCertificateVerifyProc((hostname, cert, callback) => { - callback(hostname === 'github.com') +win.webContents.session.setCertificateVerifyProc((request, callback) => { + const {hostname} = request + if(hostname === 'github.com') { + callback(0) + } else { + callback(-2) + } }) ``` From 91bd95a0b2dd2e18d71b2557fb46970202539f4e Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 22 Feb 2017 08:11:21 -0800 Subject: [PATCH 196/925] Add space after if --- docs/api/session.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/session.md b/docs/api/session.md index aeda16d38b..029b18787c 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -276,7 +276,7 @@ let win = new BrowserWindow() win.webContents.session.setCertificateVerifyProc((request, callback) => { const {hostname} = request - if(hostname === 'github.com') { + if (hostname === 'github.com') { callback(0) } else { callback(-2) From 636ef0fd29b7351422732017ff6378c0ca55fc0e Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 15 Feb 2017 11:44:25 -0800 Subject: [PATCH 197/925] Add async menu.popup on macOS --- atom/browser/api/atom_api_menu_mac.h | 4 +++ atom/browser/api/atom_api_menu_mac.mm | 36 +++++++++++++++++++++++---- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/atom/browser/api/atom_api_menu_mac.h b/atom/browser/api/atom_api_menu_mac.h index d318b7bdc0..4539efb26c 100644 --- a/atom/browser/api/atom_api_menu_mac.h +++ b/atom/browser/api/atom_api_menu_mac.h @@ -20,6 +20,8 @@ class MenuMac : public Menu { MenuMac(v8::Isolate* isolate, v8::Local wrapper); void PopupAt(Window* window, int x, int y, int positioning_item) override; + void PopupOnUI(const base::WeakPtr& native_window, + int x, int y, int positioning_item); base::scoped_nsobject menu_controller_; @@ -28,6 +30,8 @@ class MenuMac : public Menu { static void SendActionToFirstResponder(const std::string& action); + base::WeakPtrFactory weak_factory_; + DISALLOW_COPY_AND_ASSIGN(MenuMac); }; diff --git a/atom/browser/api/atom_api_menu_mac.mm b/atom/browser/api/atom_api_menu_mac.mm index 9e901d1098..ed4dae372e 100644 --- a/atom/browser/api/atom_api_menu_mac.mm +++ b/atom/browser/api/atom_api_menu_mac.mm @@ -6,10 +6,12 @@ #include "atom/browser/native_window.h" #include "atom/browser/unresponsive_suppressor.h" +#include "base/mac/scoped_sending_event.h" #include "base/message_loop/message_loop.h" #include "base/strings/sys_string_conversions.h" #include "brightray/browser/inspectable_web_contents.h" #include "brightray/browser/inspectable_web_contents_view.h" +#include "content/public/browser/browser_thread.h" #include "content/public/browser/web_contents.h" #include "atom/common/node_includes.h" @@ -19,13 +21,24 @@ namespace atom { namespace api { MenuMac::MenuMac(v8::Isolate* isolate, v8::Local wrapper) - : Menu(isolate, wrapper) { + : Menu(isolate, wrapper), + weak_factory_(this) { } void MenuMac::PopupAt(Window* window, int x, int y, int positioning_item) { NativeWindow* native_window = window->window(); if (!native_window) return; + + content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, + base::Bind(&MenuMac::PopupOnUI, weak_factory_.GetWeakPtr(), + native_window->GetWeakPtr(), x, y, positioning_item)); +} + +void MenuMac::PopupOnUI(const base::WeakPtr& native_window, + int x, int y, int positioning_item) { + if (!native_window) + return; brightray::InspectableWebContents* web_contents = native_window->inspectable_web_contents(); if (!web_contents) @@ -69,11 +82,24 @@ void MenuMac::PopupAt(Window* window, int x, int y, int positioning_item) { if (rightmostMenuPoint > screenRight) position.x = position.x - [menu size].width; - // Don't emit unresponsive event when showing menu. - atom::UnresponsiveSuppressor suppressor; + { + // Make sure events can be pumped while the menu is up. + base::MessageLoop::ScopedNestableTaskAllower allow( + base::MessageLoop::current()); - // Show the menu. - [menu popUpMenuPositioningItem:item atLocation:position inView:view]; + // One of the events that could be pumped is |window.close()|. + // User-initiated event-tracking loops protect against this by + // setting flags in -[CrApplication sendEvent:], but since + // web-content menus are initiated by IPC message the setup has to + // be done manually. + base::mac::ScopedSendingEvent sendingEventScoper; + + // Don't emit unresponsive event when showing menu. + atom::UnresponsiveSuppressor suppressor; + + // Show the menu. + [menu popUpMenuPositioningItem:item atLocation:position inView:view]; + } } // static From b091d104f5a3b997210ebdb991652f938f576d2a Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 15 Feb 2017 12:44:39 -0800 Subject: [PATCH 198/925] Add async menu.popup on Windows/Linux --- atom/browser/api/atom_api_menu_views.cc | 17 ++++++++++++----- atom/browser/api/atom_api_menu_views.h | 6 ++++++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/atom/browser/api/atom_api_menu_views.cc b/atom/browser/api/atom_api_menu_views.cc index 8a72247c9d..7a5edb9621 100644 --- a/atom/browser/api/atom_api_menu_views.cc +++ b/atom/browser/api/atom_api_menu_views.cc @@ -8,14 +8,16 @@ #include "atom/browser/unresponsive_suppressor.h" #include "content/public/browser/render_widget_host_view.h" #include "ui/display/screen.h" -#include "ui/views/controls/menu/menu_runner.h" + +using views::MenuRunner; namespace atom { namespace api { MenuViews::MenuViews(v8::Isolate* isolate, v8::Local wrapper) - : Menu(isolate, wrapper) { + : Menu(isolate, wrapper), + weak_factory_(this) { } void MenuViews::PopupAt(Window* window, int x, int y, int positioning_item) { @@ -42,10 +44,11 @@ void MenuViews::PopupAt(Window* window, int x, int y, int positioning_item) { atom::UnresponsiveSuppressor suppressor; // Show the menu. - views::MenuRunner menu_runner( + menu_runner_.reset(new MenuRunner( model(), - views::MenuRunner::CONTEXT_MENU | views::MenuRunner::HAS_MNEMONICS); - ignore_result(menu_runner.RunMenuAt( + MenuRunner::CONTEXT_MENU | MenuRunner::HAS_MNEMONICS | MenuRunner::ASYNC, + base::Bind(&MenuViews::OnMenuClosed, weak_factory_.GetWeakPtr()))); + ignore_result(menu_runner_->RunMenuAt( static_cast(window->window())->widget(), NULL, gfx::Rect(location, gfx::Size()), @@ -53,6 +56,10 @@ void MenuViews::PopupAt(Window* window, int x, int y, int positioning_item) { ui::MENU_SOURCE_MOUSE)); } +void MenuViews::OnMenuClosed() { + menu_runner_.reset(); +} + // static mate::WrappableBase* Menu::New(mate::Arguments* args) { return new MenuViews(args->isolate(), args->GetThis()); diff --git a/atom/browser/api/atom_api_menu_views.h b/atom/browser/api/atom_api_menu_views.h index 1e7abd1372..c17dee3536 100644 --- a/atom/browser/api/atom_api_menu_views.h +++ b/atom/browser/api/atom_api_menu_views.h @@ -6,7 +6,9 @@ #define ATOM_BROWSER_API_ATOM_API_MENU_VIEWS_H_ #include "atom/browser/api/atom_api_menu.h" +#include "base/memory/weak_ptr.h" #include "ui/display/screen.h" +#include "ui/views/controls/menu/menu_runner.h" namespace atom { @@ -18,8 +20,12 @@ class MenuViews : public Menu { protected: void PopupAt(Window* window, int x, int y, int positioning_item) override; + void OnMenuClosed(); private: + std::unique_ptr menu_runner_; + base::WeakPtrFactory weak_factory_; + DISALLOW_COPY_AND_ASSIGN(MenuViews); }; From 4430927f98877f8b11d784ee7d7d9629466cff31 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 15 Feb 2017 15:57:36 -0800 Subject: [PATCH 199/925] Add async option to menu.popup --- atom/browser/api/atom_api_menu.h | 5 ++--- atom/browser/api/atom_api_menu_mac.h | 5 +++-- atom/browser/api/atom_api_menu_mac.mm | 26 ++++++++++++++++++-------- atom/browser/api/atom_api_menu_views.h | 3 ++- lib/browser/api/menu.js | 14 +++++++++++++- 5 files changed, 38 insertions(+), 15 deletions(-) diff --git a/atom/browser/api/atom_api_menu.h b/atom/browser/api/atom_api_menu.h index 97b6360049..772b2f3c08 100644 --- a/atom/browser/api/atom_api_menu.h +++ b/atom/browser/api/atom_api_menu.h @@ -53,9 +53,8 @@ class Menu : public mate::TrackableObject, void ExecuteCommand(int command_id, int event_flags) override; void MenuWillShow(ui::SimpleMenuModel* source) override; - virtual void PopupAt(Window* window, - int x = -1, int y = -1, - int positioning_item = 0) = 0; + virtual void PopupAt(Window* window, int x, int y, int positioning_item, + bool async) = 0; std::unique_ptr model_; Menu* parent_; diff --git a/atom/browser/api/atom_api_menu_mac.h b/atom/browser/api/atom_api_menu_mac.h index 4539efb26c..0c5c7f7f98 100644 --- a/atom/browser/api/atom_api_menu_mac.h +++ b/atom/browser/api/atom_api_menu_mac.h @@ -19,9 +19,10 @@ class MenuMac : public Menu { protected: MenuMac(v8::Isolate* isolate, v8::Local wrapper); - void PopupAt(Window* window, int x, int y, int positioning_item) override; + void PopupAt( + Window* window, int x, int y, int positioning_item, bool async) override; void PopupOnUI(const base::WeakPtr& native_window, - int x, int y, int positioning_item); + int x, int y, int positioning_item, bool async); base::scoped_nsobject menu_controller_; diff --git a/atom/browser/api/atom_api_menu_mac.mm b/atom/browser/api/atom_api_menu_mac.mm index ed4dae372e..84a6b8d9df 100644 --- a/atom/browser/api/atom_api_menu_mac.mm +++ b/atom/browser/api/atom_api_menu_mac.mm @@ -16,6 +16,8 @@ #include "atom/common/node_includes.h" +using content::BrowserThread; + namespace atom { namespace api { @@ -25,18 +27,23 @@ MenuMac::MenuMac(v8::Isolate* isolate, v8::Local wrapper) weak_factory_(this) { } -void MenuMac::PopupAt(Window* window, int x, int y, int positioning_item) { +void MenuMac::PopupAt( + Window* window, int x, int y, int positioning_item, bool async) { NativeWindow* native_window = window->window(); if (!native_window) return; - content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, - base::Bind(&MenuMac::PopupOnUI, weak_factory_.GetWeakPtr(), - native_window->GetWeakPtr(), x, y, positioning_item)); + auto popup = base::Bind(&MenuMac::PopupOnUI, weak_factory_.GetWeakPtr(), + native_window->GetWeakPtr(), x, y, positioning_item, + async); + if (async) + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, popup); + else + popup.Run(); } void MenuMac::PopupOnUI(const base::WeakPtr& native_window, - int x, int y, int positioning_item) { + int x, int y, int positioning_item, bool async) { if (!native_window) return; brightray::InspectableWebContents* web_contents = @@ -82,7 +89,8 @@ void MenuMac::PopupOnUI(const base::WeakPtr& native_window, if (rightmostMenuPoint > screenRight) position.x = position.x - [menu size].width; - { + + if (async) { // Make sure events can be pumped while the menu is up. base::MessageLoop::ScopedNestableTaskAllower allow( base::MessageLoop::current()); @@ -96,8 +104,10 @@ void MenuMac::PopupOnUI(const base::WeakPtr& native_window, // Don't emit unresponsive event when showing menu. atom::UnresponsiveSuppressor suppressor; - - // Show the menu. + [menu popUpMenuPositioningItem:item atLocation:position inView:view]; + } else { + // Don't emit unresponsive event when showing menu. + atom::UnresponsiveSuppressor suppressor; [menu popUpMenuPositioningItem:item atLocation:position inView:view]; } } diff --git a/atom/browser/api/atom_api_menu_views.h b/atom/browser/api/atom_api_menu_views.h index c17dee3536..5e5dc8e93d 100644 --- a/atom/browser/api/atom_api_menu_views.h +++ b/atom/browser/api/atom_api_menu_views.h @@ -19,7 +19,8 @@ class MenuViews : public Menu { MenuViews(v8::Isolate* isolate, v8::Local wrapper); protected: - void PopupAt(Window* window, int x, int y, int positioning_item) override; + void PopupAt( + Window* window, int x, int y, int positioning_item, bool async) override; void OnMenuClosed(); private: diff --git a/lib/browser/api/menu.js b/lib/browser/api/menu.js index 167e9a744e..e4fda6d5b0 100644 --- a/lib/browser/api/menu.js +++ b/lib/browser/api/menu.js @@ -144,6 +144,9 @@ Menu.prototype._init = function () { } Menu.prototype.popup = function (window, x, y, positioningItem) { + let asyncPopup = false + + // menu.popup(x, y, positioningItem) if (typeof window !== 'object' || window.constructor !== BrowserWindow) { // Shift. positioningItem = y @@ -152,6 +155,15 @@ Menu.prototype.popup = function (window, x, y, positioningItem) { window = BrowserWindow.getFocusedWindow() } + // menu.popup(window, {}) + if (typeof x === 'object') { + const options = x + x = options.x + y = options.y + positioningItem = options.positioningItem + asyncPopup = options.async + } + // Default to showing under mouse location. if (typeof x !== 'number') x = -1 if (typeof y !== 'number') y = -1 @@ -159,7 +171,7 @@ Menu.prototype.popup = function (window, x, y, positioningItem) { // Default to not highlighting any item. if (typeof positioningItem !== 'number') positioningItem = -1 - this.popupAt(window, x, y, positioningItem) + this.popupAt(window, x, y, positioningItem, asyncPopup) } Menu.prototype.append = function (item) { From 66b6b4f1cb5f8c73b81561284262a24d059ea86a Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 15 Feb 2017 16:07:46 -0800 Subject: [PATCH 200/925] Map async option to MenuRunner::ASYNC flag --- atom/browser/api/atom_api_menu_views.cc | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/atom/browser/api/atom_api_menu_views.cc b/atom/browser/api/atom_api_menu_views.cc index 7a5edb9621..3be2a17a27 100644 --- a/atom/browser/api/atom_api_menu_views.cc +++ b/atom/browser/api/atom_api_menu_views.cc @@ -20,7 +20,8 @@ MenuViews::MenuViews(v8::Isolate* isolate, v8::Local wrapper) weak_factory_(this) { } -void MenuViews::PopupAt(Window* window, int x, int y, int positioning_item) { +void MenuViews::PopupAt( + Window* window, int x, int y, int positioning_item, bool async) { NativeWindow* native_window = static_cast(window->window()); if (!native_window) return; @@ -40,13 +41,17 @@ void MenuViews::PopupAt(Window* window, int x, int y, int positioning_item) { location = gfx::Point(origin.x() + x, origin.y() + y); } + int flags = MenuRunner::CONTEXT_MENU | MenuRunner::HAS_MNEMONICS; + if (async) + flags |= MenuRunner::ASYNC; + // Don't emit unresponsive event when showing menu. atom::UnresponsiveSuppressor suppressor; // Show the menu. menu_runner_.reset(new MenuRunner( model(), - MenuRunner::CONTEXT_MENU | MenuRunner::HAS_MNEMONICS | MenuRunner::ASYNC, + flags, base::Bind(&MenuViews::OnMenuClosed, weak_factory_.GetWeakPtr()))); ignore_result(menu_runner_->RunMenuAt( static_cast(window->window())->widget(), From d686cf77e985b129035a91b7ada79226d298b544 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 16 Feb 2017 08:34:39 -0800 Subject: [PATCH 201/925] Update menu.popup docs to take options object --- docs/api/menu.md | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/docs/api/menu.md b/docs/api/menu.md index b9bdc36064..6969c4457e 100644 --- a/docs/api/menu.md +++ b/docs/api/menu.md @@ -52,14 +52,20 @@ will become properties of the constructed menu items. The `menu` object has the following instance methods: -#### `menu.popup([browserWindow, x, y, positioningItem])` +#### `menu.popup([browserWindow, options])` -* `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. +* `browserWindow` BrowserWindow (optional) - Default is + `BrowserWindow.getFocusedWindow()`. +* `options` Object (optional) + * `x` Number (optional) - Default is the current mouse cursor position. + * `y` Number (**required** if `x` is used) - Default is the current mouse + cursor position. + * `async` Boolean (optional) - Set to `true` to have this method return + immediately called, `false` to return after the menu has been selected + or closed. Defaults to `false`. + * `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`. From 55f90b4a4bcdfdb7f02482ec95fb32d9250a76ac Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 16 Feb 2017 08:35:01 -0800 Subject: [PATCH 202/925] Add new menu.popup signature to planned breaking changes --- docs/tutorial/planned-breaking-changes.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/tutorial/planned-breaking-changes.md b/docs/tutorial/planned-breaking-changes.md index 57505d0b66..b5771feea5 100644 --- a/docs/tutorial/planned-breaking-changes.md +++ b/docs/tutorial/planned-breaking-changes.md @@ -57,6 +57,15 @@ crashReporter.start({ }) ``` +## `menu` + +```js +// Deprecated +menu.popup(browserWindow, 100, 200, 2) +// Replace with +menu.popup(browserWindow, {x: 100, y: 200, positioningItem: 2}) +``` + ## `nativeImage` ```js From 947556a23f91449244d25044b2d7e8c7275c4fe8 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 16 Feb 2017 09:14:31 -0800 Subject: [PATCH 203/925] Move MenuItem spec into root describe --- spec/api-menu-spec.js | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/spec/api-menu-spec.js b/spec/api-menu-spec.js index bf5c9871cd..0c3cb50e4d 100644 --- a/spec/api-menu-spec.js +++ b/spec/api-menu-spec.js @@ -429,26 +429,26 @@ describe('menu module', function () { assert.equal(item.getDefaultRoleAccelerator(), process.platform === 'win32' ? 'Control+Y' : 'Shift+CommandOrControl+Z') }) }) -}) -describe('MenuItem with custom properties in constructor', function () { - it('preserves the custom properties', function () { - var template = [{ - label: 'menu 1', - customProp: 'foo', - submenu: [] - }] + describe('MenuItem with custom properties in constructor', function () { + it('preserves the custom properties', function () { + var template = [{ + label: 'menu 1', + customProp: 'foo', + submenu: [] + }] - var menu = Menu.buildFromTemplate(template) - menu.items[0].submenu.append(new MenuItem({ - label: 'item 1', - customProp: 'bar', - overrideProperty: 'oops not allowed' - })) + var menu = Menu.buildFromTemplate(template) + menu.items[0].submenu.append(new MenuItem({ + label: 'item 1', + customProp: 'bar', + overrideProperty: 'oops not allowed' + })) - assert.equal(menu.items[0].customProp, 'foo') - assert.equal(menu.items[0].submenu.items[0].label, 'item 1') - assert.equal(menu.items[0].submenu.items[0].customProp, 'bar') - assert.equal(typeof menu.items[0].submenu.items[0].overrideProperty, 'function') + assert.equal(menu.items[0].customProp, 'foo') + assert.equal(menu.items[0].submenu.items[0].label, 'item 1') + assert.equal(menu.items[0].submenu.items[0].customProp, 'bar') + assert.equal(typeof menu.items[0].submenu.items[0].overrideProperty, 'function') + }) }) }) From 6a023dc4fe43605a8a03453f62005d0ce7fcdb22 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 16 Feb 2017 10:58:02 -0800 Subject: [PATCH 204/925] Add Menu.closePopup API on macOS --- atom/browser/api/atom_api_menu.cc | 3 ++- atom/browser/api/atom_api_menu.h | 1 + atom/browser/api/atom_api_menu_mac.h | 10 +++++++-- atom/browser/api/atom_api_menu_mac.mm | 21 +++++++++++++------ atom/browser/api/atom_api_window.h | 3 ++- atom/browser/ui/cocoa/atom_menu_controller.h | 4 ++++ atom/browser/ui/cocoa/atom_menu_controller.mm | 8 ++++++- lib/browser/api/menu.js | 9 ++++++++ 8 files changed, 48 insertions(+), 11 deletions(-) diff --git a/atom/browser/api/atom_api_menu.cc b/atom/browser/api/atom_api_menu.cc index 627ce601f5..da208b3659 100644 --- a/atom/browser/api/atom_api_menu.cc +++ b/atom/browser/api/atom_api_menu.cc @@ -176,7 +176,8 @@ void Menu::BuildPrototype(v8::Isolate* isolate, .SetMethod("isItemCheckedAt", &Menu::IsItemCheckedAt) .SetMethod("isEnabledAt", &Menu::IsEnabledAt) .SetMethod("isVisibleAt", &Menu::IsVisibleAt) - .SetMethod("popupAt", &Menu::PopupAt); + .SetMethod("popupAt", &Menu::PopupAt) + .SetMethod("closePopupAt", &Menu::ClosePopupAt); } } // namespace api diff --git a/atom/browser/api/atom_api_menu.h b/atom/browser/api/atom_api_menu.h index 772b2f3c08..59312e19a7 100644 --- a/atom/browser/api/atom_api_menu.h +++ b/atom/browser/api/atom_api_menu.h @@ -55,6 +55,7 @@ class Menu : public mate::TrackableObject, virtual void PopupAt(Window* window, int x, int y, int positioning_item, bool async) = 0; + virtual void ClosePopupAt(int32_t window_id) = 0; std::unique_ptr model_; Menu* parent_; diff --git a/atom/browser/api/atom_api_menu_mac.h b/atom/browser/api/atom_api_menu_mac.h index 0c5c7f7f98..7c41cd73e4 100644 --- a/atom/browser/api/atom_api_menu_mac.h +++ b/atom/browser/api/atom_api_menu_mac.h @@ -7,10 +7,13 @@ #include "atom/browser/api/atom_api_menu.h" +#include #include #import "atom/browser/ui/cocoa/atom_menu_controller.h" +using base::scoped_nsobject; + namespace atom { namespace api { @@ -22,9 +25,12 @@ class MenuMac : public Menu { void PopupAt( Window* window, int x, int y, int positioning_item, bool async) override; void PopupOnUI(const base::WeakPtr& native_window, - int x, int y, int positioning_item, bool async); + int32_t window_id, int x, int y, int positioning_item, + bool async); + void ClosePopupAt(int32_t window_id) override; - base::scoped_nsobject menu_controller_; + scoped_nsobject menu_controller_; + std::map> popup_controllers_; private: friend class Menu; diff --git a/atom/browser/api/atom_api_menu_mac.mm b/atom/browser/api/atom_api_menu_mac.mm index 84a6b8d9df..9741202dd7 100644 --- a/atom/browser/api/atom_api_menu_mac.mm +++ b/atom/browser/api/atom_api_menu_mac.mm @@ -34,8 +34,8 @@ void MenuMac::PopupAt( return; auto popup = base::Bind(&MenuMac::PopupOnUI, weak_factory_.GetWeakPtr(), - native_window->GetWeakPtr(), x, y, positioning_item, - async); + native_window->GetWeakPtr(), window->ID(), x, y, + positioning_item, async); if (async) BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, popup); else @@ -43,7 +43,8 @@ void MenuMac::PopupAt( } void MenuMac::PopupOnUI(const base::WeakPtr& native_window, - int x, int y, int positioning_item, bool async) { + int32_t window_id, int x, int y, int positioning_item, + bool async) { if (!native_window) return; brightray::InspectableWebContents* web_contents = @@ -51,10 +52,12 @@ void MenuMac::PopupOnUI(const base::WeakPtr& native_window, if (!web_contents) return; - base::scoped_nsobject menu_controller( - [[AtomMenuController alloc] initWithModel:model_.get() + auto close_callback = base::Bind(&MenuMac::ClosePopupAt, + weak_factory_.GetWeakPtr(), window_id); + popup_controllers_[window_id] = base::scoped_nsobject( + [[AtomMenuController alloc] initWithModel:model() useDefaultAccelerator:NO]); - NSMenu* menu = [menu_controller menu]; + NSMenu* menu = [popup_controllers_[window_id] menu]; NSView* view = web_contents->GetView()->GetNativeView(); // Which menu item to show. @@ -91,6 +94,7 @@ void MenuMac::PopupOnUI(const base::WeakPtr& native_window, if (async) { + [popup_controllers_[window_id] setCloseCallback:close_callback]; // Make sure events can be pumped while the menu is up. base::MessageLoop::ScopedNestableTaskAllower allow( base::MessageLoop::current()); @@ -109,9 +113,14 @@ void MenuMac::PopupOnUI(const base::WeakPtr& native_window, // Don't emit unresponsive event when showing menu. atom::UnresponsiveSuppressor suppressor; [menu popUpMenuPositioningItem:item atLocation:position inView:view]; + close_callback.Run(); } } +void MenuMac::ClosePopupAt(int32_t window_id) { + popup_controllers_.erase(window_id); +} + // static void Menu::SetApplicationMenu(Menu* base_menu) { MenuMac* menu = static_cast(base_menu); diff --git a/atom/browser/api/atom_api_window.h b/atom/browser/api/atom_api_window.h index 9908feedbc..80af78a5b0 100644 --- a/atom/browser/api/atom_api_window.h +++ b/atom/browser/api/atom_api_window.h @@ -51,6 +51,8 @@ class Window : public mate::TrackableObject, NativeWindow* window() const { return window_.get(); } + int32_t ID() const; + protected: Window(v8::Isolate* isolate, v8::Local wrapper, const mate::Dictionary& options); @@ -202,7 +204,6 @@ class Window : public mate::TrackableObject, void SetVibrancy(mate::Arguments* args); - int32_t ID() const; v8::Local WebContents(v8::Isolate* isolate); // Remove this window from parent window's |child_windows_|. diff --git a/atom/browser/ui/cocoa/atom_menu_controller.h b/atom/browser/ui/cocoa/atom_menu_controller.h index af0b276961..a230437f53 100644 --- a/atom/browser/ui/cocoa/atom_menu_controller.h +++ b/atom/browser/ui/cocoa/atom_menu_controller.h @@ -8,6 +8,7 @@ #import +#include "base/callback.h" #include "base/mac/scoped_nsobject.h" #include "base/strings/string16.h" @@ -27,6 +28,7 @@ class AtomMenuModel; base::scoped_nsobject menu_; BOOL isMenuOpen_; BOOL useDefaultAccelerator_; + base::Callback closeCallback; } @property(nonatomic, assign) atom::AtomMenuModel* model; @@ -35,6 +37,8 @@ class AtomMenuModel; // to the contents of the model after calling this will not be noticed. - (id)initWithModel:(atom::AtomMenuModel*)model useDefaultAccelerator:(BOOL)use; +- (void)setCloseCallback:(const base::Callback&)callback; + // Populate current NSMenu with |model|. - (void)populateWithModel:(atom::AtomMenuModel*)model; diff --git a/atom/browser/ui/cocoa/atom_menu_controller.mm b/atom/browser/ui/cocoa/atom_menu_controller.mm index 6bdaa4c780..2286b6b0ce 100644 --- a/atom/browser/ui/cocoa/atom_menu_controller.mm +++ b/atom/browser/ui/cocoa/atom_menu_controller.mm @@ -71,6 +71,10 @@ Role kRolesMap[] = { [super dealloc]; } +- (void)setCloseCallback:(const base::Callback&)callback { + closeCallback = callback; +} + - (void)populateWithModel:(atom::AtomMenuModel*)model { if (!menu_) return; @@ -265,8 +269,10 @@ Role kRolesMap[] = { - (void)menuDidClose:(NSMenu*)menu { if (isMenuOpen_) { - model_->MenuWillClose(); isMenuOpen_ = NO; + model_->MenuWillClose(); + if (!closeCallback.is_null()) + closeCallback.Run(); } } diff --git a/lib/browser/api/menu.js b/lib/browser/api/menu.js index e4fda6d5b0..30f3df70c5 100644 --- a/lib/browser/api/menu.js +++ b/lib/browser/api/menu.js @@ -174,6 +174,15 @@ Menu.prototype.popup = function (window, x, y, positioningItem) { this.popupAt(window, x, y, positioningItem, asyncPopup) } +Menu.prototype.closePopup = function (window) { + if (window == null || window.constructor !== BrowserWindow) { + window = BrowserWindow.getFocusedWindow() + } + if (window != null) { + this.closePopupAt(window.id) + } +} + Menu.prototype.append = function (item) { return this.insert(this.getItemCount(), item) } From 0a5ccdccb43653b2911fb5f4f9e2619a3d1b8e8b Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 16 Feb 2017 10:58:12 -0800 Subject: [PATCH 205/925] Add spec for async Menu.popup --- spec/api-menu-spec.js | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/spec/api-menu-spec.js b/spec/api-menu-spec.js index 0c3cb50e4d..176f1eafae 100644 --- a/spec/api-menu-spec.js +++ b/spec/api-menu-spec.js @@ -1,7 +1,8 @@ const assert = require('assert') const {ipcRenderer, remote} = require('electron') -const {Menu, MenuItem} = remote +const {BrowserWindow, Menu, MenuItem} = remote +const {closeWindow} = require('./window-helpers') describe('menu module', function () { describe('Menu.buildFromTemplate', function () { @@ -216,6 +217,30 @@ describe('menu module', function () { }) }) + describe('Menu.popup', function () { + let w = null + + afterEach(function () { + return closeWindow(w).then(function () { w = null }) + }) + + describe('when called with async: true', function () { + it('returns immediately', function () { + w = new BrowserWindow({show: false, width: 200, height: 200}) + const menu = Menu.buildFromTemplate([ + { + label: '1' + }, { + label: '2' + }, { + label: '3' + } + ]) + menu.popup(w, {x: 100, y: 100, async: true}) + menu.closePopup(w) + }) + }) + }) describe('MenuItem.click', function () { it('should be called with the item object passed', function (done) { var menu = Menu.buildFromTemplate([ From 91d1af053f996e288d8910c48230f5f22129ce72 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 16 Feb 2017 11:04:42 -0800 Subject: [PATCH 206/925] Implement Menu.closePopup on Windows/Linux --- atom/browser/api/atom_api_menu_views.cc | 15 ++++++--------- atom/browser/api/atom_api_menu_views.h | 6 ++++-- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/atom/browser/api/atom_api_menu_views.cc b/atom/browser/api/atom_api_menu_views.cc index 3be2a17a27..d3d4b2cbbc 100644 --- a/atom/browser/api/atom_api_menu_views.cc +++ b/atom/browser/api/atom_api_menu_views.cc @@ -49,11 +49,12 @@ void MenuViews::PopupAt( atom::UnresponsiveSuppressor suppressor; // Show the menu. - menu_runner_.reset(new MenuRunner( - model(), - flags, - base::Bind(&MenuViews::OnMenuClosed, weak_factory_.GetWeakPtr()))); - ignore_result(menu_runner_->RunMenuAt( + int32_t window_id = window->ID(); + auto close_callback = base::Bind( + &MenuViews::ClosePopupAt, weak_factory_.GetWeakPtr(), window_id); + menu_runners_[window_id] = std::unique_ptr(new MenuRunner( + model(), flags, close_callback)); + ignore_result(menu_runners_[window_id]->RunMenuAt( static_cast(window->window())->widget(), NULL, gfx::Rect(location, gfx::Size()), @@ -61,10 +62,6 @@ void MenuViews::PopupAt( ui::MENU_SOURCE_MOUSE)); } -void MenuViews::OnMenuClosed() { - menu_runner_.reset(); -} - // static mate::WrappableBase* Menu::New(mate::Arguments* args) { return new MenuViews(args->isolate(), args->GetThis()); diff --git a/atom/browser/api/atom_api_menu_views.h b/atom/browser/api/atom_api_menu_views.h index 5e5dc8e93d..2e4c84a03b 100644 --- a/atom/browser/api/atom_api_menu_views.h +++ b/atom/browser/api/atom_api_menu_views.h @@ -5,6 +5,8 @@ #ifndef ATOM_BROWSER_API_ATOM_API_MENU_VIEWS_H_ #define ATOM_BROWSER_API_ATOM_API_MENU_VIEWS_H_ +#include + #include "atom/browser/api/atom_api_menu.h" #include "base/memory/weak_ptr.h" #include "ui/display/screen.h" @@ -21,10 +23,10 @@ class MenuViews : public Menu { protected: void PopupAt( Window* window, int x, int y, int positioning_item, bool async) override; - void OnMenuClosed(); + void ClosePopupAt(int32_t window_id); private: - std::unique_ptr menu_runner_; + std::map> menu_runners_; base::WeakPtrFactory weak_factory_; DISALLOW_COPY_AND_ASSIGN(MenuViews); From ce5ac1b056229565ea52e7184c8e19c2d9d0afc8 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 16 Feb 2017 11:15:05 -0800 Subject: [PATCH 207/925] Implement ClosePopupAt on Windows/Linux --- atom/browser/api/atom_api_menu_views.cc | 4 ++++ atom/browser/api/atom_api_menu_views.h | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/atom/browser/api/atom_api_menu_views.cc b/atom/browser/api/atom_api_menu_views.cc index d3d4b2cbbc..59fdac3949 100644 --- a/atom/browser/api/atom_api_menu_views.cc +++ b/atom/browser/api/atom_api_menu_views.cc @@ -62,6 +62,10 @@ void MenuViews::PopupAt( ui::MENU_SOURCE_MOUSE)); } +void MenuViews::ClosePopupAt(int32_t window_id) { + menu_runners_.erase(window_id); +} + // static mate::WrappableBase* Menu::New(mate::Arguments* args) { return new MenuViews(args->isolate(), args->GetThis()); diff --git a/atom/browser/api/atom_api_menu_views.h b/atom/browser/api/atom_api_menu_views.h index 2e4c84a03b..f59e9d5b89 100644 --- a/atom/browser/api/atom_api_menu_views.h +++ b/atom/browser/api/atom_api_menu_views.h @@ -23,7 +23,7 @@ class MenuViews : public Menu { protected: void PopupAt( Window* window, int x, int y, int positioning_item, bool async) override; - void ClosePopupAt(int32_t window_id); + void ClosePopupAt(int32_t window_id) override; private: std::map> menu_runners_; From 6c6506e5aa735a6dd81d5418a568605ddae2b069 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 16 Feb 2017 11:18:13 -0800 Subject: [PATCH 208/925] Document menu.closePopup --- docs/api/menu.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/api/menu.md b/docs/api/menu.md index 6969c4457e..466d226a16 100644 --- a/docs/api/menu.md +++ b/docs/api/menu.md @@ -54,8 +54,7 @@ The `menu` object has the following instance methods: #### `menu.popup([browserWindow, options])` -* `browserWindow` BrowserWindow (optional) - Default is - `BrowserWindow.getFocusedWindow()`. +* `browserWindow` BrowserWindow (optional) - Default is the focused window. * `options` Object (optional) * `x` Number (optional) - Default is the current mouse cursor position. * `y` Number (**required** if `x` is used) - Default is the current mouse @@ -69,6 +68,12 @@ The `menu` object has the following instance methods: Pops up this menu as a context menu in the `browserWindow`. +#### `menu.closePopup([browserWindow])` + +* `browserWindow` BrowserWindow (optional) - Default is the focused window. + +Closes the context menu in the `browserWindow`. + #### `menu.append(menuItem)` * `menuItem` MenuItem From 2006e22aa45d17b98251acece9c31b4c06abca78 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 17 Feb 2017 12:09:29 -0800 Subject: [PATCH 209/925] :art: --- atom/browser/api/atom_api_menu.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/atom/browser/api/atom_api_menu.h b/atom/browser/api/atom_api_menu.h index 59312e19a7..df50640b85 100644 --- a/atom/browser/api/atom_api_menu.h +++ b/atom/browser/api/atom_api_menu.h @@ -53,8 +53,8 @@ class Menu : public mate::TrackableObject, void ExecuteCommand(int command_id, int event_flags) override; void MenuWillShow(ui::SimpleMenuModel* source) override; - virtual void PopupAt(Window* window, int x, int y, int positioning_item, - bool async) = 0; + virtual void PopupAt( + Window* window, int x, int y, int positioning_item, bool async) = 0; virtual void ClosePopupAt(int32_t window_id) = 0; std::unique_ptr model_; From a8d1a7aed485485b355bf9bcb8b119f838953694 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 17 Feb 2017 12:16:29 -0800 Subject: [PATCH 210/925] Make variables private instead of protected --- atom/browser/api/atom_api_menu_mac.h | 8 +++++--- atom/browser/api/atom_api_menu_views.h | 2 ++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/atom/browser/api/atom_api_menu_mac.h b/atom/browser/api/atom_api_menu_mac.h index 7c41cd73e4..bc70e5b5ae 100644 --- a/atom/browser/api/atom_api_menu_mac.h +++ b/atom/browser/api/atom_api_menu_mac.h @@ -29,14 +29,16 @@ class MenuMac : public Menu { bool async); void ClosePopupAt(int32_t window_id) override; - scoped_nsobject menu_controller_; - std::map> popup_controllers_; - private: friend class Menu; static void SendActionToFirstResponder(const std::string& action); + scoped_nsobject menu_controller_; + + // window ID -> open context menu + std::map> popup_controllers_; + base::WeakPtrFactory weak_factory_; DISALLOW_COPY_AND_ASSIGN(MenuMac); diff --git a/atom/browser/api/atom_api_menu_views.h b/atom/browser/api/atom_api_menu_views.h index f59e9d5b89..a974f75bc7 100644 --- a/atom/browser/api/atom_api_menu_views.h +++ b/atom/browser/api/atom_api_menu_views.h @@ -26,7 +26,9 @@ class MenuViews : public Menu { void ClosePopupAt(int32_t window_id) override; private: + // window ID -> open context menu std::map> menu_runners_; + base::WeakPtrFactory weak_factory_; DISALLOW_COPY_AND_ASSIGN(MenuViews); From d0b07d5c368e74657056f6edc95a63e5337a2309 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 22 Feb 2017 10:49:25 -0800 Subject: [PATCH 211/925] Check that x is non-null --- lib/browser/api/menu.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/browser/api/menu.js b/lib/browser/api/menu.js index 30f3df70c5..14e87d3d74 100644 --- a/lib/browser/api/menu.js +++ b/lib/browser/api/menu.js @@ -156,7 +156,7 @@ Menu.prototype.popup = function (window, x, y, positioningItem) { } // menu.popup(window, {}) - if (typeof x === 'object') { + if (x != null && typeof x === 'object') { const options = x x = options.x y = options.y From 211bedf9104edf5cd19a0d879bba78de590ed6d2 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 22 Feb 2017 11:47:58 -0800 Subject: [PATCH 212/925] Invoke close callback after itemSelected runs --- atom/browser/ui/cocoa/atom_menu_controller.mm | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/atom/browser/ui/cocoa/atom_menu_controller.mm b/atom/browser/ui/cocoa/atom_menu_controller.mm index 2286b6b0ce..b3e293153f 100644 --- a/atom/browser/ui/cocoa/atom_menu_controller.mm +++ b/atom/browser/ui/cocoa/atom_menu_controller.mm @@ -12,9 +12,12 @@ #include "ui/base/accelerators/accelerator.h" #include "ui/base/accelerators/platform_accelerator_cocoa.h" #include "ui/base/l10n/l10n_util_mac.h" +#include "content/public/browser/browser_thread.h" #include "ui/events/cocoa/cocoa_event_utils.h" #include "ui/gfx/image/image.h" +using content::BrowserThread; + namespace { struct Role { @@ -271,8 +274,11 @@ Role kRolesMap[] = { if (isMenuOpen_) { isMenuOpen_ = NO; model_->MenuWillClose(); + + // Post async task so that itemSelected runs before the close callback + // deletes the controller from the map which deallocates it if (!closeCallback.is_null()) - closeCallback.Run(); + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, closeCallback); } } From 7c4529a01a09845b208919ff46a87416a772e934 Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Wed, 22 Feb 2017 16:42:34 -0800 Subject: [PATCH 213/925] clarify actions that cause app.activate event to be emitted --- docs/api/app.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/api/app.md b/docs/api/app.md index d34daeddde..77b1e5fa21 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -125,8 +125,10 @@ Returns: * `event` Event * `hasVisibleWindows` Boolean -Emitted when the application is activated, which usually happens when the user -clicks on the application's dock icon. +Emitted when the application is activated. Various actions can trigger +this event, such as launching the application for the first time, attempting +to re-launch the application when it's already running, or clicking on the +application's dock or taskbar icon. ### Event: 'continue-activity' _macOS_ From ea3b14f0bb49217c933a01af77bddd3f0db5d60e Mon Sep 17 00:00:00 2001 From: popod Date: Thu, 23 Feb 2017 21:28:36 +0100 Subject: [PATCH 214/925] update startDrag(item) doc add doc for `files` option --- docs/api/web-contents.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index cbf261e396..efd90d6ff0 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -1162,7 +1162,7 @@ End subscribing for frame presentation events. #### `contents.startDrag(item)` * `item` Object - * `file` String - The path to the file being dragged. +  * `file` String or `files` Array - The path(s) to the file(s) being dragged. * `icon` [NativeImage](native-image.md) - The image must be non-empty on macOS. From eb260c35937bb70924c18e15b8bfab9c43493d21 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 23 Feb 2017 13:03:22 -0800 Subject: [PATCH 215/925] Update parameter prefix --- docs/api/web-contents.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index efd90d6ff0..b35bbdee7d 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -1162,7 +1162,7 @@ End subscribing for frame presentation events. #### `contents.startDrag(item)` * `item` Object -  * `file` String or `files` Array - The path(s) to the file(s) being dragged. + * `file` String or `files` Array - The path(s) to the file(s) being dragged. * `icon` [NativeImage](native-image.md) - The image must be non-empty on macOS. From ff513f2a84225b1e9bffd1c372fd7eff926f3996 Mon Sep 17 00:00:00 2001 From: liusi Date: Fri, 24 Feb 2017 14:08:21 +0800 Subject: [PATCH 216/925] remove the call to download_item_->Remove() on cancel --- atom/browser/api/atom_api_download_item.cc | 12 ++++-------- atom/browser/api/atom_api_download_item.h | 1 - 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/atom/browser/api/atom_api_download_item.cc b/atom/browser/api/atom_api_download_item.cc index 80e37ba965..3e5932cad0 100644 --- a/atom/browser/api/atom_api_download_item.cc +++ b/atom/browser/api/atom_api_download_item.cc @@ -58,7 +58,7 @@ std::map> g_download_item_objects; DownloadItem::DownloadItem(v8::Isolate* isolate, content::DownloadItem* download_item) - : download_item_(download_item), done_emitted_(false) { + : download_item_(download_item) { download_item_->AddObserver(this); Init(isolate); AttachAsUserData(download_item); @@ -76,16 +76,13 @@ DownloadItem::~DownloadItem() { } void DownloadItem::OnDownloadUpdated(content::DownloadItem* item) { - if (!download_item_->IsDone()) { - Emit("updated", item->GetState()); - - } else if (!done_emitted_) { + if (download_item_->IsDone()) { Emit("done", item->GetState()); - done_emitted_ = true; - // Destroy the item once item is downloaded. base::ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, GetDestroyClosure()); + } else { + Emit("updated", item->GetState()); } } @@ -113,7 +110,6 @@ bool DownloadItem::CanResume() const { void DownloadItem::Cancel() { download_item_->Cancel(true); - download_item_->Remove(); } int64_t DownloadItem::GetReceivedBytes() const { diff --git a/atom/browser/api/atom_api_download_item.h b/atom/browser/api/atom_api_download_item.h index 8840a4cff4..fbc74b1c81 100644 --- a/atom/browser/api/atom_api_download_item.h +++ b/atom/browser/api/atom_api_download_item.h @@ -59,7 +59,6 @@ class DownloadItem : public mate::TrackableObject, private: base::FilePath save_path_; content::DownloadItem* download_item_; - bool done_emitted_; DISALLOW_COPY_AND_ASSIGN(DownloadItem); }; From a33ffd621f184916e76fce43100701264321e2c2 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 22 Feb 2017 15:58:46 -0800 Subject: [PATCH 217/925] Use callback dialog methods in RunFileChooser --- atom/browser/web_dialog_helper.cc | 116 ++++++++++++++++++++++++------ 1 file changed, 94 insertions(+), 22 deletions(-) diff --git a/atom/browser/web_dialog_helper.cc b/atom/browser/web_dialog_helper.cc index af7cc27674..94ff44d551 100644 --- a/atom/browser/web_dialog_helper.cc +++ b/atom/browser/web_dialog_helper.cc @@ -16,7 +16,9 @@ #include "base/strings/utf_string_conversions.h" #include "chrome/common/pref_names.h" #include "components/prefs/pref_service.h" +#include "content/public/browser/browser_thread.h" #include "content/public/browser/render_frame_host.h" +#include "content/public/browser/render_process_host.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/web_contents.h" #include "content/public/common/file_chooser_file_info.h" @@ -26,6 +28,94 @@ namespace { +class FileSelectHelper : public base::RefCountedThreadSafe< + FileSelectHelper, + content::BrowserThread::DeleteOnUIThread>, + public content::WebContentsObserver { + public: + FileSelectHelper(content::RenderFrameHost* render_frame_host, + const content::FileChooserParams::Mode& mode) + : render_frame_host_(render_frame_host), mode_(mode) { + auto web_contents = content::WebContents::FromRenderFrameHost( + render_frame_host); + content::WebContentsObserver::Observe(web_contents); + // Add ref that will be released when the dialog is completed + AddRef(); + } + + void ShowOpenDialog(const file_dialog::DialogSettings& settings) { + auto callback = base::Bind(&FileSelectHelper::OnOpenDialogDone, + base::Unretained(this)); + file_dialog::ShowOpenDialog(settings, callback); + } + + void ShowSaveDialog(const file_dialog::DialogSettings& settings) { + auto callback = base::Bind(&FileSelectHelper::OnSaveDialogDone, + base::Unretained(this)); + file_dialog::ShowSaveDialog(settings, callback); + } + + private: + void OnOpenDialogDone(bool result, const std::vector& paths) { + std::vector file_info; + if (result) { + for (auto& path : paths) { + content::FileChooserFileInfo info; + info.file_path = path; + info.display_name = path.BaseName().value(); + file_info.push_back(info); + } + + if (!paths.empty()) { + auto browser_context = static_cast( + render_frame_host_->GetProcess()->GetBrowserContext()); + browser_context->prefs()->SetFilePath(prefs::kSelectFileLastDirectory, + paths[0].DirName()); + } + } + OnFilesSelected(file_info); + } + + void OnSaveDialogDone(bool result, const base::FilePath& path) { + std::vector file_info; + if (result) { + content::FileChooserFileInfo info; + info.file_path = path; + info.display_name = path.BaseName().value(); + file_info.push_back(info); + } + OnFilesSelected(file_info); + } + + void OnFilesSelected( + const std::vector& file_info) { + if (render_frame_host_) + render_frame_host_->FilesSelectedInChooser(file_info, mode_); + Release(); + } + + // content::WebContentsObserver: + void RenderFrameHostChanged(content::RenderFrameHost* old_host, + content::RenderFrameHost* new_host) override { + if (old_host == render_frame_host_) + render_frame_host_ = nullptr; + } + + // content::WebContentsObserver: + void RenderFrameDeleted(content::RenderFrameHost* deleted_host) override { + if (deleted_host == render_frame_host_) + render_frame_host_ = nullptr; + } + + // content::WebContentsObserver: + void WebContentsDestroyed() override { + render_frame_host_ = nullptr; + } + + content::RenderFrameHost* render_frame_host_; + content::FileChooserParams::Mode mode_; +}; + file_dialog::Filters GetFileTypesFromAcceptType( const std::vector& accept_types) { file_dialog::Filters filters; @@ -87,15 +177,11 @@ void WebDialogHelper::RunFileChooser( settings.parent_window = window_; settings.title = base::UTF16ToUTF8(params.title); + scoped_refptr file_select_helper( + new FileSelectHelper(render_frame_host, params.mode)); if (params.mode == content::FileChooserParams::Save) { - base::FilePath path; settings.default_path = params.default_file_name; - if (file_dialog::ShowSaveDialog(settings, &path)) { - content::FileChooserFileInfo info; - info.file_path = path; - info.display_name = path.BaseName().value(); - result.push_back(info); - } + file_select_helper->ShowSaveDialog(settings); } else { int flags = file_dialog::FILE_DIALOG_CREATE_DIRECTORY; switch (params.mode) { @@ -111,27 +197,13 @@ void WebDialogHelper::RunFileChooser( NOTREACHED(); } - std::vector paths; AtomBrowserContext* browser_context = static_cast( window_->web_contents()->GetBrowserContext()); settings.default_path = browser_context->prefs()->GetFilePath( prefs::kSelectFileLastDirectory).Append(params.default_file_name); settings.properties = flags; - if (file_dialog::ShowOpenDialog(settings, &paths)) { - for (auto& path : paths) { - content::FileChooserFileInfo info; - info.file_path = path; - info.display_name = path.BaseName().value(); - result.push_back(info); - } - if (!paths.empty()) { - browser_context->prefs()->SetFilePath(prefs::kSelectFileLastDirectory, - paths[0].DirName()); - } - } + file_select_helper->ShowOpenDialog(settings); } - - render_frame_host->FilesSelectedInChooser(result, params.mode); } void WebDialogHelper::EnumerateDirectory(content::WebContents* web_contents, From 29f92bfb53a229a3c84aa6ff69f64d40c9699d9e Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 23 Feb 2017 08:38:29 -0800 Subject: [PATCH 218/925] Extend RefCounted and add private destructor --- atom/browser/web_dialog_helper.cc | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/atom/browser/web_dialog_helper.cc b/atom/browser/web_dialog_helper.cc index 94ff44d551..8ea212504c 100644 --- a/atom/browser/web_dialog_helper.cc +++ b/atom/browser/web_dialog_helper.cc @@ -28,9 +28,7 @@ namespace { -class FileSelectHelper : public base::RefCountedThreadSafe< - FileSelectHelper, - content::BrowserThread::DeleteOnUIThread>, +class FileSelectHelper : public base::RefCounted, public content::WebContentsObserver { public: FileSelectHelper(content::RenderFrameHost* render_frame_host, @@ -44,18 +42,20 @@ class FileSelectHelper : public base::RefCountedThreadSafe< } void ShowOpenDialog(const file_dialog::DialogSettings& settings) { - auto callback = base::Bind(&FileSelectHelper::OnOpenDialogDone, - base::Unretained(this)); + auto callback = base::Bind(&FileSelectHelper::OnOpenDialogDone, this); file_dialog::ShowOpenDialog(settings, callback); } void ShowSaveDialog(const file_dialog::DialogSettings& settings) { - auto callback = base::Bind(&FileSelectHelper::OnSaveDialogDone, - base::Unretained(this)); + auto callback = base::Bind(&FileSelectHelper::OnSaveDialogDone, this); file_dialog::ShowSaveDialog(settings, callback); } private: + friend class base::RefCounted; + + ~FileSelectHelper() {} + void OnOpenDialogDone(bool result, const std::vector& paths) { std::vector file_info; if (result) { From a62c2f9e2e276cde2f989d815600480c71894cf7 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 23 Feb 2017 08:52:48 -0800 Subject: [PATCH 219/925] Remove unused include --- atom/browser/web_dialog_helper.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/atom/browser/web_dialog_helper.cc b/atom/browser/web_dialog_helper.cc index 8ea212504c..d9e747ad80 100644 --- a/atom/browser/web_dialog_helper.cc +++ b/atom/browser/web_dialog_helper.cc @@ -16,7 +16,6 @@ #include "base/strings/utf_string_conversions.h" #include "chrome/common/pref_names.h" #include "components/prefs/pref_service.h" -#include "content/public/browser/browser_thread.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/render_view_host.h" From 6837ec8576df4846f6fe5a46d7ce11506c9870a6 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 24 Feb 2017 07:49:26 -0800 Subject: [PATCH 220/925] Check render frame host before getting context --- atom/browser/web_dialog_helper.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atom/browser/web_dialog_helper.cc b/atom/browser/web_dialog_helper.cc index d9e747ad80..3ca5ba8f50 100644 --- a/atom/browser/web_dialog_helper.cc +++ b/atom/browser/web_dialog_helper.cc @@ -65,7 +65,7 @@ class FileSelectHelper : public base::RefCounted, file_info.push_back(info); } - if (!paths.empty()) { + if (render_frame_host_ && !paths.empty()) { auto browser_context = static_cast( render_frame_host_->GetProcess()->GetBrowserContext()); browser_context->prefs()->SetFilePath(prefs::kSelectFileLastDirectory, From ef085a1f15e5f1a8f0f1e19fe3f04c4f0e3247ab Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 24 Feb 2017 07:56:29 -0800 Subject: [PATCH 221/925] Remove unneeded AddRef/Release calls handled by base::Bind --- atom/browser/web_dialog_helper.cc | 3 --- 1 file changed, 3 deletions(-) diff --git a/atom/browser/web_dialog_helper.cc b/atom/browser/web_dialog_helper.cc index 3ca5ba8f50..448fccd31f 100644 --- a/atom/browser/web_dialog_helper.cc +++ b/atom/browser/web_dialog_helper.cc @@ -36,8 +36,6 @@ class FileSelectHelper : public base::RefCounted, auto web_contents = content::WebContents::FromRenderFrameHost( render_frame_host); content::WebContentsObserver::Observe(web_contents); - // Add ref that will be released when the dialog is completed - AddRef(); } void ShowOpenDialog(const file_dialog::DialogSettings& settings) { @@ -90,7 +88,6 @@ class FileSelectHelper : public base::RefCounted, const std::vector& file_info) { if (render_frame_host_) render_frame_host_->FilesSelectedInChooser(file_info, mode_); - Release(); } // content::WebContentsObserver: From 3e05350d6b9ede5bde005f85e430d8345f8567d3 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 22 Feb 2017 12:52:43 -0800 Subject: [PATCH 222/925] Check that window is non-null before accessing constructor prop --- lib/browser/api/menu.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/browser/api/menu.js b/lib/browser/api/menu.js index 14e87d3d74..28f51ca6be 100644 --- a/lib/browser/api/menu.js +++ b/lib/browser/api/menu.js @@ -147,7 +147,7 @@ Menu.prototype.popup = function (window, x, y, positioningItem) { let asyncPopup = false // menu.popup(x, y, positioningItem) - if (typeof window !== 'object' || window.constructor !== BrowserWindow) { + if (window != null && (typeof window !== 'object' || window.constructor !== BrowserWindow)) { // Shift. positioningItem = y y = x From c951e253c6c025e8d104f30e37b0d5994bf19deb Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 22 Feb 2017 12:53:29 -0800 Subject: [PATCH 223/925] Default to focused window whenever param is null/undefined --- lib/browser/api/menu.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/browser/api/menu.js b/lib/browser/api/menu.js index 28f51ca6be..9615962ac6 100644 --- a/lib/browser/api/menu.js +++ b/lib/browser/api/menu.js @@ -152,7 +152,7 @@ Menu.prototype.popup = function (window, x, y, positioningItem) { positioningItem = y y = x x = window - window = BrowserWindow.getFocusedWindow() + window = null } // menu.popup(window, {}) @@ -164,6 +164,9 @@ Menu.prototype.popup = function (window, x, y, positioningItem) { asyncPopup = options.async } + // Default to showing in focused window. + if (window == null) window = BrowserWindow.getFocusedWindow() + // Default to showing under mouse location. if (typeof x !== 'number') x = -1 if (typeof y !== 'number') y = -1 From 739f3ed10207ea62eaaabb86d2ece9e32b4a9e9f Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 22 Feb 2017 12:59:26 -0800 Subject: [PATCH 224/925] Popup pepper menu in owner browser window --- lib/browser/api/web-contents.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/browser/api/web-contents.js b/lib/browser/api/web-contents.js index c43a288239..6c03800bcd 100644 --- a/lib/browser/api/web-contents.js +++ b/lib/browser/api/web-contents.js @@ -253,7 +253,7 @@ WebContents.prototype._init = function () { this.on('pepper-context-menu', function (event, params) { // Access Menu via electron.Menu to prevent circular require const menu = electron.Menu.buildFromTemplate(params.menu) - menu.popup(params.x, params.y) + menu.popup(event.sender.getOwnerBrowserWindow(), params.x, params.y) }) // The devtools requests the webContents to reload. From be16e75be28bfddbb545fddc86786f24c8d15fe5 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 22 Feb 2017 10:05:16 -0800 Subject: [PATCH 225/925] Support explicit cancelId on macOS --- atom/browser/ui/message_box_mac.mm | 17 +++++++++++++---- docs/api/dialog.md | 4 ++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/atom/browser/ui/message_box_mac.mm b/atom/browser/ui/message_box_mac.mm index 0d03eefc20..3550e47cf8 100644 --- a/atom/browser/ui/message_box_mac.mm +++ b/atom/browser/ui/message_box_mac.mm @@ -57,6 +57,7 @@ NSAlert* CreateNSAlert(NativeWindow* parent_window, MessageBoxType type, const std::vector& buttons, int default_id, + int cancel_id, const std::string& title, const std::string& message, const std::string& detail, @@ -89,7 +90,14 @@ NSAlert* CreateNSAlert(NativeWindow* parent_window, } NSArray* ns_buttons = [alert buttons]; - if (default_id >= 0 && default_id < static_cast([ns_buttons count])) { + int button_count = static_cast([ns_buttons count]); + + // Bind cancel id button to escape key if there is more than one button + if (button_count > 1 && cancel_id >= 0 && cancel_id < button_count) { + [[ns_buttons objectAtIndex:cancel_id] setKeyEquivalent:@"\e"]; + } + + if (default_id >= 0 && default_id < button_count) { // Focus the button at default_id if the user opted to do so. // The first button added gets set as the default selected. // So remove that default, and make the requested button the default. @@ -129,7 +137,8 @@ int ShowMessageBox(NativeWindow* parent_window, const std::string& detail, const gfx::ImageSkia& icon) { NSAlert* alert = CreateNSAlert(parent_window, type, buttons, default_id, - title, message, detail, "", false, icon); + cancel_id, title, message, detail, "", false, + icon); // Use runModal for synchronous alert without parent, since we don't have a // window to wait for. @@ -166,8 +175,8 @@ void ShowMessageBox(NativeWindow* parent_window, const gfx::ImageSkia& icon, const MessageBoxCallback& callback) { NSAlert* alert = - CreateNSAlert(parent_window, type, buttons, default_id, title, message, - detail, checkbox_label, checkbox_checked, icon); + CreateNSAlert(parent_window, type, buttons, default_id, cancel_id, title, + message, detail, checkbox_label, checkbox_checked, icon); ModalDelegate* delegate = [[ModalDelegate alloc] initWithCallback:callback andAlert:alert callEndModal:false]; diff --git a/docs/api/dialog.md b/docs/api/dialog.md index 60a184a1e1..dc1b665fed 100644 --- a/docs/api/dialog.md +++ b/docs/api/dialog.md @@ -141,8 +141,8 @@ will be passed via `callback(filename)` * `cancelId` Integer (optional) - The value will be returned when user cancels the dialog instead of clicking the buttons of the dialog. By default it is the index of the buttons that have "cancel" or "no" as label, or 0 if there is no such - buttons. On macOS and Windows the index of the "Cancel" button will always - be used as `cancelId` even if it is specified. + buttons. On Windows the index of the "Cancel" button will always be used + as `cancelId` even if it is specified. * `noLink` Boolean (optional) - On Windows Electron will try to figure out which one of the `buttons` are common buttons (like "Cancel" or "Yes"), and show the others as command links in the dialog. This can make the dialog appear in From e6fec57bd86274c5430dd2d7800fd3f58bb17b87 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 23 Feb 2017 11:26:37 -0800 Subject: [PATCH 226/925] Add failing spec for Buffer from preload required module --- spec/fixtures/module/preload-node-off-wrapper.js | 3 +++ spec/fixtures/module/preload-required-module.js | 5 +++++ spec/webview-spec.js | 12 +++++++++++- 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 spec/fixtures/module/preload-node-off-wrapper.js create mode 100644 spec/fixtures/module/preload-required-module.js diff --git a/spec/fixtures/module/preload-node-off-wrapper.js b/spec/fixtures/module/preload-node-off-wrapper.js new file mode 100644 index 0000000000..dbe1330adc --- /dev/null +++ b/spec/fixtures/module/preload-node-off-wrapper.js @@ -0,0 +1,3 @@ +setImmediate(function () { + require('./preload-required-module') +}) diff --git a/spec/fixtures/module/preload-required-module.js b/spec/fixtures/module/preload-required-module.js new file mode 100644 index 0000000000..fbe6f95dc0 --- /dev/null +++ b/spec/fixtures/module/preload-required-module.js @@ -0,0 +1,5 @@ +try { + console.log([typeof process, typeof setImmediate, typeof global, typeof Buffer].join(' ')) +} catch (e) { + console.log(e.message) +} diff --git a/spec/webview-spec.js b/spec/webview-spec.js index 027a3a9520..83cb0ee925 100644 --- a/spec/webview-spec.js +++ b/spec/webview-spec.js @@ -181,7 +181,7 @@ describe(' tag', function () { document.body.appendChild(webview) }) - it('preload script can still use "process" and "Buffer" in required modules when nodeintegration is off', function (done) { + it('preload script can still use "process" and "Buffer" when nodeintegration is off', function (done) { webview.addEventListener('console-message', function (e) { assert.equal(e.message, 'object undefined object function') done() @@ -191,6 +191,16 @@ describe(' tag', function () { document.body.appendChild(webview) }) + it('preload script can require modules that still use "process" and "Buffer"when nodeintegration is off', function (done) { + webview.addEventListener('console-message', function (e) { + assert.equal(e.message, 'object undefined object function') + done() + }) + webview.setAttribute('preload', fixtures + '/module/preload-node-off-wrapper.js') + webview.src = 'file://' + fixtures + '/api/blank.html' + document.body.appendChild(webview) + }) + it('receives ipc message in preload script', function (done) { var message = 'boom!' var listener = function (e) { From ad03ba73a4234a8e21587b847d5284ecdba4c56c Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 23 Feb 2017 11:30:07 -0800 Subject: [PATCH 227/925] Upgrade node for Buffer wrapper fix --- spec/webview-spec.js | 2 +- vendor/node | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/webview-spec.js b/spec/webview-spec.js index 83cb0ee925..c29456b17e 100644 --- a/spec/webview-spec.js +++ b/spec/webview-spec.js @@ -191,7 +191,7 @@ describe(' tag', function () { document.body.appendChild(webview) }) - it('preload script can require modules that still use "process" and "Buffer"when nodeintegration is off', function (done) { + it('preload script can require modules that still use "process" and "Buffer" when nodeintegration is off', function (done) { webview.addEventListener('console-message', function (e) { assert.equal(e.message, 'object undefined object function') done() diff --git a/vendor/node b/vendor/node index 9eeeb53aa6..0f84d972a1 160000 --- a/vendor/node +++ b/vendor/node @@ -1 +1 @@ -Subproject commit 9eeeb53aa697a54561ffc6c94f06b3ac5963f398 +Subproject commit 0f84d972a1b48b7da361f9717ff43349a7946abd From 6c40b1eead05f1b9a2b3b5068ebe56ea585dce2d Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 23 Feb 2017 11:35:28 -0800 Subject: [PATCH 228/925] Verify global.Buffer is undefined --- spec/fixtures/module/preload-required-module.js | 2 +- spec/webview-spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/fixtures/module/preload-required-module.js b/spec/fixtures/module/preload-required-module.js index fbe6f95dc0..1028d55cb0 100644 --- a/spec/fixtures/module/preload-required-module.js +++ b/spec/fixtures/module/preload-required-module.js @@ -1,5 +1,5 @@ try { - console.log([typeof process, typeof setImmediate, typeof global, typeof Buffer].join(' ')) + console.log([typeof process, typeof setImmediate, typeof global, typeof Buffer, typeof global.Buffer].join(' ')) } catch (e) { console.log(e.message) } diff --git a/spec/webview-spec.js b/spec/webview-spec.js index c29456b17e..93924e036c 100644 --- a/spec/webview-spec.js +++ b/spec/webview-spec.js @@ -193,7 +193,7 @@ describe(' tag', function () { it('preload script can require modules that still use "process" and "Buffer" when nodeintegration is off', function (done) { webview.addEventListener('console-message', function (e) { - assert.equal(e.message, 'object undefined object function') + assert.equal(e.message, 'object undefined object function undefined') done() }) webview.setAttribute('preload', fixtures + '/module/preload-node-off-wrapper.js') From cdc6b13fc808bee59e6bcdd4f02a6dbf8821a81b Mon Sep 17 00:00:00 2001 From: James Wheare Date: Fri, 24 Feb 2017 23:56:47 +0000 Subject: [PATCH 229/925] Clarify docs for dialog.showMessageBox cancelId --- docs/api/dialog.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/api/dialog.md b/docs/api/dialog.md index dc1b665fed..b96cbbe9aa 100644 --- a/docs/api/dialog.md +++ b/docs/api/dialog.md @@ -138,11 +138,10 @@ will be passed via `callback(filename)` * `checkboxChecked` Boolean (optional) - Initial checked state of the checkbox. `false` by default. * `icon` [NativeImage](native-image.md) (optional) - * `cancelId` Integer (optional) - The value will be returned when user cancels the dialog - instead of clicking the buttons of the dialog. By default it is the index - of the buttons that have "cancel" or "no" as label, or 0 if there is no such - buttons. On Windows the index of the "Cancel" button will always be used - as `cancelId` even if it is specified. + * `cancelId` Integer (optional) - The index of the button to be used to cancel the dialog, via + the `Esc` key. By default this is assigned to the first button with "cancel" or "no" as the + label. If no such labeled buttons exist and this option is not set, `0` will be used as the + return value or callback response. This option is ignored on Windows. * `noLink` Boolean (optional) - On Windows Electron will try to figure out which one of the `buttons` are common buttons (like "Cancel" or "Yes"), and show the others as command links in the dialog. This can make the dialog appear in From 32f44d79176c6ed3650939e4070313400b3d0496 Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Sat, 25 Feb 2017 18:59:13 -0800 Subject: [PATCH 230/925] add a guide to keyboard shortcuts --- docs/tutorial/shortcuts.md | 65 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 docs/tutorial/shortcuts.md diff --git a/docs/tutorial/shortcuts.md b/docs/tutorial/shortcuts.md new file mode 100644 index 0000000000..215a6d47eb --- /dev/null +++ b/docs/tutorial/shortcuts.md @@ -0,0 +1,65 @@ +# Keyboard Shortcuts + +> Configure local and global keyboard shortcuts + +## Global Shortcuts + +You can use the [globalShortcut] module to detect keyboard events even when +the application does not have keyboard focus. + +```js +const {app, globalShortcut} = require('electron') + +app.on('ready', () => { + globalShortcut.register('CommandOrControl+X', () => { + console.log('CommandOrControl+X is pressed') + }) +}) +``` + +## Local Shortcuts + +You can use the [Menu] module to configure keyboard shortcuts that will +be triggered only when the app is focused. To do so, specify an +[`accelerator`] property when creating a [MenuItem]. + +```js +const {Menu, MenuItem} = require('electron') +const menu = new Menu() + +menu.append(new MenuItem({ + label: 'Print', + accelerator: 'CmdOrCtrl+P' + click: () => { console.log('time to print stuff') } +})) +``` + +It's easy to configure different key combinations based on the user's operating system. + +```js +{ + accelerator: process.platform === 'darwin' ? 'Alt+Cmd+I' : 'Ctrl+Shift+I' +} +``` + + +## Local Shortcuts without a Menu + +If you want to configure a local keyboard shortcut to trigger an action that +_does not_ have a corresponding menu item, you can use the +[electron-localshortcut] npm module. + +If you want to handle keyboard shortcuts for a [BrowserWindow], you can use the `keyup` and `keydown` event listeners on the window object inside the renderer process. + +```js +window.addEventListener('keyup', doSomething, true) +``` + +Note the third parameter `true` which means the listener will always receive key presses before other listeners so they can't have `stopPropagation()` called on them. + +[Menu]: ../api/menu.md +[MenuItem]: ../api/menu-item.md +[globalShortcut]: ../api/global-shortcut.md +[`accelerator`]: ../api/accelerator.md +[electron-localshortcut]: http://ghub.io/electron-localshortcut +[BrowserWindow]: ../api/browser-window.md From 5c6407503232f05f8819df2bea9df00cba605069 Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Sat, 25 Feb 2017 19:07:09 -0800 Subject: [PATCH 231/925] add section for browser window events --- docs/tutorial/shortcuts.md | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/docs/tutorial/shortcuts.md b/docs/tutorial/shortcuts.md index 215a6d47eb..48caebd672 100644 --- a/docs/tutorial/shortcuts.md +++ b/docs/tutorial/shortcuts.md @@ -42,13 +42,12 @@ It's easy to configure different key combinations based on the user's operating } ``` - -## Local Shortcuts without a Menu - If you want to configure a local keyboard shortcut to trigger an action that _does not_ have a corresponding menu item, you can use the [electron-localshortcut] npm module. +## Shortcuts within a BrowserWindow + If you want to handle keyboard shortcuts for a [BrowserWindow], you can use the `keyup` and `keydown` event listeners on the window object inside the renderer process. ```js @@ -57,9 +56,39 @@ window.addEventListener('keyup', doSomething, true) Note the third parameter `true` which means the listener will always receive key presses before other listeners so they can't have `stopPropagation()` called on them. +If you don't want to do manual shortcut parsing there are libraries that do advanced key detection such as [mousetrap]. + +```js +Mousetrap.bind('4', function () { console.log('4')}) +Mousetrap.bind('?', function () { console.log('show shortcuts!')}) +Mousetrap.bind('esc', function () { console.log('escape')}, 'keyup') + +// combinations +Mousetrap.bind('command+shift+k', function () { console.log('command shift k')}) + +// map multiple combinations to the same callback +Mousetrap.bind(['command+k', 'ctrl+k'], function () { + console.log('command k or control k') + + // return false to prevent default browser behavior + // and stop event from bubbling + return false +}) + +// gmail style sequences +Mousetrap.bind('g i', function () { console.log('go to inbox')}) +Mousetrap.bind('* a', function () { console.log('select all')}) + +// konami code! +Mousetrap.bind('up up down down left right left right b a enter', function () { + console.log('konami code') +}) +``` + [Menu]: ../api/menu.md [MenuItem]: ../api/menu-item.md [globalShortcut]: ../api/global-shortcut.md [`accelerator`]: ../api/accelerator.md [electron-localshortcut]: http://ghub.io/electron-localshortcut [BrowserWindow]: ../api/browser-window.md +[mousetrap]: https://github.com/ccampbell/mousetrap From ba4a2d7c307cc5fe9e57aab898f6b619babc1f81 Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Sat, 25 Feb 2017 19:21:33 -0800 Subject: [PATCH 232/925] describe local shortcuts before global --- docs/tutorial/shortcuts.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/tutorial/shortcuts.md b/docs/tutorial/shortcuts.md index 48caebd672..131406a98e 100644 --- a/docs/tutorial/shortcuts.md +++ b/docs/tutorial/shortcuts.md @@ -2,21 +2,6 @@ > Configure local and global keyboard shortcuts -## Global Shortcuts - -You can use the [globalShortcut] module to detect keyboard events even when -the application does not have keyboard focus. - -```js -const {app, globalShortcut} = require('electron') - -app.on('ready', () => { - globalShortcut.register('CommandOrControl+X', () => { - console.log('CommandOrControl+X is pressed') - }) -}) -``` - ## Local Shortcuts You can use the [Menu] module to configure keyboard shortcuts that will @@ -46,6 +31,21 @@ If you want to configure a local keyboard shortcut to trigger an action that _does not_ have a corresponding menu item, you can use the [electron-localshortcut] npm module. +## Global Shortcuts + +You can use the [globalShortcut] module to detect keyboard events even when +the application does not have keyboard focus. + +```js +const {app, globalShortcut} = require('electron') + +app.on('ready', () => { + globalShortcut.register('CommandOrControl+X', () => { + console.log('CommandOrControl+X is pressed') + }) +}) +``` + ## Shortcuts within a BrowserWindow If you want to handle keyboard shortcuts for a [BrowserWindow], you can use the `keyup` and `keydown` event listeners on the window object inside the renderer process. From c10f1210bc9eb94f79730964081eb58a2a2a3933 Mon Sep 17 00:00:00 2001 From: Oden Date: Sun, 26 Feb 2017 20:30:26 -0800 Subject: [PATCH 233/925] :memo: Update link to Blink features. --- docs-translations/jp/api/webview-tag.md | 2 +- docs-translations/ko-KR/api/browser-window.md | 6 +++--- docs-translations/ko-KR/api/webview-tag.md | 6 +++--- docs-translations/zh-CN/api/browser-window.md | 2 +- docs-translations/zh-CN/api/webview-tag.md | 4 ++-- docs/api/browser-window.md | 8 ++++---- docs/api/webview-tag.md | 8 ++++---- 7 files changed, 18 insertions(+), 18 deletions(-) diff --git a/docs-translations/jp/api/webview-tag.md b/docs-translations/jp/api/webview-tag.md index 301c89a0b2..b305ef8619 100644 --- a/docs-translations/jp/api/webview-tag.md +++ b/docs-translations/jp/api/webview-tag.md @@ -741,4 +741,4 @@ DevToolsが閉じられた際に発生します。 DevToolsにフォーカスが当たった際 / 開かれた際に発生します。 -[blink-feature-string]: https://code.google.com/p/chromium/codesearch#chromium/src/out/Debug/gen/blink/platform/RuntimeEnabledFeatures.cpp&sq=package:chromium&type=cs&l=527 +[blink-feature-string]: https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.json5?l=62 diff --git a/docs-translations/ko-KR/api/browser-window.md b/docs-translations/ko-KR/api/browser-window.md index 3c94b87464..136d4a38b6 100644 --- a/docs-translations/ko-KR/api/browser-window.md +++ b/docs-translations/ko-KR/api/browser-window.md @@ -250,10 +250,10 @@ On Windows it is * `scrollBounce` Boolean - macOS에서 스크롤 튕기기 효과 (탄성 밴딩)를 활성화 합니다. 기본값은 `false`입니다. * `blinkFeatures` String - 활성화 할 `CSSVariables,KeyboardEventKey`같이 `,`로 - 구분된 기능 문자열들의 리스트입니다. [RuntimeEnabledFeatures.in][blink-feature-string] + 구분된 기능 문자열들의 리스트입니다. [RuntimeEnabledFeatures.json5][blink-feature-string] 파일에서 찾을 수 있습니다. * `disableBlinkFeatures` String - 비활성화 할 `CSSVariables,KeyboardEventKey`같이 - `,`로 구분된 기능 문자열들의 리스트입니다. [RuntimeEnabledFeatures.in][blink-feature-string] + `,`로 구분된 기능 문자열들의 리스트입니다. [RuntimeEnabledFeatures.json5][blink-feature-string] 파일에서 찾을 수 있습니다. * `defaultFontFamily` Object - font-family의 기본 폰트를 지정합니다. * `standard` String - 기본값 `Times New Roman`. @@ -1174,6 +1174,6 @@ Returns `BrowserWindow` - 부모 윈도우. Returns `BrowserWindow[]` - 모든 자식 윈도우. -[blink-feature-string]: https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.in +[blink-feature-string]: https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.json5?l=62 [window-levels]: https://developer.apple.com/reference/appkit/nswindow/1664726-window_levels [quick-look]: https://en.wikipedia.org/wiki/Quick_Look diff --git a/docs-translations/ko-KR/api/webview-tag.md b/docs-translations/ko-KR/api/webview-tag.md index b40dc0605b..962738d0ae 100644 --- a/docs-translations/ko-KR/api/webview-tag.md +++ b/docs-translations/ko-KR/api/webview-tag.md @@ -209,7 +209,7 @@ API를 사용할 수 있습니다. 이를 지정하면 내부에서 로우레벨 ``` 활성화할 blink 기능을 지정한 `,`로 구분된 문자열의 리스트입니다. 지원하는 기능 -문자열의 전체 목록은 [RuntimeEnabledFeatures.in][blink-feature-string] 파일에서 +문자열의 전체 목록은 [RuntimeEnabledFeatures.json5][blink-feature-string] 파일에서 찾을 수 있습니다. ### `disableblinkfeatures` @@ -219,7 +219,7 @@ API를 사용할 수 있습니다. 이를 지정하면 내부에서 로우레벨 ``` 비활성화할 blink 기능을 지정한 `,`로 구분된 문자열의 리스트입니다. 지원하는 기능 -문자열의 전체 목록은 [RuntimeEnabledFeatures.in][blink-feature-string] 파일에서 +문자열의 전체 목록은 [RuntimeEnabledFeatures.json5][blink-feature-string] 파일에서 찾을 수 있습니다. ### `guestinstance` @@ -864,4 +864,4 @@ Returns: 개발자 도구가 포커스되거나 열렸을 때 발생하는 이벤트입니다. -[blink-feature-string]: https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.in +[blink-feature-string]: https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.json5?l=62 diff --git a/docs-translations/zh-CN/api/browser-window.md b/docs-translations/zh-CN/api/browser-window.md index 2487133c4f..45fff19a78 100644 --- a/docs-translations/zh-CN/api/browser-window.md +++ b/docs-translations/zh-CN/api/browser-window.md @@ -839,7 +839,7 @@ windows上句柄类型为 `HWND` ,macOS `NSView*` , Linux `Window`. 忽略窗口的所有鼠标事件. -[blink-feature-string]: https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.in +[blink-feature-string]: https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.json5?l=62 [quick-look]: https://en.wikipedia.org/wiki/Quick_Look [vibrancy-docs]: https://developer.apple.com/reference/appkit/nsvisualeffectview?language=objc [window-levels]: https://developer.apple.com/reference/appkit/nswindow/1664726-window_levels diff --git a/docs-translations/zh-CN/api/webview-tag.md b/docs-translations/zh-CN/api/webview-tag.md index ca824fce4e..8eb8eb0939 100644 --- a/docs-translations/zh-CN/api/webview-tag.md +++ b/docs-translations/zh-CN/api/webview-tag.md @@ -193,7 +193,7 @@ CSS通过 `flex` 布局设置 `width` 和 `height`,并允许元素缩小到0px 指定要禁用的 blink 特征的字符串列表,用 `,` 分隔。 支持的功能字符串的完整列表 -[RuntimeEnabledFeatures.in][blink-feature-string]。 +[RuntimeEnabledFeatures.json5][blink-feature-string]。 ### `guestinstance` @@ -835,4 +835,4 @@ ipcRenderer.on('ping', () => { 在开发者工具获取焦点的时候触发。 -[blink-feature-string]: https://code.google.com/p/chromium/codesearch#chromium/src/out/Debug/gen/blink/platform/RuntimeEnabledFeatures.cpp&sq=package:chromium&type=cs&l=527 +[blink-feature-string]: https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.json5?l=62 diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index dd477e8c16..a8aa5e70c0 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -256,12 +256,12 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. macOS. Default is `false`. * `blinkFeatures` String (optional) - A list of feature strings separated by `,`, like `CSSVariables,KeyboardEventKey` to enable. The full list of supported feature - strings can be found in the [RuntimeEnabledFeatures.in][blink-feature-string] + strings can be found in the [RuntimeEnabledFeatures.json5][blink-feature-string] file. * `disableBlinkFeatures` String (optional) - A list of feature strings separated by `,`, like `CSSVariables,KeyboardEventKey` to disable. The full list of supported feature strings can be found in the - [RuntimeEnabledFeatures.in][blink-feature-string] file. + [RuntimeEnabledFeatures.json5][blink-feature-string] file. * `defaultFontFamily` Object (optional) - Sets the default font for the font-family. * `standard` String (optional) - Defaults to `Times New Roman`. * `serif` String (optional) - Defaults to `Times New Roman`. @@ -853,7 +853,7 @@ On Linux always returns `true`. [macOS docs][window-levels] for more details. * `relativeLevel` Integer (optional) _macOS_ - The number of layers higher to set this window relative to the given `level`. The default is `0`. Note that Apple - discourages setting levels higher than 1 above `screen-saver`. + discourages setting levels higher than 1 above `screen-saver`. Sets whether the window should show always on top of other windows. After setting this, the window is still a normal window, not a toolbox window which @@ -1266,7 +1266,7 @@ Controls whether to hide cursor when typing. Adds a vibrancy effect to the browser window. Passing `null` or an empty string will remove the vibrancy effect on the window. -[blink-feature-string]: https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.in +[blink-feature-string]: https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.json5?l=62 [quick-look]: https://en.wikipedia.org/wiki/Quick_Look [vibrancy-docs]: https://developer.apple.com/reference/appkit/nsvisualeffectview?language=objc [window-levels]: https://developer.apple.com/reference/appkit/nswindow/1664726-window_levels diff --git a/docs/api/webview-tag.md b/docs/api/webview-tag.md index b0e1721ad2..4887ecaa62 100644 --- a/docs/api/webview-tag.md +++ b/docs/api/webview-tag.md @@ -212,7 +212,7 @@ The full list of supported preference strings can be found in [BrowserWindow](br The string follows the same format as the features string in `window.open`. A name by itself is given a `true` boolean value. A preference can be set to another value by including an `=`, followed by the value. -Special values `yes` and `1` are interpreted as `true`, while `no` and `0` are interpreted as `false`. +Special values `yes` and `1` are interpreted as `true`, while `no` and `0` are interpreted as `false`. ### `blinkfeatures` @@ -222,7 +222,7 @@ Special values `yes` and `1` are interpreted as `true`, while `no` and `0` are i A list of strings which specifies the blink features to be enabled separated by `,`. The full list of supported feature strings can be found in the -[RuntimeEnabledFeatures.in][blink-feature-string] file. +[RuntimeEnabledFeatures.json5][blink-feature-string] file. ### `disableblinkfeatures` @@ -232,7 +232,7 @@ The full list of supported feature strings can be found in the A list of strings which specifies the blink features to be disabled separated by `,`. The full list of supported feature strings can be found in the -[RuntimeEnabledFeatures.in][blink-feature-string] file. +[RuntimeEnabledFeatures.json5][blink-feature-string] file. ### `guestinstance` @@ -913,4 +913,4 @@ Emitted when DevTools is closed. Emitted when DevTools is focused / opened. -[blink-feature-string]: https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.in +[blink-feature-string]: https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.json5?l=62 From 6b138e02150fb39f41f7adfeaf056a180308c244 Mon Sep 17 00:00:00 2001 From: serant Date: Mon, 27 Feb 2017 11:01:44 -0500 Subject: [PATCH 234/925] Fixed typo in translation doc for BrowserWindow --- docs-translations/zh-TW/tutorial/quick-start.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs-translations/zh-TW/tutorial/quick-start.md b/docs-translations/zh-TW/tutorial/quick-start.md index 8b139d742b..3fe46f1ada 100644 --- a/docs-translations/zh-TW/tutorial/quick-start.md +++ b/docs-translations/zh-TW/tutorial/quick-start.md @@ -23,8 +23,8 @@ Electron 的用戶擁有在網頁中呼叫 Node.js APIs 的能力,允許低級 ## 主行程與渲染行程的區別 -主行程創造網頁透過創造 `BroswerWindow` 實例。每一個 `BroswerWindow` 實例都在自己的渲染行程裡運行著一個網頁。 -當一個 `BroswerWindow` 實例被銷毀,對應的渲染行程也會被終止。主行程管理所有網頁和與之對應的渲染行程。 +主行程創造網頁透過創造 `BrowserWindow` 實例。每一個 `BrowserWindow` 實例都在自己的渲染行程裡運行著一個網頁。 +當一個 `BrowserWindow` 實例被銷毀,對應的渲染行程也會被終止。主行程管理所有網頁和與之對應的渲染行程。 每一個渲染行程都是相互獨立的,並且只關心他們自己的網頁。 在網頁中,是不允許呼叫原生 GUI 相關 APIs 因為管理原生 GUI 資源在網頁上是非常危險而且容易造成資源洩露。 From f774ea857e7ee838f88460cc2ff35b1e6605f66a Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Mon, 27 Feb 2017 08:20:23 -0800 Subject: [PATCH 235/925] remove electron-localshortcut recommendation --- docs/tutorial/shortcuts.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/docs/tutorial/shortcuts.md b/docs/tutorial/shortcuts.md index 131406a98e..dee0e10e17 100644 --- a/docs/tutorial/shortcuts.md +++ b/docs/tutorial/shortcuts.md @@ -27,10 +27,6 @@ It's easy to configure different key combinations based on the user's operating } ``` -If you want to configure a local keyboard shortcut to trigger an action that -_does not_ have a corresponding menu item, you can use the -[electron-localshortcut] npm module. - ## Global Shortcuts You can use the [globalShortcut] module to detect keyboard events even when @@ -89,6 +85,5 @@ Mousetrap.bind('up up down down left right left right b a enter', function () { [MenuItem]: ../api/menu-item.md [globalShortcut]: ../api/global-shortcut.md [`accelerator`]: ../api/accelerator.md -[electron-localshortcut]: http://ghub.io/electron-localshortcut [BrowserWindow]: ../api/browser-window.md [mousetrap]: https://github.com/ccampbell/mousetrap From 250c656aa760fc8aa1d7ce315dd8664b1aefa3e6 Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Mon, 27 Feb 2017 09:34:35 -0800 Subject: [PATCH 236/925] lint --- docs/tutorial/shortcuts.md | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/docs/tutorial/shortcuts.md b/docs/tutorial/shortcuts.md index dee0e10e17..9b20d23f6d 100644 --- a/docs/tutorial/shortcuts.md +++ b/docs/tutorial/shortcuts.md @@ -14,7 +14,7 @@ const menu = new Menu() menu.append(new MenuItem({ label: 'Print', - accelerator: 'CmdOrCtrl+P' + accelerator: 'CmdOrCtrl+P', click: () => { console.log('time to print stuff') } })) ``` @@ -55,28 +55,27 @@ Note the third parameter `true` which means the listener will always receive key If you don't want to do manual shortcut parsing there are libraries that do advanced key detection such as [mousetrap]. ```js -Mousetrap.bind('4', function () { console.log('4')}) -Mousetrap.bind('?', function () { console.log('show shortcuts!')}) -Mousetrap.bind('esc', function () { console.log('escape')}, 'keyup') +Mousetrap.bind('4', () => { console.log('4') }) +Mousetrap.bind('?', () => { console.log('show shortcuts!') }) +Mousetrap.bind('esc', () => { console.log('escape') }, 'keyup') // combinations -Mousetrap.bind('command+shift+k', function () { console.log('command shift k')}) +Mousetrap.bind('command+shift+k', () => { console.log('command shift k') }) // map multiple combinations to the same callback -Mousetrap.bind(['command+k', 'ctrl+k'], function () { +Mousetrap.bind(['command+k', 'ctrl+k'], () => { console.log('command k or control k') - // return false to prevent default browser behavior - // and stop event from bubbling + // return false to prevent default behavior and stop event from bubbling return false }) // gmail style sequences -Mousetrap.bind('g i', function () { console.log('go to inbox')}) -Mousetrap.bind('* a', function () { console.log('select all')}) +Mousetrap.bind('g i', () => { console.log('go to inbox') }) +Mousetrap.bind('* a', () => { console.log('select all') }) // konami code! -Mousetrap.bind('up up down down left right left right b a enter', function () { +Mousetrap.bind('up up down down left right left right b a enter', () => { console.log('konami code') }) ``` From 19a948298bf053b0180efc3d6d4382dfd2d0ecb3 Mon Sep 17 00:00:00 2001 From: Lasse Jacobs Date: Thu, 23 Feb 2017 14:12:46 -0800 Subject: [PATCH 237/925] :memo: Fix some spelling mistakes [ci-skip] --- docs-translations/nl/glossary.md | 60 +++++++++++++++++++++++++++----- 1 file changed, 51 insertions(+), 9 deletions(-) diff --git a/docs-translations/nl/glossary.md b/docs-translations/nl/glossary.md index 3af61d4c5b..2b99f82da5 100644 --- a/docs-translations/nl/glossary.md +++ b/docs-translations/nl/glossary.md @@ -1,34 +1,34 @@ # Woordenlijst -Deze pagina defineerd bepaalde terminologie die veel gebruikt wordt binnen Electron's ontwikkeling. +Deze pagina definieert bepaalde terminologie die veel gebruikt wordt binnen Electrons ontwikkeling. ### ASAR -ASAR staat voor "Atom Shell Archive Format". Een [asar][asar] archief is een simpel `tar`-achtig formaat die bestanden samenvoegt in een enkel bestand. Electron kan er willekeurige bestanden uit lezen zonder het hele bestand uit te pakken. +ASAR staat voor "Atom Shell Archive Format". Een [asar][asar] archief is een simpel `tar`-achtig formaat die bestanden samenvoegt in een enkel bestand. Electron kan er willekeurige bestanden uitlezen zonder het hele bestand uit te pakken. De ASAR-indeling is in de eerste plaats gemaakt om de prestaties op Windows te verbeteren ... TODO ### Brightray -[Brightray][brightray] is een statische bibliotheek die[libchromiumcontent] gemakkelijker in applicaties te gebruiken maakt. Het werd speciaal gemaakt voor Electron, maar kan ook worden gebruikt om Chromium's renderer in applicaties die niet op Electron gebaseerd zijn in te schakelen. +[Brightray][brightray] is een statische bibliotheek die [libchromiumcontent] gemakkelijker te gebruiken maakt in applicaties. Het werd speciaal gemaakt voor Electron, maar kan ook worden gebruikt om Chromium's renderer in applicaties die niet op Electron gebaseerd zijn in te schakelen. -Brightray is een laag-niveau afhankelijkheid van Electron, wat geen betrekking heeft op de meerderheid van Electron's gebruikers. +Brightray is een laag-niveau afhankelijkheid van Electron, wat geen betrekking heeft op de meerderheid van Electrons gebruikers. ### DMG -Een Apple Disk Image is een pakket formaat gebruikt door macOS. DMG bestanden worden gebruikt voor het verspreiden van programma installeerders. [electron-builder] ondersteunt `dmg` als een bouw doelwit. +Een Apple Disk Image is een pakket formaat gebruikt door macOS. DMG bestanden worden gebruikt voor het verspreiden van programma installers. [electron-builder] ondersteunt `dmg` als een build target. ### IPC -IPC staat voor "Inter-Process Communicatie". Electron gebruikt IPC om geseralizeerde JSON berichten te sturen tussen het [hoofd] en het [renderer] proces. +IPC staat voor "Inter-Process Communication". Electron gebruikt IPC om geseralizeerde JSON berichten te sturen tussen het [hoofd] en het [renderer] proces. ### libchromiumcontent -Een enkele, gedeelde bibliotheek die de Chromium inhouds module en al zijn afhankelijkheden(e.g., Blink, [V8], etc.) bevat. +Een enkele, gedeelde bibliotheek die de Chromium content module en al zijn afhankelijkheden(e.g., Blink, [V8], etc.) bevat. ### main process -Het hoofd proces, vaak een bestand genoemd `main.js`, is het toeganspunt voor elke Electron applicatie. Het controleert de levensduur van de app, van open naar dicht. Het beheert ook de basis elementen zoals het menu, menubalk, etc. Het hoofd proces is verantwoordelijk voor het maken van elk nieuw renderer proces in de app. De volledige Node API is ingebouwd. +Het hoofdproces(main process), vaak een bestand genoemd `main.js`, is het toegangspunt voor elke Electron applicatie. Het controleert de levensduur van de app, van open naar dicht. Het beheert ook de basis elementen zoals het menu, menubalk, etc. Het hoofd proces is verantwoordelijk voor het maken van elk nieuw renderer proces in de app. De volledige Node API is ingebouwd. Elke app's hoofd proces is opgegeven in het `main` attribuut in de `package.json`. Dit is hoe `electron .` weet welk bestand het moet uitvoeren bij het opstarten. Zie ook: [process](#process), [renderer process](#renderer-process) @@ -39,10 +39,49 @@ Acroniem voor Apple's Mac App Store. Voor meer informatie over het indienen van ### native modules -Native modules (ook wel [addons] in Node.js) zijn modules geschreven in C of C++ die kunnen ingeladen worden in Node.js of Electron met de `require()` functie, en gebruikt worden net alsof ze een gewone Node.js module zijn. Ze worden voornamelijk gebruikt om een een interface te voorzien tussen JavaScript die in Node.js loopt en C/C++. Native Node modules zijn ondersteund door Electron, maar aangezien Electron zeer waarschijnlijk een andere versie van V8 gebruikt dan uit de Node binaire geinstalleerd op uw systeem, moet u de locatie van Electron headers handmatig opgeven bij het bouwen van native modules. +Native modules (ook wel [addons] in Node.js) zijn modules geschreven in C of C++ die kunnen ingeladen worden in Node.js of Electron met de `require()` functie, en gebruikt worden net alsof ze een gewone Node.js module zijn. Ze worden voornamelijk gebruikt om een een interface te voorzien tussen JavaScript die in Node.js loopt en C/C++. Native Node modules zijn ondersteund door Electron, maar aangezien Electron zeer waarschijnlijk een andere versie van V8 gebruikt dan uit de Node binaire geïnstalleerd op uw systeem, moet u de locatie van Electron headers handmatig opgeven bij het bouwen van native modules. Zie ook: [Using Native Node Modules]. +### NSIS + +NSIS staat voor "Nullsoft Scriptable Install System" is een script gestuurde installer authoring tool voor Microsoft Windows. Het is vrijgegeven onder een combinatie van vrije software licenties, en is een veel gebruikte alternatief voor commerciële gepatenteerd producten zoals InstallShield. [electron-builder] supports NSIS als een build target. + +### process + +Een proces is een instantie van een computerprogramma dat wordt uitgevoerd. Electron apps die gebruik maken van het [main] en een of meer [renderer] processen zijn eigenlijk meerdere programma's tegelijk aan het uitvoeren. + +In Node.js en Electron, elk lopend proces heeft een `proces` object. Dit object is een global that informatie voorziet over, en controle over, het huidige proces. Als een global is het altijd beschikbaar voor applicaties zonder gebruik van `require()`. + +Zie ook: [main process](#main-process), [renderer process](#renderer-process) + +### renderer process + +Het renderer proces is een browservenster in uw app. In tegenstelling tot het hoofdproces, kunnen er meerdere van zijn en elke is een apart proces. Ze kunnen ook verborgen zijn. + +In normale browsers worden webpagina's meestal uitgevoerd in een sandbox-omgeving en hebben geen toegang tot native resources. Electron gebruikers hebben echter de bevoegdheid om gebruik te maken van Node.js zijn API in webpagina's die het toe laten in lager niveau van operating system te gebruiken. + +Zie ook: [process](#process), [main process](#main-process) + +### Squirrel + +Squirrel is een open-source framework dat Electron apps in staat stelt om automatisch te updaten als nieuwe versies uitkomen. Kijk naar [autoUpdater] API voor informatie over hoe te starten met Squirrel. + +### userland + +Userland of gebruikers-land is een term die zijn oorsprong heeft in de Unix gemeenschap, waar "userland" of "userspace" verwijst naar de programma's die uitgevoerd worden buiten de kernel. Meer recent is de term populair geworden in de Node en npm community om het verschil aan te geven tussen de beschikbare features in de "Node core" versus pakketten gepubliceerd naar de npm register door een veel grotere "gebruiker" gemeenschap. + +Net zoals Node, is Electron gericht op het hebben van een kleine API die alle noodzakelijke functies voorziet voor het ontwikkelen van multi-platform desktop applicaties. Deze ontwerpfilosofie zorgt ervoor dat Electron een flexibel tool is zonder overdreven te beschrijven hoe het gebruikt moet worden. Userland zorgt ervoor dat gebruikers tools kunnen maken en delen die extra functionaliteit bieden bovenop wat beschikbaar is in de "core". + +### V8 + +V8 is Google's open source JavaScript-engine. Het is geschreven in C++ en wordt gebruikt in Google Chrome, de open source browser van Google. V8 kan standalone draaien, of kan worden gebruikt in een C++ applicatie. + +### webview + +`Webview` tags worden gebruikt om 'gast' content (zoals in externe webpagina's) in uw Electron app. Ze lijken op `iframe`s, maar verschillen erin dat elke webview in een apart proces loopt. Het hoeft niet dezelfde rechten hebben als uw webpagina en alle interacties tussen uw app en ingebouwde inhoud zal asynchroon zijn. Dit houd uw app veilig voor het ingebouwde content. + + [addons]: https://nodejs.org/api/addons.html [asar]: https://github.com/electron/asar @@ -56,3 +95,6 @@ Zie ook: [Using Native Node Modules]. [Using Native Node Modules]: tutorial/using-native-node-modules.md [userland]: #userland [V8]: #v8 + + + From 76e1f882c06ec8adb57dc4adedb863c6458ef4f0 Mon Sep 17 00:00:00 2001 From: Lasse Jacobs Date: Fri, 24 Feb 2017 15:08:06 -0800 Subject: [PATCH 238/925] :memo: Fix some grammar mistakes [ci-skip] --- docs-translations/nl/glossary.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs-translations/nl/glossary.md b/docs-translations/nl/glossary.md index 2b99f82da5..9b675893ab 100644 --- a/docs-translations/nl/glossary.md +++ b/docs-translations/nl/glossary.md @@ -51,35 +51,35 @@ NSIS staat voor "Nullsoft Scriptable Install System" is een script gestuurde ins Een proces is een instantie van een computerprogramma dat wordt uitgevoerd. Electron apps die gebruik maken van het [main] en een of meer [renderer] processen zijn eigenlijk meerdere programma's tegelijk aan het uitvoeren. -In Node.js en Electron, elk lopend proces heeft een `proces` object. Dit object is een global that informatie voorziet over, en controle over, het huidige proces. Als een global is het altijd beschikbaar voor applicaties zonder gebruik van `require()`. +In Node.js en Electron heeft elk lopend proces een `proces` object. Dit object is een global dat informatie voorziet over, en controle over, het huidige proces. Als een global is het altijd beschikbaar voor applicaties zonder gebruik van `require()`. Zie ook: [main process](#main-process), [renderer process](#renderer-process) ### renderer process -Het renderer proces is een browservenster in uw app. In tegenstelling tot het hoofdproces, kunnen er meerdere van zijn en elke is een apart proces. Ze kunnen ook verborgen zijn. +Het renderer proces is een browservenster in uw app. In tegenstelling tot het hoofdproces, kunnen er meerdere van zijn en elk is een apart proces. Ze kunnen ook verborgen zijn. -In normale browsers worden webpagina's meestal uitgevoerd in een sandbox-omgeving en hebben geen toegang tot native resources. Electron gebruikers hebben echter de bevoegdheid om gebruik te maken van Node.js zijn API in webpagina's die het toe laten in lager niveau van operating system te gebruiken. +In normale browsers worden webpagina's meestal uitgevoerd in een sandbox-omgeving en hebben geen toegang tot native resources. Electron gebruikers hebben echter de bevoegdheid om gebruik te maken van Node.js zijn API in webpagina's die het toe laten een lager niveau van operating system te gebruiken. Zie ook: [process](#process), [main process](#main-process) ### Squirrel -Squirrel is een open-source framework dat Electron apps in staat stelt om automatisch te updaten als nieuwe versies uitkomen. Kijk naar [autoUpdater] API voor informatie over hoe te starten met Squirrel. +Squirrel is een open-source framework dat Electron apps in staat stelt om automatisch te updaten als nieuwe versies uitkomen. Kijk naar de [autoUpdater] API voor informatie over hoe te starten met Squirrel. ### userland -Userland of gebruikers-land is een term die zijn oorsprong heeft in de Unix gemeenschap, waar "userland" of "userspace" verwijst naar de programma's die uitgevoerd worden buiten de kernel. Meer recent is de term populair geworden in de Node en npm community om het verschil aan te geven tussen de beschikbare features in de "Node core" versus pakketten gepubliceerd naar de npm register door een veel grotere "gebruiker" gemeenschap. +Userland of gebruikers-land is een term die zijn oorsprong heeft in de Unix gemeenschap, waar "userland" of "userspace" verwijst naar de programma's die uitgevoerd worden buiten de kernel. Meer recent is de term populair geworden in de Node en npm community om het verschil aan te geven tussen de beschikbare features in de "Node core" versus pakketten gepubliceerd naar het npm register door een veel grotere "gebruiker" gemeenschap. Net zoals Node, is Electron gericht op het hebben van een kleine API die alle noodzakelijke functies voorziet voor het ontwikkelen van multi-platform desktop applicaties. Deze ontwerpfilosofie zorgt ervoor dat Electron een flexibel tool is zonder overdreven te beschrijven hoe het gebruikt moet worden. Userland zorgt ervoor dat gebruikers tools kunnen maken en delen die extra functionaliteit bieden bovenop wat beschikbaar is in de "core". ### V8 -V8 is Google's open source JavaScript-engine. Het is geschreven in C++ en wordt gebruikt in Google Chrome, de open source browser van Google. V8 kan standalone draaien, of kan worden gebruikt in een C++ applicatie. +V8 is Google's open source JavaScript-engine. Het is geschreven in C++ en wordt gebruikt in Google Chrome. V8 kan standalone draaien, of kan worden gebruikt in een C++ applicatie. ### webview -`Webview` tags worden gebruikt om 'gast' content (zoals in externe webpagina's) in uw Electron app. Ze lijken op `iframe`s, maar verschillen erin dat elke webview in een apart proces loopt. Het hoeft niet dezelfde rechten hebben als uw webpagina en alle interacties tussen uw app en ingebouwde inhoud zal asynchroon zijn. Dit houd uw app veilig voor het ingebouwde content. +`Webview` tags worden gebruikt om 'gast' content (zoals in externe webpagina's) in uw Electron app te verwerken. Ze lijken op `iframe`s, maar verschillen erin dat elke webview op een apart proces loopt. Het hoeft niet dezelfde rechten te hebben als uw webpagina en alle interacties tussen uw app en ingebouwde inhoud zal asynchroon zijn. Dit houd uw app veilig voor het ingebouwde content. From 5fe62d23d9e6922b83452d620bae62710ccb7d0b Mon Sep 17 00:00:00 2001 From: Rik Theunis Date: Mon, 27 Feb 2017 12:38:20 +0100 Subject: [PATCH 239/925] :memo: Fix typos and grammatical errors. [ci skip] --- docs-translations/nl/glossary.md | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/docs-translations/nl/glossary.md b/docs-translations/nl/glossary.md index 9b675893ab..a0923fee7e 100644 --- a/docs-translations/nl/glossary.md +++ b/docs-translations/nl/glossary.md @@ -4,13 +4,13 @@ Deze pagina definieert bepaalde terminologie die veel gebruikt wordt binnen Elec ### ASAR -ASAR staat voor "Atom Shell Archive Format". Een [asar][asar] archief is een simpel `tar`-achtig formaat die bestanden samenvoegt in een enkel bestand. Electron kan er willekeurige bestanden uitlezen zonder het hele bestand uit te pakken. +ASAR staat voor "Atom Shell Archive Format". Een [asar][asar] archief is een simpel `tar`-achtig formaat dat bestanden samenvoegt in een enkel bestand. Electron kan er willekeurige bestanden uitlezen zonder het hele bestand uit te pakken. De ASAR-indeling is in de eerste plaats gemaakt om de prestaties op Windows te verbeteren ... TODO ### Brightray -[Brightray][brightray] is een statische bibliotheek die [libchromiumcontent] gemakkelijker te gebruiken maakt in applicaties. Het werd speciaal gemaakt voor Electron, maar kan ook worden gebruikt om Chromium's renderer in applicaties die niet op Electron gebaseerd zijn in te schakelen. +[Brightray][brightray] is een statische bibliotheek die [libchromiumcontent] gemakkelijker te gebruiken maakt in applicaties. Het werd speciaal gemaakt voor Electron, maar kan ook worden gebruikt om Chromiums renderer in applicaties, die niet op Electron gebaseerd zijn, in te schakelen. Brightray is een laag-niveau afhankelijkheid van Electron, wat geen betrekking heeft op de meerderheid van Electrons gebruikers. @@ -20,7 +20,7 @@ Een Apple Disk Image is een pakket formaat gebruikt door macOS. DMG bestanden wo ### IPC -IPC staat voor "Inter-Process Communication". Electron gebruikt IPC om geseralizeerde JSON berichten te sturen tussen het [hoofd] en het [renderer] proces. +IPC staat voor "Inter-Process Communication". Electron gebruikt IPC om geserialiseerde JSON berichten te sturen tussen het [hoofd] en het [renderer] proces. ### libchromiumcontent @@ -28,8 +28,8 @@ Een enkele, gedeelde bibliotheek die de Chromium content module en al zijn afhan ### main process -Het hoofdproces(main process), vaak een bestand genoemd `main.js`, is het toegangspunt voor elke Electron applicatie. Het controleert de levensduur van de app, van open naar dicht. Het beheert ook de basis elementen zoals het menu, menubalk, etc. Het hoofd proces is verantwoordelijk voor het maken van elk nieuw renderer proces in de app. De volledige Node API is ingebouwd. -Elke app's hoofd proces is opgegeven in het `main` attribuut in de `package.json`. Dit is hoe `electron .` weet welk bestand het moet uitvoeren bij het opstarten. +Het hoofdproces(main process), vaak een bestand genaamd `main.js`, is het toegangspunt voor elke Electron applicatie. Het controleert de levensduur van de app, van begin tot einde. Het beheert ook de basis elementen zoals het menu, de menubalk, etc. Het hoofdproces is verantwoordelijk voor het maken van elk nieuw renderer proces in de app. De volledige Node API is ingebouwd. +Elke app's hoofdproces is opgegeven in het `main` attribuut in de `package.json`. Dit is hoe `electron .` weet welk bestand het moet uitvoeren bij het opstarten. Zie ook: [process](#process), [renderer process](#renderer-process) @@ -39,27 +39,27 @@ Acroniem voor Apple's Mac App Store. Voor meer informatie over het indienen van ### native modules -Native modules (ook wel [addons] in Node.js) zijn modules geschreven in C of C++ die kunnen ingeladen worden in Node.js of Electron met de `require()` functie, en gebruikt worden net alsof ze een gewone Node.js module zijn. Ze worden voornamelijk gebruikt om een een interface te voorzien tussen JavaScript die in Node.js loopt en C/C++. Native Node modules zijn ondersteund door Electron, maar aangezien Electron zeer waarschijnlijk een andere versie van V8 gebruikt dan uit de Node binaire geïnstalleerd op uw systeem, moet u de locatie van Electron headers handmatig opgeven bij het bouwen van native modules. +Native modules (ook wel [addons] in Node.js) zijn modules geschreven in C of C++ die kunnen ingeladen worden in Node.js of Electron met de `require()` functie en kunnen gebruikt worden net alsof ze gewone Node.js module zijn. Ze worden voornamelijk gebruikt om een een interface te voorzien tussen JavaScript, die in Node.js loopt, en C/C++. Native Node modules worden ondersteund door Electron, maar aangezien Electron zeer waarschijnlijk een andere versie van V8 gebruikt dan de binaire Node geïnstalleerd op uw systeem, moet u de locatie van Electron headers handmatig opgeven bij het bouwen van native modules. Zie ook: [Using Native Node Modules]. ### NSIS -NSIS staat voor "Nullsoft Scriptable Install System" is een script gestuurde installer authoring tool voor Microsoft Windows. Het is vrijgegeven onder een combinatie van vrije software licenties, en is een veel gebruikte alternatief voor commerciële gepatenteerd producten zoals InstallShield. [electron-builder] supports NSIS als een build target. +NSIS staat voor "Nullsoft Scriptable Install System", wat een script gestuurde installer authoring tool is voor Microsoft Windows. Het is vrijgegeven onder een combinatie van vrije software licenties en is een veel gebruikt alternatief voor commercieel gepatenteerde producten zoals InstallShield. [electron-builder] ondersteunt NSIS als een build target. ### process -Een proces is een instantie van een computerprogramma dat wordt uitgevoerd. Electron apps die gebruik maken van het [main] en een of meer [renderer] processen zijn eigenlijk meerdere programma's tegelijk aan het uitvoeren. +Een proces is een instantie van een computerprogramma dat wordt uitgevoerd. Electron apps die gebruik maken van het [main] en één of meer [renderer] processen voeren eigenlijk meerdere programma's tegelijk uit. -In Node.js en Electron heeft elk lopend proces een `proces` object. Dit object is een global dat informatie voorziet over, en controle over, het huidige proces. Als een global is het altijd beschikbaar voor applicaties zonder gebruik van `require()`. +In Node.js en Electron heeft elk lopend proces een `proces` object. Dit object is een globale die informatie en controle vooziet over het huidige proces. Als een globale is het altijd beschikbaar voor applicaties zonder gebruik van `require()`. Zie ook: [main process](#main-process), [renderer process](#renderer-process) ### renderer process -Het renderer proces is een browservenster in uw app. In tegenstelling tot het hoofdproces, kunnen er meerdere van zijn en elk is een apart proces. Ze kunnen ook verborgen zijn. +Het renderer proces is een browservenster in uw app. In tegenstelling tot het hoofdproces, kunnen er meerdere renderer processen zijn en elk van deze is een apart proces. Ze kunnen ook verborgen zijn. -In normale browsers worden webpagina's meestal uitgevoerd in een sandbox-omgeving en hebben geen toegang tot native resources. Electron gebruikers hebben echter de bevoegdheid om gebruik te maken van Node.js zijn API in webpagina's die het toe laten een lager niveau van operating system te gebruiken. +In normale browsers worden webpagina's meestal uitgevoerd in een sandbox-omgeving en hebben geen toegang tot native resources. Electron gebruikers hebben echter de bevoegdheid om gebruik te maken van de Node.js API in webpagina's die het toelaten om een lager niveau van besturingssysteem interacties te gebruiken. Zie ook: [process](#process), [main process](#main-process) @@ -69,9 +69,9 @@ Squirrel is een open-source framework dat Electron apps in staat stelt om automa ### userland -Userland of gebruikers-land is een term die zijn oorsprong heeft in de Unix gemeenschap, waar "userland" of "userspace" verwijst naar de programma's die uitgevoerd worden buiten de kernel. Meer recent is de term populair geworden in de Node en npm community om het verschil aan te geven tussen de beschikbare features in de "Node core" versus pakketten gepubliceerd naar het npm register door een veel grotere "gebruiker" gemeenschap. +Userland of gebruikers-land is een term die zijn oorsprong heeft in de Unix gemeenschap, waar "userland" of "userspace" verwijst naar de programma's die uitgevoerd worden buiten de kernel. Recent is de term populair geworden in de Node en npm community om het verschil aan te geven tussen de beschikbare features in de "Node core" versus pakketten gepubliceerd naar het npm register door een veel grotere "gebruiker" gemeenschap. -Net zoals Node, is Electron gericht op het hebben van een kleine API die alle noodzakelijke functies voorziet voor het ontwikkelen van multi-platform desktop applicaties. Deze ontwerpfilosofie zorgt ervoor dat Electron een flexibel tool is zonder overdreven te beschrijven hoe het gebruikt moet worden. Userland zorgt ervoor dat gebruikers tools kunnen maken en delen die extra functionaliteit bieden bovenop wat beschikbaar is in de "core". +Net zoals Node, is Electron gericht op het hebben van een kleine API die alle noodzakelijke functies voorziet voor het ontwikkelen van multi-platform desktop applicaties. Deze ontwerpfilosofie zorgt ervoor dat Electron een flexibele tool is zonder overdreven te beschrijven hoe het gebruikt moet worden. Userland zorgt ervoor dat gebruikers tools kunnen maken en delen die extra functionaliteit bieden bovenop wat beschikbaar is in de "core". ### V8 @@ -79,7 +79,7 @@ V8 is Google's open source JavaScript-engine. Het is geschreven in C++ en wordt ### webview -`Webview` tags worden gebruikt om 'gast' content (zoals in externe webpagina's) in uw Electron app te verwerken. Ze lijken op `iframe`s, maar verschillen erin dat elke webview op een apart proces loopt. Het hoeft niet dezelfde rechten te hebben als uw webpagina en alle interacties tussen uw app en ingebouwde inhoud zal asynchroon zijn. Dit houd uw app veilig voor het ingebouwde content. +`Webview` tags worden gebruikt om 'gast' content (zoals in externe webpagina's) in uw Electron app te verwerken. Ze lijken op `iframe`s, maar verschillen erin dat elke webview op een apart proces loopt. Het heeft niet dezelfde rechten als uw webpagina en alle interacties tussen uw app en ingebouwde inhoud zullen asynchroon zijn. Dit houdt uw app veilig voor de ingebouwde content. @@ -95,6 +95,3 @@ V8 is Google's open source JavaScript-engine. Het is geschreven in C++ en wordt [Using Native Node Modules]: tutorial/using-native-node-modules.md [userland]: #userland [V8]: #v8 - - - From f4717b34e913ecb021b6b11c93f0db23c9a678dd Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 22 Feb 2017 14:15:20 -0800 Subject: [PATCH 240/925] Register visibility change listener when attached --- lib/renderer/web-view/web-view.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/renderer/web-view/web-view.js b/lib/renderer/web-view/web-view.js index 5e5a10cb00..b474f95bd1 100644 --- a/lib/renderer/web-view/web-view.js +++ b/lib/renderer/web-view/web-view.js @@ -34,10 +34,10 @@ class WebViewImpl { this.viewInstanceId = getNextId() shadowRoot.appendChild(this.browserPluginNode) + // Forward window visibility changes to guest contents this.onVisibilityChanged = (event, visibilityState) => { this.webviewNode.send('ELECTRON_RENDERER_WINDOW_VISIBILITY_CHANGE', visibilityState) } - ipcRenderer.on('ELECTRON_RENDERER_WINDOW_VISIBILITY_CHANGE', this.onVisibilityChanged) } createBrowserPluginNode () { @@ -315,6 +315,7 @@ var registerWebViewElement = function () { return } if (!internal.elementAttached) { + ipcRenderer.on('ELECTRON_RENDERER_WINDOW_VISIBILITY_CHANGE', internal.onVisibilityChanged) guestViewInternal.registerEvents(internal, internal.viewInstanceId) internal.elementAttached = true instance = internal.attributes[webViewConstants.ATTRIBUTE_GUESTINSTANCE].getValue() From 7564a9973bea48b5e30d651c281668c7ea2d609f Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 22 Feb 2017 14:15:59 -0800 Subject: [PATCH 241/925] Use const instead of var --- lib/renderer/web-view/web-view.js | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/lib/renderer/web-view/web-view.js b/lib/renderer/web-view/web-view.js index b474f95bd1..3e8d33c663 100644 --- a/lib/renderer/web-view/web-view.js +++ b/lib/renderer/web-view/web-view.js @@ -264,8 +264,7 @@ const registerBrowserPluginElement = function () { this.style.flex = '1 1 auto' } proto.attributeChangedCallback = function (name, oldValue, newValue) { - var internal - internal = v8Util.getHiddenValue(this, 'internal') + const internal = v8Util.getHiddenValue(this, 'internal') if (internal) { internal.handleBrowserPluginAttributeMutation(name, oldValue, newValue) } @@ -285,21 +284,19 @@ const registerBrowserPluginElement = function () { } // Registers custom element. -var registerWebViewElement = function () { +const registerWebViewElement = function () { const proto = Object.create(HTMLObjectElement.prototype) proto.createdCallback = function () { return new WebViewImpl(this) } proto.attributeChangedCallback = function (name, oldValue, newValue) { - var internal - internal = v8Util.getHiddenValue(this, 'internal') + const internal = v8Util.getHiddenValue(this, 'internal') if (internal) { internal.handleWebviewAttributeMutation(name, oldValue, newValue) } } proto.detachedCallback = function () { - var internal - internal = v8Util.getHiddenValue(this, 'internal') + const internal = v8Util.getHiddenValue(this, 'internal') if (!internal) { return } @@ -309,8 +306,7 @@ var registerWebViewElement = function () { internal.reset() } proto.attachedCallback = function () { - var internal, instance - internal = v8Util.getHiddenValue(this, 'internal') + const internal = v8Util.getHiddenValue(this, 'internal') if (!internal) { return } @@ -318,7 +314,7 @@ var registerWebViewElement = function () { ipcRenderer.on('ELECTRON_RENDERER_WINDOW_VISIBILITY_CHANGE', internal.onVisibilityChanged) guestViewInternal.registerEvents(internal, internal.viewInstanceId) internal.elementAttached = true - instance = internal.attributes[webViewConstants.ATTRIBUTE_GUESTINSTANCE].getValue() + const instance = internal.attributes[webViewConstants.ATTRIBUTE_GUESTINSTANCE].getValue() if (instance) { internal.attachGuestInstance(instance) } else { From 1d84d83fd4d26bd378edcb3e8558e550acafdb99 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 24 Feb 2017 10:18:09 -0800 Subject: [PATCH 242/925] Forward webview visibility change events from browser process --- lib/browser/api/browser-window.js | 4 +++- lib/browser/guest-view-manager.js | 12 ++++++++++++ lib/renderer/web-view/web-view.js | 8 -------- spec/webview-spec.js | 2 +- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/lib/browser/api/browser-window.js b/lib/browser/api/browser-window.js index 6528da1ba0..1aa34d45a0 100644 --- a/lib/browser/api/browser-window.js +++ b/lib/browser/api/browser-window.js @@ -110,7 +110,9 @@ BrowserWindow.prototype._init = function () { const newState = this.isVisible() && !this.isMinimized() if (isVisible !== newState) { isVisible = newState - this.webContents.send('ELECTRON_RENDERER_WINDOW_VISIBILITY_CHANGE', isVisible ? 'visible' : 'hidden') + const visibilityState = isVisible ? 'visible' : 'hidden' + this.webContents.send('ELECTRON_RENDERER_WINDOW_VISIBILITY_CHANGE', visibilityState) + this.webContents.emit('-window-visibility-change', visibilityState) } } diff --git a/lib/browser/guest-view-manager.js b/lib/browser/guest-view-manager.js index 721da5acc4..0bfe77d1e5 100644 --- a/lib/browser/guest-view-manager.js +++ b/lib/browser/guest-view-manager.js @@ -252,6 +252,17 @@ const watchEmbedder = function (embedder) { } watchedEmbedders.add(embedder) + // Forward embedder window visiblity change events to guest + const onVisibilityChange = function (visibilityState) { + for (const guestInstanceId of Object.keys(guestInstances)) { + const guestInstance = guestInstances[guestInstanceId] + if (guestInstance.embedder === embedder) { + guestInstance.guest.send('ELECTRON_RENDERER_WINDOW_VISIBILITY_CHANGE', visibilityState) + } + } + } + embedder.on('-window-visibility-change', onVisibilityChange) + const destroyEvents = ['will-destroy', 'crashed', 'did-navigate'] const destroy = function () { for (const guestInstanceId of Object.keys(guestInstances)) { @@ -263,6 +274,7 @@ const watchEmbedder = function (embedder) { for (const event of destroyEvents) { embedder.removeListener(event, destroy) } + embedder.removeListener('-window-visibility-change', onVisibilityChange) watchedEmbedders.delete(embedder) } diff --git a/lib/renderer/web-view/web-view.js b/lib/renderer/web-view/web-view.js index 3e8d33c663..ac45ab32a1 100644 --- a/lib/renderer/web-view/web-view.js +++ b/lib/renderer/web-view/web-view.js @@ -33,11 +33,6 @@ class WebViewImpl { this.setupFocusPropagation() this.viewInstanceId = getNextId() shadowRoot.appendChild(this.browserPluginNode) - - // Forward window visibility changes to guest contents - this.onVisibilityChanged = (event, visibilityState) => { - this.webviewNode.send('ELECTRON_RENDERER_WINDOW_VISIBILITY_CHANGE', visibilityState) - } } createBrowserPluginNode () { @@ -50,8 +45,6 @@ class WebViewImpl { // Resets some state upon reattaching element to the DOM. reset () { - ipcRenderer.removeListener('ELECTRON_RENDERER_WINDOW_VISIBILITY_CHANGE', this.onVisibilityChanged) - // If guestInstanceId is defined then the has navigated and has // already picked up a partition ID. Thus, we need to reset the initialization // state. However, it may be the case that beforeFirstNavigation is false BUT @@ -311,7 +304,6 @@ const registerWebViewElement = function () { return } if (!internal.elementAttached) { - ipcRenderer.on('ELECTRON_RENDERER_WINDOW_VISIBILITY_CHANGE', internal.onVisibilityChanged) guestViewInternal.registerEvents(internal, internal.viewInstanceId) internal.elementAttached = true const instance = internal.attributes[webViewConstants.ATTRIBUTE_GUESTINSTANCE].getValue() diff --git a/spec/webview-spec.js b/spec/webview-spec.js index 93924e036c..c038e3db88 100644 --- a/spec/webview-spec.js +++ b/spec/webview-spec.js @@ -1086,7 +1086,7 @@ describe(' tag', function () { assert.equal(visibilityState, 'hidden') assert.equal(hidden, true) - w.webContents.send('ELECTRON_RENDERER_WINDOW_VISIBILITY_CHANGE', 'visible') + w.webContents.emit('-window-visibility-change', 'visible') ipcMain.once('pong', function (event, visibilityState, hidden) { assert.equal(visibilityState, 'visible') From 54232ee21a3825e3f11dd0cacf90eb5ef714a2ab Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 24 Feb 2017 11:56:27 -0800 Subject: [PATCH 243/925] Emit event after listener is registered --- spec/webview-spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/webview-spec.js b/spec/webview-spec.js index c038e3db88..a1585922e1 100644 --- a/spec/webview-spec.js +++ b/spec/webview-spec.js @@ -1086,13 +1086,13 @@ describe(' tag', function () { assert.equal(visibilityState, 'hidden') assert.equal(hidden, true) - w.webContents.emit('-window-visibility-change', 'visible') - ipcMain.once('pong', function (event, visibilityState, hidden) { assert.equal(visibilityState, 'visible') assert.equal(hidden, false) done() }) + + w.webContents.emit('-window-visibility-change', 'visible') }) w.loadURL('file://' + fixtures + '/pages/webview-visibilitychange.html') From 61bce3ef393bd17ad3f3a0599ee436f5d870e388 Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Tue, 28 Feb 2017 08:44:12 -0800 Subject: [PATCH 244/925] rename shortcuts to keyboard-shortcuts for clarity --- docs/tutorial/{shortcuts.md => keyboard-shortcuts.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/tutorial/{shortcuts.md => keyboard-shortcuts.md} (100%) diff --git a/docs/tutorial/shortcuts.md b/docs/tutorial/keyboard-shortcuts.md similarity index 100% rename from docs/tutorial/shortcuts.md rename to docs/tutorial/keyboard-shortcuts.md From 8e7a26ec25bcc1e45ad14a48f5e233fa641576d8 Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Tue, 28 Feb 2017 08:44:28 -0800 Subject: [PATCH 245/925] link to keyboard shortcuts from docs README --- docs/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/README.md b/docs/README.md index 9159257682..56a423f491 100644 --- a/docs/README.md +++ b/docs/README.md @@ -32,6 +32,7 @@ an issue: * [Using Widevine CDM Plugin](tutorial/using-widevine-cdm-plugin.md) * [Testing on Headless CI Systems (Travis, Jenkins)](tutorial/testing-on-headless-ci.md) * [Offscreen Rendering](tutorial/offscreen-rendering.md) +* [Keyboard Shortcuts](tutorial/keyboard-shortcuts.md) ## Tutorials From e8a79cb88d98c572ad34bc8a86c917ae8f2f3aa3 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 23 Feb 2017 10:01:27 -0800 Subject: [PATCH 246/925] Don't require browser process module from renderer This can cause issues when it is accessed from the require cache or module tree since the getters throw errors when called. --- lib/browser/api/exports/electron.js | 25 ++++++++------- lib/renderer/api/remote.js | 49 ++++++++++++++++++++++------- 2 files changed, 50 insertions(+), 24 deletions(-) diff --git a/lib/browser/api/exports/electron.js b/lib/browser/api/exports/electron.js index 9a486b575d..904f0bab73 100644 --- a/lib/browser/api/exports/electron.js +++ b/lib/browser/api/exports/electron.js @@ -5,6 +5,7 @@ common.defineProperties(exports) Object.defineProperties(exports, { // Browser side modules, please sort with alphabet order. + // Any new modules added here must also be added to the array in remote.js app: { enumerable: true, get: function () { @@ -35,18 +36,18 @@ Object.defineProperties(exports, { return require('../dialog') } }, - ipcMain: { - enumerable: true, - get: function () { - return require('../ipc-main') - } - }, globalShortcut: { enumerable: true, get: function () { return require('../global-shortcut') } }, + ipcMain: { + enumerable: true, + get: function () { + return require('../ipc-main') + } + }, Menu: { enumerable: true, get: function () { @@ -59,6 +60,12 @@ Object.defineProperties(exports, { return require('../menu-item') } }, + net: { + enumerable: true, + get: function () { + return require('../net') + } + }, powerMonitor: { enumerable: true, get: function () { @@ -107,12 +114,6 @@ Object.defineProperties(exports, { return require('../web-contents') } }, - net: { - enumerable: true, - get: function () { - return require('../net') - } - }, // The internal modules, invisible unless you know their names. NavigationController: { diff --git a/lib/renderer/api/remote.js b/lib/renderer/api/remote.js index 8258a91a66..240aa22d89 100644 --- a/lib/renderer/api/remote.js +++ b/lib/renderer/api/remote.js @@ -288,18 +288,6 @@ ipcRenderer.on('ELECTRON_RENDERER_RELEASE_CALLBACK', function (event, id) { callbacksRegistry.remove(id) }) -// List all built-in modules in browser process. -const browserModules = require('../../browser/api/exports/electron') - -// And add a helper receiver for each one. -for (let name of Object.getOwnPropertyNames(browserModules)) { - Object.defineProperty(exports, name, { - get: function () { - return exports.getBuiltin(name) - } - }) -} - // Get remote module. exports.require = function (module) { return metaToValue(ipcRenderer.sendSync('ELECTRON_BROWSER_REQUIRE', module)) @@ -344,3 +332,40 @@ exports.getGuestWebContents = function (guestInstanceId) { const meta = ipcRenderer.sendSync('ELECTRON_BROWSER_GUEST_WEB_CONTENTS', guestInstanceId) return metaToValue(meta) } + +const addBuiltinProperty = (name) => { + Object.defineProperty(exports, name, { + get: function () { + return exports.getBuiltin(name) + } + }) +} + +// Add each browser module name as a property +// This list should match the exports in browser/api/exports/electron.js +const browserModules = [ + 'app', + 'autoUpdater', + 'BrowserWindow', + 'contentTracing', + 'dialog', + 'globalShortcut', + 'ipcMain', + 'Menu', + 'MenuItem', + 'net', + 'powerMonitor', + 'powerSaveBlocker', + 'protocol', + 'screen', + 'session', + 'systemPreferences', + 'Tray', + 'webContents' +] +browserModules.forEach(addBuiltinProperty) + +// Add each common module name as a property +const commonModules = {} +require('../../common/api/exports/electron').defineProperties(commonModules) +Object.getOwnPropertyNames(commonModules).forEach(addBuiltinProperty) From ca8983bf6c726fc54398bfc4e42296dc3db9f105 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 24 Feb 2017 10:43:39 -0800 Subject: [PATCH 247/925] Add initial spec for remote modules --- spec/api-ipc-spec.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/spec/api-ipc-spec.js b/spec/api-ipc-spec.js index 372855619d..e812eb5fef 100644 --- a/spec/api-ipc-spec.js +++ b/spec/api-ipc-spec.js @@ -154,6 +154,15 @@ describe('ipc module', function () { }) }) + describe('remote modules', function () { + it('includes browser process modules as properties', function () { + assert.equal(typeof remote.app.getPath, 'function') + assert.equal(typeof remote.webContents.getFocusedWebContents, 'function') + assert.equal(typeof remote.clipboard.readText, 'function') + assert.equal(typeof remote.shell.openExternal, 'function') + }) + }) + describe('remote object in renderer', function () { it('can change its properties', function () { var property = remote.require(path.join(fixtures, 'module', 'property.js')) From bb1a981a019d4fdc3df2b14a060eabac7553cfce Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 24 Feb 2017 10:53:11 -0800 Subject: [PATCH 248/925] Mention variable name to update --- lib/browser/api/exports/electron.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/browser/api/exports/electron.js b/lib/browser/api/exports/electron.js index 904f0bab73..2a3f1d97c4 100644 --- a/lib/browser/api/exports/electron.js +++ b/lib/browser/api/exports/electron.js @@ -4,8 +4,9 @@ const common = require('../../../common/api/exports/electron') common.defineProperties(exports) Object.defineProperties(exports, { - // Browser side modules, please sort with alphabet order. - // Any new modules added here must also be added to the array in remote.js + // Browser side modules, please sort alphabetically alphabet order. + // Any modules added here must also be added to the browserModules array + // in remote.js app: { enumerable: true, get: function () { From 7a99f0435b06751921e62ad7c749b0cfc17a794d Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 27 Feb 2017 08:56:32 -0800 Subject: [PATCH 249/925] Fix typo in comment --- lib/browser/api/exports/electron.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/browser/api/exports/electron.js b/lib/browser/api/exports/electron.js index 2a3f1d97c4..11698f1df5 100644 --- a/lib/browser/api/exports/electron.js +++ b/lib/browser/api/exports/electron.js @@ -4,7 +4,7 @@ const common = require('../../../common/api/exports/electron') common.defineProperties(exports) Object.defineProperties(exports, { - // Browser side modules, please sort alphabetically alphabet order. + // Browser side modules, please sort alphabetically. // Any modules added here must also be added to the browserModules array // in remote.js app: { From 879082b1a69629219703c18b0c4c084ee0dff41a Mon Sep 17 00:00:00 2001 From: Nitish Sakhawalkar Date: Tue, 28 Feb 2017 16:49:03 -0800 Subject: [PATCH 250/925] Issue #8735:Support base URL option to loadURL for data URLs --- atom/browser/api/atom_api_web_contents.cc | 216 +++++++++++----------- docs/api/browser-window.md | 1 + docs/api/web-contents.md | 1 + docs/api/webview-tag.md | 1 + spec/api-browser-window-spec.js | 8 + spec/fixtures/api/loaded-from-dataurl.js | 1 + 6 files changed, 122 insertions(+), 106 deletions(-) create mode 100644 spec/fixtures/api/loaded-from-dataurl.js diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 89e7a7d65b..4690f8c0b0 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -90,7 +90,7 @@ struct PrintSettings { namespace mate { -template<> +template <> struct Converter { static bool FromV8(v8::Isolate* isolate, v8::Local val, @@ -112,9 +112,10 @@ struct Converter { } }; -template<> +template <> struct Converter { - static bool FromV8(v8::Isolate* isolate, v8::Local val, + static bool FromV8(v8::Isolate* isolate, + v8::Local val, PrintSettings* out) { mate::Dictionary dict; if (!ConvertFromV8(isolate, val, &dict)) @@ -125,7 +126,7 @@ struct Converter { } }; -template<> +template <> struct Converter { static v8::Local ToV8(v8::Isolate* isolate, WindowOpenDisposition val) { @@ -154,9 +155,10 @@ struct Converter { } }; -template<> +template <> struct Converter { - static bool FromV8(v8::Isolate* isolate, v8::Local val, + static bool FromV8(v8::Isolate* isolate, + v8::Local val, content::SavePageType* out) { std::string save_type; if (!ConvertFromV8(isolate, val, &save_type)) @@ -175,24 +177,36 @@ struct Converter { } }; -template<> +template <> struct Converter { static v8::Local ToV8(v8::Isolate* isolate, atom::api::WebContents::Type val) { using Type = atom::api::WebContents::Type; std::string type = ""; switch (val) { - case Type::BACKGROUND_PAGE: type = "backgroundPage"; break; - case Type::BROWSER_WINDOW: type = "window"; break; - case Type::REMOTE: type = "remote"; break; - case Type::WEB_VIEW: type = "webview"; break; - case Type::OFF_SCREEN: type = "offscreen"; break; - default: break; + case Type::BACKGROUND_PAGE: + type = "backgroundPage"; + break; + case Type::BROWSER_WINDOW: + type = "window"; + break; + case Type::REMOTE: + type = "remote"; + break; + case Type::WEB_VIEW: + type = "webview"; + break; + case Type::OFF_SCREEN: + type = "offscreen"; + break; + default: + break; } return mate::ConvertToV8(isolate, type); } - static bool FromV8(v8::Isolate* isolate, v8::Local val, + static bool FromV8(v8::Isolate* isolate, + v8::Local val, atom::api::WebContents::Type* out) { using Type = atom::api::WebContents::Type; std::string type; @@ -213,7 +227,6 @@ struct Converter { } // namespace mate - namespace atom { namespace api { @@ -307,10 +320,10 @@ WebContents::WebContents(v8::Isolate* isolate, const mate::Dictionary& options) content::WebContents* web_contents; if (IsGuest()) { scoped_refptr site_instance = - content::SiteInstance::CreateForURL( - session->browser_context(), GURL("chrome-guest://fake-host")); - content::WebContents::CreateParams params( - session->browser_context(), site_instance); + content::SiteInstance::CreateForURL(session->browser_context(), + GURL("chrome-guest://fake-host")); + content::WebContents::CreateParams params(session->browser_context(), + site_instance); guest_delegate_.reset(new WebViewGuestDelegate); params.guest_delegate = guest_delegate_.get(); web_contents = content::WebContents::Create(params); @@ -335,7 +348,7 @@ WebContents::WebContents(v8::Isolate* isolate, const mate::Dictionary& options) } void WebContents::InitWithSessionAndOptions(v8::Isolate* isolate, - content::WebContents *web_contents, + content::WebContents* web_contents, mate::Handle session, const mate::Dictionary& options) { Observe(web_contents); @@ -441,8 +454,8 @@ void WebContents::AddNewContents(content::WebContents* source, v8::HandleScope handle_scope(isolate()); auto api_web_contents = CreateFrom(isolate(), new_contents); if (Emit("-add-new-contents", api_web_contents, disposition, user_gesture, - initial_rect.x(), initial_rect.y(), initial_rect.width(), - initial_rect.height())) { + initial_rect.x(), initial_rect.y(), initial_rect.width(), + initial_rect.height())) { api_web_contents->DestroyWebContents(); } } @@ -520,8 +533,8 @@ bool WebContents::PreHandleKeyboardEvent( content::WebContents* source, const content::NativeWebKeyboardEvent& event, bool* is_keyboard_shortcut) { - if (event.type == blink::WebInputEvent::Type::RawKeyDown - || event.type == blink::WebInputEvent::Type::KeyUp) + if (event.type == blink::WebInputEvent::Type::RawKeyDown || + event.type == blink::WebInputEvent::Type::KeyUp) return Emit("before-input-event", event); else return false; @@ -529,8 +542,7 @@ bool WebContents::PreHandleKeyboardEvent( void WebContents::EnterFullscreenModeForTab(content::WebContents* source, const GURL& origin) { - auto permission_helper = - WebContentsPermissionHelper::FromWebContents(source); + auto permission_helper = WebContentsPermissionHelper::FromWebContents(source); auto callback = base::Bind(&WebContents::OnEnterFullscreenModeForTab, base::Unretained(this), source, origin); permission_helper->RequestFullscreenPermission(callback); @@ -600,10 +612,9 @@ void WebContents::FindReply(content::WebContents* web_contents, Emit("found-in-page", result); } -bool WebContents::CheckMediaAccessPermission( - content::WebContents* web_contents, - const GURL& security_origin, - content::MediaStreamType type) { +bool WebContents::CheckMediaAccessPermission(content::WebContents* web_contents, + const GURL& security_origin, + content::MediaStreamType type) { return true; } @@ -616,10 +627,9 @@ void WebContents::RequestMediaAccessPermission( permission_helper->RequestMediaAccessPermission(request, callback); } -void WebContents::RequestToLockMouse( - content::WebContents* web_contents, - bool user_gesture, - bool last_unlocked_by_target) { +void WebContents::RequestToLockMouse(content::WebContents* web_contents, + bool user_gesture, + bool last_unlocked_by_target) { auto permission_helper = WebContentsPermissionHelper::FromWebContents(web_contents); permission_helper->RequestPointerLockPermission(user_gesture); @@ -710,26 +720,17 @@ void WebContents::DidStopLoading() { void WebContents::DidGetResourceResponseStart( const content::ResourceRequestDetails& details) { - Emit("did-get-response-details", - details.socket_address.IsEmpty(), - details.url, - details.original_url, - details.http_response_code, - details.method, - details.referrer, - details.headers.get(), + Emit("did-get-response-details", details.socket_address.IsEmpty(), + details.url, details.original_url, details.http_response_code, + details.method, details.referrer, details.headers.get(), ResourceTypeToString(details.resource_type)); } void WebContents::DidGetRedirectForResourceRequest( const content::ResourceRedirectDetails& details) { - Emit("did-get-redirect-request", - details.url, - details.new_url, + Emit("did-get-redirect-request", details.url, details.new_url, (details.resource_type == content::RESOURCE_TYPE_MAIN_FRAME), - details.http_response_code, - details.method, - details.referrer, + details.http_response_code, details.method, details.referrer, details.headers.get()); } @@ -794,8 +795,8 @@ void WebContents::DevToolsOpened() { // Set inspected tabID. base::FundamentalValue tab_id(ID()); - managed_web_contents()->CallClientFunction( - "DevToolsAPI.setInspectedTabId", &tab_id, nullptr, nullptr); + managed_web_contents()->CallClientFunction("DevToolsAPI.setInspectedTabId", + &tab_id, nullptr, nullptr); // Inherit owner window in devtools. if (owner_window()) @@ -824,7 +825,7 @@ bool WebContents::OnMessageReceived(const IPC::Message& message) { IPC_MESSAGE_HANDLER_DELAY_REPLY(AtomViewHostMsg_GetZoomLevel, OnGetZoomLevel) IPC_MESSAGE_HANDLER_CODE(ViewHostMsg_SetCursor, OnCursorChange, - handled = false) + handled = false) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() @@ -858,14 +859,13 @@ void WebContents::WebContentsDestroyed() { Emit("destroyed"); // Destroy the native class in next tick. - base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, GetDestroyClosure()); + base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, GetDestroyClosure()); } void WebContents::NavigationEntryCommitted( const content::LoadCommittedDetails& details) { - Emit("navigation-entry-commited", details.entry->GetURL(), - details.is_in_page, details.did_replace_entry); + Emit("navigation-entry-commited", details.entry->GetURL(), details.is_in_page, + details.did_replace_entry); } int64_t WebContents::GetID() const { @@ -889,11 +889,9 @@ bool WebContents::Equal(const WebContents* web_contents) const { void WebContents::LoadURL(const GURL& url, const mate::Dictionary& options) { if (!url.is_valid() || url.spec().size() > url::kMaxURLChars) { - Emit("did-fail-load", - static_cast(net::ERR_INVALID_URL), + Emit("did-fail-load", static_cast(net::ERR_INVALID_URL), net::ErrorToShortString(net::ERR_INVALID_URL), - url.possibly_invalid_spec(), - true); + url.possibly_invalid_spec(), true); return; } @@ -918,6 +916,12 @@ void WebContents::LoadURL(const GURL& url, const mate::Dictionary& options) { params.load_type = content::NavigationController::LOAD_TYPE_HTTP_POST; } + GURL base_url_for_data_url; + if (options.Get("baseURLForDataURL", &base_url_for_data_url)) { + params.base_url_for_data_url = base_url_for_data_url; + params.load_type = content::NavigationController::LOAD_TYPE_DATA; + } + params.transition_type = ui::PAGE_TRANSITION_TYPED; params.should_clear_history_list = true; params.override_user_agent = content::NavigationController::UA_OVERRIDE_TRUE; @@ -943,7 +947,7 @@ void WebContents::LoadURL(const GURL& url, const mate::Dictionary& options) { void WebContents::DownloadURL(const GURL& url) { auto browser_context = web_contents()->GetBrowserContext(); auto download_manager = - content::BrowserContext::GetDownloadManager(browser_context); + content::BrowserContext::GetDownloadManager(browser_context); download_manager->DownloadUrl( content::DownloadUrlParameters::CreateForWebContentsMainFrame( @@ -967,7 +971,8 @@ bool WebContents::IsLoadingMainFrame() const { // instance when navigating, regardless of origin. See AtomBrowserClient. return (web_contents()->GetLastCommittedURL().is_empty() || web_contents()->GetSiteInstance() != - web_contents()->GetPendingSiteInstance()) && IsLoading(); + web_contents()->GetPendingSiteInstance()) && + IsLoading(); } bool WebContents::IsWaitingForResponse() const { @@ -1111,15 +1116,13 @@ void WebContents::InspectServiceWorker() { } } -void WebContents::HasServiceWorker( - const base::Callback& callback) { +void WebContents::HasServiceWorker(const base::Callback& callback) { auto context = GetServiceWorkerContext(web_contents()); if (!context) return; context->CheckHasServiceWorker(web_contents()->GetLastCommittedURL(), - GURL::EmptyGURL(), - callback); + GURL::EmptyGURL(), callback); } void WebContents::UnregisterServiceWorker( @@ -1141,22 +1144,21 @@ bool WebContents::IsAudioMuted() { } void WebContents::Print(mate::Arguments* args) { - PrintSettings settings = { false, false }; + PrintSettings settings = {false, false}; if (args->Length() == 1 && !args->GetNext(&settings)) { args->ThrowError(); return; } - printing::PrintViewManagerBasic::FromWebContents(web_contents())-> - PrintNow(web_contents()->GetMainFrame(), - settings.silent, - settings.print_background); + printing::PrintViewManagerBasic::FromWebContents(web_contents()) + ->PrintNow(web_contents()->GetMainFrame(), settings.silent, + settings.print_background); } void WebContents::PrintToPDF(const base::DictionaryValue& setting, const PrintToPDFCallback& callback) { - printing::PrintPreviewMessageHandler::FromWebContents(web_contents())-> - PrintToPDF(setting, callback); + printing::PrintPreviewMessageHandler::FromWebContents(web_contents()) + ->PrintToPDF(setting, callback); } void WebContents::AddWorkSpace(mate::Arguments* args, @@ -1261,7 +1263,8 @@ void WebContents::Focus() { #if !defined(OS_MACOSX) bool WebContents::IsFocused() const { auto view = web_contents()->GetRenderWidgetHostView(); - if (!view) return false; + if (!view) + return false; if (GetType() != BACKGROUND_PAGE) { auto window = web_contents()->GetNativeView()->GetToplevelWindow(); @@ -1313,8 +1316,8 @@ void WebContents::SendInputEvent(v8::Isolate* isolate, } } - isolate->ThrowException(v8::Exception::Error(mate::StringToV8( - isolate, "Invalid event object"))); + isolate->ThrowException( + v8::Exception::Error(mate::StringToV8(isolate, "Invalid event object"))); } void WebContents::BeginFrameSubscription(mate::Arguments* args) { @@ -1329,8 +1332,8 @@ void WebContents::BeginFrameSubscription(mate::Arguments* args) { const auto view = web_contents()->GetRenderWidgetHostView(); if (view) { - std::unique_ptr frame_subscriber(new FrameSubscriber( - isolate(), view, callback, only_dirty)); + std::unique_ptr frame_subscriber( + new FrameSubscriber(isolate(), view, callback, only_dirty)); view->BeginFrameSubscription(std::move(frame_subscriber)); } } @@ -1383,8 +1386,8 @@ void WebContents::CapturePage(mate::Arguments* args) { base::Callback callback; if (!(args->Length() == 1 && args->GetNext(&callback)) && - !(args->Length() == 2 && args->GetNext(&rect) - && args->GetNext(&callback))) { + !(args->Length() == 2 && args->GetNext(&rect) && + args->GetNext(&callback))) { args->ThrowError(); return; } @@ -1397,22 +1400,21 @@ void WebContents::CapturePage(mate::Arguments* args) { } // Capture full page if user doesn't specify a |rect|. - const gfx::Size view_size = rect.IsEmpty() ? view->GetViewBounds().size() : - rect.size(); + const gfx::Size view_size = + rect.IsEmpty() ? view->GetViewBounds().size() : rect.size(); // By default, the requested bitmap size is the view size in screen // coordinates. However, if there's more pixel detail available on the // current system, increase the requested bitmap size to capture it all. gfx::Size bitmap_size = view_size; const gfx::NativeView native_view = view->GetNativeView(); - const float scale = - display::Screen::GetScreen()->GetDisplayNearestWindow(native_view) - .device_scale_factor(); + const float scale = display::Screen::GetScreen() + ->GetDisplayNearestWindow(native_view) + .device_scale_factor(); if (scale > 1.0f) bitmap_size = gfx::ScaleToCeiledSize(view_size, scale); - host->CopyFromBackingStore(gfx::Rect(rect.origin(), view_size), - bitmap_size, + host->CopyFromBackingStore(gfx::Rect(rect.origin(), view_size), bitmap_size, base::Bind(&OnCapturePageDone, callback), kBGRA_8888_SkColorType); } @@ -1423,10 +1425,10 @@ void WebContents::OnCursorChange(const content::WebCursor& cursor) { if (cursor.IsCustom()) { Emit("cursor-changed", CursorTypeToString(info), - gfx::Image::CreateFrom1xBitmap(info.custom_image), - info.image_scale_factor, - gfx::Size(info.custom_image.width(), info.custom_image.height()), - info.hotspot); + gfx::Image::CreateFrom1xBitmap(info.custom_image), + info.image_scale_factor, + gfx::Size(info.custom_image.width(), info.custom_image.height()), + info.hotspot); } else { Emit("cursor-changed", CursorTypeToString(info)); } @@ -1632,10 +1634,8 @@ void WebContents::BuildPrototype(v8::Isolate* isolate, .SetMethod("closeDevTools", &WebContents::CloseDevTools) .SetMethod("isDevToolsOpened", &WebContents::IsDevToolsOpened) .SetMethod("isDevToolsFocused", &WebContents::IsDevToolsFocused) - .SetMethod("enableDeviceEmulation", - &WebContents::EnableDeviceEmulation) - .SetMethod("disableDeviceEmulation", - &WebContents::DisableDeviceEmulation) + .SetMethod("enableDeviceEmulation", &WebContents::EnableDeviceEmulation) + .SetMethod("disableDeviceEmulation", &WebContents::DisableDeviceEmulation) .SetMethod("toggleDevTools", &WebContents::ToggleDevTools) .SetMethod("inspectElement", &WebContents::InspectElement) .SetMethod("setAudioMuted", &WebContents::SetAudioMuted) @@ -1658,8 +1658,7 @@ void WebContents::BuildPrototype(v8::Isolate* isolate, .SetMethod("tabTraverse", &WebContents::TabTraverse) .SetMethod("_send", &WebContents::SendIPCMessage) .SetMethod("sendInputEvent", &WebContents::SendInputEvent) - .SetMethod("beginFrameSubscription", - &WebContents::BeginFrameSubscription) + .SetMethod("beginFrameSubscription", &WebContents::BeginFrameSubscription) .SetMethod("endFrameSubscription", &WebContents::EndFrameSubscription) .SetMethod("startDrag", &WebContents::StartDrag) .SetMethod("setSize", &WebContents::SetSize) @@ -1717,27 +1716,30 @@ void WebContents::OnRendererMessageSync(const base::string16& channel, // static mate::Handle WebContents::CreateFrom( - v8::Isolate* isolate, content::WebContents* web_contents) { + v8::Isolate* isolate, + content::WebContents* web_contents) { // We have an existing WebContents object in JS. auto existing = TrackableObject::FromWrappedClass(isolate, web_contents); if (existing) return mate::CreateHandle(isolate, static_cast(existing)); // Otherwise create a new WebContents wrapper object. - return mate::CreateHandle(isolate, new WebContents(isolate, web_contents, - REMOTE)); + return mate::CreateHandle(isolate, + new WebContents(isolate, web_contents, REMOTE)); } mate::Handle WebContents::CreateFrom( - v8::Isolate* isolate, content::WebContents* web_contents, Type type) { + v8::Isolate* isolate, + content::WebContents* web_contents, + Type type) { // Otherwise create a new WebContents wrapper object. - return mate::CreateHandle(isolate, new WebContents(isolate, web_contents, - type)); + return mate::CreateHandle(isolate, + new WebContents(isolate, web_contents, type)); } // static -mate::Handle WebContents::Create( - v8::Isolate* isolate, const mate::Dictionary& options) { +mate::Handle WebContents::Create(v8::Isolate* isolate, + const mate::Dictionary& options) { return mate::CreateHandle(isolate, new WebContents(isolate, options)); } @@ -1749,8 +1751,10 @@ namespace { using atom::api::WebContents; -void Initialize(v8::Local exports, v8::Local unused, - v8::Local context, void* priv) { +void Initialize(v8::Local exports, + v8::Local unused, + v8::Local context, + void* priv) { v8::Isolate* isolate = context->GetIsolate(); mate::Dictionary dict(isolate, exports); dict.Set("WebContents", WebContents::GetConstructor(isolate)->GetFunction()); diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index dd477e8c16..b263420b75 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -1004,6 +1004,7 @@ Same as `webContents.capturePage([rect, ]callback)`. * `userAgent` String (optional) - A user agent originating the request. * `extraHeaders` String (optional) - Extra headers separated by "\n" * `postData` ([UploadRawData](structures/upload-raw-data.md) | [UploadFile](structures/upload-file.md) | [UploadFileSystem](structures/upload-file-system.md) | [UploadBlob](structures/upload-blob.md))[] - (optional) + * `baseURLForDataURL` String(optional) - Base url for files to be loaded by the data url. This is needed only if the specified `url` is a data url and needs to load other files. Same as `webContents.loadURL(url[, options])`. diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index b35bbdee7d..e0ed37136f 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -541,6 +541,7 @@ that can't be set via `` attributes. * `userAgent` String (optional) - A user agent originating the request. * `extraHeaders` String (optional) - Extra headers separated by "\n" * `postData` ([UploadRawData](structures/upload-raw-data.md) | [UploadFile](structures/upload-file.md) | [UploadFileSystem](structures/upload-file-system.md) | [UploadBlob](structures/upload-blob.md))[] - (optional) + * `baseURLForDataURL` String(optional) - Base url for files to be loaded by the data url. This is needed only if the specified `url` is a data url and needs to load other files. Loads the `url` in the window. The `url` must contain the protocol prefix, e.g. the `http://` or `file://`. If the load should bypass http cache then diff --git a/docs/api/webview-tag.md b/docs/api/webview-tag.md index b0e1721ad2..ccd6a9b686 100644 --- a/docs/api/webview-tag.md +++ b/docs/api/webview-tag.md @@ -310,6 +310,7 @@ webview.addEventListener('dom-ready', () => { * `userAgent` String (optional) - A user agent originating the request. * `extraHeaders` String (optional) - Extra headers separated by "\n" * `postData` ([UploadRawData](structures/upload-raw-data.md) | [UploadFile](structures/upload-file.md) | [UploadFileSystem](structures/upload-file-system.md) | [UploadBlob](structures/upload-blob.md))[] - (optional) + * `baseURLForDataURL` String(optional) - Base url for files to be loaded by the data url. This is needed only if the specified `url` is a data url and needs to load other files. Loads the `url` in the webview, the `url` must contain the protocol prefix, e.g. the `http://` or `file://`. diff --git a/spec/api-browser-window-spec.js b/spec/api-browser-window-spec.js index 354befdb3f..3a19b3a31e 100644 --- a/spec/api-browser-window-spec.js +++ b/spec/api-browser-window-spec.js @@ -256,6 +256,14 @@ describe('BrowserWindow module', function () { w.loadURL(server.url) }) + it('should support support base url for data urls', (done) => { + ipcMain.once('answer', function (event, test) { + assert.equal(test, 'test') + done() + }) + w.loadURL('data:text/html,', {baseURLForDataURL: 'file://' + path.join(fixtures, 'api') + path.sep}) + }) + it('sets the content type header on multi part forms', function (done) { w.webContents.on('did-finish-load', () => { w.webContents.session.webRequest.onBeforeSendHeaders((details, callback) => { diff --git a/spec/fixtures/api/loaded-from-dataurl.js b/spec/fixtures/api/loaded-from-dataurl.js new file mode 100644 index 0000000000..c4dbdd044b --- /dev/null +++ b/spec/fixtures/api/loaded-from-dataurl.js @@ -0,0 +1 @@ +require('electron').ipcRenderer.send('answer', 'test') From 69f0cb26e745be271d05d0d75219a9b0233fa44b Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Tue, 28 Feb 2017 21:19:55 -0800 Subject: [PATCH 251/925] use https for all Electron website URLs --- README.md | 4 ++-- default_app/index.html | 4 ++-- default_app/main.js | 2 +- docs-translations/fr-FR/README.md | 2 +- docs-translations/fr-FR/tutorial/quick-start.md | 2 +- docs-translations/it-IT/README.md | 2 +- docs-translations/jp/README.md | 2 +- docs-translations/jp/api/menu.md | 2 +- docs-translations/jp/api/webview-tag.md | 2 +- .../jp/tutorial/using-selenium-and-webdriver.md | 2 +- docs-translations/ko-KR/api/clipboard.md | 2 +- docs-translations/ko-KR/api/menu.md | 2 +- docs-translations/ko-KR/api/webview-tag.md | 2 +- docs-translations/ko-KR/project/README.md | 4 ++-- docs-translations/ko-KR/tutorial/about.md | 14 +++++++------- .../ko-KR/tutorial/accessibility.md | 4 ++-- docs-translations/ko-KR/tutorial/quick-start.md | 2 +- .../tutorial/using-selenium-and-webdriver.md | 2 +- docs-translations/pt-BR/README.md | 2 +- docs-translations/pt-BR/project/README.md | 4 ++-- .../pt-BR/tutorial/accessibility.md | 6 +++--- docs-translations/ru-RU/README.md | 2 +- docs-translations/th-TH/tutorial/about.md | 14 +++++++------- .../th-TH/tutorial/accessibility.md | 6 +++--- docs-translations/th-TH/tutorial/quick-start.md | 2 +- docs-translations/tr-TR/README.md | 2 +- docs-translations/uk-UA/README.md | 2 +- docs-translations/zh-CN/api/menu.md | 2 +- docs-translations/zh-CN/api/webview-tag.md | 2 +- docs-translations/zh-CN/project/README.md | 4 ++-- docs-translations/zh-CN/tutorial/quick-start.md | 2 +- .../tutorial/using-selenium-and-webdriver.md | 2 +- docs-translations/zh-TW/project/README.md | 4 ++-- docs-translations/zh-TW/tutorial/about.md | 16 ++++++++-------- .../zh-TW/tutorial/accessibility.md | 6 +++--- docs/api/clipboard.md | 2 +- docs/api/menu.md | 2 +- docs/api/webview-tag.md | 2 +- docs/tutorial/about.md | 16 ++++++++-------- docs/tutorial/accessibility.md | 6 +++--- docs/tutorial/quick-start.md | 2 +- docs/tutorial/using-selenium-and-webdriver.md | 2 +- spec/api-clipboard-spec.js | 4 ++-- 43 files changed, 85 insertions(+), 85 deletions(-) diff --git a/README.md b/README.md index cb3583afdf..655d613baf 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Electron Logo](http://electron.atom.io/images/electron-logo.svg)](http://electron.atom.io/) +[![Electron Logo](https://electron.atom.io/images/electron-logo.svg)](https://electron.atom.io/) [![Travis Build Status](https://travis-ci.org/electron/electron.svg?branch=master)](https://travis-ci.org/electron/electron) [![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/kvxe4byi7jcxbe26/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/electron) @@ -10,7 +10,7 @@ The Electron framework lets you write cross-platform desktop applications using JavaScript, HTML and CSS. It is based on [Node.js](https://nodejs.org/) and [Chromium](http://www.chromium.org) and is used by the [Atom -editor](https://github.com/atom/atom) and many other [apps](http://electron.atom.io/apps). +editor](https://github.com/atom/atom) and many other [apps](https://electron.atom.io/apps). Follow [@ElectronJS](https://twitter.com/electronjs) on Twitter for important announcements. diff --git a/default_app/index.html b/default_app/index.html index 292fd119b2..11d9938318 100644 --- a/default_app/index.html +++ b/default_app/index.html @@ -149,9 +149,9 @@ diff --git a/default_app/main.js b/default_app/main.js index 4fd01f28e0..31c55ac95f 100644 --- a/default_app/main.js +++ b/default_app/main.js @@ -129,7 +129,7 @@ app.once('ready', () => { { label: 'Learn More', click () { - shell.openExternal('http://electron.atom.io') + shell.openExternal('https://electron.atom.io') } }, { diff --git a/docs-translations/fr-FR/README.md b/docs-translations/fr-FR/README.md index 487751d6e2..0bf176536c 100644 --- a/docs-translations/fr-FR/README.md +++ b/docs-translations/fr-FR/README.md @@ -3,7 +3,7 @@ Le numéro de version devrait faire partie de l'URL de la page. Si ce n'est pas le cas, vous utilisez probablement la documentation d'une branche de développement qui peut contenir des changements API qui ne sont pas compatibles avec votre version d'Electron. Si c'est le cas, vous pouvez changer -de version sur la liste [versions disponibles](http://electron.atom.io/docs/), +de version sur la liste [versions disponibles](https://electron.atom.io/docs/), ou, si vous utilisez l'interface de GitHub, ouvrez la liste déroulante "Switch branches/tags" afin de sélectionner le tag de votre version. diff --git a/docs-translations/fr-FR/tutorial/quick-start.md b/docs-translations/fr-FR/tutorial/quick-start.md index 406bbfebaa..33fdc5e6fd 100644 --- a/docs-translations/fr-FR/tutorial/quick-start.md +++ b/docs-translations/fr-FR/tutorial/quick-start.md @@ -241,7 +241,7 @@ $ npm start ``` Pour plus d'exemples app, consultez la section -[list of boilerplates](http://electron.atom.io/community/#boilerplates) +[list of boilerplates](https://electron.atom.io/community/#boilerplates) Créé par la communauté impressionnante d'électrons. [share-data]: ../faq.md#how-to-share-data-between-web-pages diff --git a/docs-translations/it-IT/README.md b/docs-translations/it-IT/README.md index 6f0d43ee4e..53030825c7 100644 --- a/docs-translations/it-IT/README.md +++ b/docs-translations/it-IT/README.md @@ -4,7 +4,7 @@ Se cos� non fosse, stai probabilmente utilizzando una documentazione facente parte di una branch di sviluppo che potrebbe contenere modifiche all'API che non sono compatibili con la tua versione di Electron. In questo caso, puoi passare a una differente versione della documentazione dalla lista di -[versioni disponibili](http://electron.atom.io/docs/) su atom.io, o nel caso tu +[versioni disponibili](https://electron.atom.io/docs/) su atom.io, o nel caso tu stia usando l'interfaccia di GitHub, apri il menu a tendina "Switch branches/tags" e seleziona il tag che corrisponde alla tua versione. diff --git a/docs-translations/jp/README.md b/docs-translations/jp/README.md index e415faa6ca..93f64c4db0 100644 --- a/docs-translations/jp/README.md +++ b/docs-translations/jp/README.md @@ -1,7 +1,7 @@ 使用している Electron のバージョンに応じたドキュメントを使うように確認してください。 ドキュメントのバージョン番号はページの URL の一部になっています。 そうでない場合、おそらくご使用の Electron のバージョンと互換性のない API 変更を含んだ development ブランチのドキュメントを使っているものと思われます。 -その場合、atom.io の [available versions](http://electron.atom.io/docs/) リストにある別のバージョンのドキュメントに切り替えることができます。また GitHub で閲覧している場合、"Switch branches/tags" ドロップダウンを開いて、バージョンに対応したタグを選ぶこともできます。 +その場合、atom.io の [available versions](https://electron.atom.io/docs/) リストにある別のバージョンのドキュメントに切り替えることができます。また GitHub で閲覧している場合、"Switch branches/tags" ドロップダウンを開いて、バージョンに対応したタグを選ぶこともできます。 _リンクになっていないリストは未翻訳のものです。_ ## FAQ diff --git a/docs-translations/jp/api/menu.md b/docs-translations/jp/api/menu.md index 22dd019ed1..73869e8b76 100644 --- a/docs-translations/jp/api/menu.md +++ b/docs-translations/jp/api/menu.md @@ -129,7 +129,7 @@ var template = [ submenu: [ { label: 'Learn More', - click: function () { require('electron').shell.openExternal('http://electron.atom.io') } + click: function () { require('electron').shell.openExternal('https://electron.atom.io') } } ] } diff --git a/docs-translations/jp/api/webview-tag.md b/docs-translations/jp/api/webview-tag.md index b305ef8619..cc68f1345a 100644 --- a/docs-translations/jp/api/webview-tag.md +++ b/docs-translations/jp/api/webview-tag.md @@ -148,7 +148,7 @@ Electronアプリ内でウェブページのような外部コンテンツを埋 ```html - + ``` ページで使用されるセッションを設定します。もし、`partition`が`persist:`から始まる場合、アプリ上の同じ`partition`を指定した全てのページで有効な永続セッションを使用します。 diff --git a/docs-translations/jp/tutorial/using-selenium-and-webdriver.md b/docs-translations/jp/tutorial/using-selenium-and-webdriver.md index e348a0596d..1584ab736f 100644 --- a/docs-translations/jp/tutorial/using-selenium-and-webdriver.md +++ b/docs-translations/jp/tutorial/using-selenium-and-webdriver.md @@ -156,4 +156,4 @@ Electronはリビルドせずにアプリケーションをテストするため もしくは、アプリのフォルダーを引数にしてElectronバイナリを実行します。これによって、Electronのリソースディレクトリにアプリをコピー&ペーストする必要がなくなります。 [chrome-driver]: https://sites.google.com/a/chromium.org/chromedriver/ -[spectron]: http://electron.atom.io/spectron +[spectron]: https://electron.atom.io/spectron diff --git a/docs-translations/ko-KR/api/clipboard.md b/docs-translations/ko-KR/api/clipboard.md index 75918c6647..ee76187ba4 100644 --- a/docs-translations/ko-KR/api/clipboard.md +++ b/docs-translations/ko-KR/api/clipboard.md @@ -102,7 +102,7 @@ Returns `Object`: ```javascript clipboard.write({ - text: 'http://electron.atom.io', + text: 'https://electron.atom.io', bookmark: 'Electron Homepage' }) ``` diff --git a/docs-translations/ko-KR/api/menu.md b/docs-translations/ko-KR/api/menu.md index 87c11eb801..505f381904 100644 --- a/docs-translations/ko-KR/api/menu.md +++ b/docs-translations/ko-KR/api/menu.md @@ -104,7 +104,7 @@ const template = [ submenu: [ { label: 'Learn More', - click () { require('electron').shell.openExternal('http://electron.atom.io') } + click () { require('electron').shell.openExternal('https://electron.atom.io') } } ] } diff --git a/docs-translations/ko-KR/api/webview-tag.md b/docs-translations/ko-KR/api/webview-tag.md index 962738d0ae..55bf5d00ac 100644 --- a/docs-translations/ko-KR/api/webview-tag.md +++ b/docs-translations/ko-KR/api/webview-tag.md @@ -166,7 +166,7 @@ API를 사용할 수 있습니다. 이를 지정하면 내부에서 로우레벨 ```html - + ``` 페이지에서 사용하는 세션을 설정합니다. diff --git a/docs-translations/ko-KR/project/README.md b/docs-translations/ko-KR/project/README.md index eeba00d40f..a9db702d02 100644 --- a/docs-translations/ko-KR/project/README.md +++ b/docs-translations/ko-KR/project/README.md @@ -1,4 +1,4 @@ -[![Electron Logo](http://electron.atom.io/images/electron-logo.svg)](http://electron.atom.io/) +[![Electron Logo](https://electron.atom.io/images/electron-logo.svg)](https://electron.atom.io/) [![Travis Build Status](https://travis-ci.org/electron/electron.svg?branch=master)](https://travis-ci.org/electron/electron) [![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/kvxe4byi7jcxbe26/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/electron) @@ -9,7 +9,7 @@ Electron 프레임워크는 JavaScript, HTML 그리고 CSS를 사용하여 Cross-Platform 데스크톱 애플리케이션을 개발할 수 있도록 해주는 프레임워크입니다. [Node.js](https://nodejs.org/)와 [Chromium](http://www.chromium.org)을 기반으로 만들어졌으며 [Atom Editor](https://github.com/atom/atom)에 사용되고 있습니다. -더 많은 애플리케이션은 [이곳](http://electron.atom.io/apps)에서 확인하세요. +더 많은 애플리케이션은 [이곳](https://electron.atom.io/apps)에서 확인하세요. Electron에 대한 중요한 알림을 받고 싶다면 Twitter에서 [@ElectronJS](https://twitter.com/electronjs)를 팔로우 하세요. diff --git a/docs-translations/ko-KR/tutorial/about.md b/docs-translations/ko-KR/tutorial/about.md index eb58a821b9..7a33f916e2 100644 --- a/docs-translations/ko-KR/tutorial/about.md +++ b/docs-translations/ko-KR/tutorial/about.md @@ -1,6 +1,6 @@ # Electron 에 대하여 -[Electron](http://electron.atom.io) 은 HTML, CSS 와 Javascript 로 크로스플랫폼 +[Electron](https://electron.atom.io) 은 HTML, CSS 와 Javascript 로 크로스플랫폼 데스크톱 애플리케이션을 위해 Github 에서 개발한 오픈소스 라이브러리 입니다. Electron 은 [Chromium](https://www.chromium.org/Home) 와 [Node.js](https://nodejs.org) 를 단일 실행으로 합치고 앱을 Mac, Windows 와 @@ -47,7 +47,7 @@ Chromium 이 사용하는 버전. 대부분은 동작하지만 가끔 Node.js Node.js 와 Chromium 에 대한 의존성이 강해서, Electron 은 버전관리가 까다롭고 [`semver`을 따르지 않습니다](http://semver.org). 그러므로 항상 Electron 의 특정 버전을 참조해야 합니다. [Electron 의 버전관리] -(http://electron.atom.io/docs/tutorial/electron-versioning/)를 읽거나 +(https://electron.atom.io/docs/tutorial/electron-versioning/)를 읽거나 [현재 쓰이는 버전](https://electron.atom.io/#electron-versions)을 보세요. ### LTS @@ -57,7 +57,7 @@ Node.js 와 Chromium 에 대한 의존성이 강해서, Electron 은 버전관 새버전으로 업그레이드 해야합니다. 주 버전은 `v1.0.0` 입니다. 아직 이 버전을 사용중이지 않다면, -[v1.0.0 변화에 대해 읽어보세요](http://electron.atom.io/blog/2016/05/11/electron-1-0). +[v1.0.0 변화에 대해 읽어보세요](https://electron.atom.io/blog/2016/05/11/electron-1-0). ## 중심 철학 @@ -70,7 +70,7 @@ Electron 을 작고 (파일 크기) 지속가능하게 (의존성 및 API 의 Electron 에 추가된 새로운 기능은 주로 네이티브 API 입니다. 기능은 가능한한 Node.js 모듈로 해야합니다. [커뮤니티에 의해 개발된 Electron 도구들] -(http://electron.atom.io/community)을 보세요. +(https://electron.atom.io/community)을 보세요. ## 이력 @@ -81,6 +81,6 @@ Node.js 모듈로 해야합니다. [커뮤니티에 의해 개발된 Electron | **2013년 4월**| [Atom Shell 탄생](https://github.com/electron/electron/commit/6ef8875b1e93787fa9759f602e7880f28e8e6b45).| | **2014년 5월** | [Atom Shell 오픈소스화](http://blog.atom.io/2014/05/06/atom-is-now-open-source.html). | | **2015년 4월** | [Electron 으로 개명](https://github.com/electron/electron/pull/1389). | -| **2016년 5월** | [Electron v1.0.0 출시](http://electron.atom.io/blog/2016/05/11/electron-1-0).| -| **2016년 5월** | [Electron 앱이 Mac App Store 와 호환](http://electron.atom.io/docs/tutorial/mac-app-store-submission-guide).| -| **2016년 8월** | [Windows Store 의 Electron 앱 지원](http://electron.atom.io/docs/tutorial/windows-store-guide).| +| **2016년 5월** | [Electron v1.0.0 출시](https://electron.atom.io/blog/2016/05/11/electron-1-0).| +| **2016년 5월** | [Electron 앱이 Mac App Store 와 호환](https://electron.atom.io/docs/tutorial/mac-app-store-submission-guide).| +| **2016년 8월** | [Windows Store 의 Electron 앱 지원](https://electron.atom.io/docs/tutorial/windows-store-guide).| diff --git a/docs-translations/ko-KR/tutorial/accessibility.md b/docs-translations/ko-KR/tutorial/accessibility.md index c8521bb700..b9354edab2 100644 --- a/docs-translations/ko-KR/tutorial/accessibility.md +++ b/docs-translations/ko-KR/tutorial/accessibility.md @@ -14,7 +14,7 @@ HTML 입니다. 그러나 검사를 위한 URL 이 없기 때문에 Electron 앱 이 새 기능들은 Electron 앱에 검사 도구를 제공합니다. Spectron 으로 테스트 하기 위한 검사를 추가 하거나 Devtron 으로 개발자 도구의 것을 사용할 수 있습니다. 자세한 정보는 도구의 요약이나 -[접근성 문서](http://electron.atom.io/docs/tutorial/accessibility) 를 읽어보세요. +[접근성 문서](https://electron.atom.io/docs/tutorial/accessibility) 를 읽어보세요. ### Spectron @@ -47,5 +47,5 @@ Devtron 에서 앱의 페이지를 검사할 수 있는 접근성 탭이 생겼 통해 더 알아볼 수 있습니다. Electron 을 위한 다른 훌륭한 접근성 도구를 알고계시다면, -[접근성 문서](http://electron.atom.io/docs/tutorial/accessibility) 에 풀 +[접근성 문서](https://electron.atom.io/docs/tutorial/accessibility) 에 풀 요청과 함께 추가해 주세요. diff --git a/docs-translations/ko-KR/tutorial/quick-start.md b/docs-translations/ko-KR/tutorial/quick-start.md index 671937e113..c4004e41f7 100644 --- a/docs-translations/ko-KR/tutorial/quick-start.md +++ b/docs-translations/ko-KR/tutorial/quick-start.md @@ -243,7 +243,7 @@ $ npm start ``` 더 많은 예시 앱을 보려면 대단한 Electron 커뮤니티에 의해 만들어진 -[보일러플레이트 리스트](http://electron.atom.io/community/#boilerplates)를 +[보일러플레이트 리스트](https://electron.atom.io/community/#boilerplates)를 참고하세요. [share-data]: ../faq.md#어떻게-웹-페이지-간에-데이터를-공유할-수-있나요 diff --git a/docs-translations/ko-KR/tutorial/using-selenium-and-webdriver.md b/docs-translations/ko-KR/tutorial/using-selenium-and-webdriver.md index e9b229498a..d5ea5ad74b 100644 --- a/docs-translations/ko-KR/tutorial/using-selenium-and-webdriver.md +++ b/docs-translations/ko-KR/tutorial/using-selenium-and-webdriver.md @@ -167,4 +167,4 @@ client 디렉터리로 복사하는 불필요한 과정을 생략할 수 있습니다. [chrome-driver]: https://sites.google.com/a/chromium.org/chromedriver/ -[spectron]: http://electron.atom.io/spectron +[spectron]: https://electron.atom.io/spectron diff --git a/docs-translations/pt-BR/README.md b/docs-translations/pt-BR/README.md index be838f6145..1ed5c45571 100644 --- a/docs-translations/pt-BR/README.md +++ b/docs-translations/pt-BR/README.md @@ -2,7 +2,7 @@ Por favor, certifique-se de que está utilizando a documentação que correspond O número da versão deve ser uma parte da URL da página. Se não for, você provavelmente está utilizando a documentação de um branch de desenvolvimento que pode conter mudanças na API que não são compatíveis com a sua versão do Electron. Se este for o caso, você pode mudar para uma versão diferente da -documentação na lista de [versões disponíveis](http://electron.atom.io/docs/) em atom.io, +documentação na lista de [versões disponíveis](https://electron.atom.io/docs/) em atom.io, ou se você estiver usando a interface do GitHub, abra o *dropdown* "Switch branches/tags" e selecione a *tag* que corresponde à sua versão. diff --git a/docs-translations/pt-BR/project/README.md b/docs-translations/pt-BR/project/README.md index 6c435edaa5..d986549f4d 100644 --- a/docs-translations/pt-BR/project/README.md +++ b/docs-translations/pt-BR/project/README.md @@ -1,4 +1,4 @@ -[![Electron Logo](http://electron.atom.io/images/electron-logo.svg)](http://electron.atom.io/) +[![Electron Logo](https://electron.atom.io/images/electron-logo.svg)](https://electron.atom.io/) [![Travis Build Status](https://travis-ci.org/electron/electron.svg?branch=master)](https://travis-ci.org/electron/electron) [![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/kvxe4byi7jcxbe26/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/electron) @@ -7,7 +7,7 @@ :memo: Available Translations: [Korean](https://github.com/electron/electron/tree/master/docs-translations/ko-KR/project/README.md) | [Simplified Chinese](https://github.com/electron/electron/tree/master/docs-translations/zh-CN/project/README.md) | [Brazilian Portuguese](https://github.com/electron/electron/tree/master/docs-translations/pt-BR/project/README.md) -O framework Electron permite escrever aplicações desktop multi-plataforma usando JavaScript, HTML e CSS. Baseia-se em [Node.js](https://nodejs.org/) e [Chromium](http://www.chromium.org) e é usado pelo [editor Atom](https://github.com/atom/atom) e muitas outras [aplicações](http://electron.atom.io/apps). +O framework Electron permite escrever aplicações desktop multi-plataforma usando JavaScript, HTML e CSS. Baseia-se em [Node.js](https://nodejs.org/) e [Chromium](http://www.chromium.org) e é usado pelo [editor Atom](https://github.com/atom/atom) e muitas outras [aplicações](https://electron.atom.io/apps). Siga [@ElectronJS](https://twitter.com/electronjs) no Twitter para anúncios importantes. diff --git a/docs-translations/pt-BR/tutorial/accessibility.md b/docs-translations/pt-BR/tutorial/accessibility.md index 21570ff405..f4168c6c9a 100644 --- a/docs-translations/pt-BR/tutorial/accessibility.md +++ b/docs-translations/pt-BR/tutorial/accessibility.md @@ -1,12 +1,12 @@ # Acessibilidade -Fazendo aplicações acessíveis é importante e nós estamos felizes em apresentar uma nova funcionalidade para [Devtron](http://electron.atom.io/devtron) e [Spectron](http://electron.atom.io/spectron) que dá aos desenvolvedores a oportunidade de fazer as suas aplicações melhor para todos. +Fazendo aplicações acessíveis é importante e nós estamos felizes em apresentar uma nova funcionalidade para [Devtron](https://electron.atom.io/devtron) e [Spectron](https://electron.atom.io/spectron) que dá aos desenvolvedores a oportunidade de fazer as suas aplicações melhor para todos. --- Preocupações de acessibilidade em aplicações Electron são semelhantes aos de websites, porque eles são ambos em última análise HTML. Com aplicativos Electron, no entanto, você não pode usar recursos on-line para auditorias de acessibilidade porque a sua aplicação não tem uma URL para apontar para o auditor. -Esses novos recursos trazem essas ferramentas de auditoria para a sua aplicação Electron. Você pode optar por adicionar auditorias aos seus testes com Spectron ou usá-los dentro do DevTools com Devtron. Leia a seguir para obter um resumo das ferramentas ou verifique nossa [documentação de acessibilidade](http://electron.atom.io/docs/tutorial/accessibility) para obter mais informações. +Esses novos recursos trazem essas ferramentas de auditoria para a sua aplicação Electron. Você pode optar por adicionar auditorias aos seus testes com Spectron ou usá-los dentro do DevTools com Devtron. Leia a seguir para obter um resumo das ferramentas ou verifique nossa [documentação de acessibilidade](https://electron.atom.io/docs/tutorial/accessibility) para obter mais informações. ### Spectron @@ -30,4 +30,4 @@ Em Devtron há uma nova guia de acessibilidade que permitirá auditar uma págin Ambas as ferramentas estão usando a biblioteca [Accessibility Developer Tools](https://github.com/GoogleChrome/accessibility-developer-tools) construída pela Google for Chrome. Você pode aprender mais sobre as regras de auditoria da biblioteca de acessibilidade no [wiki do repositório](https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules). -Se você souber de outras ferramentas de acessibilidade para o Electron, adicione-as à [documentação de acessibilidade](http://electron.atom.io/docs/tutorial/accessibility) através de um pull request. +Se você souber de outras ferramentas de acessibilidade para o Electron, adicione-as à [documentação de acessibilidade](https://electron.atom.io/docs/tutorial/accessibility) através de um pull request. diff --git a/docs-translations/ru-RU/README.md b/docs-translations/ru-RU/README.md index f853cb18f3..96df31da25 100644 --- a/docs-translations/ru-RU/README.md +++ b/docs-translations/ru-RU/README.md @@ -3,7 +3,7 @@ возможно, используете документацию ветки разработки, которая может содержать изменения api, которые не совместимы с вашей версией Electron. Если это так, Вы можете переключиться на другую версию документации в списке -[доступные версии](http://electron.atom.io/docs/) на [atom.io](atom.io), или +[доступные версии](https://electron.atom.io/docs/) на [atom.io](atom.io), или если Вы используете интерфейс GitHub, откройте список "переключение ветки/тега" и выберите тег, который соответствует вашей версии. diff --git a/docs-translations/th-TH/tutorial/about.md b/docs-translations/th-TH/tutorial/about.md index 6f7e3cf236..310682e33b 100644 --- a/docs-translations/th-TH/tutorial/about.md +++ b/docs-translations/th-TH/tutorial/about.md @@ -1,6 +1,6 @@ # เกี่ยวกับ Electron -[Electron](http://electron.atom.io) เป็นโอเพ่นซอร์สไลบรารี พัฒนา โดย GitHub สำหรับการสร้างการใช้งานเดสก์ทอปข้ามแพลตฟอร์มกับ HTML, CSS และ JavaScript Electron เกิดขึ้นได้โดยการ่วม [Chromium](https://www.chromium.org/Home) กับ [Node.js](https://nodejs.org) เข้าด้วยกันเป็นหนึงruntimeและปพลิเคชันสามารถบรรจุสำหรับ Mac, Windows และ Linux +[Electron](https://electron.atom.io) เป็นโอเพ่นซอร์สไลบรารี พัฒนา โดย GitHub สำหรับการสร้างการใช้งานเดสก์ทอปข้ามแพลตฟอร์มกับ HTML, CSS และ JavaScript Electron เกิดขึ้นได้โดยการ่วม [Chromium](https://www.chromium.org/Home) กับ [Node.js](https://nodejs.org) เข้าด้วยกันเป็นหนึงruntimeและปพลิเคชันสามารถบรรจุสำหรับ Mac, Windows และ Linux Electron began in 2013 as the framework on which [Atom](https://atom.io), GitHub's hackable text editor, would be built. The two were open sourced in the Spring of 2014. @@ -27,13 +27,13 @@ When a new version of Node.js is released, Electron usually waits about a month ### Versioning -Due to the hard dependency on Node.js and Chromium, Electron is in a tricky versioning position and [does not follow `semver`](http://semver.org). You should therefore always reference a specific version of Electron. [Read more about Electron's versioning](http://electron.atom.io/docs/tutorial/electron-versioning/) or see the [versions currently in use](https://electron.atom.io/#electron-versions). +Due to the hard dependency on Node.js and Chromium, Electron is in a tricky versioning position and [does not follow `semver`](http://semver.org). You should therefore always reference a specific version of Electron. [Read more about Electron's versioning](https://electron.atom.io/docs/tutorial/electron-versioning/) or see the [versions currently in use](https://electron.atom.io/#electron-versions). ### LTS Long term support of older versions of Electron does not currently exist. If your current version of Electron works for you, you can stay on it for as long as you'd like. If you want to make use of new features as they come in you should upgrade to a newer version. -A major update came with version `v1.0.0`. If you're not yet using this version, you should [read more about the `v1.0.0` changes](http://electron.atom.io/blog/2016/05/11/electron-1-0). +A major update came with version `v1.0.0`. If you're not yet using this version, you should [read more about the `v1.0.0` changes](https://electron.atom.io/blog/2016/05/11/electron-1-0). ## Core Philosophy @@ -41,7 +41,7 @@ In order to keep Electron small (file size) and sustainable (the spread of depen For instance, Electron uses just the rendering library from Chromium rather than all of Chromium. This makes it easier to upgrade Chromium but also means some browser features found in Google Chrome do not exist in Electron. -New features added to Electron should primarily be native APIs. If a feature can be its own Node.js module, it probably should be. See the [Electron tools built by the community](http://electron.atom.io/community). +New features added to Electron should primarily be native APIs. If a feature can be its own Node.js module, it probably should be. See the [Electron tools built by the community](https://electron.atom.io/community). ## History @@ -52,6 +52,6 @@ Below are milestones in Electron's history. | **April 2013**| [Atom Shell is started](https://github.com/electron/electron/commit/6ef8875b1e93787fa9759f602e7880f28e8e6b45).| | **May 2014** | [Atom Shell is open sourced](http://blog.atom.io/2014/05/06/atom-is-now-open-source.html). | | **April 2015** | [Atom Shell is re-named Electron](https://github.com/electron/electron/pull/1389). | -| **May 2016** | [Electron releases `v1.0.0`](http://electron.atom.io/blog/2016/05/11/electron-1-0).| -| **May 2016** | [Electron apps compatible with Mac App Store](http://electron.atom.io/docs/tutorial/mac-app-store-submission-guide).| -| **August 2016** | [Windows Store support for Electron apps](http://electron.atom.io/docs/tutorial/windows-store-guide).| +| **May 2016** | [Electron releases `v1.0.0`](https://electron.atom.io/blog/2016/05/11/electron-1-0).| +| **May 2016** | [Electron apps compatible with Mac App Store](https://electron.atom.io/docs/tutorial/mac-app-store-submission-guide).| +| **August 2016** | [Windows Store support for Electron apps](https://electron.atom.io/docs/tutorial/windows-store-guide).| diff --git a/docs-translations/th-TH/tutorial/accessibility.md b/docs-translations/th-TH/tutorial/accessibility.md index 527a57e7ed..0fda6e6a95 100644 --- a/docs-translations/th-TH/tutorial/accessibility.md +++ b/docs-translations/th-TH/tutorial/accessibility.md @@ -1,6 +1,6 @@ # การเข้าถึง (Accessibility) -การที่จะทำให้แอพพิเคชั่นนั้นเข้าถึงได้เป็นเรื่องที่สำคัญมาก และ เรามีความสุขที่จะต้อนรับความสามารถใหม่ของเราสู่ [Devtron](http://electron.atom.io/devtron) และ [Spectron](http://electron.atom.io/spectron) ซึ่งได้ให้โอกาสผู้พัฒนาในการที่จะสร้างแอพพิเคชั่นที่ดีขึ้นเพื่อทุกๆคน +การที่จะทำให้แอพพิเคชั่นนั้นเข้าถึงได้เป็นเรื่องที่สำคัญมาก และ เรามีความสุขที่จะต้อนรับความสามารถใหม่ของเราสู่ [Devtron](https://electron.atom.io/devtron) และ [Spectron](https://electron.atom.io/spectron) ซึ่งได้ให้โอกาสผู้พัฒนาในการที่จะสร้างแอพพิเคชั่นที่ดีขึ้นเพื่อทุกๆคน --- @@ -8,7 +8,7 @@ ความสามารถใหม่ๆนี้นำอุปกรณ์การแก้ไขต่างๆเข้ามาใส่แอพ Electron ของคุณ คุณสามารถเลือกที่จะแก้ไขบททดสองของคุณได้ด้วย Spectron หรือว่าใช้มันใน DevTools ด้วย Devtron -กรุณาอ่านต่อเพื่อบทสรุปของอุปกรณ์หรือดู [เอกสารการเข้าถึง](http://electron.atom.io/docs/tutorial/accessibility) ของเราสำหรับข้อมูลเพิ่มเติม +กรุณาอ่านต่อเพื่อบทสรุปของอุปกรณ์หรือดู [เอกสารการเข้าถึง](https://electron.atom.io/docs/tutorial/accessibility) ของเราสำหรับข้อมูลเพิ่มเติม ### Spectron @@ -37,4 +37,4 @@ app.client.autidAccessibility().then(function (audit) { คุณสามารถศึกษาเพิ่มเติมเกี่ยวกับมันได้ที่ [รีโปนี้](https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules) -ถ้าคุณมีความรู้เกี่ยวกับอุปกรณือื่นๆที่สามารถใช้กับ Electron ได้ โปรดใส่มันเพิ่มใน [เอกสารการเข้าถึง](http://electron.atom.io/docs/tutorial/accessibility) ด้วยการขอดึงจาก Electron (pull request) +ถ้าคุณมีความรู้เกี่ยวกับอุปกรณือื่นๆที่สามารถใช้กับ Electron ได้ โปรดใส่มันเพิ่มใน [เอกสารการเข้าถึง](https://electron.atom.io/docs/tutorial/accessibility) ด้วยการขอดึงจาก Electron (pull request) diff --git a/docs-translations/th-TH/tutorial/quick-start.md b/docs-translations/th-TH/tutorial/quick-start.md index 57e726ef52..291d22d639 100644 --- a/docs-translations/th-TH/tutorial/quick-start.md +++ b/docs-translations/th-TH/tutorial/quick-start.md @@ -238,7 +238,7 @@ $ npm start ``` For more example apps, see the -[list of boilerplates](http://electron.atom.io/community/#boilerplates) +[list of boilerplates](https://electron.atom.io/community/#boilerplates) created by the awesome electron community. [share-data]: ../faq.md#how-to-share-data-between-web-pages diff --git a/docs-translations/tr-TR/README.md b/docs-translations/tr-TR/README.md index 526ea279aa..d9d8a1bdce 100644 --- a/docs-translations/tr-TR/README.md +++ b/docs-translations/tr-TR/README.md @@ -1,6 +1,6 @@ Lütfen kullandığınız dokümanın Electron versiyonunuzla aynı olduğundan emin olun. Versiyon numarası okuduğunuz dokümanın URL'sindekiyle aynı olmalı. Eğer aynı değilse, muhtemelen geliştirme aşamasındaki API değişikliklerini içerebilecek dokümantasyonudur. -Eğer öyleyse, atom.io üzerinden [mevcut sürümler](http://electron.atom.io/docs/)e göz atabilirsiniz ya da eğer GitHub arayüzünü kullanıyorsanız "Switch branches/tags" açılır menüsünden versiyonunuza uygun olanı seçebilirsiniz. +Eğer öyleyse, atom.io üzerinden [mevcut sürümler](https://electron.atom.io/docs/)e göz atabilirsiniz ya da eğer GitHub arayüzünü kullanıyorsanız "Switch branches/tags" açılır menüsünden versiyonunuza uygun olanı seçebilirsiniz. ## SSS(Sıkça Sorulan Sorular) diff --git a/docs-translations/uk-UA/README.md b/docs-translations/uk-UA/README.md index b229da2fd5..414c2d9ecc 100644 --- a/docs-translations/uk-UA/README.md +++ b/docs-translations/uk-UA/README.md @@ -3,7 +3,7 @@ використовуєте документацію із development гілки, яка може містити зміни в API, які не сумісні з вашою версією Electron. Якщо це так, тоді Ви можете переключитись на іншу версію документації -із списку [доступних версій](http://electron.atom.io/docs/) на atom.io, +із списку [доступних версій](https://electron.atom.io/docs/) на atom.io, або якщо ви використовуєте інтеррфейс GitHub, тоді відкрийте список "Switch branches/tags" і виберіть потрібну вам версію із списку тегів. diff --git a/docs-translations/zh-CN/api/menu.md b/docs-translations/zh-CN/api/menu.md index 0baddba1da..5d31d62f1a 100644 --- a/docs-translations/zh-CN/api/menu.md +++ b/docs-translations/zh-CN/api/menu.md @@ -172,7 +172,7 @@ const template = [ submenu: [ { label: 'Learn More', - click () { require('electron').shell.openExternal('http://electron.atom.io') } + click () { require('electron').shell.openExternal('https://electron.atom.io') } } ] } diff --git a/docs-translations/zh-CN/api/webview-tag.md b/docs-translations/zh-CN/api/webview-tag.md index 8eb8eb0939..db5b809ab8 100644 --- a/docs-translations/zh-CN/api/webview-tag.md +++ b/docs-translations/zh-CN/api/webview-tag.md @@ -147,7 +147,7 @@ CSS通过 `flex` 布局设置 `width` 和 `height`,并允许元素缩小到0px ```html - + ``` 为 page 设置 session。如果初始值为 `partition` ,这个 page 将会为app中的所有 page 应用同一个持续有效的 session。如果没有 `persist:` 前缀, 这个 page 将会使用一个历史 session。通过分配使用相同的 `partition`, 所有的 page 都可以分享相同的session。如果 `partition` 没有设置,那 app 将使用默认的 session。 diff --git a/docs-translations/zh-CN/project/README.md b/docs-translations/zh-CN/project/README.md index 224243f400..711dece2fc 100644 --- a/docs-translations/zh-CN/project/README.md +++ b/docs-translations/zh-CN/project/README.md @@ -1,4 +1,4 @@ -[![Electron Logo](http://electron.atom.io/images/electron-logo.svg)](http://electron.atom.io/) +[![Electron Logo](https://electron.atom.io/images/electron-logo.svg)](https://electron.atom.io/) [![Travis Build Status](https://travis-ci.org/electron/electron.svg?branch=master)](https://travis-ci.org/electron/electron) [![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/kvxe4byi7jcxbe26/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/electron) @@ -7,7 +7,7 @@ Electron框架,让您可使用JavaScript, HTML 及 CSS 编写桌面程序。 它是基于[Node.js](https://nodejs.org/)和[Chromium](http://www.chromium.org)开发的, -[Atom editor](https://github.com/atom/atom)以及很多其他的[apps](http://electron.atom.io/apps)就是使用Electron编写的。 +[Atom editor](https://github.com/atom/atom)以及很多其他的[apps](https://electron.atom.io/apps)就是使用Electron编写的。 请关注Twitter [@ElectronJS](https://twitter.com/electronjs) 以获得重要通告。 diff --git a/docs-translations/zh-CN/tutorial/quick-start.md b/docs-translations/zh-CN/tutorial/quick-start.md index 390b311fcf..92c2fdbddd 100644 --- a/docs-translations/zh-CN/tutorial/quick-start.md +++ b/docs-translations/zh-CN/tutorial/quick-start.md @@ -195,6 +195,6 @@ $ cd electron-quick-start $ npm install && npm start ``` -更多 apps 例子,查看 electron 社区创建的 [list of boilerplates](http://electron.atom.io/community/#boilerplates)。 +更多 apps 例子,查看 electron 社区创建的 [list of boilerplates](https://electron.atom.io/community/#boilerplates)。 [share-data]: ../faq.md#how-to-share-data-between-web-pages diff --git a/docs-translations/zh-CN/tutorial/using-selenium-and-webdriver.md b/docs-translations/zh-CN/tutorial/using-selenium-and-webdriver.md index 799c482bd9..b512344312 100644 --- a/docs-translations/zh-CN/tutorial/using-selenium-and-webdriver.md +++ b/docs-translations/zh-CN/tutorial/using-selenium-and-webdriver.md @@ -155,4 +155,4 @@ client 当然,你也可以在运行 Electron 时传入参数指定你 app 的所在文件夹。这步可以免去你拷贝-粘贴你的 app 到 Electron 的资源目录。 [chrome-driver]: https://sites.google.com/a/chromium.org/chromedriver/ -[spectron]: http://electron.atom.io/spectron +[spectron]: https://electron.atom.io/spectron diff --git a/docs-translations/zh-TW/project/README.md b/docs-translations/zh-TW/project/README.md index dae51010d1..4a5d4c602b 100644 --- a/docs-translations/zh-TW/project/README.md +++ b/docs-translations/zh-TW/project/README.md @@ -1,4 +1,4 @@ -[![Electron Logo](http://electron.atom.io/images/electron-logo.svg)](http://electron.atom.io/) +[![Electron Logo](https://electron.atom.io/images/electron-logo.svg)](https://electron.atom.io/) [![Travis Build Status](https://travis-ci.org/electron/electron.svg?branch=master)](https://travis-ci.org/electron/electron) [![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/kvxe4byi7jcxbe26/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/electron) @@ -7,7 +7,7 @@ Electron框架讓你可以用JavaScript, HTML 和 CSS 編寫跨平台的應用程式。 它是基於[Node.js](https://nodejs.org/)和[Chromium](http://www.chromium.org), -並且被[Atom editor](https://github.com/atom/atom)及許多其他的[apps](http://electron.atom.io/apps)所使用。 +並且被[Atom editor](https://github.com/atom/atom)及許多其他的[apps](https://electron.atom.io/apps)所使用。 請關注[@ElectronJS](https://twitter.com/electronjs)的Twitter以獲得重要公告。 diff --git a/docs-translations/zh-TW/tutorial/about.md b/docs-translations/zh-TW/tutorial/about.md index 03852baa89..686d1d92a7 100644 --- a/docs-translations/zh-TW/tutorial/about.md +++ b/docs-translations/zh-TW/tutorial/about.md @@ -1,10 +1,10 @@ # 關於 Electron -[Electron](http://electron.atom.io) 是 GitHub 為了透過 HTML, CSS 和 JavaScript 開發跨平台桌面應用程式, 所使用的一個開放原始碼函式庫。為了達成這個目標,Electron 把 [Chromium](https://www.chromium.org/Home) 和 [Node.js](https://nodejs.org) 整合成單一的執行程式,應用程式可以在 Mac, Windows, 和 Linux 作業系統下執行。 +[Electron](https://electron.atom.io) 是 GitHub 為了透過 HTML, CSS 和 JavaScript 開發跨平台桌面應用程式, 所使用的一個開放原始碼函式庫。為了達成這個目標,Electron 把 [Chromium](https://www.chromium.org/Home) 和 [Node.js](https://nodejs.org) 整合成單一的執行程式,應用程式可以在 Mac, Windows, 和 Linux 作業系統下執行。 Electron 在 2013 年創立,做為 [Atom](https://atom.io) (一款由GitHub 推出,可以快速修改調整的文字編輯器) 的程式框架, 這兩個專案在 2014 年春天開放原始碼。 -在這之後,Electron 變成一個非成流行的工具,為許多開放原始碼開發者、新創事業,以及已發展的公司所使用(請見[Apps](http://electron.atom.io/apps/))。 +在這之後,Electron 變成一個非成流行的工具,為許多開放原始碼開發者、新創事業,以及已發展的公司所使用(請見[Apps](https://electron.atom.io/apps/))。 若要了解更多關於 Electron 開發者或發行版的資訊,或想要開始使用 Electron 開發應用程式,可以參考[快速入門](https://github.com/electron/electron/blob/master/docs-translations/zh-TW/tutorial/quick-start.md) @@ -27,13 +27,13 @@ Electron 在 Chromium 提出新的穩定版本時會提出更新,時間通常 ### 版本控制 -由於同時高度依賴 Node.js 和 Chromium, Electron 在版本控制上處於一個有點特別的情況,所以不太遵照[ `semver`規範](http://semver.org)。你必須隨時參考一個特定的 Electron 版本。請參考 [Read more about Electron's versioning](http://electron.atom.io/docs/tutorial/electron-versioning/) 或是察看 [versions currently in use](https://electron.atom.io/#electron-versions). +由於同時高度依賴 Node.js 和 Chromium, Electron 在版本控制上處於一個有點特別的情況,所以不太遵照[ `semver`規範](http://semver.org)。你必須隨時參考一個特定的 Electron 版本。請參考 [Read more about Electron's versioning](https://electron.atom.io/docs/tutorial/electron-versioning/) 或是察看 [versions currently in use](https://electron.atom.io/#electron-versions). ### 長期支援 Electron 目前並未對舊的版本提供長期支援,如果你目前使用的 Electron 版本可以運作, 你可以隨自己的喜好持續使用。如果你想使用新版本所提供的功能,你必須更新到較新的版本。 -一個主要的更新是在 `v1.0.0` 版本。如果你使用比這個版本更舊的 Electron,你必須參考 [read more about the `v1.0.0` changes](http://electron.atom.io/blog/2016/05/11/electron-1-0). +一個主要的更新是在 `v1.0.0` 版本。如果你使用比這個版本更舊的 Electron,你必須參考 [read more about the `v1.0.0` changes](https://electron.atom.io/blog/2016/05/11/electron-1-0). ## 核心哲學 @@ -41,7 +41,7 @@ Electron 目前並未對舊的版本提供長期支援,如果你目前使用 舉例來說, Electron 只使用 Chromium 在圖形渲染上的函式庫,而不使用整個 Chromium,這讓它更容易隨著 Chromium 更新,但也表示有些在 Google Chrome 瀏覽器上擁有的功能,在 Electron中並不存在。 -會加入 Electron 的新功能,主要是原生的 APIs。如果某個功能被與它相關的 Node.js 模組所擁有,它在 Electron 中也必須存在。請參考[Electron tools built by the community](http://electron.atom.io/community). +會加入 Electron 的新功能,主要是原生的 APIs。如果某個功能被與它相關的 Node.js 模組所擁有,它在 Electron 中也必須存在。請參考[Electron tools built by the community](https://electron.atom.io/community). ## 歷史 @@ -52,6 +52,6 @@ Electron 目前並未對舊的版本提供長期支援,如果你目前使用 | **2013年4月**| [Atom Shell 專案開始](https://github.com/electron/electron/commit/6ef8875b1e93787fa9759f602e7880f28e8e6b45).| | **2014年5月** | [Atom Shell 專案開放原始碼](http://blog.atom.io/2014/05/06/atom-is-now-open-source.html). | | **2015年4月** | [Atom Shell 專案重新命名為 Electron](https://github.com/electron/electron/pull/1389). | -| **2016年5月** | [Electron 發行 `v1.0.0` 版](http://electron.atom.io/blog/2016/05/11/electron-1-0).| -| **2016年5月** | [Electron 應用程式可以和 Mac App 市集相容](http://electron.atom.io/docs/tutorial/mac-app-store-submission-guide).| -| **2016年8月** | [Windows 市集支援 Electron 應用程式](http://electron.atom.io/docs/tutorial/windows-store-guide).| +| **2016年5月** | [Electron 發行 `v1.0.0` 版](https://electron.atom.io/blog/2016/05/11/electron-1-0).| +| **2016年5月** | [Electron 應用程式可以和 Mac App 市集相容](https://electron.atom.io/docs/tutorial/mac-app-store-submission-guide).| +| **2016年8月** | [Windows 市集支援 Electron 應用程式](https://electron.atom.io/docs/tutorial/windows-store-guide).| diff --git a/docs-translations/zh-TW/tutorial/accessibility.md b/docs-translations/zh-TW/tutorial/accessibility.md index 35ecf81183..3ecbea66b6 100644 --- a/docs-translations/zh-TW/tutorial/accessibility.md +++ b/docs-translations/zh-TW/tutorial/accessibility.md @@ -1,12 +1,12 @@ # 可存取性 -產生具可存取性的應用程式是非常重要的,我們非常高興能介紹 [Devtron](http://electron.atom.io/devtron) 和 [Spectron](http://electron.atom.io/spectron) 這兩個新功能,這可以讓開發者更有機會能開發對大家來說更棒的應用程式。 +產生具可存取性的應用程式是非常重要的,我們非常高興能介紹 [Devtron](https://electron.atom.io/devtron) 和 [Spectron](https://electron.atom.io/spectron) 這兩個新功能,這可以讓開發者更有機會能開發對大家來說更棒的應用程式。 --- 在 Electron 應用程式中,可存取性的議題和在開發網站時非常類似,因為在根本上兩者都使用 HTML。然而對 Electron 應用程式來說,你不能為了增加可存取性而使用線上的程式審計機制,因為你的應用程式並沒有一個 URL 連結能夠引導審計者. -這些新的功能也為你的 Electron 應用程式帶來新的審計工具,你可以選擇要透過 Spectron 為你的測試增加審計,或是透過 Devtron 在開發者工具中使用。請參考 [accessibility documentation](http://electron.atom.io/docs/tutorial/accessibility) 以獲得更多資訊。 +這些新的功能也為你的 Electron 應用程式帶來新的審計工具,你可以選擇要透過 Spectron 為你的測試增加審計,或是透過 Devtron 在開發者工具中使用。請參考 [accessibility documentation](https://electron.atom.io/docs/tutorial/accessibility) 以獲得更多資訊。 ### Spectron @@ -30,4 +30,4 @@ app.client.auditAccessibility().then(function (audit) { 這兩個工具都使用 Google 為 Chrome 所建立的 [Accessibility Developer Tools](https://github.com/GoogleChrome/accessibility-developer-tools) 函式庫。你可以在 [repository's wiki](https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules) 學到更多在這個函式庫中有關可存取性審計的資訊。 -如果你知道其他有關 Electron 可存取性來說更好的工具, 請把他們透過 pull request 加入 [accessibility documentation](http://electron.atom.io/docs/tutorial/accessibility) 。 +如果你知道其他有關 Electron 可存取性來說更好的工具, 請把他們透過 pull request 加入 [accessibility documentation](https://electron.atom.io/docs/tutorial/accessibility) 。 diff --git a/docs/api/clipboard.md b/docs/api/clipboard.md index 932a1da7b2..e0fdd42438 100644 --- a/docs/api/clipboard.md +++ b/docs/api/clipboard.md @@ -103,7 +103,7 @@ clipboard. ```js clipboard.write({ - text: 'http://electron.atom.io', + text: 'https://electron.atom.io', bookmark: 'Electron Homepage' }) ``` diff --git a/docs/api/menu.md b/docs/api/menu.md index 466d226a16..cd6817625a 100644 --- a/docs/api/menu.md +++ b/docs/api/menu.md @@ -192,7 +192,7 @@ const template = [ submenu: [ { label: 'Learn More', - click () { require('electron').shell.openExternal('http://electron.atom.io') } + click () { require('electron').shell.openExternal('https://electron.atom.io') } } ] } diff --git a/docs/api/webview-tag.md b/docs/api/webview-tag.md index 4887ecaa62..3a726f6e04 100644 --- a/docs/api/webview-tag.md +++ b/docs/api/webview-tag.md @@ -177,7 +177,7 @@ Web security is enabled by default. ```html - + ``` Sets the session used by the page. If `partition` starts with `persist:`, the diff --git a/docs/tutorial/about.md b/docs/tutorial/about.md index a816c094b4..f812a810e9 100644 --- a/docs/tutorial/about.md +++ b/docs/tutorial/about.md @@ -1,10 +1,10 @@ # About Electron -[Electron](http://electron.atom.io) is an open source library developed by GitHub for building cross-platform desktop applications with HTML, CSS, and JavaScript. Electron accomplishes this by combining [Chromium](https://www.chromium.org/Home) and [Node.js](https://nodejs.org) into a single runtime and apps can be packaged for Mac, Windows, and Linux. +[Electron](https://electron.atom.io) is an open source library developed by GitHub for building cross-platform desktop applications with HTML, CSS, and JavaScript. Electron accomplishes this by combining [Chromium](https://www.chromium.org/Home) and [Node.js](https://nodejs.org) into a single runtime and apps can be packaged for Mac, Windows, and Linux. Electron began in 2013 as the framework on which [Atom](https://atom.io), GitHub's hackable text editor, would be built. The two were open sourced in the Spring of 2014. -It has since become a popular tool used by open source developers, startups, and established companies. [See who is building on Electron](http://electron.atom.io/apps/). +It has since become a popular tool used by open source developers, startups, and established companies. [See who is building on Electron](https://electron.atom.io/apps/). Read on to learn more about the contributors and releases of Electron or get started building with Electron in the [Quick Start Guide](quick-start.md). @@ -27,13 +27,13 @@ In Electron, Node.js and Chromium share a single V8 instance—usually the versi ### Versioning -Due to the hard dependency on Node.js and Chromium, Electron is in a tricky versioning position and [does not follow `semver`](http://semver.org). You should therefore always reference a specific version of Electron. [Read more about Electron's versioning](http://electron.atom.io/docs/tutorial/electron-versioning/) or see the [versions currently in use](https://electron.atom.io/#electron-versions). +Due to the hard dependency on Node.js and Chromium, Electron is in a tricky versioning position and [does not follow `semver`](http://semver.org). You should therefore always reference a specific version of Electron. [Read more about Electron's versioning](https://electron.atom.io/docs/tutorial/electron-versioning/) or see the [versions currently in use](https://electron.atom.io/#electron-versions). ### LTS Long term support of older versions of Electron does not currently exist. If your current version of Electron works for you, you can stay on it for as long as you'd like. If you want to make use of new features as they come in you should upgrade to a newer version. -A major update came with version `v1.0.0`. If you're not yet using this version, you should [read more about the `v1.0.0` changes](http://electron.atom.io/blog/2016/05/11/electron-1-0). +A major update came with version `v1.0.0`. If you're not yet using this version, you should [read more about the `v1.0.0` changes](https://electron.atom.io/blog/2016/05/11/electron-1-0). ## Core Philosophy @@ -41,7 +41,7 @@ In order to keep Electron small (file size) and sustainable (the spread of depen For instance, Electron uses just the rendering library from Chromium rather than all of Chromium. This makes it easier to upgrade Chromium but also means some browser features found in Google Chrome do not exist in Electron. -New features added to Electron should primarily be native APIs. If a feature can be its own Node.js module, it probably should be. See the [Electron tools built by the community](http://electron.atom.io/community). +New features added to Electron should primarily be native APIs. If a feature can be its own Node.js module, it probably should be. See the [Electron tools built by the community](https://electron.atom.io/community). ## History @@ -52,6 +52,6 @@ Below are milestones in Electron's history. | **April 2013**| [Atom Shell is started](https://github.com/electron/electron/commit/6ef8875b1e93787fa9759f602e7880f28e8e6b45).| | **May 2014** | [Atom Shell is open sourced](http://blog.atom.io/2014/05/06/atom-is-now-open-source.html). | | **April 2015** | [Atom Shell is re-named Electron](https://github.com/electron/electron/pull/1389). | -| **May 2016** | [Electron releases `v1.0.0`](http://electron.atom.io/blog/2016/05/11/electron-1-0).| -| **May 2016** | [Electron apps compatible with Mac App Store](http://electron.atom.io/docs/tutorial/mac-app-store-submission-guide).| -| **August 2016** | [Windows Store support for Electron apps](http://electron.atom.io/docs/tutorial/windows-store-guide).| +| **May 2016** | [Electron releases `v1.0.0`](https://electron.atom.io/blog/2016/05/11/electron-1-0).| +| **May 2016** | [Electron apps compatible with Mac App Store](https://electron.atom.io/docs/tutorial/mac-app-store-submission-guide).| +| **August 2016** | [Windows Store support for Electron apps](https://electron.atom.io/docs/tutorial/windows-store-guide).| diff --git a/docs/tutorial/accessibility.md b/docs/tutorial/accessibility.md index 22b9704bdb..d256ff5567 100644 --- a/docs/tutorial/accessibility.md +++ b/docs/tutorial/accessibility.md @@ -1,12 +1,12 @@ # Accessibility -Making accessible applications is important and we're happy to introduce new functionality to [Devtron](http://electron.atom.io/devtron) and [Spectron](http://electron.atom.io/spectron) that gives developers the opportunity to make their apps better for everyone. +Making accessible applications is important and we're happy to introduce new functionality to [Devtron](https://electron.atom.io/devtron) and [Spectron](https://electron.atom.io/spectron) that gives developers the opportunity to make their apps better for everyone. --- Accessibility concerns in Electron applications are similar to those of websites because they're both ultimately HTML. With Electron apps, however, you can't use the online resources for accessibility audits because your app doesn't have a URL to point the auditor to. -These new features bring those auditing tools to your Electron app. You can choose to add audits to your tests with Spectron or use them within DevTools with Devtron. Read on for a summary of the tools or checkout our [accessibility documentation](http://electron.atom.io/docs/tutorial/accessibility) for more information. +These new features bring those auditing tools to your Electron app. You can choose to add audits to your tests with Spectron or use them within DevTools with Devtron. Read on for a summary of the tools or checkout our [accessibility documentation](https://electron.atom.io/docs/tutorial/accessibility) for more information. ### Spectron @@ -30,4 +30,4 @@ In Devtron, there is a new accessibility tab which will allow you to audit a pag Both of these tools are using the [Accessibility Developer Tools](https://github.com/GoogleChrome/accessibility-developer-tools) library built by Google for Chrome. You can learn more about the accessibility audit rules this library uses on that [repository's wiki](https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules). -If you know of other great accessibility tools for Electron, add them to the [accessibility documentation](http://electron.atom.io/docs/tutorial/accessibility) with a pull request. +If you know of other great accessibility tools for Electron, add them to the [accessibility documentation](https://electron.atom.io/docs/tutorial/accessibility) with a pull request. diff --git a/docs/tutorial/quick-start.md b/docs/tutorial/quick-start.md index d95957a48c..f490b73889 100644 --- a/docs/tutorial/quick-start.md +++ b/docs/tutorial/quick-start.md @@ -238,7 +238,7 @@ $ npm start ``` For more example apps, see the -[list of boilerplates](http://electron.atom.io/community/#boilerplates) +[list of boilerplates](https://electron.atom.io/community/#boilerplates) created by the awesome electron community. [share-data]: ../faq.md#how-to-share-data-between-web-pages diff --git a/docs/tutorial/using-selenium-and-webdriver.md b/docs/tutorial/using-selenium-and-webdriver.md index 464d1ce99f..f7fe2347d9 100644 --- a/docs/tutorial/using-selenium-and-webdriver.md +++ b/docs/tutorial/using-selenium-and-webdriver.md @@ -169,4 +169,4 @@ your app's folder. This eliminates the need to copy-paste your app into Electron's resource directory. [chrome-driver]: https://sites.google.com/a/chromium.org/chromedriver/ -[spectron]: http://electron.atom.io/spectron +[spectron]: https://electron.atom.io/spectron diff --git a/spec/api-clipboard-spec.js b/spec/api-clipboard-spec.js index 2b4aac9fae..fdd5711d11 100644 --- a/spec/api-clipboard-spec.js +++ b/spec/api-clipboard-spec.js @@ -45,10 +45,10 @@ describe('clipboard module', function () { it('returns title and url', function () { if (process.platform === 'linux') return - clipboard.writeBookmark('a title', 'http://electron.atom.io') + clipboard.writeBookmark('a title', 'https://electron.atom.io') assert.deepEqual(clipboard.readBookmark(), { title: 'a title', - url: 'http://electron.atom.io' + url: 'https://electron.atom.io' }) clipboard.writeText('no bookmark') From 166bf7ab8c04ecc9f3a16b44f49b4355bec6324b Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Tue, 28 Feb 2017 21:21:34 -0800 Subject: [PATCH 252/925] link to default docs URL from default app nav --- default_app/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/default_app/index.html b/default_app/index.html index 11d9938318..3f29908a56 100644 --- a/default_app/index.html +++ b/default_app/index.html @@ -149,7 +149,7 @@ From 15bf7ed53657b023bb75234bdfd41fa29401bb96 Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Tue, 28 Feb 2017 21:24:31 -0800 Subject: [PATCH 253/925] use https URLs for images --- docs/development/setting-up-symbol-server.md | 4 ++-- docs/tutorial/desktop-environment-integration.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/development/setting-up-symbol-server.md b/docs/development/setting-up-symbol-server.md index 098fd2a5df..100384d4d3 100644 --- a/docs/development/setting-up-symbol-server.md +++ b/docs/development/setting-up-symbol-server.md @@ -43,8 +43,8 @@ SRV*c:\code\symbols\*http://msdl.microsoft.com/download/symbols;SRV*c:\code\symb ## Using the symbol server in Visual Studio - - + + ## Troubleshooting: Symbols will not load diff --git a/docs/tutorial/desktop-environment-integration.md b/docs/tutorial/desktop-environment-integration.md index cbe1021c58..412341fe70 100644 --- a/docs/tutorial/desktop-environment-integration.md +++ b/docs/tutorial/desktop-environment-integration.md @@ -65,7 +65,7 @@ the application via JumpList or dock menu, respectively. __JumpList:__ -![JumpList Recent Files](http://i.msdn.microsoft.com/dynimg/IC420538.png) +![JumpList Recent Files](https://cloud.githubusercontent.com/assets/2289/23446924/11a27b98-fdfc-11e6-8485-cc3b1e86b80a.png) __Application dock menu:__ From 34e21cb91f572c351a53784fc08bf87f7227d47b Mon Sep 17 00:00:00 2001 From: Tom Floyer Date: Wed, 1 Mar 2017 12:03:00 +0300 Subject: [PATCH 254/925] Fixed typo --- docs-translations/ru-RU/tutorial/quick-start.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs-translations/ru-RU/tutorial/quick-start.md b/docs-translations/ru-RU/tutorial/quick-start.md index a0916fc5da..f485e477f7 100644 --- a/docs-translations/ru-RU/tutorial/quick-start.md +++ b/docs-translations/ru-RU/tutorial/quick-start.md @@ -25,7 +25,7 @@ __главным процессом__. Скрипт, который работа реальные ресурсы компьютера. Пользователи Electron напротив могут использовать API Node.js на страницах, что допускает более низкоуровневую работу с операционной системой. -### Разница мужду главным процессом и процессом-рендерером +### Разница между главным процессом и процессом-рендерером Главный процесс создаёт веб-страницы используя `BrowserWindow`. Каждый экземпляр `BrowserWindow` показывает веб-страницу через свой собственный процесс-рендерер. From d009b3267a864db68a68cbc06f06cb4f8c6f65f9 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 28 Feb 2017 09:21:25 -0800 Subject: [PATCH 255/925] Bump v1.6.2 --- atom/browser/resources/mac/Info.plist | 4 ++-- atom/browser/resources/win/atom.rc | 8 ++++---- atom/common/atom_version.h | 2 +- electron.gyp | 2 +- package.json | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/atom/browser/resources/mac/Info.plist b/atom/browser/resources/mac/Info.plist index ed10c6fc37..48160b9e33 100644 --- a/atom/browser/resources/mac/Info.plist +++ b/atom/browser/resources/mac/Info.plist @@ -17,9 +17,9 @@ CFBundleIconFile electron.icns CFBundleVersion - 1.6.1 + 1.6.2 CFBundleShortVersionString - 1.6.1 + 1.6.2 LSApplicationCategoryType public.app-category.developer-tools LSMinimumSystemVersion diff --git a/atom/browser/resources/win/atom.rc b/atom/browser/resources/win/atom.rc index abda92004f..c94af0f2f2 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,6,1,0 - PRODUCTVERSION 1,6,1,0 + FILEVERSION 1,6,2,0 + PRODUCTVERSION 1,6,2,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -74,12 +74,12 @@ BEGIN BEGIN VALUE "CompanyName", "GitHub, Inc." VALUE "FileDescription", "Electron" - VALUE "FileVersion", "1.6.1" + VALUE "FileVersion", "1.6.2" VALUE "InternalName", "electron.exe" VALUE "LegalCopyright", "Copyright (C) 2015 GitHub, Inc. All rights reserved." VALUE "OriginalFilename", "electron.exe" VALUE "ProductName", "Electron" - VALUE "ProductVersion", "1.6.1" + VALUE "ProductVersion", "1.6.2" VALUE "SquirrelAwareVersion", "1" END END diff --git a/atom/common/atom_version.h b/atom/common/atom_version.h index 9b824c0364..d3bd04ca01 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 6 -#define ATOM_PATCH_VERSION 1 +#define ATOM_PATCH_VERSION 2 #define ATOM_VERSION_IS_RELEASE 1 diff --git a/electron.gyp b/electron.gyp index 9a31b0edba..9fd7c1091f 100644 --- a/electron.gyp +++ b/electron.gyp @@ -4,7 +4,7 @@ 'product_name%': 'Electron', 'company_name%': 'GitHub, Inc', 'company_abbr%': 'github', - 'version%': '1.6.1', + 'version%': '1.6.2', 'js2c_input_dir': '<(SHARED_INTERMEDIATE_DIR)/js2c', }, 'includes': [ diff --git a/package.json b/package.json index e880e8ce75..66e7af7fb6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "electron", - "version": "1.6.1", + "version": "1.6.2", "devDependencies": { "asar": "^0.11.0", "browserify": "^13.1.0", From 4aaa936803ffd5f985dd0e42a7b69238f6e67a22 Mon Sep 17 00:00:00 2001 From: Felix Rieseberg Date: Wed, 1 Mar 2017 11:48:51 -0800 Subject: [PATCH 256/925] Fix init check for process.windowsStore Windows now allows users to move their `WindowsApps` folder, meaning that it can end up on a different drive (and outside of `Program Files`). --- lib/common/init.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/common/init.js b/lib/common/init.js index 44939b52f4..00acd6f06a 100644 --- a/lib/common/init.js +++ b/lib/common/init.js @@ -56,7 +56,7 @@ if (process.platform === 'win32') { // // Nobody else get's to install there, changing the path is forbidden // We can therefore say that we're running as appx - if (__dirname.indexOf('\\Program Files\\WindowsApps\\') === 2) { + if (__dirname.includes('\\WindowsApps\\')) { process.windowsStore = true } } From c06c4c0ccb6593185b6de14e59c62c8e906e3b8e Mon Sep 17 00:00:00 2001 From: Nitish Sakhawalkar Date: Wed, 1 Mar 2017 14:04:41 -0800 Subject: [PATCH 257/925] Reverting formatting changes made by clang format. --- atom/browser/api/atom_api_web_contents.cc | 212 +++++++++++----------- 1 file changed, 107 insertions(+), 105 deletions(-) diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 4690f8c0b0..7733abb23a 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -90,7 +90,7 @@ struct PrintSettings { namespace mate { -template <> +template<> struct Converter { static bool FromV8(v8::Isolate* isolate, v8::Local val, @@ -112,10 +112,9 @@ struct Converter { } }; -template <> +template<> struct Converter { - static bool FromV8(v8::Isolate* isolate, - v8::Local val, + static bool FromV8(v8::Isolate* isolate, v8::Local val, PrintSettings* out) { mate::Dictionary dict; if (!ConvertFromV8(isolate, val, &dict)) @@ -126,7 +125,7 @@ struct Converter { } }; -template <> +template<> struct Converter { static v8::Local ToV8(v8::Isolate* isolate, WindowOpenDisposition val) { @@ -155,10 +154,9 @@ struct Converter { } }; -template <> +template<> struct Converter { - static bool FromV8(v8::Isolate* isolate, - v8::Local val, + static bool FromV8(v8::Isolate* isolate, v8::Local val, content::SavePageType* out) { std::string save_type; if (!ConvertFromV8(isolate, val, &save_type)) @@ -177,36 +175,24 @@ struct Converter { } }; -template <> +template<> struct Converter { static v8::Local ToV8(v8::Isolate* isolate, atom::api::WebContents::Type val) { using Type = atom::api::WebContents::Type; std::string type = ""; switch (val) { - case Type::BACKGROUND_PAGE: - type = "backgroundPage"; - break; - case Type::BROWSER_WINDOW: - type = "window"; - break; - case Type::REMOTE: - type = "remote"; - break; - case Type::WEB_VIEW: - type = "webview"; - break; - case Type::OFF_SCREEN: - type = "offscreen"; - break; - default: - break; + case Type::BACKGROUND_PAGE: type = "backgroundPage"; break; + case Type::BROWSER_WINDOW: type = "window"; break; + case Type::REMOTE: type = "remote"; break; + case Type::WEB_VIEW: type = "webview"; break; + case Type::OFF_SCREEN: type = "offscreen"; break; + default: break; } return mate::ConvertToV8(isolate, type); } - static bool FromV8(v8::Isolate* isolate, - v8::Local val, + static bool FromV8(v8::Isolate* isolate, v8::Local val, atom::api::WebContents::Type* out) { using Type = atom::api::WebContents::Type; std::string type; @@ -227,6 +213,7 @@ struct Converter { } // namespace mate + namespace atom { namespace api { @@ -320,10 +307,10 @@ WebContents::WebContents(v8::Isolate* isolate, const mate::Dictionary& options) content::WebContents* web_contents; if (IsGuest()) { scoped_refptr site_instance = - content::SiteInstance::CreateForURL(session->browser_context(), - GURL("chrome-guest://fake-host")); - content::WebContents::CreateParams params(session->browser_context(), - site_instance); + content::SiteInstance::CreateForURL( + session->browser_context(), GURL("chrome-guest://fake-host")); + content::WebContents::CreateParams params( + session->browser_context(), site_instance); guest_delegate_.reset(new WebViewGuestDelegate); params.guest_delegate = guest_delegate_.get(); web_contents = content::WebContents::Create(params); @@ -348,7 +335,7 @@ WebContents::WebContents(v8::Isolate* isolate, const mate::Dictionary& options) } void WebContents::InitWithSessionAndOptions(v8::Isolate* isolate, - content::WebContents* web_contents, + content::WebContents *web_contents, mate::Handle session, const mate::Dictionary& options) { Observe(web_contents); @@ -454,8 +441,8 @@ void WebContents::AddNewContents(content::WebContents* source, v8::HandleScope handle_scope(isolate()); auto api_web_contents = CreateFrom(isolate(), new_contents); if (Emit("-add-new-contents", api_web_contents, disposition, user_gesture, - initial_rect.x(), initial_rect.y(), initial_rect.width(), - initial_rect.height())) { + initial_rect.x(), initial_rect.y(), initial_rect.width(), + initial_rect.height())) { api_web_contents->DestroyWebContents(); } } @@ -533,8 +520,8 @@ bool WebContents::PreHandleKeyboardEvent( content::WebContents* source, const content::NativeWebKeyboardEvent& event, bool* is_keyboard_shortcut) { - if (event.type == blink::WebInputEvent::Type::RawKeyDown || - event.type == blink::WebInputEvent::Type::KeyUp) + if (event.type == blink::WebInputEvent::Type::RawKeyDown + || event.type == blink::WebInputEvent::Type::KeyUp) return Emit("before-input-event", event); else return false; @@ -542,7 +529,8 @@ bool WebContents::PreHandleKeyboardEvent( void WebContents::EnterFullscreenModeForTab(content::WebContents* source, const GURL& origin) { - auto permission_helper = WebContentsPermissionHelper::FromWebContents(source); + auto permission_helper = + WebContentsPermissionHelper::FromWebContents(source); auto callback = base::Bind(&WebContents::OnEnterFullscreenModeForTab, base::Unretained(this), source, origin); permission_helper->RequestFullscreenPermission(callback); @@ -612,9 +600,10 @@ void WebContents::FindReply(content::WebContents* web_contents, Emit("found-in-page", result); } -bool WebContents::CheckMediaAccessPermission(content::WebContents* web_contents, - const GURL& security_origin, - content::MediaStreamType type) { +bool WebContents::CheckMediaAccessPermission( + content::WebContents* web_contents, + const GURL& security_origin, + content::MediaStreamType type) { return true; } @@ -627,9 +616,10 @@ void WebContents::RequestMediaAccessPermission( permission_helper->RequestMediaAccessPermission(request, callback); } -void WebContents::RequestToLockMouse(content::WebContents* web_contents, - bool user_gesture, - bool last_unlocked_by_target) { +void WebContents::RequestToLockMouse( + content::WebContents* web_contents, + bool user_gesture, + bool last_unlocked_by_target) { auto permission_helper = WebContentsPermissionHelper::FromWebContents(web_contents); permission_helper->RequestPointerLockPermission(user_gesture); @@ -720,17 +710,26 @@ void WebContents::DidStopLoading() { void WebContents::DidGetResourceResponseStart( const content::ResourceRequestDetails& details) { - Emit("did-get-response-details", details.socket_address.IsEmpty(), - details.url, details.original_url, details.http_response_code, - details.method, details.referrer, details.headers.get(), + Emit("did-get-response-details", + details.socket_address.IsEmpty(), + details.url, + details.original_url, + details.http_response_code, + details.method, + details.referrer, + details.headers.get(), ResourceTypeToString(details.resource_type)); } void WebContents::DidGetRedirectForResourceRequest( const content::ResourceRedirectDetails& details) { - Emit("did-get-redirect-request", details.url, details.new_url, + Emit("did-get-redirect-request", + details.url, + details.new_url, (details.resource_type == content::RESOURCE_TYPE_MAIN_FRAME), - details.http_response_code, details.method, details.referrer, + details.http_response_code, + details.method, + details.referrer, details.headers.get()); } @@ -795,8 +794,8 @@ void WebContents::DevToolsOpened() { // Set inspected tabID. base::FundamentalValue tab_id(ID()); - managed_web_contents()->CallClientFunction("DevToolsAPI.setInspectedTabId", - &tab_id, nullptr, nullptr); + managed_web_contents()->CallClientFunction( + "DevToolsAPI.setInspectedTabId", &tab_id, nullptr, nullptr); // Inherit owner window in devtools. if (owner_window()) @@ -825,7 +824,7 @@ bool WebContents::OnMessageReceived(const IPC::Message& message) { IPC_MESSAGE_HANDLER_DELAY_REPLY(AtomViewHostMsg_GetZoomLevel, OnGetZoomLevel) IPC_MESSAGE_HANDLER_CODE(ViewHostMsg_SetCursor, OnCursorChange, - handled = false) + handled = false) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() @@ -859,13 +858,14 @@ void WebContents::WebContentsDestroyed() { Emit("destroyed"); // Destroy the native class in next tick. - base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, GetDestroyClosure()); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, GetDestroyClosure()); } void WebContents::NavigationEntryCommitted( const content::LoadCommittedDetails& details) { - Emit("navigation-entry-commited", details.entry->GetURL(), details.is_in_page, - details.did_replace_entry); + Emit("navigation-entry-commited", details.entry->GetURL(), + details.is_in_page, details.did_replace_entry); } int64_t WebContents::GetID() const { @@ -889,9 +889,11 @@ bool WebContents::Equal(const WebContents* web_contents) const { void WebContents::LoadURL(const GURL& url, const mate::Dictionary& options) { if (!url.is_valid() || url.spec().size() > url::kMaxURLChars) { - Emit("did-fail-load", static_cast(net::ERR_INVALID_URL), + Emit("did-fail-load", + static_cast(net::ERR_INVALID_URL), net::ErrorToShortString(net::ERR_INVALID_URL), - url.possibly_invalid_spec(), true); + url.possibly_invalid_spec(), + true); return; } @@ -921,7 +923,7 @@ void WebContents::LoadURL(const GURL& url, const mate::Dictionary& options) { params.base_url_for_data_url = base_url_for_data_url; params.load_type = content::NavigationController::LOAD_TYPE_DATA; } - + params.transition_type = ui::PAGE_TRANSITION_TYPED; params.should_clear_history_list = true; params.override_user_agent = content::NavigationController::UA_OVERRIDE_TRUE; @@ -947,7 +949,7 @@ void WebContents::LoadURL(const GURL& url, const mate::Dictionary& options) { void WebContents::DownloadURL(const GURL& url) { auto browser_context = web_contents()->GetBrowserContext(); auto download_manager = - content::BrowserContext::GetDownloadManager(browser_context); + content::BrowserContext::GetDownloadManager(browser_context); download_manager->DownloadUrl( content::DownloadUrlParameters::CreateForWebContentsMainFrame( @@ -971,8 +973,7 @@ bool WebContents::IsLoadingMainFrame() const { // instance when navigating, regardless of origin. See AtomBrowserClient. return (web_contents()->GetLastCommittedURL().is_empty() || web_contents()->GetSiteInstance() != - web_contents()->GetPendingSiteInstance()) && - IsLoading(); + web_contents()->GetPendingSiteInstance()) && IsLoading(); } bool WebContents::IsWaitingForResponse() const { @@ -1116,13 +1117,15 @@ void WebContents::InspectServiceWorker() { } } -void WebContents::HasServiceWorker(const base::Callback& callback) { +void WebContents::HasServiceWorker( + const base::Callback& callback) { auto context = GetServiceWorkerContext(web_contents()); if (!context) return; context->CheckHasServiceWorker(web_contents()->GetLastCommittedURL(), - GURL::EmptyGURL(), callback); + GURL::EmptyGURL(), + callback); } void WebContents::UnregisterServiceWorker( @@ -1144,21 +1147,22 @@ bool WebContents::IsAudioMuted() { } void WebContents::Print(mate::Arguments* args) { - PrintSettings settings = {false, false}; + PrintSettings settings = { false, false }; if (args->Length() == 1 && !args->GetNext(&settings)) { args->ThrowError(); return; } - printing::PrintViewManagerBasic::FromWebContents(web_contents()) - ->PrintNow(web_contents()->GetMainFrame(), settings.silent, - settings.print_background); + printing::PrintViewManagerBasic::FromWebContents(web_contents())-> + PrintNow(web_contents()->GetMainFrame(), + settings.silent, + settings.print_background); } void WebContents::PrintToPDF(const base::DictionaryValue& setting, const PrintToPDFCallback& callback) { - printing::PrintPreviewMessageHandler::FromWebContents(web_contents()) - ->PrintToPDF(setting, callback); + printing::PrintPreviewMessageHandler::FromWebContents(web_contents())-> + PrintToPDF(setting, callback); } void WebContents::AddWorkSpace(mate::Arguments* args, @@ -1263,8 +1267,7 @@ void WebContents::Focus() { #if !defined(OS_MACOSX) bool WebContents::IsFocused() const { auto view = web_contents()->GetRenderWidgetHostView(); - if (!view) - return false; + if (!view) return false; if (GetType() != BACKGROUND_PAGE) { auto window = web_contents()->GetNativeView()->GetToplevelWindow(); @@ -1316,8 +1319,8 @@ void WebContents::SendInputEvent(v8::Isolate* isolate, } } - isolate->ThrowException( - v8::Exception::Error(mate::StringToV8(isolate, "Invalid event object"))); + isolate->ThrowException(v8::Exception::Error(mate::StringToV8( + isolate, "Invalid event object"))); } void WebContents::BeginFrameSubscription(mate::Arguments* args) { @@ -1332,8 +1335,8 @@ void WebContents::BeginFrameSubscription(mate::Arguments* args) { const auto view = web_contents()->GetRenderWidgetHostView(); if (view) { - std::unique_ptr frame_subscriber( - new FrameSubscriber(isolate(), view, callback, only_dirty)); + std::unique_ptr frame_subscriber(new FrameSubscriber( + isolate(), view, callback, only_dirty)); view->BeginFrameSubscription(std::move(frame_subscriber)); } } @@ -1386,8 +1389,8 @@ void WebContents::CapturePage(mate::Arguments* args) { base::Callback callback; if (!(args->Length() == 1 && args->GetNext(&callback)) && - !(args->Length() == 2 && args->GetNext(&rect) && - args->GetNext(&callback))) { + !(args->Length() == 2 && args->GetNext(&rect) + && args->GetNext(&callback))) { args->ThrowError(); return; } @@ -1400,21 +1403,22 @@ void WebContents::CapturePage(mate::Arguments* args) { } // Capture full page if user doesn't specify a |rect|. - const gfx::Size view_size = - rect.IsEmpty() ? view->GetViewBounds().size() : rect.size(); + const gfx::Size view_size = rect.IsEmpty() ? view->GetViewBounds().size() : + rect.size(); // By default, the requested bitmap size is the view size in screen // coordinates. However, if there's more pixel detail available on the // current system, increase the requested bitmap size to capture it all. gfx::Size bitmap_size = view_size; const gfx::NativeView native_view = view->GetNativeView(); - const float scale = display::Screen::GetScreen() - ->GetDisplayNearestWindow(native_view) - .device_scale_factor(); + const float scale = + display::Screen::GetScreen()->GetDisplayNearestWindow(native_view) + .device_scale_factor(); if (scale > 1.0f) bitmap_size = gfx::ScaleToCeiledSize(view_size, scale); - host->CopyFromBackingStore(gfx::Rect(rect.origin(), view_size), bitmap_size, + host->CopyFromBackingStore(gfx::Rect(rect.origin(), view_size), + bitmap_size, base::Bind(&OnCapturePageDone, callback), kBGRA_8888_SkColorType); } @@ -1425,10 +1429,10 @@ void WebContents::OnCursorChange(const content::WebCursor& cursor) { if (cursor.IsCustom()) { Emit("cursor-changed", CursorTypeToString(info), - gfx::Image::CreateFrom1xBitmap(info.custom_image), - info.image_scale_factor, - gfx::Size(info.custom_image.width(), info.custom_image.height()), - info.hotspot); + gfx::Image::CreateFrom1xBitmap(info.custom_image), + info.image_scale_factor, + gfx::Size(info.custom_image.width(), info.custom_image.height()), + info.hotspot); } else { Emit("cursor-changed", CursorTypeToString(info)); } @@ -1634,8 +1638,10 @@ void WebContents::BuildPrototype(v8::Isolate* isolate, .SetMethod("closeDevTools", &WebContents::CloseDevTools) .SetMethod("isDevToolsOpened", &WebContents::IsDevToolsOpened) .SetMethod("isDevToolsFocused", &WebContents::IsDevToolsFocused) - .SetMethod("enableDeviceEmulation", &WebContents::EnableDeviceEmulation) - .SetMethod("disableDeviceEmulation", &WebContents::DisableDeviceEmulation) + .SetMethod("enableDeviceEmulation", + &WebContents::EnableDeviceEmulation) + .SetMethod("disableDeviceEmulation", + &WebContents::DisableDeviceEmulation) .SetMethod("toggleDevTools", &WebContents::ToggleDevTools) .SetMethod("inspectElement", &WebContents::InspectElement) .SetMethod("setAudioMuted", &WebContents::SetAudioMuted) @@ -1658,7 +1664,8 @@ void WebContents::BuildPrototype(v8::Isolate* isolate, .SetMethod("tabTraverse", &WebContents::TabTraverse) .SetMethod("_send", &WebContents::SendIPCMessage) .SetMethod("sendInputEvent", &WebContents::SendInputEvent) - .SetMethod("beginFrameSubscription", &WebContents::BeginFrameSubscription) + .SetMethod("beginFrameSubscription", + &WebContents::BeginFrameSubscription) .SetMethod("endFrameSubscription", &WebContents::EndFrameSubscription) .SetMethod("startDrag", &WebContents::StartDrag) .SetMethod("setSize", &WebContents::SetSize) @@ -1716,30 +1723,27 @@ void WebContents::OnRendererMessageSync(const base::string16& channel, // static mate::Handle WebContents::CreateFrom( - v8::Isolate* isolate, - content::WebContents* web_contents) { + v8::Isolate* isolate, content::WebContents* web_contents) { // We have an existing WebContents object in JS. auto existing = TrackableObject::FromWrappedClass(isolate, web_contents); if (existing) return mate::CreateHandle(isolate, static_cast(existing)); // Otherwise create a new WebContents wrapper object. - return mate::CreateHandle(isolate, - new WebContents(isolate, web_contents, REMOTE)); + return mate::CreateHandle(isolate, new WebContents(isolate, web_contents, + REMOTE)); } mate::Handle WebContents::CreateFrom( - v8::Isolate* isolate, - content::WebContents* web_contents, - Type type) { + v8::Isolate* isolate, content::WebContents* web_contents, Type type) { // Otherwise create a new WebContents wrapper object. - return mate::CreateHandle(isolate, - new WebContents(isolate, web_contents, type)); + return mate::CreateHandle(isolate, new WebContents(isolate, web_contents, + type)); } // static -mate::Handle WebContents::Create(v8::Isolate* isolate, - const mate::Dictionary& options) { +mate::Handle WebContents::Create( + v8::Isolate* isolate, const mate::Dictionary& options) { return mate::CreateHandle(isolate, new WebContents(isolate, options)); } @@ -1751,10 +1755,8 @@ namespace { using atom::api::WebContents; -void Initialize(v8::Local exports, - v8::Local unused, - v8::Local context, - void* priv) { +void Initialize(v8::Local exports, v8::Local unused, + v8::Local context, void* priv) { v8::Isolate* isolate = context->GetIsolate(); mate::Dictionary dict(isolate, exports); dict.Set("WebContents", WebContents::GetConstructor(isolate)->GetFunction()); From a0605275b91e9e0dc6d9a5c5bcd03960de26ca25 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 28 Feb 2017 09:56:09 +0900 Subject: [PATCH 258/925] Clean up node environment on exit in main process --- atom/browser/atom_browser_main_parts.cc | 1 + atom/browser/atom_browser_main_parts.h | 2 ++ atom/browser/javascript_environment.cc | 9 +++++++++ atom/browser/javascript_environment.h | 17 +++++++++++++++++ 4 files changed, 29 insertions(+) diff --git a/atom/browser/atom_browser_main_parts.cc b/atom/browser/atom_browser_main_parts.cc index e386e678a4..4031fb27cf 100644 --- a/atom/browser/atom_browser_main_parts.cc +++ b/atom/browser/atom_browser_main_parts.cc @@ -132,6 +132,7 @@ void AtomBrowserMainParts::PostEarlyInitialization() { // Create the global environment. node::Environment* env = node_bindings_->CreateEnvironment(js_env_->context()); + node_env_.reset(new NodeEnvironment(env)); // Make sure node can get correct environment when debugging. if (node_debugger_->IsRunning()) diff --git a/atom/browser/atom_browser_main_parts.h b/atom/browser/atom_browser_main_parts.h index 0d8619f686..71adc43d5e 100644 --- a/atom/browser/atom_browser_main_parts.h +++ b/atom/browser/atom_browser_main_parts.h @@ -22,6 +22,7 @@ class Browser; class JavascriptEnvironment; class NodeBindings; class NodeDebugger; +class NodeEnvironment; class BridgeTaskRunner; class AtomBrowserMainParts : public brightray::BrowserMainParts { @@ -79,6 +80,7 @@ class AtomBrowserMainParts : public brightray::BrowserMainParts { std::unique_ptr browser_; std::unique_ptr js_env_; + std::unique_ptr node_env_; std::unique_ptr node_bindings_; std::unique_ptr atom_bindings_; std::unique_ptr node_debugger_; diff --git a/atom/browser/javascript_environment.cc b/atom/browser/javascript_environment.cc index 3cdd2c771e..b3e01c1c30 100644 --- a/atom/browser/javascript_environment.cc +++ b/atom/browser/javascript_environment.cc @@ -12,6 +12,8 @@ #include "gin/array_buffer.h" #include "gin/v8_initializer.h" +#include "atom/common/node_includes.h" + namespace atom { JavascriptEnvironment::JavascriptEnvironment() @@ -46,4 +48,11 @@ bool JavascriptEnvironment::Initialize() { return true; } +NodeEnvironment::NodeEnvironment(node::Environment* env) : env_(env) { +} + +NodeEnvironment::~NodeEnvironment() { + node::FreeEnvironment(env_); +} + } // namespace atom diff --git a/atom/browser/javascript_environment.h b/atom/browser/javascript_environment.h index 1f4d2f4534..046ba3e29c 100644 --- a/atom/browser/javascript_environment.h +++ b/atom/browser/javascript_environment.h @@ -8,8 +8,13 @@ #include "base/macros.h" #include "gin/public/isolate_holder.h" +namespace node { +class Environment; +} + namespace atom { +// Manage the V8 isolate and context automatically. class JavascriptEnvironment { public: JavascriptEnvironment(); @@ -37,6 +42,18 @@ class JavascriptEnvironment { DISALLOW_COPY_AND_ASSIGN(JavascriptEnvironment); }; +// Manage the Node Environment automatically. +class NodeEnvironment { + public: + NodeEnvironment(node::Environment* env); + ~NodeEnvironment(); + + private: + node::Environment* env_; + + DISALLOW_COPY_AND_ASSIGN(NodeEnvironment); +}; + } // namespace atom #endif // ATOM_BROWSER_JAVASCRIPT_ENVIRONMENT_H_ From d379b05890ef0a461dbc37c5026227b89f81f23f Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 28 Feb 2017 10:58:52 +0900 Subject: [PATCH 259/925] async handles should be closed on exit --- atom/common/api/atom_bindings.cc | 1 + atom/common/node_bindings.cc | 1 + 2 files changed, 2 insertions(+) diff --git a/atom/common/api/atom_bindings.cc b/atom/common/api/atom_bindings.cc index 2b4bec6328..b747fabe9d 100644 --- a/atom/common/api/atom_bindings.cc +++ b/atom/common/api/atom_bindings.cc @@ -84,6 +84,7 @@ AtomBindings::AtomBindings() { } AtomBindings::~AtomBindings() { + uv_close(reinterpret_cast(&call_next_tick_async_), nullptr); } void AtomBindings::BindTo(v8::Isolate* isolate, diff --git a/atom/common/node_bindings.cc b/atom/common/node_bindings.cc index 0737314d07..4cf2ba56e4 100644 --- a/atom/common/node_bindings.cc +++ b/atom/common/node_bindings.cc @@ -117,6 +117,7 @@ NodeBindings::~NodeBindings() { // Clear uv. uv_sem_destroy(&embed_sem_); + uv_close(reinterpret_cast(&dummy_uv_handle_), nullptr); } void NodeBindings::Initialize() { From 29278e500b7c5d289b9a48a85b7fd51a9fc44f96 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 2 Mar 2017 16:49:39 +0900 Subject: [PATCH 260/925] Destroy node environment when a JS context in renderer is destroyed --- atom/renderer/atom_renderer_client.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index 9979aae900..318a9d38cd 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -381,6 +381,9 @@ void AtomRendererClient::WillReleaseScriptContext( node::Environment* env = node::Environment::GetCurrent(context); if (env) mate::EmitEvent(env->isolate(), env->process_object(), "exit"); + + // Destroy the node environment. + node::FreeEnvironment(env); } bool AtomRendererClient::ShouldFork(blink::WebLocalFrame* frame, From 24574f7299ae382b1b7a2623b8c409ef419702ed Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 2 Mar 2017 16:50:15 +0900 Subject: [PATCH 261/925] Avoid touch an Environemnt after it gets destroyed --- atom/common/api/atom_bindings.cc | 7 +++++++ atom/common/api/atom_bindings.h | 3 +++ atom/renderer/atom_renderer_client.cc | 1 + 3 files changed, 11 insertions(+) diff --git a/atom/common/api/atom_bindings.cc b/atom/common/api/atom_bindings.cc index b747fabe9d..f1b56c6759 100644 --- a/atom/common/api/atom_bindings.cc +++ b/atom/common/api/atom_bindings.cc @@ -118,6 +118,13 @@ void AtomBindings::BindTo(v8::Isolate* isolate, } } +void AtomBindings::EnvironmentDestroyed(node::Environment* env) { + auto it = std::find(pending_next_ticks_.begin(), pending_next_ticks_.end(), + env); + if (it != pending_next_ticks_.end()) + pending_next_ticks_.erase(it); +} + void AtomBindings::ActivateUVLoop(v8::Isolate* isolate) { node::Environment* env = node::Environment::GetCurrent(isolate); if (std::find(pending_next_ticks_.begin(), pending_next_ticks_.end(), env) != diff --git a/atom/common/api/atom_bindings.h b/atom/common/api/atom_bindings.h index 58c2336c0e..3fd43cc8a1 100644 --- a/atom/common/api/atom_bindings.h +++ b/atom/common/api/atom_bindings.h @@ -27,6 +27,9 @@ class AtomBindings { // load native code from Electron instead. void BindTo(v8::Isolate* isolate, v8::Local process); + // Should be called when a node::Environment has been destroyed. + void EnvironmentDestroyed(node::Environment* env); + static void Log(const base::string16& message); static void Crash(); diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index 318a9d38cd..79aea0ee0b 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -384,6 +384,7 @@ void AtomRendererClient::WillReleaseScriptContext( // Destroy the node environment. node::FreeEnvironment(env); + atom_bindings_->EnvironmentDestroyed(env); } bool AtomRendererClient::ShouldFork(blink::WebLocalFrame* frame, From 1709e74958dfcbf070af046592e805cdbd152689 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 2 Mar 2017 17:18:00 +0900 Subject: [PATCH 262/925] Fix crash when the main frame is replaced --- atom/renderer/atom_renderer_client.cc | 15 +++++++++------ atom/renderer/atom_renderer_client.h | 3 +++ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index 79aea0ee0b..cbea7f7a56 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -206,7 +206,8 @@ std::vector ParseSchemesCLISwitch(const char* switch_name) { } // namespace AtomRendererClient::AtomRendererClient() - : node_bindings_(NodeBindings::Create(false)), + : node_integration_initialized_(false), + node_bindings_(NodeBindings::Create(false)), atom_bindings_(new AtomBindings) { isolated_world_ = base::CommandLine::ForCurrentProcess()->HasSwitch( switches::kContextIsolation); @@ -342,11 +343,9 @@ void AtomRendererClient::DidCreateScriptContext( if (!render_frame->IsMainFrame() && !IsDevToolsExtension(render_frame)) return; - // Whether the node binding has been initialized. - bool first_time = node_bindings_->uv_env() == nullptr; - // Prepare the node bindings. - if (first_time) { + if (!node_integration_initialized_) { + node_integration_initialized_ = true; node_bindings_->Initialize(); node_bindings_->PrepareMessageLoop(); } @@ -362,7 +361,7 @@ void AtomRendererClient::DidCreateScriptContext( // Load everything. node_bindings_->LoadEnvironment(env); - if (first_time) { + if (node_bindings_->uv_env() == nullptr) { // Make uv loop being wrapped by window context. node_bindings_->set_uv_env(env); @@ -382,6 +381,10 @@ void AtomRendererClient::WillReleaseScriptContext( if (env) mate::EmitEvent(env->isolate(), env->process_object(), "exit"); + // The main frame may be replaced. + if (env == node_bindings_->uv_env()) + node_bindings_->set_uv_env(nullptr); + // Destroy the node environment. node::FreeEnvironment(env); atom_bindings_->EnvironmentDestroyed(env); diff --git a/atom/renderer/atom_renderer_client.h b/atom/renderer/atom_renderer_client.h index a693262fed..c1d86cddf5 100644 --- a/atom/renderer/atom_renderer_client.h +++ b/atom/renderer/atom_renderer_client.h @@ -66,6 +66,9 @@ class AtomRendererClient : public content::ContentRendererClient { std::vector>* key_systems) override; + // Whether the node integration has been initialized. + bool node_integration_initialized_; + std::unique_ptr node_bindings_; std::unique_ptr atom_bindings_; std::unique_ptr preferences_manager_; From cf198904a4f7824252e4f9cbaef575f50c08fd66 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 2 Mar 2017 17:26:15 +0900 Subject: [PATCH 263/925] Fix cpplint warnings --- atom/browser/javascript_environment.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atom/browser/javascript_environment.h b/atom/browser/javascript_environment.h index 046ba3e29c..43a7295f90 100644 --- a/atom/browser/javascript_environment.h +++ b/atom/browser/javascript_environment.h @@ -45,7 +45,7 @@ class JavascriptEnvironment { // Manage the Node Environment automatically. class NodeEnvironment { public: - NodeEnvironment(node::Environment* env); + explicit NodeEnvironment(node::Environment* env); ~NodeEnvironment(); private: From 7f6283352f9f1b9c2579a416c93726355c010356 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 1 Mar 2017 08:48:03 -0800 Subject: [PATCH 264/925] Add failing spec for fetch from isolated world --- spec/fixtures/api/isolated-preload.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/spec/fixtures/api/isolated-preload.js b/spec/fixtures/api/isolated-preload.js index 9ff121929c..4093c37d8f 100644 --- a/spec/fixtures/api/isolated-preload.js +++ b/spec/fixtures/api/isolated-preload.js @@ -1,9 +1,15 @@ +// Ensure fetch works from isolated world origin +fetch('http://localhost:1234') +fetch('https://localhost:1234') +fetch(`file://${__filename}`) + const {ipcRenderer, webFrame} = require('electron') window.foo = 3 webFrame.executeJavaScript('window.preloadExecuteJavaScriptProperty = 1234;') + window.addEventListener('message', (event) => { ipcRenderer.send('isolated-world', { preloadContext: { From 7fb77ee1cb9d57f2e59d6adc5dd159746b11a608 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 1 Mar 2017 08:48:35 -0800 Subject: [PATCH 265/925] Set isolated world security origin --- atom/renderer/atom_renderer_client.cc | 11 +++++++++-- spec/fixtures/api/isolated-preload.js | 1 - 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index 9979aae900..9f91c90196 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -86,14 +86,21 @@ class AtomRenderFrameObserver : public content::RenderFrameObserver { } void CreateIsolatedWorldContext() { + auto frame = render_frame_->GetWebFrame(); + // This maps to the name shown in the context combo box in the Console tab // of the dev tools. - render_frame_->GetWebFrame()->setIsolatedWorldHumanReadableName( + frame->setIsolatedWorldHumanReadableName( World::ISOLATED_WORLD, blink::WebString::fromUTF8("Electron Isolated Context")); + // Setup document's origin policy in isolated world + frame->setIsolatedWorldSecurityOrigin( + World::ISOLATED_WORLD, frame->document().getSecurityOrigin()); + + // Create initial script context in isolated world blink::WebScriptSource source("void 0"); - render_frame_->GetWebFrame()->executeScriptInIsolatedWorld( + frame->executeScriptInIsolatedWorld( World::ISOLATED_WORLD, &source, 1, ExtensionGroup::MAIN_GROUP); } diff --git a/spec/fixtures/api/isolated-preload.js b/spec/fixtures/api/isolated-preload.js index 4093c37d8f..0f19cdea82 100644 --- a/spec/fixtures/api/isolated-preload.js +++ b/spec/fixtures/api/isolated-preload.js @@ -9,7 +9,6 @@ window.foo = 3 webFrame.executeJavaScript('window.preloadExecuteJavaScriptProperty = 1234;') - window.addEventListener('message', (event) => { ipcRenderer.send('isolated-world', { preloadContext: { From bcda67b8eb2ee0db117cc0ecac838afe0e8b1e6c Mon Sep 17 00:00:00 2001 From: Nitish Sakhawalkar Date: Thu, 2 Mar 2017 10:50:48 -0800 Subject: [PATCH 266/925] No more extra whitespace. --- atom/browser/api/atom_api_web_contents.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 7733abb23a..a7cd223f93 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -923,7 +923,7 @@ void WebContents::LoadURL(const GURL& url, const mate::Dictionary& options) { params.base_url_for_data_url = base_url_for_data_url; params.load_type = content::NavigationController::LOAD_TYPE_DATA; } - + params.transition_type = ui::PAGE_TRANSITION_TYPED; params.should_clear_history_list = true; params.override_user_agent = content::NavigationController::UA_OVERRIDE_TRUE; From e8c0813f4638e3c89524125c57687addba1b88ea Mon Sep 17 00:00:00 2001 From: Nitish Sakhawalkar Date: Thu, 2 Mar 2017 10:58:39 -0800 Subject: [PATCH 267/925] Changes based on review. --- docs/api/browser-window.md | 2 +- docs/api/web-contents.md | 2 +- docs/api/webview-tag.md | 2 +- spec/api-browser-window-spec.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index b263420b75..6a9431a753 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -1004,7 +1004,7 @@ Same as `webContents.capturePage([rect, ]callback)`. * `userAgent` String (optional) - A user agent originating the request. * `extraHeaders` String (optional) - Extra headers separated by "\n" * `postData` ([UploadRawData](structures/upload-raw-data.md) | [UploadFile](structures/upload-file.md) | [UploadFileSystem](structures/upload-file-system.md) | [UploadBlob](structures/upload-blob.md))[] - (optional) - * `baseURLForDataURL` String(optional) - Base url for files to be loaded by the data url. This is needed only if the specified `url` is a data url and needs to load other files. + * `baseURLForDataURL` String (optional) - Base url for files to be loaded by the data url. This is needed only if the specified `url` is a data url and needs to load other files. Same as `webContents.loadURL(url[, options])`. diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index e0ed37136f..314bed167a 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -541,7 +541,7 @@ that can't be set via `` attributes. * `userAgent` String (optional) - A user agent originating the request. * `extraHeaders` String (optional) - Extra headers separated by "\n" * `postData` ([UploadRawData](structures/upload-raw-data.md) | [UploadFile](structures/upload-file.md) | [UploadFileSystem](structures/upload-file-system.md) | [UploadBlob](structures/upload-blob.md))[] - (optional) - * `baseURLForDataURL` String(optional) - Base url for files to be loaded by the data url. This is needed only if the specified `url` is a data url and needs to load other files. + * `baseURLForDataURL` String (optional) - Base url for files to be loaded by the data url. This is needed only if the specified `url` is a data url and needs to load other files. Loads the `url` in the window. The `url` must contain the protocol prefix, e.g. the `http://` or `file://`. If the load should bypass http cache then diff --git a/docs/api/webview-tag.md b/docs/api/webview-tag.md index ccd6a9b686..292af6bc20 100644 --- a/docs/api/webview-tag.md +++ b/docs/api/webview-tag.md @@ -310,7 +310,7 @@ webview.addEventListener('dom-ready', () => { * `userAgent` String (optional) - A user agent originating the request. * `extraHeaders` String (optional) - Extra headers separated by "\n" * `postData` ([UploadRawData](structures/upload-raw-data.md) | [UploadFile](structures/upload-file.md) | [UploadFileSystem](structures/upload-file-system.md) | [UploadBlob](structures/upload-blob.md))[] - (optional) - * `baseURLForDataURL` String(optional) - Base url for files to be loaded by the data url. This is needed only if the specified `url` is a data url and needs to load other files. + * `baseURLForDataURL` String (optional) - Base url for files to be loaded by the data url. This is needed only if the specified `url` is a data url and needs to load other files. Loads the `url` in the webview, the `url` must contain the protocol prefix, e.g. the `http://` or `file://`. diff --git a/spec/api-browser-window-spec.js b/spec/api-browser-window-spec.js index 3a19b3a31e..78e94b27e3 100644 --- a/spec/api-browser-window-spec.js +++ b/spec/api-browser-window-spec.js @@ -261,7 +261,7 @@ describe('BrowserWindow module', function () { assert.equal(test, 'test') done() }) - w.loadURL('data:text/html,', {baseURLForDataURL: 'file://' + path.join(fixtures, 'api') + path.sep}) + w.loadURL('data:text/html,', {baseURLForDataURL: `file://${path.join(fixtures, 'api')}${path.sep}`}) }) it('sets the content type header on multi part forms', function (done) { From 370562b129ce4de7b82cd9ad9ebbe7c2e1ff43ca Mon Sep 17 00:00:00 2001 From: Nitish Sakhawalkar Date: Thu, 2 Mar 2017 12:14:18 -0800 Subject: [PATCH 268/925] Moving data url test outside post navigation block. Trailing separator comment in the docs. --- docs/api/browser-window.md | 2 +- docs/api/web-contents.md | 2 +- docs/api/webview-tag.md | 2 +- spec/api-browser-window-spec.js | 15 ++++++++------- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index 6a9431a753..50fbc146be 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -1004,7 +1004,7 @@ Same as `webContents.capturePage([rect, ]callback)`. * `userAgent` String (optional) - A user agent originating the request. * `extraHeaders` String (optional) - Extra headers separated by "\n" * `postData` ([UploadRawData](structures/upload-raw-data.md) | [UploadFile](structures/upload-file.md) | [UploadFileSystem](structures/upload-file-system.md) | [UploadBlob](structures/upload-blob.md))[] - (optional) - * `baseURLForDataURL` String (optional) - Base url for files to be loaded by the data url. This is needed only if the specified `url` is a data url and needs to load other files. + * `baseURLForDataURL` String (optional) - Base url (with trailing path separator) for files to be loaded by the data url. This is needed only if the specified `url` is a data url and needs to load other files. Same as `webContents.loadURL(url[, options])`. diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index 314bed167a..5e3e8dd697 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -541,7 +541,7 @@ that can't be set via `` attributes. * `userAgent` String (optional) - A user agent originating the request. * `extraHeaders` String (optional) - Extra headers separated by "\n" * `postData` ([UploadRawData](structures/upload-raw-data.md) | [UploadFile](structures/upload-file.md) | [UploadFileSystem](structures/upload-file-system.md) | [UploadBlob](structures/upload-blob.md))[] - (optional) - * `baseURLForDataURL` String (optional) - Base url for files to be loaded by the data url. This is needed only if the specified `url` is a data url and needs to load other files. + * `baseURLForDataURL` String (optional) - Base url (with trailing path separator) for files to be loaded by the data url. This is needed only if the specified `url` is a data url and needs to load other files. Loads the `url` in the window. The `url` must contain the protocol prefix, e.g. the `http://` or `file://`. If the load should bypass http cache then diff --git a/docs/api/webview-tag.md b/docs/api/webview-tag.md index 292af6bc20..95cd5599d1 100644 --- a/docs/api/webview-tag.md +++ b/docs/api/webview-tag.md @@ -310,7 +310,7 @@ webview.addEventListener('dom-ready', () => { * `userAgent` String (optional) - A user agent originating the request. * `extraHeaders` String (optional) - Extra headers separated by "\n" * `postData` ([UploadRawData](structures/upload-raw-data.md) | [UploadFile](structures/upload-file.md) | [UploadFileSystem](structures/upload-file-system.md) | [UploadBlob](structures/upload-blob.md))[] - (optional) - * `baseURLForDataURL` String (optional) - Base url for files to be loaded by the data url. This is needed only if the specified `url` is a data url and needs to load other files. + * `baseURLForDataURL` String (optional) - Base url (with trailing path separator) for files to be loaded by the data url. This is needed only if the specified `url` is a data url and needs to load other files. Loads the `url` in the webview, the `url` must contain the protocol prefix, e.g. the `http://` or `file://`. diff --git a/spec/api-browser-window-spec.js b/spec/api-browser-window-spec.js index 78e94b27e3..5852c8d533 100644 --- a/spec/api-browser-window-spec.js +++ b/spec/api-browser-window-spec.js @@ -256,13 +256,6 @@ describe('BrowserWindow module', function () { w.loadURL(server.url) }) - it('should support support base url for data urls', (done) => { - ipcMain.once('answer', function (event, test) { - assert.equal(test, 'test') - done() - }) - w.loadURL('data:text/html,', {baseURLForDataURL: `file://${path.join(fixtures, 'api')}${path.sep}`}) - }) it('sets the content type header on multi part forms', function (done) { w.webContents.on('did-finish-load', () => { @@ -286,6 +279,14 @@ describe('BrowserWindow module', function () { w.loadURL(server.url) }) }) + + it('should support support base url for data urls', (done) => { + ipcMain.once('answer', function (event, test) { + assert.equal(test, 'test') + done() + }) + w.loadURL('data:text/html,', {baseURLForDataURL: `file://${path.join(fixtures, 'api')}${path.sep}`}) + }) }) describe('will-navigate event', function () { From 01423f79b3fd3aa8270abd657fb83da64e39c57c Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Thu, 2 Mar 2017 13:27:04 -0800 Subject: [PATCH 269/925] denote webview process so linter will recognize it as an API --- docs/api/webview-tag.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/api/webview-tag.md b/docs/api/webview-tag.md index 4887ecaa62..fe7b96d235 100644 --- a/docs/api/webview-tag.md +++ b/docs/api/webview-tag.md @@ -2,6 +2,8 @@ > Display external web content in an isolated frame and process. +Process: [Renderer](../tutorial/quick-start.md#renderer-process) + Use the `webview` tag to embed 'guest' content (such as web pages) in your Electron app. The guest content is contained within the `webview` container. An embedded page within your app controls how the guest content is laid out and From 5ee7434a54711bf60e06f4f8b713f2b60d25436d Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Thu, 2 Mar 2017 13:28:58 -0800 Subject: [PATCH 270/925] fix indentation of app.setLoginItemSettings arguments --- docs/api/app.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/api/app.md b/docs/api/app.md index 77b1e5fa21..e3160e9053 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -820,11 +820,11 @@ Returns `Object`: `app.getLoginItemStatus().wasOpenedAsHidden` should be checked when the app is opened to know the current value. This setting is only supported on macOS. - * `path` String (optional) _Windows_ - The executable to launch at login. - Defaults to `process.execPath`. - * `args` String[] (optional) _Windows_ - The command-line arguments to pass to - the executable. Defaults to an empty array. Take care to wrap paths in - quotes. +* `path` String (optional) _Windows_ - The executable to launch at login. + Defaults to `process.execPath`. +* `args` String[] (optional) _Windows_ - The command-line arguments to pass to + the executable. Defaults to an empty array. Take care to wrap paths in + quotes. Set the app's login item settings. From 23eecac288f4ba80f09781b4e2859a23d64513dc Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Thu, 2 Mar 2017 13:29:46 -0800 Subject: [PATCH 271/925] document arguments for webview methods --- docs/api/webview-tag.md | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/docs/api/webview-tag.md b/docs/api/webview-tag.md index fe7b96d235..8aef854c15 100644 --- a/docs/api/webview-tag.md +++ b/docs/api/webview-tag.md @@ -537,20 +537,42 @@ Stops any `findInPage` request for the `webview` with the provided `action`. ### `.print([options])` +* `options` Object (optional) + * `silent` Boolean - Don't ask user for print settings. Default is `false`. + * `printBackground` Boolean - Also prints the background color and image of + the web page. Default is `false`. + Prints `webview`'s web page. Same as `webContents.print([options])`. ### `.printToPDF(options, callback)` +* `options` Object + * `marginsType` Integer - (optional) Specifies the type of margins to use. Uses 0 for + default margin, 1 for no margin, and 2 for minimum margin. + * `pageSize` String - (optional) Specify page size of the generated PDF. Can be `A3`, + `A4`, `A5`, `Legal`, `Letter`, `Tabloid` or an Object containing `height` + and `width` in microns. + * `printBackground` Boolean - (optional) Whether to print CSS backgrounds. + * `printSelectionOnly` Boolean - (optional) Whether to print selection only. + * `landscape` Boolean - (optional) `true` for landscape, `false` for portrait. +* `callback` Function + * `error` Error + * `data` Buffer + Prints `webview`'s web page as PDF, Same as `webContents.printToPDF(options, callback)`. ### `.capturePage([rect, ]callback)` +* `rect` [Rectangle](structures/rectangle.md) (optional) - The area of the page to be captured +* `callback` Function + * `image` [NativeImage](native-image.md) + Captures a snapshot of the `webview`'s page. Same as `webContents.capturePage([rect, ]callback)`. ### `.send(channel[, arg1][, arg2][, ...])` * `channel` String -* `arg` (optional) +* `...args` any[] Send an asynchronous message to renderer process via `channel`, you can also send arbitrary arguments. The renderer process can handle the message by From ffc7a4c8a0b26f59e8c993af48c4bcb09edb8c0a Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Thu, 2 Mar 2017 13:57:04 -0800 Subject: [PATCH 272/925] move platform labels to end of description for consistency --- docs/api/dialog.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/api/dialog.md b/docs/api/dialog.md index b96cbbe9aa..230930abd9 100644 --- a/docs/api/dialog.md +++ b/docs/api/dialog.md @@ -38,14 +38,14 @@ The `dialog` module has the following methods: * `openDirectory` - Allow directories to be selected. * `multiSelections` - Allow multiple paths to be selected. * `showHiddenFiles` - Show hidden files in dialog. - * `createDirectory` _macOS_ - Allow creating new directories from dialog. - * `promptToCreate` _Windows_ - Prompt for creation if the file path entered + * `createDirectory` - Allow creating new directories from dialog. _macOS_ + * `promptToCreate` - Prompt for creation if the file path entered in the dialog does not exist. This does not actually create the file at the path but allows non-existent paths to be returned that should be - created by the application. - * `noResolveAliases` _macOS_ - Disable the automatic alias (symlink) path + created by the application. _Windows_ + * `noResolveAliases` - Disable the automatic alias (symlink) path resolution. Selected aliases will now return the alias path instead of - their target path. + their target path. _macOS_ * `normalizeAccessKeys` Boolean (optional) - Normalize the keyboard access keys across platforms. Default is `false`. Enabling this assumes `&` is used in the button labels for the placement of the keyboard shortcut access key From d75db0ea1a6f56294b7e69bcc4a1002f8909ab8b Mon Sep 17 00:00:00 2001 From: Nitish Sakhawalkar Date: Thu, 2 Mar 2017 14:37:09 -0800 Subject: [PATCH 273/925] Fixed js lint issues. --- spec/api-browser-window-spec.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spec/api-browser-window-spec.js b/spec/api-browser-window-spec.js index 5852c8d533..bb05eb5aab 100644 --- a/spec/api-browser-window-spec.js +++ b/spec/api-browser-window-spec.js @@ -256,7 +256,6 @@ describe('BrowserWindow module', function () { w.loadURL(server.url) }) - it('sets the content type header on multi part forms', function (done) { w.webContents.on('did-finish-load', () => { w.webContents.session.webRequest.onBeforeSendHeaders((details, callback) => { @@ -279,7 +278,7 @@ describe('BrowserWindow module', function () { w.loadURL(server.url) }) }) - + it('should support support base url for data urls', (done) => { ipcMain.once('answer', function (event, test) { assert.equal(test, 'test') From 67dc1b1070fec25990ca0c8001ad82b3f135618a Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Fri, 3 Mar 2017 10:53:30 -0800 Subject: [PATCH 274/925] use updated docs linter with webview support --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 66e7af7fb6..33be7e6068 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "asar": "^0.11.0", "browserify": "^13.1.0", "electabul": "~0.0.4", - "electron-docs-linter": "^1.16.1", + "electron-docs-linter": "^2.1.0", "request": "*", "standard": "^8.4.0", "standard-markdown": "^2.1.1" From 703b5738c8df36decee7891be3b9158b9283d095 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Sun, 27 Nov 2016 16:57:01 +1100 Subject: [PATCH 275/925] Initial TouchBar Magic * Make the AtomNSWindow also a NSTouchbarDelegate * Implement basic makeTouchBar and makeItemForIdentifier methods * Initial sending of touch / update events through IPC to BrowserWindowObjects TODO: * JS API * JS Object Converters * Generalize methods so that popovers can work --- atom/browser/api/atom_api_window.cc | 9 ++++ atom/browser/api/atom_api_window.h | 2 + atom/browser/native_window.cc | 10 ++++ atom/browser/native_window.h | 4 ++ atom/browser/native_window_mac.h | 1 + atom/browser/native_window_mac.mm | 78 +++++++++++++++++++++++++++ atom/browser/native_window_observer.h | 1 + 7 files changed, 105 insertions(+) diff --git a/atom/browser/api/atom_api_window.cc b/atom/browser/api/atom_api_window.cc index 2f97a88faa..1107a628a9 100644 --- a/atom/browser/api/atom_api_window.cc +++ b/atom/browser/api/atom_api_window.cc @@ -282,6 +282,10 @@ void Window::OnExecuteWindowsCommand(const std::string& command_name) { Emit("app-command", command_name); } +void Window::OnTouchBarItemResult(const std::string& item_type, const std::string& item_id) { + Emit("_touch-bar-interaction", item_type, item_id); +} + #if defined(OS_WIN) void Window::OnWindowMessage(UINT message, WPARAM w_param, LPARAM l_param) { if (IsWindowMessageHooked(message)) { @@ -840,6 +844,10 @@ void Window::SetVibrancy(mate::Arguments* args) { window_->SetVibrancy(type); } +void Window::InitTouchBar() { + window_->InitTouchBar(); +} + int32_t Window::ID() const { return weak_map_id(); } @@ -960,6 +968,7 @@ void Window::BuildPrototype(v8::Isolate* isolate, .SetMethod("setAutoHideCursor", &Window::SetAutoHideCursor) #endif .SetMethod("setVibrancy", &Window::SetVibrancy) + .SetMethod("initTouchBar", &Window::InitTouchBar) #if defined(OS_WIN) .SetMethod("hookWindowMessage", &Window::HookWindowMessage) .SetMethod("isWindowMessageHooked", &Window::IsWindowMessageHooked) diff --git a/atom/browser/api/atom_api_window.h b/atom/browser/api/atom_api_window.h index 80af78a5b0..3325c87fa9 100644 --- a/atom/browser/api/atom_api_window.h +++ b/atom/browser/api/atom_api_window.h @@ -85,6 +85,7 @@ class Window : public mate::TrackableObject, void OnRendererUnresponsive() override; void OnRendererResponsive() override; void OnExecuteWindowsCommand(const std::string& command_name) override; + void OnTouchBarItemResult(const std::string& item_type, const std::string& item_id) override; #if defined(OS_WIN) void OnWindowMessage(UINT message, WPARAM w_param, LPARAM l_param) override; @@ -203,6 +204,7 @@ class Window : public mate::TrackableObject, void SetAutoHideCursor(bool auto_hide); void SetVibrancy(mate::Arguments* args); + void InitTouchBar(); v8::Local WebContents(v8::Isolate* isolate); diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index eae68c37f5..2219a4d595 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -340,6 +340,9 @@ void NativeWindow::SetAutoHideCursor(bool auto_hide) { void NativeWindow::SetVibrancy(const std::string& filename) { } +void NativeWindow::InitTouchBar() { +} + void NativeWindow::FocusOnWebView() { web_contents()->GetRenderViewHost()->GetWidget()->Focus(); } @@ -565,6 +568,13 @@ void NativeWindow::NotifyWindowExecuteWindowsCommand( observer.OnExecuteWindowsCommand(command); } +void NativeWindow::NotifyTouchBarItemInteraction( + const std::string& type, + const std::string& item_id) { + FOR_EACH_OBSERVER(NativeWindowObserver, observers_, + OnTouchBarItemResult(type, item_id)); + } + #if defined(OS_WIN) void NativeWindow::NotifyWindowMessage( UINT message, WPARAM w_param, LPARAM l_param) { diff --git a/atom/browser/native_window.h b/atom/browser/native_window.h index aa5e7e0c71..3338396146 100644 --- a/atom/browser/native_window.h +++ b/atom/browser/native_window.h @@ -169,6 +169,9 @@ class NativeWindow : public base::SupportsUserData, // Vibrancy API virtual void SetVibrancy(const std::string& type); + // Touchbar API + virtual void InitTouchBar(); + // Webview APIs. virtual void FocusOnWebView(); virtual void BlurWebView(); @@ -228,6 +231,7 @@ class NativeWindow : public base::SupportsUserData, void NotifyWindowEnterHtmlFullScreen(); void NotifyWindowLeaveHtmlFullScreen(); void NotifyWindowExecuteWindowsCommand(const std::string& command); + void NotifyTouchBarItemInteraction(const std::string& item_type, const std::string& item_id); #if defined(OS_WIN) void NotifyWindowMessage(UINT message, WPARAM w_param, LPARAM l_param); diff --git a/atom/browser/native_window_mac.h b/atom/browser/native_window_mac.h index 2beb55c029..fec5eacb3a 100644 --- a/atom/browser/native_window_mac.h +++ b/atom/browser/native_window_mac.h @@ -100,6 +100,7 @@ class NativeWindowMac : public NativeWindow, void SetAutoHideCursor(bool auto_hide) override; void SetVibrancy(const std::string& type) override; + void InitTouchBar() override; // content::RenderWidgetHost::InputEventObserver: void OnInputEvent(const blink::WebInputEvent& event) override; diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index acbef9906a..029cf6002d 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -351,9 +351,14 @@ bool ScopedDisableResize::disable_resize_ = false; - (void)setShell:(atom::NativeWindowMac*)shell; - (void)setEnableLargerThanScreen:(bool)enable; - (void)enableWindowButtonsOffset; +- (void)reloadTouchBar; +@end + +@interface AtomNSWindow () @end @implementation AtomNSWindow + NSMutableArray* bar_items_ = [[NSMutableArray alloc] init]; - (void)setShell:(atom::NativeWindowMac*)shell { shell_ = shell; @@ -363,6 +368,75 @@ bool ScopedDisableResize::disable_resize_ = false; enable_larger_than_screen_ = enable; } +- (void)reloadTouchBar { + bar_items_ = [[NSMutableArray alloc] init]; + [bar_items_ addObject:@"com.electron.tb.button.1"]; + [bar_items_ addObject:@"com.electron.tb.button.2"]; + [bar_items_ addObject:NSTouchBarItemIdentifierOtherItemsProxy]; + NSLog(@"Reloading Touch Bar --> '%@'", bar_items_[1]); + self.touchBar = nil; +} + +- (NSTouchBar *)makeTouchBar { + NSLog(@"Making Touch Bar"); + NSTouchBar* bar = [[NSTouchBar alloc] init]; + bar.delegate = self; + + // Set the default ordering of items. + + // NSLog(@"%@", bar_items_[1]); + bar.defaultItemIdentifiers = [bar_items_ copy]; + + return bar; +} + +- (void)buttonAction:(id)sender { + NSString* item_id = [NSString stringWithFormat:@"com.electron.tb.button.%d", (int)((NSButton *)sender).tag]; + NSLog(@"Button with ID: '%@' was pressed", item_id); + shell_->NotifyTouchBarItemInteraction("button", std::string([item_id UTF8String])); +} + +- (void)colorPickerAction:(id)sender { + NSString* item_id = ((NSColorPickerTouchBarItem *)sender).identifier; + NSLog(@"ColorPicker with ID: '%@' was updated with color '%@'", item_id, ((NSColorPickerTouchBarItem *)sender).color); + shell_->NotifyTouchBarItemInteraction("color_picker", std::string([item_id UTF8String])); +} + +static NSTouchBarItemIdentifier ButtonIdentifier = @"com.electron.tb.button."; +static NSTouchBarItemIdentifier ColorPickerIdentifier = @"com.electron.tb.colorpicker."; +// static NSTouchBarItemIdentifier ListIdentifier = @"com.electron.tb.list."; +static NSTouchBarItemIdentifier LabelIdentifier = @"com.electron.tb.label."; +// static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.tb.slider."; + +- (nullable NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { + if ([identifier hasPrefix:ButtonIdentifier]) { + NSString *idCopy = [identifier copy]; + idCopy = [identifier substringFromIndex:[ButtonIdentifier length]]; + NSButton *theButton = [NSButton buttonWithTitle:@"Electron Button" target:self action:@selector(buttonAction:)]; + theButton.tag = [idCopy floatValue]; + + NSCustomTouchBarItem *customItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier]; + customItem.view = theButton; + + return customItem; + } else if ([identifier hasPrefix:LabelIdentifier]) { + NSTextField *theLabel = [NSTextField labelWithString:@"Hello From Electron"]; + + NSCustomTouchBarItem *customItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier]; + customItem.view = theLabel; + + return customItem; + } else if ([identifier hasPrefix:ColorPickerIdentifier]) { + NSColorPickerTouchBarItem *colorPickerItem = [[NSColorPickerTouchBarItem alloc] initWithIdentifier:identifier]; + colorPickerItem.target = self; + colorPickerItem.action = @selector(colorPickerAction:); + return colorPickerItem; + } + + return nil; +} + + // NSWindow overrides. - (void)swipeWithEvent:(NSEvent *)event { @@ -1346,6 +1420,10 @@ void NativeWindowMac::SetVibrancy(const std::string& type) { [effect_view setMaterial:vibrancyType]; } +void NativeWindowMac::InitTouchBar() { + [window_ reloadTouchBar]; +} + void NativeWindowMac::OnInputEvent(const blink::WebInputEvent& event) { switch (event.type) { case blink::WebInputEvent::GestureScrollBegin: diff --git a/atom/browser/native_window_observer.h b/atom/browser/native_window_observer.h index 42d6b0287f..e3d9deaed2 100644 --- a/atom/browser/native_window_observer.h +++ b/atom/browser/native_window_observer.h @@ -70,6 +70,7 @@ class NativeWindowObserver { virtual void OnWindowLeaveFullScreen() {} virtual void OnWindowEnterHtmlFullScreen() {} virtual void OnWindowLeaveHtmlFullScreen() {} + virtual void OnTouchBarItemResult(const std::string& item_type, const std::string& item_id) {} // Called when window message received #if defined(OS_WIN) From 7857c83ea134d0967f7c68d28ad3c7a4e59cf791 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Sun, 27 Nov 2016 22:54:12 +1100 Subject: [PATCH 276/925] Make dynamic buttons work along with click events --- atom/browser/api/atom_api_window.cc | 13 +++-- atom/browser/api/atom_api_window.h | 3 +- atom/browser/native_window.cc | 6 +- atom/browser/native_window.h | 4 +- atom/browser/native_window_mac.h | 7 ++- atom/browser/native_window_mac.mm | 65 +++++++++++++++++---- default_app/default_app.js | 11 +++- filenames.gypi | 1 + lib/browser/api/browser-window.js | 18 +++++- lib/browser/api/exports/electron.js | 6 ++ lib/browser/api/touch-bar.js | 89 +++++++++++++++++++++++++++++ 11 files changed, 203 insertions(+), 20 deletions(-) create mode 100644 lib/browser/api/touch-bar.js diff --git a/atom/browser/api/atom_api_window.cc b/atom/browser/api/atom_api_window.cc index 1107a628a9..a75df27fce 100644 --- a/atom/browser/api/atom_api_window.cc +++ b/atom/browser/api/atom_api_window.cc @@ -283,7 +283,7 @@ void Window::OnExecuteWindowsCommand(const std::string& command_name) { } void Window::OnTouchBarItemResult(const std::string& item_type, const std::string& item_id) { - Emit("_touch-bar-interaction", item_type, item_id); + Emit("-touch-bar-interaction", item_type, item_id); } #if defined(OS_WIN) @@ -844,8 +844,12 @@ void Window::SetVibrancy(mate::Arguments* args) { window_->SetVibrancy(type); } -void Window::InitTouchBar() { - window_->InitTouchBar(); +void Window::DestroyTouchBar() { + window_->DestroyTouchBar(); +} + +void Window::SetTouchBar(mate::Arguments* args) { + window_->SetTouchBar(args); } int32_t Window::ID() const { @@ -968,7 +972,8 @@ void Window::BuildPrototype(v8::Isolate* isolate, .SetMethod("setAutoHideCursor", &Window::SetAutoHideCursor) #endif .SetMethod("setVibrancy", &Window::SetVibrancy) - .SetMethod("initTouchBar", &Window::InitTouchBar) + .SetMethod("_destroyTouchBar", &Window::DestroyTouchBar) + .SetMethod("_setTouchBar", &Window::SetTouchBar) #if defined(OS_WIN) .SetMethod("hookWindowMessage", &Window::HookWindowMessage) .SetMethod("isWindowMessageHooked", &Window::IsWindowMessageHooked) diff --git a/atom/browser/api/atom_api_window.h b/atom/browser/api/atom_api_window.h index 3325c87fa9..54aa01d388 100644 --- a/atom/browser/api/atom_api_window.h +++ b/atom/browser/api/atom_api_window.h @@ -204,7 +204,8 @@ class Window : public mate::TrackableObject, void SetAutoHideCursor(bool auto_hide); void SetVibrancy(mate::Arguments* args); - void InitTouchBar(); + void DestroyTouchBar(); + void SetTouchBar(mate::Arguments* args); v8::Local WebContents(v8::Isolate* isolate); diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index 2219a4d595..53068045ea 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -33,6 +33,7 @@ #include "content/public/browser/render_widget_host_view.h" #include "content/public/common/content_switches.h" #include "ipc/ipc_message_macros.h" +#include "native_mate/constructor.h" #include "native_mate/dictionary.h" #include "third_party/skia/include/core/SkRegion.h" #include "ui/gfx/codec/png_codec.h" @@ -340,7 +341,10 @@ void NativeWindow::SetAutoHideCursor(bool auto_hide) { void NativeWindow::SetVibrancy(const std::string& filename) { } -void NativeWindow::InitTouchBar() { +void NativeWindow::DestroyTouchBar() { +} + +void NativeWindow::SetTouchBar(mate::Arguments* args) { } void NativeWindow::FocusOnWebView() { diff --git a/atom/browser/native_window.h b/atom/browser/native_window.h index 3338396146..bd7584e0d1 100644 --- a/atom/browser/native_window.h +++ b/atom/browser/native_window.h @@ -23,6 +23,7 @@ #include "extensions/browser/app_window/size_constraints.h" #include "ui/gfx/image/image.h" #include "ui/gfx/image/image_skia.h" +#include "native_mate/constructor.h" class SkRegion; @@ -170,7 +171,8 @@ class NativeWindow : public base::SupportsUserData, virtual void SetVibrancy(const std::string& type); // Touchbar API - virtual void InitTouchBar(); + virtual void DestroyTouchBar(); + virtual void SetTouchBar(mate::Arguments* args); // Webview APIs. virtual void FocusOnWebView(); diff --git a/atom/browser/native_window_mac.h b/atom/browser/native_window_mac.h index fec5eacb3a..026f86e599 100644 --- a/atom/browser/native_window_mac.h +++ b/atom/browser/native_window_mac.h @@ -13,6 +13,7 @@ #include "atom/browser/native_window.h" #include "base/mac/scoped_nsobject.h" #include "content/public/browser/render_widget_host.h" +#include "native_mate/constructor.h" @class AtomNSWindow; @class AtomNSWindowDelegate; @@ -100,7 +101,9 @@ class NativeWindowMac : public NativeWindow, void SetAutoHideCursor(bool auto_hide) override; void SetVibrancy(const std::string& type) override; - void InitTouchBar() override; + void DestroyTouchBar() override; + void SetTouchBar(mate::Arguments* args) override; + std::vector GetTouchBarItems(); // content::RenderWidgetHost::InputEventObserver: void OnInputEvent(const blink::WebInputEvent& event) override; @@ -155,6 +158,8 @@ class NativeWindowMac : public NativeWindow, base::scoped_nsobject window_; base::scoped_nsobject window_delegate_; + std::vector touch_bar_items_; + // Event monitor for scroll wheel event. id wheel_event_monitor_; diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 029cf6002d..87a27cae07 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -352,6 +352,8 @@ bool ScopedDisableResize::disable_resize_ = false; - (void)setEnableLargerThanScreen:(bool)enable; - (void)enableWindowButtonsOffset; - (void)reloadTouchBar; +- (void)resetTouchBar; +- (NSTouchBar*)touchBarFromMutatableArray:(NSMutableArray*)items; @end @interface AtomNSWindow () @@ -359,6 +361,7 @@ bool ScopedDisableResize::disable_resize_ = false; @implementation AtomNSWindow NSMutableArray* bar_items_ = [[NSMutableArray alloc] init]; + std::map button_labels; - (void)setShell:(atom::NativeWindowMac*)shell { shell_ = shell; @@ -368,24 +371,52 @@ bool ScopedDisableResize::disable_resize_ = false; enable_larger_than_screen_ = enable; } +- (void)resetTouchBar { + bar_items_ = [[NSMutableArray alloc] init]; + self.touchBar = nil; + NSLog(@"Destroying TouchBar"); +} + - (void)reloadTouchBar { bar_items_ = [[NSMutableArray alloc] init]; - [bar_items_ addObject:@"com.electron.tb.button.1"]; - [bar_items_ addObject:@"com.electron.tb.button.2"]; + std::vector items = shell_->GetTouchBarItems(); + std::map new_button_labels; + button_labels = new_button_labels; + + NSLog(@"reload"); + for (mate::Dictionary &item : items ) { + NSLog(@"reload iter"); + std::string type; + std::string item_id; + if (item.Get("type", &type) && item.Get("id", &item_id)) { + NSLog(@"type: %@", [NSString stringWithUTF8String:type.c_str()]); + NSLog(@"id: %@", [NSString stringWithUTF8String:item_id.c_str()]); + if (type == "button") { + std::string label; + if (item.Get("label", &label)) { + [bar_items_ addObject:[NSString stringWithFormat:@"%@%@", ButtonIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; + button_labels.insert(make_pair(item_id, label)); + } + } + } + } + // [bar_items_ addObject:@"com.electron.tb.button.1"]; + // [bar_items_ addObject:@"com.electron.tb.button.2"]; [bar_items_ addObject:NSTouchBarItemIdentifierOtherItemsProxy]; - NSLog(@"Reloading Touch Bar --> '%@'", bar_items_[1]); + // NSLog(@"Reloading Touch Bar --> '%@'", bar_items_[1]); self.touchBar = nil; } - (NSTouchBar *)makeTouchBar { NSLog(@"Making Touch Bar"); + return [self touchBarFromMutatableArray:bar_items_]; +} + +- (NSTouchBar *)touchBarFromMutatableArray:(NSMutableArray*)items { NSTouchBar* bar = [[NSTouchBar alloc] init]; bar.delegate = self; - // Set the default ordering of items. - - // NSLog(@"%@", bar_items_[1]); - bar.defaultItemIdentifiers = [bar_items_ copy]; + bar.defaultItemIdentifiers = [items copy]; return bar; } @@ -412,7 +443,7 @@ static NSTouchBarItemIdentifier LabelIdentifier = @"com.electron.tb.label."; if ([identifier hasPrefix:ButtonIdentifier]) { NSString *idCopy = [identifier copy]; idCopy = [identifier substringFromIndex:[ButtonIdentifier length]]; - NSButton *theButton = [NSButton buttonWithTitle:@"Electron Button" target:self action:@selector(buttonAction:)]; + NSButton *theButton = [NSButton buttonWithTitle:[NSString stringWithUTF8String:button_labels[std::string([idCopy UTF8String])].c_str()] target:self action:@selector(buttonAction:)]; theButton.tag = [idCopy floatValue]; NSCustomTouchBarItem *customItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier]; @@ -1420,8 +1451,22 @@ void NativeWindowMac::SetVibrancy(const std::string& type) { [effect_view setMaterial:vibrancyType]; } -void NativeWindowMac::InitTouchBar() { - [window_ reloadTouchBar]; +void NativeWindowMac::DestroyTouchBar() { + [window_ resetTouchBar]; +} + +void NativeWindowMac::SetTouchBar(mate::Arguments* args) { + std::vector items; + LOG(ERROR) << "FOO"; + if (args->GetNext(&items)) { + LOG(ERROR) << "BAR"; + touch_bar_items_ = items; + [window_ reloadTouchBar]; + } +} + +std::vector NativeWindowMac::GetTouchBarItems() { + return touch_bar_items_; } void NativeWindowMac::OnInputEvent(const blink::WebInputEvent& event) { diff --git a/default_app/default_app.js b/default_app/default_app.js index bfb97a9ab0..35e290b4e9 100644 --- a/default_app/default_app.js +++ b/default_app/default_app.js @@ -1,4 +1,4 @@ -const {app, BrowserWindow} = require('electron') +const {app, BrowserWindow,TouchBar} = require('electron') const path = require('path') let mainWindow = null @@ -24,5 +24,14 @@ exports.load = (appUrl) => { mainWindow = new BrowserWindow(options) mainWindow.loadURL(appUrl) mainWindow.focus() + + mainWindow.setTouchBar(new TouchBar([ + new (TouchBar.Button)({ + label: 'Hello World!', + click: () => { + console.log('Hello World Clicked') + } + }) + ])) }) } diff --git a/filenames.gypi b/filenames.gypi index 00297becef..0e4ad20626 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -30,6 +30,7 @@ 'lib/browser/api/session.js', 'lib/browser/api/screen.js', 'lib/browser/api/system-preferences.js', + 'lib/browser/api/touch-bar.js', 'lib/browser/api/tray.js', 'lib/browser/api/web-contents.js', 'lib/browser/chrome-extension.js', diff --git a/lib/browser/api/browser-window.js b/lib/browser/api/browser-window.js index 1aa34d45a0..fd72a4cf0f 100644 --- a/lib/browser/api/browser-window.js +++ b/lib/browser/api/browser-window.js @@ -1,6 +1,6 @@ 'use strict' -const {ipcMain} = require('electron') +const {ipcMain,TouchBar} = require('electron') const {EventEmitter} = require('events') const {BrowserWindow} = process.atomBinding('window') const v8Util = process.atomBinding('v8_util') @@ -131,6 +131,11 @@ BrowserWindow.prototype._init = function () { return this.webContents.devToolsWebContents } }) + + // Proxy TouchBar events + this.on('-touch-bar-interaction', (event, item_type, id, ...args) => { + TouchBar._event(id, ...args) + }) } BrowserWindow.getFocusedWindow = () => { @@ -198,4 +203,15 @@ Object.assign(BrowserWindow.prototype, { } }) +// TouchBar API +BrowserWindow.prototype.setTouchBar = function (touchBar) { + if (touchBar === null || typeof touchBar === 'undefined') { + this._destroyTouchBar(); + } else if (Array.isArray(touchBar)) { + this._setTouchBar((new TouchBar(touchBar)).toJSON()); + } else { + this._setTouchBar(touchBar.toJSON()) + } +} + module.exports = BrowserWindow diff --git a/lib/browser/api/exports/electron.js b/lib/browser/api/exports/electron.js index 11698f1df5..3f40595217 100644 --- a/lib/browser/api/exports/electron.js +++ b/lib/browser/api/exports/electron.js @@ -103,6 +103,12 @@ Object.defineProperties(exports, { return require('../system-preferences') } }, + TouchBar: { + enumerable: true, + get: function () { + return require('../touch-bar') + } + }, Tray: { enumerable: true, get: function () { diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js new file mode 100644 index 0000000000..d8ce377bc8 --- /dev/null +++ b/lib/browser/api/touch-bar.js @@ -0,0 +1,89 @@ +const {EventEmitter} = require('events') +const {app} = require('electron') + +class TouchBar { + constructor (items) { + this.items = items; + if (!Array.isArray(items)) { + throw new Error('The items object provided has to be an array') + } + console.log(items) + items.forEach((item) => { + if (!item.id) { + throw new Error('Each item must be an instance of a TouchBarItem') + } + }) + } + + toJSON () { + return this.items.map((item) => item.toJSON ? item.toJSON() : item); + } +} + +let item_id_incrementor = 1 +const item_event_handlers = {} + +TouchBar._event = (id, ...args) => { + const id_parts = id.split('.') + const item_id = id_parts[id_parts.length - 1] + if (item_event_handlers[item_id]) item_event_handlers[item_id](...args) +} + +class TouchBarItem { + constructor (config) { + this.id = item_id_incrementor++ + const mConfig = Object.assign({}, config || {}) + Object.defineProperty(this, 'config', { + configurable: false, + enumerable: false, + get: () => mConfig + }) + this.config.id = `${this.config.id || this.id}`; + this.config.type = 'button'; + if (typeof this.config !== 'object' || this.config === null) { + throw new Error('Provided config must be a non-null object') + } + } + + toJSON () { + return this.config + } +} + +TouchBar.Button = class TouchBarButton extends TouchBarItem { + constructor (config) { + super(config) + this.config.type = 'button'; + const click = config.click + if (typeof click === 'function') { + item_event_handlers[`${this.id}`] = click + } + } +} + +TouchBar.ColorPicker = class TouchBarColorPicker extends TouchBarItem { + constructor (config) { + super(config) + this.config.type = 'colorpicker'; + const change = config.change + if (typeof change === 'function') { + item_event_handlers[`${this.id}`] = change + } + } +} + +TouchBar.Label = class TouchBarLabel extends TouchBarItem {} + +TouchBar.List = class TouchBarList extends TouchBarItem {} + +TouchBar.Slider = class TouchBarSlider extends TouchBarItem { + constructor (config) { + super(config) + const change = config.change + if (typeof change === 'function') { + item_event_handlers[this.id] = change + } + } +} + +module.exports = TouchBar; From 18c7c3ece86014904a5ae85d883cbd813fc09535 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Mon, 28 Nov 2016 18:24:48 +1100 Subject: [PATCH 277/925] Make label and colorpicker types work --- atom/browser/api/atom_api_window.cc | 4 +-- atom/browser/api/atom_api_window.h | 2 +- atom/browser/native_window.cc | 4 +-- atom/browser/native_window.h | 2 +- atom/browser/native_window_mac.mm | 48 ++++++++++++++++++++------- atom/browser/native_window_observer.h | 2 +- default_app/default_app.js | 8 +++++ lib/browser/api/touch-bar.js | 21 +++++++----- 8 files changed, 64 insertions(+), 27 deletions(-) diff --git a/atom/browser/api/atom_api_window.cc b/atom/browser/api/atom_api_window.cc index a75df27fce..ae52b03f07 100644 --- a/atom/browser/api/atom_api_window.cc +++ b/atom/browser/api/atom_api_window.cc @@ -282,8 +282,8 @@ void Window::OnExecuteWindowsCommand(const std::string& command_name) { Emit("app-command", command_name); } -void Window::OnTouchBarItemResult(const std::string& item_type, const std::string& item_id) { - Emit("-touch-bar-interaction", item_type, item_id); +void Window::OnTouchBarItemResult(const std::string& item_type, const std::vector& args) { + Emit("-touch-bar-interaction", item_type, args); } #if defined(OS_WIN) diff --git a/atom/browser/api/atom_api_window.h b/atom/browser/api/atom_api_window.h index 54aa01d388..55347bb54d 100644 --- a/atom/browser/api/atom_api_window.h +++ b/atom/browser/api/atom_api_window.h @@ -85,7 +85,7 @@ class Window : public mate::TrackableObject, void OnRendererUnresponsive() override; void OnRendererResponsive() override; void OnExecuteWindowsCommand(const std::string& command_name) override; - void OnTouchBarItemResult(const std::string& item_type, const std::string& item_id) override; + void OnTouchBarItemResult(const std::string& item_type, const std::vector& args) override; #if defined(OS_WIN) void OnWindowMessage(UINT message, WPARAM w_param, LPARAM l_param) override; diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index 53068045ea..1aead897d9 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -574,9 +574,9 @@ void NativeWindow::NotifyWindowExecuteWindowsCommand( void NativeWindow::NotifyTouchBarItemInteraction( const std::string& type, - const std::string& item_id) { + const std::vector& args) { FOR_EACH_OBSERVER(NativeWindowObserver, observers_, - OnTouchBarItemResult(type, item_id)); + OnTouchBarItemResult(type, args)); } #if defined(OS_WIN) diff --git a/atom/browser/native_window.h b/atom/browser/native_window.h index bd7584e0d1..38636fa2a5 100644 --- a/atom/browser/native_window.h +++ b/atom/browser/native_window.h @@ -233,7 +233,7 @@ class NativeWindow : public base::SupportsUserData, void NotifyWindowEnterHtmlFullScreen(); void NotifyWindowLeaveHtmlFullScreen(); void NotifyWindowExecuteWindowsCommand(const std::string& command); - void NotifyTouchBarItemInteraction(const std::string& item_type, const std::string& item_id); + void NotifyTouchBarItemInteraction(const std::string& item_type, const std::vector& args); #if defined(OS_WIN) void NotifyWindowMessage(UINT message, WPARAM w_param, LPARAM l_param); diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 87a27cae07..1e83d133cf 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -361,7 +361,7 @@ bool ScopedDisableResize::disable_resize_ = false; @implementation AtomNSWindow NSMutableArray* bar_items_ = [[NSMutableArray alloc] init]; - std::map button_labels; + std::map item_labels; - (void)setShell:(atom::NativeWindowMac*)shell { shell_ = shell; @@ -380,8 +380,8 @@ bool ScopedDisableResize::disable_resize_ = false; - (void)reloadTouchBar { bar_items_ = [[NSMutableArray alloc] init]; std::vector items = shell_->GetTouchBarItems(); - std::map new_button_labels; - button_labels = new_button_labels; + std::map new_labels; + item_labels = new_labels; NSLog(@"reload"); for (mate::Dictionary &item : items ) { @@ -395,8 +395,16 @@ bool ScopedDisableResize::disable_resize_ = false; std::string label; if (item.Get("label", &label)) { [bar_items_ addObject:[NSString stringWithFormat:@"%@%@", ButtonIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; - button_labels.insert(make_pair(item_id, label)); + item_labels.insert(make_pair(item_id, label)); } + } else if (type == "label") { + std::string label; + if (item.Get("label", &label)) { + [bar_items_ addObject:[NSString stringWithFormat:@"%@%@", LabelIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; + item_labels.insert(make_pair(item_id, label)); + } + } else if (type == "colorpicker") { + [bar_items_ addObject:[NSString stringWithFormat:@"%@%@", ColorPickerIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; } } } @@ -424,13 +432,17 @@ bool ScopedDisableResize::disable_resize_ = false; - (void)buttonAction:(id)sender { NSString* item_id = [NSString stringWithFormat:@"com.electron.tb.button.%d", (int)((NSButton *)sender).tag]; NSLog(@"Button with ID: '%@' was pressed", item_id); - shell_->NotifyTouchBarItemInteraction("button", std::string([item_id UTF8String])); + shell_->NotifyTouchBarItemInteraction("button", { std::string([item_id UTF8String]) }); } - (void)colorPickerAction:(id)sender { NSString* item_id = ((NSColorPickerTouchBarItem *)sender).identifier; - NSLog(@"ColorPicker with ID: '%@' was updated with color '%@'", item_id, ((NSColorPickerTouchBarItem *)sender).color); - shell_->NotifyTouchBarItemInteraction("color_picker", std::string([item_id UTF8String])); + NSColor* color = ((NSColorPickerTouchBarItem *)sender).color; + NSString* colorHexString = [NSString stringWithFormat:@"#%02X%02X%02X", + (int) (color.redComponent * 0xFF), (int) (color.greenComponent * 0xFF), + (int) (color.blueComponent * 0xFF)]; + NSLog(@"ColorPicker with ID: '%@' was updated with color '%@'", item_id, colorHexString); + shell_->NotifyTouchBarItemInteraction("color_picker", { std::string([item_id UTF8String]), std::string([colorHexString UTF8String]) }); } static NSTouchBarItemIdentifier ButtonIdentifier = @"com.electron.tb.button."; @@ -439,19 +451,31 @@ static NSTouchBarItemIdentifier ColorPickerIdentifier = @"com.electron.tb.colorp static NSTouchBarItemIdentifier LabelIdentifier = @"com.electron.tb.label."; // static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.tb.slider."; +- (NSString*)idFromIdentifier:(NSString*)identifier withPrefix:(NSString*)prefix { + NSString *idCopy = [identifier copy]; + idCopy = [identifier substringFromIndex:[prefix length]]; + return idCopy; +} + +- (bool)hasLabel:(NSString*)id { + return item_labels.find(std::string([id UTF8String])) != item_labels.end(); +} + - (nullable NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { if ([identifier hasPrefix:ButtonIdentifier]) { - NSString *idCopy = [identifier copy]; - idCopy = [identifier substringFromIndex:[ButtonIdentifier length]]; - NSButton *theButton = [NSButton buttonWithTitle:[NSString stringWithUTF8String:button_labels[std::string([idCopy UTF8String])].c_str()] target:self action:@selector(buttonAction:)]; - theButton.tag = [idCopy floatValue]; + NSString* id = [self idFromIdentifier:identifier withPrefix:ButtonIdentifier]; + if (![self hasLabel:id]) return nil; + NSButton *theButton = [NSButton buttonWithTitle:[NSString stringWithUTF8String:item_labels[std::string([id UTF8String])].c_str()] target:self action:@selector(buttonAction:)]; + theButton.tag = [id floatValue]; NSCustomTouchBarItem *customItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier]; customItem.view = theButton; return customItem; } else if ([identifier hasPrefix:LabelIdentifier]) { - NSTextField *theLabel = [NSTextField labelWithString:@"Hello From Electron"]; + NSString* id = [self idFromIdentifier:identifier withPrefix:LabelIdentifier]; + if (![self hasLabel:id]) return nil; + NSTextField *theLabel = [NSTextField labelWithString:[NSString stringWithUTF8String:item_labels[std::string([id UTF8String])].c_str()]]; NSCustomTouchBarItem *customItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier]; customItem.view = theLabel; diff --git a/atom/browser/native_window_observer.h b/atom/browser/native_window_observer.h index e3d9deaed2..bd9ff21f56 100644 --- a/atom/browser/native_window_observer.h +++ b/atom/browser/native_window_observer.h @@ -70,7 +70,7 @@ class NativeWindowObserver { virtual void OnWindowLeaveFullScreen() {} virtual void OnWindowEnterHtmlFullScreen() {} virtual void OnWindowLeaveHtmlFullScreen() {} - virtual void OnTouchBarItemResult(const std::string& item_type, const std::string& item_id) {} + virtual void OnTouchBarItemResult(const std::string& item_type, const std::vector& args) {} // Called when window message received #if defined(OS_WIN) diff --git a/default_app/default_app.js b/default_app/default_app.js index 35e290b4e9..3856c8e90a 100644 --- a/default_app/default_app.js +++ b/default_app/default_app.js @@ -31,6 +31,14 @@ exports.load = (appUrl) => { click: () => { console.log('Hello World Clicked') } + }), + new (TouchBar.Label)({ + label: 'This is a Label' + }), + new (TouchBar.ColorPicker)({ + change: (...args) => { + console.log('Color was changed', ...args) + } }) ])) }) diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index d8ce377bc8..f62d7c2ff0 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -23,8 +23,9 @@ class TouchBar { let item_id_incrementor = 1 const item_event_handlers = {} -TouchBar._event = (id, ...args) => { - const id_parts = id.split('.') +TouchBar._event = (eventArgs) => { + const args = eventArgs.slice(1) + const id_parts = eventArgs[0].split('.') const item_id = id_parts[id_parts.length - 1] if (item_event_handlers[item_id]) item_event_handlers[item_id](...args) } @@ -39,7 +40,6 @@ class TouchBarItem { get: () => mConfig }) this.config.id = `${this.config.id || this.id}`; - this.config.type = 'button'; if (typeof this.config !== 'object' || this.config === null) { throw new Error('Provided config must be a non-null object') } @@ -53,7 +53,7 @@ class TouchBarItem { TouchBar.Button = class TouchBarButton extends TouchBarItem { constructor (config) { super(config) - this.config.type = 'button'; + this.config.type = 'button' const click = config.click if (typeof click === 'function') { item_event_handlers[`${this.id}`] = click @@ -64,22 +64,27 @@ TouchBar.Button = class TouchBarButton extends TouchBarItem { TouchBar.ColorPicker = class TouchBarColorPicker extends TouchBarItem { constructor (config) { super(config) - this.config.type = 'colorpicker'; - const change = config.change + this.config.type = 'colorpicker' + const change = this.config.change if (typeof change === 'function') { item_event_handlers[`${this.id}`] = change } } } -TouchBar.Label = class TouchBarLabel extends TouchBarItem {} +TouchBar.Label = class TouchBarLabel extends TouchBarItem { + constructor (config) { + super(config) + this.config.type = 'label' + } +} TouchBar.List = class TouchBarList extends TouchBarItem {} TouchBar.Slider = class TouchBarSlider extends TouchBarItem { constructor (config) { super(config) - const change = config.change + const change = this.config.change if (typeof change === 'function') { item_event_handlers[this.id] = change } From c92c4138a8a0d992984c50f9925848e495a696ea Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Mon, 28 Nov 2016 21:43:39 +1100 Subject: [PATCH 278/925] Add Slider item type and add options to the button type --- atom/browser/native_window_mac.h | 5 +- atom/browser/native_window_mac.mm | 206 +++++++++++++++++++++++------- default_app/default_app.js | 16 ++- lib/browser/api/browser-window.js | 2 +- lib/browser/api/touch-bar.js | 15 ++- 5 files changed, 191 insertions(+), 53 deletions(-) diff --git a/atom/browser/native_window_mac.h b/atom/browser/native_window_mac.h index 026f86e599..b960290c82 100644 --- a/atom/browser/native_window_mac.h +++ b/atom/browser/native_window_mac.h @@ -14,6 +14,7 @@ #include "base/mac/scoped_nsobject.h" #include "content/public/browser/render_widget_host.h" #include "native_mate/constructor.h" +#include "native_mate/persistent_dictionary.h" @class AtomNSWindow; @class AtomNSWindowDelegate; @@ -103,7 +104,7 @@ class NativeWindowMac : public NativeWindow, void SetVibrancy(const std::string& type) override; void DestroyTouchBar() override; void SetTouchBar(mate::Arguments* args) override; - std::vector GetTouchBarItems(); + std::vector GetTouchBarItems(); // content::RenderWidgetHost::InputEventObserver: void OnInputEvent(const blink::WebInputEvent& event) override; @@ -158,7 +159,7 @@ class NativeWindowMac : public NativeWindow, base::scoped_nsobject window_; base::scoped_nsobject window_delegate_; - std::vector touch_bar_items_; + std::vector touch_bar_items_; // Event monitor for scroll wheel event. id wheel_event_monitor_; diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 1e83d133cf..b7537ecb74 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -10,6 +10,7 @@ #include "atom/browser/window_list.h" #include "atom/common/color_util.h" #include "atom/common/draggable_region.h" +#include "atom/common/native_mate_converters/image_converter.h" #include "atom/common/options_switches.h" #include "base/mac/mac_util.h" #include "base/mac/scoped_cftyperef.h" @@ -361,7 +362,7 @@ bool ScopedDisableResize::disable_resize_ = false; @implementation AtomNSWindow NSMutableArray* bar_items_ = [[NSMutableArray alloc] init]; - std::map item_labels; + std::map item_id_map; - (void)setShell:(atom::NativeWindowMac*)shell { shell_ = shell; @@ -379,39 +380,29 @@ bool ScopedDisableResize::disable_resize_ = false; - (void)reloadTouchBar { bar_items_ = [[NSMutableArray alloc] init]; - std::vector items = shell_->GetTouchBarItems(); - std::map new_labels; - item_labels = new_labels; + std::vector items = shell_->GetTouchBarItems(); + std::map new_map; + item_id_map = new_map; - NSLog(@"reload"); - for (mate::Dictionary &item : items ) { - NSLog(@"reload iter"); + for (mate::PersistentDictionary &item : items ) { std::string type; std::string item_id; if (item.Get("type", &type) && item.Get("id", &item_id)) { NSLog(@"type: %@", [NSString stringWithUTF8String:type.c_str()]); NSLog(@"id: %@", [NSString stringWithUTF8String:item_id.c_str()]); + item_id_map.insert(make_pair(item_id, item)); if (type == "button") { - std::string label; - if (item.Get("label", &label)) { - [bar_items_ addObject:[NSString stringWithFormat:@"%@%@", ButtonIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; - item_labels.insert(make_pair(item_id, label)); - } + [bar_items_ addObject:[NSString stringWithFormat:@"%@%@", ButtonIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; } else if (type == "label") { - std::string label; - if (item.Get("label", &label)) { - [bar_items_ addObject:[NSString stringWithFormat:@"%@%@", LabelIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; - item_labels.insert(make_pair(item_id, label)); - } + [bar_items_ addObject:[NSString stringWithFormat:@"%@%@", LabelIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; } else if (type == "colorpicker") { [bar_items_ addObject:[NSString stringWithFormat:@"%@%@", ColorPickerIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; + } else if (type == "slider") { + [bar_items_ addObject:[NSString stringWithFormat:@"%@%@", SliderIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; } } } - // [bar_items_ addObject:@"com.electron.tb.button.1"]; - // [bar_items_ addObject:@"com.electron.tb.button.2"]; [bar_items_ addObject:NSTouchBarItemIdentifierOtherItemsProxy]; - // NSLog(@"Reloading Touch Bar --> '%@'", bar_items_[1]); self.touchBar = nil; } @@ -445,11 +436,11 @@ bool ScopedDisableResize::disable_resize_ = false; shell_->NotifyTouchBarItemInteraction("color_picker", { std::string([item_id UTF8String]), std::string([colorHexString UTF8String]) }); } -static NSTouchBarItemIdentifier ButtonIdentifier = @"com.electron.tb.button."; -static NSTouchBarItemIdentifier ColorPickerIdentifier = @"com.electron.tb.colorpicker."; -// static NSTouchBarItemIdentifier ListIdentifier = @"com.electron.tb.list."; -static NSTouchBarItemIdentifier LabelIdentifier = @"com.electron.tb.label."; -// static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.tb.slider."; +- (void)sliderAction:(id)sender { + NSString* item_id = ((NSSliderTouchBarItem *)sender).identifier; + NSLog(@"Slider with ID: '%@' was changed", item_id); + shell_->NotifyTouchBarItemInteraction("slider", { std::string([item_id UTF8String]), std::to_string([((NSSliderTouchBarItem *)sender).slider intValue]) }); +} - (NSString*)idFromIdentifier:(NSString*)identifier withPrefix:(NSString*)prefix { NSString *idCopy = [identifier copy]; @@ -457,35 +448,164 @@ static NSTouchBarItemIdentifier LabelIdentifier = @"com.electron.tb.label."; return idCopy; } -- (bool)hasLabel:(NSString*)id { - return item_labels.find(std::string([id UTF8String])) != item_labels.end(); +- (bool)hasTBDict:(std::string)id { + return item_id_map.find(id) != item_id_map.end(); } -- (nullable NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { - if ([identifier hasPrefix:ButtonIdentifier]) { - NSString* id = [self idFromIdentifier:identifier withPrefix:ButtonIdentifier]; - if (![self hasLabel:id]) return nil; - NSButton *theButton = [NSButton buttonWithTitle:[NSString stringWithUTF8String:item_labels[std::string([id UTF8String])].c_str()] target:self action:@selector(buttonAction:)]; +- (NSColor*)colorFromHexColorString:(NSString*)inColorString { + NSColor* result = nil; + unsigned colorCode = 0; + unsigned char redByte, greenByte, blueByte; + + if (nil != inColorString) + { + NSScanner* scanner = [NSScanner scannerWithString:inColorString]; + (void) [scanner scanHexInt:&colorCode]; // ignore error + } + redByte = (unsigned char)(colorCode >> 16); + greenByte = (unsigned char)(colorCode >> 8); + blueByte = (unsigned char)(colorCode); // masks off high bits + + result = [NSColor + colorWithCalibratedRed:(CGFloat)redByte / 0xff + green:(CGFloat)greenByte / 0xff + blue:(CGFloat)blueByte / 0xff + alpha:1.0]; + return result; +} + +- (nullable NSTouchBarItem *)makeButtonForID:(NSString*)id withIdentifier:(NSString*)identifier { + std::string s_id = std::string([id UTF8String]); + if (![self hasTBDict:s_id]) return nil; + mate::PersistentDictionary item = item_id_map[s_id]; + std::string label; + if (item.Get("label", &label)) { + NSButton *theButton = [NSButton buttonWithTitle:[NSString stringWithUTF8String:label.c_str()] target:self action:@selector(buttonAction:)]; theButton.tag = [id floatValue]; NSCustomTouchBarItem *customItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier]; customItem.view = theButton; + std::string customizationLabel; + if (item.Get("customizationLabel", &customizationLabel)) { + customItem.customizationLabel = [NSString stringWithUTF8String:customizationLabel.c_str()]; + } + + std::string backgroundColor; + if (item.Get("backgroundColor", &backgroundColor)) { + theButton.bezelColor = [self colorFromHexColorString:[NSString stringWithUTF8String:backgroundColor.c_str()]]; + } + + std::string labelColor; + if (item.Get("labelColor", &labelColor)) { + NSMutableAttributedString *attrTitle = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithUTF8String:label.c_str()]]; + NSUInteger len = [attrTitle length]; + NSRange range = NSMakeRange(0, len); + [attrTitle addAttribute:NSForegroundColorAttributeName value:[self colorFromHexColorString:[NSString stringWithUTF8String:labelColor.c_str()]] range:range]; + [attrTitle fixAttributesInRange:range]; + [theButton setAttributedTitle:attrTitle]; + } + + gfx::Image image; + if (item.Get("image", &image)) { + theButton.image = image.AsNSImage(); + } + return customItem; - } else if ([identifier hasPrefix:LabelIdentifier]) { - NSString* id = [self idFromIdentifier:identifier withPrefix:LabelIdentifier]; - if (![self hasLabel:id]) return nil; - NSTextField *theLabel = [NSTextField labelWithString:[NSString stringWithUTF8String:item_labels[std::string([id UTF8String])].c_str()]]; + } + return nil; +} + +- (nullable NSTouchBarItem*) makeLabelForID:(NSString*)id withIdentifier:(NSString*)identifier { + std::string s_id = std::string([id UTF8String]); + NSLog(@"Making label: '%@'", id); + if (![self hasTBDict:s_id]) return nil; + mate::PersistentDictionary item = item_id_map[s_id]; + std::string label; + if (item.Get("label", &label)) { + NSTextField *theLabel = [NSTextField labelWithString:[NSString stringWithUTF8String:label.c_str()]]; NSCustomTouchBarItem *customItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier]; customItem.view = theLabel; + std::string customizationLabel; + if (item.Get("customizationLabel", &customizationLabel)) { + customItem.customizationLabel = [NSString stringWithUTF8String:customizationLabel.c_str()]; + } + return customItem; + } + return nil; +} + +- (nullable NSTouchBarItem*) makeColorPickerForID:(NSString*)id withIdentifier:(NSString*)identifier { + std::string s_id = std::string([id UTF8String]); + if (![self hasTBDict:s_id]) return nil; + mate::PersistentDictionary item = item_id_map[s_id]; + + NSColorPickerTouchBarItem *colorPickerItem = [[NSColorPickerTouchBarItem alloc] initWithIdentifier:identifier]; + colorPickerItem.target = self; + colorPickerItem.action = @selector(colorPickerAction:); + + std::string customizationLabel; + if (item.Get("customizationLabel", &customizationLabel)) { + colorPickerItem.customizationLabel = [NSString stringWithUTF8String:customizationLabel.c_str()]; + } + + return colorPickerItem; +} + +- (nullable NSTouchBarItem*) makeSliderForID:(NSString*)id withIdentifier:(NSString*)identifier { + std::string s_id = std::string([id UTF8String]); + if (![self hasTBDict:s_id]) return nil; + mate::PersistentDictionary item = item_id_map[s_id]; + + NSSliderTouchBarItem *sliderItem = [[NSSliderTouchBarItem alloc] initWithIdentifier:identifier]; + sliderItem.target = self; + sliderItem.action = @selector(sliderAction:); + + std::string customizationLabel; + if (item.Get("customizationLabel", &customizationLabel)) { + sliderItem.customizationLabel = [NSString stringWithUTF8String:customizationLabel.c_str()]; + } + + std::string label; + if (item.Get("label", &label)) { + sliderItem.label = [NSString stringWithUTF8String:label.c_str()]; + } + + int maxValue = 100; + int minValue = 0; + int initialValue = 50; + if (item.Get("minValue", &minValue) && item.Get("maxValue", &maxValue)) { + item.Get("initialValue", &initialValue); + } + sliderItem.slider.minValue = minValue; + sliderItem.slider.maxValue = maxValue; + sliderItem.slider.doubleValue = initialValue; + + return sliderItem; +} + +static NSTouchBarItemIdentifier ButtonIdentifier = @"com.electron.tb.button."; +static NSTouchBarItemIdentifier ColorPickerIdentifier = @"com.electron.tb.colorpicker."; +// static NSTouchBarItemIdentifier ListIdentifier = @"com.electron.tb.list."; +static NSTouchBarItemIdentifier LabelIdentifier = @"com.electron.tb.label."; +static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.tb.slider."; + +- (nullable NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { + if ([identifier hasPrefix:ButtonIdentifier]) { + NSString* id = [self idFromIdentifier:identifier withPrefix:ButtonIdentifier]; + return [self makeButtonForID:id withIdentifier:identifier]; + } else if ([identifier hasPrefix:LabelIdentifier]) { + NSString* id = [self idFromIdentifier:identifier withPrefix:LabelIdentifier]; + return [self makeLabelForID:id withIdentifier:identifier]; } else if ([identifier hasPrefix:ColorPickerIdentifier]) { - NSColorPickerTouchBarItem *colorPickerItem = [[NSColorPickerTouchBarItem alloc] initWithIdentifier:identifier]; - colorPickerItem.target = self; - colorPickerItem.action = @selector(colorPickerAction:); - return colorPickerItem; + NSString* id = [self idFromIdentifier:identifier withPrefix:ColorPickerIdentifier]; + return [self makeColorPickerForID:id withIdentifier:identifier]; + } else if ([identifier hasPrefix:SliderIdentifier]) { + NSString* id = [self idFromIdentifier:identifier withPrefix:SliderIdentifier]; + return [self makeSliderForID:id withIdentifier:identifier]; } return nil; @@ -1480,16 +1600,14 @@ void NativeWindowMac::DestroyTouchBar() { } void NativeWindowMac::SetTouchBar(mate::Arguments* args) { - std::vector items; - LOG(ERROR) << "FOO"; + std::vector items; if (args->GetNext(&items)) { - LOG(ERROR) << "BAR"; touch_bar_items_ = items; [window_ reloadTouchBar]; } } -std::vector NativeWindowMac::GetTouchBarItems() { +std::vector NativeWindowMac::GetTouchBarItems() { return touch_bar_items_; } diff --git a/default_app/default_app.js b/default_app/default_app.js index 3856c8e90a..17f591b48f 100644 --- a/default_app/default_app.js +++ b/default_app/default_app.js @@ -28,6 +28,8 @@ exports.load = (appUrl) => { mainWindow.setTouchBar(new TouchBar([ new (TouchBar.Button)({ label: 'Hello World!', + backgroundColor: "DDDDDD", + labelColor: "000000", click: () => { console.log('Hello World Clicked') } @@ -36,10 +38,18 @@ exports.load = (appUrl) => { label: 'This is a Label' }), new (TouchBar.ColorPicker)({ - change: (...args) => { - console.log('Color was changed', ...args) + change: (newColor) => { + console.log('Color was changed', newColor) } - }) + }), + new (TouchBar.Slider)({ + label: 'Slider 123', + minValue: 50, + maxValue: 1000, + change: (newVal) => { + console.log('Slider was changed', newVal, typeof newVal) + } + }), ])) }) } diff --git a/lib/browser/api/browser-window.js b/lib/browser/api/browser-window.js index fd72a4cf0f..ba1fdadcb6 100644 --- a/lib/browser/api/browser-window.js +++ b/lib/browser/api/browser-window.js @@ -134,7 +134,7 @@ BrowserWindow.prototype._init = function () { // Proxy TouchBar events this.on('-touch-bar-interaction', (event, item_type, id, ...args) => { - TouchBar._event(id, ...args) + TouchBar._event(item_type, id, ...args) }) } diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index f62d7c2ff0..0709e07f1c 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -23,8 +23,11 @@ class TouchBar { let item_id_incrementor = 1 const item_event_handlers = {} -TouchBar._event = (eventArgs) => { - const args = eventArgs.slice(1) +TouchBar._event = (itemType, eventArgs) => { + let args = eventArgs.slice(1) + if (itemType === 'slider') { + args = args.map(val => parseInt(val, 10)) + } const id_parts = eventArgs[0].split('.') const item_id = id_parts[id_parts.length - 1] if (item_event_handlers[item_id]) item_event_handlers[item_id](...args) @@ -79,11 +82,17 @@ TouchBar.Label = class TouchBarLabel extends TouchBarItem { } } -TouchBar.List = class TouchBarList extends TouchBarItem {} +TouchBar.List = class TouchBarList extends TouchBarItem { + constructor (config) { + super(config) + this.config.type = 'list' + } +} TouchBar.Slider = class TouchBarSlider extends TouchBarItem { constructor (config) { super(config) + this.config.type = 'slider'; const change = this.config.change if (typeof change === 'function') { item_event_handlers[this.id] = change From 2bc45c86653029e68b1b5dfc726cd95f7a72ee3a Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Mon, 28 Nov 2016 21:46:32 +1100 Subject: [PATCH 279/925] Change demo touchbar values --- default_app/default_app.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/default_app/default_app.js b/default_app/default_app.js index 17f591b48f..feeb68871e 100644 --- a/default_app/default_app.js +++ b/default_app/default_app.js @@ -28,8 +28,8 @@ exports.load = (appUrl) => { mainWindow.setTouchBar(new TouchBar([ new (TouchBar.Button)({ label: 'Hello World!', - backgroundColor: "DDDDDD", - labelColor: "000000", + backgroundColor: "FF0000", + labelColor: "0000FF", click: () => { console.log('Hello World Clicked') } @@ -46,6 +46,7 @@ exports.load = (appUrl) => { label: 'Slider 123', minValue: 50, maxValue: 1000, + initialValue: 300, change: (newVal) => { console.log('Slider was changed', newVal, typeof newVal) } From 269d899a9939d287579749ea264125c065adb328 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Tue, 29 Nov 2016 18:00:08 +1100 Subject: [PATCH 280/925] Implement popOver item type (woo hoo it worked) --- atom/browser/native_window_mac.mm | 130 ++++++++++++++++++++---------- default_app/default_app.js | 14 ++++ lib/browser/api/touch-bar.js | 15 +++- 3 files changed, 115 insertions(+), 44 deletions(-) diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index b7537ecb74..26430ce625 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -375,39 +375,42 @@ bool ScopedDisableResize::disable_resize_ = false; - (void)resetTouchBar { bar_items_ = [[NSMutableArray alloc] init]; self.touchBar = nil; - NSLog(@"Destroying TouchBar"); } -- (void)reloadTouchBar { - bar_items_ = [[NSMutableArray alloc] init]; - std::vector items = shell_->GetTouchBarItems(); - std::map new_map; - item_id_map = new_map; +- (NSMutableArray*)identifierArrayFromDicts:(std::vector)dicts { + NSMutableArray* idents = [[NSMutableArray alloc] init]; - for (mate::PersistentDictionary &item : items ) { + for (mate::PersistentDictionary &item : dicts) { std::string type; std::string item_id; if (item.Get("type", &type) && item.Get("id", &item_id)) { - NSLog(@"type: %@", [NSString stringWithUTF8String:type.c_str()]); - NSLog(@"id: %@", [NSString stringWithUTF8String:item_id.c_str()]); item_id_map.insert(make_pair(item_id, item)); if (type == "button") { - [bar_items_ addObject:[NSString stringWithFormat:@"%@%@", ButtonIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; + [idents addObject:[NSString stringWithFormat:@"%@%@", ButtonIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; } else if (type == "label") { - [bar_items_ addObject:[NSString stringWithFormat:@"%@%@", LabelIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; + [idents addObject:[NSString stringWithFormat:@"%@%@", LabelIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; } else if (type == "colorpicker") { - [bar_items_ addObject:[NSString stringWithFormat:@"%@%@", ColorPickerIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; + [idents addObject:[NSString stringWithFormat:@"%@%@", ColorPickerIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; } else if (type == "slider") { - [bar_items_ addObject:[NSString stringWithFormat:@"%@%@", SliderIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; + [idents addObject:[NSString stringWithFormat:@"%@%@", SliderIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; + } else if (type == "popover") { + [idents addObject:[NSString stringWithFormat:@"%@%@", PopOverIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; } } } - [bar_items_ addObject:NSTouchBarItemIdentifierOtherItemsProxy]; + [idents addObject:NSTouchBarItemIdentifierOtherItemsProxy]; + + return idents; +} + +- (void)reloadTouchBar { + std::map new_map; + item_id_map = new_map; + bar_items_ = [self identifierArrayFromDicts:shell_->GetTouchBarItems()]; self.touchBar = nil; } - (NSTouchBar *)makeTouchBar { - NSLog(@"Making Touch Bar"); return [self touchBarFromMutatableArray:bar_items_]; } @@ -422,7 +425,6 @@ bool ScopedDisableResize::disable_resize_ = false; - (void)buttonAction:(id)sender { NSString* item_id = [NSString stringWithFormat:@"com.electron.tb.button.%d", (int)((NSButton *)sender).tag]; - NSLog(@"Button with ID: '%@' was pressed", item_id); shell_->NotifyTouchBarItemInteraction("button", { std::string([item_id UTF8String]) }); } @@ -432,13 +434,11 @@ bool ScopedDisableResize::disable_resize_ = false; NSString* colorHexString = [NSString stringWithFormat:@"#%02X%02X%02X", (int) (color.redComponent * 0xFF), (int) (color.greenComponent * 0xFF), (int) (color.blueComponent * 0xFF)]; - NSLog(@"ColorPicker with ID: '%@' was updated with color '%@'", item_id, colorHexString); shell_->NotifyTouchBarItemInteraction("color_picker", { std::string([item_id UTF8String]), std::string([colorHexString UTF8String]) }); } - (void)sliderAction:(id)sender { NSString* item_id = ((NSSliderTouchBarItem *)sender).identifier; - NSLog(@"Slider with ID: '%@' was changed", item_id); shell_->NotifyTouchBarItemInteraction("slider", { std::string([item_id UTF8String]), std::to_string([((NSSliderTouchBarItem *)sender).slider intValue]) }); } @@ -474,13 +474,38 @@ bool ScopedDisableResize::disable_resize_ = false; return result; } +- (NSButton*)makeButtonForDict:(mate::PersistentDictionary)dict withLabel:(std::string)label { + NSButton *theButton = [NSButton buttonWithTitle:[NSString stringWithUTF8String:label.c_str()] target:self action:@selector(buttonAction:)]; + + std::string backgroundColor; + if (dict.Get("backgroundColor", &backgroundColor)) { + theButton.bezelColor = [self colorFromHexColorString:[NSString stringWithUTF8String:backgroundColor.c_str()]]; + } + + std::string labelColor; + if (dict.Get("labelColor", &labelColor)) { + NSMutableAttributedString *attrTitle = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithUTF8String:label.c_str()]]; + NSUInteger len = [attrTitle length]; + NSRange range = NSMakeRange(0, len); + [attrTitle addAttribute:NSForegroundColorAttributeName value:[self colorFromHexColorString:[NSString stringWithUTF8String:labelColor.c_str()]] range:range]; + [attrTitle fixAttributesInRange:range]; + [theButton setAttributedTitle:attrTitle]; + } + + gfx::Image image; + if (dict.Get("image", &image)) { + theButton.image = image.AsNSImage(); + } + return theButton; +} + - (nullable NSTouchBarItem *)makeButtonForID:(NSString*)id withIdentifier:(NSString*)identifier { std::string s_id = std::string([id UTF8String]); if (![self hasTBDict:s_id]) return nil; mate::PersistentDictionary item = item_id_map[s_id]; std::string label; if (item.Get("label", &label)) { - NSButton *theButton = [NSButton buttonWithTitle:[NSString stringWithUTF8String:label.c_str()] target:self action:@selector(buttonAction:)]; + NSButton* theButton = [self makeButtonForDict:item withLabel:label]; theButton.tag = [id floatValue]; NSCustomTouchBarItem *customItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier]; @@ -491,26 +516,6 @@ bool ScopedDisableResize::disable_resize_ = false; customItem.customizationLabel = [NSString stringWithUTF8String:customizationLabel.c_str()]; } - std::string backgroundColor; - if (item.Get("backgroundColor", &backgroundColor)) { - theButton.bezelColor = [self colorFromHexColorString:[NSString stringWithUTF8String:backgroundColor.c_str()]]; - } - - std::string labelColor; - if (item.Get("labelColor", &labelColor)) { - NSMutableAttributedString *attrTitle = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithUTF8String:label.c_str()]]; - NSUInteger len = [attrTitle length]; - NSRange range = NSMakeRange(0, len); - [attrTitle addAttribute:NSForegroundColorAttributeName value:[self colorFromHexColorString:[NSString stringWithUTF8String:labelColor.c_str()]] range:range]; - [attrTitle fixAttributesInRange:range]; - [theButton setAttributedTitle:attrTitle]; - } - - gfx::Image image; - if (item.Get("image", &image)) { - theButton.image = image.AsNSImage(); - } - return customItem; } return nil; @@ -518,7 +523,6 @@ bool ScopedDisableResize::disable_resize_ = false; - (nullable NSTouchBarItem*) makeLabelForID:(NSString*)id withIdentifier:(NSString*)identifier { std::string s_id = std::string([id UTF8String]); - NSLog(@"Making label: '%@'", id); if (![self hasTBDict:s_id]) return nil; mate::PersistentDictionary item = item_id_map[s_id]; std::string label; @@ -577,9 +581,10 @@ bool ScopedDisableResize::disable_resize_ = false; int maxValue = 100; int minValue = 0; int initialValue = 50; - if (item.Get("minValue", &minValue) && item.Get("maxValue", &maxValue)) { - item.Get("initialValue", &initialValue); - } + item.Get("minValue", &minValue); + item.Get("maxValue", &maxValue); + item.Get("initialValue", &initialValue); + sliderItem.slider.minValue = minValue; sliderItem.slider.maxValue = maxValue; sliderItem.slider.doubleValue = initialValue; @@ -587,10 +592,46 @@ bool ScopedDisableResize::disable_resize_ = false; return sliderItem; } +- (nullable NSTouchBarItem*) makePopOverForID:(NSString*)id withIdentifier:(NSString*)identifier { + std::string s_id = std::string([id UTF8String]); + if (![self hasTBDict:s_id]) return nil; + mate::PersistentDictionary item = item_id_map[s_id]; + + NSPopoverTouchBarItem *popOverItem = [[NSPopoverTouchBarItem alloc] initWithIdentifier:identifier]; + + std::string customizationLabel; + if (item.Get("customizationLabel", &customizationLabel)) { + popOverItem.customizationLabel = [NSString stringWithUTF8String:customizationLabel.c_str()]; + } + + std::string label; + gfx::Image image; + if (item.Get("label", &label)) { + popOverItem.collapsedRepresentationLabel = [NSString stringWithUTF8String:label.c_str()]; + } else if (item.Get("image", &image)) { + popOverItem.collapsedRepresentationImage = image.AsNSImage(); + } + + bool showCloseButton; + if (item.Get("showCloseButton", &showCloseButton)) { + popOverItem.showsCloseButton = showCloseButton; + } + + std::vector touchBar; + if (item.Get("touchBar", &touchBar)) { + popOverItem.popoverTouchBar = [self touchBarFromMutatableArray:[self identifierArrayFromDicts:touchBar]]; + } else { + return nil; + } + + return popOverItem; +} + static NSTouchBarItemIdentifier ButtonIdentifier = @"com.electron.tb.button."; static NSTouchBarItemIdentifier ColorPickerIdentifier = @"com.electron.tb.colorpicker."; // static NSTouchBarItemIdentifier ListIdentifier = @"com.electron.tb.list."; static NSTouchBarItemIdentifier LabelIdentifier = @"com.electron.tb.label."; +static NSTouchBarItemIdentifier PopOverIdentifier = @"com.electron.tb.popover."; static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.tb.slider."; - (nullable NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { @@ -606,6 +647,9 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.tb.slider."; } else if ([identifier hasPrefix:SliderIdentifier]) { NSString* id = [self idFromIdentifier:identifier withPrefix:SliderIdentifier]; return [self makeSliderForID:id withIdentifier:identifier]; + } else if ([identifier hasPrefix:PopOverIdentifier]) { + NSString* id = [self idFromIdentifier:identifier withPrefix:PopOverIdentifier]; + return [self makePopOverForID:id withIdentifier:identifier]; } return nil; diff --git a/default_app/default_app.js b/default_app/default_app.js index feeb68871e..401703a9fe 100644 --- a/default_app/default_app.js +++ b/default_app/default_app.js @@ -28,6 +28,7 @@ exports.load = (appUrl) => { mainWindow.setTouchBar(new TouchBar([ new (TouchBar.Button)({ label: 'Hello World!', + // image: '/path/to/image', backgroundColor: "FF0000", labelColor: "0000FF", click: () => { @@ -42,6 +43,19 @@ exports.load = (appUrl) => { console.log('Color was changed', newColor) } }), + new (TouchBar.PopOver)({ + // image: '/path/to/image', + label: 'foo', + showCloseButton: true, + touchBar: new TouchBar([ + new (TouchBar.Button)({ + label: 'Sub Button', + click: () => { + console.log('Sub Button Clicked') + } + }) + ]) + }), new (TouchBar.Slider)({ label: 'Slider 123', minValue: 50, diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index 0709e07f1c..1a397fc573 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -7,7 +7,6 @@ class TouchBar { if (!Array.isArray(items)) { throw new Error('The items object provided has to be an array') } - console.log(items) items.forEach((item) => { if (!item.id) { throw new Error('Each item must be an instance of a TouchBarItem') @@ -89,6 +88,20 @@ TouchBar.List = class TouchBarList extends TouchBarItem { } } +TouchBar.PopOver = class TouchBarPopOver extends TouchBarItem { + constructor (config) { + super(config) + this.config.type = 'popover' + } + + toJSON () { + const config = this.config; + return Object.assign({}, config, { + touchBar: config.touchBar && config.touchBar.toJSON ? config.touchBar.toJSON() : [] + }) + } +} + TouchBar.Slider = class TouchBarSlider extends TouchBarItem { constructor (config) { super(config) From 43cc5079d80f2abc2fdd0a970a3287d022ecbbd9 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Tue, 29 Nov 2016 18:36:57 +1100 Subject: [PATCH 281/925] Implement group item --- atom/browser/native_window_mac.mm | 40 ++++++++++++++++++++++++++++++- default_app/default_app.js | 14 +++++++---- lib/browser/api/touch-bar.js | 21 ++++++++++------ 3 files changed, 62 insertions(+), 13 deletions(-) diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 26430ce625..81aa690cab 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -395,6 +395,8 @@ bool ScopedDisableResize::disable_resize_ = false; [idents addObject:[NSString stringWithFormat:@"%@%@", SliderIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; } else if (type == "popover") { [idents addObject:[NSString stringWithFormat:@"%@%@", PopOverIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; + } else if (type == "group") { + [idents addObject:[NSString stringWithFormat:@"%@%@", GroupIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; } } } @@ -627,14 +629,43 @@ bool ScopedDisableResize::disable_resize_ = false; return popOverItem; } +- (nullable NSTouchBarItem*) makeGroupForID:(NSString*)id withIdentifier:(NSString*)identifier { + std::string s_id = std::string([id UTF8String]); + if (![self hasTBDict:s_id]) return nil; + mate::PersistentDictionary item = item_id_map[s_id]; + + std::vector items; + if (!item.Get("items", &items)) { + return nil; + } + + NSMutableArray* generatedItems = [[NSMutableArray alloc] init]; + NSMutableArray* identList = [self identifierArrayFromDicts:items]; + for (NSUInteger i = 0; i < [identList count]; i++) { + NSTouchBarItem* generatedItem = [self makeItemForIdentifier:[identList objectAtIndex:i]]; + if (generatedItem) { + [generatedItems addObject:generatedItem]; + } + } + NSGroupTouchBarItem *groupItem = [NSGroupTouchBarItem groupItemWithIdentifier:identifier items:generatedItems]; + + std::string customizationLabel; + if (item.Get("customizationLabel", &customizationLabel)) { + groupItem.customizationLabel = [NSString stringWithUTF8String:customizationLabel.c_str()]; + } + + return groupItem; +} + static NSTouchBarItemIdentifier ButtonIdentifier = @"com.electron.tb.button."; static NSTouchBarItemIdentifier ColorPickerIdentifier = @"com.electron.tb.colorpicker."; // static NSTouchBarItemIdentifier ListIdentifier = @"com.electron.tb.list."; +static NSTouchBarItemIdentifier GroupIdentifier = @"com.electron.tb.group."; static NSTouchBarItemIdentifier LabelIdentifier = @"com.electron.tb.label."; static NSTouchBarItemIdentifier PopOverIdentifier = @"com.electron.tb.popover."; static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.tb.slider."; -- (nullable NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { +- (nullable NSTouchBarItem *)makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { if ([identifier hasPrefix:ButtonIdentifier]) { NSString* id = [self idFromIdentifier:identifier withPrefix:ButtonIdentifier]; return [self makeButtonForID:id withIdentifier:identifier]; @@ -650,11 +681,18 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.tb.slider."; } else if ([identifier hasPrefix:PopOverIdentifier]) { NSString* id = [self idFromIdentifier:identifier withPrefix:PopOverIdentifier]; return [self makePopOverForID:id withIdentifier:identifier]; + } else if ([identifier hasPrefix:GroupIdentifier]) { + NSString* id = [self idFromIdentifier:identifier withPrefix:GroupIdentifier]; + return [self makeGroupForID:id withIdentifier:identifier]; } return nil; } +- (nullable NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { + return [self makeItemForIdentifier:identifier]; +} + // NSWindow overrides. diff --git a/default_app/default_app.js b/default_app/default_app.js index 401703a9fe..9ebc6b0ae9 100644 --- a/default_app/default_app.js +++ b/default_app/default_app.js @@ -48,11 +48,15 @@ exports.load = (appUrl) => { label: 'foo', showCloseButton: true, touchBar: new TouchBar([ - new (TouchBar.Button)({ - label: 'Sub Button', - click: () => { - console.log('Sub Button Clicked') - } + new (TouchBar.Group)({ + items: new TouchBar( + [1, 2, 3].map((i) => new (TouchBar.Button)({ + label: `Button ${i}`, + click: () => { + console.log(`Button ${i} (group) Clicked`) + } + })) + ) }) ]) }), diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index 1a397fc573..0479e19252 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -74,6 +74,20 @@ TouchBar.ColorPicker = class TouchBarColorPicker extends TouchBarItem { } } +TouchBar.Group = class TouchBarGroup extends TouchBarItem { + constructor (config) { + super(config) + this.config.type = 'group' + } + + toJSON () { + const config = this.config; + return Object.assign({}, config, { + items: config.items && config.items.toJSON ? config.items.toJSON() : [] + }) + } +} + TouchBar.Label = class TouchBarLabel extends TouchBarItem { constructor (config) { super(config) @@ -81,13 +95,6 @@ TouchBar.Label = class TouchBarLabel extends TouchBarItem { } } -TouchBar.List = class TouchBarList extends TouchBarItem { - constructor (config) { - super(config) - this.config.type = 'list' - } -} - TouchBar.PopOver = class TouchBarPopOver extends TouchBarItem { constructor (config) { super(config) From 257b32b84bf6c4f8cffcf5f4db3a1329e580b596 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Tue, 29 Nov 2016 18:44:25 +1100 Subject: [PATCH 282/925] Remove unused ident --- atom/browser/native_window_mac.mm | 1 - 1 file changed, 1 deletion(-) diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 81aa690cab..dfd71555ad 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -659,7 +659,6 @@ bool ScopedDisableResize::disable_resize_ = false; static NSTouchBarItemIdentifier ButtonIdentifier = @"com.electron.tb.button."; static NSTouchBarItemIdentifier ColorPickerIdentifier = @"com.electron.tb.colorpicker."; -// static NSTouchBarItemIdentifier ListIdentifier = @"com.electron.tb.list."; static NSTouchBarItemIdentifier GroupIdentifier = @"com.electron.tb.group."; static NSTouchBarItemIdentifier LabelIdentifier = @"com.electron.tb.label."; static NSTouchBarItemIdentifier PopOverIdentifier = @"com.electron.tb.popover."; From 4f0caffc3b1362fc8827e1ffb73acf789ead8b48 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Tue, 29 Nov 2016 18:55:07 +1100 Subject: [PATCH 283/925] Fix JS linting --- default_app/default_app.js | 8 +++---- lib/browser/api/browser-window.js | 10 ++++----- lib/browser/api/touch-bar.js | 35 ++++++++++++++----------------- 3 files changed, 25 insertions(+), 28 deletions(-) diff --git a/default_app/default_app.js b/default_app/default_app.js index 9ebc6b0ae9..57110e9c90 100644 --- a/default_app/default_app.js +++ b/default_app/default_app.js @@ -1,4 +1,4 @@ -const {app, BrowserWindow,TouchBar} = require('electron') +const {app, BrowserWindow, TouchBar} = require('electron') const path = require('path') let mainWindow = null @@ -29,8 +29,8 @@ exports.load = (appUrl) => { new (TouchBar.Button)({ label: 'Hello World!', // image: '/path/to/image', - backgroundColor: "FF0000", - labelColor: "0000FF", + backgroundColor: 'FF0000', + labelColor: '0000FF', click: () => { console.log('Hello World Clicked') } @@ -68,7 +68,7 @@ exports.load = (appUrl) => { change: (newVal) => { console.log('Slider was changed', newVal, typeof newVal) } - }), + }) ])) }) } diff --git a/lib/browser/api/browser-window.js b/lib/browser/api/browser-window.js index ba1fdadcb6..ac6e3b4bb6 100644 --- a/lib/browser/api/browser-window.js +++ b/lib/browser/api/browser-window.js @@ -1,6 +1,6 @@ 'use strict' -const {ipcMain,TouchBar} = require('electron') +const {ipcMain, TouchBar} = require('electron') const {EventEmitter} = require('events') const {BrowserWindow} = process.atomBinding('window') const v8Util = process.atomBinding('v8_util') @@ -133,8 +133,8 @@ BrowserWindow.prototype._init = function () { }) // Proxy TouchBar events - this.on('-touch-bar-interaction', (event, item_type, id, ...args) => { - TouchBar._event(item_type, id, ...args) + this.on('-touch-bar-interaction', (event, itemType, id, ...args) => { + TouchBar._event(itemType, id, ...args) }) } @@ -206,9 +206,9 @@ Object.assign(BrowserWindow.prototype, { // TouchBar API BrowserWindow.prototype.setTouchBar = function (touchBar) { if (touchBar === null || typeof touchBar === 'undefined') { - this._destroyTouchBar(); + this._destroyTouchBar() } else if (Array.isArray(touchBar)) { - this._setTouchBar((new TouchBar(touchBar)).toJSON()); + this._setTouchBar((new TouchBar(touchBar)).toJSON()) } else { this._setTouchBar(touchBar.toJSON()) } diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index 0479e19252..1b18656a69 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -1,9 +1,6 @@ -const {EventEmitter} = require('events') -const {app} = require('electron') - class TouchBar { constructor (items) { - this.items = items; + this.items = items if (!Array.isArray(items)) { throw new Error('The items object provided has to be an array') } @@ -15,33 +12,33 @@ class TouchBar { } toJSON () { - return this.items.map((item) => item.toJSON ? item.toJSON() : item); + return this.items.map((item) => item.toJSON ? item.toJSON() : item) } } -let item_id_incrementor = 1 -const item_event_handlers = {} +let itemIdIncrementor = 1 +const itemEventHandlers = {} TouchBar._event = (itemType, eventArgs) => { let args = eventArgs.slice(1) if (itemType === 'slider') { args = args.map(val => parseInt(val, 10)) } - const id_parts = eventArgs[0].split('.') - const item_id = id_parts[id_parts.length - 1] - if (item_event_handlers[item_id]) item_event_handlers[item_id](...args) + const idParts = eventArgs[0].split('.') + const itemId = idParts[idParts.length - 1] + if (itemEventHandlers[itemId]) itemEventHandlers[itemId](...args) } class TouchBarItem { constructor (config) { - this.id = item_id_incrementor++ + this.id = itemIdIncrementor++ const mConfig = Object.assign({}, config || {}) Object.defineProperty(this, 'config', { configurable: false, enumerable: false, get: () => mConfig }) - this.config.id = `${this.config.id || this.id}`; + this.config.id = `${this.config.id || this.id}` if (typeof this.config !== 'object' || this.config === null) { throw new Error('Provided config must be a non-null object') } @@ -58,7 +55,7 @@ TouchBar.Button = class TouchBarButton extends TouchBarItem { this.config.type = 'button' const click = config.click if (typeof click === 'function') { - item_event_handlers[`${this.id}`] = click + itemEventHandlers[`${this.id}`] = click } } } @@ -69,7 +66,7 @@ TouchBar.ColorPicker = class TouchBarColorPicker extends TouchBarItem { this.config.type = 'colorpicker' const change = this.config.change if (typeof change === 'function') { - item_event_handlers[`${this.id}`] = change + itemEventHandlers[`${this.id}`] = change } } } @@ -81,7 +78,7 @@ TouchBar.Group = class TouchBarGroup extends TouchBarItem { } toJSON () { - const config = this.config; + const config = this.config return Object.assign({}, config, { items: config.items && config.items.toJSON ? config.items.toJSON() : [] }) @@ -102,7 +99,7 @@ TouchBar.PopOver = class TouchBarPopOver extends TouchBarItem { } toJSON () { - const config = this.config; + const config = this.config return Object.assign({}, config, { touchBar: config.touchBar && config.touchBar.toJSON ? config.touchBar.toJSON() : [] }) @@ -112,12 +109,12 @@ TouchBar.PopOver = class TouchBarPopOver extends TouchBarItem { TouchBar.Slider = class TouchBarSlider extends TouchBarItem { constructor (config) { super(config) - this.config.type = 'slider'; + this.config.type = 'slider' const change = this.config.change if (typeof change === 'function') { - item_event_handlers[this.id] = change + itemEventHandlers[this.id] = change } } } -module.exports = TouchBar; +module.exports = TouchBar From d1b3ba39bd6f9e9af2bb6bbfe98b6d57e1e310af Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Tue, 29 Nov 2016 18:57:26 +1100 Subject: [PATCH 284/925] Fix cpp linting --- atom/browser/api/atom_api_window.cc | 3 ++- atom/browser/api/atom_api_window.h | 3 ++- atom/browser/native_window.h | 5 +++-- atom/browser/native_window_observer.h | 4 +++- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/atom/browser/api/atom_api_window.cc b/atom/browser/api/atom_api_window.cc index ae52b03f07..e031916a0f 100644 --- a/atom/browser/api/atom_api_window.cc +++ b/atom/browser/api/atom_api_window.cc @@ -282,7 +282,8 @@ void Window::OnExecuteWindowsCommand(const std::string& command_name) { Emit("app-command", command_name); } -void Window::OnTouchBarItemResult(const std::string& item_type, const std::vector& args) { +void Window::OnTouchBarItemResult(const std::string& item_type, + const std::vector& args) { Emit("-touch-bar-interaction", item_type, args); } diff --git a/atom/browser/api/atom_api_window.h b/atom/browser/api/atom_api_window.h index 55347bb54d..7241982f2b 100644 --- a/atom/browser/api/atom_api_window.h +++ b/atom/browser/api/atom_api_window.h @@ -85,7 +85,8 @@ class Window : public mate::TrackableObject, void OnRendererUnresponsive() override; void OnRendererResponsive() override; void OnExecuteWindowsCommand(const std::string& command_name) override; - void OnTouchBarItemResult(const std::string& item_type, const std::vector& args) override; + void OnTouchBarItemResult(const std::string& item_type, + const std::vector& args) override; #if defined(OS_WIN) void OnWindowMessage(UINT message, WPARAM w_param, LPARAM l_param) override; diff --git a/atom/browser/native_window.h b/atom/browser/native_window.h index 38636fa2a5..b76a49a061 100644 --- a/atom/browser/native_window.h +++ b/atom/browser/native_window.h @@ -21,9 +21,9 @@ #include "content/public/browser/web_contents_observer.h" #include "content/public/browser/web_contents_user_data.h" #include "extensions/browser/app_window/size_constraints.h" +#include "native_mate/constructor.h" #include "ui/gfx/image/image.h" #include "ui/gfx/image/image_skia.h" -#include "native_mate/constructor.h" class SkRegion; @@ -233,7 +233,8 @@ class NativeWindow : public base::SupportsUserData, void NotifyWindowEnterHtmlFullScreen(); void NotifyWindowLeaveHtmlFullScreen(); void NotifyWindowExecuteWindowsCommand(const std::string& command); - void NotifyTouchBarItemInteraction(const std::string& item_type, const std::vector& args); + void NotifyTouchBarItemInteraction(const std::string& item_type, + const std::vector& args); #if defined(OS_WIN) void NotifyWindowMessage(UINT message, WPARAM w_param, LPARAM l_param); diff --git a/atom/browser/native_window_observer.h b/atom/browser/native_window_observer.h index bd9ff21f56..7fd1b7caeb 100644 --- a/atom/browser/native_window_observer.h +++ b/atom/browser/native_window_observer.h @@ -6,6 +6,7 @@ #define ATOM_BROWSER_NATIVE_WINDOW_OBSERVER_H_ #include +#include #include "base/strings/string16.h" #include "ui/base/window_open_disposition.h" @@ -70,7 +71,8 @@ class NativeWindowObserver { virtual void OnWindowLeaveFullScreen() {} virtual void OnWindowEnterHtmlFullScreen() {} virtual void OnWindowLeaveHtmlFullScreen() {} - virtual void OnTouchBarItemResult(const std::string& item_type, const std::vector& args) {} + virtual void OnTouchBarItemResult(const std::string& item_type, + const std::vector& args) {} // Called when window message received #if defined(OS_WIN) From 15dcc314d34c3b72b228c4afdeb10864f21166c4 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Fri, 2 Dec 2016 23:43:42 +1100 Subject: [PATCH 285/925] Export the TouchBar items as their own props on the electron main export --- lib/browser/api/exports/electron.js | 36 +++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/lib/browser/api/exports/electron.js b/lib/browser/api/exports/electron.js index 3f40595217..0c6ce065b0 100644 --- a/lib/browser/api/exports/electron.js +++ b/lib/browser/api/exports/electron.js @@ -109,6 +109,42 @@ Object.defineProperties(exports, { return require('../touch-bar') } }, + TouchBarButton: { + enumerable: true, + get: function () { + return require('../touch-bar').Button + } + }, + TouchBarColorPicker: { + enumerable: true, + get: function () { + return require('../touch-bar').ColorPicker + } + }, + TouchBarGroup: { + enumerable: true, + get: function () { + return require('../touch-bar').Group + } + }, + TouchBarLabel: { + enumerable: true, + get: function () { + return require('../touch-bar').Label + } + }, + TouchBarPopOver: { + enumerable: true, + get: function () { + return require('../touch-bar').PopOver + } + }, + TouchBarSlider: { + enumerable: true, + get: function () { + return require('../touch-bar').Slider + } + }, Tray: { enumerable: true, get: function () { From 61949657f0c0591556e63ef10c11bfcce4d9ed45 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Fri, 2 Dec 2016 23:58:02 +1100 Subject: [PATCH 286/925] Some docs for touch bar --- docs/api/browser-window.md | 6 ++++++ docs/api/touch-bar.md | 44 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 docs/api/touch-bar.md diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index a8aa5e70c0..fc1c705b21 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -1266,6 +1266,12 @@ Controls whether to hide cursor when typing. Adds a vibrancy effect to the browser window. Passing `null` or an empty string will remove the vibrancy effect on the window. +#### `win.setTouchBar(touchBar)` _macOS_ + +* `touchBar` TouchBar + +Sets the touchBar layout for the current window. + [blink-feature-string]: https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.json5?l=62 [quick-look]: https://en.wikipedia.org/wiki/Quick_Look [vibrancy-docs]: https://developer.apple.com/reference/appkit/nsvisualeffectview?language=objc diff --git a/docs/api/touch-bar.md b/docs/api/touch-bar.md new file mode 100644 index 0000000000..68bdaf88a1 --- /dev/null +++ b/docs/api/touch-bar.md @@ -0,0 +1,44 @@ +## Class: TouchBar + +> Create TouchBar layouts for native macOS applications + +Process: [Main](../tutorial/quick-start.md#main-process) + +### `new TouchBar(items)` + +* `items` (TouchBarButton | TouchBarColorPicker | TouchBarGroup | TouchBarLabel | TouchBarPopOver | TouchBarSlider)[] + +Creates a new touch bar. Note any changes to the TouchBar instance +will not affect the rendered TouchBar. To affect the rendered +TouchBar you **must** use either methods on the TouchBar or methods +on the TouchBar* items + +### Instance Methods + +The `menu` object has the following instance methods: + +#### `touchBar.destroy()` + +Immediately destroys the TouchBar instance and will reset the rendered +touch bar. + +## Examples + +The `TouchBar` class is only available in the main process, it is not currently possible to use in the renderer process **even** through the remote module. + +### Main process + +An example of creating a touch bar in the main process: + +```javascript +const {TouchBar, TouchBarButton} = require('electron') + +const touchBar = new TouchBar([ + new TouchBarButton({ + label: 'Example Button', + click: () => console.log('I was clicked') + }) +]) + +mainWindow.setTouchBar(touchBar) +``` From dd09c91cf29fdd9fc2bf41cf82e3c3a300ac96ba Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Fri, 16 Dec 2016 17:24:51 +1100 Subject: [PATCH 287/925] initial work on updating touch bar item config without rerender --- atom/browser/api/atom_api_window.cc | 5 ++ atom/browser/api/atom_api_window.h | 1 + atom/browser/native_window.cc | 3 ++ atom/browser/native_window.h | 1 + atom/browser/native_window_mac.h | 1 + atom/browser/native_window_mac.mm | 76 +++++++++++++++++++++------- default_app/default_app.js | 22 ++++---- default_app/icon.png | Bin 122330 -> 17533 bytes lib/browser/api/browser-window.js | 6 +++ lib/browser/api/touch-bar.js | 10 ++++ 10 files changed, 98 insertions(+), 27 deletions(-) diff --git a/atom/browser/api/atom_api_window.cc b/atom/browser/api/atom_api_window.cc index e031916a0f..adafab9173 100644 --- a/atom/browser/api/atom_api_window.cc +++ b/atom/browser/api/atom_api_window.cc @@ -853,6 +853,10 @@ void Window::SetTouchBar(mate::Arguments* args) { window_->SetTouchBar(args); } +void Window::RefreshTouchBarItem(mate::Arguments* args) { + window_->RefreshTouchBarItem(args); +} + int32_t Window::ID() const { return weak_map_id(); } @@ -975,6 +979,7 @@ void Window::BuildPrototype(v8::Isolate* isolate, .SetMethod("setVibrancy", &Window::SetVibrancy) .SetMethod("_destroyTouchBar", &Window::DestroyTouchBar) .SetMethod("_setTouchBar", &Window::SetTouchBar) + .SetMethod("_refreshTouchBarItem", &Window::RefreshTouchBarItem) #if defined(OS_WIN) .SetMethod("hookWindowMessage", &Window::HookWindowMessage) .SetMethod("isWindowMessageHooked", &Window::IsWindowMessageHooked) diff --git a/atom/browser/api/atom_api_window.h b/atom/browser/api/atom_api_window.h index 7241982f2b..2620292b25 100644 --- a/atom/browser/api/atom_api_window.h +++ b/atom/browser/api/atom_api_window.h @@ -207,6 +207,7 @@ class Window : public mate::TrackableObject, void SetVibrancy(mate::Arguments* args); void DestroyTouchBar(); void SetTouchBar(mate::Arguments* args); + void RefreshTouchBarItem(mate::Arguments* args); v8::Local WebContents(v8::Isolate* isolate); diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index 1aead897d9..ab7e108492 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -347,6 +347,9 @@ void NativeWindow::DestroyTouchBar() { void NativeWindow::SetTouchBar(mate::Arguments* args) { } +void NativeWindow::RefreshTouchBarItem(mate::Arguments* args) { +} + void NativeWindow::FocusOnWebView() { web_contents()->GetRenderViewHost()->GetWidget()->Focus(); } diff --git a/atom/browser/native_window.h b/atom/browser/native_window.h index b76a49a061..23c393e7ae 100644 --- a/atom/browser/native_window.h +++ b/atom/browser/native_window.h @@ -173,6 +173,7 @@ class NativeWindow : public base::SupportsUserData, // Touchbar API virtual void DestroyTouchBar(); virtual void SetTouchBar(mate::Arguments* args); + virtual void RefreshTouchBarItem(mate::Arguments* args); // Webview APIs. virtual void FocusOnWebView(); diff --git a/atom/browser/native_window_mac.h b/atom/browser/native_window_mac.h index b960290c82..f8ebfec3ad 100644 --- a/atom/browser/native_window_mac.h +++ b/atom/browser/native_window_mac.h @@ -104,6 +104,7 @@ class NativeWindowMac : public NativeWindow, void SetVibrancy(const std::string& type) override; void DestroyTouchBar() override; void SetTouchBar(mate::Arguments* args) override; + void RefreshTouchBarItem(mate::Arguments* args) override; std::vector GetTouchBarItems(); // content::RenderWidgetHost::InputEventObserver: diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index dfd71555ad..09317221c8 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -353,6 +353,7 @@ bool ScopedDisableResize::disable_resize_ = false; - (void)setEnableLargerThanScreen:(bool)enable; - (void)enableWindowButtonsOffset; - (void)reloadTouchBar; +- (void)refreshTouchBarItem:(mate::Arguments*)args; - (void)resetTouchBar; - (NSTouchBar*)touchBarFromMutatableArray:(NSMutableArray*)items; @end @@ -363,6 +364,7 @@ bool ScopedDisableResize::disable_resize_ = false; @implementation AtomNSWindow NSMutableArray* bar_items_ = [[NSMutableArray alloc] init]; std::map item_id_map; + std::map item_map; - (void)setShell:(atom::NativeWindowMac*)shell { shell_ = shell; @@ -405,6 +407,20 @@ bool ScopedDisableResize::disable_resize_ = false; return idents; } +- (void)refreshTouchBarItem:(mate::Arguments*)args { + std::string item_id; + std::string type; + mate::PersistentDictionary dict; + if (args->GetNext(&dict) && dict.Get("type", &type) && dict.Get("id", &item_id)) { + if (item_map.find(item_id) != item_map.end()) { + if (type == "slider") { + NSSliderTouchBarItem* item = (NSSliderTouchBarItem *)item_map[item_id]; + [self updateSlider:item withOpts:dict]; + } + } + } +} + - (void)reloadTouchBar { std::map new_map; item_id_map = new_map; @@ -505,12 +521,16 @@ bool ScopedDisableResize::disable_resize_ = false; std::string s_id = std::string([id UTF8String]); if (![self hasTBDict:s_id]) return nil; mate::PersistentDictionary item = item_id_map[s_id]; + NSCustomTouchBarItem *customItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier]; + return [self updateButton:customItem withOpts:item withID:id]; +} + +- (nullable NSTouchBarItem *)updateButton:(NSCustomTouchBarItem*)customItem withOpts:(mate::PersistentDictionary)item withID:(NSString*)id { std::string label; if (item.Get("label", &label)) { NSButton* theButton = [self makeButtonForDict:item withLabel:label]; theButton.tag = [id floatValue]; - NSCustomTouchBarItem *customItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier]; customItem.view = theButton; std::string customizationLabel; @@ -527,11 +547,15 @@ bool ScopedDisableResize::disable_resize_ = false; std::string s_id = std::string([id UTF8String]); if (![self hasTBDict:s_id]) return nil; mate::PersistentDictionary item = item_id_map[s_id]; + NSCustomTouchBarItem *customItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier]; + return [self updateLabel:customItem withOpts:item]; +} + +- (nullable NSTouchBarItem*) updateLabel:(NSCustomTouchBarItem*)customItem withOpts:(mate::PersistentDictionary)item { std::string label; if (item.Get("label", &label)) { NSTextField *theLabel = [NSTextField labelWithString:[NSString stringWithUTF8String:label.c_str()]]; - NSCustomTouchBarItem *customItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier]; customItem.view = theLabel; std::string customizationLabel; @@ -548,8 +572,11 @@ bool ScopedDisableResize::disable_resize_ = false; std::string s_id = std::string([id UTF8String]); if (![self hasTBDict:s_id]) return nil; mate::PersistentDictionary item = item_id_map[s_id]; - NSColorPickerTouchBarItem *colorPickerItem = [[NSColorPickerTouchBarItem alloc] initWithIdentifier:identifier]; + return [self updateColorPicker:colorPickerItem withOpts:item]; +} + +- (nullable NSTouchBarItem*) updateColorPicker:(NSColorPickerTouchBarItem*)colorPickerItem withOpts:(mate::PersistentDictionary)item { colorPickerItem.target = self; colorPickerItem.action = @selector(colorPickerAction:); @@ -565,8 +592,11 @@ bool ScopedDisableResize::disable_resize_ = false; std::string s_id = std::string([id UTF8String]); if (![self hasTBDict:s_id]) return nil; mate::PersistentDictionary item = item_id_map[s_id]; - NSSliderTouchBarItem *sliderItem = [[NSSliderTouchBarItem alloc] initWithIdentifier:identifier]; + return [self updateSlider:sliderItem withOpts:item]; +} + +- (nullable NSTouchBarItem*) updateSlider:(NSSliderTouchBarItem*)sliderItem withOpts:(mate::PersistentDictionary)item { sliderItem.target = self; sliderItem.action = @selector(sliderAction:); @@ -598,9 +628,11 @@ bool ScopedDisableResize::disable_resize_ = false; std::string s_id = std::string([id UTF8String]); if (![self hasTBDict:s_id]) return nil; mate::PersistentDictionary item = item_id_map[s_id]; - NSPopoverTouchBarItem *popOverItem = [[NSPopoverTouchBarItem alloc] initWithIdentifier:identifier]; + return [self updatePopOver:popOverItem withOpts:item]; +} +- (nullable NSTouchBarItem*) updatePopOver:(NSPopoverTouchBarItem*)popOverItem withOpts:(mate::PersistentDictionary)item { std::string customizationLabel; if (item.Get("customizationLabel", &customizationLabel)) { popOverItem.customizationLabel = [NSString stringWithUTF8String:customizationLabel.c_str()]; @@ -665,27 +697,31 @@ static NSTouchBarItemIdentifier PopOverIdentifier = @"com.electron.tb.popover."; static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.tb.slider."; - (nullable NSTouchBarItem *)makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { + NSTouchBarItem * item = nil; + NSString * id = nil; if ([identifier hasPrefix:ButtonIdentifier]) { - NSString* id = [self idFromIdentifier:identifier withPrefix:ButtonIdentifier]; - return [self makeButtonForID:id withIdentifier:identifier]; + id = [self idFromIdentifier:identifier withPrefix:ButtonIdentifier]; + item = [self makeButtonForID:id withIdentifier:identifier]; } else if ([identifier hasPrefix:LabelIdentifier]) { - NSString* id = [self idFromIdentifier:identifier withPrefix:LabelIdentifier]; - return [self makeLabelForID:id withIdentifier:identifier]; + id = [self idFromIdentifier:identifier withPrefix:LabelIdentifier]; + item = [self makeLabelForID:id withIdentifier:identifier]; } else if ([identifier hasPrefix:ColorPickerIdentifier]) { - NSString* id = [self idFromIdentifier:identifier withPrefix:ColorPickerIdentifier]; - return [self makeColorPickerForID:id withIdentifier:identifier]; + id = [self idFromIdentifier:identifier withPrefix:ColorPickerIdentifier]; + item = [self makeColorPickerForID:id withIdentifier:identifier]; } else if ([identifier hasPrefix:SliderIdentifier]) { - NSString* id = [self idFromIdentifier:identifier withPrefix:SliderIdentifier]; - return [self makeSliderForID:id withIdentifier:identifier]; + id = [self idFromIdentifier:identifier withPrefix:SliderIdentifier]; + item = [self makeSliderForID:id withIdentifier:identifier]; } else if ([identifier hasPrefix:PopOverIdentifier]) { - NSString* id = [self idFromIdentifier:identifier withPrefix:PopOverIdentifier]; - return [self makePopOverForID:id withIdentifier:identifier]; + id = [self idFromIdentifier:identifier withPrefix:PopOverIdentifier]; + item = [self makePopOverForID:id withIdentifier:identifier]; } else if ([identifier hasPrefix:GroupIdentifier]) { - NSString* id = [self idFromIdentifier:identifier withPrefix:GroupIdentifier]; - return [self makeGroupForID:id withIdentifier:identifier]; + id = [self idFromIdentifier:identifier withPrefix:GroupIdentifier]; + item = [self makeGroupForID:id withIdentifier:identifier]; } - return nil; + item_map.insert(make_pair(std::string([id UTF8String]), item)); + + return item; } - (nullable NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { @@ -1688,6 +1724,10 @@ void NativeWindowMac::SetTouchBar(mate::Arguments* args) { } } +void NativeWindowMac::RefreshTouchBarItem(mate::Arguments* args) { + [window_ refreshTouchBarItem:args]; +} + std::vector NativeWindowMac::GetTouchBarItems() { return touch_bar_items_; } diff --git a/default_app/default_app.js b/default_app/default_app.js index 57110e9c90..42644ac36c 100644 --- a/default_app/default_app.js +++ b/default_app/default_app.js @@ -25,6 +25,18 @@ exports.load = (appUrl) => { mainWindow.loadURL(appUrl) mainWindow.focus() + const slider = new (TouchBar.Slider)({ + label: 'Slider 123', + minValue: 50, + maxValue: 1000, + initialValue: 300, + change: (newVal) => { + console.log('Slider was changed', newVal, typeof newVal) + } + }); + + global.slider = slider; + mainWindow.setTouchBar(new TouchBar([ new (TouchBar.Button)({ label: 'Hello World!', @@ -60,15 +72,7 @@ exports.load = (appUrl) => { }) ]) }), - new (TouchBar.Slider)({ - label: 'Slider 123', - minValue: 50, - maxValue: 1000, - initialValue: 300, - change: (newVal) => { - console.log('Slider was changed', newVal, typeof newVal) - } - }) + slider, ])) }) } diff --git a/default_app/icon.png b/default_app/icon.png index ac3a6547d9eccda3c17de4c0d3516b867b86d51b..1e8be4bab538ef93ed6ae35f205f7da69239db9e 100644 GIT binary patch literal 17533 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 literal 122330 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 { item._owner = this; }) +} + +BrowserWindow.prototype._updateTouchBarItem = function (itemID) { + this._refreshTouchBarItem(itemID); } module.exports = BrowserWindow diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index 1b18656a69..a008cc7778 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -44,6 +44,16 @@ class TouchBarItem { } } + updateConfig(newConfig) { + if (!this._owner) { + throw new Error('Cannot call methods on TouchBarItems without assigning to a BrowserWindow'); + } + const dupConfig = Object.assign({}, newConfig); + delete dupConfig.id; + Object.assign(this.config, dupConfig); + this._owner._updateTouchBarItem(this.toJSON()); + } + toJSON () { return this.config } From 28d5c8bbdec6c636b83b23235707496945a29bc6 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Fri, 16 Dec 2016 18:01:49 +1100 Subject: [PATCH 288/925] Add updateConfig ability to other items --- atom/browser/native_window_mac.mm | 31 +++++++++++++++++++++++++------ default_app/default_app.js | 19 ++++++++++--------- 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 09317221c8..eb2ad60f5d 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -413,9 +413,18 @@ bool ScopedDisableResize::disable_resize_ = false; mate::PersistentDictionary dict; if (args->GetNext(&dict) && dict.Get("type", &type) && dict.Get("id", &item_id)) { if (item_map.find(item_id) != item_map.end()) { - if (type == "slider") { - NSSliderTouchBarItem* item = (NSSliderTouchBarItem *)item_map[item_id]; - [self updateSlider:item withOpts:dict]; + if (type == "button") { + [self updateButton:(NSCustomTouchBarItem *)item_map[item_id] withOpts:dict withID:[NSString stringWithUTF8String:item_id.c_str()] andCreate:false]; + } else if (type == "label") { + [self updateLabel:(NSCustomTouchBarItem *)item_map[item_id] withOpts:dict]; + } else if (type == "colorpicker") { + [self updateColorPicker:(NSColorPickerTouchBarItem *)item_map[item_id] withOpts:dict]; + } else if (type == "slider") { + [self updateSlider:(NSSliderTouchBarItem *)item_map[item_id] withOpts:dict]; + } else if (type == "popover") { + [self updatePopOver:(NSPopoverTouchBarItem *)item_map[item_id] withOpts:dict]; + } else if (type == "group") { + args->ThrowError("You can not update the config of a group. Update the individual items or replace the group"); } } } @@ -495,6 +504,10 @@ bool ScopedDisableResize::disable_resize_ = false; - (NSButton*)makeButtonForDict:(mate::PersistentDictionary)dict withLabel:(std::string)label { NSButton *theButton = [NSButton buttonWithTitle:[NSString stringWithUTF8String:label.c_str()] target:self action:@selector(buttonAction:)]; + return [self updateNSButton:theButton forDict:dict withLabel:label]; +} + +- (NSButton*)updateNSButton:(NSButton*)theButton forDict:(mate::PersistentDictionary)dict withLabel:(std::string)label { std::string backgroundColor; if (dict.Get("backgroundColor", &backgroundColor)) { theButton.bezelColor = [self colorFromHexColorString:[NSString stringWithUTF8String:backgroundColor.c_str()]]; @@ -522,13 +535,19 @@ bool ScopedDisableResize::disable_resize_ = false; if (![self hasTBDict:s_id]) return nil; mate::PersistentDictionary item = item_id_map[s_id]; NSCustomTouchBarItem *customItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier]; - return [self updateButton:customItem withOpts:item withID:id]; + return [self updateButton:customItem withOpts:item withID:id andCreate:true]; } -- (nullable NSTouchBarItem *)updateButton:(NSCustomTouchBarItem*)customItem withOpts:(mate::PersistentDictionary)item withID:(NSString*)id { +- (nullable NSTouchBarItem *)updateButton:(NSCustomTouchBarItem*)customItem withOpts:(mate::PersistentDictionary)item withID:(NSString*)id andCreate:(bool)create { std::string label; if (item.Get("label", &label)) { - NSButton* theButton = [self makeButtonForDict:item withLabel:label]; + NSButton* theButton = nil; + if (!create) { + theButton = (NSButton*)customItem.view; + [self updateNSButton:theButton forDict:item withLabel:label]; + } else { + theButton = [self makeButtonForDict:item withLabel:label]; + } theButton.tag = [id floatValue]; customItem.view = theButton; diff --git a/default_app/default_app.js b/default_app/default_app.js index 42644ac36c..549c6d7311 100644 --- a/default_app/default_app.js +++ b/default_app/default_app.js @@ -36,17 +36,18 @@ exports.load = (appUrl) => { }); global.slider = slider; + global.button = new (TouchBar.Button)({ + label: 'Hello World!', + // image: '/path/to/image', + backgroundColor: 'FF0000', + labelColor: '0000FF', + click: () => { + console.log('Hello World Clicked') + } + }); mainWindow.setTouchBar(new TouchBar([ - new (TouchBar.Button)({ - label: 'Hello World!', - // image: '/path/to/image', - backgroundColor: 'FF0000', - labelColor: '0000FF', - click: () => { - console.log('Hello World Clicked') - } - }), + button, new (TouchBar.Label)({ label: 'This is a Label' }), From ba3fbc9d1b77b13bfb3de2d8d82cba34cdc6395d Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Fri, 16 Dec 2016 19:27:54 +1100 Subject: [PATCH 289/925] Fix Group items --- atom/browser/native_window_mac.mm | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index eb2ad60f5d..fd44588098 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -693,9 +693,11 @@ bool ScopedDisableResize::disable_resize_ = false; NSMutableArray* generatedItems = [[NSMutableArray alloc] init]; NSMutableArray* identList = [self identifierArrayFromDicts:items]; for (NSUInteger i = 0; i < [identList count]; i++) { - NSTouchBarItem* generatedItem = [self makeItemForIdentifier:[identList objectAtIndex:i]]; - if (generatedItem) { - [generatedItems addObject:generatedItem]; + if ([identList objectAtIndex:i] != NSTouchBarItemIdentifierOtherItemsProxy) { + NSTouchBarItem* generatedItem = [self makeItemForIdentifier:[identList objectAtIndex:i]]; + if (generatedItem) { + [generatedItems addObject:generatedItem]; + } } } NSGroupTouchBarItem *groupItem = [NSGroupTouchBarItem groupItemWithIdentifier:identifier items:generatedItems]; From 2a00bb30c5d0fe576e4fcf7b68058a75a6177829 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 27 Feb 2017 09:20:25 -0800 Subject: [PATCH 290/925] Use new NativeWindowObserver helper --- atom/browser/native_window.cc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index ab7e108492..b008739709 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -576,11 +576,11 @@ void NativeWindow::NotifyWindowExecuteWindowsCommand( } void NativeWindow::NotifyTouchBarItemInteraction( - const std::string& type, - const std::vector& args) { - FOR_EACH_OBSERVER(NativeWindowObserver, observers_, - OnTouchBarItemResult(type, args)); - } + const std::string& type, + const std::vector& args) { + for (NativeWindowObserver& observer : observers_) + observer.OnTouchBarItemResult(type, args); +} #if defined(OS_WIN) void NativeWindow::NotifyWindowMessage( From 52905ae9b3c95c62a48db04982859fa2e319e5cd Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 27 Feb 2017 10:30:41 -0800 Subject: [PATCH 291/925] Get compiling against 10.10 SDK --- atom/browser/native_window_mac.mm | 35 ++-- .../ui/cocoa/touch_bar_forward_declarations.h | 169 ++++++++++++++++++ filenames.gypi | 1 + 3 files changed, 188 insertions(+), 17 deletions(-) create mode 100644 atom/browser/ui/cocoa/touch_bar_forward_declarations.h diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index fd44588098..a9eddb0ca0 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -7,6 +7,7 @@ #include #include +#include "atom/browser/ui/cocoa/touch_bar_forward_declarations.h" #include "atom/browser/window_list.h" #include "atom/common/color_util.h" #include "atom/common/draggable_region.h" @@ -355,14 +356,14 @@ bool ScopedDisableResize::disable_resize_ = false; - (void)reloadTouchBar; - (void)refreshTouchBarItem:(mate::Arguments*)args; - (void)resetTouchBar; -- (NSTouchBar*)touchBarFromMutatableArray:(NSMutableArray*)items; +- (NSTouchBar*)touchBarFromMutatableArray:(NSMutableArray*)items; @end @interface AtomNSWindow () @end @implementation AtomNSWindow - NSMutableArray* bar_items_ = [[NSMutableArray alloc] init]; + NSMutableArray* bar_items_ = [[NSMutableArray alloc] init]; std::map item_id_map; std::map item_map; @@ -376,11 +377,11 @@ bool ScopedDisableResize::disable_resize_ = false; - (void)resetTouchBar { bar_items_ = [[NSMutableArray alloc] init]; - self.touchBar = nil; + // self.touchBar = nil; } -- (NSMutableArray*)identifierArrayFromDicts:(std::vector)dicts { - NSMutableArray* idents = [[NSMutableArray alloc] init]; +- (NSMutableArray*)identifierArrayFromDicts:(std::vector)dicts { + NSMutableArray* idents = [[NSMutableArray alloc] init]; for (mate::PersistentDictionary &item : dicts) { std::string type; @@ -434,15 +435,15 @@ bool ScopedDisableResize::disable_resize_ = false; std::map new_map; item_id_map = new_map; bar_items_ = [self identifierArrayFromDicts:shell_->GetTouchBarItems()]; - self.touchBar = nil; + // self.touchBar = nil; } -- (NSTouchBar *)makeTouchBar { +- (NSTouchBar*)makeTouchBar { return [self touchBarFromMutatableArray:bar_items_]; } -- (NSTouchBar *)touchBarFromMutatableArray:(NSMutableArray*)items { - NSTouchBar* bar = [[NSTouchBar alloc] init]; +- (NSTouchBar*)touchBarFromMutatableArray:(NSMutableArray*)items { + NSTouchBar* bar = [[NSClassFromString(@"NSTouchBar") alloc] init]; bar.delegate = self; bar.defaultItemIdentifiers = [items copy]; @@ -534,7 +535,7 @@ bool ScopedDisableResize::disable_resize_ = false; std::string s_id = std::string([id UTF8String]); if (![self hasTBDict:s_id]) return nil; mate::PersistentDictionary item = item_id_map[s_id]; - NSCustomTouchBarItem *customItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier]; + NSCustomTouchBarItem *customItem = [[NSClassFromString(@"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]; return [self updateButton:customItem withOpts:item withID:id andCreate:true]; } @@ -566,7 +567,7 @@ bool ScopedDisableResize::disable_resize_ = false; std::string s_id = std::string([id UTF8String]); if (![self hasTBDict:s_id]) return nil; mate::PersistentDictionary item = item_id_map[s_id]; - NSCustomTouchBarItem *customItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier]; + NSCustomTouchBarItem *customItem = [[NSClassFromString(@"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]; return [self updateLabel:customItem withOpts:item]; } @@ -591,7 +592,7 @@ bool ScopedDisableResize::disable_resize_ = false; std::string s_id = std::string([id UTF8String]); if (![self hasTBDict:s_id]) return nil; mate::PersistentDictionary item = item_id_map[s_id]; - NSColorPickerTouchBarItem *colorPickerItem = [[NSColorPickerTouchBarItem alloc] initWithIdentifier:identifier]; + NSColorPickerTouchBarItem *colorPickerItem = [[NSClassFromString(@"NSColorPickerTouchBarItem") alloc] initWithIdentifier:identifier]; return [self updateColorPicker:colorPickerItem withOpts:item]; } @@ -611,7 +612,7 @@ bool ScopedDisableResize::disable_resize_ = false; std::string s_id = std::string([id UTF8String]); if (![self hasTBDict:s_id]) return nil; mate::PersistentDictionary item = item_id_map[s_id]; - NSSliderTouchBarItem *sliderItem = [[NSSliderTouchBarItem alloc] initWithIdentifier:identifier]; + NSSliderTouchBarItem *sliderItem = [[NSClassFromString(@"NSSliderTouchBarItem") alloc] initWithIdentifier:identifier]; return [self updateSlider:sliderItem withOpts:item]; } @@ -647,7 +648,7 @@ bool ScopedDisableResize::disable_resize_ = false; std::string s_id = std::string([id UTF8String]); if (![self hasTBDict:s_id]) return nil; mate::PersistentDictionary item = item_id_map[s_id]; - NSPopoverTouchBarItem *popOverItem = [[NSPopoverTouchBarItem alloc] initWithIdentifier:identifier]; + NSPopoverTouchBarItem *popOverItem = [[NSClassFromString(@"NSPopoverTouchBarItem") alloc] initWithIdentifier:identifier]; return [self updatePopOver:popOverItem withOpts:item]; } @@ -690,8 +691,8 @@ bool ScopedDisableResize::disable_resize_ = false; return nil; } - NSMutableArray* generatedItems = [[NSMutableArray alloc] init]; - NSMutableArray* identList = [self identifierArrayFromDicts:items]; + NSMutableArray* generatedItems = [[NSMutableArray alloc] init]; + NSMutableArray* identList = [self identifierArrayFromDicts:items]; for (NSUInteger i = 0; i < [identList count]; i++) { if ([identList objectAtIndex:i] != NSTouchBarItemIdentifierOtherItemsProxy) { NSTouchBarItem* generatedItem = [self makeItemForIdentifier:[identList objectAtIndex:i]]; @@ -700,7 +701,7 @@ bool ScopedDisableResize::disable_resize_ = false; } } } - NSGroupTouchBarItem *groupItem = [NSGroupTouchBarItem groupItemWithIdentifier:identifier items:generatedItems]; + NSGroupTouchBarItem *groupItem = [NSClassFromString(@"NSGroupTouchBarItem") groupItemWithIdentifier:identifier items:generatedItems]; std::string customizationLabel; if (item.Get("customizationLabel", &customizationLabel)) { diff --git a/atom/browser/ui/cocoa/touch_bar_forward_declarations.h b/atom/browser/ui/cocoa/touch_bar_forward_declarations.h new file mode 100644 index 0000000000..c6fdd1f791 --- /dev/null +++ b/atom/browser/ui/cocoa/touch_bar_forward_declarations.h @@ -0,0 +1,169 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_UI_COCOA_TOUCH_BAR_FORWARD_DECLARATIONS_H_ +#define ATOM_BROWSER_UI_COCOA_TOUCH_BAR_FORWARD_DECLARATIONS_H_ + +// Once Chrome no longer supports OSX 10.12.0, this file can be deleted. + +#import + +#if !defined(MAC_OS_X_VERSION_10_12_1) + +#pragma clang assume_nonnull begin + +@class NSTouchBar, NSTouchBarItem; +@protocol NSTouchBarDelegate; + +typedef float NSTouchBarItemPriority; +static const NSTouchBarItemPriority NSTouchBarItemPriorityHigh = 1000; +static const NSTouchBarItemPriority NSTouchBarItemPriorityNormal = 0; +static const NSTouchBarItemPriority NSTouchBarItemPriorityLow = -1000; + +typedef NSString* NSTouchBarItemIdentifier; +typedef NSString* NSTouchBarCustomizationIdentifier; + +static const NSTouchBarItemIdentifier NSTouchBarItemIdentifierFixedSpaceSmall = + @"NSTouchBarItemIdentifierFixedSpaceSmall"; + +static const NSTouchBarItemIdentifier NSTouchBarItemIdentifierFlexibleSpace = + @"NSTouchBarItemIdentifierFlexibleSpace"; + +static const NSTouchBarItemIdentifier NSTouchBarItemIdentifierOtherItemsProxy = + @"NSTouchBarItemIdentifierOtherItemsProxy"; + +@interface NSTouchBar : NSObject + +- (instancetype)init NS_DESIGNATED_INITIALIZER; +- (nullable instancetype)initWithCoder:(NSCoder*)aDecoder + NS_DESIGNATED_INITIALIZER; + +@property(copy, nullable) + NSTouchBarCustomizationIdentifier customizationIdentifier; +@property(copy) NSArray* customizationAllowedItemIdentifiers; +@property(copy) NSArray* customizationRequiredItemIdentifiers; +@property(copy) NSArray* defaultItemIdentifiers; +@property(copy, readonly) NSArray* itemIdentifiers; +@property(copy, nullable) NSTouchBarItemIdentifier principalItemIdentifier; +@property(copy) NSSet* templateItems; +@property(nullable, weak) id delegate; + +- (nullable __kindof NSTouchBarItem*)itemForIdentifier: + (NSTouchBarItemIdentifier)identifier; + +@property(readonly, getter=isVisible) BOOL visible; + +@end + +@interface NSTouchBarItem : NSObject + +- (instancetype)initWithIdentifier:(NSTouchBarItemIdentifier)identifier + NS_DESIGNATED_INITIALIZER; +- (nullable instancetype)initWithCoder:(NSCoder*)coder + NS_DESIGNATED_INITIALIZER; +- (instancetype)init NS_UNAVAILABLE; + +@property(readonly, copy) NSTouchBarItemIdentifier identifier; +@property NSTouchBarItemPriority visibilityPriority; +@property(readonly, nullable) NSView* view; +@property(readonly, nullable) NSViewController* viewController; +@property(readwrite, copy) NSString* customizationLabel; +@property(readonly, getter=isVisible) BOOL visible; + +@end + +@interface NSGroupTouchBarItem : NSTouchBarItem + ++ (NSGroupTouchBarItem*)groupItemWithIdentifier: + (NSTouchBarItemIdentifier)identifier + items:(NSArray*)items; + +@property(strong) NSTouchBar* groupTouchBar; +@property(readwrite, copy, null_resettable) NSString* customizationLabel; + +@end + +@interface NSCustomTouchBarItem : NSTouchBarItem + +@property(readwrite, strong) __kindof NSView* view; +@property(readwrite, strong, nullable) + __kindof NSViewController* viewController; +@property(readwrite, copy, null_resettable) NSString* customizationLabel; + +@end + +@interface NSColorPickerTouchBarItem : NSTouchBarItem + +@property SEL action; +@property(weak) id target; +@property(copy) NSColor *color; + +@end + +@interface NSPopoverTouchBarItem : NSTouchBarItem + +@property BOOL showsCloseButton; +@property(strong) NSImage *collapsedRepresentationImage; +@property(strong) NSString *collapsedRepresentationLabel; +@property(strong) NSTouchBar *popoverTouchBar; + +@end + +@interface NSSliderTouchBarItem : NSTouchBarItem + +@property SEL action; +@property(weak) id target; +@property(copy) NSString *label; +@property(strong) NSSlider *slider; + +@end + +@interface NSWindow (TouchBarSDK) + +@property(strong, readonly) NSTouchBar* touchBar; + +@end + +@interface NSButton (TouchBarSDK) + +@property(copy) NSColor *bezelColor; ++ (instancetype)buttonWithTitle:(NSString *)title + target:(id)target + action:(SEL)action; + +@end + +@interface NSTextField (TouchBarSDK) + ++ (instancetype)labelWithString:(NSString *)stringValue; + +@end + +@protocol NSTouchBarDelegate + +@optional +- (nullable NSTouchBarItem*)touchBar:(NSTouchBar*)touchBar + makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier; +@end + +#pragma clang assume_nonnull end + +#elif MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_12_1 + +// When compiling against the 10.12.1 SDK or later, just provide forward +// declarations to suppress the partial availability warnings. + +@class NSCustomTouchBarItem; +@class NSGroupTouchBarItem; +@class NSTouchBar; +@protocol NSTouchBarDelegate; +@class NSTouchBarItem; + +@interface NSWindow (TouchBarSDK) +@property(strong, readonly) NSTouchBar* touchBar; +@end + +#endif // MAC_OS_X_VERSION_10_12_1 + +#endif // ATOM_BROWSER_UI_COCOA_TOUCH_BAR_FORWARD_DECLARATIONS_H_ diff --git a/filenames.gypi b/filenames.gypi index 0e4ad20626..a1758e2acf 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -281,6 +281,7 @@ 'atom/browser/ui/atom_menu_model.h', 'atom/browser/ui/cocoa/atom_menu_controller.h', 'atom/browser/ui/cocoa/atom_menu_controller.mm', + 'atom/browser/ui/cocoa/touch_bar_forward_declarations.h', 'atom/browser/ui/drag_util_mac.mm', 'atom/browser/ui/drag_util_views.cc', 'atom/browser/ui/drag_util.h', From 9272582bd65853d4b27cde882b39f654c45b0136 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 27 Feb 2017 10:38:57 -0800 Subject: [PATCH 292/925] Revert default_app changes --- default_app/default_app.js | 53 +------------------------------------ default_app/icon.png | Bin 17533 -> 122330 bytes 2 files changed, 1 insertion(+), 52 deletions(-) diff --git a/default_app/default_app.js b/default_app/default_app.js index 549c6d7311..bfb97a9ab0 100644 --- a/default_app/default_app.js +++ b/default_app/default_app.js @@ -1,4 +1,4 @@ -const {app, BrowserWindow, TouchBar} = require('electron') +const {app, BrowserWindow} = require('electron') const path = require('path') let mainWindow = null @@ -24,56 +24,5 @@ exports.load = (appUrl) => { mainWindow = new BrowserWindow(options) mainWindow.loadURL(appUrl) mainWindow.focus() - - const slider = new (TouchBar.Slider)({ - label: 'Slider 123', - minValue: 50, - maxValue: 1000, - initialValue: 300, - change: (newVal) => { - console.log('Slider was changed', newVal, typeof newVal) - } - }); - - global.slider = slider; - global.button = new (TouchBar.Button)({ - label: 'Hello World!', - // image: '/path/to/image', - backgroundColor: 'FF0000', - labelColor: '0000FF', - click: () => { - console.log('Hello World Clicked') - } - }); - - mainWindow.setTouchBar(new TouchBar([ - button, - new (TouchBar.Label)({ - label: 'This is a Label' - }), - new (TouchBar.ColorPicker)({ - change: (newColor) => { - console.log('Color was changed', newColor) - } - }), - new (TouchBar.PopOver)({ - // image: '/path/to/image', - label: 'foo', - showCloseButton: true, - touchBar: new TouchBar([ - new (TouchBar.Group)({ - items: new TouchBar( - [1, 2, 3].map((i) => new (TouchBar.Button)({ - label: `Button ${i}`, - click: () => { - console.log(`Button ${i} (group) Clicked`) - } - })) - ) - }) - ]) - }), - slider, - ])) }) } diff --git a/default_app/icon.png b/default_app/icon.png index 1e8be4bab538ef93ed6ae35f205f7da69239db9e..ac3a6547d9eccda3c17de4c0d3516b867b86d51b 100644 GIT binary patch literal 122330 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 From 9bdca3bbfb16a99edc92e99e5ed4e453a4c07fa4 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 27 Feb 2017 10:40:00 -0800 Subject: [PATCH 293/925] Remove linter warnings --- lib/browser/api/browser-window.js | 6 ++++-- lib/browser/api/touch-bar.js | 12 ++++++------ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/browser/api/browser-window.js b/lib/browser/api/browser-window.js index 9f5746980f..31eac937a6 100644 --- a/lib/browser/api/browser-window.js +++ b/lib/browser/api/browser-window.js @@ -213,11 +213,13 @@ BrowserWindow.prototype.setTouchBar = function (touchBar) { this._setTouchBar(touchBar.toJSON()) } touchBar._owner = this - touchBar.items.forEach((item) => { item._owner = this; }) + touchBar.items.forEach((item) => { + item._owner = this + }) } BrowserWindow.prototype._updateTouchBarItem = function (itemID) { - this._refreshTouchBarItem(itemID); + this._refreshTouchBarItem(itemID) } module.exports = BrowserWindow diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index a008cc7778..07b2631a77 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -44,14 +44,14 @@ class TouchBarItem { } } - updateConfig(newConfig) { + updateConfig (newConfig) { if (!this._owner) { - throw new Error('Cannot call methods on TouchBarItems without assigning to a BrowserWindow'); + throw new Error('Cannot call methods on TouchBarItems without assigning to a BrowserWindow') } - const dupConfig = Object.assign({}, newConfig); - delete dupConfig.id; - Object.assign(this.config, dupConfig); - this._owner._updateTouchBarItem(this.toJSON()); + const dupConfig = Object.assign({}, newConfig) + delete dupConfig.id + Object.assign(this.config, dupConfig) + this._owner._updateTouchBarItem(this.toJSON()) } toJSON () { From d4d3c78701f00ceec05ee14ab1145ffd008e53ad Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 27 Feb 2017 10:43:44 -0800 Subject: [PATCH 294/925] Ignore lint warnings in Objective-C header --- script/cpplint.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/script/cpplint.py b/script/cpplint.py index 7ec3537e8d..9d9281fb70 100755 --- a/script/cpplint.py +++ b/script/cpplint.py @@ -9,6 +9,8 @@ from lib.util import execute IGNORE_FILES = [ os.path.join('atom', 'browser', 'mac', 'atom_application.h'), os.path.join('atom', 'browser', 'mac', 'atom_application_delegate.h'), + os.path.join('atom', 'browser', 'ui', 'cocoa', + 'touch_bar_forward_declarations.h'), os.path.join('atom', 'browser', 'resources', 'win', 'resource.h'), os.path.join('atom', 'browser', 'ui', 'cocoa', 'atom_menu_controller.h'), os.path.join('atom', 'common', 'api', 'api_messages.h'), From 69638a399cfd5b6fbb429d1774a87f3505f6a18e Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 27 Feb 2017 10:49:45 -0800 Subject: [PATCH 295/925] Make touchBar readwrite/nullable --- atom/browser/native_window_mac.mm | 4 ++-- atom/browser/ui/cocoa/touch_bar_forward_declarations.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index a9eddb0ca0..6c6847b269 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -377,7 +377,7 @@ bool ScopedDisableResize::disable_resize_ = false; - (void)resetTouchBar { bar_items_ = [[NSMutableArray alloc] init]; - // self.touchBar = nil; + self.touchBar = nil; } - (NSMutableArray*)identifierArrayFromDicts:(std::vector)dicts { @@ -435,7 +435,7 @@ bool ScopedDisableResize::disable_resize_ = false; std::map new_map; item_id_map = new_map; bar_items_ = [self identifierArrayFromDicts:shell_->GetTouchBarItems()]; - // self.touchBar = nil; + self.touchBar = nil; } - (NSTouchBar*)makeTouchBar { diff --git a/atom/browser/ui/cocoa/touch_bar_forward_declarations.h b/atom/browser/ui/cocoa/touch_bar_forward_declarations.h index c6fdd1f791..e9a6e5525b 100644 --- a/atom/browser/ui/cocoa/touch_bar_forward_declarations.h +++ b/atom/browser/ui/cocoa/touch_bar_forward_declarations.h @@ -121,7 +121,7 @@ static const NSTouchBarItemIdentifier NSTouchBarItemIdentifierOtherItemsProxy = @interface NSWindow (TouchBarSDK) -@property(strong, readonly) NSTouchBar* touchBar; +@property(strong, readwrite, nullable) NSTouchBar* touchBar; @end From eff49ad19ca4e0e616f8fb35a940fae4141bc45d Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 27 Feb 2017 11:23:33 -0800 Subject: [PATCH 296/925] Return early when touchBar is null --- lib/browser/api/browser-window.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/browser/api/browser-window.js b/lib/browser/api/browser-window.js index 31eac937a6..ecfd9195f4 100644 --- a/lib/browser/api/browser-window.js +++ b/lib/browser/api/browser-window.js @@ -205,13 +205,16 @@ Object.assign(BrowserWindow.prototype, { // TouchBar API BrowserWindow.prototype.setTouchBar = function (touchBar) { - if (touchBar === null || typeof touchBar === 'undefined') { + if (touchBar == null) { this._destroyTouchBar() - } else if (Array.isArray(touchBar)) { - this._setTouchBar((new TouchBar(touchBar)).toJSON()) - } else { - this._setTouchBar(touchBar.toJSON()) + return } + + if (Array.isArray(touchBar)) { + touchBar = new TouchBar(touchBar) + } + + this._setTouchBar(touchBar.toJSON()) touchBar._owner = this touchBar.items.forEach((item) => { item._owner = this From b16d649819359f3abd07f462eeeda7a023be3e60 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 27 Feb 2017 12:05:44 -0800 Subject: [PATCH 297/925] Use skia color helpers --- atom/browser/native_window_mac.mm | 27 ++++----------------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 6c6847b269..f8001100c2 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -459,10 +459,8 @@ bool ScopedDisableResize::disable_resize_ = false; - (void)colorPickerAction:(id)sender { NSString* item_id = ((NSColorPickerTouchBarItem *)sender).identifier; NSColor* color = ((NSColorPickerTouchBarItem *)sender).color; - NSString* colorHexString = [NSString stringWithFormat:@"#%02X%02X%02X", - (int) (color.redComponent * 0xFF), (int) (color.greenComponent * 0xFF), - (int) (color.blueComponent * 0xFF)]; - shell_->NotifyTouchBarItemInteraction("color_picker", { std::string([item_id UTF8String]), std::string([colorHexString UTF8String]) }); + std::string hex_color = atom::ToRGBHex(skia::NSDeviceColorToSkColor(color)); + shell_->NotifyTouchBarItemInteraction("color_picker", { std::string([item_id UTF8String]), hex_color }); } - (void)sliderAction:(id)sender { @@ -481,25 +479,8 @@ bool ScopedDisableResize::disable_resize_ = false; } - (NSColor*)colorFromHexColorString:(NSString*)inColorString { - NSColor* result = nil; - unsigned colorCode = 0; - unsigned char redByte, greenByte, blueByte; - - if (nil != inColorString) - { - NSScanner* scanner = [NSScanner scannerWithString:inColorString]; - (void) [scanner scanHexInt:&colorCode]; // ignore error - } - redByte = (unsigned char)(colorCode >> 16); - greenByte = (unsigned char)(colorCode >> 8); - blueByte = (unsigned char)(colorCode); // masks off high bits - - result = [NSColor - colorWithCalibratedRed:(CGFloat)redByte / 0xff - green:(CGFloat)greenByte / 0xff - blue:(CGFloat)blueByte / 0xff - alpha:1.0]; - return result; + SkColor color = atom::ParseHexColor([inColorString UTF8String]); + return skia::SkColorToCalibratedNSColor(color); } - (NSButton*)makeButtonForDict:(mate::PersistentDictionary)dict withLabel:(std::string)label { From 1b5149ae7a9ed5965cbfe78f1b2fbfa74f86059e Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 27 Feb 2017 16:07:19 -0800 Subject: [PATCH 298/925] Few memory and scope tweaks --- atom/browser/native_window_mac.mm | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index f8001100c2..4f720ef2a9 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -363,7 +363,6 @@ bool ScopedDisableResize::disable_resize_ = false; @end @implementation AtomNSWindow - NSMutableArray* bar_items_ = [[NSMutableArray alloc] init]; std::map item_id_map; std::map item_map; @@ -376,7 +375,6 @@ bool ScopedDisableResize::disable_resize_ = false; } - (void)resetTouchBar { - bar_items_ = [[NSMutableArray alloc] init]; self.touchBar = nil; } @@ -432,27 +430,25 @@ bool ScopedDisableResize::disable_resize_ = false; } - (void)reloadTouchBar { - std::map new_map; - item_id_map = new_map; - bar_items_ = [self identifierArrayFromDicts:shell_->GetTouchBarItems()]; + item_id_map.clear(); self.touchBar = nil; } - (NSTouchBar*)makeTouchBar { - return [self touchBarFromMutatableArray:bar_items_]; + NSMutableArray* item_identifiers = + [self identifierArrayFromDicts:shell_->GetTouchBarItems()]; + return [self touchBarFromMutatableArray:item_identifiers]; } - (NSTouchBar*)touchBarFromMutatableArray:(NSMutableArray*)items { NSTouchBar* bar = [[NSClassFromString(@"NSTouchBar") alloc] init]; bar.delegate = self; - - bar.defaultItemIdentifiers = [items copy]; - + bar.defaultItemIdentifiers = items; return bar; } - (void)buttonAction:(id)sender { - NSString* item_id = [NSString stringWithFormat:@"com.electron.tb.button.%d", (int)((NSButton *)sender).tag]; + NSString* item_id = [NSString stringWithFormat:@"%@.%d", ButtonIdentifier, (int)((NSButton *)sender).tag]; shell_->NotifyTouchBarItemInteraction("button", { std::string([item_id UTF8String]) }); } @@ -469,9 +465,7 @@ bool ScopedDisableResize::disable_resize_ = false; } - (NSString*)idFromIdentifier:(NSString*)identifier withPrefix:(NSString*)prefix { - NSString *idCopy = [identifier copy]; - idCopy = [identifier substringFromIndex:[prefix length]]; - return idCopy; + return [identifier substringFromIndex:[prefix length]]; } - (bool)hasTBDict:(std::string)id { @@ -497,7 +491,7 @@ bool ScopedDisableResize::disable_resize_ = false; std::string labelColor; if (dict.Get("labelColor", &labelColor)) { - NSMutableAttributedString *attrTitle = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithUTF8String:label.c_str()]]; + NSMutableAttributedString *attrTitle = [[[NSMutableAttributedString alloc] initWithString:[NSString stringWithUTF8String:label.c_str()]] autorelease]; NSUInteger len = [attrTitle length]; NSRange range = NSMakeRange(0, len); [attrTitle addAttribute:NSForegroundColorAttributeName value:[self colorFromHexColorString:[NSString stringWithUTF8String:labelColor.c_str()]] range:range]; From b632cdd37d3e119b7be14379dc8803b3547eb666 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 28 Feb 2017 12:29:23 -0800 Subject: [PATCH 299/925] Extract helper class to encapsulate touch bar items --- atom/browser/native_window_mac.mm | 356 +---------------------- atom/browser/ui/cocoa/atom_touch_bar.h | 63 +++++ atom/browser/ui/cocoa/atom_touch_bar.mm | 362 ++++++++++++++++++++++++ filenames.gypi | 2 + 4 files changed, 439 insertions(+), 344 deletions(-) create mode 100644 atom/browser/ui/cocoa/atom_touch_bar.h create mode 100644 atom/browser/ui/cocoa/atom_touch_bar.mm diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 4f720ef2a9..9ef09f625d 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -7,7 +7,7 @@ #include #include -#include "atom/browser/ui/cocoa/touch_bar_forward_declarations.h" +#include "atom/browser/ui/cocoa/atom_touch_bar.h" #include "atom/browser/window_list.h" #include "atom/common/color_util.h" #include "atom/common/draggable_region.h" @@ -356,18 +356,17 @@ bool ScopedDisableResize::disable_resize_ = false; - (void)reloadTouchBar; - (void)refreshTouchBarItem:(mate::Arguments*)args; - (void)resetTouchBar; -- (NSTouchBar*)touchBarFromMutatableArray:(NSMutableArray*)items; @end @interface AtomNSWindow () @end @implementation AtomNSWindow - std::map item_id_map; - std::map item_map; + base::scoped_nsobject touch_bar_; - (void)setShell:(atom::NativeWindowMac*)shell { shell_ = shell; + touch_bar_.reset([[AtomTouchBar alloc] initWithDelegate:self window:shell]); } - (void)setEnableLargerThanScreen:(bool)enable { @@ -378,357 +377,26 @@ bool ScopedDisableResize::disable_resize_ = false; self.touchBar = nil; } -- (NSMutableArray*)identifierArrayFromDicts:(std::vector)dicts { - NSMutableArray* idents = [[NSMutableArray alloc] init]; - - for (mate::PersistentDictionary &item : dicts) { - std::string type; - std::string item_id; - if (item.Get("type", &type) && item.Get("id", &item_id)) { - item_id_map.insert(make_pair(item_id, item)); - if (type == "button") { - [idents addObject:[NSString stringWithFormat:@"%@%@", ButtonIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; - } else if (type == "label") { - [idents addObject:[NSString stringWithFormat:@"%@%@", LabelIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; - } else if (type == "colorpicker") { - [idents addObject:[NSString stringWithFormat:@"%@%@", ColorPickerIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; - } else if (type == "slider") { - [idents addObject:[NSString stringWithFormat:@"%@%@", SliderIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; - } else if (type == "popover") { - [idents addObject:[NSString stringWithFormat:@"%@%@", PopOverIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; - } else if (type == "group") { - [idents addObject:[NSString stringWithFormat:@"%@%@", GroupIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; - } - } - } - [idents addObject:NSTouchBarItemIdentifierOtherItemsProxy]; - - return idents; -} - -- (void)refreshTouchBarItem:(mate::Arguments*)args { - std::string item_id; - std::string type; - mate::PersistentDictionary dict; - if (args->GetNext(&dict) && dict.Get("type", &type) && dict.Get("id", &item_id)) { - if (item_map.find(item_id) != item_map.end()) { - if (type == "button") { - [self updateButton:(NSCustomTouchBarItem *)item_map[item_id] withOpts:dict withID:[NSString stringWithUTF8String:item_id.c_str()] andCreate:false]; - } else if (type == "label") { - [self updateLabel:(NSCustomTouchBarItem *)item_map[item_id] withOpts:dict]; - } else if (type == "colorpicker") { - [self updateColorPicker:(NSColorPickerTouchBarItem *)item_map[item_id] withOpts:dict]; - } else if (type == "slider") { - [self updateSlider:(NSSliderTouchBarItem *)item_map[item_id] withOpts:dict]; - } else if (type == "popover") { - [self updatePopOver:(NSPopoverTouchBarItem *)item_map[item_id] withOpts:dict]; - } else if (type == "group") { - args->ThrowError("You can not update the config of a group. Update the individual items or replace the group"); - } - } - } -} - - (void)reloadTouchBar { - item_id_map.clear(); + [touch_bar_ clear]; self.touchBar = nil; } +- (void)refreshTouchBarItem:(mate::Arguments*)args { + [touch_bar_ refreshTouchBarItem:args]; +} + - (NSTouchBar*)makeTouchBar { - NSMutableArray* item_identifiers = - [self identifierArrayFromDicts:shell_->GetTouchBarItems()]; - return [self touchBarFromMutatableArray:item_identifiers]; + return [touch_bar_ makeTouchBarFromItemOptions:shell_->GetTouchBarItems()]; } -- (NSTouchBar*)touchBarFromMutatableArray:(NSMutableArray*)items { - NSTouchBar* bar = [[NSClassFromString(@"NSTouchBar") alloc] init]; - bar.delegate = self; - bar.defaultItemIdentifiers = items; - return bar; +- (nullable NSTouchBarItem*)touchBar:(NSTouchBar*)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { + return [touch_bar_ makeItemForIdentifier:identifier]; } -- (void)buttonAction:(id)sender { - NSString* item_id = [NSString stringWithFormat:@"%@.%d", ButtonIdentifier, (int)((NSButton *)sender).tag]; - shell_->NotifyTouchBarItemInteraction("button", { std::string([item_id UTF8String]) }); -} - -- (void)colorPickerAction:(id)sender { - NSString* item_id = ((NSColorPickerTouchBarItem *)sender).identifier; - NSColor* color = ((NSColorPickerTouchBarItem *)sender).color; - std::string hex_color = atom::ToRGBHex(skia::NSDeviceColorToSkColor(color)); - shell_->NotifyTouchBarItemInteraction("color_picker", { std::string([item_id UTF8String]), hex_color }); -} - -- (void)sliderAction:(id)sender { - NSString* item_id = ((NSSliderTouchBarItem *)sender).identifier; - shell_->NotifyTouchBarItemInteraction("slider", { std::string([item_id UTF8String]), std::to_string([((NSSliderTouchBarItem *)sender).slider intValue]) }); -} - -- (NSString*)idFromIdentifier:(NSString*)identifier withPrefix:(NSString*)prefix { - return [identifier substringFromIndex:[prefix length]]; -} - -- (bool)hasTBDict:(std::string)id { - return item_id_map.find(id) != item_id_map.end(); -} - -- (NSColor*)colorFromHexColorString:(NSString*)inColorString { - SkColor color = atom::ParseHexColor([inColorString UTF8String]); - return skia::SkColorToCalibratedNSColor(color); -} - -- (NSButton*)makeButtonForDict:(mate::PersistentDictionary)dict withLabel:(std::string)label { - NSButton *theButton = [NSButton buttonWithTitle:[NSString stringWithUTF8String:label.c_str()] target:self action:@selector(buttonAction:)]; - - return [self updateNSButton:theButton forDict:dict withLabel:label]; -} - -- (NSButton*)updateNSButton:(NSButton*)theButton forDict:(mate::PersistentDictionary)dict withLabel:(std::string)label { - std::string backgroundColor; - if (dict.Get("backgroundColor", &backgroundColor)) { - theButton.bezelColor = [self colorFromHexColorString:[NSString stringWithUTF8String:backgroundColor.c_str()]]; - } - - std::string labelColor; - if (dict.Get("labelColor", &labelColor)) { - NSMutableAttributedString *attrTitle = [[[NSMutableAttributedString alloc] initWithString:[NSString stringWithUTF8String:label.c_str()]] autorelease]; - NSUInteger len = [attrTitle length]; - NSRange range = NSMakeRange(0, len); - [attrTitle addAttribute:NSForegroundColorAttributeName value:[self colorFromHexColorString:[NSString stringWithUTF8String:labelColor.c_str()]] range:range]; - [attrTitle fixAttributesInRange:range]; - [theButton setAttributedTitle:attrTitle]; - } - - gfx::Image image; - if (dict.Get("image", &image)) { - theButton.image = image.AsNSImage(); - } - return theButton; -} - -- (nullable NSTouchBarItem *)makeButtonForID:(NSString*)id withIdentifier:(NSString*)identifier { - std::string s_id = std::string([id UTF8String]); - if (![self hasTBDict:s_id]) return nil; - mate::PersistentDictionary item = item_id_map[s_id]; - NSCustomTouchBarItem *customItem = [[NSClassFromString(@"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]; - return [self updateButton:customItem withOpts:item withID:id andCreate:true]; -} - -- (nullable NSTouchBarItem *)updateButton:(NSCustomTouchBarItem*)customItem withOpts:(mate::PersistentDictionary)item withID:(NSString*)id andCreate:(bool)create { - std::string label; - if (item.Get("label", &label)) { - NSButton* theButton = nil; - if (!create) { - theButton = (NSButton*)customItem.view; - [self updateNSButton:theButton forDict:item withLabel:label]; - } else { - theButton = [self makeButtonForDict:item withLabel:label]; - } - theButton.tag = [id floatValue]; - - customItem.view = theButton; - - std::string customizationLabel; - if (item.Get("customizationLabel", &customizationLabel)) { - customItem.customizationLabel = [NSString stringWithUTF8String:customizationLabel.c_str()]; - } - - return customItem; - } - return nil; -} - -- (nullable NSTouchBarItem*) makeLabelForID:(NSString*)id withIdentifier:(NSString*)identifier { - std::string s_id = std::string([id UTF8String]); - if (![self hasTBDict:s_id]) return nil; - mate::PersistentDictionary item = item_id_map[s_id]; - NSCustomTouchBarItem *customItem = [[NSClassFromString(@"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]; - return [self updateLabel:customItem withOpts:item]; -} - -- (nullable NSTouchBarItem*) updateLabel:(NSCustomTouchBarItem*)customItem withOpts:(mate::PersistentDictionary)item { - std::string label; - if (item.Get("label", &label)) { - NSTextField *theLabel = [NSTextField labelWithString:[NSString stringWithUTF8String:label.c_str()]]; - - customItem.view = theLabel; - - std::string customizationLabel; - if (item.Get("customizationLabel", &customizationLabel)) { - customItem.customizationLabel = [NSString stringWithUTF8String:customizationLabel.c_str()]; - } - - return customItem; - } - return nil; -} - -- (nullable NSTouchBarItem*) makeColorPickerForID:(NSString*)id withIdentifier:(NSString*)identifier { - std::string s_id = std::string([id UTF8String]); - if (![self hasTBDict:s_id]) return nil; - mate::PersistentDictionary item = item_id_map[s_id]; - NSColorPickerTouchBarItem *colorPickerItem = [[NSClassFromString(@"NSColorPickerTouchBarItem") alloc] initWithIdentifier:identifier]; - return [self updateColorPicker:colorPickerItem withOpts:item]; -} - -- (nullable NSTouchBarItem*) updateColorPicker:(NSColorPickerTouchBarItem*)colorPickerItem withOpts:(mate::PersistentDictionary)item { - colorPickerItem.target = self; - colorPickerItem.action = @selector(colorPickerAction:); - - std::string customizationLabel; - if (item.Get("customizationLabel", &customizationLabel)) { - colorPickerItem.customizationLabel = [NSString stringWithUTF8String:customizationLabel.c_str()]; - } - - return colorPickerItem; -} - -- (nullable NSTouchBarItem*) makeSliderForID:(NSString*)id withIdentifier:(NSString*)identifier { - std::string s_id = std::string([id UTF8String]); - if (![self hasTBDict:s_id]) return nil; - mate::PersistentDictionary item = item_id_map[s_id]; - NSSliderTouchBarItem *sliderItem = [[NSClassFromString(@"NSSliderTouchBarItem") alloc] initWithIdentifier:identifier]; - return [self updateSlider:sliderItem withOpts:item]; -} - -- (nullable NSTouchBarItem*) updateSlider:(NSSliderTouchBarItem*)sliderItem withOpts:(mate::PersistentDictionary)item { - sliderItem.target = self; - sliderItem.action = @selector(sliderAction:); - - std::string customizationLabel; - if (item.Get("customizationLabel", &customizationLabel)) { - sliderItem.customizationLabel = [NSString stringWithUTF8String:customizationLabel.c_str()]; - } - - std::string label; - if (item.Get("label", &label)) { - sliderItem.label = [NSString stringWithUTF8String:label.c_str()]; - } - - int maxValue = 100; - int minValue = 0; - int initialValue = 50; - item.Get("minValue", &minValue); - item.Get("maxValue", &maxValue); - item.Get("initialValue", &initialValue); - - sliderItem.slider.minValue = minValue; - sliderItem.slider.maxValue = maxValue; - sliderItem.slider.doubleValue = initialValue; - - return sliderItem; -} - -- (nullable NSTouchBarItem*) makePopOverForID:(NSString*)id withIdentifier:(NSString*)identifier { - std::string s_id = std::string([id UTF8String]); - if (![self hasTBDict:s_id]) return nil; - mate::PersistentDictionary item = item_id_map[s_id]; - NSPopoverTouchBarItem *popOverItem = [[NSClassFromString(@"NSPopoverTouchBarItem") alloc] initWithIdentifier:identifier]; - return [self updatePopOver:popOverItem withOpts:item]; -} - -- (nullable NSTouchBarItem*) updatePopOver:(NSPopoverTouchBarItem*)popOverItem withOpts:(mate::PersistentDictionary)item { - std::string customizationLabel; - if (item.Get("customizationLabel", &customizationLabel)) { - popOverItem.customizationLabel = [NSString stringWithUTF8String:customizationLabel.c_str()]; - } - - std::string label; - gfx::Image image; - if (item.Get("label", &label)) { - popOverItem.collapsedRepresentationLabel = [NSString stringWithUTF8String:label.c_str()]; - } else if (item.Get("image", &image)) { - popOverItem.collapsedRepresentationImage = image.AsNSImage(); - } - - bool showCloseButton; - if (item.Get("showCloseButton", &showCloseButton)) { - popOverItem.showsCloseButton = showCloseButton; - } - - std::vector touchBar; - if (item.Get("touchBar", &touchBar)) { - popOverItem.popoverTouchBar = [self touchBarFromMutatableArray:[self identifierArrayFromDicts:touchBar]]; - } else { - return nil; - } - - return popOverItem; -} - -- (nullable NSTouchBarItem*) makeGroupForID:(NSString*)id withIdentifier:(NSString*)identifier { - std::string s_id = std::string([id UTF8String]); - if (![self hasTBDict:s_id]) return nil; - mate::PersistentDictionary item = item_id_map[s_id]; - - std::vector items; - if (!item.Get("items", &items)) { - return nil; - } - - NSMutableArray* generatedItems = [[NSMutableArray alloc] init]; - NSMutableArray* identList = [self identifierArrayFromDicts:items]; - for (NSUInteger i = 0; i < [identList count]; i++) { - if ([identList objectAtIndex:i] != NSTouchBarItemIdentifierOtherItemsProxy) { - NSTouchBarItem* generatedItem = [self makeItemForIdentifier:[identList objectAtIndex:i]]; - if (generatedItem) { - [generatedItems addObject:generatedItem]; - } - } - } - NSGroupTouchBarItem *groupItem = [NSClassFromString(@"NSGroupTouchBarItem") groupItemWithIdentifier:identifier items:generatedItems]; - - std::string customizationLabel; - if (item.Get("customizationLabel", &customizationLabel)) { - groupItem.customizationLabel = [NSString stringWithUTF8String:customizationLabel.c_str()]; - } - - return groupItem; -} - -static NSTouchBarItemIdentifier ButtonIdentifier = @"com.electron.tb.button."; -static NSTouchBarItemIdentifier ColorPickerIdentifier = @"com.electron.tb.colorpicker."; -static NSTouchBarItemIdentifier GroupIdentifier = @"com.electron.tb.group."; -static NSTouchBarItemIdentifier LabelIdentifier = @"com.electron.tb.label."; -static NSTouchBarItemIdentifier PopOverIdentifier = @"com.electron.tb.popover."; -static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.tb.slider."; - -- (nullable NSTouchBarItem *)makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { - NSTouchBarItem * item = nil; - NSString * id = nil; - if ([identifier hasPrefix:ButtonIdentifier]) { - id = [self idFromIdentifier:identifier withPrefix:ButtonIdentifier]; - item = [self makeButtonForID:id withIdentifier:identifier]; - } else if ([identifier hasPrefix:LabelIdentifier]) { - id = [self idFromIdentifier:identifier withPrefix:LabelIdentifier]; - item = [self makeLabelForID:id withIdentifier:identifier]; - } else if ([identifier hasPrefix:ColorPickerIdentifier]) { - id = [self idFromIdentifier:identifier withPrefix:ColorPickerIdentifier]; - item = [self makeColorPickerForID:id withIdentifier:identifier]; - } else if ([identifier hasPrefix:SliderIdentifier]) { - id = [self idFromIdentifier:identifier withPrefix:SliderIdentifier]; - item = [self makeSliderForID:id withIdentifier:identifier]; - } else if ([identifier hasPrefix:PopOverIdentifier]) { - id = [self idFromIdentifier:identifier withPrefix:PopOverIdentifier]; - item = [self makePopOverForID:id withIdentifier:identifier]; - } else if ([identifier hasPrefix:GroupIdentifier]) { - id = [self idFromIdentifier:identifier withPrefix:GroupIdentifier]; - item = [self makeGroupForID:id withIdentifier:identifier]; - } - - item_map.insert(make_pair(std::string([id UTF8String]), item)); - - return item; -} - -- (nullable NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { - return [self makeItemForIdentifier:identifier]; -} - - // NSWindow overrides. -- (void)swipeWithEvent:(NSEvent *)event { +- (void)swipeWithEvent:(NSEvent*)event { if (event.deltaY == 1.0) { shell_->NotifyWindowSwipe("up"); } else if (event.deltaX == -1.0) { diff --git a/atom/browser/ui/cocoa/atom_touch_bar.h b/atom/browser/ui/cocoa/atom_touch_bar.h new file mode 100644 index 0000000000..2bd5a736ec --- /dev/null +++ b/atom/browser/ui/cocoa/atom_touch_bar.h @@ -0,0 +1,63 @@ +// Copyright (c) 2017 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_UI_COCOA_ATOM_TOUCH_BAR_H_ +#define ATOM_BROWSER_UI_COCOA_ATOM_TOUCH_BAR_H_ + +#import + +#include +#include +#include + +#include "atom/browser/native_window.h" +#include "atom/browser/ui/cocoa/touch_bar_forward_declarations.h" +#include "native_mate/constructor.h" +#include "native_mate/persistent_dictionary.h" + +@interface AtomTouchBar : NSObject { + @protected + std::map item_id_map; + std::map item_map; + id delegate_; + atom::NativeWindow* window_; +} + +- (id)initWithDelegate:(id)delegate + window:(atom::NativeWindow*)window; + +- (NSTouchBar*)makeTouchBarFromItemOptions:(const std::vector&)item_options; +- (NSTouchBar*)touchBarFromMutatableArray:(NSMutableArray*)items; +- (NSMutableArray*)identifierArrayFromDicts:(const std::vector&)dicts; +- (void)refreshTouchBarItem:(mate::Arguments*)args; +- (void)clear; + +- (NSString*)idFromIdentifier:(NSString*)identifier withPrefix:(NSString*)prefix; +- (bool)hasTBDict:(const std::string&)id; +- (NSColor*)colorFromHexColorString:(const std::string&)colorString; + +// Selector actions +- (void)buttonAction:(id)sender; +- (void)colorPickerAction:(id)sender; +- (void)sliderAction:(id)sender; + +// Helpers to create touch bar items +- (NSTouchBarItem*)makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier; +- (NSTouchBarItem*)makeButtonForID:(NSString*)id withIdentifier:(NSString*)identifier; +- (NSTouchBarItem*)makeLabelForID:(NSString*)id withIdentifier:(NSString*)identifier; +- (NSTouchBarItem*)makeColorPickerForID:(NSString*)id withIdentifier:(NSString*)identifier; +- (NSTouchBarItem*)makeSliderForID:(NSString*)id withIdentifier:(NSString*)identifier; +- (NSTouchBarItem*)makePopoverForID:(NSString*)id withIdentifier:(NSString*)identifier; +- (NSTouchBarItem*)makeGroupForID:(NSString*)id withIdentifier:(NSString*)identifier; + +// Helpers to update touch bar items +- (void)updateButton:(NSCustomTouchBarItem*)item withOptions:(const mate::PersistentDictionary&)options; +- (void)updateLabel:(NSCustomTouchBarItem*)item withOptions:(const mate::PersistentDictionary&)options; +- (void)updateColorPicker:(NSColorPickerTouchBarItem*)item withOptions:(const mate::PersistentDictionary)options; +- (void)updateSlider:(NSSliderTouchBarItem*)item withOptions:(const mate::PersistentDictionary&)options; +- (void)updatePopover:(NSPopoverTouchBarItem*)item withOptions:(const mate::PersistentDictionary&)options; + +@end + +#endif // ATOM_BROWSER_UI_COCOA_ATOM_TOUCH_BAR_H_ diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm new file mode 100644 index 0000000000..ac5a907d7a --- /dev/null +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -0,0 +1,362 @@ +// Copyright (c) 2017 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#import "atom/browser/ui/cocoa/atom_touch_bar.h" + +#include "atom/common/color_util.h" +#include "atom/common/native_mate_converters/image_converter.h" +#include "skia/ext/skia_utils_mac.h" +#include "ui/gfx/image/image.h" + +@implementation AtomTouchBar + +static NSTouchBarItemIdentifier ButtonIdentifier = @"com.electron.touchbar.button."; +static NSTouchBarItemIdentifier ColorPickerIdentifier = @"com.electron.touchbar.colorpicker."; +static NSTouchBarItemIdentifier GroupIdentifier = @"com.electron.touchbar.group."; +static NSTouchBarItemIdentifier LabelIdentifier = @"com.electron.touchbar.label."; +static NSTouchBarItemIdentifier PopOverIdentifier = @"com.electron.touchbar.popover."; +static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slider."; + +- (id)initWithDelegate:(id)delegate + window:(atom::NativeWindow*)window { + if ((self = [super init])) { + delegate_ = delegate; + window_ = window; + } + return self; +} + +- (void)clear { + item_id_map.clear(); +} + +- (NSTouchBar*)makeTouchBarFromItemOptions:(const std::vector&)item_options { + NSMutableArray* identifiers = [self identifierArrayFromDicts:item_options]; + return [self touchBarFromMutatableArray:identifiers]; +} + +- (NSTouchBar*)touchBarFromMutatableArray:(NSMutableArray*)items { + NSTouchBar* bar = [[NSClassFromString(@"NSTouchBar") alloc] init]; + bar.delegate = delegate_; + bar.defaultItemIdentifiers = items; + return bar; +} + +- (NSMutableArray*)identifierArrayFromDicts:(const std::vector&)dicts { + NSMutableArray* idents = [[NSMutableArray alloc] init]; + + for (const auto& item : dicts) { + std::string type; + std::string item_id; + if (item.Get("type", &type) && item.Get("id", &item_id)) { + item_id_map.insert(make_pair(item_id, item)); + if (type == "button") { + [idents addObject:[NSString stringWithFormat:@"%@%@", ButtonIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; + } else if (type == "label") { + [idents addObject:[NSString stringWithFormat:@"%@%@", LabelIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; + } else if (type == "colorpicker") { + [idents addObject:[NSString stringWithFormat:@"%@%@", ColorPickerIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; + } else if (type == "slider") { + [idents addObject:[NSString stringWithFormat:@"%@%@", SliderIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; + } else if (type == "popover") { + [idents addObject:[NSString stringWithFormat:@"%@%@", PopOverIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; + } else if (type == "group") { + [idents addObject:[NSString stringWithFormat:@"%@%@", GroupIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; + } + } + } + [idents addObject:NSTouchBarItemIdentifierOtherItemsProxy]; + + return idents; +} + +- (NSTouchBarItem*)makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { + NSTouchBarItem* item = nil; + NSString* id = nil; + if ([identifier hasPrefix:ButtonIdentifier]) { + id = [self idFromIdentifier:identifier withPrefix:ButtonIdentifier]; + item = [self makeButtonForID:id withIdentifier:identifier]; + } else if ([identifier hasPrefix:LabelIdentifier]) { + id = [self idFromIdentifier:identifier withPrefix:LabelIdentifier]; + item = [self makeLabelForID:id withIdentifier:identifier]; + } else if ([identifier hasPrefix:ColorPickerIdentifier]) { + id = [self idFromIdentifier:identifier withPrefix:ColorPickerIdentifier]; + item = [self makeColorPickerForID:id withIdentifier:identifier]; + } else if ([identifier hasPrefix:SliderIdentifier]) { + id = [self idFromIdentifier:identifier withPrefix:SliderIdentifier]; + item = [self makeSliderForID:id withIdentifier:identifier]; + } else if ([identifier hasPrefix:PopOverIdentifier]) { + id = [self idFromIdentifier:identifier withPrefix:PopOverIdentifier]; + item = [self makePopoverForID:id withIdentifier:identifier]; + } else if ([identifier hasPrefix:GroupIdentifier]) { + id = [self idFromIdentifier:identifier withPrefix:GroupIdentifier]; + item = [self makeGroupForID:id withIdentifier:identifier]; + } + + item_map.insert(make_pair(std::string([id UTF8String]), item)); + + return item; +} + + +- (void)refreshTouchBarItem:(mate::Arguments*)args { + std::string item_id; + std::string type; + mate::PersistentDictionary options; + if (!args->GetNext(&options)) return; + if (!options.Get("type", &type)) return; + if (!options.Get("id", &item_id)) return; + if (item_map.find(item_id) == item_map.end()) return; + + if (type == "button") { + [self updateButton:(NSCustomTouchBarItem*)item_map[item_id] + withOptions:options]; + } else if (type == "label") { + [self updateLabel:(NSCustomTouchBarItem*)item_map[item_id] + withOptions:options]; + } else if (type == "colorpicker") { + [self updateColorPicker:(NSColorPickerTouchBarItem*)item_map[item_id] + withOptions:options]; + } else if (type == "slider") { + [self updateSlider:(NSSliderTouchBarItem*)item_map[item_id] + withOptions:options]; + } else if (type == "popover") { + [self updatePopover:(NSPopoverTouchBarItem*)item_map[item_id] + withOptions:options]; + } else if (type == "group") { + args->ThrowError("You can not update the config of a group. Update the individual items or replace the group"); + } +} + +- (void)buttonAction:(id)sender { + NSString* item_id = [NSString stringWithFormat:@"%@.%d", ButtonIdentifier, (int)((NSButton*)sender).tag]; + window_->NotifyTouchBarItemInteraction("button", { std::string([item_id UTF8String]) }); +} + +- (void)colorPickerAction:(id)sender { + NSString* item_id = ((NSColorPickerTouchBarItem*)sender).identifier; + NSColor* color = ((NSColorPickerTouchBarItem*)sender).color; + std::string hex_color = atom::ToRGBHex(skia::NSDeviceColorToSkColor(color)); + window_->NotifyTouchBarItemInteraction("color_picker", { std::string([item_id UTF8String]), hex_color }); +} + +- (void)sliderAction:(id)sender { + NSString* item_id = ((NSSliderTouchBarItem*)sender).identifier; + window_->NotifyTouchBarItemInteraction("slider", { std::string([item_id UTF8String]), std::to_string([((NSSliderTouchBarItem*)sender).slider intValue]) }); +} + +- (NSString*)idFromIdentifier:(NSString*)identifier withPrefix:(NSString*)prefix { + return [identifier substringFromIndex:[prefix length]]; +} + +- (bool)hasTBDict:(const std::string&)id { + return item_id_map.find(id) != item_id_map.end(); +} + +- (NSColor*)colorFromHexColorString:(const std::string&)colorString { + SkColor color = atom::ParseHexColor(colorString); + return skia::SkColorToCalibratedNSColor(color); +} + +- (NSTouchBarItem*)makeButtonForID:(NSString*)id + withIdentifier:(NSString*)identifier { + std::string s_id([id UTF8String]); + if (![self hasTBDict:s_id]) return nil; + + mate::PersistentDictionary options = item_id_map[s_id]; + NSCustomTouchBarItem* item = [[NSClassFromString(@"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]; + NSButton* button = [NSButton buttonWithTitle:@"" + target:self + action:@selector(buttonAction:)]; + button.tag = [id floatValue]; + item.view = button; + [self updateButton:item withOptions:options]; + return item; +} + +- (void)updateButton:(NSCustomTouchBarItem*)item + withOptions:(const mate::PersistentDictionary&)options { + NSButton* button = (NSButton*)item.view; + + std::string customizationLabel; + if (options.Get("customizationLabel", &customizationLabel)) { + item.customizationLabel = [NSString stringWithUTF8String:customizationLabel.data()]; + } + + std::string backgroundColor; + if (options.Get("backgroundColor", &backgroundColor)) { + button.bezelColor = [self colorFromHexColorString:backgroundColor]; + } + + std::string label; + if (options.Get("label", &label)) { + button.title = [NSString stringWithUTF8String:label.data()]; + } + + std::string labelColor; + if (!label.empty() && options.Get("labelColor", &labelColor)) { + NSMutableAttributedString* attrTitle = [[[NSMutableAttributedString alloc] initWithString:[NSString stringWithUTF8String:label.data()]] autorelease]; + NSRange range = NSMakeRange(0, [attrTitle length]); + [attrTitle addAttribute:NSForegroundColorAttributeName + value:[self colorFromHexColorString:labelColor] + range:range]; + [attrTitle fixAttributesInRange:range]; + button.attributedTitle = attrTitle; + } + + gfx::Image image; + if (options.Get("image", &image)) { + button.image = image.AsNSImage(); + } +} + +- (NSTouchBarItem*)makeLabelForID:(NSString*)id + withIdentifier:(NSString*)identifier { + std::string s_id([id UTF8String]); + if (![self hasTBDict:s_id]) return nil; + + mate::PersistentDictionary item = item_id_map[s_id]; + NSCustomTouchBarItem* customItem = [[NSClassFromString(@"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]; + customItem.view = [NSTextField labelWithString:@""]; + [self updateLabel:customItem withOptions:item]; + + return customItem; +} + +- (void)updateLabel:(NSCustomTouchBarItem*)item + withOptions:(const mate::PersistentDictionary&)options { + std::string label; + options.Get("label", &label); + NSTextField* text_field = (NSTextField*)item.view; + text_field.stringValue = [NSString stringWithUTF8String:label.data()]; + + std::string customizationLabel; + if (options.Get("customizationLabel", &customizationLabel)) { + item.customizationLabel = [NSString stringWithUTF8String:customizationLabel.data()]; + } +} + +- (NSTouchBarItem*)makeColorPickerForID:(NSString*)id + withIdentifier:(NSString*)identifier { + std::string s_id([id UTF8String]); + if (![self hasTBDict:s_id]) return nil; + + mate::PersistentDictionary options = item_id_map[s_id]; + NSColorPickerTouchBarItem* item = [[NSClassFromString(@"NSColorPickerTouchBarItem") alloc] initWithIdentifier:identifier]; + item.target = self; + item.action = @selector(colorPickerAction:); + [self updateColorPicker:item withOptions:options]; + return item; +} + +- (void)updateColorPicker:(NSColorPickerTouchBarItem*)item + withOptions:(const mate::PersistentDictionary)options { + std::string customizationLabel; + if (options.Get("customizationLabel", &customizationLabel)) { + item.customizationLabel = [NSString stringWithUTF8String:customizationLabel.data()]; + } +} + +- (NSTouchBarItem*)makeSliderForID:(NSString*)id + withIdentifier:(NSString*)identifier { + std::string s_id([id UTF8String]); + if (![self hasTBDict:s_id]) return nil; + + mate::PersistentDictionary options = item_id_map[s_id]; + NSSliderTouchBarItem* item = [[NSClassFromString(@"NSSliderTouchBarItem") alloc] initWithIdentifier:identifier]; + item.target = self; + item.action = @selector(sliderAction:); + [self updateSlider:item withOptions:options]; + return item; +} + +- (void)updateSlider:(NSSliderTouchBarItem*)item + withOptions:(const mate::PersistentDictionary&)options { + std::string customizationLabel; + if (options.Get("customizationLabel", &customizationLabel)) { + item.customizationLabel = [NSString stringWithUTF8String:customizationLabel.data()]; + } + + std::string label; + options.Get("label", &label); + item.label = [NSString stringWithUTF8String:label.data()]; + + int maxValue = 100; + int minValue = 0; + int initialValue = 50; + options.Get("minValue", &minValue); + options.Get("maxValue", &maxValue); + options.Get("initialValue", &initialValue); + + item.slider.minValue = minValue; + item.slider.maxValue = maxValue; + item.slider.doubleValue = initialValue; +} + +- (NSTouchBarItem*)makePopoverForID:(NSString*)id + withIdentifier:(NSString*)identifier { + std::string s_id = std::string([id UTF8String]); + if (![self hasTBDict:s_id]) return nil; + + mate::PersistentDictionary options = item_id_map[s_id]; + NSPopoverTouchBarItem* item = [[NSClassFromString(@"NSPopoverTouchBarItem") alloc] initWithIdentifier:identifier]; + [self updatePopover:item withOptions:options]; + return item; +} + +- (void)updatePopover:(NSPopoverTouchBarItem*)item + withOptions:(const mate::PersistentDictionary&)options { + std::string customizationLabel; + if (options.Get("customizationLabel", &customizationLabel)) { + item.customizationLabel = [NSString stringWithUTF8String:customizationLabel.data()]; + } + + std::string label; + gfx::Image image; + if (options.Get("label", &label)) { + item.collapsedRepresentationLabel = [NSString stringWithUTF8String:label.data()]; + } else if (options.Get("image", &image)) { + item.collapsedRepresentationImage = image.AsNSImage(); + } + + bool showCloseButton; + if (options.Get("showCloseButton", &showCloseButton)) { + item.showsCloseButton = showCloseButton; + } + + std::vector touchBar; + if (options.Get("touchBar", &touchBar)) { + item.popoverTouchBar = [self touchBarFromMutatableArray:[self identifierArrayFromDicts:touchBar]]; + } +} + +- (NSTouchBarItem*)makeGroupForID:(NSString*)id + withIdentifier:(NSString*)identifier { + std::string s_id([id UTF8String]); + if (![self hasTBDict:s_id]) return nil; + mate::PersistentDictionary options = item_id_map[s_id]; + + std::vector items; + if (!options.Get("items", &items)) return nil; + + NSMutableArray* generatedItems = [[NSMutableArray alloc] init]; + NSMutableArray* identList = [self identifierArrayFromDicts:items]; + for (NSUInteger i = 0; i < [identList count]; i++) { + if ([identList objectAtIndex:i] != NSTouchBarItemIdentifierOtherItemsProxy) { + NSTouchBarItem* generatedItem = [self makeItemForIdentifier:[identList objectAtIndex:i]]; + if (generatedItem) { + [generatedItems addObject:generatedItem]; + } + } + } + + NSGroupTouchBarItem* item = [NSClassFromString(@"NSGroupTouchBarItem") groupItemWithIdentifier:identifier items:generatedItems]; + std::string customizationLabel; + if (options.Get("customizationLabel", &customizationLabel)) { + item.customizationLabel = [NSString stringWithUTF8String:customizationLabel.data()]; + } + return item; +} + +@end diff --git a/filenames.gypi b/filenames.gypi index a1758e2acf..6ac732c24a 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -281,6 +281,8 @@ 'atom/browser/ui/atom_menu_model.h', 'atom/browser/ui/cocoa/atom_menu_controller.h', 'atom/browser/ui/cocoa/atom_menu_controller.mm', + 'atom/browser/ui/cocoa/atom_touch_bar.h', + 'atom/browser/ui/cocoa/atom_touch_bar.mm', 'atom/browser/ui/cocoa/touch_bar_forward_declarations.h', 'atom/browser/ui/drag_util_mac.mm', 'atom/browser/ui/drag_util_views.cc', From 48515d9eccbcb9d9c0b765a76e5af021f41be187 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 28 Feb 2017 12:33:56 -0800 Subject: [PATCH 300/925] Ignore objective-c header lint --- atom/browser/native_window_mac.mm | 3 +-- script/cpplint.py | 5 +++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 9ef09f625d..c4f127459d 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -11,7 +11,6 @@ #include "atom/browser/window_list.h" #include "atom/common/color_util.h" #include "atom/common/draggable_region.h" -#include "atom/common/native_mate_converters/image_converter.h" #include "atom/common/options_switches.h" #include "base/mac/mac_util.h" #include "base/mac/scoped_cftyperef.h" @@ -396,7 +395,7 @@ bool ScopedDisableResize::disable_resize_ = false; // NSWindow overrides. -- (void)swipeWithEvent:(NSEvent*)event { +- (void)swipeWithEvent:(NSEvent *)event { if (event.deltaY == 1.0) { shell_->NotifyWindowSwipe("up"); } else if (event.deltaX == -1.0) { diff --git a/script/cpplint.py b/script/cpplint.py index 9d9281fb70..6c715522f8 100755 --- a/script/cpplint.py +++ b/script/cpplint.py @@ -9,10 +9,11 @@ from lib.util import execute IGNORE_FILES = [ os.path.join('atom', 'browser', 'mac', 'atom_application.h'), os.path.join('atom', 'browser', 'mac', 'atom_application_delegate.h'), - os.path.join('atom', 'browser', 'ui', 'cocoa', - 'touch_bar_forward_declarations.h'), os.path.join('atom', 'browser', 'resources', 'win', 'resource.h'), os.path.join('atom', 'browser', 'ui', 'cocoa', 'atom_menu_controller.h'), + os.path.join('atom', 'browser', 'ui', 'cocoa', 'atom_touch_bar.h'), + os.path.join('atom', 'browser', 'ui', 'cocoa', + 'touch_bar_forward_declarations.h'), os.path.join('atom', 'common', 'api', 'api_messages.h'), os.path.join('atom', 'common', 'common_message_generator.cc'), os.path.join('atom', 'common', 'common_message_generator.h'), From cf7cd1f32b401175123802791185f5b392bf7b21 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 28 Feb 2017 12:39:07 -0800 Subject: [PATCH 301/925] hasTBDict -> hasItemWithID --- atom/browser/ui/cocoa/atom_touch_bar.h | 2 +- atom/browser/ui/cocoa/atom_touch_bar.mm | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/atom/browser/ui/cocoa/atom_touch_bar.h b/atom/browser/ui/cocoa/atom_touch_bar.h index 2bd5a736ec..e15af0946a 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.h +++ b/atom/browser/ui/cocoa/atom_touch_bar.h @@ -34,7 +34,7 @@ - (void)clear; - (NSString*)idFromIdentifier:(NSString*)identifier withPrefix:(NSString*)prefix; -- (bool)hasTBDict:(const std::string&)id; +- (bool)hasItemWithID:(const std::string&)id; - (NSColor*)colorFromHexColorString:(const std::string&)colorString; // Selector actions diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index ac5a907d7a..6fb98639f7 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -150,7 +150,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide return [identifier substringFromIndex:[prefix length]]; } -- (bool)hasTBDict:(const std::string&)id { +- (bool)hasItemWithID:(const std::string&)id { return item_id_map.find(id) != item_id_map.end(); } @@ -162,7 +162,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide - (NSTouchBarItem*)makeButtonForID:(NSString*)id withIdentifier:(NSString*)identifier { std::string s_id([id UTF8String]); - if (![self hasTBDict:s_id]) return nil; + if (![self hasItemWithID:s_id]) return nil; mate::PersistentDictionary options = item_id_map[s_id]; NSCustomTouchBarItem* item = [[NSClassFromString(@"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]; @@ -214,7 +214,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide - (NSTouchBarItem*)makeLabelForID:(NSString*)id withIdentifier:(NSString*)identifier { std::string s_id([id UTF8String]); - if (![self hasTBDict:s_id]) return nil; + if (![self hasItemWithID:s_id]) return nil; mate::PersistentDictionary item = item_id_map[s_id]; NSCustomTouchBarItem* customItem = [[NSClassFromString(@"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]; @@ -240,7 +240,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide - (NSTouchBarItem*)makeColorPickerForID:(NSString*)id withIdentifier:(NSString*)identifier { std::string s_id([id UTF8String]); - if (![self hasTBDict:s_id]) return nil; + if (![self hasItemWithID:s_id]) return nil; mate::PersistentDictionary options = item_id_map[s_id]; NSColorPickerTouchBarItem* item = [[NSClassFromString(@"NSColorPickerTouchBarItem") alloc] initWithIdentifier:identifier]; @@ -261,7 +261,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide - (NSTouchBarItem*)makeSliderForID:(NSString*)id withIdentifier:(NSString*)identifier { std::string s_id([id UTF8String]); - if (![self hasTBDict:s_id]) return nil; + if (![self hasItemWithID:s_id]) return nil; mate::PersistentDictionary options = item_id_map[s_id]; NSSliderTouchBarItem* item = [[NSClassFromString(@"NSSliderTouchBarItem") alloc] initWithIdentifier:identifier]; @@ -297,7 +297,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide - (NSTouchBarItem*)makePopoverForID:(NSString*)id withIdentifier:(NSString*)identifier { std::string s_id = std::string([id UTF8String]); - if (![self hasTBDict:s_id]) return nil; + if (![self hasItemWithID:s_id]) return nil; mate::PersistentDictionary options = item_id_map[s_id]; NSPopoverTouchBarItem* item = [[NSClassFromString(@"NSPopoverTouchBarItem") alloc] initWithIdentifier:identifier]; @@ -334,7 +334,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide - (NSTouchBarItem*)makeGroupForID:(NSString*)id withIdentifier:(NSString*)identifier { std::string s_id([id UTF8String]); - if (![self hasTBDict:s_id]) return nil; + if (![self hasItemWithID:s_id]) return nil; mate::PersistentDictionary options = item_id_map[s_id]; std::vector items; From 8500538793a6d6a10004f3e792b55bafa2bcdba4 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 28 Feb 2017 12:43:57 -0800 Subject: [PATCH 302/925] Use base::SysUTF8ToNSString helper --- atom/browser/ui/cocoa/atom_touch_bar.mm | 35 +++++++++++++------------ 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index 6fb98639f7..34d1416121 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -6,6 +6,7 @@ #include "atom/common/color_util.h" #include "atom/common/native_mate_converters/image_converter.h" +#include "base/strings/sys_string_conversions.h" #include "skia/ext/skia_utils_mac.h" #include "ui/gfx/image/image.h" @@ -52,17 +53,17 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide if (item.Get("type", &type) && item.Get("id", &item_id)) { item_id_map.insert(make_pair(item_id, item)); if (type == "button") { - [idents addObject:[NSString stringWithFormat:@"%@%@", ButtonIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; + [idents addObject:[NSString stringWithFormat:@"%@%@", ButtonIdentifier, base::SysUTF8ToNSString(item_id)]]; } else if (type == "label") { - [idents addObject:[NSString stringWithFormat:@"%@%@", LabelIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; + [idents addObject:[NSString stringWithFormat:@"%@%@", LabelIdentifier, base::SysUTF8ToNSString(item_id)]]; } else if (type == "colorpicker") { - [idents addObject:[NSString stringWithFormat:@"%@%@", ColorPickerIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; + [idents addObject:[NSString stringWithFormat:@"%@%@", ColorPickerIdentifier, base::SysUTF8ToNSString(item_id)]]; } else if (type == "slider") { - [idents addObject:[NSString stringWithFormat:@"%@%@", SliderIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; + [idents addObject:[NSString stringWithFormat:@"%@%@", SliderIdentifier, base::SysUTF8ToNSString(item_id)]]; } else if (type == "popover") { - [idents addObject:[NSString stringWithFormat:@"%@%@", PopOverIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; + [idents addObject:[NSString stringWithFormat:@"%@%@", PopOverIdentifier, base::SysUTF8ToNSString(item_id)]]; } else if (type == "group") { - [idents addObject:[NSString stringWithFormat:@"%@%@", GroupIdentifier, [NSString stringWithUTF8String:item_id.c_str()]]]; + [idents addObject:[NSString stringWithFormat:@"%@%@", GroupIdentifier, base::SysUTF8ToNSString(item_id)]]; } } } @@ -181,7 +182,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide std::string customizationLabel; if (options.Get("customizationLabel", &customizationLabel)) { - item.customizationLabel = [NSString stringWithUTF8String:customizationLabel.data()]; + item.customizationLabel = base::SysUTF8ToNSString(customizationLabel); } std::string backgroundColor; @@ -191,12 +192,12 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide std::string label; if (options.Get("label", &label)) { - button.title = [NSString stringWithUTF8String:label.data()]; + button.title = base::SysUTF8ToNSString(label); } std::string labelColor; if (!label.empty() && options.Get("labelColor", &labelColor)) { - NSMutableAttributedString* attrTitle = [[[NSMutableAttributedString alloc] initWithString:[NSString stringWithUTF8String:label.data()]] autorelease]; + NSMutableAttributedString* attrTitle = [[[NSMutableAttributedString alloc] initWithString:base::SysUTF8ToNSString(label)] autorelease]; NSRange range = NSMakeRange(0, [attrTitle length]); [attrTitle addAttribute:NSForegroundColorAttributeName value:[self colorFromHexColorString:labelColor] @@ -229,11 +230,11 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide std::string label; options.Get("label", &label); NSTextField* text_field = (NSTextField*)item.view; - text_field.stringValue = [NSString stringWithUTF8String:label.data()]; + text_field.stringValue = base::SysUTF8ToNSString(label); std::string customizationLabel; if (options.Get("customizationLabel", &customizationLabel)) { - item.customizationLabel = [NSString stringWithUTF8String:customizationLabel.data()]; + item.customizationLabel = base::SysUTF8ToNSString(customizationLabel); } } @@ -254,7 +255,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide withOptions:(const mate::PersistentDictionary)options { std::string customizationLabel; if (options.Get("customizationLabel", &customizationLabel)) { - item.customizationLabel = [NSString stringWithUTF8String:customizationLabel.data()]; + item.customizationLabel = base::SysUTF8ToNSString(customizationLabel); } } @@ -275,12 +276,12 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide withOptions:(const mate::PersistentDictionary&)options { std::string customizationLabel; if (options.Get("customizationLabel", &customizationLabel)) { - item.customizationLabel = [NSString stringWithUTF8String:customizationLabel.data()]; + item.customizationLabel = base::SysUTF8ToNSString(customizationLabel); } std::string label; options.Get("label", &label); - item.label = [NSString stringWithUTF8String:label.data()]; + item.label = base::SysUTF8ToNSString(label); int maxValue = 100; int minValue = 0; @@ -309,13 +310,13 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide withOptions:(const mate::PersistentDictionary&)options { std::string customizationLabel; if (options.Get("customizationLabel", &customizationLabel)) { - item.customizationLabel = [NSString stringWithUTF8String:customizationLabel.data()]; + item.customizationLabel = base::SysUTF8ToNSString(customizationLabel); } std::string label; gfx::Image image; if (options.Get("label", &label)) { - item.collapsedRepresentationLabel = [NSString stringWithUTF8String:label.data()]; + item.collapsedRepresentationLabel = base::SysUTF8ToNSString(label); } else if (options.Get("image", &image)) { item.collapsedRepresentationImage = image.AsNSImage(); } @@ -354,7 +355,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide NSGroupTouchBarItem* item = [NSClassFromString(@"NSGroupTouchBarItem") groupItemWithIdentifier:identifier items:generatedItems]; std::string customizationLabel; if (options.Get("customizationLabel", &customizationLabel)) { - item.customizationLabel = [NSString stringWithUTF8String:customizationLabel.data()]; + item.customizationLabel = base::SysUTF8ToNSString(customizationLabel);; } return item; } From b30f7c3c94e6b1c2a9b92ba61925e500e79b0d46 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 28 Feb 2017 12:45:21 -0800 Subject: [PATCH 303/925] Use std::string ctor --- atom/browser/ui/cocoa/atom_touch_bar.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index 34d1416121..94d44f3265 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -297,7 +297,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide - (NSTouchBarItem*)makePopoverForID:(NSString*)id withIdentifier:(NSString*)identifier { - std::string s_id = std::string([id UTF8String]); + std::string s_id([id UTF8String]); if (![self hasItemWithID:s_id]) return nil; mate::PersistentDictionary options = item_id_map[s_id]; From f297ba987e1563aafc6574d4b418b8f75b41e0fd Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 28 Feb 2017 12:50:11 -0800 Subject: [PATCH 304/925] touchBarFromMutatableArray -> touchBarFromItemIdentifiers --- atom/browser/ui/cocoa/atom_touch_bar.h | 2 +- atom/browser/ui/cocoa/atom_touch_bar.mm | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/atom/browser/ui/cocoa/atom_touch_bar.h b/atom/browser/ui/cocoa/atom_touch_bar.h index e15af0946a..1010cf5b8a 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.h +++ b/atom/browser/ui/cocoa/atom_touch_bar.h @@ -28,7 +28,7 @@ window:(atom::NativeWindow*)window; - (NSTouchBar*)makeTouchBarFromItemOptions:(const std::vector&)item_options; -- (NSTouchBar*)touchBarFromMutatableArray:(NSMutableArray*)items; +- (NSTouchBar*)touchBarFromItemIdentifiers:(NSMutableArray*)items; - (NSMutableArray*)identifierArrayFromDicts:(const std::vector&)dicts; - (void)refreshTouchBarItem:(mate::Arguments*)args; - (void)clear; diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index 94d44f3265..48e0dbe691 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -34,10 +34,10 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide - (NSTouchBar*)makeTouchBarFromItemOptions:(const std::vector&)item_options { NSMutableArray* identifiers = [self identifierArrayFromDicts:item_options]; - return [self touchBarFromMutatableArray:identifiers]; + return [self touchBarFromItemIdentifiers:identifiers]; } -- (NSTouchBar*)touchBarFromMutatableArray:(NSMutableArray*)items { +- (NSTouchBar*)touchBarFromItemIdentifiers:(NSMutableArray*)items { NSTouchBar* bar = [[NSClassFromString(@"NSTouchBar") alloc] init]; bar.delegate = delegate_; bar.defaultItemIdentifiers = items; @@ -328,7 +328,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide std::vector touchBar; if (options.Get("touchBar", &touchBar)) { - item.popoverTouchBar = [self touchBarFromMutatableArray:[self identifierArrayFromDicts:touchBar]]; + item.popoverTouchBar = [self touchBarFromItemIdentifiers:[self identifierArrayFromDicts:touchBar]]; } } From ca29ec010139adf2bb93be98d68047cc1bf68709 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 28 Feb 2017 12:54:51 -0800 Subject: [PATCH 305/925] Use reference --- atom/browser/ui/cocoa/atom_touch_bar.h | 2 +- atom/browser/ui/cocoa/atom_touch_bar.mm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/atom/browser/ui/cocoa/atom_touch_bar.h b/atom/browser/ui/cocoa/atom_touch_bar.h index 1010cf5b8a..eb23a6a480 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.h +++ b/atom/browser/ui/cocoa/atom_touch_bar.h @@ -54,7 +54,7 @@ // Helpers to update touch bar items - (void)updateButton:(NSCustomTouchBarItem*)item withOptions:(const mate::PersistentDictionary&)options; - (void)updateLabel:(NSCustomTouchBarItem*)item withOptions:(const mate::PersistentDictionary&)options; -- (void)updateColorPicker:(NSColorPickerTouchBarItem*)item withOptions:(const mate::PersistentDictionary)options; +- (void)updateColorPicker:(NSColorPickerTouchBarItem*)item withOptions:(const mate::PersistentDictionary&)options; - (void)updateSlider:(NSSliderTouchBarItem*)item withOptions:(const mate::PersistentDictionary&)options; - (void)updatePopover:(NSPopoverTouchBarItem*)item withOptions:(const mate::PersistentDictionary&)options; diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index 48e0dbe691..4865e400c8 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -252,7 +252,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide } - (void)updateColorPicker:(NSColorPickerTouchBarItem*)item - withOptions:(const mate::PersistentDictionary)options { + withOptions:(const mate::PersistentDictionary&)options { std::string customizationLabel; if (options.Get("customizationLabel", &customizationLabel)) { item.customizationLabel = base::SysUTF8ToNSString(customizationLabel); From 14ef5c595779c6929cf487d0ebd4bba028db94c4 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 28 Feb 2017 13:50:16 -0800 Subject: [PATCH 306/925] Reset helper when makeTouchBar is called --- atom/browser/native_window_mac.mm | 13 ++++--------- atom/browser/ui/cocoa/atom_touch_bar.mm | 8 ++++---- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index c4f127459d..6cd42bc64a 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -352,9 +352,8 @@ bool ScopedDisableResize::disable_resize_ = false; - (void)setShell:(atom::NativeWindowMac*)shell; - (void)setEnableLargerThanScreen:(bool)enable; - (void)enableWindowButtonsOffset; -- (void)reloadTouchBar; -- (void)refreshTouchBarItem:(mate::Arguments*)args; - (void)resetTouchBar; +- (void)refreshTouchBarItem:(mate::Arguments*)args; @end @interface AtomNSWindow () @@ -365,7 +364,6 @@ bool ScopedDisableResize::disable_resize_ = false; - (void)setShell:(atom::NativeWindowMac*)shell { shell_ = shell; - touch_bar_.reset([[AtomTouchBar alloc] initWithDelegate:self window:shell]); } - (void)setEnableLargerThanScreen:(bool)enable { @@ -376,16 +374,12 @@ bool ScopedDisableResize::disable_resize_ = false; self.touchBar = nil; } -- (void)reloadTouchBar { - [touch_bar_ clear]; - self.touchBar = nil; -} - - (void)refreshTouchBarItem:(mate::Arguments*)args { [touch_bar_ refreshTouchBarItem:args]; } - (NSTouchBar*)makeTouchBar { + touch_bar_.reset([[AtomTouchBar alloc] initWithDelegate:self window:shell_]); return [touch_bar_ makeTouchBarFromItemOptions:shell_->GetTouchBarItems()]; } @@ -1377,6 +1371,7 @@ void NativeWindowMac::SetVibrancy(const std::string& type) { } void NativeWindowMac::DestroyTouchBar() { + touch_bar_items_.clear(); [window_ resetTouchBar]; } @@ -1384,7 +1379,7 @@ void NativeWindowMac::SetTouchBar(mate::Arguments* args) { std::vector items; if (args->GetNext(&items)) { touch_bar_items_ = items; - [window_ reloadTouchBar]; + [window_ resetTouchBar]; } } diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index 4865e400c8..f24baad10a 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -161,7 +161,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide } - (NSTouchBarItem*)makeButtonForID:(NSString*)id - withIdentifier:(NSString*)identifier { + withIdentifier:(NSString*)identifier { std::string s_id([id UTF8String]); if (![self hasItemWithID:s_id]) return nil; @@ -213,7 +213,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide } - (NSTouchBarItem*)makeLabelForID:(NSString*)id - withIdentifier:(NSString*)identifier { + withIdentifier:(NSString*)identifier { std::string s_id([id UTF8String]); if (![self hasItemWithID:s_id]) return nil; @@ -260,7 +260,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide } - (NSTouchBarItem*)makeSliderForID:(NSString*)id - withIdentifier:(NSString*)identifier { + withIdentifier:(NSString*)identifier { std::string s_id([id UTF8String]); if (![self hasItemWithID:s_id]) return nil; @@ -296,7 +296,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide } - (NSTouchBarItem*)makePopoverForID:(NSString*)id - withIdentifier:(NSString*)identifier { + withIdentifier:(NSString*)identifier { std::string s_id([id UTF8String]); if (![self hasItemWithID:s_id]) return nil; From b39b49a15a8808f95075c57a85b6eb86842ebced Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 28 Feb 2017 13:53:00 -0800 Subject: [PATCH 307/925] Rename to touch_bar_helper_ to differentiate from touchBar property --- atom/browser/native_window_mac.mm | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 6cd42bc64a..f0ac44b63c 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -336,10 +336,11 @@ bool ScopedDisableResize::disable_resize_ = false; @end -@interface AtomNSWindow : EventDispatchingWindow { +@interface AtomNSWindow : EventDispatchingWindow { @private atom::NativeWindowMac* shell_; bool enable_larger_than_screen_; + base::scoped_nsobject touch_bar_helper_; CGFloat windowButtonsInterButtonSpacing_; } @property BOOL acceptsFirstMouse; @@ -356,11 +357,7 @@ bool ScopedDisableResize::disable_resize_ = false; - (void)refreshTouchBarItem:(mate::Arguments*)args; @end -@interface AtomNSWindow () -@end - @implementation AtomNSWindow - base::scoped_nsobject touch_bar_; - (void)setShell:(atom::NativeWindowMac*)shell { shell_ = shell; @@ -375,16 +372,17 @@ bool ScopedDisableResize::disable_resize_ = false; } - (void)refreshTouchBarItem:(mate::Arguments*)args { - [touch_bar_ refreshTouchBarItem:args]; + [touch_bar_helper_ refreshTouchBarItem:args]; } - (NSTouchBar*)makeTouchBar { - touch_bar_.reset([[AtomTouchBar alloc] initWithDelegate:self window:shell_]); - return [touch_bar_ makeTouchBarFromItemOptions:shell_->GetTouchBarItems()]; + touch_bar_helper_.reset([[AtomTouchBar alloc] initWithDelegate:self window:shell_]); + return [touch_bar_helper_ makeTouchBarFromItemOptions:shell_->GetTouchBarItems()]; } -- (nullable NSTouchBarItem*)touchBar:(NSTouchBar*)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { - return [touch_bar_ makeItemForIdentifier:identifier]; +- (nullable NSTouchBarItem*)touchBar:(NSTouchBar*)touchBar + makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { + return [touch_bar_helper_ makeItemForIdentifier:identifier]; } // NSWindow overrides. From 28f2a4951bb72f80161e4498af60c35a66641d65 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 28 Feb 2017 13:54:20 -0800 Subject: [PATCH 308/925] touch_bar_helper_ -> atom_touch_bar_ --- atom/browser/native_window_mac.mm | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index f0ac44b63c..af65d53368 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -340,7 +340,7 @@ bool ScopedDisableResize::disable_resize_ = false; @private atom::NativeWindowMac* shell_; bool enable_larger_than_screen_; - base::scoped_nsobject touch_bar_helper_; + base::scoped_nsobject atom_touch_bar_; CGFloat windowButtonsInterButtonSpacing_; } @property BOOL acceptsFirstMouse; @@ -372,12 +372,12 @@ bool ScopedDisableResize::disable_resize_ = false; } - (void)refreshTouchBarItem:(mate::Arguments*)args { - [touch_bar_helper_ refreshTouchBarItem:args]; + [atom_touch_bar_ refreshTouchBarItem:args]; } - (NSTouchBar*)makeTouchBar { - touch_bar_helper_.reset([[AtomTouchBar alloc] initWithDelegate:self window:shell_]); - return [touch_bar_helper_ makeTouchBarFromItemOptions:shell_->GetTouchBarItems()]; + atom_touch_bar_.reset([[AtomTouchBar alloc] initWithDelegate:self window:shell_]); + return [atom_touch_bar_ makeTouchBarFromItemOptions:shell_->GetTouchBarItems()]; } - (nullable NSTouchBarItem*)touchBar:(NSTouchBar*)touchBar From 1972e2eff947c91bd7041ec64a1d82d3e21c2781 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 28 Feb 2017 14:09:22 -0800 Subject: [PATCH 309/925] Update renamed variable --- atom/browser/native_window_mac.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index af65d53368..1a20ea74d0 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -382,7 +382,7 @@ bool ScopedDisableResize::disable_resize_ = false; - (nullable NSTouchBarItem*)touchBar:(NSTouchBar*)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { - return [touch_bar_helper_ makeItemForIdentifier:identifier]; + return [atom_touch_bar_ makeItemForIdentifier:identifier]; } // NSWindow overrides. From cbb6f8c33ec4caa82f3bb9cd1f3010bed0c6f89a Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 28 Feb 2017 15:37:15 -0800 Subject: [PATCH 310/925] Store event listeners in each TouchBar class --- atom/browser/api/atom_api_window.cc | 6 +- atom/browser/api/atom_api_window.h | 4 +- atom/browser/native_window.cc | 6 +- atom/browser/native_window.h | 4 +- atom/browser/native_window_observer.h | 5 +- atom/browser/ui/cocoa/atom_touch_bar.mm | 33 ++++-- lib/browser/api/browser-window.js | 14 +-- lib/browser/api/touch-bar.js | 134 +++++++++++------------- 8 files changed, 104 insertions(+), 102 deletions(-) diff --git a/atom/browser/api/atom_api_window.cc b/atom/browser/api/atom_api_window.cc index adafab9173..6c14d8b3c6 100644 --- a/atom/browser/api/atom_api_window.cc +++ b/atom/browser/api/atom_api_window.cc @@ -282,9 +282,9 @@ void Window::OnExecuteWindowsCommand(const std::string& command_name) { Emit("app-command", command_name); } -void Window::OnTouchBarItemResult(const std::string& item_type, - const std::vector& args) { - Emit("-touch-bar-interaction", item_type, args); +void Window::OnTouchBarItemResult(const std::string& item_id, + const base::DictionaryValue& details) { + Emit("-touch-bar-interaction", item_id, details); } #if defined(OS_WIN) diff --git a/atom/browser/api/atom_api_window.h b/atom/browser/api/atom_api_window.h index 2620292b25..bf3e8cb5e7 100644 --- a/atom/browser/api/atom_api_window.h +++ b/atom/browser/api/atom_api_window.h @@ -85,8 +85,8 @@ class Window : public mate::TrackableObject, void OnRendererUnresponsive() override; void OnRendererResponsive() override; void OnExecuteWindowsCommand(const std::string& command_name) override; - void OnTouchBarItemResult(const std::string& item_type, - const std::vector& args) override; + void OnTouchBarItemResult(const std::string& item_id, + const base::DictionaryValue& details) override; #if defined(OS_WIN) void OnWindowMessage(UINT message, WPARAM w_param, LPARAM l_param) override; diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index b008739709..322ca8a0e4 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -576,10 +576,10 @@ void NativeWindow::NotifyWindowExecuteWindowsCommand( } void NativeWindow::NotifyTouchBarItemInteraction( - const std::string& type, - const std::vector& args) { + const std::string& item_id, + const base::DictionaryValue& details) { for (NativeWindowObserver& observer : observers_) - observer.OnTouchBarItemResult(type, args); + observer.OnTouchBarItemResult(item_id, details); } #if defined(OS_WIN) diff --git a/atom/browser/native_window.h b/atom/browser/native_window.h index 23c393e7ae..77188d8494 100644 --- a/atom/browser/native_window.h +++ b/atom/browser/native_window.h @@ -234,8 +234,8 @@ class NativeWindow : public base::SupportsUserData, void NotifyWindowEnterHtmlFullScreen(); void NotifyWindowLeaveHtmlFullScreen(); void NotifyWindowExecuteWindowsCommand(const std::string& command); - void NotifyTouchBarItemInteraction(const std::string& item_type, - const std::vector& args); + void NotifyTouchBarItemInteraction(const std::string& item_id, + const base::DictionaryValue& details); #if defined(OS_WIN) void NotifyWindowMessage(UINT message, WPARAM w_param, LPARAM l_param); diff --git a/atom/browser/native_window_observer.h b/atom/browser/native_window_observer.h index 7fd1b7caeb..7f1f5aace8 100644 --- a/atom/browser/native_window_observer.h +++ b/atom/browser/native_window_observer.h @@ -9,6 +9,7 @@ #include #include "base/strings/string16.h" +#include "base/values.h" #include "ui/base/window_open_disposition.h" #include "url/gurl.h" @@ -71,8 +72,8 @@ class NativeWindowObserver { virtual void OnWindowLeaveFullScreen() {} virtual void OnWindowEnterHtmlFullScreen() {} virtual void OnWindowLeaveHtmlFullScreen() {} - virtual void OnTouchBarItemResult(const std::string& item_type, - const std::vector& args) {} + virtual void OnTouchBarItemResult(const std::string& item_id, + const base::DictionaryValue& details) {} // Called when window message received #if defined(OS_WIN) diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index f24baad10a..194be6f5a8 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -131,20 +131,30 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide } - (void)buttonAction:(id)sender { - NSString* item_id = [NSString stringWithFormat:@"%@.%d", ButtonIdentifier, (int)((NSButton*)sender).tag]; - window_->NotifyTouchBarItemInteraction("button", { std::string([item_id UTF8String]) }); + NSString* item_id = [NSString stringWithFormat:@"%ld", ((NSButton*)sender).tag]; + window_->NotifyTouchBarItemInteraction([item_id UTF8String], + base::DictionaryValue()); } - (void)colorPickerAction:(id)sender { - NSString* item_id = ((NSColorPickerTouchBarItem*)sender).identifier; + NSString* identifier = ((NSColorPickerTouchBarItem*)sender).identifier; + NSString* item_id = [self idFromIdentifier:identifier + withPrefix:ColorPickerIdentifier]; NSColor* color = ((NSColorPickerTouchBarItem*)sender).color; std::string hex_color = atom::ToRGBHex(skia::NSDeviceColorToSkColor(color)); - window_->NotifyTouchBarItemInteraction("color_picker", { std::string([item_id UTF8String]), hex_color }); + base::DictionaryValue details; + details.SetString("color", hex_color); + window_->NotifyTouchBarItemInteraction([item_id UTF8String], details); } - (void)sliderAction:(id)sender { - NSString* item_id = ((NSSliderTouchBarItem*)sender).identifier; - window_->NotifyTouchBarItemInteraction("slider", { std::string([item_id UTF8String]), std::to_string([((NSSliderTouchBarItem*)sender).slider intValue]) }); + NSString* identifier = ((NSSliderTouchBarItem*)sender).identifier; + NSString* item_id = [self idFromIdentifier:identifier + withPrefix:SliderIdentifier]; + base::DictionaryValue details; + details.SetInteger("value", + [((NSSliderTouchBarItem*)sender).slider intValue]); + window_->NotifyTouchBarItemInteraction([item_id UTF8String], details); } - (NSString*)idFromIdentifier:(NSString*)identifier withPrefix:(NSString*)prefix { @@ -326,9 +336,10 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide item.showsCloseButton = showCloseButton; } - std::vector touchBar; - if (options.Get("touchBar", &touchBar)) { - item.popoverTouchBar = [self touchBarFromItemIdentifiers:[self identifierArrayFromDicts:touchBar]]; + mate::PersistentDictionary child; + std::vector items; + if (options.Get("child", &child) && child.Get("ordereredItems", &items)) { + item.popoverTouchBar = [self touchBarFromItemIdentifiers:[self identifierArrayFromDicts:items]]; } } @@ -338,8 +349,10 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide if (![self hasItemWithID:s_id]) return nil; mate::PersistentDictionary options = item_id_map[s_id]; + mate::PersistentDictionary child; + if (!options.Get("child", &child)) return nil; std::vector items; - if (!options.Get("items", &items)) return nil; + if (!child.Get("ordereredItems", &items)) return nil; NSMutableArray* generatedItems = [[NSMutableArray alloc] init]; NSMutableArray* identList = [self identifierArrayFromDicts:items]; diff --git a/lib/browser/api/browser-window.js b/lib/browser/api/browser-window.js index ecfd9195f4..a8a81cbd59 100644 --- a/lib/browser/api/browser-window.js +++ b/lib/browser/api/browser-window.js @@ -133,8 +133,10 @@ BrowserWindow.prototype._init = function () { }) // Proxy TouchBar events - this.on('-touch-bar-interaction', (event, itemType, id, ...args) => { - TouchBar._event(itemType, id, ...args) + this.on('-touch-bar-interaction', (event, id, details) => { + if (this._touchBar != null) { + this._touchBar.emit('interaction', id, details) + } }) } @@ -207,6 +209,7 @@ Object.assign(BrowserWindow.prototype, { BrowserWindow.prototype.setTouchBar = function (touchBar) { if (touchBar == null) { this._destroyTouchBar() + this._touchBar = null return } @@ -214,11 +217,8 @@ BrowserWindow.prototype.setTouchBar = function (touchBar) { touchBar = new TouchBar(touchBar) } - this._setTouchBar(touchBar.toJSON()) - touchBar._owner = this - touchBar.items.forEach((item) => { - item._owner = this - }) + this._touchBar = touchBar + this._setTouchBar(touchBar.ordereredItems) } BrowserWindow.prototype._updateTouchBarItem = function (itemID) { diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index 07b2631a77..fe75e3ade5 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -1,82 +1,67 @@ -class TouchBar { +const {EventEmitter} = require('events') + +let itemIdIncrementor = 1 + +class TouchBar extends EventEmitter { constructor (items) { - this.items = items + super() + if (!Array.isArray(items)) { throw new Error('The items object provided has to be an array') } + + this.items = {} + this.ordereredItems = [] + const registerItem = (item) => { + this.items[item.id] = item + if (item.child instanceof TouchBar) { + item.child.ordereredItems.forEach(registerItem) + } + } items.forEach((item) => { - if (!item.id) { + this.ordereredItems.push(item) + if (!(item instanceof TouchBarItem)) { throw new Error('Each item must be an instance of a TouchBarItem') } + registerItem(item) + }) + + this.on('interaction', (itemID, details) => { + const item = this.items[itemID] + if (item != null && item.onInteraction != null) { + item.onInteraction(details) + } }) } - - toJSON () { - return this.items.map((item) => item.toJSON ? item.toJSON() : item) - } -} - -let itemIdIncrementor = 1 -const itemEventHandlers = {} - -TouchBar._event = (itemType, eventArgs) => { - let args = eventArgs.slice(1) - if (itemType === 'slider') { - args = args.map(val => parseInt(val, 10)) - } - const idParts = eventArgs[0].split('.') - const itemId = idParts[idParts.length - 1] - if (itemEventHandlers[itemId]) itemEventHandlers[itemId](...args) } class TouchBarItem { constructor (config) { - this.id = itemIdIncrementor++ - const mConfig = Object.assign({}, config || {}) - Object.defineProperty(this, 'config', { - configurable: false, - enumerable: false, - get: () => mConfig - }) - this.config.id = `${this.config.id || this.id}` - if (typeof this.config !== 'object' || this.config === null) { - throw new Error('Provided config must be a non-null object') - } - } - - updateConfig (newConfig) { - if (!this._owner) { - throw new Error('Cannot call methods on TouchBarItems without assigning to a BrowserWindow') - } - const dupConfig = Object.assign({}, newConfig) - delete dupConfig.id - Object.assign(this.config, dupConfig) - this._owner._updateTouchBarItem(this.toJSON()) - } - - toJSON () { - return this.config + this.id = `${itemIdIncrementor++}` } } TouchBar.Button = class TouchBarButton extends TouchBarItem { constructor (config) { super(config) - this.config.type = 'button' - const click = config.click - if (typeof click === 'function') { - itemEventHandlers[`${this.id}`] = click - } + this.type = 'button' + this.label = config.label + this.backgroundColor = config.backgroundColor + this.labelColor = config.labelColor + this.onInteraction = config.click } } TouchBar.ColorPicker = class TouchBarColorPicker extends TouchBarItem { constructor (config) { super(config) - this.config.type = 'colorpicker' - const change = this.config.change + this.type = 'colorpicker' + + const {change} = config if (typeof change === 'function') { - itemEventHandlers[`${this.id}`] = change + this.onInteraction = (details) => { + change(details.color) + } } } } @@ -84,45 +69,48 @@ TouchBar.ColorPicker = class TouchBarColorPicker extends TouchBarItem { TouchBar.Group = class TouchBarGroup extends TouchBarItem { constructor (config) { super(config) - this.config.type = 'group' - } - - toJSON () { - const config = this.config - return Object.assign({}, config, { - items: config.items && config.items.toJSON ? config.items.toJSON() : [] - }) + this.type = 'group' + this.child = config.items + if (!(this.child instanceof TouchBar)) { + this.child = new TouchBar(this.items) + } } } TouchBar.Label = class TouchBarLabel extends TouchBarItem { constructor (config) { super(config) - this.config.type = 'label' + this.type = 'label' + this.label = config.label } } TouchBar.PopOver = class TouchBarPopOver extends TouchBarItem { constructor (config) { super(config) - this.config.type = 'popover' - } - - toJSON () { - const config = this.config - return Object.assign({}, config, { - touchBar: config.touchBar && config.touchBar.toJSON ? config.touchBar.toJSON() : [] - }) + this.type = 'popover' + this.label = config.label + this.showCloseButton = config.showCloseButton + this.child = config.items + if (!(this.child instanceof TouchBar)) { + this.child = new TouchBar(this.items) + } } } TouchBar.Slider = class TouchBarSlider extends TouchBarItem { constructor (config) { super(config) - this.config.type = 'slider' - const change = this.config.change + this.type = 'slider' + this.minValue = config.minValue + this.maxValue = config.maxValue + this.initialValue = config.initialValue + + const {change} = config if (typeof change === 'function') { - itemEventHandlers[this.id] = change + this.onInteraction = (details) => { + change(details.value) + } } } } From 98f5858b1139783f887b68b2e2d2935a2721423f Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 28 Feb 2017 16:08:12 -0800 Subject: [PATCH 311/925] Initial support for dynamic properties --- atom/browser/api/atom_api_window.cc | 4 ++-- atom/browser/api/atom_api_window.h | 2 +- atom/browser/native_window.cc | 2 +- atom/browser/native_window.h | 2 +- atom/browser/native_window_mac.h | 2 +- atom/browser/native_window_mac.mm | 11 ++++++----- atom/browser/ui/cocoa/atom_touch_bar.h | 2 +- atom/browser/ui/cocoa/atom_touch_bar.mm | 25 +++++++++++-------------- lib/browser/api/browser-window.js | 12 +++++++----- lib/browser/api/touch-bar.js | 17 +++++++++++++++-- 10 files changed, 46 insertions(+), 33 deletions(-) diff --git a/atom/browser/api/atom_api_window.cc b/atom/browser/api/atom_api_window.cc index 6c14d8b3c6..3f9b82af59 100644 --- a/atom/browser/api/atom_api_window.cc +++ b/atom/browser/api/atom_api_window.cc @@ -853,8 +853,8 @@ void Window::SetTouchBar(mate::Arguments* args) { window_->SetTouchBar(args); } -void Window::RefreshTouchBarItem(mate::Arguments* args) { - window_->RefreshTouchBarItem(args); +void Window::RefreshTouchBarItem(const std::string& item_id) { + window_->RefreshTouchBarItem(item_id); } int32_t Window::ID() const { diff --git a/atom/browser/api/atom_api_window.h b/atom/browser/api/atom_api_window.h index bf3e8cb5e7..697dfa3e10 100644 --- a/atom/browser/api/atom_api_window.h +++ b/atom/browser/api/atom_api_window.h @@ -207,7 +207,7 @@ class Window : public mate::TrackableObject, void SetVibrancy(mate::Arguments* args); void DestroyTouchBar(); void SetTouchBar(mate::Arguments* args); - void RefreshTouchBarItem(mate::Arguments* args); + void RefreshTouchBarItem(const std::string& item_id); v8::Local WebContents(v8::Isolate* isolate); diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index 322ca8a0e4..fb34ee021e 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -347,7 +347,7 @@ void NativeWindow::DestroyTouchBar() { void NativeWindow::SetTouchBar(mate::Arguments* args) { } -void NativeWindow::RefreshTouchBarItem(mate::Arguments* args) { +void NativeWindow::RefreshTouchBarItem(const std::string& item_id) { } void NativeWindow::FocusOnWebView() { diff --git a/atom/browser/native_window.h b/atom/browser/native_window.h index 77188d8494..8e8b613028 100644 --- a/atom/browser/native_window.h +++ b/atom/browser/native_window.h @@ -173,7 +173,7 @@ class NativeWindow : public base::SupportsUserData, // Touchbar API virtual void DestroyTouchBar(); virtual void SetTouchBar(mate::Arguments* args); - virtual void RefreshTouchBarItem(mate::Arguments* args); + virtual void RefreshTouchBarItem(const std::string& item_id); // Webview APIs. virtual void FocusOnWebView(); diff --git a/atom/browser/native_window_mac.h b/atom/browser/native_window_mac.h index f8ebfec3ad..d80ff42897 100644 --- a/atom/browser/native_window_mac.h +++ b/atom/browser/native_window_mac.h @@ -104,7 +104,7 @@ class NativeWindowMac : public NativeWindow, void SetVibrancy(const std::string& type) override; void DestroyTouchBar() override; void SetTouchBar(mate::Arguments* args) override; - void RefreshTouchBarItem(mate::Arguments* args) override; + void RefreshTouchBarItem(const std::string& item_id) override; std::vector GetTouchBarItems(); // content::RenderWidgetHost::InputEventObserver: diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 1a20ea74d0..0dc3bd952b 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -354,7 +354,8 @@ bool ScopedDisableResize::disable_resize_ = false; - (void)setEnableLargerThanScreen:(bool)enable; - (void)enableWindowButtonsOffset; - (void)resetTouchBar; -- (void)refreshTouchBarItem:(mate::Arguments*)args; +- (void)refreshTouchBarItem:(const std::string&)item_id; + @end @implementation AtomNSWindow @@ -371,8 +372,8 @@ bool ScopedDisableResize::disable_resize_ = false; self.touchBar = nil; } -- (void)refreshTouchBarItem:(mate::Arguments*)args { - [atom_touch_bar_ refreshTouchBarItem:args]; +- (void)refreshTouchBarItem:(const std::string&)item_id { + [atom_touch_bar_ refreshTouchBarItem:item_id]; } - (NSTouchBar*)makeTouchBar { @@ -1381,8 +1382,8 @@ void NativeWindowMac::SetTouchBar(mate::Arguments* args) { } } -void NativeWindowMac::RefreshTouchBarItem(mate::Arguments* args) { - [window_ refreshTouchBarItem:args]; +void NativeWindowMac::RefreshTouchBarItem(const std::string& item_id) { + [window_ refreshTouchBarItem:item_id]; } std::vector NativeWindowMac::GetTouchBarItems() { diff --git a/atom/browser/ui/cocoa/atom_touch_bar.h b/atom/browser/ui/cocoa/atom_touch_bar.h index eb23a6a480..2efabe6cf0 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.h +++ b/atom/browser/ui/cocoa/atom_touch_bar.h @@ -30,7 +30,7 @@ - (NSTouchBar*)makeTouchBarFromItemOptions:(const std::vector&)item_options; - (NSTouchBar*)touchBarFromItemIdentifiers:(NSMutableArray*)items; - (NSMutableArray*)identifierArrayFromDicts:(const std::vector&)dicts; -- (void)refreshTouchBarItem:(mate::Arguments*)args; +- (void)refreshTouchBarItem:(const std::string&)item_id; - (void)clear; - (NSString*)idFromIdentifier:(NSString*)identifier withPrefix:(NSString*)prefix; diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index 194be6f5a8..b2053f7df0 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -101,32 +101,29 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide } -- (void)refreshTouchBarItem:(mate::Arguments*)args { - std::string item_id; - std::string type; - mate::PersistentDictionary options; - if (!args->GetNext(&options)) return; - if (!options.Get("type", &type)) return; - if (!options.Get("id", &item_id)) return; +- (void)refreshTouchBarItem:(const std::string&)item_id { if (item_map.find(item_id) == item_map.end()) return; + if (![self hasItemWithID:item_id]) return; - if (type == "button") { + mate::PersistentDictionary options = item_id_map[item_id]; + std::string item_type; + options.Get("type", &item_type); + + if (item_type == "button") { [self updateButton:(NSCustomTouchBarItem*)item_map[item_id] withOptions:options]; - } else if (type == "label") { + } else if (item_type == "label") { [self updateLabel:(NSCustomTouchBarItem*)item_map[item_id] withOptions:options]; - } else if (type == "colorpicker") { + } else if (item_type == "colorpicker") { [self updateColorPicker:(NSColorPickerTouchBarItem*)item_map[item_id] withOptions:options]; - } else if (type == "slider") { + } else if (item_type == "slider") { [self updateSlider:(NSSliderTouchBarItem*)item_map[item_id] withOptions:options]; - } else if (type == "popover") { + } else if (item_type == "popover") { [self updatePopover:(NSPopoverTouchBarItem*)item_map[item_id] withOptions:options]; - } else if (type == "group") { - args->ThrowError("You can not update the config of a group. Update the individual items or replace the group"); } } diff --git a/lib/browser/api/browser-window.js b/lib/browser/api/browser-window.js index a8a81cbd59..27f425c7ae 100644 --- a/lib/browser/api/browser-window.js +++ b/lib/browser/api/browser-window.js @@ -138,6 +138,8 @@ BrowserWindow.prototype._init = function () { this._touchBar.emit('interaction', id, details) } }) + + this._touchBarListener = this._refreshTouchBarItem.bind(this) } BrowserWindow.getFocusedWindow = () => { @@ -208,8 +210,11 @@ Object.assign(BrowserWindow.prototype, { // TouchBar API BrowserWindow.prototype.setTouchBar = function (touchBar) { if (touchBar == null) { + if (this._touchBar != null) { + this._touchBar.removeListener('change', this._touchBarListener) + this._touchBar = null + } this._destroyTouchBar() - this._touchBar = null return } @@ -218,11 +223,8 @@ BrowserWindow.prototype.setTouchBar = function (touchBar) { } this._touchBar = touchBar + this._touchBar.on('change', this._touchBarListener) this._setTouchBar(touchBar.ordereredItems) } -BrowserWindow.prototype._updateTouchBarItem = function (itemID) { - this._refreshTouchBarItem(itemID) -} - module.exports = BrowserWindow diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index fe75e3ade5..237ef716fa 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -14,6 +14,9 @@ class TouchBar extends EventEmitter { this.ordereredItems = [] const registerItem = (item) => { this.items[item.id] = item + item.on('change', () => { + this.emit('change', item.id, item.type) + }) if (item.child instanceof TouchBar) { item.child.ordereredItems.forEach(registerItem) } @@ -35,8 +38,9 @@ class TouchBar extends EventEmitter { } } -class TouchBarItem { +class TouchBarItem extends EventEmitter { constructor (config) { + super() this.id = `${itemIdIncrementor++}` } } @@ -81,7 +85,16 @@ TouchBar.Label = class TouchBarLabel extends TouchBarItem { constructor (config) { super(config) this.type = 'label' - this.label = config.label + this._label = config.label + } + + set label (newLabel) { + this._label = newLabel + this.emit('change') + } + + get label () { + return this._label } } From 823b3baed020753090c523fd1883d1b6147d2d5f Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 28 Feb 2017 16:14:02 -0800 Subject: [PATCH 312/925] Use vector of dictionaries instead of mate::Arguments --- atom/browser/api/atom_api_window.cc | 4 ++-- atom/browser/api/atom_api_window.h | 3 ++- atom/browser/native_window.cc | 3 ++- atom/browser/native_window.h | 4 +++- atom/browser/native_window_mac.h | 5 ++--- atom/browser/native_window_mac.mm | 10 ++++------ 6 files changed, 15 insertions(+), 14 deletions(-) diff --git a/atom/browser/api/atom_api_window.cc b/atom/browser/api/atom_api_window.cc index 3f9b82af59..fbfba8a9e6 100644 --- a/atom/browser/api/atom_api_window.cc +++ b/atom/browser/api/atom_api_window.cc @@ -849,8 +849,8 @@ void Window::DestroyTouchBar() { window_->DestroyTouchBar(); } -void Window::SetTouchBar(mate::Arguments* args) { - window_->SetTouchBar(args); +void Window::SetTouchBar(const std::vector& items) { + window_->SetTouchBar(items); } void Window::RefreshTouchBarItem(const std::string& item_id) { diff --git a/atom/browser/api/atom_api_window.h b/atom/browser/api/atom_api_window.h index 697dfa3e10..b8060c3f47 100644 --- a/atom/browser/api/atom_api_window.h +++ b/atom/browser/api/atom_api_window.h @@ -16,6 +16,7 @@ #include "atom/common/api/atom_api_native_image.h" #include "atom/common/key_weak_map.h" #include "native_mate/handle.h" +#include "native_mate/persistent_dictionary.h" #include "ui/gfx/image/image.h" class GURL; @@ -206,7 +207,7 @@ class Window : public mate::TrackableObject, void SetVibrancy(mate::Arguments* args); void DestroyTouchBar(); - void SetTouchBar(mate::Arguments* args); + void SetTouchBar(const std::vector& items); void RefreshTouchBarItem(const std::string& item_id); v8::Local WebContents(v8::Isolate* isolate); diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index fb34ee021e..21d442c920 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -344,7 +344,8 @@ void NativeWindow::SetVibrancy(const std::string& filename) { void NativeWindow::DestroyTouchBar() { } -void NativeWindow::SetTouchBar(mate::Arguments* args) { +void NativeWindow::SetTouchBar( + const std::vector& items) { } void NativeWindow::RefreshTouchBarItem(const std::string& item_id) { diff --git a/atom/browser/native_window.h b/atom/browser/native_window.h index 8e8b613028..bb94127b7a 100644 --- a/atom/browser/native_window.h +++ b/atom/browser/native_window.h @@ -22,6 +22,7 @@ #include "content/public/browser/web_contents_user_data.h" #include "extensions/browser/app_window/size_constraints.h" #include "native_mate/constructor.h" +#include "native_mate/persistent_dictionary.h" #include "ui/gfx/image/image.h" #include "ui/gfx/image/image_skia.h" @@ -172,7 +173,8 @@ class NativeWindow : public base::SupportsUserData, // Touchbar API virtual void DestroyTouchBar(); - virtual void SetTouchBar(mate::Arguments* args); + virtual void SetTouchBar( + const std::vector& items); virtual void RefreshTouchBarItem(const std::string& item_id); // Webview APIs. diff --git a/atom/browser/native_window_mac.h b/atom/browser/native_window_mac.h index d80ff42897..388ea282c0 100644 --- a/atom/browser/native_window_mac.h +++ b/atom/browser/native_window_mac.h @@ -13,8 +13,6 @@ #include "atom/browser/native_window.h" #include "base/mac/scoped_nsobject.h" #include "content/public/browser/render_widget_host.h" -#include "native_mate/constructor.h" -#include "native_mate/persistent_dictionary.h" @class AtomNSWindow; @class AtomNSWindowDelegate; @@ -103,7 +101,8 @@ class NativeWindowMac : public NativeWindow, void SetVibrancy(const std::string& type) override; void DestroyTouchBar() override; - void SetTouchBar(mate::Arguments* args) override; + void SetTouchBar( + const std::vector& items) override; void RefreshTouchBarItem(const std::string& item_id) override; std::vector GetTouchBarItems(); diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 0dc3bd952b..d93b136458 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -1374,12 +1374,10 @@ void NativeWindowMac::DestroyTouchBar() { [window_ resetTouchBar]; } -void NativeWindowMac::SetTouchBar(mate::Arguments* args) { - std::vector items; - if (args->GetNext(&items)) { - touch_bar_items_ = items; - [window_ resetTouchBar]; - } +void NativeWindowMac::SetTouchBar( + const std::vector& items) { + touch_bar_items_ = items; + [window_ resetTouchBar]; } void NativeWindowMac::RefreshTouchBarItem(const std::string& item_id) { From 812beb240ba888bb93f183f335bdc149003a35dd Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 28 Feb 2017 16:15:39 -0800 Subject: [PATCH 313/925] Remove unneeded includes --- atom/browser/native_window.cc | 1 - atom/browser/native_window.h | 1 - 2 files changed, 2 deletions(-) diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index 21d442c920..49c08f5d4b 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -33,7 +33,6 @@ #include "content/public/browser/render_widget_host_view.h" #include "content/public/common/content_switches.h" #include "ipc/ipc_message_macros.h" -#include "native_mate/constructor.h" #include "native_mate/dictionary.h" #include "third_party/skia/include/core/SkRegion.h" #include "ui/gfx/codec/png_codec.h" diff --git a/atom/browser/native_window.h b/atom/browser/native_window.h index bb94127b7a..69b2f50962 100644 --- a/atom/browser/native_window.h +++ b/atom/browser/native_window.h @@ -21,7 +21,6 @@ #include "content/public/browser/web_contents_observer.h" #include "content/public/browser/web_contents_user_data.h" #include "extensions/browser/app_window/size_constraints.h" -#include "native_mate/constructor.h" #include "native_mate/persistent_dictionary.h" #include "ui/gfx/image/image.h" #include "ui/gfx/image/image_skia.h" From ec500b285238dd46cf484e0dfb7499911e23b0ff Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 28 Feb 2017 16:44:03 -0800 Subject: [PATCH 314/925] Use scoped_nsobject to match Chrome implementation --- atom/browser/native_window_mac.mm | 5 ++- atom/browser/ui/cocoa/atom_touch_bar.h | 3 +- atom/browser/ui/cocoa/atom_touch_bar.mm | 45 +++++++++++++------------ 3 files changed, 30 insertions(+), 23 deletions(-) diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index d93b136458..66fc97480c 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -383,7 +383,10 @@ bool ScopedDisableResize::disable_resize_ = false; - (nullable NSTouchBarItem*)touchBar:(NSTouchBar*)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { - return [atom_touch_bar_ makeItemForIdentifier:identifier]; + if (touchBar) + return [atom_touch_bar_ makeItemForIdentifier:identifier]; + else + return nil; } // NSWindow overrides. diff --git a/atom/browser/ui/cocoa/atom_touch_bar.h b/atom/browser/ui/cocoa/atom_touch_bar.h index 2efabe6cf0..50f03cda62 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.h +++ b/atom/browser/ui/cocoa/atom_touch_bar.h @@ -13,13 +13,14 @@ #include "atom/browser/native_window.h" #include "atom/browser/ui/cocoa/touch_bar_forward_declarations.h" +#include "base/mac/scoped_nsobject.h" #include "native_mate/constructor.h" #include "native_mate/persistent_dictionary.h" @interface AtomTouchBar : NSObject { @protected std::map item_id_map; - std::map item_map; + std::map> item_map; id delegate_; atom::NativeWindow* window_; } diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index b2053f7df0..c7a002081e 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -38,10 +38,11 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide } - (NSTouchBar*)touchBarFromItemIdentifiers:(NSMutableArray*)items { - NSTouchBar* bar = [[NSClassFromString(@"NSTouchBar") alloc] init]; - bar.delegate = delegate_; - bar.defaultItemIdentifiers = items; - return bar; + base::scoped_nsobject bar( + [[NSClassFromString(@"NSTouchBar") alloc] init]); + [bar setDelegate:delegate_]; + [bar setDefaultItemIdentifiers:items]; + return bar.autorelease(); } - (NSMutableArray*)identifierArrayFromDicts:(const std::vector&)dicts { @@ -51,7 +52,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide std::string type; std::string item_id; if (item.Get("type", &type) && item.Get("id", &item_id)) { - item_id_map.insert(make_pair(item_id, item)); + item_id_map[item_id] = item; if (type == "button") { [idents addObject:[NSString stringWithFormat:@"%@%@", ButtonIdentifier, base::SysUTF8ToNSString(item_id)]]; } else if (type == "label") { @@ -73,31 +74,33 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide } - (NSTouchBarItem*)makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { - NSTouchBarItem* item = nil; - NSString* id = nil; + base::scoped_nsobject item; + NSString* item_id = nil; + if ([identifier hasPrefix:ButtonIdentifier]) { - id = [self idFromIdentifier:identifier withPrefix:ButtonIdentifier]; - item = [self makeButtonForID:id withIdentifier:identifier]; + item_id = [self idFromIdentifier:identifier withPrefix:ButtonIdentifier]; + item.reset([self makeButtonForID:item_id withIdentifier:identifier]); } else if ([identifier hasPrefix:LabelIdentifier]) { - id = [self idFromIdentifier:identifier withPrefix:LabelIdentifier]; - item = [self makeLabelForID:id withIdentifier:identifier]; + item_id = [self idFromIdentifier:identifier withPrefix:LabelIdentifier]; + item.reset([self makeLabelForID:item_id withIdentifier:identifier]); } else if ([identifier hasPrefix:ColorPickerIdentifier]) { - id = [self idFromIdentifier:identifier withPrefix:ColorPickerIdentifier]; - item = [self makeColorPickerForID:id withIdentifier:identifier]; + item_id = [self idFromIdentifier:identifier withPrefix:ColorPickerIdentifier]; + item.reset([self makeColorPickerForID:item_id withIdentifier:identifier]); } else if ([identifier hasPrefix:SliderIdentifier]) { - id = [self idFromIdentifier:identifier withPrefix:SliderIdentifier]; - item = [self makeSliderForID:id withIdentifier:identifier]; + item_id = [self idFromIdentifier:identifier withPrefix:SliderIdentifier]; + item.reset([self makeSliderForID:item_id withIdentifier:identifier]); } else if ([identifier hasPrefix:PopOverIdentifier]) { - id = [self idFromIdentifier:identifier withPrefix:PopOverIdentifier]; - item = [self makePopoverForID:id withIdentifier:identifier]; + item_id = [self idFromIdentifier:identifier withPrefix:PopOverIdentifier]; + item.reset([self makePopoverForID:item_id withIdentifier:identifier]); } else if ([identifier hasPrefix:GroupIdentifier]) { - id = [self idFromIdentifier:identifier withPrefix:GroupIdentifier]; - item = [self makeGroupForID:id withIdentifier:identifier]; + item_id = [self idFromIdentifier:identifier withPrefix:GroupIdentifier]; + item.reset([self makeGroupForID:item_id withIdentifier:identifier]); } - item_map.insert(make_pair(std::string([id UTF8String]), item)); + if (item_id) + item_map[[item_id UTF8String]] = item; - return item; + return item.autorelease(); } From b959f782f6bf83fc74b2bab40f97243217f0c741 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 28 Feb 2017 17:12:35 -0800 Subject: [PATCH 315/925] Rename instance variable maps --- atom/browser/native_window_mac.mm | 2 +- atom/browser/ui/cocoa/atom_touch_bar.h | 20 ++-- atom/browser/ui/cocoa/atom_touch_bar.mm | 136 ++++++++++++------------ 3 files changed, 79 insertions(+), 79 deletions(-) diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 66fc97480c..71fdef9915 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -378,7 +378,7 @@ bool ScopedDisableResize::disable_resize_ = false; - (NSTouchBar*)makeTouchBar { atom_touch_bar_.reset([[AtomTouchBar alloc] initWithDelegate:self window:shell_]); - return [atom_touch_bar_ makeTouchBarFromItemOptions:shell_->GetTouchBarItems()]; + return [atom_touch_bar_ makeTouchBarFromSettings:shell_->GetTouchBarItems()]; } - (nullable NSTouchBarItem*)touchBar:(NSTouchBar*)touchBar diff --git a/atom/browser/ui/cocoa/atom_touch_bar.h b/atom/browser/ui/cocoa/atom_touch_bar.h index 50f03cda62..ff70f1274f 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.h +++ b/atom/browser/ui/cocoa/atom_touch_bar.h @@ -19,8 +19,8 @@ @interface AtomTouchBar : NSObject { @protected - std::map item_id_map; - std::map> item_map; + std::map settings_; + std::map> items_; id delegate_; atom::NativeWindow* window_; } @@ -28,14 +28,14 @@ - (id)initWithDelegate:(id)delegate window:(atom::NativeWindow*)window; -- (NSTouchBar*)makeTouchBarFromItemOptions:(const std::vector&)item_options; +- (NSTouchBar*)makeTouchBarFromSettings:(const std::vector&)settings; - (NSTouchBar*)touchBarFromItemIdentifiers:(NSMutableArray*)items; -- (NSMutableArray*)identifierArrayFromDicts:(const std::vector&)dicts; +- (NSMutableArray*)identifiersFromSettings:(const std::vector&)settings; - (void)refreshTouchBarItem:(const std::string&)item_id; - (void)clear; - (NSString*)idFromIdentifier:(NSString*)identifier withPrefix:(NSString*)prefix; -- (bool)hasItemWithID:(const std::string&)id; +- (bool)hasItemWithID:(const std::string&)item_id; - (NSColor*)colorFromHexColorString:(const std::string&)colorString; // Selector actions @@ -53,11 +53,11 @@ - (NSTouchBarItem*)makeGroupForID:(NSString*)id withIdentifier:(NSString*)identifier; // Helpers to update touch bar items -- (void)updateButton:(NSCustomTouchBarItem*)item withOptions:(const mate::PersistentDictionary&)options; -- (void)updateLabel:(NSCustomTouchBarItem*)item withOptions:(const mate::PersistentDictionary&)options; -- (void)updateColorPicker:(NSColorPickerTouchBarItem*)item withOptions:(const mate::PersistentDictionary&)options; -- (void)updateSlider:(NSSliderTouchBarItem*)item withOptions:(const mate::PersistentDictionary&)options; -- (void)updatePopover:(NSPopoverTouchBarItem*)item withOptions:(const mate::PersistentDictionary&)options; +- (void)updateButton:(NSCustomTouchBarItem*)item withSettings:(const mate::PersistentDictionary&)settings; +- (void)updateLabel:(NSCustomTouchBarItem*)item withSettings:(const mate::PersistentDictionary&)settings; +- (void)updateColorPicker:(NSColorPickerTouchBarItem*)item withSettings:(const mate::PersistentDictionary&)settings; +- (void)updateSlider:(NSSliderTouchBarItem*)item withSettings:(const mate::PersistentDictionary&)settings; +- (void)updatePopover:(NSPopoverTouchBarItem*)item withSettings:(const mate::PersistentDictionary&)settings; @end diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index c7a002081e..33c5201da0 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -29,11 +29,11 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide } - (void)clear { - item_id_map.clear(); + settings_.clear(); } -- (NSTouchBar*)makeTouchBarFromItemOptions:(const std::vector&)item_options { - NSMutableArray* identifiers = [self identifierArrayFromDicts:item_options]; +- (NSTouchBar*)makeTouchBarFromSettings:(const std::vector&)settings { + NSMutableArray* identifiers = [self identifiersFromSettings:settings]; return [self touchBarFromItemIdentifiers:identifiers]; } @@ -45,32 +45,32 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide return bar.autorelease(); } -- (NSMutableArray*)identifierArrayFromDicts:(const std::vector&)dicts { - NSMutableArray* idents = [[NSMutableArray alloc] init]; +- (NSMutableArray*)identifiersFromSettings:(const std::vector&)dicts { + NSMutableArray* identifiers = [[NSMutableArray alloc] init]; for (const auto& item : dicts) { std::string type; std::string item_id; if (item.Get("type", &type) && item.Get("id", &item_id)) { - item_id_map[item_id] = item; + settings_[item_id] = item; if (type == "button") { - [idents addObject:[NSString stringWithFormat:@"%@%@", ButtonIdentifier, base::SysUTF8ToNSString(item_id)]]; + [identifiers addObject:[NSString stringWithFormat:@"%@%@", ButtonIdentifier, base::SysUTF8ToNSString(item_id)]]; } else if (type == "label") { - [idents addObject:[NSString stringWithFormat:@"%@%@", LabelIdentifier, base::SysUTF8ToNSString(item_id)]]; + [identifiers addObject:[NSString stringWithFormat:@"%@%@", LabelIdentifier, base::SysUTF8ToNSString(item_id)]]; } else if (type == "colorpicker") { - [idents addObject:[NSString stringWithFormat:@"%@%@", ColorPickerIdentifier, base::SysUTF8ToNSString(item_id)]]; + [identifiers addObject:[NSString stringWithFormat:@"%@%@", ColorPickerIdentifier, base::SysUTF8ToNSString(item_id)]]; } else if (type == "slider") { - [idents addObject:[NSString stringWithFormat:@"%@%@", SliderIdentifier, base::SysUTF8ToNSString(item_id)]]; + [identifiers addObject:[NSString stringWithFormat:@"%@%@", SliderIdentifier, base::SysUTF8ToNSString(item_id)]]; } else if (type == "popover") { - [idents addObject:[NSString stringWithFormat:@"%@%@", PopOverIdentifier, base::SysUTF8ToNSString(item_id)]]; + [identifiers addObject:[NSString stringWithFormat:@"%@%@", PopOverIdentifier, base::SysUTF8ToNSString(item_id)]]; } else if (type == "group") { - [idents addObject:[NSString stringWithFormat:@"%@%@", GroupIdentifier, base::SysUTF8ToNSString(item_id)]]; + [identifiers addObject:[NSString stringWithFormat:@"%@%@", GroupIdentifier, base::SysUTF8ToNSString(item_id)]]; } } } - [idents addObject:NSTouchBarItemIdentifierOtherItemsProxy]; + [identifiers addObject:NSTouchBarItemIdentifierOtherItemsProxy]; - return idents; + return identifiers; } - (NSTouchBarItem*)makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { @@ -98,35 +98,35 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide } if (item_id) - item_map[[item_id UTF8String]] = item; + items_[[item_id UTF8String]] = item; return item.autorelease(); } - (void)refreshTouchBarItem:(const std::string&)item_id { - if (item_map.find(item_id) == item_map.end()) return; + if (items_.find(item_id) == items_.end()) return; if (![self hasItemWithID:item_id]) return; - mate::PersistentDictionary options = item_id_map[item_id]; + mate::PersistentDictionary settings = settings_[item_id]; std::string item_type; - options.Get("type", &item_type); + settings.Get("type", &item_type); if (item_type == "button") { - [self updateButton:(NSCustomTouchBarItem*)item_map[item_id] - withOptions:options]; + [self updateButton:(NSCustomTouchBarItem*)items_[item_id] + withSettings:settings]; } else if (item_type == "label") { - [self updateLabel:(NSCustomTouchBarItem*)item_map[item_id] - withOptions:options]; + [self updateLabel:(NSCustomTouchBarItem*)items_[item_id] + withSettings:settings]; } else if (item_type == "colorpicker") { - [self updateColorPicker:(NSColorPickerTouchBarItem*)item_map[item_id] - withOptions:options]; + [self updateColorPicker:(NSColorPickerTouchBarItem*)items_[item_id] + withSettings:settings]; } else if (item_type == "slider") { - [self updateSlider:(NSSliderTouchBarItem*)item_map[item_id] - withOptions:options]; + [self updateSlider:(NSSliderTouchBarItem*)items_[item_id] + withSettings:settings]; } else if (item_type == "popover") { - [self updatePopover:(NSPopoverTouchBarItem*)item_map[item_id] - withOptions:options]; + [self updatePopover:(NSPopoverTouchBarItem*)items_[item_id] + withSettings:settings]; } } @@ -161,8 +161,8 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide return [identifier substringFromIndex:[prefix length]]; } -- (bool)hasItemWithID:(const std::string&)id { - return item_id_map.find(id) != item_id_map.end(); +- (bool)hasItemWithID:(const std::string&)item_id { + return settings_.find(item_id) != settings_.end(); } - (NSColor*)colorFromHexColorString:(const std::string&)colorString { @@ -175,38 +175,38 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide std::string s_id([id UTF8String]); if (![self hasItemWithID:s_id]) return nil; - mate::PersistentDictionary options = item_id_map[s_id]; + mate::PersistentDictionary settings = settings_[s_id]; NSCustomTouchBarItem* item = [[NSClassFromString(@"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]; NSButton* button = [NSButton buttonWithTitle:@"" target:self action:@selector(buttonAction:)]; button.tag = [id floatValue]; item.view = button; - [self updateButton:item withOptions:options]; + [self updateButton:item withSettings:settings]; return item; } - (void)updateButton:(NSCustomTouchBarItem*)item - withOptions:(const mate::PersistentDictionary&)options { + withSettings:(const mate::PersistentDictionary&)settings { NSButton* button = (NSButton*)item.view; std::string customizationLabel; - if (options.Get("customizationLabel", &customizationLabel)) { + if (settings.Get("customizationLabel", &customizationLabel)) { item.customizationLabel = base::SysUTF8ToNSString(customizationLabel); } std::string backgroundColor; - if (options.Get("backgroundColor", &backgroundColor)) { + if (settings.Get("backgroundColor", &backgroundColor)) { button.bezelColor = [self colorFromHexColorString:backgroundColor]; } std::string label; - if (options.Get("label", &label)) { + if (settings.Get("label", &label)) { button.title = base::SysUTF8ToNSString(label); } std::string labelColor; - if (!label.empty() && options.Get("labelColor", &labelColor)) { + if (!label.empty() && settings.Get("labelColor", &labelColor)) { NSMutableAttributedString* attrTitle = [[[NSMutableAttributedString alloc] initWithString:base::SysUTF8ToNSString(label)] autorelease]; NSRange range = NSMakeRange(0, [attrTitle length]); [attrTitle addAttribute:NSForegroundColorAttributeName @@ -217,7 +217,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide } gfx::Image image; - if (options.Get("image", &image)) { + if (settings.Get("image", &image)) { button.image = image.AsNSImage(); } } @@ -227,23 +227,23 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide std::string s_id([id UTF8String]); if (![self hasItemWithID:s_id]) return nil; - mate::PersistentDictionary item = item_id_map[s_id]; + mate::PersistentDictionary item = settings_[s_id]; NSCustomTouchBarItem* customItem = [[NSClassFromString(@"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]; customItem.view = [NSTextField labelWithString:@""]; - [self updateLabel:customItem withOptions:item]; + [self updateLabel:customItem withSettings:item]; return customItem; } - (void)updateLabel:(NSCustomTouchBarItem*)item - withOptions:(const mate::PersistentDictionary&)options { + withSettings:(const mate::PersistentDictionary&)settings { std::string label; - options.Get("label", &label); + settings.Get("label", &label); NSTextField* text_field = (NSTextField*)item.view; text_field.stringValue = base::SysUTF8ToNSString(label); std::string customizationLabel; - if (options.Get("customizationLabel", &customizationLabel)) { + if (settings.Get("customizationLabel", &customizationLabel)) { item.customizationLabel = base::SysUTF8ToNSString(customizationLabel); } } @@ -253,18 +253,18 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide std::string s_id([id UTF8String]); if (![self hasItemWithID:s_id]) return nil; - mate::PersistentDictionary options = item_id_map[s_id]; + mate::PersistentDictionary settings = settings_[s_id]; NSColorPickerTouchBarItem* item = [[NSClassFromString(@"NSColorPickerTouchBarItem") alloc] initWithIdentifier:identifier]; item.target = self; item.action = @selector(colorPickerAction:); - [self updateColorPicker:item withOptions:options]; + [self updateColorPicker:item withSettings:settings]; return item; } - (void)updateColorPicker:(NSColorPickerTouchBarItem*)item - withOptions:(const mate::PersistentDictionary&)options { + withSettings:(const mate::PersistentDictionary&)settings { std::string customizationLabel; - if (options.Get("customizationLabel", &customizationLabel)) { + if (settings.Get("customizationLabel", &customizationLabel)) { item.customizationLabel = base::SysUTF8ToNSString(customizationLabel); } } @@ -274,31 +274,31 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide std::string s_id([id UTF8String]); if (![self hasItemWithID:s_id]) return nil; - mate::PersistentDictionary options = item_id_map[s_id]; + mate::PersistentDictionary settings = settings_[s_id]; NSSliderTouchBarItem* item = [[NSClassFromString(@"NSSliderTouchBarItem") alloc] initWithIdentifier:identifier]; item.target = self; item.action = @selector(sliderAction:); - [self updateSlider:item withOptions:options]; + [self updateSlider:item withSettings:settings]; return item; } - (void)updateSlider:(NSSliderTouchBarItem*)item - withOptions:(const mate::PersistentDictionary&)options { + withSettings:(const mate::PersistentDictionary&)settings { std::string customizationLabel; - if (options.Get("customizationLabel", &customizationLabel)) { + if (settings.Get("customizationLabel", &customizationLabel)) { item.customizationLabel = base::SysUTF8ToNSString(customizationLabel); } std::string label; - options.Get("label", &label); + settings.Get("label", &label); item.label = base::SysUTF8ToNSString(label); int maxValue = 100; int minValue = 0; int initialValue = 50; - options.Get("minValue", &minValue); - options.Get("maxValue", &maxValue); - options.Get("initialValue", &initialValue); + settings.Get("minValue", &minValue); + settings.Get("maxValue", &maxValue); + settings.Get("initialValue", &initialValue); item.slider.minValue = minValue; item.slider.maxValue = maxValue; @@ -310,36 +310,36 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide std::string s_id([id UTF8String]); if (![self hasItemWithID:s_id]) return nil; - mate::PersistentDictionary options = item_id_map[s_id]; + mate::PersistentDictionary settings = settings_[s_id]; NSPopoverTouchBarItem* item = [[NSClassFromString(@"NSPopoverTouchBarItem") alloc] initWithIdentifier:identifier]; - [self updatePopover:item withOptions:options]; + [self updatePopover:item withSettings:settings]; return item; } - (void)updatePopover:(NSPopoverTouchBarItem*)item - withOptions:(const mate::PersistentDictionary&)options { + withSettings:(const mate::PersistentDictionary&)settings { std::string customizationLabel; - if (options.Get("customizationLabel", &customizationLabel)) { + if (settings.Get("customizationLabel", &customizationLabel)) { item.customizationLabel = base::SysUTF8ToNSString(customizationLabel); } std::string label; gfx::Image image; - if (options.Get("label", &label)) { + if (settings.Get("label", &label)) { item.collapsedRepresentationLabel = base::SysUTF8ToNSString(label); - } else if (options.Get("image", &image)) { + } else if (settings.Get("image", &image)) { item.collapsedRepresentationImage = image.AsNSImage(); } bool showCloseButton; - if (options.Get("showCloseButton", &showCloseButton)) { + if (settings.Get("showCloseButton", &showCloseButton)) { item.showsCloseButton = showCloseButton; } mate::PersistentDictionary child; std::vector items; - if (options.Get("child", &child) && child.Get("ordereredItems", &items)) { - item.popoverTouchBar = [self touchBarFromItemIdentifiers:[self identifierArrayFromDicts:items]]; + if (settings.Get("child", &child) && child.Get("ordereredItems", &items)) { + item.popoverTouchBar = [self touchBarFromItemIdentifiers:[self identifiersFromSettings:items]]; } } @@ -347,15 +347,15 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide withIdentifier:(NSString*)identifier { std::string s_id([id UTF8String]); if (![self hasItemWithID:s_id]) return nil; - mate::PersistentDictionary options = item_id_map[s_id]; + mate::PersistentDictionary settings = settings_[s_id]; mate::PersistentDictionary child; - if (!options.Get("child", &child)) return nil; + if (!settings.Get("child", &child)) return nil; std::vector items; if (!child.Get("ordereredItems", &items)) return nil; NSMutableArray* generatedItems = [[NSMutableArray alloc] init]; - NSMutableArray* identList = [self identifierArrayFromDicts:items]; + NSMutableArray* identList = [self identifiersFromSettings:items]; for (NSUInteger i = 0; i < [identList count]; i++) { if ([identList objectAtIndex:i] != NSTouchBarItemIdentifierOtherItemsProxy) { NSTouchBarItem* generatedItem = [self makeItemForIdentifier:[identList objectAtIndex:i]]; @@ -367,7 +367,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide NSGroupTouchBarItem* item = [NSClassFromString(@"NSGroupTouchBarItem") groupItemWithIdentifier:identifier items:generatedItems]; std::string customizationLabel; - if (options.Get("customizationLabel", &customizationLabel)) { + if (settings.Get("customizationLabel", &customizationLabel)) { item.customizationLabel = base::SysUTF8ToNSString(customizationLabel);; } return item; From 347d472841f30d48b90c5d63e6bf9f227b0248fc Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 28 Feb 2017 17:15:30 -0800 Subject: [PATCH 316/925] Remove unused clear method --- atom/browser/ui/cocoa/atom_touch_bar.h | 4 +--- atom/browser/ui/cocoa/atom_touch_bar.mm | 4 ---- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/atom/browser/ui/cocoa/atom_touch_bar.h b/atom/browser/ui/cocoa/atom_touch_bar.h index ff70f1274f..d0146ec5f9 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.h +++ b/atom/browser/ui/cocoa/atom_touch_bar.h @@ -25,14 +25,12 @@ atom::NativeWindow* window_; } -- (id)initWithDelegate:(id)delegate - window:(atom::NativeWindow*)window; +- (id)initWithDelegate:(id)delegate window:(atom::NativeWindow*)window; - (NSTouchBar*)makeTouchBarFromSettings:(const std::vector&)settings; - (NSTouchBar*)touchBarFromItemIdentifiers:(NSMutableArray*)items; - (NSMutableArray*)identifiersFromSettings:(const std::vector&)settings; - (void)refreshTouchBarItem:(const std::string&)item_id; -- (void)clear; - (NSString*)idFromIdentifier:(NSString*)identifier withPrefix:(NSString*)prefix; - (bool)hasItemWithID:(const std::string&)item_id; diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index 33c5201da0..f7656fa803 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -28,10 +28,6 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide return self; } -- (void)clear { - settings_.clear(); -} - - (NSTouchBar*)makeTouchBarFromSettings:(const std::vector&)settings { NSMutableArray* identifiers = [self identifiersFromSettings:settings]; return [self touchBarFromItemIdentifiers:identifiers]; From d5dbe3676e993c38e09e6667acc0e46579c12568 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 1 Mar 2017 10:55:28 -0800 Subject: [PATCH 317/925] Add window helpers to TouchBar class --- lib/browser/api/browser-window.js | 47 ++++++++++++------------------- lib/browser/api/touch-bar.js | 37 ++++++++++++++++++++++-- 2 files changed, 53 insertions(+), 31 deletions(-) diff --git a/lib/browser/api/browser-window.js b/lib/browser/api/browser-window.js index 27f425c7ae..aa3872870e 100644 --- a/lib/browser/api/browser-window.js +++ b/lib/browser/api/browser-window.js @@ -131,15 +131,6 @@ BrowserWindow.prototype._init = function () { return this.webContents.devToolsWebContents } }) - - // Proxy TouchBar events - this.on('-touch-bar-interaction', (event, id, details) => { - if (this._touchBar != null) { - this._touchBar.emit('interaction', id, details) - } - }) - - this._touchBarListener = this._refreshTouchBarItem.bind(this) } BrowserWindow.getFocusedWindow = () => { @@ -204,27 +195,25 @@ Object.assign(BrowserWindow.prototype, { }, capturePage (...args) { return this.webContents.capturePage(...args) + }, + + // TouchBar API + setTouchBar (touchBar) { + if (touchBar == null) { + if (this._touchBar != null) { + this._touchBar._removeFromWindow(this) + } + this._destroyTouchBar() + return + } + + if (Array.isArray(touchBar)) { + touchBar = new TouchBar(touchBar) + } + + this._touchBar = touchBar + this._touchBar._addToWindow(this) } }) -// TouchBar API -BrowserWindow.prototype.setTouchBar = function (touchBar) { - if (touchBar == null) { - if (this._touchBar != null) { - this._touchBar.removeListener('change', this._touchBarListener) - this._touchBar = null - } - this._destroyTouchBar() - return - } - - if (Array.isArray(touchBar)) { - touchBar = new TouchBar(touchBar) - } - - this._touchBar = touchBar - this._touchBar.on('change', this._touchBarListener) - this._setTouchBar(touchBar.ordereredItems) -} - module.exports = BrowserWindow diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index 237ef716fa..20285246c3 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -10,6 +10,7 @@ class TouchBar extends EventEmitter { throw new Error('The items object provided has to be an array') } + this.windowListeners = {} this.items = {} this.ordereredItems = [] const registerItem = (item) => { @@ -28,13 +29,45 @@ class TouchBar extends EventEmitter { } registerItem(item) }) + } - this.on('interaction', (itemID, details) => { + // Called by BrowserWindow.setTouchBar + _addToWindow (window) { + const {id} = window + + // Already added to window + if (this.windowListeners.hasOwnProperty(id)) return + + const changeListener = (itemID) => { + window._refreshTouchBarItem(itemID) + } + this.on('change', changeListener) + + const interactionListener = (event, itemID, details) => { const item = this.items[itemID] if (item != null && item.onInteraction != null) { item.onInteraction(details) } - }) + } + window.on('-touch-bar-interaction', interactionListener) + + const removeListeners = () => { + this.removeListener('change', changeListener) + window.removeListener('-touch-bar-interaction', interactionListener) + window.removeListener('closed', removeListeners) + window._touchBar = null + delete this.windowListeners[id] + } + window.once('closed', removeListeners) + this.windowListeners[id] = removeListeners + + window._setTouchBar(this.ordereredItems) + } + + // Called by BrowserWindow.setTouchBar + _removeFromWindow (window) { + const removeListeners = this.windowListeners[window.id] + if (removeListeners != null) removeListeners() } } From 51f1c5a5576a0bef3f56e7f6d021ba335b1d2941 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 1 Mar 2017 11:05:34 -0800 Subject: [PATCH 318/925] Use SetTouchBar withe empty vector instead of DestroyTouchBar --- atom/browser/api/atom_api_window.cc | 7 +------ atom/browser/api/atom_api_window.h | 1 - atom/browser/native_window.cc | 3 --- atom/browser/native_window.h | 1 - atom/browser/native_window_mac.h | 1 - atom/browser/native_window_mac.mm | 5 ----- lib/browser/api/browser-window.js | 14 +++++++------- lib/browser/api/touch-bar.js | 4 +++- 8 files changed, 11 insertions(+), 25 deletions(-) diff --git a/atom/browser/api/atom_api_window.cc b/atom/browser/api/atom_api_window.cc index fbfba8a9e6..6748c9c0dd 100644 --- a/atom/browser/api/atom_api_window.cc +++ b/atom/browser/api/atom_api_window.cc @@ -845,10 +845,6 @@ void Window::SetVibrancy(mate::Arguments* args) { window_->SetVibrancy(type); } -void Window::DestroyTouchBar() { - window_->DestroyTouchBar(); -} - void Window::SetTouchBar(const std::vector& items) { window_->SetTouchBar(items); } @@ -977,8 +973,7 @@ void Window::BuildPrototype(v8::Isolate* isolate, .SetMethod("setAutoHideCursor", &Window::SetAutoHideCursor) #endif .SetMethod("setVibrancy", &Window::SetVibrancy) - .SetMethod("_destroyTouchBar", &Window::DestroyTouchBar) - .SetMethod("_setTouchBar", &Window::SetTouchBar) + .SetMethod("_setTouchBarItems", &Window::SetTouchBar) .SetMethod("_refreshTouchBarItem", &Window::RefreshTouchBarItem) #if defined(OS_WIN) .SetMethod("hookWindowMessage", &Window::HookWindowMessage) diff --git a/atom/browser/api/atom_api_window.h b/atom/browser/api/atom_api_window.h index b8060c3f47..f30baf79d4 100644 --- a/atom/browser/api/atom_api_window.h +++ b/atom/browser/api/atom_api_window.h @@ -206,7 +206,6 @@ class Window : public mate::TrackableObject, void SetAutoHideCursor(bool auto_hide); void SetVibrancy(mate::Arguments* args); - void DestroyTouchBar(); void SetTouchBar(const std::vector& items); void RefreshTouchBarItem(const std::string& item_id); diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index 49c08f5d4b..90a823f80b 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -340,9 +340,6 @@ void NativeWindow::SetAutoHideCursor(bool auto_hide) { void NativeWindow::SetVibrancy(const std::string& filename) { } -void NativeWindow::DestroyTouchBar() { -} - void NativeWindow::SetTouchBar( const std::vector& items) { } diff --git a/atom/browser/native_window.h b/atom/browser/native_window.h index 69b2f50962..941f5849a6 100644 --- a/atom/browser/native_window.h +++ b/atom/browser/native_window.h @@ -171,7 +171,6 @@ class NativeWindow : public base::SupportsUserData, virtual void SetVibrancy(const std::string& type); // Touchbar API - virtual void DestroyTouchBar(); virtual void SetTouchBar( const std::vector& items); virtual void RefreshTouchBarItem(const std::string& item_id); diff --git a/atom/browser/native_window_mac.h b/atom/browser/native_window_mac.h index 388ea282c0..03430af57c 100644 --- a/atom/browser/native_window_mac.h +++ b/atom/browser/native_window_mac.h @@ -100,7 +100,6 @@ class NativeWindowMac : public NativeWindow, void SetAutoHideCursor(bool auto_hide) override; void SetVibrancy(const std::string& type) override; - void DestroyTouchBar() override; void SetTouchBar( const std::vector& items) override; void RefreshTouchBarItem(const std::string& item_id) override; diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 71fdef9915..b3ab36ed31 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -1372,11 +1372,6 @@ void NativeWindowMac::SetVibrancy(const std::string& type) { [effect_view setMaterial:vibrancyType]; } -void NativeWindowMac::DestroyTouchBar() { - touch_bar_items_.clear(); - [window_ resetTouchBar]; -} - void NativeWindowMac::SetTouchBar( const std::vector& items) { touch_bar_items_ = items; diff --git a/lib/browser/api/browser-window.js b/lib/browser/api/browser-window.js index aa3872870e..3c936d2af5 100644 --- a/lib/browser/api/browser-window.js +++ b/lib/browser/api/browser-window.js @@ -199,20 +199,20 @@ Object.assign(BrowserWindow.prototype, { // TouchBar API setTouchBar (touchBar) { + // This property is set from within TouchBar + if (this._touchBar != null) { + this._touchBar._removeFromWindow(this) + } + if (touchBar == null) { - if (this._touchBar != null) { - this._touchBar._removeFromWindow(this) - } - this._destroyTouchBar() + this._setTouchBarItems([]) return } if (Array.isArray(touchBar)) { touchBar = new TouchBar(touchBar) } - - this._touchBar = touchBar - this._touchBar._addToWindow(this) + touchBar._addToWindow(this) } }) diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index 20285246c3..6f6a31fd25 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -38,6 +38,8 @@ class TouchBar extends EventEmitter { // Already added to window if (this.windowListeners.hasOwnProperty(id)) return + window._touchBar = this + const changeListener = (itemID) => { window._refreshTouchBarItem(itemID) } @@ -61,7 +63,7 @@ class TouchBar extends EventEmitter { window.once('closed', removeListeners) this.windowListeners[id] = removeListeners - window._setTouchBar(this.ordereredItems) + window._setTouchBarItems(this.ordereredItems) } // Called by BrowserWindow.setTouchBar From f9dd91d54d29ca6c3866cd8a940dff01c2301163 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 1 Mar 2017 11:12:22 -0800 Subject: [PATCH 319/925] Add static helper to bind touch bar to window --- lib/browser/api/browser-window.js | 20 +++----------------- lib/browser/api/touch-bar.js | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/lib/browser/api/browser-window.js b/lib/browser/api/browser-window.js index 3c936d2af5..4b3f70139f 100644 --- a/lib/browser/api/browser-window.js +++ b/lib/browser/api/browser-window.js @@ -1,6 +1,7 @@ 'use strict' -const {ipcMain, TouchBar} = require('electron') +const electron = require('electron') +const {ipcMain} = electron const {EventEmitter} = require('events') const {BrowserWindow} = process.atomBinding('window') const v8Util = process.atomBinding('v8_util') @@ -196,23 +197,8 @@ Object.assign(BrowserWindow.prototype, { capturePage (...args) { return this.webContents.capturePage(...args) }, - - // TouchBar API setTouchBar (touchBar) { - // This property is set from within TouchBar - if (this._touchBar != null) { - this._touchBar._removeFromWindow(this) - } - - if (touchBar == null) { - this._setTouchBarItems([]) - return - } - - if (Array.isArray(touchBar)) { - touchBar = new TouchBar(touchBar) - } - touchBar._addToWindow(this) + electron.TouchBar._setOnWindow(touchBar, this) } }) diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index 6f6a31fd25..93cfb8988d 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -3,6 +3,24 @@ const {EventEmitter} = require('events') let itemIdIncrementor = 1 class TouchBar extends EventEmitter { + + // Bind a touch bar to a window + static _setOnWindow (touchBar, window) { + if (window._touchBar != null) { + window._touchBar._removeFromWindow(window) + } + + if (touchBar == null) { + window._setTouchBarItems([]) + return + } + + if (Array.isArray(touchBar)) { + touchBar = new TouchBar(touchBar) + } + touchBar._addToWindow(window) + } + constructor (items) { super() From f153d082975ef3018b08ce7585cd0fa3dea1abfb Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 1 Mar 2017 12:50:36 -0800 Subject: [PATCH 320/925] Support setting the initial/available colors --- atom/browser/ui/cocoa/atom_touch_bar.mm | 21 ++++++++++++++++++- .../ui/cocoa/touch_bar_forward_declarations.h | 1 + lib/browser/api/touch-bar.js | 8 ++++--- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index f7656fa803..0613b2afc3 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -245,7 +245,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide } - (NSTouchBarItem*)makeColorPickerForID:(NSString*)id - withIdentifier:(NSString*)identifier { + withIdentifier:(NSString*)identifier { std::string s_id([id UTF8String]); if (![self hasItemWithID:s_id]) return nil; @@ -253,6 +253,25 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide NSColorPickerTouchBarItem* item = [[NSClassFromString(@"NSColorPickerTouchBarItem") alloc] initWithIdentifier:identifier]; item.target = self; item.action = @selector(colorPickerAction:); + + std::string selectedColor; + if (settings.Get("selectedColor", &selectedColor)) { + item.color = [self colorFromHexColorString:selectedColor]; + } + + std::vector colors; + if (settings.Get("availableColors", &colors) && colors.size() > 0) { + NSColorList* color_list = [[[NSColorList alloc] initWithName:identifier] autorelease]; + for (size_t i = 0; i < colors.size(); ++i) { + [color_list insertColor:[self colorFromHexColorString:colors[i]] + key:base::SysUTF8ToNSString(colors[i]) + atIndex:i]; + } + item.colorList = color_list; + } + + item.showsAlpha = NO; + [self updateColorPicker:item withSettings:settings]; return item; } diff --git a/atom/browser/ui/cocoa/touch_bar_forward_declarations.h b/atom/browser/ui/cocoa/touch_bar_forward_declarations.h index e9a6e5525b..e4e2bc15a5 100644 --- a/atom/browser/ui/cocoa/touch_bar_forward_declarations.h +++ b/atom/browser/ui/cocoa/touch_bar_forward_declarations.h @@ -98,6 +98,7 @@ static const NSTouchBarItemIdentifier NSTouchBarItemIdentifierOtherItemsProxy = @property SEL action; @property(weak) id target; @property(copy) NSColor *color; +@property(strong) NSColorList *colorList; @end diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index 93cfb8988d..62008b70d8 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -1,6 +1,6 @@ const {EventEmitter} = require('events') -let itemIdIncrementor = 1 +let nextItemID = 1 class TouchBar extends EventEmitter { @@ -94,7 +94,7 @@ class TouchBar extends EventEmitter { class TouchBarItem extends EventEmitter { constructor (config) { super() - this.id = `${itemIdIncrementor++}` + this.id = `${nextItemID++}` } } @@ -113,6 +113,8 @@ TouchBar.ColorPicker = class TouchBarColorPicker extends TouchBarItem { constructor (config) { super(config) this.type = 'colorpicker' + this.availableColors = config.availableColors + this.selectedColor = config.selectedColor const {change} = config if (typeof change === 'function') { @@ -151,7 +153,7 @@ TouchBar.Label = class TouchBarLabel extends TouchBarItem { } } -TouchBar.PopOver = class TouchBarPopOver extends TouchBarItem { +TouchBar.Popover = class TouchBarPopover extends TouchBarItem { constructor (config) { super(config) this.type = 'popover' From 61aa9bbff43c4bfcf6ed245ed3b190fd12ee0f74 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 1 Mar 2017 14:10:52 -0800 Subject: [PATCH 321/925] Add support for spacer items --- atom/browser/ui/cocoa/atom_touch_bar.mm | 12 ++++++++++-- .../ui/cocoa/touch_bar_forward_declarations.h | 3 +++ lib/browser/api/touch-bar.js | 8 ++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index 0613b2afc3..469160c5a5 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -61,6 +61,16 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide [identifiers addObject:[NSString stringWithFormat:@"%@%@", PopOverIdentifier, base::SysUTF8ToNSString(item_id)]]; } else if (type == "group") { [identifiers addObject:[NSString stringWithFormat:@"%@%@", GroupIdentifier, base::SysUTF8ToNSString(item_id)]]; + } else if (type == "spacer") { + std::string size; + item.Get("size", &size); + if (size == "large") { + [identifiers addObject:NSTouchBarItemIdentifierFixedSpaceLarge]; + } else if (size == "flexible") { + [identifiers addObject:NSTouchBarItemIdentifierFlexibleSpace]; + } else { + [identifiers addObject:NSTouchBarItemIdentifierFixedSpaceSmall]; + } } } } @@ -270,8 +280,6 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide item.colorList = color_list; } - item.showsAlpha = NO; - [self updateColorPicker:item withSettings:settings]; return item; } diff --git a/atom/browser/ui/cocoa/touch_bar_forward_declarations.h b/atom/browser/ui/cocoa/touch_bar_forward_declarations.h index e4e2bc15a5..68693cb593 100644 --- a/atom/browser/ui/cocoa/touch_bar_forward_declarations.h +++ b/atom/browser/ui/cocoa/touch_bar_forward_declarations.h @@ -27,6 +27,9 @@ typedef NSString* NSTouchBarCustomizationIdentifier; static const NSTouchBarItemIdentifier NSTouchBarItemIdentifierFixedSpaceSmall = @"NSTouchBarItemIdentifierFixedSpaceSmall"; +static const NSTouchBarItemIdentifier NSTouchBarItemIdentifierFixedSpaceLarge = + @"NSTouchBarItemIdentifierFixedSpaceLarge"; + static const NSTouchBarItemIdentifier NSTouchBarItemIdentifierFlexibleSpace = @"NSTouchBarItemIdentifierFlexibleSpace"; diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index 62008b70d8..dd8e89f4c2 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -153,6 +153,14 @@ TouchBar.Label = class TouchBarLabel extends TouchBarItem { } } +TouchBar.Spacer = class TouchBarSpacer extends TouchBarItem { + constructor (config) { + super(config) + this.type = 'spacer' + this.size = config.size + } +} + TouchBar.Popover = class TouchBarPopover extends TouchBarItem { constructor (config) { super(config) From 5f9e9d4b368d3750b582c941f10bf7e47186fbdd Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 1 Mar 2017 14:54:43 -0800 Subject: [PATCH 322/925] Add move live updating properties --- atom/browser/ui/cocoa/atom_touch_bar.mm | 80 ++++++------------------- lib/browser/api/touch-bar.js | 57 +++++++++++------- 2 files changed, 53 insertions(+), 84 deletions(-) diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index 469160c5a5..8a746ccbe2 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -196,11 +196,6 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide withSettings:(const mate::PersistentDictionary&)settings { NSButton* button = (NSButton*)item.view; - std::string customizationLabel; - if (settings.Get("customizationLabel", &customizationLabel)) { - item.customizationLabel = base::SysUTF8ToNSString(customizationLabel); - } - std::string backgroundColor; if (settings.Get("backgroundColor", &backgroundColor)) { button.bezelColor = [self colorFromHexColorString:backgroundColor]; @@ -211,17 +206,6 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide button.title = base::SysUTF8ToNSString(label); } - std::string labelColor; - if (!label.empty() && settings.Get("labelColor", &labelColor)) { - NSMutableAttributedString* attrTitle = [[[NSMutableAttributedString alloc] initWithString:base::SysUTF8ToNSString(label)] autorelease]; - NSRange range = NSMakeRange(0, [attrTitle length]); - [attrTitle addAttribute:NSForegroundColorAttributeName - value:[self colorFromHexColorString:labelColor] - range:range]; - [attrTitle fixAttributesInRange:range]; - button.attributedTitle = attrTitle; - } - gfx::Image image; if (settings.Get("image", &image)) { button.image = image.AsNSImage(); @@ -247,11 +231,6 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide settings.Get("label", &label); NSTextField* text_field = (NSTextField*)item.view; text_field.stringValue = base::SysUTF8ToNSString(label); - - std::string customizationLabel; - if (settings.Get("customizationLabel", &customizationLabel)) { - item.customizationLabel = base::SysUTF8ToNSString(customizationLabel); - } } - (NSTouchBarItem*)makeColorPickerForID:(NSString*)id @@ -263,32 +242,26 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide NSColorPickerTouchBarItem* item = [[NSClassFromString(@"NSColorPickerTouchBarItem") alloc] initWithIdentifier:identifier]; item.target = self; item.action = @selector(colorPickerAction:); - - std::string selectedColor; - if (settings.Get("selectedColor", &selectedColor)) { - item.color = [self colorFromHexColorString:selectedColor]; - } - - std::vector colors; - if (settings.Get("availableColors", &colors) && colors.size() > 0) { - NSColorList* color_list = [[[NSColorList alloc] initWithName:identifier] autorelease]; - for (size_t i = 0; i < colors.size(); ++i) { - [color_list insertColor:[self colorFromHexColorString:colors[i]] - key:base::SysUTF8ToNSString(colors[i]) - atIndex:i]; - } - item.colorList = color_list; - } - [self updateColorPicker:item withSettings:settings]; return item; } - (void)updateColorPicker:(NSColorPickerTouchBarItem*)item withSettings:(const mate::PersistentDictionary&)settings { - std::string customizationLabel; - if (settings.Get("customizationLabel", &customizationLabel)) { - item.customizationLabel = base::SysUTF8ToNSString(customizationLabel); + std::vector colors; + if (settings.Get("availableColors", &colors) && colors.size() > 0) { + NSColorList* color_list = [[[NSColorList alloc] initWithName:@""] autorelease]; + for (size_t i = 0; i < colors.size(); ++i) { + [color_list insertColor:[self colorFromHexColorString:colors[i]] + key:base::SysUTF8ToNSString(colors[i]) + atIndex:i]; + } + item.colorList = color_list; + } + + std::string selectedColor; + if (settings.Get("selectedColor", &selectedColor)) { + item.color = [self colorFromHexColorString:selectedColor]; } } @@ -307,25 +280,20 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide - (void)updateSlider:(NSSliderTouchBarItem*)item withSettings:(const mate::PersistentDictionary&)settings { - std::string customizationLabel; - if (settings.Get("customizationLabel", &customizationLabel)) { - item.customizationLabel = base::SysUTF8ToNSString(customizationLabel); - } - std::string label; settings.Get("label", &label); item.label = base::SysUTF8ToNSString(label); int maxValue = 100; int minValue = 0; - int initialValue = 50; + int value = 50; settings.Get("minValue", &minValue); settings.Get("maxValue", &maxValue); - settings.Get("initialValue", &initialValue); + settings.Get("value", &value); item.slider.minValue = minValue; item.slider.maxValue = maxValue; - item.slider.doubleValue = initialValue; + item.slider.doubleValue = value; } - (NSTouchBarItem*)makePopoverForID:(NSString*)id @@ -341,11 +309,6 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide - (void)updatePopover:(NSPopoverTouchBarItem*)item withSettings:(const mate::PersistentDictionary&)settings { - std::string customizationLabel; - if (settings.Get("customizationLabel", &customizationLabel)) { - item.customizationLabel = base::SysUTF8ToNSString(customizationLabel); - } - std::string label; gfx::Image image; if (settings.Get("label", &label)) { @@ -387,13 +350,8 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide } } } - - NSGroupTouchBarItem* item = [NSClassFromString(@"NSGroupTouchBarItem") groupItemWithIdentifier:identifier items:generatedItems]; - std::string customizationLabel; - if (settings.Get("customizationLabel", &customizationLabel)) { - item.customizationLabel = base::SysUTF8ToNSString(customizationLabel);; - } - return item; + return [NSClassFromString(@"NSGroupTouchBarItem") groupItemWithIdentifier:identifier + items:generatedItems]; } @end diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index dd8e89f4c2..12138563c4 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -96,16 +96,33 @@ class TouchBarItem extends EventEmitter { super() this.id = `${nextItemID++}` } + + _addLiveProperty (name, initialValue) { + const privateName = `_${name}` + this[privateName] = initialValue + Object.defineProperty(this, name, { + get: function () { + return this[privateName] + }, + set: function (value) { + this[privateName] = value + this.emit('change') + }, + enumerable: true + }) + } } TouchBar.Button = class TouchBarButton extends TouchBarItem { constructor (config) { super(config) this.type = 'button' - this.label = config.label - this.backgroundColor = config.backgroundColor - this.labelColor = config.labelColor - this.onInteraction = config.click + const {click, label, backgroundColor} = config + this._addLiveProperty('label', label) + this._addLiveProperty('backgroundColor', backgroundColor) + if (typeof click === 'function') { + this.onInteraction = config.click + } } } @@ -113,12 +130,13 @@ TouchBar.ColorPicker = class TouchBarColorPicker extends TouchBarItem { constructor (config) { super(config) this.type = 'colorpicker' - this.availableColors = config.availableColors - this.selectedColor = config.selectedColor + const {availableColors, change, selectedColor} = config + this._addLiveProperty('availableColors', availableColors) + this._addLiveProperty('selectedColor', selectedColor) - const {change} = config if (typeof change === 'function') { this.onInteraction = (details) => { + this._selectedColor = details.color change(details.color) } } @@ -140,16 +158,7 @@ TouchBar.Label = class TouchBarLabel extends TouchBarItem { constructor (config) { super(config) this.type = 'label' - this._label = config.label - } - - set label (newLabel) { - this._label = newLabel - this.emit('change') - } - - get label () { - return this._label + this._addLiveProperty('label', config.label) } } @@ -157,7 +166,7 @@ TouchBar.Spacer = class TouchBarSpacer extends TouchBarItem { constructor (config) { super(config) this.type = 'spacer' - this.size = config.size + this._addLiveProperty('size', config.size) } } @@ -165,7 +174,7 @@ TouchBar.Popover = class TouchBarPopover extends TouchBarItem { constructor (config) { super(config) this.type = 'popover' - this.label = config.label + this._addLiveProperty('label', config.label) this.showCloseButton = config.showCloseButton this.child = config.items if (!(this.child instanceof TouchBar)) { @@ -178,13 +187,15 @@ TouchBar.Slider = class TouchBarSlider extends TouchBarItem { constructor (config) { super(config) this.type = 'slider' - this.minValue = config.minValue - this.maxValue = config.maxValue - this.initialValue = config.initialValue + const {change, label, minValue, maxValue, value} = config + this._addLiveProperty('label', label) + this._addLiveProperty('minValue', minValue) + this._addLiveProperty('maxValue', maxValue) + this._addLiveProperty('value', value) - const {change} = config if (typeof change === 'function') { this.onInteraction = (details) => { + this._value = details.value change(details.value) } } From 708ed9d1cd1c2dc1c3b272169915d4b60cd16125 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 1 Mar 2017 15:26:10 -0800 Subject: [PATCH 323/925] Store ordered settings in AtomTouchBar --- atom/browser/native_window_mac.h | 3 --- atom/browser/native_window_mac.mm | 17 +++++++---------- atom/browser/ui/cocoa/atom_touch_bar.h | 5 +++-- atom/browser/ui/cocoa/atom_touch_bar.mm | 8 +++++--- 4 files changed, 15 insertions(+), 18 deletions(-) diff --git a/atom/browser/native_window_mac.h b/atom/browser/native_window_mac.h index 03430af57c..bd34993fb1 100644 --- a/atom/browser/native_window_mac.h +++ b/atom/browser/native_window_mac.h @@ -103,7 +103,6 @@ class NativeWindowMac : public NativeWindow, void SetTouchBar( const std::vector& items) override; void RefreshTouchBarItem(const std::string& item_id) override; - std::vector GetTouchBarItems(); // content::RenderWidgetHost::InputEventObserver: void OnInputEvent(const blink::WebInputEvent& event) override; @@ -158,8 +157,6 @@ class NativeWindowMac : public NativeWindow, base::scoped_nsobject window_; base::scoped_nsobject window_delegate_; - std::vector touch_bar_items_; - // Event monitor for scroll wheel event. id wheel_event_monitor_; diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index b3ab36ed31..b59360ec37 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -353,7 +353,7 @@ bool ScopedDisableResize::disable_resize_ = false; - (void)setShell:(atom::NativeWindowMac*)shell; - (void)setEnableLargerThanScreen:(bool)enable; - (void)enableWindowButtonsOffset; -- (void)resetTouchBar; +- (void)resetTouchBar:(const std::vector&)settings; - (void)refreshTouchBarItem:(const std::string&)item_id; @end @@ -368,7 +368,10 @@ bool ScopedDisableResize::disable_resize_ = false; enable_larger_than_screen_ = enable; } -- (void)resetTouchBar { +- (void)resetTouchBar:(const std::vector&)settings { + atom_touch_bar_.reset([[AtomTouchBar alloc] initWithDelegate:self + window:shell_ + settings:settings]); self.touchBar = nil; } @@ -377,8 +380,7 @@ bool ScopedDisableResize::disable_resize_ = false; } - (NSTouchBar*)makeTouchBar { - atom_touch_bar_.reset([[AtomTouchBar alloc] initWithDelegate:self window:shell_]); - return [atom_touch_bar_ makeTouchBarFromSettings:shell_->GetTouchBarItems()]; + return [atom_touch_bar_ makeTouchBar]; } - (nullable NSTouchBarItem*)touchBar:(NSTouchBar*)touchBar @@ -1374,18 +1376,13 @@ void NativeWindowMac::SetVibrancy(const std::string& type) { void NativeWindowMac::SetTouchBar( const std::vector& items) { - touch_bar_items_ = items; - [window_ resetTouchBar]; + [window_ resetTouchBar:items]; } void NativeWindowMac::RefreshTouchBarItem(const std::string& item_id) { [window_ refreshTouchBarItem:item_id]; } -std::vector NativeWindowMac::GetTouchBarItems() { - return touch_bar_items_; -} - void NativeWindowMac::OnInputEvent(const blink::WebInputEvent& event) { switch (event.type) { case blink::WebInputEvent::GestureScrollBegin: diff --git a/atom/browser/ui/cocoa/atom_touch_bar.h b/atom/browser/ui/cocoa/atom_touch_bar.h index d0146ec5f9..738536678b 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.h +++ b/atom/browser/ui/cocoa/atom_touch_bar.h @@ -19,15 +19,16 @@ @interface AtomTouchBar : NSObject { @protected + std::vector ordered_settings_; std::map settings_; std::map> items_; id delegate_; atom::NativeWindow* window_; } -- (id)initWithDelegate:(id)delegate window:(atom::NativeWindow*)window; +- (id)initWithDelegate:(id)delegate window:(atom::NativeWindow*)window settings:(const std::vector&)settings; -- (NSTouchBar*)makeTouchBarFromSettings:(const std::vector&)settings; +- (NSTouchBar*)makeTouchBar; - (NSTouchBar*)touchBarFromItemIdentifiers:(NSMutableArray*)items; - (NSMutableArray*)identifiersFromSettings:(const std::vector&)settings; - (void)refreshTouchBarItem:(const std::string&)item_id; diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index 8a746ccbe2..277123f3bc 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -20,16 +20,18 @@ static NSTouchBarItemIdentifier PopOverIdentifier = @"com.electron.touchbar.popo static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slider."; - (id)initWithDelegate:(id)delegate - window:(atom::NativeWindow*)window { + window:(atom::NativeWindow*)window + settings:(const std::vector&)settings { if ((self = [super init])) { delegate_ = delegate; window_ = window; + ordered_settings_ = settings; } return self; } -- (NSTouchBar*)makeTouchBarFromSettings:(const std::vector&)settings { - NSMutableArray* identifiers = [self identifiersFromSettings:settings]; +- (NSTouchBar*)makeTouchBar { + NSMutableArray* identifiers = [self identifiersFromSettings:ordered_settings_]; return [self touchBarFromItemIdentifiers:identifiers]; } From 5fe3ac60faaa0400858c863c615d4d3183eaee6c Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 1 Mar 2017 15:29:34 -0800 Subject: [PATCH 324/925] Check that atom_touch_bar_ is set --- atom/browser/native_window_mac.mm | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index b59360ec37..12315ae255 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -376,16 +376,20 @@ bool ScopedDisableResize::disable_resize_ = false; } - (void)refreshTouchBarItem:(const std::string&)item_id { - [atom_touch_bar_ refreshTouchBarItem:item_id]; + if (atom_touch_bar_) + [atom_touch_bar_ refreshTouchBarItem:item_id]; } - (NSTouchBar*)makeTouchBar { - return [atom_touch_bar_ makeTouchBar]; + if (atom_touch_bar_) + return [atom_touch_bar_ makeTouchBar]; + else + return nil; } - (nullable NSTouchBarItem*)touchBar:(NSTouchBar*)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { - if (touchBar) + if (touchBar && atom_touch_bar_) return [atom_touch_bar_ makeItemForIdentifier:identifier]; else return nil; From 76f112ffc5391133c8c946529f6735a0841d9886 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 1 Mar 2017 16:12:44 -0800 Subject: [PATCH 325/925] Only export TouchBar to start --- lib/browser/api/exports/electron.js | 36 ----------------------------- 1 file changed, 36 deletions(-) diff --git a/lib/browser/api/exports/electron.js b/lib/browser/api/exports/electron.js index 0c6ce065b0..3f40595217 100644 --- a/lib/browser/api/exports/electron.js +++ b/lib/browser/api/exports/electron.js @@ -109,42 +109,6 @@ Object.defineProperties(exports, { return require('../touch-bar') } }, - TouchBarButton: { - enumerable: true, - get: function () { - return require('../touch-bar').Button - } - }, - TouchBarColorPicker: { - enumerable: true, - get: function () { - return require('../touch-bar').ColorPicker - } - }, - TouchBarGroup: { - enumerable: true, - get: function () { - return require('../touch-bar').Group - } - }, - TouchBarLabel: { - enumerable: true, - get: function () { - return require('../touch-bar').Label - } - }, - TouchBarPopOver: { - enumerable: true, - get: function () { - return require('../touch-bar').PopOver - } - }, - TouchBarSlider: { - enumerable: true, - get: function () { - return require('../touch-bar').Slider - } - }, Tray: { enumerable: true, get: function () { From 70d61869a5626a9a988fe64dcda9105103078ddf Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 2 Mar 2017 09:16:29 -0800 Subject: [PATCH 326/925] Use array to create empty NSMutableArray --- atom/browser/ui/cocoa/atom_touch_bar.mm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index 277123f3bc..621de194ea 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -44,7 +44,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide } - (NSMutableArray*)identifiersFromSettings:(const std::vector&)dicts { - NSMutableArray* identifiers = [[NSMutableArray alloc] init]; + NSMutableArray* identifiers = [NSMutableArray array]; for (const auto& item : dicts) { std::string type; @@ -342,7 +342,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide std::vector items; if (!child.Get("ordereredItems", &items)) return nil; - NSMutableArray* generatedItems = [[NSMutableArray alloc] init]; + NSMutableArray* generatedItems = [NSMutableArray array]; NSMutableArray* identList = [self identifiersFromSettings:items]; for (NSUInteger i = 0; i < [identList count]; i++) { if ([identList objectAtIndex:i] != NSTouchBarItemIdentifierOtherItemsProxy) { From 93bbe8e70b34cff180df861da7e32484025b1d86 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 2 Mar 2017 09:22:57 -0800 Subject: [PATCH 327/925] Use device NS colors consistently --- atom/browser/ui/cocoa/atom_touch_bar.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index 621de194ea..b93c2cc3f5 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -175,7 +175,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide - (NSColor*)colorFromHexColorString:(const std::string&)colorString { SkColor color = atom::ParseHexColor(colorString); - return skia::SkColorToCalibratedNSColor(color); + return skia::SkColorToDeviceNSColor(color); } - (NSTouchBarItem*)makeButtonForID:(NSString*)id From 8d716e8b17e269f85c93b5b54ae5c4d69c6764d2 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 2 Mar 2017 09:30:21 -0800 Subject: [PATCH 328/925] Register item after validating --- lib/browser/api/touch-bar.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index 12138563c4..6871c82172 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -3,7 +3,6 @@ const {EventEmitter} = require('events') let nextItemID = 1 class TouchBar extends EventEmitter { - // Bind a touch bar to a window static _setOnWindow (touchBar, window) { if (window._touchBar != null) { @@ -31,6 +30,7 @@ class TouchBar extends EventEmitter { this.windowListeners = {} this.items = {} this.ordereredItems = [] + const registerItem = (item) => { this.items[item.id] = item item.on('change', () => { @@ -41,15 +41,14 @@ class TouchBar extends EventEmitter { } } items.forEach((item) => { - this.ordereredItems.push(item) if (!(item instanceof TouchBarItem)) { throw new Error('Each item must be an instance of a TouchBarItem') } + this.ordereredItems.push(item) registerItem(item) }) } - // Called by BrowserWindow.setTouchBar _addToWindow (window) { const {id} = window @@ -84,7 +83,6 @@ class TouchBar extends EventEmitter { window._setTouchBarItems(this.ordereredItems) } - // Called by BrowserWindow.setTouchBar _removeFromWindow (window) { const removeListeners = this.windowListeners[window.id] if (removeListeners != null) removeListeners() From d1edd80ef821c936725a0e444a1d34e8c82453cf Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 2 Mar 2017 10:23:24 -0800 Subject: [PATCH 329/925] Use NSTouchBar itemForIdentifier to lookup existing item --- atom/browser/native_window_mac.mm | 4 +- atom/browser/ui/cocoa/atom_touch_bar.h | 5 +- atom/browser/ui/cocoa/atom_touch_bar.mm | 136 ++++++++++++++---------- lib/browser/api/touch-bar.js | 4 +- 4 files changed, 85 insertions(+), 64 deletions(-) diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 12315ae255..34f4028d4a 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -376,8 +376,8 @@ bool ScopedDisableResize::disable_resize_ = false; } - (void)refreshTouchBarItem:(const std::string&)item_id { - if (atom_touch_bar_) - [atom_touch_bar_ refreshTouchBarItem:item_id]; + if (atom_touch_bar_ && self.touchBar) + [atom_touch_bar_ refreshTouchBarItem:self.touchBar id:item_id]; } - (NSTouchBar*)makeTouchBar { diff --git a/atom/browser/ui/cocoa/atom_touch_bar.h b/atom/browser/ui/cocoa/atom_touch_bar.h index 738536678b..d34a1f6d47 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.h +++ b/atom/browser/ui/cocoa/atom_touch_bar.h @@ -21,7 +21,6 @@ @protected std::vector ordered_settings_; std::map settings_; - std::map> items_; id delegate_; atom::NativeWindow* window_; } @@ -31,9 +30,11 @@ - (NSTouchBar*)makeTouchBar; - (NSTouchBar*)touchBarFromItemIdentifiers:(NSMutableArray*)items; - (NSMutableArray*)identifiersFromSettings:(const std::vector&)settings; -- (void)refreshTouchBarItem:(const std::string&)item_id; +- (void)refreshTouchBarItem:(NSTouchBar*)touchBar id:(const std::string&)item_id; + - (NSString*)idFromIdentifier:(NSString*)identifier withPrefix:(NSString*)prefix; +- (NSTouchBarItemIdentifier)identifierFromID:(const std::string&)item_id type:(const std::string&)typere; - (bool)hasItemWithID:(const std::string&)item_id; - (NSColor*)colorFromHexColorString:(const std::string&)colorString; diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index b93c2cc3f5..9f76aed5f0 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -50,29 +50,24 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide std::string type; std::string item_id; if (item.Get("type", &type) && item.Get("id", &item_id)) { - settings_[item_id] = item; - if (type == "button") { - [identifiers addObject:[NSString stringWithFormat:@"%@%@", ButtonIdentifier, base::SysUTF8ToNSString(item_id)]]; - } else if (type == "label") { - [identifiers addObject:[NSString stringWithFormat:@"%@%@", LabelIdentifier, base::SysUTF8ToNSString(item_id)]]; - } else if (type == "colorpicker") { - [identifiers addObject:[NSString stringWithFormat:@"%@%@", ColorPickerIdentifier, base::SysUTF8ToNSString(item_id)]]; - } else if (type == "slider") { - [identifiers addObject:[NSString stringWithFormat:@"%@%@", SliderIdentifier, base::SysUTF8ToNSString(item_id)]]; - } else if (type == "popover") { - [identifiers addObject:[NSString stringWithFormat:@"%@%@", PopOverIdentifier, base::SysUTF8ToNSString(item_id)]]; - } else if (type == "group") { - [identifiers addObject:[NSString stringWithFormat:@"%@%@", GroupIdentifier, base::SysUTF8ToNSString(item_id)]]; - } else if (type == "spacer") { + NSTouchBarItemIdentifier identifier = nil; + if (type == "spacer") { std::string size; item.Get("size", &size); if (size == "large") { - [identifiers addObject:NSTouchBarItemIdentifierFixedSpaceLarge]; + identifier = NSTouchBarItemIdentifierFixedSpaceLarge; } else if (size == "flexible") { - [identifiers addObject:NSTouchBarItemIdentifierFlexibleSpace]; + identifier = NSTouchBarItemIdentifierFlexibleSpace; } else { - [identifiers addObject:NSTouchBarItemIdentifierFixedSpaceSmall]; + identifier = NSTouchBarItemIdentifierFixedSpaceSmall; } + } else { + identifier = [self identifierFromID:item_id type:type]; + } + + if (identifier) { + settings_[item_id] = item; + [identifiers addObject:identifier]; } } } @@ -82,59 +77,58 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide } - (NSTouchBarItem*)makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { - base::scoped_nsobject item; NSString* item_id = nil; if ([identifier hasPrefix:ButtonIdentifier]) { item_id = [self idFromIdentifier:identifier withPrefix:ButtonIdentifier]; - item.reset([self makeButtonForID:item_id withIdentifier:identifier]); + return [self makeButtonForID:item_id withIdentifier:identifier]; } else if ([identifier hasPrefix:LabelIdentifier]) { item_id = [self idFromIdentifier:identifier withPrefix:LabelIdentifier]; - item.reset([self makeLabelForID:item_id withIdentifier:identifier]); + return [self makeLabelForID:item_id withIdentifier:identifier]; } else if ([identifier hasPrefix:ColorPickerIdentifier]) { item_id = [self idFromIdentifier:identifier withPrefix:ColorPickerIdentifier]; - item.reset([self makeColorPickerForID:item_id withIdentifier:identifier]); + return [self makeColorPickerForID:item_id withIdentifier:identifier]; } else if ([identifier hasPrefix:SliderIdentifier]) { item_id = [self idFromIdentifier:identifier withPrefix:SliderIdentifier]; - item.reset([self makeSliderForID:item_id withIdentifier:identifier]); + return [self makeSliderForID:item_id withIdentifier:identifier]; } else if ([identifier hasPrefix:PopOverIdentifier]) { item_id = [self idFromIdentifier:identifier withPrefix:PopOverIdentifier]; - item.reset([self makePopoverForID:item_id withIdentifier:identifier]); + return [self makePopoverForID:item_id withIdentifier:identifier]; } else if ([identifier hasPrefix:GroupIdentifier]) { item_id = [self idFromIdentifier:identifier withPrefix:GroupIdentifier]; - item.reset([self makeGroupForID:item_id withIdentifier:identifier]); + return [self makeGroupForID:item_id withIdentifier:identifier]; } - if (item_id) - items_[[item_id UTF8String]] = item; - - return item.autorelease(); + return nil; } -- (void)refreshTouchBarItem:(const std::string&)item_id { - if (items_.find(item_id) == items_.end()) return; +- (void)refreshTouchBarItem:(NSTouchBar*)touchBar + id:(const std::string&)item_id { if (![self hasItemWithID:item_id]) return; mate::PersistentDictionary settings = settings_[item_id]; std::string item_type; settings.Get("type", &item_type); + NSTouchBarItemIdentifier identifier = [self identifierFromID:item_id + type:item_type]; + if (!identifier) return; + + NSTouchBarItem* item = [touchBar itemForIdentifier:identifier]; + if (!item) return; + if (item_type == "button") { - [self updateButton:(NSCustomTouchBarItem*)items_[item_id] - withSettings:settings]; + [self updateButton:(NSCustomTouchBarItem*)item withSettings:settings]; } else if (item_type == "label") { - [self updateLabel:(NSCustomTouchBarItem*)items_[item_id] - withSettings:settings]; + [self updateLabel:(NSCustomTouchBarItem*)item withSettings:settings]; } else if (item_type == "colorpicker") { - [self updateColorPicker:(NSColorPickerTouchBarItem*)items_[item_id] + [self updateColorPicker:(NSColorPickerTouchBarItem*)item withSettings:settings]; } else if (item_type == "slider") { - [self updateSlider:(NSSliderTouchBarItem*)items_[item_id] - withSettings:settings]; + [self updateSlider:(NSSliderTouchBarItem*)item withSettings:settings]; } else if (item_type == "popover") { - [self updatePopover:(NSPopoverTouchBarItem*)items_[item_id] - withSettings:settings]; + [self updatePopover:(NSPopoverTouchBarItem*)item withSettings:settings]; } } @@ -169,6 +163,28 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide return [identifier substringFromIndex:[prefix length]]; } +- (NSTouchBarItemIdentifier)identifierFromID:(const std::string&)item_id + type:(const std::string&)type { + NSTouchBarItemIdentifier base_identifier = nil; + if (type == "button") + base_identifier = ButtonIdentifier; + else if (type == "label") + base_identifier = LabelIdentifier; + else if (type == "colorpicker") + base_identifier = ColorPickerIdentifier; + else if (type == "slider") + base_identifier = SliderIdentifier; + else if (type == "popover") + base_identifier = PopOverIdentifier; + else if (type == "group") + base_identifier = GroupIdentifier; + + if (base_identifier) + return [NSString stringWithFormat:@"%@%s", base_identifier, item_id.data()]; + else + return nil; +} + - (bool)hasItemWithID:(const std::string&)item_id { return settings_.find(item_id) != settings_.end(); } @@ -184,14 +200,15 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide if (![self hasItemWithID:s_id]) return nil; mate::PersistentDictionary settings = settings_[s_id]; - NSCustomTouchBarItem* item = [[NSClassFromString(@"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]; + base::scoped_nsobject item([[NSClassFromString( + @"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]); NSButton* button = [NSButton buttonWithTitle:@"" target:self action:@selector(buttonAction:)]; button.tag = [id floatValue]; - item.view = button; + [item setView:button]; [self updateButton:item withSettings:settings]; - return item; + return item.autorelease(); } - (void)updateButton:(NSCustomTouchBarItem*)item @@ -219,12 +236,12 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide std::string s_id([id UTF8String]); if (![self hasItemWithID:s_id]) return nil; - mate::PersistentDictionary item = settings_[s_id]; - NSCustomTouchBarItem* customItem = [[NSClassFromString(@"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]; - customItem.view = [NSTextField labelWithString:@""]; - [self updateLabel:customItem withSettings:item]; - - return customItem; + mate::PersistentDictionary settings = settings_[s_id]; + base::scoped_nsobject item([[NSClassFromString( + @"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]); + [item setView:[NSTextField labelWithString:@""]]; + [self updateLabel:item withSettings:settings]; + return item.autorelease(); } - (void)updateLabel:(NSCustomTouchBarItem*)item @@ -241,11 +258,12 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide if (![self hasItemWithID:s_id]) return nil; mate::PersistentDictionary settings = settings_[s_id]; - NSColorPickerTouchBarItem* item = [[NSClassFromString(@"NSColorPickerTouchBarItem") alloc] initWithIdentifier:identifier]; - item.target = self; - item.action = @selector(colorPickerAction:); + base::scoped_nsobject item([[NSClassFromString( + @"NSColorPickerTouchBarItem") alloc] initWithIdentifier:identifier]); + [item setTarget:self]; + [item setAction:@selector(colorPickerAction:)]; [self updateColorPicker:item withSettings:settings]; - return item; + return item.autorelease(); } - (void)updateColorPicker:(NSColorPickerTouchBarItem*)item @@ -273,11 +291,12 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide if (![self hasItemWithID:s_id]) return nil; mate::PersistentDictionary settings = settings_[s_id]; - NSSliderTouchBarItem* item = [[NSClassFromString(@"NSSliderTouchBarItem") alloc] initWithIdentifier:identifier]; - item.target = self; - item.action = @selector(sliderAction:); + base::scoped_nsobject item([[NSClassFromString( + @"NSSliderTouchBarItem") alloc] initWithIdentifier:identifier]); + [item setTarget:self]; + [item setAction:@selector(sliderAction:)]; [self updateSlider:item withSettings:settings]; - return item; + return item.autorelease(); } - (void)updateSlider:(NSSliderTouchBarItem*)item @@ -304,9 +323,10 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide if (![self hasItemWithID:s_id]) return nil; mate::PersistentDictionary settings = settings_[s_id]; - NSPopoverTouchBarItem* item = [[NSClassFromString(@"NSPopoverTouchBarItem") alloc] initWithIdentifier:identifier]; + base::scoped_nsobject item([[NSClassFromString( + @"NSPopoverTouchBarItem") alloc] initWithIdentifier:identifier]); [self updatePopover:item withSettings:settings]; - return item; + return item.autorelease(); } - (void)updatePopover:(NSPopoverTouchBarItem*)item diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index 6871c82172..e5be1f3b49 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -147,7 +147,7 @@ TouchBar.Group = class TouchBarGroup extends TouchBarItem { this.type = 'group' this.child = config.items if (!(this.child instanceof TouchBar)) { - this.child = new TouchBar(this.items) + this.child = new TouchBar(this.child) } } } @@ -176,7 +176,7 @@ TouchBar.Popover = class TouchBarPopover extends TouchBarItem { this.showCloseButton = config.showCloseButton this.child = config.items if (!(this.child instanceof TouchBar)) { - this.child = new TouchBar(this.items) + this.child = new TouchBar(this.child) } } } From 79b17c2cd9502c4fbb30034df8b452374f5ac0f6 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 2 Mar 2017 10:25:39 -0800 Subject: [PATCH 330/925] Expose TouchBar on remote module --- lib/renderer/api/remote.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/renderer/api/remote.js b/lib/renderer/api/remote.js index 240aa22d89..e524ecc720 100644 --- a/lib/renderer/api/remote.js +++ b/lib/renderer/api/remote.js @@ -360,6 +360,7 @@ const browserModules = [ 'screen', 'session', 'systemPreferences', + 'TouchBar', 'Tray', 'webContents' ] From ecc0478e3c16e451bd63789429943b74f7e62037 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 2 Mar 2017 10:28:07 -0800 Subject: [PATCH 331/925] Match Popover casing in macOS APIs --- atom/browser/ui/cocoa/atom_touch_bar.mm | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index 9f76aed5f0..bf0c047ecf 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -16,7 +16,7 @@ static NSTouchBarItemIdentifier ButtonIdentifier = @"com.electron.touchbar.butto static NSTouchBarItemIdentifier ColorPickerIdentifier = @"com.electron.touchbar.colorpicker."; static NSTouchBarItemIdentifier GroupIdentifier = @"com.electron.touchbar.group."; static NSTouchBarItemIdentifier LabelIdentifier = @"com.electron.touchbar.label."; -static NSTouchBarItemIdentifier PopOverIdentifier = @"com.electron.touchbar.popover."; +static NSTouchBarItemIdentifier PopoverIdentifier = @"com.electron.touchbar.popover."; static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slider."; - (id)initWithDelegate:(id)delegate @@ -91,8 +91,8 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide } else if ([identifier hasPrefix:SliderIdentifier]) { item_id = [self idFromIdentifier:identifier withPrefix:SliderIdentifier]; return [self makeSliderForID:item_id withIdentifier:identifier]; - } else if ([identifier hasPrefix:PopOverIdentifier]) { - item_id = [self idFromIdentifier:identifier withPrefix:PopOverIdentifier]; + } else if ([identifier hasPrefix:PopoverIdentifier]) { + item_id = [self idFromIdentifier:identifier withPrefix:PopoverIdentifier]; return [self makePopoverForID:item_id withIdentifier:identifier]; } else if ([identifier hasPrefix:GroupIdentifier]) { item_id = [self idFromIdentifier:identifier withPrefix:GroupIdentifier]; @@ -175,7 +175,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide else if (type == "slider") base_identifier = SliderIdentifier; else if (type == "popover") - base_identifier = PopOverIdentifier; + base_identifier = PopoverIdentifier; else if (type == "group") base_identifier = GroupIdentifier; From 506b42b5633e69babffbe5b079ab658378cb741f Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 2 Mar 2017 10:32:55 -0800 Subject: [PATCH 332/925] :art: --- atom/browser/ui/cocoa/atom_touch_bar.mm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index bf0c047ecf..c16135c9ad 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -159,7 +159,8 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide window_->NotifyTouchBarItemInteraction([item_id UTF8String], details); } -- (NSString*)idFromIdentifier:(NSString*)identifier withPrefix:(NSString*)prefix { +- (NSString*)idFromIdentifier:(NSString*)identifier + withPrefix:(NSString*)prefix { return [identifier substringFromIndex:[prefix length]]; } From a34f9d35836bd4852ae8efee9269616b9c14c9d0 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 2 Mar 2017 12:56:23 -0800 Subject: [PATCH 333/925] Support icon property to match MenuItem --- atom/browser/ui/cocoa/atom_touch_bar.mm | 2 +- lib/browser/api/touch-bar.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index c16135c9ad..9aaa534e42 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -227,7 +227,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide } gfx::Image image; - if (settings.Get("image", &image)) { + if (settings.Get("icon", &image)) { button.image = image.AsNSImage(); } } diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index e5be1f3b49..83a8028b82 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -115,9 +115,10 @@ TouchBar.Button = class TouchBarButton extends TouchBarItem { constructor (config) { super(config) this.type = 'button' - const {click, label, backgroundColor} = config + const {click, icon, label, backgroundColor} = config this._addLiveProperty('label', label) this._addLiveProperty('backgroundColor', backgroundColor) + this._addLiveProperty('icon', icon) if (typeof click === 'function') { this.onInteraction = config.click } From ce12dcd3b42dbee2a4d6bf2f0e7fbd17a31b67ba Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 2 Mar 2017 13:37:34 -0800 Subject: [PATCH 334/925] Add live popover icon property --- atom/browser/ui/cocoa/atom_touch_bar.mm | 5 +++-- lib/browser/api/touch-bar.js | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index 9aaa534e42..9c3ac9eeee 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -333,10 +333,11 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide - (void)updatePopover:(NSPopoverTouchBarItem*)item withSettings:(const mate::PersistentDictionary&)settings { std::string label; - gfx::Image image; if (settings.Get("label", &label)) { item.collapsedRepresentationLabel = base::SysUTF8ToNSString(label); - } else if (settings.Get("image", &image)) { + } + gfx::Image image; + if (settings.Get("icon", &image)) { item.collapsedRepresentationImage = image.AsNSImage(); } diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index 83a8028b82..abcf8289e6 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -174,6 +174,7 @@ TouchBar.Popover = class TouchBarPopover extends TouchBarItem { super(config) this.type = 'popover' this._addLiveProperty('label', config.label) + this._addLiveProperty('icon', config.icon) this.showCloseButton = config.showCloseButton this.child = config.items if (!(this.child instanceof TouchBar)) { From eb03ab561d6a1edb5eeba36a68822ee5e7df9e85 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 2 Mar 2017 14:29:59 -0800 Subject: [PATCH 335/925] Make config optional --- lib/browser/api/touch-bar.js | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index abcf8289e6..1698844c20 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -90,7 +90,7 @@ class TouchBar extends EventEmitter { } class TouchBarItem extends EventEmitter { - constructor (config) { + constructor () { super() this.id = `${nextItemID++}` } @@ -113,7 +113,8 @@ class TouchBarItem extends EventEmitter { TouchBar.Button = class TouchBarButton extends TouchBarItem { constructor (config) { - super(config) + super() + if (config == null) config = {} this.type = 'button' const {click, icon, label, backgroundColor} = config this._addLiveProperty('label', label) @@ -127,7 +128,8 @@ TouchBar.Button = class TouchBarButton extends TouchBarItem { TouchBar.ColorPicker = class TouchBarColorPicker extends TouchBarItem { constructor (config) { - super(config) + super() + if (config == null) config = {} this.type = 'colorpicker' const {availableColors, change, selectedColor} = config this._addLiveProperty('availableColors', availableColors) @@ -144,7 +146,8 @@ TouchBar.ColorPicker = class TouchBarColorPicker extends TouchBarItem { TouchBar.Group = class TouchBarGroup extends TouchBarItem { constructor (config) { - super(config) + super() + if (config == null) config = {} this.type = 'group' this.child = config.items if (!(this.child instanceof TouchBar)) { @@ -155,7 +158,8 @@ TouchBar.Group = class TouchBarGroup extends TouchBarItem { TouchBar.Label = class TouchBarLabel extends TouchBarItem { constructor (config) { - super(config) + super() + if (config == null) config = {} this.type = 'label' this._addLiveProperty('label', config.label) } @@ -163,7 +167,8 @@ TouchBar.Label = class TouchBarLabel extends TouchBarItem { TouchBar.Spacer = class TouchBarSpacer extends TouchBarItem { constructor (config) { - super(config) + super() + if (config == null) config = {} this.type = 'spacer' this._addLiveProperty('size', config.size) } @@ -171,7 +176,8 @@ TouchBar.Spacer = class TouchBarSpacer extends TouchBarItem { TouchBar.Popover = class TouchBarPopover extends TouchBarItem { constructor (config) { - super(config) + super() + if (config == null) config = {} this.type = 'popover' this._addLiveProperty('label', config.label) this._addLiveProperty('icon', config.icon) @@ -185,7 +191,8 @@ TouchBar.Popover = class TouchBarPopover extends TouchBarItem { TouchBar.Slider = class TouchBarSlider extends TouchBarItem { constructor (config) { - super(config) + super() + if (config == null) config = {} this.type = 'slider' const {change, label, minValue, maxValue, value} = config this._addLiveProperty('label', label) From 81ecd4499cf02e04b6b8c1f4582e67263d9d7c75 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 2 Mar 2017 14:32:38 -0800 Subject: [PATCH 336/925] Make exported properties match class names --- lib/browser/api/touch-bar.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index 1698844c20..4957f248d6 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -111,7 +111,7 @@ class TouchBarItem extends EventEmitter { } } -TouchBar.Button = class TouchBarButton extends TouchBarItem { +TouchBar.TouchBarButton = class TouchBarButton extends TouchBarItem { constructor (config) { super() if (config == null) config = {} @@ -126,7 +126,7 @@ TouchBar.Button = class TouchBarButton extends TouchBarItem { } } -TouchBar.ColorPicker = class TouchBarColorPicker extends TouchBarItem { +TouchBar.TouchBarColorPicker = class TouchBarColorPicker extends TouchBarItem { constructor (config) { super() if (config == null) config = {} @@ -144,7 +144,7 @@ TouchBar.ColorPicker = class TouchBarColorPicker extends TouchBarItem { } } -TouchBar.Group = class TouchBarGroup extends TouchBarItem { +TouchBar.TouchBarGroup = class TouchBarGroup extends TouchBarItem { constructor (config) { super() if (config == null) config = {} @@ -156,7 +156,7 @@ TouchBar.Group = class TouchBarGroup extends TouchBarItem { } } -TouchBar.Label = class TouchBarLabel extends TouchBarItem { +TouchBar.TouchBarLabel = class TouchBarLabel extends TouchBarItem { constructor (config) { super() if (config == null) config = {} @@ -165,7 +165,7 @@ TouchBar.Label = class TouchBarLabel extends TouchBarItem { } } -TouchBar.Spacer = class TouchBarSpacer extends TouchBarItem { +TouchBar.TouchBarSpacer = class TouchBarSpacer extends TouchBarItem { constructor (config) { super() if (config == null) config = {} @@ -174,7 +174,7 @@ TouchBar.Spacer = class TouchBarSpacer extends TouchBarItem { } } -TouchBar.Popover = class TouchBarPopover extends TouchBarItem { +TouchBar.TouchBarPopover = class TouchBarPopover extends TouchBarItem { constructor (config) { super() if (config == null) config = {} @@ -189,7 +189,7 @@ TouchBar.Popover = class TouchBarPopover extends TouchBarItem { } } -TouchBar.Slider = class TouchBarSlider extends TouchBarItem { +TouchBar.TouchBarSlider = class TouchBarSlider extends TouchBarItem { constructor (config) { super() if (config == null) config = {} From 002369576ff94e3b13a2d55f9191625254e35760 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 3 Mar 2017 09:54:46 -0800 Subject: [PATCH 337/925] Add initial touch bar docs --- atom/browser/ui/cocoa/atom_touch_bar.mm | 2 +- docs/api/browser-window.md | 3 +- docs/api/touch-bar-button.md | 33 +++++++ docs/api/touch-bar-color-picker.md | 28 ++++++ docs/api/touch-bar-group.md | 10 ++ docs/api/touch-bar-label.md | 19 ++++ docs/api/touch-bar-popover.md | 28 ++++++ docs/api/touch-bar-slider.md | 38 ++++++++ docs/api/touch-bar-spacer.md | 13 +++ docs/api/touch-bar.md | 123 +++++++++++++++++++----- lib/browser/api/touch-bar.js | 18 ++-- 11 files changed, 280 insertions(+), 35 deletions(-) create mode 100644 docs/api/touch-bar-button.md create mode 100644 docs/api/touch-bar-color-picker.md create mode 100644 docs/api/touch-bar-group.md create mode 100644 docs/api/touch-bar-label.md create mode 100644 docs/api/touch-bar-popover.md create mode 100644 docs/api/touch-bar-slider.md create mode 100644 docs/api/touch-bar-spacer.md diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index 9c3ac9eeee..9f006bf7df 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -341,7 +341,7 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide item.collapsedRepresentationImage = image.AsNSImage(); } - bool showCloseButton; + bool showCloseButton = true; if (settings.Get("showCloseButton", &showCloseButton)) { item.showsCloseButton = showCloseButton; } diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index fc1c705b21..677e2a773b 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -1270,7 +1270,8 @@ will remove the vibrancy effect on the window. * `touchBar` TouchBar -Sets the touchBar layout for the current window. +Sets the touchBar layout for the current window. Specifying `null` or +`undefined` clears the touch bar. [blink-feature-string]: https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.json5?l=62 [quick-look]: https://en.wikipedia.org/wiki/Quick_Look diff --git a/docs/api/touch-bar-button.md b/docs/api/touch-bar-button.md new file mode 100644 index 0000000000..018c267f30 --- /dev/null +++ b/docs/api/touch-bar-button.md @@ -0,0 +1,33 @@ +## Class: TouchBarButton + +> Create a button in the touch bar for native macOS applications + +Process: [Main](../tutorial/quick-start.md#main-process) + +### `new TouchBarButton(options)` + +* `options` Object + * `label` String (optional) - Button text. + * `backgroundColor` String (optional) - Button background color in hex format, + i.e `#ABCDEF`. + * `icon` NativeImage (optional) - Button icon. + * `click` Function (optional) - Function to call when the button is clicked. + +### Instance Properties + +The following properties are available on instances of `TouchBarButton`: + +#### `touchBarButton.label` + +The button's current text. Changing this value immediately updates the button +in the touch bar. + +#### `touchBarButton.backgroundColor` + +The button's current background color. Changing this value immediately updates +the button in the touch bar. + +#### `touchBarButton.icon` + +The button's current icon. Changing this value immediately updates the button +in the touch bar. diff --git a/docs/api/touch-bar-color-picker.md b/docs/api/touch-bar-color-picker.md new file mode 100644 index 0000000000..42a2ace22b --- /dev/null +++ b/docs/api/touch-bar-color-picker.md @@ -0,0 +1,28 @@ +## Class: TouchBarColorPicker + +> Create a color picker in the touch bar for native macOS applications + +Process: [Main](../tutorial/quick-start.md#main-process) + +### `new TouchBarColorPicker(options)` + +* `options` Object + * `availableColors` String[] (optional) - Array of hex color strings to + appear as possible colors to select. + * `selectedColor` String (optional) - The selected hex color in the picker, + i.e `#ABCDEF`. + * `change` Function (optional) - Function to call when a color is selected. + +### Instance Properties + +The following properties are available on instances of `TouchBarColorPicker`: + +#### `touchBarColorPicker.availableColors` + +The color picker's available colors to select. Changing this value immediately +updates the color picker in the touch bar. + +#### `touchBarColorPicker.selectedColor` + +The color picker's currently selected color. Changing this value immediately +updates the color picker in the touch bar. diff --git a/docs/api/touch-bar-group.md b/docs/api/touch-bar-group.md new file mode 100644 index 0000000000..d765effa89 --- /dev/null +++ b/docs/api/touch-bar-group.md @@ -0,0 +1,10 @@ +## Class: TouchBarGroup + +> Create a group in the touch bar for native macOS applications + +Process: [Main](../tutorial/quick-start.md#main-process) + +### `new TouchBarGroup(options)` + +* `options` Object + * `items` TouchBar - Items to display as a group. diff --git a/docs/api/touch-bar-label.md b/docs/api/touch-bar-label.md new file mode 100644 index 0000000000..7d0137c35a --- /dev/null +++ b/docs/api/touch-bar-label.md @@ -0,0 +1,19 @@ +## Class: TouchBarLabel + +> Create a label in the touch bar for native macOS applications + +Process: [Main](../tutorial/quick-start.md#main-process) + +### `new TouchBarLabel(options)` + +* `options` Object + * `label` String (optional) - Text to display. + +### Instance Properties + +The following properties are available on instances of `TouchBarLabel`: + +#### `touchBarLabel.label` + +The label's current text. Changing this value immediately updates the label in +the touch bar. diff --git a/docs/api/touch-bar-popover.md b/docs/api/touch-bar-popover.md new file mode 100644 index 0000000000..d065da05e1 --- /dev/null +++ b/docs/api/touch-bar-popover.md @@ -0,0 +1,28 @@ +## Class: TouchBarPopover + +> Create a popover in the touch bar for native macOS applications + +Process: [Main](../tutorial/quick-start.md#main-process) + +### `new TouchBarPopover(options)` + +* `options` Object + * `label` String (optional) - Popover button text. + * `icon` NativeImage (optional) - Popover button icon. + * `items` TouchBar (optional) - Items to display in the popover. + * `showCloseButton` Boolean (optional) - `true` to display a close button + on the left of the popover, `false` to not show it. Default is `true`. + +### Instance Properties + +The following properties are available on instances of `TouchBarPopover`: + +#### `touchBarPopover.label` + +The popover's current button text. Changing this value immediately updates the +popover in the touch bar. + +#### `touchBarPopover.icon` + +The popover's current button icon. Changing this value immediately updates the +popover in the touch bar. diff --git a/docs/api/touch-bar-slider.md b/docs/api/touch-bar-slider.md new file mode 100644 index 0000000000..1be9b45716 --- /dev/null +++ b/docs/api/touch-bar-slider.md @@ -0,0 +1,38 @@ +## Class: TouchBarSlider + +> Create a slider in the touch bar for native macOS applications + +Process: [Main](../tutorial/quick-start.md#main-process) + +### `new TouchBarSlider(options)` + +* `options` Object + * `label` String (optional) - Label text. + * `value` Integer (optional) - Selected value. + * `minValue` Integer (optional) - Minimum value. + * `maxValue` Integer (optional) - Maximum value. + * `change` Function (optional) - Function to call when the slider is changed. + +### Instance Properties + +The following properties are available on instances of `TouchBarSlider`: + +#### `touchBarSlider.label` + +The slider's current text. Changing this value immediately updates the slider +in the touch bar. + +#### `touchBarSlider.value` + +The slider's current value. Changing this value immediately updates the slider +in the touch bar. + +#### `touchBarSlider.minValue` + +The slider's current minimum value. Changing this value immediately updates the +slider in the touch bar. + +#### `touchBarSlider.maxValue` + +The slider's current maximum value. Changing this value immediately updates the +slider in the touch bar. diff --git a/docs/api/touch-bar-spacer.md b/docs/api/touch-bar-spacer.md new file mode 100644 index 0000000000..56148a44e1 --- /dev/null +++ b/docs/api/touch-bar-spacer.md @@ -0,0 +1,13 @@ +## Class: TouchBarSpacer + +> Create a spacer between two items in the touch bar for native macOS applications + +Process: [Main](../tutorial/quick-start.md#main-process) + +### `new TouchBarSlider(options)` + +* `options` Object + * `size` String (optional) - Size of spacer, possible values are: + * `small` - Small space between items. + * `large` - Large space between items. + * `flexible` - Take up all available space. diff --git a/docs/api/touch-bar.md b/docs/api/touch-bar.md index 68bdaf88a1..147087bb02 100644 --- a/docs/api/touch-bar.md +++ b/docs/api/touch-bar.md @@ -6,39 +6,114 @@ Process: [Main](../tutorial/quick-start.md#main-process) ### `new TouchBar(items)` -* `items` (TouchBarButton | TouchBarColorPicker | TouchBarGroup | TouchBarLabel | TouchBarPopOver | TouchBarSlider)[] +* `items` (TouchBarButton | TouchBarColorPicker | TouchBarGroup | TouchBarLabel | TouchBarPopOver | TouchBarSlider | TouchBarSpacer)[] -Creates a new touch bar. Note any changes to the TouchBar instance -will not affect the rendered TouchBar. To affect the rendered -TouchBar you **must** use either methods on the TouchBar or methods -on the TouchBar* items - -### Instance Methods - -The `menu` object has the following instance methods: - -#### `touchBar.destroy()` - -Immediately destroys the TouchBar instance and will reset the rendered -touch bar. +Creates a new touch bar with the specified items. Use +`BrowserWindow.setTouchBar` to add the `TouchBar` to a window. ## Examples -The `TouchBar` class is only available in the main process, it is not currently possible to use in the renderer process **even** through the remote module. +The `TouchBar` class is only available in the main process, it is not currently +possible to use in the renderer process **even** through the remote module. -### Main process - -An example of creating a touch bar in the main process: +Below is an example of a simple slot machine touch bar game with a button +and some labels. ```javascript -const {TouchBar, TouchBarButton} = require('electron') +const {app, BrowserWindow, TouchBar} = require('electron') + +const {TouchBarLabel, TouchBarButton, TouchBarSpacer} = TouchBar + +let spinning = false + +// Reel labels +const reel1 = new TouchBarLabel() +const reel2 = new TouchBarLabel() +const reel3 = new TouchBarLabel() + +// Spin result label +const result = new TouchBarLabel() + +// Spin button +const spin = new TouchBarButton({ + label: '🎰 Spin', + backgroundColor: '#7851A9', + click: () => { + // Ignore clicks if already spinning + if (spinning) { + return + } + + spinning = true + result.label = '' + + let timeout = 10 + const spinLength = 4 * 1000 // 4 seconds + const startTime = Date.now() + + const spinReels = () => { + updateReels() + + if ((Date.now() - startTime) >= spinLength) { + finishSpin() + } else { + // Slow down a bit on each spin + timeout *= 1.1 + setTimeout(spinReels, timeout) + } + } + + spinReels() + } +}) + +const getRandomValue = () => { + const values = ['🍒', '💎', '7️⃣', '🍊', '🔔', '⭐', '🍇'] + return values[Math.floor(Math.random() * values.length)] +} + +const updateReels = () => { + reel1.label = getRandomValue() + reel2.label = getRandomValue() + reel3.label = getRandomValue() +} + +const finishSpin = () => { + const uniqueValues = new Set([reel1.label, reel2.label, reel3.label]).size + if (uniqueValues === 1) { + // All 3 values are the same + result.label = '💰 Jackpot!' + } else if (uniqueValues === 2) { + // 2 values are the same + result.label = '😍 Winner!' + } else { + // No values are the same + result.label = '🙁 Spin Again' + } + spinning = false +} const touchBar = new TouchBar([ - new TouchBarButton({ - label: 'Example Button', - click: () => console.log('I was clicked') - }) + spin, + new TouchBarSpacer({size: 'large'}), + reel1, + reel2, + reel3, + new TouchBarSpacer({size: 'large'}), + result ]) -mainWindow.setTouchBar(touchBar) +let window + +app.once('ready', () => { + window = new BrowserWindow({ + frame: false, + titleBarStyle: 'hidden-inset', + width: 200, + height: 200, + backgroundColor: '#000' + }) + window.loadURL('about:blank') + window.setTouchBar(touchBar) +}) ``` diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index 4957f248d6..905179d9c5 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -165,15 +165,6 @@ TouchBar.TouchBarLabel = class TouchBarLabel extends TouchBarItem { } } -TouchBar.TouchBarSpacer = class TouchBarSpacer extends TouchBarItem { - constructor (config) { - super() - if (config == null) config = {} - this.type = 'spacer' - this._addLiveProperty('size', config.size) - } -} - TouchBar.TouchBarPopover = class TouchBarPopover extends TouchBarItem { constructor (config) { super() @@ -209,4 +200,13 @@ TouchBar.TouchBarSlider = class TouchBarSlider extends TouchBarItem { } } +TouchBar.TouchBarSpacer = class TouchBarSpacer extends TouchBarItem { + constructor (config) { + super() + if (config == null) config = {} + this.type = 'spacer' + this.size = config.size + } +} + module.exports = TouchBar From c349aeff80951561dc84bc880bca7595c011e9b8 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 3 Mar 2017 10:22:25 -0800 Subject: [PATCH 338/925] Add initial touch bar specs --- docs/api/touch-bar.md | 3 --- lib/browser/api/touch-bar.js | 8 +++--- spec/api-touch-bar-spec.js | 50 ++++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 6 deletions(-) create mode 100644 spec/api-touch-bar-spec.js diff --git a/docs/api/touch-bar.md b/docs/api/touch-bar.md index 147087bb02..a499703fd5 100644 --- a/docs/api/touch-bar.md +++ b/docs/api/touch-bar.md @@ -13,9 +13,6 @@ Creates a new touch bar with the specified items. Use ## Examples -The `TouchBar` class is only available in the main process, it is not currently -possible to use in the renderer process **even** through the remote module. - Below is an example of a simple slot machine touch bar game with a button and some labels. diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index 905179d9c5..d4c8c5c44c 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -24,7 +24,7 @@ class TouchBar extends EventEmitter { super() if (!Array.isArray(items)) { - throw new Error('The items object provided has to be an array') + throw new Error('Must specify items array as first argument') } this.windowListeners = {} @@ -42,7 +42,7 @@ class TouchBar extends EventEmitter { } items.forEach((item) => { if (!(item instanceof TouchBarItem)) { - throw new Error('Each item must be an instance of a TouchBarItem') + throw new Error('Each item must be an instance of TouchBarItem') } this.ordereredItems.push(item) registerItem(item) @@ -121,7 +121,9 @@ TouchBar.TouchBarButton = class TouchBarButton extends TouchBarItem { this._addLiveProperty('backgroundColor', backgroundColor) this._addLiveProperty('icon', icon) if (typeof click === 'function') { - this.onInteraction = config.click + this.onInteraction = () => { + config.click() + } } } } diff --git a/spec/api-touch-bar-spec.js b/spec/api-touch-bar-spec.js new file mode 100644 index 0000000000..b658c0c248 --- /dev/null +++ b/spec/api-touch-bar-spec.js @@ -0,0 +1,50 @@ +const assert = require('assert') +const {BrowserWindow, TouchBar} = require('electron').remote +const {closeWindow} = require('./window-helpers') + +const {TouchBarButton, TouchBarColorPicker, TouchBarGroup} = TouchBar +const {TouchBarLabel, TouchBarPopover, TouchBarSlider, TouchBarSpacer} = TouchBar + +describe('TouchBar module', function () { + it('throws an error when created without an items array', function () { + assert.throws(() => { + const touchBar = new TouchBar() + touchBar.toString() + }, /Must specify items array as first argument/) + }) + + it('throws an error when created with invalid items', function () { + assert.throws(() => { + const touchBar = new TouchBar([1, true, {}, []]) + touchBar.toString() + }, /Each item must be an instance of TouchBarItem/) + }) + + describe('BrowserWindow behavior', function () { + let window + + beforeEach(function () { + window = new BrowserWindow() + }) + + afterEach(function () { + window.setTouchBar(null) + return closeWindow(window).then(function () { window = null }) + }) + + it('can be added to and removed from a window', function () { + const touchBar = new TouchBar([ + new TouchBarButton({label: 'foo', backgroundColor: '#F00', click: () => {}}), + new TouchBarColorPicker({selectedColor: '#F00', change: () => {}}), + new TouchBarGroup({items: new TouchBar([new TouchBarLabel({label: 'hello'})])}), + new TouchBarLabel({label: 'bar'}), + new TouchBarPopover({items: new TouchBar([new TouchBarButton({label: 'pop'})])}), + new TouchBarSlider({label: 'slide', value: 5, minValue: 2, maxValue: 75, change: () => {}}), + new TouchBarSpacer({size: 'large'}) + ]) + window.setTouchBar(touchBar) + window.setTouchBar() + window.setTouchBar(new TouchBar([new TouchBarLabel({label: 'two'})])) + }) + }) +}) From ca2898a60e23a40d1a77e6d13710f29c98e53d6e Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 3 Mar 2017 10:49:42 -0800 Subject: [PATCH 339/925] Check that window responds to touchBar selector for pre-10.12.1 compat --- atom/browser/native_window_mac.mm | 2 ++ spec/api-touch-bar-spec.js | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 34f4028d4a..d2ca6b8621 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -369,6 +369,8 @@ bool ScopedDisableResize::disable_resize_ = false; } - (void)resetTouchBar:(const std::vector&)settings { + if (![self respondsToSelector:@selector(touchBar)]) return; + atom_touch_bar_.reset([[AtomTouchBar alloc] initWithDelegate:self window:shell_ settings:settings]); diff --git a/spec/api-touch-bar-spec.js b/spec/api-touch-bar-spec.js index b658c0c248..cdca2d47c4 100644 --- a/spec/api-touch-bar-spec.js +++ b/spec/api-touch-bar-spec.js @@ -33,16 +33,18 @@ describe('TouchBar module', function () { }) it('can be added to and removed from a window', function () { + const label = new TouchBarLabel({label: 'bar'}) const touchBar = new TouchBar([ new TouchBarButton({label: 'foo', backgroundColor: '#F00', click: () => {}}), new TouchBarColorPicker({selectedColor: '#F00', change: () => {}}), new TouchBarGroup({items: new TouchBar([new TouchBarLabel({label: 'hello'})])}), - new TouchBarLabel({label: 'bar'}), + label, new TouchBarPopover({items: new TouchBar([new TouchBarButton({label: 'pop'})])}), new TouchBarSlider({label: 'slide', value: 5, minValue: 2, maxValue: 75, change: () => {}}), new TouchBarSpacer({size: 'large'}) ]) window.setTouchBar(touchBar) + label.label = 'baz' window.setTouchBar() window.setTouchBar(new TouchBar([new TouchBarLabel({label: 'two'})])) }) From d9a8c15c69907860624e6bbb6468ffa62bb39a35 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 3 Mar 2017 10:54:21 -0800 Subject: [PATCH 340/925] Add clover to possible values --- docs/api/touch-bar.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/touch-bar.md b/docs/api/touch-bar.md index a499703fd5..fa62b8dc6c 100644 --- a/docs/api/touch-bar.md +++ b/docs/api/touch-bar.md @@ -65,7 +65,7 @@ const spin = new TouchBarButton({ }) const getRandomValue = () => { - const values = ['🍒', '💎', '7️⃣', '🍊', '🔔', '⭐', '🍇'] + const values = ['🍒', '💎', '7️⃣', '🍊', '🔔', '⭐', '🍇', '🍀'] return values[Math.floor(Math.random() * values.length)] } From fde310f50d9a427eaebe6439a35e6a46e995780e Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 3 Mar 2017 14:04:55 -0800 Subject: [PATCH 341/925] Mention it only effects 10.12.1 with touch bar --- docs/api/browser-window.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index 677e2a773b..398dd3b746 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -1271,7 +1271,8 @@ will remove the vibrancy effect on the window. * `touchBar` TouchBar Sets the touchBar layout for the current window. Specifying `null` or -`undefined` clears the touch bar. +`undefined` clears the touch bar. This method only has an effect if the +machine has a touch bar and is running on macOS 10.12.1+. [blink-feature-string]: https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.json5?l=62 [quick-look]: https://en.wikipedia.org/wiki/Quick_Look From edebb32014b00c7653fac05cb1924a5c245843dc Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 3 Mar 2017 14:07:59 -0800 Subject: [PATCH 342/925] Drop nullable for consistency --- atom/browser/native_window_mac.mm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index d2ca6b8621..c88469a9c8 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -389,8 +389,8 @@ bool ScopedDisableResize::disable_resize_ = false; return nil; } -- (nullable NSTouchBarItem*)touchBar:(NSTouchBar*)touchBar - makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { +- (NSTouchBarItem*)touchBar:(NSTouchBar*)touchBar + makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { if (touchBar && atom_touch_bar_) return [atom_touch_bar_ makeItemForIdentifier:identifier]; else From 9f323104e72ad77dff16f12951fd6d56249162c2 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 3 Mar 2017 14:11:09 -0800 Subject: [PATCH 343/925] Remove unused include --- atom/browser/native_window_observer.h | 1 - 1 file changed, 1 deletion(-) diff --git a/atom/browser/native_window_observer.h b/atom/browser/native_window_observer.h index 7f1f5aace8..3b8d86e6fb 100644 --- a/atom/browser/native_window_observer.h +++ b/atom/browser/native_window_observer.h @@ -6,7 +6,6 @@ #define ATOM_BROWSER_NATIVE_WINDOW_OBSERVER_H_ #include -#include #include "base/strings/string16.h" #include "base/values.h" From 2680ee9f8f6a3201c19d359a3f53fb11ad5b9ecc Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 3 Mar 2017 15:14:51 -0800 Subject: [PATCH 344/925] Add support for setting TouchBarLabel text color --- atom/browser/ui/cocoa/atom_touch_bar.mm | 10 +++++++++- docs/api/touch-bar-label.md | 6 ++++++ docs/api/touch-bar.md | 5 +++++ lib/browser/api/touch-bar.js | 1 + 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index 9f006bf7df..0ce674bcb9 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -247,10 +247,18 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide - (void)updateLabel:(NSCustomTouchBarItem*)item withSettings:(const mate::PersistentDictionary&)settings { + NSTextField* text_field = (NSTextField*)item.view; + std::string label; settings.Get("label", &label); - NSTextField* text_field = (NSTextField*)item.view; text_field.stringValue = base::SysUTF8ToNSString(label); + + std::string textColor; + if (settings.Get("textColor", &textColor) && !textColor.empty()) { + text_field.textColor = [self colorFromHexColorString:textColor]; + } else { + text_field.textColor = nil; + } } - (NSTouchBarItem*)makeColorPickerForID:(NSString*)id diff --git a/docs/api/touch-bar-label.md b/docs/api/touch-bar-label.md index 7d0137c35a..4caecb6a46 100644 --- a/docs/api/touch-bar-label.md +++ b/docs/api/touch-bar-label.md @@ -8,6 +8,7 @@ Process: [Main](../tutorial/quick-start.md#main-process) * `options` Object * `label` String (optional) - Text to display. + * `textColor` String (optional) - Hex color of text, i.e `#ABCDEF`. ### Instance Properties @@ -17,3 +18,8 @@ The following properties are available on instances of `TouchBarLabel`: The label's current text. Changing this value immediately updates the label in the touch bar. + +#### `touchBarLabel.textColor` + +The label's current text color. Changing this value immediately updates the +label in the touch bar. diff --git a/docs/api/touch-bar.md b/docs/api/touch-bar.md index fa62b8dc6c..2ca8c1ef32 100644 --- a/docs/api/touch-bar.md +++ b/docs/api/touch-bar.md @@ -80,12 +80,15 @@ const finishSpin = () => { if (uniqueValues === 1) { // All 3 values are the same result.label = '💰 Jackpot!' + result.textColor = '#FDFF00' } else if (uniqueValues === 2) { // 2 values are the same result.label = '😍 Winner!' + result.textColor = '#FDFF00' } else { // No values are the same result.label = '🙁 Spin Again' + result.textColor = null } spinning = false } @@ -94,7 +97,9 @@ const touchBar = new TouchBar([ spin, new TouchBarSpacer({size: 'large'}), reel1, + new TouchBarSpacer({size: 'small'}), reel2, + new TouchBarSpacer({size: 'small'}), reel3, new TouchBarSpacer({size: 'large'}), result diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index d4c8c5c44c..ee7388f177 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -164,6 +164,7 @@ TouchBar.TouchBarLabel = class TouchBarLabel extends TouchBarItem { if (config == null) config = {} this.type = 'label' this._addLiveProperty('label', config.label) + this._addLiveProperty('textColor', config.textColor) } } From b8798723176fc2160941646c3b04068533e55e4d Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 3 Mar 2017 15:24:32 -0800 Subject: [PATCH 345/925] Always set properties in update calls --- atom/browser/ui/cocoa/atom_touch_bar.mm | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index 0ce674bcb9..0b47d23fa5 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -222,9 +222,8 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide } std::string label; - if (settings.Get("label", &label)) { - button.title = base::SysUTF8ToNSString(label); - } + settings.Get("label", &label); + button.title = base::SysUTF8ToNSString(label); gfx::Image image; if (settings.Get("icon", &image)) { @@ -341,18 +340,17 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide - (void)updatePopover:(NSPopoverTouchBarItem*)item withSettings:(const mate::PersistentDictionary&)settings { std::string label; - if (settings.Get("label", &label)) { - item.collapsedRepresentationLabel = base::SysUTF8ToNSString(label); - } + settings.Get("label", &label); + item.collapsedRepresentationLabel = base::SysUTF8ToNSString(label); + gfx::Image image; if (settings.Get("icon", &image)) { item.collapsedRepresentationImage = image.AsNSImage(); } bool showCloseButton = true; - if (settings.Get("showCloseButton", &showCloseButton)) { - item.showsCloseButton = showCloseButton; - } + settings.Get("showCloseButton", &showCloseButton); + item.showsCloseButton = showCloseButton; mate::PersistentDictionary child; std::vector items; From 4d6c7798391b8909da01062300340923ff2830d7 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 3 Mar 2017 15:28:32 -0800 Subject: [PATCH 346/925] identList -> identifiers --- atom/browser/ui/cocoa/atom_touch_bar.mm | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index 0b47d23fa5..b2f8dc6105 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -371,10 +371,10 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide if (!child.Get("ordereredItems", &items)) return nil; NSMutableArray* generatedItems = [NSMutableArray array]; - NSMutableArray* identList = [self identifiersFromSettings:items]; - for (NSUInteger i = 0; i < [identList count]; i++) { - if ([identList objectAtIndex:i] != NSTouchBarItemIdentifierOtherItemsProxy) { - NSTouchBarItem* generatedItem = [self makeItemForIdentifier:[identList objectAtIndex:i]]; + NSMutableArray* identifiers = [self identifiersFromSettings:items]; + for (NSUInteger i = 0; i < [identifiers count]; i++) { + if ([identifiers objectAtIndex:i] != NSTouchBarItemIdentifierOtherItemsProxy) { + NSTouchBarItem* generatedItem = [self makeItemForIdentifier:[identifiers objectAtIndex:i]]; if (generatedItem) { [generatedItems addObject:generatedItem]; } From f97ee56c9f458cd29744c1dae339721f3ec372a3 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Sat, 4 Mar 2017 16:14:41 +1100 Subject: [PATCH 347/925] Add more explicit types to docs for TouchBar --- docs/api/touch-bar-button.md | 6 +++--- docs/api/touch-bar-color-picker.md | 5 +++-- docs/api/touch-bar-label.md | 4 ++-- docs/api/touch-bar-popover.md | 4 ++-- docs/api/touch-bar-slider.md | 9 +++++---- 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/docs/api/touch-bar-button.md b/docs/api/touch-bar-button.md index 018c267f30..30f7033caf 100644 --- a/docs/api/touch-bar-button.md +++ b/docs/api/touch-bar-button.md @@ -19,15 +19,15 @@ The following properties are available on instances of `TouchBarButton`: #### `touchBarButton.label` -The button's current text. Changing this value immediately updates the button +A `String` representing the button's current text. Changing this value immediately updates the button in the touch bar. #### `touchBarButton.backgroundColor` -The button's current background color. Changing this value immediately updates +A `String` hex code representing the button's current background color. Changing this value immediately updates the button in the touch bar. #### `touchBarButton.icon` -The button's current icon. Changing this value immediately updates the button +A `NativeImage` representing the button's current icon. Changing this value immediately updates the button in the touch bar. diff --git a/docs/api/touch-bar-color-picker.md b/docs/api/touch-bar-color-picker.md index 42a2ace22b..98512fa6da 100644 --- a/docs/api/touch-bar-color-picker.md +++ b/docs/api/touch-bar-color-picker.md @@ -12,6 +12,7 @@ Process: [Main](../tutorial/quick-start.md#main-process) * `selectedColor` String (optional) - The selected hex color in the picker, i.e `#ABCDEF`. * `change` Function (optional) - Function to call when a color is selected. + * `color` String - The color that the user selected from the picker ### Instance Properties @@ -19,10 +20,10 @@ The following properties are available on instances of `TouchBarColorPicker`: #### `touchBarColorPicker.availableColors` -The color picker's available colors to select. Changing this value immediately +A `String[]` array representing the color picker's available colors to select. Changing this value immediately updates the color picker in the touch bar. #### `touchBarColorPicker.selectedColor` -The color picker's currently selected color. Changing this value immediately +A `String` hex code representing the color picker's currently selected color. Changing this value immediately updates the color picker in the touch bar. diff --git a/docs/api/touch-bar-label.md b/docs/api/touch-bar-label.md index 4caecb6a46..c81403b80d 100644 --- a/docs/api/touch-bar-label.md +++ b/docs/api/touch-bar-label.md @@ -16,10 +16,10 @@ The following properties are available on instances of `TouchBarLabel`: #### `touchBarLabel.label` -The label's current text. Changing this value immediately updates the label in +A `String` representing the label's current text. Changing this value immediately updates the label in the touch bar. #### `touchBarLabel.textColor` -The label's current text color. Changing this value immediately updates the +A `String` hex code representing the label's current text color. Changing this value immediately updates the label in the touch bar. diff --git a/docs/api/touch-bar-popover.md b/docs/api/touch-bar-popover.md index d065da05e1..231a68c43d 100644 --- a/docs/api/touch-bar-popover.md +++ b/docs/api/touch-bar-popover.md @@ -19,10 +19,10 @@ The following properties are available on instances of `TouchBarPopover`: #### `touchBarPopover.label` -The popover's current button text. Changing this value immediately updates the +A `String` representing the popover's current button text. Changing this value immediately updates the popover in the touch bar. #### `touchBarPopover.icon` -The popover's current button icon. Changing this value immediately updates the +A `NativeImage` representing the popover's current button icon. Changing this value immediately updates the popover in the touch bar. diff --git a/docs/api/touch-bar-slider.md b/docs/api/touch-bar-slider.md index 1be9b45716..5d9bce01cc 100644 --- a/docs/api/touch-bar-slider.md +++ b/docs/api/touch-bar-slider.md @@ -12,6 +12,7 @@ Process: [Main](../tutorial/quick-start.md#main-process) * `minValue` Integer (optional) - Minimum value. * `maxValue` Integer (optional) - Maximum value. * `change` Function (optional) - Function to call when the slider is changed. + * `newValue` Number - The value that the user selected on the Slider ### Instance Properties @@ -19,20 +20,20 @@ The following properties are available on instances of `TouchBarSlider`: #### `touchBarSlider.label` -The slider's current text. Changing this value immediately updates the slider +A `String` representing the slider's current text. Changing this value immediately updates the slider in the touch bar. #### `touchBarSlider.value` -The slider's current value. Changing this value immediately updates the slider +A `Number` representing the slider's current value. Changing this value immediately updates the slider in the touch bar. #### `touchBarSlider.minValue` -The slider's current minimum value. Changing this value immediately updates the +A `Number` representing the slider's current minimum value. Changing this value immediately updates the slider in the touch bar. #### `touchBarSlider.maxValue` -The slider's current maximum value. Changing this value immediately updates the +A `Number` representing the slider's current maximum value. Changing this value immediately updates the slider in the touch bar. From c209b886bf4078cf6126c5466018a295b44b795f Mon Sep 17 00:00:00 2001 From: rhysd Date: Sun, 5 Mar 2017 15:09:35 +0900 Subject: [PATCH 348/925] fix TouchBarSpacer class name in doc --- docs/api/touch-bar-spacer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/touch-bar-spacer.md b/docs/api/touch-bar-spacer.md index 56148a44e1..b101f19aef 100644 --- a/docs/api/touch-bar-spacer.md +++ b/docs/api/touch-bar-spacer.md @@ -4,7 +4,7 @@ Process: [Main](../tutorial/quick-start.md#main-process) -### `new TouchBarSlider(options)` +### `new TouchBarSpacer(options)` * `options` Object * `size` String (optional) - Size of spacer, possible values are: From dead1ae1ba5a1374bf6f41a0a79b92da82d7263f Mon Sep 17 00:00:00 2001 From: deepak1556 Date: Mon, 20 Feb 2017 02:13:17 +0530 Subject: [PATCH 349/925] webContents: defer url load when there is a pending navigation entry --- atom/browser/api/atom_api_web_contents.cc | 99 +++++++++++++++++++---- atom/browser/api/atom_api_web_contents.h | 24 +++++- spec/api-browser-window-spec.js | 18 +++++ 3 files changed, 125 insertions(+), 16 deletions(-) diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 89e7a7d65b..94de2e6d15 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -46,6 +46,7 @@ #include "chrome/browser/printing/print_preview_message_handler.h" #include "chrome/browser/printing/print_view_manager_basic.h" #include "chrome/browser/ssl/security_state_tab_helper.h" +#include "content/browser/frame_host/navigation_entry_impl.h" #include "content/browser/renderer_host/render_widget_host_impl.h" #include "content/browser/web_contents/web_contents_impl.h" #include "content/common/view_messages.h" @@ -54,6 +55,9 @@ #include "content/public/browser/navigation_details.h" #include "content/public/browser/navigation_entry.h" #include "content/public/browser/navigation_handle.h" +#include "content/public/browser/notification_details.h" +#include "content/public/browser/notification_source.h" +#include "content/public/browser/notification_types.h" #include "content/public/browser/plugin_service.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_process_host.h" @@ -242,6 +246,22 @@ void OnCapturePageDone(base::Callback callback, callback.Run(gfx::Image::CreateFrom1xBitmap(bitmap)); } +// Set the background color of RenderWidgetHostView. +void SetBackgroundColor(content::WebContents* web_contents) { + const auto view = web_contents->GetRenderWidgetHostView(); + if (view) { + WebContentsPreferences* web_preferences = + WebContentsPreferences::FromWebContents(web_contents); + std::string color_name; + if (web_preferences->web_preferences()->GetString(options::kBackgroundColor, + &color_name)) { + view->SetBackgroundColor(ParseHexColor(color_name)); + } else { + view->SetBackgroundColor(SK_ColorTRANSPARENT); + } + } +} + } // namespace WebContents::WebContents(v8::Isolate* isolate, @@ -338,7 +358,7 @@ void WebContents::InitWithSessionAndOptions(v8::Isolate* isolate, content::WebContents *web_contents, mate::Handle session, const mate::Dictionary& options) { - Observe(web_contents); + content::WebContentsObserver::Observe(web_contents); InitWithWebContents(web_contents, session->browser_context()); managed_web_contents()->GetView()->SetDelegate(this); @@ -374,6 +394,11 @@ void WebContents::InitWithSessionAndOptions(v8::Isolate* isolate, SetOwnerWindow(owner_window); } + const content::NavigationController* controller = + &web_contents->GetController(); + registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_PENDING, + content::Source(controller)); + Init(isolate); AttachAsUserData(web_contents); } @@ -733,6 +758,30 @@ void WebContents::DidGetRedirectForResourceRequest( details.headers.get()); } +void WebContents::DidStartNavigation( + content::NavigationHandle* navigation_handle) { + if (!navigation_handle->IsInMainFrame() || navigation_handle->IsSamePage()) + return; + + if (deferred_load_url_.id) { + auto web_contents = navigation_handle->GetWebContents(); + auto& controller = web_contents->GetController(); + int id = controller.GetPendingEntry()->GetUniqueID(); + if (id == deferred_load_url_.id) { + if (!deferred_load_url_.params.url.is_empty()) { + auto params = deferred_load_url_.params; + deferred_load_url_.id = 0; + deferred_load_url_.params = + content::NavigationController::LoadURLParams(GURL()); + controller.LoadURLWithParams(params); + SetBackgroundColor(web_contents); + } else { + deferred_load_url_.id = 0; + } + } + } +} + void WebContents::DidFinishNavigation( content::NavigationHandle* navigation_handle) { bool is_main_frame = navigation_handle->IsInMainFrame(); @@ -777,6 +826,33 @@ void WebContents::DidUpdateFaviconURL( Emit("page-favicon-updated", unique_urls); } +void WebContents::Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + switch (type) { + case content::NOTIFICATION_NAV_ENTRY_PENDING: { + content::NavigationEntry* entry = + content::Details(details).ptr(); + content::NavigationEntryImpl* entry_impl = + static_cast(entry); + // In NavigatorImpl::DidStartMainFrameNavigation when there is no + // browser side pending entry available it creates a new one based + // on existing pending entry, hence we track the unique id here + // instead in WebContents::LoadURL with controller.GetPendingEntry() + // TODO(deepak1556): Remove once we have + // https://codereview.chromium.org/2661743002. + if (entry_impl->is_renderer_initiated() && + entry_impl->frame_tree_node_id() == -1) { + deferred_load_url_.id = entry->GetUniqueID(); + } + break; + } + default: + NOTREACHED(); + break; + } +} + void WebContents::DevToolsReloadPage() { Emit("devtools-reload-page"); } @@ -921,23 +997,16 @@ void WebContents::LoadURL(const GURL& url, const mate::Dictionary& options) { params.transition_type = ui::PAGE_TRANSITION_TYPED; params.should_clear_history_list = true; params.override_user_agent = content::NavigationController::UA_OVERRIDE_TRUE; - web_contents()->GetController().LoadURLWithParams(params); - // Set the background color of RenderWidgetHostView. + if (deferred_load_url_.id) { + deferred_load_url_.params = params; + return; + } + + web_contents()->GetController().LoadURLWithParams(params); // We have to call it right after LoadURL because the RenderViewHost is only // created after loading a page. - const auto view = web_contents()->GetRenderWidgetHostView(); - if (view) { - WebContentsPreferences* web_preferences = - WebContentsPreferences::FromWebContents(web_contents()); - std::string color_name; - if (web_preferences->web_preferences()->GetString(options::kBackgroundColor, - &color_name)) { - view->SetBackgroundColor(ParseHexColor(color_name)); - } else { - view->SetBackgroundColor(SK_ColorTRANSPARENT); - } - } + SetBackgroundColor(web_contents()); } void WebContents::DownloadURL(const GURL& url) { diff --git a/atom/browser/api/atom_api_web_contents.h b/atom/browser/api/atom_api_web_contents.h index a37fb8a91f..41edcc264e 100644 --- a/atom/browser/api/atom_api_web_contents.h +++ b/atom/browser/api/atom_api_web_contents.h @@ -13,6 +13,8 @@ #include "atom/browser/api/trackable_object.h" #include "atom/browser/common_web_contents_delegate.h" #include "content/common/cursors/webcursor.h" +#include "content/public/browser/notification_observer.h" +#include "content/public/browser/notification_registrar.h" #include "content/public/browser/web_contents_observer.h" #include "content/public/common/favicon_url.h" #include "native_mate/handle.h" @@ -46,7 +48,8 @@ namespace api { class WebContents : public mate::TrackableObject, public CommonWebContentsDelegate, - public content::WebContentsObserver { + public content::WebContentsObserver, + public content::NotificationObserver { public: enum Type { BACKGROUND_PAGE, // A DevTools extension background page. @@ -308,6 +311,8 @@ class WebContents : public mate::TrackableObject, const content::ResourceRequestDetails& details) override; void DidGetRedirectForResourceRequest( const content::ResourceRedirectDetails& details) override; + void DidStartNavigation( + content::NavigationHandle* navigation_handle) override; void DidFinishNavigation( content::NavigationHandle* navigation_handle) override; bool OnMessageReceived(const IPC::Message& message) override; @@ -325,6 +330,11 @@ class WebContents : public mate::TrackableObject, const MediaPlayerId& id) override; void DidChangeThemeColor(SkColor theme_color) override; + // content::NotificationObserver: + void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) override; + // brightray::InspectableWebContentsDelegate: void DevToolsReloadPage() override; @@ -334,6 +344,13 @@ class WebContents : public mate::TrackableObject, void DevToolsClosed() override; private: + struct LoadURLParams { + LoadURLParams() : params(GURL()), id(0) {} + + content::NavigationController::LoadURLParams params; + int id; + }; + AtomBrowserContext* GetBrowserContext() const; uint32_t GetNextRequestId() { @@ -384,6 +401,11 @@ class WebContents : public mate::TrackableObject, // Whether to enable devtools. bool enable_devtools_; + // Container to hold url parms for deferred load when + // there is a pending navigation entry. + LoadURLParams deferred_load_url_; + content::NotificationRegistrar registrar_; + DISALLOW_COPY_AND_ASSIGN(WebContents); }; diff --git a/spec/api-browser-window-spec.js b/spec/api-browser-window-spec.js index 354befdb3f..9631de810c 100644 --- a/spec/api-browser-window-spec.js +++ b/spec/api-browser-window-spec.js @@ -229,6 +229,24 @@ describe('BrowserWindow module', function () { w.loadURL(`data:image/png;base64,${data}`) }) + it('should not crash when there is a pending navigation entry', function (done) { + const source = ` + + ` + w.webContents.on('did-fail-load', function (event, code, desc, url, isMainFrame) { + assert.equal(url, 'http://host/') + w.webContents.loadURL('about:blank') + }) + w.webContents.on('did-navigate', function (event, url) { + if (url === 'about:blank') done() + }) + w.loadURL(`data:text/html,${source}`) + }) + describe('POST navigations', function () { afterEach(() => { w.webContents.session.webRequest.onBeforeSendHeaders(null) From eb827eb020fea24a5deec8b5f2ffe1cbdc81aa14 Mon Sep 17 00:00:00 2001 From: deepak1556 Date: Wed, 1 Mar 2017 00:21:37 +0530 Subject: [PATCH 350/925] update spec --- spec/api-browser-window-spec.js | 17 ++--------------- spec/static/main.js | 29 +++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/spec/api-browser-window-spec.js b/spec/api-browser-window-spec.js index 9631de810c..c9fe34aef8 100644 --- a/spec/api-browser-window-spec.js +++ b/spec/api-browser-window-spec.js @@ -230,21 +230,8 @@ describe('BrowserWindow module', function () { }) it('should not crash when there is a pending navigation entry', function (done) { - const source = ` - - ` - w.webContents.on('did-fail-load', function (event, code, desc, url, isMainFrame) { - assert.equal(url, 'http://host/') - w.webContents.loadURL('about:blank') - }) - w.webContents.on('did-navigate', function (event, url) { - if (url === 'about:blank') done() - }) - w.loadURL(`data:text/html,${source}`) + ipcRenderer.once('navigated-with-pending-entry', () => done()) + ipcRenderer.send('navigate-with-pending-entry', w.id) }) describe('POST navigations', function () { diff --git a/spec/static/main.js b/spec/static/main.js index c36e86aeae..1e81ddc8cf 100644 --- a/spec/static/main.js +++ b/spec/static/main.js @@ -304,6 +304,35 @@ ipcMain.on('handle-unhandled-rejection', (event, message) => { }) }) +ipcMain.on('navigate-with-pending-entry', (event, id) => { + const w = BrowserWindow.fromId(id) + + const source = ` + + Link + + + ` + + w.webContents.on('did-fail-load', () => { + w.loadURL('about:blank') + }) + + w.webContents.on('did-navigate', (e, url) => { + if (url === 'about:blank') { + event.sender.send('navigated-with-pending-entry') + } + }) + + w.webContents.session.clearHostResolverCache(() => { + w.loadURL(`data:text/html,${source}`) + }) +}) + // Suspend listeners until the next event and then restore them const suspendListeners = (emitter, eventName, callback) => { const listeners = emitter.listeners(eventName) From e936143da202ceb43c891ed769d67f8e665769aa Mon Sep 17 00:00:00 2001 From: deepak1556 Date: Mon, 6 Mar 2017 00:21:52 +0530 Subject: [PATCH 351/925] listen for browser initiated pending nav entries too --- atom/browser/api/atom_api_web_contents.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 94de2e6d15..62384dfa84 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -841,8 +841,7 @@ void WebContents::Observe(int type, // instead in WebContents::LoadURL with controller.GetPendingEntry() // TODO(deepak1556): Remove once we have // https://codereview.chromium.org/2661743002. - if (entry_impl->is_renderer_initiated() && - entry_impl->frame_tree_node_id() == -1) { + if (entry_impl->frame_tree_node_id() == -1) { deferred_load_url_.id = entry->GetUniqueID(); } break; From 90885d1d8cf97ea62f9c4d6a508a678fd7cd14ef Mon Sep 17 00:00:00 2001 From: deepak1556 Date: Mon, 6 Mar 2017 00:22:20 +0530 Subject: [PATCH 352/925] simply spec --- spec/static/main.js | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/spec/static/main.js b/spec/static/main.js index 1e81ddc8cf..cfc21249b5 100644 --- a/spec/static/main.js +++ b/spec/static/main.js @@ -307,18 +307,7 @@ ipcMain.on('handle-unhandled-rejection', (event, message) => { ipcMain.on('navigate-with-pending-entry', (event, id) => { const w = BrowserWindow.fromId(id) - const source = ` - - Link - - - ` - - w.webContents.on('did-fail-load', () => { + w.webContents.on('did-start-loading', () => { w.loadURL('about:blank') }) @@ -329,7 +318,7 @@ ipcMain.on('navigate-with-pending-entry', (event, id) => { }) w.webContents.session.clearHostResolverCache(() => { - w.loadURL(`data:text/html,${source}`) + w.loadURL('http://host') }) }) From ef5c2bbded18e7f90b5153572270cb99fd8b9035 Mon Sep 17 00:00:00 2001 From: Yarkhan Date: Sun, 5 Mar 2017 20:40:18 -0300 Subject: [PATCH 353/925] Correcting typo --- docs/tutorial/windows-store-guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/windows-store-guide.md b/docs/tutorial/windows-store-guide.md index 2075691baa..4cca798aec 100644 --- a/docs/tutorial/windows-store-guide.md +++ b/docs/tutorial/windows-store-guide.md @@ -125,7 +125,7 @@ Cortana integration, or live tiles. To check out how an Electron app that uses a background task to send toast notifications and live tiles, [check out the Microsoft-provided sample][background-task]. -## Optional: Convert using Container Virtualiziation +## Optional: Convert using Container Virtualization To generate the AppX package, the `electron-windows-store` CLI uses a template that should work for most Electron apps. However, if you are using a custom From e485ff6cea16cbde08b8611a61e17a6e57c27a94 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 6 Mar 2017 09:48:11 -0800 Subject: [PATCH 354/925] Incorporate feedback --- ISSUE_TEMPLATE.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md index 1dac63b5c7..82803b0936 100644 --- a/ISSUE_TEMPLATE.md +++ b/ISSUE_TEMPLATE.md @@ -19,9 +19,12 @@ Thanks for opening an issue! A few things to keep in mind: ### How to reproduce + From 095e79b043810498b053f67ea35c004258579118 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Thu, 23 Feb 2017 09:55:05 +1100 Subject: [PATCH 355/925] Ensure the callback is a function when executing JS --- lib/browser/api/web-contents.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/browser/api/web-contents.js b/lib/browser/api/web-contents.js index 6c03800bcd..fe3259cedf 100644 --- a/lib/browser/api/web-contents.js +++ b/lib/browser/api/web-contents.js @@ -115,8 +115,8 @@ const asyncWebFrameMethods = function (requestId, method, callback, ...args) { this.send('ELECTRON_INTERNAL_RENDERER_ASYNC_WEB_FRAME_METHOD', requestId, method, args) ipcMain.once(`ELECTRON_INTERNAL_BROWSER_ASYNC_WEB_FRAME_RESPONSE_${requestId}`, function (event, error, result) { if (error == null) { - if (callback != null) callback(result) resolve(result) + if (typeof callback === 'function') callback(result) } else { reject(error) } From 6fcb784f6ec5d84559e6478e3da5511e136ca852 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 6 Mar 2017 10:49:23 -0800 Subject: [PATCH 356/925] Add failing spec for no callback --- spec/api-browser-window-spec.js | 10 +++++++++- spec/static/main.js | 19 +++++++++++++------ 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/spec/api-browser-window-spec.js b/spec/api-browser-window-spec.js index 4ee9c5b583..b5985e4b74 100644 --- a/spec/api-browser-window-spec.js +++ b/spec/api-browser-window-spec.js @@ -1851,7 +1851,7 @@ describe('BrowserWindow module', function () { }) }) - it('resolves the returned promise with the result', function (done) { + it('resolves the returned promise with the result when a callback is specified', function (done) { ipcRenderer.send('executeJavaScript', code, true) ipcRenderer.once('executeJavaScript-promise-response', function (event, result) { assert.equal(result, expected) @@ -1859,6 +1859,14 @@ describe('BrowserWindow module', function () { }) }) + it('resolves the returned promise with the result when no callback is specified', function (done) { + ipcRenderer.send('executeJavaScript', code, false) + ipcRenderer.once('executeJavaScript-promise-response', function (event, result) { + assert.equal(result, expected) + done() + }) + }) + it('resolves the returned promise with the result if the code returns an asyncronous promise', function (done) { ipcRenderer.send('executeJavaScript', asyncCode, true) ipcRenderer.once('executeJavaScript-promise-response', function (event, result) { diff --git a/spec/static/main.js b/spec/static/main.js index cfc21249b5..1ac489ecae 100644 --- a/spec/static/main.js +++ b/spec/static/main.js @@ -193,16 +193,23 @@ app.on('ready', function () { }) ipcMain.on('executeJavaScript', function (event, code, hasCallback) { + let promise + if (hasCallback) { - window.webContents.executeJavaScript(code, (result) => { + promise = window.webContents.executeJavaScript(code, (result) => { window.webContents.send('executeJavaScript-response', result) - }).then((result) => { - window.webContents.send('executeJavaScript-promise-response', result) - }).catch((err) => { - window.webContents.send('executeJavaScript-promise-error', err) }) } else { - window.webContents.executeJavaScript(code) + promise = window.webContents.executeJavaScript(code) + } + + promise.then((result) => { + window.webContents.send('executeJavaScript-promise-response', result) + }).catch((error) => { + window.webContents.send('executeJavaScript-promise-error', error) + }) + + if (!hasCallback) { event.returnValue = 'success' } }) From 6240e30be15a7bc82a2b3f3be4397a2072ca41b7 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 6 Mar 2017 10:51:17 -0800 Subject: [PATCH 357/925] Default hasUserGesture to false when null --- lib/browser/api/web-contents.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/browser/api/web-contents.js b/lib/browser/api/web-contents.js index fe3259cedf..6af96e1757 100644 --- a/lib/browser/api/web-contents.js +++ b/lib/browser/api/web-contents.js @@ -115,8 +115,8 @@ const asyncWebFrameMethods = function (requestId, method, callback, ...args) { this.send('ELECTRON_INTERNAL_RENDERER_ASYNC_WEB_FRAME_METHOD', requestId, method, args) ipcMain.once(`ELECTRON_INTERNAL_BROWSER_ASYNC_WEB_FRAME_RESPONSE_${requestId}`, function (event, error, result) { if (error == null) { - resolve(result) if (typeof callback === 'function') callback(result) + resolve(result) } else { reject(error) } @@ -149,10 +149,17 @@ for (const method of webFrameMethodsWithResult) { // WebContents has been loaded. WebContents.prototype.executeJavaScript = function (code, hasUserGesture, callback) { const requestId = getNextId() + if (typeof hasUserGesture === 'function') { + // Shift. callback = hasUserGesture + hasUserGesture = null + } + + if (hasUserGesture == null) { hasUserGesture = false } + if (this.getURL() && !this.isLoadingMainFrame()) { return asyncWebFrameMethods.call(this, requestId, 'executeJavaScript', callback, code, hasUserGesture) } else { From 6bc464d4b060349155db5e43df1761e3036b8c05 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 6 Mar 2017 10:51:48 -0800 Subject: [PATCH 358/925] Mention userGesture default --- docs/api/web-contents.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index 5e3e8dd697..a665fae963 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -541,7 +541,7 @@ that can't be set via `` attributes. * `userAgent` String (optional) - A user agent originating the request. * `extraHeaders` String (optional) - Extra headers separated by "\n" * `postData` ([UploadRawData](structures/upload-raw-data.md) | [UploadFile](structures/upload-file.md) | [UploadFileSystem](structures/upload-file-system.md) | [UploadBlob](structures/upload-blob.md))[] - (optional) - * `baseURLForDataURL` String (optional) - Base url (with trailing path separator) for files to be loaded by the data url. This is needed only if the specified `url` is a data url and needs to load other files. + * `baseURLForDataURL` String (optional) - Base url (with trailing path separator) for files to be loaded by the data url. This is needed only if the specified `url` is a data url and needs to load other files. Loads the `url` in the window. The `url` must contain the protocol prefix, e.g. the `http://` or `file://`. If the load should bypass http cache then @@ -672,7 +672,7 @@ Injects CSS into the current web page. #### `contents.executeJavaScript(code[, userGesture, callback])` * `code` String -* `userGesture` Boolean (optional) +* `userGesture` Boolean (optional) - Default is `false`. * `callback` Function (optional) - Called after script has been executed. * `result` Any From 5a4d9807ee2f140186592919cea12c9687f0c370 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Tue, 7 Mar 2017 11:21:51 +0100 Subject: [PATCH 359/925] Update `ses.resolveProxy` return type As far as I'm aware, after using the `ses.resolveProxy` API, it seems to return a string in the shape of `PROXY foopy:80;SOCKS5 bar.com:1080`, not an object. Correct me if I'm wrong. --- docs/api/session.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/session.md b/docs/api/session.md index 029b18787c..591ac14bde 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -204,7 +204,7 @@ The `proxyBypassRules` is a comma separated list of rules described below: * `url` URL * `callback` Function - * `proxy` Object + * `proxy` string Resolves the proxy information for `url`. The `callback` will be called with `callback(proxy)` when the request is performed. From f97066511f77ef1f1b8ae1bf8bad638a2629e7b8 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Tue, 7 Mar 2017 21:25:40 +1100 Subject: [PATCH 360/925] Capitalize type --- docs/api/session.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/session.md b/docs/api/session.md index 591ac14bde..7c7831c8ac 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -204,7 +204,7 @@ The `proxyBypassRules` is a comma separated list of rules described below: * `url` URL * `callback` Function - * `proxy` string + * `proxy` String Resolves the proxy information for `url`. The `callback` will be called with `callback(proxy)` when the request is performed. From bb8d42f1e195eed273bc0efb3c131579099792e5 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 7 Mar 2017 09:42:45 -0800 Subject: [PATCH 361/925] Mark TouchBar as experimental --- docs/api/browser-window.md | 5 ++++- docs/api/touch-bar.md | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index 9463ee0cca..fe870ecb36 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -1004,7 +1004,7 @@ Same as `webContents.capturePage([rect, ]callback)`. * `userAgent` String (optional) - A user agent originating the request. * `extraHeaders` String (optional) - Extra headers separated by "\n" * `postData` ([UploadRawData](structures/upload-raw-data.md) | [UploadFile](structures/upload-file.md) | [UploadFileSystem](structures/upload-file-system.md) | [UploadBlob](structures/upload-blob.md))[] - (optional) - * `baseURLForDataURL` String (optional) - Base url (with trailing path separator) for files to be loaded by the data url. This is needed only if the specified `url` is a data url and needs to load other files. + * `baseURLForDataURL` String (optional) - Base url (with trailing path separator) for files to be loaded by the data url. This is needed only if the specified `url` is a data url and needs to load other files. Same as `webContents.loadURL(url[, options])`. @@ -1275,6 +1275,9 @@ Sets the touchBar layout for the current window. Specifying `null` or `undefined` clears the touch bar. This method only has an effect if the machine has a touch bar and is running on macOS 10.12.1+. +**Note:** The TouchBar API is currently experimental and may change or be +removed in future Electron releases. + [blink-feature-string]: https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.json5?l=62 [quick-look]: https://en.wikipedia.org/wiki/Quick_Look [vibrancy-docs]: https://developer.apple.com/reference/appkit/nsvisualeffectview?language=objc diff --git a/docs/api/touch-bar.md b/docs/api/touch-bar.md index 2ca8c1ef32..8f0f354496 100644 --- a/docs/api/touch-bar.md +++ b/docs/api/touch-bar.md @@ -11,6 +11,9 @@ Process: [Main](../tutorial/quick-start.md#main-process) Creates a new touch bar with the specified items. Use `BrowserWindow.setTouchBar` to add the `TouchBar` to a window. +**Note:** The TouchBar API is currently experimental and may change or be +removed in future Electron releases. + ## Examples Below is an example of a simple slot machine touch bar game with a button From e0260d6f534c39f4a3ad13541cd7e825af60697b Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 7 Mar 2017 09:49:39 -0800 Subject: [PATCH 362/925] Bump v1.6.3 --- atom/browser/resources/mac/Info.plist | 4 ++-- atom/browser/resources/win/atom.rc | 8 ++++---- atom/common/atom_version.h | 2 +- electron.gyp | 2 +- package.json | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/atom/browser/resources/mac/Info.plist b/atom/browser/resources/mac/Info.plist index 48160b9e33..9a1d671324 100644 --- a/atom/browser/resources/mac/Info.plist +++ b/atom/browser/resources/mac/Info.plist @@ -17,9 +17,9 @@ CFBundleIconFile electron.icns CFBundleVersion - 1.6.2 + 1.6.3 CFBundleShortVersionString - 1.6.2 + 1.6.3 LSApplicationCategoryType public.app-category.developer-tools LSMinimumSystemVersion diff --git a/atom/browser/resources/win/atom.rc b/atom/browser/resources/win/atom.rc index c94af0f2f2..4a500e3e64 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,6,2,0 - PRODUCTVERSION 1,6,2,0 + FILEVERSION 1,6,3,0 + PRODUCTVERSION 1,6,3,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -74,12 +74,12 @@ BEGIN BEGIN VALUE "CompanyName", "GitHub, Inc." VALUE "FileDescription", "Electron" - VALUE "FileVersion", "1.6.2" + VALUE "FileVersion", "1.6.3" VALUE "InternalName", "electron.exe" VALUE "LegalCopyright", "Copyright (C) 2015 GitHub, Inc. All rights reserved." VALUE "OriginalFilename", "electron.exe" VALUE "ProductName", "Electron" - VALUE "ProductVersion", "1.6.2" + VALUE "ProductVersion", "1.6.3" VALUE "SquirrelAwareVersion", "1" END END diff --git a/atom/common/atom_version.h b/atom/common/atom_version.h index d3bd04ca01..155d9566f0 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 6 -#define ATOM_PATCH_VERSION 2 +#define ATOM_PATCH_VERSION 3 #define ATOM_VERSION_IS_RELEASE 1 diff --git a/electron.gyp b/electron.gyp index 9fd7c1091f..299fe34ea6 100644 --- a/electron.gyp +++ b/electron.gyp @@ -4,7 +4,7 @@ 'product_name%': 'Electron', 'company_name%': 'GitHub, Inc', 'company_abbr%': 'github', - 'version%': '1.6.2', + 'version%': '1.6.3', 'js2c_input_dir': '<(SHARED_INTERMEDIATE_DIR)/js2c', }, 'includes': [ diff --git a/package.json b/package.json index 33be7e6068..a572a1918e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "electron", - "version": "1.6.2", + "version": "1.6.3", "devDependencies": { "asar": "^0.11.0", "browserify": "^13.1.0", From dbe9893d297c286d81c814ed39784a2a2ae5c28f Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 6 Mar 2017 11:23:28 -0800 Subject: [PATCH 363/925] Assign Promise.resolve to constant --- lib/renderer/api/remote.js | 5 ++--- lib/renderer/init.js | 3 ++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/renderer/api/remote.js b/lib/renderer/api/remote.js index e524ecc720..fd94390d77 100644 --- a/lib/renderer/api/remote.js +++ b/lib/renderer/api/remote.js @@ -3,6 +3,7 @@ const {Buffer} = require('buffer') const v8Util = process.atomBinding('v8_util') const {ipcRenderer, isPromise, CallbacksRegistry} = require('electron') +const resolvePromise = Promise.resolve.bind(Promise) const callbacksRegistry = new CallbacksRegistry() @@ -207,9 +208,7 @@ const metaToValue = function (meta) { case 'buffer': return Buffer.from(meta.value) case 'promise': - return Promise.resolve({ - then: metaToValue(meta.then) - }) + return resolvePromise({then: metaToValue(meta.then)}) case 'error': return metaToPlainObject(meta) case 'date': diff --git a/lib/renderer/init.js b/lib/renderer/init.js index d9a05592e9..24e19b9d2a 100644 --- a/lib/renderer/init.js +++ b/lib/renderer/init.js @@ -3,6 +3,7 @@ const events = require('events') const path = require('path') const Module = require('module') +const resolvePromise = Promise.resolve.bind(Promise) // We modified the original process.argv to let node.js load the // atom-renderer.js, we need to restore it here. @@ -39,7 +40,7 @@ electron.ipcRenderer.on('ELECTRON_INTERNAL_RENDERER_SYNC_WEB_FRAME_METHOD', (eve electron.ipcRenderer.on('ELECTRON_INTERNAL_RENDERER_ASYNC_WEB_FRAME_METHOD', (event, requestId, method, args) => { const responseCallback = function (result) { - Promise.resolve(result) + resolvePromise(result) .then((resolvedResult) => { event.sender.send(`ELECTRON_INTERNAL_BROWSER_ASYNC_WEB_FRAME_RESPONSE_${requestId}`, null, resolvedResult) }) From b46e48cae1da2c20387ed6a5b0ad1829c08289ca Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 6 Mar 2017 13:15:07 -0800 Subject: [PATCH 364/925] Add failing toDataURL spec --- spec/api-native-image-spec.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/spec/api-native-image-spec.js b/spec/api-native-image-spec.js index e28b37bebf..90d1845081 100644 --- a/spec/api-native-image-spec.js +++ b/spec/api-native-image-spec.js @@ -79,6 +79,23 @@ describe('nativeImage module', () => { }) }) + describe('toDataURL()', () => { + it('returns a data URL at 1x scale factor', () => { + const imageA = nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', 'logo.png')) + const imageB = nativeImage.createFromBuffer(imageA.toPNG(), { + width: imageA.getSize().width, + height: imageA.getSize().height, + scaleFactor: 2.0 + }) + assert.deepEqual(imageB.getSize(), {width: 269, height: 95}) + assert.equal(imageB.hasRepresentation(1.0), false) + + const imageC = nativeImage.createFromDataURL(imageB.toDataURL()) + assert.deepEqual(imageC.getSize(), {width: 538, height: 190}) + assert(imageB.toBitmap().equals(imageC.toBitmap())) + }) + }) + describe('createFromPath(path)', () => { it('returns an empty image for invalid paths', () => { assert(nativeImage.createFromPath('').isEmpty()) From 8eaf48e552cffef1b4c30676f15cfd67b27f3754 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 6 Mar 2017 13:22:55 -0800 Subject: [PATCH 365/925] Add spec for 1x1 PNG data URL --- spec/api-native-image-spec.js | 5 +++++ spec/fixtures/assets/1x1.png | Bin 0 -> 68 bytes 2 files changed, 5 insertions(+) create mode 100644 spec/fixtures/assets/1x1.png diff --git a/spec/api-native-image-spec.js b/spec/api-native-image-spec.js index 90d1845081..94492ca400 100644 --- a/spec/api-native-image-spec.js +++ b/spec/api-native-image-spec.js @@ -80,6 +80,11 @@ describe('nativeImage module', () => { }) describe('toDataURL()', () => { + it('returns a PNG data URL', () => { + const imageA = nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', '1x1.png')) + assert.equal(imageA.toDataURL(), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQYlWNgAAIAAAUAAdafFs0AAAAASUVORK5CYII=') + }) + it('returns a data URL at 1x scale factor', () => { const imageA = nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', 'logo.png')) const imageB = nativeImage.createFromBuffer(imageA.toPNG(), { diff --git a/spec/fixtures/assets/1x1.png b/spec/fixtures/assets/1x1.png new file mode 100644 index 0000000000000000000000000000000000000000..427fb75b86287ab01a6feec0c2b402153671b6da GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6UNeMs>Cj+yj;LRsM O7K5j&pUXO@geCx0rwkSV literal 0 HcmV?d00001 From 0bbbeb307e1b756ff86f5ba5d2bdfe263c721f5e Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 6 Mar 2017 13:24:50 -0800 Subject: [PATCH 366/925] Support toDataURL without 1x representation --- atom/common/api/atom_api_native_image.cc | 18 ++++++++++++------ atom/common/api/atom_api_native_image.h | 1 + spec/api-native-image-spec.js | 1 + 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/atom/common/api/atom_api_native_image.cc b/atom/common/api/atom_api_native_image.cc index 0c174941db..95531b1ddf 100644 --- a/atom/common/api/atom_api_native_image.cc +++ b/atom/common/api/atom_api_native_image.cc @@ -21,6 +21,7 @@ #include "net/base/data_url.h" #include "third_party/skia/include/core/SkPixelRef.h" #include "ui/base/layout.h" +#include "ui/base/webui/web_ui_util.h" #include "ui/gfx/codec/jpeg_codec.h" #include "ui/gfx/codec/png_codec.h" #include "ui/gfx/geometry/size.h" @@ -257,12 +258,12 @@ v8::Local NativeImage::ToJPEG(v8::Isolate* isolate, int quality) { } std::string NativeImage::ToDataURL() { - scoped_refptr png = image_.As1xPNGBytes(); - std::string data_url; - data_url.insert(data_url.end(), png->front(), png->front() + png->size()); - base::Base64Encode(data_url, &data_url); - data_url.insert(0, "data:image/png;base64,"); - return data_url; + if (HasRepresentation(1.0)) { + scoped_refptr png = image_.As1xPNGBytes(); + return webui::GetPngDataUrl(png->front(), png->size()); + } else { + return webui::GetBitmapDataUrl(image_.AsBitmap()); + } } v8::Local NativeImage::GetBitmap(v8::Isolate* isolate) { @@ -297,6 +298,10 @@ bool NativeImage::IsEmpty() { return image_.IsEmpty(); } +bool NativeImage::HasRepresentation(float scale_factor) { + return image_.AsImageSkia().HasRepresentation(scale_factor); +} + gfx::Size NativeImage::GetSize() { return image_.Size(); } @@ -468,6 +473,7 @@ void NativeImage::BuildPrototype( .SetMethod("resize", &NativeImage::Resize) .SetMethod("crop", &NativeImage::Crop) .SetMethod("getAspectRatio", &NativeImage::GetAspectRatio) + .SetMethod("hasRepresentation", &NativeImage::HasRepresentation) // TODO(kevinsawicki): Remove in 2.0, deprecate before then with warnings .SetMethod("toPng", &NativeImage::ToPNG) .SetMethod("toJpeg", &NativeImage::ToJPEG); diff --git a/atom/common/api/atom_api_native_image.h b/atom/common/api/atom_api_native_image.h index ee1b5f5d4b..a653f15e0a 100644 --- a/atom/common/api/atom_api_native_image.h +++ b/atom/common/api/atom_api_native_image.h @@ -83,6 +83,7 @@ class NativeImage : public mate::Wrappable { const gfx::Rect& rect); std::string ToDataURL(); bool IsEmpty(); + bool HasRepresentation(float scale_factor); gfx::Size GetSize(); float GetAspectRatio(); diff --git a/spec/api-native-image-spec.js b/spec/api-native-image-spec.js index 94492ca400..9aa0ced3ef 100644 --- a/spec/api-native-image-spec.js +++ b/spec/api-native-image-spec.js @@ -9,6 +9,7 @@ describe('nativeImage module', () => { it('returns an empty image', () => { const empty = nativeImage.createEmpty() assert.equal(empty.isEmpty(), true) + assert.equal(empty.hasRepresentation(1.0), false) assert.equal(empty.getAspectRatio(), 1) assert.equal(empty.toDataURL(), 'data:image/png;base64,') assert.deepEqual(empty.getSize(), {width: 0, height: 0}) From 50ade57700fe6e48f3a1b975ab484cc093376317 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 6 Mar 2017 13:26:55 -0800 Subject: [PATCH 367/925] Add more hasRepresentation asserts --- spec/api-native-image-spec.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/api-native-image-spec.js b/spec/api-native-image-spec.js index 9aa0ced3ef..922717fab4 100644 --- a/spec/api-native-image-spec.js +++ b/spec/api-native-image-spec.js @@ -95,9 +95,11 @@ describe('nativeImage module', () => { }) assert.deepEqual(imageB.getSize(), {width: 269, height: 95}) assert.equal(imageB.hasRepresentation(1.0), false) + assert.equal(imageB.hasRepresentation(2.0), true) const imageC = nativeImage.createFromDataURL(imageB.toDataURL()) assert.deepEqual(imageC.getSize(), {width: 538, height: 190}) + assert.equal(imageC.hasRepresentation(1.0), false) assert(imageB.toBitmap().equals(imageC.toBitmap())) }) }) From 86babdd4122d7e7bea03a937f78de6ed051487cf Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 6 Mar 2017 13:36:40 -0800 Subject: [PATCH 368/925] Remove unused include --- atom/common/api/atom_api_native_image.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/atom/common/api/atom_api_native_image.cc b/atom/common/api/atom_api_native_image.cc index 95531b1ddf..5859247574 100644 --- a/atom/common/api/atom_api_native_image.cc +++ b/atom/common/api/atom_api_native_image.cc @@ -12,7 +12,6 @@ #include "atom/common/native_mate_converters/gfx_converter.h" #include "atom/common/native_mate_converters/gurl_converter.h" #include "atom/common/native_mate_converters/value_converter.h" -#include "base/base64.h" #include "base/files/file_util.h" #include "base/strings/pattern.h" #include "base/strings/string_util.h" From 8bc65ef495437ddf5e3a974ccb787bcff7cf9915 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 6 Mar 2017 13:47:19 -0800 Subject: [PATCH 369/925] Add failing spec for toPNG with scale factor --- spec/api-native-image-spec.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/spec/api-native-image-spec.js b/spec/api-native-image-spec.js index 922717fab4..fd6aee9f1e 100644 --- a/spec/api-native-image-spec.js +++ b/spec/api-native-image-spec.js @@ -104,6 +104,25 @@ describe('nativeImage module', () => { }) }) + describe('toPNG()', () => { + it('returns a buffer at 1x scale factor', () => { + const imageA = nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', 'logo.png')) + const imageB = nativeImage.createFromBuffer(imageA.toPNG(), { + width: imageA.getSize().width, + height: imageA.getSize().height, + scaleFactor: 2.0 + }) + assert.deepEqual(imageB.getSize(), {width: 269, height: 95}) + assert.equal(imageB.hasRepresentation(1.0), false) + assert.equal(imageB.hasRepresentation(2.0), true) + + const imageC = nativeImage.createFromBuffer(imageB.toPNG()) + assert.deepEqual(imageC.getSize(), {width: 538, height: 190}) + assert.equal(imageC.hasRepresentation(1.0), true) + assert(imageB.toBitmap().equals(imageC.toBitmap())) + }) + }) + describe('createFromPath(path)', () => { it('returns an empty image for invalid paths', () => { assert(nativeImage.createFromPath('').isEmpty()) From c11cdf321bf99c73be81b92e54f28995a31f4ac2 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 6 Mar 2017 13:47:37 -0800 Subject: [PATCH 370/925] Support toPNG on image without 1x representation --- atom/common/api/atom_api_native_image.cc | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/atom/common/api/atom_api_native_image.cc b/atom/common/api/atom_api_native_image.cc index 5859247574..b68ddda592 100644 --- a/atom/common/api/atom_api_native_image.cc +++ b/atom/common/api/atom_api_native_image.cc @@ -231,10 +231,17 @@ HICON NativeImage::GetHICON(int size) { #endif v8::Local NativeImage::ToPNG(v8::Isolate* isolate) { - scoped_refptr png = image_.As1xPNGBytes(); - return node::Buffer::Copy(isolate, - reinterpret_cast(png->front()), - static_cast(png->size())).ToLocalChecked(); + if (HasRepresentation(1.0)) { + scoped_refptr png = image_.As1xPNGBytes(); + const char* data = reinterpret_cast(png->front()); + const size_t length = static_cast(png->size()); + return node::Buffer::Copy(isolate, data, length).ToLocalChecked(); + } else { + std::vector encoded; + gfx::PNGCodec::EncodeBGRASkBitmap(image_.AsBitmap(), false, &encoded); + const char* data = reinterpret_cast(encoded.data()); + return node::Buffer::Copy(isolate, data, encoded.size()).ToLocalChecked(); + } } v8::Local NativeImage::ToBitmap(v8::Isolate* isolate) { From e4ead6d0189938023b1c6a77f4aa76a8d98e6c9c Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 6 Mar 2017 14:55:57 -0800 Subject: [PATCH 371/925] Remove hasRepresentation and just use 1x png result --- atom/common/api/atom_api_native_image.cc | 16 +++++----------- atom/common/api/atom_api_native_image.h | 1 - spec/api-native-image-spec.js | 7 ------- 3 files changed, 5 insertions(+), 19 deletions(-) diff --git a/atom/common/api/atom_api_native_image.cc b/atom/common/api/atom_api_native_image.cc index b68ddda592..7437090111 100644 --- a/atom/common/api/atom_api_native_image.cc +++ b/atom/common/api/atom_api_native_image.cc @@ -231,8 +231,8 @@ HICON NativeImage::GetHICON(int size) { #endif v8::Local NativeImage::ToPNG(v8::Isolate* isolate) { - if (HasRepresentation(1.0)) { - scoped_refptr png = image_.As1xPNGBytes(); + scoped_refptr png = image_.As1xPNGBytes(); + if (IsEmpty() || png->size() > 0) { const char* data = reinterpret_cast(png->front()); const size_t length = static_cast(png->size()); return node::Buffer::Copy(isolate, data, length).ToLocalChecked(); @@ -264,12 +264,11 @@ v8::Local NativeImage::ToJPEG(v8::Isolate* isolate, int quality) { } std::string NativeImage::ToDataURL() { - if (HasRepresentation(1.0)) { - scoped_refptr png = image_.As1xPNGBytes(); + scoped_refptr png = image_.As1xPNGBytes(); + if (IsEmpty() || png->size() > 0) return webui::GetPngDataUrl(png->front(), png->size()); - } else { + else return webui::GetBitmapDataUrl(image_.AsBitmap()); - } } v8::Local NativeImage::GetBitmap(v8::Isolate* isolate) { @@ -304,10 +303,6 @@ bool NativeImage::IsEmpty() { return image_.IsEmpty(); } -bool NativeImage::HasRepresentation(float scale_factor) { - return image_.AsImageSkia().HasRepresentation(scale_factor); -} - gfx::Size NativeImage::GetSize() { return image_.Size(); } @@ -479,7 +474,6 @@ void NativeImage::BuildPrototype( .SetMethod("resize", &NativeImage::Resize) .SetMethod("crop", &NativeImage::Crop) .SetMethod("getAspectRatio", &NativeImage::GetAspectRatio) - .SetMethod("hasRepresentation", &NativeImage::HasRepresentation) // TODO(kevinsawicki): Remove in 2.0, deprecate before then with warnings .SetMethod("toPng", &NativeImage::ToPNG) .SetMethod("toJpeg", &NativeImage::ToJPEG); diff --git a/atom/common/api/atom_api_native_image.h b/atom/common/api/atom_api_native_image.h index a653f15e0a..ee1b5f5d4b 100644 --- a/atom/common/api/atom_api_native_image.h +++ b/atom/common/api/atom_api_native_image.h @@ -83,7 +83,6 @@ class NativeImage : public mate::Wrappable { const gfx::Rect& rect); std::string ToDataURL(); bool IsEmpty(); - bool HasRepresentation(float scale_factor); gfx::Size GetSize(); float GetAspectRatio(); diff --git a/spec/api-native-image-spec.js b/spec/api-native-image-spec.js index fd6aee9f1e..7e32fef561 100644 --- a/spec/api-native-image-spec.js +++ b/spec/api-native-image-spec.js @@ -9,7 +9,6 @@ describe('nativeImage module', () => { it('returns an empty image', () => { const empty = nativeImage.createEmpty() assert.equal(empty.isEmpty(), true) - assert.equal(empty.hasRepresentation(1.0), false) assert.equal(empty.getAspectRatio(), 1) assert.equal(empty.toDataURL(), 'data:image/png;base64,') assert.deepEqual(empty.getSize(), {width: 0, height: 0}) @@ -94,12 +93,9 @@ describe('nativeImage module', () => { scaleFactor: 2.0 }) assert.deepEqual(imageB.getSize(), {width: 269, height: 95}) - assert.equal(imageB.hasRepresentation(1.0), false) - assert.equal(imageB.hasRepresentation(2.0), true) const imageC = nativeImage.createFromDataURL(imageB.toDataURL()) assert.deepEqual(imageC.getSize(), {width: 538, height: 190}) - assert.equal(imageC.hasRepresentation(1.0), false) assert(imageB.toBitmap().equals(imageC.toBitmap())) }) }) @@ -113,12 +109,9 @@ describe('nativeImage module', () => { scaleFactor: 2.0 }) assert.deepEqual(imageB.getSize(), {width: 269, height: 95}) - assert.equal(imageB.hasRepresentation(1.0), false) - assert.equal(imageB.hasRepresentation(2.0), true) const imageC = nativeImage.createFromBuffer(imageB.toPNG()) assert.deepEqual(imageC.getSize(), {width: 538, height: 190}) - assert.equal(imageC.hasRepresentation(1.0), true) assert(imageB.toBitmap().equals(imageC.toBitmap())) }) }) From 82a81bb26e4c6a83734c87fbbae0ded584e09eae Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 6 Mar 2017 16:19:16 -0800 Subject: [PATCH 372/925] Support scale factor to buffer APIs --- atom/common/api/atom_api_native_image.cc | 93 ++++++++++++++++-------- atom/common/api/atom_api_native_image.h | 8 +- spec/api-native-image-spec.js | 12 +++ 3 files changed, 79 insertions(+), 34 deletions(-) diff --git a/atom/common/api/atom_api_native_image.cc b/atom/common/api/atom_api_native_image.cc index 7437090111..e63fff3a0c 100644 --- a/atom/common/api/atom_api_native_image.cc +++ b/atom/common/api/atom_api_native_image.cc @@ -230,28 +230,46 @@ HICON NativeImage::GetHICON(int size) { } #endif -v8::Local NativeImage::ToPNG(v8::Isolate* isolate) { - scoped_refptr png = image_.As1xPNGBytes(); - if (IsEmpty() || png->size() > 0) { - const char* data = reinterpret_cast(png->front()); - const size_t length = static_cast(png->size()); - return node::Buffer::Copy(isolate, data, length).ToLocalChecked(); - } else { - std::vector encoded; - gfx::PNGCodec::EncodeBGRASkBitmap(image_.AsBitmap(), false, &encoded); - const char* data = reinterpret_cast(encoded.data()); - return node::Buffer::Copy(isolate, data, encoded.size()).ToLocalChecked(); +v8::Local NativeImage::ToPNG(mate::Arguments* args) { + float scale_factor = 1.0f; + mate::Dictionary options; + if (args->GetNext(&options)) + options.Get("scaleFactor", &scale_factor); + + if (scale_factor == 1.0f) { + // Use raw 1x PNG bytes when available + scoped_refptr png = image_.As1xPNGBytes(); + if (png->size() > 0) { + const char* data = reinterpret_cast(png->front()); + size_t size = png->size(); + return node::Buffer::Copy(args->isolate(), data, size).ToLocalChecked(); + } } + + const SkBitmap bitmap = + image_.AsImageSkia().GetRepresentation(scale_factor).sk_bitmap(); + std::unique_ptr> encoded( + new std::vector()); + gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, false, encoded.get()); + const char* data = reinterpret_cast(encoded->data()); + size_t size = encoded->size(); + return node::Buffer::Copy(args->isolate(), data, size).ToLocalChecked(); } -v8::Local NativeImage::ToBitmap(v8::Isolate* isolate) { - if (IsEmpty()) return node::Buffer::New(isolate, 0).ToLocalChecked(); +v8::Local NativeImage::ToBitmap(mate::Arguments* args) { + float scale_factor = 1.0f; + mate::Dictionary options; + if (args->GetNext(&options)) + options.Get("scaleFactor", &scale_factor); - const SkBitmap* bitmap = image_.ToSkBitmap(); - SkPixelRef* ref = bitmap->pixelRef(); - return node::Buffer::Copy(isolate, + const SkBitmap bitmap = + image_.AsImageSkia().GetRepresentation(scale_factor).sk_bitmap(); + SkPixelRef* ref = bitmap.pixelRef(); + if (!ref) + return node::Buffer::New(args->isolate(), 0).ToLocalChecked(); + return node::Buffer::Copy(args->isolate(), reinterpret_cast(ref->pixels()), - bitmap->getSafeSize()).ToLocalChecked(); + bitmap.getSafeSize()).ToLocalChecked(); } v8::Local NativeImage::ToJPEG(v8::Isolate* isolate, int quality) { @@ -260,25 +278,40 @@ v8::Local NativeImage::ToJPEG(v8::Isolate* isolate, int quality) { return node::Buffer::Copy( isolate, reinterpret_cast(&output.front()), - static_cast(output.size())).ToLocalChecked(); + output.size()).ToLocalChecked(); } -std::string NativeImage::ToDataURL() { - scoped_refptr png = image_.As1xPNGBytes(); - if (IsEmpty() || png->size() > 0) - return webui::GetPngDataUrl(png->front(), png->size()); - else - return webui::GetBitmapDataUrl(image_.AsBitmap()); +std::string NativeImage::ToDataURL(mate::Arguments* args) { + float scale_factor = 1.0f; + mate::Dictionary options; + if (args->GetNext(&options)) + options.Get("scaleFactor", &scale_factor); + + if (scale_factor == 1.0f) { + // Use raw 1x PNG bytes when available + scoped_refptr png = image_.As1xPNGBytes(); + if (png->size() > 0) + return webui::GetPngDataUrl(png->front(), png->size()); + } + + return webui::GetBitmapDataUrl( + image_.AsImageSkia().GetRepresentation(scale_factor).sk_bitmap()); } -v8::Local NativeImage::GetBitmap(v8::Isolate* isolate) { - if (IsEmpty()) return node::Buffer::New(isolate, 0).ToLocalChecked(); +v8::Local NativeImage::GetBitmap(mate::Arguments* args) { + float scale_factor = 1.0f; + mate::Dictionary options; + if (args->GetNext(&options)) + options.Get("scaleFactor", &scale_factor); - const SkBitmap* bitmap = image_.ToSkBitmap(); - SkPixelRef* ref = bitmap->pixelRef(); - return node::Buffer::New(isolate, + const SkBitmap bitmap = + image_.AsImageSkia().GetRepresentation(scale_factor).sk_bitmap(); + SkPixelRef* ref = bitmap.pixelRef(); + if (!ref) + return node::Buffer::New(args->isolate(), 0).ToLocalChecked(); + return node::Buffer::New(args->isolate(), reinterpret_cast(ref->pixels()), - bitmap->getSafeSize(), + bitmap.getSafeSize(), &Noop, nullptr).ToLocalChecked(); } diff --git a/atom/common/api/atom_api_native_image.h b/atom/common/api/atom_api_native_image.h index ee1b5f5d4b..a6614a81b0 100644 --- a/atom/common/api/atom_api_native_image.h +++ b/atom/common/api/atom_api_native_image.h @@ -70,10 +70,10 @@ class NativeImage : public mate::Wrappable { ~NativeImage() override; private: - v8::Local ToPNG(v8::Isolate* isolate); + v8::Local ToPNG(mate::Arguments* args); v8::Local ToJPEG(v8::Isolate* isolate, int quality); - v8::Local ToBitmap(v8::Isolate* isolate); - v8::Local GetBitmap(v8::Isolate* isolate); + v8::Local ToBitmap(mate::Arguments* args); + v8::Local GetBitmap(mate::Arguments* args); v8::Local GetNativeHandle( v8::Isolate* isolate, mate::Arguments* args); @@ -81,7 +81,7 @@ class NativeImage : public mate::Wrappable { const base::DictionaryValue& options); mate::Handle Crop(v8::Isolate* isolate, const gfx::Rect& rect); - std::string ToDataURL(); + std::string ToDataURL(mate::Arguments* args); bool IsEmpty(); gfx::Size GetSize(); float GetAspectRatio(); diff --git a/spec/api-native-image-spec.js b/spec/api-native-image-spec.js index 7e32fef561..f8a5708edc 100644 --- a/spec/api-native-image-spec.js +++ b/spec/api-native-image-spec.js @@ -11,11 +11,15 @@ describe('nativeImage module', () => { assert.equal(empty.isEmpty(), true) assert.equal(empty.getAspectRatio(), 1) assert.equal(empty.toDataURL(), 'data:image/png;base64,') + assert.equal(empty.toDataURL({scaleFactor: 2.0}), 'data:image/png;base64,') assert.deepEqual(empty.getSize(), {width: 0, height: 0}) assert.deepEqual(empty.getBitmap(), []) + assert.deepEqual(empty.getBitmap({scaleFactor: 2.0}), []) assert.deepEqual(empty.toBitmap(), []) + assert.deepEqual(empty.toBitmap({scaleFactor: 2.0}), []) assert.deepEqual(empty.toJPEG(100), []) assert.deepEqual(empty.toPNG(), []) + assert.deepEqual(empty.toPNG({scaleFactor: 2.0}), []) if (process.platform === 'darwin') { assert.deepEqual(empty.getNativeHandle(), []) @@ -98,6 +102,14 @@ describe('nativeImage module', () => { assert.deepEqual(imageC.getSize(), {width: 538, height: 190}) assert(imageB.toBitmap().equals(imageC.toBitmap())) }) + + it('supports a scale factor', () => { + const imageA = nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', 'logo.png')) + const imageB = nativeImage.createFromDataURL(imageA.toDataURL({scaleFactor: 1.0})) + assert.deepEqual(imageB.getSize(), {width: 538, height: 190}) + const imageC = nativeImage.createFromDataURL(imageA.toDataURL({scaleFactor: 2.0})) + assert.deepEqual(imageC.getSize(), {width: 538, height: 190}) + }) }) describe('toPNG()', () => { From 6d5fbe6be23ea6889be2c9bd14ce7a23c8660776 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 6 Mar 2017 16:22:48 -0800 Subject: [PATCH 373/925] Document scale factor option --- docs/api/native-image.md | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/docs/api/native-image.md b/docs/api/native-image.md index cc910cbb8a..0350f13bc9 100644 --- a/docs/api/native-image.md +++ b/docs/api/native-image.md @@ -165,7 +165,10 @@ Process: [Main](../glossary.md#main-process), [Renderer](../glossary.md#renderer The following methods are available on instances of the `NativeImage` class: -#### `image.toPNG()` +#### `image.toPNG([options])` + +* `options` Object (optional) + * `scaleFactor` Double (optional) - Defaults to 1.0. Returns `Buffer` - A [Buffer][buffer] that contains the image's `PNG` encoded data. @@ -175,16 +178,25 @@ Returns `Buffer` - A [Buffer][buffer] that contains the image's `PNG` encoded da Returns `Buffer` - A [Buffer][buffer] that contains the image's `JPEG` encoded data. -#### `image.toBitmap()` +#### `image.toBitmap([options])` + +* `options` Object (optional) + * `scaleFactor` Double (optional) - Defaults to 1.0. Returns `Buffer` - A [Buffer][buffer] that contains a copy of the image's raw bitmap pixel data. -#### `image.toDataURL()` +#### `image.toDataURL([options])` + +* `options` Object (optional) + * `scaleFactor` Double (optional) - Defaults to 1.0. Returns `String` - The data URL of the image. -#### `image.getBitmap()` +#### `image.getBitmap([options])` + +* `options` Object (optional) + * `scaleFactor` Double (optional) - Defaults to 1.0. Returns `Buffer` - A [Buffer][buffer] that contains the image's raw bitmap pixel data. From 8852b128ece9183317857c016631af7de27ac031 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 6 Mar 2017 16:29:39 -0800 Subject: [PATCH 374/925] Add GetScaleFactorFromOptions helper --- atom/common/api/atom_api_native_image.cc | 29 +++++++++++------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/atom/common/api/atom_api_native_image.cc b/atom/common/api/atom_api_native_image.cc index e63fff3a0c..98eac6b2ff 100644 --- a/atom/common/api/atom_api_native_image.cc +++ b/atom/common/api/atom_api_native_image.cc @@ -76,6 +76,15 @@ float GetScaleFactorFromPath(const base::FilePath& path) { return 1.0f; } +// Get the scale factor from options object at the first argument +float GetScaleFactorFromOptions(mate::Arguments* args) { + float scale_factor = 1.0f; + mate::Dictionary options; + if (args->GetNext(&options)) + options.Get("scaleFactor", &scale_factor); + return scale_factor; +} + bool AddImageSkiaRep(gfx::ImageSkia* image, const unsigned char* data, size_t size, @@ -231,10 +240,7 @@ HICON NativeImage::GetHICON(int size) { #endif v8::Local NativeImage::ToPNG(mate::Arguments* args) { - float scale_factor = 1.0f; - mate::Dictionary options; - if (args->GetNext(&options)) - options.Get("scaleFactor", &scale_factor); + float scale_factor = GetScaleFactorFromOptions(args); if (scale_factor == 1.0f) { // Use raw 1x PNG bytes when available @@ -257,10 +263,7 @@ v8::Local NativeImage::ToPNG(mate::Arguments* args) { } v8::Local NativeImage::ToBitmap(mate::Arguments* args) { - float scale_factor = 1.0f; - mate::Dictionary options; - if (args->GetNext(&options)) - options.Get("scaleFactor", &scale_factor); + float scale_factor = GetScaleFactorFromOptions(args); const SkBitmap bitmap = image_.AsImageSkia().GetRepresentation(scale_factor).sk_bitmap(); @@ -282,10 +285,7 @@ v8::Local NativeImage::ToJPEG(v8::Isolate* isolate, int quality) { } std::string NativeImage::ToDataURL(mate::Arguments* args) { - float scale_factor = 1.0f; - mate::Dictionary options; - if (args->GetNext(&options)) - options.Get("scaleFactor", &scale_factor); + float scale_factor = GetScaleFactorFromOptions(args); if (scale_factor == 1.0f) { // Use raw 1x PNG bytes when available @@ -299,10 +299,7 @@ std::string NativeImage::ToDataURL(mate::Arguments* args) { } v8::Local NativeImage::GetBitmap(mate::Arguments* args) { - float scale_factor = 1.0f; - mate::Dictionary options; - if (args->GetNext(&options)) - options.Get("scaleFactor", &scale_factor); + float scale_factor = GetScaleFactorFromOptions(args); const SkBitmap bitmap = image_.AsImageSkia().GetRepresentation(scale_factor).sk_bitmap(); From 31cae8e1aaa2cf54c5c79c561d0be408890661af Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 7 Mar 2017 12:34:31 -0800 Subject: [PATCH 375/925] Add more scale factor specs --- spec/api-native-image-spec.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/spec/api-native-image-spec.js b/spec/api-native-image-spec.js index f8a5708edc..22fe187b2d 100644 --- a/spec/api-native-image-spec.js +++ b/spec/api-native-image-spec.js @@ -87,9 +87,10 @@ describe('nativeImage module', () => { it('returns a PNG data URL', () => { const imageA = nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', '1x1.png')) assert.equal(imageA.toDataURL(), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQYlWNgAAIAAAUAAdafFs0AAAAASUVORK5CYII=') + assert.equal(imageA.toDataURL({scaleFactor: 2.0}), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQYlWNgAAIAAAUAAdafFs0AAAAASUVORK5CYII=') }) - it('returns a data URL at 1x scale factor', () => { + it('returns a data URL at 1x scale factor by default', () => { const imageA = nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', 'logo.png')) const imageB = nativeImage.createFromBuffer(imageA.toPNG(), { width: imageA.getSize().width, @@ -113,7 +114,7 @@ describe('nativeImage module', () => { }) describe('toPNG()', () => { - it('returns a buffer at 1x scale factor', () => { + it('returns a buffer at 1x scale factor by default', () => { const imageA = nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', 'logo.png')) const imageB = nativeImage.createFromBuffer(imageA.toPNG(), { width: imageA.getSize().width, @@ -126,6 +127,14 @@ describe('nativeImage module', () => { assert.deepEqual(imageC.getSize(), {width: 538, height: 190}) assert(imageB.toBitmap().equals(imageC.toBitmap())) }) + + it('supports a scale factor', () => { + const imageA = nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', 'logo.png')) + const imageB = nativeImage.createFromBuffer(imageA.toPNG({scaleFactor: 1.0})) + assert.deepEqual(imageB.getSize(), {width: 538, height: 190}) + const imageC = nativeImage.createFromBuffer(imageA.toPNG({scaleFactor: 2.0}), {scaleFactor: 2.0}) + assert.deepEqual(imageC.getSize(), {width: 269, height: 95}) + }) }) describe('createFromPath(path)', () => { From b5cfa2e92dfaeedbc573a8dffe8a37a47ac7ac87 Mon Sep 17 00:00:00 2001 From: deepak1556 Date: Wed, 8 Mar 2017 19:03:43 +0530 Subject: [PATCH 376/925] track host zoom levels with HostZoomMap --- atom/browser/web_contents_zoom_controller.cc | 47 +++++--------------- atom/browser/web_contents_zoom_controller.h | 8 ---- 2 files changed, 11 insertions(+), 44 deletions(-) diff --git a/atom/browser/web_contents_zoom_controller.cc b/atom/browser/web_contents_zoom_controller.cc index 1bd4f5a9e4..f48e643d96 100644 --- a/atom/browser/web_contents_zoom_controller.cc +++ b/atom/browser/web_contents_zoom_controller.cc @@ -26,8 +26,6 @@ WebContentsZoomController::WebContentsZoomController( embedder_zoom_controller_(nullptr) { default_zoom_factor_ = content::kEpsilon; host_zoom_map_ = content::HostZoomMap::GetForWebContents(web_contents); - zoom_subscription_ = host_zoom_map_->AddZoomLevelChangedCallback(base::Bind( - &WebContentsZoomController::OnZoomLevelChanged, base::Unretained(this))); } WebContentsZoomController::~WebContentsZoomController() {} @@ -59,19 +57,10 @@ void WebContentsZoomController::SetZoomLevel(double level) { host_zoom_map_->ClearTemporaryZoomLevel(render_process_id, render_view_id); } - auto new_zoom_factor = content::ZoomLevelToZoomFactor(level); - content::NavigationEntry* entry = - web_contents()->GetController().GetLastCommittedEntry(); - if (entry) { - std::string host = net::GetHostOrSpecFromURL(entry->GetURL()); - // When new zoom level varies from kZoomFactor, it takes preference. - if (!content::ZoomValuesEqual(GetDefaultZoomFactor(), new_zoom_factor)) - host_zoom_factor_[host] = new_zoom_factor; - content::HostZoomMap::SetZoomLevel(web_contents(), level); - // Notify observers of zoom level changes. - for (Observer& observer : observers_) - observer.OnZoomLevelChanged(web_contents(), level, false); - } + content::HostZoomMap::SetZoomLevel(web_contents(), level); + // Notify observers of zoom level changes. + for (Observer& observer : observers_) + observer.OnZoomLevelChanged(web_contents(), level, false); } double WebContentsZoomController::GetZoomLevel() { @@ -117,7 +106,6 @@ void WebContentsZoomController::DidFinishNavigation( void WebContentsZoomController::WebContentsDestroyed() { observers_.Clear(); - host_zoom_factor_.clear(); embedder_zoom_controller_ = nullptr; } @@ -131,8 +119,6 @@ void WebContentsZoomController::RenderFrameHostChanged( return; host_zoom_map_ = new_host_zoom_map; - zoom_subscription_ = host_zoom_map_->AddZoomLevelChangedCallback(base::Bind( - &WebContentsZoomController::OnZoomLevelChanged, base::Unretained(this))); } void WebContentsZoomController::SetZoomFactorOnNavigationIfNeeded( @@ -156,27 +142,16 @@ void WebContentsZoomController::SetZoomFactorOnNavigationIfNeeded( // then it takes precendence. // pref store < kZoomFactor < setZoomLevel std::string host = net::GetHostOrSpecFromURL(url); + std::string scheme = url.scheme(); double zoom_factor = GetDefaultZoomFactor(); - auto it = host_zoom_factor_.find(host); - if (it != host_zoom_factor_.end()) - zoom_factor = it->second; - auto level = content::ZoomFactorToZoomLevel(zoom_factor); - if (content::ZoomValuesEqual(level, GetZoomLevel())) + double zoom_level = content::ZoomFactorToZoomLevel(zoom_factor); + if (host_zoom_map_->HasZoomLevel(scheme, host)) { + zoom_level = host_zoom_map_->GetZoomLevelForHostAndScheme(scheme, host); + } + if (content::ZoomValuesEqual(zoom_level, GetZoomLevel())) return; - SetZoomLevel(level); -} - -void WebContentsZoomController::OnZoomLevelChanged( - const content::HostZoomMap::ZoomLevelChange& change) { - if (change.mode == content::HostZoomMap::ZOOM_CHANGED_FOR_HOST) { - auto it = host_zoom_factor_.find(change.host); - if (it == host_zoom_factor_.end()) - return; - host_zoom_factor_.insert( - it, std::make_pair(change.host, - content::ZoomLevelToZoomFactor(change.zoom_level))); - } + SetZoomLevel(zoom_level); } } // namespace atom diff --git a/atom/browser/web_contents_zoom_controller.h b/atom/browser/web_contents_zoom_controller.h index 11db8745b3..a0d28ad9b9 100644 --- a/atom/browser/web_contents_zoom_controller.h +++ b/atom/browser/web_contents_zoom_controller.h @@ -58,9 +58,6 @@ class WebContentsZoomController // Called after a navigation has committed to set default zoom factor. void SetZoomFactorOnNavigationIfNeeded(const GURL& url); - // Track zoom changes of a host in other instances of a partition. - void OnZoomLevelChanged(const content::HostZoomMap::ZoomLevelChange& change); - // kZoomFactor. double default_zoom_factor_; double temporary_zoom_level_; @@ -70,15 +67,10 @@ class WebContentsZoomController WebContentsZoomController* embedder_zoom_controller_; - // Map between zoom factor and hosts in this webContent. - std::map host_zoom_factor_; - base::ObserverList observers_; content::HostZoomMap* host_zoom_map_; - std::unique_ptr zoom_subscription_; - DISALLOW_COPY_AND_ASSIGN(WebContentsZoomController); }; From 224ffe44aaa5163d9b720ebd5eb1e1b1d22b7521 Mon Sep 17 00:00:00 2001 From: deepak1556 Date: Wed, 8 Mar 2017 19:05:24 +0530 Subject: [PATCH 377/925] add spec --- .../pages/webview-origin-zoom-level.html | 20 +++++++++++++++++++ spec/webview-spec.js | 14 +++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 spec/fixtures/pages/webview-origin-zoom-level.html diff --git a/spec/fixtures/pages/webview-origin-zoom-level.html b/spec/fixtures/pages/webview-origin-zoom-level.html new file mode 100644 index 0000000000..2c83f7ae56 --- /dev/null +++ b/spec/fixtures/pages/webview-origin-zoom-level.html @@ -0,0 +1,20 @@ + + + + + diff --git a/spec/webview-spec.js b/spec/webview-spec.js index a1585922e1..2f5cb8068c 100644 --- a/spec/webview-spec.js +++ b/spec/webview-spec.js @@ -1601,5 +1601,19 @@ describe(' tag', function () { }) w.loadURL(`file://${fixtures}/pages/webview-in-page-navigate.html`) }) + + it('inherits zoom level for the origin when available', (done) => { + w = new BrowserWindow({ + show: false, + webPreferences: { + zoomFactor: 1.2 + } + }) + ipcMain.once('webview-origin-zoom-level', (event, zoomLevel) => { + assert.equal(zoomLevel, 2.0) + done() + }) + w.loadURL(`file://${fixtures}/pages/webview-origin-zoom-level.html`) + }) }) }) From 1c2a78a896ba662d401ae636e7eeba653e56e0a1 Mon Sep 17 00:00:00 2001 From: John Kleinschmidt Date: Wed, 8 Mar 2017 09:55:59 -0500 Subject: [PATCH 378/925] Add WebRTCIPPolicy setting to webContents and webview Resolves #8777 Code to set and get the policy come from the muon framework code: https://github.com/brave/muon/blob/master/atom/browser/api/atom_api_web_ contents.cc#L1324...L1343 --- atom/browser/api/atom_api_web_contents.cc | 21 ++++++++++++++++++++ atom/browser/api/atom_api_web_contents.h | 2 ++ docs/api/web-contents.md | 14 +++++++++++++ docs/api/webview-tag.md | 9 ++++++++- lib/browser/guest-view-manager.js | 3 +++ lib/renderer/web-view/web-view-attributes.js | 8 ++++++++ lib/renderer/web-view/web-view-constants.js | 1 + 7 files changed, 57 insertions(+), 1 deletion(-) diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 83fe76e6bb..bfe8a78612 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -1067,6 +1067,23 @@ void WebContents::GoToOffset(int offset) { web_contents()->GetController().GoToOffset(offset); } +const std::string& WebContents::GetWebRTCIPHandlingPolicy() const { + return web_contents()-> + GetMutableRendererPrefs()->webrtc_ip_handling_policy; +} + +void WebContents::SetWebRTCIPHandlingPolicy( + const std::string webrtc_ip_handling_policy) { + if (GetWebRTCIPHandlingPolicy() == webrtc_ip_handling_policy) + return; + web_contents()->GetMutableRendererPrefs()->webrtc_ip_handling_policy = + webrtc_ip_handling_policy; + + content::RenderViewHost* host = web_contents()->GetRenderViewHost(); + if (host) + host->SyncRendererPrefs(); +} + bool WebContents::IsCrashed() const { return web_contents()->IsCrashed(); } @@ -1765,6 +1782,10 @@ void WebContents::BuildPrototype(v8::Isolate* isolate, .SetMethod("copyImageAt", &WebContents::CopyImageAt) .SetMethod("capturePage", &WebContents::CapturePage) .SetMethod("setEmbedder", &WebContents::SetEmbedder) + .SetMethod("setWebRTCIPHandlingPolicy", + &WebContents::SetWebRTCIPHandlingPolicy) + .SetMethod("getWebRTCIPHandlingPolicy", + &WebContents::GetWebRTCIPHandlingPolicy) .SetProperty("id", &WebContents::ID) .SetProperty("session", &WebContents::Session) .SetProperty("hostWebContents", &WebContents::HostWebContents) diff --git a/atom/browser/api/atom_api_web_contents.h b/atom/browser/api/atom_api_web_contents.h index 41edcc264e..5aecab0462 100644 --- a/atom/browser/api/atom_api_web_contents.h +++ b/atom/browser/api/atom_api_web_contents.h @@ -92,6 +92,8 @@ class WebContents : public mate::TrackableObject, void GoBack(); void GoForward(); void GoToOffset(int offset); + const std::string& GetWebRTCIPHandlingPolicy() const; + void SetWebRTCIPHandlingPolicy(const std::string webrtc_ip_handling_policy); bool IsCrashed() const; void SetUserAgent(const std::string& user_agent, mate::Arguments* args); std::string GetUserAgent(); diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index a665fae963..6ef3b73d63 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -1245,6 +1245,20 @@ Schedules a full repaint of the window this web contents is in. If *offscreen rendering* is enabled invalidates the frame and generates a new one through the `'paint'` event. +#### `contents.getWebRTCIPHandlingPolicy()` + +* Returns `String` - Returns the WebRTC IP Handling Policy + +#### `contents.setWebRTCIPHandlingPolicy(policy)` + +* `policy` String - Specify the WebRTC IP Handling Policy + * `default` - Exposes user's public and local IPs. This is the default behavior if not specified. + * `default_public_interface_only` - Exposes user's public IP, but does not expose user's local IP. + * `default_public_and_private_interfaces` - Exposes user's public and local IPs. + * `disable_non_proxied_udp` - Does not expose public or local IPs. + +Setting the WebRTC IP handling policy allows you to control which IPs are exposed via WebRTC. See [BrowserLeaks](https://browserleaks.com/webrtc) for more details. + ### Instance Properties #### `contents.id` diff --git a/docs/api/webview-tag.md b/docs/api/webview-tag.md index 5efc225a1d..105dfa1b66 100644 --- a/docs/api/webview-tag.md +++ b/docs/api/webview-tag.md @@ -289,6 +289,13 @@ win.on('resize', () => { }) ``` +### `webrtcippolicy` + +```html + +``` +This attribute allows you to set the WebRTC IP handling policy which controls what IPs are exposed via WebRTC. See [webContents](web-contents.md#contentssetwebrtciphandlingpolicypolicy) for available policies. + ## Methods The `webview` tag has the following methods: @@ -312,7 +319,7 @@ webview.addEventListener('dom-ready', () => { * `userAgent` String (optional) - A user agent originating the request. * `extraHeaders` String (optional) - Extra headers separated by "\n" * `postData` ([UploadRawData](structures/upload-raw-data.md) | [UploadFile](structures/upload-file.md) | [UploadFileSystem](structures/upload-file-system.md) | [UploadBlob](structures/upload-blob.md))[] - (optional) - * `baseURLForDataURL` String (optional) - Base url (with trailing path separator) for files to be loaded by the data url. This is needed only if the specified `url` is a data url and needs to load other files. + * `baseURLForDataURL` String (optional) - Base url (with trailing path separator) for files to be loaded by the data url. This is needed only if the specified `url` is a data url and needs to load other files. Loads the `url` in the webview, the `url` must contain the protocol prefix, e.g. the `http://` or `file://`. diff --git a/lib/browser/guest-view-manager.js b/lib/browser/guest-view-manager.js index 0bfe77d1e5..00b309baa6 100644 --- a/lib/browser/guest-view-manager.js +++ b/lib/browser/guest-view-manager.js @@ -103,6 +103,9 @@ const createGuest = function (embedder, params) { height: params.maxheight } }) + if (params.webrtcippolicy) { + guest.setWebRTCIPHandlingPolicy(params.webrtcippolicy) + } if (params.src) { const opts = {} if (params.httpreferrer) { diff --git a/lib/renderer/web-view/web-view-attributes.js b/lib/renderer/web-view/web-view-attributes.js index 204046bd60..c8911f5065 100644 --- a/lib/renderer/web-view/web-view-attributes.js +++ b/lib/renderer/web-view/web-view-attributes.js @@ -304,6 +304,13 @@ class DisableBlinkFeaturesAttribute extends WebViewAttribute { } } +// Attribute specifies WebRTC IP handling policy for handling IP leaking. +class WebRTCIPHandlingPolicyAttribute extends WebViewAttribute { + constructor (webViewImpl) { + super(webViewConstants.ATTRIBUTE_WEBRTCIPPOLICY, webViewImpl) + } +} + // Attribute that specifies the web preferences to be enabled. class WebPreferencesAttribute extends WebViewAttribute { constructor (webViewImpl) { @@ -329,6 +336,7 @@ WebViewImpl.prototype.setupWebViewAttributes = function () { this.attributes[webViewConstants.ATTRIBUTE_GUESTINSTANCE] = new GuestInstanceAttribute(this) this.attributes[webViewConstants.ATTRIBUTE_DISABLEGUESTRESIZE] = new BooleanAttribute(webViewConstants.ATTRIBUTE_DISABLEGUESTRESIZE, this) this.attributes[webViewConstants.ATTRIBUTE_WEBPREFERENCES] = new WebPreferencesAttribute(this) + this.attributes[webViewConstants.ATTRIBUTE_WEBRTCIPPOLICY] = new WebRTCIPHandlingPolicyAttribute(this) const autosizeAttributes = [webViewConstants.ATTRIBUTE_MAXHEIGHT, webViewConstants.ATTRIBUTE_MAXWIDTH, webViewConstants.ATTRIBUTE_MINHEIGHT, webViewConstants.ATTRIBUTE_MINWIDTH] autosizeAttributes.forEach((attribute) => { diff --git a/lib/renderer/web-view/web-view-constants.js b/lib/renderer/web-view/web-view-constants.js index bf2601822d..42f813456c 100644 --- a/lib/renderer/web-view/web-view-constants.js +++ b/lib/renderer/web-view/web-view-constants.js @@ -20,6 +20,7 @@ module.exports = { ATTRIBUTE_GUESTINSTANCE: 'guestinstance', ATTRIBUTE_DISABLEGUESTRESIZE: 'disableguestresize', ATTRIBUTE_WEBPREFERENCES: 'webpreferences', + ATTRIBUTE_WEBRTCIPPOLICY: 'webrtcippolicy', // Internal attribute. ATTRIBUTE_INTERNALINSTANCEID: 'internalinstanceid', From 950e3436c28c842f05fd76ce7350d836c7237f33 Mon Sep 17 00:00:00 2001 From: John Kleinschmidt Date: Wed, 8 Mar 2017 13:52:04 -0500 Subject: [PATCH 379/925] Removed webrtcippolicy attribute from webview --- docs/api/webview-tag.md | 9 +-------- lib/browser/guest-view-manager.js | 3 --- lib/renderer/web-view/web-view-attributes.js | 8 -------- lib/renderer/web-view/web-view-constants.js | 1 - 4 files changed, 1 insertion(+), 20 deletions(-) diff --git a/docs/api/webview-tag.md b/docs/api/webview-tag.md index 105dfa1b66..5efc225a1d 100644 --- a/docs/api/webview-tag.md +++ b/docs/api/webview-tag.md @@ -289,13 +289,6 @@ win.on('resize', () => { }) ``` -### `webrtcippolicy` - -```html - -``` -This attribute allows you to set the WebRTC IP handling policy which controls what IPs are exposed via WebRTC. See [webContents](web-contents.md#contentssetwebrtciphandlingpolicypolicy) for available policies. - ## Methods The `webview` tag has the following methods: @@ -319,7 +312,7 @@ webview.addEventListener('dom-ready', () => { * `userAgent` String (optional) - A user agent originating the request. * `extraHeaders` String (optional) - Extra headers separated by "\n" * `postData` ([UploadRawData](structures/upload-raw-data.md) | [UploadFile](structures/upload-file.md) | [UploadFileSystem](structures/upload-file-system.md) | [UploadBlob](structures/upload-blob.md))[] - (optional) - * `baseURLForDataURL` String (optional) - Base url (with trailing path separator) for files to be loaded by the data url. This is needed only if the specified `url` is a data url and needs to load other files. + * `baseURLForDataURL` String (optional) - Base url (with trailing path separator) for files to be loaded by the data url. This is needed only if the specified `url` is a data url and needs to load other files. Loads the `url` in the webview, the `url` must contain the protocol prefix, e.g. the `http://` or `file://`. diff --git a/lib/browser/guest-view-manager.js b/lib/browser/guest-view-manager.js index 00b309baa6..0bfe77d1e5 100644 --- a/lib/browser/guest-view-manager.js +++ b/lib/browser/guest-view-manager.js @@ -103,9 +103,6 @@ const createGuest = function (embedder, params) { height: params.maxheight } }) - if (params.webrtcippolicy) { - guest.setWebRTCIPHandlingPolicy(params.webrtcippolicy) - } if (params.src) { const opts = {} if (params.httpreferrer) { diff --git a/lib/renderer/web-view/web-view-attributes.js b/lib/renderer/web-view/web-view-attributes.js index c8911f5065..204046bd60 100644 --- a/lib/renderer/web-view/web-view-attributes.js +++ b/lib/renderer/web-view/web-view-attributes.js @@ -304,13 +304,6 @@ class DisableBlinkFeaturesAttribute extends WebViewAttribute { } } -// Attribute specifies WebRTC IP handling policy for handling IP leaking. -class WebRTCIPHandlingPolicyAttribute extends WebViewAttribute { - constructor (webViewImpl) { - super(webViewConstants.ATTRIBUTE_WEBRTCIPPOLICY, webViewImpl) - } -} - // Attribute that specifies the web preferences to be enabled. class WebPreferencesAttribute extends WebViewAttribute { constructor (webViewImpl) { @@ -336,7 +329,6 @@ WebViewImpl.prototype.setupWebViewAttributes = function () { this.attributes[webViewConstants.ATTRIBUTE_GUESTINSTANCE] = new GuestInstanceAttribute(this) this.attributes[webViewConstants.ATTRIBUTE_DISABLEGUESTRESIZE] = new BooleanAttribute(webViewConstants.ATTRIBUTE_DISABLEGUESTRESIZE, this) this.attributes[webViewConstants.ATTRIBUTE_WEBPREFERENCES] = new WebPreferencesAttribute(this) - this.attributes[webViewConstants.ATTRIBUTE_WEBRTCIPPOLICY] = new WebRTCIPHandlingPolicyAttribute(this) const autosizeAttributes = [webViewConstants.ATTRIBUTE_MAXHEIGHT, webViewConstants.ATTRIBUTE_MAXWIDTH, webViewConstants.ATTRIBUTE_MINHEIGHT, webViewConstants.ATTRIBUTE_MINWIDTH] autosizeAttributes.forEach((attribute) => { diff --git a/lib/renderer/web-view/web-view-constants.js b/lib/renderer/web-view/web-view-constants.js index 42f813456c..bf2601822d 100644 --- a/lib/renderer/web-view/web-view-constants.js +++ b/lib/renderer/web-view/web-view-constants.js @@ -20,7 +20,6 @@ module.exports = { ATTRIBUTE_GUESTINSTANCE: 'guestinstance', ATTRIBUTE_DISABLEGUESTRESIZE: 'disableguestresize', ATTRIBUTE_WEBPREFERENCES: 'webpreferences', - ATTRIBUTE_WEBRTCIPPOLICY: 'webrtcippolicy', // Internal attribute. ATTRIBUTE_INTERNALINSTANCEID: 'internalinstanceid', From dcfab1b6a0830ab049fa4a1f58b15f237a47604c Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 8 Mar 2017 13:04:55 -0800 Subject: [PATCH 380/925] Link more touch bar params --- docs/api/touch-bar-button.md | 2 +- docs/api/touch-bar-group.md | 2 +- docs/api/touch-bar-popover.md | 4 ++-- docs/api/touch-bar.md | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/api/touch-bar-button.md b/docs/api/touch-bar-button.md index 30f7033caf..41a2d8505d 100644 --- a/docs/api/touch-bar-button.md +++ b/docs/api/touch-bar-button.md @@ -10,7 +10,7 @@ Process: [Main](../tutorial/quick-start.md#main-process) * `label` String (optional) - Button text. * `backgroundColor` String (optional) - Button background color in hex format, i.e `#ABCDEF`. - * `icon` NativeImage (optional) - Button icon. + * `icon` [NativeImage](native-image.md) (optional) - Button icon. * `click` Function (optional) - Function to call when the button is clicked. ### Instance Properties diff --git a/docs/api/touch-bar-group.md b/docs/api/touch-bar-group.md index d765effa89..a0086989fe 100644 --- a/docs/api/touch-bar-group.md +++ b/docs/api/touch-bar-group.md @@ -7,4 +7,4 @@ Process: [Main](../tutorial/quick-start.md#main-process) ### `new TouchBarGroup(options)` * `options` Object - * `items` TouchBar - Items to display as a group. + * `items` [TouchBar](touch-bar.md) - Items to display as a group. diff --git a/docs/api/touch-bar-popover.md b/docs/api/touch-bar-popover.md index 231a68c43d..c7391cb9b4 100644 --- a/docs/api/touch-bar-popover.md +++ b/docs/api/touch-bar-popover.md @@ -8,8 +8,8 @@ Process: [Main](../tutorial/quick-start.md#main-process) * `options` Object * `label` String (optional) - Popover button text. - * `icon` NativeImage (optional) - Popover button icon. - * `items` TouchBar (optional) - Items to display in the popover. + * `icon` [NativeImage](native-image.md) (optional) - Popover button icon. + * `items` [TouchBar](touch-bar.md) (optional) - Items to display in the popover. * `showCloseButton` Boolean (optional) - `true` to display a close button on the left of the popover, `false` to not show it. Default is `true`. diff --git a/docs/api/touch-bar.md b/docs/api/touch-bar.md index 8f0f354496..0186521387 100644 --- a/docs/api/touch-bar.md +++ b/docs/api/touch-bar.md @@ -6,7 +6,7 @@ Process: [Main](../tutorial/quick-start.md#main-process) ### `new TouchBar(items)` -* `items` (TouchBarButton | TouchBarColorPicker | TouchBarGroup | TouchBarLabel | TouchBarPopOver | TouchBarSlider | TouchBarSpacer)[] +* `items` ([TouchBarButton](touch-bar-button.md) | [TouchBarColorPicker](touch-bar-color-picker.md) | [TouchBarGroup](touch-bar-group.md) | [TouchBarLabel](touch-bar-label.md) | [TouchBarPopover](touch-bar-popover.md) | [TouchBarSlider](touch-bar-slider.md) | [TouchBarSpacer](touch-bar-spacer.md))[] Creates a new touch bar with the specified items. Use `BrowserWindow.setTouchBar` to add the `TouchBar` to a window. From d5a658bbe4858be7f8506db11ae493d493c7ef93 Mon Sep 17 00:00:00 2001 From: John Kleinschmidt Date: Wed, 8 Mar 2017 22:06:26 -0500 Subject: [PATCH 381/925] Fixed pass by reference and added spec --- atom/browser/api/atom_api_web_contents.cc | 4 ++-- atom/browser/api/atom_api_web_contents.h | 4 ++-- spec/api-web-contents-spec.js | 16 ++++++++++++++++ 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index bfe8a78612..6ba23b3c9a 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -1067,13 +1067,13 @@ void WebContents::GoToOffset(int offset) { web_contents()->GetController().GoToOffset(offset); } -const std::string& WebContents::GetWebRTCIPHandlingPolicy() const { +const std::string WebContents::GetWebRTCIPHandlingPolicy() const { return web_contents()-> GetMutableRendererPrefs()->webrtc_ip_handling_policy; } void WebContents::SetWebRTCIPHandlingPolicy( - const std::string webrtc_ip_handling_policy) { + const std::string& webrtc_ip_handling_policy) { if (GetWebRTCIPHandlingPolicy() == webrtc_ip_handling_policy) return; web_contents()->GetMutableRendererPrefs()->webrtc_ip_handling_policy = diff --git a/atom/browser/api/atom_api_web_contents.h b/atom/browser/api/atom_api_web_contents.h index 5aecab0462..2513dc7722 100644 --- a/atom/browser/api/atom_api_web_contents.h +++ b/atom/browser/api/atom_api_web_contents.h @@ -92,8 +92,8 @@ class WebContents : public mate::TrackableObject, void GoBack(); void GoForward(); void GoToOffset(int offset); - const std::string& GetWebRTCIPHandlingPolicy() const; - void SetWebRTCIPHandlingPolicy(const std::string webrtc_ip_handling_policy); + const std::string GetWebRTCIPHandlingPolicy() const; + void SetWebRTCIPHandlingPolicy(const std::string& webrtc_ip_handling_policy); bool IsCrashed() const; void SetUserAgent(const std::string& user_agent, mate::Arguments* args); std::string GetUserAgent(); diff --git a/spec/api-web-contents-spec.js b/spec/api-web-contents-spec.js index 448d2556a1..9005cce1f3 100644 --- a/spec/api-web-contents-spec.js +++ b/spec/api-web-contents-spec.js @@ -527,4 +527,20 @@ describe('webContents module', function () { w.loadURL(`file://${fixtures}/pages/c.html`) }) }) + + describe('webrtc ip policy api', () => { + it('can set and get webrtc ip policies', (done) => { + const policies = [ + 'default', + 'default_public_interface_only', + 'default_public_and_private_interfaces', + 'disable_non_proxied_udp' + ] + policies.forEach((policy) => { + w.webContents.setWebRTCIPHandlingPolicy(policy) + assert.equal(w.webContents.getWebRTCIPHandlingPolicy(), policy) + }) + done() + }) + }) }) From 410e80682a042886cbdfc0b98904e73d57426cb3 Mon Sep 17 00:00:00 2001 From: TanninOne Date: Thu, 9 Mar 2017 15:41:31 +0100 Subject: [PATCH 382/925] Update browser-window.md Notes that maximize will also show the window and that ready-to-show won't fire if the window is already being displayed. (See issue #8861) --- docs/api/browser-window.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index fe870ecb36..b169230094 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -36,8 +36,8 @@ without visual flash, there are two solutions for different situations. ### Using `ready-to-show` event While loading the page, the `ready-to-show` event will be emitted when renderer -process has done drawing for the first time, showing window after this event -will have no visual flash: +process has done drawing for the first time if the window is not shown yet. Showing +the window after this event will have no visual flash: ```javascript const {BrowserWindow} = require('electron') @@ -390,7 +390,7 @@ Emitted when the window is hidden. #### Event: 'ready-to-show' -Emitted when the web page has been rendered and window can be displayed without +Emitted when the web page has been rendered (while not being shown) and window can be displayed without a visual flash. #### Event: 'maximize' @@ -632,7 +632,8 @@ Returns `Boolean` - Whether current window is a modal window. #### `win.maximize()` -Maximizes the window. +Maximizes the window. This will also show (but not focus) the window if it +isn't being displayed already. #### `win.unmaximize()` From 3c74404a3ec285a65632fb3684228162d54de74a Mon Sep 17 00:00:00 2001 From: John Kleinschmidt Date: Thu, 9 Mar 2017 09:54:09 -0500 Subject: [PATCH 383/925] Updated docs. --- docs/api/web-contents.md | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index 6ef3b73d63..b91f6d9ede 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -1247,17 +1247,28 @@ one through the `'paint'` event. #### `contents.getWebRTCIPHandlingPolicy()` -* Returns `String` - Returns the WebRTC IP Handling Policy +* Returns `String` - Returns the WebRTC IP Handling Policy. #### `contents.setWebRTCIPHandlingPolicy(policy)` -* `policy` String - Specify the WebRTC IP Handling Policy - * `default` - Exposes user's public and local IPs. This is the default behavior if not specified. - * `default_public_interface_only` - Exposes user's public IP, but does not expose user's local IP. - * `default_public_and_private_interfaces` - Exposes user's public and local IPs. - * `disable_non_proxied_udp` - Does not expose public or local IPs. +* `policy` String - Specify the WebRTC IP Handling Policy. + * `default` - Exposes user's public and local IPs. This is the default + behavior. When this policy is used, WebRTC has the right to enumerate all + interfaces and bind them to discover public interfaces. + * `default_public_interface_only` - Exposes user's public IP, but does not + expose user's local IP. When this policy is used, WebRTC should only use the + default route used by http. This doesn't expose any local addresses. + * `default_public_and_private_interfaces` - Exposes user's public and local + IPs. When this policy is used, WebRTC should only use the default route used + by http. This also exposes the associated default private address. Default + route is the route chosen by the OS on a multi-homed endpoint. + * `disable_non_proxied_udp` - Does not expose public or local IPs. When this + policy is used, WebRTC should only use TCP to contact peers or servers unless + the proxy server supports UDP. -Setting the WebRTC IP handling policy allows you to control which IPs are exposed via WebRTC. See [BrowserLeaks](https://browserleaks.com/webrtc) for more details. +Setting the WebRTC IP handling policy allows you to control which IPs are +exposed via WebRTC. See [BrowserLeaks](https://browserleaks.com/webrtc) for +more details. ### Instance Properties From 8aba64025038e79f043793b29871e77dd88bc118 Mon Sep 17 00:00:00 2001 From: mst128256 Date: Thu, 9 Mar 2017 16:01:33 +0100 Subject: [PATCH 384/925] added default menu items for 'Edit' and 'Window' #2814 --- docs/api/menu-item.md | 3 ++ lib/browser/api/menu-item-roles.js | 81 ++++++++++++++++++++++++++++++ lib/browser/api/menu-item.js | 2 +- 3 files changed, 85 insertions(+), 1 deletion(-) diff --git a/docs/api/menu-item.md b/docs/api/menu-item.md index 1c12d9fdac..76fcae1129 100644 --- a/docs/api/menu-item.md +++ b/docs/api/menu-item.md @@ -64,6 +64,9 @@ The `role` property can have following values: * `zoomin` - Zoom in the focused page by 10% * `zoomout` - Zoom out the focused page by 10% +* `menuEdit` - Whole default "Edit" menu (Undo,Copy, etc.) +* `menuWindow` - Whole default "Window" menu (Minimize, Close, etc.) + On macOS `role` can also have following additional values: * `about` - Map to the `orderFrontStandardAboutPanel` action diff --git a/lib/browser/api/menu-item-roles.js b/lib/browser/api/menu-item-roles.js index 8a78670267..254283b86b 100644 --- a/lib/browser/api/menu-item-roles.js +++ b/lib/browser/api/menu-item-roles.js @@ -154,6 +154,73 @@ const roles = { webContents.setZoomLevel(zoomLevel - 0.5) }) } + }, + // submenu Edit (should fit both Mac & Windows) + menuEdit: { + label: 'Edit', + submenu: [ + { + role: 'undo' + }, + { + role: 'redo' + }, + { + type: 'separator' + }, + { + role: 'cut' + }, + { + role: 'copy' + }, + { + role: 'paste' + }, + + process.platform === 'darwin' ? + { + role: 'pasteandmatchstyle' + } : {}, + + { + role: 'delete' + }, + + process.platform === 'win32' ? + { + type: 'separator' + } : {}, + + { + role: 'selectall' + } + ] + }, + + // submenu Window should be used for Mac only + menuWindow: { + label: 'Window', + submenu: [ + { + role: 'minimize' + }, + { + role: 'close' + }, + + process.platform === 'darwin' ? + { + type: 'separator' + } : {}, + + process.platform === 'darwin' ? + { + label: 'Bring All to Front', + role: 'front' + } : {} + + ] } } @@ -176,6 +243,20 @@ exports.getDefaultAccelerator = (role) => { if (roles.hasOwnProperty(role)) return roles[role].accelerator } +exports.getDefaultSubmenu = (role) => { + if (roles.hasOwnProperty(role)) { + submenu = roles[role].submenu + + // remove empty objects from within the submenu + if (Array.isArray(submenu)) + submenu = submenu.filter(function(n){ + return n.constructor !== Object || Object.keys(n).length > 0 + }) + + return submenu + } +} + exports.execute = (role, focusedWindow, focusedWebContents) => { if (!canExecuteRole(role)) return false diff --git a/lib/browser/api/menu-item.js b/lib/browser/api/menu-item.js index 98b8e9980e..e95226d3b3 100644 --- a/lib/browser/api/menu-item.js +++ b/lib/browser/api/menu-item.js @@ -11,7 +11,7 @@ const MenuItem = function (options) { for (let key in options) { if (!(key in this)) this[key] = options[key] } - + this.submenu = this.submenu || roles.getDefaultSubmenu(this.role) if (this.submenu != null && this.submenu.constructor !== Menu) { this.submenu = Menu.buildFromTemplate(this.submenu) } From ff5081816f77549ab35f1fd64829a016752e4318 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 9 Mar 2017 09:53:01 -0800 Subject: [PATCH 385/925] Remove unneeded done call --- spec/api-web-contents-spec.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spec/api-web-contents-spec.js b/spec/api-web-contents-spec.js index 9005cce1f3..f081b39009 100644 --- a/spec/api-web-contents-spec.js +++ b/spec/api-web-contents-spec.js @@ -529,7 +529,7 @@ describe('webContents module', function () { }) describe('webrtc ip policy api', () => { - it('can set and get webrtc ip policies', (done) => { + it('can set and get webrtc ip policies', () => { const policies = [ 'default', 'default_public_interface_only', @@ -540,7 +540,6 @@ describe('webContents module', function () { w.webContents.setWebRTCIPHandlingPolicy(policy) assert.equal(w.webContents.getWebRTCIPHandlingPolicy(), policy) }) - done() }) }) }) From 17b70670f8ea78a19c193b9db84cf04c5d11c3bf Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 7 Mar 2017 13:47:31 -0800 Subject: [PATCH 386/925] Add initial NativeImage.addRepresentation specs --- spec/api-native-image-spec.js | 27 +++++++++++++++++++++++++++ spec/fixtures/assets/2x2.jpg | Bin 0 -> 3176 bytes spec/fixtures/assets/3x3.png | Bin 0 -> 507 bytes 3 files changed, 27 insertions(+) create mode 100644 spec/fixtures/assets/2x2.jpg create mode 100644 spec/fixtures/assets/3x3.png diff --git a/spec/api-native-image-spec.js b/spec/api-native-image-spec.js index 22fe187b2d..d9fb31a81e 100644 --- a/spec/api-native-image-spec.js +++ b/spec/api-native-image-spec.js @@ -249,4 +249,31 @@ describe('nativeImage module', () => { assert.equal(nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', 'logo.png')).getAspectRatio(), 2.8315789699554443) }) }) + + describe('addRepresentation()', () => { + it('supports adding a representation for a scale factor', () => { + const image = nativeImage.createEmpty() + image.addRepresentation({ + scaleFactor: 1.0, + buffer: nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', '1x1.png')).toPNG() + }) + image.addRepresentation({ + scaleFactor: 2.0, + buffer: nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', '2x2.jpg')).toPNG() + }) + image.addRepresentation({ + scaleFactor: 3.0, + buffer: nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', '3x3.png')).toPNG() + }) + image.addRepresentation({ + scaleFactor: 4.0, + buffer: 'invalid' + }) + + assert.equal(image.toDataURL({scaleFactor: 1.0}), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQYlWNgAAIAAAUAAdafFs0AAAAASUVORK5CYII=') + assert.equal(image.toDataURL({scaleFactor: 2.0}), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFUlEQVQYlWP8////fwYGBgYmBigAAD34BABBrq9BAAAAAElFTkSuQmCC') + assert.equal(image.toDataURL({scaleFactor: 3.0}), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAADElEQVQYlWNgIAoAAAAnAAGZWEMnAAAAAElFTkSuQmCC') + assert.equal(image.toDataURL({scaleFactor: 4.0}), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAADElEQVQYlWNgIAoAAAAnAAGZWEMnAAAAAElFTkSuQmCC') + }) + }) }) diff --git a/spec/fixtures/assets/2x2.jpg b/spec/fixtures/assets/2x2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d96fdb313314c5f328e461727e7a992cdb6474a8 GIT binary patch literal 3176 zcmeHJSxgf_82)Du+M*;B0k0UB1r$x_mI6u}3*i(FinqSu4aP*gH5$bejb@!)YDs)DzS?g#GvDs~$9(_n!!O^LA3=!5f@H11H&iT!z8O!K^&VXLr`~ zBIDreJYFmN?(8|1@wlw)!fcz-<}2jgp3)XSU)3^ap0lOiX?C%Bxy&rdA~pCLc+t*C z4NJTMi)3Zh;1N0*=**FCeCC^#}?^AX0%y1tJxQR3K7;NCp1C3XF6+ycf4PLEOuL{1$R5+=3_s+=7qEG#DYL zpv*=o4H$;XQ&k#!ds2JfM(Czqtp9sX8bbmG`?<0qs}nVL3jdRF#~nK@=lVNr3(?9#IGs(JGlEUaEs<8<-0b#Bk% zfY`J&C^fgNTD@lNy7e2{Hf`Rrb=&sN9XogL*}HH5frEz*cON}={KUyqr_Wrtc1%j$KE3bjg{O?-LyU-sr@Xtbx=)MVhMY z?}WAfld`a|5nT_U5C|0{fp3W<@l8<}w1!ro(P~s^Lx@%}NP!fMCu_Ae*W0qmPX2bzW}+TK9c|d literal 0 HcmV?d00001 diff --git a/spec/fixtures/assets/3x3.png b/spec/fixtures/assets/3x3.png new file mode 100644 index 0000000000000000000000000000000000000000..b1c1d9d688c59194fbad1431f6c2fffc7eaa1bf1 GIT binary patch literal 507 zcmeAS@N?(olHy`uVBq!ia0vp^%plCc1|-8Yw(bW~Ea{HEjtmSN`?>!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8X6a5n0T@z%2~Ij105p zNH8!kMrMXYltlRYSS9D@>LsS+C#C9DVstT4fPE4;bsH1+JHo@{EISEfi{E8 zw==W>t3(ll+GC>+vK+}V5TAlYfnK%aveAbJn;nB3GS( TF(Rpo5fsFpu6{1-oD!M Date: Tue, 7 Mar 2017 13:48:12 -0800 Subject: [PATCH 387/925] Add addRepresentation buffer support --- atom/common/api/atom_api_native_image.cc | 25 +++++++++++++++++++++++- atom/common/api/atom_api_native_image.h | 2 ++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/atom/common/api/atom_api_native_image.cc b/atom/common/api/atom_api_native_image.cc index 98eac6b2ff..e30b407e9e 100644 --- a/atom/common/api/atom_api_native_image.cc +++ b/atom/common/api/atom_api_native_image.cc @@ -15,7 +15,6 @@ #include "base/files/file_util.h" #include "base/strings/pattern.h" #include "base/strings/string_util.h" -#include "native_mate/dictionary.h" #include "native_mate/object_template_builder.h" #include "net/base/data_url.h" #include "third_party/skia/include/core/SkPixelRef.h" @@ -387,6 +386,29 @@ mate::Handle NativeImage::Crop(v8::Isolate* isolate, new NativeImage(isolate, gfx::Image(cropped))); } +void NativeImage::AddRepresentation(const mate::Dictionary& options) { + int width = 0; + int height = 0; + float scale_factor = 1.0f; + options.Get("width", &width); + options.Get("height", &height); + options.Get("scaleFactor", &scale_factor); + + v8::Local buffer; + if (options.Get("buffer", &buffer) && node::Buffer::HasInstance(buffer)) { + gfx::ImageSkia image_skia = image_.AsImageSkia(); + AddImageSkiaRep( + &image_skia, + reinterpret_cast(node::Buffer::Data(buffer)), + node::Buffer::Length(buffer), + width, height, scale_factor); + + if (IsEmpty()) { + gfx::Image image(image_skia); + image_.SwapRepresentations(&image); + } + } +} #if !defined(OS_MACOSX) void NativeImage::SetTemplateImage(bool setAsTemplate) { @@ -504,6 +526,7 @@ void NativeImage::BuildPrototype( .SetMethod("resize", &NativeImage::Resize) .SetMethod("crop", &NativeImage::Crop) .SetMethod("getAspectRatio", &NativeImage::GetAspectRatio) + .SetMethod("addRepresentation", &NativeImage::AddRepresentation) // TODO(kevinsawicki): Remove in 2.0, deprecate before then with warnings .SetMethod("toPng", &NativeImage::ToPNG) .SetMethod("toJpeg", &NativeImage::ToJPEG); diff --git a/atom/common/api/atom_api_native_image.h b/atom/common/api/atom_api_native_image.h index a6614a81b0..6ff7a0e29f 100644 --- a/atom/common/api/atom_api_native_image.h +++ b/atom/common/api/atom_api_native_image.h @@ -9,6 +9,7 @@ #include #include "base/values.h" +#include "native_mate/dictionary.h" #include "native_mate/handle.h" #include "native_mate/wrappable.h" #include "ui/gfx/geometry/rect.h" @@ -85,6 +86,7 @@ class NativeImage : public mate::Wrappable { bool IsEmpty(); gfx::Size GetSize(); float GetAspectRatio(); + void AddRepresentation(const mate::Dictionary& options); // Mark the image as template image. void SetTemplateImage(bool setAsTemplate); From 7e039d92ec4fb02804f4fc3afed8c1c80e99ab9f Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 7 Mar 2017 14:24:37 -0800 Subject: [PATCH 388/925] Support adding representation from data URL --- atom/common/api/atom_api_native_image.cc | 27 +++++++++++++++++++----- spec/api-native-image-spec.js | 27 +++++++++++++++++++++++- 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/atom/common/api/atom_api_native_image.cc b/atom/common/api/atom_api_native_image.cc index e30b407e9e..6811ab9193 100644 --- a/atom/common/api/atom_api_native_image.cc +++ b/atom/common/api/atom_api_native_image.cc @@ -394,20 +394,37 @@ void NativeImage::AddRepresentation(const mate::Dictionary& options) { options.Get("height", &height); options.Get("scaleFactor", &scale_factor); + bool skia_rep_added = false; + gfx::ImageSkia image_skia = image_.AsImageSkia(); + v8::Local buffer; + GURL url; if (options.Get("buffer", &buffer) && node::Buffer::HasInstance(buffer)) { - gfx::ImageSkia image_skia = image_.AsImageSkia(); AddImageSkiaRep( &image_skia, reinterpret_cast(node::Buffer::Data(buffer)), node::Buffer::Length(buffer), width, height, scale_factor); - - if (IsEmpty()) { - gfx::Image image(image_skia); - image_.SwapRepresentations(&image); + skia_rep_added = true; + } else if (options.Get("dataURL", &url)) { + std::string mime_type, charset, data; + if (net::DataURL::Parse(url, &mime_type, &charset, &data)) { + if (mime_type == "image/png" || mime_type == "image/jpeg") { + AddImageSkiaRep( + &image_skia, + reinterpret_cast(data.c_str()), + data.size(), + width, height, scale_factor); + skia_rep_added = true; + } } } + + // Re-initialize image when first representation is added to an empty image + if (skia_rep_added && IsEmpty()) { + gfx::Image image(image_skia); + image_.SwapRepresentations(&image); + } } #if !defined(OS_MACOSX) diff --git a/spec/api-native-image-spec.js b/spec/api-native-image-spec.js index d9fb31a81e..af90332e5a 100644 --- a/spec/api-native-image-spec.js +++ b/spec/api-native-image-spec.js @@ -251,7 +251,7 @@ describe('nativeImage module', () => { }) describe('addRepresentation()', () => { - it('supports adding a representation for a scale factor', () => { + it('supports adding a buffer representation for a scale factor', () => { const image = nativeImage.createEmpty() image.addRepresentation({ scaleFactor: 1.0, @@ -275,5 +275,30 @@ describe('nativeImage module', () => { assert.equal(image.toDataURL({scaleFactor: 3.0}), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAADElEQVQYlWNgIAoAAAAnAAGZWEMnAAAAAElFTkSuQmCC') assert.equal(image.toDataURL({scaleFactor: 4.0}), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAADElEQVQYlWNgIAoAAAAnAAGZWEMnAAAAAElFTkSuQmCC') }) + + it('supports adding a data URL representation for a scale factor', () => { + const image = nativeImage.createEmpty() + image.addRepresentation({ + scaleFactor: 1.0, + dataURL: nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', '1x1.png')).toDataURL() + }) + image.addRepresentation({ + scaleFactor: 2.0, + dataURL: nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', '2x2.jpg')).toDataURL() + }) + image.addRepresentation({ + scaleFactor: 3.0, + dataURL: nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', '3x3.png')).toDataURL() + }) + image.addRepresentation({ + scaleFactor: 4.0, + dataURL: 'invalid' + }) + + assert.equal(image.toDataURL({scaleFactor: 1.0}), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQYlWNgAAIAAAUAAdafFs0AAAAASUVORK5CYII=') + assert.equal(image.toDataURL({scaleFactor: 2.0}), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFUlEQVQYlWP8////fwYGBgYmBigAAD34BABBrq9BAAAAAElFTkSuQmCC') + assert.equal(image.toDataURL({scaleFactor: 3.0}), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAADElEQVQYlWNgIAoAAAAnAAGZWEMnAAAAAElFTkSuQmCC') + assert.equal(image.toDataURL({scaleFactor: 4.0}), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAADElEQVQYlWNgIAoAAAAnAAGZWEMnAAAAAElFTkSuQmCC') + }) }) }) From 6e977cbc3abe9ee8c3233ac8bcadefd78c78371a Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 7 Mar 2017 14:26:31 -0800 Subject: [PATCH 389/925] Assert emptiness and size of built up images --- spec/api-native-image-spec.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/api-native-image-spec.js b/spec/api-native-image-spec.js index af90332e5a..206a180441 100644 --- a/spec/api-native-image-spec.js +++ b/spec/api-native-image-spec.js @@ -270,6 +270,8 @@ describe('nativeImage module', () => { buffer: 'invalid' }) + assert.equal(image.isEmpty(), false) + assert.deepEqual(image.getSize(), {width: 1, height: 1}) assert.equal(image.toDataURL({scaleFactor: 1.0}), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQYlWNgAAIAAAUAAdafFs0AAAAASUVORK5CYII=') assert.equal(image.toDataURL({scaleFactor: 2.0}), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFUlEQVQYlWP8////fwYGBgYmBigAAD34BABBrq9BAAAAAElFTkSuQmCC') assert.equal(image.toDataURL({scaleFactor: 3.0}), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAADElEQVQYlWNgIAoAAAAnAAGZWEMnAAAAAElFTkSuQmCC') @@ -295,6 +297,8 @@ describe('nativeImage module', () => { dataURL: 'invalid' }) + assert.equal(image.isEmpty(), false) + assert.deepEqual(image.getSize(), {width: 1, height: 1}) assert.equal(image.toDataURL({scaleFactor: 1.0}), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQYlWNgAAIAAAUAAdafFs0AAAAASUVORK5CYII=') assert.equal(image.toDataURL({scaleFactor: 2.0}), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFUlEQVQYlWP8////fwYGBgYmBigAAD34BABBrq9BAAAAAElFTkSuQmCC') assert.equal(image.toDataURL({scaleFactor: 3.0}), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAADElEQVQYlWNgIAoAAAAnAAGZWEMnAAAAAElFTkSuQmCC') From 089bfd2cd2e24e5bb85e0a89c0bdf67b78c5f242 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 7 Mar 2017 14:29:37 -0800 Subject: [PATCH 390/925] Document nativeImage.addRepresentation(options) --- docs/api/native-image.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/api/native-image.md b/docs/api/native-image.md index 0350f13bc9..69658d6f87 100644 --- a/docs/api/native-image.md +++ b/docs/api/native-image.md @@ -265,4 +265,18 @@ will be preserved in the resized image. Returns `Float` - The image's aspect ratio. +#### `image.addRepresentation(options)` + +* `options` Object + * `scaleFactor` Double - The scale factor to add the image representation for. + * `width` Integer (optional) + * `height` Integer (optional) + * `buffer` Buffer (optional) - The buffer containing the raw image data. + * `dataURL` String (optional) - The data URL containing either a base 64 + encoded PNG or JPEG image. + +Add an image representation for a specific scale factor. This can be used +to explicitly add different scale factor representations to an image. This +can be called on empty images. + [buffer]: https://nodejs.org/api/buffer.html#buffer_class_buffer From 2946f624ac3fc0fa7bd4cfe288765c645110cd65 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 8 Mar 2017 14:53:08 -0800 Subject: [PATCH 391/925] Add spec for adding representation to existing image --- spec/api-native-image-spec.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/spec/api-native-image-spec.js b/spec/api-native-image-spec.js index 206a180441..9b8e5c3a2e 100644 --- a/spec/api-native-image-spec.js +++ b/spec/api-native-image-spec.js @@ -304,5 +304,20 @@ describe('nativeImage module', () => { assert.equal(image.toDataURL({scaleFactor: 3.0}), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAADElEQVQYlWNgIAoAAAAnAAGZWEMnAAAAAElFTkSuQmCC') assert.equal(image.toDataURL({scaleFactor: 4.0}), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAADElEQVQYlWNgIAoAAAAnAAGZWEMnAAAAAElFTkSuQmCC') }) + + it('supports adding a representation to an existing image', () => { + const image = nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', '1x1.png')) + image.addRepresentation({ + scaleFactor: 2.0, + dataURL: nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', '2x2.jpg')).toDataURL() + }) + image.addRepresentation({ + scaleFactor: 2.0, + dataURL: nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', '3x3.png')).toDataURL() + }) + + assert.equal(image.toDataURL({scaleFactor: 1.0}), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQYlWNgAAIAAAUAAdafFs0AAAAASUVORK5CYII=') + assert.equal(image.toDataURL({scaleFactor: 2.0}), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFUlEQVQYlWP8////fwYGBgYmBigAAD34BABBrq9BAAAAAElFTkSuQmCC') + }) }) }) From f19998ef4c7ac77aa55e4f2cc5e30fe97fa9de14 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 9 Mar 2017 10:12:03 -0800 Subject: [PATCH 392/925] Add more default values --- docs/api/native-image.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/api/native-image.md b/docs/api/native-image.md index 69658d6f87..100fb0e1a2 100644 --- a/docs/api/native-image.md +++ b/docs/api/native-image.md @@ -247,8 +247,8 @@ Returns `NativeImage` - The cropped image. #### `image.resize(options)` * `options` Object - * `width` Integer (optional) - * `height` Integer (optional) + * `width` Integer (optional) - Defaults to the image's width. + * `height` Integer (optional) - Defaults to the image's height * `quality` String (optional) - The desired quality of the resize image. Possible values are `good`, `better` or `best`. The default is `best`. These values express a desired quality/speed tradeoff. They are translated @@ -269,8 +269,10 @@ Returns `Float` - The image's aspect ratio. * `options` Object * `scaleFactor` Double - The scale factor to add the image representation for. - * `width` Integer (optional) - * `height` Integer (optional) + * `width` Integer (optional) - Defaults to 0. Required if a bitmap buffer + is specified as `buffer`. + * `height` Integer (optional) - Defaults to 0. Required if a bitmap buffer + is specified as `buffer`. * `buffer` Buffer (optional) - The buffer containing the raw image data. * `dataURL` String (optional) - The data URL containing either a base 64 encoded PNG or JPEG image. From e03e37e984cd75cc82ea8d821ae0d5aaca9d1cf2 Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Mon, 6 Mar 2017 15:59:38 -0300 Subject: [PATCH 393/925] Pass `uploadToServer` parameter to linux crash reporter --- atom/common/crash_reporter/crash_reporter_linux.cc | 14 ++++++++++++-- atom/common/crash_reporter/crash_reporter_linux.h | 3 +++ docs/api/crash-reporter.md | 6 +++--- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/atom/common/crash_reporter/crash_reporter_linux.cc b/atom/common/crash_reporter/crash_reporter_linux.cc index 9020977716..d14c15de19 100644 --- a/atom/common/crash_reporter/crash_reporter_linux.cc +++ b/atom/common/crash_reporter/crash_reporter_linux.cc @@ -38,7 +38,8 @@ static const off_t kMaxMinidumpFileSize = 1258291; CrashReporterLinux::CrashReporterLinux() : process_start_time_(0), - pid_(getpid()) { + pid_(getpid()), + upload_to_server_(true) { // Set the base process start time value. struct timeval tv; if (!gettimeofday(&tv, NULL)) { @@ -69,6 +70,7 @@ void CrashReporterLinux::InitBreakpad(const std::string& product_name, crash_keys_->SetKeyValue("prod", ATOM_PRODUCT_NAME); crash_keys_->SetKeyValue("ver", version.c_str()); upload_url_ = submit_url; + upload_to_server_ = upload_to_server; for (StringMap::const_iterator iter = upload_parameters_.begin(); iter != upload_parameters_.end(); ++iter) @@ -79,6 +81,14 @@ void CrashReporterLinux::SetUploadParameters() { upload_parameters_["platform"] = "linux"; } +void CrashReporterLinux::SetUploadToServer(const bool upload_to_server) { + upload_to_server_ = upload_to_server; +} + +bool CrashReporterLinux::GetUploadToServer() { + return upload_to_server_; +} + void CrashReporterLinux::EnableCrashDumping(const base::FilePath& crashes_dir) { base::CreateDirectory(crashes_dir); @@ -117,7 +127,7 @@ bool CrashReporterLinux::CrashDone(const MinidumpDescriptor& minidump, info.fd = minidump.fd(); info.distro = base::g_linux_distro; info.distro_length = my_strlen(base::g_linux_distro); - info.upload = true; + info.upload = self->upload_to_server_; info.process_start_time = self->process_start_time_; info.oom_size = base::g_oom_size; info.pid = self->pid_; diff --git a/atom/common/crash_reporter/crash_reporter_linux.h b/atom/common/crash_reporter/crash_reporter_linux.h index 997caf1c27..75a57ec1c1 100644 --- a/atom/common/crash_reporter/crash_reporter_linux.h +++ b/atom/common/crash_reporter/crash_reporter_linux.h @@ -34,7 +34,9 @@ class CrashReporterLinux : public CrashReporter { const base::FilePath& crashes_dir, bool upload_to_server, bool skip_system_crash_handler) override; + void SetUploadToServer(bool upload_to_server) override; void SetUploadParameters() override; + bool GetUploadToServer() override; private: friend struct base::DefaultSingletonTraits; @@ -54,6 +56,7 @@ class CrashReporterLinux : public CrashReporter { uint64_t process_start_time_; pid_t pid_; std::string upload_url_; + bool upload_to_server_; DISALLOW_COPY_AND_ASSIGN(CrashReporterLinux); }; diff --git a/docs/api/crash-reporter.md b/docs/api/crash-reporter.md index 818aa8e7b5..98bf0f3662 100644 --- a/docs/api/crash-reporter.md +++ b/docs/api/crash-reporter.md @@ -40,7 +40,7 @@ The `crashReporter` module has the following methods: * `companyName` String (optional) * `submitURL` String - URL that crash reports will be sent to as POST. * `productName` String (optional) - Defaults to `app.getName()`. - * `uploadToServer` Boolean (optional) _macOS_ - Whether crash reports should be sent to the server + * `uploadToServer` Boolean (optional) _Linux_ _macOS_ - Whether crash reports should be sent to the server Default is `true`. * `ignoreSystemCrashHandler` Boolean (optional) - Default is `false`. * `extra` Object (optional) - An object you can define that will be sent along with the @@ -99,14 +99,14 @@ Returns [`CrashReport[]`](structures/crash-report.md): Returns all uploaded crash reports. Each report contains the date and uploaded ID. -### `crashReporter.getUploadToServer()` _macOS_ +### `crashReporter.getUploadToServer()` _Linux_ _macOS_ Returns `Boolean` - Whether reports should be submitted to the server. Set through the `start` method or `setUploadToServer`. **Note:** This API can only be called from the main process. -### `crashReporter.setUploadToServer(uploadToServer)` _macOS_ +### `crashReporter.setUploadToServer(uploadToServer)` _Linux_ _macOS_ * `uploadToServer` Boolean _macOS_ - Whether reports should be submitted to the server From 44bd93589a5ee3732d5a51432830970a433a0674 Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Mon, 6 Mar 2017 11:58:10 -0300 Subject: [PATCH 394/925] Refactor electron exports to keep module list in separate files --- filenames.gypi | 3 + lib/browser/api/exports/electron.js | 134 ++------------------------- lib/browser/api/module-list.js | 24 +++++ lib/common/api/exports/electron.js | 58 ++---------- lib/common/api/module-list.js | 12 +++ lib/renderer/api/exports/electron.js | 40 ++------ lib/renderer/api/module-list.js | 8 ++ lib/renderer/api/remote.js | 36 ++----- 8 files changed, 79 insertions(+), 236 deletions(-) create mode 100644 lib/browser/api/module-list.js create mode 100644 lib/common/api/module-list.js create mode 100644 lib/renderer/api/module-list.js diff --git a/filenames.gypi b/filenames.gypi index 6ac732c24a..c7d3adb369 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -22,6 +22,7 @@ 'lib/browser/api/menu.js', 'lib/browser/api/menu-item.js', 'lib/browser/api/menu-item-roles.js', + 'lib/browser/api/module-list.js', 'lib/browser/api/navigation-controller.js', 'lib/browser/api/net.js', 'lib/browser/api/power-monitor.js', @@ -47,6 +48,7 @@ 'lib/common/api/deprecations.js', 'lib/common/api/is-promise.js', 'lib/common/api/exports/electron.js', + 'lib/common/api/module-list.js', 'lib/common/api/native-image.js', 'lib/common/api/shell.js', 'lib/common/init.js', @@ -66,6 +68,7 @@ 'lib/renderer/api/exports/electron.js', 'lib/renderer/api/ipc-renderer.js', 'lib/renderer/api/ipc-renderer-setup.js', + 'lib/renderer/api/module-list.js', 'lib/renderer/api/remote.js', 'lib/renderer/api/screen.js', 'lib/renderer/api/web-frame.js', diff --git a/lib/browser/api/exports/electron.js b/lib/browser/api/exports/electron.js index 3f40595217..753aa30fd5 100644 --- a/lib/browser/api/exports/electron.js +++ b/lib/browser/api/exports/electron.js @@ -1,131 +1,13 @@ const common = require('../../../common/api/exports/electron') +// since browser module list is also used in renderer, keep it separate. +const moduleList = require('../module-list') // Import common modules. common.defineProperties(exports) -Object.defineProperties(exports, { - // Browser side modules, please sort alphabetically. - // Any modules added here must also be added to the browserModules array - // in remote.js - app: { - enumerable: true, - get: function () { - return require('../app') - } - }, - autoUpdater: { - enumerable: true, - get: function () { - return require('../auto-updater') - } - }, - BrowserWindow: { - enumerable: true, - get: function () { - return require('../browser-window') - } - }, - contentTracing: { - enumerable: true, - get: function () { - return require('../content-tracing') - } - }, - dialog: { - enumerable: true, - get: function () { - return require('../dialog') - } - }, - globalShortcut: { - enumerable: true, - get: function () { - return require('../global-shortcut') - } - }, - ipcMain: { - enumerable: true, - get: function () { - return require('../ipc-main') - } - }, - Menu: { - enumerable: true, - get: function () { - return require('../menu') - } - }, - MenuItem: { - enumerable: true, - get: function () { - return require('../menu-item') - } - }, - net: { - enumerable: true, - get: function () { - return require('../net') - } - }, - powerMonitor: { - enumerable: true, - get: function () { - return require('../power-monitor') - } - }, - powerSaveBlocker: { - enumerable: true, - get: function () { - return require('../power-save-blocker') - } - }, - protocol: { - enumerable: true, - get: function () { - return require('../protocol') - } - }, - screen: { - enumerable: true, - get: function () { - return require('../screen') - } - }, - session: { - enumerable: true, - get: function () { - return require('../session') - } - }, - systemPreferences: { - enumerable: true, - get: function () { - return require('../system-preferences') - } - }, - TouchBar: { - enumerable: true, - get: function () { - return require('../touch-bar') - } - }, - Tray: { - enumerable: true, - get: function () { - return require('../tray') - } - }, - webContents: { - enumerable: true, - get: function () { - return require('../web-contents') - } - }, - - // The internal modules, invisible unless you know their names. - NavigationController: { - get: function () { - return require('../navigation-controller') - } - } -}) +for (const module of moduleList) { + Object.defineProperty(exports, module.name, { + enumerable: !module.private, + get: () => require(`../${module.file}`) + }) +} diff --git a/lib/browser/api/module-list.js b/lib/browser/api/module-list.js new file mode 100644 index 0000000000..3274f0b6d4 --- /dev/null +++ b/lib/browser/api/module-list.js @@ -0,0 +1,24 @@ +// Browser side modules, please sort alphabetically. +module.exports = [ + {name: 'app', file: 'app'}, + {name: 'autoUpdater', file: 'auto-updater'}, + {name: 'BrowserWindow', file: 'browser-window'}, + {name: 'contentTracing', file: 'content-tracing'}, + {name: 'dialog', file: 'dialog'}, + {name: 'globalShortcut', file: 'global-shortcut'}, + {name: 'ipcMain', file: 'ipc-main'}, + {name: 'Menu', file: 'menu'}, + {name: 'MenuItem', file: 'menu-item'}, + {name: 'net', file: 'net'}, + {name: 'powerMonitor', file: 'power-monitor'}, + {name: 'powerSaveBlocker', file: 'power-save-blocker'}, + {name: 'protocol', file: 'protocol'}, + {name: 'screen', file: 'screen'}, + {name: 'session', file: 'session'}, + {name: 'systemPreferences', file: 'system-preferences'}, + {name: 'TouchBar', file: 'touch-bar'}, + {name: 'Tray', file: 'tray'}, + {name: 'webContents', file: 'web-contents'}, + // The internal modules, invisible unless you know their names. + {name: 'NavigationController', file: 'navigation-controller', private: true} +] diff --git a/lib/common/api/exports/electron.js b/lib/common/api/exports/electron.js index efa44d452c..88b1f78587 100644 --- a/lib/common/api/exports/electron.js +++ b/lib/common/api/exports/electron.js @@ -1,53 +1,13 @@ +const moduleList = require('../module-list') + // Attaches properties to |exports|. exports.defineProperties = function (exports) { - return Object.defineProperties(exports, { - // Common modules, please sort with alphabet order. - clipboard: { - // Must be enumerable, otherwise it woulde be invisible to remote module. - enumerable: true, - get: function () { - return require('../clipboard') - } - }, - crashReporter: { - enumerable: true, - get: function () { - return require('../crash-reporter') - } - }, - nativeImage: { - enumerable: true, - get: function () { - return require('../native-image') - } - }, - shell: { - enumerable: true, - get: function () { - return require('../shell') - } - }, - - // The internal modules, invisible unless you know their names. - CallbacksRegistry: { - get: function () { - return require('../callbacks-registry') - } - }, - deprecate: { - get: function () { - return require('../deprecate') - } - }, - deprecations: { - get: function () { - return require('../deprecations') - } - }, - isPromise: { - get: function () { - return require('../is-promise') - } + const descriptors = {} + for (const module of moduleList) { + descriptors[module.name] = { + enumerable: !module.private, + get: () => require(`../${module.file}`) } - }) + } + return Object.defineProperties(exports, descriptors) } diff --git a/lib/common/api/module-list.js b/lib/common/api/module-list.js new file mode 100644 index 0000000000..00c208c805 --- /dev/null +++ b/lib/common/api/module-list.js @@ -0,0 +1,12 @@ +// Common modules, please sort alphabetically +module.exports = [ + {name: 'clipboard', file: 'clipboard'}, + {name: 'crashReporter', file: 'crash-reporter'}, + {name: 'nativeImage', file: 'native-image'}, + {name: 'shell', file: 'shell'}, + // The internal modules, invisible unless you know their names. + {name: 'CallbacksRegistry', file: 'callbacks-registry', private: true}, + {name: 'deprecate', file: 'deprecate', private: true}, + {name: 'deprecations', file: 'deprecations', private: true}, + {name: 'isPromise', file: 'is-promise', private: true} +] diff --git a/lib/renderer/api/exports/electron.js b/lib/renderer/api/exports/electron.js index 8e05adc980..d9b377a985 100644 --- a/lib/renderer/api/exports/electron.js +++ b/lib/renderer/api/exports/electron.js @@ -1,38 +1,12 @@ const common = require('../../../common/api/exports/electron') +const moduleList = require('../module-list') // Import common modules. common.defineProperties(exports) -Object.defineProperties(exports, { - // Renderer side modules, please sort with alphabet order. - desktopCapturer: { - enumerable: true, - get: function () { - return require('../desktop-capturer') - } - }, - ipcRenderer: { - enumerable: true, - get: function () { - return require('../ipc-renderer') - } - }, - remote: { - enumerable: true, - get: function () { - return require('../remote') - } - }, - screen: { - enumerable: true, - get: function () { - return require('../screen') - } - }, - webFrame: { - enumerable: true, - get: function () { - return require('../web-frame') - } - } -}) +for (const module of moduleList) { + Object.defineProperty(exports, module.name, { + enumerable: !module.private, + get: () => require(`../${module.file}`) + }) +} diff --git a/lib/renderer/api/module-list.js b/lib/renderer/api/module-list.js new file mode 100644 index 0000000000..4d0e1959de --- /dev/null +++ b/lib/renderer/api/module-list.js @@ -0,0 +1,8 @@ +// Renderer side modules, please sort alphabetically. +module.exports = [ + {name: 'desktopCapturer', file: 'desktop-capturer'}, + {name: 'ipcRenderer', file: 'ipc-renderer'}, + {name: 'remote', file: 'remote'}, + {name: 'screen', file: 'screen'}, + {name: 'webFrame', file: 'web-frame'} +] diff --git a/lib/renderer/api/remote.js b/lib/renderer/api/remote.js index fd94390d77..8277e16c21 100644 --- a/lib/renderer/api/remote.js +++ b/lib/renderer/api/remote.js @@ -340,32 +340,12 @@ const addBuiltinProperty = (name) => { }) } -// Add each browser module name as a property -// This list should match the exports in browser/api/exports/electron.js -const browserModules = [ - 'app', - 'autoUpdater', - 'BrowserWindow', - 'contentTracing', - 'dialog', - 'globalShortcut', - 'ipcMain', - 'Menu', - 'MenuItem', - 'net', - 'powerMonitor', - 'powerSaveBlocker', - 'protocol', - 'screen', - 'session', - 'systemPreferences', - 'TouchBar', - 'Tray', - 'webContents' -] -browserModules.forEach(addBuiltinProperty) +const browserModules = + require('../../common/api/module-list').concat( + require('../../browser/api/module-list')) -// Add each common module name as a property -const commonModules = {} -require('../../common/api/exports/electron').defineProperties(commonModules) -Object.getOwnPropertyNames(commonModules).forEach(addBuiltinProperty) +// And add a helper receiver for each one. +browserModules + .filter((m) => !m.private) + .map((m) => m.name) + .forEach(addBuiltinProperty) From d78f3cae7b81a12ded2f88ca67c776dd9184eca7 Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Mon, 20 Feb 2017 10:59:39 -0300 Subject: [PATCH 395/925] Expose builtin v8 modules to AtomSandboxedRendererClient - Adapt node.cc code that implements `process.binding` to create a similar object in AtomSandboxedRendererClient. - Replace the ipc binding object passed to `lib/sandboxed_renderer/init.js` by the new binding object. - Refactor the initialization script to use this new object to fetch the ipc binding and store as a hidden value using the `v8_util` module. This change also required applying a patch to node.js, so the submodule commit was updated. --- .../atom_sandboxed_renderer_client.cc | 69 ++++++++++++++++--- .../renderer/atom_sandboxed_renderer_client.h | 6 +- filenames.gypi | 1 + lib/common/atom-binding-setup.js | 13 ++++ lib/common/init.js | 14 +--- lib/sandboxed_renderer/init.js | 15 ++-- vendor/node | 2 +- 7 files changed, 89 insertions(+), 31 deletions(-) create mode 100644 lib/common/atom-binding-setup.js diff --git a/atom/renderer/atom_sandboxed_renderer_client.cc b/atom/renderer/atom_sandboxed_renderer_client.cc index 6c3f3e5b49..16f44ace12 100644 --- a/atom/renderer/atom_sandboxed_renderer_client.cc +++ b/atom/renderer/atom_sandboxed_renderer_client.cc @@ -11,6 +11,7 @@ #include "atom/common/api/api_messages.h" #include "atom/common/native_mate_converters/string16_converter.h" #include "atom/common/native_mate_converters/value_converter.h" +#include "atom/common/node_includes.h" #include "atom/common/options_switches.h" #include "atom/renderer/api/atom_api_renderer_ipc.h" #include "atom/renderer/atom_render_view_observer.h" @@ -21,6 +22,8 @@ #include "content/public/renderer/render_view.h" #include "content/public/renderer/render_view_observer.h" #include "ipc/ipc_message_macros.h" +#include "native_mate/converter.h" +#include "native_mate/dictionary.h" #include "third_party/WebKit/public/web/WebFrame.h" #include "third_party/WebKit/public/web/WebKit.h" #include "third_party/WebKit/public/web/WebLocalFrame.h" @@ -31,7 +34,57 @@ namespace atom { namespace { -const std::string kBindingKey = "binding"; +const std::string kIpcKey = "ipc"; +const std::string kModuleCacheKey = "native-module-cache"; + + +v8::Local GetModuleCache(v8::Isolate* isolate) { + mate::Dictionary global(isolate, isolate->GetCurrentContext()->Global()); + v8::Local cache; + + if (!global.GetHidden(kModuleCacheKey, &cache)) { + cache = v8::Object::New(isolate); + global.SetHidden(kModuleCacheKey, cache); + } + + return cache->ToObject(); +} + +// adapted from node.cc +v8::Local GetBinding(v8::Isolate* isolate, v8::Local key, + mate::Arguments* margs) { + v8::Local exports; + std::string module_key = mate::V8ToString(key); + mate::Dictionary cache(isolate, GetModuleCache(isolate)); + + if (cache.Get(module_key.c_str(), &exports)) { + return exports; + } + + auto mod = node::get_builtin_module(module_key.c_str()); + + if (!mod) { + char errmsg[1024]; + snprintf(errmsg, sizeof(errmsg), "No such module: %s", module_key.c_str()); + margs->ThrowError(errmsg); + return exports; + } + + exports = v8::Object::New(isolate); + DCHECK_EQ(mod->nm_register_func, nullptr); + DCHECK_NE(mod->nm_context_register_func, nullptr); + mod->nm_context_register_func(exports, v8::Null(isolate), + isolate->GetCurrentContext(), mod->nm_priv); + cache.Set(module_key.c_str(), exports); + return exports; +} + +void InitializeBindings(v8::Local binding, + v8::Local context) { + auto isolate = context->GetIsolate(); + mate::Dictionary b(isolate, binding); + b.SetMethod("get", GetBinding); +} class AtomSandboxedRenderFrameObserver : public content::RenderFrameObserver { public: @@ -100,7 +153,7 @@ class AtomSandboxedRenderViewObserver : public AtomRenderViewObserver { mate::ConvertToV8(isolate, channel), mate::ConvertToV8(isolate, args) }; - renderer_client_->InvokeBindingCallback( + renderer_client_->InvokeIpcCallback( context, "onMessage", std::vector>(argv, argv + 2)); @@ -158,17 +211,13 @@ void AtomSandboxedRendererClient::DidCreateScriptContext( script->Run(context).ToLocalChecked()); // Create and initialize the binding object auto binding = v8::Object::New(isolate); - api::Initialize(binding, v8::Null(isolate), context, nullptr); + InitializeBindings(binding, context); v8::Local args[] = { binding, mate::ConvertToV8(isolate, preload_script) }; // Execute the function with proper arguments ignore_result(func->Call(context, v8::Null(isolate), 2, args)); - // Store the bindingt privately for handling messages from the main process. - auto binding_key = mate::ConvertToV8(isolate, kBindingKey)->ToString(); - auto private_binding_key = v8::Private::ForApi(isolate, binding_key); - context->Global()->SetPrivate(context, private_binding_key, binding); } void AtomSandboxedRendererClient::WillReleaseScriptContext( @@ -176,15 +225,15 @@ void AtomSandboxedRendererClient::WillReleaseScriptContext( auto isolate = context->GetIsolate(); v8::HandleScope handle_scope(isolate); v8::Context::Scope context_scope(context); - InvokeBindingCallback(context, "onExit", std::vector>()); + InvokeIpcCallback(context, "onExit", std::vector>()); } -void AtomSandboxedRendererClient::InvokeBindingCallback( +void AtomSandboxedRendererClient::InvokeIpcCallback( v8::Handle context, std::string callback_name, std::vector> args) { auto isolate = context->GetIsolate(); - auto binding_key = mate::ConvertToV8(isolate, kBindingKey)->ToString(); + auto binding_key = mate::ConvertToV8(isolate, kIpcKey)->ToString(); auto private_binding_key = v8::Private::ForApi(isolate, binding_key); auto global_object = context->Global(); v8::Local value; diff --git a/atom/renderer/atom_sandboxed_renderer_client.h b/atom/renderer/atom_sandboxed_renderer_client.h index 66d8278915..0912c6b24b 100644 --- a/atom/renderer/atom_sandboxed_renderer_client.h +++ b/atom/renderer/atom_sandboxed_renderer_client.h @@ -21,9 +21,9 @@ class AtomSandboxedRendererClient : public content::ContentRendererClient { v8::Handle context, content::RenderFrame* render_frame); void WillReleaseScriptContext( v8::Handle context, content::RenderFrame* render_frame); - void InvokeBindingCallback(v8::Handle context, - std::string callback_name, - std::vector> args); + void InvokeIpcCallback(v8::Handle context, + std::string callback_name, + std::vector> args); // content::ContentRendererClient: void RenderFrameCreated(content::RenderFrame*) override; void RenderViewCreated(content::RenderView*) override; diff --git a/filenames.gypi b/filenames.gypi index c7d3adb369..f265af30cd 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -51,6 +51,7 @@ 'lib/common/api/module-list.js', 'lib/common/api/native-image.js', 'lib/common/api/shell.js', + 'lib/common/atom-binding-setup.js', 'lib/common/init.js', 'lib/common/parse-features-string.js', 'lib/common/reset-search-paths.js', diff --git a/lib/common/atom-binding-setup.js b/lib/common/atom-binding-setup.js new file mode 100644 index 0000000000..8292a23d69 --- /dev/null +++ b/lib/common/atom-binding-setup.js @@ -0,0 +1,13 @@ +module.exports = function atomBindingSetup (binding, processType) { + return function atomBinding (name) { + try { + return binding(`atom_${processType}_${name}`) + } catch (error) { + if (/No such module/.test(error.message)) { + return binding(`atom_common_${name}`) + } else { + throw error + } + } + } +} diff --git a/lib/common/init.js b/lib/common/init.js index 00acd6f06a..d732352a30 100644 --- a/lib/common/init.js +++ b/lib/common/init.js @@ -1,18 +1,6 @@ const timers = require('timers') -const {binding} = process - -process.atomBinding = function (name) { - try { - return binding(`atom_${process.type}_${name}`) - } catch (error) { - if (/No such module/.test(error.message)) { - return binding(`atom_common_${name}`) - } else { - throw error - } - } -} +process.atomBinding = require('./atom-binding-setup')(process.binding, process.type) // setImmediate and process.nextTick makes use of uv_check and uv_prepare to // run the callbacks, however since we only run uv loop on requests, the diff --git a/lib/sandboxed_renderer/init.js b/lib/sandboxed_renderer/init.js index 68daf476ca..81ffd19a99 100644 --- a/lib/sandboxed_renderer/init.js +++ b/lib/sandboxed_renderer/init.js @@ -2,25 +2,32 @@ // in filenames.gypi so they get built into the preload_bundle.js bundle /* eslint no-eval: "off" */ -/* global binding, preloadPath, process, Buffer */ +/* global binding, preloadPath, Buffer */ const events = require('events') +const atomBinding = require('../common/atom-binding-setup')(binding.get, 'renderer') + +const v8Util = atomBinding('v8_util') +const ipc = atomBinding('ipc') + const ipcRenderer = new events.EventEmitter() const proc = new events.EventEmitter() // eval in window scope: // http://www.ecma-international.org/ecma-262/5.1/#sec-10.4.2 const geval = eval -require('../renderer/api/ipc-renderer-setup')(ipcRenderer, binding) +require('../renderer/api/ipc-renderer-setup')(ipcRenderer, ipc) -binding.onMessage = function (channel, args) { +ipc.onMessage = function (channel, args) { ipcRenderer.emit(channel, ...args) } -binding.onExit = function () { +ipc.onExit = function () { proc.emit('exit') } +v8Util.setHiddenValue(global, 'ipc', ipc) + const preloadModules = new Map([ ['electron', { ipcRenderer: ipcRenderer diff --git a/vendor/node b/vendor/node index 0f84d972a1..a6663598aa 160000 --- a/vendor/node +++ b/vendor/node @@ -1 +1 @@ -Subproject commit 0f84d972a1b48b7da361f9717ff43349a7946abd +Subproject commit a6663598aa78832e7955cb93c51a098eac787abb From 2b8203e383f7681d6070572e530e803f987b5527 Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Mon, 27 Feb 2017 10:23:32 -0300 Subject: [PATCH 396/925] Make sandboxed ipcRenderer API compatible with non-sandboxed version --- lib/sandboxed_renderer/init.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/sandboxed_renderer/init.js b/lib/sandboxed_renderer/init.js index 81ffd19a99..82b703f259 100644 --- a/lib/sandboxed_renderer/init.js +++ b/lib/sandboxed_renderer/init.js @@ -19,7 +19,7 @@ const geval = eval require('../renderer/api/ipc-renderer-setup')(ipcRenderer, ipc) ipc.onMessage = function (channel, args) { - ipcRenderer.emit(channel, ...args) + ipcRenderer.emit(channel, {sender: ipcRenderer}, ...args) } ipc.onExit = function () { From cd05834d96357b7b1592cee91eae965be1c11a15 Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Mon, 27 Feb 2017 14:14:36 -0300 Subject: [PATCH 397/925] Refactor sandboxed renderer init scripts This change gives sandboxed renderer scripts a similar structure to what already exists in the lib/{browser,renderer,common} directories. It also allows sandboxed renderer initialization to share more code with non-sandboxed renderers (ipcRenderer is now imported directly from lib/renderer/api/ipc-renderer). --- .../atom_sandboxed_renderer_client.cc | 2 +- electron.gyp | 2 + filenames.gypi | 1 + .../api/exports/electron.js | 8 ++ lib/sandboxed_renderer/api/ipc-renderer.js | 20 +++++ lib/sandboxed_renderer/init.js | 81 ++++++++++--------- 6 files changed, 77 insertions(+), 37 deletions(-) create mode 100644 lib/sandboxed_renderer/api/exports/electron.js create mode 100644 lib/sandboxed_renderer/api/ipc-renderer.js diff --git a/atom/renderer/atom_sandboxed_renderer_client.cc b/atom/renderer/atom_sandboxed_renderer_client.cc index 16f44ace12..952ce26d6c 100644 --- a/atom/renderer/atom_sandboxed_renderer_client.cc +++ b/atom/renderer/atom_sandboxed_renderer_client.cc @@ -34,7 +34,7 @@ namespace atom { namespace { -const std::string kIpcKey = "ipc"; +const std::string kIpcKey = "ipcNative"; const std::string kModuleCacheKey = "native-module-cache"; diff --git a/electron.gyp b/electron.gyp index 299fe34ea6..47bc67b98e 100644 --- a/electron.gyp +++ b/electron.gyp @@ -452,6 +452,8 @@ 'browserify', '--', 'lib/sandboxed_renderer/init.js', + '-r', + './lib/sandboxed_renderer/api/exports/electron.js:electron', '-o', '<@(_outputs)', ], diff --git a/filenames.gypi b/filenames.gypi index f265af30cd..2404574982 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -81,6 +81,7 @@ 'browserify_entries': [ 'lib/renderer/api/ipc-renderer-setup.js', 'lib/sandboxed_renderer/init.js', + 'lib/sandboxed_renderer/api/exports/electron.js', ], 'isolated_context_browserify_entries': [ 'lib/renderer/window-setup.js', diff --git a/lib/sandboxed_renderer/api/exports/electron.js b/lib/sandboxed_renderer/api/exports/electron.js new file mode 100644 index 0000000000..3a09caa11b --- /dev/null +++ b/lib/sandboxed_renderer/api/exports/electron.js @@ -0,0 +1,8 @@ +Object.defineProperties(exports, { + ipcRenderer: { + enumerable: true, + get: function () { + return require('../ipc-renderer') + } + } +}) diff --git a/lib/sandboxed_renderer/api/ipc-renderer.js b/lib/sandboxed_renderer/api/ipc-renderer.js new file mode 100644 index 0000000000..008372b4aa --- /dev/null +++ b/lib/sandboxed_renderer/api/ipc-renderer.js @@ -0,0 +1,20 @@ +const ipcRenderer = require('../../renderer/api/ipc-renderer') + +const v8Util = process.atomBinding('v8_util') +const ipcNative = process.atomBinding('ipc') + +// AtomSandboxedRendererClient will look for the "ipcNative" hidden object when +// invoking the `onMessage`/`onExit` callbacks. We could reuse "ipc" and assign +// `onMessage`/`onExit` directly to `ipcRenderer`, but it is better to separate +// private/public APIs. +v8Util.setHiddenValue(global, 'ipcNative', ipcNative) + +ipcNative.onMessage = function (channel, args) { + ipcRenderer.emit(channel, {sender: ipcRenderer}, ...args) +} + +ipcNative.onExit = function () { + process.emit('exit') +} + +module.exports = ipcRenderer diff --git a/lib/sandboxed_renderer/init.js b/lib/sandboxed_renderer/init.js index 82b703f259..390ab1259d 100644 --- a/lib/sandboxed_renderer/init.js +++ b/lib/sandboxed_renderer/init.js @@ -5,35 +5,39 @@ /* global binding, preloadPath, Buffer */ const events = require('events') -const atomBinding = require('../common/atom-binding-setup')(binding.get, 'renderer') +process.atomBinding = require('../common/atom-binding-setup')(binding.get, 'renderer') -const v8Util = atomBinding('v8_util') -const ipc = atomBinding('ipc') - -const ipcRenderer = new events.EventEmitter() -const proc = new events.EventEmitter() -// eval in window scope: -// http://www.ecma-international.org/ecma-262/5.1/#sec-10.4.2 -const geval = eval - -require('../renderer/api/ipc-renderer-setup')(ipcRenderer, ipc) - -ipc.onMessage = function (channel, args) { - ipcRenderer.emit(channel, {sender: ipcRenderer}, ...args) +const v8Util = process.atomBinding('v8_util') +// The `lib/renderer/api/ipc-renderer.js` module looks for the ipc object in the +// "ipc" hidden value +v8Util.setHiddenValue(global, 'ipc', new events.EventEmitter()) +// The process object created by browserify is not an event emitter, fix it so +// the API is more compatible with non-sandboxed renderers. +for (let prop of Object.keys(events.EventEmitter.prototype)) { + if (process.hasOwnProperty(prop)) { + delete process[prop] + } } +Object.setPrototypeOf(process, events.EventEmitter.prototype) -ipc.onExit = function () { - proc.emit('exit') -} - -v8Util.setHiddenValue(global, 'ipc', ipc) - +const electron = require('electron') const preloadModules = new Map([ - ['electron', { - ipcRenderer: ipcRenderer - }] + ['electron', electron] ]) +// Fetch the preload script. This needs to be done through the browser process +// since we may not have filesystem access in a sandboxed renderer. +let preloadSrc = electron.ipcRenderer.sendSync('ELECTRON_BROWSER_READ_FILE', preloadPath) +if (preloadSrc.err) { + throw new Error(preloadSrc.err) +} + +// Pass different process object to the preload script(which should not have +// access to things like `process.atomBinding`). +const preloadProcess = new events.EventEmitter() +process.on('exit', () => preloadProcess.emit('exit')) + +// This is the `require` function that will be visible to the preload script function preloadRequire (module) { if (preloadModules.has(module)) { return preloadModules.get(module) @@ -41,26 +45,31 @@ function preloadRequire (module) { throw new Error('module not found') } -// Fetch the source for the preload -let preloadSrc = ipcRenderer.sendSync('ELECTRON_BROWSER_READ_FILE', preloadPath) -if (preloadSrc.err) { - throw new Error(preloadSrc.err) -} - -// Wrap the source into a function receives a `require` function as argument. -// Browserify bundles can make use of this, as explained in: -// https://github.com/substack/node-browserify#multiple-bundles +// Wrap the script into a function executed in global scope. It won't have +// access to the current scope, so we'll expose a few objects as arguments: // -// For example, the user can create a browserify bundle with: +// - `require`: The `preloadRequire` function +// - `process`: The `preloadProcess` object +// - `Buffer`: Browserify `Buffer` implementation +// - `global`: The window object, which is aliased to `global` by browserify. +// +// Browserify bundles can make use of an external require function as explained +// in https://github.com/substack/node-browserify#multiple-bundles, so electron +// apps can use multi-module preload scripts in sandboxed renderers. +// +// For example, the user can create a bundle with: // // $ browserify -x electron preload.js > renderer.js // // and any `require('electron')` calls in `preload.js` will work as expected -// since browserify won't try to include `electron` in the bundle and will fall -// back to the `preloadRequire` function above. +// since browserify won't try to include `electron` in the bundle, falling back +// to the `preloadRequire` function above. let preloadWrapperSrc = `(function(require, process, Buffer, global) { ${preloadSrc.data} })` +// eval in window scope: +// http://www.ecma-international.org/ecma-262/5.1/#sec-10.4.2 +const geval = eval let preloadFn = geval(preloadWrapperSrc) -preloadFn(preloadRequire, proc, Buffer, global) +preloadFn(preloadRequire, preloadProcess, Buffer, global) From dda22885414cf2c205a25feee505a1a4238c1ca7 Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Mon, 27 Feb 2017 14:34:08 -0300 Subject: [PATCH 398/925] Remove lib/renderer/api/ipc-renderer-setup.js This file is no longer required since sandboxed renderer directly imports ipc-renderer.js. --- filenames.gypi | 2 -- lib/renderer/api/ipc-renderer-setup.js | 40 -------------------------- lib/renderer/api/ipc-renderer.js | 37 +++++++++++++++++++++++- 3 files changed, 36 insertions(+), 43 deletions(-) delete mode 100644 lib/renderer/api/ipc-renderer-setup.js diff --git a/filenames.gypi b/filenames.gypi index 2404574982..44a6fbfc43 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -68,7 +68,6 @@ 'lib/renderer/api/desktop-capturer.js', 'lib/renderer/api/exports/electron.js', 'lib/renderer/api/ipc-renderer.js', - 'lib/renderer/api/ipc-renderer-setup.js', 'lib/renderer/api/module-list.js', 'lib/renderer/api/remote.js', 'lib/renderer/api/screen.js', @@ -79,7 +78,6 @@ 'lib/renderer/extensions/web-navigation.js', ], 'browserify_entries': [ - 'lib/renderer/api/ipc-renderer-setup.js', 'lib/sandboxed_renderer/init.js', 'lib/sandboxed_renderer/api/exports/electron.js', ], diff --git a/lib/renderer/api/ipc-renderer-setup.js b/lib/renderer/api/ipc-renderer-setup.js deleted file mode 100644 index c899452fdd..0000000000 --- a/lib/renderer/api/ipc-renderer-setup.js +++ /dev/null @@ -1,40 +0,0 @@ -// Any requires added here need to be added to the browserify_entries array -// in filenames.gypi so they get built into the preload_bundle.js bundle - -module.exports = function (ipcRenderer, binding) { - ipcRenderer.send = function (...args) { - return binding.send('ipc-message', args) - } - - ipcRenderer.sendSync = function (...args) { - return JSON.parse(binding.sendSync('ipc-message-sync', args)) - } - - ipcRenderer.sendToHost = function (...args) { - return binding.send('ipc-message-host', args) - } - - ipcRenderer.sendTo = function (webContentsId, channel, ...args) { - if (typeof webContentsId !== 'number') { - throw new TypeError('First argument has to be webContentsId') - } - - ipcRenderer.send('ELECTRON_BROWSER_SEND_TO', false, webContentsId, channel, ...args) - } - - ipcRenderer.sendToAll = function (webContentsId, channel, ...args) { - if (typeof webContentsId !== 'number') { - throw new TypeError('First argument has to be webContentsId') - } - - ipcRenderer.send('ELECTRON_BROWSER_SEND_TO', true, webContentsId, channel, ...args) - } - - const removeAllListeners = ipcRenderer.removeAllListeners.bind(ipcRenderer) - ipcRenderer.removeAllListeners = function (...args) { - if (args.length === 0) { - throw new Error('Removing all listeners from ipcRenderer will make Electron internals stop working. Please specify a event name') - } - removeAllListeners(...args) - } -} diff --git a/lib/renderer/api/ipc-renderer.js b/lib/renderer/api/ipc-renderer.js index 0a84a0d8ba..d173adb778 100644 --- a/lib/renderer/api/ipc-renderer.js +++ b/lib/renderer/api/ipc-renderer.js @@ -5,6 +5,41 @@ const v8Util = process.atomBinding('v8_util') // Created by init.js. const ipcRenderer = v8Util.getHiddenValue(global, 'ipc') -require('./ipc-renderer-setup')(ipcRenderer, binding) + +ipcRenderer.send = function (...args) { + return binding.send('ipc-message', args) +} + +ipcRenderer.sendSync = function (...args) { + return JSON.parse(binding.sendSync('ipc-message-sync', args)) +} + +ipcRenderer.sendToHost = function (...args) { + return binding.send('ipc-message-host', args) +} + +ipcRenderer.sendTo = function (webContentsId, channel, ...args) { + if (typeof webContentsId !== 'number') { + throw new TypeError('First argument has to be webContentsId') + } + + ipcRenderer.send('ELECTRON_BROWSER_SEND_TO', false, webContentsId, channel, ...args) +} + +ipcRenderer.sendToAll = function (webContentsId, channel, ...args) { + if (typeof webContentsId !== 'number') { + throw new TypeError('First argument has to be webContentsId') + } + + ipcRenderer.send('ELECTRON_BROWSER_SEND_TO', true, webContentsId, channel, ...args) +} + +const removeAllListeners = ipcRenderer.removeAllListeners.bind(ipcRenderer) +ipcRenderer.removeAllListeners = function (...args) { + if (args.length === 0) { + throw new Error('Removing all listeners from ipcRenderer will make Electron internals stop working. Please specify a event name') + } + removeAllListeners(...args) +} module.exports = ipcRenderer From 1c027c526b6b09cd7091824b4666d7ee3a62ee91 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Fri, 10 Mar 2017 17:40:39 +1100 Subject: [PATCH 399/925] Add segmented control implementation --- atom/browser/ui/cocoa/atom_touch_bar.mm | 86 ++++++++++++++++++++++++- lib/browser/api/touch-bar.js | 19 ++++++ 2 files changed, 104 insertions(+), 1 deletion(-) diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index b2f8dc6105..37dc6e7f18 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -18,6 +18,7 @@ static NSTouchBarItemIdentifier GroupIdentifier = @"com.electron.touchbar.group. static NSTouchBarItemIdentifier LabelIdentifier = @"com.electron.touchbar.label."; static NSTouchBarItemIdentifier PopoverIdentifier = @"com.electron.touchbar.popover."; static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slider."; +static NSTouchBarItemIdentifier SegmentedControlIdentifier = @"com.electron.touchbar.segmentedcontrol."; - (id)initWithDelegate:(id)delegate window:(atom::NativeWindow*)window @@ -97,6 +98,9 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide } else if ([identifier hasPrefix:GroupIdentifier]) { item_id = [self idFromIdentifier:identifier withPrefix:GroupIdentifier]; return [self makeGroupForID:item_id withIdentifier:identifier]; + } else if ([identifier hasPrefix:SegmentedControlIdentifier]) { + item_id = [self idFromIdentifier:identifier withPrefix:SegmentedControlIdentifier]; + return [self makeSegmentedControlForID:item_id withIdentifier:identifier]; } return nil; @@ -129,7 +133,8 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide [self updateSlider:(NSSliderTouchBarItem*)item withSettings:settings]; } else if (item_type == "popover") { [self updatePopover:(NSPopoverTouchBarItem*)item withSettings:settings]; - } + } else if (item_type == "segmented_control") + [self updateSegmentedControl:(NSCustomTouchBarItem*)item withSettings:settings]; } - (void)buttonAction:(id)sender { @@ -164,6 +169,14 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide return [identifier substringFromIndex:[prefix length]]; } +- (void)segmentedControlAction:(id)sender { + NSString* item_id = [NSString stringWithFormat:@"%ld", ((NSSegmentedControl*)sender).tag]; + base::DictionaryValue details; + details.SetInteger("selectedIndex", ((NSSegmentedControl*)sender).selectedSegment); + window_->NotifyTouchBarItemInteraction([item_id UTF8String], + details); +} + - (NSTouchBarItemIdentifier)identifierFromID:(const std::string&)item_id type:(const std::string&)type { NSTouchBarItemIdentifier base_identifier = nil; @@ -179,6 +192,8 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide base_identifier = PopoverIdentifier; else if (type == "group") base_identifier = GroupIdentifier; + else if (type == "segmented_control") + base_identifier = SegmentedControlIdentifier; if (base_identifier) return [NSString stringWithFormat:@"%@%s", base_identifier, item_id.data()]; @@ -384,4 +399,73 @@ static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slide items:generatedItems]; } +- (NSTouchBarItem*)makeSegmentedControlForID:(NSString*)id + withIdentifier:(NSString*)identifier { + std::string s_id([id UTF8String]); + if (![self hasItemWithID:s_id]) return nil; + + mate::PersistentDictionary settings = settings_[s_id]; + base::scoped_nsobject item([[NSClassFromString( + @"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]); + + NSSegmentedControl* control = [NSSegmentedControl segmentedControlWithLabels:[NSMutableArray array] + trackingMode:NSSegmentSwitchTrackingSelectOne + target:self + action:@selector(segmentedControlAction:)]; + control.tag = [id floatValue]; + [item setView:control]; + + [self updateSegmentedControl:item withSettings:settings]; + return item.autorelease(); +} + +- (void)updateSegmentedControl:(NSCustomTouchBarItem*)item + withSettings:(const mate::PersistentDictionary&)settings { + + NSSegmentedControl* control = item.view; + + std::string segmentStyle; + settings.Get("segmentStyle", &segmentStyle); + if (segmentStyle == "automatic") + control.segmentStyle = NSSegmentStyleAutomatic; + else if (segmentStyle == "rounded") + control.segmentStyle = NSSegmentStyleRounded; + else if (segmentStyle == "textured-rounded") + control.segmentStyle = NSSegmentStyleTexturedRounded; + else if (segmentStyle == "round-rect") + control.segmentStyle = NSSegmentStyleRoundRect; + else if (segmentStyle == "textured-square") + control.segmentStyle = NSSegmentStyleTexturedSquare; + else if (segmentStyle == "capsule") + control.segmentStyle = NSSegmentStyleCapsule; + else if (segmentStyle == "small-square") + control.segmentStyle = NSSegmentStyleSmallSquare; + else if (segmentStyle == "separated") + control.segmentStyle = NSSegmentStyleSeparated; + else + control.segmentStyle = NSSegmentStyleAutomatic; + + std::vector segments; + settings.Get("segments", &segments); + + control.segmentCount = segments.size(); + for (int i = 0; i < (int)segments.size(); i++) { + std::string label; + gfx::Image image; + bool enabled = true; + segments[i].Get("enabled", &enabled); + if (segments[i].Get("label", &label)) { + [control setLabel:base::SysUTF8ToNSString(label) forSegment:i]; + } else if (segments[i].Get("icon", &image)) { + [control setImage:image.AsNSImage() forSegment:i]; + [control setImageScaling:NSImageScaleProportionallyUpOrDown forSegment:i]; + } + [control setEnabled:enabled forSegment:i]; + } + + int selectedIndex = 0; + settings.Get("selectedIndex", &selectedIndex); + control.selectedSegment = selectedIndex; +} + @end diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index ee7388f177..a6e2e593cc 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -212,4 +212,23 @@ TouchBar.TouchBarSpacer = class TouchBarSpacer extends TouchBarItem { } } +TouchBar.TouchBarSegmentedControl = class TouchBarSegmentedControl extends TouchBarItem { + constructor (config) { + super() + if (config == null) config = {} + const {segmentStyle, segments, selectedIndex, change} = config + this.type = 'segmented_control' + this._addLiveProperty('segmentStyle', segmentStyle); + this._addLiveProperty('segments', segments || []); + this._addLiveProperty('selectedIndex', selectedIndex) + + if (typeof change === 'function') { + this.onInteraction = (details) => { + this._selectedIndex = details.selectedIndex; + change(details.selectedIndex); + } + } + } +} + module.exports = TouchBar From e6a66b6006103817ff4275df3b793344bb3b5d87 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Fri, 10 Mar 2017 17:50:23 +1100 Subject: [PATCH 400/925] Add docs for segmented control touch bar item --- .../structures/segmented-control-segment.md | 5 +++ docs/api/touch-bar-segmented-control.md | 41 +++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 docs/api/structures/segmented-control-segment.md create mode 100644 docs/api/touch-bar-segmented-control.md diff --git a/docs/api/structures/segmented-control-segment.md b/docs/api/structures/segmented-control-segment.md new file mode 100644 index 0000000000..ae01a07f32 --- /dev/null +++ b/docs/api/structures/segmented-control-segment.md @@ -0,0 +1,5 @@ +# SegmentedControlSegment Object + +* `label` String - (Optional) The text to appear in this segment +* `icon` NativeImage - (Optional) The image to appear in this segment +* `enabled` Boolean - (Optional) Whether this segment is selectable. Default: true diff --git a/docs/api/touch-bar-segmented-control.md b/docs/api/touch-bar-segmented-control.md new file mode 100644 index 0000000000..8f30f77169 --- /dev/null +++ b/docs/api/touch-bar-segmented-control.md @@ -0,0 +1,41 @@ +## Class: TouchBarSegmentedControl + +> Create a segmented control (a button group) where one button has a selected state + +Process: [Main](../tutorial/quick-start.md#main-process) + +### `new TouchBarSegmentedControl(options)` + +* `options` Object + * `segmentStyle` String - (Optional) Style of the segments: + * `automatic` - Default + * `rounded` + * `textured-rounded` + * `round-rect` + * `textured-square` + * `capsule` + * `small-square` + * `separated` + * `segments` [SegmentedControlSegment[]](structures/segmented-control-segment.md) - An array of segments to place in this control + * `selectedIndex` Integer (Optional) - The index of the currently selected segment, will update automatically with user interaction + * `change` Function - Called when the user selects a new segment + * `selectedIndex` - The index of the segment the user selected + +### Instance Properties + +The following properties are available on instances of `TouchBarSegmentedControl`: + +#### `touchBarSegmentedControl.segmentStyle` + +A `String` representing the controls current segment style. Updating this value immediately updates the control +in the touch bar. + +#### `touchBarSegmentedControl.segments` + +A `SegmentedControlSegment[]` array representing the segments in this control. Updating this value immediately +updates the control in the touch bar. Updating deep properties inside this array **does not update the touch bar**. + +#### `touchBarSegmentedControl.selectedIndex` + +An `Integer` representing the currently selected segment. Changing this value immediately updates the control +in the touch bar. User interaction with the touch bar will update this value automatically. From bfe63d7a884ea573c4a979a60cdc857f1a2c1f47 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Fri, 10 Mar 2017 17:56:26 +1100 Subject: [PATCH 401/925] Fix linting --- lib/browser/api/touch-bar.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index a6e2e593cc..0d428cb0f0 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -218,14 +218,14 @@ TouchBar.TouchBarSegmentedControl = class TouchBarSegmentedControl extends Touch if (config == null) config = {} const {segmentStyle, segments, selectedIndex, change} = config this.type = 'segmented_control' - this._addLiveProperty('segmentStyle', segmentStyle); - this._addLiveProperty('segments', segments || []); + this._addLiveProperty('segmentStyle', segmentStyle) + this._addLiveProperty('segments', segments || []) this._addLiveProperty('selectedIndex', selectedIndex) if (typeof change === 'function') { this.onInteraction = (details) => { - this._selectedIndex = details.selectedIndex; - change(details.selectedIndex); + this._selectedIndex = details.selectedIndex + change(details.selectedIndex) } } } From 7fefb75de5dd6cb4e4c8501d02e419d8fd5b206a Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Fri, 10 Mar 2017 08:08:23 -0800 Subject: [PATCH 402/925] update versioning doc --- docs/tutorial/electron-versioning.md | 60 +++++++++++++++++++++------- 1 file changed, 45 insertions(+), 15 deletions(-) diff --git a/docs/tutorial/electron-versioning.md b/docs/tutorial/electron-versioning.md index cae99344a6..b1812d84b4 100644 --- a/docs/tutorial/electron-versioning.md +++ b/docs/tutorial/electron-versioning.md @@ -1,21 +1,51 @@ # Electron Versioning -If you are a seasoned Node developer, you are surely aware of `semver` - and -might be used to giving your dependency management systems only rough guidelines -rather than fixed version numbers. Due to the hard dependency on Node and -Chromium, Electron is in a slightly more difficult position and does not follow -semver. You should therefore always reference a specific version of Electron. +If you've been using Node and npm for a while, you are probably aware of [Semantic Versioning], or SemVer for short. It's a convention for specifying version numbers for software that helps communicate intentions to the users of your software. -Version numbers are bumped using the following rules: +## Overview of Semantic Versioning -* Major: For breaking changes in Electron's API - if you upgrade from `0.37.0` - to `1.0.0`, you will have to update your app. -* Minor: For major Chrome and minor Node upgrades; or significant Electron - changes - if you upgrade from `1.0.0` to `1.1.0`, your app is supposed to +Semantic versions are always made up of three numbers: + +``` +major.minor.patch +``` + +Semantic version numbers are bumped (incremented) using the following rules: + +* **Major** is for changes that break backwards compatibility. +* **Minor** is for new features that don't break backwards compatibility. +* **Patch** is for bug fixes and other minor changes. + +A simple mnemonic for remembering this scheme is as follows: + +``` +breaking.feature.fix +``` + +## Electron Versioning + +Due to its dependency on Node and Chromium, it is not possible for the Electron +project to adhere to a SemVer policy. **You should therefore always +reference a specific version of Electron.** + +Electron version numbers are bumped using the following rules: + +* **Major** is for breaking changes in Electron's API. If you upgrade from `0.37.0` + to `1.0.0`, you will have to make changes to your app. +* **Minor** is for major Chrome and minor Node upgrades, or significant Electron + changes. If you upgrade from `1.5.0` to `1.6.0`, your app is supposed to still work, but you might have to work around small changes. -* Patch: For new features and bug fixes - if you upgrade from `1.0.0` to - `1.0.1`, your app will continue to work as-is. +* **Patch** is for new features and bug fixes. If you upgrade from `1.6.2` to + `1.6.3`, your app will continue to work as-is. -If you are using `electron` or `electron-prebuilt`, we recommend that you set a fixed version -number (`1.1.0` instead of `^1.1.0`) to ensure that all upgrades of Electron are -a manual operation made by you, the developer. +We recommend that you set a fixed version when installing Electron from npm: + +```sh +npm install electron --save-exact --save-dev +``` + +The `--save-exact` flag will add `electron` to your `package.json` file without +using a `^` or `~`, e.g. `1.6.2` instead of `^1.6.2`. This practice ensures that +all upgrades of Electron are a manual operation made by you, the developer. + +[Semantic Versioning](http://semver.org) From 582662e40fc96801e5d2be9dd4d9bc8b9981de78 Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Fri, 10 Mar 2017 09:03:04 -0800 Subject: [PATCH 403/925] fix markdown link --- docs/tutorial/electron-versioning.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/electron-versioning.md b/docs/tutorial/electron-versioning.md index b1812d84b4..eccd9459f8 100644 --- a/docs/tutorial/electron-versioning.md +++ b/docs/tutorial/electron-versioning.md @@ -48,4 +48,4 @@ The `--save-exact` flag will add `electron` to your `package.json` file without using a `^` or `~`, e.g. `1.6.2` instead of `^1.6.2`. This practice ensures that all upgrades of Electron are a manual operation made by you, the developer. -[Semantic Versioning](http://semver.org) +[Semantic Versioning]: http://semver.org From 458e4be77c9be1d51b00f75444785d90414924d9 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 10 Mar 2017 09:53:47 -0800 Subject: [PATCH 404/925] Add forward declaration for 10.12 API --- atom/browser/ui/cocoa/touch_bar_forward_declarations.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/atom/browser/ui/cocoa/touch_bar_forward_declarations.h b/atom/browser/ui/cocoa/touch_bar_forward_declarations.h index 68693cb593..8c7b85e3a9 100644 --- a/atom/browser/ui/cocoa/touch_bar_forward_declarations.h +++ b/atom/browser/ui/cocoa/touch_bar_forward_declarations.h @@ -144,6 +144,15 @@ static const NSTouchBarItemIdentifier NSTouchBarItemIdentifierOtherItemsProxy = @end +@interface NSSegmentedControl (TouchBarSDK) + ++ (instancetype)segmentedControlWithLabels:(NSArray*)labels + trackingMode:(NSSegmentSwitchTracking)trackingMode + target:(id)target + action:(SEL)action; + +@end + @protocol NSTouchBarDelegate @optional From 3b2faf7b8941f4bf011c28780c5c4658cde150a3 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 10 Mar 2017 10:04:22 -0800 Subject: [PATCH 405/925] Add initial TouchBarSegmentedControl spec --- spec/api-touch-bar-spec.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/spec/api-touch-bar-spec.js b/spec/api-touch-bar-spec.js index cdca2d47c4..fa2c2a8eec 100644 --- a/spec/api-touch-bar-spec.js +++ b/spec/api-touch-bar-spec.js @@ -3,7 +3,7 @@ const {BrowserWindow, TouchBar} = require('electron').remote const {closeWindow} = require('./window-helpers') const {TouchBarButton, TouchBarColorPicker, TouchBarGroup} = TouchBar -const {TouchBarLabel, TouchBarPopover, TouchBarSlider, TouchBarSpacer} = TouchBar +const {TouchBarLabel, TouchBarPopover, TouchBarSegmentedControl, TouchBarSlider, TouchBarSpacer} = TouchBar describe('TouchBar module', function () { it('throws an error when created without an items array', function () { @@ -41,7 +41,12 @@ describe('TouchBar module', function () { label, new TouchBarPopover({items: new TouchBar([new TouchBarButton({label: 'pop'})])}), new TouchBarSlider({label: 'slide', value: 5, minValue: 2, maxValue: 75, change: () => {}}), - new TouchBarSpacer({size: 'large'}) + new TouchBarSpacer({size: 'large'}), + new TouchBarSegmentedControl({ + segmentStyle: 'capsule', + segments: [{label: 'baz', enabled: false}], + selectedIndex: 0 + }) ]) window.setTouchBar(touchBar) label.label = 'baz' From 3349e32196cabc3fb1d2371cab4d8872fbfbdb28 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 10 Mar 2017 10:09:14 -0800 Subject: [PATCH 406/925] Verify selectedIndex to prevent NSRangeException --- atom/browser/ui/cocoa/atom_touch_bar.mm | 3 ++- spec/api-touch-bar-spec.js | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index 37dc6e7f18..d6f7c1c1d7 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -465,7 +465,8 @@ static NSTouchBarItemIdentifier SegmentedControlIdentifier = @"com.electron.touc int selectedIndex = 0; settings.Get("selectedIndex", &selectedIndex); - control.selectedSegment = selectedIndex; + if (selectedIndex >= 0 && selectedIndex < control.segmentCount) + control.selectedSegment = selectedIndex; } @end diff --git a/spec/api-touch-bar-spec.js b/spec/api-touch-bar-spec.js index fa2c2a8eec..a09e0314c4 100644 --- a/spec/api-touch-bar-spec.js +++ b/spec/api-touch-bar-spec.js @@ -45,8 +45,9 @@ describe('TouchBar module', function () { new TouchBarSegmentedControl({ segmentStyle: 'capsule', segments: [{label: 'baz', enabled: false}], - selectedIndex: 0 - }) + selectedIndex: 5 + }), + new TouchBarSegmentedControl({segments: []}) ]) window.setTouchBar(touchBar) label.label = 'baz' From 5b1d5b39a2cce41562ade569462b2d5ec0a5464b Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 10 Mar 2017 10:11:10 -0800 Subject: [PATCH 407/925] Set automatic via else block --- atom/browser/ui/cocoa/atom_touch_bar.mm | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index d6f7c1c1d7..33a6d8c343 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -400,7 +400,7 @@ static NSTouchBarItemIdentifier SegmentedControlIdentifier = @"com.electron.touc } - (NSTouchBarItem*)makeSegmentedControlForID:(NSString*)id - withIdentifier:(NSString*)identifier { + withIdentifier:(NSString*)identifier { std::string s_id([id UTF8String]); if (![self hasItemWithID:s_id]) return nil; @@ -420,15 +420,13 @@ static NSTouchBarItemIdentifier SegmentedControlIdentifier = @"com.electron.touc } - (void)updateSegmentedControl:(NSCustomTouchBarItem*)item - withSettings:(const mate::PersistentDictionary&)settings { + withSettings:(const mate::PersistentDictionary&)settings { NSSegmentedControl* control = item.view; std::string segmentStyle; settings.Get("segmentStyle", &segmentStyle); - if (segmentStyle == "automatic") - control.segmentStyle = NSSegmentStyleAutomatic; - else if (segmentStyle == "rounded") + if (segmentStyle == "rounded") control.segmentStyle = NSSegmentStyleRounded; else if (segmentStyle == "textured-rounded") control.segmentStyle = NSSegmentStyleTexturedRounded; From da023b72ee3c942cac09be3377d60c6c0a676ec2 Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Mon, 27 Feb 2017 15:13:41 -0300 Subject: [PATCH 408/925] Dynamically generate dependencies of browserify build actions Instead of having to list in filenames.gypi every javascript file that may go into a browserify build, generate this list dynamically when the build files are created by gyp. --- electron.gyp | 20 ++++++++++++++------ filenames.gypi | 8 -------- lib/sandboxed_renderer/init.js | 3 --- tools/list-browserify-deps.py | 19 +++++++++++++++++++ 4 files changed, 33 insertions(+), 17 deletions(-) create mode 100755 tools/list-browserify-deps.py diff --git a/electron.gyp b/electron.gyp index 47bc67b98e..6b3fe01d09 100644 --- a/electron.gyp +++ b/electron.gyp @@ -436,11 +436,21 @@ # depend on this target to ensure the '<(js2c_input_dir)' is created 'atom_js2c_copy', ], + 'variables': { + 'sandbox_args': [ + './lib/sandboxed_renderer/init.js', + '-r', + './lib/sandboxed_renderer/api/exports/electron.js:electron' + ], + 'isolated_args': [ + 'lib/isolated_renderer/init.js', + ] + }, 'actions': [ { 'action_name': 'atom_browserify_sandbox', 'inputs': [ - '<@(browserify_entries)', + ' Date: Fri, 10 Mar 2017 22:24:00 -0300 Subject: [PATCH 409/925] Add translated CODE_OF_CONDUCT.md and README.md for spanish language. --- README.md | 2 +- .../es/project/CODE_OF_CONDUCT.md | 46 ++++++++++ docs-translations/es/project/README.md | 87 +++++++++++++++++++ 3 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 docs-translations/es/project/CODE_OF_CONDUCT.md create mode 100644 docs-translations/es/project/README.md diff --git a/README.md b/README.md index 655d613baf..3a31689291 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![devDependency Status](https://david-dm.org/electron/electron/dev-status.svg)](https://david-dm.org/electron/electron?type=dev) [![Join the Electron Community on Slack](http://atom-slack.herokuapp.com/badge.svg)](http://atom-slack.herokuapp.com/) -:memo: Available Translations: [Korean](https://github.com/electron/electron/tree/master/docs-translations/ko-KR/project/README.md) | [Simplified Chinese](https://github.com/electron/electron/tree/master/docs-translations/zh-CN/project/README.md) | [Brazilian Portuguese](https://github.com/electron/electron/tree/master/docs-translations/pt-BR/project/README.md) | [Traditional Chinese](https://github.com/electron/electron/tree/master/docs-translations/zh-TW/project/README.md) +:memo: Available Translations: [Korean](https://github.com/electron/electron/tree/master/docs-translations/ko-KR/project/README.md) | [Simplified Chinese](https://github.com/electron/electron/tree/master/docs-translations/zh-CN/project/README.md) | [Brazilian Portuguese](https://github.com/electron/electron/tree/master/docs-translations/pt-BR/project/README.md) | [Traditional Chinese](https://github.com/electron/electron/tree/master/docs-translations/zh-TW/project/README.md) | [Spanish](https://github.com/electron/electron/tree/master/docs-translations/es/project/README.md) The Electron framework lets you write cross-platform desktop applications using JavaScript, HTML and CSS. It is based on [Node.js](https://nodejs.org/) and diff --git a/docs-translations/es/project/CODE_OF_CONDUCT.md b/docs-translations/es/project/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000..8c8aafbc1e --- /dev/null +++ b/docs-translations/es/project/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Código de Conducta convenido para Contribuyentes + +## Nuestro compromiso + +En el interés de fomentar una comunidad abierta y acogedora, nosotros como contribuyentes y administradores nos comprometemos a hacer de la participación en nuestro proyecto y nuestra comunidad una experiencia libre de acoso para todos, independientemente de la edad, dimensión corporal, discapacidad, etnia, identidad y expresión de género, nivel de experiencia, nacionalidad, apariencia física, raza, religión, identidad u orientación sexual. + +## Nuestros estándares + +Ejemplos de comportamiento que contribuyen a crear un ambiente positivo: + +* Uso de lenguaje amable e inclusivo +* Respeto a diferentes puntos de vista y experiencias +* Aceptación de críticas constructivas +* Enfocarse en lo que es mejor para la comunidad +* Mostrar empatía a otros miembros de la comunidad + +Ejemplos de comportamiento inaceptable por participantes: + +* Uso de lenguaje o imágenes sexuales y atención sexual no deseada +* Comentarios insultantes o despectivos (*trolling*) y ataques personales o políticos +* Acoso público o privado +* Publicación de información privada de terceros sin su consentimiento, como direcciones físicas o electrónicas +* Otros tipos de conducta que pudieran considerarse inapropiadas en un entorno profesional. + +## Nuestras responsabilidades + +Los administradores del proyecto son responsables de clarificar los estándares de comportamiento aceptable y se espera que tomen medidas correctivas y apropiadas en respuesta a situaciones de conducta inaceptable. + +Los administradores del proyecto tienen el derecho y la responsabilidad de eliminar, editar o rechazar comentarios, *commits*, código, ediciones de documentación, *issues*, y otras contribuciones que no estén alineadas con este Código de Conducta, o de prohibir temporal o permanentemente a cualquier colaborador cuyo comportamiento sea inapropiado, amenazante, ofensivo o perjudicial. + +## Alcance + +Este código de conducta aplica tanto a espacios del proyecto como a espacios públicos donde un individuo esté en representación del proyecto o comunidad. Ejemplos de esto incluye el uso de la cuenta oficial de correo electrónico, publicaciones a través de las redes sociales oficiales, o presentaciones con personas designadas en eventos *online* u *offline*. La representación del proyecto puede ser clarificada explicitamente por los administradores del proyecto. + +## Aplicación + +Ejemplos de abuso, acoso u otro tipo de comportamiento inaceptable puede ser reportado al equipo del proyecto en [INSERTE CORREO AQUÍ]. Todas las quejas serán revisadas e investigadas, generando un resultado apropiado a las circunstancias. El equipo del proyecto está obligado a mantener confidencialidad de la persona que reportó el incidente. Detalles específicos acerca de las políticas de aplicación pueden ser publicadas por separado. + +Administradores que no sigan o que no hagan cumplir este Código de Conducta pueden ser eliminados de forma temporal o permanente del equipo administrador. + +## Atribución + +Este Código de Conducta es una adaptación del [Contributor Covenant][homepage], versión 1.4, disponible en [http://contributor-covenant.org/version/1/4/es/][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/es/ diff --git a/docs-translations/es/project/README.md b/docs-translations/es/project/README.md new file mode 100644 index 0000000000..c62c5c7dc6 --- /dev/null +++ b/docs-translations/es/project/README.md @@ -0,0 +1,87 @@ +[![Electron Logo](https://electron.atom.io/images/electron-logo.svg)](https://electron.atom.io/) + +[![Travis Build Status](https://travis-ci.org/electron/electron.svg?branch=master)](https://travis-ci.org/electron/electron) +[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/kvxe4byi7jcxbe26/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/electron) +[![devDependency Status](https://david-dm.org/electron/electron/dev-status.svg)](https://david-dm.org/electron/electron?type=dev) +[![Join the Electron Community on Slack](http://atom-slack.herokuapp.com/badge.svg)](http://atom-slack.herokuapp.com/) + +:memo: Traducciones disponibles: [Koreano](https://github.com/electron/electron/tree/master/docs-translations/ko-KR/project/README.md) | [Chino Simplificado](https://github.com/electron/electron/tree/master/docs-translations/zh-CN/project/README.md) | [Portugues Brasileño](https://github.com/electron/electron/tree/master/docs-translations/pt-BR/project/README.md) | [Chino Tradicional](https://github.com/electron/electron/tree/master/docs-translations/zh-TW/project/README.md) + +Electron es framework que permite escribir aplicaciones de escritorio multiplataforma +usando JavaScript, HTML y CSS. Está basado en [Node.js](https://nodejs.org/) con +[Chromium](http://www.chromium.org). Es usado por [Atom +editor](https://github.com/atom/atom) y muchas otras [aplicaciones](https://electron.atom.io/apps). + +Sigue a [@ElectronJS](https://twitter.com/electronjs) en Twitter para estar informado de anuncios +importantes. + +Este projecto se adhiere a [Código de Conducta convenido para Contribuyentes](CODE_OF_CONDUCT.md). +Si desea participar, debes seguir este código de conducta. Por favor reporta un comportamiento +no aceptado a electron@github.com. + +## Downloads + +Para instalar binarios precompilados, usa +[`npm`](https://docs.npmjs.com/): + +```sh +# Instalación de las dependencias de desarrollo +npm install electron --save-dev + +# Instalación de `electron` de manera global a tu $PATH +npm install electron -g +``` + +Mira la [página de lanzamientos](https://github.com/electron/electron/releases) para +los prebuilt binaries, debug symbols, and more. + +### Mirrors + +- [China](https://npm.taobao.org/mirrors/electron) + +## Documentación + +Las guias y API de referencia están disponibles en el directorio +[docs](https://github.com/electron/electron/tree/master/docs). Ahí también +puedes encontrar documentos que describen como construir y contribuir a Electron. + +## Traducciones de la Documentación + +- [Portugues Brasileño](https://github.com/electron/electron/tree/master/docs-translations/pt-BR) +- [Koreano](https://github.com/electron/electron/tree/master/docs-translations/ko-KR) +- [Japonés](https://github.com/electron/electron/tree/master/docs-translations/jp) +- [Español](https://github.com/electron/electron/tree/master/docs-translations/es) +- [Chino Simplificado](https://github.com/electron/electron/tree/master/docs-translations/zh-CN) +- [Chino Tradicional](https://github.com/electron/electron/tree/master/docs-translations/zh-TW) +- [Turco](https://github.com/electron/electron/tree/master/docs-translations/tr-TR) +- [Thai](https://github.com/electron/electron/tree/master/docs-Translations/th-TH) +- [Ucraniano](https://github.com/electron/electron/tree/master/docs-translations/uk-UA) +- [Ruso](https://github.com/electron/electron/tree/master/docs-translations/ru-RU) +- [Frances](https://github.com/electron/electron/tree/master/docs-translations/fr-FR) + +## Inicio rápido + +Clona y ejecuta el repositorio [`electron/electron-quick-start`](https://github.com/electron/electron-quick-start) +para ver una aplicación minima en acción. + +## Comunidad + +Puedes preguntar y interactuar con la comunidad en los siguientes lugares: +- [`electron`](http://discuss.atom.io/c/electron) Categoría en los Foros de +Atom. +- `#atom-shell` canal de IRC en Freenode +- [`Atom`](http://atom-slack.herokuapp.com/) canales en Slack +- [`electron-br`](https://electron-br.slack.com) *(Portugues Brasileño)* +- [`electron-kr`](http://www.meetup.com/electron-kr/) *(Koreano)* +- [`electron-jp`](https://electron-jp.slack.com) *(Japonés)* +- [`electron-tr`](http://www.meetup.com/Electron-JS-Istanbul/) *(Turco)* +- [`electron-id`](https://electron-id.slack.com) *(Indonés* + +Mira [awesome-electron](https://github.com/sindresorhus/awesome-electron) +donde la comunidad mantiene una lista útil de ejemplos de aplicaciones, herramientas y recursos. + +## Licencia + +[MIT](https://github.com/electron/electron/blob/master/LICENSE) + +Si usas los logos de Electron ó GitHub, asegurate de seguir las [GitHub logo guidelines](https://github.com/logos). From 4281c73cf28b979c3b286e248109092dbaceebd9 Mon Sep 17 00:00:00 2001 From: deepak1556 Date: Sun, 12 Mar 2017 00:53:39 +0530 Subject: [PATCH 410/925] renderer: reset plugin cache when render frame is created --- atom/renderer/atom_renderer_client.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index 078bcf743c..637792c2ae 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -271,6 +271,9 @@ void AtomRendererClient::RenderFrameCreated( // FIXME(zcbenz): Can this be moved elsewhere? blink::WebSecurityPolicy::registerURLSchemeAsAllowingServiceWorkers("file"); + // This is required for widevine plugin detection provided during runtime. + blink::resetPluginCache(); + // Parse --secure-schemes=scheme1,scheme2 std::vector secure_schemes_list = ParseSchemesCLISwitch(switches::kSecureSchemes); From 3b265747fef74682c26909570181a1673bddccfd Mon Sep 17 00:00:00 2001 From: Adham Saad Date: Sun, 12 Mar 2017 14:17:03 +0200 Subject: [PATCH 411/925] updating README --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 655d613baf..0822d70742 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +Attempting to add full kiosk mode functionality on windows 7 +== + [![Electron Logo](https://electron.atom.io/images/electron-logo.svg)](https://electron.atom.io/) [![Travis Build Status](https://travis-ci.org/electron/electron.svg?branch=master)](https://travis-ci.org/electron/electron) From 25a231fc50b43ae6b338cb5cc9969bf3b35ecaa6 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Mon, 13 Mar 2017 10:51:12 +1100 Subject: [PATCH 412/925] Initial NSScrubber implementation --- .../ui/cocoa/atom_scrubber_data_source.h | 25 ++++++ .../ui/cocoa/atom_scrubber_data_source.mm | 55 ++++++++++++++ atom/browser/ui/cocoa/atom_touch_bar.h | 2 +- atom/browser/ui/cocoa/atom_touch_bar.mm | 76 ++++++++++++++++++- filenames.gypi | 2 + lib/browser/api/touch-bar.js | 20 +++++ 6 files changed, 178 insertions(+), 2 deletions(-) create mode 100644 atom/browser/ui/cocoa/atom_scrubber_data_source.h create mode 100644 atom/browser/ui/cocoa/atom_scrubber_data_source.mm diff --git a/atom/browser/ui/cocoa/atom_scrubber_data_source.h b/atom/browser/ui/cocoa/atom_scrubber_data_source.h new file mode 100644 index 0000000000..c2d6299930 --- /dev/null +++ b/atom/browser/ui/cocoa/atom_scrubber_data_source.h @@ -0,0 +1,25 @@ +// Copyright (c) 2017 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_UI_COCOA_ATOM_SCRUBBER_DATA_SOURCE_H_ +#define ATOM_BROWSER_UI_COCOA_ATOM_SCRUBBER_DATA_SOURCE_H_ + +#import + +#include +#include + +#include "native_mate/persistent_dictionary.h" + +@interface AtomScrubberDataSource : NSObject { + @protected + std::vector items_; +} + +- (id)initWithItems:(std::vector)items; +- (void)setItems:(std::vector)items; + +@end + +#endif // ATOM_BROWSER_UI_COCOA_ATOM_SCRUBBER_DATA_SOURCE_H_ diff --git a/atom/browser/ui/cocoa/atom_scrubber_data_source.mm b/atom/browser/ui/cocoa/atom_scrubber_data_source.mm new file mode 100644 index 0000000000..b320d7a2c3 --- /dev/null +++ b/atom/browser/ui/cocoa/atom_scrubber_data_source.mm @@ -0,0 +1,55 @@ +// Copyright (c) 2017 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#import "atom/browser/ui/cocoa/atom_scrubber_data_source.h" + +#include "atom/common/native_mate_converters/image_converter.h" +#include "base/strings/sys_string_conversions.h" +#include "ui/gfx/image/image.h" + +@implementation AtomScrubberDataSource + +static NSString* const TextItemIdentifier = @"scrubber.text.item"; +static NSString* const ImageItemIdentifier = @"scrubber.image.item"; + +- (id)initWithItems:(std::vector)items { + if ((self = [super init])) { + items_ = items; + } + return self; +} + +- (NSInteger)numberOfItemsForScrubber:(NSScrubber *)theScrubber { + return items_.size(); +} + +- (NSScrubberItemView *)scrubber:(NSScrubber *)scrubber viewForItemAtIndex:(NSInteger)index { + mate::PersistentDictionary item = items_[index]; + + NSScrubberItemView* itemView; + std::string title; + + if (item.Get("label", &title)) { + NSScrubberTextItemView* view = [scrubber makeItemWithIdentifier:TextItemIdentifier owner:self]; + view.title = base::SysUTF8ToNSString(title); + + itemView = view; + } else { + NSScrubberImageItemView* view = [scrubber makeItemWithIdentifier:ImageItemIdentifier owner:self]; + gfx::Image image; + if (item.Get("image", &image)) { + view.image = image.AsNSImage(); + } + + itemView = view; + } + + return itemView; +} + +- (void)setItems:(std::vector)items { + items_ = items; +} + +@end diff --git a/atom/browser/ui/cocoa/atom_touch_bar.h b/atom/browser/ui/cocoa/atom_touch_bar.h index d34a1f6d47..858799b939 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.h +++ b/atom/browser/ui/cocoa/atom_touch_bar.h @@ -17,7 +17,7 @@ #include "native_mate/constructor.h" #include "native_mate/persistent_dictionary.h" -@interface AtomTouchBar : NSObject { +@interface AtomTouchBar : NSObject { @protected std::vector ordered_settings_; std::map settings_; diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index 33a6d8c343..aaf1578918 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -4,6 +4,7 @@ #import "atom/browser/ui/cocoa/atom_touch_bar.h" +#include "atom/browser/ui/cocoa/atom_scrubber_data_source.h" #include "atom/common/color_util.h" #include "atom/common/native_mate_converters/image_converter.h" #include "base/strings/sys_string_conversions.h" @@ -19,6 +20,10 @@ static NSTouchBarItemIdentifier LabelIdentifier = @"com.electron.touchbar.label. static NSTouchBarItemIdentifier PopoverIdentifier = @"com.electron.touchbar.popover."; static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slider."; static NSTouchBarItemIdentifier SegmentedControlIdentifier = @"com.electron.touchbar.segmentedcontrol."; +static NSTouchBarItemIdentifier ScrubberIdentifier = @"com.electron.touchbar.scrubber."; + +static NSString* const TextScrubberItemIdentifier = @"scrubber.text.item"; +static NSString* const ImageScrubberItemIdentifier = @"scrubber.image.item"; - (id)initWithDelegate:(id)delegate window:(atom::NativeWindow*)window @@ -101,6 +106,9 @@ static NSTouchBarItemIdentifier SegmentedControlIdentifier = @"com.electron.touc } else if ([identifier hasPrefix:SegmentedControlIdentifier]) { item_id = [self idFromIdentifier:identifier withPrefix:SegmentedControlIdentifier]; return [self makeSegmentedControlForID:item_id withIdentifier:identifier]; + } else if ([identifier hasPrefix:ScrubberIdentifier]) { + item_id = [self idFromIdentifier:identifier withPrefix:ScrubberIdentifier]; + return [self makeScrubberForID:item_id withIdentifier:identifier]; } return nil; @@ -133,8 +141,12 @@ static NSTouchBarItemIdentifier SegmentedControlIdentifier = @"com.electron.touc [self updateSlider:(NSSliderTouchBarItem*)item withSettings:settings]; } else if (item_type == "popover") { [self updatePopover:(NSPopoverTouchBarItem*)item withSettings:settings]; - } else if (item_type == "segmented_control") + } else if (item_type == "segmented_control") { [self updateSegmentedControl:(NSCustomTouchBarItem*)item withSettings:settings]; + } else if (item_type == "scrubber") { + [self updateScrubber:(NSCustomTouchBarItem*)item withSettings:settings]; + } + } - (void)buttonAction:(id)sender { @@ -177,6 +189,20 @@ static NSTouchBarItemIdentifier SegmentedControlIdentifier = @"com.electron.touc details); } +- (void)scrubber:(NSScrubber *)scrubber didSelectItemAtIndex:(NSInteger)selectedIndex { + base::DictionaryValue details; + details.SetInteger("selectedIndex", (long)selectedIndex); + details.SetString("type", "select"); + window_->NotifyTouchBarItemInteraction([scrubber.identifier UTF8String], details); +} + +- (void)scrubber:(NSScrubber *)scrubber didHighlightItemAtIndex:(NSInteger)highlightedIndex { + base::DictionaryValue details; + details.SetInteger("highlightedIndex", (long)highlightedIndex); + details.SetString("type", "highlight"); + window_->NotifyTouchBarItemInteraction([scrubber.identifier UTF8String], details); +} + - (NSTouchBarItemIdentifier)identifierFromID:(const std::string&)item_id type:(const std::string&)type { NSTouchBarItemIdentifier base_identifier = nil; @@ -194,6 +220,8 @@ static NSTouchBarItemIdentifier SegmentedControlIdentifier = @"com.electron.touc base_identifier = GroupIdentifier; else if (type == "segmented_control") base_identifier = SegmentedControlIdentifier; + else if (type == "scrubber") + base_identifier = ScrubberIdentifier; if (base_identifier) return [NSString stringWithFormat:@"%@%s", base_identifier, item_id.data()]; @@ -467,4 +495,50 @@ static NSTouchBarItemIdentifier SegmentedControlIdentifier = @"com.electron.touc control.selectedSegment = selectedIndex; } +- (NSTouchBarItem*)makeScrubberForID:(NSString*)id + withIdentifier:(NSString*)identifier { + std::string s_id([id UTF8String]); + if (![self hasItemWithID:s_id]) return nil; + + mate::PersistentDictionary settings = settings_[s_id]; + base::scoped_nsobject item([[NSClassFromString( + @"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]); + + + int width = 320; + int height = 30; + settings.Get("frameWidth", &width); + settings.Get("frameHeight", &height); + NSScrubber* scrubber = [[NSScrubber alloc] initWithFrame:NSMakeRect(0, 0, width, height)]; + + [scrubber registerClass:[NSScrubberTextItemView class] forItemIdentifier:TextScrubberItemIdentifier]; + [scrubber registerClass:[NSScrubberImageItemView class] forItemIdentifier:ImageScrubberItemIdentifier]; + + scrubber.delegate = self; + scrubber.identifier = id; + std::vector items; + settings.Get("items", &items); + scrubber.dataSource = [[AtomScrubberDataSource alloc] initWithItems:items]; + scrubber.mode = NSScrubberModeFree; + + [item setView:scrubber]; + + [self updateScrubber:item withSettings:settings]; + return item.autorelease(); +} + +- (void)updateScrubber:(NSCustomTouchBarItem*)item + withSettings:(const mate::PersistentDictionary&)settings { + + NSScrubber* scrubber = item.view; + + std::vector items; + settings.Get("items", &items); + + AtomScrubberDataSource* source = scrubber.dataSource; + [source setItems:items]; + + [scrubber reloadData]; +} + @end diff --git a/filenames.gypi b/filenames.gypi index 44a6fbfc43..d59a08da2f 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -287,6 +287,8 @@ 'atom/browser/ui/cocoa/atom_touch_bar.h', 'atom/browser/ui/cocoa/atom_touch_bar.mm', 'atom/browser/ui/cocoa/touch_bar_forward_declarations.h', + 'atom/browser/ui/cocoa/atom_scrubber_data_source.h', + 'atom/browser/ui/cocoa/atom_scrubber_data_source.mm', 'atom/browser/ui/drag_util_mac.mm', 'atom/browser/ui/drag_util_views.cc', 'atom/browser/ui/drag_util.h', diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index 0d428cb0f0..60a82f2903 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -231,4 +231,24 @@ TouchBar.TouchBarSegmentedControl = class TouchBarSegmentedControl extends Touch } } +TouchBar.TouchBarScrubber = class TouchBarScrubber extends TouchBarItem { + constructor(config) { + super() + if (config == null) config = {} + const {items, onSelect, onHighlight} = config + this.type = 'scrubber' + this._addLiveProperty('items', items) + + if (typeof onSelect === 'function' || typeof onHighlight === 'function') { + this.onInteraction = (details) => { + if (details.type === 'select') { + onSelect(details.selectedIndex); + } else if (details.type === 'highlight') { + onHighlight(details.highlightedIndex); + } + } + } + } +} + module.exports = TouchBar From a85ae27d68688837734a65d0d470c420d8044d32 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Mon, 13 Mar 2017 11:00:10 +1100 Subject: [PATCH 413/925] Fix linting --- atom/browser/ui/cocoa/atom_scrubber_data_source.h | 2 +- lib/browser/api/touch-bar.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/atom/browser/ui/cocoa/atom_scrubber_data_source.h b/atom/browser/ui/cocoa/atom_scrubber_data_source.h index c2d6299930..5ffcd5015d 100644 --- a/atom/browser/ui/cocoa/atom_scrubber_data_source.h +++ b/atom/browser/ui/cocoa/atom_scrubber_data_source.h @@ -13,7 +13,7 @@ #include "native_mate/persistent_dictionary.h" @interface AtomScrubberDataSource : NSObject { - @protected + @protected std::vector items_; } diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index 60a82f2903..7f997edde8 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -232,7 +232,7 @@ TouchBar.TouchBarSegmentedControl = class TouchBarSegmentedControl extends Touch } TouchBar.TouchBarScrubber = class TouchBarScrubber extends TouchBarItem { - constructor(config) { + constructor (config) { super() if (config == null) config = {} const {items, onSelect, onHighlight} = config @@ -242,9 +242,9 @@ TouchBar.TouchBarScrubber = class TouchBarScrubber extends TouchBarItem { if (typeof onSelect === 'function' || typeof onHighlight === 'function') { this.onInteraction = (details) => { if (details.type === 'select') { - onSelect(details.selectedIndex); + onSelect(details.selectedIndex) } else if (details.type === 'highlight') { - onHighlight(details.highlightedIndex); + onHighlight(details.highlightedIndex) } } } From cfb3798703f262d9a3a94b9442aedfb8151eb3cd Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Mon, 13 Mar 2017 11:00:25 +1100 Subject: [PATCH 414/925] Add docs for TouchBarScrubbber --- docs/api/structures/scrubber-item.md | 4 ++++ docs/api/touch-bar-scrubber.md | 23 +++++++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 docs/api/structures/scrubber-item.md create mode 100644 docs/api/touch-bar-scrubber.md diff --git a/docs/api/structures/scrubber-item.md b/docs/api/structures/scrubber-item.md new file mode 100644 index 0000000000..b1711cec1c --- /dev/null +++ b/docs/api/structures/scrubber-item.md @@ -0,0 +1,4 @@ +# ScrubberItem Object + +* `label` String - (Optional) The text to appear in this item +* `image` NativeImage - (Optional) The image to appear in this item diff --git a/docs/api/touch-bar-scrubber.md b/docs/api/touch-bar-scrubber.md new file mode 100644 index 0000000000..3143e95305 --- /dev/null +++ b/docs/api/touch-bar-scrubber.md @@ -0,0 +1,23 @@ +## Class: TouchBarScrubber + +> Create a scrubber (a scrollablbe selector) + +Process: [Main](../tutorial/quick-start.md#main-process) + +### `new TouchBarScrubber(options)` + +* `options` Object + * `items` [ScrubberItem[]](structures/scrubber-item.md) - An array of items to place in this scruber + * `onSelect` Function - Called when the user taps an item that was not the last tapped item + * `selectedIndex` - The index of the item the user selected + * `onHightlight` Function - Called when the user taps any item + * `highlightedIndex` - The index of the item the user touched + +### Instance Properties + +The following properties are available on instances of `TouchBarScrubber`: + +#### `touchBarSegmentedControl.items` + +A `ScrubberItem[]` array representing the items in this scrubber. Updating this value immediately +updates the control in the touch bar. Updating deep properties inside this array **does not update the touch bar**. From 76ee7fda2b2ec83de0755555604c80442f9882b3 Mon Sep 17 00:00:00 2001 From: mst128256 Date: Mon, 13 Mar 2017 14:26:34 +0100 Subject: [PATCH 415/925] Fixed linting --- lib/browser/api/menu-item-roles.js | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/lib/browser/api/menu-item-roles.js b/lib/browser/api/menu-item-roles.js index 254283b86b..8919b8d10e 100644 --- a/lib/browser/api/menu-item-roles.js +++ b/lib/browser/api/menu-item-roles.js @@ -178,8 +178,7 @@ const roles = { role: 'paste' }, - process.platform === 'darwin' ? - { + process.platform === 'darwin' ? { role: 'pasteandmatchstyle' } : {}, @@ -187,9 +186,8 @@ const roles = { role: 'delete' }, - process.platform === 'win32' ? - { - type: 'separator' + process.platform === 'win32' ? { + type: 'separator' } : {}, { @@ -209,13 +207,11 @@ const roles = { role: 'close' }, - process.platform === 'darwin' ? - { + process.platform === 'darwin' ? { type: 'separator' } : {}, - process.platform === 'darwin' ? - { + process.platform === 'darwin' ? { label: 'Bring All to Front', role: 'front' } : {} @@ -245,13 +241,14 @@ exports.getDefaultAccelerator = (role) => { exports.getDefaultSubmenu = (role) => { if (roles.hasOwnProperty(role)) { - submenu = roles[role].submenu + let submenu = roles[role].submenu // remove empty objects from within the submenu - if (Array.isArray(submenu)) - submenu = submenu.filter(function(n){ + if (Array.isArray(submenu)) { + submenu = submenu.filter(function (n) { return n.constructor !== Object || Object.keys(n).length > 0 }) + } return submenu } From 634bd7b17faceef7775d7c68390b7ff8f224d51e Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 13 Mar 2017 10:07:48 -0700 Subject: [PATCH 416/925] Add NSScrubber forward declarations --- .../ui/cocoa/atom_scrubber_data_source.h | 1 + atom/browser/ui/cocoa/atom_touch_bar.mm | 12 ++--- .../ui/cocoa/touch_bar_forward_declarations.h | 51 ++++++++++++++++++- 3 files changed, 57 insertions(+), 7 deletions(-) diff --git a/atom/browser/ui/cocoa/atom_scrubber_data_source.h b/atom/browser/ui/cocoa/atom_scrubber_data_source.h index 5ffcd5015d..5850e30769 100644 --- a/atom/browser/ui/cocoa/atom_scrubber_data_source.h +++ b/atom/browser/ui/cocoa/atom_scrubber_data_source.h @@ -10,6 +10,7 @@ #include #include +#include "atom/browser/ui/cocoa/touch_bar_forward_declarations.h" #include "native_mate/persistent_dictionary.h" @interface AtomScrubberDataSource : NSObject { diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index aaf1578918..093f3d787e 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -146,7 +146,7 @@ static NSString* const ImageScrubberItemIdentifier = @"scrubber.image.item"; } else if (item_type == "scrubber") { [self updateScrubber:(NSCustomTouchBarItem*)item withSettings:settings]; } - + } - (void)buttonAction:(id)sender { @@ -504,15 +504,15 @@ static NSString* const ImageScrubberItemIdentifier = @"scrubber.image.item"; base::scoped_nsobject item([[NSClassFromString( @"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]); - + int width = 320; int height = 30; settings.Get("frameWidth", &width); settings.Get("frameHeight", &height); - NSScrubber* scrubber = [[NSScrubber alloc] initWithFrame:NSMakeRect(0, 0, width, height)]; + NSScrubber* scrubber = [[NSClassFromString(@"NSScrubber") alloc] initWithFrame:NSMakeRect(0, 0, width, height)]; - [scrubber registerClass:[NSScrubberTextItemView class] forItemIdentifier:TextScrubberItemIdentifier]; - [scrubber registerClass:[NSScrubberImageItemView class] forItemIdentifier:ImageScrubberItemIdentifier]; + [scrubber registerClass:NSClassFromString(@"NSScrubberTextItemView") forItemIdentifier:TextScrubberItemIdentifier]; + [scrubber registerClass:NSClassFromString(@"NSScrubberImageItemView") forItemIdentifier:ImageScrubberItemIdentifier]; scrubber.delegate = self; scrubber.identifier = id; @@ -520,7 +520,7 @@ static NSString* const ImageScrubberItemIdentifier = @"scrubber.image.item"; settings.Get("items", &items); scrubber.dataSource = [[AtomScrubberDataSource alloc] initWithItems:items]; scrubber.mode = NSScrubberModeFree; - + [item setView:scrubber]; [self updateScrubber:item withSettings:settings]; diff --git a/atom/browser/ui/cocoa/touch_bar_forward_declarations.h b/atom/browser/ui/cocoa/touch_bar_forward_declarations.h index 8c7b85e3a9..898bdee25e 100644 --- a/atom/browser/ui/cocoa/touch_bar_forward_declarations.h +++ b/atom/browser/ui/cocoa/touch_bar_forward_declarations.h @@ -14,13 +14,19 @@ #pragma clang assume_nonnull begin @class NSTouchBar, NSTouchBarItem; -@protocol NSTouchBarDelegate; +@class NSScrubber, NSScrubberItemView, NSScrubberArrangedView, NSScrubberTextItemView, NSScrubberImageItemView; +@protocol NSTouchBarDelegate, NSScrubberDelegate, NSScrubberDataSource; typedef float NSTouchBarItemPriority; static const NSTouchBarItemPriority NSTouchBarItemPriorityHigh = 1000; static const NSTouchBarItemPriority NSTouchBarItemPriorityNormal = 0; static const NSTouchBarItemPriority NSTouchBarItemPriorityLow = -1000; +enum NSScrubberMode { + NSScrubberModeFixed = 0, + NSScrubberModeFree +}; + typedef NSString* NSTouchBarItemIdentifier; typedef NSString* NSTouchBarCustomizationIdentifier; @@ -123,6 +129,41 @@ static const NSTouchBarItemIdentifier NSTouchBarItemIdentifierOtherItemsProxy = @end +@interface NSScrubber : NSView + +@property(weak) id delegate; +@property(weak) id dataSource; +@property NSScrubberMode mode; + +- (void)registerClass:(Class)itemViewClass + forItemIdentifier:(NSString*)itemIdentifier; + +- (__kindof NSScrubberItemView*)makeItemWithIdentifier:(NSString*)itemIdentifier + owner:(id)owner; +- (void)reloadData; + +@end + +@interface NSScrubberArrangedView : NSView + +@end + +@interface NSScrubberItemView : NSScrubberArrangedView + +@end + +@interface NSScrubberTextItemView : NSScrubberItemView + +@property(copy) NSString* title; + +@end + +@interface NSScrubberImageItemView : NSScrubberItemView + +@property(copy) NSImage* image; + +@end + @interface NSWindow (TouchBarSDK) @property(strong, readwrite, nullable) NSTouchBar* touchBar; @@ -160,6 +201,14 @@ static const NSTouchBarItemIdentifier NSTouchBarItemIdentifierOtherItemsProxy = makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier; @end +@protocol NSScrubberDelegate + +@end + +@protocol NSScrubberDataSource + +@end + #pragma clang assume_nonnull end #elif MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_12_1 From dac6e0ce66ead1fbf82568f6f718553a777154ce Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 13 Mar 2017 10:09:09 -0700 Subject: [PATCH 417/925] Sort filenames --- filenames.gypi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/filenames.gypi b/filenames.gypi index d59a08da2f..f56d50062a 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -284,11 +284,11 @@ 'atom/browser/ui/atom_menu_model.h', 'atom/browser/ui/cocoa/atom_menu_controller.h', 'atom/browser/ui/cocoa/atom_menu_controller.mm', + 'atom/browser/ui/cocoa/atom_scrubber_data_source.h', + 'atom/browser/ui/cocoa/atom_scrubber_data_source.mm', 'atom/browser/ui/cocoa/atom_touch_bar.h', 'atom/browser/ui/cocoa/atom_touch_bar.mm', 'atom/browser/ui/cocoa/touch_bar_forward_declarations.h', - 'atom/browser/ui/cocoa/atom_scrubber_data_source.h', - 'atom/browser/ui/cocoa/atom_scrubber_data_source.mm', 'atom/browser/ui/drag_util_mac.mm', 'atom/browser/ui/drag_util_views.cc', 'atom/browser/ui/drag_util.h', From 37c9d76cd5263ea4cdc6d2e553f59b4b689f979b Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 13 Mar 2017 10:25:30 -0700 Subject: [PATCH 418/925] Take const references --- atom/browser/ui/cocoa/atom_scrubber_data_source.h | 4 ++-- atom/browser/ui/cocoa/atom_scrubber_data_source.mm | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/atom/browser/ui/cocoa/atom_scrubber_data_source.h b/atom/browser/ui/cocoa/atom_scrubber_data_source.h index 5850e30769..57ef150909 100644 --- a/atom/browser/ui/cocoa/atom_scrubber_data_source.h +++ b/atom/browser/ui/cocoa/atom_scrubber_data_source.h @@ -18,8 +18,8 @@ std::vector items_; } -- (id)initWithItems:(std::vector)items; -- (void)setItems:(std::vector)items; +- (id)initWithItems:(const std::vector&)items; +- (void)setItems:(const std::vector&)items; @end diff --git a/atom/browser/ui/cocoa/atom_scrubber_data_source.mm b/atom/browser/ui/cocoa/atom_scrubber_data_source.mm index b320d7a2c3..3c8bb6b932 100644 --- a/atom/browser/ui/cocoa/atom_scrubber_data_source.mm +++ b/atom/browser/ui/cocoa/atom_scrubber_data_source.mm @@ -13,18 +13,19 @@ static NSString* const TextItemIdentifier = @"scrubber.text.item"; static NSString* const ImageItemIdentifier = @"scrubber.image.item"; -- (id)initWithItems:(std::vector)items { +- (id)initWithItems:(const std::vector&)items { if ((self = [super init])) { items_ = items; } return self; } -- (NSInteger)numberOfItemsForScrubber:(NSScrubber *)theScrubber { +- (NSInteger)numberOfItemsForScrubber:(NSScrubber*)theScrubber { return items_.size(); } -- (NSScrubberItemView *)scrubber:(NSScrubber *)scrubber viewForItemAtIndex:(NSInteger)index { +- (NSScrubberItemView*)scrubber:(NSScrubber*)scrubber + viewForItemAtIndex:(NSInteger)index { mate::PersistentDictionary item = items_[index]; NSScrubberItemView* itemView; @@ -48,7 +49,7 @@ static NSString* const ImageItemIdentifier = @"scrubber.image.item"; return itemView; } -- (void)setItems:(std::vector)items { +- (void)setItems:(const std::vector&)items { items_ = items; } From 2fe21ecdd08ff8e7d3a66d18e6789318c8723a62 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 13 Mar 2017 10:28:30 -0700 Subject: [PATCH 419/925] :art: --- atom/browser/ui/cocoa/atom_touch_bar.mm | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index 093f3d787e..13b25c68a5 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -189,16 +189,16 @@ static NSString* const ImageScrubberItemIdentifier = @"scrubber.image.item"; details); } -- (void)scrubber:(NSScrubber *)scrubber didSelectItemAtIndex:(NSInteger)selectedIndex { +- (void)scrubber:(NSScrubber*)scrubber didSelectItemAtIndex:(NSInteger)selectedIndex { base::DictionaryValue details; - details.SetInteger("selectedIndex", (long)selectedIndex); + details.SetInteger("selectedIndex", selectedIndex); details.SetString("type", "select"); window_->NotifyTouchBarItemInteraction([scrubber.identifier UTF8String], details); } -- (void)scrubber:(NSScrubber *)scrubber didHighlightItemAtIndex:(NSInteger)highlightedIndex { +- (void)scrubber:(NSScrubber*)scrubber didHighlightItemAtIndex:(NSInteger)highlightedIndex { base::DictionaryValue details; - details.SetInteger("highlightedIndex", (long)highlightedIndex); + details.SetInteger("highlightedIndex", highlightedIndex); details.SetString("type", "highlight"); window_->NotifyTouchBarItemInteraction([scrubber.identifier UTF8String], details); } @@ -496,7 +496,7 @@ static NSString* const ImageScrubberItemIdentifier = @"scrubber.image.item"; } - (NSTouchBarItem*)makeScrubberForID:(NSString*)id - withIdentifier:(NSString*)identifier { + withIdentifier:(NSString*)identifier { std::string s_id([id UTF8String]); if (![self hasItemWithID:s_id]) return nil; @@ -528,8 +528,7 @@ static NSString* const ImageScrubberItemIdentifier = @"scrubber.image.item"; } - (void)updateScrubber:(NSCustomTouchBarItem*)item - withSettings:(const mate::PersistentDictionary&)settings { - + withSettings:(const mate::PersistentDictionary&)settings { NSScrubber* scrubber = item.view; std::vector items; From d7f458d232ad2822002042bbc5e69f66af0710d9 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 13 Mar 2017 10:31:51 -0700 Subject: [PATCH 420/925] Add protocol methods --- atom/browser/ui/cocoa/touch_bar_forward_declarations.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/atom/browser/ui/cocoa/touch_bar_forward_declarations.h b/atom/browser/ui/cocoa/touch_bar_forward_declarations.h index 898bdee25e..1a5d9311d2 100644 --- a/atom/browser/ui/cocoa/touch_bar_forward_declarations.h +++ b/atom/browser/ui/cocoa/touch_bar_forward_declarations.h @@ -199,14 +199,21 @@ static const NSTouchBarItemIdentifier NSTouchBarItemIdentifierOtherItemsProxy = @optional - (nullable NSTouchBarItem*)touchBar:(NSTouchBar*)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier; + @end @protocol NSScrubberDelegate +- (void)scrubber:(NSScrubber *)scrubber didHighlightItemAtIndex:(NSInteger)highlightedIndex; +- (void)scrubber:(NSScrubber *)scrubber didSelectItemAtIndex:(NSInteger)selectedIndex; + @end @protocol NSScrubberDataSource +- (NSInteger)numberOfItemsForScrubber:(NSScrubber *)scrubber; +- (NSInteger)numberOfItemsForScrubber:(NSScrubber *)scrubber; + @end #pragma clang assume_nonnull end From 4f31a5c452c038a79d8575c58258f9c44e0ea45f Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 13 Mar 2017 10:39:42 -0700 Subject: [PATCH 421/925] Fix duplicate protocol method declaration --- atom/browser/ui/cocoa/touch_bar_forward_declarations.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/atom/browser/ui/cocoa/touch_bar_forward_declarations.h b/atom/browser/ui/cocoa/touch_bar_forward_declarations.h index 1a5d9311d2..9246379cf5 100644 --- a/atom/browser/ui/cocoa/touch_bar_forward_declarations.h +++ b/atom/browser/ui/cocoa/touch_bar_forward_declarations.h @@ -212,7 +212,8 @@ static const NSTouchBarItemIdentifier NSTouchBarItemIdentifierOtherItemsProxy = @protocol NSScrubberDataSource - (NSInteger)numberOfItemsForScrubber:(NSScrubber *)scrubber; -- (NSInteger)numberOfItemsForScrubber:(NSScrubber *)scrubber; +- (__kindof NSScrubberItemView *)scrubber:(NSScrubber *)scrubber + viewForItemAtIndex:(NSInteger)index; @end From e52ece1a1ef685c2cb59593129e2797636cd14cc Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 13 Mar 2017 11:06:41 -0700 Subject: [PATCH 422/925] Implement NSScrubberDataSource in AtomTouchBar --- .../ui/cocoa/atom_scrubber_data_source.h | 26 --------- .../ui/cocoa/atom_scrubber_data_source.mm | 56 ------------------ atom/browser/ui/cocoa/atom_touch_bar.h | 2 +- atom/browser/ui/cocoa/atom_touch_bar.mm | 57 ++++++++++++++----- filenames.gypi | 2 - 5 files changed, 45 insertions(+), 98 deletions(-) delete mode 100644 atom/browser/ui/cocoa/atom_scrubber_data_source.h delete mode 100644 atom/browser/ui/cocoa/atom_scrubber_data_source.mm diff --git a/atom/browser/ui/cocoa/atom_scrubber_data_source.h b/atom/browser/ui/cocoa/atom_scrubber_data_source.h deleted file mode 100644 index 57ef150909..0000000000 --- a/atom/browser/ui/cocoa/atom_scrubber_data_source.h +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) 2017 GitHub, Inc. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#ifndef ATOM_BROWSER_UI_COCOA_ATOM_SCRUBBER_DATA_SOURCE_H_ -#define ATOM_BROWSER_UI_COCOA_ATOM_SCRUBBER_DATA_SOURCE_H_ - -#import - -#include -#include - -#include "atom/browser/ui/cocoa/touch_bar_forward_declarations.h" -#include "native_mate/persistent_dictionary.h" - -@interface AtomScrubberDataSource : NSObject { - @protected - std::vector items_; -} - -- (id)initWithItems:(const std::vector&)items; -- (void)setItems:(const std::vector&)items; - -@end - -#endif // ATOM_BROWSER_UI_COCOA_ATOM_SCRUBBER_DATA_SOURCE_H_ diff --git a/atom/browser/ui/cocoa/atom_scrubber_data_source.mm b/atom/browser/ui/cocoa/atom_scrubber_data_source.mm deleted file mode 100644 index 3c8bb6b932..0000000000 --- a/atom/browser/ui/cocoa/atom_scrubber_data_source.mm +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) 2017 GitHub, Inc. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#import "atom/browser/ui/cocoa/atom_scrubber_data_source.h" - -#include "atom/common/native_mate_converters/image_converter.h" -#include "base/strings/sys_string_conversions.h" -#include "ui/gfx/image/image.h" - -@implementation AtomScrubberDataSource - -static NSString* const TextItemIdentifier = @"scrubber.text.item"; -static NSString* const ImageItemIdentifier = @"scrubber.image.item"; - -- (id)initWithItems:(const std::vector&)items { - if ((self = [super init])) { - items_ = items; - } - return self; -} - -- (NSInteger)numberOfItemsForScrubber:(NSScrubber*)theScrubber { - return items_.size(); -} - -- (NSScrubberItemView*)scrubber:(NSScrubber*)scrubber - viewForItemAtIndex:(NSInteger)index { - mate::PersistentDictionary item = items_[index]; - - NSScrubberItemView* itemView; - std::string title; - - if (item.Get("label", &title)) { - NSScrubberTextItemView* view = [scrubber makeItemWithIdentifier:TextItemIdentifier owner:self]; - view.title = base::SysUTF8ToNSString(title); - - itemView = view; - } else { - NSScrubberImageItemView* view = [scrubber makeItemWithIdentifier:ImageItemIdentifier owner:self]; - gfx::Image image; - if (item.Get("image", &image)) { - view.image = image.AsNSImage(); - } - - itemView = view; - } - - return itemView; -} - -- (void)setItems:(const std::vector&)items { - items_ = items; -} - -@end diff --git a/atom/browser/ui/cocoa/atom_touch_bar.h b/atom/browser/ui/cocoa/atom_touch_bar.h index 858799b939..29cdf79b25 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.h +++ b/atom/browser/ui/cocoa/atom_touch_bar.h @@ -17,7 +17,7 @@ #include "native_mate/constructor.h" #include "native_mate/persistent_dictionary.h" -@interface AtomTouchBar : NSObject { +@interface AtomTouchBar : NSObject { @protected std::vector ordered_settings_; std::map settings_; diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index 13b25c68a5..1d62f1e8a9 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -504,40 +504,71 @@ static NSString* const ImageScrubberItemIdentifier = @"scrubber.image.item"; base::scoped_nsobject item([[NSClassFromString( @"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]); - int width = 320; int height = 30; settings.Get("frameWidth", &width); settings.Get("frameHeight", &height); - NSScrubber* scrubber = [[NSClassFromString(@"NSScrubber") alloc] initWithFrame:NSMakeRect(0, 0, width, height)]; + NSScrubber* scrubber = [[[NSClassFromString(@"NSScrubber") alloc] initWithFrame:NSMakeRect(0, 0, width, height)] autorelease]; [scrubber registerClass:NSClassFromString(@"NSScrubberTextItemView") forItemIdentifier:TextScrubberItemIdentifier]; [scrubber registerClass:NSClassFromString(@"NSScrubberImageItemView") forItemIdentifier:ImageScrubberItemIdentifier]; scrubber.delegate = self; + scrubber.dataSource = self; scrubber.identifier = id; - std::vector items; - settings.Get("items", &items); - scrubber.dataSource = [[AtomScrubberDataSource alloc] initWithItems:items]; scrubber.mode = NSScrubberModeFree; [item setView:scrubber]; - [self updateScrubber:item withSettings:settings]; + return item.autorelease(); } - (void)updateScrubber:(NSCustomTouchBarItem*)item withSettings:(const mate::PersistentDictionary&)settings { NSScrubber* scrubber = item.view; - - std::vector items; - settings.Get("items", &items); - - AtomScrubberDataSource* source = scrubber.dataSource; - [source setItems:items]; - [scrubber reloadData]; } +- (NSInteger)numberOfItemsForScrubber:(NSScrubber*)scrubber { + std::string s_id([[scrubber identifier] UTF8String]); + if (![self hasItemWithID:s_id]) return 0; + + mate::PersistentDictionary settings = settings_[s_id]; + std::vector items; + settings.Get("items", &items); + return items.size(); +} + +- (NSScrubberItemView*)scrubber:(NSScrubber*)scrubber + viewForItemAtIndex:(NSInteger)index { + std::string s_id([[scrubber identifier] UTF8String]); + if (![self hasItemWithID:s_id]) return nil; + + mate::PersistentDictionary settings = settings_[s_id]; + std::vector items; + settings.Get("items", &items); + mate::PersistentDictionary item = items[index]; + + NSScrubberItemView* itemView; + std::string title; + + if (item.Get("label", &title)) { + NSScrubberTextItemView* view = [scrubber makeItemWithIdentifier:TextScrubberItemIdentifier + owner:self]; + view.title = base::SysUTF8ToNSString(title); + itemView = view; + } else { + NSScrubberImageItemView* view = [scrubber makeItemWithIdentifier:ImageScrubberItemIdentifier + owner:self]; + gfx::Image image; + if (item.Get("image", &image)) { + view.image = image.AsNSImage(); + } + itemView = view; + } + + return itemView; +} + @end diff --git a/filenames.gypi b/filenames.gypi index f56d50062a..44a6fbfc43 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -284,8 +284,6 @@ 'atom/browser/ui/atom_menu_model.h', 'atom/browser/ui/cocoa/atom_menu_controller.h', 'atom/browser/ui/cocoa/atom_menu_controller.mm', - 'atom/browser/ui/cocoa/atom_scrubber_data_source.h', - 'atom/browser/ui/cocoa/atom_scrubber_data_source.mm', 'atom/browser/ui/cocoa/atom_touch_bar.h', 'atom/browser/ui/cocoa/atom_touch_bar.mm', 'atom/browser/ui/cocoa/touch_bar_forward_declarations.h', From 268dd1a6c218a2a41e241a52fc924e9c45ee4271 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 13 Mar 2017 11:15:36 -0700 Subject: [PATCH 423/925] Remove unneeded include --- atom/browser/ui/cocoa/atom_touch_bar.mm | 1 - 1 file changed, 1 deletion(-) diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index 1d62f1e8a9..bba1845d25 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -4,7 +4,6 @@ #import "atom/browser/ui/cocoa/atom_touch_bar.h" -#include "atom/browser/ui/cocoa/atom_scrubber_data_source.h" #include "atom/common/color_util.h" #include "atom/common/native_mate_converters/image_converter.h" #include "base/strings/sys_string_conversions.h" From 0dcdd60010a916c0cc03d2ec5356aa1ba7ad5714 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 13 Mar 2017 11:17:55 -0700 Subject: [PATCH 424/925] Add initial TouchBarScrubber spec --- spec/api-touch-bar-spec.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/spec/api-touch-bar-spec.js b/spec/api-touch-bar-spec.js index a09e0314c4..525cef28a2 100644 --- a/spec/api-touch-bar-spec.js +++ b/spec/api-touch-bar-spec.js @@ -3,7 +3,7 @@ const {BrowserWindow, TouchBar} = require('electron').remote const {closeWindow} = require('./window-helpers') const {TouchBarButton, TouchBarColorPicker, TouchBarGroup} = TouchBar -const {TouchBarLabel, TouchBarPopover, TouchBarSegmentedControl, TouchBarSlider, TouchBarSpacer} = TouchBar +const {TouchBarLabel, TouchBarPopover, TouchBarScrubber, TouchBarSegmentedControl, TouchBarSlider, TouchBarSpacer} = TouchBar describe('TouchBar module', function () { it('throws an error when created without an items array', function () { @@ -47,7 +47,10 @@ describe('TouchBar module', function () { segments: [{label: 'baz', enabled: false}], selectedIndex: 5 }), - new TouchBarSegmentedControl({segments: []}) + new TouchBarSegmentedControl({segments: []}), + new TouchBarScrubber({ + items: [{label: 'foo'}, {label: 'bar'}, {label: 'baz'}] + }) ]) window.setTouchBar(touchBar) label.label = 'baz' From 5e70adb5110f731a4c8e17ecaad116aad9bb18b7 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 13 Mar 2017 11:22:08 -0700 Subject: [PATCH 425/925] Guard against only specifying select or highlight handler --- lib/browser/api/touch-bar.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index 7f997edde8..62b3e2195b 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -235,11 +235,14 @@ TouchBar.TouchBarScrubber = class TouchBarScrubber extends TouchBarItem { constructor (config) { super() if (config == null) config = {} - const {items, onSelect, onHighlight} = config + const {items} = config + let {onSelect, onHighlight} = config this.type = 'scrubber' this._addLiveProperty('items', items) if (typeof onSelect === 'function' || typeof onHighlight === 'function') { + if (onSelect == null) onSelect = () => {} + if (onHighlight == null) onHighlight = () => {} this.onInteraction = (details) => { if (details.type === 'select') { onSelect(details.selectedIndex) From fdd574cea544ea5608ab9ba56873bcd9bd2c7366 Mon Sep 17 00:00:00 2001 From: deepak1556 Date: Tue, 17 Jan 2017 19:53:13 +0530 Subject: [PATCH 426/925] browser: implement pdf renderer as webui --- .gitmodules | 3 + atom/app/atom_content_client.cc | 21 +++ atom/browser/atom_browser_main_parts.cc | 4 + .../atom_resource_dispatcher_host_delegate.cc | 44 +++++ .../atom_resource_dispatcher_host_delegate.h | 7 + .../browser/atom_web_ui_controller_factory.cc | 158 ++++++++++++++++++ atom/browser/atom_web_ui_controller_factory.h | 40 +++++ atom/renderer/atom_renderer_client.cc | 3 + electron.gyp | 31 ++++ filenames.gypi | 8 + lib/renderer/init.js | 2 +- script/create-dist.py | 2 + 12 files changed, 322 insertions(+), 1 deletion(-) create mode 100644 atom/browser/atom_web_ui_controller_factory.cc create mode 100644 atom/browser/atom_web_ui_controller_factory.h diff --git a/.gitmodules b/.gitmodules index 5bc253bad5..80cafd9bdd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,3 +22,6 @@ [submodule "vendor/boto"] path = vendor/boto url = https://github.com/boto/boto.git +[submodule "vendor/grit"] + path = vendor/grit + url = https://chromium.googlesource.com/chromium/src/tools/grit.git diff --git a/atom/app/atom_content_client.cc b/atom/app/atom_content_client.cc index 760a42732b..f1b7a9c36b 100644 --- a/atom/app/atom_content_client.cc +++ b/atom/app/atom_content_client.cc @@ -18,6 +18,7 @@ #include "content/public/common/content_constants.h" #include "content/public/common/pepper_plugin_info.h" #include "content/public/common/user_agent.h" +#include "pdf/pdf.h" #include "ppapi/shared_impl/ppapi_permissions.h" #include "third_party/widevine/cdm/stub/widevine_cdm_version.h" #include "ui/base/l10n/l10n_util.h" @@ -108,6 +109,25 @@ content::PepperPluginInfo CreateWidevineCdmInfo(const base::FilePath& path, } #endif +void ComputeBuiltInPlugins(std::vector* plugins) { + content::PepperPluginInfo pdf_info; + pdf_info.is_internal = true; + pdf_info.is_out_of_process = true; + pdf_info.name = "Chromium PDF Viewer"; + pdf_info.description = "Portable Document Format"; + pdf_info.path = base::FilePath::FromUTF8Unsafe("internal-pdf-viewer"); + content::WebPluginMimeType pdf_mime_type("application/x-google-chrome-pdf", + "pdf", "Portable Document Format"); + pdf_info.mime_types.push_back(pdf_mime_type); + pdf_info.internal_entry_points.get_interface = chrome_pdf::PPP_GetInterface; + pdf_info.internal_entry_points.initialize_module = + chrome_pdf::PPP_InitializeModule; + pdf_info.internal_entry_points.shutdown_module = + chrome_pdf::PPP_ShutdownModule; + pdf_info.permissions = ppapi::PERMISSION_PRIVATE | ppapi::PERMISSION_DEV; + plugins->push_back(pdf_info); +} + void ConvertStringWithSeparatorToVector(std::vector* vec, const char* separator, const char* cmd_switch) { @@ -190,6 +210,7 @@ void AtomContentClient::AddPepperPlugins( #if defined(WIDEVINE_CDM_AVAILABLE) && BUILDFLAG(ENABLE_PEPPER_CDMS) AddWidevineCdmFromCommandLine(plugins); #endif + ComputeBuiltInPlugins(plugins); } void AtomContentClient::AddServiceWorkerSchemes( diff --git a/atom/browser/atom_browser_main_parts.cc b/atom/browser/atom_browser_main_parts.cc index 4031fb27cf..eb09b18b71 100644 --- a/atom/browser/atom_browser_main_parts.cc +++ b/atom/browser/atom_browser_main_parts.cc @@ -12,6 +12,7 @@ #include "atom/browser/browser.h" #include "atom/browser/javascript_environment.h" #include "atom/browser/node_debugger.h" +#include "atom/browser/atom_web_ui_controller_factory.h" #include "atom/common/api/atom_bindings.h" #include "atom/common/node_bindings.h" #include "atom/common/node_includes.h" @@ -166,6 +167,9 @@ void AtomBrowserMainParts::PreMainMessageLoopRun() { base::Bind(&v8::Isolate::LowMemoryNotification, base::Unretained(js_env_->isolate()))); + content::WebUIControllerFactory::RegisterFactory( + AtomWebUIControllerFactory::GetInstance()); + brightray::BrowserMainParts::PreMainMessageLoopRun(); bridge_task_runner_->MessageLoopIsReady(); bridge_task_runner_ = nullptr; diff --git a/atom/browser/atom_resource_dispatcher_host_delegate.cc b/atom/browser/atom_resource_dispatcher_host_delegate.cc index 78589112c5..1438c220b0 100644 --- a/atom/browser/atom_resource_dispatcher_host_delegate.cc +++ b/atom/browser/atom_resource_dispatcher_host_delegate.cc @@ -9,8 +9,11 @@ #include "atom/common/platform_util.h" #include "base/strings/utf_string_conversions.h" #include "content/public/browser/browser_thread.h" +#include "content/public/browser/stream_handle.h" +#include "content/public/browser/stream_info.h" #include "net/base/escape.h" #include "net/ssl/client_cert_store.h" +#include "net/url_request/url_request.h" #include "url/gurl.h" #if defined(USE_NSS_CERTS) @@ -57,6 +60,22 @@ void HandleExternalProtocolInUI( permission_helper->RequestOpenExternalPermission(callback, has_user_gesture); } +void OnPdfStreamCreated(std::unique_ptr stream, + int64_t expected_content_size, + const content::ResourceRequestInfo::WebContentsGetter& + web_contents_getter) { + content::WebContents* web_contents = web_contents_getter.Run(); + if (!web_contents) + return; + + LOG(WARNING) << stream->handle->GetURL(); + LOG(WARNING) << stream->original_url; + + content::NavigationController::LoadURLParams params( + GURL("chrome://pdf-viewer/index.html")); + web_contents->GetController().LoadURLWithParams(params); +} + } // namespace AtomResourceDispatcherHostDelegate::AtomResourceDispatcherHostDelegate() { @@ -95,4 +114,29 @@ AtomResourceDispatcherHostDelegate::CreateClientCertStore( #endif } +bool AtomResourceDispatcherHostDelegate::ShouldInterceptResourceAsStream( + net::URLRequest* request, + const base::FilePath& plugin_path, + const std::string& mime_type, + GURL* origin, + std::string* payload) { + if (mime_type == "application/pdf") { + *origin = GURL("chrome://pdf-viewer/"); + return true; + } + return false; +} + +void AtomResourceDispatcherHostDelegate::OnStreamCreated( + net::URLRequest* request, + std::unique_ptr stream) { + const content::ResourceRequestInfo* info = + content::ResourceRequestInfo::ForRequest(request); + content::BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&OnPdfStreamCreated, base::Passed(&stream), + request->GetExpectedContentSize(), + info->GetWebContentsGetterForRequest())); +} + } // namespace atom diff --git a/atom/browser/atom_resource_dispatcher_host_delegate.h b/atom/browser/atom_resource_dispatcher_host_delegate.h index 681fec6f6f..2a5ca1edd0 100644 --- a/atom/browser/atom_resource_dispatcher_host_delegate.h +++ b/atom/browser/atom_resource_dispatcher_host_delegate.h @@ -22,6 +22,13 @@ class AtomResourceDispatcherHostDelegate net::URLRequest* request) override; std::unique_ptr CreateClientCertStore( content::ResourceContext* resource_context) override; + bool ShouldInterceptResourceAsStream(net::URLRequest* request, + const base::FilePath& plugin_path, + const std::string& mime_type, + GURL* origin, + std::string* payload) override; + void OnStreamCreated(net::URLRequest* request, + std::unique_ptr stream) override; }; } // namespace atom diff --git a/atom/browser/atom_web_ui_controller_factory.cc b/atom/browser/atom_web_ui_controller_factory.cc new file mode 100644 index 0000000000..3682309a71 --- /dev/null +++ b/atom/browser/atom_web_ui_controller_factory.cc @@ -0,0 +1,158 @@ +// Copyright (c) 2017 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/atom_web_ui_controller_factory.h" + +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/url_data_source.h" +#include "content/public/browser/web_contents.h" +#include "content/public/browser/web_ui.h" +#include "content/public/browser/web_ui_controller.h" +#include "content/public/common/bindings_policy.h" +#include "grit/pdf_viewer_resources_map.h" +#include "ui/base/resource/resource_bundle.h" + +namespace atom { + +namespace { + +const char kChromeUIPdfViewerHost[] = "pdf-viewer"; + +std::string PathWithoutParams(const std::string& path) { + return GURL(std::string("chrome://pdf-viewer/") + path).path().substr(1); +} + +std::string GetMimeTypeForPath(const std::string& path) { + std::string filename = PathWithoutParams(path); + if (base::EndsWith(filename, ".html", base::CompareCase::INSENSITIVE_ASCII)) { + return "text/html"; + } else if (base::EndsWith(filename, ".css", + base::CompareCase::INSENSITIVE_ASCII)) { + return "text/css"; + } else if (base::EndsWith(filename, ".js", + base::CompareCase::INSENSITIVE_ASCII)) { + return "application/javascript"; + } else if (base::EndsWith(filename, ".png", + base::CompareCase::INSENSITIVE_ASCII)) { + return "image/png"; + } else if (base::EndsWith(filename, ".gif", + base::CompareCase::INSENSITIVE_ASCII)) { + return "image/gif"; + } else if (base::EndsWith(filename, ".svg", + base::CompareCase::INSENSITIVE_ASCII)) { + return "image/svg+xml"; + } else if (base::EndsWith(filename, ".manifest", + base::CompareCase::INSENSITIVE_ASCII)) { + return "text/cache-manifest"; + } + return "text/html"; +} + +class BundledDataSource : public content::URLDataSource { + public: + BundledDataSource() { + for (size_t i = 0; i < kPdfViewerResourcesSize; ++i) { + base::FilePath resource_path = + base::FilePath().AppendASCII(kPdfViewerResources[i].name); + resource_path = resource_path.NormalizePathSeparators(); + + DCHECK(path_to_resource_id_.find(resource_path) == + path_to_resource_id_.end()); + path_to_resource_id_[resource_path] = kPdfViewerResources[i].value; + } + } + + // content::URLDataSource implementation. + std::string GetSource() const override { return kChromeUIPdfViewerHost; } + + void StartDataRequest(const std::string& path, + int render_process_id, + int render_frame_id, + const GotDataCallback& callback) override { + std::string filename = PathWithoutParams(path); + std::map::const_iterator entry = + path_to_resource_id_.find(base::FilePath(filename)); + if (entry != path_to_resource_id_.end()) { + int resource_id = entry->second; + const ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); + callback.Run(rb.LoadDataResourceBytes(resource_id)); + } + } + + std::string GetMimeType(const std::string& path) const override { + return GetMimeTypeForPath(path); + } + + bool ShouldAddContentSecurityPolicy() const override { return false; } + + bool ShouldDenyXFrameOptions() const override { return false; } + + bool ShouldServeMimeTypeAsContentTypeHeader() const override { return true; } + + private: + ~BundledDataSource() override {} + + // A map from a resource path to the resource ID. + std::map path_to_resource_id_; + + DISALLOW_COPY_AND_ASSIGN(BundledDataSource); +}; + +class PdfViewerUI : public content::WebUIController { + public: + PdfViewerUI(content::BrowserContext* browser_context, content::WebUI* web_ui) + : content::WebUIController(web_ui) { + content::URLDataSource::Add(browser_context, new BundledDataSource); + } + + void RenderViewCreated(content::RenderViewHost* rvh) override { + rvh->AllowBindings(content::BINDINGS_POLICY_WEB_UI); + } +}; +} + +// static +AtomWebUIControllerFactory* AtomWebUIControllerFactory::GetInstance() { + return base::Singleton::get(); +} + +AtomWebUIControllerFactory::AtomWebUIControllerFactory() {} + +AtomWebUIControllerFactory::~AtomWebUIControllerFactory() {} + +content::WebUI::TypeID AtomWebUIControllerFactory::GetWebUIType( + content::BrowserContext* browser_context, + const GURL& url) const { + if (url.host() == kChromeUIPdfViewerHost) { + return const_cast(this); + } + + return content::WebUI::kNoWebUI; +} + +bool AtomWebUIControllerFactory::UseWebUIForURL( + content::BrowserContext* browser_context, + const GURL& url) const { + return GetWebUIType(browser_context, url) != content::WebUI::kNoWebUI; +} + +bool AtomWebUIControllerFactory::UseWebUIBindingsForURL( + content::BrowserContext* browser_context, + const GURL& url) const { + return UseWebUIForURL(browser_context, url); +} + +content::WebUIController* +AtomWebUIControllerFactory::CreateWebUIControllerForURL(content::WebUI* web_ui, + const GURL& url) const { + if (url.host() == kChromeUIPdfViewerHost) { + auto browser_context = web_ui->GetWebContents()->GetBrowserContext(); + return new PdfViewerUI(browser_context, web_ui); + } + return nullptr; +} + +} // namespace atom diff --git a/atom/browser/atom_web_ui_controller_factory.h b/atom/browser/atom_web_ui_controller_factory.h new file mode 100644 index 0000000000..d63aa0ecee --- /dev/null +++ b/atom/browser/atom_web_ui_controller_factory.h @@ -0,0 +1,40 @@ +// Copyright (c) 2017 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_ATOM_WEB_UI_CONTROLLER_FACTORY_H_ +#define ATOM_BROWSER_ATOM_WEB_UI_CONTROLLER_FACTORY_H_ + +#include "base/macros.h" +#include "base/memory/singleton.h" +#include "content/public/browser/web_ui.h" +#include "content/public/browser/web_ui_controller_factory.h" + +namespace atom { + +class AtomWebUIControllerFactory : public content::WebUIControllerFactory { + public: + static AtomWebUIControllerFactory* GetInstance(); + + AtomWebUIControllerFactory(); + virtual ~AtomWebUIControllerFactory(); + + content::WebUI::TypeID GetWebUIType(content::BrowserContext* browser_context, + const GURL& url) const override; + bool UseWebUIForURL(content::BrowserContext* browser_context, + const GURL& url) const override; + bool UseWebUIBindingsForURL(content::BrowserContext* browser_context, + const GURL& url) const override; + content::WebUIController* CreateWebUIControllerForURL( + content::WebUI* web_ui, + const GURL& url) const override; + + private: + friend struct base::DefaultSingletonTraits; + + DISALLOW_COPY_AND_ASSIGN(AtomWebUIControllerFactory); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_ATOM_WEB_UI_CONTROLLER_FACTORY_H_ diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index 637792c2ae..1db52bd7cb 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -274,6 +274,9 @@ void AtomRendererClient::RenderFrameCreated( // This is required for widevine plugin detection provided during runtime. blink::resetPluginCache(); + blink::WebSecurityPolicy::addOriginAccessWhitelistEntry( + GURL("chrome://pdf-viewer/"), "file", "", true); + // Parse --secure-schemes=scheme1,scheme2 std::vector secure_schemes_list = ParseSchemesCLISwitch(switches::kSecureSchemes); diff --git a/electron.gyp b/electron.gyp index 47bc67b98e..9a47100008 100644 --- a/electron.gyp +++ b/electron.gyp @@ -6,6 +6,7 @@ 'company_abbr%': 'github', 'version%': '1.6.3', 'js2c_input_dir': '<(SHARED_INTERMEDIATE_DIR)/js2c', + 'grit_dir': 'vendor/grit', }, 'includes': [ 'filenames.gypi', @@ -210,6 +211,7 @@ 'type': 'static_library', 'dependencies': [ 'atom_js2c', + 'pdfviewer', 'vendor/brightray/brightray.gyp:brightray', 'vendor/node/node.gyp:node', ], @@ -417,6 +419,34 @@ } ], }, # target app2asar + { + 'target_name': 'pdfviewer', + 'type': 'none', + 'actions': [ + { + 'action_name': 'pdfviewer', + 'inputs': [ + '<(grit_dir)/grit.py', + '<@(pdf_viewer_sources)', + ], + 'outputs': [ + 'pdf_viewer_resouces.h', + 'pdf_viewer_resouces_map.cc', + 'pdf_viewer_resouces_map.h', + 'pdf_viewer_resources.pak', + ], + 'action': [ + 'python', + '<(grit_dir)/grit.py', + '-i', + '<@(pdf_viewer_sources)', + 'build', + '-o', + '<(SHARED_INTERMEDIATE_DIR)/grit', + ], + } + ], + }, # target pdfviewer { 'target_name': 'atom_js2c_copy', 'type': 'none', @@ -543,6 +573,7 @@ '<(libchromiumcontent_dir)/icudtl.dat', '<(libchromiumcontent_dir)/natives_blob.bin', '<(libchromiumcontent_dir)/snapshot_blob.bin', + '<(PRODUCT_DIR)/pdf_viewer_resources.pak', ], 'xcode_settings': { 'ATOM_BUNDLE_ID': 'com.<(company_abbr).<(project_name).framework', diff --git a/filenames.gypi b/filenames.gypi index 44a6fbfc43..3c7e4d2172 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -96,6 +96,9 @@ 'default_app/main.js', 'default_app/package.json', ], + 'pdf_viewer_sources': [ + 'atom/browser/resources/pdf_viewer/resources.grd', + ], 'lib_sources': [ 'atom/app/atom_content_client.cc', 'atom/app/atom_content_client.h', @@ -195,6 +198,8 @@ 'atom/browser/atom_resource_dispatcher_host_delegate.h', 'atom/browser/atom_speech_recognition_manager_delegate.cc', 'atom/browser/atom_speech_recognition_manager_delegate.h', + 'atom/browser/atom_web_ui_controller_factory.cc', + 'atom/browser/atom_web_ui_controller_factory.h', 'atom/browser/bridge_task_runner.cc', 'atom/browser/bridge_task_runner.h', 'atom/browser/browser.cc', @@ -617,6 +622,9 @@ 'chromium_src/net/test/embedded_test_server/tcp_listen_socket.h', '<@(native_mate_files)', '<(SHARED_INTERMEDIATE_DIR)/atom_natives.h', + '<(SHARED_INTERMEDIATE_DIR)/grit/pdf_viewer_resources.h', + '<(SHARED_INTERMEDIATE_DIR)/grit/pdf_viewer_resources_map.cc', + '<(SHARED_INTERMEDIATE_DIR)/grit/pdf_viewer_resources_map.h', ], 'lib_sources_linux': [ 'chromium_src/chrome/browser/icon_loader_auralinux.cc', diff --git a/lib/renderer/init.js b/lib/renderer/init.js index 24e19b9d2a..7bd707c102 100644 --- a/lib/renderer/init.js +++ b/lib/renderer/init.js @@ -76,7 +76,7 @@ if (window.location.protocol === 'chrome-devtools:') { // Override some inspector APIs. require('./inspector') nodeIntegration = 'true' -} else if (window.location.protocol === 'chrome-extension:') { +} else if (window.location.protocol === 'chrome-extension:' || window.location.protocol === 'chrome:') { // Add implementations of chrome API. require('./chrome-api').injectTo(window.location.hostname, isBackgroundPage, window) nodeIntegration = 'false' diff --git a/script/create-dist.py b/script/create-dist.py index 5a63b20707..35a109cfa3 100755 --- a/script/create-dist.py +++ b/script/create-dist.py @@ -31,6 +31,7 @@ TARGET_BINARIES = { 'win32': [ '{0}.exe'.format(PROJECT_NAME), # 'electron.exe' 'content_shell.pak', + 'pdf_viewer_resources.pak', 'd3dcompiler_47.dll', 'icudtl.dat', 'libEGL.dll', @@ -48,6 +49,7 @@ TARGET_BINARIES = { 'linux': [ PROJECT_NAME, # 'electron' 'content_shell.pak', + 'pdf_viewer_resources.pak', 'icudtl.dat', 'libffmpeg.so', 'libnode.so', From be480fb634fe01b74af7a68f18c2b838108b9c0c Mon Sep 17 00:00:00 2001 From: deepak1556 Date: Tue, 17 Jan 2017 19:57:16 +0530 Subject: [PATCH 427/925] import pdf ui --- .../resources/pdf_viewer/browser_api.js | 204 ++++ .../resources/pdf_viewer/elements/icons.html | 19 + .../pdf_viewer/elements/shared-icon-style.css | 15 + .../viewer-bookmark/viewer-bookmark.css | 52 + .../viewer-bookmark/viewer-bookmark.html | 26 + .../viewer-bookmark/viewer-bookmark.js | 92 ++ .../viewer-bookmarks-content.html | 11 + .../viewer-bookmarks-content.js | 7 + .../viewer-error-screen.css | 7 + .../viewer-error-screen.html | 22 + .../viewer-error-screen.js | 35 + .../viewer-page-indicator.css | 35 + .../viewer-page-indicator.html | 10 + .../viewer-page-indicator.js | 64 ++ .../viewer-page-selector.css | 52 + .../viewer-page-selector.html | 19 + .../viewer-page-selector.js | 59 ++ .../viewer-password-screen.html | 30 + .../viewer-password-screen.js | 57 ++ .../viewer-pdf-toolbar/viewer-pdf-toolbar.css | 92 ++ .../viewer-pdf-toolbar.html | 68 ++ .../viewer-pdf-toolbar/viewer-pdf-toolbar.js | 146 +++ .../viewer-toolbar-dropdown.css | 59 ++ .../viewer-toolbar-dropdown.html | 29 + .../viewer-toolbar-dropdown.js | 138 +++ .../viewer-zoom-button.css | 32 + .../viewer-zoom-button.html | 15 + .../viewer-zoom-toolbar/viewer-zoom-button.js | 95 ++ .../viewer-zoom-toolbar.css | 41 + .../viewer-zoom-toolbar.html | 21 + .../viewer-zoom-toolbar.js | 75 ++ atom/browser/resources/pdf_viewer/index.css | 50 + atom/browser/resources/pdf_viewer/index.html | 42 + atom/browser/resources/pdf_viewer/main.js | 55 ++ .../browser/resources/pdf_viewer/navigator.js | 211 +++++ atom/browser/resources/pdf_viewer/ods-cpp.pdf | Bin 0 -> 1519810 bytes .../pdf_viewer/open_pdf_params_parser.js | 147 +++ atom/browser/resources/pdf_viewer/pdf.js | 889 ++++++++++++++++++ .../resources/pdf_viewer/pdf_scripting_api.js | 262 ++++++ .../resources/pdf_viewer/resources.grd | 59 ++ .../resources/pdf_viewer/toolbar_manager.js | 252 +++++ atom/browser/resources/pdf_viewer/viewport.js | 599 ++++++++++++ .../resources/pdf_viewer/viewport_scroller.js | 135 +++ .../resources/pdf_viewer/zoom_manager.js | 83 ++ 44 files changed, 4411 insertions(+) create mode 100644 atom/browser/resources/pdf_viewer/browser_api.js create mode 100644 atom/browser/resources/pdf_viewer/elements/icons.html create mode 100644 atom/browser/resources/pdf_viewer/elements/shared-icon-style.css create mode 100644 atom/browser/resources/pdf_viewer/elements/viewer-bookmark/viewer-bookmark.css create mode 100644 atom/browser/resources/pdf_viewer/elements/viewer-bookmark/viewer-bookmark.html create mode 100644 atom/browser/resources/pdf_viewer/elements/viewer-bookmark/viewer-bookmark.js create mode 100644 atom/browser/resources/pdf_viewer/elements/viewer-bookmarks-content/viewer-bookmarks-content.html create mode 100644 atom/browser/resources/pdf_viewer/elements/viewer-bookmarks-content/viewer-bookmarks-content.js create mode 100644 atom/browser/resources/pdf_viewer/elements/viewer-error-screen/viewer-error-screen.css create mode 100644 atom/browser/resources/pdf_viewer/elements/viewer-error-screen/viewer-error-screen.html create mode 100644 atom/browser/resources/pdf_viewer/elements/viewer-error-screen/viewer-error-screen.js create mode 100644 atom/browser/resources/pdf_viewer/elements/viewer-page-indicator/viewer-page-indicator.css create mode 100644 atom/browser/resources/pdf_viewer/elements/viewer-page-indicator/viewer-page-indicator.html create mode 100644 atom/browser/resources/pdf_viewer/elements/viewer-page-indicator/viewer-page-indicator.js create mode 100644 atom/browser/resources/pdf_viewer/elements/viewer-page-selector/viewer-page-selector.css create mode 100644 atom/browser/resources/pdf_viewer/elements/viewer-page-selector/viewer-page-selector.html create mode 100644 atom/browser/resources/pdf_viewer/elements/viewer-page-selector/viewer-page-selector.js create mode 100644 atom/browser/resources/pdf_viewer/elements/viewer-password-screen/viewer-password-screen.html create mode 100644 atom/browser/resources/pdf_viewer/elements/viewer-password-screen/viewer-password-screen.js create mode 100644 atom/browser/resources/pdf_viewer/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.css create mode 100644 atom/browser/resources/pdf_viewer/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.html create mode 100644 atom/browser/resources/pdf_viewer/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.js create mode 100644 atom/browser/resources/pdf_viewer/elements/viewer-toolbar-dropdown/viewer-toolbar-dropdown.css create mode 100644 atom/browser/resources/pdf_viewer/elements/viewer-toolbar-dropdown/viewer-toolbar-dropdown.html create mode 100644 atom/browser/resources/pdf_viewer/elements/viewer-toolbar-dropdown/viewer-toolbar-dropdown.js create mode 100644 atom/browser/resources/pdf_viewer/elements/viewer-zoom-toolbar/viewer-zoom-button.css create mode 100644 atom/browser/resources/pdf_viewer/elements/viewer-zoom-toolbar/viewer-zoom-button.html create mode 100644 atom/browser/resources/pdf_viewer/elements/viewer-zoom-toolbar/viewer-zoom-button.js create mode 100644 atom/browser/resources/pdf_viewer/elements/viewer-zoom-toolbar/viewer-zoom-toolbar.css create mode 100644 atom/browser/resources/pdf_viewer/elements/viewer-zoom-toolbar/viewer-zoom-toolbar.html create mode 100644 atom/browser/resources/pdf_viewer/elements/viewer-zoom-toolbar/viewer-zoom-toolbar.js create mode 100644 atom/browser/resources/pdf_viewer/index.css create mode 100644 atom/browser/resources/pdf_viewer/index.html create mode 100644 atom/browser/resources/pdf_viewer/main.js create mode 100644 atom/browser/resources/pdf_viewer/navigator.js create mode 100644 atom/browser/resources/pdf_viewer/ods-cpp.pdf create mode 100644 atom/browser/resources/pdf_viewer/open_pdf_params_parser.js create mode 100644 atom/browser/resources/pdf_viewer/pdf.js create mode 100644 atom/browser/resources/pdf_viewer/pdf_scripting_api.js create mode 100644 atom/browser/resources/pdf_viewer/resources.grd create mode 100644 atom/browser/resources/pdf_viewer/toolbar_manager.js create mode 100644 atom/browser/resources/pdf_viewer/viewport.js create mode 100644 atom/browser/resources/pdf_viewer/viewport_scroller.js create mode 100644 atom/browser/resources/pdf_viewer/zoom_manager.js diff --git a/atom/browser/resources/pdf_viewer/browser_api.js b/atom/browser/resources/pdf_viewer/browser_api.js new file mode 100644 index 0000000000..e4bcf2388f --- /dev/null +++ b/atom/browser/resources/pdf_viewer/browser_api.js @@ -0,0 +1,204 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +'use strict'; + +/** + * Returns a promise that will resolve to the default zoom factor. + * @param {!Object} streamInfo The stream object pointing to the data contained + * in the PDF. + * @return {Promise} A promise that will resolve to the default zoom + * factor. + */ +function lookupDefaultZoom(streamInfo) { + // Webviews don't run in tabs so |streamInfo.tabId| is -1 when running within + // a webview. + if (!chrome.tabs || streamInfo.tabId < 0) + return Promise.resolve(1); + + return new Promise(function(resolve, reject) { + chrome.tabs.getZoomSettings(streamInfo.tabId, function(zoomSettings) { + resolve(zoomSettings.defaultZoomFactor); + }); + }); +} + +/** + * Returns a promise that will resolve to the initial zoom factor + * upon starting the plugin. This may differ from the default zoom + * if, for example, the page is zoomed before the plugin is run. + * @param {!Object} streamInfo The stream object pointing to the data contained + * in the PDF. + * @return {Promise} A promise that will resolve to the initial zoom + * factor. + */ +function lookupInitialZoom(streamInfo) { + // Webviews don't run in tabs so |streamInfo.tabId| is -1 when running within + // a webview. + if (!chrome.tabs || streamInfo.tabId < 0) + return Promise.resolve(1); + + return new Promise(function(resolve, reject) { + chrome.tabs.getZoom(streamInfo.tabId, resolve); + }); +} + +/** + * A class providing an interface to the browser. + */ +class BrowserApi { + /** + * @constructor + * @param {!Object} streamInfo The stream object which points to the data + * contained in the PDF. + * @param {number} defaultZoom The default browser zoom. + * @param {number} initialZoom The initial browser zoom + * upon starting the plugin. + * @param {boolean} manageZoom Whether to manage zoom. + */ + constructor(streamInfo, defaultZoom, initialZoom, manageZoom) { + this.streamInfo_ = streamInfo; + this.defaultZoom_ = defaultZoom; + this.initialZoom_ = initialZoom; + this.manageZoom_ = manageZoom; + } + + /** + * Returns a promise to a BrowserApi. + * @param {!Object} streamInfo The stream object pointing to the data + * contained in the PDF. + * @param {boolean} manageZoom Whether to manage zoom. + */ + static create(streamInfo, manageZoom) { + /*return Promise.all([ + lookupDefaultZoom(streamInfo), + lookupInitialZoom(streamInfo) + ]).then(function(zoomFactors) {*/ + return new BrowserApi( + streamInfo, 1.0, 1.0, manageZoom); + //}); + } + + /** + * Returns the stream info pointing to the data contained in the PDF. + * @return {Object} The stream info object. + */ + getStreamInfo() { + return this.streamInfo_; + } + + /** + * Aborts the stream. + */ + abortStream() { + if (chrome.mimeHandlerPrivate) + chrome.mimeHandlerPrivate.abortStream(); + } + + /** + * Sets the browser zoom. + * @param {number} zoom The zoom factor to send to the browser. + * @return {Promise} A promise that will be resolved when the browser zoom + * has been updated. + */ + setZoom(zoom) { + if (!this.manageZoom_) + return Promise.resolve(); + return new Promise(function(resolve, reject) { + chrome.tabs.setZoom(this.streamInfo_.tabId, zoom, resolve); + }.bind(this)); + } + + /** + * Returns the default browser zoom factor. + * @return {number} The default browser zoom factor. + */ + getDefaultZoom() { + return this.defaultZoom_; + } + + /** + * Returns the initial browser zoom factor. + * @return {number} The initial browser zoom factor. + */ + getInitialZoom() { + return this.initialZoom_; + } + + /** + * Adds an event listener to be notified when the browser zoom changes. + * @param {function} listener The listener to be called with the new zoom + * factor. + */ + addZoomEventListener(listener) { + if (!this.manageZoom_) + return; + + chrome.tabs.onZoomChange.addListener(function(zoomChangeInfo) { + if (zoomChangeInfo.tabId != this.streamInfo_.tabId) + return; + listener(zoomChangeInfo.newZoomFactor); + }.bind(this)); + } +}; + +/** + * Creates a BrowserApi for an extension running as a mime handler. + * @return {Promise} A promise to a BrowserApi instance constructed + * using the mimeHandlerPrivate API. + */ +function createBrowserApiForMimeHandlerView() { + return new Promise(function(resolve, reject) { + chrome.mimeHandlerPrivate.getStreamInfo(resolve); + }).then(function(streamInfo) { + let manageZoom = !streamInfo.embedded && streamInfo.tabId != -1; + return new Promise(function(resolve, reject) { + if (!manageZoom) { + resolve(); + return; + } + chrome.tabs.setZoomSettings( + streamInfo.tabId, {mode: 'manual', scope: 'per-tab'}, resolve); + }).then(function() { return BrowserApi.create(streamInfo, manageZoom); }); + }); +} + +/** + * Creates a BrowserApi instance for an extension not running as a mime handler. + * @return {Promise} A promise to a BrowserApi instance constructed + * from the URL. + */ +function createBrowserApi(streamURL, originalURL) { + //let url = window.location.search.substring(1); + let streamInfo = { + streamUrl: streamURL, + originalUrl: originalURL, + responseHeaders: {}, + embedded: window.parent != window, + tabId: -1, + }; + return new Promise(function(resolve, reject) { + if (!chrome.tabs) { + resolve(); + return; + } + //chrome.tabs.getCurrent(function(tab) { + streamInfo.tabId = -1; + resolve(); + //}); + }).then(function() { return BrowserApi.create(streamInfo, false); }); +} + +/** + * Returns a promise that will resolve to a BrowserApi instance. + * @return {Promise} A promise to a BrowserApi instance for the + * current environment. + +function createBrowserApi(streamURL, originalURL) { + //if (window.location.search) + return createBrowserApiForStandaloneExtension(); + + //return createBrowserApiForMimeHandlerView(); +} +*/ diff --git a/atom/browser/resources/pdf_viewer/elements/icons.html b/atom/browser/resources/pdf_viewer/elements/icons.html new file mode 100644 index 0000000000..3442301b92 --- /dev/null +++ b/atom/browser/resources/pdf_viewer/elements/icons.html @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + diff --git a/atom/browser/resources/pdf_viewer/elements/shared-icon-style.css b/atom/browser/resources/pdf_viewer/elements/shared-icon-style.css new file mode 100644 index 0000000000..59037556c7 --- /dev/null +++ b/atom/browser/resources/pdf_viewer/elements/shared-icon-style.css @@ -0,0 +1,15 @@ +/* Copyright 2015 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. */ + +:root { + --iron-icon-height: 20px; + --iron-icon-width: 20px; + --paper-icon-button: { + height: 32px; + padding: 6px; + width: 32px; + }; + --paper-icon-button-ink-color: rgb(189, 189, 189); + --viewer-icon-ink-color: rgb(189, 189, 189); +} diff --git a/atom/browser/resources/pdf_viewer/elements/viewer-bookmark/viewer-bookmark.css b/atom/browser/resources/pdf_viewer/elements/viewer-bookmark/viewer-bookmark.css new file mode 100644 index 0000000000..74ac2728bd --- /dev/null +++ b/atom/browser/resources/pdf_viewer/elements/viewer-bookmark/viewer-bookmark.css @@ -0,0 +1,52 @@ +/* Copyright 2015 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. */ + +#item { + @apply(--layout-center); + @apply(--layout-horizontal); + color: rgb(80, 80, 80); + cursor: pointer; + font-size: 77.8%; + height: 30px; + position: relative; +} + +#item:hover { + background-color: rgb(237, 237, 237); + color: rgb(20, 20, 20); +} + +paper-ripple { + /* Allowing the ripple to capture pointer events prevents a focus rectangle + * for showing up for clicks, while still allowing it with tab-navigation. + * This undoes a paper-ripple bugfix aimed at non-Chrome browsers. + * TODO(tsergeant): Improve focus in viewer-bookmark so this can be removed + * (https://crbug.com/5448190). */ + pointer-events: auto; +} + +#title { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +#expand { + --iron-icon-height: 16px; + --iron-icon-width: 16px; + --paper-icon-button-ink-color: var(--paper-grey-900); + height: 28px; + min-width: 28px; + padding: 6px; + transition: transform 150ms; + width: 28px; +} + +:host-context([dir=rtl]) #expand { + transform: rotate(180deg); +} + +:host([children-shown]) #expand { + transform: rotate(90deg); +} diff --git a/atom/browser/resources/pdf_viewer/elements/viewer-bookmark/viewer-bookmark.html b/atom/browser/resources/pdf_viewer/elements/viewer-bookmark/viewer-bookmark.html new file mode 100644 index 0000000000..0228cb835f --- /dev/null +++ b/atom/browser/resources/pdf_viewer/elements/viewer-bookmark/viewer-bookmark.html @@ -0,0 +1,26 @@ + + + + + + + + + + diff --git a/atom/browser/resources/pdf_viewer/elements/viewer-bookmark/viewer-bookmark.js b/atom/browser/resources/pdf_viewer/elements/viewer-bookmark/viewer-bookmark.js new file mode 100644 index 0000000000..9d6399070e --- /dev/null +++ b/atom/browser/resources/pdf_viewer/elements/viewer-bookmark/viewer-bookmark.js @@ -0,0 +1,92 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +(function() { + /** Amount that each level of bookmarks is indented by (px). */ + var BOOKMARK_INDENT = 20; + + Polymer({ + is: 'viewer-bookmark', + + properties: { + /** + * A bookmark object, each containing a: + * - title + * - page (optional) + * - children (an array of bookmarks) + */ + bookmark: { + type: Object, + observer: 'bookmarkChanged_' + }, + + depth: { + type: Number, + observer: 'depthChanged' + }, + + childDepth: Number, + + childrenShown: { + type: Boolean, + reflectToAttribute: true, + value: false + }, + + keyEventTarget: { + type: Object, + value: function() { + return this.$.item; + } + } + }, + + behaviors: [ + Polymer.IronA11yKeysBehavior + ], + + keyBindings: { + 'enter': 'onEnter_', + 'space': 'onSpace_' + }, + + bookmarkChanged_: function() { + this.$.expand.style.visibility = + this.bookmark.children.length > 0 ? 'visible' : 'hidden'; + }, + + depthChanged: function() { + this.childDepth = this.depth + 1; + this.$.item.style.webkitPaddingStart = + (this.depth * BOOKMARK_INDENT) + 'px'; + }, + + onClick: function() { + if (this.bookmark.hasOwnProperty('page')) + this.fire('change-page', {page: this.bookmark.page}); + else if (this.bookmark.hasOwnProperty('uri')) + this.fire('navigate', {uri: this.bookmark.uri, newtab: true}); + }, + + onEnter_: function(e) { + // Don't allow events which have propagated up from the expand button to + // trigger a click. + if (e.detail.keyboardEvent.target != this.$.expand) + this.onClick(); + }, + + onSpace_: function(e) { + // paper-icon-button stops propagation of space events, so there's no need + // to check the event source here. + this.onClick(); + // Prevent default space scroll behavior. + e.detail.keyboardEvent.preventDefault(); + }, + + toggleChildren: function(e) { + this.childrenShown = !this.childrenShown; + e.stopPropagation(); // Prevent the above onClick handler from firing. + } + }); +})(); diff --git a/atom/browser/resources/pdf_viewer/elements/viewer-bookmarks-content/viewer-bookmarks-content.html b/atom/browser/resources/pdf_viewer/elements/viewer-bookmarks-content/viewer-bookmarks-content.html new file mode 100644 index 0000000000..f6bfcafe55 --- /dev/null +++ b/atom/browser/resources/pdf_viewer/elements/viewer-bookmarks-content/viewer-bookmarks-content.html @@ -0,0 +1,11 @@ + + + + + + + diff --git a/atom/browser/resources/pdf_viewer/elements/viewer-bookmarks-content/viewer-bookmarks-content.js b/atom/browser/resources/pdf_viewer/elements/viewer-bookmarks-content/viewer-bookmarks-content.js new file mode 100644 index 0000000000..1aa2b5cc23 --- /dev/null +++ b/atom/browser/resources/pdf_viewer/elements/viewer-bookmarks-content/viewer-bookmarks-content.js @@ -0,0 +1,7 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +Polymer({ + is: 'viewer-bookmarks-content' +}); diff --git a/atom/browser/resources/pdf_viewer/elements/viewer-error-screen/viewer-error-screen.css b/atom/browser/resources/pdf_viewer/elements/viewer-error-screen/viewer-error-screen.css new file mode 100644 index 0000000000..e1e91bd9b3 --- /dev/null +++ b/atom/browser/resources/pdf_viewer/elements/viewer-error-screen/viewer-error-screen.css @@ -0,0 +1,7 @@ +/* Copyright 2015 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. */ + +.last-item { + margin-bottom: 24px; +} diff --git a/atom/browser/resources/pdf_viewer/elements/viewer-error-screen/viewer-error-screen.html b/atom/browser/resources/pdf_viewer/elements/viewer-error-screen/viewer-error-screen.html new file mode 100644 index 0000000000..cdadea281b --- /dev/null +++ b/atom/browser/resources/pdf_viewer/elements/viewer-error-screen/viewer-error-screen.html @@ -0,0 +1,22 @@ + + + + + + + + + + diff --git a/atom/browser/resources/pdf_viewer/elements/viewer-error-screen/viewer-error-screen.js b/atom/browser/resources/pdf_viewer/elements/viewer-error-screen/viewer-error-screen.js new file mode 100644 index 0000000000..4e461b6ad0 --- /dev/null +++ b/atom/browser/resources/pdf_viewer/elements/viewer-error-screen/viewer-error-screen.js @@ -0,0 +1,35 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +Polymer({ + is: 'viewer-error-screen', + properties: { + reloadFn: { + type: Object, + value: null, + observer: 'reloadFnChanged_' + }, + + strings: Object, + }, + + reloadFnChanged_: function() { + // The default margins in paper-dialog don't work well with hiding/showing + // the .buttons div. We need to manually manage the bottom margin to get + // around this. + if (this.reloadFn) + this.$['load-failed-message'].classList.remove('last-item'); + else + this.$['load-failed-message'].classList.add('last-item'); + }, + + show: function() { + this.$.dialog.open(); + }, + + reload: function() { + if (this.reloadFn) + this.reloadFn(); + } +}); diff --git a/atom/browser/resources/pdf_viewer/elements/viewer-page-indicator/viewer-page-indicator.css b/atom/browser/resources/pdf_viewer/elements/viewer-page-indicator/viewer-page-indicator.css new file mode 100644 index 0000000000..ec90ec99f5 --- /dev/null +++ b/atom/browser/resources/pdf_viewer/elements/viewer-page-indicator/viewer-page-indicator.css @@ -0,0 +1,35 @@ +/* Copyright 2013 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. */ + +:host { + -webkit-transition: opacity 400ms ease-in-out; + pointer-events: none; + position: fixed; + right: 0; +} + +#text { + background-color: rgba(0, 0, 0, 0.5); + border-radius: 5px; + color: white; + float: left; + font-family: sans-serif; + font-size: 12px; + font-weight: bold; + line-height: 48px; + text-align: center; + text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.8); + width: 62px; +} + +#triangle-right { + border-bottom: 6px solid transparent; + border-left: 8px solid rgba(0, 0, 0, 0.5); + border-top: 6px solid transparent; + display: inline; + float: left; + height: 0; + margin-top: 18px; + width: 0; +} \ No newline at end of file diff --git a/atom/browser/resources/pdf_viewer/elements/viewer-page-indicator/viewer-page-indicator.html b/atom/browser/resources/pdf_viewer/elements/viewer-page-indicator/viewer-page-indicator.html new file mode 100644 index 0000000000..970e0a6a4b --- /dev/null +++ b/atom/browser/resources/pdf_viewer/elements/viewer-page-indicator/viewer-page-indicator.html @@ -0,0 +1,10 @@ + + + + + + + diff --git a/atom/browser/resources/pdf_viewer/elements/viewer-page-indicator/viewer-page-indicator.js b/atom/browser/resources/pdf_viewer/elements/viewer-page-indicator/viewer-page-indicator.js new file mode 100644 index 0000000000..2cf279b68b --- /dev/null +++ b/atom/browser/resources/pdf_viewer/elements/viewer-page-indicator/viewer-page-indicator.js @@ -0,0 +1,64 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +Polymer({ + is: 'viewer-page-indicator', + + properties: { + label: { + type: String, + value: '1' + }, + + index: { + type: Number, + observer: 'indexChanged' + }, + + pageLabels: { + type: Array, + value: null, + observer: 'pageLabelsChanged' + } + }, + + timerId: undefined, + + ready: function() { + var callback = this.fadeIn.bind(this, 2000); + window.addEventListener('scroll', function() { + requestAnimationFrame(callback); + }); + }, + + initialFadeIn: function() { + this.fadeIn(6000); + }, + + fadeIn: function(displayTime) { + var percent = window.scrollY / + (document.body.scrollHeight - + document.documentElement.clientHeight); + this.style.top = percent * + (document.documentElement.clientHeight - this.offsetHeight) + 'px'; + this.style.opacity = 1; + clearTimeout(this.timerId); + + this.timerId = setTimeout(function() { + this.style.opacity = 0; + this.timerId = undefined; + }.bind(this), displayTime); + }, + + pageLabelsChanged: function() { + this.indexChanged(); + }, + + indexChanged: function() { + if (this.pageLabels) + this.label = this.pageLabels[this.index]; + else + this.label = String(this.index + 1); + } +}); diff --git a/atom/browser/resources/pdf_viewer/elements/viewer-page-selector/viewer-page-selector.css b/atom/browser/resources/pdf_viewer/elements/viewer-page-selector/viewer-page-selector.css new file mode 100644 index 0000000000..ab124ab262 --- /dev/null +++ b/atom/browser/resources/pdf_viewer/elements/viewer-page-selector/viewer-page-selector.css @@ -0,0 +1,52 @@ +/* Copyright 2015 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. */ + +:host { + color: #fff; + font-size: 94.4%; +} + +:host ::selection { + background: rgba(255, 255, 255, 0.3); +} + +#pageselector { + --paper-input-container-underline: { + visibility: hidden; + }; + --paper-input-container-underline-focus: { + visibility: hidden; + }; + display: inline-block; + padding: 0; + width: 1ch; +} + +#input { + -webkit-margin-start: -3px; + color: #fff; + line-height: 18px; + padding: 3px; + text-align: end; +} + +#input:focus, +#input:hover { + background-color: rgba(0, 0, 0, 0.5); + border-radius: 2px; +} + +#slash { + padding: 0 3px; +} + +#pagelength-spacer { + display: inline-block; + text-align: start; +} + +#slash, +#pagelength { + font-size: 76.5%; +} diff --git a/atom/browser/resources/pdf_viewer/elements/viewer-page-selector/viewer-page-selector.html b/atom/browser/resources/pdf_viewer/elements/viewer-page-selector/viewer-page-selector.html new file mode 100644 index 0000000000..bebbd75400 --- /dev/null +++ b/atom/browser/resources/pdf_viewer/elements/viewer-page-selector/viewer-page-selector.html @@ -0,0 +1,19 @@ + + + + + + + + + diff --git a/atom/browser/resources/pdf_viewer/elements/viewer-page-selector/viewer-page-selector.js b/atom/browser/resources/pdf_viewer/elements/viewer-page-selector/viewer-page-selector.js new file mode 100644 index 0000000000..ad344f60e1 --- /dev/null +++ b/atom/browser/resources/pdf_viewer/elements/viewer-page-selector/viewer-page-selector.js @@ -0,0 +1,59 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +Polymer({ + is: 'viewer-page-selector', + + properties: { + /** + * The number of pages the document contains. + */ + docLength: { + type: Number, + value: 1, + observer: 'docLengthChanged' + }, + + /** + * The current page being viewed (1-based). A change to pageNo is mirrored + * immediately to the input field. A change to the input field is not + * mirrored back until pageNoCommitted() is called and change-page is fired. + */ + pageNo: { + type: Number, + value: 1 + }, + + strings: Object, + }, + + pageNoCommitted: function() { + var page = parseInt(this.$.input.value); + + if (!isNaN(page) && page <= this.docLength && page > 0) + this.fire('change-page', {page: page - 1}); + else + this.$.input.value = this.pageNo; + this.$.input.blur(); + }, + + docLengthChanged: function() { + var numDigits = this.docLength.toString().length; + this.$.pageselector.style.width = numDigits + 'ch'; + // Set both sides of the slash to the same width, so that the layout is + // exactly centered. + this.$['pagelength-spacer'].style.width = numDigits + 'ch'; + }, + + select: function() { + this.$.input.select(); + }, + + /** + * @return {boolean} True if the selector input field is currently focused. + */ + isActive: function() { + return this.shadowRoot.activeElement == this.$.input; + } +}); diff --git a/atom/browser/resources/pdf_viewer/elements/viewer-password-screen/viewer-password-screen.html b/atom/browser/resources/pdf_viewer/elements/viewer-password-screen/viewer-password-screen.html new file mode 100644 index 0000000000..343f579f43 --- /dev/null +++ b/atom/browser/resources/pdf_viewer/elements/viewer-password-screen/viewer-password-screen.html @@ -0,0 +1,30 @@ + + + + + + + + + + + + diff --git a/atom/browser/resources/pdf_viewer/elements/viewer-password-screen/viewer-password-screen.js b/atom/browser/resources/pdf_viewer/elements/viewer-password-screen/viewer-password-screen.js new file mode 100644 index 0000000000..2ba723e995 --- /dev/null +++ b/atom/browser/resources/pdf_viewer/elements/viewer-password-screen/viewer-password-screen.js @@ -0,0 +1,57 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +Polymer({ + is: 'viewer-password-screen', + + properties: { + invalid: Boolean, + + active: { + type: Boolean, + value: false, + observer: 'activeChanged' + }, + + strings: Object, + }, + + ready: function() { + this.activeChanged(); + }, + + accept: function() { + this.active = false; + }, + + deny: function() { + this.$.password.disabled = false; + this.$.submit.disabled = false; + this.invalid = true; + this.$.password.focus(); + this.$.password.select(); + }, + + handleKey: function(e) { + if (e.keyCode == 13) + this.submit(); + }, + + submit: function() { + if (this.$.password.value.length == 0) + return; + this.$.password.disabled = true; + this.$.submit.disabled = true; + this.fire('password-submitted', {password: this.$.password.value}); + }, + + activeChanged: function() { + if (this.active) { + this.$.dialog.open(); + this.$.password.focus(); + } else { + this.$.dialog.close(); + } + } +}); diff --git a/atom/browser/resources/pdf_viewer/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.css b/atom/browser/resources/pdf_viewer/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.css new file mode 100644 index 0000000000..37e8401816 --- /dev/null +++ b/atom/browser/resources/pdf_viewer/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.css @@ -0,0 +1,92 @@ +/* Copyright 2015 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. */ + +:host ::selection { + background: rgba(255, 255, 255, 0.3); +} + +/* We introduce a wrapper aligner element to help with laying out the main + * toolbar content without changing the bottom-aligned progress bar. */ +#aligner { + @apply(--layout-horizontal); + @apply(--layout-center); + padding: 0 16px; + width: 100%; +} + +#title { + @apply(--layout-flex-5); + font-size: 77.8%; + font-weight: 500; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +#pageselector-container { + @apply(--layout-flex-1); + text-align: center; + /* The container resizes according to the width of the toolbar. On small + * screens with large numbers of pages, overflow page numbers without + * wrapping. */ + white-space: nowrap; +} + +#buttons { + @apply(--layout-flex-5); + -webkit-user-select: none; + text-align: end; +} + +paper-icon-button { + -webkit-margin-end: 12px; +} + +viewer-toolbar-dropdown { + -webkit-margin-end: 4px; +} + +paper-progress { + --paper-progress-active-color: var(--google-blue-300); + --paper-progress-container-color: transparent; + --paper-progress-height: 3px; + transition: opacity 150ms; + width: 100%; +} + +paper-toolbar { + --paper-toolbar-background: rgb(50, 54, 57); + --paper-toolbar-height: 48px; + @apply(--shadow-elevation-2dp); + color: rgb(241, 241, 241); + font-size: 1.5em; +} + +.invisible { + visibility: hidden; +} + +@media(max-width: 675px) { + #bookmarks, + #rotate-left { + display: none; + } + + #pageselector-container { + flex: 2; + } +} + +@media(max-width: 450px) { + #rotate-right { + display: none; + } +} + +@media(max-width: 400px) { + #buttons, + #pageselector-container { + display: none; + } +} diff --git a/atom/browser/resources/pdf_viewer/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.html b/atom/browser/resources/pdf_viewer/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.html new file mode 100644 index 0000000000..ca028bd050 --- /dev/null +++ b/atom/browser/resources/pdf_viewer/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.html @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/atom/browser/resources/pdf_viewer/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.js b/atom/browser/resources/pdf_viewer/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.js new file mode 100644 index 0000000000..d4e8cbd663 --- /dev/null +++ b/atom/browser/resources/pdf_viewer/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.js @@ -0,0 +1,146 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +(function() { + Polymer({ + is: 'viewer-pdf-toolbar', + + behaviors: [ + Polymer.NeonAnimationRunnerBehavior + ], + + properties: { + /** + * The current loading progress of the PDF document (0 - 100). + */ + loadProgress: { + type: Number, + observer: 'loadProgressChanged' + }, + + /** + * The title of the PDF document. + */ + docTitle: String, + + /** + * The number of the page being viewed (1-based). + */ + pageNo: Number, + + /** + * Tree of PDF bookmarks (or null if the document has no bookmarks). + */ + bookmarks: { + type: Object, + value: null + }, + + /** + * The number of pages in the PDF document. + */ + docLength: Number, + + /** + * Whether the toolbar is opened and visible. + */ + opened: { + type: Boolean, + value: true + }, + + strings: Object, + + animationConfig: { + value: function() { + return { + 'entry': { + name: 'transform-animation', + node: this, + transformFrom: 'translateY(-100%)', + transformTo: 'translateY(0%)', + timing: { + easing: 'cubic-bezier(0, 0, 0.2, 1)', + duration: 250 + } + }, + 'exit': { + name: 'slide-up-animation', + node: this, + timing: { + easing: 'cubic-bezier(0.4, 0, 1, 1)', + duration: 250 + } + } + }; + } + } + }, + + listeners: { + 'neon-animation-finish': '_onAnimationFinished' + }, + + _onAnimationFinished: function() { + this.style.transform = this.opened ? 'none' : 'translateY(-100%)'; + }, + + loadProgressChanged: function() { + if (this.loadProgress >= 100) { + this.$.pageselector.classList.toggle('invisible', false); + this.$.buttons.classList.toggle('invisible', false); + this.$.progress.style.opacity = 0; + } + }, + + hide: function() { + if (this.opened) + this.toggleVisibility(); + }, + + show: function() { + if (!this.opened) { + this.toggleVisibility(); + } + }, + + toggleVisibility: function() { + this.opened = !this.opened; + this.cancelAnimation(); + this.playAnimation(this.opened ? 'entry' : 'exit'); + }, + + selectPageNumber: function() { + this.$.pageselector.select(); + }, + + shouldKeepOpen: function() { + return this.$.bookmarks.dropdownOpen || this.loadProgress < 100 || + this.$.pageselector.isActive(); + }, + + hideDropdowns: function() { + if (this.$.bookmarks.dropdownOpen) { + this.$.bookmarks.toggleDropdown(); + return true; + } + return false; + }, + + setDropdownLowerBound: function(lowerBound) { + this.$.bookmarks.lowerBound = lowerBound; + }, + + rotateRight: function() { + this.fire('rotate-right'); + }, + + download: function() { + this.fire('save'); + }, + + print: function() { + this.fire('print'); + } + }); +})(); diff --git a/atom/browser/resources/pdf_viewer/elements/viewer-toolbar-dropdown/viewer-toolbar-dropdown.css b/atom/browser/resources/pdf_viewer/elements/viewer-toolbar-dropdown/viewer-toolbar-dropdown.css new file mode 100644 index 0000000000..5a6d55bf20 --- /dev/null +++ b/atom/browser/resources/pdf_viewer/elements/viewer-toolbar-dropdown/viewer-toolbar-dropdown.css @@ -0,0 +1,59 @@ +/* Copyright 2015 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. */ + +:host { + text-align: start; +} + +#container { + position: absolute; + /* Controls the position of the dropdown relative to the right of the screen. + * Default is aligned with the right of the toolbar buttons. + * TODO(tsergeant): Change the layout of the dropdown so this is not required. + */ + right: var(--viewer-toolbar-dropdown-right-distance, 36px); +} + +:host-context([dir=rtl]) #container { + left: var(--viewer-toolbar-dropdown-right-distance, 36px); + right: auto; +} + +paper-material { + background-color: rgb(256, 256, 256); + border-radius: 4px; + overflow-y: hidden; + padding-bottom: 2px; + width: 260px; +} + +#scroll-container { + max-height: 300px; + overflow-y: auto; + padding: 6px 0 4px 0; +} + +#icon { + cursor: pointer; + display: inline-block; +} + +:host([dropdown-open]) #icon { + background-color: rgb(25, 27, 29); + border-radius: 4px; +} + +#arrow { + -webkit-margin-start: -12px; + -webkit-padding-end: 4px; +} + +h1 { + border-bottom: 1px solid rgb(219, 219, 219); + color: rgb(33, 33, 33); + font-size: 77.8%; + font-weight: 500; + margin: 0; + padding: 14px 28px; +} diff --git a/atom/browser/resources/pdf_viewer/elements/viewer-toolbar-dropdown/viewer-toolbar-dropdown.html b/atom/browser/resources/pdf_viewer/elements/viewer-toolbar-dropdown/viewer-toolbar-dropdown.html new file mode 100644 index 0000000000..e759825c94 --- /dev/null +++ b/atom/browser/resources/pdf_viewer/elements/viewer-toolbar-dropdown/viewer-toolbar-dropdown.html @@ -0,0 +1,29 @@ + + + + + + + + + + + + + diff --git a/atom/browser/resources/pdf_viewer/elements/viewer-toolbar-dropdown/viewer-toolbar-dropdown.js b/atom/browser/resources/pdf_viewer/elements/viewer-toolbar-dropdown/viewer-toolbar-dropdown.js new file mode 100644 index 0000000000..17a04c04d7 --- /dev/null +++ b/atom/browser/resources/pdf_viewer/elements/viewer-toolbar-dropdown/viewer-toolbar-dropdown.js @@ -0,0 +1,138 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +(function() { + /** + * Size of additional padding in the inner scrollable section of the dropdown. + */ + var DROPDOWN_INNER_PADDING = 12; + + /** Size of vertical padding on the outer #dropdown element. */ + var DROPDOWN_OUTER_PADDING = 2; + + /** Minimum height of toolbar dropdowns (px). */ + var MIN_DROPDOWN_HEIGHT = 200; + + Polymer({ + is: 'viewer-toolbar-dropdown', + + properties: { + /** String to be displayed at the top of the dropdown. */ + header: String, + + /** Icon to display when the dropdown is closed. */ + closedIcon: String, + + /** Icon to display when the dropdown is open. */ + openIcon: String, + + /** True if the dropdown is currently open. */ + dropdownOpen: { + type: Boolean, + reflectToAttribute: true, + value: false + }, + + /** Toolbar icon currently being displayed. */ + dropdownIcon: { + type: String, + computed: 'computeIcon_(dropdownOpen, closedIcon, openIcon)' + }, + + /** Lowest vertical point that the dropdown should occupy (px). */ + lowerBound: { + type: Number, + observer: 'lowerBoundChanged_' + }, + + /** + * True if the max-height CSS property for the dropdown scroll container + * is valid. If false, the height will be updated the next time the + * dropdown is visible. + */ + maxHeightValid_: false, + + /** Current animation being played, or null if there is none. */ + animation_: Object + }, + + computeIcon_: function(dropdownOpen, closedIcon, openIcon) { + return dropdownOpen ? openIcon : closedIcon; + }, + + lowerBoundChanged_: function() { + this.maxHeightValid_ = false; + if (this.dropdownOpen) + this.updateMaxHeight(); + }, + + toggleDropdown: function() { + this.dropdownOpen = !this.dropdownOpen; + if (this.dropdownOpen) { + this.$.dropdown.style.display = 'block'; + if (!this.maxHeightValid_) + this.updateMaxHeight(); + } + this.cancelAnimation_(); + this.playAnimation_(this.dropdownOpen); + }, + + updateMaxHeight: function() { + var scrollContainer = this.$['scroll-container']; + var height = this.lowerBound - + scrollContainer.getBoundingClientRect().top - + DROPDOWN_INNER_PADDING; + height = Math.max(height, MIN_DROPDOWN_HEIGHT); + scrollContainer.style.maxHeight = height + 'px'; + this.maxHeightValid_ = true; + }, + + cancelAnimation_: function() { + if (this._animation) + this._animation.cancel(); + }, + + /** + * Start an animation on the dropdown. + * @param {boolean} isEntry True to play entry animation, false to play + * exit. + * @private + */ + playAnimation_: function(isEntry) { + this.animation_ = isEntry ? this.animateEntry_() : this.animateExit_(); + this.animation_.onfinish = function() { + this.animation_ = null; + if (!this.dropdownOpen) + this.$.dropdown.style.display = 'none'; + }.bind(this); + }, + + animateEntry_: function() { + var maxHeight = this.$.dropdown.getBoundingClientRect().height - + DROPDOWN_OUTER_PADDING; + + if (maxHeight < 0) + maxHeight = 0; + + var fade = new KeyframeEffect(this.$.dropdown, [ + {opacity: 0}, + {opacity: 1} + ], {duration: 150, easing: 'cubic-bezier(0, 0, 0.2, 1)'}); + var slide = new KeyframeEffect(this.$.dropdown, [ + {height: '20px', transform: 'translateY(-10px)'}, + {height: maxHeight + 'px', transform: 'translateY(0)'} + ], {duration: 250, easing: 'cubic-bezier(0, 0, 0.2, 1)'}); + + return document.timeline.play(new GroupEffect([fade, slide])); + }, + + animateExit_: function() { + return this.$.dropdown.animate([ + {transform: 'translateY(0)', opacity: 1}, + {transform: 'translateY(-5px)', opacity: 0} + ], {duration: 100, easing: 'cubic-bezier(0.4, 0, 1, 1)'}); + } + }); + +})(); diff --git a/atom/browser/resources/pdf_viewer/elements/viewer-zoom-toolbar/viewer-zoom-button.css b/atom/browser/resources/pdf_viewer/elements/viewer-zoom-toolbar/viewer-zoom-button.css new file mode 100644 index 0000000000..0245418559 --- /dev/null +++ b/atom/browser/resources/pdf_viewer/elements/viewer-zoom-toolbar/viewer-zoom-button.css @@ -0,0 +1,32 @@ +/* Copyright 2015 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. */ + +#wrapper { + transition: transform 250ms; + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); +} + +:host([closed]) #wrapper { + /* 132px roughly flips the location of the button across the right edge of the + * page. */ + transform: translateX(132px); + transition-timing-function: cubic-bezier(0.4, 0, 1, 1); +} + +:host-context([dir=rtl]):host([closed]) #wrapper { + transform: translateX(-132px); +} + +paper-fab { + --paper-fab-keyboard-focus-background: var(--viewer-icon-ink-color); + --paper-fab-mini: { + height: 36px; + padding: 8px; + width: 36px; + }; + @apply(--shadow-elevation-4dp); + background-color: rgb(242, 242, 242); + color: rgb(96, 96, 96); + overflow: visible; +} diff --git a/atom/browser/resources/pdf_viewer/elements/viewer-zoom-toolbar/viewer-zoom-button.html b/atom/browser/resources/pdf_viewer/elements/viewer-zoom-toolbar/viewer-zoom-button.html new file mode 100644 index 0000000000..8108d7d7a1 --- /dev/null +++ b/atom/browser/resources/pdf_viewer/elements/viewer-zoom-toolbar/viewer-zoom-button.html @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/atom/browser/resources/pdf_viewer/elements/viewer-zoom-toolbar/viewer-zoom-button.js b/atom/browser/resources/pdf_viewer/elements/viewer-zoom-toolbar/viewer-zoom-button.js new file mode 100644 index 0000000000..df556aced2 --- /dev/null +++ b/atom/browser/resources/pdf_viewer/elements/viewer-zoom-toolbar/viewer-zoom-button.js @@ -0,0 +1,95 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +Polymer({ + is: 'viewer-zoom-button', + + properties: { + /** + * Icons to be displayed on the FAB. Multiple icons should be separated with + * spaces, and will be cycled through every time the FAB is clicked. + */ + icons: String, + + /** + * Array version of the list of icons. Polymer does not allow array + * properties to be set from HTML, so we must use a string property and + * perform the conversion manually. + * @private + */ + icons_: { + type: Array, + value: [''], + computed: 'computeIconsArray_(icons)' + }, + + tooltips: Array, + + closed: { + type: Boolean, + reflectToAttribute: true, + value: false + }, + + delay: { + type: Number, + observer: 'delayChanged_' + }, + + /** + * Index of the icon currently being displayed. + */ + activeIndex: { + type: Number, + value: 0 + }, + + /** + * Icon currently being displayed on the FAB. + * @private + */ + visibleIcon_: { + type: String, + computed: 'computeVisibleIcon_(icons_, activeIndex)' + }, + + visibleTooltip_: { + type: String, + computed: 'computeVisibleTooltip_(tooltips, activeIndex)' + } + }, + + computeIconsArray_: function(icons) { + return icons.split(' '); + }, + + computeVisibleIcon_: function(icons, activeIndex) { + return icons[activeIndex]; + }, + + computeVisibleTooltip_: function(tooltips, activeIndex) { + return tooltips[activeIndex]; + }, + + delayChanged_: function() { + this.$.wrapper.style.transitionDelay = this.delay + 'ms'; + }, + + show: function() { + this.closed = false; + }, + + hide: function() { + this.closed = true; + }, + + fireClick: function() { + // We cannot attach an on-click to the entire viewer-zoom-button, as this + // will include clicks on the margins. Instead, proxy clicks on the FAB + // through. + this.fire('fabclick'); + + this.activeIndex = (this.activeIndex + 1) % this.icons_.length; + } +}); diff --git a/atom/browser/resources/pdf_viewer/elements/viewer-zoom-toolbar/viewer-zoom-toolbar.css b/atom/browser/resources/pdf_viewer/elements/viewer-zoom-toolbar/viewer-zoom-toolbar.css new file mode 100644 index 0000000000..c7577ef547 --- /dev/null +++ b/atom/browser/resources/pdf_viewer/elements/viewer-zoom-toolbar/viewer-zoom-toolbar.css @@ -0,0 +1,41 @@ +/* Copyright 2015 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. */ + +:host { + -webkit-user-select: none; + bottom: 0; + padding: 48px 0; + position: fixed; + right: 0; + z-index: 3; +} + +:host-context([dir=rtl]) { + left: 0; + right: auto; +} + +#zoom-buttons { + position: relative; + right: 48px; +} + +:host-context([dir=rtl]) #zoom-buttons { + left: 48px; + right: auto; +} + +viewer-zoom-button { + display: block; +} + +/* A small gap between the zoom in/zoom out buttons. */ +#zoom-out-button { + margin-top: 10px; +} + +/* A larger gap between the fit button and bottom two buttons. */ +#zoom-in-button { + margin-top: 24px; +} diff --git a/atom/browser/resources/pdf_viewer/elements/viewer-zoom-toolbar/viewer-zoom-toolbar.html b/atom/browser/resources/pdf_viewer/elements/viewer-zoom-toolbar/viewer-zoom-toolbar.html new file mode 100644 index 0000000000..ff8c21ee71 --- /dev/null +++ b/atom/browser/resources/pdf_viewer/elements/viewer-zoom-toolbar/viewer-zoom-toolbar.html @@ -0,0 +1,21 @@ + + + + + + + + + + diff --git a/atom/browser/resources/pdf_viewer/elements/viewer-zoom-toolbar/viewer-zoom-toolbar.js b/atom/browser/resources/pdf_viewer/elements/viewer-zoom-toolbar/viewer-zoom-toolbar.js new file mode 100644 index 0000000000..060178fcc6 --- /dev/null +++ b/atom/browser/resources/pdf_viewer/elements/viewer-zoom-toolbar/viewer-zoom-toolbar.js @@ -0,0 +1,75 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +(function() { + + var FIT_TO_PAGE = 0; + var FIT_TO_WIDTH = 1; + + Polymer({ + is: 'viewer-zoom-toolbar', + + properties: { + strings: { + type: Object, + observer: 'updateTooltips_' + }, + + visible_: { + type: Boolean, + value: true + } + }, + + isVisible: function() { + return this.visible_; + }, + + /** + * Change button tooltips to match any changes to localized strings. + */ + updateTooltips_: function() { + this.$['fit-button'].tooltips = [ + this.strings.tooltipFitToPage, + this.strings.tooltipFitToWidth + ]; + this.$['zoom-in-button'].tooltips = [this.strings.tooltipZoomIn]; + this.$['zoom-out-button'].tooltips = [this.strings.tooltipZoomOut]; + }, + + fitToggle: function() { + if (this.$['fit-button'].activeIndex == FIT_TO_WIDTH) + this.fire('fit-to-width'); + else + this.fire('fit-to-page'); + }, + + zoomIn: function() { + this.fire('zoom-in'); + }, + + zoomOut: function() { + this.fire('zoom-out'); + }, + + show: function() { + if (!this.visible_) { + this.visible_ = true; + this.$['fit-button'].show(); + this.$['zoom-in-button'].show(); + this.$['zoom-out-button'].show(); + } + }, + + hide: function() { + if (this.visible_) { + this.visible_ = false; + this.$['fit-button'].hide(); + this.$['zoom-in-button'].hide(); + this.$['zoom-out-button'].hide(); + } + }, + }); + +})(); diff --git a/atom/browser/resources/pdf_viewer/index.css b/atom/browser/resources/pdf_viewer/index.css new file mode 100644 index 0000000000..a443bac50e --- /dev/null +++ b/atom/browser/resources/pdf_viewer/index.css @@ -0,0 +1,50 @@ +/* Copyright 2015 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. */ + +body { + background-color: rgb(82, 86, 89); + font-family: 'Roboto', 'Noto', sans-serif; + margin: 0; +} + +viewer-page-indicator { + visibility: hidden; + z-index: 2; +} + +viewer-pdf-toolbar { + position: fixed; + width: 100%; + z-index: 4; +} + +#plugin { + height: 100%; + position: fixed; + width: 100%; + z-index: 1; +} + +#sizer { + position: absolute; + z-index: 0; +} + +@media(max-height: 250px) { + viewer-pdf-toolbar { + display: none; + } +} + +@media(max-height: 200px) { + viewer-zoom-toolbar { + display: none; + } +} + +@media(max-width: 300px) { + viewer-zoom-toolbar { + display: none; + } +} diff --git a/atom/browser/resources/pdf_viewer/index.html b/atom/browser/resources/pdf_viewer/index.html new file mode 100644 index 0000000000..e479ff7d98 --- /dev/null +++ b/atom/browser/resources/pdf_viewer/index.html @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/atom/browser/resources/pdf_viewer/main.js b/atom/browser/resources/pdf_viewer/main.js new file mode 100644 index 0000000000..9496a17568 --- /dev/null +++ b/atom/browser/resources/pdf_viewer/main.js @@ -0,0 +1,55 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +'use strict'; + +/** + * Global PDFViewer object, accessible for testing. + * @type Object + */ +var viewer; + + +//(function() { + /** + * Stores any pending messages received which should be passed to the + * PDFViewer when it is created. + * @type Array + */ + var pendingMessages = []; + + /** + * Handles events that are received prior to the PDFViewer being created. + * @param {Object} message A message event received. + */ + function handleScriptingMessage(message) { + pendingMessages.push(message); + } + + /** + * Initialize the global PDFViewer and pass any outstanding messages to it. + * @param {Object} browserApi An object providing an API to the browser. + */ + function initViewer(browserApi) { + // PDFViewer will handle any messages after it is created. + window.removeEventListener('message', handleScriptingMessage, false); + viewer = new PDFViewer(browserApi); + while (pendingMessages.length > 0) + viewer.handleScriptingMessage(pendingMessages.shift()); + } + + /** + * Entrypoint for starting the PDF viewer. This function obtains the browser + * API for the PDF and constructs a PDFViewer object with it. + */ + function main(streamURL, originalURL) { + // Set up an event listener to catch scripting messages which are sent prior + // to the PDFViewer being created. + window.addEventListener('message', handleScriptingMessage, false); + + createBrowserApi(streamURL, originalURL).then(initViewer); + }; + + //main(); +//})(); diff --git a/atom/browser/resources/pdf_viewer/navigator.js b/atom/browser/resources/pdf_viewer/navigator.js new file mode 100644 index 0000000000..611b4a91bd --- /dev/null +++ b/atom/browser/resources/pdf_viewer/navigator.js @@ -0,0 +1,211 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +'use strict'; + +/** + * Creates a new Navigator for navigating to links inside or outside the PDF. + * @param {string} originalUrl The original page URL. + * @param {Object} viewport The viewport info of the page. + * @param {Object} paramsParser The object for URL parsing. + * @param {Function} navigateInCurrentTabCallback The Callback function that + * gets called when navigation happens in the current tab. + * @param {Function} navigateInNewTabCallback The Callback function + * that gets called when navigation happens in the new tab. + */ +function Navigator(originalUrl, + viewport, + paramsParser, + navigateInCurrentTabCallback, + navigateInNewTabCallback) { + this.originalUrl_ = originalUrl; + this.viewport_ = viewport; + this.paramsParser_ = paramsParser; + this.navigateInCurrentTabCallback_ = navigateInCurrentTabCallback; + this.navigateInNewTabCallback_ = navigateInNewTabCallback; +} + +/** + * Represents options when navigating to a new url. C++ counterpart of + * the enum is in ui/base/window_open_disposition.h. This enum represents + * the only values that are passed from Plugin. + * @enum {number} + */ +Navigator.WindowOpenDisposition = { + CURRENT_TAB: 1, + NEW_FOREGROUND_TAB: 3, + NEW_BACKGROUND_TAB: 4, + NEW_WINDOW: 6, + SAVE_TO_DISK: 7 +}; + +Navigator.prototype = { + /** + * @private + * Function to navigate to the given URL. This might involve navigating + * within the PDF page or opening a new url (in the same tab or a new tab). + * @param {string} url The URL to navigate to. + * @param {number} disposition The window open disposition when + * navigating to the new URL. + */ + navigate: function(url, disposition) { + if (url.length == 0) + return; + + // If |urlFragment| starts with '#', then it's for the same URL with a + // different URL fragment. + if (url.charAt(0) == '#') { + // if '#' is already present in |originalUrl| then remove old fragment + // and add new url fragment. + var hashIndex = this.originalUrl_.search('#'); + if (hashIndex != -1) + url = this.originalUrl_.substring(0, hashIndex) + url; + else + url = this.originalUrl_ + url; + } + + // If there's no scheme, then take a guess at the scheme. + if (url.indexOf('://') == -1 && url.indexOf('mailto:') == -1) + url = this.guessUrlWithoutScheme_(url); + + if (!this.isValidUrl_(url)) + return; + + switch (disposition) { + case Navigator.WindowOpenDisposition.CURRENT_TAB: + this.paramsParser_.getViewportFromUrlParams( + url, this.onViewportReceived_.bind(this)); + break; + case Navigator.WindowOpenDisposition.NEW_BACKGROUND_TAB: + this.navigateInNewTabCallback_(url, false); + break; + case Navigator.WindowOpenDisposition.NEW_FOREGROUND_TAB: + this.navigateInNewTabCallback_(url, true); + break; + case Navigator.WindowOpenDisposition.NEW_WINDOW: + // TODO(jaepark): Shift + left clicking a link in PDF should open the + // link in a new window. See http://crbug.com/628057. + this.paramsParser_.getViewportFromUrlParams( + url, this.onViewportReceived_.bind(this)); + break; + case Navigator.WindowOpenDisposition.SAVE_TO_DISK: + // TODO(jaepark): Alt + left clicking a link in PDF should + // download the link. + this.paramsParser_.getViewportFromUrlParams( + url, this.onViewportReceived_.bind(this)); + break; + default: + break; + } + }, + + /** + * @private + * Called when the viewport position is received. + * @param {Object} viewportPosition Dictionary containing the viewport + * position. + */ + onViewportReceived_: function(viewportPosition) { + var originalUrl = this.originalUrl_; + var hashIndex = originalUrl.search('#'); + if (hashIndex != -1) + originalUrl = originalUrl.substring(0, hashIndex); + + var newUrl = viewportPosition.url; + hashIndex = newUrl.search('#'); + if (hashIndex != -1) + newUrl = newUrl.substring(0, hashIndex); + + var pageNumber = viewportPosition.page; + if (pageNumber != undefined && originalUrl == newUrl) + this.viewport_.goToPage(pageNumber); + else + this.navigateInCurrentTabCallback_(viewportPosition.url); + }, + + /** + * @private + * Checks if the URL starts with a scheme and s not just a scheme. + * @param {string} The input URL + * @return {boolean} Whether the url is valid. + */ + isValidUrl_: function(url) { + // Make sure |url| starts with a valid scheme. + if (url.indexOf('http://') != 0 && + url.indexOf('https://') != 0 && + url.indexOf('ftp://') != 0 && + url.indexOf('file://') != 0 && + url.indexOf('mailto:') != 0) { + return false; + } + + // Make sure |url| is not only a scheme. + if (url == 'http://' || + url == 'https://' || + url == 'ftp://' || + url == 'file://' || + url == 'mailto:') { + return false; + } + + return true; + }, + + /** + * @private + * Attempt to figure out what a URL is when there is no scheme. + * @param {string} The input URL + * @return {string} The URL with a scheme or the original URL if it is not + * possible to determine the scheme. + */ + guessUrlWithoutScheme_: function(url) { + // If the original URL is mailto:, that does not make sense to start with, + // and neither does adding |url| to it. + // If the original URL is not a valid URL, this cannot make a valid URL. + // In both cases, just bail out. + if (this.originalUrl_.startsWith('mailto:') || + !this.isValidUrl_(this.originalUrl_)) { + return url; + } + + // Check for absolute paths. + if (url.startsWith('/')) { + var schemeEndIndex = this.originalUrl_.indexOf('://'); + var firstSlash = this.originalUrl_.indexOf('/', schemeEndIndex + 3); + // e.g. http://www.foo.com/bar -> http://www.foo.com + var domain = firstSlash != -1 ? + this.originalUrl_.substr(0, firstSlash) : this.originalUrl_; + return domain + url; + } + + // Check for obvious relative paths. + var isRelative = false; + if (url.startsWith('.') || url.startsWith('\\')) + isRelative = true; + + // In Adobe Acrobat Reader XI, it looks as though links with less than + // 2 dot separators in the domain are considered relative links, and + // those with 2 of more are considered http URLs. e.g. + // + // www.foo.com/bar -> http + // foo.com/bar -> relative link + if (!isRelative) { + var domainSeparatorIndex = url.indexOf('/'); + var domainName = domainSeparatorIndex == -1 ? + url : url.substr(0, domainSeparatorIndex); + var domainDotCount = (domainName.match(/\./g) || []).length; + if (domainDotCount < 2) + isRelative = true; + } + + if (isRelative) { + var slashIndex = this.originalUrl_.lastIndexOf('/'); + var path = slashIndex != -1 ? + this.originalUrl_.substr(0, slashIndex) : this.originalUrl_; + return path + '/' + url; + } + + return 'http://' + url; + } +}; diff --git a/atom/browser/resources/pdf_viewer/ods-cpp.pdf b/atom/browser/resources/pdf_viewer/ods-cpp.pdf new file mode 100644 index 0000000000000000000000000000000000000000..592eb38c2c3f3aecb8486d5aceeef153630bbfdf GIT binary patch literal 1519810 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