Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/chromeos/accessibility/accessibility_manager.h" | 5 #include "chrome/browser/chromeos/accessibility/accessibility_manager.h" |
| 6 | 6 |
| 7 #include <stddef.h> | 7 #include <stddef.h> |
| 8 #include <stdint.h> | 8 #include <stdint.h> |
| 9 | 9 |
| 10 #include <memory> | 10 #include <memory> |
| (...skipping 16 matching lines...) Expand all Loading... | |
| 27 #include "base/memory/singleton.h" | 27 #include "base/memory/singleton.h" |
| 28 #include "base/metrics/histogram_macros.h" | 28 #include "base/metrics/histogram_macros.h" |
| 29 #include "base/path_service.h" | 29 #include "base/path_service.h" |
| 30 #include "base/strings/string_split.h" | 30 #include "base/strings/string_split.h" |
| 31 #include "base/strings/string_util.h" | 31 #include "base/strings/string_util.h" |
| 32 #include "base/time/time.h" | 32 #include "base/time/time.h" |
| 33 #include "base/values.h" | 33 #include "base/values.h" |
| 34 #include "chrome/browser/accessibility/accessibility_extension_api.h" | 34 #include "chrome/browser/accessibility/accessibility_extension_api.h" |
| 35 #include "chrome/browser/browser_process.h" | 35 #include "chrome/browser/browser_process.h" |
| 36 #include "chrome/browser/chrome_notification_types.h" | 36 #include "chrome/browser/chrome_notification_types.h" |
| 37 #include "chrome/browser/chromeos/accessibility/accessibility_extension_loader.h " | |
| 37 #include "chrome/browser/chromeos/accessibility/accessibility_highlight_manager. h" | 38 #include "chrome/browser/chromeos/accessibility/accessibility_highlight_manager. h" |
| 38 #include "chrome/browser/chromeos/accessibility/magnification_manager.h" | 39 #include "chrome/browser/chromeos/accessibility/magnification_manager.h" |
| 39 #include "chrome/browser/chromeos/login/lock/screen_locker.h" | |
| 40 #include "chrome/browser/chromeos/login/ui/login_display_host.h" | |
| 41 #include "chrome/browser/chromeos/login/ui/webui_login_view.h" | |
| 42 #include "chrome/browser/chromeos/profiles/profile_helper.h" | 40 #include "chrome/browser/chromeos/profiles/profile_helper.h" |
| 43 #include "chrome/browser/chromeos/ui/accessibility_focus_ring_controller.h" | 41 #include "chrome/browser/chromeos/ui/accessibility_focus_ring_controller.h" |
| 44 #include "chrome/browser/extensions/api/braille_display_private/stub_braille_con troller.h" | 42 #include "chrome/browser/extensions/api/braille_display_private/stub_braille_con troller.h" |
| 45 #include "chrome/browser/extensions/component_loader.h" | |
| 46 #include "chrome/browser/extensions/extension_service.h" | 43 #include "chrome/browser/extensions/extension_service.h" |
| 47 #include "chrome/browser/extensions/tab_helper.h" | |
| 48 #include "chrome/browser/prefs/incognito_mode_prefs.h" | 44 #include "chrome/browser/prefs/incognito_mode_prefs.h" |
| 49 #include "chrome/browser/profiles/profile.h" | 45 #include "chrome/browser/profiles/profile.h" |
| 50 #include "chrome/browser/profiles/profile_manager.h" | 46 #include "chrome/browser/profiles/profile_manager.h" |
| 51 #include "chrome/common/chrome_paths.h" | 47 #include "chrome/common/chrome_paths.h" |
| 52 #include "chrome/common/extensions/api/accessibility_private.h" | 48 #include "chrome/common/extensions/api/accessibility_private.h" |
| 53 #include "chrome/common/extensions/extension_constants.h" | 49 #include "chrome/common/extensions/extension_constants.h" |
| 54 #include "chrome/common/extensions/manifest_handlers/content_scripts_handler.h" | |
| 55 #include "chrome/common/pref_names.h" | 50 #include "chrome/common/pref_names.h" |
| 56 #include "chrome/grit/browser_resources.h" | 51 #include "chrome/grit/browser_resources.h" |
| 57 #include "chromeos/audio/audio_a11y_controller.h" | 52 #include "chromeos/audio/audio_a11y_controller.h" |
| 58 #include "chromeos/audio/chromeos_sounds.h" | 53 #include "chromeos/audio/chromeos_sounds.h" |
| 59 #include "chromeos/login/login_state.h" | 54 #include "chromeos/login/login_state.h" |
| 60 #include "components/prefs/pref_member.h" | 55 #include "components/prefs/pref_member.h" |
| 61 #include "components/prefs/pref_service.h" | 56 #include "components/prefs/pref_service.h" |
| 62 #include "components/user_manager/user_manager.h" | 57 #include "components/user_manager/user_manager.h" |
| 63 #include "content/public/browser/browser_accessibility_state.h" | 58 #include "content/public/browser/browser_accessibility_state.h" |
| 64 #include "content/public/browser/browser_thread.h" | 59 #include "content/public/browser/browser_thread.h" |
| 65 #include "content/public/browser/notification_details.h" | 60 #include "content/public/browser/notification_details.h" |
| 66 #include "content/public/browser/notification_service.h" | 61 #include "content/public/browser/notification_service.h" |
| 67 #include "content/public/browser/notification_source.h" | 62 #include "content/public/browser/notification_source.h" |
| 68 #include "content/public/browser/render_process_host.h" | |
| 69 #include "content/public/browser/render_view_host.h" | 63 #include "content/public/browser/render_view_host.h" |
| 70 #include "content/public/browser/web_contents.h" | |
| 71 #include "content/public/browser/web_ui.h" | 64 #include "content/public/browser/web_ui.h" |
| 72 #include "content/public/common/content_switches.h" | 65 #include "content/public/common/content_switches.h" |
| 73 #include "extensions/browser/event_router.h" | 66 #include "extensions/browser/event_router.h" |
| 74 #include "extensions/browser/extension_api_frame_id_map.h" | |
| 75 #include "extensions/browser/extension_registry.h" | 67 #include "extensions/browser/extension_registry.h" |
| 76 #include "extensions/browser/extension_system.h" | 68 #include "extensions/browser/extension_system.h" |
| 77 #include "extensions/browser/file_reader.h" | |
| 78 #include "extensions/browser/script_executor.h" | |
| 79 #include "extensions/common/extension.h" | 69 #include "extensions/common/extension.h" |
| 80 #include "extensions/common/extension_messages.h" | 70 #include "extensions/common/extension_messages.h" |
| 81 #include "extensions/common/extension_resource.h" | 71 #include "extensions/common/extension_resource.h" |
| 82 #include "extensions/common/host_id.h" | 72 #include "extensions/common/host_id.h" |
| 83 #include "media/audio/sounds/sounds_manager.h" | 73 #include "media/audio/sounds/sounds_manager.h" |
| 84 #include "ui/base/ime/chromeos/input_method_manager.h" | 74 #include "ui/base/ime/chromeos/input_method_manager.h" |
| 85 #include "ui/base/resource/resource_bundle.h" | 75 #include "ui/base/resource/resource_bundle.h" |
| 86 #include "ui/keyboard/keyboard_controller.h" | 76 #include "ui/keyboard/keyboard_controller.h" |
| 87 #include "ui/keyboard/keyboard_util.h" | 77 #include "ui/keyboard/keyboard_util.h" |
| 88 | 78 |
| (...skipping 23 matching lines...) Expand all Loading... | |
| 112 if (g_braille_controller_for_test) | 102 if (g_braille_controller_for_test) |
| 113 return g_braille_controller_for_test; | 103 return g_braille_controller_for_test; |
| 114 // Don't use the real braille controller for tests to avoid automatically | 104 // Don't use the real braille controller for tests to avoid automatically |
| 115 // starting ChromeVox which confuses some tests. | 105 // starting ChromeVox which confuses some tests. |
| 116 base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); | 106 base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); |
| 117 if (command_line->HasSwitch(switches::kTestType)) | 107 if (command_line->HasSwitch(switches::kTestType)) |
| 118 return StubBrailleController::GetInstance(); | 108 return StubBrailleController::GetInstance(); |
| 119 return BrailleController::GetInstance(); | 109 return BrailleController::GetInstance(); |
| 120 } | 110 } |
| 121 | 111 |
| 122 base::FilePath GetChromeVoxPath() { | |
| 123 base::FilePath path; | |
| 124 if (!PathService::Get(chrome::DIR_RESOURCES, &path)) | |
| 125 NOTREACHED(); | |
| 126 path = path.Append(extension_misc::kChromeVoxExtensionPath); | |
| 127 return path; | |
| 128 } | |
| 129 | |
| 130 // Uses the ScriptExecutor associated with the given |render_view_host| to | |
| 131 // execute the given |code|. | |
| 132 void ExecuteScriptHelper( | |
| 133 content::RenderViewHost* render_view_host, | |
| 134 const std::string& code, | |
| 135 const std::string& extension_id) { | |
| 136 content::WebContents* web_contents = | |
| 137 content::WebContents::FromRenderViewHost(render_view_host); | |
| 138 if (!web_contents) | |
| 139 return; | |
| 140 if (!extensions::TabHelper::FromWebContents(web_contents)) | |
| 141 extensions::TabHelper::CreateForWebContents(web_contents); | |
| 142 extensions::TabHelper::FromWebContents(web_contents) | |
| 143 ->script_executor() | |
| 144 ->ExecuteScript(HostID(HostID::EXTENSIONS, extension_id), | |
| 145 extensions::ScriptExecutor::JAVASCRIPT, code, | |
| 146 extensions::ScriptExecutor::INCLUDE_SUB_FRAMES, | |
| 147 extensions::ExtensionApiFrameIdMap::kTopFrameId, | |
| 148 extensions::ScriptExecutor::DONT_MATCH_ABOUT_BLANK, | |
| 149 extensions::UserScript::DOCUMENT_IDLE, | |
| 150 extensions::ScriptExecutor::ISOLATED_WORLD, | |
| 151 extensions::ScriptExecutor::DEFAULT_PROCESS, | |
| 152 GURL(), // No webview src. | |
| 153 GURL(), // No file url. | |
| 154 false, // Not user gesture. | |
| 155 extensions::ScriptExecutor::NO_RESULT, | |
| 156 extensions::ScriptExecutor::ExecuteScriptCallback()); | |
| 157 } | |
| 158 | |
| 159 // Helper class that directly loads an extension's content scripts into | |
| 160 // all of the frames corresponding to a given RenderViewHost. | |
| 161 class ContentScriptLoader { | |
| 162 public: | |
| 163 // Initialize the ContentScriptLoader with the ID of the extension | |
| 164 // and the RenderViewHost where the scripts should be loaded. | |
| 165 ContentScriptLoader(const std::string& extension_id, | |
| 166 int render_process_id, | |
| 167 int render_view_id) | |
| 168 : extension_id_(extension_id), | |
| 169 render_process_id_(render_process_id), | |
| 170 render_view_id_(render_view_id) {} | |
| 171 | |
| 172 // Call this once with the ExtensionResource corresponding to each | |
| 173 // content script to be loaded. | |
| 174 void AppendScript(extensions::ExtensionResource resource) { | |
| 175 resources_.push(resource); | |
| 176 } | |
| 177 | |
| 178 // Finally, call this method once to fetch all of the resources and | |
| 179 // load them. This method will delete this object when done. | |
| 180 void Run() { | |
| 181 if (resources_.empty()) { | |
| 182 delete this; | |
| 183 return; | |
| 184 } | |
| 185 | |
| 186 extensions::ExtensionResource resource = resources_.front(); | |
| 187 resources_.pop(); | |
| 188 scoped_refptr<FileReader> reader(new FileReader(resource, base::Bind( | |
| 189 &ContentScriptLoader::OnFileLoaded, base::Unretained(this)))); | |
| 190 reader->Start(); | |
| 191 } | |
| 192 | |
| 193 private: | |
| 194 void OnFileLoaded(bool success, std::unique_ptr<std::string> data) { | |
| 195 if (success) { | |
| 196 RenderViewHost* render_view_host = | |
| 197 RenderViewHost::FromID(render_process_id_, render_view_id_); | |
| 198 if (render_view_host) | |
| 199 ExecuteScriptHelper(render_view_host, *data, extension_id_); | |
| 200 } | |
| 201 Run(); | |
| 202 } | |
| 203 | |
| 204 std::string extension_id_; | |
| 205 int render_process_id_; | |
| 206 int render_view_id_; | |
| 207 std::queue<extensions::ExtensionResource> resources_; | |
| 208 }; | |
| 209 | |
| 210 void InjectChromeVoxContentScript( | |
| 211 ExtensionService* extension_service, | |
| 212 int render_process_id, | |
| 213 int render_view_id, | |
| 214 const base::Closure& done_cb); | |
| 215 | |
| 216 void AddChromeVoxExtensionToComponentLoader( | |
| 217 extensions::ComponentLoader* component_loader, | |
| 218 base::Closure done_cb) { | |
| 219 component_loader->AddComponentFromDir( | |
| 220 GetChromeVoxPath(), extension_misc::kChromeVoxExtensionId, done_cb); | |
| 221 } | |
| 222 | |
| 223 void LoadChromeVoxExtension( | |
| 224 Profile* profile, | |
| 225 RenderViewHost* render_view_host, | |
| 226 base::Closure done_cb) { | |
| 227 ExtensionService* extension_service = | |
| 228 extensions::ExtensionSystem::Get(profile)->extension_service(); | |
| 229 if (render_view_host) { | |
| 230 // Wrap the passed in callback to inject the content script. | |
| 231 done_cb = base::Bind( | |
| 232 &InjectChromeVoxContentScript, | |
| 233 extension_service, | |
| 234 render_view_host->GetProcess()->GetID(), | |
| 235 render_view_host->GetRoutingID(), | |
| 236 done_cb); | |
| 237 } | |
| 238 | |
| 239 AddChromeVoxExtensionToComponentLoader(extension_service->component_loader(), | |
| 240 done_cb); | |
| 241 } | |
| 242 | |
| 243 void InjectChromeVoxContentScript( | |
| 244 ExtensionService* extension_service, | |
| 245 int render_process_id, | |
| 246 int render_view_id, | |
| 247 const base::Closure& done_cb) { | |
| 248 // Make sure to always run |done_cb|. ChromeVox was loaded even if we end up | |
| 249 // not injecting into this particular render view. | |
| 250 base::ScopedClosureRunner done_runner(done_cb); | |
| 251 RenderViewHost* render_view_host = | |
| 252 RenderViewHost::FromID(render_process_id, render_view_id); | |
| 253 if (!render_view_host) | |
| 254 return; | |
| 255 const content::WebContents* web_contents = | |
| 256 content::WebContents::FromRenderViewHost(render_view_host); | |
| 257 GURL content_url; | |
| 258 if (web_contents) | |
| 259 content_url = web_contents->GetLastCommittedURL(); | |
| 260 const extensions::Extension* extension = | |
| 261 extensions::ExtensionRegistry::Get(extension_service->profile()) | |
| 262 ->enabled_extensions() | |
| 263 .GetByID(extension_misc::kChromeVoxExtensionId); | |
| 264 | |
| 265 // Set a flag to tell ChromeVox that it's just been enabled, | |
| 266 // so that it won't interrupt our speech feedback enabled message. | |
| 267 ExecuteScriptHelper(render_view_host, "window.INJECTED_AFTER_LOAD = true;", | |
| 268 extension->id()); | |
| 269 | |
| 270 // Inject ChromeVox' content scripts. | |
| 271 ContentScriptLoader* loader = new ContentScriptLoader( | |
| 272 extension->id(), render_view_host->GetProcess()->GetID(), | |
| 273 render_view_host->GetRoutingID()); | |
| 274 | |
| 275 const extensions::UserScriptList& content_scripts = | |
| 276 extensions::ContentScriptsInfo::GetContentScripts(extension); | |
| 277 for (const std::unique_ptr<extensions::UserScript>& script : | |
| 278 content_scripts) { | |
| 279 if (web_contents && !script->MatchesURL(content_url)) | |
| 280 continue; | |
| 281 for (const std::unique_ptr<extensions::UserScript::File>& file : | |
| 282 script->js_scripts()) { | |
| 283 extensions::ExtensionResource resource = | |
| 284 extension->GetResource(file->relative_path()); | |
| 285 loader->AppendScript(resource); | |
| 286 } | |
| 287 } | |
| 288 loader->Run(); // It cleans itself up when done. | |
| 289 } | |
| 290 | |
| 291 void UnloadChromeVoxExtension(Profile* profile) { | |
| 292 base::FilePath path = GetChromeVoxPath(); | |
| 293 ExtensionService* extension_service = | |
| 294 extensions::ExtensionSystem::Get(profile)->extension_service(); | |
| 295 extension_service->component_loader()->Remove(path); | |
| 296 } | |
| 297 | |
| 298 } // namespace | 112 } // namespace |
| 299 | 113 |
| 300 class ChromeVoxPanelWidgetObserver : public views::WidgetObserver { | 114 class ChromeVoxPanelWidgetObserver : public views::WidgetObserver { |
| 301 public: | 115 public: |
| 302 ChromeVoxPanelWidgetObserver(views::Widget* widget, | 116 ChromeVoxPanelWidgetObserver(views::Widget* widget, |
| 303 AccessibilityManager* manager) | 117 AccessibilityManager* manager) |
| 304 : widget_(widget), manager_(manager) { | 118 : widget_(widget), manager_(manager) { |
| 305 widget_->AddObserver(this); | 119 widget_->AddObserver(this); |
| 306 } | 120 } |
| 307 | 121 |
| (...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 402 g_accessibility_manager = NULL; | 216 g_accessibility_manager = NULL; |
| 403 } | 217 } |
| 404 | 218 |
| 405 // static | 219 // static |
| 406 AccessibilityManager* AccessibilityManager::Get() { | 220 AccessibilityManager* AccessibilityManager::Get() { |
| 407 return g_accessibility_manager; | 221 return g_accessibility_manager; |
| 408 } | 222 } |
| 409 | 223 |
| 410 AccessibilityManager::AccessibilityManager() | 224 AccessibilityManager::AccessibilityManager() |
| 411 : profile_(NULL), | 225 : profile_(NULL), |
| 412 chrome_vox_loaded_on_lock_screen_(false), | |
| 413 chrome_vox_loaded_on_user_screen_(false), | |
| 414 large_cursor_pref_handler_(prefs::kAccessibilityLargeCursorEnabled), | 226 large_cursor_pref_handler_(prefs::kAccessibilityLargeCursorEnabled), |
| 415 spoken_feedback_pref_handler_(prefs::kAccessibilitySpokenFeedbackEnabled), | 227 spoken_feedback_pref_handler_(prefs::kAccessibilitySpokenFeedbackEnabled), |
| 416 high_contrast_pref_handler_(prefs::kAccessibilityHighContrastEnabled), | 228 high_contrast_pref_handler_(prefs::kAccessibilityHighContrastEnabled), |
| 417 autoclick_pref_handler_(prefs::kAccessibilityAutoclickEnabled), | 229 autoclick_pref_handler_(prefs::kAccessibilityAutoclickEnabled), |
| 418 autoclick_delay_pref_handler_(prefs::kAccessibilityAutoclickDelayMs), | 230 autoclick_delay_pref_handler_(prefs::kAccessibilityAutoclickDelayMs), |
| 419 virtual_keyboard_pref_handler_( | 231 virtual_keyboard_pref_handler_( |
| 420 prefs::kAccessibilityVirtualKeyboardEnabled), | 232 prefs::kAccessibilityVirtualKeyboardEnabled), |
| 421 mono_audio_pref_handler_(prefs::kAccessibilityMonoAudioEnabled), | 233 mono_audio_pref_handler_(prefs::kAccessibilityMonoAudioEnabled), |
| 422 caret_highlight_pref_handler_(prefs::kAccessibilityCaretHighlightEnabled), | 234 caret_highlight_pref_handler_(prefs::kAccessibilityCaretHighlightEnabled), |
| 423 cursor_highlight_pref_handler_( | 235 cursor_highlight_pref_handler_( |
| (...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 471 bundle.GetRawDataResource(IDR_SOUND_SPOKEN_FEEDBACK_ENABLED_WAV)); | 283 bundle.GetRawDataResource(IDR_SOUND_SPOKEN_FEEDBACK_ENABLED_WAV)); |
| 472 manager->Initialize( | 284 manager->Initialize( |
| 473 SOUND_SPOKEN_FEEDBACK_DISABLED, | 285 SOUND_SPOKEN_FEEDBACK_DISABLED, |
| 474 bundle.GetRawDataResource(IDR_SOUND_SPOKEN_FEEDBACK_DISABLED_WAV)); | 286 bundle.GetRawDataResource(IDR_SOUND_SPOKEN_FEEDBACK_DISABLED_WAV)); |
| 475 manager->Initialize(SOUND_PASSTHROUGH, | 287 manager->Initialize(SOUND_PASSTHROUGH, |
| 476 bundle.GetRawDataResource(IDR_SOUND_PASSTHROUGH_WAV)); | 288 bundle.GetRawDataResource(IDR_SOUND_PASSTHROUGH_WAV)); |
| 477 manager->Initialize(SOUND_EXIT_SCREEN, | 289 manager->Initialize(SOUND_EXIT_SCREEN, |
| 478 bundle.GetRawDataResource(IDR_SOUND_EXIT_SCREEN_WAV)); | 290 bundle.GetRawDataResource(IDR_SOUND_EXIT_SCREEN_WAV)); |
| 479 manager->Initialize(SOUND_ENTER_SCREEN, | 291 manager->Initialize(SOUND_ENTER_SCREEN, |
| 480 bundle.GetRawDataResource(IDR_SOUND_ENTER_SCREEN_WAV)); | 292 bundle.GetRawDataResource(IDR_SOUND_ENTER_SCREEN_WAV)); |
| 293 | |
| 294 base::FilePath resources_path; | |
| 295 if (!PathService::Get(chrome::DIR_RESOURCES, &resources_path)) | |
| 296 NOTREACHED(); | |
| 297 chromevox_loader_ = base::WrapUnique(new AccessibilityExtensionLoader( | |
| 298 extension_misc::kChromeVoxExtensionId, | |
| 299 resources_path.Append(extension_misc::kChromeVoxExtensionPath))); | |
| 481 } | 300 } |
| 482 | 301 |
| 483 AccessibilityManager::~AccessibilityManager() { | 302 AccessibilityManager::~AccessibilityManager() { |
| 484 CHECK(this == g_accessibility_manager); | 303 CHECK(this == g_accessibility_manager); |
| 485 AccessibilityStatusEventDetails details(ACCESSIBILITY_MANAGER_SHUTDOWN, false, | 304 AccessibilityStatusEventDetails details(ACCESSIBILITY_MANAGER_SHUTDOWN, false, |
| 486 ash::A11Y_NOTIFICATION_NONE); | 305 ash::A11Y_NOTIFICATION_NONE); |
| 487 NotifyAccessibilityStatusChanged(details); | 306 NotifyAccessibilityStatusChanged(details); |
| 488 input_method::InputMethodManager::Get()->RemoveObserver(this); | 307 input_method::InputMethodManager::Get()->RemoveObserver(this); |
| 489 | 308 |
| 490 if (chromevox_panel_) { | 309 if (chromevox_panel_) { |
| (...skipping 124 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 615 | 434 |
| 616 spoken_feedback_notification_ = ash::A11Y_NOTIFICATION_NONE; | 435 spoken_feedback_notification_ = ash::A11Y_NOTIFICATION_NONE; |
| 617 } | 436 } |
| 618 | 437 |
| 619 void AccessibilityManager::UpdateSpokenFeedbackFromPref() { | 438 void AccessibilityManager::UpdateSpokenFeedbackFromPref() { |
| 620 if (!profile_) | 439 if (!profile_) |
| 621 return; | 440 return; |
| 622 | 441 |
| 623 const bool enabled = profile_->GetPrefs()->GetBoolean( | 442 const bool enabled = profile_->GetPrefs()->GetBoolean( |
| 624 prefs::kAccessibilitySpokenFeedbackEnabled); | 443 prefs::kAccessibilitySpokenFeedbackEnabled); |
| 625 | |
| 626 // If ChromeVox was already enabled, but not for this profile, | |
| 627 // add it to this profile. | |
| 628 auto* extension_service = | |
| 629 extensions::ExtensionSystem::Get(profile_)->extension_service(); | |
| 630 auto* component_loader = extension_service->component_loader(); | |
| 631 bool is_already_loaded = | |
| 632 component_loader->Exists(extension_misc::kChromeVoxExtensionId); | |
| 633 if (spoken_feedback_enabled_ && enabled && !is_already_loaded) | |
| 634 AddChromeVoxExtensionToComponentLoader(component_loader, base::Closure()); | |
| 635 | |
| 636 if (spoken_feedback_enabled_ == enabled) | 444 if (spoken_feedback_enabled_ == enabled) |
| 637 return; | 445 return; |
| 638 | 446 |
| 639 spoken_feedback_enabled_ = enabled; | 447 spoken_feedback_enabled_ = enabled; |
| 640 | 448 |
| 641 AccessibilityStatusEventDetails details( | 449 AccessibilityStatusEventDetails details( |
| 642 ACCESSIBILITY_TOGGLE_SPOKEN_FEEDBACK, | 450 ACCESSIBILITY_TOGGLE_SPOKEN_FEEDBACK, |
| 643 enabled, | 451 enabled, |
| 644 spoken_feedback_notification_); | 452 spoken_feedback_notification_); |
| 645 | 453 |
| 646 NotifyAccessibilityStatusChanged(details); | 454 NotifyAccessibilityStatusChanged(details); |
| 647 | 455 |
| 648 if (enabled) { | 456 if (enabled) { |
| 649 LoadChromeVox(); | 457 chromevox_loader_->Load(profile_, "window.INJECTED_AFTER_LOAD = true;", |
| 458 base::Bind(&AccessibilityManager::PostLoadChromeVox, | |
| 459 weak_ptr_factory_.GetWeakPtr())); | |
| 460 | |
|
David Tseng
2016/09/08 21:18:42
Remove
dmazzoni
2016/09/13 23:03:19
Done
| |
| 650 } else { | 461 } else { |
| 651 UnloadChromeVox(); | 462 chromevox_loader_->Unload(); |
| 652 } | 463 } |
| 653 UpdateBrailleImeState(); | 464 UpdateBrailleImeState(); |
| 654 | 465 |
| 655 // ChromeVox focus highlighting overrides the other focus highlighting. | 466 // ChromeVox focus highlighting overrides the other focus highlighting. |
| 656 UpdateFocusHighlightFromPref(); | 467 UpdateFocusHighlightFromPref(); |
| 657 } | 468 } |
| 658 | 469 |
| 659 void AccessibilityManager::LoadChromeVox() { | |
| 660 base::Closure done_cb = base::Bind(&AccessibilityManager::PostLoadChromeVox, | |
| 661 weak_ptr_factory_.GetWeakPtr(), | |
| 662 profile_); | |
| 663 ScreenLocker* screen_locker = ScreenLocker::default_screen_locker(); | |
| 664 if (screen_locker && screen_locker->locked()) { | |
| 665 // If on the lock screen, loads ChromeVox only to the lock screen as for | |
| 666 // now. On unlock, it will be loaded to the user screen. | |
| 667 // (see. AccessibilityManager::Observe()) | |
| 668 LoadChromeVoxToLockScreen(done_cb); | |
| 669 } else { | |
| 670 LoadChromeVoxToUserScreen(done_cb); | |
| 671 } | |
| 672 } | |
| 673 | |
| 674 void AccessibilityManager::LoadChromeVoxToUserScreen( | |
| 675 const base::Closure& done_cb) { | |
| 676 if (chrome_vox_loaded_on_user_screen_) | |
| 677 return; | |
| 678 | |
| 679 // Determine whether an OOBE screen is currently being shown. If so, | |
| 680 // ChromeVox will be injected directly into that screen. | |
| 681 content::WebUI* login_web_ui = NULL; | |
| 682 | |
| 683 if (ProfileHelper::IsSigninProfile(profile_)) { | |
| 684 LoginDisplayHost* login_display_host = LoginDisplayHost::default_host(); | |
| 685 if (login_display_host) { | |
| 686 WebUILoginView* web_ui_login_view = | |
| 687 login_display_host->GetWebUILoginView(); | |
| 688 if (web_ui_login_view) | |
| 689 login_web_ui = web_ui_login_view->GetWebUI(); | |
| 690 } | |
| 691 | |
| 692 // Lock screen uses the signin progile. | |
| 693 chrome_vox_loaded_on_lock_screen_ = true; | |
| 694 } | |
| 695 | |
| 696 chrome_vox_loaded_on_user_screen_ = true; | |
| 697 LoadChromeVoxExtension( | |
| 698 profile_, login_web_ui ? | |
| 699 login_web_ui->GetWebContents()->GetRenderViewHost() : NULL, | |
| 700 done_cb); | |
| 701 } | |
| 702 | |
| 703 void AccessibilityManager::LoadChromeVoxToLockScreen( | |
| 704 const base::Closure& done_cb) { | |
| 705 if (chrome_vox_loaded_on_lock_screen_) | |
| 706 return; | |
| 707 | |
| 708 ScreenLocker* screen_locker = ScreenLocker::default_screen_locker(); | |
| 709 if (screen_locker && screen_locker->locked()) { | |
| 710 content::WebUI* lock_web_ui = screen_locker->GetAssociatedWebUI(); | |
| 711 if (lock_web_ui) { | |
| 712 Profile* profile = Profile::FromWebUI(lock_web_ui); | |
| 713 chrome_vox_loaded_on_lock_screen_ = true; | |
| 714 LoadChromeVoxExtension( | |
| 715 profile, | |
| 716 lock_web_ui->GetWebContents()->GetRenderViewHost(), | |
| 717 done_cb); | |
| 718 } | |
| 719 } | |
| 720 } | |
| 721 | |
| 722 void AccessibilityManager::UnloadChromeVox() { | |
| 723 if (chromevox_panel_) { | |
| 724 chromevox_panel_->Close(); | |
| 725 chromevox_panel_ = nullptr; | |
| 726 } | |
| 727 | |
| 728 if (chrome_vox_loaded_on_lock_screen_) | |
| 729 UnloadChromeVoxFromLockScreen(); | |
| 730 | |
| 731 if (chrome_vox_loaded_on_user_screen_) { | |
| 732 UnloadChromeVoxExtension(profile_); | |
| 733 chrome_vox_loaded_on_user_screen_ = false; | |
| 734 } | |
| 735 | |
| 736 PostUnloadChromeVox(profile_); | |
| 737 } | |
| 738 | |
| 739 void AccessibilityManager::UnloadChromeVoxFromLockScreen() { | |
| 740 // Lock screen uses the signin progile. | |
| 741 Profile* signin_profile = ProfileHelper::GetSigninProfile(); | |
| 742 UnloadChromeVoxExtension(signin_profile); | |
| 743 chrome_vox_loaded_on_lock_screen_ = false; | |
| 744 } | |
| 745 | |
| 746 bool AccessibilityManager::IsSpokenFeedbackEnabled() { | 470 bool AccessibilityManager::IsSpokenFeedbackEnabled() { |
| 747 return spoken_feedback_enabled_; | 471 return spoken_feedback_enabled_; |
| 748 } | 472 } |
| 749 | 473 |
| 750 void AccessibilityManager::ToggleSpokenFeedback( | 474 void AccessibilityManager::ToggleSpokenFeedback( |
| 751 ash::AccessibilityNotificationVisibility notify) { | 475 ash::AccessibilityNotificationVisibility notify) { |
| 752 EnableSpokenFeedback(!IsSpokenFeedbackEnabled(), notify); | 476 EnableSpokenFeedback(!IsSpokenFeedbackEnabled(), notify); |
| 753 } | 477 } |
| 754 | 478 |
| 755 void AccessibilityManager::EnableHighContrast(bool enabled) { | 479 void AccessibilityManager::EnableHighContrast(bool enabled) { |
| (...skipping 501 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1257 local_state_pref_change_registrar_->Init(g_browser_process->local_state()); | 981 local_state_pref_change_registrar_->Init(g_browser_process->local_state()); |
| 1258 local_state_pref_change_registrar_->Add( | 982 local_state_pref_change_registrar_->Add( |
| 1259 prefs::kApplicationLocale, | 983 prefs::kApplicationLocale, |
| 1260 base::Bind(&AccessibilityManager::OnLocaleChanged, | 984 base::Bind(&AccessibilityManager::OnLocaleChanged, |
| 1261 base::Unretained(this))); | 985 base::Unretained(this))); |
| 1262 | 986 |
| 1263 content::BrowserAccessibilityState::GetInstance()->AddHistogramCallback( | 987 content::BrowserAccessibilityState::GetInstance()->AddHistogramCallback( |
| 1264 base::Bind( | 988 base::Bind( |
| 1265 &AccessibilityManager::UpdateChromeOSAccessibilityHistograms, | 989 &AccessibilityManager::UpdateChromeOSAccessibilityHistograms, |
| 1266 base::Unretained(this))); | 990 base::Unretained(this))); |
| 991 | |
| 992 chromevox_loader_->SetProfile(profile); | |
| 993 | |
| 994 extensions::ExtensionRegistry* registry = | |
| 995 extensions::ExtensionRegistry::Get(profile); | |
| 996 if (!extension_registry_observer_.IsObserving(registry)) | |
| 997 extension_registry_observer_.Add(registry); | |
| 1267 } | 998 } |
| 1268 | 999 |
| 1269 large_cursor_pref_handler_.HandleProfileChanged(profile_, profile); | 1000 large_cursor_pref_handler_.HandleProfileChanged(profile_, profile); |
| 1270 spoken_feedback_pref_handler_.HandleProfileChanged(profile_, profile); | 1001 spoken_feedback_pref_handler_.HandleProfileChanged(profile_, profile); |
| 1271 high_contrast_pref_handler_.HandleProfileChanged(profile_, profile); | 1002 high_contrast_pref_handler_.HandleProfileChanged(profile_, profile); |
| 1272 autoclick_pref_handler_.HandleProfileChanged(profile_, profile); | 1003 autoclick_pref_handler_.HandleProfileChanged(profile_, profile); |
| 1273 autoclick_delay_pref_handler_.HandleProfileChanged(profile_, profile); | 1004 autoclick_delay_pref_handler_.HandleProfileChanged(profile_, profile); |
| 1274 virtual_keyboard_pref_handler_.HandleProfileChanged(profile_, profile); | 1005 virtual_keyboard_pref_handler_.HandleProfileChanged(profile_, profile); |
| 1275 mono_audio_pref_handler_.HandleProfileChanged(profile_, profile); | 1006 mono_audio_pref_handler_.HandleProfileChanged(profile_, profile); |
| 1276 caret_highlight_pref_handler_.HandleProfileChanged(profile_, profile); | 1007 caret_highlight_pref_handler_.HandleProfileChanged(profile_, profile); |
| (...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1337 base::TimeDelta AccessibilityManager::PlayShutdownSound() { | 1068 base::TimeDelta AccessibilityManager::PlayShutdownSound() { |
| 1338 if (!system_sounds_enabled_) | 1069 if (!system_sounds_enabled_) |
| 1339 return base::TimeDelta(); | 1070 return base::TimeDelta(); |
| 1340 system_sounds_enabled_ = false; | 1071 system_sounds_enabled_ = false; |
| 1341 if (!PlayEarcon(SOUND_SHUTDOWN, PlaySoundOption::SPOKEN_FEEDBACK_ENABLED)) | 1072 if (!PlayEarcon(SOUND_SHUTDOWN, PlaySoundOption::SPOKEN_FEEDBACK_ENABLED)) |
| 1342 return base::TimeDelta(); | 1073 return base::TimeDelta(); |
| 1343 return media::SoundsManager::Get()->GetDuration(SOUND_SHUTDOWN); | 1074 return media::SoundsManager::Get()->GetDuration(SOUND_SHUTDOWN); |
| 1344 } | 1075 } |
| 1345 | 1076 |
| 1346 void AccessibilityManager::InjectChromeVox(RenderViewHost* render_view_host) { | 1077 void AccessibilityManager::InjectChromeVox(RenderViewHost* render_view_host) { |
| 1347 LoadChromeVoxExtension(profile_, render_view_host, base::Closure()); | 1078 chromevox_loader_->InjectContentScript(render_view_host); |
|
David Tseng
2016/09/08 20:39:31
This doesn't look equivalent.
dmazzoni
2016/09/08 21:00:55
You're right, but I think it was doing extra unnec
David Tseng
2016/09/08 21:18:42
Same comment here -- if you're sure, then that's f
dmazzoni
2016/09/13 23:03:19
Makes sense.
Done.
| |
| 1348 } | 1079 } |
| 1349 | 1080 |
| 1350 std::unique_ptr<AccessibilityStatusSubscription> | 1081 std::unique_ptr<AccessibilityStatusSubscription> |
| 1351 AccessibilityManager::RegisterCallback(const AccessibilityStatusCallback& cb) { | 1082 AccessibilityManager::RegisterCallback(const AccessibilityStatusCallback& cb) { |
| 1352 return callback_list_.Add(cb); | 1083 return callback_list_.Add(cb); |
| 1353 } | 1084 } |
| 1354 | 1085 |
| 1355 void AccessibilityManager::NotifyAccessibilityStatusChanged( | 1086 void AccessibilityManager::NotifyAccessibilityStatusChanged( |
| 1356 AccessibilityStatusEventDetails& details) { | 1087 AccessibilityStatusEventDetails& details) { |
| 1357 callback_list_.Notify(details); | 1088 callback_list_.Notify(details); |
| (...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1437 // Update |profile_| when exiting a session or shutting down. | 1168 // Update |profile_| when exiting a session or shutting down. |
| 1438 Profile* profile = content::Source<Profile>(source).ptr(); | 1169 Profile* profile = content::Source<Profile>(source).ptr(); |
| 1439 if (profile_ == profile) | 1170 if (profile_ == profile) |
| 1440 SetProfile(NULL); | 1171 SetProfile(NULL); |
| 1441 break; | 1172 break; |
| 1442 } | 1173 } |
| 1443 case chrome::NOTIFICATION_SCREEN_LOCK_STATE_CHANGED: { | 1174 case chrome::NOTIFICATION_SCREEN_LOCK_STATE_CHANGED: { |
| 1444 bool is_screen_locked = *content::Details<bool>(details).ptr(); | 1175 bool is_screen_locked = *content::Details<bool>(details).ptr(); |
| 1445 if (spoken_feedback_enabled_) { | 1176 if (spoken_feedback_enabled_) { |
| 1446 if (is_screen_locked) | 1177 if (is_screen_locked) |
| 1447 LoadChromeVoxToLockScreen(base::Closure()); | 1178 chromevox_loader_->LoadToLockScreen(base::Closure()); |
| 1448 // If spoken feedback was enabled, make sure it is also enabled on | 1179 // If spoken feedback was enabled, make sure it is also enabled on |
| 1449 // the user screen. | 1180 // the user screen. |
| 1450 // The status tray gets verbalized by user screen ChromeVox, so we need | 1181 // The status tray gets verbalized by user screen ChromeVox, so we need |
| 1451 // to load it on the user screen even if the screen is locked. | 1182 // to load it on the user screen even if the screen is locked. |
| 1452 LoadChromeVoxToUserScreen(base::Closure()); | 1183 chromevox_loader_->LoadToUserScreen(base::Closure()); |
| 1453 } | 1184 } |
| 1454 break; | 1185 break; |
| 1455 } | 1186 } |
| 1456 } | 1187 } |
| 1457 } | 1188 } |
| 1458 | 1189 |
| 1459 void AccessibilityManager::OnBrailleDisplayStateChanged( | 1190 void AccessibilityManager::OnBrailleDisplayStateChanged( |
| 1460 const DisplayState& display_state) { | 1191 const DisplayState& display_state) { |
| 1461 braille_display_connected_ = display_state.available; | 1192 braille_display_connected_ = display_state.available; |
| 1462 if (braille_display_connected_) { | 1193 if (braille_display_connected_) { |
| (...skipping 19 matching lines...) Expand all Loading... | |
| 1482 } | 1213 } |
| 1483 } | 1214 } |
| 1484 | 1215 |
| 1485 void AccessibilityManager::OnExtensionUnloaded( | 1216 void AccessibilityManager::OnExtensionUnloaded( |
| 1486 content::BrowserContext* browser_context, | 1217 content::BrowserContext* browser_context, |
| 1487 const extensions::Extension* extension, | 1218 const extensions::Extension* extension, |
| 1488 extensions::UnloadedExtensionInfo::Reason reason) { | 1219 extensions::UnloadedExtensionInfo::Reason reason) { |
| 1489 if (extension->id() == keyboard_listener_extension_id_) { | 1220 if (extension->id() == keyboard_listener_extension_id_) { |
| 1490 keyboard_listener_extension_id_ = std::string(); | 1221 keyboard_listener_extension_id_ = std::string(); |
| 1491 keyboard_listener_capture_ = false; | 1222 keyboard_listener_capture_ = false; |
| 1492 extension_registry_observer_.Remove( | 1223 } |
| 1493 extensions::ExtensionRegistry::Get(browser_context)); | 1224 |
| 1225 if (extension->id() == extension_misc::kChromeVoxExtensionId) { | |
| 1226 // Do any teardown work needed immediately after ChromeVox actually unloads. | |
| 1227 PlayEarcon(SOUND_SPOKEN_FEEDBACK_DISABLED, PlaySoundOption::ALWAYS); | |
| 1228 // Clear the accessibility focus ring. | |
| 1229 AccessibilityFocusRingController::GetInstance()->SetFocusRing( | |
| 1230 std::vector<gfx::Rect>(), | |
| 1231 AccessibilityFocusRingController::PERSIST_FOCUS_RING); | |
| 1232 | |
| 1233 if (chromevox_panel_) { | |
| 1234 chromevox_panel_->Close(); | |
| 1235 chromevox_panel_ = nullptr; | |
| 1236 } | |
| 1494 } | 1237 } |
| 1495 } | 1238 } |
| 1496 | 1239 |
| 1497 void AccessibilityManager::OnShutdown(extensions::ExtensionRegistry* registry) { | 1240 void AccessibilityManager::OnShutdown(extensions::ExtensionRegistry* registry) { |
| 1498 extension_registry_observer_.Remove(registry); | 1241 extension_registry_observer_.Remove(registry); |
| 1499 } | 1242 } |
| 1500 | 1243 |
| 1501 void AccessibilityManager::PostLoadChromeVox(Profile* profile) { | 1244 void AccessibilityManager::PostLoadChromeVox() { |
| 1502 // Do any setup work needed immediately after ChromeVox actually loads. | 1245 // Do any setup work needed immediately after ChromeVox actually loads. |
| 1503 PlayEarcon(SOUND_SPOKEN_FEEDBACK_ENABLED, PlaySoundOption::ALWAYS); | 1246 PlayEarcon(SOUND_SPOKEN_FEEDBACK_ENABLED, PlaySoundOption::ALWAYS); |
| 1504 | 1247 |
| 1505 if (chrome_vox_loaded_on_lock_screen_ || | 1248 if (chromevox_loader_->loaded_on_lock_screen() || |
| 1506 should_speak_chrome_vox_announcements_on_user_screen_) { | 1249 should_speak_chrome_vox_announcements_on_user_screen_) { |
| 1507 extensions::EventRouter* event_router = | 1250 extensions::EventRouter* event_router = |
| 1508 extensions::EventRouter::Get(profile); | 1251 extensions::EventRouter::Get(profile_); |
| 1509 CHECK(event_router); | 1252 CHECK(event_router); |
| 1510 | 1253 |
| 1511 std::unique_ptr<base::ListValue> event_args(new base::ListValue()); | 1254 std::unique_ptr<base::ListValue> event_args(new base::ListValue()); |
| 1512 std::unique_ptr<extensions::Event> event(new extensions::Event( | 1255 std::unique_ptr<extensions::Event> event(new extensions::Event( |
| 1513 extensions::events::ACCESSIBILITY_PRIVATE_ON_INTRODUCE_CHROME_VOX, | 1256 extensions::events::ACCESSIBILITY_PRIVATE_ON_INTRODUCE_CHROME_VOX, |
| 1514 extensions::api::accessibility_private::OnIntroduceChromeVox:: | 1257 extensions::api::accessibility_private::OnIntroduceChromeVox:: |
| 1515 kEventName, | 1258 kEventName, |
| 1516 std::move(event_args))); | 1259 std::move(event_args))); |
| 1517 event_router->DispatchEventWithLazyListener( | 1260 event_router->DispatchEventWithLazyListener( |
| 1518 extension_misc::kChromeVoxExtensionId, std::move(event)); | 1261 extension_misc::kChromeVoxExtensionId, std::move(event)); |
| 1519 } | 1262 } |
| 1520 | 1263 |
| 1521 should_speak_chrome_vox_announcements_on_user_screen_ = | 1264 should_speak_chrome_vox_announcements_on_user_screen_ = |
| 1522 chrome_vox_loaded_on_lock_screen_; | 1265 chromevox_loader_->loaded_on_lock_screen(); |
| 1523 | 1266 |
| 1524 if (!chromevox_panel_) { | 1267 if (!chromevox_panel_) { |
| 1525 chromevox_panel_ = new ChromeVoxPanel(profile_); | 1268 chromevox_panel_ = new ChromeVoxPanel(profile_); |
| 1526 chromevox_panel_widget_observer_.reset( | 1269 chromevox_panel_widget_observer_.reset( |
| 1527 new ChromeVoxPanelWidgetObserver(chromevox_panel_->GetWidget(), this)); | 1270 new ChromeVoxPanelWidgetObserver(chromevox_panel_->GetWidget(), this)); |
| 1528 } | 1271 } |
| 1529 } | 1272 } |
| 1530 | 1273 |
| 1531 void AccessibilityManager::PostUnloadChromeVox(Profile* profile) { | |
| 1532 // Do any teardown work needed immediately after ChromeVox actually unloads. | |
| 1533 PlayEarcon(SOUND_SPOKEN_FEEDBACK_DISABLED, PlaySoundOption::ALWAYS); | |
| 1534 // Clear the accessibility focus ring. | |
| 1535 AccessibilityFocusRingController::GetInstance()->SetFocusRing( | |
| 1536 std::vector<gfx::Rect>(), | |
| 1537 AccessibilityFocusRingController::PERSIST_FOCUS_RING); | |
| 1538 } | |
| 1539 | |
| 1540 void AccessibilityManager::OnChromeVoxPanelClosing() { | 1274 void AccessibilityManager::OnChromeVoxPanelClosing() { |
| 1541 aura::Window* root_window = chromevox_panel_->GetRootWindow(); | 1275 aura::Window* root_window = chromevox_panel_->GetRootWindow(); |
| 1542 chromevox_panel_widget_observer_.reset(nullptr); | 1276 chromevox_panel_widget_observer_.reset(nullptr); |
| 1543 chromevox_panel_ = nullptr; | 1277 chromevox_panel_ = nullptr; |
| 1544 | 1278 |
| 1545 ash::WmShelf* shelf = | 1279 ash::WmShelf* shelf = |
| 1546 ash::WmShelf::ForWindow(ash::WmWindowAura::Get(root_window)); | 1280 ash::WmShelf::ForWindow(ash::WmWindowAura::Get(root_window)); |
| 1547 if (!shelf->IsShelfInitialized()) | 1281 if (!shelf->IsShelfInitialized()) |
| 1548 return; | 1282 return; |
| 1549 | 1283 |
| (...skipping 12 matching lines...) Expand all Loading... | |
| 1562 content::BrowserContext* context) { | 1296 content::BrowserContext* context) { |
| 1563 keyboard_listener_extension_id_ = id; | 1297 keyboard_listener_extension_id_ = id; |
| 1564 | 1298 |
| 1565 extensions::ExtensionRegistry* registry = | 1299 extensions::ExtensionRegistry* registry = |
| 1566 extensions::ExtensionRegistry::Get(context); | 1300 extensions::ExtensionRegistry::Get(context); |
| 1567 if (!extension_registry_observer_.IsObserving(registry) && !id.empty()) | 1301 if (!extension_registry_observer_.IsObserving(registry) && !id.empty()) |
| 1568 extension_registry_observer_.Add(registry); | 1302 extension_registry_observer_.Add(registry); |
| 1569 } | 1303 } |
| 1570 | 1304 |
| 1571 } // namespace chromeos | 1305 } // namespace chromeos |
| OLD | NEW |