Merge branch 'master' into master
This commit is contained in:
commit
9b3960fe90
125 changed files with 5299 additions and 4365 deletions
|
@ -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
44
Jenkinsfile
vendored
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)*
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -45,6 +45,7 @@ class Notification : public mate::TrackableObject<Notification>,
|
|||
~Notification() override;
|
||||
|
||||
void Show();
|
||||
void Close();
|
||||
|
||||
// Prop Getters
|
||||
base::string16 GetTitle() const;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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"];
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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_;
|
||||
|
|
|
@ -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...);
|
||||
}
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -117,6 +117,14 @@ void NativeWindow::InitFromOptions(const mate::Dictionary& options) {
|
|||
bool center;
|
||||
if (options.Get(options::kX, &x) && options.Get(options::kY, &y)) {
|
||||
SetPosition(gfx::Point(x, y));
|
||||
|
||||
#if defined(OS_WIN)
|
||||
// FIXME(felixrieseberg): Dirty, dirty workaround for
|
||||
// https://github.com/electron/electron/issues/10862
|
||||
// Somehow, we need to call `SetBounds` twice to get
|
||||
// usable results. The root cause is still unknown.
|
||||
SetPosition(gfx::Point(x, y));
|
||||
#endif
|
||||
} else if (options.Get(options::kCenter, ¢er) && center) {
|
||||
Center();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -19,6 +19,7 @@ class CertVerifierRequest;
|
|||
struct VerifyRequestParams {
|
||||
std::string hostname;
|
||||
std::string default_result;
|
||||
int error_code;
|
||||
scoped_refptr<net::X509Certificate> certificate;
|
||||
};
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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_;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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`에 반영되어 있지 않습니다. 한국어 번역
|
||||
|
|
|
@ -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) 형식으로 작성.
|
||||
|
|
|
@ -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을 빌드 하는 방법과 프로젝트에 기여하는 법
|
||||
또한 문서에 포함되어 있으니 참고하시기 바랍니다.
|
||||
|
||||
## 참조 문서 (번역)
|
||||
|
|
|
@ -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` 를 사용해야 합니다.
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
109
docs-translations/zh-CN/api/notification.md
Normal file
109
docs-translations/zh-CN/api/notification.md
Normal 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) 文档获取更多信息.
|
|
@ -0,0 +1,19 @@
|
|||
# NotificationAction 对象
|
||||
|
||||
* `type` 字符串 - 动作的类型, 可以是 `button`.
|
||||
* `text` 字符串 - (可选) 指定动作的标签.
|
||||
|
||||
## 平台 / 动作 支持
|
||||
|
||||
| 动作类型 | 平台支持 | `text`用法 | 默认 `text` | 限制 |
|
||||
|-------------|------------------|-----------------|----------------|-------------|
|
||||
| `button` | macOS | 用作按钮的标签 | "Show" | 最多只有一个按钮,如果仅提供多个,则仅使用最后一个按钮。 此操作也与 `hasReply` 不兼容,如果 `hasReply` 为 `true` ,将被忽略。 |
|
||||
|
||||
### macOS 上的按钮支持
|
||||
|
||||
为了让额外的通知按钮在 MacOS 上工作,您的应用程序必须符合以下条件。
|
||||
|
||||
* 应用程序已签名。
|
||||
* 在 `info.plist` 中,将应用程序的 `NSUserNotificationAlertStyle` 设为 `alert` 。
|
||||
|
||||
如果这些要求中的任何一个都不符合,按钮就不会出现。
|
|
@ -173,7 +173,7 @@ required[, optional]
|
|||
如果参数或方法对某些平台是唯一的,那么这些平台将使用数据类型后面的空格分隔的斜体列表来表示。 值可以是 `macOS`,`Windows` 或 `Linux`
|
||||
|
||||
```markdown
|
||||
* `animate` Boolean (optional) _macOS_ _Windows_ - Animate the thing.
|
||||
* `animate` Boolean (optional) _macOS_ _Windows_ - 进行动画处理的事情.
|
||||
```
|
||||
|
||||
`Array` 类型的参数, 必须在指定数组下面的描述中描述可能包含的元素.
|
||||
|
|
57
docs-translations/zh-CN/tutorial/notifications.md
Normal file
57
docs-translations/zh-CN/tutorial/notifications.md
Normal 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.
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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`.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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])`
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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': [
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:'])
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
})())
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
16
package.json
16
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "electron",
|
||||
"version": "1.8.1",
|
||||
"version": "1.8.2-beta.2",
|
||||
"repository": "https://github.com/electron/electron",
|
||||
"description": "Build cross platform desktop apps with JavaScript, HTML, and CSS",
|
||||
"devDependencies": {
|
||||
|
@ -9,15 +9,18 @@
|
|||
"check-for-leaks": "^1.0.2",
|
||||
"colors": "^1.1.2",
|
||||
"dotenv-safe": "^4.0.4",
|
||||
"dugite": "^1.45.0",
|
||||
"electabul": "~0.0.4",
|
||||
"electron-docs-linter": "^2.3.3",
|
||||
"electron-typescript-definitions": "^1.2.7",
|
||||
"electron-typescript-definitions": "^1.2.10",
|
||||
"github": "^9.2.0",
|
||||
"heads": "^1.3.0",
|
||||
"husky": "^0.14.3",
|
||||
"minimist": "^1.2.0",
|
||||
"nugget": "^2.0.1",
|
||||
"request": "^2.68.0",
|
||||
"standard": "^8.4.0",
|
||||
"standard-markdown": "^4.0.0",
|
||||
"sumchecker": "^2.0.2",
|
||||
"temp": "^0.8.3"
|
||||
},
|
||||
"standard": {
|
||||
|
@ -49,12 +52,15 @@
|
|||
"lint-api-docs-js": "standard-markdown docs && standard-markdown docs-translations",
|
||||
"create-api-json": "electron-docs-linter docs --outfile=out/electron-api.json --version=$npm_package_version",
|
||||
"create-typescript-definitions": "npm run create-api-json && electron-typescript-definitions --in=out/electron-api.json --out=out/electron.d.ts",
|
||||
"merge-release": "node ./script/merge-release.js",
|
||||
"mock-release": "node ./script/ci-release-build.js",
|
||||
"preinstall": "node -e 'process.exit(0)'",
|
||||
"publish-to-npm": "node ./script/publish-to-npm.js",
|
||||
"prepack": "check-for-leaks",
|
||||
"prepush": "check-for-leaks",
|
||||
"prerelease": "node ./script/prerelease",
|
||||
"release": "./script/upload.py -p",
|
||||
"prepare-release": "node ./script/prepare-release.js",
|
||||
"prerelease": "python ./script/bootstrap.py -v --dev && npm run build",
|
||||
"release": "node ./script/release.js",
|
||||
"repl": "python ./script/start.py --interactive",
|
||||
"start": "python ./script/start.py",
|
||||
"test": "python ./script/test.py"
|
||||
|
|
|
@ -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):
|
||||
|
|
56
script/ci-release-build.js
Normal file
56
script/ci-release-build.js
Normal 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
10
script/cpplint.py
vendored
|
@ -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
116
script/merge-release.js
Executable 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
173
script/prepare-release.js
Executable 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)
|
|
@ -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))
|
||||
}
|
|
@ -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
462
script/release.js
Executable 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)
|
|
@ -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()
|
||||
|
|
114
script/upload.py
114
script/upload.py
|
@ -36,17 +36,16 @@ PDB_NAME = get_zip_name(PROJECT_NAME, ELECTRON_VERSION, 'pdb')
|
|||
def main():
|
||||
args = parse_args()
|
||||
|
||||
if not args.publish_release:
|
||||
if not dist_newer_than_head():
|
||||
run_python_script('create-dist.py')
|
||||
if not dist_newer_than_head():
|
||||
run_python_script('create-dist.py')
|
||||
|
||||
build_version = get_electron_build_version()
|
||||
if not ELECTRON_VERSION.startswith(build_version):
|
||||
error = 'Tag name ({0}) should match build version ({1})\n'.format(
|
||||
ELECTRON_VERSION, build_version)
|
||||
sys.stderr.write(error)
|
||||
sys.stderr.flush()
|
||||
return 1
|
||||
build_version = get_electron_build_version()
|
||||
if not ELECTRON_VERSION.startswith(build_version):
|
||||
error = 'Tag name ({0}) should match build version ({1})\n'.format(
|
||||
ELECTRON_VERSION, build_version)
|
||||
sys.stderr.write(error)
|
||||
sys.stderr.flush()
|
||||
return 1
|
||||
|
||||
github = GitHub(auth_token())
|
||||
releases = github.repos(ELECTRON_REPO).releases.get()
|
||||
|
@ -57,54 +56,44 @@ def main():
|
|||
tag_exists = True
|
||||
break
|
||||
|
||||
assert tag_exists == args.overwrite, \
|
||||
'You have to pass --overwrite to overwrite a published release'
|
||||
|
||||
if not args.overwrite:
|
||||
release = create_or_get_release_draft(github, releases, args.version,
|
||||
tag_exists)
|
||||
|
||||
if args.publish_release:
|
||||
# Upload the Node SHASUMS*.txt.
|
||||
run_python_script('upload-node-checksums.py', '-v', ELECTRON_VERSION)
|
||||
|
||||
# Upload the index.json.
|
||||
run_python_script('upload-index-json.py')
|
||||
|
||||
# Create and upload the Electron SHASUMS*.txt
|
||||
release_electron_checksums(release)
|
||||
|
||||
# Press the publish button.
|
||||
publish_release(github, release['id'])
|
||||
|
||||
# TODO: run publish-to-npm script here
|
||||
|
||||
# Do not upload other files when passed "-p".
|
||||
return
|
||||
if not args.upload_to_s3:
|
||||
assert tag_exists == args.overwrite, \
|
||||
'You have to pass --overwrite to overwrite a published release'
|
||||
if not args.overwrite:
|
||||
release = create_or_get_release_draft(github, releases, args.version,
|
||||
tag_exists)
|
||||
|
||||
# Upload Electron with GitHub Releases API.
|
||||
upload_electron(github, release, os.path.join(DIST_DIR, DIST_NAME))
|
||||
upload_electron(github, release, os.path.join(DIST_DIR, SYMBOLS_NAME))
|
||||
upload_electron(github, release, os.path.join(DIST_DIR, DIST_NAME),
|
||||
args.upload_to_s3)
|
||||
upload_electron(github, release, os.path.join(DIST_DIR, SYMBOLS_NAME),
|
||||
args.upload_to_s3)
|
||||
if PLATFORM == 'darwin':
|
||||
upload_electron(github, release, os.path.join(DIST_DIR,
|
||||
'electron-api.json'))
|
||||
upload_electron(github, release, os.path.join(DIST_DIR, 'electron.d.ts'))
|
||||
upload_electron(github, release, os.path.join(DIST_DIR, DSYM_NAME))
|
||||
'electron-api.json'), args.upload_to_s3)
|
||||
upload_electron(github, release, os.path.join(DIST_DIR, 'electron.d.ts'),
|
||||
args.upload_to_s3)
|
||||
upload_electron(github, release, os.path.join(DIST_DIR, DSYM_NAME),
|
||||
args.upload_to_s3)
|
||||
elif PLATFORM == 'win32':
|
||||
upload_electron(github, release, os.path.join(DIST_DIR, PDB_NAME))
|
||||
upload_electron(github, release, os.path.join(DIST_DIR, PDB_NAME),
|
||||
args.upload_to_s3)
|
||||
|
||||
# Upload free version of ffmpeg.
|
||||
ffmpeg = get_zip_name('ffmpeg', ELECTRON_VERSION)
|
||||
upload_electron(github, release, os.path.join(DIST_DIR, ffmpeg))
|
||||
upload_electron(github, release, os.path.join(DIST_DIR, ffmpeg),
|
||||
args.upload_to_s3)
|
||||
|
||||
# Upload chromedriver and mksnapshot for minor version update.
|
||||
if parse_version(args.version)[2] == '0':
|
||||
chromedriver = get_zip_name('chromedriver', ELECTRON_VERSION)
|
||||
upload_electron(github, release, os.path.join(DIST_DIR, chromedriver))
|
||||
upload_electron(github, release, os.path.join(DIST_DIR, chromedriver),
|
||||
args.upload_to_s3)
|
||||
mksnapshot = get_zip_name('mksnapshot', ELECTRON_VERSION)
|
||||
upload_electron(github, release, os.path.join(DIST_DIR, mksnapshot))
|
||||
upload_electron(github, release, os.path.join(DIST_DIR, mksnapshot),
|
||||
args.upload_to_s3)
|
||||
|
||||
if PLATFORM == 'win32' and not tag_exists:
|
||||
if PLATFORM == 'win32' and not tag_exists and not args.upload_to_s3:
|
||||
# Upload PDBs to Windows symbol server.
|
||||
run_python_script('upload-windows-pdb.py')
|
||||
|
||||
|
@ -123,6 +112,12 @@ def parse_args():
|
|||
parser.add_argument('-p', '--publish-release',
|
||||
help='Publish the release',
|
||||
action='store_true')
|
||||
parser.add_argument('-s', '--upload_to_s3',
|
||||
help='Upload assets to s3 bucket',
|
||||
dest='upload_to_s3',
|
||||
action='store_true',
|
||||
default=False,
|
||||
required=False)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
|
@ -206,17 +201,17 @@ def create_release_draft(github, tag):
|
|||
return r
|
||||
|
||||
|
||||
def release_electron_checksums(release):
|
||||
checksums = run_python_script('merge-electron-checksums.py',
|
||||
'-v', ELECTRON_VERSION)
|
||||
filename = 'SHASUMS256.txt'
|
||||
filepath = os.path.join(SOURCE_ROOT, filename)
|
||||
with open(filepath, 'w') as sha_file:
|
||||
sha_file.write(checksums.decode('utf-8'))
|
||||
upload_io_to_github(release, filename, filepath)
|
||||
def upload_electron(github, release, file_path, upload_to_s3):
|
||||
|
||||
# if upload_to_s3 is set, skip github upload.
|
||||
if upload_to_s3:
|
||||
bucket, access_key, secret_key = s3_config()
|
||||
key_prefix = 'electron-artifacts/{0}'.format(release['tag_name'])
|
||||
s3put(bucket, access_key, secret_key, os.path.dirname(file_path),
|
||||
key_prefix, [file_path])
|
||||
upload_sha256_checksum(release['tag_name'], file_path, key_prefix)
|
||||
return
|
||||
|
||||
def upload_electron(github, release, file_path):
|
||||
# Delete the original file before uploading in CI.
|
||||
filename = os.path.basename(file_path)
|
||||
if os.environ.has_key('CI'):
|
||||
|
@ -239,7 +234,7 @@ def upload_electron(github, release, file_path):
|
|||
arm_filename = filename.replace('armv7l', 'arm')
|
||||
arm_file_path = os.path.join(os.path.dirname(file_path), arm_filename)
|
||||
shutil.copy2(file_path, arm_file_path)
|
||||
upload_electron(github, release, arm_file_path)
|
||||
upload_electron(github, release, arm_file_path, upload_to_s3)
|
||||
|
||||
|
||||
def upload_io_to_github(release, filename, filepath):
|
||||
|
@ -249,9 +244,11 @@ def upload_io_to_github(release, filename, filepath):
|
|||
execute(['node', script_path, filepath, filename, str(release['id'])])
|
||||
|
||||
|
||||
def upload_sha256_checksum(version, file_path):
|
||||
def upload_sha256_checksum(version, file_path, key_prefix=None):
|
||||
bucket, access_key, secret_key = s3_config()
|
||||
checksum_path = '{}.sha256sum'.format(file_path)
|
||||
if key_prefix is None:
|
||||
key_prefix = 'atom-shell/tmp/{0}'.format(version)
|
||||
sha256 = hashlib.sha256()
|
||||
with open(file_path, 'rb') as f:
|
||||
sha256.update(f.read())
|
||||
|
@ -260,12 +257,7 @@ def upload_sha256_checksum(version, file_path):
|
|||
with open(checksum_path, 'w') as checksum:
|
||||
checksum.write('{} *{}'.format(sha256.hexdigest(), filename))
|
||||
s3put(bucket, access_key, secret_key, os.path.dirname(checksum_path),
|
||||
'atom-shell/tmp/{0}'.format(version), [checksum_path])
|
||||
|
||||
|
||||
def publish_release(github, release_id):
|
||||
data = dict(draft=False)
|
||||
github.repos(ELECTRON_REPO).releases(release_id).patch(data=data)
|
||||
key_prefix, [checksum_path])
|
||||
|
||||
|
||||
def auth_token():
|
||||
|
|
|
@ -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/)
|
||||
})
|
||||
|
|
|
@ -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
|
@ -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')
|
||||
|
|
|
@ -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()
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue