From a870799c32e5d3c61ba0602569a255feb36cef3f Mon Sep 17 00:00:00 2001 From: Rafael Nobre Date: Mon, 26 Jun 2017 16:14:44 -0300 Subject: [PATCH 1/8] Exposes more Handoff related APIs to Electron. --- atom/browser/api/atom_api_app.cc | 26 +++++++- atom/browser/api/atom_api_app.h | 13 +++- atom/browser/browser.h | 22 +++++++ atom/browser/browser_mac.mm | 36 ++++++++++ atom/browser/browser_observer.h | 17 ++++- atom/browser/mac/atom_application.h | 6 +- atom/browser/mac/atom_application.mm | 38 +++++++++++ atom/browser/mac/atom_application_delegate.mm | 15 +++++ docs/api/app.md | 66 +++++++++++++++++++ 9 files changed, 234 insertions(+), 5 deletions(-) diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index 4942752ce0..e9211aec3d 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -584,17 +584,35 @@ void App::OnAccessibilitySupportChanged() { } #if defined(OS_MACOSX) +void App::OnWillContinueUserActivity( + bool* prevent_default, + const std::string& type) { + *prevent_default = Emit("will-continue-activity", type); +} +void App::OnDidFailToContinueUserActivity( + const std::string& type, + const std::string& error) { + Emit("continue-activity-error", type, error); +} void App::OnContinueUserActivity( bool* prevent_default, const std::string& type, const base::DictionaryValue& user_info) { *prevent_default = Emit("continue-activity", type, user_info); } - +void App::OnUserActivityWasContinued( + const std::string& type, + const base::DictionaryValue& user_info) { + Emit("activity-was-continued", type, user_info); +} +void App::OnUpdateUserActivityState( + const std::string& type, + const base::DictionaryValue& user_info) { + Emit("update-activity-state", type, user_info); +} void App::OnNewWindowForTab() { Emit("new-window-for-tab"); } - #endif void App::OnLogin(LoginHandler* login_handler, @@ -1139,6 +1157,10 @@ void App::BuildPrototype( base::Bind(&Browser::SetUserActivity, browser)) .SetMethod("getCurrentActivityType", base::Bind(&Browser::GetCurrentActivityType, browser)) + .SetMethod("invalidateCurrentActivity", + base::Bind(&Browser::InvalidateCurrentActivity, browser)) + .SetMethod("updateCurrentActivity", + base::Bind(&Browser::UpdateCurrentActivity, browser)) .SetMethod("setAboutPanelOptions", base::Bind(&Browser::SetAboutPanelOptions, browser)) #endif diff --git a/atom/browser/api/atom_api_app.h b/atom/browser/api/atom_api_app.h index 05b304c9bf..f78d8ffc13 100644 --- a/atom/browser/api/atom_api_app.h +++ b/atom/browser/api/atom_api_app.h @@ -113,11 +113,22 @@ class App : public AtomBrowserClient::Delegate, const base::DictionaryValue& request_details) override; void OnAccessibilitySupportChanged() override; #if defined(OS_MACOSX) + void OnWillContinueUserActivity( + bool* prevent_default, + const std::string& type) override; + void OnDidFailToContinueUserActivity( + const std::string& type, + const std::string& error) override; void OnContinueUserActivity( bool* prevent_default, const std::string& type, const base::DictionaryValue& user_info) override; - + void OnUserActivityWasContinued( + const std::string& type, + const base::DictionaryValue& user_info) override; + void OnUpdateUserActivityState( + const std::string& type, + const base::DictionaryValue& user_info) override; void OnNewWindowForTab() override; #endif diff --git a/atom/browser/browser.h b/atom/browser/browser.h index 2204eafdd5..9d18f91e59 100644 --- a/atom/browser/browser.h +++ b/atom/browser/browser.h @@ -119,10 +119,32 @@ class Browser : public WindowListObserver { // Returns the type name of the current user activity. std::string GetCurrentActivityType(); + // Invalidates the current user activity. + void InvalidateCurrentActivity(); + + // Updates the current user activity + void UpdateCurrentActivity(const std::string& type, + const base::DictionaryValue& user_info); + + // Indicates that an user activity is about to be resumed. + bool WillContinueUserActivity(const std::string& type); + + // Indicates a failure to resume a Handoff activity. + void DidFailToContinueUserActivity(const std::string& type, + const std::string& error); + // Resumes an activity via hand-off. bool ContinueUserActivity(const std::string& type, const base::DictionaryValue& user_info); + // Indicates that an activity was continued on another device. + void UserActivityWasContinued(const std::string& type, + const base::DictionaryValue& user_info); + + // Gives an oportunity to update the Handoff payload. + void UpdateUserActivityState(const std::string& type, + const base::DictionaryValue& user_info); + // Bounce the dock icon. enum BounceType { BOUNCE_CRITICAL = 0, diff --git a/atom/browser/browser_mac.mm b/atom/browser/browser_mac.mm index 38a0a003d9..7c189c8650 100644 --- a/atom/browser/browser_mac.mm +++ b/atom/browser/browser_mac.mm @@ -144,6 +144,30 @@ std::string Browser::GetCurrentActivityType() { return base::SysNSStringToUTF8(userActivity.activityType); } +void Browser::InvalidateCurrentActivity() { + [[AtomApplication sharedApplication] invalidateCurrentActivity]; +} + +void Browser::UpdateCurrentActivity(const std::string& type, + const base::DictionaryValue& user_info) { + [[AtomApplication sharedApplication] + updateCurrentActivity:base::SysUTF8ToNSString(type) + withUserInfo:DictionaryValueToNSDictionary(user_info)]; +} + +bool Browser::WillContinueUserActivity(const std::string& type) { + bool prevent_default = false; + for (BrowserObserver& observer : observers_) + observer.OnWillContinueUserActivity(&prevent_default, type); + return prevent_default; +} + +void Browser::DidFailToContinueUserActivity(const std::string& type, + const std::string& error) { + for (BrowserObserver& observer : observers_) + observer.OnDidFailToContinueUserActivity(type, error); +} + bool Browser::ContinueUserActivity(const std::string& type, const base::DictionaryValue& user_info) { bool prevent_default = false; @@ -151,6 +175,18 @@ bool Browser::ContinueUserActivity(const std::string& type, observer.OnContinueUserActivity(&prevent_default, type, user_info); return prevent_default; } + +void Browser::UserActivityWasContinued(const std::string& type, + const base::DictionaryValue& user_info) { + for (BrowserObserver& observer : observers_) + observer.OnUserActivityWasContinued(type, user_info); +} + +void Browser::UpdateUserActivityState(const std::string& type, + const base::DictionaryValue& user_info) { + for (BrowserObserver& observer : observers_) + observer.OnUpdateUserActivityState(type, user_info); +} Browser::LoginItemSettings Browser::GetLoginItemSettings( const LoginItemSettings& options) { diff --git a/atom/browser/browser_observer.h b/atom/browser/browser_observer.h index 3e50fc228b..2944aac131 100644 --- a/atom/browser/browser_observer.h +++ b/atom/browser/browser_observer.h @@ -56,12 +56,27 @@ class BrowserObserver { virtual void OnAccessibilitySupportChanged() {} #if defined(OS_MACOSX) + // The browser wants to report that an user activity will resume. (macOS only) + virtual void OnWillContinueUserActivity( + bool* prevent_default, + const std::string& type) {} + // The browser wants to report an user activity resuming error. (macOS only) + virtual void OnDidFailToContinueUserActivity( + const std::string& type, + const std::string& error) {} // The browser wants to resume a user activity via handoff. (macOS only) virtual void OnContinueUserActivity( bool* prevent_default, const std::string& type, const base::DictionaryValue& user_info) {} - + // The browser wants to notify that an user activity was resumed. (macOS only) + virtual void OnUserActivityWasContinued( + const std::string& type, + const base::DictionaryValue& user_info) {} + // The browser wants to update an user activity payload. (macOS only) + virtual void OnUpdateUserActivityState( + const std::string& type, + const base::DictionaryValue& user_info) {} // User clicked the native macOS new tab button. (macOS only) virtual void OnNewWindowForTab() {} #endif diff --git a/atom/browser/mac/atom_application.h b/atom/browser/mac/atom_application.h index 73baf9e7d9..7e0d552b20 100644 --- a/atom/browser/mac/atom_application.h +++ b/atom/browser/mac/atom_application.h @@ -6,7 +6,8 @@ #import "base/mac/scoped_nsobject.h" @interface AtomApplication : NSApplication { + CrAppControlProtocol, + NSUserActivityDelegate> { @private BOOL handlingSendEvent_; base::scoped_nsobject currentActivity_; @@ -24,5 +25,8 @@ - (void)setCurrentActivity:(NSString*)type withUserInfo:(NSDictionary*)userInfo withWebpageURL:(NSURL*)webpageURL; +- (void)invalidateCurrentActivity; +- (void)updateCurrentActivity:(NSString *)type + withUserInfo:(NSDictionary*)userInfo; @end diff --git a/atom/browser/mac/atom_application.mm b/atom/browser/mac/atom_application.mm index 52975e30c7..a96d49c702 100644 --- a/atom/browser/mac/atom_application.mm +++ b/atom/browser/mac/atom_application.mm @@ -4,6 +4,7 @@ #import "atom/browser/mac/atom_application.h" +#include "atom/browser/mac/dict_util.h" #include "atom/browser/browser.h" #include "base/auto_reset.h" #include "base/strings/sys_string_conversions.h" @@ -35,6 +36,7 @@ [[NSUserActivity alloc] initWithActivityType:type]); [currentActivity_ setUserInfo:userInfo]; [currentActivity_ setWebpageURL:webpageURL]; + [currentActivity_ setDelegate: self]; [currentActivity_ becomeCurrent]; } @@ -42,6 +44,42 @@ return currentActivity_.get(); } +- (void)invalidateCurrentActivity { + if (currentActivity_.get() != NULL) { + [currentActivity_.get() invalidate]; + currentActivity_.reset(); + } +} + +- (void)updateCurrentActivity:(NSString *)type + withUserInfo:(NSDictionary*)userInfo { + if (currentActivity_.get() != NULL) { + [currentActivity_.get() addUserInfoEntriesFromDictionary:userInfo]; + } +} + +- (void)updateUserActivityState:(NSUserActivity *)userActivity { + std::string activity_type(base::SysNSStringToUTF8(userActivity.activityType)); + std::unique_ptr user_info = + atom::NSDictionaryToDictionaryValue(userActivity.userInfo); + + atom::Browser* browser = atom::Browser::Get(); + browser->UpdateUserActivityState(activity_type, *user_info); + + [super updateUserActivityState:userActivity]; +} + +- (void)userActivityWasContinued:(NSUserActivity *)userActivity { + std::string activity_type(base::SysNSStringToUTF8(userActivity.activityType)); + std::unique_ptr user_info = + atom::NSDictionaryToDictionaryValue(userActivity.userInfo); + + atom::Browser* browser = atom::Browser::Get(); + + browser->UserActivityWasContinued(activity_type, *user_info); + [userActivity setNeedsSave:YES]; +} + - (void)awakeFromNib { [[NSAppleEventManager sharedAppleEventManager] setEventHandler:self diff --git a/atom/browser/mac/atom_application_delegate.mm b/atom/browser/mac/atom_application_delegate.mm index ccf51133ce..02eb931a8c 100644 --- a/atom/browser/mac/atom_application_delegate.mm +++ b/atom/browser/mac/atom_application_delegate.mm @@ -118,6 +118,21 @@ continueUserActivity:(NSUserActivity*)userActivity return browser->ContinueUserActivity(activity_type, *user_info) ? YES : NO; } +- (BOOL)application:(NSApplication *)application willContinueUserActivityWithType:(NSString *)userActivityType { + std::string activity_type(base::SysNSStringToUTF8(userActivityType)); + + atom::Browser* browser = atom::Browser::Get(); + return browser->WillContinueUserActivity(activity_type) ? YES : NO; +} + +- (void)application:(NSApplication *)application didFailToContinueUserActivityWithType:(NSString *)userActivityType error:(NSError *)error { + std::string activity_type(base::SysNSStringToUTF8(userActivityType)); + std::string error_message(base::SysNSStringToUTF8([error localizedDescription])); + + atom::Browser* browser = atom::Browser::Get(); + browser->DidFailToContinueUserActivity(activity_type, error_message); +} + - (IBAction)newWindowForTab:(id)sender { atom::Browser::Get()->NewWindowForTab(); } diff --git a/docs/api/app.md b/docs/api/app.md index 01d13b17e1..9170c77151 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -149,6 +149,53 @@ ID as the activity's source app and that supports the activity's type. Supported activity types are specified in the app's `Info.plist` under the `NSUserActivityTypes` key. +### Event: 'will-continue-activity' _macOS_ + +Returns: + +* `event` Event +* `type` String - A string identifying the activity. Maps to + [`NSUserActivity.activityType`][activity-type]. + +Emitted during [Handoff][handoff] before an activity from a different device wants +to be resumed. You should call `event.preventDefault()` if you want to handle +this event. + +### Event: 'continue-activity-error' _macOS_ + +Returns: + +* `event` Event +* `type` String - A string identifying the activity. Maps to + [`NSUserActivity.activityType`][activity-type]. +* `error` String - A string with the error's localized description. + +Emitted during [Handoff][handoff] when an activity from a different device +fails to be resumed. + +### Event: 'activity-was-continued' _macOS_ + +Returns: + +* `event` Event +* `type` String - A string identifying the activity. Maps to + [`NSUserActivity.activityType`][activity-type]. +* `userInfo` Object - Contains app-specific state stored by the activity. + +Emitted during [Handoff][handoff] after an activity from this device was successfully +resumed. + +### Event: 'update-activity-state' _macOS_ + +Returns: + +* `event` Event +* `type` String - A string identifying the activity. Maps to + [`NSUserActivity.activityType`][activity-type]. +* `userInfo` Object - Contains app-specific state stored by the activity. + +Emitted during [Handoff][handoff] when its user info should be updated before resuming. + ### Event: 'new-window-for-tab' _macOS_ Returns: @@ -748,6 +795,25 @@ is eligible for [Handoff][handoff] to another device afterward. Returns `String` - The type of the currently running activity. +### `app.invalidateCurrentActivity()` _macOS_ + +* `type` String - Uniquely identifies the activity. Maps to + [`NSUserActivity.activityType`][activity-type]. +* `userInfo` Object - App-specific state to store for use by another device. +* `webpageURL` String (optional) - The webpage to load in a browser if no suitable app is + installed on the resuming device. The scheme must be `http` or `https`. + +Invalidates de current Handoff user activity. + +### `app.updateCurrentActivity(type, userInfo[, webpageURL])` _macOS_ + +* `type` String - Uniquely identifies the activity. Maps to + [`NSUserActivity.activityType`][activity-type]. +* `userInfo` Object - App-specific state to store for use by another device. + +Updates the current `NSUserActivity` if its type matches `type`, merging the entries from +`userInfo` into its current userInfo dictionary. + ### `app.setAppUserModelId(id)` _Windows_ * `id` String From f6ac00532fc46876626e5cd2d8722bc8b904422e Mon Sep 17 00:00:00 2001 From: Rafael Nobre Date: Thu, 27 Jul 2017 17:24:59 -0300 Subject: [PATCH 2/8] Fix typo and remove extraneous doc entries. --- docs/api/app.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/docs/api/app.md b/docs/api/app.md index 9170c77151..310474f92f 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -799,13 +799,10 @@ Returns `String` - The type of the currently running activity. * `type` String - Uniquely identifies the activity. Maps to [`NSUserActivity.activityType`][activity-type]. -* `userInfo` Object - App-specific state to store for use by another device. -* `webpageURL` String (optional) - The webpage to load in a browser if no suitable app is - installed on the resuming device. The scheme must be `http` or `https`. -Invalidates de current Handoff user activity. +Invalidates the current Handoff user activity. -### `app.updateCurrentActivity(type, userInfo[, webpageURL])` _macOS_ +### `app.updateCurrentActivity(type, userInfo)` _macOS_ * `type` String - Uniquely identifies the activity. Maps to [`NSUserActivity.activityType`][activity-type]. From 76efee675f39fd9fe13e826e6ecbfd5819ebc360 Mon Sep 17 00:00:00 2001 From: Rafael Nobre Date: Wed, 2 Aug 2017 14:41:48 -0300 Subject: [PATCH 3/8] Requests updated user info data before each Handoff operation. --- atom/browser/mac/atom_application.mm | 1 + 1 file changed, 1 insertion(+) diff --git a/atom/browser/mac/atom_application.mm b/atom/browser/mac/atom_application.mm index a96d49c702..578c25a82c 100644 --- a/atom/browser/mac/atom_application.mm +++ b/atom/browser/mac/atom_application.mm @@ -66,6 +66,7 @@ atom::Browser* browser = atom::Browser::Get(); browser->UpdateUserActivityState(activity_type, *user_info); + [userActivity setNeedsSave:YES]; [super updateUserActivityState:userActivity]; } From c23b4a48ec0cd29d178b4eec19a4662b0493b350 Mon Sep 17 00:00:00 2001 From: Rafael Nobre Date: Mon, 7 Aug 2017 16:28:00 -0300 Subject: [PATCH 4/8] Fixes NSUserActivityDelegate that was called on a background thread thus not working. --- atom/browser/mac/atom_application.mm | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/atom/browser/mac/atom_application.mm b/atom/browser/mac/atom_application.mm index 578c25a82c..d825fe4272 100644 --- a/atom/browser/mac/atom_application.mm +++ b/atom/browser/mac/atom_application.mm @@ -38,6 +38,7 @@ [currentActivity_ setWebpageURL:webpageURL]; [currentActivity_ setDelegate: self]; [currentActivity_ becomeCurrent]; + [currentActivity_ setNeedsSave:YES]; } - (NSUserActivity*)getCurrentActivity { @@ -58,26 +59,28 @@ } } -- (void)updateUserActivityState:(NSUserActivity *)userActivity { - std::string activity_type(base::SysNSStringToUTF8(userActivity.activityType)); - std::unique_ptr user_info = - atom::NSDictionaryToDictionaryValue(userActivity.userInfo); +- (void)userActivityWillSave:(NSUserActivity *)userActivity { + dispatch_sync(dispatch_get_main_queue(), ^{ + std::string activity_type(base::SysNSStringToUTF8(userActivity.activityType)); + std::unique_ptr user_info = + atom::NSDictionaryToDictionaryValue(userActivity.userInfo); - atom::Browser* browser = atom::Browser::Get(); - browser->UpdateUserActivityState(activity_type, *user_info); - + atom::Browser* browser = atom::Browser::Get(); + browser->UpdateUserActivityState(activity_type, *user_info); + }); [userActivity setNeedsSave:YES]; - [super updateUserActivityState:userActivity]; } - (void)userActivityWasContinued:(NSUserActivity *)userActivity { - std::string activity_type(base::SysNSStringToUTF8(userActivity.activityType)); - std::unique_ptr user_info = + dispatch_async(dispatch_get_main_queue(), ^{ + std::string activity_type(base::SysNSStringToUTF8(userActivity.activityType)); + std::unique_ptr user_info = atom::NSDictionaryToDictionaryValue(userActivity.userInfo); - atom::Browser* browser = atom::Browser::Get(); + atom::Browser* browser = atom::Browser::Get(); - browser->UserActivityWasContinued(activity_type, *user_info); + browser->UserActivityWasContinued(activity_type, *user_info); + }); [userActivity setNeedsSave:YES]; } From 9483f0fc14138d56ec57106fcb1a626b3114b0be Mon Sep 17 00:00:00 2001 From: Rafael Nobre Date: Wed, 9 Aug 2017 12:09:47 -0300 Subject: [PATCH 5/8] Adds synchronization logic to allow NSUserActivityDelegate to wait Javascript updating the NSUserActiity UserInfo if requested. --- atom/browser/api/atom_api_app.cc | 3 ++- atom/browser/api/atom_api_app.h | 1 + atom/browser/browser.h | 2 +- atom/browser/browser_mac.mm | 6 ++++-- atom/browser/browser_observer.h | 1 + atom/browser/mac/atom_application.h | 2 ++ atom/browser/mac/atom_application.mm | 23 ++++++++++++++++++++++- 7 files changed, 33 insertions(+), 5 deletions(-) diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index e9211aec3d..3fe6063bda 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -606,9 +606,10 @@ void App::OnUserActivityWasContinued( Emit("activity-was-continued", type, user_info); } void App::OnUpdateUserActivityState( + bool* prevent_default, const std::string& type, const base::DictionaryValue& user_info) { - Emit("update-activity-state", type, user_info); + *prevent_default = Emit("update-activity-state", type, user_info); } void App::OnNewWindowForTab() { Emit("new-window-for-tab"); diff --git a/atom/browser/api/atom_api_app.h b/atom/browser/api/atom_api_app.h index f78d8ffc13..1134046017 100644 --- a/atom/browser/api/atom_api_app.h +++ b/atom/browser/api/atom_api_app.h @@ -127,6 +127,7 @@ class App : public AtomBrowserClient::Delegate, const std::string& type, const base::DictionaryValue& user_info) override; void OnUpdateUserActivityState( + bool* prevent_default, const std::string& type, const base::DictionaryValue& user_info) override; void OnNewWindowForTab() override; diff --git a/atom/browser/browser.h b/atom/browser/browser.h index 9d18f91e59..3549d45a8a 100644 --- a/atom/browser/browser.h +++ b/atom/browser/browser.h @@ -142,7 +142,7 @@ class Browser : public WindowListObserver { const base::DictionaryValue& user_info); // Gives an oportunity to update the Handoff payload. - void UpdateUserActivityState(const std::string& type, + bool UpdateUserActivityState(const std::string& type, const base::DictionaryValue& user_info); // Bounce the dock icon. diff --git a/atom/browser/browser_mac.mm b/atom/browser/browser_mac.mm index 7c189c8650..779d488b3c 100644 --- a/atom/browser/browser_mac.mm +++ b/atom/browser/browser_mac.mm @@ -182,10 +182,12 @@ void Browser::UserActivityWasContinued(const std::string& type, observer.OnUserActivityWasContinued(type, user_info); } -void Browser::UpdateUserActivityState(const std::string& type, +bool Browser::UpdateUserActivityState(const std::string& type, const base::DictionaryValue& user_info) { + bool prevent_default = false; for (BrowserObserver& observer : observers_) - observer.OnUpdateUserActivityState(type, user_info); + observer.OnUpdateUserActivityState(&prevent_default, type, user_info); + return prevent_default; } Browser::LoginItemSettings Browser::GetLoginItemSettings( diff --git a/atom/browser/browser_observer.h b/atom/browser/browser_observer.h index 2944aac131..d6b2c9954a 100644 --- a/atom/browser/browser_observer.h +++ b/atom/browser/browser_observer.h @@ -75,6 +75,7 @@ class BrowserObserver { const base::DictionaryValue& user_info) {} // The browser wants to update an user activity payload. (macOS only) virtual void OnUpdateUserActivityState( + bool* prevent_default, const std::string& type, const base::DictionaryValue& user_info) {} // User clicked the native macOS new tab button. (macOS only) diff --git a/atom/browser/mac/atom_application.h b/atom/browser/mac/atom_application.h index 7e0d552b20..5a610bded4 100644 --- a/atom/browser/mac/atom_application.h +++ b/atom/browser/mac/atom_application.h @@ -11,6 +11,8 @@ @private BOOL handlingSendEvent_; base::scoped_nsobject currentActivity_; + NSCondition *handoffLock_; + BOOL updateReceived_; } + (AtomApplication*)sharedApplication; diff --git a/atom/browser/mac/atom_application.mm b/atom/browser/mac/atom_application.mm index d825fe4272..5ab9781965 100644 --- a/atom/browser/mac/atom_application.mm +++ b/atom/browser/mac/atom_application.mm @@ -57,17 +57,36 @@ if (currentActivity_.get() != NULL) { [currentActivity_.get() addUserInfoEntriesFromDictionary:userInfo]; } + + [handoffLock_ lock]; + updateReceived_ = YES; + [handoffLock_ signal]; + [handoffLock_ unlock]; } - (void)userActivityWillSave:(NSUserActivity *)userActivity { + + __block BOOL shouldWait = NO; + dispatch_sync(dispatch_get_main_queue(), ^{ std::string activity_type(base::SysNSStringToUTF8(userActivity.activityType)); std::unique_ptr user_info = atom::NSDictionaryToDictionaryValue(userActivity.userInfo); atom::Browser* browser = atom::Browser::Get(); - browser->UpdateUserActivityState(activity_type, *user_info); + shouldWait = browser->UpdateUserActivityState(activity_type, *user_info) ? YES : NO; }); + + if (shouldWait) { + [handoffLock_ lock]; + updateReceived_ = NO; + while (!updateReceived_) { + BOOL isSignaled = [handoffLock_ waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]]; + if (!isSignaled) { break; } + } + [handoffLock_ unlock]; + } + [userActivity setNeedsSave:YES]; } @@ -90,6 +109,8 @@ andSelector:@selector(handleURLEvent:withReplyEvent:) forEventClass:kInternetEventClass andEventID:kAEGetURL]; + + handoffLock_ = [NSCondition new]; } - (void)handleURLEvent:(NSAppleEventDescriptor*)event From 77a1c5d7fc3bc099918c3953981d99d04f8ccdc3 Mon Sep 17 00:00:00 2001 From: Rafael Nobre Date: Wed, 9 Aug 2017 14:28:43 -0300 Subject: [PATCH 6/8] Avoids deadlock in case NSUserActivityDelegate method start being called from main thread. --- atom/browser/mac/atom_application.mm | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/atom/browser/mac/atom_application.mm b/atom/browser/mac/atom_application.mm index 5ab9781965..25cb95e15e 100644 --- a/atom/browser/mac/atom_application.mm +++ b/atom/browser/mac/atom_application.mm @@ -10,6 +10,14 @@ #include "base/strings/sys_string_conversions.h" #include "content/public/browser/browser_accessibility_state.h" +static inline void dispatch_sync_main(dispatch_block_t block) { + if ([NSThread isMainThread]) { + block(); + } else { + dispatch_sync(dispatch_get_main_queue(), block); + } +} + @implementation AtomApplication + (AtomApplication*)sharedApplication { @@ -68,7 +76,7 @@ __block BOOL shouldWait = NO; - dispatch_sync(dispatch_get_main_queue(), ^{ + dispatch_sync_main(^{ std::string activity_type(base::SysNSStringToUTF8(userActivity.activityType)); std::unique_ptr user_info = atom::NSDictionaryToDictionaryValue(userActivity.userInfo); From 33dd5e26fb2943d79510598ca157c4ccb5aef205 Mon Sep 17 00:00:00 2001 From: Rafael Nobre Date: Wed, 9 Aug 2017 14:29:12 -0300 Subject: [PATCH 7/8] Updates documentation for Handoff related API. --- docs/api/app.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/api/app.md b/docs/api/app.md index 310474f92f..b3f4126c97 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -183,7 +183,7 @@ Returns: * `userInfo` Object - Contains app-specific state stored by the activity. Emitted during [Handoff][handoff] after an activity from this device was successfully -resumed. +resumed on another one. ### Event: 'update-activity-state' _macOS_ @@ -194,7 +194,7 @@ Returns: [`NSUserActivity.activityType`][activity-type]. * `userInfo` Object - Contains app-specific state stored by the activity. -Emitted during [Handoff][handoff] when its user info should be updated before resuming. +Emitted when [Handoff][handoff] is about to be resumed on another device. If you need to update the state to be transferred, you should call `event.preventDefault()` immediatelly, construct a new `userInfo` dictionary and call `app.updateCurrentActiviy()` in a timely manner. Otherwise the operation will fail and `continue-activity-error` will be called. ### Event: 'new-window-for-tab' _macOS_ @@ -800,7 +800,7 @@ Returns `String` - The type of the currently running activity. * `type` String - Uniquely identifies the activity. Maps to [`NSUserActivity.activityType`][activity-type]. -Invalidates the current Handoff user activity. +Invalidates the current [Handoff][handoff] user activity. ### `app.updateCurrentActivity(type, userInfo)` _macOS_ @@ -808,8 +808,8 @@ Invalidates the current Handoff user activity. [`NSUserActivity.activityType`][activity-type]. * `userInfo` Object - App-specific state to store for use by another device. -Updates the current `NSUserActivity` if its type matches `type`, merging the entries from -`userInfo` into its current userInfo dictionary. +Updates the current activity if its type matches `type`, merging the entries from +`userInfo` into its current `userInfo` dictionary. ### `app.setAppUserModelId(id)` _Windows_ From ff023115f5f6a1aa27485e3ccd08c7ca2300c394 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 14 Sep 2017 16:12:34 +0900 Subject: [PATCH 8/8] Style fixes --- atom/browser/api/atom_api_app.cc | 5 +++ atom/browser/browser_mac.mm | 10 +++--- atom/browser/mac/atom_application.h | 4 +-- atom/browser/mac/atom_application.mm | 32 +++++++++---------- atom/browser/mac/atom_application_delegate.mm | 8 ++--- 5 files changed, 32 insertions(+), 27 deletions(-) diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index 3fe6063bda..4f48d7a4fb 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -589,28 +589,33 @@ void App::OnWillContinueUserActivity( const std::string& type) { *prevent_default = Emit("will-continue-activity", type); } + void App::OnDidFailToContinueUserActivity( const std::string& type, const std::string& error) { Emit("continue-activity-error", type, error); } + void App::OnContinueUserActivity( bool* prevent_default, const std::string& type, const base::DictionaryValue& user_info) { *prevent_default = Emit("continue-activity", type, user_info); } + void App::OnUserActivityWasContinued( const std::string& type, const base::DictionaryValue& user_info) { Emit("activity-was-continued", type, user_info); } + void App::OnUpdateUserActivityState( bool* prevent_default, const std::string& type, const base::DictionaryValue& user_info) { *prevent_default = Emit("update-activity-state", type, user_info); } + void App::OnNewWindowForTab() { Emit("new-window-for-tab"); } diff --git a/atom/browser/browser_mac.mm b/atom/browser/browser_mac.mm index 779d488b3c..d187fa6cf9 100644 --- a/atom/browser/browser_mac.mm +++ b/atom/browser/browser_mac.mm @@ -150,18 +150,18 @@ void Browser::InvalidateCurrentActivity() { void Browser::UpdateCurrentActivity(const std::string& type, const base::DictionaryValue& user_info) { - [[AtomApplication sharedApplication] + [[AtomApplication sharedApplication] updateCurrentActivity:base::SysUTF8ToNSString(type) withUserInfo:DictionaryValueToNSDictionary(user_info)]; } bool Browser::WillContinueUserActivity(const std::string& type) { bool prevent_default = false; - for (BrowserObserver& observer : observers_) - observer.OnWillContinueUserActivity(&prevent_default, type); + for (BrowserObserver& observer : observers_) + observer.OnWillContinueUserActivity(&prevent_default, type); return prevent_default; } - + void Browser::DidFailToContinueUserActivity(const std::string& type, const std::string& error) { for (BrowserObserver& observer : observers_) @@ -175,7 +175,7 @@ bool Browser::ContinueUserActivity(const std::string& type, observer.OnContinueUserActivity(&prevent_default, type, user_info); return prevent_default; } - + void Browser::UserActivityWasContinued(const std::string& type, const base::DictionaryValue& user_info) { for (BrowserObserver& observer : observers_) diff --git a/atom/browser/mac/atom_application.h b/atom/browser/mac/atom_application.h index 5a610bded4..45bb011cd7 100644 --- a/atom/browser/mac/atom_application.h +++ b/atom/browser/mac/atom_application.h @@ -11,7 +11,7 @@ @private BOOL handlingSendEvent_; base::scoped_nsobject currentActivity_; - NSCondition *handoffLock_; + NSCondition* handoffLock_; BOOL updateReceived_; } @@ -28,7 +28,7 @@ withUserInfo:(NSDictionary*)userInfo withWebpageURL:(NSURL*)webpageURL; - (void)invalidateCurrentActivity; -- (void)updateCurrentActivity:(NSString *)type +- (void)updateCurrentActivity:(NSString*)type withUserInfo:(NSDictionary*)userInfo; @end diff --git a/atom/browser/mac/atom_application.mm b/atom/browser/mac/atom_application.mm index 25cb95e15e..f934bfeacd 100644 --- a/atom/browser/mac/atom_application.mm +++ b/atom/browser/mac/atom_application.mm @@ -10,14 +10,17 @@ #include "base/strings/sys_string_conversions.h" #include "content/public/browser/browser_accessibility_state.h" -static inline void dispatch_sync_main(dispatch_block_t block) { - if ([NSThread isMainThread]) { - block(); - } else { - dispatch_sync(dispatch_get_main_queue(), block); - } +namespace { + +inline void dispatch_sync_main(dispatch_block_t block) { + if ([NSThread isMainThread]) + block(); + else + dispatch_sync(dispatch_get_main_queue(), block); } +} // namespace + @implementation AtomApplication + (AtomApplication*)sharedApplication { @@ -44,7 +47,7 @@ static inline void dispatch_sync_main(dispatch_block_t block) { [[NSUserActivity alloc] initWithActivityType:type]); [currentActivity_ setUserInfo:userInfo]; [currentActivity_ setWebpageURL:webpageURL]; - [currentActivity_ setDelegate: self]; + [currentActivity_ setDelegate:self]; [currentActivity_ becomeCurrent]; [currentActivity_ setNeedsSave:YES]; } @@ -54,16 +57,16 @@ static inline void dispatch_sync_main(dispatch_block_t block) { } - (void)invalidateCurrentActivity { - if (currentActivity_.get() != NULL) { - [currentActivity_.get() invalidate]; + if (currentActivity_) { + [currentActivity_ invalidate]; currentActivity_.reset(); } } -- (void)updateCurrentActivity:(NSString *)type +- (void)updateCurrentActivity:(NSString*)type withUserInfo:(NSDictionary*)userInfo { - if (currentActivity_.get() != NULL) { - [currentActivity_.get() addUserInfoEntriesFromDictionary:userInfo]; + if (currentActivity_) { + [currentActivity_ addUserInfoEntriesFromDictionary:userInfo]; } [handoffLock_ lock]; @@ -73,9 +76,7 @@ static inline void dispatch_sync_main(dispatch_block_t block) { } - (void)userActivityWillSave:(NSUserActivity *)userActivity { - __block BOOL shouldWait = NO; - dispatch_sync_main(^{ std::string activity_type(base::SysNSStringToUTF8(userActivity.activityType)); std::unique_ptr user_info = @@ -90,7 +91,7 @@ static inline void dispatch_sync_main(dispatch_block_t block) { updateReceived_ = NO; while (!updateReceived_) { BOOL isSignaled = [handoffLock_ waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]]; - if (!isSignaled) { break; } + if (!isSignaled) break; } [handoffLock_ unlock]; } @@ -105,7 +106,6 @@ static inline void dispatch_sync_main(dispatch_block_t block) { atom::NSDictionaryToDictionaryValue(userActivity.userInfo); atom::Browser* browser = atom::Browser::Get(); - browser->UserActivityWasContinued(activity_type, *user_info); }); [userActivity setNeedsSave:YES]; diff --git a/atom/browser/mac/atom_application_delegate.mm b/atom/browser/mac/atom_application_delegate.mm index 02eb931a8c..043448d49a 100644 --- a/atom/browser/mac/atom_application_delegate.mm +++ b/atom/browser/mac/atom_application_delegate.mm @@ -118,17 +118,17 @@ continueUserActivity:(NSUserActivity*)userActivity return browser->ContinueUserActivity(activity_type, *user_info) ? YES : NO; } -- (BOOL)application:(NSApplication *)application willContinueUserActivityWithType:(NSString *)userActivityType { +- (BOOL)application:(NSApplication*)application willContinueUserActivityWithType:(NSString*)userActivityType { std::string activity_type(base::SysNSStringToUTF8(userActivityType)); - + atom::Browser* browser = atom::Browser::Get(); return browser->WillContinueUserActivity(activity_type) ? YES : NO; } -- (void)application:(NSApplication *)application didFailToContinueUserActivityWithType:(NSString *)userActivityType error:(NSError *)error { +- (void)application:(NSApplication*)application didFailToContinueUserActivityWithType:(NSString*)userActivityType error:(NSError*)error { std::string activity_type(base::SysNSStringToUTF8(userActivityType)); std::string error_message(base::SysNSStringToUTF8([error localizedDescription])); - + atom::Browser* browser = atom::Browser::Get(); browser->DidFailToContinueUserActivity(activity_type, error_message); }