diff --git a/.circleci/config.yml b/.circleci/config.yml index a43cb917fab..edfb1c23154 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -19,6 +19,10 @@ jobs: case ${MESSAGE} in Bump* ) echo 'export ELECTRON_RELEASE=1' >> $BASH_ENV esac + if [ -n "${RUN_RELEASE_BUILD}" ]; then + echo 'release build triggered from api' + echo 'export ELECTRON_RELEASE=1 TRIGGERED_BY_API=1' >> $BASH_ENV + fi - run: name: Bootstrap command: | @@ -52,9 +56,12 @@ jobs: - run: name: Upload distribution command: | - if [ "$ELECTRON_RELEASE" == "1" ]; then - echo 'Uploading Electron release distribution' + if [ "$ELECTRON_RELEASE" == "1" ] && [ "$TRIGGERED_BY_API" != "1" ]; then + echo 'Uploading Electron release distribution to github releases' script/upload.py + elif [ "$ELECTRON_RELEASE" == "1" ] && [ "$TRIGGERED_BY_API" == "1" ]; then + echo 'Uploading Electron release distribution to s3' + script/upload.py --upload_to_s3 else echo 'Skipping upload distribution because build is not for release' fi @@ -63,7 +70,7 @@ jobs: - image: electronbuilds/electron:0.0.3 environment: TARGET_ARCH: arm64 - resource_class: xlarge + resource_class: xlarge steps: - checkout - run: @@ -76,6 +83,10 @@ jobs: case ${MESSAGE} in Bump* ) echo 'export ELECTRON_RELEASE=1' >> $BASH_ENV esac + if [ -n "${RUN_RELEASE_BUILD}" ]; then + echo 'release build triggered from api' + echo 'export ELECTRON_RELEASE=1 TRIGGERED_BY_API=1' >> $BASH_ENV + fi - run: name: Bootstrap command: | @@ -109,9 +120,12 @@ jobs: - run: name: Upload distribution command: | - if [ "$ELECTRON_RELEASE" == "1" ]; then - echo 'Uploading Electron release distribution' + if [ "$ELECTRON_RELEASE" == "1" ] && [ "$TRIGGERED_BY_API" != "1" ]; then + echo 'Uploading Electron release distribution to github releases' script/upload.py + elif [ "$ELECTRON_RELEASE" == "1" ] && [ "$TRIGGERED_BY_API" == "1" ]; then + echo 'Uploading Electron release distribution to s3' + script/upload.py --upload_to_s3 else echo 'Skipping upload distribution because build is not for release' fi @@ -133,6 +147,10 @@ jobs: case ${MESSAGE} in Bump* ) echo 'export ELECTRON_RELEASE=1' >> $BASH_ENV esac + if [ -n "${RUN_RELEASE_BUILD}" ]; then + echo 'release build triggered from api' + echo 'export ELECTRON_RELEASE=1 TRIGGERED_BY_API=1' >> $BASH_ENV + fi - run: name: Bootstrap command: | @@ -166,9 +184,12 @@ jobs: - run: name: Upload distribution command: | - if [ "$ELECTRON_RELEASE" == "1" ]; then - echo 'Uploading Electron release distribution' + if [ "$ELECTRON_RELEASE" == "1" ] && [ "$TRIGGERED_BY_API" != "1" ]; then + echo 'Uploading Electron release distribution to github releases' script/upload.py + elif [ "$ELECTRON_RELEASE" == "1" ] && [ "$TRIGGERED_BY_API" == "1" ]; then + echo 'Uploading Electron release distribution to s3' + script/upload.py --upload_to_s3 else echo 'Skipping upload distribution because build is not for release' fi @@ -191,6 +212,10 @@ jobs: case ${MESSAGE} in Bump* ) echo 'export ELECTRON_RELEASE=1' >> $BASH_ENV esac + if [ -n "${RUN_RELEASE_BUILD}" ]; then + echo 'release build triggered from api' + echo 'export ELECTRON_RELEASE=1 TRIGGERED_BY_API=1' >> $BASH_ENV + fi - run: name: Bootstrap command: | @@ -224,9 +249,12 @@ jobs: - run: name: Upload distribution command: | - if [ "$ELECTRON_RELEASE" == "1" ]; then - echo 'Uploading Electron release distribution' + if [ "$ELECTRON_RELEASE" == "1" ] && [ "$TRIGGERED_BY_API" != "1" ]; then + echo 'Uploading Electron release distribution to github releases' script/upload.py + elif [ "$ELECTRON_RELEASE" == "1" ] && [ "$TRIGGERED_BY_API" == "1" ]; then + echo 'Uploading Electron release distribution to s3' + script/upload.py --upload_to_s3 else echo 'Skipping upload distribution because build is not for release' fi diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 00000000000..51ad1b492dc --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,44 @@ +pipeline { + agent none + stages { + stage('Build') { + parallel { + stage('electron-osx-x64') { + agent { + label 'osx' + } + steps { + sh 'script/bootstrap.py --target_arch=x64 --dev' + sh 'npm run lint' + sh 'script/build.py -c D' + sh 'script/test.py --ci --rebuild_native_modules' + } + post { + always { + cleanWs() + } + } + } + stage('electron-mas-x64') { + agent { + label 'osx' + } + environment { + MAS_BUILD = '1' + } + steps { + sh 'script/bootstrap.py --target_arch=x64 --dev' + sh 'npm run lint' + sh 'script/build.py -c D' + sh 'script/test.py --ci --rebuild_native_modules' + } + post { + always { + cleanWs() + } + } + } + } + } + } +} diff --git a/README.md b/README.md index 4ac256847b5..95e4c2ddf8f 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,6 @@ forums - [`Atom`](http://atom-slack.herokuapp.com/) channel on Slack - [`electron-ru`](https://telegram.me/electron_ru) *(Russian)* - [`electron-br`](https://electron-br.slack.com) *(Brazilian Portuguese)* -- [`electron-kr`](http://www.meetup.com/electron-kr/) *(Korean)* - [`electron-jp`](https://electron-jp.slack.com) *(Japanese)* - [`electron-tr`](http://electron-tr.herokuapp.com) *(Turkish)* - [`electron-id`](https://electron-id.slack.com) *(Indonesia)* diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index d8ce8170fb0..d878926580a 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -1073,8 +1073,17 @@ std::vector App::GetAppMetrics(v8::Isolate* isolate) { cpu_dict.Set("percentCPUUsage", process_metric.second->metrics->GetPlatformIndependentCPUUsage() / processor_count); + +#if !defined(OS_WIN) cpu_dict.Set("idleWakeupsPerSecond", process_metric.second->metrics->GetIdleWakeupsPerSecond()); +#else + // Chrome's underlying process_metrics.cc will throw a non-fatal warning + // that this method isn't implemented on Windows, so set it to 0 instead + // of calling it + cpu_dict.Set("idleWakeupsPerSecond", 0); +#endif + pid_dict.Set("cpu", cpu_dict); pid_dict.Set("pid", process_metric.second->pid); pid_dict.Set("type", diff --git a/atom/browser/api/atom_api_notification.cc b/atom/browser/api/atom_api_notification.cc index ef5b9ce1296..8aea3d7ba27 100644 --- a/atom/browser/api/atom_api_notification.cc +++ b/atom/browser/api/atom_api_notification.cc @@ -169,14 +169,22 @@ void Notification::NotificationDisplayed() { } void Notification::NotificationDestroyed() { - Emit("close"); } void Notification::NotificationClosed() { + Emit("close"); +} + +void Notification::Close() { + if (notification_) { + notification_->Dismiss(); + notification_.reset(); + } } // Showing notifications void Notification::Show() { + Close(); if (presenter_) { notification_ = presenter_->CreateNotification(this); if (notification_) { @@ -207,6 +215,7 @@ void Notification::BuildPrototype(v8::Isolate* isolate, mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate()) .MakeDestroyable() .SetMethod("show", &Notification::Show) + .SetMethod("close", &Notification::Close) .SetProperty("title", &Notification::GetTitle, &Notification::SetTitle) .SetProperty("subtitle", &Notification::GetSubtitle, &Notification::SetSubtitle) diff --git a/atom/browser/api/atom_api_notification.h b/atom/browser/api/atom_api_notification.h index d4679e7c2c9..50197350bc5 100644 --- a/atom/browser/api/atom_api_notification.h +++ b/atom/browser/api/atom_api_notification.h @@ -45,6 +45,7 @@ class Notification : public mate::TrackableObject, ~Notification() override; void Show(); + void Close(); // Prop Getters base::string16 GetTitle() const; diff --git a/atom/browser/api/atom_api_session.cc b/atom/browser/api/atom_api_session.cc index cd149f9cfde..0d1f6a71782 100644 --- a/atom/browser/api/atom_api_session.cc +++ b/atom/browser/api/atom_api_session.cc @@ -212,6 +212,7 @@ struct Converter { dict.Set("hostname", val.hostname); dict.Set("certificate", val.certificate); dict.Set("verificationResult", val.default_result); + dict.Set("errorCode", val.error_code); return dict.GetHandle(); } }; diff --git a/atom/browser/api/atom_api_system_preferences.cc b/atom/browser/api/atom_api_system_preferences.cc index 8426c4dbbdf..74d6d03ce7c 100644 --- a/atom/browser/api/atom_api_system_preferences.cc +++ b/atom/browser/api/atom_api_system_preferences.cc @@ -67,6 +67,7 @@ void SystemPreferences::BuildPrototype( &SystemPreferences::UnsubscribeLocalNotification) .SetMethod("getUserDefault", &SystemPreferences::GetUserDefault) .SetMethod("setUserDefault", &SystemPreferences::SetUserDefault) + .SetMethod("removeUserDefault", &SystemPreferences::RemoveUserDefault) .SetMethod("isSwipeTrackingFromScrollEventsEnabled", &SystemPreferences::IsSwipeTrackingFromScrollEventsEnabled) #endif diff --git a/atom/browser/api/atom_api_system_preferences.h b/atom/browser/api/atom_api_system_preferences.h index 895974e9aef..ea5daed94a5 100644 --- a/atom/browser/api/atom_api_system_preferences.h +++ b/atom/browser/api/atom_api_system_preferences.h @@ -76,6 +76,7 @@ class SystemPreferences : public mate::EventEmitter void SetUserDefault(const std::string& name, const std::string& type, mate::Arguments* args); + void RemoveUserDefault(const std::string& name); bool IsSwipeTrackingFromScrollEventsEnabled(); #endif bool IsDarkMode(); diff --git a/atom/browser/api/atom_api_system_preferences_mac.mm b/atom/browser/api/atom_api_system_preferences_mac.mm index 877ad1a1328..58e9b848e25 100644 --- a/atom/browser/api/atom_api_system_preferences_mac.mm +++ b/atom/browser/api/atom_api_system_preferences_mac.mm @@ -229,6 +229,11 @@ void SystemPreferences::SetUserDefault(const std::string& name, } } +void SystemPreferences::RemoveUserDefault(const std::string& name) { + NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; + [defaults removeObjectForKey:base::SysUTF8ToNSString(name)]; +} + bool SystemPreferences::IsDarkMode() { NSString* mode = [[NSUserDefaults standardUserDefaults] stringForKey:@"AppleInterfaceStyle"]; diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 89727cb27d0..0cfc07e65a1 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -286,12 +286,13 @@ WebContents::WebContents(v8::Isolate* isolate, request_id_(0), background_throttling_(true), enable_devtools_(true) { + const mate::Dictionary options = mate::Dictionary::CreateEmpty(isolate); if (type == REMOTE) { web_contents->SetUserAgentOverride(GetBrowserContext()->GetUserAgent()); Init(isolate); AttachAsUserData(web_contents); + InitZoomController(web_contents, options); } else { - const mate::Dictionary options = mate::Dictionary::CreateEmpty(isolate); auto session = Session::CreateFrom(isolate, GetBrowserContext()); session_.Reset(isolate, session.ToV8()); InitWithSessionAndOptions(isolate, web_contents, session, options); @@ -392,6 +393,15 @@ WebContents::WebContents(v8::Isolate* isolate, const mate::Dictionary& options) InitWithSessionAndOptions(isolate, web_contents, session, options); } +void WebContents::InitZoomController(content::WebContents* web_contents, + const mate::Dictionary& options) { + WebContentsZoomController::CreateForWebContents(web_contents); + zoom_controller_ = WebContentsZoomController::FromWebContents(web_contents); + double zoom_factor; + if (options.Get(options::kZoomFactor, &zoom_factor)) + zoom_controller_->SetDefaultZoomFactor(zoom_factor); +} + void WebContents::InitWithSessionAndOptions(v8::Isolate* isolate, content::WebContents *web_contents, mate::Handle session, @@ -409,11 +419,7 @@ void WebContents::InitWithSessionAndOptions(v8::Isolate* isolate, // 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(options::kZoomFactor, &zoom_factor)) - zoom_controller_->SetDefaultZoomFactor(zoom_factor); + InitZoomController(web_contents, options); web_contents->SetUserAgentOverride(GetBrowserContext()->GetUserAgent()); diff --git a/atom/browser/api/atom_api_web_contents.h b/atom/browser/api/atom_api_web_contents.h index 6aeab4e4c7a..7a63ce3af44 100644 --- a/atom/browser/api/atom_api_web_contents.h +++ b/atom/browser/api/atom_api_web_contents.h @@ -385,6 +385,9 @@ class WebContents : public mate::TrackableObject, // get the zoom level. void OnGetZoomLevel(IPC::Message* reply_msg); + void InitZoomController(content::WebContents* web_contents, + const mate::Dictionary& options); + v8::Global session_; v8::Global devtools_web_contents_; v8::Global debugger_; diff --git a/atom/browser/api/event_emitter.h b/atom/browser/api/event_emitter.h index ead3beddaac..f5a8025e860 100644 --- a/atom/browser/api/event_emitter.h +++ b/atom/browser/api/event_emitter.h @@ -79,8 +79,12 @@ class EventEmitter : public Wrappable { const Args&... args) { v8::Locker locker(isolate()); v8::HandleScope handle_scope(isolate()); + v8::Local wrapper = GetWrapper(); + if (wrapper.IsEmpty()) { + return false; + } v8::Local event = internal::CreateJSEvent( - isolate(), GetWrapper(), sender, message); + isolate(), wrapper, sender, message); return EmitWithEvent(name, event, args...); } diff --git a/atom/browser/api/trackable_object.h b/atom/browser/api/trackable_object.h index e632f52b84c..6fcde898a67 100644 --- a/atom/browser/api/trackable_object.h +++ b/atom/browser/api/trackable_object.h @@ -62,7 +62,10 @@ class TrackableObject : public TrackableObjectBase, public: // Mark the JS object as destroyed. void MarkDestroyed() { - Wrappable::GetWrapper()->SetAlignedPointerInInternalField(0, nullptr); + v8::Local wrapper = Wrappable::GetWrapper(); + if (!wrapper.IsEmpty()) { + wrapper->SetAlignedPointerInInternalField(0, nullptr); + } } bool IsDestroyed() { diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index 487557cd311..b22ddfb2509 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -117,6 +117,14 @@ void NativeWindow::InitFromOptions(const mate::Dictionary& options) { bool center; if (options.Get(options::kX, &x) && options.Get(options::kY, &y)) { SetPosition(gfx::Point(x, y)); + +#if defined(OS_WIN) + // FIXME(felixrieseberg): Dirty, dirty workaround for + // https://github.com/electron/electron/issues/10862 + // Somehow, we need to call `SetBounds` twice to get + // usable results. The root cause is still unknown. + SetPosition(gfx::Point(x, y)); +#endif } else if (options.Get(options::kCenter, ¢er) && center) { Center(); } diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 252df0ebb20..80554a37a6d 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -4,6 +4,7 @@ #include "atom/browser/native_window_mac.h" +#include #include #include @@ -465,7 +466,7 @@ bool ScopedDisableResize::disable_resize_ = false; @end -#if !defined(MAC_OS_X_VERSION_10_12) +#if !defined(AVAILABLE_MAC_OS_X_VERSION_10_12_AND_LATER) enum { NSWindowTabbingModeDisallowed = 2 @@ -482,7 +483,7 @@ enum { - (IBAction)toggleTabBar:(id)sender; @end -#endif // MAC_OS_X_VERSION_10_12 +#endif @interface AtomNSWindow : EventDispatchingWindow { @private diff --git a/atom/browser/net/atom_cert_verifier.cc b/atom/browser/net/atom_cert_verifier.cc index 2a2229f19d1..9cc4a613261 100644 --- a/atom/browser/net/atom_cert_verifier.cc +++ b/atom/browser/net/atom_cert_verifier.cc @@ -92,6 +92,7 @@ class CertVerifierRequest : public AtomCertVerifier::Request { std::unique_ptr request(new VerifyRequestParams()); request->hostname = params_.hostname(); request->default_result = net::ErrorToString(error); + request->error_code = error; request->certificate = params_.certificate(); BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, diff --git a/atom/browser/net/atom_cert_verifier.h b/atom/browser/net/atom_cert_verifier.h index 09fa0f2778e..76382bf4c5d 100644 --- a/atom/browser/net/atom_cert_verifier.h +++ b/atom/browser/net/atom_cert_verifier.h @@ -19,6 +19,7 @@ class CertVerifierRequest; struct VerifyRequestParams { std::string hostname; std::string default_result; + int error_code; scoped_refptr certificate; }; diff --git a/atom/browser/net/url_request_fetch_job.cc b/atom/browser/net/url_request_fetch_job.cc index ff426dd08c8..04d0ddd1e6a 100644 --- a/atom/browser/net/url_request_fetch_job.cc +++ b/atom/browser/net/url_request_fetch_job.cc @@ -258,7 +258,9 @@ void URLRequestFetchJob::OnURLFetchComplete(const net::URLFetcher* source) { HeadersCompleted(); return; } - ReadRawDataComplete(0); + if (request_->status().is_io_pending()) { + ReadRawDataComplete(0); + } } else { NotifyStartError(fetcher_->GetStatus()); } diff --git a/atom/browser/resources/mac/Info.plist b/atom/browser/resources/mac/Info.plist index 9f8c8173a91..3313eba36bb 100644 --- a/atom/browser/resources/mac/Info.plist +++ b/atom/browser/resources/mac/Info.plist @@ -17,9 +17,9 @@ CFBundleIconFile electron.icns CFBundleVersion - 1.8.1 + 1.8.2 CFBundleShortVersionString - 1.8.1 + 1.8.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 38b94f86f9b..c349f99d712 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,8,1,0 - PRODUCTVERSION 1,8,1,0 + FILEVERSION 1,8,2,2 + PRODUCTVERSION 1,8,2,2 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -74,12 +74,12 @@ BEGIN BEGIN VALUE "CompanyName", "GitHub, Inc." VALUE "FileDescription", "Electron" - VALUE "FileVersion", "1.8.1" + VALUE "FileVersion", "1.8.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.8.1" + VALUE "ProductVersion", "1.8.2" VALUE "SquirrelAwareVersion", "1" END END diff --git a/atom/browser/ui/cocoa/atom_touch_bar.h b/atom/browser/ui/cocoa/atom_touch_bar.h index 26662cf4850..d36e45898ea 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 4c5c99a2503..927c8d1572b 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -667,4 +667,40 @@ static NSString* const ImageScrubberItemIdentifier = @"scrubber.image.item"; return itemView; } +- (NSSize)scrubber:(NSScrubber *)scrubber layout:(NSScrubberFlowLayout *)layout sizeForItemAtIndex:(NSInteger)itemIndex +{ + NSInteger width = 50; + NSInteger height = 30; + NSInteger margin = 15; + NSSize defaultSize = NSMakeSize(width, height); + + std::string s_id([[scrubber identifier] UTF8String]); + if (![self hasItemWithID:s_id]) return defaultSize; + + mate::PersistentDictionary settings = settings_[s_id]; + std::vector items; + if (!settings.Get("items", &items)) return defaultSize; + + if (itemIndex >= static_cast(items.size())) return defaultSize; + + mate::PersistentDictionary item = items[itemIndex]; + std::string title; + + if (item.Get("label", &title)) { + NSSize size = NSMakeSize(CGFLOAT_MAX, CGFLOAT_MAX); + NSRect textRect = [base::SysUTF8ToNSString(title) boundingRectWithSize:size + options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading + attributes:@{ NSFontAttributeName: [NSFont systemFontOfSize:0]}]; + + width = textRect.size.width + margin; + } else { + gfx::Image image; + if (item.Get("icon", &image)) { + width = image.AsNSImage().size.width; + } + } + + return NSMakeSize(width, height); +} + @end diff --git a/atom/browser/ui/cocoa/touch_bar_forward_declarations.h b/atom/browser/ui/cocoa/touch_bar_forward_declarations.h index 6fe7c820a1c..062723bca67 100644 --- a/atom/browser/ui/cocoa/touch_bar_forward_declarations.h +++ b/atom/browser/ui/cocoa/touch_bar_forward_declarations.h @@ -15,7 +15,7 @@ @class NSTouchBar, NSTouchBarItem; @class NSScrubber, NSScrubberItemView, NSScrubberArrangedView, NSScrubberTextItemView, NSScrubberImageItemView, NSScrubberSelectionStyle; -@protocol NSTouchBarDelegate, NSScrubberDelegate, NSScrubberDataSource; +@protocol NSTouchBarDelegate, NSScrubberDelegate, NSScrubberDataSource, NSScrubberFlowLayoutDelegate, NSScrubberFlowLayout; typedef float NSTouchBarItemPriority; static const NSTouchBarItemPriority NSTouchBarItemPriorityHigh = 1000; @@ -149,6 +149,9 @@ static const NSTouchBarItemIdentifier NSTouchBarItemIdentifierOtherItemsProxy = @end +@interface NSScrubberFlowLayout: NSObject +@end + @interface NSScrubberSelectionStyle : NSObject @property(class, strong, readonly) NSScrubberSelectionStyle* outlineOverlayStyle; @@ -229,6 +232,12 @@ static const NSTouchBarItemIdentifier NSTouchBarItemIdentifierOtherItemsProxy = @end +@protocol NSScrubberFlowLayoutDelegate + +- (NSSize)scrubber:(NSScrubber *)scrubber layout:(NSScrubberFlowLayout *)layout sizeForItemAtIndex:(NSInteger)itemIndex; + +@end + #pragma clang assume_nonnull end #elif MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_12_1 diff --git a/atom/common/api/atom_api_crash_reporter.cc b/atom/common/api/atom_api_crash_reporter.cc index 0edd787e558..f435cdf030b 100644 --- a/atom/common/api/atom_api_crash_reporter.cc +++ b/atom/common/api/atom_api_crash_reporter.cc @@ -31,14 +31,26 @@ struct Converter { namespace { +// TODO(2.0) Remove void SetExtraParameter(const std::string& key, mate::Arguments* args) { std::string value; if (args->GetNext(&value)) - CrashReporter::GetInstance()->SetExtraParameter(key, value); + CrashReporter::GetInstance()->AddExtraParameter(key, value); else CrashReporter::GetInstance()->RemoveExtraParameter(key); } +void AddExtraParameter(const std::string& key, const std::string& value) { + CrashReporter::GetInstance()->AddExtraParameter(key, value); +} + +void RemoveExtraParameter(const std::string& key) { + CrashReporter::GetInstance()->RemoveExtraParameter(key); +} + +std::map GetParameters() { + return CrashReporter::GetInstance()->GetParameters(); +} void Initialize(v8::Local exports, v8::Local unused, v8::Local context, void* priv) { @@ -46,6 +58,9 @@ void Initialize(v8::Local exports, v8::Local unused, auto reporter = base::Unretained(CrashReporter::GetInstance()); dict.SetMethod("start", base::Bind(&CrashReporter::Start, reporter)); dict.SetMethod("setExtraParameter", &SetExtraParameter); + dict.SetMethod("addExtraParameter", &AddExtraParameter); + dict.SetMethod("removeExtraParameter", &RemoveExtraParameter); + dict.SetMethod("getParameters", &GetParameters); dict.SetMethod("getUploadedReports", base::Bind(&CrashReporter::GetUploadedReports, reporter)); dict.SetMethod("setUploadToServer", diff --git a/atom/common/api/atom_api_native_image.cc b/atom/common/api/atom_api_native_image.cc index 4d30a88e0d9..469a76fd7f3 100644 --- a/atom/common/api/atom_api_native_image.cc +++ b/atom/common/api/atom_api_native_image.cc @@ -541,6 +541,13 @@ mate::Handle NativeImage::CreateFromDataURL( return CreateEmpty(isolate); } +#if !defined(OS_MACOSX) +mate::Handle NativeImage::CreateFromNamedImage( + mate::Arguments* args, const std::string& name) { + return CreateEmpty(args->isolate()); +} +#endif + // static void NativeImage::BuildPrototype( v8::Isolate* isolate, v8::Local prototype) { @@ -609,6 +616,8 @@ void Initialize(v8::Local exports, v8::Local unused, dict.SetMethod("createFromBuffer", &atom::api::NativeImage::CreateFromBuffer); dict.SetMethod("createFromDataURL", &atom::api::NativeImage::CreateFromDataURL); + dict.SetMethod("createFromNamedImage", + &atom::api::NativeImage::CreateFromNamedImage); } } // namespace diff --git a/atom/common/api/atom_api_native_image.h b/atom/common/api/atom_api_native_image.h index 6ff7a0e29f3..8bdca3a1a7b 100644 --- a/atom/common/api/atom_api_native_image.h +++ b/atom/common/api/atom_api_native_image.h @@ -53,6 +53,8 @@ class NativeImage : public mate::Wrappable { mate::Arguments* args, v8::Local buffer); static mate::Handle CreateFromDataURL( v8::Isolate* isolate, const GURL& url); + static mate::Handle CreateFromNamedImage( + mate::Arguments* args, const std::string& name); static void BuildPrototype(v8::Isolate* isolate, v8::Local prototype); diff --git a/atom/common/api/atom_api_native_image_mac.mm b/atom/common/api/atom_api_native_image_mac.mm index ad72d4b1492..110a01efced 100644 --- a/atom/common/api/atom_api_native_image_mac.mm +++ b/atom/common/api/atom_api_native_image_mac.mm @@ -6,10 +6,56 @@ #import +#include "base/strings/sys_string_conversions.h" +#include "ui/gfx/color_utils.h" +#include "ui/gfx/image/image.h" +#include "ui/gfx/image/image_skia.h" +#include "ui/gfx/image/image_skia_operations.h" + namespace atom { namespace api { +NSData* bufferFromNSImage(NSImage* image) { + CGImageRef ref = [image CGImageForProposedRect:nil context:nil hints:nil]; + NSBitmapImageRep* rep = [[NSBitmapImageRep alloc] initWithCGImage:ref]; + [rep setSize:[image size]]; + return [rep representationUsingType:NSPNGFileType properties:[[NSDictionary alloc] init]]; +} + +double safeShift(double in, double def) { + if (in >= 0 || in <= 1 || in == def) return in; + return def; +} + +mate::Handle NativeImage::CreateFromNamedImage( + mate::Arguments* args, const std::string& name) { + @autoreleasepool { + std::vector hsl_shift; + NSImage* image = [NSImage imageNamed:base::SysUTF8ToNSString(name)]; + if (!image.valid) { + return CreateEmpty(args->isolate()); + } + + NSData* png_data = bufferFromNSImage(image); + + if (args->GetNext(&hsl_shift) && hsl_shift.size() == 3) { + gfx::Image gfx_image = gfx::Image::CreateFrom1xPNGBytes( + reinterpret_cast((char *) [png_data bytes]), [png_data length]); + color_utils::HSL shift = { + safeShift(hsl_shift[0], -1), + safeShift(hsl_shift[1], 0.5), + safeShift(hsl_shift[2], 0.5) + }; + png_data = bufferFromNSImage(gfx::Image( + gfx::ImageSkiaOperations::CreateHSLShiftedImage( + gfx_image.AsImageSkia(), shift)).CopyNSImage()); + } + + return CreateFromPNG(args->isolate(), (char *) [png_data bytes], [png_data length]); + } +} + void NativeImage::SetTemplateImage(bool setAsTemplate) { [image_.AsNSImage() setTemplate:setAsTemplate]; } diff --git a/atom/common/atom_version.h b/atom/common/atom_version.h index a7f2f8099e7..c80b709304a 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 8 -#define ATOM_PATCH_VERSION 1 +#define ATOM_PATCH_VERSION 2 #define ATOM_VERSION_IS_RELEASE 1 diff --git a/atom/common/crash_reporter/crash_reporter.cc b/atom/common/crash_reporter/crash_reporter.cc index d901f83fa49..88930f0d3ce 100644 --- a/atom/common/crash_reporter/crash_reporter.cc +++ b/atom/common/crash_reporter/crash_reporter.cc @@ -86,13 +86,17 @@ void CrashReporter::InitBreakpad(const std::string& product_name, void CrashReporter::SetUploadParameters() { } -void CrashReporter::SetExtraParameter(const std::string& key, +void CrashReporter::AddExtraParameter(const std::string& key, const std::string& value) { } void CrashReporter::RemoveExtraParameter(const std::string& key) { } +std::map CrashReporter::GetParameters() const { + return upload_parameters_; +} + #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 2bdcd9b02d3..99c4c9818d8 100644 --- a/atom/common/crash_reporter/crash_reporter.h +++ b/atom/common/crash_reporter/crash_reporter.h @@ -37,9 +37,10 @@ class CrashReporter { virtual void SetUploadToServer(bool upload_to_server); virtual bool GetUploadToServer(); - virtual void SetExtraParameter(const std::string& key, + virtual void AddExtraParameter(const std::string& key, const std::string& value); virtual void RemoveExtraParameter(const std::string& key); + virtual std::map GetParameters() const; protected: CrashReporter(); diff --git a/atom/common/crash_reporter/crash_reporter_mac.h b/atom/common/crash_reporter/crash_reporter_mac.h index 583c99a86e9..c1b2431af6f 100644 --- a/atom/common/crash_reporter/crash_reporter_mac.h +++ b/atom/common/crash_reporter/crash_reporter_mac.h @@ -5,6 +5,7 @@ #ifndef ATOM_COMMON_CRASH_REPORTER_CRASH_REPORTER_MAC_H_ #define ATOM_COMMON_CRASH_REPORTER_CRASH_REPORTER_MAC_H_ +#include #include #include @@ -34,9 +35,10 @@ class CrashReporterMac : public CrashReporter { void SetUploadParameters() override; void SetUploadToServer(bool upload_to_server) override; bool GetUploadToServer() override; - void SetExtraParameter(const std::string& key, + void AddExtraParameter(const std::string& key, const std::string& value) override; void RemoveExtraParameter(const std::string& key) override; + std::map GetParameters() const 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 990e1b3b195..3f82d2466fb 100644 --- a/atom/common/crash_reporter/crash_reporter_mac.mm +++ b/atom/common/crash_reporter/crash_reporter_mac.mm @@ -105,12 +105,13 @@ void CrashReporterMac::SetCrashKeyValue(const base::StringPiece& key, simple_string_dictionary_->SetKeyValue(key.data(), value.data()); } -void CrashReporterMac::SetExtraParameter(const std::string& key, +void CrashReporterMac::AddExtraParameter(const std::string& key, const std::string& value) { - if (simple_string_dictionary_) + if (simple_string_dictionary_) { SetCrashKeyValue(key, value); - else + } else { upload_parameters_[key] = value; + } } void CrashReporterMac::RemoveExtraParameter(const std::string& key) { @@ -120,6 +121,20 @@ void CrashReporterMac::RemoveExtraParameter(const std::string& key) { upload_parameters_.erase(key); } +std::map CrashReporterMac::GetParameters() const { + if (simple_string_dictionary_) { + std::map ret; + crashpad::SimpleStringDictionary::Iterator iter(*simple_string_dictionary_); + for(;;) { + const auto entry = iter.Next(); + if (!entry) break; + ret[entry->key] = entry->value; + } + return ret; + } + return upload_parameters_; +} + std::vector CrashReporterMac::GetUploadedReports(const base::FilePath& crashes_dir) { std::vector uploaded_reports; diff --git a/atom/common/platform_util_linux.cc b/atom/common/platform_util_linux.cc index 18e37558514..76cdfaf45a8 100644 --- a/atom/common/platform_util_linux.cc +++ b/atom/common/platform_util_linux.cc @@ -75,11 +75,11 @@ bool ShowItemInFolder(const base::FilePath& full_path) { if (!base::DirectoryExists(dir)) return false; - return XDGOpen(dir.value(), true); + return XDGOpen(dir.value(), false); } bool OpenItem(const base::FilePath& full_path) { - return XDGOpen(full_path.value(), true); + return XDGOpen(full_path.value(), false); } bool OpenExternal(const GURL& url, bool activate) { diff --git a/brightray/browser/browser_main_parts.cc b/brightray/browser/browser_main_parts.cc index e5406e519a6..f3ab093af06 100644 --- a/brightray/browser/browser_main_parts.cc +++ b/brightray/browser/browser_main_parts.cc @@ -166,16 +166,12 @@ BrowserMainParts::~BrowserMainParts() { #if defined(OS_WIN) || defined(OS_LINUX) void OverrideAppLogsPath() { -#if defined(OS_WIN) - std::wstring app_name = base::UTF8ToWide(GetApplicationName()); - std::wstring log_path = L"%HOMEDRIVE%%HOMEPATH%\\AppData\\Roaming\\"; - std::wstring app_log_path = log_path + app_name + L"\\logs"; -#else - std::string app_name = GetApplicationName(); - std::string home_path = std::string(getenv("HOME")); - std::string app_log_path = home_path + "/.config/" + app_name + "/logs"; -#endif - PathService::Override(DIR_APP_LOGS, base::FilePath(app_log_path)); + base::FilePath path; + if (PathService::Get(brightray::DIR_APP_DATA, &path)) { + path = path.Append(base::FilePath::FromUTF8Unsafe(GetApplicationName())); + path = path.Append(base::FilePath::FromUTF8Unsafe("logs")); + PathService::Override(DIR_APP_LOGS, path); + } } #endif diff --git a/brightray/browser/mac/cocoa_notification.mm b/brightray/browser/mac/cocoa_notification.mm index cd7b69ef405..5e51974b3e4 100644 --- a/brightray/browser/mac/cocoa_notification.mm +++ b/brightray/browser/mac/cocoa_notification.mm @@ -13,6 +13,8 @@ namespace brightray { +int g_identifier_ = 1; + CocoaNotification::CocoaNotification(NotificationDelegate* delegate, NotificationPresenter* presenter) : Notification(delegate, presenter) { @@ -29,6 +31,8 @@ void CocoaNotification::Show(const NotificationOptions& options) { [notification_ setTitle:base::SysUTF16ToNSString(options.title)]; [notification_ setSubtitle:base::SysUTF16ToNSString(options.subtitle)]; [notification_ setInformativeText:base::SysUTF16ToNSString(options.msg)]; + [notification_ setIdentifier:[NSString stringWithFormat:@"%s%d", "ElectronNotification", g_identifier_]]; + g_identifier_++; if ([notification_ respondsToSelector:@selector(setContentImage:)] && !options.icon.drawsNothing()) { diff --git a/brightray/browser/mac/notification_center_delegate.mm b/brightray/browser/mac/notification_center_delegate.mm index 834b9e5ee43..d8a8c901834 100644 --- a/brightray/browser/mac/notification_center_delegate.mm +++ b/brightray/browser/mac/notification_center_delegate.mm @@ -33,7 +33,7 @@ notification->NotificationReplied([notif.response.string UTF8String]); } else if (notif.activationType == NSUserNotificationActivationTypeActionButtonClicked) { notification->NotificationButtonClicked(); - } else { + } else if (notif.activationType == NSUserNotificationActivationTypeContentsClicked) { notification->NotificationClicked(); } } diff --git a/brightray/browser/mac/notification_presenter_mac.mm b/brightray/browser/mac/notification_presenter_mac.mm index 5afadab7ebb..b8903057346 100644 --- a/brightray/browser/mac/notification_presenter_mac.mm +++ b/brightray/browser/mac/notification_presenter_mac.mm @@ -18,7 +18,8 @@ CocoaNotification* NotificationPresenterMac::GetNotification( NSUserNotification* ns_notification) { for (Notification* notification : notifications()) { auto native_notification = static_cast(notification); - if ([native_notification->notification() isEqual:ns_notification]) + if ([native_notification->notification().identifier + isEqual:ns_notification.identifier]) return native_notification; } return nullptr; diff --git a/default_app/main.js b/default_app/main.js index 3085d4c52bf..9cd225f60f4 100644 --- a/default_app/main.js +++ b/default_app/main.js @@ -27,8 +27,7 @@ for (let i = 0; i < argv.length; i++) { } else if (argv[i] === '--default' || argv[i] === '-d') { option.default = true break - } else if (argv[i] === '--interactive' || argv[i] === '-i' || - argv[i] === '-repl' || argv[i] === '-r') { + } else if (argv[i] === '--interactive' || argv[i] === '-i' || argv[i] === '-repl') { option.interactive = true } else if (argv[i] === '--test-type=webdriver') { option.webdriver = true diff --git a/docs-translations/ko-KR/README.md b/docs-translations/ko-KR/README.md index f829e22752a..f5361dd360c 100644 --- a/docs-translations/ko-KR/README.md +++ b/docs-translations/ko-KR/README.md @@ -1,8 +1,7 @@ 반드시 사용하는 Electron 버전과 문서 버전을 일치시켜야 합니다. 버전 숫자는 문서 페이지 URL에 포함되어 있습니다. 만약 그렇지 않다면, 아마 개발 브랜치의 문서일 것이며 당신의 Electron 버전과 호환되지 않는 API 변경을 포함할 것 입니다. -이전 버전 문서는 깃허브에서 [태그로 열어] -(https://github.com/electron/electron/tree/v1.4.0) 볼 수 있습니다. +이전 버전 문서는 깃허브에서 [태그로 열어](https://github.com/electron/electron/tree/v1.4.0) 볼 수 있습니다. "branches/tags 변경" 드롭다운을 열고 해당 버전의 태그를 선택하세요. **역자주:** 한국어 번역 문서는 `atom.io`에 반영되어 있지 않습니다. 한국어 번역 diff --git a/docs-translations/ko-KR/project/CONTRIBUTING.md b/docs-translations/ko-KR/project/CONTRIBUTING.md index 8d59d72ad45..7bcd70d00f8 100644 --- a/docs-translations/ko-KR/project/CONTRIBUTING.md +++ b/docs-translations/ko-KR/project/CONTRIBUTING.md @@ -13,7 +13,7 @@ ## 이슈 제출 * [여기](https://github.com/electron/electron/issues/new)에서 새로운 이슈를 만들 수 -있습니다. 하지만 이슈를 작성하기 전에 아래의 항목들을 숙지하고 가능한한 이슈 보고에 +있습니다. 하지만 이슈를 작성하기 전에 아래의 항목들을 숙지하고 가능한 한 이슈 보고에 대해 최대한 많은 정보와 자세한 설명을 포함해야 합니다. 가능하다면 다음 항목을 포함해야 합니다: * 사용하고 있는 Electron의 버전 @@ -28,9 +28,9 @@ ## Pull Request 하기 -* 가능한한 스크린샷과 GIF 애니메이션 이미지를 pull request에 추가 +* 가능한 한 스크린샷과 GIF 애니메이션 이미지를 pull request에 추가 * JavaScript, C++과 Python등 -[참조 문서에 정의된 코딩스타일](/docs-translations/ko-KR/development/coding-style.md)을 +[참조 문서에 정의된 코딩 스타일](/docs-translations/ko-KR/development/coding-style.md)을 준수 * [문서 스타일 가이드](/docs-translations/ko-KR/styleguide.md)에 따라 문서를 [Markdown](https://daringfireball.net/projects/markdown) 형식으로 작성. diff --git a/docs-translations/ko-KR/project/README.md b/docs-translations/ko-KR/project/README.md index 23c233518d6..d9373539dbd 100644 --- a/docs-translations/ko-KR/project/README.md +++ b/docs-translations/ko-KR/project/README.md @@ -42,7 +42,7 @@ npm install electron --save-dev ## 참조 문서 [Docs](https://github.com/electron/electron/tree/master/docs-translations/ko-KR/README.md)에 -개발 지침과 API 레퍼런스가 있습니다. Electron을 빌드 하는 방법과 프로젝트에 기여하는법 +개발 지침과 API 레퍼런스가 있습니다. Electron을 빌드 하는 방법과 프로젝트에 기여하는 법 또한 문서에 포함되어 있으니 참고하시기 바랍니다. ## 참조 문서 (번역) diff --git a/docs-translations/ko-KR/styleguide.md b/docs-translations/ko-KR/styleguide.md index 055cb091856..7c449419a86 100644 --- a/docs-translations/ko-KR/styleguide.md +++ b/docs-translations/ko-KR/styleguide.md @@ -43,9 +43,9 @@ API 참고문서에는 이 규칙에 예외가 있습니다. ## 마크다운 규칙 -* 코드 블록에서 `cmd` 대신 `bash` 를 사용하세요. (문법 강조기 입니다) +* 코드 블록에서 `cmd` 대신 `bash` 를 사용해야합니다. (문법 강조기 때문입니다.) * 한줄은 영문 80 자 길이로 맞춰야 합니다. -* 2단계를 넘는 중첩 목록은 사용하지 마세요. (마크다운 렌더러 때문입니다) +* 2단계를 넘는 중첩 목록은 사용하면 안됩니다. (마크다운 렌더러 때문입니다.) * 모든 `js` 와 `javascript` 는 [표준-마크다운](http://npm.im/standard-markdown)에 의해 분석됩니다. @@ -158,8 +158,8 @@ API 참고문서에는 이 규칙에 예외가 있습니다. 제목은 `###` 또는 `####`-단계로 합니다. 모듈이냐 클래스냐에 따라 달라집니다. -모듈에서 `objectName` 은 모듕의 이름입니다. 클래스에서 그것은 클래스의 -인스턴스의 이름이며, 모듈의 이름과 달라야합니다. +모듈의 경우, `objectName` 은 모듈의 이름입니다. 클래스의 경우, 클래스의 +인스턴스 이름이어야 하며, 모듈의 이름과 달라야합니다. 예를 들어, `session` 모듈의 `Session` 클래스의 메소드는 `objectName` 으로 `ses` 를 사용해야 합니다. diff --git a/docs-translations/tr-TR/project/README.md b/docs-translations/tr-TR/project/README.md index f1998ba09bd..ea6ee9f09e8 100644 --- a/docs-translations/tr-TR/project/README.md +++ b/docs-translations/tr-TR/project/README.md @@ -17,23 +17,23 @@ Bu proje katılımcı sözleşmesine bağlıdır. Katılarak, bu kodun sürdürülebilir olduğunu üstlenmeniz beklenmekte. Lütfen uygun olmayan davranışları electron@github.com'a rapor edin. -## Downloads +## İndirme Bağlantıları Electron prebuilt mimarisini yüklemek için, [`npm`](https://docs.npmjs.com/): ```sh -# Development dependency olarak yükleyin +# Geliştirme bağımlılığı olarak yükleyin npm install electron --save-dev # `electron` komutunu global olarak $PATH'a yükleyin npm install electron -g ``` -Prebuilt mimarileri, debug sembolleri, ve fazlası için -[releases page](https://github.com/electron/electron/releases) sayfasını ziyaret edin. +Prebuilt mimarileri, debug sembolleri ve fazlası için +[releases](https://github.com/electron/electron/releases) sayfasını ziyaret edin. -### Mirrors +### Alternatif Bağlantılar - [China](https://npm.taobao.org/mirrors/electron) diff --git a/docs-translations/zh-CN/api/notification.md b/docs-translations/zh-CN/api/notification.md new file mode 100644 index 00000000000..ff00ec53e8f --- /dev/null +++ b/docs-translations/zh-CN/api/notification.md @@ -0,0 +1,109 @@ +# 通知 + +> 创建系统桌面通知 + +进程: [Main](../glossary.md#main-process) + +## 在渲染器进程中使用 + +如果你想要从渲染器进程显示通知,则应使用 [HTML5 Notification API](../tutorial/notifications.md) + +## 类: Notification + +> 创建系统桌面通知 + +进程: [Main](../glossary.md#main-process) + +`Notification` 是一个 +[EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter). + +它通过由 `options ` 设置的原生属性创建一个新的 `Notification`. + +### 静态方法 + +`Notification` 类具有以下静态方法: + +#### `Notification.isSupported()` + +返回 `Boolean` - 无论当前系统是否支持桌面通知 + +### `new Notification([options])` _实验性_ + +* `options` 对象 + * `title` 字符串 - 通知的标题,显示在通知窗口的顶部. + * `subtitle` 字符串 - (可选) 通知的副标题,将显示在标题下方. _macOS_ + * `body` 字符串 - 通知的正文,将显示在标题或副标题下方. + * `silent` 布尔 - (可选) 是否在显示通知时发出系统通知提示音. + * `icon` [NativeImage](native-image.md) - (可选) 通知所使用的图标 + * `hasReply` 布尔 - (可选) 是否在通知中添加内联的回复选项. _macOS_ + * `replyPlaceholder` 字符串 - (可选) 在内联输入字段中的提示占位符. _macOS_ + * `sound` 字符串 - (可选) 显示通知时要播放的声音文件的名称. _macOS_ + * `actions` [NotificationAction[]](structures/notification-action.md) - (可选) 添加到通知中的操作. 请阅读 `NotificationAction` 文档中的可用操作和限制. _macOS_ + + +### 实例事件 + +使用 `new Notification` 创建的对象会发出以下事件: + +**注意:** 某些事件仅在特定的操作系统上可用,请参照标签标示。 + +#### 事件: 'show' + +返回: + +* `event` 事件 + +当向用户显示通知时发出. 注意这可以被多次触发, 因为通知可以通过 `show()` 方法多次显示. + +#### 事件: 'click' + +返回: + +* `event` 事件 + +当用户点击通知时发出. + +#### 事件: 'close' + +返回: + +* `event` 事件 + +当用户手动关闭通知时发出. + +在关闭所有通知的情况下,不能保证会发送此事件. + +#### 事件: 'reply' _macOS_ + +返回: + +* `event` 事件 +* `reply` 字符串 - 用户输入到内联回复字段的字符串. + +当用户点击 `hasReply: true` 的通知上的 “回复” 按钮时发出. + +#### Event: 'action' _macOS_ + +返回: + +* `event` 事件 +* `index` Number - The index of the action that was activated + +### 实例方法 + +使用 `new Notification` 创建的对象具有以下实例方法: + +#### `notification.show()` + +立即向用户显示通知. 请注意这不同于 HTML5 Notification 的实现, 简单地实例化一个 `new Notification` 不会立即向用户显示, 你需要在操作系统显示之前调用此方法. + +### 播放声音 + +在 macOS 上, 你可以在显示通知时指定要播放的声音的名称. 除了自定义声音文件, 可以使用任意默认声音 (在 “系统偏好设置” > “声音” 下) . 同时确保声音文件被复制到应用程序包下 (例如`YourApp.app/Contents/Resources`), 或以下位置之一: + +* `~/Library/Sounds` +* `/Library/Sounds` +* `/Network/Library/Sounds` +* `/System/Library/Sounds` + +查看 [`NSSound`](https://developer.apple.com/documentation/appkit/nssound) 文档获取更多信息. diff --git a/docs-translations/zh-CN/api/structures/notification-action.md b/docs-translations/zh-CN/api/structures/notification-action.md new file mode 100644 index 00000000000..1ceb880acce --- /dev/null +++ b/docs-translations/zh-CN/api/structures/notification-action.md @@ -0,0 +1,19 @@ +# NotificationAction 对象 + +* `type` 字符串 - 动作的类型, 可以是 `button`. +* `text` 字符串 - (可选) 指定动作的标签. + +## 平台 / 动作 支持 + +| 动作类型 | 平台支持 | `text`用法 | 默认 `text` | 限制 | +|-------------|------------------|-----------------|----------------|-------------| +| `button` | macOS | 用作按钮的标签 | "Show" | 最多只有一个按钮,如果仅提供多个,则仅使用最后一个按钮。 此操作也与 `hasReply` 不兼容,如果 `hasReply` 为 `true` ,将被忽略。 | + +### macOS 上的按钮支持 + +为了让额外的通知按钮在 MacOS 上工作,您的应用程序必须符合以下条件。 + +* 应用程序已签名。 +* 在 `info.plist` 中,将应用程序的 `NSUserNotificationAlertStyle` 设为 `alert` 。 + +如果这些要求中的任何一个都不符合,按钮就不会出现。 diff --git a/docs-translations/zh-CN/styleguide.md b/docs-translations/zh-CN/styleguide.md index 74f888ace01..660776d505f 100644 --- a/docs-translations/zh-CN/styleguide.md +++ b/docs-translations/zh-CN/styleguide.md @@ -173,7 +173,7 @@ required[, optional] 如果参数或方法对某些平台是唯一的,那么这些平台将使用数据类型后面的空格分隔的斜体列表来表示。 值可以是 `macOS`,`Windows` 或 `Linux` ```markdown -* `animate` Boolean (optional) _macOS_ _Windows_ - Animate the thing. +* `animate` Boolean (optional) _macOS_ _Windows_ - 进行动画处理的事情. ``` `Array` 类型的参数, 必须在指定数组下面的描述中描述可能包含的元素. diff --git a/docs-translations/zh-CN/tutorial/notifications.md b/docs-translations/zh-CN/tutorial/notifications.md new file mode 100644 index 00000000000..41ed77820bc --- /dev/null +++ b/docs-translations/zh-CN/tutorial/notifications.md @@ -0,0 +1,57 @@ +# 通知 (Windows, Linux, macOS) + +所有三个操作系统都提供了应用程序向用户发送通知的手段。Electron允许开发者使用 [HTML5 Notification API](https://notifications.spec.whatwg.org/) 发送通知,并使用当前运行的操作系统的本地通知 API 来显示它。 + +**注意:** 由于这是一个 HTML5 API,它只能在渲染器进程中使用。 如果你想在主进程中显示通知,请查看 [Notification](../api/notification.md) 模块. + +```javascript +let myNotification = new Notification('标题', { + body: '通知正文内容' +}) + +myNotification.onclick = () => { + console.log('通知被点击') +} +``` + +虽然操作系统的代码和用户体验相似,但依然存在微妙的差异。 + +## Windows + +* 在 Windows 10 上, 通知 "仅能运行". +* 在 Windows 8.1 和 Windows 8 上, 你的应用程序的快捷方式,[Application User Model ID][app-user-model-id],必须安装到 “开始” 屏幕。但是请注意,它不需要被固定到开始屏幕。 +* 在 Windows 7 上, 通知通过视觉上类似于较新系统原生的一个自定义的实现来工作。 + +此外,在Windows 8中,通知正文的最大长度为250个字符,Windows团队建议将通知保留为200个字符。 然而,Windows 10中已经删除了这个限制,但是Windows团队要求开发人员合理使用。 尝试将大量文本发送到API(数千个字符)可能会导致不稳定。 + +### 高级通知 + +Windows 的更高版本允许高级通知,自定义模板,图像和其他灵活元素。 要发送这些通知(来自主进程或渲染器进程),请使用用户区模块 [electron-windows-notifications](https://github.com/felixrieseberg/electron-windows-notifications) 来用原生节点附件发送 `ToastNotification` 和 `TileNotification` 对象。 + +虽然包含按钮的通知只能使用 `electron-windows-notifications`,但处理回复则需要使用[`electron-windows-interactive-notifications`](https://github.com/felixrieseberg/electron-windows-interactive-notifications),这有助于注册所需的COM组件,并使用输入的用户数据调用Electron应用程序。 + +### 免打扰模式 / 演示模式 + +要检测是否允许发送通知,请使用用户区模块 [electron-notification-state](https://github.com/felixrieseberg/electron-notification-state)。 + +这样,您可以提前确定 Windows 是否会将通知忽略。 + +## macOS + +macOS上的通知是最直接的,但你应该注意 [苹果关于通知的人机接口指南](https://developer.apple.com/library/mac/documentation/UserExperience/Conceptual/OSXHIGuidelines/NotificationCenter.html). + +请注意,通知的大小限制为256个字节,如果超过该限制,则会被截断。 + +### 高级通知 + +后来的 macOS 版本允许有一个输入字段的通知,允许用户快速回复通知。为了通过输入字段发送通知,请使用用户区模块 [node-mac-notifier](https://github.com/CharlieHess/node-mac-notifier)。 + +### 勿扰 / 会话状态 + +要检测是否允许发送通知,请使用用户区模块 [electron-notification-state](https://github.com/felixrieseberg/electron-notification-state). + +这样可以提前检测是否显示通知。 + +## Linux + +通知使用 `libnotify` 发送,可以在 [Desktop Notifications Specification] [notification-spec] 之后的任何桌面环境中显示通知,包括 Cinnamon, Enlightenment, Unity, GNOME, KDE. diff --git a/docs/api/app.md b/docs/api/app.md index 6c8bdd1be8c..f7ff23e3a75 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -543,7 +543,7 @@ bar, and on macOS you can visit it from dock menu. Clears the recent documents list. -### `app.setAsDefaultProtocolClient(protocol[, path, args])` _macOS_ _Windows_ +### `app.setAsDefaultProtocolClient(protocol[, path, args])` * `protocol` String - The name of your protocol, without `://`. If you want your app to handle `electron://` links, call this method with `electron` as the diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index e2b7c3f048d..4b4a677af92 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -200,7 +200,7 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. 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, + * `backgroundColor` String (optional) - Window's background color as a 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 @@ -322,7 +322,7 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. * `defaultEncoding` String (optional) - Defaults to `ISO-8859-1`. * `backgroundThrottling` Boolean (optional) - Whether to throttle animations and timers when the page becomes background. This also affects the - [Page Visibility API][#page-visibility]. Defaults to `true`. + [Page Visibility API](#page-visibility). 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 @@ -1443,6 +1443,10 @@ removed in future Electron releases. * `browserView` [BrowserView](browser-view.md) +#### `win.getBrowserView()` _Experimental_ + +Returns `BrowserView | null` - an attached BrowserView. Returns `null` if none is attached. + **Note:** The BrowserView API is currently experimental and may change or be removed in future Electron releases. diff --git a/docs/api/crash-reporter.md b/docs/api/crash-reporter.md index 0c1c0fdec0e..46c32f5632a 100644 --- a/docs/api/crash-reporter.md +++ b/docs/api/crash-reporter.md @@ -116,18 +116,23 @@ called before `start` is called. **Note:** This API can only be called from the main process. -### `crashReporter.setExtraParameter(key, value)` _macOS_ +### `crashReporter.addExtraParameter(key, value)` _macOS_ * `key` String - Parameter key, must be less than 64 characters long. * `value` String - Parameter value, must be less than 64 characters long. - Specifying `null` or `undefined` will remove the key from the extra - parameters. Set an extra parameter to 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. +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. + +### `crashReporter.removeExtraParameter(key)` _macOS_ + +* `key` String - Parameter key, must be less than 64 characters long. + +Remove a extra parameter from the current set of parameters so that it will not be sent with the crash report. + +### `crashReporter.getParameters()` + +See all of the current parameters being passed to the crash reporter. ## Crash Report Payload diff --git a/docs/api/ipc-renderer.md b/docs/api/ipc-renderer.md index e23f3734e5e..7fed5a953c4 100644 --- a/docs/api/ipc-renderer.md +++ b/docs/api/ipc-renderer.md @@ -74,6 +74,14 @@ 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.sendTo(windowId, channel, [, arg1][, arg2][, ...])` + +* `windowId` Number +* `channel` String +* `...args` any[] + +Sends a message to a window with `windowid` via `channel` + ### `ipcRenderer.sendToHost(channel[, arg1][, arg2][, ...])` * `channel` String diff --git a/docs/api/native-image.md b/docs/api/native-image.md index 8163c233962..186df8fb1f9 100644 --- a/docs/api/native-image.md +++ b/docs/api/native-image.md @@ -157,6 +157,34 @@ Returns `NativeImage` Creates a new `NativeImage` instance from `dataURL`. +### `nativeImage.createFromNamedImage(imageName[, hslShift])` _macOS_ + +* `imageName` String +* `hslShift` Number[] + +Returns `NativeImage` + +Creates a new `NativeImage` instance from the NSImage that maps to the +given image name. See [`NSImageName`](https://developer.apple.com/documentation/appkit/nsimagename?language=objc) +for a list of possible values. + +The `hslShift` is applied to the image with the following rules +* `hsl_shift[0]` (hue): The absolute hue value for the image - 0 and 1 map + to 0 and 360 on the hue color wheel (red). +* `hsl_shift[1]` (saturation): A saturation shift for the image, with the + following key values: + 0 = remove all color. + 0.5 = leave unchanged. + 1 = fully saturate the image. +* `hsl_shift[2]` (lightness): A lightness shift for the image, with the + following key values: + 0 = remove all lightness (make all pixels black). + 0.5 = leave unchanged. + 1 = full lightness (make all pixels white). + +This means that `[-1, 0, 1]` will make the image completely white and +`[-1, 1, 0]` will make the image completely black. + ## Class: NativeImage > Natively wrap images such as tray, dock, and application icons. diff --git a/docs/api/notification.md b/docs/api/notification.md index 6dab356bbda..6c3363a9038 100644 --- a/docs/api/notification.md +++ b/docs/api/notification.md @@ -34,7 +34,7 @@ Returns `Boolean` - Whether or not desktop notifications are supported on the cu * `subtitle` String - (optional) A subtitle for the notification, which will be displayed below the title. _macOS_ * `body` String - The body text of the notification, which will be displayed below the title or subtitle * `silent` Boolean - (optional) Whether or not to emit an OS notification noise when showing the notification - * `icon` [NativeImage](native-image.md) - (optional) An icon to use in the notification + * `icon` (String | [NativeImage](native-image.md)) - (optional) An icon to use in the notification * `hasReply` Boolean - (optional) Whether or not to add an inline reply option to the notification. _macOS_ * `replyPlaceholder` String - (optional) The placeholder to write in the inline reply input field. _macOS_ * `sound` String - (optional) The name of the sound file to play when the notification is shown. _macOS_ @@ -74,7 +74,7 @@ Returns: Emitted when the notification is closed by manual intervention from the user. -This event is not guarunteed to be emitted in all cases where the notification +This event is not guaranteed to be emitted in all cases where the notification is closed. #### Event: 'reply' _macOS_ @@ -104,6 +104,13 @@ HTML5 Notification implementation, simply instantiating a `new Notification` doe not immediately show it to the user, you need to call this method before the OS will display it. +If the notification has been shown before, this method will dismiss the previously +shown notification and create a new one with identical properties. + +#### `notification.close()` + +Dismisses the notification. + ### Playing Sounds On macOS, you can specify the name of the sound you'd like to play when the diff --git a/docs/api/session.md b/docs/api/session.md index 34fd8c481d5..25ca1b3a824 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -253,7 +253,8 @@ the original network configuration. * `request` Object * `hostname` String * `certificate` [Certificate](structures/certificate.md) - * `error` String - Verification result from chromium. + * `verificationResult` String - Verification result from chromium. + * `errorCode` Integer - Error code. * `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). diff --git a/docs/api/shell.md b/docs/api/shell.md index ae30de76f8b..85c5e762609 100644 --- a/docs/api/shell.md +++ b/docs/api/shell.md @@ -36,7 +36,7 @@ Open the given file in the desktop's default manner. ### `shell.openExternal(url[, options, callback])` -* `url` String +* `url` String - max 2081 characters on windows, or the function returns false * `options` Object (optional) _macOS_ * `activate` Boolean - `true` to bring the opened application to the foreground. The default is `true`. diff --git a/docs/api/system-preferences.md b/docs/api/system-preferences.md index 54d3ea8d6a2..5ec61224617 100644 --- a/docs/api/system-preferences.md +++ b/docs/api/system-preferences.md @@ -112,9 +112,9 @@ Same as `unsubscribeNotification`, but removes the subscriber from `NSNotificati * `type` String - Can be `string`, `boolean`, `integer`, `float`, `double`, `url`, `array`, `dictionary` -Returns `any` - The value of `key` in system preferences. +Returns `any` - The value of `key` in `NSUserDefaults`. -This API uses `NSUserDefaults` on macOS. Some popular `key` and `type`s are: +Some popular `key` and `type`s are: * `AppleInterfaceStyle`: `string` * `AppleAquaColorVariant`: `integer` @@ -130,15 +130,22 @@ This API uses `NSUserDefaults` on macOS. Some popular `key` and `type`s are: * `type` String - See [`getUserDefault`][#systempreferencesgetuserdefaultkey-type-macos] * `value` String -Set the value of `key` in system preferences. +Set the value of `key` in `NSUserDefaults`. 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: +Some popular `key` and `type`s are: * `ApplePressAndHoldEnabled`: `boolean` +### `systemPreferences.removeUserDefault(key)` _macOS_ + +* `key` String + +Removes the `key` in `NSUserDefaults`. This can be used to restore the default +or global value of a `key` previously set with `setUserDefault`. + ### `systemPreferences.isAeroGlassEnabled()` _Windows_ Returns `Boolean` - `true` if [DWM composition][dwm-composition] (Aero Glass) is diff --git a/docs/api/tray.md b/docs/api/tray.md index 53efaa71201..52147bcc292 100644 --- a/docs/api/tray.md +++ b/docs/api/tray.md @@ -245,8 +245,8 @@ win.on('hide', () => { * `options` Object * `icon` ([NativeImage](native-image.md) | String) - (optional) - * `title` String - (optional) - * `content` String - (optional) + * `title` String + * `content` String Displays a tray balloon. diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index c356e8eac61..07ccefeff8e 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -581,6 +581,16 @@ that can't be set via `` attributes. **Note:** The specified `preload` script option will be appear as `preloadURL` (not `preload`) in the `webPreferences` object emitted with this event. +#### Event: 'did-attach-webview' + +Returns: + +* `event` Event +* `webContents` WebContents - The guest web contents that is used by the + ``. + +Emitted when a `` has been attached to this web contents. + ### Instance Methods #### `contents.loadURL(url[, options])` diff --git a/docs/development/build-instructions-windows.md b/docs/development/build-instructions-windows.md index 6bfd2425767..e0d9fc28072 100644 --- a/docs/development/build-instructions-windows.md +++ b/docs/development/build-instructions-windows.md @@ -6,7 +6,7 @@ Follow the guidelines below for building Electron on Windows. * Windows 7 / Server 2008 R2 or higher * Visual Studio 2015 Update 3 - [download VS 2015 Community Edition for - free](https://www.visualstudio.com/en-us/products/visual-studio-community-vs.aspx) + free](https://www.visualstudio.com/vs/older-downloads/) * [Python 2.7](http://www.python.org/download/releases/2.7/) * [Node.js](http://nodejs.org/download/) * [Git](http://git-scm.com) diff --git a/docs/development/releasing.md b/docs/development/releasing.md index aafee3291a4..a72d7d151fb 100644 --- a/docs/development/releasing.md +++ b/docs/development/releasing.md @@ -2,47 +2,51 @@ This document describes the process for releasing a new version of Electron. -## Find out what version change is needed -Is this a major, minor, patch, or beta version change? Read the [Version Change Rules](docs/tutorial/electron-versioning.md#version-change-rules) to find out. - -## Create a temporary branch +## Determine which branch to release from - **If releasing beta,** create a new branch from `master`. -- **If releasing a stable version,** create a new branch from the beta branch you're stablizing. +- **If releasing a stable version,** create a new branch from the beta branch you're stabilizing. -Name the new branch `release` or anything you like. +## Find out what version change is needed +Run `npm run prepare-release -- --notesOnly` to view auto generated release +notes. The notes generated should help you determine if this is a major, minor, +patch, or beta version change. Read the +[Version Change Rules](../tutorial/electron-versioning.md#version-change-rules) for more information. +## Run the prepare-release script +The prepare release script will do the following: +1. Check if a release is already in process and if so it will halt. +2. Create a release branch. +3. Bump the version number in several files. See [this bump commit] for an example. +4. Create a draft release on GitHub with auto-generated release notes +5. Push the release branch so that the release builds get built. +Once you have determined which type of version change is needed, run the +`prepare-release` script with arguments according to your need: +- `[major|minor|patch|beta]` to increment one of the version numbers, or +- `--stable` to indicate this is a stable version + +For example: + +### Major version change ```sh -git checkout master -git pull -git checkout -b release +npm run prepare-release -- major ``` - -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. - -## Check for extant drafts - -The upload script [looks for an existing draft release](https://github.com/electron/electron/blob/7961a97d7ddbed657c6c867cc8426e02c236c077/script/upload.py#L173-L181). To prevent your new release -from clobbering an existing draft, check [the releases page] and -make sure there are no drafts. - -## Bump the version - -Run the `bump-version` script with arguments according to your need: -- `--bump=[major|minor|patch|beta]` to increment one of the version numbers, or -- `--stable` to indicate this is a stable version, or -- `--version={version}` to set version number directly. - -**Note**: you can use both `--bump` and `--stable` simultaneously. - -There is also a `dry-run` flag you can use to make sure the version number generated is correct before committing. - +### Minor version change ```sh -npm run bump-version -- --bump=patch --stable -git push origin HEAD +npm run prepare-release -- minor +``` +### Patch version change +```sh +npm run prepare-release -- patch +``` +### Beta version change +```sh +npm run prepare-release -- beta +``` +### Promote beta to stable +```sh +npm run prepare-release -- --stable ``` - -This will bump the version number in several files. See [this bump commit] for an example. ## Wait for builds :hourglass_flowing_sand: @@ -159,65 +163,46 @@ This release is published to [npm](https://www.npmjs.com/package/electron) under 1. Uncheck the `prerelease` checkbox if you're publishing a stable release; leave it checked for beta releases. 1. Click 'Save draft'. **Do not click 'Publish release'!** 1. Wait for all builds to pass before proceeding. +1. You can run `npm run release --validateRelease` to verify that all of the +required files have been created for the release. ## Merge temporary branch +Once the release builds have finished, merge the `release` branch back into +the source release branch using the `merge-release` script. +If the branch cannot be successfully merged back this script will automatically +rebase the `release` branch and push the changes which will trigger the release +builds again, which means you will need to wait for the release builds to run +again before proceeding. -Merge the temporary branch back into master, without creating a merge commit: - +### Merging back into master ```sh -git checkout master -git merge release --no-commit -git push origin master +npm run merge-release -- master ``` -If this fails, rebase with master and rebuild: - +### Merging back into old release branch ```sh -git pull -git checkout release -git rebase master -git push origin HEAD +npm run merge-release -- 1-7-x ``` -## 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. +Once the merge has finished successfully, run the `release` script +via `npm run release` to finish the release process. This script will do the +following: +1. Build the project to validate that the correct version number is being released. +2. Download the binaries and generate the node headers and the .lib linker used +on Windows by node-gyp to build native modules. +3. Create and upload the SHASUMS files stored on S3 for the node files. +4. Create and upload the SHASUMS256.txt file stored on the GitHub release. +5. Validate that all of the required files are present on GitHub and S3 and have +the correct checksums as specified in the SHASUMS files. +6. Publish the release on GitHub +7. Delete the `release` branch. -```sh -npm run release -``` +## Publish to npm -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 -``` +Once the publish is successful, run `npm run publish-to-npm` to publish to +release to npm. [the releases page]: https://github.com/electron/electron/releases [this bump commit]: https://github.com/electron/electron/commit/78ec1b8f89b3886b856377a1756a51617bc33f5a diff --git a/docs/glossary.md b/docs/glossary.md index d1e2739e745..ee6e07883eb 100644 --- a/docs/glossary.md +++ b/docs/glossary.md @@ -91,9 +91,11 @@ free software licenses, and is a widely-used alternative to commercial proprietary products like InstallShield. [electron-builder] supports NSIS as a build target. -## OSR +### OSR -Off-screen rendering. +OSR (Off-screen rendering) can be used for loading heavy page in +background and then displaying it after (it will be much faster). +It allows you to render page without showing it on screen. ### process diff --git a/docs/tutorial/accessibility.md b/docs/tutorial/accessibility.md index d2b6099bba5..b003d794e3a 100644 --- a/docs/tutorial/accessibility.md +++ b/docs/tutorial/accessibility.md @@ -44,7 +44,7 @@ By using [`app.setAccessibilitySupportEnabled(enabled)`](https://electron.atom.i Electron application will enable accessibility automatically when it detects assistive technology (Windows) or VoiceOver (macOS). See Chrome's [accessibility documentation](https://www.chromium.org/developers/design-documents/accessibility#TOC-How-Chrome-detects-the-presence-of-Assistive-Technology) for more details. -On macOS, thrid-party assistive technology can switch accessibility inside Electron applications by setting the attribute `AXManualAccessibility` programmatically: +On macOS, third-party assistive technology can switch accessibility inside Electron applications by setting the attribute `AXManualAccessibility` programmatically: ```objc CFStringRef kAXManualAccessibility = CFSTR("AXManualAccessibility"); diff --git a/docs/tutorial/debugging-main-process.md b/docs/tutorial/debugging-main-process.md index 3b729dbddda..ba0b317d8b0 100644 --- a/docs/tutorial/debugging-main-process.md +++ b/docs/tutorial/debugging-main-process.md @@ -22,7 +22,7 @@ electron --inspect=5858 your/app ### `--inspect-brk=[port]` -Like `--inspector` but pauses execution on the first line of JavaScript. +Like `--inspect` but pauses execution on the first line of JavaScript. ## External Debuggers diff --git a/docs/tutorial/notifications.md b/docs/tutorial/notifications.md index 2ded823f917..a1f5085c933 100644 --- a/docs/tutorial/notifications.md +++ b/docs/tutorial/notifications.md @@ -85,3 +85,6 @@ Notifications are sent using `libnotify` which can show notifications on any desktop environment that follows [Desktop Notifications Specification][notification-spec], including Cinnamon, Enlightenment, Unity, GNOME, KDE. + +[notification-spec]: https://developer.gnome.org/notification-spec/ +[app-user-model-id]: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx diff --git a/docs/tutorial/online-offline-events.md b/docs/tutorial/online-offline-events.md index bc9f7ecc945..b42b9e3df31 100644 --- a/docs/tutorial/online-offline-events.md +++ b/docs/tutorial/online-offline-events.md @@ -1,7 +1,12 @@ # 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. +[Online and offline event](https://developer.mozilla.org/en-US/docs/Online_and_offline_events) detection can be implemented in the renderer process using the [`navigator.onLine`](http://html5index.org/Offline%20-%20NavigatorOnLine.html) attribute, part of standard HTML5 API. +The `navigator.onLine` attribute returns `false` if any network requests are guaranteed to fail i.e. definitely offline (disconnected from the network). It returns `true` in all other cases. +Since all other conditions return `true`, one has to be mindful of getting false positives, as we cannot assume `true` value necessarily means that Electron can access the internet. 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. + +Example: _main.js_ @@ -78,13 +83,3 @@ _online-status.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/tutorial/quick-start.md b/docs/tutorial/quick-start.md index a641a0d0c75..835a75a9775 100644 --- a/docs/tutorial/quick-start.md +++ b/docs/tutorial/quick-start.md @@ -187,6 +187,12 @@ $ ./node_modules/.bin/electron . $ .\node_modules\.bin\electron . ``` +#### Node v8.2.0 and later + +``` +$ npx electron . +``` + ### Manually Downloaded Electron Binary If you downloaded Electron manually, you can also use the included diff --git a/electron.gyp b/electron.gyp index ff7ddf6952f..aa81d930683 100644 --- a/electron.gyp +++ b/electron.gyp @@ -4,7 +4,7 @@ 'product_name%': 'Electron', 'company_name%': 'GitHub, Inc', 'company_abbr%': 'github', - 'version%': '1.8.1', + 'version%': '1.8.2-beta.2', 'js2c_input_dir': '<(SHARED_INTERMEDIATE_DIR)/js2c', }, 'includes': [ diff --git a/lib/browser/api/app.js b/lib/browser/api/app.js index d1ca60280e9..381a2a86637 100644 --- a/lib/browser/api/app.js +++ b/lib/browser/api/app.js @@ -10,7 +10,9 @@ const electron = require('electron') const {deprecate, Menu} = electron const {EventEmitter} = require('events') +// App is an EventEmitter. Object.setPrototypeOf(App.prototype, EventEmitter.prototype) +EventEmitter.call(app) Object.assign(app, { setApplicationMenu (menu) { diff --git a/lib/browser/api/auto-updater/auto-updater-native.js b/lib/browser/api/auto-updater/auto-updater-native.js index 80e2eb1b849..cdd390d0a55 100644 --- a/lib/browser/api/auto-updater/auto-updater-native.js +++ b/lib/browser/api/auto-updater/auto-updater-native.js @@ -1,6 +1,8 @@ const EventEmitter = require('events').EventEmitter const {autoUpdater, AutoUpdater} = process.atomBinding('auto_updater') +// AutoUpdater is an EventEmitter. Object.setPrototypeOf(AutoUpdater.prototype, EventEmitter.prototype) +EventEmitter.call(autoUpdater) module.exports = autoUpdater diff --git a/lib/browser/api/menu.js b/lib/browser/api/menu.js index 174acd7d919..caa65afbbf0 100644 --- a/lib/browser/api/menu.js +++ b/lib/browser/api/menu.js @@ -5,139 +5,39 @@ const EventEmitter = require('events').EventEmitter const v8Util = process.atomBinding('v8_util') const bindings = process.atomBinding('menu') -// Automatically generated radio menu item's group id. -var nextGroupId = 0 - -// Search between separators to find a radio menu item and return its group id, -// otherwise generate a group id. -var generateGroupId = function (items, pos) { - var i, item, j, k, ref1, ref2, ref3 - if (pos > 0) { - for (i = j = ref1 = pos - 1; ref1 <= 0 ? j <= 0 : j >= 0; i = ref1 <= 0 ? ++j : --j) { - item = items[i] - if (item.type === 'radio') { - return item.groupId - } - if (item.type === 'separator') { - break - } - } - } else if (pos < items.length) { - for (i = k = ref2 = pos, ref3 = items.length - 1; ref2 <= ref3 ? k <= ref3 : k >= ref3; i = ref2 <= ref3 ? ++k : --k) { - item = items[i] - if (item.type === 'radio') { - return item.groupId - } - if (item.type === 'separator') { - break - } - } - } - return ++nextGroupId -} - -// Returns the index of item according to |id|. -var indexOfItemById = function (items, id) { - var i, item, j, len - for (i = j = 0, len = items.length; j < len; i = ++j) { - item = items[i] - if (item.id === id) { - return i - } - } - return -1 -} - -// Returns the index of where to insert the item according to |position|. -var indexToInsertByPosition = function (items, position) { - var insertIndex - if (!position) { - return items.length - } - const [query, id] = position.split('=') - insertIndex = indexOfItemById(items, id) - if (insertIndex === -1 && query !== 'endof') { - console.warn("Item with id '" + id + "' is not found") - return items.length - } - switch (query) { - case 'after': - insertIndex++ - break - case 'endof': - - // If the |id| doesn't exist, then create a new group with the |id|. - if (insertIndex === -1) { - items.push({ - id: id, - type: 'separator' - }) - insertIndex = items.length - 1 - } - - // Find the end of the group. - insertIndex++ - while (insertIndex < items.length && items[insertIndex].type !== 'separator') { - insertIndex++ - } - } - return insertIndex -} - -const Menu = bindings.Menu +const {Menu} = bindings +let applicationMenu = null +let groupIdIndex = 0 Object.setPrototypeOf(Menu.prototype, EventEmitter.prototype) +/* Instance Methods */ + Menu.prototype._init = function () { this.commandsMap = {} this.groupsMap = {} this.items = [] this.delegate = { - isCommandIdChecked: (commandId) => { - var command = this.commandsMap[commandId] - return command != null ? command.checked : undefined - }, - isCommandIdEnabled: (commandId) => { - var command = this.commandsMap[commandId] - return command != null ? command.enabled : undefined - }, - isCommandIdVisible: (commandId) => { - var command = this.commandsMap[commandId] - return command != null ? command.visible : undefined - }, - getAcceleratorForCommandId: (commandId, useDefaultAccelerator) => { - const command = this.commandsMap[commandId] - if (command == null) return - if (command.accelerator != null) return command.accelerator + isCommandIdChecked: id => this.commandsMap[id] ? this.commandsMap[id].checked : undefined, + isCommandIdEnabled: id => this.commandsMap[id] ? this.commandsMap[id].enabled : undefined, + isCommandIdVisible: id => this.commandsMap[id] ? this.commandsMap[id].visible : undefined, + getAcceleratorForCommandId: (id, useDefaultAccelerator) => { + const command = this.commandsMap[id] + if (!command) return + if (command.accelerator) return command.accelerator if (useDefaultAccelerator) return command.getDefaultRoleAccelerator() }, - getIconForCommandId: (commandId) => { - var command = this.commandsMap[commandId] - return command != null ? command.icon : undefined - }, - executeCommand: (event, commandId) => { - const command = this.commandsMap[commandId] - if (command == null) return + getIconForCommandId: id => this.commandsMap[id] ? this.commandsMap[id].icon : undefined, + executeCommand: (event, id) => { + const command = this.commandsMap[id] + if (!command) return command.click(event, BrowserWindow.getFocusedWindow(), webContents.getFocusedWebContents()) }, menuWillShow: () => { - // Make sure radio groups have at least one menu item seleted. - var checked, group, id, j, len, radioItem, ref1 - ref1 = this.groupsMap - for (id in ref1) { - group = ref1[id] - checked = false - for (j = 0, len = group.length; j < len; j++) { - radioItem = group[j] - if (!radioItem.checked) { - continue - } - checked = true - break - } - if (!checked) { - v8Util.setHiddenValue(group[0], 'checked', true) - } + // Ensure radio groups have at least one menu item seleted + for (const id in this.groupsMap) { + const found = this.groupsMap[id].find(item => item.checked) || null + if (!found) v8Util.setHiddenValue(this.groupsMap[id][0], 'checked', true) } } } @@ -145,55 +45,47 @@ Menu.prototype._init = function () { Menu.prototype.popup = function (window, x, y, positioningItem) { let asyncPopup + let [newX, newY, newPosition, newWindow] = [x, y, positioningItem, window] // menu.popup(x, y, positioningItem) - if (window != null && (typeof window !== 'object' || window.constructor !== BrowserWindow)) { - // Shift. - positioningItem = y - y = x - x = window - window = null + if (!window) { + // shift over values + if (typeof window !== 'object' || window.constructor !== BrowserWindow) { + [newPosition, newY, newX, newWindow] = [y, x, window, null] + } } // menu.popup(window, {}) - if (x != null && typeof x === 'object') { - const options = x - x = options.x - y = options.y - positioningItem = options.positioningItem - asyncPopup = options.async + if (x && typeof x === 'object') { + const opts = x + newX = opts.x + newY = opts.y + newPosition = opts.positioningItem + asyncPopup = opts.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 - - // Default to not highlighting any item. - if (typeof positioningItem !== 'number') positioningItem = -1 - - // Default to synchronous for backwards compatibility. + // set defaults + if (typeof x !== 'number') newX = -1 + if (typeof y !== 'number') newY = -1 + if (typeof positioningItem !== 'number') newPosition = -1 + if (!window) newWindow = BrowserWindow.getFocusedWindow() if (typeof asyncPopup !== 'boolean') asyncPopup = false - this.popupAt(window, x, y, positioningItem, asyncPopup) + this.popupAt(newWindow, newX, newY, newPosition, asyncPopup) } Menu.prototype.closePopup = function (window) { - if (window == null || window.constructor !== BrowserWindow) { + if (!window || window.constructor !== BrowserWindow) { window = BrowserWindow.getFocusedWindow() } - if (window != null) { - this.closePopupAt(window.id) - } + if (window) this.closePopupAt(window.id) } Menu.prototype.getMenuItemById = function (id) { const items = this.items let found = items.find(item => item.id === id) || null - for (let i = 0, length = items.length; !found && i < length; i++) { + for (let i = 0; !found && i < items.length; i++) { if (items[i].submenu) { found = items[i].submenu.getMenuItemById(id) } @@ -206,61 +98,17 @@ Menu.prototype.append = function (item) { } Menu.prototype.insert = function (pos, item) { - var base, name - if ((item != null ? item.constructor : void 0) !== MenuItem) { + if ((item ? item.constructor : void 0) !== MenuItem) { throw new TypeError('Invalid item') } - switch (item.type) { - case 'normal': - this.insertItem(pos, item.commandId, item.label) - break - case 'checkbox': - this.insertCheckItem(pos, item.commandId, item.label) - break - case 'separator': - this.insertSeparator(pos) - break - case 'submenu': - this.insertSubMenu(pos, item.commandId, item.label, item.submenu) - break - case 'radio': - // Grouping radio menu items. - item.overrideReadOnlyProperty('groupId', generateGroupId(this.items, pos)) - if ((base = this.groupsMap)[name = item.groupId] == null) { - base[name] = [] - } - this.groupsMap[item.groupId].push(item) - // Setting a radio menu item should flip other items in the group. - v8Util.setHiddenValue(item, 'checked', item.checked) - Object.defineProperty(item, 'checked', { - enumerable: true, - get: function () { - return v8Util.getHiddenValue(item, 'checked') - }, - set: () => { - var j, len, otherItem, ref1 - ref1 = this.groupsMap[item.groupId] - for (j = 0, len = ref1.length; j < len; j++) { - otherItem = ref1[j] - if (otherItem !== item) { - v8Util.setHiddenValue(otherItem, 'checked', false) - } - } - return v8Util.setHiddenValue(item, 'checked', true) - } - }) - this.insertRadioItem(pos, item.commandId, item.label, item.groupId) - } - if (item.sublabel != null) { - this.setSublabel(pos, item.sublabel) - } - if (item.icon != null) { - this.setIcon(pos, item.icon) - } - if (item.role != null) { - this.setRole(pos, item.role) - } + // insert item depending on its type + insertItemByType.call(this, item, pos) + + // set item properties + if (item.sublabel) this.setSublabel(pos, item.sublabel) + if (item.icon) this.setIcon(pos, item.icon) + if (item.role) this.setRole(pos, item.role) // Make menu accessable to items. item.overrideReadOnlyProperty('menu', this) @@ -270,73 +118,150 @@ Menu.prototype.insert = function (pos, item) { this.commandsMap[item.commandId] = item } -// Force menuWillShow to be called Menu.prototype._callMenuWillShow = function () { - if (this.delegate != null) { - this.delegate.menuWillShow() - } - this.items.forEach(function (item) { - if (item.submenu != null) { - item.submenu._callMenuWillShow() - } + if (this.delegate) this.delegate.menuWillShow() + this.items.forEach(item => { + if (item.submenu) item.submenu._callMenuWillShow() }) } -var applicationMenu = null +/* Static Methods */ -Menu.setApplicationMenu = function (menu) { - if (!(menu === null || menu.constructor === Menu)) { - throw new TypeError('Invalid menu') - } - - // Keep a reference. - applicationMenu = menu - if (process.platform === 'darwin') { - if (menu === null) { - return - } - menu._callMenuWillShow() - bindings.setApplicationMenu(menu) - } else { - BrowserWindow.getAllWindows().forEach(function (window) { - window.setMenu(menu) - }) - } -} - -Menu.getApplicationMenu = function () { - return applicationMenu -} +Menu.getApplicationMenu = () => applicationMenu Menu.sendActionToFirstResponder = bindings.sendActionToFirstResponder +// set application menu with a preexisting menu +Menu.setApplicationMenu = function (menu) { + if (!(menu || menu.constructor === Menu)) { + throw new TypeError('Invalid menu') + } + + applicationMenu = menu + if (process.platform === 'darwin') { + if (!menu) return + menu._callMenuWillShow() + bindings.setApplicationMenu(menu) + } else { + const windows = BrowserWindow.getAllWindows() + return windows.map(w => w.setMenu(menu)) + } +} + Menu.buildFromTemplate = function (template) { - var insertIndex, item, j, k, len, len1, menu, menuItem, positionedTemplate - if (!Array.isArray(template)) { + if (!(template instanceof Array)) { throw new TypeError('Invalid template for Menu') } - positionedTemplate = [] - insertIndex = 0 - for (j = 0, len = template.length; j < len; j++) { - item = template[j] - if (item.position) { - insertIndex = indexToInsertByPosition(positionedTemplate, item.position) - } else { - // If no |position| is specified, insert after last item. - insertIndex++ - } - positionedTemplate.splice(insertIndex, 0, item) - } - menu = new Menu() - for (k = 0, len1 = positionedTemplate.length; k < len1; k++) { - item = positionedTemplate[k] + + const menu = new Menu() + const positioned = [] + let idx = 0 + + // sort template by position + template.forEach(item => { + idx = (item.position) ? indexToInsertByPosition(positioned, item.position) : idx += 1 + positioned.splice(idx, 0, item) + }) + + // add each item from positioned menu to application menu + positioned.forEach((item) => { if (typeof item !== 'object') { throw new TypeError('Invalid template for MenuItem') } - menuItem = new MenuItem(item) - menu.append(menuItem) - } + menu.append(new MenuItem(item)) + }) + return menu } +/* Helper Functions */ + +// Search between separators to find a radio menu item and return its group id +function generateGroupId (items, pos) { + if (pos > 0) { + for (let idx = pos - 1; idx >= 0; idx--) { + if (items[idx].type === 'radio') return items[idx].groupId + if (items[idx].type === 'separator') break + } + } else if (pos < items.length) { + for (let idx = pos; idx <= items.length - 1; idx++) { + if (items[idx].type === 'radio') return items[idx].groupId + if (items[idx].type === 'separator') break + } + } + groupIdIndex += 1 + return groupIdIndex +} + +function indexOfItemById (items, id) { + const foundItem = items.find(item => item.id === id) || -1 + return items.indexOf(foundItem) +} + +function indexToInsertByPosition (items, position) { + if (!position) return items.length + + const [query, id] = position.split('=') // parse query and id from position + const idx = indexOfItemById(items, id) // calculate initial index of item + + // warn if query doesn't exist + if (idx === -1 && query !== 'endof') { + console.warn(`Item with id ${id} is not found`) + return items.length + } + + // compute new index based on query + const queries = { + after: (index) => { + index += 1 + return index + }, + endof: (index) => { + if (index === -1) { + items.push({id, type: 'separator'}) + index = items.length - 1 + } + + index += 1 + while (index < items.length && items[index].type !== 'separator') index += 1 + return index + } + } + + // return new index if needed, or original indexOfItemById + return (query in queries) ? queries[query](idx) : idx +} + +function insertItemByType (item, pos) { + const types = { + normal: () => this.insertItem(pos, item.commandId, item.label), + checkbox: () => this.insertCheckItem(pos, item.commandId, item.label), + separator: () => this.insertSeparator(pos), + submenu: () => this.insertSubMenu(pos, item.commandId, item.label, item.submenu), + radio: () => { + // Grouping radio menu items + item.overrideReadOnlyProperty('groupId', generateGroupId(this.items, pos)) + if (this.groupsMap[item.groupId] == null) { + this.groupsMap[item.groupId] = [] + } + this.groupsMap[item.groupId].push(item) + + // Setting a radio menu item should flip other items in the group. + v8Util.setHiddenValue(item, 'checked', item.checked) + Object.defineProperty(item, 'checked', { + enumerable: true, + get: () => v8Util.getHiddenValue(item, 'checked'), + set: () => { + this.groupsMap[item.groupId].forEach(other => { + if (other !== item) v8Util.setHiddenValue(other, 'checked', false) + }) + v8Util.setHiddenValue(item, 'checked', true) + } + }) + this.insertRadioItem(pos, item.commandId, item.label, item.groupId) + } + } + types[item.type]() +} + module.exports = Menu diff --git a/lib/browser/api/net.js b/lib/browser/api/net.js index d32f6946eee..330faa82a8d 100644 --- a/lib/browser/api/net.js +++ b/lib/browser/api/net.js @@ -9,7 +9,10 @@ const {Session} = process.atomBinding('session') const {net, Net} = process.atomBinding('net') const {URLRequest} = net +// Net is an EventEmitter. Object.setPrototypeOf(Net.prototype, EventEmitter.prototype) +EventEmitter.call(net) + Object.setPrototypeOf(URLRequest.prototype, EventEmitter.prototype) const kSupportedProtocols = new Set(['http:', 'https:']) diff --git a/lib/browser/api/power-monitor.js b/lib/browser/api/power-monitor.js index aee9b450c84..e1dff2c3b73 100644 --- a/lib/browser/api/power-monitor.js +++ b/lib/browser/api/power-monitor.js @@ -1,6 +1,8 @@ const {EventEmitter} = require('events') const {powerMonitor, PowerMonitor} = process.atomBinding('power_monitor') +// PowerMonitor is an EventEmitter. Object.setPrototypeOf(PowerMonitor.prototype, EventEmitter.prototype) +EventEmitter.call(powerMonitor) module.exports = powerMonitor diff --git a/lib/browser/api/screen.js b/lib/browser/api/screen.js index 83381ab5d79..8287bfa8bfd 100644 --- a/lib/browser/api/screen.js +++ b/lib/browser/api/screen.js @@ -1,6 +1,8 @@ const {EventEmitter} = require('events') const {screen, Screen} = process.atomBinding('screen') +// Screen is an EventEmitter. Object.setPrototypeOf(Screen.prototype, EventEmitter.prototype) +EventEmitter.call(screen) module.exports = screen diff --git a/lib/browser/api/system-preferences.js b/lib/browser/api/system-preferences.js index 7d9d475bc24..e4485d3d872 100644 --- a/lib/browser/api/system-preferences.js +++ b/lib/browser/api/system-preferences.js @@ -1,6 +1,8 @@ const {EventEmitter} = require('events') const {systemPreferences, SystemPreferences} = process.atomBinding('system_preferences') +// SystemPreferences is an EventEmitter. Object.setPrototypeOf(SystemPreferences.prototype, EventEmitter.prototype) +EventEmitter.call(systemPreferences) module.exports = systemPreferences diff --git a/lib/browser/desktop-capturer.js b/lib/browser/desktop-capturer.js index da1d481c605..48a7a2ad691 100644 --- a/lib/browser/desktop-capturer.js +++ b/lib/browser/desktop-capturer.js @@ -1,23 +1,23 @@ 'use strict' -const ipcMain = require('electron').ipcMain -const desktopCapturer = process.atomBinding('desktop_capturer').desktopCapturer +const {ipcMain} = require('electron') +const {desktopCapturer} = process.atomBinding('desktop_capturer') -var deepEqual = function (opt1, opt2) { - return JSON.stringify(opt1) === JSON.stringify(opt2) -} +const deepEqual = (a, b) => JSON.stringify(a) === JSON.stringify(b) // A queue for holding all requests from renderer process. -var requestsQueue = [] +let requestsQueue = [] -ipcMain.on('ELECTRON_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', function (event, captureWindow, captureScreen, thumbnailSize, id) { - var request - request = { - id: id, +const electronSources = 'ELECTRON_BROWSER_DESKTOP_CAPTURER_GET_SOURCES' +const capturerResult = (id) => `ELECTRON_RENDERER_DESKTOP_CAPTURER_RESULT_${id}` + +ipcMain.on(electronSources, (event, captureWindow, captureScreen, thumbnailSize, id) => { + const request = { + id, options: { - captureWindow: captureWindow, - captureScreen: captureScreen, - thumbnailSize: thumbnailSize + captureWindow, + captureScreen, + thumbnailSize }, webContents: event.sender } @@ -28,45 +28,38 @@ ipcMain.on('ELECTRON_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', function (event, cap // If the WebContents is destroyed before receiving result, just remove the // reference from requestsQueue to make the module not send the result to it. - event.sender.once('destroyed', function () { + event.sender.once('destroyed', () => { request.webContents = null }) }) -desktopCapturer.emit = function (event, name, sources) { +desktopCapturer.emit = (event, name, sources) => { // Receiving sources result from main process, now send them back to renderer. - var handledRequest, i, len, ref, ref1, request, result, source, unhandledRequestsQueue - handledRequest = requestsQueue.shift(0) - result = (function () { - var i, len, results - results = [] - for (i = 0, len = sources.length; i < len; i++) { - source = sources[i] - results.push({ - id: source.id, - name: source.name, - thumbnail: source.thumbnail.toDataURL() - }) + const handledRequest = requestsQueue.shift() + const handledWebContents = handledRequest.webContents + const unhandledRequestsQueue = [] + + const result = sources.map(source => { + return { + id: source.id, + name: source.name, + thumbnail: source.thumbnail.toDataURL() } - return results - })() - if ((ref = handledRequest.webContents) != null) { - ref.send('ELECTRON_RENDERER_DESKTOP_CAPTURER_RESULT_' + handledRequest.id, result) + }) + + if (handledWebContents) { + handledWebContents.send(capturerResult(handledRequest.id), result) } - // Check the queue to see whether there is other same request. If has, handle - // it for reducing redunplicated `desktopCaptuer.startHandling` calls. - unhandledRequestsQueue = [] - for (i = 0, len = requestsQueue.length; i < len; i++) { - request = requestsQueue[i] + // Check the queue to see whether there is another identical request & handle + requestsQueue.forEach(request => { + const webContents = request.webContents if (deepEqual(handledRequest.options, request.options)) { - if ((ref1 = request.webContents) != null) { - ref1.send('ELECTRON_RENDERER_DESKTOP_CAPTURER_RESULT_' + request.id, result) - } + if (webContents) webContents.send(capturerResult(request.id), result) } else { unhandledRequestsQueue.push(request) } - } + }) requestsQueue = unhandledRequestsQueue // If the requestsQueue is not empty, start a new request handling. diff --git a/lib/browser/guest-view-manager.js b/lib/browser/guest-view-manager.js index 731f82838af..0338b776f89 100644 --- a/lib/browser/guest-view-manager.js +++ b/lib/browser/guest-view-manager.js @@ -76,7 +76,7 @@ const createGuest = function (embedder, params) { watchEmbedder(embedder) // Init guest web view after attached. - guest.on('did-attach', function () { + guest.on('did-attach', function (event) { params = this.attachParams delete this.attachParams @@ -114,6 +114,7 @@ const createGuest = function (embedder, params) { this.loadURL(params.src, opts) } guest.allowPopups = params.allowpopups + embedder.emit('did-attach-webview', event, guest) }) const sendToEmbedder = (channel, ...args) => { diff --git a/lib/common/api/crash-reporter.js b/lib/common/api/crash-reporter.js index 7a54e24fbc2..219ae904276 100644 --- a/lib/common/api/crash-reporter.js +++ b/lib/common/api/crash-reporter.js @@ -9,36 +9,26 @@ const binding = process.atomBinding('crash_reporter') class CrashReporter { start (options) { - if (options == null) { - options = {} - } + if (options == null) options = {} this.productName = options.productName != null ? options.productName : app.getName() - let {companyName, extra, ignoreSystemCrashHandler, submitURL, uploadToServer} = options - if (uploadToServer == null) { - // TODO: Remove deprecated autoSubmit property in 2.0 - uploadToServer = options.autoSubmit - } + let { + companyName, + extra, + ignoreSystemCrashHandler, + submitURL, + uploadToServer + } = options - if (uploadToServer == null) { - uploadToServer = true - } + if (uploadToServer == null) uploadToServer = options.autoSubmit + if (uploadToServer == null) uploadToServer = true + if (ignoreSystemCrashHandler == null) ignoreSystemCrashHandler = false + if (extra == null) extra = {} + + if (extra._productName == null) extra._productName = this.getProductName() + if (extra._companyName == null) extra._companyName = companyName + if (extra._version == null) extra._version = app.getVersion() - if (ignoreSystemCrashHandler == null) { - ignoreSystemCrashHandler = false - } - if (extra == null) { - extra = {} - } - if (extra._productName == null) { - extra._productName = this.getProductName() - } - if (extra._companyName == null) { - extra._companyName = companyName - } - if (extra._version == null) { - extra._version = app.getVersion() - } if (companyName == null) { throw new Error('companyName is a required option to crashReporter.start') } @@ -47,15 +37,16 @@ class CrashReporter { } if (process.platform === 'win32') { + const env = { + ELECTRON_INTERNAL_CRASH_SERVICE: 1 + } const args = [ '--reporter-url=' + submitURL, '--application-name=' + this.getProductName(), '--crashes-directory=' + this.getCrashesDirectory(), '--v=1' ] - const env = { - ELECTRON_INTERNAL_CRASH_SERVICE: 1 - } + this._crashServiceProcess = spawn(process.execPath, args, { env: env, detached: true @@ -67,11 +58,7 @@ class CrashReporter { getLastCrashReport () { const reports = this.getUploadedReports() - if (reports.length > 0) { - return reports[0] - } else { - return null - } + return (reports.length > 0) ? reports[0] : null } getUploadedReports () { @@ -79,7 +66,7 @@ class CrashReporter { } getCrashesDirectory () { - const crashesDir = this.getProductName() + ' Crashes' + const crashesDir = `${this.getProductName()} Crashes` return path.join(this.getTempDirectory(), crashesDir) } @@ -95,7 +82,6 @@ class CrashReporter { try { this.tempDirectory = app.getPath('temp') } catch (error) { - // app.getPath may throw so fallback to OS temp directory this.tempDirectory = os.tmpdir() } } @@ -118,9 +104,30 @@ class CrashReporter { } } + // TODO(2.0) Remove setExtraParameter (key, value) { + // TODO(alexeykuzmin): Warning disabled since it caused + // a couple of Crash Reported tests to time out on Mac. Add it back. + // https://github.com/electron/electron/issues/11012 + + // if (!process.noDeprecations) { + // deprecate.warn('crashReporter.setExtraParameter', + // 'crashReporter.addExtraParameter or crashReporter.removeExtraParameter') + // } binding.setExtraParameter(key, value) } + + addExtraParameter (key, value) { + binding.addExtraParameter(key, value) + } + + removeExtraParameter (key) { + binding.removeExtraParameter(key) + } + + getParameters (key, value) { + return binding.getParameters() + } } module.exports = new CrashReporter() diff --git a/lib/renderer/api/desktop-capturer.js b/lib/renderer/api/desktop-capturer.js index e998c786a59..b9320c893fc 100644 --- a/lib/renderer/api/desktop-capturer.js +++ b/lib/renderer/api/desktop-capturer.js @@ -1,46 +1,43 @@ -const ipcRenderer = require('electron').ipcRenderer -const nativeImage = require('electron').nativeImage +const {ipcRenderer, nativeImage} = require('electron') -var nextId = 0 -var includes = [].includes +const includes = [].includes +let currentId = 0 -var getNextId = function () { - return ++nextId +const incrementId = () => { + currentId += 1 + return currentId } -// |options.type| can not be empty and has to include 'window' or 'screen'. -var isValid = function (options) { - return ((options != null ? options.types : void 0) != null) && Array.isArray(options.types) +// |options.types| can't be empty and must be an array +function isValid (options) { + const types = options ? options.types : undefined + return Array.isArray(types) } exports.getSources = function (options, callback) { - var captureScreen, captureWindow, id - if (!isValid(options)) { - return callback(new Error('Invalid options')) - } - captureWindow = includes.call(options.types, 'window') - captureScreen = includes.call(options.types, 'screen') + if (!isValid(options)) return callback(new Error('Invalid options')) + const captureWindow = includes.call(options.types, 'window') + const captureScreen = includes.call(options.types, 'screen') + if (options.thumbnailSize == null) { options.thumbnailSize = { width: 150, height: 150 } } - id = getNextId() + + const id = incrementId() ipcRenderer.send('ELECTRON_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', captureWindow, captureScreen, options.thumbnailSize, id) - return ipcRenderer.once('ELECTRON_RENDERER_DESKTOP_CAPTURER_RESULT_' + id, function (event, sources) { - var source - callback(null, (function () { - var i, len, results - results = [] - for (i = 0, len = sources.length; i < len; i++) { - source = sources[i] + return ipcRenderer.once(`ELECTRON_RENDERER_DESKTOP_CAPTURER_RESULT_${id}`, (event, sources) => { + callback(null, (() => { + const results = [] + sources.forEach(source => { results.push({ id: source.id, name: source.name, thumbnail: nativeImage.createFromDataURL(source.thumbnail) }) - } + }) return results })()) }) diff --git a/lib/renderer/api/remote.js b/lib/renderer/api/remote.js index fdc95cdb5cd..a4f8072ed57 100644 --- a/lib/renderer/api/remote.js +++ b/lib/renderer/api/remote.js @@ -9,16 +9,11 @@ const {ipcRenderer, isPromise, CallbacksRegistry} = require('electron') const resolvePromise = Promise.resolve.bind(Promise) const callbacksRegistry = new CallbacksRegistry() - const remoteObjectCache = v8Util.createIDWeakMap() // Convert the arguments object into an array of meta data. -const wrapArgs = function (args, visited) { - if (visited == null) { - visited = new Set() - } - - const valueToMeta = function (value) { +function wrapArgs (args, visited = new Set()) { + const valueToMeta = (value) => { // Check for circular reference. if (visited.has(value)) { return { @@ -62,7 +57,7 @@ const wrapArgs = function (args, visited) { let meta = { type: 'object', - name: value.constructor != null ? value.constructor.name : '', + name: value.constructor ? value.constructor.name : '', members: [] } visited.add(value) @@ -99,7 +94,7 @@ const wrapArgs = function (args, visited) { // Populate object's members from descriptors. // The |ref| will be kept referenced by |members|. // This matches |getObjectMemebers| in rpc-server. -const setObjectMembers = function (ref, object, metaId, members) { +function setObjectMembers (ref, object, metaId, members) { if (!Array.isArray(members)) return for (let member of members) { @@ -108,44 +103,41 @@ const setObjectMembers = function (ref, object, metaId, members) { let descriptor = { enumerable: member.enumerable } if (member.type === 'method') { const remoteMemberFunction = function (...args) { + let command if (this && this.constructor === remoteMemberFunction) { - // Constructor call. - let ret = ipcRenderer.sendSync('ELECTRON_BROWSER_MEMBER_CONSTRUCTOR', metaId, member.name, wrapArgs(args)) - return metaToValue(ret) + command = 'ELECTRON_BROWSER_MEMBER_CONSTRUCTOR' } else { - // Call member function. - let ret = ipcRenderer.sendSync('ELECTRON_BROWSER_MEMBER_CALL', metaId, member.name, wrapArgs(args)) - return metaToValue(ret) + command = 'ELECTRON_BROWSER_MEMBER_CALL' } + const ret = ipcRenderer.sendSync(command, metaId, member.name, wrapArgs(args)) + return metaToValue(ret) } let descriptorFunction = proxyFunctionProperties(remoteMemberFunction, metaId, member.name) - descriptor.get = function () { + descriptor.get = () => { descriptorFunction.ref = ref // The member should reference its object. return descriptorFunction } // Enable monkey-patch the method - descriptor.set = function (value) { + descriptor.set = (value) => { descriptorFunction = value return value } descriptor.configurable = true } else if (member.type === 'get') { - descriptor.get = function () { - return metaToValue(ipcRenderer.sendSync('ELECTRON_BROWSER_MEMBER_GET', metaId, member.name)) + descriptor.get = () => { + const command = 'ELECTRON_BROWSER_MEMBER_GET' + const meta = ipcRenderer.sendSync(command, metaId, member.name) + return metaToValue(meta) } - // Only set setter when it is writable. if (member.writable) { - descriptor.set = function (value) { + descriptor.set = (value) => { const args = wrapArgs([value]) - const meta = ipcRenderer.sendSync('ELECTRON_BROWSER_MEMBER_SET', metaId, member.name, args) - // Meta will be non-null when a setter error occurred so parse it - // to a value so it gets re-thrown. - if (meta != null) { - metaToValue(meta) - } + const command = 'ELECTRON_BROWSER_MEMBER_SET' + const meta = ipcRenderer.sendSync(command, metaId, member.name, args) + if (meta != null) metaToValue(meta) return value } } @@ -157,7 +149,7 @@ const setObjectMembers = function (ref, object, metaId, members) { // Populate object's prototype from descriptor. // This matches |getObjectPrototype| in rpc-server. -const setObjectPrototype = function (ref, object, metaId, descriptor) { +function setObjectPrototype (ref, object, metaId, descriptor) { if (descriptor === null) return let proto = {} setObjectMembers(ref, proto, metaId, descriptor.members) @@ -166,14 +158,15 @@ const setObjectPrototype = function (ref, object, metaId, descriptor) { } // Wrap function in Proxy for accessing remote properties -const proxyFunctionProperties = function (remoteMemberFunction, metaId, name) { +function proxyFunctionProperties (remoteMemberFunction, metaId, name) { let loaded = false // Lazily load function properties const loadRemoteProperties = () => { if (loaded) return loaded = true - const meta = ipcRenderer.sendSync('ELECTRON_BROWSER_MEMBER_GET', metaId, name) + const command = 'ELECTRON_BROWSER_MEMBER_GET' + const meta = ipcRenderer.sendSync(command, metaId, name) setObjectMembers(remoteMemberFunction, remoteMemberFunction, meta.id, meta.members) } @@ -186,13 +179,9 @@ const proxyFunctionProperties = function (remoteMemberFunction, metaId, name) { get: (target, property, receiver) => { if (!target.hasOwnProperty(property)) loadRemoteProperties() const value = target[property] - - // Bind toString to target if it is a function to avoid - // Function.prototype.toString is not generic errors if (property === 'toString' && typeof value === 'function') { return value.bind(target) } - return value }, ownKeys: (target) => { @@ -201,7 +190,7 @@ const proxyFunctionProperties = function (remoteMemberFunction, metaId, name) { }, getOwnPropertyDescriptor: (target, property) => { let descriptor = Object.getOwnPropertyDescriptor(target, property) - if (descriptor != null) return descriptor + if (descriptor) return descriptor loadRemoteProperties() return Object.getOwnPropertyDescriptor(target, property) } @@ -209,155 +198,130 @@ const proxyFunctionProperties = function (remoteMemberFunction, metaId, name) { } // Convert meta data from browser into real value. -const metaToValue = function (meta) { - var el, i, len, ref1, results, ret - switch (meta.type) { - case 'value': - return meta.value - case 'array': - ref1 = meta.members - results = [] - for (i = 0, len = ref1.length; i < len; i++) { - el = ref1[i] - results.push(metaToValue(el)) - } - return results - case 'buffer': - return Buffer.from(meta.value) - case 'promise': - return resolvePromise({then: metaToValue(meta.then)}) - case 'error': - return metaToPlainObject(meta) - case 'date': - return new Date(meta.value) - case 'exception': - throw new Error(meta.message + '\n' + meta.stack) - default: - if (remoteObjectCache.has(meta.id)) return remoteObjectCache.get(meta.id) +function metaToValue (meta) { + const types = { + value: () => meta.value, + array: () => meta.members.map((member) => metaToValue(member)), + buffer: () => Buffer.from(meta.value), + promise: () => resolvePromise({then: metaToValue(meta.then)}), + error: () => metaToPlainObject(meta), + date: () => new Date(meta.value), + exception: () => { throw new Error(`${meta.message}\n${meta.stack}`) } + } - if (meta.type === 'function') { - // A shadow class to represent the remote function object. - let remoteFunction = function (...args) { - if (this && this.constructor === remoteFunction) { - // Constructor call. - let obj = ipcRenderer.sendSync('ELECTRON_BROWSER_CONSTRUCTOR', meta.id, wrapArgs(args)) - // Returning object in constructor will replace constructed object - // with the returned object. - // http://stackoverflow.com/questions/1978049/what-values-can-a-constructor-return-to-avoid-returning-this - return metaToValue(obj) - } else { - // Function call. - let obj = ipcRenderer.sendSync('ELECTRON_BROWSER_FUNCTION_CALL', meta.id, wrapArgs(args)) - return metaToValue(obj) - } + if (meta.type in types) { + return types[meta.type]() + } else { + let ret + if (remoteObjectCache.has(meta.id)) { + return remoteObjectCache.get(meta.id) + } + + // A shadow class to represent the remote function object. + if (meta.type === 'function') { + let remoteFunction = function (...args) { + let command + if (this && this.constructor === remoteFunction) { + command = 'ELECTRON_BROWSER_CONSTRUCTOR' + } else { + command = 'ELECTRON_BROWSER_FUNCTION_CALL' } - ret = remoteFunction - } else { - ret = {} + const obj = ipcRenderer.sendSync(command, meta.id, wrapArgs(args)) + return metaToValue(obj) } + ret = remoteFunction + } else { + ret = {} + } - // Populate delegate members. - setObjectMembers(ret, ret, meta.id, meta.members) - // Populate delegate prototype. - setObjectPrototype(ret, ret, meta.id, meta.proto) + setObjectMembers(ret, ret, meta.id, meta.members) + setObjectPrototype(ret, ret, meta.id, meta.proto) + Object.defineProperty(ret.constructor, 'name', { value: meta.name }) - // Set constructor.name to object's name. - Object.defineProperty(ret.constructor, 'name', { value: meta.name }) - - // Track delegate object's life time, and tell the browser to clean up - // when the object is GCed. - v8Util.setRemoteObjectFreer(ret, meta.id) - - // Remember object's id. - v8Util.setHiddenValue(ret, 'atomId', meta.id) - remoteObjectCache.set(meta.id, ret) - return ret + // Track delegate obj's lifetime & tell browser to clean up when object is GCed. + v8Util.setRemoteObjectFreer(ret, meta.id) + v8Util.setHiddenValue(ret, 'atomId', meta.id) + remoteObjectCache.set(meta.id, ret) + return ret } } // Construct a plain object from the meta. -const metaToPlainObject = function (meta) { - var i, len, obj, ref1 - obj = (function () { - switch (meta.type) { - case 'error': - return new Error() - default: - return {} - } - })() - ref1 = meta.members - for (i = 0, len = ref1.length; i < len; i++) { - let {name, value} = ref1[i] +function metaToPlainObject (meta) { + const obj = (() => meta.type === 'error' ? new Error() : {})() + for (let i = 0; i < meta.members.length; i++) { + let {name, value} = meta.members[i] obj[name] = value } return obj } // Browser calls a callback in renderer. -ipcRenderer.on('ELECTRON_RENDERER_CALLBACK', function (event, id, args) { +ipcRenderer.on('ELECTRON_RENDERER_CALLBACK', (event, id, args) => { callbacksRegistry.apply(id, metaToValue(args)) }) // A callback in browser is released. -ipcRenderer.on('ELECTRON_RENDERER_RELEASE_CALLBACK', function (event, id) { +ipcRenderer.on('ELECTRON_RENDERER_RELEASE_CALLBACK', (event, id) => { callbacksRegistry.remove(id) }) process.on('exit', () => { - ipcRenderer.sendSync('ELECTRON_BROWSER_CONTEXT_RELEASE') + const command = 'ELECTRON_BROWSER_CONTEXT_RELEASE' + ipcRenderer.sendSync(command) }) -// Get remote module. -exports.require = function (module) { - return metaToValue(ipcRenderer.sendSync('ELECTRON_BROWSER_REQUIRE', module)) +exports.require = (module) => { + const command = 'ELECTRON_BROWSER_REQUIRE' + const meta = ipcRenderer.sendSync(command, module) + return metaToValue(meta) } // Alias to remote.require('electron').xxx. -exports.getBuiltin = function (module) { - return metaToValue(ipcRenderer.sendSync('ELECTRON_BROWSER_GET_BUILTIN', module)) +exports.getBuiltin = (module) => { + const command = 'ELECTRON_BROWSER_GET_BUILTIN' + const meta = ipcRenderer.sendSync(command, module) + return metaToValue(meta) } -// Get current BrowserWindow. -exports.getCurrentWindow = function () { - return metaToValue(ipcRenderer.sendSync('ELECTRON_BROWSER_CURRENT_WINDOW')) +exports.getCurrentWindow = () => { + const command = 'ELECTRON_BROWSER_CURRENT_WINDOW' + const meta = ipcRenderer.sendSync(command) + return metaToValue(meta) } // Get current WebContents object. -exports.getCurrentWebContents = function () { +exports.getCurrentWebContents = () => { return metaToValue(ipcRenderer.sendSync('ELECTRON_BROWSER_CURRENT_WEB_CONTENTS')) } // Get a global object in browser. -exports.getGlobal = function (name) { - return metaToValue(ipcRenderer.sendSync('ELECTRON_BROWSER_GLOBAL', name)) +exports.getGlobal = (name) => { + const command = 'ELECTRON_BROWSER_GLOBAL' + const meta = ipcRenderer.sendSync(command, name) + return metaToValue(meta) } // Get the process object in browser. -exports.__defineGetter__('process', function () { - return exports.getGlobal('process') -}) +exports.__defineGetter__('process', () => exports.getGlobal('process')) -// Create a funtion that will return the specifed value when called in browser. -exports.createFunctionWithReturnValue = function (returnValue) { - const func = function () { - return returnValue - } +// Create a function that will return the specified value when called in browser. +exports.createFunctionWithReturnValue = (returnValue) => { + const func = () => returnValue v8Util.setHiddenValue(func, 'returnValue', true) return func } // Get the guest WebContents from guestInstanceId. -exports.getGuestWebContents = function (guestInstanceId) { - const meta = ipcRenderer.sendSync('ELECTRON_BROWSER_GUEST_WEB_CONTENTS', guestInstanceId) +exports.getGuestWebContents = (guestInstanceId) => { + const command = 'ELECTRON_BROWSER_GUEST_WEB_CONTENTS' + const meta = ipcRenderer.sendSync(command, guestInstanceId) return metaToValue(meta) } const addBuiltinProperty = (name) => { Object.defineProperty(exports, name, { - get: function () { - return exports.getBuiltin(name) - } + get: () => exports.getBuiltin(name) }) } diff --git a/lib/renderer/api/web-frame.js b/lib/renderer/api/web-frame.js index f9d2b3b1918..3241858cfac 100644 --- a/lib/renderer/api/web-frame.js +++ b/lib/renderer/api/web-frame.js @@ -5,6 +5,7 @@ const {webFrame, WebFrame} = process.atomBinding('web_frame') // WebFrame is an EventEmitter. Object.setPrototypeOf(WebFrame.prototype, EventEmitter.prototype) +EventEmitter.call(webFrame) // Lots of webview would subscribe to webFrame's events. webFrame.setMaxListeners(0) diff --git a/lib/renderer/content-scripts-injector.js b/lib/renderer/content-scripts-injector.js index d26802c8a8e..5bc41780af9 100644 --- a/lib/renderer/content-scripts-injector.js +++ b/lib/renderer/content-scripts-injector.js @@ -5,7 +5,7 @@ const {runInThisContext} = require('vm') // https://developer.chrome.com/extensions/match_patterns const matchesPattern = function (pattern) { if (pattern === '') return true - const regexp = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$') + const regexp = new RegExp(`^${pattern.replace(/\*/g, '.*')}$`) const url = `${location.protocol}//${location.host}${location.pathname}` return url.match(regexp) } @@ -14,7 +14,7 @@ const matchesPattern = function (pattern) { const runContentScript = function (extensionId, url, code) { const context = {} require('./chrome-api').injectTo(extensionId, false, context) - const wrapper = `(function (chrome) {\n ${code}\n })` + const wrapper = `((chrome) => {\n ${code}\n })` const compiledWrapper = runInThisContext(wrapper, { filename: url, lineOffset: 1, @@ -23,6 +23,23 @@ const runContentScript = function (extensionId, url, code) { return compiledWrapper.call(this, context.chrome) } +const runStylesheet = function (url, code) { + const wrapper = `((code) => { + function init() { + const styleElement = document.createElement('style'); + styleElement.textContent = code; + document.head.append(styleElement); + } + document.addEventListener('DOMContentLoaded', init); + })` + const compiledWrapper = runInThisContext(wrapper, { + filename: url, + lineOffset: 1, + displayErrors: true + }) + return compiledWrapper.call(this, code) +} + // Run injected scripts. // https://developer.chrome.com/extensions/content_scripts const injectContentScript = function (extensionId, script) { @@ -35,19 +52,22 @@ const injectContentScript = function (extensionId, script) { process.once('document-start', fire) } else if (script.runAt === 'document_end') { process.once('document-end', fire) - } else if (script.runAt === 'document_idle') { + } else { document.addEventListener('DOMContentLoaded', fire) } } } if (script.css) { - for (const {code} of script.css) { - process.once('document-end', () => { - var node = document.createElement('style') - node.innerHTML = code - window.document.body.appendChild(node) - }) + for (const {url, code} of script.css) { + const fire = runStylesheet.bind(window, url, code) + if (script.runAt === 'document_start') { + process.once('document-start', fire) + } else if (script.runAt === 'document_end') { + process.once('document-end', fire) + } else { + document.addEventListener('DOMContentLoaded', fire) + } } } } diff --git a/npm/install.js b/npm/install.js index 0419196de2a..8cb9ed8815f 100755 --- a/npm/install.js +++ b/npm/install.js @@ -29,7 +29,7 @@ download({ arch: process.env.npm_config_arch, strictSSL: process.env.npm_config_strict_ssl === 'true', force: process.env.force_no_cache === 'true', - quiet: ['info', 'verbose', 'silly', 'http'].indexOf(process.env.npm_config_loglevel) === -1 + quiet: process.env.npm_config_loglevel === 'silent' || process.env.CI }, extractFile) // unzips and makes path.txt point at the correct executable diff --git a/package.json b/package.json index 39d4354f01b..8c31ab7a04d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "electron", - "version": "1.8.1", + "version": "1.8.2-beta.2", "repository": "https://github.com/electron/electron", "description": "Build cross platform desktop apps with JavaScript, HTML, and CSS", "devDependencies": { @@ -9,15 +9,18 @@ "check-for-leaks": "^1.0.2", "colors": "^1.1.2", "dotenv-safe": "^4.0.4", + "dugite": "^1.45.0", "electabul": "~0.0.4", "electron-docs-linter": "^2.3.3", - "electron-typescript-definitions": "^1.2.7", + "electron-typescript-definitions": "^1.2.10", "github": "^9.2.0", - "heads": "^1.3.0", "husky": "^0.14.3", + "minimist": "^1.2.0", + "nugget": "^2.0.1", "request": "^2.68.0", "standard": "^8.4.0", "standard-markdown": "^4.0.0", + "sumchecker": "^2.0.2", "temp": "^0.8.3" }, "standard": { @@ -49,12 +52,15 @@ "lint-api-docs-js": "standard-markdown docs && standard-markdown docs-translations", "create-api-json": "electron-docs-linter docs --outfile=out/electron-api.json --version=$npm_package_version", "create-typescript-definitions": "npm run create-api-json && electron-typescript-definitions --in=out/electron-api.json --out=out/electron.d.ts", + "merge-release": "node ./script/merge-release.js", + "mock-release": "node ./script/ci-release-build.js", "preinstall": "node -e 'process.exit(0)'", "publish-to-npm": "node ./script/publish-to-npm.js", "prepack": "check-for-leaks", "prepush": "check-for-leaks", - "prerelease": "node ./script/prerelease", - "release": "./script/upload.py -p", + "prepare-release": "node ./script/prepare-release.js", + "prerelease": "python ./script/bootstrap.py -v --dev && npm run build", + "release": "node ./script/release.js", "repl": "python ./script/start.py --interactive", "start": "python ./script/start.py", "test": "python ./script/test.py" diff --git a/script/bump-version.py b/script/bump-version.py index 8664736cca3..42d6392baef 100755 --- a/script/bump-version.py +++ b/script/bump-version.py @@ -90,6 +90,7 @@ def main(): update_package_json(version, suffix) tag_version(version, suffix) + print 'Bumped to version: {0}'.format(version + suffix) def increase_version(versions, index): for i in range(index + 1, 4): diff --git a/script/ci-release-build.js b/script/ci-release-build.js new file mode 100644 index 00000000000..76eeb8e080d --- /dev/null +++ b/script/ci-release-build.js @@ -0,0 +1,56 @@ +const args = require('minimist')(process.argv.slice(2)) +const assert = require('assert') +const request = require('request') + +const ciJobs = [ + 'electron-linux-arm64', + 'electron-linux-ia32', + 'electron-linux-x64', + 'electron-linux-arm' +] + +const CIcall = (buildUrl, targetBranch, job) => { + console.log(`Triggering CircleCI to run build job: ${job} on branch: ${targetBranch} with release flag.`) + + request({ + method: 'POST', + url: buildUrl, + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }, + body: JSON.stringify({ + 'build_parameters': { + 'RUN_RELEASE_BUILD': 'true', + 'CIRCLE_JOB': job + } + }) + }, (err, res, body) => { + if (!err && res.statusCode >= 200 && res.statusCode < 300) { + const build = JSON.parse(body) + console.log(`Check ${build.build_url} for status. (${job})`) + } else { + console.log('Error: ', `(status ${res.statusCode})`, err || JSON.parse(res.body), job) + } + }) +} + +if (args._.length < 1) { + console.log(`Trigger Circle CI to build release builds of electron. + Usage: ci-release-build.js [--job=CI_JOB_NAME] TARGET_BRANCH + `) + process.exit(0) +} + +assert(process.env.CIRCLE_TOKEN, 'CIRCLE_TOKEN not found in environment') + +const targetBranch = args._[0] +const job = args['job'] +const circleBuildUrl = `https://circleci.com/api/v1.1/project/github/electron/electron/tree/${targetBranch}?circle-token=${process.env.CIRCLE_TOKEN}` + +if (job) { + assert(ciJobs.includes(job), `Unknown CI job name: ${job}.`) + CIcall(circleBuildUrl, targetBranch, job) +} else { + ciJobs.forEach((job) => CIcall(circleBuildUrl, targetBranch, job)) +} diff --git a/script/cpplint.py b/script/cpplint.py index e602b3cb1e6..5dfca8f76ec 100755 --- a/script/cpplint.py +++ b/script/cpplint.py @@ -38,6 +38,10 @@ SOURCE_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) def main(): + if not os.path.isfile(cpplint_path()): + print("[INFO] Skipping cpplint, dependencies has not been bootstrapped") + return + os.chdir(SOURCE_ROOT) atom_files = list_files('atom', ['app', 'browser', 'common', 'renderer', 'utility'], @@ -60,9 +64,13 @@ def list_files(parent, directories, filters): def call_cpplint(files): - cpplint = os.path.join(SOURCE_ROOT, 'vendor', 'depot_tools', 'cpplint.py') + cpplint = cpplint_path() execute([sys.executable, cpplint] + files) +def cpplint_path(): + return os.path.join(SOURCE_ROOT, 'vendor', 'depot_tools', 'cpplint.py') + + if __name__ == '__main__': sys.exit(main()) diff --git a/script/merge-release.js b/script/merge-release.js new file mode 100755 index 00000000000..60ac3acb244 --- /dev/null +++ b/script/merge-release.js @@ -0,0 +1,116 @@ +#!/usr/bin/env node + +require('colors') +const assert = require('assert') +const branchToRelease = process.argv[2] +const fail = '\u2717'.red +const { GitProcess, GitError } = require('dugite') +const pass = '\u2713'.green +const path = require('path') +const pkg = require('../package.json') + +assert(process.env.ELECTRON_GITHUB_TOKEN, 'ELECTRON_GITHUB_TOKEN not found in environment') +if (!branchToRelease) { + console.log(`Usage: merge-release branch`) + process.exit(1) +} +const gitDir = path.resolve(__dirname, '..') + +async function callGit (args, errorMessage, successMessage) { + let gitResult = await GitProcess.exec(args, gitDir) + if (gitResult.exitCode === 0) { + console.log(`${pass} ${successMessage}`) + return true + } else { + console.log(`${fail} ${errorMessage} ${gitResult.stderr}`) + process.exit(1) + } +} + +async function checkoutBranch (branchName) { + console.log(`Checking out ${branchName}.`) + let errorMessage = `Error checking out branch ${branchName}:` + let successMessage = `Successfully checked out branch ${branchName}.` + return await callGit(['checkout', branchName], errorMessage, successMessage) +} + +async function commitMerge () { + console.log(`Committing the merge for v${pkg.version}`) + let errorMessage = `Error committing merge:` + let successMessage = `Successfully committed the merge for v${pkg.version}` + let gitArgs = ['commit', '-m', `v${pkg.version}`] + return await callGit(gitArgs, errorMessage, successMessage) +} + +async function mergeReleaseIntoBranch (branchName) { + console.log(`Merging release branch into ${branchName}.`) + let mergeArgs = ['merge', 'release', '--squash'] + let mergeDetails = await GitProcess.exec(mergeArgs, gitDir) + if (mergeDetails.exitCode === 0) { + return true + } else { + const error = GitProcess.parseError(mergeDetails.stderr) + if (error === GitError.MergeConflicts) { + console.log(`${fail} Could not merge release branch into ${branchName} ` + + `due to merge conflicts.`) + return false + } else { + console.log(`${fail} Could not merge release branch into ${branchName} ` + + `due to an error: ${mergeDetails.stderr}.`) + process.exit(1) + } + } +} + +async function pushBranch (branchName) { + console.log(`Pushing branch ${branchName}.`) + let pushArgs = ['push', 'origin', branchName] + let errorMessage = `Could not push branch ${branchName} due to an error:` + let successMessage = `Successfully pushed branch ${branchName}.` + return await callGit(pushArgs, errorMessage, successMessage) +} + +async function pull () { + console.log(`Performing a git pull`) + let errorMessage = `Could not pull due to an error:` + let successMessage = `Successfully performed a git pull` + return await callGit(['pull'], errorMessage, successMessage) +} + +async function rebase (targetBranch) { + console.log(`Rebasing release branch from ${targetBranch}`) + let errorMessage = `Could not rebase due to an error:` + let successMessage = `Successfully rebased release branch from ` + + `${targetBranch}` + return await callGit(['rebase', targetBranch], errorMessage, successMessage) +} + +async function mergeRelease () { + await checkoutBranch(branchToRelease) + let mergeSuccess = await mergeReleaseIntoBranch(branchToRelease) + if (mergeSuccess) { + console.log(`${pass} Successfully merged release branch into ` + + `${branchToRelease}.`) + await commitMerge() + let pushSuccess = await pushBranch(branchToRelease) + if (pushSuccess) { + console.log(`${pass} Success!!! ${branchToRelease} now has the latest release!`) + } + } else { + console.log(`Trying rebase of ${branchToRelease} into release branch.`) + await pull() + await checkoutBranch('release') + let rebaseResult = await rebase(branchToRelease) + if (rebaseResult) { + let pushResult = pushBranch('HEAD') + if (pushResult) { + console.log(`Rebase of ${branchToRelease} into release branch was ` + + `successful. Let release builds run and then try this step again.`) + } + // Exit as failure so release doesn't continue + process.exit(1) + } + } +} + +mergeRelease() diff --git a/script/prepare-release.js b/script/prepare-release.js new file mode 100755 index 00000000000..ae8b0c7bfe5 --- /dev/null +++ b/script/prepare-release.js @@ -0,0 +1,173 @@ +#!/usr/bin/env node + +require('colors') +const args = require('minimist')(process.argv.slice(2)) +const assert = require('assert') +const { execSync } = require('child_process') +const fail = '\u2717'.red +const { GitProcess, GitError } = require('dugite') +const GitHub = require('github') +const pass = '\u2713'.green +const path = require('path') +const pkg = require('../package.json') +const versionType = args._[0] + +// TODO (future) automatically determine version based on conventional commits +// via conventional-recommended-bump + +assert(process.env.ELECTRON_GITHUB_TOKEN, 'ELECTRON_GITHUB_TOKEN not found in environment') +if (!versionType && !args.notesOnly) { + console.log(`Usage: prepare-release versionType [major | minor | patch | beta]` + + ` (--stable) (--notesOnly)`) + process.exit(1) +} + +const github = new GitHub() +const gitDir = path.resolve(__dirname, '..') +github.authenticate({type: 'token', token: process.env.ELECTRON_GITHUB_TOKEN}) + +async function createReleaseBranch () { + console.log(`Creating release branch.`) + let checkoutDetails = await GitProcess.exec([ 'checkout', '-b', 'release' ], gitDir) + if (checkoutDetails.exitCode === 0) { + console.log(`${pass} Successfully created the release branch.`) + } else { + const error = GitProcess.parseError(checkoutDetails.stderr) + if (error === GitError.BranchAlreadyExists) { + console.log(`${fail} Release branch already exists, aborting prepare ` + + `release process.`) + } else { + console.log(`${fail} Error creating release branch: ` + + `${checkoutDetails.stderr}`) + } + process.exit(1) + } +} + +function getNewVersion () { + console.log(`Bumping for new "${versionType}" version.`) + let bumpScript = path.join(__dirname, 'bump-version.py') + let scriptArgs = [bumpScript, `--bump ${versionType}`] + if (args.stable) { + scriptArgs.push('--stable') + } + try { + let bumpVersion = execSync(scriptArgs.join(' '), {encoding: 'UTF-8'}) + bumpVersion = bumpVersion.substr(bumpVersion.indexOf(':') + 1).trim() + let newVersion = `v${bumpVersion}` + console.log(`${pass} Successfully bumped version to ${newVersion}`) + return newVersion + } catch (err) { + console.log(`${fail} Could not bump version, error was:`, err) + } +} + +async function getCurrentBranch (gitDir) { + console.log(`Determining current git branch`) + let gitArgs = ['rev-parse', '--abbrev-ref', 'HEAD'] + let branchDetails = await GitProcess.exec(gitArgs, gitDir) + if (branchDetails.exitCode === 0) { + let currentBranch = branchDetails.stdout.trim() + console.log(`${pass} Successfully determined current git branch is ` + + `${currentBranch}`) + return currentBranch + } else { + let error = GitProcess.parseError(branchDetails.stderr) + console.log(`${fail} Could not get details for the current branch, + error was ${branchDetails.stderr}`, error) + process.exit(1) + } +} + +async function getReleaseNotes (currentBranch) { + console.log(`Generating release notes for ${currentBranch}.`) + let githubOpts = { + owner: 'electron', + repo: 'electron', + base: `v${pkg.version}`, + head: currentBranch + } + let releaseNotes = '(placeholder)\n' + console.log(`Checking for commits from ${pkg.version} to ${currentBranch}`) + let commitComparison = await github.repos.compareCommits(githubOpts) + .catch(err => { + console.log(`{$fail} Error checking for commits from ${pkg.version} to ` + + `${currentBranch}`, err) + process.exit(1) + }) + + commitComparison.data.commits.forEach(commitEntry => { + let commitMessage = commitEntry.commit.message + if (commitMessage.toLowerCase().indexOf('merge') > -1) { + releaseNotes += `${commitMessage} \n` + } + }) + console.log(`${pass} Done generating release notes for ${currentBranch}.`) + return releaseNotes +} + +async function createRelease (branchToTarget, isBeta) { + let releaseNotes = await getReleaseNotes(branchToTarget) + let newVersion = getNewVersion() + const githubOpts = { + owner: 'electron', + repo: 'electron' + } + console.log(`Checking for existing draft release.`) + let releases = await github.repos.getReleases(githubOpts) + .catch(err => { + console.log('$fail} Could not get releases. Error was', err) + }) + let drafts = releases.data.filter(release => release.draft) + if (drafts.length > 0) { + console.log(`${fail} Aborting because draft release for + ${drafts[0].tag_name} already exists.`) + process.exit(1) + } + console.log(`${pass} A draft release does not exist; creating one.`) + githubOpts.body = releaseNotes + githubOpts.draft = true + githubOpts.name = `electron ${newVersion}` + if (isBeta) { + githubOpts.body = `Note: This is a beta release. Please file new issues ` + + `for any bugs you find in it.\n \n This release is published to npm ` + + `under the beta tag and can be installed via npm install electron@beta, ` + + `or npm i electron@${newVersion.substr(1)}.` + githubOpts.name = `${githubOpts.name}` + githubOpts.prerelease = true + } + githubOpts.tag_name = newVersion + githubOpts.target_commitish = branchToTarget + await github.repos.createRelease(githubOpts) + .catch(err => { + console.log(`${fail} Error creating new release: `, err) + process.exit(1) + }) + console.log(`${pass} Draft release for ${newVersion} has been created.`) +} + +async function pushRelease () { + let pushDetails = await GitProcess.exec(['push', 'origin', 'HEAD'], gitDir) + if (pushDetails.exitCode === 0) { + console.log(`${pass} Successfully pushed the release branch. Wait for ` + + `release builds to finish before running "npm run release".`) + } else { + console.log(`${fail} Error pushing the release branch: ` + + `${pushDetails.stderr}`) + process.exit(1) + } +} + +async function prepareRelease (isBeta, notesOnly) { + let currentBranch = await getCurrentBranch(gitDir) + if (notesOnly) { + let releaseNotes = await getReleaseNotes(currentBranch) + console.log(`Draft release notes are: ${releaseNotes}`) + } else { + await createReleaseBranch() + await createRelease(currentBranch, isBeta) + await pushRelease() + } +} + +prepareRelease(!args.stable, args.notesOnly) diff --git a/script/prerelease.js b/script/prerelease.js deleted file mode 100755 index 5dd4be1ff55..00000000000 --- a/script/prerelease.js +++ /dev/null @@ -1,114 +0,0 @@ -#!/usr/bin/env node - -require('colors') -const assert = require('assert') -const GitHub = require('github') -const heads = require('heads') -const pkg = require('../package.json') -const pass = '\u2713'.green -const fail = '\u2717'.red -let failureCount = 0 - -assert(process.env.ELECTRON_GITHUB_TOKEN, 'ELECTRON_GITHUB_TOKEN not found in environment') - -const github = new GitHub() -github.authenticate({type: 'token', token: process.env.ELECTRON_GITHUB_TOKEN}) -github.repos.getReleases({owner: 'electron', repo: 'electron'}) - .then(res => { - const releases = res.data - const drafts = releases - .filter(release => release.draft) // comment out for testing - // .filter(release => release.tag_name === 'v1.7.5') // uncomment for testing - - check(drafts.length === 1, 'one draft exists', true) - const draft = drafts[0] - - check(draft.tag_name === `v${pkg.version}`, `draft release version matches local package.json (v${pkg.version})`) - check(draft.body.length > 50 && !draft.body.includes('(placeholder)'), 'draft has release notes') - - const requiredAssets = assetsForVersion(draft.tag_name).sort() - const extantAssets = draft.assets.map(asset => asset.name).sort() - - requiredAssets.forEach(asset => { - check(extantAssets.includes(asset), asset) - }) - - const s3Urls = s3UrlsForVersion(draft.tag_name) - heads(s3Urls) - .then(results => { - results.forEach((result, i) => { - check(result === 200, s3Urls[i]) - }) - - process.exit(failureCount > 0 ? 1 : 0) - }) - .catch(err => { - console.error('Error making HEAD requests for S3 assets') - console.error(err) - process.exit(1) - }) - }) - -function check (condition, statement, exitIfFail = false) { - if (condition) { - console.log(`${pass} ${statement}`) - } else { - failureCount++ - console.log(`${fail} ${statement}`) - if (exitIfFail) process.exit(1) - } -} - -function assetsForVersion (version) { - const patterns = [ - 'electron-{{VERSION}}-darwin-x64-dsym.zip', - 'electron-{{VERSION}}-darwin-x64-symbols.zip', - 'electron-{{VERSION}}-darwin-x64.zip', - 'electron-{{VERSION}}-linux-arm-symbols.zip', - 'electron-{{VERSION}}-linux-arm.zip', - 'electron-{{VERSION}}-linux-arm64-symbols.zip', - 'electron-{{VERSION}}-linux-arm64.zip', - 'electron-{{VERSION}}-linux-armv7l-symbols.zip', - 'electron-{{VERSION}}-linux-armv7l.zip', - 'electron-{{VERSION}}-linux-ia32-symbols.zip', - 'electron-{{VERSION}}-linux-ia32.zip', - 'electron-{{VERSION}}-linux-x64-symbols.zip', - 'electron-{{VERSION}}-linux-x64.zip', - 'electron-{{VERSION}}-mas-x64-dsym.zip', - 'electron-{{VERSION}}-mas-x64-symbols.zip', - 'electron-{{VERSION}}-mas-x64.zip', - 'electron-{{VERSION}}-win32-ia32-pdb.zip', - 'electron-{{VERSION}}-win32-ia32-symbols.zip', - 'electron-{{VERSION}}-win32-ia32.zip', - 'electron-{{VERSION}}-win32-x64-pdb.zip', - 'electron-{{VERSION}}-win32-x64-symbols.zip', - 'electron-{{VERSION}}-win32-x64.zip', - 'electron-api.json', - 'electron.d.ts', - 'ffmpeg-{{VERSION}}-darwin-x64.zip', - 'ffmpeg-{{VERSION}}-linux-arm.zip', - 'ffmpeg-{{VERSION}}-linux-arm64.zip', - 'ffmpeg-{{VERSION}}-linux-armv7l.zip', - 'ffmpeg-{{VERSION}}-linux-ia32.zip', - 'ffmpeg-{{VERSION}}-linux-x64.zip', - 'ffmpeg-{{VERSION}}-mas-x64.zip', - 'ffmpeg-{{VERSION}}-win32-ia32.zip', - 'ffmpeg-{{VERSION}}-win32-x64.zip' - ] - return patterns.map(pattern => pattern.replace(/{{VERSION}}/g, version)) -} - -function s3UrlsForVersion (version) { - const bucket = 'https://gh-contractor-zcbenz.s3.amazonaws.com/' - const patterns = [ - 'atom-shell/dist/{{VERSION}}/iojs-{{VERSION}}-headers.tar.gz', - 'atom-shell/dist/{{VERSION}}/iojs-{{VERSION}}.tar.gz', - 'atom-shell/dist/{{VERSION}}/node-{{VERSION}}.tar.gz', - 'atom-shell/dist/{{VERSION}}/node.lib', - 'atom-shell/dist/{{VERSION}}/win-x64/iojs.lib', - 'atom-shell/dist/{{VERSION}}/win-x86/iojs.lib', - 'atom-shell/dist/{{VERSION}}/x64/node.lib', - 'atom-shell/dist/index.json' - ] - return patterns.map(pattern => bucket + pattern.replace(/{{VERSION}}/g, version)) -} diff --git a/script/publish-to-npm.js b/script/publish-to-npm.js index 21960455cfe..aaf93f33cd9 100644 --- a/script/publish-to-npm.js +++ b/script/publish-to-npm.js @@ -114,7 +114,7 @@ new Promise((resolve, reject) => { cwd: tempDir }) const checkVersion = childProcess.execSync(`${path.join(tempDir, 'node_modules', '.bin', 'electron')} -v`) - assert.strictEqual(checkVersion.toString().trim(), `v${rootPackageJson.version}`) + assert.ok((`v${rootPackageJson.version}`.indexOf(checkVersion.toString().trim()) === 0), `Version is correct`) resolve(tarballPath) }) }) diff --git a/script/release.js b/script/release.js new file mode 100755 index 00000000000..42c55b5cee3 --- /dev/null +++ b/script/release.js @@ -0,0 +1,462 @@ +#!/usr/bin/env node + +require('colors') +const args = require('minimist')(process.argv.slice(2)) +const assert = require('assert') +const fs = require('fs') +const { execSync } = require('child_process') +const GitHub = require('github') +const { GitProcess } = require('dugite') +const nugget = require('nugget') +const pkg = require('../package.json') +const pkgVersion = `v${pkg.version}` +const pass = '\u2713'.green +const path = require('path') +const fail = '\u2717'.red +const sumchecker = require('sumchecker') +const temp = require('temp').track() +const { URL } = require('url') +let failureCount = 0 + +assert(process.env.ELECTRON_GITHUB_TOKEN, 'ELECTRON_GITHUB_TOKEN not found in environment') + +const github = new GitHub({ + followRedirects: false +}) +github.authenticate({type: 'token', token: process.env.ELECTRON_GITHUB_TOKEN}) +const gitDir = path.resolve(__dirname, '..') + +async function getDraftRelease (version, skipValidation) { + let releaseInfo = await github.repos.getReleases({owner: 'electron', repo: 'electron'}) + let drafts + let versionToCheck + if (version) { + drafts = releaseInfo.data + .filter(release => release.tag_name === version) + versionToCheck = version + } else { + drafts = releaseInfo.data + .filter(release => release.draft) + versionToCheck = pkgVersion + } + + const draft = drafts[0] + if (!skipValidation) { + failureCount = 0 + check(drafts.length === 1, 'one draft exists', true) + check(draft.tag_name === versionToCheck, `draft release version matches local package.json (${versionToCheck})`) + if (versionToCheck.indexOf('beta')) { + check(draft.prerelease, 'draft is a prerelease') + } + check(draft.body.length > 50 && !draft.body.includes('(placeholder)'), 'draft has release notes') + check((failureCount === 0), `Draft release looks good to go.`, true) + } + return draft +} + +async function validateReleaseAssets (release) { + const requiredAssets = assetsForVersion(release.tag_name).sort() + const extantAssets = release.assets.map(asset => asset.name).sort() + const downloadUrls = release.assets.map(asset => asset.browser_download_url).sort() + + failureCount = 0 + requiredAssets.forEach(asset => { + check(extantAssets.includes(asset), asset) + }) + check((failureCount === 0), `All required GitHub assets exist for release`, true) + + if (release.draft) { + await verifyAssets(release) + } else { + await verifyShasums(downloadUrls) + .catch(err => { + console.log(`${fail} error verifyingShasums`, err) + }) + } + const s3Urls = s3UrlsForVersion(release.tag_name) + await verifyShasums(s3Urls, true) +} + +function check (condition, statement, exitIfFail = false) { + if (condition) { + console.log(`${pass} ${statement}`) + } else { + failureCount++ + console.log(`${fail} ${statement}`) + if (exitIfFail) process.exit(1) + } +} + +function assetsForVersion (version) { + const patterns = [ + `electron-${version}-darwin-x64-dsym.zip`, + `electron-${version}-darwin-x64-symbols.zip`, + `electron-${version}-darwin-x64.zip`, + `electron-${version}-linux-arm-symbols.zip`, + `electron-${version}-linux-arm.zip`, + `electron-${version}-linux-arm64-symbols.zip`, + `electron-${version}-linux-arm64.zip`, + `electron-${version}-linux-armv7l-symbols.zip`, + `electron-${version}-linux-armv7l.zip`, + `electron-${version}-linux-ia32-symbols.zip`, + `electron-${version}-linux-ia32.zip`, + `electron-${version}-linux-x64-symbols.zip`, + `electron-${version}-linux-x64.zip`, + `electron-${version}-mas-x64-dsym.zip`, + `electron-${version}-mas-x64-symbols.zip`, + `electron-${version}-mas-x64.zip`, + `electron-${version}-win32-ia32-pdb.zip`, + `electron-${version}-win32-ia32-symbols.zip`, + `electron-${version}-win32-ia32.zip`, + `electron-${version}-win32-x64-pdb.zip`, + `electron-${version}-win32-x64-symbols.zip`, + `electron-${version}-win32-x64.zip`, + `electron-api.json`, + `electron.d.ts`, + `ffmpeg-${version}-darwin-x64.zip`, + `ffmpeg-${version}-linux-arm.zip`, + `ffmpeg-${version}-linux-arm64.zip`, + `ffmpeg-${version}-linux-armv7l.zip`, + `ffmpeg-${version}-linux-ia32.zip`, + `ffmpeg-${version}-linux-x64.zip`, + `ffmpeg-${version}-mas-x64.zip`, + `ffmpeg-${version}-win32-ia32.zip`, + `ffmpeg-${version}-win32-x64.zip`, + `SHASUMS256.txt` + ] + return patterns +} + +function s3UrlsForVersion (version) { + const bucket = `https://gh-contractor-zcbenz.s3.amazonaws.com/` + const patterns = [ + `${bucket}atom-shell/dist/${version}/iojs-${version}-headers.tar.gz`, + `${bucket}atom-shell/dist/${version}/iojs-${version}.tar.gz`, + `${bucket}atom-shell/dist/${version}/node-${version}.tar.gz`, + `${bucket}atom-shell/dist/${version}/node.lib`, + `${bucket}atom-shell/dist/${version}/win-x64/iojs.lib`, + `${bucket}atom-shell/dist/${version}/win-x86/iojs.lib`, + `${bucket}atom-shell/dist/${version}/x64/node.lib`, + `${bucket}atom-shell/dist/${version}/SHASUMS.txt`, + `${bucket}atom-shell/dist/${version}/SHASUMS256.txt`, + `${bucket}atom-shell/dist/index.json` + ] + return patterns +} + +function checkVersion () { + console.log(`Verifying that app version matches package version ${pkgVersion}.`) + let startScript = path.join(__dirname, 'start.py') + let appVersion = runScript(startScript, ['--version']).trim() + check((pkgVersion.indexOf(appVersion) === 0), `App version ${appVersion} matches ` + + `package version ${pkgVersion}.`, true) +} + +function runScript (scriptName, scriptArgs, cwd) { + let scriptCommand = `${scriptName} ${scriptArgs.join(' ')}` + let scriptOptions = { + encoding: 'UTF-8' + } + if (cwd) { + scriptOptions.cwd = cwd + } + try { + return execSync(scriptCommand, scriptOptions) + } catch (err) { + console.log(`${fail} Error running ${scriptName}`, err) + process.exit(1) + } +} + +function uploadNodeShasums () { + console.log('Uploading Node SHASUMS file to S3.') + let scriptPath = path.join(__dirname, 'upload-node-checksums.py') + runScript(scriptPath, ['-v', pkgVersion]) + console.log(`${pass} Done uploading Node SHASUMS file to S3.`) +} + +function uploadIndexJson () { + console.log('Uploading index.json to S3.') + let scriptPath = path.join(__dirname, 'upload-index-json.py') + runScript(scriptPath, []) + console.log(`${pass} Done uploading index.json to S3.`) +} + +async function createReleaseShasums (release) { + let fileName = 'SHASUMS256.txt' + let existingAssets = release.assets.filter(asset => asset.name === fileName) + if (existingAssets.length > 0) { + console.log(`${fileName} already exists on GitHub; deleting before creating new file.`) + await github.repos.deleteAsset({ + owner: 'electron', + repo: 'electron', + id: existingAssets[0].id + }).catch(err => { + console.log(`${fail} Error deleting ${fileName} on GitHub:`, err) + }) + } + console.log(`Creating and uploading the release ${fileName}.`) + let scriptPath = path.join(__dirname, 'merge-electron-checksums.py') + let checksums = runScript(scriptPath, ['-v', pkgVersion]) + console.log(`${pass} Generated release SHASUMS.`) + let filePath = await saveShaSumFile(checksums, fileName) + console.log(`${pass} Created ${fileName} file.`) + await uploadShasumFile(filePath, fileName, release) + console.log(`${pass} Successfully uploaded ${fileName} to GitHub.`) +} + +async function uploadShasumFile (filePath, fileName, release) { + let githubOpts = { + owner: 'electron', + repo: 'electron', + id: release.id, + filePath, + name: fileName + } + return await github.repos.uploadAsset(githubOpts) + .catch(err => { + console.log(`${fail} Error uploading ${filePath} to GitHub:`, err) + process.exit(1) + }) +} + +function saveShaSumFile (checksums, fileName) { + return new Promise((resolve, reject) => { + temp.open(fileName, (err, info) => { + if (err) { + console.log(`${fail} Could not create ${fileName} file`) + process.exit(1) + } else { + fs.writeFileSync(info.fd, checksums) + fs.close(info.fd, (err) => { + if (err) { + console.log(`${fail} Could close ${fileName} file`) + process.exit(1) + } + resolve(info.path) + }) + } + }) + }) +} + +async function publishRelease (release) { + let githubOpts = { + owner: 'electron', + repo: 'electron', + id: release.id, + tag_name: release.tag_name, + draft: false + } + return await github.repos.editRelease(githubOpts) + .catch(err => { + console.log(`${fail} Error publishing release:`, err) + process.exit(1) + }) +} + +async function makeRelease (releaseToValidate) { + if (releaseToValidate) { + console.log(`Validating release ${args.validateRelease}`) + let release = await getDraftRelease(args.validateRelease) + await validateReleaseAssets(release) + } else { + checkVersion() + let draftRelease = await getDraftRelease() + uploadNodeShasums() + uploadIndexJson() + await createReleaseShasums(draftRelease) + // Fetch latest version of release before verifying + draftRelease = await getDraftRelease(pkgVersion, true) + await validateReleaseAssets(draftRelease) + await publishRelease(draftRelease) + await cleanupReleaseBranch() + console.log(`${pass} SUCCESS!!! Release has been published. Please run ` + + `"npm run publish-to-npm" to publish release to npm.`) + } +} + +async function makeTempDir () { + return new Promise((resolve, reject) => { + temp.mkdir('electron-publish', (err, dirPath) => { + if (err) { + reject(err) + } else { + resolve(dirPath) + } + }) + }) +} + +async function verifyAssets (release) { + let downloadDir = await makeTempDir() + let githubOpts = { + owner: 'electron', + repo: 'electron', + headers: { + Accept: 'application/octet-stream' + } + } + console.log(`Downloading files from GitHub to verify shasums`) + let shaSumFile = 'SHASUMS256.txt' + let filesToCheck = await Promise.all(release.assets.map(async (asset) => { + githubOpts.id = asset.id + let assetDetails = await github.repos.getAsset(githubOpts) + await downloadFiles(assetDetails.meta.location, downloadDir, false, asset.name) + return asset.name + })).catch(err => { + console.log(`${fail} Error downloading files from GitHub`, err) + process.exit(1) + }) + filesToCheck = filesToCheck.filter(fileName => fileName !== shaSumFile) + let checkerOpts + await validateChecksums({ + algorithm: 'sha256', + filesToCheck, + fileDirectory: downloadDir, + shaSumFile, + checkerOpts, + fileSource: 'GitHub' + }) +} + +function downloadFiles (urls, directory, quiet, targetName) { + return new Promise((resolve, reject) => { + let nuggetOpts = { + dir: directory + } + if (quiet) { + nuggetOpts.quiet = quiet + } + if (targetName) { + nuggetOpts.target = targetName + } + nugget(urls, nuggetOpts, (err) => { + if (err) { + reject(err) + } else { + resolve() + } + }) + }) +} + +async function verifyShasums (urls, isS3) { + let fileSource = isS3 ? 'S3' : 'GitHub' + console.log(`Downloading files from ${fileSource} to verify shasums`) + let downloadDir = await makeTempDir() + let filesToCheck = [] + try { + if (!isS3) { + await downloadFiles(urls, downloadDir) + filesToCheck = urls.map(url => { + let currentUrl = new URL(url) + return path.basename(currentUrl.pathname) + }).filter(file => file.indexOf('SHASUMS') === -1) + } else { + const s3VersionPath = `/atom-shell/dist/${pkgVersion}/` + await Promise.all(urls.map(async (url) => { + let currentUrl = new URL(url) + let dirname = path.dirname(currentUrl.pathname) + let filename = path.basename(currentUrl.pathname) + let s3VersionPathIdx = dirname.indexOf(s3VersionPath) + if (s3VersionPathIdx === -1 || dirname === s3VersionPath) { + if (s3VersionPathIdx !== -1 && filename.indexof('SHASUMS') === -1) { + filesToCheck.push(filename) + } + await downloadFiles(url, downloadDir, true) + } else { + let subDirectory = dirname.substr(s3VersionPathIdx + s3VersionPath.length) + let fileDirectory = path.join(downloadDir, subDirectory) + try { + fs.statSync(fileDirectory) + } catch (err) { + fs.mkdirSync(fileDirectory) + } + filesToCheck.push(path.join(subDirectory, filename)) + await downloadFiles(url, fileDirectory, true) + } + })) + } + } catch (err) { + console.log(`${fail} Error downloading files from ${fileSource}`, err) + process.exit(1) + } + console.log(`${pass} Successfully downloaded the files from ${fileSource}.`) + let checkerOpts + if (isS3) { + checkerOpts = { defaultTextEncoding: 'binary' } + } + + await validateChecksums({ + algorithm: 'sha256', + filesToCheck, + fileDirectory: downloadDir, + shaSumFile: 'SHASUMS256.txt', + checkerOpts, + fileSource + }) + + if (isS3) { + await validateChecksums({ + algorithm: 'sha1', + filesToCheck, + fileDirectory: downloadDir, + shaSumFile: 'SHASUMS.txt', + checkerOpts, + fileSource + }) + } +} + +async function validateChecksums (validationArgs) { + console.log(`Validating checksums for files from ${validationArgs.fileSource} ` + + `against ${validationArgs.shaSumFile}.`) + let shaSumFilePath = path.join(validationArgs.fileDirectory, validationArgs.shaSumFile) + let checker = new sumchecker.ChecksumValidator(validationArgs.algorithm, + shaSumFilePath, validationArgs.checkerOpts) + await checker.validate(validationArgs.fileDirectory, validationArgs.filesToCheck) + .catch(err => { + if (err instanceof sumchecker.ChecksumMismatchError) { + console.error(`${fail} The checksum of ${err.filename} from ` + + `${validationArgs.fileSource} did not match the shasum in ` + + `${validationArgs.shaSumFile}`) + } else if (err instanceof sumchecker.ChecksumParseError) { + console.error(`${fail} The checksum file ${validationArgs.shaSumFile} ` + + `from ${validationArgs.fileSource} could not be parsed.`, err) + } else if (err instanceof sumchecker.NoChecksumFoundError) { + console.error(`${fail} The file ${err.filename} from ` + + `${validationArgs.fileSource} was not in the shasum file ` + + `${validationArgs.shaSumFile}.`) + } else { + console.error(`${fail} Error matching files from ` + + `${validationArgs.fileSource} shasums in ${validationArgs.shaSumFile}.`, err) + } + process.exit(1) + }) + console.log(`${pass} All files from ${validationArgs.fileSource} match ` + + `shasums defined in ${validationArgs.shaSumFile}.`) +} + +async function cleanupReleaseBranch () { + console.log(`Cleaning up release branch.`) + let errorMessage = `Could not delete local release branch.` + let successMessage = `Successfully deleted local release branch.` + await callGit(['branch', '-D', 'release'], errorMessage, successMessage) + errorMessage = `Could not delete remote release branch.` + successMessage = `Successfully deleted remote release branch.` + return await callGit(['push', 'origin', ':release'], errorMessage, successMessage) +} + +async function callGit (args, errorMessage, successMessage) { + let gitResult = await GitProcess.exec(args, gitDir) + if (gitResult.exitCode === 0) { + console.log(`${pass} ${successMessage}`) + return true + } else { + console.log(`${fail} ${errorMessage} ${gitResult.stderr}`) + process.exit(1) + } +} + +makeRelease(args.validateRelease) diff --git a/script/upload-to-github.js b/script/upload-to-github.js index 82374ef49b6..7c3f8d9c11c 100644 --- a/script/upload-to-github.js +++ b/script/upload-to-github.js @@ -2,6 +2,10 @@ const GitHub = require('github') const github = new GitHub() github.authenticate({type: 'token', token: process.env.ELECTRON_GITHUB_TOKEN}) +if (process.argv.length < 5) { + console.log('Usage: upload-to-github filePath fileName releaseId') + process.exit(1) +} let filePath = process.argv[2] let fileName = process.argv[3] let releaseId = process.argv[4] @@ -13,9 +17,35 @@ let githubOpts = { filePath: filePath, name: fileName } -github.repos.uploadAsset(githubOpts).then(() => { - process.exit() -}).catch((err) => { - console.log(`Error uploading ${fileName} to GitHub:`, err) - process.exitCode = 1 -}) + +let retry = 0 + +function uploadToGitHub () { + github.repos.uploadAsset(githubOpts).then(() => { + console.log(`Successfully uploaded ${fileName} to GitHub.`) + process.exit() + }).catch((err) => { + if (retry < 4) { + console.log(`Error uploading ${fileName} to GitHub, will retry. Error was:`, err) + retry++ + github.repos.getRelease(githubOpts).then(release => { + let existingAssets = release.data.assets.filter(asset => asset.name === fileName) + if (existingAssets.length > 0) { + console.log(`${fileName} already exists; will delete before retrying upload.`) + github.repos.deleteAsset({ + owner: 'electron', + repo: 'electron', + id: existingAssets[0].id + }).then(uploadToGitHub).catch(uploadToGitHub) + } else { + uploadToGitHub() + } + }) + } else { + console.log(`Error retrying uploading ${fileName} to GitHub:`, err) + process.exitCode = 1 + } + }) +} + +uploadToGitHub() diff --git a/script/upload.py b/script/upload.py index cc1dd8e5d07..a1362bafb9d 100755 --- a/script/upload.py +++ b/script/upload.py @@ -36,17 +36,16 @@ PDB_NAME = get_zip_name(PROJECT_NAME, ELECTRON_VERSION, 'pdb') def main(): args = parse_args() - if not args.publish_release: - if not dist_newer_than_head(): - run_python_script('create-dist.py') + if not dist_newer_than_head(): + run_python_script('create-dist.py') - build_version = get_electron_build_version() - if not ELECTRON_VERSION.startswith(build_version): - error = 'Tag name ({0}) should match build version ({1})\n'.format( - ELECTRON_VERSION, build_version) - sys.stderr.write(error) - sys.stderr.flush() - return 1 + build_version = get_electron_build_version() + if not ELECTRON_VERSION.startswith(build_version): + error = 'Tag name ({0}) should match build version ({1})\n'.format( + ELECTRON_VERSION, build_version) + sys.stderr.write(error) + sys.stderr.flush() + return 1 github = GitHub(auth_token()) releases = github.repos(ELECTRON_REPO).releases.get() @@ -57,54 +56,44 @@ def main(): tag_exists = True break - assert tag_exists == args.overwrite, \ - 'You have to pass --overwrite to overwrite a published release' - - if not args.overwrite: - release = create_or_get_release_draft(github, releases, args.version, - tag_exists) - - if args.publish_release: - # Upload the Node SHASUMS*.txt. - run_python_script('upload-node-checksums.py', '-v', ELECTRON_VERSION) - - # Upload the index.json. - run_python_script('upload-index-json.py') - - # Create and upload the Electron SHASUMS*.txt - release_electron_checksums(release) - - # Press the publish button. - publish_release(github, release['id']) - - # TODO: run publish-to-npm script here - - # Do not upload other files when passed "-p". - return + if not args.upload_to_s3: + assert tag_exists == args.overwrite, \ + 'You have to pass --overwrite to overwrite a published release' + if not args.overwrite: + release = create_or_get_release_draft(github, releases, args.version, + tag_exists) # Upload Electron with GitHub Releases API. - upload_electron(github, release, os.path.join(DIST_DIR, DIST_NAME)) - upload_electron(github, release, os.path.join(DIST_DIR, SYMBOLS_NAME)) + upload_electron(github, release, os.path.join(DIST_DIR, DIST_NAME), + args.upload_to_s3) + upload_electron(github, release, os.path.join(DIST_DIR, SYMBOLS_NAME), + args.upload_to_s3) if PLATFORM == 'darwin': upload_electron(github, release, os.path.join(DIST_DIR, - 'electron-api.json')) - upload_electron(github, release, os.path.join(DIST_DIR, 'electron.d.ts')) - upload_electron(github, release, os.path.join(DIST_DIR, DSYM_NAME)) + 'electron-api.json'), args.upload_to_s3) + upload_electron(github, release, os.path.join(DIST_DIR, 'electron.d.ts'), + args.upload_to_s3) + upload_electron(github, release, os.path.join(DIST_DIR, DSYM_NAME), + args.upload_to_s3) elif PLATFORM == 'win32': - upload_electron(github, release, os.path.join(DIST_DIR, PDB_NAME)) + upload_electron(github, release, os.path.join(DIST_DIR, PDB_NAME), + args.upload_to_s3) # Upload free version of ffmpeg. ffmpeg = get_zip_name('ffmpeg', ELECTRON_VERSION) - upload_electron(github, release, os.path.join(DIST_DIR, ffmpeg)) + upload_electron(github, release, os.path.join(DIST_DIR, ffmpeg), + args.upload_to_s3) # Upload chromedriver and mksnapshot for minor version update. if parse_version(args.version)[2] == '0': chromedriver = get_zip_name('chromedriver', ELECTRON_VERSION) - upload_electron(github, release, os.path.join(DIST_DIR, chromedriver)) + upload_electron(github, release, os.path.join(DIST_DIR, chromedriver), + args.upload_to_s3) mksnapshot = get_zip_name('mksnapshot', ELECTRON_VERSION) - upload_electron(github, release, os.path.join(DIST_DIR, mksnapshot)) + upload_electron(github, release, os.path.join(DIST_DIR, mksnapshot), + args.upload_to_s3) - if PLATFORM == 'win32' and not tag_exists: + if PLATFORM == 'win32' and not tag_exists and not args.upload_to_s3: # Upload PDBs to Windows symbol server. run_python_script('upload-windows-pdb.py') @@ -123,6 +112,12 @@ def parse_args(): parser.add_argument('-p', '--publish-release', help='Publish the release', action='store_true') + parser.add_argument('-s', '--upload_to_s3', + help='Upload assets to s3 bucket', + dest='upload_to_s3', + action='store_true', + default=False, + required=False) return parser.parse_args() @@ -206,17 +201,17 @@ def create_release_draft(github, tag): return r -def release_electron_checksums(release): - checksums = run_python_script('merge-electron-checksums.py', - '-v', ELECTRON_VERSION) - filename = 'SHASUMS256.txt' - filepath = os.path.join(SOURCE_ROOT, filename) - with open(filepath, 'w') as sha_file: - sha_file.write(checksums.decode('utf-8')) - upload_io_to_github(release, filename, filepath) +def upload_electron(github, release, file_path, upload_to_s3): + # if upload_to_s3 is set, skip github upload. + if upload_to_s3: + bucket, access_key, secret_key = s3_config() + key_prefix = 'electron-artifacts/{0}'.format(release['tag_name']) + s3put(bucket, access_key, secret_key, os.path.dirname(file_path), + key_prefix, [file_path]) + upload_sha256_checksum(release['tag_name'], file_path, key_prefix) + return -def upload_electron(github, release, file_path): # Delete the original file before uploading in CI. filename = os.path.basename(file_path) if os.environ.has_key('CI'): @@ -239,7 +234,7 @@ def upload_electron(github, release, file_path): arm_filename = filename.replace('armv7l', 'arm') arm_file_path = os.path.join(os.path.dirname(file_path), arm_filename) shutil.copy2(file_path, arm_file_path) - upload_electron(github, release, arm_file_path) + upload_electron(github, release, arm_file_path, upload_to_s3) def upload_io_to_github(release, filename, filepath): @@ -249,9 +244,11 @@ def upload_io_to_github(release, filename, filepath): execute(['node', script_path, filepath, filename, str(release['id'])]) -def upload_sha256_checksum(version, file_path): +def upload_sha256_checksum(version, file_path, key_prefix=None): bucket, access_key, secret_key = s3_config() checksum_path = '{}.sha256sum'.format(file_path) + if key_prefix is None: + key_prefix = 'atom-shell/tmp/{0}'.format(version) sha256 = hashlib.sha256() with open(file_path, 'rb') as f: sha256.update(f.read()) @@ -260,12 +257,7 @@ def upload_sha256_checksum(version, file_path): with open(checksum_path, 'w') as checksum: checksum.write('{} *{}'.format(sha256.hexdigest(), filename)) s3put(bucket, access_key, secret_key, os.path.dirname(checksum_path), - 'atom-shell/tmp/{0}'.format(version), [checksum_path]) - - -def publish_release(github, release_id): - data = dict(draft=False) - github.repos(ELECTRON_REPO).releases(release_id).patch(data=data) + key_prefix, [checksum_path]) def auth_token(): diff --git a/spec/api-app-spec.js b/spec/api-app-spec.js index 58925deca48..5624c319bf3 100644 --- a/spec/api-app-spec.js +++ b/spec/api-app-spec.js @@ -11,17 +11,17 @@ 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 () { +describe('electron module', () => { + it('does not expose internal modules to require', () => { + assert.throws(() => { require('clipboard') }, /Cannot find module 'clipboard'/) }) - describe('require("electron")', function () { + describe('require("electron")', () => { let window = null - beforeEach(function () { + beforeEach(() => { window = new BrowserWindow({ show: false, width: 400, @@ -29,24 +29,22 @@ describe('electron module', function () { }) }) - afterEach(function () { - return closeWindow(window).then(function () { window = null }) + afterEach(() => { + return closeWindow(window).then(() => { window = null }) }) - it('always returns the internal electron module', function (done) { - ipcMain.once('answer', function () { - done() - }) - window.loadURL('file://' + path.join(__dirname, 'fixtures', 'api', 'electron-module-app', 'index.html')) + it('always returns the internal electron module', (done) => { + ipcMain.once('answer', () => done()) + window.loadURL(`file://${path.join(__dirname, 'fixtures', 'api', 'electron-module-app', 'index.html')}`) }) }) }) -describe('app module', function () { +describe('app module', () => { let server, secureUrl const certPath = path.join(__dirname, 'fixtures', 'certificates') - before(function () { + before(() => { const options = { key: fs.readFileSync(path.join(certPath, 'server.key')), cert: fs.readFileSync(path.join(certPath, 'server.pem')), @@ -58,7 +56,7 @@ describe('app module', function () { rejectUnauthorized: false } - server = https.createServer(options, function (req, res) { + server = https.createServer(options, (req, res) => { if (req.client.authorized) { res.writeHead(200) res.end('authorized') @@ -68,24 +66,24 @@ describe('app module', function () { } }) - server.listen(0, '127.0.0.1', function () { + server.listen(0, '127.0.0.1', () => { const port = server.address().port secureUrl = `https://127.0.0.1:${port}` }) }) - after(function () { + after(() => { server.close() }) - describe('app.getVersion()', function () { - it('returns the version field of package.json', function () { + describe('app.getVersion()', () => { + it('returns the version field of package.json', () => { assert.equal(app.getVersion(), '0.1.0') }) }) - describe('app.setVersion(version)', function () { - it('overrides the version', function () { + describe('app.setVersion(version)', () => { + it('overrides the version', () => { assert.equal(app.getVersion(), '0.1.0') app.setVersion('test-version') assert.equal(app.getVersion(), 'test-version') @@ -93,14 +91,14 @@ describe('app module', function () { }) }) - describe('app.getName()', function () { - it('returns the name field of package.json', function () { + describe('app.getName()', () => { + it('returns the name field of package.json', () => { assert.equal(app.getName(), 'Electron Test') }) }) - describe('app.setName(name)', function () { - it('overrides the name', function () { + describe('app.setName(name)', () => { + it('overrides the name', () => { assert.equal(app.getName(), 'Electron Test') app.setName('test-name') assert.equal(app.getName(), 'test-name') @@ -108,36 +106,35 @@ describe('app module', function () { }) }) - describe('app.getLocale()', function () { - it('should not be empty', function () { + describe('app.getLocale()', () => { + it('should not be empty', () => { assert.notEqual(app.getLocale(), '') }) }) - describe('app.isInApplicationsFolder()', function () { - it('should be false during tests', function () { + describe('app.isInApplicationsFolder()', () => { + it('should be false during tests', () => { if (process.platform !== 'darwin') return - assert.equal(app.isInApplicationsFolder(), false) }) }) - describe('app.exit(exitCode)', function () { - var appProcess = null + describe('app.exit(exitCode)', () => { + let appProcess = null - afterEach(function () { + afterEach(() => { if (appProcess != null) appProcess.kill() }) - it('emits a process exit event with the code', function (done) { - var appPath = path.join(__dirname, 'fixtures', 'api', 'quit-app') - var electronPath = remote.getGlobal('process').execPath - var output = '' + it('emits a process exit event with the code', (done) => { + const appPath = path.join(__dirname, 'fixtures', 'api', 'quit-app') + const electronPath = remote.getGlobal('process').execPath + let output = '' appProcess = ChildProcess.spawn(electronPath, [appPath]) - appProcess.stdout.on('data', function (data) { + appProcess.stdout.on('data', (data) => { output += data }) - appProcess.on('close', function (code) { + appProcess.on('close', (code) => { if (process.platform !== 'win32') { assert.notEqual(output.indexOf('Exit event with code: 123'), -1) } @@ -146,7 +143,7 @@ describe('app module', function () { }) }) - it('closes all windows', function (done) { + it('closes all windows', (done) => { var appPath = path.join(__dirname, 'fixtures', 'api', 'exit-closes-all-windows-app') var electronPath = remote.getGlobal('process').execPath appProcess = ChildProcess.spawn(electronPath, [appPath]) @@ -157,7 +154,7 @@ describe('app module', function () { }) }) - describe('app.makeSingleInstance', function () { + describe('app.makeSingleInstance', () => { it('prevents the second launch of app', function (done) { this.timeout(120000) const appPath = path.join(__dirname, 'fixtures', 'api', 'singleton') @@ -178,11 +175,11 @@ describe('app module', function () { }) }) - describe('app.relaunch', function () { + describe('app.relaunch', () => { let server = null const socketPath = process.platform === 'win32' ? '\\\\.\\pipe\\electron-app-relaunch' : '/tmp/electron-app-relaunch' - beforeEach(function (done) { + beforeEach((done) => { fs.unlink(socketPath, () => { server = net.createServer() server.listen(socketPath) @@ -190,14 +187,12 @@ describe('app module', function () { }) }) - afterEach(function (done) { + afterEach((done) => { server.close(() => { if (process.platform === 'win32') { done() } else { - fs.unlink(socketPath, () => { - done() - }) + fs.unlink(socketPath, () => done()) } }) }) @@ -206,11 +201,9 @@ describe('app module', function () { this.timeout(120000) let state = 'none' - server.once('error', (error) => { - done(error) - }) + server.once('error', (error) => done(error)) server.on('connection', (client) => { - client.once('data', function (data) { + client.once('data', (data) => { if (String(data) === 'false' && state === 'none') { state = 'first-launch' } else if (String(data) === 'true' && state === 'first-launch') { @@ -226,42 +219,36 @@ describe('app module', function () { }) }) - describe('app.setUserActivity(type, userInfo)', function () { - if (process.platform !== 'darwin') { - return - } + describe('app.setUserActivity(type, userInfo)', () => { + if (process.platform !== 'darwin') return - it('sets the current activity', function () { + it('sets the current activity', () => { app.setUserActivity('com.electron.testActivity', {testData: '123'}) assert.equal(app.getCurrentActivityType(), 'com.electron.testActivity') }) }) - xdescribe('app.importCertificate', function () { + xdescribe('app.importCertificate', () => { if (process.platform !== 'linux') return var w = null - afterEach(function () { - return closeWindow(w).then(function () { w = null }) - }) + afterEach(() => closeWindow(w).then(() => { w = null })) - it('can import certificate into platform cert store', function (done) { + it('can import certificate into platform cert store', (done) => { let options = { certificate: path.join(certPath, 'client.p12'), password: 'electron' } - w = new BrowserWindow({ - show: false - }) + w = new BrowserWindow({ show: false }) - w.webContents.on('did-finish-load', function () { + w.webContents.on('did-finish-load', () => { assert.equal(w.webContents.getTitle(), 'authorized') done() }) - ipcRenderer.once('select-client-certificate', function (event, webContentsId, list) { + ipcRenderer.once('select-client-certificate', (event, webContentsId, list) => { assert.equal(webContentsId, w.webContents.id) assert.equal(list.length, 1) assert.equal(list[0].issuerName, 'Intermediate CA') @@ -271,7 +258,7 @@ describe('app module', function () { event.sender.send('client-certificate-response', list[0]) }) - app.importCertificate(options, function (result) { + app.importCertificate(options, (result) => { assert(!result) ipcRenderer.sendSync('set-client-certificate-option', false) w.loadURL(secureUrl) @@ -279,79 +266,69 @@ describe('app module', function () { }) }) - describe('BrowserWindow events', function () { - var w = null + describe('BrowserWindow events', () => { + let w = null - afterEach(function () { - return closeWindow(w).then(function () { w = null }) - }) + afterEach(() => closeWindow(w).then(() => { w = null })) - it('should emit browser-window-focus event when window is focused', function (done) { - app.once('browser-window-focus', function (e, window) { + it('should emit browser-window-focus event when window is focused', (done) => { + app.once('browser-window-focus', (e, window) => { assert.equal(w.id, window.id) done() }) - w = new BrowserWindow({ - show: false - }) + w = new BrowserWindow({ show: false }) w.emit('focus') }) - it('should emit browser-window-blur event when window is blured', function (done) { - app.once('browser-window-blur', function (e, window) { + it('should emit browser-window-blur event when window is blured', (done) => { + app.once('browser-window-blur', (e, window) => { assert.equal(w.id, window.id) done() }) - w = new BrowserWindow({ - show: false - }) + w = new BrowserWindow({ show: false }) w.emit('blur') }) - it('should emit browser-window-created event when window is created', function (done) { - app.once('browser-window-created', function (e, window) { - setImmediate(function () { + it('should emit browser-window-created event when window is created', (done) => { + app.once('browser-window-created', (e, window) => { + setImmediate(() => { assert.equal(w.id, window.id) done() }) }) - w = new BrowserWindow({ - show: false - }) + w = new BrowserWindow({ show: false }) }) - it('should emit web-contents-created event when a webContents is created', function (done) { - app.once('web-contents-created', function (e, webContents) { - setImmediate(function () { + it('should emit web-contents-created event when a webContents is created', (done) => { + app.once('web-contents-created', (e, webContents) => { + setImmediate(() => { assert.equal(w.webContents.id, webContents.id) done() }) }) - w = new BrowserWindow({ - show: false - }) + w = new BrowserWindow({ show: false }) }) }) - describe('app.setBadgeCount API', function () { + describe('app.setBadgeCount API', () => { const shouldFail = process.platform === 'win32' || (process.platform === 'linux' && !app.isUnityRunning()) - afterEach(function () { + afterEach(() => { app.setBadgeCount(0) }) - it('returns false when failed', function () { + it('returns false when failed', () => { assert.equal(app.setBadgeCount(42), !shouldFail) }) - it('should set a badge count', function () { + it('should set a badge count', () => { app.setBadgeCount(42) assert.equal(app.getBadgeCount(), shouldFail ? 0 : 42) }) }) - describe('app.get/setLoginItemSettings API', function () { + describe('app.get/setLoginItemSettings API', () => { if (process.platform === 'linux') return const updateExe = path.resolve(path.dirname(process.execPath), '..', 'Update.exe') @@ -360,17 +337,17 @@ describe('app module', function () { '--process-start-args', `"--hidden"` ] - beforeEach(function () { + beforeEach(() => { app.setLoginItemSettings({openAtLogin: false}) app.setLoginItemSettings({openAtLogin: false, path: updateExe, args: processStartArgs}) }) - afterEach(function () { + afterEach(() => { app.setLoginItemSettings({openAtLogin: false}) app.setLoginItemSettings({openAtLogin: false, path: updateExe, args: processStartArgs}) }) - it('returns the login item status of the app', function () { + it('returns the login item status of the app', () => { app.setLoginItemSettings({openAtLogin: true}) assert.deepEqual(app.getLoginItemSettings(), { openAtLogin: true, @@ -409,35 +386,35 @@ describe('app module', function () { }) }) - describe('isAccessibilitySupportEnabled API', function () { - it('returns whether the Chrome has accessibility APIs enabled', function () { + describe('isAccessibilitySupportEnabled API', () => { + it('returns whether the Chrome has accessibility APIs enabled', () => { assert.equal(typeof app.isAccessibilitySupportEnabled(), 'boolean') }) }) - describe('getPath(name)', function () { - it('returns paths that exist', function () { + describe('getPath(name)', () => { + it('returns paths that exist', () => { assert.equal(fs.existsSync(app.getPath('exe')), true) assert.equal(fs.existsSync(app.getPath('home')), true) assert.equal(fs.existsSync(app.getPath('temp')), true) }) - it('throws an error when the name is invalid', function () { - assert.throws(function () { + it('throws an error when the name is invalid', () => { + assert.throws(() => { app.getPath('does-not-exist') }, /Failed to get 'does-not-exist' path/) }) - it('returns the overridden path', function () { + it('returns the overridden path', () => { app.setPath('music', __dirname) assert.equal(app.getPath('music'), __dirname) }) }) - xdescribe('select-client-certificate event', function () { + xdescribe('select-client-certificate event', () => { let w = null - beforeEach(function () { + beforeEach(() => { w = new BrowserWindow({ show: false, webPreferences: { @@ -446,12 +423,10 @@ describe('app module', function () { }) }) - afterEach(function () { - return closeWindow(w).then(function () { w = null }) - }) + afterEach(() => closeWindow(w).then(() => { w = null })) - it('can respond with empty certificate list', function (done) { - w.webContents.on('did-finish-load', function () { + it('can respond with empty certificate list', (done) => { + w.webContents.on('did-finish-load', () => { assert.equal(w.webContents.getTitle(), 'denied') server.close() done() @@ -498,7 +473,7 @@ describe('app module', function () { }) }) - describe('getFileIcon() API', function () { + describe('getFileIcon() API', () => { // FIXME Get these specs running on Linux CI if (process.platform === 'linux' && isCI) return @@ -509,16 +484,16 @@ describe('app module', function () { large: process.platform === 'win32' ? 32 : 48 } - it('fetches a non-empty icon', function (done) { - app.getFileIcon(iconPath, function (err, icon) { + it('fetches a non-empty icon', (done) => { + app.getFileIcon(iconPath, (err, icon) => { assert.equal(err, null) assert.equal(icon.isEmpty(), false) done() }) }) - it('fetches normal icon size by default', function (done) { - app.getFileIcon(iconPath, function (err, icon) { + it('fetches normal icon size by default', (done) => { + app.getFileIcon(iconPath, (err, icon) => { const size = icon.getSize() assert.equal(err, null) assert.equal(size.height, sizes.normal) @@ -527,9 +502,9 @@ describe('app module', function () { }) }) - describe('size option', function () { - it('fetches a small icon', function (done) { - app.getFileIcon(iconPath, { size: 'small' }, function (err, icon) { + describe('size option', () => { + it('fetches a small icon', (done) => { + app.getFileIcon(iconPath, { size: 'small' }, (err, icon) => { const size = icon.getSize() assert.equal(err, null) assert.equal(size.height, sizes.small) @@ -538,7 +513,7 @@ describe('app module', function () { }) }) - it('fetches a normal icon', function (done) { + it('fetches a normal icon', (done) => { app.getFileIcon(iconPath, { size: 'normal' }, function (err, icon) { const size = icon.getSize() assert.equal(err, null) @@ -548,7 +523,7 @@ describe('app module', function () { }) }) - it('fetches a large icon', function (done) { + it('fetches a large icon', (done) => { // macOS does not support large icons if (process.platform === 'darwin') return done() @@ -563,8 +538,8 @@ describe('app module', function () { }) }) - describe('getAppMetrics() API', function () { - it('returns memory and cpu stats of all running electron processes', function () { + describe('getAppMetrics() API', () => { + it('returns memory and cpu stats of all running electron processes', () => { const appMetrics = app.getAppMetrics() assert.ok(appMetrics.length > 0, 'App memory info object is not > 0') const types = [] @@ -588,15 +563,15 @@ describe('app module', function () { }) }) - describe('getGPUFeatureStatus() API', function () { - it('returns the graphic features statuses', function () { + describe('getGPUFeatureStatus() API', () => { + it('returns the graphic features statuses', () => { const features = app.getGPUFeatureStatus() assert.equal(typeof features.webgl, 'string') assert.equal(typeof features.gpu_compositing, 'string') }) }) - describe('mixed sandbox option', function () { + describe('mixed sandbox option', () => { // FIXME Get these specs running on Linux if (process.platform === 'linux') return @@ -604,7 +579,7 @@ describe('app module', function () { let server = null const socketPath = process.platform === 'win32' ? '\\\\.\\pipe\\electron-mixed-sandbox' : '/tmp/electron-mixed-sandbox' - beforeEach(function (done) { + beforeEach((done) => { fs.unlink(socketPath, () => { server = net.createServer() server.listen(socketPath) @@ -612,18 +587,14 @@ describe('app module', function () { }) }) - afterEach(function (done) { - if (appProcess != null) { - appProcess.kill() - } + afterEach((done) => { + if (appProcess != null) appProcess.kill() server.close(() => { if (process.platform === 'win32') { done() } else { - fs.unlink(socketPath, () => { - done() - }) + fs.unlink(socketPath, () => done()) } }) }) @@ -680,9 +651,9 @@ describe('app module', function () { }) }) - describe('disableDomainBlockingFor3DAPIs() API', function () { - it('throws when called after app is ready', function () { - assert.throws(function () { + describe('disableDomainBlockingFor3DAPIs() API', () => { + it('throws when called after app is ready', () => { + assert.throws(() => { app.disableDomainBlockingFor3DAPIs() }, /before app is ready/) }) diff --git a/spec/api-browser-view-spec.js b/spec/api-browser-view-spec.js index 11cf582b002..6debec2a768 100644 --- a/spec/api-browser-view-spec.js +++ b/spec/api-browser-view-spec.js @@ -6,11 +6,11 @@ const {closeWindow} = require('./window-helpers') const {remote} = require('electron') const {BrowserView, BrowserWindow} = remote -describe('BrowserView module', function () { - var w = null - var view = null +describe('BrowserView module', () => { + let w = null + let view = null - beforeEach(function () { + beforeEach(() => { w = new BrowserWindow({ show: false, width: 400, @@ -21,68 +21,68 @@ describe('BrowserView module', function () { }) }) - afterEach(function () { + afterEach(() => { if (view) { view.destroy() view = null } - return closeWindow(w).then(function () { w = null }) + return closeWindow(w).then(() => { w = null }) }) - describe('BrowserView.setBackgroundColor()', function () { - it('does not throw for valid args', function () { + describe('BrowserView.setBackgroundColor()', () => { + it('does not throw for valid args', () => { view = new BrowserView() view.setBackgroundColor('#000') }) - it('throws for invalid args', function () { + it('throws for invalid args', () => { view = new BrowserView() - assert.throws(function () { + assert.throws(() => { view.setBackgroundColor(null) }, /conversion failure/) }) }) - describe('BrowserView.setAutoResize()', function () { - it('does not throw for valid args', function () { + describe('BrowserView.setAutoResize()', () => { + it('does not throw for valid args', () => { view = new BrowserView() view.setAutoResize({}) view.setAutoResize({ width: true, height: false }) }) - it('throws for invalid args', function () { + it('throws for invalid args', () => { view = new BrowserView() - assert.throws(function () { + assert.throws(() => { view.setAutoResize(null) }, /conversion failure/) }) }) - describe('BrowserView.setBounds()', function () { - it('does not throw for valid args', function () { + describe('BrowserView.setBounds()', () => { + it('does not throw for valid args', () => { view = new BrowserView() view.setBounds({ x: 0, y: 0, width: 1, height: 1 }) }) - it('throws for invalid args', function () { + it('throws for invalid args', () => { view = new BrowserView() - assert.throws(function () { + assert.throws(() => { view.setBounds(null) }, /conversion failure/) - assert.throws(function () { + assert.throws(() => { view.setBounds({}) }, /conversion failure/) }) }) - describe('BrowserWindow.setBrowserView()', function () { - it('does not throw for valid args', function () { + describe('BrowserWindow.setBrowserView()', () => { + it('does not throw for valid args', () => { view = new BrowserView() w.setBrowserView(view) }) - it('does not throw if called multiple times with same view', function () { + it('does not throw if called multiple times with same view', () => { view = new BrowserView() w.setBrowserView(view) w.setBrowserView(view) @@ -90,8 +90,23 @@ describe('BrowserView module', function () { }) }) - describe('BrowserView.webContents.getOwnerBrowserWindow()', function () { - it('points to owning window', function () { + describe('BrowserWindow.getBrowserView()', () => { + it('returns the set view', () => { + view = new BrowserView() + w.setBrowserView(view) + assert.notEqual(view.id, null) + let view2 = w.getBrowserView() + assert.equal(view2.webContents.id, view.webContents.id) + }) + + it('returns null if none is set', () => { + let view = w.getBrowserView() + assert.equal(null, view) + }) + }) + + describe('BrowserView.webContents.getOwnerBrowserWindow()', () => { + it('points to owning window', () => { view = new BrowserView() assert.ok(!view.webContents.getOwnerBrowserWindow()) w.setBrowserView(view) @@ -101,8 +116,8 @@ describe('BrowserView module', function () { }) }) - describe('BrowserView.fromId()', function () { - it('returns the view with given id', function () { + describe('BrowserView.fromId()', () => { + it('returns the view with given id', () => { view = new BrowserView() w.setBrowserView(view) assert.notEqual(view.id, null) diff --git a/spec/api-browser-window-spec.js b/spec/api-browser-window-spec.js index 6cb668206d4..185b8fba820 100644 --- a/spec/api-browser-window-spec.js +++ b/spec/api-browser-window-spec.js @@ -14,13 +14,14 @@ const {app, ipcMain, BrowserWindow, protocol, webContents} = remote const isCI = remote.getGlobal('isCi') const nativeModulesEnabled = remote.getGlobal('nativeModulesEnabled') -describe('BrowserWindow module', function () { - var fixtures = path.resolve(__dirname, 'fixtures') - var w = null - var ws = null - var server, postData +describe('BrowserWindow module', () => { + const fixtures = path.resolve(__dirname, 'fixtures') + let w = null + let ws = null + let server + let postData - before(function (done) { + before((done) => { const filePath = path.join(fixtures, 'pages', 'a.html') const fileStats = fs.statSync(filePath) postData = [ @@ -36,14 +37,12 @@ describe('BrowserWindow module', function () { modificationTime: fileStats.mtime.getTime() / 1000 } ] - server = http.createServer(function (req, res) { + server = http.createServer((req, res) => { function respond () { if (req.method === 'POST') { let body = '' req.on('data', (data) => { - if (data) { - body += data - } + if (data) body += data }) req.on('end', () => { let parsedData = qs.parse(body) @@ -61,18 +60,18 @@ describe('BrowserWindow module', function () { } setTimeout(respond, req.url.includes('slow') ? 200 : 0) }) - server.listen(0, '127.0.0.1', function () { - server.url = 'http://127.0.0.1:' + server.address().port + server.listen(0, '127.0.0.1', () => { + server.url = `http://127.0.0.1:${server.address().port}` done() }) }) - after(function () { + after(() => { server.close() server = null }) - beforeEach(function () { + beforeEach(() => { w = new BrowserWindow({ show: false, width: 400, @@ -83,14 +82,14 @@ describe('BrowserWindow module', function () { }) }) - afterEach(function () { - return closeWindow(w).then(function () { w = null }) + afterEach(() => { + return closeWindow(w).then(() => { w = null }) }) - describe('BrowserWindow.close()', function () { + describe('BrowserWindow.close()', () => { let server - before(function (done) { + before((done) => { server = http.createServer((request, response) => { switch (request.url) { case '/404': @@ -119,36 +118,28 @@ describe('BrowserWindow module', function () { }) }) - after(function () { + after(() => { server.close() server = null }) - it('should emit unload handler', function (done) { - w.webContents.on('did-finish-load', function () { - w.close() - }) - w.once('closed', function () { - var test = path.join(fixtures, 'api', 'unload') - var content = fs.readFileSync(test) + it('should emit unload handler', (done) => { + w.webContents.on('did-finish-load', () => { w.close() }) + w.once('closed', () => { + const test = path.join(fixtures, 'api', 'unload') + const content = fs.readFileSync(test) fs.unlinkSync(test) assert.equal(String(content), 'unload') done() }) w.loadURL('file://' + path.join(fixtures, 'api', 'unload.html')) }) - - it('should emit beforeunload handler', function (done) { - w.once('onbeforeunload', function () { - done() - }) - w.webContents.on('did-finish-load', function () { - w.close() - }) - w.loadURL('file://' + path.join(fixtures, 'api', 'beforeunload-false.html')) + it('should emit beforeunload handler', (done) => { + w.once('onbeforeunload', () => { done() }) + w.webContents.on('did-finish-load', () => { w.close() }) + w.loadURL(`file://${path.join(fixtures, 'api', 'beforeunload-false.html')}`) }) - - it('should not crash when invoked synchronously inside navigation observer', function (done) { + it('should not crash when invoked synchronously inside navigation observer', (done) => { const events = [ { name: 'did-start-loading', url: `${server.url}/200` }, { name: 'did-get-redirect-request', url: `${server.url}/301` }, @@ -174,69 +165,60 @@ describe('BrowserWindow module', function () { } let gen = genNavigationEvent() - ipcRenderer.on(responseEvent, function () { + ipcRenderer.on(responseEvent, () => { if (!gen.next().value) done() }) gen.next() }) }) - describe('window.close()', function () { - it('should emit unload handler', function (done) { - w.once('closed', function () { - var test = path.join(fixtures, 'api', 'close') - var content = fs.readFileSync(test) + describe('window.close()', () => { + it('should emit unload handler', (done) => { + w.once('closed', () => { + const test = path.join(fixtures, 'api', 'close') + const content = fs.readFileSync(test) fs.unlinkSync(test) assert.equal(String(content), 'close') done() }) - w.loadURL('file://' + path.join(fixtures, 'api', 'close.html')) + w.loadURL(`file://${path.join(fixtures, 'api', 'close.html')}`) }) - - it('should emit beforeunload handler', function (done) { - w.once('onbeforeunload', function () { - done() - }) - w.loadURL('file://' + path.join(fixtures, 'api', 'close-beforeunload-false.html')) + it('should emit beforeunload handler', (done) => { + w.once('onbeforeunload', () => { done() }) + w.loadURL(`file://${path.join(fixtures, 'api', 'close-beforeunload-false.html')}`) }) }) - describe('BrowserWindow.destroy()', function () { - it('prevents users to access methods of webContents', function () { + describe('BrowserWindow.destroy()', () => { + it('prevents users to access methods of webContents', () => { const contents = w.webContents w.destroy() - assert.throws(function () { + assert.throws(() => { contents.getId() }, /Object has been destroyed/) }) }) - describe('BrowserWindow.loadURL(url)', function () { - it('should emit did-start-loading event', function (done) { - w.webContents.on('did-start-loading', function () { - done() - }) + describe('BrowserWindow.loadURL(url)', () => { + it('should emit did-start-loading event', (done) => { + w.webContents.on('did-start-loading', () => { done() }) w.loadURL('about:blank') }) - - it('should emit ready-to-show event', function (done) { - w.on('ready-to-show', function () { - done() - }) + it('should emit ready-to-show event', (done) => { + w.on('ready-to-show', () => { done() }) w.loadURL('about:blank') }) - - it('should emit did-get-response-details event', function (done) { + it('should emit did-get-response-details event', (done) => { // expected {fileName: resourceType} pairs - var expectedResources = { + const expectedResources = { 'did-get-response-details.html': 'mainFrame', 'logo.png': 'image' } - var responses = 0 - w.webContents.on('did-get-response-details', function (event, status, newUrl, oldUrl, responseCode, method, referrer, headers, resourceType) { - responses++ - var fileName = newUrl.slice(newUrl.lastIndexOf('/') + 1) - var expectedType = expectedResources[fileName] + let responses = 0 + w.webContents.on('did-get-response-details', (event, status, newUrl, oldUrl, responseCode, method, referrer, headers, resourceType) => { + responses += 1 + const fileName = newUrl.slice(newUrl.lastIndexOf('/') + 1) + const expectedType = expectedResources[fileName] assert(!!expectedType, `Unexpected response details for ${newUrl}`) assert(typeof status === 'boolean', 'status should be boolean') assert.equal(responseCode, 200) @@ -245,15 +227,12 @@ describe('BrowserWindow module', function () { assert(!!headers, 'headers should be present') assert(typeof headers === 'object', 'headers should be object') assert.equal(resourceType, expectedType, 'Incorrect resourceType') - if (responses === Object.keys(expectedResources).length) { - done() - } + if (responses === Object.keys(expectedResources).length) done() }) - w.loadURL('file://' + path.join(fixtures, 'pages', 'did-get-response-details.html')) + w.loadURL(`file://${path.join(fixtures, 'pages', 'did-get-response-details.html')}`) }) - - it('should emit did-fail-load event for files that do not exist', function (done) { - w.webContents.on('did-fail-load', function (event, code, desc, url, isMainFrame) { + it('should emit did-fail-load event for files that do not exist', (done) => { + w.webContents.on('did-fail-load', (event, code, desc, url, isMainFrame) => { assert.equal(code, -6) assert.equal(desc, 'ERR_FILE_NOT_FOUND') assert.equal(isMainFrame, true) @@ -261,9 +240,8 @@ describe('BrowserWindow module', function () { }) w.loadURL('file://a.txt') }) - - it('should emit did-fail-load event for invalid URL', function (done) { - w.webContents.on('did-fail-load', function (event, code, desc, url, isMainFrame) { + it('should emit did-fail-load event for invalid URL', (done) => { + w.webContents.on('did-fail-load', (event, code, desc, url, isMainFrame) => { assert.equal(desc, 'ERR_INVALID_URL') assert.equal(code, -300) assert.equal(isMainFrame, true) @@ -271,25 +249,22 @@ describe('BrowserWindow module', function () { }) w.loadURL('http://example:port') }) - - it('should set `mainFrame = false` on did-fail-load events in iframes', function (done) { - w.webContents.on('did-fail-load', function (event, code, desc, url, isMainFrame) { + it('should set `mainFrame = false` on did-fail-load events in iframes', (done) => { + w.webContents.on('did-fail-load', (event, code, desc, url, isMainFrame) => { assert.equal(isMainFrame, false) done() }) - w.loadURL('file://' + path.join(fixtures, 'api', 'did-fail-load-iframe.html')) + w.loadURL(`file://${path.join(fixtures, 'api', 'did-fail-load-iframe.html')}`) }) - - it('does not crash in did-fail-provisional-load handler', function (done) { - w.webContents.once('did-fail-provisional-load', function () { + it('does not crash in did-fail-provisional-load handler', (done) => { + w.webContents.once('did-fail-provisional-load', () => { w.loadURL('http://127.0.0.1:11111') done() }) w.loadURL('http://127.0.0.1:11111') }) - - it('should emit did-fail-load event for URL exceeding character limit', function (done) { - w.webContents.on('did-fail-load', function (event, code, desc, url, isMainFrame) { + it('should emit did-fail-load event for URL exceeding character limit', (done) => { + w.webContents.on('did-fail-load', (event, code, desc, url, isMainFrame) => { assert.equal(desc, 'ERR_INVALID_URL') assert.equal(code, -300) assert.equal(isMainFrame, true) @@ -299,17 +274,14 @@ describe('BrowserWindow module', function () { w.loadURL(`data:image/png;base64,${data}`) }) - describe('POST navigations', function () { - afterEach(() => { - w.webContents.session.webRequest.onBeforeSendHeaders(null) - }) + describe('POST navigations', () => { + afterEach(() => { w.webContents.session.webRequest.onBeforeSendHeaders(null) }) - it('supports specifying POST data', function (done) { + it('supports specifying POST data', (done) => { w.webContents.on('did-finish-load', () => done()) w.loadURL(server.url, {postData: postData}) }) - - it('sets the content type header on URL encoded forms', function (done) { + it('sets the content type header on URL encoded forms', (done) => { w.webContents.on('did-finish-load', () => { w.webContents.session.webRequest.onBeforeSendHeaders((details, callback) => { assert.equal(details.requestHeaders['content-type'], 'application/x-www-form-urlencoded') @@ -325,8 +297,7 @@ describe('BrowserWindow module', function () { }) w.loadURL(server.url) }) - - it('sets the content type header on multi part forms', function (done) { + it('sets the content type header on multi part forms', (done) => { w.webContents.on('did-finish-load', () => { w.webContents.session.webRequest.onBeforeSendHeaders((details, callback) => { assert(details.requestHeaders['content-type'].startsWith('multipart/form-data; boundary=----WebKitFormBoundary')) @@ -350,7 +321,7 @@ describe('BrowserWindow module', function () { }) it('should support support base url for data urls', (done) => { - ipcMain.once('answer', function (event, test) { + ipcMain.once('answer', (event, test) => { assert.equal(test, 'test') done() }) @@ -358,33 +329,27 @@ describe('BrowserWindow module', function () { }) }) - describe('will-navigate event', function () { + describe('will-navigate event', () => { it('allows the window to be closed from the event listener', (done) => { ipcRenderer.send('close-on-will-navigate', w.id) - ipcRenderer.once('closed-on-will-navigate', () => { - done() - }) - w.loadURL('file://' + fixtures + '/pages/will-navigate.html') + ipcRenderer.once('closed-on-will-navigate', () => { done() }) + w.loadURL(`file://${fixtures}/pages/will-navigate.html`) }) }) - describe('BrowserWindow.show()', function () { - if (isCI) { - return - } + describe('BrowserWindow.show()', () => { + if (isCI) return - it('should focus on window', function () { + it('should focus on window', () => { w.show() assert(w.isFocused()) }) - - it('should make the window visible', function () { + it('should make the window visible', () => { w.show() assert(w.isVisible()) }) - - it('emits when window is shown', function (done) { - w.once('show', function () { + it('emits when window is shown', (done) => { + w.once('show', () => { assert.equal(w.isVisible(), true) done() }) @@ -392,25 +357,21 @@ describe('BrowserWindow module', function () { }) }) - describe('BrowserWindow.hide()', function () { - if (isCI) { - return - } + describe('BrowserWindow.hide()', () => { + if (isCI) return - it('should defocus on window', function () { + it('should defocus on window', () => { w.hide() assert(!w.isFocused()) }) - - it('should make the window not visible', function () { + it('should make the window not visible', () => { w.show() w.hide() assert(!w.isVisible()) }) - - it('emits when window is hidden', function (done) { + it('emits when window is hidden', (done) => { w.show() - w.once('hide', function () { + w.once('hide', () => { assert.equal(w.isVisible(), false) done() }) @@ -418,46 +379,46 @@ describe('BrowserWindow module', function () { }) }) - describe('BrowserWindow.showInactive()', function () { - it('should not focus on window', function () { + describe('BrowserWindow.showInactive()', () => { + it('should not focus on window', () => { w.showInactive() assert(!w.isFocused()) }) }) - describe('BrowserWindow.focus()', function () { - it('does not make the window become visible', function () { + describe('BrowserWindow.focus()', () => { + it('does not make the window become visible', () => { assert.equal(w.isVisible(), false) w.focus() assert.equal(w.isVisible(), false) }) }) - describe('BrowserWindow.blur()', function () { - it('removes focus from window', function () { + describe('BrowserWindow.blur()', () => { + it('removes focus from window', () => { w.blur() assert(!w.isFocused()) }) }) - describe('BrowserWindow.capturePage(rect, callback)', function () { - it('calls the callback with a Buffer', function (done) { + describe('BrowserWindow.capturePage(rect, callback)', () => { + it('calls the callback with a Buffer', (done) => { w.capturePage({ x: 0, y: 0, width: 100, height: 100 - }, function (image) { + }, (image) => { assert.equal(image.isEmpty(), true) done() }) }) }) - describe('BrowserWindow.setSize(width, height)', function () { - it('sets the window size', function (done) { - var size = [300, 400] - w.once('resize', function () { + describe('BrowserWindow.setSize(width, height)', () => { + it('sets the window size', (done) => { + const size = [300, 400] + w.once('resize', () => { assertBoundsEqual(w.getSize(), size) done() }) @@ -465,8 +426,8 @@ describe('BrowserWindow module', function () { }) }) - describe('BrowserWindow.setMinimum/MaximumSize(width, height)', function () { - it('sets the maximum and minimum size of the window', function () { + describe('BrowserWindow.setMinimum/MaximumSize(width, height)', () => { + it('sets the maximum and minimum size of the window', () => { assert.deepEqual(w.getMinimumSize(), [0, 0]) assert.deepEqual(w.getMaximumSize(), [0, 0]) @@ -480,12 +441,12 @@ describe('BrowserWindow module', function () { }) }) - describe('BrowserWindow.setAspectRatio(ratio)', function () { - it('resets the behaviour when passing in 0', function (done) { - var size = [300, 400] + describe('BrowserWindow.setAspectRatio(ratio)', () => { + it('resets the behaviour when passing in 0', (done) => { + const size = [300, 400] w.setAspectRatio(1 / 2) w.setAspectRatio(0) - w.once('resize', function () { + w.once('resize', () => { assertBoundsEqual(w.getSize(), size) done() }) @@ -493,11 +454,11 @@ describe('BrowserWindow module', function () { }) }) - describe('BrowserWindow.setPosition(x, y)', function () { - it('sets the window position', function (done) { - var pos = [10, 10] - w.once('move', function () { - var newPos = w.getPosition() + describe('BrowserWindow.setPosition(x, y)', () => { + it('sets the window position', (done) => { + const pos = [10, 10] + w.once('move', () => { + const newPos = w.getPosition() assert.equal(newPos[0], pos[0]) assert.equal(newPos[1], pos[1]) done() @@ -506,16 +467,15 @@ describe('BrowserWindow module', function () { }) }) - describe('BrowserWindow.setContentSize(width, height)', function () { - it('sets the content size', function () { - var size = [400, 400] + describe('BrowserWindow.setContentSize(width, height)', () => { + it('sets the content size', () => { + const size = [400, 400] w.setContentSize(size[0], size[1]) var after = w.getContentSize() assert.equal(after[0], size[0]) assert.equal(after[1], size[1]) }) - - it('works for a frameless window', function () { + it('works for a frameless window', () => { w.destroy() w = new BrowserWindow({ show: false, @@ -523,25 +483,24 @@ describe('BrowserWindow module', function () { width: 400, height: 400 }) - var size = [400, 400] + const size = [400, 400] w.setContentSize(size[0], size[1]) - var after = w.getContentSize() + const after = w.getContentSize() assert.equal(after[0], size[0]) assert.equal(after[1], size[1]) }) }) - describe('BrowserWindow.setContentBounds(bounds)', function () { - it('sets the content size and position', function (done) { - var bounds = {x: 10, y: 10, width: 250, height: 250} - w.once('resize', function () { + describe('BrowserWindow.setContentBounds(bounds)', () => { + it('sets the content size and position', (done) => { + const bounds = {x: 10, y: 10, width: 250, height: 250} + w.once('resize', () => { assertBoundsEqual(w.getContentBounds(), bounds) done() }) w.setContentBounds(bounds) }) - - it('works for a frameless window', function (done) { + it('works for a frameless window', (done) => { w.destroy() w = new BrowserWindow({ show: false, @@ -549,8 +508,8 @@ describe('BrowserWindow module', function () { width: 300, height: 300 }) - var bounds = {x: 10, y: 10, width: 250, height: 250} - w.once('resize', function () { + const bounds = {x: 10, y: 10, width: 250, height: 250} + w.once('resize', () => { assert.deepEqual(w.getContentBounds(), bounds) done() }) @@ -558,9 +517,9 @@ describe('BrowserWindow module', function () { }) }) - describe('BrowserWindow.setProgressBar(progress)', function () { - it('sets the progress', function () { - assert.doesNotThrow(function () { + describe('BrowserWindow.setProgressBar(progress)', () => { + it('sets the progress', () => { + assert.doesNotThrow(() => { if (process.platform === 'darwin') { app.dock.setIcon(path.join(fixtures, 'assets', 'logo.png')) } @@ -572,28 +531,25 @@ describe('BrowserWindow module', function () { w.setProgressBar(-1) }) }) - - it('sets the progress using "paused" mode', function () { - assert.doesNotThrow(function () { + it('sets the progress using "paused" mode', () => { + assert.doesNotThrow(() => { w.setProgressBar(0.5, {mode: 'paused'}) }) }) - - it('sets the progress using "error" mode', function () { - assert.doesNotThrow(function () { + it('sets the progress using "error" mode', () => { + assert.doesNotThrow(() => { w.setProgressBar(0.5, {mode: 'error'}) }) }) - - it('sets the progress using "normal" mode', function () { - assert.doesNotThrow(function () { + it('sets the progress using "normal" mode', () => { + assert.doesNotThrow(() => { w.setProgressBar(0.5, {mode: 'normal'}) }) }) }) - describe('BrowserWindow.setAlwaysOnTop(flag, level)', function () { - it('sets the window as always on top', function () { + describe('BrowserWindow.setAlwaysOnTop(flag, level)', () => { + it('sets the window as always on top', () => { assert.equal(w.isAlwaysOnTop(), false) w.setAlwaysOnTop(true, 'screen-saver') assert.equal(w.isAlwaysOnTop(), true) @@ -602,26 +558,23 @@ describe('BrowserWindow module', function () { w.setAlwaysOnTop(true) assert.equal(w.isAlwaysOnTop(), true) }) - - it('raises an error when relativeLevel is out of bounds', function () { + it('raises an error when relativeLevel is out of bounds', () => { if (process.platform !== 'darwin') return - assert.throws(function () { + assert.throws(() => { w.setAlwaysOnTop(true, '', -2147483644) }) - assert.throws(function () { + assert.throws(() => { w.setAlwaysOnTop(true, '', 2147483632) }) }) }) - describe('BrowserWindow.alwaysOnTop() resets level on minimize', function () { - if (process.platform !== 'darwin') { - return - } + describe('BrowserWindow.alwaysOnTop() resets level on minimize', () => { + if (process.platform !== 'darwin') return - it('resets the windows level on minimize', function () { + it('resets the windows level on minimize', () => { assert.equal(w.isAlwaysOnTop(), false) w.setAlwaysOnTop(true, 'screen-saver') assert.equal(w.isAlwaysOnTop(), true) @@ -637,10 +590,8 @@ describe('BrowserWindow module', function () { it('is not available on non-macOS platforms', () => { assert.ok(!w.setAutoHideCursor) }) - return } - it('allows changing cursor auto-hiding', () => { assert.doesNotThrow(() => { w.setAutoHideCursor(false) @@ -651,9 +602,7 @@ describe('BrowserWindow module', function () { describe('BrowserWindow.selectPreviousTab()', () => { it('does not throw', () => { - if (process.platform !== 'darwin') { - return - } + if (process.platform !== 'darwin') return assert.doesNotThrow(() => { w.selectPreviousTab() @@ -663,9 +612,7 @@ describe('BrowserWindow module', function () { describe('BrowserWindow.selectNextTab()', () => { it('does not throw', () => { - if (process.platform !== 'darwin') { - return - } + if (process.platform !== 'darwin') return assert.doesNotThrow(() => { w.selectNextTab() @@ -675,9 +622,7 @@ describe('BrowserWindow module', function () { describe('BrowserWindow.mergeAllWindows()', () => { it('does not throw', () => { - if (process.platform !== 'darwin') { - return - } + if (process.platform !== 'darwin') return assert.doesNotThrow(() => { w.mergeAllWindows() @@ -687,9 +632,7 @@ describe('BrowserWindow module', function () { describe('BrowserWindow.moveTabToNewWindow()', () => { it('does not throw', () => { - if (process.platform !== 'darwin') { - return - } + if (process.platform !== 'darwin') return assert.doesNotThrow(() => { w.moveTabToNewWindow() @@ -699,9 +642,7 @@ describe('BrowserWindow module', function () { describe('BrowserWindow.toggleTabBar()', () => { it('does not throw', () => { - if (process.platform !== 'darwin') { - return - } + if (process.platform !== 'darwin') return assert.doesNotThrow(() => { w.toggleTabBar() @@ -709,11 +650,10 @@ describe('BrowserWindow module', function () { }) }) - describe('BrowserWindow.addTabbedWindow()', function (done) { - it('does not throw', function () { - if (process.platform !== 'darwin') { - return - } + describe('BrowserWindow.addTabbedWindow()', (done) => { + it('does not throw', () => { + if (process.platform !== 'darwin') return + const tabbedWindow = new BrowserWindow({}) assert.doesNotThrow(() => { w.addTabbedWindow(tabbedWindow) @@ -722,9 +662,9 @@ describe('BrowserWindow module', function () { }) }) - describe('BrowserWindow.setVibrancy(type)', function () { - it('allows setting, changing, and removing the vibrancy', function () { - assert.doesNotThrow(function () { + describe('BrowserWindow.setVibrancy(type)', () => { + it('allows setting, changing, and removing the vibrancy', () => { + assert.doesNotThrow(() => { w.setVibrancy('light') w.setVibrancy('dark') w.setVibrancy(null) @@ -734,13 +674,13 @@ describe('BrowserWindow module', function () { }) }) - describe('BrowserWindow.setAppDetails(options)', function () { - it('supports setting the app details', function () { + describe('BrowserWindow.setAppDetails(options)', () => { + it('supports setting the app details', () => { if (process.platform !== 'win32') return const iconPath = path.join(fixtures, 'assets', 'icon.ico') - assert.doesNotThrow(function () { + assert.doesNotThrow(() => { w.setAppDetails({appId: 'my.app.id'}) w.setAppDetails({appIconPath: iconPath, appIconIndex: 0}) w.setAppDetails({appIconPath: iconPath}) @@ -757,47 +697,39 @@ describe('BrowserWindow module', function () { w.setAppDetails({}) }) - assert.throws(function () { + assert.throws(() => { w.setAppDetails() }, /Insufficient number of arguments\./) }) }) - describe('BrowserWindow.fromId(id)', function () { - it('returns the window with id', function () { + describe('BrowserWindow.fromId(id)', () => { + it('returns the window with id', () => { assert.equal(w.id, BrowserWindow.fromId(w.id).id) }) }) - describe('BrowserWindow.fromWebContents(webContents)', function () { + describe('BrowserWindow.fromWebContents(webContents)', () => { let contents = null - beforeEach(function () { - contents = webContents.create({}) - }) + beforeEach(() => { contents = webContents.create({}) }) - afterEach(function () { - contents.destroy() - }) + afterEach(() => { contents.destroy() }) - it('returns the window with the webContents', function () { + it('returns the window with the webContents', () => { assert.equal(BrowserWindow.fromWebContents(w.webContents).id, w.id) assert.equal(BrowserWindow.fromWebContents(contents), undefined) }) }) - describe('BrowserWindow.fromDevToolsWebContents(webContents)', function () { + describe('BrowserWindow.fromDevToolsWebContents(webContents)', () => { let contents = null - beforeEach(function () { - contents = webContents.create({}) - }) + beforeEach(() => { contents = webContents.create({}) }) - afterEach(function () { - contents.destroy() - }) + afterEach(() => { contents.destroy() }) - it('returns the window with the webContents', function (done) { + it('returns the window with the webContents', (done) => { w.webContents.once('devtools-opened', () => { assert.equal(BrowserWindow.fromDevToolsWebContents(w.devToolsWebContents).id, w.id) assert.equal(BrowserWindow.fromDevToolsWebContents(w.webContents), undefined) @@ -808,8 +740,8 @@ describe('BrowserWindow module', function () { }) }) - describe('BrowserWindow.setOpacity(opacity)', function () { - it('make window with initial opacity', function () { + describe('BrowserWindow.setOpacity(opacity)', () => { + it('make window with initial opacity', () => { w.destroy() w = new BrowserWindow({ show: false, @@ -819,9 +751,8 @@ describe('BrowserWindow module', function () { }) assert.equal(w.getOpacity(), 0.5) }) - - it('allows setting the opacity', function () { - assert.doesNotThrow(function () { + it('allows setting the opacity', () => { + assert.doesNotThrow(() => { w.setOpacity(0.0) assert.equal(w.getOpacity(), 0.0) w.setOpacity(0.5) @@ -832,8 +763,8 @@ describe('BrowserWindow module', function () { }) }) - describe('"useContentSize" option', function () { - it('make window created with content size when used', function () { + describe('"useContentSize" option', () => { + it('make window created with content size when used', () => { w.destroy() w = new BrowserWindow({ show: false, @@ -841,18 +772,16 @@ describe('BrowserWindow module', function () { height: 400, useContentSize: true }) - var contentSize = w.getContentSize() + const contentSize = w.getContentSize() assert.equal(contentSize[0], 400) assert.equal(contentSize[1], 400) }) - - it('make window created with window size when not used', function () { - var size = w.getSize() + it('make window created with window size when not used', () => { + const size = w.getSize() assert.equal(size[0], 400) assert.equal(size[1], 400) }) - - it('works for a frameless window', function () { + it('works for a frameless window', () => { w.destroy() w = new BrowserWindow({ show: false, @@ -861,24 +790,20 @@ describe('BrowserWindow module', function () { height: 400, useContentSize: true }) - var contentSize = w.getContentSize() + const contentSize = w.getContentSize() assert.equal(contentSize[0], 400) assert.equal(contentSize[1], 400) - var size = w.getSize() + const size = w.getSize() assert.equal(size[0], 400) assert.equal(size[1], 400) }) }) - describe('"titleBarStyle" option', function () { - if (process.platform !== 'darwin') { - return - } - if (parseInt(os.release().split('.')[0]) < 14) { - return - } + describe('"titleBarStyle" option', () => { + if (process.platform !== 'darwin') return + if (parseInt(os.release().split('.')[0]) < 14) return - it('creates browser window with hidden title bar', function () { + it('creates browser window with hidden title bar', () => { w.destroy() w = new BrowserWindow({ show: false, @@ -886,11 +811,10 @@ describe('BrowserWindow module', function () { height: 400, titleBarStyle: 'hidden' }) - var contentSize = w.getContentSize() + const contentSize = w.getContentSize() assert.equal(contentSize[1], 400) }) - - it('creates browser window with hidden inset title bar', function () { + it('creates browser window with hidden inset title bar', () => { w.destroy() w = new BrowserWindow({ show: false, @@ -898,17 +822,15 @@ describe('BrowserWindow module', function () { height: 400, titleBarStyle: 'hidden-inset' }) - var contentSize = w.getContentSize() + const contentSize = w.getContentSize() assert.equal(contentSize[1], 400) }) }) - describe('enableLargerThanScreen" option', function () { - if (process.platform === 'linux') { - return - } + describe('enableLargerThanScreen" option', () => { + if (process.platform === 'linux') return - beforeEach(function () { + beforeEach(() => { w.destroy() w = new BrowserWindow({ show: true, @@ -918,15 +840,14 @@ describe('BrowserWindow module', function () { }) }) - it('can move the window out of screen', function () { + it('can move the window out of screen', () => { w.setPosition(-10, -10) - var after = w.getPosition() + const after = w.getPosition() assert.equal(after[0], -10) assert.equal(after[1], -10) }) - - it('can set the window larger than screen', function () { - var size = screen.getPrimaryDisplay().size + it('can set the window larger than screen', () => { + const size = screen.getPrimaryDisplay().size size.width += 100 size.height += 100 w.setSize(size.width, size.height) @@ -934,8 +855,8 @@ describe('BrowserWindow module', function () { }) }) - describe('"zoomToPageWidth" option', function () { - it('sets the window width to the page width when used', function () { + describe('"zoomToPageWidth" option', () => { + it('sets the window width to the page width when used', () => { if (process.platform !== 'darwin') return w.destroy() @@ -950,8 +871,8 @@ describe('BrowserWindow module', function () { }) }) - describe('"tabbingIdentifier" option', function () { - it('can be set on a window', function () { + describe('"tabbingIdentifier" option', () => { + it('can be set on a window', () => { w.destroy() w = new BrowserWindow({ tabbingIdentifier: 'group1' @@ -964,15 +885,13 @@ describe('BrowserWindow module', function () { }) }) - describe('"webPreferences" option', function () { - afterEach(function () { - ipcMain.removeAllListeners('answer') - }) + describe('"webPreferences" option', () => { + afterEach(() => { ipcMain.removeAllListeners('answer') }) - describe('"preload" option', function () { - it('loads the script before other scripts in window', function (done) { - var preload = path.join(fixtures, 'module', 'set-global.js') - ipcMain.once('answer', function (event, test) { + describe('"preload" option', () => { + it('loads the script before other scripts in window', (done) => { + const preload = path.join(fixtures, 'module', 'set-global.js') + ipcMain.once('answer', (event, test) => { assert.equal(test, 'preload') done() }) @@ -983,12 +902,11 @@ describe('BrowserWindow module', function () { preload: preload } }) - w.loadURL('file://' + path.join(fixtures, 'api', 'preload.html')) + w.loadURL(`file://${path.join(fixtures, 'api', 'preload.html')}`) }) - - it('can successfully delete the Buffer global', function (done) { - var preload = path.join(fixtures, 'module', 'delete-buffer.js') - ipcMain.once('answer', function (event, test) { + it('can successfully delete the Buffer global', (done) => { + const preload = path.join(fixtures, 'module', 'delete-buffer.js') + ipcMain.once('answer', (event, test) => { assert.equal(test.toString(), 'buffer') done() }) @@ -999,14 +917,14 @@ describe('BrowserWindow module', function () { preload: preload } }) - w.loadURL('file://' + path.join(fixtures, 'api', 'preload.html')) + w.loadURL(`file://${path.join(fixtures, 'api', 'preload.html')}`) }) }) - 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, typeofProcess, typeofBuffer) { + describe('"node-integration" option', () => { + it('disables node integration when specified to false', (done) => { + const preload = path.join(fixtures, 'module', 'send-later.js') + ipcMain.once('answer', (event, typeofProcess, typeofBuffer) => { assert.equal(typeofProcess, 'undefined') assert.equal(typeofBuffer, 'undefined') done() @@ -1019,11 +937,11 @@ describe('BrowserWindow module', function () { nodeIntegration: false } }) - w.loadURL('file://' + path.join(fixtures, 'api', 'blank.html')) + w.loadURL(`file://${path.join(fixtures, 'api', 'blank.html')}`) }) }) - describe('"sandbox" option', function () { + describe('"sandbox" option', () => { function waitForEvents (emitter, events, callback) { let count = events.length for (let event of events) { @@ -1044,19 +962,19 @@ describe('BrowserWindow module', function () { }) } - before(function (done) { - protocol.interceptStringProtocol('http', crossDomainHandler, function () { + before((done) => { + protocol.interceptStringProtocol('http', crossDomainHandler, () => { done() }) }) - after(function (done) { - protocol.uninterceptProtocol('http', function () { + after((done) => { + protocol.uninterceptProtocol('http', () => { done() }) }) - it('exposes ipcRenderer to preload script', function (done) { + it('exposes ipcRenderer to preload script', (done) => { ipcMain.once('answer', function (event, test) { assert.equal(test, 'preload') done() @@ -1072,7 +990,7 @@ describe('BrowserWindow module', function () { w.loadURL('file://' + path.join(fixtures, 'api', 'preload.html')) }) - it('exposes "exit" event to preload script', function (done) { + it('exposes "exit" event to preload script', (done) => { w.destroy() w = new BrowserWindow({ show: false, @@ -1094,7 +1012,7 @@ describe('BrowserWindow module', function () { }) }) - it('should open windows in same domain with cross-scripting enabled', function (done) { + it('should open windows in same domain with cross-scripting enabled', (done) => { w.destroy() w = new BrowserWindow({ show: false, @@ -1123,7 +1041,7 @@ describe('BrowserWindow module', function () { }) }) - it('should open windows in another domain with cross-scripting disabled', function (done) { + it('should open windows in another domain with cross-scripting disabled', (done) => { w.destroy() w = new BrowserWindow({ show: false, @@ -1162,7 +1080,7 @@ describe('BrowserWindow module', function () { }) }) - it('should inherit the sandbox setting in opened windows', function (done) { + it('should inherit the sandbox setting in opened windows', (done) => { w.destroy() w = new BrowserWindow({ show: false, @@ -1180,7 +1098,7 @@ describe('BrowserWindow module', function () { w.loadURL(`file://${path.join(fixtures, 'api', 'new-window.html')}`) }) - it('should open windows with the options configured via new-window event listeners', function (done) { + it('should open windows with the options configured via new-window event listeners', (done) => { w.destroy() w = new BrowserWindow({ show: false, @@ -1199,7 +1117,7 @@ describe('BrowserWindow module', function () { w.loadURL(`file://${path.join(fixtures, 'api', 'new-window.html')}`) }) - it('should set ipc event sender correctly', function (done) { + it('should set ipc event sender correctly', (done) => { w.destroy() w = new BrowserWindow({ show: false, @@ -1230,15 +1148,15 @@ describe('BrowserWindow module', function () { }) }) - describe('event handling', function () { - it('works for window events', function (done) { + describe('event handling', () => { + it('works for window events', (done) => { waitForEvents(w, [ 'page-title-updated' ], done) w.loadURL('file://' + path.join(fixtures, 'api', 'sandbox.html?window-events')) }) - it('works for stop events', function (done) { + it('works for stop events', (done) => { waitForEvents(w.webContents, [ 'did-navigate', 'did-fail-load', @@ -1247,7 +1165,7 @@ describe('BrowserWindow module', function () { w.loadURL('file://' + path.join(fixtures, 'api', 'sandbox.html?webcontents-stop')) }) - it('works for web contents events', function (done) { + it('works for web contents events', (done) => { waitForEvents(w.webContents, [ 'did-finish-load', 'did-frame-finish-load', @@ -1262,7 +1180,7 @@ describe('BrowserWindow module', function () { }) }) - it('can get printer list', function (done) { + it('can get printer list', (done) => { w.destroy() w = new BrowserWindow({ show: false, @@ -1272,14 +1190,14 @@ describe('BrowserWindow module', function () { } }) w.loadURL('data:text/html,%3Ch1%3EHello%2C%20World!%3C%2Fh1%3E') - w.webContents.once('did-finish-load', function () { + w.webContents.once('did-finish-load', () => { const printers = w.webContents.getPrinters() assert.equal(Array.isArray(printers), true) done() }) }) - it('can print to PDF', function (done) { + it('can print to PDF', (done) => { w.destroy() w = new BrowserWindow({ show: false, @@ -1289,7 +1207,7 @@ describe('BrowserWindow module', function () { } }) w.loadURL('data:text/html,%3Ch1%3EHello%2C%20World!%3C%2Fh1%3E') - w.webContents.once('did-finish-load', function () { + w.webContents.once('did-finish-load', () => { w.webContents.printToPDF({}, function (error, data) { assert.equal(error, null) assert.equal(data instanceof Buffer, true) @@ -1426,33 +1344,29 @@ describe('BrowserWindow module', function () { assert.equal(content, 'Hello') done() }) - w.loadURL('file://' + path.join(fixtures, 'api', 'native-window-open-blank.html')) + w.loadURL(`file://${path.join(fixtures, 'api', 'native-window-open-blank.html')}`) }) - it('opens window of same domain with cross-scripting enabled', (done) => { ipcMain.once('answer', (event, content) => { assert.equal(content, 'Hello') done() }) - w.loadURL('file://' + path.join(fixtures, 'api', 'native-window-open-file.html')) + w.loadURL(`file://${path.join(fixtures, 'api', 'native-window-open-file.html')}`) }) - it('blocks accessing cross-origin frames', (done) => { ipcMain.once('answer', (event, content) => { assert.equal(content, 'Blocked a frame with origin "file://" from accessing a cross-origin frame.') done() }) - w.loadURL('file://' + path.join(fixtures, 'api', 'native-window-open-cross-origin.html')) + w.loadURL(`file://${path.join(fixtures, 'api', 'native-window-open-cross-origin.html')}`) }) - it('opens window from ` w.webContents.on('did-frame-finish-load', (e, isMainFrame) => { @@ -557,12 +551,12 @@ describe('webContents module', function () { }) }) - describe('will-prevent-unload event', function () { - it('does not emit if beforeunload returns undefined', function (done) { - w.once('closed', function () { + describe('will-prevent-unload event', () => { + it('does not emit if beforeunload returns undefined', (done) => { + w.once('closed', () => { done() }) - w.webContents.on('will-prevent-unload', function (e) { + w.webContents.on('will-prevent-unload', (e) => { assert.fail('should not have fired') }) w.loadURL('file://' + path.join(fixtures, 'api', 'close-beforeunload-undefined.html')) @@ -575,15 +569,15 @@ describe('webContents module', function () { w.loadURL('file://' + path.join(fixtures, 'api', 'close-beforeunload-false.html')) }) - it('supports calling preventDefault on will-prevent-unload events', function (done) { + it('supports calling preventDefault on will-prevent-unload events', (done) => { ipcRenderer.send('prevent-next-will-prevent-unload', w.webContents.id) w.once('closed', () => done()) w.loadURL('file://' + path.join(fixtures, 'api', 'close-beforeunload-false.html')) }) }) - describe('setIgnoreMenuShortcuts(ignore)', function () { - it('does not throw', function () { + describe('setIgnoreMenuShortcuts(ignore)', () => { + it('does not throw', () => { assert.equal(w.webContents.setIgnoreMenuShortcuts(true), undefined) assert.equal(w.webContents.setIgnoreMenuShortcuts(false), undefined) }) @@ -594,7 +588,7 @@ describe('webContents module', function () { xdescribe('destroy()', () => { let server - before(function (done) { + before((done) => { server = http.createServer((request, response) => { switch (request.url) { case '/404': @@ -619,7 +613,7 @@ describe('webContents module', function () { }) }) - after(function () { + after(() => { server.close() server = null }) @@ -659,18 +653,18 @@ describe('webContents module', function () { describe('did-change-theme-color event', () => { it('is triggered with correct theme color', (done) => { - var count = 0 + let count = 0 w.webContents.on('did-change-theme-color', (e, color) => { if (count === 0) { - count++ + count += 1 assert.equal(color, '#FFEEDD') - w.loadURL('file://' + path.join(__dirname, 'fixtures', 'pages', 'base-page.html')) + w.loadURL(`file://${path.join(__dirname, 'fixtures', 'pages', 'base-page.html')}`) } else if (count === 1) { assert.equal(color, null) done() } }) - w.loadURL('file://' + path.join(__dirname, 'fixtures', 'pages', 'theme-color.html')) + w.loadURL(`file://${path.join(__dirname, 'fixtures', 'pages', 'theme-color.html')}`) }) }) }) diff --git a/spec/api-web-request-spec.js b/spec/api-web-request-spec.js index 85afb4c9399..6ad0550eb7d 100644 --- a/spec/api-web-request-spec.js +++ b/spec/api-web-request-spec.js @@ -4,89 +4,79 @@ const qs = require('querystring') const remote = require('electron').remote const session = remote.session -describe('webRequest module', function () { - var ses = session.defaultSession - var server = http.createServer(function (req, res) { +describe('webRequest module', () => { + const ses = session.defaultSession + const server = http.createServer((req, res) => { if (req.url === '/serverRedirect') { res.statusCode = 301 res.setHeader('Location', 'http://' + req.rawHeaders[1]) res.end() } else { res.setHeader('Custom', ['Header']) - var content = req.url + let content = req.url if (req.headers.accept === '*/*;test/header') { content += 'header/received' } res.end(content) } }) - var defaultURL = null + let defaultURL = null - before(function (done) { - server.listen(0, '127.0.0.1', function () { - var port = server.address().port + before((done) => { + server.listen(0, '127.0.0.1', () => { + const port = server.address().port defaultURL = 'http://127.0.0.1:' + port + '/' done() }) }) - after(function () { + after(() => { server.close() }) - describe('webRequest.onBeforeRequest', function () { - afterEach(function () { + describe('webRequest.onBeforeRequest', () => { + afterEach(() => { ses.webRequest.onBeforeRequest(null) }) - it('can cancel the request', function (done) { - ses.webRequest.onBeforeRequest(function (details, callback) { + it('can cancel the request', (done) => { + ses.webRequest.onBeforeRequest((details, callback) => { callback({ cancel: true }) }) $.ajax({ url: defaultURL, - success: function () { + success: () => { done('unexpected success') }, - error: function () { + error: () => { done() } }) }) - it('can filter URLs', function (done) { - var filter = { - urls: [defaultURL + 'filter/*'] - } - ses.webRequest.onBeforeRequest(filter, function (details, callback) { - callback({ - cancel: true - }) + it('can filter URLs', (done) => { + const filter = { urls: [defaultURL + 'filter/*'] } + ses.webRequest.onBeforeRequest(filter, (details, callback) => { + callback({cancel: true}) }) $.ajax({ - url: defaultURL + 'nofilter/test', - success: function (data) { + url: `${defaultURL}nofilter/test`, + success: (data) => { assert.equal(data, '/nofilter/test') $.ajax({ - url: defaultURL + 'filter/test', - success: function () { - done('unexpected success') - }, - error: function () { - done() - } + url: `${defaultURL}filter/test`, + success: () => done('unexpected success'), + error: () => done() }) }, - error: function (xhr, errorType) { - done(errorType) - } + error: (xhr, errorType) => done(errorType) }) }) - it('receives details object', function (done) { - ses.webRequest.onBeforeRequest(function (details, callback) { + it('receives details object', (done) => { + ses.webRequest.onBeforeRequest((details, callback) => { assert.equal(typeof details.id, 'number') assert.equal(typeof details.timestamp, 'number') assert.equal(typeof details.webContentsId, 'number') @@ -98,162 +88,138 @@ describe('webRequest module', function () { }) $.ajax({ url: defaultURL, - success: function (data) { + success: (data) => { assert.equal(data, '/') done() }, - error: function (xhr, errorType) { - done(errorType) - } + error: (xhr, errorType) => done(errorType) }) }) - it('receives post data in details object', function (done) { - var postData = { + it('receives post data in details object', (done) => { + const postData = { name: 'post test', type: 'string' } - ses.webRequest.onBeforeRequest(function (details, callback) { + ses.webRequest.onBeforeRequest((details, callback) => { assert.equal(details.url, defaultURL) assert.equal(details.method, 'POST') assert.equal(details.uploadData.length, 1) - var data = qs.parse(details.uploadData[0].bytes.toString()) + const data = qs.parse(details.uploadData[0].bytes.toString()) assert.deepEqual(data, postData) - callback({ - cancel: true - }) + callback({ cancel: true }) }) $.ajax({ url: defaultURL, type: 'POST', data: postData, - success: function () {}, - error: function () { - done() - } + success: () => {}, + error: () => done() }) }) - it('can redirect the request', function (done) { - ses.webRequest.onBeforeRequest(function (details, callback) { + it('can redirect the request', (done) => { + ses.webRequest.onBeforeRequest((details, callback) => { if (details.url === defaultURL) { - callback({ - redirectURL: defaultURL + 'redirect' - }) + callback({ redirectURL: `${defaultURL}redirect` }) } else { callback({}) } }) $.ajax({ url: defaultURL, - success: function (data) { + success: (data) => { assert.equal(data, '/redirect') done() }, - error: function (xhr, errorType) { - done(errorType) - } + error: (xhr, errorType) => done(errorType) }) }) }) - describe('webRequest.onBeforeSendHeaders', function () { - afterEach(function () { + describe('webRequest.onBeforeSendHeaders', () => { + afterEach(() => { ses.webRequest.onBeforeSendHeaders(null) }) - it('receives details object', function (done) { - ses.webRequest.onBeforeSendHeaders(function (details, callback) { + it('receives details object', (done) => { + ses.webRequest.onBeforeSendHeaders((details, callback) => { assert.equal(typeof details.requestHeaders, 'object') assert.equal(details.requestHeaders['Foo.Bar'], 'baz') callback({}) }) $.ajax({ url: defaultURL, - headers: { - 'Foo.Bar': 'baz' - }, - success: function (data) { + headers: { 'Foo.Bar': 'baz' }, + success: (data) => { assert.equal(data, '/') done() }, - error: function (xhr, errorType) { - done(errorType) - } + error: (xhr, errorType) => done(errorType) }) }) - it('can change the request headers', function (done) { - ses.webRequest.onBeforeSendHeaders(function (details, callback) { - var requestHeaders = details.requestHeaders + it('can change the request headers', (done) => { + ses.webRequest.onBeforeSendHeaders((details, callback) => { + const requestHeaders = details.requestHeaders requestHeaders.Accept = '*/*;test/header' - callback({ - requestHeaders: requestHeaders - }) + callback({ requestHeaders: requestHeaders }) }) $.ajax({ url: defaultURL, - success: function (data) { + success: (data) => { assert.equal(data, '/header/received') done() }, - error: function (xhr, errorType) { - done(errorType) - } + error: (xhr, errorType) => done(errorType) }) }) - it('resets the whole headers', function (done) { - var requestHeaders = { + it('resets the whole headers', (done) => { + const requestHeaders = { Test: 'header' } - ses.webRequest.onBeforeSendHeaders(function (details, callback) { - callback({ - requestHeaders: requestHeaders - }) + ses.webRequest.onBeforeSendHeaders((details, callback) => { + callback({ requestHeaders: requestHeaders }) }) - ses.webRequest.onSendHeaders(function (details) { + ses.webRequest.onSendHeaders((details) => { assert.deepEqual(details.requestHeaders, requestHeaders) done() }) $.ajax({ url: defaultURL, - error: function (xhr, errorType) { - done(errorType) - } + error: (xhr, errorType) => done(errorType) }) }) }) - describe('webRequest.onSendHeaders', function () { - afterEach(function () { + describe('webRequest.onSendHeaders', () => { + afterEach(() => { ses.webRequest.onSendHeaders(null) }) - it('receives details object', function (done) { - ses.webRequest.onSendHeaders(function (details) { + it('receives details object', (done) => { + ses.webRequest.onSendHeaders((details) => { assert.equal(typeof details.requestHeaders, 'object') }) $.ajax({ url: defaultURL, - success: function (data) { + success: (data) => { assert.equal(data, '/') done() }, - error: function (xhr, errorType) { - done(errorType) - } + error: (xhr, errorType) => done(errorType) }) }) }) - describe('webRequest.onHeadersReceived', function () { - afterEach(function () { + describe('webRequest.onHeadersReceived', () => { + afterEach(() => { ses.webRequest.onHeadersReceived(null) }) - it('receives details object', function (done) { - ses.webRequest.onHeadersReceived(function (details, callback) { + it('receives details object', (done) => { + ses.webRequest.onHeadersReceived((details, callback) => { assert.equal(details.statusLine, 'HTTP/1.1 200 OK') assert.equal(details.statusCode, 200) assert.equal(details.responseHeaders['Custom'], 'Header') @@ -261,76 +227,64 @@ describe('webRequest module', function () { }) $.ajax({ url: defaultURL, - success: function (data) { + success: (data) => { assert.equal(data, '/') done() }, - error: function (xhr, errorType) { - done(errorType) - } + error: (xhr, errorType) => done(errorType) }) }) - it('can change the response header', function (done) { - ses.webRequest.onHeadersReceived(function (details, callback) { - var responseHeaders = details.responseHeaders + it('can change the response header', (done) => { + ses.webRequest.onHeadersReceived((details, callback) => { + const responseHeaders = details.responseHeaders responseHeaders['Custom'] = ['Changed'] - callback({ - responseHeaders: responseHeaders - }) + callback({ responseHeaders: responseHeaders }) }) $.ajax({ url: defaultURL, - success: function (data, status, xhr) { + success: (data, status, xhr) => { assert.equal(xhr.getResponseHeader('Custom'), 'Changed') assert.equal(data, '/') done() }, - error: function (xhr, errorType) { - done(errorType) - } + error: (xhr, errorType) => done(errorType) }) }) - it('does not change header by default', function (done) { - ses.webRequest.onHeadersReceived(function (details, callback) { + it('does not change header by default', (done) => { + ses.webRequest.onHeadersReceived((details, callback) => { callback({}) }) $.ajax({ url: defaultURL, - success: function (data, status, xhr) { + success: (data, status, xhr) => { assert.equal(xhr.getResponseHeader('Custom'), 'Header') assert.equal(data, '/') done() }, - error: function (xhr, errorType) { - done(errorType) - } + error: (xhr, errorType) => done(errorType) }) }) - it('follows server redirect', function (done) { - ses.webRequest.onHeadersReceived(function (details, callback) { - var responseHeaders = details.responseHeaders - callback({ - responseHeaders: responseHeaders - }) + it('follows server redirect', (done) => { + ses.webRequest.onHeadersReceived((details, callback) => { + const responseHeaders = details.responseHeaders + callback({ responseHeaders: responseHeaders }) }) $.ajax({ url: defaultURL + 'serverRedirect', - success: function (data, status, xhr) { + success: (data, status, xhr) => { assert.equal(xhr.getResponseHeader('Custom'), 'Header') done() }, - error: function (xhr, errorType) { - done(errorType) - } + error: (xhr, errorType) => done(errorType) }) }) - it('can change the header status', function (done) { - ses.webRequest.onHeadersReceived(function (details, callback) { - var responseHeaders = details.responseHeaders + it('can change the header status', (done) => { + ses.webRequest.onHeadersReceived((details, callback) => { + const responseHeaders = details.responseHeaders callback({ responseHeaders: responseHeaders, statusLine: 'HTTP/1.1 404 Not Found' @@ -338,9 +292,8 @@ describe('webRequest module', function () { }) $.ajax({ url: defaultURL, - success: function (data, status, xhr) { - }, - error: function (xhr, errorType) { + success: (data, status, xhr) => {}, + error: (xhr, errorType) => { assert.equal(xhr.getResponseHeader('Custom'), 'Header') done() } @@ -348,13 +301,13 @@ describe('webRequest module', function () { }) }) - describe('webRequest.onResponseStarted', function () { - afterEach(function () { + describe('webRequest.onResponseStarted', () => { + afterEach(() => { ses.webRequest.onResponseStarted(null) }) - it('receives details object', function (done) { - ses.webRequest.onResponseStarted(function (details) { + it('receives details object', (done) => { + ses.webRequest.onResponseStarted((details) => { assert.equal(typeof details.fromCache, 'boolean') assert.equal(details.statusLine, 'HTTP/1.1 200 OK') assert.equal(details.statusCode, 200) @@ -362,36 +315,32 @@ describe('webRequest module', function () { }) $.ajax({ url: defaultURL, - success: function (data, status, xhr) { + success: (data, status, xhr) => { assert.equal(xhr.getResponseHeader('Custom'), 'Header') assert.equal(data, '/') done() }, - error: function (xhr, errorType) { - done(errorType) - } + error: (xhr, errorType) => done(errorType) }) }) }) - describe('webRequest.onBeforeRedirect', function () { - afterEach(function () { + describe('webRequest.onBeforeRedirect', () => { + afterEach(() => { ses.webRequest.onBeforeRedirect(null) ses.webRequest.onBeforeRequest(null) }) - it('receives details object', function (done) { - var redirectURL = defaultURL + 'redirect' - ses.webRequest.onBeforeRequest(function (details, callback) { + it('receives details object', (done) => { + const redirectURL = defaultURL + 'redirect' + ses.webRequest.onBeforeRequest((details, callback) => { if (details.url === defaultURL) { - callback({ - redirectURL: redirectURL - }) + callback({ redirectURL: redirectURL }) } else { callback({}) } }) - ses.webRequest.onBeforeRedirect(function (details) { + ses.webRequest.onBeforeRedirect((details) => { assert.equal(typeof details.fromCache, 'boolean') assert.equal(details.statusLine, 'HTTP/1.1 307 Internal Redirect') assert.equal(details.statusCode, 307) @@ -399,62 +348,54 @@ describe('webRequest module', function () { }) $.ajax({ url: defaultURL, - success: function (data) { + success: (data) => { assert.equal(data, '/redirect') done() }, - error: function (xhr, errorType) { - done(errorType) - } + error: (xhr, errorType) => done(errorType) }) }) }) - describe('webRequest.onCompleted', function () { - afterEach(function () { + describe('webRequest.onCompleted', () => { + afterEach(() => { ses.webRequest.onCompleted(null) }) - it('receives details object', function (done) { - ses.webRequest.onCompleted(function (details) { + it('receives details object', (done) => { + ses.webRequest.onCompleted((details) => { assert.equal(typeof details.fromCache, 'boolean') assert.equal(details.statusLine, 'HTTP/1.1 200 OK') assert.equal(details.statusCode, 200) }) $.ajax({ url: defaultURL, - success: function (data) { + success: (data) => { assert.equal(data, '/') done() }, - error: function (xhr, errorType) { - done(errorType) - } + error: (xhr, errorType) => done(errorType) }) }) }) - describe('webRequest.onErrorOccurred', function () { - afterEach(function () { + describe('webRequest.onErrorOccurred', () => { + afterEach(() => { ses.webRequest.onErrorOccurred(null) ses.webRequest.onBeforeRequest(null) }) - it('receives details object', function (done) { - ses.webRequest.onBeforeRequest(function (details, callback) { - callback({ - cancel: true - }) + it('receives details object', (done) => { + ses.webRequest.onBeforeRequest((details, callback) => { + callback({ cancel: true }) }) - ses.webRequest.onErrorOccurred(function (details) { + ses.webRequest.onErrorOccurred((details) => { assert.equal(details.error, 'net::ERR_BLOCKED_BY_CLIENT') done() }) $.ajax({ url: defaultURL, - success: function () { - done('unexpected success') - } + success: () => done('unexpected success') }) }) }) diff --git a/spec/chromium-spec.js b/spec/chromium-spec.js index b8184d79e40..a6bc149c1d1 100644 --- a/spec/chromium-spec.js +++ b/spec/chromium-spec.js @@ -11,62 +11,55 @@ const {app, BrowserWindow, ipcMain, protocol, session, webContents} = remote const isCI = remote.getGlobal('isCi') -describe('chromium feature', function () { - var fixtures = path.resolve(__dirname, 'fixtures') - var listener = null +describe('chromium feature', () => { + const fixtures = path.resolve(__dirname, 'fixtures') + let listener = null let w = null - afterEach(function () { + afterEach(() => { if (listener != null) { window.removeEventListener('message', listener) } listener = null }) - afterEach(function () { - return closeWindow(w).then(function () { w = null }) - }) + afterEach(() => closeWindow(w).then(() => { w = null })) - describe('heap snapshot', function () { - it('does not crash', function () { + describe('heap snapshot', () => { + it('does not crash', () => { if (process.env.TRAVIS === 'true') return process.atomBinding('v8_util').takeHeapSnapshot() }) }) - describe('sending request of http protocol urls', function () { - it('does not crash', function (done) { - var server = http.createServer(function (req, res) { + describe('sending request of http protocol urls', () => { + it('does not crash', (done) => { + const server = http.createServer((req, res) => { res.end() server.close() done() }) - server.listen(0, '127.0.0.1', function () { - var port = server.address().port - $.get('http://127.0.0.1:' + port) + server.listen(0, '127.0.0.1', () => { + const port = server.address().port + $.get(`http://127.0.0.1:${port}`) }) }) }) - xdescribe('navigator.webkitGetUserMedia', function () { - it('calls its callbacks', function (done) { + describe('navigator.webkitGetUserMedia', () => { + it('calls its callbacks', (done) => { navigator.webkitGetUserMedia({ audio: true, video: false - }, function () { - done() - }, function () { - done() - }) + }, () => done(), + () => done()) }) }) - describe('navigator.mediaDevices', function () { - if (isCI) { - return - } + describe('navigator.mediaDevices', () => { + if (isCI) return - it('can return labels of enumerated devices', function (done) { + it('can return labels of enumerated devices', (done) => { navigator.mediaDevices.enumerateDevices().then((devices) => { const labels = devices.map((device) => device.label) const labelFound = labels.some((label) => !!label) @@ -78,7 +71,7 @@ describe('chromium feature', function () { }).catch(done) }) - it('can return new device id when cookie storage is cleared', function (done) { + it('can return new device id when cookie storage is cleared', (done) => { const options = { origin: null, storages: ['cookies'] @@ -91,91 +84,78 @@ describe('chromium feature', function () { session: ses } }) - w.webContents.on('ipc-message', function (event, args) { - if (args[0] === 'deviceIds') { - deviceIds.push(args[1]) - } + w.webContents.on('ipc-message', (event, args) => { + if (args[0] === 'deviceIds') deviceIds.push(args[1]) if (deviceIds.length === 2) { assert.notDeepEqual(deviceIds[0], deviceIds[1]) - closeWindow(w).then(function () { + closeWindow(w).then(() => { w = null done() - }).catch(function (error) { - done(error) - }) + }).catch((error) => done(error)) } else { - ses.clearStorageData(options, function () { + ses.clearStorageData(options, () => { w.webContents.reload() }) } }) - w.loadURL('file://' + fixtures + '/pages/media-id-reset.html') + w.loadURL(`file://${fixtures}/pages/media-id-reset.html`) }) }) - describe('navigator.language', function () { - it('should not be empty', function () { + describe('navigator.language', () => { + it('should not be empty', () => { assert.notEqual(navigator.language, '') }) }) - describe('navigator.serviceWorker', function () { - it('should register for file scheme', function (done) { - w = new BrowserWindow({ - show: false - }) - w.webContents.on('ipc-message', function (event, args) { + describe('navigator.serviceWorker', () => { + it('should register for file scheme', (done) => { + w = new BrowserWindow({ show: false }) + w.webContents.on('ipc-message', (event, args) => { if (args[0] === 'reload') { w.webContents.reload() } else if (args[0] === 'error') { - done('unexpected error : ' + args[1]) + done(`unexpected error : ${args[1]}`) } else if (args[0] === 'response') { assert.equal(args[1], 'Hello from serviceWorker!') session.defaultSession.clearStorageData({ storages: ['serviceworkers'] - }, function () { - done() - }) + }, () => done()) } }) w.loadURL(`file://${fixtures}/pages/service-worker/index.html`) }) - it('should register for intercepted file scheme', function (done) { + it('should register for intercepted file scheme', (done) => { const customSession = session.fromPartition('intercept-file') - customSession.protocol.interceptBufferProtocol('file', function (request, callback) { + customSession.protocol.interceptBufferProtocol('file', (request, callback) => { let file = url.parse(request.url).pathname - // Remove leading slash before drive letter on Windows - if (file[0] === '/' && process.platform === 'win32') { - file = file.slice(1) - } + if (file[0] === '/' && process.platform === 'win32') file = file.slice(1) + const content = fs.readFileSync(path.normalize(file)) const ext = path.extname(file) let type = 'text/html' - if (ext === '.js') { - type = 'application/javascript' - } + + if (ext === '.js') type = 'application/javascript' callback({data: content, mimeType: type}) - }, function (error) { + }, (error) => { if (error) done(error) }) w = new BrowserWindow({ show: false, - webPreferences: { - session: customSession - } + webPreferences: { session: customSession } }) - w.webContents.on('ipc-message', function (event, args) { + w.webContents.on('ipc-message', (event, args) => { if (args[0] === 'reload') { w.webContents.reload() } else if (args[0] === 'error') { - done('unexpected error : ' + args[1]) + done(`unexpected error : ${args[1]}`) } else if (args[0] === 'response') { assert.equal(args[1], 'Hello from serviceWorker!') customSession.clearStorageData({ storages: ['serviceworkers'] - }, function () { + }, () => { customSession.protocol.uninterceptProtocol('file', (error) => done(error)) }) } @@ -184,53 +164,51 @@ describe('chromium feature', function () { }) }) - describe('window.open', function () { - if (process.env.TRAVIS === 'true' && process.platform === 'darwin') { - return - } + describe('window.open', () => { + if (process.env.TRAVIS === 'true' && process.platform === 'darwin') return - it('returns a BrowserWindowProxy object', function () { - var b = window.open('about:blank', '', 'show=no') + it('returns a BrowserWindowProxy object', () => { + const b = window.open('about:blank', '', 'show=no') assert.equal(b.closed, false) assert.equal(b.constructor.name, 'BrowserWindowProxy') b.close() }) - it('accepts "nodeIntegration" as feature', function (done) { - var b - listener = function (event) { + it('accepts "nodeIntegration" as feature', (done) => { + let b + listener = (event) => { assert.equal(event.data.isProcessGlobalUndefined, true) b.close() done() } window.addEventListener('message', listener) - b = window.open('file://' + fixtures + '/pages/window-opener-node.html', '', 'nodeIntegration=no,show=no') + b = window.open(`file://${fixtures}/pages/window-opener-node.html`, '', 'nodeIntegration=no,show=no') }) - it('inherit options of parent window', function (done) { - var b - listener = function (event) { - var ref1 = remote.getCurrentWindow().getSize() - var width = ref1[0] - var height = ref1[1] - assert.equal(event.data, 'size: ' + width + ' ' + height) + it('inherit options of parent window', (done) => { + let b + listener = (event) => { + const ref1 = remote.getCurrentWindow().getSize() + const width = ref1[0] + const height = ref1[1] + assert.equal(event.data, `size: ${width} ${height}`) b.close() done() } window.addEventListener('message', listener) - b = window.open('file://' + fixtures + '/pages/window-open-size.html', '', 'show=no') + b = window.open(`file://${fixtures}/pages/window-open-size.html`, '', 'show=no') }) - it('disables node integration when it is disabled on the parent window', function (done) { - var b - listener = function (event) { + it('disables node integration when it is disabled on the parent window', (done) => { + let b + listener = (event) => { assert.equal(event.data.isProcessGlobalUndefined, true) b.close() done() } window.addEventListener('message', listener) - var windowUrl = require('url').format({ + const windowUrl = require('url').format({ pathname: `${fixtures}/pages/window-opener-no-node-integration.html`, protocol: 'file', query: { @@ -241,8 +219,8 @@ describe('chromium feature', function () { b = window.open(windowUrl, '', 'nodeIntegration=no,show=no') }) - it('disables node integration when it is disabled on the parent window for chrome devtools URLs', function (done) { - var b + it('disables node integration when it is disabled on the parent window for chrome devtools URLs', (done) => { + let b app.once('web-contents-created', (event, contents) => { contents.once('did-finish-load', () => { contents.executeJavaScript('typeof process').then((typeofProcessGlobal) => { @@ -255,8 +233,8 @@ describe('chromium feature', function () { b = window.open('chrome-devtools://devtools/bundled/inspector.html', '', 'nodeIntegration=no,show=no') }) - it('disables JavaScript when it is disabled on the parent window', function (done) { - var b + it('disables JavaScript when it is disabled on the parent window', (done) => { + let b app.once('web-contents-created', (event, contents) => { contents.once('did-finish-load', () => { app.once('browser-window-created', (event, window) => { @@ -272,7 +250,7 @@ describe('chromium feature', function () { }) }) - var windowUrl = require('url').format({ + const windowUrl = require('url').format({ pathname: `${fixtures}/pages/window-no-javascript.html`, protocol: 'file', slashes: true @@ -280,16 +258,16 @@ describe('chromium feature', function () { b = window.open(windowUrl, '', 'javascript=no,show=no') }) - it('disables the tag when it is disabled on the parent window', function (done) { + it('disables the tag when it is disabled on the parent window', (done) => { let b - listener = function (event) { + listener = (event) => { assert.equal(event.data.isWebViewGlobalUndefined, true) b.close() done() } window.addEventListener('message', listener) - var windowUrl = require('url').format({ + const windowUrl = require('url').format({ pathname: `${fixtures}/pages/window-opener-no-webview-tag.html`, protocol: 'file', query: { @@ -300,24 +278,24 @@ describe('chromium feature', function () { b = window.open(windowUrl, '', 'webviewTag=no,nodeIntegration=yes,show=no') }) - it('does not override child options', function (done) { - var b, size - size = { + it('does not override child options', (done) => { + let b + const size = { width: 350, height: 450 } - listener = function (event) { - assert.equal(event.data, 'size: ' + size.width + ' ' + size.height) + listener = (event) => { + assert.equal(event.data, `size: ${size.width} ${size.height}`) b.close() done() } window.addEventListener('message', listener) - b = window.open('file://' + fixtures + '/pages/window-open-size.html', '', 'show=no,width=' + size.width + ',height=' + size.height) + b = window.open(`file://${fixtures}/pages/window-open-size.html`, '', 'show=no,width=' + size.width + ',height=' + size.height) }) it('handles cycles when merging the parent options into the child options', (done) => { w = BrowserWindow.fromId(ipcRenderer.sendSync('create-window-with-options-cycle')) - w.loadURL('file://' + fixtures + '/pages/window-open.html') + w.loadURL(`file://${fixtures}/pages/window-open.html`) w.webContents.once('new-window', (event, url, frameName, disposition, options) => { assert.equal(options.show, false) assert.deepEqual(options.foo, { @@ -337,12 +315,13 @@ describe('chromium feature', function () { }) }) - it('defines a window.location getter', function (done) { - var b, targetURL + it('defines a window.location getter', (done) => { + let b + let targetURL if (process.platform === 'win32') { - targetURL = 'file:///' + fixtures.replace(/\\/g, '/') + '/pages/base-page.html' + targetURL = `file:///${fixtures.replace(/\\/g, '/')}/pages/base-page.html` } else { - targetURL = 'file://' + fixtures + '/pages/base-page.html' + targetURL = `file://${fixtures}/pages/base-page.html` } app.once('browser-window-created', (event, window) => { window.webContents.once('did-finish-load', () => { @@ -354,34 +333,33 @@ describe('chromium feature', function () { b = window.open(targetURL) }) - it('defines a window.location setter', function (done) { + it('defines a window.location setter', (done) => { let b app.once('browser-window-created', (event, {webContents}) => { - webContents.once('did-finish-load', function () { + webContents.once('did-finish-load', () => { // When it loads, redirect - b.location = 'file://' + fixtures + '/pages/base-page.html' - webContents.once('did-finish-load', function () { + b.location = `file://${fixtures}/pages/base-page.html` + webContents.once('did-finish-load', () => { // After our second redirect, cleanup and callback b.close() done() }) }) }) - // Load a page that definitely won't redirect b = window.open('about:blank') }) - it('open a blank page when no URL is specified', function (done) { + it('open a blank page when no URL is specified', (done) => { let b app.once('browser-window-created', (event, {webContents}) => { - webContents.once('did-finish-load', function () { + webContents.once('did-finish-load', () => { const {location} = b b.close() assert.equal(location, 'about:blank') let c app.once('browser-window-created', (event, {webContents}) => { - webContents.once('did-finish-load', function () { + webContents.once('did-finish-load', () => { const {location} = c c.close() assert.equal(location, 'about:blank') @@ -394,17 +372,17 @@ describe('chromium feature', function () { b = window.open() }) - it('throws an exception when the arguments cannot be converted to strings', function () { - assert.throws(function () { + it('throws an exception when the arguments cannot be converted to strings', () => { + assert.throws(() => { window.open('', {toString: null}) }, /Cannot convert object to primitive value/) - assert.throws(function () { + assert.throws(() => { window.open('', '', {toString: 3}) }, /Cannot convert object to primitive value/) }) - it('sets the window title to the specified frameName', function (done) { + it('sets the window title to the specified frameName', (done) => { let b app.once('browser-window-created', (event, createdWindow) => { assert.equal(createdWindow.getTitle(), 'hello') @@ -414,7 +392,7 @@ describe('chromium feature', function () { b = window.open('', 'hello') }) - it('does not throw an exception when the frameName is a built-in object property', function (done) { + it('does not throw an exception when the frameName is a built-in object property', (done) => { let b app.once('browser-window-created', (event, createdWindow) => { assert.equal(createdWindow.getTitle(), '__proto__') @@ -424,32 +402,30 @@ describe('chromium feature', function () { b = window.open('', '__proto__') }) - it('does not throw an exception when the features include webPreferences', function () { + it('does not throw an exception when the features include webPreferences', () => { let b - assert.doesNotThrow(function () { + assert.doesNotThrow(() => { b = window.open('', '', 'webPreferences=') }) b.close() }) }) - describe('window.opener', function () { - let url = 'file://' + fixtures + '/pages/window-opener.html' + describe('window.opener', () => { + let url = `file://${fixtures}/pages/window-opener.html` - it('is null for main window', function (done) { - w = new BrowserWindow({ - show: false - }) - w.webContents.once('ipc-message', function (event, args) { + it('is null for main window', (done) => { + w = new BrowserWindow({ show: false }) + w.webContents.once('ipc-message', (event, args) => { assert.deepEqual(args, ['opener', null]) done() }) w.loadURL(url) }) - it('is not null for window opened by window.open', function (done) { + it('is not null for window opened by window.open', (done) => { let b - listener = function (event) { + listener = (event) => { assert.equal(event.data, 'object') b.close() done() @@ -459,29 +435,27 @@ describe('chromium feature', function () { }) }) - describe('window.opener access from BrowserWindow', function () { + describe('window.opener access from BrowserWindow', () => { const scheme = 'other' let url = `${scheme}://${fixtures}/pages/window-opener-location.html` let w = null - before(function (done) { - protocol.registerFileProtocol(scheme, function (request, callback) { + before((done) => { + protocol.registerFileProtocol(scheme, (request, callback) => { callback(`${fixtures}/pages/window-opener-location.html`) - }, function (error) { - done(error) - }) + }, (error) => done(error)) }) - after(function () { + after(() => { protocol.unregisterProtocol(scheme) }) - afterEach(function () { + afterEach(() => { w.close() }) - it('does nothing when origin of current window does not match opener', function (done) { - listener = function (event) { + it('does nothing when origin of current window does not match opener', (done) => { + listener = (event) => { assert.equal(event.data, undefined) done() } @@ -489,8 +463,8 @@ describe('chromium feature', function () { w = window.open(url, '', 'show=no') }) - it('works when origin matches', function (done) { - listener = function (event) { + it('works when origin matches', (done) => { + listener = (event) => { assert.equal(event.data, location.href) done() } @@ -498,8 +472,8 @@ describe('chromium feature', function () { w = window.open(`file://${fixtures}/pages/window-opener-location.html`, '', 'show=no') }) - it('works when origin does not match opener but has node integration', function (done) { - listener = function (event) { + it('works when origin does not match opener but has node integration', (done) => { + listener = (event) => { assert.equal(event.data, location.href) done() } @@ -508,31 +482,29 @@ describe('chromium feature', function () { }) }) - describe('window.opener access from ', function () { + describe('window.opener access from ', () => { const scheme = 'other' const srcPath = `${fixtures}/pages/webview-opener-postMessage.html` const pageURL = `file://${fixtures}/pages/window-opener-location.html` let webview = null - before(function (done) { - protocol.registerFileProtocol(scheme, function (request, callback) { + before((done) => { + protocol.registerFileProtocol(scheme, (request, callback) => { callback(srcPath) - }, function (error) { - done(error) - }) + }, (error) => done(error)) }) - after(function () { + after(() => { protocol.unregisterProtocol(scheme) }) - afterEach(function () { + afterEach(() => { if (webview != null) webview.remove() }) - it('does nothing when origin of webview src URL does not match opener', function (done) { + it('does nothing when origin of webview src URL does not match opener', (done) => { webview = new WebView() - webview.addEventListener('console-message', function (e) { + webview.addEventListener('console-message', (e) => { assert.equal(e.message, 'null') done() }) @@ -548,9 +520,9 @@ describe('chromium feature', function () { document.body.appendChild(webview) }) - it('works when origin matches', function (done) { + it('works when origin matches', (done) => { webview = new WebView() - webview.addEventListener('console-message', function (e) { + webview.addEventListener('console-message', (e) => { assert.equal(e.message, webview.src) done() }) @@ -566,9 +538,9 @@ describe('chromium feature', function () { document.body.appendChild(webview) }) - it('works when origin does not match opener but has node integration', function (done) { + it('works when origin does not match opener but has node integration', (done) => { webview = new WebView() - webview.addEventListener('console-message', function (e) { + webview.addEventListener('console-message', (e) => { webview.remove() assert.equal(e.message, webview.src) done() @@ -587,13 +559,13 @@ describe('chromium feature', function () { }) }) - describe('window.postMessage', function () { - it('sets the source and origin correctly', function (done) { - var b - listener = function (event) { + describe('window.postMessage', () => { + it('sets the source and origin correctly', (done) => { + let b + listener = (event) => { window.removeEventListener('message', listener) b.close() - var message = JSON.parse(event.data) + const message = JSON.parse(event.data) assert.equal(message.data, 'testing') assert.equal(message.origin, 'file://') assert.equal(message.sourceEqualsOpener, true) @@ -602,26 +574,26 @@ describe('chromium feature', function () { } window.addEventListener('message', listener) app.once('browser-window-created', (event, {webContents}) => { - webContents.once('did-finish-load', function () { + webContents.once('did-finish-load', () => { b.postMessage('testing', '*') }) }) - b = window.open('file://' + fixtures + '/pages/window-open-postMessage.html', '', 'show=no') + b = window.open(`file://${fixtures}/pages/window-open-postMessage.html`, '', 'show=no') }) - it('throws an exception when the targetOrigin cannot be converted to a string', function () { - var b = window.open('') - assert.throws(function () { + it('throws an exception when the targetOrigin cannot be converted to a string', () => { + const b = window.open('') + assert.throws(() => { b.postMessage('test', {toString: null}) }, /Cannot convert object to primitive value/) b.close() }) }) - describe('window.opener.postMessage', function () { - it('sets source and origin correctly', function (done) { - var b - listener = function (event) { + describe('window.opener.postMessage', () => { + it('sets source and origin correctly', (done) => { + let b + listener = (event) => { window.removeEventListener('message', listener) b.close() assert.equal(event.source, b) @@ -629,12 +601,12 @@ describe('chromium feature', function () { done() } window.addEventListener('message', listener) - b = window.open('file://' + fixtures + '/pages/window-opener-postMessage.html', '', 'show=no') + b = window.open(`file://${fixtures}/pages/window-opener-postMessage.html`, '', 'show=no') }) - it('supports windows opened from a ', function (done) { + it('supports windows opened from a ', (done) => { const webview = new WebView() - webview.addEventListener('console-message', function (e) { + webview.addEventListener('console-message', (e) => { webview.remove() assert.equal(e.message, 'message') done() @@ -651,29 +623,29 @@ describe('chromium feature', function () { document.body.appendChild(webview) }) - describe('targetOrigin argument', function () { + describe('targetOrigin argument', () => { let serverURL let server - beforeEach(function (done) { - server = http.createServer(function (req, res) { + beforeEach((done) => { + server = http.createServer((req, res) => { res.writeHead(200) const filePath = path.join(fixtures, 'pages', 'window-opener-targetOrigin.html') res.end(fs.readFileSync(filePath, 'utf8')) }) - server.listen(0, '127.0.0.1', function () { + server.listen(0, '127.0.0.1', () => { serverURL = `http://127.0.0.1:${server.address().port}` done() }) }) - afterEach(function () { + afterEach(() => { server.close() }) - it('delivers messages that match the origin', function (done) { + it('delivers messages that match the origin', (done) => { let b - listener = function (event) { + listener = (event) => { window.removeEventListener('message', listener) b.close() assert.equal(event.data, 'deliver') @@ -685,32 +657,30 @@ describe('chromium feature', function () { }) }) - describe('creating a Uint8Array under browser side', function () { - it('does not crash', function () { - var RUint8Array = remote.getGlobal('Uint8Array') - var arr = new RUint8Array() + describe('creating a Uint8Array under browser side', () => { + it('does not crash', () => { + const RUint8Array = remote.getGlobal('Uint8Array') + const arr = new RUint8Array() assert(arr) }) }) - describe('webgl', function () { - if (isCI && process.platform === 'win32') { - return - } + describe('webgl', () => { + if (isCI && process.platform === 'win32') return - it('can be get as context in canvas', function () { + it('can be get as context in canvas', () => { if (process.platform === 'linux') return - var webgl = document.createElement('canvas').getContext('webgl') + const webgl = document.createElement('canvas').getContext('webgl') assert.notEqual(webgl, null) }) }) - describe('web workers', function () { - it('Worker can work', function (done) { - var worker = new Worker('../fixtures/workers/worker.js') - var message = 'ping' - worker.onmessage = function (event) { + describe('web workers', () => { + it('Worker can work', (done) => { + const worker = new Worker('../fixtures/workers/worker.js') + const message = 'ping' + worker.onmessage = (event) => { assert.equal(event.data, message) worker.terminate() done() @@ -718,95 +688,95 @@ describe('chromium feature', function () { worker.postMessage(message) }) - it('Worker has no node integration by default', function (done) { + it('Worker has no node integration by default', (done) => { let worker = new Worker('../fixtures/workers/worker_node.js') - worker.onmessage = function (event) { + worker.onmessage = (event) => { assert.equal(event.data, 'undefined undefined undefined undefined') worker.terminate() done() } }) - it('Worker has node integration with nodeIntegrationInWorker', function (done) { + it('Worker has node integration with nodeIntegrationInWorker', (done) => { let webview = new WebView() - webview.addEventListener('ipc-message', function (e) { + webview.addEventListener('ipc-message', (e) => { assert.equal(e.channel, 'object function object function') webview.remove() done() }) - webview.src = 'file://' + fixtures + '/pages/worker.html' + webview.src = `file://${fixtures}/pages/worker.html` webview.setAttribute('webpreferences', 'nodeIntegration, nodeIntegrationInWorker') document.body.appendChild(webview) }) - it('SharedWorker can work', function (done) { - var worker = new SharedWorker('../fixtures/workers/shared_worker.js') - var message = 'ping' - worker.port.onmessage = function (event) { + it('SharedWorker can work', (done) => { + const worker = new SharedWorker('../fixtures/workers/shared_worker.js') + const message = 'ping' + worker.port.onmessage = (event) => { assert.equal(event.data, message) done() } worker.port.postMessage(message) }) - it('SharedWorker has no node integration by default', function (done) { + it('SharedWorker has no node integration by default', (done) => { let worker = new SharedWorker('../fixtures/workers/shared_worker_node.js') - worker.port.onmessage = function (event) { + worker.port.onmessage = (event) => { assert.equal(event.data, 'undefined undefined undefined undefined') done() } }) - it('SharedWorker has node integration with nodeIntegrationInWorker', function (done) { + it('SharedWorker has node integration with nodeIntegrationInWorker', (done) => { let webview = new WebView() - webview.addEventListener('console-message', function (e) { + webview.addEventListener('console-message', (e) => { console.log(e) }) - webview.addEventListener('ipc-message', function (e) { + webview.addEventListener('ipc-message', (e) => { assert.equal(e.channel, 'object function object function') webview.remove() done() }) - webview.src = 'file://' + fixtures + '/pages/shared_worker.html' + webview.src = `file://${fixtures}/pages/shared_worker.html` webview.setAttribute('webpreferences', 'nodeIntegration, nodeIntegrationInWorker') document.body.appendChild(webview) }) }) - describe('iframe', function () { - var iframe = null + describe('iframe', () => { + let iframe = null - beforeEach(function () { + beforeEach(() => { iframe = document.createElement('iframe') }) - afterEach(function () { + afterEach(() => { document.body.removeChild(iframe) }) - it('does not have node integration', function (done) { - iframe.src = 'file://' + fixtures + '/pages/set-global.html' + it('does not have node integration', (done) => { + iframe.src = `file://${fixtures}/pages/set-global.html` document.body.appendChild(iframe) - iframe.onload = function () { + iframe.onload = () => { assert.equal(iframe.contentWindow.test, 'undefined undefined undefined') done() } }) }) - describe('storage', function () { - it('requesting persitent quota works', function (done) { - navigator.webkitPersistentStorage.requestQuota(1024 * 1024, function (grantedBytes) { + describe('storage', () => { + it('requesting persitent quota works', (done) => { + navigator.webkitPersistentStorage.requestQuota(1024 * 1024, (grantedBytes) => { assert.equal(grantedBytes, 1048576) done() }) }) - describe('custom non standard schemes', function () { + describe('custom non standard schemes', () => { const protocolName = 'storage' let contents = null - before(function (done) { - const handler = function (request, callback) { + before((done) => { + const handler = (request, callback) => { let parsedUrl = url.parse(request.url) let filename switch (parsedUrl.pathname) { @@ -817,28 +787,26 @@ describe('chromium feature', function () { case '/cookie' : filename = 'cookie.html'; break default : filename = '' } - callback({path: fixtures + '/pages/storage/' + filename}) + callback({path: `${fixtures}/pages/storage/${filename}`}) } - protocol.registerFileProtocol(protocolName, handler, function (error) { - done(error) - }) + protocol.registerFileProtocol(protocolName, handler, (error) => done(error)) }) - after(function (done) { + after((done) => { protocol.unregisterProtocol(protocolName, () => done()) }) - beforeEach(function () { + beforeEach(() => { contents = webContents.create({}) }) - afterEach(function () { + afterEach(() => { contents.destroy() contents = null }) - it('cannot access localStorage', function (done) { - ipcMain.once('local-storage-response', function (event, error) { + it('cannot access localStorage', (done) => { + ipcMain.once('local-storage-response', (event, error) => { assert.equal( error, 'Failed to read the \'localStorage\' property from \'Window\': Access is denied for this document.') @@ -847,87 +815,85 @@ describe('chromium feature', function () { contents.loadURL(protocolName + '://host/localStorage') }) - it('cannot access sessionStorage', function (done) { - ipcMain.once('session-storage-response', function (event, error) { + it('cannot access sessionStorage', (done) => { + ipcMain.once('session-storage-response', (event, error) => { assert.equal( error, 'Failed to read the \'sessionStorage\' property from \'Window\': Access is denied for this document.') done() }) - contents.loadURL(protocolName + '://host/sessionStorage') + contents.loadURL(`${protocolName}://host/sessionStorage`) }) - it('cannot access WebSQL database', function (done) { - ipcMain.once('web-sql-response', function (event, error) { + it('cannot access WebSQL database', (done) => { + ipcMain.once('web-sql-response', (event, error) => { assert.equal( error, 'An attempt was made to break through the security policy of the user agent.') done() }) - contents.loadURL(protocolName + '://host/WebSQL') + contents.loadURL(`${protocolName}://host/WebSQL`) }) - it('cannot access indexedDB', function (done) { - ipcMain.once('indexed-db-response', function (event, error) { + it('cannot access indexedDB', (done) => { + ipcMain.once('indexed-db-response', (event, error) => { assert.equal(error, 'The user denied permission to access the database.') done() }) - contents.loadURL(protocolName + '://host/indexedDB') + contents.loadURL(`${protocolName}://host/indexedDB`) }) - it('cannot access cookie', function (done) { - ipcMain.once('cookie-response', function (event, cookie) { + it('cannot access cookie', (done) => { + ipcMain.once('cookie-response', (event, cookie) => { assert(!cookie) done() }) - contents.loadURL(protocolName + '://host/cookie') + contents.loadURL(`${protocolName}://host/cookie`) }) }) }) - describe('websockets', function () { - var wss = null - var server = null - var WebSocketServer = ws.Server + describe('websockets', () => { + let wss = null + let server = null + const WebSocketServer = ws.Server - afterEach(function () { + afterEach(() => { wss.close() server.close() }) - it('has user agent', function (done) { + it('has user agent', (done) => { server = http.createServer() - server.listen(0, '127.0.0.1', function () { - var port = server.address().port - wss = new WebSocketServer({ - server: server - }) + server.listen(0, '127.0.0.1', () => { + const port = server.address().port + wss = new WebSocketServer({ server: server }) wss.on('error', done) - wss.on('connection', function (ws) { + wss.on('connection', (ws) => { if (ws.upgradeReq.headers['user-agent']) { done() } else { done('user agent is empty') } }) - var socket = new WebSocket(`ws://127.0.0.1:${port}`) + const socket = new WebSocket(`ws://127.0.0.1:${port}`) assert(socket) }) }) }) - describe('Promise', function () { - it('resolves correctly in Node.js calls', function (done) { + describe('Promise', () => { + it('resolves correctly in Node.js calls', (done) => { document.registerElement('x-element', { prototype: Object.create(HTMLElement.prototype, { createdCallback: { - value: function () {} + value: () => {} } }) }) - setImmediate(function () { - var called = false - Promise.resolve().then(function () { + setImmediate(() => { + let called = false + Promise.resolve().then(() => { done(called ? void 0 : new Error('wrong sequence')) }) document.createElement('x-element') @@ -935,17 +901,17 @@ describe('chromium feature', function () { }) }) - it('resolves correctly in Electron calls', function (done) { + it('resolves correctly in Electron calls', (done) => { document.registerElement('y-element', { prototype: Object.create(HTMLElement.prototype, { createdCallback: { - value: function () {} + value: () => {} } }) }) - remote.getGlobal('setImmediate')(function () { - var called = false - Promise.resolve().then(function () { + remote.getGlobal('setImmediate')(() => { + let called = false + Promise.resolve().then(() => { done(called ? void 0 : new Error('wrong sequence')) }) document.createElement('y-element') @@ -954,29 +920,26 @@ describe('chromium feature', function () { }) }) - describe('fetch', function () { - it('does not crash', function (done) { - const server = http.createServer(function (req, res) { + describe('fetch', () => { + it('does not crash', (done) => { + const server = http.createServer((req, res) => { res.end('test') server.close() }) - server.listen(0, '127.0.0.1', function () { + server.listen(0, '127.0.0.1', () => { const port = server.address().port - fetch(`http://127.0.0.1:${port}`).then((res) => { - return res.body.getReader() - }).then((reader) => { - reader.read().then((r) => { - reader.cancel() - done() - }) - }).catch(function (e) { - done(e) - }) + fetch(`http://127.0.0.1:${port}`).then((res) => res.body.getReader()) + .then((reader) => { + reader.read().then((r) => { + reader.cancel() + done() + }) + }).catch((e) => done(e)) }) }) }) - describe('PDF Viewer', function () { + describe('PDF Viewer', () => { const pdfSource = url.format({ pathname: path.join(fixtures, 'assets', 'cat.pdf').replace(/\\/g, '/'), protocol: 'file', @@ -1002,6 +965,7 @@ describe('chromium feature', function () { }) } + function testPDFIsLoadedInSubFrame (page, preloadFile, done) { const pagePath = url.format({ pathname: path.join(fixtures, 'pages', page).replace(/\\/g, '/'), @@ -1027,10 +991,10 @@ describe('chromium feature', function () { it('opens when loading a pdf resource as top level navigation', function (done) { createBrowserWindow({plugins: true, preload: 'preload-pdf-loaded.js'}) ipcMain.once('pdf-loaded', function (event, state) { - assert.equal(state, 'success') + assert.equal(state, 'success') done() }) - w.webContents.on('page-title-updated', function () { + w.webContents.on('page-title-updated', () => { const parsedURL = url.parse(w.webContents.getURL(), true) assert.equal(parsedURL.protocol, 'chrome:') assert.equal(parsedURL.hostname, 'pdf-viewer') @@ -1040,13 +1004,14 @@ describe('chromium feature', function () { w.webContents.loadURL(pdfSource) }) + it('opens a pdf link given params, the query string should be escaped', function (done) { createBrowserWindow({plugins: true, preload: 'preload-pdf-loaded.js'}) ipcMain.once('pdf-loaded', function (event, state) { - assert.equal(state, 'success') + assert.equal(state, 'success') done() }) - w.webContents.on('page-title-updated', function () { + w.webContents.on('page-title-updated', () => { const parsedURL = url.parse(w.webContents.getURL(), true) assert.equal(parsedURL.protocol, 'chrome:') assert.equal(parsedURL.hostname, 'pdf-viewer') @@ -1058,10 +1023,11 @@ describe('chromium feature', function () { w.webContents.loadURL(pdfSourceWithParams) }) + it('should download a pdf when plugins are disabled', function (done) { createBrowserWindow({plugins: false, preload: 'preload-pdf-loaded.js'}) ipcRenderer.sendSync('set-download-option', false, false) - ipcRenderer.once('download-done', function (event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename) { + ipcRenderer.once('download-done', (event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename) => { assert.equal(state, 'completed') assert.equal(filename, 'cat.pdf') assert.equal(mimeType, 'application/pdf') @@ -1079,13 +1045,11 @@ describe('chromium feature', function () { allowServiceWorkers: false, corsEnabled: false }) - fetch(pdfSource).then(function (res) { + fetch(pdfSource).then((res) => { assert.equal(res.status, 200) assert.notEqual(document.title, 'cat.pdf') done() - }).catch(function (e) { - done(e) - }) + }).catch((e) => done(e)) }) it('opens when loading a pdf resource in a iframe', function (done) { @@ -1097,44 +1061,42 @@ describe('chromium feature', function () { }) }) - describe('window.alert(message, title)', function () { - it('throws an exception when the arguments cannot be converted to strings', function () { - assert.throws(function () { + describe('window.alert(message, title)', () => { + it('throws an exception when the arguments cannot be converted to strings', () => { + assert.throws(() => { window.alert({toString: null}) }, /Cannot convert object to primitive value/) - assert.throws(function () { + assert.throws(() => { window.alert('message', {toString: 3}) }, /Cannot convert object to primitive value/) }) }) - describe('window.confirm(message, title)', function () { - it('throws an exception when the arguments cannot be converted to strings', function () { - assert.throws(function () { + describe('window.confirm(message, title)', () => { + it('throws an exception when the arguments cannot be converted to strings', () => { + assert.throws(() => { window.confirm({toString: null}, 'title') }, /Cannot convert object to primitive value/) - assert.throws(function () { + assert.throws(() => { window.confirm('message', {toString: 3}) }, /Cannot convert object to primitive value/) }) }) - describe('window.history', function () { - describe('window.history.go(offset)', function () { - it('throws an exception when the argumnet cannot be converted to a string', function () { - assert.throws(function () { + describe('window.history', () => { + describe('window.history.go(offset)', () => { + it('throws an exception when the argumnet cannot be converted to a string', () => { + assert.throws(() => { window.history.go({toString: null}) }, /Cannot convert object to primitive value/) }) }) - describe('window.history.pushState', function () { + describe('window.history.pushState', () => { it('should push state after calling history.pushState() from the same url', (done) => { - w = new BrowserWindow({ - show: false - }) + w = new BrowserWindow({ show: false }) w.webContents.once('did-finish-load', () => { // History should have current page by now. assert.equal(w.webContents.length(), 1) diff --git a/spec/fixtures/api/crash.html b/spec/fixtures/api/crash.html index 8b64eddd25b..a3dbeb6ded4 100644 --- a/spec/fixtures/api/crash.html +++ b/spec/fixtures/api/crash.html @@ -1,30 +1,34 @@ + - + if (process.platform === 'win32') { + ipcRenderer.sendSync('crash-service-pid', crashReporter._crashServiceProcess.pid) + } + + if (!uploadToServer) { + ipcRenderer.sendSync('list-existing-dumps') + } + + setImmediate(() => { process.crash() }) + - + + \ No newline at end of file diff --git a/spec/fixtures/pages/webview-did-attach-event.html b/spec/fixtures/pages/webview-did-attach-event.html new file mode 100644 index 00000000000..4c6a0b9e500 --- /dev/null +++ b/spec/fixtures/pages/webview-did-attach-event.html @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/spec/modules-spec.js b/spec/modules-spec.js index 51f54201f56..f082aeddcd3 100644 --- a/spec/modules-spec.js +++ b/spec/modules-spec.js @@ -7,45 +7,45 @@ const {closeWindow} = require('./window-helpers') const nativeModulesEnabled = remote.getGlobal('nativeModulesEnabled') -describe('modules support', function () { - var fixtures = path.join(__dirname, 'fixtures') +describe('modules support', () => { + const fixtures = path.join(__dirname, 'fixtures') - describe('third-party module', function () { - describe('runas', function () { + describe('third-party module', () => { + describe('runas', () => { if (!nativeModulesEnabled) return - it('can be required in renderer', function () { + it('can be required in renderer', () => { require('runas') }) - it('can be required in node binary', function (done) { - var runas = path.join(fixtures, 'module', 'runas.js') - var child = require('child_process').fork(runas) - child.on('message', function (msg) { + it('can be required in node binary', (done) => { + const runas = path.join(fixtures, 'module', 'runas.js') + const child = require('child_process').fork(runas) + child.on('message', (msg) => { assert.equal(msg, 'ok') done() }) }) }) - describe('ffi', function () { + describe('ffi', () => { if (!nativeModulesEnabled) return if (process.platform === 'win32') return - it('does not crash', function () { - var ffi = require('ffi') - var libm = ffi.Library('libm', { + it('does not crash', () => { + const ffi = require('ffi') + const libm = ffi.Library('libm', { ceil: ['double', ['double']] }) assert.equal(libm.ceil(1.5), 2) }) }) - describe('q', function () { - var Q = require('q') - describe('Q.when', function () { - it('emits the fullfil callback', function (done) { - Q(true).then(function (val) { + describe('q', () => { + const Q = require('q') + describe('Q.when', () => { + it('emits the fullfil callback', (done) => { + Q(true).then((val) => { assert.equal(val, true) done() }) @@ -53,9 +53,9 @@ describe('modules support', function () { }) }) - describe('coffee-script', function () { - it('can be registered and used to require .coffee files', function () { - assert.doesNotThrow(function () { + describe('coffee-script', () => { + it('can be registered and used to require .coffee files', () => { + assert.doesNotThrow(() => { require('coffee-script').register() }) assert.strictEqual(require('./fixtures/module/test.coffee'), true) @@ -63,36 +63,36 @@ describe('modules support', function () { }) }) - describe('global variables', function () { - describe('process', function () { - it('can be declared in a module', function () { + describe('global variables', () => { + describe('process', () => { + it('can be declared in a module', () => { assert.strictEqual(require('./fixtures/module/declare-process'), 'declared process') }) }) - describe('global', function () { - it('can be declared in a module', function () { + describe('global', () => { + it('can be declared in a module', () => { assert.strictEqual(require('./fixtures/module/declare-global'), 'declared global') }) }) - describe('Buffer', function () { - it('can be declared in a module', function () { + describe('Buffer', () => { + it('can be declared in a module', () => { assert.strictEqual(require('./fixtures/module/declare-buffer'), 'declared Buffer') }) }) }) - describe('Module._nodeModulePaths', function () { - describe('when the path is inside the resources path', function () { - it('does not include paths outside of the resources path', function () { + describe('Module._nodeModulePaths', () => { + describe('when the path is inside the resources path', () => { + it('does not include paths outside of the resources path', () => { let modulePath = process.resourcesPath assert.deepEqual(Module._nodeModulePaths(modulePath), [ path.join(process.resourcesPath, 'node_modules') ]) modulePath = process.resourcesPath + '-foo' - let nodeModulePaths = Module._nodeModulePaths(modulePath) + const nodeModulePaths = Module._nodeModulePaths(modulePath) assert(nodeModulePaths.includes(path.join(modulePath, 'node_modules'))) assert(nodeModulePaths.includes(path.join(modulePath, '..', 'node_modules'))) @@ -124,8 +124,8 @@ describe('modules support', function () { }) }) - describe('when the path is outside the resources path', function () { - it('includes paths outside of the resources path', function () { + describe('when the path is outside the resources path', () => { + it('includes paths outside of the resources path', () => { let modulePath = path.resolve('/foo') assert.deepEqual(Module._nodeModulePaths(modulePath), [ path.join(modulePath, 'node_modules'), @@ -140,9 +140,7 @@ describe('modules support', function () { let w beforeEach(() => { - w = new BrowserWindow({ - show: false - }) + w = new BrowserWindow({show: false}) }) afterEach(async () => { diff --git a/spec/node-spec.js b/spec/node-spec.js index 7e83ec9e565..1603b160ca6 100644 --- a/spec/node-spec.js +++ b/spec/node-spec.js @@ -7,80 +7,80 @@ const {ipcRenderer, remote} = require('electron') const isCI = remote.getGlobal('isCi') -describe('node feature', function () { - var fixtures = path.join(__dirname, 'fixtures') +describe('node feature', () => { + const fixtures = path.join(__dirname, 'fixtures') - describe('child_process', function () { - describe('child_process.fork', function () { - it('works in current process', function (done) { - var child = ChildProcess.fork(path.join(fixtures, 'module', 'ping.js')) - child.on('message', function (msg) { + describe('child_process', () => { + describe('child_process.fork', () => { + it('works in current process', (done) => { + const child = ChildProcess.fork(path.join(fixtures, 'module', 'ping.js')) + child.on('message', (msg) => { assert.equal(msg, 'message') done() }) child.send('message') }) - it('preserves args', function (done) { - var args = ['--expose_gc', '-test', '1'] - var child = ChildProcess.fork(path.join(fixtures, 'module', 'process_args.js'), args) - child.on('message', function (msg) { + it('preserves args', (done) => { + const args = ['--expose_gc', '-test', '1'] + const child = ChildProcess.fork(path.join(fixtures, 'module', 'process_args.js'), args) + child.on('message', (msg) => { assert.deepEqual(args, msg.slice(2)) done() }) child.send('message') }) - it('works in forked process', function (done) { - var child = ChildProcess.fork(path.join(fixtures, 'module', 'fork_ping.js')) - child.on('message', function (msg) { + it('works in forked process', (done) => { + const child = ChildProcess.fork(path.join(fixtures, 'module', 'fork_ping.js')) + child.on('message', (msg) => { assert.equal(msg, 'message') done() }) child.send('message') }) - it('works in forked process when options.env is specifed', function (done) { - var child = ChildProcess.fork(path.join(fixtures, 'module', 'fork_ping.js'), [], { + it('works in forked process when options.env is specifed', (done) => { + const child = ChildProcess.fork(path.join(fixtures, 'module', 'fork_ping.js'), [], { path: process.env['PATH'] }) - child.on('message', function (msg) { + child.on('message', (msg) => { assert.equal(msg, 'message') done() }) child.send('message') }) - it('works in browser process', function (done) { - var fork = remote.require('child_process').fork - var child = fork(path.join(fixtures, 'module', 'ping.js')) - child.on('message', function (msg) { + it('works in browser process', (done) => { + const fork = remote.require('child_process').fork + const child = fork(path.join(fixtures, 'module', 'ping.js')) + child.on('message', (msg) => { assert.equal(msg, 'message') done() }) child.send('message') }) - it('has String::localeCompare working in script', function (done) { - var child = ChildProcess.fork(path.join(fixtures, 'module', 'locale-compare.js')) - child.on('message', function (msg) { + it('has String::localeCompare working in script', (done) => { + const child = ChildProcess.fork(path.join(fixtures, 'module', 'locale-compare.js')) + child.on('message', (msg) => { assert.deepEqual(msg, [0, -1, 1]) done() }) child.send('message') }) - it('has setImmediate working in script', function (done) { - var child = ChildProcess.fork(path.join(fixtures, 'module', 'set-immediate.js')) - child.on('message', function (msg) { + it('has setImmediate working in script', (done) => { + const child = ChildProcess.fork(path.join(fixtures, 'module', 'set-immediate.js')) + child.on('message', (msg) => { assert.equal(msg, 'ok') done() }) child.send('message') }) - it('pipes stdio', function (done) { - let child = ChildProcess.fork(path.join(fixtures, 'module', 'process-stdout.js'), {silent: true}) + it('pipes stdio', (done) => { + const child = ChildProcess.fork(path.join(fixtures, 'module', 'process-stdout.js'), {silent: true}) let data = '' child.stdout.on('data', (chunk) => { data += String(chunk) @@ -92,7 +92,7 @@ describe('node feature', function () { }) }) - it('works when sending a message to a process forked with the --eval argument', function (done) { + it('works when sending a message to a process forked with the --eval argument', (done) => { const source = "process.on('message', (message) => { process.send(message) })" const forked = ChildProcess.fork('--eval', [source]) forked.once('message', (message) => { @@ -103,16 +103,14 @@ describe('node feature', function () { }) }) - describe('child_process.spawn', function () { + describe('child_process.spawn', () => { let child - afterEach(function () { - if (child != null) { - child.kill() - } + afterEach(() => { + if (child != null) child.kill() }) - it('supports spawning Electron as a node process via the ELECTRON_RUN_AS_NODE env var', function (done) { + it('supports spawning Electron as a node process via the ELECTRON_RUN_AS_NODE env var', (done) => { child = ChildProcess.spawn(process.execPath, [path.join(__dirname, 'fixtures', 'module', 'run-as-node.js')], { env: { ELECTRON_RUN_AS_NODE: true @@ -120,10 +118,10 @@ describe('node feature', function () { }) let output = '' - child.stdout.on('data', function (data) { + child.stdout.on('data', (data) => { output += data }) - child.stdout.on('close', function () { + child.stdout.on('close', () => { assert.deepEqual(JSON.parse(output), { processLog: process.platform === 'win32' ? 'function' : 'undefined', processType: 'undefined', @@ -133,7 +131,7 @@ describe('node feature', function () { }) }) - it('supports starting the v8 inspector with --inspect/--inspect-brk', function (done) { + it('supports starting the v8 inspector with --inspect/--inspect-brk', (done) => { child = ChildProcess.spawn(process.execPath, ['--inspect-brk', path.join(__dirname, 'fixtures', 'module', 'run-as-node.js')], { env: { ELECTRON_RUN_AS_NODE: true @@ -141,36 +139,31 @@ describe('node feature', function () { }) let output = '' - child.stderr.on('data', function (data) { + child.stderr.on('data', (data) => { output += data - - if (output.trim().startsWith('Debugger listening on ws://')) { - done() - } + if (output.trim().startsWith('Debugger listening on ws://')) done() }) - child.stdout.on('data', function (data) { + child.stdout.on('data', (data) => { done(new Error(`Unexpected output: ${data.toString()}`)) }) }) }) }) - describe('contexts', function () { - describe('setTimeout in fs callback', function () { - if (process.env.TRAVIS === 'true') { - return - } + describe('contexts', () => { + describe('setTimeout in fs callback', () => { + if (process.env.TRAVIS === 'true') return - it('does not crash', function (done) { - fs.readFile(__filename, function () { + it('does not crash', (done) => { + fs.readFile(__filename, () => { setTimeout(done, 0) }) }) }) - describe('error thrown in renderer process node context', function () { - it('gets emitted as a process uncaughtException event', function (done) { + describe('error thrown in renderer process node context', () => { + it('gets emitted as a process uncaughtException event', (done) => { const error = new Error('boo!') const listeners = process.listeners('uncaughtException') process.removeAllListeners('uncaughtException') @@ -188,30 +181,31 @@ describe('node feature', function () { }) }) - describe('error thrown in main process node context', function () { - it('gets emitted as a process uncaughtException event', function () { + describe('error thrown in main process node context', () => { + it('gets emitted as a process uncaughtException event', () => { const error = ipcRenderer.sendSync('handle-uncaught-exception', 'hello') assert.equal(error, 'hello') }) }) - describe('promise rejection in main process node context', function () { - it('gets emitted as a process unhandledRejection event', function () { + describe('promise rejection in main process node context', () => { + it('gets emitted as a process unhandledRejection event', () => { const error = ipcRenderer.sendSync('handle-unhandled-rejection', 'hello') assert.equal(error, 'hello') }) }) - describe('setTimeout called under Chromium event loop in browser process', function () { - it('can be scheduled in time', function (done) { + describe('setTimeout called under Chromium event loop in browser process', () => { + it('can be scheduled in time', (done) => { remote.getGlobal('setTimeout')(done, 0) }) }) - describe('setInterval called under Chromium event loop in browser process', function () { - it('can be scheduled in time', function (done) { - var clear, interval - clear = function () { + describe('setInterval called under Chromium event loop in browser process', () => { + it('can be scheduled in time', (done) => { + let clear + let interval + clear = () => { remote.getGlobal('clearInterval')(interval) done() } @@ -220,29 +214,29 @@ describe('node feature', function () { }) }) - describe('message loop', function () { - describe('process.nextTick', function () { - it('emits the callback', function (done) { + describe('message loop', () => { + describe('process.nextTick', () => { + it('emits the callback', (done) => { process.nextTick(done) }) - it('works in nested calls', function (done) { - process.nextTick(function () { - process.nextTick(function () { + it('works in nested calls', (done) => { + process.nextTick(() => { + process.nextTick(() => { process.nextTick(done) }) }) }) }) - describe('setImmediate', function () { - it('emits the callback', function (done) { + describe('setImmediate', () => { + it('emits the callback', (done) => { setImmediate(done) }) - it('works in nested calls', function (done) { - setImmediate(function () { - setImmediate(function () { + it('works in nested calls', (done) => { + setImmediate(() => { + setImmediate(() => { setImmediate(done) }) }) @@ -250,19 +244,17 @@ describe('node feature', function () { }) }) - describe('net.connect', function () { - if (process.platform !== 'darwin') { - return - } + describe('net.connect', () => { + if (process.platform !== 'darwin') return - it('emit error when connect to a socket path without listeners', function (done) { - var socketPath = path.join(os.tmpdir(), 'atom-shell-test.sock') - var script = path.join(fixtures, 'module', 'create_socket.js') - var child = ChildProcess.fork(script, [socketPath]) - child.on('exit', function (code) { + it('emit error when connect to a socket path without listeners', (done) => { + const socketPath = path.join(os.tmpdir(), 'atom-shell-test.sock') + const script = path.join(fixtures, 'module', 'create_socket.js') + const child = ChildProcess.fork(script, [socketPath]) + child.on('exit', (code) => { assert.equal(code, 0) - var client = require('net').connect(socketPath) - client.on('error', function (error) { + const client = require('net').connect(socketPath) + client.on('error', (error) => { assert.equal(error.code, 'ECONNREFUSED') done() }) @@ -270,45 +262,45 @@ describe('node feature', function () { }) }) - describe('Buffer', function () { - it('can be created from WebKit external string', function () { - var p = document.createElement('p') + describe('Buffer', () => { + it('can be created from WebKit external string', () => { + const p = document.createElement('p') p.innerText = '闲云潭影日悠悠,物换星移几度秋' - var b = new Buffer(p.innerText) + const b = new Buffer(p.innerText) assert.equal(b.toString(), '闲云潭影日悠悠,物换星移几度秋') assert.equal(Buffer.byteLength(p.innerText), 45) }) - it('correctly parses external one-byte UTF8 string', function () { - var p = document.createElement('p') + it('correctly parses external one-byte UTF8 string', () => { + const p = document.createElement('p') p.innerText = 'Jøhänñéß' - var b = new Buffer(p.innerText) + const b = new Buffer(p.innerText) assert.equal(b.toString(), 'Jøhänñéß') assert.equal(Buffer.byteLength(p.innerText), 13) }) - it('does not crash when creating large Buffers', function () { - var buffer = new Buffer(new Array(4096).join(' ')) + it('does not crash when creating large Buffers', () => { + let buffer = new Buffer(new Array(4096).join(' ')) assert.equal(buffer.length, 4095) buffer = new Buffer(new Array(4097).join(' ')) assert.equal(buffer.length, 4096) }) }) - describe('process.stdout', function () { - it('does not throw an exception when accessed', function () { - assert.doesNotThrow(function () { + describe('process.stdout', () => { + it('does not throw an exception when accessed', () => { + assert.doesNotThrow(() => { process.stdout }) }) - it('does not throw an exception when calling write()', function () { - assert.doesNotThrow(function () { + it('does not throw an exception when calling write()', () => { + assert.doesNotThrow(() => { process.stdout.write('test') }) }) - it('should have isTTY defined on Mac and Linux', function () { + it('should have isTTY defined on Mac and Linux', () => { if (isCI) return if (process.platform === 'win32') { @@ -319,26 +311,26 @@ describe('node feature', function () { }) }) - describe('process.stdin', function () { - it('does not throw an exception when accessed', function () { - assert.doesNotThrow(function () { + describe('process.stdin', () => { + it('does not throw an exception when accessed', () => { + assert.doesNotThrow(() => { process.stdin }) }) - it('returns null when read from', function () { + it('returns null when read from', () => { assert.equal(process.stdin.read(), null) }) }) - describe('process.version', function () { - it('should not have -pre', function () { + describe('process.version', () => { + it('should not have -pre', () => { assert(!process.version.endsWith('-pre')) }) }) - describe('vm.createContext', function () { - it('should not crash', function () { + describe('vm.createContext', () => { + it('should not crash', () => { require('vm').runInNewContext('') }) }) diff --git a/spec/webview-spec.js b/spec/webview-spec.js index 1d56b430733..63b17e65d8d 100644 --- a/spec/webview-spec.js +++ b/spec/webview-spec.js @@ -12,32 +12,29 @@ const nativeModulesEnabled = remote.getGlobal('nativeModulesEnabled') describe(' tag', function () { this.timeout(3 * 60 * 1000) - var fixtures = path.join(__dirname, 'fixtures') - - var webview = null + const fixtures = path.join(__dirname, 'fixtures') + let webview = null let w = null - beforeEach(function () { + beforeEach(() => { webview = new WebView() }) - afterEach(function () { + afterEach(() => { if (!document.body.contains(webview)) { document.body.appendChild(webview) } webview.remove() - return closeWindow(w).then(function () { w = null }) + return closeWindow(w).then(() => { w = null }) }) - it('works without script tag in page', function (done) { + it('works without script tag in page', (done) => { w = new BrowserWindow({show: false}) - ipcMain.once('pong', function () { - done() - }) + ipcMain.once('pong', () => done()) w.loadURL('file://' + fixtures + '/pages/webview-no-script.html') }) - it('is disabled when nodeIntegration is disabled', function (done) { + it('is disabled when nodeIntegration is disabled', (done) => { w = new BrowserWindow({ show: false, webPreferences: { @@ -45,17 +42,17 @@ describe(' tag', function () { preload: path.join(fixtures, 'module', 'preload-webview.js') } }) - ipcMain.once('webview', function (event, type) { + ipcMain.once('webview', (event, type) => { if (type === 'undefined') { done() } else { done('WebView still exists') } }) - w.loadURL('file://' + fixtures + '/pages/webview-no-script.html') + w.loadURL(`file://${fixtures}/pages/webview-no-script.html`) }) - it('is enabled when the webviewTag option is enabled and the nodeIntegration option is disabled', function (done) { + it('is enabled when the webviewTag option is enabled and the nodeIntegration option is disabled', (done) => { w = new BrowserWindow({ show: false, webPreferences: { @@ -64,42 +61,42 @@ describe(' tag', function () { webviewTag: true } }) - ipcMain.once('webview', function (event, type) { + ipcMain.once('webview', (event, type) => { if (type !== 'undefined') { done() } else { done('WebView is not created') } }) - w.loadURL('file://' + fixtures + '/pages/webview-no-script.html') + w.loadURL(`file://${fixtures}/pages/webview-no-script.html`) }) - describe('src attribute', function () { - it('specifies the page to load', function (done) { - webview.addEventListener('console-message', function (e) { + describe('src attribute', () => { + it('specifies the page to load', (done) => { + webview.addEventListener('console-message', (e) => { assert.equal(e.message, 'a') done() }) - webview.src = 'file://' + fixtures + '/pages/a.html' + webview.src = `file://${fixtures}/pages/a.html` document.body.appendChild(webview) }) - it('navigates to new page when changed', function (done) { - var listener = function () { - webview.src = 'file://' + fixtures + '/pages/b.html' - webview.addEventListener('console-message', function (e) { + it('navigates to new page when changed', (done) => { + const listener = () => { + webview.src = `file://${fixtures}/pages/b.html` + webview.addEventListener('console-message', (e) => { assert.equal(e.message, 'b') done() }) webview.removeEventListener('did-finish-load', listener) } webview.addEventListener('did-finish-load', listener) - webview.src = 'file://' + fixtures + '/pages/a.html' + webview.src = `file://${fixtures}/pages/a.html` document.body.appendChild(webview) }) - it('resolves relative URLs', function (done) { - var listener = function (e) { + it('resolves relative URLs', (done) => { + const listener = (e) => { assert.equal(e.message, 'Window script is loaded before preload script') webview.removeEventListener('console-message', listener) done() @@ -109,7 +106,7 @@ describe(' tag', function () { document.body.appendChild(webview) }) - it('ignores empty values', function () { + it('ignores empty values', () => { assert.equal(webview.src, '') webview.src = '' assert.equal(webview.src, '') @@ -120,41 +117,41 @@ describe(' tag', function () { }) }) - describe('nodeintegration attribute', function () { - it('inserts no node symbols when not set', function (done) { - webview.addEventListener('console-message', function (e) { + describe('nodeintegration attribute', () => { + it('inserts no node symbols when not set', (done) => { + webview.addEventListener('console-message', (e) => { assert.equal(e.message, 'undefined undefined undefined undefined') done() }) - webview.src = 'file://' + fixtures + '/pages/c.html' + webview.src = `file://${fixtures}/pages/c.html` document.body.appendChild(webview) }) - it('inserts node symbols when set', function (done) { - webview.addEventListener('console-message', function (e) { + it('inserts node symbols when set', (done) => { + webview.addEventListener('console-message', (e) => { assert.equal(e.message, 'function object object') done() }) webview.setAttribute('nodeintegration', 'on') - webview.src = 'file://' + fixtures + '/pages/d.html' + webview.src = `file://${fixtures}/pages/d.html` document.body.appendChild(webview) }) - it('loads node symbols after POST navigation when set', function (done) { + it('loads node symbols after POST navigation when set', (done) => { // FIXME Figure out why this is timing out on AppVeyor if (process.env.APPVEYOR === 'True') return done() - webview.addEventListener('console-message', function (e) { + webview.addEventListener('console-message', (e) => { assert.equal(e.message, 'function object object') done() }) webview.setAttribute('nodeintegration', 'on') - webview.src = 'file://' + fixtures + '/pages/post.html' + webview.src = `file://${fixtures}/pages/post.html` document.body.appendChild(webview) }) - it('disables node integration on child windows when it is disabled on the webview', function (done) { - app.once('browser-window-created', function (event, window) { + it('disables node integration on child windows when it is disabled on the webview', (done) => { + app.once('browser-window-created', (event, window) => { assert.equal(window.webContents.getWebPreferences().nodeIntegration, false) done() }) @@ -172,12 +169,12 @@ describe(' tag', function () { document.body.appendChild(webview) }) - it('loads native modules when navigation happens', function (done) { + it('loads native modules when navigation happens', (done) => { if (!nativeModulesEnabled) return done() - var listener = function () { + const listener = () => { webview.removeEventListener('did-finish-load', listener) - var listener2 = function (e) { + const listener2 = (e) => { assert.equal(e.message, 'function') done() } @@ -186,88 +183,88 @@ describe(' tag', function () { } webview.addEventListener('did-finish-load', listener) webview.setAttribute('nodeintegration', 'on') - webview.src = 'file://' + fixtures + '/pages/native-module.html' + webview.src = `file://${fixtures}/pages/native-module.html` document.body.appendChild(webview) }) }) - describe('preload attribute', function () { - it('loads the script before other scripts in window', function (done) { - var listener = function (e) { + describe('preload attribute', () => { + it('loads the script before other scripts in window', (done) => { + const listener = (e) => { assert.equal(e.message, 'function object object function') webview.removeEventListener('console-message', listener) done() } webview.addEventListener('console-message', listener) - webview.setAttribute('preload', fixtures + '/module/preload.js') - webview.src = 'file://' + fixtures + '/pages/e.html' + webview.setAttribute('preload', `${fixtures}/module/preload.js`) + webview.src = `file://${fixtures}/pages/e.html` document.body.appendChild(webview) }) - it('preload script can still use "process" and "Buffer" when nodeintegration is off', function (done) { - webview.addEventListener('console-message', function (e) { + it('preload script can still use "process" and "Buffer" when nodeintegration is off', (done) => { + webview.addEventListener('console-message', (e) => { assert.equal(e.message, 'object undefined object function') done() }) - webview.setAttribute('preload', fixtures + '/module/preload-node-off.js') - webview.src = 'file://' + fixtures + '/api/blank.html' + webview.setAttribute('preload', `${fixtures}/module/preload-node-off.js`) + webview.src = `file://${fixtures}/api/blank.html` 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) { + it('preload script can require modules that still use "process" and "Buffer" when nodeintegration is off', (done) => { + webview.addEventListener('console-message', (e) => { assert.equal(e.message, 'object undefined object function undefined') done() }) - webview.setAttribute('preload', fixtures + '/module/preload-node-off-wrapper.js') - webview.src = 'file://' + fixtures + '/api/blank.html' + 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) { + it('receives ipc message in preload script', (done) => { + const message = 'boom!' + const listener = (e) => { assert.equal(e.channel, 'pong') assert.deepEqual(e.args, [message]) webview.removeEventListener('ipc-message', listener) done() } - var listener2 = function () { + const listener2 = () => { webview.send('ping', message) webview.removeEventListener('did-finish-load', listener2) } webview.addEventListener('ipc-message', listener) webview.addEventListener('did-finish-load', listener2) - webview.setAttribute('preload', fixtures + '/module/preload-ipc.js') - webview.src = 'file://' + fixtures + '/pages/e.html' + webview.setAttribute('preload', `${fixtures}/module/preload-ipc.js`) + webview.src = `file://${fixtures}/pages/e.html` document.body.appendChild(webview) }) - it('works without script tag in page', function (done) { - var listener = function (e) { + it('works without script tag in page', (done) => { + const listener = (e) => { assert.equal(e.message, 'function object object function') webview.removeEventListener('console-message', listener) done() } webview.addEventListener('console-message', listener) - webview.setAttribute('preload', fixtures + '/module/preload.js') - webview.src = 'file://' + fixtures + '/pages/base-page.html' + webview.setAttribute('preload', `${fixtures}/module/preload.js`) + webview.src = `file://${fixtures}pages/base-page.html` document.body.appendChild(webview) }) - it('resolves relative URLs', function (done) { - var listener = function (e) { + it('resolves relative URLs', (done) => { + const listener = (e) => { assert.equal(e.message, 'function object object function') webview.removeEventListener('console-message', listener) done() } webview.addEventListener('console-message', listener) - webview.src = 'file://' + fixtures + '/pages/e.html' + webview.src = `file://${fixtures}/pages/e.html` webview.preload = '../fixtures/module/preload.js' document.body.appendChild(webview) }) - it('ignores empty values', function () { + it('ignores empty values', () => { assert.equal(webview.preload, '') webview.preload = '' assert.equal(webview.preload, '') @@ -278,191 +275,189 @@ describe(' tag', function () { }) }) - describe('httpreferrer attribute', function () { - it('sets the referrer url', function (done) { - var referrer = 'http://github.com/' - var listener = function (e) { + describe('httpreferrer attribute', () => { + it('sets the referrer url', (done) => { + const referrer = 'http://github.com/' + const listener = (e) => { assert.equal(e.message, referrer) webview.removeEventListener('console-message', listener) done() } webview.addEventListener('console-message', listener) webview.setAttribute('httpreferrer', referrer) - webview.src = 'file://' + fixtures + '/pages/referrer.html' + webview.src = `file://${fixtures}/pages/referrer.html` document.body.appendChild(webview) }) }) - describe('useragent attribute', function () { - it('sets the user agent', function (done) { - var referrer = 'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; AS; rv:11.0) like Gecko' - var listener = function (e) { + describe('useragent attribute', () => { + it('sets the user agent', (done) => { + const referrer = 'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; AS; rv:11.0) like Gecko' + const listener = (e) => { assert.equal(e.message, referrer) webview.removeEventListener('console-message', listener) done() } webview.addEventListener('console-message', listener) webview.setAttribute('useragent', referrer) - webview.src = 'file://' + fixtures + '/pages/useragent.html' + webview.src = `file://${fixtures}/pages/useragent.html` document.body.appendChild(webview) }) }) - describe('disablewebsecurity attribute', function () { - it('does not disable web security when not set', function (done) { - var jqueryPath = path.join(__dirname, '/static/jquery-2.0.3.min.js') - var src = ` ` - var encoded = btoa(unescape(encodeURIComponent(src))) - var listener = function (e) { + describe('disablewebsecurity attribute', () => { + it('does not disable web security when not set', (done) => { + const jqueryPath = path.join(__dirname, '/static/jquery-2.0.3.min.js') + const src = ` ` + const encoded = btoa(unescape(encodeURIComponent(src))) + const listener = (e) => { assert(/Not allowed to load local resource/.test(e.message)) webview.removeEventListener('console-message', listener) done() } webview.addEventListener('console-message', listener) - webview.src = 'data:text/html;base64,' + encoded + webview.src = `data:text/html;base64,${encoded}` document.body.appendChild(webview) }) - it('disables web security when set', function (done) { - var jqueryPath = path.join(__dirname, '/static/jquery-2.0.3.min.js') - var src = ` ` - var encoded = btoa(unescape(encodeURIComponent(src))) - var listener = function (e) { + it('disables web security when set', (done) => { + const jqueryPath = path.join(__dirname, '/static/jquery-2.0.3.min.js') + const src = ` ` + const encoded = btoa(unescape(encodeURIComponent(src))) + const listener = (e) => { assert.equal(e.message, 'ok') webview.removeEventListener('console-message', listener) done() } webview.addEventListener('console-message', listener) webview.setAttribute('disablewebsecurity', '') - webview.src = 'data:text/html;base64,' + encoded + webview.src = `data:text/html;base64,${encoded}` document.body.appendChild(webview) }) - it('does not break node integration', function (done) { - webview.addEventListener('console-message', function (e) { + it('does not break node integration', (done) => { + webview.addEventListener('console-message', (e) => { assert.equal(e.message, 'function object object') done() }) webview.setAttribute('nodeintegration', 'on') webview.setAttribute('disablewebsecurity', '') - webview.src = 'file://' + fixtures + '/pages/d.html' + webview.src = `file://${fixtures}/pages/d.html` document.body.appendChild(webview) }) - it('does not break preload script', function (done) { - var listener = function (e) { + it('does not break preload script', (done) => { + const listener = (e) => { assert.equal(e.message, 'function object object function') webview.removeEventListener('console-message', listener) done() } webview.addEventListener('console-message', listener) webview.setAttribute('disablewebsecurity', '') - webview.setAttribute('preload', fixtures + '/module/preload.js') - webview.src = 'file://' + fixtures + '/pages/e.html' + webview.setAttribute('preload', `${fixtures}/module/preload.js`) + webview.src = `file://${fixtures}/pages/e.html` document.body.appendChild(webview) }) }) - describe('partition attribute', function () { - it('inserts no node symbols when not set', function (done) { - webview.addEventListener('console-message', function (e) { + describe('partition attribute', () => { + it('inserts no node symbols when not set', (done) => { + webview.addEventListener('console-message', (e) => { assert.equal(e.message, 'undefined undefined undefined undefined') done() }) - webview.src = 'file://' + fixtures + '/pages/c.html' + webview.src = `file://${fixtures}/pages/c.html` webview.partition = 'test1' document.body.appendChild(webview) }) - it('inserts node symbols when set', function (done) { - webview.addEventListener('console-message', function (e) { + it('inserts node symbols when set', (done) => { + webview.addEventListener('console-message', (e) => { assert.equal(e.message, 'function object object') done() }) webview.setAttribute('nodeintegration', 'on') - webview.src = 'file://' + fixtures + '/pages/d.html' + webview.src = `file://${fixtures}/pages/d.html` webview.partition = 'test2' document.body.appendChild(webview) }) - it('isolates storage for different id', function (done) { - var listener = function (e) { + it('isolates storage for different id', (done) => { + const listener = (e) => { assert.equal(e.message, ' 0') webview.removeEventListener('console-message', listener) done() } window.localStorage.setItem('test', 'one') webview.addEventListener('console-message', listener) - webview.src = 'file://' + fixtures + '/pages/partition/one.html' + webview.src = `file://${fixtures}/pages/partition/one.html` webview.partition = 'test3' document.body.appendChild(webview) }) - it('uses current session storage when no id is provided', function (done) { - var listener = function (e) { + it('uses current session storage when no id is provided', (done) => { + const listener = (e) => { assert.equal(e.message, 'one 1') webview.removeEventListener('console-message', listener) done() } window.localStorage.setItem('test', 'one') webview.addEventListener('console-message', listener) - webview.src = 'file://' + fixtures + '/pages/partition/one.html' + webview.src = `file://${fixtures}/pages/partition/one.html` document.body.appendChild(webview) }) }) - describe('allowpopups attribute', function () { - if (process.env.TRAVIS === 'true' && process.platform === 'darwin') { - return - } + describe('allowpopups attribute', () => { + if (process.env.TRAVIS === 'true' && process.platform === 'darwin') return - it('can not open new window when not set', function (done) { - var listener = function (e) { + it('can not open new window when not set', (done) => { + const listener = (e) => { assert.equal(e.message, 'null') webview.removeEventListener('console-message', listener) done() } webview.addEventListener('console-message', listener) - webview.src = 'file://' + fixtures + '/pages/window-open-hide.html' + webview.src = `file://${fixtures}/pages/window-open-hide.html` document.body.appendChild(webview) }) - it('can open new window when set', function (done) { - var listener = function (e) { + it('can open new window when set', (done) => { + const listener = (e) => { assert.equal(e.message, 'window') webview.removeEventListener('console-message', listener) done() } webview.addEventListener('console-message', listener) webview.setAttribute('allowpopups', 'on') - webview.src = 'file://' + fixtures + '/pages/window-open-hide.html' + webview.src = `file://${fixtures}/pages/window-open-hide.html` document.body.appendChild(webview) }) }) - describe('webpreferences attribute', function () { - it('can enable nodeintegration', function (done) { - webview.addEventListener('console-message', function (e) { + describe('webpreferences attribute', () => { + it('can enable nodeintegration', (done) => { + webview.addEventListener('console-message', (e) => { assert.equal(e.message, 'function object object') done() }) webview.setAttribute('webpreferences', 'nodeIntegration') - webview.src = 'file://' + fixtures + '/pages/d.html' + webview.src = `file://${fixtures}/pages/d.html` document.body.appendChild(webview) }) - it('can disables web security and enable nodeintegration', function (done) { - var jqueryPath = path.join(__dirname, '/static/jquery-2.0.3.min.js') - var src = ` ` - var encoded = btoa(unescape(encodeURIComponent(src))) - var listener = function (e) { + it('can disables web security and enable nodeintegration', (done) => { + const jqueryPath = path.join(__dirname, '/static/jquery-2.0.3.min.js') + const src = ` ` + const encoded = btoa(unescape(encodeURIComponent(src))) + const listener = (e) => { assert.equal(e.message, 'ok function') webview.removeEventListener('console-message', listener) done() } webview.addEventListener('console-message', listener) webview.setAttribute('webpreferences', 'webSecurity=no, nodeIntegration=yes') - webview.src = 'data:text/html;base64,' + encoded + webview.src = `data:text/html;base64,${encoded}` document.body.appendChild(webview) }) @@ -499,60 +494,58 @@ describe(' tag', function () { }) }) - describe('new-window event', function () { - if (process.env.TRAVIS === 'true' && process.platform === 'darwin') { - return - } + describe('new-window event', () => { + if (process.env.TRAVIS === 'true' && process.platform === 'darwin') return - it('emits when window.open is called', function (done) { - webview.addEventListener('new-window', function (e) { + it('emits when window.open is called', (done) => { + webview.addEventListener('new-window', (e) => { assert.equal(e.url, 'http://host/') assert.equal(e.frameName, 'host') done() }) - webview.src = 'file://' + fixtures + '/pages/window-open.html' + webview.src = `file://${fixtures}/pages/window-open.html` document.body.appendChild(webview) }) - it('emits when link with target is called', function (done) { - webview.addEventListener('new-window', function (e) { + it('emits when link with target is called', (done) => { + webview.addEventListener('new-window', (e) => { assert.equal(e.url, 'http://host/') assert.equal(e.frameName, 'target') done() }) - webview.src = 'file://' + fixtures + '/pages/target-name.html' + webview.src = `file://${fixtures}/pages/target-name.html` document.body.appendChild(webview) }) }) - describe('ipc-message event', function () { - it('emits when guest sends a ipc message to browser', function (done) { - webview.addEventListener('ipc-message', function (e) { + describe('ipc-message event', () => { + it('emits when guest sends a ipc message to browser', (done) => { + webview.addEventListener('ipc-message', (e) => { assert.equal(e.channel, 'channel') assert.deepEqual(e.args, ['arg1', 'arg2']) done() }) - webview.src = 'file://' + fixtures + '/pages/ipc-message.html' + webview.src = `file://${fixtures}/pages/ipc-message.html` webview.setAttribute('nodeintegration', 'on') document.body.appendChild(webview) }) }) - describe('page-title-set event', function () { - it('emits when title is set', function (done) { - webview.addEventListener('page-title-set', function (e) { + describe('page-title-set event', () => { + it('emits when title is set', (done) => { + webview.addEventListener('page-title-set', (e) => { assert.equal(e.title, 'test') assert(e.explicitSet) done() }) - webview.src = 'file://' + fixtures + '/pages/a.html' + webview.src = `file://${fixtures}/pages/a.html` document.body.appendChild(webview) }) }) - describe('page-favicon-updated event', function () { - it('emits when favicon urls are received', function (done) { - webview.addEventListener('page-favicon-updated', function (e) { + describe('page-favicon-updated event', () => { + it('emits when favicon urls are received', (done) => { + webview.addEventListener('page-favicon-updated', (e) => { assert.equal(e.favicons.length, 2) if (process.platform === 'win32') { assert(/^file:\/\/\/[A-Z]:\/favicon.png$/i.test(e.favicons[0])) @@ -561,33 +554,33 @@ describe(' tag', function () { } done() }) - webview.src = 'file://' + fixtures + '/pages/a.html' + webview.src = `file://${fixtures}/pages/a.html` document.body.appendChild(webview) }) }) - describe('will-navigate event', function () { - it('emits when a url that leads to oustide of the page is clicked', function (done) { - webview.addEventListener('will-navigate', function (e) { + describe('will-navigate event', () => { + it('emits when a url that leads to oustide of the page is clicked', (done) => { + webview.addEventListener('will-navigate', (e) => { assert.equal(e.url, 'http://host/') done() }) - webview.src = 'file://' + fixtures + '/pages/webview-will-navigate.html' + webview.src = `file://${fixtures}/pages/webview-will-navigate.html` document.body.appendChild(webview) }) }) - describe('did-navigate event', function () { - var p = path.join(fixtures, 'pages', 'webview-will-navigate.html') + describe('did-navigate event', () => { + let p = path.join(fixtures, 'pages', 'webview-will-navigate.html') p = p.replace(/\\/g, '/') - var pageUrl = url.format({ + const pageUrl = url.format({ protocol: 'file', slashes: true, pathname: p }) - it('emits when a url that leads to outside of the page is clicked', function (done) { - webview.addEventListener('did-navigate', function (e) { + it('emits when a url that leads to outside of the page is clicked', (done) => { + webview.addEventListener('did-navigate', (e) => { assert.equal(e.url, pageUrl) done() }) @@ -596,42 +589,42 @@ describe(' tag', function () { }) }) - describe('did-navigate-in-page event', function () { - it('emits when an anchor link is clicked', function (done) { - var p = path.join(fixtures, 'pages', 'webview-did-navigate-in-page.html') + describe('did-navigate-in-page event', () => { + it('emits when an anchor link is clicked', (done) => { + let p = path.join(fixtures, 'pages', 'webview-did-navigate-in-page.html') p = p.replace(/\\/g, '/') - var pageUrl = url.format({ + const pageUrl = url.format({ protocol: 'file', slashes: true, pathname: p }) - webview.addEventListener('did-navigate-in-page', function (e) { - assert.equal(e.url, pageUrl + '#test_content') + webview.addEventListener('did-navigate-in-page', (e) => { + assert.equal(e.url, `${pageUrl}#test_content`) done() }) webview.src = pageUrl document.body.appendChild(webview) }) - it('emits when window.history.replaceState is called', function (done) { - webview.addEventListener('did-navigate-in-page', function (e) { + it('emits when window.history.replaceState is called', (done) => { + webview.addEventListener('did-navigate-in-page', (e) => { assert.equal(e.url, 'http://host/') done() }) - webview.src = 'file://' + fixtures + '/pages/webview-did-navigate-in-page-with-history.html' + webview.src = `file://${fixtures}/pages/webview-did-navigate-in-page-with-history.html` document.body.appendChild(webview) }) - it('emits when window.location.hash is changed', function (done) { - var p = path.join(fixtures, 'pages', 'webview-did-navigate-in-page-with-hash.html') + it('emits when window.location.hash is changed', (done) => { + let p = path.join(fixtures, 'pages', 'webview-did-navigate-in-page-with-hash.html') p = p.replace(/\\/g, '/') - var pageUrl = url.format({ + const pageUrl = url.format({ protocol: 'file', slashes: true, pathname: p }) - webview.addEventListener('did-navigate-in-page', function (e) { - assert.equal(e.url, pageUrl + '#test') + webview.addEventListener('did-navigate-in-page', (e) => { + assert.equal(e.url, `${pageUrl}#test`) done() }) webview.src = pageUrl @@ -639,91 +632,89 @@ describe(' tag', function () { }) }) - describe('close event', function () { - it('should fire when interior page calls window.close', function (done) { - webview.addEventListener('close', function () { - done() - }) - webview.src = 'file://' + fixtures + '/pages/close.html' + describe('close event', () => { + it('should fire when interior page calls window.close', (done) => { + webview.addEventListener('close', () => done()) + webview.src = `file://${fixtures}/pages/close.html` document.body.appendChild(webview) }) }) - describe('devtools-opened event', function () { - it('should fire when webview.openDevTools() is called', function (done) { - var listener = function () { + describe('devtools-opened event', () => { + it('should fire when webview.openDevTools() is called', (done) => { + const listener = () => { webview.removeEventListener('devtools-opened', listener) webview.closeDevTools() done() } webview.addEventListener('devtools-opened', listener) - webview.addEventListener('dom-ready', function () { + webview.addEventListener('dom-ready', () => { webview.openDevTools() }) - webview.src = 'file://' + fixtures + '/pages/base-page.html' + webview.src = `file://${fixtures}/pages/base-page.html` document.body.appendChild(webview) }) }) - describe('devtools-closed event', function () { - it('should fire when webview.closeDevTools() is called', function (done) { - var listener2 = function () { + describe('devtools-closed event', () => { + it('should fire when webview.closeDevTools() is called', (done) => { + const listener2 = () => { webview.removeEventListener('devtools-closed', listener2) done() } - var listener = function () { + const listener = () => { webview.removeEventListener('devtools-opened', listener) webview.closeDevTools() } webview.addEventListener('devtools-opened', listener) webview.addEventListener('devtools-closed', listener2) - webview.addEventListener('dom-ready', function () { + webview.addEventListener('dom-ready', () => { webview.openDevTools() }) - webview.src = 'file://' + fixtures + '/pages/base-page.html' + webview.src = `file://${fixtures}/pages/base-page.html` document.body.appendChild(webview) }) }) - describe('devtools-focused event', function () { - it('should fire when webview.openDevTools() is called', function (done) { - var listener = function () { + describe('devtools-focused event', () => { + it('should fire when webview.openDevTools() is called', (done) => { + const listener = () => { webview.removeEventListener('devtools-focused', listener) webview.closeDevTools() done() } webview.addEventListener('devtools-focused', listener) - webview.addEventListener('dom-ready', function () { + webview.addEventListener('dom-ready', () => { webview.openDevTools() }) - webview.src = 'file://' + fixtures + '/pages/base-page.html' + webview.src = `file://${fixtures}/pages/base-page.html` document.body.appendChild(webview) }) }) - describe('.reload()', function () { - it('should emit beforeunload handler', function (done) { - var listener = function (e) { + describe('.reload()', () => { + it('should emit beforeunload handler', (done) => { + const listener = (e) => { assert.equal(e.channel, 'onbeforeunload') webview.removeEventListener('ipc-message', listener) done() } - var listener2 = function () { + const listener2 = () => { webview.reload() webview.removeEventListener('did-finish-load', listener2) } webview.addEventListener('ipc-message', listener) webview.addEventListener('did-finish-load', listener2) webview.setAttribute('nodeintegration', 'on') - webview.src = 'file://' + fixtures + '/pages/beforeunload-false.html' + webview.src = `file://${fixtures}/pages/beforeunload-false.html` document.body.appendChild(webview) }) }) - describe('.goForward()', function () { - it('should work after a replaced history entry', function (done) { - var loadCount = 1 - var listener = function (e) { + describe('.goForward()', () => { + it('should work after a replaced history entry', (done) => { + let loadCount = 1 + const listener = (e) => { if (loadCount === 1) { assert.equal(e.channel, 'history') assert.equal(e.args[0], 1) @@ -738,9 +729,9 @@ describe(' tag', function () { } } - var loadListener = function (e) { + const loadListener = (e) => { if (loadCount === 1) { - webview.src = 'file://' + fixtures + '/pages/base-page.html' + webview.src = `file://${fixtures}/pages/base-page.html` } else if (loadCount === 2) { assert(webview.canGoBack()) assert(!webview.canGoForward()) @@ -756,20 +747,20 @@ describe(' tag', function () { done() } - loadCount++ + loadCount += 1 } webview.addEventListener('ipc-message', listener) webview.addEventListener('did-finish-load', loadListener) webview.setAttribute('nodeintegration', 'on') - webview.src = 'file://' + fixtures + '/pages/history-replace.html' + webview.src = `file://${fixtures}/pages/history-replace.html` document.body.appendChild(webview) }) }) - describe('.clearHistory()', function () { - it('should clear the navigation history', function (done) { - var listener = function (e) { + describe('.clearHistory()', () => { + it('should clear the navigation history', (done) => { + const listener = (e) => { assert.equal(e.channel, 'history') assert.equal(e.args[0], 2) assert(webview.canGoBack()) @@ -780,18 +771,18 @@ describe(' tag', function () { } webview.addEventListener('ipc-message', listener) webview.setAttribute('nodeintegration', 'on') - webview.src = 'file://' + fixtures + '/pages/history.html' + webview.src = `file://${fixtures}/pages/history.html` document.body.appendChild(webview) }) }) - describe('basic auth', function () { - var auth = require('basic-auth') + describe('basic auth', () => { + const auth = require('basic-auth') - it('should authenticate with correct credentials', function (done) { - var message = 'Authenticated' - var server = http.createServer(function (req, res) { - var credentials = auth(req) + it('should authenticate with correct credentials', (done) => { + const message = 'Authenticated' + const server = http.createServer((req, res) => { + const credentials = auth(req) if (credentials.name === 'test' && credentials.pass === 'test') { res.end(message) } else { @@ -799,64 +790,64 @@ describe(' tag', function () { } server.close() }) - server.listen(0, '127.0.0.1', function () { - var port = server.address().port - webview.addEventListener('ipc-message', function (e) { + server.listen(0, '127.0.0.1', () => { + const port = server.address().port + webview.addEventListener('ipc-message', (e) => { assert.equal(e.channel, message) done() }) - webview.src = 'file://' + fixtures + '/pages/basic-auth.html?port=' + port + webview.src = `file://${fixtures}/pages/basic-auth.html?port=${port}` webview.setAttribute('nodeintegration', 'on') document.body.appendChild(webview) }) }) }) - describe('dom-ready event', function () { - it('emits when document is loaded', function (done) { - var server = http.createServer(function () {}) - server.listen(0, '127.0.0.1', function () { - var port = server.address().port - webview.addEventListener('dom-ready', function () { + describe('dom-ready event', () => { + it('emits when document is loaded', (done) => { + const server = http.createServer(() => {}) + server.listen(0, '127.0.0.1', () => { + const port = server.address().port + webview.addEventListener('dom-ready', () => { done() }) - webview.src = 'file://' + fixtures + '/pages/dom-ready.html?port=' + port + webview.src = `file://${fixtures}/pages/dom-ready.html?port=${port}` document.body.appendChild(webview) }) }) - it('throws a custom error when an API method is called before the event is emitted', function () { - assert.throws(function () { + it('throws a custom error when an API method is called before the event is emitted', () => { + assert.throws(() => { webview.stop() }, 'Cannot call stop because the webContents is unavailable. The WebView must be attached to the DOM and the dom-ready event emitted before this method can be called.') }) }) - describe('executeJavaScript', function () { - it('should support user gesture', function (done) { + describe('executeJavaScript', () => { + it('should support user gesture', (done) => { if (process.env.TRAVIS !== 'true' || process.platform === 'darwin') return done() - var listener = function () { + const listener = () => { webview.removeEventListener('enter-html-full-screen', listener) done() } - var listener2 = function () { - var jsScript = "document.querySelector('video').webkitRequestFullscreen()" + const listener2 = () => { + const jsScript = "document.querySelector('video').webkitRequestFullscreen()" webview.executeJavaScript(jsScript, true) webview.removeEventListener('did-finish-load', listener2) } webview.addEventListener('enter-html-full-screen', listener) webview.addEventListener('did-finish-load', listener2) - webview.src = 'file://' + fixtures + '/pages/fullscreen.html' + webview.src = `file://${fixtures}/pages/fullscreen.html` document.body.appendChild(webview) }) - it('can return the result of the executed script', function (done) { + it('can return the result of the executed script', (done) => { if (process.env.TRAVIS === 'true' && process.platform === 'darwin') return done() - var listener = function () { - var jsScript = "'4'+2" - webview.executeJavaScript(jsScript, false, function (result) { + const listener = () => { + const jsScript = "'4'+2" + webview.executeJavaScript(jsScript, false, (result) => { assert.equal(result, '42') done() }) @@ -868,32 +859,32 @@ describe(' tag', function () { }) }) - describe('sendInputEvent', function () { - it('can send keyboard event', function (done) { - webview.addEventListener('ipc-message', function (e) { + describe('sendInputEvent', () => { + it('can send keyboard event', (done) => { + webview.addEventListener('ipc-message', (e) => { assert.equal(e.channel, 'keyup') assert.deepEqual(e.args, ['C', 'KeyC', 67, true, false]) done() }) - webview.addEventListener('dom-ready', function () { + webview.addEventListener('dom-ready', () => { webview.sendInputEvent({ type: 'keyup', keyCode: 'c', modifiers: ['shift'] }) }) - webview.src = 'file://' + fixtures + '/pages/onkeyup.html' + webview.src = `file://${fixtures}/pages/onkeyup.html` webview.setAttribute('nodeintegration', 'on') document.body.appendChild(webview) }) - it('can send mouse event', function (done) { - webview.addEventListener('ipc-message', function (e) { + it('can send mouse event', (done) => { + webview.addEventListener('ipc-message', (e) => { assert.equal(e.channel, 'mouseup') assert.deepEqual(e.args, [10, 20, false, true]) done() }) - webview.addEventListener('dom-ready', function () { + webview.addEventListener('dom-ready', () => { webview.sendInputEvent({ type: 'mouseup', modifiers: ['ctrl'], @@ -901,32 +892,32 @@ describe(' tag', function () { y: 20 }) }) - webview.src = 'file://' + fixtures + '/pages/onmouseup.html' + webview.src = `file://${fixtures}/pages/onmouseup.html` webview.setAttribute('nodeintegration', 'on') document.body.appendChild(webview) }) }) - describe('media-started-playing media-paused events', function () { - it('emits when audio starts and stops playing', function (done) { - var audioPlayed = false - webview.addEventListener('media-started-playing', function () { + describe('media-started-playing media-paused events', () => { + it('emits when audio starts and stops playing', (done) => { + let audioPlayed = false + webview.addEventListener('media-started-playing', () => { audioPlayed = true }) - webview.addEventListener('media-paused', function () { + webview.addEventListener('media-paused', () => { assert(audioPlayed) done() }) - webview.src = 'file://' + fixtures + '/pages/audio.html' + webview.src = `file://${fixtures}/pages/audio.html` document.body.appendChild(webview) }) }) - describe('found-in-page event', function () { - it('emits when a request is made', function (done) { + describe('found-in-page event', () => { + it('emits when a request is made', (done) => { let requestId = null let activeMatchOrdinal = [] - const listener = function (e) { + const listener = (e) => { assert.equal(e.result.requestId, requestId) assert.equal(e.result.matches, 3) activeMatchOrdinal.push(e.result.activeMatchOrdinal) @@ -938,29 +929,27 @@ describe(' tag', function () { listener2() } } - const listener2 = function () { + const listener2 = () => { requestId = webview.findInPage('virtual') } webview.addEventListener('found-in-page', listener) webview.addEventListener('did-finish-load', listener2) - webview.src = 'file://' + fixtures + '/pages/content.html' + webview.src = `file://${fixtures}/pages/content.html` document.body.appendChild(webview) }) }) - xdescribe('did-change-theme-color event', function () { - it('emits when theme color changes', function (done) { - webview.addEventListener('did-change-theme-color', function () { - done() - }) - webview.src = 'file://' + fixtures + '/pages/theme-color.html' + describe('did-change-theme-color event', () => { + it('emits when theme color changes', (done) => { + webview.addEventListener('did-change-theme-color', () => done()) + webview.src = `file://${fixtures}/pages/theme-color.html` document.body.appendChild(webview) }) }) - describe('permission-request event', function () { + describe('permission-request event', () => { function setUpRequestHandler (webview, requestedPermission, completed) { - var listener = function (webContents, permission, callback) { + const listener = function (webContents, permission, callback) { if (webContents.getId() === webview.getId()) { // requestMIDIAccess with sysex requests both midi and midiSysex so // grant the first midi one and then reject the midiSysex one @@ -976,94 +965,89 @@ describe(' tag', function () { session.fromPartition(webview.partition).setPermissionRequestHandler(listener) } - it('emits when using navigator.getUserMedia api', function (done) { - if (isCI) { - done() - return - } + it('emits when using navigator.getUserMedia api', (done) => { + if (isCI) return done() - webview.addEventListener('ipc-message', function (e) { + webview.addEventListener('ipc-message', (e) => { assert.equal(e.channel, 'message') assert.deepEqual(e.args, ['PermissionDeniedError']) done() }) - webview.src = 'file://' + fixtures + '/pages/permissions/media.html' + webview.src = `file://${fixtures}/pages/permissions/media.html` webview.partition = 'permissionTest' webview.setAttribute('nodeintegration', 'on') setUpRequestHandler(webview, 'media') document.body.appendChild(webview) }) - it('emits when using navigator.geolocation api', function (done) { - webview.addEventListener('ipc-message', function (e) { + it('emits when using navigator.geolocation api', (done) => { + webview.addEventListener('ipc-message', (e) => { assert.equal(e.channel, 'message') assert.deepEqual(e.args, ['User denied Geolocation']) done() }) - webview.src = 'file://' + fixtures + '/pages/permissions/geolocation.html' + webview.src = `file://${fixtures}/pages/permissions/geolocation.html` webview.partition = 'permissionTest' webview.setAttribute('nodeintegration', 'on') setUpRequestHandler(webview, 'geolocation') document.body.appendChild(webview) }) - it('emits when using navigator.requestMIDIAccess without sysex api', function (done) { - webview.addEventListener('ipc-message', function (e) { + it('emits when using navigator.requestMIDIAccess without sysex api', (done) => { + webview.addEventListener('ipc-message', (e) => { assert.equal(e.channel, 'message') assert.deepEqual(e.args, ['SecurityError']) done() }) - webview.src = 'file://' + fixtures + '/pages/permissions/midi.html' + webview.src = `file://${fixtures}/pages/permissions/midi.html` webview.partition = 'permissionTest' webview.setAttribute('nodeintegration', 'on') setUpRequestHandler(webview, 'midi') document.body.appendChild(webview) }) - it('emits when using navigator.requestMIDIAccess with sysex api', function (done) { - webview.addEventListener('ipc-message', function (e) { + it('emits when using navigator.requestMIDIAccess with sysex api', (done) => { + webview.addEventListener('ipc-message', (e) => { assert.equal(e.channel, 'message') assert.deepEqual(e.args, ['SecurityError']) done() }) - webview.src = 'file://' + fixtures + '/pages/permissions/midi-sysex.html' + webview.src = `file://${fixtures}/pages/permissions/midi-sysex.html` webview.partition = 'permissionTest' webview.setAttribute('nodeintegration', 'on') setUpRequestHandler(webview, 'midiSysex') document.body.appendChild(webview) }) - it('emits when accessing external protocol', function (done) { + it('emits when accessing external protocol', (done) => { webview.src = 'magnet:test' webview.partition = 'permissionTest' setUpRequestHandler(webview, 'openExternal', done) document.body.appendChild(webview) }) - it('emits when using Notification.requestPermission', function (done) { - webview.addEventListener('ipc-message', function (e) { + it('emits when using Notification.requestPermission', (done) => { + webview.addEventListener('ipc-message', (e) => { assert.equal(e.channel, 'message') assert.deepEqual(e.args, ['granted']) done() }) - webview.src = 'file://' + fixtures + '/pages/permissions/notification.html' + webview.src = `file://${fixtures}/pages/permissions/notification.html` webview.partition = 'permissionTest' webview.setAttribute('nodeintegration', 'on') - session.fromPartition(webview.partition).setPermissionRequestHandler(function (webContents, permission, callback) { + session.fromPartition(webview.partition).setPermissionRequestHandler((webContents, permission, callback) => { if (webContents.getId() === webview.getId()) { assert.equal(permission, 'notifications') - setTimeout(function () { - callback(true) - }, 10) + setTimeout(() => { callback(true) }, 10) } }) document.body.appendChild(webview) }) }) - describe('.getWebContents', function () { - it('can return the webcontents associated', function (done) { - webview.addEventListener('did-finish-load', function () { + describe('.getWebContents', () => { + it('can return the webcontents associated', (done) => { + webview.addEventListener('did-finish-load', () => { const webviewContents = webview.getWebContents() assert(webviewContents) assert.equal(webviewContents.getURL(), 'about:blank') @@ -1074,18 +1058,18 @@ describe(' tag', function () { }) }) - describe('did-get-response-details event', function () { - it('emits for the page and its resources', function (done) { + describe('did-get-response-details event', () => { + it('emits for the page and its resources', (done) => { // expected {fileName: resourceType} pairs - var expectedResources = { + const expectedResources = { 'did-get-response-details.html': 'mainFrame', 'logo.png': 'image' } - var responses = 0 - webview.addEventListener('did-get-response-details', function (event) { - responses++ - var fileName = event.newURL.slice(event.newURL.lastIndexOf('/') + 1) - var expectedType = expectedResources[fileName] + let responses = 0 + webview.addEventListener('did-get-response-details', (event) => { + responses += 1 + const fileName = event.newURL.slice(event.newURL.lastIndexOf('/') + 1) + const expectedType = expectedResources[fileName] assert(!!expectedType, `Unexpected response details for ${event.newURL}`) assert(typeof event.status === 'boolean', 'status should be boolean') assert.equal(event.httpResponseCode, 200) @@ -1094,45 +1078,38 @@ describe(' tag', function () { assert(!!event.headers, 'headers should be present') assert(typeof event.headers === 'object', 'headers should be object') assert.equal(event.resourceType, expectedType, 'Incorrect resourceType') - if (responses === Object.keys(expectedResources).length) { - done() - } + if (responses === Object.keys(expectedResources).length) done() }) - webview.src = 'file://' + path.join(fixtures, 'pages', 'did-get-response-details.html') + webview.src = `file://${path.join(fixtures, 'pages', 'did-get-response-details.html')}` document.body.appendChild(webview) }) }) - describe('document.visibilityState/hidden', function () { - afterEach(function () { + describe('document.visibilityState/hidden', () => { + afterEach(() => { ipcMain.removeAllListeners('pong') }) - it('updates when the window is shown after the ready-to-show event', function (done) { - w = new BrowserWindow({ - show: false - }) - - w.once('ready-to-show', function () { + it('updates when the window is shown after the ready-to-show event', (done) => { + w = new BrowserWindow({ show: false }) + w.once('ready-to-show', () => { w.show() }) - ipcMain.on('pong', function (event, visibilityState, hidden) { + ipcMain.on('pong', (event, visibilityState, hidden) => { if (!hidden) { assert.equal(visibilityState, 'visible') done() } }) - w.loadURL('file://' + fixtures + '/pages/webview-visibilitychange.html') + w.loadURL(`file://${fixtures}/pages/webview-visibilitychange.html`) }) - it('inherits the parent window visibility state and receives visibilitychange events', function (done) { - w = new BrowserWindow({ - show: false - }) + it('inherits the parent window visibility state and receives visibilitychange events', (done) => { + w = new BrowserWindow({ show: false }) - ipcMain.once('pong', function (event, visibilityState, hidden) { + ipcMain.once('pong', (event, visibilityState, hidden) => { assert.equal(visibilityState, 'hidden') assert.equal(hidden, true) @@ -1141,11 +1118,9 @@ describe(' tag', function () { assert.equal(hidden, false) done() }) - w.webContents.emit('-window-visibility-change', 'visible') }) - - w.loadURL('file://' + fixtures + '/pages/webview-visibilitychange.html') + w.loadURL(`file://${fixtures}/pages/webview-visibilitychange.html`) }) }) @@ -1157,7 +1132,7 @@ describe(' tag', function () { done() }) webview.setAttribute('nodeintegration', 'yes') - webview.src = 'file://' + fixtures + '/pages/a.html' + webview.src = `file://${fixtures}/pages/a.html` document.body.appendChild(webview) }) @@ -1166,7 +1141,7 @@ describe(' tag', function () { webview.addEventListener('destroyed', () => { done() }) - webview.src = 'file://' + fixtures + '/pages/c.html' + webview.src = `file://${fixtures}/pages/c.html` document.body.appendChild(webview) }) @@ -1178,131 +1153,140 @@ describe(' tag', function () { }) webview.setAttribute('nodeintegration', 'yes') webview.setAttribute('preload', path.join(fixtures, 'module', 'preload-set-global.js')) - webview.src = 'file://' + fixtures + '/pages/a.html' + webview.src = `file://${fixtures}/pages/a.html` document.body.appendChild(webview) }) }) - it('loads devtools extensions registered on the parent window', function (done) { - w = new BrowserWindow({ - show: false + describe('did-attach-webview event', () => { + it('is emitted when a webview has been attached', (done) => { + w = new BrowserWindow({ show: false }) + w.webContents.on('did-attach-webview', (event, webContents) => { + ipcMain.once('webview-dom-ready', (event, id) => { + assert.equal(webContents.id, id) + done() + }) + }) + w.loadURL(`file://${fixtures}/pages/webview-did-attach-event.html`) }) + }) + it('loads devtools extensions registered on the parent window', (done) => { + w = new BrowserWindow({ show: false }) BrowserWindow.removeDevToolsExtension('foo') - var extensionPath = path.join(__dirname, 'fixtures', 'devtools-extensions', 'foo') + const extensionPath = path.join(__dirname, 'fixtures', 'devtools-extensions', 'foo') BrowserWindow.addDevToolsExtension(extensionPath) - w.loadURL('file://' + fixtures + '/pages/webview-devtools.html') + w.loadURL(`file://${fixtures}/pages/webview-devtools.html`) - ipcMain.once('answer', function (event, message) { + ipcMain.once('answer', (event, message) => { assert.equal(message.runtimeId, 'foo') assert.notEqual(message.tabId, w.webContents.id) done() }) }) - describe('guestinstance attribute', function () { - it('before loading there is no attribute', function () { + describe('guestinstance attribute', () => { + it('before loading there is no attribute', () => { document.body.appendChild(webview) assert(!webview.hasAttribute('guestinstance')) }) - it('loading a page sets the guest view', function (done) { - var loadListener = function () { + it('loading a page sets the guest view', (done) => { + const loadListener = () => { webview.removeEventListener('did-finish-load', loadListener, false) - var instance = webview.getAttribute('guestinstance') + const instance = webview.getAttribute('guestinstance') assert.equal(instance, parseInt(instance)) - var guest = getGuestWebContents(parseInt(instance)) + const guest = getGuestWebContents(parseInt(instance)) assert.equal(guest, webview.getWebContents()) done() } webview.addEventListener('did-finish-load', loadListener, false) - webview.src = 'file://' + fixtures + '/api/blank.html' + webview.src = `file://${fixtures}/api/blank.html` document.body.appendChild(webview) }) - it('deleting the attribute destroys the webview', function (done) { - var loadListener = function () { + it('deleting the attribute destroys the webview', (done) => { + const loadListener = () => { webview.removeEventListener('did-finish-load', loadListener, false) - var destroyListener = function () { + const destroyListener = () => { webview.removeEventListener('destroyed', destroyListener, false) assert.equal(getGuestWebContents(instance), null) done() } webview.addEventListener('destroyed', destroyListener, false) - var instance = parseInt(webview.getAttribute('guestinstance')) + const instance = parseInt(webview.getAttribute('guestinstance')) webview.removeAttribute('guestinstance') } webview.addEventListener('did-finish-load', loadListener, false) - webview.src = 'file://' + fixtures + '/api/blank.html' + webview.src = `file://${fixtures}/api/blank.html` document.body.appendChild(webview) }) - it('setting the attribute on a new webview moves the contents', function (done) { - var loadListener = function () { + it('setting the attribute on a new webview moves the contents', (done) => { + const loadListener = () => { webview.removeEventListener('did-finish-load', loadListener, false) - var webContents = webview.getWebContents() - var instance = webview.getAttribute('guestinstance') + const webContents = webview.getWebContents() + const instance = webview.getAttribute('guestinstance') - var destroyListener = function () { + const destroyListener = () => { webview.removeEventListener('destroyed', destroyListener, false) assert.equal(webContents, webview2.getWebContents()) // Make sure that events are hooked up to the right webview now - webview2.addEventListener('console-message', function (e) { + webview2.addEventListener('console-message', (e) => { assert.equal(e.message, 'a') document.body.removeChild(webview2) done() }) - webview2.src = 'file://' + fixtures + '/pages/a.html' + webview2.src = `file://${fixtures}/pages/a.html` } webview.addEventListener('destroyed', destroyListener, false) - var webview2 = new WebView() + const webview2 = new WebView() webview2.setAttribute('guestinstance', instance) document.body.appendChild(webview2) } webview.addEventListener('did-finish-load', loadListener, false) - webview.src = 'file://' + fixtures + '/api/blank.html' + webview.src = `file://${fixtures}/api/blank.html` document.body.appendChild(webview) }) - it('setting the attribute to an invalid guestinstance does nothing', function (done) { - var loadListener = function () { + it('setting the attribute to an invalid guestinstance does nothing', (done) => { + const loadListener = () => { webview.removeEventListener('did-finish-load', loadListener, false) webview.setAttribute('guestinstance', 55) // Make sure that events are still hooked up to the webview - webview.addEventListener('console-message', function (e) { + webview.addEventListener('console-message', (e) => { assert.equal(e.message, 'a') done() }) - - webview.src = 'file://' + fixtures + '/pages/a.html' + webview.src = `file://${fixtures}/pages/a.html` } webview.addEventListener('did-finish-load', loadListener, false) - webview.src = 'file://' + fixtures + '/api/blank.html' + webview.src = `file://${fixtures}/api/blank.html` document.body.appendChild(webview) }) - it('setting the attribute on an existing webview moves the contents', function (done) { - var load1Listener = function () { + it('setting the attribute on an existing webview moves the contents', (done) => { + const load1Listener = () => { webview.removeEventListener('did-finish-load', load1Listener, false) - var webContents = webview.getWebContents() - var instance = webview.getAttribute('guestinstance') - var destroyedInstance + const webContents = webview.getWebContents() + const instance = webview.getAttribute('guestinstance') + let destroyedInstance - var destroyListener = function () { + const destroyListener = () => { webview.removeEventListener('destroyed', destroyListener, false) assert.equal(webContents, webview2.getWebContents()) assert.equal(null, getGuestWebContents(parseInt(destroyedInstance))) // Make sure that events are hooked up to the right webview now - webview2.addEventListener('console-message', function (e) { + webview2.addEventListener('console-message', (e) => { assert.equal(e.message, 'a') document.body.removeChild(webview2) done() @@ -1312,8 +1296,8 @@ describe(' tag', function () { } webview.addEventListener('destroyed', destroyListener, false) - var webview2 = new WebView() - var load2Listener = function () { + const webview2 = new WebView() + const load2Listener = () => { webview2.removeEventListener('did-finish-load', load2Listener, false) destroyedInstance = webview2.getAttribute('guestinstance') assert.notEqual(instance, destroyedInstance) @@ -1329,71 +1313,69 @@ describe(' tag', function () { document.body.appendChild(webview) }) - it('moving a guest back to its original webview should work', function (done) { - var loadListener = function () { + it('moving a guest back to its original webview should work', (done) => { + const loadListener = () => { webview.removeEventListener('did-finish-load', loadListener, false) - var webContents = webview.getWebContents() - var instance = webview.getAttribute('guestinstance') + const webContents = webview.getWebContents() + const instance = webview.getAttribute('guestinstance') - var destroy1Listener = function () { + const destroy1Listener = () => { webview.removeEventListener('destroyed', destroy1Listener, false) assert.equal(webContents, webview2.getWebContents()) assert.equal(null, webview.getWebContents()) - var destroy2Listener = function () { + const destroy2Listener = () => { webview2.removeEventListener('destroyed', destroy2Listener, false) assert.equal(webContents, webview.getWebContents()) assert.equal(null, webview2.getWebContents()) // Make sure that events are hooked up to the right webview now - webview.addEventListener('console-message', function (e) { + webview.addEventListener('console-message', (e) => { assert.equal(e.message, 'a') document.body.removeChild(webview2) done() }) - webview.src = 'file://' + fixtures + '/pages/a.html' + webview.src = `file://${fixtures}/pages/a.html` } webview2.addEventListener('destroyed', destroy2Listener, false) - webview.setAttribute('guestinstance', instance) } webview.addEventListener('destroyed', destroy1Listener, false) - var webview2 = new WebView() + const webview2 = new WebView() webview2.setAttribute('guestinstance', instance) document.body.appendChild(webview2) } webview.addEventListener('did-finish-load', loadListener, false) - webview.src = 'file://' + fixtures + '/api/blank.html' + webview.src = `file://${fixtures}/api/blank.html` document.body.appendChild(webview) }) - it('setting the attribute on a webview in a different window moves the contents', function (done) { - var loadListener = function () { + it('setting the attribute on a webview in a different window moves the contents', (done) => { + const loadListener = () => { webview.removeEventListener('did-finish-load', loadListener, false) - var instance = webview.getAttribute('guestinstance') + const instance = webview.getAttribute('guestinstance') w = new BrowserWindow({ show: false }) - w.webContents.once('did-finish-load', function () { - ipcMain.once('pong', function () { + w.webContents.once('did-finish-load', () => { + ipcMain.once('pong', () => { assert(!webview.hasAttribute('guestinstance')) - done() }) w.webContents.send('guestinstance', instance) }) - w.loadURL('file://' + fixtures + '/pages/webview-move-to-window.html') + w.loadURL(`file://${fixtures}/pages/webview-move-to-window.html`) } webview.addEventListener('did-finish-load', loadListener, false) - webview.src = 'file://' + fixtures + '/api/blank.html' + webview.src = `file://${fixtures}/api/blank.html` document.body.appendChild(webview) }) - it('does not delete the guestinstance attribute when moving the webview to another parent node', function (done) { + it('does not delete the guestinstance attribute when moving the webview to another parent node', (done) => { webview.addEventListener('dom-ready', function domReadyListener () { - webview.addEventListener('did-attach', function () { + webview.addEventListener('did-attach', () => { assert(webview.guestinstance != null) assert(webview.getWebContents() != null) done() @@ -1401,20 +1383,20 @@ describe(' tag', function () { document.body.replaceChild(webview, div) }) - webview.src = 'file://' + fixtures + '/pages/a.html' + webview.src = `file://${fixtures}/pages/a.html` const div = document.createElement('div') div.appendChild(webview) document.body.appendChild(div) }) - it('does not destroy the webContents when hiding/showing the webview (regression)', function (done) { + it('does not destroy the webContents when hiding/showing the webview (regression)', (done) => { webview.addEventListener('dom-ready', function domReadyListener () { const instance = webview.getAttribute('guestinstance') assert(instance != null) // Wait for event directly since attach happens asynchronously over IPC - ipcMain.once('ELECTRON_GUEST_VIEW_MANAGER_ATTACH_GUEST', function () { + ipcMain.once('ELECTRON_GUEST_VIEW_MANAGER_ATTACH_GUEST', () => { assert(webview.getWebContents() != null) assert.equal(instance, webview.getAttribute('guestinstance')) done() @@ -1424,18 +1406,18 @@ describe(' tag', function () { webview.offsetHeight webview.style.display = 'block' }) - webview.src = 'file://' + fixtures + '/pages/a.html' + webview.src = `file://${fixtures}/pages/a.html` document.body.appendChild(webview) }) - it('does not reload the webContents when hiding/showing the webview (regression)', function (done) { + it('does not reload the webContents when hiding/showing the webview (regression)', (done) => { webview.addEventListener('dom-ready', function domReadyListener () { - webview.addEventListener('did-start-loading', function () { + webview.addEventListener('did-start-loading', () => { done(new Error('webview started loading unexpectedly')) }) // Wait for event directly since attach happens asynchronously over IPC - webview.addEventListener('did-attach', function () { + webview.addEventListener('did-attach', () => { done() }) @@ -1443,15 +1425,15 @@ describe(' tag', function () { webview.offsetHeight webview.style.display = 'block' }) - webview.src = 'file://' + fixtures + '/pages/a.html' + webview.src = `file://${fixtures}/pages/a.html` document.body.appendChild(webview) }) }) - describe('DOM events', function () { + describe('DOM events', () => { let div - beforeEach(function () { + beforeEach(() => { div = document.createElement('div') div.style.width = '100px' div.style.height = '10px' @@ -1460,12 +1442,12 @@ describe(' tag', function () { webview.style.width = '100%' }) - afterEach(function () { + afterEach(() => { if (div != null) div.remove() }) - it('emits resize events', function (done) { - webview.addEventListener('dom-ready', function () { + it('emits resize events', (done) => { + webview.addEventListener('dom-ready', () => { div.style.width = '1234px' div.style.height = '789px' }) @@ -1490,9 +1472,9 @@ describe(' tag', function () { assert(!webview.hasAttribute('disableguestresize')) }) - it('resizes guest when attribute is not present', done => { + it('resizes guest when attribute is not present', (done) => { w = new BrowserWindow({show: false, width: 200, height: 200}) - w.loadURL('file://' + fixtures + '/pages/webview-guest-resize.html') + w.loadURL(`file://${fixtures}/pages/webview-guest-resize.html`) w.webContents.once('did-finish-load', () => { const CONTENT_SIZE = 300 @@ -1521,7 +1503,7 @@ describe(' tag', function () { it('does not resize guest when attribute is present', done => { w = new BrowserWindow({show: false, width: 200, height: 200}) - w.loadURL('file://' + fixtures + '/pages/webview-no-guest-resize.html') + w.loadURL(`file://${fixtures}/pages/webview-no-guest-resize.html`) w.webContents.once('did-finish-load', () => { const CONTENT_SIZE = 300 @@ -1554,7 +1536,7 @@ describe(' tag', function () { it('dispatches element resize event even when attribute is present', done => { w = new BrowserWindow({show: false, width: 200, height: 200}) - w.loadURL('file://' + fixtures + '/pages/webview-no-guest-resize.html') + w.loadURL(`file://${fixtures}/pages/webview-no-guest-resize.html`) w.webContents.once('did-finish-load', () => { const CONTENT_SIZE = 300 @@ -1572,7 +1554,7 @@ describe(' tag', function () { if (process.env.TRAVIS === 'true') return done() w = new BrowserWindow({show: false, width: 200, height: 200}) - w.loadURL('file://' + fixtures + '/pages/webview-no-guest-resize.html') + w.loadURL(`file://${fixtures}/pages/webview-no-guest-resize.html`) w.webContents.once('did-finish-load', () => { const GUEST_WIDTH = 10 @@ -1681,7 +1663,7 @@ describe(' tag', function () { }) describe('nativeWindowOpen option', () => { - beforeEach(function () { + beforeEach(() => { webview.setAttribute('allowpopups', 'on') webview.setAttribute('nodeintegration', 'on') webview.setAttribute('webpreferences', 'nativeWindowOpen=1') @@ -1692,7 +1674,7 @@ describe(' tag', function () { assert.equal(content, 'Hello') done() }) - webview.src = 'file://' + path.join(fixtures, 'api', 'native-window-open-blank.html') + webview.src = `file://${path.join(fixtures, 'api', 'native-window-open-blank.html')}` document.body.appendChild(webview) }) @@ -1701,7 +1683,7 @@ describe(' tag', function () { assert.equal(content, 'Hello') done() }) - webview.src = 'file://' + path.join(fixtures, 'api', 'native-window-open-file.html') + webview.src = `file://${path.join(fixtures, 'api', 'native-window-open-file.html')}` document.body.appendChild(webview) }) @@ -1711,7 +1693,7 @@ describe(' tag', function () { assert.equal(windowOpenReturnedNull, true) done() }) - webview.src = 'file://' + path.join(fixtures, 'api', 'native-window-open-no-allowpopups.html') + webview.src = `file://${path.join(fixtures, 'api', 'native-window-open-no-allowpopups.html')}` document.body.appendChild(webview) }) @@ -1720,25 +1702,23 @@ describe(' tag', function () { assert.equal(content, 'Blocked a frame with origin "file://" from accessing a cross-origin frame.') done() }) - webview.src = 'file://' + path.join(fixtures, 'api', 'native-window-open-cross-origin.html') + webview.src = `file://${path.join(fixtures, 'api', 'native-window-open-cross-origin.html')}` document.body.appendChild(webview) }) it('emits a new-window event', (done) => { - webview.addEventListener('new-window', function (e) { + webview.addEventListener('new-window', (e) => { assert.equal(e.url, 'http://host/') assert.equal(e.frameName, 'host') done() }) - webview.src = 'file://' + fixtures + '/pages/window-open.html' + webview.src = `file://${fixtures}/pages/window-open.html` document.body.appendChild(webview) }) it('emits a browser-window-created event', (done) => { - app.once('browser-window-created', () => { - done() - }) - webview.src = 'file://' + fixtures + '/pages/window-open.html' + app.once('browser-window-created', () => done()) + webview.src = `file://${fixtures}/pages/window-open.html` document.body.appendChild(webview) }) @@ -1749,7 +1729,7 @@ describe(' tag', function () { done() } }) - webview.src = 'file://' + fixtures + '/pages/window-open.html' + webview.src = `file://${fixtures}/pages/window-open.html` document.body.appendChild(webview) }) }) diff --git a/tools/posix/generate_breakpad_symbols.py b/tools/posix/generate_breakpad_symbols.py index d3f98d4d794..c854a23c9cf 100755 --- a/tools/posix/generate_breakpad_symbols.py +++ b/tools/posix/generate_breakpad_symbols.py @@ -117,7 +117,7 @@ def GetSharedLibraryDependenciesLinux(binary): for line in ldd.splitlines(): m = lib_re.match(line) if m: - result.append(m.group(1)) + result.append(os.path.realpath(m.group(1))) return result diff --git a/vendor/libchromiumcontent b/vendor/libchromiumcontent index d6df80e2074..1aaa5c36280 160000 --- a/vendor/libchromiumcontent +++ b/vendor/libchromiumcontent @@ -1 +1 @@ -Subproject commit d6df80e20748f35373f15c2bfbd70befb4bb6221 +Subproject commit 1aaa5c3628084035d5e349bdc9cef6dbeb87bcc8 diff --git a/vendor/native_mate b/vendor/native_mate index f047bb61bbd..bf92fa88b73 160000 --- a/vendor/native_mate +++ b/vendor/native_mate @@ -1 +1 @@ -Subproject commit f047bb61bbd985079dd93e7ed322999550efed1d +Subproject commit bf92fa88b735b8c9ff951e6ef78a531bd10e8778