diff options
author | erikchen@chromium.org <erikchen@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-07-31 11:35:51 +0000 |
---|---|---|
committer | erikchen@chromium.org <erikchen@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-07-31 11:35:51 +0000 |
commit | cc405e47896d2b031f8f42a93337538e162ab1e4 (patch) | |
tree | 3a77174e1cb82ded7d8b10887a9ed5ac99a01a3f /content | |
parent | 97b9212540414b501335f3a0adc7284db1eb0050 (diff) | |
download | chromium_src-cc405e47896d2b031f8f42a93337538e162ab1e4.zip chromium_src-cc405e47896d2b031f8f42a93337538e162ab1e4.tar.gz chromium_src-cc405e47896d2b031f8f42a93337538e162ab1e4.tar.bz2 |
mac: Load the system hotkeys after launch. (reland)
---------------Reland CL Description---------------------------
System mouse hotkeys were incorrectly being parsed as system keyboard hotkeys.
Other minor changes include:
- Expanded unit tests to include a sparsely populated symbolichotkeys.plist
from a real machine.
- Use correct types for the key_code and modifiers of the event.
- An event must have at least one of ctr/alt/cmd down to be considered a
hotkey.
- Use the NSDeviceIndependentModifierFlagsMask mask to prune out
device-dependent modifier flags, including event coalescing information.
---------------Original CL Description---------------------------
Original CL: https://codereview.chromium.org/370293004/
Shortly after launch, the system hotkeys are loaded and parsed. If a hotkey is
reserved by the system, it is not passed to the renderer. This allows system
hotkeys like (cmd + `) to work even if a flash plugin is selected.
Add a histogram to ensure that the system hotkey plist is being correctly
loaded and parsed.
BUG=383558, 395187
TEST=Open 2 Chrome windows. Navigate one to www.twitch.tv. The site should
include a flash plugin that automatically starts playing a video. Select the
flash plugin. The hotkey combination (cmd + `) should switch between the open
windows. The hotkey combination (cmd + L) should have no effect (it is a Chrome
hotkey, not a browser hotkey).
Review URL: https://codereview.chromium.org/408973002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@286737 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content')
-rw-r--r-- | content/browser/browser_main_loop.cc | 2 | ||||
-rw-r--r-- | content/browser/cocoa/system_hotkey_helper_mac.h | 56 | ||||
-rw-r--r-- | content/browser/cocoa/system_hotkey_helper_mac.mm | 77 | ||||
-rw-r--r-- | content/browser/cocoa/system_hotkey_map.h | 66 | ||||
-rw-r--r-- | content/browser/cocoa/system_hotkey_map.mm | 165 | ||||
-rw-r--r-- | content/browser/cocoa/system_hotkey_map_unittest.mm | 196 | ||||
-rw-r--r-- | content/browser/renderer_host/render_widget_host_view_mac.mm | 30 | ||||
-rw-r--r-- | content/content_browser.gypi | 4 | ||||
-rw-r--r-- | content/content_tests.gypi | 1 | ||||
-rw-r--r-- | content/test/data/mac/mac_system_hotkeys.plist | 866 | ||||
-rw-r--r-- | content/test/data/mac/mac_system_hotkeys_sparse.plist | 133 |
11 files changed, 1596 insertions, 0 deletions
diff --git a/content/browser/browser_main_loop.cc b/content/browser/browser_main_loop.cc index 6b5cdd4..ce7e224 100644 --- a/content/browser/browser_main_loop.cc +++ b/content/browser/browser_main_loop.cc @@ -86,6 +86,7 @@ #if defined(OS_MACOSX) && !defined(OS_IOS) #include "content/browser/bootstrap_sandbox_mac.h" +#include "content/browser/cocoa/system_hotkey_helper_mac.h" #include "content/browser/theme_helper_mac.h" #endif @@ -1048,6 +1049,7 @@ int BrowserMainLoop::BrowserThreadsStarted() { #if defined(OS_MACOSX) ThemeHelperMac::GetInstance(); + SystemHotkeyHelperMac::GetInstance()->DeferredLoadSystemHotkeys(); if (ShouldEnableBootstrapSandbox()) { TRACE_EVENT0("startup", "BrowserMainLoop::BrowserThreadsStarted:BootstrapSandbox"); diff --git a/content/browser/cocoa/system_hotkey_helper_mac.h b/content/browser/cocoa/system_hotkey_helper_mac.h new file mode 100644 index 0000000..63d9e51 --- /dev/null +++ b/content/browser/cocoa/system_hotkey_helper_mac.h @@ -0,0 +1,56 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_COCOA_SYSTEM_HOTKEY_HELPER_MAC_H_ +#define CONTENT_BROWSER_COCOA_SYSTEM_HOTKEY_HELPER_MAC_H_ + +#include "base/memory/singleton.h" +#include "base/memory/weak_ptr.h" + +#ifdef __OBJC__ +@class NSDictionary; +#else +class NSDictionary; +#endif + +namespace content { + +class SystemHotkeyMap; + +// This singleton holds a global mapping of hotkeys reserved by OSX. +class SystemHotkeyHelperMac { + public: + // Return pointer to the singleton instance for the current process. + static SystemHotkeyHelperMac* GetInstance(); + + // Loads the system hot keys after a brief delay, to reduce file system access + // immediately after launch. + void DeferredLoadSystemHotkeys(); + + // Guaranteed to not be NULL. + SystemHotkeyMap* map() { return map_.get(); } + + private: + friend struct DefaultSingletonTraits<SystemHotkeyHelperMac>; + + SystemHotkeyHelperMac(); + ~SystemHotkeyHelperMac(); + + // Must be called from the FILE thread. Loads the file containing the system + // hotkeys into a NSDictionary* object, and passes the result to FileDidLoad + // on the UI thread. + void LoadSystemHotkeys(); + + // Must be called from the UI thread. This takes ownership of |dictionary|. + // Parses the system hotkeys from the plist stored in |dictionary|. + void FileDidLoad(NSDictionary* dictionary); + + scoped_ptr<SystemHotkeyMap> map_; + + DISALLOW_COPY_AND_ASSIGN(SystemHotkeyHelperMac); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_COCOA_SYSTEM_HOTKEY_HELPER_MAC_H_ diff --git a/content/browser/cocoa/system_hotkey_helper_mac.mm b/content/browser/cocoa/system_hotkey_helper_mac.mm new file mode 100644 index 0000000..68c5282 --- /dev/null +++ b/content/browser/cocoa/system_hotkey_helper_mac.mm @@ -0,0 +1,77 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/cocoa/system_hotkey_helper_mac.h" + +#include "base/bind.h" +#include "base/files/file_path.h" +#include "base/mac/foundation_util.h" +#include "base/message_loop/message_loop.h" +#include "base/metrics/histogram.h" +#include "content/browser/cocoa/system_hotkey_map.h" +#include "content/public/browser/browser_thread.h" + +namespace { + +NSString* kSystemHotkeyPlistExtension = + @"/Preferences/com.apple.symbolichotkeys.plist"; + +// Amount of time to delay loading the hotkeys in seconds. +const int kLoadHotkeysDelaySeconds = 10; + +} // namespace + +namespace content { + +// static +SystemHotkeyHelperMac* SystemHotkeyHelperMac::GetInstance() { + return Singleton<SystemHotkeyHelperMac>::get(); +} + +void SystemHotkeyHelperMac::DeferredLoadSystemHotkeys() { + BrowserThread::PostDelayedTask( + BrowserThread::FILE, + FROM_HERE, + base::Bind(&SystemHotkeyHelperMac::LoadSystemHotkeys, + base::Unretained(this)), + base::TimeDelta::FromSeconds(kLoadHotkeysDelaySeconds)); +} + +SystemHotkeyHelperMac::SystemHotkeyHelperMac() : map_(new SystemHotkeyMap) { +} + +SystemHotkeyHelperMac::~SystemHotkeyHelperMac() { +} + +void SystemHotkeyHelperMac::LoadSystemHotkeys() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + + std::string library_path(base::mac::GetUserLibraryPath().value()); + NSString* expanded_file_path = + [NSString stringWithFormat:@"%s%@", + library_path.c_str(), + kSystemHotkeyPlistExtension]; + + // Loads the file into memory. + NSData* data = [NSData dataWithContentsOfFile:expanded_file_path]; + // Intentionally create the object with +1 retain count, as FileDidLoad + // will destroy the object. + NSDictionary* dictionary = [SystemHotkeyMap::DictionaryFromData(data) retain]; + + BrowserThread::PostTask(BrowserThread::UI, + FROM_HERE, + base::Bind(&SystemHotkeyHelperMac::FileDidLoad, + base::Unretained(this), + dictionary)); +} + +void SystemHotkeyHelperMac::FileDidLoad(NSDictionary* dictionary) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + bool success = map()->ParseDictionary(dictionary); + UMA_HISTOGRAM_BOOLEAN("OSX.SystemHotkeyMap.LoadSuccess", success); + [dictionary release]; +} + +} // namespace content diff --git a/content/browser/cocoa/system_hotkey_map.h b/content/browser/cocoa/system_hotkey_map.h new file mode 100644 index 0000000..1909aa5 --- /dev/null +++ b/content/browser/cocoa/system_hotkey_map.h @@ -0,0 +1,66 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_COCOA_SYSTEM_HOTKEY_MAP_H_ +#define CONTENT_BROWSER_COCOA_SYSTEM_HOTKEY_MAP_H_ + +#import <Cocoa/Cocoa.h> +#include <vector> + +#include "base/gtest_prod_util.h" +#include "base/macros.h" +#include "content/common/content_export.h" + +namespace content { + +struct SystemHotkey; + +// Maintains a listing of all OSX system hotkeys. e.g. (cmd + `) These hotkeys +// should have higher priority than web content, so NSEvents that correspond to +// a system hotkey should not be passed to the renderer. +class CONTENT_EXPORT SystemHotkeyMap { + public: + SystemHotkeyMap(); + ~SystemHotkeyMap(); + + // Converts the plist stored in |data| into an NSDictionary. Returns nil on + // error. + static NSDictionary* DictionaryFromData(NSData* data); + + // Parses the property list data commonly stored at + // ~/Library/Preferences/com.apple.symbolichotkeys.plist + // Returns false on encountering an irrecoverable error. + // Can be called multiple times. Only the results from the most recent + // invocation are stored. + bool ParseDictionary(NSDictionary* dictionary); + + // Whether the event corresponds to a hotkey that has been reserved by the + // system. + bool IsEventReserved(NSEvent* event) const; + + private: + FRIEND_TEST_ALL_PREFIXES(SystemHotkeyMapTest, Parse); + FRIEND_TEST_ALL_PREFIXES(SystemHotkeyMapTest, ParseCustomEntries); + FRIEND_TEST_ALL_PREFIXES(SystemHotkeyMapTest, ParseMouse); + + // Whether the hotkey has been reserved by the user. + bool IsHotkeyReserved(unsigned short key_code, NSUInteger modifiers) const; + + // Create at least one record of a hotkey that is reserved by the user. + // Certain system hotkeys automatically reserve multiple key combinations. + void ReserveHotkey(unsigned short key_code, + NSUInteger modifiers, + NSString* system_effect); + + // Create a record of a hotkey that is reserved by the user. + void ReserveHotkey(unsigned short key_code, NSUInteger modifiers); + + std::vector<SystemHotkey> system_hotkeys_; + + DISALLOW_COPY_AND_ASSIGN(SystemHotkeyMap); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_COCOA_SYSTEM_HOTKEY_MAP_H_ diff --git a/content/browser/cocoa/system_hotkey_map.mm b/content/browser/cocoa/system_hotkey_map.mm new file mode 100644 index 0000000..da04fa8 --- /dev/null +++ b/content/browser/cocoa/system_hotkey_map.mm @@ -0,0 +1,165 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "content/browser/cocoa/system_hotkey_map.h" + +#pragma mark - NSDictionary Helper Functions + +namespace { + +// All 4 following functions return nil if the object doesn't exist, or isn't of +// the right class. +id ObjectForKey(NSDictionary* dict, NSString* key, Class aClass) { + id object = [dict objectForKey:key]; + if (![object isKindOfClass:aClass]) + return nil; + return object; +} + +NSDictionary* DictionaryForKey(NSDictionary* dict, NSString* key) { + return ObjectForKey(dict, key, [NSDictionary class]); +} + +NSArray* ArrayForKey(NSDictionary* dict, NSString* key) { + return ObjectForKey(dict, key, [NSArray class]); +} + +NSNumber* NumberForKey(NSDictionary* dict, NSString* key) { + return ObjectForKey(dict, key, [NSNumber class]); +} + +NSString* StringForKey(NSDictionary* dict, NSString* key) { + return ObjectForKey(dict, key, [NSString class]); +} + +} // namespace + +#pragma mark - SystemHotkey + +namespace content { + +struct SystemHotkey { + unsigned short key_code; + NSUInteger modifiers; +}; + +#pragma mark - SystemHotkeyMap + +SystemHotkeyMap::SystemHotkeyMap() { +} +SystemHotkeyMap::~SystemHotkeyMap() { +} + +NSDictionary* SystemHotkeyMap::DictionaryFromData(NSData* data) { + if (!data) + return nil; + + NSError* error = nil; + NSPropertyListFormat format; + NSDictionary* dictionary = + [NSPropertyListSerialization propertyListWithData:data + options:0 + format:&format + error:&error]; + + if (![dictionary isKindOfClass:[NSDictionary class]]) + return nil; + + return dictionary; +} + +bool SystemHotkeyMap::ParseDictionary(NSDictionary* dictionary) { + system_hotkeys_.clear(); + + if (!dictionary) + return false; + + NSDictionary* hotkey_dictionaries = + DictionaryForKey(dictionary, @"AppleSymbolicHotKeys"); + if (!hotkey_dictionaries) + return false; + + for (NSString* hotkey_system_effect in [hotkey_dictionaries allKeys]) { + if (![hotkey_system_effect isKindOfClass:[NSString class]]) + continue; + + NSDictionary* hotkey_dictionary = + [hotkey_dictionaries objectForKey:hotkey_system_effect]; + if (![hotkey_dictionary isKindOfClass:[NSDictionary class]]) + continue; + + NSNumber* enabled = NumberForKey(hotkey_dictionary, @"enabled"); + if (!enabled || enabled.boolValue == NO) + continue; + + NSDictionary* value = DictionaryForKey(hotkey_dictionary, @"value"); + if (!value) + continue; + + NSString* type = StringForKey(value, @"type"); + if (!type || ![type isEqualToString:@"standard"]) + continue; + + NSArray* parameters = ArrayForKey(value, @"parameters"); + if (!parameters || [parameters count] != 3) + continue; + + NSNumber* key_code = [parameters objectAtIndex:1]; + if (![key_code isKindOfClass:[NSNumber class]]) + continue; + + NSNumber* modifiers = [parameters objectAtIndex:2]; + if (![modifiers isKindOfClass:[NSNumber class]]) + continue; + + ReserveHotkey(key_code.unsignedShortValue, + modifiers.unsignedIntegerValue, + hotkey_system_effect); + } + + return true; +} + +bool SystemHotkeyMap::IsEventReserved(NSEvent* event) const { + return IsHotkeyReserved(event.keyCode, event.modifierFlags); +} + +bool SystemHotkeyMap::IsHotkeyReserved(unsigned short key_code, + NSUInteger modifiers) const { + modifiers &= NSDeviceIndependentModifierFlagsMask; + std::vector<SystemHotkey>::const_iterator it; + for (it = system_hotkeys_.begin(); it != system_hotkeys_.end(); ++it) { + if (it->key_code == key_code && it->modifiers == modifiers) + return true; + } + return false; +} + +void SystemHotkeyMap::ReserveHotkey(unsigned short key_code, + NSUInteger modifiers, + NSString* system_effect) { + ReserveHotkey(key_code, modifiers); + + // If a hotkey exists for toggling through the windows of an application, then + // adding shift to that hotkey toggles through the windows backwards. + if ([system_effect isEqualToString:@"27"]) + ReserveHotkey(key_code, modifiers | NSShiftKeyMask); +} + +void SystemHotkeyMap::ReserveHotkey(unsigned short key_code, + NSUInteger modifiers) { + // Hotkeys require at least one of control, command, or alternate keys to be + // down. + NSUInteger required_modifiers = + NSControlKeyMask | NSCommandKeyMask | NSAlternateKeyMask; + if ((modifiers & required_modifiers) == 0) + return; + + SystemHotkey hotkey; + hotkey.key_code = key_code; + hotkey.modifiers = modifiers; + system_hotkeys_.push_back(hotkey); +} + +} // namespace content diff --git a/content/browser/cocoa/system_hotkey_map_unittest.mm b/content/browser/cocoa/system_hotkey_map_unittest.mm new file mode 100644 index 0000000..5f088fb --- /dev/null +++ b/content/browser/cocoa/system_hotkey_map_unittest.mm @@ -0,0 +1,196 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <gtest/gtest.h> + +#import <Carbon/Carbon.h> +#import <Cocoa/Cocoa.h> + +#include "base/files/file_path.h" +#include "base/mac/scoped_nsobject.h" +#include "base/path_service.h" +#import "content/browser/cocoa/system_hotkey_map.h" +#include "content/public/common/content_paths.h" + +namespace content { + +class SystemHotkeyMapTest : public ::testing::Test { + protected: + SystemHotkeyMapTest() {} + + NSData* DataFromTestFile(const char* file) { + base::FilePath test_data_dir; + bool result = PathService::Get(DIR_TEST_DATA, &test_data_dir); + if (!result) + return nil; + + base::FilePath test_path = test_data_dir.AppendASCII(file); + std::string test_path_string = test_path.AsUTF8Unsafe(); + NSString* file_path = + [NSString stringWithUTF8String:test_path_string.c_str()]; + return [NSData dataWithContentsOfFile:file_path]; + } + + void AddEntryToDictionary(BOOL enabled, + unsigned short key_code, + NSUInteger modifiers) { + NSMutableArray* parameters = [NSMutableArray array]; + // The first parameter is unused. + [parameters addObject:[NSNumber numberWithInt:65535]]; + [parameters addObject:[NSNumber numberWithUnsignedShort:key_code]]; + [parameters addObject:[NSNumber numberWithUnsignedInteger:modifiers]]; + + NSMutableDictionary* value_dictionary = [NSMutableDictionary dictionary]; + [value_dictionary setObject:parameters forKey:@"parameters"]; + [value_dictionary setObject:@"standard" forKey:@"type"]; + + NSMutableDictionary* outer_dictionary = [NSMutableDictionary dictionary]; + [outer_dictionary setObject:value_dictionary forKey:@"value"]; + + NSNumber* enabled_number = [NSNumber numberWithBool:enabled]; + [outer_dictionary setObject:enabled_number forKey:@"enabled"]; + + NSString* key = [NSString stringWithFormat:@"%d", count_]; + [system_hotkey_inner_dictionary_ setObject:outer_dictionary forKey:key]; + ++count_; + } + + virtual void SetUp() OVERRIDE { + system_hotkey_dictionary_.reset([[NSMutableDictionary alloc] init]); + system_hotkey_inner_dictionary_.reset([[NSMutableDictionary alloc] init]); + [system_hotkey_dictionary_ setObject:system_hotkey_inner_dictionary_ + forKey:@"AppleSymbolicHotKeys"]; + count_ = 100; + } + + virtual void TearDown() OVERRIDE { + system_hotkey_dictionary_.reset(); + system_hotkey_inner_dictionary_.reset(); + } + + // A constructed dictionary that matches the format of the one that would be + // parsed from the system hotkeys plist. + base::scoped_nsobject<NSMutableDictionary> system_hotkey_dictionary_; + + private: + // A reference to the mutable dictionary to which new entries are added. + base::scoped_nsobject<NSMutableDictionary> system_hotkey_inner_dictionary_; + // Each entry in the system_hotkey_inner_dictionary_ needs to have a unique + // key. This count is used to generate those unique keys. + int count_; +}; + +TEST_F(SystemHotkeyMapTest, Parse) { + // This plist was pulled from a real machine. It is extensively populated, + // and has no missing or incomplete entries. + NSData* data = DataFromTestFile("mac/mac_system_hotkeys.plist"); + ASSERT_TRUE(data); + + NSDictionary* dictionary = SystemHotkeyMap::DictionaryFromData(data); + ASSERT_TRUE(dictionary); + + SystemHotkeyMap map; + bool result = map.ParseDictionary(dictionary); + EXPECT_TRUE(result); + + // Command + ` is a common key binding. It should exist. + unsigned short key_code = kVK_ANSI_Grave; + NSUInteger modifiers = NSCommandKeyMask; + EXPECT_TRUE(map.IsHotkeyReserved(key_code, modifiers)); + + // Command + Shift + ` is a common key binding. It should exist. + modifiers = NSCommandKeyMask | NSShiftKeyMask; + EXPECT_TRUE(map.IsHotkeyReserved(key_code, modifiers)); + + // Command + Shift + Ctr + ` is not a common key binding. + modifiers = NSCommandKeyMask | NSShiftKeyMask | NSControlKeyMask; + EXPECT_FALSE(map.IsHotkeyReserved(key_code, modifiers)); + + // Command + L is not a common key binding. + key_code = kVK_ANSI_L; + modifiers = NSCommandKeyMask; + EXPECT_FALSE(map.IsHotkeyReserved(key_code, modifiers)); +} + +TEST_F(SystemHotkeyMapTest, ParseNil) { + NSDictionary* dictionary = nil; + + SystemHotkeyMap map; + bool result = map.ParseDictionary(dictionary); + EXPECT_FALSE(result); +} + +TEST_F(SystemHotkeyMapTest, ParseMouse) { + // This plist was pulled from a real machine. It has missing entries, + // incomplete entries, and mouse hotkeys. + NSData* data = DataFromTestFile("mac/mac_system_hotkeys_sparse.plist"); + ASSERT_TRUE(data); + + NSDictionary* dictionary = SystemHotkeyMap::DictionaryFromData(data); + ASSERT_TRUE(dictionary); + + SystemHotkeyMap map; + bool result = map.ParseDictionary(dictionary); + EXPECT_TRUE(result); + + // Command + ` is a common key binding. It is missing. + // TODO(erikchen): OSX uses the default value when the keybinding is missing, + // so the hotkey should still be reserved. + // http://crbug.com/383558 + unsigned short key_code = kVK_ANSI_Grave; + NSUInteger modifiers = NSCommandKeyMask; + EXPECT_FALSE(map.IsHotkeyReserved(key_code, modifiers)); + + // There is a mouse keybinding for 0x08. It should not apply to keyboard + // hotkeys. + key_code = kVK_ANSI_C; + modifiers = 0; + EXPECT_FALSE(map.IsHotkeyReserved(key_code, modifiers)); + + // Command + Alt + = is an accessibility shortcut. Its entry in the plist is + // incomplete. + // TODO(erikchen): OSX uses the default bindings, so this hotkey should still + // be reserved. + // http://crbug.com/383558 + key_code = kVK_ANSI_Equal; + modifiers = NSCommandKeyMask | NSAlternateKeyMask; + EXPECT_FALSE(map.IsHotkeyReserved(key_code, modifiers)); +} + +TEST_F(SystemHotkeyMapTest, ParseCustomEntries) { + unsigned short key_code = kVK_ANSI_C; + + AddEntryToDictionary(YES, key_code, 0); + AddEntryToDictionary(YES, key_code, NSAlphaShiftKeyMask); + AddEntryToDictionary(YES, key_code, NSShiftKeyMask); + AddEntryToDictionary(YES, key_code, NSControlKeyMask); + AddEntryToDictionary(YES, key_code, NSFunctionKeyMask); + AddEntryToDictionary(YES, key_code, NSFunctionKeyMask | NSControlKeyMask); + AddEntryToDictionary(NO, key_code, NSAlternateKeyMask); + + SystemHotkeyMap map; + + bool result = map.ParseDictionary(system_hotkey_dictionary_.get()); + EXPECT_TRUE(result); + + // Entries without control, command, or alternate key mask should not be + // reserved. + EXPECT_FALSE(map.IsHotkeyReserved(key_code, 0)); + EXPECT_FALSE(map.IsHotkeyReserved(key_code, NSAlphaShiftKeyMask)); + EXPECT_FALSE(map.IsHotkeyReserved(key_code, NSShiftKeyMask)); + EXPECT_FALSE(map.IsHotkeyReserved(key_code, NSFunctionKeyMask)); + + // Unlisted entries should not be reserved. + EXPECT_FALSE(map.IsHotkeyReserved(key_code, NSCommandKeyMask)); + + // Disabled entries should not be reserved. + EXPECT_FALSE(map.IsHotkeyReserved(key_code, NSAlternateKeyMask)); + + // Other entries should be reserved. + EXPECT_TRUE(map.IsHotkeyReserved(key_code, NSControlKeyMask)); + EXPECT_TRUE( + map.IsHotkeyReserved(key_code, NSFunctionKeyMask | NSControlKeyMask)); +} + +} // namespace content diff --git a/content/browser/renderer_host/render_widget_host_view_mac.mm b/content/browser/renderer_host/render_widget_host_view_mac.mm index b26950a..e60324a 100644 --- a/content/browser/renderer_host/render_widget_host_view_mac.mm +++ b/content/browser/renderer_host/render_widget_host_view_mac.mm @@ -28,6 +28,8 @@ #include "base/sys_info.h" #import "content/browser/accessibility/browser_accessibility_cocoa.h" #include "content/browser/accessibility/browser_accessibility_manager_mac.h" +#import "content/browser/cocoa/system_hotkey_helper_mac.h" +#import "content/browser/cocoa/system_hotkey_map.h" #include "content/browser/compositor/resize_lock.h" #include "content/browser/frame_host/frame_tree.h" #include "content/browser/frame_host/frame_tree_node.h" @@ -96,6 +98,17 @@ using blink::WebMouseEvent; using blink::WebMouseWheelEvent; using blink::WebGestureEvent; +namespace { + +// Whether a keyboard event has been reserved by OSX. +BOOL EventIsReservedBySystem(NSEvent* event) { + content::SystemHotkeyHelperMac* helper = + content::SystemHotkeyHelperMac::GetInstance(); + return helper->map()->IsEventReserved(event); +} + +} // namespace + // These are not documented, so use only after checking -respondsToSelector:. @interface NSApplication (UndocumentedSpeechMethods) - (void)speakString:(NSString*)string; @@ -2556,6 +2569,10 @@ void RenderWidgetHostViewMac::OnDisplayMetricsChanged( if ([[self window] firstResponder] != self) return NO; + // If the event is reserved by the system, then do not pass it to web content. + if (EventIsReservedBySystem(theEvent)) + return NO; + // If we return |NO| from this function, cocoa will send the key event to // the menu and only if the menu does not process the event to |keyDown:|. We // want to send the event to a renderer _before_ sending it to the menu, so @@ -2603,6 +2620,19 @@ void RenderWidgetHostViewMac::OnDisplayMetricsChanged( - (void)keyEvent:(NSEvent*)theEvent wasKeyEquivalent:(BOOL)equiv { TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::keyEvent"); + + // If the user changes the system hotkey mapping after Chrome has been + // launched, then it is possible that a formerly reserved system hotkey is no + // longer reserved. The hotkey would have skipped the renderer, but would + // also have not been handled by the system. If this is the case, immediately + // return. + // TODO(erikchen): SystemHotkeyHelperMac should use the File System Events + // api to monitor changes to system hotkeys. This logic will have to be + // updated. + // http://crbug.com/383558. + if (EventIsReservedBySystem(theEvent)) + return; + DCHECK([theEvent type] != NSKeyDown || !equiv == !([theEvent modifierFlags] & NSCommandKeyMask)); diff --git a/content/content_browser.gypi b/content/content_browser.gypi index 8adb26e..bc7356d 100644 --- a/content/content_browser.gypi +++ b/content/content_browser.gypi @@ -412,6 +412,10 @@ 'browser/child_process_launcher.h', 'browser/child_process_security_policy_impl.cc', 'browser/child_process_security_policy_impl.h', + 'browser/cocoa/system_hotkey_helper_mac.h', + 'browser/cocoa/system_hotkey_helper_mac.mm', + 'browser/cocoa/system_hotkey_map.h', + 'browser/cocoa/system_hotkey_map.mm', 'browser/cross_site_request_manager.cc', 'browser/cross_site_request_manager.h', 'browser/devtools/devtools_agent_host_impl.cc', diff --git a/content/content_tests.gypi b/content/content_tests.gypi index 61f541c..5aa4712 100644 --- a/content/content_tests.gypi +++ b/content/content_tests.gypi @@ -407,6 +407,7 @@ 'browser/browser_url_handler_impl_unittest.cc', 'browser/byte_stream_unittest.cc', 'browser/child_process_security_policy_unittest.cc', + 'browser/cocoa/system_hotkey_map_unittest.mm', 'browser/compositor/software_browser_compositor_output_surface_unittest.cc', 'browser/compositor/software_output_device_ozone_unittest.cc', 'browser/databases_table_unittest.cc', diff --git a/content/test/data/mac/mac_system_hotkeys.plist b/content/test/data/mac/mac_system_hotkeys.plist new file mode 100644 index 0000000..c541463 --- /dev/null +++ b/content/test/data/mac/mac_system_hotkeys.plist @@ -0,0 +1,866 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>AppleSymbolicHotKeys</key> + <dict> + <key>10</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>65535</integer> + <integer>96</integer> + <integer>262144</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>11</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>65535</integer> + <integer>97</integer> + <integer>262144</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>118</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>65535</integer> + <integer>18</integer> + <integer>262144</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>119</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>65535</integer> + <integer>19</integer> + <integer>262144</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>12</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>65535</integer> + <integer>122</integer> + <integer>262144</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>120</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>65535</integer> + <integer>20</integer> + <integer>262144</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>121</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>65535</integer> + <integer>21</integer> + <integer>262144</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>13</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>65535</integer> + <integer>98</integer> + <integer>262144</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>15</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>56</integer> + <integer>28</integer> + <integer>1572864</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>16</key> + <dict> + <key>enabled</key> + <false/> + </dict> + <key>160</key> + <dict> + <key>enabled</key> + <false/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>65535</integer> + <integer>65535</integer> + <integer>0</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>162</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>65535</integer> + <integer>96</integer> + <integer>1572864</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>163</key> + <dict> + <key>enabled</key> + <false/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>65535</integer> + <integer>65535</integer> + <integer>0</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>17</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>61</integer> + <integer>24</integer> + <integer>1572864</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>175</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>65535</integer> + <integer>65535</integer> + <integer>0</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>18</key> + <dict> + <key>enabled</key> + <false/> + </dict> + <key>19</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>45</integer> + <integer>27</integer> + <integer>1572864</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>20</key> + <dict> + <key>enabled</key> + <false/> + </dict> + <key>21</key> + <dict> + <key>enabled</key> + <false/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>56</integer> + <integer>28</integer> + <integer>1835008</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>22</key> + <dict> + <key>enabled</key> + <false/> + </dict> + <key>23</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>92</integer> + <integer>42</integer> + <integer>1572864</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>24</key> + <dict> + <key>enabled</key> + <false/> + </dict> + <key>25</key> + <dict> + <key>enabled</key> + <false/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>46</integer> + <integer>47</integer> + <integer>1835008</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>26</key> + <dict> + <key>enabled</key> + <false/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>44</integer> + <integer>43</integer> + <integer>1835008</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>27</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>96</integer> + <integer>50</integer> + <integer>1048576</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>28</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>51</integer> + <integer>20</integer> + <integer>1179648</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>29</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>51</integer> + <integer>20</integer> + <integer>1441792</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>30</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>52</integer> + <integer>21</integer> + <integer>1179648</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>31</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>52</integer> + <integer>21</integer> + <integer>1441792</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>32</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>65535</integer> + <integer>126</integer> + <integer>262144</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>33</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>65535</integer> + <integer>125</integer> + <integer>262144</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>34</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>65535</integer> + <integer>126</integer> + <integer>393216</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>35</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>65535</integer> + <integer>125</integer> + <integer>393216</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>36</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>65535</integer> + <integer>103</integer> + <integer>0</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>37</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>65535</integer> + <integer>103</integer> + <integer>131072</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>51</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>39</integer> + <integer>50</integer> + <integer>1572864</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>52</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>100</integer> + <integer>2</integer> + <integer>1572864</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>53</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>65535</integer> + <integer>107</integer> + <integer>0</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>54</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>65535</integer> + <integer>113</integer> + <integer>0</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>55</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>65535</integer> + <integer>107</integer> + <integer>524288</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>56</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>65535</integer> + <integer>113</integer> + <integer>524288</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>57</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>65535</integer> + <integer>100</integer> + <integer>262144</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>59</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>65535</integer> + <integer>96</integer> + <integer>1048576</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>60</key> + <dict> + <key>enabled</key> + <false/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>32</integer> + <integer>49</integer> + <integer>1048576</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>61</key> + <dict> + <key>enabled</key> + <false/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>32</integer> + <integer>49</integer> + <integer>1572864</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>62</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>65535</integer> + <integer>111</integer> + <integer>0</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>63</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>65535</integer> + <integer>111</integer> + <integer>131072</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>64</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>65535</integer> + <integer>49</integer> + <integer>1048576</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>65</key> + <dict> + <key>enabled</key> + <false/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>65535</integer> + <integer>49</integer> + <integer>1572864</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>7</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>65535</integer> + <integer>120</integer> + <integer>262144</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>79</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>65535</integer> + <integer>123</integer> + <integer>3932160</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>8</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>65535</integer> + <integer>99</integer> + <integer>262144</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>80</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>65535</integer> + <integer>123</integer> + <integer>4063232</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>81</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>65535</integer> + <integer>124</integer> + <integer>3932160</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>82</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>65535</integer> + <integer>124</integer> + <integer>4063232</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>9</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>65535</integer> + <integer>118</integer> + <integer>262144</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + <key>98</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>47</integer> + <integer>44</integer> + <integer>1179648</integer> + </array> + <key>type</key> + <string>standard</string> + </dict> + </dict> + </dict> +</dict> +</plist> diff --git a/content/test/data/mac/mac_system_hotkeys_sparse.plist b/content/test/data/mac/mac_system_hotkeys_sparse.plist new file mode 100644 index 0000000..ffda3af --- /dev/null +++ b/content/test/data/mac/mac_system_hotkeys_sparse.plist @@ -0,0 +1,133 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>AppleSymbolicHotKeys</key> + <dict> + <key>15</key> + <dict> + <key>enabled</key> + <true/> + </dict> + <key>16</key> + <dict> + <key>enabled</key> + <false/> + </dict> + <key>17</key> + <dict> + <key>enabled</key> + <true/> + </dict> + <key>18</key> + <dict> + <key>enabled</key> + <false/> + </dict> + <key>19</key> + <dict> + <key>enabled</key> + <true/> + </dict> + <key>20</key> + <dict> + <key>enabled</key> + <false/> + </dict> + <key>21</key> + <dict> + <key>enabled</key> + <false/> + </dict> + <key>22</key> + <dict> + <key>enabled</key> + <false/> + </dict> + <key>23</key> + <dict> + <key>enabled</key> + <true/> + </dict> + <key>24</key> + <dict> + <key>enabled</key> + <false/> + </dict> + <key>25</key> + <dict> + <key>enabled</key> + <false/> + </dict> + <key>26</key> + <dict> + <key>enabled</key> + <false/> + </dict> + <key>38</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>8</integer> + <integer>8</integer> + <integer>0</integer> + </array> + <key>type</key> + <string>button</string> + </dict> + </dict> + <key>39</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>16</integer> + <integer>16</integer> + <integer>0</integer> + </array> + <key>type</key> + <string>button</string> + </dict> + </dict> + <key>40</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>8</integer> + <integer>8</integer> + <integer>131072</integer> + </array> + <key>type</key> + <string>button</string> + </dict> + </dict> + <key>41</key> + <dict> + <key>enabled</key> + <true/> + <key>value</key> + <dict> + <key>parameters</key> + <array> + <integer>16</integer> + <integer>16</integer> + <integer>131072</integer> + </array> + <key>type</key> + <string>button</string> + </dict> + </dict> + </dict> +</dict> +</plist> |