New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New makeSingleInstance API #12782
New makeSingleInstance API #12782
Changes from 3 commits
01bef05
cc3062a
24667e1
0e83284
067234e
85e3d16
d9b6a24
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -350,8 +350,8 @@ struct Converter<content::CertificateRequestResultType> { | |
namespace atom { | ||
|
||
ProcessMetric::ProcessMetric(int type, | ||
base::ProcessId pid, | ||
std::unique_ptr<base::ProcessMetrics> metrics) { | ||
base::ProcessId pid, | ||
std::unique_ptr<base::ProcessMetrics> metrics) { | ||
this->type = type; | ||
this->pid = pid; | ||
this->metrics = std::move(metrics); | ||
|
@@ -864,25 +864,52 @@ std::string App::GetLocale() { | |
return g_browser_process->GetApplicationLocale(); | ||
} | ||
|
||
bool App::MakeSingleInstance( | ||
const ProcessSingleton::NotificationCallback& callback) { | ||
bool App::OnSecondInstance(const base::CommandLine::StringVector& cmd, | ||
const base::FilePath& cwd) { | ||
Emit("second-instance", cmd, cwd); | ||
// This value is used literally no where, but we need it for Reasons(tm) | ||
return false; | ||
} | ||
|
||
bool App::IsPrimaryInstance(mate::Arguments* args) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
if (single_instance_state_ == SingleInstanceState::UNINITIALIZED) { | ||
args->ThrowError( | ||
"You can not check if you are the primary instance without calling " | ||
"makeSingleInstance"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (bikeshed) "Must call makeSingleInstance() before IsPrimaryInstance()" |
||
} | ||
return single_instance_state_ == SingleInstanceState::PRIMARY; | ||
} | ||
|
||
bool App::IsSingleInstance() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Method should be const |
||
if (process_singleton_) | ||
return true; | ||
return false; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. did There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah |
||
} | ||
|
||
bool App::MakeSingleInstance() { | ||
if (IsSingleInstance()) | ||
return false; | ||
|
||
base::FilePath user_dir; | ||
PathService::Get(brightray::DIR_USER_DATA, &user_dir); | ||
|
||
ProcessSingleton::NotificationCallback cb = | ||
base::Bind(&App::OnSecondInstance, base::Unretained(this)); | ||
|
||
process_singleton_.reset(new ProcessSingleton( | ||
user_dir, base::Bind(NotificationCallbackWrapper, callback))); | ||
user_dir, base::Bind(NotificationCallbackWrapper, cb))); | ||
|
||
switch (process_singleton_->NotifyOtherProcessOrCreate()) { | ||
case ProcessSingleton::NotifyResult::LOCK_ERROR: | ||
case ProcessSingleton::NotifyResult::PROFILE_IN_USE: | ||
case ProcessSingleton::NotifyResult::PROCESS_NOTIFIED: { | ||
process_singleton_.reset(); | ||
single_instance_state_ = SingleInstanceState::SECONDARY; | ||
return true; | ||
} | ||
case ProcessSingleton::NotifyResult::PROCESS_NONE: | ||
default: // Shouldn't be needed, but VS warns if it is not there. | ||
single_instance_state_ = SingleInstanceState::PRIMARY; | ||
return false; | ||
} | ||
} | ||
|
@@ -1252,6 +1279,8 @@ void App::BuildPrototype(v8::Isolate* isolate, | |
#if defined(USE_NSS_CERTS) | ||
.SetMethod("importCertificate", &App::ImportCertificate) | ||
#endif | ||
.SetMethod("isSingleInstance", &App::IsSingleInstance) | ||
.SetMethod("isPrimaryInstance", &App::IsPrimaryInstance) | ||
.SetMethod("makeSingleInstance", &App::MakeSingleInstance) | ||
.SetMethod("releaseSingleInstance", &App::ReleaseSingleInstance) | ||
.SetMethod("relaunch", &App::Relaunch) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -45,6 +45,12 @@ namespace atom { | |
enum class JumpListResult : int; | ||
#endif | ||
|
||
enum SingleInstanceState { | ||
UNINITIALIZED, | ||
PRIMARY, | ||
SECONDARY, | ||
}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider making this an |
||
|
||
struct ProcessMetric { | ||
int type; | ||
base::ProcessId pid; | ||
|
@@ -178,8 +184,12 @@ class App : public AtomBrowserClient::Delegate, | |
|
||
void SetDesktopName(const std::string& desktop_name); | ||
std::string GetLocale(); | ||
bool MakeSingleInstance( | ||
const ProcessSingleton::NotificationCallback& callback); | ||
bool OnSecondInstance(const base::CommandLine::StringVector& cmd, | ||
const base::FilePath& cwd); | ||
bool IsPrimaryInstance(mate::Arguments* args); | ||
bool IsSingleInstance(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we do go with this three-method API ... these two functions have names which are confusingly similar to each other. Since we're trying to make this readable without reaching for the docs, is there a clearer way to name these? |
||
// TODO(MarshallOfSound): Remove return value in 4.0 | ||
bool MakeSingleInstance(); | ||
void ReleaseSingleInstance(); | ||
bool Relaunch(mate::Arguments* args); | ||
void DisableHardwareAcceleration(mate::Arguments* args); | ||
|
@@ -229,6 +239,9 @@ class App : public AtomBrowserClient::Delegate, | |
std::unordered_map<base::ProcessId, std::unique_ptr<atom::ProcessMetric>>; | ||
ProcessMetricMap app_metrics_; | ||
|
||
SingleInstanceState single_instance_state_ = | ||
SingleInstanceState::UNINITIALIZED; | ||
|
||
DISALLOW_COPY_AND_ASSIGN(App); | ||
}; | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -373,6 +373,22 @@ app.on('session-created', (event, session) => { | |
}) | ||
``` | ||
|
||
### Event: 'second-instance' | ||
|
||
Returns: | ||
|
||
* `argv` String[] - An array of the second instance's command line arguments | ||
* `workingDirectory` String - The second instance's working directory | ||
|
||
This event will be emitted inside the primary instance of your application | ||
when a second instance has been executed. `argv` is an Array of the second instance's | ||
command line arguments, and `workingDirectory` is its current working directory. Usually | ||
applications respond to this by making their primary window focused and | ||
non-minimized. | ||
|
||
This event is guaranteed to be emitted after the `ready` event of `app` | ||
gets emitted. | ||
|
||
## Methods | ||
|
||
The `app` object has the following methods: | ||
|
@@ -742,33 +758,31 @@ app.setJumpList([ | |
]) | ||
``` | ||
|
||
### `app.makeSingleInstance(callback)` | ||
|
||
* `callback` Function | ||
* `argv` String[] - An array of the second instance's command line arguments | ||
* `workingDirectory` String - The second instance's working directory | ||
|
||
Returns `Boolean`. | ||
### `app.makeSingleInstance()` | ||
|
||
This method makes your application a Single Instance Application - instead of | ||
allowing multiple instances of your app to run, this will ensure that only a | ||
single instance of your app is running, and other instances signal this | ||
instance and exit. | ||
|
||
`callback` will be called by the first instance with `callback(argv, workingDirectory)` | ||
when a second instance has been executed. `argv` is an Array of the second instance's | ||
command line arguments, and `workingDirectory` is its current working directory. Usually | ||
applications respond to this by making their primary window focused and | ||
non-minimized. | ||
### `app.isSingleInstance()` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this need to be public API? It's not in your new use example in the PR There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't need to be, but it's helpful for modules that need to know whether or not they are in a single instance app There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK now we're getting somewhere, maybe there is a use case for these that I'm not seeing. When would this method be useful? |
||
|
||
The `callback` is guaranteed to be executed after the `ready` event of `app` | ||
gets emitted. | ||
Returns `Boolean` | ||
|
||
This methods returns whether or not `makeSingleInstance` has been called. | ||
|
||
This method returns `false` if your process is the primary instance of the | ||
application and your app should continue loading. And returns `true` if your | ||
### `app.isPrimaryInstance()` | ||
|
||
Returns `Boolean` | ||
|
||
This method returns `true` if your process is the primary instance of the | ||
application and your app should continue loading. And returns `false` if your | ||
process has sent its parameters to another instance, and you should immediately | ||
quit. | ||
|
||
This method will throw an error if you call it without first calling | ||
`app.makeSingleInstance()`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Why not make these a single API call as before? I don't see how that goes against the goal of replacing a callback with a signal? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because Like I've seen this before: if (app.makeSingleInstance(cb)) {
return app.quit();
} Which although is API correct, makes no sense to anyone reading that. Especially when most "action" api's that Electron exposes, the boolean value indicates success. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We're in agreement the current API is unintuitive -- the proposed API is more readable and has return values that make sense. However, I'm not sure how useful these new methods are. I don't see a use case for Which comes back to the current API: putting aside the unintuitive return value and slightly opaque name for the moment, it does have strengths in that it's impossible to call the pieces out of order, to miss a call, or to forget to register for a signal. It's very difficult to get wrong. So what if we preserved that while fixing the name and return value? Something like: if (!app.registerSecondInstanceHandler(cb)) {
app.quit();
} This seems pretty readable to me. |
||
|
||
On macOS the system enforces single instance automatically when users try to open | ||
a second instance of your app in Finder, and the `open-file` and `open-url` | ||
events will be emitted for that. However when users start your app in command | ||
|
@@ -782,15 +796,19 @@ starts: | |
const {app} = require('electron') | ||
let myWindow = null | ||
|
||
const isSecondInstance = app.makeSingleInstance((commandLine, workingDirectory) => { | ||
app.makeSingleInstance() | ||
|
||
app.on('second-instance', (commandLine, workingDirectory) => { | ||
// Someone tried to run a second instance, we should focus our window. | ||
if (myWindow) { | ||
if (myWindow.isMinimized()) myWindow.restore() | ||
myWindow.focus() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hey, not a blocker to this review (esp since it's not new to this diff) but what is the difference between It looks like this block could be replaced with |
||
} | ||
}) | ||
|
||
if (isSecondInstance) { | ||
const isPrimaryInstance = app.isPrimaryInstance() | ||
|
||
if (!isPrimaryInstance) { | ||
app.quit() | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -109,6 +109,16 @@ for (let name of events) { | |
}) | ||
} | ||
|
||
// TODO(MarshallOfSound): Remove in 4.0 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should be added to docs/tutorial/planned-breaking-changes.md |
||
const originalMakeSingleInstance = app.makeSingleInstance.bind(app) | ||
app.makeSingleInstance = (cb) => { | ||
if (cb && typeof cb === 'function') { | ||
deprecate.warn('app.makeSingleInstance(cb)', 'app.makeSingleInstance() and app.on(\'second-instance\', cb)') | ||
app.on('second-instance', (event, ...args) => cb(...args)) | ||
} | ||
return originalMakeSingleInstance() | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Needs a deprecate mesage for |
||
// Wrappers for native classes. | ||
const {DownloadItem} = process.atomBinding('download_item') | ||
Object.setPrototypeOf(DownloadItem.prototype, EventEmitter.prototype) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the kind of comment that makes kittens cry.
What would be good is adding something to
planned-breaking-changes.md
that discusses the future change (and links to the issues), and having these changes refer toplanned-breaking-changes.md
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be fair this comment and the return value still being there is me not being bothered to refactor a little bit more 😆 I'll clear this out and make it return
void
👍