diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index 304e53a5554..f15fad9d47f 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -20,6 +20,7 @@ #include "atom/common/native_mate_converters/image_converter.h" #include "atom/common/native_mate_converters/net_converter.h" #include "atom/common/native_mate_converters/value_converter.h" +#include "atom/common/native_mate_converters/string_map_converter.h" #include "atom/common/node_includes.h" #include "atom/common/options_switches.h" #include "base/command_line.h" @@ -249,6 +250,12 @@ void App::OnFinishLaunching() { Emit("ready"); } +void App::OnContinueUserActivity(bool* prevent_default, + const std::string& type, + const std::map& user_info) { + *prevent_default = Emit("continue-activity", type, user_info); +} + void App::OnLogin(LoginHandler* login_handler) { v8::Locker locker(isolate()); v8::HandleScope handle_scope(isolate()); @@ -460,6 +467,10 @@ void App::BuildPrototype( #if defined(OS_MACOSX) .SetMethod("hide", base::Bind(&Browser::Hide, browser)) .SetMethod("show", base::Bind(&Browser::Show, browser)) + .SetMethod("setUserActivity", + base::Bind(&Browser::SetUserActivity, browser)) + .SetMethod("getCurrentActivityType", + base::Bind(&Browser::GetCurrentActivityType, browser)) #endif #if defined(OS_WIN) .SetMethod("setUserTasks", diff --git a/atom/browser/api/atom_api_app.h b/atom/browser/api/atom_api_app.h index c99d5df7793..321230d711e 100644 --- a/atom/browser/api/atom_api_app.h +++ b/atom/browser/api/atom_api_app.h @@ -6,6 +6,7 @@ #define ATOM_BROWSER_API_ATOM_API_APP_H_ #include +#include #include "atom/browser/api/event_emitter.h" #include "atom/browser/atom_browser_client.h" @@ -71,6 +72,9 @@ class App : public AtomBrowserClient::Delegate, void OnWillFinishLaunching() override; void OnFinishLaunching() override; void OnLogin(LoginHandler* login_handler) override; + void OnContinueUserActivity(bool* prevent_default, + const std::string& type, + const std::map& user_info) override; // content::ContentBrowserClient: void AllowCertificateError( diff --git a/atom/browser/browser.cc b/atom/browser/browser.cc index 093209ef7c4..ee2a225246b 100644 --- a/atom/browser/browser.cc +++ b/atom/browser/browser.cc @@ -137,6 +137,19 @@ void Browser::Activate(bool has_visible_windows) { OnActivate(has_visible_windows)); } +#if defined(OS_MACOSX) +bool Browser::ContinueUserActivity(const std::string& type, + const std::map& user_info) { + bool prevent_default = false; + FOR_EACH_OBSERVER(BrowserObserver, + observers_, + OnContinueUserActivity(&prevent_default, type, user_info)); + + return prevent_default; +} +#endif + void Browser::WillFinishLaunching() { FOR_EACH_OBSERVER(BrowserObserver, observers_, OnWillFinishLaunching()); } diff --git a/atom/browser/browser.h b/atom/browser/browser.h index 8329b14bc2c..5c3dcaf5122 100644 --- a/atom/browser/browser.h +++ b/atom/browser/browser.h @@ -7,6 +7,7 @@ #include #include +#include #include "base/macros.h" #include "base/compiler_specific.h" @@ -92,6 +93,17 @@ class Browser : public WindowListObserver { // Show the application. void Show(); + // Creates an activity and sets it as the one currently in use. + void SetUserActivity(const std::string& type, + const std::map& user_info); + + // Returns the type name of the current user activity. + std::string GetCurrentActivityType(); + + // Resumes an activity via hand-off. + bool ContinueUserActivity(const std::string& type, + const std::map& 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 c10369a2e7a..42d10debd09 100644 --- a/atom/browser/browser_mac.mm +++ b/atom/browser/browser_mac.mm @@ -69,7 +69,7 @@ bool Browser::IsDefaultProtocolClient(const std::string& protocol) { return false; NSString* protocol_ns = [NSString stringWithUTF8String:protocol.c_str()]; - + CFStringRef bundle = LSCopyDefaultHandlerForURLScheme(base::mac::NSToCFCast(protocol_ns)); NSString* bundleId = static_cast( @@ -77,7 +77,7 @@ bool Browser::IsDefaultProtocolClient(const std::string& protocol) { if (!bundleId) return false; - // Ensure the comparison is case-insensitive + // Ensure the comparison is case-insensitive // as LS does not persist the case of the bundle id. NSComparisonResult result = [bundleId caseInsensitiveCompare:identifier]; @@ -87,6 +87,30 @@ bool Browser::IsDefaultProtocolClient(const std::string& protocol) { void Browser::SetAppUserModelID(const base::string16& name) { } +void Browser::SetUserActivity(const std::string& type, const std::map& user_info) { + NSString* type_ns = [NSString stringWithUTF8String:type.c_str()]; + NSUserActivity* user_activity = [[NSUserActivity alloc] initWithActivityType:type_ns]; + + base::scoped_nsobject user_info_args([[NSMutableDictionary alloc] init]); + for (auto const &pair : user_info) { + NSString* value_ns = [NSString stringWithUTF8String:pair.second.c_str()]; + NSString* key_ns = [NSString stringWithUTF8String:pair.first.c_str()]; + + [user_info_args.get() setObject:value_ns + forKey:key_ns]; + } + + user_activity.userInfo = user_info_args.get(); + [user_activity becomeCurrent]; + + [[AtomApplication sharedApplication] setCurrentActivity:user_activity]; +} + +std::string Browser::GetCurrentActivityType() { + NSUserActivity* user_activity = [[AtomApplication sharedApplication] getCurrentActivity]; + return base::SysNSStringToUTF8(user_activity.activityType); +} + std::string Browser::GetExecutableFileVersion() const { return brightray::GetApplicationVersion(); } diff --git a/atom/browser/browser_observer.h b/atom/browser/browser_observer.h index f6d76bc13fb..4ad001370c0 100644 --- a/atom/browser/browser_observer.h +++ b/atom/browser/browser_observer.h @@ -6,6 +6,7 @@ #define ATOM_BROWSER_BROWSER_OBSERVER_H_ #include +#include namespace atom { @@ -45,6 +46,11 @@ class BrowserObserver { // The browser requests HTTP login. virtual void OnLogin(LoginHandler* login_handler) {} + // The browser wants to resume a user activity via handoff. (OS X only) + virtual void OnContinueUserActivity(bool* prevent_default, + const std::string& type, + const std::map& user_info) {} + protected: virtual ~BrowserObserver() {} }; diff --git a/atom/browser/mac/atom_application.h b/atom/browser/mac/atom_application.h index 9fafb054b09..37074cae7bf 100644 --- a/atom/browser/mac/atom_application.h +++ b/atom/browser/mac/atom_application.h @@ -3,11 +3,13 @@ // found in the LICENSE file. #import "base/mac/scoped_sending_event.h" +#import "base/mac/scoped_nsobject.h" @interface AtomApplication : NSApplication { @private BOOL handlingSendEvent_; + base::scoped_nsobject currentActivity_; } + (AtomApplication*)sharedApplication; @@ -18,4 +20,8 @@ // CrAppControlProtocol: - (void)setHandlingSendEvent:(BOOL)handlingSendEvent; +- (NSUserActivity*)getCurrentActivity; + +- (void)setCurrentActivity:(NSUserActivity*)userActivity; + @end diff --git a/atom/browser/mac/atom_application.mm b/atom/browser/mac/atom_application.mm index cc9c6accc83..18115113047 100644 --- a/atom/browser/mac/atom_application.mm +++ b/atom/browser/mac/atom_application.mm @@ -28,6 +28,14 @@ handlingSendEvent_ = handlingSendEvent; } +- (void)setCurrentActivity:(NSUserActivity*)userActivity { + currentActivity_ = base::scoped_nsobject(userActivity); +} + +- (NSUserActivity*)getCurrentActivity { + return currentActivity_.get(); +} + - (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 7662162ab61..0904679c49a 100644 --- a/atom/browser/mac/atom_application_delegate.mm +++ b/atom/browser/mac/atom_application_delegate.mm @@ -59,4 +59,23 @@ return flag; } +- (BOOL)application:(NSApplication *)sender +continueUserActivity:(NSUserActivity *)userActivity + restorationHandler:(void (^)(NSArray *restorableObjects))restorationHandler { + std::string activity_type(base::SysNSStringToUTF8(userActivity.activityType)); + + std::map user_info; + base::scoped_nsobject keys([userActivity.userInfo allKeys]); + + for (NSString* key in keys.get()) { + NSString* value = [userActivity.userInfo objectForKey:key]; + std::string key_str(base::SysNSStringToUTF8(key)); + std::string value_str(base::SysNSStringToUTF8(value)); + user_info[key_str] = value_str; + } + + atom::Browser* browser = atom::Browser::Get(); + return browser->ContinueUserActivity(activity_type, user_info) ? YES : NO; +} + @end diff --git a/atom/common/api/atom_api_crash_reporter.cc b/atom/common/api/atom_api_crash_reporter.cc index e1932ad7f5f..5db1461f88c 100644 --- a/atom/common/api/atom_api_crash_reporter.cc +++ b/atom/common/api/atom_api_crash_reporter.cc @@ -6,6 +6,7 @@ #include #include "atom/common/crash_reporter/crash_reporter.h" +#include "atom/common/native_mate_converters/string_map_converter.h" #include "base/bind.h" #include "native_mate/dictionary.h" @@ -15,24 +16,6 @@ using crash_reporter::CrashReporter; namespace mate { -template<> -struct Converter > { - static bool FromV8(v8::Isolate* isolate, - v8::Local val, - std::map* out) { - if (!val->IsObject()) - return false; - - v8::Local dict = val->ToObject(); - v8::Local keys = dict->GetOwnPropertyNames(); - for (uint32_t i = 0; i < keys->Length(); ++i) { - v8::Local key = keys->Get(i); - (*out)[V8ToString(key)] = V8ToString(dict->Get(key)); - } - return true; - } -}; - template<> struct Converter { static v8::Local ToV8(v8::Isolate* isolate, diff --git a/atom/common/native_mate_converters/string_map_converter.cc b/atom/common/native_mate_converters/string_map_converter.cc new file mode 100644 index 00000000000..475b052ba65 --- /dev/null +++ b/atom/common/native_mate_converters/string_map_converter.cc @@ -0,0 +1,36 @@ +// Copyright (c) 2014 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/common/native_mate_converters/string_map_converter.h" + +namespace mate { + +bool Converter>::FromV8(v8::Isolate* isolate, + v8::Local val, + std::map* out) { + if (!val->IsObject()) + return false; + + v8::Local dict = val->ToObject(); + v8::Local keys = dict->GetOwnPropertyNames(); + for (uint32_t i = 0; i < keys->Length(); ++i) { + v8::Local key = keys->Get(i); + (*out)[V8ToString(key)] = V8ToString(dict->Get(key)); + } + return true; +} + +v8::Local Converter>::ToV8( + v8::Isolate* isolate, + const std::map& in) { + mate::Dictionary dict(isolate, v8::Object::New(isolate)); + + for (auto const &pair : in) { + dict.Set(pair.first, pair.second); + } + + return dict.GetHandle(); +} + +} // namespace mate diff --git a/atom/common/native_mate_converters/string_map_converter.h b/atom/common/native_mate_converters/string_map_converter.h new file mode 100644 index 00000000000..54e4297714c --- /dev/null +++ b/atom/common/native_mate_converters/string_map_converter.h @@ -0,0 +1,28 @@ +// Copyright (c) 2014 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_COMMON_NATIVE_MATE_CONVERTERS_STRING_MAP_CONVERTER_H_ +#define ATOM_COMMON_NATIVE_MATE_CONVERTERS_STRING_MAP_CONVERTER_H_ + +#include +#include + +#include "native_mate/converter.h" +#include "native_mate/dictionary.h" + +namespace mate { + +template<> +struct Converter> { + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + std::map* out); + + static v8::Local ToV8(v8::Isolate* isolate, + const std::map& in); +}; + +} // namespace mate + +#endif // ATOM_COMMON_NATIVE_MATE_CONVERTERS_STRING_MAP_CONVERTER_H_ diff --git a/docs/api/app.md b/docs/api/app.md index 1f682933d23..3ade1da034f 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -108,8 +108,26 @@ Returns: * `event` Event * `hasVisibleWindows` Boolean -Emitted when the application is activated, which usually happens when clicks on -the applications's dock icon. +Emitted when the application is activated, which usually happens when the user clicks on +the application's dock icon. + +### Event: 'continue-activity' _OS X_ + +Returns: + +* `event` Event +* `type` String - A string identifying the event. Maps to [`NSUserActivity.activityType`](https://developer.apple.com/library/ios/documentation/Foundation/Reference/NSUserActivity_Class/index.html#//apple_ref/occ/instp/NSUserActivity/activityType). +* `userInfo` Object - Contains app-specific state stored by the activity on +another device. Currently only string data is supported. + +Emitted during [handoff](https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/Handoff/HandoffFundamentals/HandoffFundamentals.html) when an activity from a different device wants to be +resumed. You should call `event.preventDefault()` if you want to handle this +event. + +A user activity can be continued only in an app that has the same developer +Team 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: 'browser-window-blur' @@ -386,12 +404,12 @@ default protocol handler. ### `app.isDefaultProtocolClient(protocol)` _OS X_ _Windows_ -* `protocol` String - The name of your protocol, without `://`. +* `protocol` String - The name of your protocol, without `://`. This method checks if the current executable is the default handler for a protocol -(aka URI scheme). If so, it will return true. Otherwise, it will return false. +(aka URI scheme). If so, it will return true. Otherwise, it will return false. -**Note:** On OS X, you can use this method to check if the app has been registered as the default protocol handler for a protocol. You can also verify this by checking `~/Library/Preferences/com.apple.LaunchServices.plist` on the OS X machine. +**Note:** On OS X, you can use this method to check if the app has been registered as the default protocol handler for a protocol. You can also verify this by checking `~/Library/Preferences/com.apple.LaunchServices.plist` on the OS X machine. Please refer to [Apple's documentation][LSCopyDefaultHandlerForURLScheme] for details. The API uses the Windows Registry and LSCopyDefaultHandlerForURLScheme internally. @@ -482,6 +500,20 @@ app.on('ready', function() { }); ``` +### `app.setUserActivity(type, userInfo)` _OS X_ + +* `type` String - Uniquely identifies the activity. It's recommended to use a +reverse-DNS string. +* `userInfo` Object - App-specific state to store for use by another device. +Currently only string data is supported. + +Creates an `NSUserActivity` and sets it as the current activity. The activity +is eligible for [handoff](https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/Handoff/HandoffFundamentals/HandoffFundamentals.html) to another device afterward. + +### `app.getCurrentActivityType()` _OS X_ + +Returns the type of the currently running activity. + ### `app.setAppUserModelId(id)` _Windows_ * `id` String @@ -568,5 +600,5 @@ Sets the `image` associated with this dock icon. [tasks]:http://msdn.microsoft.com/en-us/library/windows/desktop/dd378460(v=vs.85).aspx#tasks [app-user-model-id]: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx [CFBundleURLTypes]: https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/TP40009249-102207-TPXREF115 -[LSCopyDefaultHandlerForURLScheme]: +[LSCopyDefaultHandlerForURLScheme]: https://developer.apple.com/library/mac/documentation/Carbon/Reference/LaunchServicesReference/#//apple_ref/c/func/LSCopyDefaultHandlerForURLScheme diff --git a/filenames.gypi b/filenames.gypi index 3ad66e7fafc..f0e3942a5e2 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -361,6 +361,8 @@ 'atom/common/native_mate_converters/image_converter.h', 'atom/common/native_mate_converters/net_converter.cc', 'atom/common/native_mate_converters/net_converter.h', + 'atom/common/native_mate_converters/string_map_converter.cc', + 'atom/common/native_mate_converters/string_map_converter.h', 'atom/common/native_mate_converters/string16_converter.h', 'atom/common/native_mate_converters/ui_base_types_converter.h', 'atom/common/native_mate_converters/v8_value_converter.cc', diff --git a/spec/api-app-spec.js b/spec/api-app-spec.js index 1c20ef8e452..30df5361041 100644 --- a/spec/api-app-spec.js +++ b/spec/api-app-spec.js @@ -7,6 +7,7 @@ const remote = require('electron').remote const app = remote.require('electron').app const BrowserWindow = remote.require('electron').BrowserWindow +const isCI = remote.getGlobal('isCi') describe('electron module', function () { it('allows old style require by default', function () { @@ -89,6 +90,17 @@ describe('app module', function () { }) }) + describe('app.setUserActivity(type, userInfo)', function () { + if (isCI && process.platform !== 'darwin') { + return + } + + it('sets the current activity', function () { + app.setUserActivity('com.electron.testActivity', {testData: '123'}); + assert.equal(app.getCurrentActivityType(), 'com.electron.testActivity'); + }) + }) + describe('app.importCertificate', function () { if (process.platform !== 'linux') return