diff options
author | plundblad@chromium.org <plundblad@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-05-07 13:30:20 +0000 |
---|---|---|
committer | plundblad@chromium.org <plundblad@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-05-07 13:30:20 +0000 |
commit | 2e5e0b1b4fe9ecbc812ae56813376e600f35cb2b (patch) | |
tree | a1ebc0f019f68df6b66e1215931e598ac7007d4a | |
parent | ee83d8358c6bca47a642b1856af8c337738f5a57 (diff) | |
download | chromium_src-2e5e0b1b4fe9ecbc812ae56813376e600f35cb2b.zip chromium_src-2e5e0b1b4fe9ecbc812ae56813376e600f35cb2b.tar.gz chromium_src-2e5e0b1b4fe9ecbc812ae56813376e600f35cb2b.tar.bz2 |
Add IME for braille input.
BUG=310285
R=dmazzoni@chromium.org,shuchen@chromium.org,jhawkins@chromium.org
Review URL: https://codereview.chromium.org/242453007
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@268724 0039d316-1c4b-4281-b951-d872f2087c98
27 files changed, 1168 insertions, 29 deletions
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp index 6881fb5..48a4321 100644 --- a/chrome/app/chromeos_strings.grdp +++ b/chrome/app/chromeos_strings.grdp @@ -1238,6 +1238,9 @@ Press any key to continue exploring. <message name="IDS_LANGUAGES_MEDIUM_LEN_NAME_KOREAN" desc="Medium length name for the input method for Korean which is show following the text: Your input method has changed to..."> Korean </message> + <message name="IDS_LANGUAGES_MEDIUM_LEN_NAME_BRAILLE" desc="Medium length name for the input method for the hardware keyboard on a braille display. Shown after the text: Your input method has changed to..."> + Braille + </message> <message name="IDS_KEYBOARD_SELECTION_SELECT" desc="Label for keyboard selection dropdown"> Select your keyboard: </message> diff --git a/chrome/browser/chromeos/accessibility/accessibility_manager.cc b/chrome/browser/chromeos/accessibility/accessibility_manager.cc index 58a9d6f..8f65061 100644 --- a/chrome/browser/chromeos/accessibility/accessibility_manager.cc +++ b/chrome/browser/chromeos/accessibility/accessibility_manager.cc @@ -18,6 +18,8 @@ #include "base/path_service.h" #include "base/prefs/pref_member.h" #include "base/prefs/pref_service.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" #include "base/time/time.h" #include "base/values.h" #include "chrome/browser/accessibility/accessibility_extension_api.h" @@ -39,6 +41,7 @@ #include "chrome/common/extensions/manifest_handlers/content_scripts_handler.h" #include "chrome/common/pref_names.h" #include "chromeos/audio/chromeos_sounds.h" +#include "chromeos/ime/input_method_manager.h" #include "chromeos/login/login_state.h" #include "content/public/browser/browser_accessibility_state.h" #include "content/public/browser/browser_thread.h" @@ -66,6 +69,7 @@ using content::BrowserThread; using content::RenderViewHost; using extensions::api::braille_display_private::BrailleController; using extensions::api::braille_display_private::DisplayState; +using extensions::api::braille_display_private::KeyEvent; namespace chromeos { @@ -310,7 +314,8 @@ AccessibilityManager::AccessibilityManager() should_speak_chrome_vox_announcements_on_user_screen_(true), system_sounds_enabled_(false), braille_display_connected_(false), - scoped_braille_observer_(this) { + scoped_braille_observer_(this), + braille_ime_current_(false) { notification_registrar_.Add(this, chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE, content::NotificationService::AllSources()); @@ -511,6 +516,7 @@ void AccessibilityManager::UpdateSpokenFeedbackFromPref() { } else { UnloadChromeVox(); } + UpdateBrailleImeState(); } void AccessibilityManager::LoadChromeVox() { @@ -764,7 +770,33 @@ void AccessibilityManager::CheckBrailleState() { void AccessibilityManager::ReceiveBrailleDisplayState( scoped_ptr<extensions::api::braille_display_private::DisplayState> state) { - OnDisplayStateChanged(*state); + OnBrailleDisplayStateChanged(*state); +} + +void AccessibilityManager::UpdateBrailleImeState() { + if (!profile_) + return; + PrefService* pref_service = profile_->GetPrefs(); + std::vector<std::string> preload_engines; + base::SplitString(pref_service->GetString(prefs::kLanguagePreloadEngines), + ',', + &preload_engines); + std::vector<std::string>::iterator it = + std::find(preload_engines.begin(), + preload_engines.end(), + extension_misc::kBrailleImeEngineId); + bool is_enabled = (it != preload_engines.end()); + bool should_be_enabled = + (spoken_feedback_enabled_ && braille_display_connected_); + if (is_enabled == should_be_enabled) + return; + if (should_be_enabled) + preload_engines.push_back(extension_misc::kBrailleImeEngineId); + else + preload_engines.erase(it); + pref_service->SetString(prefs::kLanguagePreloadEngines, + JoinString(preload_engines, ',')); + braille_ime_current_ = false; } // Overridden from InputMethodManager::Observer. @@ -777,6 +809,10 @@ void AccessibilityManager::InputMethodChanged( manager->IsISOLevel5ShiftUsedByCurrentInputMethod(), manager->IsAltGrUsedByCurrentInputMethod()); #endif + const chromeos::input_method::InputMethodDescriptor descriptor = + manager->GetCurrentInputMethod(); + braille_ime_current_ = + (descriptor.id() == extension_misc::kBrailleImeEngineId); } void AccessibilityManager::SetProfile(Profile* profile) { @@ -841,7 +877,8 @@ void AccessibilityManager::SetProfile(Profile* profile) { if (!had_profile && profile) CheckBrailleState(); - + else + UpdateBrailleImeState(); UpdateLargeCursorFromPref(); UpdateStickyKeysFromPref(); UpdateSpokenFeedbackFromPref(); @@ -982,11 +1019,13 @@ void AccessibilityManager::Observe( } } -void AccessibilityManager::OnDisplayStateChanged( +void AccessibilityManager::OnBrailleDisplayStateChanged( const DisplayState& display_state) { braille_display_connected_ = display_state.available; - if (braille_display_connected_) + if (braille_display_connected_) { EnableSpokenFeedback(true, ash::A11Y_NOTIFICATION_SHOW); + } + UpdateBrailleImeState(); AccessibilityStatusEventDetails details( ACCESSIBILITY_BRAILLE_DISPLAY_CONNECTION_STATE_CHANGED, @@ -995,6 +1034,16 @@ void AccessibilityManager::OnDisplayStateChanged( NotifyAccessibilityStatusChanged(details); } +void AccessibilityManager::OnBrailleKeyEvent(const KeyEvent& event) { + // Ensure the braille IME is active on braille keyboard (dots) input. + if ((event.command == + extensions::api::braille_display_private::KEY_COMMAND_DOTS) && + !braille_ime_current_) { + input_method::InputMethodManager::Get()->ChangeInputMethod( + extension_misc::kBrailleImeEngineId); + } +} + void AccessibilityManager::PostLoadChromeVox(Profile* profile) { // Do any setup work needed immediately after ChromeVox actually loads. if (system_sounds_enabled_) diff --git a/chrome/browser/chromeos/accessibility/accessibility_manager.h b/chrome/browser/chromeos/accessibility/accessibility_manager.h index a0aec2c..91a4f4c 100644 --- a/chrome/browser/chromeos/accessibility/accessibility_manager.h +++ b/chrome/browser/chromeos/accessibility/accessibility_manager.h @@ -213,6 +213,7 @@ class AccessibilityManager void CheckBrailleState(); void ReceiveBrailleDisplayState( scoped_ptr<extensions::api::braille_display_private::DisplayState> state); + void UpdateBrailleImeState(); void SetProfile(Profile* profile); @@ -225,9 +226,11 @@ class AccessibilityManager // extensions::api::braille_display_private::BrailleObserver implementation. // Enables spoken feedback if a braille display becomes available. - virtual void OnDisplayStateChanged( + virtual void OnBrailleDisplayStateChanged( const extensions::api::braille_display_private::DisplayState& display_state) OVERRIDE; + virtual void OnBrailleKeyEvent( + const extensions::api::braille_display_private::KeyEvent& event) OVERRIDE; // InputMethodManager::Observer virtual void InputMethodChanged(input_method::InputMethodManager* manager, @@ -276,6 +279,8 @@ class AccessibilityManager ScopedObserver<extensions::api::braille_display_private::BrailleController, AccessibilityManager> scoped_braille_observer_; + bool braille_ime_current_; + DISALLOW_COPY_AND_ASSIGN(AccessibilityManager); }; diff --git a/chrome/browser/chromeos/accessibility/accessibility_manager_browsertest.cc b/chrome/browser/chromeos/accessibility/accessibility_manager_browsertest.cc index d397f23..4389ed4 100644 --- a/chrome/browser/chromeos/accessibility/accessibility_manager_browsertest.cc +++ b/chrome/browser/chromeos/accessibility/accessibility_manager_browsertest.cc @@ -15,19 +15,30 @@ #include "chrome/browser/chromeos/login/login_utils.h" #include "chrome/browser/chromeos/login/user_manager.h" #include "chrome/browser/chromeos/login/user_manager_impl.h" +#include "chrome/browser/chromeos/preferences.h" #include "chrome/browser/chromeos/profiles/profile_helper.h" #include "chrome/browser/extensions/api/braille_display_private/mock_braille_controller.h" +#include "chrome/browser/prefs/pref_service_syncable.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile_manager.h" +#include "chrome/common/extensions/extension_constants.h" #include "chrome/common/pref_names.h" #include "chrome/test/base/in_process_browser_test.h" #include "chrome/test/base/testing_profile.h" #include "chromeos/chromeos_switches.h" +#include "chromeos/ime/component_extension_ime_manager.h" +#include "chromeos/ime/input_method_manager.h" #include "content/public/browser/notification_service.h" +#include "content/public/test/test_utils.h" #include "testing/gtest/include/gtest/gtest.h" +using chromeos::input_method::InputMethodManager; +using chromeos::input_method::InputMethodUtil; +using chromeos::input_method::InputMethodDescriptors; +using content::BrowserThread; using extensions::api::braille_display_private::BrailleObserver; using extensions::api::braille_display_private::DisplayState; +using extensions::api::braille_display_private::KeyEvent; using extensions::api::braille_display_private::MockBrailleController; namespace chromeos { @@ -189,7 +200,25 @@ int GetAutoclickDelayFromPref() { return GetPrefs()->GetInteger(prefs::kAutoclickDelayMs); } -} // anonymouse namespace +bool IsBrailleImeActive() { + InputMethodManager* imm = InputMethodManager::Get(); + scoped_ptr<InputMethodDescriptors> descriptors = + imm->GetActiveInputMethods(); + for (InputMethodDescriptors::const_iterator i = descriptors->begin(); + i != descriptors->end(); + ++i) { + if (i->id() == extension_misc::kBrailleImeEngineId) + return true; + } + return false; +} + +bool IsBrailleImeCurrent() { + InputMethodManager* imm = InputMethodManager::Get(); + return imm->GetCurrentInputMethod().id() == + extension_misc::kBrailleImeEngineId; +} +} // anonymous namespace class AccessibilityManagerTest : public InProcessBrowserTest { protected: @@ -217,6 +246,12 @@ class AccessibilityManagerTest : public InProcessBrowserTest { AccessibilityManager::SetBrailleControllerForTest(NULL); } + void SetBrailleDisplayAvailability(bool available) { + braille_controller_.SetAvailable(available); + braille_controller_.GetObserver()->OnBrailleDisplayStateChanged( + *braille_controller_.GetDisplayState()); + } + int default_autoclick_delay() const { return default_autoclick_delay_; } int default_autoclick_delay_; @@ -291,10 +326,7 @@ IN_PROC_BROWSER_TEST_F(AccessibilityManagerTest, BrailleOnLoginScreen) { EXPECT_FALSE(IsSpokenFeedbackEnabled()); // Signal the accessibility manager that a braille display was connected. - braille_controller_.SetAvailable(true); - braille_controller_.GetObserver()->OnDisplayStateChanged( - *braille_controller_.GetDisplayState()); - + SetBrailleDisplayAvailability(true); // Confirms that the spoken feedback is enabled. EXPECT_TRUE(IsSpokenFeedbackEnabled()); } @@ -582,6 +614,56 @@ IN_PROC_BROWSER_TEST_P(AccessibilityManagerUserTypeTest, EXPECT_EQ(kTestAutoclickDelayMs, GetAutoclickDelayFromPref()); } +IN_PROC_BROWSER_TEST_P(AccessibilityManagerUserTypeTest, BrailleWhenLoggedIn) { + // Logs in. + const char* user_name = GetParam(); + UserManager::Get()->UserLoggedIn(user_name, user_name, true); + UserManager::Get()->SessionStarted(); + // The |ComponentExtensionIMEManager| defers some initialization to the + // |FILE| thread. We need to wait for that to finish before continuing. + InputMethodManager* imm = InputMethodManager::Get(); + while (!imm->GetComponentExtensionIMEManager()->IsInitialized()) { + content::RunAllPendingInMessageLoop(BrowserThread::FILE); + } + // This object watches for IME preference changes and reflects those in + // the IME framework state. + chromeos::Preferences prefs; + prefs.InitUserPrefsForTesting(PrefServiceSyncable::FromProfile(GetProfile()), + UserManager::Get()->GetActiveUser()); + + // Make sure we start in the expected state. + EXPECT_FALSE(IsBrailleImeActive()); + EXPECT_FALSE(IsSpokenFeedbackEnabled()); + + // Signal the accessibility manager that a braille display was connected. + SetBrailleDisplayAvailability(true); + + // Now, both spoken feedback and the Braille IME should be enabled. + EXPECT_TRUE(IsSpokenFeedbackEnabled()); + EXPECT_TRUE(IsBrailleImeActive()); + + // Send a braille dots key event and make sure that the braille IME is + // enabled. + KeyEvent event; + event.command = extensions::api::braille_display_private::KEY_COMMAND_DOTS; + event.braille_dots.reset(new int(0)); + braille_controller_.GetObserver()->OnBrailleKeyEvent(event); + EXPECT_TRUE(IsBrailleImeCurrent()); + + // Unplug the display. Spolken feedback remains on, but the Braille IME + // should get deactivated. + SetBrailleDisplayAvailability(false); + EXPECT_TRUE(IsSpokenFeedbackEnabled()); + EXPECT_FALSE(IsBrailleImeActive()); + EXPECT_FALSE(IsBrailleImeCurrent()); + + // Plugging in a display while spoken feedback is enabled should activate + // the Braille IME. + SetBrailleDisplayAvailability(true); + EXPECT_TRUE(IsSpokenFeedbackEnabled()); + EXPECT_TRUE(IsBrailleImeActive()); +} + IN_PROC_BROWSER_TEST_F(AccessibilityManagerTest, AcessibilityMenuVisibility) { // Log in. UserManager::Get()->UserLoggedIn(kTestUserName, kTestUserName, true); diff --git a/chrome/browser/chromeos/input_method/component_extension_ime_manager_impl.cc b/chrome/browser/chromeos/input_method/component_extension_ime_manager_impl.cc index 8afa4b9..6ce6bae 100644 --- a/chrome/browser/chromeos/input_method/component_extension_ime_manager_impl.cc +++ b/chrome/browser/chromeos/input_method/component_extension_ime_manager_impl.cc @@ -6,10 +6,13 @@ #include "base/file_util.h" #include "base/logging.h" +#include "base/path_service.h" #include "chrome/browser/extensions/component_loader.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile_manager.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/extensions/extension_file_util.h" #include "content/public/browser/browser_thread.h" #include "extensions/browser/extension_system.h" #include "extensions/common/extension.h" @@ -84,6 +87,11 @@ struct WhitelistedComponentExtensionIME { "/usr/share/chromeos-assets/input_methods/nacl_mozc", }, #endif + { + // Braille hardware keyboard IME that works together with ChromeVox. + extension_misc::kBrailleImeExtensionId, + extension_misc::kBrailleImeExtensionPath, + }, }; extensions::ComponentLoader* GetComponentLoader() { @@ -284,6 +292,12 @@ void ComponentExtensionIMEManagerImpl::ReadComponentExtensionsInfo( component_ime.path = base::FilePath( whitelisted_component_extension[i].path); + if (!component_ime.path.IsAbsolute()) { + base::FilePath resources_path; + if (!PathService::Get(chrome::DIR_RESOURCES, &resources_path)) + NOTREACHED(); + component_ime.path = resources_path.Append(component_ime.path); + } const base::FilePath manifest_path = component_ime.path.Append("manifest.json"); diff --git a/chrome/browser/chromeos/input_method/input_method_util.cc b/chrome/browser/chromeos/input_method/input_method_util.cc index 21b9d64..d4355db 100644 --- a/chrome/browser/chromeos/input_method/input_method_util.cc +++ b/chrome/browser/chromeos/input_method/input_method_util.cc @@ -15,6 +15,7 @@ #include "base/strings/string_split.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" +#include "chrome/common/extensions/extension_constants.h" #include "chromeos/ime/component_extension_ime_manager.h" #include "chromeos/ime/extension_ime_util.h" // For SetHardwareKeyboardLayoutForTesting. @@ -70,6 +71,10 @@ const struct { { "_comp_ime_bdgdidmhaijohebebipajioienkglgfohangul_3setnoshift", "\xed\x95\x9c" }, { "_comp_ime_bdgdidmhaijohebebipajioienkglgfohangul_romaja", "\xed\x95\x9c" }, + { extension_misc::kBrailleImeEngineId, + // U+2803 U+2817 U+2807 (Unicode braille patterns for the letters 'brl' in + // English (and many other) braille codes. + "\xe2\xa0\x83\xe2\xa0\x97\xe2\xa0\x87" }, }; const size_t kMappingFromIdToIndicatorTextLen = @@ -104,6 +109,8 @@ const struct { IDS_LANGUAGES_MEDIUM_LEN_NAME_CHINESE_TRADITIONAL }, { "_comp_ime_gjaehgfemfahhmlgpdfknkhdnemmolopzh-hant-t-i0-cangjie-1987", IDS_LANGUAGES_MEDIUM_LEN_NAME_CHINESE_TRADITIONAL }, + { extension_misc::kBrailleImeEngineId, + IDS_LANGUAGES_MEDIUM_LEN_NAME_BRAILLE }, }; const size_t kMappingImeIdToMediumLenNameResourceIdLen = ARRAYSIZE_UNSAFE(kMappingImeIdToMediumLenNameResourceId); diff --git a/chrome/browser/chromeos/system/tray_accessibility_browsertest.cc b/chrome/browser/chromeos/system/tray_accessibility_browsertest.cc index b6fc802..3e5d178 100644 --- a/chrome/browser/chromeos/system/tray_accessibility_browsertest.cc +++ b/chrome/browser/chromeos/system/tray_accessibility_browsertest.cc @@ -251,7 +251,7 @@ class TrayAccessibilityTest void SetBrailleConnected(bool connected) { braille_controller_.SetAvailable(connected); - braille_controller_.GetObserver()->OnDisplayStateChanged( + braille_controller_.GetObserver()->OnBrailleDisplayStateChanged( *braille_controller_.GetDisplayState()); } diff --git a/chrome/browser/extensions/api/braille_display_private/braille_controller.h b/chrome/browser/extensions/api/braille_display_private/braille_controller.h index ed7954d..f975fc3 100644 --- a/chrome/browser/extensions/api/braille_display_private/braille_controller.h +++ b/chrome/browser/extensions/api/braille_display_private/braille_controller.h @@ -39,8 +39,9 @@ class BrailleController { // Observer for events from the BrailleController class BrailleObserver { public: - virtual void OnDisplayStateChanged(const DisplayState& display_state) {} - virtual void OnKeyEvent(const KeyEvent& event) {} + virtual void OnBrailleDisplayStateChanged( + const DisplayState& display_state) {} + virtual void OnBrailleKeyEvent(const KeyEvent& event) {} }; } // namespace braille_display_private diff --git a/chrome/browser/extensions/api/braille_display_private/braille_controller_brlapi.cc b/chrome/browser/extensions/api/braille_display_private/braille_controller_brlapi.cc index 38431ad..60eaa5e 100644 --- a/chrome/browser/extensions/api/braille_display_private/braille_controller_brlapi.cc +++ b/chrome/browser/extensions/api/braille_display_private/braille_controller_brlapi.cc @@ -347,7 +347,7 @@ void BrailleControllerImpl::DispatchKeyEvent(scoped_ptr<KeyEvent> event) { return; } VLOG(1) << "Dispatching key event: " << *event->ToValue(); - FOR_EACH_OBSERVER(BrailleObserver, observers_, OnKeyEvent(*event)); + FOR_EACH_OBSERVER(BrailleObserver, observers_, OnBrailleKeyEvent(*event)); } void BrailleControllerImpl::DispatchOnDisplayStateChanged( @@ -363,7 +363,7 @@ void BrailleControllerImpl::DispatchOnDisplayStateChanged( return; } FOR_EACH_OBSERVER(BrailleObserver, observers_, - OnDisplayStateChanged(*new_state)); + OnBrailleDisplayStateChanged(*new_state)); } } // namespace braille_display_private diff --git a/chrome/browser/extensions/api/braille_display_private/braille_display_private_api.cc b/chrome/browser/extensions/api/braille_display_private/braille_display_private_api.cc index 5cbcb34..8c0fa61 100644 --- a/chrome/browser/extensions/api/braille_display_private/braille_display_private_api.cc +++ b/chrome/browser/extensions/api/braille_display_private/braille_display_private_api.cc @@ -60,7 +60,7 @@ BrailleDisplayPrivateAPI::GetFactoryInstance() { return g_factory.Pointer(); } -void BrailleDisplayPrivateAPI::OnDisplayStateChanged( +void BrailleDisplayPrivateAPI::OnBrailleDisplayStateChanged( const DisplayState& display_state) { scoped_ptr<Event> event(new Event( OnDisplayStateChanged::kEventName, @@ -68,8 +68,7 @@ void BrailleDisplayPrivateAPI::OnDisplayStateChanged( event_delegate_->BroadcastEvent(event.Pass()); } -void BrailleDisplayPrivateAPI::OnKeyEvent( - const KeyEvent& key_event) { +void BrailleDisplayPrivateAPI::OnBrailleKeyEvent(const KeyEvent& key_event) { // Key events only go to extensions of the active profile. if (!IsProfileActive()) return; diff --git a/chrome/browser/extensions/api/braille_display_private/braille_display_private_api.h b/chrome/browser/extensions/api/braille_display_private/braille_display_private_api.h index 3fe1ba28..6c049d6 100644 --- a/chrome/browser/extensions/api/braille_display_private/braille_display_private_api.h +++ b/chrome/browser/extensions/api/braille_display_private/braille_display_private_api.h @@ -37,9 +37,9 @@ class BrailleDisplayPrivateAPI : public BrowserContextKeyedAPI, GetFactoryInstance(); // BrailleObserver implementation. - virtual void OnDisplayStateChanged( + virtual void OnBrailleDisplayStateChanged( const api::braille_display_private::DisplayState& display_state) OVERRIDE; - virtual void OnKeyEvent( + virtual void OnBrailleKeyEvent( const api::braille_display_private::KeyEvent& keyEvent) OVERRIDE; // EventRouter::Observer implementation. diff --git a/chrome/browser/extensions/api/braille_display_private/braille_display_private_apitest.cc b/chrome/browser/extensions/api/braille_display_private/braille_display_private_apitest.cc index c28feb6..d212b77 100644 --- a/chrome/browser/extensions/api/braille_display_private/braille_display_private_apitest.cc +++ b/chrome/browser/extensions/api/braille_display_private/braille_display_private_apitest.cc @@ -304,23 +304,23 @@ IN_PROC_BROWSER_TEST_F(BrailleDisplayPrivateAPIUserTest, // Send key event to both profiles. KeyEvent key_event; key_event.command = KEY_COMMAND_LINE_UP; - signin_api.OnKeyEvent(key_event); - user_api.OnKeyEvent(key_event); + signin_api.OnBrailleKeyEvent(key_event); + user_api.OnBrailleKeyEvent(key_event); EXPECT_EQ(0, signin_delegate->GetEventCount()); EXPECT_EQ(1, user_delegate->GetEventCount()); // Lock screen, and make sure that the key event goes to the // signin profile. LockScreen(tester.get()); - signin_api.OnKeyEvent(key_event); - user_api.OnKeyEvent(key_event); + signin_api.OnBrailleKeyEvent(key_event); + user_api.OnBrailleKeyEvent(key_event); EXPECT_EQ(1, signin_delegate->GetEventCount()); EXPECT_EQ(1, user_delegate->GetEventCount()); // Unlock screen, making sur ekey events go to the user profile again. DismissLockScreen(tester.get()); - signin_api.OnKeyEvent(key_event); - user_api.OnKeyEvent(key_event); + signin_api.OnBrailleKeyEvent(key_event); + user_api.OnBrailleKeyEvent(key_event); EXPECT_EQ(1, signin_delegate->GetEventCount()); EXPECT_EQ(2, user_delegate->GetEventCount()); } diff --git a/chrome/browser/extensions/api/braille_display_private/brlapi_connection.cc b/chrome/browser/extensions/api/braille_display_private/brlapi_connection.cc index 987f4b5..59b29b8 100644 --- a/chrome/browser/extensions/api/braille_display_private/brlapi_connection.cc +++ b/chrome/browser/extensions/api/braille_display_private/brlapi_connection.cc @@ -121,7 +121,10 @@ BrlapiConnection::ConnectResult BrlapiConnectionImpl::Connect( } const brlapi_keyCode_t extraKeys[] = { - BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_OFFLINE, + BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_OFFLINE, + // brltty 5.1 converts dot input to Unicode characters unless we + // explicitly accept this command. + BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_PASSDOTS, }; if (libbrlapi_loader_->brlapi__acceptKeys( handle_.get(), brlapi_rangeType_command, extraKeys, diff --git a/chrome/browser/resources/chromeos/braille_ime/OWNERS b/chrome/browser/resources/chromeos/braille_ime/OWNERS new file mode 100644 index 0000000..dfab84a --- /dev/null +++ b/chrome/browser/resources/chromeos/braille_ime/OWNERS @@ -0,0 +1,4 @@ +aboxhall@chromium.org +dmazzoni@chromium.org +dtseng@chromium.org +plundblad@chromium.org diff --git a/chrome/browser/resources/chromeos/braille_ime/PRESUBMIT.py b/chrome/browser/resources/chromeos/braille_ime/PRESUBMIT.py new file mode 100644 index 0000000..2a6d3ab --- /dev/null +++ b/chrome/browser/resources/chromeos/braille_ime/PRESUBMIT.py @@ -0,0 +1,25 @@ +# 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. + +"""Presubmit script for the Braille IME.""" + +def CheckChangeOnUpload(input_api, output_api): + def FileFilter(path): + return path.endswith('.js') or path.endswith('check_braille_ime.py') + if not any((FileFilter(p) for p in input_api.LocalPaths())): + return [] + import sys + if not sys.platform.startswith('linux'): + return [] + sys.path.insert(0, input_api.PresubmitLocalPath()) + try: + from check_braille_ime import check_braille_ime + finally: + sys.path.pop(0) + success, output = check_braille_ime() + if not success: + return [output_api.PresubmitError( + 'Braille IME closure compilation failed', + long_text=output)] + return [] diff --git a/chrome/browser/resources/chromeos/braille_ime/braille_ime.js b/chrome/browser/resources/chromeos/braille_ime/braille_ime.js new file mode 100644 index 0000000..885f6df --- /dev/null +++ b/chrome/browser/resources/chromeos/braille_ime/braille_ime.js @@ -0,0 +1,413 @@ +// 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. + +'use strict'; + +/** + * @fileoverview Braille hardware keyboard input method. + * + * This method is automatically enabled when a braille display is connected + * and ChromeVox is turned on. Most of the braille input and editing logic + * is located in ChromeVox where the braille translation library is available. + * This IME connects to ChromeVox and communicates using messages as follows: + * + * Sent from this IME to ChromeVox: + * {type: 'activeState', active: boolean} + * {type: 'inputContext', context: InputContext} + * Sent on focus/blur to inform ChromeVox of the type of the current field. + * In the latter case (blur), context is null. + * {type: 'reset'} + * Sent when the {code onReset} IME event fires. + * {type: 'brailleDots', dots: number} + * Sent when the user typed a braille cell using the standard keyboard. + * ChromeVox treats this similarly to entering braille input using the + * braille display. + * + * Sent from ChromeVox to this IME: + * {type: 'replaceText', contextID: number, deleteBefore: number, + * newText: string} + * Deletes {@code deleteBefore} characters before the cursor (or selection) + * and inserts {@code newText}. {@code contextID} identifies the text field + * to apply the update to (no change will happen if focus has moved to a + * different field). + */ + +/** + * @constructor + */ +var BrailleIme = function() {}; + +BrailleIme.prototype = { + /** + * Whether to enable extra debug logging for the IME. + * @const {boolean} + * @private + */ + DEBUG: false, + + /** + * ChromeVox extension ID. + * @const {string} + * @private + */ + CHROMEVOX_EXTENSION_ID_: 'mndnfokpggljbaajbnioimlmbfngpief', + + /** + * Name of the port used for communication with ChromeVox. + * @const {string} + * @private + */ + PORT_NAME: 'cvox.BrailleIme.Port', + + /** + * Identifier for the use standard keyboard option used in the menu and + * {@code localStorage}. This can be switched on to type braille using the + * standard keyboard, or off (default) for the usual keyboard behaviour. + * @const {string} + */ + USE_STANDARD_KEYBOARD_ID: 'useStandardKeyboard', + + // State related to the support for typing braille using a standrad + // (querty) keyboard. + + /** @private {boolean} */ + useStandardKeyboard_: false, + + /** + * Braille dots for keys that are currently pressed. + * @private {number} + */ + pressed_: 0, + + /** + * Dots that have been pressed at some point since {@code pressed_} was last + * {@code 0}. + * @private {number} + */ + accumulated_: 0, + + /** + * Maps key codes on a standard keyboard to the correspodning dots. + * Keys on the 'home row' correspond to the keys on a Perkins-style keyboard. + * Note that the mapping below is arranged like the dots in a braille cell. + * Only 6 dot input is supported. + * @private + * @const {Object.<string, string>} + */ + CODE_TO_DOT_: {'KeyF': 0x01, 'KeyJ': 0x08, + 'KeyD': 0x02, 'KeyK': 0x10, + 'KeyS': 0x04, 'KeyL': 0x20 }, + + /** + * The current engine ID as set by {@code onActivate}, or the empty string if + * the IME is not active. + * @type {string} + * @private + */ + engineID_: '', + + /** + * The port used to communicate with ChromeVox. + * @type {Port} port_ + * @private + */ + port_: null, + + /** + * Registers event listeners in the chrome IME API. + */ + init: function() { + chrome.input.ime.onActivate.addListener(this.onActivate_.bind(this)); + chrome.input.ime.onDeactivated.addListener(this.onDeactivated_.bind(this)); + chrome.input.ime.onFocus.addListener(this.onFocus_.bind(this)); + chrome.input.ime.onBlur.addListener(this.onBlur_.bind(this)); + chrome.input.ime.onInputContextUpdate.addListener( + this.onInputContextUpdate_.bind(this)); + chrome.input.ime.onKeyEvent.addListener(this.onKeyEvent_.bind(this)); + chrome.input.ime.onReset.addListener(this.onReset_.bind(this)); + chrome.input.ime.onMenuItemActivated.addListener( + this.onMenuItemActivated_.bind(this)); + this.connectChromeVox_(); + }, + + /** + * Called by the IME framework when this IME is activated. + * @param {string} engineID Engine ID, should be 'braille'. + * @private + */ + onActivate_: function(engineID) { + this.log_('onActivate', engineID); + this.engineID_ = engineID; + if (!this.port_) { + this.connectChromeVox_(); + } + this.useStandardKeyboard_ = + localStorage[this.USE_STANDARD_KEYBOARD_ID] === String(true); + this.accumulated_ = 0; + this.pressed_ = 0; + this.updateMenuItems_(); + this.sendActiveState_(); + }, + + /** + * Called by the IME framework when this IME is deactivated. + * @param {string} engineID Engine ID, should be 'braille'. + * @private + */ + onDeactivated_: function(engineID) { + this.log_('onDectivated', engineID); + this.engineID_ = ''; + this.sendActiveState_(); + }, + + /** + * Called by the IME framework when a text field receives focus. + * @param {InputContext} context Input field context. + * @private + */ + onFocus_: function(context) { + this.log_('onFocus', JSON.stringify(context)); + this.sendInputContext_(context); + }, + + /** + * Called by the IME framework when a text field looses focus. + * @param {number} contextID Input field context ID. + * @private + */ + onBlur_: function(contextID) { + this.log_('onBlur', contextID + ''); + this.sendInputContext_(null); + }, + + /** + * Called by the IME framework when the current input context is updated. + * @param {InputContext} context Input field context. + * @private + */ + onInputContextUpdate_: function(context) { + this.log_('onInputContextUpdate', JSON.stringify(context)); + this.sendInputContext_(context); + }, + + /** + * Called by the system when this IME is active and a key event is generated. + * @param {string} engineID Engine ID, should be 'braille'. + * @param {!ChromeKeyboardEvent} event The keyboard event. + * @return {boolean} Whether the event was handled by this IME (true) or + * should be allowed to propagate. + * @private + */ + onKeyEvent_: function(engineID, event) { + this.log_('onKeyEvent', engineID + ', ' + JSON.stringify(event)); + return this.processKey_(event.code, event.type); + }, + + /** + * Called when chrome ends the current text input session. + * @param {string} engineID Engine ID, should be 'braille'. + * @private + */ + onReset_: function(engineID) { + this.log_('onReset', engineID); + this.engineID_ = engineID; + this.sendToChromeVox_({type: 'reset'}); + }, + + /** + * Called by the IME framework when a menu item is activated. + * @param {string} engineID Engine ID, should be 'braille'. + * @param {string} itemID Identifies the menu item. + * @private + */ + onMenuItemActivated_: function(engineID, itemID) { + if (engineID === this.engineID_ && + itemID === this.USE_STANDARD_KEYBOARD_ID) { + this.useStandardKeyboard_ = !this.useStandardKeyboard_; + localStorage[this.USE_STANDARD_KEYBOARD_ID] = + String(this.useStandardKeyboard_); + if (!this.useStandardKeyboard_) { + this.accumulated_ = 0; + this.pressed_ = 0; + } + this.updateMenuItems_(); + } + }, + + /** + * Outputs a log message to the console, only if {@link BrailleIme.DEBUG} + * is set to true. + * @param {string} func Name of the caller. + * @param {string} message Message to output. + * @private + */ + log_: function(func, message) { + if (func === 'onKeyEvent') { + return; + } + if (this.DEBUG) { + console.log('BrailleIme.' + func + ': ' + message); + } + }, + + /** + * Handles a querty key on the home row as a braille key. + * @param {string} code Key code. + * @param {string} type Type of key event. + * @return {boolean} Whether the key event was handled or not. + * @private + */ + processKey_: function(code, type) { + if (!this.useStandardKeyboard_) { + return false; + } + var dot = this.CODE_TO_DOT_[code]; + if (!dot) { + this.pressed_ = 0; + this.accumulated_ = 0; + return false; + } + if (type === 'keydown') { + this.pressed_ |= dot; + this.accumulated_ |= this.pressed_; + return true; + } else if (type == 'keyup') { + this.pressed_ &= ~dot; + if (this.pressed_ == 0 && this.accumulated_ != 0) { + this.sendToChromeVox_({type: 'brailleDots', dots: this.accumulated_}); + this.accumulated_ = 0; + } + return true; + } + return false; + }, + + /** + * Connects to the ChromeVox extension for message passing. + * @private + */ + connectChromeVox_: function() { + if (this.port_) { + this.port_.disconnect(); + this.port_ = null; + } + this.port_ = chrome.runtime.connect( + this.CHROMEVOX_EXTENSION_ID_, {name: this.PORT_NAME}); + this.port_.onMessage.addListener( + this.onChromeVoxMessage_.bind(this)); + this.port_.onDisconnect.addListener( + this.onChromeVoxDisconnect_.bind(this)); + }, + + /** + * Handles a message from the ChromeVox extension. + * @param {*} message The message from the extension. + * @private + */ + onChromeVoxMessage_: function(message) { + this.log_('onChromeVoxMessage', JSON.stringify(message)); + message = /** @type {{type: string}} */ (message); + switch (message.type) { + case 'replaceText': + message = + /** + * @type {{contextID: number, deleteBefore: number, + * newText: string}} + */ + (message); + this.replaceText_(message.contextID, message.deleteBefore, + message.newText); + break; + } + }, + + /** + * Handles a disconnect event from the ChromeVox side. + * @private + */ + onChromeVoxDisconnect_: function() { + this.port_ = null; + this.log_('onChromeVoxDisconnect', + JSON.stringify(chrome.runtime.lastError)); + }, + + /** + * Sends a message to the ChromeVox extension. + * @param {Object} message The message to send. + * @private + */ + sendToChromeVox_: function(message) { + if (this.port_) { + this.port_.postMessage(message); + } + }, + + /** + * Sends the given input context to ChromeVox. + * @param {InputContext} context Input context, or null when there's no input + * context. + * @private + */ + sendInputContext_: function(context) { + this.sendToChromeVox_({type: 'inputContext', context: context}); + }, + + /** + * Sends the active state to ChromeVox. + * @private + */ + sendActiveState_: function() { + this.sendToChromeVox_({type: 'activeState', + active: this.engineID_.length > 0}); + }, + + /** + * Replaces text in the current text field. + * @param {number} contextID Context for the input field to replace the + * text in. + * @param {number} deleteBefore How many characters to delete before the + * cursor. + * @param {string} toInsert Text to insert at the cursor. + */ + replaceText_: function(contextID, deleteBefore, toInsert) { + var addText = function() { + chrome.input.ime.commitText( + {contextID: contextID, text: toInsert}); + }.bind(this); + if (deleteBefore > 0) { + var deleteText = function() { + chrome.input.ime.deleteSurroundingText( + {engineID: this.engineID_, contextID: contextID, + offset: -deleteBefore, length: deleteBefore}, addText); + }.bind(this); + // Make sure there's no non-zero length selection so that + // deleteSurroundingText works correctly. + chrome.input.ime.deleteSurroundingText( + {engineID: this.engineID_, contextID: contextID, + offset: 0, length: 0}, deleteText); + } else { + addText(); + } + }, + + /** + * Updates the menu items for this IME. + */ + updateMenuItems_: function() { + // TODO(plundblad): Localize when translations available. + chrome.input.ime.setMenuItems( + {engineID: this.engineID_, + items: [ + { + id: this.USE_STANDARD_KEYBOARD_ID, + label: 'Use standard keyboard for braille', + style: 'check', + visible: true, + checked: this.useStandardKeyboard_, + enabled: true + } + ] + }); + } +}; diff --git a/chrome/browser/resources/chromeos/braille_ime/braille_ime_unittest.gtestjs b/chrome/browser/resources/chromeos/braille_ime/braille_ime_unittest.gtestjs new file mode 100644 index 0000000..d011ee1 --- /dev/null +++ b/chrome/browser/resources/chromeos/braille_ime/braille_ime_unittest.gtestjs @@ -0,0 +1,216 @@ +// 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. + +/** + * @fileoverview Unit test for the Braille IME. + */ + +/** + * Mock Chrome event supporting one listener. + * @constructor + */ +function MockEvent() {} + +MockEvent.prototype = { + /** @type {Function?} */ + listener: null, + + /** + * @param {Function} listener + */ + addListener: function(listener) { + assertTrue(this.listener === null); + this.listener = listener; + }, + + /** + * Dispatches an event to the listener if any. + * @param {...*} var_args Arguments to pass to the event listener. + * @return {*} Return value from listener or {@code undefined} if no + * listener. + */ + dispatch: function() { + if (this.listener) { + return this.listener.apply(null, arguments); + } + } +}; + +/** + * Mock port that supports the {@code onMessage} and {@code onDisconnect} + * events as well as {@code postMessage}. + * @constructor. + */ +function MockPort() { + this.onMessage = new MockEvent(); + this.onDisconnect = new MockEvent(); + /** @type {Array.<Object>} */ + this.messages = []; +} + +MockPort.prototype = { + /** + * Stores {@code message} in this object. + * @param {Object} message Message to store. + */ + postMessage: function(message) { + this.messages.push(message); + } +}; + +/** + * Engine ID as specified in manifest. + * @const {string} + */ +ENGINE_ID = 'braille'; + +var localStorage; + +/** + * Test fixture for the braille IME unit test. + * @constructor + * @extends {testing.Test} + */ +function BrailleImeUnitTest() { + testing.Test.call(this); +} + +BrailleImeUnitTest.prototype = { + __proto__: testing.Test.prototype, + + /** @Override */ + extraLibraries: [ + 'braille_ime.js' + ], + + /** @Override */ + setUp: function() { + chrome = chrome || {}; + chrome.input = chrome.input || {}; + chrome.input.ime = chrome.input.ime || {}; + chrome.runtime = chrome.runtime || {}; + localStorage = {}; + this.createIme(); + }, + + createIme: function() { + var IME_EVENTS = [ 'onActivate', 'onDeactivated', 'onFocus', 'onBlur', + 'onInputContextUpdate', 'onKeyEvent', 'onReset', + 'onMenuItemActivated' ]; + for (var i = 0, name; name = IME_EVENTS[i]; ++i) { + this[name] = chrome.input.ime[name] = new MockEvent(); + + } + chrome.input.ime.setMenuItems = function(parameters) { + this.menuItems = parameters.items; + }.bind(this); + chrome.runtime.connect = function() { + this.port = new MockPort(); + return this.port; + }.bind(this); + this.menuItems = null; + this.port = null; + this.ime = new BrailleIme(); + this.ime.init(); + }, + + activateIme: function() { + this.onActivate.dispatch(ENGINE_ID); + assertThat(this.port.messages, + eqJSON([{type: 'activeState', active: true}])); + this.port.messages.length = 0; + }, + + sendKeyDown: function(code) { + return this.onKeyEvent.dispatch(ENGINE_ID, {code: code, type: 'keydown'}); + }, + + sendKeyUp: function(code) { + return this.onKeyEvent.dispatch(ENGINE_ID, {code: code, type: 'keyup'}); + }, +}; + +TEST_F('BrailleImeUnitTest', 'KeysWhenStandardKeyboardDisabled', function() { + this.activateIme(); + expectFalse(this.sendKeyDown('KeyF')); + expectFalse(this.sendKeyDown('KeyD')); + expectFalse(this.sendKeyUp('KeyD')); + expectFalse(this.sendKeyUp('KeyF')); + expectEquals(0, this.port.messages.length); +}); + +TEST_F('BrailleImeUnitTest', 'KeysWhenStandardKeysEnabled', function() { + this.activateIme(); + assertFalse(this.menuItems[0].checked); + this.onMenuItemActivated.dispatch(ENGINE_ID, this.menuItems[0].id); + assertTrue(this.menuItems[0].checked); + // Type the letters 'b' and 'c' and verify the right dots get sent. + expectTrue(this.sendKeyDown('KeyF')); + expectTrue(this.sendKeyDown('KeyD')); + expectTrue(this.sendKeyUp('KeyD')); + expectTrue(this.sendKeyUp('KeyF')); + expectTrue(this.sendKeyDown('KeyJ')); + expectTrue(this.sendKeyDown('KeyF')); + expectTrue(this.sendKeyUp('KeyJ')); + expectTrue(this.sendKeyUp('KeyF')); + // Make sure that other keys are not handled, either by themselves or while + // one of the 'braille keys' is pressed. + expectFalse(this.sendKeyDown('KeyX')); + expectFalse(this.sendKeyUp('KeyX')); + + expectTrue(this.sendKeyDown('KeyS')); // Dot 3 + expectFalse(this.sendKeyDown('KeyG')); // To the right of dot 1. + expectTrue(this.sendKeyUp('KeyS')); + expectFalse(this.sendKeyUp('KeyG')); + + assertThat(this.port.messages, + eqJSON([{type: 'brailleDots', dots: 0x03}, + {type: 'brailleDots', dots: 0x09}])); +}); + +TEST_F('BrailleImeUnitTest', 'UseStandardKeyboardSettingPreserved', function() { + this.activateIme(); + assertFalse(this.menuItems[0].checked); + this.onMenuItemActivated.dispatch(ENGINE_ID, this.menuItems[0].id); + assertTrue(this.menuItems[0].checked); + // Create a new instance and make sure the setting is still turned on. + console.log('localStorage: ' + JSON.stringify(localStorage)); + this.createIme(); + this.activateIme(); + assertTrue(this.menuItems[0].checked); +}); + +TEST_F('BrailleImeUnitTest', 'ReplaceText', function() { + var CONTEXT_ID = 1; + var hasSelection = false; + var text = 'Hi, '; + chrome.input.ime.commitText = function(params) { + assertEquals(CONTEXT_ID, params.contextID); + text += params.text; + }; + chrome.input.ime.deleteSurroundingText = function(params, callback) { + assertEquals(ENGINE_ID, params.engineID); + assertEquals(CONTEXT_ID, params.contextID); + assertEquals(0, params.offset + params.length); + if (hasSelection) { + assertEquals(0, params.length); + hasSelection = false; + } else { + text = text.slice(0, params.offset); + } + callback(); + }; + var sendReplaceText = function(deleteBefore, newText) { + this.port.onMessage.dispatch( + {type: 'replaceText', contextID: CONTEXT_ID, + deleteBefore: deleteBefore, newText: newText}); + }.bind(this); + this.activateIme(); + sendReplaceText(0, 'hello!'); + assertEquals('Hi, hello!', text); + hasSelection = true; + sendReplaceText('hello!'.length, 'good bye!'); + assertFalse(hasSelection); + assertEquals('Hi, good bye!', text); +}); diff --git a/chrome/browser/resources/chromeos/braille_ime/check_braille_ime.py b/chrome/browser/resources/chromeos/braille_ime/check_braille_ime.py new file mode 100755 index 0000000..9e72cb9 --- /dev/null +++ b/chrome/browser/resources/chromeos/braille_ime/check_braille_ime.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python + +# 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. + +'''Uses the closure compiler to check the braille ime.''' + +import os +import re +import subprocess +import sys +import tempfile + + +# Compiler path, relative to Chromium repository root. +_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +_CLOSURE_COMPILER_JAR = os.path.normpath( + os.path.join( + _SCRIPT_DIR, '../../../../..', + 'third_party/WebKit/Source/devtools/scripts/closure/compiler.jar')) +# List of compilation errors to enable with the --jscomp_errors flag. +_JSCOMP_ERRORS = [ 'accessControls', 'checkTypes', 'checkVars', 'invalidCasts', + 'missingProperties', 'undefinedNames', 'undefinedVars', + 'visibility' ] + +_java_executable = 'java' + + +def _error(msg): + print >>sys.stderr, msg + sys.exit(1) + + +def _execute_command(args, ignore_exit_status=False): + try: + return subprocess.check_output(args, stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as e: + if ignore_exit_status and e.returncode > 0: + return e.output + _error('%s\nCommand \'%s\' returned non-zero exit status %d' % + (e.output, ' '.join(e.cmd), e.returncode)) + except (OSError, IOError) as e: + _error('Error executing %s: %s' % (_java_executable, str(e))) + + +def _check_java(): + global _java_executable + java_home = os.environ.get('JAVAHOME') + if java_home is not None: + _java_executable = os.path.join(java_home, 'bin/java') + output = _execute_command([_java_executable, '-version']) + match = re.search(r'version "(?:\d+)\.(\d+)', output) + if match is None or int(match.group(1)) < 7: + _error('Java 7 or later is required: \n%s' % output) + + +def _run_closure_compiler(): + print 'Compiling Braille IME.' + args = [_java_executable, '-jar', _CLOSURE_COMPILER_JAR] + args.extend(['--compilation_level', 'SIMPLE_OPTIMIZATIONS']) + args.extend(['--jscomp_error=%s' % error for error in _JSCOMP_ERRORS]) + args.extend(['--externs', 'externs.js']) + args.extend(['--js', 'braille_ime.js']) + args.extend(['--js', 'main.js']) + args.extend(['--js_output_file', '/dev/null']) + output = _execute_command(args, ignore_exit_status=True) + success = len(output) == 0 + return success, output + + +def check_braille_ime(): + _check_java() + return _run_closure_compiler() + + +def main(argv): + success, output = check_braille_ime() + print output + return int(not success) + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/chrome/browser/resources/chromeos/braille_ime/externs.js b/chrome/browser/resources/chromeos/braille_ime/externs.js new file mode 100644 index 0000000..b67dd28 --- /dev/null +++ b/chrome/browser/resources/chromeos/braille_ime/externs.js @@ -0,0 +1,170 @@ +// 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. + +/** + * @fileoverview Externs for the braille IME. + * @externs + */ + +/** + * @const + */ +chrome.input = {}; + +/** @const */ +chrome.input.ime = {}; + +/** + * @constructor + */ +function ChromeInputImeOnKeyEventEvent() {} + +/** + * @param {function(string, !ChromeKeyboardEvent): (boolean|undefined)} callback + * @param {Array.<string>=} opt_extraInfoSpec + */ +ChromeInputImeOnKeyEventEvent.prototype.addListener = + function(callback, opt_extraInfoSpec) {}; + +/** + * @param {!Object.<string,(string|number)>} parameters An object with + * 'contextID' (number) and 'text' (string) keys. + * @param {function(boolean): void=} opt_callback Callback function. + */ +chrome.input.ime.commitText = function(parameters, opt_callback) {}; + +/** + * @param {!Object.<string,(string|number)>} parameters An object with + * 'contextID' (number) and 'text' (string) keys. + * @param {function(boolean): void=} opt_callback Callback function. + */ +chrome.input.ime.deleteSurroundingText = function(parameters, opt_callback) {}; + +/** + * @param {{engineID: string, items: Array.<chrome.input.ime.MenuItem>}} + * parameters + * @param {function()=} opt_callback + */ +chrome.input.ime.setMenuItems = function(parameters, opt_callback) {}; + +/** @type {!ChromeEvent} */ +chrome.input.ime.onActivate; + +/** @type {!ChromeEvent} */ +chrome.input.ime.onBlur; + +/** @type {!ChromeEvent} */ +chrome.input.ime.onDeactivated; + +/** @type {!ChromeEvent} */ +chrome.input.ime.onFocus; + +/** @type {!ChromeEvent} */ +chrome.input.ime.onInputContextUpdate; + +/** @type {!ChromeInputImeOnKeyEventEvent} */ +chrome.input.ime.onKeyEvent; + +/** @type {!ChromeEvent} */ +chrome.input.ime.onMenuItemActivated; + +/** @type {!ChromeEvent} */ +chrome.input.ime.onReset; + +/** + * @const + */ +chrome.runtime = {}; + +/** @type {!Object|undefined} */ +chrome.runtime.lastError = {}; + +/** + * @param {string|!Object.<string>=} opt_extensionIdOrConnectInfo Either the + * extensionId to connect to, in which case connectInfo params can be + * passed in the next optional argument, or the connectInfo params. + * @param {!Object.<string>=} opt_connectInfo The connectInfo object, + * if arg1 was the extensionId to connect to. + * @return {!Port} New port. + */ +chrome.runtime.connect = function( + opt_extensionIdOrConnectInfo, opt_connectInfo) {}; + +/** + * @constructor + */ +function Port() {} + +/** @type {ChromeEvent} */ +Port.prototype.onDisconnect; + +/** @type {ChromeEvent} */ +Port.prototype.onMessage; + +/** + * @param {Object.<string>} obj Message object. + */ +Port.prototype.postMessage = function(obj) {}; + +/** + * Note: as of 2012-04-12, this function is no longer documented on + * the public web pages, but there are still existing usages. + */ +Port.prototype.disconnect = function() {}; + +/** + * @constructor + */ +function ChromeEvent() {} + +/** @param {Function} callback */ +ChromeEvent.prototype.addListener = function(callback) {}; + +/** + * @constructor + */ +function ChromeKeyboardEvent() {} + +/** @type {string} */ +ChromeKeyboardEvent.prototype.type; + +/** @type {string} */ +ChromeKeyboardEvent.prototype.code; + +/** @type {boolean} */ +ChromeKeyboardEvent.prototype.altKey; + +/** @type {boolean} */ +ChromeKeyboardEvent.prototype.ctrlKey; + +/** @type {boolean} */ +ChromeKeyboardEvent.prototype.shiftKey; + +/** + * @constructor + */ +function InputContext() {} + +/** @type {number} */ +InputContext.prototype.contextID; + +/** @type {string} */ +InputContext.prototype.type; + +/** + * @typedef {{ + * id: string, + * label: (string|undefined), + * style: string, + * visible: (boolean|undefined), + * checked: (boolean|undefined), + * enabled: (boolean|undefined) + * }} + */ +chrome.input.ime.MenuItem; + +/** + * @type {Object} + */ +var localStorage; diff --git a/chrome/browser/resources/chromeos/braille_ime/main.js b/chrome/browser/resources/chromeos/braille_ime/main.js new file mode 100644 index 0000000..603ee28 --- /dev/null +++ b/chrome/browser/resources/chromeos/braille_ime/main.js @@ -0,0 +1,11 @@ +// 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. + +/** + * The Braille IME object. Attached to the window object for ease of + * debugging. + * @type {BrailleIme} + */ +window.ime = new BrailleIme(); +window.ime.init(); diff --git a/chrome/browser/resources/chromeos/braille_ime/manifest.json b/chrome/browser/resources/chromeos/braille_ime/manifest.json new file mode 100644 index 0000000..3a95dce --- /dev/null +++ b/chrome/browser/resources/chromeos/braille_ime/manifest.json @@ -0,0 +1,24 @@ +{ + "name": "Braille IME", + "description": "Braille Input Method Extension.", + "version": "1.0", + "background": { + "scripts": [ "braille_ime.js", "main.js" ], + "persistent": true + }, + // chrome-extension://jddehjeebkoimngcbdkaahpobgicbffp + "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCvDjqqYESDQe3OcI65JctUYLSlQ7RAd902VUw+RO/70fJ7SSkg8+2y+5paD6+g8f6wgFsgVsbTX2UM+tsmGKWR23bgSQxYhfZUZgP7qFdk72hGRUnKnXA+JOJ5maI4v+w18WPTWYOFJt2NOvat+GKKF0CAFQG+z2Ucn/sRZVfnrQIDAQAB", + "manifest_version": 2, + "permissions": [ + "input" + ], + "input_components": [ + { + "name": "Braille Keyboard", + "type": "ime", + "id": "braille", + "language": ["None"], + "description": "Braille hardware keyboard" + } + ] +} diff --git a/chrome/browser/resources/component_extension_resources.grd b/chrome/browser/resources/component_extension_resources.grd index b289b9b..d3d4d4e 100644 --- a/chrome/browser/resources/component_extension_resources.grd +++ b/chrome/browser/resources/component_extension_resources.grd @@ -26,6 +26,11 @@ <includes> <include name="IDR_NETWORK_SPEECH_SYNTHESIS_JS" file="network_speech_synthesis/tts_extension.js" type="BINDATA" /> + <if expr="chromeos"> + <include name="IDR_BRAILLE_IME_JS" file="chromeos/braille_ime/braille_ime.js" type="BINDATA" /> + <include name="IDR_BRAILLE_IME_MAIN_JS" file="chromeos/braille_ime/main.js" type="BINDATA" /> + </if> + <include name="IDR_BOOKMARK_MANAGER_BOOKMARK_MANAGER_SEARCH" file="bookmark_manager/images/bookmark_manager_search.png" type="BINDATA" /> <include name="IDR_BOOKMARK_MANAGER_BOOKMARK_MANAGER_SEARCH_RTL" file="bookmark_manager/images/bookmark_manager_search_rtl.png" type="BINDATA" /> <include name="IDR_BOOKMARK_MANAGER_BOOKMARK_MAIN_JS" file="bookmark_manager/js/main.js" type="BINDATA" /> diff --git a/chrome/chrome_resources.gyp b/chrome/chrome_resources.gyp index ea06883..c41896f 100644 --- a/chrome/chrome_resources.gyp +++ b/chrome/chrome_resources.gyp @@ -114,6 +114,14 @@ ], }], ['chromeos==1 and disable_nacl==0 and disable_nacl_untrusted==0', { + 'copies' : [ + { + 'destination': '<(PRODUCT_DIR)/resources/chromeos/braille_ime', + 'files': [ + 'browser/resources/chromeos/braille_ime/manifest.json', + ], + }, + ], 'dependencies': [ '../chrome/third_party/chromevox/chromevox.gyp:chromevox_resources', 'chromevox_strings', diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi index 9181b98..4066c4e 100644 --- a/chrome/chrome_tests_unit.gypi +++ b/chrome/chrome_tests_unit.gypi @@ -1200,6 +1200,8 @@ 'browser/renderer_host/plugin_info_message_filter_unittest.cc', 'browser/renderer_host/web_cache_manager_unittest.cc', 'browser/renderer_host/chrome_render_widget_host_view_mac_history_swiper_unit_test.mm', + 'browser/resources/chromeos/braille_ime/braille_ime.js', + 'browser/resources/chromeos/braille_ime/braille_ime_unittest.gtestjs', 'browser/resources/google_now/background.js', 'browser/resources/google_now/background_test_util.js', 'browser/resources/google_now/background_unittest.gtestjs', @@ -2300,6 +2302,7 @@ ['exclude', '^browser/ui/webui/chromeos/login'], ['exclude', '^browser/ui/webui/options/chromeos/'], ['exclude', '^browser/ui/webui/options/chromeos/'], + ['exclude', '^browser/resources/chromeos/'], ], 'sources!': [ 'browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api_unittest.cc', diff --git a/chrome/common/extensions/extension_constants.cc b/chrome/common/extensions/extension_constants.cc index 3b5105b..15a3fe4 100644 --- a/chrome/common/extensions/extension_constants.cc +++ b/chrome/common/extensions/extension_constants.cc @@ -133,6 +133,12 @@ const char kPlatformAppLaunchHistogram[] = "Apps.AppLaunch"; const char kChromeVoxExtensionId[] = "mndnfokpggljbaajbnioimlmbfngpief"; const char kChromeVoxExtensionPath[] = "chromeos/chromevox"; +const char kBrailleImeExtensionId[] = + "jddehjeebkoimngcbdkaahpobgicbffp"; +const char kBrailleImeExtensionPath[] = + "chromeos/braille_ime"; +const char kBrailleImeEngineId[] = + "_comp_ime_jddehjeebkoimngcbdkaahpobgicbffpbraille"; const char kConnectivityDiagnosticsPath[] = "/usr/share/chromeos-assets/connectivity_diagnostics"; const char kConnectivityDiagnosticsLauncherPath[] = diff --git a/chrome/common/extensions/extension_constants.h b/chrome/common/extensions/extension_constants.h index 73ea270..7a02bfe 100644 --- a/chrome/common/extensions/extension_constants.h +++ b/chrome/common/extensions/extension_constants.h @@ -240,6 +240,11 @@ extern const char kChromeVoxExtensionId[]; // Path to preinstalled ChromeVox screen reader extension (relative to // |chrome::DIR_RESOURCES|). extern const char kChromeVoxExtensionPath[]; +// Extension id, path (relative to |chrome::DIR_RESOURCES|) and IME engine +// id for the builtin-in Braille IME extension. +extern const char kBrailleImeExtensionId[]; +extern const char kBrailleImeExtensionPath[]; +extern const char kBrailleImeEngineId[]; // Path to preinstalled Connectivity Diagnostics extension. extern const char kConnectivityDiagnosticsPath[]; extern const char kConnectivityDiagnosticsLauncherPath[]; diff --git a/chrome/test/data/webui/test_api.js b/chrome/test/data/webui/test_api.js index 83c5eb8..c16f362 100644 --- a/chrome/test/data/webui/test_api.js +++ b/chrome/test/data/webui/test_api.js @@ -445,7 +445,9 @@ var testing = {}; try { this.setUp(); } catch(e) { - console.error(e.stack); + // Mock4JSException doesn't inherit from Error, so fall back on + // toString(). + console.error(e.stack || e.toString()); } if (!this.deferred_) |