diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index 4942752ce0b..e9211aec3dd 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 05b304c9bfd..f78d8ffc13a 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 2204eafdd55..9d18f91e592 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 38a0a003d96..7c189c86500 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 3e50fc228b9..2944aac1313 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 73baf9e7d98..7e0d552b208 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 52975e30c7a..a96d49c7025 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 ccf51133ce2..02eb931a8c2 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 01d13b17e11..9170c771519 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