summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorplundblad@chromium.org <plundblad@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-05-07 13:30:20 +0000
committerplundblad@chromium.org <plundblad@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-05-07 13:30:20 +0000
commit2e5e0b1b4fe9ecbc812ae56813376e600f35cb2b (patch)
treea1ebc0f019f68df6b66e1215931e598ac7007d4a
parentee83d8358c6bca47a642b1856af8c337738f5a57 (diff)
downloadchromium_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
-rw-r--r--chrome/app/chromeos_strings.grdp3
-rw-r--r--chrome/browser/chromeos/accessibility/accessibility_manager.cc59
-rw-r--r--chrome/browser/chromeos/accessibility/accessibility_manager.h7
-rw-r--r--chrome/browser/chromeos/accessibility/accessibility_manager_browsertest.cc92
-rw-r--r--chrome/browser/chromeos/input_method/component_extension_ime_manager_impl.cc14
-rw-r--r--chrome/browser/chromeos/input_method/input_method_util.cc7
-rw-r--r--chrome/browser/chromeos/system/tray_accessibility_browsertest.cc2
-rw-r--r--chrome/browser/extensions/api/braille_display_private/braille_controller.h5
-rw-r--r--chrome/browser/extensions/api/braille_display_private/braille_controller_brlapi.cc4
-rw-r--r--chrome/browser/extensions/api/braille_display_private/braille_display_private_api.cc5
-rw-r--r--chrome/browser/extensions/api/braille_display_private/braille_display_private_api.h4
-rw-r--r--chrome/browser/extensions/api/braille_display_private/braille_display_private_apitest.cc12
-rw-r--r--chrome/browser/extensions/api/braille_display_private/brlapi_connection.cc5
-rw-r--r--chrome/browser/resources/chromeos/braille_ime/OWNERS4
-rw-r--r--chrome/browser/resources/chromeos/braille_ime/PRESUBMIT.py25
-rw-r--r--chrome/browser/resources/chromeos/braille_ime/braille_ime.js413
-rw-r--r--chrome/browser/resources/chromeos/braille_ime/braille_ime_unittest.gtestjs216
-rwxr-xr-xchrome/browser/resources/chromeos/braille_ime/check_braille_ime.py84
-rw-r--r--chrome/browser/resources/chromeos/braille_ime/externs.js170
-rw-r--r--chrome/browser/resources/chromeos/braille_ime/main.js11
-rw-r--r--chrome/browser/resources/chromeos/braille_ime/manifest.json24
-rw-r--r--chrome/browser/resources/component_extension_resources.grd5
-rw-r--r--chrome/chrome_resources.gyp8
-rw-r--r--chrome/chrome_tests_unit.gypi3
-rw-r--r--chrome/common/extensions/extension_constants.cc6
-rw-r--r--chrome/common/extensions/extension_constants.h5
-rw-r--r--chrome/test/data/webui/test_api.js4
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_)