Merge branch 'master' into master

This commit is contained in:
Ahmed 2017-11-08 18:03:55 +01:00 committed by GitHub
commit 9b3960fe90
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
125 changed files with 5299 additions and 4365 deletions

View file

@ -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

44
Jenkinsfile vendored Normal file
View file

@ -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()
}
}
}
}
}
}
}

View file

@ -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)*

View file

@ -1073,8 +1073,17 @@ std::vector<mate::Dictionary> 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",

View file

@ -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)

View file

@ -45,6 +45,7 @@ class Notification : public mate::TrackableObject<Notification>,
~Notification() override;
void Show();
void Close();
// Prop Getters
base::string16 GetTitle() const;

View file

@ -212,6 +212,7 @@ struct Converter<atom::VerifyRequestParams> {
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();
}
};

View file

@ -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

View file

@ -76,6 +76,7 @@ class SystemPreferences : public mate::EventEmitter<SystemPreferences>
void SetUserDefault(const std::string& name,
const std::string& type,
mate::Arguments* args);
void RemoveUserDefault(const std::string& name);
bool IsSwipeTrackingFromScrollEventsEnabled();
#endif
bool IsDarkMode();

View file

@ -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"];

View file

@ -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<api::Session> 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());

View file

@ -385,6 +385,9 @@ class WebContents : public mate::TrackableObject<WebContents>,
// get the zoom level.
void OnGetZoomLevel(IPC::Message* reply_msg);
void InitZoomController(content::WebContents* web_contents,
const mate::Dictionary& options);
v8::Global<v8::Value> session_;
v8::Global<v8::Value> devtools_web_contents_;
v8::Global<v8::Value> debugger_;

View file

@ -79,8 +79,12 @@ class EventEmitter : public Wrappable<T> {
const Args&... args) {
v8::Locker locker(isolate());
v8::HandleScope handle_scope(isolate());
v8::Local<v8::Object> wrapper = GetWrapper();
if (wrapper.IsEmpty()) {
return false;
}
v8::Local<v8::Object> event = internal::CreateJSEvent(
isolate(), GetWrapper(), sender, message);
isolate(), wrapper, sender, message);
return EmitWithEvent(name, event, args...);
}

View file

@ -62,7 +62,10 @@ class TrackableObject : public TrackableObjectBase,
public:
// Mark the JS object as destroyed.
void MarkDestroyed() {
Wrappable<T>::GetWrapper()->SetAlignedPointerInInternalField(0, nullptr);
v8::Local<v8::Object> wrapper = Wrappable<T>::GetWrapper();
if (!wrapper.IsEmpty()) {
wrapper->SetAlignedPointerInInternalField(0, nullptr);
}
}
bool IsDestroyed() {

View file

@ -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, &center) && center) {
Center();
}

View file

@ -4,6 +4,7 @@
#include "atom/browser/native_window_mac.h"
#include <AvailabilityMacros.h>
#include <Quartz/Quartz.h>
#include <string>
@ -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<QLPreviewPanelDataSource, QLPreviewPanelDelegate, NSTouchBarDelegate> {
@private

View file

@ -92,6 +92,7 @@ class CertVerifierRequest : public AtomCertVerifier::Request {
std::unique_ptr<VerifyRequestParams> 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,

View file

@ -19,6 +19,7 @@ class CertVerifierRequest;
struct VerifyRequestParams {
std::string hostname;
std::string default_result;
int error_code;
scoped_refptr<net::X509Certificate> certificate;
};

View file

@ -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());
}

View file

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

View file

@ -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

View file

@ -17,7 +17,7 @@
#include "native_mate/constructor.h"
#include "native_mate/persistent_dictionary.h"
@interface AtomTouchBar : NSObject<NSScrubberDelegate, NSScrubberDataSource> {
@interface AtomTouchBar : NSObject<NSScrubberDelegate, NSScrubberDataSource, NSScrubberFlowLayoutDelegate> {
@protected
std::vector<mate::PersistentDictionary> ordered_settings_;
std::map<std::string, mate::PersistentDictionary> settings_;

View file

@ -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<mate::PersistentDictionary> items;
if (!settings.Get("items", &items)) return defaultSize;
if (itemIndex >= static_cast<NSInteger>(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

View file

@ -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<NSCoding>
@property(class, strong, readonly) NSScrubberSelectionStyle* outlineOverlayStyle;
@ -229,6 +232,12 @@ static const NSTouchBarItemIdentifier NSTouchBarItemIdentifierOtherItemsProxy =
@end
@protocol NSScrubberFlowLayoutDelegate<NSObject>
- (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

View file

@ -31,14 +31,26 @@ struct Converter<CrashReporter::UploadReportResult> {
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<std::string, std::string> GetParameters() {
return CrashReporter::GetInstance()->GetParameters();
}
void Initialize(v8::Local<v8::Object> exports, v8::Local<v8::Value> unused,
v8::Local<v8::Context> context, void* priv) {
@ -46,6 +58,9 @@ void Initialize(v8::Local<v8::Object> exports, v8::Local<v8::Value> 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",

View file

@ -541,6 +541,13 @@ mate::Handle<NativeImage> NativeImage::CreateFromDataURL(
return CreateEmpty(isolate);
}
#if !defined(OS_MACOSX)
mate::Handle<NativeImage> NativeImage::CreateFromNamedImage(
mate::Arguments* args, const std::string& name) {
return CreateEmpty(args->isolate());
}
#endif
// static
void NativeImage::BuildPrototype(
v8::Isolate* isolate, v8::Local<v8::FunctionTemplate> prototype) {
@ -609,6 +616,8 @@ void Initialize(v8::Local<v8::Object> exports, v8::Local<v8::Value> unused,
dict.SetMethod("createFromBuffer", &atom::api::NativeImage::CreateFromBuffer);
dict.SetMethod("createFromDataURL",
&atom::api::NativeImage::CreateFromDataURL);
dict.SetMethod("createFromNamedImage",
&atom::api::NativeImage::CreateFromNamedImage);
}
} // namespace

View file

@ -53,6 +53,8 @@ class NativeImage : public mate::Wrappable<NativeImage> {
mate::Arguments* args, v8::Local<v8::Value> buffer);
static mate::Handle<NativeImage> CreateFromDataURL(
v8::Isolate* isolate, const GURL& url);
static mate::Handle<NativeImage> CreateFromNamedImage(
mate::Arguments* args, const std::string& name);
static void BuildPrototype(v8::Isolate* isolate,
v8::Local<v8::FunctionTemplate> prototype);

View file

@ -6,10 +6,56 @@
#import <Cocoa/Cocoa.h>
#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> NativeImage::CreateFromNamedImage(
mate::Arguments* args, const std::string& name) {
@autoreleasepool {
std::vector<double> 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<const unsigned char*>((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];
}

View file

@ -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

View file

@ -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<std::string, std::string> CrashReporter::GetParameters() const {
return upload_parameters_;
}
#if defined(OS_MACOSX) && defined(MAS_BUILD)
// static
CrashReporter* CrashReporter::GetInstance() {

View file

@ -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<std::string, std::string> GetParameters() const;
protected:
CrashReporter();

View file

@ -5,6 +5,7 @@
#ifndef ATOM_COMMON_CRASH_REPORTER_CRASH_REPORTER_MAC_H_
#define ATOM_COMMON_CRASH_REPORTER_CRASH_REPORTER_MAC_H_
#include <map>
#include <string>
#include <vector>
@ -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<std::string, std::string> GetParameters() const override;
private:
friend struct base::DefaultSingletonTraits<CrashReporterMac>;

View file

@ -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<std::string, std::string> CrashReporterMac::GetParameters() const {
if (simple_string_dictionary_) {
std::map<std::string, std::string> 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<CrashReporter::UploadReportResult>
CrashReporterMac::GetUploadedReports(const base::FilePath& crashes_dir) {
std::vector<CrashReporter::UploadReportResult> uploaded_reports;

View file

@ -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) {

View file

@ -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

View file

@ -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()) {

View file

@ -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();
}
}

View file

@ -18,7 +18,8 @@ CocoaNotification* NotificationPresenterMac::GetNotification(
NSUserNotification* ns_notification) {
for (Notification* notification : notifications()) {
auto native_notification = static_cast<CocoaNotification*>(notification);
if ([native_notification->notification() isEqual:ns_notification])
if ([native_notification->notification().identifier
isEqual:ns_notification.identifier])
return native_notification;
}
return nullptr;

View file

@ -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

View file

@ -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`에 반영되어 있지 않습니다. 한국어 번역

View file

@ -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) 형식으로 작성.

View file

@ -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을 빌드 하는 방법과 프로젝트에 기여하는
또한 문서에 포함되어 있으니 참고하시기 바랍니다.
## 참조 문서 (번역)

View file

@ -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` 를 사용해야 합니다.

View file

@ -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)

View file

@ -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) 文档获取更多信息.

View file

@ -0,0 +1,19 @@
# NotificationAction 对象
* `type` 字符串 - 动作的类型, 可以是 `button`.
* `text` 字符串 - (可选) 指定动作的标签.
## 平台 / 动作 支持
| 动作类型 | 平台支持 | `text`用法 | 默认 `text` | 限制 |
|-------------|------------------|-----------------|----------------|-------------|
| `button` | macOS | 用作按钮的标签 | "Show" | 最多只有一个按钮,如果仅提供多个,则仅使用最后一个按钮。 此操作也与 `hasReply` 不兼容,如果 `hasReply``true` ,将被忽略。 |
### macOS 上的按钮支持
为了让额外的通知按钮在 MacOS 上工作,您的应用程序必须符合以下条件。
* 应用程序已签名。
* 在 `info.plist` 中,将应用程序的 `NSUserNotificationAlertStyle` 设为 `alert`
如果这些要求中的任何一个都不符合,按钮就不会出现。

View file

@ -173,7 +173,7 @@ required[, optional]
如果参数或方法对某些平台是唯一的,那么这些平台将使用数据类型后面的空格分隔的斜体列表来表示。 值可以是 `macOS``Windows` 或 `Linux`
```markdown
* `animate` Boolean (optional) _macOS_ _Windows_ - Animate the thing.
* `animate` Boolean (optional) _macOS_ _Windows_ - 进行动画处理的事情.
```
`Array` 类型的参数, 必须在指定数组下面的描述中描述可能包含的元素.

View file

@ -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.

View file

@ -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

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -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

View file

@ -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).

View file

@ -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`.

View file

@ -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

View file

@ -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.

View file

@ -581,6 +581,16 @@ that can't be set via `<webview>` 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
`<webview>`.
Emitted when a `<webview>` has been attached to this web contents.
### Instance Methods
#### `contents.loadURL(url[, options])`

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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");

View file

@ -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

View file

@ -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

View file

@ -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_
</body>
</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.

View file

@ -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

View file

@ -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': [

View file

@ -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) {

View file

@ -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

View file

@ -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

View file

@ -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:'])

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -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) => {

View file

@ -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()

View file

@ -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
})())
})

View file

@ -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)
})
}

View file

@ -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)

View file

@ -5,7 +5,7 @@ const {runInThisContext} = require('vm')
// https://developer.chrome.com/extensions/match_patterns
const matchesPattern = function (pattern) {
if (pattern === '<all_urls>') 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)
}
}
}
}

View file

@ -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

View file

@ -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"

View file

@ -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):

View file

@ -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))
}

10
script/cpplint.py vendored
View file

@ -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())

116
script/merge-release.js Executable file
View file

@ -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()

173
script/prepare-release.js Executable file
View file

@ -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)

View file

@ -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))
}

View file

@ -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)
})
})

462
script/release.js Executable file
View file

@ -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)

View file

@ -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()

View file

@ -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():

View file

@ -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('<title>authorized</title>')
@ -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/)
})

View file

@ -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)

File diff suppressed because it is too large Load diff

View file

@ -4,45 +4,45 @@ const {Buffer} = require('buffer')
const {clipboard, nativeImage} = require('electron')
describe('clipboard module', function () {
var fixtures = path.resolve(__dirname, 'fixtures')
describe('clipboard module', () => {
const fixtures = path.resolve(__dirname, 'fixtures')
describe('clipboard.readImage()', function () {
it('returns NativeImage intance', function () {
var p = path.join(fixtures, 'assets', 'logo.png')
var i = nativeImage.createFromPath(p)
describe('clipboard.readImage()', () => {
it('returns NativeImage instance', () => {
const p = path.join(fixtures, 'assets', 'logo.png')
const i = nativeImage.createFromPath(p)
clipboard.writeImage(p)
assert.equal(clipboard.readImage().toDataURL(), i.toDataURL())
})
})
describe('clipboard.readText()', function () {
it('returns unicode string correctly', function () {
var text = '千江有水千江月,万里无云万里天'
describe('clipboard.readText()', () => {
it('returns unicode string correctly', () => {
const text = '千江有水千江月,万里无云万里天'
clipboard.writeText(text)
assert.equal(clipboard.readText(), text)
})
})
describe('clipboard.readHTML()', function () {
it('returns markup correctly', function () {
var text = '<string>Hi</string>'
var markup = process.platform === 'darwin' ? "<meta charset='utf-8'><string>Hi</string>" : process.platform === 'linux' ? '<meta http-equiv="content-type" ' + 'content="text/html; charset=utf-8"><string>Hi</string>' : '<string>Hi</string>'
describe('clipboard.readHTML()', () => {
it('returns markup correctly', () => {
const text = '<string>Hi</string>'
const markup = process.platform === 'darwin' ? "<meta charset='utf-8'><string>Hi</string>" : process.platform === 'linux' ? '<meta http-equiv="content-type" ' + 'content="text/html; charset=utf-8"><string>Hi</string>' : '<string>Hi</string>'
clipboard.writeHTML(text)
assert.equal(clipboard.readHTML(), markup)
})
})
describe('clipboard.readRTF', function () {
it('returns rtf text correctly', function () {
var rtf = '{\\rtf1\\ansi{\\fonttbl\\f0\\fswiss Helvetica;}\\f0\\pard\nThis is some {\\b bold} text.\\par\n}'
describe('clipboard.readRTF', () => {
it('returns rtf text correctly', () => {
const rtf = '{\\rtf1\\ansi{\\fonttbl\\f0\\fswiss Helvetica;}\\f0\\pard\nThis is some {\\b bold} text.\\par\n}'
clipboard.writeRTF(rtf)
assert.equal(clipboard.readRTF(), rtf)
})
})
describe('clipboard.readBookmark', function () {
it('returns title and url', function () {
describe('clipboard.readBookmark', () => {
it('returns title and url', () => {
if (process.platform === 'linux') return
clipboard.writeBookmark('a title', 'https://electron.atom.io')
@ -59,14 +59,14 @@ describe('clipboard module', function () {
})
})
describe('clipboard.write()', function () {
it('returns data correctly', function () {
var text = 'test'
var rtf = '{\\rtf1\\utf8 text}'
var p = path.join(fixtures, 'assets', 'logo.png')
var i = nativeImage.createFromPath(p)
var markup = process.platform === 'darwin' ? "<meta charset='utf-8'><b>Hi</b>" : process.platform === 'linux' ? '<meta http-equiv="content-type" ' + 'content="text/html; charset=utf-8"><b>Hi</b>' : '<b>Hi</b>'
var bookmark = {title: 'a title', url: 'test'}
describe('clipboard.write()', () => {
it('returns data correctly', () => {
const text = 'test'
const rtf = '{\\rtf1\\utf8 text}'
const p = path.join(fixtures, 'assets', 'logo.png')
const i = nativeImage.createFromPath(p)
const markup = process.platform === 'darwin' ? "<meta charset='utf-8'><b>Hi</b>" : process.platform === 'linux' ? '<meta http-equiv="content-type" ' + 'content="text/html; charset=utf-8"><b>Hi</b>' : '<b>Hi</b>'
const bookmark = {title: 'a title', url: 'test'}
clipboard.write({
text: 'test',
html: '<b>Hi</b>',
@ -85,8 +85,8 @@ describe('clipboard module', function () {
})
})
describe('clipboard.read/writeFindText(text)', function () {
it('reads and write text to the find pasteboard', function () {
describe('clipboard.read/writeFindText(text)', () => {
it('reads and write text to the find pasteboard', () => {
if (process.platform !== 'darwin') return
clipboard.writeFindText('find this')
@ -110,8 +110,8 @@ describe('clipboard module', function () {
})
})
describe('clipboard.readBuffer(format)', function () {
it('returns a Buffer of the content for the specified format', function () {
describe('clipboard.readBuffer(format)', () => {
it('returns a Buffer of the content for the specified format', () => {
if (process.platform !== 'darwin') return
const buffer = Buffer.from('this is binary', 'utf8')

View file

@ -11,46 +11,40 @@ const {closeWindow} = require('./window-helpers')
const {remote} = require('electron')
const {app, BrowserWindow, crashReporter} = remote.require('electron')
describe('crashReporter module', function () {
if (process.mas || process.env.DISABLE_CRASH_REPORTER_TESTS) {
return
}
describe('crashReporter module', () => {
if (process.mas || process.env.DISABLE_CRASH_REPORTER_TESTS) return
var originalTempDirectory = null
var tempDirectory = null
let originalTempDirectory = null
let tempDirectory = null
before(function () {
before(() => {
tempDirectory = temp.mkdirSync('electronCrashReporterSpec-')
originalTempDirectory = app.getPath('temp')
app.setPath('temp', tempDirectory)
})
after(function () {
after(() => {
app.setPath('temp', originalTempDirectory)
})
var fixtures = path.resolve(__dirname, 'fixtures')
const fixtures = path.resolve(__dirname, 'fixtures')
const generateSpecs = (description, browserWindowOpts) => {
describe(description, function () {
var w = null
var stopServer = null
describe(description, () => {
let w = null
let stopServer = null
beforeEach(function () {
beforeEach(() => {
stopServer = null
w = new BrowserWindow(Object.assign({
show: false
}, browserWindowOpts))
w = new BrowserWindow(Object.assign({ show: false }, browserWindowOpts))
})
afterEach(function () {
return closeWindow(w).then(function () { w = null })
})
afterEach(() => closeWindow(w).then(() => { w = null }))
afterEach(function () {
afterEach(() => {
stopCrashService()
})
afterEach(function (done) {
afterEach((done) => {
if (stopServer != null) {
stopServer(done)
} else {
@ -77,7 +71,6 @@ describe('crashReporter module', function () {
done: done
})
})
it('should send minidump when node processes crash', function (done) {
if (process.env.APPVEYOR === 'True') return done()
if (process.env.TRAVIS === 'true') return done()
@ -110,7 +103,6 @@ describe('crashReporter module', function () {
done: done
})
})
it('should not send minidump if uploadToServer is false', function (done) {
this.timeout(120000)
@ -123,12 +115,8 @@ describe('crashReporter module', function () {
crashReporter.setUploadToServer(false)
}
const testDone = (uploaded) => {
if (uploaded) {
return done(new Error('Uploaded crash report'))
}
if (process.platform === 'darwin') {
crashReporter.setUploadToServer(true)
}
if (uploaded) return done(new Error('Uploaded crash report'))
if (process.platform === 'darwin') crashReporter.setUploadToServer(true)
assert(fs.existsSync(dumpFile))
done()
}
@ -136,13 +124,10 @@ describe('crashReporter module', function () {
let pollInterval
const pollDumpFile = () => {
fs.readdir(crashesDir, (err, files) => {
if (err) {
return
}
if (err) return
const dumps = files.filter((file) => /\.dmp$/.test(file) && !existingDumpFiles.has(file))
if (!dumps.length) {
return
}
if (!dumps.length) return
assert.equal(1, dumps.length)
dumpFile = path.join(crashesDir, dumps[0])
clearInterval(pollInterval)
@ -179,7 +164,6 @@ describe('crashReporter module', function () {
done: testDone.bind(null, true)
})
})
it('should send minidump with updated extra parameters', function (done) {
if (process.env.APPVEYOR === 'True') return done()
if (process.env.TRAVIS === 'true') return done()
@ -191,7 +175,7 @@ describe('crashReporter module', function () {
const crashUrl = url.format({
protocol: 'file',
pathname: path.join(fixtures, 'api', 'crash-restart.html'),
search: '?port=' + port
search: `?port=${port}`
})
w.loadURL(crashUrl)
},
@ -210,22 +194,32 @@ describe('crashReporter module', function () {
}
})
describe('.start(options)', function () {
it('requires that the companyName and submitURL options be specified', function () {
assert.throws(function () {
crashReporter.start({
companyName: 'Missing submitURL'
})
describe('getProductName', () => {
it('returns the product name if one is specified', () => {
const name = crashReporter.getProductName()
const expectedName = (process.platform === 'darwin') ? 'Electron Test' : 'Zombies'
assert.equal(name, expectedName)
})
})
describe('getTempDirectory', () => {
it('returns temp directory for app if one is specified', () => {
const tempDir = crashReporter.getTempDirectory()
assert.equal(tempDir, app.getPath('temp'))
})
})
describe('start(options)', () => {
it('requires that the companyName and submitURL options be specified', () => {
assert.throws(() => {
crashReporter.start({companyName: 'Missing submitURL'})
}, /submitURL is a required option to crashReporter\.start/)
assert.throws(function () {
crashReporter.start({
submitURL: 'Missing companyName'
})
assert.throws(() => {
crashReporter.start({submitURL: 'Missing companyName'})
}, /companyName is a required option to crashReporter\.start/)
})
it('can be called multiple times', function () {
assert.doesNotThrow(function () {
it('can be called multiple times', () => {
assert.doesNotThrow(() => {
crashReporter.start({
companyName: 'Umbrella Corporation',
submitURL: 'http://127.0.0.1/crashes'
@ -239,12 +233,41 @@ describe('crashReporter module', function () {
})
})
describe('.get/setUploadToServer', function () {
it('throws an error when called from the renderer process', function () {
describe('getCrashesDirectory', () => {
it('correctly returns the directory', () => {
const crashesDir = crashReporter.getCrashesDirectory()
let dir
if (process.platform === 'win32') {
dir = `${app.getPath('temp')}/Zombies Crashes`
} else {
dir = `${app.getPath('temp')}/Electron Test Crashes`
}
assert.equal(crashesDir, dir)
})
})
describe('getUploadedReports', () => {
it('returns an array of reports', () => {
const reports = crashReporter.getUploadedReports()
assert(typeof reports === 'object')
})
})
describe('getLastCrashReport', () => {
it('correctly returns the most recent report', () => {
if (process.env.TRAVIS === 'False') {
const reports = crashReporter.getUploadedReports()
const lastReport = reports[0]
assert(lastReport != null)
}
})
})
describe('getUploadToServer()', () => {
it('throws an error when called from the renderer process', () => {
assert.throws(() => require('electron').crashReporter.getUploadToServer())
})
it('can be read/set from the main process', function () {
it('returns true when uploadToServer is set to true', () => {
if (process.platform === 'darwin') {
crashReporter.start({
companyName: 'Umbrella Corporation',
@ -252,13 +275,87 @@ describe('crashReporter module', function () {
uploadToServer: true
})
assert.equal(crashReporter.getUploadToServer(), true)
}
})
it('returns false when uploadToServer is set to false', () => {
if (process.platform === 'darwin') {
crashReporter.start({
companyName: 'Umbrella Corporation',
submitURL: 'http://127.0.0.1/crashes',
uploadToServer: true
})
crashReporter.setUploadToServer(false)
assert.equal(crashReporter.getUploadToServer(), false)
} else {
}
})
})
describe('setUploadToServer(uploadToServer)', () => {
it('throws an error when called from the renderer process', () => {
assert.throws(() => require('electron').crashReporter.setUploadToServer('arg'))
})
it('sets uploadToServer false when called with false', () => {
if (process.platform === 'darwin') {
crashReporter.start({
companyName: 'Umbrella Corporation',
submitURL: 'http://127.0.0.1/crashes',
uploadToServer: true
})
crashReporter.setUploadToServer(false)
assert.equal(crashReporter.getUploadToServer(), false)
}
})
it('sets uploadToServer true when called with true', () => {
if (process.platform === 'darwin') {
crashReporter.start({
companyName: 'Umbrella Corporation',
submitURL: 'http://127.0.0.1/crashes',
uploadToServer: false
})
crashReporter.setUploadToServer(true)
assert.equal(crashReporter.getUploadToServer(), true)
}
})
})
describe('Parameters', () => {
it('returns all of the current parameters', () => {
crashReporter.start({
companyName: 'Umbrella Corporation',
submitURL: 'http://127.0.0.1/crashes'
})
const parameters = crashReporter.getParameters()
assert(typeof parameters === 'object')
})
it('adds a parameter to current parameters', () => {
// only run on MacOS
if (process.platform !== 'darwin') return
crashReporter.start({
companyName: 'Umbrella Corporation',
submitURL: 'http://127.0.0.1/crashes'
})
crashReporter.addExtraParameter('hello', 'world')
assert('hello' in crashReporter.getParameters())
})
it('removes a parameter from current parameters', () => {
// only run on MacOS
if (process.platform !== 'darwin') return
crashReporter.start({
companyName: 'Umbrella Corporation',
submitURL: 'http://127.0.0.1/crashes'
})
crashReporter.addExtraParameter('hello', 'world')
assert('hello' in crashReporter.getParameters())
crashReporter.removeExtraParameter('hello')
assert(!('hello' in crashReporter.getParameters()))
})
})
})
const waitForCrashReport = () => {
@ -279,9 +376,9 @@ const waitForCrashReport = () => {
}
const startServer = ({callback, processType, done}) => {
var called = false
var server = http.createServer((req, res) => {
var form = new multiparty.Form()
let called = false
let server = http.createServer((req, res) => {
const form = new multiparty.Form()
form.parse(req, (error, fields) => {
if (error) throw error
if (called) return
@ -335,7 +432,7 @@ const startServer = ({callback, processType, done}) => {
for (const connection of activeConnections) {
connection.destroy()
}
server.close(function () {
server.close(() => {
done()
})
}

View file

@ -4,11 +4,11 @@ const path = require('path')
const {closeWindow} = require('./window-helpers')
const BrowserWindow = require('electron').remote.BrowserWindow
describe('debugger module', function () {
var fixtures = path.resolve(__dirname, 'fixtures')
var w = null
describe('debugger module', () => {
const fixtures = path.resolve(__dirname, 'fixtures')
let w = null
beforeEach(function () {
beforeEach(() => {
w = new BrowserWindow({
show: false,
width: 400,
@ -16,13 +16,11 @@ describe('debugger module', function () {
})
})
afterEach(function () {
return closeWindow(w).then(function () { w = null })
})
afterEach(() => closeWindow(w).then(() => { w = null }))
describe('debugger.attach', function () {
it('fails when devtools is already open', function (done) {
w.webContents.on('did-finish-load', function () {
describe('debugger.attach', () => {
it('fails when devtools is already open', (done) => {
w.webContents.on('did-finish-load', () => {
w.webContents.openDevTools()
try {
w.webContents.debugger.attach()
@ -31,10 +29,10 @@ describe('debugger module', function () {
done()
}
})
w.webContents.loadURL('file://' + path.join(fixtures, 'pages', 'a.html'))
w.webContents.loadURL(`file://${path.join(fixtures, 'pages', 'a.html')}`)
})
it('fails when protocol version is not supported', function (done) {
it('fails when protocol version is not supported', (done) => {
try {
w.webContents.debugger.attach('2.0')
} catch (err) {
@ -43,20 +41,20 @@ describe('debugger module', function () {
}
})
it('attaches when no protocol version is specified', function (done) {
it('attaches when no protocol version is specified', (done) => {
try {
w.webContents.debugger.attach()
} catch (err) {
done('unexpected error : ' + err)
done(`unexpected error : ${err}`)
}
assert(w.webContents.debugger.isAttached())
done()
})
})
describe('debugger.detach', function () {
it('fires detach event', function (done) {
w.webContents.debugger.on('detach', function (e, reason) {
describe('debugger.detach', () => {
it('fires detach event', (done) => {
w.webContents.debugger.on('detach', (e, reason) => {
assert.equal(reason, 'target closed')
assert(!w.webContents.debugger.isAttached())
done()
@ -64,23 +62,23 @@ describe('debugger module', function () {
try {
w.webContents.debugger.attach()
} catch (err) {
done('unexpected error : ' + err)
done(`unexpected error : ${err}`)
}
w.webContents.debugger.detach()
})
})
describe('debugger.sendCommand', function () {
describe('debugger.sendCommand', () => {
let server
afterEach(function () {
afterEach(() => {
if (server != null) {
server.close()
server = null
}
})
it('retuns response', function (done) {
it('retuns response', (done) => {
w.webContents.loadURL('about:blank')
try {
w.webContents.debugger.attach()
@ -100,9 +98,9 @@ describe('debugger module', function () {
w.webContents.debugger.sendCommand('Runtime.evaluate', params, callback)
})
it('fires message event', function (done) {
var url = process.platform !== 'win32'
? 'file://' + path.join(fixtures, 'pages', 'a.html')
it('fires message event', (done) => {
const url = process.platform !== 'win32'
? `file://${path.join(fixtures, 'pages', 'a.html')}`
: 'file:///' + path.join(fixtures, 'pages', 'a.html').replace(/\\/g, '/')
w.webContents.loadURL(url)
try {
@ -110,7 +108,7 @@ describe('debugger module', function () {
} catch (err) {
done('unexpected error : ' + err)
}
w.webContents.debugger.on('message', function (e, method, params) {
w.webContents.debugger.on('message', (e, method, params) => {
if (method === 'Console.messageAdded') {
assert.equal(params.message.level, 'log')
assert.equal(params.message.url, url)
@ -122,25 +120,25 @@ describe('debugger module', function () {
w.webContents.debugger.sendCommand('Console.enable')
})
it('returns error message when command fails', function (done) {
it('returns error message when command fails', (done) => {
w.webContents.loadURL('about:blank')
try {
w.webContents.debugger.attach()
} catch (err) {
done('unexpected error : ' + err)
done(`unexpected error : ${err}`)
}
w.webContents.debugger.sendCommand('Test', function (err) {
w.webContents.debugger.sendCommand('Test', (err) => {
assert.equal(err.message, "'Test' wasn't found")
w.webContents.debugger.detach()
done()
})
})
it('handles invalid unicode characters in message', function (done) {
it('handles invalid unicode characters in message', (done) => {
try {
w.webContents.debugger.attach()
} catch (err) {
done('unexpected error : ' + err)
done(`unexpected error : ${err}`)
}
w.webContents.debugger.on('message', (event, method, params) => {

Some files were not shown because too many files have changed in this diff Show more