summaryrefslogtreecommitdiff
path: root/chromium/chrome/browser
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@theqtcompany.com>2015-06-18 14:10:49 +0200
committerOswald Buddenhagen <oswald.buddenhagen@theqtcompany.com>2015-06-18 13:53:24 +0000
commit813fbf95af77a531c57a8c497345ad2c61d475b3 (patch)
tree821b2c8de8365f21b6c9ba17a236fb3006a1d506 /chromium/chrome/browser
parentaf6588f8d723931a298c995fa97259bb7f7deb55 (diff)
downloadqtwebengine-chromium-813fbf95af77a531c57a8c497345ad2c61d475b3.tar.gz
BASELINE: Update chromium to 44.0.2403.47
Change-Id: Ie056fedba95cf5e5c76b30c4b2c80fca4764aa2f Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@theqtcompany.com>
Diffstat (limited to 'chromium/chrome/browser')
-rw-r--r--chromium/chrome/browser/apps/app_shim/browser_app_shim.gypi5
-rw-r--r--chromium/chrome/browser/browser_resources.grd80
-rw-r--r--chromium/chrome/browser/devtools/device/webrtc/resources.grd17
-rw-r--r--chromium/chrome/browser/devtools/devtools_protocol_constants.gyp2
-rw-r--r--chromium/chrome/browser/devtools/webrtc_device_provider_resources.gyp26
-rw-r--r--chromium/chrome/browser/extensions/api/api_registration.gyp4
-rw-r--r--chromium/chrome/browser/media/desktop_streams_registry.cc6
-rw-r--r--chromium/chrome/browser/media/router/media_router.gyp75
-rw-r--r--chromium/chrome/browser/media/router/media_router.gypi44
-rw-r--r--chromium/chrome/browser/media_router_resources.grdp25
-rw-r--r--chromium/chrome/browser/polymer_resources.grdp50
-rw-r--r--chromium/chrome/browser/resources/BUILD.gn28
-rw-r--r--chromium/chrome/browser/resources/PRESUBMIT.py95
-rw-r--r--chromium/chrome/browser/resources/PRESUBMIT_test.py109
-rw-r--r--chromium/chrome/browser/resources/about_conflicts.html9
-rw-r--r--chromium/chrome/browser/resources/about_credits.js29
-rw-r--r--chromium/chrome/browser/resources/about_credits.tmpl67
-rw-r--r--chromium/chrome/browser/resources/about_flash.html9
-rw-r--r--chromium/chrome/browser/resources/about_invalidations.css1
-rw-r--r--chromium/chrome/browser/resources/about_invalidations.html3
-rw-r--r--chromium/chrome/browser/resources/about_invalidations.js4
-rw-r--r--chromium/chrome/browser/resources/about_memory.css4
-rw-r--r--chromium/chrome/browser/resources/about_memory.html15
-rw-r--r--chromium/chrome/browser/resources/about_memory_linux.html9
-rw-r--r--chromium/chrome/browser/resources/about_memory_mac.html14
-rw-r--r--chromium/chrome/browser/resources/about_nacl.html9
-rw-r--r--chromium/chrome/browser/resources/about_stats.html135
-rw-r--r--chromium/chrome/browser/resources/about_stats.js200
-rw-r--r--chromium/chrome/browser/resources/about_sys/about_sys.css3
-rw-r--r--chromium/chrome/browser/resources/about_sys/about_sys.html6
-rw-r--r--chromium/chrome/browser/resources/about_version.css3
-rw-r--r--chromium/chrome/browser/resources/about_version.html8
-rw-r--r--chromium/chrome/browser/resources/about_version.js2
-rw-r--r--chromium/chrome/browser/resources/about_voicesearch.html9
-rw-r--r--chromium/chrome/browser/resources/app_list/audio_manager.js107
-rw-r--r--chromium/chrome/browser/resources/app_list/hotword_nacl.nmf7
-rw-r--r--chromium/chrome/browser/resources/app_list/plugin_manager.js168
-rw-r--r--chromium/chrome/browser/resources/app_list/recommended_apps.css31
-rw-r--r--chromium/chrome/browser/resources/app_list/recommended_apps.js90
-rw-r--r--chromium/chrome/browser/resources/app_list/speech_manager.js312
-rw-r--r--chromium/chrome/browser/resources/app_list/speech_recognition_manager.js91
-rw-r--r--chromium/chrome/browser/resources/app_list/start_page.css29
-rw-r--r--chromium/chrome/browser/resources/app_list/start_page.html15
-rw-r--r--chromium/chrome/browser/resources/app_list/start_page.js146
-rw-r--r--chromium/chrome/browser/resources/bookmark_manager/css/bmm.css14
-rw-r--r--chromium/chrome/browser/resources/bookmark_manager/js/bmm.js24
-rw-r--r--chromium/chrome/browser/resources/bookmark_manager/js/bmm/bookmark_list.js8
-rw-r--r--chromium/chrome/browser/resources/bookmark_manager/js/bmm/bookmark_tree.js6
-rw-r--r--chromium/chrome/browser/resources/bookmark_manager/js/bmm_test.html3
-rw-r--r--chromium/chrome/browser/resources/bookmark_manager/js/main.js113
-rw-r--r--chromium/chrome/browser/resources/bookmark_manager/main.html29
-rw-r--r--chromium/chrome/browser/resources/certificate_viewer.html8
-rw-r--r--chromium/chrome/browser/resources/chromeos/OWNERS2
-rw-r--r--chromium/chrome/browser/resources/chromeos/about_os_credits.html16
-rw-r--r--chromium/chrome/browser/resources/chromeos/bluetooth_pair_device.html8
-rw-r--r--chromium/chrome/browser/resources/chromeos/braille_ime/braille_ime.js36
-rw-r--r--chromium/chrome/browser/resources/chromeos/braille_ime/externs.js2
-rw-r--r--chromium/chrome/browser/resources/chromeos/certificate_manager_dialog.html8
-rw-r--r--chromium/chrome/browser/resources/chromeos/certificate_manager_dialog.js3
-rw-r--r--chromium/chrome/browser/resources/chromeos/charger_replacement.css153
-rw-r--r--chromium/chrome/browser/resources/chromeos/charger_replacement.html222
-rw-r--r--chromium/chrome/browser/resources/chromeos/charger_replacement.js375
-rw-r--r--chromium/chrome/browser/resources/chromeos/choose_mobile_network.html9
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/braille/braille_display_manager.js (renamed from chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/braille_display_manager.js)211
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/braille/braille_display_manager_test.unitjs (renamed from chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/braille_display_manager_test.unitjs)77
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/braille/braille_input_handler.js587
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/braille/braille_input_handler_test.unitjs (renamed from chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/braille_input_handler_test.unitjs)55
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/braille/braille_key_types.js (renamed from chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/braille_key_types.js)4
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/braille/braille_table.js (renamed from chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/braille_table.js)10
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/braille/braille_table_test.extjs (renamed from chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/braille_table_test.extjs)23
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/braille/braille_translator_manager.js233
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/braille/braille_translator_manager_test.extjs116
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/braille/expanding_braille_translator.js (renamed from chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/expanding_braille_translator.js)20
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/braille/expanding_braille_translator_test.unitjs (renamed from chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/expanding_braille_translator_test.unitjs)16
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/braille/liblouis.js (renamed from chromium/chrome/browser/resources/chromeos/chromevox/liblouis_nacl/liblouis.js)107
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/braille/liblouis_test.extjs168
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/braille/nav_braille.js (renamed from chromium/chrome/browser/resources/chromeos/chromevox/common/nav_braille.js)10
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/braille/pan_strategy.js220
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/braille/pan_strategy_test.unitjs121
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/braille/spans.js65
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox.gyp29
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/accessibility_api_handler.js699
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/background.js138
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/externs.js4
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/injected_script_loader.js54
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/kbexplorer.html1
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/keymaps/key_map.js20
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/mathmaps/math_map.js16
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/options.html9
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/options.js63
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/prefs.js2
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/tabs_api_handler.js3
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/active_indicator.js19
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/api.js90
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/api_implementation.js8
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/console_tts.js8
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/event_watcher.js54
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/event_watcher_test.unitjs67
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/externs.js71
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/history.js5
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/init_document.js16
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/init_globals.js1
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/initial_speech.js5
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/keyboard_handler.js4
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/live_regions.js35
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/live_regions_test.unitjs24
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/navigation_history.js2
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/navigation_manager.js51
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/navigation_manager_test.unitjs13
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/navigation_shifter.js2
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/navigation_speaker.js6
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/pdf_processor.js339
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/script_installer.js100
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/serializer.js2
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/ui/node_search_widget.js5
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/ui/search_widget.js13
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/user_commands.js27
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/user_commands_test.unitjs5
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/user_event_detail.js2
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/messages/msgs.js6
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/messages/spoken_message.js9
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/messages/spoken_messages.js82
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox_tests.gypi33
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/common/aria_util.js22
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/common/aural_style_util.js6
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/common/braille_util.js202
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/common/braille_util_test.unitjs71
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/common/chrome_extension_externs.js723
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/common/chromevox.js25
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/common/chromevox_json.js6
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/common/command_store.js8
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/common/composite_tts.js2
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/common/content_editable_extractor.js6
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/common/description_util.js24
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/common/dom_predicates.js60
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/common/dom_util.js47
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/common/editable_text.js804
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/common/editable_text_area_shadow.js6
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/common/editable_text_base.js712
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/common/editable_text_test.unitjs25
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/common/externs.js15
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/common/find_util.js2
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/common/interframe.js31
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/common/key_sequence.js8
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/common/key_util.js12
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/common/math_semantic_attr.js174
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/common/math_semantic_tree.js120
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/common/math_semantic_util.js16
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/common/math_util.js20
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/common/memoize.js2
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/common/nav_description.js2
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/common/nav_math_description.js2
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/common/node_state.js2
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/common/page_selection.js4
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/common/selection_util.js3
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/common/spannable.js30
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/common/string_util.js32
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/common/string_util_test.unitjs32
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/common/traverse_content.js2
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/common/traverse_math.js14
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/common/traverse_table.js41
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/common/traverse_util.js74
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/automation_predicate.js113
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/automation_util.js107
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/automation_util_test.extjs102
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/background.js528
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/background_test.extjs223
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/classic_compatibility.js143
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/cursors.js40
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/cursors_test.extjs39
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/output.js1153
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/output_test.extjs372
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/extensions/searchvox/search.js6
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/extensions/searchvox/search_tools.js4
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/generate_manifest.gypi6
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/braille.js2
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/braille_background.js148
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/braille_input_handler.js523
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/braille_integration_test.unitjs143
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/extension_bridge.js70
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/externs.js9
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/host.js2
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/mathjax.js6
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/tts.js6
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/tts_background.js41
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/host/interface/abstract_host.js2
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/host/interface/abstract_tts.js6
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/host/testing/tts.js6
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/liblouis_nacl/externs.js22
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/liblouis_nacl/liblouis_test.extjs130
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/manifest.json.jinja2170
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/base_rule_store.js20
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/math_simple_store.js6
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/math_store.js10
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/mathml_store_rules.js48
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/mathml_store_util.js18
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/speech_rule.js16
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/speech_rule_engine.js12
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/speech_rule_evaluator.js2
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/speech_rule_functions.js12
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/speech_rule_store.js2
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/store_util.js2
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings.grd192
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/testing/assert_additions.js4
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/testing/callback_helper.js45
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/testing/chromevox_e2e_test_base.js70
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/testing/chromevox_next_e2e_test_base.js41
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/testing/chromevox_unittest_base.js35
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/testing/common.js14
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/testing/mock_tts.js42
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/testing/tester.js4
-rwxr-xr-xchromium/chrome/browser/resources/chromeos/chromevox/tools/check_chromevox.py1
-rwxr-xr-xchromium/chrome/browser/resources/chromeos/chromevox/tools/generate_deps.py26
-rwxr-xr-xchromium/chrome/browser/resources/chromeos/chromevox/tools/generate_manifest.py18
-rwxr-xr-xchromium/chrome/browser/resources/chromeos/chromevox/tools/jscompilerwrapper.py1
-rwxr-xr-xchromium/chrome/browser/resources/chromeos/chromevox/tools/print_js_deps.py89
-rwxr-xr-xchromium/chrome/browser/resources/chromeos/chromevox/tools/publish_webstore_extension.py155
-rwxr-xr-xchromium/chrome/browser/resources/chromeos/chromevox/tools/upload_chromevox_to_webstore.py121
-rwxr-xr-xchromium/chrome/browser/resources/chromeos/chromevox/tools/webstore_extension_util.py (renamed from chromium/chrome/browser/resources/chromeos/chromevox/tools/chromevox_webstore_util.py)59
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/walkers/abstract_shifter.js3
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/walkers/abstract_walker.js2
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/walkers/layout_line_walker.js3
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/walkers/structural_line_walker.js1
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/walkers/structural_line_walker_test.unitjs1
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/walkers/table_shifter.js4
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/walkers/table_walker.js42
-rw-r--r--chromium/chrome/browser/resources/chromeos/compiled_resources.gyp17
-rw-r--r--chromium/chrome/browser/resources/chromeos/cryptohome.html3
-rw-r--r--chromium/chrome/browser/resources/chromeos/drive_internals.html3
-rw-r--r--chromium/chrome/browser/resources/chromeos/first_run/app/main.html3
-rw-r--r--chromium/chrome/browser/resources/chromeos/first_run/first_run.css2
-rw-r--r--chromium/chrome/browser/resources/chromeos/first_run/first_run.html3
-rw-r--r--chromium/chrome/browser/resources/chromeos/first_run/preload.css56
-rw-r--r--chromium/chrome/browser/resources/chromeos/genius_app/manifest.json1
-rw-r--r--chromium/chrome/browser/resources/chromeos/guest_session_tab.html7
-rw-r--r--chromium/chrome/browser/resources/chromeos/image_burner.css10
-rw-r--r--chromium/chrome/browser/resources/chromeos/image_burner.html2
-rw-r--r--chromium/chrome/browser/resources/chromeos/input_method/cangjie_manifest.json19
-rw-r--r--chromium/chrome/browser/resources/chromeos/input_method/google_input_tools_manifest.json531
-rw-r--r--chromium/chrome/browser/resources/chromeos/input_method/google_xkb_manifest.json608
-rw-r--r--chromium/chrome/browser/resources/chromeos/input_method/hangul_manifest.json129
-rw-r--r--chromium/chrome/browser/resources/chromeos/input_method/m17n_manifest.json93
-rw-r--r--chromium/chrome/browser/resources/chromeos/input_method/mozc_manifest.json41
-rw-r--r--chromium/chrome/browser/resources/chromeos/input_method/pinyin_manifest.json19
-rw-r--r--chromium/chrome/browser/resources/chromeos/input_method/xkb_manifest.json526
-rw-r--r--chromium/chrome/browser/resources/chromeos/input_method/zhuyin_manifest.json19
-rw-r--r--chromium/chrome/browser/resources/chromeos/keyboard_overlay.css4
-rw-r--r--chromium/chrome/browser/resources/chromeos/keyboard_overlay.html5
-rw-r--r--chromium/chrome/browser/resources/chromeos/keyboard_overlay.js155
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/OWNERS1
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/accessibility_menu.css6
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/apps_menu.js2
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/controller-pairing-screen.html221
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/controller-pairing-screen.js77
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/custom_elements.html5
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/custom_elements.js7
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/custom_elements_login.html10
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/custom_elements_login.js10
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/custom_elements_oobe.html17
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/custom_elements_oobe.js16
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/demo_user_login.css48
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/demo_user_login.html8
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/demo_user_login.js46
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/gaia.css4
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/gaia_buttons.html42
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/gaia_buttons.js32
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/gaia_card.css113
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/gaia_card.html40
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/gaia_header.html23
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/gaia_icon_button.css31
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/gaia_input.css34
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/gaia_input.html45
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/gaia_input.js59
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/gaia_input_form.css16
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/gaia_input_form.html38
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/gaia_input_form.js24
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/gaia_password_changed.css31
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/gaia_password_changed.html93
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/gaia_password_changed.js71
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/header_bar.css54
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/header_bar.html19
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/header_bar.js144
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/host-pairing-screen.html76
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/host-pairing-screen.js20
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/html-echo.html4
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/indeterminate-progress.html4
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/indeterminate-progress.js1
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/login.html12
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/login.js1
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/login_common.js49
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/login_resources.html1
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/network_dropdown.css6
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/notification_card.css50
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/notification_card.html62
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/notification_card.js16
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/offline_gaia.css25
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/offline_gaia.html95
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/offline_gaia.js99
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/oobe-screen.html2
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/oobe-screen.js7
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/oobe.html12
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/oobe.js26
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/oobe_screen.css4
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/oobe_screen_controller_pairing.css6
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/oobe_screen_controller_pairing.html219
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/oobe_screen_controller_pairing.js75
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/oobe_screen_enable_debugging.css163
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/oobe_screen_enable_debugging.html49
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/oobe_screen_enable_debugging.js166
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/oobe_screen_eula.js22
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/oobe_screen_hid_detection.css2
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/oobe_screen_hid_detection.html2
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/oobe_screen_hid_detection.js186
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/oobe_screen_host_pairing.html78
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/oobe_screen_host_pairing.js18
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/oobe_screen_network.css8
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/oobe_screen_network.html4
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/oobe_screen_network.js101
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment.css46
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment.html45
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment.js205
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment_webview.css188
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment_webview.html72
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment_webview.js293
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/oobe_screen_reset.js241
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/oobe_screen_reset_confirmation_overlay.js32
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/oobe_screen_terms_of_service.js2
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/oobe_screen_update.css2
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/oobe_screen_update.js71
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/oobe_screen_user_image.css33
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/oobe_screen_user_image.html9
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/oobe_screen_user_image.js156
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/oobe_screens.html1
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/pairing_device_list.css2
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/saml_confirm_password.css22
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/saml_confirm_password.html77
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/saml_confirm_password.js57
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/screen_confirm_password.css11
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/screen_confirm_password.html5
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/screen_confirm_password.js36
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/screen_context_test.html2
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/screen_device_disabled.css12
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/screen_device_disabled.html5
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/screen_device_disabled.js7
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/screen_error_message.js106
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/screen_gaia_signin.css160
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/screen_gaia_signin.html28
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/screen_gaia_signin.js399
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/screen_password_changed.css13
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/screen_password_changed.html5
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/screen_password_changed.js53
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/screen_supervised_user_creation.css19
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/screen_supervised_user_creation.html12
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/screen_supervised_user_creation.js23
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/test_util.js28
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/throbber_notice.css22
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/throbber_notice.html12
-rw-r--r--chromium/chrome/browser/resources/chromeos/login/version.html1
-rw-r--r--chromium/chrome/browser/resources/chromeos/merge_session_load.html7
-rw-r--r--chromium/chrome/browser/resources/chromeos/mobile_setup.css6
-rw-r--r--chromium/chrome/browser/resources/chromeos/mobile_setup.html5
-rw-r--r--chromium/chrome/browser/resources/chromeos/mobile_setup_portal.html5
-rw-r--r--chromium/chrome/browser/resources/chromeos/neterror.css8
-rw-r--r--chromium/chrome/browser/resources/chromeos/neterror.js8
-rw-r--r--chromium/chrome/browser/resources/chromeos/network/network_config.js147
-rw-r--r--chromium/chrome/browser/resources/chromeos/network_configuration/js/network_status.js38
-rw-r--r--chromium/chrome/browser/resources/chromeos/network_ui/compiled_resources.gyp24
-rw-r--r--chromium/chrome/browser/resources/chromeos/network_ui/network_ui.css67
-rw-r--r--chromium/chrome/browser/resources/chromeos/network_ui/network_ui.html77
-rw-r--r--chromium/chrome/browser/resources/chromeos/network_ui/network_ui.js297
-rw-r--r--chromium/chrome/browser/resources/chromeos/nfc_debug.css1
-rw-r--r--chromium/chrome/browser/resources/chromeos/nfc_debug.html9
-rw-r--r--chromium/chrome/browser/resources/chromeos/offline_app_load.html6
-rw-r--r--chromium/chrome/browser/resources/chromeos/offline_net_load.html66
-rw-r--r--chromium/chrome/browser/resources/chromeos/power.html7
-rw-r--r--chromium/chrome/browser/resources/chromeos/power.js27
-rw-r--r--chromium/chrome/browser/resources/chromeos/provided_file_systems.css1
-rw-r--r--chromium/chrome/browser/resources/chromeos/provided_file_systems.html1
-rw-r--r--chromium/chrome/browser/resources/chromeos/provided_file_systems.js21
-rw-r--r--chromium/chrome/browser/resources/chromeos/proxy_settings.html6
-rw-r--r--chromium/chrome/browser/resources/chromeos/salsa.css8
-rw-r--r--chromium/chrome/browser/resources/chromeos/salsa.html17
-rw-r--r--chromium/chrome/browser/resources/chromeos/salsa.js22
-rw-r--r--chromium/chrome/browser/resources/chromeos/set_time.html8
-rw-r--r--chromium/chrome/browser/resources/chromeos/sim_unlock.css4
-rw-r--r--chromium/chrome/browser/resources/chromeos/sim_unlock.html7
-rw-r--r--chromium/chrome/browser/resources/chromeos/slow.css1
-rw-r--r--chromium/chrome/browser/resources/chromeos/slow.html9
-rw-r--r--chromium/chrome/browser/resources/chromeos/user_images_grid.js17
-rw-r--r--chromium/chrome/browser/resources/chromeos/wallpaper_manager/css/wallpaper_manager.css28
-rw-r--r--chromium/chrome/browser/resources/chromeos/wallpaper_manager/images/icon128.pngbin1293 -> 8136 bytes
-rw-r--r--chromium/chrome/browser/resources/chromeos/wallpaper_manager/images/icon16.pngbin0 -> 666 bytes
-rw-r--r--chromium/chrome/browser/resources/chromeos/wallpaper_manager/images/icon256.pngbin0 -> 21538 bytes
-rw-r--r--chromium/chrome/browser/resources/chromeos/wallpaper_manager/images/icon32.pngbin0 -> 1358 bytes
-rw-r--r--chromium/chrome/browser/resources/chromeos/wallpaper_manager/images/icon48.pngbin0 -> 2226 bytes
-rw-r--r--chromium/chrome/browser/resources/chromeos/wallpaper_manager/images/icon64.pngbin0 -> 3223 bytes
-rw-r--r--chromium/chrome/browser/resources/chromeos/wallpaper_manager/images/icon96.pngbin0 -> 5609 bytes
-rw-r--r--chromium/chrome/browser/resources/chromeos/wallpaper_manager/js/constants.js19
-rw-r--r--chromium/chrome/browser/resources/chromeos/wallpaper_manager/js/event_page.js251
-rw-r--r--chromium/chrome/browser/resources/chromeos/wallpaper_manager/js/main_scripts.js1
-rw-r--r--chromium/chrome/browser/resources/chromeos/wallpaper_manager/js/util.js66
-rw-r--r--chromium/chrome/browser/resources/chromeos/wallpaper_manager/js/wallpaper_categories_list.js88
-rw-r--r--chromium/chrome/browser/resources/chromeos/wallpaper_manager/js/wallpaper_images_grid.js77
-rw-r--r--chromium/chrome/browser/resources/chromeos/wallpaper_manager/js/wallpaper_manager.js227
-rw-r--r--chromium/chrome/browser/resources/chromeos/wallpaper_manager/main.html15
-rw-r--r--chromium/chrome/browser/resources/chromeos/wallpaper_manager/manifest.json8
-rw-r--r--chromium/chrome/browser/resources/component_extension_resources.grd120
-rw-r--r--chromium/chrome/browser/resources/components.css8
-rw-r--r--chromium/chrome/browser/resources/components.html9
-rw-r--r--chromium/chrome/browser/resources/contextual_search/header.svg75
-rw-r--r--chromium/chrome/browser/resources/contextual_search/promo.css104
-rw-r--r--chromium/chrome/browser/resources/contextual_search/promo.html29
-rw-r--r--chromium/chrome/browser/resources/contextual_search/promo.js32
-rw-r--r--chromium/chrome/browser/resources/copresence.css69
-rw-r--r--chromium/chrome/browser/resources/copresence.html75
-rw-r--r--chromium/chrome/browser/resources/copresence.js130
-rw-r--r--chromium/chrome/browser/resources/crashes.css6
-rw-r--r--chromium/chrome/browser/resources/crashes.html9
-rw-r--r--chromium/chrome/browser/resources/cryptotoken/OWNERS2
-rw-r--r--chromium/chrome/browser/resources/cryptotoken/appid.js44
-rw-r--r--chromium/chrome/browser/resources/cryptotoken/approvedorigins.js2
-rw-r--r--chromium/chrome/browser/resources/cryptotoken/cryptotokenapprovedorigins.js57
-rw-r--r--chromium/chrome/browser/resources/cryptotoken/cryptotokenbackground.js104
-rw-r--r--chromium/chrome/browser/resources/cryptotoken/cryptotokenorigincheck.js53
-rw-r--r--chromium/chrome/browser/resources/cryptotoken/devicestatuscodes.js3
-rw-r--r--chromium/chrome/browser/resources/cryptotoken/enroller.js159
-rw-r--r--chromium/chrome/browser/resources/cryptotoken/generichelper.js2
-rw-r--r--chromium/chrome/browser/resources/cryptotoken/gnubbies.js14
-rw-r--r--chromium/chrome/browser/resources/cryptotoken/gnubby-u2f.js10
-rw-r--r--chromium/chrome/browser/resources/cryptotoken/gnubby.js51
-rw-r--r--chromium/chrome/browser/resources/cryptotoken/gnubbycodetypes.js1
-rw-r--r--chromium/chrome/browser/resources/cryptotoken/gnubbydevice.js28
-rw-r--r--chromium/chrome/browser/resources/cryptotoken/gnubbyfactory.js9
-rw-r--r--chromium/chrome/browser/resources/cryptotoken/googleapprovedorigins.js32
-rw-r--r--chromium/chrome/browser/resources/cryptotoken/gstaticorigincheck.js51
-rw-r--r--chromium/chrome/browser/resources/cryptotoken/hidgnubbydevice.js56
-rw-r--r--chromium/chrome/browser/resources/cryptotoken/manifest.json22
-rw-r--r--chromium/chrome/browser/resources/cryptotoken/multiplesigner.js8
-rw-r--r--chromium/chrome/browser/resources/cryptotoken/origincheck.js4
-rw-r--r--chromium/chrome/browser/resources/cryptotoken/requestqueue.js2
-rw-r--r--chromium/chrome/browser/resources/cryptotoken/sha256.js8
-rw-r--r--chromium/chrome/browser/resources/cryptotoken/signer.js24
-rw-r--r--chromium/chrome/browser/resources/cryptotoken/singlesigner.js77
-rw-r--r--chromium/chrome/browser/resources/cryptotoken/textfetcher.js4
-rw-r--r--chromium/chrome/browser/resources/cryptotoken/usbenrollhandler.js9
-rw-r--r--chromium/chrome/browser/resources/cryptotoken/usbgnubbydevice.js6
-rw-r--r--chromium/chrome/browser/resources/cryptotoken/usbgnubbyfactory.js8
-rw-r--r--chromium/chrome/browser/resources/cryptotoken/usbsignhandler.js5
-rw-r--r--chromium/chrome/browser/resources/cryptotoken/util.js10
-rw-r--r--chromium/chrome/browser/resources/cryptotoken/webrequest.js14
-rw-r--r--chromium/chrome/browser/resources/cryptotoken/webrequestsender.js32
-rw-r--r--chromium/chrome/browser/resources/device_log_ui/OWNERS1
-rw-r--r--chromium/chrome/browser/resources/device_log_ui/device_log_ui.css115
-rw-r--r--chromium/chrome/browser/resources/device_log_ui/device_log_ui.html68
-rw-r--r--chromium/chrome/browser/resources/device_log_ui/device_log_ui.js149
-rw-r--r--chromium/chrome/browser/resources/domain_reliability_internals.html9
-rw-r--r--chromium/chrome/browser/resources/downloads/OWNERS1
-rw-r--r--chromium/chrome/browser/resources/downloads/compiled_resources.gyp18
-rw-r--r--chromium/chrome/browser/resources/downloads/downloads.css48
-rw-r--r--chromium/chrome/browser/resources/downloads/downloads.html80
-rw-r--r--chromium/chrome/browser/resources/downloads/downloads.js991
-rw-r--r--chromium/chrome/browser/resources/downloads/externs.js37
-rw-r--r--chromium/chrome/browser/resources/downloads/focus_row.js85
-rw-r--r--chromium/chrome/browser/resources/downloads/item.js58
-rw-r--r--chromium/chrome/browser/resources/downloads/item_view.js431
-rw-r--r--chromium/chrome/browser/resources/downloads/manager.js228
-rw-r--r--chromium/chrome/browser/resources/easy_unlock/manifest.json25
-rw-r--r--chromium/chrome/browser/resources/easy_unlock/manifest_signin.json56
-rw-r--r--chromium/chrome/browser/resources/extensions/chromeos/kiosk_app_list.js2
-rw-r--r--chromium/chrome/browser/resources/extensions/chromeos/kiosk_apps.css14
-rw-r--r--chromium/chrome/browser/resources/extensions/chromeos/kiosk_apps.js2
-rw-r--r--chromium/chrome/browser/resources/extensions/compiled_resources.gyp10
-rw-r--r--chromium/chrome/browser/resources/extensions/extension_code.js13
-rw-r--r--chromium/chrome/browser/resources/extensions/extension_command_list.js59
-rw-r--r--chromium/chrome/browser/resources/extensions/extension_commands_overlay.js2
-rw-r--r--chromium/chrome/browser/resources/extensions/extension_error.css100
-rw-r--r--chromium/chrome/browser/resources/extensions/extension_error.html14
-rw-r--r--chromium/chrome/browser/resources/extensions/extension_error.js382
-rw-r--r--chromium/chrome/browser/resources/extensions/extension_error_overlay.css6
-rw-r--r--chromium/chrome/browser/resources/extensions/extension_error_overlay.html1
-rw-r--r--chromium/chrome/browser/resources/extensions/extension_error_overlay.js283
-rw-r--r--chromium/chrome/browser/resources/extensions/extension_focus_manager.js12
-rw-r--r--chromium/chrome/browser/resources/extensions/extension_info.css44
-rw-r--r--chromium/chrome/browser/resources/extensions/extension_info.html31
-rw-r--r--chromium/chrome/browser/resources/extensions/extension_info.js23
-rw-r--r--chromium/chrome/browser/resources/extensions/extension_list.js1370
-rw-r--r--chromium/chrome/browser/resources/extensions/extension_load_error.html2
-rw-r--r--chromium/chrome/browser/resources/extensions/extension_loader.js24
-rw-r--r--chromium/chrome/browser/resources/extensions/extension_options_overlay.js169
-rw-r--r--chromium/chrome/browser/resources/extensions/extensions.css196
-rw-r--r--chromium/chrome/browser/resources/extensions/extensions.html64
-rw-r--r--chromium/chrome/browser/resources/extensions/extensions.js372
-rw-r--r--chromium/chrome/browser/resources/extensions/pack_extension_overlay.html1
-rw-r--r--chromium/chrome/browser/resources/extensions/pack_extension_overlay.js135
-rw-r--r--chromium/chrome/browser/resources/extensions_infobar.css16
-rw-r--r--chromium/chrome/browser/resources/extensions_infobar_mac.css16
-rw-r--r--chromium/chrome/browser/resources/feedback/OWNERS1
-rw-r--r--chromium/chrome/browser/resources/feedback/css/feedback.css12
-rw-r--r--chromium/chrome/browser/resources/feedback/html/default.html7
-rw-r--r--chromium/chrome/browser/resources/feedback/js/event_handler.js114
-rw-r--r--chromium/chrome/browser/resources/flags.css30
-rw-r--r--chromium/chrome/browser/resources/flags.html9
-rw-r--r--chromium/chrome/browser/resources/gaia_auth/background.js29
-rw-r--r--chromium/chrome/browser/resources/gaia_auth/channel.js8
-rw-r--r--chromium/chrome/browser/resources/gaia_auth/main.html6
-rw-r--r--chromium/chrome/browser/resources/gaia_auth/main.js129
-rw-r--r--chromium/chrome/browser/resources/gaia_auth/manifest.json5
-rw-r--r--chromium/chrome/browser/resources/gaia_auth/manifest_keyboard.json5
-rw-r--r--chromium/chrome/browser/resources/gaia_auth/offline.css85
-rw-r--r--chromium/chrome/browser/resources/gaia_auth/offline.html2
-rw-r--r--chromium/chrome/browser/resources/gaia_auth/offline.js33
-rw-r--r--chromium/chrome/browser/resources/gaia_auth/saml_injected.js85
-rw-r--r--chromium/chrome/browser/resources/gaia_auth/success.html2
-rw-r--r--chromium/chrome/browser/resources/gaia_auth/util.js25
-rw-r--r--chromium/chrome/browser/resources/gaia_auth_host/authenticator.js601
-rw-r--r--chromium/chrome/browser/resources/gaia_auth_host/gaia_auth_host.js62
-rw-r--r--chromium/chrome/browser/resources/gaia_auth_host/post_message_channel.js372
-rw-r--r--chromium/chrome/browser/resources/gaia_auth_host/saml_handler.js453
-rw-r--r--chromium/chrome/browser/resources/gaia_auth_host/webview_saml_injected.js6
-rw-r--r--chromium/chrome/browser/resources/gcm_internals.html18
-rw-r--r--chromium/chrome/browser/resources/gcm_internals.js1
-rw-r--r--chromium/chrome/browser/resources/gesture_config.css1
-rw-r--r--chromium/chrome/browser/resources/gesture_config.html2
-rw-r--r--chromium/chrome/browser/resources/get_salient_image_url.js67
-rw-r--r--chromium/chrome/browser/resources/google_now/background.js206
-rw-r--r--chromium/chrome/browser/resources/google_now/background_test_util.js2
-rw-r--r--chromium/chrome/browser/resources/google_now/background_unittest.gtestjs4
-rw-r--r--chromium/chrome/browser/resources/google_now/cards.js20
-rw-r--r--chromium/chrome/browser/resources/google_now/common_test_util.js2
-rw-r--r--chromium/chrome/browser/resources/google_now/manifest.json8
-rw-r--r--chromium/chrome/browser/resources/google_now/utility.js27
-rw-r--r--chromium/chrome/browser/resources/hangout_services/OWNERS1
-rw-r--r--chromium/chrome/browser/resources/hangout_services/background.html1
-rw-r--r--chromium/chrome/browser/resources/hangout_services/manifest.json5
-rw-r--r--chromium/chrome/browser/resources/hangout_services/thunk.js54
-rw-r--r--chromium/chrome/browser/resources/help/help.html9
-rw-r--r--chromium/chrome/browser/resources/help/help.js1
-rw-r--r--chromium/chrome/browser/resources/help/help_content.css51
-rw-r--r--chromium/chrome/browser/resources/help/help_page.js6
-rw-r--r--chromium/chrome/browser/resources/help_app/OWNERS3
-rw-r--r--chromium/chrome/browser/resources/help_app/manifest.json3
-rw-r--r--chromium/chrome/browser/resources/history/compiled_resources.gyp1
-rw-r--r--chromium/chrome/browser/resources/history/history.css15
-rw-r--r--chromium/chrome/browser/resources/history/history.html35
-rw-r--r--chromium/chrome/browser/resources/history/history.js330
-rw-r--r--chromium/chrome/browser/resources/history/history_focus_manager.js2
-rw-r--r--chromium/chrome/browser/resources/history/history_mobile.css47
-rw-r--r--chromium/chrome/browser/resources/history/other_devices.js38
-rw-r--r--chromium/chrome/browser/resources/hotword/always_on_manager.js2
-rw-r--r--chromium/chrome/browser/resources/hotword/audio_client.js14
-rw-r--r--chromium/chrome/browser/resources/hotword/base_session_manager.js19
-rw-r--r--chromium/chrome/browser/resources/hotword/constants.js105
-rw-r--r--chromium/chrome/browser/resources/hotword/keep_alive.js54
-rw-r--r--chromium/chrome/browser/resources/hotword/manager.js15
-rw-r--r--chromium/chrome/browser/resources/hotword/manifest.json43
-rw-r--r--chromium/chrome/browser/resources/hotword/nacl_manager.js148
-rw-r--r--chromium/chrome/browser/resources/hotword/page_audio_manager.js112
-rw-r--r--chromium/chrome/browser/resources/hotword/state_manager.js227
-rw-r--r--chromium/chrome/browser/resources/hotword/training_manager.js167
-rw-r--r--chromium/chrome/browser/resources/hotword_audio_verification/OWNERS1
-rw-r--r--chromium/chrome/browser/resources/hotword_audio_verification/event_page.js8
-rw-r--r--chromium/chrome/browser/resources/hotword_audio_verification/flow.js449
-rw-r--r--chromium/chrome/browser/resources/hotword_audio_verification/images/bt-close-1x.pngbin155 -> 0 bytes
-rw-r--r--chromium/chrome/browser/resources/hotword_audio_verification/images/bt-close-2x.pngbin3257 -> 0 bytes
-rw-r--r--chromium/chrome/browser/resources/hotword_audio_verification/images/gradient-1x.pngbin0 -> 2275 bytes
-rw-r--r--chromium/chrome/browser/resources/hotword_audio_verification/images/gradient-2x.pngbin0 -> 4493 bytes
-rw-r--r--chromium/chrome/browser/resources/hotword_audio_verification/images/header-optin-1x.pngbin1730 -> 0 bytes
-rw-r--r--chromium/chrome/browser/resources/hotword_audio_verification/images/header-optin-2x.pngbin8755 -> 0 bytes
-rw-r--r--chromium/chrome/browser/resources/hotword_audio_verification/images/ic-check-blue-1x.pngbin197 -> 289 bytes
-rw-r--r--chromium/chrome/browser/resources/hotword_audio_verification/images/ic-check-blue-2x.pngbin3194 -> 526 bytes
-rw-r--r--chromium/chrome/browser/resources/hotword_audio_verification/images/ic-check-gray-1x.pngbin140 -> 222 bytes
-rw-r--r--chromium/chrome/browser/resources/hotword_audio_verification/images/ic-check-gray-2x.pngbin3116 -> 386 bytes
-rw-r--r--chromium/chrome/browser/resources/hotword_audio_verification/images/ic-error-1x.pngbin3175 -> 216 bytes
-rw-r--r--chromium/chrome/browser/resources/hotword_audio_verification/images/ic-error-2x.pngbin3688 -> 299 bytes
-rw-r--r--chromium/chrome/browser/resources/hotword_audio_verification/images/ic-x-white-1x.pngbin0 -> 380 bytes
-rw-r--r--chromium/chrome/browser/resources/hotword_audio_verification/images/ic-x-white-2x.pngbin0 -> 203 bytes
-rw-r--r--chromium/chrome/browser/resources/hotword_audio_verification/images/icon-128.pngbin0 -> 1170 bytes
-rw-r--r--chromium/chrome/browser/resources/hotword_audio_verification/images/icon-16.pngbin0 -> 184 bytes
-rw-r--r--chromium/chrome/browser/resources/hotword_audio_verification/images/icon-48.pngbin0 -> 408 bytes
-rw-r--r--chromium/chrome/browser/resources/hotword_audio_verification/images/intro-1x.pngbin0 -> 7920 bytes
-rw-r--r--chromium/chrome/browser/resources/hotword_audio_verification/images/intro-2x.pngbin0 -> 9665 bytes
-rw-r--r--chromium/chrome/browser/resources/hotword_audio_verification/images/mic-1x.pngbin1968 -> 0 bytes
-rw-r--r--chromium/chrome/browser/resources/hotword_audio_verification/images/mic-2x.pngbin11627 -> 0 bytes
-rw-r--r--chromium/chrome/browser/resources/hotword_audio_verification/images/placeholder-loader-1x.pngbin317 -> 289 bytes
-rw-r--r--chromium/chrome/browser/resources/hotword_audio_verification/images/placeholder-loader-2x.pngbin3768 -> 534 bytes
-rw-r--r--chromium/chrome/browser/resources/hotword_audio_verification/main.html16
-rw-r--r--chromium/chrome/browser/resources/hotword_audio_verification/main.js58
-rw-r--r--chromium/chrome/browser/resources/hotword_audio_verification/manifest.json12
-rw-r--r--chromium/chrome/browser/resources/hotword_audio_verification/steps/audio_history_step.html38
-rw-r--r--chromium/chrome/browser/resources/hotword_audio_verification/steps/finished_step.html43
-rw-r--r--chromium/chrome/browser/resources/hotword_audio_verification/steps/hotword_audio_history_step.html29
-rw-r--r--chromium/chrome/browser/resources/hotword_audio_verification/steps/hotword_only_step.html41
-rw-r--r--chromium/chrome/browser/resources/hotword_audio_verification/steps/speech_training_step.html47
-rw-r--r--chromium/chrome/browser/resources/hotword_audio_verification/steps/start_step.html17
-rw-r--r--chromium/chrome/browser/resources/hotword_audio_verification/style.css374
-rw-r--r--chromium/chrome/browser/resources/hotword_helper/audio_client.js387
-rw-r--r--chromium/chrome/browser/resources/hotword_helper/manager.js232
-rw-r--r--chromium/chrome/browser/resources/hotword_helper/manifest.json41
-rw-r--r--chromium/chrome/browser/resources/identity_internals.html9
-rw-r--r--chromium/chrome/browser/resources/identity_internals.js2
-rw-r--r--chromium/chrome/browser/resources/identity_scope_approval_dialog/scope_approval_dialog.css12
-rw-r--r--chromium/chrome/browser/resources/identity_scope_approval_dialog/scope_approval_dialog.html1
-rw-r--r--chromium/chrome/browser/resources/inline_login/inline_login.css1
-rw-r--r--chromium/chrome/browser/resources/inline_login/inline_login.html12
-rw-r--r--chromium/chrome/browser/resources/inline_login/inline_login.js65
-rw-r--r--chromium/chrome/browser/resources/inline_login/new_inline_login.html11
-rw-r--r--chromium/chrome/browser/resources/inspect/inspect.css90
-rw-r--r--chromium/chrome/browser/resources/inspect/inspect.html39
-rw-r--r--chromium/chrome/browser/resources/inspect/inspect.js220
-rw-r--r--chromium/chrome/browser/resources/instant/instant.css1
-rw-r--r--chromium/chrome/browser/resources/instant/instant.html3
-rw-r--r--chromium/chrome/browser/resources/local_discovery/local_discovery.css2
-rw-r--r--chromium/chrome/browser/resources/local_discovery/local_discovery.html9
-rw-r--r--chromium/chrome/browser/resources/local_discovery/local_discovery.js6
-rw-r--r--chromium/chrome/browser/resources/local_ntp/OWNERS1
-rw-r--r--chromium/chrome/browser/resources/local_ntp/instant_iframe_validation.js2
-rw-r--r--chromium/chrome/browser/resources/local_ntp/local_ntp.css395
-rw-r--r--chromium/chrome/browser/resources/local_ntp/local_ntp.html4
-rw-r--r--chromium/chrome/browser/resources/local_ntp/local_ntp.js243
-rw-r--r--chromium/chrome/browser/resources/local_ntp/local_ntp_design.js113
-rw-r--r--chromium/chrome/browser/resources/local_ntp/local_ntp_fast.css330
-rw-r--r--chromium/chrome/browser/resources/local_ntp/local_ntp_fast.html31
-rw-r--r--chromium/chrome/browser/resources/local_ntp/local_ntp_fast.js808
-rw-r--r--chromium/chrome/browser/resources/local_ntp/most_visited_single.css382
-rw-r--r--chromium/chrome/browser/resources/local_ntp/most_visited_single.html18
-rw-r--r--chromium/chrome/browser/resources/local_ntp/most_visited_single.js533
-rw-r--r--chromium/chrome/browser/resources/local_ntp/most_visited_thumbnail.css13
-rw-r--r--chromium/chrome/browser/resources/local_ntp/most_visited_thumbnail.html2
-rw-r--r--chromium/chrome/browser/resources/local_ntp/most_visited_thumbnail.js15
-rw-r--r--chromium/chrome/browser/resources/local_ntp/most_visited_title.css17
-rw-r--r--chromium/chrome/browser/resources/local_ntp/most_visited_title.html2
-rw-r--r--chromium/chrome/browser/resources/local_ntp/most_visited_title.js5
-rw-r--r--chromium/chrome/browser/resources/local_ntp/most_visited_util.js37
-rw-r--r--chromium/chrome/browser/resources/local_state/local_state.html16
-rw-r--r--chromium/chrome/browser/resources/local_state/local_state.js31
-rw-r--r--chromium/chrome/browser/resources/md_settings/OWNERS2
-rw-r--r--chromium/chrome/browser/resources/md_settings/md_settings.css23
-rw-r--r--chromium/chrome/browser/resources/md_settings/md_settings.html16
-rw-r--r--chromium/chrome/browser/resources/media/webrtc_logs.html9
-rw-r--r--chromium/chrome/browser/resources/media_router/OWNERS9
-rw-r--r--chromium/chrome/browser/resources/media_router/elements/icon/sad-face.pngbin0 -> 1088 bytes
-rw-r--r--chromium/chrome/browser/resources/media_router/elements/icon/sad-face2x.pngbin0 -> 1121 bytes
-rw-r--r--chromium/chrome/browser/resources/media_router/elements/issue_banner/issue_banner.css57
-rw-r--r--chromium/chrome/browser/resources/media_router/elements/issue_banner/issue_banner.html46
-rw-r--r--chromium/chrome/browser/resources/media_router/elements/issue_banner/issue_banner.js34
-rw-r--r--chromium/chrome/browser/resources/media_router/elements/media_router_container/media_router_container.css56
-rw-r--r--chromium/chrome/browser/resources/media_router/elements/media_router_container/media_router_container.html70
-rw-r--r--chromium/chrome/browser/resources/media_router/elements/media_router_container/media_router_container.js370
-rw-r--r--chromium/chrome/browser/resources/media_router/elements/route_details/route_details.css35
-rw-r--r--chromium/chrome/browser/resources/media_router/elements/route_details/route_details.html28
-rw-r--r--chromium/chrome/browser/resources/media_router/elements/route_details/route_details.js61
-rw-r--r--chromium/chrome/browser/resources/media_router/media_router.css15
-rw-r--r--chromium/chrome/browser/resources/media_router/media_router.html20
-rw-r--r--chromium/chrome/browser/resources/media_router/media_router.js88
-rw-r--r--chromium/chrome/browser/resources/media_router/media_router_common.css15
-rw-r--r--chromium/chrome/browser/resources/media_router/media_router_data.js164
-rw-r--r--chromium/chrome/browser/resources/media_router/media_router_ui_interface.js125
-rw-r--r--chromium/chrome/browser/resources/memory_internals/memory_internals.html3
-rw-r--r--chromium/chrome/browser/resources/net_export/net_export.css5
-rw-r--r--chromium/chrome/browser/resources/net_export/net_export.html49
-rw-r--r--chromium/chrome/browser/resources/net_export/net_export.js25
-rw-r--r--chromium/chrome/browser/resources/net_internals/bandwidth_view.html41
-rw-r--r--chromium/chrome/browser/resources/net_internals/bandwidth_view.js231
-rw-r--r--chromium/chrome/browser/resources/net_internals/browser_bridge.js53
-rw-r--r--chromium/chrome/browser/resources/net_internals/capture_view.js4
-rw-r--r--chromium/chrome/browser/resources/net_internals/chromeos_view.html4
-rw-r--r--chromium/chrome/browser/resources/net_internals/cros_log_analyzer_view.html57
-rw-r--r--chromium/chrome/browser/resources/net_internals/cros_log_entry.js102
-rw-r--r--chromium/chrome/browser/resources/net_internals/cros_log_marker.js395
-rw-r--r--chromium/chrome/browser/resources/net_internals/cros_log_visualizer.js376
-rw-r--r--chromium/chrome/browser/resources/net_internals/cros_log_visualizer_view.css234
-rw-r--r--chromium/chrome/browser/resources/net_internals/cros_log_visualizer_view.html56
-rw-r--r--chromium/chrome/browser/resources/net_internals/cros_log_visualizer_view.js374
-rw-r--r--chromium/chrome/browser/resources/net_internals/dns_view.html2
-rw-r--r--chromium/chrome/browser/resources/net_internals/dns_view.js2
-rw-r--r--chromium/chrome/browser/resources/net_internals/events_view.css3
-rw-r--r--chromium/chrome/browser/resources/net_internals/hsts_view.js13
-rw-r--r--chromium/chrome/browser/resources/net_internals/index.html11
-rw-r--r--chromium/chrome/browser/resources/net_internals/index.js7
-rw-r--r--chromium/chrome/browser/resources/net_internals/log_util.js7
-rw-r--r--chromium/chrome/browser/resources/net_internals/log_view_painter.js57
-rw-r--r--chromium/chrome/browser/resources/net_internals/logs_view.css45
-rw-r--r--chromium/chrome/browser/resources/net_internals/logs_view.html19
-rw-r--r--chromium/chrome/browser/resources/net_internals/logs_view.js215
-rw-r--r--chromium/chrome/browser/resources/net_internals/main.css20
-rw-r--r--chromium/chrome/browser/resources/net_internals/main.js29
-rw-r--r--chromium/chrome/browser/resources/net_internals/quic_view.html7
-rw-r--r--chromium/chrome/browser/resources/net_internals/sdch_view.html73
-rw-r--r--chromium/chrome/browser/resources/net_internals/sdch_view.js60
-rw-r--r--chromium/chrome/browser/resources/net_internals/sockets_view.html7
-rw-r--r--chromium/chrome/browser/resources/net_internals/source_entry.js8
-rw-r--r--chromium/chrome/browser/resources/net_internals/spdy_view.html20
-rw-r--r--chromium/chrome/browser/resources/net_internals/spdy_view.js4
-rw-r--r--chromium/chrome/browser/resources/net_internals/status_view.css1
-rw-r--r--chromium/chrome/browser/resources/net_internals/test_view.html13
-rw-r--r--chromium/chrome/browser/resources/net_internals/test_view.js163
-rw-r--r--chromium/chrome/browser/resources/net_internals/timeline_view.css4
-rw-r--r--chromium/chrome/browser/resources/net_internals/timeline_view.html3
-rw-r--r--chromium/chrome/browser/resources/net_internals/timeline_view.js6
-rw-r--r--chromium/chrome/browser/resources/net_internals/waterfall_row.js11
-rw-r--r--chromium/chrome/browser/resources/network_speech_synthesis/tts_extension.js4
-rw-r--r--chromium/chrome/browser/resources/notification_1line.html4
-rw-r--r--chromium/chrome/browser/resources/notification_2line.html13
-rw-r--r--chromium/chrome/browser/resources/notification_icon.html5
-rw-r--r--chromium/chrome/browser/resources/ntp4/apps_page.css3
-rw-r--r--chromium/chrome/browser/resources/ntp4/apps_page.js46
-rw-r--r--chromium/chrome/browser/resources/ntp4/compiled_resources.gyp2
-rw-r--r--chromium/chrome/browser/resources/ntp4/dot_list.js1
-rw-r--r--chromium/chrome/browser/resources/ntp4/footer_menu.css4
-rw-r--r--chromium/chrome/browser/resources/ntp4/guest_tab.html12
-rw-r--r--chromium/chrome/browser/resources/ntp4/incognito_tab.css4
-rw-r--r--chromium/chrome/browser/resources/ntp4/incognito_tab.html10
-rw-r--r--chromium/chrome/browser/resources/ntp4/most_visited_page.css2
-rw-r--r--chromium/chrome/browser/resources/ntp4/most_visited_page.js2
-rw-r--r--chromium/chrome/browser/resources/ntp4/new_guest_tab_theme.css22
-rw-r--r--chromium/chrome/browser/resources/ntp4/new_tab.css43
-rw-r--r--chromium/chrome/browser/resources/ntp4/new_tab.html27
-rw-r--r--chromium/chrome/browser/resources/ntp4/new_tab.js72
-rw-r--r--chromium/chrome/browser/resources/ntp4/new_tab_theme.css3
-rw-r--r--chromium/chrome/browser/resources/ntp4/other_sessions.js6
-rw-r--r--chromium/chrome/browser/resources/ntp4/page_list_view.js28
-rw-r--r--chromium/chrome/browser/resources/ntp4/recently_closed.js122
-rw-r--r--chromium/chrome/browser/resources/ntp4/suggestions_page.css109
-rw-r--r--chromium/chrome/browser/resources/ntp4/suggestions_page.js478
-rw-r--r--chromium/chrome/browser/resources/ntp4/tile_page.js6
-rw-r--r--chromium/chrome/browser/resources/omnibox/omnibox.html3
-rw-r--r--chromium/chrome/browser/resources/omnibox/omnibox.js21
-rw-r--r--chromium/chrome/browser/resources/options/autofill_edit_address_overlay.js13
-rw-r--r--chromium/chrome/browser/resources/options/autofill_edit_creditcard_overlay.js10
-rw-r--r--chromium/chrome/browser/resources/options/autofill_edit_overlay.css13
-rw-r--r--chromium/chrome/browser/resources/options/autofill_options.css23
-rw-r--r--chromium/chrome/browser/resources/options/autofill_options.html10
-rw-r--r--chromium/chrome/browser/resources/options/autofill_options.js63
-rw-r--r--chromium/chrome/browser/resources/options/autofill_options_list.js212
-rw-r--r--chromium/chrome/browser/resources/options/browser_options.css41
-rw-r--r--chromium/chrome/browser/resources/options/browser_options.html96
-rw-r--r--chromium/chrome/browser/resources/options/browser_options.js227
-rw-r--r--chromium/chrome/browser/resources/options/browser_options_profile_list.js13
-rw-r--r--chromium/chrome/browser/resources/options/browser_options_startup_page_list.js4
-rw-r--r--chromium/chrome/browser/resources/options/certificate_manager.js21
-rw-r--r--chromium/chrome/browser/resources/options/certificate_tree.js4
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/accounts_options_page.css16
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/accounts_user_list.js2
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/bluetooth_add_device_overlay.js2
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/bluetooth_device_list.js3
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/bluetooth_pair_device_overlay.js4
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/change_picture_options.css6
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/change_picture_options.js12
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/display_options.css20
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/display_options.html20
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/display_options.js240
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/display_overscan.css12
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/internet_detail.css8
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/internet_detail.html48
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/internet_detail.js758
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/keyboard_overlay.js6
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/network_list.js773
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/onc_data.js13
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/preferred_networks.js13
-rw-r--r--chromium/chrome/browser/resources/options/chromeos/vpn_providers.js123
-rw-r--r--chromium/chrome/browser/resources/options/clear_browser_data_overlay.css6
-rw-r--r--chromium/chrome/browser/resources/options/clear_browser_data_overlay.html4
-rw-r--r--chromium/chrome/browser/resources/options/compiled_resources.gyp9
-rw-r--r--chromium/chrome/browser/resources/options/content_settings.html91
-rw-r--r--chromium/chrome/browser/resources/options/content_settings.js12
-rw-r--r--chromium/chrome/browser/resources/options/content_settings_exceptions_area.html30
-rw-r--r--chromium/chrome/browser/resources/options/content_settings_exceptions_area.js70
-rw-r--r--chromium/chrome/browser/resources/options/controlled_setting.css88
-rw-r--r--chromium/chrome/browser/resources/options/controlled_setting.js218
-rw-r--r--chromium/chrome/browser/resources/options/cookies_list.js13
-rw-r--r--chromium/chrome/browser/resources/options/deletable_item_list.js16
-rw-r--r--chromium/chrome/browser/resources/options/editable_text_field.js11
-rw-r--r--chromium/chrome/browser/resources/options/handler_options.html14
-rw-r--r--chromium/chrome/browser/resources/options/handler_options.js2
-rw-r--r--chromium/chrome/browser/resources/options/handler_options_list.js6
-rw-r--r--chromium/chrome/browser/resources/options/hotword_search_setting_indicator.js13
-rw-r--r--chromium/chrome/browser/resources/options/inline_editable_list.js403
-rw-r--r--chromium/chrome/browser/resources/options/language_add_language_overlay.js2
-rw-r--r--chromium/chrome/browser/resources/options/language_dictionary_overlay_word_list.js26
-rw-r--r--chromium/chrome/browser/resources/options/language_options.js17
-rw-r--r--chromium/chrome/browser/resources/options/manage_profile_overlay.css2
-rw-r--r--chromium/chrome/browser/resources/options/manage_profile_overlay.js34
-rw-r--r--chromium/chrome/browser/resources/options/options.html15
-rw-r--r--chromium/chrome/browser/resources/options/options.js1
-rw-r--r--chromium/chrome/browser/resources/options/options_bundle.js3
-rw-r--r--chromium/chrome/browser/resources/options/options_page.css15
-rw-r--r--chromium/chrome/browser/resources/options/password_manager.html11
-rw-r--r--chromium/chrome/browser/resources/options/password_manager.js18
-rw-r--r--chromium/chrome/browser/resources/options/password_manager_list.css9
-rw-r--r--chromium/chrome/browser/resources/options/password_manager_list.js148
-rw-r--r--chromium/chrome/browser/resources/options/pref_ui.js54
-rw-r--r--chromium/chrome/browser/resources/options/reset_profile_settings_overlay.css2
-rw-r--r--chromium/chrome/browser/resources/options/search_engine_manager_engine_list.js11
-rw-r--r--chromium/chrome/browser/resources/options/search_page.js2
-rw-r--r--chromium/chrome/browser/resources/options/settings_banner.css6
-rw-r--r--chromium/chrome/browser/resources/options/startup_overlay.js1
-rw-r--r--chromium/chrome/browser/resources/options/subpages_tab_controls.css2
-rw-r--r--chromium/chrome/browser/resources/options/supervised_user_create_confirm.css6
-rw-r--r--chromium/chrome/browser/resources/options/supervised_user_import.css14
-rw-r--r--chromium/chrome/browser/resources/options/supervised_user_import.js7
-rw-r--r--chromium/chrome/browser/resources/options/supervised_user_learn_more.css2
-rw-r--r--chromium/chrome/browser/resources/options/supervised_user_list.js1
-rw-r--r--chromium/chrome/browser/resources/options/supervised_user_list_data.js4
-rw-r--r--chromium/chrome/browser/resources/options/sync_setup_overlay.css269
-rw-r--r--chromium/chrome/browser/resources/options/sync_setup_overlay.html22
-rw-r--r--chromium/chrome/browser/resources/options/sync_setup_overlay.js68
-rw-r--r--chromium/chrome/browser/resources/options/website_settings.css1
-rw-r--r--chromium/chrome/browser/resources/options_test_resources.grd19
-rw-r--r--chromium/chrome/browser/resources/password_manager_internals/password_manager_internals.html4
-rw-r--r--chromium/chrome/browser/resources/pdf/background.js63
-rw-r--r--chromium/chrome/browser/resources/pdf/browser_api.js159
-rw-r--r--chromium/chrome/browser/resources/pdf/content_script.js7
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-bookmark/viewer-bookmark.css7
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-bookmark/viewer-bookmark.html17
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-bookmark/viewer-bookmark.js21
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-bookmarks-content/viewer-bookmarks-content.html10
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-button/img/hiDPI/button_fit_page.png (renamed from chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/hiDPI/button_fit_page.png)bin1776 -> 1776 bytes
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-button/img/hiDPI/button_fit_width.png (renamed from chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/hiDPI/button_fit_width.png)bin1268 -> 1268 bytes
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-button/img/hiDPI/button_play.png (renamed from chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/hiDPI/button_play.png)bin1087 -> 1087 bytes
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-button/img/hiDPI/button_print.png (renamed from chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/hiDPI/button_print.png)bin904 -> 904 bytes
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-button/img/hiDPI/button_save.png (renamed from chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/hiDPI/button_save.png)bin979 -> 979 bytes
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-button/img/hiDPI/button_zoom_in.png (renamed from chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/hiDPI/button_zoom_in.png)bin1859 -> 1859 bytes
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-button/img/hiDPI/button_zoom_out.png (renamed from chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/hiDPI/button_zoom_out.png)bin1803 -> 1803 bytes
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-button/img/lowDPI/button_fit_page.png (renamed from chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/lowDPI/button_fit_page.png)bin999 -> 999 bytes
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-button/img/lowDPI/button_fit_width.png (renamed from chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/lowDPI/button_fit_width.png)bin696 -> 696 bytes
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-button/img/lowDPI/button_play.png (renamed from chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/lowDPI/button_play.png)bin664 -> 664 bytes
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-button/img/lowDPI/button_print.png (renamed from chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/lowDPI/button_print.png)bin797 -> 797 bytes
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-button/img/lowDPI/button_save.png (renamed from chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/lowDPI/button_save.png)bin695 -> 695 bytes
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-button/img/lowDPI/button_zoom_in.png (renamed from chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/lowDPI/button_zoom_in.png)bin1115 -> 1115 bytes
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-button/img/lowDPI/button_zoom_out.png (renamed from chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/lowDPI/button_zoom_out.png)bin1079 -> 1079 bytes
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-button/viewer-button.css (renamed from chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/viewer-button.css)0
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-button/viewer-button.html (renamed from chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/viewer-button.html)2
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-button/viewer-button.js (renamed from chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/viewer-button.js)0
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-error-screen/viewer-error-screen.css (renamed from chromium/chrome/browser/resources/pdf/html_office/elements/viewer-error-screen/viewer-error-screen.css)4
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-error-screen/viewer-error-screen.html (renamed from chromium/chrome/browser/resources/pdf/html_office/elements/viewer-error-screen/viewer-error-screen.html)2
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-error-screen/viewer-error-screen.js (renamed from chromium/chrome/browser/resources/pdf/html_office/elements/viewer-error-screen/viewer-error-screen.js)0
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-page-indicator/viewer-page-indicator.css (renamed from chromium/chrome/browser/resources/pdf/html_office/elements/viewer-page-indicator/viewer-page-indicator.css)0
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-page-indicator/viewer-page-indicator.html (renamed from chromium/chrome/browser/resources/pdf/html_office/elements/viewer-page-indicator/viewer-page-indicator.html)2
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-page-indicator/viewer-page-indicator.js (renamed from chromium/chrome/browser/resources/pdf/html_office/elements/viewer-page-indicator/viewer-page-indicator.js)0
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-page-selector/viewer-page-selector.css34
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-page-selector/viewer-page-selector.html14
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-page-selector/viewer-page-selector.js56
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-password-screen/viewer-password-screen.css (renamed from chromium/chrome/browser/resources/pdf/html_office/elements/viewer-password-screen/viewer-password-screen.css)10
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-password-screen/viewer-password-screen.html (renamed from chromium/chrome/browser/resources/pdf/html_office/elements/viewer-password-screen/viewer-password-screen.html)3
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-password-screen/viewer-password-screen.js (renamed from chromium/chrome/browser/resources/pdf/html_office/elements/viewer-password-screen/viewer-password-screen.js)13
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.css66
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.html47
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.js88
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-progress-bar/viewer-progress-bar.css (renamed from chromium/chrome/browser/resources/pdf/html_office/elements/viewer-progress-bar/viewer-progress-bar.css)4
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-progress-bar/viewer-progress-bar.html (renamed from chromium/chrome/browser/resources/pdf/html_office/elements/viewer-progress-bar/viewer-progress-bar.html)2
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-progress-bar/viewer-progress-bar.js (renamed from chromium/chrome/browser/resources/pdf/html_office/elements/viewer-progress-bar/viewer-progress-bar.js)2
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-toolbar/viewer-toolbar.css (renamed from chromium/chrome/browser/resources/pdf/html_office/elements/viewer-toolbar/viewer-toolbar.css)0
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-toolbar/viewer-toolbar.html (renamed from chromium/chrome/browser/resources/pdf/html_office/elements/viewer-toolbar/viewer-toolbar.html)2
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-toolbar/viewer-toolbar.js (renamed from chromium/chrome/browser/resources/pdf/html_office/elements/viewer-toolbar/viewer-toolbar.js)0
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-zoom-toolbar/viewer-zoom-button.css9
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-zoom-toolbar/viewer-zoom-button.html11
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-zoom-toolbar/viewer-zoom-button.js30
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-zoom-toolbar/viewer-zoom-toolbar.css44
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-zoom-toolbar/viewer-zoom-toolbar.html20
-rw-r--r--chromium/chrome/browser/resources/pdf/elements/viewer-zoom-toolbar/viewer-zoom-toolbar.js61
-rw-r--r--chromium/chrome/browser/resources/pdf/html_office/OWNERS3
-rw-r--r--chromium/chrome/browser/resources/pdf/html_office/codereview.settings7
-rw-r--r--chromium/chrome/browser/resources/pdf/includes.js12
-rw-r--r--chromium/chrome/browser/resources/pdf/index-material.css62
-rw-r--r--chromium/chrome/browser/resources/pdf/index-material.html41
-rw-r--r--chromium/chrome/browser/resources/pdf/index.css2
-rw-r--r--chromium/chrome/browser/resources/pdf/index.html56
-rw-r--r--chromium/chrome/browser/resources/pdf/main.js75
-rw-r--r--chromium/chrome/browser/resources/pdf/manifest.json31
-rw-r--r--chromium/chrome/browser/resources/pdf/navigator.js95
-rw-r--r--chromium/chrome/browser/resources/pdf/open_pdf_params_parser.js80
-rw-r--r--chromium/chrome/browser/resources/pdf/pdf.js546
-rw-r--r--chromium/chrome/browser/resources/pdf/pdf_extension_test.cc95
-rw-r--r--chromium/chrome/browser/resources/pdf/pdf_scripting_api.js188
-rw-r--r--chromium/chrome/browser/resources/pdf/ui_manager.js57
-rw-r--r--chromium/chrome/browser/resources/pdf/viewport.js97
-rw-r--r--chromium/chrome/browser/resources/pdf/viewport_scroller.js135
-rw-r--r--chromium/chrome/browser/resources/pdf/zoom_manager.js83
-rw-r--r--chromium/chrome/browser/resources/plugin_metadata/OWNERS1
-rw-r--r--chromium/chrome/browser/resources/plugin_metadata/plugins_chromeos.json13
-rw-r--r--chromium/chrome/browser/resources/plugin_metadata/plugins_linux.json13
-rw-r--r--chromium/chrome/browser/resources/plugin_metadata/plugins_mac.json28
-rw-r--r--chromium/chrome/browser/resources/plugin_metadata/plugins_win.json39
-rw-r--r--chromium/chrome/browser/resources/plugins.css8
-rw-r--r--chromium/chrome/browser/resources/plugins.html9
-rw-r--r--chromium/chrome/browser/resources/policy.html21
-rw-r--r--chromium/chrome/browser/resources/policy.js25
-rw-r--r--chromium/chrome/browser/resources/predictors/predictors.html5
-rw-r--r--chromium/chrome/browser/resources/prerender_url_whitelist.dat1
-rw-r--r--chromium/chrome/browser/resources/print_preview/OWNERS2
-rw-r--r--chromium/chrome/browser/resources/print_preview/cloud_print_interface.js16
-rw-r--r--chromium/chrome/browser/resources/print_preview/component.js4
-rw-r--r--chromium/chrome/browser/resources/print_preview/data/app_state.js23
-rw-r--r--chromium/chrome/browser/resources/print_preview/data/destination.js63
-rw-r--r--chromium/chrome/browser/resources/print_preview/data/destination_store.js175
-rw-r--r--chromium/chrome/browser/resources/print_preview/data/invitation_store.js6
-rw-r--r--chromium/chrome/browser/resources/print_preview/data/local_parsers.js26
-rw-r--r--chromium/chrome/browser/resources/print_preview/data/margins.js2
-rw-r--r--chromium/chrome/browser/resources/print_preview/data/measurement_system.js6
-rw-r--r--chromium/chrome/browser/resources/print_preview/data/page_number_set.js6
-rw-r--r--chromium/chrome/browser/resources/print_preview/data/print_ticket_store.js7
-rw-r--r--chromium/chrome/browser/resources/print_preview/data/ticket_items/color.js8
-rw-r--r--chromium/chrome/browser/resources/print_preview/data/ticket_items/custom_margins.js2
-rw-r--r--chromium/chrome/browser/resources/print_preview/data/ticket_items/page_range.js4
-rw-r--r--chromium/chrome/browser/resources/print_preview/data/ticket_items/vendor_items.js6
-rw-r--r--chromium/chrome/browser/resources/print_preview/data/user_info.js6
-rw-r--r--chromium/chrome/browser/resources/print_preview/native_layer.js111
-rw-r--r--chromium/chrome/browser/resources/print_preview/preview_generator.js2
-rw-r--r--chromium/chrome/browser/resources/print_preview/previewarea/margin_control.css1
-rw-r--r--chromium/chrome/browser/resources/print_preview/previewarea/margin_control_container.js19
-rw-r--r--chromium/chrome/browser/resources/print_preview/previewarea/preview_area.css11
-rw-r--r--chromium/chrome/browser/resources/print_preview/previewarea/preview_area.js41
-rw-r--r--chromium/chrome/browser/resources/print_preview/print_header.js2
-rw-r--r--chromium/chrome/browser/resources/print_preview/print_preview.html9
-rw-r--r--chromium/chrome/browser/resources/print_preview/print_preview.js47
-rw-r--r--chromium/chrome/browser/resources/print_preview/print_preview_page.html117
-rw-r--r--chromium/chrome/browser/resources/print_preview/print_preview_utils.js45
-rw-r--r--chromium/chrome/browser/resources/print_preview/search/destination_list.js32
-rw-r--r--chromium/chrome/browser/resources/print_preview/search/destination_list_item.css26
-rw-r--r--chromium/chrome/browser/resources/print_preview/search/destination_list_item.html4
-rw-r--r--chromium/chrome/browser/resources/print_preview/search/destination_list_item.js59
-rw-r--r--chromium/chrome/browser/resources/print_preview/search/destination_search.css25
-rw-r--r--chromium/chrome/browser/resources/print_preview/search/destination_search.html11
-rw-r--r--chromium/chrome/browser/resources/print_preview/search/destination_search.js30
-rw-r--r--chromium/chrome/browser/resources/print_preview/settings/advanced_settings/advanced_settings.html6
-rw-r--r--chromium/chrome/browser/resources/print_preview/settings/advanced_settings/advanced_settings.js8
-rw-r--r--chromium/chrome/browser/resources/print_preview/settings/advanced_settings/advanced_settings_item.js43
-rw-r--r--chromium/chrome/browser/resources/print_preview/settings/copies_settings.html10
-rw-r--r--chromium/chrome/browser/resources/print_preview/settings/copies_settings.js6
-rw-r--r--chromium/chrome/browser/resources/print_preview/settings/destination_settings.css4
-rw-r--r--chromium/chrome/browser/resources/print_preview/settings/dpi_settings.css2
-rw-r--r--chromium/chrome/browser/resources/print_preview/settings/media_size_settings.css2
-rw-r--r--chromium/chrome/browser/resources/print_preview/settings/more_settings.css8
-rw-r--r--chromium/chrome/browser/resources/print_preview/settings/more_settings.js4
-rw-r--r--chromium/chrome/browser/resources/print_preview/settings/settings_section_select.js37
-rw-r--r--chromium/chrome/browser/resources/profile_signin_confirmation.html9
-rw-r--r--chromium/chrome/browser/resources/profiler/profiler.html8
-rw-r--r--chromium/chrome/browser/resources/profiler/profiler.js21
-rw-r--r--chromium/chrome/browser/resources/quota_internals/event_handler.js4
-rw-r--r--chromium/chrome/browser/resources/quota_internals/main.css2
-rw-r--r--chromium/chrome/browser/resources/quota_internals/main.html9
-rw-r--r--chromium/chrome/browser/resources/reader_out_of_date.html79
-rw-r--r--chromium/chrome/browser/resources/roboto/OWNERS1
-rw-r--r--chromium/chrome/browser/resources/roboto/roboto.woffbin14828 -> 0 bytes
-rw-r--r--chromium/chrome/browser/resources/roboto/roboto.woff2bin11892 -> 0 bytes
-rw-r--r--chromium/chrome/browser/resources/search_header.css2
-rw-r--r--chromium/chrome/browser/resources/security_warnings/captive_portal.js5
-rw-r--r--chromium/chrome/browser/resources/security_warnings/extended_reporting.js40
-rw-r--r--chromium/chrome/browser/resources/security_warnings/images/1x/brokenssl_red.pngbin1609 -> 1570 bytes
-rw-r--r--chromium/chrome/browser/resources/security_warnings/images/1x/captive_portal_page_icon.pngbin0 -> 1349 bytes
-rw-r--r--chromium/chrome/browser/resources/security_warnings/images/1x/clock.pngbin4689 -> 1591 bytes
-rw-r--r--chromium/chrome/browser/resources/security_warnings/images/1x/stop_sign.pngbin1086 -> 1651 bytes
-rw-r--r--chromium/chrome/browser/resources/security_warnings/images/2x/brokenssl_red.pngbin3151 -> 3024 bytes
-rw-r--r--chromium/chrome/browser/resources/security_warnings/images/2x/captive_portal_page_icon.pngbin0 -> 2827 bytes
-rw-r--r--chromium/chrome/browser/resources/security_warnings/images/2x/clock.pngbin6865 -> 3461 bytes
-rw-r--r--chromium/chrome/browser/resources/security_warnings/images/2x/stop_sign.pngbin1872 -> 3165 bytes
-rw-r--r--chromium/chrome/browser/resources/security_warnings/interstitial_ui.html21
-rw-r--r--chromium/chrome/browser/resources/security_warnings/interstitial_v2.css485
-rw-r--r--chromium/chrome/browser/resources/security_warnings/interstitial_v2.html64
-rw-r--r--chromium/chrome/browser/resources/security_warnings/interstitial_v2.js101
-rw-r--r--chromium/chrome/browser/resources/security_warnings/interstitial_v2_mobile.js47
-rw-r--r--chromium/chrome/browser/resources/security_warnings/safe_browsing.js35
-rw-r--r--chromium/chrome/browser/resources/security_warnings/ssl.js8
-rw-r--r--chromium/chrome/browser/resources/set_as_default_browser.css14
-rw-r--r--chromium/chrome/browser/resources/set_as_default_browser.html8
-rw-r--r--chromium/chrome/browser/resources/settings/OWNERS5
-rw-r--r--chromium/chrome/browser/resources/settings/a11y_page/a11y_page.css20
-rw-r--r--chromium/chrome/browser/resources/settings/a11y_page/a11y_page.html82
-rw-r--r--chromium/chrome/browser/resources/settings/a11y_page/a11y_page.js72
-rw-r--r--chromium/chrome/browser/resources/settings/checkbox/checkbox.css19
-rw-r--r--chromium/chrome/browser/resources/settings/checkbox/checkbox.html19
-rw-r--r--chromium/chrome/browser/resources/settings/checkbox/checkbox.js44
-rw-r--r--chromium/chrome/browser/resources/settings/date_time_page/date_time_page.css14
-rw-r--r--chromium/chrome/browser/resources/settings/date_time_page/date_time_page.html22
-rw-r--r--chromium/chrome/browser/resources/settings/date_time_page/date_time_page.js76
-rw-r--r--chromium/chrome/browser/resources/settings/date_time_page/demo.html12
-rw-r--r--chromium/chrome/browser/resources/settings/date_time_page/demo.js9
-rw-r--r--chromium/chrome/browser/resources/settings/downloads_page/downloads_page.css14
-rw-r--r--chromium/chrome/browser/resources/settings/downloads_page/downloads_page.html33
-rw-r--r--chromium/chrome/browser/resources/settings/downloads_page/downloads_page.js82
-rw-r--r--chromium/chrome/browser/resources/settings/internet_page/internet_detail_page.html49
-rw-r--r--chromium/chrome/browser/resources/settings/internet_page/internet_detail_page.js216
-rw-r--r--chromium/chrome/browser/resources/settings/internet_page/internet_detail_page_style.html38
-rw-r--r--chromium/chrome/browser/resources/settings/internet_page/internet_page.html12
-rw-r--r--chromium/chrome/browser/resources/settings/internet_page/internet_page.js67
-rw-r--r--chromium/chrome/browser/resources/settings/internet_page/network_summary.html49
-rw-r--r--chromium/chrome/browser/resources/settings/internet_page/network_summary.js343
-rw-r--r--chromium/chrome/browser/resources/settings/internet_page/network_summary_item.html38
-rw-r--r--chromium/chrome/browser/resources/settings/internet_page/network_summary_item.js171
-rw-r--r--chromium/chrome/browser/resources/settings/internet_page/network_summary_item_style.html31
-rw-r--r--chromium/chrome/browser/resources/settings/internet_page/network_summary_style.html10
-rw-r--r--chromium/chrome/browser/resources/settings/polymer_config.js5
-rw-r--r--chromium/chrome/browser/resources/settings/pref_tracker/pref_tracker.html6
-rw-r--r--chromium/chrome/browser/resources/settings/pref_tracker/pref_tracker.js83
-rw-r--r--chromium/chrome/browser/resources/settings/prefs/prefs.html8
-rw-r--r--chromium/chrome/browser/resources/settings/prefs/prefs.js136
-rw-r--r--chromium/chrome/browser/resources/settings/prefs/prefs_types.js23
-rw-r--r--chromium/chrome/browser/resources/settings/routes.html17
-rw-r--r--chromium/chrome/browser/resources/settings/search_engines_page/search_engine_adder.css20
-rw-r--r--chromium/chrome/browser/resources/settings/search_engines_page/search_engine_adder.html43
-rw-r--r--chromium/chrome/browser/resources/settings/search_engines_page/search_engine_adder.js29
-rw-r--r--chromium/chrome/browser/resources/settings/search_engines_page/search_engines_page.html17
-rw-r--r--chromium/chrome/browser/resources/settings/search_engines_page/search_engines_page.js68
-rw-r--r--chromium/chrome/browser/resources/settings/search_page/search_page.css17
-rw-r--r--chromium/chrome/browser/resources/settings/search_page/search_page.html29
-rw-r--r--chromium/chrome/browser/resources/settings/search_page/search_page.js114
-rw-r--r--chromium/chrome/browser/resources/settings/settings.html12
-rw-r--r--chromium/chrome/browser/resources/settings/settings.js16
-rw-r--r--chromium/chrome/browser/resources/settings/settings_drawer/settings_drawer.css32
-rw-r--r--chromium/chrome/browser/resources/settings/settings_drawer/settings_drawer.html21
-rw-r--r--chromium/chrome/browser/resources/settings/settings_drawer/settings_drawer.js61
-rw-r--r--chromium/chrome/browser/resources/settings/settings_main/settings_main.css24
-rw-r--r--chromium/chrome/browser/resources/settings/settings_main/settings_main.html54
-rw-r--r--chromium/chrome/browser/resources/settings/settings_main/settings_main.js121
-rw-r--r--chromium/chrome/browser/resources/settings/settings_menu/settings_menu.css23
-rw-r--r--chromium/chrome/browser/resources/settings/settings_menu/settings_menu.html20
-rw-r--r--chromium/chrome/browser/resources/settings/settings_menu/settings_menu.js39
-rw-r--r--chromium/chrome/browser/resources/settings/settings_page/settings_page.css28
-rw-r--r--chromium/chrome/browser/resources/settings/settings_page/settings_page_header.css28
-rw-r--r--chromium/chrome/browser/resources/settings/settings_page/settings_page_header.html23
-rw-r--r--chromium/chrome/browser/resources/settings/settings_page/settings_page_header.js126
-rw-r--r--chromium/chrome/browser/resources/settings/settings_resources.grd197
-rw-r--r--chromium/chrome/browser/resources/settings/settings_ui/settings_ui.css15
-rw-r--r--chromium/chrome/browser/resources/settings/settings_ui/settings_ui.html21
-rw-r--r--chromium/chrome/browser/resources/settings/settings_ui/settings_ui.js37
-rw-r--r--chromium/chrome/browser/resources/signin_internals/signin_index.html22
-rw-r--r--chromium/chrome/browser/resources/signin_internals/signin_internals.js3
-rw-r--r--chromium/chrome/browser/resources/suggestions_internals/suggestions_internals.css37
-rw-r--r--chromium/chrome/browser/resources/suggestions_internals/suggestions_internals.html25
-rw-r--r--chromium/chrome/browser/resources/suggestions_internals/suggestions_internals.js186
-rw-r--r--chromium/chrome/browser/resources/supervised_user_block_interstitial.css14
-rw-r--r--chromium/chrome/browser/resources/supervised_user_block_interstitial.html47
-rw-r--r--chromium/chrome/browser/resources/supervised_user_block_interstitial.js27
-rw-r--r--chromium/chrome/browser/resources/sync_file_system_internals/main.css8
-rw-r--r--chromium/chrome/browser/resources/sync_file_system_internals/main.html9
-rw-r--r--chromium/chrome/browser/resources/sync_file_system_internals/utils.js2
-rw-r--r--chromium/chrome/browser/resources/sync_internals/OWNERS1
-rw-r--r--chromium/chrome/browser/resources/sync_internals/about.css5
-rw-r--r--chromium/chrome/browser/resources/sync_internals/data.js2
-rw-r--r--chromium/chrome/browser/resources/sync_internals/index.html9
-rw-r--r--chromium/chrome/browser/resources/sync_internals/sync_node_browser.css2
-rw-r--r--chromium/chrome/browser/resources/translate_internals/translate_internals.html9
-rw-r--r--chromium/chrome/browser/resources/uber/uber.html8
-rw-r--r--chromium/chrome/browser/resources/uber/uber.js3
-rw-r--r--chromium/chrome/browser/resources/uber/uber_frame.css4
-rw-r--r--chromium/chrome/browser/resources/uber/uber_frame.html21
-rw-r--r--chromium/chrome/browser/resources/uber/uber_frame.js15
-rw-r--r--chromium/chrome/browser/resources/uber/uber_shared.css2
-rw-r--r--chromium/chrome/browser/resources/uber/uber_utils.js2
-rw-r--r--chromium/chrome/browser/resources/user_actions/user_actions.html3
-rw-r--r--chromium/chrome/browser/resources/user_actions/user_actions.js2
-rw-r--r--chromium/chrome/browser/resources/user_manager/control_bar.css12
-rw-r--r--chromium/chrome/browser/resources/user_manager/user_manager.css25
-rw-r--r--chromium/chrome/browser/resources/user_manager/user_manager.html11
-rw-r--r--chromium/chrome/browser/resources/user_manager/user_manager.js7
-rw-r--r--chromium/chrome/browser/resources/user_manager/user_manager_tutorial.css16
-rw-r--r--chromium/chrome/browser/resources/user_manager/user_manager_tutorial.html1
-rw-r--r--chromium/chrome/browser/resources/webstore_app/OWNERS13
-rw-r--r--chromium/chrome/browser/resources/whispernet_proxy/background.html3
-rw-r--r--chromium/chrome/browser/resources/whispernet_proxy/js/init.js74
-rw-r--r--chromium/chrome/browser/resources/whispernet_proxy/js/nacl.js7
-rw-r--r--chromium/chrome/browser/resources/whispernet_proxy/js/wrapper.js146
-rw-r--r--chromium/chrome/browser/resources/whispernet_proxy/whispernet_proxy.nmf.png2
-rw-r--r--chromium/chrome/browser/resources/whispernet_proxy/whispernet_proxy_pnacl.pexe.pngbin738684 -> 808408 bytes
-rw-r--r--chromium/chrome/browser/safe_browsing/incident_reporting/verifier_test/verifier_unittest.gyp (renamed from chromium/chrome/browser/safe_browsing/verifier_test/verifier_unittest.gyp)6
-rw-r--r--chromium/chrome/browser/ui/libgtk2ui/libgtk2ui.gyp18
1066 files changed, 38982 insertions, 21367 deletions
diff --git a/chromium/chrome/browser/apps/app_shim/browser_app_shim.gypi b/chromium/chrome/browser/apps/app_shim/browser_app_shim.gypi
index 4c6c9aa80c8..806ebe7a1f2 100644
--- a/chromium/chrome/browser/apps/app_shim/browser_app_shim.gypi
+++ b/chromium/chrome/browser/apps/app_shim/browser_app_shim.gypi
@@ -10,8 +10,9 @@
'target_name': 'browser_app_shim',
'type': 'static_library',
'dependencies': [
- # Since browser_app_shim and browser depend on each other, we omit the
- # dependency on browser here.
+ # Since browser_app_shim and chrome.gyp:browser depend on each other,
+ # we omit the dependency on browser here.
+ '../content/content.gyp:content_browser',
'../content/content.gyp:content_common',
],
'sources': [
diff --git a/chromium/chrome/browser/browser_resources.grd b/chromium/chrome/browser/browser_resources.grd
index 7ce174275e8..ed14ec79441 100644
--- a/chromium/chrome/browser/browser_resources.grd
+++ b/chromium/chrome/browser/browser_resources.grd
@@ -23,16 +23,11 @@
<structure name="IDR_APP_LIST_START_PAGE_CSS" file="resources\app_list\start_page.css" flattenhtml="true" type="chrome_html" />
<structure name="IDR_APP_LIST_START_PAGE_HTML" file="resources\app_list\start_page.html" flattenhtml="true" type="chrome_html" />
<structure name="IDR_APP_LIST_START_PAGE_JS" file="resources\app_list\start_page.js" flattenhtml="true" type="chrome_html" />
- <structure name="IDR_APP_LIST_HOTWORD_NACL_NMF" file="resources\app_list\hotword_nacl.nmf" flattenhtml="true" type="chrome_html" />
</if>
<if expr="is_android">
<structure name="IDR_CONTEXTUAL_SEARCH_PROMO_CSS" file="resources\contextual_search\promo.css" flattenhtml="true" type="chrome_html" />
<structure name="IDR_CONTEXTUAL_SEARCH_PROMO_HTML" file="resources\contextual_search\promo.html" flattenhtml="true" type="chrome_html" />
</if>
- <if expr="chromeos">
- <structure name="IDR_DEMO_USER_LOGIN_HTML" file="resources\chromeos\login\demo_user_login.html" flattenhtml="true" type="chrome_html" />
- <structure name="IDR_DEMO_USER_LOGIN_JS" file="resources\chromeos\login\demo_user_login.js" flattenhtml="true" type="chrome_html" />
- </if>
<if expr="not is_android">
<structure name="IDR_DOWNLOADS_CSS" file="resources\downloads\downloads.css" flattenhtml="true" type="chrome_html" />
<structure name="IDR_DOWNLOADS_HTML" file="resources\downloads\downloads.html" flattenhtml="true" allowexternalscript="true" type="chrome_html" />
@@ -61,7 +56,6 @@
</if>
<if expr="not is_android">
<structure name="IDR_NEW_INCOGNITO_TAB_THEME_CSS" file="resources\ntp4\new_incognito_tab_theme.css" flattenhtml="true" type="chrome_html" />
- <structure name="IDR_NEW_GUEST_TAB_THEME_CSS" file="resources\ntp4\new_guest_tab_theme.css" flattenhtml="true" type="chrome_html" />
<structure name="IDR_NEW_TAB_4_HTML" file="resources\ntp4\new_tab.html" flattenhtml="true" type="chrome_html" />
<structure name="IDR_NEW_TAB_4_THEME_CSS" file="resources\ntp4\new_tab_theme.css" flattenhtml="true" type="chrome_html" />
</if>
@@ -71,15 +65,17 @@
<structure name="IDR_OOBE_ENROLLMENT_HTML" file="resources\chromeos\login\oobe_screen_oauth_enrollment.html" flattenhtml="true" type="chrome_html" />
<structure name="IDR_OOBE_ENROLLMENT_CSS" file="resources\chromeos\login\oobe_screen_oauth_enrollment.css" flattenhtml="true" type="chrome_html" />
<structure name="IDR_OOBE_ENROLLMENT_JS" file="resources\chromeos\login\oobe_screen_oauth_enrollment.js" flattenhtml="true" type="chrome_html" />
+ <structure name="IDR_OOBE_ENROLLMENT_WEBVIEW_HTML" file="resources\chromeos\login\oobe_screen_oauth_enrollment_webview.html" flattenhtml="true" type="chrome_html" />
+ <structure name="IDR_OOBE_ENROLLMENT_WEBVIEW_CSS" file="resources\chromeos\login\oobe_screen_oauth_enrollment_webview.css" flattenhtml="true" type="chrome_html" />
+ <structure name="IDR_OOBE_ENROLLMENT_WEBVIEW_JS" file="resources\chromeos\login\oobe_screen_oauth_enrollment_webview.js" flattenhtml="true" type="chrome_html" />
<structure name="IDR_KEYBOARD_UTILS_JS" file="resources\chromeos\keyboard\keyboard_utils.js" flattenhtml="true" type="chrome_html" />
- <structure name="IDR_NETWORK_CONFIG_JS" file="resources\chromeos\network\network_config.js" flattenhtml="true" type="chrome_html" />
- <!-- TODO(dzhioev): move polymer to chrome://resources when
- http://crbug.com/418199 is fixed. -->
- <part file="polymer_resources.grdp" />
- <structure name="IDR_CUSTOM_ELEMENTS_HTML" file="resources\chromeos\login\custom_elements.html" flattenhtml="true" type="chrome_html" />
- <structure name="IDR_CUSTOM_ELEMENTS_JS" file="resources\chromeos\login\custom_elements.js" flattenhtml="true" type="chrome_html" />
+ <structure name="IDR_CUSTOM_ELEMENTS_OOBE_HTML" file="resources\chromeos\login\custom_elements_oobe.html" flattenhtml="true" type="chrome_html" />
+ <structure name="IDR_CUSTOM_ELEMENTS_OOBE_JS" file="resources\chromeos\login\custom_elements_oobe.js" flattenhtml="true" type="chrome_html" />
+ <structure name="IDR_CUSTOM_ELEMENTS_LOGIN_HTML" file="resources\chromeos\login\custom_elements_login.html" flattenhtml="true" type="chrome_html" />
+ <structure name="IDR_CUSTOM_ELEMENTS_LOGIN_JS" file="resources\chromeos\login\custom_elements_login.js" flattenhtml="true" type="chrome_html" />
</if>
<structure name="IDR_READER_OUT_OF_DATE_HTML" file="resources\reader_out_of_date.html" flattenhtml="true" type="chrome_html" />
+ <structure name="IDR_SECURITY_INTERSTITIAL_UI_HTML" file="resources\security_warnings\interstitial_ui.html" flattenhtml="true" type="chrome_html" />
<structure name="IDR_SECURITY_INTERSTITIAL_HTML" file="resources\security_warnings\interstitial_v2.html" flattenhtml="true" type="chrome_html" />
</structures>
<includes>
@@ -87,7 +83,7 @@
<include name="IDR_ABOUT_CONFLICTS_HTML" file="resources\about_conflicts.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" />
<include name="IDR_ABOUT_CONFLICTS_JS" file="resources\about_conflicts.js" type="BINDATA" />
</if>
- <if expr="not is_android">
+ <if expr="enable_plugins">
<include name="IDR_ABOUT_FLASH_HTML" file="resources\about_flash.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" />
<include name="IDR_ABOUT_FLASH_JS" file="resources\about_flash.js" type="BINDATA" />
</if>
@@ -97,8 +93,6 @@
<include name="IDR_ABOUT_NACL_CSS" file="resources\about_nacl.css" flattenhtml="true" type="chrome_html" />
<include name="IDR_ABOUT_NACL_JS" file="resources\about_nacl.js" type="BINDATA" />
</if>
- <include name="IDR_ABOUT_STATS_HTML" file="resources\about_stats.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" />
- <include name="IDR_ABOUT_STATS_JS" file="resources\about_stats.js" type="BINDATA" />
<if expr="not is_android">
<include name="IDR_ABOUT_SYS_HTML" file="resources\about_sys\about_sys.html" flattenhtml="true" type="BINDATA" />
</if>
@@ -120,6 +114,11 @@
<include name="IDR_CONTEXTUAL_SEARCH_PROMO_JS" file="resources\contextual_search\promo.js" flattenhtml="true" type="BINDATA" />
<include name="IDR_CONTEXTUAL_SEARCH_PROMO_HEADER_SVG" file="resources\contextual_search\header.svg" type="BINDATA" />
</if>
+ <if expr="not is_android and not is_ios">
+ <include name="IDR_COPRESENCE_CSS" file="resources\copresence.css" type="BINDATA" />
+ <include name="IDR_COPRESENCE_HTML" file="resources\copresence.html" type="BINDATA" />
+ <include name="IDR_COPRESENCE_JS" file="resources\copresence.js" type="BINDATA" />
+ </if>
<if expr="enable_printing">
<include name="IDR_CLOUDPRINT_MANIFEST" file="resources\cloud_print_app\manifest.json" type="BINDATA" />
</if>
@@ -134,30 +133,26 @@
<include name="IDR_DOMAIN_RELIABILITY_INTERNALS_CSS" file="resources\domain_reliability_internals.css" type="BINDATA" />
<include name="IDR_DOMAIN_RELIABILITY_INTERNALS_JS" file="resources\domain_reliability_internals.js" type="BINDATA" />
<if expr="not is_android">
- <include name="IDR_DOWNLOADS_JS" file="resources\downloads\downloads.js" type="BINDATA" />
+ <include name="IDR_DOWNLOAD_FOCUS_ROW_JS" file="resources\downloads\focus_row.js" type="BINDATA" />
+ <include name="IDR_DOWNLOAD_ITEM_JS" file="resources\downloads\item.js" type="BINDATA" />
+ <include name="IDR_DOWNLOAD_ITEM_VIEW_JS" file="resources\downloads\item_view.js" type="BINDATA" />
+ <include name="IDR_DOWNLOAD_MANAGER_JS" file="resources\downloads\manager.js" type="BINDATA" />
</if>
<if expr="enable_extensions">
<include name="IDR_EXTENSION_COMMAND_LIST_JS" file="resources\extensions\extension_command_list.js" flattenhtml="true" type="BINDATA" />
- <if expr="is_macosx">
- <include name="IDR_EXTENSIONS_INFOBAR_CSS" file="resources\extensions_infobar_mac.css" flattenhtml="true" type="BINDATA" />
- </if>
- <if expr="not is_macosx">
- <include name="IDR_EXTENSIONS_INFOBAR_CSS" file="resources\extensions_infobar.css" flattenhtml="true" type="BINDATA" />
- </if>
<include name="IDR_EXTENSION_LIST_JS" file="resources\extensions\extension_list.js" flattenhtml="true" type="BINDATA" />
- <include name="IDR_EXTENSION_INFO_CSS" file="resources\extensions\extension_info.css" flattenhtml="true" type="BINDATA" />
- <include name="IDR_EXTENSION_INFO_HTML" file="resources\extensions\extension_info.html" flattenhtml="true" type="BINDATA" />
- <include name="IDR_EXTENSION_INFO_JS" file="resources\extensions\extension_info.js" flattenhtml="true" type="BINDATA" />
<include name="IDR_EXTENSIONS_JS" file="resources\extensions\extensions.js" flattenhtml="true" type="BINDATA" />
</if>
<include name="IDR_FEEDBACK_MANIFEST" file="resources\feedback\manifest.json" type="BINDATA" />
<include name="IDR_FLAGS_HTML" file="resources\flags.html" flattenhtml="true" type="BINDATA" />
<include name="IDR_FLAGS_JS" file="resources\flags.js" type="BINDATA" />
+ <if expr="is_android">
+ <include name="IDR_GET_SALIENT_IMAGE_URL_JS" file="resources\get_salient_image_url.js" type="BINDATA" />
+ </if>
<if expr="_google_chrome or enable_hangout_services_extension">
<!-- Hangout Services extension, included in Google Chrome builds only. -->
<include name="IDR_HANGOUT_SERVICES_MANIFEST" file="resources\hangout_services\manifest.json" type="BINDATA" />
</if>
- <include name="IDR_HOTWORD_HELPER_MANIFEST" file="resources\hotword_helper\manifest.json" type="BINDATA" />
<include name="IDR_HOTWORD_MANIFEST" file="resources\hotword\manifest.json" type="BINDATA" />
<include name="IDR_HOTWORD_AUDIO_VERIFICATION_MANIFEST" file="resources\hotword_audio_verification\manifest.json" type="BINDATA" />
<if expr="not is_android">
@@ -204,8 +199,13 @@
<include name="IDR_LOCAL_NTP_HTML" file="resources\local_ntp\local_ntp.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" />
<include name="IDR_LOCAL_NTP_CSS" file="resources\local_ntp\local_ntp.css" type="BINDATA" />
<include name="IDR_LOCAL_NTP_JS" file="resources\local_ntp\local_ntp.js" flattenhtml="true" type="BINDATA" />
+ <include name="IDR_LOCAL_NTP_FAST_HTML" file="resources\local_ntp\local_ntp_fast.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" />
+ <include name="IDR_LOCAL_NTP_FAST_CSS" file="resources\local_ntp\local_ntp_fast.css" type="BINDATA" />
+ <include name="IDR_LOCAL_NTP_FAST_JS" file="resources\local_ntp\local_ntp_fast.js" flattenhtml="true" type="BINDATA" />
<include name="IDR_LOCAL_NTP_UTIL_JS" file="resources\local_ntp\local_ntp_util.js" flattenhtml="true" type="BINDATA" />
<include name="IDR_LOCAL_NTP_DESIGN_JS" file="resources\local_ntp\local_ntp_design.js" flattenhtml="true" type="BINDATA" />
+ <include name="IDR_LOCAL_STATE_HTML" file="resources\local_state\local_state.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" />
+ <include name="IDR_LOCAL_STATE_JS" file="resources\local_state\local_state.js" flattenhtml="true" allowexternalscript="true" type="BINDATA" />
<include name="IDR_MOST_VISITED_IFRAME_CSS" file="resources\local_ntp\most_visited_iframe.css" type="BINDATA" />
<include name="IDR_MOST_VISITED_TITLE_HTML" file="resources\local_ntp\most_visited_title.html" type="BINDATA" />
<include name="IDR_MOST_VISITED_TITLE_CSS" file="resources\local_ntp\most_visited_title.css" type="BINDATA" />
@@ -213,12 +213,11 @@
<include name="IDR_MOST_VISITED_THUMBNAIL_HTML" file="resources\local_ntp\most_visited_thumbnail.html" type="BINDATA" />
<include name="IDR_MOST_VISITED_THUMBNAIL_CSS" file="resources\local_ntp\most_visited_thumbnail.css" type="BINDATA" />
<include name="IDR_MOST_VISITED_THUMBNAIL_JS" file="resources\local_ntp\most_visited_thumbnail.js" type="BINDATA" />
+ <include name="IDR_MOST_VISITED_SINGLE_HTML" file="resources\local_ntp\most_visited_single.html" type="BINDATA" />
+ <include name="IDR_MOST_VISITED_SINGLE_CSS" file="resources\local_ntp\most_visited_single.css" type="BINDATA" />
+ <include name="IDR_MOST_VISITED_SINGLE_JS" file="resources\local_ntp\most_visited_single.js" type="BINDATA" />
<include name="IDR_MOST_VISITED_UTIL_JS" file="resources\local_ntp\most_visited_util.js" type="BINDATA" flattenhtml="true" />
<include name="IDR_MOST_VISITED_WINDOW_DISPOSITION_UTIL_JS" file="resources\local_ntp\window_disposition_util.js" type="BINDATA" />
- <if expr="is_android">
- <include name="IDR_ROBOTO_WOFF" file="resources\roboto\roboto.woff" type="BINDATA" />
- <include name="IDR_ROBOTO_WOFF2" file="resources\roboto\roboto.woff2" type="BINDATA" />
- </if>
<include name="IDR_OMNIBOX_HTML" file="resources\omnibox\omnibox.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" />
<include name="IDR_OMNIBOX_CSS" file="resources\omnibox\omnibox.css" type="BINDATA" />
<include name="IDR_OMNIBOX_JS" file="resources\omnibox\omnibox.js" type="BINDATA" />
@@ -252,10 +251,8 @@
</if>
<include name="IDR_POLICY_HTML" file="resources\policy.html" flattenhtml="true" allowexternalscript="true" type="BINDATA"/>
<include name="IDR_POLICY_JS" file="resources\policy.js" type="BINDATA"/>
- <include name="IDR_PRERENDER_URL_WHITELIST" file="resources\prerender_url_whitelist.dat" type="BINDATA"/>
<if expr="enable_print_preview">
<include name="IDR_PRINT_PREVIEW_HTML" file="resources\print_preview\print_preview.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" />
- <include name="IDR_PRINT_PREVIEW_PAGE" file="resources\print_preview\print_preview_page.html" flattenhtml="true" allowexternalscript="false" type="BINDATA" />
<include name="IDR_PRINT_PREVIEW_JS" file="resources\print_preview\print_preview.js" flattenhtml="true" type="BINDATA" />
<include name="IDR_PRINT_PREVIEW_IMAGES_PRINTER"
file="resources\print_preview\images\printer.png" type="BINDATA" />
@@ -282,13 +279,6 @@
<if expr="enable_google_now">
<include name="IDR_GOOGLE_NOW_MANIFEST" file="resources\google_now\manifest.json" type="BINDATA" />
</if>
- <if expr="not is_android">
- <include name="IDR_SUGGESTIONS_INTERNALS_HTML" file="resources\suggestions_internals\suggestions_internals.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" />
- <include name="IDR_SUGGESTIONS_INTERNALS_CSS" file="resources\suggestions_internals\suggestions_internals.css" type="BINDATA" />
- <include name="IDR_SUGGESTIONS_INTERNALS_JS" file="resources\suggestions_internals\suggestions_internals.js" type="BINDATA" />
- <include name="IDR_SUGGESTIONS_PAGE_CSS" file="resources\ntp4\suggestions_page.css" type="BINDATA" />
- <include name="IDR_SUGGESTIONS_PAGE_JS" file="resources\ntp4\suggestions_page.js" type="BINDATA" />
- </if>
<include name="IDR_UBER_HTML" file="resources\uber\uber.html" flattenhtml="true" type="BINDATA" />
<include name="IDR_UBER_JS" file="resources\uber\uber.js" type="BINDATA" />
<include name="IDR_UBER_FRAME_HTML" file="resources\uber\uber_frame.html" flattenhtml="true" type="BINDATA" />
@@ -345,7 +335,6 @@
<include name="IDR_SIM_UNLOCK_HTML" file="resources\chromeos\sim_unlock.html" flattenhtml="true" type="BINDATA" />
<include name="IDR_SLOW_HTML" file="resources\chromeos\slow.html" flattenhtml="true" type="BINDATA" />
<include name="IDR_SLOW_JS" file="resources\chromeos\slow.js" type="BINDATA" />
- <include name="IDR_CHARGER_REPLACEMENT_HTML" file="resources\chromeos\charger_replacement.html" flattenhtml="true" type="BINDATA" />
</if>
<if expr="chromeos">
<include name="IDR_DEMO_APP_MANIFEST" file="resources\chromeos\demo_app\manifest.json" type="BINDATA" />
@@ -388,6 +377,9 @@
<include name="IDR_IDENTITY_INTERNALS_CSS" file="resources\identity_internals.css" type="BINDATA" />
<include name="IDR_IDENTITY_INTERNALS_JS" file="resources\identity_internals.js" type="BINDATA" />
</if>
+ <include name="IDR_DEVICE_LOG_UI_HTML" file="resources\device_log_ui\device_log_ui.html" type="BINDATA" />
+ <include name="IDR_DEVICE_LOG_UI_JS" file="resources\device_log_ui\device_log_ui.js" type="BINDATA" />
+ <include name="IDR_DEVICE_LOG_UI_CSS" file="resources\device_log_ui\device_log_ui.css" type="BINDATA" />
<if expr="chromeos">
<include name="IDR_NETWORK_UI_HTML" file="resources\chromeos\network_ui\network_ui.html" type="BINDATA" />
<include name="IDR_NETWORK_UI_JS" file="resources\chromeos\network_ui\network_ui.js" type="BINDATA" />
@@ -425,6 +417,7 @@
<include name="IDR_GCM_INTERNALS_CSS" file="resources\gcm_internals.css" type="BINDATA" />
<include name="IDR_GCM_INTERNALS_JS" file="resources\gcm_internals.js" type="BINDATA" />
<include name="IDR_EASY_UNLOCK_MANIFEST" file="resources\easy_unlock\manifest.json" type="BINDATA" />
+ <include name="IDR_EASY_UNLOCK_MANIFEST_SIGNIN" file="resources\easy_unlock\manifest_signin.json" type="BINDATA" />
<if expr="chromeos">
<include name="IDR_SET_TIME_HTML" file="resources\chromeos\set_time.html" type="BINDATA" />
<include name="IDR_SET_TIME_CSS" file="resources\chromeos\set_time.css" type="BINDATA" />
@@ -442,11 +435,16 @@
<include name="IDR_ZHUYIN_MANIFEST" file="resources\chromeos\input_method\zhuyin_manifest.json" type="BINDATA" />
<include name="IDR_CANGJIE_MANIFEST" file="resources\chromeos\input_method\cangjie_manifest.json" type="BINDATA" />
<include name="IDR_MOZC_MANIFEST" file="resources\chromeos\input_method\mozc_manifest.json" type="BINDATA" />
+ <include name="IDR_HANGUL_MANIFEST" file="resources\chromeos\input_method\hangul_manifest.json" type="BINDATA" />
</if>
- <include name="IDR_HANGUL_MANIFEST" file="resources\chromeos\input_method\hangul_manifest.json" type="BINDATA" />
<include name="IDR_BRAILLE_MANIFEST" file="resources\chromeos\braille_ime\manifest.json" type="BINDATA" />
</if>
<include name="IDR_WHISPERNET_PROXY_MANIFEST" file="resources\whispernet_proxy\manifest.json" type="BINDATA" />
+ <include name="IDR_MD_SETTINGS_UI_HTML" file="resources\md_settings\md_settings.html" type="BINDATA" />
+ <include name="IDR_MD_SETTINGS_UI_CSS" file="resources\md_settings\md_settings.css" type="BINDATA" />
+ <if expr="enable_media_router">
+ <part file="media_router_resources.grdp" />
+ </if>
</includes>
</release>
</grit>
diff --git a/chromium/chrome/browser/devtools/device/webrtc/resources.grd b/chromium/chrome/browser/devtools/device/webrtc/resources.grd
new file mode 100644
index 00000000000..01524333346
--- /dev/null
+++ b/chromium/chrome/browser/devtools/device/webrtc/resources.grd
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<grit latest_public_release="0" current_release="1">
+ <outputs>
+ <output filename="grit/webrtc_device_provider_resources.h" type="rc_header">
+ <emit emit_type='prepend'></emit>
+ </output>
+ <output filename="grit/webrtc_device_provider_resources_map.cc" type="resource_file_map_source" />
+ <output filename="grit/webrtc_device_provider_resources_map.h" type="resource_map_header" />
+ <output filename="webrtc_device_provider_resources.pak" type="data_package" />
+ </outputs>
+ <release seq="1">
+ <includes>
+ <include name="IDR_BACKGROUND_WORKER_HTML" file="background_worker.html" flattenhtml="false" allowexternalscript="true" type="BINDATA" />
+ <include name="IDR_WEBRTC_DEVICE_PROVIDER_JS" file="js/webrtc_device_provider.js" type="BINDATA" />
+ </includes>
+ </release>
+</grit>
diff --git a/chromium/chrome/browser/devtools/devtools_protocol_constants.gyp b/chromium/chrome/browser/devtools/devtools_protocol_constants.gyp
index 01031f4811e..edaa1f9257a 100644
--- a/chromium/chrome/browser/devtools/devtools_protocol_constants.gyp
+++ b/chromium/chrome/browser/devtools/devtools_protocol_constants.gyp
@@ -13,7 +13,7 @@
'variables': {
'blink_protocol': '../../../third_party/WebKit/Source/devtools/protocol.json',
'browser_protocol': '../../../content/browser/devtools/browser_protocol.json',
- 'generator': '../../../content/public/browser/devtools_protocol_constants_generator.py',
+ 'generator': 'devtools_protocol_constants_generator.py',
'package': 'chrome'
},
'inputs': [
diff --git a/chromium/chrome/browser/devtools/webrtc_device_provider_resources.gyp b/chromium/chrome/browser/devtools/webrtc_device_provider_resources.gyp
new file mode 100644
index 00000000000..35d645f3f7c
--- /dev/null
+++ b/chromium/chrome/browser/devtools/webrtc_device_provider_resources.gyp
@@ -0,0 +1,26 @@
+# 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.
+
+{
+ 'targets': [
+ {
+ # GN version: //chrome/browser/devtools:webrtc_device_provider_resources
+ 'target_name': 'webrtc_device_provider_resources',
+ 'type': 'none',
+ 'variables': {
+ 'grit_out_dir': '<(SHARED_INTERMEDIATE_DIR)/chrome',
+ },
+ 'actions': [
+ {
+ 'action_name': 'generate_webrtc_device_provider_resources',
+ 'variables': {
+ 'grit_grd_file': 'device/webrtc/resources.grd',
+ },
+ 'includes': [ '../../../build/grit_action.gypi' ],
+ },
+ ],
+ 'includes': [ '../../../build/grit_target.gypi' ],
+ },
+ ]
+}
diff --git a/chromium/chrome/browser/extensions/api/api_registration.gyp b/chromium/chrome/browser/extensions/api/api_registration.gyp
index 8d44c8d349e..458126fb484 100644
--- a/chromium/chrome/browser/extensions/api/api_registration.gyp
+++ b/chromium/chrome/browser/extensions/api/api_registration.gyp
@@ -16,6 +16,10 @@
],
'dependencies': [
'<(DEPTH)/chrome/common/extensions/api/api.gyp:chrome_api',
+
+ # Different APIs include headers from these targets.
+ "<(DEPTH)/content/content.gyp:content_browser",
+
# Different APIs include some headers from chrome/common that in turn
# include generated headers from these targets.
# TODO(brettw) this should be made unnecessary if possible.
diff --git a/chromium/chrome/browser/media/desktop_streams_registry.cc b/chromium/chrome/browser/media/desktop_streams_registry.cc
index 9f6a0ebec9c..bd21240cdc5 100644
--- a/chromium/chrome/browser/media/desktop_streams_registry.cc
+++ b/chromium/chrome/browser/media/desktop_streams_registry.cc
@@ -36,7 +36,7 @@ std::string DesktopStreamsRegistry::RegisterStream(
const GURL& origin,
const content::DesktopMediaID& source,
const std::string& extension_name) {
- DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
std::string id = GenerateRandomStreamId();
DCHECK(approved_streams_.find(id) == approved_streams_.end());
@@ -62,7 +62,7 @@ content::DesktopMediaID DesktopStreamsRegistry::RequestMediaForStreamId(
int render_frame_id,
const GURL& origin,
std::string* extension_name) {
- DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
StreamsMap::iterator it = approved_streams_.find(id);
@@ -82,7 +82,7 @@ content::DesktopMediaID DesktopStreamsRegistry::RequestMediaForStreamId(
}
void DesktopStreamsRegistry::CleanupStream(const std::string& id) {
- DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
approved_streams_.erase(id);
}
diff --git a/chromium/chrome/browser/media/router/media_router.gyp b/chromium/chrome/browser/media/router/media_router.gyp
new file mode 100644
index 00000000000..7f8366465d4
--- /dev/null
+++ b/chromium/chrome/browser/media/router/media_router.gyp
@@ -0,0 +1,75 @@
+# Copyright 2015 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.
+
+{
+ 'includes': [
+ 'media_router.gypi',
+ ],
+ 'targets': [
+ {
+ # GN version: //chrome/browser/media/router:router
+ 'target_name': 'media_router',
+ 'type': 'static_library',
+ 'include_dirs': [
+ '<(DEPTH)',
+ '<(DEPTH)/third_party/mojo/src',
+ ],
+ 'dependencies': [
+ # media_router_type_converters.h needs the generated file.
+ 'media_router_mojo_gen',
+ 'media_router_mojo',
+ '<(DEPTH)/base/base.gyp:base',
+ '<(DEPTH)/components/components.gyp:keyed_service_core',
+ '<(DEPTH)/extensions/extensions.gyp:extensions_browser',
+ '<(DEPTH)/skia/skia.gyp:skia',
+ '<(DEPTH)/url/url.gyp:url_lib',
+ ],
+ 'sources': [
+ '<@(media_router_sources)',
+ ],
+ },
+ {
+ # Mojo compiler for the Media Router internal API.
+ 'target_name': 'media_router_mojo_gen',
+ 'type': 'none',
+ 'sources': [
+ 'media_router.mojom',
+ ],
+ 'includes': [
+ '../../../../third_party/mojo/mojom_bindings_generator.gypi',
+ ],
+ },
+ {
+ 'target_name': 'media_router_mojo',
+ 'type': 'static_library',
+ 'include_dirs': [
+ '<(DEPTH)/third_party/mojo/src',
+ ],
+ 'dependencies': [
+ 'media_router_mojo_gen',
+ ],
+ 'sources': [
+ '<(SHARED_INTERMEDIATE_DIR)/chrome/browser/media/router/media_router.mojom.cc',
+ '<(SHARED_INTERMEDIATE_DIR)/chrome/browser/media/router/media_router.mojom.h',
+ ],
+ },
+ {
+ # GN version: //chrome/browser/media/router:test_support
+ 'target_name': 'media_router_test_support',
+ 'type': 'static_library',
+ 'include_dirs': [
+ '<(DEPTH)',
+ ],
+ 'dependencies': [
+ 'media_router',
+ 'media_router_mojo',
+ '<(DEPTH)/base/base.gyp:base',
+ '<(DEPTH)/testing/gmock.gyp:gmock',
+ ],
+ 'sources': [
+ '<@(media_router_test_support_sources)',
+ ],
+ },
+ ],
+}
diff --git a/chromium/chrome/browser/media/router/media_router.gypi b/chromium/chrome/browser/media/router/media_router.gypi
new file mode 100644
index 00000000000..32bce7122a7
--- /dev/null
+++ b/chromium/chrome/browser/media/router/media_router.gypi
@@ -0,0 +1,44 @@
+# Copyright (c) 2015 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.
+
+{
+ 'variables': {
+ # File lists shared with GN build.
+ 'media_router_sources': [
+ 'create_session_request.cc',
+ 'create_session_request.h',
+ 'issue.cc',
+ 'issue.h',
+ 'issue_manager.cc',
+ 'issue_manager.h',
+ 'issues_observer.h',
+ 'media_route.cc',
+ 'media_route.h',
+ 'media_route_id.h',
+ 'media_router.h',
+ 'media_router_type_converters.cc',
+ 'media_router_type_converters.h',
+ 'media_routes_observer.cc',
+ 'media_routes_observer.h',
+ 'media_sink.cc',
+ 'media_sink.h',
+ 'media_sinks_observer.cc',
+ 'media_sinks_observer.h',
+ 'media_source.cc',
+ 'media_source.h',
+ 'media_source_helper.cc',
+ 'media_source_helper.h',
+ 'presentation_media_sinks_observer.cc',
+ 'presentation_media_sinks_observer.h',
+ 'route_id_manager.cc',
+ 'route_id_manager.h',
+ ],
+ 'media_router_test_support_sources': [
+ 'mock_media_router.cc',
+ 'mock_media_router.h',
+ 'mock_screen_availability_listener.cc',
+ 'mock_screen_availability_listener.h',
+ ],
+ },
+}
diff --git a/chromium/chrome/browser/media_router_resources.grdp b/chromium/chrome/browser/media_router_resources.grdp
new file mode 100644
index 00000000000..c5b0c76c314
--- /dev/null
+++ b/chromium/chrome/browser/media_router_resources.grdp
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<grit-part>
+ <!-- General -->
+ <include name="IDR_MEDIA_ROUTER_COMMON_CSS" file="resources\media_router\media_router_common.css" type="BINDATA" />
+ <include name="IDR_MEDIA_ROUTER_CSS" file="resources\media_router\media_router.css" type="BINDATA" />
+ <include name="IDR_MEDIA_ROUTER_DATA_JS" file="resources\media_router\media_router_data.js" type="BINDATA" />
+ <include name="IDR_MEDIA_ROUTER_JS" file="resources\media_router\media_router.js" flattenhtml="true" type="BINDATA" />
+ <include name="IDR_MEDIA_ROUTER_HTML" file="resources\media_router\media_router.html" type="BINDATA" />
+ <include name="IDR_MEDIA_ROUTER_UI_INTERFACE_JS" file="resources\media_router\media_router_ui_interface.js" type="BINDATA" />
+
+ <!-- Polymer -->
+ <include name="IDR_ISSUE_BANNER_HTML" file="resources\media_router\elements\issue_banner\issue_banner.html" type="BINDATA" />
+ <include name="IDR_ISSUE_BANNER_JS" file="resources\media_router\elements\issue_banner\issue_banner.js" type="BINDATA" />
+ <include name="IDR_ISSUE_BANNER_CSS" file="resources\media_router\elements\issue_banner\issue_banner.css" type="BINDATA" />
+ <include name="IDR_MEDIA_ROUTER_CONTAINER_HTML" file="resources\media_router\elements\media_router_container\media_router_container.html" type="BINDATA" />
+ <include name="IDR_MEDIA_ROUTER_CONTAINER_CSS" file="resources\media_router\elements\media_router_container\media_router_container.css" type="BINDATA" />
+ <include name="IDR_MEDIA_ROUTER_CONTAINER_JS" file="resources\media_router\elements\media_router_container\media_router_container.js" type="BINDATA" />
+ <include name="IDR_ROUTE_DETAILS_HTML" file="resources\media_router\elements\route_details\route_details.html" type="BINDATA" />
+ <include name="IDR_ROUTE_DETAILS_CSS" file="resources\media_router\elements\route_details\route_details.css" type="BINDATA" />
+ <include name="IDR_ROUTE_DETAILS_JS" file="resources\media_router\elements\route_details\route_details.js" type="BINDATA" />
+
+ <!-- Icons -->
+ <include name="IDR_SAD_FACE_ICON" file="resources\media_router\elements\icon\sad-face.png" type="BINDATA" />
+ <include name="IDR_SAD_FACE_2X_ICON" file="resources\media_router\elements\icon\sad-face2x.png" type="BINDATA" />
+</grit-part>
diff --git a/chromium/chrome/browser/polymer_resources.grdp b/chromium/chrome/browser/polymer_resources.grdp
deleted file mode 100644
index a548ac795d1..00000000000
--- a/chromium/chrome/browser/polymer_resources.grdp
+++ /dev/null
@@ -1,50 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<grit-part>
- <structure name="IDR_POLYMER_CORE_ANIMATED_PAGES_CORE_ANIMATED_PAGES_CSS" file="..\..\third_party\polymer\components-chromium\core-animated-pages\core-animated-pages.css" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_CORE_ANIMATED_PAGES_CORE_ANIMATED_PAGES_EXTRACTED_JS" file="..\..\third_party\polymer\components-chromium\core-animated-pages\core-animated-pages-extracted.js" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_CORE_ANIMATED_PAGES_CORE_ANIMATED_PAGES_HTML" file="..\..\third_party\polymer\components-chromium\core-animated-pages\core-animated-pages.html" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_CORE_ANIMATED_PAGES_TRANSITIONS_CORE_TRANSITION_PAGES_EXTRACTED_JS" file="..\..\third_party\polymer\components-chromium\core-animated-pages\transitions\core-transition-pages-extracted.js" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_CORE_ANIMATED_PAGES_TRANSITIONS_CORE_TRANSITION_PAGES_HTML" file="..\..\third_party\polymer\components-chromium\core-animated-pages\transitions\core-transition-pages.html" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_CORE_ANIMATED_PAGES_TRANSITIONS_CROSS_FADE_HTML" file="..\..\third_party\polymer\components-chromium\core-animated-pages\transitions\cross-fade.html" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_CORE_ANIMATED_PAGES_TRANSITIONS_HERO_TRANSITION_EXTRACTED_JS" file="..\..\third_party\polymer\components-chromium\core-animated-pages\transitions\hero-transition-extracted.js" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_CORE_ANIMATED_PAGES_TRANSITIONS_HERO_TRANSITION_HTML" file="..\..\third_party\polymer\components-chromium\core-animated-pages\transitions\hero-transition.html" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_CORE_ICON_CORE_ICON_CSS" file="..\..\third_party\polymer\components-chromium\core-icon\core-icon.css" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_CORE_ICON_CORE_ICON_EXTRACTED_JS" file="..\..\third_party\polymer\components-chromium\core-icon\core-icon-extracted.js" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_CORE_ICON_CORE_ICON_HTML" file="..\..\third_party\polymer\components-chromium\core-icon\core-icon.html" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_CORE_ICONSET_CORE_ICONSET_EXTRACTED_JS" file="..\..\third_party\polymer\components-chromium\core-iconset\core-iconset-extracted.js" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_CORE_ICONSET_CORE_ICONSET_HTML" file="..\..\third_party\polymer\components-chromium\core-iconset\core-iconset.html" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_CORE_ICONSET_SVG_CORE_ICONSET_SVG_EXTRACTED_JS" file="..\..\third_party\polymer\components-chromium\core-iconset-svg\core-iconset-svg-extracted.js" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_CORE_ICONSET_SVG_CORE_ICONSET_SVG_HTML" file="..\..\third_party\polymer\components-chromium\core-iconset-svg\core-iconset-svg.html" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_CORE_ITEM_CORE_ITEM_CSS" file="..\..\third_party\polymer\components-chromium\core-item\core-item.css" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_CORE_ITEM_CORE_ITEM_EXTRACTED_JS" file="..\..\third_party\polymer\components-chromium\core-item\core-item-extracted.js" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_CORE_ITEM_CORE_ITEM_HTML" file="..\..\third_party\polymer\components-chromium\core-item\core-item.html" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_CORE_META_CORE_META_EXTRACTED_JS" file="..\..\third_party\polymer\components-chromium\core-meta\core-meta-extracted.js" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_CORE_META_CORE_META_HTML" file="..\..\third_party\polymer\components-chromium\core-meta\core-meta.html" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_CORE_RANGE_CORE_RANGE_EXTRACTED_JS" file="..\..\third_party\polymer\components-chromium\core-range\core-range-extracted.js" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_CORE_RANGE_CORE_RANGE_HTML" file="..\..\third_party\polymer\components-chromium\core-range\core-range.html" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_CORE_SELECTION_CORE_SELECTION_EXTRACTED_JS" file="..\..\third_party\polymer\components-chromium\core-selection\core-selection-extracted.js" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_CORE_SELECTION_CORE_SELECTION_HTML" file="..\..\third_party\polymer\components-chromium\core-selection\core-selection.html" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_CORE_SELECTOR_CORE_SELECTOR_EXTRACTED_JS" file="..\..\third_party\polymer\components-chromium\core-selector\core-selector-extracted.js" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_CORE_SELECTOR_CORE_SELECTOR_HTML" file="..\..\third_party\polymer\components-chromium\core-selector\core-selector.html" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_CORE_STYLE_CORE_STYLE_EXTRACTED_JS" file="..\..\third_party\polymer\components-chromium\core-style\core-style-extracted.js" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_CORE_STYLE_CORE_STYLE_HTML" file="..\..\third_party\polymer\components-chromium\core-style\core-style.html" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_CORE_TRANSITION_CORE_TRANSITION_EXTRACTED_JS" file="..\..\third_party\polymer\components-chromium\core-transition\core-transition-extracted.js" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_CORE_TRANSITION_CORE_TRANSITION_HTML" file="..\..\third_party\polymer\components-chromium\core-transition\core-transition.html" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_PAPER_BUTTON_PAPER_BUTTON_CSS" file="..\..\third_party\polymer\components-chromium\paper-button\paper-button.css" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_PAPER_BUTTON_PAPER_BUTTON_EXTRACTED_JS" file="..\..\third_party\polymer\components-chromium\paper-button\paper-button-extracted.js" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_PAPER_BUTTON_PAPER_BUTTON_HTML" file="..\..\third_party\polymer\components-chromium\paper-button\paper-button.html" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_PAPER_FOCUSABLE_PAPER_FOCUSABLE_EXTRACTED_JS" file="..\..\third_party\polymer\components-chromium\paper-focusable\paper-focusable-extracted.js" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_PAPER_FOCUSABLE_PAPER_FOCUSABLE_HTML" file="..\..\third_party\polymer\components-chromium\paper-focusable\paper-focusable.html" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_PAPER_PROGRESS_PAPER_PROGRESS_CSS" file="..\..\third_party\polymer\components-chromium\paper-progress\paper-progress.css" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_PAPER_PROGRESS_PAPER_PROGRESS_EXTRACTED_JS" file="..\..\third_party\polymer\components-chromium\paper-progress\paper-progress-extracted.js" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_PAPER_PROGRESS_PAPER_PROGRESS_HTML" file="..\..\third_party\polymer\components-chromium\paper-progress\paper-progress.html" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_PAPER_RIPPLE_PAPER_RIPPLE_EXTRACTED_JS" file="..\..\third_party\polymer\components-chromium\paper-ripple\paper-ripple-extracted.js" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_PAPER_RIPPLE_PAPER_RIPPLE_HTML" file="..\..\third_party\polymer\components-chromium\paper-ripple\paper-ripple.html" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_PAPER_SHADOW_PAPER_SHADOW_CSS" file="..\..\third_party\polymer\components-chromium\paper-shadow\paper-shadow.css" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_PAPER_SHADOW_PAPER_SHADOW_EXTRACTED_JS" file="..\..\third_party\polymer\components-chromium\paper-shadow\paper-shadow-extracted.js" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_PAPER_SHADOW_PAPER_SHADOW_HTML" file="..\..\third_party\polymer\components-chromium\paper-shadow\paper-shadow.html" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_PLATFORM_PLATFORM_JS" file="..\..\third_party\polymer\components-chromium\platform\platform.js" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_POLYMER_LAYOUT_HTML" file="..\..\third_party\polymer\components-chromium\polymer\layout.html" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_POLYMER_POLYMER_HTML" file="..\..\third_party\polymer\components-chromium\polymer\polymer.html" flattenhtml="false" type="chrome_html" />
- <structure name="IDR_POLYMER_POLYMER_POLYMER_JS" file="..\..\third_party\polymer\components-chromium\polymer\polymer.js" flattenhtml="false" type="chrome_html" />
-</grit-part>
diff --git a/chromium/chrome/browser/resources/BUILD.gn b/chromium/chrome/browser/resources/BUILD.gn
index c7f75f32895..92c6c4a3671 100644
--- a/chromium/chrome/browser/resources/BUILD.gn
+++ b/chromium/chrome/browser/resources/BUILD.gn
@@ -69,8 +69,12 @@ grit("translate_internals_resources") {
# GYP version: copy command of chrome_extra_resources
copy("extension_resource_demo") {
- sources = [ "extension_resource/demo/library.js" ]
- outputs = [ "$root_out_dir/resources/extension/demo/library.js" ]
+ sources = [
+ "extension_resource/demo/library.js",
+ ]
+ outputs = [
+ "$root_out_dir/resources/extension/demo/library.js",
+ ]
}
if (!is_ios) {
@@ -85,6 +89,17 @@ if (!is_ios) {
output_dir = "$root_gen_dir/chrome"
}
+ grit("settings_resources") {
+ source = "settings/settings_resources.grd"
+ outputs = [
+ "grit/settings_resources.h",
+ "grit/settings_resources_map.cc",
+ "grit/settings_resources_map.h",
+ "settings_resources.pak",
+ ]
+ output_dir = "$root_gen_dir/chrome"
+ }
+
grit("options_resources") {
source = "options_resources.grd"
outputs = [
@@ -94,6 +109,15 @@ if (!is_ios) {
output_dir = "$root_gen_dir/chrome"
}
+ grit("options_test_resources") {
+ source = "options_test_resources.grd"
+ outputs = [
+ "grit/options_test_resources.h",
+ "options_test_resources.pak",
+ ]
+ output_dir = "$root_gen_dir/chrome"
+ }
+
grit("quota_internals_resources") {
source = "quota_internals_resources.grd"
outputs = [
diff --git a/chromium/chrome/browser/resources/PRESUBMIT.py b/chromium/chrome/browser/resources/PRESUBMIT.py
new file mode 100644
index 00000000000..502d47a88d2
--- /dev/null
+++ b/chromium/chrome/browser/resources/PRESUBMIT.py
@@ -0,0 +1,95 @@
+# 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 HTML files in chrome/browser/resources.
+
+See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
+for more details about the presubmit API built into depot_tools.
+"""
+
+ACTION_XML_PATH = '../../../tools/metrics/actions/actions.xml'
+
+def CheckUserActionUpdate(input_api, output_api, action_xml_path):
+ """Checks if any new user action has been added."""
+ if any('actions.xml' == input_api.os_path.basename(f) for f in
+ input_api.change.LocalPaths()):
+ # If actions.xml is already included in the changelist, the PRESUBMIT
+ # for actions.xml will do a more complete presubmit check.
+ return []
+
+ file_filter = lambda f: f.LocalPath().endswith('.html')
+ action_re = r'(^|\s+)metric\s*=\s*"([^ ]*)"'
+ current_actions = None
+ for f in input_api.AffectedFiles(file_filter=file_filter):
+ for line_num, line in f.ChangedContents():
+ match = input_api.re.search(action_re, line)
+ if match:
+ # Loads contents in tools/metrics/actions/actions.xml to memory. It's
+ # loaded only once.
+ if not current_actions:
+ with open(action_xml_path) as actions_f:
+ current_actions = actions_f.read()
+
+ metric_name = match.group(2)
+ is_boolean = IsBoolean(f.NewContents(), metric_name, input_api)
+
+ # Search for the matched user action name in |current_actions|.
+ if not IsActionPresent(current_actions, metric_name, is_boolean):
+ return [output_api.PresubmitPromptWarning(
+ 'File %s line %d: %s is missing in '
+ 'tools/metrics/actions/actions.xml. Please run '
+ 'tools/metrics/actions/extract_actions.py to update.'
+ % (f.LocalPath(), line_num, metric_name), [])]
+ return []
+
+
+def IsActionPresent(current_actions, metric_name, is_boolean):
+ """Checks if metric_name is defined in the actions file.
+
+ Checks whether there's matching entries in an actions.xml file for the given
+ |metric_name|, depending on whether it is a boolean action.
+
+ Args:
+ current_actions: The content of the actions.xml file.
+ metric_name: The name for which the check should be done.
+ is_boolean: Whether the action comes from a boolean control.
+ """
+ if not is_boolean:
+ action = 'name="{0}"'.format(metric_name)
+ return action in current_actions
+
+ action_disabled = 'name="{0}_Disable"'.format(metric_name)
+ action_enabled = 'name="{0}_Enable"'.format(metric_name)
+
+ return (action_disabled in current_actions and
+ action_enabled in current_actions)
+
+
+def IsBoolean(new_content_lines, metric_name, input_api):
+ """Check whether action defined in the changed code is boolean or not.
+
+ Checks whether the action comes from boolean control based on the HTML
+ elements attributes.
+
+ Args:
+ new_content_lines: List of changed lines.
+ metric_name: The name for which the check should be done.
+ """
+ new_content = '\n'.join(new_content_lines)
+
+ html_element_re = r'<(.*?)(^|\s+)metric\s*=\s*"%s"(.*?)>' % (metric_name)
+ type_re = (r'datatype\s*=\s*"boolean"|type\s*=\s*"checkbox"|'
+ 'type\s*=\s*"radio".*?value\s*=\s*("true"|"false")')
+
+ match = input_api.re.search(html_element_re, new_content, input_api.re.DOTALL)
+ return (match and
+ any(input_api.re.search(type_re, match.group(i)) for i in (1, 3)))
+
+
+def CheckChangeOnUpload(input_api, output_api):
+ return CheckUserActionUpdate(input_api, output_api, ACTION_XML_PATH)
+
+
+def CheckChangeOnCommit(input_api, output_api):
+ return CheckUserActionUpdate(input_api, output_api, ACTION_XML_PATH)
diff --git a/chromium/chrome/browser/resources/PRESUBMIT_test.py b/chromium/chrome/browser/resources/PRESUBMIT_test.py
new file mode 100644
index 00000000000..923f2be1f4c
--- /dev/null
+++ b/chromium/chrome/browser/resources/PRESUBMIT_test.py
@@ -0,0 +1,109 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import os
+import sys
+import imp
+import tempfile
+import unittest
+import PRESUBMIT
+
+sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(
+ os.path.abspath(__file__))))))
+from PRESUBMIT_test_mocks import MockInputApi, MockOutputApi
+from PRESUBMIT_test_mocks import MockFile, MockChange
+
+class HTMLActionAdditionTest(unittest.TestCase):
+
+ def testActionXMLChanged(self):
+ mock_input_api = MockInputApi()
+ lines = ['<input id="testinput" pref="testpref"',
+ 'metric="validaction" type="checkbox" dialog-pref>']
+ mock_input_api.files = [MockFile('path/valid.html', lines)]
+ mock_input_api.change = MockChange(['path/valid.html','actions.xml'])
+ action_xml_path = self._createActionXMLFile()
+ self.assertEqual([], PRESUBMIT.CheckUserActionUpdate(mock_input_api,
+ MockOutputApi(),
+ action_xml_path))
+
+ def testValidChange_StartOfLine(self):
+ lines = ['<input id="testinput" pref="testpref"',
+ 'metric="validaction" type="checkbox" dialog-pref>']
+ self.assertEqual([], self._testChange(lines))
+
+ def testValidChange_StartsWithSpace(self):
+ lines = ['<input id="testinput" pref="testpref"',
+ ' metric="validaction" type="checkbox" dialog-pref>']
+ self.assertEqual([], self._testChange(lines))
+
+ def testValidChange_Radio(self):
+ lines = ['<input id="testinput" pref="testpref"',
+ ' metric="validaction" type="radio" dialog-pref value="true">']
+ self.assertEqual([], self._testChange(lines))
+
+ def testValidChange_UsingDatatype(self):
+ lines = ['<input id="testinput" pref="testpref"',
+ ' metric="validaction" datatype="boolean" dialog-pref>']
+ self.assertEqual([], self._testChange(lines))
+
+ def testValidChange_NotBoolean(self):
+ lines = ['<input id="testinput" pref="testpref"',
+ ' metric="notboolean_validaction" dialog-pref>']
+ self.assertEqual([], self._testChange(lines))
+
+ def testInvalidChange(self):
+ lines = ['<input id="testinput" pref="testpref"',
+ 'metric="invalidaction" type="checkbox" dialog-pref>']
+ warnings = self._testChange(lines)
+ self.assertEqual(1, len(warnings), warnings)
+
+ def testInValidChange_Radio(self):
+ lines = ['<input id="testinput" pref="testpref"',
+ ' metric="validaction" type="radio" dialog-pref value="string">']
+ warnings = self._testChange(lines)
+ self.assertEqual(1, len(warnings), warnings)
+
+ def testValidChange_MultilineType(self):
+ lines = ['<input id="testinput" pref="testpref"\n'
+ ' metric="validaction" type=\n'
+ ' "radio" dialog-pref value=\n'
+ ' "false">']
+ warnings = self._testChange(lines)
+ self.assertEqual([], self._testChange(lines))
+
+ def _testChange(self, lines):
+ mock_input_api = MockInputApi()
+ mock_input_api.files = [MockFile('path/test.html', lines)]
+
+ action_xml_path = self._createActionXMLFile()
+ return PRESUBMIT.CheckUserActionUpdate(mock_input_api,
+ MockOutputApi(),
+ action_xml_path)
+
+ def _createActionXMLFile(self):
+ content = ('<actions>'
+ '<action name="validaction_Disable">'
+ ' <owner>Please list the metric\'s owners.</owner>'
+ ' <description>Enter the description of this user action.</description>'
+ '</action>'
+ '<action name="validaction_Enable">'
+ ' <owner>Please list the metric\'s owners. </owner>'
+ ' <description>Enter the description of this user action.</description>'
+ '</action>'
+ '<action name="notboolean_validaction">'
+ ' <owner>Please list the metric\'s owners.</owner>'
+ ' <description>Enter the description of this user action.</description>'
+ '</action>'
+ '</actions>')
+ sys_temp = tempfile.gettempdir()
+ action_xml_path = os.path.join(sys_temp, 'actions_test.xml')
+ if not os.path.exists(action_xml_path):
+ with open(action_xml_path, 'w+') as action_file:
+ action_file.write(content)
+
+ return action_xml_path
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/chromium/chrome/browser/resources/about_conflicts.html b/chromium/chrome/browser/resources/about_conflicts.html
index 80773dc7fe3..e8a455e698b 100644
--- a/chromium/chrome/browser/resources/about_conflicts.html
+++ b/chromium/chrome/browser/resources/about_conflicts.html
@@ -1,7 +1,8 @@
-<!DOCTYPE HTML>
-<html i18n-values="dir:textdirection;">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
<head>
<meta charset="utf-8">
+<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<style>
body {
margin: 10px;
@@ -153,7 +154,7 @@ html[dir=rtl] .clearing {
}
</style>
</head>
-<body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<body>
<div id="loading-message" i18n-content="loadingMessage"></div>
<div id="body-container" style="visibility:hidden">
@@ -260,7 +261,7 @@ html[dir=rtl] .clearing {
<script src="chrome://resources/js/util.js"></script>
<script src="chrome://conflicts/strings.js"></script>
-<script src="chrome://resources/js/i18n_template2.js"></script>
+<script src="chrome://resources/js/i18n_template.js"></script>
<script src="chrome://conflicts/conflicts.js"></script>
</body>
diff --git a/chromium/chrome/browser/resources/about_credits.js b/chromium/chrome/browser/resources/about_credits.js
index c3b7d440c0a..818ef5e77b2 100644
--- a/chromium/chrome/browser/resources/about_credits.js
+++ b/chromium/chrome/browser/resources/about_credits.js
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-function $(o) {return document.getElementById(o);}
+function $(id) { return document.getElementById(id); }
function toggle(o) {
var licence = o.nextSibling;
@@ -14,25 +14,28 @@ function toggle(o) {
if (licence.style && licence.style.display == 'block') {
licence.style.display = 'none';
- o.innerHTML = 'show license';
+ o.textContent = 'show license';
} else {
licence.style.display = 'block';
- o.innerHTML = 'hide license';
+ o.textContent = 'hide license';
}
return false;
}
-document.body.onload = function () {
- var links = document.getElementsByTagName("a");
- for (var i = 0; i < links.length; i++) {
- if (links[i].className === "show") {
- links[i].onclick = function () { return toggle(this); };
- }
+document.addEventListener('DOMContentLoaded', function() {
+ if (cr.isChromeOS) {
+ var keyboardUtils = document.createElement('script');
+ keyboardUtils.src = 'chrome://credits/keyboard_utils.js';
+ document.body.appendChild(keyboardUtils);
}
- $("print-link").onclick = function () {
- window.print();
- return false;
+ var links = document.querySelectorAll('a.show');
+ for (var i = 0; i < links.length; ++i) {
+ links[i].onclick = function() { return toggle(this); };
}
-};
+ $('print-link').onclick = function() {
+ window.print();
+ return false;
+ };
+});
diff --git a/chromium/chrome/browser/resources/about_credits.tmpl b/chromium/chrome/browser/resources/about_credits.tmpl
index f2899ea0bd7..e9f119d6777 100644
--- a/chromium/chrome/browser/resources/about_credits.tmpl
+++ b/chromium/chrome/browser/resources/about_credits.tmpl
@@ -1,67 +1,62 @@
-<!DOCTYPE HTML>
+<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Credits</title>
+<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<style>
body {
- font-family:Helvetica,Arial,sans-serif;
- background-color:white;
- font-size:84%;
- max-width:1020px;
+ background-color: white;
+ font-size: 84%;
+ max-width: 1020px;
}
.page-title {
- font-size:164%;
- font-weight:bold;
+ font-size: 164%;
+ font-weight: bold;
}
.product {
- background-color:#c3d9ff;
- overflow:auto;
- padding:2px;
- margin-top:16px;
- border-radius:5px;
+ background-color: #c3d9ff;
+ border-radius: 5px;
+ margin-top: 16px;
+ overflow: auto;
+ padding: 2px;
}
.product .title {
- font-size:110%;
- font-weight:bold;
- float:left;
- margin:3px;
+ float: left;
+ font-size: 110%;
+ font-weight: bold;
+ margin: 3px;
}
.product .homepage {
- text-align:right;
- float:right;
- margin:3px;
+ float: right;
+ margin: 3px;
+ text-align: right;
}
.product .homepage::after {
- content:" - ";
+ content: " - ";
}
.product .show {
- text-align:right;
- float:right;
- margin:3px;
+ float: right;
+ margin: 3px;
+ text-align: right;
}
.licence {
- clear:both;
- background-color:#e8eef7;
- padding:16px;
- border-radius:3px;
- display:none;
+ background-color: #e8eef7;
+ border-radius: 3px;
+ clear: both;
+ display: none;
+ padding: 16px;
}
.licence h3 {
- margin-top:0px;
+ margin-top: 0;
}
.licence pre {
white-space: pre-wrap;
}
-.dialog #print-link {
- display: none;
-}
+.dialog #print-link,
.dialog .homepage {
display: none;
}
-.highlight a:focus,
- box-shadow: 0 0 23px rgb(77, 144, 254) !important;
-}
</style>
</head>
<body>
@@ -70,7 +65,7 @@ body {
<div style="clear:both; overflow:auto;"><!-- Chromium <3s the following projects -->
{{entries}}
</div>
+<script src="chrome://resources/js/cr.js"></script>
<script src="chrome://credits/credits.js"></script>
-<script src="chrome://credits/keyboard_utils.js"></script>
</body>
</html>
diff --git a/chromium/chrome/browser/resources/about_flash.html b/chromium/chrome/browser/resources/about_flash.html
index 6abe8905f08..c9d4ad17554 100644
--- a/chromium/chrome/browser/resources/about_flash.html
+++ b/chromium/chrome/browser/resources/about_flash.html
@@ -1,7 +1,8 @@
-<!DOCTYPE HTML>
-<html i18n-values="dir:textdirection;">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
<head>
<meta charset="utf-8">
+<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<style>
.key {
font-weight: bold;
@@ -12,7 +13,7 @@
}
</style>
</head>
-<body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<body>
<div id="loading-message" i18n-content="loadingMessage">LOADING_MESSAGE</div>
<div id="body-container" style="visibility:hidden">
<div id="header"><h1 i18n-content="flashLongTitle">ABOUT_FLASH</h1></div>
@@ -29,7 +30,7 @@
<script src="chrome://resources/js/load_time_data.js"></script>
<script src="chrome://flash/about_flash.js"></script>
<script src="chrome://flash/strings.js"></script>
-<script src="chrome://resources/js/i18n_template2.js"></script>
+<script src="chrome://resources/js/i18n_template.js"></script>
<script src="chrome://resources/js/jstemplate_compiled.js"></script>
<script src="chrome://resources/js/util.js"></script>
</body>
diff --git a/chromium/chrome/browser/resources/about_invalidations.css b/chromium/chrome/browser/resources/about_invalidations.css
index 2747f694b81..4f52a608d2e 100644
--- a/chromium/chrome/browser/resources/about_invalidations.css
+++ b/chromium/chrome/browser/resources/about_invalidations.css
@@ -4,7 +4,6 @@
*/
body {
- font-family: Ubuntu, Arial, sans-serif;
font-size: 80%;
}
diff --git a/chromium/chrome/browser/resources/about_invalidations.html b/chromium/chrome/browser/resources/about_invalidations.html
index 7809bbd3741..7b8927197ff 100644
--- a/chromium/chrome/browser/resources/about_invalidations.html
+++ b/chromium/chrome/browser/resources/about_invalidations.html
@@ -1,4 +1,4 @@
-<!DOCTYPE HTML>
+<!doctype html>
<html>
<head>
<meta charset="utf-8">
@@ -7,6 +7,7 @@
<script src="chrome://resources/js/parse_html_subset.js"></script>
<script src="chrome://resources/js/util.js"></script>
<script src="chrome://invalidations/about_invalidations.js"></script>
+ <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="about_invalidations.css">
</head>
<body>
diff --git a/chromium/chrome/browser/resources/about_invalidations.js b/chromium/chrome/browser/resources/about_invalidations.js
index 83a187a57ae..0aaf8379fc6 100644
--- a/chromium/chrome/browser/resources/about_invalidations.js
+++ b/chromium/chrome/browser/resources/about_invalidations.js
@@ -68,7 +68,7 @@ cr.define('chrome.invalidations', function() {
/**
* Adds to the log the latest invalidations received
- * @param {!Array.<!Object>} allInvalidations The array of ObjectId
+ * @param {!Array<!Object>} allInvalidations The array of ObjectId
* that contains the invalidations received by the InvalidatorService.
*/
function logInvalidations(allInvalidations) {
@@ -139,7 +139,7 @@ cr.define('chrome.invalidations', function() {
/**
* Shows the handlers that are currently registered for invalidations
* (but might not have objects ids registered yet).
- * @param {!Array.<string>} allHandlers An array of Strings that are
+ * @param {!Array<string>} allHandlers An array of Strings that are
* the names of all the handlers currently registered in the
* InvalidatorService.
*/
diff --git a/chromium/chrome/browser/resources/about_memory.css b/chromium/chrome/browser/resources/about_memory.css
index 4fb27728b43..688c7e62b41 100644
--- a/chromium/chrome/browser/resources/about_memory.css
+++ b/chromium/chrome/browser/resources/about_memory.css
@@ -56,7 +56,7 @@ div#header {
}
div#header h1 {
- background: url('chrome://resources/images/gear.png') 12px 60% no-repeat;
+ background: url(chrome://resources/images/gear.png) 12px 60% no-repeat;
color: white;
display: inline;
margin: 0;
@@ -258,7 +258,7 @@ table.list#browserComparison .name {
}
div.help {
- background: url('chrome://resources/images/help.png') center bottom no-repeat;
+ background: url(chrome://resources/images/help.png) center bottom no-repeat;
display: inline-block;
height: 14px;
margin: -1px 0;
diff --git a/chromium/chrome/browser/resources/about_memory.html b/chromium/chrome/browser/resources/about_memory.html
index a2f4e1bd3e6..6b670a19ed3 100644
--- a/chromium/chrome/browser/resources/about_memory.html
+++ b/chromium/chrome/browser/resources/about_memory.html
@@ -1,4 +1,4 @@
-<!DOCTYPE HTML>
+<!doctype html>
<!--
about:memory template page
@@ -7,17 +7,10 @@ about:memory template page
<head>
<meta charset="utf-8">
<title>About Memory</title>
+ <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="chrome://memory-redirect/about_memory.css">
<style>
-body {
- font-family: Helvetica, Arial, sans-serif;
-}
-div#header select {
- font-family: Helvetica, Arial, sans-serif;
-}
-div.otherbrowsers {
- font-family: Helvetica, Arial, sans-serif;
-}
+
table.list#browserComparison tr:not([class*='firstRow']) > *:nth-child(1),
table.list#browserComparison tr:not([class*='firstRow']) > *:nth-child(4),
table.list#browserComparison tr.firstRow th:nth-child(1),
@@ -185,7 +178,7 @@ table.list#memoryDetails tr.firstRow th:nth-child(3) {
not just Chrome.
</div>
<div class="otherbrowsers" jsdisplay="browsers.length > 1">
- Note: Chrome includes memory used by plug-ins, other browsers may not.
+ Note: Chrome includes memory used by plugins, other browsers may not.
</div>
<br><br><br>
diff --git a/chromium/chrome/browser/resources/about_memory_linux.html b/chromium/chrome/browser/resources/about_memory_linux.html
index c320298324e..9ea8ac954ea 100644
--- a/chromium/chrome/browser/resources/about_memory_linux.html
+++ b/chromium/chrome/browser/resources/about_memory_linux.html
@@ -1,4 +1,4 @@
-<!DOCTYPE HTML>
+<!doctype html>
<!--
about:memory template page
@@ -10,13 +10,14 @@ about:memory template page
<if expr="is_android">
<meta name="viewport" content="width=device-width">
</if>
+ <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="chrome://memory-redirect/about_memory.css">
<link rel="stylesheet" href="about_memory_linux.css">
<script src="chrome://resources/js/load_time_data.js"></script>
<script src="chrome://memory-redirect/memory.js"></script>
<script src="chrome://memory-redirect/strings.js"></script>
</head>
-<body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<body>
<div id="header">
<h1>
About memory
@@ -102,7 +103,7 @@ about:memory template page
</div>
<div class="otherbrowsers"
jsdisplay="show_other_browsers && browsers.length > 1">
- Note: Chrome includes memory used by plug-ins, other browsers may not.
+ Note: Chrome includes memory used by plugins, other browsers may not.
</div>
<br><br><br>
@@ -205,7 +206,7 @@ about:memory template page
<div class="otherbrowsers">(The memory usage of our renderer processes is slightly less accurate when they are sandboxed.)</div>
</div>
- <script src="chrome://resources/js/i18n_template2.js"></script>
+ <script src="chrome://resources/js/i18n_template.js"></script>
<script src="chrome://resources/js/jstemplate_compiled.js"></script>
</body>
</html>
diff --git a/chromium/chrome/browser/resources/about_memory_mac.html b/chromium/chrome/browser/resources/about_memory_mac.html
index d7a9b9b315f..6662ee0cc50 100644
--- a/chromium/chrome/browser/resources/about_memory_mac.html
+++ b/chromium/chrome/browser/resources/about_memory_mac.html
@@ -1,4 +1,4 @@
-<!DOCTYPE HTML>
+<!doctype html>
<!--
about:memory template page
@@ -7,17 +7,9 @@ about:memory template page
<head>
<meta charset="utf-8">
<title>About Memory</title>
+ <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="chrome://memory-redirect/about_memory.css">
<style>
-body {
- font-family: Helvetica, sans-serif;
-}
-div#header select {
- font-family: Helvetica, sans-serif;
-}
-div.otherbrowsers {
- font-family: Helvetica, sans-serif;
-}
table.list#browserComparison tr:not([class*='firstRow']) > *:nth-child(1),
table.list#browserComparison tr:not([class*='firstRow']) > *:nth-child(6),
table.list#browserComparison tr.firstRow th:nth-child(1) {
@@ -144,7 +136,7 @@ table.list#memoryDetails tr.firstRow th:nth-child(2) {
not just Chrome.
</div>
<div class="otherbrowsers" jsdisplay="browsers.length > 1">
- Note: Chrome includes memory used by plug-ins, other browsers may not.
+ Note: Chrome includes memory used by plugins, other browsers may not.
</div>
<div class="otherbrowsers">
(Bug: We seriously overcount our own memory usage: <a href="http://crbug.com/25454">Issue 25454</a>.)
diff --git a/chromium/chrome/browser/resources/about_nacl.html b/chromium/chrome/browser/resources/about_nacl.html
index e4a610ab400..29a99bc3288 100644
--- a/chromium/chrome/browser/resources/about_nacl.html
+++ b/chromium/chrome/browser/resources/about_nacl.html
@@ -1,10 +1,11 @@
-<!DOCTYPE HTML>
-<html i18n-values="dir:textdirection;">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
<head>
<meta charset="utf-8">
+ <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="chrome://nacl/about_nacl.css">
</head>
-<body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<body>
<div id="loading-message" i18n-content="loadingMessage"></div>
<div id="body-container" hidden>
<div id="header">
@@ -21,7 +22,7 @@
<script src="chrome://resources/js/load_time_data.js"></script>
<script src="chrome://nacl/about_nacl.js"></script>
<script src="chrome://nacl/strings.js"></script>
-<script src="chrome://resources/js/i18n_template2.js"></script>
+<script src="chrome://resources/js/i18n_template.js"></script>
<script src="chrome://resources/js/jstemplate_compiled.js"></script>
<script src="chrome://resources/js/util.js"></script>
</body>
diff --git a/chromium/chrome/browser/resources/about_stats.html b/chromium/chrome/browser/resources/about_stats.html
deleted file mode 100644
index c5ee0c77c6e..00000000000
--- a/chromium/chrome/browser/resources/about_stats.html
+++ /dev/null
@@ -1,135 +0,0 @@
-<!DOCTYPE HTML>
-
-<html id="t">
-<head>
-<meta charset="utf-8">
-<title>About Stats</title>
-<style>
-body {
- border-top: 10px solid #3B85E3;
- color: #333;
- font-family: Verdana, Helvetica, Arial, sans-serif;
-}
-body, td {
- font-size: 11px;
-}
-a:link, a:visited {
- color: #2C3EBA;
- text-decoration: none;
-}
-a:hover {
- color: red;
- text-decoration: underline;
-}
-h1 {
- border-left: 10px solid #FFF;
- font-size: 16px;
- font-weight: bold;
- margin: 0;
- padding: 0.2em;
- color: #3B85E3;
-}
-h2 {
- border-left: 10px solid #FFF;
- font-size: 11px;
- font-weight: normal;
- margin: 0;
- padding: 0 6em 0.2em 0.2em;
-}
-.details {
- margin: 0.4em 1.9em 0 1.2em;
- padding: 0 0.4em 0.3em 0;
- white-space: nowrap;
-}
-.details .outer {
- padding-right: 0;
- vertical-align: top;
-}
-.details .top {
- border-top: 2px solid #333;
- font-weight: bold;
- margin-top: 0.4em;
-}
-.details .header2 {
- font-weight: bold;
- padding-left: 0.9em;
-}
-.details .key {
- padding-left: 1.1em;
- vertical-align: top;
-}
-.details .value {
- text-align: right;
- color: #333;
- font-weight: bold;
-}
-.details .zebra {
- background: #EEE;
-}
-.lower {
- text-transform: lowercase;
-}
-</style>
-<script src="chrome://resources/js/util.js"></script>
-<script src="chrome://stats/stats.js"></script>
-<script src="chrome://stats/strings.js"></script>
-</head>
-<body>
- <div style="float: right">
- <br>Filter: <input id="filter" type="text" value="">
- </div>
- <h1 class="lower">About Stats</h1>
- <h2>Shhh! This page is secret!</h2><br>
- <table class="details" cellspacing="0" cellpadding="0" border="0">
- <tbody>
- <tr>
- <td class="outer">
- <table cellspacing="0" cellpadding="0" border="0">
- <tbody>
- <tr>
- <td class="top" width="100">Counters</td>
- <td class="top value" colspan=2></td>
- </tr>
- <tr>
- <td class="header2 lower" name="string-sort" width="200">name</td>
- <td class="header2 lower" name="number-sort">value</td>
- <td class="header2 lower" name="number-sort">delta</td>
- </tr>
- <tr jsselect="counters" name="counter">
- <td class="key" width="200" jscontent="name"></td>
- <td class="value" jscontent="value"></td>
- <td class="value" jscontent="delta"></td>
- </tr>
- </tbody>
- </table>
- </td>
- <td width="15"></td>
- <td class="outer">
- <table cellspacing="0" cellpadding="0" border="0">
- <tbody>
- <tr>
- <td class="top" width="100">Timers</td>
- <td class="top value"></td>
- <td class="top value" colspan=3></td>
- </tr>
- <tr>
- <td class="header2 lower" name="string-sort" width="200">name</td>
- <td class="header2 lower" name="number-sort">count</td>
- <td class="header2 lower" name="number-sort">time (ms)</td>
- <td class="header2 lower" name="number-sort">avg time (ms)</td>
- </tr>
- <tr jsselect="timers" name="timer">
- <td class="key" width="200" jscontent="name"></td>
- <td class="value" jscontent="value"></td>
- <td class="value" jscontent="time"></td>
- <td class="value"></td>
- </tr>
- </tbody>
- </table>
- </td>
- </tr>
- </tbody>
- </table><br>
- <script src="chrome://resources/js/jstemplate_compiled.js"></script>
-</body>
-</html>
diff --git a/chromium/chrome/browser/resources/about_stats.js b/chromium/chrome/browser/resources/about_stats.js
deleted file mode 100644
index 380d65ec8a3..00000000000
--- a/chromium/chrome/browser/resources/about_stats.js
+++ /dev/null
@@ -1,200 +0,0 @@
-// Copyright (c) 2012 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.
-
-/* Counter accessor for Name Node. */
-function getCounterNameFromCounterNode(node) {
- return node.childNodes[1];
-}
-
-/* Counter accessor for Value Node. */
-function getCounterValueFromCounterNode(node) {
- return node.childNodes[3];
-}
-
-/* Counter accessor for Delta Node. */
-function getCounterDeltaFromCounterNode(node) {
- return node.childNodes[5];
-}
-
-/* Timer accessor for Name Node. */
-function getTimerNameFromTimerNode(node) {
- return node.childNodes[1];
-}
-
-/* Timer accessor for Value Node. */
-function getTimerValueFromTimerNode(node) {
- return node.childNodes[3];
-}
-
-/* Timer accessor for Time Node. */
-function getTimerTimeFromTimerNode(node) {
- return node.childNodes[5];
-}
-
-/* Timer accessor for Average Time Node. */
-function getTimerAvgTimeFromTimerNode(node) {
- return node.childNodes[7];
-}
-
-/* Do the filter work. Hide all nodes matching node.*/
-function filterMatching(text, nodelist, functionToGetNameNode) {
- var showAll = text.length == 0;
- for (var i = 0, node; node = nodelist[i]; i++) {
- var name = functionToGetNameNode(node).innerHTML.toLowerCase();
- if (showAll || name.indexOf(text) >= 0)
- node.style.display = 'table-row';
- else
- node.style.display = 'none';
- }
-}
-
-/* Hides or shows counters based on the user's current filter selection. */
-function doFilter() {
- var filter = $('filter');
- var text = filter.value.toLowerCase();
- var nodes = document.getElementsByName('counter');
- filterMatching(text, nodes, getCounterNameFromCounterNode);
- var nodes = document.getElementsByName('timer');
- filterMatching(text, nodes, getTimerNameFromTimerNode);
-}
-
-/* Colors the counters based on increasing or decreasing value. */
-function doColor() {
- var nodes = document.getElementsByName('counter');
- for (var i = 0, node; node = nodes[i]; i++) {
- var child = getCounterDeltaFromCounterNode(node);
- var delta = child.innerHTML;
- if (delta > 0)
- child.style.color = 'Green';
- else if (delta == 0)
- child.style.color = 'Black';
- else
- child.style.color = 'Red';
- }
-}
-
-/* Counters with no values are null. Remove them. */
-function removeNullValues() {
- var nodes = document.getElementsByName('counter');
- for (var i = nodes.length - 1; i >= 0; i--) {
- var node = nodes[i];
- var value = getCounterValueFromCounterNode(node).innerHTML;
- if (value == 'null')
- node.parentNode.removeChild(node);
- }
- var nodes = document.getElementsByName('timer');
- for (var i = 0, node; node = nodes[i]; i++) {
- var valueNode = getTimerValueFromTimerNode(node);
- if (valueNode.innerHTML == 'null')
- valueNode.innerHTML = '';
- }
-}
-
-/* Compute the average time for timers */
-function computeTimes() {
- var nodes = document.getElementsByName('timer');
- for (var i = 0, node; node = nodes[i]; i++) {
- var count = getTimerValueFromTimerNode(node).innerHTML;
- if (count.length > 0) {
- var time = getTimerTimeFromTimerNode(node).innerHTML;
- var avg = getTimerAvgTimeFromTimerNode(node);
- avg.innerHTML = Math.round(time / count * 100) / 100;
- }
- }
-}
-
-/* All the work we do onload. */
-function onLoadWork() {
- // This is the javascript code that processes the template:
- var input = new JsEvalContext(templateData);
- var output = $('t');
- jstProcess(input, output);
-
- // Add handlers to dynamically created HTML elements.
- var elements = document.getElementsByName('string-sort');
- for (var i = 0; i < elements.length; ++i)
- elements[i].onclick = function() { sortTable('string'); };
-
- elements = document.getElementsByName('number-sort');
- for (i = 0; i < elements.length; ++i)
- elements[i].onclick = function() { sortTable('number'); };
-
- doColor();
- removeNullValues();
- computeTimes();
-
- var filter = $('filter');
- filter.onkeyup = doFilter;
- filter.focus();
-}
-
-// The function should only be used as the event handler
-// on a table cell element. To use it, put it in a <td> element:
-// <td onclick="sort('string')" ...>
-//
-// The function sorts rows after the row with onclick event handler.
-//
-// type: the data type, 'string', 'number'
-function sortTable(type) {
- var cell = event.target;
- var cnum = cell.cellIndex;
-
- var row = cell.parentNode;
- var startIndex = row.rowIndex + 1;
-
- var tbody = row.parentNode;
- var table = tbody.parentNode;
-
- var rows = new Array();
-
- var indexes = new Array();
- // skip the first row
- for (var i = startIndex; i < table.rows.length; i++)
- rows.push(table.rows[i]);
-
- // a, b are strings
- function compareStrings(a, b) {
- if (a == b) return 0;
- if (a < b) return -1;
- return 1;
- }
-
- // a, b are numbers
- function compareNumbers(a, b) {
- var x = isNaN(a) ? 0 : a;
- var y = isNaN(b) ? 0 : b;
- return x - y;
- }
-
- var sortFunc;
- if (type === 'string') {
- sortFunc = function(a, b) {
- var x = a.cells[cnum].innerText;
- var y = b.cells[cnum].innerText;
- return compareStrings(x, y);
- };
-
- } else if (type === 'number') {
- sortFunc = function(a, b) {
- var x = parseFloat(a.cells[cnum].innerText);
- var y = parseFloat(b.cells[cnum].innerText);
- return compareNumbers(x, y);
- };
- }
-
- rows.sort(sortFunc);
-
- // change tables
- if (cell._reverse) {
- for (var i = rows.length - 1; i >= 0; i--)
- tbody.appendChild(rows[i]);
- cell._reverse = false;
- } else {
- for (var i = 0; i < rows.length; i++)
- tbody.appendChild(rows[i]);
- cell._reverse = true;
- }
-}
-
-document.addEventListener('DOMContentLoaded', onLoadWork);
diff --git a/chromium/chrome/browser/resources/about_sys/about_sys.css b/chromium/chrome/browser/resources/about_sys/about_sys.css
index a1c8b27e41a..cfbf99fd84c 100644
--- a/chromium/chrome/browser/resources/about_sys/about_sys.css
+++ b/chromium/chrome/browser/resources/about_sys/about_sys.css
@@ -8,7 +8,6 @@
}
body {
- font-family: Arial, Helvetica, sans-serif;
font-size: 84%;
margin: 0;
min-width: 45em;
@@ -60,7 +59,7 @@ html[dir='rtl'] #header {
#header h1 {
-webkit-padding-start: 3em;
- background: url('../../../../ui/webui/resources/images/gear.png') no-repeat;
+ background: url(../../../../ui/webui/resources/images/gear.png) no-repeat;
background-position: 12px 60%;
color: white;
display: inline;
diff --git a/chromium/chrome/browser/resources/about_sys/about_sys.html b/chromium/chrome/browser/resources/about_sys/about_sys.html
index 5a5065361eb..9b1cd36485e 100644
--- a/chromium/chrome/browser/resources/about_sys/about_sys.html
+++ b/chromium/chrome/browser/resources/about_sys/about_sys.html
@@ -1,9 +1,9 @@
-<!DOCTYPE HTML>
-<html id="t" i18n-values="dir:textdirection;">
+<!doctype html>
+<html id="t" i18n-values="dir:textdirection;lang:language">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title i18n-content="title"></title>
-
+ <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="about_sys.css">
<script src="chrome://resources/js/util.js"></script>
<script src="chrome://resources/js/i18n_template_no_process.js"></script>
diff --git a/chromium/chrome/browser/resources/about_version.css b/chromium/chrome/browser/resources/about_version.css
index c7197d26e24..343dedfafa7 100644
--- a/chromium/chrome/browser/resources/about_version.css
+++ b/chromium/chrome/browser/resources/about_version.css
@@ -5,7 +5,7 @@
body {
background-color: white;
color: black;
- font-family: Helvetica,Arial,sans-serif;
+ font-size: 100%;
margin: 0;
}
@@ -59,4 +59,5 @@ body {
font-family: monospace;
max-width: 430px;
padding-left: 5px;
+ vertical-align: bottom;
}
diff --git a/chromium/chrome/browser/resources/about_version.html b/chromium/chrome/browser/resources/about_version.html
index e03ac8026cd..1973db19329 100644
--- a/chromium/chrome/browser/resources/about_version.html
+++ b/chromium/chrome/browser/resources/about_version.html
@@ -1,13 +1,14 @@
-<!DOCTYPE HTML>
+<!doctype html>
<!--
about:version template page
-->
-<html id="t" i18n-values="dir:textdirection;">
+<html id="t" i18n-values="dir:textdirection;lang:language">
<head>
<meta charset="utf-8">
<title i18n-content="title"></title>
+ <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<if expr="is_android">
<meta name="viewport" content="width=device-width">
</if>
@@ -41,6 +42,7 @@ about:version template page
<span i18n-content="version"></span>
(<span i18n-content="official"></span>)
<span i18n-content="version_modifier"></span>
+ <span i18n-content="version_bitsize"></span>
</td>
</tr>
<tr>
@@ -101,6 +103,6 @@ about:version template page
</if>
</table>
</div>
- <script src="chrome://resources/js/i18n_template2.js"></script>
+ <script src="chrome://resources/js/i18n_template.js"></script>
</body>
</html>
diff --git a/chromium/chrome/browser/resources/about_version.js b/chromium/chrome/browser/resources/about_version.js
index d97191771e5..476311bfae9 100644
--- a/chromium/chrome/browser/resources/about_version.js
+++ b/chromium/chrome/browser/resources/about_version.js
@@ -6,7 +6,7 @@
* Callback from the backend with the list of variations to display.
* This call will build the variations section of the version page, or hide that
* section if there are none to display.
- * @param {!Array.<string>} variationsList The list of variations.
+ * @param {!Array<string>} variationsList The list of variations.
*/
function returnVariationInfo(variationsList) {
$('variations-section').hidden = !variationsList.length;
diff --git a/chromium/chrome/browser/resources/about_voicesearch.html b/chromium/chrome/browser/resources/about_voicesearch.html
index 8be3e855311..93a43dc6485 100644
--- a/chromium/chrome/browser/resources/about_voicesearch.html
+++ b/chromium/chrome/browser/resources/about_voicesearch.html
@@ -1,7 +1,8 @@
-<!DOCTYPE HTML>
-<html i18n-values="dir:textdirection;">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
<head>
<meta charset="utf-8">
+<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<style>
.key {
font-weight: bold;
@@ -12,7 +13,7 @@
}
</style>
</head>
-<body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<body>
<div id="loading-message" i18n-content="loadingMessage">LOADING_MESSAGE</div>
<div id="body-container" hidden>
<div id="header">
@@ -30,7 +31,7 @@
<script src="chrome://resources/js/load_time_data.js"></script>
<script src="chrome://voicesearch/about_voicesearch.js"></script>
<script src="chrome://voicesearch/strings.js"></script>
-<script src="chrome://resources/js/i18n_template2.js"></script>
+<script src="chrome://resources/js/i18n_template.js"></script>
<script src="chrome://resources/js/jstemplate_compiled.js"></script>
<script src="chrome://resources/js/util.js"></script>
</body>
diff --git a/chromium/chrome/browser/resources/app_list/audio_manager.js b/chromium/chrome/browser/resources/app_list/audio_manager.js
deleted file mode 100644
index aa6beb0a2a3..00000000000
--- a/chromium/chrome/browser/resources/app_list/audio_manager.js
+++ /dev/null
@@ -1,107 +0,0 @@
-// Copyright 2013 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 The manager of audio streams.
- */
-
-cr.define('speech', function() {
- 'use strict';
-
- /**
- * The enum of the status of hotword audio recognition.
- *
- * @enum {number}
- */
- var AudioState = {
- STOPPED: 0,
- LISTENING: 1
- };
-
- /**
- * @constructor
- * @extends {cr.EventTarget}
- */
- function AudioManager() {
- var audioContext = new window.AudioContext();
- this.sampleRate = audioContext.sampleRate;
- this.audioProc_ = null;
- this.audioIn_ = null;
- this.stream_ = null;
- this.state = AudioState.STOPPED;
- };
-
- AudioManager.prototype.__proto__ = cr.EventTarget.prototype;
-
- /**
- * Called when the audio data arrives.
- *
- * @param {Event} audioEvent The audio event.
- * @private
- */
- AudioManager.prototype.onAudioProcess_ = function(audioEvent) {
- var data = audioEvent.inputBuffer.getChannelData(0);
- var intData = new Int16Array(data.length);
- for (var i = 0; i < data.length; ++i)
- intData[i] = Math.round(data[i] * 32767);
- var event = new Event('audio');
- event.data = intData;
- this.dispatchEvent(event);
- };
-
- /**
- * Called when the audio stream is ready.
- *
- * @param {MediaStream} stream The media stream which is now available.
- * @private
- */
- AudioManager.prototype.onAudioReady_ = function(stream) {
- var audioContext = new window.AudioContext();
- this.stream_ = stream;
- this.audioIn_ = audioContext.createMediaStreamSource(stream);
- this.audioProc_ = audioContext.createScriptProcessor(
- 4096 /* buffer size */, 1 /* channels */, 1 /* channels */);
- this.audioProc_.onaudioprocess = this.onAudioProcess_.bind(this);
-
- this.audioIn_.connect(this.audioProc_);
- this.audioProc_.connect(audioContext.destination);
- this.state = AudioState.LISTENING;
- };
-
- /**
- * Starts the audio processing.
- */
- AudioManager.prototype.start = function() {
- if (this.state == AudioState.LISTENING)
- return;
-
- navigator.webkitGetUserMedia(
- {audio: true},
- this.onAudioReady_.bind(this),
- function(msg) { console.error('Failed to getUserMedia: ' + msg); });
- };
-
- /**
- * Stops the audio processing.
- */
- AudioManager.prototype.stop = function() {
- if (this.state != AudioState.LISTENING)
- return;
- this.audioProc_.disconnect();
- this.audioIn_.disconnect();
- var audioTracks = this.stream_.getAudioTracks();
- for (var i = 0; i < audioTracks.length; ++i) {
- audioTracks[i].stop();
- }
- this.audioProc_ = null;
- this.audioIn_ = null;
- this.stream_ = null;
- this.state = AudioState.STOPPED;
- };
-
- return {
- AudioManager: AudioManager,
- AudioState: AudioState
- };
-});
diff --git a/chromium/chrome/browser/resources/app_list/hotword_nacl.nmf b/chromium/chrome/browser/resources/app_list/hotword_nacl.nmf
deleted file mode 100644
index 2a3ae61a77f..00000000000
--- a/chromium/chrome/browser/resources/app_list/hotword_nacl.nmf
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "program": {
- "x86-64": { "url": "chrome://app-list/hotword_x86_64.nexe" },
- "x86-32": { "url": "chrome://app-list/hotword_x86_32.nexe" },
- "arm": {"url": "chrome://app-list/hotword_arm.nexe" }
- }
-}
diff --git a/chromium/chrome/browser/resources/app_list/plugin_manager.js b/chromium/chrome/browser/resources/app_list/plugin_manager.js
deleted file mode 100644
index 6a025e0548a..00000000000
--- a/chromium/chrome/browser/resources/app_list/plugin_manager.js
+++ /dev/null
@@ -1,168 +0,0 @@
-// Copyright 2013 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 The manager of offline hotword speech recognizer plugin.
- */
-
-cr.define('speech', function() {
- 'use strict';
-
- /** The timeout milliseconds to load the model file. */
- var MODEL_LOAD_TIMEOUT = 2000;
-
- /**
- * The type of the plugin state.
- ** @enum {number}
- */
- var PluginState = {
- UNINITIALIZED: 0,
- LOADED: 1,
- READY: 2,
- RECOGNIZING: 3
- };
-
- /**
- * The command names of the plugin.
- * @enum {string}
- */
- var pluginCommands = {
- SET_SAMPLING_RATE: 'h',
- SET_CONFIG: 'm',
- START_RECOGNIZING: 'r',
- STOP_RECOGNIZING: 's'
- };
-
- /**
- * The regexp pattern of the hotword recognition result.
- */
- var recognitionPattern = /^(HotwordFiredEvent:|hotword)/;
-
- /**
- * @constructor
- */
- function PluginManager(prefix, onReady, onRecognized, onError) {
- this.state = PluginState.UNINITIALIZED;
- this.onReady_ = onReady;
- this.onRecognized_ = onRecognized;
- this.onError_ = onError;
- this.samplingRate_ = null;
- this.config_ = null;
- this.modelLoadTimeoutId_ = null;
- var recognizer = $('recognizer');
- if (!recognizer) {
- recognizer = document.createElement('EMBED');
- recognizer.id = 'recognizer';
- recognizer.type = 'application/x-nacl';
- recognizer.src = 'chrome://app-list/hotword_' + prefix + '.nmf';
- recognizer.width = '1';
- recognizer.height = '1';
- document.body.appendChild(recognizer);
- }
- recognizer.addEventListener('error', onError);
- recognizer.addEventListener('message', this.onMessage_.bind(this));
- recognizer.addEventListener('load', this.onLoad_.bind(this));
- };
-
- /**
- * The event handler of the plugin status.
- *
- * @param {Event} messageEvent the event object from the plugin.
- * @private
- */
- PluginManager.prototype.onMessage_ = function(messageEvent) {
- if (messageEvent.data == 'audio') {
- var wasNotReady = this.state < PluginState.READY;
- this.state = PluginState.RECOGNIZING;
- if (wasNotReady) {
- window.clearTimeout(this.modelLoadTimeoutId_);
- this.modelLoadTimeoutId_ = null;
- this.onReady_(this);
- }
- } else if (messageEvent.data == 'stopped' &&
- this.state == PluginState.RECOGNIZING) {
- this.state = PluginState.READY;
- } else if (recognitionPattern.exec(messageEvent.data)) {
- this.onRecognized_();
- }
- };
-
- /**
- * The event handler when the plugin is loaded.
- *
- * @private
- */
- PluginManager.prototype.onLoad_ = function() {
- if (this.state == PluginState.UNINITIALIZED) {
- this.state = PluginState.LOADED;
- if (this.samplingRate_ && this.config_)
- this.initialize_(this.samplingRate_, this.config_);
- // Sets the timeout for initialization in case that NaCl module failed to
- // respond during the initialization.
- this.modelLoadTimeoutId_ = window.setTimeout(
- this.onError_, MODEL_LOAD_TIMEOUT);
- }
- };
-
- /**
- * Sends the initialization messages to the plugin. This method is private.
- * The initialization will happen from onLoad_ or scheduleInitialize.
- *
- * @param {number} samplingRate the sampling rate the plugin accepts.
- * @param {string} config the url of the config file.
- * @private
- */
- PluginManager.prototype.initialize_ = function(samplingRate, config) {
- $('recognizer').postMessage(
- pluginCommands.SET_SAMPLING_RATE + samplingRate);
- $('recognizer').postMessage(pluginCommands.SET_CONFIG + config);
- };
-
- /**
- * Initializes the plugin with the specified parameter, or schedules the
- * initialization if the plugin is not ready.
- *
- * @param {number} samplingRate the sampling rate the plugin accepts.
- * @param {string} config the url of the config file.
- */
- PluginManager.prototype.scheduleInitialize = function(samplingRate, config) {
- if (this.state == PluginState.UNINITIALIZED) {
- this.samplingRate_ = samplingRate;
- this.config_ = config;
- } else {
- this.initialize_(samplingRate, config);
- }
- };
-
- /**
- * Asks the plugin to start recognizing the hotword.
- */
- PluginManager.prototype.startRecognizer = function() {
- if (this.state == PluginState.READY)
- $('recognizer').postMessage(pluginCommands.START_RECOGNIZING);
- };
-
- /**
- * Asks the plugin to stop recognizing the hotword.
- */
- PluginManager.prototype.stopRecognizer = function() {
- if (this.state == PluginState.RECOGNIZING)
- $('recognizer').postMessage(pluginCommands.STOP_RECOGNIZING);
- };
-
- /**
- * Sends the actual audio wave data.
- *
- * @param {cr.event.Event} event The event for the audio data.
- */
- PluginManager.prototype.sendAudioData = function(event) {
- if (this.state == PluginState.RECOGNIZING)
- $('recognizer').postMessage(event.data.buffer);
- };
-
- return {
- PluginManager: PluginManager,
- PluginState: PluginState,
- };
-});
diff --git a/chromium/chrome/browser/resources/app_list/recommended_apps.css b/chromium/chrome/browser/resources/app_list/recommended_apps.css
deleted file mode 100644
index b02c4968c42..00000000000
--- a/chromium/chrome/browser/resources/app_list/recommended_apps.css
+++ /dev/null
@@ -1,31 +0,0 @@
-/* Copyright 2013 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. */
-
-.recommended-apps {
- -webkit-align-items: center;
- -webkit-justify-content: space-around;
- display: -webkit-flex;
- padding: 0 10px;
-}
-
-.app {
- background-position: center 5px;
- background-repeat: no-repeat;
- background-size: 48px;
- color: rgb(90, 90, 90);
- cursor: default;
- font-size: 11px;
- font-weight: bold;
- height: 20px;
- overflow: hidden;
- padding-top: 58px;
- text-align: center;
- text-overflow: ellipsis;
- white-space: nowrap;
- width: 88px;
-}
-
-.app:hover {
- background-color: rgb(230, 230, 230);
-}
diff --git a/chromium/chrome/browser/resources/app_list/recommended_apps.js b/chromium/chrome/browser/resources/app_list/recommended_apps.js
deleted file mode 100644
index d5fe706776d..00000000000
--- a/chromium/chrome/browser/resources/app_list/recommended_apps.js
+++ /dev/null
@@ -1,90 +0,0 @@
-// Copyright 2013 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 Implement the recommended apps card in the launcher start page.
- */
-
-cr.define('appList.startPage', function() {
- 'use strict';
-
- /**
- * Create a view with icon and label for the given app data.
- * @constructor
- * @extends {HTMLDivElement}
- */
- var AppItemView = cr.ui.define('div');
-
- AppItemView.prototype = {
- __proto__: HTMLDivElement.prototype,
-
- /**
- * The app id of the app displayed by this view. Used to launch
- * the app when the view is clicked.
- * @type {string}
- */
- appId: '',
-
- /**
- * Sets the icon URL to display the app icon.
- * @type {string}
- */
- set iconUrl(url) {
- this.style.backgroundImage = 'url(' + url + ')';
- },
-
- /**
- * Sets the text title.
- * @type {string}
- */
- set textTitle(title) {
- this.textContent = title;
- },
-
- /** @override */
- decorate: function() {
- this.className = 'app';
- this.addEventListener('click', this.handleClick_.bind(this));
- },
-
- /**
- * Handles 'click' event.
- * @private
- */
- handleClick_: function() {
- assert(this.appId);
- chrome.send('launchApp', [this.appId]);
- }
- };
-
- /**
- * Create recommended apps card.
- * @constructor
- * @extends {HTMLDivElement}
- */
- var RecommendedApps = cr.ui.define('div');
-
- RecommendedApps.prototype = {
- __proto__: HTMLDivElement.prototype,
-
- /** @override */
- decorate: function() {
- this.className = 'recommended-apps';
- },
-
- /**
- * Sets the apps to be displayed in this card.
- */
- setApps: function(apps) {
- this.textContent = '';
- for (var i = 0; i < apps.length; ++i) {
- this.appendChild(new AppItemView(apps[i]));
- }
- }
- };
-
- return {
- RecommendedApps: RecommendedApps
- };
-});
diff --git a/chromium/chrome/browser/resources/app_list/speech_manager.js b/chromium/chrome/browser/resources/app_list/speech_manager.js
deleted file mode 100644
index 74f4668ffdb..00000000000
--- a/chromium/chrome/browser/resources/app_list/speech_manager.js
+++ /dev/null
@@ -1,312 +0,0 @@
-// Copyright 2013 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 The class to Manage both offline / online speech recognition.
- */
-
-<include src="plugin_manager.js">
-<include src="audio_manager.js">
-<include src="speech_recognition_manager.js">
-
-cr.define('speech', function() {
- 'use strict';
-
- /**
- * The state of speech recognition.
- *
- * @enum {string}
- */
- var SpeechState = {
- READY: 'READY',
- HOTWORD_RECOGNIZING: 'HOTWORD_RECOGNIZING',
- RECOGNIZING: 'RECOGNIZING',
- IN_SPEECH: 'IN_SPEECH',
- STOPPING: 'STOPPING',
- NETWORK_ERROR: 'NETWORK_ERROR'
- };
-
- /**
- * The time to show the network error message in seconds.
- *
- * @const {number}
- */
- var SPEECH_ERROR_TIMEOUT = 3;
-
- /**
- * Checks the prefix for the hotword module based on the language. This is
- * fragile if the file structure has changed.
- */
- function getHotwordPrefix() {
- var prefix = navigator.language.toLowerCase();
- if (prefix == 'en-gb')
- return prefix;
- var hyphen = prefix.indexOf('-');
- if (hyphen >= 0)
- prefix = prefix.substr(0, hyphen);
- if (prefix == 'en')
- prefix = '';
- return prefix;
- }
-
- /**
- * @constructor
- */
- function SpeechManager() {
- this.audioManager_ = new speech.AudioManager();
- this.audioManager_.addEventListener('audio', this.onAudioLevel_.bind(this));
- this.speechRecognitionManager_ = new speech.SpeechRecognitionManager(this);
- this.errorTimeoutId_ = null;
- }
-
- /**
- * Updates the state.
- *
- * @param {SpeechState} newState The new state.
- * @private
- */
- SpeechManager.prototype.setState_ = function(newState) {
- if (this.state == newState)
- return;
-
- this.state = newState;
- chrome.send('setSpeechRecognitionState', [this.state]);
- };
-
- /**
- * Called with the mean audio level when audio data arrives.
- *
- * @param {cr.event.Event} event The event object for the audio data.
- * @private
- */
- SpeechManager.prototype.onAudioLevel_ = function(event) {
- var data = event.data;
- var level = 0;
- for (var i = 0; i < data.length; ++i)
- level += Math.abs(data[i]);
- level /= data.length;
- chrome.send('speechSoundLevel', [level]);
- };
-
- /**
- * Called when the hotword recognizer is ready.
- *
- * @param {PluginManager} pluginManager The hotword plugin manager which gets
- * ready.
- * @private
- */
- SpeechManager.prototype.onHotwordRecognizerReady_ = function(pluginManager) {
- this.pluginManager_ = pluginManager;
- this.audioManager_.addEventListener(
- 'audio', pluginManager.sendAudioData.bind(pluginManager));
- this.pluginManager_.startRecognizer();
- this.audioManager_.start();
- this.setState_(SpeechState.HOTWORD_RECOGNIZING);
- };
-
- /**
- * Called when an error happens for loading the hotword recognizer.
- *
- * @private
- */
- SpeechManager.prototype.onHotwordRecognizerLoadError_ = function() {
- this.setHotwordEnabled(false);
- this.setState_(SpeechState.READY);
- };
-
- /**
- * Called when the hotword is recognized.
- *
- * @private
- */
- SpeechManager.prototype.onHotwordRecognized_ = function() {
- if (this.state != SpeechState.HOTWORD_RECOGNIZING)
- return;
- this.pluginManager_.stopRecognizer();
- this.speechRecognitionManager_.start();
- };
-
- /**
- * Called when the speech recognition has happened.
- *
- * @param {string} result The speech recognition result.
- * @param {boolean} isFinal Whether the result is final or not.
- */
- SpeechManager.prototype.onSpeechRecognized = function(result, isFinal) {
- chrome.send('speechResult', [result, isFinal]);
- if (isFinal)
- this.speechRecognitionManager_.stop();
- };
-
- /**
- * Called when the speech recognition has started.
- */
- SpeechManager.prototype.onSpeechRecognitionStarted = function() {
- this.setState_(SpeechState.RECOGNIZING);
- };
-
- /**
- * Called when the speech recognition has ended.
- */
- SpeechManager.prototype.onSpeechRecognitionEnded = function() {
- // Do not handle the speech recognition ends if it ends due to an error
- // because an error message should be shown for a while.
- // See onSpeechRecognitionError.
- if (this.state == SpeechState.NETWORK_ERROR)
- return;
-
- // Restarts the hotword recognition.
- if (this.state != SpeechState.STOPPING && this.pluginManager_) {
- this.pluginManager_.startRecognizer();
- this.audioManager_.start();
- this.setState_(SpeechState.HOTWORD_RECOGNIZING);
- } else {
- this.audioManager_.stop();
- this.setState_(SpeechState.READY);
- }
- };
-
- /**
- * Called when a speech has started.
- */
- SpeechManager.prototype.onSpeechStarted = function() {
- if (this.state == SpeechState.RECOGNIZING)
- this.setState_(SpeechState.IN_SPEECH);
- };
-
- /**
- * Called when a speech has ended.
- */
- SpeechManager.prototype.onSpeechEnded = function() {
- if (this.state == SpeechState.IN_SPEECH)
- this.setState_(SpeechState.RECOGNIZING);
- };
-
- /**
- * Called when the speech manager should recover from the error state.
- *
- * @private
- */
- SpeechManager.prototype.onSpeechRecognitionErrorTimeout_ = function() {
- this.errorTimeoutId_ = null;
- this.setState_(SpeechState.READY);
- this.onSpeechRecognitionEnded();
- };
-
- /**
- * Called when an error happened during the speech recognition.
- *
- * @param {SpeechRecognitionError} e The error object.
- */
- SpeechManager.prototype.onSpeechRecognitionError = function(e) {
- if (e.error == 'network') {
- this.setState_(SpeechState.NETWORK_ERROR);
- this.errorTimeoutId_ = window.setTimeout(
- this.onSpeechRecognitionErrorTimeout_.bind(this),
- SPEECH_ERROR_TIMEOUT * 1000);
- } else {
- if (this.state != SpeechState.STOPPING)
- this.setState_(SpeechState.READY);
- }
- };
-
- /**
- * Changes the availability of the hotword plugin.
- *
- * @param {boolean} enabled Whether enabled or not.
- */
- SpeechManager.prototype.setHotwordEnabled = function(enabled) {
- var recognizer = $('recognizer');
- if (enabled) {
- if (recognizer)
- return;
- if (!this.naclArch)
- return;
-
- var prefix = getHotwordPrefix();
- var pluginManager = new speech.PluginManager(
- prefix,
- this.onHotwordRecognizerReady_.bind(this),
- this.onHotwordRecognized_.bind(this),
- this.onHotwordRecognizerLoadError_.bind(this));
- var modelUrl = 'chrome://app-list/_platform_specific/' + this.naclArch +
- '_' + prefix + '/hotword.data';
- pluginManager.scheduleInitialize(this.audioManager_.sampleRate, modelUrl);
- } else {
- if (!recognizer)
- return;
- document.body.removeChild(recognizer);
- this.pluginManager_ = null;
- if (this.state == SpeechState.HOTWORD_RECOGNIZING) {
- this.audioManager_.stop();
- this.setState_(SpeechState.READY);
- }
- }
- };
-
- /**
- * Sets the NaCl architecture for the hotword module.
- *
- * @param {string} arch The architecture.
- */
- SpeechManager.prototype.setNaclArch = function(arch) {
- this.naclArch = arch;
- };
-
- /**
- * Called when the app-list bubble is shown.
- *
- * @param {boolean} hotwordEnabled Whether the hotword is enabled or not.
- */
- SpeechManager.prototype.onShown = function(hotwordEnabled) {
- this.setHotwordEnabled(hotwordEnabled);
-
- // No one sets the state if the content is initialized on shown but hotword
- // is not enabled. Sets the state in such case.
- if (!this.state && !hotwordEnabled)
- this.setState_(SpeechState.READY);
- };
-
- /**
- * Called when the app-list bubble is hidden.
- */
- SpeechManager.prototype.onHidden = function() {
- this.setHotwordEnabled(false);
-
- // SpeechRecognition is asynchronous.
- this.audioManager_.stop();
- if (this.state == SpeechState.RECOGNIZING ||
- this.state == SpeechState.IN_SPEECH) {
- this.setState_(SpeechState.STOPPING);
- this.speechRecognitionManager_.stop();
- } else {
- this.setState_(SpeechState.READY);
- }
- };
-
- /**
- * Toggles the current state of speech recognition.
- */
- SpeechManager.prototype.toggleSpeechRecognition = function() {
- if (this.state == SpeechState.NETWORK_ERROR) {
- if (this.errorTimeoutId_)
- window.clearTimeout(this.errorTimeoutId_);
- this.onSpeechRecognitionErrorTimeout_();
- } else if (this.state == SpeechState.RECOGNIZING ||
- this.state == SpeechState.IN_SPEECH) {
- this.audioManager_.stop();
- this.speechRecognitionManager_.stop();
- } else {
- if (this.pluginManager_)
- this.pluginManager_.stopRecognizer();
- if (this.audioManager_.state == speech.AudioState.STOPPED)
- this.audioManager_.start();
- this.speechRecognitionManager_.start();
- }
- };
-
- return {
- SpeechManager: SpeechManager
- };
-});
diff --git a/chromium/chrome/browser/resources/app_list/speech_recognition_manager.js b/chromium/chrome/browser/resources/app_list/speech_recognition_manager.js
deleted file mode 100644
index bffb383de84..00000000000
--- a/chromium/chrome/browser/resources/app_list/speech_recognition_manager.js
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright 2013 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 The manager of speech recognition.
- */
-
-cr.define('speech', function() {
- 'use strict';
-
- /**
- * @constructor
- */
- function SpeechRecognitionManager(delegate) {
- this.isActive = true;
- this.delegate_ = delegate;
-
- this.recognizer_ = new window.webkitSpeechRecognition();
- this.recognizer_.continuous = true;
- this.recognizer_.interimResults = true;
- this.recognizer_.lang = navigator.language;
-
- this.recognizer_.onresult = this.onRecognizerResult_.bind(this);
- if (this.delegate_) {
- this.recognizer_.onstart =
- this.delegate_.onSpeechRecognitionStarted.bind(this.delegate_);
- this.recognizer_.onend =
- this.delegate_.onSpeechRecognitionEnded.bind(this.delegate_);
- this.recognizer_.onspeechstart =
- this.delegate_.onSpeechStarted.bind(this.delegate_);
- this.recognizer_.onspeechend =
- this.delegate_.onSpeechEnded.bind(this.delegate_);
- this.recognizer_.onerror = this.onRecognizerError_.bind(this);
- }
- }
-
- /**
- * Called when new speech recognition results arrive.
- *
- * @param {Event} speechEvent The event to contain the recognition results.
- * @private
- */
- SpeechRecognitionManager.prototype.onRecognizerResult_ = function(
- speechEvent) {
- // Do not collect interim result for now.
- var result = '';
- var isFinal = false;
- for (var i = 0; i < speechEvent.results.length; i++) {
- if (speechEvent.results[i].isFinal)
- isFinal = true;
- result += speechEvent.results[i][0].transcript;
- }
- if (this.delegate_)
- this.delegate_.onSpeechRecognized(result, isFinal);
- };
-
- /**
- * Called when an error happens in speech recognition.
- *
- * @param {SpeechRecognitionError} error The error data.
- * @private
- */
- SpeechRecognitionManager.prototype.onRecognizerError_ = function(error) {
- if (error.error == 'language-not-supported') {
- // Falls back to English and restart.
- this.recognizer_.lang = 'en-US';
- this.start();
- } else {
- this.delegate_.onSpeechRecognitionError(error);
- }
- };
-
- /**
- * Starts the speech recognition through webkitSpeechRecognition.
- */
- SpeechRecognitionManager.prototype.start = function() {
- this.recognizer_.start();
- };
-
- /**
- * Stops the ongoing speech recognition.
- */
- SpeechRecognitionManager.prototype.stop = function() {
- this.recognizer_.abort();
- };
-
- return {
- SpeechRecognitionManager: SpeechRecognitionManager
- };
-});
diff --git a/chromium/chrome/browser/resources/app_list/start_page.css b/chromium/chrome/browser/resources/app_list/start_page.css
index 73e357851df..268feeb6c51 100644
--- a/chromium/chrome/browser/resources/app_list/start_page.css
+++ b/chromium/chrome/browser/resources/app_list/start_page.css
@@ -3,9 +3,8 @@
* found in the LICENSE file. */
html,
-body,
-#start-page {
- background-color: rgb(245, 245, 245);
+body {
+ -webkit-user-select: none;
height: 100%;
margin: 0;
overflow: hidden;
@@ -13,23 +12,23 @@ body,
width: 100%;
}
-#start-page {
- -webkit-align-items: stretch;
- -webkit-flex-direction: column;
- -webkit-justify-content: flex-start;
- -webkit-user-select: none;
- box-sizing: border-box;
- display: -webkit-flex;
- padding: 10px;
+#doodle {
+ display: none;
+ justify-content: center;
}
-#logo {
+#default_logo {
background-image: -webkit-image-set(
- url('chrome://theme/IDR_LOCAL_NTP_IMAGES_LOGO_PNG') 1x,
- url('chrome://theme/IDR_LOCAL_NTP_IMAGES_LOGO_PNG@2x') 2x);
+ url(chrome://theme/IDR_LOCAL_NTP_IMAGES_LOGO_PNG) 1x,
+ url(chrome://theme/IDR_LOCAL_NTP_IMAGES_LOGO_PNG@2x) 2x);
+ background-repeat: no-repeat;
height: 95px;
margin: auto;
width: 269px;
}
-<include src="recommended_apps.css">
+#logo_container {
+ bottom: 0;
+ position: absolute;
+ width: 100%;
+}
diff --git a/chromium/chrome/browser/resources/app_list/start_page.html b/chromium/chrome/browser/resources/app_list/start_page.html
index 2a939afe69d..5388e65d334 100644
--- a/chromium/chrome/browser/resources/app_list/start_page.html
+++ b/chromium/chrome/browser/resources/app_list/start_page.html
@@ -1,7 +1,8 @@
-<!DOCTYPE HTML>
-<html i18n-values="dir:textdirection">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
<head>
<meta charset="utf-8">
+ <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="chrome://app-list/start_page.css">
<script src="chrome://resources/js/load_time_data.js"></script>
<script src="chrome://resources/js/cr.js"></script>
@@ -10,11 +11,13 @@
<script src="chrome://resources/js/util.js"></script>
<script src="chrome://app-list/strings.js"></script>
<script src="chrome://app-list/start_page.js"></script>
+ <base id="base">
</head>
-<body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
- <div id="logo"></div>
- <div id="start-page"></div>
- <script src="chrome://resources/js/i18n_template2.js"></script>
+<body>
+ <div id="logo_container">
+ <div id="default_logo"></div>
+ </div>
+ <script src="chrome://resources/js/i18n_template.js"></script>
</body>
</html>
diff --git a/chromium/chrome/browser/resources/app_list/start_page.js b/chromium/chrome/browser/resources/app_list/start_page.js
index 999373f3ecd..10ea9f5ab46 100644
--- a/chromium/chrome/browser/resources/app_list/start_page.js
+++ b/chromium/chrome/browser/resources/app_list/start_page.js
@@ -6,115 +6,103 @@
* @fileoverview App launcher start page implementation.
*/
-<include src="recommended_apps.js">
-<include src="speech_manager.js">
-
cr.define('appList.startPage', function() {
'use strict';
- var speechManager = null;
-
- /**
- * Creates a StartPage object.
- * @constructor
- * @extends {HTMLDivElement}
- */
- var StartPage = cr.ui.define('div');
-
- StartPage.prototype = {
- __proto__: HTMLDivElement.prototype,
-
- /**
- * Instance of the recommended apps card.
- * @type {appsList.startPage.RecommendedApps}
- * @private
- */
- recommendedApps_: null,
-
- /** @override */
- decorate: function() {
- this.recommendedApps_ = new appList.startPage.RecommendedApps();
- this.appendChild(this.recommendedApps_);
- },
-
- /**
- * Sets the recommended apps.
- * @param {!Array.<!{appId: string,
- * iconUrl: string,
- * textTitle: string}>} apps An array of app info
- * dictionary to be displayed in the AppItemView.
- */
- setRecommendedApps: function(apps) {
- this.recommendedApps_.setApps(apps);
- }
- };
+ // The element containing the current Google Doodle.
+ var doodle = null;
/**
* Initialize the page.
*/
function initialize() {
- StartPage.decorate($('start-page'));
- speechManager = new speech.SpeechManager();
chrome.send('initialize');
}
/**
- * Sets the recommended apps.
- * @param {Array.<Object>} apps An array of app info dictionary.
+ * Invoked when the app-list bubble is shown.
*/
- function setRecommendedApps(apps) {
- $('start-page').setRecommendedApps(apps);
+ function onAppListShown() {
+ chrome.send('appListShown', [this.doodle != null]);
}
/**
- * Invoked when the hotword plugin availability is changed.
+ * Sets the doodle's visibility, hiding or showing the default logo.
*
- * @param {boolean} enabled Whether the plugin is enabled or not.
+ * @param {boolean} visible Whether the doodle should be made visible.
*/
- function setHotwordEnabled(enabled) {
- speechManager.setHotwordEnabled(enabled);
- }
-
- /**
- * Sets the architecture of NaCl module to be loaded for hotword.
- * @param {string} arch The architecture.
- */
- function setNaclArch(arch) {
- speechManager.setNaclArch(arch);
+ function setDoodleVisible(visible) {
+ var doodle = $('doodle');
+ var defaultLogo = $('default_logo');
+ if (visible) {
+ doodle.style.display = 'flex';
+ defaultLogo.style.display = 'none';
+ } else {
+ if (doodle)
+ doodle.style.display = 'none';
+
+ defaultLogo.style.display = 'block';
+ }
}
/**
- * Invoked when the app-list bubble is shown.
+ * Invoked when the app-list doodle is updated.
*
- * @param {boolean} hotwordEnabled Whether the hotword is enabled or not.
+ * @param {Object} data The data object representing the current doodle.
*/
- function onAppListShown(hotwordEnabled) {
- speechManager.onShown(hotwordEnabled);
- }
+ function onAppListDoodleUpdated(data, base_url) {
+ if (this.doodle) {
+ this.doodle.parentNode.removeChild(this.doodle);
+ this.doodle = null;
+ }
- /**
- * Invoked when the app-list bubble is hidden.
- */
- function onAppListHidden() {
- speechManager.onHidden();
- }
+ var doodleData = data.ddljson;
+ if (!doodleData || !doodleData.transparent_large_image) {
+ setDoodleVisible(false);
+ return;
+ }
- /**
- * Invoked when the user explicitly wants to toggle the speech recognition
- * state.
- */
- function toggleSpeechRecognition() {
- speechManager.toggleSpeechRecognition();
+ // Set the page's base URL so that links will resolve relative to the Google
+ // homepage.
+ $('base').href = base_url;
+
+ this.doodle = document.createElement('div');
+ this.doodle.id = 'doodle';
+ this.doodle.style.display = 'none';
+
+ var doodleImage = document.createElement('img');
+ doodleImage.id = 'doodle_image';
+ if (doodleData.alt_text) {
+ doodleImage.alt = doodleData.alt_text;
+ doodleImage.title = doodleData.alt_text;
+ }
+
+ doodleImage.onload = function() {
+ setDoodleVisible(true);
+ };
+ doodleImage.src = doodleData.transparent_large_image.url;
+
+ if (doodleData.target_url) {
+ var doodleLink = document.createElement('a');
+ doodleLink.id = 'doodle_link';
+ doodleLink.href = doodleData.target_url;
+ doodleLink.target = '_blank';
+ doodleLink.appendChild(doodleImage);
+ doodleLink.onclick = function() {
+ chrome.send('doodleClicked');
+ return true;
+ };
+ this.doodle.appendChild(doodleLink);
+ } else {
+ this.doodle.appendChild(doodleImage);
+ }
+ $('logo_container').appendChild(this.doodle);
}
return {
initialize: initialize,
- setRecommendedApps: setRecommendedApps,
- setHotwordEnabled: setHotwordEnabled,
- setNaclArch: setNaclArch,
+ onAppListDoodleUpdated: onAppListDoodleUpdated,
onAppListShown: onAppListShown,
- onAppListHidden: onAppListHidden,
- toggleSpeechRecognition: toggleSpeechRecognition
};
});
diff --git a/chromium/chrome/browser/resources/bookmark_manager/css/bmm.css b/chromium/chrome/browser/resources/bookmark_manager/css/bmm.css
index cbe770da9a5..41e229ef53e 100644
--- a/chromium/chrome/browser/resources/bookmark_manager/css/bmm.css
+++ b/chromium/chrome/browser/resources/bookmark_manager/css/bmm.css
@@ -79,8 +79,8 @@ html[dir=rtl] list .label {
list > .folder > .label {
background-image: -webkit-image-set(
- url('../../../../../ui/resources/default_100_percent/common/folder_closed.png') 1x,
- url('../../../../../ui/resources/default_200_percent/common/folder_closed.png') 2x);
+ url(../../../../../ui/resources/default_100_percent/common/folder_closed.png) 1x,
+ url(../../../../../ui/resources/default_200_percent/common/folder_closed.png) 2x);
}
/* We need to ensure that even empty labels take up space */
@@ -185,8 +185,8 @@ list [editing] input {
html[dir=rtl] list > .folder > .label {
background-image: -webkit-image-set(
- url('../../../../../ui/resources/default_100_percent/common/folder_closed_rtl.png') 1x,
- url('../../../../../ui/resources/default_200_percent/common/folder_closed_rtl.png') 2x);
+ url(../../../../../ui/resources/default_100_percent/common/folder_closed_rtl.png) 1x,
+ url(../../../../../ui/resources/default_200_percent/common/folder_closed_rtl.png) 2x);
}
<if expr="is_macosx">
@@ -195,8 +195,8 @@ list > .folder > .label,
.tree-row[may-have-children] > .tree-label,
.tree-item[expanded] > .tree-row > .tree-label {
background-image: -webkit-image-set(
- url('../../../../app/theme/default_100_percent/mac/bookmark_bar_folder.png') 1x,
- url('../../../../app/theme/default_200_percent/mac/bookmark_bar_folder.png') 2x);
+ url(../../../../app/theme/default_100_percent/mac/bookmark_bar_folder.png) 1x,
+ url(../../../../app/theme/default_200_percent/mac/bookmark_bar_folder.png) 2x);
}
</if>
@@ -365,7 +365,7 @@ html[dir=rtl] #organize-button {
@media (pointer:coarse) {
list > *,
- menu > button,
+ cr-menu > button,
.tree-item > .tree-row {
line-height: 28px;
}
diff --git a/chromium/chrome/browser/resources/bookmark_manager/js/bmm.js b/chromium/chrome/browser/resources/bookmark_manager/js/bmm.js
index f010d82d214..06fd50d97ea 100644
--- a/chromium/chrome/browser/resources/bookmark_manager/js/bmm.js
+++ b/chromium/chrome/browser/resources/bookmark_manager/js/bmm.js
@@ -3,6 +3,8 @@
// found in the LICENSE file.
cr.define('bmm', function() {
+ 'use strict';
+
/**
* Whether a node contains another node.
* TODO(yosin): Once JavaScript style guide is updated and linter follows
@@ -36,11 +38,17 @@ cr.define('bmm', function() {
* Promise version of chrome.bookmarkManagerPrivate.getSubtree.
* @param {string} id .
* @param {boolean} foldersOnly .
- * @return {!Promise.<!Array.<!BookmarkTreeNode>>} .
+ * @return {!Promise<!Array<!BookmarkTreeNode>>} .
*/
function getSubtreePromise(id, foldersOnly) {
- return new Promise(function(resolve) {
- chrome.bookmarkManagerPrivate.getSubtree(id, foldersOnly, resolve);
+ return new Promise(function(resolve, reject) {
+ chrome.bookmarkManagerPrivate.getSubtree(id, foldersOnly, function(node) {
+ if (chrome.runtime.lastError) {
+ reject(new Error(chrome.runtime.lastError.message));
+ return;
+ }
+ resolve(node);
+ });
});
}
@@ -48,13 +56,17 @@ cr.define('bmm', function() {
* Loads a subtree of the bookmark tree and returns a {@code Promise} that
* will be fulfilled when done. This reuses multiple loads so that we do not
* load the same subtree more than once at the same time.
- * @return {!Promise.<!BookmarkTreeNode>} The future promise for the load.
+ * @return {!Promise<!BookmarkTreeNode>} The future promise for the load.
*/
function loadSubtree(id) {
if (!loadingPromises[id]) {
loadingPromises[id] = getSubtreePromise(id, false).then(function(nodes) {
- delete loadingPromises[id];
return nodes && nodes[0];
+ }, function(error) {
+ console.error(error.message);
+ });
+ loadingPromises[id].then(function() {
+ delete loadingPromises[id];
});
}
return loadingPromises[id];
@@ -64,7 +76,7 @@ cr.define('bmm', function() {
* Loads the entire bookmark tree and returns a {@code Promise} that will
* be fulfilled when done. This reuses multiple loads so that we do not load
* the same tree more than once at the same time.
- * @return {!Promise.<!BookmarkTreeNode>} The future promise for the load.
+ * @return {!Promise<!BookmarkTreeNode>} The future promise for the load.
*/
function loadTree() {
return loadSubtree('');
diff --git a/chromium/chrome/browser/resources/bookmark_manager/js/bmm/bookmark_list.js b/chromium/chrome/browser/resources/bookmark_manager/js/bmm/bookmark_list.js
index f825da2c502..718762ef417 100644
--- a/chromium/chrome/browser/resources/bookmark_manager/js/bmm/bookmark_list.js
+++ b/chromium/chrome/browser/resources/bookmark_manager/js/bmm/bookmark_list.js
@@ -6,7 +6,7 @@
// that handles the loading and the events from the bookmark backend.
/**
- * @typedef {{childIds: Array.<string>}}
+ * @typedef {{childIds: Array<string>}}
*
* @see chrome/common/extensions/api/bookmarks.json
*/
@@ -23,6 +23,8 @@ var ReorderInfo;
var MoveInfo;
cr.define('bmm', function() {
+ 'use strict';
+
var List = cr.ui.List;
var ListItem = cr.ui.ListItem;
var ArrayDataModel = cr.ui.ArrayDataModel;
@@ -30,7 +32,7 @@ cr.define('bmm', function() {
/**
* Basic array data model for use with bookmarks.
- * @param {!Array.<!BookmarkTreeNode>} items The bookmark items.
+ * @param {!Array<!BookmarkTreeNode>} items The bookmark items.
* @constructor
* @extends {ArrayDataModel}
*/
@@ -128,7 +130,7 @@ cr.define('bmm', function() {
/**
* Callback function for loading items.
- * @param {Array.<!BookmarkTreeNode>} items The loaded items.
+ * @param {Array<!BookmarkTreeNode>} items The loaded items.
* @private
*/
handleBookmarkCallback_: function(items) {
diff --git a/chromium/chrome/browser/resources/bookmark_manager/js/bmm/bookmark_tree.js b/chromium/chrome/browser/resources/bookmark_manager/js/bmm/bookmark_tree.js
index e622db7f57f..fbd74c5b923 100644
--- a/chromium/chrome/browser/resources/bookmark_manager/js/bmm/bookmark_tree.js
+++ b/chromium/chrome/browser/resources/bookmark_manager/js/bmm/bookmark_tree.js
@@ -4,6 +4,8 @@
cr.define('bmm', function() {
+ 'use strict';
+
/**
* The id of the bookmark root.
* @type {string}
@@ -139,7 +141,7 @@ cr.define('bmm', function() {
chrome.bookmarks.getChildren(parent.bookmarkNode.id, function(children) {
var isFolder = /**
* @type {function (BookmarkTreeNode, number,
- * Array.<(BookmarkTreeNode)>)}
+ * Array<(BookmarkTreeNode)>)}
*/(bmm.isFolder);
var index = children.filter(isFolder).map(function(item) {
return item.id;
@@ -264,7 +266,7 @@ cr.define('bmm', function() {
* parentTreeItem.
* @param {!cr.ui.Tree|!cr.ui.TreeItem} parentTreeItem The parent tree
* element to append to.
- * @param {!Array.<BookmarkTreeNode>} bookmarkNodes A list of bookmark
+ * @param {!Array<BookmarkTreeNode>} bookmarkNodes A list of bookmark
* nodes to be added.
* @return {boolean} Whether any directories where added.
*/
diff --git a/chromium/chrome/browser/resources/bookmark_manager/js/bmm_test.html b/chromium/chrome/browser/resources/bookmark_manager/js/bmm_test.html
index feba65aa159..00d410867d9 100644
--- a/chromium/chrome/browser/resources/bookmark_manager/js/bmm_test.html
+++ b/chromium/chrome/browser/resources/bookmark_manager/js/bmm_test.html
@@ -1,8 +1,9 @@
-<!DOCTYPE html>
+<!doctype html>
<html>
<head>
<!-- TODO(arv): Check in Closure unit tests and make this run as part of the
tests -->
+<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<script src="https://cdn.rawgit.com/google/closure-library/master/closure/goog/base.js"></script>
<script src="../../../../../ui/webui/resources/js/cr.js"></script>
<script src="bmm.js"></script>
diff --git a/chromium/chrome/browser/resources/bookmark_manager/js/main.js b/chromium/chrome/browser/resources/bookmark_manager/js/main.js
index 123df708511..1c97b64eb7b 100644
--- a/chromium/chrome/browser/resources/bookmark_manager/js/main.js
+++ b/chromium/chrome/browser/resources/bookmark_manager/js/main.js
@@ -3,6 +3,8 @@
// found in the LICENSE file.
(function() {
+'use strict';
+
/** @const */ var BookmarkList = bmm.BookmarkList;
/** @const */ var BookmarkTree = bmm.BookmarkTree;
/** @const */ var Command = cr.ui.Command;
@@ -16,7 +18,7 @@
/**
* An array containing the BookmarkTreeNodes that were deleted in the last
* deletion action. This is used for implementing undo.
- * @type {?{nodes: Array.<Array.<BookmarkTreeNode>>, target: EventTarget}}
+ * @type {?{nodes: Array<Array<BookmarkTreeNode>>, target: EventTarget}}
*/
var lastDeleted;
@@ -308,15 +310,19 @@ function handleLoadForTree(e) {
/**
* Returns a promise for all the URLs in the {@code nodes} and the direct
* children of {@code nodes}.
- * @param {!Array.<BookmarkTreeNode>} nodes .
- * @return {!Promise.<Array.<string>>} .
+ * @param {!Array<BookmarkTreeNode>} nodes .
+ * @return {!Promise<Array<string>>} .
*/
function getAllUrls(nodes) {
var urls = [];
// Adds the node and all its direct children.
+ // TODO(deepak.m1): Here node should exist. When we delete the nodes then
+ // datamodel gets updated but still it shows deleted items as selected items
+ // and accessing those nodes throws chrome.runtime.lastError. This cause
+ // undefined value for node. Please refer https://crbug.com/480935.
function addNodes(node) {
- if (node.id == 'new')
+ if (!node || node.id == 'new')
return;
if (node.children) {
@@ -346,7 +352,7 @@ function getAllUrls(nodes) {
/**
* Returns the nodes (non recursive) to use for the open commands.
* @param {HTMLElement} target
- * @return {!Array.<BookmarkTreeNode>}
+ * @return {!Array<BookmarkTreeNode>}
*/
function getNodesForOpen(target) {
if (target == bmm.tree) {
@@ -371,7 +377,7 @@ function getNodesForOpen(target) {
* Returns a promise that will contain all URLs of all the selected bookmarks
* and the nested bookmarks for use with the open commands.
* @param {HTMLElement} target The target list or tree.
- * @return {Promise.<Array.<string>>} .
+ * @return {Promise<Array<string>>} .
*/
function getUrlsForOpenCommands(target) {
return getAllUrls(getNodesForOpen(target));
@@ -429,7 +435,8 @@ function updatePasteCommand(opt_f) {
var promises = [];
// The folders menu.
- if (bmm.tree.selectedItem) {
+ // We can not paste into search item in tree.
+ if (bmm.tree.selectedItem && bmm.tree.selectedItem != searchTreeItem) {
promises.push(new Promise(function(resolve) {
var id = bmm.tree.selectedItem.bookmarkId;
chrome.bookmarkManagerPrivate.canPaste(id, function(canPaste) {
@@ -470,6 +477,18 @@ function updatePasteCommand(opt_f) {
});
}
+function handleCanExecuteForSearchBox(e) {
+ var command = e.command;
+ switch (command.id) {
+ case 'delete-command':
+ case 'undo-command':
+ // Pass the delete and undo commands through
+ // (fixes http://crbug.com/278112).
+ e.canExecute = false;
+ break;
+ }
+}
+
function handleCanExecuteForDocument(e) {
var command = e.command;
switch (command.id) {
@@ -489,11 +508,9 @@ function handleCanExecuteForDocument(e) {
break;
case 'undo-command':
- // If the search box is active, pass the undo command through
- // (fixes http://crbug.com/278112). Otherwise, because
- // the global undo command has no visible UI, always enable it, and
- // just make it a no-op if undo is not possible.
- e.canExecute = e.currentTarget.activeElement !== $('term');
+ // Because the global undo command has no visible UI, always enable it,
+ // and just make it a no-op if undo is not possible.
+ e.canExecute = true;
break;
default:
@@ -625,7 +642,7 @@ function canExecuteForList(e) {
break;
case 'open-in-same-window-command':
- e.canExecute = hasSelected();
+ e.canExecute = (e.target == bmm.list) && hasSelected();
break;
default:
@@ -847,7 +864,7 @@ function getSelectedBookmarkNodes(opt_target) {
/**
* @param {EventTarget=} opt_target The target list or tree.
- * @return {!Array.<string>} An array of the selected bookmark IDs.
+ * @return {!Array<string>} An array of the selected bookmark IDs.
*/
function getSelectedBookmarkIds(opt_target) {
var selectedNodes = getSelectedBookmarkNodes(opt_target);
@@ -866,7 +883,7 @@ function isUnmodifiable(node) {
}
/**
- * @param {Array.<BookmarkTreeNode>} nodes A list of BookmarkTreeNodes.
+ * @param {Array<BookmarkTreeNode>} nodes A list of BookmarkTreeNodes.
* @return {boolean} Whether any of the nodes is managed.
*/
function hasUnmodifiable(nodes) {
@@ -1008,10 +1025,12 @@ function computeParentFolderForNewItem() {
/**
* Callback for rename folder and edit command. This starts editing for
- * selected item.
+ * the passed in target, or the selected item.
+ * @param {EventTarget=} opt_target The target to start editing. If absent or
+ * null, the selected item will be edited instead.
*/
-function editSelectedItem() {
- if (document.activeElement == bmm.tree) {
+function editItem(opt_target) {
+ if ((opt_target || document.activeElement) == bmm.tree) {
bmm.tree.selectedItem.editing = true;
} else {
var li = bmm.list.getListItem(bmm.list.selectedItem);
@@ -1029,12 +1048,18 @@ function newFolder(opt_target) {
performGlobalUndo = null; // This can't be undone, so disable global undo.
var parentId = computeParentFolderForNewItem();
-
+ var selectedItems = bmm.list.selectedItems;
+ var newIndex;
// Callback is called after tree and list data model updated.
function createFolder(callback) {
+ if (selectedItems.length == 1 && document.activeElement != bmm.tree &&
+ !bmm.isFolder(selectedItems[0]) && selectedItems[0].id != 'new') {
+ newIndex = bmm.list.dataModel.indexOf(selectedItems[0]) + 1;
+ }
chrome.bookmarks.create({
title: loadTimeData.getString('new_folder_name'),
- parentId: parentId
+ parentId: parentId,
+ index: newIndex
}, callback);
}
@@ -1048,8 +1073,8 @@ function newFolder(opt_target) {
}
function editNewFolderInList() {
- createFolder(function() {
- var index = bmm.list.dataModel.length - 1;
+ createFolder(function(newNode) {
+ var index = newNode.index;
var sm = bmm.list.selectionModel;
sm.anchorIndex = sm.leadIndex = sm.selectedIndex = index;
scrollIntoViewAndMakeEditable(index);
@@ -1080,20 +1105,29 @@ function scrollIntoViewAndMakeEditable(index) {
*/
function addPage() {
var parentId = computeParentFolderForNewItem();
-
+ var selectedItems = bmm.list.selectedItems;
+ var newIndex;
function editNewBookmark() {
+ if (selectedItems.length == 1 && document.activeElement != bmm.tree &&
+ !bmm.isFolder(selectedItems[0])) {
+ newIndex = bmm.list.dataModel.indexOf(selectedItems[0]) + 1;
+ }
+
var fakeNode = {
title: '',
url: '',
parentId: parentId,
+ index: newIndex,
id: 'new'
};
var dataModel = bmm.list.dataModel;
- var length = dataModel.length;
- dataModel.splice(length, 0, fakeNode);
+ var index = dataModel.length;
+ if (newIndex != undefined)
+ index = newIndex;
+ dataModel.splice(index, 0, fakeNode);
var sm = bmm.list.selectionModel;
- sm.anchorIndex = sm.leadIndex = sm.selectedIndex = length;
- scrollIntoViewAndMakeEditable(length);
+ sm.anchorIndex = sm.leadIndex = sm.selectedIndex = index;
+ scrollIntoViewAndMakeEditable(index);
};
navigateTo(parentId, editNewBookmark);
@@ -1210,7 +1244,7 @@ function hasSelectedAncestor(parentNode) {
/**
* @param {EventTarget=} opt_target A target to get bookmark IDs from.
- * @return {Array.<string>} An array of bookmarks IDs.
+ * @return {Array<string>} An array of bookmarks IDs.
*/
function getFilteredSelectedBookmarkIds(opt_target) {
// Remove duplicates from filteredIds and return.
@@ -1232,7 +1266,7 @@ function getFilteredSelectedBookmarkIds(opt_target) {
*/
function handleCommand(e) {
var command = e.command;
- var target;
+ var target = assertInstanceof(e.target, HTMLElement);
switch (command.id) {
case 'import-menu-command':
recordUserAction('Import');
@@ -1261,20 +1295,17 @@ function handleCommand(e) {
case 'open-in-new-tab-command':
case 'open-in-background-tab-command':
recordUserAction('OpenInNewTab');
- openBookmarks(LinkKind.BACKGROUND_TAB,
- assertInstanceof(e.target, HTMLElement));
+ openBookmarks(LinkKind.BACKGROUND_TAB, target);
break;
case 'open-in-new-window-command':
recordUserAction('OpenInNewWindow');
- openBookmarks(LinkKind.WINDOW,
- assertInstanceof(e.target, HTMLElement));
+ openBookmarks(LinkKind.WINDOW, target);
break;
case 'open-incognito-window-command':
recordUserAction('OpenIncognito');
- openBookmarks(LinkKind.INCOGNITO,
- assertInstanceof(e.target, HTMLElement));
+ openBookmarks(LinkKind.INCOGNITO, target);
break;
case 'delete-from-folders-menu-command':
@@ -1320,17 +1351,16 @@ function handleCommand(e) {
chrome.bookmarkManagerPrivate.sortChildren(bmm.list.parentId);
break;
- case 'rename-folder-command':
- editSelectedItem();
- break;
case 'rename-folder-from-folders-menu-command':
- bmm.tree.selectedItem.editing = true;
+ target = bmm.tree;
+ case 'rename-folder-command':
+ editItem(target);
break;
case 'edit-command':
recordUserAction('Edit');
- editSelectedItem();
+ editItem();
break;
case 'new-folder-from-folders-menu-command':
@@ -1412,7 +1442,7 @@ function continueInitializeBookmarkManager(localizedStrings) {
bmm.treeLookup[searchTreeItem.bookmarkId] = searchTreeItem;
- cr.ui.decorate('menu', Menu);
+ cr.ui.decorate('cr-menu', Menu);
cr.ui.decorate('button[menu]', MenuButton);
cr.ui.decorate('command', Command);
BookmarkList.decorate($('list'));
@@ -1449,6 +1479,7 @@ function continueInitializeBookmarkManager(localizedStrings) {
});
$('term').addEventListener('search', handleSearch);
+ $('term').addEventListener('canExecute', handleCanExecuteForSearchBox);
$('folders-button').addEventListener('click', handleMenuButtonClicked);
$('organize-button').addEventListener('click', handleMenuButtonClicked);
diff --git a/chromium/chrome/browser/resources/bookmark_manager/main.html b/chromium/chrome/browser/resources/bookmark_manager/main.html
index c98ff7ae957..14ca3a2781e 100644
--- a/chromium/chrome/browser/resources/bookmark_manager/main.html
+++ b/chromium/chrome/browser/resources/bookmark_manager/main.html
@@ -1,5 +1,5 @@
-<!DOCTYPE html>
-<html i18n-values="dir:textdirection">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
<!--
Copyright (c) 2012 The Chromium Authors. All rights reserved.
@@ -12,6 +12,8 @@ found in the LICENSE file.
<meta charset="utf-8">
<title i18n-content="title"></title>
+<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
+<link rel="stylesheet" href="chrome://resources/css/i18n_process.css">
<link rel="stylesheet" href="chrome://resources/css/list.css">
<link rel="stylesheet" href="chrome://resources/css/tree.css">
<link rel="stylesheet" href="chrome://resources/css/menu.css">
@@ -53,7 +55,7 @@ found in the LICENSE file.
<script src="js/bmm/bookmark_tree.js"></script>
<script src="js/dnd.js"></script>
</head>
-<body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<body>
<header>
<h1 i18n-content="title"></h1>
@@ -67,7 +69,7 @@ found in the LICENSE file.
<div id="tree-pane" class="pane">
<div>
<button menu="#folders-menu" i18n-content="folders_menu"
- id="folders-button"></button>
+ id="folders-button" class="custom-appearance"></button>
</div>
<div id="tree-container">
<tree id="tree" role="tree"></tree>
@@ -76,13 +78,14 @@ found in the LICENSE file.
<div class="splitter">
<div>
<!-- Used to occupy the correct amount of vertical space. -->
- <button menu="#no-menu"></button>
+ <button menu="#no-menu" i18n-content="folders_menu"
+ class="custom-appearance"></button>
</div>
</div>
<div id="list-pane" class="pane">
<div>
<button menu="#organize-menu" i18n-content="organize_menu"
- id="organize-button"></button>
+ id="organize-button" class="custom-appearance"></button>
</div>
<!-- The list doesn't use a scroll container as it truncates horizontally
and handles its own vertical overflow. -->
@@ -144,7 +147,7 @@ found in the LICENSE file.
<!-- TODO(arv): I think the commands might be better created in code? -->
-<menu id="folders-menu">
+<cr-menu id="folders-menu">
<button command="#new-folder-from-folders-menu-command"></button>
<hr>
<button command="#rename-folder-from-folders-menu-command"></button>
@@ -155,11 +158,11 @@ found in the LICENSE file.
<hr>
<button command="#delete-from-folders-menu-command"></button>
<button command="#undo-delete-from-folders-menu-command"></button>
-</menu>
+</cr-menu>
-<menu id="no-menu"></menu>
+<cr-menu id="no-menu"></cr-menu>
-<menu id="organize-menu">
+<cr-menu id="organize-menu">
<button command="#add-new-bookmark-command"></button>
<button command="#new-folder-command"></button>
<hr>
@@ -178,9 +181,9 @@ found in the LICENSE file.
<hr>
<button command="#import-menu-command"></button>
<button command="#export-menu-command"></button>
-</menu>
+</cr-menu>
-<menu id="context-menu">
+<cr-menu id="context-menu">
<button command="#open-in-new-tab-command"></button>
<button command="#open-in-new-window-command"></button>
<button command="#open-incognito-window-command"></button>
@@ -198,7 +201,7 @@ found in the LICENSE file.
<hr>
<button command="#add-new-bookmark-command"></button>
<button command="#new-folder-command"></button>
-</menu>
+</cr-menu>
<script src="js/main.js"></script>
</body>
diff --git a/chromium/chrome/browser/resources/certificate_viewer.html b/chromium/chrome/browser/resources/certificate_viewer.html
index 29c6068c453..23a3477454c 100644
--- a/chromium/chrome/browser/resources/certificate_viewer.html
+++ b/chromium/chrome/browser/resources/certificate_viewer.html
@@ -1,5 +1,5 @@
-<!DOCTYPE HTML>
-<html i18n-values="dir:textdirection">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
<head>
<meta charset="utf-8">
<title id="title"></title>
@@ -18,7 +18,7 @@
<script src="chrome://resources/js/util.js"></script>
<script src="certificate_viewer.js"></script>
</head>
- <body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+ <body>
<tabbox id="tabbox">
<tabs id="tabs" class="new-style-tabs">
<tab i18n-content="general"></tab>
@@ -121,5 +121,5 @@
</tabpanels>
</tabbox>
</body>
- <script src="chrome://resources/js/i18n_template2.js"></script>
+ <script src="chrome://resources/js/i18n_template.js"></script>
</html>
diff --git a/chromium/chrome/browser/resources/chromeos/OWNERS b/chromium/chrome/browser/resources/chromeos/OWNERS
index 418c659dd6f..03bb6b3e8e5 100644
--- a/chromium/chrome/browser/resources/chromeos/OWNERS
+++ b/chromium/chrome/browser/resources/chromeos/OWNERS
@@ -2,8 +2,8 @@ xiyuan@chromium.org
nkostylev@chromium.org
achuith@chromium.org
zelidrag@chromium.org
-rbyers@chromium.org
satorux@chromium.org
+stevenjb@chromium.org
per-file drive_internals*=hashimoto@chromium.org
per-file drive_internals*=kinaba@chromium.org
diff --git a/chromium/chrome/browser/resources/chromeos/about_os_credits.html b/chromium/chrome/browser/resources/chromeos/about_os_credits.html
index 0749e29a9e1..efac743b8a2 100644
--- a/chromium/chrome/browser/resources/chromeos/about_os_credits.html
+++ b/chromium/chrome/browser/resources/chromeos/about_os_credits.html
@@ -1,11 +1,11 @@
-<!DOCTYPE HTML>
+<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Credits</title>
+<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<style>
body {
- font-family:Helvetica,Arial,sans-serif;
background-color:white;
font-size:84%;
max-width:1020px;
@@ -23196,7 +23196,7 @@ Description: ===============================
Questions, comments, and bug reports should be directed to the `distutils-sig
mailing list`_. If you have written (or know of) any tutorials, documentation,
- plug-ins, or other resources for setuptools users, please let us know about
+ plugins, or other resources for setuptools users, please let us know about
them there, so this reference list can be updated. If you have working,
*tested* patches to correct problems or add features, you may submit them to
the `setuptools bug tracker`_.
@@ -23232,10 +23232,10 @@ Description: ===============================
* Phillip J. Eby is the principal author and maintainer of setuptools, and
first proposed the idea of an importable binary distribution format for
- Python application plug-ins.
+ Python application plugins.
* Significant parts of the implementation of setuptools were funded by the Open
- Source Applications Foundation, to provide a plug-in infrastructure for the
+ Source Applications Foundation, to provide a plugin infrastructure for the
Chandler PIM application. In addition, many OSAF staffers (such as Mike
"Code Bear" Taylor) contributed their time and stress as guinea pigs for the
use of eggs and setuptools, even before eggs were "cool". (Thanks, guys!)
@@ -24234,7 +24234,7 @@ Description: ===============================
Questions, comments, and bug reports should be directed to the `distutils-sig
mailing list`_. If you have written (or know of) any tutorials, documentation,
- plug-ins, or other resources for setuptools users, please let us know about
+ plugins, or other resources for setuptools users, please let us know about
them there, so this reference list can be updated. If you have working,
*tested* patches to correct problems or add features, you may submit them to
the `setuptools bug tracker`_.
@@ -24270,10 +24270,10 @@ Description: ===============================
* Phillip J. Eby is the principal author and maintainer of setuptools, and
first proposed the idea of an importable binary distribution format for
- Python application plug-ins.
+ Python application plugins.
* Significant parts of the implementation of setuptools were funded by the Open
- Source Applications Foundation, to provide a plug-in infrastructure for the
+ Source Applications Foundation, to provide a plugin infrastructure for the
Chandler PIM application. In addition, many OSAF staffers (such as Mike
"Code Bear" Taylor) contributed their time and stress as guinea pigs for the
use of eggs and setuptools, even before eggs were "cool". (Thanks, guys!)
diff --git a/chromium/chrome/browser/resources/chromeos/bluetooth_pair_device.html b/chromium/chrome/browser/resources/chromeos/bluetooth_pair_device.html
index 2d06538991b..ab7c6ed1500 100644
--- a/chromium/chrome/browser/resources/chromeos/bluetooth_pair_device.html
+++ b/chromium/chrome/browser/resources/chromeos/bluetooth_pair_device.html
@@ -1,5 +1,5 @@
-<!DOCTYPE HTML>
-<html i18n-values="dir:textdirection">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="chrome://resources/css/chrome_shared.css">
@@ -31,7 +31,7 @@
<script src="bluetooth_pair_device.js"></script>
</head>
-<body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<body>
<div id="overlay-container" class="overlay">
<include src="../options/chromeos/bluetooth_pair_device_overlay.html">
</div>
@@ -39,6 +39,6 @@
<div id="bluetooth-container"></div>
</div>
<div id="searchBox" hidden><input id="search-field" type=text></div>
-<script src="chrome://resources/js/i18n_template2.js"></script>
+<script src="chrome://resources/js/i18n_template.js"></script>
</body>
</html>
diff --git a/chromium/chrome/browser/resources/chromeos/braille_ime/braille_ime.js b/chromium/chrome/browser/resources/chromeos/braille_ime/braille_ime.js
index 70746918a4e..deed955617b 100644
--- a/chromium/chrome/browser/resources/chromeos/braille_ime/braille_ime.js
+++ b/chromium/chrome/browser/resources/chromeos/braille_ime/braille_ime.js
@@ -18,7 +18,7 @@
* 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.
+ * 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
@@ -108,7 +108,7 @@ BrailleIme.prototype = {
* Note that the mapping below is arranged like the dots in a braille cell.
* Only 6 dot input is supported.
* @private
- * @const {Object.<string, number>}
+ * @const {Object<string, number>}
*/
CODE_TO_DOT_: {'KeyF': 0x01, 'KeyJ': 0x08,
'KeyD': 0x02, 'KeyK': 0x10,
@@ -184,7 +184,7 @@ BrailleIme.prototype = {
* @private
*/
onFocus_: function(context) {
- this.log_('onFocus', JSON.stringify(context));
+ this.log_('onFocus', context);
this.sendInputContext_(context);
},
@@ -204,7 +204,7 @@ BrailleIme.prototype = {
* @private
*/
onInputContextUpdate_: function(context) {
- this.log_('onInputContextUpdate', JSON.stringify(context));
+ this.log_('onInputContextUpdate', context);
this.sendInputContext_(context);
},
@@ -215,7 +215,6 @@ BrailleIme.prototype = {
* @private
*/
onKeyEvent_: function(engineID, event) {
- this.log_('onKeyEvent', engineID + ', ' + JSON.stringify(event));
var result = this.processKey_(event);
if (result !== undefined) {
chrome.input.ime.keyEventHandled(event.requestId, result);
@@ -257,14 +256,14 @@ BrailleIme.prototype = {
* 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.
+ * @param {Object|string=} message Message to output.
* @private
*/
log_: function(func, message) {
- if (func === 'onKeyEvent') {
- return;
- }
if (this.DEBUG) {
+ if (typeof(message) !== 'string') {
+ message = JSON.stringify(message);
+ }
console.log('BrailleIme.' + func + ': ' + message);
}
},
@@ -341,8 +340,8 @@ BrailleIme.prototype = {
* @private
*/
onChromeVoxMessage_: function(message) {
- this.log_('onChromeVoxMessage', JSON.stringify(message));
message = /** @type {{type: string}} */ (message);
+ this.log_('onChromeVoxMessage', message);
switch (message.type) {
case 'replaceText':
message =
@@ -372,8 +371,7 @@ BrailleIme.prototype = {
*/
onChromeVoxDisconnect_: function() {
this.port_ = null;
- this.log_('onChromeVoxDisconnect',
- JSON.stringify(chrome.runtime.lastError));
+ this.log_('onChromeVoxDisconnect', chrome.runtime.lastError);
},
/**
@@ -415,16 +413,12 @@ BrailleIme.prototype = {
* @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);
+ var addText = chrome.input.ime.commitText.bind(
+ null, {contextID: contextID, text: toInsert}, function() {});
if (deleteBefore > 0) {
- var deleteText = function() {
- chrome.input.ime.deleteSurroundingText(
- {engineID: this.engineID_, contextID: contextID,
- offset: -deleteBefore, length: deleteBefore}, addText);
- }.bind(this);
+ var deleteText = chrome.input.ime.deleteSurroundingText.bind(null,
+ {engineID: this.engineID_, contextID: contextID,
+ offset: -deleteBefore, length: deleteBefore}, addText);
// Make sure there's no non-zero length selection so that
// deleteSurroundingText works correctly.
chrome.input.ime.deleteSurroundingText(
diff --git a/chromium/chrome/browser/resources/chromeos/braille_ime/externs.js b/chromium/chrome/browser/resources/chromeos/braille_ime/externs.js
index 38f08853b6f..da4b07e871d 100644
--- a/chromium/chrome/browser/resources/chromeos/braille_ime/externs.js
+++ b/chromium/chrome/browser/resources/chromeos/braille_ime/externs.js
@@ -8,6 +8,6 @@
*/
/**
- * @type {Object}
+ * @type {Storage}
*/
var localStorage;
diff --git a/chromium/chrome/browser/resources/chromeos/certificate_manager_dialog.html b/chromium/chrome/browser/resources/chromeos/certificate_manager_dialog.html
index 89eb1ad6654..854d4e19694 100644
--- a/chromium/chrome/browser/resources/chromeos/certificate_manager_dialog.html
+++ b/chromium/chrome/browser/resources/chromeos/certificate_manager_dialog.html
@@ -1,5 +1,5 @@
-<!DOCTYPE HTML>
-<html i18n-values="dir:textdirection;highlight:highlightStrength">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language;highlight:highlightStrength">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="chrome://resources/css/chrome_shared.css">
@@ -47,7 +47,7 @@
<script src="chrome://certificate-manager/strings.js"></script>
</head>
-<body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<body>
<include src="../options/certificate_manager.html">
<div id="overlay-container-2" class="overlay transparent" hidden>
<include src="../options/alert_overlay.html">
@@ -57,6 +57,6 @@
<include src="../options/certificate_import_error_overlay.html">
</div>
<div id="page-container" hidden></div>
-<script src="chrome://resources/js/i18n_template2.js"></script>
+<script src="chrome://resources/js/i18n_template.js"></script>
</body>
</html>
diff --git a/chromium/chrome/browser/resources/chromeos/certificate_manager_dialog.js b/chromium/chrome/browser/resources/chromeos/certificate_manager_dialog.js
index 435dfb2309b..ed35f86f49f 100644
--- a/chromium/chrome/browser/resources/chromeos/certificate_manager_dialog.js
+++ b/chromium/chrome/browser/resources/chromeos/certificate_manager_dialog.js
@@ -40,7 +40,8 @@ function load() {
$('cert-manager-header').hidden = true;
PageManager.isDialog = true;
- CertificateManager.getInstance().initializePage(true);
+ CertificateManager.getInstance().setIsKiosk(true);
+ CertificateManager.getInstance().initializePage();
PageManager.registerOverlay(AlertOverlay.getInstance(),
CertificateManager.getInstance());
PageManager.registerOverlay(CertificateBackupOverlay.getInstance(),
diff --git a/chromium/chrome/browser/resources/chromeos/charger_replacement.css b/chromium/chrome/browser/resources/chromeos/charger_replacement.css
deleted file mode 100644
index ae3637d7c5f..00000000000
--- a/chromium/chrome/browser/resources/chromeos/charger_replacement.css
+++ /dev/null
@@ -1,153 +0,0 @@
-/* Copyright (c) 2013 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. */
-
-body {
- padding: 8px;
-}
-
-h2 {
- margin-bottom: 20px;
-}
-
-.page {
- display: none;
- margin-left: 20px;
- margin-right: 20px;
- padding: 0;
-}
-
-body[page='0'] #check-charger,
-body[page='1'] #confirm-safe-charger {
- display: block;
-}
-body[page='2'] #charger-update {
- display: block;
-}
-body[page='3'] #order-charger-online {
- display: block;
-}
-body[page='4'] #confirm-online-order {
- display: block;
-}
-body[page='5'] #finish-not-order-charger {
- display: block;
-}
-body[page='6'] #order-charger-offline {
- display: block;
-}
-body[page='7'] #charger-still-bad {
- display: block;
-}
-
-.button-strip {
- display: -webkit-flex;
- justify-content: flex-end;
- margin-top: 20px;
-}
-
-.command-button {
- width: 80px;
-}
-
-.stop-use-recalled-charger {
- font-weight: bold;
-}
-
-.checkbox-label {
- margin-left: 5px;
-}
-
-.selected-charger {
- border: 2px solid rgb(66, 129, 244);
-}
-
-.de-selected-charger {
- border: 2px solid #999;
-}
-
-#select-device-country {
- width: 270px;
-}
-
-#charger-selection {
- display: -webkit-flex;
- justify-content: space-between;
- margin-bottom: 25px;
- margin-top: 15px;
-}
-
-#charger-selection img {
- display: block;
-}
-
-#safe-charger-checkmark {
- display: block;
- margin-left: auto;
- margin-right: auto;
-}
-
-#go-with-good-charger {
- margin-bottom: 30px;
- text-align: center;
-}
-
-#safe-charger-checkmark {
- margin-bottom: 40px;
- margin-top: 40px;
-}
-
-#back-to-check-charger {
- margin-right: 10px;
-}
-
-#charger-order-form {
- border-style: none;
- height: 500px;
- width: 1100px;
-}
-
-#back-to-check-charger-from-charger-update {
- margin-right: 10px;
-}
-
-#not-order-charger-checkbox-strip {
- display: -webkit-flex;
- margin-left: 20px;
- margin-top: 5px;
-}
-
-#offline-content {
- display: -webkit-flex;
- justify-content: space-between;
-}
-
-#offline-order-contact {
- font-weight: bold;
-}
-
-#offline-content-left {
- width: 450px;
-}
-
-#offline-content-divider {
- border: 1px solid #f5f5f5;
- margin-left: 30px;
- margin-right: 30px;
-}
-
-#offline-content-right {
- display: -webkit-flex;
- flex-direction: column;
- height: 465px;
- justify-content: space-between;
-}
-
-#confirm-offline-order-checkbox-strip {
- display: -webkit-flex;
- flex-direction: row;
-}
-
-#finish-still-bad-charger {
- margin-top: 40px;
-}
diff --git a/chromium/chrome/browser/resources/chromeos/charger_replacement.html b/chromium/chrome/browser/resources/chromeos/charger_replacement.html
deleted file mode 100644
index b6c394e597a..00000000000
--- a/chromium/chrome/browser/resources/chromeos/charger_replacement.html
+++ /dev/null
@@ -1,222 +0,0 @@
-<!DOCTYPE HTML>
-<html i18n-values="dir:textdirection">
-<head>
-<meta charset="utf-8">
-<link rel="stylesheet" href="chrome://resources/css/chrome_shared.css">
-<link rel="stylesheet" href="charger_replacement.css">
-
-<script src="chrome://resources/js/cr.js"></script>
-<script src="chrome://resources/js/event_tracker.js"></script>
-<script src="chrome://resources/js/cr/event_target.js"></script>
-<script src="chrome://resources/js/cr/ui.js"></script>
-<script src="chrome://resources/js/cr/ui/touch_handler.js"></script>
-<script src="chrome://resources/js/load_time_data.js"></script>
-<script src="chrome://resources/js/util.js"></script>
-<script src="chrome://charger-replacement/strings.js"></script>
-
-<script src="../uber/uber_utils.js"></script>
-<script src="charger_replacement.js"></script>
-
-</head>
-
-<body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
-
-<div id="check-charger" class="page">
- <h2 i18n-content="checkChargerTitle"></h2>
- <p>
- <span i18n-content="checkChargerDamage"></span><br><br>
- <span i18n-content="checkOriginalCharger"></span><br>
- </p>
- <p i18n-content="whereDevicePurchased"></p>
- <div>
- <select id="select-device-country">
- <option selected="true" style="display:none;"
- i18n-content="selectCountry"></option>
- <option value="au" i18n-content="au"></option>
- <option value="ca" i18n-content="ca"></option>
- <option value="ire" i18n-content="ire"></option>
- <option value="uk" i18n-content="uk"></option>
- <option value="us" i18n-content="us"></option>
- </select>
- </div>
- <div id="charger-selection-strip">
- <p i18n-content="checkChargerSelectCharger"></p>
- <div id="charger-selection">
- <div id="new-charger-box" class="de-selected-charger">
- <img id="new-charger" src="images/new_charger_us.png">
- <img id="new-charger-us" src="images/new_charger_us.png" hidden>
- <img id="new-charger-uk" src="images/new_charger_uk.png" hidden>
- <img id="new-charger-au" src="images/new_charger_au.png" hidden>
- </div>
- <div id="original-charger-box" class="de-selected-charger">
- <img id="original-charger" src="images/original_charger_us.png">
- <img id="original-charger-us" src="images/original_charger_us.png" hidden>
- <img id="original-charger-uk" src="images/original_charger_uk.png" hidden>
- <img id="original-charger-au" src="images/original_charger_au.png" hidden>
- </div>
- </div>
- </div>
- <div class="button-strip">
- <button type="button" id="check-charger-next-step" class="command-button"
- i18n-content="nextStepButtonText"></button>
- </div>
-</div>
-
-<div id="confirm-safe-charger" class="page">
- <h2 i18n-content="confirmSafeChargerTitle"></h2>
- <img id="safe-charger-checkmark" src="images/safe-charger-checkmark.png">
- <p id="go-with-good-charger" i18n-content="goWithSafeCharger"></p>
- <div class="button-strip">
- <button type="button" id="back-to-check-charger"
- i18n-content="prevStepText" class="command-button"></button>
- <button type="button" id="finish-safe-charger"
- i18n-content="finishText" class="command-button"></button>
- </div>
-</div>
-
-<div id="charger-update" class="page">
- <h2 i18n-content="chargerUpdateTitle"></h2>
- <p>
- <span i18n-content="chargerUpdateP1"></span>
- <span class="stop-use-recalled-charger"
- i18n-content="stopUsingRecalledCharger"></span>
- </p>
- <p i18n-content="chargerUpdateP2"></p>
- <p i18n-values=".innerHTML:chargerUpdateFAQ"></p>
- <div>
- <div class="radio">
- <input id="order-new-charger" name="order-new-charger" type="radio"
- value="0">
- <label for="order-new-charger" i18n-content="orderNewCharger"></label>
- </div>
- <div class="radio">
- <div>
- <input id="not-order-new-charger" name="order-new-charger" type="radio"
- value="1">
- <label for="not-order-new-charger" i18n-content="notOrderNewCharger">
- </label>
- </div>
- </div>
- </div>
- <div id="not-order-charger-checkbox-strip">
- <div>
- <input id="confirm-not-order-charger" type="checkbox">
- </div>
- <div id="confirm-not-order-charger-label-strip" class="checkbox-label">
- <div>
- <label for="confirm-not-order-charger"
- i18n-content="confirmNotOrderNewCharger">
- </label>
- </div>
- <div>
- <label for="confirm-not-order-charger"
- i18n-content="noMoreShowText">
- </label>
- </div>
- </div>
- </div>
- <div class="button-strip">
- <button type="button" id="back-to-check-charger-from-charger-update"
- i18n-content="prevStepText" class="command-button"></button>
- <button type="button" id="next-to-charger-update"
- i18n-content="nextStepButtonText" class="command-button"></button>
- </div>
-
-</div>
-
-<div id="order-charger-online" class="page">
- <iframe id="charger-order-form" name="charger-order-form">
- </iframe>
-</div>
-
-<div id="confirm-online-order" class="page">
- <h2 i18n-content="confirmOnlineOrder"></h2>
- <img id="safe-charger-checkmark" src="images/safe-charger-checkmark.png">
- <p>
- <span i18n-content="confirmReceivingOnlineOrder"></span><br>
- </p>
- <span i18n-content="needMoreInformation"></span><br>
- <a class="link" href="#" i18n-content="faqLink"></a>
- <div class="button-strip">
- <button type="button" id="finish-online-order"
- i18n-content="finishText" class="command-button"></button>
- </div>
-</div>
-
-<div id="finish-not-order-charger" class="page">
- <h2 i18n-content="finishNotOrderChargerTitle"></h2>
- <p>
- <span class="stop-use-recalled-charger"
- i18n-content="stopUsingRecalledCharger"></span>
- </p>
- <p i18n-values=".innerHTML:finishNotOrderChargerP2"></p>
- <p>
- <span i18n-content="finishNotOrderChargerMoreInfo"></span>
- <a class="link" href="#" i18n-content="faqLink"></a>
- </p><br>
- <div class="button-strip">
- <button type="button" id="finish-not-order-new-charger"
- i18n-content="finishText" class="command-button"></button>
- </div>
-</div>
-
-<div id="order-charger-offline" class="page">
- <h2 i18n-content="orderChargerOfflineTitle"></h2>
- <div id="offline-content">
- <div id="offline-content-left">
- <p>
- <span i18n-content="offlineChargerOrderParagraph1"></span>
- <span class="stop-use-recalled-charger"
- i18n-content="stopUsingRecalledCharger"></span><br>
- </p>
- <p i18n-content="offlineChargerOrderParagraph2"></p>
- <p i18n-values=".innerHTML:chargerUpdateFAQ"></p>
- <p i18n-values=".innerHTML:privacyPolicy"></p>
- </div>
- <div id="offline-content-divider"></div>
- <div id="offline-content-right">
- <div>
- <p id="offline-order-contact"
- i18n-content="offlineOrderPhoneNumber"></p>
- </div>
- <div>
- <div id="confirm-offline-order-checkbox-strip">
- <div>
- <input id="offline-order-confirm" type="checkbox">
- </div>
- <div class="checkbox-label">
- <label for="offline-order-confirm"
- i18n-content="offlineSafeChargerConfirmationText">
- </label>
- </div>
- </div>
- <div class="button-strip">
- <button type="button" id="finish-offline-order"
- i18n-content="finishText" class="command-button"></button>
- </div>
- </div>
- </div>
- </div>
-</div>
-
-<div id="charger-still-bad" class="page">
- <h2 i18n-content="chargerStillBadTitle"></h2>
- <p>
- <span i18n-content="chargedOrdered"></span><br>
- <span class="stop-use-recalled-charger"
- i18n-content="stopUsingRecalledCharger"></span><br>
- </p>
- <p i18n-values=".innerHTML:chargerUpdateFAQ"></p>
- <p>
- <span i18n-content="findMoreInfo"></span>
- <a class="link" href="#" i18n-content="faqLink"></a>
- </p>
- <div class="button-strip">
- <button type="button" id="finish-still-bad-charger"
- i18n-content="finishText" class="command-button"></button>
- </div>
-</div>
-
-<script src="chrome://resources/js/i18n_template2.js"></script>
-</body>
-</html>
diff --git a/chromium/chrome/browser/resources/chromeos/charger_replacement.js b/chromium/chrome/browser/resources/chromeos/charger_replacement.js
deleted file mode 100644
index 9115f22b90e..00000000000
--- a/chromium/chrome/browser/resources/chromeos/charger_replacement.js
+++ /dev/null
@@ -1,375 +0,0 @@
-// Copyright (c) 2013 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.
-
-cr.define('chargerReplacement', function() {
-
- /**
- * Enumeration of charger selection.
- * @enum {int}
- */
- var CHARGER_SELECTION = {
- NONE: 0,
- GOOD_CHARGER: 1,
- ORIGINAL_CHARGER: 2
- };
-
- /**
- * Enumeration of html pages.
- * @enum {int}
- */
- var PAGES = {
- CHECK_CHARGER: 0,
- CONFIRM_SAFE_CHARGER: 1,
- CHARGER_UPDATE: 2,
- ORDER_CHARGER_ONLINE: 3,
- CONFIRM_ONLINE_ORDER: 4,
- FINISH_NOT_ORDER_CHARGER: 5,
- ORDER_CHARGER_OFFLINE: 6,
- CHARGER_STILL_BAD: 7,
- };
-
- /**
- * Enumeration of the user's choice for order new charger or not.
- */
- var USER_CHOICE_FOR_CHARGER_UPDATE = {
- ORDER_NEW_CHARGER: 0,
- NOT_ORDER_NEW_CHARGER: 1,
- };
-
- /**
- * Enumeration of messages sent from iFrame order form.
- */
- var ORDER_CHARGER_IFRAME_MESSAGE = {
- FORM_OPEN: 'FORM_OPEN',
- SUBMIT: 'SUBMIT',
- SUCCESS: 'SUCCESS',
- FAILURE: 'FAILURE',
- LINK: 'LINK',
- };
-
- /**
- * Enumeration of countries where user might purchase the device.
- */
- var COUNTRY = {
- AU: 'au',
- CA: 'ca',
- IRE: 'ire',
- UK: 'uk',
- US: 'us',
- };
-
- /**
- * Dialog argument passed from chrome to indicate whether user has ordered
- * new charger.
- */
- var NEW_CHARGER_ORDERED = '1';
-
- /**
- * Charger order form iFrame url.
- */
- var ORDER_CHARGER_IFRAME_URL = 'https://chromesafetycheck.appspot.com';
-
- /**
- * maximum delay in milliseconds for loading the online charger order form
- * into iFrame.
- */
- var ONLINE_ORDER_FORM_LOADING_DELAY = 30000;
-
- /**
- * maximum delay in milliseconds for server to respond after user submits
- * the order form.
- */
- var ONLINE_ORDER_SUBMISSION_DELAY = 60000;
-
- /**
- * urls of href links on UI.
- */
- var CHARGER_FAQ_LINK = 'http://chromebook.com/hp11charger/';
- var PRIVACY_POLICY_LINK = 'http://www.google.com/policies/privacy';
-
- var onlineOrderSubmitTimer;
-
- /**
- * flag for whether the online charger order form is loaded.
- */
- var isOrderFormLoaded = false;
-
- /**
- * flag for whether user's online charger order form submission has been
- * recevied by Google. True if the server responds with SUCCESSS or
- * FAILURE. FAILURE indicate user has some input error in form and should
- * submit again.
- */
- var isOrderSubmissionReceived = false;
-
- /**
- * charger selection by user.
- */
- var chargerSelection = CHARGER_SELECTION.NONE;
-
- /**
- * Set window the specified size and center it to screen.
- */
- function setWindowSizeAndCenter(width, height) {
- window.resizeTo(width, height);
- window.moveTo(window.screen.width / 2 - width / 2,
- window.screen.height / 2 - height / 2);
- }
-
- /**
- * Show a particular page.
- */
- function showPage(page) {
- switch (page) {
- case PAGES.CHECK_CHARGER:
- setWindowSizeAndCenter(500, 590);
- break;
- case PAGES.CONFIRM_SAFE_CHARGER:
- setWindowSizeAndCenter(400, 325);
- break;
- case PAGES.CHARGER_UPDATE:
- setWindowSizeAndCenter(510, 505);
- break;
- case PAGES.ORDER_CHARGER_ONLINE:
- $('charger-order-form').src = ORDER_CHARGER_IFRAME_URL;
- setWindowSizeAndCenter(1150, 550);
- setTimeout(checkOnlineOrderFormLoaded, ONLINE_ORDER_FORM_LOADING_DELAY);
- break;
- case PAGES.CONFIRM_ONLINE_ORDER:
- setWindowSizeAndCenter(420, 380);
- break;
- case PAGES.FINISH_NOT_ORDER_CHARGER:
- setWindowSizeAndCenter(430, 350);
- break;
- case PAGES.ORDER_CHARGER_OFFLINE:
- setWindowSizeAndCenter(750, 600);
- break;
- case PAGES.CHARGER_STILL_BAD:
- setWindowSizeAndCenter(430, 380);
- break;
- }
- document.body.setAttribute('page', page);
- }
-
- /**
- * Select a country from the drop down list.
- */
- function selectCountry() {
- var country = $('select-device-country').value;
- if (country == COUNTRY.US || country == COUNTRY.CA) {
- $('new-charger').src = $('new-charger-us').src;
- $('original-charger').src = $('original-charger-us').src;
- } else if (country == COUNTRY.AU) {
- $('new-charger').src = $('new-charger-au').src;
- $('original-charger').src = $('original-charger-au').src;
- } else {
- $('new-charger').src = $('new-charger-uk').src;
- $('original-charger').src = $('original-charger-uk').src;
- }
- $('charger-selection-strip').style.visibility = 'visible';
- }
-
- /**
- * Toggle charger box border color based on if it is selected.
- */
- function ToggleChargerSelection(charger, selected) {
- charger.classList.toggle('selected-charger', selected);
- charger.classList.toggle('de-selected-charger', !selected);
- }
-
- /**
- * Select a charger, either original or good charger with green sticker.
- */
- function selectCharger(selection) {
- if (selection == CHARGER_SELECTION.NONE)
- return;
-
- chargerSelection = selection;
- $('check-charger-next-step').disabled = false;
- if (chargerSelection == CHARGER_SELECTION.GOOD_CHARGER) {
- var selectedCharger = $('new-charger-box');
- var notSelectedCharger = $('original-charger-box');
- } else {
- var selectedCharger = $('original-charger-box');
- var notSelectedCharger = $('new-charger-box');
- }
- ToggleChargerSelection(selectedCharger, true);
- ToggleChargerSelection(notSelectedCharger, false);
- }
-
- /**
- * Process the flow after user select a charger.
- */
- function afterSelectCharger(dialogArg) {
- if (chargerSelection == CHARGER_SELECTION.NONE)
- return;
-
- if (chargerSelection == CHARGER_SELECTION.GOOD_CHARGER) {
- showPage(PAGES.CONFIRM_SAFE_CHARGER);
- } else {
- if (dialogArg == NEW_CHARGER_ORDERED)
- showPage(PAGES.CHARGER_STILL_BAD);
- else
- showPage(PAGES.CHARGER_UPDATE);
- }
- }
-
- /**
- * Proceed to next step after user make the choice for charger update.
- */
- function nextStepForChargerUpdate() {
- var radios = document.getElementsByName('order-new-charger');
- if (radios[USER_CHOICE_FOR_CHARGER_UPDATE.ORDER_NEW_CHARGER].checked) {
- if (navigator.onLine)
- showPage(PAGES.ORDER_CHARGER_ONLINE);
- else
- showPage(PAGES.ORDER_CHARGER_OFFLINE);
- } else {
- showPage(PAGES.FINISH_NOT_ORDER_CHARGER);
- }
- }
-
- /**
- * Update the UI after user confirms the choice for charger update.
- */
- function afterUserConfirmationForChargerUpdate() {
- if ($('order-new-charger').checked) {
- $('not-order-charger-checkbox-strip').style.visibility = 'hidden';
- $('next-to-charger-update').disabled = false;
- } else {
- $('not-order-charger-checkbox-strip').style.visibility = 'visible';
- $('next-to-charger-update').disabled =
- !$('confirm-not-order-charger').checked;
- }
- }
-
- /**
- * Check if the online order form has been loaded in iFrame.
- */
- function checkOnlineOrderFormLoaded() {
- if (!isOrderFormLoaded)
- showPage(PAGES.ORDER_CHARGER_OFFLINE);
- }
-
- /**
- * Check if the online charger order has been successful or not.
- */
- function checkOnlineOrderSubmissionResponse() {
- if (!isOrderSubmissionReceived)
- showPage(PAGES.ORDER_CHARGER_OFFLINE);
- }
-
- /**
- * Handle the messages posted by the iFrame for online order form.
- */
- function handleWindowMessage(e) {
- if (e.origin != ORDER_CHARGER_IFRAME_URL)
- return;
-
- var type = e.data['type'];
- if (type == ORDER_CHARGER_IFRAME_MESSAGE.FORM_OPEN) {
- isOrderFormLoaded = true;
- } else if (type == ORDER_CHARGER_IFRAME_MESSAGE.SUBMIT) {
- if (onlineOrderSubmitTimer)
- clearTimeout(onlineOrderSubmitTimer);
- onlineOrderSubmitTimer = setTimeout(checkOnlineOrderSubmissionResponse,
- ONLINE_ORDER_SUBMISSION_DELAY);
- } else if (type == ORDER_CHARGER_IFRAME_MESSAGE.SUCCESS) {
- isOrderSubmissionReceived = true;
- showPage(PAGES.CONFIRM_ONLINE_ORDER);
- } else if (type == ORDER_CHARGER_IFRAME_MESSAGE.FAILURE) {
- isOrderSubmissionReceived = true;
- } else if (type == ORDER_CHARGER_IFRAME_MESSAGE.LINK) {
- chrome.send('showLink', [e.data['link']]);
- }
- }
-
- /**
- * Page loaded.
- */
- function load() {
- var dialogArg = chrome.getVariableValue('dialogArguments');
- showPage(PAGES.CHECK_CHARGER);
- $('check-charger-next-step').disabled = true;
- $('charger-selection-strip').style.visibility = 'hidden';
- $('order-new-charger').checked = true;
- $('finish-offline-order').disabled = true;
- $('check-charger-next-step').onclick = function() {
- afterSelectCharger(dialogArg);
- };
- $('select-device-country').onchange = function() {
- selectCountry();
- };
- $('new-charger').onclick = function() {
- selectCharger(CHARGER_SELECTION.GOOD_CHARGER);
- };
- $('original-charger').onclick = function() {
- selectCharger(CHARGER_SELECTION.ORIGINAL_CHARGER);
- };
- $('back-to-check-charger').onclick = function() {
- showPage(PAGES.CHECK_CHARGER);
- };
- $('finish-safe-charger').onclick = function() {
- chrome.send('confirmSafeCharger');
- chrome.send('dialogClose');
- };
- $('not-order-charger-checkbox-strip').style.visibility = 'hidden';
- $('back-to-check-charger-from-charger-update').onclick = function() {
- showPage(PAGES.CHECK_CHARGER);
- };
- $('next-to-charger-update').onclick = function() {
- nextStepForChargerUpdate();
- };
- $('order-new-charger').onclick = function() {
- afterUserConfirmationForChargerUpdate();
- };
- $('not-order-new-charger').onclick = function() {
- afterUserConfirmationForChargerUpdate();
- };
- $('confirm-not-order-charger').onclick = function() {
- afterUserConfirmationForChargerUpdate();
- };
- $('finish-not-order-new-charger').onclick = function() {
- chrome.send('confirmNotOrderNewCharger');
- chrome.send('dialogClose');
- };
- $('finish-online-order').onclick = function() {
- chrome.send('confirmChargerOrderedOnline');
- chrome.send('dialogClose');
- };
- $('offline-order-confirm').onclick = function() {
- $('finish-offline-order').disabled = !$('offline-order-confirm').checked;
- };
- $('finish-offline-order').onclick = function() {
- chrome.send('confirmChargerOrderByPhone');
- chrome.send('dialogClose');
- };
- $('finish-still-bad-charger').onclick = function() {
- chrome.send('confirmStillUseBadCharger');
- chrome.send('dialogClose');
- };
-
- var links = document.getElementsByClassName('link');
- for (var i = 0; i < links.length; ++i) {
- if (links[i].id == 'privacy-link') {
- links[i].onclick = function() {
- chrome.send('showLink', [PRIVACY_POLICY_LINK]);
- };
- } else {
- links[i].onclick = function() {
- chrome.send('showLink', [CHARGER_FAQ_LINK]);
- };
- }
- }
-
- window.addEventListener('message', handleWindowMessage);
- }
-
- return {
- load: load
- };
-});
-
-document.addEventListener('DOMContentLoaded', chargerReplacement.load);
-
diff --git a/chromium/chrome/browser/resources/chromeos/choose_mobile_network.html b/chromium/chrome/browser/resources/chromeos/choose_mobile_network.html
index 27de1351186..0ae0abf2343 100644
--- a/chromium/chrome/browser/resources/chromeos/choose_mobile_network.html
+++ b/chromium/chrome/browser/resources/chromeos/choose_mobile_network.html
@@ -1,8 +1,9 @@
-<!DOCTYPE HTML>
-<html i18n-values="dir:textdirection">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
<head>
<meta charset="utf-8">
<title i18n-content="title"></title>
+<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="chrome://resources/css/widgets.css">
<link rel="stylesheet" href="mobile_dialogs.css">
<link rel="stylesheet" href="choose_mobile_network.css">
@@ -13,7 +14,7 @@
<script src="chrome://choose-mobile-network/strings.js"></script>
<script src="chrome://choose-mobile-network/choose_mobile_network.js"></script>
</head>
-<body i18n-values=".style.fontFamily:fontfamily;">
+<body>
<div id="container" class="container">
<div id="choose-mobile-network" class="dialog">
<h1 i18n-content="chooseNetworkTitle"></h1>
@@ -37,6 +38,6 @@
</div>
</div>
</div>
- <script src="chrome://resources/js/i18n_template2.js"></script>
+ <script src="chrome://resources/js/i18n_template.js"></script>
</body>
</html>
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/braille_display_manager.js b/chromium/chrome/browser/resources/chromeos/chromevox/braille/braille_display_manager.js
index 7b1adec3869..b94ef8674f5 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/braille_display_manager.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/braille/braille_display_manager.js
@@ -14,17 +14,20 @@ goog.require('cvox.BrailleDisplayState');
goog.require('cvox.ExpandingBrailleTranslator');
goog.require('cvox.LibLouis');
goog.require('cvox.NavBraille');
+goog.require('cvox.PanStrategy');
/**
+ * @param {!cvox.BrailleTranslatorManager} translatorManager Keeps track
+ * of the current translator to use.
* @constructor
*/
-cvox.BrailleDisplayManager = function() {
+cvox.BrailleDisplayManager = function(translatorManager) {
/**
- * @type {cvox.ExpandingBrailleTranslator}
+ * @type {!cvox.BrailleTranslatorManager}
* @private
*/
- this.translator_ = null;
+ this.translatorManager_ = translatorManager;
/**
* @type {!cvox.NavBraille}
* @private
@@ -42,10 +45,15 @@ cvox.BrailleDisplayManager = function() {
*/
this.translatedContent_ = new ArrayBuffer(0);
/**
- * @type {number}
+ * @type {!ArrayBuffer}
* @private
*/
- this.panPosition_ = 0;
+ this.displayedContent_ = this.translatedContent_;
+ /**
+ * @type {cvox.PanStrategy}
+ * @private
+ */
+ this.panStrategy_ = new cvox.WrappingPanStrategy();
/**
* @type {function(!cvox.BrailleKeyEvent, cvox.NavBraille)}
* @private
@@ -68,16 +76,29 @@ cvox.BrailleDisplayManager = function() {
*/
this.realDisplayState_ = this.displayState_;
/**
- * @type {!Array.<number>}
+ * @type {!Array<number>}
* @private
*/
this.textToBraille_ = [];
/**
- * @type {Array.<number>}
+ * @type {!Array<number>}
* @private
*/
this.brailleToText_ = [];
+ translatorManager.addChangeListener(function() {
+ this.translateContent_(this.content_, this.expansionType_);
+ }.bind(this));
+
+ chrome.storage.onChanged.addListener(function(changes, area) {
+ if (area == 'local' && 'brailleWordWrap' in changes) {
+ this.updatePanStrategy_(changes.brailleWordWrap.newValue);
+ }
+ }.bind(this));
+ chrome.storage.local.get({brailleWordWrap: true}, function(items) {
+ this.updatePanStrategy_(items.brailleWordWrap);
+ }.bind(this));
+
cvox.BrailleCaptionsBackground.init(goog.bind(
this.onCaptionsStateChanged_, this));
if (goog.isDef(chrome.brailleDisplayPrivate)) {
@@ -130,36 +151,13 @@ cvox.BrailleDisplayManager.prototype.setCommandListener = function(func) {
/**
- * Sets the translator to be used for the braille content and refreshes the
- * braille display with the current content using the new translator.
- * @param {cvox.LibLouis.Translator} defaultTranslator Translator to use by
- * default from now on.
- * @param {cvox.LibLouis.Translator=} opt_uncontractedTranslator Translator
- * to use around text selection end-points.
- */
-cvox.BrailleDisplayManager.prototype.setTranslator =
- function(defaultTranslator, opt_uncontractedTranslator) {
- var hadTranslator = (this.translator_ != null);
- if (defaultTranslator) {
- this.translator_ = new cvox.ExpandingBrailleTranslator(
- defaultTranslator, opt_uncontractedTranslator);
- } else {
- this.translator_ = null;
- }
- this.translateContent_(this.content_, this.expansionType_);
- if (hadTranslator && !this.translator_) {
- this.refresh_();
- }
-};
-
-
-/**
* @param {!cvox.BrailleDisplayState} newState Display state reported
* by the extension API.
* @private
*/
-cvox.BrailleDisplayManager.prototype.refreshDisplayState_ =
- function(newState) {
+cvox.BrailleDisplayManager.prototype.refreshDisplayState_ = function(
+ newState) {
+ var oldSize = this.displayState_.textCellCount || 0;
this.realDisplayState_ = newState;
if (newState.available) {
this.displayState_ = newState;
@@ -167,7 +165,10 @@ cvox.BrailleDisplayManager.prototype.refreshDisplayState_ =
this.displayState_ =
cvox.BrailleCaptionsBackground.getVirtualDisplayState();
}
- this.panPosition_ = 0;
+ var newSize = this.displayState_.textCellCount || 0;
+ if (oldSize != newSize) {
+ this.panStrategy_.setDisplaySize(newSize);
+ }
this.refresh_();
};
@@ -189,14 +190,14 @@ cvox.BrailleDisplayManager.prototype.refresh_ = function() {
if (!this.displayState_.available) {
return;
}
- var buf = this.translatedContent_.slice(this.panPosition_,
- this.panPosition_ + this.displayState_.textCellCount);
+ var viewPort = this.panStrategy_.viewPort;
+ var buf = this.displayedContent_.slice(viewPort.start, viewPort.end);
if (this.realDisplayState_.available) {
chrome.brailleDisplayPrivate.writeDots(buf);
}
if (cvox.BrailleCaptionsBackground.isEnabled()) {
- var start = this.brailleToTextPosition_(this.panPosition_);
- var end = this.brailleToTextPosition_(this.panPosition_ + buf.byteLength);
+ var start = this.brailleToTextPosition_(viewPort.start);
+ var end = this.brailleToTextPosition_(viewPort.end);
cvox.BrailleCaptionsBackground.setContent(
this.content_.text.toString().substring(start, end), buf);
}
@@ -212,58 +213,61 @@ cvox.BrailleDisplayManager.prototype.refresh_ = function() {
*/
cvox.BrailleDisplayManager.prototype.translateContent_ = function(
newContent, newExpansionType) {
- if (!this.translator_) {
+ var writeTranslatedContent = function(cells, textToBraille, brailleToText) {
this.content_ = newContent;
this.expansionType_ = newExpansionType;
- this.translatedContent_ = new ArrayBuffer(0);
- this.textToBraille_.length = 0;
- this.brailleToText_.length = 0;
- return;
+ this.textToBraille_ = textToBraille;
+ this.brailleToText_ = brailleToText;
+ var startIndex = this.content_.startIndex;
+ var endIndex = this.content_.endIndex;
+ var targetPosition;
+ if (startIndex >= 0) {
+ var translatedStartIndex;
+ var translatedEndIndex;
+ if (startIndex >= textToBraille.length) {
+ // Allow the cells to be extended with one extra cell for
+ // a carret after the last character.
+ var extCells = new ArrayBuffer(cells.byteLength + 1);
+ new Uint8Array(extCells).set(new Uint8Array(cells));
+ // Last byte is initialized to 0.
+ cells = extCells;
+ translatedStartIndex = cells.byteLength - 1;
+ } else {
+ translatedStartIndex = textToBraille[startIndex];
+ }
+ if (endIndex >= textToBraille.length) {
+ // endIndex can't be past-the-end of the last cell unless
+ // startIndex is too, so we don't have to do another
+ // extension here.
+ translatedEndIndex = cells.byteLength;
+ } else {
+ translatedEndIndex = textToBraille[endIndex];
+ }
+ this.translatedContent_ = cells;
+ // Copy the transalted content to a separate buffer and add the cursor
+ // to it.
+ this.displayedContent_ = new ArrayBuffer(cells.byteLength);
+ new Uint8Array(this.displayedContent_).set(new Uint8Array(cells));
+ this.writeCursor_(this.displayedContent_,
+ translatedStartIndex, translatedEndIndex);
+ targetPosition = translatedStartIndex;
+ } else {
+ this.translatedContent_ = this.displayedContent_ = cells;
+ targetPosition = 0;
+ }
+ this.panStrategy_.setContent(this.translatedContent_, targetPosition);
+ this.refresh_();
+ }.bind(this);
+
+ var translator = this.translatorManager_.getExpandingTranslator();
+ if (!translator) {
+ writeTranslatedContent(new ArrayBuffer(0), [], []);
+ } else {
+ translator.translate(
+ newContent.text,
+ newExpansionType,
+ writeTranslatedContent);
}
- this.translator_.translate(
- newContent.text,
- newExpansionType,
- goog.bind(function(cells, textToBraille, brailleToText) {
- this.content_ = newContent;
- this.expansionType_ = newExpansionType;
- var startIndex = this.content_.startIndex;
- var endIndex = this.content_.endIndex;
- this.panPosition_ = 0;
- if (startIndex >= 0) {
- var translatedStartIndex;
- var translatedEndIndex;
- if (startIndex >= textToBraille.length) {
- // Allow the cells to be extended with one extra cell for
- // a carret after the last character.
- var extCells = new ArrayBuffer(cells.byteLength + 1);
- var extCellsView = new Uint8Array(extCells);
- extCellsView.set(new Uint8Array(cells));
- // Last byte is initialized to 0.
- cells = extCells;
- translatedStartIndex = cells.byteLength - 1;
- } else {
- translatedStartIndex = textToBraille[startIndex];
- }
- if (endIndex >= textToBraille.length) {
- // endIndex can't be past-the-end of the last cell unless
- // startIndex is too, so we don't have to do another
- // extension here.
- translatedEndIndex = cells.byteLength;
- } else {
- translatedEndIndex = textToBraille[endIndex];
- }
- this.writeCursor_(cells, translatedStartIndex, translatedEndIndex);
- if (this.displayState_.available) {
- var textCells = this.displayState_.textCellCount;
- this.panPosition_ = Math.floor(translatedStartIndex / textCells) *
- textCells;
- }
- }
- this.translatedContent_ = cells;
- this.brailleToText_ = brailleToText;
- this.textToBraille_ = textToBraille;
- this.refresh_();
- }, this));
};
@@ -281,7 +285,7 @@ cvox.BrailleDisplayManager.prototype.onKeyEvent_ = function(event) {
break;
case cvox.BrailleKeyCommand.ROUTING:
event.displayPosition = this.brailleToTextPosition_(
- event.displayPosition + this.panPosition_);
+ event.displayPosition + this.panStrategy_.viewPort.start);
// fall through
default:
this.commandListener_(event, this.content_);
@@ -297,15 +301,13 @@ cvox.BrailleDisplayManager.prototype.onKeyEvent_ = function(event) {
* @private
*/
cvox.BrailleDisplayManager.prototype.panLeft_ = function() {
- if (this.panPosition_ <= 0) {
+ if (this.panStrategy_.previous()) {
+ this.refresh_();
+ } else {
this.commandListener_({
command: cvox.BrailleKeyCommand.PAN_LEFT
}, this.content_);
- return;
}
- this.panPosition_ = Math.max(
- 0, this.panPosition_ - this.displayState_.textCellCount);
- this.refresh_();
};
@@ -316,15 +318,13 @@ cvox.BrailleDisplayManager.prototype.panLeft_ = function() {
* @private
*/
cvox.BrailleDisplayManager.prototype.panRight_ = function() {
- var newPosition = this.panPosition_ + this.displayState_.textCellCount;
- if (newPosition >= this.translatedContent_.byteLength) {
+ if (this.panStrategy_.next()) {
+ this.refresh_();
+ } else {
this.commandListener_({
command: cvox.BrailleKeyCommand.PAN_RIGHT
}, this.content_);
- return;
}
- this.panPosition_ = newPosition;
- this.refresh_();
};
@@ -378,3 +378,18 @@ cvox.BrailleDisplayManager.prototype.brailleToTextPosition_ =
return mapping[braillePosition];
}
};
+
+
+/**
+ * @param {boolean} wordWrap
+ * @private
+ */
+cvox.BrailleDisplayManager.prototype.updatePanStrategy_ = function(wordWrap) {
+ var newStrategy = wordWrap ? new cvox.WrappingPanStrategy() :
+ new cvox.FixedPanStrategy();
+ newStrategy.setDisplaySize(this.displayState_.textCellCount || 0);
+ newStrategy.setContent(this.translatedContent_,
+ this.panStrategy_.viewPort.start);
+ this.panStrategy_ = newStrategy;
+ this.refresh_();
+};
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/braille_display_manager_test.unitjs b/chromium/chrome/browser/resources/chromeos/chromevox/braille/braille_display_manager_test.unitjs
index 45859157e02..db4b37fed45 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/braille_display_manager_test.unitjs
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/braille/braille_display_manager_test.unitjs
@@ -3,8 +3,8 @@
// found in the LICENSE file.
// Include test fixture.
-GEN_INCLUDE(['../../testing/chromevox_unittest_base.js',
- '../../testing/fake_objects.js']);
+GEN_INCLUDE(['../testing/chromevox_unittest_base.js',
+ '../testing/fake_objects.js']);
/**
* Test fixture.
@@ -30,6 +30,7 @@ CvoxBrailleDisplayManagerUnitTest.prototype = {
this.NAV_BRAILLE = new cvox.NavBraille({ text: 'Hello, world!' });
this.EMPTY_NAV_BRAILLE = new cvox.NavBraille({ text: '' });
this.translator = new FakeTranslator();
+ this.translatorManager = new FakeTranslatorManager();
/** @const */
this.DISPLAY_SIZE = 12;
},
@@ -67,8 +68,9 @@ CvoxBrailleDisplayManagerUnitTest.prototype = {
assertEquals(1, this.writtenCells.length);
var a = new Uint8Array(this.writtenCells[0]);
this.writtenCells.length = 0;
- assertEquals(start, a[0] & ~cvox.BrailleDisplayManager.CURSOR_DOTS_,
- 'Start mismatch: ' + start + ' vs. ' + a[0]);
+ var firstCell = a[0] & ~cvox.BrailleDisplayManager.CURSOR_DOTS_;
+ assertEquals(start, firstCell,
+ 'Start mismatch: ' + start + ' vs. ' + firstCell);
if (opt_selStart !== undefined) {
for (var i = opt_selStart; i < opt_selEnd; ++i) {
assertEquals(cvox.BrailleDisplayManager.CURSOR_DOTS_,
@@ -92,16 +94,17 @@ CvoxBrailleDisplayManagerUnitTest.prototype = {
}
};
-/** @extends {cvox.LibLouis.Translator} */
+/** @extends {cvox.ExpandingBrailleTranslator} */
function FakeTranslator() {
}
FakeTranslator.prototype = {
/**
* Does a translation where every other character becomes two cells.
- * @override
- */
- translate: function(text, callback) {
+ * @override
+ */
+ translate: function(spannable, expansionType, callback) {
+ text = spannable.toString();
var buf = new Uint8Array(text.length + text.length / 2);
var textToBraille = [];
var brailleToText = [];
@@ -121,13 +124,49 @@ FakeTranslator.prototype = {
}
};
+/** @extends {cvox.BrailleTranslatorManager} */
+function FakeTranslatorManager() {
+}
+
+FakeTranslatorManager.prototype = {
+ changeListener: null,
+ translator: null,
+
+ setTranslator: function(translator) {
+ this.translator = translator;
+ if (this.changeListener) {
+ this.changeListener();
+ }
+ },
+
+ addChangeListener: function(listener) {
+ assertEquals(null, this.changeListener);
+ this.changeListener = listener;
+ },
+
+ getExpandingTranslator: function() {
+ return this.translator;
+ }
+};
+
var chrome = {};
+// Fake chrome.storage API.
+chrome.storage = {
+ onChanged: new FakeChromeEvent(),
+
+ local: {
+ get: function(object, callback) {
+ callback({brailleWordWrap: false});
+ }
+ }
+};
+
TEST_F('CvoxBrailleDisplayManagerUnitTest', 'NoApi', function() {
- var manager = new cvox.BrailleDisplayManager();
+ var manager = new cvox.BrailleDisplayManager(this.translatorManager);
manager.setContent(this.NAV_BRAILLE);
- manager.setTranslator(this.translator);
+ this.translatorManager.setTranslator(this.translator);
manager.setContent(this.NAV_BRAILLE);
});
@@ -139,9 +178,9 @@ TEST_F('CvoxBrailleDisplayManagerUnitTest', 'NoDisplay', function() {
this.addFakeApi();
this.displayState = {available: false};
- var manager = new cvox.BrailleDisplayManager();
+ var manager = new cvox.BrailleDisplayManager(this.translatorManager);
manager.setContent(this.NAV_BRAILLE);
- manager.setTranslator(this.translator);
+ this.translatorManager.setTranslator(this.translator);
manager.setContent(this.NAV_BRAILLE);
assertEquals(0, this.writtenCells.length);
});
@@ -153,10 +192,11 @@ TEST_F('CvoxBrailleDisplayManagerUnitTest', 'NoDisplay', function() {
TEST_F('CvoxBrailleDisplayManagerUnitTest', 'BasicSetContent', function() {
this.addFakeApi();
this.displayAvailable();
- var manager = new cvox.BrailleDisplayManager();
+ var manager = new cvox.BrailleDisplayManager(this.translatorManager);
this.assertEmptyDisplayAndClear();
manager.setContent(this.NAV_BRAILLE);
- manager.setTranslator(this.translator);
+ this.assertEmptyDisplayAndClear();
+ this.translatorManager.setTranslator(this.translator);
this.assertDisplayPositionAndClear(0);
manager.setContent(this.NAV_BRAILLE);
this.assertDisplayPositionAndClear(0);
@@ -171,10 +211,11 @@ TEST_F('CvoxBrailleDisplayManagerUnitTest', 'SetEmptyContentWithTranslator',
this.addFakeApi();
this.displayAvailable();
- var manager = new cvox.BrailleDisplayManager();
+ var manager = new cvox.BrailleDisplayManager(this.translatorManager);
this.assertEmptyDisplayAndClear();
manager.setContent(this.NAV_BRAILLE);
- manager.setTranslator(this.translator);
+ this.assertEmptyDisplayAndClear();
+ this.translatorManager.setTranslator(this.translator);
this.assertDisplayPositionAndClear(0);
manager.setContent(this.EMPTY_NAV_BRAILLE);
this.assertEmptyDisplayAndClear();
@@ -193,9 +234,9 @@ TEST_F('CvoxBrailleDisplayManagerUnitTest', 'CursorAndPanning', function() {
this.addFakeApi();
this.displayAvailable();
- var manager = new cvox.BrailleDisplayManager();
+ var manager = new cvox.BrailleDisplayManager(this.translatorManager);
this.assertEmptyDisplayAndClear();
- manager.setTranslator(this.translator);
+ this.translatorManager.setTranslator(this.translator);
this.assertEmptyDisplayAndClear();
// Cursor at beginning of line.
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/braille/braille_input_handler.js b/chromium/chrome/browser/resources/chromeos/chromevox/braille/braille_input_handler.js
new file mode 100644
index 00000000000..25677d5b539
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/braille/braille_input_handler.js
@@ -0,0 +1,587 @@
+// 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 Handles braille input keys when the user is typing or editing
+ * text in an input field. This class cooperates with the Braille IME
+ * that is built into Chrome OS to do the actual text editing.
+ */
+
+goog.provide('cvox.BrailleInputHandler');
+
+goog.require('StringUtil');
+goog.require('cvox.BrailleKeyCommand');
+goog.require('cvox.BrailleKeyEvent');
+goog.require('cvox.ExpandingBrailleTranslator');
+
+/**
+ * @param {!cvox.BrailleTranslatorManager} translatorManager Keeps track of
+ * the current braille translator(s).
+ * @constructor
+ */
+cvox.BrailleInputHandler = function(translatorManager) {
+ /**
+ * Port of the connected IME if any.
+ * @type {Port}
+ * @private
+ */
+ this.imePort_ = null;
+ /**
+ * {code true} when the Braille IME is connected and has signaled that it is
+ * active.
+ * @type {boolean}
+ * @private
+ */
+ this.imeActive_ = false;
+ /**
+ * The input context of the current input field, as reported by the IME.
+ * {@code null} if no input field has focus.
+ * @type {{contextID: number, type: string}?}
+ * @private
+ */
+ this.inputContext_ = null;
+ /**
+ * @type {!cvox.BrailleTranslatorManager}
+ * @private
+ */
+ this.translatorManager_ = translatorManager;
+ /**
+ * Text that currently precedes the first selection end-point.
+ * @type {string}
+ * @private
+ */
+ this.currentTextBefore_ = '';
+ /**
+ * Text that currently follows the last selection end-point.
+ * @type {string}
+ * @private
+ */
+ this.currentTextAfter_ = '';
+ /**
+ * Cells that were entered while the IME wasn't active. These will be
+ * submitted once the IME becomes active and reports the current input field.
+ * This is necessary because the IME is activated on the first braille
+ * dots command, but we'll receive the command in parallel. To work around
+ * the race, we store the cell entered until we can submit it to the IME.
+ * @type {!Array<number>}
+ * @private
+ */
+ this.pendingCells_ = [];
+ /**
+ * @type {cvox.BrailleInputHandler.EntryState_}
+ * @private
+ */
+ this.entryState_ = null;
+
+ this.translatorManager_.addChangeListener(
+ this.clearEntryState_.bind(this));
+};
+
+/**
+ * The ID of the Braille IME extension built into Chrome OS.
+ * @const {string}
+ * @private
+ */
+cvox.BrailleInputHandler.IME_EXTENSION_ID_ =
+ 'jddehjeebkoimngcbdkaahpobgicbffp';
+
+/**
+ * Name of the port to use for communicating with the Braille IME.
+ * @const {string}
+ * @private
+ */
+cvox.BrailleInputHandler.IME_PORT_NAME_ = 'cvox.BrailleIme.Port';
+
+/**
+ * Regular expression that matches a string that starts with at least one
+ * non-whitespace character.
+ * @const {RegExp}
+ * @private
+ */
+cvox.BrailleInputHandler.STARTS_WITH_NON_WHITESPACE_RE_ = /^\S/;
+
+/**
+ * Regular expression that matches a string that ends with at least one
+ * non-whitespace character.
+ * @const {RegExp}
+ * @private
+ */
+cvox.BrailleInputHandler.ENDS_WITH_NON_WHITESPACE_RE_ = /\S$/;
+
+cvox.BrailleInputHandler.prototype = {
+ /**
+ * Starts to listen for connections from the Chrome OS braille IME.
+ */
+ init: function() {
+ chrome.runtime.onConnectExternal.addListener(this.onImeConnect_.bind(this));
+ },
+
+ /**
+ * Called when the content on the braille display is updated. Modifies the
+ * input state according to the new content.
+ * @param {cvox.Spannable} text Text, optionally with value and selection
+ * spans.
+ */
+ onDisplayContentChanged: function(text) {
+ var valueSpan = text.getSpanInstanceOf(cvox.ValueSpan);
+ var selectionSpan = text.getSpanInstanceOf(cvox.ValueSelectionSpan);
+ if (!(valueSpan && selectionSpan))
+ return;
+ // The type casts are ok because the spans are known to exist.
+ var valueStart = /** @type {number} */ (text.getSpanStart(valueSpan));
+ var valueEnd = /** @type {number} */ (text.getSpanEnd(valueSpan));
+ var selectionStart =
+ /** @type {number} */ (text.getSpanStart(selectionSpan));
+ var selectionEnd = /** @type {number} */ (text.getSpanEnd(selectionSpan));
+ if (selectionStart < valueStart || selectionEnd > valueEnd) {
+ console.error('Selection outside of value in braille content');
+ this.clearEntryState_();
+ return;
+ }
+ var newTextBefore = text.toString().substring(valueStart, selectionStart);
+ if (this.currentTextBefore_ !== newTextBefore && this.entryState_)
+ this.entryState_.onTextBeforeChanged(newTextBefore);
+ this.currentTextBefore_ = newTextBefore;
+ this.currentTextAfter_ = text.toString().substring(selectionEnd, valueEnd);
+ },
+
+ /**
+ * Handles braille key events used for input by editing the current input
+ * field appropriately.
+ * @param {!cvox.BrailleKeyEvent} event The key event.
+ * @return {boolean} {@code true} if the event was handled, {@code false}
+ * if it should propagate further.
+ */
+ onBrailleKeyEvent: function(event) {
+ if (event.command === cvox.BrailleKeyCommand.DOTS)
+ return this.onBrailleDots_(/** @type {number} */(event.brailleDots));
+ // Any other braille command cancels the pending cells.
+ this.pendingCells_.length = 0;
+ if (event.command === cvox.BrailleKeyCommand.STANDARD_KEY) {
+ if (event.standardKeyCode === 'Backspace' &&
+ !event.altKey && !event.ctrlKey && !event.shiftKey &&
+ this.onBackspace_()) {
+ return true;
+ } else {
+ this.clearEntryState_();
+ this.sendKeyEventPair_(event);
+ return true;
+ }
+ }
+ return false;
+ },
+
+ /**
+ * Returns how the value of the currently displayed content should be
+ * expanded given the current input state.
+ * @return {cvox.ExpandingBrailleTranslator.ExpansionType}
+ * The current expansion type.
+ */
+ getExpansionType: function() {
+ if (this.inAlwaysUncontractedContext_())
+ return cvox.ExpandingBrailleTranslator.ExpansionType.ALL;
+ if (this.entryState_ &&
+ this.entryState_.translator ===
+ this.translatorManager_.getDefaultTranslator()) {
+ return cvox.ExpandingBrailleTranslator.ExpansionType.NONE;
+ }
+ return cvox.ExpandingBrailleTranslator.ExpansionType.SELECTION;
+ },
+
+ /**
+ * @return {boolean} {@code true} if we have an input context and
+ * uncontracted braille should always be used for that context.
+ * @private
+ */
+ inAlwaysUncontractedContext_: function() {
+ var inputType = this.inputContext_ ? this.inputContext_.type : '';
+ return inputType === 'url' || inputType === 'email';
+ },
+
+ /**
+ * Called when a user typed a braille cell.
+ * @param {number} dots The dot pattern of the cell.
+ * @return {boolean} Whether the event was handled or should be allowed to
+ * propagate further.
+ * @private
+ */
+ onBrailleDots_: function(dots) {
+ if (!this.imeActive_) {
+ this.pendingCells_.push(dots);
+ return true;
+ }
+ if (!this.inputContext_)
+ return false;
+ // Avoid accumulating cells forever when typing without moving the cursor
+ // by flushing the input when we see a blank cell.
+ // Note that this might switch to contracted if appropriate.
+ if (this.entryState_ && this.entryState_.lastCellIsBlank())
+ this.clearEntryState_();
+ if (!this.entryState_) {
+ this.entryState_ = this.createEntryState_();
+ if (!this.entryState_)
+ return false;
+ }
+ this.entryState_.appendCell(dots);
+ return true;
+ },
+
+ /**
+ * Handles the backspace key by deleting the last typed cell if possible.
+ * @return {boolean} {@code true} if the event was handled, {@code false}
+ * if it wasn't and should propagate further.
+ * @private
+ */
+ onBackspace_: function() {
+ if (this.imeActive_ && this.entryState_) {
+ this.entryState_.deleteLastCell();
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Creates a new empty {@code EntryState_} based on the current input context
+ * and surrounding text.
+ * @return {cvox.BrailleInputHandler.EntryState_} The newly created state
+ * object, or null if it couldn't be created (e.g. if there's no braille
+ * translator available yet).
+ * @private
+ */
+ createEntryState_: function() {
+ var translator = this.translatorManager_.getDefaultTranslator();
+ if (!translator)
+ return null;
+ var uncontractedTranslator =
+ this.translatorManager_.getUncontractedTranslator();
+ if (uncontractedTranslator) {
+ var textBefore = this.currentTextBefore_;
+ var textAfter = this.currentTextAfter_;
+ if (this.inAlwaysUncontractedContext_() ||
+ (cvox.BrailleInputHandler.ENDS_WITH_NON_WHITESPACE_RE_.test(
+ textBefore)) ||
+ (cvox.BrailleInputHandler.STARTS_WITH_NON_WHITESPACE_RE_.test(
+ textAfter))) {
+ translator = uncontractedTranslator;
+ }
+ }
+
+ return new cvox.BrailleInputHandler.EditsEntryState_(this, translator);
+ },
+
+ /**
+ * Clears the current entry state without committing it.
+ * @private
+ */
+ clearEntryState_: function() {
+ if (this.entryState_) {
+ this.entryState_.inputHandler_ = null;
+ this.entryState_ = null;
+ }
+ },
+
+ /**
+ * Called when another extension connects to this extension. Accepts
+ * connections from the ChromeOS builtin Braille IME and ignores connections
+ * from other extensions.
+ * @param {Port} port The port used to communicate with the other extension.
+ * @private
+ */
+ onImeConnect_: function(port) {
+ if (port.name !== cvox.BrailleInputHandler.IME_PORT_NAME_ ||
+ port.sender.id !== cvox.BrailleInputHandler.IME_EXTENSION_ID_) {
+ return;
+ }
+ if (this.imePort_)
+ this.imePort_.disconnect();
+ port.onDisconnect.addListener(this.onImeDisconnect_.bind(this, port));
+ port.onMessage.addListener(this.onImeMessage_.bind(this));
+ this.imePort_ = port;
+ },
+
+ /**
+ * Called when a message is received from the IME.
+ * @param {*} message The message.
+ * @private
+ */
+ onImeMessage_: function(message) {
+ if (!goog.isObject(message)) {
+ console.error('Unexpected message from Braille IME: ',
+ JSON.stringify(message));
+ }
+ switch (message.type) {
+ case 'activeState':
+ this.imeActive_ = message.active;
+ break;
+ case 'inputContext':
+ this.inputContext_ = message.context;
+ this.clearEntryState_();
+ if (this.imeActive_ && this.inputContext_)
+ this.pendingCells_.forEach(this.onBrailleDots_, this);
+ this.pendingCells_.length = 0;
+ break;
+ case 'brailleDots':
+ this.onBrailleDots_(message['dots']);
+ break;
+ case 'backspace':
+ // Note that we can't send the backspace key through the
+ // virtualKeyboardPrivate API in this case because it would then be
+ // processed by the IME again, leading to an infinite loop.
+ this.postImeMessage_(
+ {type: 'keyEventHandled', requestId: message['requestId'],
+ result: this.onBackspace_()});
+ break;
+ case 'reset':
+ this.clearEntryState_();
+ break;
+ default:
+ console.error('Unexpected message from Braille IME: ',
+ JSON.stringify(message));
+ break;
+ }
+ },
+
+ /**
+ * Called when the IME port is disconnected.
+ * @param {Port} port The port that was disconnected.
+ * @private
+ */
+ onImeDisconnect_: function(port) {
+ this.imePort_ = null;
+ this.clearEntryState_();
+ this.imeActive_ = false;
+ this.inputContext_ = null;
+ },
+
+ /**
+ * Posts a message to the IME.
+ * @param {Object} message The message.
+ * @return {boolean} {@code true} if the message was sent, {@code false} if
+ * there was no connection open to the IME.
+ * @private
+ */
+ postImeMessage_: function(message) {
+ if (this.imePort_) {
+ this.imePort_.postMessage(message);
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Sends a {@code keydown} key event followed by a {@code keyup} event
+ * corresponding to an event generated by the braille display.
+ * @param {!cvox.BrailleKeyEvent} event The braille key event to base the
+ * key events on.
+ * @private
+ */
+ sendKeyEventPair_: function(event) {
+ // Use the virtual keyboard API instead of the IME key event API
+ // so that these keys work even if the Braille IME is not active.
+ var keyName = /** @type {string} */ (event.standardKeyCode);
+ var numericCode = cvox.BrailleKeyEvent.keyCodeToLegacyCode(keyName);
+ if (!goog.isDef(numericCode))
+ throw Error('Unknown key code in event: ' + JSON.stringify(event));
+ var keyEvent = {
+ type: 'keydown',
+ keyCode: numericCode,
+ keyName: keyName,
+ charValue: cvox.BrailleKeyEvent.keyCodeToCharValue(keyName),
+ // See chrome/common/extensions/api/virtual_keyboard_private.json for
+ // these constants.
+ modifiers: (event.shiftKey ? 2 : 0) |
+ (event.ctrlKey ? 4 : 0) |
+ (event.altKey ? 8 : 0)
+ };
+ chrome.virtualKeyboardPrivate.sendKeyEvent(keyEvent);
+ keyEvent.type = 'keyup';
+ chrome.virtualKeyboardPrivate.sendKeyEvent(keyEvent);
+ }
+};
+
+/**
+ * The entry state is the state related to entering a series of braille cells
+ * without 'interruption', where interruption can be things like non braille
+ * keyboard input or unexpected changes to the text surrounding the cursor.
+ * @param {!cvox.BrailleInputHandler} inputHandler
+ * @param {!cvox.LibLouis.Translator} translator
+ * @constructor
+ * @private
+ */
+cvox.BrailleInputHandler.EntryState_ = function(inputHandler, translator) {
+ /**
+ * @type {cvox.BrailleInputHandler}
+ * @private
+ */
+ this.inputHandler_ = inputHandler;
+ /**
+ * The translator currently used for typing, if
+ * {@code this.cells_.length > 0}.
+ * @type {!cvox.LibLouis.Translator}
+ * @private
+ */
+ this.translator_ = translator;
+ /**
+ * Braille cells that have been typed by the user so far.
+ * @type {!Array<number>}
+ * @private
+ */
+ this.cells_ = [];
+ /**
+ * Text resulting from translating {@code this.cells_}.
+ * @type {string}
+ * @private
+ */
+ this.text_ = '';
+ /**
+ * List of strings that we expect to be set as preceding text of the
+ * selection. This is populated when we send text changes to the IME so that
+ * our own changes don't reset the pending cells.
+ * @type {!Array<string>}
+ * @private
+ */
+ this.pendingTextsBefore_ = [];
+};
+
+cvox.BrailleInputHandler.EntryState_.prototype = {
+ /**
+ * @return {!cvox.LibLouis.Translator} The translator used by this entry
+ * state. This doesn't change for a given object.
+ */
+ get translator() {
+ return this.translator_;
+ },
+
+ /**
+ * Appends a braille cell to the current input and updates the text if
+ * necessary.
+ * @param {number} cell The braille cell to append.
+ */
+ appendCell: function(cell) {
+ this.cells_.push(cell);
+ this.updateText_();
+ },
+
+ /**
+ * Deletes the last cell of the input and updates the text if neccary.
+ * If there's no more input in this object afterwards, clears the entry state
+ * of the input handler.
+ */
+ deleteLastCell: function() {
+ if (--this.cells_.length <= 0) {
+ this.sendTextChange_('');
+ this.inputHandler_.clearEntryState_();
+ return;
+ }
+ this.updateText_();
+ },
+
+ /**
+ * Called when the text before the cursor changes giving this object a
+ * chance to clear the entry state of the input handler if the change
+ * wasn't expected.
+ * @param {string} newText New text before the cursor.
+ */
+ onTextBeforeChanged: function(newText) {
+ // See if we are expecting this change as a result of one of our own edits.
+ // Allow changes to be coalesced by the input system in an attempt to not
+ // be too brittle.
+ for (var i = 0; i < this.pendingTextsBefore_.length; ++i) {
+ if (newText === this.pendingTextsBefore_[i]) {
+ // Delete all previous expected changes and ignore this one.
+ this.pendingTextsBefore_.splice(0, i + 1);
+ return;
+ }
+ }
+ // There was an actual text change (or cursor movement) that we hadn't
+ // caused ourselves, reset any pending input.
+ this.inputHandler_.clearEntryState_();
+ },
+
+ /** @return {boolean} */
+ lastCellIsBlank: function() {
+ return this.cells_[this.cells_.length - 1] === 0;
+ },
+
+ /**
+ * Updates the translated text based on the current cells and sends the
+ * delta to the IME.
+ * @private
+ */
+ updateText_: function() {
+ var cellsBuffer = new Uint8Array(this.cells_).buffer;
+ this.translator_.backTranslate(cellsBuffer, function(result) {
+ if (result === null) {
+ console.error('Error when backtranslating braille cells');
+ return;
+ }
+ if (!this.inputHandler_)
+ return;
+ this.sendTextChange_(result);
+ this.text_ = result;
+ }.bind(this));
+ },
+
+ /**
+ * Sends new text to the IME. This dhould be overriden by subclasses.
+ * The old text is still available in the {@code text_} property.
+ * @param {string} newText Text to send.
+ * @private
+ */
+ sendTextChange_: function(newText) {
+ }
+};
+
+/**
+ * Entry state that uses {@code deleteSurroundingText} and {@code commitText}
+ * calls to the IME to update the currently enetered text.
+ * @param {!cvox.BrailleInputHandler} inputHandler
+ * @param {!cvox.LibLouis.Translator} translator
+ * @constructor
+ * @extends {cvox.BrailleInputHandler.EntryState_}
+ * @private
+ */
+cvox.BrailleInputHandler.EditsEntryState_ = function(
+ inputHandler, translator) {
+ cvox.BrailleInputHandler.EntryState_.call(this, inputHandler, translator);
+};
+
+cvox.BrailleInputHandler.EditsEntryState_.prototype = {
+ __proto__: cvox.BrailleInputHandler.EntryState_.prototype,
+
+ /** @override */
+ sendTextChange_: function(newText) {
+ var oldText = this.text_;
+ // Find the common prefix of the old and new text.
+ var commonPrefixLength = StringUtil.longestCommonPrefixLength(
+ oldText, newText);
+ // How many characters we need to delete from the existing text to replace
+ // them with characters from the new text.
+ var deleteLength = oldText.length - commonPrefixLength;
+ // New text, if any, to insert after deleting the deleteLength characters
+ // before the cursor.
+ var toInsert = newText.substring(commonPrefixLength);
+ if (deleteLength > 0 || toInsert.length > 0) {
+ // After deleting, we expect this text to be present before the cursor.
+ var textBeforeAfterDelete =
+ this.inputHandler_.currentTextBefore_.substring(
+ 0, this.inputHandler_.currentTextBefore_.length - deleteLength);
+ if (deleteLength > 0) {
+ // Queue this text up to be ignored when the change comes in.
+ this.pendingTextsBefore_.push(textBeforeAfterDelete);
+ }
+ if (toInsert.length > 0) {
+ // Likewise, queue up what we expect to be before the cursor after
+ // the replacement text is inserted.
+ this.pendingTextsBefore_.push(textBeforeAfterDelete + toInsert);
+ }
+ // Send the replace operation to be performed asynchronously by the IME.
+ this.inputHandler_.postImeMessage_(
+ {type: 'replaceText',
+ contextID: this.inputHandler_.inputContext_.contextID,
+ deleteBefore: deleteLength,
+ newText: toInsert});
+ }
+ }
+};
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/braille_input_handler_test.unitjs b/chromium/chrome/browser/resources/chromeos/chromevox/braille/braille_input_handler_test.unitjs
index d6dca722481..59fa53e7f55 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/braille_input_handler_test.unitjs
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/braille/braille_input_handler_test.unitjs
@@ -3,9 +3,9 @@
// found in the LICENSE file.
// Include test fixture.
-GEN_INCLUDE(['../../testing/chromevox_unittest_base.js']);
+GEN_INCLUDE(['../testing/chromevox_unittest_base.js']);
-GEN_INCLUDE(['../../testing/fake_objects.js']);
+GEN_INCLUDE(['../testing/fake_objects.js']);
// Fake out the Chrome API namespace we depend on.
var chrome = {};
@@ -319,6 +319,39 @@ FakeTranslator.prototype.backTranslate = function(cells, callback) {
callback(result);
};
+/** @extends {cvox.BrailleTranslatorManager} */
+function FakeTranslatorManager() {
+}
+
+FakeTranslatorManager.prototype = {
+ defaultTranslator: null,
+ uncontractedTranslator: null,
+ changeListener: null,
+
+ /** @override */
+ getDefaultTranslator: function() {
+ return this.defaultTranslator;
+ },
+
+ /** @override */
+ getUncontractedTranslator: function() {
+ return this.uncontractedTranslator;
+ },
+
+ /** @override */
+ addChangeListener: function(listener) {
+ assertEquals(null, this.changeListener);
+ },
+
+ setTranslators: function(defaultTranslator, uncontractedTranslator) {
+ this.defaultTranslator = defaultTranslator;
+ this.uncontractedTranslator = uncontractedTranslator;
+ if (this.changeListener) {
+ this.changeListener();
+ }
+ }
+};
+
/**
* Converts a list of cells, represented as a string, to an array.
* @param {string} cells A string with space separated groups of digits.
@@ -358,7 +391,6 @@ CvoxBrailleInputHandlerUnitTest.prototype = {
closureModuleDeps: [
'cvox.BrailleInputHandler',
'cvox.BrailleUtil',
- 'cvox.Spannable',
],
/**
@@ -438,7 +470,8 @@ CvoxBrailleInputHandlerUnitTest.prototype = {
setUp: function() {
chrome.runtime.onConnectExternal = new FakeChromeEvent();
this.port = new FakePort();
- this.inputHandler = new cvox.BrailleInputHandler();
+ this.translatorManager = new FakeTranslatorManager();
+ this.inputHandler = new cvox.BrailleInputHandler(this.translatorManager);
this.inputHandler.init();
this.uncontractedTranslator = new FakeTranslator(UNCONTRACTED_TABLE);
this.contractedTranslator = new FakeTranslator(CONTRACTED_TABLE, true);
@@ -469,7 +502,7 @@ TEST_F('CvoxBrailleInputHandlerUnitTest', 'NoTranslator', function() {
TEST_F('CvoxBrailleInputHandlerUnitTest', 'InputUncontracted', function() {
- this.inputHandler.setTranslator(this.uncontractedTranslator);
+ this.translatorManager.setTranslators(this.uncontractedTranslator, null);
var editor = this.createEditor();
editor.setActive(true);
@@ -497,8 +530,8 @@ TEST_F('CvoxBrailleInputHandlerUnitTest', 'InputUncontracted', function() {
TEST_F('CvoxBrailleInputHandlerUnitTest', 'InputContracted', function() {
var editor = this.createEditor();
- this.inputHandler.setTranslator(this.contractedTranslator,
- this.uncontractedTranslator);
+ this.translatorManager.setTranslators(this.contractedTranslator,
+ this.uncontractedTranslator);
editor.setActive(true);
editor.focus('text');
this.assertExpandingSelection();
@@ -550,8 +583,8 @@ TEST_F('CvoxBrailleInputHandlerUnitTest', 'InputContracted', function() {
TEST_F('CvoxBrailleInputHandlerUnitTest', 'TypingUrlWithContracted',
function() {
var editor = this.createEditor();
- this.inputHandler.setTranslator(this.contractedTranslator,
- this.uncontractedTranslator);
+ this.translatorManager.setTranslators(this.contractedTranslator,
+ this.uncontractedTranslator);
editor.setActive(true);
editor.focus('url');
this.assertExpandingAll();
@@ -573,8 +606,8 @@ TEST_F('CvoxBrailleInputHandlerUnitTest', 'TypingUrlWithContracted',
TEST_F('CvoxBrailleInputHandlerUnitTest', 'Backspace', function() {
var editor = this.createEditor();
- this.inputHandler.setTranslator(this.contractedTranslator,
- this.uncontractedTranslator);
+ this.translatorManager.setTranslators(this.contractedTranslator,
+ this.uncontractedTranslator);
editor.setActive(true);
editor.focus('text');
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/braille_key_types.js b/chromium/chrome/browser/resources/chromeos/chromevox/braille/braille_key_types.js
index b2b800ca566..8454dfb1b1b 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/braille_key_types.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/braille/braille_key_types.js
@@ -15,8 +15,6 @@ goog.provide('cvox.BrailleDisplayState');
goog.provide('cvox.BrailleKeyCommand');
goog.provide('cvox.BrailleKeyEvent');
-goog.require('cvox.ChromeVox');
-
/**
* The set of commands sent from a braille display.
@@ -98,7 +96,7 @@ cvox.BrailleKeyEvent.keyCodeToCharValue = function(keyCode) {
/**
* Map from DOM level 4 key codes to legacy numeric key codes.
- * @private {Object.<string, number>}
+ * @private {Object<string, number>}
*/
cvox.BrailleKeyEvent.legacyKeyCodeMap_ = {
'Backspace': 8,
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/braille_table.js b/chromium/chrome/browser/resources/chromeos/chromevox/braille/braille_table.js
index 318448289ad..1095e6465fb 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/braille_table.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/braille/braille_table.js
@@ -25,7 +25,7 @@ cvox.BrailleTable.Table;
/**
* @const {string}
*/
-cvox.BrailleTable.TABLE_PATH = 'chromevox/background/braille/tables.json';
+cvox.BrailleTable.TABLE_PATH = 'braille/tables.json';
/**
@@ -37,7 +37,7 @@ cvox.BrailleTable.COMMON_DEFS_FILENAME_ = 'cvox-common.cti';
/**
* Retrieves a list of all available braille tables.
- * @param {function(!Array.<cvox.BrailleTable.Table>)} callback
+ * @param {function(!Array<cvox.BrailleTable.Table>)} callback
* Called asynchronously with an array of tables.
*/
cvox.BrailleTable.getAll = function(callback) {
@@ -60,7 +60,7 @@ cvox.BrailleTable.getAll = function(callback) {
if (xhr.status == 200) {
callback(
appendCommonFilename(
- /** @type {!Array.<cvox.BrailleTable.Table>} */ (
+ /** @type {!Array<cvox.BrailleTable.Table>} */ (
JSON.parse(xhr.responseText))));
}
}
@@ -71,7 +71,7 @@ cvox.BrailleTable.getAll = function(callback) {
/**
* Finds a table in a list of tables by id.
- * @param {!Array.<cvox.BrailleTable.Table>} tables tables to search in.
+ * @param {!Array<cvox.BrailleTable.Table>} tables tables to search in.
* @param {string} id id of table to find.
* @return {cvox.BrailleTable.Table} The found table, or null if not found.
*/
@@ -84,7 +84,7 @@ cvox.BrailleTable.forId = function(tables, id) {
* Returns an uncontracted braille table corresponding to another, possibly
* contracted, table. If {@code table} is the lowest-grade table for its
* locale and dot count, {@code table} itself is returned.
- * @param {!Array.<cvox.BrailleTable.Table>} tables tables to search in.
+ * @param {!Array<cvox.BrailleTable.Table>} tables tables to search in.
* @param {!cvox.BrailleTable.Table} table Table to match.
* @return {!cvox.BrailleTable.Table} Corresponding uncontracted table,
* or {@code table} if it is uncontracted.
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/braille_table_test.extjs b/chromium/chrome/browser/resources/chromeos/chromevox/braille/braille_table_test.extjs
index 7afd2f0db87..6adae043965 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/braille_table_test.extjs
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/braille/braille_table_test.extjs
@@ -3,8 +3,8 @@
// found in the LICENSE file.
// Include test fixture.
-GEN_INCLUDE(['../../testing/chromevox_e2e_test_base.js',
- '../../testing/assert_additions.js']);
+GEN_INCLUDE(['../testing/chromevox_e2e_test_base.js',
+ '../testing/assert_additions.js']);
/**
* Test fixture for cvox.BrailleTable tests.
@@ -13,7 +13,9 @@ GEN_INCLUDE(['../../testing/chromevox_e2e_test_base.js',
* @constructor
* @extends {ChromeVoxE2ETest}
*/
-function CvoxBrailleTableTest() {}
+function CvoxBrailleTableTest() {
+ ChromeVoxE2ETest.call(this);
+}
CvoxBrailleTableTest.prototype = {
__proto__: ChromeVoxE2ETest.prototype,
@@ -24,7 +26,7 @@ CvoxBrailleTableTest.prototype = {
* NOTE: This will need to be adjusted when more tables are added.
*/
TEST_F('CvoxBrailleTableTest', 'testGetAllAndValidate', function() {
- cvox.BrailleTable.getAll(function(tables) {
+ cvox.BrailleTable.getAll(this.newCallback(function(tables) {
expectEquals(68, tables.length);
assertNotNullNorUndefined(
cvox.BrailleTable.forId(tables, 'en-US-g1'),
@@ -34,13 +36,12 @@ TEST_F('CvoxBrailleTableTest', 'testGetAllAndValidate', function() {
expectTrue(table.dots === '6' || table.dots === '8');
expectTrue(cvox.BrailleTable.getDisplayName(table).length > 0);
}
- testDone();
- });
+ }));
});
/** Tests getDisplayName for some specific representative cases. */
TEST_F('CvoxBrailleTableTest', 'testGetDisplayName', function() {
- cvox.BrailleTable.getAll(function(tables) {
+ cvox.BrailleTable.getAll(this.newCallback(function(tables) {
var table = cvox.BrailleTable.forId(tables, 'bg-comp8');
expectEquals('Bulgarian', cvox.BrailleTable.getDisplayName(table));
table = cvox.BrailleTable.forId(tables, 'ar-g1');
@@ -48,15 +49,14 @@ TEST_F('CvoxBrailleTableTest', 'testGetDisplayName', function() {
table = cvox.BrailleTable.forId(tables, 'en-UEB-g1');
expectEquals('English (UEB), Grade 1',
cvox.BrailleTable.getDisplayName(table));
- testDone();
- });
+ }));
});
/**
* Tests the getUncontracted function.
*/
TEST_F('CvoxBrailleTableTest', 'testGetUncontracted', function() {
- cvox.BrailleTable.getAll(function(tables) {
+ cvox.BrailleTable.getAll(this.newCallback(function(tables) {
function expectUncontracted(uncontractedId, idToCheck) {
var checkedTable = cvox.BrailleTable.forId(tables, idToCheck);
var uncontractedTable = cvox.BrailleTable.getUncontracted(
@@ -70,6 +70,5 @@ TEST_F('CvoxBrailleTableTest', 'testGetUncontracted', function() {
expectUncontracted('sv-comp8', 'sv-g1');
expectUncontracted('ar-g1', 'ar-g1');
expectUncontracted('de-comp8', 'de-CH-g2');
- testDone();
- });
+ }));
});
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/braille/braille_translator_manager.js b/chromium/chrome/browser/resources/chromeos/chromevox/braille/braille_translator_manager.js
new file mode 100644
index 00000000000..2a152b22837
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/braille/braille_translator_manager.js
@@ -0,0 +1,233 @@
+// 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 Keeps track of the current braille translators.
+ */
+
+goog.provide('cvox.BrailleTranslatorManager');
+
+goog.require('cvox.BrailleTable');
+goog.require('cvox.ExpandingBrailleTranslator');
+goog.require('cvox.LibLouis');
+
+/**
+ * @param {cvox.LibLouis=} opt_liblouisForTest Liblouis instance to use
+ * for testing.
+ * @constructor
+ */
+cvox.BrailleTranslatorManager = function(opt_liblouisForTest) {
+ /**
+ * @type {!cvox.LibLouis}
+ * @private
+ */
+ this.liblouis_ = opt_liblouisForTest || new cvox.LibLouis(
+ chrome.extension.getURL('braille/liblouis_nacl.nmf'),
+ chrome.extension.getURL('braille/tables'));
+ /**
+ * @type {!Array<function()>}
+ * @private
+ */
+ this.changeListeners_ = [];
+ /**
+ * @type {!Array<cvox.BrailleTable.Table>}
+ * @private
+ */
+ this.tables_ = [];
+ /**
+ * @type {cvox.ExpandingBrailleTranslator}
+ * @private
+ */
+ this.expandingTranslator_ = null;
+ /**
+ * @type {cvox.LibLouis.Translator}
+ * @private
+ */
+ this.defaultTranslator_ = null;
+ /**
+ * @type {string?}
+ * @private
+ */
+ this.defaultTableId_ = null;
+ /**
+ * @type {cvox.LibLouis.Translator}
+ * @private
+ */
+ this.uncontractedTranslator_ = null;
+ /**
+ * @type {string?}
+ * @private
+ */
+ this.uncontractedTableId_ = null;
+
+ if (!opt_liblouisForTest) {
+ document.addEventListener('DOMContentLoaded',
+ this.loadLiblouis_.bind(this),
+ false);
+ }
+};
+
+cvox.BrailleTranslatorManager.prototype = {
+ /**
+ * Adds a listener to be called whenever there is a change in the
+ * translator(s) returned by other methods of this instance.
+ * @param {function()} listener The listener.
+ */
+ addChangeListener: function(listener) {
+ this.changeListeners_.push(listener);
+ },
+
+ /**
+ * Refreshes the braille translator(s) used for input and output. This
+ * should be called when something has changed (such as a preference) to
+ * make sure that the correct translator is used.
+ */
+ refresh: function() {
+ var tables = this.tables_;
+ if (tables.length == 0)
+ return;
+
+ // First, see if we have a braille table set previously.
+ var table = cvox.BrailleTable.forId(tables, localStorage['brailleTable']);
+ if (!table) {
+ // Match table against current locale.
+ var currentLocale = chrome.i18n.getMessage('@@ui_locale').split(/[_-]/);
+ var major = currentLocale[0];
+ var minor = currentLocale[1];
+ var firstPass = tables.filter(function(table) {
+ return table.locale.split(/[_-]/)[0] == major;
+ });
+ if (firstPass.length > 0) {
+ table = firstPass[0];
+ if (minor) {
+ var secondPass = firstPass.filter(function(table) {
+ return table.locale.split(/[_-]/)[1] == minor;
+ });
+ if (secondPass.length > 0)
+ table = secondPass[0];
+ }
+ }
+ }
+ if (!table)
+ table = cvox.BrailleTable.forId(tables, 'en-US-comp8');
+
+ // TODO(plundblad): Only update when user explicitly selects a table
+ // so that switching locales changes table by default. crbug.com/441206.
+ localStorage['brailleTable'] = table.id;
+ if (!localStorage['brailleTable6'])
+ localStorage['brailleTable6'] = 'en-US-g1';
+ if (!localStorage['brailleTable8'])
+ localStorage['brailleTable8'] = 'en-US-comp8';
+
+ if (table.dots == '6') {
+ localStorage['brailleTableType'] = 'brailleTable6';
+ localStorage['brailleTable6'] = table.id;
+ } else {
+ localStorage['brailleTableType'] = 'brailleTable8';
+ localStorage['brailleTable8'] = table.id;
+ }
+
+ // If the user explicitly set an 8 dot table, use that when looking
+ // for an uncontracted table. Otherwise, use the current table and let
+ // getUncontracted find an appropriate corresponding table.
+ var table8Dot = cvox.BrailleTable.forId(tables,
+ localStorage['brailleTable8']);
+ var uncontractedTable = cvox.BrailleTable.getUncontracted(
+ tables, table8Dot || table);
+
+ var newDefaultTableId = table.id;
+ var newUncontractedTableId = table.id === uncontractedTable.id ?
+ null : uncontractedTable.id;
+ if (newDefaultTableId === this.defaultTableId_ &&
+ newUncontractedTableId === this.uncontractedTableId_) {
+ return;
+ }
+
+ var finishRefresh = function(defaultTranslator, uncontractedTranslator) {
+ this.defaultTableId_ = newDefaultTableId;
+ this.uncontractedTableId_ = newUncontractedTableId;
+ this.expandingTranslator_ = new cvox.ExpandingBrailleTranslator(
+ defaultTranslator, uncontractedTranslator);
+ this.defaultTranslator_ = defaultTranslator;
+ this.uncontractedTranslator_ = uncontractedTranslator;
+ this.changeListeners_.forEach(function(listener) { listener(); });
+ }.bind(this);
+
+ this.liblouis_.getTranslator(table.fileNames, function(translator) {
+ if (!newUncontractedTableId) {
+ finishRefresh(translator, null);
+ } else {
+ this.liblouis_.getTranslator(
+ uncontractedTable.fileNames,
+ function(uncontractedTranslator) {
+ finishRefresh(translator, uncontractedTranslator);
+ });
+ }
+ }.bind(this));
+ },
+
+ /**
+ * @return {cvox.ExpandingBrailleTranslator} The current expanding braille
+ * translator, or {@code null} if none is available.
+ */
+ getExpandingTranslator: function() {
+ return this.expandingTranslator_;
+ },
+
+ /**
+ * @return {cvox.LibLouis.Translator} The current braille translator to use
+ * by default, or {@code null} if none is available.
+ */
+ getDefaultTranslator: function() {
+ return this.defaultTranslator_;
+ },
+
+ /**
+ * @return {cvox.LibLouis.Translator} The current uncontracted braille
+ * translator, or {@code null} if it is the same as the default
+ * translator.
+ */
+ getUncontractedTranslator: function() {
+ return this.uncontractedTranslator_;
+ },
+
+ /**
+ * Asynchronously fetches the list of braille tables and refreshes the
+ * translators when done.
+ * @private
+ */
+ fetchTables_: function() {
+ cvox.BrailleTable.getAll(function(tables) {
+ this.tables_ = tables;
+ this.refresh();
+ }.bind(this));
+ },
+
+ /**
+ * Loads the liblouis instance by attaching it to the document.
+ * @private
+ */
+ loadLiblouis_: function() {
+ // Cast away nullability. When the document is loaded, it will always
+ // have a body.
+ this.liblouis_.attachToElement(
+ /** @type {!HTMLBodyElement} */ (document.body));
+ this.fetchTables_();
+ },
+
+ /**
+ * @return {!cvox.LibLouis} The liblouis instance used by this object.
+ */
+ getLibLouisForTest: function() {
+ return this.liblouis_;
+ },
+
+ /**
+ * @return {!Array<cvox.BrailleTable.Table>} The currently loaded braille
+ * tables, or an empty array if they are not yet loaded.
+ */
+ getTablesForTest: function() {
+ return this.tables_;
+ }
+};
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/braille/braille_translator_manager_test.extjs b/chromium/chrome/browser/resources/chromeos/chromevox/braille/braille_translator_manager_test.extjs
new file mode 100644
index 00000000000..24a414f9ebd
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/braille/braille_translator_manager_test.extjs
@@ -0,0 +1,116 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Include test fixture.
+GEN_INCLUDE(['../testing/chromevox_e2e_test_base.js',
+ '../testing/assert_additions.js']);
+
+/**
+ * Test fixture for cvox.BrailleTranslatorManager tests.
+ * This is an E2E test because there's no easy way to load a data file in
+ * a webui-style test.
+ * @constructor
+ * @extends {ChromeVoxE2ETest}
+ */
+function CvoxBrailleTranslatorManagerTest() {
+ ChromeVoxE2ETest.call(this);
+}
+
+CvoxBrailleTranslatorManagerTest.prototype = {
+ __proto__: ChromeVoxE2ETest.prototype,
+
+ /** @override */
+ setUp: function() {
+ this.liblouis = new FakeLibLouis();
+ this.manager = new cvox.BrailleTranslatorManager(this.liblouis);
+ this.liblouis.translatorManager = this.manager;
+ // This is called by an event handler in production, but we don't rely
+ // on that for this test.
+ this.manager.loadLiblouis_();
+ },
+
+ addChangeListener: function(callback) {
+ return this.manager.addChangeListener(callOnce(this.newCallback(callback)));
+ },
+};
+
+/** @extends {cvox.LibLouis} */
+function FakeLibLouis() {
+}
+
+FakeLibLouis.prototype = {
+ /** @override */
+ attachToElement: function() {},
+
+ /** @override */
+ getTranslator: function(fileNames, callback) {
+ var tables = this.translatorManager.getTablesForTest();
+ var result = null;
+ if (tables != null) {
+ var found = tables.filter(function(table) {
+ return table.fileNames === fileNames;
+ })[0];
+ if (found) {
+ result = new FakeTranslator(found);
+ }
+ }
+ callback(result);
+ }
+};
+
+/**
+ * @extends {cvox.LibLouis.Translator}
+ * @param {cvox.BrailleTable.Table} table
+ * @constructor
+ */
+function FakeTranslator(table) {
+ this.table = table;
+}
+
+function callOnce(callback) {
+ var called = false;
+ return function() {
+ if (!called) {
+ called = true;
+ callback.apply(null, arguments);
+ }
+ };
+}
+
+TEST_F('CvoxBrailleTranslatorManagerTest', 'testInitial', function() {
+ assertEquals(null, this.manager.getExpandingTranslator());
+ assertEquals(null, this.manager.getDefaultTranslator());
+ assertEquals(null, this.manager.getUncontractedTranslator());
+ this.addChangeListener(function() {
+ assertNotEquals(null, this.manager.getExpandingTranslator());
+ assertEquals('en-US-comp8', this.manager.getDefaultTranslator().table.id);
+ assertEquals(null, this.manager.getUncontractedTranslator());
+ });
+});
+
+TEST_F('CvoxBrailleTranslatorManagerTest', 'testRefreshWithoutChange',
+ function() {
+ this.addChangeListener(function() {
+ assertNotEquals(null, this.manager.getExpandingTranslator());
+ // This works because the fake liblouis is actually not asynchonous.
+ this.manager.addChangeListener(function() {
+ assertNotReached('Refresh should not be called without a change.');
+ });
+ this.manager.refresh();
+ });
+});
+
+TEST_F('CvoxBrailleTranslatorManagerTest', 'testRefreshWithChange',
+ function() {
+ this.addChangeListener(function() {
+ assertNotEquals(null, this.manager.getExpandingTranslator());
+ this.addChangeListener(function() {
+ assertEquals('en-UEB-g2', this.manager.getDefaultTranslator().table.id);
+ assertEquals('en-US-comp8',
+ this.manager.getUncontractedTranslator().table.id);
+ });
+ localStorage['brailleTable'] = 'en-UEB-g2';
+ this.manager.refresh();
+ });
+});
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/expanding_braille_translator.js b/chromium/chrome/browser/resources/chromeos/chromevox/braille/expanding_braille_translator.js
index 6d8b9f64ca3..24b28b42e34 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/expanding_braille_translator.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/braille/expanding_braille_translator.js
@@ -9,9 +9,10 @@
goog.provide('cvox.ExpandingBrailleTranslator');
-goog.require('cvox.BrailleUtil');
goog.require('cvox.LibLouis');
goog.require('cvox.Spannable');
+goog.require('cvox.ValueSelectionSpan');
+goog.require('cvox.ValueSpan');
/**
@@ -48,7 +49,7 @@ cvox.ExpandingBrailleTranslator =
/**
* What expansion to apply to the part of the translated string marked by the
- * {@code cvox.BrailleUtil.ValueSpan} spannable.
+ * {@code cvox.ValueSpan} spannable.
* @enum {number}
*/
cvox.ExpandingBrailleTranslator.ExpansionType = {
@@ -80,7 +81,7 @@ cvox.ExpandingBrailleTranslator.ExpansionType = {
* @param {!cvox.Spannable} text Text to translate.
* @param {cvox.ExpandingBrailleTranslator.ExpansionType} expansionType
* Indicates how the text marked by a value span, if any, is expanded.
- * @param {function(!ArrayBuffer, !Array.<number>, !Array.<number>)}
+ * @param {function(!ArrayBuffer, !Array<number>, !Array<number>)}
* callback Called when the translation is done. It takes resulting
* braille cells and positional mappings as parameters.
*/
@@ -191,7 +192,7 @@ cvox.ExpandingBrailleTranslator.rangeForPosition_ = function(
* @param {!cvox.Spannable} text Text to find expansion ranges in.
* @param {cvox.ExpandingBrailleTranslator.ExpansionType} expansionType
* Indicates how the text marked up as the value is expanded.
- * @return {!Array.<cvox.ExpandingBrailleTranslator.Range_>} The calculated
+ * @return {!Array<cvox.ExpandingBrailleTranslator.Range_>} The calculated
* ranges.
* @private
*/
@@ -200,7 +201,7 @@ cvox.ExpandingBrailleTranslator.prototype.findExpandRanges_ = function(
var result = [];
if (this.uncontractedTranslator_ &&
expansionType != cvox.ExpandingBrailleTranslator.ExpansionType.NONE) {
- var value = text.getSpanInstanceOf(cvox.BrailleUtil.ValueSpan);
+ var value = text.getSpanInstanceOf(cvox.ValueSpan);
if (value) {
// The below type casts are valid because the ranges must be valid when
// the span is known to exist.
@@ -227,15 +228,14 @@ cvox.ExpandingBrailleTranslator.prototype.findExpandRanges_ = function(
* @param {cvox.Spannable} text Text to find ranges in.
* @param {number} valueStart Start of the value in {@code text}.
* @param {number} valueEnd End of the value in {@code text}.
- * @param {Array.<cvox.ExpandingBrailleTranslator.Range_>} outRanges
+ * @param {Array<cvox.ExpandingBrailleTranslator.Range_>} outRanges
* Destination for the expansion ranges. Untouched if no ranges
* are found. Note that ranges may be coalesced.
* @private
*/
cvox.ExpandingBrailleTranslator.prototype.addRangesForSelection_ = function(
text, valueStart, valueEnd, outRanges) {
- var selection = text.getSpanInstanceOf(
- cvox.BrailleUtil.ValueSelectionSpan);
+ var selection = text.getSpanInstanceOf(cvox.ValueSelectionSpan);
if (!selection) {
return;
}
@@ -282,9 +282,9 @@ cvox.ExpandingBrailleTranslator.prototype.addRangesForSelection_ = function(
* translation result is empty.
* @param {number} inputLength Length of the input to the translation.
* Used for populating {@code textToBraille} if null.
- * @param {function(!ArrayBuffer, !Array.<number>, !Array.<number>)} callback
+ * @param {function(!ArrayBuffer, !Array<number>, !Array<number>)} callback
* The callback to adapt.
- * @return {function(ArrayBuffer, Array.<number>, Array.<number>)}
+ * @return {function(ArrayBuffer, Array<number>, Array<number>)}
* An adapted version of the callback.
* @private
*/
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/expanding_braille_translator_test.unitjs b/chromium/chrome/browser/resources/chromeos/chromevox/braille/expanding_braille_translator_test.unitjs
index eeba5d9aae6..bd598dd6e27 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/expanding_braille_translator_test.unitjs
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/braille/expanding_braille_translator_test.unitjs
@@ -3,8 +3,8 @@
// found in the LICENSE file.
// Include test fixture.
-GEN_INCLUDE(['../../testing/chromevox_unittest_base.js',
- '../../testing/assert_additions.js']);
+GEN_INCLUDE(['../testing/chromevox_unittest_base.js',
+ '../testing/assert_additions.js']);
/**
* Test fixture.
@@ -18,10 +18,11 @@ CvoxExpandingBrailleTranslatorUnitTest.prototype = {
/** @override */
closureModuleDeps: [
- 'cvox.BrailleUtil',
'cvox.ExpandingBrailleTranslator',
'cvox.LibLouis',
'cvox.Spannable',
+ 'cvox.ValueSelectionSpan',
+ 'cvox.ValueSpan',
]
};
@@ -73,8 +74,8 @@ function assertArrayBufferMatches(expected, actual) {
TEST_F('CvoxExpandingBrailleTranslatorUnitTest', 'TranslationError',
function() {
- var text = new cvox.Spannable('error ok', new cvox.BrailleUtil.ValueSpan());
- text.setSpan(new cvox.BrailleUtil.ValueSelectionSpan, 0, 0);
+ var text = new cvox.Spannable('error ok', new cvox.ValueSpan());
+ text.setSpan(new cvox.ValueSelectionSpan, 0, 0);
var contractedTranslator = new FakeTranslator('c');
// Translator that always results in an error.
var uncontractedTranslator = {
@@ -199,11 +200,10 @@ function runTranslationTestVariants(testCase, contracted, valueExpansion) {
function createText(text, opt_selectionStart, opt_selectionEnd) {
var result = new cvox.Spannable(text);
- result.setSpan(
- new cvox.BrailleUtil.ValueSpan, 0, text.length);
+ result.setSpan(new cvox.ValueSpan, 0, text.length);
if (goog.isDef(opt_selectionStart)) {
result.setSpan(
- new cvox.BrailleUtil.ValueSelectionSpan,
+ new cvox.ValueSelectionSpan,
opt_selectionStart,
goog.isDef(opt_selectionEnd) ? opt_selectionEnd : opt_selectionStart);
}
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/liblouis_nacl/liblouis.js b/chromium/chrome/browser/resources/chromeos/chromevox/braille/liblouis.js
index 3bd2397a95c..f56a8a7f218 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/liblouis_nacl/liblouis.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/braille/liblouis.js
@@ -31,27 +31,13 @@ cvox.LibLouis = function(nmfPath, opt_tablesDir) {
/**
* Native Client <embed> element.
* {@code null} when no <embed> is attached to the DOM.
- * @private {NaClEmbedElement}
+ * @private {HTMLEmbedElement}
*/
this.embedElement_ = null;
/**
- * The state of the instance.
- * @private {cvox.LibLouis.InstanceState}
- */
- this.instanceState_ =
- cvox.LibLouis.InstanceState.NOT_LOADED;
-
- /**
- * Pending requests to construct translators.
- * @private {!Array.<{tableName: string,
- * callback: function(cvox.LibLouis.Translator)}>}
- */
- this.pendingTranslators_ = [];
-
- /**
* Pending RPC callbacks. Maps from message IDs to callbacks.
- * @private {!Object.<string, function(!Object)>}
+ * @private {!Object<string, function(!Object)>}
*/
this.pendingRpcCallbacks_ = {};
@@ -64,15 +50,10 @@ cvox.LibLouis = function(nmfPath, opt_tablesDir) {
/**
- * Describes the loading state of the instance.
- * @enum {number}
+ * Set to {@code true} to enable debug logging of RPC messages.
+ * @type {boolean}
*/
-cvox.LibLouis.InstanceState = {
- NOT_LOADED: 0,
- LOADING: 1,
- LOADED: 2,
- ERROR: -1
-};
+cvox.LibLouis.DEBUG = false;
/**
@@ -82,7 +63,7 @@ cvox.LibLouis.InstanceState = {
*/
cvox.LibLouis.prototype.attachToElement = function(elem) {
if (this.isAttached()) {
- throw Error('instance already attached');
+ throw Error('Instance already attached');
}
var embed = document.createElement('embed');
@@ -100,9 +81,7 @@ cvox.LibLouis.prototype.attachToElement = function(elem) {
embed.addEventListener('message', goog.bind(this.onInstanceMessage_, this),
false /* useCapture */);
elem.appendChild(embed);
-
- this.embedElement_ = /** @type {!NaClEmbedElement} */ (embed);
- this.instanceState_ = cvox.LibLouis.InstanceState.LOADING;
+ this.embedElement_ = /** @type {!HTMLEmbedElement} */ (embed);
};
@@ -116,8 +95,10 @@ cvox.LibLouis.prototype.detach = function() {
this.embedElement_.parentNode.removeChild(this.embedElement_);
this.embedElement_ = null;
- this.instanceState_ =
- cvox.LibLouis.InstanceState.NOT_LOADED;
+ for (var id in this.pendingRpcCallbacks_) {
+ this.pendingRpcCallbacks_[id]({});
+ }
+ this.pendingRpcCallbacks_ = {};
};
@@ -132,35 +113,25 @@ cvox.LibLouis.prototype.isAttached = function() {
/**
* Returns a translator for the desired table, asynchronously.
+ * This object must be attached to a document when requesting a translator.
* @param {string} tableNames Comma separated list of braille table names for
* liblouis.
* @param {function(cvox.LibLouis.Translator)} callback
* Callback which will receive the translator, or {@code null} on failure.
*/
-cvox.LibLouis.prototype.getTranslator =
- function(tableNames, callback) {
- switch (this.instanceState_) {
- case cvox.LibLouis.InstanceState.NOT_LOADED:
- case cvox.LibLouis.InstanceState.LOADING:
- this.pendingTranslators_.push(
- { tableNames: tableNames, callback: callback });
- return;
- case cvox.LibLouis.InstanceState.ERROR:
- callback(null /* translator */);
- return;
- case cvox.LibLouis.InstanceState.LOADED:
- this.rpc_('CheckTable', { 'table_names': tableNames },
- goog.bind(function(reply) {
- if (reply['success']) {
- var translator = new cvox.LibLouis.Translator(
- this, tableNames);
- callback(translator);
- } else {
- callback(null /* translator */);
- }
- }, this));
- return;
+cvox.LibLouis.prototype.getTranslator = function(tableNames, callback) {
+ if (!this.isAttached()) {
+ callback(null /* translator */);
+ return;
}
+ this.rpc_('CheckTable', { 'table_names': tableNames }, function(reply) {
+ if (reply['success']) {
+ var translator = new cvox.LibLouis.Translator(this, tableNames);
+ callback(translator);
+ } else {
+ callback(null /* translator */);
+ }
+ }.bind(this));
};
@@ -174,15 +145,14 @@ cvox.LibLouis.prototype.getTranslator =
*/
cvox.LibLouis.prototype.rpc_ =
function(command, message, callback) {
- if (this.instanceState_ !==
- cvox.LibLouis.InstanceState.LOADED) {
- throw Error('cannot send RPC: liblouis instance not loaded');
+ if (!this.isAttached()) {
+ throw Error('Cannot send RPC: liblouis instance not loaded');
}
var messageId = '' + this.nextMessageId_++;
message['message_id'] = messageId;
message['command'] = command;
var json = JSON.stringify(message);
- if (goog.DEBUG) {
+ if (cvox.LibLouis.DEBUG) {
window.console.debug('RPC -> ' + json);
}
this.embedElement_.postMessage(json);
@@ -197,11 +167,6 @@ cvox.LibLouis.prototype.rpc_ =
*/
cvox.LibLouis.prototype.onInstanceLoad_ = function(e) {
window.console.info('loaded liblouis Native Client instance');
- this.instanceState_ = cvox.LibLouis.InstanceState.LOADED;
- this.pendingTranslators_.forEach(goog.bind(function(record) {
- this.getTranslator(record.tableNames, record.callback);
- }, this));
- this.pendingTranslators_.length = 0;
};
@@ -212,11 +177,7 @@ cvox.LibLouis.prototype.onInstanceLoad_ = function(e) {
*/
cvox.LibLouis.prototype.onInstanceError_ = function(e) {
window.console.error('failed to load liblouis Native Client instance');
- this.instanceState_ = cvox.LibLouis.InstanceState.ERROR;
- this.pendingTranslators_.forEach(goog.bind(function(record) {
- this.getTranslator(record.tableNames, record.callback);
- }, this));
- this.pendingTranslators_.length = 0;
+ this.detach();
};
@@ -226,7 +187,7 @@ cvox.LibLouis.prototype.onInstanceError_ = function(e) {
* @private
*/
cvox.LibLouis.prototype.onInstanceMessage_ = function(e) {
- if (goog.DEBUG) {
+ if (cvox.LibLouis.DEBUG) {
window.console.debug('RPC <- ' + e.data);
}
var message = /** @type {!Object} */ (JSON.parse(e.data));
@@ -272,13 +233,17 @@ cvox.LibLouis.Translator = function(instance, tableNames) {
/**
* Translates text into braille cells.
* @param {string} text Text to be translated.
- * @param {function(ArrayBuffer, Array.<number>, Array.<number>)} callback
+ * @param {function(ArrayBuffer, Array<number>, Array<number>)} callback
* Callback for result. Takes 3 parameters: the resulting cells,
* mapping from text to braille positions and mapping from braille to
* text positions. If translation fails for any reason, all parameters are
* {@code null}.
*/
cvox.LibLouis.Translator.prototype.translate = function(text, callback) {
+ if (!this.instance_.isAttached()) {
+ callback(null /*cells*/, null /*textToBraille*/, null /*brailleToText*/);
+ return;
+ }
var message = { 'table_names': this.tableNames_, 'text': text };
this.instance_.rpc_('Translate', message, function(reply) {
var cells = null;
@@ -309,6 +274,10 @@ cvox.LibLouis.Translator.prototype.translate = function(text, callback) {
*/
cvox.LibLouis.Translator.prototype.backTranslate =
function(cells, callback) {
+ if (!this.instance_.isAttached()) {
+ callback(null /*text*/);
+ return;
+ }
if (cells.byteLength == 0) {
// liblouis doesn't handle empty input, so handle that trivially
// here.
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/braille/liblouis_test.extjs b/chromium/chrome/browser/resources/chromeos/chromevox/braille/liblouis_test.extjs
new file mode 100644
index 00000000000..38d1e4b92db
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/braille/liblouis_test.extjs
@@ -0,0 +1,168 @@
+// 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 Tests for the liblouis Native Client wrapper, as seen from
+ * the JavaScript interface.
+ */
+
+// Include test fixture.
+GEN_INCLUDE(['../testing/chromevox_e2e_test_base.js',
+ '../testing/assert_additions.js']);
+
+/**
+ * @constructor
+ * @extends {ChromeVoxE2ETest}
+ */
+function CvoxLibLouisTest() {
+ ChromeVoxE2ETest.call(this);
+}
+
+CvoxLibLouisTest.prototype = {
+ __proto__: ChromeVoxE2ETest.prototype,
+
+ createLiblouis: function() {
+ return new cvox.LibLouis(
+ chrome.extension.getURL('braille/liblouis_nacl.nmf'),
+ chrome.extension.getURL('braille/tables'));
+ },
+
+ createAndAttachLiblouis: function() {
+ var liblouis = this.createLiblouis();
+ liblouis.attachToElement(document.body);
+ return liblouis;
+ },
+
+ withTranslator: function(liblouis, tableNames, callback) {
+ liblouis.getTranslator(tableNames, this.newCallback(callback));
+ },
+};
+
+function assertEqualsUint8Array(expected, actual) {
+ var as_array = [];
+ var uint8array = new Uint8Array(actual);
+ for (var i = 0; i < uint8array.length; ++i) {
+ as_array[i] = uint8array[i];
+ }
+ assertEqualsJSON(expected, as_array);
+}
+
+TEST_F('CvoxLibLouisTest', 'checkAllTables', function() {
+ var liblouis = this.createAndAttachLiblouis();
+ cvox.BrailleTable.getAll(this.newCallback(function(tables) {
+ var i = 0;
+ var checkNextTable = function() {
+ var table = tables[i++];
+ if (table) {
+ this.withTranslator(liblouis, table.fileNames, function(translator) {
+ assertNotEquals(null, translator,
+ 'Table ' + table + ' should be valid');
+ checkNextTable();
+ });
+ }
+ }.bind(this);
+ checkNextTable();
+ }.bind(this)));
+});
+
+TEST_F('CvoxLibLouisTest', 'testTranslateComputerBraille', function() {
+ var liblouis = this.createAndAttachLiblouis();
+ this.withTranslator(liblouis, 'en-us-comp8.ctb', function(translator) {
+ translator.translate('Hello!', this.newCallback(
+ function(cells, textToBraille, brailleToText) {
+ assertEqualsUint8Array([0x53, 0x11, 0x07, 0x07, 0x15, 0x2e], cells);
+ assertEqualsJSON([0, 1, 2, 3, 4, 5], textToBraille);
+ assertEqualsJSON([0, 1, 2, 3, 4, 5], brailleToText);
+ }));
+ });
+});
+
+TEST_F('CvoxLibLouisTest', 'testBackTranslateComputerBraille', function() {
+ var liblouis = this.createAndAttachLiblouis();
+ this.withTranslator(liblouis, 'en-us-comp8.ctb', function(translator) {
+ var cells = new Uint8Array([0x53, 0x11, 0x07, 0x07, 0x15, 0x2e]);
+ translator.backTranslate(cells.buffer, this.newCallback(function(text) {
+ assertEquals('Hello!', text);
+ }));
+ });
+});
+
+TEST_F('CvoxLibLouisTest', 'testTranslateGermanGrade2Braille', function() {
+ var liblouis = this.createAndAttachLiblouis();
+ // This is one of the moderately large tables.
+ this.withTranslator(liblouis, 'de-de-g2.ctb', function(translator) {
+ translator.translate('München', this.newCallback(
+ function(cells, textToBraille, brailleToText) {
+ assertEqualsUint8Array([0x0d, 0x33, 0x1d, 0x39, 0x09], cells);
+ assertEqualsJSON([0, 1, 2, 3, 3, 4, 4], textToBraille);
+ assertEqualsJSON([0, 1, 2, 3, 5], brailleToText);
+ }));
+ });
+});
+
+TEST_F('CvoxLibLouisTest', 'testBackTranslateGermanComputerBraille', function() {
+ var liblouis = this.createAndAttachLiblouis();
+ this.withTranslator(liblouis, 'de-de-comp8.ctb', function(translator) {
+ var cells = new Uint8Array([0xb3]);
+ translator.backTranslate(cells.buffer, this.newCallback(function(text) {
+ assertEquals('ü', text);
+ }));
+ });
+});
+
+TEST_F('CvoxLibLouisTest', 'testBackTranslateEmptyCells', function() {
+ var liblouis = this.createAndAttachLiblouis();
+ this.withTranslator(liblouis, 'de-de-comp8.ctb', function(translator) {
+ translator.backTranslate(
+ new Uint8Array().buffer,
+ this.newCallback(function(text) {
+ assertNotEquals(null, text);
+ assertEquals(0, text.length);
+ }));
+ });
+});
+
+TEST_F('CvoxLibLouisTest', 'testGetTranslatorBeforeAttach', function() {
+ var liblouis = this.createLiblouis();
+ assertFalse(liblouis.isAttached());
+ this.withTranslator(liblouis, 'en-us-comp8.ctb', function(translator) {
+ assertEquals(null, translator);
+ });
+});
+
+TEST_F('CvoxLibLouisTest', 'testGetInvalidTranslator', function() {
+ var liblouis = this.createAndAttachLiblouis();
+ this.withTranslator(liblouis, 'nonexistant-table', function(translator) {
+ assertEquals(null, translator);
+ });
+});
+
+TEST_F('CvoxLibLouisTest', 'testTranslateAfterDetach', function() {
+ var liblouis = this.createAndAttachLiblouis();
+ this.withTranslator(liblouis, 'de-de-comp8.ctb', function(translator) {
+ liblouis.detach();
+ translator.translate('Hamburg', this.newCallback(
+ function(cells, textToBraille, brailleToText) {
+ assertEquals(null, cells);
+ assertEquals(null, textToBraille);
+ assertEquals(null, brailleToText);
+ }));
+ });
+});
+
+TEST_F('CvoxLibLouisTest', 'testDetachWithOutstandingCallbacks', function() {
+ var liblouis = this.createAndAttachLiblouis();
+ this.withTranslator(liblouis, 'de-de-comp8.ctb', function(translator) {
+ var called = false;
+ translator.translate('Berlin', this.newCallback(
+ function(cells, textToBraille, brailleToText) {
+ assertEquals(null, cells);
+ assertEquals(null, textToBraille);
+ assertEquals(null, brailleToText);
+ called = true;
+ }));
+ assertFalse(called);
+ liblouis.detach();
+ });
+});
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/common/nav_braille.js b/chromium/chrome/browser/resources/chromeos/chromevox/braille/nav_braille.js
index f74b63d5a06..4aaa9487ec8 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/common/nav_braille.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/braille/nav_braille.js
@@ -11,8 +11,6 @@
goog.provide('cvox.NavBraille');
-goog.require('cvox.ChromeVox');
-goog.require('cvox.CursorSelection');
goog.require('cvox.Spannable');
/**
@@ -111,11 +109,3 @@ cvox.NavBraille.prototype.toJson = function() {
endIndex: this.endIndex
};
};
-
-
-/**
- * Sends braille to the background page.
- */
-cvox.NavBraille.prototype.write = function() {
- cvox.ChromeVox.braille.write(this);
-};
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/braille/pan_strategy.js b/chromium/chrome/browser/resources/chromeos/chromevox/braille/pan_strategy.js
new file mode 100644
index 00000000000..9984b263eff
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/braille/pan_strategy.js
@@ -0,0 +1,220 @@
+// Copyright 2015 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 Logic for panning a braille display within a line of braille
+ * content that might not fit on a single display.
+ */
+
+goog.provide('cvox.PanStrategy');
+
+/**
+ * @constructor
+ *
+ * A stateful class that keeps track of the current 'viewport' of a braille
+ * display in a line of content.
+ */
+cvox.PanStrategy = function() {
+ /**
+ * @type {number}
+ * @private
+ */
+ this.displaySize_ = 0;
+ /**
+ * @type {number}
+ * @private
+ */
+ this.contentLength_ = 0;
+ /**
+ * Points before which it is desirable to break content if it doesn't fit
+ * on the display.
+ * @type {!Array<number>}
+ * @private
+ */
+ this.breakPoints_ = [];
+ /**
+ * @type {!cvox.PanStrategy.Range}
+ * @private
+ */
+ this.viewPort_ = {start: 0, end: 0};
+};
+
+/**
+ * A range used to represent the viewport with inclusive start and xclusive
+ * end position.
+ * @typedef {{start: number, end: number}}
+ */
+cvox.PanStrategy.Range;
+
+cvox.PanStrategy.prototype = {
+ /**
+ * Gets the current viewport which is never larger than the current
+ * display size and whose end points are always within the limits of
+ * the current content.
+ * @type {!cvox.PanStrategy.Range}
+ */
+ get viewPort() {
+ return this.viewPort_;
+ },
+
+ /**
+ * Sets the display size. This call may update the viewport.
+ * @param {number} size the new display size, or {@code 0} if no display is
+ * present.
+ */
+ setDisplaySize: function(size) {
+ this.displaySize_ = size;
+ this.panToPosition_(this.viewPort_.start);
+ },
+
+ /**
+ * Sets the current content that panning should happen within. This call may
+ * change the viewport.
+ * @param {!ArrayBuffer} translatedContent The new content.
+ * @param {number} targetPosition Target position. The viewport is changed
+ * to overlap this position.
+ */
+ setContent: function(translatedContent, targetPosition) {
+ this.breakPoints_ = this.calculateBreakPoints_(translatedContent);
+ this.contentLength_ = translatedContent.byteLength;
+ this.panToPosition_(targetPosition);
+ },
+
+ /**
+ * If possible, changes the viewport to a part of the line that follows
+ * the current viewport.
+ * @return {boolean} {@code true} if the viewport was changed.
+ */
+ next: function() {
+ var newStart = this.viewPort_.end;
+ var newEnd;
+ if (newStart + this.displaySize_ < this.contentLength_) {
+ newEnd = this.extendRight_(newStart);
+ } else {
+ newEnd = this.contentLength_;
+ }
+ if (newEnd > newStart) {
+ this.viewPort_ = {start: newStart, end: newEnd};
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * If possible, changes the viewport to a part of the line that precedes
+ * the current viewport.
+ * @return {boolean} {@code true} if the viewport was changed.
+ */
+ previous: function() {
+ if (this.viewPort_.start > 0) {
+ var newStart, newEnd;
+ if (this.viewPort_.start <= this.displaySize_) {
+ newStart = 0;
+ newEnd = this.extendRight_(newStart);
+ } else {
+ newEnd = this.viewPort_.start;
+ var limit = newEnd - this.displaySize_;
+ newStart = limit;
+ var pos = 0;
+ while (pos < this.breakPoints_.length &&
+ this.breakPoints_[pos] < limit) {
+ pos++;
+ }
+ if (pos < this.breakPoints_.length &&
+ this.breakPoints_[pos] < newEnd) {
+ newStart = this.breakPoints_[pos];
+ }
+ }
+ if (newStart < newEnd) {
+ this.viewPort_ = {start: newStart, end: newEnd};
+ return true;
+ }
+ }
+ return false;
+ },
+
+ /**
+ * Finds the end position for a new viewport start position, considering
+ * current breakpoints as well as display size and content length.
+ * @param {number} from Start of the region to extend.
+ * @return {number}
+ * @private
+ */
+ extendRight_: function(from) {
+ var limit = Math.min(from + this.displaySize_, this.contentLength_);
+ var pos = 0;
+ var result = limit;
+ while (pos < this.breakPoints_.length && this.breakPoints_[pos] <= from) {
+ pos++;
+ }
+ while (pos < this.breakPoints_.length && this.breakPoints_[pos] <= limit) {
+ result = this.breakPoints_[pos];
+ pos++;
+ }
+ return result;
+ },
+
+ /**
+ * Overridden by subclasses to provide breakpoints given translated
+ * braille cell content.
+ * @param {!ArrayBuffer} content New display content.
+ * @return {!Array<number>} The points before which it is desirable to break
+ * content if needed or the empty array if no points are more desirable
+ * than any position.
+ * @private
+ */
+ calculateBreakPoints_: function(content) {return [];},
+
+ /**
+ * Moves the viewport so that it overlaps a target position without taking
+ * the current viewport position into consideration.
+ * @param {number} position Target position.
+ */
+ panToPosition_: function(position) {
+ if (this.displaySize_ > 0) {
+ this.viewPort_ = {start: 0, end: 0};
+ while (this.next() && this.viewPort_.end <= position) {
+ // Nothing to do.
+ }
+ } else {
+ this.viewPort_ = {start: position, end: position};
+ }
+ },
+};
+
+/**
+ * A pan strategy that fits as much content on the display as possible, that
+ * is, it doesn't do any wrapping.
+ * @constructor
+ * @extends {cvox.PanStrategy}
+ */
+cvox.FixedPanStrategy = cvox.PanStrategy;
+/**
+ * A pan strategy that tries to wrap 'words' when breaking content.
+ * A 'word' in this context is just a chunk of non-blank braille cells
+ * delimited by blank cells.
+ * @constructor
+ * @extends {cvox.PanStrategy}
+ */
+cvox.WrappingPanStrategy = function() {
+ cvox.PanStrategy.call(this);
+};
+
+cvox.WrappingPanStrategy.prototype = {
+ __proto__: cvox.PanStrategy.prototype,
+
+ /** @override */
+ calculateBreakPoints_: function(content) {
+ var view = new Uint8Array(content);
+ var newContentLength = view.length;
+ var result = [];
+ var lastCellWasBlank = false;
+ for (var pos = 0; pos < view.length; ++pos) {
+ if (lastCellWasBlank && view[pos] != 0) {
+ result.push(pos);
+ }
+ lastCellWasBlank = (view[pos] == 0);
+ }
+ return result;
+ },
+};
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/braille/pan_strategy_test.unitjs b/chromium/chrome/browser/resources/chromeos/chromevox/braille/pan_strategy_test.unitjs
new file mode 100644
index 00000000000..3c7464888ea
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/braille/pan_strategy_test.unitjs
@@ -0,0 +1,121 @@
+// Copyright 2015 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.
+
+GEN_INCLUDE(['../testing/chromevox_unittest_base.js']);
+
+/**
+ * Test fixture.
+ * @constructor
+ * @extends {ChromeVoxUnitTestBase}
+ */
+function CvoxPanStrategyUnitTest() {}
+
+CvoxPanStrategyUnitTest.prototype = {
+ __proto__: ChromeVoxUnitTestBase.prototype,
+
+ /** @override */
+ closureModuleDeps: [
+ 'cvox.PanStrategy',
+ ],
+};
+
+/**
+ * @param {string} content String representing the content with spaces
+ * representing blank cells and any other character representing
+ * arbitrary non-blank cells.
+ */
+function createArrayBuffer(content) {
+ var result = new ArrayBuffer(content.length);
+ var view = new Uint8Array(result);
+ for (var i = 0; i < content.length; ++i) {
+ view[i] = (content[i] != ' ' ? 1 : 0);
+ }
+ return result;
+}
+
+ TEST_F('CvoxPanStrategyUnitTest', 'FixedPanning', function() {
+ var panner = new cvox.FixedPanStrategy();
+
+ panner.setDisplaySize(0);
+ panner.setContent(createArrayBuffer(''), 0);
+ assertEqualsJSON({start: 0, end: 0}, panner.viewPort);
+ assertFalse(panner.previous());
+ assertFalse(panner.next());
+
+ // 25 cells with a blank cell in the first 10 characters.
+ var content = createArrayBuffer('01234567 9012345678901234');
+ panner.setContent(content, 0);
+ assertEqualsJSON({start: 0, end: 0}, panner.viewPort);
+ assertFalse(panner.next());
+ assertFalse(panner.previous());
+
+ panner.setDisplaySize(10);
+ assertEqualsJSON({start: 0, end: 10}, panner.viewPort);
+ assertTrue(panner.next());
+ assertEqualsJSON({start: 10, end: 20}, panner.viewPort);
+ assertTrue(panner.next());
+ assertEqualsJSON({start: 20, end: 25}, panner.viewPort);
+ assertFalse(panner.next());
+ assertEqualsJSON({start: 20, end: 25}, panner.viewPort);
+ assertTrue(panner.previous());
+ assertEqualsJSON({start: 10, end: 20}, panner.viewPort);
+ assertTrue(panner.previous());
+ assertEqualsJSON({start: 0, end: 10}, panner.viewPort);
+
+ panner.setContent(content, 19);
+ assertEqualsJSON({start: 10, end: 20}, panner.viewPort);
+
+ panner.setContent(content, 20);
+ assertEqualsJSON({start: 20, end: 25}, panner.viewPort);
+
+ panner.setDisplaySize(8);
+ assertEqualsJSON({start: 16, end: 24}, panner.viewPort);
+
+ panner.viewPort_ = {start: 2, end: 10};
+ assertTrue(panner.previous());
+ assertEqualsJSON({start: 0, end: 8}, panner.viewPort);
+});
+
+TEST_F('CvoxPanStrategyUnitTest', 'WrappedPanning', function() {
+ var panner = new cvox.WrappingPanStrategy();
+
+ // 30 cells with blank cells at positions 8, 22 and 26.
+ var content = createArrayBuffer('01234567 9012345678901 345 789');
+ panner.setContent(content, 0);
+ assertEqualsJSON({start: 0, end: 0}, panner.viewPort);
+ assertFalse(panner.next());
+ assertFalse(panner.previous());
+
+ panner.setDisplaySize(10);
+ assertEqualsJSON({start: 0, end: 9}, panner.viewPort);
+ assertTrue(panner.next());
+ assertEqualsJSON({start: 9, end: 19}, panner.viewPort);
+ assertTrue(panner.next());
+ assertEqualsJSON({start: 19, end: 27}, panner.viewPort);
+ assertTrue(panner.next());
+ assertEqualsJSON({start: 27, end: 30}, panner.viewPort);
+ assertFalse(panner.next());
+ assertEqualsJSON({start: 27, end: 30}, panner.viewPort);
+ assertTrue(panner.previous());
+ assertEqualsJSON({start: 23, end: 27}, panner.viewPort);
+ assertTrue(panner.previous());
+ assertEqualsJSON({start: 13, end: 23}, panner.viewPort);
+ assertTrue(panner.previous());
+ assertEqualsJSON({start: 9, end: 13}, panner.viewPort);
+ assertTrue(panner.previous());
+ assertEqualsJSON({start: 0, end: 9}, panner.viewPort);
+
+ panner.setContent(content, 21);
+ assertEqualsJSON({start: 19, end: 27}, panner.viewPort);
+
+ panner.setContent(content, 30);
+ assertEqualsJSON({start: 27, end: 30}, panner.viewPort);
+
+ panner.setDisplaySize(8);
+ assertEqualsJSON({start: 23, end: 30}, panner.viewPort);
+
+ panner.viewPort_ = {start: 2, end: 10};
+ assertTrue(panner.previous());
+ assertEqualsJSON({start: 0, end: 8}, panner.viewPort);
+});
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/braille/spans.js b/chromium/chrome/browser/resources/chromeos/chromevox/braille/spans.js
new file mode 100644
index 00000000000..560fa0fca67
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/braille/spans.js
@@ -0,0 +1,65 @@
+// Copyright 2015 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 Objects used in spannables as annotations for ARIA values
+ * and selections.
+ */
+
+goog.provide('cvox.ValueSelectionSpan');
+goog.provide('cvox.ValueSpan');
+
+goog.require('cvox.Spannable');
+
+/**
+ * Attached to the value region of a braille spannable.
+ * @param {number} offset The offset of the span into the value.
+ * @constructor
+ */
+cvox.ValueSpan = function(offset) {
+ /**
+ * The offset of the span into the value.
+ * @type {number}
+ */
+ this.offset = offset;
+};
+
+
+/**
+ * Creates a value span from a json serializable object.
+ * @param {!Object} obj The json serializable object to convert.
+ * @return {!cvox.ValueSpan} The value span.
+ */
+cvox.ValueSpan.fromJson = function(obj) {
+ return new cvox.ValueSpan(obj.offset);
+};
+
+
+/**
+ * Converts this object to a json serializable object.
+ * @return {!Object} The JSON representation.
+ */
+cvox.ValueSpan.prototype.toJson = function() {
+ return this;
+};
+
+
+cvox.Spannable.registerSerializableSpan(
+ cvox.ValueSpan,
+ 'cvox.ValueSpan',
+ cvox.ValueSpan.fromJson,
+ cvox.ValueSpan.prototype.toJson);
+
+
+/**
+ * Attached to the selected text within a value.
+ * @constructor
+ */
+cvox.ValueSelectionSpan = function() {
+};
+
+
+cvox.Spannable.registerStatelessSerializableSpan(
+ cvox.ValueSelectionSpan,
+ 'cvox.ValueSelectionSpan');
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox.gyp b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox.gyp
index 760675fe4b8..226c35c229e 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox.gyp
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox.gyp
@@ -24,10 +24,8 @@
'type': 'none',
'dependencies': [
'chromevox_resources',
- 'chromevox1_manifest',
- 'chromevox1_guest_manifest',
- 'chromevox2_manifest',
- 'chromevox2_guest_manifest',
+ 'chromevox_manifest',
+ 'chromevox_guest_manifest',
],
},
{
@@ -157,7 +155,7 @@
'includes': ['generate_deps.gypi'],
},
{
- 'target_name': 'chromevox1_manifest',
+ 'target_name': 'chromevox_manifest',
'type': 'none',
'variables': {
'output_manifest_path': '<(chromevox_dest_dir)/manifest.json',
@@ -165,7 +163,7 @@
'includes': [ 'generate_manifest.gypi', ],
},
{
- 'target_name': 'chromevox1_guest_manifest',
+ 'target_name': 'chromevox_guest_manifest',
'type': 'none',
'variables': {
'output_manifest_path': '<(chromevox_dest_dir)/manifest_guest.json',
@@ -173,25 +171,6 @@
},
'includes': [ 'generate_manifest.gypi', ],
},
- {
- 'target_name': 'chromevox2_manifest',
- 'type': 'none',
- 'variables': {
- 'output_manifest_path': '<(chromevox_dest_dir)/manifest_next.json',
- 'is_chromevox_next': 1,
- },
- 'includes': [ 'generate_manifest.gypi', ],
- },
- {
- 'target_name': 'chromevox2_guest_manifest',
- 'type': 'none',
- 'variables': {
- 'output_manifest_path': '<(chromevox_dest_dir)/manifest_next_guest.json',
- 'is_guest_manifest': 1,
- 'is_chromevox_next': 1,
- },
- 'includes': [ 'generate_manifest.gypi', ],
- },
],
'conditions': [
['chromevox_compress_js==1', {
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/accessibility_api_handler.js b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/accessibility_api_handler.js
deleted file mode 100644
index 3667b6060c2..00000000000
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/accessibility_api_handler.js
+++ /dev/null
@@ -1,699 +0,0 @@
-// 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 Accesses Chrome's accessibility extension API and gives
- * spoken feedback for events that happen in the "Chrome of Chrome".
- */
-
-goog.provide('cvox.AccessibilityApiHandler');
-
-goog.require('cvox.AbstractEarcons');
-goog.require('cvox.AbstractTts');
-goog.require('cvox.BrailleInterface');
-goog.require('cvox.BrailleUtil');
-goog.require('cvox.ChromeVoxEditableTextBase');
-goog.require('cvox.NavBraille');
-goog.require('cvox.QueueMode');
-
-
-/**
- * The chrome.experimental.accessibility API is moving to
- * chrome.accessibilityPrivate, so provide an alias during the transition.
- *
- * TODO(dmazzoni): Remove after the stable version of Chrome no longer
- * has the experimental accessibility API.
- */
-chrome.experimental = chrome.experimental || {};
-/**
- * Fall back on the experimental API if the new name is not available.
- */
-chrome.accessibilityPrivate = chrome.accessibilityPrivate ||
- chrome.experimental.accessibility;
-
-
-/**
- * Class that adds listeners and handles events from the accessibility API.
- * @constructor
- * @implements {cvox.TtsCapturingEventListener}
- * @param {cvox.TtsInterface} tts The TTS to use for speaking.
- * @param {cvox.BrailleInterface} braille The braille interface to use for
- * brailing.
- * @param {Object} earcons The earcons object to use for playing
- * earcons.
- */
-cvox.AccessibilityApiHandler = function(tts, braille, earcons) {
- this.tts = tts;
- this.braille = braille;
- this.earcons = earcons;
- /**
- * Tracks the previous description received.
- * @type {Object}
- * @private
- */
- this.prevDescription_ = {};
- /**
- * Array of strings to speak the next time TTS is idle.
- * @type {!Array.<string>}
- * @private
- */
- this.idleSpeechQueue_ = [];
-
- try {
- chrome.accessibilityPrivate.setAccessibilityEnabled(true);
- chrome.accessibilityPrivate.setNativeAccessibilityEnabled(
- !cvox.ChromeVox.isActive);
- this.addEventListeners_();
- if (cvox.ChromeVox.isActive) {
- this.queueAlertsForActiveTab();
- }
- } catch (err) {
- console.log('Error trying to access accessibility extension api.');
- }
-};
-
-/**
- * The interface used to manage speech.
- * @type {cvox.TtsInterface}
- */
-cvox.AccessibilityApiHandler.prototype.tts = null;
-
-/**
- * The interface used to manage braille.
- * @type {cvox.BrailleInterface}
- */
-cvox.AccessibilityApiHandler.prototype.braille = null;
-
-/**
- * The object used to manage arcons.
- * @type Object
- */
-cvox.AccessibilityApiHandler.prototype.earcons = null;
-
-/**
- * The object that can describe changes and cursor movement in a generic
- * editable text field.
- * @type {Object}
- */
-cvox.AccessibilityApiHandler.prototype.editableTextHandler = null;
-
-/**
- * The name of the editable text field associated with
- * |editableTextHandler|, so we can tell when focus moves.
- * @type {string}
- */
-cvox.AccessibilityApiHandler.prototype.editableTextName = '';
-
-/**
- * The queue mode for the next focus event.
- * @type {cvox.QueueMode}
- */
-cvox.AccessibilityApiHandler.prototype.nextQueueMode = cvox.QueueMode.FLUSH;
-
-/**
- * The timeout id for the pending text changed event - the return
- * value from window.setTimeout. We need to delay text events slightly
- * and return only the last one because sometimes we get a rapid
- * succession of related events that should all be considered one
- * bulk change - in particular, autocomplete in the location bar comes
- * as multiple events in a row.
- * @type {?number}
- */
-cvox.AccessibilityApiHandler.prototype.textChangeTimeout = null;
-
-/**
- * Most controls have a "context" - the name of the window, dialog, toolbar,
- * or menu they're contained in. We announce a context once, when you
- * first enter it - and we don't announce it again when you move to something
- * else within the same context. This variable keeps track of the most
- * recent context.
- * @type {?string}
- */
-cvox.AccessibilityApiHandler.prototype.lastContext = null;
-
-/**
- * Delay in ms between when a text event is received and when it's spoken.
- * @type {number}
- * @const
- */
-cvox.AccessibilityApiHandler.prototype.TEXT_CHANGE_DELAY = 10;
-
-/**
- * ID returned from setTimeout to queue up speech on idle.
- * @type {?number}
- * @private
- */
-cvox.AccessibilityApiHandler.prototype.idleSpeechTimeout_ = null;
-
-/**
- * Milliseconds of silence to wait before considering speech to be idle.
- * @const
- */
-cvox.AccessibilityApiHandler.prototype.IDLE_SPEECH_DELAY_MS = 500;
-
-/**
- * Called to let us know that the last speech came from web, and not from
- * native UI. Clear the context and any state associated with the last
- * focused control.
- */
-cvox.AccessibilityApiHandler.prototype.setWebContext = function() {
- // This will never be spoken - it's just supposed to be a string that
- // won't match the context of the next control that gets focused.
- this.lastContext = '--internal-web--';
- this.editableTextHandler = null;
- this.editableTextName = '';
-
- if (chrome.accessibilityPrivate.setFocusRing &&
- cvox.ChromeVox.isChromeOS) {
- // Clear the focus ring.
- chrome.accessibilityPrivate.setFocusRing([]);
- }
-};
-
-/**
- * Adds event listeners.
- * @private
- */
-cvox.AccessibilityApiHandler.prototype.addEventListeners_ = function() {
- /** Alias getMsg as msg. */
- var msg = goog.bind(cvox.ChromeVox.msgs.getMsg, cvox.ChromeVox.msgs);
-
- var accessibility = chrome.accessibilityPrivate;
-
- chrome.tabs.onActivated.addListener(goog.bind(function(activeInfo) {
- if (!cvox.ChromeVox.isActive) {
- return;
- }
- chrome.tabs.get(activeInfo.tabId, goog.bind(function(tab) {
- if (tab.status == 'loading') {
- return;
- }
- this.queueAlertsForActiveTab();
- }, this));
- }, this));
-
- chrome.accessibilityPrivate.onWindowOpened.addListener(
- goog.bind(function(win) {
- if (!cvox.ChromeVox.isActive) {
- return;
- }
- this.tts.speak(win.name,
- cvox.QueueMode.FLUSH,
- cvox.AbstractTts.PERSONALITY_ANNOUNCEMENT);
- this.braille.write(cvox.NavBraille.fromText(win.name));
- // Queue the next utterance because a window opening is always followed
- // by a focus event.
- this.nextQueueMode = cvox.QueueMode.QUEUE;
- this.earcons.playEarcon(cvox.AbstractEarcons.OBJECT_OPEN);
- this.queueAlertsForActiveTab();
- }, this));
-
- chrome.accessibilityPrivate.onWindowClosed.addListener(
- goog.bind(function(win) {
- if (!cvox.ChromeVox.isActive) {
- return;
- }
- // Don't speak, just play the earcon.
- this.earcons.playEarcon(cvox.AbstractEarcons.OBJECT_CLOSE);
- }, this));
-
- chrome.accessibilityPrivate.onMenuOpened.addListener(
- goog.bind(function(menu) {
- if (!cvox.ChromeVox.isActive) {
- return;
- }
- this.tts.speak(msg('chrome_menu_opened', [menu.name]),
- cvox.QueueMode.FLUSH,
- cvox.AbstractTts.PERSONALITY_ANNOUNCEMENT);
- this.braille.write(
- cvox.NavBraille.fromText(msg('chrome_menu_opened', [menu.name])));
- this.earcons.playEarcon(cvox.AbstractEarcons.OBJECT_OPEN);
- }, this));
-
- chrome.accessibilityPrivate.onMenuClosed.addListener(
- goog.bind(function(menu) {
- if (!cvox.ChromeVox.isActive) {
- return;
- }
- // Don't speak, just play the earcon.
- this.earcons.playEarcon(cvox.AbstractEarcons.OBJECT_CLOSE);
- }, this));
-
- // systemPrivate API is only available when this extension is loaded as a
- // component extension embedded in Chrome.
- chrome.permissions.contains(
- { permissions: ['systemPrivate'] },
- goog.bind(function(result) {
- if (!result) {
- return;
- }
-
- // TODO(plundblad): Remove when the native sound is turned on by default.
- // See crbug.com:225886.
- var addOnVolumeChangedListener = goog.bind(function() {
- chrome.systemPrivate.onVolumeChanged.addListener(goog.bind(
- function(volume) {
- if (!cvox.ChromeVox.isActive) {
- return;
- }
- // Don't speak, just play the earcon.
- this.earcons.playEarcon(cvox.AbstractEarcons.TASK_SUCCESS);
- }, this));
- }, this);
- if (chrome.commandLinePrivate) {
- chrome.commandLinePrivate.hasSwitch('disable-volume-adjust-sound',
- goog.bind(function(result) {
- if (result) {
- addOnVolumeChangedListener();
- }
- }, this));
- } else {
- addOnVolumeChangedListener();
- }
-
- chrome.systemPrivate.onBrightnessChanged.addListener(
- goog.bind(
- /**
- * @param {{brightness: number, userInitiated: boolean}} brightness
- */
- function(brightness) {
- if (brightness.userInitiated) {
- this.earcons.playEarcon(cvox.AbstractEarcons.TASK_SUCCESS);
- this.tts.speak(
- msg('chrome_brightness_changed', [brightness.brightness]),
- cvox.QueueMode.FLUSH,
- cvox.AbstractTts.PERSONALITY_ANNOUNCEMENT);
- this.braille.write(cvox.NavBraille.fromText(
- msg('chrome_brightness_changed', [brightness.brightness])));
- }
- }, this));
-
- chrome.systemPrivate.onScreenUnlocked.addListener(goog.bind(function() {
- chrome.systemPrivate.getUpdateStatus(goog.bind(function(status) {
- if (!cvox.ChromeVox.isActive) {
- return;
- }
- // Speak about system update when it's ready, otherwise speak nothing.
- if (status.state == 'NeedRestart') {
- this.tts.speak(msg('chrome_system_need_restart'),
- cvox.QueueMode.FLUSH,
- cvox.AbstractTts.PERSONALITY_ANNOUNCEMENT);
- this.braille.write(
- cvox.NavBraille.fromText(msg('chrome_system_need_restart')));
- }
- }, this));
- }, this));
-
- chrome.systemPrivate.onWokeUp.addListener(goog.bind(function() {
- if (!cvox.ChromeVox.isActive) {
- return;
- }
- // Don't speak, just play the earcon.
- this.earcons.playEarcon(cvox.AbstractEarcons.OBJECT_OPEN);
- }, this));
- }, this));
-
- chrome.accessibilityPrivate.onControlFocused.addListener(
- goog.bind(this.onControlFocused, this));
-
- chrome.accessibilityPrivate.onControlAction.addListener(
- goog.bind(function(ctl) {
- if (!cvox.ChromeVox.isActive) {
- return;
- }
-
- var description = this.describe(ctl, true);
- this.tts.speak(description.utterance,
- cvox.QueueMode.FLUSH,
- description.ttsProps);
- description.braille.write();
- if (description.earcon) {
- this.earcons.playEarcon(description.earcon);
- }
- }, this));
-
- try {
- chrome.accessibilityPrivate.onControlHover.addListener(
- goog.bind(function(ctl) {
- if (!cvox.ChromeVox.isActive) {
- return;
- }
-
- var hasTouch = 'ontouchstart' in window;
- if (!hasTouch) {
- return;
- }
-
- var description = this.describe(ctl, false);
- this.tts.speak(description.utterance,
- cvox.QueueMode.FLUSH,
- description.ttsProps);
- description.braille.write();
- if (description.earcon) {
- this.earcons.playEarcon(description.earcon);
- }
- }, this));
- } catch (e) {}
-
- chrome.accessibilityPrivate.onTextChanged.addListener(
- goog.bind(function(ctl) {
- if (!cvox.ChromeVox.isActive) {
- return;
- }
-
- if (!this.editableTextHandler ||
- this.editableTextName != ctl.name ||
- this.lastContext != ctl.context) {
- // Chrome won't send a text change event on a control that isn't
- // focused. If we get a text change event and it doesn't match the
- // focused control, treat it as a focus event initially.
- this.onControlFocused(ctl);
- return;
- }
-
- // Only send the most recent text changed event - throw away anything
- // that was pending.
- if (this.textChangeTimeout) {
- window.clearTimeout(this.textChangeTimeout);
- }
-
- // Handle the text change event after a small delay, so multiple
- // events in rapid succession are handled as a single change. This is
- // specifically for the location bar with autocomplete - typing a
- // character and getting the autocompleted text and getting that
- // text selected may be three separate events.
- this.textChangeTimeout = window.setTimeout(
- goog.bind(function() {
- var textChangeEvent = new cvox.TextChangeEvent(
- ctl.details.value,
- ctl.details.selectionStart,
- ctl.details.selectionEnd,
- true); // triggered by user
- this.editableTextHandler.changed(
- textChangeEvent);
- this.describe(ctl, false).braille.write();
- }, this), this.TEXT_CHANGE_DELAY);
- }, this));
-
- this.tts.addCapturingEventListener(this);
-};
-
-/**
- * Handle the feedback when a new control gets focus.
- * @param {AccessibilityObject} ctl The focused control.
- */
-cvox.AccessibilityApiHandler.prototype.onControlFocused = function(ctl) {
- if (!cvox.ChromeVox.isActive) {
- return;
- }
-
- if (ctl.bounds &&
- chrome.accessibilityPrivate.setFocusRing &&
- cvox.ChromeVox.isChromeOS) {
- chrome.accessibilityPrivate.setFocusRing([ctl.bounds]);
- }
-
- // Call this first because it may clear this.editableTextHandler.
- var description = this.describe(ctl, false);
-
- if (ctl.type == 'textbox') {
- var start = ctl.details.selectionStart;
- var end = ctl.details.selectionEnd;
- if (start > end) {
- start = ctl.details.selectionEnd;
- end = ctl.details.selectionStart;
- }
- this.editableTextName = ctl.name;
- this.editableTextHandler =
- new cvox.ChromeVoxEditableTextBase(
- ctl.details.value,
- start,
- end,
- ctl.details.isPassword,
- this.tts);
- } else {
- this.editableTextHandler = null;
- }
-
- this.tts.speak(description.utterance,
- this.nextQueueMode,
- description.ttsProps);
- description.braille.write();
- this.nextQueueMode = cvox.QueueMode.FLUSH;
- if (description.earcon) {
- this.earcons.playEarcon(description.earcon);
- }
-};
-
-/**
- * Called when any speech starts.
- */
-cvox.AccessibilityApiHandler.prototype.onTtsStart = function() {
- if (this.idleSpeechTimeout_) {
- window.clearTimeout(this.idleSpeechTimeout_);
- }
-};
-
-/**
- * Called when any speech ends.
- */
-cvox.AccessibilityApiHandler.prototype.onTtsEnd = function() {
- if (this.idleSpeechQueue_.length > 0) {
- this.idleSpeechTimeout_ = window.setTimeout(
- goog.bind(this.onTtsIdle, this),
- this.IDLE_SPEECH_DELAY_MS);
- }
-};
-
-/**
- * Called when speech has been idle for a certain minimum delay.
- * Speaks queued messages.
- */
-cvox.AccessibilityApiHandler.prototype.onTtsIdle = function() {
- if (this.idleSpeechQueue_.length == 0) {
- return;
- }
- var utterance = this.idleSpeechQueue_.shift();
- var msg = goog.bind(cvox.ChromeVox.msgs.getMsg, cvox.ChromeVox.msgs);
- this.tts.speak(utterance,
- cvox.QueueMode.FLUSH,
- cvox.AbstractTts.PERSONALITY_ANNOUNCEMENT);
-};
-
-/**
- * Given a control received from the accessibility api, determine an
- * utterance to speak, text to braille, and an earcon to play to describe it.
- * @param {Object} control The control that had an action performed on it.
- * @param {boolean} isSelect True if the action is a select action,
- * otherwise it's a focus action.
- * @return {Object} An object containing a string field |utterance|, object
- * |ttsProps|, |braille|, and earcon |earcon|.
- */
-cvox.AccessibilityApiHandler.prototype.describe = function(control, isSelect) {
- /** Alias getMsg as msg. */
- var msg = goog.bind(cvox.ChromeVox.msgs.getMsg, cvox.ChromeVox.msgs);
-
- var s = '';
- var braille = {};
- var ttsProps = cvox.AbstractTts.PERSONALITY_ANNOUNCEMENT;
-
- var context = control.context;
- if (context && context != this.lastContext) {
- s += context + ', ';
- this.lastContext = context;
- this.editableTextHandler = null;
- }
-
- var earcon = undefined;
- var name = control.name.replace(/[_&]+/g, '').replace('...', '');
- braille.name = control.name;
- switch (control.type) {
- case 'checkbox':
- braille.roleMsg = 'input_type_checkbox';
- if (control.details.isChecked) {
- earcon = cvox.AbstractEarcons.CHECK_ON;
- s += msg('describe_checkbox_checked', [name]);
- braille.state = msg('checkbox_checked_state_brl');
- } else {
- earcon = cvox.AbstractEarcons.CHECK_OFF;
- s += msg('describe_checkbox_unchecked', [name]);
- braille.state = msg('checkbox_unchecked_state_brl');
- }
- break;
- case 'radiobutton':
- s += name;
- braille.roleMsg = 'input_type_radio';
- if (control.details.isChecked) {
- earcon = cvox.AbstractEarcons.CHECK_ON;
- s += msg('describe_radio_selected', [name]);
- braille.state = msg('radio_selected_state_brl');
- } else {
- earcon = cvox.AbstractEarcons.CHECK_OFF;
- s += msg('describe_radio_unselected', [name]);
- braille.state = msg('radio_unselected_state_brl');
- }
- break;
- case 'menu':
- s += msg('describe_menu', [name]);
- braille.roleMsg = 'aria_role_menu';
- break;
- case 'menuitem':
- s += msg(
- control.details.hasSubmenu ?
- 'describe_menu_item_with_submenu' : 'describe_menu_item', [name]);
- braille.roleMsg = 'aria_role_menuitem';
- if (control.details.hasSubmenu) {
- braille.state = msg('aria_has_submenu_brl');
- }
- break;
- case 'window':
- s += msg('describe_window', [name]);
- // No specialization for braille.
- braille.name = s;
- break;
- case 'alert':
- earcon = cvox.AbstractEarcons.ALERT_NONMODAL;
- s += msg('aria_role_alert') + ': ' + name;
- ttsProps = cvox.AbstractTts.PERSONALITY_SYSTEM_ALERT;
- braille.roleMsg = 'aria_role_alert';
- isSelect = false;
- break;
- case 'textbox':
- earcon = cvox.AbstractEarcons.EDITABLE_TEXT;
- var unnamed = name == '' ? 'unnamed_' : '';
- var type, value;
- if (control.details.isPassword) {
- type = 'password';
- braille.roleMsg = 'input_type_password';
- value = control.details.value.replace(/./g, '*');
- } else {
- type = 'textbox';
- braille.roleMsg = 'input_type_text';
- value = control.details.value;
- }
- s += msg('describe_' + unnamed + type, [value, name]);
- braille.value = cvox.BrailleUtil.createValue(
- value, control.details.selectionStart, control.details.selectionEnd);
- break;
- case 'button':
- earcon = cvox.AbstractEarcons.BUTTON;
- s += msg('describe_button', [name]);
- braille.roleMsg = 'tag_button';
- break;
- case 'statictext':
- s += control.name;
- break;
- case 'combobox':
- case 'listbox':
- earcon = cvox.AbstractEarcons.LISTBOX;
- var unnamed = name == '' ? 'unnamed_' : '';
- s += msg('describe_' + unnamed + control.type,
- [control.details.value, name]);
- braille.roleMsg = 'tag_select';
- break;
- case 'link':
- earcon = cvox.AbstractEarcons.LINK;
- s += msg('describe_link', [name]);
- braille.roleMsg = 'tag_link';
- break;
- case 'tab':
- s += msg('describe_tab', [name]);
- braille.roleMsg = 'aria_role_tab';
- break;
- case 'slider':
- s += msg('describe_slider', [control.details.stringValue, name]);
- braille.value = cvox.BrailleUtil.createValue(control.details.stringValue);
- braille.roleMsg = 'aria_role_slider';
- break;
- case 'treeitem':
- if (this.prevDescription_ &&
- this.prevDescription_.details &&
- goog.isDef(control.details.itemDepth) &&
- this.prevDescription_.details.itemDepth !=
- control.details.itemDepth) {
- s += msg('describe_depth', [control.details.itemDepth]);
- }
- s += name + ' ' + msg('aria_role_treeitem');
- s += control.details.isItemExpanded ?
- msg('aria_expanded_true') : msg('aria_expanded_false');
-
- braille.name = Array(control.details.itemDepth).join(' ') + braille.name;
- braille.roleMsg = 'aria_role_treeitem';
- braille.state = control.details.isItemExpanded ?
- msg('aria_expanded_true_brl') : msg('aria_expanded_false_brl');
- break;
-
- default:
- s += name + ', ' + control.type;
- braille.role = control.type;
- }
-
- if (isSelect && control.type != 'slider') {
- s += msg('describe_selected');
- }
- if (control.details && control.details.itemCount >= 0) {
- s += msg('describe_index',
- [control.details.itemIndex + 1, control.details.itemCount]);
- braille.state = braille.state ? braille.state + ' ' : '';
- braille.state += msg('LIST_POSITION_BRL',
- [control.details.itemIndex + 1, control.details.itemCount]);
- }
-
- var description = {};
- description.utterance = s;
- description.ttsProps = ttsProps;
- var spannable = cvox.BrailleUtil.getTemplated(null, null, braille);
- var valueSelectionSpan = spannable.getSpanInstanceOf(
- cvox.BrailleUtil.ValueSelectionSpan);
- var brailleObj = {text: spannable};
- if (valueSelectionSpan) {
- brailleObj.startIndex = spannable.getSpanStart(valueSelectionSpan);
- brailleObj.endIndex = spannable.getSpanEnd(valueSelectionSpan);
- }
- description.braille = new cvox.NavBraille(brailleObj);
- description.earcon = earcon;
- this.prevDescription_ = control;
- return description;
-};
-
-/**
- * Queues alerts for the active tab, if any, which will be spoken
- * as soon as speech is idle.
- */
-cvox.AccessibilityApiHandler.prototype.queueAlertsForActiveTab = function() {
- this.idleSpeechQueue_.length = 0;
- var msg = goog.bind(cvox.ChromeVox.msgs.getMsg, cvox.ChromeVox.msgs);
-
- chrome.tabs.query({'active': true, 'currentWindow': true},
- goog.bind(function(tabs) {
- if (tabs.length < 1) {
- return;
- }
- chrome.accessibilityPrivate.getAlertsForTab(
- tabs[0].id, goog.bind(function(alerts) {
- if (alerts.length == 0) {
- return;
- }
-
- var utterance = '';
-
- if (alerts.length == 1) {
- utterance += msg('page_has_one_alert_singular');
- } else {
- utterance += msg('page_has_alerts_plural',
- [alerts.length]);
- }
-
- for (var i = 0; i < alerts.length; i++) {
- utterance += ' ' + alerts[i].message;
- }
-
- utterance += ' ' + msg('review_alerts');
-
- if (this.idleSpeechQueue_.indexOf(utterance) == -1) {
- this.idleSpeechQueue_.push(utterance);
- }
- }, this));
- }, this));
-};
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/background.js b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/background.js
index aaccf063697..daad1a48933 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/background.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/background.js
@@ -9,7 +9,6 @@
goog.provide('cvox.ChromeVoxBackground');
goog.require('cvox.AbstractEarcons');
-goog.require('cvox.AccessibilityApiHandler');
goog.require('cvox.BrailleBackground');
goog.require('cvox.BrailleCaptionsBackground');
goog.require('cvox.ChromeVox');
@@ -87,8 +86,6 @@ cvox.ChromeVoxBackground.prototype.init = function() {
*/
this.backgroundBraille_ = new cvox.BrailleBackground();
- this.accessibilityApiHandler_ = new cvox.AccessibilityApiHandler(
- this.tts, this.backgroundBraille_, this.earcons);
this.tabsApiHandler_ = new cvox.TabsApiHandler(
this.tts, this.backgroundBraille_, this.earcons);
@@ -97,15 +94,12 @@ cvox.ChromeVoxBackground.prototype.init = function() {
cvox.ChromeVox.braille = this.backgroundBraille_;
cvox.ChromeVox.earcons = this.earcons;
- // TODO(dtseng): Remove the second check on or after m33.
if (cvox.ChromeVox.isChromeOS &&
- chrome.accessibilityPrivate.onChromeVoxLoadStateChanged) {
- chrome.accessibilityPrivate.onChromeVoxLoadStateChanged.addListener(
- this.onLoadStateChanged);
+ chrome.accessibilityPrivate.onIntroduceChromeVox) {
+ chrome.accessibilityPrivate.onIntroduceChromeVox.addListener(
+ this.onIntroduceChromeVox);
}
- this.checkVersionNumber();
-
// Set up a message passing system for goog.provide() calls from
// within the content scripts.
chrome.extension.onMessage.addListener(
@@ -122,21 +116,14 @@ cvox.ChromeVoxBackground.prototype.init = function() {
});
var self = this;
- if (chrome.commandLinePrivate) {
- chrome.commandLinePrivate.hasSwitch('enable-chromevox-next',
- goog.bind(function(result) {
- if (result) {
- return;
- }
- // Inject the content script into all running tabs.
- chrome.windows.getAll({'populate': true}, function(windows) {
- for (var i = 0; i < windows.length; i++) {
- var tabs = windows[i].tabs;
- self.injectChromeVoxIntoTabs(tabs);
- }
- });
- }, this));
- }
+
+ // Inject the content script into all running tabs.
+ chrome.windows.getAll({'populate': true}, function(windows) {
+ for (var i = 0; i < windows.length; i++) {
+ var tabs = windows[i].tabs;
+ self.injectChromeVoxIntoTabs(tabs);
+ }
+ });
if (localStorage['active'] == 'false') {
// Warn the user when the browser first starts if ChromeVox is inactive.
@@ -154,7 +141,7 @@ cvox.ChromeVoxBackground.prototype.init = function() {
/**
* Inject ChromeVox into a tab.
- * @param {Array.<Tab>} tabs The tab where ChromeVox scripts should be injected.
+ * @param {Array<Tab>} tabs The tab where ChromeVox scripts should be injected.
*/
cvox.ChromeVoxBackground.prototype.injectChromeVoxIntoTabs = function(tabs) {
var listOfFiles;
@@ -233,9 +220,6 @@ cvox.ChromeVoxBackground.prototype.injectChromeVoxIntoTabs = function(tabs) {
*/
cvox.ChromeVoxBackground.prototype.onTtsMessage = function(msg) {
if (msg['action'] == 'speak') {
- // Tell the handler for native UI (chrome of chrome) events that
- // the last speech came from web, and not from native UI.
- this.accessibilityApiHandler_.setWebContext();
this.tts.speak(msg['text'],
/** cvox.QueueMode */msg['queueMode'],
msg['properties']);
@@ -397,14 +381,16 @@ cvox.ChromeVoxBackground.prototype.addBridgeListener = function() {
break;
case 'TTS':
if (msg['startCallbackId'] != undefined) {
- msg['properties']['startCallback'] = function() {
+ msg['properties']['startCallback'] = function(opt_cleanupOnly) {
port.postMessage({'message': 'TTS_CALLBACK',
+ 'cleanupOnly': opt_cleanupOnly,
'id': msg['startCallbackId']});
};
}
if (msg['endCallbackId'] != undefined) {
- msg['properties']['endCallback'] = function() {
+ msg['properties']['endCallback'] = function(opt_cleanupOnly) {
port.postMessage({'message': 'TTS_CALLBACK',
+ 'cleanupOnly': opt_cleanupOnly,
'id': msg['endCallbackId']});
};
}
@@ -430,67 +416,6 @@ cvox.ChromeVoxBackground.prototype.addBridgeListener = function() {
/**
- * Checks the version number. If it has changed, display release notes
- * to the user.
- */
-cvox.ChromeVoxBackground.prototype.checkVersionNumber = function() {
- // Don't update version or show release notes if the current tab is within an
- // incognito window (which may occur on ChromeOS immediately after OOBE).
- if (this.isIncognito_()) {
- return;
- }
- this.localStorageVersion = localStorage['versionString'];
- this.showNotesIfNewVersion();
-};
-
-
-/**
- * Display release notes to the user.
- */
-cvox.ChromeVoxBackground.prototype.displayReleaseNotes = function() {
- chrome.tabs.create(
- {'url': 'http://chromevox.com/release_notes.html'});
-};
-
-
-/**
- * Gets the current version number from the extension manifest.
- */
-cvox.ChromeVoxBackground.prototype.showNotesIfNewVersion = function() {
- // Check version number in manifest.
- var url = chrome.extension.getURL('manifest.json');
- var xhr = new XMLHttpRequest();
- var context = this;
- xhr.onreadystatechange = function() {
- if (xhr.readyState == 4) {
- var manifest = JSON.parse(xhr.responseText);
- console.log('Version: ' + manifest.version);
-
- var shouldShowReleaseNotes =
- (context.localStorageVersion != manifest.version);
-
- // On Chrome OS, don't show the release notes the first time, only
- // after a version upgrade.
- if (navigator.userAgent.indexOf('CrOS') != -1 &&
- context.localStorageVersion == undefined) {
- shouldShowReleaseNotes = false;
- }
-
- if (shouldShowReleaseNotes) {
- context.displayReleaseNotes();
- }
-
- // Update version number in local storage
- localStorage['versionString'] = manifest.version;
- this.localStorageVersion = manifest.version;
- }
- };
- xhr.open('GET', url);
- xhr.send();
-};
-
-
-/**
* Read and apply preferences that affect the background context.
*/
cvox.ChromeVoxBackground.prototype.readPrefs = function() {
@@ -519,26 +444,16 @@ cvox.ChromeVoxBackground.prototype.isIncognito_ = function() {
};
-// TODO(dtseng): The loading param is no longer used. Remove it once the
-// upstream Chrome API changes.
/**
- * Handles the onChromeVoxLoadStateChanged event.
- * @param {boolean} loading True if ChromeVox is loading; false if it is
- * unloading.
- * @param {boolean} makeAnnouncements True if announcements should be made.
+ * Handles the onIntroduceChromeVox event.
*/
-cvox.ChromeVoxBackground.prototype.onLoadStateChanged = function(
- loading, makeAnnouncements) {
- if (loading) {
- if (makeAnnouncements) {
- cvox.ChromeVox.tts.speak(cvox.ChromeVox.msgs.getMsg('chromevox_intro'),
- cvox.QueueMode.QUEUE,
- {doNotInterrupt: true});
- cvox.ChromeVox.braille.write(cvox.NavBraille.fromText(
- cvox.ChromeVox.msgs.getMsg('intro_brl')));
- }
- }
- };
+cvox.ChromeVoxBackground.prototype.onIntroduceChromeVox = function() {
+ cvox.ChromeVox.tts.speak(cvox.ChromeVox.msgs.getMsg('chromevox_intro'),
+ cvox.QueueMode.QUEUE,
+ {doNotInterrupt: true});
+ cvox.ChromeVox.braille.write(cvox.NavBraille.fromText(
+ cvox.ChromeVox.msgs.getMsg('intro_brl')));
+};
// Create the background page object and export a function window['speak']
@@ -552,8 +467,9 @@ cvox.ChromeVoxBackground.prototype.onLoadStateChanged = function(
// Export the prefs object for access by the options page.
window['prefs'] = background.prefs;
- // Export the braille object for access by the options page.
- window['braille'] = cvox.ChromeVox.braille;
+ // Export the braille translator manager for access by the options page.
+ window['braille_translator_manager'] =
+ background.backgroundBraille_.getTranslatorManager();
// Export injection for ChromeVox Next.
cvox.ChromeVox.injectChromeVoxIntoTabs =
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/externs.js b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/externs.js
index 07e22c2fb47..fc5a149acba 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/externs.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/externs.js
@@ -47,13 +47,13 @@ chrome.accessibilityPrivate.setNativeAccessibilityEnabled = function(on) {
/**
* @param {number} tabId
- * @param {function(Array.<!Object>)} callback
+ * @param {function(Array<!Object>)} callback
*/
chrome.accessibilityPrivate.getAlertsForTab =
function(tabId, callback) {};
/**
- * @param {Array.<{left: number, top: number, width: number, height: number}>}
+ * @param {Array<{left: number, top: number, width: number, height: number}>}
* rects The bounding rects to draw focus ring(s) around, in global
* screen coordinates.
*/
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/injected_script_loader.js b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/injected_script_loader.js
index f36f9907693..1cb54c8760c 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/injected_script_loader.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/injected_script_loader.js
@@ -17,8 +17,8 @@ cvox.InjectedScriptLoader = function() { };
/**
* Loads a dictionary of file contents for Javascript files.
- * @param {Array.<string>} files A list of file names.
- * @param {function(Object.<string,string>)} done A function called when all
+ * @param {Array<string>} files A list of file names.
+ * @param {function(Object<string,string>)} done A function called when all
* the files have been loaded. Called with the code map as the first
* parameter.
*/
@@ -27,32 +27,32 @@ cvox.InjectedScriptLoader.fetchCode = function(files, done) {
var waiting = files.length;
var startTime = new Date();
var loadScriptAsCode = function(src) {
- // Load the script by fetching its source and running 'eval' on it
- // directly, with a magic comment that makes Chrome treat it like it
- // loaded normally. Wait until it's fetched before loading the
- // next script.
- var xhr = new XMLHttpRequest();
- var url = chrome.extension.getURL(src) + '?' + new Date().getTime();
- xhr.onreadystatechange = function() {
- if (xhr.readyState == 4) {
- var scriptText = xhr.responseText;
- // Add a magic comment to the bottom of the file so that
- // Chrome knows the name of the script in the JavaScript debugger.
- var debugSrc = src.replace('closure/../', '');
- // The 'chromevox' id is only used in the DevTools instead of a long
- // extension id.
- scriptText += '\n//# sourceURL= chrome-extension://chromevox/' +
- debugSrc + '\n';
- code[src] = scriptText;
- waiting--;
- if (waiting == 0) {
- done(code);
- }
+ // Load the script by fetching its source and running 'eval' on it
+ // directly, with a magic comment that makes Chrome treat it like it
+ // loaded normally. Wait until it's fetched before loading the
+ // next script.
+ var xhr = new XMLHttpRequest();
+ var url = chrome.extension.getURL(src) + '?' + new Date().getTime();
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState == 4) {
+ var scriptText = xhr.responseText;
+ // Add a magic comment to the bottom of the file so that
+ // Chrome knows the name of the script in the JavaScript debugger.
+ var debugSrc = src.replace('closure/../', '');
+ // The 'chromevox' id is only used in the DevTools instead of a long
+ // extension id.
+ scriptText += '\n//# sourceURL= chrome-extension://chromevox/' +
+ debugSrc + '\n';
+ code[src] = scriptText;
+ waiting--;
+ if (waiting == 0) {
+ done(code);
}
- };
- xhr.open('GET', url);
- xhr.send(null);
- }
+ }
+ };
+ xhr.open('GET', url);
+ xhr.send(null);
+ };
files.forEach(function(f) { loadScriptAsCode(f); });
};
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/kbexplorer.html b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/kbexplorer.html
index f45ccbc307e..468828e6fb7 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/kbexplorer.html
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/kbexplorer.html
@@ -1,3 +1,4 @@
+<!DOCTYPE HTML>
<!-- 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. -->
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/keymaps/key_map.js b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/keymaps/key_map.js
index c0bd759c6bc..5720fa8087b 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/keymaps/key_map.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/keymaps/key_map.js
@@ -27,7 +27,7 @@ goog.require('cvox.KeyUtil');
goog.require('cvox.PlatformUtil');
/**
- * @param {Array.<Object.<string,
+ * @param {Array<Object<string,
* {command: string, sequence: cvox.KeySequence}>>}
* commandsAndKeySequences An array of pairs - KeySequences and commands.
* @constructor
@@ -35,7 +35,7 @@ goog.require('cvox.PlatformUtil');
cvox.KeyMap = function(commandsAndKeySequences) {
/**
* An array of bindings - commands and KeySequences.
- * @type {Array.<Object.<string,
+ * @type {Array<Object<string,
* {command: string, sequence: cvox.KeySequence}>>}
* @private
*/
@@ -44,7 +44,7 @@ cvox.KeyMap = function(commandsAndKeySequences) {
/**
* Maps a command to a key. This optimizes the process of searching for a
* key sequence when you already know the command.
- * @type {Object.<string, cvox.KeySequence>}
+ * @type {Object<string, cvox.KeySequence>}
* @private
*/
this.commandToKey_ = {};
@@ -66,7 +66,7 @@ cvox.KeyMap.KEYMAP_PATH = 'chromevox/background/keymaps/';
* TODO(dtseng): Not really sure this belongs here, but it doesn't seem to be
* user configurable, so it doesn't make sense to json-stringify it.
* Should have class to siwtch among and manage multiple key maps.
- * @type {Object.<string, Object.<string, string>>}
+ * @type {Object<string, Object<string, string>>}
* @const
*/
cvox.KeyMap.AVAILABLE_MAP_INFO = {
@@ -101,7 +101,7 @@ cvox.KeyMap.prototype.length = function() {
/**
* Returns a copy of all KeySequences in this map.
- * @return {Array.<cvox.KeySequence>} Array of all keys.
+ * @return {Array<cvox.KeySequence>} Array of all keys.
*/
cvox.KeyMap.prototype.keys = function() {
return this.bindings_.map(function(binding) {
@@ -112,12 +112,12 @@ cvox.KeyMap.prototype.keys = function() {
/**
* Returns a collection of command, KeySequence bindings.
- * @return {Array.<Object.<string, cvox.KeySequence>>} Array of all command,
+ * @return {Array<Object<string, cvox.KeySequence>>} Array of all command,
* key bindings.
* @suppress {checkTypes} inconsistent return type
- * found : (Array.<(Object.<{command: string,
+ * found : (Array<(Object<{command: string,
* sequence: (cvox.KeySequence|null)}>|null)>|null)
- * required: (Array.<(Object.<(cvox.KeySequence|null)>|null)>|null)
+ * required: (Array<(Object<(cvox.KeySequence|null)>|null)>|null)
*/
cvox.KeyMap.prototype.bindings = function() {
return this.bindings_;
@@ -220,7 +220,7 @@ cvox.KeyMap.prototype.commandForKey = function(key) {
/**
* Gets a key given a command.
* @param {string} command The command to query.
- * @return {!Array.<cvox.KeySequence>} The keys associated with that command,
+ * @return {!Array<cvox.KeySequence>} The keys associated with that command,
* if any.
*/
cvox.KeyMap.prototype.keyForCommand = function(command) {
@@ -338,7 +338,7 @@ cvox.KeyMap.fromDefaults = function() {
cvox.KeyMap.fromJSON = function(json) {
try {
var commandsAndKeySequences =
- /** @type {Array.<Object.<string,
+ /** @type {Array<Object<string,
* {command: string, sequence: cvox.KeySequence}>>} */
(JSON.parse(json).bindings);
commandsAndKeySequences = commandsAndKeySequences.filter(function(value) {
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/mathmaps/math_map.js b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/mathmaps/math_map.js
index 410eaac9ac8..0fd5306671b 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/mathmaps/math_map.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/mathmaps/math_map.js
@@ -42,13 +42,13 @@ cvox.MathMap = function() {
var cstrValues = this.store.getDynamicConstraintValues();
/**
* Array of domain names.
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.allDomains = cstrValues.domain;
/**
* Array of style names.
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.allStyles = cstrValues.style;
};
@@ -92,7 +92,7 @@ cvox.MathMap.FUNCTIONS_PATH_ = cvox.MathMap.MATHMAP_PATH_ + 'functions/';
/**
* Array of JSON filenames containing symbol definitions for math speak.
- * @type {Array.<string>}
+ * @type {Array<string>}
* @const
* @private
*/
@@ -121,7 +121,7 @@ cvox.MathMap.SYMBOLS_FILES_ = [
/**
* Array of JSON filenames containing symbol definitions for math speak.
- * @type {Array.<string>}
+ * @type {Array<string>}
* @const
* @private
*/
@@ -146,8 +146,8 @@ cvox.MathMap.loadFile = function(file) {
/**
* Loads a list of JSON files.
- * @param {Array.<string>} files An array of valid filenames.
- * @return {Array.<string>} A string representing JSON array.
+ * @param {Array<string>} files An array of valid filenames.
+ * @return {Array<string>} A string representing JSON array.
*/
cvox.MathMap.loadFiles = function(files) {
return files.map(cvox.MathMap.loadFile);
@@ -156,8 +156,8 @@ cvox.MathMap.loadFiles = function(files) {
/**
* Creates an array of JSON objects from a list of files.
- * @param {Array.<string>} files An array of filenames.
- * @return {Array.<Object>} Array of JSON objects.
+ * @param {Array<string>} files An array of filenames.
+ * @return {Array<Object>} Array of JSON objects.
*/
cvox.MathMap.parseFiles = function(files) {
var strs = cvox.MathMap.loadFiles(files);
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/options.html b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/options.html
index 8c938fac194..58d0e7c6ab1 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/options.html
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/options.html
@@ -77,6 +77,15 @@
</p>
<select class="pref" id="brailleTable8" aria-labelledby="braille_description_8"></select>
<button id="brailleTableType"></button>
+ <div class="option">
+ <label>
+ <input id="brailleWordWrap" type="checkbox" class="checkbox pref" name="brailleWordWrap" />
+ <span class="i18n" msgid="options_braille_word_wrap">
+ Enable word wrap
+ </span>
+ </label>
+ </div>
+
<br><br>
</div>
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/options.js b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/options.js
index 461c0435642..b40df3c09fc 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/options.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/options.js
@@ -9,8 +9,8 @@
goog.provide('cvox.OptionsPage');
-goog.require('cvox.BrailleBackground');
goog.require('cvox.BrailleTable');
+goog.require('cvox.BrailleTranslatorManager');
goog.require('cvox.ChromeEarcons');
goog.require('cvox.ChromeHost');
goog.require('cvox.ChromeTts');
@@ -26,12 +26,6 @@ goog.require('cvox.PlatformFilter');
goog.require('cvox.PlatformUtil');
/**
- * This object is exported by the main background page.
- */
-window.braille;
-
-
-/**
* Class to manage the options page.
* @constructor
*/
@@ -48,7 +42,7 @@ cvox.OptionsPage.prefs;
/**
* A mapping from keycodes to their human readable text equivalents.
* This is initialized in cvox.OptionsPage.init for internationalization.
- * @type {Object.<string, string>}
+ * @type {Object<string, string>}
*/
cvox.OptionsPage.KEYCODE_TO_TEXT = {
};
@@ -56,7 +50,7 @@ cvox.OptionsPage.KEYCODE_TO_TEXT = {
/**
* A mapping from human readable text to keycode values.
* This is initialized in cvox.OptionsPage.init for internationalization.
- * @type {Object.<string, string>}
+ * @type {Object<string, string>}
*/
cvox.OptionsPage.TEXT_TO_KEYCODE = {
};
@@ -74,10 +68,13 @@ cvox.OptionsPage.init = function() {
cvox.OptionsPage.addKeys();
cvox.OptionsPage.populateVoicesSelect();
cvox.BrailleTable.getAll(function(tables) {
- /** @type {!Array.<cvox.BrailleTable.Table>} */
+ /** @type {!Array<cvox.BrailleTable.Table>} */
cvox.OptionsPage.brailleTables = tables;
cvox.OptionsPage.populateBrailleTablesSelect();
});
+ chrome.storage.local.get({'brailleWordWrap': true}, function(items) {
+ $('brailleWordWrap').checked = items.brailleWordWrap;
+ });
cvox.ChromeVox.msgs.addTranslatedMessagesToDom(document);
cvox.OptionsPage.hidePlatformSpecifics();
@@ -101,6 +98,31 @@ cvox.OptionsPage.init = function() {
$('version').textContent =
chrome.app.getDetails().version;
}
+
+ // Temporary secret way to enable ChromeVox Next for the current run of
+ // ChromeVox.
+ var next = 'next';
+ document.body.addEventListener('keypress', function(evt) {
+ if (next === undefined) {
+ return;
+ }
+ var key = String.fromCharCode(evt.charCode);
+ if (next[0] === key) {
+ next = next.slice(1);
+
+ if (next === '') {
+ cvox.OptionsPage.speak(
+ 'You are now running ChromeVox Next; open a new tab to start',
+ cvox.QueueMode.FLUSH);
+ next = undefined;
+ chrome.extension.getBackgroundPage()['global']
+ .backgroundObj.forceChromeVoxNextActive();
+ }
+ } else {
+ next = 'next';
+ }
+ return true;
+ }, true);
};
/**
@@ -360,7 +382,6 @@ cvox.OptionsPage.populateVoicesSelect = function() {
/**
* Populates the braille select control.
- * @this {cvox.OptionsPage}
*/
cvox.OptionsPage.populateBrailleTablesSelect = function() {
if (!cvox.ChromeVox.isChromeOS) {
@@ -400,9 +421,7 @@ cvox.OptionsPage.populateBrailleTablesSelect = function() {
var sel = node.options[selIndex];
localStorage['brailleTable'] = sel.id;
localStorage[node.id] = sel.id;
- /** @type {cvox.BrailleBackground} */
- var braille = chrome.extension.getBackgroundPage().braille;
- braille.refreshTranslator();
+ cvox.OptionsPage.getBrailleTranslatorManager().refresh();
};
};
@@ -441,8 +460,7 @@ cvox.OptionsPage.populateBrailleTablesSelect = function() {
tableTypeButton.textContent =
cvox.ChromeVox.msgs.getMsg('options_braille_table_type_8');
}
- var braille = chrome.extension.getBackgroundPage().braille;
- braille.refreshTranslator();
+ cvox.OptionsPage.getBrailleTranslatorManager().refresh();
};
updateTableType(false);
@@ -478,7 +496,9 @@ cvox.OptionsPage.setValue = function(element, value) {
cvox.OptionsPage.eventListener = function(event) {
window.setTimeout(function() {
var target = event.target;
- if (target.classList.contains('pref')) {
+ if (target.id == 'brailleWordWrap') {
+ chrome.storage.local.set({brailleWordWrap: target.checked});
+ } else if (target.classList.contains('pref')) {
if (target.tagName == 'INPUT' && target.type == 'checkbox') {
cvox.OptionsPage.prefs.setPref(target.name, target.checked);
} else if (target.tagName == 'INPUT' && target.type == 'radio') {
@@ -511,7 +531,7 @@ cvox.OptionsPage.eventListener = function(event) {
/**
* Refreshes all dynamic content on the page.
-This includes all key related information.
+ * This includes all key related information.
*/
cvox.OptionsPage.reset = function() {
var selectKeyMap = $('cvox_keymaps');
@@ -566,6 +586,13 @@ cvox.OptionsPage.speak = function(textString, queueMode, properties) {
speak.apply(null, arguments);
};
+/**
+ * @return {cvox.BrailleTranslatorManager}
+ */
+cvox.OptionsPage.getBrailleTranslatorManager = function() {
+ return chrome.extension.getBackgroundPage()['braille_translator_manager'];
+};
+
document.addEventListener('DOMContentLoaded', function() {
cvox.OptionsPage.init();
}, false);
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/prefs.js b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/prefs.js
index 9c29b02c5e5..15f2938a946 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/prefs.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/prefs.js
@@ -56,7 +56,7 @@ cvox.ChromeVoxPrefs = function() {
/**
* The default value of all preferences except the key map.
* @const
- * @type {Object.<string, Object>}
+ * @type {Object<string, Object>}
*/
cvox.ChromeVoxPrefs.DEFAULT_PREFS = {
'active': true,
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/tabs_api_handler.js b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/tabs_api_handler.js
index afbd98fcc03..baa004dc9cb 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/tabs_api_handler.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/tabs_api_handler.js
@@ -12,6 +12,7 @@ goog.provide('cvox.TabsApiHandler');
goog.require('cvox.AbstractEarcons');
goog.require('cvox.AbstractTts');
goog.require('cvox.BrailleInterface');
+goog.require('cvox.ChromeVox');
goog.require('cvox.NavBraille');
@@ -31,7 +32,7 @@ cvox.TabsApiHandler = function(tts, braille, earcons) {
this.braille_ = braille;
/** @type {cvox.AbstractEarcons} @private */
this.earcons_ = earcons;
- /** @type {function(string)} @private */
+ /** @type {function(string, Array<string>=)} @private */
this.msg_ = cvox.ChromeVox.msgs.getMsg.bind(cvox.ChromeVox.msgs);
/**
* Tracks whether the active tab has finished loading.
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/active_indicator.js b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/active_indicator.js
index 297fcf19407..f318aee63a1 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/active_indicator.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/active_indicator.js
@@ -49,7 +49,7 @@ cvox.ActiveIndicator = function() {
/**
* The current indicator rects.
- * @type {Array.<ClientRect>}
+ * @type {Array<ClientRect>}
* @private
*/
this.rects_ = null;
@@ -57,7 +57,7 @@ cvox.ActiveIndicator = function() {
/**
* The most recent target of a call to syncToNode, syncToRange, or
* syncToCursorSelection.
- * @type {Array.<Node>|Range}
+ * @type {Array<Node>|Range}
* @private
*/
this.lastSyncTarget_ = null;
@@ -65,7 +65,7 @@ cvox.ActiveIndicator = function() {
/**
* The most recent client rects for the active indicator, so we
* can tell when it moved.
- * @type {ClientRectList|Array.<ClientRect>}
+ * @type {ClientRectList|Array<ClientRect>}
* @private
*/
this.lastClientRects_ = null;
@@ -253,7 +253,7 @@ cvox.ActiveIndicator.prototype.syncToNode = function(node) {
/**
* Move the indicator to surround the given nodes.
- * @param {Array.<Node>} nodes The new targets of the indicator.
+ * @param {Array<Node>} nodes The new targets of the indicator.
*/
cvox.ActiveIndicator.prototype.syncToNodes = function(nodes) {
var clientRects = this.clientRectsFromNodes_(nodes);
@@ -362,8 +362,8 @@ cvox.ActiveIndicator.prototype.handleUpdateIndicatorIfChanged_ = function() {
};
/**
- * @param {Array.<Node>} nodes An array of nodes.
- * @return {Array.<ClientRect>} An array of client rects corresponding to
+ * @param {Array<Node>} nodes An array of nodes.
+ * @return {Array<ClientRect>} An array of client rects corresponding to
* those nodes.
* @private
*/
@@ -404,7 +404,8 @@ cvox.ActiveIndicator.prototype.clientRectsFromNodes_ = function(nodes) {
* is designed to handle the slightly non-rectangular shape of a typical
* text paragraph, but not anything more complicated than that.
*
- * @param {ClientRectList|Array.<ClientRect>} immutableRects The object rectangles.
+ * @param {ClientRectList|Array<ClientRect>} immutableRects The object
+ * rectangles.
* @param {number} margin Margin in pixels.
* @private
*/
@@ -473,7 +474,7 @@ cvox.ActiveIndicator.prototype.moveIndicator_ = function(
var now = new Date().getTime();
var delta = now - this.lastMoveTime_;
this.container_.className = 'cvox_indicator_container';
- if (!document.hasFocus() || this.blurred_) {
+ if (!cvox.ChromeVox.documentHasFocus() || this.blurred_) {
this.container_.classList.add('cvox_indicator_window_not_focused');
}
if (delta > cvox.ActiveIndicator.NORMAL_ANIM_DELAY_MS) {
@@ -605,7 +606,7 @@ cvox.ActiveIndicator.prototype.moveIndicator_ = function(
* a different color. That will make it much easier to see where each piece
* starts and ends.
*
- * @param {Array.<ClientRect>} rects The list of rects in the region.
+ * @param {Array<ClientRect>} rects The list of rects in the region.
* These should already be sorted (top to bottom and left to right).
* @param {?Element} parent If present, try to reuse the existing element
* (and animate the transition).
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/api.js b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/api.js
index e74a886f5ed..4fd18fee17c 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/api.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/api.js
@@ -48,7 +48,7 @@ if (typeof(goog) != 'undefined' && goog.require) {
* The channel between the page and content script.
* @type {MessageChannel}
*/
- var channel_;
+ var channel;
/**
* Tracks whether or not the ChromeVox API should be considered active.
@@ -64,7 +64,7 @@ if (typeof(goog) != 'undefined' && goog.require) {
/**
* Map from callback ID to callback function.
- * @type {Object.<number, function(*)>}
+ * @type {Object<number, function(*)>}
*/
var callbackMap_ = {};
@@ -72,18 +72,18 @@ if (typeof(goog) != 'undefined' && goog.require) {
* Internal function to connect to the content script.
*/
function connect_() {
- if (channel_) {
+ if (channel) {
// If there is already an existing channel, close the existing ports.
- channel_.port1.close();
- channel_.port2.close();
- channel_ = null;
+ channel.port1.close();
+ channel.port2.close();
+ channel = null;
}
- channel_ = new MessageChannel();
- window.postMessage(PORT_SETUP_MSG, [channel_.port2], '*');
- channel_.port1.onmessage = function(event) {
+ channel = new MessageChannel();
+ window.postMessage(PORT_SETUP_MSG, [channel.port2], '*');
+ channel.port1.onmessage = function(event) {
if (event.data == DISCONNECT_MSG) {
- channel_ = null;
+ channel = null;
}
try {
var message = JSON.parse(event.data);
@@ -111,7 +111,7 @@ if (typeof(goog) != 'undefined' && goog.require) {
}
message['args'] = [id].concat(message['args']);
callbackMap_[id] = callback;
- channel_.port1.postMessage(JSON.stringify(message));
+ channel.port1.postMessage(JSON.stringify(message));
}
/**
@@ -147,9 +147,9 @@ if (typeof(goog) != 'undefined' && goog.require) {
*
* @type {*}
*/
- var implementation_ = null;
+ var implementation = null;
if (typeof(cvox.ApiImplementation) != 'undefined') {
- implementation_ = cvox.ApiImplementation;
+ implementation = cvox.ApiImplementation;
}
@@ -165,7 +165,7 @@ if (typeof(goog) != 'undefined' && goog.require) {
*/
cvox.Api.internalEnable = function() {
isActive_ = true;
- if (!implementation_) {
+ if (!implementation) {
connect_();
}
var event = document.createEvent('UIEvents');
@@ -179,7 +179,7 @@ if (typeof(goog) != 'undefined' && goog.require) {
*/
cvox.Api.internalDisable = function() {
isActive_ = false;
- channel_ = null;
+ channel = null;
var event = document.createEvent('UIEvents');
event.initEvent('chromeVoxUnloaded', true, false);
document.dispatchEvent(event);
@@ -196,10 +196,10 @@ if (typeof(goog) != 'undefined' && goog.require) {
* @return {boolean} True if ChromeVox is currently active.
*/
cvox.Api.isChromeVoxActive = function() {
- if (implementation_) {
+ if (implementation) {
return isActive_;
}
- return !!channel_;
+ return !!channel;
};
/**
@@ -214,8 +214,8 @@ if (typeof(goog) != 'undefined' && goog.require) {
return;
}
- if (implementation_) {
- implementation_.speak(textString, queueMode, properties);
+ if (implementation) {
+ implementation.speak(textString, queueMode, properties);
} else {
var message = {
'cmd': 'speak',
@@ -237,8 +237,8 @@ if (typeof(goog) != 'undefined' && goog.require) {
return;
}
- if (implementation_) {
- implementation_.speak(cvox.DomUtil.getName(targetNode),
+ if (implementation) {
+ implementation.speak(cvox.DomUtil.getName(targetNode),
queueMode, properties);
} else {
var message = {
@@ -258,13 +258,13 @@ if (typeof(goog) != 'undefined' && goog.require) {
return;
}
- if (implementation_) {
- implementation_.stop();
+ if (implementation) {
+ implementation.stop();
} else {
var message = {
'cmd': 'stop'
};
- channel_.port1.postMessage(JSON.stringify(message));
+ channel.port1.postMessage(JSON.stringify(message));
}
};
@@ -309,14 +309,14 @@ if (typeof(goog) != 'undefined' && goog.require) {
if (!cvox.Api.isChromeVoxActive()) {
return;
}
- if (implementation_) {
- implementation_.playEarcon(earcon);
+ if (implementation) {
+ implementation.playEarcon(earcon);
} else {
var message = {
'cmd': 'playEarcon',
'args': [earcon]
};
- channel_.port1.postMessage(JSON.stringify(message));
+ channel.port1.postMessage(JSON.stringify(message));
}
};
@@ -335,14 +335,14 @@ if (typeof(goog) != 'undefined' && goog.require) {
return;
}
- if (implementation_) {
- implementation_.syncToNode(targetNode, speakNode);
+ if (implementation) {
+ implementation.syncToNode(targetNode, speakNode);
} else {
var message = {
'cmd': 'syncToNodeRef',
'args': [cvox.ApiUtils.makeNodeReference(targetNode), speakNode]
};
- channel_.port1.postMessage(JSON.stringify(message));
+ channel.port1.postMessage(JSON.stringify(message));
}
};
@@ -356,7 +356,7 @@ if (typeof(goog) != 'undefined' && goog.require) {
return;
}
- if (implementation_) {
+ if (implementation) {
callback(cvox.ChromeVox.navigationManager.getCurrentNode());
} else {
callAsync_({'cmd': 'getCurrentNode'}, function(response) {
@@ -371,7 +371,7 @@ if (typeof(goog) != 'undefined' && goog.require) {
*
* @param {Node} targetNode The node that the NodeDescriptions should be
* spoken using the given NodeDescriptions.
- * @param {Array.<Object>} nodeDescriptions The Array of
+ * @param {Array<Object>} nodeDescriptions The Array of
* NodeDescriptions for the given node.
*/
cvox.Api.setSpeechForNode = function(targetNode, nodeDescriptions) {
@@ -392,14 +392,14 @@ if (typeof(goog) != 'undefined' && goog.require) {
return;
}
- if (implementation_) {
+ if (implementation) {
cvox.DomUtil.clickElem(targetElement, shiftKey, true);
} else {
var message = {
'cmd': 'clickNodeRef',
'args': [cvox.ApiUtils.makeNodeReference(targetElement), shiftKey]
};
- channel_.port1.postMessage(JSON.stringify(message));
+ channel.port1.postMessage(JSON.stringify(message));
}
};
@@ -412,7 +412,7 @@ if (typeof(goog) != 'undefined' && goog.require) {
if (!cvox.Api.isChromeVoxActive() || !callback) {
return;
}
- if (implementation_) {
+ if (implementation) {
callback(cvox.BuildInfo.build);
} else {
callAsync_({'cmd': 'getBuild'}, function(response) {
@@ -431,7 +431,7 @@ if (typeof(goog) != 'undefined' && goog.require) {
if (!cvox.Api.isChromeVoxActive() || !callback) {
return;
}
- if (implementation_) {
+ if (implementation) {
callback(cvox.ChromeVox.version + '');
} else {
callAsync_({'cmd': 'getVersion'}, function(response) {
@@ -442,13 +442,13 @@ if (typeof(goog) != 'undefined' && goog.require) {
/**
* Returns the key codes of the ChromeVox modifier keys.
- * @param {function(Array.<number>)} callback Function to receive the keys.
+ * @param {function(Array<number>)} callback Function to receive the keys.
*/
cvox.Api.getCvoxModifierKeys = function(callback) {
if (!cvox.Api.isChromeVoxActive() || !callback) {
return;
}
- if (implementation_) {
+ if (implementation) {
callback(cvox.KeyUtil.cvoxModKeyCodes());
} else {
callAsync_({'cmd': 'getCvoxModKeys'}, function(response) {
@@ -481,7 +481,7 @@ if (typeof(goog) != 'undefined' && goog.require) {
return;
}
- if (implementation_) {
+ if (implementation) {
var keySeq = cvox.KeyUtil.keyEventToKeySequence(keyEvent);
callback(cvox.ChromeVoxKbHandler.handlerKeyMap.hasKey(keySeq));
} else {
@@ -512,14 +512,14 @@ if (typeof(goog) != 'undefined' && goog.require) {
return;
}
- if (implementation_) {
- implementation_.setKeyEcho(keyEcho);
+ if (implementation) {
+ implementation.setKeyEcho(keyEcho);
} else {
var message = {
'cmd': 'setKeyEcho',
'args': [keyEcho]
};
- channel_.port1.postMessage(JSON.stringify(message));
+ channel.port1.postMessage(JSON.stringify(message));
}
};
@@ -552,11 +552,11 @@ if (typeof(goog) != 'undefined' && goog.require) {
}
var constraintList = Array.prototype.slice.call(arguments, 4);
var args = [name, dynamic, action, prec].concat(constraintList);
- if (implementation_) {
- implementation_.Math.defineRule.apply(implementation_.Math, args);
+ if (implementation) {
+ implementation.Math.defineRule.apply(implementation.Math, args);
} else {
var msg = {'cmd': 'Math.defineRule', args: args};
- channel_.port1.postMessage(JSON.stringify(msg));
+ channel.port1.postMessage(JSON.stringify(msg));
}
};
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/api_implementation.js b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/api_implementation.js
index 0db02e9e250..cd780970ea1 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/api_implementation.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/api_implementation.js
@@ -48,10 +48,10 @@ cvox.ApiImplementation.init = function(opt_onload) {
scripts.push(cvox.ChromeVox.host.getApiSrc());
scripts.push(cvox.ApiImplementation.siteSpecificScriptLoader);
- var apiScript = cvox.ScriptInstaller.installScript(scripts,
+ var didInstall = cvox.ScriptInstaller.installScript(scripts,
'cvoxapi', opt_onload, cvox.ApiImplementation.siteSpecificScriptBase);
- if (!apiScript) {
+ if (!didInstall) {
// If the API script is already installed, just re-enable it.
window.location.href = 'javascript:cvox.Api.internalEnable();';
}
@@ -287,7 +287,7 @@ cvox.ApiImplementation.syncToNode = function(
cvox.TtsCategory.NAV);
}
- cvox.ChromeVox.navigationManager.getBraille().write();
+ cvox.ChromeVox.braille.write(cvox.ChromeVox.navigationManager.getBraille());
cvox.ChromeVox.navigationManager.updatePosition(targetNode);
};
@@ -310,7 +310,7 @@ cvox.ApiImplementation.getCurrentNode = function(callbackId) {
* a call was made. Otherwise returns the description that the NavigationManager
* would speak.
* @param {Node} node The node for which to get the description.
- * @return {Array.<cvox.NavDescription>} The description array.
+ * @return {Array<cvox.NavDescription>} The description array.
* @private
*/
cvox.ApiImplementation.getDesc_ = function(node) {
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/console_tts.js b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/console_tts.js
index 00abb033a1c..306ab277418 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/console_tts.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/console_tts.js
@@ -42,14 +42,6 @@ cvox.ConsoleTts.prototype.speak = function(textString, queueMode, properties) {
}
logStr += ' "' + textString + '"';
window['console']['log'](logStr);
-
- if (properties && properties['startCallback'] != undefined) {
- window.console.log(' using startCallback');
- }
-
- if (properties && properties['endCallback'] != undefined) {
- window.console.log(' using endCallback');
- }
}
return this;
};
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/event_watcher.js b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/event_watcher.js
index 04aef5171de..b5638968f71 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/event_watcher.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/event_watcher.js
@@ -162,7 +162,7 @@ cvox.ChromeVoxEventWatcher.init = function(doc) {
/**
* Array of events that need to be processed.
- * @type {Array.<Event>}
+ * @type {Array<Event>}
* @private
*/
cvox.ChromeVoxEventWatcher.events_ = new Array();
@@ -189,7 +189,7 @@ cvox.ChromeVoxEventWatcher.init = function(doc) {
/**
* A list of callbacks to be called when the EventWatcher has
* completed processing all events in its queue.
- * @type {Array.<function()>}
+ * @type {Array<function()>}
* @private
*/
cvox.ChromeVoxEventWatcher.readyCallbacks_ = new Array();
@@ -218,7 +218,7 @@ cvox.ChromeVoxEventWatcher.secondPassThroughKeyUp_ = false;
cvox.ChromeVox.searchKeyHeld = false;
/**
- * The mutation observer that listens for chagnes to text controls
+ * The mutation observer that listens for changes to text controls
* that might not send other events.
* @type {MutationObserver}
* @private
@@ -252,15 +252,17 @@ cvox.ChromeVoxEventWatcher.readFrom = function(store) {
* event was received.
*
* @param {Event} evt The event to be added to the events queue.
- * @param {boolean=} opt_ignoreVisibility Whether to ignore visibility
- * checking on the document. By default, this is set to false (so an
- * invisible document would result in this event not being added).
*/
-cvox.ChromeVoxEventWatcher.addEvent = function(evt, opt_ignoreVisibility) {
+cvox.ChromeVoxEventWatcher.addEvent = function(evt) {
// Don't add any events to the events queue if ChromeVox is inactive or the
- // page is hidden unless specified to not do so.
- if (!cvox.ChromeVox.isActive ||
- (document.webkitHidden && !opt_ignoreVisibility)) {
+ // document isn't focused.
+ if (!cvox.ChromeVox.isActive || !cvox.ChromeVox.documentHasFocus()) {
+ if (evt.type == 'focus') {
+ // If it's a focus event, update the active indicator so that it
+ // properly shows and hides as focus moves to iframe and webview
+ // elements.
+ cvox.ChromeVox.navigationManager.activeIndicator.syncToNode(evt.target);
+ }
return;
}
cvox.ChromeVoxEventWatcher.events_.push(evt);
@@ -470,7 +472,7 @@ cvox.ChromeVoxEventWatcher.setLastFocusedNode_ = function(element) {
/**
* Called when there's any mutation of the document. We use this to
* handle live region updates.
- * @param {Array.<MutationRecord>} mutations The mutations.
+ * @param {Array<MutationRecord>} mutations The mutations.
* @return {boolean} True if the default action should be performed.
*/
cvox.ChromeVoxEventWatcher.mutationHandler = function(mutations) {
@@ -486,7 +488,7 @@ cvox.ChromeVoxEventWatcher.mutationHandler = function(mutations) {
var evt = new window.Event('LiveRegion');
evt.navDescriptions = navDescriptions;
evt.assertive = assertive;
- cvox.ChromeVoxEventWatcher.addEvent(evt, true);
+ cvox.ChromeVoxEventWatcher.addEvent(evt);
return true;
});
};
@@ -645,6 +647,9 @@ cvox.ChromeVoxEventWatcher.focusEventWatcher = function(evt) {
* @param {Event} evt The focus event to handle.
*/
cvox.ChromeVoxEventWatcher.focusHandler = function(evt) {
+ if (!cvox.ChromeVox.documentHasFocus()) {
+ return;
+ }
if (evt.target &&
evt.target.hasAttribute &&
evt.target.getAttribute('aria-hidden') == 'true' &&
@@ -872,6 +877,16 @@ cvox.ChromeVoxEventWatcher.changeEventWatcher = function(evt) {
* @return {boolean} True if the default action should be performed.
*/
cvox.ChromeVoxEventWatcher.clipboardEventWatcher = function(evt) {
+ // Don't announce anything unless this document has focus and the
+ // editable element that's the target of the clipboard event is visible.
+ var targetNode = /** @type {Node} */(evt.target);
+ if (!cvox.ChromeVox.documentHasFocus() ||
+ !targetNode ||
+ !cvox.DomUtil.isVisible(targetNode) ||
+ cvox.AriaUtil.isHidden(targetNode)) {
+ return true;
+ }
+
cvox.ChromeVox.tts.speak(cvox.ChromeVox.msgs.getMsg(evt.type).toLowerCase(),
cvox.QueueMode.QUEUE);
var text = '';
@@ -938,7 +953,7 @@ cvox.ChromeVoxEventWatcher.getInitialVisibility = function() {
/**
* Speaks the text of one live region.
* @param {boolean} assertive True if it's an assertive live region.
- * @param {Array.<cvox.NavDescription>} messages An array of navDescriptions
+ * @param {Array<cvox.NavDescription>} messages An array of navDescriptions
* representing the description of the live region changes.
* @private
*/
@@ -1117,19 +1132,18 @@ cvox.ChromeVoxEventWatcher.handleControlChanged = function(control) {
announceChange = true;
}
+ var activeDescendant = cvox.AriaUtil.getActiveDescendant(control);
if ((parentControl &&
parentControl != control &&
document.activeElement == control)) {
- // If focus has been set on a child of the parent control, we need to
- // sync to that node so that ChromeVox navigation will be in sync with
- // focus navigation.
+ // Sync ChromeVox to the newly selected control.
cvox.ApiImplementation.syncToNode(
- control, true,
+ activeDescendant || control, true,
cvox.ChromeVoxEventWatcher.queueMode_());
announceChange = false;
- } else if (cvox.AriaUtil.getActiveDescendant(control)) {
+ } else if (activeDescendant) {
cvox.ChromeVox.navigationManager.updateSelToArbitraryNode(
- cvox.AriaUtil.getActiveDescendant(control),
+ activeDescendant,
true);
announceChange = true;
@@ -1139,7 +1153,7 @@ cvox.ChromeVoxEventWatcher.handleControlChanged = function(control) {
cvox.ChromeVox.tts.speak(newValue,
cvox.ChromeVoxEventWatcher.queueMode_(),
null);
- cvox.NavBraille.fromText(newValue).write();
+ cvox.ChromeVox.braille.write(cvox.NavBraille.fromText(newValue));
}
};
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/event_watcher_test.unitjs b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/event_watcher_test.unitjs
index ce462921b0f..06329b5d163 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/event_watcher_test.unitjs
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/event_watcher_test.unitjs
@@ -10,7 +10,9 @@ GEN_INCLUDE(['../../testing/chromevox_unittest_base.js']);
* @constructor
* @extends {ChromeVoxUnitTestBase}
*/
-function CvoxEventWatcherUnitTest() {}
+function CvoxEventWatcherUnitTest() {
+ ChromeVoxUnitTestBase.call(this);
+}
CvoxEventWatcherUnitTest.prototype = {
__proto__: ChromeVoxUnitTestBase.prototype,
@@ -85,8 +87,7 @@ CvoxEventWatcherUnitTest.prototype = {
TEST_F('CvoxEventWatcherUnitTest', 'ButtonFocusFeedback', function() {
this.loadHtml('<div> <button id="alpha">Alpha</button> </div>');
this.setFocus('alpha');
- this.waitForCalm(this.assertSpoken, 'Alpha Button')
- .waitForCalm(testDone);
+ this.waitForCalm(this.assertSpoken, 'Alpha Button');
});
/**
@@ -106,8 +107,7 @@ TEST_F('CvoxEventWatcherUnitTest', 'FocusLinksBackwards', function() {
.waitForCalm(this.setFocus, 'l1')
.waitForCalm(this.assertSpoken,
'1 Internal link 2 Internal link 3 Internal link ' +
- '2 Internal link 1 Internal link')
- .waitForCalm(testDone);
+ '2 Internal link 1 Internal link');
});
/**
@@ -120,8 +120,7 @@ TEST_F('CvoxEventWatcherUnitTest', 'TextFocusFeedback', function() {
'</div>');
this.setFocus('mytext');
- this.waitForCalm(this.assertSpoken, 'Label Value Edit text')
- .waitForCalm(testDone);
+ this.waitForCalm(this.assertSpoken, 'Label Value Edit text');
});
/**
@@ -134,8 +133,7 @@ TEST_F('CvoxEventWatcherUnitTest', 'ContentEditableFocusFeedback', function() {
'</div>');
this.setFocus('mytext');
- this.waitForCalm(this.assertSpoken, 'Label This is editable Edit text')
- .waitForCalm(testDone);
+ this.waitForCalm(this.assertSpoken, 'Label This is editable Edit text');
});
/**
@@ -162,8 +160,7 @@ TEST_F('CvoxEventWatcherUnitTest', 'DialogFeedback', function() {
})
.waitForCalm(this.assertSpoken, 'OK Button')
.waitForCalm(this.setFocus, 'show')
- .waitForCalm(this.assertSpoken, 'Exited dialog. Show Button')
- .waitForCalm(testDone);
+ .waitForCalm(this.assertSpoken, 'Exited dialog. Show Button');
});
/**
@@ -182,8 +179,7 @@ TEST_F('CvoxEventWatcherUnitTest', 'AlertDialogFeedback', function() {
this.waitForCalm(this.assertSpoken,
'Entered dialog ' +
'Are you sure you want to install Windows? Yes Button No Button ' +
- 'No Button')
- .waitForCalm(testDone);
+ 'No Button');
});
/**
@@ -214,8 +210,7 @@ TEST_F('CvoxEventWatcherUnitTest', 'DoubleFocusAlertDialogFeedback',
.queue('Yes')
.queue('Button'))
.waitForCalm(this.setFocus, 'outside')
- .waitForCalm(this.assertSpoken, 'Exited dialog. Outside Button')
- .waitForCalm(testDone);
+ .waitForCalm(this.assertSpoken, 'Exited dialog. Outside Button');
});
/**
@@ -246,8 +241,7 @@ TEST_F('CvoxEventWatcherUnitTest', 'CloseDialogTabRecovery', function() {
.waitForCalm(this.assertSpoken, 'invalid after click')
.waitForCalm(displayNone)
.waitForCalm(this.userCommand, 'forward')
- .waitForCalm(this.assertSpoken, 'valid text after')
- .waitForCalm(testDone);
+ .waitForCalm(this.assertSpoken, 'valid text after');
});
/**
@@ -278,8 +272,7 @@ TEST_F('CvoxEventWatcherUnitTest', 'ListBoxFeedback', function() {
{ 'target': listbox,
'type': 'keydown' }));
})
- .waitForCalm(this.assertSpoken, 'Yellow 2 of 3')
- .waitForCalm(testDone);
+ .waitForCalm(this.assertSpoken, 'Yellow 2 of 3');
});
/**
@@ -302,8 +295,7 @@ TEST_F('CvoxEventWatcherUnitTest', 'ListBoxOptionFeedback', function() {
this.waitForCalm(this.assertSpoken, 'List box Yellow 2 of 3')
.waitForCalm(this.setFocus, 'red')
- .waitForCalm(this.assertSpoken, 'Red Selected 1 of 3')
- .waitForCalm(testDone);
+ .waitForCalm(this.assertSpoken, 'Red Selected 1 of 3');
});
/**
@@ -333,8 +325,7 @@ TEST_F('CvoxEventWatcherUnitTest', 'ListBoxOptionFeedbackWithFocus', function()
.waitForCalm(this.setFocus, 'green')
.waitForCalm(this.assertSpoken, 'Green 3 of 4')
.waitForCalm(this.userCommand, 'forward')
- .waitForCalm(this.assertSpoken, 'Blue 4 of 4')
- .waitForCalm(testDone);
+ .waitForCalm(this.assertSpoken, 'Blue 4 of 4');
});
/**
@@ -369,8 +360,7 @@ TEST_F('CvoxEventWatcherUnitTest', 'EditableText', function() {
.queue('abc')
.queue('Edit text')
.flush('d')
- .flush('e'))
- .waitForCalm(testDone);
+ .flush('e'));
});
/**
@@ -403,8 +393,7 @@ TEST_F('CvoxEventWatcherUnitTest', 'EditableTextListbox', function() {
input.setAttribute('aria-activedescendant', 'option1');
this.changeTextField(input, '', 0, 0, 40); // 'down'
})
- .waitForCalm(this.assertSpoken, 'First pick 1 of 2')
- .waitForCalm(testDone);
+ .waitForCalm(this.assertSpoken, 'First pick 1 of 2');
});
/**
@@ -438,8 +427,7 @@ TEST_F('CvoxEventWatcherUnitTest', 'EditableTextListboxUpdatingInput',
input.setAttribute('aria-activedescendant', 'option1');
this.changeTextField(input, 'First pick', 9, 9, 40); // 'down'
})
- .waitForCalm(this.assertSpoken, 'First pick')
- .waitForCalm(testDone);
+ .waitForCalm(this.assertSpoken, 'First pick');
});
/**
@@ -482,8 +470,7 @@ TEST_F('CvoxEventWatcherUnitTest', 'MultilineNavigation', function() {
.waitForCalm(setAreaCursor, 9)
.waitForCalm(this.assertSpoken, 'Blank')
.waitForCalm(setAreaCursor, 10)
- .waitForCalm(this.assertSpoken, 'three')
- .waitForCalm(testDone);
+ .waitForCalm(this.assertSpoken, 'three');
});
TEST_F('CvoxEventWatcherUnitTest', 'ShouldWaitToProcess', function() {
@@ -529,8 +516,7 @@ TEST_F('CvoxEventWatcherUnitTest', 'AriaHiddenFeedback', function() {
.waitForCalm(this.setFocus, 'button3')
.waitForCalm(this.assertSpoken, '')
.waitForCalm(this.setFocus, 'button4')
- .waitForCalm(this.assertSpoken, 'Button 4 Button')
- .waitForCalm(testDone);
+ .waitForCalm(this.assertSpoken, 'Button 4 Button');
});
/**
@@ -571,8 +557,7 @@ TEST_F('CvoxEventWatcherUnitTest',
'green Radio button selected green Radio button selected')
.waitForCalm(performKeyDown, 'Left') // left arrow
// Arrowed beyond beginning. Should be quiet.
- .waitForCalm(this.assertSpoken, '')
- .waitForCalm(testDone);
+ .waitForCalm(this.assertSpoken, '');
});
/**
@@ -635,8 +620,7 @@ TEST_F('CvoxEventWatcherUnitTest', 'TimeWidget', function() {
this.waitForCalm(performKeyDown, 'Down') // down arrow
.waitForCalm(performKeyUp, 'Down') // down arrow
.waitForCalm(this.assertSpoken,
- 'PM')
- .waitForCalm(testDone);
+ 'PM');
});
/**
@@ -696,8 +680,7 @@ TEST_F('CvoxEventWatcherUnitTest', 'DateWidget', function() {
.waitForCalm(performKeyDown, 'Down') // down arrow
.waitForCalm(performKeyUp, 'Down') // down arrow
.waitForCalm(this.assertSpoken,
- '1998')
- .waitForCalm(testDone);
+ '1998');
});
/**
@@ -735,14 +718,13 @@ TEST_F('CvoxEventWatcherUnitTest', 'ToggleOnKeyUp', function() {
this.waitForCalm(keyupSpaceAndMarkPressed)
.waitForCalm(this.assertSpoken, 'Pressed')
.waitForCalm(keyupSpaceAndMarkNotPressed)
- .waitForCalm(this.assertSpoken, 'Not pressed')
- .waitForCalm(testDone);
+ .waitForCalm(this.assertSpoken, 'Not pressed');
});
/**
* Exiting dialog message should not interrupt a live region.
*/
-TEST_F('CvoxEventWatcherUnitTest', 'ExitDialogWithLiveRegion', function() {
+TEST_F('CvoxEventWatcherUnitTest', 'DISABLED_ExitDialogWithLiveRegion', function() {
this.loadHtml(
'<div role="dialog" aria-label="MyAlert">' +
' <h1>Heading</h1>' +
@@ -769,6 +751,5 @@ TEST_F('CvoxEventWatcherUnitTest', 'ExitDialogWithLiveRegion', function() {
assertEquals('Exited dialog.', ulist[1]);
assertEquals('Final focus', ulist[2]);
assertEquals('Button', ulist[3]);
- testDone();
});
});
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/externs.js b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/externs.js
index 980e2671142..8be8020cda8 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/externs.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/externs.js
@@ -21,7 +21,7 @@ function MathJax() {}
MathJax.Callback;
/**
- * @param {Array.<*>} args
+ * @param {Array<*>} args
* @param {string} err
*/
MathJax.Callback.After = function(args, err) { };
@@ -37,7 +37,7 @@ MathJax.Jax;
/**
* @typedef {{inputID: string,
* spanID: number,
- * data: Array.<Object>,
+ * data: Array<Object>,
* id: string,
* texClass: string}}
*/
@@ -70,12 +70,12 @@ MathJax.Hub.getAllJax = function() { };
/**
* @type {{PreProcessor: Function,
- * MessageHook: function(string, function(Array.<string>)):
- * function(Array.<string>),
- * StartupHook: function(string, function(Array.<string>)):
- * function(Array.<string>),
- * LoadHook: function(string, function(Array.<string>)):
- * function(Array.<string>)}}
+ * MessageHook: function(string, function(Array<string>)):
+ * function(Array<string>),
+ * StartupHook: function(string, function(Array<string>)):
+ * function(Array<string>),
+ * LoadHook: function(string, function(Array<string>)):
+ * function(Array<string>)}}
*/
MathJax.Hub.Register;
@@ -147,8 +147,8 @@ MathJax.HTML;
* Creates an HTML element from a node tag, an object with attributes and an
* array of text content.
* @param {string} tag
- * @param {Object.<string, string>} attribs
- * @param {Array.<string>} text
+ * @param {Object<string, string>} attribs
+ * @param {Array<string>} text
*/
MathJax.HTML.Element = function(tag, attribs, text) { };
@@ -169,3 +169,54 @@ MathJax.InputJax.TeX;
* @typedef {Object}
*/
function mediaWiki() {}
+
+
+/**
+ * This is the definition of the type that's returned from the PDF plug-in.
+ * @constructor
+ */
+var PDFAccessibilityJSONReply = function() {};
+
+/**
+ * Whether the PDF has finished loading or not.
+ * @type {boolean}
+ */
+PDFAccessibilityJSONReply.prototype.loaded;
+
+/**
+ * Whether the PDF allows accessible text access. Unfortunately PDFs can
+ * mark themselves as not copyable even for accessibility.
+ * @type {boolean}
+ */
+PDFAccessibilityJSONReply.prototype.copyable;
+
+/**
+ * The number of pages in the PDF.
+ * @type {number}
+ */
+PDFAccessibilityJSONReply.prototype.numberOfPages;
+
+/**
+ * The height of each PDF page in points.
+ * @type {number}
+ */
+PDFAccessibilityJSONReply.prototype.height;
+
+/**
+ * The width of each PDF page in points.
+ * @type {number}
+ */
+PDFAccessibilityJSONReply.prototype.width;
+
+/**
+ * The text boxes in the PDF, this is where most of the content is returned.
+ * Each text box has a bounding box (left, top, width, height) and
+ * each of these contains an array of nodes of type 'text' or 'url'.
+ * @type {Array<
+ * {left: number, top: number, width: number, height: number,
+ * textNodes: Array<
+ * {type: string, text: string, url: string}>
+ * }>
+ * }
+ */
+PDFAccessibilityJSONReply.prototype.textBox;
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/history.js b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/history.js
index 615ee04b04d..6db9d0de41f 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/history.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/history.js
@@ -42,7 +42,7 @@ cvox.HistoryEvent = function(opt_json) {
/**
* An array of spoken output.
- * @type {Array.<string>}
+ * @type {Array<string>}
* @private
*/
this.spoken_ = [];
@@ -347,6 +347,7 @@ cvox.History.prototype.increaseOrDecreaseProperty =
cvox.History.prototype.getDefaultProperty = function(property) { };
+/** TODO: add doc comment. */
cvox.History.dumpJs = function() {
var history = cvox.History.getInstance();
history.addBigTextBox_();
@@ -355,7 +356,7 @@ cvox.History.dumpJs = function() {
/**
- * @param {Array.<string>=} opt_skipCommands
+ * @param {Array<string>=} opt_skipCommands
* @return {string} A string of Javascript output.
* @private
*/
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/init_document.js b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/init_document.js
index 411918376ed..448897e1b08 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/init_document.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/init_document.js
@@ -16,10 +16,19 @@ goog.require('cvox.ChromeVox');
goog.require('cvox.HostFactory');
goog.require('cvox.InitGlobals');
+/**
+ * The time to pause before trying again to initialize, in ms. This
+ * number starts small and keeps growing so that we don't waste CPU
+ * time on a page that takes a long time to load.
+ * @type {number}
+ * @private
+ */
cvox.ChromeVox.initTimeout_ = 100;
/**
- * Call the init function later, safely
+ * Call the init function later, safely.
+ * @param {string} reason A developer-readable string to log to the console
+ * explaining why we're trying again.
* @private
*/
cvox.ChromeVox.recallInit_ = function(reason) {
@@ -35,6 +44,11 @@ cvox.ChromeVox.recallInit_ = function(reason) {
* Initializes cvox.ChromeVox when the document is ready.
*/
cvox.ChromeVox.initDocument = function() {
+ // Don't start the content script on the ChromeVox background page.
+ if (/^chrome-extension:\/\/.*background\.html$/.test(window.location.href)) {
+ return;
+ }
+
if (!document.body) {
cvox.ChromeVox.recallInit_('ChromeVox not starting on unloaded page: ' +
document.location.href + '.');
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/init_globals.js b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/init_globals.js
index 5cc7a26408e..a8d79269c6f 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/init_globals.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/init_globals.js
@@ -17,7 +17,6 @@ goog.require('cvox.ConsoleTts');
goog.require('cvox.HostFactory');
goog.require('cvox.NavigationManager');
goog.require('cvox.Serializer');
-goog.require('cvox.SpokenMessages');
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/initial_speech.js b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/initial_speech.js
index 3e13f57f4fc..fa1fcf63f9f 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/initial_speech.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/initial_speech.js
@@ -62,7 +62,8 @@ cvox.InitialSpeech.speak = function() {
// actually happens inside of NavigationManager.reset, which doesn't get
// called until AbstractHost.onPageLoad, but we need to speak and braille the
// initial node here.
- if (document.hasFocus() && document.activeElement == document.body) {
+ if (cvox.ChromeVox.documentHasFocus() &&
+ document.activeElement == document.body) {
cvox.ChromeVox.navigationManager.syncToBeginning();
}
@@ -74,7 +75,7 @@ cvox.InitialSpeech.speak = function() {
}
// If this iframe has focus, speak and braille the current focused element.
- if (document.hasFocus()) {
+ if (cvox.ChromeVox.documentHasFocus()) {
if (!disableSpeak) {
cvox.ChromeVoxEventSuspender.withSuspendedEvents(function() {
cvox.ChromeVox.navigationManager.finishNavCommand(
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/keyboard_handler.js b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/keyboard_handler.js
index 89609ba9b5b..f486e039a03 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/keyboard_handler.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/keyboard_handler.js
@@ -44,9 +44,9 @@ cvox.ChromeVoxKbHandler.loadKeyToFunctionsTable = function(
* Converts the key bindings table into an array that is sorted by the lengths
* of the key bindings. After the sort, the key bindings that describe single
* keys will come before the key bindings that describe multiple keys.
- * @param {Object.<string, string>} keyToFunctionsTable Contains each key
+ * @param {Object<string, string>} keyToFunctionsTable Contains each key
* binding and its associated function name.
- * @return {Array.<Array.<string>>} The sorted key bindings table in
+ * @return {Array<Array<string>>} The sorted key bindings table in
* array form. Each entry in the array is itself an array containing the
* key binding and its associated function name.
* @private
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/live_regions.js b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/live_regions.js
index a08723f2e94..d4ed721a08d 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/live_regions.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/live_regions.js
@@ -49,7 +49,7 @@ cvox.LiveRegions.VISIBILITY_TIMEOUT_MS = 50;
/**
* A mapping from announced text to the time it was last spoken.
- * @type {Object.<string, Date>}
+ * @type {Object<string, Date>}
*/
cvox.LiveRegions.lastAnnouncedMap = {};
@@ -61,21 +61,13 @@ cvox.LiveRegions.lastAnnouncedMap = {};
cvox.LiveRegions.MAX_DISCARD_DUPS_MS = 2000;
/**
- * Maximum time interval in which to discard duplicate live region announcement
- * when document.webkitHidden.
- * @type {number}
- * @const
- */
-cvox.LiveRegions.HIDDEN_DOC_MAX_DISCARD_DUPS_MS = 60000;
-
-/**
* @type {Date}
*/
cvox.LiveRegions.lastAnnouncedTime = null;
/**
* Tracks nodes handled during mutation processing.
- * @type {!Array.<Node>}
+ * @type {!Array<Node>}
*/
cvox.LiveRegions.nodesAlreadyHandled = [];
@@ -90,7 +82,7 @@ cvox.LiveRegions.nodesAlreadyHandled = [];
cvox.LiveRegions.init = function(pageLoadTime, queueMode, disableSpeak) {
cvox.LiveRegions.pageLoadTime = pageLoadTime;
- if (disableSpeak || !document.hasFocus()) {
+ if (disableSpeak || !cvox.ChromeVox.documentHasFocus()) {
return false;
}
@@ -144,8 +136,8 @@ cvox.LiveRegions.init = function(pageLoadTime, queueMode, disableSpeak) {
* This function is not reentrant, it uses some global state to keep
* track of nodes it's already spoken once.
*
- * @param {Array.<MutationRecord>} mutations The mutations.
- * @param {function(boolean, Array.<cvox.NavDescription>)} handler
+ * @param {Array<MutationRecord>} mutations The mutations.
+ * @param {function(boolean, Array<cvox.NavDescription>)} handler
* A callback function that handles each live region description found.
* The function is passed a boolean indicating if the live region is
* assertive, and an array of navdescriptions to speak.
@@ -229,7 +221,7 @@ cvox.LiveRegions.processMutations = function(mutations, handler) {
* @param {Node} parent The parent node.
* @param {boolean} isRemoval True if this node was removed.
* @param {boolean} subtree True if we should check the subtree.
- * @param {function(boolean, Array.<cvox.NavDescription>)} handler
+ * @param {function(boolean, Array<cvox.NavDescription>)} handler
* Callback function to be called for each live region found.
*/
cvox.LiveRegions.handleOneChangedNode = function(
@@ -293,7 +285,7 @@ cvox.LiveRegions.handleOneChangedNode = function(
* @param {Node} node A node in a live region.
* @param {Node} liveRoot The root of the live region this node is in.
* @param {boolean} isRemoval True if this node was removed.
- * @param {function(boolean, Array.<cvox.NavDescription>)} handler
+ * @param {function(boolean, Array<cvox.NavDescription>)} handler
* Callback function to be called for each live region found.
*/
cvox.LiveRegions.announceChangeIfVisible = function(
@@ -315,7 +307,7 @@ cvox.LiveRegions.announceChangeIfVisible = function(
* @param {Node} node A node in a live region.
* @param {Node} liveRoot The root of the live region this node is in.
* @param {boolean} isRemoval True if this node was removed.
- * @param {function(boolean, Array.<cvox.NavDescription>)} handler
+ * @param {function(boolean, Array<cvox.NavDescription>)} handler
* Callback function to be called for each live region found.
*/
cvox.LiveRegions.announceChange = function(
@@ -367,14 +359,11 @@ cvox.LiveRegions.announceChange = function(
}
}
- var discardDupsMs = document.webkitHidden ?
- cvox.LiveRegions.HIDDEN_DOC_MAX_DISCARD_DUPS_MS :
- cvox.LiveRegions.MAX_DISCARD_DUPS_MS;
-
// First, evict expired entries.
var now = new Date();
for (var announced in cvox.LiveRegions.lastAnnouncedMap) {
- if (now - cvox.LiveRegions.lastAnnouncedMap[announced] > discardDupsMs) {
+ if (now - cvox.LiveRegions.lastAnnouncedMap[announced] >
+ cvox.LiveRegions.MAX_DISCARD_DUPS_MS) {
delete cvox.LiveRegions.lastAnnouncedMap[announced];
}
}
@@ -390,7 +379,7 @@ cvox.LiveRegions.announceChange = function(
cvox.LiveRegions.lastAnnouncedMap[key] = now;
var assertive = cvox.AriaUtil.getAriaLive(liveRoot) == 'assertive';
- if (cvox.Interframe.isIframe() && !document.hasFocus()) {
+ if (cvox.Interframe.isIframe() && !cvox.ChromeVox.documentHasFocus()) {
cvox.Interframe.sendMessageToParentWindow(
{'command': 'speakLiveRegion',
'content': JSON.stringify(navDescriptions),
@@ -441,7 +430,7 @@ cvox.LiveRegions.announceChange = function(
* single string, otherwise each leaf node gets its own string.
*
* @param {Node} node A node in a live region.
- * @return {Array.<cvox.NavDescription>} An array of NavDescriptions
+ * @return {Array<cvox.NavDescription>} An array of NavDescriptions
* describing atomic nodes or leaf nodes in the subtree rooted
* at this node.
*/
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/live_regions_test.unitjs b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/live_regions_test.unitjs
index 8f0e3df87d9..ded94e04be4 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/live_regions_test.unitjs
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/live_regions_test.unitjs
@@ -10,7 +10,9 @@ GEN_INCLUDE(['../../testing/chromevox_unittest_base.js']);
* @constructor
* @extends {ChromeVoxUnitTestBase}
*/
-function CvoxLiveRegionsUnitTest() {}
+function CvoxLiveRegionsUnitTest() {
+ ChromeVoxUnitTestBase.call(this);
+}
CvoxLiveRegionsUnitTest.prototype = {
__proto__: ChromeVoxUnitTestBase.prototype,
@@ -42,7 +44,6 @@ TEST_F('CvoxLiveRegionsUnitTest', 'InsertNonLiveRegion', function() {
this.waitForCalm(function() {
assertEquals(0, cvox.ChromeVoxTester.getUtteranceList().length);
- testDone();
});
});
@@ -59,7 +60,6 @@ TEST_F('CvoxLiveRegionsUnitTest', 'InsertAlertLiveRegion', function() {
var utterances = cvox.ChromeVoxTester.getUtteranceList();
assertEquals('Alpha', utterances[0]);
assertEquals('Alert', utterances[1]);
- testDone();
});
});
@@ -86,7 +86,6 @@ TEST_F('CvoxLiveRegionsUnitTest', 'RevealAlertLiveRegion', function() {
this.waitForCalm(function() {
var utterances = cvox.ChromeVoxTester.getUtteranceList();
assertEquals('I just appeared!', utterances[0]);
- testDone();
});
});
@@ -102,7 +101,6 @@ TEST_F('CvoxLiveRegionsUnitTest', 'InsertPoliteLiveRegion', function() {
this.waitForCalm(function() {
var utterances = cvox.ChromeVoxTester.getUtteranceList();
assertEquals('Beta', utterances[0]);
- testDone();
});
});
@@ -122,7 +120,6 @@ TEST_F('CvoxLiveRegionsUnitTest', 'ModifyStatusLiveRegion', function() {
this.waitForCalm(function() {
var utterances = cvox.ChromeVoxTester.getUtteranceList();
assertEquals('Delta', utterances[utterances.length - 1]);
- testDone();
});
});
});
@@ -156,7 +153,6 @@ TEST_F('CvoxLiveRegionsUnitTest', 'AddToLiveRegion', function() {
var utterances = cvox.ChromeVoxTester.getUtteranceList();
assertEquals('Eric', utterances[utterances.length - 2]);
assertEquals('Larry Sergey Eric', utterances[utterances.length - 1]);
- testDone();
});
});
});
@@ -181,7 +177,6 @@ TEST_F('CvoxLiveRegionsUnitTest', 'RemoveFromLiveRegion', function() {
var utterances = cvox.ChromeVoxTester.getUtteranceList();
assertEquals(1, utterances.length);
assertEquals('removed:, Jack', utterances[0]);
- testDone();
});
});
@@ -203,7 +198,6 @@ TEST_F('CvoxLiveRegionsUnitTest', 'ProgressBarLiveRegionEvents', function() {
this.waitForCalm(function() {
var utterances = cvox.ChromeVoxTester.getUtteranceList();
assertEquals('Progress bar 2', utterances[utterances.length - 1]);
- testDone();
});
});
@@ -251,8 +245,7 @@ TEST_F('CvoxLiveRegionsUnitTest', 'FocusTriggeredAlertLiveRegion', function() {
.categoryFlush('Address')
.queue('Edit text')
.categoryFlush('Not a valid name!')
- .queue('Alert'))
- .waitForCalm(testDone);
+ .queue('Alert'));
});
@@ -272,8 +265,7 @@ TEST_F('CvoxLiveRegionsUnitTest', 'FocusThenLiveRegion', function() {
new cvox.SpokenListBuilder()
.categoryFlush('Button To Focus')
.queue('Button')
- .categoryFlush('Live region text'))
- .waitForCalm(testDone);
+ .categoryFlush('Live region text'));
});
@@ -295,8 +287,7 @@ TEST_F('CvoxLiveRegionsUnitTest', 'LiveRegionThenFocus', function() {
new cvox.SpokenListBuilder()
.categoryFlush('Live region text')
.categoryFlush('Button To Focus')
- .queue('Button'))
- .waitForCalm(testDone);
+ .queue('Button'));
});
@@ -317,6 +308,5 @@ TEST_F('CvoxLiveRegionsUnitTest', 'TwoElementsInLiveRegion', function() {
$('hidden').style.display = 'block';
this.waitForCalm(this.assertSpokenList,
new cvox.SpokenListBuilder()
- .categoryFlush('L1, L2'))
- .waitForCalm(testDone);
+ .categoryFlush('L1, L2'));
});
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/navigation_history.js b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/navigation_history.js
index 343fc30571e..968564cdc27 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/navigation_history.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/navigation_history.js
@@ -43,7 +43,7 @@ cvox.NavigationHistory.prototype.reset_ = function() {
/**
* An array of nodes ordered from newest to oldest in the history.
* The most recent nodes are at the start of the array.
- * @type {Array.<Node>}
+ * @type {Array<Node>}
* @private
*/
this.history_ = [startNode];
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/navigation_manager.js b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/navigation_manager.js
index 2ec204e1ff4..6dc66039f69 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/navigation_manager.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/navigation_manager.js
@@ -43,6 +43,8 @@ cvox.NavigationManager = function() {
this.addInterframeListener_();
this.reset();
+
+ this.iframeRetries_ = 0;
};
/**
@@ -65,9 +67,7 @@ cvox.NavigationManager.prototype.storeOn = function(store) {
cvox.NavigationManager.prototype.readFrom = function(store) {
this.curSel_.setReversed(store['reversed']);
this.shifter_.readFrom(store);
- if (store['keepReading']) {
- this.startReading(cvox.QueueMode.FLUSH);
- }
+ this.keepReading_ = store['keepReading'];
};
/**
@@ -81,7 +81,7 @@ cvox.NavigationManager.prototype.reset = function() {
this.navSpeaker_ = new cvox.NavigationSpeaker();
/**
- * @type {!Array.<Object>}
+ * @type {!Array<Object>}
* @private
*/
this.shifterTypes_ = [cvox.NavigationShifter,
@@ -89,7 +89,7 @@ cvox.NavigationManager.prototype.reset = function() {
cvox.MathShifter];
/**
- * @type {!Array.<!cvox.AbstractShifter>}
+ * @type {!Array<!cvox.AbstractShifter>}
*/
this.shifterStack_ = [];
@@ -329,7 +329,7 @@ cvox.NavigationManager.prototype.hasNext_ = function() {
/**
* Delegates to NavigationShifter with current page state.
- * @param {function(Array.<Node>)} predicate A function taking an array
+ * @param {function(Array<Node>)} predicate A function taking an array
* of unique ancestor nodes as a parameter and returning a desired node.
* It returns null if that node can't be found.
* @param {string=} opt_predicateName The programmatic name that exists in
@@ -417,7 +417,7 @@ cvox.NavigationManager.prototype.togglePageSel = function() {
// which requires any extensive knowledge.
/**
* Delegates to NavigationShifter with the current page state.
- * @return {Array.<cvox.NavDescription>} The summary of the current position.
+ * @return {Array<cvox.NavDescription>} The summary of the current position.
*/
cvox.NavigationManager.prototype.getDescription = function() {
// Handle description of special content. Consider moving to DescriptionUtil.
@@ -633,7 +633,7 @@ cvox.NavigationManager.prototype.ensureNotSubnavigating = function() {
/**
* Delegates to NavigationSpeaker.
- * @param {Array.<cvox.NavDescription>} descriptionArray The array of
+ * @param {Array<cvox.NavDescription>} descriptionArray The array of
* NavDescriptions to speak.
* @param {cvox.QueueMode} initialQueueMode The initial queue mode.
* @param {Function} completionFunction Function to call when finished speaking.
@@ -740,7 +740,7 @@ cvox.NavigationManager.prototype.finishNavCommand = function(
null,
cvox.TtsCategory.NAV);
- this.getBraille().write();
+ cvox.ChromeVox.braille.write(this.getBraille());
this.updatePosition(this.getCurrentNode());
};
@@ -854,6 +854,11 @@ cvox.NavigationManager.prototype.isReading = function() {
cvox.NavigationManager.prototype.startCallbackReading_ =
cvox.ChromeVoxEventSuspender.withSuspendedEvents(function(queueMode) {
this.finishNavCommand('', true, queueMode, goog.bind(function() {
+ if (this.prevReadingSel_ == this.curSel_) {
+ this.stopReading();
+ return;
+ }
+ this.prevReadingSel_ = this.curSel_;
if (this.next_(true) && this.keepReading_) {
this.startCallbackReading_(cvox.QueueMode.QUEUE);
}
@@ -889,7 +894,7 @@ cvox.NavigationManager.prototype.startNonCallbackReading_ =
* Unlike getDescription, this does not shorten the position based on the
* previous position.
*
- * @return {Array.<cvox.NavDescription>} The summary of the current position.
+ * @return {Array<cvox.NavDescription>} The summary of the current position.
*/
cvox.NavigationManager.prototype.getFullDescription = function() {
if (this.pageSel_) {
@@ -946,12 +951,9 @@ cvox.NavigationManager.prototype.addInterframeListener_ = function() {
return;
}
cvox.ChromeVox.serializer.readFrom(message);
- if (self.keepReading_) {
- return;
- }
+
cvox.ChromeVoxEventSuspender.withSuspendedEvents(function() {
window.focus();
-
if (message['findNext']) {
var predicateName = message['findNext'];
var predicate = cvox.DomPredicates[predicateName];
@@ -983,6 +985,9 @@ cvox.NavigationManager.prototype.addInterframeListener_ = function() {
// Now speak what ended up being selected.
// TODO(deboer): Some of this could be moved to readFrom
self.finishNavCommand('', true);
+ if (self.keepReading_) {
+ self.startReading(cvox.QueueMode.FLUSH);
+ }
})();
});
};
@@ -1142,6 +1147,7 @@ cvox.NavigationManager.prototype.tryIframe_ = function(node) {
};
cvox.ChromeVox.serializer.storeOn(message);
cvox.Interframe.sendMessageToParentWindow(message);
+ this.keepReading_ = false;
return true;
}
@@ -1160,8 +1166,20 @@ cvox.NavigationManager.prototype.tryIframe_ = function(node) {
if (iframeId == undefined) {
iframeId = this.nextIframeId;
this.nextIframeId++;
- this.iframeIdMap[iframeId] = iframeElement;
- cvox.Interframe.sendIdToIFrame(iframeId, iframeElement);
+ cvox.Interframe.sendIdToIFrame(iframeId, iframeElement, function() {
+ this.iframeIdMap[iframeId] = iframeElement;
+ this.iframeRetries_ = 0;
+ }.bind(this));
+ }
+
+ // We never received an ack from the iframe.
+ if (!this.iframeIdMap[iframeId]) {
+ this.iframeRetries_++;
+ if (this.iframeRetries_ > 5) {
+ // Give up.
+ this.iframeRetries_ = 0;
+ return false;
+ }
}
var message = {
@@ -1170,7 +1188,6 @@ cvox.NavigationManager.prototype.tryIframe_ = function(node) {
};
cvox.ChromeVox.serializer.storeOn(message);
cvox.Interframe.sendMessageToIFrame(message, iframeElement);
-
return true;
};
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/navigation_manager_test.unitjs b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/navigation_manager_test.unitjs
index 3af5bd2bdad..c2e47233c23 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/navigation_manager_test.unitjs
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/navigation_manager_test.unitjs
@@ -10,7 +10,9 @@ GEN_INCLUDE(['../../testing/chromevox_unittest_base.js']);
* @constructor
* @extends {ChromeVoxUnitTestBase}
*/
-function CvoxNavigationManagerUnitTest() {}
+function CvoxNavigationManagerUnitTest() {
+ ChromeVoxUnitTestBase.call(this);
+}
CvoxNavigationManagerUnitTest.prototype = {
__proto__: ChromeVoxUnitTestBase.prototype,
@@ -47,7 +49,7 @@ CvoxNavigationManagerUnitTest.prototype = {
*/
checkNavSequence: function(strategies, commandsAndExpectations) {
if (strategies.length == 0)
- testDone();
+ return;
var strategy = strategies.shift();
this.waitForCalm(cvox.ChromeVoxTester.setStrategy, strategy)
@@ -322,7 +324,6 @@ TEST_F('CvoxNavigationManagerUnitTest', 'FindNextHeading', function() {
this.waitForCalm(this.userCommand, 'backward');
this.waitForCalm(this.assertSpoken, 'Some text.');
- this.waitForCalm(testDone);
});
@@ -1266,8 +1267,7 @@ TEST_F('CvoxNavigationManagerUnitTest', 'ContinuousReading', function() {
.waitForCalm(cvox.ChromeVoxTester.syncToFirstNode)
.waitForCalm(cvox.ChromeVoxTester.readFromHere)
.waitForCalm(this.assertSpoken,
- 'Before Some text. First Second Third Internal link After')
- .waitForCalm(testDone);
+ 'Before Some text. First Second Third Internal link After');
});
@@ -1391,6 +1391,5 @@ TEST_F('CvoxNavigationManagerUnitTest', 'AriaMathRoles', function() {
this.waitForCalm(this.userCommand, 'forward')
.waitForCalm(this.assertSpoken, 'Math a times x squared plus b times x plus c equals 0')
.waitForCalm(this.userCommand, 'forward')
- .waitForCalm(this.assertSpoken, 'Math square root of n cube')
- .waitForCalm(testDone);
+ .waitForCalm(this.assertSpoken, 'Math square root of n cube');
});
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/navigation_shifter.js b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/navigation_shifter.js
index 01187fb663d..209388cc582 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/navigation_shifter.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/navigation_shifter.js
@@ -70,7 +70,7 @@ goog.inherits(cvox.NavigationShifter, cvox.AbstractShifter);
// These "const" literals may be used, but no order may be assumed
// between them by any outside callers.
/**
- * @type {Object.<string, number>}
+ * @type {Object<string, number>}
*/
cvox.NavigationShifter.GRANULARITIES = {
'CHARACTER': 0,
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/navigation_speaker.js b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/navigation_speaker.js
index 70cac8d1d26..6de2b8559d7 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/navigation_speaker.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/navigation_speaker.js
@@ -38,7 +38,7 @@ cvox.NavigationSpeaker = function() {
* Speak all of the NavDescriptions in the given array (as returned by
* getDescription), including playing earcons.
*
- * @param {Array.<cvox.NavDescription>} descriptionArray The array of
+ * @param {Array<cvox.NavDescription>} descriptionArray The array of
* NavDescriptions to speak.
* @param {number} initialQueueMode The initial queue mode.
* @param {Function} completionFunction Function to call when finished speaking.
@@ -118,9 +118,9 @@ cvox.NavigationSpeaker.structuredElement = function(annon) {
/**
* Reorder special annotations for structured elements to be spoken first.
- * @param {Array.<cvox.NavDescription>} descriptionArray The array of
+ * @param {Array<cvox.NavDescription>} descriptionArray The array of
* NavDescriptions to speak.
- * @return {Array.<cvox.NavDescription>} The reordered array.
+ * @return {Array<cvox.NavDescription>} The reordered array.
*/
cvox.NavigationSpeaker.prototype.reorderAnnotations = function(
descriptionArray) {
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/pdf_processor.js b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/pdf_processor.js
index b6ca6d1fb5a..1ba8973ba16 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/pdf_processor.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/pdf_processor.js
@@ -11,7 +11,40 @@ goog.provide('cvox.PdfProcessor');
goog.require('cvox.QueueMode');
/**
- * Process PDFs created with Chrome's built-in PDF plug-in, which has an
+ * The array of PDFs yet to process.
+ * @type {Array<HTMLEmbedElement>}
+ */
+cvox.PdfProcessor.pdfEmbeds = [];
+
+/**
+ * The current PDF we're processing, or null if we're not processing one.
+ * @type {HTMLEmbedElement}
+ */
+cvox.PdfProcessor.pdfEmbed = null;
+
+/**
+ * The number of pages in the current PDF, or null if we haven't
+ * retreived that yet.
+ * @type {?number}
+ */
+cvox.PdfProcessor.pageCount = null;
+
+/**
+ * The 0-based index of the page we're currently retrieving, or null
+ * if we don't know how many pages there are yet.
+ * @type {?number}
+ */
+cvox.PdfProcessor.pageIndex = null;
+
+/**
+ * The element on the page where all of the generated content from the PDF
+ * will go, or null if we're not currently processing a PDF.
+ * @type {Element}
+ */
+cvox.PdfProcessor.documentDiv = null;
+
+/**
+ * Process PDFs created with Chrome's built-in PDF plugin, which has an
* accessibility hook.
*/
cvox.PdfProcessor.processEmbeddedPdfs = function() {
@@ -19,119 +52,201 @@ cvox.PdfProcessor.processEmbeddedPdfs = function() {
return;
}
- var es = document.querySelectorAll('embed[type="application/pdf"]');
- for (var i = 0; i < es.length; i++) {
- var e = es[i];
- if (typeof e.accessibility === 'function') {
- var infoJSON = e.accessibility();
- var info = cvox.ChromeVoxJSON.parse(infoJSON);
+ var pdfEmbeds = document.querySelectorAll('embed[type="application/pdf"]');
+ if (pdfEmbeds.length == 0) {
+ return;
+ }
- if (!info.loaded) {
- window.setTimeout(cvox.PdfProcessor.processEmbeddedPdfs, 100);
- continue;
- }
- if (!info.copyable) {
- cvox.ChromeVox.tts.speak(
- cvox.ChromeVox.msgs.getMsg('copy_protected_pdf'),
- cvox.QueueMode.QUEUE);
- continue;
- }
+ // Convert it to an Array so we can slice off one at a time, and stick
+ // it in a class variable. The responses from the plug-in come as a
+ // generic 'message' event to the window with no context, so we have
+ // to use global state to keep track of progress.
+ cvox.PdfProcessor.pdfEmbeds = Array.prototype.slice.call(pdfEmbeds);
+
+ // Install our event listener for responses.
+ window.addEventListener('message',
+ /** @type {EventListener} */(cvox.PdfProcessor.onMessage));
- var div = document.createElement('DIV');
-
- var headerDiv = document.createElement('DIV');
- headerDiv.style.position = 'relative';
- headerDiv.style.background = 'white';
- headerDiv.style.margin = '20pt';
- headerDiv.style.padding = '20pt';
- headerDiv.style.border = '1px solid #000';
- var filename = e.src.substr(e.src.lastIndexOf('/') + 1);
- document.title = filename;
- var html = cvox.ChromeVox.msgs.getMsg(
- 'pdf_header', [filename, e.src + '#original']);
- headerDiv.innerHTML = html;
- // Set up a handler to reload the page when 'Show original' is clicked.
- var showLink = headerDiv.getElementsByTagName('a')[0];
- showLink.addEventListener('click', function() {
- window.location.href = e.src + '#original';
- window.location.reload();
- }, true);
- div.appendChild(headerDiv);
-
- // Document Styles
- div.style.position = 'relative';
- div.style.background = '#CCC';
- div.style.paddingTop = '1pt';
- div.style.paddingBottom = '1pt';
- div.style.width = '100%';
- div.style.minHeight = '100%';
-
- var displayPage = function(i) {
- var json = e.accessibility(i);
- var page = cvox.ChromeVoxJSON.parse(json);
- var pageDiv = document.createElement('DIV');
- var pageAnchor = document.createElement('A');
-
- // Page Achor Setup
- pageAnchor.name = 'page' + i;
-
- // Page Styles
- pageDiv.style.position = 'relative';
- pageDiv.style.background = 'white';
- pageDiv.style.margin = 'auto';
- pageDiv.style.marginTop = '20pt';
- pageDiv.style.marginBottom = '20pt';
- pageDiv.style.height = page.height + 'pt';
- pageDiv.style.width = page.width + 'pt';
- pageDiv.style.boxShadow = '0pt 0pt 10pt #333';
-
- // Text Nodes
- var texts = page['textBox'];
- for (var j = 0; j < texts.length; j++) {
- var text = texts[j];
- var textSpan = document.createElement('Span');
-
- // Text Styles
- textSpan.style.position = 'absolute';
- textSpan.style.left = text.left + 'pt';
- textSpan.style.top = text.top + 'pt';
- textSpan.style.fontSize = (0.8 * text.height) + 'pt';
-
- // Text Content
- for (var k = 0; k < text['textNodes'].length; k++) {
- var node = text['textNodes'][k];
- if (node.type == 'text') {
- textSpan.appendChild(document.createTextNode(node.text));
- } else if (node.type == 'url') {
- var a = document.createElement('A');
- a.href = node.url;
- a.appendChild(document.createTextNode(node.text));
- textSpan.appendChild(a);
- }
- }
-
- pageDiv.appendChild(textSpan);
- }
- div.appendChild(pageAnchor);
- div.appendChild(pageDiv);
-
- if (i < info['numberOfPages'] - 1) {
- window.setTimeout(function() { displayPage(i + 1); }, 0);
- } else {
- // NOTE(deboer): In the case of 'native' pdfs loaded directly
- // from a URL, we can not delete them outright. Doing so exposes a
- // bug where all keyboard events are lost. We set it to
- // 'display: none' instead.
- e.style.display = 'none';
- e.parentNode.appendChild(div);
-
- // TODO(stoarca): Why are we resetting the navigationManager?
- // This function is too big and confusing, it needs to be cleaned up.
- cvox.ChromeVox.navigationManager.reset();
- }
- };
-
- window.setTimeout(function() { displayPage(0); }, 0);
+ // Start processing the first one.
+ cvox.PdfProcessor.processNextPdf();
+};
+
+/**
+ * Pull off the next <embed> element from |cvox.PdfProcessor.pdfEmbeds|
+ * and send a message to it to begin processing. If there are no more
+ * elements in the array, remove the event listener and reset
+ * NavigationManager so that it lands at the top of the now-modified page.
+ */
+cvox.PdfProcessor.processNextPdf = function() {
+ if (cvox.PdfProcessor.pdfEmbeds.length == 0) {
+ window.removeEventListener('message',
+ /** @type {EventListener} */(cvox.PdfProcessor.onMessage));
+ cvox.PdfProcessor.pdfEmbeds = null;
+ cvox.PdfProcessor.pdfEmbed = null;
+
+ cvox.ChromeVox.navigationManager.reset();
+ return;
+ }
+
+ cvox.PdfProcessor.pdfEmbed = cvox.PdfProcessor.pdfEmbeds.shift();
+ cvox.PdfProcessor.pageCount = null;
+ cvox.PdfProcessor.pageIndex = null;
+ cvox.PdfProcessor.pdfEmbed.postMessage({'type': 'getAccessibilityJSON'});
+};
+
+/**
+ * Handler for the 'message' event on the window, which is how we get responses
+ * from Chrome's PDF plugin.
+ *
+ * @param {{data: {type: string, json: string}}} message The message from the
+ * PDF plugin containing a type identifier and JSON string.
+ */
+cvox.PdfProcessor.onMessage = function(message) {
+ // Exit if it's not an accessibility JSON reply message.
+ if (message.data.type != 'getAccessibilityJSONReply') {
+ return;
+ }
+
+ // Exit if we aren't in the middle of processing a PDF.
+ if (!cvox.PdfProcessor.pdfEmbed) {
+ return;
+ }
+
+ // Parse the JSON.
+ var info = /** @type {PDFAccessibilityJSONReply} */(
+ JSON.parse(message.data.json));
+
+ // If we already know how many pages are in the doc, we expect this message
+ // contains the data for one particular page.
+ if (cvox.PdfProcessor.pageCount !== null) {
+ cvox.PdfProcessor.processOnePage(info);
+ return;
+ }
+
+ // If not, we expect this message contains the info about the PDF overall:
+ // whether it's loaded, whether it's copyable, and how many total pages
+ // there are.
+ if (!info.loaded) {
+ cvox.PdfProcessor.pdfEmbeds.unshift(cvox.PdfProcessor.pdfEmbed);
+ cvox.PdfProcessor.pdfEmbed = null;
+ window.setTimeout(cvox.PdfProcessor.processNextPdf, 100);
+ return;
+ }
+
+ // Create the initial HTML skeleton.
+ cvox.PdfProcessor.documentDiv = document.createElement('DIV');
+ var headerDiv = document.createElement('DIV');
+ headerDiv.style.position = 'relative';
+ headerDiv.style.background = 'white';
+ headerDiv.style.margin = '20pt';
+ headerDiv.style.padding = '20pt';
+ headerDiv.style.border = '1px solid #000';
+ var src = cvox.PdfProcessor.pdfEmbed.src;
+ var filename = src.substr(src.lastIndexOf('/') + 1);
+ document.title = filename;
+ var html = cvox.ChromeVox.msgs.getMsg(
+ 'pdf_header', [filename, src + '#original']);
+ headerDiv.innerHTML = html;
+ // Set up a handler to reload the page when 'Show original' is clicked.
+ var showLink = headerDiv.getElementsByTagName('a')[0];
+ showLink.addEventListener('click', function() {
+ window.location.href = src + '#original';
+ window.location.reload();
+ }, true);
+ cvox.PdfProcessor.documentDiv.appendChild(headerDiv);
+ cvox.PdfProcessor.documentDiv.style.position = 'relative';
+ cvox.PdfProcessor.documentDiv.style.background = '#CCC';
+ cvox.PdfProcessor.documentDiv.style.paddingTop = '1pt';
+ cvox.PdfProcessor.documentDiv.style.paddingBottom = '1pt';
+ cvox.PdfProcessor.documentDiv.style.width = '100%';
+ cvox.PdfProcessor.documentDiv.style.minHeight = '100%';
+
+ if (!info.copyable) {
+ var alert = document.createElement('div');
+ alert.setAttribute('role', 'alert');
+ alert.innerText = cvox.ChromeVox.msgs.getMsg('copy_protected_pdf');
+ cvox.PdfProcessor.documentDiv.appendChild(alert);
+ cvox.PdfProcessor.pdfEmbed.parentElement.appendChild(
+ cvox.PdfProcessor.documentDiv);
+ return;
+ }
+
+ // Start processing the first page.
+ cvox.PdfProcessor.pageCount = info.numberOfPages;
+ cvox.PdfProcessor.pageIndex = -1;
+ cvox.PdfProcessor.getNextPage();
+};
+
+/**
+ * Send a message to the PDF plugin to get the next page. If we've finished
+ * getting all of the pages, clean up and get the next PDF in the document.
+ */
+cvox.PdfProcessor.getNextPage = function() {
+ cvox.PdfProcessor.pageIndex++;
+ if (cvox.PdfProcessor.pageIndex >= cvox.PdfProcessor.pageCount) {
+ cvox.PdfProcessor.pdfEmbed.style.display = 'none';
+ cvox.PdfProcessor.pdfEmbed.parentElement.appendChild(
+ cvox.PdfProcessor.documentDiv);
+ cvox.PdfProcessor.processNextPdf();
+ return;
+ }
+
+ cvox.PdfProcessor.pdfEmbed.postMessage(
+ {'type': 'getAccessibilityJSON',
+ 'page': cvox.PdfProcessor.pageIndex});
+};
+
+/**
+ * Process one page in the PDF file and turn it into HTML.
+ * @param {PDFAccessibilityJSONReply} info The data from one page of the PDF.
+ */
+cvox.PdfProcessor.processOnePage = function(info) {
+ var pageDiv = document.createElement('DIV');
+ var pageAnchor = document.createElement('A');
+
+ // Page Achor Setup
+ pageAnchor.name = 'page' + cvox.PdfProcessor.pageIndex;
+
+ // Page Styles
+ pageDiv.style.position = 'relative';
+ pageDiv.style.background = 'white';
+ pageDiv.style.margin = 'auto';
+ pageDiv.style.marginTop = '20pt';
+ pageDiv.style.marginBottom = '20pt';
+ pageDiv.style.height = info.height + 'pt';
+ pageDiv.style.width = info.width + 'pt';
+ pageDiv.style.boxShadow = '0pt 0pt 10pt #333';
+
+ // Text Nodes
+ var texts = info.textBox;
+ for (var j = 0; j < texts.length; j++) {
+ var text = texts[j];
+ var textSpan = document.createElement('Span');
+
+ // Text Styles
+ textSpan.style.position = 'absolute';
+ textSpan.style.left = text.left + 'pt';
+ textSpan.style.top = text.top + 'pt';
+ textSpan.style.fontSize = (0.8 * text.height) + 'pt';
+
+ // Text Content
+ for (var k = 0; k < text.textNodes.length; k++) {
+ var node = text.textNodes[k];
+ if (node.type == 'text') {
+ textSpan.appendChild(document.createTextNode(node.text));
+ } else if (node.type == 'url') {
+ var a = document.createElement('A');
+ a.href = node.url;
+ a.appendChild(document.createTextNode(node.text));
+ textSpan.appendChild(a);
+ }
}
+
+ pageDiv.appendChild(textSpan);
}
+ cvox.PdfProcessor.documentDiv.appendChild(pageAnchor);
+ cvox.PdfProcessor.documentDiv.appendChild(pageDiv);
+
+ // Now get the next page.
+ cvox.PdfProcessor.getNextPage();
};
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/script_installer.js b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/script_installer.js
index 4c112b5e4c8..a76e503688c 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/script_installer.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/script_installer.js
@@ -20,10 +20,11 @@ cvox.ScriptInstaller.blacklistPattern = /chrome:\/\/|chrome-extension:\/\//;
/**
* Installs a script in the web page.
- * @param {Array.<string>} srcs An array of URLs of scripts.
+ * @param {Array<string>} srcs An array of URLs of scripts.
* @param {string} uid A unique id. This function won't install the same set of
* scripts twice.
- * @param {function()=} opt_onload A function called when the script has loaded.
+ * @param {function()=} opt_onload A function called when the last script
+ * has loaded.
* @param {string=} opt_chromevoxScriptBase An optional chromevoxScriptBase
* attribute to add.
* @return {boolean} False if the script already existed and this function
@@ -37,43 +38,72 @@ cvox.ScriptInstaller.installScript = function(srcs, uid, opt_onload,
if (document.querySelector('script[' + uid + ']')) {
return false;
}
- if (!srcs) {
+ if (!srcs || srcs.length == 0) {
return false;
}
- for (var i = 0, scriptSrc; scriptSrc = srcs[i]; i++) {
- // Directly write the contents of the script we are trying to inject into
- // the page.
- var xhr = new XMLHttpRequest();
- var url = scriptSrc + '?' + new Date().getTime();
- xhr.onreadystatechange = function() {
- if (xhr.readyState == 4) {
- var scriptText = xhr.responseText;
- // Add a magic comment to the bottom of the file so that
- // Chrome knows the name of the script in the JavaScript debugger.
- scriptText += '\n//# sourceURL=' + scriptSrc + '\n';
- var apiScript = document.createElement('script');
- apiScript.type = 'text/javascript';
- apiScript.setAttribute(uid, '1');
- apiScript.textContent = scriptText;
- if (opt_chromevoxScriptBase) {
- apiScript.setAttribute('chromevoxScriptBase',
- opt_chromevoxScriptBase);
- }
- cvox.DomUtil.addNodeToHead(apiScript);
- }
- };
- try {
- xhr.open('GET', url, false);
- xhr.send(null);
- } catch (exception) {
- window.console.log('Warning: ChromeVox external script loading for ' +
- document.location + ' stopped after failing to install ' + scriptSrc);
- return false;
+ cvox.ScriptInstaller.installScriptHelper_(srcs, uid, opt_onload,
+ opt_chromevoxScriptBase);
+ return true;
+};
+
+/**
+ * Helper that installs one script and calls itself recursively when each
+ * script loads.
+ * @param {Array<string>} srcs An array of URLs of scripts.
+ * @param {string} uid A unique id. This function won't install the same set of
+ * scripts twice.
+ * @param {function()=} opt_onload A function called when the
+ * last script has loaded.
+ * @param {string=} opt_chromevoxScriptBase An optional chromevoxScriptBase
+ * attribute to add.
+ * @private
+ */
+cvox.ScriptInstaller.installScriptHelper_ = function(srcs, uid, opt_onload,
+ opt_chromevoxScriptBase) {
+ function next() {
+ if (srcs.length > 0) {
+ cvox.ScriptInstaller.installScriptHelper_(srcs, uid, opt_onload,
+ opt_chromevoxScriptBase);
+ } else if (opt_onload) {
+ opt_onload();
}
}
- if (opt_onload) {
- opt_onload();
+
+ var scriptSrc = srcs.shift();
+ if (!scriptSrc) {
+ next();
+ return;
+ }
+
+ var xhr = new XMLHttpRequest();
+ var url = scriptSrc + '?' + new Date().getTime();
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState == 4) {
+ var scriptText = xhr.responseText;
+ // Add a magic comment to the bottom of the file so that
+ // Chrome knows the name of the script in the JavaScript debugger.
+ scriptText += '\n//# sourceURL=' + scriptSrc + '\n';
+
+ var apiScript = document.createElement('script');
+ apiScript.type = 'text/javascript';
+ apiScript.setAttribute(uid, '1');
+ apiScript.textContent = scriptText;
+ if (opt_chromevoxScriptBase) {
+ apiScript.setAttribute('chromevoxScriptBase',
+ opt_chromevoxScriptBase);
+ }
+ cvox.DomUtil.addNodeToHead(apiScript);
+ next();
+ }
+ };
+
+ try {
+ xhr.open('GET', url, true);
+ xhr.send(null);
+ } catch (exception) {
+ console.log('Warning: ChromeVox external script loading for ' +
+ document.location + ' stopped after failing to install ' + scriptSrc);
+ next();
}
- return true;
};
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/serializer.js b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/serializer.js
index dcb7c82d9d3..619533686b5 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/serializer.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/serializer.js
@@ -25,6 +25,7 @@ cvox.Serializer = function() { };
cvox.Serializer.prototype.storeOn = function(store) {
cvox.ChromeVox.storeOn(store);
cvox.ChromeVoxEventWatcher.storeOn(store);
+ cvox.ChromeVox.navigationManager.storeOn(store);
};
/**
@@ -35,4 +36,5 @@ cvox.Serializer.prototype.storeOn = function(store) {
cvox.Serializer.prototype.readFrom = function(store) {
cvox.ChromeVox.readFrom(store);
cvox.ChromeVoxEventWatcher.readFrom(store);
+ cvox.ChromeVox.navigationManager.readFrom(store);
};
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/ui/node_search_widget.js b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/ui/node_search_widget.js
index 093642c0ecd..c6bc416509d 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/ui/node_search_widget.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/ui/node_search_widget.js
@@ -12,14 +12,13 @@ goog.provide('cvox.NodeSearchWidget');
goog.require('cvox.ChromeVox');
goog.require('cvox.DomUtil');
goog.require('cvox.SearchWidget');
-goog.require('cvox.SpokenMessages');
/**
* @constructor
* @param {string} typeMsg A message id identifying the type of items
* contained in the list.
- * @param {?function(Array.<Node>)} predicate A predicate; if null, no predicate
+ * @param {?function(Array<Node>)} predicate A predicate; if null, no predicate
* applies.
* @extends {cvox.SearchWidget}
*/
@@ -58,7 +57,7 @@ cvox.NodeSearchWidget.prototype.getPredicate = function() {
/**
* Shows a list generated dynamic satisfying some predicate.
* @param {string} typeMsg The message id of the type contained in nodes.
- * @param {function(Array.<Node>)} predicate The predicate.
+ * @param {function(Array<Node>)} predicate The predicate.
* @return {cvox.NodeSearchWidget} The widget.
*/
cvox.NodeSearchWidget.create = function(typeMsg, predicate) {
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/ui/search_widget.js b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/ui/search_widget.js
index 479adbcd54b..e98e7f65bce 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/ui/search_widget.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/ui/search_widget.js
@@ -14,6 +14,7 @@ goog.require('cvox.ApiImplementation');
goog.require('cvox.ChromeVox');
goog.require('cvox.CursorSelection');
goog.require('cvox.NavigationManager');
+goog.require('cvox.SpokenMessages');
goog.require('cvox.Widget');
@@ -230,7 +231,7 @@ cvox.SearchWidget.prototype.onNavigate = function() {
/**
* Gets the predicate to apply to every search.
- * @return {?function(Array.<Node>)} A predicate; if null, no predicate applies.
+ * @return {?function(Array<Node>)} A predicate; if null, no predicate applies.
*/
cvox.SearchWidget.prototype.getPredicate = function() {
return null;
@@ -240,7 +241,7 @@ cvox.SearchWidget.prototype.getPredicate = function() {
/**
* Goes to the next or previous result. For use in AndroidVox.
* @param {boolean=} opt_reverse Whether to find the next result in reverse.
- * @return {Array.<cvox.NavDescription>} The next result.
+ * @return {Array<cvox.NavDescription>} The next result.
*/
cvox.SearchWidget.prototype.nextResult = function(opt_reverse) {
if (!this.isActive()) {
@@ -337,7 +338,7 @@ cvox.SearchWidget.prototype.toggleCaseSensitivity_ = function() {
* Gets the next result.
*
* @param {string} searchStr The text to search for.
- * @return {Array.<cvox.NavDescription>} The next result, in the form of
+ * @return {Array<cvox.NavDescription>} The next result, in the form of
* NavDescriptions.
* @private
*/
@@ -403,7 +404,7 @@ cvox.SearchWidget.prototype.beginSearch_ = function(searchStr) {
*
* @param {string} searchStr The text to search for.
* @param {boolean=} opt_reversed The direction.
- * @return {Array.<cvox.NavDescription>} The next result.
+ * @return {Array<cvox.NavDescription>} The next result.
* @private
*/
cvox.SearchWidget.prototype.next_ = function(searchStr, opt_reversed) {
@@ -412,7 +413,7 @@ cvox.SearchWidget.prototype.next_ = function(searchStr, opt_reversed) {
var success = false;
if (this.getPredicate()) {
success = cvox.ChromeVox.navigationManager.findNext(
- /** @type {function(Array.<Node>)} */ (this.getPredicate()));
+ /** @type {function(Array<Node>)} */ (this.getPredicate()));
// TODO(dtseng): findNext always seems to point direction forward!
cvox.ChromeVox.navigationManager.setReversed(!!opt_reversed);
if (!success) {
@@ -435,7 +436,7 @@ cvox.SearchWidget.prototype.next_ = function(searchStr, opt_reversed) {
* speak it, focus the node if applicable, and speak some instructions
* at the end.
*
- * @param {Array.<cvox.NavDescription>} result The description of the next
+ * @param {Array<cvox.NavDescription>} result The description of the next
* result. If null, no more results were found and an error will be presented.
* @param {string} searchStr The text to search for.
* @private
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/user_commands.js b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/user_commands.js
index 2d6bb5aa1b8..6d6b220d6cb 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/user_commands.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/user_commands.js
@@ -55,7 +55,7 @@ cvox.ChromeVoxUserCommands.init_ = function() {
/**
- * @type {!Object.<string, function(Object=): boolean>}
+ * @type {!Object<string, function(Object=): boolean>}
*/
cvox.ChromeVoxUserCommands.commands;
@@ -102,10 +102,8 @@ cvox.ChromeVoxUserCommands.handleTabAction_ = function() {
return false;
}
- // If the user is already focused on a link or control,
- // nothing more needs to be done.
- var isLinkControl = cvox.ChromeVoxUserCommands.isFocusedOnLinkControl_();
- if (isLinkControl) {
+ // If the user is already focused on anything, nothing more needs to be done.
+ if (document.activeElement != document.body) {
return true;
}
@@ -164,20 +162,6 @@ cvox.ChromeVoxUserCommands.handleTabAction_ = function() {
/**
- * @return {boolean} True if we are focused on a link or any other control.
- * @private
- */
-cvox.ChromeVoxUserCommands.isFocusedOnLinkControl_ = function() {
- var tagName = 'A';
- if ((document.activeElement.tagName == tagName) ||
- cvox.DomUtil.isControl(document.activeElement)) {
- return true;
- }
- return false;
-};
-
-
-/**
* If a lingering tab dummy span exists, remove it.
*/
cvox.ChromeVoxUserCommands.removeTabDummySpan = function() {
@@ -607,9 +591,10 @@ cvox.ChromeVoxUserCommands.doCommand_ = function(cmdStruct) {
case 'speakTableLocation':
case 'exitShifterContent':
if (!cvox.DomPredicates.tablePredicate(cvox.DomUtil.getAncestors(
- cvox.ChromeVox.navigationManager.getCurrentNode())) ||
- !cvox.ChromeVox.navigationManager.performAction(cmd)) {
+ cvox.ChromeVox.navigationManager.getCurrentNode()))) {
errorMsg = 'not_inside_table';
+ } else if (!cvox.ChromeVox.navigationManager.performAction(cmd)) {
+ errorMsg = 'not_in_table_mode';
}
break;
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/user_commands_test.unitjs b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/user_commands_test.unitjs
index cb122e6fb95..8459a35dca5 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/user_commands_test.unitjs
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/user_commands_test.unitjs
@@ -10,7 +10,9 @@ GEN_INCLUDE(['../../testing/chromevox_unittest_base.js']);
* @constructor
* @extends {ChromeVoxUnitTestBase}
*/
-function CvoxUserCommandsUnitTest() {}
+function CvoxUserCommandsUnitTest() {
+ ChromeVoxUnitTestBase.call(this);
+}
CvoxUserCommandsUnitTest.prototype = {
__proto__: ChromeVoxUnitTestBase.prototype,
@@ -57,7 +59,6 @@ TEST_F('CvoxUserCommandsUnitTest', 'TabHandling', function() {
var id = document.activeElement.nextSibling.id;
assertEquals('foo', id);
});
- this.waitForCalm(testDone);
});
/**
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/user_event_detail.js b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/user_event_detail.js
index 1dfbb88bc57..b5276997763 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/user_event_detail.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/injected/user_event_detail.js
@@ -41,7 +41,7 @@ goog.require('cvox.ChromeVox');
* content and functionality.
* @constructor
*
- * @param {Object.<{command: (string),
+ * @param {Object<{command: (string),
* status: (undefined|string),
* resultNode: (undefined|Node),
* customCommand: (undefined|string)
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/messages/msgs.js b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/messages/msgs.js
index 7f726f08c89..fc4fc80ae52 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/messages/msgs.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/messages/msgs.js
@@ -14,7 +14,7 @@ goog.provide('cvox.Msgs');
*/
cvox.Msgs = function() {
/**
- * @type {Object.<string, string>}
+ * @type {Object<string, string>}
* @private
*/
this.localeNameDict_ = null;
@@ -46,7 +46,7 @@ cvox.Msgs.prototype.getLocale = function() {
* typos early.
*
* @param {string} messageId The id.
- * @param {Array.<string>=} opt_subs Substitution strings.
+ * @param {Array<string>=} opt_subs Substitution strings.
* @return {string} The message.
*/
cvox.Msgs.prototype.getMsg = function(messageId, opt_subs) {
@@ -98,7 +98,7 @@ cvox.Msgs.prototype.getNumber = function(num) {
*/
cvox.Msgs.prototype.getLocaleDisplayName = function(locale) {
if (!this.localeNameDict_) {
- this.localeNameDict_ = /** @type {Object.<string, string>} */(
+ this.localeNameDict_ = /** @type {Object<string, string>} */(
JSON.parse(this.getMsg('locale_dict')));
}
var name = this.localeNameDict_[locale];
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/messages/spoken_message.js b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/messages/spoken_message.js
index 30790ad5e5c..fef1f7c32a4 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/messages/spoken_message.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/messages/spoken_message.js
@@ -13,15 +13,6 @@ goog.provide('cvox.SpokenMessage');
* @constructor
*/
cvox.SpokenMessage = function() {
- /** @type {?number} */
- this.count = null;
-
/** @type {Array} */
this.id = null;
-
- /**
- * A message that has been already localized and should be sent to tts raw.
- * @type {?string}
- */
- this.raw = null;
};
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/messages/spoken_messages.js b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/messages/spoken_messages.js
index ba2c55fb7b5..6b04b0866a4 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/messages/spoken_messages.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/messages/spoken_messages.js
@@ -6,11 +6,9 @@
* @fileoverview Useful abstraction when speaking messages.
*
* Usage:
- * $m('aria_role_link').withCount(document.links.length)
+ * $m('aria_role_link')
* .andPause()
* .andMessage('aria_role_forms')
- * .withCount(document.forms.length)
- * .andEnd()
* .speakFlush();
*
*/
@@ -49,34 +47,11 @@ cvox.SpokenMessages.speak = function(mode) {
var message = cvox.SpokenMessages.messages[i];
// An invalid message format.
- if (!message || (!message.id && !message.raw))
+ if (!message || !message.id)
throw 'Invalid message received.';
- var finalText = '';
- if (message.count != null) {
- if (message.count <= 0) {
- try {
- finalText +=
- cvox.ChromeVox.msgs.getMsg(message.id[0] + '_optional_default');
- } catch(e) {
- // The message doesn't exist.
- continue;
- }
- } else if (message.count == 1) {
- finalText += cvox.ChromeVox.msgs.getMsg(message.id[0] + '_singular');
- } else {
- finalText += cvox.ChromeVox.msgs.getMsg(message.id[0] + '_plural',
- [message.count]);
- }
- } else {
- if (message.raw) {
- finalText += message.raw;
- } else {
- finalText +=
- cvox.ChromeVox.msgs.getMsg.apply(cvox.ChromeVox.msgs, message.id);
- }
- }
-
+ var finalText = cvox.ChromeVox.msgs.getMsg.apply(cvox.ChromeVox.msgs,
+ message.id);
cvox.ChromeVox.tts.speak(finalText, mode,
cvox.AbstractTts.PERSONALITY_ANNOUNCEMENT);
@@ -98,42 +73,6 @@ cvox.SpokenMessages.currentMessage = function() {
};
/**
- * Quantifies the current message.
- * This will modify the way the message gets read.
- * For example, if the count is 2, the message becomes pluralized according
- * to our i18n resources. The message "2 links" is a possible output.
- * @param {number} count Quantifies current message.
- * @return {Object} This object, useful for chaining.
- */
-cvox.SpokenMessages.withCount = function(count) {
- cvox.SpokenMessages.currentMessage().count = count;
- return cvox.SpokenMessages;
-};
-
-/**
- * Quantifies the current message.
- * Modifies the message with a current index/total description (commonly seen
- * in lists).
- * @param {number} index The current item.
- * @param {number} total The total number of items.
- * @return {Object} This object, useful for chaining.
- */
-cvox.SpokenMessages.andIndexTotal = function(index, total) {
- var newMessage = new cvox.SpokenMessage();
- newMessage.raw = cvox.ChromeVox.msgs.getMsg('index_total', [index, total]);
- cvox.SpokenMessages.messages.push(newMessage);
- return cvox.SpokenMessages;
-};
-
-/**
- * Ends a message. with an appropriate marker.
- * @return {Object} This object, useful for chaining.
- */
-cvox.SpokenMessages.andEnd = function() {
- return cvox.SpokenMessages.andMessage('end');
-};
-
-/**
* Adds a message.
* @param {string|Array} messageId The id of the message.
* @return {Object} This object, useful for chaining.
@@ -145,19 +84,6 @@ cvox.SpokenMessages.andMessage = function(messageId) {
return cvox.SpokenMessages;
};
-
-/**
- * Adds a string as a message.
- * @param {string} message An already localized string.
- * @return {Object} This object, useful for chaining.
- */
-cvox.SpokenMessages.andRawMessage = function(message) {
- var newMessage = new cvox.SpokenMessage();
- newMessage.raw = message;
- cvox.SpokenMessages.messages.push(newMessage);
- return cvox.SpokenMessages;
-};
-
/**
* Pauses after the message, with an appropriate marker.
* @return {Object} This object, useful for chaining.
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox_tests.gypi b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox_tests.gypi
index e9c9f615b24..9b0bd1296f1 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox_tests.gypi
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox_tests.gypi
@@ -30,6 +30,14 @@
'<(DEPTH)/v8/tools/gyp/v8.gyp:v8',
'chromevox_test_deps_js',
],
+ 'conditions': [
+ [ 'cld_version==0 or cld_version==2', {
+ 'dependencies': [
+ # Interactive tests should use whatever CLD2 data access mode that
+ # the application embedder is using.
+ '<(DEPTH)/third_party/cld_2/cld_2.gyp:cld2_platform_impl', ],
+ }],
+ ],
'defines': [
'HAS_OUT_OF_PROC_TEST_RUNNER',
],
@@ -105,7 +113,9 @@
'<(mock_js)',
'<(test_api_js)',
'<(js2gtest)',
+ 'testing/callback_helper.js',
'testing/chromevox_e2e_test_base.js',
+ 'testing/chromevox_next_e2e_test_base.js',
'testing/assert_additions.js',
],
'outputs': [
@@ -129,22 +139,29 @@
},
],
'sources': [
+ '<(DEPTH)/chrome/browser/extensions/browsertest_util.cc',
+ '<(DEPTH)/chrome/browser/extensions/browsertest_util.h',
'<(DEPTH)/chrome/browser/ui/webui/web_ui_test_handler.cc',
'<(DEPTH)/chrome/browser/ui/webui/web_ui_test_handler.h',
'<(DEPTH)/chrome/test/base/browser_tests_main.cc',
- '<(DEPTH)/chrome/test/base/extension_load_waiter_one_shot.cc',
- '<(DEPTH)/chrome/test/base/extension_load_waiter_one_shot.h',
'<(DEPTH)/chrome/test/base/extension_js_browser_test.cc',
'<(DEPTH)/chrome/test/base/extension_js_browser_test.h',
+ '<(DEPTH)/chrome/test/base/extension_load_waiter_one_shot.cc',
+ '<(DEPTH)/chrome/test/base/extension_load_waiter_one_shot.h',
'<(DEPTH)/chrome/test/base/javascript_browser_test.cc',
'<(DEPTH)/chrome/test/base/javascript_browser_test.h',
'<(DEPTH)/chrome/test/base/test_chrome_web_ui_controller_factory.cc',
'<(DEPTH)/chrome/test/base/test_chrome_web_ui_controller_factory.h',
'<(DEPTH)/chrome/test/base/web_ui_browser_test.cc',
'<(DEPTH)/chrome/test/base/web_ui_browser_test.h',
- '<(DEPTH)/chrome/browser/extensions/browsertest_util.cc',
- '<(DEPTH)/chrome/browser/extensions/browsertest_util.h',
+ 'braille/braille_display_manager_test.unitjs',
+ 'braille/braille_input_handler_test.unitjs',
+ 'braille/braille_table_test.extjs',
+ 'braille/braille_translator_manager_test.extjs',
+ 'braille/expanding_braille_translator_test.unitjs',
+ 'braille/liblouis_test.extjs',
+ 'braille/pan_strategy_test.unitjs',
'common/aria_util_test.unitjs',
'common/braille_text_handler_test.unitjs',
'common/braille_util_test.unitjs',
@@ -160,19 +177,17 @@
'common/page_selection_test.unitjs',
'common/selection_util_test.unitjs',
'common/spannable_test.unitjs',
+ 'common/string_util_test.unitjs',
'chromevox/injected/event_watcher_test.unitjs',
'chromevox/injected/live_regions_test.unitjs',
'chromevox/injected/user_commands_test.unitjs',
'chromevox/injected/navigation_manager_test.unitjs',
+ 'cvox2/background/automation_util_test.extjs',
'cvox2/background/background_test.extjs',
'cvox2/background/cursors_test.extjs',
- 'host/chrome/braille_display_manager_test.unitjs',
- 'host/chrome/braille_input_handler_test.unitjs',
+ 'cvox2/background/output_test.extjs',
'host/chrome/braille_integration_test.unitjs',
- 'host/chrome/braille_table_test.extjs',
- 'host/chrome/expanding_braille_translator_test.unitjs',
'host/chrome/tts_background_test.extjs',
- 'liblouis_nacl/liblouis_test.extjs',
'walkers/character_walker_test.unitjs',
'walkers/group_walker_test.unitjs',
'walkers/object_walker_test.unitjs',
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/common/aria_util.js b/chromium/chrome/browser/resources/chromeos/chromevox/common/aria_util.js
index 47d2ee96c30..7df05627b2c 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/common/aria_util.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/common/aria_util.js
@@ -34,7 +34,7 @@ cvox.AriaUtil.NO_ROLE_NAME = ' ';
* Note: If you are adding a new mapping, the new message identifier needs a
* corresponding braille message. For example, a message id 'tag_button'
* requires another message 'tag_button_brl' within messages.js.
- * @type {Object.<string, string>}
+ * @type {Object<string, string>}
*/
cvox.AriaUtil.WIDGET_ROLE_TO_NAME = {
'alert' : 'aria_role_alert',
@@ -79,7 +79,7 @@ cvox.AriaUtil.WIDGET_ROLE_TO_NAME = {
* Note: If you are adding a new mapping, the new message identifier needs a
* corresponding braille message. For example, a message id 'tag_button'
* requires another message 'tag_button_brl' within messages.js.
- * @type {Object.<string, string>}
+ * @type {Object<string, string>}
*/
cvox.AriaUtil.STRUCTURE_ROLE_TO_NAME = {
'article' : 'aria_role_article',
@@ -109,7 +109,7 @@ cvox.AriaUtil.STRUCTURE_ROLE_TO_NAME = {
/**
- * @type {Array.<Object>}
+ * @type {Array<Object>}
*/
cvox.AriaUtil.ATTRIBUTE_VALUE_TO_STATUS = [
{ name: 'aria-autocomplete', values:
@@ -381,9 +381,9 @@ cvox.AriaUtil.getStateMsgs = function(targetNode, primary) {
for (var i = 0, attr; attr = cvox.AriaUtil.ATTRIBUTE_VALUE_TO_STATUS[i];
i++) {
var value = targetNode.getAttribute(attr.name);
- var msg_id = attr.values[value];
- if (msg_id) {
- state.push([msg_id]);
+ var msgId = attr.values[value];
+ if (msgId) {
+ state.push([msgId]);
}
}
if (targetNode.getAttribute('role') == 'grid') {
@@ -583,8 +583,8 @@ cvox.AriaUtil.getActiveDescendantId_ = function(targetNode) {
* Returns the list of elements that are one aria-level below.
*
* @param {Node} parentControl The node whose descendants should be analyzed.
- * @param {Array.<string>} role The role(s) of descendant we are looking for.
- * @return {Array.<Node>} The array of matching nodes.
+ * @param {Array<string>} role The role(s) of descendant we are looking for.
+ * @return {Array<Node>} The array of matching nodes.
*/
cvox.AriaUtil.getNextLevel = function(parentControl, role) {
var result = [];
@@ -608,8 +608,8 @@ cvox.AriaUtil.getNextLevel = function(parentControl, role) {
* Recursively finds the first node(s) that match the role.
*
* @param {Element} current The node to start looking at.
- * @param {Array.<string>} role The role(s) to match.
- * @return {Array.<Element>} The array of matching nodes.
+ * @param {Array<string>} role The role(s) to match.
+ * @return {Array<Element>} The array of matching nodes.
*/
cvox.AriaUtil.getNextLevelItems = function(current, role) {
if (current.nodeType != 1) { // If reached a node that is not an element.
@@ -838,7 +838,7 @@ cvox.AriaUtil.getAriaRelevant = function(node, change) {
* node or contain this node.
*
* @param {Node} node The node to be checked.
- * @return {Array.<Element>} All live regions affected by this node changing.
+ * @return {Array<Element>} All live regions affected by this node changing.
*/
cvox.AriaUtil.getLiveRegions = function(node) {
var result = [];
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/common/aural_style_util.js b/chromium/chrome/browser/resources/chromeos/chromevox/common/aural_style_util.js
index 568084bccf5..0e8c3b5cf57 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/common/aural_style_util.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/common/aural_style_util.js
@@ -55,7 +55,7 @@ cvox.AuralStyleConverter.identity = function(value) {
* Conversion from an aural style property to Chrome TTS property.
* TODO(dtseng): no-op's below need to be supported by the extension API itself
* or by ChromeVox.
- * @type {Object.<cvox.AuralProperty, string>}
+ * @type {Object<cvox.AuralProperty, string>}
*/
cvox.AuralStyleConverter.propertyTable = {
VOLUME: 'volume',
@@ -86,7 +86,7 @@ cvox.AuralStyleConverter.propertyTable = {
* Conversion from an aural style value to Chrome TTS value.
* TODO(dtseng): Conversion of aural CSS values is incomplete; everything is an
* identity conversion at the moment.
- * @type {Object.<cvox.AuralProperty, function(*)>}
+ * @type {Object<cvox.AuralProperty, function(*)>}
*/
cvox.AuralStyleConverter.valueTable = {
VOLUME: cvox.AuralStyleConverter.identity,
@@ -129,7 +129,7 @@ cvox.AuralStyleConverter.convertRule = function(property, value) {
/**
* Converts an aural CSS style block to a TTS property object.
- * @param {Object.<cvox.AuralProperty, *>} style The style.
+ * @param {Object<cvox.AuralProperty, *>} style The style.
* @return {Object} The tts property object.
*/
cvox.AuralStyleConverter.convertStyle = function(style) {
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/common/braille_util.js b/chromium/chrome/browser/resources/chromeos/chromevox/common/braille_util.js
index 4e2ce623171..bc133b7c5dd 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/common/braille_util.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/common/braille_util.js
@@ -11,10 +11,13 @@ goog.provide('cvox.BrailleUtil');
goog.require('cvox.ChromeVox');
goog.require('cvox.DomUtil');
+goog.require('cvox.EditableTextAreaShadow');
goog.require('cvox.Focuser');
goog.require('cvox.NavBraille');
goog.require('cvox.NodeStateUtil');
goog.require('cvox.Spannable');
+goog.require('cvox.ValueSelectionSpan');
+goog.require('cvox.ValueSpan');
/**
@@ -30,7 +33,7 @@ cvox.BrailleUtil.ITEM_SEPARATOR = ' ';
* Containers are distinguished from roles by their appearance higher up in the
* DOM tree of a selected node.
* This list should be very short.
- * @type {!Array.<string>}
+ * @type {!Array<string>}
*/
cvox.BrailleUtil.CONTAINER = [
'tag_h1_brl',
@@ -51,82 +54,34 @@ cvox.BrailleUtil.CONTAINER = [
* c: replaced with braille container role; this potentially returns whitespace,
* so place at the beginning or end of templates for trimming.
* v: replaced with braille value.
- * @type {Object.<string, string>}
+ * @type {Object<string, string>}
*/
cvox.BrailleUtil.TEMPLATE = {
'base': 'c n v r s',
'aria_role_alert': 'r: n',
- 'aria_role_button': '[n]',
- 'aria_role_textbox': 'n: v r',
- 'input_type_button': '[n]',
- 'input_type_checkbox': 'n (s)',
- 'input_type_email': 'n: v r',
- 'input_type_number': 'n: v r',
- 'input_type_password': 'n: v r',
- 'input_type_search': 'n: v r',
- 'input_type_submit': '[n]',
- 'input_type_text': 'n: v r',
- 'input_type_tel': 'n: v r',
- 'input_type_url': 'n: v r',
- 'tag_button': '[n]',
- 'tag_textarea': 'n: v r'
+ 'aria_role_button': 'n r s',
+ 'aria_role_checkbox': 'n r (s)',
+ 'aria_role_menuitemcheckbox': 'n r (s)',
+ 'aria_role_menuitemradio': 'n r (s)',
+ 'aria_role_radio': 'n r (s)',
+ 'aria_role_textbox': 'n: v r s',
+ 'input_type_button': 'n r s',
+ 'input_type_checkbox': 'n r (s)',
+ 'input_type_email': 'n: v r s',
+ 'input_type_number': 'n: v r s',
+ 'input_type_password': 'n: v r s',
+ 'input_type_radio': 'n r (s)',
+ 'input_type_search': 'n: v r s',
+ 'input_type_submit': 'n r s',
+ 'input_type_text': 'n: v r s',
+ 'input_type_tel': 'n: v r s',
+ 'input_type_url': 'n: v r s',
+ 'tag_button': 'n r s',
+ 'tag_textarea': 'n: v r s'
};
/**
- * Attached to the value region of a braille spannable.
- * @param {number} offset The offset of the span into the value.
- * @constructor
- */
-cvox.BrailleUtil.ValueSpan = function(offset) {
- /**
- * The offset of the span into the value.
- * @type {number}
- */
- this.offset = offset;
-};
-
-
-/**
- * Creates a value span from a json serializable object.
- * @param {!Object} obj The json serializable object to convert.
- * @return {!cvox.BrailleUtil.ValueSpan} The value span.
- */
-cvox.BrailleUtil.ValueSpan.fromJson = function(obj) {
- return new cvox.BrailleUtil.ValueSpan(obj.offset);
-};
-
-
-/**
- * Converts this object to a json serializable object.
- * @return {!Object} The JSON representation.
- */
-cvox.BrailleUtil.ValueSpan.prototype.toJson = function() {
- return this;
-};
-
-
-cvox.Spannable.registerSerializableSpan(
- cvox.BrailleUtil.ValueSpan,
- 'cvox.BrailleUtil.ValueSpan',
- cvox.BrailleUtil.ValueSpan.fromJson,
- cvox.BrailleUtil.ValueSpan.prototype.toJson);
-
-
-/**
- * Attached to the selected text within a value.
- * @constructor
- */
-cvox.BrailleUtil.ValueSelectionSpan = function() {
-};
-
-
-cvox.Spannable.registerStatelessSerializableSpan(
- cvox.BrailleUtil.ValueSelectionSpan,
- 'cvox.BrailleUtil.ValueSelectionSpan');
-
-
-/**
* Gets the braille name for a node.
* See DomUtil for a more precise definition of 'name'.
* Additionally, whitespace is trimmed.
@@ -165,44 +120,29 @@ cvox.BrailleUtil.getRoleMsg = function(node) {
/**
- * Gets the braille role of a node.
- * See DomUtil for a more precise definition of 'role'.
- * @param {Node} node The node.
- * @return {string} The string representation.
- */
-cvox.BrailleUtil.getRole = function(node) {
- if (!node) {
- return '';
- }
- var roleMsg = cvox.BrailleUtil.getRoleMsg(node);
- return roleMsg ? cvox.ChromeVox.msgs.getMsg(roleMsg) : '';
-};
-
-
-/**
- * Gets the braille state of a node.
- * @param {Node} node The node.
+ * Transforms a {@code cvox.NodeState} list of state messages to the
+ * corresponding messages for braille and expands them into a localized
+ * string suitable for output on a braille display.
+ * @param {cvox.NodeState} stateMsgs The states to expand. The content of this
+ * array is modified.
* @return {string} The string representation.
+ * @private
*/
-cvox.BrailleUtil.getState = function(node) {
- if (!node) {
- return '';
- }
- return cvox.NodeStateUtil.expand(
- cvox.DomUtil.getStateMsgs(node, true).map(function(state) {
- // Check to see if a variant of the message with '_brl' exists,
- // and use it if so.
- //
- // Note: many messages are templatized, and if we don't pass any
- // argument to substitute, getMsg might throw an error if the
- // resulting string is empty. To avoid this, we pass a dummy
- // substitution string array here.
- var dummySubs = ['dummy', 'dummy', 'dummy'];
- if (cvox.ChromeVox.msgs.getMsg(state[0] + '_brl', dummySubs)) {
- state[0] += '_brl';
- }
- return state;
- }));
+cvox.BrailleUtil.expandStateMsgs_ = function(stateMsgs) {
+ stateMsgs.forEach(function(state) {
+ // Check to see if a variant of the message with '_brl' exists,
+ // and use it if so.
+ //
+ // Note: many messages are templatized, and if we don't pass any
+ // argument to substitute, getMsg might throw an error if the
+ // resulting string is empty. To avoid this, we pass a dummy
+ // substitution string array here.
+ var dummySubs = ['dummy', 'dummy', 'dummy'];
+ if (cvox.ChromeVox.msgs.getMsg(state[0] + '_brl', dummySubs)) {
+ state[0] += '_brl';
+ }
+ });
+ return cvox.NodeStateUtil.expand(stateMsgs);
};
@@ -228,8 +168,8 @@ cvox.BrailleUtil.getContainer = function(prev, node) {
/**
- * Gets the braille value of a node. A cvox.BrailleUtil.ValueSpan will be
- * attached, along with (possibly) a cvox.BrailleUtil.ValueSelectionSpan.
+ * Gets the braille value of a node. A {@code cvox.ValueSpan} will be
+ * attached, along with (possibly) a {@code cvox.ValueSelectionSpan}.
* @param {Node} node The node.
* @return {!cvox.Spannable} The value spannable.
*/
@@ -237,7 +177,7 @@ cvox.BrailleUtil.getValue = function(node) {
if (!node) {
return new cvox.Spannable();
}
- var valueSpan = new cvox.BrailleUtil.ValueSpan(0 /* offset */);
+ var valueSpan = new cvox.ValueSpan(0 /* offset */);
if (cvox.DomUtil.isInputTypeText(node)) {
var value = node.value;
if (node.type === 'password') {
@@ -250,7 +190,7 @@ cvox.BrailleUtil.getValue = function(node) {
node.selectionStart, 0, spannable.getLength());
var selectionEnd = cvox.BrailleUtil.clamp_(
node.selectionEnd, 0, spannable.getLength());
- spannable.setSpan(new cvox.BrailleUtil.ValueSelectionSpan(),
+ spannable.setSpan(new cvox.ValueSelectionSpan(),
Math.min(selectionStart, selectionEnd),
Math.max(selectionStart, selectionEnd));
}
@@ -269,7 +209,7 @@ cvox.BrailleUtil.getValue = function(node) {
node.selectionStart - lineStart, 0, spannable.getLength());
var selectionEnd = cvox.BrailleUtil.clamp_(
node.selectionEnd - lineStart, 0, spannable.getLength());
- spannable.setSpan(new cvox.BrailleUtil.ValueSelectionSpan(),
+ spannable.setSpan(new cvox.ValueSelectionSpan(),
Math.min(selectionStart, selectionEnd),
Math.max(selectionStart, selectionEnd));
}
@@ -297,14 +237,22 @@ cvox.BrailleUtil.getTemplated = function(prev, node, opt_override) {
opt_override = opt_override ? opt_override : {};
var roleMsg = opt_override.roleMsg ||
(node ? cvox.DomUtil.getRoleMsg(node, cvox.VERBOSITY_VERBOSE) : '');
- var role = opt_override.role;
- if (!role && opt_override.roleMsg) {
- role = cvox.ChromeVox.msgs.getMsg(opt_override.roleMsg + '_brl') ||
- cvox.ChromeVox.msgs.getMsg(opt_override.roleMsg);
- }
- role = role || cvox.BrailleUtil.getRole(node);
var template = cvox.BrailleUtil.TEMPLATE[roleMsg] ||
cvox.BrailleUtil.TEMPLATE['base'];
+ var state = opt_override.state;
+ if (!state) {
+ if (node) {
+ state = cvox.BrailleUtil.expandStateMsgs_(
+ cvox.DomUtil.getStateMsgs(node, true));
+ } else {
+ state = '';
+ }
+ }
+ var role = opt_override.role || '';
+ if (!role && roleMsg) {
+ role = cvox.ChromeVox.msgs.getMsg(roleMsg + '_brl') ||
+ cvox.ChromeVox.msgs.getMsg(roleMsg);
+ }
var templated = new cvox.Spannable();
var mapChar = function(c) {
@@ -314,7 +262,7 @@ cvox.BrailleUtil.getTemplated = function(prev, node, opt_override) {
case 'r':
return role;
case 's':
- return opt_override.state || cvox.BrailleUtil.getState(node);
+ return state;
case 'c':
return opt_override.container ||
cvox.BrailleUtil.getContainer(prev, node);
@@ -327,8 +275,13 @@ cvox.BrailleUtil.getTemplated = function(prev, node, opt_override) {
for (var i = 0; i < template.length; i++) {
var component = mapChar(template[i]);
templated.append(component);
- // Ignore the next whitespace separator if the current component is empty.
- if (!component.toString() && template[i + 1] == ' ') {
+ // Ignore the next whitespace separator if the current component is empty,
+ // unless the empty value has a selection, in which case the cursor
+ // should be placed on the empty space after the empty value.
+ if (!component.toString() && template[i + 1] == ' ' &&
+ (!(component instanceof cvox.Spannable) ||
+ !/**@type {cvox.Spannable}*/(component).getSpanInstanceOf(
+ cvox.ValueSelectionSpan))) {
i++;
}
}
@@ -338,8 +291,8 @@ cvox.BrailleUtil.getTemplated = function(prev, node, opt_override) {
/**
* Creates a braille value from a string and, optionally, a selection range.
- * A cvox.BrailleUtil.ValueSpan will be
- * attached, along with a cvox.BrailleUtil.ValueSelectionSpan if applicable.
+ * A {@code cvox.ValueSpan} will be attached, along with a
+ * {@code cvox.ValueSelectionSpan} if applicable.
* @param {string} text The text to display as the value.
* @param {number=} opt_selStart Selection start.
* @param {number=} opt_selEnd Selection end if different from selection start.
@@ -349,7 +302,7 @@ cvox.BrailleUtil.getTemplated = function(prev, node, opt_override) {
cvox.BrailleUtil.createValue = function(text, opt_selStart, opt_selEnd,
opt_textOffset) {
var spannable = new cvox.Spannable(
- text, new cvox.BrailleUtil.ValueSpan(opt_textOffset || 0));
+ text, new cvox.ValueSpan(opt_textOffset || 0));
if (goog.isDef(opt_selStart)) {
opt_selEnd = goog.isDef(opt_selEnd) ? opt_selEnd : opt_selStart;
// TODO(plundblad): This looses the distinction between the selection
@@ -361,8 +314,7 @@ cvox.BrailleUtil.createValue = function(text, opt_selStart, opt_selEnd,
opt_selEnd = temp;
}
- spannable.setSpan(new cvox.BrailleUtil.ValueSelectionSpan(),
- opt_selStart, opt_selEnd);
+ spannable.setSpan(new cvox.ValueSelectionSpan(), opt_selStart, opt_selEnd);
}
return spannable;
};
@@ -390,7 +342,7 @@ cvox.BrailleUtil.click = function(braille, opt_displayPosition) {
node instanceof HTMLTextAreaElement)) {
var valueSpan = spans.filter(
function(s) {
- return s instanceof cvox.BrailleUtil.ValueSpan;
+ return s instanceof cvox.ValueSpan;
})[0];
if (valueSpan) {
if (document.activeElement !== node) {
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/common/braille_util_test.unitjs b/chromium/chrome/browser/resources/chromeos/chromevox/common/braille_util_test.unitjs
index 7e0a8cfa867..a1ee77ff9f4 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/common/braille_util_test.unitjs
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/common/braille_util_test.unitjs
@@ -60,7 +60,7 @@ CvoxBrailleUtilUnitTest.prototype = {
startIndex: actual.startIndex,
endIndex: actual.endIndex
});
- assertThat(new cvox.NavBraille(expected), eqJSON(actual));
+ assertThat(actual, eqJSON(new cvox.NavBraille(expected)));
}
};
@@ -120,7 +120,7 @@ TEST_F('CvoxBrailleUtilUnitTest', 'NameTemplate', function() {
var button = cvox.CursorSelection.fromNode($('1'));
this.assertBrailleEquals(
- {text: '[Submit]',
+ {text: 'Submit btn',
startIndex: 0,
endIndex: 1
}, this.getBraille_(button, button));
@@ -130,13 +130,13 @@ TEST_F('CvoxBrailleUtilUnitTest', 'NameTemplate', function() {
// Note: the cursor appears where text would be typed.
this.assertBrailleEquals(
- {text: 'Search: edtxt',
+ {text: 'Search: ed',
startIndex: 0,
endIndex: 1
}, this.getBraille_(input, input));
inputElement.focus();
this.assertBrailleEquals(
- {text: 'Search: edtxt',
+ {text: 'Search: ed',
startIndex: 8,
endIndex: 8
}, this.getBraille_(input, input));
@@ -157,7 +157,7 @@ TEST_F('CvoxBrailleUtilUnitTest', 'TextField', function() {
// Note: the cursor appears where text would be typed.
// The cursor is at the beginning by default.
this.assertBrailleEquals(
- {text: 'Search: larry edtxt',
+ {text: 'Search: larry ed',
startIndex: 0,
endIndex: 1
}, this.getBraille_(input, input));
@@ -165,7 +165,7 @@ TEST_F('CvoxBrailleUtilUnitTest', 'TextField', function() {
inputElement.selectionStart = 0;
inputElement.selectionEnd = 5;
this.assertBrailleEquals(
- {text: 'Search: larry edtxt',
+ {text: 'Search: larry ed',
startIndex: 8,
endIndex: 13
}, this.getBraille_(input, input));
@@ -184,13 +184,13 @@ TEST_F('CvoxBrailleUtilUnitTest', 'TextFieldEmpty', function() {
var input = cvox.CursorSelection.fromNode($('1'));
this.assertBrailleEquals(
- {text: ': edtxt',
+ {text: ': ed',
startIndex: 0,
endIndex: 1
}, this.getBraille_(input, input));
inputElement.focus();
this.assertBrailleEquals(
- {text: ': edtxt',
+ {text: ': ed',
startIndex: 2,
endIndex: 2
}, this.getBraille_(input, input));
@@ -213,7 +213,7 @@ TEST_F('CvoxBrailleUtilUnitTest', 'TextFieldSelection', function() {
inputElem.selectionStart = 2;
inputElem.selectionEnd = 5;
this.assertBrailleEquals(
- {text: ': strawberry edtxt',
+ {text: ': strawberry ed',
startIndex: 4,
endIndex: 7
}, this.getBraille_(input, input));
@@ -222,7 +222,7 @@ TEST_F('CvoxBrailleUtilUnitTest', 'TextFieldSelection', function() {
inputElem.selectionStart = 10;
inputElem.selectionEnd = 10;
this.assertBrailleEquals(
- {text: ': strawberry edtxt',
+ {text: ': strawberry ed',
startIndex: 12,
endIndex: 12
}, this.getBraille_(input, input));
@@ -239,7 +239,7 @@ TEST_F('CvoxBrailleUtilUnitTest', 'StateTemplate', function() {
var checkbox = cvox.CursorSelection.fromNode($('1'));
this.assertBrailleEquals(
- {text: 'Save ( )',
+ {text: 'Save chk ( )',
startIndex: 0,
endIndex: 1
}, this.getBraille_(checkbox, checkbox));
@@ -247,7 +247,7 @@ TEST_F('CvoxBrailleUtilUnitTest', 'StateTemplate', function() {
$('1').checked = true;
this.assertBrailleEquals(
- {text: 'Save (x)',
+ {text: 'Save chk (x)',
startIndex: 0,
endIndex: 1
}, this.getBraille_(checkbox, checkbox));
@@ -286,7 +286,7 @@ TEST_F('CvoxBrailleUtilUnitTest', 'ContainerTemplate', function() {
var navBraille = this.getBraille_(
cvox.CursorSelection.fromBody(), link);
this.assertBrailleEquals(
- {text: 'h1 Skip To Menu int lnk',
+ {text: 'h1 Skip To Menu intlnk',
startIndex: 0,
endIndex: 1
}, navBraille);
@@ -303,14 +303,33 @@ TEST_F('CvoxBrailleUtilUnitTest', 'LinkSpans', function() {
var link2 = $('2');
var navBraille = this.getBraille_(
cvox.CursorSelection.fromBody(), cvox.CursorSelection.fromNode(link1));
- assertEquals('Hello int lnk from ChromeVox lnk',
+ assertEquals('Hello intlnk from ChromeVox lnk',
navBraille.text.toString());
assertEquals(link1, navBraille.text.getSpan(0));
- assertEquals(link1, navBraille.text.getSpan(12));
- assertEquals('undefined', typeof navBraille.text.getSpan(13));
- assertEquals('undefined', typeof navBraille.text.getSpan(18));
- assertEquals(link2, navBraille.text.getSpan(19));
- assertEquals(link2, navBraille.text.getSpan(31));
+ assertEquals(link1, navBraille.text.getSpan(11));
+ assertEquals('undefined', typeof navBraille.text.getSpan(12));
+ assertEquals('undefined', typeof navBraille.text.getSpan(17));
+ assertEquals(link2, navBraille.text.getSpan(18));
+ assertEquals(link2, navBraille.text.getSpan(30));
+});
+
+
+TEST_F('CvoxBrailleUtilUnitTest', 'VisitedLink', function() {
+ this.loadHtml('<p><a id="1" href="http://visited.link">Hello</a> there.');
+ var link = $('1');
+ var navBraille = this.getBraille_(
+ cvox.CursorSelection.fromBody(), cvox.CursorSelection.fromNode(link));
+ this.assertBrailleEquals({text: 'Hello lnk there.',
+ startIndex: 0,
+ endIndex: 1},
+ navBraille);
+ cvox.ChromeVox.visitedUrls[link.href] = true;
+ navBraille = this.getBraille_(
+ cvox.CursorSelection.fromBody(), cvox.CursorSelection.fromNode(link));
+ this.assertBrailleEquals({text: 'Hello vlnk there.',
+ startIndex: 0,
+ endIndex: 1},
+ navBraille);
});
@@ -324,7 +343,7 @@ TEST_F('CvoxBrailleUtilUnitTest', 'NestedElements', function() {
var link = $('batman-link');
var navBraille = this.getBraille_(
cvox.CursorSelection.fromBody(), cvox.CursorSelection.fromNode(h1));
- assertEquals('h1 Larry, Sergey int lnk and Eric',
+ assertEquals('h1 Larry, Sergey intlnk and Eric',
navBraille.text.toString());
this.assertTextNodeChildOf_(h1, navBraille.text.getSpan(0));
this.assertTextNodeChildOf_(h1, navBraille.text.getSpan(5));
@@ -370,32 +389,32 @@ TEST_F('CvoxBrailleUtilUnitTest', 'CreateValue', function() {
// Value without a selection.
s = cvox.BrailleUtil.createValue('value');
assertEquals('value', s.toString());
- assertUndefined(s.getSpanInstanceOf(cvox.BrailleUtil.ValueSelectionSpan));
- valueSpan = s.getSpanInstanceOf(cvox.BrailleUtil.ValueSpan);
+ assertUndefined(s.getSpanInstanceOf(cvox.ValueSelectionSpan));
+ valueSpan = s.getSpanInstanceOf(cvox.ValueSpan);
assertEquals(0, s.getSpanStart(valueSpan));
assertEquals(s.getLength(), s.getSpanEnd(valueSpan));
// Value with a carret at the start of the text.
s = cvox.BrailleUtil.createValue('value', 0);
- selectionSpan = s.getSpanInstanceOf(cvox.BrailleUtil.ValueSelectionSpan);
+ selectionSpan = s.getSpanInstanceOf(cvox.ValueSelectionSpan);
assertEquals(0, s.getSpanStart(selectionSpan));
assertEquals(0, s.getSpanEnd(selectionSpan));
// Value with a carret inside the text.
s = cvox.BrailleUtil.createValue('value', 1);
- selectionSpan = s.getSpanInstanceOf(cvox.BrailleUtil.ValueSelectionSpan);
+ selectionSpan = s.getSpanInstanceOf(cvox.ValueSelectionSpan);
assertEquals(1, s.getSpanStart(selectionSpan));
assertEquals(1, s.getSpanEnd(selectionSpan));
// Value with a carret at the end of the text.
s = cvox.BrailleUtil.createValue('value', 5);
- selectionSpan = s.getSpanInstanceOf(cvox.BrailleUtil.ValueSelectionSpan);
+ selectionSpan = s.getSpanInstanceOf(cvox.ValueSelectionSpan);
assertEquals(5, s.getSpanStart(selectionSpan));
assertEquals(5, s.getSpanEnd(selectionSpan));
// All of the value selected selected with reversed start and end.
s = cvox.BrailleUtil.createValue('value', 5, 0);
- selectionSpan = s.getSpanInstanceOf(cvox.BrailleUtil.ValueSelectionSpan);
+ selectionSpan = s.getSpanInstanceOf(cvox.ValueSelectionSpan);
assertEquals(0, s.getSpanStart(selectionSpan));
assertEquals(5, s.getSpanEnd(selectionSpan));
});
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/common/chrome_extension_externs.js b/chromium/chrome/browser/resources/chromeos/chromevox/common/chrome_extension_externs.js
index 6f532a96ac2..86fa446e2d9 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/common/chrome_extension_externs.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/common/chrome_extension_externs.js
@@ -37,10 +37,10 @@ chrome.extension.inIncognitoContext;
/**
- * @param {string|Object.<string>=} opt_extensionIdOrConnectInfo Either the
+ * @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,
+ * @param {Object<string>=} opt_connectInfo The connectInfo object,
* if arg1 was the extensionId to connect to.
* @return {Port} New port.
*/
@@ -161,7 +161,7 @@ chrome.runtime.Manifest.Oauth2 = function() {};
/** @type {string} */
chrome.runtime.Manifest.Oauth2.prototype.client_id;
-/**@type {!Array.<string>} */
+/**@type {!Array<string>} */
chrome.runtime.Manifest.Oauth2.prototype.scopes;
@@ -181,10 +181,10 @@ chrome.runtime.getManifest = function() {};
chrome.runtime.getURL = function(path) {};
/**
- * @param {string|!Object.<string>=} opt_extensionIdOrConnectInfo Either the
+ * @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,
+ * @param {!Object<string>=} opt_connectInfo The connectInfo object,
* if arg1 was the extensionId to connect to.
* @return {!Port} New port.
*/
@@ -325,7 +325,7 @@ chrome.tabs.captureVisibleTab = function(windowId, options, callback) {};
/**
* @param {number} tabId Tab Id.
- * @param {Object.<string>=} opt_connectInfo Info Object.
+ * @param {Object<string>=} opt_connectInfo Info Object.
*/
chrome.tabs.connect = function(tabId, opt_connectInfo) {};
@@ -365,7 +365,7 @@ chrome.tabs.get = function(tabId, callback) {};
* the public web pages, but there are still existing usages
*
* @param {number?} windowId Window id.
- * @param {function(Array.<Tab>): void} callback Callback.
+ * @param {function(Array<Tab>): void} callback Callback.
*/
chrome.tabs.getAllInWindow = function(windowId, callback) {};
@@ -387,7 +387,7 @@ chrome.tabs.getSelected = function(windowId, callback) {};
/**
- * @param {Object.<string, (number|Array.<number>)>} highlightInfo
+ * @param {Object<string, (number|Array<number>)>} highlightInfo
* An object with 'windowId' (number) and 'tabs'
* (number or array of numbers) keys.
* @param {function(Window): void} callback Callback function invoked
@@ -407,7 +407,7 @@ chrome.tabs.insertCSS = function(tabId, details, opt_callback) {};
/**
* @param {number} tabId Tab id.
- * @param {Object.<string, number>} moveProperties An object with 'index'
+ * @param {Object<string, number>} moveProperties An object with 'index'
* and optional 'windowId' keys.
* @param {function(Tab): void=} opt_callback Callback.
*/
@@ -415,18 +415,18 @@ chrome.tabs.move = function(tabId, moveProperties, opt_callback) {};
/**
- * @param {Object.<string, (number|string)>} queryInfo An object which may have
+ * @param {Object<string, (number|string)>} queryInfo An object which may have
* 'active', 'pinned', 'highlighted', 'status', 'title', 'url', 'windowId',
* and 'windowType' keys.
- * @param {function(Array.<Tab>): void=} opt_callback Callback.
- * @return {!Array.<Tab>}
+ * @param {function(Array<Tab>): void=} opt_callback Callback.
+ * @return {!Array<Tab>}
*/
chrome.tabs.query = function(queryInfo, opt_callback) {};
/**
* @param {number=} opt_tabId Tab id.
- * @param {Object.<string, boolean>=} opt_reloadProperties An object which
+ * @param {Object<string, boolean>=} opt_reloadProperties An object which
* may have a 'bypassCache' key.
* @param {function(): void=} opt_callback The callback function invoked
* after the tab has been reloaded.
@@ -435,7 +435,7 @@ chrome.tabs.reload = function(opt_tabId, opt_reloadProperties, opt_callback) {};
/**
- * @param {number|Array.<number>} tabIds A tab ID or an array of tab IDs.
+ * @param {number|Array<number>} tabIds A tab ID or an array of tab IDs.
* @param {function(Tab): void=} opt_callback Callback.
*/
chrome.tabs.remove = function(tabIds, opt_callback) {};
@@ -461,7 +461,7 @@ chrome.tabs.sendRequest = function(tabId, request, opt_callback) {};
/**
* @param {number} tabId Tab id.
- * @param {Object.<string, (string|boolean)>} updateProperties An object which
+ * @param {Object<string, (string|boolean)>} updateProperties An object which
* may have 'url' or 'selected' key.
* @param {function(Tab): void=} opt_callback Callback.
*/
@@ -532,7 +532,7 @@ chrome.windows.get = function(id, opt_getInfo, opt_callback) {};
/**
* @param {Object=} opt_getInfo May have 'populate' key. Or the callback.
- * @param {function(!Array.<!ChromeWindow>): void=} opt_callback Callback.
+ * @param {function(!Array<!ChromeWindow>): void=} opt_callback Callback.
*/
chrome.windows.getAll = function(opt_getInfo, opt_callback) {};
@@ -598,7 +598,7 @@ chrome.i18n = {};
/**
- * @param {function(Array.<string>): void} callback The callback function which
+ * @param {function(Array<string>): void} callback The callback function which
* accepts an array of the accept languages of the browser, such as
* 'en-US','en','zh-CN'.
*/
@@ -607,7 +607,7 @@ chrome.i18n.getAcceptLanguages = function(callback) {};
/**
* @param {string} messageName
- * @param {(string|Array.<string>)=} opt_args
+ * @param {(string|Array<string>)=} opt_args
* @return {string}
*/
chrome.i18n.getMessage = function(messageName, opt_args) {};
@@ -664,13 +664,13 @@ TtsVoice.prototype.gender;
TtsVoice.prototype.extensionId;
-/** @type {Array.<string>} */
+/** @type {Array<string>} */
TtsVoice.prototype.eventTypes;
/**
* Gets an array of all available voices.
- * @param {function(Array.<TtsVoice>)=} opt_callback An optional callback
+ * @param {function(Array<TtsVoice>)=} opt_callback An optional callback
* function.
*/
chrome.tts.getVoices = function(opt_callback) {};
@@ -708,7 +708,7 @@ chrome.history = {};
/**
- * @param {Object.<string, string>} details Object with a 'url' key.
+ * @param {Object<string, string>} details Object with a 'url' key.
*/
chrome.history.addUrl = function(details) {};
@@ -720,7 +720,7 @@ chrome.history.deleteAll = function(callback) {};
/**
- * @param {Object.<string, string>} range Object with 'startTime'
+ * @param {Object<string, string>} range Object with 'startTime'
* and 'endTime' keys.
* @param {function(): void} callback Callback function.
*/
@@ -728,25 +728,25 @@ chrome.history.deleteRange = function(range, callback) {};
/**
- * @param {Object.<string, string>} details Object with a 'url' key.
+ * @param {Object<string, string>} details Object with a 'url' key.
*/
chrome.history.deleteUrl = function(details) {};
/**
- * @param {Object.<string, string>} details Object with a 'url' key.
- * @param {function(!Array.<!VisitItem>): void} callback Callback function.
- * @return {!Array.<!VisitItem>}
+ * @param {Object<string, string>} details Object with a 'url' key.
+ * @param {function(!Array<!VisitItem>): void} callback Callback function.
+ * @return {!Array<!VisitItem>}
*/
chrome.history.getVisits = function(details, callback) {};
/**
- * @param {Object.<string, string>} query Object with a 'text' (string)
+ * @param {Object<string, string>} query Object with a 'text' (string)
* key and optional 'startTime' (number), 'endTime' (number) and
* 'maxResults' keys.
- * @param {function(!Array.<!HistoryItem>): void} callback Callback function.
- * @return {!Array.<!HistoryItem>}
+ * @param {function(!Array<!HistoryItem>): void} callback Callback function.
+ * @return {!Array<!HistoryItem>}
*/
chrome.history.search = function(query, callback) {};
@@ -767,8 +767,8 @@ chrome.permissions = {};
/**
* @typedef {{
- * permissions: (Array.<string>|undefined),
- * origins: (Array.<string>|undefined)
+ * permissions: (Array<string>|undefined),
+ * origins: (Array<string>|undefined)
* }}
* @see http://developer.chrome.com/extensions/permissions.html#type-Permissions
*/
@@ -914,7 +914,7 @@ ChromeWindow.prototype.width;
ChromeWindow.prototype.height;
-/** @type {Array.<Tab>} */
+/** @type {Array<Tab>} */
ChromeWindow.prototype.tabs;
@@ -983,7 +983,7 @@ Port.prototype.sender;
/**
- * @param {Object.<string>} obj Message object.
+ * @param {Object<string>} obj Message object.
*/
Port.prototype.postMessage = function(obj) {};
@@ -1053,7 +1053,7 @@ BookmarkTreeNode.prototype.dateAdded;
BookmarkTreeNode.prototype.dateGroupModified;
-/** @type {Array.<BookmarkTreeNode>} */
+/** @type {Array<BookmarkTreeNode>} */
BookmarkTreeNode.prototype.children;
@@ -1211,16 +1211,378 @@ chrome.storage.local.remove = function(keys, opt_callback) {};
chrome.storage.onChanged;
+// Begin auto generated externs; do not edit.
+// The following was generated from:
+//
+// python tools/json_schema_compiler/compiler.py
+// -g externs
+// chrome/common/extensions/api/automation.idl
+
/**
* @const
*/
chrome.automation = {};
/**
+ * @enum {string}
+ */
+chrome.automation.EventType = {
+ activedescendantchanged: 'activedescendantchanged',
+ alert: 'alert',
+ ariaAttributeChanged: 'ariaAttributeChanged',
+ autocorrectionOccured: 'autocorrectionOccured',
+ blur: 'blur',
+ checkedStateChanged: 'checkedStateChanged',
+ childrenChanged: 'childrenChanged',
+ focus: 'focus',
+ hide: 'hide',
+ hover: 'hover',
+ invalidStatusChanged: 'invalidStatusChanged',
+ layoutComplete: 'layoutComplete',
+ liveRegionChanged: 'liveRegionChanged',
+ loadComplete: 'loadComplete',
+ locationChanged: 'locationChanged',
+ menuEnd: 'menuEnd',
+ menuListItemSelected: 'menuListItemSelected',
+ menuListValueChanged: 'menuListValueChanged',
+ menuPopupEnd: 'menuPopupEnd',
+ menuPopupStart: 'menuPopupStart',
+ menuStart: 'menuStart',
+ rowCollapsed: 'rowCollapsed',
+ rowCountChanged: 'rowCountChanged',
+ rowExpanded: 'rowExpanded',
+ scrollPositionChanged: 'scrollPositionChanged',
+ scrolledToAnchor: 'scrolledToAnchor',
+ selectedChildrenChanged: 'selectedChildrenChanged',
+ selection: 'selection',
+ selectionAdd: 'selectionAdd',
+ selectionRemove: 'selectionRemove',
+ show: 'show',
+ textChanged: 'textChanged',
+ textSelectionChanged: 'textSelectionChanged',
+ treeChanged: 'treeChanged',
+ valueChanged: 'valueChanged',
+};
+
+/**
+ * @enum {string}
+ */
+chrome.automation.RoleType = {
+ alertDialog: 'alertDialog',
+ alert: 'alert',
+ annotation: 'annotation',
+ application: 'application',
+ article: 'article',
+ banner: 'banner',
+ blockquote: 'blockquote',
+ busyIndicator: 'busyIndicator',
+ button: 'button',
+ buttonDropDown: 'buttonDropDown',
+ canvas: 'canvas',
+ caption: 'caption',
+ cell: 'cell',
+ checkBox: 'checkBox',
+ client: 'client',
+ colorWell: 'colorWell',
+ columnHeader: 'columnHeader',
+ column: 'column',
+ comboBox: 'comboBox',
+ complementary: 'complementary',
+ contentInfo: 'contentInfo',
+ date: 'date',
+ dateTime: 'dateTime',
+ definition: 'definition',
+ descriptionListDetail: 'descriptionListDetail',
+ descriptionList: 'descriptionList',
+ descriptionListTerm: 'descriptionListTerm',
+ desktop: 'desktop',
+ details: 'details',
+ dialog: 'dialog',
+ directory: 'directory',
+ disclosureTriangle: 'disclosureTriangle',
+ div: 'div',
+ document: 'document',
+ embeddedObject: 'embeddedObject',
+ figcaption: 'figcaption',
+ figure: 'figure',
+ footer: 'footer',
+ form: 'form',
+ grid: 'grid',
+ group: 'group',
+ heading: 'heading',
+ iframe: 'iframe',
+ iframePresentational: 'iframePresentational',
+ ignored: 'ignored',
+ imageMapLink: 'imageMapLink',
+ imageMap: 'imageMap',
+ image: 'image',
+ inlineTextBox: 'inlineTextBox',
+ labelText: 'labelText',
+ legend: 'legend',
+ lineBreak: 'lineBreak',
+ link: 'link',
+ listBoxOption: 'listBoxOption',
+ listBox: 'listBox',
+ listItem: 'listItem',
+ listMarker: 'listMarker',
+ list: 'list',
+ locationBar: 'locationBar',
+ log: 'log',
+ main: 'main',
+ marquee: 'marquee',
+ math: 'math',
+ menuBar: 'menuBar',
+ menuButton: 'menuButton',
+ menuItem: 'menuItem',
+ menuItemCheckBox: 'menuItemCheckBox',
+ menuItemRadio: 'menuItemRadio',
+ menuListOption: 'menuListOption',
+ menuListPopup: 'menuListPopup',
+ menu: 'menu',
+ meter: 'meter',
+ navigation: 'navigation',
+ note: 'note',
+ outline: 'outline',
+ pane: 'pane',
+ paragraph: 'paragraph',
+ popUpButton: 'popUpButton',
+ pre: 'pre',
+ presentational: 'presentational',
+ progressIndicator: 'progressIndicator',
+ radioButton: 'radioButton',
+ radioGroup: 'radioGroup',
+ region: 'region',
+ rootWebArea: 'rootWebArea',
+ rowHeader: 'rowHeader',
+ row: 'row',
+ ruby: 'ruby',
+ ruler: 'ruler',
+ svgRoot: 'svgRoot',
+ scrollArea: 'scrollArea',
+ scrollBar: 'scrollBar',
+ seamlessWebArea: 'seamlessWebArea',
+ search: 'search',
+ searchBox: 'searchBox',
+ slider: 'slider',
+ sliderThumb: 'sliderThumb',
+ spinButtonPart: 'spinButtonPart',
+ spinButton: 'spinButton',
+ splitter: 'splitter',
+ staticText: 'staticText',
+ status: 'status',
+ switch: 'switch',
+ tabGroup: 'tabGroup',
+ tabList: 'tabList',
+ tabPanel: 'tabPanel',
+ tab: 'tab',
+ tableHeaderContainer: 'tableHeaderContainer',
+ table: 'table',
+ textField: 'textField',
+ time: 'time',
+ timer: 'timer',
+ titleBar: 'titleBar',
+ toggleButton: 'toggleButton',
+ toolbar: 'toolbar',
+ treeGrid: 'treeGrid',
+ treeItem: 'treeItem',
+ tree: 'tree',
+ unknown: 'unknown',
+ tooltip: 'tooltip',
+ webArea: 'webArea',
+ webView: 'webView',
+ window: 'window',
+};
+
+/**
+ * @enum {string}
+ */
+chrome.automation.StateType = {
+ busy: 'busy',
+ checked: 'checked',
+ collapsed: 'collapsed',
+ default: 'default',
+ disabled: 'disabled',
+ editable: 'editable',
+ enabled: 'enabled',
+ expanded: 'expanded',
+ focusable: 'focusable',
+ focused: 'focused',
+ haspopup: 'haspopup',
+ horizontal: 'horizontal',
+ hovered: 'hovered',
+ indeterminate: 'indeterminate',
+ invisible: 'invisible',
+ linked: 'linked',
+ multiselectable: 'multiselectable',
+ offscreen: 'offscreen',
+ pressed: 'pressed',
+ protected: 'protected',
+ readOnly: 'readOnly',
+ required: 'required',
+ selectable: 'selectable',
+ selected: 'selected',
+ vertical: 'vertical',
+ visited: 'visited',
+};
+
+/**
+ * @enum {string}
+ */
+chrome.automation.TreeChangeType = {
+ nodeCreated: 'nodeCreated',
+ subtreeCreated: 'subtreeCreated',
+ nodeChanged: 'nodeChanged',
+ nodeRemoved: 'nodeRemoved',
+};
+
+/**
+ * @typedef {{
+ * left: number,
+ * top: number,
+ * width: number,
+ * height: number
+ * }}
+ */
+chrome.automation.Rect;
+
+/**
+ * @typedef {{
+ * role: (!chrome.automation.RoleType|undefined),
+ * state: (Object|undefined),
+ * attributes: (Object|undefined)
+ * }}
+ */
+chrome.automation.FindParams;
+
+/**
+ * @constructor
+ */
+chrome.automation.AutomationEvent = function() {};
+
+/**
+ * @typedef {{
+ * target: chrome.automation.AutomationNode,
+ * type: !chrome.automation.TreeChangeType
+ * }}
+ */
+chrome.automation.TreeChange;
+
+/**
* @constructor
*/
chrome.automation.AutomationNode = function() {};
+/**
+ * @typedef {{
+ * activedescendant: chrome.automation.AutomationNode
+ * }}
+ */
+chrome.automation.ActiveDescendantMixin;
+
+/**
+ * @typedef {{
+ * url: string
+ * }}
+ */
+chrome.automation.LinkMixins;
+
+/**
+ * @typedef {{
+ * docUrl: string,
+ * docTitle: string,
+ * docLoaded: boolean,
+ * docLoadingProgress: number
+ * }}
+ */
+chrome.automation.DocumentMixins;
+
+/**
+ * @typedef {{
+ * scrollX: number,
+ * scrollXMin: number,
+ * scrollXMax: number,
+ * scrollY: number,
+ * scrollYMin: number,
+ * scrollYMax: number
+ * }}
+ */
+chrome.automation.ScrollableMixins;
+
+/**
+ * @typedef {{
+ * textSelStart: number,
+ * textSelEnd: number
+ * }}
+ */
+chrome.automation.EditableTextMixins;
+
+/**
+ * @typedef {{
+ * valueForRange: number,
+ * minValueForRange: number,
+ * maxValueForRange: number
+ * }}
+ */
+chrome.automation.RangeMixins;
+
+/**
+ * @typedef {{
+ * tableRowCount: number,
+ * tableColumnCount: number
+ * }}
+ */
+chrome.automation.TableMixins;
+
+/**
+ * @typedef {{
+ * tableCellColumnIndex: number,
+ * tableCellColumnSpan: number,
+ * tableCellRowIndex: number,
+ * tableCellRowSpan: number
+ * }}
+ */
+chrome.automation.TableCellMixins;
+
+/**
+ * Get the automation tree for the tab with the given tabId, or the current tab
+ * if no tabID is given, enabling automation if necessary. Returns a tree with a
+ * placeholder root node; listen for the "loadComplete" event to get a
+ * notification that the tree has fully loaded (the previous root node reference
+ * will stop working at or before this point).
+ * @param {number} tabId
+ * @param {function(chrome.automation.AutomationNode):void} callback
+ * Called when the <code>AutomationNode</code> for the page is available.
+ */
+chrome.automation.getTree = function(tabId, callback) {};
+
+/**
+ * Get the automation tree for the whole desktop which consists of all on screen
+ * views. Note this API is currently only supported on Chrome OS.
+ * @param {function(chrome.automation.AutomationNode):void} callback
+ * Called when the <code>AutomationNode</code> for the page is available.
+ */
+chrome.automation.getDesktop = function(callback) {};
+
+/**
+ * Add a tree change observer. Tree change observers are static/global,
+ * they listen to tree changes across all trees.
+ * @param {function(chrome.automation.TreeChange):void} observer
+ * A listener for tree changes on the <code>AutomationNode</code> tree.
+ */
+chrome.automation.addTreeChangeObserver = function(observer) {};
+
+/**
+ * Remove a tree change observer.
+ * @param {function(chrome.automation.TreeChange):void} observer
+ * A listener for tree changes on the <code>AutomationNode</code> tree.
+ */
+chrome.automation.removeTreeChangeObserver = function(observer) {};
+
+//
+// End auto generated externs; do not edit.
+//
+
+
/**
* @type {chrome.automation.RoleType}
@@ -1229,11 +1591,26 @@ chrome.automation.AutomationNode.prototype.role;
/**
+ * @type {!Object<chrome.automation.StateType, boolean>}
+ */
+chrome.automation.AutomationNode.prototype.state;
+
+
+/**
+ * @type {number}
+ */
+chrome.automation.AutomationNode.prototype.indexInParent;
+
+
+/**
* @type {{
* name: string,
+ * url: string,
* value: string,
- * wordStarts: Array.<number>,
- * wordEnds: Array.<number>
+ * textSelStart: number,
+ * textSelEnd: number,
+ * wordStarts: Array<number>,
+ * wordEnds: Array<number>
* }}
*/
chrome.automation.AutomationNode.prototype.attributes;
@@ -1246,39 +1623,39 @@ chrome.automation.AutomationNode.prototype.root;
/**
- * @return {chrome.automation.AutomationNode}
+ * @type {chrome.automation.AutomationNode}
*/
-chrome.automation.AutomationNode.prototype.firstChild = function() {};
+chrome.automation.AutomationNode.prototype.firstChild;
/**
- * @return {chrome.automation.AutomationNode}
+ * @type {chrome.automation.AutomationNode}
*/
-chrome.automation.AutomationNode.prototype.lastChild = function() {};
+chrome.automation.AutomationNode.prototype.lastChild;
/**
- * @return {chrome.automation.AutomationNode}
+ * @type {chrome.automation.AutomationNode}
*/
-chrome.automation.AutomationNode.prototype.nextSibling = function() {};
+chrome.automation.AutomationNode.prototype.nextSibling;
/**
- * @return {chrome.automation.AutomationNode}
+ * @type {chrome.automation.AutomationNode}
*/
-chrome.automation.AutomationNode.prototype.previousSibling = function() {};
+chrome.automation.AutomationNode.prototype.previousSibling;
/**
- * @return {chrome.automation.AutomationNode}
+ * @type {chrome.automation.AutomationNode}
*/
-chrome.automation.AutomationNode.prototype.parent = function() {};
+chrome.automation.AutomationNode.prototype.parent;
/**
- * @return {!Array.<chrome.automation.AutomationNode>}
+ * @type {!Array<chrome.automation.AutomationNode>}
*/
-chrome.automation.AutomationNode.prototype.children = function() {};
+chrome.automation.AutomationNode.prototype.children;
/**
@@ -1305,232 +1682,72 @@ chrome.automation.AutomationNode.prototype.removeEventListener =
function(eventType, callback, capture) {};
-chrome.automation.AutomationNode.prototype.focus = function() {};
+/**
+ * @type {chrome.automation.AutomationNode}
+ */
+chrome.automation.TreeChange.prototype.target;
/**
- * @param {function(chrome.automation.AutomationNode)} callback
+ * @type {chrome.automation.TreeChangeType}
*/
-chrome.automation.getDesktop = function(callback) {};
+chrome.automation.TreeChange.prototype.type;
/**
- * @param {function(chrome.automation.AutomationNode)} callback
+ * @param {function(chrome.automation.TreeChange) : void}
+ * callback
*/
-chrome.automation.getTree = function(callback) {};
+chrome.automation.AutomationNode.prototype.addTreeChangeObserver =
+ function(callback) {};
/**
- * @const
+ * @param {function(chrome.automation.TreeChange) : void}
+ * callback
*/
-chrome.commands = {};
+chrome.automation.AutomationNode.prototype.removeTreeChangeObserver =
+ function(callback) {};
+
+
+chrome.automation.AutomationNode.prototype.doDefault = function() {};
+
+
+chrome.automation.AutomationNode.prototype.focus = function() {};
+
+/** @type {string} */
+chrome.automation.AutomationNode.prototype.containerLiveStatus;
+
+/** @type {string} */
+chrome.automation.AutomationNode.prototype.containerLiveRelevant;
+
+/** @type {boolean} */
+chrome.automation.AutomationNode.prototype.containerLiveAtomic;
+
+/** @type {boolean} */
+chrome.automation.AutomationNode.prototype.containerLiveBusy;
/**
- * @type {ChromeEvent}
+ * @param {Object} findParams
*/
-chrome.commands.onCommand;
+chrome.automation.AutomationNode.prototype.find = function(findParams) {};
-// Begin auto generated externs; do not edit.
-// The following was generated from tools/json_schema_compiler/compiler.py.
/**
- * Possible events fired on an $(ref:automation.AutomationNode).
- * @enum {string}
+ * @const
*/
-chrome.automation.EventType = {
- activedescendantchanged: 'activedescendantchanged',
- alert: 'alert',
- ariaAttributeChanged: 'ariaAttributeChanged',
- autocorrectionOccured: 'autocorrectionOccured',
- blur: 'blur',
- checkedStateChanged: 'checkedStateChanged',
- childrenChanged: 'childrenChanged',
- focus: 'focus',
- hide: 'hide',
- hover: 'hover',
- invalidStatusChanged: 'invalidStatusChanged',
- layoutComplete: 'layoutComplete',
- liveRegionChanged: 'liveRegionChanged',
- loadComplete: 'loadComplete',
- locationChanged: 'locationChanged',
- menuEnd: 'menuEnd',
- menuListItemSelected: 'menuListItemSelected',
- menuListValueChanged: 'menuListValueChanged',
- menuPopupEnd: 'menuPopupEnd',
- menuPopupStart: 'menuPopupStart',
- menuStart: 'menuStart',
- rowCollapsed: 'rowCollapsed',
- rowCountChanged: 'rowCountChanged',
- rowExpanded: 'rowExpanded',
- scrollPositionChanged: 'scrollPositionChanged',
- scrolledToAnchor: 'scrolledToAnchor',
- selectedChildrenChanged: 'selectedChildrenChanged',
- selectedTextChanged: 'selectedTextChanged',
- selection: 'selection',
- selectionAdd: 'selectionAdd',
- selectionRemove: 'selectionRemove',
- show: 'show',
- textChanged: 'textChanged',
- textInserted: 'textInserted',
- textRemoved: 'textRemoved',
- textSelectionChanged: 'textSelectionChanged',
- valueChanged: 'valueChanged'
-};
+chrome.commands = {};
+
+
/**
- * Describes the purpose of an $(ref:automation.AutomationNode).
- * @enum {string}
+ * @type {ChromeEvent}
*/
-chrome.automation.RoleType = {
- alertDialog: 'alertDialog',
- alert: 'alert',
- annotation: 'annotation',
- application: 'application',
- article: 'article',
- banner: 'banner',
- browser: 'browser',
- busyIndicator: 'busyIndicator',
- button: 'button',
- buttonDropDown: 'buttonDropDown',
- canvas: 'canvas',
- cell: 'cell',
- checkBox: 'checkBox',
- client: 'client',
- colorWell: 'colorWell',
- columnHeader: 'columnHeader',
- column: 'column',
- comboBox: 'comboBox',
- complementary: 'complementary',
- contentInfo: 'contentInfo',
- definition: 'definition',
- descriptionListDetail: 'descriptionListDetail',
- descriptionListTerm: 'descriptionListTerm',
- desktop: 'desktop',
- dialog: 'dialog',
- directory: 'directory',
- disclosureTriangle: 'disclosureTriangle',
- div: 'div',
- document: 'document',
- drawer: 'drawer',
- editableText: 'editableText',
- embeddedObject: 'embeddedObject',
- footer: 'footer',
- form: 'form',
- grid: 'grid',
- group: 'group',
- growArea: 'growArea',
- heading: 'heading',
- helpTag: 'helpTag',
- horizontalRule: 'horizontalRule',
- iframe: 'iframe',
- ignored: 'ignored',
- imageMapLink: 'imageMapLink',
- imageMap: 'imageMap',
- image: 'image',
- incrementor: 'incrementor',
- inlineTextBox: 'inlineTextBox',
- labelText: 'labelText',
- legend: 'legend',
- link: 'link',
- listBoxOption: 'listBoxOption',
- listBox: 'listBox',
- listItem: 'listItem',
- listMarker: 'listMarker',
- list: 'list',
- locationBar: 'locationBar',
- log: 'log',
- main: 'main',
- marquee: 'marquee',
- mathElement: 'mathElement',
- math: 'math',
- matte: 'matte',
- menuBar: 'menuBar',
- menuButton: 'menuButton',
- menuItem: 'menuItem',
- menuListOption: 'menuListOption',
- menuListPopup: 'menuListPopup',
- menu: 'menu',
- navigation: 'navigation',
- note: 'note',
- outline: 'outline',
- pane: 'pane',
- paragraph: 'paragraph',
- popUpButton: 'popUpButton',
- presentational: 'presentational',
- progressIndicator: 'progressIndicator',
- radioButton: 'radioButton',
- radioGroup: 'radioGroup',
- region: 'region',
- rootWebArea: 'rootWebArea',
- rowHeader: 'rowHeader',
- row: 'row',
- rulerMarker: 'rulerMarker',
- ruler: 'ruler',
- svgRoot: 'svgRoot',
- scrollArea: 'scrollArea',
- scrollBar: 'scrollBar',
- seamlessWebArea: 'seamlessWebArea',
- search: 'search',
- sheet: 'sheet',
- slider: 'slider',
- sliderThumb: 'sliderThumb',
- spinButtonPart: 'spinButtonPart',
- spinButton: 'spinButton',
- splitGroup: 'splitGroup',
- splitter: 'splitter',
- staticText: 'staticText',
- status: 'status',
- systemWide: 'systemWide',
- tabGroup: 'tabGroup',
- tabList: 'tabList',
- tabPanel: 'tabPanel',
- tab: 'tab',
- tableHeaderContainer: 'tableHeaderContainer',
- table: 'table',
- textArea: 'textArea',
- textField: 'textField',
- timer: 'timer',
- titleBar: 'titleBar',
- toggleButton: 'toggleButton',
- toolbar: 'toolbar',
- treeGrid: 'treeGrid',
- treeItem: 'treeItem',
- tree: 'tree',
- unknown: 'unknown',
- tooltip: 'tooltip',
- valueIndicator: 'valueIndicator',
- webArea: 'webArea',
- window: 'window'
-};
+chrome.commands.onCommand;
+
/**
- * Describes characteristics of an $(ref:automation.AutomationNode).
- * @enum {string}
+ * @param {function(Array<{description: string,
+ * name: string,
+ * shortcut: string}>): void} callback
*/
-chrome.automation.StateType = {
- busy: 'busy',
- checked: 'checked',
- collapsed: 'collapsed',
- default: 'default',
- disabled: 'disabled',
- editable: 'editable',
- enabled: 'enabled',
- expanded: 'expanded',
- focusable: 'focusable',
- focused: 'focused',
- haspopup: 'haspopup',
- hovered: 'hovered',
- indeterminate: 'indeterminate',
- invisible: 'invisible',
- linked: 'linked',
- multiselectable: 'multiselectable',
- offscreen: 'offscreen',
- pressed: 'pressed',
- protected: 'protected',
- readOnly: 'readOnly',
- required: 'required',
- selectable: 'selectable',
- selected: 'selected',
- vertical: 'vertical',
- visited: 'visited'
-};
-// End auto generated externs; do not edit.
+chrome.commands.getAll = function(callback) {};
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/common/chromevox.js b/chromium/chrome/browser/resources/chromeos/chromevox/common/chromevox.js
index c5f9aa6025d..db20382a06a 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/common/chromevox.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/common/chromevox.js
@@ -150,11 +150,11 @@ cvox.ChromeVox.verbosity = cvox.VERBOSITY_VERBOSE;
cvox.ChromeVox.typingEcho = 0;
/**
* Echoing on key press events.
- * @type {Object.<string, boolean>}
+ * @type {Object<string, boolean>}
*/
cvox.ChromeVox.keyEcho = {};
/**
- * @type {Object.<string, {x:number, y:number}>}
+ * @type {Object<string, {x:number, y:number}>}
*/
cvox.ChromeVox.position = {};
/**
@@ -181,10 +181,10 @@ if (cvox.ChromeVox.isChromeOS) {
* where the subsequent independent key downs (while modifier keys are down)
* are a part of the same shortcut. This array is populated in
* cvox.ChromeVoxKbHandler.loadKeyToFunctionsTable().
- * @type {!Array.<cvox.KeySequence>}
+ * @type {!Array<cvox.KeySequence>}
*/
cvox.ChromeVox.sequenceSwitchKeyCodes = [];
-/** @type {Object.<string, boolean>} */
+/** @type {Object<string, boolean>} */
cvox.ChromeVox.visitedUrls = {};
/**
* This function can be called before doing an operation that may trigger
@@ -275,3 +275,20 @@ function $(id) {
* @param {Array} tabs
*/
cvox.ChromeVox.injectChromeVoxIntoTabs = function(tabs) {};
+
+/**
+ * Returns whether the document has focus, taking into account whether
+ * it's hidden and also that if an iframe or webview element has focus,
+ * the focus is really inside that frame and not in this document.
+ * @return {boolean} True if the document has focus.
+ */
+cvox.ChromeVox.documentHasFocus = function() {
+ if (!document.hasFocus() || document.hidden) {
+ return false;
+ }
+ if (document.activeElement.tagName == 'IFRAME' ||
+ document.activeElement.tagName == 'WEBVIEW') {
+ return false;
+ }
+ return true;
+};
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/common/chromevox_json.js b/chromium/chrome/browser/resources/chromeos/chromevox/common/chromevox_json.js
index ae18d75a726..96c78ca9f31 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/common/chromevox_json.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/common/chromevox_json.js
@@ -15,9 +15,7 @@ goog.provide('cvox.ChromeVoxJSON');
*/
if (!cvox.ChromeVoxJSON) {
- /**
- * @type {Object}
- */
+ /** Placeholder object. */
cvox.ChromeVoxJSON = {};
}
@@ -227,7 +225,7 @@ if (window.JSON && window.JSON.toString() == '[object JSON]') {
if (typeof cvox.ChromeVoxJSON.stringify !== 'function') {
/**
* @param {*} value Input object.
- * @param {(Array.<string>|(function(string, *) : *)|null)=} replacer
+ * @param {(Array<string>|(function(string, *) : *)|null)=} replacer
* Replacer array or function.
* @param {(number|string|null)=} space Whitespace character.
* @return {string} json string which represents jsonObj.
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/common/command_store.js b/chromium/chrome/browser/resources/chromeos/chromevox/common/command_store.js
index a50e56547f9..3fdf69e409e 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/common/command_store.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/common/command_store.js
@@ -38,7 +38,7 @@ goog.require('cvox.PlatformFilter');
/**
* Returns all of the categories in the store as an array.
- * @return {Array.<string>} The collection of categories.
+ * @return {Array<string>} The collection of categories.
*/
cvox.CommandStore.categories = function() {
var categorySet = {};
@@ -79,7 +79,7 @@ cvox.CommandStore.categoryForCommand = function(command) {
/**
* Gets all commands for a category.
* @param {string} category The category to query.
- * @return {Array.<string>} The commands, if any.
+ * @return {Array<string>} The commands, if any.
*/
cvox.CommandStore.commandsForCategory = function(category) {
var ret = [];
@@ -95,7 +95,7 @@ cvox.CommandStore.commandsForCategory = function(category) {
/**
* List of commands and their properties
- * @type {Object.<string, {forward: (undefined|boolean),
+ * @type {Object<string, {forward: (undefined|boolean),
* backward: (undefined|boolean),
* announce: boolean,
* category: (undefined|string),
@@ -678,7 +678,7 @@ cvox.CommandStore.CMD_WHITELIST = {
/**
* List of find next commands and their associated data.
- * @type {Object.<string, {predicate: string,
+ * @type {Object<string, {predicate: string,
* forwardError: string,
* backwardError: string}>}
* predicate: The name of the predicate. This must be defined in DomPredicates.
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/common/composite_tts.js b/chromium/chrome/browser/resources/chromeos/chromevox/common/composite_tts.js
index 1b1cfb0d628..bba28401525 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/common/composite_tts.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/common/composite_tts.js
@@ -19,7 +19,7 @@ goog.require('cvox.TtsInterface');
*/
cvox.CompositeTts = function() {
/**
- * @type {Array.<cvox.TtsInterface>}
+ * @type {Array<cvox.TtsInterface>}
* @private
*/
this.ttsEngines_ = [];
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/common/content_editable_extractor.js b/chromium/chrome/browser/resources/chromeos/chromevox/common/content_editable_extractor.js
index e7f3c4a9cc8..ce0d9415f0d 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/common/content_editable_extractor.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/common/content_editable_extractor.js
@@ -40,14 +40,14 @@ cvox.ContentEditableExtractor = function() {
/**
* Map from line index to a data structure containing the start
* and end index within the line.
- * @type {Object.<number, {startIndex: number, endIndex: number}>}
+ * @type {Object<number, {startIndex: number, endIndex: number}>}
* @private
*/
this.lines_ = {};
/**
* Map from 0-based character index to 0-based line index.
- * @type {Array.<number>}
+ * @type {Array<number>}
* @private
*/
this.characterToLineMap_ = [];
@@ -61,7 +61,7 @@ cvox.ContentEditableExtractor.prototype.update = function(element) {
/**
* Map from line index to a data structure containing the start
* and end index within the line.
- * @type {Object.<number, {startIndex: number, endIndex: number}>}
+ * @type {Object<number, {startIndex: number, endIndex: number}>}
*/
var lines = {0: {startIndex: 0, endIndex: 0}};
var startCursor = new cvox.Cursor(element, 0, '');
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/common/description_util.js b/chromium/chrome/browser/resources/chromeos/chromevox/common/description_util.js
index 67e161eeda6..0fb55784c3d 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/common/description_util.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/common/description_util.js
@@ -23,7 +23,7 @@ goog.require('cvox.TraverseMath');
/**
* Lists all Node tagName's who's description is derived from its subtree.
- * @type {Object.<string, boolean>}
+ * @type {Object<string, boolean>}
*/
cvox.DescriptionUtil.COLLECTION_NODE_TYPE = {
'H1': true,
@@ -38,7 +38,7 @@ cvox.DescriptionUtil.COLLECTION_NODE_TYPE = {
* Get a control's complete description in the same format as if you
* navigated to the node.
* @param {Element} control A control.
- * @param {Array.<Node>=} opt_changedAncestors The changed ancestors that will
+ * @param {Array<Node>=} opt_changedAncestors The changed ancestors that will
* be used to determine what needs to be spoken. If this is not provided, the
* ancestors used to determine what needs to be spoken will just be the control
* itself and its surrounding control if it has one.
@@ -91,7 +91,7 @@ cvox.DescriptionUtil.getControlDescription =
* ancestor nodes. The ancestors are in order from the highest in the
* tree to the lowest, i.e. ending with the current leaf node.
*
- * @param {Array.<Node>} ancestorsArray An array of ancestor nodes.
+ * @param {Array<Node>} ancestorsArray An array of ancestor nodes.
* @param {boolean} recursive Whether or not the element's subtree should
* be used; true by default.
* @param {number} verbosity The verbosity setting.
@@ -182,7 +182,7 @@ cvox.DescriptionUtil.getDescriptionFromAncestors = function(
* @param {boolean} recursive Whether or not the element's subtree should
* be used; true by default.
* @param {number} verbosity The verbosity setting.
- * @return {!Array.<cvox.NavDescription>} The description of the navigation
+ * @return {!Array<cvox.NavDescription>} The description of the navigation
* action.
*/
cvox.DescriptionUtil.getDescriptionFromNavigation =
@@ -232,7 +232,7 @@ cvox.DescriptionUtil.getDescriptionFromNavigation =
* This is an awkward design, and should be changed in the future.
* @param {!cvox.CursorSelection} prevSel The previous selection.
* @param {!cvox.CursorSelection} sel The selection.
- * @return {!Array.<!cvox.NavDescription>} The descriptions as described above.
+ * @return {!Array<!cvox.NavDescription>} The descriptions as described above.
*/
cvox.DescriptionUtil.getCollectionDescription = function(prevSel, sel) {
var descriptions = cvox.DescriptionUtil.getRawDescriptions_(prevSel, sel);
@@ -253,7 +253,7 @@ cvox.DescriptionUtil.subWalker_ = new cvox.BareObjectWalker();
* Returns the descriptions that would be gotten by an object walker.
* @param {!cvox.CursorSelection} prevSel The previous selection.
* @param {!cvox.CursorSelection} sel The selection.
- * @return {!Array.<!cvox.NavDescription>} The descriptions.
+ * @return {!Array<!cvox.NavDescription>} The descriptions.
* @private
*/
cvox.DescriptionUtil.getRawDescriptions_ = function(prevSel, sel) {
@@ -303,7 +303,7 @@ cvox.DescriptionUtil.getRawDescriptions_ = function(prevSel, sel) {
* object walker.
* @param {?Element} prevnode The previous element if there is one.
* @param {!Element} node The target element.
- * @return {!Array.<!cvox.NavDescription>} The descriptions.
+ * @return {!Array<!cvox.NavDescription>} The descriptions.
*/
cvox.DescriptionUtil.getFullDescriptionsFromChildren =
function(prevnode, node) {
@@ -352,7 +352,7 @@ cvox.DescriptionUtil.getFullDescriptionsFromChildren =
/**
* Modify the descriptions to say that it is a collection.
- * @param {Array.<cvox.NavDescription>} descriptions The descriptions.
+ * @param {Array<cvox.NavDescription>} descriptions The descriptions.
* @private
*/
cvox.DescriptionUtil.insertCollectionDescription_ = function(descriptions) {
@@ -387,8 +387,8 @@ cvox.DescriptionUtil.insertCollectionDescription_ = function(descriptions) {
/**
* Pulls the annotations from a description array.
- * @param {Array.<cvox.NavDescription>} descriptions The descriptions.
- * @return {Array.<string>} The annotations.
+ * @param {Array<cvox.NavDescription>} descriptions The descriptions.
+ * @return {Array<string>} The annotations.
* @private
*/
cvox.DescriptionUtil.getAnnotations_ = function(descriptions) {
@@ -432,7 +432,7 @@ cvox.DescriptionUtil.isAnnotationCollection_ = function(annotation) {
/**
* Determines whether to describe the exit of an ancestor chain.
- * @param {Array.<Node>} ancestors The ancestors exited during navigation.
+ * @param {Array<Node>} ancestors The ancestors exited during navigation.
* @return {boolean} The result.
* @private
*/
@@ -452,7 +452,7 @@ cvox.DescriptionUtil.shouldDescribeExit_ = function(ancestors) {
/**
* Generates a description for a math node.
* @param {!Node} node The given node.
- * @return {!Array.<cvox.NavDescription>} A list of Navigation descriptions.
+ * @return {!Array<cvox.NavDescription>} A list of Navigation descriptions.
*/
cvox.DescriptionUtil.getMathDescription = function(node) {
// TODO (sorge) This function should evantually be removed. Descriptions
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/common/dom_predicates.js b/chromium/chrome/browser/resources/chromeos/chromevox/common/dom_predicates.js
index f47833243f0..685a8ecf8f4 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/common/dom_predicates.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/common/dom_predicates.js
@@ -14,7 +14,7 @@ goog.provide('cvox.DomPredicates');
/**
* Checkbox.
- * @param {Array.<Node>} nodes An array of nodes to check.
+ * @param {Array<Node>} nodes An array of nodes to check.
* @return {?Node} Node in the array that is a checkbox.
*/
cvox.DomPredicates.checkboxPredicate = function(nodes) {
@@ -31,7 +31,7 @@ cvox.DomPredicates.checkboxPredicate = function(nodes) {
/**
* Radio button.
- * @param {Array.<Node>} nodes An array of nodes to check.
+ * @param {Array<Node>} nodes An array of nodes to check.
* @return {?Node} Node in the array that is a radio button.
*/
cvox.DomPredicates.radioPredicate = function(nodes) {
@@ -47,7 +47,7 @@ cvox.DomPredicates.radioPredicate = function(nodes) {
/**
* Slider.
- * @param {Array.<Node>} nodes An array of nodes to check.
+ * @param {Array<Node>} nodes An array of nodes to check.
* @return {?Node} Node in the array that is a slider.
*/
cvox.DomPredicates.sliderPredicate = function(nodes) {
@@ -63,7 +63,7 @@ cvox.DomPredicates.sliderPredicate = function(nodes) {
/**
* Graphic.
- * @param {Array.<Node>} nodes An array of nodes to check.
+ * @param {Array<Node>} nodes An array of nodes to check.
* @return {?Node} Node in the array that is a graphic.
*/
cvox.DomPredicates.graphicPredicate = function(nodes) {
@@ -79,7 +79,7 @@ cvox.DomPredicates.graphicPredicate = function(nodes) {
/**
* Button.
- * @param {Array.<Node>} nodes An array of nodes to check.
+ * @param {Array<Node>} nodes An array of nodes to check.
* @return {?Node} Node in the array that is a button.
*/
cvox.DomPredicates.buttonPredicate = function(nodes) {
@@ -98,7 +98,7 @@ cvox.DomPredicates.buttonPredicate = function(nodes) {
/**
* Combo box.
- * @param {Array.<Node>} nodes An array of nodes to check.
+ * @param {Array<Node>} nodes An array of nodes to check.
* @return {?Node} Node in the array that is a combo box.
*/
cvox.DomPredicates.comboBoxPredicate = function(nodes) {
@@ -115,7 +115,7 @@ cvox.DomPredicates.comboBoxPredicate = function(nodes) {
/**
* Editable text field.
- * @param {Array.<Node>} nodes An array of nodes to check.
+ * @param {Array<Node>} nodes An array of nodes to check.
* @return {?Node} Node in the array that is an editable text field.
*/
cvox.DomPredicates.editTextPredicate = function(nodes) {
@@ -134,7 +134,7 @@ cvox.DomPredicates.editTextPredicate = function(nodes) {
/**
* Heading.
- * @param {Array.<Node>} nodes An array of nodes to check.
+ * @param {Array<Node>} nodes An array of nodes to check.
* @return {?Node} Node in the array that is a heading.
*/
cvox.DomPredicates.headingPredicate = function(nodes) {
@@ -159,7 +159,7 @@ cvox.DomPredicates.headingPredicate = function(nodes) {
/**
* Heading level 1.
- * @param {Array.<Node>} nodes An array of nodes to check.
+ * @param {Array<Node>} nodes An array of nodes to check.
* @return {?Node} Node in the array that is a heading level 1.
* TODO: handle ARIA headings with ARIA heading levels?
*/
@@ -170,7 +170,7 @@ cvox.DomPredicates.heading1Predicate = function(nodes) {
/**
* Heading level 2.
- * @param {Array.<Node>} nodes An array of nodes to check.
+ * @param {Array<Node>} nodes An array of nodes to check.
* @return {?Node} Node in the array that is a heading level 2.
*/
cvox.DomPredicates.heading2Predicate = function(nodes) {
@@ -180,7 +180,7 @@ cvox.DomPredicates.heading2Predicate = function(nodes) {
/**
* Heading level 3.
- * @param {Array.<Node>} nodes An array of nodes to check.
+ * @param {Array<Node>} nodes An array of nodes to check.
* @return {?Node} Node in the array that is a heading level 3.
*/
cvox.DomPredicates.heading3Predicate = function(nodes) {
@@ -190,7 +190,7 @@ cvox.DomPredicates.heading3Predicate = function(nodes) {
/**
* Heading level 4.
- * @param {Array.<Node>} nodes An array of nodes to check.
+ * @param {Array<Node>} nodes An array of nodes to check.
* @return {?Node} Node in the array that is a heading level 4.
*/
cvox.DomPredicates.heading4Predicate = function(nodes) {
@@ -200,7 +200,7 @@ cvox.DomPredicates.heading4Predicate = function(nodes) {
/**
* Heading level 5.
- * @param {Array.<Node>} nodes An array of nodes to check.
+ * @param {Array<Node>} nodes An array of nodes to check.
* @return {?Node} Node in the array that is a heading level 5.
*/
cvox.DomPredicates.heading5Predicate = function(nodes) {
@@ -210,7 +210,7 @@ cvox.DomPredicates.heading5Predicate = function(nodes) {
/**
* Heading level 6.
- * @param {Array.<Node>} nodes An array of nodes to check.
+ * @param {Array<Node>} nodes An array of nodes to check.
* @return {?Node} Node in the array that is a heading level 6.
*/
cvox.DomPredicates.heading6Predicate = function(nodes) {
@@ -220,7 +220,7 @@ cvox.DomPredicates.heading6Predicate = function(nodes) {
/**
* Link.
- * @param {Array.<Node>} nodes An array of nodes to check.
+ * @param {Array<Node>} nodes An array of nodes to check.
* @return {?Node} Node in the array that is a link.
*/
cvox.DomPredicates.linkPredicate = function(nodes) {
@@ -236,7 +236,7 @@ cvox.DomPredicates.linkPredicate = function(nodes) {
/**
* Table.
- * @param {Array.<Node>} nodes An array of nodes to check.
+ * @param {Array<Node>} nodes An array of nodes to check.
* @return {?Node} Node in the array that is a data table.
*/
cvox.DomPredicates.tablePredicate = function(nodes) {
@@ -251,7 +251,7 @@ cvox.DomPredicates.tablePredicate = function(nodes) {
/**
* Table Cell.
- * @param {Array.<Node>} nodes An array of nodes to check.
+ * @param {Array<Node>} nodes An array of nodes to check.
* @return {?Node} Node in the array that is a table cell.
*/
cvox.DomPredicates.cellPredicate = function(nodes) {
@@ -269,7 +269,7 @@ cvox.DomPredicates.cellPredicate = function(nodes) {
/**
* Visited link.
- * @param {Array.<Node>} nodes An array of nodes to check.
+ * @param {Array<Node>} nodes An array of nodes to check.
* @return {?Node} Node in the array that is a visited link.
*/
cvox.DomPredicates.visitedLinkPredicate = function(nodes) {
@@ -284,7 +284,7 @@ cvox.DomPredicates.visitedLinkPredicate = function(nodes) {
/**
* List.
- * @param {Array.<Node>} nodes An array of nodes to check.
+ * @param {Array<Node>} nodes An array of nodes to check.
* @return {?Node} Node in the array that is a list.
*/
cvox.DomPredicates.listPredicate = function(nodes) {
@@ -301,7 +301,7 @@ cvox.DomPredicates.listPredicate = function(nodes) {
/**
* List item.
- * @param {Array.<Node>} nodes An array of nodes to check.
+ * @param {Array<Node>} nodes An array of nodes to check.
* @return {?Node} Node in the array that is a list item.
*/
cvox.DomPredicates.listItemPredicate = function(nodes) {
@@ -318,7 +318,7 @@ cvox.DomPredicates.listItemPredicate = function(nodes) {
/**
* Blockquote.
- * @param {Array.<Node>} nodes An array of nodes to check.
+ * @param {Array<Node>} nodes An array of nodes to check.
* @return {?Node} Node in the array that is a blockquote.
*/
cvox.DomPredicates.blockquotePredicate = function(nodes) {
@@ -328,7 +328,7 @@ cvox.DomPredicates.blockquotePredicate = function(nodes) {
/**
* Form field.
- * @param {Array.<Node>} nodes An array of nodes to check.
+ * @param {Array<Node>} nodes An array of nodes to check.
* @return {?Node} Node in the array that is any type of form field.
*/
cvox.DomPredicates.formFieldPredicate = function(nodes) {
@@ -343,7 +343,7 @@ cvox.DomPredicates.formFieldPredicate = function(nodes) {
/**
* ARIA landmark.
- * @param {Array.<Node>} nodes An array of nodes to check.
+ * @param {Array<Node>} nodes An array of nodes to check.
* @return {?Node} Node in the array that is an ARIA landmark.
*/
cvox.DomPredicates.landmarkPredicate = function(nodes) {
@@ -375,7 +375,7 @@ cvox.DomPredicates.containsTagName_ = function(arr, tagName) {
/**
* MathML expression
- * @param {Array.<Node>} nodes An array of nodes to check.
+ * @param {Array<Node>} nodes An array of nodes to check.
* @return {?Node} Node in the array that is a math expression.
*/
cvox.DomPredicates.mathPredicate = function(nodes) {
@@ -385,7 +385,7 @@ cvox.DomPredicates.mathPredicate = function(nodes) {
/**
* SECTION: A section is anything that indicates a new section. This includes
* headings and landmarks.
- * @param {Array.<Node>} nodes An array of nodes to check.
+ * @param {Array<Node>} nodes An array of nodes to check.
* @return {?Node} Node in the array that is considered a section marker.
*/
cvox.DomPredicates.sectionPredicate = function(nodes) {
@@ -416,7 +416,7 @@ cvox.DomPredicates.sectionPredicate = function(nodes) {
/**
* CONTROL: A control is anything that the user can interact with. This includes
* form fields and links.
- * @param {Array.<Node>} nodes An array of nodes to check.
+ * @param {Array<Node>} nodes An array of nodes to check.
* @return {?Node} Node in the array that is considered a control.
*/
cvox.DomPredicates.controlPredicate = function(nodes) {
@@ -434,7 +434,7 @@ cvox.DomPredicates.controlPredicate = function(nodes) {
/**
* Caption.
- * @param {Array.<Node>} nodes An array of nodes to check.
+ * @param {Array<Node>} nodes An array of nodes to check.
* @return {?Node} Node in the array that is a caption.
*/
cvox.DomPredicates.captionPredicate = function(nodes) {
@@ -448,7 +448,7 @@ cvox.DomPredicates.captionPredicate = function(nodes) {
/**
* Article.
- * @param {Array.<Node>} nodes An array of nodes to check.
+ * @param {Array<Node>} nodes An array of nodes to check.
* @return {?Node} Node in the array that is a article.
*/
cvox.DomPredicates.articlePredicate = function(nodes) {
@@ -464,7 +464,7 @@ cvox.DomPredicates.articlePredicate = function(nodes) {
/**
* Media.
- * @param {Array.<Node>} nodes An array of nodes to check.
+ * @param {Array<Node>} nodes An array of nodes to check.
* @return {?Node} Node in the array that is a media widget (video or audio).
*/
cvox.DomPredicates.mediaPredicate = function(nodes) {
@@ -480,7 +480,7 @@ cvox.DomPredicates.mediaPredicate = function(nodes) {
/**
* Ordered List.
- * @param {Array.<Node>} nodes An array of nodes to check.
+ * @param {Array<Node>} nodes An array of nodes to check.
* @return {?Node} Node in the array that is a ordered list.
*/
cvox.DomPredicates.orderedListPredicate = function(nodes) {
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/common/dom_util.js b/chromium/chrome/browser/resources/chromeos/chromevox/common/dom_util.js
index 6f9ba4add25..f27290da55b 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/common/dom_util.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/common/dom_util.js
@@ -108,7 +108,7 @@ cvox.DomUtil.TAG_TO_INFORMATION_TABLE_BRIEF_MSG = {
/**
* These tags are treated as text formatters.
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
cvox.DomUtil.FORMATTING_TAGS =
['B', 'BIG', 'CITE', 'CODE', 'DFN', 'EM', 'I', 'KBD', 'SAMP', 'SMALL',
@@ -230,6 +230,9 @@ cvox.DomUtil.hasInvisibleAncestor_ = function(node) {
*/
cvox.DomUtil.hasVisibleNodeSubtree_ = function(root, recursive) {
if (!(root instanceof Element)) {
+ if (!root.parentElement) {
+ return false;
+ }
var parentStyle = document.defaultView
.getComputedStyle(root.parentElement, null);
var isVisibleParent = !cvox.DomUtil.isInvisibleStyle(parentStyle);
@@ -946,7 +949,7 @@ cvox.DomUtil.getImageTitle = function(node) {
* the complete set of ids they map to, so that we can skip elements that
* just label other elements and not double-speak them. We cache this
* result and then throw it away at the next event loop.
- * @return {Object.<string, boolean>} Set of all ids that are mapped
+ * @return {Object<string, boolean>} Set of all ids that are mapped
* by aria-labelledby.
*/
cvox.DomUtil.getLabelledByTargets = function() {
@@ -1179,7 +1182,7 @@ cvox.DomUtil.computeHasContent_ = function(node) {
* is the current node.
*
* @param {Node} targetNode The node to get ancestors for.
- * @return {Array.<Node>} An array of ancestors for the targetNode.
+ * @return {Array<Node>} An array of ancestors for the targetNode.
*/
cvox.DomUtil.getAncestors = function(targetNode) {
var ancestors = new Array();
@@ -1231,7 +1234,7 @@ cvox.DomUtil.compareAncestors = function(ancestorsA, ancestorsB) {
* @param {Node} currentNode The current node.
* @param {boolean=} opt_fallback True returns node's ancestors in the case
* where node's ancestors is a subset of previousNode's ancestors.
- * @return {Array.<Node>} An array of unique ancestors for the current node
+ * @return {Array<Node>} An array of unique ancestors for the current node
* (inclusive).
*/
cvox.DomUtil.getUniqueAncestors = function(
@@ -1262,6 +1265,10 @@ cvox.DomUtil.getRoleMsg = function(targetNode, verbosity) {
cvox.DomUtil.isInternalLink(targetNode)) {
info = 'internal_link';
} else if (targetNode.tagName == 'A' &&
+ targetNode.getAttribute('href') &&
+ cvox.ChromeVox.visitedUrls[targetNode.href]) {
+ info = 'visited_link';
+ } else if (targetNode.tagName == 'A' &&
targetNode.getAttribute('name')) {
info = ''; // Don't want to add any role to anchors.
} else if (targetNode.isContentEditable) {
@@ -1393,11 +1400,6 @@ cvox.DomUtil.getStateMsgs = function(targetNode, primary) {
info.push(['aria_disabled_true']);
}
- if (cvox.DomPredicates.linkPredicate([targetNode]) &&
- cvox.ChromeVox.visitedUrls[targetNode.href]) {
- info.push(['visited_url']);
- }
-
if (targetNode.accessKey) {
info.push(['access_key', targetNode.accessKey]);
}
@@ -1969,7 +1971,7 @@ cvox.DomUtil.getContainingTable = function(node, kwargs) {
/**
* Extracts a table node from a list of nodes.
- * @param {Array.<Node>} nodes The list of nodes.
+ * @param {Array<Node>} nodes The list of nodes.
* @param {{allowCaptions: (undefined|boolean)}=} kwargs Optional named args.
* allowCaptions: If true, will return true even if inside a caption. False
* by default.
@@ -2212,7 +2214,7 @@ cvox.DomUtil.countNodes = function(root, p) {
* using a depth first search.
* @param {Node} root The root of the tree to search.
* @param {function(Node) : boolean} p The filter function.
- * @param {Array.<Node>} rv The found nodes are added to this array.
+ * @param {Array<Node>} rv The found nodes are added to this array.
* @param {boolean} findOne If true we exit after the first found node.
* @param {number} maxChildCount The max child count. This is used as a kill
* switch - if there are more nodes than this, terminate the search.
@@ -2260,7 +2262,7 @@ cvox.DomUtil.toArray = function(nodeList) {
/**
* Creates a new element with the same attributes and no children.
* @param {Node|Text} node A node to clone.
- * @param {Object.<string, boolean>} skipattrs Set the attribute to true to
+ * @param {Object<string, boolean>} skipattrs Set the attribute to true to
* skip it during cloning.
* @return {Node|Text} The cloned node.
*/
@@ -2288,7 +2290,7 @@ cvox.DomUtil.shallowChildlessClone = function(node, skipattrs) {
/**
* Creates a new element with the same attributes and clones of children.
* @param {Node|Text} node A node to clone.
- * @param {Object.<string, boolean>} skipattrs Set the attribute to true to
+ * @param {Object<string, boolean>} skipattrs Set the attribute to true to
* skip it during cloning.
* @return {Node|Text} The cloned node.
*/
@@ -2377,7 +2379,7 @@ cvox.DomUtil.getContainingMath = function(node) {
/**
* Extracts a math node from a list of nodes.
- * @param {Array.<Node>} nodes The list of nodes.
+ * @param {Array<Node>} nodes The list of nodes.
* @return {Node} The math node if the list of nodes contains a math node.
* Null if it does not.
*/
@@ -2406,8 +2408,8 @@ cvox.DomUtil.isMath = function(node) {
/**
* Specifies node classes in which we expect maths expressions a alt text.
- * @type {{tex: Array.<string>,
- * asciimath: Array.<string>}}
+ * @type {{tex: Array<string>,
+ * asciimath: Array<string>}}
*/
// These are the classes for which we assume they contain Maths in the ALT or
// TITLE attribute.
@@ -2448,9 +2450,14 @@ cvox.DomUtil.isMathImg = function(node) {
if (node.tagName != 'IMG') {
return false;
}
- var className = node.className.toLowerCase();
- return cvox.DomUtil.ALT_MATH_CLASSES.tex.indexOf(className) != -1 ||
- cvox.DomUtil.ALT_MATH_CLASSES.asciimath.indexOf(className) != -1;
+ for (var i = 0, className; className = node.classList.item(i); i++) {
+ className = className.toLowerCase();
+ if (cvox.DomUtil.ALT_MATH_CLASSES.tex.indexOf(className) != -1 ||
+ cvox.DomUtil.ALT_MATH_CLASSES.asciimath.indexOf(className) != -1) {
+ return true;
+ }
+ }
+ return false;
};
@@ -2536,7 +2543,7 @@ cvox.DomUtil.getNodeTagName = function(node) {
/**
* Cleaning up a list of nodes to remove empty text nodes.
* @param {NodeList} nodes The nodes list.
- * @return {!Array.<Node|string|null>} The cleaned up list of nodes.
+ * @return {!Array<Node|string|null>} The cleaned up list of nodes.
*/
cvox.DomUtil.purgeNodes = function(nodes) {
return cvox.DomUtil.toArray(nodes).
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/common/editable_text.js b/chromium/chrome/browser/resources/chromeos/chromevox/common/editable_text.js
index 5968c61f33e..d336d37f1d5 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/common/editable_text.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/common/editable_text.js
@@ -3,106 +3,34 @@
// found in the LICENSE file.
goog.provide('cvox.ChromeVoxEditableContentEditable');
+goog.provide('cvox.ChromeVoxEditableElement');
goog.provide('cvox.ChromeVoxEditableHTMLInput');
goog.provide('cvox.ChromeVoxEditableTextArea');
-goog.provide('cvox.ChromeVoxEditableTextBase');
-goog.provide('cvox.TextChangeEvent');
goog.provide('cvox.TextHandlerInterface');
-goog.provide('cvox.TypingEcho');
goog.require('cvox.BrailleTextHandler');
+goog.require('cvox.ChromeVoxEditableTextBase');
goog.require('cvox.ContentEditableExtractor');
goog.require('cvox.DomUtil');
goog.require('cvox.EditableTextAreaShadow');
+goog.require('cvox.TextChangeEvent');
goog.require('cvox.TtsInterface');
-goog.require('goog.i18n.MessageFormat');
/**
- * @fileoverview Gives the user spoken feedback as they type, select text,
- * and move the cursor in editable text controls, including multiline
- * controls.
+ * @fileoverview Gives the user spoken and braille feedback as they type,
+ * select text, and move the cursor in editable HTML text controls, including
+ * multiline controls and contenteditable regions.
*
- * The majority of the code is in ChromeVoxEditableTextBase, a generalized
- * class that takes the current state in the form of a text string, a
- * cursor start location and a cursor end location, and calls a speak
- * method with the resulting text to be spoken. If the control is multiline,
- * information about line breaks (including automatic ones) is also needed.
- *
- * Two subclasses, ChromeVoxEditableHTMLInput and
+ * The two subclasses, ChromeVoxEditableHTMLInput and
* ChromeVoxEditableTextArea, take a HTML input (type=text) or HTML
* textarea node (respectively) in the constructor, and automatically
* handle retrieving the current state of the control, including
* computing line break information for a textarea using an offscreen
- * shadow object. It is still the responsibility of the user of this
- * class to trap key and focus events and call this class's update
- * method.
- *
- */
-
-
-/**
- * A class containing the information needed to speak
- * a text change event to the user.
+ * shadow object. It is the responsibility of the user of these classes to
+ * trap key and focus events and call the update method as needed.
*
- * @constructor
- * @param {string} newValue The new string value of the editable text control.
- * @param {number} newStart The new 0-based start cursor/selection index.
- * @param {number} newEnd The new 0-based end cursor/selection index.
- * @param {boolean} triggeredByUser .
- */
-cvox.TextChangeEvent = function(newValue, newStart, newEnd, triggeredByUser) {
- this.value = newValue;
- this.start = newStart;
- this.end = newEnd;
- this.triggeredByUser = triggeredByUser;
-
- // Adjust offsets to be in left to right order.
- if (this.start > this.end) {
- var tempOffset = this.end;
- this.end = this.start;
- this.start = tempOffset;
- }
-};
-
-
-/**
- * A list of typing echo options.
- * This defines the way typed characters get spoken.
- * CHARACTER: echoes typed characters.
- * WORD: echoes a word once a breaking character is typed (i.e. spacebar).
- * CHARACTER_AND_WORD: combines CHARACTER and WORD behavior.
- * NONE: speaks nothing when typing.
- * COUNT: The number of possible echo levels.
- * @enum
- */
-cvox.TypingEcho = {
- CHARACTER: 0,
- WORD: 1,
- CHARACTER_AND_WORD: 2,
- NONE: 3,
- COUNT: 4
-};
-
-
-/**
- * @param {number} cur Current typing echo.
- * @return {number} Next typing echo.
- */
-cvox.TypingEcho.cycle = function(cur) {
- return (cur + 1) % cvox.TypingEcho.COUNT;
-};
-
-
-/**
- * Return if characters should be spoken given the typing echo option.
- * @param {number} typingEcho Typing echo option.
- * @return {boolean} Whether the character should be spoken.
*/
-cvox.TypingEcho.shouldSpeakChar = function(typingEcho) {
- return typingEcho == cvox.TypingEcho.CHARACTER_AND_WORD ||
- typingEcho == cvox.TypingEcho.CHARACTER;
-};
/**
@@ -120,659 +48,6 @@ cvox.TextHandlerInterface.prototype.changed = function(evt) {};
/**
- * A class representing an abstracted editable text control.
- * @param {string} value The string value of the editable text control.
- * @param {number} start The 0-based start cursor/selection index.
- * @param {number} end The 0-based end cursor/selection index.
- * @param {boolean} isPassword Whether the text control if a password field.
- * @param {cvox.TtsInterface} tts A TTS object.
- * @constructor
- */
-cvox.ChromeVoxEditableTextBase = function(value, start, end, isPassword, tts) {
- /**
- * Current value of the text field.
- * @type {string}
- * @protected
- */
- this.value = value;
-
- /**
- * 0-based selection start index.
- * @type {number}
- * @protected
- */
- this.start = start;
-
- /**
- * 0-based selection end index.
- * @type {number}
- * @protected
- */
- this.end = end;
-
- /**
- * True if this is a password field.
- * @type {boolean}
- * @protected
- */
- this.isPassword = isPassword;
-
- /**
- * Text-to-speech object implementing speak() and stop() methods.
- * @type {cvox.TtsInterface}
- * @protected
- */
- this.tts = tts;
-
- /**
- * Whether or not the text field is multiline.
- * @type {boolean}
- * @protected
- */
- this.multiline = false;
-
- /**
- * An optional handler for braille output.
- * @type {cvox.BrailleTextHandler|undefined}
- * @private
- */
- this.brailleHandler_ = cvox.ChromeVox.braille ?
- new cvox.BrailleTextHandler(cvox.ChromeVox.braille) : undefined;
-};
-
-
-/**
- * Performs setup for this element.
- */
-cvox.ChromeVoxEditableTextBase.prototype.setup = function() {};
-
-
-/**
- * Performs teardown for this element.
- */
-cvox.ChromeVoxEditableTextBase.prototype.teardown = function() {};
-
-
-/**
- * Whether or not moving the cursor from one character to another considers
- * the cursor to be a block (false) or an i-beam (true).
- *
- * If the cursor is a block, then the value of the character to the right
- * of the cursor index is always read when the cursor moves, no matter what
- * the previous cursor location was - this is how PC screenreaders work.
- *
- * If the cursor is an i-beam, moving the cursor by one character reads the
- * character that was crossed over, which may be the character to the left or
- * right of the new cursor index depending on the direction.
- *
- * If the current platform is a Mac, we will use an i-beam cursor. If not,
- * then we will use the block cursor.
- *
- * @type {boolean}
- */
-cvox.ChromeVoxEditableTextBase.useIBeamCursor = cvox.ChromeVox.isMac;
-
-
-/**
- * Switches on or off typing echo based on events. When set, editable text
- * updates for single-character insertions are handled in event watcher's key
- * press handler.
- * @type {boolean}
- */
-cvox.ChromeVoxEditableTextBase.eventTypingEcho = false;
-
-
-/**
- * The maximum number of characters that are short enough to speak in response
- * to an event. For example, if the user selects "Hello", we will speak
- * "Hello, selected", but if the user selects 1000 characters, we will speak
- * "text selected" instead.
- *
- * @type {number}
- */
-cvox.ChromeVoxEditableTextBase.prototype.maxShortPhraseLen = 60;
-
-
-/**
- * Whether or not the text control is a password.
- *
- * @type {boolean}
- */
-cvox.ChromeVoxEditableTextBase.prototype.isPassword = false;
-
-
-/**
- * Whether or not the last update to the text and selection was described.
- *
- * Some consumers of this flag like |ChromeVoxEventWatcher| depend on and
- * react to when this flag is false by generating alternative feedback.
- * @type {boolean}
- */
-cvox.ChromeVoxEditableTextBase.prototype.lastChangeDescribed = false;
-
-
-/**
- * Get the line number corresponding to a particular index.
- * Default implementation that can be overridden by subclasses.
- * @param {number} index The 0-based character index.
- * @return {number} The 0-based line number corresponding to that character.
- */
-cvox.ChromeVoxEditableTextBase.prototype.getLineIndex = function(index) {
- return 0;
-};
-
-
-/**
- * Get the start character index of a line.
- * Default implementation that can be overridden by subclasses.
- * @param {number} index The 0-based line index.
- * @return {number} The 0-based index of the first character in this line.
- */
-cvox.ChromeVoxEditableTextBase.prototype.getLineStart = function(index) {
- return 0;
-};
-
-
-/**
- * Get the end character index of a line.
- * Default implementation that can be overridden by subclasses.
- * @param {number} index The 0-based line index.
- * @return {number} The 0-based index of the end of this line.
- */
-cvox.ChromeVoxEditableTextBase.prototype.getLineEnd = function(index) {
- return this.value.length;
-};
-
-
-/**
- * Get the full text of the current line.
- * @param {number} index The 0-based line index.
- * @return {string} The text of the line.
- */
-cvox.ChromeVoxEditableTextBase.prototype.getLine = function(index) {
- var lineStart = this.getLineStart(index);
- var lineEnd = this.getLineEnd(index);
- return this.value.substr(lineStart, lineEnd - lineStart);
-};
-
-
-/**
- * @param {string} ch The character to test.
- * @return {boolean} True if a character is whitespace.
- */
-cvox.ChromeVoxEditableTextBase.prototype.isWhitespaceChar = function(ch) {
- return ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t';
-};
-
-
-/**
- * @param {string} ch The character to test.
- * @return {boolean} True if a character breaks a word, used to determine
- * if the previous word should be spoken.
- */
-cvox.ChromeVoxEditableTextBase.prototype.isWordBreakChar = function(ch) {
- return !!ch.match(/^\W$/);
-};
-
-
-/**
- * @param {cvox.TextChangeEvent} evt The new text changed event to test.
- * @return {boolean} True if the event, when compared to the previous text,
- * should trigger description.
- */
-cvox.ChromeVoxEditableTextBase.prototype.shouldDescribeChange = function(evt) {
- if (evt.value == this.value &&
- evt.start == this.start &&
- evt.end == this.end) {
- return false;
- }
- return true;
-};
-
-
-/**
- * Speak text, but if it's a single character, describe the character.
- * @param {string} str The string to speak.
- * @param {boolean=} opt_triggeredByUser True if the speech was triggered by a
- * user action.
- * @param {Object=} opt_personality Personality used to speak text.
- */
-cvox.ChromeVoxEditableTextBase.prototype.speak =
- function(str, opt_triggeredByUser, opt_personality) {
- // If there is a node associated with the editable text object,
- // make sure that node has focus before speaking it.
- if (this.node && (document.activeElement != this.node)) {
- return;
- }
- var queueMode = cvox.QueueMode.QUEUE;
- if (opt_triggeredByUser === true) {
- queueMode = cvox.QueueMode.FLUSH;
- }
- this.tts.speak(str, queueMode, opt_personality || {});
-};
-
-
-/**
- * Update the state of the text and selection and describe any changes as
- * appropriate.
- *
- * @param {cvox.TextChangeEvent} evt The text change event.
- */
-cvox.ChromeVoxEditableTextBase.prototype.changed = function(evt) {
- if (!this.shouldDescribeChange(evt)) {
- this.lastChangeDescribed = false;
- return;
- }
-
- if (evt.value == this.value) {
- this.describeSelectionChanged(evt);
- } else {
- this.describeTextChanged(evt);
- }
- this.lastChangeDescribed = true;
-
- this.value = evt.value;
- this.start = evt.start;
- this.end = evt.end;
-
- if (this.brailleHandler_) {
- var line = this.getLine(this.getLineIndex(evt.start));
- var lineStart = this.getLineStart(this.getLineIndex(evt.start));
- var start = evt.start - lineStart;
- var end = Math.min(evt.end - lineStart, line.length);
- this.brailleHandler_.changed(line, start, end, this.multiline, this.node,
- lineStart);
- }
-};
-
-
-/**
- * Describe a change in the selection or cursor position when the text
- * stays the same.
- * @param {cvox.TextChangeEvent} evt The text change event.
- */
-cvox.ChromeVoxEditableTextBase.prototype.describeSelectionChanged =
- function(evt) {
- // TODO(deboer): Factor this into two function:
- // - one to determine the selection event
- // - one to speak
-
- if (this.isPassword) {
- this.speak((new goog.i18n.MessageFormat(cvox.ChromeVox.msgs.getMsg('dot'))
- .format({'COUNT': 1})), evt.triggeredByUser);
- return;
- }
- if (evt.start == evt.end) {
- // It's currently a cursor.
- if (this.start != this.end) {
- // It was previously a selection, so just announce 'unselected'.
- this.speak(cvox.ChromeVox.msgs.getMsg('Unselected'), evt.triggeredByUser);
- } else if (this.getLineIndex(this.start) !=
- this.getLineIndex(evt.start)) {
- // Moved to a different line; read it.
- var lineValue = this.getLine(this.getLineIndex(evt.start));
- if (lineValue == '') {
- lineValue = cvox.ChromeVox.msgs.getMsg('text_box_blank');
- } else if (/^\s+$/.test(lineValue)) {
- lineValue = cvox.ChromeVox.msgs.getMsg('text_box_whitespace');
- }
- this.speak(lineValue, evt.triggeredByUser);
- } else if (this.start == evt.start + 1 ||
- this.start == evt.start - 1) {
- // Moved by one character; read it.
- if (!cvox.ChromeVoxEditableTextBase.useIBeamCursor) {
- if (evt.start == this.value.length) {
- if (cvox.ChromeVox.verbosity == cvox.VERBOSITY_VERBOSE) {
- this.speak(cvox.ChromeVox.msgs.getMsg('end_of_text_verbose'),
- evt.triggeredByUser);
- } else {
- this.speak(cvox.ChromeVox.msgs.getMsg('end_of_text_brief'),
- evt.triggeredByUser);
- }
- } else {
- this.speak(this.value.substr(evt.start, 1),
- evt.triggeredByUser,
- {'phoneticCharacters': evt.triggeredByUser});
- }
- } else {
- this.speak(this.value.substr(Math.min(this.start, evt.start), 1),
- evt.triggeredByUser,
- {'phoneticCharacters': evt.triggeredByUser});
- }
- } else {
- // Moved by more than one character. Read all characters crossed.
- this.speak(this.value.substr(Math.min(this.start, evt.start),
- Math.abs(this.start - evt.start)), evt.triggeredByUser);
- }
- } else {
- // It's currently a selection.
- if (this.start + 1 == evt.start &&
- this.end == this.value.length &&
- evt.end == this.value.length) {
- // Autocomplete: the user typed one character of autocompleted text.
- this.speak(this.value.substr(this.start, 1), evt.triggeredByUser);
- this.speak(this.value.substr(evt.start));
- } else if (this.start == this.end) {
- // It was previously a cursor.
- this.speak(this.value.substr(evt.start, evt.end - evt.start),
- evt.triggeredByUser);
- this.speak(cvox.ChromeVox.msgs.getMsg('selected'));
- } else if (this.start == evt.start && this.end < evt.end) {
- this.speak(this.value.substr(this.end, evt.end - this.end),
- evt.triggeredByUser);
- this.speak(cvox.ChromeVox.msgs.getMsg('added_to_selection'));
- } else if (this.start == evt.start && this.end > evt.end) {
- this.speak(this.value.substr(evt.end, this.end - evt.end),
- evt.triggeredByUser);
- this.speak(cvox.ChromeVox.msgs.getMsg('removed_from_selection'));
- } else if (this.end == evt.end && this.start > evt.start) {
- this.speak(this.value.substr(evt.start, this.start - evt.start),
- evt.triggeredByUser);
- this.speak(cvox.ChromeVox.msgs.getMsg('added_to_selection'));
- } else if (this.end == evt.end && this.start < evt.start) {
- this.speak(this.value.substr(this.start, evt.start - this.start),
- evt.triggeredByUser);
- this.speak(cvox.ChromeVox.msgs.getMsg('removed_from_selection'));
- } else {
- // The selection changed but it wasn't an obvious extension of
- // a previous selection. Just read the new selection.
- this.speak(this.value.substr(evt.start, evt.end - evt.start),
- evt.triggeredByUser);
- this.speak(cvox.ChromeVox.msgs.getMsg('selected'));
- }
- }
-};
-
-
-/**
- * Describe a change where the text changes.
- * @param {cvox.TextChangeEvent} evt The text change event.
- */
-cvox.ChromeVoxEditableTextBase.prototype.describeTextChanged = function(evt) {
- var personality = {};
- if (evt.value.length < this.value.length) {
- personality = cvox.AbstractTts.PERSONALITY_DELETED;
- }
- if (this.isPassword) {
- this.speak((new goog.i18n.MessageFormat(cvox.ChromeVox.msgs.getMsg('dot'))
- .format({'COUNT': 1})), evt.triggeredByUser, personality);
- return;
- }
-
- var value = this.value;
- var len = value.length;
- var newLen = evt.value.length;
- var autocompleteSuffix = '';
- // Make a copy of evtValue and evtEnd to avoid changing anything in
- // the event itself.
- var evtValue = evt.value;
- var evtEnd = evt.end;
-
- // First, see if there's a selection at the end that might have been
- // added by autocomplete. If so, strip it off into a separate variable.
- if (evt.start < evtEnd && evtEnd == newLen) {
- autocompleteSuffix = evtValue.substr(evt.start);
- evtValue = evtValue.substr(0, evt.start);
- evtEnd = evt.start;
- }
-
- // Now see if the previous selection (if any) was deleted
- // and any new text was inserted at that character position.
- // This would handle pasting and entering text by typing, both from
- // a cursor and from a selection.
- var prefixLen = this.start;
- var suffixLen = len - this.end;
- if (newLen >= prefixLen + suffixLen + (evtEnd - evt.start) &&
- evtValue.substr(0, prefixLen) == value.substr(0, prefixLen) &&
- evtValue.substr(newLen - suffixLen) == value.substr(this.end)) {
- // However, in a dynamic content editable, defer to authoritative events
- // (clipboard, key press) to reduce guess work when observing insertions.
- // Only use this logic when observing deletions (and insertion of word
- // breakers).
- // TODO(dtseng): Think about a more reliable way to do this.
- if (!(this instanceof cvox.ChromeVoxEditableContentEditable) ||
- newLen < len ||
- this.isWordBreakChar(evt.value[newLen - 1] || '')) {
- this.describeTextChangedHelper(
- evt, prefixLen, suffixLen, autocompleteSuffix, personality);
- }
- return;
- }
-
- // Next, see if one or more characters were deleted from the previous
- // cursor position and the new cursor is in the expected place. This
- // handles backspace, forward-delete, and similar shortcuts that delete
- // a word or line.
- prefixLen = evt.start;
- suffixLen = newLen - evtEnd;
- if (this.start == this.end &&
- evt.start == evtEnd &&
- evtValue.substr(0, prefixLen) == value.substr(0, prefixLen) &&
- evtValue.substr(newLen - suffixLen) ==
- value.substr(len - suffixLen)) {
- this.describeTextChangedHelper(
- evt, prefixLen, suffixLen, autocompleteSuffix, personality);
- return;
- }
-
- // If all else fails, we assume the change was not the result of a normal
- // user editing operation, so we'll have to speak feedback based only
- // on the changes to the text, not the cursor position / selection.
- // First, restore the autocomplete text if any.
- evtValue += autocompleteSuffix;
-
- // Try to do a diff between the new and the old text. If it is a one character
- // insertion/deletion at the start or at the end, just speak that character.
- if ((evtValue.length == (value.length + 1)) ||
- ((evtValue.length + 1) == value.length)) {
- // The user added text either to the beginning or the end.
- if (evtValue.length > value.length) {
- if (evtValue.indexOf(value) == 0) {
- this.speak(evtValue[evtValue.length - 1], evt.triggeredByUser,
- personality);
- return;
- } else if (evtValue.indexOf(value) == 1) {
- this.speak(evtValue[0], evt.triggeredByUser, personality);
- return;
- }
- }
- // The user deleted text either from the beginning or the end.
- if (evtValue.length < value.length) {
- if (value.indexOf(evtValue) == 0) {
- this.speak(value[value.length - 1], evt.triggeredByUser, personality);
- return;
- } else if (value.indexOf(evtValue) == 1) {
- this.speak(value[0], evt.triggeredByUser, personality);
- return;
- }
- }
- }
-
- if (this.multiline) {
- // Fall back to announce deleted but omit the text that was deleted.
- if (evt.value.length < this.value.length) {
- this.speak(cvox.ChromeVox.msgs.getMsg('text_deleted'),
- evt.triggeredByUser, personality);
- }
- // The below is a somewhat loose way to deal with non-standard
- // insertions/deletions. Intentionally skip for multiline since deletion
- // announcements are covered above and insertions are non-standard (possibly
- // due to auto complete). Since content editable's often refresh content by
- // removing and inserting entire chunks of text, this type of logic often
- // results in unintended consequences such as reading all text when only one
- // character has been entered.
- return;
- }
-
- // If the text is short, just speak the whole thing.
- if (newLen <= this.maxShortPhraseLen) {
- this.describeTextChangedHelper(evt, 0, 0, '', personality);
- return;
- }
-
- // Otherwise, look for the common prefix and suffix, but back up so
- // that we can speak complete words, to be minimally confusing.
- prefixLen = 0;
- while (prefixLen < len &&
- prefixLen < newLen &&
- value[prefixLen] == evtValue[prefixLen]) {
- prefixLen++;
- }
- while (prefixLen > 0 && !this.isWordBreakChar(value[prefixLen - 1])) {
- prefixLen--;
- }
-
- suffixLen = 0;
- while (suffixLen < (len - prefixLen) &&
- suffixLen < (newLen - prefixLen) &&
- value[len - suffixLen - 1] == evtValue[newLen - suffixLen - 1]) {
- suffixLen++;
- }
- while (suffixLen > 0 && !this.isWordBreakChar(value[len - suffixLen])) {
- suffixLen--;
- }
-
- this.describeTextChangedHelper(evt, prefixLen, suffixLen, '', personality);
-};
-
-
-/**
- * The function called by describeTextChanged after it's figured out
- * what text was deleted, what text was inserted, and what additional
- * autocomplete text was added.
- * @param {cvox.TextChangeEvent} evt The text change event.
- * @param {number} prefixLen The number of characters in the common prefix
- * of this.value and newValue.
- * @param {number} suffixLen The number of characters in the common suffix
- * of this.value and newValue.
- * @param {string} autocompleteSuffix The autocomplete string that was added
- * to the end, if any. It should be spoken at the end of the utterance
- * describing the change.
- * @param {Object=} opt_personality Personality to speak the text.
- */
-cvox.ChromeVoxEditableTextBase.prototype.describeTextChangedHelper = function(
- evt, prefixLen, suffixLen, autocompleteSuffix, opt_personality) {
- var len = this.value.length;
- var newLen = evt.value.length;
- var deletedLen = len - prefixLen - suffixLen;
- var deleted = this.value.substr(prefixLen, deletedLen);
- var insertedLen = newLen - prefixLen - suffixLen;
- var inserted = evt.value.substr(prefixLen, insertedLen);
- var utterance = '';
- var triggeredByUser = evt.triggeredByUser;
-
- if (insertedLen > 1) {
- utterance = inserted;
- } else if (insertedLen == 1) {
- if ((cvox.ChromeVox.typingEcho == cvox.TypingEcho.WORD ||
- cvox.ChromeVox.typingEcho == cvox.TypingEcho.CHARACTER_AND_WORD) &&
- this.isWordBreakChar(inserted) &&
- prefixLen > 0 &&
- !this.isWordBreakChar(evt.value.substr(prefixLen - 1, 1))) {
- // Speak previous word.
- var index = prefixLen;
- while (index > 0 && !this.isWordBreakChar(evt.value[index - 1])) {
- index--;
- }
- if (index < prefixLen) {
- utterance = evt.value.substr(index, prefixLen + 1 - index);
- } else {
- utterance = inserted;
- triggeredByUser = false; // Implies QUEUE_MODE_QUEUE.
- }
- } else if (cvox.ChromeVox.typingEcho == cvox.TypingEcho.CHARACTER ||
- cvox.ChromeVox.typingEcho == cvox.TypingEcho.CHARACTER_AND_WORD) {
- // This particular case is handled in event watcher. See the key press
- // handler for more details.
- utterance = cvox.ChromeVoxEditableTextBase.eventTypingEcho ? '' :
- inserted;
- }
- } else if (deletedLen > 1 && !autocompleteSuffix) {
- utterance = deleted + ', deleted';
- } else if (deletedLen == 1) {
- utterance = deleted;
- }
-
- if (autocompleteSuffix && utterance) {
- utterance += ', ' + autocompleteSuffix;
- } else if (autocompleteSuffix) {
- utterance = autocompleteSuffix;
- }
-
- if (utterance) {
- this.speak(utterance, triggeredByUser, opt_personality);
- }
-};
-
-
-/**
- * Moves the cursor forward by one character.
- * @return {boolean} True if the action was handled.
- */
-cvox.ChromeVoxEditableTextBase.prototype.moveCursorToNextCharacter =
- function() { return false; };
-
-
-/**
- * Moves the cursor backward by one character.
- * @return {boolean} True if the action was handled.
- */
-cvox.ChromeVoxEditableTextBase.prototype.moveCursorToPreviousCharacter =
- function() { return false; };
-
-
-/**
- * Moves the cursor forward by one word.
- * @return {boolean} True if the action was handled.
- */
-cvox.ChromeVoxEditableTextBase.prototype.moveCursorToNextWord =
- function() { return false; };
-
-
-/**
- * Moves the cursor backward by one word.
- * @return {boolean} True if the action was handled.
- */
-cvox.ChromeVoxEditableTextBase.prototype.moveCursorToPreviousWord =
- function() { return false; };
-
-
-/**
- * Moves the cursor forward by one line.
- * @return {boolean} True if the action was handled.
- */
-cvox.ChromeVoxEditableTextBase.prototype.moveCursorToNextLine =
- function() { return false; };
-
-
-/**
- * Moves the cursor backward by one line.
- * @return {boolean} True if the action was handled.
- */
-cvox.ChromeVoxEditableTextBase.prototype.moveCursorToPreviousLine =
- function() { return false; };
-
-
-/**
- * Moves the cursor forward by one paragraph.
- * @return {boolean} True if the action was handled.
- */
-cvox.ChromeVoxEditableTextBase.prototype.moveCursorToNextParagraph =
- function() { return false; };
-
-
-/**
- * Moves the cursor backward by one paragraph.
- * @return {boolean} True if the action was handled.
- */
-cvox.ChromeVoxEditableTextBase.prototype.moveCursorToPreviousParagraph =
- function() { return false; };
-
-
-/******************************************/
-
-
-/**
* A subclass of ChromeVoxEditableTextBase a text element that's part of
* the webpage DOM. Contains common code shared by both EditableHTMLInput
* and EditableTextArea, but that might not apply to a non-DOM text box.
@@ -790,6 +65,14 @@ cvox.ChromeVoxEditableElement = function(node, value, start, end, isPassword,
goog.base(this, value, start, end, isPassword, tts);
/**
+ * An optional handler for braille output.
+ * @type {cvox.BrailleTextHandler|undefined}
+ * @private
+ */
+ this.brailleHandler_ = cvox.ChromeVox.braille ?
+ new cvox.BrailleTextHandler(cvox.ChromeVox.braille) : undefined;
+
+ /**
* The DOM node which allows text input.
* @type {Element}
* @protected
@@ -807,12 +90,7 @@ goog.inherits(cvox.ChromeVoxEditableElement,
cvox.ChromeVoxEditableTextBase);
-/**
- * Update the state of the text and selection and describe any changes as
- * appropriate.
- *
- * @param {cvox.TextChangeEvent} evt The text change event.
- */
+/** @override */
cvox.ChromeVoxEditableElement.prototype.changed = function(evt) {
// Ignore changes to the cursor and selection if they happen immediately
// after the description was just spoken. This avoid double-speaking when,
@@ -826,10 +104,24 @@ cvox.ChromeVoxEditableElement.prototype.changed = function(evt) {
this.justSpokeDescription_ = false;
}
goog.base(this, 'changed', evt);
+ if (this.lastChangeDescribed) {
+ this.brailleCurrentLine_();
+ }
};
/** @override */
+cvox.ChromeVoxEditableElement.prototype.speak = function(
+ str, opt_triggeredByUser, opt_personality) {
+ // If there is a node associated with the editable text object,
+ // make sure that node has focus before speaking it.
+ if (this.node && (document.activeElement != this.node)) {
+ return;
+ }
+ goog.base(this, 'speak', str, opt_triggeredByUser, opt_personality);
+};
+
+/** @override */
cvox.ChromeVoxEditableElement.prototype.moveCursorToNextCharacter = function() {
var node = this.node;
node.selectionEnd++;
@@ -916,6 +208,29 @@ cvox.ChromeVoxEditableElement.prototype.moveCursorToPreviousParagraph =
return true;
};
+/**
+ * Shows the current line on the braille display.
+ * @private
+ */
+cvox.ChromeVoxEditableElement.prototype.brailleCurrentLine_ = function() {
+ if (this.brailleHandler_) {
+ var lineIndex = this.getLineIndex(this.start);
+ var line = this.getLine(lineIndex);
+ // Collapsable whitespace inside the contenteditable is represented
+ // as non-breaking spaces. This confuses braille input (which relies on
+ // the text being added to be the same as the text in the input field).
+ // Since the non-breaking spaces are just an artifact of how
+ // contenteditable is implemented, normalize to normal spaces instead.
+ if (this instanceof cvox.ChromeVoxEditableContentEditable) {
+ line = line.replace(/\u00A0/g, ' ');
+ }
+ var lineStart = this.getLineStart(lineIndex);
+ var start = this.start - lineStart;
+ var end = Math.min(this.end - lineStart, line.length);
+ this.brailleHandler_.changed(line, start, end, this.multiline, this.node,
+ lineStart);
+ }
+};
/******************************************/
@@ -1237,9 +552,7 @@ cvox.ChromeVoxEditableContentEditable.prototype.getExtractor = function() {
};
-/**
- * @override
- */
+/** @override */
cvox.ChromeVoxEditableContentEditable.prototype.changed =
function(evt) {
if (!evt.triggeredByUser) {
@@ -1248,6 +561,11 @@ cvox.ChromeVoxEditableContentEditable.prototype.changed =
// Take over here if we can't describe a change; assume it's a blank line.
if (!this.shouldDescribeChange(evt)) {
this.speak(cvox.ChromeVox.msgs.getMsg('text_box_blank'), true);
+ if (this.brailleHandler_) {
+ this.brailleHandler_.changed('' /*line*/, 0 /*start*/, 0 /*end*/,
+ true /*multiline*/, null /*element*/,
+ evt.start /*lineStart*/);
+ }
} else {
goog.base(this, 'changed', evt);
}
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/common/editable_text_area_shadow.js b/chromium/chrome/browser/resources/chromeos/chromevox/common/editable_text_area_shadow.js
index 58940f2626a..bd381156c69 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/common/editable_text_area_shadow.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/common/editable_text_area_shadow.js
@@ -23,14 +23,14 @@ cvox.EditableTextAreaShadow = function() {
/**
* Map from line index to a data structure containing the start
* and end index within the line.
- * @type {Object.<number, {startIndex: number, endIndex: number}>}
+ * @type {Object<number, {startIndex: number, endIndex: number}>}
* @private
*/
this.lines_ = {};
/**
* Map from 0-based character index to 0-based line index.
- * @type {Array.<number>}
+ * @type {Array<number>}
* @private
*/
this.characterToLineMap_ = [];
@@ -72,7 +72,7 @@ cvox.EditableTextAreaShadow.prototype.update = function(element) {
/**
* Map from line index to a data structure containing the start
* and end index within the line.
- * @type {Object.<number, {startIndex: number, endIndex: number}>}
+ * @type {Object<number, {startIndex: number, endIndex: number}>}
*/
var lines = {0: {startIndex: 0, endIndex: 0}};
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/common/editable_text_base.js b/chromium/chrome/browser/resources/chromeos/chromevox/common/editable_text_base.js
new file mode 100644
index 00000000000..573123915ba
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/common/editable_text_base.js
@@ -0,0 +1,712 @@
+// Copyright 2015 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.
+
+goog.provide('cvox.ChromeVoxEditableTextBase');
+goog.provide('cvox.TextChangeEvent');
+goog.provide('cvox.TypingEcho');
+
+goog.require('cvox.AbstractTts');
+goog.require('cvox.ChromeVox');
+goog.require('cvox.TtsInterface');
+goog.require('goog.i18n.MessageFormat');
+
+
+/**
+ * @fileoverview Generalized logic for providing spoken feedback when editing
+ * text fields, both single and multiline fields.
+ *
+ * {@code ChromeVoxEditableTextBase} is a generalized class that takes the
+ * current state in the form of a text string, a cursor start location and a
+ * cursor end location, and calls a speak method with the resulting text to
+ * be spoken. This class can be used directly for single line fields or
+ * extended to override methods that extract lines for multiline fields
+ * or to provide other customizations.
+ */
+
+
+/**
+ * A class containing the information needed to speak
+ * a text change event to the user.
+ *
+ * @constructor
+ * @param {string} newValue The new string value of the editable text control.
+ * @param {number} newStart The new 0-based start cursor/selection index.
+ * @param {number} newEnd The new 0-based end cursor/selection index.
+ * @param {boolean} triggeredByUser .
+ */
+cvox.TextChangeEvent = function(newValue, newStart, newEnd, triggeredByUser) {
+ this.value = newValue;
+ this.start = newStart;
+ this.end = newEnd;
+ this.triggeredByUser = triggeredByUser;
+
+ // Adjust offsets to be in left to right order.
+ if (this.start > this.end) {
+ var tempOffset = this.end;
+ this.end = this.start;
+ this.start = tempOffset;
+ }
+};
+
+
+/**
+ * A list of typing echo options.
+ * This defines the way typed characters get spoken.
+ * CHARACTER: echoes typed characters.
+ * WORD: echoes a word once a breaking character is typed (i.e. spacebar).
+ * CHARACTER_AND_WORD: combines CHARACTER and WORD behavior.
+ * NONE: speaks nothing when typing.
+ * COUNT: The number of possible echo levels.
+ * @enum
+ */
+cvox.TypingEcho = {
+ CHARACTER: 0,
+ WORD: 1,
+ CHARACTER_AND_WORD: 2,
+ NONE: 3,
+ COUNT: 4
+};
+
+
+/**
+ * @param {number} cur Current typing echo.
+ * @return {number} Next typing echo.
+ */
+cvox.TypingEcho.cycle = function(cur) {
+ return (cur + 1) % cvox.TypingEcho.COUNT;
+};
+
+
+/**
+ * Return if characters should be spoken given the typing echo option.
+ * @param {number} typingEcho Typing echo option.
+ * @return {boolean} Whether the character should be spoken.
+ */
+cvox.TypingEcho.shouldSpeakChar = function(typingEcho) {
+ return typingEcho == cvox.TypingEcho.CHARACTER_AND_WORD ||
+ typingEcho == cvox.TypingEcho.CHARACTER;
+};
+
+
+/**
+ * A class representing an abstracted editable text control.
+ * @param {string} value The string value of the editable text control.
+ * @param {number} start The 0-based start cursor/selection index.
+ * @param {number} end The 0-based end cursor/selection index.
+ * @param {boolean} isPassword Whether the text control if a password field.
+ * @param {cvox.TtsInterface} tts A TTS object.
+ * @constructor
+ */
+cvox.ChromeVoxEditableTextBase = function(value, start, end, isPassword, tts) {
+ /**
+ * Current value of the text field.
+ * @type {string}
+ * @protected
+ */
+ this.value = value;
+
+ /**
+ * 0-based selection start index.
+ * @type {number}
+ * @protected
+ */
+ this.start = start;
+
+ /**
+ * 0-based selection end index.
+ * @type {number}
+ * @protected
+ */
+ this.end = end;
+
+ /**
+ * True if this is a password field.
+ * @type {boolean}
+ * @protected
+ */
+ this.isPassword = isPassword;
+
+ /**
+ * Text-to-speech object implementing speak() and stop() methods.
+ * @type {cvox.TtsInterface}
+ * @protected
+ */
+ this.tts = tts;
+
+ /**
+ * Whether or not the text field is multiline.
+ * @type {boolean}
+ * @protected
+ */
+ this.multiline = false;
+
+ /**
+ * Whether or not the last update to the text and selection was described.
+ *
+ * Some consumers of this flag like |ChromeVoxEventWatcher| depend on and
+ * react to when this flag is false by generating alternative feedback.
+ * @type {boolean}
+ */
+ this.lastChangeDescribed = false;
+
+};
+
+
+/**
+ * Performs setup for this element.
+ */
+cvox.ChromeVoxEditableTextBase.prototype.setup = function() {};
+
+
+/**
+ * Performs teardown for this element.
+ */
+cvox.ChromeVoxEditableTextBase.prototype.teardown = function() {};
+
+
+/**
+ * Whether or not moving the cursor from one character to another considers
+ * the cursor to be a block (false) or an i-beam (true).
+ *
+ * If the cursor is a block, then the value of the character to the right
+ * of the cursor index is always read when the cursor moves, no matter what
+ * the previous cursor location was - this is how PC screenreaders work.
+ *
+ * If the cursor is an i-beam, moving the cursor by one character reads the
+ * character that was crossed over, which may be the character to the left or
+ * right of the new cursor index depending on the direction.
+ *
+ * If the current platform is a Mac, we will use an i-beam cursor. If not,
+ * then we will use the block cursor.
+ *
+ * @type {boolean}
+ */
+cvox.ChromeVoxEditableTextBase.useIBeamCursor = cvox.ChromeVox.isMac;
+
+
+/**
+ * Switches on or off typing echo based on events. When set, editable text
+ * updates for single-character insertions are handled in event watcher's key
+ * press handler.
+ * @type {boolean}
+ */
+cvox.ChromeVoxEditableTextBase.eventTypingEcho = false;
+
+
+/**
+ * The maximum number of characters that are short enough to speak in response
+ * to an event. For example, if the user selects "Hello", we will speak
+ * "Hello, selected", but if the user selects 1000 characters, we will speak
+ * "text selected" instead.
+ *
+ * @type {number}
+ */
+cvox.ChromeVoxEditableTextBase.prototype.maxShortPhraseLen = 60;
+
+
+/**
+ * Get the line number corresponding to a particular index.
+ * Default implementation that can be overridden by subclasses.
+ * @param {number} index The 0-based character index.
+ * @return {number} The 0-based line number corresponding to that character.
+ */
+cvox.ChromeVoxEditableTextBase.prototype.getLineIndex = function(index) {
+ return 0;
+};
+
+
+/**
+ * Get the start character index of a line.
+ * Default implementation that can be overridden by subclasses.
+ * @param {number} index The 0-based line index.
+ * @return {number} The 0-based index of the first character in this line.
+ */
+cvox.ChromeVoxEditableTextBase.prototype.getLineStart = function(index) {
+ return 0;
+};
+
+
+/**
+ * Get the end character index of a line.
+ * Default implementation that can be overridden by subclasses.
+ * @param {number} index The 0-based line index.
+ * @return {number} The 0-based index of the end of this line.
+ */
+cvox.ChromeVoxEditableTextBase.prototype.getLineEnd = function(index) {
+ return this.value.length;
+};
+
+
+/**
+ * Get the full text of the current line.
+ * @param {number} index The 0-based line index.
+ * @return {string} The text of the line.
+ */
+cvox.ChromeVoxEditableTextBase.prototype.getLine = function(index) {
+ var lineStart = this.getLineStart(index);
+ var lineEnd = this.getLineEnd(index);
+ return this.value.substr(lineStart, lineEnd - lineStart);
+};
+
+
+/**
+ * @param {string} ch The character to test.
+ * @return {boolean} True if a character is whitespace.
+ */
+cvox.ChromeVoxEditableTextBase.prototype.isWhitespaceChar = function(ch) {
+ return ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t';
+};
+
+
+/**
+ * @param {string} ch The character to test.
+ * @return {boolean} True if a character breaks a word, used to determine
+ * if the previous word should be spoken.
+ */
+cvox.ChromeVoxEditableTextBase.prototype.isWordBreakChar = function(ch) {
+ return !!ch.match(/^\W$/);
+};
+
+
+/**
+ * @param {cvox.TextChangeEvent} evt The new text changed event to test.
+ * @return {boolean} True if the event, when compared to the previous text,
+ * should trigger description.
+ */
+cvox.ChromeVoxEditableTextBase.prototype.shouldDescribeChange = function(evt) {
+ if (evt.value == this.value &&
+ evt.start == this.start &&
+ evt.end == this.end) {
+ return false;
+ }
+ return true;
+};
+
+
+/**
+ * Speak text, but if it's a single character, describe the character.
+ * @param {string} str The string to speak.
+ * @param {boolean=} opt_triggeredByUser True if the speech was triggered by a
+ * user action.
+ * @param {Object=} opt_personality Personality used to speak text.
+ */
+cvox.ChromeVoxEditableTextBase.prototype.speak =
+ function(str, opt_triggeredByUser, opt_personality) {
+ var queueMode = cvox.QueueMode.QUEUE;
+ if (opt_triggeredByUser === true) {
+ queueMode = cvox.QueueMode.FLUSH;
+ }
+ this.tts.speak(str, queueMode, opt_personality || {});
+};
+
+
+/**
+ * Update the state of the text and selection and describe any changes as
+ * appropriate.
+ *
+ * @param {cvox.TextChangeEvent} evt The text change event.
+ */
+cvox.ChromeVoxEditableTextBase.prototype.changed = function(evt) {
+ if (!this.shouldDescribeChange(evt)) {
+ this.lastChangeDescribed = false;
+ return;
+ }
+
+ if (evt.value == this.value) {
+ this.describeSelectionChanged(evt);
+ } else {
+ this.describeTextChanged(evt);
+ }
+ this.lastChangeDescribed = true;
+
+ this.value = evt.value;
+ this.start = evt.start;
+ this.end = evt.end;
+};
+
+
+/**
+ * Describe a change in the selection or cursor position when the text
+ * stays the same.
+ * @param {cvox.TextChangeEvent} evt The text change event.
+ */
+cvox.ChromeVoxEditableTextBase.prototype.describeSelectionChanged =
+ function(evt) {
+ // TODO(deboer): Factor this into two function:
+ // - one to determine the selection event
+ // - one to speak
+
+ if (this.isPassword) {
+ this.speak((new goog.i18n.MessageFormat(cvox.ChromeVox.msgs.getMsg('dot'))
+ .format({'COUNT': 1})), evt.triggeredByUser);
+ return;
+ }
+ if (evt.start == evt.end) {
+ // It's currently a cursor.
+ if (this.start != this.end) {
+ // It was previously a selection, so just announce 'unselected'.
+ this.speak(cvox.ChromeVox.msgs.getMsg('Unselected'), evt.triggeredByUser);
+ } else if (this.getLineIndex(this.start) !=
+ this.getLineIndex(evt.start)) {
+ // Moved to a different line; read it.
+ var lineValue = this.getLine(this.getLineIndex(evt.start));
+ if (lineValue == '') {
+ lineValue = cvox.ChromeVox.msgs.getMsg('text_box_blank');
+ } else if (/^\s+$/.test(lineValue)) {
+ lineValue = cvox.ChromeVox.msgs.getMsg('text_box_whitespace');
+ }
+ this.speak(lineValue, evt.triggeredByUser);
+ } else if (this.start == evt.start + 1 ||
+ this.start == evt.start - 1) {
+ // Moved by one character; read it.
+ if (!cvox.ChromeVoxEditableTextBase.useIBeamCursor) {
+ if (evt.start == this.value.length) {
+ if (cvox.ChromeVox.verbosity == cvox.VERBOSITY_VERBOSE) {
+ this.speak(cvox.ChromeVox.msgs.getMsg('end_of_text_verbose'),
+ evt.triggeredByUser);
+ } else {
+ this.speak(cvox.ChromeVox.msgs.getMsg('end_of_text_brief'),
+ evt.triggeredByUser);
+ }
+ } else {
+ this.speak(this.value.substr(evt.start, 1),
+ evt.triggeredByUser,
+ {'phoneticCharacters': evt.triggeredByUser});
+ }
+ } else {
+ this.speak(this.value.substr(Math.min(this.start, evt.start), 1),
+ evt.triggeredByUser,
+ {'phoneticCharacters': evt.triggeredByUser});
+ }
+ } else {
+ // Moved by more than one character. Read all characters crossed.
+ this.speak(this.value.substr(Math.min(this.start, evt.start),
+ Math.abs(this.start - evt.start)), evt.triggeredByUser);
+ }
+ } else {
+ // It's currently a selection.
+ if (this.start + 1 == evt.start &&
+ this.end == this.value.length &&
+ evt.end == this.value.length) {
+ // Autocomplete: the user typed one character of autocompleted text.
+ this.speak(this.value.substr(this.start, 1), evt.triggeredByUser);
+ this.speak(this.value.substr(evt.start));
+ } else if (this.start == this.end) {
+ // It was previously a cursor.
+ this.speak(this.value.substr(evt.start, evt.end - evt.start),
+ evt.triggeredByUser);
+ this.speak(cvox.ChromeVox.msgs.getMsg('selected'));
+ } else if (this.start == evt.start && this.end < evt.end) {
+ this.speak(this.value.substr(this.end, evt.end - this.end),
+ evt.triggeredByUser);
+ this.speak(cvox.ChromeVox.msgs.getMsg('added_to_selection'));
+ } else if (this.start == evt.start && this.end > evt.end) {
+ this.speak(this.value.substr(evt.end, this.end - evt.end),
+ evt.triggeredByUser);
+ this.speak(cvox.ChromeVox.msgs.getMsg('removed_from_selection'));
+ } else if (this.end == evt.end && this.start > evt.start) {
+ this.speak(this.value.substr(evt.start, this.start - evt.start),
+ evt.triggeredByUser);
+ this.speak(cvox.ChromeVox.msgs.getMsg('added_to_selection'));
+ } else if (this.end == evt.end && this.start < evt.start) {
+ this.speak(this.value.substr(this.start, evt.start - this.start),
+ evt.triggeredByUser);
+ this.speak(cvox.ChromeVox.msgs.getMsg('removed_from_selection'));
+ } else {
+ // The selection changed but it wasn't an obvious extension of
+ // a previous selection. Just read the new selection.
+ this.speak(this.value.substr(evt.start, evt.end - evt.start),
+ evt.triggeredByUser);
+ this.speak(cvox.ChromeVox.msgs.getMsg('selected'));
+ }
+ }
+};
+
+
+/**
+ * Describe a change where the text changes.
+ * @param {cvox.TextChangeEvent} evt The text change event.
+ */
+cvox.ChromeVoxEditableTextBase.prototype.describeTextChanged = function(evt) {
+ var personality = {};
+ if (evt.value.length < this.value.length) {
+ personality = cvox.AbstractTts.PERSONALITY_DELETED;
+ }
+ if (this.isPassword) {
+ this.speak((new goog.i18n.MessageFormat(cvox.ChromeVox.msgs.getMsg('dot'))
+ .format({'COUNT': 1})), evt.triggeredByUser, personality);
+ return;
+ }
+
+ var value = this.value;
+ var len = value.length;
+ var newLen = evt.value.length;
+ var autocompleteSuffix = '';
+ // Make a copy of evtValue and evtEnd to avoid changing anything in
+ // the event itself.
+ var evtValue = evt.value;
+ var evtEnd = evt.end;
+
+ // First, see if there's a selection at the end that might have been
+ // added by autocomplete. If so, strip it off into a separate variable.
+ if (evt.start < evtEnd && evtEnd == newLen) {
+ autocompleteSuffix = evtValue.substr(evt.start);
+ evtValue = evtValue.substr(0, evt.start);
+ evtEnd = evt.start;
+ }
+
+ // Now see if the previous selection (if any) was deleted
+ // and any new text was inserted at that character position.
+ // This would handle pasting and entering text by typing, both from
+ // a cursor and from a selection.
+ var prefixLen = this.start;
+ var suffixLen = len - this.end;
+ if (newLen >= prefixLen + suffixLen + (evtEnd - evt.start) &&
+ evtValue.substr(0, prefixLen) == value.substr(0, prefixLen) &&
+ evtValue.substr(newLen - suffixLen) == value.substr(this.end)) {
+ // However, in a dynamic content editable, defer to authoritative events
+ // (clipboard, key press) to reduce guess work when observing insertions.
+ // Only use this logic when observing deletions (and insertion of word
+ // breakers).
+ // TODO(dtseng): Think about a more reliable way to do this.
+ if (!(this instanceof cvox.ChromeVoxEditableContentEditable) ||
+ newLen < len ||
+ this.isWordBreakChar(evt.value[newLen - 1] || '')) {
+ this.describeTextChangedHelper(
+ evt, prefixLen, suffixLen, autocompleteSuffix, personality);
+ }
+ return;
+ }
+
+ // Next, see if one or more characters were deleted from the previous
+ // cursor position and the new cursor is in the expected place. This
+ // handles backspace, forward-delete, and similar shortcuts that delete
+ // a word or line.
+ prefixLen = evt.start;
+ suffixLen = newLen - evtEnd;
+ if (this.start == this.end &&
+ evt.start == evtEnd &&
+ evtValue.substr(0, prefixLen) == value.substr(0, prefixLen) &&
+ evtValue.substr(newLen - suffixLen) ==
+ value.substr(len - suffixLen)) {
+ this.describeTextChangedHelper(
+ evt, prefixLen, suffixLen, autocompleteSuffix, personality);
+ return;
+ }
+
+ // If all else fails, we assume the change was not the result of a normal
+ // user editing operation, so we'll have to speak feedback based only
+ // on the changes to the text, not the cursor position / selection.
+ // First, restore the autocomplete text if any.
+ evtValue += autocompleteSuffix;
+
+ // Try to do a diff between the new and the old text. If it is a one character
+ // insertion/deletion at the start or at the end, just speak that character.
+ if ((evtValue.length == (value.length + 1)) ||
+ ((evtValue.length + 1) == value.length)) {
+ // The user added text either to the beginning or the end.
+ if (evtValue.length > value.length) {
+ if (evtValue.indexOf(value) == 0) {
+ this.speak(evtValue[evtValue.length - 1], evt.triggeredByUser,
+ personality);
+ return;
+ } else if (evtValue.indexOf(value) == 1) {
+ this.speak(evtValue[0], evt.triggeredByUser, personality);
+ return;
+ }
+ }
+ // The user deleted text either from the beginning or the end.
+ if (evtValue.length < value.length) {
+ if (value.indexOf(evtValue) == 0) {
+ this.speak(value[value.length - 1], evt.triggeredByUser, personality);
+ return;
+ } else if (value.indexOf(evtValue) == 1) {
+ this.speak(value[0], evt.triggeredByUser, personality);
+ return;
+ }
+ }
+ }
+
+ if (this.multiline) {
+ // Fall back to announce deleted but omit the text that was deleted.
+ if (evt.value.length < this.value.length) {
+ this.speak(cvox.ChromeVox.msgs.getMsg('text_deleted'),
+ evt.triggeredByUser, personality);
+ }
+ // The below is a somewhat loose way to deal with non-standard
+ // insertions/deletions. Intentionally skip for multiline since deletion
+ // announcements are covered above and insertions are non-standard (possibly
+ // due to auto complete). Since content editable's often refresh content by
+ // removing and inserting entire chunks of text, this type of logic often
+ // results in unintended consequences such as reading all text when only one
+ // character has been entered.
+ return;
+ }
+
+ // If the text is short, just speak the whole thing.
+ if (newLen <= this.maxShortPhraseLen) {
+ this.describeTextChangedHelper(evt, 0, 0, '', personality);
+ return;
+ }
+
+ // Otherwise, look for the common prefix and suffix, but back up so
+ // that we can speak complete words, to be minimally confusing.
+ prefixLen = 0;
+ while (prefixLen < len &&
+ prefixLen < newLen &&
+ value[prefixLen] == evtValue[prefixLen]) {
+ prefixLen++;
+ }
+ while (prefixLen > 0 && !this.isWordBreakChar(value[prefixLen - 1])) {
+ prefixLen--;
+ }
+
+ suffixLen = 0;
+ while (suffixLen < (len - prefixLen) &&
+ suffixLen < (newLen - prefixLen) &&
+ value[len - suffixLen - 1] == evtValue[newLen - suffixLen - 1]) {
+ suffixLen++;
+ }
+ while (suffixLen > 0 && !this.isWordBreakChar(value[len - suffixLen])) {
+ suffixLen--;
+ }
+
+ this.describeTextChangedHelper(evt, prefixLen, suffixLen, '', personality);
+};
+
+
+/**
+ * The function called by describeTextChanged after it's figured out
+ * what text was deleted, what text was inserted, and what additional
+ * autocomplete text was added.
+ * @param {cvox.TextChangeEvent} evt The text change event.
+ * @param {number} prefixLen The number of characters in the common prefix
+ * of this.value and newValue.
+ * @param {number} suffixLen The number of characters in the common suffix
+ * of this.value and newValue.
+ * @param {string} autocompleteSuffix The autocomplete string that was added
+ * to the end, if any. It should be spoken at the end of the utterance
+ * describing the change.
+ * @param {Object=} opt_personality Personality to speak the text.
+ */
+cvox.ChromeVoxEditableTextBase.prototype.describeTextChangedHelper = function(
+ evt, prefixLen, suffixLen, autocompleteSuffix, opt_personality) {
+ var len = this.value.length;
+ var newLen = evt.value.length;
+ var deletedLen = len - prefixLen - suffixLen;
+ var deleted = this.value.substr(prefixLen, deletedLen);
+ var insertedLen = newLen - prefixLen - suffixLen;
+ var inserted = evt.value.substr(prefixLen, insertedLen);
+ var utterance = '';
+ var triggeredByUser = evt.triggeredByUser;
+
+ if (insertedLen > 1) {
+ utterance = inserted;
+ } else if (insertedLen == 1) {
+ if ((cvox.ChromeVox.typingEcho == cvox.TypingEcho.WORD ||
+ cvox.ChromeVox.typingEcho == cvox.TypingEcho.CHARACTER_AND_WORD) &&
+ this.isWordBreakChar(inserted) &&
+ prefixLen > 0 &&
+ !this.isWordBreakChar(evt.value.substr(prefixLen - 1, 1))) {
+ // Speak previous word.
+ var index = prefixLen;
+ while (index > 0 && !this.isWordBreakChar(evt.value[index - 1])) {
+ index--;
+ }
+ if (index < prefixLen) {
+ utterance = evt.value.substr(index, prefixLen + 1 - index);
+ } else {
+ utterance = inserted;
+ triggeredByUser = false; // Implies QUEUE_MODE_QUEUE.
+ }
+ } else if (cvox.ChromeVox.typingEcho == cvox.TypingEcho.CHARACTER ||
+ cvox.ChromeVox.typingEcho == cvox.TypingEcho.CHARACTER_AND_WORD) {
+ // This particular case is handled in event watcher. See the key press
+ // handler for more details.
+ utterance = cvox.ChromeVoxEditableTextBase.eventTypingEcho ? '' :
+ inserted;
+ }
+ } else if (deletedLen > 1 && !autocompleteSuffix) {
+ utterance = deleted + ', deleted';
+ } else if (deletedLen == 1) {
+ utterance = deleted;
+ }
+
+ if (autocompleteSuffix && utterance) {
+ utterance += ', ' + autocompleteSuffix;
+ } else if (autocompleteSuffix) {
+ utterance = autocompleteSuffix;
+ }
+
+ if (utterance) {
+ this.speak(utterance, triggeredByUser, opt_personality);
+ }
+};
+
+
+/**
+ * Moves the cursor forward by one character.
+ * @return {boolean} True if the action was handled.
+ */
+cvox.ChromeVoxEditableTextBase.prototype.moveCursorToNextCharacter =
+ function() { return false; };
+
+
+/**
+ * Moves the cursor backward by one character.
+ * @return {boolean} True if the action was handled.
+ */
+cvox.ChromeVoxEditableTextBase.prototype.moveCursorToPreviousCharacter =
+ function() { return false; };
+
+
+/**
+ * Moves the cursor forward by one word.
+ * @return {boolean} True if the action was handled.
+ */
+cvox.ChromeVoxEditableTextBase.prototype.moveCursorToNextWord =
+ function() { return false; };
+
+
+/**
+ * Moves the cursor backward by one word.
+ * @return {boolean} True if the action was handled.
+ */
+cvox.ChromeVoxEditableTextBase.prototype.moveCursorToPreviousWord =
+ function() { return false; };
+
+
+/**
+ * Moves the cursor forward by one line.
+ * @return {boolean} True if the action was handled.
+ */
+cvox.ChromeVoxEditableTextBase.prototype.moveCursorToNextLine =
+ function() { return false; };
+
+
+/**
+ * Moves the cursor backward by one line.
+ * @return {boolean} True if the action was handled.
+ */
+cvox.ChromeVoxEditableTextBase.prototype.moveCursorToPreviousLine =
+ function() { return false; };
+
+
+/**
+ * Moves the cursor forward by one paragraph.
+ * @return {boolean} True if the action was handled.
+ */
+cvox.ChromeVoxEditableTextBase.prototype.moveCursorToNextParagraph =
+ function() { return false; };
+
+
+/**
+ * Moves the cursor backward by one paragraph.
+ * @return {boolean} True if the action was handled.
+ */
+cvox.ChromeVoxEditableTextBase.prototype.moveCursorToPreviousParagraph =
+ function() { return false; };
+
+
+/******************************************/
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/common/editable_text_test.unitjs b/chromium/chrome/browser/resources/chromeos/chromevox/common/editable_text_test.unitjs
index 6da825d3163..2df3086d66b 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/common/editable_text_test.unitjs
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/common/editable_text_test.unitjs
@@ -95,6 +95,7 @@ CvoxEditableTextUnitTest.prototype = {
/** @override */
closureModuleDeps: [
+ 'cvox.ChromeVoxEditableElement',
'cvox.ChromeVoxEditableHTMLInput',
'cvox.ChromeVoxEditableTextBase',
'cvox.ChromeVoxEventWatcher',
@@ -275,7 +276,7 @@ TEST_F('CvoxEditableTextUnitTest', 'Selection', function() {
TEST_F('CvoxEditableTextUnitTest', 'MultiLineText', function() {
var str = 'This string\nspans\nfive lines.\n \n';
var tts = new TestTts();
- var obj = new cvox.ChromeVoxEditableTextBase(str, 0, 0, false, tts);
+ var obj = new cvox.ChromeVoxEditableElement(null, str, 0, 0, false, tts);
obj.multiline = true;
obj.getLineIndex = function(index) {
if (index >= 33) {
@@ -668,3 +669,25 @@ TEST_F('CvoxEditableTextUnitTest', 'TextChangeEvent', function() {
assertEquals(1, event3.start);
assertEquals(1, event3.end);
});
+
+TEST_F('CvoxEditableTextUnitTest', 'ContentEditableBraille', function() {
+ this.loadDoc(function() {/*!
+ <div id='1' contenteditable='true'>
+ Some &nbsp; text.<br><br>
+ After blank line.
+ </div>
+ */});
+ var element = $('1');
+ element.focus();
+ var editable = new cvox.ChromeVoxEditableContentEditable(
+ element, new TestTts());
+ var firstLine = 'Some text.\n';
+ for (var i = 0; i < firstLine.length; ++i) {
+ editable.update(true);
+ TestBraille.assertContent(firstLine, i, i);
+ window.getSelection().modify('move', 'forward', 'character');
+ }
+ // We should have crossed the line break to the second line which is blank.
+ editable.update(true);
+ TestBraille.assertContent('', 0, 0);
+});
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/common/externs.js b/chromium/chrome/browser/resources/chromeos/chromevox/common/externs.js
index 7d34b6d6fc2..2e2886d4237 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/common/externs.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/common/externs.js
@@ -5,18 +5,3 @@
Range.prototype.getBoundingClientRect;
Document.prototype.documentElement.innerWidth;
Document.prototype.documentElement.head;
-
-/** @constructor */
-function WeakMap() {}
-
-/**
- * @param {Object} key
- * @return {*}
- */
-WeakMap.prototype.get = function(key) {};
-
-/**
- * @param {Object} key
- * @param {*} value
- */
-WeakMap.prototype.set = function(key, value) {};
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/common/find_util.js b/chromium/chrome/browser/resources/chromeos/chromevox/common/find_util.js
index 486d406b6f5..a5ac8a6b371 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/common/find_util.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/common/find_util.js
@@ -24,7 +24,7 @@ cvox.FindUtil.objectWalker_ = new cvox.BareObjectWalker();
* Finds the next selection that matches the predicate function starting from
* sel. Undefined if the nodes in sel are not attached to the document.
* @param {!cvox.CursorSelection} sel The selection from which to start.
- * @param {function(Array.<Node>):Node} predicate A function taking a
+ * @param {function(Array<Node>):Node} predicate A function taking a
* unique ancestor tree and outputting Node if the ancestor tree matches
* the desired node to find.
* @param {boolean=} opt_initialNode Whether to start the search from node
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/common/interframe.js b/chromium/chrome/browser/resources/chromeos/chromevox/common/interframe.js
index 5f4b800bf0a..8d7b7fc117f 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/common/interframe.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/common/interframe.js
@@ -41,6 +41,14 @@ cvox.Interframe.IF_MSG_PREFIX = 'cvox.INTERFRAME:';
cvox.Interframe.SET_ID = 'cvox.INTERFRAME_SET_ID';
/**
+ * The message used by a child frame to acknowledge an id was set (sent to its
+ * parent frame.
+ * @type {string}
+ * @const
+ */
+cvox.Interframe.ACK_SET_ID = 'cvox.INTERFRAME_ACK_SET_ID';
+
+/**
* The ID of this window (relative to its parent farme).
* @type {number|string|undefined}
*/
@@ -49,11 +57,19 @@ cvox.Interframe.id;
/**
* Array of functions that have been registered as listeners to interframe
* messages send to this window.
- * @type {Array.<function(Object)>}
+ * @type {Array<function(Object)>}
*/
cvox.Interframe.listeners = [];
/**
+ * Maps an id to a function which gets called when a frame first sends an ack
+ * for a set id msg.
+ @dict {!Object<number|string, function()>}
+ * @private
+ */
+cvox.Interframe.idToCallback_ = {};
+
+/**
* Flag for unit testing. When false, skips over iframe.contentWindow check
* in sendMessageToIframe. This is needed because in the wild, ChromeVox may
* not have access to iframe.contentWindow due to the same-origin security
@@ -74,6 +90,12 @@ cvox.Interframe.init = function() {
cvox.ChromeVoxJSON.parse(suffix));
if (message['command'] == cvox.Interframe.SET_ID) {
cvox.Interframe.id = message['id'];
+ message['command'] = cvox.Interframe.ACK_SET_ID;
+ cvox.Interframe.sendMessageToParentWindow(message);
+ } else if (message['command'] == cvox.Interframe.ACK_SET_ID) {
+ cvox.Interframe.id = message['id'];
+ var callback = cvox.Interframe.idToCallback_[cvox.Interframe.id];
+ callback();
}
for (var i = 0, listener; listener = cvox.Interframe.listeners[i]; i++) {
listener(message);
@@ -196,8 +218,13 @@ cvox.Interframe.sendMessageToParentWindow = function(message) {
* @param {number|string} id The ID you want to receive in replies from
* this iframe.
* @param {HTMLIFrameElement} iframe The iframe to assign.
+ * @param {function()=} opt_callback Called when a ack msg arrives from the
+ *frame.
*/
-cvox.Interframe.sendIdToIFrame = function(id, iframe) {
+cvox.Interframe.sendIdToIFrame = function(id, iframe, opt_callback) {
+ if (opt_callback) {
+ cvox.Interframe.idToCallback_[id] = opt_callback;
+ }
var message = {'command': cvox.Interframe.SET_ID, 'id': id};
cvox.Interframe.sendMessageToIFrame(message, iframe);
};
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/common/key_sequence.js b/chromium/chrome/browser/resources/chromeos/chromevox/common/key_sequence.js
index 5759399cb27..0a6f3d51c01 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/common/key_sequence.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/common/key_sequence.js
@@ -11,6 +11,7 @@
goog.provide('cvox.KeySequence');
goog.require('cvox.ChromeVox');
+goog.require('cvox.PlatformFilter');
/**
@@ -47,6 +48,9 @@ cvox.KeySequence = function(
/** @type {boolean} */
this.doubleTap = !!opt_doubleTap;
+ /** @type {cvox.PlatformFilter} */
+ this.platformFilter;
+
if (opt_cvoxModifier == undefined) {
this.cvoxModifier = this.isCVoxModifierActive(originalEvent);
} else {
@@ -96,7 +100,7 @@ cvox.KeySequence = function(
// TODO(dtseng): This is incomplete; pull once we have appropriate libs.
/**
* Maps a keypress keycode to a keydown or keyup keycode.
- * @type {Object.<number, number>}
+ * @type {Object<number, number>}
*/
cvox.KeySequence.KEY_PRESS_CODE = {
39: 222,
@@ -114,7 +118,7 @@ cvox.KeySequence.KEY_PRESS_CODE = {
* A cache of all key sequences that have been set as double-tappable. We need
* this cache because repeated key down computations causes ChromeVox to become
* less responsive. This list is small so we currently use an array.
- * @type {!Array.<cvox.KeySequence>}
+ * @type {!Array<cvox.KeySequence>}
*/
cvox.KeySequence.doubleTapCache = [];
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/common/key_util.js b/chromium/chrome/browser/resources/chromeos/chromevox/common/key_util.js
index 9bbf893c5ef..5ff64d01d7a 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/common/key_util.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/common/key_util.js
@@ -9,10 +9,18 @@
goog.provide('cvox.KeyUtil');
+goog.provide('cvox.SimpleKeyEvent');
goog.require('cvox.ChromeVox');
goog.require('cvox.KeySequence');
+/**
+ * @typedef {{ctrlKey: (boolean|undefined),
+ * altKey: (boolean|undefined),
+ * shiftKey: (boolean|undefined),
+ * keyCode: (number|undefined)}}
+ */
+cvox.SimpleKeyEvent;
/**
* Create the namespace
@@ -59,7 +67,7 @@ cvox.KeyUtil.maxSeqLength = 2;
/**
* Convert a key event into a Key Sequence representation.
*
- * @param {Event} keyEvent The keyEvent to convert.
+ * @param {Event|cvox.SimpleKeyEvent} keyEvent The keyEvent to convert.
* @return {cvox.KeySequence} A key sequence representation of the key event.
*/
cvox.KeyUtil.keyEventToKeySequence = function(keyEvent) {
@@ -199,7 +207,7 @@ cvox.KeyUtil.modStringToKeyCode = function(keyString) {
/**
* Returns the key codes of a string respresentation of the ChromeVox modifiers.
*
- * @return {Array.<number>} Array of key codes.
+ * @return {Array<number>} Array of key codes.
*/
cvox.KeyUtil.cvoxModKeyCodes = function() {
var modKeyCombo = cvox.ChromeVox.modKeyStr.split(/\+/g);
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/common/math_semantic_attr.js b/chromium/chrome/browser/resources/chromeos/chromevox/common/math_semantic_attr.js
index 69b616eab54..55428e861fb 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/common/math_semantic_attr.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/common/math_semantic_attr.js
@@ -55,7 +55,7 @@ goog.require('cvox.SemanticUtil');
cvox.SemanticAttr = function() {
// Punctuation Characters.
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.generalPunctuations =
[
@@ -75,28 +75,28 @@ cvox.SemanticAttr = function() {
this.invisibleComma_ = cvox.SemanticUtil.numberToUnicode(0x2063);
this.generalPunctuations.push(this.invisibleComma_);
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.ellipses =
[
'…', '⋮', '⋯', '⋰', '⋱', '︙'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.fullStops =
[
'.', '﹒', '.'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.dashes =
[
'‒', '–', '—', '―', '〜', '︱', '︲', '﹘'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.primes =
[
@@ -108,7 +108,7 @@ cvox.SemanticAttr = function() {
// record pairs of opening/closing and top/bottom fences.
/**
* Mapping opening to closing fences.
- * @type {Object.<string, string>}
+ * @type {Object<string, string>}
*/
this.openClosePairs =
{
@@ -137,7 +137,7 @@ cvox.SemanticAttr = function() {
};
/**
* Mapping top to bottom fences.
- * @type {Object.<string, string>}
+ * @type {Object<string, string>}
*/
this.topBottomPairs =
{
@@ -146,31 +146,31 @@ cvox.SemanticAttr = function() {
'﹃': '﹄', '﹇': '﹈'
};
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.leftFences = cvox.SemanticUtil.objectsToKeys(this.openClosePairs);
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.rightFences = cvox.SemanticUtil.objectsToValues(this.openClosePairs);
this.rightFences.push('〟');
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.topFences = cvox.SemanticUtil.objectsToKeys(this.topBottomPairs);
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.bottomFences = cvox.SemanticUtil.objectsToValues(this.topBottomPairs);
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.neutralFences =
[
'|', '¦', '‖', '❘', '⦀', '⫴', '¦', '|'
];
/** Array of all fences.
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.fences = this.neutralFences.concat(
this.leftFences, this.rightFences, this.topFences, this.bottomFences);
@@ -178,7 +178,7 @@ cvox.SemanticAttr = function() {
// Identifiers.
// Latin Alphabets.
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.capitalLatin =
[
@@ -186,7 +186,7 @@ cvox.SemanticAttr = function() {
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.smallLatin =
[
@@ -196,7 +196,7 @@ cvox.SemanticAttr = function() {
'ı', 'ȷ'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.capitalLatinFullWidth =
[
@@ -204,7 +204,7 @@ cvox.SemanticAttr = function() {
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.smallLatinFullWidth =
[
@@ -212,7 +212,7 @@ cvox.SemanticAttr = function() {
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.capitalLatinBold =
[
@@ -220,7 +220,7 @@ cvox.SemanticAttr = function() {
'𝐍', '𝐎', '𝐏', '𝐐', '𝐑', '𝐒', '𝐓', '𝐔', '𝐕', '𝐖', '𝐗', '𝐘', '𝐙'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.smallLatinBold =
[
@@ -228,7 +228,7 @@ cvox.SemanticAttr = function() {
'𝐧', '𝐨', '𝐩', '𝐪', '𝐫', '𝐬', '𝐭', '𝐮', '𝐯', '𝐰', '𝐱', '𝐲', '𝐳'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.capitalLatinItalic =
[
@@ -236,7 +236,7 @@ cvox.SemanticAttr = function() {
'𝑁', '𝑂', '𝑃', '𝑄', '𝑅', '𝑆', '𝑇', '𝑈', '𝑉', '𝑊', '𝑋', '𝑌', '𝑍'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.smallLatinItalic =
[
@@ -246,7 +246,7 @@ cvox.SemanticAttr = function() {
'𝚤', '𝚥'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.capitalLatinScript =
[
@@ -256,7 +256,7 @@ cvox.SemanticAttr = function() {
'℘'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.smallLatinScript =
[
@@ -266,7 +266,7 @@ cvox.SemanticAttr = function() {
'ℓ'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.capitalLatinBoldScript =
[
@@ -274,7 +274,7 @@ cvox.SemanticAttr = function() {
'𝓝', '𝓞', '𝓟', '𝓠', '𝓡', '𝓢', '𝓣', '𝓤', '𝓥', '𝓦', '𝓧', '𝓨', '𝓩'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.smallLatinBoldScript =
[
@@ -282,7 +282,7 @@ cvox.SemanticAttr = function() {
'𝓷', '𝓸', '𝓹', '𝓺', '𝓻', '𝓼', '𝓽', '𝓾', '𝓿', '𝔀', '𝔁', '𝔂', '𝔃'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.capitalLatinFraktur =
[
@@ -290,7 +290,7 @@ cvox.SemanticAttr = function() {
'𝔑', '𝔒', '𝔓', '𝔔', 'ℜ', '𝔖', '𝔗', '𝔘', '𝔙', '𝔚', '𝔛', '𝔜', 'ℨ'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.smallLatinFraktur =
[
@@ -298,7 +298,7 @@ cvox.SemanticAttr = function() {
'𝔫', '𝔬', '𝔭', '𝔮', '𝔯', '𝔰', '𝔱', '𝔲', '𝔳', '𝔴', '𝔵', '𝔶', '𝔷'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.capitalLatinDoubleStruck =
[
@@ -306,7 +306,7 @@ cvox.SemanticAttr = function() {
'ℕ', '𝕆', 'ℙ', 'ℚ', 'ℝ', '𝕊', '𝕋', '𝕌', '𝕍', '𝕎', '𝕏', '𝕐', 'ℤ'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.smallLatinDoubleStruck =
[
@@ -314,7 +314,7 @@ cvox.SemanticAttr = function() {
'𝕟', '𝕠', '𝕡', '𝕢', '𝕣', '𝕤', '𝕥', '𝕦', '𝕧', '𝕨', '𝕩', '𝕪', '𝕫'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.capitalLatinBoldFraktur =
[
@@ -322,7 +322,7 @@ cvox.SemanticAttr = function() {
'𝕹', '𝕺', '𝕻', '𝕼', '𝕽', '𝕾', '𝕿', '𝖀', '𝖁', '𝖂', '𝖃', '𝖄', '𝖅'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.smallLatinBoldFraktur =
[
@@ -330,7 +330,7 @@ cvox.SemanticAttr = function() {
'𝖓', '𝖔', '𝖕', '𝖖', '𝖗', '𝖘', '𝖙', '𝖚', '𝖛', '𝖜', '𝖝', '𝖞', '𝖟'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.capitalLatinSansSerif =
[
@@ -338,7 +338,7 @@ cvox.SemanticAttr = function() {
'𝖭', '𝖮', '𝖯', '𝖰', '𝖱', '𝖲', '𝖳', '𝖴', '𝖵', '𝖶', '𝖷', '𝖸', '𝖹'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.smallLatinSansSerif =
[
@@ -346,7 +346,7 @@ cvox.SemanticAttr = function() {
'𝗇', '𝗈', '𝗉', '𝗊', '𝗋', '𝗌', '𝗍', '𝗎', '𝗏', '𝗐', '𝗑', '𝗒', '𝗓'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.capitalLatinSansSerifBold =
[
@@ -354,7 +354,7 @@ cvox.SemanticAttr = function() {
'𝗡', '𝗢', '𝗣', '𝗤', '𝗥', '𝗦', '𝗧', '𝗨', '𝗩', '𝗪', '𝗫', '𝗬', '𝗭'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.smallLatinSansSerifBold =
[
@@ -362,7 +362,7 @@ cvox.SemanticAttr = function() {
'𝗻', '𝗼', '𝗽', '𝗾', '𝗿', '𝘀', '𝘁', '𝘂', '𝘃', '𝘄', '𝘅', '𝘆', '𝘇'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.capitalLatinSansSerifItalic =
[
@@ -370,7 +370,7 @@ cvox.SemanticAttr = function() {
'𝘕', '𝘖', '𝘗', '𝘘', '𝘙', '𝘚', '𝘛', '𝘜', '𝘝', '𝘞', '𝘟', '𝘠', '𝘡'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.smallLatinSansSerifItalic =
[
@@ -378,7 +378,7 @@ cvox.SemanticAttr = function() {
'𝘯', '𝘰', '𝘱', '𝘲', '𝘳', '𝘴', '𝘵', '𝘶', '𝘷', '𝘸', '𝘹', '𝘺', '𝘻'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.capitalLatinMonospace =
[
@@ -386,7 +386,7 @@ cvox.SemanticAttr = function() {
'𝙽', '𝙾', '𝙿', '𝚀', '𝚁', '𝚂', '𝚃', '𝚄', '𝚅', '𝚆', '𝚇', '𝚈', '𝚉'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.smallLatinMonospace =
[
@@ -394,7 +394,7 @@ cvox.SemanticAttr = function() {
'𝚗', '𝚘', '𝚙', '𝚚', '𝚛', '𝚜', '𝚝', '𝚞', '𝚟', '𝚠', '𝚡', '𝚢', '𝚣'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.latinDoubleStruckItalic =
[
@@ -403,7 +403,7 @@ cvox.SemanticAttr = function() {
// Greek Alphabets
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.capitalGreek =
[
@@ -411,7 +411,7 @@ cvox.SemanticAttr = function() {
'Ξ', 'Ο', 'Π', 'Ρ', 'Σ', 'Τ', 'Υ', 'Φ', 'Χ', 'Ψ', 'Ω'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.smallGreek =
[
@@ -419,7 +419,7 @@ cvox.SemanticAttr = function() {
'ξ', 'ο', 'π', 'ρ', 'ς', 'σ', 'τ', 'υ', 'φ', 'χ', 'ψ', 'ω'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.capitalGreekBold =
[
@@ -427,7 +427,7 @@ cvox.SemanticAttr = function() {
'𝚵', '𝚶', '𝚷', '𝚸', '𝚺', '𝚻', '𝚼', '𝚽', '𝚾', '𝚿', '𝛀'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.smallGreekBold =
[
@@ -435,7 +435,7 @@ cvox.SemanticAttr = function() {
'𝛏', '𝛐', '𝛑', '𝛒', '𝛓', '𝛔', '𝛕', '𝛖', '𝛗', '𝛘', '𝛙', '𝛚'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.capitalGreekItalic =
[
@@ -443,7 +443,7 @@ cvox.SemanticAttr = function() {
'𝛯', '𝛰', '𝛱', '𝛲', '𝛴', '𝛵', '𝛶', '𝛷', '𝛸', '𝛹', '𝛺'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.smallGreekItalic =
[
@@ -451,7 +451,7 @@ cvox.SemanticAttr = function() {
'𝜉', '𝜊', '𝜋', '𝜌', '𝜍', '𝜎', '𝜏', '𝜐', '𝜑', '𝜒', '𝜓', '𝜔'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.capitalGreekSansSerifBold =
[
@@ -459,7 +459,7 @@ cvox.SemanticAttr = function() {
'𝝣', '𝝤', '𝝥', '𝝦', '𝝨', '𝝩', '𝝪', '𝝫', '𝝬', '𝝭', '𝝮'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.smallGreekSansSerifBold =
[
@@ -467,7 +467,7 @@ cvox.SemanticAttr = function() {
'𝝽', '𝝾', '𝝿', '𝞀', '𝞁', '𝞂', '𝞃', '𝞄', '𝞅', '𝞆', '𝞇', '𝞈'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.greekDoubleStruck =
[
@@ -476,7 +476,7 @@ cvox.SemanticAttr = function() {
// Other alphabets.
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.hebrewLetters =
[
@@ -485,7 +485,7 @@ cvox.SemanticAttr = function() {
//Operator symbols
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.additions =
[
@@ -494,7 +494,7 @@ cvox.SemanticAttr = function() {
'◁', '⩞', '⊕'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
/**
* Invisible operator for plus.
@@ -504,7 +504,7 @@ cvox.SemanticAttr = function() {
this.invisiblePlus_ = cvox.SemanticUtil.numberToUnicode(0x2064);
this.additions.push(this.invisiblePlus_);
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.multiplications =
[
@@ -519,7 +519,7 @@ cvox.SemanticAttr = function() {
this.invisibleTimes_ = cvox.SemanticUtil.numberToUnicode(0x2062);
this.multiplications.push(this.invisibleTimes_);
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.subtractions =
[
@@ -527,7 +527,7 @@ cvox.SemanticAttr = function() {
'⨫', '⨬', '⨺', '⩁', '⩬', '﹣', '-', '‐', '‑'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.divisions =
[
@@ -542,7 +542,7 @@ cvox.SemanticAttr = function() {
//Relation symbols
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.equalities =
[
@@ -552,7 +552,7 @@ cvox.SemanticAttr = function() {
'⩴', '⩵', '⩶', '⩷', '⩸', '⋕', '⩭', '⩪', '⩫', '⩬', '﹦', '='
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.inequalities =
[
@@ -569,14 +569,14 @@ cvox.SemanticAttr = function() {
'⫷', '⫸', '⫹', '⫺', '⧀', '⧁', '﹤', '﹥', '<', '>'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.relations =
[
// TODO (sorge): Add all the other relations.
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.arrows =
[
@@ -613,7 +613,7 @@ cvox.SemanticAttr = function() {
//Big operation symbols
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.sumOps =
[
@@ -622,7 +622,7 @@ cvox.SemanticAttr = function() {
'⨆', '⨇', '⨈', '⨉', '⨊', '⨋', '⫼', '⫿'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.intOps =
[
@@ -630,7 +630,7 @@ cvox.SemanticAttr = function() {
'⨐', '⨑', '⨒', '⨓', '⨔', '⨕', '⨖', '⨗', '⨘', '⨙', '⨚', '⨛', '⨜'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.prefixOps =
// TODO (sorge) Insert nabla, differential operators etc.
@@ -638,7 +638,7 @@ cvox.SemanticAttr = function() {
'∀', '∃'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.operatorBits =
// TODO (sorge) What to do if single glyphs of big ops occur on their own.
@@ -652,70 +652,70 @@ cvox.SemanticAttr = function() {
// Numbers.
// Digits.
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.digitsNormal =
[
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.digitsFullWidth =
[
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.digitsBold =
[
'𝟎', '𝟏', '𝟐', '𝟑', '𝟒', '𝟓', '𝟔', '𝟕', '𝟖', '𝟗'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.digitsDoubleStruck =
[
'𝟘', '𝟙', '𝟚', '𝟛', '𝟜', '𝟝', '𝟞', '𝟟', '𝟠', '𝟡'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.digitsSansSerif =
[
'𝟢', '𝟣', '𝟤', '𝟥', '𝟦', '𝟧', '𝟨', '𝟩', '𝟪', '𝟫'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.digitsSansSerifBold =
[
'𝟬', '𝟭', '𝟮', '𝟯', '𝟰', '𝟱', '𝟲', '𝟳', '𝟴', '𝟵'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.digitsMonospace =
[
'𝟶', '𝟷', '𝟸', '𝟹', '𝟺', '𝟻', '𝟼', '𝟽', '𝟾', '𝟿'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.digitsSuperscript =
[
'²', '³', '¹', '⁰', '⁴', '⁵', '⁶', '⁷', '⁸', '⁹'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.digitsSubscript =
[
'₀', '₁', '₂', '₃', '₄', '₅', '₆', '₇', '₈', '₉'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.fractions =
[
@@ -723,7 +723,7 @@ cvox.SemanticAttr = function() {
'⅚', '⅛', '⅜', '⅝', '⅞', '⅟', '↉'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.enclosedNumbers =
// Encircled numbers.
@@ -738,7 +738,7 @@ cvox.SemanticAttr = function() {
'㉙', '㉚', '㉛', '㉜', '㉝', '㉞', '㉟', '㊱', '㊲', '㊳', '㊴',
'㊵', '㊶', '㊷', '㊸', '㊹', '㊺', '㊻', '㊼', '㊽', '㊾', '㊿'];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.fencedNumbers =
// Numbers in Parenthesis.
@@ -747,7 +747,7 @@ cvox.SemanticAttr = function() {
'⒁', '⒂', '⒃', '⒄', '⒅', '⒆', '⒇'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.punctuatedNumbers =
// Numbers with other punctuation.
@@ -756,25 +756,25 @@ cvox.SemanticAttr = function() {
'🄀', '🄁', '🄂', '🄃', '🄄', '🄅', '🄆', '🄇', '🄈', '🄉', '🄊' // comma.
];
/** Array of all single digits.
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.digits = this.digitsNormal.concat(
this.digitsFullWidth, this.digitsBold, this.digitsDoubleStruck,
this.digitsSansSerif, this.digitsSansSerifBold, this.digitsMonospace);
/** Array of all non-digit number symbols.
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.numbers = this.fractions.concat(
this.digitsSuperscript, this.digitsSubscript,
this.enclosedNumbers, this.fencedNumbers, this.punctuatedNumbers);
/** Array of all number symbols.
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.allNumbers = this.digits.concat(this.numbers);
// Functions.
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.trigonometricFunctions =
[
@@ -782,7 +782,7 @@ cvox.SemanticAttr = function() {
'arccsc', 'arcsec', 'arcsin', 'arctan'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.hyperbolicFunctions =
[
@@ -791,21 +791,21 @@ cvox.SemanticAttr = function() {
'arccosh', 'arccoth', 'arccsch', 'arcsech', 'arcsinh', 'arctanh'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.algebraicFunctions =
[
'deg', 'det', 'dim', 'hom', 'ker', 'Tr', 'tr'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.elementaryFunctions =
[
'log', 'ln', 'lg', 'exp', 'expt', 'gcd', 'gcd', 'arg', 'im', 're', 'Pr'
];
/** All predefined prefix functions.
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.prefixFunctions = this.trigonometricFunctions.concat(
this.hyperbolicFunctions,
@@ -814,7 +814,7 @@ cvox.SemanticAttr = function() {
);
/** Limit functions are handled separately as they can have lower (and upper)
* limiting expressions.
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.limitFunctions =
[
@@ -822,7 +822,7 @@ cvox.SemanticAttr = function() {
'projlim'
];
/**
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.infixFunctions =
[
@@ -830,7 +830,7 @@ cvox.SemanticAttr = function() {
];
/**
* Default assignments of semantic attributes.
- * @type {Array.<{set: Array.<string>,
+ * @type {Array<{set: Array<string>,
* role: cvox.SemanticAttr.Role,
* type: cvox.SemanticAttr.Type,
* font: cvox.SemanticAttr.Font}>} The semantic meaning of the symbol.
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/common/math_semantic_tree.js b/chromium/chrome/browser/resources/chromeos/chromevox/common/math_semantic_tree.js
index 647aaec6d7f..991ae3244ca 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/common/math_semantic_tree.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/common/math_semantic_tree.js
@@ -50,7 +50,7 @@ cvox.SemanticTree.Node = function(id) {
/** @type {number} */
this.id = id;
- /** @type {Array.<Element>} */
+ /** @type {Array<Element>} */
this.mathml = [];
/** @type {cvox.SemanticTree.Node} */
@@ -65,7 +65,7 @@ cvox.SemanticTree.Node = function(id) {
/** @type {cvox.SemanticAttr.Font} */
this.font = cvox.SemanticAttr.Font.UNKNOWN;
- /** @type {!Array.<cvox.SemanticTree.Node>} */
+ /** @type {!Array<cvox.SemanticTree.Node>} */
this.childNodes = [];
/** @type {string} */
@@ -73,7 +73,7 @@ cvox.SemanticTree.Node = function(id) {
/** Branch nodes can store additional nodes that can be useful.
* E.g. a node of type FENCED can have the opening and closing fences here.
- * @type {!Array.<cvox.SemanticTree.Node>}
+ * @type {!Array<cvox.SemanticTree.Node>}
*/
this.contentNodes = [];
};
@@ -83,7 +83,7 @@ cvox.SemanticTree.Node = function(id) {
* Retrieve all subnodes (including the node itself) that satisfy a given
* predicate.
* @param {function(cvox.SemanticTree.Node): boolean} pred The predicate.
- * @return {!Array.<cvox.SemanticTree.Node>} The nodes in the tree for which the
+ * @return {!Array<cvox.SemanticTree.Node>} The nodes in the tree for which the
* predicate holds.
*/
cvox.SemanticTree.Node.prototype.querySelectorAll = function(pred) {
@@ -124,7 +124,7 @@ cvox.SemanticTree.Node.prototype.querySelectorAll = function(pred) {
/**
* Translates a list of nodes into XML representation.
* @param {string} tag Name of the enclosing tag.
- * @param {!Array.<!cvox.SemanticTree.Node>} nodes A list of nodes.
+ * @param {!Array<!cvox.SemanticTree.Node>} nodes A list of nodes.
* @return {Node} An XML representation of the node list.
*/
var xmlNodeList = function(tag, nodes) {
@@ -283,7 +283,7 @@ cvox.SemanticTree.Node.prototype.updateContent_ = function(content) {
* Adds MathML nodes to the node's store of MathML nodes if necessary only, as
* we can not necessarily assume that the MathML of the content nodes and
* children are all disjoint.
- * @param {Array.<Node>} mmlNodes List of MathML nodes.
+ * @param {Array<Node>} mmlNodes List of MathML nodes.
* @private
*/
cvox.SemanticTree.Node.prototype.addMathmlNodes_ = function(mmlNodes) {
@@ -297,7 +297,7 @@ cvox.SemanticTree.Node.prototype.addMathmlNodes_ = function(mmlNodes) {
/**
* Removes MathML nodes from the node's store of MathML nodes.
- * @param {Array.<Node>} mmlNodes List of MathML nodes.
+ * @param {Array<Node>} mmlNodes List of MathML nodes.
* @private
*/
cvox.SemanticTree.Node.prototype.removeMathmlNodes_ = function(mmlNodes) {
@@ -493,8 +493,8 @@ cvox.SemanticTree.prototype.parseMathml_ = function(mml) {
/**
* Parse a list of MathML nodes into the semantic tree.
- * @param {Array.<Element>} mmls A list of MathML nodes.
- * @return {!Array.<cvox.SemanticTree.Node>} The list of resulting semantic
+ * @param {Array<Element>} mmls A list of MathML nodes.
+ * @return {!Array<cvox.SemanticTree.Node>} The list of resulting semantic
* node.
* @private
*/
@@ -549,8 +549,8 @@ cvox.SemanticTree.prototype.makeLeafNode_ = function(mml) {
/**
* Create a branching node.
* @param {!cvox.SemanticAttr.Type} type The type of the node.
- * @param {!Array.<cvox.SemanticTree.Node>} children The child nodes.
- * @param {!Array.<cvox.SemanticTree.Node>} contentNodes The content Nodes.
+ * @param {!Array<cvox.SemanticTree.Node>} children The child nodes.
+ * @param {!Array<cvox.SemanticTree.Node>} contentNodes The content Nodes.
* @param {string=} content Content string if there is any.
* @return {!cvox.SemanticTree.Node} The new node.
* @private
@@ -577,7 +577,7 @@ cvox.SemanticTree.prototype.makeBranchNode_ = function(
/**
* Create a branching node for an implicit operation, currently assumed to
* be of multiplicative type.
- * @param {!Array.<!cvox.SemanticTree.Node>} nodes The operands.
+ * @param {!Array<!cvox.SemanticTree.Node>} nodes The operands.
* @return {!cvox.SemanticTree.Node} The new branch node.
* @private
*/
@@ -596,7 +596,7 @@ cvox.SemanticTree.prototype.makeImplicitNode_ = function(nodes) {
/**
* Create a branching node for an infix operation.
- * @param {!Array.<cvox.SemanticTree.Node>} children The operands.
+ * @param {!Array<cvox.SemanticTree.Node>} children The operands.
* @param {!cvox.SemanticTree.Node} opNode The operator.
* @return {!cvox.SemanticTree.Node} The new branch node.
* @private
@@ -612,7 +612,7 @@ cvox.SemanticTree.prototype.makeInfixNode_ = function(children, opNode) {
* one content (thereby concatenating the content of each node into a single
* content string) with the inner node as a child.
* @param {!cvox.SemanticTree.Node} inner The inner node.
- * @param {!Array.<cvox.SemanticTree.Node>} nodeList List of nodes.
+ * @param {!Array<cvox.SemanticTree.Node>} nodeList List of nodes.
* @param {!cvox.SemanticAttr.Type} type The new type of the node.
* @return {!cvox.SemanticTree.Node} The new branch node.
* @private
@@ -635,7 +635,7 @@ cvox.SemanticTree.prototype.makeConcatNode_ = function(inner, nodeList, type) {
* Example: + - a becomes (+ (- (a)))
* Input: a [+, -] -> Output: content: '+ -', child: a
* @param {!cvox.SemanticTree.Node} node The inner node.
- * @param {!Array.<cvox.SemanticTree.Node>} prefixes Prefix operators
+ * @param {!Array<cvox.SemanticTree.Node>} prefixes Prefix operators
* from the outermost to the innermost.
* @return {!cvox.SemanticTree.Node} The new branch node.
* @private
@@ -662,7 +662,7 @@ cvox.SemanticTree.prototype.makePrefixNode_ = function(node, prefixes) {
* Example: a - + becomes (((a) -) +)
* Input: a [-, +] -> Output: content: '- +', child: a
* @param {!cvox.SemanticTree.Node} node The inner node.
- * @param {!Array.<cvox.SemanticTree.Node>} postfixes Postfix operators from
+ * @param {!Array<cvox.SemanticTree.Node>} postfixes Postfix operators from
* innermost to outermost.
* @return {!cvox.SemanticTree.Node} The new branch node.
* @private
@@ -683,7 +683,7 @@ cvox.SemanticTree.prototype.makePostfixNode_ = function(node, postfixes) {
*
* This is the main heuristic to rewrite a flat row of terms into a meaningful
* term tree.
- * @param {!Array.<cvox.SemanticTree.Node>} nodes The list of nodes.
+ * @param {!Array<cvox.SemanticTree.Node>} nodes The list of nodes.
* @return {!cvox.SemanticTree.Node} The root node of the syntax tree.
* @private
*/
@@ -702,7 +702,7 @@ cvox.SemanticTree.prototype.processRow_ = function(nodes) {
/**
* Constructs a syntax tree with relation and operator precedence from a list
* of nodes.
- * @param {!Array.<!cvox.SemanticTree.Node>} nodes The list of nodes.
+ * @param {!Array<!cvox.SemanticTree.Node>} nodes The list of nodes.
* @return {!cvox.SemanticTree.Node} The root node of the syntax tree.
* @private
*/
@@ -732,7 +732,7 @@ cvox.SemanticTree.prototype.processRelationsInRow_ = function(nodes) {
/**
* Constructs a syntax tree with operator precedence from a list nodes.
- * @param {!Array.<!cvox.SemanticTree.Node>} nodes The list of nodes.
+ * @param {!Array<!cvox.SemanticTree.Node>} nodes The list of nodes.
* @return {!cvox.SemanticTree.Node} The root node of the syntax tree.
* @private
*/
@@ -762,7 +762,7 @@ cvox.SemanticTree.prototype.processOperationsInRow_ = function(nodes) {
// At this point, we know that split.head is not empty!
var node = this.makePrefixNode_(
this.makeImplicitNode_(
- /** @type {!Array.<!cvox.SemanticTree.Node>} */ (split.head)),
+ /** @type {!Array<!cvox.SemanticTree.Node>} */ (split.head)),
prefix);
if (!split.div) {
return node;
@@ -774,11 +774,11 @@ cvox.SemanticTree.prototype.processOperationsInRow_ = function(nodes) {
/**
* Recursively constructs syntax tree with operator precedence from a list nodes
* given a initial root node.
- * @param {!Array.<cvox.SemanticTree.Node>} nodes The list of nodes.
+ * @param {!Array<cvox.SemanticTree.Node>} nodes The list of nodes.
* @param {!cvox.SemanticTree.Node} root Initial tree.
* @param {!cvox.SemanticTree.Node} lastop Last operator that has not been
* processed yet.
- * @param {Array.<cvox.SemanticTree.Node>=} prefixes Operator nodes that will
+ * @param {Array<cvox.SemanticTree.Node>=} prefixes Operator nodes that will
* become prefix operation (or postfix in case they come after last operand).
* @return {!cvox.SemanticTree.Node} The root node of the syntax tree.
* @private
@@ -918,8 +918,8 @@ cvox.SemanticTree.prototype.appendExistingOperator_ = function(root, op, node) {
* number of matching fences. E.g. || a|b || would be turned into a fenced
* node with fences || and content a|b.
* 4. Any remaining unmatched delimiters are turned into punctuation nodes.
- * @param {!Array.<!cvox.SemanticTree.Node>} nodes The list of nodes.
- * @return {!Array.<!cvox.SemanticTree.Node>} The new list of nodes.
+ * @param {!Array<!cvox.SemanticTree.Node>} nodes The list of nodes.
+ * @return {!Array<!cvox.SemanticTree.Node>} The new list of nodes.
* @private
*/
cvox.SemanticTree.prototype.getFencesInRow_ = function(nodes) {
@@ -934,13 +934,13 @@ cvox.SemanticTree.prototype.getFencesInRow_ = function(nodes) {
* Recursively processes a list of nodes and combines all the fenced expressions
* into single nodes. It also processes singular fences, building expressions
* that are only fenced left or right.
- * @param {!Array.<cvox.SemanticTree.Node>} fences FIFO queue of fence nodes.
- * @param {!Array.<Array.<cvox.SemanticTree.Node>>} content FIFO queue content
+ * @param {!Array<cvox.SemanticTree.Node>} fences FIFO queue of fence nodes.
+ * @param {!Array<Array<cvox.SemanticTree.Node>>} content FIFO queue content
* between fences.
- * @param {!Array.<cvox.SemanticTree.Node>} openStack LIFO stack of open fences.
- * @param {!Array.<!Array.<cvox.SemanticTree.Node>>} contentStack LIFO stack of
+ * @param {!Array<cvox.SemanticTree.Node>} openStack LIFO stack of open fences.
+ * @param {!Array<!Array<cvox.SemanticTree.Node>>} contentStack LIFO stack of
* content between fences yet to be processed.
- * @return {!Array.<cvox.SemanticTree.Node>} A list of nodes with all fenced
+ * @return {!Array<cvox.SemanticTree.Node>} A list of nodes with all fenced
* expressions processed.
* @private
*/
@@ -1057,10 +1057,10 @@ cvox.SemanticTree.prototype.processFences_ = function(
// TODO (sorge) The following could be done with linear programming.
/**
* Trys to combine neutral fences as much as possible.
- * @param {!Array.<!cvox.SemanticTree.Node>} fences A list of neutral fences.
- * @param {!Array.<!Array.<cvox.SemanticTree.Node>>} content Intermediate
+ * @param {!Array<!cvox.SemanticTree.Node>} fences A list of neutral fences.
+ * @param {!Array<!Array<cvox.SemanticTree.Node>>} content Intermediate
* content. Observe that |content| = |fences| - 1
- * @return {!Array.<cvox.SemanticTree.Node>} List of node with fully fenced
+ * @return {!Array<cvox.SemanticTree.Node>} List of node with fully fenced
* nodes.
* @private
*/
@@ -1099,12 +1099,12 @@ cvox.SemanticTree.prototype.processNeutralFences_ = function(fences, content) {
* return: [c1 | c2 | c3 ], c4, ... cn
* @param {!cvox.SemanticTree.Node} leftFence The left fence.
* @param {!cvox.SemanticTree.Node} rightFence The right fence.
- * @param {!Array.<cvox.SemanticTree.Node>} midFences A list of intermediate
+ * @param {!Array<cvox.SemanticTree.Node>} midFences A list of intermediate
* fences.
- * @param {!Array.<!Array.<cvox.SemanticTree.Node>>} content Intermediate
+ * @param {!Array<!Array<cvox.SemanticTree.Node>>} content Intermediate
* content. Observe that |content| = |fences| - 1 + k where k >= 0 is the
* remainder.
- * @return {!Array.<!Array.<cvox.SemanticTree.Node>>} List of content nodes
+ * @return {!Array<!Array<cvox.SemanticTree.Node>>} List of content nodes
* where the first is the fully fenced node wrt. the given left and right
* fence.
* @private
@@ -1163,7 +1163,7 @@ cvox.SemanticTree.fenceToPunct_ = function(fence) {
* Create a fenced node.
* @param {cvox.SemanticTree.Node} ofence Opening fence.
* @param {cvox.SemanticTree.Node} cfence Closing fence.
- * @param {!Array.<cvox.SemanticTree.Node>} content The content
+ * @param {!Array<cvox.SemanticTree.Node>} content The content
* between the fences.
* @return {!cvox.SemanticTree.Node} The new node.
* @private
@@ -1184,8 +1184,8 @@ cvox.SemanticTree.prototype.makeHorizontalFencedNode_ = function(
/**
* Combines sequences of punctuated expressions in a list of nodes.
- * @param {!Array.<cvox.SemanticTree.Node>} nodes The list of nodes.
- * @return {!Array.<cvox.SemanticTree.Node>} The new list of nodes.
+ * @param {!Array<cvox.SemanticTree.Node>} nodes The list of nodes.
+ * @return {!Array<cvox.SemanticTree.Node>} The new list of nodes.
* @private
*/
cvox.SemanticTree.prototype.getPunctuationInRow_ = function(nodes) {
@@ -1219,9 +1219,9 @@ cvox.SemanticTree.prototype.getPunctuationInRow_ = function(nodes) {
/**
* Create a punctuated node.
- * @param {!Array.<!cvox.SemanticTree.Node>} nodes List of all nodes separated
+ * @param {!Array<!cvox.SemanticTree.Node>} nodes List of all nodes separated
* by punctuations.
- * @param {!Array.<!cvox.SemanticTree.Node>} punctuations List of all separating
+ * @param {!Array<!cvox.SemanticTree.Node>} punctuations List of all separating
* punctations. Observe that punctations is a subset of nodes.
* @return {!cvox.SemanticTree.Node}
* @private
@@ -1248,7 +1248,7 @@ cvox.SemanticTree.prototype.makePunctuatedNode_ = function(
* Creates a limit node from a sub/superscript or over/under node if the central
* element is a big operator. Otherwise it creates the standard elements.
* @param {string} mmlTag The tag name of the original node.
- * @param {!Array.<!cvox.SemanticTree.Node>} children The children of the
+ * @param {!Array<!cvox.SemanticTree.Node>} children The children of the
* original node.
* @return {!cvox.SemanticTree.Node} The newly created limit node.
* @private
@@ -1331,10 +1331,10 @@ cvox.SemanticTree.prototype.makeLimitNode_ = function(mmlTag, children) {
* symbol. If we have an explicit function application symbol
* following the expression we turn into a prefix function. Otherwise
* we decide heuristically if we could have a function application.
- * @param {!Array.<cvox.SemanticTree.Node>} restNodes The remainder list of
+ * @param {!Array<cvox.SemanticTree.Node>} restNodes The remainder list of
* nodes.
- * @param {!Array.<cvox.SemanticTree.Node>=} result The result node list.
- * @return {!Array.<!cvox.SemanticTree.Node>} The fully processed list.
+ * @param {!Array<cvox.SemanticTree.Node>=} result The result node list.
+ * @return {!Array<!cvox.SemanticTree.Node>} The fully processed list.
* @private
*/
cvox.SemanticTree.prototype.getFunctionsInRow_ = function(restNodes, result) {
@@ -1360,7 +1360,7 @@ cvox.SemanticTree.prototype.getFunctionsInRow_ = function(restNodes, result) {
/**
* Classifies a function wrt. the heuristic that should be applied.
* @param {!cvox.SemanticTree.Node} funcNode The node to be classified.
- * @param {!Array.<cvox.SemanticTree.Node>} restNodes The remainder list of
+ * @param {!Array<cvox.SemanticTree.Node>} restNodes The remainder list of
* nodes. They can useful to look ahead if there is an explicit function
* application. If there is one, it will be destructively removed!
* @return {!string} The string specifying the heuristic.
@@ -1422,10 +1422,10 @@ cvox.SemanticTree.propagatePrefixFunc_ = function(funcNode) {
* Computes the arguments for a function from a list of nodes depending on the
* given heuristic.
* @param {!cvox.SemanticTree.Node} func A function node.
- * @param {!Array.<cvox.SemanticTree.Node>} rest List of nodes to choose
+ * @param {!Array<cvox.SemanticTree.Node>} rest List of nodes to choose
* arguments from.
* @param {string} heuristic The heuristic to follow.
- * @return {!Array.<!cvox.SemanticTree.Node>} The function and the remainder of
+ * @return {!Array<!cvox.SemanticTree.Node>} The function and the remainder of
* the rest list.
* @private
*/
@@ -1482,12 +1482,12 @@ cvox.SemanticTree.prototype.getFunctionArgs_ = function(func, rest, heuristic) {
/**
* Tail recursive function to obtain integral arguments.
- * @param {!Array.<cvox.SemanticTree.Node>} nodes List of nodes to take
+ * @param {!Array<cvox.SemanticTree.Node>} nodes List of nodes to take
* arguments from.
- * @param {Array.<cvox.SemanticTree.Node>=} args List of integral arguments.
- * @return {{integrand: !Array.<cvox.SemanticTree.Node>,
+ * @param {Array<cvox.SemanticTree.Node>=} args List of integral arguments.
+ * @return {{integrand: !Array<cvox.SemanticTree.Node>,
* intvar: cvox.SemanticTree.Node,
- * rest: !Array.<cvox.SemanticTree.Node>}}
+ * rest: !Array<cvox.SemanticTree.Node>}}
* Result split into integrand, integral variable and the remaining
* elements.
* @private
@@ -1664,8 +1664,8 @@ cvox.SemanticTree.generalFunctionBoundary_ = function(node) {
/**
* Rewrites tables into matrices or case statements in a list of nodes.
- * @param {!Array.<cvox.SemanticTree.Node>} nodes List of nodes to rewrite.
- * @return {!Array.<cvox.SemanticTree.Node>} The new list of nodes.
+ * @param {!Array<cvox.SemanticTree.Node>} nodes List of nodes to rewrite.
+ * @return {!Array<cvox.SemanticTree.Node>} The new list of nodes.
* @private
*/
cvox.SemanticTree.prototype.processTablesInRow_ = function(nodes) {
@@ -1745,7 +1745,7 @@ cvox.SemanticTree.prototype.tableToMatrixOrVector_ = function(node) {
* Heuristic to decide if we have a case statement: An expression with a
* singular open fence before it.
* @param {!cvox.SemanticTree.Node} table A table node.
- * @param {!Array.<cvox.SemanticTree.Node>} prevNodes A list of previous nodes.
+ * @param {!Array<cvox.SemanticTree.Node>} prevNodes A list of previous nodes.
* @return {boolean} True if we believe we have a case statement.
* @private
*/
@@ -1852,13 +1852,13 @@ cvox.SemanticTree.assignRoleToRow_ = function(row, role) {
/**
* Splits a list of nodes wrt. to a given predicate.
- * @param {Array.<cvox.SemanticTree.Node>} nodes A list of nodes.
+ * @param {Array<cvox.SemanticTree.Node>} nodes A list of nodes.
* @param {!function(cvox.SemanticTree.Node): boolean} pred Predicate for the
* partitioning relation.
* @param {boolean=} reverse If true slicing is done from the end.
- * @return {{head: !Array.<cvox.SemanticTree.Node>,
+ * @return {{head: !Array<cvox.SemanticTree.Node>,
* div: cvox.SemanticTree.Node,
- * tail: !Array.<cvox.SemanticTree.Node>}} The split list.
+ * tail: !Array<cvox.SemanticTree.Node>}} The split list.
* @private
*/
cvox.SemanticTree.sliceNodes_ = function(nodes, pred, reverse) {
@@ -1889,11 +1889,11 @@ cvox.SemanticTree.sliceNodes_ = function(nodes, pred, reverse) {
/**
* Partitions a list of nodes wrt. to a given predicate. Effectively works like
* a PER on the ordered set of nodes.
- * @param {!Array.<!cvox.SemanticTree.Node>} nodes A list of nodes.
+ * @param {!Array<!cvox.SemanticTree.Node>} nodes A list of nodes.
* @param {!function(cvox.SemanticTree.Node): boolean} pred Predicate for the
* partitioning relation.
- * @return {{rel: !Array.<cvox.SemanticTree.Node>,
- * comp: !Array.<!Array.<cvox.SemanticTree.Node>>}}
+ * @return {{rel: !Array<cvox.SemanticTree.Node>,
+ * comp: !Array<!Array<cvox.SemanticTree.Node>>}}
* The partitioning given in terms of a collection of elements satisfying
* the predicate and a collection of complementary sets lying inbetween the
* related elements. Observe that we always have |comp| = |rel| + 1.
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/common/math_semantic_util.js b/chromium/chrome/browser/resources/chromeos/chromevox/common/math_semantic_util.js
index 525fe4867fc..b444a584bdf 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/common/math_semantic_util.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/common/math_semantic_util.js
@@ -17,8 +17,8 @@ cvox.SemanticUtil = function() { };
/**
* Merges keys of objects into an array.
- * @param {...Object.<string, string>} objects Optional objects.
- * @return {Array.<string>} Array of all keys of the objects.
+ * @param {...Object<string, string>} objects Optional objects.
+ * @return {Array<string>} Array of all keys of the objects.
*/
cvox.SemanticUtil.objectsToKeys = function(objects) {
objects = Array.prototype.slice.call(arguments, 0);
@@ -29,8 +29,8 @@ cvox.SemanticUtil.objectsToKeys = function(objects) {
/**
* Merges values of objects into an array.
- * @param {...Object.<string, string>} objects Optional objects.
- * @return {Array.<string>} Array of all values of the objects.
+ * @param {...Object<string, string>} objects Optional objects.
+ * @return {Array<string>} Array of all values of the objects.
*/
cvox.SemanticUtil.objectsToValues = function(objects) {
objects = Array.prototype.slice.call(arguments, 0);
@@ -96,7 +96,7 @@ cvox.SemanticUtil.tagName = function(node) {
/**
* List of MathML Tags that are to be ignored.
- * @type {Array.<string>}
+ * @type {Array<string>}
* @const
*/
cvox.SemanticUtil.IGNORETAGS = [
@@ -107,7 +107,7 @@ cvox.SemanticUtil.IGNORETAGS = [
/**
* List of MathML Tags to be ignore if they have no children.
- * @type {Array.<string>}
+ * @type {Array<string>}
* @const
*/
cvox.SemanticUtil.EMPTYTAGS = ['MATH', 'MROW', 'MPADDED', 'MSTYLE'];
@@ -118,8 +118,8 @@ cvox.SemanticUtil.EMPTYTAGS = ['MATH', 'MROW', 'MPADDED', 'MSTYLE'];
* ignored if they have empty children.
* Observe that this is currently not recursive, i.e. will not take care of
* pathological cases, where content is hidden in incorrectly used tags!
- * @param {Array.<Element>} nodes The node list to be cleaned.
- * @return {Array.<Element>} The cleansed list.
+ * @param {Array<Element>} nodes The node list to be cleaned.
+ * @return {Array<Element>} The cleansed list.
*/
cvox.SemanticUtil.purgeNodes = function(nodes) {
var nodeArray = [];
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/common/math_util.js b/chromium/chrome/browser/resources/chromeos/chromevox/common/math_util.js
index 34641dedef9..4de27ae3913 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/common/math_util.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/common/math_util.js
@@ -16,7 +16,7 @@ goog.require('cvox.XpathUtil');
* Checks if a node is in a given class of MathML nodes.
* @private
* @param {!Node} node The node to test.
- * @param {Array.<string>} tags List of tag names.
+ * @param {Array<string>} tags List of tag names.
* @return {boolean} True if node has a tag name included in tags.
*/
cvox.MathUtil.isMathmlNodeOfClass_ = function(node, tags) {
@@ -28,7 +28,7 @@ cvox.MathUtil.isMathmlNodeOfClass_ = function(node, tags) {
* Checks if a node is in a given class of MathJax nodes.
* @private
* @param {!Node} node The node to test.
- * @param {Array.<string>} tags List of tag names.
+ * @param {Array<string>} tags List of tag names.
* @return {boolean} True if node has a tag name included in tags.
*/
cvox.MathUtil.isMathjaxNodeOfClass_ = function(node, tags) {
@@ -46,7 +46,7 @@ cvox.MathUtil.isMathjaxNodeOfClass_ = function(node, tags) {
* of MathML or MathJax nodes.
* @private
* @param {!Node} node The node to test.
- * @param {Array.<string>} tags List of tag names.
+ * @param {Array<string>} tags List of tag names.
* @return {boolean} True if node has a tag name included in tags.
*/
cvox.MathUtil.isMathNodeOfClass_ = function(node, tags) {
@@ -58,7 +58,7 @@ cvox.MathUtil.isMathNodeOfClass_ = function(node, tags) {
/**
* Array of MathML Token Elements.
- * @type {!Array.<string>}
+ * @type {!Array<string>}
*/
cvox.MathUtil.TOKEN_LIST = ['MI', 'MN', 'MO', 'MTEXT', 'MSPACE', 'MS'];
@@ -82,7 +82,7 @@ cvox.MathUtil.isToken = function(element) {
/**
* Array of MathML Layout Schemata.
- * @type {!Array.<string>}
+ * @type {!Array<string>}
*/
cvox.MathUtil.LAYOUT_LIST = ['MROW', 'MFRAC', 'MSQRT', 'MROOT', 'MSTYLE',
'MERROR', 'MPADDED', 'MPHANTOM', 'MFENCED',
@@ -113,7 +113,7 @@ cvox.MathUtil.isLayout = function(element) {
/**
* Array of MathML Script Schemata.
- * @type {!Array.<string>}
+ * @type {!Array<string>}
*/
cvox.MathUtil.SCRIPT_LIST = ['MSUB', 'MSUP', 'MSUBSUP', 'MUNDER', 'MOVER',
'MUNDEROVER', 'MMULTISCRIPTS', 'MPRESCRIPTS'];
@@ -143,7 +143,7 @@ cvox.MathUtil.isScript = function(element) {
/**
* Array of MathML Table and Matrix tokens.
- * @type {!Array.<string>}
+ * @type {!Array<string>}
*/
cvox.MathUtil.TABLES_LIST = ['MTABLE', 'MLABELEDTR', 'MTR', 'MTD',
'MALIGNGROUP', 'MALIGNMARK'];
@@ -168,7 +168,7 @@ cvox.MathUtil.isTables = function(element) {
/**
* Array of MathML Elementary Layout Schemata.
- * @type {!Array.<string>}
+ * @type {!Array<string>}
*/
cvox.MathUtil.ELEMENTARY_LIST = ['MSTACK', 'MLONGDIV', 'MSGROUP', 'MSROW',
'MSCARRIES', 'MSCARRY', 'MSLINE'];
@@ -197,7 +197,7 @@ cvox.MathUtil.isElementary = function(element) {
/**
* Array of all valid tags in a MathML expression.
* This is a union of all other token lists.
- * @type {!Array.<string>}
+ * @type {!Array<string>}
*/
cvox.MathUtil.MATHML_TAG_LIST = [cvox.MathUtil.TOKEN_LIST,
cvox.MathUtil.LAYOUT_LIST,
@@ -221,7 +221,7 @@ cvox.MathUtil.isMathmlTag = function(element) {
/**
* Array of MathML Whitespace and Alignment tokens.
* These are elements that can occur in the other token lists.
- * @type {!Array.<string>}
+ * @type {!Array<string>}
*/
cvox.MathUtil.WHITESPACE_LIST = ['MSROW', 'MROW', 'MSPACE',
'MPHANTOM', 'MPADDED'];
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/common/memoize.js b/chromium/chrome/browser/resources/chromeos/chromevox/common/memoize.js
index 271722c99e5..227b26f2646 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/common/memoize.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/common/memoize.js
@@ -51,7 +51,7 @@ cvox.Memoize = function() {
* to function result. This variable is null when we're out of scope, and it's
* a map from string to WeakMap to result when we're in scope.
*
- * @type {?Object.<string, WeakMap.<Node, *> >}
+ * @type {?Object<string, WeakMap<Node, *> >}
* @private
*/
cvox.Memoize.nodeMap_ = null;
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/common/nav_description.js b/chromium/chrome/browser/resources/chromeos/chromevox/common/nav_description.js
index 1a946467500..e143ca17d1d 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/common/nav_description.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/common/nav_description.js
@@ -23,7 +23,7 @@ goog.require('cvox.QueueMode');
* text: (string),
* userValue: (undefined|string),
* annotation: (undefined|string),
- * earcons: (undefined|Array.<number>),
+ * earcons: (undefined|Array<number>),
* personality: (undefined|Object),
* hint: (undefined|string),
category: (undefined|string)}} kwargs The arguments for this
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/common/nav_math_description.js b/chromium/chrome/browser/resources/chromeos/chromevox/common/nav_math_description.js
index 4e4bdf0ca21..c4023070d6c 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/common/nav_math_description.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/common/nav_math_description.js
@@ -20,7 +20,7 @@ goog.require('cvox.NavDescription');
* text: (string),
* userValue: (undefined|string),
* annotation: (undefined|string),
- * earcons: (undefined|Array.<number>),
+ * earcons: (undefined|Array<number>),
* personality: (undefined|Object),
* hint: (undefined|string),
* category: (undefined|string),
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/common/node_state.js b/chromium/chrome/browser/resources/chromeos/chromevox/common/node_state.js
index 4793b3ecfb7..6abc835bd65 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/common/node_state.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/common/node_state.js
@@ -16,7 +16,7 @@ goog.provide('cvox.NodeStateUtil');
* cvox.ChromeVox.getMsg() call. For example [list_position, 3, 5] maps to
* getMsg('list_position', [3, 5]);
*
- * @typedef {!Array.<!Array.<string|number>>}
+ * @typedef {!Array<!Array<string|number>>}
*/
cvox.NodeState;
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/common/page_selection.js b/chromium/chrome/browser/resources/chromeos/chromevox/common/page_selection.js
index 0517c58f1cd..ad39e4a59e3 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/common/page_selection.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/common/page_selection.js
@@ -37,7 +37,7 @@ cvox.PageSelection = function(sel) {
* @param {!cvox.CursorSelection} prevSel Previous CursorSelection in
* navigation.
* @param {!cvox.CursorSelection} curSel Current CursorSelection in navigation.
- * @return {Array.<cvox.NavDescription>} The new description.
+ * @return {Array<cvox.NavDescription>} The new description.
*/
cvox.PageSelection.prototype.getDescription =
function(navShifter, prevSel, curSel) {
@@ -71,7 +71,7 @@ cvox.PageSelection.prototype.getDescription =
* Use this description when you want to describe the entire selection
* represented by this instance.
*
- * @return {Array.<cvox.NavDescription>} The new description.
+ * @return {Array<cvox.NavDescription>} The new description.
*/
cvox.PageSelection.prototype.getFullDescription = function() {
return [new cvox.NavDescription(
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/common/selection_util.js b/chromium/chrome/browser/resources/chromeos/chromevox/common/selection_util.js
index 74c5cc4b4b0..3057d2efc9b 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/common/selection_util.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/common/selection_util.js
@@ -207,7 +207,7 @@ cvox.SelectionUtil.isRangeValid = function(range) {
* Returns absolute top and left positions of an element.
*
* @param {!Node} node The element for which to compute the position.
- * @return {Array.<number>} Index 0 is the left; index 1 is the top.
+ * @return {Array<number>} Index 0 is the left; index 1 is the top.
* @private
*/
cvox.SelectionUtil.findPos_ = function(node) {
@@ -588,6 +588,7 @@ cvox.SelectionUtil.getText = function() {
* if you want i18n tests to pass.
*
* @return {string} The text.
+ * @private
*/
cvox.SelectionUtil.getSelectionText_ = function() {
return '' + window.getSelection();
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/common/spannable.js b/chromium/chrome/browser/resources/chromeos/chromevox/common/spannable.js
index ea113a8041e..b4773e274a4 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/common/spannable.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/common/spannable.js
@@ -12,7 +12,7 @@ goog.require('goog.object');
/**
* @constructor
- * @param {string=} opt_string Initial value of the spannable.
+ * @param {string|!cvox.Spannable=} opt_string Initial value of the spannable.
* @param {*=} opt_annotation Initial annotation for the entire string.
*/
cvox.Spannable = function(opt_string, opt_annotation) {
@@ -21,15 +21,19 @@ cvox.Spannable = function(opt_string, opt_annotation) {
* @type {string}
* @private
*/
- this.string_ = opt_string || '';
+ this.string_ = opt_string instanceof cvox.Spannable ? '' : opt_string || '';
/**
* Spans (annotations).
- * @type {!Array.<!{ value: *, start: number, end: number }>}
+ * @type {!Array<!{ value: *, start: number, end: number }>}
* @private
*/
this.spans_ = [];
+ // Append the initial spannable.
+ if (opt_string instanceof cvox.Spannable)
+ this.append(opt_string);
+
// Optionally annotate the entire string.
if (goog.isDef(opt_annotation)) {
var len = this.string_.length;
@@ -135,6 +139,22 @@ cvox.Spannable.prototype.getSpanInstanceOf = function(constructor) {
}
};
+/**
+ * Returns all span values which are an instance of a given constructor.
+ * @param {!Function} constructor Constructor.
+ * @return {!Array<Object>} Array of object.
+ */
+cvox.Spannable.prototype.getSpansInstanceOf = function(constructor) {
+ var ret = [];
+ for (var i = 0; i < this.spans_.length; i++) {
+ var span = this.spans_[i];
+ if (span.value instanceof constructor) {
+ ret.push(span.value);
+ }
+ }
+ return ret;
+};
+
/**
* Returns all spans matching a position.
@@ -385,7 +405,7 @@ cvox.Spannable.SerializeInfo_;
/**
* The serialized format of a spannable.
- * @typedef {{string: string, spans: Array.<cvox.Spannable.SerializedSpan_>}}
+ * @typedef {{string: string, spans: Array<cvox.Spannable.SerializedSpan_>}}
* @private
*/
cvox.Spannable.SerializedSpannable_;
@@ -400,7 +420,7 @@ cvox.Spannable.SerializedSpan_;
/**
* Maps type names to serialization info objects.
- * @type {Object.<string, cvox.Spannable.SerializeInfo_>}
+ * @type {Object<string, cvox.Spannable.SerializeInfo_>}
* @private
*/
cvox.Spannable.serializableSpansByName_ = {};
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/common/string_util.js b/chromium/chrome/browser/resources/chromeos/chromevox/common/string_util.js
new file mode 100644
index 00000000000..5b771c5a774
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/common/string_util.js
@@ -0,0 +1,32 @@
+// Copyright 2015 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 Utilities for strings.
+ */
+
+goog.provide('StringUtil');
+
+/**
+ * @constructor
+ */
+StringUtil = function() {};
+
+/**
+ * Returns the length of the longest common prefix of two strings.
+ * @param {string} first The first string.
+ * @param {string} second The second string.
+ * @return {number} The length of the longest common prefix, which may be 0
+ * for an empty common prefix.
+ */
+StringUtil.longestCommonPrefixLength = function(first, second) {
+ var limit = Math.min(first.length, second.length);
+ var i;
+ for (i = 0; i < limit; ++i) {
+ if (first.charAt(i) != second.charAt(i)) {
+ break;
+ }
+ }
+ return i;
+};
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/common/string_util_test.unitjs b/chromium/chrome/browser/resources/chromeos/chromevox/common/string_util_test.unitjs
new file mode 100644
index 00000000000..efd31a5afdb
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/common/string_util_test.unitjs
@@ -0,0 +1,32 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Include test fixture.
+GEN_INCLUDE(['../testing/chromevox_unittest_base.js']);
+
+/**
+ * Test fixture.
+ * @constructor
+ * @extends {ChromeVoxUnitTestBase}
+ */
+function StringUtilUnitTest() {
+ ChromeVoxUnitTestBase.call(this);
+}
+
+StringUtilUnitTest.prototype = {
+ __proto__: ChromeVoxUnitTestBase.prototype,
+
+ /** @override */
+ closureModuleDeps: [
+ 'StringUtil',
+ ],
+};
+
+TEST_F('StringUtilUnitTest', 'longestCommonPrefixLength', function() {
+ var lcpl = StringUtil.longestCommonPrefixLength;
+ assertEquals(0, lcpl('', ''));
+ assertEquals(0, lcpl('', 'hello'));
+ assertEquals(0, lcpl('hello', ''));
+ assertEquals(1, lcpl('hi', 'hello'));
+});
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/common/traverse_content.js b/chromium/chrome/browser/resources/chromeos/chromevox/common/traverse_content.js
index 4df917ef969..4ca1ec4183c 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/common/traverse_content.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/common/traverse_content.js
@@ -115,7 +115,7 @@ cvox.TraverseContent.kParagraph = 'paragraph';
/**
* A constant array of all granularities.
- * @type {Array.<string>}
+ * @type {Array<string>}
* @const
*/
cvox.TraverseContent.kAllGrains =
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/common/traverse_math.js b/chromium/chrome/browser/resources/chromeos/chromevox/common/traverse_math.js
index 83a622035d9..df4814ee58d 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/common/traverse_math.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/common/traverse_math.js
@@ -30,14 +30,14 @@ cvox.TraverseMath = function() {
/**
* Dictionary of all LaTeX elements in the page if there are any.
- * @type {!Object.<string, !Node>}
+ * @type {!Object<string, !Node>}
* @private
*/
this.allTexs_ = {};
/**
* Dictionary of all MathJaxs elements in the page if there are any.
- * @type {!Object.<string, !Node>}
+ * @type {!Object<string, !Node>}
* @private
*/
this.allMathjaxs_ = {};
@@ -45,7 +45,7 @@ cvox.TraverseMath = function() {
/**
* Dictionary of all MathJaxs elements that have not yet been translated at
* page load or during MathJax rendering.
- * @type {!Object.<string, !Node>}
+ * @type {!Object<string, !Node>}
* @private
*/
this.todoMathjaxs_ = {};
@@ -65,13 +65,13 @@ cvox.TraverseMath = function() {
/**
* List of domain names.
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.allDomains = [];
/**
* List of style names.
- * @type {Array.<string>}
+ * @type {Array<string>}
*/
this.allStyles = [];
@@ -337,8 +337,8 @@ cvox.TraverseMath.prototype.nextParentChild = function(r) {
/**
* Adds a list of domains and styles to the existing one.
- * @param {Array.<string>} domains List of domain names.
- * @param {Array.<string>} styles List of style names.
+ * @param {Array<string>} domains List of domain names.
+ * @param {Array<string>} styles List of style names.
*/
cvox.TraverseMath.prototype.addDomainsAndStyles = function(domains, styles) {
this.allDomains.push.apply(
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/common/traverse_table.js b/chromium/chrome/browser/resources/chromeos/chromevox/common/traverse_table.js
index 6253fa07f75..8927c1d8846 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/common/traverse_table.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/common/traverse_table.js
@@ -22,7 +22,19 @@ goog.require('cvox.TraverseUtil');
* An object that represents an active table cell inside the shadow table.
* @constructor
*/
-function ShadowTableNode() {}
+function ShadowTableNode() {
+ /**
+ * The cells that are row headers of the corresponding active table cell
+ * @type {!Array}
+ */
+ this.rowHeaderCells = [];
+
+ /**
+ * The cells that are column headers of the corresponding active table cell
+ * @type {!Array}
+ */
+ this.colHeaderCells = [];
+}
/**
@@ -68,21 +80,6 @@ ShadowTableNode.prototype.activeCell;
/**
- * The cells that are row headers of the corresponding active table cell
- * @type {!Array}
- */
-ShadowTableNode.prototype.rowHeaderCells = [];
-
-
-/**
- * The cells that are column headers of the corresponding active table cell
- * @type {!Array}
- */
-ShadowTableNode.prototype.colHeaderCells = [];
-
-
-
-/**
* Initializes the traversal with the provided table node.
*
* @constructor
@@ -114,7 +111,7 @@ cvox.TraverseTable = function(tableNode) {
* the (1,3) position, eliminating the need to check for predecessor cells
* with rowspan/colspan every time we traverse the table.
*
- * @type {!Array.<Array.<ShadowTableNode>>}
+ * @type {!Array<Array<ShadowTableNode>>}
* @private
*/
this.shadowTable_ = [];
@@ -125,7 +122,7 @@ cvox.TraverseTable = function(tableNode) {
* initialization and then only recalculated if the table changes.
* This array is used by findHeaderCells() to determine table row headers
* and column headers.
- * @type {Array.<ShadowTableNode>}
+ * @type {Array<ShadowTableNode>}
* @private
*/
this.candidateHeaders_ = [];
@@ -136,7 +133,7 @@ cvox.TraverseTable = function(tableNode) {
* other cells) then the first one will be associated with the ID. This means
* that shadow nodes that have spanned set to true will not be included in
* this array.
- * @type {Array.<ShadowTableNode>}
+ * @type {Array<ShadowTableNode>}
* @private
*/
this.idToShadowNode_ = [];
@@ -265,7 +262,7 @@ cvox.TraverseTable.prototype.initialize = function(tableNode) {
* Finds the cell cursor containing the specified node within the table.
* Returns null if there is no close cell.
* @param {!Node} node The node for which to find the cursor.
- * @return {Array.<number>} The table index for the node.
+ * @return {Array<number>} The table index for the node.
*/
cvox.TraverseTable.prototype.findNearestCursor = function(node) {
// TODO (stoarca): The current structure for representing the
@@ -856,7 +853,7 @@ cvox.TraverseTable.prototype.getCell = function() {
/**
* Gets the cell at the specified location.
- * @param {Array.<number>} index The index <i, j> of the required cell.
+ * @param {Array<number>} index The index <i, j> of the required cell.
* @return {?Node} The cell <TD> or <TH> or role='gridcell' node at the
* specified location. Null if that cell does not exist.
*/
@@ -1220,7 +1217,7 @@ cvox.TraverseTable.prototype.goToCol = function(index) {
/**
* Moves to the cell at the specified index <i, j> in the table. Updates the
* cell cursor.
- * @param {Array.<number>} index The index <i, j> of the required cell.
+ * @param {Array<number>} index The index <i, j> of the required cell.
* @return {boolean} Either:
* 1) True if the index is valid and the update has been made.
* 2) False if the index is not valid (either less than 0, greater than
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/common/traverse_util.js b/chromium/chrome/browser/resources/chromeos/chromevox/common/traverse_util.js
index 18781e376d6..7ae41862861 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/common/traverse_util.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/common/traverse_util.js
@@ -111,8 +111,8 @@ cvox.TraverseUtil.isHidden = function(node) {
* @param {cvox.Cursor} cursor The cursor location where the search should
* start. On exit, the cursor will be immediately to the right of the
* character returned.
- * @param {Array.<Element>} elementsEntered Any HTML elements entered.
- * @param {Array.<Element>} elementsLeft Any HTML elements left.
+ * @param {Array<Element>} elementsEntered Any HTML elements entered.
+ * @param {Array<Element>} elementsLeft Any HTML elements left.
* @return {?string} The character found, or null if the bottom of the
* document has been reached.
*/
@@ -205,8 +205,8 @@ cvox.TraverseUtil.forwardsChar = function(
* @param {cvox.Cursor} cursor The cursor location where the search should
* start. On exit, the cursor will be immediately to the left of the
* character returned.
- * @param {Array.<Element>} elementsEntered Any HTML elements entered.
- * @param {Array.<Element>} elementsLeft Any HTML elements left.
+ * @param {Array<Element>} elementsEntered Any HTML elements entered.
+ * @param {Array<Element>} elementsLeft Any HTML elements left.
* @return {?string} The previous character, or null if the top of the
* document has been reached.
*/
@@ -310,8 +310,8 @@ cvox.TraverseUtil.backwardsChar = function(
* the char.
* @param {!cvox.Cursor} endCursor The position to start searching for the next
* char. On exit, will point to the position past the char.
- * @param {Array.<Element>} elementsEntered Any HTML elements entered.
- * @param {Array.<Element>} elementsLeft Any HTML elements left.
+ * @param {Array<Element>} elementsEntered Any HTML elements entered.
+ * @param {Array<Element>} elementsLeft Any HTML elements left.
* initial and final cursor position will be pushed onto this array.
* @param {boolean} skipWhitespace If true, will keep scanning until a
* non-whitespace character is found.
@@ -373,8 +373,8 @@ cvox.TraverseUtil.getNextChar = function(
* char. On exit, will point to the position before the char.
* @param {!cvox.Cursor} endCursor The position to start searching for the next
* char. On exit, will point to the position past the char.
- * @param {Array.<Element>} elementsEntered Any HTML elements entered.
- * @param {Array.<Element>} elementsLeft Any HTML elements left.
+ * @param {Array<Element>} elementsEntered Any HTML elements entered.
+ * @param {Array<Element>} elementsLeft Any HTML elements left.
* initial and final cursor position will be pushed onto this array.
* @param {boolean} skipWhitespace If true, will keep scanning until a
* non-whitespace character is found.
@@ -431,8 +431,8 @@ cvox.TraverseUtil.getPreviousChar = function(
* word returned.
* @param {cvox.Cursor} endCursor The position to start searching for the next
* word. On exit, will point to the end of the word returned.
- * @param {Array.<Element>} elementsEntered Any HTML elements entered.
- * @param {Array.<Element>} elementsLeft Any HTML elements left.
+ * @param {Array<Element>} elementsEntered Any HTML elements entered.
+ * @param {Array<Element>} elementsLeft Any HTML elements left.
* @return {?string} The next word, or null if the bottom of the
* document has been reached.
*/
@@ -492,8 +492,8 @@ cvox.TraverseUtil.getNextWord = function(startCursor, endCursor,
* word returned.
* @param {cvox.Cursor} endCursor On exit, will point to the end of the
* word returned.
- * @param {Array.<Element>} elementsEntered Any HTML elements entered.
- * @param {Array.<Element>} elementsLeft Any HTML elements left.
+ * @param {Array<Element>} elementsEntered Any HTML elements entered.
+ * @param {Array<Element>} elementsLeft Any HTML elements left.
* @return {?string} The previous word, or null if the bottom of the
* document has been reached.
*/
@@ -546,9 +546,9 @@ cvox.TraverseUtil.getPreviousWord = function(startCursor, endCursor,
/**
* Given elements entered and left, and break tags, returns true if the
* current word should break.
- * @param {Array.<Element>} elementsEntered Any HTML elements entered.
- * @param {Array.<Element>} elementsLeft Any HTML elements left.
- * @param {Object.<string, boolean>} breakTags Associative array of tags
+ * @param {Array<Element>} elementsEntered Any HTML elements entered.
+ * @param {Array<Element>} elementsLeft Any HTML elements left.
+ * @param {Object<string, boolean>} breakTags Associative array of tags
* that should break.
* @return {boolean} True if elementsEntered or elementsLeft include an
* element with one of these tags.
@@ -584,9 +584,9 @@ cvox.TraverseUtil.includesBreakTagOrSkippedNode = function(
* sentence.
* @param {cvox.Cursor} endCursor The position to start searching for the next
* sentence. On exit, will point to the end of the returned string.
- * @param {Array.<Element>} elementsEntered Any HTML elements entered.
- * @param {Array.<Element>} elementsLeft Any HTML elements left.
- * @param {Object.<string, boolean>} breakTags Associative array of tags
+ * @param {Array<Element>} elementsEntered Any HTML elements entered.
+ * @param {Array<Element>} elementsLeft Any HTML elements left.
+ * @param {Object<string, boolean>} breakTags Associative array of tags
* that should break the sentence.
* @return {?string} The next sentence, or null if the bottom of the
* document has been reached.
@@ -610,9 +610,9 @@ cvox.TraverseUtil.getNextSentence = function(
* @param {cvox.Cursor} startCursor The position to start searching for the next
* sentence. On exit, will point to the start of the returned string.
* @param {cvox.Cursor} endCursor On exit, the end of the returned string.
- * @param {Array.<Element>} elementsEntered Any HTML elements entered.
- * @param {Array.<Element>} elementsLeft Any HTML elements left.
- * @param {Object.<string, boolean>} breakTags Associative array of tags
+ * @param {Array<Element>} elementsEntered Any HTML elements entered.
+ * @param {Array<Element>} elementsLeft Any HTML elements left.
+ * @param {Object<string, boolean>} breakTags Associative array of tags
* that should break the sentence.
* @return {?string} The previous sentence, or null if the bottom of the
* document has been reached.
@@ -636,9 +636,9 @@ cvox.TraverseUtil.getPreviousSentence = function(
* @param {cvox.Cursor} startCursor On exit, marks the beginning of the line.
* @param {cvox.Cursor} endCursor The position to start searching for the next
* line. On exit, will point to the end of the returned string.
- * @param {Array.<Element>} elementsEntered Any HTML elements entered.
- * @param {Array.<Element>} elementsLeft Any HTML elements left.
- * @param {Object.<string, boolean>} breakTags Associative array of tags
+ * @param {Array<Element>} elementsEntered Any HTML elements entered.
+ * @param {Array<Element>} elementsLeft Any HTML elements left.
+ * @param {Object<string, boolean>} breakTags Associative array of tags
* that should break the line.
* @return {?string} The next line, or null if the bottom of the
* document has been reached.
@@ -682,9 +682,9 @@ cvox.TraverseUtil.getNextLine = function(
* @param {cvox.Cursor} startCursor The position to start searching for the next
* line. On exit, will point to the start of the returned string.
* @param {cvox.Cursor} endCursor On exit, the end of the returned string.
- * @param {Array.<Element>} elementsEntered Any HTML elements entered.
- * @param {Array.<Element>} elementsLeft Any HTML elements left.
- * @param {Object.<string, boolean>} breakTags Associative array of tags
+ * @param {Array<Element>} elementsEntered Any HTML elements entered.
+ * @param {Array<Element>} elementsLeft Any HTML elements left.
+ * @param {Object<string, boolean>} breakTags Associative array of tags
* that should break the line.
* @return {?string} The previous line, or null if the bottom of the
* document has been reached.
@@ -729,8 +729,8 @@ cvox.TraverseUtil.getPreviousLine = function(
* paragraph.
* @param {cvox.Cursor} endCursor The position to start searching for the next
* paragraph. On exit, will point to the end of the returned string.
- * @param {Array.<Element>} elementsEntered Any HTML elements entered.
- * @param {Array.<Element>} elementsLeft Any HTML elements left.
+ * @param {Array<Element>} elementsEntered Any HTML elements entered.
+ * @param {Array<Element>} elementsLeft Any HTML elements left.
* @return {?string} The next paragraph, or null if the bottom of the
* document has been reached.
*/
@@ -765,8 +765,8 @@ cvox.TraverseUtil.getNextParagraph = function(startCursor, endCursor,
* @param {cvox.Cursor} startCursor The position to start searching for the next
* paragraph. On exit, will point to the start of the returned string.
* @param {cvox.Cursor} endCursor On exit, the end of the returned string.
- * @param {Array.<Element>} elementsEntered Any HTML elements entered.
- * @param {Array.<Element>} elementsLeft Any HTML elements left.
+ * @param {Array<Element>} elementsEntered Any HTML elements entered.
+ * @param {Array<Element>} elementsLeft Any HTML elements left.
* @return {?string} The previous paragraph, or null if the bottom of the
* document has been reached.
*/
@@ -815,9 +815,9 @@ cvox.TraverseUtil.getPreviousParagraph = function(
* next string.
* @param {cvox.Cursor} endCursor The position to start searching for the next
* string. On exit, will point to the end of the returned string.
- * @param {Array.<Element>} elementsEntered Any HTML elements entered.
- * @param {Array.<Element>} elementsLeft Any HTML elements left.
- * @param {function(string, string, Array.<Element>, Array.<Element>)}
+ * @param {Array<Element>} elementsEntered Any HTML elements entered.
+ * @param {Array<Element>} elementsLeft Any HTML elements left.
+ * @param {function(string, string, Array<Element>, Array<Element>)}
* breakBefore Function that takes the string so far, next word to be
* added, and elements entered and left, and returns true if the string
* should be ended before adding this word.
@@ -877,9 +877,9 @@ cvox.TraverseUtil.getNextString = function(
* string returned.
* @param {cvox.Cursor} endCursor On exit, will point to the end of the
* string returned.
- * @param {Array.<Element>} elementsEntered Any HTML elements entered.
- * @param {Array.<Element>} elementsLeft Any HTML elements left.
- * @param {function(string, string, Array.<Element>, Array.<Element>)}
+ * @param {Array<Element>} elementsEntered Any HTML elements entered.
+ * @param {Array<Element>} elementsLeft Any HTML elements left.
+ * @param {function(string, string, Array<Element>, Array<Element>)}
* breakBefore Function that takes the string so far, the word to be
* added, and nodes crossed, and returns true if the string should be
* ended before adding this word.
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/automation_predicate.js b/chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/automation_predicate.js
index ba8e05519d9..9dedefa5856 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/automation_predicate.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/automation_predicate.js
@@ -10,6 +10,9 @@ goog.provide('AutomationPredicate');
goog.provide('AutomationPredicate.Binary');
goog.provide('AutomationPredicate.Unary');
+goog.scope(function() {
+var RoleType = chrome.automation.RoleType;
+
/**
* @constructor
*/
@@ -28,34 +31,110 @@ AutomationPredicate.Binary;
/**
* Constructs a predicate given a role.
- * @param {chrome.automation.RoleType} role
+ * @param {RoleType} role
* @return {AutomationPredicate.Unary}
*/
-AutomationPredicate.makeRolePredicate = function(role) {
+AutomationPredicate.withRole = function(role) {
return function(node) {
return node.role == role;
};
};
/** @type {AutomationPredicate.Unary} */
-AutomationPredicate.heading =
- AutomationPredicate.makeRolePredicate(
- chrome.automation.RoleType.heading);
+AutomationPredicate.checkBox = AutomationPredicate.withRole(RoleType.checkBox);
+/** @type {AutomationPredicate.Unary} */
+AutomationPredicate.comboBox = AutomationPredicate.withRole(RoleType.comboBox);
+/** @type {AutomationPredicate.Unary} */
+AutomationPredicate.editText = AutomationPredicate.withRole(RoleType.textField);
+/** @type {AutomationPredicate.Unary} */
+AutomationPredicate.heading = AutomationPredicate.withRole(RoleType.heading);
/** @type {AutomationPredicate.Unary} */
AutomationPredicate.inlineTextBox =
- AutomationPredicate.makeRolePredicate(
- chrome.automation.RoleType.inlineTextBox);
+ AutomationPredicate.withRole(RoleType.inlineTextBox);
+/** @type {AutomationPredicate.Unary} */
+AutomationPredicate.link = AutomationPredicate.withRole(RoleType.link);
/** @type {AutomationPredicate.Unary} */
-AutomationPredicate.link =
- AutomationPredicate.makeRolePredicate(
- chrome.automation.RoleType.link);
+AutomationPredicate.table = AutomationPredicate.withRole(RoleType.table);
+
+/**
+ * @param {chrome.automation.AutomationNode} node
+ * @return {boolean}
+ */
+AutomationPredicate.button = function(node) {
+ return /button/i.test(node.role);
+};
+
+/**
+ * @param {chrome.automation.AutomationNode} node
+ * @return {boolean}
+ */
+AutomationPredicate.formField = function(node) {
+ switch (node.role) {
+ case 'button':
+ case 'buttonDropDown':
+ case 'checkBox':
+ case 'comboBox':
+ case 'date':
+ case 'dateTime':
+ case 'details':
+ case 'disclosureTriangle':
+ case 'form':
+ case 'menuButton':
+ case 'menuListPopup':
+ case 'popUpButton':
+ case 'radioButton':
+ case 'searchBox':
+ case 'slider':
+ case 'spinButton':
+ case 'switch':
+ case 'tab':
+ case 'textField':
+ case 'time':
+ case 'toggleButton':
+ case 'tree':
+ return true;
+ }
+ return false;
+};
+
+/**
+ * @param {chrome.automation.AutomationNode} node
+ * @return {boolean}
+ */
+AutomationPredicate.landmark = function(node) {
+ switch (node.role) {
+ case 'application':
+ case 'banner':
+ case 'complementary':
+ case 'contentInfo':
+ case 'form':
+ case 'main':
+ case 'navigation':
+ case 'search':
+ return true;
+ }
+ return false;
+};
+
+/**
+ * @param {chrome.automation.AutomationNode} node
+ * @return {boolean}
+ */
+AutomationPredicate.visitedLink = function(node) {
+ return node.state.visited;
+};
/**
* @param {chrome.automation.AutomationNode} node
* @return {boolean}
*/
AutomationPredicate.leaf = function(node) {
- return !node.firstChild();
+ return !node.firstChild ||
+ node.role == RoleType.button ||
+ node.role == RoleType.slider ||
+ node.children.every(function(n) {
+ return n.state.invisible;
+ });
};
/**
@@ -79,3 +158,15 @@ AutomationPredicate.linebreak = function(first, second) {
return fl.top != sl.top ||
(fl.top + fl.height != sl.top + sl.height);
};
+
+/**
+ * Leaf nodes that should be ignored.
+ * @param {chrome.automation.AutomationNode} node
+ * @return {boolean}
+ */
+AutomationPredicate.shouldIgnoreLeaf = function(node) {
+ return AutomationPredicate.leaf(node) &&
+ node.role == RoleType.client;
+};
+
+}); // goog.scope
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/automation_util.js b/chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/automation_util.js
index 4486e195a7f..4275a1ccae1 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/automation_util.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/automation_util.js
@@ -45,13 +45,13 @@ AutomationUtil.findNodePre = function(cur, dir, pred) {
if (pred(cur))
return cur;
- var child = dir == Dir.BACKWARD ? cur.lastChild() : cur.firstChild();
+ var child = dir == Dir.BACKWARD ? cur.lastChild : cur.firstChild;
while (child) {
var ret = AutomationUtil.findNodePre(child, dir, pred);
if (ret)
return ret;
child = dir == Dir.BACKWARD ?
- child.previousSibling() : child.nextSibling();
+ child.previousSibling : child.nextSibling;
}
};
@@ -64,13 +64,13 @@ AutomationUtil.findNodePre = function(cur, dir, pred) {
* @return {AutomationNode}
*/
AutomationUtil.findNodePost = function(cur, dir, pred) {
- var child = dir == Dir.BACKWARD ? cur.lastChild() : cur.firstChild();
+ var child = dir == Dir.BACKWARD ? cur.lastChild : cur.firstChild;
while (child) {
var ret = AutomationUtil.findNodePost(child, dir, pred);
if (ret)
return ret;
child = dir == Dir.BACKWARD ?
- child.previousSibling() : child.nextSibling();
+ child.previousSibling : child.nextSibling;
}
if (pred(cur))
@@ -87,10 +87,14 @@ AutomationUtil.findNodePost = function(cur, dir, pred) {
AutomationUtil.findNextSubtree = function(cur, dir) {
while (cur) {
var next = dir == Dir.BACKWARD ?
- cur.previousSibling() : cur.nextSibling();
+ cur.previousSibling : cur.nextSibling;
+ if (!AutomationUtil.isInSameTree(cur, next))
+ return null;
if (next)
return next;
- cur = cur.parent();
+ if (!AutomationUtil.isInSameTree(cur, cur.parent))
+ return null;
+ cur = cur.parent;
}
};
@@ -109,6 +113,8 @@ AutomationUtil.findNextNode = function(cur, dir, pred) {
return null;
cur = next;
next = AutomationUtil.findNodePre(next, dir, pred);
+ if (next && AutomationPredicate.shouldIgnoreLeaf(next))
+ next = null;
} while (!next);
return next;
};
@@ -154,4 +160,93 @@ AutomationUtil.findNodeUntil = function(cur, dir, pred, opt_options) {
return opt_options.before ? before : after;
};
+/**
+ * Returns an array containing ancestors of node starting at root down to node.
+ * @param {!AutomationNode} node
+ * @return {!Array<AutomationNode>}
+ */
+AutomationUtil.getAncestors = function(node) {
+ var ret = [];
+ var candidate = node;
+ while (candidate) {
+ ret.push(candidate);
+
+ if (!AutomationUtil.isInSameTree(candidate, candidate.parent))
+ break;
+
+ candidate = candidate.parent;
+ }
+ return ret.reverse();
+};
+
+/**
+ * Gets the first index where the two input arrays differ. Returns -1 if they
+ * do not.
+ * @param {!Array<AutomationNode>} ancestorsA
+ * @param {!Array<AutomationNode>} ancestorsB
+ * @return {number}
+ */
+AutomationUtil.getDivergence = function(ancestorsA, ancestorsB) {
+ for (var i = 0; i < ancestorsA.length; i++) {
+ if (ancestorsA[i] !== ancestorsB[i])
+ return i;
+ }
+ if (ancestorsA.length == ancestorsB.length)
+ return -1;
+ return ancestorsA.length;
+};
+
+/**
+ * Returns ancestors of |node| that are not also ancestors of |prevNode|.
+ * @param {!AutomationNode} prevNode
+ * @param {!AutomationNode} node
+ * @return {!Array<AutomationNode>}
+ */
+AutomationUtil.getUniqueAncestors = function(prevNode, node) {
+ var prevAncestors = AutomationUtil.getAncestors(prevNode);
+ var ancestors = AutomationUtil.getAncestors(node);
+ var divergence = AutomationUtil.getDivergence(prevAncestors, ancestors);
+ return ancestors.slice(divergence);
+};
+
+/**
+ * Given |nodeA| and |nodeB| in that order, determines their ordering in the
+ * document.
+ * @param {!AutomationNode} nodeA
+ * @param {!AutomationNode} nodeB
+ * @return {AutomationUtil.Dir}
+ */
+AutomationUtil.getDirection = function(nodeA, nodeB) {
+ var ancestorsA = AutomationUtil.getAncestors(nodeA);
+ var ancestorsB = AutomationUtil.getAncestors(nodeB);
+ var divergence = AutomationUtil.getDivergence(ancestorsA, ancestorsB);
+
+ // Default to Dir.FORWARD.
+ if (divergence == -1)
+ return Dir.FORWARD;
+
+ var divA = ancestorsA[divergence];
+ var divB = ancestorsB[divergence];
+
+ // One of the nodes is an ancestor of the other. Don't distinguish and just
+ // consider it Dir.FORWARD.
+ if (!divA || !divB || divA.parent === nodeB || divB.parent === nodeA)
+ return Dir.FORWARD;
+
+ return divA.indexInParent <= divB.indexInParent ? Dir.FORWARD : Dir.BACKWARD;
+};
+
+/**
+ * Determines whether the two given nodes come from the same tree source.
+ * @param {AutomationNode} a
+ * @param {AutomationNode} b
+ * @return {boolean}
+ */
+AutomationUtil.isInSameTree = function(a, b) {
+ if (!a || !b)
+ return true;
+
+ return a.root === b.root;
+};
+
}); // goog.scope
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/automation_util_test.extjs b/chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/automation_util_test.extjs
new file mode 100644
index 00000000000..2c75b9fc26b
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/automation_util_test.extjs
@@ -0,0 +1,102 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Include test fixture.
+GEN_INCLUDE(['../../testing/chromevox_next_e2e_test_base.js']);
+
+/**
+ * Test fixture for automation_util.js.
+ * @constructor
+ * @extends {ChromeVoxE2ETestBase}
+ */
+function AutomationUtilE2ETest() {
+ ChromeVoxNextE2ETest.call(this);
+}
+
+AutomationUtilE2ETest.prototype = {
+ __proto__: ChromeVoxNextE2ETest.prototype,
+
+ /** @override */
+ setUp: function() {
+ window.Dir = AutomationUtil.Dir;
+ },
+
+ basicDoc: function() {/*!
+ <p><a href='#'></a>hello</p>
+ <h1><ul><li>a</ul><button></h1>
+ */}
+};
+
+TEST_F('AutomationUtilE2ETest', 'GetAncestors', function() {
+ this.runWithLoadedTree(this.basicDoc, function(root) {
+ var expectedLength = 1;
+ while (root) {
+ var ancestors = AutomationUtil.getAncestors(root);
+ assertEquals(expectedLength++, ancestors.length);
+ root = root.firstChild;
+ }
+ });
+});
+
+TEST_F('AutomationUtilE2ETest', 'GetUniqueAncestors', function() {
+ this.runWithLoadedTree(this.basicDoc, function(root) {
+ var leftmost = root, rightmost = root;
+ while (leftmost.firstChild)
+ leftmost = leftmost.firstChild;
+ while (rightmost.lastChild)
+ rightmost = rightmost.lastChild;
+
+ var leftAncestors = AutomationUtil.getAncestors(leftmost);
+ var rightAncestors = AutomationUtil.getAncestors(rightmost);
+ assertEquals(chrome.automation.RoleType.link, leftmost.role);
+ assertEquals(chrome.automation.RoleType.button, rightmost.role);
+ assertEquals(
+ 1, AutomationUtil.getDivergence(leftAncestors, rightAncestors));
+ assertEquals(
+ -1, AutomationUtil.getDivergence(leftAncestors, leftAncestors));
+
+ var uniqueAncestorsLeft =
+ AutomationUtil.getUniqueAncestors(rightmost, leftmost);
+ var uniqueAncestorsRight =
+ AutomationUtil.getUniqueAncestors(leftmost, rightmost);
+
+ assertEquals(2, uniqueAncestorsLeft.length);
+ assertEquals(chrome.automation.RoleType.paragraph,
+ uniqueAncestorsLeft[0].role);
+ assertEquals(chrome.automation.RoleType.link,
+ uniqueAncestorsLeft[1].role);
+
+ assertEquals(3, uniqueAncestorsRight.length);
+ assertEquals(chrome.automation.RoleType.heading,
+ uniqueAncestorsRight[0].role);
+ assertEquals(chrome.automation.RoleType.group,
+ uniqueAncestorsRight[1].role);
+ assertEquals(chrome.automation.RoleType.button,
+ uniqueAncestorsRight[2].role);
+
+ assertEquals(
+ 1, AutomationUtil.getUniqueAncestors(leftmost, leftmost).length);
+
+ });
+});
+
+TEST_F('AutomationUtilE2ETest', 'GetDirection', function() {
+ this.runWithLoadedTree(this.basicDoc, function(root) {
+ var left = root, right = root;
+
+ // Same node.
+ assertEquals(Dir.FORWARD, AutomationUtil.getDirection(left, right));
+
+ // Ancestor.
+ left = left.firstChild;
+ assertEquals(Dir.FORWARD, AutomationUtil.getDirection(left, right));
+ assertEquals(Dir.FORWARD, AutomationUtil.getDirection(right, left));
+
+ // Ordered.
+ right = right.lastChild;
+ assertEquals(Dir.BACKWARD, AutomationUtil.getDirection(right, left));
+ assertEquals(Dir.FORWARD, AutomationUtil.getDirection(left, right));
+
+ });
+});
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/background.js b/chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/background.js
index 5bb97e200e8..586fd374f1b 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/background.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/background.js
@@ -12,18 +12,27 @@ goog.provide('global');
goog.require('AutomationPredicate');
goog.require('AutomationUtil');
+goog.require('ClassicCompatibility');
goog.require('Output');
+goog.require('Output.EventType');
goog.require('cursors.Cursor');
-goog.require('cvox.TabsApiHandler');
+goog.require('cvox.ChromeVoxEditableTextBase');
goog.scope(function() {
var AutomationNode = chrome.automation.AutomationNode;
var Dir = AutomationUtil.Dir;
var EventType = chrome.automation.EventType;
-/** Classic Chrome accessibility API. */
-global.accessibility =
- chrome.accessibilityPrivate || chrome.experimental.accessibility;
+/**
+ * All possible modes ChromeVox can run.
+ * @enum {string}
+ */
+var ChromeVoxMode = {
+ CLASSIC: 'classic',
+ COMPAT: 'compat',
+ NEXT: 'next',
+ FORCE_NEXT: 'force_next'
+};
/**
* ChromeVox2 background page.
@@ -33,18 +42,10 @@ Background = function() {
/**
* A list of site substring patterns to use with ChromeVox next. Keep these
* strings relatively specific.
- * @type {!Array.<string>}
- * @private
- */
- this.whitelist_ = ['http://www.chromevox.com/', 'chromevox_next_test'];
-
- /**
- * @type {cvox.TabsApiHandler}
+ * @type {!Array<string>}
* @private
*/
- this.tabsHandler_ = new cvox.TabsApiHandler(cvox.ChromeVox.tts,
- cvox.ChromeVox.braille,
- cvox.ChromeVox.earcons);
+ this.whitelist_ = ['chromevox_next_test'];
/**
* @type {cursors.Range}
@@ -53,20 +54,14 @@ Background = function() {
this.currentRange_ = null;
/**
- * Whether ChromeVox Next is active.
- * @type {boolean}
- * @private
- */
- this.active_ = false;
-
- /**
- * @type {!Output}
+ * Which variant of ChromeVox is active.
+ * @type {ChromeVoxMode}
* @private
*/
- this.output_ = new Output();
+ this.mode_ = ChromeVoxMode.CLASSIC;
- // Only needed with unmerged ChromeVox classic loaded before.
- global.accessibility.setAccessibilityEnabled(false);
+ /** @type {!ClassicCompatibility} @private */
+ this.compat_ = new ClassicCompatibility(this.mode_ === ChromeVoxMode.COMPAT);
// Manually bind all functions to |this|.
for (var func in this) {
@@ -76,81 +71,79 @@ Background = function() {
/**
* Maps an automation event to its listener.
- * @type {!Object.<EventType, function(Object) : void>}
+ * @type {!Object<EventType, function(Object) : void>}
*/
this.listeners_ = {
- focus: this.onFocus,
- loadComplete: this.onLoadComplete
+ alert: this.onEventDefault,
+ focus: this.onEventDefault,
+ hover: this.onEventDefault,
+ loadComplete: this.onLoadComplete,
+ menuStart: this.onEventDefault,
+ menuEnd: this.onEventDefault,
+ menuListValueChanged: this.onEventDefault,
+ textChanged: this.onTextOrTextSelectionChanged,
+ textSelectionChanged: this.onTextOrTextSelectionChanged,
+ valueChanged: this.onValueChanged
};
// Register listeners for ...
// Desktop.
- chrome.automation.getDesktop(this.onGotTree);
-
- // Tabs.
- chrome.tabs.onUpdated.addListener(this.onTabUpdated);
-
- // Commands.
- chrome.commands.onCommand.addListener(this.onGotCommand);
+ chrome.automation.getDesktop(this.onGotDesktop);
};
Background.prototype = {
- /**
- * Handles chrome.tabs.onUpdated.
- * @param {number} tabId
- * @param {Object} changeInfo
- */
- onTabUpdated: function(tabId, changeInfo) {
- if (changeInfo.status != 'complete')
- return;
- chrome.tabs.get(tabId, function(tab) {
- if (!tab.url)
- return;
-
- var next = this.isWhitelisted_(tab.url);
- this.toggleChromeVoxVersion({next: next, classic: !next});
- }.bind(this));
+ /** Forces ChromeVox Next to be active for all tabs. */
+ forceChromeVoxNextActive: function() {
+ this.setChromeVoxMode(ChromeVoxMode.FORCE_NEXT);
},
/**
* Handles all setup once a new automation tree appears.
- * @param {chrome.automation.AutomationNode} root
+ * @param {chrome.automation.AutomationNode} desktop
*/
- onGotTree: function(root) {
+ onGotDesktop: function(desktop) {
// Register all automation event listeners.
for (var eventType in this.listeners_)
- root.addEventListener(eventType, this.listeners_[eventType], true);
-
- if (root.attributes.docLoaded) {
- this.onLoadComplete({target: root});
+ desktop.addEventListener(eventType, this.listeners_[eventType], true);
+
+ // Register a tree change observer.
+ chrome.automation.addTreeChangeObserver(this.onTreeChange);
+
+ // The focused state gets set on the containing webView node.
+ var webView = desktop.find({role: chrome.automation.RoleType.webView,
+ state: {focused: true}});
+ if (webView) {
+ var root = webView.find({role: chrome.automation.RoleType.rootWebArea});
+ if (root) {
+ this.onLoadComplete(
+ {target: root,
+ type: chrome.automation.EventType.loadComplete});
+ }
}
},
/**
* Handles chrome.commands.onCommand.
* @param {string} command
+ * @param {boolean=} opt_skipCompat Whether to skip compatibility checks.
*/
- onGotCommand: function(command) {
- if (command == 'toggleChromeVoxVersion') {
- this.toggleChromeVoxVersion();
+ onGotCommand: function(command, opt_skipCompat) {
+ if (!this.currentRange_)
return;
+
+ if (!opt_skipCompat) {
+ if (this.compat_.onGotCommand(command))
+ return;
}
- if (!this.active_ || !this.currentRange_)
+ if (this.mode_ === ChromeVoxMode.CLASSIC)
return;
var current = this.currentRange_;
var dir = Dir.FORWARD;
var pred = null;
+ var predErrorMsg = undefined;
switch (command) {
- case 'nextHeading':
- dir = Dir.FORWARD;
- pred = AutomationPredicate.heading;
- break;
- case 'previousHeading':
- dir = Dir.BACKWARD;
- pred = AutomationPredicate.heading;
- break;
case 'nextCharacter':
current = current.move(cursors.Unit.CHARACTER, Dir.FORWARD);
break;
@@ -169,13 +162,95 @@ Background.prototype = {
case 'previousLine':
current = current.move(cursors.Unit.LINE, Dir.BACKWARD);
break;
+ case 'nextButton':
+ dir = Dir.FORWARD;
+ pred = AutomationPredicate.button;
+ predErrorMsg = 'no_next_button';
+ break;
+ case 'previousButton':
+ dir = Dir.BACKWARD;
+ pred = AutomationPredicate.button;
+ predErrorMsg = 'no_previous_button';
+ break;
+ case 'nextCheckBox':
+ dir = Dir.FORWARD;
+ pred = AutomationPredicate.checkBox;
+ predErrorMsg = 'no_next_checkbox';
+ break;
+ case 'previousCheckBox':
+ dir = Dir.BACKWARD;
+ pred = AutomationPredicate.checkBox;
+ predErrorMsg = 'no_previous_checkbox';
+ break;
+ case 'nextComboBox':
+ dir = Dir.FORWARD;
+ pred = AutomationPredicate.comboBox;
+ predErrorMsg = 'no_next_combo_box';
+ break;
+ case 'previousComboBox':
+ dir = Dir.BACKWARD;
+ pred = AutomationPredicate.comboBox;
+ predErrorMsg = 'no_previous_combo_box';
+ break;
+ case 'nextEditText':
+ dir = Dir.FORWARD;
+ pred = AutomationPredicate.editText;
+ predErrorMsg = 'no_next_edit_text';
+ break;
+ case 'previousEditText':
+ dir = Dir.BACKWARD;
+ pred = AutomationPredicate.editText;
+ predErrorMsg = 'no_previous_edit_text';
+ break;
+ case 'nextFormField':
+ dir = Dir.FORWARD;
+ pred = AutomationPredicate.formField;
+ predErrorMsg = 'no_next_form_field';
+ break;
+ case 'previousFormField':
+ dir = Dir.BACKWARD;
+ pred = AutomationPredicate.formField;
+ predErrorMsg = 'no_previous_form_field';
+ break;
+ case 'nextHeading':
+ dir = Dir.FORWARD;
+ pred = AutomationPredicate.heading;
+ predErrorMsg = 'no_next_heading';
+ break;
+ case 'previousHeading':
+ dir = Dir.BACKWARD;
+ pred = AutomationPredicate.heading;
+ predErrorMsg = 'no_previous_heading';
+ break;
case 'nextLink':
dir = Dir.FORWARD;
pred = AutomationPredicate.link;
+ predErrorMsg = 'no_next_link';
break;
case 'previousLink':
dir = Dir.BACKWARD;
pred = AutomationPredicate.link;
+ predErrorMsg = 'no_previous_link';
+ break;
+ case 'nextTable':
+ dir = Dir.FORWARD;
+ pred = AutomationPredicate.table;
+ predErrorMsg = 'no_next_table';
+ break;
+ case 'previousTable':
+ dir = Dir.BACKWARD;
+ pred = AutomationPredicate.table;
+ predErrorMsg = 'no_previous_table';
+ break;
+ case 'nextVisitedLink':
+ dir = Dir.FORWARD;
+ pred = AutomationPredicate.visitedLink;
+ predErrorMsg = 'no_next_visited_link';
+ break;
+ case 'previousVisitedLink':
+ dir = Dir.BACKWARD;
+ pred = AutomationPredicate.visitedLink;
+ predErrorMsg = 'no_previous_visited_link';
break;
case 'nextElement':
current = current.move(cursors.Unit.NODE, Dir.FORWARD);
@@ -184,36 +259,73 @@ Background.prototype = {
current = current.move(cursors.Unit.NODE, Dir.BACKWARD);
break;
case 'goToBeginning':
- var node = AutomationUtil.findNodePost(current.getStart().getNode().root,
- Dir.FORWARD,
- AutomationPredicate.leaf);
- if (node)
- current = cursors.Range.fromNode(node);
- break;
- case 'goToEnd':
var node =
AutomationUtil.findNodePost(current.getStart().getNode().root,
- Dir.BACKWARD,
- AutomationPredicate.leaf);
- if (node)
- current = cursors.Range.fromNode(node);
+ Dir.FORWARD,
+ AutomationPredicate.leaf);
+ if (node)
+ current = cursors.Range.fromNode(node);
+ break;
+ case 'goToEnd':
+ var node =
+ AutomationUtil.findNodePost(current.getStart().getNode().root,
+ Dir.BACKWARD,
+ AutomationPredicate.leaf);
+ if (node)
+ current = cursors.Range.fromNode(node);
break;
+ case 'doDefault':
+ if (this.currentRange_) {
+ var actionNode = this.currentRange_.getStart().getNode();
+ if (actionNode.role == chrome.automation.RoleType.inlineTextBox)
+ actionNode = actionNode.parent;
+ actionNode.doDefault();
+ }
+ break;
+ case 'continuousRead':
+ global.isReadingContinuously = true;
+ var continueReading = function(prevRange) {
+ if (!global.isReadingContinuously || !this.currentRange_)
+ return;
+
+ new Output().withSpeechAndBraille(
+ this.currentRange_, prevRange, Output.EventType.NAVIGATE)
+ .onSpeechEnd(function() { continueReading(prevRange); })
+ .go();
+ prevRange = this.currentRange_;
+ this.currentRange_ =
+ this.currentRange_.move(cursors.Unit.NODE, Dir.FORWARD);
+
+ if (!this.currentRange_ || this.currentRange_.equals(prevRange))
+ global.isReadingContinuously = false;
+ }.bind(this);
+
+ continueReading(null);
+ return;
}
if (pred) {
var node = AutomationUtil.findNextNode(
current.getBound(dir).getNode(), dir, pred);
- if (node)
+ if (node) {
current = cursors.Range.fromNode(node);
+ } else {
+ cvox.ChromeVox.tts.speak(cvox.ChromeVox.msgs.getMsg(predErrorMsg),
+ cvox.QueueMode.FLUSH);
+ return;
+ }
}
if (current) {
// TODO(dtseng): Figure out what it means to focus a range.
current.getStart().getNode().focus();
+ var prevRange = this.currentRange_;
this.currentRange_ = current;
- this.output_.output(this.currentRange_);
+ new Output().withSpeechAndBraille(
+ this.currentRange_, prevRange, Output.EventType.NAVIGATE)
+ .go();
}
},
@@ -221,13 +333,32 @@ Background.prototype = {
* Provides all feedback once ChromeVox's focus changes.
* @param {Object} evt
*/
- onFocus: function(evt) {
+ onEventDefault: function(evt) {
var node = evt.target;
+
if (!node)
return;
+ var prevRange = this.currentRange_;
+
this.currentRange_ = cursors.Range.fromNode(node);
- this.output_.output(this.currentRange_);
+
+ // Check to see if we've crossed roots. Only care about focused roots.
+ if (!prevRange ||
+ (prevRange.getStart().getNode().root != node.root &&
+ node.root.focused))
+ this.setupChromeVoxVariants_(node.root.attributes.url || '');
+
+ // Don't process nodes inside of web content if ChromeVox Next is inactive.
+ if (node.root.role != chrome.automation.RoleType.desktop &&
+ this.mode_ === ChromeVoxMode.CLASSIC) {
+ chrome.accessibilityPrivate.setFocusRing([]);
+ return;
+ }
+
+ new Output().withSpeechAndBraille(
+ this.currentRange_, prevRange, evt.type)
+ .go();
},
/**
@@ -235,17 +366,144 @@ Background.prototype = {
* @param {Object} evt
*/
onLoadComplete: function(evt) {
+ this.setupChromeVoxVariants_(evt.target.attributes.url);
+
+ // Don't process nodes inside of web content if ChromeVox Next is inactive.
+ if (evt.target.root.role != chrome.automation.RoleType.desktop &&
+ this.mode_ === ChromeVoxMode.CLASSIC)
+ return;
+
if (this.currentRange_)
return;
- var node = AutomationUtil.findNodePost(evt.target,
+ var root = evt.target;
+ var webView = root;
+ while (webView && webView.role != chrome.automation.RoleType.webView)
+ webView = webView.parent;
+
+ if (!webView || !webView.state.focused)
+ return;
+
+ var node = AutomationUtil.findNodePost(root,
Dir.FORWARD,
AutomationPredicate.leaf);
+
if (node)
this.currentRange_ = cursors.Range.fromNode(node);
if (this.currentRange_)
- this.output_.output(this.currentRange_);
+ new Output().withSpeechAndBraille(
+ this.currentRange_, null, evt.type)
+ .go();
+ },
+
+ /**
+ * Provides all feedback once a text selection change event fires.
+ * @param {Object} evt
+ */
+ onTextOrTextSelectionChanged: function(evt) {
+ // Don't process nodes inside of web content if ChromeVox Next is inactive.
+ if (evt.target.root.role != chrome.automation.RoleType.desktop &&
+ this.mode_ === ChromeVoxMode.CLASSIC)
+ return;
+
+ if (!evt.target.state.focused)
+ return;
+
+ if (!this.currentRange_) {
+ this.onEventDefault(evt);
+ this.currentRange_ = cursors.Range.fromNode(evt.target);
+ }
+
+ var textChangeEvent = new cvox.TextChangeEvent(
+ evt.target.attributes.value,
+ evt.target.attributes.textSelStart,
+ evt.target.attributes.textSelEnd,
+ true); // triggered by user
+ if (!this.editableTextHandler ||
+ evt.target != this.currentRange_.getStart().getNode()) {
+ this.editableTextHandler =
+ new cvox.ChromeVoxEditableTextBase(
+ textChangeEvent.value,
+ textChangeEvent.start,
+ textChangeEvent.end,
+ evt.target.state['protected'],
+ cvox.ChromeVox.tts);
+ }
+
+ this.editableTextHandler.changed(textChangeEvent);
+ new Output().withBraille(
+ this.currentRange_, null, evt.type)
+ .go();
+ },
+
+ /**
+ * Provides all feedback once a value changed event fires.
+ * @param {Object} evt
+ */
+ onValueChanged: function(evt) {
+ // Don't process nodes inside of web content if ChromeVox Next is inactive.
+ if (evt.target.root.role != chrome.automation.RoleType.desktop &&
+ this.mode_ === ChromeVoxMode.CLASSIC)
+ return;
+
+ if (!evt.target.state.focused)
+ return;
+
+ // Value change events fire on web text fields and text areas when pressing
+ // enter; suppress them.
+ if (!this.currentRange_ ||
+ evt.target.role != chrome.automation.RoleType.textField) {
+ this.onEventDefault(evt);
+ this.currentRange_ = cursors.Range.fromNode(evt.target);
+ }
+ },
+
+ /**
+ * Called when the automation tree is changed.
+ * @param {chrome.automation.TreeChange} treeChange
+ */
+ onTreeChange: function(treeChange) {
+ var node = treeChange.target;
+ if (!node.containerLiveStatus)
+ return;
+
+ if (node.containerLiveRelevant.indexOf('additions') >= 0 &&
+ treeChange.type == 'nodeCreated')
+ this.outputLiveRegionChange_(node, null);
+ if (node.containerLiveRelevant.indexOf('text') >= 0 &&
+ treeChange.type == 'nodeChanged')
+ this.outputLiveRegionChange_(node, null);
+ if (node.containerLiveRelevant.indexOf('removals') >= 0 &&
+ treeChange.type == 'nodeRemoved')
+ this.outputLiveRegionChange_(node, '@live_regions_removed');
+ },
+
+ /**
+ * Given a node that needs to be spoken as part of a live region
+ * change and an additional optional format string, output the
+ * live region description.
+ * @param {!chrome.automation.AutomationNode} node The changed node.
+ * @param {?string} opt_prependFormatStr If set, a format string for
+ * cvox2.Output to prepend to the output.
+ * @private
+ **/
+ outputLiveRegionChange_: function(node, opt_prependFormatStr) {
+ var range = cursors.Range.fromNode(node);
+ var output = new Output();
+ if (opt_prependFormatStr) {
+ output.format(opt_prependFormatStr);
+ }
+ output.withSpeech(range, null, Output.EventType.NAVIGATE);
+ output.go();
+ },
+
+ /**
+ * @return {boolean}
+ * @private
+ */
+ isWhitelistedForCompat_: function(url) {
+ return url.indexOf('chrome://md-settings') != -1;
},
/**
@@ -253,13 +511,34 @@ Background.prototype = {
* @param {string} url
* @return {boolean} Whether the given |url| is whitelisted.
*/
- isWhitelisted_: function(url) {
+ isWhitelistedForNext_: function(url) {
return this.whitelist_.some(function(item) {
return url.indexOf(item) != -1;
}.bind(this));
},
/**
+ * Setup ChromeVox variants.
+ * @param {string} url
+ * @private
+ */
+ setupChromeVoxVariants_: function(url) {
+ if (this.mode_ === ChromeVoxMode.FORCE_NEXT)
+ return;
+
+ this.compat_.active = this.isWhitelistedForCompat_(url);
+ var mode = this.mode_;
+ if (this.compat_.active)
+ mode = ChromeVoxMode.COMPAT;
+ else if (this.isWhitelistedForNext_(url))
+ mode = ChromeVoxMode.NEXT;
+ else
+ mode = ChromeVoxMode.CLASSIC;
+
+ this.setChromeVoxMode(mode);
+ },
+
+ /**
* Disables classic ChromeVox.
* @param {number} tabId The tab where ChromeVox classic is running in.
*/
@@ -271,75 +550,38 @@ Background.prototype = {
},
/**
- * Toggles between ChromeVox Next and Classic.
- * @param {{classic: boolean, next: boolean}=} opt_options Forceably set.
- */
- toggleChromeVoxVersion: function(opt_options) {
- if (!opt_options) {
- opt_options = {};
- opt_options.next = !this.active_;
- opt_options.classic = !opt_options.next;
- }
+ * Sets the current ChromeVox mode.
+ * @param {ChromeVoxMode} mode
+ */
+ setChromeVoxMode: function(mode) {
+ if (mode === this.mode_)
+ return;
- if (opt_options.next) {
- chrome.automation.getTree(this.onGotTree);
- this.active_ = true;
+ if (mode === ChromeVoxMode.NEXT ||
+ mode === ChromeVoxMode.COMPAT ||
+ mode === ChromeVoxMode.FORCE_NEXT) {
+ if (!chrome.commands.onCommand.hasListener(this.onGotCommand))
+ chrome.commands.onCommand.addListener(this.onGotCommand);
} else {
- if (this.active_) {
- for (var eventType in this.listeners_) {
- this.currentRange_.getStart().getNode().root.removeEventListener(
- eventType, this.listeners_[eventType], true);
- }
- }
- this.active_ = false;
+ if (chrome.commands.onCommand.hasListener(this.onGotCommand))
+ chrome.commands.onCommand.removeListener(this.onGotCommand);
}
chrome.tabs.query({active: true}, function(tabs) {
- if (opt_options.classic) {
- cvox.ChromeVox.injectChromeVoxIntoTabs(tabs);
+ if (mode === ChromeVoxMode.CLASSIC) {
+ // This case should do nothing because Classic gets injected by the
+ // extension system via our manifest. Once ChromeVox Next is enabled
+ // for tabs, re-enable.
+ // cvox.ChromeVox.injectChromeVoxIntoTabs(tabs);
} else {
tabs.forEach(function(tab) {
this.disableClassicChromeVox_(tab.id);
}.bind(this));
}
}.bind(this));
- },
-
- /**
- * Handles output of a Range.
- * @param {!cursors.Range} range Current location.
- */
- handleOutput: function(range) {
- // TODO(dtseng): This is just placeholder logic for generating descriptions
- // pending further design discussion.
- function getCursorDesc(cursor) {
- var node = cursor.getNode();
- var container = node;
- while (container &&
- (container.role == chrome.automation.RoleType.inlineTextBox ||
- container.role == chrome.automation.RoleType.staticText))
- container = container.parent();
-
- var role = container ? container.role : node.role;
- return [node.attributes.name, node.attributes.value, role].join(', ');
- }
-
- // Walk the range and collect descriptions.
- var output = '';
- var cursor = range.getStart();
- var nodeLocations = [];
- while (cursor.getNode() != range.getEnd().getNode()) {
- output += getCursorDesc(cursor);
- nodeLocations.push(cursor.getNode().location);
- cursor = cursor.move(
- cursors.Unit.NODE, cursors.Movement.DIRECTIONAL, Dir.FORWARD);
- }
- output += getCursorDesc(range.getEnd());
- nodeLocations.push(range.getEnd().getNode().location);
- cvox.ChromeVox.tts.speak(output, cvox.QueueMode.FLUSH);
- cvox.ChromeVox.braille.write(cvox.NavBraille.fromText(output));
- chrome.accessibilityPrivate.setFocusRing(nodeLocations);
+ this.compat_.active = mode === ChromeVoxMode.COMPAT;
+ this.mode_ = mode;
}
};
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/background_test.extjs b/chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/background_test.extjs
index 44f728cb158..1afd0a233e1 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/background_test.extjs
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/background_test.extjs
@@ -12,7 +12,9 @@ GEN_INCLUDE(['../../testing/mock_tts.js']);
* @constructor
* @extends {ChromeVoxNextE2ETest}
*/
-function BackgroundTest() {}
+function BackgroundTest() {
+ ChromeVoxNextE2ETest.call(this);
+}
BackgroundTest.prototype = {
__proto__: ChromeVoxNextE2ETest.prototype,
@@ -45,6 +47,14 @@ BackgroundTest.prototype = {
<a href='#bar'>echo</a>
<h2>foxtraut</h2>
<p>end<span>of test</span></p>
+ */},
+
+ formsDoc: function() {/*!
+ <select id="fruitSelect">
+ <option>apple</option>
+ <option>grape</option>
+ <option> banana</option>
+ </select>
*/}
};
@@ -59,103 +69,138 @@ SYNC_TEST_F('BackgroundTest', 'NextNamespaces', function() {
assertEquals('function', typeof(Background));
});
-/** Tests that ChromeVox reads the desktop tree. */
-TEST_F('BackgroundTest', 'DesktopFocus', function() {
- function findStatusTray(root) {
- if (root.role == chrome.automation.RoleType.button &&
- root.attributes.name &&
- root.attributes.name.indexOf('Status tray') != -1) {
- return root;
- }
- for (var i = 0; i < root.children().length; i++) {
- var found = findStatusTray(root.children()[i]);
- if (found)
- return found;
- }
- return null;
- }
-
- chrome.automation.getDesktop(function(root) {
- var testButton = findStatusTray(root);
- cvox.ChromeVox.tts.expectSpeech('Status tray', testDone);
- testButton.focus();
- });
-});
-
/** Tests feedback once a page loads. */
-TEST_F('BackgroundTest', 'InitialFeedback', function() {
- this.runWithDocument(function() {/*!
+TEST_F('BackgroundTest', 'MANUAL_InitialFeedback', function() {
+ cvox.ChromeVox.tts.expectSpeech('start', this.newCallback());
+
+ this.runWithTab(function() {/*!
<p>start
<p>end
- */},
- function() {
- cvox.ChromeVox.tts.expectSpeech('start', function() {
- global.backgroundObj.onGotCommand('nextLine');
- });
- cvox.ChromeVox.tts.expectSpeech('end', testDone);
- }.bind(this));
+ */});
});
/** Tests consistency of navigating forward and backward. */
-TEST_F('BackgroundTest', 'ForwardBackwardNavigation', function() {
- this.runWithDocument(this.linksAndHeadingsDoc, function() {
- var doCmd = this.doCmd.bind(this);
- var expectAfter =
- cvox.ChromeVox.tts.expectSpeechAfter.bind(cvox.ChromeVox.tts);
-
- cvox.ChromeVox.tts.expectSpeech('start');
- expectAfter('alpha', doCmd('nextLink'));
- expectAfter('beta', doCmd('nextLink'));
- expectAfter('delta', doCmd('nextLink'));
- expectAfter('beta', doCmd('previousLink'));
-
- expectAfter('charlie', doCmd('nextHeading'));
- expectAfter('foxtraut', doCmd('nextHeading'));
- expectAfter('charlie', doCmd('previousHeading'));
-
- expectAfter('delta', doCmd('nextElement'));
- expectAfter('echo', doCmd('nextElement'));
- expectAfter('foxtraut', doCmd('nextElement'));
- expectAfter('end', doCmd('nextElement'));
- expectAfter('foxtraut', doCmd('previousElement'));
-
- // TODO(dtseng): cleanup these utterances.
- expectAfter(', end, paragraph, of test, paragraph', doCmd('nextLine'));
-
- expectAfter('start', doCmd('goToBeginning'));
- expectAfter('of test', doCmd('goToEnd'));
+TEST_F('BackgroundTest', 'MANUAL_ForwardBackwardNavigation', function() {
+ this.runWithLoadedTree(this.linksAndHeadingsDoc, function() {
+ var doCmd = this.doCmd.bind(this);
+ var expectAfter =
+ cvox.ChromeVox.tts.expectSpeechAfter.bind(cvox.ChromeVox.tts);
+
+ expectAfter('alpha', doCmd('nextLink'));
+ expectAfter('beta', doCmd('nextLink'));
+ expectAfter('delta', doCmd('nextLink'));
+ expectAfter('beta', doCmd('previousLink'));
+
+ expectAfter('charlie', doCmd('nextHeading'));
+ expectAfter('foxtraut', doCmd('nextHeading'));
+ expectAfter('charlie', doCmd('previousHeading'));
+
+ expectAfter('delta', doCmd('nextElement'));
+ expectAfter('echo', doCmd('nextElement'));
+ expectAfter('foxtraut', doCmd('nextElement'));
+ expectAfter('end', doCmd('nextElement'));
+ expectAfter('foxtraut', doCmd('previousElement'));
+ expectAfter('end of test', doCmd('nextLine'));
+
+ expectAfter('start', doCmd('goToBeginning'));
+ expectAfter('of test', doCmd('goToEnd'));
+
+ cvox.ChromeVox.tts.finishExpectations(this.newCallback());
+ });
+});
- cvox.ChromeVox.tts.finishExpectations();
- }.bind(this)
- );
+TEST_F('BackgroundTest', 'MANUAL_CaretNavigation', function() {
+ this.runWithLoadedTree(this.linksAndHeadingsDoc, function() {
+ var doCmd = this.doCmd.bind(this);
+ var expectAfter =
+ cvox.ChromeVox.tts.expectSpeechAfter.bind(cvox.ChromeVox.tts);
+
+ expectAfter('t', doCmd('nextCharacter'), true);
+ expectAfter('a', doCmd('nextCharacter'), true);
+ expectAfter('Link alpha', doCmd('nextWord'), true);
+ expectAfter('Link beta', doCmd('nextWord'), true);
+ expectAfter('Heading charlie', doCmd('nextWord'), true);
+ expectAfter('Link delta', doCmd('nextLine'), true);
+ expectAfter('Link echo', doCmd('nextLine'), true);
+ expectAfter('Heading foxtraut', doCmd('nextLine'), true);
+ expectAfter(
+ 'end of test', doCmd('nextLine'), true);
+ expectAfter('n', doCmd('nextCharacter'), true);
+ expectAfter('e', doCmd('previousCharacter'), true);
+ expectAfter('Heading t', doCmd('previousCharacter'), true);
+ expectAfter('foxtraut', doCmd('previousWord'), true);
+ expectAfter('Link echo', doCmd('previousWord'), true);
+ expectAfter('Link a', doCmd('previousCharacter'), true);
+ expectAfter('t', doCmd('previousCharacter'), true);
+ expectAfter('Link echo', doCmd('nextWord'), true);
+
+ cvox.ChromeVox.tts.finishExpectations(this.newCallback());
+ });
});
-TEST_F('BackgroundTest', 'CaretNavigation', function() {
- this.runWithDocument(this.linksAndHeadingsDoc, function() {
- var doCmd = this.doCmd.bind(this);
- var expectAfter =
- cvox.ChromeVox.tts.expectSpeechAfter.bind(cvox.ChromeVox.tts);
-
- cvox.ChromeVox.tts.expectSpeech('start');
- expectAfter('t', doCmd('nextCharacter'), true);
- expectAfter('a', doCmd('nextCharacter'), true);
- expectAfter('alpha', doCmd('nextWord'), true);
- expectAfter('beta', doCmd('nextWord'), true);
- expectAfter('charlie', doCmd('nextWord'), true);
- expectAfter('delta', doCmd('nextLine'), true);
- expectAfter('echo', doCmd('nextLine'), true);
- expectAfter('foxtraut', doCmd('nextLine'), true);
- expectAfter(
- ', end, paragraph, of test, paragraph', doCmd('nextLine'), true);
- expectAfter('n', doCmd('nextCharacter'), true);
- expectAfter('e', doCmd('previousCharacter'), true);
- expectAfter('t', doCmd('previousCharacter'), true);
- expectAfter('foxtraut', doCmd('previousWord'), true);
- expectAfter('echo', doCmd('previousWord'), true);
- expectAfter('a', doCmd('previousCharacter'), true);
- expectAfter('t', doCmd('previousCharacter'), true);
- expectAfter('echo', doCmd('nextWord'), true);
+// Flaky: http://crbug.com/451362
+TEST_F('BackgroundTest', 'DISABLED_SelectSingleBasic', function() {
+ this.runWithLoadedTree(this.formsDoc, function() {
+ var sendDownToSelect =
+ this.sendKeyToElement.bind(this, undefined, 'Down', '#fruitSelect');
+ var expect = cvox.ChromeVox.tts.expectSpeech.bind(cvox.ChromeVox.tts);
+ expect('apple Menu item 1 of 3 ', sendDownToSelect, true);
+ expect('grape 2 of 3 ', sendDownToSelect, true);
+ expect('banana 3 of 3 ', function() {}, true);
+ cvox.ChromeVox.tts.finishExpectations(this.newCallback());
+ });
+});
+
+TEST_F('BackgroundTest', 'MANUAL_ContinuousRead', function() {
+ this.runWithLoadedTree(this.linksAndHeadingsDoc, function() {
+ cvox.ChromeVox.tts.expectSpeech('start');
+ cvox.ChromeVox.tts.expectSpeechAfter('start', this.doCmd('continuousRead'));
+ cvox.ChromeVox.tts.expectSpeech('alpha');
+ cvox.ChromeVox.tts.expectSpeech('Link');
+ cvox.ChromeVox.tts.expectSpeech('beta');
+ cvox.ChromeVox.tts.expectSpeech('Link');
+ cvox.ChromeVox.tts.expectSpeech('Heading 1');
+ cvox.ChromeVox.tts.expectSpeech('charlie');
+ cvox.ChromeVox.tts.finishExpectations();
+ });
+});
+
+TEST_F('BackgroundTest', 'LiveRegionAddElement', function() {
+ this.runWithLoadedTree(
+ function() {/*!
+ <h1>Document with live region</h1>
+ <p id="live" aria-live="polite"></p>
+ <button id="go">Go</button>
+ <script>
+ document.getElementById('go').addEventListener('click', function() {
+ document.getElementById('live').innerHTML = 'Hello, world';
+ }, false);
+ </script>
+ */},
+ function(rootNode) {
+ var go = rootNode.find({ role: chrome.automation.RoleType.button });
+ go.doDefault();
+ cvox.ChromeVox.tts.expectSpeech('Hello, world', testDone);
+ cvox.ChromeVox.tts.finishExpectations();
+ }.bind(this));
+});
+TEST_F('BackgroundTest', 'LiveRegionRemoveElement', function() {
+ this.runWithLoadedTree(
+ function() {/*!
+ <h1>Document with live region</h1>
+ <p id="live" aria-live="polite" aria-relevant="removals">Hello, world</p>
+ <button id="go">Go</button>
+ <script>
+ document.getElementById('go').addEventListener('click', function() {
+ document.getElementById('live').innerHTML = '';
+ }, false);
+ </script>
+ */},
+ function(rootNode) {
+ var go = rootNode.find({ role: chrome.automation.RoleType.button });
+ go.doDefault();
+ cvox.ChromeVox.tts.expectSpeech('removed: Hello, world', testDone);
cvox.ChromeVox.tts.finishExpectations();
- }.bind(this));
+ }.bind(this));
});
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/classic_compatibility.js b/chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/classic_compatibility.js
new file mode 100644
index 00000000000..dae832c0c9c
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/classic_compatibility.js
@@ -0,0 +1,143 @@
+// Copyright 2015 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 Provides a compatibility layer for ChromeVox Classic during the
+ * transition to ChromeVox Next.
+ */
+
+goog.provide('ClassicCompatibility');
+
+goog.require('cvox.KeyMap');
+goog.require('cvox.KeySequence');
+goog.require('cvox.KeyUtil');
+goog.require('cvox.SimpleKeyEvent');
+
+/**
+ * @param {boolean=} opt_active Whether compatibility is currently active.
+ * @constructor
+ */
+var ClassicCompatibility = function(opt_active) {
+ /** @type {boolean} */
+ this.active = !!opt_active;
+
+ /**
+ * @type {!Array<{description: string, name: string, shortcut: string}>}
+ * @private
+ */
+ this.commands_ = [];
+
+ /** @type {!cvox.KeyMap} */
+ this.keyMap = cvox.KeyMap.fromCurrentKeyMap();
+
+ chrome.commands.getAll(function(commands) {
+ this.commands_ = commands;
+ }.bind(this));
+};
+
+ClassicCompatibility.prototype = {
+ /**
+ * Processes a ChromeVox Next command.
+ * @param {string} command
+ * @return {boolean} Whether the command was successfully processed.
+ */
+ onGotCommand: function(command) {
+ if (!this.active)
+ return false;
+
+ var commandInfo = this.commands_.filter(function(c) {
+ return c.name == command;
+ }.bind(this))[0];
+ if (!commandInfo)
+ return false;
+ var shortcut = commandInfo.shortcut;
+ var evt = this.convertCommandShortcutToKeyEvent_(shortcut);
+ this.simulateKeyDown_(evt);
+ return true;
+ },
+
+ /**
+ * @param {cvox.SimpleKeyEvent} evt
+ * @private
+ */
+ simulateKeyDown_: function(evt) {
+ var keySequence = cvox.KeyUtil.keyEventToKeySequence(evt);
+ var classicCommand = this.keyMap.commandForKey(keySequence);
+ if (classicCommand) {
+ var nextCommand = this.getNextCommand_(classicCommand);
+ if (nextCommand)
+ global.backgroundObj.onGotCommand(nextCommand, true);
+ }
+ },
+
+ /**
+ * @param {string} shortcut
+ * @return {cvox.SimpleKeyEvent}
+ * @private
+ */
+ convertCommandShortcutToKeyEvent_: function(shortcut) {
+ var evt = {};
+ shortcut.split('+').forEach(function(token) {
+ // Known tokens.
+ switch (token) {
+ case 'Ctrl':
+ evt.ctrlKey = true;
+ break;
+ case 'Shift':
+ evt.shiftKey = true;
+ break;
+ case 'Alt':
+ evt.altKey = true;
+ break;
+ case 'Search':
+ evt.searchKeyHeld = true;
+ break;
+ case 'Space':
+ evt.keyCode = 32;
+ break;
+ case 'Left Arrow':
+ evt.keyCode = 37;
+ break;
+ case 'Up Arrow':
+ evt.keyCode = 38;
+ break;
+ case 'Right Arrow':
+ evt.keyCode = 39;
+ break;
+ case 'Down Arrow':
+ evt.keyCode = 40;
+ break;
+ default:
+ evt.keyCode = token.charCodeAt(0);
+ }
+ });
+
+ return evt;
+ },
+
+ /**
+ * Maps a Classic command to an approximate equivalent in Next.
+ * @param {string} classicCommand
+ * @return {string}
+ * @private
+ */
+ getNextCommand_: function(classicCommand) {
+ switch (classicCommand) {
+ case 'right':
+ return 'nextElement';
+ case 'forward':
+ return 'nextLine';
+ case 'left':
+ return 'previousElement';
+ case 'backward':
+ return 'previousLine';
+ case 'forceClickOnCurrentItem':
+ return 'doDefault';
+ case 'readFromHere':
+ return 'continuousRead';
+ default:
+ return classicCommand;
+ }
+ }
+};
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/cursors.js b/chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/cursors.js
index 8c83ee5480b..82c0d5d981a 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/cursors.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/cursors.js
@@ -272,6 +272,38 @@ cursors.Range.fromNode = function(node) {
return new cursors.Range(cursor, cursor);
};
+ /**
+ * Given |rangeA| and |rangeB| in order, determine which |Dir|
+ * relates them.
+ * @param {!cursors.Range} rangeA
+ * @param {!cursors.Range} rangeB
+ * @return {Dir}
+ */
+cursors.Range.getDirection = function(rangeA, rangeB) {
+ if (!rangeA || !rangeB)
+ return Dir.FORWARD;
+
+ // They are the same range.
+ if (rangeA.getStart().getNode() === rangeB.getStart().getNode() &&
+ rangeB.getEnd().getNode() === rangeA.getEnd().getNode())
+ return Dir.FORWARD;
+
+ var testDirA =
+ AutomationUtil.getDirection(
+ rangeA.getStart().getNode(), rangeB.getEnd().getNode());
+ var testDirB =
+ AutomationUtil.getDirection(
+ rangeB.getStart().getNode(), rangeA.getEnd().getNode());
+
+ // The two ranges are either partly overlapping or non overlapping.
+ if (testDirA == Dir.FORWARD && testDirB == Dir.BACKWARD)
+ return Dir.FORWARD;
+ else if (testDirA == Dir.BACKWARD && testDirB == Dir.FORWARD)
+ return Dir.BACKWARD;
+ else
+ return testDirA;
+};
+
cursors.Range.prototype = {
/**
* Returns true if |rhs| is equal to this range.
@@ -287,9 +319,13 @@ cursors.Range.prototype = {
* Gets a cursor bounding this range.
* @param {Dir} dir Which endpoint cursor to return; Dir.FORWARD for end,
* Dir.BACKWARD for start.
+ * @param {boolean=} opt_reverse Specify to have Dir.BACKWARD return end,
+ * Dir.FORWARD return start.
* @return {!cursors.Cursor}
*/
- getBound: function(dir) {
+ getBound: function(dir, opt_reverse) {
+ if (opt_reverse)
+ return dir == Dir.BACKWARD ? this.end_ : this.start_;
return dir == Dir.FORWARD ? this.end_ : this.start_;
},
@@ -308,7 +344,7 @@ cursors.Range.prototype = {
},
/**
- * Returns whether true if this range covers less than a node.
+ * Returns true if this range covers less than a node.
* @return {boolean}
*/
isSubNode: function() {
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/cursors_test.extjs b/chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/cursors_test.extjs
index 7cc281a4b09..5f045757832 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/cursors_test.extjs
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/cursors_test.extjs
@@ -10,7 +10,9 @@ GEN_INCLUDE(['../../testing/chromevox_next_e2e_test_base.js']);
* @constructor
* @extends {ChromeVoxNextE2ETest}
*/
-function CursorsTest() {}
+function CursorsTest() {
+ ChromeVoxNextE2ETest.call(this);
+}
CursorsTest.prototype = {
__proto__: ChromeVoxNextE2ETest.prototype,
@@ -70,6 +72,7 @@ CursorsTest.prototype = {
range = range.move(move[0], move[1]);
var expectedStart = move[2];
var expectedEnd = move[3];
+
this.makeCursorAssertion(expectedStart, range.getStart());
this.makeCursorAssertion(expectedEnd, range.getEnd());
}
@@ -93,28 +96,24 @@ CursorsTest.prototype = {
* @param {string=} opt_testType Either CURSOR or RANGE.
*/
runCursorMovesOnDocument: function(doc, moves, opt_testType) {
- this.runWithDocument(doc,
- function() {
- chrome.automation.getTree(function(root) {
- var start = null;
+ this.runWithLoadedTree(doc,
+ function(root) {
+ var start = null;
- // This occurs as a result of a load complete.
- var start = AutomationUtil.findNodePost(root,
- FORWARD,
- AutomationPredicate.leaf);
+ // This occurs as a result of a load complete.
+ var start = AutomationUtil.findNodePost(root,
+ FORWARD,
+ AutomationPredicate.leaf);
+ var cursor = new cursors.Cursor(start, 0);
+ if (!opt_testType || opt_testType == this.CURSOR) {
var cursor = new cursors.Cursor(start, 0);
- if (!opt_testType || opt_testType == this.CURSOR) {
- var cursor = new cursors.Cursor(start, 0);
- this.cursorMoveAndAssert(cursor, moves);
- testDone();
- } else if (opt_testType == this.RANGE) {
- var range = new cursors.Range(cursor, cursor);
- this.rangeMoveAndAssert(range, moves);
- testDone();
- }
- }.bind(this));
- }.bind(this));
+ this.cursorMoveAndAssert(cursor, moves);
+ } else if (opt_testType == this.RANGE) {
+ var range = new cursors.Range(cursor, cursor);
+ this.rangeMoveAndAssert(range, moves);
+ }
+ });
},
simpleDoc: function() {/*!
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/output.js b/chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/output.js
index 1f58c977ee4..3be31387ffb 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/output.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/output.js
@@ -7,92 +7,1155 @@
*/
goog.provide('Output');
+goog.provide('Output.EventType');
goog.require('AutomationUtil.Dir');
goog.require('cursors.Cursor');
goog.require('cursors.Range');
goog.require('cursors.Unit');
+goog.require('cvox.AbstractEarcons');
+goog.require('cvox.NavBraille');
+goog.require('cvox.Spannable');
+goog.require('cvox.ValueSelectionSpan');
+goog.require('cvox.ValueSpan');
-/** @constructor */
+goog.scope(function() {
+var Dir = AutomationUtil.Dir;
+
+/**
+ * An Output object formats a cursors.Range into speech, braille, or both
+ * representations. This is typically a cvox.Spannable.
+ *
+ * The translation from Range to these output representations rely upon format
+ * rules which specify how to convert AutomationNode objects into annotated
+ * strings.
+ * The format of these rules is as follows.
+ *
+ * $ prefix: used to substitute either an attribute or a specialized value from
+ * an AutomationNode. Specialized values include role and state. Attributes
+ * available for substitution are AutomationNode.prototype.attributes and
+ * AutomationNode.prototype.state.
+ * For example, $value $role $enabled
+ * @ prefix: used to substitute a message. Note the ability to specify params to
+ * the message. For example, '@tag_html' '@selected_index($text_sel_start,
+ * $text_sel_end').
+ * = suffix: used to specify substitution only if not previously appended.
+ * For example, $name= would insert the name attribute only if no name
+ * attribute had been inserted previously.
+ * @constructor
+ */
Output = function() {
- /** @type {cursors.Range} @private */
- this.prevRange_ = null;
+ // TODO(dtseng): Include braille specific rules.
+ /** @type {!Array<cvox.Spannable>} */
+ this.buffer_ = [];
+ /** @type {!Array<cvox.Spannable>} */
+ this.brailleBuffer_ = [];
+ /** @type {!Array<Object>} */
+ this.locations_ = [];
+ /** @type {function(?)} */
+ this.speechEndCallback_;
+
+ /**
+ * Current global options.
+ * @type {{speech: boolean, braille: boolean, location: boolean}}
+ */
+ this.formatOptions_ = {speech: true, braille: false, location: true};
+
+ /**
+ * Speech properties to apply to the entire output.
+ * @type {!Object<string, *>}
+ */
+ this.speechProperties_ = {};
+};
+
+/**
+ * Delimiter to use between output values.
+ * @type {string}
+ */
+Output.SPACE = ' ';
+
+/**
+ * Metadata about supported automation roles.
+ * @const {Object<string, {msgId: string,
+ * earconId: (string|undefined),
+ * inherits: (string|undefined)}>}
+ * msgId: the message id of the role.
+ * earconId: an optional earcon to play when encountering the role.
+ * inherits: inherits rules from this role.
+ * @private
+ */
+Output.ROLE_INFO_ = {
+ alert: {
+ msgId: 'aria_role_alert',
+ earconId: 'ALERT_NONMODAL',
+ },
+ alertDialog: {
+ msgId: 'aria_role_alertdialog'
+ },
+ article: {
+ msgId: 'aria_role_article',
+ inherits: 'abstractContainer'
+ },
+ application: {
+ msgId: 'aria_role_application',
+ inherits: 'abstractContainer'
+ },
+ banner: {
+ msgId: 'aria_role_banner',
+ inherits: 'abstractContainer'
+ },
+ button: {
+ msgId: 'tag_button',
+ earconId: 'BUTTON'
+ },
+ cell: {
+ msgId: 'aria_role_gridcell'
+ },
+ checkBox: {
+ msgId: 'input_type_checkbox'
+ },
+ columnHeader: {
+ msgId: 'aria_role_columnheader',
+ inherits: 'abstractContainer'
+ },
+ comboBox: {
+ msgId: 'aria_role_combobox'
+ },
+ complementary: {
+ msgId: 'aria_role_complementary',
+ inherits: 'abstractContainer'
+ },
+ contentInfo: {
+ msgId: 'aria_role_contentinfo',
+ inherits: 'abstractContainer'
+ },
+ date: {
+ msgId: 'input_type_date',
+ inherits: 'abstractContainer'
+ },
+ definition: {
+ msgId: 'aria_role_definition',
+ inherits: 'abstractContainer'
+ },
+ dialog: {
+ msgId: 'dialog'
+ },
+ directory: {
+ msgId: 'aria_role_directory',
+ inherits: 'abstractContainer'
+ },
+ document: {
+ msgId: 'aria_role_document',
+ inherits: 'abstractContainer'
+ },
+ form: {
+ msgId: 'aria_role_form',
+ inherits: 'abstractContainer'
+ },
+ grid: {
+ msgId: 'aria_role_grid'
+ },
+ group: {
+ msgId: 'aria_role_group'
+ },
+ heading: {
+ msgId: 'aria_role_heading',
+ },
+ image: {
+ msgId: 'aria_role_img',
+ },
+ link: {
+ msgId: 'tag_link',
+ earconId: 'LINK'
+ },
+ listBox: {
+ msgId: 'aria_role_listbox',
+ earconId: 'LISTBOX'
+ },
+ listBoxOption: {
+ msgId: 'aria_role_listitem',
+ earconId: 'LIST_ITEM'
+ },
+ listItem: {
+ msgId: 'aria_role_listitem',
+ earconId: 'LIST_ITEM'
+ },
+ log: {
+ msgId: 'aria_role_log',
+ },
+ main: {
+ msgId: 'aria_role_main',
+ inherits: 'abstractContainer'
+ },
+ marquee: {
+ msgId: 'aria_role_marquee',
+ },
+ math: {
+ msgId: 'aria_role_math',
+ inherits: 'abstractContainer'
+ },
+ menu: {
+ msgId: 'aria_role_menu'
+ },
+ menuBar: {
+ msgId: 'aria_role_menubar',
+ },
+ menuItem: {
+ msgId: 'aria_role_menuitem'
+ },
+ menuItemCheckBox: {
+ msgId: 'aria_role_menuitemcheckbox'
+ },
+ menuItemRadio: {
+ msgId: 'aria_role_menuitemradio'
+ },
+ menuListOption: {
+ msgId: 'aria_role_menuitem'
+ },
+ menuListPopup: {
+ msgId: 'aria_role_menu'
+ },
+ navigation: {
+ msgId: 'aria_role_navigation',
+ inherits: 'abstractContainer'
+ },
+ note: {
+ msgId: 'aria_role_note',
+ inherits: 'abstractContainer'
+ },
+ region: {
+ msgId: 'aria_role_region',
+ inherits: 'abstractContainer'
+ },
+ popUpButton: {
+ msgId: 'tag_button',
+ earcon: 'LISTBOX'
+ },
+ radioButton: {
+ msgId: 'input_type_radio'
+ },
+ radioGroup: {
+ msgId: 'aria_role_radiogroup',
+ },
+ rowHeader: {
+ msgId: 'aria_role_rowheader',
+ inherits: 'abstractContainer'
+ },
+ scrollBar: {
+ msgId: 'aria_role_scrollbar',
+ },
+ search: {
+ msgId: 'aria_role_search',
+ inherits: 'abstractContainer'
+ },
+ separator: {
+ msgId: 'aria_role_separator',
+ inherits: 'abstractContainer'
+ },
+ spinButton: {
+ msgId: 'aria_role_spinbutton',
+ earconId: 'LISTBOX'
+ },
+ status: {
+ msgId: 'aria_role_status'
+ },
+ tab: {
+ msgId: 'aria_role_tab'
+ },
+ tabList: {
+ msgId: 'aria_role_tablist'
+ },
+ tabPanel: {
+ msgId: 'aria_role_tabpanel'
+ },
+ textBox: {
+ msgId: 'input_type_text',
+ earconId: 'EDITABLE_TEXT'
+ },
+ textField: {
+ msgId: 'input_type_text',
+ earconId: 'EDITABLE_TEXT'
+ },
+ time: {
+ msgId: 'tag_time',
+ inherits: 'abstractContainer'
+ },
+ timer: {
+ msgId: 'aria_role_timer'
+ },
+ toolbar: {
+ msgId: 'aria_role_toolbar'
+ },
+ tree: {
+ msgId: 'aria_role_tree'
+ },
+ treeItem: {
+ msgId: 'aria_role_treeitem'
+ }
+};
+
+/**
+ * Metadata about supported automation states.
+ * @const {!Object<string,
+ * {on: {msgId: string, earconId: string},
+ * off: {msgId: string, earconId: string},
+ * omitted: {msgId: string, earconId: string}}>}
+ * on: info used to describe a state that is set to true.
+ * off: info used to describe a state that is set to false.
+ * omitted: info used to describe a state that is undefined.
+ * @private
+ */
+Output.STATE_INFO_ = {
+ checked: {
+ on: {
+ earconId: 'CHECK_ON',
+ msgId: 'checkbox_checked_state'
+ },
+ off: {
+ earconId: 'CHECK_OFF',
+ msgId: 'checkbox_unchecked_state'
+ },
+ omitted: {
+ earconId: 'CHECK_OFF',
+ msgId: 'checkbox_unchecked_state'
+ }
+ },
+ collapsed: {
+ on: {
+ msgId: 'aria_expanded_false'
+ },
+ off: {
+ msgId: 'aria_expanded_true'
+ }
+ },
+ expanded: {
+ on: {
+ msgId: 'aria_expanded_true'
+ },
+ off: {
+ msgId: 'aria_expanded_false'
+ }
+ },
+ visited: {
+ on: {
+ msgId: 'visited_state'
+ }
+ }
+};
+
+/**
+ * Rules specifying format of AutomationNodes for output.
+ * @type {!Object<string, Object<string, Object<string, string>>>}
+ */
+Output.RULES = {
+ navigate: {
+ 'default': {
+ speak: '$name $value $description $help $role',
+ braille: ''
+ },
+ abstractContainer: {
+ enter: '$name $role',
+ leave: '@exited_container($role)'
+ },
+ alert: {
+ speak: '!doNotInterrupt $role $descendants'
+ },
+ alertDialog: {
+ enter: '$name $role $descendants'
+ },
+ checkBox: {
+ speak: '$name $role $checked'
+ },
+ dialog: {
+ enter: '$name $role'
+ },
+ grid: {
+ enter: '$name $role'
+ },
+ heading: {
+ enter: '@tag_h+$hierarchicalLevel',
+ speak: '@tag_h+$hierarchicalLevel $nameOrDescendants='
+ },
+ inlineTextBox: {
+ speak: '$value='
+ },
+ link: {
+ enter: '$name $visited $role',
+ stay: '$name= $visited $role',
+ speak: '$name= $visited $role'
+ },
+ list: {
+ enter: '$role @list_with_items($countChildren(listItem))'
+ },
+ listBox: {
+ enter: '$name $role @list_with_items($countChildren(listBoxOption))'
+ },
+ listBoxOption: {
+ speak: '$name $role @describe_index($indexInParent, $parentChildCount)'
+ },
+ listItem: {
+ enter: '$role'
+ },
+ menu: {
+ enter: '$name $role @list_with_items($countChildren(menuItem))'
+ },
+ menuItem: {
+ speak: '$if($haspopup, @describe_menu_item_with_submenu($name), ' +
+ '@describe_menu_item($name)) ' +
+ '@describe_index($indexInParent, $parentChildCount)'
+ },
+ menuListOption: {
+ speak: '$name $value @aria_role_menuitem ' +
+ '@describe_index($indexInParent, $parentChildCount)'
+ },
+ paragraph: {
+ speak: '$value'
+ },
+ popUpButton: {
+ speak: '$value $name $role @aria_has_popup ' +
+ '$if($collapsed, @aria_expanded_false, @aria_expanded_true)'
+ },
+ radioButton: {
+ speak: '$if($checked, @describe_radio_selected($name), ' +
+ '@describe_radio_unselected($name))'
+ },
+ radioGroup: {
+ enter: '$name $role'
+ },
+ slider: {
+ speak: '@describe_slider($value, $name) $help'
+ },
+ staticText: {
+ speak: '$value $name'
+ },
+ tab: {
+ speak: '@describe_tab($name)'
+ },
+ textField: {
+ speak: '$name $value $if(' +
+ '$textInputType, @input_type_+$textInputType, @input_type_text)',
+ braille: ''
+ },
+ toolbar: {
+ enter: '$name $role'
+ },
+ tree: {
+ enter: '$name $role @list_with_items($countChildren(treeItem))'
+ },
+ treeItem: {
+ enter: '$role $expanded $collapsed ' +
+ '@describe_index($indexInParent, $parentChildCount) ' +
+ '@describe_depth($hierarchicalLevel)'
+ },
+ window: {
+ enter: '$name',
+ speak: '@describe_window($name) $earcon(OBJECT_OPEN)'
+ }
+ },
+ menuStart: {
+ 'default': {
+ speak: '@chrome_menu_opened($name) $earcon(OBJECT_OPEN)'
+ }
+ },
+ menuEnd: {
+ 'default': {
+ speak: '@chrome_menu_closed $earcon(OBJECT_CLOSE)'
+ }
+ },
+ menuListValueChanged: {
+ 'default': {
+ speak: '$value $name ' +
+ '$find({"state": {"selected": true, "invisible": false}}, ' +
+ '@describe_index($indexInParent, $parentChildCount)) '
+ }
+ },
+ alert: {
+ default: {
+ speak: '!doNotInterrupt ' +
+ '@aria_role_alert $name $earcon(ALERT_NONMODAL) $descendants'
+ }
+ }
+};
+
+/**
+ * Custom actions performed while rendering an output string.
+ * @param {function()} action
+ * @constructor
+ */
+Output.Action = function(action) {
+ this.action_ = action;
+};
+
+Output.Action.prototype = {
+ run: function() {
+ this.action_();
+ }
+};
+
+/**
+ * Action to play a earcon.
+ * @param {string} earconId
+ * @constructor
+ * @extends {Output.Action}
+ */
+Output.EarconAction = function(earconId) {
+ Output.Action.call(this, function() {
+ cvox.ChromeVox.earcons.playEarcon(
+ cvox.AbstractEarcons[earconId]);
+ });
+};
+
+Output.EarconAction.prototype = {
+ __proto__: Output.Action.prototype
+};
+
+/**
+ * Annotation for selection.
+ * @param {number} startIndex
+ * @param {number} endIndex
+ * @constructor
+ */
+Output.SelectionSpan = function(startIndex, endIndex) {
+ // TODO(dtseng): Direction lost below; should preserve for braille panning.
+ this.startIndex = startIndex < endIndex ? startIndex : endIndex;
+ this.endIndex = endIndex > startIndex ? endIndex : startIndex;
+};
+
+/**
+ * Possible events handled by ChromeVox internally.
+ * @enum {string}
+ */
+Output.EventType = {
+ NAVIGATE: 'navigate'
};
Output.prototype = {
/**
- * Handles output of the given range.
+ * Gets the output buffer for speech.
+ * @param {string=} opt_separator Used to join components of the output.
+ * @return {!cvox.Spannable}
+ */
+ toSpannable: function(opt_separator) {
+ opt_separator = opt_separator || '';
+ return this.buffer_.reduce(function(prev, cur) {
+ if (prev === null)
+ return cur;
+ prev.append(opt_separator);
+ prev.append(cur);
+ return prev;
+ }, null);
+ },
+
+ /**
+ * Gets the output buffer for speech with separator '|'.
+ * @return {!cvox.Spannable}
+ */
+ toSpannableForTest: function() {
+ return this.toSpannable('|');
+ },
+
+ /**
+ * Specify ranges for speech.
+ * @param {!cursors.Range} range
+ * @param {cursors.Range} prevRange
+ * @param {chrome.automation.EventType|Output.EventType} type
+ * @return {!Output}
+ */
+ withSpeech: function(range, prevRange, type) {
+ this.formatOptions_ = {speech: true, braille: false, location: true};
+ this.render_(range, prevRange, type, this.buffer_);
+ return this;
+ },
+
+ /**
+ * Specify ranges for braille.
+ * @param {!cursors.Range} range
+ * @param {cursors.Range} prevRange
+ * @param {chrome.automation.EventType|Output.EventType} type
+ * @return {!Output}
+ */
+ withBraille: function(range, prevRange, type) {
+ this.formatOptions_ = {speech: false, braille: true, location: false};
+ this.render_(range, prevRange, type, this.brailleBuffer_);
+ return this;
+ },
+
+ /**
+ * Specify the same ranges for speech and braille.
+ * @param {!cursors.Range} range
+ * @param {cursors.Range} prevRange
+ * @param {chrome.automation.EventType|Output.EventType} type
+ * @return {!Output}
+ */
+ withSpeechAndBraille: function(range, prevRange, type) {
+ this.withSpeech(range, prevRange, type);
+ this.withBraille(range, prevRange, type);
+ return this;
+ },
+
+ /**
+ * Apply a format string directly to the output buffer. This lets you
+ * output a message directly to the buffer using the format syntax.
+ * @param {string} formatStr
+ * @return {!Output}
+ */
+ format: function(formatStr) {
+ this.formatOptions_ = {speech: true, braille: false, location: true};
+ this.format_(null, formatStr, this.buffer_);
+
+ this.formatOptions_ = {speech: false, braille: true, location: false};
+ this.format_(null, formatStr, this.brailleBuffer_);
+
+ return this;
+ },
+
+ /**
+ * Triggers callback for a speech event.
+ * @param {function()} callback
+ */
+ onSpeechEnd: function(callback) {
+ this.speechEndCallback_ = function(opt_cleanupOnly) {
+ if (!opt_cleanupOnly)
+ callback();
+ }.bind(this);
+ return this;
+ },
+
+ /**
+ * Executes all specified output.
+ */
+ go: function() {
+ // Speech.
+ var queueMode = cvox.QueueMode.FLUSH;
+ this.buffer_.forEach(function(buff, i, a) {
+ if (buff.toString()) {
+ (function() {
+ var scopedBuff = buff;
+ this.speechProperties_['startCallback'] = function() {
+ var actions = scopedBuff.getSpansInstanceOf(Output.Action);
+ if (actions) {
+ actions.forEach(function(a) {
+ a.run();
+ });
+ }
+ };
+ }.bind(this)());
+
+ if (this.speechEndCallback_ && i == a.length - 1)
+ this.speechProperties_['endCallback'] = this.speechEndCallback_;
+ else
+ this.speechProperties_['endCallback'] = null;
+ cvox.ChromeVox.tts.speak(
+ buff.toString(), queueMode, this.speechProperties_);
+ queueMode = cvox.QueueMode.QUEUE;
+ }
+ }.bind(this));
+
+ // Braille.
+ var buff = this.brailleBuffer_.reduce(function(prev, cur) {
+ if (prev.getLength() > 0 && cur.getLength() > 0)
+ prev.append(Output.SPACE);
+ prev.append(cur);
+ return prev;
+ }, new cvox.Spannable());
+
+ var selSpan =
+ buff.getSpanInstanceOf(Output.SelectionSpan);
+ var startIndex = -1, endIndex = -1;
+ if (selSpan) {
+ // Casts ok, since the span is known to be in the spannable.
+ var valueStart =
+ /** @type {number} */ (buff.getSpanStart(selSpan));
+ var valueEnd =
+ /** @type {number} */ (buff.getSpanEnd(selSpan));
+ startIndex = valueStart + selSpan.startIndex;
+ endIndex = valueStart + selSpan.endIndex;
+ buff.setSpan(new cvox.ValueSpan(0),
+ valueStart, valueEnd);
+ buff.setSpan(new cvox.ValueSelectionSpan(),
+ startIndex, endIndex);
+ }
+
+ var output = new cvox.NavBraille({
+ text: buff,
+ startIndex: startIndex,
+ endIndex: endIndex
+ });
+
+ if (this.brailleBuffer_)
+ cvox.ChromeVox.braille.write(output);
+
+ // Display.
+ chrome.accessibilityPrivate.setFocusRing(this.locations_);
+ },
+
+ /**
+ * Renders the given range using optional context previous range and event
+ * type.
+ * @param {!cursors.Range} range
+ * @param {cursors.Range} prevRange
+ * @param {chrome.automation.EventType|string} type
+ * @param {!Array<cvox.Spannable>} buff Buffer to receive rendered output.
+ * @private
*/
- output: function(range) {
- var out = {};
+ render_: function(range, prevRange, type, buff) {
if (range.isSubNode())
- out = this.subNode_(range);
- else
- out = this.node_(range);
+ this.subNode_(range, prevRange, type, buff);
+ else
+ this.range_(range, prevRange, type, buff);
+ },
+
+ /**
+ * Format the node given the format specifier.
+ * @param {chrome.automation.AutomationNode} node
+ * @param {string|!Object} format The output format either specified as an
+ * output template string or a parsed output format tree.
+ * @param {!Array<cvox.Spannable>} buff Buffer to receive rendered output.
+ * @param {!Object=} opt_exclude A set of attributes to exclude.
+ * @private
+ */
+ format_: function(node, format, buff, opt_exclude) {
+ opt_exclude = opt_exclude || {};
+ var tokens = [];
+ var args = null;
+
+ // Hacky way to support args.
+ if (typeof(format) == 'string') {
+ format = format.replace(/([,:])\W/g, '$1');
+ tokens = format.split(' ');
+ } else {
+ tokens = [format];
+ }
+
+ tokens.forEach(function(token) {
+ // Ignore empty tokens.
+ if (!token)
+ return;
+
+ // Parse the token.
+ var tree;
+ if (typeof(token) == 'string')
+ tree = this.createParseTree(token);
+ else
+ tree = token;
+
+ // Obtain the operator token.
+ token = tree.value;
+
+ // Set suffix options.
+ var options = {};
+ options.annotation = [];
+ options.isUnique = token[token.length - 1] == '=';
+ if (options.isUnique)
+ token = token.substring(0, token.length - 1);
- cvox.ChromeVox.tts.speak(out.text, cvox.QueueMode.FLUSH);
- cvox.ChromeVox.braille.write(cvox.NavBraille.fromText(out.text));
- chrome.accessibilityPrivate.setFocusRing(out.locations);
+ // Process token based on prefix.
+ var prefix = token[0];
+ token = token.slice(1);
- this.prevRange_ = range;
+ if (opt_exclude[token])
+ return;
+
+ // All possible tokens based on prefix.
+ if (prefix == '$') {
+ if (token == 'value') {
+ var text = node.attributes.value;
+ if (text !== undefined) {
+ if (node.attributes.textSelStart !== undefined) {
+ options.annotation.push(new Output.SelectionSpan(
+ node.attributes.textSelStart,
+ node.attributes.textSelEnd));
+ }
+ }
+ // Annotate this as a name so we don't duplicate names from ancestors.
+ if (node.role == chrome.automation.RoleType.inlineTextBox)
+ token = 'name';
+ options.annotation.push(token);
+ this.append_(buff, text, options);
+ } else if (token == 'name') {
+ options.annotation.push(token);
+ var earconFinder = node;
+ while (earconFinder) {
+ var info = Output.ROLE_INFO_[earconFinder.role];
+ if (info && info.earconId) {
+ options.annotation.push(
+ new Output.EarconAction(info.earconId));
+ break;
+ }
+ earconFinder = earconFinder.parent;
+ }
+ this.append_(buff, node.attributes.name, options);
+ } else if (token == 'nameOrDescendants') {
+ options.annotation.push(token);
+ if (node.name)
+ this.append_(buff, node.name, options);
+ else
+ this.format_(node, '$descendants', buff);
+ } else if (token == 'indexInParent') {
+ options.annotation.push(token);
+ this.append_(buff, String(node.indexInParent + 1));
+ } else if (token == 'parentChildCount') {
+ options.annotation.push(token);
+ if (node.parent)
+ this.append_(buff, String(node.parent.children.length));
+ } else if (token == 'state') {
+ options.annotation.push(token);
+ Object.getOwnPropertyNames(node.state).forEach(function(s) {
+ this.append_(buff, s, options);
+ }.bind(this));
+ } else if (token == 'find') {
+ // Find takes two arguments: JSON query string and format string.
+ if (tree.firstChild) {
+ var jsonQuery = tree.firstChild.value;
+ node = node.find(
+ /** @type {Object}*/(JSON.parse(jsonQuery)));
+ var formatString = tree.firstChild.nextSibling;
+ if (node)
+ this.format_(node, formatString, buff);
+ }
+ } else if (token == 'descendants') {
+ if (AutomationPredicate.leaf(node))
+ return;
+
+ // Construct a range to the leftmost and rightmost leaves.
+ var leftmost = AutomationUtil.findNodePre(
+ node, Dir.FORWARD, AutomationPredicate.leaf);
+ var rightmost = AutomationUtil.findNodePre(
+ node, Dir.BACKWARD, AutomationPredicate.leaf);
+ if (!leftmost || !rightmost)
+ return;
+
+ var subrange = new cursors.Range(
+ new cursors.Cursor(leftmost, 0),
+ new cursors.Cursor(rightmost, 0));
+ var prev = null;
+ if (node)
+ prev = cursors.Range.fromNode(node);
+ this.range_(subrange, prev, 'navigate', buff);
+ } else if (token == 'role') {
+ options.annotation.push(token);
+ var msg = node.role;
+ var info = Output.ROLE_INFO_[node.role];
+ if (info) {
+ if (this.formatOptions_.braille)
+ msg = cvox.ChromeVox.msgs.getMsg(info.msgId + '_brl');
+ else
+ msg = cvox.ChromeVox.msgs.getMsg(info.msgId);
+ } else {
+ console.error('Missing role info for ' + node.role);
+ }
+ this.append_(buff, msg, options);
+ } else if (node.attributes[token] !== undefined) {
+ options.annotation.push(token);
+ var value = node.attributes[token];
+ if (typeof value == 'number')
+ value = String(value);
+ this.append_(buff, value, options);
+ } else if (Output.STATE_INFO_[token]) {
+ options.annotation.push('state');
+ var stateInfo = Output.STATE_INFO_[token];
+ var resolvedInfo = {};
+ if (node.state[token] === undefined)
+ resolvedInfo = stateInfo.omitted;
+ else
+ resolvedInfo = node.state[token] ? stateInfo.on : stateInfo.off;
+ if (!resolvedInfo)
+ return;
+ if (resolvedInfo.earconId) {
+ options.annotation.push(
+ new Output.EarconAction(resolvedInfo.earconId));
+ }
+ var msgId =
+ this.formatOptions_.braille ? resolvedInfo.msgId + '_brl' :
+ resolvedInfo.msgId;
+ var msg = cvox.ChromeVox.msgs.getMsg(msgId);
+ this.append_(buff, msg, options);
+ } else if (tree.firstChild) {
+ // Custom functions.
+ if (token == 'if') {
+ var cond = tree.firstChild;
+ var attrib = cond.value.slice(1);
+ if (node.attributes[attrib] || node.state[attrib])
+ this.format_(node, cond.nextSibling, buff);
+ else
+ this.format_(node, cond.nextSibling.nextSibling, buff);
+ } else if (token == 'earcon') {
+ // Assumes there's existing output in our buffer.
+ var lastBuff = buff[buff.length - 1];
+ if (!lastBuff)
+ return;
+
+ lastBuff.setSpan(
+ new Output.EarconAction(tree.firstChild.value), 0, 0);
+ } else if (token == 'countChildren') {
+ var role = tree.firstChild.value;
+ var count = node.children.filter(function(e) {
+ return e.role == role;
+ }).length;
+ this.append_(buff, String(count));
+ }
+ }
+ } else if (prefix == '@') {
+ // Tokens can have substitutions.
+ var pieces = token.split('+');
+ token = pieces.reduce(function(prev, cur) {
+ var lookup = cur;
+ if (cur[0] == '$')
+ lookup = node.attributes[cur.slice(1)];
+ return prev + lookup;
+ }.bind(this), '');
+ var msgId = token;
+ var msgArgs = [];
+ var curMsg = tree.firstChild;
+
+ while (curMsg) {
+ var arg = curMsg.value;
+ if (arg[0] != '$') {
+ console.error('Unexpected value: ' + arg);
+ return;
+ }
+ var msgBuff = [];
+ this.format_(node, curMsg, msgBuff);
+ msgArgs = msgArgs.concat(msgBuff);
+ curMsg = curMsg.nextSibling;
+ }
+ var msg = cvox.ChromeVox.msgs.getMsg(msgId, msgArgs);
+ try {
+ if (this.formatOptions_.braille)
+ msg = cvox.ChromeVox.msgs.getMsg(msgId + '_brl', msgArgs) || msg;
+ } catch(e) {}
+
+ if (msg) {
+ this.append_(buff, msg, options);
+ }
+ } else if (prefix == '!') {
+ this.speechProperties_[token] = true;
+ }
+ }.bind(this));
},
/**
* @param {!cursors.Range} range
- * @return {{text: string, locations: !Array.<Object>}}
+ * @param {cursors.Range} prevRange
+ * @param {chrome.automation.EventType|string} type
+ * @param {!Array<cvox.Spannable>} rangeBuff
* @private
*/
- node_: function(range) {
- // Walk the range and collect descriptions.
- var out = {text: '', locations: []};
+ range_: function(range, prevRange, type, rangeBuff) {
+ if (!prevRange)
+ prevRange = cursors.Range.fromNode(range.getStart().getNode().root);
+
var cursor = range.getStart();
+ var prevNode = prevRange.getStart().getNode();
+
+ var formatNodeAndAncestors = function(node, prevNode) {
+ var buff = [];
+ this.ancestry_(node, prevNode, type, buff);
+ this.node_(node, prevNode, type, buff);
+ if (this.formatOptions_.location)
+ this.locations_.push(node.location);
+ return buff;
+ }.bind(this);
+
while (cursor.getNode() != range.getEnd().getNode()) {
- out.text += this.getCursorDesc_(cursor);
- out.locations.push(cursor.getNode().location);
+ var node = cursor.getNode();
+ rangeBuff.push.apply(rangeBuff, formatNodeAndAncestors(node, prevNode));
+ prevNode = node;
cursor = cursor.move(cursors.Unit.NODE,
cursors.Movement.DIRECTIONAL,
- AutomationUtil.Dir.FORWARD);
+ Dir.FORWARD);
}
- out.text += this.getCursorDesc_(range.getEnd());
- out.locations.push(range.getEnd().getNode().location);
- return out;
+ var lastNode = range.getEnd().getNode();
+ rangeBuff.push.apply(rangeBuff, formatNodeAndAncestors(lastNode, prevNode));
},
- /**
+ /**
+ * @param {!chrome.automation.AutomationNode} node
+ * @param {!chrome.automation.AutomationNode} prevNode
+ * @param {chrome.automation.EventType|string} type
+ * @param {!Array<cvox.Spannable>} buff
+ * @param {!Object=} opt_exclude A list of attributes to exclude from
+ * processing.
+ * @private
+ */
+ ancestry_: function(node, prevNode, type, buff, opt_exclude) {
+ opt_exclude = opt_exclude || {};
+ var prevUniqueAncestors =
+ AutomationUtil.getUniqueAncestors(node, prevNode);
+ var uniqueAncestors = AutomationUtil.getUniqueAncestors(prevNode, node);
+
+ // First, look up the event type's format block.
+ // Navigate is the default event.
+ var eventBlock = Output.RULES[type] || Output.RULES['navigate'];
+
+ var getMergedRoleBlock = function(role) {
+ var parentRole = (Output.ROLE_INFO_[role] || {}).inherits;
+ var roleBlock = eventBlock[role] || eventBlock['default'];
+ var parentRoleBlock = parentRole ? eventBlock[parentRole] : {};
+ var mergedRoleBlock = {};
+ for (var key in parentRoleBlock)
+ mergedRoleBlock[key] = parentRoleBlock[key];
+ for (var key in roleBlock)
+ mergedRoleBlock[key] = roleBlock[key];
+ return mergedRoleBlock;
+ };
+
+ for (var i = 0, formatPrevNode;
+ (formatPrevNode = prevUniqueAncestors[i]);
+ i++) {
+ var roleBlock = getMergedRoleBlock(formatPrevNode.role);
+ if (roleBlock.leave)
+ this.format_(formatPrevNode, roleBlock.leave, buff, opt_exclude);
+ }
+
+ var enterOutputs = [];
+ var enterRole = {};
+ for (var j = uniqueAncestors.length - 2, formatNode;
+ (formatNode = uniqueAncestors[j]);
+ j--) {
+ var roleBlock = getMergedRoleBlock(formatNode.role);
+ if (roleBlock.enter) {
+ if (enterRole[formatNode.role])
+ continue;
+ enterRole[formatNode.role] = true;
+ var tempBuff = [];
+ this.format_(formatNode, roleBlock.enter, tempBuff, opt_exclude);
+ enterOutputs.unshift(tempBuff);
+ }
+ if (formatNode.role == 'window')
+ break;
+ }
+ enterOutputs.forEach(function(b) {
+ buff.push.apply(buff, b);
+ });
+
+ if (!opt_exclude.stay) {
+ var commonFormatNode = uniqueAncestors[0];
+ while (commonFormatNode && commonFormatNode.parent) {
+ commonFormatNode = commonFormatNode.parent;
+ var roleBlock =
+ eventBlock[commonFormatNode.role] || eventBlock['default'];
+ if (roleBlock.stay)
+ this.format_(commonFormatNode, roleBlock.stay, buff, opt_exclude);
+ }
+ }
+ },
+
+ /**
+ * @param {!chrome.automation.AutomationNode} node
+ * @param {!chrome.automation.AutomationNode} prevNode
+ * @param {chrome.automation.EventType|string} type
+ * @param {!Array<cvox.Spannable>} buff
+ * @private
+ */
+ node_: function(node, prevNode, type, buff) {
+ // Navigate is the default event.
+ var eventBlock = Output.RULES[type] || Output.RULES['navigate'];
+ var roleBlock = eventBlock[node.role] || eventBlock['default'];
+ var speakFormat = roleBlock.speak || eventBlock['default'].speak;
+ this.format_(node, speakFormat, buff);
+ },
+
+ /**
* @param {!cursors.Range} range
- * @return {{text: string, locations: !Array.<Object>}}
+ * @param {cursors.Range} prevRange
+ * @param {chrome.automation.EventType|string} type
+ * @param {!Array<cvox.Spannable>} buff
* @private
*/
- subNode_: function(range) {
- // TODO(dtseng): Need a way to select subnode ranges (or compute their
- // rects).
- var out = {text: '', locations: []};
+ subNode_: function(range, prevRange, type, buff) {
+ if (!prevRange)
+ prevRange = range;
+ var dir = cursors.Range.getDirection(prevRange, range);
+ var prevNode = prevRange.getBound(dir).getNode();
+ this.ancestry_(
+ range.getStart().getNode(), prevNode, type, buff,
+ {stay: true, name: true, value: true});
var startIndex = range.getStart().getIndex();
var endIndex = range.getEnd().getIndex();
if (startIndex === endIndex)
endIndex++;
+ this.append_(
+ buff, range.getStart().getText().substring(startIndex, endIndex));
+ },
+
+ /**
+ * Appends output to the |buff|.
+ * @param {!Array<cvox.Spannable>} buff
+ * @param {string|!cvox.Spannable} value
+ * @param {{isUnique: (boolean|undefined),
+ * annotation: !Array<*>}=} opt_options
+ */
+ append_: function(buff, value, opt_options) {
+ opt_options = opt_options || {isUnique: false, annotation: []};
- out.text = range.getStart().getText().substring(startIndex, endIndex);
+ // Reject empty values without annotations.
+ if ((!value || value.length == 0) && opt_options.annotation.length == 0)
+ return;
+
+ var spannableToAdd = new cvox.Spannable(value);
+ opt_options.annotation.forEach(function(a) {
+ spannableToAdd.setSpan(a, 0, spannableToAdd.getLength());
+ });
+
+ // Early return if the buffer is empty.
+ if (buff.length == 0) {
+ buff.push(spannableToAdd);
+ return;
+ }
- return out;
+ // |isUnique| specifies an annotation that cannot be duplicated.
+ if (opt_options.isUnique) {
+ var alreadyAnnotated = buff.some(function(s) {
+ return opt_options.annotation.some(function(annotation) {
+ return s.getSpanStart(annotation) != undefined;
+ });
+ });
+ if (alreadyAnnotated)
+ return;
+ }
+
+ buff.push(spannableToAdd);
},
- // TODO(dtseng): This is just placeholder logic for generating descriptions
- // pending further design discussion.
/**
- * @param {!cursors.Cursor} cursor
- * @return {string}
- * @private
+ * Parses the token containing a custom function and returns a tree.
+ * @param {string} inputStr
+ * @return {Object}
*/
- getCursorDesc_: function(cursor) {
- var node = cursor.getNode();
- var container = node;
- while (container &&
- (container.role == chrome.automation.RoleType.inlineTextBox ||
- container.role == chrome.automation.RoleType.staticText))
- container = container.parent();
+ createParseTree: function(inputStr) {
+ var root = {value: ''};
+ var currentNode = root;
+ var index = 0;
+ var braceNesting = 0;
+ while (index < inputStr.length) {
+ if (inputStr[index] == '(') {
+ currentNode.firstChild = {value: ''};
+ currentNode.firstChild.parent = currentNode;
+ currentNode = currentNode.firstChild;
+ } else if (inputStr[index] == ')') {
+ currentNode = currentNode.parent;
+ } else if (inputStr[index] == '{') {
+ braceNesting++;
+ currentNode.value += inputStr[index];
+ } else if (inputStr[index] == '}') {
+ braceNesting--;
+ currentNode.value += inputStr[index];
+ } else if (inputStr[index] == ',' && braceNesting === 0) {
+ currentNode.nextSibling = {value: ''};
+ currentNode.nextSibling.parent = currentNode.parent;
+ currentNode = currentNode.nextSibling;
+ } else {
+ currentNode.value += inputStr[index];
+ }
+ index++;
+ }
+
+ if (currentNode != root)
+ throw 'Unbalanced parenthesis.';
- var role = container ? container.role : node.role;
- return [node.attributes.name, node.attributes.value, role].join(', ');
+ return root;
}
};
+
+}); // goog.scope
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/output_test.extjs b/chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/output_test.extjs
new file mode 100644
index 00000000000..2f5caf455af
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/cvox2/background/output_test.extjs
@@ -0,0 +1,372 @@
+// 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.
+
+GEN_INCLUDE(['../../testing/assert_additions.js']);
+GEN_INCLUDE(['../../testing/chromevox_next_e2e_test_base.js']);
+
+/**
+ * Test fixture for output.js.
+ * @constructor
+ * @extends {ChromeVoxNextE2ETestBase}
+ */
+function OutputE2ETest() {
+ ChromeVoxNextE2ETest.call(this);
+}
+
+OutputE2ETest.prototype = {
+ __proto__: ChromeVoxNextE2ETest.prototype,
+
+ /** @override */
+ setUp: function() {
+ window.Dir = AutomationUtil.Dir;
+ }
+};
+
+TEST_F('OutputE2ETest', 'Links', function() {
+ this.runWithLoadedTree('<a href="#">Click here</a>',
+ function(root) {
+ var el = root.firstChild.firstChild;
+ var range = cursors.Range.fromNode(el);
+ var o = new Output().withSpeechAndBraille(range, null, 'navigate');
+ assertEqualsJSON({string_: 'Click hereLink', 'spans_': [
+ // Attributes.
+ {value: 'name', start: 0, end: 10},
+
+ // Link earcon (based on the name).
+ {value: {}, start: 0, end: 10},
+
+ {value: 'role', start: 10, end: 14}
+ ]}, o.toSpannable());
+ });
+});
+
+TEST_F('OutputE2ETest', 'Checkbox', function() {
+ this.runWithLoadedTree('<input type="checkbox">',
+ function(root) {
+ var el = root.firstChild.firstChild;
+ var range = cursors.Range.fromNode(el);
+ var o = new Output().withSpeechAndBraille(range, null, 'navigate');
+ assertEqualsJSON({string_: 'Check boxnot checked', 'spans_': [
+ // Attributes.
+ {value: 'name', start: 0, end: 0},
+ {value: 'role', start: 0, end: 9},
+ {value: 'state', start: 9, end: 20},
+
+ // Link earcon (based on the state).
+ {value: {}, start: 9, end: 20}
+ ]}, o.toSpannable());
+ });
+});
+
+TEST_F('OutputE2ETest', 'InLineTextBoxValueGetsIgnored', function() {
+ this.runWithLoadedTree('<p>OK',
+ function(root) {
+ var el = root.firstChild.firstChild.firstChild;
+ assertEquals('inlineTextBox', el.role);
+ var range = cursors.Range.fromNode(el);
+ var o = new Output().withSpeechAndBraille(range, null, 'navigate');
+ assertEqualsJSON({string_: 'OK', 'spans_': [
+ // Attributes.
+ {value: 'name', start: 0, end: 2}
+ ]}, o.toSpannable());
+
+ el = root.firstChild.firstChild;
+ assertEquals('staticText', el.role);
+ range = cursors.Range.fromNode(el);
+ o = new Output().withSpeechAndBraille(range, null, 'navigate');
+ assertEqualsJSON({string_: 'OK', 'spans_': [
+ // Attributes.
+ {value: 'value', start: 0, end: 2},
+
+ // The name is an empty string.
+ {value: 'name', start: 2, end: 2}
+ ]}, o.toSpannable());
+ });
+});
+
+TEST_F('OutputE2ETest', 'Headings', function() {
+ this.runWithLoadedTree(function() {/*!
+ <h1>a</h1><h2>b</h2><h3>c</h3><h4>d</h4><h5>e</h5><h6>f</h6>
+ <h1><a href="a.com">b</a></h1> */},
+ function(root) {
+ var el = root.firstChild;
+ var range = cursors.Range.fromNode(el);
+ var o = new Output().withSpeechAndBraille(range, null, 'navigate');
+ assertEqualsJSON({string_: 'Heading 1a', 'spans_': [
+ // Attributes.
+ {value: 'nameOrDescendants', start: 9, end: 10}
+ ]}, o.toSpannable());
+
+ el = el.nextSibling;
+ range = cursors.Range.fromNode(el);
+ o = new Output().withSpeechAndBraille(range, null, 'navigate');
+ assertEqualsJSON({string_: 'Heading 2b', 'spans_': [
+ // Attributes.
+ {value: 'nameOrDescendants', start: 9, end: 10}
+ ]}, o.toSpannable());
+
+ el = el.nextSibling;
+ range = cursors.Range.fromNode(el);
+ o = new Output().withSpeechAndBraille(range, null, 'navigate');
+ assertEqualsJSON({string_: 'Heading 3c', 'spans_': [
+ // Attributes.
+ {value: 'nameOrDescendants', start: 9, end: 10}
+ ]}, o.toSpannable());
+
+ el = el.nextSibling;
+ range = cursors.Range.fromNode(el);
+ o = new Output().withSpeechAndBraille(range, null, 'navigate');
+ assertEqualsJSON({string_: 'Heading 4d', 'spans_': [
+ // Attributes.
+ {value: 'nameOrDescendants', start: 9, end: 10}
+ ]}, o.toSpannable());
+
+ el = el.nextSibling;
+ range = cursors.Range.fromNode(el);
+ o = new Output().withSpeechAndBraille(range, null, 'navigate');
+ assertEqualsJSON({string_: 'Heading 5e', 'spans_': [
+ // Attributes.
+ {value: 'nameOrDescendants', start: 9, end: 10}
+ ]}, o.toSpannable());
+
+ el = el.nextSibling;
+ range = cursors.Range.fromNode(el);
+ o = new Output().withSpeechAndBraille(range, null, 'navigate');
+ assertEqualsJSON({string_: 'Heading 6f', 'spans_': [
+ // Attributes.
+ {value: 'nameOrDescendants', start: 9, end: 10}
+ ]}, o.toSpannable());
+
+ el = el.nextSibling;
+ range = cursors.Range.fromNode(el);
+ o = new Output().withSpeechAndBraille(range, null, 'navigate');
+ assertEqualsJSON({string_: 'Heading 1|b|Link', 'spans_': [
+ // Link.
+ {value: 'name', start: 10, end: 11},
+ {value: {}, start: 10, end: 11},
+ {value: 'role', start: 12, end: 16}
+ ]}, o.toSpannableForTest());
+ });
+});
+
+TEST_F('OutputE2ETest', 'Audio', function() {
+ this.runWithLoadedTree('<audio src="foo.mp3" controls></audio>',
+ function(root) {
+ var el = root.firstChild.firstChild.firstChild.firstChild;
+ var range = cursors.Range.fromNode(el);
+ var o = new Output().withSpeechAndBraille(range, null, 'navigate');
+ assertEqualsJSON(
+ {string_: 'media control|Tool bar|||play|begin playback|Button',
+ spans_:
+ // Entered container toolbar.
+
+ // Button.
+ [{value: 'name', start: 23, end: 23},
+
+ // Button earcon.
+ {value: {}, start: 23, end: 23},
+
+ {value: 'value', start: 24, end: 24},
+ {value: 'description', start: 25, end: 29},
+ {value: 'help', start: 30, end: 44},
+ {value: 'role', start: 45, end: 51}]
+ }, o.toSpannableForTest());
+
+ el = el.nextSibling;
+ var prevRange = range;
+ range = cursors.Range.fromNode(el);
+ var o = new Output().withSpeechAndBraille(range, prevRange, 'navigate');
+ assertEqualsJSON({string_: '0, , slider|audio time scrubber',
+ spans_:
+ [{value: 'help', start: 12, end: 31}]
+ }, o.toSpannableForTest());
+ });
+});
+
+TEST_F('OutputE2ETest', 'Input', function() {
+ this.runWithLoadedTree(
+ '<input type="text"></input>' +
+ '<input type="email"></input>' +
+ '<input type="password"></input>' +
+ '<input type="tel"></input>' +
+ '<input type="number"></input>' +
+ '<input type="time"></input>' +
+ '<input type="date"></input>',
+ function(root) {
+ var expected = {string_: '', 'spans_': [
+ {value: 'name', start: 0, end: 0},
+
+ // Earcon
+ {value: {}, start: 0, end: 0},
+
+ // Selection span.
+ {value: {startIndex: 0, endIndex: 0}, start: 1, end: 1},
+
+ {value: 'value', start: 1, end: 1}
+ ]};
+
+ var expectedValues = [
+ '||Edit text',
+ '||Edit text, email entry',
+ '||Password edit text',
+ '||Edit text, number entry',
+ {string_: '||Spin button', spans_: [{value: 'name', start: 0, end: 0},
+ {value: {}, start: 0, end: 0},
+ {value: {startIndex: 0, endIndex: 0}, start: 1, end: 1},
+ {value: 'value', start: 1, end: 1},
+ {value: 'role', start: 2, end: 13}]},
+ {string_: '||Time', spans_: [{value: 'name', start: 0, end: 0},
+ {value: 'value', start: 1, end: 1},
+ {value: 'role', start: 2, end: 6}]},
+ {string_: '||Date control', spans_: [{value: 'name', start: 0, end: 0},
+ {value: 'value', start: 1, end: 1},
+ {value: 'role', start: 2, end: 14}]}
+ ];
+
+ var el = root.firstChild.firstChild;
+ expectedValues.forEach(function(expectedValue) {
+ var range = cursors.Range.fromNode(el);
+ var o = new Output().withSpeechAndBraille(range, null, 'navigate');
+ if (typeof expectedValue == 'object') {
+ assertEqualsJSON(expectedValue, o.toSpannableForTest());
+ } else {
+ expected.string_ = expectedValue;
+ assertEqualsJSON(expected, o.toSpannableForTest());
+ }
+ el = el.nextSibling;
+ });
+ });
+});
+
+TEST_F('OutputE2ETest', 'List', function() {
+ this.runWithLoadedTree(
+ '<ul><li>a<li>b<li>c</ul>',
+ function(root) {
+ var el = root.firstChild.firstChild;
+ var range = cursors.Range.fromNode(el);
+ var o = new Output().withSpeechAndBraille(range, null, 'navigate');
+ assertEqualsJSON({string_: 'list|with 3 items|a||List item', spans_: [
+ {value: 'name', start: 18, end: 19},
+ {value: {}, start: 18, end: 19},
+ {value: 'value', start:20, end: 20},
+ {value: 'role', start: 21, end: 30}
+ ]}, o.toSpannableForTest());
+ });
+});
+
+TEST_F('OutputE2ETest', 'Tree', function() {
+ this.runWithLoadedTree(function() {/*!
+ <ul role="tree">
+ <li aria-expanded="true" role="treeitem">a
+ <li role="treeitem">b
+ <li aria-expanded="false" role="treeitem">c
+ </ul>
+ */},
+ function(root) {
+ var el = root.firstChild.children[0].firstChild;
+ var range = cursors.Range.fromNode(el);
+ var o = new Output().withSpeechAndBraille(range, null, 'navigate');
+ assertEqualsJSON(
+ {string_: '|Tree|with 3 items|Tree item|Expanded| 1 of 3 | level 1 |a|',
+ spans_: [
+ // Enter rule.
+
+ // TreeItem.
+ {value: 'role','start': 19, end: 28},
+ {value: 'state', start: 29, end: 37},
+
+ // InLineTextBox.
+ {value: 'value', start: 57, end: 58},
+ {value: 'name', start: 59, end: 59}
+ ]}, o.toSpannableForTest());
+
+ el = root.firstChild.children[1].firstChild;
+ var range = cursors.Range.fromNode(el);
+ var o = new Output().withSpeechAndBraille(range, null, 'navigate');
+ assertEqualsJSON(
+ {string_: '|Tree|with 3 items|Tree item| 2 of 3 | level 1 |b|',
+ spans_: [
+ // Enter rule.
+
+ // TreeItem.
+ {value: 'role','start': 19, end: 28},
+
+ // InLineTextBox.
+ {value: 'value', start: 48, end: 49},
+ {value: 'name', start: 50, end: 50}
+ ]}, o.toSpannableForTest());
+
+ el = root.firstChild.children[2].firstChild;
+ var range = cursors.Range.fromNode(el);
+ var o = new Output().withSpeechAndBraille(range, null, 'navigate');
+ assertEqualsJSON(
+ {string_: '|Tree|with 3 items|Tree item|Collapsed| 3 of 3 | level 1 |c|',
+ spans_: [
+ // Enter rule.
+
+ // TreeItem.
+ {value: 'role','start': 19, end: 28},
+ {value: 'state', start: 29, end: 38},
+
+ // InLineTextBox.
+ {value: 'value', start: 58, end: 59},
+ {value: 'name', start: 60, end: 60}
+ ]}, o.toSpannableForTest());
+ });
+});
+
+TEST_F('OutputE2ETest', 'Menu', function() {
+ this.runWithLoadedTree(function() {/*!
+ <div role="menu">
+ <div role="menuitem">a</div>
+ </div>
+ */},
+ function(root) {
+ var el = root.firstChild.firstChild;
+ var range = cursors.Range.fromNode(el);
+ var o = new Output().withSpeechAndBraille(range, null, 'navigate');
+ assertEqualsJSON({string_:
+ '|Menu|with 1 items|a, menu item| 1 of 1 ', spans_: [
+ {value: 'name', start: 0, end: 0},
+ {value: 'role', start:1, end: 5}
+ ]}, o.toSpannableForTest());
+ });
+});
+
+TEST_F('OutputE2ETest', 'ListBox', function() {
+ this.runWithLoadedTree(function() {/*!
+ <select multiple>
+ <option>1</option>
+ <option>2</option>
+ </select>
+ */},
+ function(root) {
+ var el = root.firstChild.firstChild.firstChild;
+ var range = cursors.Range.fromNode(el);
+ var o = new Output().withSpeechAndBraille(range, null, 'navigate');
+ assertEqualsJSON({string_: '|List box|with 2 items||List item| 1 of 2 ',
+ spans_: [
+ // ListBox.
+ // Earcon.
+ {value: {}, start: 0, end: 0},
+
+ {value: 'name', start: 23, end: 23},
+
+ // Earcon.
+ {value: {}, start: 23, end: 23},
+
+ {value: 'role', start:24, end: 33}
+ ]}, o.toSpannableForTest());
+ });
+});
+
+SYNC_TEST_F('OutputE2ETest', 'MessageIdValidity', function() {
+ for (var key in Output.ROLE_INFO_) {
+ var value = Output.ROLE_INFO_[key];
+ cvox.ChromeVox.msgs.getMsg(value.msgId);
+ assertFalse(/[A-Z]+/.test(value.msgId));
+ if (value.earconId)
+ assertNotNullNorUndefined(cvox.AbstractEarcons[value.earconId]);
+ }
+});
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/extensions/searchvox/search.js b/chromium/chrome/browser/resources/chromeos/chromevox/extensions/searchvox/search.js
index 4b53144adee..ad7feb0d573 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/extensions/searchvox/search.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/extensions/searchvox/search.js
@@ -23,7 +23,7 @@ cvox.Search = function() {
/**
* Selectors to match results.
- * @type {Object.<string, string>}
+ * @type {Object<string, string>}
*/
cvox.Search.selectors = {};
@@ -56,13 +56,13 @@ cvox.Search.index;
/**
* Array of the search results.
- * @type {Array.<Element>}
+ * @type {Array<Element>}
*/
cvox.Search.results = [];
/**
* Array of the navigation panes.
- * @type {Array.<Element>}
+ * @type {Array<Element>}
*/
cvox.Search.panes = [];
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/extensions/searchvox/search_tools.js b/chromium/chrome/browser/resources/chromeos/chromevox/extensions/searchvox/search_tools.js
index 8ed3bdb151c..28b94a67272 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/extensions/searchvox/search_tools.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/extensions/searchvox/search_tools.js
@@ -29,7 +29,7 @@ cvox.SearchTool.menuIndex;
/**
* Array of menus.
- * @type {Array.<Node>}
+ * @type {Array<Node>}
*/
cvox.SearchTool.menus = [];
@@ -41,7 +41,7 @@ cvox.SearchTool.menuItemIndex;
/**
* Array of menu items for the current menu.
- * @type {Array.<Node>}
+ * @type {Array<Node>}
*/
cvox.SearchTool.menuItems = [];
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/generate_manifest.gypi b/chromium/chrome/browser/resources/chromeos/chromevox/generate_manifest.gypi
index de83b64ee5c..ab5cd4e04ba 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/generate_manifest.gypi
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/generate_manifest.gypi
@@ -13,14 +13,14 @@
#
# is_guest_manifest: 1 or 0; generates a manifest usable while in guest
# mode.
-# is_chromevox_next: 1 or 0; generates a manifest for ChromeVox Next.
+# is_chromevox_classic: 1 or 0; generates a manifest for ChromeVox Classic.
# chromevox_compress_js: 1 or 0; whether the javascript is compressed.
{
'variables': {
'generate_manifest_script_path': 'tools/generate_manifest.py',
'is_guest_manifest%': 0,
- 'is_chromevox_next%': 0,
+ 'is_chromevox_classic%': 0,
'key': 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDltVl1k15pjRzuZfMc3B69inxwm2bZeZ2O8/zFO+NluHnBm3GJ3fzdOoFGJd+M16I8p7zxxQyHeDMfWYASyCeB8XnUEDKjqNLQfCnncsANzHsYoEbYj2nEUML2P13b9q+AAvpCBpAJ4cZp81e9n1y/vbSXHE4385cgkKueItzikQIDAQAB',
},
'includes': [
@@ -42,7 +42,7 @@
'<(generate_manifest_script_path)',
'--is_guest_manifest=<(is_guest_manifest)',
'--key=<(key)',
- '--is_chromevox_next=<(is_chromevox_next)',
+ '--is_chromevox_classic=<(is_chromevox_classic)',
'--is_js_compressed=<(chromevox_compress_js)',
'--set_version=<(version_full)',
'--output_manifest=<(output_manifest_path)',
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/braille.js b/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/braille.js
index 1b202ade3c2..7683ae8adc8 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/braille.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/braille.js
@@ -49,7 +49,7 @@ cvox.ChromeBraille = function() {
// within the iframe even though the containing document also has focus.
// Don't process the event if this document isn't focused or focus lies in
// a descendant.
- if (!document.hasFocus() || document.activeElement.tagName == 'IFRAME') {
+ if (!cvox.ChromeVox.documentHasFocus()) {
return;
}
if (msg['message'] == 'BRAILLE' && msg['args']) {
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/braille_background.js b/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/braille_background.js
index 9c497115524..9183a7b12fa 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/braille_background.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/braille_background.js
@@ -12,9 +12,7 @@ goog.require('cvox.AbstractBraille');
goog.require('cvox.BrailleDisplayManager');
goog.require('cvox.BrailleInputHandler');
goog.require('cvox.BrailleKeyEvent');
-goog.require('cvox.BrailleTable');
-goog.require('cvox.ChromeVox');
-goog.require('cvox.LibLouis');
+goog.require('cvox.BrailleTranslatorManager');
/**
@@ -23,27 +21,26 @@ goog.require('cvox.LibLouis');
* Display manager (for mocking in tests).
* @param {cvox.BrailleInputHandler=} opt_inputHandlerForTest Input handler
* (for mocking in tests).
+ * @param {cvox.BrailleTranslatorManager=} opt_translatorManagerForTest
+ * Braille translator manager (for mocking in tests)
* @extends {cvox.AbstractBraille}
*/
cvox.BrailleBackground = function(opt_displayManagerForTest,
- opt_inputHandlerForTest) {
+ opt_inputHandlerForTest,
+ opt_translatorManagerForTest) {
goog.base(this);
/**
+ * @type {!cvox.BrailleTranslatorManager}
+ * @private*/
+ this.translatorManager_ = opt_translatorManagerForTest ||
+ new cvox.BrailleTranslatorManager();
+ /**
* @type {cvox.BrailleDisplayManager}
* @private
*/
this.displayManager_ = opt_displayManagerForTest ||
- new cvox.BrailleDisplayManager();
- cvox.BrailleTable.getAll(goog.bind(function(tables) {
- /**
- * @type {!Array.<cvox.BrailleTable.Table>}
- * @private
- */
- this.tables_ = tables;
- this.initialize_(0);
- }, this));
- this.displayManager_.setCommandListener(
- goog.bind(this.onBrailleKeyEvent_, this));
+ new cvox.BrailleDisplayManager(this.translatorManager_);
+ this.displayManager_.setCommandListener(this.onBrailleKeyEvent_.bind(this));
/**
* @type {cvox.NavBraille}
* @private
@@ -59,7 +56,7 @@ cvox.BrailleBackground = function(opt_displayManagerForTest,
* @private
*/
this.inputHandler_ = opt_inputHandlerForTest ||
- new cvox.BrailleInputHandler();
+ new cvox.BrailleInputHandler(this.translatorManager_);
this.inputHandler_.init();
};
goog.inherits(cvox.BrailleBackground, cvox.AbstractBraille);
@@ -83,83 +80,11 @@ cvox.BrailleBackground.prototype.setCommandListener = function(func) {
/**
- * Refreshes the braille translator used for output. This should be
- * called when something changed (such as a preference) to make sure that
- * the correct translator is used.
+ * @return {cvox.BrailleTranslatorManager} The translator manager used by this
+ * instance.
*/
-cvox.BrailleBackground.prototype.refreshTranslator = function() {
- if (!this.liblouis_) {
- return;
- }
- // First, see if we have a braille table set previously.
- var tables = this.tables_;
- var table = cvox.BrailleTable.forId(tables, localStorage['brailleTable']);
- if (!table) {
- // Match table against current locale.
- var currentLocale = chrome.i18n.getMessage('@@ui_locale').split(/[_-]/);
- var major = currentLocale[0];
- var minor = currentLocale[1];
- var firstPass = tables.filter(function(table) {
- return table.locale.split(/[_-]/)[0] == major;
- });
- if (firstPass.length > 0) {
- table = firstPass[0];
- if (minor) {
- var secondPass = firstPass.filter(function(table) {
- return table.locale.split(/[_-]/)[1] == minor;
- });
- if (secondPass.length > 0) {
- table = secondPass[0];
- }
- }
- }
- }
- if (!table) {
- table = cvox.BrailleTable.forId(tables, 'en-US-comp8');
- }
- // TODO(plundblad): ONly update when user explicitly chooses a table
- // so that switching locales changes table by default.
- localStorage['brailleTable'] = table.id;
- if (table.dots == '6') {
- localStorage['brailleTableType'] = 'brailleTable6';
- localStorage['brailleTable6'] = table.id;
- } else {
- localStorage['brailleTableType'] = 'brailleTable8';
- localStorage['brailleTable8'] = table.id;
- }
-
- // Initialize all other defaults.
- // TODO(plundblad): Stop doing this here.
- if (!localStorage['brailleTable6']) {
- localStorage['brailleTable6'] = 'en-US-g1';
- }
- if (!localStorage['brailleTable8']) {
- localStorage['brailleTable8'] = 'en-US-comp8';
- }
-
- // If the user explicitly set an 8 dot table, use that when looking
- // for an uncontracted table. Otherwise, use the current table and let
- // getUncontracted find an appropriate corresponding table.
- var table8Dot = cvox.BrailleTable.forId(tables,
- localStorage['brailleTable8']);
- var uncontractedTable = cvox.BrailleTable.getUncontracted(
- tables,
- table8Dot ? table8Dot : table);
- this.liblouis_.getTranslator(table.fileNames, goog.bind(
- function(translator) {
- if (uncontractedTable.id === table.id) {
- this.displayManager_.setTranslator(translator);
- this.inputHandler_.setTranslator(translator);
- } else {
- this.liblouis_.getTranslator(uncontractedTable.fileNames, goog.bind(
- function(uncontractedTranslator) {
- this.displayManager_.setTranslator(
- translator, uncontractedTranslator);
- this.inputHandler_.setTranslator(
- translator, uncontractedTranslator);
- }, this));
- }
- }, this));
+cvox.BrailleBackground.prototype.getTranslatorManager = function() {
+ return this.translatorManager_;
};
@@ -179,45 +104,6 @@ cvox.BrailleBackground.prototype.onBrailleMessage = function(msg) {
/**
- * @return {cvox.LibLouis} The liblouis instance used by this object, or null
- * if not initialized yet.
- */
-cvox.BrailleBackground.prototype.getLibLouisForTest = function() {
- return this.liblouis_;
-};
-
-
-/**
- * Initialization to be done after part of the background page's DOM has been
- * constructed. Currently only used on ChromeOS.
- * @param {number} retries Number of retries.
- * @private
- */
-cvox.BrailleBackground.prototype.initialize_ = function(retries) {
- if (retries > 5) {
- console.error(
- 'Timeout waiting for document.body; not initializing braille.');
- return;
- }
- if (!document.body) {
- window.setTimeout(goog.bind(this.initialize_, this, ++retries), 500);
- } else {
- /**
- * @type {cvox.LibLouis}
- * @private
- */
- this.liblouis_ = new cvox.LibLouis(
- chrome.extension.getURL(
- 'chromevox/background/braille/liblouis_nacl.nmf'),
- chrome.extension.getURL(
- 'chromevox/background/braille/tables'));
- this.liblouis_.attachToElement(document.body);
- this.refreshTranslator();
- }
-};
-
-
-/**
* Handles braille key events by dispatching either to the input handler or
* a content script.
* @param {!cvox.BrailleKeyEvent} brailleEvt The event.
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/braille_input_handler.js b/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/braille_input_handler.js
deleted file mode 100644
index cb1f2c6a115..00000000000
--- a/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/braille_input_handler.js
+++ /dev/null
@@ -1,523 +0,0 @@
-// 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 Handles braille input keys when the user is typing or editing
- * text in an input field. This class cooperates with the Braille IME
- * that is built into Chrome OS to do the actual text editing.
- */
-
-goog.provide('cvox.BrailleInputHandler');
-
-goog.require('cvox.BrailleKeyCommand');
-goog.require('cvox.BrailleKeyEvent');
-goog.require('cvox.ExpandingBrailleTranslator');
-
-
-/**
- * @constructor
- */
-cvox.BrailleInputHandler = function() {
- /**
- * Port of the connected IME if any.
- * @type {Port}
- * @private
- */
- this.imePort_ = null;
- /**
- * {code true} when the Braille IME is connected and has signaled that it is
- * active.
- * @type {boolean}
- * @private
- */
- this.imeActive_ = false;
- /**
- * The input context of the current input field, as reported by the IME.
- * {@code null} if no input field has focus.
- * @type {{contextID: number, type: string}?}
- * @private
- */
- this.inputContext_ = null;
- /**
- * @type {cvox.LibLouis.Translator}
- * @private
- */
- this.defaultTranslator_ = null;
- /**
- * @type {cvox.LibLouis.Translator}
- * @private
- */
- this.uncontractedTranslator_ = null;
- /**
- * The translator currently used for typing, if
- * {@code this.cells_.length > 0}.
- * @type {cvox.LibLouis.Translator}
- * @private
- */
- this.activeTranslator_ = null;
- /**
- * Braille cells that have been typed by the user so far.
- * @type {Array.<number>}
- * @private
- */
- this.cells_ = [];
- /**
- * Text resulting from translating {@code this.cells_}.
- * @type {string}
- * @private
- */
- this.text_ = '';
- /**
- * Text that currently precedes the first selection end-point.
- * @type {string}
- * @private
- */
- this.currentTextBefore_ = '';
- /**
- * Text that currently follows the last selection end-point.
- * @type {string}
- * @private
- */
- this.currentTextAfter_ = '';
- /**
- * List of strings that we expect to be set as preceding text of the
- * selection. This is populated when we send text changes to the IME so that
- * our own changes don't reset the pending cells.
- * @type {Array.<string>}
- * @private
- */
- this.pendingTextsBefore_ = [];
- /**
- * Cells that were entered while the IME wasn't active. These will be
- * submitted once the IME becomes active and reports the current input field.
- * This is necessary because the IME is activated on the first braille
- * dots command, but we'll receive the command in parallel. To work around
- * the race, we store the cell entered until we can submit it to the IME.
- * @type {Array.<number>}
- * @private
- */
- this.pendingCells_ = [];
-};
-
-
-/**
- * The ID of the Braille IME extension built into Chrome OS.
- * @const {string}
- * @private
- */
-cvox.BrailleInputHandler.IME_EXTENSION_ID_ =
- 'jddehjeebkoimngcbdkaahpobgicbffp';
-
-
-/**
- * Name of the port to use for communicating with the Braille IME.
- * @const {string}
- * @private
- */
-cvox.BrailleInputHandler.IME_PORT_NAME_ = 'cvox.BrailleIme.Port';
-
-
-/**
- * Starts to listen for connections from the ChromeOS braille IME.
- */
-cvox.BrailleInputHandler.prototype.init = function() {
- chrome.runtime.onConnectExternal.addListener(
- goog.bind(this.onImeConnect_, this));
-};
-
-
-/**
- * Sets the translator(s) to be used for input.
- * @param {cvox.LibLouis.Translator} defaultTranslator Translator to use by
- * default from now on.
- * @param {cvox.LibLouis.Translator=} opt_uncontractedTranslator Translator
- * to be used inside a word (non-whitespace).
- */
-cvox.BrailleInputHandler.prototype.setTranslator = function(
- defaultTranslator, opt_uncontractedTranslator) {
- this.defaultTranslator_ = defaultTranslator;
- this.uncontractedTranslator_ = opt_uncontractedTranslator || null;
- this.resetText_();
-};
-
-
-/**
- * Called when the content on the braille display is updated. Modifies the
- * input state according to the new content.
- * @param {cvox.Spannable} text Text, optionally with value and selection
- * spans.
- */
-cvox.BrailleInputHandler.prototype.onDisplayContentChanged = function(text) {
- var valueSpan = text.getSpanInstanceOf(cvox.BrailleUtil.ValueSpan);
- var selectionSpan = text.getSpanInstanceOf(
- cvox.BrailleUtil.ValueSelectionSpan);
- if (!(valueSpan && selectionSpan)) {
- return;
- }
- // The type casts are ok because the spans are known to exist.
- var valueStart = /** @type {number} */ (text.getSpanStart(valueSpan));
- var valueEnd = /** @type {number} */ (text.getSpanEnd(valueSpan));
- var selectionStart =
- /** @type {number} */ (text.getSpanStart(selectionSpan));
- var selectionEnd = /** @type {number} */ (text.getSpanEnd(selectionSpan));
- if (selectionStart < valueStart || selectionEnd > valueEnd) {
- console.error('Selection outside of value in braille content');
- this.resetText_();
- return;
- }
- var oldTextBefore = this.currentTextBefore_;
- var oldTextAfter = this.currentTextAfter_;
- this.currentTextBefore_ = text.toString().substring(
- valueStart, selectionStart);
- this.currentTextAfter_ = text.toString().substring(selectionEnd, valueEnd);
- if (this.cells_.length > 0) {
- // Ignore this change if the preceding text hasn't changed.
- if (oldTextBefore === this.currentTextBefore_) {
- return;
- }
- // See if we are expecting this change as a result of one of our own edits.
- if (this.pendingTextsBefore_.length > 0) {
- // Allow changes to be coalesced by the input system in an attempt to not
- // be too brittle.
- for (var i = 0; i < this.pendingTextsBefore_.length; ++i) {
- if (this.currentTextBefore_ === this.pendingTextsBefore_[i]) {
- // Delete all previous expected changes and ignore this one.
- this.pendingTextsBefore_.splice(0, i + 1);
- return;
- }
- }
- }
- // There was an actual text change (or cursor movement) that we hadn't
- // caused ourselves, reset any pending input.
- this.resetText_();
- } else {
- this.updateActiveTranslator_();
- }
-};
-
-
-/**
- * Handles braille key events used for input by editing the current input field
- * appropriately.
- * @param {!cvox.BrailleKeyEvent} event The key event.
- * @return {boolean} {@code true} if the event was handled, {@code false}
- * if it should propagate further.
- */
-cvox.BrailleInputHandler.prototype.onBrailleKeyEvent = function(event) {
- if (event.command === cvox.BrailleKeyCommand.DOTS) {
- return this.onBrailleDots_(/** @type {number} */(event.brailleDots));
- }
- // Any other braille command cancels the pending cells.
- this.pendingCells_.length = 0;
- if (event.command === cvox.BrailleKeyCommand.STANDARD_KEY) {
- if (event.standardKeyCode === 'Backspace' &&
- !event.altKey && !event.ctrlKey && !event.shiftKey &&
- this.onBackspace_()) {
- return true;
- } else {
- this.sendKeyEventPair_(event);
- return true;
- }
- }
- return false;
-};
-
-
-/**
- * Returns how the value of the currently displayed content should be expanded
- * given the current input state.
- * @return {cvox.ExpandingBrailleTranslator.ExpansionType}
- * The current expansion type.
- */
-cvox.BrailleInputHandler.prototype.getExpansionType = function() {
- if (this.inAlwaysUncontractedContext_()) {
- return cvox.ExpandingBrailleTranslator.ExpansionType.ALL;
- }
- if (this.cells_.length > 0 &&
- this.activeTranslator_ === this.defaultTranslator_) {
- return cvox.ExpandingBrailleTranslator.ExpansionType.NONE;
- }
- return cvox.ExpandingBrailleTranslator.ExpansionType.SELECTION;
-};
-
-
-/**
- * @return {boolean} {@code true} if we have an input context and uncontracted
- * braille should always be used for that context.
- * @private
- */
-cvox.BrailleInputHandler.prototype.inAlwaysUncontractedContext_ = function() {
- if (this.inputContext_) {
- var inputType = this.inputContext_.type;
- return inputType === 'url' || inputType === 'email';
- }
- return false;
-};
-
-
-/**
- * Called when a user typed a braille cell.
- * @param {number} dots The dot pattern of the cell.
- * @return {boolean} Whether the event was handled or should be allowed to
- * propagate further.
- * @private
- */
-cvox.BrailleInputHandler.prototype.onBrailleDots_ = function(dots) {
- if (!this.imeActive_) {
- this.pendingCells_.push(dots);
- return true;
- }
- if (!this.inputContext_ || !this.activeTranslator_) {
- return false;
- }
- // Avoid accumulating cells forever when typing without moving the cursor
- // by flushing the input when we see a blank cell.
- // Note that this might switch to contracted if appropriate.
- if (this.cells_.length > 0 && this.cells_[this.cells_.length - 1] == 0) {
- this.resetText_();
- }
- this.cells_.push(dots);
- this.updateText_();
- return true;
-};
-
-
-/**
- * Handles the backspace key by deleting the last typed cell if possible.
- * @return {boolean} {@code true} if the event was handled, {@code false}
- * if it wasn't and should propagate further.
- * @private
- */
-cvox.BrailleInputHandler.prototype.onBackspace_ = function() {
- if (this.imeActive_ && this.cells_.length > 0) {
- --this.cells_.length;
- this.updateText_();
- return true;
- }
- return false;
-};
-
-
-/**
- * Updates the translated text based on the current cells and sends the
- * delta to the IME.
- * @private
- */
-cvox.BrailleInputHandler.prototype.updateText_ = function() {
- var cellsBuffer = new Uint8Array(this.cells_).buffer;
- this.activeTranslator_.backTranslate(cellsBuffer, goog.bind(function(result) {
- if (result === null) {
- console.error('Error when backtranslating braille cells');
- return;
- }
- var oldLength = this.text_.length;
- // Find the common prefix of the old and new text.
- var commonPrefixLength = this.longestCommonPrefixLength_(
- this.text_, result);
- this.text_ = result;
- // How many characters we need to delete from the existing text to replace
- // them with characters from the new text.
- var deleteLength = oldLength - commonPrefixLength;
- // New text, if any, to insert after deleting the deleteLength characters
- // before the cursor.
- var toInsert = result.substring(commonPrefixLength);
- if (deleteLength > 0 || toInsert.length > 0) {
- // After deleting, we expect this text to be present before the cursor.
- var textBeforeAfterDelete = this.currentTextBefore_.substring(
- 0, this.currentTextBefore_.length - deleteLength);
- if (deleteLength > 0) {
- // Queue this text up to be ignored when the change comes in.
- this.pendingTextsBefore_.push(textBeforeAfterDelete);
- }
- if (toInsert.length > 0) {
- // Likewise, queue up what we expect to be before the cursor after
- // the replacement text is inserted.
- this.pendingTextsBefore_.push(textBeforeAfterDelete + toInsert);
- }
- // Send the replace operation to be performed asynchronously by
- // the IME.
- this.postImeMessage_({type: 'replaceText',
- contextID: this.inputContext_.contextID,
- deleteBefore: deleteLength,
- newText: toInsert});
- }
- }, this));
-};
-
-
-/**
- * Resets the pending braille input and text.
- * @private
- */
-cvox.BrailleInputHandler.prototype.resetText_ = function() {
- this.cells_.length = 0;
- this.text_ = '';
- this.pendingTextsBefore_.length = 0;
- this.updateActiveTranslator_();
-};
-
-
-/**
- * Updates the active translator based on the current input context.
- * @private
- */
-cvox.BrailleInputHandler.prototype.updateActiveTranslator_ = function() {
- this.activeTranslator_ = this.defaultTranslator_;
- if (this.uncontractedTranslator_) {
- var textBefore = this.currentTextBefore_;
- var textAfter = this.currentTextAfter_;
- if (this.inAlwaysUncontractedContext_() ||
- (textBefore.length > 0 && /\S$/.test(textBefore)) ||
- (textAfter.length > 0 && /^\S/.test(textAfter))) {
- this.activeTranslator_ = this.uncontractedTranslator_;
- }
- }
-};
-
-
-/**
- * Called when another extension connects to this extension. Accepts
- * connections from the ChromeOS builtin Braille IME and ignores connections
- * from other extensions.
- * @param {Port} port The port used to communicate with the other extension.
- * @private
- */
-cvox.BrailleInputHandler.prototype.onImeConnect_ = function(port) {
- if (port.name !== cvox.BrailleInputHandler.IME_PORT_NAME_ ||
- port.sender.id !== cvox.BrailleInputHandler.IME_EXTENSION_ID_) {
- return;
- }
- if (this.imePort_) {
- this.imePort_.disconnect();
- }
- port.onDisconnect.addListener(goog.bind(this.onImeDisconnect_, this, port));
- port.onMessage.addListener(goog.bind(this.onImeMessage_, this));
- this.imePort_ = port;
-};
-
-
-/**
- * Called when a message is received from the IME.
- * @param {*} message The message.
- * @private
- */
-cvox.BrailleInputHandler.prototype.onImeMessage_ = function(message) {
- if (!goog.isObject(message)) {
- console.error('Unexpected message from Braille IME: ',
- JSON.stringify(message));
- }
- switch (message.type) {
- case 'activeState':
- this.imeActive_ = message.active;
- break;
- case 'inputContext':
- this.inputContext_ = message.context;
- this.resetText_();
- if (this.imeActive_ && this.inputContext_) {
- this.pendingCells_.forEach(goog.bind(this.onBrailleDots_, this));
- }
- this.pendingCells_.length = 0;
- break;
- case 'brailleDots':
- this.onBrailleDots_(message['dots']);
- break;
- case 'backspace':
- // Note that we can't send the backspace key through the
- // virtualKeyboardPrivate API in this case because it would then be
- // processed by the IME again, leading to an infinite loop.
- this.postImeMessage_(
- {type: 'keyEventHandled', requestId: message['requestId'],
- result: this.onBackspace_()});
- break;
- case 'reset':
- this.resetText_();
- break;
- default:
- console.error('Unexpected message from Braille IME: ',
- JSON.stringify(message));
- break;
- }
-};
-
-
-/**
- * Called when the IME port is disconnected.
- * @param {Port} port The port that was disconnected.
- * @private
- */
-cvox.BrailleInputHandler.prototype.onImeDisconnect_ = function(port) {
- this.imePort_ = null;
- this.resetText_();
- this.imeActive_ = false;
- this.inputContext_ = null;
-};
-
-
-/**
- * Posts a message to the IME.
- * @param {Object} message The message.
- * @return {boolean} {@code true} if the message was sent, {@code false} if
- * there was no connection open to the IME.
- * @private
- */
-cvox.BrailleInputHandler.prototype.postImeMessage_ = function(message) {
- if (this.imePort_) {
- this.imePort_.postMessage(message);
- return true;
- }
- return false;
-};
-
-
-/**
- * Sends a {@code keydown} key event followed by a {@code keyup} event
- * corresponding to an event generated by the braille display.
- * @param {!cvox.BrailleKeyEvent} event The braille key event to base the
- * key events on.
- * @private
- */
-cvox.BrailleInputHandler.prototype.sendKeyEventPair_ = function(event) {
- // Use the virtual keyboard API instead of the IME key event API
- // so that these keys work even if the Braille IME is not active.
- var keyName = /** @type {string} */ (event.standardKeyCode);
- var numericCode = cvox.BrailleKeyEvent.keyCodeToLegacyCode(keyName);
- if (!goog.isDef(numericCode)) {
- throw Error('Unknown key code in event: ' + JSON.stringify(event));
- }
- var keyEvent = {
- type: 'keydown',
- keyCode: numericCode,
- keyName: keyName,
- charValue: cvox.BrailleKeyEvent.keyCodeToCharValue(keyName),
- // See chrome/common/extensions/api/virtual_keyboard_private.json for
- // these constants.
- modifiers: (event.shiftKey ? 2 : 0) |
- (event.ctrlKey ? 4 : 0) |
- (event.altKey ? 8 : 0)
- };
- chrome.virtualKeyboardPrivate.sendKeyEvent(keyEvent);
- keyEvent.type = 'keyup';
- chrome.virtualKeyboardPrivate.sendKeyEvent(keyEvent);
-};
-
-
-/**
- * Returns the length of the longest common prefix of two strings.
- * @param {string} first The first string.
- * @param {string} second The second string.
- * @return {number} The longest common prefix, which may be 0 for an
- * empty common prefix.
- * @private
- */
-cvox.BrailleInputHandler.prototype.longestCommonPrefixLength_ = function(
- first, second) {
- var limit = Math.min(first.length, second.length);
- var i;
- for (i = 0; i < limit; ++i) {
- if (first.charAt(i) != second.charAt(i)) {
- break;
- }
- }
- return i;
-};
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/braille_integration_test.unitjs b/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/braille_integration_test.unitjs
index 4fbe3dbd1f7..ce91f82b298 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/braille_integration_test.unitjs
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/braille_integration_test.unitjs
@@ -14,7 +14,9 @@ GEN_INCLUDE(['../../testing/chromevox_unittest_base.js',
* @constructor
* @extends {ChromeVoxUnitTestBase}
*/
-function CvoxBrailleIntegrationUnitTest() {}
+function CvoxBrailleIntegrationUnitTest() {
+ ChromeVoxUnitTestBase.call(this);
+}
CvoxBrailleIntegrationUnitTest.prototype = {
__proto__: ChromeVoxUnitTestBase.prototype,
@@ -24,19 +26,23 @@ CvoxBrailleIntegrationUnitTest.prototype = {
'cvox.BrailleBackground',
'cvox.BrailleInputHandler',
'cvox.BrailleKeyCommand',
- 'cvox.BrailleUtil',
'cvox.ChromeBraille',
'cvox.ExpandingBrailleTranslator',
+ 'cvox.ValueSelectionSpan',
+ 'cvox.ValueSpan',
],
/** @override */
+ isAsync: true,
+
+ /** @override */
setUp: function() {
- cvox.BrailleTable.getAll = function() {}
this.displayManager = new FakeDisplayManager();
this.inputHandler = new FakeInputHandler();
+ this.translatorManager = new FakeTranslatorManager();
this.brailleBackground = new cvox.BrailleBackground(
- this.displayManager, this.inputHandler);
+ this.displayManager, this.inputHandler, this.translatorManager);
cvox.ExtensionBridge = new FakeExtensionBridge(this.brailleBackground);
@@ -62,6 +68,25 @@ CvoxBrailleIntegrationUnitTest.prototype = {
sendCommand: function(command, content) {
this.displayManager.commandListener(command, content);
+ },
+
+ /**
+ * Waits for {@code document} to gain focus, arranging to call
+ * {@code testDone} afterwards.
+ * @param {Function()} callback Called when focus is gained
+ */
+ awaitDocumentFocused: function(callback) {
+ callback = this.newCallback(callback);
+ if (!document.hasFocus()) {
+ var listener = this.newCallback(function() {
+ assertTrue(document.hasFocus());
+ window.removeEventListener('focus', listener);
+ callback();
+ });
+ window.addEventListener('focus', listener);
+ } else {
+ callback();
+ }
}
};
@@ -136,14 +161,19 @@ FakeInputHandler.prototype = {
}
};
+/** @extends {cvox.BrailleTranslatorManager} */
+function FakeTranslatorManager() {
+}
+
TEST_F('CvoxBrailleIntegrationUnitTest', 'Write', function() {
this.braille.write(this.content1);
assertEqualsJSON(this.content1, this.displayManager.content);
+ testDone();
});
TEST_F('CvoxBrailleIntegrationUnitTest', 'WriteWithSpans', function() {
- var selectionSpan = new cvox.BrailleUtil.ValueSelectionSpan();
- var valueSpan = new cvox.BrailleUtil.ValueSpan(20);
+ var selectionSpan = new cvox.ValueSelectionSpan();
+ var valueSpan = new cvox.ValueSpan(20);
var toSend = cvox.NavBraille.fromText(
new cvox.Spannable('Hello', valueSpan));
toSend.text.setSpan(selectionSpan, 0, 0);
@@ -154,67 +184,72 @@ TEST_F('CvoxBrailleIntegrationUnitTest', 'WriteWithSpans', function() {
this.braille.write(toSend);
assertEqualsJSON(expected, this.displayManager.content);
+ testDone();
});
TEST_F('CvoxBrailleIntegrationUnitTest', 'CommandNoContent', function() {
// Commands are only delivered to the content script if the window has focus.
- window.focus();
- this.sendCommand(this.command1, null);
- assertEqualsJSON(this.command1, this.lastCommand);
- assertEquals(null, this.lastCommandContent);
+ this.awaitDocumentFocused(function() {
+ this.sendCommand(this.command1, null);
+ assertEqualsJSON(this.command1, this.lastCommand);
+ assertEquals(null, this.lastCommandContent);
+ });
});
-TEST_F('CvoxBrailleIntegrationUnitTest', 'InterleavedWritesAndCommands',
+TEST_F('CvoxBrailleIntegrationUnitTest',
+ 'InterleavedWritesAndCommands',
function() {
- window.focus();
- this.braille.write(this.content1);
- this.sendCommand(this.command1, this.displayManager.content);
- assertEqualsJSON(this.command1, this.lastCommand);
- assertEqualsJSON(this.content1, this.lastCommandContent);
-
- var savedContent1 = this.displayManager.content;
- this.braille.write(this.content2);
- // Old content still on display.
- this.sendCommand(this.command1, savedContent1);
- assertEqualsJSON(this.command1, this.lastCommand);
- assertEquals(null, this.lastCommandContent);
- this.sendCommand(this.command2, this.displayManager.content);
- assertEqualsJSON(this.command2, this.lastCommand);
- assertEqualsJSON(this.content2, this.lastCommandContent);
-
+ this.awaitDocumentFocused(function() {
+ this.braille.write(this.content1);
+ this.sendCommand(this.command1, this.displayManager.content);
+ assertEqualsJSON(this.command1, this.lastCommand);
+ assertEqualsJSON(this.content1, this.lastCommandContent);
+
+ var savedContent1 = this.displayManager.content;
+ this.braille.write(this.content2);
+ // Old content still on display.
+ this.sendCommand(this.command1, savedContent1);
+ assertEqualsJSON(this.command1, this.lastCommand);
+ assertEquals(null, this.lastCommandContent);
+ this.sendCommand(this.command2, this.displayManager.content);
+ assertEqualsJSON(this.command2, this.lastCommand);
+ assertEqualsJSON(this.content2, this.lastCommandContent);
+ });
});
TEST_F('CvoxBrailleIntegrationUnitTest', 'CommandAfterBackgroundWrite',
function() {
- window.focus();
- this.braille.write(this.content1);
- this.sendCommand(this.command1, this.displayManager.content);
- assertEqualsJSON(this.command1, this.lastCommand);
- assertEqualsJSON(this.content1, this.lastCommandContent);
-
- this.brailleBackground.write(this.content2);
- assertEqualsJSON(this.content2, this.displayManager.content);
- this.sendCommand(this.command2, this.displayManager.content);
- assertEqualsJSON(this.command2, this.lastCommand);
- assertEquals(null, this.lastCommandContent);
+ this.awaitDocumentFocused(function() {
+ this.braille.write(this.content1);
+ this.sendCommand(this.command1, this.displayManager.content);
+ assertEqualsJSON(this.command1, this.lastCommand);
+ assertEqualsJSON(this.content1, this.lastCommandContent);
+
+ this.brailleBackground.write(this.content2);
+ assertEqualsJSON(this.content2, this.displayManager.content);
+ this.sendCommand(this.command2, this.displayManager.content);
+ assertEqualsJSON(this.command2, this.lastCommand);
+ assertEquals(null, this.lastCommandContent);
+ });
});
TEST_F('CvoxBrailleIntegrationUnitTest', 'CommandAfterOtherTabWrite',
function() {
- window.focus();
- // Ignore the listener of the braille from the second 'tab'.
- cvox.ExtensionBridge.addMessageListener = function() {}
- // Create another content script braille object, presumably from another
- // tab.
- var anotherBraille = new cvox.ChromeBraille();
- this.braille.write(this.content1);
- this.sendCommand(this.command1, this.displayManager.content);
- // Now, this other braille gets a different unique id.
- cvox.ExtensionBridge.uniqueId = function() { return 2; }
- anotherBraille.write(this.content2);
- this.sendCommand(this.command2, this.displayManager.content);
- // The first 'tab' still gets the command, but since the second 'tab's\
- // braille was on the display, it gets null content.
- assertEqualsJSON(this.command2, this.lastCommand);
- assertEquals(null, this.lastCommandContent);
+ this.awaitDocumentFocused(function() {
+ // Ignore the listener of the braille from the second 'tab'.
+ cvox.ExtensionBridge.addMessageListener = function() {}
+ // Create another content script braille object, presumably from another
+ // tab.
+ var anotherBraille = new cvox.ChromeBraille();
+ this.braille.write(this.content1);
+ this.sendCommand(this.command1, this.displayManager.content);
+ // Now, this other braille gets a different unique id.
+ cvox.ExtensionBridge.uniqueId = function() { return 2; }
+ anotherBraille.write(this.content2);
+ this.sendCommand(this.command2, this.displayManager.content);
+ // The first 'tab' still gets the command, but since the second 'tab's\
+ // braille was on the display, it gets null content.
+ assertEqualsJSON(this.command2, this.lastCommand);
+ assertEquals(null, this.lastCommandContent);
+ });
});
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/extension_bridge.js b/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/extension_bridge.js
index 722c7e0b150..45cb302c2c2 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/extension_bridge.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/extension_bridge.js
@@ -150,7 +150,7 @@ cvox.ExtensionBridge.uniqueId = function() {
cvox.ExtensionBridge.initBackground = function() {
var self = cvox.ExtensionBridge;
- /** @type {!Array.<Port>} @private */
+ /** @type {!Array<Port>} @private */
self.portCache_ = [];
/** @type {number} */
self.nextPongId_ = 1;
@@ -164,18 +164,18 @@ cvox.ExtensionBridge.initBackground = function() {
self.portCache_.push(port);
- port.onMessage.addListener(
- function(message) {
- if (message[cvox.ExtensionBridge.PING_MSG]) {
- var pongMessage = {};
- pongMessage[cvox.ExtensionBridge.PONG_MSG] = self.nextPongId_++;
- port.postMessage(pongMessage);
- return;
- }
- for (var i = 0; i < self.messageListeners.length; i++) {
- self.messageListeners[i](message, port);
- }
- });
+ port.onMessage.addListener(function(message) {
+ if (message[cvox.ExtensionBridge.PING_MSG]) {
+ var pongMessage = {};
+ pongMessage[cvox.ExtensionBridge.PONG_MSG] = self.nextPongId_++;
+ port.postMessage(pongMessage);
+ return;
+ }
+
+ for (var i = 0; i < self.messageListeners.length; i++) {
+ self.messageListeners[i](message, port);
+ }
+ });
port.onDisconnect.addListener(function(message) {
for (var i = 0; i < self.portCache_.length; i++) {
@@ -235,29 +235,27 @@ cvox.ExtensionBridge.setupBackgroundPort = function() {
// Set up the connection to the background page.
var self = cvox.ExtensionBridge;
self.backgroundPort = chrome.extension.connect({name: self.PORT_NAME});
- self.backgroundPort.onMessage.addListener(
- function(message) {
- if (message[cvox.ExtensionBridge.PONG_MSG]) {
- self.gotPongFromBackgroundPage(
- message[cvox.ExtensionBridge.PONG_MSG]);
- } else {
- for (var i = 0; i < self.messageListeners.length; i++) {
- self.messageListeners[i](message, self.backgroundPort);
- }
- }
- });
- self.backgroundPort.onDisconnect.addListener(
- function(event) {
- // If we're not connected yet, don't give up - try again.
- if (!self.connected) {
- self.backgroundPort = null;
- return;
- }
-
- for (var i = 0; i < self.disconnectListeners.length; i++) {
- self.disconnectListeners[i]();
- }
- });
+ self.backgroundPort.onMessage.addListener(function(message) {
+ if (message[cvox.ExtensionBridge.PONG_MSG]) {
+ self.gotPongFromBackgroundPage(
+ message[cvox.ExtensionBridge.PONG_MSG]);
+ } else {
+ for (var i = 0; i < self.messageListeners.length; i++) {
+ self.messageListeners[i](message, self.backgroundPort);
+ }
+ }
+ });
+ self.backgroundPort.onDisconnect.addListener(function(event) {
+ // If we're not connected yet, don't give up - try again.
+ if (!self.connected) {
+ self.backgroundPort = null;
+ return;
+ }
+
+ for (var i = 0; i < self.disconnectListeners.length; i++) {
+ self.disconnectListeners[i]();
+ }
+ });
};
/**
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/externs.js b/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/externs.js
index f49f93e27df..fbf813c3168 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/externs.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/externs.js
@@ -3,15 +3,6 @@
// found in the LICENSE file.
/**
- * @param {string} url
- * @constructor
- */
-function Audio(url) {}
-Audio.prototype.play;
-Audio.prototype.pause;
-Audio.prototype.autoplay;
-
-/**
* @type {Object}
*/
chrome.brailleDisplayPrivate = {};
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/host.js b/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/host.js
index c5e8e440464..dda8a892bcc 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/host.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/host.js
@@ -70,7 +70,7 @@ cvox.ChromeHost.prototype.init = function() {
if (prefs['position']) {
cvox.ChromeVox.position =
- /** @type {Object.<string, {x:number, y:number}>} */ (
+ /** @type {Object<string, {x:number, y:number}>} */ (
JSON.parse(prefs['position']));
}
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/mathjax.js b/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/mathjax.js
index fe152153c2b..b03f7ac9c6a 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/mathjax.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/mathjax.js
@@ -38,7 +38,7 @@ cvox.ChromeMathJax = function() {
/**
* Map from callback ID to callback function.
- * @type {Object.<number, Function>}
+ * @type {Object<number, Function>}
* @private
*/
this.callbackMap_ = {};
@@ -105,7 +105,7 @@ cvox.ChromeMathJax.prototype.init = function() {
* Destructive Retrieval of a callback function from the mapping.
* @param {string} data The command to be sent to the content script.
* @param {Function} callback A callback function.
- * @param {Object.<string, *>=} args Object of arguments.
+ * @param {Object<string, *>=} args Object of arguments.
*/
cvox.ChromeMathJax.prototype.postMsg = function(data, callback, args) {
args = args || {};
@@ -136,7 +136,7 @@ cvox.ChromeMathJax.prototype.portSetup = function(event) {
/**
* Call the appropriate Cvox function dealing with MathJax return values.
- * @param {{cmd: string, id: string, args: Object.<string, string>}} message A
+ * @param {{cmd: string, id: string, args: Object<string, string>}} message A
* message object.
*/
cvox.ChromeMathJax.prototype.dispatchMessage = function(message) {
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/tts.js b/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/tts.js
index 6d466a52885..77e257f6b97 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/tts.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/tts.js
@@ -32,7 +32,7 @@ cvox.ChromeTts.callId = 1;
/**
* Maps call ids to callback functions.
- * @type {Object.<number, Function>}
+ * @type {Object<number, Function>}
*/
cvox.ChromeTts.functionMap = new Object();
@@ -97,7 +97,9 @@ cvox.ChromeTts.prototype.addBridgeListener = function() {
var id = msg['id'];
var func = cvox.ChromeTts.functionMap[id];
if (func != undefined) {
- func();
+ if (!msg['cleanupOnly']) {
+ func();
+ }
delete cvox.ChromeTts.functionMap[id];
}
}
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/tts_background.js b/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/tts_background.js
index 9886e2fd17b..9eabcf4a5a6 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/tts_background.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/tts_background.js
@@ -66,7 +66,7 @@ cvox.TtsBackground = function(opt_enableMath) {
parseInt(localStorage[cvox.AbstractTts.PUNCTUATION_ECHO] || 1, 10);
/**
- * @type {!Array.<{name:(string),
+ * @type {!Array<{name:(string),
* msg:(string),
* regexp:(RegExp),
* clear:(boolean)}>}
@@ -108,7 +108,7 @@ cvox.TtsBackground = function(opt_enableMath) {
* A list of punctuation characters that should always be spliced into output
* even with literal word substitutions.
* This is important for tts prosity.
- * @type {!Array.<string>}
+ * @type {!Array<string>}
* @private
*/
this.retainPunctuation_ =
@@ -128,11 +128,11 @@ cvox.TtsBackground = function(opt_enableMath) {
try {
/**
- * @type {Object.<string, string>}
+ * @type {Object<string, string>}
* @private
* @const
*/
- this.PHONETIC_MAP_ = /** @type {Object.<string, string>} */(
+ this.PHONETIC_MAP_ = /** @type {Object<string, string>} */(
JSON.parse(cvox.ChromeVox.msgs.getMsg('phonetic_map')));
} catch (e) {
console.log('Error; unable to parse phonetic map msg.');
@@ -140,7 +140,7 @@ cvox.TtsBackground = function(opt_enableMath) {
/**
* Capturing tts event listeners.
- * @type {Array.<cvox.TtsCapturingEventListener>}
+ * @type {Array<cvox.TtsCapturingEventListener>}
* @private
*/
this.capturingTtsEventListeners_ = [];
@@ -154,7 +154,7 @@ cvox.TtsBackground = function(opt_enableMath) {
/**
* The utterance queue.
- * @type {Array.<cvox.Utterance>}
+ * @type {Array<cvox.Utterance>}
* @private
*/
this.utteranceQueue_ = [];
@@ -192,7 +192,7 @@ cvox.TtsBackground.PHONETIC_DELAY_MS_ = 1000;
/**
* The list of properties allowed to be passed to the chrome.tts.speak API.
* Anything outside this list will be stripped.
- * @type {Array.<string>}
+ * @type {Array<string>}
* @private
* @const
*/
@@ -229,6 +229,7 @@ cvox.TtsBackground.prototype.speak = function(
if (splitTextString.length > 2) {
var startCallback = properties['startCallback'];
var endCallback = properties['endCallback'];
+ var onEvent = properties['onEvent'];
for (var i = 0; i < splitTextString.length; i++) {
var propertiesCopy = {};
for (var p in properties) {
@@ -237,6 +238,8 @@ cvox.TtsBackground.prototype.speak = function(
propertiesCopy['startCallback'] = i == 0 ? startCallback : null;
propertiesCopy['endCallback'] =
i == (splitTextString.length - 1) ? endCallback : null;
+ propertiesCopy['onEvent'] =
+ i == (splitTextString.length - 1) ? onEvent : null;
this.speak(splitTextString[i], queueMode, propertiesCopy);
queueMode = cvox.QueueMode.QUEUE;
}
@@ -346,8 +349,9 @@ cvox.TtsBackground.prototype.startSpeakingNextItemInQueue_ = function() {
var utteranceId = this.currentUtterance_.id;
this.currentUtterance_.properties['onEvent'] = goog.bind(function(event) {
- this.onTtsEvent_(event, utteranceId);
- }, this);
+ this.onTtsEvent_(event, utteranceId);
+ },
+ this);
var validatedProperties = {};
for (var i = 0; i < cvox.TtsBackground.ALLOWED_PROPERTIES_.length; i++) {
@@ -394,14 +398,26 @@ cvox.TtsBackground.prototype.onTtsEvent_ = function(event, utteranceId) {
}
break;
case 'end':
+ // End callbacks could cause additional speech to queue up.
+ this.currentUtterance_ = null;
this.capturingTtsEventListeners_.forEach(function(listener) {
listener.onTtsEnd();
});
- // Intentionally falls through.
+ if (utterance.properties['endCallback']) {
+ try {
+ utterance.properties['endCallback']();
+ } catch (e) {
+ }
+ }
+ this.startSpeakingNextItemInQueue_();
+ break;
case 'interrupted':
this.cancelUtterance_(utterance);
this.currentUtterance_ = null;
- this.startSpeakingNextItemInQueue_();
+ for (var i = 0; i < this.utteranceQueue_.length; i++) {
+ this.cancelUtterance_(this.utteranceQueue_[i]);
+ }
+ this.utteranceQueue_.length = 0;
break;
case 'error':
this.onError_(event['errorMessage']);
@@ -451,7 +467,7 @@ cvox.TtsBackground.prototype.shouldCancel_ =
cvox.TtsBackground.prototype.cancelUtterance_ = function(utterance) {
if (utterance && utterance.properties['endCallback']) {
try {
- utterance.properties['endCallback']();
+ utterance.properties['endCallback'](true);
} catch (e) {
}
}
@@ -480,6 +496,7 @@ cvox.TtsBackground.prototype.stop = function() {
for (var i = 0; i < this.utteranceQueue_.length; i++) {
this.cancelUtterance_(this.utteranceQueue_[i]);
}
+
this.utteranceQueue_.length = 0;
chrome.tts.stop();
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/host/interface/abstract_host.js b/chromium/chrome/browser/resources/chromeos/chromevox/host/interface/abstract_host.js
index 13ff05011ef..c4e1e2fcf1f 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/host/interface/abstract_host.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/host/interface/abstract_host.js
@@ -143,7 +143,7 @@ cvox.AbstractHost.prototype.onStateChanged_ = function(state) {
cvox.ChromeVox.navigationManager.showOrHideIndicator(true);
cvox.ChromeVoxEventWatcher.init(window);
if (document.activeElement) {
- var speakNodeAlso = document.hasFocus() && !document.webkitHidden;
+ var speakNodeAlso = cvox.ChromeVox.documentHasFocus();
cvox.ApiImplementation.syncToNode(
document.activeElement, speakNodeAlso);
} else {
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/host/interface/abstract_tts.js b/chromium/chrome/browser/resources/chromeos/chromevox/host/interface/abstract_tts.js
index ae4346bbe0b..835f3fb4719 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/host/interface/abstract_tts.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/host/interface/abstract_tts.js
@@ -422,7 +422,7 @@ cvox.AbstractTts.DEBUG = true;
/**
* Character dictionary. These symbols are replaced with their human readable
* equivalents. This replacement only occurs for single character utterances.
- * @type {Object.<string, string>}
+ * @type {Object<string, string>}
*/
cvox.AbstractTts.CHARACTER_DICTIONARY = {
' ': 'space',
@@ -469,7 +469,7 @@ cvox.AbstractTts.CHARACTER_DICTIONARY = {
* should be spelled out the way most TTS engines will pronounce it
* correctly. This particular dictionary only handles letters and numbers,
* no symbols.
- * @type {Object.<string, string>}
+ * @type {Object<string, string>}
*/
cvox.AbstractTts.PRONUNCIATION_DICTIONARY = {
'admob': 'ad-mob',
@@ -509,7 +509,7 @@ cvox.AbstractTts.pronunciationDictionaryRegexp_;
*
* For example, do not include '$' here because $2 should be read as
* "two dollars".
- * @type {Object.<string, string>}
+ * @type {Object<string, string>}
*/
cvox.AbstractTts.SUBSTITUTION_DICTIONARY = {
'://': 'colon slash slash',
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/host/testing/tts.js b/chromium/chrome/browser/resources/chromeos/chromevox/host/testing/tts.js
index a16e300854e..168769bde6c 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/host/testing/tts.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/host/testing/tts.js
@@ -90,7 +90,7 @@ cvox.TestTts.prototype.getUtterancesAsString = function() {
*
* For example, if two strings were spoken with a queue mode of FLUSH,
* only the second string will be returned.
- * @return {Array.<string>} A list of strings representing the speech output.
+ * @return {Array<string>} A list of strings representing the speech output.
*/
cvox.TestTts.prototype.getSpeechQueueOutput = function() {
var queue = [];
@@ -118,7 +118,7 @@ cvox.TestTts.prototype.getSpeechQueueOutput = function() {
/**
* Return a list of strings of what was spoken by tts.speak().
- * @return {Array.<string>} A list of all utterances spoken since
+ * @return {Array<string>} A list of all utterances spoken since
* initialization or the last call to clearUtterances.
*/
cvox.TestTts.prototype.getUtteranceList = function() {
@@ -131,7 +131,7 @@ cvox.TestTts.prototype.getUtteranceList = function() {
/**
* Return a list of strings of what was spoken by tts.speak().
- * @return {Array.<{text: string, queueMode: cvox.QueueMode}>}
+ * @return {Array<{text: string, queueMode: cvox.QueueMode}>}
* A list of info about all utterances spoken since
* initialization or the last call to clearUtterances.
*/
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/liblouis_nacl/externs.js b/chromium/chrome/browser/resources/chromeos/chromevox/liblouis_nacl/externs.js
deleted file mode 100644
index c73d8e97940..00000000000
--- a/chromium/chrome/browser/resources/chromeos/chromevox/liblouis_nacl/externs.js
+++ /dev/null
@@ -1,22 +0,0 @@
-// 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 Additional externs for the Closure Compiler.
- * @author jbroman@google.com (Jeremy Roman)
- * @externs
- */
-
-/**
- * <embed> element which wraps a Native Client module.
- * @constructor
- * @extends HTMLEmbedElement
- */
-function NaClEmbedElement() {}
-
-/**
- * Exposed when Native Client is present.
- * @param {*} message Message to post.
- */
-NaClEmbedElement.prototype.postMessage = function(message) {};
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/liblouis_nacl/liblouis_test.extjs b/chromium/chrome/browser/resources/chromeos/chromevox/liblouis_nacl/liblouis_test.extjs
deleted file mode 100644
index 3946e4d07fd..00000000000
--- a/chromium/chrome/browser/resources/chromeos/chromevox/liblouis_nacl/liblouis_test.extjs
+++ /dev/null
@@ -1,130 +0,0 @@
-// 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 Tests for the liblouis Native Client wrapper, as seen from
- * the JavaScript interface.
- */
-
-// Include test fixture.
-GEN_INCLUDE(['../testing/chromevox_e2e_test_base.js',
- '../testing/assert_additions.js']);
-
-/**
- * @constructor
- * @extends {ChromeVoxE2ETest}
- */
-function CvoxLibLouisTest() {}
-
-CvoxLibLouisTest.prototype = {
- __proto__: ChromeVoxE2ETest.prototype,
-
- /** @override */
- setUp: function() {
- // Avoid loading a second liblouis instance and get the one that chromevox
- // is already creating.
- this.instance = cvox.ChromeVox.braille.getLibLouisForTest();
- },
-};
-
-function assertEqualsUint8Array(expected, actual) {
- var as_array = [];
- var uint8array = new Uint8Array(actual);
- for (var i = 0; i < uint8array.length; ++i) {
- as_array[i] = uint8array[i];
- }
- assertEqualsJSON(expected, as_array);
-}
-
-TEST_F('CvoxLibLouisTest', 'checkAllTables', function() {
- cvox.BrailleTable.getAll(function(all_tables) {
- var i = 0;
- var checkNextTable = function() {
- var table = all_tables[i++];
- if (table) {
- this.instance.getTranslator(table.fileNames, function(translator) {
- assertNotEquals(null, translator,
- 'Table ' + table + ' should be valid');
- checkNextTable();
- });
- } else {
- testDone();
- }
- }.bind(this);
- checkNextTable();
- }.bind(this));
-});
-
-TEST_F('CvoxLibLouisTest', 'testTranslateComputerBraille', function () {
- this.instance.getTranslator('en-us-comp8.ctb', function(translator) {
- translator.translate(
- 'Hello!', function(cells, textToBraille, brailleToText) {
- assertEqualsUint8Array([0x53, 0x11, 0x07, 0x07, 0x15, 0x2e], cells);
- assertEqualsJSON([0, 1, 2, 3, 4, 5], textToBraille);
- assertEqualsJSON([0, 1, 2, 3, 4, 5], brailleToText);
- testDone();
- });
- });
-});
-
-TEST_F('CvoxLibLouisTest', 'testBackTranslateComputerBraille', function() {
- this.instance.getTranslator('en-us-comp8.ctb', function(translator) {
- var cells = new Uint8Array([0x53, 0x11, 0x07, 0x07, 0x15, 0x2e]);
- translator.backTranslate(cells.buffer, function(text) {
- assertEquals('Hello!', text);
- testDone();
- });
- });
-});
-
-TEST_F('CvoxLibLouisTest', 'testTranslateGermanGrade2Braille', function() {
- // This is one of the moderately large tables.
- this.instance.getTranslator('de-de-g2.ctb', function(translator) {
- translator.translate(
- 'München', function(cells, textToBraille, brailleToText) {
- assertEqualsUint8Array([0x0d, 0x33, 0x1d, 0x39, 0x09], cells);
- assertEqualsJSON([0, 1, 2, 3, 3, 4, 4], textToBraille);
- assertEqualsJSON([0, 1, 2, 3, 5], brailleToText);
- testDone();
- });
- });
-});
-
-TEST_F('CvoxLibLouisTest', 'testBackTranslateGermanComputerBraille', function() {
- this.instance.getTranslator('de-de-comp8.ctb', function(translator) {
- var cells = new Uint8Array([0xb3]);
- translator.backTranslate(cells.buffer, function(text) {
- assertEquals('ü', text);
- testDone();
- });
- });
-});
-
-TEST_F('CvoxLibLouisTest', 'testBackTranslateEmptyCells', function() {
- this.instance.getTranslator('de-de-comp8.ctb', function(translator) {
- var cells = new Uint8Array();
- translator.backTranslate(cells.buffer, function(text) {
- assertNotEquals(null, text);
- assertEquals(0, text.length);
- testDone();
- });
- });
-});
-
-TEST_F('CvoxLibLouisTest', 'testGetTranslatorBeforeAttach', function() {
- this.instance.detach();
- assertFalse(this.instance.isAttached());
- this.instance.getTranslator('en-us-comp8.ctb', function(translator) {
- assertNotEquals(null, translator);
- testDone();
- });
- this.instance.attachToElement(document.body);
-});
-
-TEST_F('CvoxLibLouisTest', 'testGetInvalidTranslator', function() {
- this.instance.getTranslator('nonexistant-table', function(translator) {
- assertEquals(null, translator);
- testDone();
- });
-});
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/manifest.json.jinja2 b/chromium/chrome/browser/resources/chromeos/chromevox/manifest.json.jinja2
index e14cbb15107..68b27ada976 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/manifest.json.jinja2
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/manifest.json.jinja2
@@ -3,26 +3,20 @@
"key": "{{key}}",
{% endif %}
"manifest_version": 2,
- "name": "ChromeVox",
+ "name": "__MSG_CHROMEVOX_NAME__",
"version": "{{set_version}}",
- "description": "ChromeVox - Giving Voice to Chrome.",
+ "description": "__MSG_CHROMEVOX_DESCRIPTION__",
{% if is_guest_manifest == '1' %}
"incognito": "split",
{% endif %}
"background": {
-{% if is_chromevox_next == '1' %}
"page": "cvox2/background/background.html"
-{% else %}
- "page": "chromevox/background/background.html"
-{% endif %}
},
"permissions": [
"accessibilityPrivate",
"bookmarks",
"brailleDisplayPrivate",
-{% if is_chromevox_next == '1' %}
"commands.accessibility",
-{% endif %}
"commandLinePrivate",
"experimental",
"history",
@@ -33,11 +27,12 @@
"virtualKeyboardPrivate",
"<all_urls>"
],
-{% if is_chromevox_next == '0' %}
"content_scripts": [
{
"matches": [ "<all_urls>" ],
- "exclude_globs": [ "chrome-extension://mndnfokpggljbaajbnioimlmbfngpief/chromevox/background/background.html" ],
+ "exclude_globs": [
+ "chrome-extension://mndnfokpggljbaajbnioimlmbfngpief/chromevox/background/background.html"
+ ],
"all_frames": true,
"js": [
{% if is_js_compressed == '1' %}
@@ -51,26 +46,25 @@
]
}
],
-{% endif %}
"web_accessible_resources": [
"chromevox/injected/api.js",
"chromevox/injected/api_util.js",
"chromevox/injected/mathjax.js",
"chromevox/injected/mathjax_external_util.js"
],
-{% if is_chromevox_next == '1' %}
"automation": {
"desktop": true
},
+{% if is_chromevox_classic == '0' %}
"commands": {
"nextElement": {
- "description": "Moves to the next element",
+ "description": "__MSG_CHROMEVOX_NEXT_OBJECT__",
"suggested_key": {
"chromeos": "Search+Right"
}
},
"previousElement": {
- "description": "Moves to the previous element",
+ "description": "__MSG_CHROMEVOX_PREVIOUS_OBJECT__",
"suggested_key": {
"chromeos": "Search+Left"
}
@@ -111,16 +105,64 @@
"chromeos": "Search+Up"
}
},
- "nextLink": {
- "description": "__MSG_CHROMEVOX_NEXT_LINK__",
+ "nextButton": {
+ "description": "__MSG_CHROMEVOX_NEXT_BUTTON__",
"suggested_key": {
- "chromeos": "Search+L"
+ "chromeos": "Search+B"
}
},
- "previousLink": {
- "description": "__MSG_CHROMEVOX_PREVIOUS_LINK__",
+ "previousButton": {
+ "description": "__MSG_CHROMEVOX_PREVIOUS_BUTTON__",
"suggested_key": {
- "chromeos": "Search+Shift+L"
+ "chromeos": "Search+Shift+B"
+ }
+ },
+ "nextCheckBox": {
+ "description": "__MSG_CHROMEVOX_NEXT_CHECKBOX__",
+ "suggested_key": {
+ "chromeos": "Search+X"
+ }
+ },
+ "previousCheckBox": {
+ "description": "__MSG_CHROMEVOX_PREVIOUS_CHECKBOX__",
+ "suggested_key": {
+ "chromeos": "Search+Shift+X"
+ }
+ },
+ "nextComboBox": {
+ "description": "__MSG_CHROMEVOX_NEXT_COMBO_BOX__",
+ "suggested_key": {
+ "chromeos": "Search+C"
+ }
+ },
+ "previousComboBox": {
+ "description": "__MSG_CHROMEVOX_PREVIOUS_COMBO_BOX__",
+ "suggested_key": {
+ "chromeos": "Search+Shift+C"
+ }
+ },
+ "nextEditText": {
+ "description": "__MSG_CHROMEVOX_NEXT_EDIT_TEXT__",
+ "suggested_key": {
+ "chromeos": "Search+E"
+ }
+ },
+ "previousEditText": {
+ "description": "__MSG_CHROMEVOX_PREVIOUS_EDIT_TEXT__",
+ "suggested_key": {
+ "chromeos": "Search+Shift+E"
+ }
+ },
+ "nextFormField": {
+ "description": "__MSG_CHROMEVOX_NEXT_FORM_FIELD__",
+ "suggested_key": {
+ "chromeos": "Search+F"
+ }
+ },
+ "previousFormField": {
+ "description": "__MSG_CHROMEVOX_PREVIOUS_FORM_FIELD__",
+ "suggested_key": {
+ "chromeos": "Search+Shift+F"
}
},
"nextHeading": {
@@ -135,23 +177,107 @@
"chromeos": "Search+Shift+H"
}
},
+ "compatPreviousLine": {
+ "description": "__MSG_CHROMEVOX_PREVIOUS_HEADING__",
+ "suggested_key": {
+ "chromeos": "Search+Shift+Up"
+ }
+ },
+ "compatNextLine": {
+ "description": "__MSG_CHROMEVOX_PREVIOUS_HEADING__",
+ "suggested_key": {
+ "chromeos": "Search+Shift+Down"
+ }
+ },
+ "compatPrevious": {
+ "description": "__MSG_CHROMEVOX_PREVIOUS_HEADING__",
+ "suggested_key": {
+ "chromeos": "Search+Shift+P"
+ }
+ },
+ "compatNext": {
+ "description": "__MSG_CHROMEVOX_PREVIOUS_HEADING__",
+ "suggested_key": {
+ "chromeos": "Search+Shift+N"
+ }
+ },
+ "nextLink": {
+ "description": "__MSG_CHROMEVOX_NEXT_LINK__",
+ "suggested_key": {
+ "chromeos": "Search+L"
+ }
+ },
+ "previousLink": {
+ "description": "__MSG_CHROMEVOX_PREVIOUS_LINK__",
+ "suggested_key": {
+ "chromeos": "Search+Shift+L"
+ }
+ },
+ "nextTable": {
+ "description": "__MSG_CHROMEVOX_NEXT_TABLE__",
+ "suggested_key": {
+ "chromeos": "Search+T"
+ }
+ },
+ "previousTable": {
+ "description": "__MSG_CHROMEVOX_PREVIOUS_TABLE__",
+ "suggested_key": {
+ "chromeos": "Search+Shift+T"
+ }
+ },
+ "nextVisitedLink": {
+ "description": "__MSG_CHROMEVOX_NEXT_VISITED_LINK__",
+ "suggested_key": {
+ "chromeos": "Search+V"
+ }
+ },
+ "previousVisitedLink": {
+ "description": "__MSG_CHROMEVOX_PREVIOUS_VISITED_LINK__",
+ "suggested_key": {
+ "chromeos": "Search+Shift+V"
+ }
+ },
"goToBeginning": {
- "description": "__MSG_CHROMEVOX_SKIP_TO_BEGINNING__",
+ "description": "__MSG_CHROMEVOX_JUMP_TO_TOP__",
"suggested_key": {
"chromeos": "Search+Ctrl+Left"
}
},
"goToEnd": {
- "description": "__MSG_CHROMEVOX_SKIP_TO_END__",
+ "description": "__MSG_CHROMEVOX_JUMP_TO_BOTTOM__",
"suggested_key": {
"chromeos": "Search+Ctrl+Right"
}
},
+ "doDefault": {
+ "description": "__MSG_CHROMEVOX_PERFORM_DEFAULT_ACTION__",
+ "suggested_key": {
+ "chromeos": "Search+Space"
+ }
+ },
+ "compatDoDefault": {
+ "description": "__MSG_CHROMEVOX_PERFORM_DEFAULT_ACTION__",
+ "suggested_key": {
+ "chromeos": "Search+Shift+Space"
+ }
+ },
"toggleChromeVoxVersion": {
"description": "__MSG_CHROMEVOX_TOGGLE_CHROMEVOX__",
"suggested_key": {
"chromeos": "Search+Ctrl+Q"
}
+ },
+ "continuousRead": {
+ "description": "__MSG_CHROMEVOX_READ_FROM_HERE__",
+ "suggested_key": {
+ "chromeos": "Search+R"
+ }
+ },
+ "compatContinuousRead": {
+ "description": "__MSG_CHROMEVOX_READ_FROM_HERE__",
+ "suggested_key": {
+ "chromeos": "Search+Shift+R"
+ }
}
},
{% endif %}
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/base_rule_store.js b/chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/base_rule_store.js
index cfaae6d4c09..079e4191ac2 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/base_rule_store.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/base_rule_store.js
@@ -44,20 +44,20 @@ cvox.BaseRuleStore = function() {
/**
* Set of speech rules in the store.
- * @type {!Array.<cvox.SpeechRule>}
+ * @type {!Array<cvox.SpeechRule>}
* @private
*/
this.speechRules_ = [];
/**
* A priority list of dynamic constraint attributes.
- * @type {!Array.<cvox.SpeechRule.DynamicCstrAttrib>}
+ * @type {!Array<cvox.SpeechRule.DynamicCstrAttrib>}
*/
this.dynamicCstrAttribs = [cvox.SpeechRule.DynamicCstrAttrib.STYLE];
/**
* List of TTS properties overridden by the store when it is active.
- * @type {!Array.<string>}
+ * @type {!Array<string>}
*/
this.defaultTtsProps = [];
};
@@ -197,7 +197,7 @@ cvox.BaseRuleStore.prototype.removeDuplicates = function(rule) {
* Checks if we have a custom query and applies it. Otherwise returns null.
* @param {!Node} node The initial node.
* @param {string} funcName A function name.
- * @return {Array.<Node>} The list of resulting nodes.
+ * @return {Array<Node>} The list of resulting nodes.
*/
cvox.BaseRuleStore.prototype.applyCustomQuery = function(
node, funcName) {
@@ -212,7 +212,7 @@ cvox.BaseRuleStore.prototype.applyCustomQuery = function(
* @param {!Node} node The initial node.
* @param {string} expr An Xpath expression string or a name of a custom
* query.
- * @return {Array.<Node>} The list of resulting nodes.
+ * @return {Array<Node>} The list of resulting nodes.
*/
cvox.BaseRuleStore.prototype.applySelector = function(node, expr) {
var result = this.applyCustomQuery(node, expr);
@@ -264,7 +264,7 @@ cvox.BaseRuleStore.prototype.testDynamicConstraints = function(
// The idea is that when we can not find a speech rule matching the value for
// a particular attribute in the dynamic constraintwe choose the one that has
// the value 'default'.
- var allKeys = /** @type {Array.<cvox.SpeechRule.DynamicCstrAttrib>} */ (
+ var allKeys = /** @type {Array<cvox.SpeechRule.DynamicCstrAttrib>} */ (
Object.keys(dynamic));
return allKeys.every(
function(key) {
@@ -276,7 +276,7 @@ cvox.BaseRuleStore.prototype.testDynamicConstraints = function(
/**
* Get a set of all dynamic constraint values.
- * @return {!Object.<cvox.SpeechRule.DynamicCstrAttrib, Array.<string>>} The
+ * @return {!Object<cvox.SpeechRule.DynamicCstrAttrib, Array<string>>} The
* object with all annotations.
*/
cvox.BaseRuleStore.prototype.getDynamicConstraintValues = function() {
@@ -320,7 +320,7 @@ cvox.BaseRuleStore.prototype.countMatchingDynamicConstraintValues_ = function(
* 1) that best match the dynamic constraints.
* 2) with the most additional constraints.
* @param {cvox.SpeechRule.DynamicCstr} dynamic Dynamic constraints.
- * @param {!Array.<cvox.SpeechRule>} rules An array of rules.
+ * @param {!Array<cvox.SpeechRule>} rules An array of rules.
* @return {cvox.SpeechRule} The most constraint rule.
* @private
*/
@@ -389,8 +389,8 @@ cvox.BaseRuleStore.compareDynamicConstraints_ = function(
/**
* Compares two static constraints (i.e., lists of precondition constraints) and
* returns true if they are equal.
- * @param {Array.<string>} cstr1 First static constraints.
- * @param {Array.<string>} cstr2 Second static constraints.
+ * @param {Array<string>} cstr1 First static constraints.
+ * @param {Array<string>} cstr2 Second static constraints.
* @return {boolean} True if the static constraints are equal.
* @private
*/
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/math_simple_store.js b/chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/math_simple_store.js
index 33d727d37af..4136c84589e 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/math_simple_store.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/math_simple_store.js
@@ -34,7 +34,7 @@ goog.inherits(cvox.MathSimpleStore, cvox.MathStore);
* only into a list of speech rules.
* @param {string} name Name for the rules.
* @param {string} str String for precondition and constraints.
- * @param {Object.<string, Object.<string, string>>} mapping Simple string
+ * @param {Object<string, Object<string, string>>} mapping Simple string
* mapping.
*/
cvox.MathSimpleStore.prototype.defineRulesFromMappings = function(
@@ -58,7 +58,7 @@ cvox.MathSimpleStore.prototype.defineRulesFromMappings = function(
cvox.MathCompoundStore = function() {
/**
* A set of efficient substores.
- * @type {Object.<string, cvox.MathStore>}
+ * @type {Object<string, cvox.MathStore>}
* @private
*/
this.subStores_ = {};
@@ -142,7 +142,7 @@ cvox.MathCompoundStore.prototype.lookupString = function(text, dynamic) {
/**
* Get a set of all dynamic constraint values.
- * @return {!Object.<cvox.SpeechRule.DynamicCstrAttrib, Array.<string>>} The
+ * @return {!Object<cvox.SpeechRule.DynamicCstrAttrib, Array<string>>} The
* object with all annotations.
*/
cvox.MathCompoundStore.prototype.getDynamicConstraintValues = function() {
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/math_store.js b/chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/math_store.js
index ceda6f57bfb..4c33452b6b8 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/math_store.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/math_store.js
@@ -35,7 +35,7 @@ cvox.MathStore = function() {
/**
* @override
*/
- this.defaultTtsProps = [cvox.AbstractTts.PITCH, cvox.AbstractTts.RATE];
+ this.defaultTtsProps = [cvox.AbstractTts.PITCH];
};
goog.inherits(cvox.MathStore, cvox.BaseRuleStore);
@@ -159,7 +159,7 @@ cvox.MathStore.prototype.defineRulesAlias = function(name, query, cstr) {
* Adds a new speech rule as alias of the given rule.
* @param {cvox.SpeechRule} rule The existing rule.
* @param {string} query Precondition query of the rule.
- * @param {Array.<string>} cstrList List of additional constraints.
+ * @param {Array<string>} cstrList List of additional constraints.
* @private
*/
cvox.MathStore.prototype.addAlias_ = function(rule, query, cstrList) {
@@ -185,7 +185,7 @@ cvox.MathStore.prototype.evaluateDefault = function(node) {
* string into components such as single characters, function names or words,
* numbers, etc. and creates the appropriate navigation descriptions.
* @param {string} str A string.
- * @return {!Array.<cvox.NavDescription>} Messages for the math expression.
+ * @return {!Array<cvox.NavDescription>} Messages for the math expression.
* @private
*/
cvox.MathStore.prototype.evaluateString_ = function(str) {
@@ -259,8 +259,8 @@ cvox.MathStore.prototype.evaluate_ = function(text) {
/**
* Removes all empty strings from an array of strings.
- * @param {Array.<string>} strs An array of strings.
- * @return {Array.<string>} The cleaned array.
+ * @param {Array<string>} strs An array of strings.
+ * @return {Array<string>} The cleaned array.
* @private
*/
cvox.MathStore.removeEmpty_ = function(strs) {
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/mathml_store_rules.js b/chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/mathml_store_rules.js
index 1e5eebebaed..ec16ce9a7c8 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/mathml_store_rules.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/mathml_store_rules.js
@@ -95,13 +95,14 @@ cvox.MathmlStoreRules.initCustomFunctions_ = function() {
cvox.MathmlStoreRules.initDefaultRules_ = function() {
// Initial rule
defineDefaultMathmlRule('math', '[m] ./*');
+ defineDefaultMathmlRule('semantics', '[n] ./*[1]');
// Space elements
defineDefaultMathmlRule('mspace', '[p] (pause:250)');
defineDefaultMathmlRule('mstyle', '[m] ./*');
defineDefaultMathmlRule('mpadded', '[m] ./*');
- defineDefaultMathmlRule('merror', '[m] ./*');
- defineDefaultMathmlRule('mphantom', '[m] ./*');
+ defineDefaultMathmlRule('merror', '[t] ""');
+ defineDefaultMathmlRule('mphantom', '[t] ""');
// Token elements.
defineDefaultMathmlRule('mtext', '[t] text(); [p] (pause:200)');
@@ -164,31 +165,58 @@ cvox.MathmlStoreRules.initDefaultRules_ = function() {
defineDefaultMathmlRule(
'mfrac', ' [p] (pause:400); [n] ./*[1] (pitch:0.3);' +
' [t] "divided by"; [n] ./*[2] (pitch:-0.3); [p] (pause:400)');
+ defineRule(
+ 'mfrac', 'default.short', '[p] (pause:200); [t] "start frac";' +
+ '[n] ./*[1] (pitch:0.3); [t] "over"; ' +
+ '[n] ./*[2] (pitch:-0.3); [p] (pause:400); [t] "end frac"',
+ 'self::mathml:mfrac');
defineRule(
'mfenced-single', 'default.default',
- '[t] @open (context:"opening"); [m] ./* (separator:@separators);' +
- '[t] @close (context:"closing")',
+ '[t] concat(substring(@open, 0 div boolean(@open)), ' +
+ 'substring("(", 0 div not(boolean(@open)))) (context:"opening"); ' +
+ '[m] ./* (separator:@separators); ' +
+ '[t] concat(substring(@close, 0 div boolean(@close)), ' +
+ 'substring(")", 0 div not(boolean(@close)))) (context:"closing")',
'self::mathml:mfenced', 'string-length(string(@separators))=1');
defineRule(
+ 'mfenced-omit', 'default.default',
+ '[t] concat(substring(@open, 0 div boolean(@open)), ' +
+ 'substring("(", 0 div not(boolean(@open)))) (context:"opening"); ' +
+ '[m] ./*; ' +
+ '[t] concat(substring(@close, 0 div boolean(@close)), ' +
+ 'substring(")", 0 div not(boolean(@close)))) (context:"closing")',
+ 'self::mathml:mfenced', '@separators',
+ 'string-length(string(@separators))=0', 'string(@separators)=""');
+
+ defineRule(
'mfenced-empty', 'default.default',
- '[t] @open (context:"opening"); [m] ./*;' +
- '[t] @close (context:"closing")',
+ '[t] concat(substring(@open, 0 div boolean(@open)), ' +
+ 'substring("(", 0 div not(boolean(@open)))) (context:"opening"); ' +
+ '[m] ./*;' +
+ '[t] concat(substring(@close, 0 div boolean(@close)), ' +
+ 'substring(")", 0 div not(boolean(@close)))) (context:"closing")',
'self::mathml:mfenced', 'string-length(string(@separators))=1',
'string(@separators)=" "');
defineRule(
'mfenced-comma', 'default.default',
- '[t] @open (context:"opening"); [m] ./* (separator:"comma");' +
- '[t] @close (context:"closing")',
+ '[t] concat(substring(@open, 0 div boolean(@open)), ' +
+ 'substring("(", 0 div not(boolean(@open)))) (context:"opening"); ' +
+ '[m] ./* (separator:"comma");' +
+ '[t] concat(substring(@close, 0 div boolean(@close)), ' +
+ 'substring(")", 0 div not(boolean(@close)))) (context:"closing")',
'self::mathml:mfenced');
defineRule(
'mfenced-multi', 'default.default',
- '[t] @open (context:"opening"); [m] ./* (sepFunc:CTXFmfSeparators,' +
- 'separator:@separators); [t] @close (context:"closing")',
+ '[t] concat(substring(@open, 0 div boolean(@open)), ' +
+ 'substring("(", 0 div not(boolean(@open)))) (context:"opening"); ' +
+ '[m] ./* (sepFunc:CTXFmfSeparators, separator:@separators); ' +
+ '[t] concat(substring(@close, 0 div boolean(@close)), ' +
+ 'substring(")", 0 div not(boolean(@close)))) (context:"closing")',
'self::mathml:mfenced', 'string-length(string(@separators))>1');
// Mtable rules.
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/mathml_store_util.js b/chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/mathml_store_util.js
index ec396407ad9..7c1d4010d86 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/mathml_store_util.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/mathml_store_util.js
@@ -26,7 +26,7 @@ cvox.MathmlStoreUtil.matchMathjaxToMathml = function(inner) {
/**
* Retrieve an extender symbol for a given node.
* @param {!Node} jax The MathJax node.
- * @return {Array.<Node>} The resulting node list.
+ * @return {Array<Node>} The resulting node list.
*/
cvox.MathmlStoreUtil.retrieveMathjaxExtender = function(jax) {
var ext = cvox.MathmlStoreUtil.matchMathjaxToMathml(jax);
@@ -40,7 +40,7 @@ cvox.MathmlStoreUtil.retrieveMathjaxExtender = function(jax) {
/**
* Retrieve an extender symbol for a given node.
* @param {!Node} jax The MathJax node.
- * @return {Array.<Node>} The resulting node list.
+ * @return {Array<Node>} The resulting node list.
*/
cvox.MathmlStoreUtil.retrieveMathjaxLeaf = function(jax) {
var leaf = cvox.MathmlStoreUtil.matchMathjaxToMathml(jax);
@@ -56,7 +56,7 @@ cvox.MathmlStoreUtil.retrieveMathjaxLeaf = function(jax) {
* if it is of the right tag.
* @param {!Node} jax The Mathjax node.
* @param {!string} tag The required tag.
- * @return {Array.<Node>} The resulting node list.
+ * @return {Array<Node>} The resulting node list.
*/
cvox.MathmlStoreUtil.checkMathjaxTag = function(jax, tag) {
var node = cvox.MathmlStoreUtil.matchMathjaxToMathml(jax);
@@ -70,7 +70,7 @@ cvox.MathmlStoreUtil.checkMathjaxTag = function(jax, tag) {
/**
* Returns MathML node if MathJax is munder.
* @param {!Node} jax The Mathjax node.
- * @return {Array.<Node>} The resulting node list.
+ * @return {Array<Node>} The resulting node list.
*/
cvox.MathmlStoreUtil.checkMathjaxMunder = function(jax) {
return cvox.MathmlStoreUtil.checkMathjaxTag(jax, 'MUNDER');
@@ -80,7 +80,7 @@ cvox.MathmlStoreUtil.checkMathjaxMunder = function(jax) {
/**
* Returns MathML node if MathJax is mover.
* @param {!Node} jax The Mathjax node.
- * @return {Array.<Node>} The resulting node list.
+ * @return {Array<Node>} The resulting node list.
*/
cvox.MathmlStoreUtil.checkMathjaxMover = function(jax) {
return cvox.MathmlStoreUtil.checkMathjaxTag(jax, 'MOVER');
@@ -90,7 +90,7 @@ cvox.MathmlStoreUtil.checkMathjaxMover = function(jax) {
/**
* Returns MathML node if MathJax is msub.
* @param {!Node} jax The Mathjax node.
- * @return {Array.<Node>} The resulting node list.
+ * @return {Array<Node>} The resulting node list.
*/
cvox.MathmlStoreUtil.checkMathjaxMsub = function(jax) {
return cvox.MathmlStoreUtil.checkMathjaxTag(jax, 'MSUB');
@@ -100,7 +100,7 @@ cvox.MathmlStoreUtil.checkMathjaxMsub = function(jax) {
/**
* Returns MathML node if MathJax is msup.
* @param {!Node} jax The Mathjax node.
- * @return {Array.<Node>} The resulting node list.
+ * @return {Array<Node>} The resulting node list.
*/
cvox.MathmlStoreUtil.checkMathjaxMsup = function(jax) {
return cvox.MathmlStoreUtil.checkMathjaxTag(jax, 'MSUP');
@@ -144,7 +144,7 @@ cvox.MathmlStoreUtil.nextSeparatorFunction = function(separators) {
/**
* Computes the correct separators for each node.
- * @param {Array.<Node>} nodes A node array.
+ * @param {Array<Node>} nodes A node array.
* @param {string} context A context string.
* @return {function(): string} A closure that returns the next separator for an
* mfenced expression starting with the first node in nodes.
@@ -159,7 +159,7 @@ cvox.MathmlStoreUtil.mfencedSeparators = function(nodes, context) {
/**
* Iterates over the list of content nodes of the parent of the given nodes.
- * @param {Array.<Node>} nodes A node array.
+ * @param {Array<Node>} nodes A node array.
* @param {string} context A context string.
* @return {function(): string} A closure that returns the content of the next
* content node. Returns only context string if list is exhausted.
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/speech_rule.js b/chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/speech_rule.js
index 37f860bfae7..cf07c6bf502 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/speech_rule.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/speech_rule.js
@@ -179,7 +179,7 @@ cvox.SpeechRule.Component.fromString = function(input) {
cvox.SpeechRule.Component.prototype.toString = function() {
var strs = '';
strs += cvox.SpeechRule.Type.toString(this.type);
- strs += this.content ? ' ' + this.content : '';
+ strs += this.content ? ' ' + this.content : '';
var attribs = this.getAttributes();
if (attribs.length > 0) {
strs += ' (' + attribs.join(', ') + ')';
@@ -220,7 +220,7 @@ cvox.SpeechRule.Component.prototype.addAttributes = function(attrs) {
/**
* Transforms the attributes of an object into a list of strings.
- * @return {Array.<string>} List of translated attribute:value strings.
+ * @return {Array<string>} List of translated attribute:value strings.
*/
cvox.SpeechRule.Component.prototype.getAttributes = function() {
var attribs = [];
@@ -235,11 +235,11 @@ cvox.SpeechRule.Component.prototype.getAttributes = function() {
/**
* A speech rule is a collection of speech components.
- * @param {Array.<cvox.SpeechRule.Component>} components The input rule.
+ * @param {Array<cvox.SpeechRule.Component>} components The input rule.
* @constructor
*/
cvox.SpeechRule.Action = function(components) {
- /** @type {Array.<cvox.SpeechRule.Component>} */
+ /** @type {Array<cvox.SpeechRule.Component>} */
this.components = components;
};
@@ -278,14 +278,14 @@ cvox.SpeechRule.Action.prototype.toString = function() {
/**
* Constructs a valid precondition for a speech rule.
* @param {string} query A node selector function or xpath expression.
- * @param {Array.<string>=} opt_constraints A list of constraint functions.
+ * @param {Array<string>=} opt_constraints A list of constraint functions.
* @constructor
*/
cvox.SpeechRule.Precondition = function(query, opt_constraints) {
/** @type {string} */
this.query = query;
- /** @type {!Array.<string>} */
+ /** @type {!Array<string>} */
this.constraints = opt_constraints || [];
};
@@ -306,7 +306,7 @@ cvox.SpeechRule.Precondition.prototype.toString = function() {
* ['[t] "matrix; 3 by 3"', ' [n] ./*[1]'].
* @param {string} str String to be split.
* @param {string} sep Separator symbol.
- * @return {Array.<string>} A list of single component strings.
+ * @return {Array<string>} A list of single component strings.
* @private
*/
cvox.SpeechRule.splitString_ = function(str, sep) {
@@ -361,7 +361,7 @@ cvox.SpeechRule.DynamicCstrAttrib =
/**
* Dynamic constraints are a means to specialize rules that can be changed
* dynamically by the user, for example by choosing different styles, etc.
- * @typedef {!Object.<cvox.SpeechRule.DynamicCstrAttrib, string>}
+ * @typedef {!Object<cvox.SpeechRule.DynamicCstrAttrib, string>}
*/
cvox.SpeechRule.DynamicCstr;
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/speech_rule_engine.js b/chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/speech_rule_engine.js
index 39bc8ac0adf..d7be38210ec 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/speech_rule_engine.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/speech_rule_engine.js
@@ -108,7 +108,7 @@ cvox.SpeechRuleEngine.prototype.constructString = function(node, expr) {
* Computes a speech object for a given node. Returns the empty list if
* no node is given.
* @param {Node} node The node to be evaluated.
- * @return {!Array.<cvox.NavDescription>} A list of navigation descriptions for
+ * @return {!Array<cvox.NavDescription>} A list of navigation descriptions for
* that node.
*/
cvox.SpeechRuleEngine.prototype.evaluateNode = function(node) {
@@ -122,7 +122,7 @@ cvox.SpeechRuleEngine.prototype.evaluateNode = function(node) {
/**
* Applies rules recursively to compute the final speech object.
* @param {!Node} node Node to apply the speech rule to.
- * @return {!Array.<cvox.NavDescription>} A list of Navigation descriptions.
+ * @return {!Array<cvox.NavDescription>} A list of Navigation descriptions.
* @private
*/
cvox.SpeechRuleEngine.prototype.evaluateTree_ = function(node) {
@@ -179,7 +179,7 @@ cvox.SpeechRuleEngine.prototype.evaluateTree_ = function(node) {
/**
* Evaluates a list of nodes into a list of navigation descriptions.
- * @param {!Array.<Node>} nodes Array of nodes.
+ * @param {!Array<Node>} nodes Array of nodes.
* @param {string} sepFunc Name of a function used to compute a separator
* between every element.
* @param {string} separator A string that is used as argument to the sepFunc or
@@ -188,7 +188,7 @@ cvox.SpeechRuleEngine.prototype.evaluateTree_ = function(node) {
* for every element in the list.
* @param {string} context Additional context string that is given to the
* ctxtFunc function or used directly if ctxtFunc is not supplied.
- * @return {Array.<cvox.NavDescription>} A list of Navigation descriptions.
+ * @return {Array<cvox.NavDescription>} A list of Navigation descriptions.
* @private
*/
cvox.SpeechRuleEngine.prototype.evaluateNodeList_ = function(
@@ -237,11 +237,11 @@ cvox.SpeechRuleEngine.propMap = {'pitch': cvox.AbstractTts.RELATIVE_PITCH,
/**
* Adds personality to every Navigation Descriptions in input list.
- * @param {Array.<cvox.NavDescription>} navs A list of Navigation descriptions.
+ * @param {Array<cvox.NavDescription>} navs A list of Navigation descriptions.
* @param {Object} props Property dictionary.
* TODO (sorge) Fully specify, when we have finalised the speech rule
* format.
- * @return {Array.<cvox.NavDescription>} The modified array.
+ * @return {Array<cvox.NavDescription>} The modified array.
* @private
*/
cvox.SpeechRuleEngine.prototype.addPersonality_ = function(navs, props) {
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/speech_rule_evaluator.js b/chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/speech_rule_evaluator.js
index d51e4595d11..a355c81daf7 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/speech_rule_evaluator.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/speech_rule_evaluator.js
@@ -23,6 +23,6 @@ cvox.SpeechRuleEvaluator = goog.abstractMethod;
/**
* Default evaluation of a node if no speech rule is applicable.
* @param {!Node} node The target node (or root of subtree).
- * @return {!Array.<cvox.NavDescription>} The resulting description.
+ * @return {!Array<cvox.NavDescription>} The resulting description.
*/
cvox.SpeechRuleEvaluator.prototype.evaluateDefault = goog.abstractMethod;
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/speech_rule_functions.js b/chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/speech_rule_functions.js
index 2e865487e50..f4edabb8255 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/speech_rule_functions.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/speech_rule_functions.js
@@ -24,7 +24,7 @@ cvox.SpeechRuleFunctions = function() { };
* Private superclass of all the custom function stores.
* @constructor
* @param {string} prefix A prefix string for the function names.
- * @param {Object.<string, Function>} store Storage object.
+ * @param {Object<string, Function>} store Storage object.
* @private
*/
cvox.SpeechRuleFunctions.Store_ = function(prefix, store) {
@@ -59,7 +59,7 @@ cvox.SpeechRuleFunctions.Store_.prototype.lookup = function(name) {
/**
* Context function for use in speech rules.
- * @typedef {function(!Node): Array.<Node>}
+ * @typedef {function(!Node): Array<Node>}
*/
cvox.SpeechRuleFunctions.CustomQuery;
@@ -70,7 +70,7 @@ cvox.SpeechRuleFunctions.CustomQuery;
*/
cvox.SpeechRuleFunctions.CustomQueries = function() {
var store =
- /** @type {Object.<string, cvox.SpeechRuleFunctions.CustomQuery>} */ ({});
+ /** @type {Object<string, cvox.SpeechRuleFunctions.CustomQuery>} */ ({});
goog.base(this, 'CQF', store);
};
goog.inherits(cvox.SpeechRuleFunctions.CustomQueries,
@@ -90,7 +90,7 @@ cvox.SpeechRuleFunctions.CustomString;
*/
cvox.SpeechRuleFunctions.CustomStrings = function() {
var store =
- /** @type {Object.<string, cvox.SpeechRuleFunctions.CustomString>} */ ({});
+ /** @type {Object<string, cvox.SpeechRuleFunctions.CustomString>} */ ({});
goog.base(this, 'CSF', store);
};
goog.inherits(cvox.SpeechRuleFunctions.CustomStrings,
@@ -99,7 +99,7 @@ goog.inherits(cvox.SpeechRuleFunctions.CustomStrings,
/**
* Context function for use in speech rules.
- * @typedef {function(Array.<Node>, ?string): (function(): string)}
+ * @typedef {function(Array<Node>, ?string): (function(): string)}
*/
cvox.SpeechRuleFunctions.ContextFunction;
@@ -110,7 +110,7 @@ cvox.SpeechRuleFunctions.ContextFunction;
*/
cvox.SpeechRuleFunctions.ContextFunctions = function() {
var store =
- /** @type {Object.<string, cvox.SpeechRuleFunctions.ContextFunction>} */
+ /** @type {Object<string, cvox.SpeechRuleFunctions.ContextFunction>} */
({});
goog.base(this, 'CTXF', store);
};
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/speech_rule_store.js b/chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/speech_rule_store.js
index 66ac5c5ae2b..0ab964ac642 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/speech_rule_store.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/speech_rule_store.js
@@ -44,7 +44,7 @@ cvox.SpeechRuleStore.prototype.findRule = goog.abstractMethod;
/**
* Retrieves all rules satisfying a given predicate.
* @param {function(cvox.SpeechRule): boolean} pred A predicate on speech rules.
- * @return {Array.<cvox.SpeechRule>} All speech rules in the store satisfying
+ * @return {Array<cvox.SpeechRule>} All speech rules in the store satisfying
* pred.
*/
cvox.SpeechRuleStore.prototype.findAllRules = goog.abstractMethod;
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/store_util.js b/chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/store_util.js
index acc65fa07a7..f7d53356be0 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/store_util.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/speech_rules/store_util.js
@@ -12,7 +12,7 @@ goog.provide('cvox.StoreUtil');
/**
* Count list of nodes and concatenate this with the context string.
* Returns a closure with a local state.
- * @param {Array.<Node>} nodes A node array.
+ * @param {Array<Node>} nodes A node array.
* @param {?string} context A context string.
* @return {function(): string} A function returning a string.
*/
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings.grd b/chromium/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings.grd
index 67c15d20a67..5ca3676de27 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings.grd
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings.grd
@@ -461,7 +461,7 @@
<message desc="Labels the voice selection list box." name="IDS_CHROMEVOX_OPTIONS_VOICES_DESCRIPTION">
Change the current voice by selecting an option from the list below.
</message>
- <message desc="An options page section header for options about the ChromeVox braille table. This section lets users change the braille table by selecting a different braille table from a listbox. A braille table describes how text gets converted from a unicode encoding into a pattern of dots. This varies based on locale and contraction. See http://en.wikipedia.org/wiki/Braille for a more in-depth discussion." name="IDS_CHROMEVOX_OPTIONS_BRAILLE">
+ <message desc="An options page section header for options about the ChromeVox braille support. This section allows the user to customize varous aspects of the braille output and input support." name="IDS_CHROMEVOX_OPTIONS_BRAILLE">
Braille
</message>
<message desc="Labels the braille table type button when the current table is an 6 dot table. A braille table describes how text gets converted from a unicode encoding into a pattern of dots. This varies based on locale and contraction. See http://en.wikipedia.org/wiki/Braille for a more in-depth discussion." name="IDS_CHROMEVOX_OPTIONS_BRAILLE_TABLE_TYPE_6">
@@ -485,6 +485,9 @@
<message desc="Labels the braille table selection list box. A braille table describes how text gets converted from a unicode encoding into a pattern of dots. This varies based on locale and contraction. See http://en.wikipedia.org/wiki/Braille for a more in-depth discussion." name="IDS_CHROMEVOX_OPTIONS_BRAILLE_DESCRIPTION_8">
Change the current 8 dot braille table by selecting an option from the list below.
</message>
+ <message desc="Labels the checkbox that enables wrapping of words if a whole line doesn't fit on a braille display. When this option is enabled, an effort is made to keep the characters of words together on the display. Otherwise, as many characters as possible are put on each braille display line, possible splitting words between lines." name="IDS_CHROMEVOX_OPTIONS_BRAILLE_WORD_WRAP">
+ Enable word wrap
+ </message>
<message desc="An options page section header for options about key shortcuts. This section lets users change the key bindings for ChromeVox actions. The section has a list of actions and a text field to change the binding (e.g. Ctrl-B) for each action." name="IDS_CHROMEVOX_OPTIONS_KEYBOARD_SHORTCUTS">
Keyboard shortcuts
</message>
@@ -530,6 +533,9 @@
<message desc="Spoken when the user opens a Chrome menu named 'title'." name="IDS_CHROMEVOX_CHROME_MENU_OPENED">
<ph name="title">$1</ph> menu opened
</message>
+ <message desc="Spoken when the user closes any Chrome menu." name="IDS_CHROMEVOX_CHROME_MENU_CLOSED">
+ menu closed
+ </message>
<message desc="Describes a HTML checkbox named 'name' in the checked state." name="IDS_CHROMEVOX_DESCRIBE_CHECKBOX_CHECKED">
<ph name="name">$1</ph> checkbox checked
</message>
@@ -602,7 +608,7 @@
<message desc="Describes a HTML listbox named 'name'." name="IDS_CHROMEVOX_DESCRIBE_LISTBOX">
<ph name="value">$1</ph>, <ph name="name">$2</ph>, list box
</message>
- <message desc="Describes an unnamed HTML combo box." name="IDS_CHROMEVOX_DESCRIBE_UNNAMED_LISTBOX">
+ <message desc="Describes an unnamed HTML list box." name="IDS_CHROMEVOX_DESCRIBE_UNNAMED_LISTBOX">
<ph name="value">$1</ph>, list box
</message>
<message desc="Describes a HTML link named 'name'." name="IDS_CHROMEVOX_DESCRIBE_LINK">
@@ -644,6 +650,12 @@
<message desc="Spoken when the user enters a dialog with the text 'text'." name="IDS_CHROMEVOX_ENTERING_DIALOG">
Entered dialog
</message>
+ <message desc="Spoken for an element with role dialog." name="IDS_CHROMEVOX_DIALOG">
+ Dialog
+ </message>
+ <message desc="Brailled for an element with role dialog." name="IDS_CHROMEVOX_DIALOG_BRL">
+ dlg
+ </message>
<message desc="Spoken before the list of elements when a live region of a page is removed." name="IDS_CHROMEVOX_LIVE_REGIONS_REMOVED">
removed:
</message>
@@ -692,6 +704,9 @@
<message desc="Spoken when the user attempts a table mode command, but is not in a table." name="IDS_CHROMEVOX_NOT_INSIDE_TABLE">
Not inside table.
</message>
+ <message desc="Spoken when the user attempts to use a table command inside a table, but without using table mode." name="IDS_CHROMEVOX_NOT_IN_TABLE_MODE">
+ Not in table mode.
+ </message>
<message desc="Spoken, in table mode, when the user attempts to navigate to a non-existant next row." name="IDS_CHROMEVOX_NO_CELL_BELOW">
No cell below.
</message>
@@ -939,7 +954,7 @@
has submenu
</message>
<message desc="Brailled after a menu if the menu has a submenu." name="IDS_CHROMEVOX_ARIA_HAS_SUBMENU_BRL">
- +submnu
+ ->
</message>
<message desc='Spoken after an element is spoken if the element has a pop up. For example "Button Add friends has pop up"' name="IDS_CHROMEVOX_ARIA_HAS_POPUP">
has pop up
@@ -993,13 +1008,13 @@
Check box
</message>
<message desc="Braille of element with the ARIA role checkbox." name="IDS_CHROMEVOX_ARIA_ROLE_CHECKBOX_BRL">
- chx
+ chk
</message>
<message desc="Describes an element with the ARIA role combobox." name="IDS_CHROMEVOX_ARIA_ROLE_COMBOBOX">
Combo box
</message>
<message desc="Braille of element with the ARIA role combobox." name="IDS_CHROMEVOX_ARIA_ROLE_COMBOBOX_BRL">
- cbx
+ cbo
</message>
<message desc="Describes an element with the ARIA role dialog." name="IDS_CHROMEVOX_ARIA_ROLE_DIALOG">
Dialog
@@ -1017,7 +1032,7 @@
Cell
</message>
<message desc="Braille of element with the ARIA role gridcell." name="IDS_CHROMEVOX_ARIA_ROLE_GRIDCELL_BRL">
- cl
+ cll
</message>
<message desc="Describes the position of an element with the ARIA role gridcell." name="IDS_CHROMEVOX_ARIA_ROLE_GRIDCELL_POS">
row <ph name="row">$1</ph> column <ph name="col">$2</ph>
@@ -1028,12 +1043,6 @@
<message desc="Braille of element with the ARIA role link." name="IDS_CHROMEVOX_ARIA_ROLE_LINK_BRL">
lnk
</message>
- <message desc="Describes a single element with the ARIA role link with count." name="IDS_CHROMEVOX_ARIA_ROLE_LINK_SINGULAR">
- 1 link
- </message>
- <message desc="Describes multiple elements with the ARIA role link." name="IDS_CHROMEVOX_ARIA_ROLE_LINK_PLURAL">
- <ph name="num">$1</ph> links
- </message>
<message desc="Describes an element with the ARIA role listbox." name="IDS_CHROMEVOX_ARIA_ROLE_LISTBOX">
List box
</message>
@@ -1050,7 +1059,7 @@
Marquee
</message>
<message desc="Braille of element with the ARIA role marquee." name="IDS_CHROMEVOX_ARIA_ROLE_MARQUEE_BRL">
- maqe
+ marquee
</message>
<message desc="Describes an element with the ARIA role menu." name="IDS_CHROMEVOX_ARIA_ROLE_MENU">
Menu
@@ -1062,25 +1071,25 @@
Menu bar
</message>
<message desc="Braille of element with the ARIA role menubar." name="IDS_CHROMEVOX_ARIA_ROLE_MENUBAR_BRL">
- mnu br
+ mnubr
</message>
<message desc="Describes an element with the ARIA role menuitem." name="IDS_CHROMEVOX_ARIA_ROLE_MENUITEM">
Menu item
</message>
<message desc="Braille of element with the ARIA role menuitem." name="IDS_CHROMEVOX_ARIA_ROLE_MENUITEM_BRL">
- mnu itm
+ mnuitm
</message>
<message desc="Describes an element with the ARIA role menuitemcheckbox." name="IDS_CHROMEVOX_ARIA_ROLE_MENUITEMCHECKBOX">
Menu item check box
</message>
<message desc="Braille of element with the ARIA role menuitemcheckbox." name="IDS_CHROMEVOX_ARIA_ROLE_MENUITEMCHECKBOX_BRL">
- mnu itm chx
+ chkmnuitm
</message>
<message desc="Describes an element with the ARIA role menuitemradio." name="IDS_CHROMEVOX_ARIA_ROLE_MENUITEMRADIO">
Menu item radio button
</message>
<message desc="Braille of element with the ARIA role menuitemradio." name="IDS_CHROMEVOX_ARIA_ROLE_MENUITEMRADIO_BRL">
- mnu itm rd
+ rdmnuitm
</message>
<message desc="Describes an element with the ARIA role option." name="IDS_CHROMEVOX_ARIA_ROLE_OPTION">
''' '''
@@ -1092,31 +1101,31 @@
Pop-up button
</message>
<message desc="Braille of element with the ARIA role button, with a pop-up." name="IDS_CHROMEVOX_ARIA_ROLE_POPUP_BUTTON_BRL">
- pup btn
+ popbtn
</message>
<message desc="Describes an element with the ARIA role progressbar." name="IDS_CHROMEVOX_ARIA_ROLE_PROGRESSBAR">
Progress bar
</message>
<message desc="Braille of element with the ARIA role progressbar." name="IDS_CHROMEVOX_ARIA_ROLE_PROGRESSBAR_BRL">
- prog br
+ pgbar
</message>
<message desc="Describes an element with the ARIA role radio." name="IDS_CHROMEVOX_ARIA_ROLE_RADIO">
Radio button
</message>
<message desc="Braille of element with the ARIA role radio." name="IDS_CHROMEVOX_ARIA_ROLE_RADIO_BRL">
- rd
+ rbtn
</message>
<message desc="Describes an element with the ARIA role radiogroup." name="IDS_CHROMEVOX_ARIA_ROLE_RADIOGROUP">
Radio button group
</message>
<message desc="Braille of element with the ARIA role radiogroup." name="IDS_CHROMEVOX_ARIA_ROLE_RADIOGROUP_BRL">
- rd grp
+ rdgrp
</message>
<message desc="Describes an element with the ARIA role scrollbar." name="IDS_CHROMEVOX_ARIA_ROLE_SCROLLBAR">
Scroll bar
</message>
<message desc="Braille of element with the ARIA role scrollbar." name="IDS_CHROMEVOX_ARIA_ROLE_SCROLLBAR_BRL">
- scr br
+ scbr
</message>
<message desc="Describes an element with the ARIA role slider." name="IDS_CHROMEVOX_ARIA_ROLE_SLIDER">
Slider
@@ -1128,7 +1137,7 @@
Spin button
</message>
<message desc="Braille of element with the ARIA role spinbutton." name="IDS_CHROMEVOX_ARIA_ROLE_SPINBUTTON_BRL">
- spn btn
+ spnbtn
</message>
<message desc="Describes an element with the ARIA role status." name="IDS_CHROMEVOX_ARIA_ROLE_STATUS">
Status
@@ -1146,19 +1155,19 @@
Tab list
</message>
<message desc="Brailles an element with the ARIA role tablist." name="IDS_CHROMEVOX_ARIA_ROLE_TABLIST_BRL">
- tab lst
+ tablst
</message>
<message desc="Describes an element with the ARIA role tabpanel." name="IDS_CHROMEVOX_ARIA_ROLE_TABPANEL">
Tab panel
</message>
<message desc="Braille of element with the ARIA role tabpanel." name="IDS_CHROMEVOX_ARIA_ROLE_TABPANEL_BRL">
- tab pnl
+ tabpnl
</message>
<message desc="Describes an element with the ARIA role textbox." name="IDS_CHROMEVOX_ARIA_ROLE_TEXTBOX">
Text box
</message>
<message desc="Braille of element with the ARIA role textbox." name="IDS_CHROMEVOX_ARIA_ROLE_TEXTBOX_BRL">
- txtbx
+ ed
</message>
<message desc="Describes an element with the ARIA role timer." name="IDS_CHROMEVOX_ARIA_ROLE_TIMER">
Timer
@@ -1170,25 +1179,31 @@
Tool bar
</message>
<message desc="Braille of element with the ARIA role toolbar." name="IDS_CHROMEVOX_ARIA_ROLE_TOOLBAR_BRL">
- tl br
+ tlbar
</message>
<message desc="Describes an element with the ARIA role tooltip." name="IDS_CHROMEVOX_ARIA_ROLE_TOOLTIP">
Tool tip
</message>
<message desc="Braille of element with the ARIA role tooltip." name="IDS_CHROMEVOX_ARIA_ROLE_TOOLTIP_BRL">
- tl tp
+ tltip
+ </message>
+ <message desc="Describes an element with the ARIA role tree." name="IDS_CHROMEVOX_ARIA_ROLE_TREE">
+ Tree
+ </message>
+ <message desc="Braille of element with the ARIA role tree." name="IDS_CHROMEVOX_ARIA_ROLE_TREE_BRL">
+ tree
</message>
<message desc="Describes an element with the ARIA role treeitem." name="IDS_CHROMEVOX_ARIA_ROLE_TREEITEM">
Tree item
</message>
<message desc="Braille of element with the ARIA role treeitem." name="IDS_CHROMEVOX_ARIA_ROLE_TREEITEM_BRL">
- tr itm
+ tritm
</message>
<message desc="Describes an element with the ARIA role article." name="IDS_CHROMEVOX_ARIA_ROLE_ARTICLE">
Article
</message>
<message desc="Braille of element with the ARIA role article." name="IDS_CHROMEVOX_ARIA_ROLE_ARTICLE_BRL">
- acl
+ article
</message>
<message desc="Describes an element with the ARIA role application." name="IDS_CHROMEVOX_ARIA_ROLE_APPLICATION">
Application
@@ -1206,19 +1221,19 @@
Column header
</message>
<message desc="Braille of element with the ARIA role columnheader." name="IDS_CHROMEVOX_ARIA_ROLE_COLUMNHEADER_BRL">
- clm hd
+ colhdr
</message>
<message desc="Describes an element with the ARIA role complementary." name="IDS_CHROMEVOX_ARIA_ROLE_COMPLEMENTARY">
Complementary
</message>
<message desc="Braille of element with the ARIA role complementary." name="IDS_CHROMEVOX_ARIA_ROLE_COMPLEMENTARY_BRL">
- cmpy
+ complementary
</message>
<message desc="Describes an element with the ARIA role contentinfo." name="IDS_CHROMEVOX_ARIA_ROLE_CONTENTINFO">
Content info
</message>
<message desc="Braille of element with the ARIA role contentinfo." name="IDS_CHROMEVOX_ARIA_ROLE_CONTENTINFO_BRL">
- cnt in
+ cntntinfo
</message>
<message desc="Describes an element with the ARIA role definition." name="IDS_CHROMEVOX_ARIA_ROLE_DEFINITION">
Definition
@@ -1242,13 +1257,7 @@
Form
</message>
<message desc="Braille of element with the ARIA role form." name="IDS_CHROMEVOX_ARIA_ROLE_FORM_BRL">
- frm
- </message>
- <message desc="Describes a single element with the ARIA role form with count." name="IDS_CHROMEVOX_ARIA_ROLE_FORM_SINGULAR">
- 1 form
- </message>
- <message desc="Describes multiple elements with the ARIA role form." name="IDS_CHROMEVOX_ARIA_ROLE_FORM_PLURAL">
- <ph name="num">$1</ph> forms
+ form
</message>
<message desc="Describes an element with the ARIA role group." name="IDS_CHROMEVOX_ARIA_ROLE_GROUP">
Group
@@ -1260,7 +1269,7 @@
Heading
</message>
<message desc="Braille of element with the ARIA role heading." name="IDS_CHROMEVOX_ARIA_ROLE_HEADING_BRL">
- hd
+ hdng
</message>
<message desc="Describes an element with the ARIA role img." name="IDS_CHROMEVOX_ARIA_ROLE_IMG">
Image
@@ -1314,37 +1323,37 @@
Row header
</message>
<message desc="Braille of element with the ARIA role rowheader." name="IDS_CHROMEVOX_ARIA_ROLE_ROWHEADER_BRL">
- rw hd
+ rwhdr
</message>
<message desc="Describes an element with the ARIA role search." name="IDS_CHROMEVOX_ARIA_ROLE_SEARCH">
Search
</message>
<message desc="Braille of element with the ARIA role search." name="IDS_CHROMEVOX_ARIA_ROLE_SEARCH_BRL">
- srch
+ srched
</message>
<message desc="Describes an element with the ARIA role separator." name="IDS_CHROMEVOX_ARIA_ROLE_SEPARATOR">
Separator
</message>
<message desc="Braille of element with the ARIA role separator." name="IDS_CHROMEVOX_ARIA_ROLE_SEPARATOR_BRL">
- sprtr
+ seprtr
</message>
<message desc="Describes an element with the ARIA attribute aria-autocomplete=inline." name="IDS_CHROMEVOX_ARIA_AUTOCOMPLETE_INLINE">
Autocompletion inline
</message>
<message desc="Braille of element with the ARIA attribute aria-autocomplete=inline." name="IDS_CHROMEVOX_ARIA_AUTOCOMPLETE_INLINE_BRL">
- autocomplete
+ autoinl
</message>
<message desc="Describes an element with the ARIA attribute aria-autocomplete=list." name="IDS_CHROMEVOX_ARIA_AUTOCOMPLETE_LIST">
Autocompletion list
</message>
<message desc="Braille of element with the ARIA attribute aria-autocomplete=list." name="IDS_CHROMEVOX_ARIA_AUTOCOMPLETE_LIST_BRL">
- autocomplete lst
+ autolst
</message>
<message desc="Describes an element with the ARIA attribute aria-autocomplete=both." name="IDS_CHROMEVOX_ARIA_AUTOCOMPLETE_BOTH">
Autocompletion inline and list
</message>
<message desc="Brailles an element with the ARIA attribute aria-autocomplete=both." name="IDS_CHROMEVOX_ARIA_AUTOCOMPLETE_BOTH_BRL">
- autocomplete lst
+ autoinl+lst
</message>
<message desc="Describes an element with the ARIA attribute aria-checked=true." name="IDS_CHROMEVOX_ARIA_CHECKED_TRUE">
Checked
@@ -1362,25 +1371,25 @@
Partially checked
</message>
<message desc="Braille of element with the ARIA attribute aria-checked=mixed." name="IDS_CHROMEVOX_ARIA_CHECKED_MIXED_BRL">
- /x
+ -
</message>
<message desc="Describes an element with the ARIA attribute aria-disabled=true." name="IDS_CHROMEVOX_ARIA_DISABLED_TRUE">
Disabled
</message>
<message desc="Braille of element with the ARIA attribute aria-disabled=true." name="IDS_CHROMEVOX_ARIA_DISABLED_TRUE_BRL">
- =
+ xx
</message>
<message desc="Describes an element with the ARIA attribute aria-expanded=true." name="IDS_CHROMEVOX_ARIA_EXPANDED_TRUE">
Expanded
</message>
<message desc="Braille of element with the ARIA attribute aria-expanded=true." name="IDS_CHROMEVOX_ARIA_EXPANDED_TRUE_BRL">
- &gt;
+ -
</message>
<message desc="Describes an element with the ARIA attribute aria-expanded=false." name="IDS_CHROMEVOX_ARIA_EXPANDED_FALSE">
Collapsed
</message>
<message desc="Braille of element with the ARIA attribute aria-expanded=false." name="IDS_CHROMEVOX_ARIA_EXPANDED_FALSE_BRL">
- &lt;
+ +
</message>
<message desc="Describes an element with the ARIA attribute aria-invalid=true." name="IDS_CHROMEVOX_ARIA_INVALID_TRUE">
Invalid input
@@ -1392,7 +1401,7 @@
Grammatical mistake detected
</message>
<message desc="Braille of element with the ARIA attribute aria-invalid=grammar." name="IDS_CHROMEVOX_ARIA_INVALID_GRAMMAR_BRL">
- Grammatical mistake detected
+ grammatical mistake
</message>
<message desc="Describes an element with the ARIA attribute aria-invalid=spelling." name="IDS_CHROMEVOX_ARIA_INVALID_SPELLING">
Spelling mistake detected
@@ -1404,19 +1413,19 @@
Multi line
</message>
<message desc="Braille of element with the ARIA attribute aria-multiline=true." name="IDS_CHROMEVOX_ARIA_MULTILINE_TRUE_BRL">
- mult ln
+ multln
</message>
<message desc="Describes an element with the ARIA attribute aria-multiselectable=true." name="IDS_CHROMEVOX_ARIA_MULTISELECTABLE_TRUE">
Multi select
</message>
<message desc="Braille of element with the ARIA attribute aria-multiselectable=true." name="IDS_CHROMEVOX_ARIA_MULTISELECTABLE_TRUE_BRL">
- mult sel
+ multsel
</message>
<message desc="Describes an element with the ARIA attribute aria-pressed=true." name="IDS_CHROMEVOX_ARIA_PRESSED_TRUE">
Pressed
</message>
<message desc="Braille of element with the ARIA attribute aria-pressed=true." name="IDS_CHROMEVOX_ARIA_PRESSED_TRUE_BRL">
- x
+ =
</message>
<message desc="Describes an element with the ARIA attribute aria-pressed=false." name="IDS_CHROMEVOX_ARIA_PRESSED_FALSE">
Not pressed
@@ -1428,7 +1437,7 @@
Partially pressed
</message>
<message desc="Braille of element with the ARIA attribute aria-pressed=mixed." name="IDS_CHROMEVOX_ARIA_PRESSED_MIXED_BRL">
- /x
+ -
</message>
<message desc="Describes an element with the ARIA attribute aria-readonly=true." name="IDS_CHROMEVOX_ARIA_READONLY_TRUE">
Read only
@@ -1440,7 +1449,7 @@
Required
</message>
<message desc="Braille of element with the ARIA attribute aria-required=true." name="IDS_CHROMEVOX_ARIA_REQUIRED_TRUE_BRL">
- req
+ rq
</message>
<message desc="Describes an element with the ARIA attribute aria-selected=true." name="IDS_CHROMEVOX_ARIA_SELECTED_TRUE">
Selected
@@ -1457,9 +1466,21 @@
<message desc="Spoken to describe an &lt;a&gt; tag." name="IDS_CHROMEVOX_TAG_LINK">
Link
</message>
+ <message desc="Spoken to describe a visited link." name="IDS_CHROMEVOX_VISITED_LINK">
+ Visited link
+ </message>
+ <message desc="Spoken to describe the visited state (e.g. for links)." name="IDS_CHROMEVOX_VISITED_STATE">
+ Visited
+ </message>
+ <message desc="Brailled to describe the visited state (e.g. for links)." name="IDS_CHROMEVOX_VISITED_STATE_BRL">
+ vtd
+ </message>
<message desc="Brailled to describe an &lt;a&gt; tag." name="IDS_CHROMEVOX_TAG_LINK_BRL">
lnk
</message>
+ <message desc="Brailled to describe a visited link." name="IDS_CHROMEVOX_VISITED_LINK_BRL">
+ vlnk
+ </message>
<message desc="Spoken to describe a &lt;button&gt; tag." name="IDS_CHROMEVOX_TAG_BUTTON">
Button
</message>
@@ -1518,13 +1539,13 @@
Combo box
</message>
<message desc="Brailled to describe a &lt;select&gt; tag." name="IDS_CHROMEVOX_TAG_SELECT_BRL">
- cbx
+ cbo
</message>
<message desc="Spoken to describe a &lt;textarea&gt; tag." name="IDS_CHROMEVOX_TAG_TEXTAREA">
Text area
</message>
<message desc="Brailled to describe a &lt;textarea&gt; tag." name="IDS_CHROMEVOX_TAG_TEXTAREA_BRL">
- txta
+ mled
</message>
<message desc="Spoken to describe a &lt;table&gt; tag." name="IDS_CHROMEVOX_TAG_TABLE">
table
@@ -1542,7 +1563,7 @@
Section
</message>
<message desc="Brailled to describe a &lt;section&gt; tag." name="IDS_CHROMEVOX_TAG_SECTION_BRL">
- stn
+ sctn
</message>
<message desc="Spoken to describe a &lt;nav&gt; tag." name="IDS_CHROMEVOX_TAG_NAV">
Navigation
@@ -1566,7 +1587,7 @@
Heading group
</message>
<message desc="Brailled to describe a &lt;hgroup&gt; tag." name="IDS_CHROMEVOX_TAG_HGROUP_BRL">
- hgrp
+ hdnggrp
</message>
<message desc="Spoken to describe a &lt;header&gt; tag." name="IDS_CHROMEVOX_TAG_HEADER">
Header
@@ -1614,7 +1635,7 @@
Check box
</message>
<message desc="Brailles an &lt;input&gt; element with type=checkbox." name="IDS_CHROMEVOX_INPUT_TYPE_CHECKBOX_BRL">
- chx
+ chk
</message>
<message desc="Describes an &lt;input&gt; element with type=color." name="IDS_CHROMEVOX_INPUT_TYPE_COLOR">
Color picker
@@ -1644,7 +1665,7 @@
Edit text, email entry
</message>
<message desc="Brailles an &lt;input&gt; element with type=email." name="IDS_CHROMEVOX_INPUT_TYPE_EMAIL_BRL">
- edtxt email
+ @ed
</message>
<message desc="Describes an &lt;input&gt; element with type=file." name="IDS_CHROMEVOX_INPUT_TYPE_FILE">
File selection
@@ -1668,25 +1689,25 @@
Edit text numeric only
</message>
<message desc="Brailles an &lt;input&gt; element with type=number." name="IDS_CHROMEVOX_INPUT_TYPE_NUMBER_BRL">
- edtxt#
+ #ed
</message>
<message desc="Describes an &lt;input&gt; element with type=password." name="IDS_CHROMEVOX_INPUT_TYPE_PASSWORD">
Password edit text
</message>
<message desc="Brailles an &lt;input&gt; element with type=password." name="IDS_CHROMEVOX_INPUT_TYPE_PASSWORD_BRL">
- pwd edtxt
+ pwded
</message>
<message desc="Describes an &lt;input&gt; element with type=radio." name="IDS_CHROMEVOX_INPUT_TYPE_RADIO">
Radio button
</message>
<message desc="Brailles an &lt;input&gt; element with type=radio." name="IDS_CHROMEVOX_INPUT_TYPE_RADIO_BRL">
- rd btn
+ rbtn
</message>
<message desc="Describes an &lt;input&gt; element with type=range." name="IDS_CHROMEVOX_INPUT_TYPE_RANGE">
Slider
</message>
<message desc="Brailles an &lt;input&gt; element with type=range." name="IDS_CHROMEVOX_INPUT_TYPE_RANGE_BRL">
- slr
+ sldr
</message>
<message desc="Describes an &lt;input&gt; element with type=reset." name="IDS_CHROMEVOX_INPUT_TYPE_RESET">
Reset
@@ -1698,7 +1719,7 @@
Edit text, search entry
</message>
<message desc="Brailles an &lt;input&gt; element with type=search." name="IDS_CHROMEVOX_INPUT_TYPE_SEARCH_BRL">
- search edtxt
+ srched
</message>
<message desc="Describes an &lt;input&gt; element with type=submit." name="IDS_CHROMEVOX_INPUT_TYPE_SUBMIT">
Button
@@ -1710,19 +1731,19 @@
Edit text, number entry
</message>
<message desc="Brailles an &lt;input&gt; element with type=tel." name="IDS_CHROMEVOX_INPUT_TYPE_TEL_BRL">
- tele# edtxt
+ #ed
</message>
<message desc="Describes an &lt;input&gt; element with type=text." name="IDS_CHROMEVOX_INPUT_TYPE_TEXT">
Edit text
</message>
<message desc="Brailles an &lt;input&gt; element with type=text." name="IDS_CHROMEVOX_INPUT_TYPE_TEXT_BRL">
- edtxt
+ ed
</message>
<message desc="Describes an &lt;input&gt; element with type=url." name="IDS_CHROMEVOX_INPUT_TYPE_URL">
Edit text, URL entry
</message>
<message desc="Brailles an &lt;input&gt; element with type=url." name="IDS_CHROMEVOX_INPUT_TYPE_URL_BRL">
- url edtxt
+ urled
</message>
<message desc="Describes an &lt;input&gt; element with type=week." name="IDS_CHROMEVOX_INPUT_TYPE_WEEK">
Week of the year control
@@ -1734,7 +1755,7 @@
Internal link
</message>
<message desc="Brailles to describe a &lt;a&gt; tag with a link to an internal anchor." name="IDS_CHROMEVOX_INTERNAL_LINK_BRL">
- int lnk
+ intlnk
</message>
<message desc="In an editable text box, describes a blank line." name="IDS_CHROMEVOX_TEXT_BOX_BLANK">
Blank
@@ -1758,7 +1779,7 @@
with submenu
</message>
<message desc="Brailled after an menu is spoken if the menu has a submenu." name="IDS_CHROMEVOX_HAS_SUBMENU_BRL">
- +submnu
+ ->
</message>
<message desc="Phrase indicating a control has a pop-up component to it." name="IDS_CHROMEVOX_HAS_POPUP">
has popup
@@ -1802,12 +1823,12 @@
<message desc="The spoken feedback when ChromeVox becomes inactive." name="IDS_CHROMEVOX_CHROMEVOX_INACTIVE">
ChromeVox is now inactive.
</message>
+ <message desc="The description of the command to toggle ChromeVox classic." name="IDS_CHROMEVOX_TOGGLE_CHROMEVOX">
+ Toggle ChromeVox classic.
+ </message>
<message desc="The indicator of a pause to tts." name="IDS_CHROMEVOX_PAUSE">
, '''
</message>
- <message desc="The indicator of a end to tts." name="IDS_CHROMEVOX_END">
- . '''
- </message>
<message desc="Description of the previous different element command displayed in the options page." name="IDS_CHROMEVOX_PREVIOUS_DIFFERENT_ELEMENT">
Previous different element.
</message>
@@ -1826,9 +1847,6 @@
<message desc="Verbal indication of no more different elements." name="IDS_CHROMEVOX_NO_MORE_DIFFERENT_ELEMENTS">
No more different elements.
</message>
- <message desc="Describes an element with the ARIA role link." name="IDS_CHROMEVOX_INDEX_TOTAL">
- <ph name="index">$1</ph> of <ph name="total">$2</ph>
- </message>
<message desc="Description of the enter group exploration user command. Displayed in the Options page." name="IDS_CHROMEVOX_ENTER_CSS_SPACE">
Enter group exploration
</message>
@@ -1986,7 +2004,7 @@
Math
</message>
<message desc="Brailled phrase indicating the current selection is a Math object." name="IDS_CHROMEVOX_MATH_EXPR_BRL">
- Math
+ math
</message>
<message desc="Describes an element with the ARIA role math." name="IDS_CHROMEVOX_NOT_INSIDE_MATH">
Not inside math
@@ -2067,7 +2085,7 @@
clickable
</message>
<message desc="Brailled to describe a clickable element." name="IDS_CHROMEVOX_CLICKABLE_BRL">
- clickable
+ clk
</message>
<message desc="The description of the previous character command. Displayed in the Options page." name="IDS_CHROMEVOX_PREVIOUS_CHARACTER">
Previous Character
@@ -2216,12 +2234,6 @@
<message desc='Used as a phonetic word hint for a particular letter. The word is used to clarify similarly sounding letters like m and n. This mapping is taken directly from the NATO phonetic standard: https://en.wikipedia.org/wiki/NATO_phonetic_alphabet Please retain the structure of this string. The structure is of the form {"letter": "phonetic word equivalent", ..., "letter": "phonetic word equivalent"}. The first part of the mapping (letter) should be all letters of the localization in lower case. The second part (phonetic word equivalent) should be the word that describes the letter.' name="IDS_CHROMEVOX_PHONETIC_MAP">
{"a": "alpha", "b": "bravo", "c": "charlie", "d": "delta", "e": "echo", "f": "foxtrot", "g": "golf", "h": "hotel", "i": "india", "j": "juliet","k": "kilo", "l": "lima", "m": "mike", "n": "november", "o": "oscar","p": "papa", "q": "quebec", "r": "romeo", "s": "sierra", "t": "tango", "u": "uniform", "v": "victor", "w": "whiskey","x": "xray", "y": "yankee", "z": "zulu"}
</message>
- <message desc="Announces that the current page has 1 alert." name="IDS_CHROMEVOX_PAGE_HAS_ONE_ALERT_SINGULAR">
- This page has 1 alert
- </message>
- <message desc="Announces that the current page has multiple alerts." name="IDS_CHROMEVOX_PAGE_HAS_ALERTS_PLURAL">
- This page has <ph name="num">$1</ph> alerts
- </message>
<message desc="Describes a key sequence that will let the user review (examine and make a decision on) all of the alerts on the page." name="IDS_CHROMEVOX_REVIEW_ALERTS">
Press Alt+Shift+A to review alerts
</message>
@@ -2258,12 +2270,6 @@
<message desc="Describes the perform default action command. This is usually triggered by hitting the enter key over a control. Shown in options page." name="IDS_CHROMEVOX_PERFORM_DEFAULT_ACTION">
Perform default action
</message>
- <message desc="Spoken to describe a link (url) that has been previously visited." name="IDS_CHROMEVOX_VISITED_URL">
- visited
- </message>
- <message desc="Brailled to describe a link (url) that has been previously visited." name="IDS_CHROMEVOX_VISITED_URL_BRL">
- visited
- </message>
<message desc="Exclamation (!) character description." name="IDS_CHROMEVOX_EXCLAMATION">
{COUNT, plural, =1 {exclamation point}other {# exclamation points}}
</message>
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/testing/assert_additions.js b/chromium/chrome/browser/resources/chromeos/chromevox/testing/assert_additions.js
index 394a0ea3478..2811966fe7f 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/testing/assert_additions.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/testing/assert_additions.js
@@ -47,8 +47,8 @@ function assertException(msg, fn, error) {
/**
* Asserts that two arrays of strings are equal.
- * @param {Array.<string>} array1 The expected array.
- * @param {Array.<string>} array2 The test array.
+ * @param {Array<string>} array1 The expected array.
+ * @param {Array<string>} array2 The test array.
*/
function assertEqualStringArrays(array1, array2) {
var same = true;
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/testing/callback_helper.js b/chromium/chrome/browser/resources/chromeos/chromevox/testing/callback_helper.js
new file mode 100644
index 00000000000..fba23c3b420
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/testing/callback_helper.js
@@ -0,0 +1,45 @@
+// Copyright 2015 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.
+
+/**
+ * Creates wrappers for callbacks and calls testDone() when all callbacks
+ * have been invoked.
+ * @param {testing.Test} fixture
+ */
+function CallbackHelper(fixture) {
+ /** @type {Object} fixture */
+ this.fixture_ = fixture;
+ /** @type {number} */
+ this.pendingCallbacks_ = 0;
+}
+
+CallbackHelper.prototype = {
+ /**
+ * @param {Function=} opt_callback
+ * @return {Function}
+ */
+ wrap: function(opt_callback) {
+ var callback = opt_callback || function() {};
+ var savedArgs = new SaveMockArguments();
+ var lastCall = null;
+ var completionAction = callFunctionWithSavedArgs(savedArgs, function() {
+ if (lastCall) {
+ throw new Error('Called more than once, first call here: ' + lastCall);
+ } else {
+ lastCall = new Error().stack;
+ }
+ callback.apply(this.fixture_, arguments);
+ if (--this.pendingCallbacks_ <= 0)
+ testDone();
+ }.bind(this));
+ // runAllActionsAsync catches exceptions and puts them in the test
+ // framework's list of errors and fails the test.
+ var runAll = runAllActionsAsync(WhenTestDone.ASSERT, completionAction);
+ ++this.pendingCallbacks_;
+ return function() {
+ savedArgs.arguments = Array.prototype.slice.call(arguments);
+ runAll.invoke();
+ }
+ }
+};
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/testing/chromevox_e2e_test_base.js b/chromium/chrome/browser/resources/chromeos/chromevox/testing/chromevox_e2e_test_base.js
index 4728104eed0..16a0a5a32e9 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/testing/chromevox_e2e_test_base.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/testing/chromevox_e2e_test_base.js
@@ -3,7 +3,8 @@
// found in the LICENSE file.
GEN_INCLUDE([
- 'chrome/browser/resources/chromeos/chromevox/testing/common.js']);
+ 'chrome/browser/resources/chromeos/chromevox/testing/common.js',
+ 'chrome/browser/resources/chromeos/chromevox/testing/callback_helper.js']);
/**
* Base test fixture for ChromeVox end to end tests.
@@ -12,7 +13,9 @@ GEN_INCLUDE([
* background page context.
* @constructor
*/
-function ChromeVoxE2ETest() {}
+function ChromeVoxE2ETest() {
+ this.callbackHelper_ = new CallbackHelper(this);
+}
ChromeVoxE2ETest.prototype = {
__proto__: testing.Test.prototype,
@@ -43,11 +46,6 @@ ChromeVoxE2ETest.prototype = {
/** @override */
testGenPreamble: function() {
GEN_BLOCK(function() {/*!
- if (chromeos::AccessibilityManager::Get()->IsSpokenFeedbackEnabled()) {
- chromeos::AccessibilityManager::Get()->EnableSpokenFeedback(false,
- ui::A11Y_NOTIFICATION_NONE);
- }
-
base::Closure load_cb =
base::Bind(&chromeos::AccessibilityManager::EnableSpokenFeedback,
base::Unretained(chromeos::AccessibilityManager::Get()),
@@ -58,11 +56,26 @@ ChromeVoxE2ETest.prototype = {
},
/**
- * Run a test with the specified HTML snippet loaded.
+ * Launch a new tab, wait until tab status complete, then run callback.
* @param {function() : void} doc Snippet wrapped inside of a function.
* @param {function()} callback Called once the document is ready.
*/
- runWithDocument: function(doc, callback) {
+ runWithLoadedTab: function(doc, callback) {
+ this.launchNewTabWithDoc(doc, function(tab) {
+ chrome.tabs.onUpdated.addListener(function(tabId, changeInfo) {
+ if (tabId == tab.id && changeInfo.status == 'complete') {
+ callback(tabId);
+ }
+ });
+ });
+ },
+
+ /**
+ * Launches the given document in a new tab.
+ * @param {function() : void} doc Snippet wrapped inside of a function.
+ * @param {function()} opt_callback Called once the document is created.
+ */
+ runWithTab: function(doc, opt_callback) {
var docString = TestUtils.extractHtmlFromCommentEncodedString(doc);
var url = 'data:text/html,<!doctype html>' +
docString +
@@ -71,13 +84,38 @@ ChromeVoxE2ETest.prototype = {
active: true,
url: url
};
- chrome.tabs.create(createParams, function(tab) {
- chrome.tabs.onUpdated.addListener(function(tabId, changeInfo) {
- if (tabId == tab.id && changeInfo.status == 'complete') {
- callback();
- }
- });
- });
+ chrome.tabs.create(createParams, opt_callback);
+ },
+
+ /**
+ * Send a key to the page.
+ * @param {number} tabId Of the page.
+ * @param {string} key Name of the key (e.g. Down).
+ * @param {string} elementQueryString
+ */
+ sendKeyToElement: function(tabId, key, elementQueryString) {
+ var code = TestUtils.extractHtmlFromCommentEncodedString(function() {/*!
+ var target = document.body.querySelector('$1');
+ target.focus();
+ var evt = document.createEvent('KeyboardEvent');
+ evt.initKeyboardEvent('keydown', true, true, window, '$0', 0, false,
+ false, false, false);
+ document.activeElement.dispatchEvent(evt);
+ */}, [key, elementQueryString]);
+
+ chrome.tabs.executeScript(tabId, {code: code});
+ },
+
+ /**
+ * Creates a callback that optionally calls {@code opt_callback} when
+ * called. If this method is called one or more times, then
+ * {@code testDone()} will be called when all callbacks have been called.
+ * @param {Function=} opt_callback Wrapped callback that will have its this
+ * reference bound to the test fixture.
+ * @return {Function}
+ */
+ newCallback: function(opt_callback) {
+ return this.callbackHelper_.wrap(opt_callback);
}
};
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/testing/chromevox_next_e2e_test_base.js b/chromium/chrome/browser/resources/chromeos/chromevox/testing/chromevox_next_e2e_test_base.js
index b25dbf8b997..4493cec5082 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/testing/chromevox_next_e2e_test_base.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/testing/chromevox_next_e2e_test_base.js
@@ -14,32 +14,33 @@ GEN_INCLUDE(['chrome/browser/resources/chromeos/chromevox/testing/' +
* @constructor
* @extends {ChromeVoxE2ETest}
*/
-function ChromeVoxNextE2ETest() {}
+function ChromeVoxNextE2ETest() {
+ ChromeVoxE2ETest.call(this);
+}
ChromeVoxNextE2ETest.prototype = {
__proto__: ChromeVoxE2ETest.prototype,
/**
- * This method is called without |this| bound to an instance of
- * ChromeVoxNextE2ETest.
- * @override
+ * Launches a new tab with the given document, and runs callback when a load
+ * complete fires.
+ * @param {function() : void} doc Snippet wrapped inside of a function.
+ * @param {function()} opt_callback Called once the document is ready.
*/
- testGenCppIncludes: function() {
- ChromeVoxE2ETest.prototype.testGenCppIncludes.call(this);
- GEN('#include "base/command_line.h"');
- GEN('#include "chromeos/chromeos_switches.h"');
- },
+ runWithLoadedTree: function(doc, callback) {
+ callback = this.newCallback(callback);
+ chrome.automation.getDesktop(function(r) {
+ var listener = function(evt) {
+ if (!evt.target.attributes.url ||
+ evt.target.attributes.url.indexOf('test') == -1)
+ return;
- /**
- * This method is called without |this| bound to an instance of
- * ChromeVoxNextE2ETest.
- * @override
- */
- testGenPreamble: function() {
- GEN_BLOCK(function() {/*!
- CommandLine* command_line = CommandLine::ForCurrentProcess();
- command_line->AppendSwitch(chromeos::switches::kEnableChromeVoxNext);
- */});
- ChromeVoxE2ETest.prototype.testGenPreamble.call(this);
+ r.removeEventListener(listener);
+ callback && callback(evt.target);
+ callback = null;
+ };
+ r.addEventListener('loadComplete', listener, true);
+ this.runWithTab(doc);
+ }.bind(this));
}
};
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/testing/chromevox_unittest_base.js b/chromium/chrome/browser/resources/chromeos/chromevox/testing/chromevox_unittest_base.js
index 869a0c3f62a..759591e1c7f 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/testing/chromevox_unittest_base.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/testing/chromevox_unittest_base.js
@@ -5,7 +5,8 @@
GEN_INCLUDE([
'chrome/browser/resources/chromeos/chromevox/testing/assert_additions.js']);
GEN_INCLUDE([
- 'chrome/browser/resources/chromeos/chromevox/testing/common.js']);
+ 'chrome/browser/resources/chromeos/chromevox/testing/common.js',
+ 'chrome/browser/resources/chromeos/chromevox/testing/callback_helper.js']);
/**
* Base test fixture for ChromeVox unit tests.
@@ -17,7 +18,11 @@ GEN_INCLUDE([
* @constructor
* @extends {testing.Test}
*/
-function ChromeVoxUnitTestBase() {}
+function ChromeVoxUnitTestBase() {
+ if (this.isAsync) {
+ this.callbackHelper_ = new CallbackHelper(this);
+ }
+}
ChromeVoxUnitTestBase.prototype = {
__proto__: testing.Test.prototype,
@@ -115,12 +120,11 @@ ChromeVoxUnitTestBase.prototype = {
* @return {ChromeVoxUnitTestBase} this.
*/
waitForCalm: function(func, var_args) {
- var me = this;
var calmArguments = Array.prototype.slice.call(arguments);
calmArguments.shift();
- cvox.ChromeVoxEventWatcher.addReadyCallback(function() {
- func.apply(me, calmArguments);
- });
+ cvox.ChromeVoxEventWatcher.addReadyCallback(this.newCallback(function() {
+ func.apply(this, calmArguments);
+ }));
return this; // for chaining.
},
@@ -190,5 +194,24 @@ ChromeVoxUnitTestBase.prototype = {
*/
spokenList: function() {
return new cvox.SpokenListBuilder();
+ },
+
+ /**
+ * @type {CallbackHelper}
+ * @private
+ */
+ callbackHelper_: null,
+
+ /**
+ * Creates a callback that optionally calls {@code opt_callback} when
+ * called. If this method is called one or more times, then
+ * {@code testDone()} will be called when all callbacks have been called.
+ * @param {Function=} opt_callback Wrapped callback that will have its this
+ * reference bound to the test fixture.
+ * @return {Function}
+ */
+ newCallback: function(opt_callback) {
+ assertNotEquals(null, this.callbackHelper_);
+ return this.callbackHelper_.wrap(opt_callback);
}
};
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/testing/common.js b/chromium/chrome/browser/resources/chromeos/chromevox/testing/common.js
index 38f2d59ff67..83570fc8fae 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/testing/common.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/testing/common.js
@@ -28,10 +28,16 @@ var TestUtils = function() {};
*
* @param {Function} commentEncodedHtml The html , embedded as a
* comment inside an anonymous function - see example, above.
+ * @param {!Array=} opt_args Optional arguments to be substituted in the form
+ * $0, ... within the code block.
* @return {string} The html text.
*/
-TestUtils.extractHtmlFromCommentEncodedString = function(commentEncodedHtml) {
- return commentEncodedHtml.toString().
- replace(/^[^\/]+\/\*!?/, '').
- replace(/\*\/[^\/]+$/, '');
+TestUtils.extractHtmlFromCommentEncodedString =
+ function(commentEncodedHtml, opt_args) {
+ var stringified = commentEncodedHtml.toString();
+ if (opt_args) {
+ for (var i = 0; i < opt_args.length; i++)
+ stringified = stringified.replace('$' + i, opt_args[i]);
+ }
+ return stringified.replace(/^[^\/]+\/\*!?/, '').replace(/\*\/[^\/]+$/, '');
};
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/testing/mock_tts.js b/chromium/chrome/browser/resources/chromeos/chromevox/testing/mock_tts.js
index 17562cdfb18..3f32d0ca400 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/testing/mock_tts.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/testing/mock_tts.js
@@ -13,7 +13,7 @@ var MockTts = function() {
MockTts.prototype = {
/**
* A list of predicate, start, and end callbacks for a pending expectation.
- * @type {!Array.<{{predicate: function(string) : boolean,
+ * @type {!Array<{{predicate: function(string) : boolean,
* startCallback: function() : void,
* endCallback: function() : void}>}
* @private
@@ -22,14 +22,14 @@ MockTts.prototype = {
/**
* A list of strings stored whenever there are no expectations.
- * @type {!Array.<string}
+ * @type {!Array<string}
* @private
*/
idleUtterances_: [],
/** @override */
speak: function(textString, queueMode, properties) {
- this.process_(textString);
+ this.process_(textString, false, properties);
},
/**
@@ -37,11 +37,12 @@ MockTts.prototype = {
* |opt_callback| is called.
* @param {string} expectedText
* @param {function() : void=} opt_callback
+ * @param {boolean=} opt_exact Expect an exact match; defaults to false.
*/
- expectSpeech: function(expectedText, opt_callback) {
+ expectSpeech: function(expectedText, opt_callback, opt_exact) {
var expectation = {};
expectation.endCallback = opt_callback;
- this.addExpectation_(expectedText, expectation);
+ this.addExpectation_(expectedText, expectation, opt_exact);
},
/**
@@ -61,10 +62,11 @@ MockTts.prototype = {
},
/**
- * Finishes expectations and calls testDone.
+ * Finishes expectations and calls {@code callback} afterwards.
+ * @param {Function} callback
*/
- finishExpectations: function() {
- this.expectSpeechAfter('', testDone);
+ finishExpectations: function(callback) {
+ this.expectSpeechAfter('', callback);
},
/**
@@ -87,28 +89,38 @@ MockTts.prototype = {
// Process any idleUtterances.
this.idleUtterances_.forEach(function(utterance) {
- this.process_(utterance, true);
- });
+ this.process_(utterance.text, true, utterance.properties);
+ }.bind(this));
},
/**
* @param {string} textString Utterance to match against callbacks.
* @param {boolean=} opt_manual True if called outside of tts.speak.
+ * @param {!Object=} opt_properties
* @private
*/
- process_: function(textString, opt_manual) {
+ process_: function(textString, opt_manual, opt_properties) {
+ var utterance = {text: textString, properties: opt_properties};
if (this.expectations_.length == 0) {
- if (!opt_manual)
- this.idleUtterances_.push(textString);
+ if (!opt_manual) {
+ this.idleUtterances_.push(utterance);
+ }
return;
}
- var allUtterances = this.idleUtterances_.concat([textString]);
+ var allUtterances = this.idleUtterances_.concat([utterance]);
var targetExpectation = this.expectations_.shift();
- if (allUtterances.some(targetExpectation.predicate)) {
+ allUtterances = allUtterances.filter(function(u) {
+ return targetExpectation.predicate(u.text);
+ });
+ if (allUtterances.length > 0) {
+ var matchingProperties = allUtterances[0].properties;
this.idleUtterances_.length = 0;
if (targetExpectation.endCallback)
targetExpectation.endCallback();
+ if (matchingProperties && matchingProperties.endCallback) {
+ matchingProperties.endCallback();
+ }
var nextExpectation = this.expectations_[0];
if (nextExpectation && nextExpectation.startCallback)
nextExpectation.startCallback();
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/testing/tester.js b/chromium/chrome/browser/resources/chromeos/chromevox/testing/tester.js
index 1f226bdac79..13012d9d0c5 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/testing/tester.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/testing/tester.js
@@ -84,7 +84,7 @@ cvox.ChromeVoxTester.clearUtterances = function() {
/**
* Return a list of strings of what was spoken by tts.speak().
- * @return {Array.<string>} A list of all utterances spoken since
+ * @return {Array<string>} A list of all utterances spoken since
* initialization or the last call to clearUtterances.
*/
cvox.ChromeVoxTester.getUtteranceList = function() {
@@ -92,7 +92,7 @@ cvox.ChromeVoxTester.getUtteranceList = function() {
};
/**
- * @type {Object.<string, number>} Map from a navigation strategy name
+ * @type {Object<string, number>} Map from a navigation strategy name
* to the Navigation Manager strategy enum.
*/
cvox.ChromeVoxTester.STRATEGY_MAP = {
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/tools/check_chromevox.py b/chromium/chrome/browser/resources/chromeos/chromevox/tools/check_chromevox.py
index e474009cd88..d372b06e414 100755
--- a/chromium/chrome/browser/resources/chromeos/chromevox/tools/check_chromevox.py
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/tools/check_chromevox.py
@@ -40,7 +40,6 @@ _COMMON_EXTERNS = [
CVoxPath('common/chrome_extension_externs.js'),
CVoxPath('chromevox/background/externs.js'),
CVoxPath('chromevox/injected/externs.js'),
- CVoxPath('liblouis_nacl/externs.js'),
CVoxPath('host/chrome/externs.js')]
# List of top-level scripts and externs that we can check.
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/tools/generate_deps.py b/chromium/chrome/browser/resources/chromeos/chromevox/tools/generate_deps.py
index 42cd6134ced..4468b7f928c 100755
--- a/chromium/chrome/browser/resources/chromeos/chromevox/tools/generate_deps.py
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/tools/generate_deps.py
@@ -23,6 +23,16 @@ sys.path.insert(0, os.path.join(
import source
+def _HasSameContent(filename, content):
+ '''Returns true if the given file is readable and has the given content.'''
+ try:
+ with open(filename) as file:
+ return file.read() == content
+ except:
+ # Ignore all errors and fall back on a safe bet.
+ return False
+
+
def main():
parser = optparse.OptionParser(description=__doc__)
parser.add_option('-w', '--rewrite_prefix', action='append', default=[],
@@ -42,15 +52,17 @@ def main():
path_rewriter = PathRewriter(options.prefix_map)
+ content = ''
+ for path in args:
+ js_deps = source.Source(source.GetFileContents(path))
+ path = path_rewriter.RewritePath(path)
+ content += 'goog.addDependency(\'%s\', %s, %s);\n' % (
+ path, sorted(js_deps.provides), sorted(js_deps.requires))
+ if _HasSameContent(options.output_file, content):
+ return
# Write the generated deps file.
with open(options.output_file, 'w') as output:
- for path in args:
- js_deps = source.Source(source.GetFileContents(path))
- path = path_rewriter.RewritePath(path)
- line = 'goog.addDependency(\'%s\', %s, %s);\n' % (
- path, sorted(js_deps.provides), sorted(js_deps.requires))
- output.write(line)
-
+ output.write(content)
if __name__ == '__main__':
main()
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/tools/generate_manifest.py b/chromium/chrome/browser/resources/chromeos/chromevox/tools/generate_manifest.py
index d155d74b478..69c6bc18b13 100755
--- a/chromium/chrome/browser/resources/chromeos/chromevox/tools/generate_manifest.py
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/tools/generate_manifest.py
@@ -43,13 +43,13 @@ def main():
'-o', '--output_manifest', action='store', metavar='OUTPUT_MANIFEST',
help='File to place generated manifest')
parser.add_option(
- '--is_guest_manifest', action='store', metavar='NUM',
+ '--is_guest_manifest', default='0', action='store', metavar='NUM',
help='Whether to generate a guest mode capable manifest')
parser.add_option(
- '--is_chromevox_next', action='store', metavar='NUM',
- help='Whether to generate a ChromeVox Next manifest')
+ '--is_chromevox_classic', default='0', action='store', metavar='NUM',
+ help='Whether to generate a ChromeVox Classic manifest')
parser.add_option(
- '--is_js_compressed', action='store', metavar='NUM',
+ '--is_js_compressed', default='1', action='store', metavar='NUM',
help='Whether compressed JavaScript files are used')
parser.add_option(
'--set_version', action='store', metavar='SET_VERSION',
@@ -62,7 +62,15 @@ def main():
if len(args) != 1:
print >>sys.stderr, 'Expected exactly one argument'
sys.exit(1)
- processJinjaTemplate(args[0], options.output_manifest, parser.values.__dict__)
+ if options.output_manifest is None:
+ print >>sys.stderr, '--output_manifest option must be specified'
+ sys.exit(1)
+ if options.set_version is None:
+ print >>sys.stderr, '--set_version option must be specified'
+ sys.exit(1)
+
+ context = {k: v for k, v in parser.values.__dict__.items() if v is not None}
+ processJinjaTemplate(args[0], options.output_manifest, context)
if __name__ == '__main__':
main()
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/tools/jscompilerwrapper.py b/chromium/chrome/browser/resources/chromeos/chromevox/tools/jscompilerwrapper.py
index 11c7a1f7283..fb4b80dfad7 100755
--- a/chromium/chrome/browser/resources/chromeos/chromevox/tools/jscompilerwrapper.py
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/tools/jscompilerwrapper.py
@@ -64,6 +64,7 @@ def RunCompiler(js_files, externs=[]):
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(['--language_in', 'ECMASCRIPT5'])
args.extend(['--externs=%s' % extern for extern in externs])
args.extend(['--js=%s' % js for js in js_files])
args.extend(['--js_output_file', '/dev/null'])
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/tools/print_js_deps.py b/chromium/chrome/browser/resources/chromeos/chromevox/tools/print_js_deps.py
new file mode 100755
index 00000000000..4782c3d6f8d
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/tools/print_js_deps.py
@@ -0,0 +1,89 @@
+#!/usr/bin/env python
+
+# Copyright 2015 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.
+
+'''Print the dependency tree for a JavaScript module.
+
+Given one or more root directories, specified by -r options and one top-level
+file, walk the dependency tree and print all modules encountered.
+A module is only expanded once; on a second encounter, its dependencies
+are represented by a line containing the characters '[...]' as a short-hand.
+'''
+
+import optparse
+import os
+import sys
+
+from jsbundler import ReadSources
+
+
+def Die(message):
+ '''Prints an error message and exit the program.'''
+ print >>sys.stderr, message
+ sys.exit(1)
+
+
+def CreateOptionParser():
+ parser = optparse.OptionParser(description=__doc__)
+ parser.usage = '%prog [options] <top_level_file>'
+ parser.add_option('-r', '--root', dest='roots', action='append', default=[],
+ metavar='ROOT',
+ help='Roots of directory trees to scan for sources. '
+ 'If none specified, all of ChromeVox and closure sources '
+ 'are scanned.')
+ return parser
+
+
+def DefaultRoots():
+ script_dir = os.path.dirname(os.path.abspath(__file__))
+ source_root_dir = os.path.join(script_dir, *[os.path.pardir] * 6)
+ return [os.path.relpath(os.path.join(script_dir, os.path.pardir)),
+ os.path.relpath(
+ os.path.join(source_root_dir, 'chrome', 'third_party',
+ 'chromevox', 'third_party',
+ 'closure-library', 'closure'))]
+
+
+def WalkDeps(sources, start_source):
+ def Walk(source, depth):
+ indent = ' ' * depth
+ if source.GetInPath() in expanded and len(source.requires) > 0:
+ print '%s[...]' % indent
+ return
+ expanded.add(source.GetInPath())
+ for require in source.requires:
+ if not require in providers:
+ Die('%s not provided, required by %s' % (require, source.GetInPath()))
+ require_source = providers[require]
+ print '%s%s (%s)' % (indent, require, require_source.GetInPath())
+ Walk(require_source, depth + 1)
+
+ # Create a map from provided module names to source objects.
+ providers = {}
+ expanded = set()
+ for source in sources.values():
+ for provide in source.provides:
+ if provide in providers:
+ Die('%s provided multiple times' % provide)
+ providers[provide] = source
+
+ print '(%s)' % start_source.GetInPath()
+ Walk(start_source, 1)
+
+
+def main():
+ parser = CreateOptionParser()
+ options, args = parser.parse_args()
+ if len(args) != 1:
+ Die('Exactly one top-level source file must be specified.')
+ start_path = args[0]
+ roots = options.roots or DefaultRoots()
+ sources = ReadSources(roots=roots, source_files=[start_path])
+ start_source = sources[start_path]
+ WalkDeps(sources, start_source)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/tools/publish_webstore_extension.py b/chromium/chrome/browser/resources/chromeos/chromevox/tools/publish_webstore_extension.py
new file mode 100755
index 00000000000..fe8af1ea44e
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/tools/publish_webstore_extension.py
@@ -0,0 +1,155 @@
+#!/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.
+
+'''Publishes a set of extensions to the webstore.
+ Given an unpacked extension, compresses and sends to the Chrome webstore.
+
+ Releasing to the webstore should involve the following manual steps before
+ running this script:
+ 1. clean the output directory.
+ 2. make a release build.
+ 3. run manual smoke tests.
+ 4. run automated tests.
+'''
+
+import webstore_extension_util
+import generate_manifest
+import json
+import optparse
+import os
+import sys
+import tempfile
+from zipfile import ZipFile
+
+_CHROMEVOX_ID = 'kgejglhpjiefppelpmljglcjbhoiplfn'
+_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
+_CHROME_SOURCE_DIR = os.path.normpath(
+ os.path.join(
+ _SCRIPT_DIR, *[os.path.pardir] * 6))
+
+sys.path.insert(
+ 0, os.path.join(_CHROME_SOURCE_DIR, 'build', 'util'))
+import version
+
+# A list of files (or directories) to exclude from the webstore build.
+EXCLUDE_PATHS = [
+ 'manifest.json',
+ 'manifest_guest.json',
+ ]
+
+
+def CreateOptionParser():
+ parser = optparse.OptionParser(description=__doc__)
+ parser.usage = (
+ '%prog --client_secret <client_secret> extension_id:extension_path ...')
+ parser.add_option('-c', '--client_secret', dest='client_secret',
+ action='store', metavar='CLIENT_SECRET')
+ parser.add_option('-p', '--publish', action='store_true',
+ help='publish the extension(s)')
+ return parser
+
+
+def GetVersion():
+ '''Returns the chrome version string.'''
+ filename = os.path.join(_CHROME_SOURCE_DIR, 'chrome', 'VERSION')
+ values = version.fetch_values([filename])
+ return version.subst_template('@MAJOR@.@MINOR@.@BUILD@.@PATCH@', values)
+
+
+def MakeChromeVoxManifest():
+ '''Create a manifest for the webstore.
+
+ Returns:
+ Temporary file with generated manifest.
+ '''
+ new_file = tempfile.NamedTemporaryFile(mode='w+a', bufsize=0)
+ in_file_name = os.path.join(_SCRIPT_DIR, os.path.pardir,
+ 'manifest.json.jinja2')
+ context = {
+ 'is_chromevox_classic': '1',
+ 'is_guest_manifest': '0',
+ 'is_js_compressed': '1',
+ 'set_version': GetVersion()
+ }
+ generate_manifest.processJinjaTemplate(in_file_name, new_file.name, context)
+ return new_file
+
+
+def RunInteractivePrompt(client_secret, output_path):
+ input = ''
+ while True:
+ print 'u upload'
+ print 'g get upload status'
+ print 't publish trusted tester'
+ print 'p publish public'
+ print 'q quit'
+ input = raw_input('Please select an option: ')
+ input = input.strip()
+ if input == 'g':
+ print ('Upload status: %s' %
+ webstore_extension_util.GetUploadStatus(client_secret).read())
+ elif input == 'u':
+ print ('Uploaded with status: %s' %
+ webstore_extension_util.PostUpload(output_path.name, client_secret))
+ elif input == 't':
+ print ('Published to trusted testers with status: %s' %
+ webstore_extension_util.PostPublishTrustedTesters(
+ client_secret).read())
+ elif input == 'p':
+ print ('Published to public with status: %s' %
+ webstore_extension_util.PostPublish(client_secret).read())
+ elif input == 'q':
+ sys.exit()
+ else:
+ print 'Unrecognized option: %s' % input
+
+def main():
+ options, args = CreateOptionParser().parse_args()
+ if len(args) < 1 or not options.client_secret:
+ print 'Expected at least one argument and --client_secret flag'
+ print str(args)
+ sys.exit(1)
+
+ client_secret = options.client_secret
+
+ for extension in args:
+ webstore_extension_util.g_app_id, extension_path = extension.split(':')
+ output_path = tempfile.NamedTemporaryFile()
+ extension_path = os.path.expanduser(extension_path)
+
+ is_chromevox = webstore_extension_util.g_app_id == _CHROMEVOX_ID
+
+ with ZipFile(output_path, 'w') as zip:
+ for root, dirs, files in os.walk(extension_path):
+ rel_path = os.path.join(os.path.relpath(root, extension_path), '')
+
+ if is_chromevox and rel_path in EXCLUDE_PATHS:
+ continue
+
+ for extension_file in files:
+ if is_chromevox and extension_file in EXCLUDE_PATHS:
+ continue
+
+ zip.write(os.path.join(root, extension_file),
+ os.path.join(rel_path, extension_file))
+
+ if is_chromevox:
+ manifest_file = MakeChromeVoxManifest()
+ zip.write(manifest_file.name, 'manifest.json')
+
+ print 'Created extension zip file in %s' % output_path.name
+ print 'Please run manual smoke tests before proceeding.'
+ if options.publish:
+ print('Uploading...%s' %
+ webstore_extension_util.PostUpload(output_path.name, client_secret))
+ print('publishing...%s' %
+ webstore_extension_util.PostPublish(client_secret).read())
+ else:
+ RunInteractivePrompt(client_secret, output_path)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/tools/upload_chromevox_to_webstore.py b/chromium/chrome/browser/resources/chromeos/chromevox/tools/upload_chromevox_to_webstore.py
deleted file mode 100755
index ad51cec1a7b..00000000000
--- a/chromium/chrome/browser/resources/chromeos/chromevox/tools/upload_chromevox_to_webstore.py
+++ /dev/null
@@ -1,121 +0,0 @@
-#!/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.
-
-'''Publishes ChromeVox to the webstore.
- Given an unpacked extension, compresses and sends to the Chrome webstore.
-
- Releasing to the webstore should involve the following manual steps before
- running this script:
- 1. clean the output directory.
- 2. make a release build.
- 3. run manual smoke tests.
- 4. run automated ChromeVox tests.
-'''
-
-import chromevox_webstore_util
-import json
-import optparse
-import os
-import sys
-import tempfile
-from zipfile import ZipFile
-
-# A list of files (or directories) to exclude from the webstore build.
-EXCLUDE_PATHS = [
- 'cvox2/background/',
- 'deps.js',
- 'manifest_guest.json',
- 'manifest_next.json',
- 'manifest_next_guest.json'
- ]
-
-
-def CreateOptionParser():
- parser = optparse.OptionParser(description=__doc__)
- parser.usage = '%prog <extension_path> <output_path> <client_secret'
- return parser
-
-def MakeManifestEdits(root, old, new_file):
- '''Customize a manifest for the webstore.
-
- Args:
- root: The directory containing file.
-
- old: A json file.
-
- new_file: a temporary file to place the manifest in.
-
- Returns:
- File of the new manifest.
- '''
- with open(os.path.join(root, old)) as old_file:
- new_contents = json.loads(old_file.read())
- new_contents.pop('key', '')
- new_file.file.write(json.dumps(new_contents))
-
-def RunInteractivePrompt(client_secret, output_path):
- input = ''
- while True:
- print 'u upload'
- print 'g get upload status'
- print 't publish trusted tester'
- print 'p publish public'
- print 'q quit'
- input = raw_input('Please select an option: ')
- input = input.strip()
- if input == 'g':
- print ('Upload status: %s' %
- chromevox_webstore_util.GetUploadStatus(client_secret).read())
- elif input == 'u':
- print ('Uploaded with status: %s' %
- chromevox_webstore_util.PostUpload(output_path, client_secret))
- elif input == 't':
- print ('Published to trusted testers with status: %s' %
- chromevox_webstore_util.PostPublishTrustedTesters(
- client_secret).read())
- elif input == 'p':
- print ('Published to public with status: %s' %
- chromevox_webstore_util.PostPublish(client_secret).read())
- elif input == 'q':
- sys.exit()
- else:
- print 'Unrecognized option: %s' % input
-
-def main():
- _, args = CreateOptionParser().parse_args()
- if len(args) != 3:
- print 'Expected exactly three arguments'
- sys.exit(1)
-
- extension_path = args[0]
- output_path = args[1]
- client_secret = args[2]
-
- with ZipFile(output_path, 'w') as zip:
- for root, dirs, files in os.walk(extension_path):
- rel_path = os.path.join(os.path.relpath(root, extension_path), '')
- if rel_path in EXCLUDE_PATHS:
- continue
-
- for extension_file in files:
- if extension_file in EXCLUDE_PATHS:
- continue
- if extension_file == 'manifest.json':
- new_file = tempfile.NamedTemporaryFile(mode='w+a', bufsize=0)
- MakeManifestEdits(root, extension_file, new_file)
- zip.write(
- new_file.name, os.path.join(rel_path, extension_file))
- continue
-
- zip.write(os.path.join(root, extension_file),
- os.path.join(rel_path, extension_file))
- print 'Created ChromeVox zip file in %s' % output_path
- print 'Please run manual smoke tests before proceeding.'
- RunInteractivePrompt(client_secret, output_path)
-
-
-if __name__ == '__main__':
- main()
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/tools/chromevox_webstore_util.py b/chromium/chrome/browser/resources/chromeos/chromevox/tools/webstore_extension_util.py
index 5aa96f707d8..e0f8490f963 100755
--- a/chromium/chrome/browser/resources/chromeos/chromevox/tools/chromevox_webstore_util.py
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/tools/webstore_extension_util.py
@@ -24,19 +24,32 @@ PROJECT_ARGS = {
'redirect_uri': 'http://localhost:8000'
}
-PORT = 8000
+# Persists across all utility methods for authentication.
+g_auth_code = None
+g_oauth_token = None
+
+# The app id to use for all utility methods.
+g_app_id = ''
-APP_ID = 'kgejglhpjiefppelpmljglcjbhoiplfn'
+# Constants.
+PORT = 8000
OAUTH_DOMAIN = 'accounts.google.com'
OAUTH_AUTH_COMMAND = '/o/oauth2/auth'
OAUTH_TOKEN_COMMAND = '/o/oauth2/token'
WEBSTORE_API_SCOPE = 'https://www.googleapis.com/auth/chromewebstore'
-
API_ENDPOINT_DOMAIN = 'www.googleapis.com'
-COMMAND_GET_UPLOAD_STATUS = (
- '/chromewebstore/v1.1/items/%s?projection=draft' % APP_ID)
-COMMAND_POST_PUBLISH = '/chromewebstore/v1.1/items/%s/publish' % APP_ID
-COMMAND_POST_UPLOAD = '/upload/chromewebstore/v1.1/items/%s' % APP_ID
+
+def GetUploadStatusCommand():
+ global g_app_id
+ return '/chromewebstore/v1.1/items/%s?projection=draft' % g_app_id
+
+def GetPublishCommand():
+ global g_app_id
+ return '/chromewebstore/v1.1/items/%s/publish' % g_app_id
+
+def GetUploadCommand():
+ global g_app_id
+ return '/upload/chromewebstore/v1.1/items/%s' % g_app_id
class CodeRequestHandler(SocketServer.StreamRequestHandler):
def handle(self):
@@ -45,6 +58,10 @@ class CodeRequestHandler(SocketServer.StreamRequestHandler):
self.rfile.close()
def GetAuthCode():
+ global g_auth_code
+ if g_auth_code:
+ return g_auth_code
+
Handler = CodeRequestHandler
httpd = SocketServer.TCPServer(("", PORT), Handler)
query = '&'.join(['response_type=code',
@@ -56,9 +73,14 @@ def GetAuthCode():
webbrowser.open(auth_url)
httpd.handle_request()
httpd.server_close()
- return httpd.code
+ g_auth_code = httpd.code
+ return g_auth_code
def GetOauthToken(code, client_secret):
+ global g_oauth_token
+ if g_oauth_token:
+ return g_oauth_token
+
PROJECT_ARGS['code'] = code
PROJECT_ARGS['client_secret'] = client_secret
body = urllib.urlencode(PROJECT_ARGS)
@@ -69,11 +91,14 @@ def GetOauthToken(code, client_secret):
conn.endheaders()
conn.send(body)
content = conn.getresponse().read()
- return json.loads(content)
+ conn.close()
+ g_oauth_token = json.loads(content)
+ return g_oauth_token
def GetPopulatedHeader(client_secret):
code = GetAuthCode()
access_token = GetOauthToken(code, client_secret)
+
url = 'www.googleapis.com'
return {'Authorization': 'Bearer %(access_token)s' % access_token,
@@ -85,21 +110,25 @@ def SendGetCommand(command, client_secret):
headers = GetPopulatedHeader(client_secret)
conn = httplib.HTTPSConnection(API_ENDPOINT_DOMAIN)
conn.request('GET', command, '', headers)
- return conn.getresponse()
+ r = conn.getresponse()
+ conn.close()
+ return r
def SendPostCommand(command, client_secret, header_additions = {}, body=None):
headers = GetPopulatedHeader(client_secret)
headers = dict(headers.items() + header_additions.items())
conn = httplib.HTTPSConnection(API_ENDPOINT_DOMAIN)
conn.request('POST', command, body, headers)
- return conn.getresponse()
+ r = conn.getresponse()
+ conn.close()
+ return r
def GetUploadStatus(client_secret):
'''Gets the status of a previous upload.
Args:
client_secret ChromeVox's client secret creds.
'''
- return SendGetCommand(COMMAND_GET_UPLOAD_STATUS, client_secret)
+ return SendGetCommand(GetUploadStatusCommand(), client_secret)
# httplib fails to persist the connection during upload; use curl instead.
def PostUpload(file, client_secret):
@@ -116,7 +145,7 @@ def PostUpload(file, client_secret):
'-T %s' % file,
'-v',
'https://%s%s' % (API_ENDPOINT_DOMAIN,
- COMMAND_POST_UPLOAD)])
+ GetUploadCommand())])
print 'Running %s' % curl_command
if os.system(curl_command) != 0:
@@ -127,7 +156,7 @@ def PostPublishTrustedTesters(client_secret):
Args:
client_secret ChromeVox's client secret creds.
'''
- return SendPostCommand(COMMAND_POST_PUBLISH,
+ return SendPostCommand(GetPublishCommand(),
client_secret,
{ 'publishTarget': 'trustedTesters'})
@@ -136,4 +165,4 @@ def PostPublish(client_secret):
Args:
client_secret ChromeVox's client secret creds.
'''
- return SendPostCommand(COMMAND_POST_PUBLISH, client_secret)
+ return SendPostCommand(GetPublishCommand(), client_secret)
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/walkers/abstract_shifter.js b/chromium/chrome/browser/resources/chromeos/chromevox/walkers/abstract_shifter.js
index 976d07e89e5..3da50994e6b 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/walkers/abstract_shifter.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/walkers/abstract_shifter.js
@@ -34,6 +34,7 @@ cvox.AbstractShifter.prototype.next = goog.abstractMethod;
/**
* Gets the first (or last) selection for this shifter's current granularity.
+ * @param {?} sel
* @param {{reversed: (undefined|boolean)}=} kwargs Extra arguments.
* reversed: If true, syncs to the end and returns a reversed selection.
* False by default.
@@ -63,7 +64,7 @@ cvox.AbstractShifter.prototype.getName = goog.abstractMethod;
* Gets the current description.
* @param {!cvox.CursorSelection} prevSel The previous selection, for context.
* @param {!cvox.CursorSelection} sel The current selection.
- * @return {Array.<cvox.NavDescription>} The description array.
+ * @return {Array<cvox.NavDescription>} The description array.
*/
cvox.AbstractShifter.prototype.getDescription = goog.abstractMethod;
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/walkers/abstract_walker.js b/chromium/chrome/browser/resources/chromeos/chromevox/walkers/abstract_walker.js
index b8d5192daa0..c704cd9f437 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/walkers/abstract_walker.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/walkers/abstract_walker.js
@@ -131,7 +131,7 @@ cvox.AbstractWalker.prototype.sync = goog.abstractMethod;
* selection. Undefined behavior otherwise.
* @param {!cvox.CursorSelection} prevSel The valid previous selection.
* @param {!cvox.CursorSelection} sel The valid current selection.
- * @return {!Array.<!cvox.NavDescription>} The description array.
+ * @return {!Array<!cvox.NavDescription>} The description array.
*/
cvox.AbstractWalker.prototype.getDescription = goog.abstractMethod;
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/walkers/layout_line_walker.js b/chromium/chrome/browser/resources/chromeos/chromevox/walkers/layout_line_walker.js
index 8495713e8a0..b46a089df19 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/walkers/layout_line_walker.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/walkers/layout_line_walker.js
@@ -214,8 +214,7 @@ cvox.LayoutLineWalker.prototype.extend_ = function(start) {
cvox.LayoutLineWalker.prototype.appendBraille_ = function(
prevSel, sel, cur, braille) {
var item = this.subWalker_.getBraille(prevSel, cur).text;
- var valueSelectionSpan = item.getSpanInstanceOf(
- cvox.BrailleUtil.ValueSelectionSpan);
+ var valueSelectionSpan = item.getSpanInstanceOf(cvox.ValueSelectionSpan);
if (braille.text.getLength() > 0) {
braille.text.append(cvox.BrailleUtil.ITEM_SEPARATOR);
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/walkers/structural_line_walker.js b/chromium/chrome/browser/resources/chromeos/chromevox/walkers/structural_line_walker.js
index ff665102aa2..376b04fb89d 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/walkers/structural_line_walker.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/walkers/structural_line_walker.js
@@ -10,6 +10,7 @@
goog.provide('cvox.StructuralLineWalker');
goog.require('cvox.AbstractSelectionWalker');
+goog.require('cvox.BrailleUtil');
goog.require('cvox.TraverseContent');
/**
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/walkers/structural_line_walker_test.unitjs b/chromium/chrome/browser/resources/chromeos/chromevox/walkers/structural_line_walker_test.unitjs
index e7f41af454e..7dc9b159605 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/walkers/structural_line_walker_test.unitjs
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/walkers/structural_line_walker_test.unitjs
@@ -17,7 +17,6 @@ CvoxStructuralLineWalkerUnitTest.prototype = {
/** @override */
closureModuleDeps: [
- 'cvox.BrailleUtil',
'cvox.StructuralLineWalker',
'cvox.TestMsgs',
],
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/walkers/table_shifter.js b/chromium/chrome/browser/resources/chromeos/chromevox/walkers/table_shifter.js
index 1e8ade6bdc2..1f9421e96bf 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/walkers/table_shifter.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/walkers/table_shifter.js
@@ -71,8 +71,8 @@ cvox.TableShifter.prototype.getName = function() {
* @override
* @suppress {checkTypes} actual parameter 2 of
* cvox.Msgs.prototype.getMsg does not match formal parameter
- * found : Array.<number>
- * required: (Array.<string>|null|undefined)
+ * found : Array<number>
+ * required: (Array<string>|null|undefined)
*/
cvox.TableShifter.prototype.getDescription = function(prevSel, sel) {
var descs = this.currentWalker_.getDescription(prevSel, sel);
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/walkers/table_walker.js b/chromium/chrome/browser/resources/chromeos/chromevox/walkers/table_walker.js
index ba4444e2172..cd6f3c885d7 100644
--- a/chromium/chrome/browser/resources/chromeos/chromevox/walkers/table_walker.js
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/walkers/table_walker.js
@@ -57,8 +57,8 @@ cvox.TableWalker.prototype.sync = function(sel) {
* @override
* @suppress {checkTypes} actual parameter 2 of
* cvox.Msgs.prototype.getMsg does not match formal parameter
- * found : Array.<number>
- * required: (Array.<string>|null|undefined)
+ * found : Array<number>
+ * required: (Array<string>|null|undefined)
*/
cvox.TableWalker.prototype.getDescription = function(prevSel, sel) {
var position = this.syncPosition_(sel);
@@ -102,7 +102,7 @@ cvox.TableWalker.prototype.getGranularityMsg = goog.abstractMethod;
* Returns the first cell of the table that this selection is inside.
* @param {!cvox.CursorSelection} sel The selection.
* @return {cvox.CursorSelection} The selection for first cell of the table.
- * @expose
+ * @export
*/
cvox.TableWalker.prototype.goToFirstCell = function(sel) {
return this.goTo_(sel, goog.bind(function(position) {
@@ -114,7 +114,7 @@ cvox.TableWalker.prototype.goToFirstCell = function(sel) {
* Returns the last cell of the table that this selection is inside.
* @param {!cvox.CursorSelection} sel The selection.
* @return {cvox.CursorSelection} The selection for the last cell of the table.
- * @expose
+ * @export
*/
cvox.TableWalker.prototype.goToLastCell = function(sel) {
return this.goTo_(sel, goog.bind(function(position) {
@@ -126,7 +126,7 @@ cvox.TableWalker.prototype.goToLastCell = function(sel) {
* Returns the first cell of the row that the selection is in.
* @param {!cvox.CursorSelection} sel The selection.
* @return {cvox.CursorSelection} The selection for the first cell in the row.
- * @expose
+ * @export
*/
cvox.TableWalker.prototype.goToRowFirstCell = function(sel) {
return this.goTo_(sel, goog.bind(function(position) {
@@ -138,7 +138,7 @@ cvox.TableWalker.prototype.goToRowFirstCell = function(sel) {
* Returns the last cell of the row that the selection is in.
* @param {!cvox.CursorSelection} sel The selection.
* @return {cvox.CursorSelection} The selection for the last cell in the row.
- * @expose
+ * @export
*/
cvox.TableWalker.prototype.goToRowLastCell = function(sel) {
return this.goTo_(sel, goog.bind(function(position) {
@@ -150,7 +150,7 @@ cvox.TableWalker.prototype.goToRowLastCell = function(sel) {
* Returns the first cell of the column that the selection is in.
* @param {!cvox.CursorSelection} sel The selection.
* @return {cvox.CursorSelection} The selection for the first cell in the col.
- * @expose
+ * @export
*/
cvox.TableWalker.prototype.goToColFirstCell = function(sel) {
return this.goTo_(sel, goog.bind(function(position) {
@@ -162,7 +162,7 @@ cvox.TableWalker.prototype.goToColFirstCell = function(sel) {
* Returns the last cell of the column that the selection is in.
* @param {!cvox.CursorSelection} sel The selection.
* @return {cvox.CursorSelection} The selection for the last cell in the col.
- * @expose
+ * @export
*/
cvox.TableWalker.prototype.goToColLastCell = function(sel) {
return this.goTo_(sel, goog.bind(function(position) {
@@ -175,7 +175,7 @@ cvox.TableWalker.prototype.goToColLastCell = function(sel) {
* @param {!cvox.CursorSelection} sel The selection.
* @return {cvox.CursorSelection} The selection for the first cell in the next
* row.
- * @expose
+ * @export
*/
cvox.TableWalker.prototype.nextRow = function(sel) {
return this.goTo_(sel, goog.bind(function(position) {
@@ -189,7 +189,7 @@ cvox.TableWalker.prototype.nextRow = function(sel) {
* @param {!cvox.CursorSelection} sel The selection.
* @return {cvox.CursorSelection} The selection for the first cell in the
* next col.
- * @expose
+ * @export
*/
cvox.TableWalker.prototype.nextCol = function(sel) {
return this.goTo_(sel, goog.bind(function(position) {
@@ -201,7 +201,7 @@ cvox.TableWalker.prototype.nextCol = function(sel) {
/**
* @param {!cvox.CursorSelection} sel The current selection.
* @return {cvox.CursorSelection} The resulting selection.
- * @expose
+ * @export
*/
cvox.TableWalker.prototype.announceHeaders = function(sel) {
cvox.ChromeVox.tts.speak(this.getHeaderText_(sel),
@@ -213,7 +213,7 @@ cvox.TableWalker.prototype.announceHeaders = function(sel) {
/**
* @param {!cvox.CursorSelection} sel The current selection.
* @return {cvox.CursorSelection} The resulting selection.
- * @expose
+ * @export
*/
cvox.TableWalker.prototype.speakTableLocation = function(sel) {
cvox.ChromeVox.navigationManager.speakDescriptionArray(
@@ -227,7 +227,7 @@ cvox.TableWalker.prototype.speakTableLocation = function(sel) {
/**
* @param {!cvox.CursorSelection} sel The current selection.
* @return {cvox.CursorSelection} The resulting selection.
- * @expose
+ * @export
*/
cvox.TableWalker.prototype.exitShifterContent = function(sel) {
var tableNode = this.getTableNode_(sel);
@@ -266,12 +266,12 @@ cvox.TableWalker.prototype.getHeaderText_ = function(sel) {
/**
* Returns the location description.
* @param {!cvox.CursorSelection} sel A valid selection.
- * @return {Array.<cvox.NavDescription>} The location description.
+ * @return {Array<cvox.NavDescription>} The location description.
* @suppress {checkTypes} actual parameter 2 of
* cvox.Msgs.prototype.getMsg does not match
* formal parameter
- * found : Array.<number>
- * required: (Array.<string>|null|undefined)
+ * found : Array<number>
+ * required: (Array<string>|null|undefined)
* @private
*/
cvox.TableWalker.prototype.getLocationDescription_ = function(sel) {
@@ -286,7 +286,7 @@ cvox.TableWalker.prototype.getLocationDescription_ = function(sel) {
/**
* Returns the text content of the row header(s) of the cell that contains sel.
- * @param {!Array.<number>} position The selection.
+ * @param {!Array<number>} position The selection.
* @return {!string} The header text.
* @private
*/
@@ -316,7 +316,7 @@ cvox.TableWalker.prototype.getRowHeaderText_ = function(position) {
/**
* Returns the text content of the col header(s) of the cell that contains sel.
- * @param {!Array.<number>} position The selection.
+ * @param {!Array<number>} position The selection.
* @return {!string} The header text.
* @private
*/
@@ -347,7 +347,7 @@ cvox.TableWalker.prototype.getColHeaderText_ = function(position) {
/**
* Returns the location info of sel within the containing table.
* @param {!cvox.CursorSelection} sel The selection.
- * @return {Array.<number>} The location info:
+ * @return {Array<number>} The location info:
* [row index, row count, col index, col count].
*/
cvox.TableWalker.prototype.getLocationInfo = function(sel) {
@@ -378,7 +378,7 @@ cvox.TableWalker.prototype.isInTable = function(sel) {
* Wrapper for going to somewhere so that boilerplate is not repeated.
* @param {!cvox.CursorSelection} sel The selection from which to base the
* movement.
- * @param {function(Array.<number>):boolean} f The function to use for moving.
+ * @param {function(Array<number>):boolean} f The function to use for moving.
* Returns true on success and false on failure.
* @return {cvox.CursorSelection} The resulting selection.
* @private
@@ -410,7 +410,7 @@ cvox.TableWalker.prototype.getTableNode_ = function(sel) {
/**
* Sync the backing traversal utility to the given selection.
* @param {!cvox.CursorSelection} sel The selection.
- * @return {Array.<number>} The position [x, y] of the selection.
+ * @return {Array<number>} The position [x, y] of the selection.
* @private
*/
cvox.TableWalker.prototype.syncPosition_ = function(sel) {
diff --git a/chromium/chrome/browser/resources/chromeos/compiled_resources.gyp b/chromium/chrome/browser/resources/chromeos/compiled_resources.gyp
new file mode 100644
index 00000000000..d921a8fba01
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/compiled_resources.gyp
@@ -0,0 +1,17 @@
+# Copyright 2015 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.
+{
+ 'targets': [
+ {
+ 'target_name': 'certificate_manager_dialog',
+ 'variables': {
+ 'depends': [
+ '../../../../chrome/browser/resources/options/compiled_resources.gyp:options_bundle',
+ ],
+ 'externs': ['<(CLOSURE_DIR)/externs/chrome_send_externs.js'],
+ },
+ 'includes': ['../../../../third_party/closure_compiler/compile_js.gypi'],
+ }
+ ],
+}
diff --git a/chromium/chrome/browser/resources/chromeos/cryptohome.html b/chromium/chrome/browser/resources/chromeos/cryptohome.html
index cab0ac0660c..9e11f11a886 100644
--- a/chromium/chrome/browser/resources/chromeos/cryptohome.html
+++ b/chromium/chrome/browser/resources/chromeos/cryptohome.html
@@ -1,8 +1,9 @@
-<!DOCTYPE HTML>
+<!doctype html>
<html>
<head>
<title>About Cryptohome</title>
<meta charset="utf-8">
+ <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<script src="chrome://resources/js/util.js"></script>
<script src="chrome://cryptohome/cryptohome.js"></script>
</head>
diff --git a/chromium/chrome/browser/resources/chromeos/drive_internals.html b/chromium/chrome/browser/resources/chromeos/drive_internals.html
index 9dd19a7401e..fe4c9f24be6 100644
--- a/chromium/chrome/browser/resources/chromeos/drive_internals.html
+++ b/chromium/chrome/browser/resources/chromeos/drive_internals.html
@@ -1,8 +1,9 @@
-<!DOCTYPE HTML>
+<!doctype html>
<html>
<head>
<title>drive-internals</title>
<meta charset="utf-8">
+ <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="drive_internals.css">
<script src="chrome://resources/js/util.js"></script>
<script src="chrome://drive-internals/drive_internals.js"></script>
diff --git a/chromium/chrome/browser/resources/chromeos/first_run/app/main.html b/chromium/chrome/browser/resources/chromeos/first_run/app/main.html
index 2931dc5e152..32e52a3c0bc 100644
--- a/chromium/chrome/browser/resources/chromeos/first_run/app/main.html
+++ b/chromium/chrome/browser/resources/chromeos/first_run/app/main.html
@@ -1,8 +1,9 @@
<!doctype html>
-<html i18n-values="dir:textdirection;.style.fontFamily:fontfamily;">
+<html i18n-values="dir:textdirection;.style.fontFamily:fontfamily;lang:language">
<head>
<meta charset=utf-8>
<title></title>
+ <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="chrome://resources/css/widgets.css">
<link rel="stylesheet" href="chrome://resources/css/apps/common.css">
<link rel="stylesheet" href="chrome://resources/css/apps/topbutton_bar.css">
diff --git a/chromium/chrome/browser/resources/chromeos/first_run/first_run.css b/chromium/chrome/browser/resources/chromeos/first_run/first_run.css
index c0ef61881dc..e5d5d0bd9b8 100644
--- a/chromium/chrome/browser/resources/chromeos/first_run/first_run.css
+++ b/chromium/chrome/browser/resources/chromeos/first_run/first_run.css
@@ -69,7 +69,7 @@ body {
}
#help #completion-image {
- background-image: url('chrome://theme/IDR_FIRST_RUN_COMPLETION');
+ background-image: url(chrome://theme/IDR_FIRST_RUN_COMPLETION);
display: inline-block;
height: 153px;
width: 400px;
diff --git a/chromium/chrome/browser/resources/chromeos/first_run/first_run.html b/chromium/chrome/browser/resources/chromeos/first_run/first_run.html
index e4d129b3247..29fe952b2bf 100644
--- a/chromium/chrome/browser/resources/chromeos/first_run/first_run.html
+++ b/chromium/chrome/browser/resources/chromeos/first_run/first_run.html
@@ -3,6 +3,7 @@
<head>
<meta charset=utf-8>
<title></title>
+ <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="chrome://resources/css/widgets.css">
<link rel="stylesheet" href="chrome://resources/css/apps/common.css">
<link rel="stylesheet" href="bubble.css">
@@ -24,7 +25,7 @@
<include src="tray_step.html">
<include src="help_step.html">
</div>
- <script src="chrome://resources/js/i18n_template2.js"></script>
+ <script src="chrome://resources/js/i18n_template.js"></script>
</body>
</html>
diff --git a/chromium/chrome/browser/resources/chromeos/first_run/preload.css b/chromium/chrome/browser/resources/chromeos/first_run/preload.css
index 78e74c0f48e..03baa02837d 100644
--- a/chromium/chrome/browser/resources/chromeos/first_run/preload.css
+++ b/chromium/chrome/browser/resources/chromeos/first_run/preload.css
@@ -13,20 +13,20 @@
@media all and (-webkit-max-device-pixel-ratio: 1.5) {
body::after {
content:
- url('chrome://resources/images/apps/button.png')
- url('chrome://resources/images/apps/button_hover.png')
- url('chrome://resources/images/apps/button_pressed.png')
- url('chrome://resources/images/apps/button_focused.png')
- url('chrome://resources/images/apps/button_focused_hover.png')
- url('chrome://resources/images/apps/button_focused_pressed.png')
- url('chrome://resources/images/apps/button_inactive.png')
- url('chrome://resources/images/apps/blue_button.png')
- url('chrome://resources/images/apps/blue_button_hover.png')
- url('chrome://resources/images/apps/blue_button_pressed.png')
- url('chrome://resources/images/apps/blue_button_focused.png')
- url('chrome://resources/images/apps/blue_button_focused_hover.png')
- url('chrome://resources/images/apps/blue_button_focused_pressed.png')
- url('chrome://resources/images/apps/blue_button_inactive.png');
+ url(chrome://resources/images/apps/button.png)
+ url(chrome://resources/images/apps/button_hover.png)
+ url(chrome://resources/images/apps/button_pressed.png)
+ url(chrome://resources/images/apps/button_focused.png)
+ url(chrome://resources/images/apps/button_focused_hover.png)
+ url(chrome://resources/images/apps/button_focused_pressed.png)
+ url(chrome://resources/images/apps/button_inactive.png)
+ url(chrome://resources/images/apps/blue_button.png)
+ url(chrome://resources/images/apps/blue_button_hover.png)
+ url(chrome://resources/images/apps/blue_button_pressed.png)
+ url(chrome://resources/images/apps/blue_button_focused.png)
+ url(chrome://resources/images/apps/blue_button_focused_hover.png)
+ url(chrome://resources/images/apps/blue_button_focused_pressed.png)
+ url(chrome://resources/images/apps/blue_button_inactive.png);
display: none;
}
}
@@ -34,20 +34,20 @@
@media all and (-webkit-min-device-pixel-ratio: 1.5) {
body::after {
content:
- url('chrome://resources/images/2x/apps/button.png')
- url('chrome://resources/images/2x/apps/button_hover.png')
- url('chrome://resources/images/2x/apps/button_pressed.png')
- url('chrome://resources/images/2x/apps/button_focused.png')
- url('chrome://resources/images/2x/apps/button_focused_hover.png')
- url('chrome://resources/images/2x/apps/button_focused_pressed.png')
- url('chrome://resources/images/2x/apps/button_inactive.png')
- url('chrome://resources/images/2x/apps/blue_button.png')
- url('chrome://resources/images/2x/apps/blue_button_hover.png')
- url('chrome://resources/images/2x/apps/blue_button_pressed.png')
- url('chrome://resources/images/2x/apps/blue_button_focused.png')
- url('chrome://resources/images/2x/apps/blue_button_focused_hover.png')
- url('chrome://resources/images/2x/apps/blue_button_focused_pressed.png')
- url('chrome://resources/images/2x/apps/blue_button_inactive.png');
+ url(chrome://resources/images/2x/apps/button.png)
+ url(chrome://resources/images/2x/apps/button_hover.png)
+ url(chrome://resources/images/2x/apps/button_pressed.png)
+ url(chrome://resources/images/2x/apps/button_focused.png)
+ url(chrome://resources/images/2x/apps/button_focused_hover.png)
+ url(chrome://resources/images/2x/apps/button_focused_pressed.png)
+ url(chrome://resources/images/2x/apps/button_inactive.png)
+ url(chrome://resources/images/2x/apps/blue_button.png)
+ url(chrome://resources/images/2x/apps/blue_button_hover.png)
+ url(chrome://resources/images/2x/apps/blue_button_pressed.png)
+ url(chrome://resources/images/2x/apps/blue_button_focused.png)
+ url(chrome://resources/images/2x/apps/blue_button_focused_hover.png)
+ url(chrome://resources/images/2x/apps/blue_button_focused_pressed.png)
+ url(chrome://resources/images/2x/apps/blue_button_inactive.png);
display: none;
}
}
diff --git a/chromium/chrome/browser/resources/chromeos/genius_app/manifest.json b/chromium/chrome/browser/resources/chromeos/genius_app/manifest.json
index 84f38e9784a..a583f04683a 100644
--- a/chromium/chrome/browser/resources/chromeos/genius_app/manifest.json
+++ b/chromium/chrome/browser/resources/chromeos/genius_app/manifest.json
@@ -23,7 +23,6 @@
}
},
"permissions": [
- "alarms",
"identity",
"identity.email",
"chromeosInfoPrivate",
diff --git a/chromium/chrome/browser/resources/chromeos/guest_session_tab.html b/chromium/chrome/browser/resources/chromeos/guest_session_tab.html
index b7e92b0d719..8ad954d96af 100644
--- a/chromium/chrome/browser/resources/chromeos/guest_session_tab.html
+++ b/chromium/chrome/browser/resources/chromeos/guest_session_tab.html
@@ -1,8 +1,9 @@
-<!DOCTYPE html>
-<html i18n-values="dir:textdirection;">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
<head>
<meta charset="utf-8">
<title i18n-content="title"></title>
+<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="../ntp4/incognito_and_guest_tab.css">
<link rel="stylesheet" href="login/enterprise_info.css">
<script>
@@ -12,7 +13,7 @@ document.write('<link id="incognitothemecss" rel="stylesheet" ' +
Date.now() + '">');
</script>
</head>
-<body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<body>
<div id="enterprise-info" location="guest-tab"
i18n-values="visible:enterpriseInfoVisible;">
<img src="chrome://theme/IDR_CONTROLLED_SETTING_MANDATORY"/>
diff --git a/chromium/chrome/browser/resources/chromeos/image_burner.css b/chromium/chrome/browser/resources/chromeos/image_burner.css
index fa5b3f4b533..50e732784f3 100644
--- a/chromium/chrome/browser/resources/chromeos/image_burner.css
+++ b/chromium/chrome/browser/resources/chromeos/image_burner.css
@@ -133,19 +133,19 @@ html[dir='rtl'] .float-start {
}
#main-content.device-detected-none #status-icon {
- background: url('insert.png');
+ background: url(insert.png);
background-repeat: no-repeat;
display: block;
}
#main-content.device-detected-usb #status-icon {
- background: url('detected_usb.png');
+ background: url(detected_usb.png);
background-repeat: no-repeat;
display: block;
}
#main-content.device-detected-sd #status-icon {
- background: url('detected_sd.png');
+ background: url(detected_sd.png);
background-repeat: no-repeat;
display: block;
}
@@ -189,13 +189,13 @@ html[dir='rtl'] .float-start {
#main-content.warning #warning-icon,
#main-content.error #warning-icon {
- background: url('../../../../ui/webui/resources/images/icon_warning.png');
+ background: url(../../../../ui/webui/resources/images/icon_warning.png);
background-repeat: no-repeat;
display: block;
}
#main-content.success #warning-icon {
- background: url('../../../../ui/webui/resources/images/icon_checkmark.png');
+ background: url(../../../../ui/webui/resources/images/icon_checkmark.png);
background-repeat: no-repeat;
display: block;
}
diff --git a/chromium/chrome/browser/resources/chromeos/image_burner.html b/chromium/chrome/browser/resources/chromeos/image_burner.html
index 606b20ea28f..56377086367 100644
--- a/chromium/chrome/browser/resources/chromeos/image_burner.html
+++ b/chromium/chrome/browser/resources/chromeos/image_burner.html
@@ -1,4 +1,4 @@
-<!DOCTYPE html>
+<!doctype html>
<html>
<head>
<!-- TODO(crbug.com/427785): Remove this file after 2015-10-31. -->
diff --git a/chromium/chrome/browser/resources/chromeos/input_method/cangjie_manifest.json b/chromium/chrome/browser/resources/chromeos/input_method/cangjie_manifest.json
index 89b5476ce8c..d1dee84090e 100644
--- a/chromium/chrome/browser/resources/chromeos/input_method/cangjie_manifest.json
+++ b/chromium/chrome/browser/resources/chromeos/input_method/cangjie_manifest.json
@@ -13,13 +13,18 @@
"ime_path": "/usr/share/chromeos-assets/input_methods/cangjie",
"input_components": [
{
- "name": "__MSG_inputmethod_cangjie__",
- "type": "ime",
- "id": "zh-hant-t-i0-cangjie-1987",
- "indicator": "\u5009",
- "description": "Cangjie",
- "language": ["zh-TW", "zh"],
- "layouts": ["us"]
+ "name": "__MSG_inputmethod_cangjie__",
+ "type": "ime",
+ "id": "zh-hant-t-i0-cangjie-1987",
+ "indicator": "\u5009",
+ "description": "Cangjie",
+ "language": [
+ "zh-TW",
+ "zh"
+ ],
+ "layouts": [
+ "us"
+ ]
}
],
"manifest_version": 2
diff --git a/chromium/chrome/browser/resources/chromeos/input_method/google_input_tools_manifest.json b/chromium/chrome/browser/resources/chromeos/input_method/google_input_tools_manifest.json
index c9f2f28ba27..581ceec9ec0 100644
--- a/chromium/chrome/browser/resources/chromeos/input_method/google_input_tools_manifest.json
+++ b/chromium/chrome/browser/resources/chromeos/input_method/google_input_tools_manifest.json
@@ -6,28 +6,50 @@
"default_locale": "en",
"incognito": "split",
"permissions": [
+ "accessibilityFeatures.read",
+ "app.window.alpha",
+ "app.window.alwaysOnTop",
+ "app.window.ime",
+ "audioCapture",
+ "https://clients4.google.com/",
+ "https://dl.google.com/",
+ "https://www.googleapis.com/",
"input",
- "unlimitedStorage",
"inputMethodPrivate",
- "virtualKeyboardPrivate",
"metricsPrivate",
- "https://dl.google.com/",
- "https://clients4.google.com/",
- "accessibilityFeatures.read"
+ "tabs",
+ "tts",
+ "unlimitedStorage",
+ "virtualKeyboardPrivate"
],
"background": {
"page": "background.html"
},
+ "content_scripts": [
+ {
+ "matches": [
+ "https://www.googleapis.com/auth/imesync*"
+ ],
+ "js": [
+ "chos_inject-debug.js"
+ ]
+ }
+ ],
"ime_path": "/usr/share/chromeos-assets/input_methods/input_tools",
"input_components": [
{
"name": "__MSG_inputmethod_pinyin__",
"type": "ime",
"id": "zh-t-i0-pinyin",
- "indicator": "\u62FC",
+ "indicator": "\u62fc",
"description": "Pinyin",
- "language": ["zh-CN", "zh"],
- "layouts": ["us"],
+ "language": [
+ "zh-CN",
+ "zh"
+ ],
+ "layouts": [
+ "us"
+ ],
"input_view": "inputview.html#id=pinyin-zh-CN.compact.qwerty&language=zh-CN&passwordLayout=pinyin-zh-CN.en.compact.qwerty&name=inputmethod_pinyin",
"options_page": "hmm_options.html?code=zh-t-i0-pinyin"
},
@@ -35,10 +57,15 @@
"name": "__MSG_inputmethod_traditional_pinyin__",
"type": "ime",
"id": "zh-hant-t-i0-pinyin",
- "indicator": "\u62FC",
+ "indicator": "\u62fc",
"description": "Pinyin for Tranditional Chinese",
- "language": ["zh-TW", "zh"],
- "layouts": ["us"],
+ "language": [
+ "zh-TW",
+ "zh"
+ ],
+ "layouts": [
+ "us"
+ ],
"input_view": "inputview.html#id=pinyin-zh-TW&language=zh-TW&passwordLayout=pinyin-zh-TW&name=inputmethod_traditional_pinyin",
"options_page": "hmm_options.html?code=zh-hant-t-i0-pinyin"
},
@@ -48,38 +75,58 @@
"id": "zh-hant-t-i0-cangjie-1987",
"indicator": "\u5009",
"description": "Cangjie",
- "language": ["zh-TW", "zh"],
- "layouts": ["us"],
+ "language": [
+ "zh-TW",
+ "zh"
+ ],
+ "layouts": [
+ "us"
+ ],
"input_view": "inputview.html#id=cangjie&language=zh-TW&passwordLayout=cangjie&name=inputmethod_cangjie"
},
{
"name": "__MSG_inputmethod_quick__",
"type": "ime",
"id": "zh-hant-t-i0-cangjie-1987-x-m0-simplified",
- "indicator": "\u901F",
+ "indicator": "\u901f",
"description": "Quick",
- "language": ["zh-TW", "zh"],
- "layouts": ["us"],
+ "language": [
+ "zh-TW",
+ "zh"
+ ],
+ "layouts": [
+ "us"
+ ],
"input_view": "inputview.html#id=quick&language=zh-TW&passwordLayout=quick&name=inputmethod_quick"
},
{
"name": "__MSG_inputmethod_wubi__",
"type": "ime",
"id": "zh-t-i0-wubi-1986",
- "indicator": "\u4E94",
+ "indicator": "\u4e94",
"description": "Wubi",
- "language": ["zh-CN", "zh"],
- "layouts": ["us"],
+ "language": [
+ "zh-CN",
+ "zh"
+ ],
+ "layouts": [
+ "us"
+ ],
"input_view": "inputview.html#id=wubi&language=zh-CN&passwordLayout=wubi&name=inputmethod_wubi"
},
{
"name": "__MSG_inputmethod_array__",
"type": "ime",
"id": "zh-hant-t-i0-array-1992",
- "indicator": "\u884C\u5217",
+ "indicator": "\u884c\u5217",
"description": "Array",
- "language": ["zh-TW", "zh"],
- "layouts": ["us"],
+ "language": [
+ "zh-TW",
+ "zh"
+ ],
+ "layouts": [
+ "us"
+ ],
"input_view": "inputview.html#id=array&language=zh-TW&passwordLayout=array&name=inputmethod_array"
},
{
@@ -88,18 +135,28 @@
"id": "zh-hant-t-i0-dayi-1988",
"indicator": "\u5927\u6613",
"description": "Dayi",
- "language": ["zh-TW", "zh"],
- "layouts": ["us"],
+ "language": [
+ "zh-TW",
+ "zh"
+ ],
+ "layouts": [
+ "us"
+ ],
"input_view": "inputview.html#id=dayi&language=zh-TW&passwordLayout=dayi&name=inputmethod_dayi"
},
{
"name": "__MSG_inputmethod_zhuyin__",
"type": "ime",
"id": "zh-hant-t-i0-und",
- "indicator": "\u9177",
+ "indicator": "\u6CE8",
"description": "Zhuyin",
- "language": ["zh-TW", "zh"],
- "layouts": ["us"],
+ "language": [
+ "zh-TW",
+ "zh"
+ ],
+ "layouts": [
+ "us"
+ ],
"input_view": "inputview.html#id=zhuyin&language=zh-TW&passwordLayout=zhuyin&name=inputmethod_zhuyin",
"options_page": "hmm_options.html?code=zh-hant-t-i0-und"
},
@@ -109,7 +166,9 @@
"id": "am-t-i0-und",
"description": "Amharic",
"language": "am",
- "layouts": ["us"],
+ "layouts": [
+ "us"
+ ],
"input_view": "inputview.html#id=t13n&language=am&passwordLayout=t13n&name=transliteration_am"
},
{
@@ -118,7 +177,9 @@
"id": "ar-t-i0-und",
"description": "Arabic",
"language": "ar",
- "layouts": ["us"],
+ "layouts": [
+ "us"
+ ],
"input_view": "inputview.html#id=t13n-rtl&language=ar&passwordLayout=t13n-rtl&name=transliteration_ar"
},
{
@@ -127,7 +188,9 @@
"id": "bn-t-i0-und",
"description": "Bengali",
"language": "bn",
- "layouts": ["us"],
+ "layouts": [
+ "us"
+ ],
"input_view": "inputview.html#id=t13n&language=bn&passwordLayout=t13n&name=transliteration_bn"
},
{
@@ -136,7 +199,9 @@
"id": "el-t-i0-und",
"description": "Greek",
"language": "el",
- "layouts": ["us"],
+ "layouts": [
+ "us"
+ ],
"input_view": "inputview.html#id=t13n&language=el&passwordLayout=t13n&name=transliteration_el"
},
{
@@ -145,7 +210,9 @@
"id": "fa-t-i0-und",
"description": "Persian",
"language": "fa",
- "layouts": ["us"],
+ "layouts": [
+ "us"
+ ],
"input_view": "inputview.html#id=t13n-rtl&language=fa&passwordLayout=t13n-rtl&name=transliteration_fa"
},
{
@@ -154,7 +221,9 @@
"id": "gu-t-i0-und",
"description": "Gujarati",
"language": "gu",
- "layouts": ["us"],
+ "layouts": [
+ "us"
+ ],
"input_view": "inputview.html#id=t13n&language=gu&passwordLayout=t13n&name=transliteration_gu"
},
{
@@ -163,7 +232,9 @@
"id": "he-t-i0-und",
"description": "Hebrew",
"language": "he",
- "layouts": ["us"],
+ "layouts": [
+ "us"
+ ],
"input_view": "inputview.html#id=t13n-rtl&language=he&passwordLayout=t13n-rtl&name=transliteration_he"
},
{
@@ -172,7 +243,9 @@
"id": "hi-t-i0-und",
"description": "Hindi",
"language": "hi",
- "layouts": ["us"],
+ "layouts": [
+ "us"
+ ],
"input_view": "inputview.html#id=t13n&language=hi&passwordLayout=t13n&name=transliteration_hi"
},
{
@@ -181,7 +254,9 @@
"id": "kn-t-i0-und",
"description": "Kannada",
"language": "kn",
- "layouts": ["us"],
+ "layouts": [
+ "us"
+ ],
"input_view": "inputview.html#id=t13n&language=kn&passwordLayout=t13n&name=transliteration_kn"
},
{
@@ -190,7 +265,9 @@
"id": "ml-t-i0-und",
"description": "Malayalam",
"language": "ml",
- "layouts": ["us"],
+ "layouts": [
+ "us"
+ ],
"input_view": "inputview.html#id=t13n&language=ml&passwordLayout=t13n&name=transliteration_ml"
},
{
@@ -199,7 +276,9 @@
"id": "mr-t-i0-und",
"description": "Marathi",
"language": "mr",
- "layouts": ["us"],
+ "layouts": [
+ "us"
+ ],
"input_view": "inputview.html#id=t13n&language=mr&passwordLayout=t13n&name=transliteration_mr"
},
{
@@ -208,7 +287,9 @@
"id": "ne-t-i0-und",
"description": "Nepali",
"language": "ne",
- "layouts": ["us"],
+ "layouts": [
+ "us"
+ ],
"input_view": "inputview.html#id=t13n&language=ne&passwordLayout=t13n&name=transliteration_ne"
},
{
@@ -217,7 +298,9 @@
"id": "or-t-i0-und",
"description": "Oriya",
"language": "or",
- "layouts": ["us"],
+ "layouts": [
+ "us"
+ ],
"input_view": "inputview.html#id=t13n&language=or&passwordLayout=t13n&name=transliteration_or"
},
{
@@ -226,7 +309,9 @@
"id": "pa-t-i0-und",
"description": "Punjabi",
"language": "pa",
- "layouts": ["us"],
+ "layouts": [
+ "us"
+ ],
"input_view": "inputview.html#id=t13n&language=pa&passwordLayout=t13n&name=transliteration_pa"
},
{
@@ -235,7 +320,9 @@
"id": "sa-t-i0-und",
"description": "Sanskrit",
"language": "sa",
- "layouts": ["us"],
+ "layouts": [
+ "us"
+ ],
"input_view": "inputview.html#id=t13n&language=sa&passwordLayout=t13n&name=transliteration_sa"
},
{
@@ -244,7 +331,9 @@
"id": "sr-t-i0-und",
"description": "Serbian",
"language": "sr",
- "layouts": ["us"],
+ "layouts": [
+ "us"
+ ],
"input_view": "inputview.html#id=t13n&language=sr&passwordLayout=t13n&name=transliteration_sr"
},
{
@@ -253,7 +342,9 @@
"id": "ta-t-i0-und",
"description": "Tamil",
"language": "ta",
- "layouts": ["us"],
+ "layouts": [
+ "us"
+ ],
"input_view": "inputview.html#id=t13n&language=ta&passwordLayout=t13n&name=transliteration_ta"
},
{
@@ -262,7 +353,9 @@
"id": "te-t-i0-und",
"description": "Telugu",
"language": "te",
- "layouts": ["us"],
+ "layouts": [
+ "us"
+ ],
"input_view": "inputview.html#id=t13n&language=te&passwordLayout=t13n&name=transliteration_te"
},
{
@@ -271,7 +364,9 @@
"id": "ti-t-i0-und",
"description": "Tigrinya",
"language": "ti",
- "layouts": ["us"],
+ "layouts": [
+ "us"
+ ],
"input_view": "inputview.html#id=t13n&language=ti&passwordLayout=t13n&name=transliteration_ti"
},
{
@@ -280,18 +375,22 @@
"id": "ur-t-i0-und",
"description": "Urdu",
"language": "ur",
- "layouts": ["us"],
+ "layouts": [
+ "us"
+ ],
"input_view": "inputview.html#id=t13n-rtl&language=ur&passwordLayout=t13n-rtl&name=transliteration_ur"
},
{
"name": "__MSG_inputmethod_hangul__",
"type": "ime",
"id": "ko-t-i0-und",
- "indicator": "\uD55C",
+ "indicator": "\ud55c",
"description": "Korean input method.",
"language": "ko",
- "layouts": ["us"],
- "input_view": "inputview.html#id=m17n:ko_2set&language=ko&passwordLayout=us&name=inputmethod_hangul",
+ "layouts": [
+ "us"
+ ],
+ "input_view": "inputview.html#id=ko&language=ko&passwordLayout=us-ltr&name=inputmethod_hangul",
"options_page": "hmm_options.html?code=ko-t-i0-und"
},
{
@@ -301,8 +400,10 @@
"indicator": "\u3042",
"description": "Japanese input method.",
"language": "ja",
- "layouts": ["us"],
- "input_view": "inputview.html#id=jp_us&language=ja&passwordLayout=us&name=appNameUSKeyboard",
+ "layouts": [
+ "us"
+ ],
+ "input_view": "inputview.html#id=jp_us&language=ja&passwordLayout=us-ltr&name=appNameUSKeyboard",
"options_page": "mozc_option.html"
},
{
@@ -312,8 +413,10 @@
"indicator": "\u3042",
"description": "Japanese input method.",
"language": "ja",
- "layouts": ["jp"],
- "input_view": "inputview.html#id=jp&language=ja&passwordLayout=us&name=appNameJPKeyboard",
+ "layouts": [
+ "jp"
+ ],
+ "input_view": "inputview.html#id=jp&language=ja&passwordLayout=us-ltr&name=appNameJPKeyboard",
"options_page": "mozc_option.html"
},
{
@@ -321,288 +424,404 @@
"type": "ime",
"id": "vkd_bn_phone",
"description": "",
- "language": ["bn"],
- "layouts": ["us"],
- "input_view": "inputview.html#id=m17n:bn_phone&language=bn&passwordLayout=us&name=keyboard_bengali_phonetic"
+ "language": [
+ "bn"
+ ],
+ "layouts": [
+ "us"
+ ],
+ "input_view": "inputview.html#id=m17n:bn_phone&language=bn&passwordLayout=us-ltr&name=keyboard_bengali_phonetic"
},
{
"name": "__MSG_keyboard_gujarati_phonetic__",
"type": "ime",
"id": "vkd_gu_phone",
"description": "",
- "language": ["gu"],
- "layouts": ["us"],
- "input_view": "inputview.html#id=m17n:gu_phone&language=gu&passwordLayout=us&name=keyboard_gujarati_phonetic"
+ "language": [
+ "gu"
+ ],
+ "layouts": [
+ "us"
+ ],
+ "input_view": "inputview.html#id=m17n:gu_phone&language=gu&passwordLayout=us-ltr&name=keyboard_gujarati_phonetic"
},
{
"name": "__MSG_keyboard_devanagari_phonetic__",
"type": "ime",
"id": "vkd_deva_phone",
"description": "",
- "language": ["hi", "mr"],
- "layouts": ["us"],
- "input_view": "inputview.html#id=m17n:deva_phone&language=hi&passwordLayout=us&name=keyboard_devanagari_phonetic"
+ "language": [
+ "hi",
+ "mr"
+ ],
+ "layouts": [
+ "us"
+ ],
+ "input_view": "inputview.html#id=m17n:deva_phone&language=hi&passwordLayout=us-ltr&name=keyboard_devanagari_phonetic"
},
{
"name": "__MSG_keyboard_kannada_phonetic__",
"type": "ime",
"id": "vkd_kn_phone",
"description": "",
- "language": ["kn"],
- "layouts": ["us"],
- "input_view": "inputview.html#id=m17n:kn_phone&language=kn&passwordLayout=us&name=keyboard_kannada_phonetic"
+ "language": [
+ "kn"
+ ],
+ "layouts": [
+ "us"
+ ],
+ "input_view": "inputview.html#id=m17n:kn_phone&language=kn&passwordLayout=us-ltr&name=keyboard_kannada_phonetic"
},
{
"name": "__MSG_keyboard_malayalam_phonetic__",
"type": "ime",
"id": "vkd_ml_phone",
"description": "",
- "language": ["ml"],
- "layouts": ["us"],
- "input_view": "inputview.html#id=m17n:ml_phone&language=ml&passwordLayout=us&name=keyboard_malayalam_phonetic"
+ "language": [
+ "ml"
+ ],
+ "layouts": [
+ "us"
+ ],
+ "input_view": "inputview.html#id=m17n:ml_phone&language=ml&passwordLayout=us-ltr&name=keyboard_malayalam_phonetic"
},
{
"name": "__MSG_keyboard_tamil_inscript__",
"type": "ime",
"id": "vkd_ta_inscript",
"description": "",
- "language": ["ta"],
- "layouts": ["us"],
- "input_view": "inputview.html#id=m17n:ta_inscript&language=ta&passwordLayout=us&name=keyboard_tamil_inscript"
+ "language": [
+ "ta"
+ ],
+ "layouts": [
+ "us"
+ ],
+ "input_view": "inputview.html#id=m17n:ta_inscript&language=ta&passwordLayout=us-ltr&name=keyboard_tamil_inscript"
},
{
"name": "__MSG_keyboard_tamil_phonetic__",
"type": "ime",
"id": "vkd_ta_phone",
"description": "",
- "language": ["ta"],
- "layouts": ["us"],
- "input_view": "inputview.html#id=m17n:ta_phone&language=ta&passwordLayout=us&name=keyboard_tamil_phonetic"
+ "language": [
+ "ta"
+ ],
+ "layouts": [
+ "us"
+ ],
+ "input_view": "inputview.html#id=m17n:ta_phone&language=ta&passwordLayout=us-ltr&name=keyboard_tamil_phonetic"
},
{
"name": "__MSG_keyboard_tamil_tamil99__",
"type": "ime",
"id": "vkd_ta_tamil99",
"description": "",
- "language": ["ta"],
- "layouts": ["us"],
- "input_view": "inputview.html#id=m17n:ta_tamil99&language=ta&passwordLayout=us&name=keyboard_tamil_tamil99"
+ "language": [
+ "ta"
+ ],
+ "layouts": [
+ "us"
+ ],
+ "input_view": "inputview.html#id=m17n:ta_tamil99&language=ta&passwordLayout=us-ltr&name=keyboard_tamil_tamil99"
},
{
"name": "__MSG_keyboard_tamil_typewriter__",
"type": "ime",
"id": "vkd_ta_typewriter",
"description": "",
- "language": ["ta"],
- "layouts": ["us"],
- "input_view": "inputview.html#id=m17n:ta_typewriter&language=ta&passwordLayout=us&name=keyboard_tamil_typewriter"
+ "language": [
+ "ta"
+ ],
+ "layouts": [
+ "us"
+ ],
+ "input_view": "inputview.html#id=m17n:ta_typewriter&language=ta&passwordLayout=us-ltr&name=keyboard_tamil_typewriter"
},
{
"name": "__MSG_keyboard_tamil_itrans__",
"type": "ime",
"id": "vkd_ta_itrans",
"description": "",
- "language": ["ta"],
- "layouts": ["us"],
- "input_view": "inputview.html#id=m17n:ta_itrans&language=ta&passwordLayout=us&name=keyboard_tamil_itrans"
+ "language": [
+ "ta"
+ ],
+ "layouts": [
+ "us"
+ ],
+ "input_view": "inputview.html#id=m17n:ta_itrans&language=ta&passwordLayout=us-ltr&name=keyboard_tamil_itrans"
},
{
"name": "__MSG_keyboard_telugu_phonetic__",
"type": "ime",
"id": "vkd_te_phone",
"description": "",
- "language": ["te"],
- "layouts": ["us"],
- "input_view": "inputview.html#id=m17n:te_phone&language=te&passwordLayout=us&name=keyboard_telugu_phonetic"
+ "language": [
+ "te"
+ ],
+ "layouts": [
+ "us"
+ ],
+ "input_view": "inputview.html#id=m17n:te_phone&language=te&passwordLayout=us-ltr&name=keyboard_telugu_phonetic"
},
{
"name": "__MSG_keyboard_ethiopic__",
"type": "ime",
"id": "vkd_ethi",
"description": "",
- "language": ["am"],
- "layouts": ["us"],
- "input_view": "inputview.html#id=m17n:ethi&language=am&passwordLayout=us&name=keyboard_ethiopic"
+ "language": [
+ "am"
+ ],
+ "layouts": [
+ "us"
+ ],
+ "input_view": "inputview.html#id=m17n:ethi&language=am&passwordLayout=us-ltr&name=keyboard_ethiopic"
},
{
"name": "__MSG_keyboard_thai_kedmanee__",
"type": "ime",
"id": "vkd_th",
"description": "",
- "language": ["th"],
- "layouts": ["us"],
- "input_view": "inputview.html#id=m17n:th&language=th&passwordLayout=us&name=keyboard_thai_kedmanee"
+ "language": [
+ "th"
+ ],
+ "layouts": [
+ "us"
+ ],
+ "input_view": "inputview.html#id=m17n:th&language=th&passwordLayout=us-ltr&name=keyboard_thai_kedmanee"
},
{
"name": "__MSG_keyboard_thai_pattachote__",
"type": "ime",
"id": "vkd_th_pattajoti",
"description": "",
- "language": ["th"],
- "layouts": ["us"],
- "input_view": "inputview.html#id=m17n:th_pattajoti&language=th&passwordLayout=us&name=keyboard_thai_pattachote"
+ "language": [
+ "th"
+ ],
+ "layouts": [
+ "us"
+ ],
+ "input_view": "inputview.html#id=m17n:th_pattajoti&language=th&passwordLayout=us-ltr&name=keyboard_thai_pattachote"
},
{
"name": "__MSG_keyboard_thai_tis__",
"type": "ime",
"id": "vkd_th_tis",
"description": "",
- "language": ["th"],
- "layouts": ["us"],
- "input_view": "inputview.html#id=m17n:th_tis&language=th&passwordLayout=us&name=keyboard_thai_tis"
+ "language": [
+ "th"
+ ],
+ "layouts": [
+ "us"
+ ],
+ "input_view": "inputview.html#id=m17n:th_tis&language=th&passwordLayout=us-ltr&name=keyboard_thai_tis"
},
{
"name": "__MSG_keyboard_persian__",
"type": "ime",
"id": "vkd_fa",
"description": "",
- "language": ["fa"],
- "layouts": ["us"],
- "input_view": "inputview.html#id=m17n:fa&language=fa&passwordLayout=us&name=keyboard_persian"
+ "language": [
+ "fa"
+ ],
+ "layouts": [
+ "us"
+ ],
+ "input_view": "inputview.html#id=m17n:fa&language=fa&passwordLayout=us-rtl&name=keyboard_persian"
},
{
"name": "__MSG_keyboard_vietnamese_tcvn__",
"type": "ime",
"id": "vkd_vi_tcvn",
"description": "",
- "language": ["vi"],
- "layouts": ["us"],
- "input_view": "inputview.html#id=m17n:vi_tcvn&language=vi&passwordLayout=us&name=keyboard_vietnamese_tcvn"
+ "language": [
+ "vi"
+ ],
+ "layouts": [
+ "us"
+ ],
+ "input_view": "inputview.html#id=m17n:vi_tcvn&language=vi&passwordLayout=us-ltr&name=keyboard_vietnamese_tcvn"
},
{
"name": "__MSG_keyboard_vietnamese_telex__",
"type": "ime",
"id": "vkd_vi_telex",
"description": "",
- "language": ["vi"],
- "layouts": ["us"],
- "input_view": "inputview.html#id=m17n:vi_telex&language=vi&passwordLayout=us&name=keyboard_vietnamese_telex"
+ "language": [
+ "vi"
+ ],
+ "layouts": [
+ "us"
+ ],
+ "input_view": "inputview.html#id=m17n:vi_telex&language=vi&passwordLayout=us-ltr&name=keyboard_vietnamese_telex"
},
{
"name": "__MSG_keyboard_vietnamese_viqr__",
"type": "ime",
"id": "vkd_vi_viqr",
"description": "",
- "language": ["vi"],
- "layouts": ["us"],
- "input_view": "inputview.html#id=m17n:vi_viqr&language=vi&passwordLayout=us&name=keyboard_vietnamese_viqr"
- },
- {
- "name": "__MSG_keyboard_vietnamese_vni__",
- "type": "ime",
- "id": "vkd_vi_vni",
- "description": "",
- "language": ["vi"],
- "layouts": ["us"],
- "input_view": "inputview.html#id=m17n:vi_vni&language=vi&passwordLayout=us&name=keyboard_vietnamese_vni"
+ "language": [
+ "vi"
+ ],
+ "layouts": [
+ "us"
+ ],
+ "input_view": "inputview.html#id=m17n:vi_viqr&language=vi&passwordLayout=us-ltr&name=keyboard_vietnamese_viqr"
},
{
"name": "__MSG_keyboard_arabic__",
"type": "ime",
"id": "vkd_ar",
"description": "",
- "language": ["ar"],
- "layouts": ["us"],
- "input_view": "inputview.html#id=m17n:ar&language=ar&passwordLayout=us&name=keyboard_arabic"
+ "language": [
+ "ar"
+ ],
+ "layouts": [
+ "us"
+ ],
+ "input_view": "inputview.html#id=m17n:ar&language=ar&passwordLayout=us-rtl&name=keyboard_arabic"
},
{
"name": "__MSG_keyboard_lao__",
"type": "ime",
"id": "vkd_lo",
"description": "",
- "language": ["lo"],
- "layouts": ["us"],
- "input_view": "inputview.html#id=m17n:lo&language=lo&passwordLayout=us&name=keyboard_laothian"
+ "language": [
+ "lo"
+ ],
+ "layouts": [
+ "us"
+ ],
+ "input_view": "inputview.html#id=m17n:lo&language=lo&passwordLayout=us-ltr&name=keyboard_laothian"
},
{
"name": "__MSG_keyboard_nepali_inscript__",
"type": "ime",
"id": "vkd_ne_inscript",
"description": "",
- "language": ["ne"],
- "layouts": ["us"],
- "input_view": "inputview.html#id=m17n:ne_inscript&language=ne&passwordLayout=us&name=keyboard_nepali_inscript"
+ "language": [
+ "ne"
+ ],
+ "layouts": [
+ "us"
+ ],
+ "input_view": "inputview.html#id=m17n:ne_inscript&language=ne&passwordLayout=us-ltr&name=keyboard_nepali_inscript"
},
{
"name": "__MSG_keyboard_nepali_phonetic__",
"type": "ime",
"id": "vkd_ne_phone",
"description": "",
- "language": ["ne"],
- "layouts": ["us"],
- "input_view": "inputview.html#id=m17n:ne_phone&language=ne&passwordLayout=us&name=keyboard_nepali_phonetic"
+ "language": [
+ "ne"
+ ],
+ "layouts": [
+ "us"
+ ],
+ "input_view": "inputview.html#id=m17n:ne_phone&language=ne&passwordLayout=us-ltr&name=keyboard_nepali_phonetic"
},
{
"name": "__MSG_keyboard_khmer__",
"type": "ime",
"id": "vkd_km",
"description": "",
- "language": ["km"],
- "layouts": ["us"],
- "input_view": "inputview.html#id=m17n:km&language=km&passwordLayout=us&name=keyboard_khmer"
+ "language": [
+ "km"
+ ],
+ "layouts": [
+ "us"
+ ],
+ "input_view": "inputview.html#id=m17n:km&language=km&passwordLayout=us-ltr&name=keyboard_khmer"
},
{
"name": "__MSG_keyboard_myanmar__",
"type": "ime",
"id": "vkd_my",
"description": "",
- "language": ["my"],
- "layouts": ["us"],
- "input_view": "inputview.html#id=m17n:my&language=my&passwordLayout=us&name=keyboard_myanmar"
+ "language": [
+ "my"
+ ],
+ "layouts": [
+ "us"
+ ],
+ "input_view": "inputview.html#id=m17n:my&language=my&passwordLayout=us-ltr&name=keyboard_myanmar"
},
{
"name": "__MSG_keyboard_sinhala__",
"type": "ime",
"id": "vkd_si",
"description": "",
- "language": ["si"],
- "layouts": ["us"],
- "input_view": "inputview.html#id=m17n:si&language=si&passwordLayout=us&name=keyboard_sinhala"
+ "language": [
+ "si"
+ ],
+ "layouts": [
+ "us"
+ ],
+ "input_view": "inputview.html#id=m17n:si&language=si&passwordLayout=us-ltr&name=keyboard_sinhala"
},
{
"name": "__MSG_keyboard_soranikurdish_en__",
"type": "ime",
"id": "vkd_ckb_en",
"description": "Sorani Kurdish - English-based",
- "language": ["ckb"],
- "layouts": ["us"],
- "input_view": "inputview.html#id=m17n:ckb_en&language=ckb&passwordLayout=us&name=keyboard_soranikurdish_en"
+ "language": [
+ "ckb"
+ ],
+ "layouts": [
+ "us"
+ ],
+ "input_view": "inputview.html#id=m17n:ckb_en&language=ckb&passwordLayout=us-rtl&name=keyboard_soranikurdish_en"
},
{
"name": "__MSG_keyboard_soranikurdish_ar__",
"type": "ime",
"id": "vkd_ckb_ar",
"description": "Sorani Kurdish - Arabic-based",
- "language": ["ckb"],
- "layouts": ["us"],
- "input_view": "inputview.html#id=m17n:ckb_ar&language=ckb&passwordLayout=us&name=keyboard_soranikurdish_ar"
+ "language": [
+ "ckb"
+ ],
+ "layouts": [
+ "us"
+ ],
+ "input_view": "inputview.html#id=m17n:ckb_ar&language=ckb&passwordLayout=us-rtl&name=keyboard_soranikurdish_ar"
},
{
"name": "__MSG_keyboard_myanmar_myansan__",
"type": "ime",
"id": "vkd_my_myansan",
"description": "",
- "language": ["my"],
- "layouts": ["us"],
- "input_view": "inputview.html#id=m17n:my_myansan&language=my&passwordLayout=us&name=keyboard_myanmar_myansan"
+ "language": [
+ "my"
+ ],
+ "layouts": [
+ "us"
+ ],
+ "input_view": "inputview.html#id=m17n:my_myansan&language=my&passwordLayout=us-ltr&name=keyboard_myanmar_myansan"
},
{
"name": "__MSG_keyboard_russian_phonetic_aatseel__",
"type": "ime",
"id": "vkd_ru_phone_aatseel",
"description": "",
- "language": ["ru"],
- "layouts": ["us"],
- "input_view": "inputview.html#id=m17n:ru_phone_aatseel&language=ru&passwordLayout=us&name=keyboard_russian_phonetic_aatseel"
+ "language": [
+ "ru"
+ ],
+ "layouts": [
+ "us"
+ ],
+ "input_view": "inputview.html#id=m17n:ru_phone_aatseel&language=ru&passwordLayout=us-ltr&name=keyboard_russian_phonetic_aatseel"
},
{
"name": "__MSG_keyboard_russian_phonetic_yazhert__",
"type": "ime",
"id": "vkd_ru_phone_yazhert",
"description": "",
- "language": ["ru"],
- "layouts": ["us"],
- "input_view": "inputview.html#id=m17n:ru_phone_yazhert&language=ru&passwordLayout=us&name=keyboard_russian_phonetic_yazhert"
+ "language": [
+ "ru"
+ ],
+ "layouts": [
+ "us"
+ ],
+ "input_view": "inputview.html#id=m17n:ru_phone_yazhert&language=ru&passwordLayout=us-ltr&name=keyboard_russian_phonetic_yazhert"
}
],
"manifest_version": 2
diff --git a/chromium/chrome/browser/resources/chromeos/input_method/google_xkb_manifest.json b/chromium/chrome/browser/resources/chromeos/input_method/google_xkb_manifest.json
index 11d9c084338..592556427a5 100644
--- a/chromium/chrome/browser/resources/chromeos/input_method/google_xkb_manifest.json
+++ b/chromium/chrome/browser/resources/chromeos/input_method/google_xkb_manifest.json
@@ -6,21 +6,36 @@
"incognito": "split",
"key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC7C0oB6YTnf69uhWnVTZl5TB/psHrJXgIPLYchFb0whlVCG8fqMo9lW/oxBmZXZ3N8T7zZrdYI/SUjoc9I5R/dMVVD2q4iKox+x7xlTbqSdVeOb6b9ZVJ24pLbO1L7feSNSBgR0t61jrC2eY/gf78h7w58UEQBPFT2mUxhhwodyQIDAQAB",
"permissions": [
- "app.window.ime",
+ "accessibilityFeatures.read",
"app.window.alpha",
+ "app.window.alwaysOnTop",
+ "app.window.ime",
+ "audioCapture",
+ "https://clients4.google.com/",
+ "https://dl.google.com/",
+ "https://www.googleapis.com/",
"input",
"inputMethodPrivate",
- "virtualKeyboardPrivate",
"metricsPrivate",
+ "tabs",
+ "tts",
"unlimitedStorage",
- "https://dl.google.com/",
- "https://clients4.google.com/",
- "accessibilityFeatures.read"
+ "virtualKeyboardPrivate"
],
"background": {
"page": "background.html",
"persistent": false
},
+ "content_scripts": [
+ {
+ "matches": [
+ "https://www.googleapis.com/auth/imesync*"
+ ],
+ "js": [
+ "chos_inject-debug.js"
+ ]
+ }
+ ],
"ime_path": "/usr/share/chromeos-assets/input_methods/input_tools",
"input_components": [
{
@@ -28,8 +43,15 @@
"type": "ime",
"id": "xkb:us::eng",
"description": "",
- "language": ["en", "en-US", "en-AU", "en-NZ"],
- "layouts": ["us"],
+ "language": [
+ "en",
+ "en-US",
+ "en-AU",
+ "en-NZ"
+ ],
+ "layouts": [
+ "us"
+ ],
"input_view": "inputview.html#id=us.compact.qwerty&language=en-US&passwordLayout=us.compact.qwerty&name=keyboard_us",
"options_page": "hmm_options.html?code=xkb:us::eng"
},
@@ -38,9 +60,13 @@
"type": "ime",
"id": "xkb:us::ind",
"description": "",
- "language": ["id"],
- "layouts": ["us"],
- "input_view": "inputview.html#id=us&language=id&passwordLayout=us&name=keyboard_us",
+ "language": [
+ "id"
+ ],
+ "layouts": [
+ "us"
+ ],
+ "input_view": "inputview.html#id=us-ltr&language=id&passwordLayout=us-ltr&name=keyboard_us",
"options_page": "hmm_options.html?code=xkb:us::ind"
},
{
@@ -48,9 +74,13 @@
"type": "ime",
"id": "xkb:us::fil",
"description": "",
- "language": ["fil"],
- "layouts": ["us"],
- "input_view": "inputview.html#id=us&language=fil&passwordLayout=us&name=keyboard_us",
+ "language": [
+ "fil"
+ ],
+ "layouts": [
+ "us"
+ ],
+ "input_view": "inputview.html#id=us-ltr&language=fil&passwordLayout=us-ltr&name=keyboard_us",
"options_page": "hmm_options.html?code=xkb:us::fil"
},
{
@@ -58,9 +88,13 @@
"type": "ime",
"id": "xkb:us::msa",
"description": "",
- "language": ["ms"],
- "layouts": ["us"],
- "input_view": "inputview.html#id=us&language=ms&passwordLayout=us&name=keyboard_us",
+ "language": [
+ "ms"
+ ],
+ "layouts": [
+ "us"
+ ],
+ "input_view": "inputview.html#id=us-ltr&language=ms&passwordLayout=us-ltr&name=keyboard_us",
"options_page": "hmm_options.html?code=xkb:us::msa"
},
{
@@ -69,20 +103,29 @@
"id": "xkb:us:intl:eng",
"indicator": "INTL",
"description": "",
- "language": ["en", "en-US"],
- "layouts": ["us(intl)"],
+ "language": [
+ "en",
+ "en-US"
+ ],
+ "layouts": [
+ "us(intl)"
+ ],
"input_view": "inputview.html#id=us-intl&language=en-US&passwordLayout=us-intl&name=keyboard_us_international",
"options_page": "hmm_options.html?code=xkb:us:intl:eng"
},
{
- "name": "__MSG_keyboard_us_international__",
+ "name": "__MSG_keyboard_netherlands__",
"type": "ime",
"id": "xkb:us:intl:nld",
- "indicator": "INTL",
- "description": "",
- "language": ["nl"],
- "layouts": ["us(intl)"],
- "input_view": "inputview.html#id=us-intl&language=nl&passwordLayout=us-intl&name=keyboard_us_international",
+ "indicator": "NLD",
+ "description": "",
+ "language": [
+ "nl"
+ ],
+ "layouts": [
+ "us(intl)"
+ ],
+ "input_view": "inputview.html#id=nl.compact.qwerty&language=nl&passwordLayout=nl.compact.qwerty&name=keyboard_netherlands",
"options_page": "hmm_options.html?code=xkb:us:intl:nld"
},
{
@@ -91,8 +134,12 @@
"id": "xkb:us:intl:por",
"indicator": "INTL",
"description": "",
- "language": ["pt-BR"],
- "layouts": ["us(intl)"],
+ "language": [
+ "pt-BR"
+ ],
+ "layouts": [
+ "us(intl)"
+ ],
"input_view": "inputview.html#id=us-intl&language=pt-BR&passwordLayout=us-intl&name=keyboard_us_international",
"options_page": "hmm_options.html?code=xkb:us:intl:por"
},
@@ -102,8 +149,13 @@
"id": "xkb:us:altgr-intl:eng",
"indicator": "EXTD",
"description": "",
- "language": ["en", "en-US"],
- "layouts": ["us(altgr-intl)"],
+ "language": [
+ "en",
+ "en-US"
+ ],
+ "layouts": [
+ "us(altgr-intl)"
+ ],
"input_view": "inputview.html#id=us-altgr-intl&language=en-US&passwordLayout=us-altgr-intl&name=keyboard_us_extended",
"options_page": "hmm_options.html?code=xkb:us:altgr-intl:eng"
},
@@ -113,19 +165,45 @@
"id": "xkb:us:dvorak:eng",
"indicator": "DV",
"description": "",
- "language": ["en", "en-US"],
- "layouts": ["us(dvorak)"],
+ "language": [
+ "en",
+ "en-US"
+ ],
+ "layouts": [
+ "us(dvorak)"
+ ],
"input_view": "inputview.html#id=us-dvorak&language=en-US&passwordLayout=us-dvorak&name=keyboard_us_dvorak",
"options_page": "hmm_options.html?code=xkb:us:dvorak:eng"
},
{
+ "name": "__MSG_keyboard_us_dvp__",
+ "type": "ime",
+ "id": "xkb:us:dvp:eng",
+ "indicator": "DVP",
+ "description": "",
+ "language": [
+ "en",
+ "en-US"
+ ],
+ "layouts": [
+ "us(dvp)"
+ ],
+ "input_view": "inputview.html#id=us-dvp&language=en-US&passwordLayout=us-dvp&name=keyboard_us_dvp",
+ "options_page": "hmm_options.html?code=xkb:us:dvp:eng"
+ },
+ {
"name": "__MSG_keyboard_us_colemak__",
"type": "ime",
"id": "xkb:us:colemak:eng",
"indicator": "CO",
"description": "",
- "language": ["en", "en-US"],
- "layouts": ["us(colemak)"],
+ "language": [
+ "en",
+ "en-US"
+ ],
+ "layouts": [
+ "us(colemak)"
+ ],
"input_view": "inputview.html#id=us-colemak&language=en-US&passwordLayout=us-colemak&name=keyboard_us_colemak",
"options_page": "hmm_options.html?code=xkb:us:colemak:eng"
},
@@ -134,9 +212,13 @@
"type": "ime",
"id": "xkb:be::nld",
"description": "",
- "language": ["nl"],
- "layouts": ["be"],
- "input_view": "inputview.html#id=be.compact.qwerty&language=nl&passwordLayout=be.compact.qwerty&name=keyboard_belgian",
+ "language": [
+ "nl"
+ ],
+ "layouts": [
+ "be"
+ ],
+ "input_view": "inputview.html#id=be&language=nl&passwordLayout=be&name=keyboard_belgian",
"options_page": "hmm_options.html?code=xkb:be::nld"
},
{
@@ -144,8 +226,13 @@
"type": "ime",
"id": "xkb:fr::fra",
"description": "",
- "language": ["fr", "fr-FR"],
- "layouts": ["fr"],
+ "language": [
+ "fr",
+ "fr-FR"
+ ],
+ "layouts": [
+ "fr"
+ ],
"input_view": "inputview.html#id=fr.compact.qwerty&language=fr&passwordLayout=fr.compact.qwerty&name=keyboard_french",
"options_page": "hmm_options.html?code=xkb:fr::fra"
},
@@ -154,8 +241,12 @@
"type": "ime",
"id": "xkb:be::fra",
"description": "",
- "language": ["fr"],
- "layouts": ["be"],
+ "language": [
+ "fr"
+ ],
+ "layouts": [
+ "be"
+ ],
"input_view": "inputview.html#id=be&language=fr&passwordLayout=be&name=keyboard_belgian",
"options_page": "hmm_options.html?code=xkb:be::fra"
},
@@ -164,8 +255,13 @@
"type": "ime",
"id": "xkb:ca::fra",
"description": "",
- "language": ["fr", "fr-CA"],
- "layouts": ["ca"],
+ "language": [
+ "fr",
+ "fr-CA"
+ ],
+ "layouts": [
+ "ca"
+ ],
"input_view": "inputview.html#id=ca.compact.qwerty&language=fr&passwordLayout=ca.compact.qwerty&name=keyboard_canadian_french",
"options_page": "hmm_options.html?code=xkb:ca::fra"
},
@@ -174,8 +270,13 @@
"type": "ime",
"id": "xkb:ch:fr:fra",
"description": "",
- "language": ["fr", "fr-CH"],
- "layouts": ["ch(fr)"],
+ "language": [
+ "fr",
+ "fr-CH"
+ ],
+ "layouts": [
+ "ch(fr)"
+ ],
"input_view": "inputview.html#id=ch-fr&language=fr&passwordLayout=ch-fr&name=keyboard_swiss_french",
"options_page": "hmm_options.html?code=xkb:ch:fr:fra"
},
@@ -184,8 +285,13 @@
"type": "ime",
"id": "xkb:ca:multix:fra",
"description": "",
- "language": ["fr", "fr-CA"],
- "layouts": ["ca(multix)"],
+ "language": [
+ "fr",
+ "fr-CA"
+ ],
+ "layouts": [
+ "ca(multix)"
+ ],
"input_view": "inputview.html#id=ca-multix&language=fr&passwordLayout=ca-multix&name=keyboard_canadian_multilingual",
"options_page": "hmm_options.html?code=xkb:ca:multix:fra"
},
@@ -194,8 +300,13 @@
"type": "ime",
"id": "xkb:de::ger",
"description": "",
- "language": ["de", "de-DE"],
- "layouts": ["de"],
+ "language": [
+ "de",
+ "de-DE"
+ ],
+ "layouts": [
+ "de"
+ ],
"input_view": "inputview.html#id=de.compact.qwerty&language=de&passwordLayout=de.compact.qwerty&name=keyboard_german",
"options_page": "hmm_options.html?code=xkb:de::ger"
},
@@ -205,8 +316,13 @@
"id": "xkb:de:neo:ger",
"indicator": "NEO",
"description": "",
- "language": ["de", "de-DE"],
- "layouts": ["de(neo)"],
+ "language": [
+ "de",
+ "de-DE"
+ ],
+ "layouts": [
+ "de(neo)"
+ ],
"input_view": "inputview.html#id=de-neo&language=de&passwordLayout=de-neo&name=keyboard_german_neo_2",
"options_page": "hmm_options.html?code=xkb:de:neo:ger"
},
@@ -215,8 +331,12 @@
"type": "ime",
"id": "xkb:be::ger",
"description": "",
- "language": ["de"],
- "layouts": ["be"],
+ "language": [
+ "de"
+ ],
+ "layouts": [
+ "be"
+ ],
"input_view": "inputview.html#id=be&language=de&passwordLayout=be&name=keyboard_belgian",
"options_page": "hmm_options.html?code=xkb:be::ger"
},
@@ -225,8 +345,13 @@
"type": "ime",
"id": "xkb:ch::ger",
"description": "",
- "language": ["de", "de-CH"],
- "layouts": ["ch"],
+ "language": [
+ "de",
+ "de-CH"
+ ],
+ "layouts": [
+ "ch"
+ ],
"input_view": "inputview.html#id=ch&language=de&passwordLayout=ch&name=keyboard_swiss",
"options_page": "hmm_options.html?code=xkb:ch::ger"
},
@@ -236,8 +361,12 @@
"id": "xkb:jp::jpn",
"indicator": "JA",
"description": "",
- "language": ["ja"],
- "layouts": ["jp"],
+ "language": [
+ "ja"
+ ],
+ "layouts": [
+ "jp"
+ ],
"input_view": "inputview.html#id=jp&language=ja&passwordLayout=jp&name=keyboard_japanese",
"options_page": "hmm_options.html?code=xkb:jp::jpn"
},
@@ -246,9 +375,13 @@
"type": "ime",
"id": "xkb:ru::rus",
"description": "",
- "language": ["ru"],
- "layouts": ["ru"],
- "input_view": "inputview.html#id=ru&language=ru&passwordLayout=us&name=keyboard_russian",
+ "language": [
+ "ru"
+ ],
+ "layouts": [
+ "ru"
+ ],
+ "input_view": "inputview.html#id=ru&language=ru&passwordLayout=us-ltr&name=keyboard_russian",
"options_page": "hmm_options.html?code=xkb:ru::rus"
},
{
@@ -256,9 +389,13 @@
"type": "ime",
"id": "xkb:ru:phonetic:rus",
"description": "",
- "language": ["ru"],
- "layouts": ["ru(phonetic)"],
- "input_view": "inputview.html#id=ru-phonetic&language=ru&passwordLayout=us&name=keyboard_russian_phonetic",
+ "language": [
+ "ru"
+ ],
+ "layouts": [
+ "ru(phonetic)"
+ ],
+ "input_view": "inputview.html#id=ru-phonetic&language=ru&passwordLayout=us-ltr&name=keyboard_russian_phonetic",
"options_page": "hmm_options.html?code=xkb:ru:phonetic:rus"
},
{
@@ -266,8 +403,13 @@
"type": "ime",
"id": "xkb:br::por",
"description": "",
- "language": ["pt-BR", "pt"],
- "layouts": ["br"],
+ "language": [
+ "pt-BR",
+ "pt"
+ ],
+ "layouts": [
+ "br"
+ ],
"input_view": "inputview.html#id=br&language=pt-BR&passwordLayout=br&name=keyboard_brazilian",
"options_page": "hmm_options.html?code=xkb:br::por"
},
@@ -276,9 +418,13 @@
"type": "ime",
"id": "xkb:bg::bul",
"description": "",
- "language": ["bg"],
- "layouts": ["bg"],
- "input_view": "inputview.html#id=bg&language=bg&passwordLayout=us&name=keyboard_bulgarian",
+ "language": [
+ "bg"
+ ],
+ "layouts": [
+ "bg"
+ ],
+ "input_view": "inputview.html#id=bg&language=bg&passwordLayout=us-ltr&name=keyboard_bulgarian",
"options_page": "hmm_options.html?code=xkb:bg::bul"
},
{
@@ -286,9 +432,13 @@
"type": "ime",
"id": "xkb:bg:phonetic:bul",
"description": "",
- "language": ["bg"],
- "layouts": ["bg(phonetic)"],
- "input_view": "inputview.html#id=bg-phonetic&language=bg&passwordLayout=us&name=keyboard_bulgarian_phonetic",
+ "language": [
+ "bg"
+ ],
+ "layouts": [
+ "bg(phonetic)"
+ ],
+ "input_view": "inputview.html#id=bg-phonetic&language=bg&passwordLayout=us-ltr&name=keyboard_bulgarian_phonetic",
"options_page": "hmm_options.html?code=xkb:bg:phonetic:bul"
},
{
@@ -296,8 +446,13 @@
"type": "ime",
"id": "xkb:ca:eng:eng",
"description": "",
- "language": ["en", "en-CA"],
- "layouts": ["ca(eng)"],
+ "language": [
+ "en",
+ "en-CA"
+ ],
+ "layouts": [
+ "ca(eng)"
+ ],
"input_view": "inputview.html#id=ca-eng.compact.qwerty&language=en-CA&passwordLayout=ca-eng.compact.qwerty&name=keyboard_canadian_english",
"options_page": "hmm_options.html?code=xkb:ca:eng:eng"
},
@@ -306,8 +461,12 @@
"type": "ime",
"id": "xkb:cz::cze",
"description": "",
- "language": ["cs"],
- "layouts": ["cz"],
+ "language": [
+ "cs"
+ ],
+ "layouts": [
+ "cz"
+ ],
"input_view": "inputview.html#id=cz&language=cs&passwordLayout=cz&name=keyboard_czech",
"options_page": "hmm_options.html?code=xkb:cz::cze"
},
@@ -317,8 +476,12 @@
"id": "xkb:cz:qwerty:cze",
"indicator": "CS",
"description": "",
- "language": ["cs"],
- "layouts": ["cz(qwerty)"],
+ "language": [
+ "cs"
+ ],
+ "layouts": [
+ "cz(qwerty)"
+ ],
"input_view": "inputview.html#id=cz-qwerty&language=cs&passwordLayout=cz-qwerty&name=keyboard_czech_qwerty",
"options_page": "hmm_options.html?code=xkb:cz:qwerty:cze"
},
@@ -327,8 +490,12 @@
"type": "ime",
"id": "xkb:ee::est",
"description": "",
- "language": ["et"],
- "layouts": ["ee"],
+ "language": [
+ "et"
+ ],
+ "layouts": [
+ "ee"
+ ],
"input_view": "inputview.html#id=ee&language=et&passwordLayout=ee&name=keyboard_estonian",
"options_page": "hmm_options.html?code=xkb:ee::est"
},
@@ -337,8 +504,12 @@
"type": "ime",
"id": "xkb:es::spa",
"description": "",
- "language": ["es"],
- "layouts": ["es"],
+ "language": [
+ "es"
+ ],
+ "layouts": [
+ "es"
+ ],
"input_view": "inputview.html#id=es&language=es&passwordLayout=es&name=keyboard_spanish",
"options_page": "hmm_options.html?code=xkb:es::spa"
},
@@ -348,8 +519,12 @@
"id": "xkb:es:cat:cat",
"indicator": "CAS",
"description": "",
- "language": ["ca"],
- "layouts": ["es(cat)"],
+ "language": [
+ "ca"
+ ],
+ "layouts": [
+ "es(cat)"
+ ],
"input_view": "inputview.html#id=es-cat&language=ca&passwordLayout=es-cat&name=keyboard_catalan",
"options_page": "hmm_options.html?code=xkb:es:cat:cat"
},
@@ -358,8 +533,12 @@
"type": "ime",
"id": "xkb:dk::dan",
"description": "",
- "language": ["da"],
- "layouts": ["dk"],
+ "language": [
+ "da"
+ ],
+ "layouts": [
+ "dk"
+ ],
"input_view": "inputview.html#id=dk.compact.qwerty&language=da&passwordLayout=dk.compact.qwerty&name=keyboard_danish",
"options_page": "hmm_options.html?code=xkb:dk::dan"
},
@@ -368,9 +547,13 @@
"type": "ime",
"id": "xkb:gr::gre",
"description": "",
- "language": ["el"],
- "layouts": ["gr"],
- "input_view": "inputview.html#id=gr&language=el&passwordLayout=us&name=keyboard_greek",
+ "language": [
+ "el"
+ ],
+ "layouts": [
+ "gr"
+ ],
+ "input_view": "inputview.html#id=gr&language=el&passwordLayout=us-ltr&name=keyboard_greek",
"options_page": "hmm_options.html?code=xkb:gr::gre"
},
{
@@ -378,9 +561,13 @@
"type": "ime",
"id": "xkb:il::heb",
"description": "",
- "language": ["he"],
- "layouts": ["il"],
- "input_view": "inputview.html#id=il&language=he&passwordLayout=us&name=keyboard_hebrew",
+ "language": [
+ "he"
+ ],
+ "layouts": [
+ "il"
+ ],
+ "input_view": "inputview.html#id=il&language=he&passwordLayout=us-rtl&name=keyboard_hebrew",
"options_page": "hmm_options.html?code=xkb:il::heb"
},
{
@@ -389,8 +576,13 @@
"id": "xkb:latam::spa",
"indicator": "LA",
"description": "",
- "language": ["es", "es-419"],
- "layouts": ["latam"],
+ "language": [
+ "es",
+ "es-419"
+ ],
+ "layouts": [
+ "latam"
+ ],
"input_view": "inputview.html#id=latam&language=es&passwordLayout=latam&name=keyboard_latin_american",
"options_page": "hmm_options.html?code=xkb:latam::spa"
},
@@ -399,8 +591,12 @@
"type": "ime",
"id": "xkb:lt::lit",
"description": "",
- "language": ["lt"],
- "layouts": ["lt"],
+ "language": [
+ "lt"
+ ],
+ "layouts": [
+ "lt"
+ ],
"input_view": "inputview.html#id=lt&language=lt&passwordLayout=lt&name=keyboard_lithuanian",
"options_page": "hmm_options.html?code=xkb:lt::lit"
},
@@ -409,8 +605,12 @@
"type": "ime",
"id": "xkb:lv:apostrophe:lav",
"description": "",
- "language": ["lv"],
- "layouts": ["lv(apostrophe)"],
+ "language": [
+ "lv"
+ ],
+ "layouts": [
+ "lv(apostrophe)"
+ ],
"input_view": "inputview.html#id=lv-apostrophe&language=lv&passwordLayout=lv-apostrophe&name=keyboard_latvian",
"options_page": "hmm_options.html?code=xkb:lv:apostrophe:lav"
},
@@ -419,8 +619,12 @@
"type": "ime",
"id": "xkb:hr::scr",
"description": "",
- "language": ["hr"],
- "layouts": ["hr"],
+ "language": [
+ "hr"
+ ],
+ "layouts": [
+ "hr"
+ ],
"input_view": "inputview.html#id=hr&language=hr&passwordLayout=hr&name=keyboard_croatian",
"options_page": "hmm_options.html?code=xkb:hr::scr"
},
@@ -429,8 +633,13 @@
"type": "ime",
"id": "xkb:gb:extd:eng",
"description": "",
- "language": ["en", "en-GB"],
- "layouts": ["gb(extd)"],
+ "language": [
+ "en",
+ "en-GB"
+ ],
+ "layouts": [
+ "gb(extd)"
+ ],
"input_view": "inputview.html#id=gb-extd.compact.qwerty&language=en-GB&passwordLayout=gb-extd.compact.qwerty&name=keyboard_uk",
"options_page": "hmm_options.html?code=xkb:gb:extd:eng"
},
@@ -440,8 +649,13 @@
"id": "xkb:gb:dvorak:eng",
"indicator": "DV",
"description": "",
- "language": ["en", "en-GB"],
- "layouts": ["gb(dvorak)"],
+ "language": [
+ "en",
+ "en-GB"
+ ],
+ "layouts": [
+ "gb(dvorak)"
+ ],
"input_view": "inputview.html#id=gb-dvorak&language=en-GB&passwordLayout=gb-dvorak&name=keyboard_uk_dvorak",
"options_page": "hmm_options.html?code=xkb:gb:dvorak:eng"
},
@@ -450,8 +664,12 @@
"type": "ime",
"id": "xkb:fi::fin",
"description": "",
- "language": ["fi"],
- "layouts": ["fi"],
+ "language": [
+ "fi"
+ ],
+ "layouts": [
+ "fi"
+ ],
"input_view": "inputview.html#id=fi.compact.qwerty&language=fi&passwordLayout=fi.compact.qwerty&name=keyboard_finnish",
"options_page": "hmm_options.html?code=xkb:fi::fin"
},
@@ -460,8 +678,12 @@
"type": "ime",
"id": "xkb:hu::hun",
"description": "",
- "language": ["hu"],
- "layouts": ["hu"],
+ "language": [
+ "hu"
+ ],
+ "layouts": [
+ "hu"
+ ],
"input_view": "inputview.html#id=hu&language=hu&passwordLayout=hu&name=keyboard_hungarian",
"options_page": "hmm_options.html?code=xkb:hu::hun"
},
@@ -470,8 +692,13 @@
"type": "ime",
"id": "xkb:it::ita",
"description": "",
- "language": ["it", "it-IT"],
- "layouts": ["it"],
+ "language": [
+ "it",
+ "it-IT"
+ ],
+ "layouts": [
+ "it"
+ ],
"input_view": "inputview.html#id=it&language=it&passwordLayout=it&name=keyboard_italian",
"options_page": "hmm_options.html?code=xkb:it::ita"
},
@@ -480,8 +707,12 @@
"type": "ime",
"id": "xkb:is::ice",
"description": "",
- "language": ["is"],
- "layouts": ["is"],
+ "language": [
+ "is"
+ ],
+ "layouts": [
+ "is"
+ ],
"input_view": "inputview.html#id=is.compact.qwerty&language=is&passwordLayout=is.compact.qwerty&name=keyboard_icelandic",
"options_page": "hmm_options.html?code=xkb:is::ice"
},
@@ -490,8 +721,14 @@
"type": "ime",
"id": "xkb:no::nob",
"description": "",
- "language": ["nb", "nn", "no"],
- "layouts": ["no"],
+ "language": [
+ "nb",
+ "nn",
+ "no"
+ ],
+ "layouts": [
+ "no"
+ ],
"input_view": "inputview.html#id=no.compact.qwerty&language=no&passwordLayout=no.compact.qwerty&name=keyboard_norwegian",
"options_page": "hmm_options.html?code=xkb:no::nob"
},
@@ -500,8 +737,12 @@
"type": "ime",
"id": "xkb:pl::pol",
"description": "",
- "language": ["pl"],
- "layouts": ["pl"],
+ "language": [
+ "pl"
+ ],
+ "layouts": [
+ "pl"
+ ],
"input_view": "inputview.html#id=pl&language=pl&passwordLayout=pl&name=keyboard_polish",
"options_page": "hmm_options.html?code=xkb:pl::pol"
},
@@ -510,8 +751,13 @@
"type": "ime",
"id": "xkb:pt::por",
"description": "",
- "language": ["pt-PT", "pt"],
- "layouts": ["pt"],
+ "language": [
+ "pt-PT",
+ "pt"
+ ],
+ "layouts": [
+ "pt"
+ ],
"input_view": "inputview.html#id=pt&language=pt-PT&passwordLayout=pt&name=keyboard_portuguese",
"options_page": "hmm_options.html?code=xkb:pt::por"
},
@@ -520,18 +766,40 @@
"type": "ime",
"id": "xkb:ro::rum",
"description": "",
- "language": ["ro"],
- "layouts": ["ro"],
+ "language": [
+ "ro"
+ ],
+ "layouts": [
+ "ro"
+ ],
"input_view": "inputview.html#id=ro&language=ro&passwordLayout=ro&name=keyboard_romanian",
"options_page": "hmm_options.html?code=xkb:ro::rum"
},
{
+ "name": "__MSG_keyboard_romanian_standard__",
+ "type": "ime",
+ "id": "xkb:ro:std:rum",
+ "description": "",
+ "language": [
+ "ro"
+ ],
+ "layouts": [
+ "ro(std)"
+ ],
+ "input_view": "inputview.html#id=ro-std&language=ro&passwordLayout=ro-std&name=keyboard_romanian_standard",
+ "options_page": "hmm_options.html?code=xkb:ro:std:rum"
+ },
+ {
"name": "__MSG_keyboard_swedish__",
"type": "ime",
"id": "xkb:se::swe",
"description": "",
- "language": ["sv"],
- "layouts": ["se"],
+ "language": [
+ "sv"
+ ],
+ "layouts": [
+ "se"
+ ],
"input_view": "inputview.html#id=se.compact.qwerty&language=sv&passwordLayout=se.compact.qwerty&name=keyboard_swedish",
"options_page": "hmm_options.html?code=xkb:se::swe"
},
@@ -540,9 +808,13 @@
"type": "ime",
"id": "xkb:sk::slo",
"description": "",
- "language": ["sk"],
- "layouts": ["sk"],
- "input_view": "inputview.html#id=sk&language=sk&passwordLayout=us&name=keyboard_slovakian",
+ "language": [
+ "sk"
+ ],
+ "layouts": [
+ "sk"
+ ],
+ "input_view": "inputview.html#id=sk&language=sk&passwordLayout=us-ltr&name=keyboard_slovakian",
"options_page": "hmm_options.html?code=xkb:sk::slo"
},
{
@@ -550,8 +822,12 @@
"type": "ime",
"id": "xkb:si::slv",
"description": "",
- "language": ["sl"],
- "layouts": ["si"],
+ "language": [
+ "sl"
+ ],
+ "layouts": [
+ "si"
+ ],
"input_view": "inputview.html#id=si&language=sl&passwordLayout=si&name=keyboard_slovenian",
"options_page": "hmm_options.html?code=xkb:si::slv"
},
@@ -560,9 +836,13 @@
"type": "ime",
"id": "xkb:rs::srp",
"description": "",
- "language": ["sr"],
- "layouts": ["rs"],
- "input_view": "inputview.html#id=rs&language=sr&passwordLayout=us&name=keyboard_serbian",
+ "language": [
+ "sr"
+ ],
+ "layouts": [
+ "rs"
+ ],
+ "input_view": "inputview.html#id=rs&language=sr&passwordLayout=us-ltr&name=keyboard_serbian",
"options_page": "hmm_options.html?code=xkb:rs::srp"
},
{
@@ -570,8 +850,12 @@
"type": "ime",
"id": "xkb:tr::tur",
"description": "",
- "language": ["tr"],
- "layouts": ["tr"],
+ "language": [
+ "tr"
+ ],
+ "layouts": [
+ "tr"
+ ],
"input_view": "inputview.html#id=tr&language=tr&passwordLayout=tr&name=keyboard_turkish",
"options_page": "hmm_options.html?code=xkb:tr::tur"
},
@@ -580,9 +864,13 @@
"type": "ime",
"id": "xkb:ua::ukr",
"description": "",
- "language": ["uk"],
- "layouts": ["ua"],
- "input_view": "inputview.html#id=ua&language=uk&passwordLayout=us&name=keyboard_ukrainian",
+ "language": [
+ "uk"
+ ],
+ "layouts": [
+ "ua"
+ ],
+ "input_view": "inputview.html#id=ua&language=uk&passwordLayout=us-ltr&name=keyboard_ukrainian",
"options_page": "hmm_options.html?code=xkb:ua::ukr"
},
{
@@ -590,9 +878,13 @@
"type": "ime",
"id": "xkb:by::bel",
"description": "",
- "language": ["be"],
- "layouts": ["by"],
- "input_view": "inputview.html#id=by&language=be&passwordLayout=us&name=keyboard_belarusian",
+ "language": [
+ "be"
+ ],
+ "layouts": [
+ "by"
+ ],
+ "input_view": "inputview.html#id=by&language=be&passwordLayout=us-ltr&name=keyboard_belarusian",
"options_page": "hmm_options.html?code=xkb:by::bel"
},
{
@@ -600,9 +892,13 @@
"type": "ime",
"id": "xkb:am:phonetic:arm",
"description": "",
- "language": ["hy"],
- "layouts": ["am"],
- "input_view": "inputview.html#id=am&language=hy&passwordLayout=us&name=keyboard_armenian_phonetic",
+ "language": [
+ "hy"
+ ],
+ "layouts": [
+ "am(phonetic)"
+ ],
+ "input_view": "inputview.html#id=am-phonetic&language=hy&passwordLayout=us-ltr&name=keyboard_armenian_phonetic",
"options_page": "hmm_options.html?code=xkb:am:phonetic:arm"
},
{
@@ -610,9 +906,13 @@
"type": "ime",
"id": "xkb:ge::geo",
"description": "",
- "language": ["ka"],
- "layouts": ["ge"],
- "input_view": "inputview.html#id=ge&language=ka&passwordLayout=us&name=keyboard_georgian",
+ "language": [
+ "ka"
+ ],
+ "layouts": [
+ "ge"
+ ],
+ "input_view": "inputview.html#id=ge&language=ka&passwordLayout=us-ltr&name=keyboard_georgian",
"options_page": "hmm_options.html?code=xkb:ge::geo"
},
{
@@ -620,9 +920,13 @@
"type": "ime",
"id": "xkb:mn::mon",
"description": "",
- "language": ["mn"],
- "layouts": ["mn"],
- "input_view": "inputview.html#id=mn&language=mn&passwordLayout=us&name=keyboard_mongolian",
+ "language": [
+ "mn"
+ ],
+ "layouts": [
+ "mn"
+ ],
+ "input_view": "inputview.html#id=mn&language=mn&passwordLayout=us-ltr&name=keyboard_mongolian",
"options_page": "hmm_options.html?code=xkb:mn::mon"
},
{
@@ -630,8 +934,12 @@
"type": "ime",
"id": "xkb:ie::ga",
"description": "",
- "language": ["ga"],
- "layouts": ["ie"],
+ "language": [
+ "ga"
+ ],
+ "layouts": [
+ "ie"
+ ],
"input_view": "inputview.html#id=ie.compact.qwerty&language=ga&passwordLayout=ie.compact.qwerty&name=keyboard_irish",
"options_page": "hmm_options.html?code=xkb:ie::ga"
},
@@ -640,8 +948,12 @@
"type": "ime",
"id": "xkb:mt::mlt",
"description": "",
- "language": ["mt"],
- "layouts": ["mt"],
+ "language": [
+ "mt"
+ ],
+ "layouts": [
+ "mt"
+ ],
"input_view": "inputview.html#id=mt&language=mt&passwordLayout=mt&name=keyboard_maltese",
"options_page": "hmm_options.html?code=xkb:mt::mlt"
}
diff --git a/chromium/chrome/browser/resources/chromeos/input_method/hangul_manifest.json b/chromium/chrome/browser/resources/chromeos/input_method/hangul_manifest.json
index 4024a481c2a..f6216def4c5 100644
--- a/chromium/chrome/browser/resources/chromeos/input_method/hangul_manifest.json
+++ b/chromium/chrome/browser/resources/chromeos/input_method/hangul_manifest.json
@@ -18,61 +18,80 @@
"page": "backgroundpage.html"
},
"ime_path": "/usr/share/chromeos-assets/input_methods/hangul",
- "input_components": [{
- "name": "__MSG_inputmethod_Hangul_2_Set__",
- "type": "ime",
- "id": "hangul_2set",
- "indicator": "\uD55C",
- "description": "",
- "language": "ko",
- "layouts": ["us"],
- "input_view": "inputview.html#id=m17n:ko_2set&language=ko&passwordLayout=us&name=Hangul_2_Set&isFakeEventUsed=true"
- }, {
- "name": "__MSG_inputmethod_Hangul_3_Set_390__",
- "type": "ime",
- "id": "hangul_3set390",
- "indicator": "\uD55C",
- "description": "",
- "language": "ko",
- "layouts": ["us"],
- "input_view": "inputview.html#id=us&language=ko&passwordLayout=us&name=Hangul_3_Set_390&isFakeEventUsed=true"
- }, {
- "name": "__MSG_inputmethod_Hangul_3_Set_Final__",
- "type": "ime",
- "id": "hangul_3setfinal",
- "indicator": "\uD55C",
- "description": "",
- "language": "ko",
- "layouts": ["us"],
- "input_view": "inputview.html#id=us&language=ko&passwordLayout=us&name=Hangul_3_Set_Final&isFakeEventUsed=true"
- }, {
- "name": "__MSG_inputmethod_Hangul_3_Set_No_Shift__",
- "type": "ime",
- "id": "hangul_3setnoshift",
- "indicator": "\uD55C",
- "description": "",
- "language": "ko",
- "layouts": ["us"],
- "input_view": "inputview.html#id=us&language=ko&passwordLayout=us&name=Hangul_3_Set_No_Shift&isFakeEventUsed=true"
- }, {
- "name": "__MSG_inputmethod_Hangul_Romaja__",
- "type": "ime",
- "id": "hangul_romaja",
- "indicator": "\uD55C",
- "description": "",
- "language": "ko",
- "layouts": ["us"],
- "input_view": "inputview.html#id=us&language=ko&passwordLayout=us&name=Hangul_Romaja&isFakeEventUsed=true"
- }, {
- "name": "__MSG_inputmethod_Hangul_Ahnmatae__",
- "type": "ime",
- "id": "hangul_ahnmatae",
- "indicator": "\uD55C",
- "description": "",
- "language": "ko",
- "layouts": ["us"],
- "input_view": "inputview.html#id=us&language=ko&passwordLayout=us&name=Hangul_Ahnmatae&isFakeEventUsed=true"
- }],
+ "input_components": [
+ {
+ "name": "__MSG_inputmethod_Hangul_2_Set__",
+ "type": "ime",
+ "id": "hangul_2set",
+ "indicator": "\ud55c",
+ "description": "",
+ "language": "ko",
+ "layouts": [
+ "us"
+ ],
+ "input_view": "inputview.html#id=m17n:ko_2set&language=ko&passwordLayout=us&name=Hangul_2_Set&isFakeEventUsed=true"
+ },
+ {
+ "name": "__MSG_inputmethod_Hangul_3_Set_390__",
+ "type": "ime",
+ "id": "hangul_3set390",
+ "indicator": "\ud55c",
+ "description": "",
+ "language": "ko",
+ "layouts": [
+ "us"
+ ],
+ "input_view": "inputview.html#id=us&language=ko&passwordLayout=us&name=Hangul_3_Set_390&isFakeEventUsed=true"
+ },
+ {
+ "name": "__MSG_inputmethod_Hangul_3_Set_Final__",
+ "type": "ime",
+ "id": "hangul_3setfinal",
+ "indicator": "\ud55c",
+ "description": "",
+ "language": "ko",
+ "layouts": [
+ "us"
+ ],
+ "input_view": "inputview.html#id=us&language=ko&passwordLayout=us&name=Hangul_3_Set_Final&isFakeEventUsed=true"
+ },
+ {
+ "name": "__MSG_inputmethod_Hangul_3_Set_No_Shift__",
+ "type": "ime",
+ "id": "hangul_3setnoshift",
+ "indicator": "\ud55c",
+ "description": "",
+ "language": "ko",
+ "layouts": [
+ "us"
+ ],
+ "input_view": "inputview.html#id=us&language=ko&passwordLayout=us&name=Hangul_3_Set_No_Shift&isFakeEventUsed=true"
+ },
+ {
+ "name": "__MSG_inputmethod_Hangul_Romaja__",
+ "type": "ime",
+ "id": "hangul_romaja",
+ "indicator": "\ud55c",
+ "description": "",
+ "language": "ko",
+ "layouts": [
+ "us"
+ ],
+ "input_view": "inputview.html#id=us&language=ko&passwordLayout=us&name=Hangul_Romaja&isFakeEventUsed=true"
+ },
+ {
+ "name": "__MSG_inputmethod_Hangul_Ahnmatae__",
+ "type": "ime",
+ "id": "hangul_ahnmatae",
+ "indicator": "\ud55c",
+ "description": "",
+ "language": "ko",
+ "layouts": [
+ "us"
+ ],
+ "input_view": "inputview.html#id=us&language=ko&passwordLayout=us&name=Hangul_Ahnmatae&isFakeEventUsed=true"
+ }
+ ],
"icons": {
"128": "icon128.png"
}
diff --git a/chromium/chrome/browser/resources/chromeos/input_method/m17n_manifest.json b/chromium/chrome/browser/resources/chromeos/input_method/m17n_manifest.json
index c85b7c62fb4..145235ffc26 100644
--- a/chromium/chrome/browser/resources/chromeos/input_method/m17n_manifest.json
+++ b/chromium/chrome/browser/resources/chromeos/input_method/m17n_manifest.json
@@ -8,7 +8,9 @@
"input"
],
"background": {
- "scripts": ["cros_vk_background.js"]
+ "scripts": [
+ "cros_vk_background.js"
+ ]
},
"ime_path": "/usr/share/chromeos-assets/input_methods/keyboard_layouts",
"input_components": [
@@ -18,7 +20,9 @@
"id": "vkd_bn_phone",
"description": "",
"language": "bn",
- "layouts": ["us"]
+ "layouts": [
+ "us"
+ ]
},
{
"name": "__MSG_keyboard_gujarati_phonetic__",
@@ -26,15 +30,22 @@
"id": "vkd_gu_phone",
"description": "",
"language": "gu",
- "layouts": ["us"]
+ "layouts": [
+ "us"
+ ]
},
{
"name": "__MSG_keyboard_devanagari_phonetic__",
"type": "ime",
"id": "vkd_deva_phone",
"description": "",
- "language": ["hi", "mr"],
- "layouts": ["us"]
+ "language": [
+ "hi",
+ "mr"
+ ],
+ "layouts": [
+ "us"
+ ]
},
{
"name": "__MSG_keyboard_kannada_phonetic__",
@@ -42,7 +53,9 @@
"id": "vkd_kn_phone",
"description": "",
"language": "kn",
- "layouts": ["us"]
+ "layouts": [
+ "us"
+ ]
},
{
"name": "__MSG_keyboard_malayalam_phonetic__",
@@ -50,7 +63,9 @@
"id": "vkd_ml_phone",
"description": "",
"language": "ml",
- "layouts": ["us"]
+ "layouts": [
+ "us"
+ ]
},
{
"name": "__MSG_keyboard_tamil_inscript__",
@@ -58,7 +73,9 @@
"id": "vkd_ta_inscript",
"description": "",
"language": "ta",
- "layouts": ["us"]
+ "layouts": [
+ "us"
+ ]
},
{
"name": "__MSG_keyboard_tamil_phonetic__",
@@ -66,7 +83,9 @@
"id": "vkd_ta_phone",
"description": "",
"language": "ta",
- "layouts": ["us"]
+ "layouts": [
+ "us"
+ ]
},
{
"name": "__MSG_keyboard_tamil_tamil99__",
@@ -74,7 +93,9 @@
"id": "vkd_ta_tamil99",
"description": "",
"language": "ta",
- "layouts": ["us"]
+ "layouts": [
+ "us"
+ ]
},
{
"name": "__MSG_keyboard_tamil_typewriter__",
@@ -82,7 +103,9 @@
"id": "vkd_ta_typewriter",
"description": "",
"language": "ta",
- "layouts": ["us"]
+ "layouts": [
+ "us"
+ ]
},
{
"name": "__MSG_keyboard_tamil_itrans__",
@@ -90,7 +113,9 @@
"id": "vkd_ta_itrans",
"description": "",
"language": "ta",
- "layouts": ["us"]
+ "layouts": [
+ "us"
+ ]
},
{
"name": "__MSG_keyboard_telugu_phonetic__",
@@ -98,7 +123,9 @@
"id": "vkd_te_phone",
"description": "",
"language": "te",
- "layouts": ["us"]
+ "layouts": [
+ "us"
+ ]
},
{
"name": "__MSG_keyboard_ethiopic__",
@@ -106,7 +133,9 @@
"id": "vkd_ethi",
"description": "",
"language": "am",
- "layouts": ["us"]
+ "layouts": [
+ "us"
+ ]
},
{
"name": "__MSG_keyboard_thai_kedmanee__",
@@ -114,7 +143,9 @@
"id": "vkd_th",
"description": "",
"language": "th",
- "layouts": ["us"]
+ "layouts": [
+ "us"
+ ]
},
{
"name": "__MSG_keyboard_thai_pattachote__",
@@ -122,7 +153,9 @@
"id": "vkd_th_pattajoti",
"description": "",
"language": "th",
- "layouts": ["us"]
+ "layouts": [
+ "us"
+ ]
},
{
"name": "__MSG_keyboard_thai_tis__",
@@ -130,7 +163,9 @@
"id": "vkd_th_tis",
"description": "",
"language": "th",
- "layouts": ["us"]
+ "layouts": [
+ "us"
+ ]
},
{
"name": "__MSG_keyboard_persian__",
@@ -138,7 +173,9 @@
"id": "vkd_fa",
"description": "",
"language": "fa",
- "layouts": ["us"]
+ "layouts": [
+ "us"
+ ]
},
{
"name": "__MSG_keyboard_vietnamese_tcvn__",
@@ -146,7 +183,9 @@
"id": "vkd_vi_tcvn",
"description": "",
"language": "vi",
- "layouts": ["us"]
+ "layouts": [
+ "us"
+ ]
},
{
"name": "__MSG_keyboard_vietnamese_telex__",
@@ -154,7 +193,9 @@
"id": "vkd_vi_telex",
"description": "",
"language": "vi",
- "layouts": ["us"]
+ "layouts": [
+ "us"
+ ]
},
{
"name": "__MSG_keyboard_vietnamese_viqr__",
@@ -162,7 +203,9 @@
"id": "vkd_vi_viqr",
"description": "",
"language": "vi",
- "layouts": ["us"]
+ "layouts": [
+ "us"
+ ]
},
{
"name": "__MSG_keyboard_vietnamese_vni__",
@@ -170,7 +213,9 @@
"id": "vkd_vi_vni",
"description": "",
"language": "vi",
- "layouts": ["us"]
+ "layouts": [
+ "us"
+ ]
},
{
"name": "__MSG_keyboard_arabic__",
@@ -178,7 +223,9 @@
"id": "vkd_ar",
"description": "",
"language": "ar",
- "layouts": ["us"]
+ "layouts": [
+ "us"
+ ]
}
],
"manifest_version": 2
diff --git a/chromium/chrome/browser/resources/chromeos/input_method/mozc_manifest.json b/chromium/chrome/browser/resources/chromeos/input_method/mozc_manifest.json
index 6bce93fdb3e..dc29e902802 100644
--- a/chromium/chrome/browser/resources/chromeos/input_method/mozc_manifest.json
+++ b/chromium/chrome/browser/resources/chromeos/input_method/mozc_manifest.json
@@ -14,23 +14,30 @@
"page": "nacl_mozc.html"
},
"ime_path": "/usr/share/chromeos-assets/input_methods/nacl_mozc",
- "input_components": [{
- "name": "__MSG_inputmethod_mozc_us__",
- "type": "ime",
- "id": "nacl_mozc_us",
- "indicator": "\u3042",
- "description": "Japanese input method.",
- "language": "ja",
- "layouts": ["us"]
- },{
- "name": "__MSG_inputmethod_mozc_jp__",
- "type": "ime",
- "id": "nacl_mozc_jp",
- "indicator": "\u3042",
- "description": "Japanese input method.",
- "language": "ja",
- "layouts": ["jp"]
- }],
+ "input_components": [
+ {
+ "name": "__MSG_inputmethod_mozc_us__",
+ "type": "ime",
+ "id": "nacl_mozc_us",
+ "indicator": "\u3042",
+ "description": "Japanese input method.",
+ "language": "ja",
+ "layouts": [
+ "us"
+ ]
+ },
+ {
+ "name": "__MSG_inputmethod_mozc_jp__",
+ "type": "ime",
+ "id": "nacl_mozc_jp",
+ "indicator": "\u3042",
+ "description": "Japanese input method.",
+ "language": "ja",
+ "layouts": [
+ "jp"
+ ]
+ }
+ ],
"options_page": "options.html",
"icons": {
"128": "product_icon_32bpp-128.png"
diff --git a/chromium/chrome/browser/resources/chromeos/input_method/pinyin_manifest.json b/chromium/chrome/browser/resources/chromeos/input_method/pinyin_manifest.json
index 0030aef0121..f6fc4c0f799 100644
--- a/chromium/chrome/browser/resources/chromeos/input_method/pinyin_manifest.json
+++ b/chromium/chrome/browser/resources/chromeos/input_method/pinyin_manifest.json
@@ -14,13 +14,18 @@
"ime_path": "/usr/share/chromeos-assets/input_methods/pinyin",
"input_components": [
{
- "name": "__MSG_inputmethod_pinyin__",
- "type": "ime",
- "id": "zh-t-i0-pinyin",
- "indicator": "\u62FC",
- "description": "Pinyin",
- "language": ["zh-CN", "zh"],
- "layouts": ["us"]
+ "name": "__MSG_inputmethod_pinyin__",
+ "type": "ime",
+ "id": "zh-t-i0-pinyin",
+ "indicator": "\u62fc",
+ "description": "Pinyin",
+ "language": [
+ "zh-CN",
+ "zh"
+ ],
+ "layouts": [
+ "us"
+ ]
}
],
"options_page": "option.html",
diff --git a/chromium/chrome/browser/resources/chromeos/input_method/xkb_manifest.json b/chromium/chrome/browser/resources/chromeos/input_method/xkb_manifest.json
index 974b7a7d9ae..bd05625efe9 100644
--- a/chromium/chrome/browser/resources/chromeos/input_method/xkb_manifest.json
+++ b/chromium/chrome/browser/resources/chromeos/input_method/xkb_manifest.json
@@ -22,8 +22,15 @@
"type": "ime",
"id": "xkb:us::eng",
"description": "",
- "language": ["en", "en-US", "en-AU", "en-NZ"],
- "layouts": ["us"],
+ "language": [
+ "en",
+ "en-US",
+ "en-AU",
+ "en-NZ"
+ ],
+ "layouts": [
+ "us"
+ ],
"input_view": "inputview.html?id=us.compact.qwerty&language=en-US&passwordLayout=us.compact.qwerty&name=keyboard_us"
},
{
@@ -31,8 +38,12 @@
"type": "ime",
"id": "xkb:us::ind",
"description": "",
- "language": ["id"],
- "layouts": ["us"],
+ "language": [
+ "id"
+ ],
+ "layouts": [
+ "us"
+ ],
"input_view": "inputview.html?id=us&language=id&passwordLayout=us&name=keyboard_us"
},
{
@@ -40,8 +51,12 @@
"type": "ime",
"id": "xkb:us::fil",
"description": "",
- "language": ["fil"],
- "layouts": ["us"],
+ "language": [
+ "fil"
+ ],
+ "layouts": [
+ "us"
+ ],
"input_view": "inputview.html?id=us&language=fil&passwordLayout=us&name=keyboard_us"
},
{
@@ -49,8 +64,12 @@
"type": "ime",
"id": "xkb:us::msa",
"description": "",
- "language": ["ms"],
- "layouts": ["us"],
+ "language": [
+ "ms"
+ ],
+ "layouts": [
+ "us"
+ ],
"input_view": "inputview.html?id=us&language=ms&passwordLayout=us&name=keyboard_us"
},
{
@@ -59,19 +78,28 @@
"id": "xkb:us:intl:eng",
"indicator": "INTL",
"description": "",
- "language": ["en", "en-US"],
- "layouts": ["us(intl)"],
+ "language": [
+ "en",
+ "en-US"
+ ],
+ "layouts": [
+ "us(intl)"
+ ],
"input_view": "inputview.html?id=us-intl&language=en-US&passwordLayout=us-intl&name=keyboard_us_international"
},
{
- "name": "__MSG_keyboard_us_international__",
+ "name": "__MSG_keyboard_netherlands__",
"type": "ime",
"id": "xkb:us:intl:nld",
- "indicator": "INTL",
+ "indicator": "NLD",
"description": "",
- "language": ["nl"],
- "layouts": ["us(intl)"],
- "input_view": "inputview.html?id=us-intl&language=nl&passwordLayout=us-intl&name=keyboard_us_international"
+ "language": [
+ "nl"
+ ],
+ "layouts": [
+ "us(intl)"
+ ],
+ "input_view": "inputview.html?id=nl.compact.qwerty&language=nl&passwordLayout=nl.compact.qwerty&name=keyboard_netherlands"
},
{
"name": "__MSG_keyboard_us_international__",
@@ -79,8 +107,12 @@
"id": "xkb:us:intl:por",
"indicator": "INTL",
"description": "",
- "language": ["pt-BR"],
- "layouts": ["us(intl)"],
+ "language": [
+ "pt-BR"
+ ],
+ "layouts": [
+ "us(intl)"
+ ],
"input_view": "inputview.html?id=us-intl&language=pt-BR&passwordLayout=us-intl&name=keyboard_us_international"
},
{
@@ -89,8 +121,13 @@
"id": "xkb:us:altgr-intl:eng",
"indicator": "EXTD",
"description": "",
- "language": ["en", "en-US"],
- "layouts": ["us(altgr-intl)"],
+ "language": [
+ "en",
+ "en-US"
+ ],
+ "layouts": [
+ "us(altgr-intl)"
+ ],
"input_view": "inputview.html?id=us-altgr-intl&language=en-US&passwordLayout=us-altgr-intl&name=keyboard_us_extended"
},
{
@@ -99,18 +136,43 @@
"id": "xkb:us:dvorak:eng",
"indicator": "DV",
"description": "",
- "language": ["en", "en-US"],
- "layouts": ["us(dvorak)"],
+ "language": [
+ "en",
+ "en-US"
+ ],
+ "layouts": [
+ "us(dvorak)"
+ ],
"input_view": "inputview.html?id=us-dvorak&language=en-US&passwordLayout=us-dvorak&name=keyboard_us_dvorak"
},
{
+ "name": "__MSG_keyboard_us_dvp__",
+ "type": "ime",
+ "id": "xkb:us:dvp:eng",
+ "indicator": "DVP",
+ "description": "",
+ "language": [
+ "en",
+ "en-US"
+ ],
+ "layouts": [
+ "us(dvp)"
+ ],
+ "input_view": "inputview.html?id=us-dvp&language=en-US&passwordLayout=us-dvp&name=keyboard_us_dvp"
+ },
+ {
"name": "__MSG_keyboard_us_colemak__",
"type": "ime",
"id": "xkb:us:colemak:eng",
"indicator": "CO",
"description": "",
- "language": ["en", "en-US"],
- "layouts": ["us(colemak)"],
+ "language": [
+ "en",
+ "en-US"
+ ],
+ "layouts": [
+ "us(colemak)"
+ ],
"input_view": "inputview.html?id=us-colemak&language=en-US&passwordLayout=us-colemak&name=keyboard_us_colemak"
},
{
@@ -118,17 +180,26 @@
"type": "ime",
"id": "xkb:be::nld",
"description": "",
- "language": ["nl"],
- "layouts": ["be"],
- "input_view": "inputview.html?id=be.compact.qwerty&language=nl&passwordLayout=be.compact.qwerty&name=keyboard_belgian"
+ "language": [
+ "nl"
+ ],
+ "layouts": [
+ "be"
+ ],
+ "input_view": "inputview.html?id=be&language=nl&passwordLayout=be&name=keyboard_belgian"
},
{
"name": "__MSG_keyboard_french__",
"type": "ime",
"id": "xkb:fr::fra",
"description": "",
- "language": ["fr", "fr-FR"],
- "layouts": ["fr"],
+ "language": [
+ "fr",
+ "fr-FR"
+ ],
+ "layouts": [
+ "fr"
+ ],
"input_view": "inputview.html?id=fr.compact.qwerty&language=fr&passwordLayout=fr.compact.qwerty&name=keyboard_french"
},
{
@@ -136,8 +207,12 @@
"type": "ime",
"id": "xkb:be::fra",
"description": "",
- "language": ["fr"],
- "layouts": ["be"],
+ "language": [
+ "fr"
+ ],
+ "layouts": [
+ "be"
+ ],
"input_view": "inputview.html?id=be&language=fr&passwordLayout=be&name=keyboard_belgian"
},
{
@@ -145,8 +220,13 @@
"type": "ime",
"id": "xkb:ca::fra",
"description": "",
- "language": ["fr", "fr-CA"],
- "layouts": ["ca"],
+ "language": [
+ "fr",
+ "fr-CA"
+ ],
+ "layouts": [
+ "ca"
+ ],
"input_view": "inputview.html?id=ca.compact.qwerty&language=fr&passwordLayout=ca.compact.qwerty&name=keyboard_canadian_french"
},
{
@@ -154,8 +234,13 @@
"type": "ime",
"id": "xkb:ch:fr:fra",
"description": "",
- "language": ["fr", "fr-CH"],
- "layouts": ["ch(fr)"],
+ "language": [
+ "fr",
+ "fr-CH"
+ ],
+ "layouts": [
+ "ch(fr)"
+ ],
"input_view": "inputview.html?id=ch-fr&language=fr&passwordLayout=ch-fr&name=keyboard_swiss_french"
},
{
@@ -163,8 +248,13 @@
"type": "ime",
"id": "xkb:ca:multix:fra",
"description": "",
- "language": ["fr", "fr-CA"],
- "layouts": ["ca(multix)"],
+ "language": [
+ "fr",
+ "fr-CA"
+ ],
+ "layouts": [
+ "ca(multix)"
+ ],
"input_view": "inputview.html?id=ca-multix&language=fr&passwordLayout=ca-multix&name=keyboard_canadian_multilingual"
},
{
@@ -172,8 +262,13 @@
"type": "ime",
"id": "xkb:de::ger",
"description": "",
- "language": ["de", "de-DE"],
- "layouts": ["de"],
+ "language": [
+ "de",
+ "de-DE"
+ ],
+ "layouts": [
+ "de"
+ ],
"input_view": "inputview.html?id=de.compact.qwerty&language=de&passwordLayout=de.compact.qwerty&name=keyboard_german"
},
{
@@ -182,8 +277,13 @@
"id": "xkb:de:neo:ger",
"indicator": "NEO",
"description": "",
- "language": ["de", "de-DE"],
- "layouts": ["de(neo)"],
+ "language": [
+ "de",
+ "de-DE"
+ ],
+ "layouts": [
+ "de(neo)"
+ ],
"input_view": "inputview.html?id=de-neo&language=de&passwordLayout=de-neo&name=keyboard_german_neo_2"
},
{
@@ -191,8 +291,12 @@
"type": "ime",
"id": "xkb:be::ger",
"description": "",
- "language": ["de"],
- "layouts": ["be"],
+ "language": [
+ "de"
+ ],
+ "layouts": [
+ "be"
+ ],
"input_view": "inputview.html?id=be&language=de&passwordLayout=be&name=keyboard_belgian"
},
{
@@ -200,8 +304,13 @@
"type": "ime",
"id": "xkb:ch::ger",
"description": "",
- "language": ["de", "de-CH"],
- "layouts": ["ch"],
+ "language": [
+ "de",
+ "de-CH"
+ ],
+ "layouts": [
+ "ch"
+ ],
"input_view": "inputview.html?id=ch&language=de&passwordLayout=ch&name=keyboard_swiss"
},
{
@@ -210,8 +319,12 @@
"id": "xkb:jp::jpn",
"indicator": "JA",
"description": "",
- "language": ["ja"],
- "layouts": ["jp"],
+ "language": [
+ "ja"
+ ],
+ "layouts": [
+ "jp"
+ ],
"input_view": "inputview.html?id=jp&language=ja&passwordLayout=jp&name=keyboard_japanese"
},
{
@@ -219,8 +332,12 @@
"type": "ime",
"id": "xkb:ru::rus",
"description": "",
- "language": ["ru"],
- "layouts": ["ru"],
+ "language": [
+ "ru"
+ ],
+ "layouts": [
+ "ru"
+ ],
"input_view": "inputview.html?id=ru&language=ru&passwordLayout=us&name=keyboard_russian"
},
{
@@ -228,8 +345,12 @@
"type": "ime",
"id": "xkb:ru:phonetic:rus",
"description": "",
- "language": ["ru"],
- "layouts": ["ru(phonetic)"],
+ "language": [
+ "ru"
+ ],
+ "layouts": [
+ "ru(phonetic)"
+ ],
"input_view": "inputview.html?id=ru-phonetic&language=ru&passwordLayout=us&name=keyboard_russian_phonetic"
},
{
@@ -237,8 +358,13 @@
"type": "ime",
"id": "xkb:br::por",
"description": "",
- "language": ["pt-BR", "pt"],
- "layouts": ["br"],
+ "language": [
+ "pt-BR",
+ "pt"
+ ],
+ "layouts": [
+ "br"
+ ],
"input_view": "inputview.html?id=br&language=pt-BR&passwordLayout=br&name=keyboard_brazilian"
},
{
@@ -246,8 +372,12 @@
"type": "ime",
"id": "xkb:bg::bul",
"description": "",
- "language": ["bg"],
- "layouts": ["bg"],
+ "language": [
+ "bg"
+ ],
+ "layouts": [
+ "bg"
+ ],
"input_view": "inputview.html?id=bg&language=bg&passwordLayout=us&name=keyboard_bulgarian"
},
{
@@ -255,8 +385,12 @@
"type": "ime",
"id": "xkb:bg:phonetic:bul",
"description": "",
- "language": ["bg"],
- "layouts": ["bg(phonetic)"],
+ "language": [
+ "bg"
+ ],
+ "layouts": [
+ "bg(phonetic)"
+ ],
"input_view": "inputview.html?id=bg-phonetic&language=bg&passwordLayout=us&name=keyboard_bulgarian_phonetic"
},
{
@@ -264,8 +398,13 @@
"type": "ime",
"id": "xkb:ca:eng:eng",
"description": "",
- "language": ["en", "en-CA"],
- "layouts": ["ca(eng)"],
+ "language": [
+ "en",
+ "en-CA"
+ ],
+ "layouts": [
+ "ca(eng)"
+ ],
"input_view": "inputview.html?id=ca-eng.compact.qwerty&language=en-CA&passwordLayout=ca-eng.compact.qwerty&name=keyboard_canadian_english"
},
{
@@ -273,8 +412,12 @@
"type": "ime",
"id": "xkb:cz::cze",
"description": "",
- "language": ["cs"],
- "layouts": ["cz"],
+ "language": [
+ "cs"
+ ],
+ "layouts": [
+ "cz"
+ ],
"input_view": "inputview.html?id=cz&language=cs&passwordLayout=cz&name=keyboard_czech"
},
{
@@ -283,8 +426,12 @@
"id": "xkb:cz:qwerty:cze",
"indicator": "CS",
"description": "",
- "language": ["cs"],
- "layouts": ["cz(qwerty)"],
+ "language": [
+ "cs"
+ ],
+ "layouts": [
+ "cz(qwerty)"
+ ],
"input_view": "inputview.html?id=cz-qwerty&language=cs&passwordLayout=cz-qwerty&name=keyboard_czech_qwerty"
},
{
@@ -292,8 +439,12 @@
"type": "ime",
"id": "xkb:ee::est",
"description": "",
- "language": ["et"],
- "layouts": ["ee"],
+ "language": [
+ "et"
+ ],
+ "layouts": [
+ "ee"
+ ],
"input_view": "inputview.html?id=ee&language=et&passwordLayout=ee&name=keyboard_estonian"
},
{
@@ -301,8 +452,12 @@
"type": "ime",
"id": "xkb:es::spa",
"description": "",
- "language": ["es"],
- "layouts": ["es"],
+ "language": [
+ "es"
+ ],
+ "layouts": [
+ "es"
+ ],
"input_view": "inputview.html?id=es&language=es&passwordLayout=es&name=keyboard_spanish"
},
{
@@ -311,8 +466,12 @@
"id": "xkb:es:cat:cat",
"indicator": "CAS",
"description": "",
- "language": ["ca"],
- "layouts": ["es(cat)"],
+ "language": [
+ "ca"
+ ],
+ "layouts": [
+ "es(cat)"
+ ],
"input_view": "inputview.html?id=es-cat&language=ca&passwordLayout=es-cat&name=keyboard_catalan"
},
{
@@ -320,8 +479,12 @@
"type": "ime",
"id": "xkb:dk::dan",
"description": "",
- "language": ["da"],
- "layouts": ["dk"],
+ "language": [
+ "da"
+ ],
+ "layouts": [
+ "dk"
+ ],
"input_view": "inputview.html?id=dk.compact.qwerty&language=da&passwordLayout=dk.compact.qwerty&name=keyboard_danish"
},
{
@@ -329,8 +492,12 @@
"type": "ime",
"id": "xkb:gr::gre",
"description": "",
- "language": ["el"],
- "layouts": ["gr"],
+ "language": [
+ "el"
+ ],
+ "layouts": [
+ "gr"
+ ],
"input_view": "inputview.html?id=gr&language=el&passwordLayout=us&name=keyboard_greek"
},
{
@@ -338,8 +505,12 @@
"type": "ime",
"id": "xkb:il::heb",
"description": "",
- "language": ["he"],
- "layouts": ["il"],
+ "language": [
+ "he"
+ ],
+ "layouts": [
+ "il"
+ ],
"input_view": "inputview.html?id=il&language=he&passwordLayout=us&name=keyboard_hebrew"
},
{
@@ -348,8 +519,13 @@
"id": "xkb:latam::spa",
"indicator": "LA",
"description": "",
- "language": ["es", "es-419"],
- "layouts": ["latam"],
+ "language": [
+ "es",
+ "es-419"
+ ],
+ "layouts": [
+ "latam"
+ ],
"input_view": "inputview.html?id=latam&language=es&passwordLayout=latam&name=keyboard_latin_american"
},
{
@@ -357,8 +533,12 @@
"type": "ime",
"id": "xkb:lt::lit",
"description": "",
- "language": ["lt"],
- "layouts": ["lt"],
+ "language": [
+ "lt"
+ ],
+ "layouts": [
+ "lt"
+ ],
"input_view": "inputview.html?id=lt&language=lt&passwordLayout=lt&name=keyboard_lithuanian"
},
{
@@ -366,8 +546,12 @@
"type": "ime",
"id": "xkb:lv:apostrophe:lav",
"description": "",
- "language": ["lv"],
- "layouts": ["lv(apostrophe)"],
+ "language": [
+ "lv"
+ ],
+ "layouts": [
+ "lv(apostrophe)"
+ ],
"input_view": "inputview.html?id=lv-apostrophe&language=lv&passwordLayout=lv-apostrophe&name=keyboard_latvian"
},
{
@@ -375,8 +559,12 @@
"type": "ime",
"id": "xkb:hr::scr",
"description": "",
- "language": ["hr"],
- "layouts": ["hr"],
+ "language": [
+ "hr"
+ ],
+ "layouts": [
+ "hr"
+ ],
"input_view": "inputview.html?id=hr&language=hr&passwordLayout=hr&name=keyboard_croatian"
},
{
@@ -384,8 +572,13 @@
"type": "ime",
"id": "xkb:gb:extd:eng",
"description": "",
- "language": ["en", "en-GB"],
- "layouts": ["gb(extd)"],
+ "language": [
+ "en",
+ "en-GB"
+ ],
+ "layouts": [
+ "gb(extd)"
+ ],
"input_view": "inputview.html?id=gb-extd.compact.qwerty&language=en-GB&passwordLayout=gb-extd.compact.qwerty&name=keyboard_uk"
},
{
@@ -394,8 +587,13 @@
"id": "xkb:gb:dvorak:eng",
"indicator": "DV",
"description": "",
- "language": ["en", "en-GB"],
- "layouts": ["gb(dvorak)"],
+ "language": [
+ "en",
+ "en-GB"
+ ],
+ "layouts": [
+ "gb(dvorak)"
+ ],
"input_view": "inputview.html?id=gb-dvorak&language=en-GB&passwordLayout=gb-dvorak&name=keyboard_uk_dvorak"
},
{
@@ -403,8 +601,12 @@
"type": "ime",
"id": "xkb:fi::fin",
"description": "",
- "language": ["fi"],
- "layouts": ["fi"],
+ "language": [
+ "fi"
+ ],
+ "layouts": [
+ "fi"
+ ],
"input_view": "inputview.html?id=fi.compact.qwerty&language=fi&passwordLayout=fi.compact.qwerty&name=keyboard_finnish"
},
{
@@ -412,8 +614,12 @@
"type": "ime",
"id": "xkb:hu::hun",
"description": "",
- "language": ["hu"],
- "layouts": ["hu"],
+ "language": [
+ "hu"
+ ],
+ "layouts": [
+ "hu"
+ ],
"input_view": "inputview.html?id=hu&language=hu&passwordLayout=hu&name=keyboard_hungarian"
},
{
@@ -421,8 +627,13 @@
"type": "ime",
"id": "xkb:it::ita",
"description": "",
- "language": ["it", "it-IT"],
- "layouts": ["it"],
+ "language": [
+ "it",
+ "it-IT"
+ ],
+ "layouts": [
+ "it"
+ ],
"input_view": "inputview.html?id=it&language=it&passwordLayout=it&name=keyboard_italian"
},
{
@@ -430,8 +641,12 @@
"type": "ime",
"id": "xkb:is::ice",
"description": "",
- "language": ["is"],
- "layouts": ["is"],
+ "language": [
+ "is"
+ ],
+ "layouts": [
+ "is"
+ ],
"input_view": "inputview.html?id=is.compact.qwerty&language=is&passwordLayout=is.compact.qwerty&name=keyboard_icelandic"
},
{
@@ -439,8 +654,14 @@
"type": "ime",
"id": "xkb:no::nob",
"description": "",
- "language": ["nb", "nn", "no"],
- "layouts": ["no"],
+ "language": [
+ "nb",
+ "nn",
+ "no"
+ ],
+ "layouts": [
+ "no"
+ ],
"input_view": "inputview.html?id=no.compact.qwerty&language=no&passwordLayout=no.compact.qwerty&name=keyboard_norwegian"
},
{
@@ -448,8 +669,12 @@
"type": "ime",
"id": "xkb:pl::pol",
"description": "",
- "language": ["pl"],
- "layouts": ["pl"],
+ "language": [
+ "pl"
+ ],
+ "layouts": [
+ "pl"
+ ],
"input_view": "inputview.html?id=pl&language=pl&passwordLayout=pl&name=keyboard_polish"
},
{
@@ -457,8 +682,13 @@
"type": "ime",
"id": "xkb:pt::por",
"description": "",
- "language": ["pt-PT", "pt"],
- "layouts": ["pt"],
+ "language": [
+ "pt-PT",
+ "pt"
+ ],
+ "layouts": [
+ "pt"
+ ],
"input_view": "inputview.html?id=pt&language=pt-PT&passwordLayout=pt&name=keyboard_portuguese"
},
{
@@ -466,8 +696,12 @@
"type": "ime",
"id": "xkb:ro::rum",
"description": "",
- "language": ["ro"],
- "layouts": ["ro"],
+ "language": [
+ "ro"
+ ],
+ "layouts": [
+ "ro"
+ ],
"input_view": "inputview.html?id=ro&language=ro&passwordLayout=ro&name=keyboard_romanian"
},
{
@@ -475,8 +709,12 @@
"type": "ime",
"id": "xkb:se::swe",
"description": "",
- "language": ["sv"],
- "layouts": ["se"],
+ "language": [
+ "sv"
+ ],
+ "layouts": [
+ "se"
+ ],
"input_view": "inputview.html?id=se.compact.qwerty&language=sv&passwordLayout=se.compact.qwerty&name=keyboard_swedish"
},
{
@@ -484,8 +722,12 @@
"type": "ime",
"id": "xkb:sk::slo",
"description": "",
- "language": ["sk"],
- "layouts": ["sk"],
+ "language": [
+ "sk"
+ ],
+ "layouts": [
+ "sk"
+ ],
"input_view": "inputview.html?id=sk&language=sk&passwordLayout=us&name=keyboard_slovakian"
},
{
@@ -493,8 +735,12 @@
"type": "ime",
"id": "xkb:si::slv",
"description": "",
- "language": ["sl"],
- "layouts": ["si"],
+ "language": [
+ "sl"
+ ],
+ "layouts": [
+ "si"
+ ],
"input_view": "inputview.html?id=si&language=sl&passwordLayout=si&name=keyboard_slovenian"
},
{
@@ -502,8 +748,12 @@
"type": "ime",
"id": "xkb:rs::srp",
"description": "",
- "language": ["sr"],
- "layouts": ["rs"],
+ "language": [
+ "sr"
+ ],
+ "layouts": [
+ "rs"
+ ],
"input_view": "inputview.html?id=rs&language=sr&passwordLayout=us&name=keyboard_serbian"
},
{
@@ -511,8 +761,12 @@
"type": "ime",
"id": "xkb:tr::tur",
"description": "",
- "language": ["tr"],
- "layouts": ["tr"],
+ "language": [
+ "tr"
+ ],
+ "layouts": [
+ "tr"
+ ],
"input_view": "inputview.html?id=tr&language=tr&passwordLayout=tr&name=keyboard_turkish"
},
{
@@ -520,8 +774,12 @@
"type": "ime",
"id": "xkb:ua::ukr",
"description": "",
- "language": ["uk"],
- "layouts": ["ua"],
+ "language": [
+ "uk"
+ ],
+ "layouts": [
+ "ua"
+ ],
"input_view": "inputview.html?id=ua&language=uk&passwordLayout=us&name=keyboard_ukrainian"
},
{
@@ -529,8 +787,12 @@
"type": "ime",
"id": "xkb:by::bel",
"description": "",
- "language": ["be"],
- "layouts": ["by"],
+ "language": [
+ "be"
+ ],
+ "layouts": [
+ "by"
+ ],
"input_view": "inputview.html?id=by&language=be&passwordLayout=us&name=keyboard_belarusian"
},
{
@@ -538,8 +800,12 @@
"type": "ime",
"id": "xkb:am:phonetic:arm",
"description": "",
- "language": ["hy"],
- "layouts": ["am"],
+ "language": [
+ "hy"
+ ],
+ "layouts": [
+ "am"
+ ],
"input_view": "inputview.html?id=am&language=hy&passwordLayout=us&name=keyboard_armenian_phonetic"
},
{
@@ -547,8 +813,12 @@
"type": "ime",
"id": "xkb:ge::geo",
"description": "",
- "language": ["ka"],
- "layouts": ["ge"],
+ "language": [
+ "ka"
+ ],
+ "layouts": [
+ "ge"
+ ],
"input_view": "inputview.html?id=ge&language=ka&passwordLayout=us&name=keyboard_georgian"
},
{
@@ -556,8 +826,12 @@
"type": "ime",
"id": "xkb:mn::mon",
"description": "",
- "language": ["mn"],
- "layouts": ["mn"],
+ "language": [
+ "mn"
+ ],
+ "layouts": [
+ "mn"
+ ],
"input_view": "inputview.html?id=mn&language=mn&passwordLayout=us&name=keyboard_mongolian"
},
{
@@ -565,8 +839,12 @@
"type": "ime",
"id": "xkb:ie::ga",
"description": "",
- "language": ["ga"],
- "layouts": ["ie"],
+ "language": [
+ "ga"
+ ],
+ "layouts": [
+ "ie"
+ ],
"input_view": "inputview.html?id=ie.compact.qwerty&language=ga&passwordLayout=ie.compact.qwerty&name=keyboard_irish"
}
],
diff --git a/chromium/chrome/browser/resources/chromeos/input_method/zhuyin_manifest.json b/chromium/chrome/browser/resources/chromeos/input_method/zhuyin_manifest.json
index c574bb2ab80..ebbde26523a 100644
--- a/chromium/chrome/browser/resources/chromeos/input_method/zhuyin_manifest.json
+++ b/chromium/chrome/browser/resources/chromeos/input_method/zhuyin_manifest.json
@@ -14,13 +14,18 @@
"ime_path": "/usr/share/chromeos-assets/input_methods/zhuyin",
"input_components": [
{
- "name": "__MSG_inputmethod_zhuyin__",
- "type": "ime",
- "id": "zh-hant-t-i0-und",
- "indicator": "\u9177",
- "description": "Zhuyin",
- "language": ["zh-TW", "zh"],
- "layouts": ["us"]
+ "name": "__MSG_inputmethod_zhuyin__",
+ "type": "ime",
+ "id": "zh-hant-t-i0-und",
+ "indicator": "\u6CE8",
+ "description": "Zhuyin",
+ "language": [
+ "zh-TW",
+ "zh"
+ ],
+ "layouts": [
+ "us"
+ ]
}
],
"options_page": "option.html",
diff --git a/chromium/chrome/browser/resources/chromeos/keyboard_overlay.css b/chromium/chrome/browser/resources/chromeos/keyboard_overlay.css
index ff291ac27c6..7b89b228301 100644
--- a/chromium/chrome/browser/resources/chromeos/keyboard_overlay.css
+++ b/chromium/chrome/browser/resources/chromeos/keyboard_overlay.css
@@ -6,6 +6,7 @@
body {
-webkit-user-select: none;
background: #fff;
+ font-size: 100%;
margin: 0;
overflow: hidden;
padding: 0;
@@ -15,7 +16,6 @@ body {
background: -webkit-linear-gradient(#484848, #252525) no-repeat;
background-color: #252525;
border-radius: 6px;
- font-family: 'Noto Sans UI', ui-sans;
}
.keyboard-overlay-instructions {
@@ -65,7 +65,7 @@ body {
border-radius: 4px;
color: rgb(151, 151, 151);
display: -webkit-box;
- font-size: 75%;
+ font-size: 80%;
font-weight: bold;
position: absolute;
}
diff --git a/chromium/chrome/browser/resources/chromeos/keyboard_overlay.html b/chromium/chrome/browser/resources/chromeos/keyboard_overlay.html
index c1d557087d9..e8ebd344480 100644
--- a/chromium/chrome/browser/resources/chromeos/keyboard_overlay.html
+++ b/chromium/chrome/browser/resources/chromeos/keyboard_overlay.html
@@ -1,13 +1,14 @@
-<!DOCTYPE html>
+<!doctype html>
<html>
<head>
<meta charset="utf-8" content="text/html">
<title i18n-content="keyboardOverlayTitle"></title>
+<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="keyboard_overlay.css">
<script src="chrome://resources/js/load_time_data.js"></script>
<script src="chrome://resources/js/util.js"></script>
<script src="keyboard_overlay.js"></script>
<body class="keyboard-overlay-keyboard"></body>
<script src="strings.js"></script>
-<script src="chrome://resources/js/i18n_template2.js"></script>
+<script src="chrome://resources/js/i18n_template.js"></script>
</html>
diff --git a/chromium/chrome/browser/resources/chromeos/keyboard_overlay.js b/chromium/chrome/browser/resources/chromeos/keyboard_overlay.js
index f4c7c20b826..3447f7dec98 100644
--- a/chromium/chrome/browser/resources/chromeos/keyboard_overlay.js
+++ b/chromium/chrome/browser/resources/chromeos/keyboard_overlay.js
@@ -28,8 +28,11 @@ var MODIFIER_TO_CLASS = {
var IDENTIFIER_TO_CLASS = {
'2A': 'is-shift',
+ '36': 'is-shift',
'1D': 'is-ctrl',
+ 'E0 1D': 'is-ctrl',
'38': 'is-alt',
+ 'E0 38': 'is-alt',
'E0 5B': 'is-search'
};
@@ -97,6 +100,36 @@ var KEYCODE_TO_LABEL = {
222: '\'',
};
+/**
+ * Some key labels define actions (like for example 'vol. up' or 'mute').
+ * These labels should be localized. (crbug.com/471025).
+ */
+var LABEL_TO_LOCALIZED_LABEL_ID = {
+ 'esc' : 'keyboardOverlayEscKeyLabel',
+ 'back' : 'keyboardOverlayBackKeyLabel',
+ 'forward' : 'keyboardOverlayForwardKeyLabel',
+ 'reload' : 'keyboardOverlayReloadKeyLabel',
+ 'full screen' : 'keyboardOverlayFullScreenKeyLabel',
+ 'switch window' : 'keyboardOverlaySwitchWinKeyLabel',
+ 'bright down' : 'keyboardOverlayBrightDownKeyLabel',
+ 'bright up' : 'keyboardOverlayBrightUpKeyLabel',
+ 'mute' : 'keyboardOverlayMuteKeyLabel',
+ 'vol. down' : 'keyboardOverlayVolDownKeyLabel',
+ 'vol. up' : 'keyboardOverlayVolUpKeyLabel',
+ 'power' : 'keyboardOverlayPowerKeyLabel',
+ 'backspace' : 'keyboardOverlayBackspaceKeyLabel',
+ 'tab' : 'keyboardOverlayTabKeyLabel',
+ 'search' : 'keyboardOverlaySearchKeyLabel',
+ 'enter' : 'keyboardOverlayEnterKeyLabel',
+ 'shift' : 'keyboardOverlayShiftKeyLabel',
+ 'ctrl' : 'keyboardOverlayCtrlKeyLabel',
+ 'alt' : 'keyboardOverlayAltKeyLabel',
+ 'left' : 'keyboardOverlayLeftKeyLabel',
+ 'right' : 'keyboardOverlayRightKeyLabel',
+ 'up' : 'keyboardOverlayUpKeyLabel',
+ 'down' : 'keyboardOverlayDownKeyLabel',
+};
+
var IME_ID_PREFIX = '_comp_ime_';
var EXTENSION_ID_LEN = 32;
@@ -188,10 +221,47 @@ function hex2char(hex) {
return result;
}
-var searchIsPressed = false;
+/**
+ * Returns a list of modifiers normalized to ignore the distinction between
+ * right or left keys.
+ * @param {Array} modifiers List of modifiers with distinction between right
+ * and left keys.
+ * @return {Array} List of normalized modifiers ignoring the difference between
+ * right or left keys.
+ */
+function normalizeModifiers(modifiers) {
+ var result = [];
+ if (contains(modifiers, 'L_SHIFT') || contains(modifiers, 'R_SHIFT')) {
+ result.push('SHIFT');
+ }
+ if (contains(modifiers, 'L_CTRL') || contains(modifiers, 'R_CTRL')) {
+ result.push('CTRL');
+ }
+ if (contains(modifiers, 'L_ALT') || contains(modifiers, 'R_ALT')) {
+ result.push('ALT');
+ }
+ if (contains(modifiers, 'SEARCH')) {
+ result.push('SEARCH');
+ }
+ return result.sort();
+}
+
+/**
+ * This table will contain the status of the modifiers.
+ */
+var isPressed = {
+ 'L_SHIFT': false,
+ 'R_SHIFT': false,
+ 'L_CTRL': false,
+ 'R_CTRL': false,
+ 'L_ALT': false,
+ 'R_ALT': false,
+ 'SEARCH': false,
+};
/**
- * Returns a list of modifiers from the key event.
+ * Returns a list of modifiers from the key event distinguishing right and left
+ * keys.
* @param {Event} e The key event.
* @return {Array} List of modifiers based on key event.
*/
@@ -199,7 +269,6 @@ function getModifiers(e) {
if (!e)
return [];
- var isKeyDown = (e.type == 'keydown');
var keyCodeToModifier = {
16: 'SHIFT',
17: 'CTRL',
@@ -207,19 +276,19 @@ function getModifiers(e) {
91: 'SEARCH',
};
var modifierWithKeyCode = keyCodeToModifier[e.keyCode];
- var isPressed = {
- 'SHIFT': e.shiftKey,
- 'CTRL': e.ctrlKey,
- 'ALT': e.altKey,
- 'SEARCH': searchIsPressed
- };
- if (modifierWithKeyCode)
- isPressed[modifierWithKeyCode] = isKeyDown;
+ /** @const */ var DOM_KEY_LOCATION_LEFT = 1;
+ var side = (e.location == DOM_KEY_LOCATION_LEFT) ? 'L_' : 'R_';
+ var isKeyDown = (e.type == 'keydown');
- searchIsPressed = isPressed['SEARCH'];
+ if (modifierWithKeyCode == 'SEARCH') {
+ isPressed['SEARCH'] = isKeyDown;
+ } else {
+ isPressed[side + modifierWithKeyCode] = isKeyDown;
+ }
// make the result array
- return ['SHIFT', 'CTRL', 'ALT', 'SEARCH'].filter(
+ return result = ['L_SHIFT', 'R_SHIFT', 'L_CTRL', 'R_CTRL', 'L_ALT', 'R_ALT',
+ 'SEARCH'].filter(
function(modifier) {
return isPressed[modifier];
}).sort();
@@ -269,18 +338,24 @@ function contains(list, e) {
* Returns a list of the class names corresponding to the identifier and
* modifiers.
* @param {string} identifier Key identifier.
- * @param {Array} modifiers List of key modifiers.
+ * @param {Array} modifiers List of key modifiers (with distinction between
+ * right and left keys).
+ * @param {Array} normalizedModifiers List of key modifiers (without distinction
+ * between right or left keys).
* @return {Array} List of class names corresponding to specified params.
*/
-function getKeyClasses(identifier, modifiers) {
+function getKeyClasses(identifier, modifiers, normalizedModifiers) {
var classes = ['keyboard-overlay-key'];
- for (var i = 0; i < modifiers.length; ++i) {
- classes.push(MODIFIER_TO_CLASS[modifiers[i]]);
+ for (var i = 0; i < normalizedModifiers.length; ++i) {
+ classes.push(MODIFIER_TO_CLASS[normalizedModifiers[i]]);
}
- if ((identifier == '2A' && contains(modifiers, 'SHIFT')) ||
- (identifier == '1D' && contains(modifiers, 'CTRL')) ||
- (identifier == '38' && contains(modifiers, 'ALT')) ||
+ if ((identifier == '2A' && contains(modifiers, 'L_SHIFT')) ||
+ (identifier == '36' && contains(modifiers, 'R_SHIFT')) ||
+ (identifier == '1D' && contains(modifiers, 'L_CTRL')) ||
+ (identifier == 'E0 1D' && contains(modifiers, 'R_CTRL')) ||
+ (identifier == '38' && contains(modifiers, 'L_ALT')) ||
+ (identifier == 'E0 38' && contains(modifiers, 'R_ALT')) ||
(identifier == 'E0 5B' && contains(modifiers, 'SEARCH'))) {
classes.push('pressed');
classes.push(IDENTIFIER_TO_CLASS[identifier]);
@@ -375,6 +450,12 @@ function getKeyTextValue(keyData) {
if (keyData.label == 'space') {
return '';
}
+ // some key labels define actions such as 'mute' or 'vol. up'. Those actions
+ // should be localized (crbug.com/471025).
+ var localizedLabel = LABEL_TO_LOCALIZED_LABEL_ID[keyData.label];
+ if (localizedLabel)
+ return loadTimeData.getString(localizedLabel);
+
return keyData.label;
}
@@ -391,8 +472,10 @@ function getKeyTextValue(keyData) {
/**
* Updates the whole keyboard.
* @param {Array} modifiers Key Modifier list.
+ * @param {Array} normModifiers Key Modifier list ignoring the distinction
+ * between right and left keys.
*/
-function update(modifiers) {
+function update(modifiers, normModifiers) {
var instructions = $('instructions');
if (modifiers.length == 0) {
instructions.style.visibility = 'visible';
@@ -406,18 +489,14 @@ function update(modifiers) {
for (var i = 0; i < layout.length; ++i) {
var identifier = remapIdentifier(layout[i][0]);
var keyData = keyboardGlyphData.keys[identifier];
- var classes = getKeyClasses(identifier, modifiers, keyData);
- var keyLabel = getKeyLabel(keyData, modifiers);
- var shortcutId = shortcutData[getAction(keyLabel, modifiers)];
- if (modifiers.length == 1 && modifiers[0] == 'SHIFT' &&
- identifier == '2A') {
- // Currently there is no way to identify whether the left shift or the
- // right shift is preesed from the key event, so I assume the left shift
- // key is pressed here and do not show keyboard shortcut description for
- // 'Shift - Shift' (Toggle caps lock) on the left shift key, the
- // identifier of which is '2A'.
- // TODO(mazda): Remove this workaround (http://crosbug.com/18047)
- shortcutId = null;
+ var classes = getKeyClasses(identifier, modifiers, normModifiers);
+ var keyLabel = getKeyLabel(keyData, normModifiers);
+ var shortcutId = shortcutData[getAction(keyLabel, normModifiers)];
+ if (modifiers.length == 0 &&
+ (identifier == '2A' || identifier == '36')) {
+ // Either the right or left shift keys are used to disable the caps lock
+ // if it was enabled. To fix crbug.com/453623.
+ shortcutId = 'keyboardOverlayDisableCapsLock';
}
if (shortcutId) {
classes.push('is-shortcut');
@@ -466,6 +545,8 @@ function handleKeyEvent(e) {
return;
}
+ var modifiers = getModifiers(e);
+
// To avoid flickering as the user releases the modifier keys that were held
// to trigger the overlay, avoid updating in response to keyup events until at
// least one keydown event has been received.
@@ -477,9 +558,9 @@ function handleKeyEvent(e) {
}
}
- var modifiers = getModifiers(e);
- update(modifiers);
- KeyboardOverlayAccessibilityHelper.maybeSpeakAllShortcuts(modifiers);
+ var normModifiers = normalizeModifiers(modifiers);
+ update(modifiers, normModifiers);
+ KeyboardOverlayAccessibilityHelper.maybeSpeakAllShortcuts(normModifiers);
e.preventDefault();
}
@@ -704,7 +785,7 @@ function initKeyboardOverlayId(inputMethodId) {
if (hasDiamondKey() && getLayoutName() != 'J')
initDiamondKey();
initLayout();
- update([]);
+ update([], []);
window.webkitRequestAnimationFrame(function() {
chrome.send('didPaint');
});
diff --git a/chromium/chrome/browser/resources/chromeos/login/OWNERS b/chromium/chrome/browser/resources/chromeos/login/OWNERS
index 7cbbc2d0d0e..a5a5813634b 100644
--- a/chromium/chrome/browser/resources/chromeos/login/OWNERS
+++ b/chromium/chrome/browser/resources/chromeos/login/OWNERS
@@ -1,5 +1,4 @@
nkostylev@chromium.org
dpolukhin@chromium.org
-ygorshenin@chromium.org
antrim@chromium.org
dzhioev@chromium.org
diff --git a/chromium/chrome/browser/resources/chromeos/login/accessibility_menu.css b/chromium/chrome/browser/resources/chromeos/login/accessibility_menu.css
index ac9a33f9555..cb8350a18c8 100644
--- a/chromium/chrome/browser/resources/chromeos/login/accessibility_menu.css
+++ b/chromium/chrome/browser/resources/chromeos/login/accessibility_menu.css
@@ -20,7 +20,7 @@
}
.close-button {
- background: url('chrome://theme/IDR_CLOSE_DIALOG') center no-repeat;
+ background: url(chrome://theme/IDR_CLOSE_DIALOG) center no-repeat;
height: 14px;
position: absolute;
right: 7px;
@@ -29,11 +29,11 @@
}
.close-button:hover {
- background-image: url('chrome://theme/IDR_CLOSE_DIALOG_H');
+ background-image: url(chrome://theme/IDR_CLOSE_DIALOG_H);
}
.close-button:active {
- background-image: url('chrome://theme/IDR_CLOSE_DIALOG_P');
+ background-image: url(chrome://theme/IDR_CLOSE_DIALOG_P);
}
html[dir=rtl] .close-button {
diff --git a/chromium/chrome/browser/resources/chromeos/login/apps_menu.js b/chromium/chrome/browser/resources/chromeos/login/apps_menu.js
index 37fa769ce12..d60dce3dce4 100644
--- a/chromium/chrome/browser/resources/chromeos/login/apps_menu.js
+++ b/chromium/chrome/browser/resources/chromeos/login/apps_menu.js
@@ -144,7 +144,7 @@ cr.define('login', function() {
/**
* Sets apps to be displayed in the apps menu.
- * @param {!Array.<!Object>} apps An array of app info objects.
+ * @param {!Array<!Object>} apps An array of app info objects.
*/
AppsMenuButton.setApps = function(apps) {
$('show-apps-button').data = apps;
diff --git a/chromium/chrome/browser/resources/chromeos/login/controller-pairing-screen.html b/chromium/chrome/browser/resources/chromeos/login/controller-pairing-screen.html
new file mode 100644
index 00000000000..6ecf0859577
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/login/controller-pairing-screen.html
@@ -0,0 +1,221 @@
+<link rel="import" href="chrome://oobe/custom_elements.html">
+<link rel="import" href="chrome://resources/polymer/core-animated-pages/core-animated-pages.html">
+<link rel="import" href="chrome://resources/polymer/core-iconset-svg/core-iconset-svg.html">
+<link rel="import" href="chrome://resources/polymer/core-item/core-item.html">
+<link rel="import" href="chrome://resources/polymer/core-selector/core-selector.html">
+<link rel="import" href="chrome://resources/polymer/paper-button/paper-button.html">
+<link rel="import" href="chrome://resources/polymer/paper-progress/paper-progress.html">
+<link rel="import" href="chrome://resources/polymer/paper-shadow/paper-shadow.html">
+<link rel="import" href="chrome://resources/polymer/polymer/polymer.html">
+
+<!--
+List of devices.
+Published properties:
+ * devices - array of strings, the model of the list.
+ * selected - a name of the selected device ('null' if no devices are
+ selected).
+ * connecting - a binary attribute. If set, the list does not respond to the
+ user actions and a spinner is shown near selected device.
+-->
+<polymer-element name="pairing-device-list"
+ attributes="devices selected connecting">
+ <template>
+ <link rel="stylesheet" href="pairing_device_list.css">
+
+ <core-iconset-svg id="icon" iconSize="24">
+ <svg><defs><g id="circle">
+ <circle cx="12" cy="12" r="12"></circle>
+ </g></defs></svg>
+ </core-iconset-svg>
+
+ <core-selector selected="{{selected}}">
+ <template repeat="{{device in devices}}">
+ <!-- TODO(dzhioev): replace 'core-item' with 'paper-item'.
+ http://crbug.com/423368 -->
+ <core-item name="{{device}}" relative>
+ <core-icon icon="icon:circle"
+ style="color: {{device | colorByName}}"></core-icon>
+ <div>{{device}}</div>
+ <div flex horizontal end-justified layout center>
+ <div class="throbber"></div>
+ </div>
+ </core-item>
+ </template>
+ </core-selector>
+ </template>
+</polymer-element>
+
+<!--
+Single page of the controller's out-of-box flow.
+The page consists of the top part and the bottom part.
+The top part contains a title of the page. Direct successors of the
+<controller-pairing-page> having 'title' class will be inserted there.
+The bottom part contains controls that are aligned right (all the successors
+that are <paper-button>s) and a content of the page (all the other successors).
+Special case is a help button (<paper-button> with 'help' class set) which
+is aligned left.
+There are several classes that can be used to change the page appearance:
+ * split - if this class is set, top and bottom parts will have different
+ colors.
+ * big-font - if this class is set, slightly bigger font is used on page.
+ * progress - if this class is set and 'split' is not, progress bar is shown
+ instead of top and bottom parts separator.
+
+Also height of the top part can be specified in CSS as follows:
+
+controller-pairing-page::shadow #top {
+ height: 100px;
+}
+-->
+<polymer-element name="controller-pairing-page" noscript>
+ <template>
+ <link rel="stylesheet" href="controller_pairing_page.css">
+
+ <div vertical layout fit>
+ <div id="top" hero hero-id="top" relative vertical end-justified layout>
+ <content select=".title"></content>
+ <div id="separator">
+ <indeterminate-progress fill runnerColor="white"
+ backgroundColor="#87ceac" runnerPortion="40">
+ </indeterminate-progress>
+ </div>
+ </div>
+ <div id="bottom" hero hero-id="bottom" flex vertical layout>
+ <div flex vertical layout>
+ <content select=":not(paper-button)"></content>
+ </div>
+ <div id="controls" horizontal layout center>
+ <div flex>
+ <content select="paper-button.help-button"></content>
+ </div>
+ <content select="paper-button"></content>
+ </div>
+ </div>
+ </div>
+ </template>
+</polymer-element>
+
+<polymer-element name="controller-pairing-screen" extends="oobe-screen">
+ <template>
+ <link rel="stylesheet" href="oobe_screen_controller_pairing.css">
+
+ <template id="help-button">
+ <paper-button class="help-button" on-tap="{{helpButtonClicked}}">
+ {{'helpBtn' | i18n}}
+ </paper-button>
+ </template>
+
+ <template id="progress">
+ <indeterminate-progress runnerColor="#0f9d58" backgroundColor="#87ceac"
+ runnerPortion="23"></indeterminate-progress>
+ </template>
+
+ <paper-shadow z="1" fit>
+ <core-animated-pages transitions="cross-fade-all hero-transition"
+ selected="{{C.page}}" fit>
+ <controller-pairing-page name="devices-discovery" class="big-font">
+ <div class="title">{{'welcomeTitle' | i18n}}</div>
+ <div>{{'searching' | i18n}}</div>
+ <template bind ref="help-button"></template>
+ </controller-pairing-page>
+
+ <controller-pairing-page name="device-select" class="split">
+ <div class="title">{{'selectTitle' | i18n}}</div>
+ <pairing-device-list devices="{{C.devices}}"
+ selected="{{selectedDevice}}"></pairing-device-list>
+ <template bind ref="help-button"></template>
+ <paper-button on-tap="{{userActed}}" action="chooseDevice"
+ disabled?="{{C.controlsDisabled}}">
+ {{'connectBtn' | i18n}}
+ </paper-button>
+ </controller-pairing-page>
+
+ <controller-pairing-page name="device-not-found">
+ <div class="title">{{'troubleConnectingTitle' | i18n}}</div>
+ <div>{{'connectingAdvice' | i18n}}</div>
+ <paper-button on-tap="{{userActed}}" action="repeatDiscovery">
+ {{'adviceGotItBtn' | i18n}}
+ </paper-button>
+ </controller-pairing-page>
+
+ <controller-pairing-page name="establishing-connection" class="split">
+ <div class="title">{{'selectTitle' | i18n}}</div>
+ <pairing-device-list devices="{{C.devices}}"
+ selected="{{selectedDevice}}" connecting></pairing-device-list>
+ <template bind ref="help-button"></template>
+ <paper-button disabled>{{'connecting' | i18n}}</paper-button>
+ </controller-pairing-page>
+
+ <controller-pairing-page name="establishing-connection-error">
+ <!-- TODO(dzhioev): Strings TBD. http://crbug.com/423740 -->
+ <div class="title">Unable to connect to {{selectedDevice}}</div>
+ <paper-button on-tap="{{userActed}}" action="repeatDiscovery">
+ Repeat discovery
+ </paper-button>
+ </controller-pairing-page>
+
+ <controller-pairing-page name="code-confirmation" class="split">
+ <div class="title">{{'confirmationTitle' | i18n}}</div>
+ <div>{{'confirmationQuestion' | i18n}}</div>
+ <div id="code">{{C.code}}</div>
+ <paper-button on-tap="{{userActed}}" action="rejectCode"
+ disabled?="{{C.controlsDisabled}}">
+ {{'rejectCodeBtn' | i18n}}
+ </paper-button>
+ <paper-button on-tap="{{userActed}}" action="acceptCode"
+ disabled?="{{C.controlsDisabled}}">
+ {{'acceptCodeBtn' | i18n}}
+ </paper-button>
+ </controller-pairing-page>
+
+ <controller-pairing-page name="host-update" class="split">
+ <div class="title">{{'updateTitle' | i18n}}</div>
+ <div>{{'updateText' | i18n}}</div>
+ <template bind ref="progress"></template>
+ </controller-pairing-page>
+
+ <controller-pairing-page name="host-connection-lost" class="split">
+ <div class="title">{{'connectionLostTitle' | i18n}}</div>
+ <div>{{'connectionLostText' | i18n}}</div>
+ <template bind ref="progress"></template>
+ </controller-pairing-page>
+
+ <controller-pairing-page name="enrollment-introduction" class="split">
+ <div class="title">{{'enrollTitle' | i18n}}</div>
+ <p>{{'enrollText1' | i18n}}</p>
+ <p><strong>{{'enrollText2' | i18n}}</strong></p>
+ <paper-button on-click="{{userActed}}" action="proceedToAuthentication"
+ disabled?="{{C.controlsDisabled}}">
+ {{'continueBtn' | i18n}}
+ </paper-button>
+ </controller-pairing-page>
+
+ <controller-pairing-page name="authentication" class="split">
+ <div class="title">{{'enrollTitle' | i18n}}</div>
+ <div>Not implemented.</div>
+ </controller-pairing-page>
+
+ <controller-pairing-page name="host-enrollment" class="progress">
+ <!-- 'enrollmentTitle' contains <strong> tag. -->
+ <html-echo class="title"
+ content="{{['enrollmentInProgress', C.enrollmentDomain] | i18n}}">
+ </html-echo>
+ </controller-pairing-page>
+
+ <controller-pairing-page name="host-enrollment-error" class="progress">
+ <div class="title">{{'enrollmentErrorTitle' | i18n}}</div>
+ <div>{{'enrollmentErrorHostRestarts' | i18n}}</div>
+ </controller-pairing-page>
+
+ <controller-pairing-page name="pairing-done" class="big-font">
+ <div class="title">{{'successTitle' | i18n}}</div>
+ <div>{{['successText', selectedDevice] | i18n}}</div>
+ <paper-button on-click="{{userActed}}" action="startSession"
+ disabled?="{{C.controlsDisabled}}">
+ {{'continueToHangoutsBtn' | i18n}}
+ </paper-button>
+ </controller-pairing-page>
+ </core-animated-pages>
+ </paper-shadow>
+ </template>
+</polymer-element>
diff --git a/chromium/chrome/browser/resources/chromeos/login/controller-pairing-screen.js b/chromium/chrome/browser/resources/chromeos/login/controller-pairing-screen.js
new file mode 100644
index 00000000000..c481427581d
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/login/controller-pairing-screen.js
@@ -0,0 +1,77 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+Polymer('pairing-device-list', (function() {
+ /** @const */ var ICON_COLORS = ['#F0B9CB', '#F0ACC3', '#F098B6', '#F084A9',
+ '#F06D99', '#F05287', '#F0467F', '#F03473',
+ '#F01E65', '#F00051'];
+ return {
+ /* Returns pseudo-random color depending of hash of the |name|. */
+ colorByName: function(name) {
+ var hash = 0;
+ for (var i = 0; i < name.length; ++i)
+ hash = (name.charCodeAt(i) + 31 * hash) | 0;
+ return ICON_COLORS[hash % ICON_COLORS.length];
+ }
+ };
+})());
+
+Polymer('controller-pairing-screen', (function() {
+ 'use strict';
+
+ // Keep these constants synced with corresponding constants defined in
+ // controller_pairing_screen_actor.{h,cc}.
+ /** @const */ var CONTEXT_KEY_CONTROLS_DISABLED = 'controlsDisabled';
+ /** @const */ var CONTEXT_KEY_SELECTED_DEVICE = 'selectedDevice';
+ /** @const */ var CONTEXT_KEY_ACCOUNT_ID = 'accountId';
+
+ /** @const */ var ACTION_ENROLL = 'enroll';
+
+ /** @const */ var PAGE_AUTHENTICATION = 'authentication';
+
+ return {
+ gaiaHost_: null,
+ selectedDevice: null,
+
+ observe: {
+ 'C.devices': 'deviceListChanged',
+ 'C.page': 'pageChanged'
+ },
+
+ /** @override */
+ initialize: function() {
+ this.context.set(CONTEXT_KEY_CONTROLS_DISABLED, true);
+ this.commitContextChanges();
+ },
+
+ pageChanged: function(oldPage, newPage) {
+ if (newPage == PAGE_AUTHENTICATION) {
+ this.gaiaHost_.load(cr.login.GaiaAuthHost.AuthMode.DEFAULT,
+ {},
+ this.onAuthCompleted_.bind(this));
+ }
+ },
+
+ deviceListChanged: function() {
+ this.selectedDevice = this.context.get(CONTEXT_KEY_SELECTED_DEVICE);
+ },
+
+ selectedDeviceChanged: function() {
+ this.context.set(CONTEXT_KEY_SELECTED_DEVICE,
+ this.selectedDevice ? this.selectedDevice : '');
+ this.commitContextChanges();
+ },
+
+ helpButtonClicked: function() {
+ console.error('Help is not implemented yet.');
+ },
+
+ onAuthCompleted_: function(credentials) {
+ this.context.set(CONTEXT_KEY_ACCOUNT_ID, credentials.email);
+ this.commitContextChanges();
+ this.send(login.Screen.CALLBACK_USER_ACTED, ACTION_ENROLL);
+ }
+ };
+})());
+
diff --git a/chromium/chrome/browser/resources/chromeos/login/custom_elements.html b/chromium/chrome/browser/resources/chromeos/login/custom_elements.html
deleted file mode 100644
index 7bc58751f1e..00000000000
--- a/chromium/chrome/browser/resources/chromeos/login/custom_elements.html
+++ /dev/null
@@ -1,5 +0,0 @@
-<include src="oobe-screen.html">
-<include src="html-echo.html">
-<include src="indeterminate-progress.html">
-
-<script src="chrome://oobe/custom_elements.js"></script>
diff --git a/chromium/chrome/browser/resources/chromeos/login/custom_elements.js b/chromium/chrome/browser/resources/chromeos/login/custom_elements.js
deleted file mode 100644
index fcf0be4a959..00000000000
--- a/chromium/chrome/browser/resources/chromeos/login/custom_elements.js
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (c) 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-<include src="oobe-screen.js">
-<include src="html-echo.js">
-<include src="indeterminate-progress.js">
diff --git a/chromium/chrome/browser/resources/chromeos/login/custom_elements_login.html b/chromium/chrome/browser/resources/chromeos/login/custom_elements_login.html
new file mode 100644
index 00000000000..2ce14252e49
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/login/custom_elements_login.html
@@ -0,0 +1,10 @@
+<include src="gaia_card.html">
+<include src="gaia_buttons.html">
+<include src="gaia_input.html">
+<include src="gaia_input_form.html">
+<include src="gaia_header.html">
+<include src="offline_gaia.html">
+<include src="gaia_password_changed.html">
+<include src="saml_confirm_password.html">
+
+<script src="chrome://oobe/custom_elements.js"></script>
diff --git a/chromium/chrome/browser/resources/chromeos/login/custom_elements_login.js b/chromium/chrome/browser/resources/chromeos/login/custom_elements_login.js
new file mode 100644
index 00000000000..745de57e735
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/login/custom_elements_login.js
@@ -0,0 +1,10 @@
+// Copyright (c) 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+<include src="gaia_buttons.js">
+<include src="gaia_input.js">
+<include src="gaia_input_form.js">
+<include src="offline_gaia.js">
+<include src="gaia_password_changed.js">
+<include src="saml_confirm_password.js">
diff --git a/chromium/chrome/browser/resources/chromeos/login/custom_elements_oobe.html b/chromium/chrome/browser/resources/chromeos/login/custom_elements_oobe.html
new file mode 100644
index 00000000000..54ca3677eab
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/login/custom_elements_oobe.html
@@ -0,0 +1,17 @@
+<include src="oobe-screen.html">
+<include src="html-echo.html">
+<include src="indeterminate-progress.html">
+<include src="controller-pairing-screen.html">
+<include src="host-pairing-screen.html">
+<include src="throbber_notice.html">
+<include src="gaia_card.html">
+<include src="gaia_buttons.html">
+<include src="gaia_input.html">
+<include src="gaia_input_form.html">
+<include src="gaia_header.html">
+<include src="offline_gaia.html">
+<include src="gaia_password_changed.html">
+<include src="saml_confirm_password.html">
+<include src="notification_card.html">
+
+<script src="chrome://oobe/custom_elements.js"></script>
diff --git a/chromium/chrome/browser/resources/chromeos/login/custom_elements_oobe.js b/chromium/chrome/browser/resources/chromeos/login/custom_elements_oobe.js
new file mode 100644
index 00000000000..a358fdc3a39
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/login/custom_elements_oobe.js
@@ -0,0 +1,16 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+<include src="oobe-screen.js">
+<include src="html-echo.js">
+<include src="indeterminate-progress.js">
+<include src="controller-pairing-screen.js">
+<include src="host-pairing-screen.js">
+<include src="gaia_buttons.js">
+<include src="gaia_input.js">
+<include src="gaia_input_form.js">
+<include src="offline_gaia.js">
+<include src="gaia_password_changed.js">
+<include src="saml_confirm_password.js">
+<include src="notification_card.js">
diff --git a/chromium/chrome/browser/resources/chromeos/login/demo_user_login.css b/chromium/chrome/browser/resources/chromeos/login/demo_user_login.css
deleted file mode 100644
index dae85d085d8..00000000000
--- a/chromium/chrome/browser/resources/chromeos/login/demo_user_login.css
+++ /dev/null
@@ -1,48 +0,0 @@
-/* Copyright (c) 2012 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.
- */
-
-html,
-body {
- height: 100%;
- width: 100%;
-}
-
-body {
- background: #000;
- margin: 0;
- overflow: hidden;
- padding: 0;
-}
-
-#page {
- -webkit-box-align: center;
- -webkit-box-pack: center;
- /* Delay the login screen for 2 seconds then fade it in. */
- -webkit-transition: opacity 1s linear 2s;
- background: white;
- display: -webkit-box;
- height: 100%;
- opacity: 0;
- position: absolute;
- width: 100%;
-}
-
-#logo-container {
- text-align: center;
-}
-
-#logo {
- width: 150px;
-}
-
-#demo-login-text {
- font-size: 14px;
- padding-left: 30px;
- padding-top: 10px;
-}
-
-#lower-container {
- height: 40px;
-}
diff --git a/chromium/chrome/browser/resources/chromeos/login/demo_user_login.html b/chromium/chrome/browser/resources/chromeos/login/demo_user_login.html
index a541fef2551..96a722ac4f4 100644
--- a/chromium/chrome/browser/resources/chromeos/login/demo_user_login.html
+++ b/chromium/chrome/browser/resources/chromeos/login/demo_user_login.html
@@ -1,5 +1,5 @@
-<!DOCTYPE HTML>
-<html i18n-values="dir:textdirection">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="chrome://resources/css/spinner.css">
@@ -10,7 +10,7 @@
<script src="chrome://oobe/strings.js"></script>
<script src="chrome://oobe/demo_user_login.js"></script>
</head>
-<body i18n-values=".style.fontFamily:fontfamily;">
+<body>
<div id="page">
<div id="logo-container">
<div>
@@ -22,6 +22,6 @@
</div>
</div>
</div>
- <script src="chrome://resources/js/i18n_template2.js"></script>
+ <script src="chrome://resources/js/i18n_template.js"></script>
</body>
</html>
diff --git a/chromium/chrome/browser/resources/chromeos/login/demo_user_login.js b/chromium/chrome/browser/resources/chromeos/login/demo_user_login.js
deleted file mode 100644
index a6c3ea276cd..00000000000
--- a/chromium/chrome/browser/resources/chromeos/login/demo_user_login.js
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright (c) 2012 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 Demo login UI.
- */
-
-/**
- * Handles a user clicking anywhere on the screen. This will log the demo user
- * in. Yes, this actually _is the intention.
- * @param {Event} e The click event that triggered this function.
- */
-onClick = function(e) {
- document.removeEventListener('click', onClick);
- e.stopPropagation();
- showLoginSpinner();
- chrome.send('launchDemoUser');
-};
-
-/**
- * Initializes the click handler.
- */
-initialize = function() {
- $('page').style.opacity = 1;
- document.addEventListener('click', onClick);
- // Report back sign in UI being painted.
- window.requestAnimationFrame(function() {
- chrome.send('loginVisible', ['demo']);
- });
-};
-
-/**
- * Show the login spinner.
- */
-showLoginSpinner = function() {
- // We're already logging in - don't login on click.
- document.removeEventListener('click', onClick);
-
- // Hide the "Click to start" and show the spinner.
- $('demo-login-text').hidden = true;
- $('login-spinner').hidden = false;
-};
-
-disableTextSelectAndDrag();
-document.addEventListener('DOMContentLoaded', initialize);
diff --git a/chromium/chrome/browser/resources/chromeos/login/gaia.css b/chromium/chrome/browser/resources/chromeos/login/gaia.css
index 3a5ab17b369..c8602c703fd 100644
--- a/chromium/chrome/browser/resources/chromeos/login/gaia.css
+++ b/chromium/chrome/browser/resources/chromeos/login/gaia.css
@@ -45,10 +45,10 @@
}
.blue-button:focus {
- -webkit-box-shadow: inset 0 0 0 1px #fff;
+ box-shadow: inset 0 0 0 1px #fff;
}
.blue-button:focus:hover {
- -webkit-box-shadow: inset 0 0 0 1px #fff, 0 1px 1px rgba(0, 0, 0, 0.1);
+ box-shadow: inset 0 0 0 1px #fff, 0 1px 1px rgba(0, 0, 0, 0.1);
}
diff --git a/chromium/chrome/browser/resources/chromeos/login/gaia_buttons.html b/chromium/chrome/browser/resources/chromeos/login/gaia_buttons.html
new file mode 100644
index 00000000000..00ddf846370
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/login/gaia_buttons.html
@@ -0,0 +1,42 @@
+<link rel="import" href="chrome://resources/polymer/core-icon/core-icon.html">
+<link rel="import" href="chrome://resources/polymer/core-icons/core-icons.html">
+<link rel="import" href="chrome://resources/polymer/paper-button/paper-button.html">
+<link rel="import" href="chrome://resources/polymer/polymer/polymer.html">
+
+<!--
+ Simple paper- and core-icon- buttons which also triggers 'tap' event on keys
+ 'Enter' and 'Space'.
+-->
+<polymer-element name="gaia-paper-button" extends="paper-button"
+ on-keydown="onKeyDown" tabindex="0">
+ <template>
+ <shadow></shadow>
+ </template>
+</polymer-element>
+
+<!--
+ Decorator for a <button>. When applied to a <button>, the button looks like
+ an icon from a material-design icons set. Icon's name should be specified in
+ 'icon' attribute.
+
+ Example:
+ <button is="gaia-icon-button" icon="close"></button>
+-->
+<polymer-element name="gaia-icon-button" extends="button" attributes="icon"
+ on-mousedown="{{onMouseDown}}">
+ <template>
+ <link rel="stylesheet" href="gaia_icon_button.css">
+ <core-icon icon="{{icon}}" fit></core-icon>
+ </template>
+</polymer-element>
+
+<!--
+ Paper-button which is raised when it's focused.
+ It also triggers 'tap' event on keys 'Enter' and 'Space'.
+-->
+<polymer-element name="gaia-raised-on-focus-button" extends="gaia-paper-button"
+ on-focus="{{onButtonFocus}}" on-blur="{{onButtonBlur}}">
+ <template>
+ <shadow></shadow>
+ </template>
+</polymer-element>
diff --git a/chromium/chrome/browser/resources/chromeos/login/gaia_buttons.js b/chromium/chrome/browser/resources/chromeos/login/gaia_buttons.js
new file mode 100644
index 00000000000..443cd6d938a
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/login/gaia_buttons.js
@@ -0,0 +1,32 @@
+/* Copyright 2015 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.
+ */
+
+Polymer('gaia-paper-button', {
+ onKeyDown: function(e) {
+ if (!this.disabled && (e.keyCode == 13 || e.keyCode == 32))
+ this.fire('tap');
+ }
+});
+
+Polymer('gaia-icon-button', {
+ ready: function() {
+ this.classList.add('custom-appearance');
+ },
+
+ onMouseDown: function(e) {
+ /* Prevents button focusing after mouse click. */
+ e.preventDefault();
+ }
+});
+
+Polymer('gaia-raised-on-focus-button', {
+ onButtonFocus: function() {
+ this.raised = true;
+ },
+
+ onButtonBlur: function() {
+ this.raised = false;
+ },
+});
diff --git a/chromium/chrome/browser/resources/chromeos/login/gaia_card.css b/chromium/chrome/browser/resources/chromeos/login/gaia_card.css
new file mode 100644
index 00000000000..0336e54cd7d
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/login/gaia_card.css
@@ -0,0 +1,113 @@
+/* Copyright 2015 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.
+ */
+
+:host {
+ height: 528px;
+ width: 448px;
+}
+
+.gaia-header {
+ background-color: rgb(66, 133, 244);
+ color: rgb(255, 255, 255);
+ height: 164px;
+}
+
+.gaia-footer,
+#progress-bar::shadow #progressContainer {
+ background-color: rgba(0, 0, 0, 0.02);
+}
+
+.gaia-footer {
+ position: relative;
+}
+
+.header-container {
+ padding: 34px 40px 16px;
+}
+
+.footer-container {
+ padding: 24px 40px 34px;
+}
+
+::content /deep/ .blue-button {
+ background-color: rgb(66, 133, 244);
+ color: rgb(255, 255, 255);
+ min-width: 90px;
+}
+
+::content /deep/ .dialog-action-button {
+ color: rgb(66, 133, 244);
+}
+
+::content /deep/ .dialog-action-button:focus {
+ background: rgb(217, 221, 234);
+}
+
+::content /deep/ .link-button {
+ color: rgb(66, 133, 244);
+ font-size: 14px;
+ margin: 0;
+ text-align: left;
+ text-transform: none;
+}
+
+::content /deep/ .link-button:focus {
+ background: rgb(217, 221, 234);
+}
+
+::content /deep/ .link-button::shadow .button-content {
+ padding: 0;
+}
+
+::content /deep/ div.gaia-body-text {
+ margin-bottom: 24px;
+}
+
+::content /deep/ div.gaia-body-text p {
+ color: rgba(0, 0, 0, 0.87);
+ font-size: 14px;
+ line-height: 20px;
+ margin: 0;
+}
+
+::content /deep/ p.email,
+::content /deep/ p.enterprise-info {
+ color: rgb(255, 255, 255);
+ font-size: 15px;
+ margin: 8px 0 0 0;
+}
+
+::content /deep/ h1.welcome-message {
+ color: rgb(255, 255, 255);
+ font-size: 20px;
+ font-weight: normal;
+ margin-bottom: 0;
+}
+
+.overlay {
+ background-color: rgba(0, 0, 0, 0.5);
+ display: none;
+ height: 100%;
+ position: absolute;
+ right: 0;
+ top: 0;
+ width: 100%;
+ z-index: 11;
+}
+
+#progress-bar {
+ display: none;
+ width: 100%;
+}
+
+#progress-bar::shadow #activeProgress {
+ background-color: rgb(255, 184, 9);
+}
+
+:host(.full-disabled) #full-overlay,
+:host(.disabled) #bottom-overlay,
+:host(.disabled) #progress-bar {
+ display: block;
+}
diff --git a/chromium/chrome/browser/resources/chromeos/login/gaia_card.html b/chromium/chrome/browser/resources/chromeos/login/gaia_card.html
new file mode 100644
index 00000000000..91980eff72f
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/login/gaia_card.html
@@ -0,0 +1,40 @@
+<link rel="import" href="chrome://resources/polymer/polymer/polymer.html">
+<link rel="import" href="chrome://resources/polymer/polymer/layout.html">
+<link rel="import" href="chrome://resources/polymer/paper-progress/paper-progress.html">
+
+<!--
+ Simple Gaia card which should be used for local UI elements that look like
+ GAIA. It has blue header and grey footer.
+
+ Example:
+ <gaia-card>
+ <div class="header" flex vertical layout end-justified start>
+ ...
+ </div>
+ <gaia-input-form class="footer" ...>
+ </gaia-input-form>
+ </gaia-card>
+
+ Add class |header| to all which you want to go inside blue header. Similar
+ with class |footer|.
+-->
+<polymer-element name="gaia-card" noscript vertical layout>
+ <template>
+ <link rel="stylesheet" href="gaia_card.css">
+ <div class="gaia-header" vertical layout>
+ <div class="header-container" flex vertical layout>
+ <content select=".header"></content>
+ </div>
+ </div>
+ <paper-progress id="progress-bar" indeterminate></paper-progress>
+ <div class="gaia-footer" flex vertical layout>
+ <div class="footer-container" flex vertical layout>
+ <content select=".footer"></content>
+ </div>
+ <div id="bottom-overlay" class="overlay">
+ </div>
+ </div>
+ <div id="full-overlay" class="overlay">
+ </div>
+ </template>
+</polymer-element>
diff --git a/chromium/chrome/browser/resources/chromeos/login/gaia_header.html b/chromium/chrome/browser/resources/chromeos/login/gaia_header.html
new file mode 100644
index 00000000000..c59a1de8824
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/login/gaia_header.html
@@ -0,0 +1,23 @@
+<link rel="import" href="chrome://resources/polymer/polymer/polymer.html">
+<link rel="import" href="chrome://resources/polymer/polymer/layout.html">
+
+<!--
+ Blue header for New Gaia UI, contains blue avatar logo and user email.
+
+ Example:
+ <gaia-header id="passwordHeader">
+ </gaia-header>
+
+ Attributes:
+ 'email' - displayed email.
+-->
+<polymer-element name="gaia-header" flex vertical layout noscript
+ attributes="email">
+ <template>
+ <div flex vertical justified layout start>
+ <img self-start src="chrome://theme/IDR_LOGO_AVATAR_CIRCLE_BLUE_COLOR"
+ alt>
+ <p class="email">{{email}}</p>
+ </div>
+ </template>
+</polymer-element>
diff --git a/chromium/chrome/browser/resources/chromeos/login/gaia_icon_button.css b/chromium/chrome/browser/resources/chromeos/login/gaia_icon_button.css
new file mode 100644
index 00000000000..dc61a0a88c3
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/login/gaia_icon_button.css
@@ -0,0 +1,31 @@
+/* Copyright 2015 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.
+ */
+
+:host {
+ background: transparent;
+ border: none;
+ border-radius: 50%;
+ height: 24px;
+ margin: 0;
+ outline: none;
+ padding: 0;
+ width: 24px;
+}
+
+:host(:hover) {
+ cursor: pointer;
+}
+
+:host(:focus) {
+ background: rgba(0, 0, 0, .16);
+ border-color: transparent;
+ opacity: 0.75;
+}
+
+:host(:active) {
+ background: rgba(0, 0, 0, .12);
+ opacity: 1.0;
+}
+
diff --git a/chromium/chrome/browser/resources/chromeos/login/gaia_input.css b/chromium/chrome/browser/resources/chromeos/login/gaia_input.css
new file mode 100644
index 00000000000..655201421be
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/login/gaia_input.css
@@ -0,0 +1,34 @@
+/* Copyright 2015 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.
+ */
+
+paper-input-decorator /deep/ .unfocused-underline {
+ background-color: rgb(212, 212, 212);
+}
+
+paper-input-decorator /deep/ .focused-underline {
+ background-color: rgb(66, 133, 244);
+}
+
+paper-input-decorator /deep/ ::-webkit-input-placeholder,
+paper-input-decorator /deep/ .label-text {
+ color: rgba(0, 0, 0, 0.54) !important;
+}
+
+paper-input-decorator {
+ color: rgba(0, 0, 0, 0.87);
+ font-size: 15px;
+ padding: 0;
+}
+
+:host-context(html[dir=rtl]) input {
+ flex-direction: row-reverse;
+}
+
+#domainLabel {
+ color: rgba(0, 0, 0, 0.54);
+ direction: ltr;
+ width: auto;
+}
+
diff --git a/chromium/chrome/browser/resources/chromeos/login/gaia_input.html b/chromium/chrome/browser/resources/chromeos/login/gaia_input.html
new file mode 100644
index 00000000000..21b01158797
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/login/gaia_input.html
@@ -0,0 +1,45 @@
+<link rel="import" href="chrome://resources/polymer/core-input/core-input.html">
+<link rel="import" href="chrome://resources/polymer/paper-input/paper-input-decorator.html">
+<link rel="import" href="chrome://resources/polymer/polymer/layout.html">
+<link rel="import" href="chrome://resources/polymer/polymer/polymer.html">
+
+<!--
+ Material desing input field, that supports different input types and email
+ validation and matches GAIA's visual style.
+
+ Examples:
+ <gaia-input value="Simple text input" required></gaia-input>
+ <gaia-input type="email" domain="example.com"></gaia-input>
+
+ Attributes:
+ 'label' - same as <paper-input>'s 'label'.
+ 'value' - same as <input>'s 'value'.
+ 'type' - same as <input>'s 'type'.
+ 'domain' - optional attribute for email input. The domain is displayed in
+ the end of input field, if user is not provided any.
+ 'error' - error message displayed in case if 'isInvalid' is true.
+ 'isInvalid' - whether input data is invalid. Note: it is not changed
+ automatically. Can be changed manually or with checkValidity()
+ method.
+ 'required' - whether empty field is invalid.
+
+ Methods:
+ 'focus' - focuses input field.
+ 'checkValidity' - returns current validity state of the input form. Updates
+ 'isInvalid' at the end.
+-->
+<polymer-element name="gaia-input" attributes="label value type domain required
+ error isInvalid">
+ <template>
+ <link rel="stylesheet" href="gaia_input.css">
+ <paper-input-decorator id="decorator" error="{{error}}" label="{{label}}"
+ on-tap="{{onTap}}" isInvalid="{{isInvalid}}" floatingLabel autoValidate>
+ <div id="container" horizontal layout>
+ <input id="input" is="core-input" on-keydown="{{onKeyDown}}"
+ value="{{value}}" required?="{{required}}" flex>
+ <span id="domainLabel">{{domain}}</span>
+ </div>
+ </paper-input-decorator>
+ </template>
+</polymer-element>
+
diff --git a/chromium/chrome/browser/resources/chromeos/login/gaia_input.js b/chromium/chrome/browser/resources/chromeos/login/gaia_input.js
new file mode 100644
index 00000000000..af168f7c0eb
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/login/gaia_input.js
@@ -0,0 +1,59 @@
+/* Copyright 2015 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.
+ */
+
+Polymer('gaia-input', (function() {
+ var INPUT_EMAIL_PATTERN = "^[a-zA-Z0-9.!#$%&'*+=?^_`{|}~-]+(@[^\\s@]+)?$";
+
+ return {
+ required: false,
+
+ ready: function() {
+ this.typeChanged();
+ },
+
+ onKeyDown: function(e) {
+ this.isInvalid = false;
+ },
+
+ setDomainVisibility: function() {
+ this.$.domainLabel.hidden = (this.type !== 'email') || !this.domain ||
+ (this.value.indexOf('@') !== -1);
+ },
+
+ onTap: function() {
+ this.isInvalid = false;
+ },
+
+ focus: function() {
+ this.$.input.focus();
+ },
+
+ checkValidity: function() {
+ var isValid = this.$.input.validity.valid;
+ this.isInvalid = !isValid;
+ return isValid;
+ },
+
+ typeChanged: function() {
+ if (this.type == 'email') {
+ this.$.input.pattern = INPUT_EMAIL_PATTERN;
+ this.$.input.type = 'text';
+ } else {
+ this.$.input.removeAttribute('pattern');
+ this.$.input.type = this.type;
+ }
+ this.setDomainVisibility();
+ },
+
+ valueChanged: function() {
+ this.setDomainVisibility();
+ },
+
+ domainChanged: function() {
+ this.setDomainVisibility();
+ },
+ };
+})());
+
diff --git a/chromium/chrome/browser/resources/chromeos/login/gaia_input_form.css b/chromium/chrome/browser/resources/chromeos/login/gaia_input_form.css
new file mode 100644
index 00000000000..7175f961cd0
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/login/gaia_input_form.css
@@ -0,0 +1,16 @@
+/* Copyright 2015 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.
+ */
+
+:host {
+ font-size: 16px;
+}
+
+#inputs::content gaia-input {
+ margin-bottom: 24px;
+}
+
+#inputs::content gaia-input:last-of-type {
+ margin-bottom: 28px;
+}
diff --git a/chromium/chrome/browser/resources/chromeos/login/gaia_input_form.html b/chromium/chrome/browser/resources/chromeos/login/gaia_input_form.html
new file mode 100644
index 00000000000..b887708eee7
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/login/gaia_input_form.html
@@ -0,0 +1,38 @@
+<link rel="import" href="chrome://resources/polymer/polymer/polymer.html">
+<link rel="import" href="chrome://resources/polymer/polymer/layout.html">
+
+<!--
+ A simple input form with a button. Being used for typing email or password.
+ User should put one or more <gaia-input>s inside.
+
+ Example:
+ <gaia-input-form buttonText="Submit">
+ <gaia-input label="Email" type="email"></gaia-input>
+ <gaia-input label="Password" type="password"></gaia-input>
+ <gaia-input label="OTP"></gaia-input>
+ </gaia-input-form>
+
+ Attributes:
+ 'buttonText' - text on the button.
+
+ Events:
+ 'submit' - fired on button click or "Enter" press inside input field.
+
+ Methods:
+ 'focus' - focuses input field.
+-->
+<polymer-element name="gaia-input-form" vertical start-justified layout
+ attributes="buttonText"
+ on-keydown="{{onKeyDown}}">
+ <template>
+ <link rel="stylesheet" href="gaia_input_form.css">
+ <content id="inputs" select="gaia-input"></content>
+ <div horizontal justified layout center reverse>
+ <gaia-raised-on-focus-button id="button" class="blue-button"
+ on-tap="{{onButtonClicked}}" self-end>
+ {{buttonText}}
+ </gaia-raised-on-focus-button>
+ <content> </content>
+ </div>
+ </template>
+</polymer-element>
diff --git a/chromium/chrome/browser/resources/chromeos/login/gaia_input_form.js b/chromium/chrome/browser/resources/chromeos/login/gaia_input_form.js
new file mode 100644
index 00000000000..811f08b989c
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/login/gaia_input_form.js
@@ -0,0 +1,24 @@
+/* Copyright 2015 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.
+ */
+
+Polymer('gaia-input-form', (function() {
+ return {
+ onButtonClicked: function() {
+ this.fire('submit');
+ },
+
+ onKeyDown: function(e) {
+ if (e.keyCode == 13 && !this.$.button.disabled)
+ this.onButtonClicked();
+ },
+
+ set disabled(value) {
+ var controls = this.querySelectorAll(
+ ':host /deep/ [role="button"], :host /deep/ [is="core-input"]');
+ for (var i = 0, control; control = controls[i]; ++i)
+ control.disabled = value;
+ },
+ };
+})());
diff --git a/chromium/chrome/browser/resources/chromeos/login/gaia_password_changed.css b/chromium/chrome/browser/resources/chromeos/login/gaia_password_changed.css
new file mode 100644
index 00000000000..9c2529c231b
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/login/gaia_password_changed.css
@@ -0,0 +1,31 @@
+/* Copyright 2015 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.
+ */
+
+:host {
+ height: 100%;
+ width: 100%;
+}
+
+core-icon[icon='warning'] {
+ color: rgb(255, 193, 7);
+ margin: 0 15px 0 0;
+}
+
+:host-context(html[dir=rtl]) core-icon[icon='warning'] {
+ margin: 0 0 0 15px;
+}
+
+#closeButton {
+ color: white;
+ position: absolute;
+ right: 10px;
+ top: 10px;
+ z-index: 1;
+}
+
+:host-context(html[dir=rtl]) #closeButton {
+ left: 10px;
+ right: auto;
+}
diff --git a/chromium/chrome/browser/resources/chromeos/login/gaia_password_changed.html b/chromium/chrome/browser/resources/chromeos/login/gaia_password_changed.html
new file mode 100644
index 00000000000..be137ac0eb9
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/login/gaia_password_changed.html
@@ -0,0 +1,93 @@
+<link rel="import" href="chrome://resources/polymer/polymer/polymer.html">
+<link rel="import" href="chrome://resources/polymer/polymer/layout.html">
+<link rel="import" href="chrome://resources/polymer/core-animated-pages/core-animated-pages.html">
+<link rel="import" href="chrome://resources/polymer/core-animated-pages/transitions/cross-fade.html">
+
+<!--
+ Password changed UI for the New Gaia flow.
+ Contains two cards with a fade transition between them:
+ 1. Old password input form.
+ 2. Warning about data loss
+ 3. Spinner with notice "Please wait";
+
+ Example:
+ <gaia-password-changed id="gaia-password-changed" hidden>
+ </gaia-password-changed>
+
+ Attributes:
+ 'email' - displayed email in header.
+
+ Events:
+ 'passwordEnter' - fired when user enters password. Fires with an argument
+ |password|.
+ 'proceedAnyway' - fired when user decides to skip old password and
+ lose all the data in cryptohome.
+ 'cancel' - fired when user press X-button.
+
+ Methods:
+ 'invalidate' - mark password input as invalid.
+ 'reset' - reset element, sets in on the first screen and enables
+ buttons.
+ 'focus' - if current card is the first one it focuses password input.
+
+-->
+<polymer-element name="gaia-password-changed" vertical layout
+ attributes="email">
+ <template>
+ <link rel="stylesheet" href="gaia_password_changed.css">
+ <core-animated-pages id="animatedPages" transitions="cross-fade-all" flex
+ on-core-animated-pages-transition-end="{{onTransitionEnd}}">
+ <section flex>
+ <gaia-card id="oldPasswordCard">
+ <gaia-header class="header" email="{{email}}">
+ </gaia-header>
+ <div horizontal layout center class="footer gaia-body-text">
+ <p i18n-content="passwordChangedTitle">
+ </p>
+ </div>
+ <gaia-input-form class="footer" id="oldPasswordInputForm"
+ i18n-values="buttonText:nextButtonText">
+ <gaia-input id="oldPasswordInput" type="password"
+ i18n-values="error:oldPasswordIncorrect;
+ label:oldPasswordHint">
+ </gaia-input>
+ <gaia-paper-button noink i18n-content="forgotOldPasswordButtonText"
+ class="link-button"
+ on-tap="{{onForgotPasswordClicked}}">
+ </gaia-paper-button>
+ </gaia-input-form>
+ </gaia-card>
+ </section>
+ <section flex>
+ <gaia-card>
+ <gaia-header class="header" email="{{email}}">
+ </gaia-header>
+ <div class="footer">
+ <div horizontal layout center class="gaia-body-text">
+ <core-icon icon="warning"></core-icon>
+ <p flex i18n-content="passwordChangedProceedAnywayTitle">
+ </p>
+ </div>
+ <div horizontal layout justified center>
+ <gaia-paper-button noink i18n-content="passwordChangedTryAgain"
+ class="link-button"
+ on-tap="{{onTryAgainClicked}}">
+ </gaia-paper-button>
+ <gaia-raised-on-focus-button id="proceedAnywayBtn"
+ class="blue-button" on-tap="{{onProceedClicked}}"
+ i18n-content="proceedAnywayButton">
+ </gaia-raised-on-focus-button>
+ </div>
+ </div>
+ </gaia-card>
+ </section>
+ <section flex vertical layout center-justified>
+ <throbber-notice i18n-values="text:gaiaLoadingNewGaia" self-center>
+ </throbber-notice>
+ </section>
+ </core-animated-pages>
+ <button id="closeButton" is="gaia-icon-button" icon="close"
+ i18n-values="aria-label:closeButton" on-click="{{onClose}}">
+ </button>
+ </template>
+</polymer-element>
diff --git a/chromium/chrome/browser/resources/chromeos/login/gaia_password_changed.js b/chromium/chrome/browser/resources/chromeos/login/gaia_password_changed.js
new file mode 100644
index 00000000000..9887a4b41a2
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/login/gaia_password_changed.js
@@ -0,0 +1,71 @@
+/* Copyright 2015 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.
+ */
+
+Polymer('gaia-password-changed', {
+ invalidate: function() {
+ this.$.oldPasswordInput.isInvalid = true;
+ },
+
+ reset: function() {
+ this.$.animatedPages.selected = 0;
+ this.clearPassword();
+ this.$.oldPasswordInput.isInvalid = false;
+ this.disabled = false;
+ this.$.closeButton.hidden = false;
+ this.$.oldPasswordCard.classList.remove('disabled');
+ },
+
+ ready: function() {
+ this.$.oldPasswordInputForm.addEventListener('submit', function() {
+ var inputPassword = this.$.oldPasswordInput.value;
+ if (!inputPassword)
+ this.invalidate();
+ else {
+ this.$.oldPasswordCard.classList.add('disabled');
+ this.disabled = true;
+ this.fire('passwordEnter', {password: inputPassword});
+ }
+ }.bind(this));
+ },
+
+ focus: function() {
+ if (this.$.animatedPages.selected == 0)
+ this.$.oldPasswordInput.focus();
+ },
+
+ set disabled(value) {
+ this.$.oldPasswordInputForm.disabled = value;
+ },
+
+ onForgotPasswordClicked: function() {
+ this.clearPassword();
+ this.$.animatedPages.selected += 1;
+ },
+
+ onTryAgainClicked: function() {
+ this.$.oldPasswordInput.isInvalid = false;
+ this.$.animatedPages.selected -= 1;
+ },
+
+ onTransitionEnd: function() {
+ this.focus();
+ },
+
+ clearPassword: function() {
+ this.$.oldPasswordInput.value = '';
+ },
+
+ onProceedClicked: function() {
+ this.disabled = true;
+ this.$.closeButton.hidden = true;
+ this.$.animatedPages.selected = 2;
+ this.fire('proceedAnyway');
+ },
+
+ onClose: function() {
+ this.disabled = true;
+ this.fire('cancel');
+ },
+});
diff --git a/chromium/chrome/browser/resources/chromeos/login/header_bar.css b/chromium/chrome/browser/resources/chromeos/login/header_bar.css
index 9d26fcb2d94..3e8a1e8dd5f 100644
--- a/chromium/chrome/browser/resources/chromeos/login/header_bar.css
+++ b/chromium/chrome/browser/resources/chromeos/login/header_bar.css
@@ -4,6 +4,7 @@
*/
#login-header-bar {
+ -webkit-padding-start: 15px;
border-bottom: 3px solid black;
bottom: 0;
left: 0;
@@ -17,7 +18,7 @@
Otherwise if opacity: 0.8 is set here it will affect button text as well.
Have to position background differently so that opacity is not applied
to child elements. */
- background-image: url('chrome://theme/IDR_LAUNCHER_BACKGROUND');
+ background-image: url(chrome://theme/IDR_LAUNCHER_BACKGROUND);
padding-top: 7px;
}
@@ -36,16 +37,12 @@ html[screen=oobe] .login-header-bar-animate-slow {
-webkit-transition: opacity 2s ease-out;
}
-.header-bar-item:first-child {
- -webkit-padding-start: 15px;
-}
-
#login-header-bar button,
#login-header-bar button:active,
#login-header-bar button:focus,
#login-header-bar button:hover {
- -webkit-box-shadow: none;
background: transparent none;
+ box-shadow: none;
cursor: pointer;
height: 34px;
margin: 0;
@@ -68,13 +65,36 @@ html[screen=oobe] .login-header-bar-animate-slow {
height: 34px;
}
+.add-supervised-user-menu {
+ display: none;
+}
+
+#more-settings-header-bar-item.active button.add-supervised-user-menu {
+ background-color: white;
+ border: 1px solid lightgray;
+ border-radius: 2px;
+ bottom: 15px;
+ color: black !important;
+ display: block;
+ font-size: 13px;
+ height: auto;
+ left: 15px;
+ margin: 0 0 -10px -10px;
+ min-height: 34px;
+ position: absolute;
+ text-align: center;
+ width: 180px;
+}
+
html[dir=rtl] .header-bar-item {
background-position: right center;
}
#login-header-bar #shutdown-button,
+#login-header-bar #restart-button,
#login-header-bar #add-user-button,
#login-header-bar #guest-user-button,
+#login-header-bar #more-settings-button,
#login-header-bar #cancel-multiple-sign-in-button {
-webkit-padding-start: 24px;
background-position: left center;
@@ -83,27 +103,41 @@ html[dir=rtl] .header-bar-item {
}
html[dir=rtl] #login-header-bar #shutdown-button,
+html[dir=rtl] #login-header-bar #restart-button,
html[dir=rtl] #login-header-bar #add-user-button,
+html[dir=rtl] #login-header-bar #more-settings-button,
html[dir=rtl] #login-header-bar #guest-user-button,
html[dir=rtl] #login-header-bar #cancel-multiple-sign-in-button {
background-position: right center;
}
+#login-header-bar #more-settings-header-bar-item {
+ position: relative;
+}
+
#login-header-bar #shutdown-button {
- background-image: url('chrome://theme/IDR_ICON_POWER_WHITE');
+ background-image: url(chrome://theme/IDR_ICON_POWER_WHITE);
+}
+
+#login-header-bar #restart-button {
+ background-image: url(chrome://theme/IDR_ICON_POWER_WHITE);
}
#login-header-bar #add-user-button {
- background-image: url('chrome://theme/IDR_ICON_ADD_USER_WHITE');
+ background-image: url(chrome://theme/IDR_ICON_ADD_USER_WHITE);
+}
+
+#login-header-bar #more-settings-button {
+ background-image: url(chrome://theme/IDR_ICON_MORE_VERT_WHITE);
}
#login-header-bar #guest-user-button {
- background-image: url('chrome://theme/IDR_ICON_GUEST_WHITE');
+ background-image: url(chrome://theme/IDR_ICON_GUEST_WHITE);
}
#login-header-bar #cancel-multiple-sign-in-button {
/* TODO(dzhioev): replace with appropriate image when possible. */
- background-image: url('chrome://theme/IDR_PANEL_CLOSE');
+ background-image: url(chrome://theme/IDR_PANEL_CLOSE);
}
.button-restricted {
diff --git a/chromium/chrome/browser/resources/chromeos/login/header_bar.html b/chromium/chrome/browser/resources/chromeos/login/header_bar.html
index 9f5f97287ad..1358dec7a68 100644
--- a/chromium/chrome/browser/resources/chromeos/login/header_bar.html
+++ b/chromium/chrome/browser/resources/chromeos/login/header_bar.html
@@ -3,19 +3,31 @@
<button id="shutdown-button" class="custom-appearance"
i18n-content="shutDown"></button>
</div>
+ <div id="restart-header-bar-item" class="header-bar-item">
+ <button id="restart-button" class="custom-appearance"
+ i18n-content="restart"></button>
+ </div>
<div id="apps-header-bar-item" class="header-bar-item" hidden>
<button id="show-apps-button" class="custom-appearance"
i18n-content="showApps"></button>
</div>
+ <div id="guest-user-header-bar-item" class="header-bar-item" hidden>
+ <button id="guest-user-button" class="custom-appearance"
+ i18n-content="browseAsGuest"></button>
+ </div>
<div id="add-user-header-bar-item" class="header-bar-item" hidden>
<button id="add-user-button" class="custom-appearance"
i18n-content="addUser"></button>
<button id="cancel-add-user-button" class="custom-appearance"
i18n-content="cancel" hidden></button>
</div>
- <div id="guest-user-header-bar-item" class="header-bar-item" hidden>
- <button id="guest-user-button" class="custom-appearance"
- i18n-content="browseAsGuest"></button>
+ <div id="more-settings-header-bar-item" class="header-bar-item">
+ <button id="more-settings-button" class="custom-appearance"
+ i18n-values="aria-label:moreOptions"></button>
+ <button class="add-supervised-user-menu custom-appearance"
+ id="add-supervised-user-menu" tabindex="0"
+ i18n-content="addSupervisedUser">
+ </button>
</div>
<div id="sign-out-user-item" class="header-bar-item" hidden>
<button id="sign-out-user-button" class="custom-appearance"
@@ -24,7 +36,6 @@
<div id="cancel-multiple-sign-in-item" class="header-bar-item" hidden>
<button id="cancel-multiple-sign-in-button" class="custom-appearance"
i18n-content="cancel"></button>
- </button>
</div>
<div id="cancel-consumer-management-enrollment" class="header-bar-item"
hidden>
diff --git a/chromium/chrome/browser/resources/chromeos/login/header_bar.js b/chromium/chrome/browser/resources/chromeos/login/header_bar.js
index 18735c3611f..abfba3c2847 100644
--- a/chromium/chrome/browser/resources/chromeos/login/header_bar.js
+++ b/chromium/chrome/browser/resources/chromeos/login/header_bar.js
@@ -21,6 +21,21 @@ cr.define('login', function() {
// Whether guest button should be shown when header bar is in normal mode.
showGuest_: false,
+ // Whether new Gaia flow is active.
+ isNewGaiaFlow_: false,
+
+ // Whether the reboot button should be shown the when header bar is in
+ // normal mode.
+ showReboot_: false,
+
+ // Whether the shutdown button should be shown when the header bar is in
+ // normal mode.
+ showShutdown_: true,
+
+ // Whether the create supervised user button should be shown when the header
+ // bar is in normal mode. It will be shown in "More settings" menu.
+ showCreateSupervised_: false,
+
// Current UI state of the sign-in screen.
signinUIState_: SIGNIN_UI_STATE.HIDDEN,
@@ -29,12 +44,19 @@ cr.define('login', function() {
/** @override */
decorate: function() {
+ document.addEventListener('click', this.handleClick_.bind(this));
$('shutdown-header-bar-item').addEventListener('click',
this.handleShutdownClick_);
$('shutdown-button').addEventListener('click',
this.handleShutdownClick_);
+ $('restart-header-bar-item').addEventListener('click',
+ this.handleShutdownClick_);
+ $('restart-button').addEventListener('click',
+ this.handleShutdownClick_);
$('add-user-button').addEventListener('click',
this.handleAddUserClick_);
+ $('more-settings-button').addEventListener('click',
+ this.handleMoreSettingsClick_.bind(this));
$('cancel-add-user-button').addEventListener('click',
this.handleCancelAddUserClick_);
$('guest-user-header-bar-item').addEventListener('click',
@@ -48,6 +70,8 @@ cr.define('login', function() {
$('cancel-consumer-management-enrollment-button').addEventListener(
'click',
this.handleCancelConsumerManagementEnrollmentClick_);
+ this.addSupervisedUserMenu.addEventListener('click',
+ this.handleAddSupervisedUserClick_.bind(this));
if (Oobe.getInstance().displayType == DISPLAY_TYPE.LOGIN ||
Oobe.getInstance().displayType == DISPLAY_TYPE.OOBE) {
if (Oobe.getInstance().newKioskUI)
@@ -82,6 +106,32 @@ cr.define('login', function() {
button.disabled = value;
},
+ get getMoreSettingsMenu() {
+ return $('more-settings-header-bar-item');
+ },
+
+ get addSupervisedUserMenu() {
+ return this.querySelector('.add-supervised-user-menu');
+ },
+
+ /**
+ * Whether action box button is in active state.
+ * @type {boolean}
+ */
+ get isMoreSettingsActive() {
+ return this.getMoreSettingsMenu.classList.contains('active');
+ },
+ set isMoreSettingsActive(active) {
+ if (active == this.isMoreSettingsActive)
+ return;
+ if (active) {
+ this.getMoreSettingsMenu.classList.add('active');
+ } else {
+ this.getMoreSettingsMenu.classList.remove('active');
+ }
+ },
+
+
/**
* Add user button click handler.
*
@@ -95,6 +145,21 @@ cr.define('login', function() {
e.stopPropagation();
},
+ handleMoreSettingsClick_: function(e) {
+ this.isMoreSettingsActive = !this.isMoreSettingsActive;
+ this.addSupervisedUserMenu.focus();
+ e.stopPropagation();
+ },
+
+ handleClick_: function(e) {
+ this.isMoreSettingsActive = false;
+ },
+
+ handleAddSupervisedUserClick_: function(e) {
+ chrome.send('showSupervisedUserCreationScreen');
+ e.preventDefault();
+ },
+
/**
* Cancel add user button click handler.
*
@@ -176,6 +241,36 @@ cr.define('login', function() {
this.updateUI_();
},
+ set newGaiaFlow(value) {
+ this.isNewGaiaFlow_ = value;
+ this.updateUI_();
+ },
+
+ set showCreateSupervisedButton(value) {
+ this.showCreateSupervised_ = value;
+ this.updateUI_();
+ },
+
+ /**
+ * If true the "Restart" button is shown.
+ *
+ * @type {boolean}
+ */
+ set showRebootButton(value) {
+ this.showReboot_ = value;
+ this.updateUI_();
+ },
+
+ /**
+ * If true the "Shutdown" button is shown.
+ *
+ * @type {boolean}
+ */
+ set showShutdownButton(value) {
+ this.showShutdown_ = value;
+ this.updateUI_();
+ },
+
/**
* Current header bar UI / sign in state.
*
@@ -228,23 +323,48 @@ cr.define('login', function() {
(this.signinUIState_ == SIGNIN_UI_STATE.SAML_PASSWORD_CONFIRM);
var isEnrollingConsumerManagement = (this.signinUIState_ ==
SIGNIN_UI_STATE.CONSUMER_MANAGEMENT_ENROLLMENT);
+ var isPasswordChangedUI =
+ (this.signinUIState_ == SIGNIN_UI_STATE.PASSWORD_CHANGED);
var isMultiProfilesUI =
(Oobe.getInstance().displayType == DISPLAY_TYPE.USER_ADDING);
var isLockScreen =
(Oobe.getInstance().displayType == DISPLAY_TYPE.LOCK);
+ var isNewGaiaScreenWithBackButton =
+ gaiaIsActive &&
+ this.isNewGaiaFlow_ &&
+ !($('back-button-item').hidden);
+ var supervisedUserCreationDialogIsActiveAndNotIntro =
+ supervisedUserCreationDialogIsActive &&
+ $('supervised-user-creation').currentPage_ != 'intro';
$('add-user-button').hidden =
- !accountPickerIsActive || isMultiProfilesUI || isLockScreen;
- $('cancel-add-user-button').hidden = accountPickerIsActive ||
+ (!this.isNewGaiaFlow_ && !accountPickerIsActive) ||
+ (this.isNewGaiaFlow_ && gaiaIsActive) ||
+ isMultiProfilesUI ||
+ isLockScreen ||
+ supervisedUserCreationDialogIsActiveAndNotIntro;
+ $('more-settings-header-bar-item').hidden =
+ !this.showCreateSupervised_ ||
+ isNewGaiaScreenWithBackButton ||
+ supervisedUserCreationDialogIsActive;
+ $('cancel-add-user-button').hidden =
+ ((gaiaIsActive || isPasswordChangedUI || isSamlPasswordConfirm) &&
+ this.isNewGaiaFlow_) ||
+ accountPickerIsActive ||
!this.allowCancel_ ||
wrongHWIDWarningIsActive ||
- isMultiProfilesUI;
- $('guest-user-header-bar-item').hidden = gaiaIsActive ||
- supervisedUserCreationDialogIsActive ||
+ isMultiProfilesUI ||
+ supervisedUserCreationDialogIsActive;
+ $('guest-user-header-bar-item').hidden =
+ (gaiaIsActive && !this.isNewGaiaFlow_) ||
+ supervisedUserCreationDialogIsActiveAndNotIntro ||
!this.showGuest_ ||
wrongHWIDWarningIsActive ||
isSamlPasswordConfirm ||
- isMultiProfilesUI;
+ isMultiProfilesUI ||
+ isNewGaiaScreenWithBackButton;
+ $('restart-header-bar-item').hidden = !this.showReboot_;
+ $('shutdown-header-bar-item').hidden = !this.showShutdown_;
$('sign-out-user-item').hidden = !isLockScreen;
$('add-user-header-bar-item').hidden =
@@ -282,11 +402,13 @@ cr.define('login', function() {
},
/**
- * Animates Header bar to slowly appear on the screen.
+ * Animates Header bar to appear on the screen.
*
+ * @param {boolean} fast Whether the animation should complete quickly or
+ * slowly.
* @param {function()} callback will be called once animation is finished.
*/
- animateIn: function(callback) {
+ animateIn: function(fast, callback) {
if (callback) {
var launcher = this;
launcher.addEventListener(
@@ -298,7 +420,7 @@ cr.define('login', function() {
ensureTransitionEndEvent(launcher, 2250);
}
- if (Oobe.getInstance().displayType == DISPLAY_TYPE.OOBE) {
+ if (fast) {
this.classList.remove('login-header-bar-animate-slow');
this.classList.add('login-header-bar-animate-fast');
} else {
@@ -320,8 +442,8 @@ cr.define('login', function() {
/**
* Convenience wrapper of animateIn.
*/
- HeaderBar.animateIn = function(callback) {
- $('login-header-bar').animateIn(callback);
+ HeaderBar.animateIn = function(fast, callback) {
+ $('login-header-bar').animateIn(fast, callback);
};
return {
diff --git a/chromium/chrome/browser/resources/chromeos/login/host-pairing-screen.html b/chromium/chrome/browser/resources/chromeos/login/host-pairing-screen.html
new file mode 100644
index 00000000000..32858deb4ac
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/login/host-pairing-screen.html
@@ -0,0 +1,76 @@
+<link rel="import" href="chrome://oobe/custom_elements.html">
+<link rel="import" href="chrome://resources/polymer/core-animated-pages/core-animated-pages.html">
+<link rel="import" href="chrome://resources/polymer/core-iconset-svg/core-iconset-svg.html">
+<link rel="import" href="chrome://resources/polymer/core-item/core-item.html">
+<link rel="import" href="chrome://resources/polymer/polymer/polymer.html">
+
+<core-iconset-svg id="host-pairing-icons" iconSize="48">
+ <svg>
+ <defs>
+ <g id="cast">
+ <include src="../../../../app/theme/cast_icon.svg">
+ </g>
+ </defs>
+ </svg>
+</core-iconset-svg>
+
+<polymer-element name="host-pairing-page" noscript>
+ <template>
+ <link rel="stylesheet" href="oobe_screen_host_pairing_page.css">
+
+ <div id="title">
+ <content select=".title"></content>
+ </div>
+ <div id="content">
+ <content></content>
+ </div>
+ </template>
+</polymer-element>
+
+<polymer-element name="host-pairing-screen" extends="oobe-screen">
+ <template>
+ <link rel="stylesheet" href="oobe_screen_host_pairing.css">
+
+ <core-animated-pages transitions="cross-fade-all"
+ selected="{{C.page}}">
+ <host-pairing-page name="welcome">
+ <div class="title">{{'welcomeTitle' | i18n}}</div>
+ <div>{{'welcomeText' | i18n}}</div>
+ </host-pairing-page>
+ <host-pairing-page name="code-confirmation">
+ <div class="title">{{'confirmationTitle' | i18n}}</div>
+ <div id="code">{{C.code}}</div>
+ </host-pairing-page>
+ <host-pairing-page name="update">
+ <div class="title">{{'updatingTitle' | i18n}}</div>
+ <!-- Not yet implemented on backend side. -->
+ <!--div>{{['updatingText', C.downloadedMb, C.totalMb] | i18n}}</div-->
+ </host-pairing-page>
+ <host-pairing-page name="enrollment-introduction">
+ <div class="title">{{'enrollTitle' | i18n}}</div>
+ </host-pairing-page>
+ <host-pairing-page name="enrollment">
+ <div class="title">
+ <!-- 'enrollmentTitle' contains <strong> tag. We need to wrap it in
+ 'html-echo' to prevent HTML escaping. -->
+ <html-echo
+ content="{{['enrollingTitle', C.enrollmentDomain] | i18n}}">
+ </html-echo>
+ </div>
+ </host-pairing-page>
+ <host-pairing-page name="enrollment-error">
+ <div class="title">{{'enrollmentErrorTitle' | i18n}}</div>
+ <div>{{'errorNeedsRestart' | i18n}}</div>
+ </host-pairing-page>
+ <host-pairing-page name="pairing-done">
+ <div class="title">{{'doneTitle' | i18n}}</div>
+ <div>{{'doneText' | i18n}}</div>
+ </host-pairing-page>
+ </core-animated-pages>
+ <core-item id="device-indicator"class="font-scalable"
+ icon="host-pairing-icons:cast">
+ <div id="device-label">{{C.deviceName}}</div>
+ </core-item>
+ <div id="illustration"></div>
+ </template>
+</polymer-element>
diff --git a/chromium/chrome/browser/resources/chromeos/login/host-pairing-screen.js b/chromium/chrome/browser/resources/chromeos/login/host-pairing-screen.js
new file mode 100644
index 00000000000..bfeed107a79
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/login/host-pairing-screen.js
@@ -0,0 +1,20 @@
+// 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.
+
+Polymer('host-pairing-screen', (function() {
+ 'use strict';
+
+ /** @const */ var CALLBACK_CONTEXT_READY = 'contextReady';
+
+ return {
+ onBeforeShow: function() {
+ Oobe.getInstance().headerHidden = true;
+ },
+
+ /** @override */
+ initialize: function() {
+ this.send(CALLBACK_CONTEXT_READY);
+ }
+ };
+})());
diff --git a/chromium/chrome/browser/resources/chromeos/login/html-echo.html b/chromium/chrome/browser/resources/chromeos/login/html-echo.html
index 67c2a0e4b3a..fdd145cc293 100644
--- a/chromium/chrome/browser/resources/chromeos/login/html-echo.html
+++ b/chromium/chrome/browser/resources/chromeos/login/html-echo.html
@@ -1,8 +1,8 @@
-<link rel="import" href="polymer/polymer/polymer.html">
+<link rel="import" href="chrome://resources/polymer/polymer/polymer.html">
<!--
The <html-echo> injects given |content| into its innerHTML, bypassing HTML
escaping, which is always made by Polymer when we insert the result of an
- Polymer expresion into element's body.
+ Polymer expression into element's body.
Example:
diff --git a/chromium/chrome/browser/resources/chromeos/login/indeterminate-progress.html b/chromium/chrome/browser/resources/chromeos/login/indeterminate-progress.html
index 897738418d2..b5f52b2cdb7 100644
--- a/chromium/chrome/browser/resources/chromeos/login/indeterminate-progress.html
+++ b/chromium/chrome/browser/resources/chromeos/login/indeterminate-progress.html
@@ -1,5 +1,5 @@
-<link rel="import" href="polymer/polymer/polymer.html">
-<link rel="import" href="polymer/paper-progress/paper-progress.html">
+<link rel="import" href="chrome://resources/polymer/polymer/polymer.html">
+<link rel="import" href="chrome://resources/polymer/paper-progress/paper-progress.html">
<!--
Progress bar for the cases when the length of the task is unknown.
diff --git a/chromium/chrome/browser/resources/chromeos/login/indeterminate-progress.js b/chromium/chrome/browser/resources/chromeos/login/indeterminate-progress.js
index 3c13c337022..2a59b2715e9 100644
--- a/chromium/chrome/browser/resources/chromeos/login/indeterminate-progress.js
+++ b/chromium/chrome/browser/resources/chromeos/login/indeterminate-progress.js
@@ -20,7 +20,6 @@ Polymer('indeterminate-progress', {
},
ready: function() {
- this.async(this.doProgress, null, this.timeout);
},
doProgress: function() {
diff --git a/chromium/chrome/browser/resources/chromeos/login/login.html b/chromium/chrome/browser/resources/chromeos/login/login.html
index 87aca736b0f..11cb9edf051 100644
--- a/chromium/chrome/browser/resources/chromeos/login/login.html
+++ b/chromium/chrome/browser/resources/chromeos/login/login.html
@@ -1,14 +1,20 @@
-<!DOCTYPE html>
-<html i18n-values="dir:textdirection;build:buildType;highlight:highlightStrength">
+<!doctype html>
+<html i18n-values="dir:textdirection;
+ build:buildType;
+ highlight:highlightStrength;
+ lang:language">
<head>
<meta charset="utf-8">
<meta name="google" value="notranslate">
<title i18n-content="title"></title>
<include src="login_resources.html">
+<include src="throbber_notice.html">
+<include src="notification_card.html">
<script src="chrome://oobe/login.js"></script>
+<script src="chrome://oobe/gaia_auth_host.js"></script>
</head>
<body i18n-values=".style.fontFamily:fontfamily;">
<include src="screen_container.html">
- <script src="chrome://resources/js/i18n_template2.js"></script>
+ <script src="chrome://resources/js/i18n_template_polymer.js"></script>
</body>
</html>
diff --git a/chromium/chrome/browser/resources/chromeos/login/login.js b/chromium/chrome/browser/resources/chromeos/login/login.js
index 393130e11e7..ef3e6f071e2 100644
--- a/chromium/chrome/browser/resources/chromeos/login/login.js
+++ b/chromium/chrome/browser/resources/chromeos/login/login.js
@@ -7,6 +7,7 @@
*/
<include src="login_common.js">
+<include src="notification_card.js">
cr.define('cr.ui.Oobe', function() {
return {
diff --git a/chromium/chrome/browser/resources/chromeos/login/login_common.js b/chromium/chrome/browser/resources/chromeos/login/login_common.js
index c25f59bcfbf..a3205720c98 100644
--- a/chromium/chrome/browser/resources/chromeos/login/login_common.js
+++ b/chromium/chrome/browser/resources/chromeos/login/login_common.js
@@ -6,6 +6,7 @@
* @fileoverview Common OOBE controller methods.
*/
+<include src="test_util.js">
<include src="../../../../../ui/login/screen.js">
<include src="screen_context.js">
<include src="../user_images_grid.js">
@@ -94,7 +95,7 @@ cr.define('cr.ui', function() {
// Callback to animate the header bar in.
var showHeaderBar = function() {
- login.HeaderBar.animateIn(function() {
+ login.HeaderBar.animateIn(false, function() {
chrome.send('headerBarVisible');
});
};
@@ -103,12 +104,25 @@ cr.define('cr.ui', function() {
} else {
document.body.classList.remove('oobe-display');
Oobe.getInstance().prepareForLoginDisplay_();
+ // Ensure header bar is visible when switching to Login UI from oobe.
+ if (Oobe.getInstance().displayType == DISPLAY_TYPE.OOBE)
+ login.HeaderBar.animateIn(true);
}
Oobe.getInstance().headerHidden = false;
};
/**
+ * When |showShutdown| is set to "true", the shutdown button is shown and the
+ * reboot button hidden. If set to "false", the reboot button is visible and
+ * the shutdown button hidden.
+ */
+ Oobe.showShutdown = function(showShutdown) {
+ $('login-header-bar').showShutdownButton = showShutdown;
+ $('login-header-bar').showRebootButton = !showShutdown;
+ };
+
+ /**
* Enables keyboard driven flow.
*/
Oobe.enableKeyboardFlow = function(value) {
@@ -155,8 +169,8 @@ cr.define('cr.ui', function() {
* Shows password changed screen that offers migration.
* @param {boolean} showError Whether to show the incorrect password error.
*/
- Oobe.showPasswordChangedScreen = function(showError) {
- DisplayManager.showPasswordChangedScreen(showError);
+ Oobe.showPasswordChangedScreen = function(showError, email) {
+ DisplayManager.showPasswordChangedScreen(showError, email);
};
/**
@@ -197,7 +211,9 @@ cr.define('cr.ui', function() {
* Displays animations that have to happen once login UI is fully displayed.
*/
Oobe.animateOnceFullyDisplayed = function() {
- login.HeaderBar.animateIn();
+ login.HeaderBar.animateIn(true, function() {
+ chrome.send('headerBarVisible');
+ });
};
/**
@@ -214,8 +230,8 @@ cr.define('cr.ui', function() {
* If the text is empty, the entire notification will be hidden.
* @param {string} messageText The message text.
*/
- Oobe.setEnterpriseInfo = function(messageText) {
- DisplayManager.setEnterpriseInfo(messageText);
+ Oobe.setEnterpriseInfo = function(messageText, assetId) {
+ DisplayManager.setEnterpriseInfo(messageText, assetId);
};
/**
@@ -293,6 +309,13 @@ cr.define('cr.ui', function() {
};
/**
+ * Shows the add user dialog. Used in browser tests.
+ */
+ Oobe.showAddUserForTesting = function() {
+ chrome.send('showAddUser');
+ };
+
+ /**
* Hotrod requisition for telemetry.
*/
Oobe.remoraRequisitionForTesting = function() {
@@ -300,6 +323,13 @@ cr.define('cr.ui', function() {
};
/**
+ * Begin enterprise enrollment for telemetry.
+ */
+ Oobe.switchToEnterpriseEnrollmentForTesting = function() {
+ chrome.send('toggleEnrollmentScreen');
+ };
+
+ /**
* Finish enterprise enrollment for telemetry.
*/
Oobe.enterpriseEnrollmentDone = function() {
@@ -330,6 +360,13 @@ cr.define('cr.ui', function() {
Oobe.getInstance().setClientAreaSize(width, height);
};
+ /**
+ * Checks whether the New Gaia flow is active.
+ */
+ Oobe.isNewGaiaFlow = function() {
+ return document.querySelector('.new-gaia-flow') != undefined;
+ };
+
// Export
return {
Oobe: Oobe
diff --git a/chromium/chrome/browser/resources/chromeos/login/login_resources.html b/chromium/chrome/browser/resources/chromeos/login/login_resources.html
index 09d6f69ab9b..08d04d68432 100644
--- a/chromium/chrome/browser/resources/chromeos/login/login_resources.html
+++ b/chromium/chrome/browser/resources/chromeos/login/login_resources.html
@@ -13,6 +13,7 @@
<link rel="stylesheet" href="../../../../../ui/login/oobe.css">
<link rel="stylesheet" href="oobe_popup_overlay.css">
<link rel="stylesheet" href="oobe_screen.css">
+<link rel="stylesheet" href="oobe_screen_enable_debugging.css">
<link rel="stylesheet" href="oobe_screen_eula.css">
<link rel="stylesheet" href="oobe_screen_eula_installation_settings_overlay.css">
<link rel="stylesheet" href="oobe_screen_network.css">
diff --git a/chromium/chrome/browser/resources/chromeos/login/network_dropdown.css b/chromium/chrome/browser/resources/chromeos/login/network_dropdown.css
index 09e2ba31085..b15dcda3073 100644
--- a/chromium/chrome/browser/resources/chromeos/login/network_dropdown.css
+++ b/chromium/chrome/browser/resources/chromeos/login/network_dropdown.css
@@ -11,7 +11,7 @@
-webkit-padding-end: 20px;
-webkit-padding-start: 1px;
-webkit-user-select: none;
- background-image: url('../../../../../ui/webui/resources/images/select.png'),
+ background-image: url(../../../../../ui/webui/resources/images/select.png),
linear-gradient(to bottom,
rgb(237, 237, 237),
rgb(237, 237, 237) 38%,
@@ -43,7 +43,7 @@ html[dir=rtl] .dropdown-title {
}
.dropdown-title:enabled:hover {
- background-image: url('../../../../../ui/webui/resources/images/select.png'),
+ background-image: url(../../../../../ui/webui/resources/images/select.png),
linear-gradient(to bottom,
rgb(237, 237, 237),
rgb(237, 237, 237) 38%,
@@ -55,7 +55,7 @@ html[dir=rtl] .dropdown-title {
}
.dropdown-title:enabled:active {
- background-image: url('../../../../../ui/webui/resources/images/select.png'),
+ background-image: url(../../../../../ui/webui/resources/images/select.png),
linear-gradient(to bottom,
rgb(231, 231, 231),
rgb(231, 231, 231) 38%,
diff --git a/chromium/chrome/browser/resources/chromeos/login/notification_card.css b/chromium/chrome/browser/resources/chromeos/login/notification_card.css
new file mode 100644
index 00000000000..7a728f3b0be
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/login/notification_card.css
@@ -0,0 +1,50 @@
+/* Copyright 2015 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.
+ */
+
+#container {
+ padding: 40px;
+}
+
+#icon-container {
+ margin-bottom: 14px;
+}
+
+/* ':host' and '[icon]' are needed to increase selector's specificity. */
+:host core-icon[icon] {
+ height: 28px;
+ width: 28px;
+}
+
+core-icon[icon=warning] {
+ color: rgb(251, 192, 45);
+}
+
+core-icon[icon=done] {
+ color: rgb(15, 157, 88);
+}
+
+#heading {
+ font-size: 20px;
+ margin-bottom: 14px;
+}
+
+#text-container {
+ color: grey;
+ line-height: 130%;
+ max-width: 240px;
+ text-align: center;
+}
+
+paper-button {
+ background-color: rgb(66, 133, 244);
+ color: rgb(255, 255, 255);
+ min-width: 126px;
+}
+
+a {
+ color: rgb(66, 133, 244);
+ text-decoration: none;
+}
+
diff --git a/chromium/chrome/browser/resources/chromeos/login/notification_card.html b/chromium/chrome/browser/resources/chromeos/login/notification_card.html
new file mode 100644
index 00000000000..351d1fbd3b1
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/login/notification_card.html
@@ -0,0 +1,62 @@
+<!-- Copyright 2015 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. -->
+
+<link rel="import" href="chrome://resources/polymer/polymer/polymer.html">
+<link rel="import" href="chrome://resources/polymer/paper-button/paper-button.html">
+<link rel="import" href="chrome://resources/polymer/core-icons/core-icons.html">
+
+<!--
+ A simple notification card with a button, link (optional) and icon (optional).
+ Example:
+ <notification-card buttonLabel="OK" linkLabel="What?" heading="Hello!"
+ type="success">
+ Great success!
+ </notification-card>
+
+ Atributes:
+ 'buttonLabel' - label displayed on the button. If empty or not set, the
+ button is hidden.
+ 'linkLabel' - text of the link. If empty or not set, the link is hidden.
+ 'heading' - heading. Can be omitted.
+ 'type' - icon type. Can be either 'success' or 'fail'. If not set, no icon
+ is displayed.
+
+ Events:
+ 'buttonclick' - fired on button click.
+ 'linkclick' - fired on link click.
+
+-->
+<polymer-element name="notification-card"
+ attributes="buttonLabel linkLabel heading type">
+ <template>
+ <link rel="stylesheet" href="notification_card.css">
+
+ <div id="container" vertical layout center fit>
+ <div flex vertical layout center center-justified>
+ <div id="icon-container" vertical layout center hidden?="{{!type}}">
+ <template if="{{type == 'fail'}}">
+ <core-icon icon="warning"></core-icon>
+ </template>
+ <template if="{{type == 'success'}}">
+ <core-icon icon="done"></core-icon>
+ </template>
+ </div>
+ <div id="heading" hidden?="{{!heading}}">
+ {{heading}}
+ </div>
+ <div id="text-container">
+ <content></content>
+ </div>
+ </div>
+ <div self-stretch horizontal reverse layout justified center>
+ <paper-button on-tap="{{buttonClicked}}" hidden?="{{!buttonLabel}}">
+ {{buttonLabel}}
+ </paper-button>
+ <a href="#" on-click="{{linkClicked}}" hidden?="{{!linkLabel}}">
+ {{linkLabel}}
+ </a>
+ </div>
+ </div>
+ </template>
+</polymer-element>
diff --git a/chromium/chrome/browser/resources/chromeos/login/notification_card.js b/chromium/chrome/browser/resources/chromeos/login/notification_card.js
new file mode 100644
index 00000000000..edc91a8bf2f
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/login/notification_card.js
@@ -0,0 +1,16 @@
+// Copyright 2015 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.
+
+Polymer('notification-card', (function() {
+ return {
+ buttonClicked: function() {
+ this.fire('buttonclick');
+ },
+
+ linkClicked: function(e) {
+ this.fire('linkclick');
+ e.preventDefault();
+ }
+ };
+})());
diff --git a/chromium/chrome/browser/resources/chromeos/login/offline_gaia.css b/chromium/chrome/browser/resources/chromeos/login/offline_gaia.css
new file mode 100644
index 00000000000..f9de8ddbb1d
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/login/offline_gaia.css
@@ -0,0 +1,25 @@
+/* Copyright 2015 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.
+ */
+
+:host {
+ font-size: 18px;
+ height: 100%;
+ overflow: hidden;
+ width: 100%;
+}
+
+#backButton {
+ color: white;
+ left: 10px;
+ position: absolute;
+ top: 10px;
+ z-index: 1;
+}
+
+:host-context(html[dir=rtl]) #backButton {
+ left: auto;
+ right: 10px;
+ transform: scaleX(-1);
+}
diff --git a/chromium/chrome/browser/resources/chromeos/login/offline_gaia.html b/chromium/chrome/browser/resources/chromeos/login/offline_gaia.html
new file mode 100644
index 00000000000..ee1955708c5
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/login/offline_gaia.html
@@ -0,0 +1,95 @@
+<link rel="import" href="chrome://resources/polymer/polymer/polymer.html">
+<link rel="import" href="chrome://resources/polymer/polymer/layout.html">
+<link rel="import" href="chrome://resources/polymer/core-animated-pages/core-animated-pages.html">
+<link rel="import" href="chrome://resources/polymer/core-animated-pages/transitions/slide-from-right.html">
+<link rel="import" href="chrome://resources/polymer/paper-button/paper-button.html">
+<link rel="import" href="chrome://resources/polymer/paper-dialog/paper-action-dialog.html">
+
+<!--
+ Offline UI for the New Gaia flow.
+ Contains two cards with a slide transition between them:
+ 1. Email input form.
+ 2. Password input form.
+
+ Example:
+ <offline-gaia></offline-gaia>
+
+ Attributes:
+ 'enterpriseInfo' - Information about device management.
+ 'emailDomain' - autocomplete domain for the email input.
+
+ Events:
+ 'authCompleted' - fired when user enters login and password. Fires with an
+ argument |credentials| which contains.
+ |credentials| = { 'useOffline': true,
+ 'email': <email>,
+ 'password': <typed password> }
+ If user did not type domain |email| will be added by
+ "@gmail.com" or by 'emailDomain' if it is set.
+ Methods:
+ 'focus' - focuses current screen (email input or password input);
+ 'setEmail' - accepts an argument |email|. If |email| is empty it sets
+ current screen to the email input, otherwise it sets current
+ screen to password input and shows error that previously
+ entered password is incorrect.
+-->
+<polymer-element name="offline-gaia" vertical layout
+ attributes="enterpriseInfo emailDomain">
+ <template>
+ <link rel="stylesheet" href="offline_gaia.css">
+ <core-animated-pages id="animatedPages" transitions="slide-from-right"
+ valueattr="id"
+ on-core-animated-pages-transition-end="{{onTransitionEnd}}" flex>
+ <section flex id="emailSection">
+ <gaia-card>
+ <div class="header" flex vertical layout end-justified start>
+ <h1 class="welcome-message" i18n-content="offlineLoginWelcome"></h1>
+ <p class="enterprise-info" hidden?="{{!enterpriseInfo}}">
+ {{enterpriseInfo}}
+ </p>
+ </div>
+ <div class="footer" flex vertical layout justified>
+ <gaia-input-form i18n-values="buttonText:offlineLoginNextBtn"
+ on-submit="{{onEmailSubmitted}}">
+ <gaia-input id="emailInput" type="email" required
+ domain="{{emailDomain}}"
+ i18n-values="error:offlineLoginInvalidEmail;
+ label:offlineLoginEmail">
+ </gaia-input>
+ </gaia-input-form>
+ <img self-center src="chrome://theme/IDR_LOGO_GOOGLE_COLOR_90"
+ alt="">
+ </div>
+ </gaia-card>
+ </section>
+ <section flex id="passwordSection">
+ <gaia-card>
+ <gaia-header class="header" id="passwordHeader">
+ </gaia-header>
+ <gaia-input-form class="footer"
+ i18n-values="buttonText:offlineLoginNextBtn"
+ on-submit="{{onPasswordSubmitted}}">
+ <gaia-input id="passwordInput" type="password" required
+ i18n-values="error:offlineLoginInvalidPassword;
+ label:offlineLoginPassword">
+ </gaia-input>
+ <gaia-paper-button noink class="link-button"
+ i18n-content="offlineLoginForgotPasswordBtn"
+ on-tap="{{onForgotPasswordClicked}}">
+ </gaia-paper-button>
+ <paper-action-dialog id="forgotPasswordDlg" autoCloseDisabled
+ backdrop on-keydown="{{onKeyDownOnDialog}}">
+ <p i18n-content="offlineLoginForgotPasswordDlg"></p>
+ <gaia-paper-button id="dialogCloseBtn" affirmative autofocus
+ i18n-content="offlineLoginCloseBtn">
+ </gaia-paper-button>
+ </paper-action-dialog>
+ </gaia-input-form>
+ </gaia-card>
+ </section>
+ </core-animated-pages>
+ <button id="backButton" is="gaia-icon-button" icon="arrow-back"
+ i18n-values="aria-label:backButton" on-click="{{onBack}}">
+ </button>
+ </template>
+</polymer-element>
diff --git a/chromium/chrome/browser/resources/chromeos/login/offline_gaia.js b/chromium/chrome/browser/resources/chromeos/login/offline_gaia.js
new file mode 100644
index 00000000000..6a8ae675f61
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/login/offline_gaia.js
@@ -0,0 +1,99 @@
+/* Copyright 2015 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.
+ */
+
+Polymer('offline-gaia', (function() {
+ var DEFAULT_EMAIL_DOMAIN = '@gmail.com';
+
+ return {
+ onTransitionEnd: function() {
+ this.focus();
+ },
+
+ focus: function() {
+ if (this.$.animatedPages.selected == 'emailSection')
+ this.$.emailInput.focus();
+ else
+ this.$.passwordInput.focus();
+ },
+
+ onForgotPasswordClicked: function() {
+ this.$.forgotPasswordDlg.toggle();
+ },
+
+ onForgotPasswordKeyDown: function(e) {
+ if (e.keyCode == 13 || e.keyCode == 32)
+ return this.onForgotPasswordClicked();
+ },
+
+ onKeyDownOnDialog: function(e) {
+ if (e.keyCode == 27) {
+ // Esc
+ this.$.forgotPasswordDlg.close();
+ e.preventDefault();
+ }
+ },
+
+ setEmail: function(email) {
+ // Reorder elements for proper animation for rtl languages.
+ if (document.querySelector('html[dir=rtl]')) {
+ this.$.emailSection.parentNode.insertBefore(this.$.passwordSection,
+ this.$.emailSection);
+ }
+ if (email) {
+ if (this.emailDomain)
+ email = email.replace(this.emailDomain, '');
+ this.switchToPasswordCard(email);
+ this.$.passwordInput.isInvalid = true;
+ } else {
+ this.$.emailInput.value = '';
+ this.switchToEmailCard();
+ }
+ },
+
+ onBack: function() {
+ this.switchToEmailCard();
+ },
+
+ switchToEmailCard() {
+ this.$.passwordInput.value = '';
+ this.$.passwordInput.isInvalid = false;
+ this.$.emailInput.isInvalid = false;
+ this.$.backButton.hidden = true;
+ this.$.animatedPages.selected = 'emailSection';
+ },
+
+ switchToPasswordCard(email) {
+ this.$.emailInput.value = email;
+ if (email.indexOf('@') === -1) {
+ if (this.emailDomain)
+ email = email + this.emailDomain;
+ else
+ email = email + DEFAULT_EMAIL_DOMAIN;
+ }
+ this.$.passwordHeader.email = email;
+ this.$.backButton.hidden = false;
+ this.$.animatedPages.selected = 'passwordSection';
+ },
+
+ onEmailSubmitted: function() {
+ if (this.$.emailInput.checkValidity())
+ this.switchToPasswordCard(this.$.emailInput.value);
+ else
+ this.$.emailInput.focus();
+ },
+
+ onPasswordSubmitted: function() {
+ if (!this.$.passwordInput.checkValidity())
+ return;
+ var msg = {
+ 'useOffline': true,
+ 'email': this.$.passwordHeader.email,
+ 'password': this.$.passwordInput.value
+ };
+ this.$.passwordInput.value = '';
+ this.fire('authCompleted', msg);
+ }
+ };
+})());
diff --git a/chromium/chrome/browser/resources/chromeos/login/oobe-screen.html b/chromium/chrome/browser/resources/chromeos/login/oobe-screen.html
index bba88bc28c6..145ef7bb9ca 100644
--- a/chromium/chrome/browser/resources/chromeos/login/oobe-screen.html
+++ b/chromium/chrome/browser/resources/chromeos/login/oobe-screen.html
@@ -1,3 +1,3 @@
-<link rel="import" href="polymer/polymer/polymer.html">
+<link rel="import" href="chrome://resources/polymer/polymer/polymer.html">
<polymer-element name="oobe-screen" attributes="name"></polymer-element>
diff --git a/chromium/chrome/browser/resources/chromeos/login/oobe-screen.js b/chromium/chrome/browser/resources/chromeos/login/oobe-screen.js
index cc5ff253355..b8bb0e9719d 100644
--- a/chromium/chrome/browser/resources/chromeos/login/oobe-screen.js
+++ b/chromium/chrome/browser/resources/chromeos/login/oobe-screen.js
@@ -29,8 +29,8 @@ Polymer('oobe-screen', (function() {
* reason: such name doesn't take much space in HTML data bindings, which
* are used very often.
* C binded to the native part of the context, that means that all the
- * changes in the native part appear in C automticaly. Reverse is not true,
- * you should use:
+ * changes in the native part appear in C automatically. Reverse is not
+ * true, you should use:
* this.context.set(...);
* this.context.commitContextChanges();
* to send updates to the native part.
@@ -39,7 +39,7 @@ Polymer('oobe-screen', (function() {
C: null,
/**
- * Called when the screen is beeing registered.
+ * Called when the screen is being registered.
*/
initialize: doNothing,
@@ -67,7 +67,6 @@ Polymer('oobe-screen', (function() {
*/
decorate: function(screen) {
this.screen_ = screen;
- screen.initialize();
this.context = screen.screenContext_;
this.C = this.context.storage_;
this.contextObservers_ = {};
diff --git a/chromium/chrome/browser/resources/chromeos/login/oobe.html b/chromium/chrome/browser/resources/chromeos/login/oobe.html
index bf174410e48..cdfed605874 100644
--- a/chromium/chrome/browser/resources/chromeos/login/oobe.html
+++ b/chromium/chrome/browser/resources/chromeos/login/oobe.html
@@ -1,14 +1,18 @@
-<!DOCTYPE html>
-<html i18n-values="dir:textdirection;build:buildType;highlight:highlightStrength">
+<!doctype html>
+<html i18n-values="dir:textdirection;
+ build:buildType;
+ highlight:highlightStrength;
+ lang:language">
<head>
<meta charset="utf-8">
<meta name="google" value="notranslate">
<title i18n-content="title"></title>
-<script src="chrome://oobe/polymer/platform/platform.js"></script>
<include src="login_resources.html">
+<link rel="import" href="chrome://oobe/custom_elements.html">
<link rel="stylesheet" href="roboto_font.css">
<link rel="stylesheet" href="accessibility_menu.css">
<script src="chrome://oobe/oobe.js"></script>
+<script src="chrome://oobe/gaia_auth_host.js"></script>
</head>
<body class="oobe-display" i18n-values=".style.fontFamily:fontfamily;">
<include src="screen_container.html">
@@ -16,6 +20,6 @@
<div id="popup-overlay" class="popup-overlay" hidden>
<include src="oobe_screen_eula_installation_settings_overlay.html">
</div>
- <script src="chrome://resources/js/i18n_template2.js"></script>
+ <script src="chrome://resources/js/i18n_template_polymer.js"></script>
</body>
</html>
diff --git a/chromium/chrome/browser/resources/chromeos/login/oobe.js b/chromium/chrome/browser/resources/chromeos/login/oobe.js
index ea7a0b9f797..33729ee4dbc 100644
--- a/chromium/chrome/browser/resources/chromeos/login/oobe.js
+++ b/chromium/chrome/browser/resources/chromeos/login/oobe.js
@@ -14,13 +14,14 @@
/* @const */ var WAIT_FOR_POLYMER = true;
<include src="login_common.js">
+<include src="oobe_screen_auto_enrollment_check.js">
+<include src="oobe_screen_controller_pairing.js">
+<include src="oobe_screen_enable_debugging.js">
<include src="oobe_screen_eula.js">
-<include src="oobe_screen_network.js">
<include src="oobe_screen_hid_detection.js">
-<include src="oobe_screen_update.js">
-<include src="oobe_screen_controller_pairing.js">
<include src="oobe_screen_host_pairing.js">
-<include src="oobe_screen_auto_enrollment_check.js">
+<include src="oobe_screen_network.js">
+<include src="oobe_screen_update.js">
cr.define('cr.ui.Oobe', function() {
return {
@@ -49,11 +50,11 @@ cr.define('cr.ui.Oobe', function() {
}
}
if (callback) {
- var sendCallback = function() {
- chrome.send(callback, [select.options[select.selectedIndex].value]);
+ var runCallback = function() {
+ callback(select.options[select.selectedIndex].value);
};
- select.addEventListener('blur', sendCallback);
- select.addEventListener('click', sendCallback);
+ select.addEventListener('blur', runCallback);
+ select.addEventListener('click', runCallback);
select.addEventListener('keyup', function(event) {
var keycodeInterested = [
9, // Tab
@@ -61,7 +62,7 @@ cr.define('cr.ui.Oobe', function() {
27, // Escape
];
if (keycodeInterested.indexOf(event.keyCode) >= 0)
- sendCallback();
+ runCallback();
});
}
},
@@ -78,6 +79,7 @@ cr.define('cr.ui.Oobe', function() {
login.EulaScreen.register();
login.UpdateScreen.register();
login.AutoEnrollmentCheckScreen.register();
+ login.EnableDebuggingScreen.register();
login.ResetScreen.register();
login.AutolaunchScreen.register();
login.KioskEnableScreen.register();
@@ -278,9 +280,9 @@ cr.define('cr.ui.Oobe', function() {
i18nTemplate.process(document, loadTimeData);
// Update language and input method menu lists.
- Oobe.setupSelect($('language-select'), data.languageList, '');
- Oobe.setupSelect($('keyboard-select'), data.inputMethodsList, '');
- Oobe.setupSelect($('timezone-select'), data.timezoneList, '');
+ Oobe.setupSelect($('language-select'), data.languageList);
+ Oobe.setupSelect($('keyboard-select'), data.inputMethodsList);
+ Oobe.setupSelect($('timezone-select'), data.timezoneList);
// Update localized content of the screens.
Oobe.updateLocalizedContent();
diff --git a/chromium/chrome/browser/resources/chromeos/login/oobe_screen.css b/chromium/chrome/browser/resources/chromeos/login/oobe_screen.css
index 2ac04e6be7b..5d4abc57646 100644
--- a/chromium/chrome/browser/resources/chromeos/login/oobe_screen.css
+++ b/chromium/chrome/browser/resources/chromeos/login/oobe_screen.css
@@ -92,6 +92,10 @@ html[dir=rtl] .step-controls {
top: 50%;
}
+.new-gaia-flow .step-loading {
+ margin-top: -42px;
+}
+
/* Fixes perfomance problem caused by http://crbug.com/229405 . */
.step.hidden .throbber,
.step.hidden .spinner,
diff --git a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_controller_pairing.css b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_controller_pairing.css
index e92e1b4dd22..926e3507e27 100644
--- a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_controller_pairing.css
+++ b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_controller_pairing.css
@@ -4,6 +4,7 @@
*/
/* TODO(dzhioev): support RTL. http://crbug.com/423354 */
+/* TODO(xdai): Remove the hard-coded font-family for 'Roboto'. */
:host {
-webkit-user-select: none;
@@ -15,11 +16,6 @@
width: 720px;
}
-core-animated-pages {
- height: 100%;
- width: 100%;
-}
-
.gaia-frame {
height: 300px;
width: 339px;
diff --git a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_controller_pairing.html b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_controller_pairing.html
index fc8132c11d7..4f02eb82d68 100644
--- a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_controller_pairing.html
+++ b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_controller_pairing.html
@@ -1,223 +1,4 @@
-<link rel="import" href="chrome://oobe/custom_elements.html">
-<link rel="import" href="polymer/core-animated-pages/core-animated-pages.html">
-<link rel="import" href="polymer/core-iconset-svg/core-iconset-svg.html">
-<link rel="import" href="polymer/core-item/core-item.html">
-<link rel="import" href="polymer/core-selector/core-selector.html">
-<link rel="import" href="polymer/paper-button/paper-button.html">
-<link rel="import" href="polymer/paper-progress/paper-progress.html">
-<link rel="import" href="polymer/paper-shadow/paper-shadow.html">
-<link rel="import" href="polymer/polymer/polymer.html">
-
-<!--
-List of devices.
-Published properties:
- * devices - array of strings, the model of the list.
- * selected - a name of the selected device ('null' if no devices are
- selected).
- * connecting - a binary attribute. If set, the list does not respond to the
- user actions and a spinner is shown near selected device.
--->
-<polymer-element name="pairing-device-list"
- attributes="devices selected connecting">
- <template>
- <link rel="stylesheet" href="pairing_device_list.css">
-
- <core-iconset-svg id="icon" iconSize="24">
- <svg><defs><g id="circle">
- <circle cx="12" cy="12" r="12"></circle>
- </g></defs></svg>
- </core-iconset-svg>
-
- <core-selector selected="{{selected}}">
- <template repeat="{{device in devices}}">
- <!-- TODO(dzhioev): replace 'core-item' with 'paper-item'.
- http://crbug.com/423368 -->
- <core-item name="{{device}}" relative>
- <core-icon icon="icon:circle"
- style="color: {{device | colorByName}}"></core-icon>
- <div>{{device}}</div>
- <div flex horizontal end-justified layout center>
- <div class="throbber"></div>
- </div>
- </core-item>
- </template>
- </core-selector>
- </template>
-</polymer-element>
-
-<!--
-Single page of the controller's out-of-box flow.
-The page consists of the top part and the bottom part.
-The top part contains a title of the page. Direct successors of the
-<controller-pairing-page> having 'title' class will be inserted there.
-The bottom part contains controls that are aligned right (all the successors
-that are <paper-button>s) and a content of the page (all the other successors).
-Special case is a help button (<paper-button> with 'help' class set) which
-is aligned left.
-There are several classes that can be used to change the page appearance:
- * split - if this class is set, top and bottom parts will have different
- colors.
- * big-font - if this class is set, slightly bigger font is used on page.
- * progress - if this class is set and 'split' is not, progress bar is shown
- instead of top and bottom parts separator.
-
-Also height of the top part can be specified in CSS as follows:
-
-controller-pairing-page::shadow #top {
- height: 100px;
-}
--->
-<polymer-element name="controller-pairing-page" noscript>
- <template>
- <link rel="stylesheet" href="controller_pairing_page.css">
-
- <div vertical layout fit>
- <div id="top" hero hero-id="top" relative vertical end-justified layout>
- <content select=".title"></content>
- <div id="separator">
- <indeterminate-progress fill runnerColor="white"
- backgroundColor="#87ceac" runnerPortion="40">
- </indeterminate-progress>
- </div>
- </div>
- <div id="bottom" hero hero-id="bottom" flex vertical layout>
- <div flex vertical layout>
- <content select=":not(paper-button)"></content>
- </div>
- <div id="controls" horizontal layout center>
- <div flex>
- <content select="paper-button.help-button"></content>
- </div>
- <content select="paper-button"></content>
- </div>
- </div>
- </div>
- </template>
-</polymer-element>
-
-<polymer-element name="controller-pairing-screen" extends="oobe-screen">
- <template>
- <link rel="stylesheet" href="oobe_screen_controller_pairing.css">
-
- <template id="help-button">
- <paper-button class="help-button" on-tap="{{helpButtonClicked}}"
- label="{{'helpBtn' | i18n}}"></paper-button>
- </template>
-
- <template id="progress">
- <indeterminate-progress runnerColor="#0f9d58" backgroundColor="#87ceac"
- runnerPortion="23"></indeterminate-progress>
- </template>
-
- <paper-shadow z="1"></paper-shadow>
-
- <core-animated-pages transitions="cross-fade-all hero-transition"
- selected="{{C.page}}">
- <controller-pairing-page name="devices-discovery" class="big-font">
- <div class="title">{{'welcomeTitle' | i18n}}</div>
- <div>{{'searching' | i18n}}</div>
- <template bind ref="help-button"></template>
- </controller-pairing-page>
-
- <controller-pairing-page name="device-select" class="split">
- <div class="title">{{'selectTitle' | i18n}}</div>
- <pairing-device-list devices="{{C.devices}}"
- selected="{{selectedDevice}}"></pairing-device-list>
- <template bind ref="help-button"></template>
- <paper-button label="{{'connectBtn' | i18n}}" on-tap="{{userActed}}"
- action="chooseDevice" disabled?="{{C.controlsDisabled}}">
- </paper-button>
- </controller-pairing-page>
-
- <controller-pairing-page name="device-not-found">
- <div class="title">{{'troubleConnectingTitle' | i18n}}</div>
- <div>{{'connectingAdvice' | i18n}}</div>
- <paper-button label="{{'adviceGotItBtn' | i18n}}" on-tap="{{userActed}}"
- action="repeatDiscovery">
- </paper-button>
- </controller-pairing-page>
-
- <controller-pairing-page name="establishing-connection" class="split">
- <div class="title">{{'selectTitle' | i18n}}</div>
- <pairing-device-list devices="{{C.devices}}"
- selected="{{selectedDevice}}" connecting></pairing-device-list>
- <template bind ref="help-button"></template>
- <paper-button label="{{'connecting' | i18n}}" disabled></paper-button>
- </controller-pairing-page>
-
- <controller-pairing-page name="establishing-connection-error">
- <!-- TODO(dzhioev): Strings TBD. http://crbug.com/423740 -->
- <div class="title">Unable to connect to {{selectedDevice}}</div>
- <paper-button on-tap="{{userActed}}" action="repeatDiscovery"
- label="Repeat discovery"></paper-button>
- </button>
- </controller-pairing-page>
-
- <controller-pairing-page name="code-confirmation" class="split">
- <div class="title">{{'confirmationTitle' | i18n}}</div>
- <div>{{'confirmationQuestion' | i18n}}</div>
- <div id="code">{{C.code}}</div>
- <paper-button label="{{'rejectCodeBtn' | i18n}}" on-tap="{{userActed}}"
- action="rejectCode" disabled?="{{C.controlsDisabled}}">
- </paper-button>
- <paper-button label="{{'acceptCodeBtn' | i18n}}" on-tap="{{userActed}}"
- action="acceptCode" disabled?="{{C.controlsDisabled}}">
- </paper-button>
- </controller-pairing-page>
-
- <controller-pairing-page name="host-update" class="split">
- <div class="title">{{'updateTitle' | i18n}}</div>
- <div>{{'updateText' | i18n}}</div>
- <template bind ref="progress"></template>
- </controller-pairing-page>
-
- <controller-pairing-page name="host-connection-lost" class="split">
- <div class="title">{{'connectionLostTitle' | i18n}}</div>
- <div>{{'connectionLostText' | i18n}}</div>
- <template bind ref="progress"></template>
- </controller-pairing-page>
-
- <controller-pairing-page name="enrollment-introduction" class="split">
- <div class="title">{{'enrollTitle' | i18n}}</div>
- <p>{{'enrollText1' | i18n}}</p>
- <p><strong>{{'enrollText2' | i18n}}</strong></p>
- <paper-button label="{{'continueBtn' | i18n}}" on-click="{{userActed}}"
- action="proceedToAuthentication" disabled?="{{C.controlsDisabled}}">
- </paper-button>
- </controller-pairing-page>
-
- <controller-pairing-page name="authentication" class="split">
- <div class="title">{{'enrollTitle' | i18n}}</div>
- <iframe id="gaiaFrame" frameBorder="0" scrolling="no" class="gaia-frame"
- flex self-center></iframe>
- </controller-pairing-page>
-
- <controller-pairing-page name="host-enrollment" class="progress">
- <!-- 'enrollmentTitle' contains <strong> tag. -->
- <html-echo class="title"
- content="{{['enrollmentInProgress', C.enrollmentDomain] | i18n}}">
- </html-echo>
- </controller-pairing-page>
-
- <controller-pairing-page name="host-enrollment-error" class="progress">
- <div class="title">{{'enrollmentErrorTitle' | i18n}}</div>
- <div>{{'enrollmentErrorHostRestarts' | i18n}}</div>
- </controller-pairing-page>
-
- <controller-pairing-page name="pairing-done" class="big-font">
- <div class="title">{{'successTitle' | i18n}}</div>
- <div>{{['successText', selectedDevice] | i18n}}</div>
- <paper-button label="{{'continueToHangoutsBtn' | i18n}}"
- on-click="{{userActed}}" action="startSession"
- disabled?="{{C.controlsDisabled}}">
- </paper-button>
- </controller-pairing-page>
- </core-animated-pages>
- </template>
-</polymer-element>
-
<div class="step hidden no-logo" id="controller-pairing" hidden>
<controller-pairing-screen name="ControllerPairingScreen">
</controller-pairing-screen>
</div>
-
diff --git a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_controller_pairing.js b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_controller_pairing.js
index 9f0b233f60f..cc50c3c554e 100644
--- a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_controller_pairing.js
+++ b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_controller_pairing.js
@@ -13,78 +13,3 @@ login.createScreen('ControllerPairingScreen', 'controller-pairing', function() {
}
};
});
-
-Polymer('pairing-device-list', (function() {
- /** @const */ var ICON_COLORS = ['#F0B9CB', '#F0ACC3', '#F098B6', '#F084A9',
- '#F06D99', '#F05287', '#F0467F', '#F03473',
- '#F01E65', '#F00051'];
- return {
- /* Returns pseudo-random color depending of hash of the |name|. */
- colorByName: function(name) {
- var hash = 0;
- for (var i = 0; i < name.length; ++i)
- hash = (name.charCodeAt(i) + 31 * hash) | 0;
- return ICON_COLORS[hash % ICON_COLORS.length];
- }
- };
-})());
-
-Polymer('controller-pairing-screen', (function() {
- 'use strict';
-
- // Keep these constants synced with corresponding constants defined in
- // controller_pairing_screen_actor.{h,cc}.
- /** @const */ var CONTEXT_KEY_CONTROLS_DISABLED = 'controlsDisabled';
- /** @const */ var CONTEXT_KEY_SELECTED_DEVICE = 'selectedDevice';
- /** @const */ var CONTEXT_KEY_ACCOUNT_ID = 'accountId';
-
- /** @const */ var ACTION_ENROLL = 'enroll';
-
- /** @const */ var PAGE_AUTHENTICATION = 'authentication';
-
- return {
- gaiaHost_: null,
- selectedDevice: null,
-
- observe: {
- 'C.devices': 'deviceListChanged',
- 'C.page': 'pageChanged'
- },
-
- /** @override */
- initialize: function() {
- this.context.set(CONTEXT_KEY_CONTROLS_DISABLED, true);
- this.commitContextChanges();
- this.gaiaHost_ = new cr.login.GaiaAuthHost(this.$.gaiaFrame);
- },
-
- pageChanged: function(oldPage, newPage) {
- if (newPage == PAGE_AUTHENTICATION) {
- this.gaiaHost_.load(cr.login.GaiaAuthHost.AuthMode.DEFAULT,
- {},
- this.onAuthCompleted_.bind(this));
- }
- },
-
- deviceListChanged: function() {
- this.selectedDevice = this.context.get(CONTEXT_KEY_SELECTED_DEVICE);
- },
-
- selectedDeviceChanged: function() {
- this.context.set(CONTEXT_KEY_SELECTED_DEVICE,
- this.selectedDevice ? this.selectedDevice : '');
- this.commitContextChanges();
- },
-
- helpButtonClicked: function() {
- console.error('Help is not implemented yet.');
- },
-
- onAuthCompleted_: function(credentials) {
- this.context.set(CONTEXT_KEY_ACCOUNT_ID, credentials.email);
- this.commitContextChanges();
- this.send(login.Screen.CALLBACK_USER_ACTED, ACTION_ENROLL);
- }
- };
-})());
-
diff --git a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_enable_debugging.css b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_enable_debugging.css
new file mode 100644
index 00000000000..9c23f20e6ea
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_enable_debugging.css
@@ -0,0 +1,163 @@
+/* Copyright 2013 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.
+ */
+
+#debugging {
+ display: flex;
+ flex-flow: column;
+ font-size: 16px;
+ min-height: 423px;
+ text-align: center;
+ width: 722px;
+}
+
+#debugging .step-contents {
+ margin: 80px 100px;
+}
+
+#debugging.setup-view .step-contents {
+ margin-bottom: 40px;
+ margin-top: 40px;
+}
+
+#debugging.done-view .step-contents,
+#debugging.error-view .step-contents {
+ margin: 80px;
+}
+
+#debugging #debugging-controls {
+ height: auto;
+ justify-content: center;
+ margin-top: auto;
+ padding-bottom: 80px;
+ padding-right: 0;
+ position: static;
+}
+
+#debugging.setup-view #debugging-controls {
+ padding-bottom: 40px;
+}
+
+#debugging-controls button {
+ margin: 0 10px;
+}
+
+#enable-debugging-icon {
+ background-position: center;
+ background-repeat: no-repeat;
+ width: 100%;
+}
+
+.remove-protection-view #enable-debugging-icon,
+.setup-view #enable-debugging-icon,
+.wait-view #enable-debugging-icon {
+ background-image: url(chrome://theme/IDR_RESET_WARNING);
+ background-size: 39px 72px;
+ height: 72px;
+}
+
+.done-view #enable-debugging-icon {
+ background-image: url(chrome://theme/IDR_ENABLE_DEBUGGING_SUCCESS);
+ background-size: 86px 66px;
+ height: 66px;
+}
+
+.error-view #enable-debugging-icon {
+ background-image: url(chrome://theme/IDR_ENABLE_DEBUGGING_FAILURE);
+ background-size: 66px;
+ height: 66px;
+}
+
+/** Preload icons */
+#enable-debugging-icon::after {
+ content:
+ url(chrome://theme/IDR_RESET_WARNING)
+ url(chrome://theme/IDR_ENABLE_DEBUGGING_SUCCESS)
+ url(chrome://theme/IDR_ENABLE_DEBUGGING_FAILURE);
+ display: none;
+}
+
+.enable-debugging-title {
+ color: #464646;
+ font-size: 24px;
+ font-weight: bold;
+ padding: 30px 45px;
+}
+
+.enable-debugging-text {
+ color: #606060;
+}
+
+#debugging #debugging-warning-details,
+#debugging #debugging-done-msg {
+ padding: 20px 40px 0;
+}
+
+#debugging #enable-debugging-wait-msg {
+ display: inline-block;
+ padding: 25px 0;
+ vertical-align: middle;
+}
+
+#enable-debugging-passwords {
+ font-style: italic;
+ margin-bottom: 10px;
+ margin-top: 20px;
+}
+
+#enable-debugging-password-note {
+ color: #c6c6c6;
+ font-size: 12px;
+}
+
+.enable-debugging-password-input {
+ align-items: center;
+ display: flex;
+ flex-direction: column;
+ margin: 10px 0;
+}
+
+.enable-debugging-password-input input[type=password] {
+ margin: 5px 0;
+ width: 300px;
+}
+
+.enable-debugging-password-input
+ input[type=password]::-webkit-input-placeholder {
+ font-size: 14px;
+ font-style: normal;
+}
+
+.done-specific,
+.error-specific,
+.remove-protection-specific,
+.setup-specific,
+.wait-specific {
+ display: none;
+}
+
+.error-view .error-specific,
+.done-view .done-specific,
+.remove-protection-view .remove-protection-specific,
+.setup-view .setup-specific,
+.wait-view .wait-specific {
+ display: block;
+}
+
+#debugging-remove-protection-button,
+#debugging-enable-button,
+#debugging-cancel-button,
+#debugging-ok-button {
+ display: none;
+}
+
+.remove-protection-view #debugging-remove-protection-button,
+.remove-protection-view #debugging-cancel-button,
+.setup-view #debugging-enable-button,
+.setup-view #debugging-cancel-button,
+.done-view #debugging-ok-button,
+.error-view #debugging-ok-button {
+ display: inline-block;
+}
+
diff --git a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_enable_debugging.html b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_enable_debugging.html
new file mode 100644
index 00000000000..c3f878b7b24
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_enable_debugging.html
@@ -0,0 +1,49 @@
+<div class="step faded hidden no-logo" id="debugging" role="group" hidden
+ i18n-values="aria-label:enableDebuggingScreenAccessibleTitle">
+ <div class="step-contents" aria-live="polite" aria-atomic="true">
+ <div id="enable-debugging-icon"></div>
+ <div i18n-content="enableDebuggingScreenTitle"
+ class="enable-debugging-title remove-protection-specific setup-specific
+ done-specific wait-specific">
+ </div>
+ <div i18n-content="enableDebuggingErrorTitle"
+ class="enable-debugging-title error-specific">
+ </div>
+ <div id="enable-debugging-info"
+ class="enable-debugging-text
+ remove-protection-specific setup-specific state-specific
+ error-specific done-specific">
+ <div id="enable-debugging-remove-protection-details"
+ class="remove-protection-specific">
+ <span i18n-content="enableDebuggingRemveRootfsMessage"></span>
+ <a id="enable-debugging-help-link" href="#"
+ i18n-content="enableDebuggingLearnMore"></a>
+ </div>
+ <div id="enable-debugging-setup-details"
+ i18n-content="enableDebuggingSetupMessage"
+ class="setup-specific">
+ </div>
+ <div id="enable-debugging-passwords" class="setup-specific">
+ <div class="enable-debugging-password-input">
+ <input id="enable-debugging-password" type="password">
+ <input id="enable-debugging-password2" type="password">
+ </div>
+ <div id="enable-debugging-password-note"
+ i18n-content="enableDebuggingPasswordLengthNote">
+ </div>
+ </div>
+ <div class="enable-debugging-completed-details done-specific"
+ i18n-content="enableDebuggingDoneMessage">
+ </div>
+ <div class="enable-debugging-error-details error-specific"
+ i18n-content="enableDebuggingErrorMessage">
+ </div>
+ </div>
+ <div id="revert-promise" class="enable-debugging-text wait-specific">
+ <div id="enable-debugging-wait-msg"
+ i18n-content="enableDebuggingWaitMessage">
+ </div>
+ </div>
+ </div>
+ <div id="debugging-controls" class="step-controls"></div>
+</div>
diff --git a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_enable_debugging.js b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_enable_debugging.js
new file mode 100644
index 00000000000..a1b70bb1803
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_enable_debugging.js
@@ -0,0 +1,166 @@
+// Copyright (c) 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 Enable developer features screen implementation.
+ */
+
+login.createScreen('EnableDebuggingScreen', 'debugging', function() {
+ return {
+
+ /* Possible UI states of the enable debugging screen. */
+ UI_STATE: {
+ ERROR: -1,
+ NONE: 0,
+ REMOVE_PROTECTION: 1,
+ SETUP: 2,
+ WAIT: 3,
+ DONE: 4
+ },
+
+ EXTERNAL_API: [
+ 'updateState'
+ ],
+
+ /** @override */
+ decorate: function() {
+ $('enable-debugging-help-link').addEventListener('click',
+ function(event) {
+ chrome.send('enableDebuggingOnLearnMore');
+ });
+
+ var password = $('enable-debugging-password');
+ var password2 = $('enable-debugging-password2');
+ password.addEventListener('input', this.onPasswordChanged_.bind(this));
+ password2.addEventListener('input', this.onPasswordChanged_.bind(this));
+ password.placeholder =
+ loadTimeData.getString('enableDebuggingPasswordLabel');
+ password2.placeholder =
+ loadTimeData.getString('enableDebuggingConfirmPasswordLabel');
+ },
+
+ /**
+ * Header text of the screen.
+ * @type {string}
+ */
+ get header() {
+ return loadTimeData.getString('enableDebuggingScreenTitle');
+ },
+
+ /**
+ * Buttons in oobe wizard's button strip.
+ * @type {array} Array of Buttons.
+ */
+ get buttons() {
+ var buttons = [];
+ var rootfsRemoveButton = this.ownerDocument.createElement('button');
+ rootfsRemoveButton.id = 'debugging-remove-protection-button';
+ rootfsRemoveButton.textContent =
+ loadTimeData.getString('enableDebuggingRemoveButton');
+ rootfsRemoveButton.addEventListener('click', function(e) {
+ chrome.send('enableDebuggingOnRemoveRootFSProtection');
+ e.stopPropagation();
+ });
+ buttons.push(rootfsRemoveButton);
+
+ var enableButton = this.ownerDocument.createElement('button');
+ enableButton.id = 'debugging-enable-button';
+ enableButton.textContent =
+ loadTimeData.getString('enableDebuggingEnableButton');
+ enableButton.addEventListener('click', function(e) {
+ chrome.send('enableDebuggingOnSetup',
+ [$('enable-debugging-password').value]);
+ e.stopPropagation();
+ });
+ buttons.push(enableButton);
+
+ var cancelButton = this.ownerDocument.createElement('button');
+ cancelButton.id = 'debugging-cancel-button';
+ cancelButton.textContent =
+ loadTimeData.getString('enableDebuggingCancelButton');
+ cancelButton.addEventListener('click', function(e) {
+ chrome.send('enableDebuggingOnCancel');
+ e.stopPropagation();
+ });
+ buttons.push(cancelButton);
+
+ var okButton = this.ownerDocument.createElement('button');
+ okButton.id = 'debugging-ok-button';
+ okButton.textContent =
+ loadTimeData.getString('enableDebuggingOKButton');
+ okButton.addEventListener('click', function(e) {
+ chrome.send('enableDebuggingOnDone');
+ e.stopPropagation();
+ });
+ buttons.push(okButton);
+
+ return buttons;
+ },
+
+ /**
+ * Returns a control which should receive an initial focus.
+ */
+ get defaultControl() {
+ if (this.state_ == this.UI_STATE.REMOVE_PROTECTION)
+ return $('debugging-remove-protection-button');
+ else if (this.state_ == this.UI_STATE.SETUP)
+ return $('enable-debugging-password');
+ else if (this.state_ == this.UI_STATE.DONE ||
+ this.state_ == this.UI_STATE.ERROR) {
+ return $('debugging-ok-button');
+ }
+
+ return $('debugging-cancel-button');
+ },
+
+ /**
+ * Cancels the enable debugging screen and drops the user back to the
+ * network settings.
+ */
+ cancel: function() {
+ chrome.send('enableDebuggingOnCancel');
+ },
+
+ /**
+ * Event handler that is invoked just before the screen in shown.
+ * @param {Object} data Screen init payload.
+ */
+ onBeforeShow: function(data) {
+ this.setDialogView_(this.UI_STATE.NONE);
+ },
+
+ onPasswordChanged_: function() {
+ var enableButton = $('debugging-enable-button');
+ var password = $('enable-debugging-password');
+ var password2 = $('enable-debugging-password2');
+ var pwd = password.value;
+ var pwd2 = password2.value;
+ enableButton.disabled = !((pwd.length == 0 && pwd2.length == 0) ||
+ (pwd == pwd2 && pwd.length >= 4));
+ },
+
+ /**
+ * Sets css style for corresponding state of the screen.
+ * @param {number} state.
+ * @private
+ */
+ setDialogView_: function(state) {
+ this.state_ = state;
+ this.classList.toggle('remove-protection-view',
+ state == this.UI_STATE.REMOVE_PROTECTION);
+ this.classList.toggle('setup-view', state == this.UI_STATE.SETUP);
+ this.classList.toggle('wait-view', state == this.UI_STATE.WAIT);
+ this.classList.toggle('done-view', state == this.UI_STATE.DONE);
+ this.classList.toggle('error-view', state == this.UI_STATE.ERROR);
+ this.defaultControl.focus();
+
+ if (Oobe.getInstance().currentScreen === this)
+ Oobe.getInstance().updateScreenSize(this);
+ },
+
+ updateState: function(state) {
+ this.setDialogView_(state);
+ }
+ };
+});
diff --git a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_eula.js b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_eula.js
index 7cafec281cb..f6c85cc2cdf 100644
--- a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_eula.js
+++ b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_eula.js
@@ -7,6 +7,8 @@
*/
login.createScreen('EulaScreen', 'eula', function() {
+ var CONTEXT_KEY_USAGE_STATS_ENABLED = 'usageStatsEnabled';
+
return {
/** @override */
decorate: function() {
@@ -34,6 +36,14 @@ login.createScreen('EulaScreen', 'eula', function() {
}, 0);
event.preventDefault();
});
+
+ var self = this;
+ $('usage-stats').addEventListener('click', function(event) {
+ self.context.set(CONTEXT_KEY_USAGE_STATS_ENABLED,
+ $('usage-stats').checked);
+ self.commitContextChanges();
+ event.stopPropagation();
+ });
},
/**
@@ -76,23 +86,17 @@ login.createScreen('EulaScreen', 'eula', function() {
get buttons() {
var buttons = [];
- var backButton = this.ownerDocument.createElement('button');
- backButton.id = 'back-button';
+ var backButton = this.declareButton('back-button');
backButton.textContent = loadTimeData.getString('back');
- backButton.addEventListener('click', function(e) {
- chrome.send('eulaOnExit', [false, $('usage-stats').checked]);
- e.stopPropagation();
- });
buttons.push(backButton);
- var acceptButton = this.ownerDocument.createElement('button');
- acceptButton.id = 'accept-button';
+ var acceptButton = this.declareButton('accept-button');
acceptButton.disabled = true;
acceptButton.classList.add('preserve-disabled-state');
acceptButton.textContent = loadTimeData.getString('acceptAgreement');
acceptButton.addEventListener('click', function(e) {
$('eula').classList.add('loading'); // Mark EULA screen busy.
- chrome.send('eulaOnExit', [true, $('usage-stats').checked]);
+ Oobe.clearErrors();
e.stopPropagation();
});
buttons.push(acceptButton);
diff --git a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_hid_detection.css b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_hid_detection.css
index 401eab307ca..31f559386e3 100644
--- a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_hid_detection.css
+++ b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_hid_detection.css
@@ -94,6 +94,6 @@
visibility: visible;
}
-.pairing #hid-keyboard-pincode {
+.pairing .show-pincode #hid-keyboard-pincode {
display: block;
}
diff --git a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_hid_detection.html b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_hid_detection.html
index 84208d23376..49a670ed6a2 100644
--- a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_hid_detection.html
+++ b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_hid_detection.html
@@ -23,7 +23,7 @@
</span>
</div>
</div>
- <div id="hid-keyboard-block">
+ <div id="hid-keyboard-block" class="searching">
<div id="hid-keyboard-icon-block">
<img id="hid-keyboard-icon" src="chrome://theme/IDR_BLUETOOTH_KEYBOARD"
alt="">
diff --git a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_hid_detection.js b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_hid_detection.js
index 7610e2b788a..ba3564353f5 100644
--- a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_hid_detection.js
+++ b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_hid_detection.js
@@ -7,11 +7,17 @@
*/
login.createScreen('HIDDetectionScreen', 'hid-detection', function() {
+ var CONTEXT_KEY_KEYBOARD_STATE = 'keyboard-state';
+ var CONTEXT_KEY_MOUSE_STATE = 'mouse-state';
+ var CONTEXT_KEY_KEYBOARD_PINCODE = 'keyboard-pincode';
+ var CONTEXT_KEY_KEYBOARD_ENTERED_PART_EXPECTED = 'num-keys-entered-expected';
+ var CONTEXT_KEY_KEYBOARD_ENTERED_PART_PINCODE = 'num-keys-entered-pincode';
+ var CONTEXT_KEY_MOUSE_DEVICE_NAME = 'mouse-device-name';
+ var CONTEXT_KEY_KEYBOARD_DEVICE_NAME = 'keyboard-device-name';
+ var CONTEXT_KEY_KEYBOARD_LABEL = 'keyboard-device-label';
+ var CONTEXT_KEY_CONTINUE_BUTTON_ENABLED = 'continue-button-enabled';
+
return {
- EXTERNAL_API: [
- 'setPointingDeviceState',
- 'setKeyboardDeviceState',
- ],
/**
* Enumeration of possible states during pairing. The value associated with
@@ -22,19 +28,110 @@ login.createScreen('HIDDetectionScreen', 'hid-detection', function() {
PAIRING: {
STARTUP: 'bluetoothStartConnecting',
REMOTE_PIN_CODE: 'bluetoothRemotePinCode',
- REMOTE_PASSKEY: 'bluetoothRemotePasskey',
CONNECT_FAILED: 'bluetoothConnectFailed',
CANCELED: 'bluetoothPairingCanceled',
// Pairing dismissed (succeeded or canceled).
DISMISSED: 'bluetoothPairingDismissed'
},
+ // Enumeration of possible connection states of a device.
+ CONNECTION: {
+ SEARCHING: 'searching',
+ CONNECTED: 'connected',
+ PAIRING: 'pairing',
+ PAIRED: 'paired',
+ // Special info state.
+ UPDATE: 'update'
+ },
+
+ // Possible ids of device blocks.
+ BLOCK: {
+ MOUSE: 'hid-mouse-block',
+ KEYBOARD: 'hid-keyboard-block'
+ },
+
/**
* Button to move to usual OOBE flow after detection.
* @private
*/
continueButton_: null,
+ /** @override */
+ decorate: function() {
+ var self = this;
+
+ this.context.addObserver(
+ CONTEXT_KEY_MOUSE_STATE,
+ function(stateId) {
+ if (stateId === undefined)
+ return;
+ self.setDeviceBlockState_('hid-mouse-block', stateId);
+ }
+ );
+ this.context.addObserver(
+ CONTEXT_KEY_KEYBOARD_STATE,
+ function(stateId) {
+ if (stateId === undefined)
+ return;
+ self.setDeviceBlockState_('hid-keyboard-block', stateId);
+ if (stateId == self.CONNECTION.PAIRED) {
+ $('hid-keyboard-label-paired').textContent = self.context.get(
+ CONTEXT_KEY_KEYBOARD_LABEL, '');
+ } else if (stateId == self.CONNECTION.PAIRING) {
+ $('hid-keyboard-label-pairing').textContent = self.context.get(
+ CONTEXT_KEY_KEYBOARD_LABEL, '');
+ } else if (stateId == self.CONNECTION.CONNECTED) {
+ }
+ }
+ );
+ this.context.addObserver(
+ CONTEXT_KEY_KEYBOARD_PINCODE,
+ function(pincode) {
+ self.setPincodeKeysState_();
+ if (!pincode) {
+ $('hid-keyboard-pincode').classList.remove('show-pincode');
+ return;
+ }
+ if (self.context.get(CONTEXT_KEY_KEYBOARD_STATE, '') !=
+ self.CONNECTION.PAIRING) {
+ return;
+ }
+ $('hid-keyboard-pincode').classList.add('show-pincode');
+ for (var i = 0, len = pincode.length; i < len; i++) {
+ var pincodeSymbol = $('hid-keyboard-pincode-sym-' + (i + 1));
+ pincodeSymbol.textContent = pincode[i];
+ }
+ announceAccessibleMessage(
+ self.context.get(CONTEXT_KEY_KEYBOARD_LABEL, '') + ' ' + pincode +
+ ' ' + loadTimeData.getString('hidDetectionBTEnterKey'));
+ }
+ );
+ this.context.addObserver(
+ CONTEXT_KEY_KEYBOARD_ENTERED_PART_EXPECTED,
+ function(entered_part_expected) {
+ if (self.context.get(CONTEXT_KEY_KEYBOARD_STATE, '') != 'pairing')
+ return;
+ self.setPincodeKeysState_();
+ }
+ );
+ this.context.addObserver(
+ CONTEXT_KEY_KEYBOARD_ENTERED_PART_PINCODE,
+ function(entered_part) {
+ if (self.context.get(CONTEXT_KEY_KEYBOARD_STATE, '') !=
+ self.CONNECTION.PAIRING) {
+ return;
+ }
+ self.setPincodeKeysState_();
+ }
+ );
+ this.context.addObserver(
+ CONTEXT_KEY_CONTINUE_BUTTON_ENABLED,
+ function(enabled) {
+ $('hid-continue-button').disabled = !enabled;
+ }
+ );
+ },
+
/**
* Buttons in oobe wizard's button strip.
* @type {array} Array of Buttons.
@@ -50,7 +147,6 @@ login.createScreen('HIDDetectionScreen', 'hid-detection', function() {
e.stopPropagation();
});
buttons.push(continueButton);
- this.continueButton_ = continueButton;
return buttons;
},
@@ -59,98 +155,62 @@ login.createScreen('HIDDetectionScreen', 'hid-detection', function() {
* Returns a control which should receive an initial focus.
*/
get defaultControl() {
- return this.continueButton_;
+ return $('hid-continue-button');
},
/**
* Sets a device-block css class to reflect device state of searching,
* connected, pairing or paired (for BT devices).
- * @param {blockId} id one of 'hid-mouse-block' or 'hid-keyboard-block'.
- * @param {state} one of 'searching', 'connected', 'pairing', 'paired',
+ * @param {blockId} id one of keys of this.BLOCK dict.
+ * @param {state} one of keys of this.CONNECTION dict.
* @private
*/
setDeviceBlockState_: function(blockId, state) {
if (state == 'update')
return;
var deviceBlock = $(blockId);
- var states = ['searching', 'connected', 'pairing', 'paired'];
- for (var i = 0; i < states.length; ++i) {
- if (states[i] != state)
- deviceBlock.classList.remove(states[i]);
+ for (var key in this.CONNECTION) {
+ var stateCase = this.CONNECTION[key];
+ deviceBlock.classList.toggle(stateCase, stateCase == state);
}
- deviceBlock.classList.add(state);
},
/**
* Sets state for mouse-block.
- * @param {state} one of 'searching', 'connected', 'paired'.
+ * @param {state} one of keys of this.CONNECTION dict.
*/
setPointingDeviceState: function(state) {
if (state === undefined)
return;
- this.setDeviceBlockState_('hid-mouse-block', state);
+ this.setDeviceBlockState_(this.BLOCK.MOUSE, state);
},
/**
* Sets state for pincode key elements.
- * @param {entered} int, number of typed keys of pincode, -1 if keys press
- * detection is not supported by device.
*/
- setPincodeKeysState_: function(entered) {
+ setPincodeKeysState_: function() {
+ var entered = this.context.get(
+ CONTEXT_KEY_KEYBOARD_ENTERED_PART_PINCODE, 0);
+ // whether the functionality of getting num of entered keys is available.
+ var expected = this.context.get(
+ CONTEXT_KEY_KEYBOARD_ENTERED_PART_EXPECTED, false);
var pincodeLength = 7; // including enter-key
for (var i = 0; i < pincodeLength; i++) {
var pincodeSymbol = $('hid-keyboard-pincode-sym-' + (i + 1));
- pincodeSymbol.classList.remove('key-typed');
- pincodeSymbol.classList.remove('key-untyped');
- pincodeSymbol.classList.remove('key-next');
- if (i < entered)
- pincodeSymbol.classList.add('key-typed');
- else if (i == entered)
- pincodeSymbol.classList.add('key-next');
- else if (entered != -1)
- pincodeSymbol.classList.add('key-untyped');
- }
- },
-
- /**
- * Sets state for keyboard-block.
- * @param {data} dict with parameters.
- */
- setKeyboardDeviceState: function(data) {
- if (data === undefined || !('state' in data))
- return;
- var state = data['state'];
- this.setDeviceBlockState_('hid-keyboard-block', state);
- if (state == 'paired')
- $('hid-keyboard-label-paired').textContent = data['keyboard-label'];
- else if (state == 'pairing') {
- $('hid-keyboard-label-pairing').textContent = data['keyboard-label'];
- if (data['pairing-state'] == this.PAIRING.REMOTE_PIN_CODE ||
- data['pairing-state'] == this.PAIRING.REMOTE_PASSKEY) {
- this.setPincodeKeysState_(-1);
- for (var i = 0, len = data['pincode'].length; i < len; i++) {
- var pincodeSymbol = $('hid-keyboard-pincode-sym-' + (i + 1));
- pincodeSymbol.textContent = data['pincode'][i];
- }
- announceAccessibleMessage(
- data['keyboard-label'] + ' ' + data['pincode'] + ' ' +
- loadTimeData.getString('hidDetectionBTEnterKey'));
- }
- } else if (state == 'update') {
- if ('keysEntered' in data) {
- this.setPincodeKeysState_(data['keysEntered']);
- }
+ pincodeSymbol.classList.toggle('key-typed', i < entered && expected);
+ pincodeSymbol.classList.toggle('key-untyped', i > entered && expected);
+ pincodeSymbol.classList.toggle('key-next', i == entered && expected);
}
},
- /**
+ /*
* Event handler that is invoked just before the screen in shown.
* @param {Object} data Screen init payload.
*/
onBeforeShow: function(data) {
- $('hid-continue-button').disabled = true;
- this.setDeviceBlockState_('hid-mouse-block', 'searching');
- this.setDeviceBlockState_('hid-keyboard-block', 'searching');
+ this.setDeviceBlockState_('hid-mouse-block', this.CONNECTION.SEARCHING);
+ this.setDeviceBlockState_('hid-keyboard-block',
+ this.CONNECTION.SEARCHING);
},
};
});
diff --git a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_host_pairing.html b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_host_pairing.html
index 9fabf45d6e5..0bee2ba7320 100644
--- a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_host_pairing.html
+++ b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_host_pairing.html
@@ -1,81 +1,3 @@
-<link rel="import" href="chrome://oobe/custom_elements.html">
-<link rel="import" href="polymer/core-animated-pages/core-animated-pages.html">
-<link rel="import" href="polymer/core-iconset-svg/core-iconset-svg.html">
-<link rel="import" href="polymer/core-item/core-item.html">
-<link rel="import" href="polymer/polymer/polymer.html">
-
-<core-iconset-svg id="host-pairing-icons" iconSize="48">
- <svg>
- <defs>
- <g id="cast">
- <include src="../../../../app/theme/cast_icon.svg">
- </g>
- </defs>
- </svg>
-</core-iconset-svg>
-
-<polymer-element name="host-pairing-page" noscript>
- <template>
- <link rel="stylesheet" href="oobe_screen_host_pairing_page.css">
-
- <div id="title">
- <content select=".title"></content>
- </div>
- <div id="content">
- <content></content>
- </div>
- </template>
-</polymer-element>
-
-<polymer-element name="host-pairing-screen" extends="oobe-screen">
- <template>
- <link rel="stylesheet" href="oobe_screen_host_pairing.css">
-
- <core-animated-pages transitions="cross-fade-all"
- selected="{{C.page}}">
- <host-pairing-page name="welcome">
- <div class="title">{{'welcomeTitle' | i18n}}</div>
- <div>{{'welcomeText' | i18n}}</div>
- </host-pairing-page>
- <host-pairing-page name="code-confirmation">
- <div class="title">{{'confirmationTitle' | i18n}}</div>
- <div id="code">{{C.code}}</div>
- </host-pairing-page>
- <host-pairing-page name="update">
- <div class="title">{{'updatingTitle' | i18n}}</div>
- <!-- Not yet implemented on backend side. -->
- <!--div>{{['updatingText', C.downloadedMb, C.totalMb] | i18n}}</div-->
- </host-pairing-page>
- <host-pairing-page name="enrollment-introduction">
- <div class="title">{{'enrollTitle' | i18n}}</div>
- </host-pairing-page>
- <host-pairing-page name="enrollment">
- <div class="title">
- <!-- 'enrollmentTitle' contains <strong> tag. We need to wrap it in
- 'html-echo' to prevent HTML escaping. -->
- <html-echo
- content="{{['enrollingTitle', C.enrollmentDomain] | i18n}}">
- </html-echo>
- </div>
- </host-pairing-page>
- <host-pairing-page name="enrollment-error">
- <div class="title">{{'enrollmentErrorTitle' | i18n}}</div>
- <div>{{'errorNeedsRestart' | i18n}}</div>
- </host-pairing-page>
- <host-pairing-page name="pairing-done">
- <div class="title">{{'doneTitle' | i18n}}</div>
- <div>{{'doneText' | i18n}}</div>
- </host-pairing-page>
- </core-animated-pages>
- <core-item id="device-indicator"class="font-scalable"
- icon="host-pairing-icons:cast">
- <div id="device-label">{{C.deviceName}}</div>
- </core-item>
- <div id="illustration"></div>
- </template>
-</polymer-element>
-
<div class="step hidden no-logo fullscreen" id="host-pairing" hidden>
<host-pairing-screen name="HostPairingScreen"></host-pairing-screen>
</div>
-
diff --git a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_host_pairing.js b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_host_pairing.js
index 32beec2ffc3..9b62a88e162 100644
--- a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_host_pairing.js
+++ b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_host_pairing.js
@@ -24,21 +24,3 @@ login.createScreen('HostPairingScreen', 'host-pairing', function() {
}
};
});
-
-Polymer('host-pairing-screen', (function() {
- 'use strict';
-
- /** @const */ var CALLBACK_CONTEXT_READY = 'contextReady';
-
- return {
- onBeforeShow: function() {
- Oobe.getInstance().headerHidden = true;
- },
-
- /** @override */
- initialize: function() {
- this.send(CALLBACK_CONTEXT_READY);
- }
- };
-})());
-
diff --git a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_network.css b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_network.css
index 7e3d86006c2..d68d22646b8 100644
--- a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_network.css
+++ b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_network.css
@@ -29,6 +29,14 @@
width: 435px;
}
+.connect-debugging-specific {
+ display: none;
+}
+
+.connect-debugging-view .connect-debugging-specific {
+ display: block;
+}
+
html:not([highlight=strong]) #network-timezone-setting {
display: none;
}
diff --git a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_network.html b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_network.html
index 17464ff9dca..ffa420c28d2 100644
--- a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_network.html
+++ b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_network.html
@@ -37,5 +37,9 @@
<a id="connect-accessibility-link" href="#" role="button"
i18n-content="accessibilityLink"></a>
</div>
+ <div class="connect-debugging-specific">
+ <a id="connect-debugging-features-link" href="#" role="button"
+ i18n-content="debuggingFeaturesLink"></a>
+ </div>
</div>
</div>
diff --git a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_network.js b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_network.js
index 78df29c3539..bba9c388247 100644
--- a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_network.js
+++ b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_network.js
@@ -7,11 +7,16 @@
*/
login.createScreen('NetworkScreen', 'connect', function() {
+ var USER_ACTION_CONTINUE_BUTTON_CLICKED = 'continue';
+ var USER_ACTION_CONNECT_DEBUGGING_FEATURES_CLICKED =
+ 'connect-debugging-features';
+ var CONTEXT_KEY_LOCALE = 'locale';
+ var CONTEXT_KEY_INPUT_METHOD = 'input-method';
+ var CONTEXT_KEY_TIMEZONE = 'timezone';
+ var CONTEXT_KEY_CONTINUE_BUTTON_ENABLED = 'continue-button-enabled';
+
return {
EXTERNAL_API: [
- 'enableContinueButton',
- 'setInputMethod',
- 'setTimezone',
'showError'
],
@@ -22,29 +27,68 @@ login.createScreen('NetworkScreen', 'connect', function() {
/** @override */
decorate: function() {
+ var self = this;
+
Oobe.setupSelect($('language-select'),
loadTimeData.getValue('languageList'),
- 'networkOnLanguageChanged');
-
+ function(languageId) {
+ self.context.set(CONTEXT_KEY_LOCALE, languageId);
+ self.commitContextChanges();
+ });
Oobe.setupSelect($('keyboard-select'),
loadTimeData.getValue('inputMethodsList'),
- 'networkOnInputMethodChanged');
-
+ function(inputMethodId) {
+ self.context.set(CONTEXT_KEY_INPUT_METHOD,
+ inputMethodId);
+ self.commitContextChanges();
+ });
Oobe.setupSelect($('timezone-select'),
loadTimeData.getValue('timezoneList'),
- 'networkOnTimezoneChanged');
+ function(timezoneId) {
+ self.context.set(CONTEXT_KEY_TIMEZONE, timezoneId);
+ self.commitContextChanges();
+ });
this.dropdown_ = $('networks-list');
cr.ui.DropDown.decorate(this.dropdown_);
+
+ this.declareUserAction(
+ $('connect-debugging-features-link'),
+ { action_id: USER_ACTION_CONNECT_DEBUGGING_FEATURES_CLICKED,
+ event: 'click'
+ });
+ this.declareUserAction(
+ $('connect-debugging-features-link'),
+ { action_id: USER_ACTION_CONNECT_DEBUGGING_FEATURES_CLICKED,
+ condition: function(event) { return event.keyCode == 32; },
+ event: 'keyup'
+ });
+
+ this.context.addObserver(
+ CONTEXT_KEY_INPUT_METHOD,
+ function(inputMethodId) {
+ option = $('keyboard-select').querySelector(
+ 'option[value="' + inputMethodId + '"]');
+ if (option)
+ option.selected = true;
+ });
+ this.context.addObserver(CONTEXT_KEY_TIMEZONE, function(timezoneId) {
+ $('timezone-select').value = timezoneId;
+ });
+ this.context.addObserver(CONTEXT_KEY_CONTINUE_BUTTON_ENABLED,
+ function(enabled) {
+ $('continue-button').disabled = !enabled;
+ });
},
onBeforeShow: function(data) {
cr.ui.DropDown.show('networks-list', true, -1);
+ this.classList.toggle('connect-debugging-view',
+ data && 'isDeveloperMode' in data && data['isDeveloperMode']);
},
onBeforeHide: function() {
cr.ui.DropDown.hide('networks-list');
- this.enableContinueButton(false);
},
/**
@@ -62,15 +106,13 @@ login.createScreen('NetworkScreen', 'connect', function() {
get buttons() {
var buttons = [];
- var continueButton = this.ownerDocument.createElement('button');
- continueButton.disabled = true;
- continueButton.id = 'continue-button';
+ var continueButton = this.declareButton(
+ 'continue-button',
+ USER_ACTION_CONTINUE_BUTTON_CLICKED);
+ continueButton.disabled = !this.context.get(
+ CONTEXT_KEY_CONTINUE_BUTTON_ENABLED, false /* default */);
continueButton.textContent = loadTimeData.getString('continueButton');
continueButton.classList.add('preserve-disabled-state');
- continueButton.addEventListener('click', function(e) {
- chrome.send('networkOnExit');
- e.stopPropagation();
- });
buttons.push(continueButton);
return buttons;
@@ -84,33 +126,6 @@ login.createScreen('NetworkScreen', 'connect', function() {
},
/**
- * Enables/disables continue button.
- * @param {boolean} enable Should the button be enabled?
- */
- enableContinueButton: function(enable) {
- $('continue-button').disabled = !enable;
- },
-
- /**
- * Sets the current input method.
- * @param {string} inputMethodId The ID of the input method to select.
- */
- setInputMethod: function(inputMethodId) {
- option = $('keyboard-select').querySelector(
- 'option[value="' + inputMethodId + '"]');
- if (option)
- option.selected = true;
- },
-
- /**
- * Sets the current timezone.
- * @param {string} timezoneId The timezone ID to select.
- */
- setTimezone: function(timezoneId) {
- $('timezone-select').value = timezoneId;
- },
-
- /**
* Shows the network error message.
* @param {string} message Message to be shown.
*/
diff --git a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment.css b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment.css
index ffb70e96476..8589779b48c 100644
--- a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment.css
+++ b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment.css
@@ -21,7 +21,7 @@
.oauth-enroll-state-working #oauth-enroll-step-working,
.oauth-enroll-state-error #oauth-enroll-step-error,
-.oauth-enroll-state-explain #oauth-enroll-step-explain,
+.oauth-enroll-state-attribute-prompt #oauth-enroll-step-attribute-prompt,
.oauth-enroll-state-success #oauth-enroll-step-success {
display: table;
height: 480px;
@@ -71,8 +71,7 @@
margin-top: 20px;
}
-#oauth-enroll-learn-more-link,
-.oauth-enroll-explain-link {
+#oauth-enroll-learn-more-link {
display: none;
}
@@ -82,10 +81,6 @@
display: inline;
}
-#oauth-enrollment.mode-auto .oauth-enroll-explain-link {
- display: inline;
-}
-
.oauth-enroll-step-content {
display: table-cell;
vertical-align: middle;
@@ -107,10 +102,6 @@
width: 22px;
}
-#oauth-enrollment.mode-auto #oauth-enroll-error-retry {
- display: none;
-}
-
.oauth-enroll-step-message {
display: inline-block;
max-width: 400px;
@@ -118,10 +109,6 @@
vertical-align: top;
}
-#oauth-enroll-explain-message {
- white-space: pre-line;
-}
-
.oauth-enroll-link {
color: rgb(37, 79, 155);
cursor: pointer;
@@ -139,10 +126,10 @@
.oauth-enroll-state-working.mode-forced #oauth-enroll-back-button,
.oauth-enroll-state-working.mode-recovery #oauth-enroll-back-button,
.oauth-enroll-state-error.mode-manual #oauth-enroll-cancel-button,
-.oauth-enroll-state-error.mode-auto #oauth-enroll-retry-button,
+.oauth-enroll-state-error.mode-manual #oauth-enroll-done-button,
.oauth-enroll-state-error.mode-forced #oauth-enroll-back-button,
.oauth-enroll-state-error.mode-recovery #oauth-enroll-back-button,
-.oauth-enroll-state-explain #oauth-enroll-explain-retry-button,
+.oauth-enroll-state-attribute-prompt #oauth-enroll-continue-button,
.oauth-enroll-state-success #oauth-enroll-done-button {
display: inline;
}
@@ -176,6 +163,31 @@
display: none;
}
+#oauth-enrollment.saml #oauth-enroll-signin-divider,
+#oauth-enrollment.saml #oauth-enroll-signin-right {
+ display: none;
+}
+
#oauth-saml-notice-message {
margin: 0 auto;
}
+
+.oauth-enroll-textbox {
+ display: inline-block;
+ position: relative;
+ top: 10px;
+ vertical-align: top;
+}
+
+.oauth-enroll-textbox.oauth-enroll-attribute-field[type='text'] {
+ border-color: black;
+ display: inline;
+ font-size: 16px;
+ position: relative;
+ width: 250px;
+}
+
+.oauth-enroll-textbox.auth-enroll-attribute-label {
+ display: inline;
+ position: relative;
+}
diff --git a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment.html b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment.html
index 1bf1c5bb9c2..eccf643054e 100644
--- a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment.html
+++ b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment.html
@@ -11,8 +11,6 @@
<div i18n-content="oauthEnrollDescription"></div>
<div><a id="oauth-enroll-learn-more-link" class="oauth-enroll-link"
href="#" i18n-content="oauthEnrollExplainLink"></a></div>
- <div><a class="oauth-enroll-explain-link oauth-enroll-link"
- href="#" i18n-content="oauthEnrollExplainLink"></a></div>
</div>
</div>
<div id="oauth-enroll-step-working">
@@ -21,7 +19,7 @@
<span class="spinner"></span>
</span>
<span class="oauth-enroll-step-message">
- <span id="oauth-enroll-working-message"></span>
+ <span i18n-content="oauthEnrollWorking"></span>
</span>
</div>
</div>
@@ -35,31 +33,50 @@
<p>
<a id="oauth-enroll-error-retry" class="oauth-enroll-link" href="#"
i18n-content="oauthEnrollRetry"></a>
- <a class="oauth-enroll-explain-link oauth-enroll-link" href="#"
- i18n-content="oauthEnrollExplainLink"></a>
</p>
</span>
</div>
</div>
- <div id="oauth-enroll-step-explain">
+ <div id="oauth-enroll-step-success">
<div class="oauth-enroll-step-content">
<span class="oauth-enroll-step-icon">
- <img src="chrome://theme/IDR_INFO">
+ <img src="chrome://theme/IDR_ENROLL_SUCCESS">
</span>
<span class="oauth-enroll-step-message">
- <span id="oauth-enroll-explain-message"
- i18n-content="oauthEnrollExplain"></span>
+ <span i18n-content="oauthEnrollSuccess"></span>
</span>
</div>
</div>
- <div id="oauth-enroll-step-success">
+ <div id="oauth-enroll-step-attribute-prompt">
<div class="oauth-enroll-step-content">
- <span class="oauth-enroll-step-icon">
- <img src="chrome://theme/IDR_ENROLL_SUCCESS">
- </span>
<span class="oauth-enroll-step-message">
- <span i18n-content="oauthEnrollSuccess"></span>
+ <span id="oauth-enroll-attribute-prompt-message">
+ </span>
</span>
+ <p>
+ <div class="oauth-enroll-textbox">
+ <label>
+ <span i18n-content="oauthEnrollAssetIdLabel"
+ class="oauth_enroll-attribute-label"></span>
+ <p>
+ <input class="oauth-enroll-attribute-field"
+ id="oauth-enroll-asset-id" maxlength="200"/>
+ </p>
+ </label>
+ </div>
+ </p>
+ <p>
+ <div class="oauth-enroll-textbox">
+ <label>
+ <span i18n-content="oauthEnrollLocationLabel"
+ class="oauth-enroll-attribute-label"></span>
+ <p>
+ <input class="oauth-enroll-attribute-field"
+ id="oauth-enroll-location" maxlength="200"/>
+ </p>
+ </label>
+ </div>
+ </p>
</div>
</div>
</div>
diff --git a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment.js b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment.js
index 67e163d28b7..7ac1826601c 100644
--- a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment.js
+++ b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment.js
@@ -5,8 +5,8 @@
login.createScreen('OAuthEnrollmentScreen', 'oauth-enrollment', function() {
/** @const */ var STEP_SIGNIN = 'signin';
/** @const */ var STEP_WORKING = 'working';
+ /** @const */ var STEP_ATTRIBUTE_PROMPT = 'attribute-prompt';
/** @const */ var STEP_ERROR = 'error';
- /** @const */ var STEP_EXPLAIN = 'explain';
/** @const */ var STEP_SUCCESS = 'success';
/** @const */ var HELP_TOPIC_ENROLLMENT = 4631259;
@@ -15,8 +15,8 @@ login.createScreen('OAuthEnrollmentScreen', 'oauth-enrollment', function() {
EXTERNAL_API: [
'showStep',
'showError',
- 'showWorking',
'doReload',
+ 'showAttributePromptStep',
],
/**
@@ -25,10 +25,9 @@ login.createScreen('OAuthEnrollmentScreen', 'oauth-enrollment', function() {
signInUrl_: null,
/**
- * Dialog to confirm that auto-enrollment should really be cancelled.
- * This is only created the first time it's used.
+ * Gaia auth params for sign in frame.
*/
- confirmDialog_: null,
+ signInParams_: {},
/**
* The current step. This is the last value passed to showStep().
@@ -36,10 +35,30 @@ login.createScreen('OAuthEnrollmentScreen', 'oauth-enrollment', function() {
currentStep_: null,
/**
- * Opaque token used to correlate request and response while retrieving the
- * authenticated user's e-mail address from GAIA.
+ * The help topic to show when the user clicks the learn more link.
*/
- attemptToken_: null,
+ learnMoreHelpTopicID_: null,
+
+ /**
+ * We block esc, back button and cancel button until gaia is loaded to
+ * prevent multiple cancel events.
+ */
+ isCancelDisabled_: null,
+
+ get isCancelDisabled() { return this.isCancelDisabled_ },
+ set isCancelDisabled(disabled) {
+ if (disabled == this.isCancelDisabled)
+ return;
+ this.isCancelDisabled_ = disabled;
+
+ $('oauth-enroll-back-button').disabled = disabled;
+ $('oauth-enroll-back-button').
+ classList.toggle('preserve-disabled-state', disabled);
+
+ $('oauth-enroll-cancel-button').disabled = disabled;
+ $('oauth-enroll-cancel-button').
+ classList.toggle('preserve-disabled-state', disabled);
+ },
/** @override */
decorate: function() {
@@ -48,15 +67,7 @@ login.createScreen('OAuthEnrollmentScreen', 'oauth-enrollment', function() {
$('oauth-enroll-error-retry').addEventListener('click',
this.doRetry_.bind(this));
$('oauth-enroll-learn-more-link').addEventListener(
- 'click',
- function() {
- chrome.send('launchHelpApp', [HELP_TOPIC_ENROLLMENT]);
- });
- var links = document.querySelectorAll('.oauth-enroll-explain-link');
- for (var i = 0; i < links.length; i++) {
- links[i].addEventListener('click',
- this.showStep.bind(this, STEP_EXPLAIN));
- }
+ 'click', this.launchLearnMoreHelp_.bind(this));
this.updateLocalizedContent();
},
@@ -110,22 +121,11 @@ login.createScreen('OAuthEnrollmentScreen', 'oauth-enrollment', function() {
makeButton(
'oauth-enroll-back-button',
['oauth-enroll-focus-on-error'],
- loadTimeData.getString('oauthEnrollCancelAutoEnrollmentGoBack'),
+ loadTimeData.getString('oauthEnrollBack'),
function() {
+ this.isCancelDisabled = true;
chrome.send('oauthEnrollClose', ['cancel']);
- });
-
- makeButton(
- 'oauth-enroll-retry-button',
- ['oauth-enroll-focus-on-error'],
- loadTimeData.getString('oauthEnrollRetry'),
- this.doRetry_.bind(this));
-
- makeButton(
- 'oauth-enroll-explain-retry-button',
- ['oauth-enroll-focus-on-explain'],
- loadTimeData.getString('oauthEnrollExplainButton'),
- this.doRetry_.bind(this));
+ }.bind(this));
makeButton(
'oauth-enroll-done-button',
@@ -135,6 +135,16 @@ login.createScreen('OAuthEnrollmentScreen', 'oauth-enrollment', function() {
chrome.send('oauthEnrollClose', ['done']);
});
+ makeButton(
+ 'oauth-enroll-continue-button',
+ ['oauth-enroll-focus-on-attribute-prompt'],
+ loadTimeData.getString('oauthEnrollContinue'),
+ function() {
+ chrome.send('oauthEnrollAttributes',
+ [$('oauth-enroll-asset-id').value,
+ $('oauth-enroll-location').value]);
+ });
+
return buttons;
},
@@ -144,38 +154,90 @@ login.createScreen('OAuthEnrollmentScreen', 'oauth-enrollment', function() {
* URL.
*/
onBeforeShow: function(data) {
- var url = data.signin_url;
- url += '?gaiaUrl=' + encodeURIComponent(data.gaiaUrl);
- this.signInUrl_ = url;
- var modes = ['manual', 'forced', 'auto'];
+ this.signInParams_ = {};
+ this.signInParams_['gaiaUrl'] = data.gaiaUrl;
+ this.signInParams_['needPassword'] = false;
+ this.signInUrl_ = data.signin_url;
+ var modes = ['manual', 'forced', 'recovery'];
for (var i = 0; i < modes.length; ++i) {
this.classList.toggle('mode-' + modes[i],
data.enrollment_mode == modes[i]);
}
this.managementDomain_ = data.management_domain;
- $('oauth-enroll-signin-frame').contentWindow.location.href =
- this.signInUrl_;
+ this.isCancelDisabled = true;
+ this.doReload();
+ this.learnMoreHelpTopicID_ = data.learn_more_help_topic_id;
this.updateLocalizedContent();
this.showStep(STEP_SIGNIN);
},
/**
+ * Shows attribute-prompt step with pre-filled asset ID and location.
+ */
+ showAttributePromptStep: function(annotated_asset_id, annotated_location) {
+ $('oauth-enroll-attribute-prompt-message').innerHTML =
+ loadTimeData.getString('oauthEnrollAttributes');
+
+ $('oauth-enroll-asset-id').value = annotated_asset_id;
+ $('oauth-enroll-location').value = annotated_location;
+
+ this.showStep(STEP_ATTRIBUTE_PROMPT);
+ },
+
+ /**
* Cancels enrollment and drops the user back to the login screen.
*/
cancel: function() {
+ if (this.isCancelDisabled)
+ return;
+ this.isCancelDisabled = true;
chrome.send('oauthEnrollClose', ['cancel']);
},
/**
+ * Returns whether |step| is the device attribute update error or not.
+ */
+ isAttributeUpdateError: function(step) {
+ return this.currentStep_ == STEP_ATTRIBUTE_PROMPT && step == STEP_ERROR;
+ },
+
+ /**
+ * Shows cancel or done button.
+ */
+ hideCancelShowDone: function(hide) {
+ $('oauth-enroll-cancel-button').hidden = hide;
+ $('oauth-enroll-cancel-button').disabled = hide;
+
+ $('oauth-enroll-done-button').hidden = !hide;
+ $('oauth-enroll-done-button').disabled = !hide;
+ },
+
+ /**
* Switches between the different steps in the enrollment flow.
* @param {string} step the steps to show, one of "signin", "working",
- * "error", "success".
+ * "attribute-prompt", "error", "success".
*/
showStep: function(step) {
+ var focusStep = step;
+
this.classList.toggle('oauth-enroll-state-' + this.currentStep_, false);
this.classList.toggle('oauth-enroll-state-' + step, true);
+
+ /**
+ * In case of device attribute update error done button should be shown
+ * instead of cancel button and focus on success,
+ * because enrollment has completed
+ */
+ if (this.isAttributeUpdateError(step)) {
+ focusStep = STEP_SUCCESS;
+ this.hideCancelShowDone(true);
+ } else {
+ if (step == STEP_ERROR)
+ this.hideCancelShowDone(false);
+ }
+
var focusElements =
- this.querySelectorAll('.oauth-enroll-focus-on-' + step);
+ this.querySelectorAll('.oauth-enroll-focus-on-' + focusStep);
for (var i = 0; i < focusElements.length; ++i) {
if (getComputedStyle(focusElements[i])['display'] != 'none') {
focusElements[i].focus();
@@ -196,39 +258,17 @@ login.createScreen('OAuthEnrollmentScreen', 'oauth-enrollment', function() {
this.showStep(STEP_ERROR);
},
- /**
- * Sets a progress message and switches to the working screen.
- * @param {string} message the progress message.
- */
- showWorking: function(message) {
- $('oauth-enroll-working-message').textContent = message;
- this.showStep(STEP_WORKING);
- },
-
doReload: function() {
- $('oauth-enroll-signin-frame').contentWindow.location.href =
- this.signInUrl_;
- },
+ var signInFrame = $('oauth-enroll-signin-frame');
- /**
- * Handler for cancellations of an enforced auto-enrollment.
- */
- cancelAutoEnrollment: function() {
- // Only to be activated for the explain step in auto-enrollment.
- if (this.currentStep_ !== STEP_EXPLAIN)
- return;
+ var sendParamsOnLoad = function() {
+ signInFrame.removeEventListener('load', sendParamsOnLoad);
+ signInFrame.contentWindow.postMessage(this.signInParams_,
+ 'chrome-extension://mfffpogegjflfpflabcdkioaeobkgjik');
+ }.bind(this);
- if (!this.confirmDialog_) {
- this.confirmDialog_ = new cr.ui.dialogs.ConfirmDialog(document.body);
- this.confirmDialog_.setOkLabel(
- loadTimeData.getString('oauthEnrollCancelAutoEnrollmentConfirm'));
- this.confirmDialog_.setCancelLabel(
- loadTimeData.getString('oauthEnrollCancelAutoEnrollmentGoBack'));
- this.confirmDialog_.setInitialFocusOnCancel();
- }
- this.confirmDialog_.show(
- loadTimeData.getString('oauthEnrollCancelAutoEnrollmentReally'),
- this.onConfirmCancelAutoEnrollment_.bind(this));
+ signInFrame.addEventListener('load', sendParamsOnLoad);
+ signInFrame.contentWindow.location.href = this.signInUrl_;
},
/**
@@ -241,13 +281,6 @@ login.createScreen('OAuthEnrollmentScreen', 'oauth-enrollment', function() {
},
/**
- * Handler for confirmation of cancellation of auto-enrollment.
- */
- onConfirmCancelAutoEnrollment_: function() {
- chrome.send('oauthEnrollClose', ['autocancel']);
- },
-
- /**
* Checks if a given HTML5 message comes from the URL loaded into the signin
* frame.
* @param {Object} m HTML5 message.
@@ -271,7 +304,8 @@ login.createScreen('OAuthEnrollmentScreen', 'oauth-enrollment', function() {
if (msg.method == 'completeLogin') {
// A user has successfully authenticated via regular GAIA or SAML.
- chrome.send('oauthEnrollCompleteLogin', [msg.email]);
+ chrome.send('oauthEnrollCompleteLogin', [msg.email,
+ '' /* auth_code */]);
}
if (msg.method == 'authPageLoaded' && this.currentStep_ == STEP_SIGNIN) {
@@ -281,6 +315,14 @@ login.createScreen('OAuthEnrollmentScreen', 'oauth-enrollment', function() {
msg.domain);
}
this.classList.toggle('saml', msg.isSAML);
+ }
+
+ if (msg.method == 'resetAuthFlow') {
+ this.classList.remove('saml');
+ }
+
+ if (msg.method == 'loginUILoaded' && this.currentStep_ == STEP_SIGNIN) {
+ this.isCancelDisabled = false;
chrome.send('frameLoadingCompleted', [0]);
}
@@ -295,6 +337,15 @@ login.createScreen('OAuthEnrollmentScreen', 'oauth-enrollment', function() {
loadTimeData.getString('fatalEnrollmentError'),
false);
}
+ },
+
+ /**
+ * Opens the learn more help topic.
+ */
+ launchLearnMoreHelp_: function() {
+ if (this.learnMoreHelpTopicID_) {
+ chrome.send('launchHelpApp', [this.learnMoreHelpTopicID_]);
+ }
}
};
});
diff --git a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment_webview.css b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment_webview.css
new file mode 100644
index 00000000000..897bbae8023
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment_webview.css
@@ -0,0 +1,188 @@
+/* Copyright 2015 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.
+ */
+
+#oauth-enrollment {
+ height: 528px;
+ padding: 0;
+ width: 448px;
+}
+
+#oauth-enrollment.saml {
+ height: 528px;
+ width: 562px;
+}
+
+#oauth-enroll-step-contents {
+ color: #666;
+ height: 100%;
+ width: 100%;
+}
+
+#oauth-enroll-step-contents > div {
+ display: none;
+}
+
+.oauth-enroll-state-error #oauth-enroll-step-error,
+.oauth-enroll-state-success #oauth-enroll-step-success {
+ display: table;
+ height: 480px;
+ margin: 0 auto;
+}
+
+.oauth-enroll-state-working #oauth-enroll-step-signin,
+.oauth-enroll-state-attribute-prompt-error
+ #oauth-enroll-step-attribute-prompt-error,
+.oauth-enroll-state-signin #oauth-enroll-step-signin,
+.oauth-enroll-state-attribute-prompt
+ #oauth-enroll-step-attribute-prompt {
+ display: block;
+ height: 100%;
+ width: 100%;
+}
+
+#oauth-enroll-auth-view-container,
+#oauth-enroll-auth-view {
+ display: block;
+ height: 100%;
+ overflow: hidden;
+ padding: 0;
+ width: 100%;
+}
+
+#oauth-enrollment.mode-manual #oauth-enroll-learn-more-link,
+#oauth-enrollment.mode-forced #oauth-enroll-learn-more-link,
+#oauth-enrollment.mode-recovery #oauth-enroll-learn-more-link {
+ display: inline;
+}
+
+.oauth-enroll-step-content {
+ display: table-cell;
+ vertical-align: middle;
+}
+
+.oauth-enroll-step-message {
+ display: inline-block;
+ max-width: 400px;
+ text-align: left;
+ vertical-align: top;
+}
+
+.oauth-enroll-link {
+ color: rgb(17, 85, 204);
+ cursor: pointer;
+ text-decoration: none;
+}
+
+.oauth-enroll-button {
+ display: none;
+}
+
+#oauth-enroll-back-button,
+#oauth-enrollment.saml #oauth-enroll-back-button {
+ display: none;
+}
+
+.oauth-enroll-state-signin #oauth-enroll-back-button {
+ display: inline;
+}
+
+.oauth-enroll-state-signin.mode-manual #oauth-enroll-cancel-button,
+.oauth-enroll-state-signin.mode-forced #oauth-enroll-refresh-button,
+.oauth-enroll-state-signin.mode-recovery #oauth-enroll-refresh-button,
+.oauth-enroll-state-working.mode-manual #oauth-enroll-cancel-button,
+.oauth-enroll-state-working.mode-forced #oauth-enroll-refresh-button,
+.oauth-enroll-stain-working.mode-recovery #oauth-enroll-refresh-button,
+.oauth-enroll-state-error.mode-manual #oauth-enroll-cancel-button,
+.oauth-enroll-state-error.mode-manual #oauth-enroll-done-button,
+.oauth-enroll-state-success #oauth-enroll-done-button {
+ display: inline;
+}
+
+#oobe.oauth-enrollment #header-oauth-enrollment {
+ display: block;
+}
+
+#oauth-saml-notice-container {
+ -webkit-margin-start: 19px;
+ left: 0;
+ position: absolute;
+ right: 0;
+ text-align: start;
+ top: 15px;
+}
+
+#oauth-enrollment.saml {
+ padding-top: 47px;
+}
+
+#oauth-enrollment.saml #oauth-enrollment-controls,
+#oauth-enrollment.saml #oauth-enroll-signin-link-container {
+ -webkit-padding-end: 17px;
+}
+
+#oauth-enrollment:not(.saml) #oauth-saml-notice-container {
+ display: none;
+}
+
+#oauth-saml-notice-message {
+ color: rgb(106, 106, 106);
+ font-size: 13px;
+ margin: 0 auto;
+}
+
+#oauth-enroll-cancel-button,
+#oauth-enroll-back-button,
+#oauth-enroll-refresh-button {
+ position: absolute;
+ top: 10px;
+ z-index: 1;
+}
+
+#oauth-enroll-cancel-button,
+#oauth-enroll-refresh-button {
+ color: rgba(0, 0, 0, .54);
+ right: 10px;
+}
+
+html[dir=rtl] #oauth-enroll-cancel-button,
+html[dir=rtl] #oauth-enroll-refresh-button {
+ left: 10px;
+ right: auto;
+}
+
+#oauth-enrollment.saml #oauth-enroll-cancel-button,
+#oauth-enrollment.saml #oauth-enroll-refresh-button {
+ color: rgba(0, 0, 0, .54);
+}
+
+.oauth-enroll-state-signin #oauth-enroll-cancel-button,
+.oauth-enroll-state-working #oauth-enroll-cancel-button,
+.oauth-enroll-state-signin #oauth-enroll-refresh-button,
+.oauth-enroll-state-working #oauth-enroll-refresh-button {
+ color: white;
+}
+
+#oauth-enroll-back-button {
+ color: white;
+ left: 10px;
+}
+
+html[dir=rtl] #oauth-enroll-back-button {
+ -webkit-transform: scaleX(-1);
+ left: auto;
+ right: 10px;
+}
+
+#oauth-enrollment.saml #oauth-enroll-back-button {
+ display: none;
+}
+
+#oauth-enroll-attribute-prompt-message {
+ color: black;
+}
+
+#oauth-enroll-skip-button {
+ min-width: 0;
+}
diff --git a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment_webview.html b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment_webview.html
new file mode 100644
index 00000000000..19c973ff0f5
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment_webview.html
@@ -0,0 +1,72 @@
+<!-- Copyright 2015 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. -->
+
+<div id="oauth-enrollment" class="step no-logo hidden" hidden>
+ <div id="oauth-enroll-step-contents">
+ <div id="oauth-enroll-step-signin">
+ <div id="oauth-enroll-auth-view-container"></div>
+ </div>
+ <div id="oauth-enroll-step-error">
+ <notification-card id="oauth-enroll-error-card" type="fail"
+ i18n-values="buttonLabel:oauthEnrollRetry">
+ </notification-card>
+ </div>
+ <div id="oauth-enroll-step-success">
+ <notification-card id="oauth-enroll-success-card" type="success"
+ i18n-values="buttonLabel:oauthEnrollDone"
+ i18n-content="oauthEnrollSuccess">
+ </notification-card>
+ </div>
+ <div id="oauth-enroll-step-attribute-prompt">
+ <gaia-card id="oauth-enroll-attribute-prompt-card">
+ <div class="header" flex vertical layout end-justified start>
+ <h1 class="welcome-message" style="text-transform:capitalize"
+ i18n-content="oauthEnrollScreenTitle"></h1>
+ <p class="enterprise-info"
+ i18n-content="oauthEnrollDeviceInformation"></p>
+ </div>
+ <div class="footer" flex vertical layout justified>
+ <div class="oauth-enroll-step-message">
+ <span id="oauth-enroll-attribute-prompt-message"
+ i18n-content="oauthEnrollAttributeExplanation"></span>
+ <a href="#" id="oauth-enroll-learn-more-link"
+ class="oauth-enroll-link"
+ i18n-content="oauthEnrollExplaneAttributeLink"></a>
+ <gaia-input-form i18n-values="buttonText:oauthEnrollNextBtn">
+ <gaia-input id="oauth-enroll-asset-id"
+ type="text"
+ i18n-values="label:oauthEnrollAssetIdLabel">
+ </gaia-input>
+ <gaia-input id="oauth-enroll-location"
+ type="text"
+ i18n-values="label:oauthEnrollLocationLabel">
+ </gaia-input>
+ <gaia-paper-button noink id="oauth-enroll-skip-button"
+ class="link-button"
+ i18n-content="oauthEnrollSkip">
+ </gaia-paper-button>
+ </gaia-input-form>
+ </div>
+ </div>
+ </gaia-card>
+ </div>
+ <div id="oauth-enroll-step-attribute-prompt-error">
+ <notification-card id="oauth-enroll-attribute-prompt-error-card"
+ type="fail" i18n-values="buttonLabel:oauthEnrollDone">
+ </notification-card>
+ </div>
+ </div>
+ <div id="oauth-saml-notice-container">
+ <span id="oauth-saml-notice-message"></span>
+ </div>
+ <div id="oauth-enrollment-controls" class="step-controls"></div>
+ <button id="oauth-enroll-back-button" is="gaia-icon-button" icon="arrow-back"
+ hidden i18n-values="aria-label:backButton" tabindex="0"></button>
+ <button id="oauth-enroll-cancel-button" is="gaia-icon-button" icon="close"
+ class="oauth-enroll-button" i18n-values="aria-label:closeButton"
+ tabindex="0"></button>
+ <button id="oauth-enroll-refresh-button" is="gaia-icon-button" icon="refresh"
+ class="oauth-enroll-button" i18n-values="aria-label:closeButton"
+ tabindex="0"></button>
+</div>
diff --git a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment_webview.js b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment_webview.js
new file mode 100644
index 00000000000..4cd8fe50fb8
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment_webview.js
@@ -0,0 +1,293 @@
+// Copyright 2015 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.
+
+login.createScreen('OAuthEnrollmentScreen', 'oauth-enrollment', function() {
+ /** @const */ var STEP_SIGNIN = 'signin';
+ /** @const */ var STEP_WORKING = 'working';
+ /** @const */ var STEP_ATTRIBUTE_PROMPT = 'attribute-prompt';
+ /** @const */ var STEP_ERROR = 'error';
+ /** @const */ var STEP_SUCCESS = 'success';
+
+ /* TODO(dzhioev): define this step on C++ side.
+ /** @const */ var STEP_ATTRIBUTE_PROMPT_ERROR = 'attribute-prompt-error';
+
+ /** @const */ var HELP_TOPIC_ENROLLMENT = 4631259;
+
+ return {
+ EXTERNAL_API: [
+ 'showStep',
+ 'showError',
+ 'doReload',
+ 'showAttributePromptStep',
+ ],
+
+ /**
+ * Authenticator object that wraps GAIA webview.
+ */
+ authenticator_: null,
+
+ /**
+ * The current step. This is the last value passed to showStep().
+ */
+ currentStep_: null,
+
+ /**
+ * We block esc, back button and cancel button until gaia is loaded to
+ * prevent multiple cancel events.
+ */
+ isCancelDisabled_: null,
+
+ get isCancelDisabled() { return this.isCancelDisabled_ },
+ set isCancelDisabled(disabled) {
+ if (disabled == this.isCancelDisabled)
+ return;
+ this.isCancelDisabled_ = disabled;
+ },
+
+ /** @override */
+ decorate: function() {
+ var webview = document.createElement('webview');
+ webview.id = webview.name = 'oauth-enroll-auth-view';
+ webview.classList.toggle('oauth-enroll-focus-on-signin', true);
+ $('oauth-enroll-auth-view-container').appendChild(webview);
+ this.authenticator_ = new cr.login.Authenticator(webview);
+
+ this.authenticator_.addEventListener('ready',
+ (function() {
+ if (this.currentStep_ != STEP_SIGNIN)
+ return;
+ this.isCancelDisabled = false;
+ chrome.send('frameLoadingCompleted', [0]);
+ }).bind(this));
+
+ this.authenticator_.addEventListener('authCompleted',
+ (function(e) {
+ var detail = e.detail;
+ if (!detail.email || !detail.authCode) {
+ this.showError(
+ loadTimeData.getString('fatalEnrollmentError'),
+ false);
+ return;
+ }
+ chrome.send('oauthEnrollCompleteLogin', [detail.email,
+ detail.authCode]);
+ }).bind(this));
+
+ this.authenticator_.addEventListener('authFlowChange',
+ (function(e) {
+ var isSAML = this.authenticator_.authFlow ==
+ cr.login.Authenticator.AuthFlow.SAML;
+ if (isSAML) {
+ $('oauth-saml-notice-message').textContent =
+ loadTimeData.getStringF('samlNotice',
+ this.authenticator_.authDomain);
+ }
+ this.classList.toggle('saml', isSAML);
+ if (Oobe.getInstance().currentScreen == this)
+ Oobe.getInstance().updateScreenSize(this);
+ }).bind(this));
+
+ this.authenticator_.addEventListener('backButton',
+ (function(e) {
+ $('oauth-enroll-back-button').hidden = !e.detail;
+ }).bind(this));
+
+ this.authenticator_.insecureContentBlockedCallback =
+ (function(url) {
+ this.showError(
+ loadTimeData.getStringF('insecureURLEnrollmentError', url),
+ false);
+ }).bind(this);
+
+ this.authenticator_.missingGaiaInfoCallback =
+ (function() {
+ this.showError(
+ loadTimeData.getString('fatalEnrollmentError'),
+ false);
+ }).bind(this);
+
+ $('oauth-enroll-error-card').addEventListener('buttonclick',
+ this.doRetry_.bind(this));
+ function doneCallback() {
+ chrome.send('oauthEnrollClose', ['done']);
+ };
+
+ $('oauth-enroll-attribute-prompt-error-card').addEventListener(
+ 'buttonclick', doneCallback);
+ $('oauth-enroll-success-card').addEventListener(
+ 'buttonclick', doneCallback);
+
+ $('oauth-enroll-cancel-button').addEventListener('click',
+ this.cancel.bind(this));
+ $('oauth-enroll-refresh-button').addEventListener('click',
+ this.cancel.bind(this));
+
+ $('oauth-enroll-back-button').addEventListener('click',
+ (function(e) {
+ $('oauth-enroll-back-button').hidden = true;
+ $('oauth-enroll-auth-view').back();
+ e.preventDefault();
+ }).bind(this));
+
+ $('oauth-enroll-attribute-prompt-card').addEventListener('submit',
+ this.onAttributesSubmitted.bind(this));
+
+ $('oauth-enroll-learn-more-link').addEventListener('click',
+ function(event) {
+ chrome.send('oauthEnrollOnLearnMore');
+ });
+
+ $('oauth-enroll-skip-button').addEventListener('click',
+ this.onSkipButtonClicked.bind(this));
+ },
+
+ /**
+ * Header text of the screen.
+ * @type {string}
+ */
+ get header() {
+ return loadTimeData.getString('oauthEnrollScreenTitle');
+ },
+
+ /**
+ * Buttons in oobe wizard's button strip.
+ * @type {array} Array of Buttons.
+ */
+ get buttons() {
+ var buttons = [];
+ var ownerDocument = this.ownerDocument;
+
+ function makeButton(id, classes, label, handler) {
+ var button = ownerDocument.createElement('button');
+ button.id = id;
+ button.classList.add('oauth-enroll-button');
+ button.classList.add.apply(button.classList, classes);
+ button.textContent = label;
+ button.addEventListener('click', handler);
+ buttons.push(button);
+ }
+
+ return buttons;
+ },
+
+ /**
+ * Event handler that is invoked just before the frame is shown.
+ * @param {Object} data Screen init payload, contains the signin frame
+ * URL.
+ */
+ onBeforeShow: function(data) {
+ var gaiaParams = {};
+ gaiaParams.gaiaUrl = data.gaiaUrl;
+ gaiaParams.gaiaPath = 'embedded/setup/chromeos';
+ gaiaParams.isNewGaiaFlowChromeOS = true;
+ gaiaParams.needPassword = false;
+ if (data.management_domain) {
+ gaiaParams.enterpriseDomain = data.management_domain;
+ gaiaParams.emailDomain = data.management_domain;
+ }
+ gaiaParams.flow = 'enterprise';
+ this.authenticator_.load(cr.login.Authenticator.AuthMode.DEFAULT,
+ gaiaParams);
+
+ var modes = ['manual', 'forced', 'recovery'];
+ for (var i = 0; i < modes.length; ++i) {
+ this.classList.toggle('mode-' + modes[i],
+ data.enrollment_mode == modes[i]);
+ }
+ this.isCancelDisabled = true;
+ this.showStep(STEP_SIGNIN);
+ },
+
+ /**
+ * Shows attribute-prompt step with pre-filled asset ID and
+ * location.
+ */
+ showAttributePromptStep: function(annotated_asset_id, annotated_location) {
+ $('oauth-enroll-asset-id').value = annotated_asset_id;
+ $('oauth-enroll-location').value = annotated_location;
+ $('oauth-enroll-back-button').hidden = true;
+
+ this.showStep(STEP_ATTRIBUTE_PROMPT);
+ },
+
+ /**
+ * Cancels enrollment and drops the user back to the login screen.
+ */
+ cancel: function() {
+ if (this.isCancelDisabled)
+ return;
+ this.isCancelDisabled = true;
+ chrome.send('oauthEnrollClose', ['cancel']);
+ },
+
+ /**
+ * Switches between the different steps in the enrollment flow.
+ * @param {string} step the steps to show, one of "signin", "working",
+ * "attribute-prompt", "error", "success".
+ */
+ showStep: function(step) {
+ this.classList.toggle('oauth-enroll-state-' + this.currentStep_, false);
+ this.classList.toggle('oauth-enroll-state-' + step, true);
+
+ var focusElements =
+ this.querySelectorAll('.oauth-enroll-focus-on-' + step);
+ for (var i = 0; i < focusElements.length; ++i) {
+ if (getComputedStyle(focusElements[i])['display'] != 'none') {
+ focusElements[i].focus();
+ break;
+ }
+ }
+ this.currentStep_ = step;
+ },
+
+ /**
+ * Sets an error message and switches to the error screen.
+ * @param {string} message the error message.
+ * @param {boolean} retry whether the retry link should be shown.
+ */
+ showError: function(message, retry) {
+ if (this.currentStep_ == STEP_ATTRIBUTE_PROMPT) {
+ $('oauth-enroll-attribute-prompt-error-card').textContent =
+ message;
+ this.showStep(STEP_ATTRIBUTE_PROMPT_ERROR);
+ return;
+ }
+ $('oauth-enroll-error-card').textContent = message;
+ $('oauth-enroll-error-card').buttonLabel =
+ retry ? loadTimeData.getString('oauthEnrollRetry') : '';
+ this.showStep(STEP_ERROR);
+ },
+
+ doReload: function() {
+ this.authenticator_.reload();
+ },
+
+ /**
+ * Retries the enrollment process after an error occurred in a previous
+ * attempt. This goes to the C++ side through |chrome| first to clean up the
+ * profile, so that the next attempt is performed with a clean state.
+ */
+ doRetry_: function() {
+ chrome.send('oauthEnrollRetry');
+ },
+
+ /**
+ * Skips the device attribute update,
+ * shows the successful enrollment step.
+ */
+ onSkipButtonClicked: function() {
+ this.showStep(STEP_SUCCESS);
+ },
+
+ /**
+ * Uploads the device attributes to server. This goes to C++ side through
+ * |chrome| and launches the device attribute update negotiation.
+ */
+ onAttributesSubmitted: function() {
+ chrome.send('oauthEnrollAttributes',
+ [$('oauth-enroll-asset-id').value,
+ $('oauth-enroll-location').value]);
+ }
+ };
+});
diff --git a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_reset.js b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_reset.js
index a495a28491e..874f8e3165f 100644
--- a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_reset.js
+++ b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_reset.js
@@ -7,6 +7,18 @@
*/
login.createScreen('ResetScreen', 'reset', function() {
+ var USER_ACTION_CANCEL_RESET = 'cancel-reset';
+ var USER_ACTION_RESTART_PRESSED = 'restart-pressed';
+ var USER_ACTION_LEARN_MORE_PRESSED = 'learn-more-link';
+ var USER_ACTION_SHOW_CONFIRMATION = 'show-confirmation';
+ var USER_ACTION_POWERWASH_PRESSED = 'powerwash-pressed';
+ var USER_ACTION_RESET_CONFIRM_DISMISSED = 'reset-confirm-dismissed';
+ var CONTEXT_KEY_ROLLBACK_AVAILABLE = 'rollback-available';
+ var CONTEXT_KEY_ROLLBACK_CHECKED = 'rollback-checked';
+ var CONTEXT_KEY_IS_OFFICIAL_BUILD = 'is-official-build';
+ var CONTEXT_KEY_IS_CONFIRMATIONAL_VIEW = 'is-confirmational-view';
+ var CONTEXT_KEY_SCREEN_STATE = 'screen-state';
+
return {
/* Possible UI states of the reset screen. */
@@ -14,20 +26,85 @@ login.createScreen('ResetScreen', 'reset', function() {
REVERT_PROMISE: 'ui-state-revert-promise',
RESTART_REQUIRED: 'ui-state-restart-required',
POWERWASH_PROPOSAL: 'ui-state-powerwash-proposal',
- ROLLBACK_PROPOSAL: 'ui-state-rollback-proposal'
+ ROLLBACK_PROPOSAL: 'ui-state-rollback-proposal',
+ ERROR: 'ui-state-error',
+ },
+
+ RESET_SCREEN_STATE: {
+ RESTART_REQUIRED: 0,
+ REVERT_PROMISE: 1,
+ POWERWASH_PROPOSAL: 2, // supports 2 ui-states
+ ERROR: 3,
},
- EXTERNAL_API: [
- 'hideRollbackOption',
- 'showRollbackOption',
- 'updateViewOnRollbackCall'
- ],
/** @override */
decorate: function() {
- $('powerwash-help-link').addEventListener('click', function(event) {
- chrome.send('resetOnLearnMore');
- });
+ var self = this;
+
+ this.declareUserAction($('powerwash-help-link'),
+ { action_id: USER_ACTION_LEARN_MORE_PRESSED,
+ event: 'click'
+ });
+ this.declareUserAction($('reset-confirm-dismiss'),
+ { action_id: USER_ACTION_RESET_CONFIRM_DISMISSED,
+ event: 'click'
+ });
+ this.declareUserAction($('reset-confirm-commit'),
+ { action_id: USER_ACTION_POWERWASH_PRESSED,
+ event: 'click'
+ });
+
+ this.context.addObserver(
+ CONTEXT_KEY_SCREEN_STATE,
+ function(state) {
+ if (state == self.RESET_SCREEN_STATE.RESTART_REQUIRED)
+ self.ui_state = self.RESET_SCREEN_UI_STATE.RESTART_REQUIRED;
+ if (state == self.RESET_SCREEN_STATE.REVERT_PROMISE)
+ self.ui_state = self.RESET_SCREEN_UI_STATE.REVERT_PROMISE;
+ else if (state == self.RESET_SCREEN_STATE.POWERWASH_PROPOSAL)
+ self.ui_state = self.RESET_SCREEN_UI_STATE.POWERWASH_PROPOSAL;
+ self.setDialogView_();
+ if (state == self.RESET_SCREEN_STATE.REVERT_PROMISE) {
+ announceAccessibleMessage(
+ loadTimeData.getString('resetRevertSpinnerMessage'));
+ }
+ }
+ );
+
+ this.context.addObserver(
+ CONTEXT_KEY_IS_OFFICIAL_BUILD,
+ function(isOfficial) {
+ $('powerwash-help-link').setAttribute('hidden', !isOfficial);
+ }
+ );
+ this.context.addObserver(
+ CONTEXT_KEY_ROLLBACK_CHECKED,
+ function(rollbackChecked) {
+ self.setRollbackOptionView();
+ }
+ );
+ this.context.addObserver(
+ CONTEXT_KEY_ROLLBACK_AVAILABLE,
+ function(rollbackAvailable) {
+ self.setRollbackOptionView();
+ }
+ );
+ this.context.addObserver(
+ CONTEXT_KEY_IS_CONFIRMATIONAL_VIEW,
+ function(is_confirmational) {
+ if (is_confirmational) {
+ console.log(self.context.get(CONTEXT_KEY_SCREEN_STATE, 0));
+ if (self.context.get(CONTEXT_KEY_SCREEN_STATE, 0) !=
+ self.RESET_SCREEN_STATE.POWERWASH_PROPOSAL)
+ return;
+ console.log(self);
+ reset.ConfirmResetOverlay.getInstance().initializePage();
+ } else {
+ $('overlay-reset').setAttribute('hidden', true);
+ }
+ }
+ );
},
/**
@@ -47,10 +124,10 @@ login.createScreen('ResetScreen', 'reset', function() {
var restartButton = this.ownerDocument.createElement('button');
restartButton.id = 'reset-restart-button';
restartButton.textContent = loadTimeData.getString('resetButtonRestart');
- restartButton.addEventListener('click', function(e) {
- chrome.send('restartOnReset');
- e.stopPropagation();
- });
+ this.declareUserAction(restartButton,
+ { action_id: USER_ACTION_RESTART_PRESSED,
+ event: 'click'
+ });
buttons.push(restartButton);
// Button that leads to confirmation pop-up dialog.
@@ -58,24 +135,19 @@ login.createScreen('ResetScreen', 'reset', function() {
toConfirmButton.id = 'reset-toconfirm-button';
toConfirmButton.textContent =
loadTimeData.getString('resetButtonPowerwash');
- toConfirmButton.addEventListener('click', function(e) {
- // change view to confirmational
- reset.ConfirmResetOverlay.getInstance().initializePage();
-
- var resetScreen = $('reset');
- resetScreen.isConfirmational = true;
- chrome.send('showConfirmationOnReset');
- e.stopPropagation();
- });
+ this.declareUserAction(toConfirmButton,
+ { action_id: USER_ACTION_SHOW_CONFIRMATION,
+ event: 'click'
+ });
buttons.push(toConfirmButton);
var cancelButton = this.ownerDocument.createElement('button');
cancelButton.id = 'reset-cancel-button';
cancelButton.textContent = loadTimeData.getString('cancelButton');
- cancelButton.addEventListener('click', function(e) {
- chrome.send('cancelOnReset');
- e.stopPropagation();
- });
+ this.declareUserAction(cancelButton,
+ { action_id: USER_ACTION_CANCEL_RESET,
+ event: 'click'
+ });
buttons.push(cancelButton);
return buttons;
@@ -86,25 +158,25 @@ login.createScreen('ResetScreen', 'reset', function() {
*/
get defaultControl() {
// choose
- if (this.needRestart)
+ if (this.context.get(CONTEXT_KEY_SCREEN_STATE,
+ this.RESET_SCREEN_STATE.RESTART_REQUIRED) ==
+ this.RESET_SCREEN_STATE.RESTART_REQUIRED)
return $('reset-restart-button');
- if (this.isConfirmational)
- if (this.rollbackChecked)
- return $('reset-button');
- else
- return $('reset-toconfirm-button');
- return $('reset-button');
+ if (this.context.get(CONTEXT_KEY_IS_CONFIRMATIONAL_VIEW, false))
+ return $('reset-confirm-commit');
+ return $('reset-toconfirm-button');
},
/**
* Cancels the reset and drops the user back to the login screen.
*/
cancel: function() {
- if (this.isConfirmational) {
- reset.ConfirmResetOverlay.getInstance().handleDismiss_();
+ if (this.context.get(CONTEXT_KEY_IS_CONFIRMATIONAL_VIEW, false)) {
+ $('reset').send(login.Screen.CALLBACK_USER_ACTED,
+ USER_ACTION_RESET_CONFIRM_DISMISSED);
return;
}
- chrome.send('cancelOnReset');
+ this.send(login.Screen.CALLBACK_USER_ACTED, USER_ACTION_CANCEL_RESET);
},
/**
@@ -112,80 +184,55 @@ login.createScreen('ResetScreen', 'reset', function() {
* @param {Object} data Screen init payload.
*/
onBeforeShow: function(data) {
- if (data === undefined)
- return;
-
- this.rollbackChecked = false;
- this.rollbackAvailable = false;
- this.isConfirmational = false;
- this.hasLearnMoreLink = false;
-
- if (!('isOfficialBuild' in data && data['isOfficialBuild']))
- $('powerwash-help-link').setAttribute('hidden', true);
-
- if ('rollbackAvailable' in data)
- this.rollbackAvailable = data['rollbackAvailable'];
-
- if ('restartRequired' in data && data['restartRequired']) {
- this.restartRequired = true;
- this.setDialogView_(this.RESET_SCREEN_UI_STATE.RESTART_REQUIRED);
- } else {
- this.restartRequired = false;
- this.setDialogView_(this.RESET_SCREEN_UI_STATE.POWERWASH_PROPOSAL);
- }
},
/**
* Sets css style for corresponding state of the screen.
- * @param {string} state.
* @private
*/
setDialogView_: function(state) {
+ state = this.ui_state;
var resetOverlay = $('reset-confirm-overlay');
- this.classList.remove('revert-promise-view');
- this.classList.remove('restart-required-view');
- this.classList.remove('powerwash-proposal-view');
- this.classList.remove('rollback-proposal-view');
- resetOverlay.classList.remove('powerwash-proposal-view');
- resetOverlay.classList.remove('rollback-proposal-view');
- if (state == this.RESET_SCREEN_UI_STATE.REVERT_PROMISE) {
- this.classList.add('revert-promise-view');
- } else if (state == this.RESET_SCREEN_UI_STATE.RESTART_REQUIRED) {
- this.classList.add('restart-required-view');
- } else if (state == this.RESET_SCREEN_UI_STATE.POWERWASH_PROPOSAL) {
- this.classList.add('powerwash-proposal-view');
- resetOverlay.classList.add('powerwash-proposal-view');
- } else if (state == this.RESET_SCREEN_UI_STATE.ROLLBACK_PROPOSAL) {
- this.classList.add('rollback-proposal-view');
- resetOverlay.classList.add('rollback-proposal-view');
- } else { // error
- console.error('State ' + state + ' is not supported by setDialogView.');
- }
- },
-
- updateViewOnRollbackCall: function() {
- this.setDialogView_(this.RESET_SCREEN_UI_STATE.REVERT_PROMISE);
- announceAccessibleMessage(
- loadTimeData.getString('resetRevertSpinnerMessage'));
+ this.classList.toggle(
+ 'revert-promise-view',
+ state == this.RESET_SCREEN_UI_STATE.REVERT_PROMISE);
+ this.classList.toggle(
+ 'restart-required-view',
+ state == this.RESET_SCREEN_UI_STATE.RESTART_REQUIRED);
+ this.classList.toggle(
+ 'powerwash-proposal-view',
+ state == this.RESET_SCREEN_UI_STATE.POWERWASH_PROPOSAL);
+ resetOverlay.classList.toggle(
+ 'powerwash-proposal-view',
+ state == this.RESET_SCREEN_UI_STATE.POWERWASH_PROPOSAL);
+ this.classList.toggle(
+ 'rollback-proposal-view',
+ state == this.RESET_SCREEN_UI_STATE.ROLLBACK_PROPOSAL);
+ resetOverlay.classList.toggle(
+ 'rollback-proposal-view',
+ state == this.RESET_SCREEN_UI_STATE.ROLLBACK_PROPOSAL);
},
- showRollbackOption: function() {
- if (this.rollbackChecked || this.isConfirmational)
+ setRollbackOptionView: function() {
+ if (this.context.get(CONTEXT_KEY_IS_CONFIRMATIONAL_VIEW, false))
return;
- $('reset-toconfirm-button').textContent = loadTimeData.getString(
- 'resetButtonPowerwashAndRollback');
- this.setDialogView_(this.RESET_SCREEN_UI_STATE.ROLLBACK_PROPOSAL);
- this.rollbackChecked = true;
- },
-
- hideRollbackOption: function() {
- if (!this.rollbackChecked || this.isConfirmational)
+ if (this.context.get(CONTEXT_KEY_SCREEN_STATE) !=
+ this.RESET_SCREEN_STATE.POWERWASH_PROPOSAL)
return;
- $('reset-toconfirm-button').textContent = loadTimeData.getString(
- 'resetButtonPowerwash');
- this.setDialogView_(this.RESET_SCREEN_UI_STATE.POWERWASH_PROPOSAL);
- this.rollbackChecked = false;
+ if (this.context.get(CONTEXT_KEY_ROLLBACK_AVAILABLE, false) &&
+ this.context.get(CONTEXT_KEY_ROLLBACK_CHECKED, false)) {
+ // show rollback option
+ $('reset-toconfirm-button').textContent = loadTimeData.getString(
+ 'resetButtonPowerwashAndRollback');
+ this.ui_state = this.RESET_SCREEN_UI_STATE.ROLLBACK_PROPOSAL;
+ } else {
+ // hide rollback option
+ $('reset-toconfirm-button').textContent = loadTimeData.getString(
+ 'resetButtonPowerwash');
+ this.ui_state = this.RESET_SCREEN_UI_STATE.POWERWASH_PROPOSAL;
+ }
+ this.setDialogView_();
}
};
});
diff --git a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_reset_confirmation_overlay.js b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_reset_confirmation_overlay.js
index 188acb9e758..b1f3288d2ec 100644
--- a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_reset_confirmation_overlay.js
+++ b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_reset_confirmation_overlay.js
@@ -4,6 +4,7 @@
cr.define('reset', function() {
+ var USER_ACTION_RESET_CONFIRM_DISMISSED = 'reset-confirm-dismissed';
/**
* ResetScreenConfirmationOverlay class
* Encapsulated handling of the 'Confirm reset device' overlay OOBE page.
@@ -20,34 +21,13 @@ cr.define('reset', function() {
*/
initializePage: function() {
var overlay = $('reset-confirm-overlay');
- overlay.addEventListener('cancelOverlay', this.handleDismiss_.bind(this));
-
- $('reset-confirm-dismiss').addEventListener('click', this.handleDismiss_);
- $('reset-confirm-commit').addEventListener('click', this.handleCommit_);
-
+ overlay.addEventListener('cancelOverlay', function(e) {
+ $('reset').send(login.Screen.CALLBACK_USER_ACTED,
+ USER_ACTION_RESET_CONFIRM_DISMISSED);
+ e.stopPropagation();
+ });
$('overlay-reset').removeAttribute('hidden');
},
-
- /**
- * Handles a click on the dismiss button.
- * @param {Event} e The click event.
- */
- handleDismiss_: function(e) {
- $('reset').isConfirmational = false;
- $('overlay-reset').setAttribute('hidden', true);
- e.stopPropagation();
- },
-
- /**
- * Handles a click on the commit button.
- * @param {Event} e The click event.
- */
- handleCommit_: function(e) {
- $('reset').isConfirmational = false;
- chrome.send('powerwashOnReset', [$('reset').rollbackChecked]);
- $('overlay-reset').setAttribute('hidden', true);
- e.stopPropagation();
- },
};
// Export
diff --git a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_terms_of_service.js b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_terms_of_service.js
index 840a49bb736..3e11a158578 100644
--- a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_terms_of_service.js
+++ b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_terms_of_service.js
@@ -77,7 +77,7 @@ login.createScreen('TermsOfServiceScreen', 'terms-of-service',
var acceptButton = this.ownerDocument.createElement('button');
acceptButton.id = 'tos-accept-button';
- acceptButton.disabled = true;
+ acceptButton.disabled = this.classList.contains('tos-loading');
acceptButton.classList.add('preserve-disabled-state');
acceptButton.textContent =
loadTimeData.getString('termsOfServiceAcceptButton');
diff --git a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_update.css b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_update.css
index bd650cfd201..f5223478a70 100644
--- a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_update.css
+++ b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_update.css
@@ -33,7 +33,7 @@
#update #update-cancel-hint {
-webkit-margin-start: 45px;
color: rgb(170, 0, 0);
- margin-top: 15px;
+ margin-top: 30px;
position: absolute;
}
diff --git a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_update.js b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_update.js
index 34c9f036184..1fa14c9797c 100644
--- a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_update.js
+++ b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_update.js
@@ -7,17 +7,56 @@
*/
login.createScreen('UpdateScreen', 'update', function() {
+ var USER_ACTION_CANCEL_UPDATE_SHORTCUT = 'cancel-update';
+ var CONTEXT_KEY_TIME_LEFT_SEC = 'time-left-sec';
+ var CONTEXT_KEY_SHOW_TIME_LEFT = 'show-time-left';
+ var CONTEXT_KEY_UPDATE_MESSAGE = 'update-msg';
+ var CONTEXT_KEY_SHOW_CURTAIN = 'show-curtain';
+ var CONTEXT_KEY_SHOW_PROGRESS_MESSAGE = 'show-progress-msg';
+ var CONTEXT_KEY_PROGRESS = 'progress';
+ var CONTEXT_KEY_PROGRESS_MESSAGE = 'progress-msg';
+ var CONTEXT_KEY_CANCEL_UPDATE_SHORTCUT_ENABLED = 'cancel-update-enabled';
+
return {
- EXTERNAL_API: [
- 'enableUpdateCancel',
- 'setUpdateProgress',
- 'showEstimatedTimeLeft',
- 'setEstimatedTimeLeft',
- 'showProgressMessage',
- 'setProgressMessage',
- 'setUpdateMessage',
- 'showUpdateCurtain'
- ],
+ EXTERNAL_API: [],
+
+ /** @override */
+ decorate: function() {
+ var self = this;
+
+ this.context.addObserver(CONTEXT_KEY_TIME_LEFT_SEC,
+ function(time_left_sec) {
+ self.setEstimatedTimeLeft(time_left_sec);
+ });
+ this.context.addObserver(CONTEXT_KEY_SHOW_TIME_LEFT,
+ function(show_time_left) {
+ self.showEstimatedTimeLeft(show_time_left);
+ });
+ this.context.addObserver(CONTEXT_KEY_UPDATE_MESSAGE,
+ function(update_msg) {
+ self.setUpdateMessage(update_msg);
+ });
+ this.context.addObserver(CONTEXT_KEY_SHOW_CURTAIN,
+ function(show_curtain) {
+ self.showUpdateCurtain(show_curtain);
+ });
+ this.context.addObserver(CONTEXT_KEY_SHOW_PROGRESS_MESSAGE,
+ function(show_progress_msg) {
+ self.showProgressMessage(show_progress_msg);
+ });
+ this.context.addObserver(CONTEXT_KEY_PROGRESS,
+ function(progress) {
+ self.setUpdateProgress(progress);
+ });
+ this.context.addObserver(CONTEXT_KEY_PROGRESS_MESSAGE,
+ function(progress_msg) {
+ self.setProgressMessage(progress_msg);
+ });
+ this.context.addObserver(CONTEXT_KEY_CANCEL_UPDATE_SHORTCUT_ENABLED,
+ function(enabled) {
+ $('update-cancel-hint').hidden = !enabled;
+ });
+ },
/**
* Header text of the screen.
@@ -32,18 +71,12 @@ login.createScreen('UpdateScreen', 'update', function() {
*/
cancel: function() {
// It's safe to act on the accelerator even if it's disabled on official
- // builds, since Chrome will just ignore the message in that case.
+ // builds, since Chrome will just ignore this user action in that case.
var updateCancelHint = $('update-cancel-hint').firstElementChild;
updateCancelHint.textContent =
loadTimeData.getString('cancelledUpdateMessage');
- chrome.send('cancelUpdate');
- },
-
- /**
- * Makes 'press Escape to cancel update' hint visible.
- */
- enableUpdateCancel: function() {
- $('update-cancel-hint').hidden = false;
+ this.send(login.Screen.CALLBACK_USER_ACTED,
+ USER_ACTION_CANCEL_UPDATE_SHORTCUT);
},
/**
diff --git a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_user_image.css b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_user_image.css
index c5e6a097223..e753aa26eec 100644
--- a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_user_image.css
+++ b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_user_image.css
@@ -96,6 +96,33 @@
top: 14px;
}
+.image-loading.user-image .new-gaia-flow #user-image {
+ height: 528px; /* Should be the same as #gaia-signin height. */
+ padding: 0 0 0;
+ width: 448px; /* Should be the same as #gaia-signin width. */
+}
+
+.new-gaia-flow #user-image.loading {
+ min-height: 528px; /* Should be the same as #gaia-signin height. */
+ width: 448px; /* Should be the same as #gaia-signin width. */
+}
+
+.new-gaia-flow #old-flow-throbber {
+ display: none;
+}
+
+#new-flow-throbber {
+ display: none;
+}
+
+.new-gaia-flow #new-flow-throbber {
+ display: block;
+}
+
+.image-loading.user-image .new-gaia-flow #step-logo {
+ display: none;
+}
+
html[dir=rtl] #user-image-preview {
float: left;
}
@@ -173,7 +200,7 @@ html[dir=rtl] #user-image-preview {
#flip-photo {
-webkit-transition: opacity 75ms linear;
- background: url('chrome://theme/IDR_MIRROR_FLIP') no-repeat;
+ background: url(chrome://theme/IDR_MIRROR_FLIP) no-repeat;
border: none;
bottom: 44px; /* 8px + image bottom. */
display: block;
@@ -211,13 +238,13 @@ html[dir=rtl] #flip-photo {
}
.camera:not(.live) #discard-photo {
- background: url('chrome://theme/IDR_USER_IMAGE_RECYCLE')
+ background: url(chrome://theme/IDR_USER_IMAGE_RECYCLE)
no-repeat center center;
display: block;
}
.camera.live.online #take-photo {
- background: url('chrome://theme/IDR_USER_IMAGE_CAPTURE')
+ background: url(chrome://theme/IDR_USER_IMAGE_CAPTURE)
no-repeat center -1px;
display: block;
}
diff --git a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_user_image.html b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_user_image.html
index cf9461fcf06..d51a9938462 100644
--- a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_user_image.html
+++ b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_user_image.html
@@ -35,7 +35,12 @@
</div>
<div id="user-image-controls" class="step-controls"></div>
<div id="user-images-loading" class="step-loading">
- <div class="throbber"></div>
- <div i18n-content="syncingPreferences"></div>
+ <div id="old-flow-throbber" class="step-loading">
+ <div class="throbber"></div>
+ <div i18n-content="syncingPreferences"></div>
+ </div>
+ <throbber-notice i18n-values="text:syncingPreferencesNewGaia"
+ id="new-flow-throbber">
+ </throbber-notice>
</div>
</div>
diff --git a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_user_image.js b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_user_image.js
index 83c1203a05c..6ac5d341163 100644
--- a/chromium/chrome/browser/resources/chromeos/login/oobe_screen_user_image.js
+++ b/chromium/chrome/browser/resources/chromeos/login/oobe_screen_user_image.js
@@ -6,64 +6,19 @@
* @fileoverview Oobe user image screen implementation.
*/
-cr.define('login', function() {
+login.createScreen('UserImageScreen', 'user-image', function() {
+ var CONTEXT_KEY_IS_CAMERA_PRESENT = 'isCameraPresent';
+ var CONTEXT_KEY_SELECTED_IMAGE_URL = 'selectedImageURL';
+ var CONTEXT_KEY_PROFILE_PICTURE_DATA_URL = 'profilePictureDataURL';
+
var UserImagesGrid = options.UserImagesGrid;
var ButtonImages = UserImagesGrid.ButtonImages;
- /**
- * Array of button URLs used on this page.
- * @type {Array.<string>}
- * @const
- */
- var ButtonImageUrls = [
- ButtonImages.TAKE_PHOTO
- ];
-
- /**
- * Whether the web camera item should be preselected, if available.
- * @type {boolean}
- * @const
- */
- var PRESELECT_CAMERA = false;
-
- /**
- * Creates a new OOBE screen div.
- * @constructor
- * @extends {HTMLDivElement}
- */
- var UserImageScreen = cr.ui.define(login.Screen);
-
- /**
- * Registers with Oobe.
- * @param {boolean} lazyInit If true, screen is decorated on first show.
- */
- UserImageScreen.register = function(lazyInit) {
- var screen = $('user-image');
- if (lazyInit) {
- screen.__proto__ = UserImageScreen.prototype;
- screen.deferredDecorate = function() {
- UserImageScreen.decorate(screen);
- };
- } else {
- UserImageScreen.decorate(screen);
- }
- Oobe.getInstance().registerScreen(screen);
- };
-
- UserImageScreen.prototype = {
- __proto__: login.Screen.prototype,
-
- /**
- * Currently selected user image index (take photo button is with zero
- * index).
- * @type {number}
- */
- selectedUserImage_: -1,
-
- /**
- * URL for profile picture.
- */
- profileImageUrl_: null,
+ return {
+ EXTERNAL_API: [
+ 'setDefaultImages',
+ 'hideCurtain'
+ ],
/** @override */
decorate: function(element) {
@@ -129,6 +84,22 @@ cr.define('login', function() {
previewElement.classList.remove('animation');
});
+ var self = this;
+ this.context.addObserver(CONTEXT_KEY_IS_CAMERA_PRESENT,
+ function(present) {
+ $('user-image-grid').cameraPresent = present;
+ });
+ this.context.addObserver(CONTEXT_KEY_SELECTED_IMAGE_URL,
+ this.setSelectedImage_);
+ this.context.addObserver(CONTEXT_KEY_PROFILE_PICTURE_DATA_URL,
+ function(url) {
+ self.profileImageLoading = false;
+ if (url) {
+ self.profileImage_ =
+ $('user-image-grid').updateItem(self.profileImage_, url);
+ }
+ });
+
this.updateLocalizedContent();
chrome.send('getImages');
@@ -306,6 +277,7 @@ cr.define('login', function() {
*/
onBeforeShow: function(data) {
Oobe.getInstance().headerHidden = true;
+ $('oobe').classList.add('image-loading');
var imageGrid = $('user-image-grid');
imageGrid.updateAndFocus();
chrome.send('onUserImageScreenShown');
@@ -316,6 +288,7 @@ cr.define('login', function() {
*/
onBeforeHide: function() {
$('user-image-grid').stopCamera();
+ $('oobe').classList.remove('image-loading');
},
/**
@@ -333,54 +306,14 @@ cr.define('login', function() {
},
/**
- * Updates user profile image.
- * @param {?string} imageUrl Image encoded as data URL. If null, user has
- * the default profile image, which we don't want to show.
- * @private
- */
- setProfileImage_: function(imageUrl) {
- this.profileImageLoading = false;
- this.profileImageUrl_ = imageUrl;
- if (imageUrl !== null) {
- this.profileImage_ =
- $('user-image-grid').updateItem(this.profileImage_, imageUrl);
- }
- },
-
- /**
- * @param {boolean} present Whether camera is detected.
- */
- setCameraPresent_: function(present) {
- $('user-image-grid').cameraPresent = present;
- },
-
- /**
- * Controls the profile image as one of image options.
- * @param {enabled} Whether profile image option should be displayed.
- * @private
- */
- setProfilePictureEnabled_: function(enabled) {
- var imageGrid = $('user-image-grid');
- if (enabled) {
- } else {
- imageGrid.removeItem(this.profileImage_);
- }
- },
-
- /**
* Appends default images to the image grid. Should only be called once.
- * @param {Array.<{url: string, author: string, website: string}>} images
+ * @param {Array<{url: string, author: string, website: string}>} images
* An array of default images data, including URL, author and website.
- * @private
*/
- setDefaultImages_: function(imagesData) {
- var imageGrid = $('user-image-grid');
- for (var i = 0, data; data = imagesData[i]; i++) {
- var item = imageGrid.addItem(data.url, data.title);
- item.type = 'default';
- item.author = data.author || '';
- item.website = data.website || '';
- }
+ setDefaultImages: function(imagesData) {
+ $('user-image-grid').setDefaultImages(imagesData);
+ this.setSelectedImage_(
+ this.context.get(CONTEXT_KEY_SELECTED_IMAGE_URL, ''));
chrome.send('screenReady');
},
@@ -390,6 +323,8 @@ cr.define('login', function() {
* @private
*/
setSelectedImage_: function(url) {
+ if (!url)
+ return;
var imageGrid = $('user-image-grid');
imageGrid.selectedItemUrl = url;
imageGrid.focus();
@@ -397,10 +332,10 @@ cr.define('login', function() {
/**
* Hides curtain with spinner.
- * @private
*/
- hideCurtain_: function() {
+ hideCurtain: function() {
this.classList.remove('loading');
+ $('oobe').classList.remove('image-loading');
Oobe.getInstance().updateScreenSize(this);
},
@@ -440,21 +375,6 @@ cr.define('login', function() {
[imageGrid.selectedItemUrl,
imageGrid.selectionType,
!imageGrid.inProgramSelection]);
- },
- };
-
- // Forward public APIs to private implementations.
- cr.makePublic(UserImageScreen, [
- 'setDefaultImages',
- 'setCameraPresent',
- 'setProfilePictureEnabled',
- 'setProfileImage',
- 'setSelectedImage',
- 'hideCurtain'
- ], 'user-image');
-
- return {
- UserImageScreen: UserImageScreen
+ }
};
});
-
diff --git a/chromium/chrome/browser/resources/chromeos/login/oobe_screens.html b/chromium/chrome/browser/resources/chromeos/login/oobe_screens.html
index b71fc5b3f70..508eea33264 100644
--- a/chromium/chrome/browser/resources/chromeos/login/oobe_screens.html
+++ b/chromium/chrome/browser/resources/chromeos/login/oobe_screens.html
@@ -1,5 +1,6 @@
<include src="oobe_screen_eula.html">
<include src="oobe_screen_network.html">
+<include src="oobe_screen_enable_debugging.html">
<include src="oobe_screen_reset.html">
<include src="oobe_screen_autolaunch.html">
<include src="oobe_screen_enable_kiosk.html">
diff --git a/chromium/chrome/browser/resources/chromeos/login/pairing_device_list.css b/chromium/chrome/browser/resources/chromeos/login/pairing_device_list.css
index c51e95d7327..9075c99afe9 100644
--- a/chromium/chrome/browser/resources/chromeos/login/pairing_device_list.css
+++ b/chromium/chrome/browser/resources/chromeos/login/pairing_device_list.css
@@ -47,7 +47,7 @@ core-item:not(:last-child)::after {
}
:host([connecting]) core-item.core-selected .throbber {
- background: url('chrome://resources/images/throbber.svg') no-repeat;
+ background: url(chrome://resources/images/throbber.svg) no-repeat;
background-size: cover;
display: inline-block;
height: 25px;
diff --git a/chromium/chrome/browser/resources/chromeos/login/saml_confirm_password.css b/chromium/chrome/browser/resources/chromeos/login/saml_confirm_password.css
new file mode 100644
index 00000000000..eab03e13d29
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/login/saml_confirm_password.css
@@ -0,0 +1,22 @@
+/* Copyright 2015 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.
+ */
+
+#closeButton {
+ color: white;
+ position: absolute;
+ right: 10px;
+ top: 10px;
+ z-index: 1;
+}
+
+:host-context(html[dir=rtl]) #closeButton {
+ left: 10px;
+ right: auto;
+}
+
+#cancelConfirmDlg {
+ position: absolute;
+ width: 384px;
+}
diff --git a/chromium/chrome/browser/resources/chromeos/login/saml_confirm_password.html b/chromium/chrome/browser/resources/chromeos/login/saml_confirm_password.html
new file mode 100644
index 00000000000..47800e2600f
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/login/saml_confirm_password.html
@@ -0,0 +1,77 @@
+<link rel="import" href="chrome://resources/polymer/polymer/polymer.html">
+<link rel="import" href="chrome://resources/polymer/polymer/layout.html">
+<link rel="import" href="chrome://resources/polymer/core-animated-pages/core-animated-pages.html">
+<link rel="import" href="chrome://resources/polymer/core-animated-pages/transitions/cross-fade.html">
+
+<!--
+ SAML password confirmation UI for the New Gaia flow.
+ Contains two cards with a fade transition between them:
+ 1. Password input form.
+ 2. Spinner with notice "Please wait";
+
+ Example:
+ <saml-confirm-password id="saml-confirm-password" hidden>
+ </saml-confirm-password>
+
+ Attributes:
+ 'email' - Displayed email in header.
+
+ Events:
+ 'passwordEnter' - Fired when user enters password. Fires with an argument
+ |password|.
+ 'cancel' - Fired when user presses the X-button and then presses YES
+ in the cancel confirmation dialog.
+
+ Methods:
+ 'invalidate' - Mark password input as invalid.
+ 'reset' - Reset element, switches to the first screen, closes
+ the confirm dialog, displays the close button,
+ empties password field and enables buttons.
+ 'focus' - If the current card is the first one, focuses password input.
+-->
+<polymer-element name="saml-confirm-password" vertical layout
+ attributes="email">
+ <template>
+ <link rel="stylesheet" href="saml_confirm_password.css">
+ <core-animated-pages id="animatedPages" transitions="cross-fade-all" flex
+ on-core-animated-pages-transition-end="{{onTransitionEnd}}">
+ <section flex>
+ <gaia-card id="confirmPasswordCard">
+ <gaia-header class="header" email="{{email}}">
+ </gaia-header>
+ <div horizontal layout center class="footer gaia-body-text">
+ <p i18n-content="confirmPasswordTitle">
+ </p>
+ </div>
+ <gaia-input-form id="inputForm" class="footer"
+ on-submit="{{onPasswordSubmitted}}"
+ i18n-values="buttonText:nextButtonText">
+ <gaia-input id="passwordInput" type="password" required
+ i18n-values="error:confirmPasswordIncorrectPassword;
+ label:confirmPasswordLabel;">
+ </gaia-input>
+ </gaia-input-form>
+ <paper-action-dialog class="footer" id="cancelConfirmDlg"
+ layered="false">
+ <p i18n-content="accountSetupCancelDialogTitle"></p>
+ <gaia-paper-button class="dialog-action-button" affirmative
+ autofocus i18n-content="accountSetupCancelDialogNo">
+ </gaia-paper-button>
+ <gaia-paper-button id="confirmCancel" on-tap="{{onConfirmCancel}}"
+ class="dialog-action-button" affirmative
+ i18n-content="accountSetupCancelDialogYes">
+ </gaia-paper-button>
+ </paper-action-dialog>
+ </gaia-card>
+ </section>
+ <section flex vertical layout center-justified>
+ <throbber-notice i18n-values="text:gaiaLoadingNewGaia" self-center>
+ </throbber-notice>
+ </section>
+ </core-animated-pages>
+ <button id="closeButton" is="gaia-icon-button" icon="close"
+ on-click="{{onClose}}" i18n-values="aria-label:closeButton"
+ tabindex="0">
+ </button>
+ </template>
+</polymer-element>
diff --git a/chromium/chrome/browser/resources/chromeos/login/saml_confirm_password.js b/chromium/chrome/browser/resources/chromeos/login/saml_confirm_password.js
new file mode 100644
index 00000000000..5e6110a2e86
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/login/saml_confirm_password.js
@@ -0,0 +1,57 @@
+/* Copyright 2015 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.
+ */
+
+Polymer('saml-confirm-password', {
+ onClose: function() {
+ this.disabled = true;
+ this.$.cancelConfirmDlg.toggle();
+ },
+
+ onConfirmCancel: function() {
+ this.fire('cancel');
+ },
+
+ reset: function() {
+ this.$.cancelConfirmDlg.close();
+ this.disabled = false;
+ this.$.closeButton.hidden = false;
+ this.$.animatedPages.selected = 0;
+ this.$.passwordInput.value = '';
+ },
+
+ invalidate: function() {
+ this.$.passwordInput.isInvalid = true;
+ },
+
+ focus: function() {
+ if (this.$.animatedPages.selected == 0)
+ this.$.passwordInput.focus();
+ },
+
+ onPasswordSubmitted: function() {
+ var inputPassword = this.$.passwordInput.value;
+ this.$.passwordInput.value = '';
+ if (!inputPassword) {
+ this.invalidate();
+ } else {
+ this.$.animatedPages.selected += 1;
+ this.$.closeButton.hidden = true;
+ this.fire('passwordEnter', {password: inputPassword});
+ }
+ },
+
+ set disabled(value) {
+ this.$.confirmPasswordCard.classList.toggle('full-disabled', value);
+ this.$.inputForm.disabled = value;
+ this.$.closeButton.disabled = value;
+ },
+
+ ready: function() {
+ this.$.cancelConfirmDlg.addEventListener('core-overlay-close-completed',
+ function() {
+ this.disabled = false;
+ }.bind(this));
+ }
+});
diff --git a/chromium/chrome/browser/resources/chromeos/login/screen_confirm_password.css b/chromium/chrome/browser/resources/chromeos/login/screen_confirm_password.css
index 9fc699f87a6..0b29e02f93a 100644
--- a/chromium/chrome/browser/resources/chromeos/login/screen_confirm_password.css
+++ b/chromium/chrome/browser/resources/chromeos/login/screen_confirm_password.css
@@ -47,4 +47,15 @@
margin-top: 1em;
}
+.confirm-password .new-gaia-flow #step-logo {
+ display: none;
+}
+
+.new-gaia-flow #saml-confirm-password,
+.new-gaia-flow #confirm-password {
+ height: 528px;
+ padding: 0;
+ width: 448px;
+}
+
<include src="gaia.css">
diff --git a/chromium/chrome/browser/resources/chromeos/login/screen_confirm_password.html b/chromium/chrome/browser/resources/chromeos/login/screen_confirm_password.html
index d5687f236f1..51b6841d45a 100644
--- a/chromium/chrome/browser/resources/chromeos/login/screen_confirm_password.html
+++ b/chromium/chrome/browser/resources/chromeos/login/screen_confirm_password.html
@@ -1,5 +1,6 @@
+<link rel="import" href="chrome://oobe/custom_elements.html">
<div id="confirm-password" class="step hidden" hidden>
- <div class="step-contents">
+ <div id="saml-confirm-password-contents" class="step-contents">
<div id="confirm-password-main">
<p id="confirm-password-title" i18n-content="confirmPasswordTitle"></p>
<div id="confirm-password-input-container">
@@ -19,4 +20,6 @@
<p class="signin-text" i18n-content="confirmPasswordText"></p>
</div>
</div>
+ <saml-confirm-password id="saml-confirm-password" hidden>
+ </saml-confirm-password>
</div>
diff --git a/chromium/chrome/browser/resources/chromeos/login/screen_confirm_password.js b/chromium/chrome/browser/resources/chromeos/login/screen_confirm_password.js
index 2ca191ff296..2c287408a5e 100644
--- a/chromium/chrome/browser/resources/chromeos/login/screen_confirm_password.js
+++ b/chromium/chrome/browser/resources/chromeos/login/screen_confirm_password.js
@@ -24,6 +24,14 @@ login.createScreen('ConfirmPasswordScreen', 'confirm-password', function() {
'keydown', this.onPasswordFieldKeyDown_.bind(this));
$('confirm-password-confirm-button').addEventListener(
'click', this.onConfirmPassword_.bind(this));
+
+ $('saml-confirm-password').addEventListener('cancel', function(e) {
+ Oobe.showScreen({id: SCREEN_ACCOUNT_PICKER});
+ Oobe.resetSigninUI(true);
+ });
+ $('saml-confirm-password').addEventListener('passwordEnter', function(e) {
+ this.callback_(e.detail.password);
+ }.bind(this));
},
get defaultControl() {
@@ -36,6 +44,18 @@ login.createScreen('ConfirmPasswordScreen', 'confirm-password', function() {
SIGNIN_UI_STATE.SAML_PASSWORD_CONFIRM;
},
+ /** @override */
+ onAfterShow: function(data) {
+ if (Oobe.isNewGaiaFlow())
+ $('saml-confirm-password').focus();
+ },
+
+ /** @override */
+ onBeforeHide: function() {
+ if (Oobe.isNewGaiaFlow())
+ $('saml-confirm-password').reset();
+ },
+
/**
* Handle 'keydown' event on password input field.
*/
@@ -53,15 +73,25 @@ login.createScreen('ConfirmPasswordScreen', 'confirm-password', function() {
/**
* Shows the confirm password screen.
+ * @param {string} email The authenticated user's e-mail.
* @param {number} attemptCount Number of attempts tried, starting at 0.
* @param {function(string)} callback The callback to be invoked when the
* screen is dismissed.
*/
- show: function(attemptCount, callback) {
+ show: function(email, attemptCount, callback) {
this.callback_ = callback;
this.classList.toggle('error', attemptCount > 0);
-
- $('confirm-password-input').value = '';
+ if (Oobe.isNewGaiaFlow()) {
+ $('saml-confirm-password-contents').hidden = true;
+ var samlConfirmPassword = $('saml-confirm-password');
+ samlConfirmPassword.reset();
+ samlConfirmPassword.hidden = false;
+ samlConfirmPassword.email = email;
+ if (attemptCount > 0)
+ samlConfirmPassword.invalidate();
+ } else {
+ $('confirm-password-input').value = '';
+ }
Oobe.showScreen({id: SCREEN_CONFIRM_PASSWORD});
$('progress-dots').hidden = true;
}
diff --git a/chromium/chrome/browser/resources/chromeos/login/screen_context_test.html b/chromium/chrome/browser/resources/chromeos/login/screen_context_test.html
index c3971b37d1a..bc77e4bb158 100644
--- a/chromium/chrome/browser/resources/chromeos/login/screen_context_test.html
+++ b/chromium/chrome/browser/resources/chromeos/login/screen_context_test.html
@@ -1,4 +1,4 @@
-<!DOCTYPE html>
+<!doctype html>
<title></title>
<script src="https://cdn.rawgit.com/google/closure-library/master/closure/goog/base.js"></script>
<script src="../../../../../ui/webui/resources/js/cr.js"></script>
diff --git a/chromium/chrome/browser/resources/chromeos/login/screen_device_disabled.css b/chromium/chrome/browser/resources/chromeos/login/screen_device_disabled.css
index 4d8b1b18d99..52e9b9095c5 100644
--- a/chromium/chrome/browser/resources/chromeos/login/screen_device_disabled.css
+++ b/chromium/chrome/browser/resources/chromeos/login/screen_device_disabled.css
@@ -25,7 +25,7 @@
#device-disabled-symbol {
align-self: center;
- background-image: url('chrome://theme/IDR_DEVICE_DISABLED');
+ background-image: url(chrome://theme/IDR_DEVICE_DISABLED);
height: 24px;
width: 24px;
}
@@ -50,17 +50,9 @@
}
#device-disabled-message {
- margin-top: -3px;
+ margin: -3px 0 -5px;
max-height: 400px;
overflow: auto;
text-overflow: ellipsis;
white-space: pre-wrap;
}
-
-#device-disabled-help-link-container {
- margin: 17px 0 -5px;
-}
-
-#device-disabled-help-link {
- color: rgb(85, 149, 243);
-}
diff --git a/chromium/chrome/browser/resources/chromeos/login/screen_device_disabled.html b/chromium/chrome/browser/resources/chromeos/login/screen_device_disabled.html
index 5479dfb8ba5..3b3e088dab0 100644
--- a/chromium/chrome/browser/resources/chromeos/login/screen_device_disabled.html
+++ b/chromium/chrome/browser/resources/chromeos/login/screen_device_disabled.html
@@ -7,10 +7,5 @@
</div>
<div id="device-disabled-bottom-container">
<div id="device-disabled-message"></div>
- <div id="device-disabled-help-link-container">
- <a id="device-disabled-help-link" class="oauth-enroll-link"
- href="#" i18n-content="deviceDisabledHelpLink">
- </a>
- </div>
</div>
</div>
diff --git a/chromium/chrome/browser/resources/chromeos/login/screen_device_disabled.js b/chromium/chrome/browser/resources/chromeos/login/screen_device_disabled.js
index 543bb0549cc..baaf9c3fd09 100644
--- a/chromium/chrome/browser/resources/chromeos/login/screen_device_disabled.js
+++ b/chromium/chrome/browser/resources/chromeos/login/screen_device_disabled.js
@@ -7,8 +7,6 @@
*/
login.createScreen('DeviceDisabledScreen', 'device-disabled', function() {
- /** @const */ var HELP_TOPIC_DEVICE_DISABLING = 4631259;
-
return {
EXTERNAL_API: [
'setEnrollmentDomain',
@@ -23,11 +21,6 @@ login.createScreen('DeviceDisabledScreen', 'device-disabled', function() {
/** @override */
decorate: function() {
this.setEnrollmentDomain(null);
- $('device-disabled-help-link').addEventListener(
- 'click',
- function() {
- chrome.send('launchHelpApp', [HELP_TOPIC_DEVICE_DISABLING]);
- });
},
/**
diff --git a/chromium/chrome/browser/resources/chromeos/login/screen_error_message.js b/chromium/chrome/browser/resources/chromeos/login/screen_error_message.js
index d0c47e5f0d9..561f6ff8cdd 100644
--- a/chromium/chrome/browser/resources/chromeos/login/screen_error_message.js
+++ b/chromium/chrome/browser/resources/chromeos/login/screen_error_message.js
@@ -7,6 +7,20 @@
*/
login.createScreen('ErrorMessageScreen', 'error-message', function() {
+ var CONTEXT_KEY_ERROR_STATE_CODE = 'error-state-code';
+ var CONTEXT_KEY_ERROR_STATE_NETWORK = 'error-state-network';
+ var CONTEXT_KEY_GUEST_SIGNIN_ALLOWED = 'guest-signin-allowed';
+ var CONTEXT_KEY_OFFLINE_SIGNIN_ALLOWED = 'offline-signin-allowed';
+ var CONTEXT_KEY_SHOW_CONNECTING_INDICATOR = 'show-connecting-indicator';
+ var CONTEXT_KEY_UI_STATE = 'ui-state';
+
+ var USER_ACTION_CONFIGURE_CERTS = 'configure-certs';
+ var USER_ACTION_DIAGNOSE = 'diagnose';
+ var USER_ACTION_LAUNCH_OOBE_GUEST = 'launch-oobe-guest';
+ var USER_ACTION_LOCAL_STATE_POWERWASH = 'local-state-error-powerwash';
+ var USER_ACTION_REBOOT = 'reboot';
+ var USER_ACTION_SHOW_CAPTIVE_PORTAL = 'show-captive-portal';
+
// Link which starts guest session for captive portal fixing.
/** @const */ var FIX_CAPTIVE_PORTAL_ID = 'captive-portal-fix-link';
@@ -79,12 +93,38 @@ login.createScreen('ErrorMessageScreen', 'error-message', function() {
decorate: function() {
cr.ui.DropDown.decorate($('offline-networks-list'));
this.updateLocalizedContent();
+
+ var self = this;
+ this.context.addObserver(CONTEXT_KEY_ERROR_STATE_CODE,
+ function(error_state) {
+ self.setErrorState(error_state);
+ });
+ this.context.addObserver(CONTEXT_KEY_ERROR_STATE_NETWORK,
+ function(network) {
+ self.setNetwork_(network);
+ });
+ this.context.addObserver(CONTEXT_KEY_GUEST_SIGNIN_ALLOWED,
+ function(allowed) {
+ self.allowGuestSignin(allowed);
+ });
+ this.context.addObserver(CONTEXT_KEY_OFFLINE_SIGNIN_ALLOWED,
+ function(allowed) {
+ self.allowOfflineLogin(allowed);
+ });
+ this.context.addObserver(CONTEXT_KEY_SHOW_CONNECTING_INDICATOR,
+ function(show) {
+ self.showConnectingIndicator(show);
+ });
+ this.context.addObserver(CONTEXT_KEY_UI_STATE, function(ui_state) {
+ self.setUIState(ui_state);
+ });
},
/**
* Updates localized content of the screen that is not updated via template.
*/
updateLocalizedContent: function() {
+ var self = this;
$('auto-enrollment-offline-message-text').innerHTML =
loadTimeData.getStringF(
'autoEnrollmentOfflineMessageBody',
@@ -102,7 +142,8 @@ login.createScreen('ErrorMessageScreen', 'error-message', function() {
'<a id="' + FIX_CAPTIVE_PORTAL_ID + '" class="signin-link" href="#">',
'</a>');
$(FIX_CAPTIVE_PORTAL_ID).onclick = function() {
- chrome.send('showCaptivePortal');
+ self.send(login.Screen.CALLBACK_USER_ACTED,
+ USER_ACTION_SHOW_CAPTIVE_PORTAL);
};
$('captive-portal-proxy-message-text').innerHTML =
@@ -139,15 +180,18 @@ login.createScreen('ErrorMessageScreen', 'error-message', function() {
'guestSignin',
'<a id="error-guest-signin-link" class="signin-link" href="#">',
'</a>');
- $('error-guest-signin-link').onclick = this.launchGuestSession_;
+ $('error-guest-signin-link').addEventListener(
+ 'click',
+ this.launchGuestSession_.bind(this));
$('error-guest-signin-fix-network').innerHTML = loadTimeData.getStringF(
'guestSigninFixNetwork',
'<a id="error-guest-fix-network-signin-link" class="signin-link" ' +
'href="#">',
'</a>');
- $('error-guest-fix-network-signin-link').onclick =
- this.launchGuestSession_;
+ $('error-guest-fix-network-signin-link').addEventListener(
+ 'click',
+ this.launchGuestSession_.bind(this));
$('error-offline-login').innerHTML = loadTimeData.getStringF(
'offlineLogin',
@@ -160,7 +204,7 @@ login.createScreen('ErrorMessageScreen', 'error-message', function() {
var ellipsis = '';
for (var i = 1; i <= 3; ++i) {
ellipsis +=
- '<span id="connecting-indicator-ellipsis-' + i + '">.</span>';
+ '<span id="connecting-indicator-ellipsis-' + i + '"></span>';
}
$('connecting-indicator').innerHTML =
loadTimeData.getStringF('connectingIndicatorText', ellipsis);
@@ -175,18 +219,6 @@ login.createScreen('ErrorMessageScreen', 'error-message', function() {
onBeforeShow: function(data) {
cr.ui.Oobe.clearErrors();
cr.ui.DropDown.show('offline-networks-list', false);
- if (data === undefined)
- return;
- if ('uiState' in data)
- this.setUIState(data['uiState']);
- if ('errorState' in data && 'network' in data)
- this.setErrorState(data['errorState'], data['network']);
- if ('guestSigninAllowed' in data)
- this.allowGuestSignin(data['guestSigninAllowed']);
- if ('offlineLoginAllowed' in data)
- this.allowOfflineLogin(data['offlineLoginAllowed']);
- if ('showConnectingIndicator' in data)
- this.showConnectingIndicator(data['showConnectingIndicator']);
},
/**
@@ -202,12 +234,13 @@ login.createScreen('ErrorMessageScreen', 'error-message', function() {
*/
get buttons() {
var buttons = [];
+ var self = this;
var rebootButton = this.ownerDocument.createElement('button');
rebootButton.textContent = loadTimeData.getString('rebootButton');
rebootButton.classList.add('show-with-ui-state-kiosk-mode');
rebootButton.addEventListener('click', function(e) {
- chrome.send('rebootButtonClicked');
+ self.send(login.Screen.CALLBACK_USER_ACTED, USER_ACTION_REBOOT);
e.stopPropagation();
});
buttons.push(rebootButton);
@@ -216,7 +249,7 @@ login.createScreen('ErrorMessageScreen', 'error-message', function() {
diagnoseButton.textContent = loadTimeData.getString('diagnoseButton');
diagnoseButton.classList.add('show-with-ui-state-kiosk-mode');
diagnoseButton.addEventListener('click', function(e) {
- chrome.send('diagnoseButtonClicked');
+ self.send(login.Screen.CALLBACK_USER_ACTED, USER_ACTION_DIAGNOSE);
e.stopPropagation();
});
buttons.push(diagnoseButton);
@@ -225,7 +258,8 @@ login.createScreen('ErrorMessageScreen', 'error-message', function() {
certsButton.textContent = loadTimeData.getString('configureCertsButton');
certsButton.classList.add('show-with-ui-state-kiosk-mode');
certsButton.addEventListener('click', function(e) {
- chrome.send('configureCertsClicked');
+ self.send(login.Screen.CALLBACK_USER_ACTED,
+ USER_ACTION_CONFIGURE_CERTS);
e.stopPropagation();
});
buttons.push(certsButton);
@@ -261,7 +295,8 @@ login.createScreen('ErrorMessageScreen', 'error-message', function() {
loadTimeData.getString('localStateErrorPowerwashButton');
powerwashButton.classList.add('show-with-ui-state-local-state-error');
powerwashButton.addEventListener('click', function(e) {
- chrome.send('localStateErrorPowerwashButtonClicked');
+ self.send(login.Screen.CALLBACK_USER_ACTED,
+ USER_ACTION_LOCAL_STATE_POWERWASH);
e.stopPropagation();
});
buttons.push(powerwashButton);
@@ -291,17 +326,25 @@ login.createScreen('ErrorMessageScreen', 'error-message', function() {
/**
* Sets current error state of the screen.
* @param {string} error_state New error state of the screen.
- * @param {string} network Name of the current network
* @private
*/
- setErrorState_: function(error_state, network) {
+ setErrorState_: function(error_state) {
this.classList.remove(this.error_state);
+ this.error_state = error_state;
+ this.classList.add(this.error_state);
+ this.onContentChange_();
+ },
+
+ /**
+ * Sets network.
+ * @param {string} network Name of the current network
+ * @private
+ */
+ setNetwork_: function(network) {
var networkNameElems =
document.getElementsByClassName(CURRENT_NETWORK_NAME_CLASS);
for (var i = 0; i < networkNameElems.length; ++i)
networkNameElems[i].textContent = network;
- this.error_state = error_state;
- this.classList.add(this.error_state);
this.onContentChange_();
},
@@ -318,8 +361,12 @@ login.createScreen('ErrorMessageScreen', 'error-message', function() {
* @private
*/
launchGuestSession_: function() {
- chrome.send(Oobe.getInstance().isOobeUI() ?
- 'launchOobeGuestSession' : 'launchIncognito');
+ if (Oobe.getInstance().isOobeUI()) {
+ this.send(login.Screen.CALLBACK_USER_ACTED,
+ USER_ACTION_LAUNCH_OOBE_GUEST);
+ } else {
+ chrome.send('launchIncognito');
+ }
},
/**
@@ -352,11 +399,10 @@ login.createScreen('ErrorMessageScreen', 'error-message', function() {
/**
* Sets current error state of the screen.
* @param {number} error_state New error state of the screen.
- * @param {string} network Name of the current network
* @private
*/
- setErrorState: function(error_state, network) {
- this.setErrorState_(ERROR_STATES[error_state], network);
+ setErrorState: function(error_state) {
+ this.setErrorState_(ERROR_STATES[error_state]);
},
/**
diff --git a/chromium/chrome/browser/resources/chromeos/login/screen_gaia_signin.css b/chromium/chrome/browser/resources/chromeos/login/screen_gaia_signin.css
index e6e4725f42d..840f74bbee6 100644
--- a/chromium/chrome/browser/resources/chromeos/login/screen_gaia_signin.css
+++ b/chromium/chrome/browser/resources/chromeos/login/screen_gaia_signin.css
@@ -9,14 +9,37 @@
width: 722px; /* Should be the same as #user-image.loading width. */
}
-#gaia-signin:not(.saml).no-right-panel {
+#gaia-signin:not(.full-width).no-right-panel {
width: 522px;
}
-#gaia-signin.saml {
+.new-gaia-flow #gaia-signin {
+ height: 528px;
+ padding: 0 0 0;
+ width: 448px;
+}
+
+.new-gaia-flow .throbber {
+ display: none;
+}
+
+throbber-notice {
+ display: none;
+}
+
+.new-gaia-flow throbber-notice {
+ display: block;
+}
+
+#gaia-signin.full-width {
padding: 75px 0 0;
}
+.new-gaia-flow #gaia-signin.full-width {
+ padding-top: 47px;
+ width: 562px;
+}
+
#signin-right {
-webkit-margin-start: 30px;
margin-top: 80px;
@@ -37,8 +60,53 @@
}
}
-.no-right-panel #signin-right,
-.saml #signin-right {
+.new-gaia-flow #signin-right,
+.no-right-panel #signin-right {
+ display: none;
+}
+
+#close-button-item,
+#back-button-item {
+ color: white;
+ position: absolute;
+ top: 10px;
+ z-index: 1;
+}
+
+#close-button-item:disabled,
+#back-button-item:disabled {
+ color: rgb(127, 127, 127);
+}
+
+#close-button-item {
+ right: 10px;
+}
+
+#back-button-item {
+ left: 10px;
+}
+
+html[dir=rtl] #close-button-item {
+ left: 10px;
+ right: auto;
+}
+
+html[dir=rtl] #back-button-item {
+ -webkit-transform: scaleX(-1);
+ left: auto;
+ right: 10px;
+}
+
+.loading #close-button-item,
+.new-gaia-flow .full-width #close-button-item {
+ color: rgba(0, 0, 0, .54);
+}
+
+.new-gaia-flow .full-width #back-button-item {
+ display: none;
+}
+
+.loading.auth-completed #close-button-item {
display: none;
}
@@ -64,8 +132,12 @@
height: 100%;
}
-.saml #gaia-signin-form-container,
-.saml #signin-frame {
+.new-gaia-flow #gaia-signin-form-container {
+ width: 100%;
+}
+
+.full-width #signin-frame,
+.full-width #gaia-signin-form-container {
width: 100%;
}
@@ -79,8 +151,9 @@
width: 1px;
}
-.no-right-panel #gaia-signin-divider,
-.saml #gaia-signin-divider {
+.gaia-signin .new-gaia-flow #step-logo,
+.gaia-signin .new-gaia-flow #gaia-signin-divider,
+.no-right-panel #gaia-signin-divider {
display: none;
}
@@ -91,9 +164,23 @@
width: 400px;
}
+webview#signin-frame {
+ -webkit-transition: opacity 500ms ease-in;
+ display: block;
+ margin-left: 0;
+ opacity: 0;
+ overflow: hidden;
+ padding: 0;
+ width: 448px;
+}
+
+webview#signin-frame.show {
+ -webkit-transition: opacity 500ms ease-out;
+ opacity: 1;
+}
+
/* Simpler alignment if no right panel. */
-.no-right-panel #signin-frame,
-.saml #signin-frame {
+.no-right-panel #signin-frame {
margin-left: 0;
}
@@ -114,6 +201,10 @@
text-align: center;
}
+.new-gaia-flow #enterprise-info-container {
+ display: none;
+}
+
#enterprise-info-container #enterprise-info {
display: inline-block;
}
@@ -125,7 +216,7 @@
}
#createSupervisedUserSeparator {
- background-color: rgba(128,128,128,0.3);
+ background-color: rgba(128, 128, 128, 0.3);
height: 1px;
width: 200px;
}
@@ -156,6 +247,53 @@
top: 50px;
}
+.new-gaia-flow #saml-notice-container {
+ -webkit-margin-start: 19px;
+ text-align: start;
+ top: 15px;
+}
+
#saml-notice-message {
margin: 0 auto;
}
+
+.new-gaia-flow #saml-notice-message {
+ color: rgb(106, 106, 106);
+ font-size: 13px;
+}
+
+#gaia-whitelist-error {
+ bottom: 0;
+ display: none;
+ left: 0;
+ position: absolute;
+ right: 0;
+ top: 0;
+}
+
+.new-gaia-flow #gaia-whitelist-error {
+ display: block;
+ visibility: hidden;
+}
+
+.new-gaia-flow .whitelist-error #gaia-whitelist-error {
+ visibility: visible;
+}
+
+.new-gaia-flow #gaia-signin.whitelist-error .step-contents {
+ visibility: hidden;
+}
+
+.new-gaia-flow #gaia-signin.whitelist-error .step-loading {
+ visibility: hidden;
+}
+
+html /deep/ #forgotPasswordDlg {
+ position: fixed;
+ top: 200px;
+ width: 350px;
+}
+
+html /deep/ #forgotPasswordDlg gaia-paper-button[autofocus] {
+ color: rgb(66, 133, 244);
+}
diff --git a/chromium/chrome/browser/resources/chromeos/login/screen_gaia_signin.html b/chromium/chrome/browser/resources/chromeos/login/screen_gaia_signin.html
index 64f62caf901..6df41a0bdb2 100644
--- a/chromium/chrome/browser/resources/chromeos/login/screen_gaia_signin.html
+++ b/chromium/chrome/browser/resources/chromeos/login/screen_gaia_signin.html
@@ -1,14 +1,17 @@
+<link rel="import" href="chrome://oobe/custom_elements.html">
+
<div class="step right hidden" id="gaia-signin" role="group"
aria-live="polite" hidden>
<div class="step-contents">
<div id="gaia-signin-form-container">
- <div id="login-box"><!-- Aligned with the login box in iframe --></div>
- <iframe id="signin-frame" name="signin-frame" hidden
- src="about:blank"
- marginwidth="0"
- marginheight="0"
- frameborder="0"
- scrolling="no"></iframe>
+ <div id="login-box"><!-- Aligned with the login box in iframe --></div>
+ <iframe id="signin-frame" name="signin-frame" hidden
+ src="about:blank"
+ marginwidth="0"
+ marginheight="0"
+ frameborder="0"
+ scrolling="no"></iframe>
+ <offline-gaia id="offline-gaia" hidden></offline-gaia>
</div>
<div id="gaia-signin-divider" class="signin-divider" hidden></div>
<div id="signin-right" hidden>
@@ -18,7 +21,7 @@
<div id="createSupervisedUserPane" class="signin-text">
<div id="createSupervisedUserSeparator"></div>
<div id="createSupervisedUserLogo">
- <img src="chrome://theme/IDR_SUPERVISED_USER_ICON">
+ <img src="chrome://theme/IDR_LEGACY_SUPERVISED_USER_ICON">
<span i18n-content="createSupervisedUserFeatureName"></span>
</div>
<div id="createSupervisedUserLinkPlaceholder"></div>
@@ -29,11 +32,20 @@
</div>
<div id="gaia-loading" class="step-loading">
<div class="throbber"></div>
+ <throbber-notice i18n-values="text:gaiaLoadingNewGaia"></throbber-notice>
</div>
+ <notification-card id="gaia-whitelist-error" type="fail"
+ i18n-values="buttonLabel:tryAgainButton;
+ linkLabel:learnMoreButton">
+ </notification-card>
<div id="enterprise-info-container" hidden>
<include src="enterprise_info.html">
</div>
<div id="saml-notice-container" hidden>
<span id="saml-notice-message"></span>
</div>
+ <button id="back-button-item" is="gaia-icon-button" icon="arrow-back" hidden
+ i18n-values="aria-label:backButton" tabindex="0"></button>
+ <button id="close-button-item" is="gaia-icon-button" icon="close" hidden
+ i18n-values="aria-label:closeButton" tabindex="0"></button>
</div>
diff --git a/chromium/chrome/browser/resources/chromeos/login/screen_gaia_signin.js b/chromium/chrome/browser/resources/chromeos/login/screen_gaia_signin.js
index ff044033a19..388a8a55750 100644
--- a/chromium/chrome/browser/resources/chromeos/login/screen_gaia_signin.js
+++ b/chromium/chrome/browser/resources/chromeos/login/screen_gaia_signin.js
@@ -6,25 +6,33 @@
* @fileoverview Oobe signin screen implementation.
*/
-<include src="../../gaia_auth_host/gaia_auth_host.js">
-
login.createScreen('GaiaSigninScreen', 'gaia-signin', function() {
// Gaia loading time after which error message must be displayed and
// lazy portal check should be fired.
/** @const */ var GAIA_LOADING_PORTAL_SUSSPECT_TIME_SEC = 7;
+ // GAIA animation guard timer. Started when GAIA page is loaded
+ // (Authenticator 'ready' event) and is intended to guard against edge cases
+ // when 'showView' message is not generated/received.
+ /** @const */ var GAIA_ANIMATION_GUARD_MILLISEC = 300;
+
// Maximum Gaia loading time in seconds.
/** @const */ var MAX_GAIA_LOADING_TIME_SEC = 60;
/** @const */ var HELP_TOPIC_ENTERPRISE_REPORTING = 2535613;
+ // The help topic regarding user not being in the whitelist.
+ /** @const */ var HELP_CANT_ACCESS_ACCOUNT = 188036;
+
return {
EXTERNAL_API: [
'loadAuthExtension',
'updateAuthExtension',
'doReload',
+ 'onWebviewError',
'onFrameError',
- 'updateCancelButtonState'
+ 'updateCancelButtonState',
+ 'showWhitelistCheckFailedError',
],
/**
@@ -49,6 +57,12 @@ login.createScreen('GaiaSigninScreen', 'gaia-signin', function() {
isLocal_: false,
/**
+ * Whether new Gaia flow is active.
+ * @type {boolean}
+ */
+ isNewGaiaFlow: false,
+
+ /**
* Email of the user, which is logging in using offline mode.
* @type {string}
*/
@@ -69,6 +83,22 @@ login.createScreen('GaiaSigninScreen', 'gaia-signin', function() {
loadingTimer_: undefined,
/**
+ * Timer id of a guard timer that is fired in case 'showView' message
+ * is not received from GAIA.
+ * @type {number}
+ * @private
+ */
+ loadAnimationGuardTimer_: undefined,
+
+ /**
+ * Whether we've processed 'showView' message - either from GAIA or from
+ * guard timer.
+ * @type {boolean}
+ * @private
+ */
+ showViewProcessed_: undefined,
+
+ /**
* Whether user can cancel Gaia screen.
* @type {boolean}
* @private
@@ -88,11 +118,40 @@ login.createScreen('GaiaSigninScreen', 'gaia-signin', function() {
*/
samlPasswordConfirmAttempt_: 0,
+ /**
+ * Whether we should show webview based signin.
+ * @type {boolean}
+ * @private
+ */
+ isWebviewSignin: false,
+
/** @override */
decorate: function() {
- this.gaiaAuthHost_ = new cr.login.GaiaAuthHost($('signin-frame'));
+ this.isWebviewSignin = loadTimeData.getValue('isWebviewSignin');
+ if (this.isWebviewSignin) {
+ // Replace iframe with webview.
+ var webview = this.ownerDocument.createElement('webview');
+ webview.id = 'signin-frame';
+ webview.name = 'signin-frame';
+ webview.hidden = true;
+ $('signin-frame').parentNode.replaceChild(webview, $('signin-frame'));
+ this.gaiaAuthHost_ = new cr.login.GaiaAuthHost(webview);
+
+ $('offline-gaia').addEventListener('authCompleted',
+ this.onAuthCompletedMessage_.bind(this));
+ } else {
+ this.gaiaAuthHost_ = new cr.login.GaiaAuthHost($('signin-frame'));
+ }
this.gaiaAuthHost_.addEventListener(
'ready', this.onAuthReady_.bind(this));
+ this.gaiaAuthHost_.addEventListener(
+ 'dialogShown', this.onDialogShown_.bind(this));
+ this.gaiaAuthHost_.addEventListener(
+ 'dialogHidden', this.onDialogHidden_.bind(this));
+ this.gaiaAuthHost_.addEventListener(
+ 'backButton', this.onBackButton_.bind(this));
+ this.gaiaAuthHost_.addEventListener(
+ 'showView', this.onShowView_.bind(this));
this.gaiaAuthHost_.confirmPasswordCallback =
this.onAuthConfirmPassword_.bind(this);
this.gaiaAuthHost_.noPasswordCallback =
@@ -103,14 +162,38 @@ login.createScreen('GaiaSigninScreen', 'gaia-signin', function() {
this.missingGaiaInfo_.bind(this);
this.gaiaAuthHost_.samlApiUsedCallback =
this.samlApiUsed_.bind(this);
+ this.gaiaAuthHost_.addEventListener('authDomainChange',
+ this.onAuthDomainChange_.bind(this));
this.gaiaAuthHost_.addEventListener('authFlowChange',
this.onAuthFlowChange_.bind(this));
+ this.gaiaAuthHost_.addEventListener('authCompleted',
+ this.onAuthCompletedMessage_.bind(this));
+ this.gaiaAuthHost_.addEventListener('loadAbort',
+ this.onLoadAbortMessage_.bind(this));
$('enterprise-info-hint-link').addEventListener('click', function(e) {
chrome.send('launchHelpApp', [HELP_TOPIC_ENTERPRISE_REPORTING]);
e.preventDefault();
});
+ $('back-button-item').addEventListener('click', function(e) {
+ $('back-button-item').hidden = true;
+ $('signin-frame').back();
+ e.preventDefault();
+ });
+
+ $('close-button-item').addEventListener('click', function(e) {
+ this.cancel();
+ e.preventDefault();
+ }.bind(this));
+
+ $('gaia-whitelist-error').addEventListener('buttonclick', function() {
+ this.showWhitelistCheckFailedError(false);
+ }.bind(this));
+
+ $('gaia-whitelist-error').addEventListener('linkclick', function() {
+ chrome.send('launchHelpApp', [HELP_CANT_ACCESS_ACCOUNT]);
+ });
this.updateLocalizedContent();
},
@@ -137,6 +220,10 @@ login.createScreen('GaiaSigninScreen', 'gaia-signin', function() {
*/
set isLocal(value) {
this.isLocal_ = value;
+ if (this.isNewGaiaFlow) {
+ $('signin-frame').hidden = this.isLocal_;
+ $('offline-gaia').hidden = !this.isLocal_;
+ }
chrome.send('updateOfflineLogin', [value]);
},
@@ -147,10 +234,18 @@ login.createScreen('GaiaSigninScreen', 'gaia-signin', function() {
*/
showLoadingUI_: function(show) {
$('gaia-loading').hidden = !show;
- this.gaiaAuthHost_.frame.hidden = show;
+ if (this.isNewGaiaFlow && this.isLocal) {
+ $('offline-gaia').hidden = show;
+ } else {
+ $('signin-frame').hidden = show;
+ }
$('signin-right').hidden = show;
$('enterprise-info-container').hidden = show;
$('gaia-signin-divider').hidden = show;
+ this.classList.toggle('loading', show);
+ $('signin-frame').classList.remove('show');
+ if (!show)
+ this.classList.remove('auth-completed');
},
/**
@@ -161,7 +256,7 @@ login.createScreen('GaiaSigninScreen', 'gaia-signin', function() {
if (this != Oobe.getInstance().currentScreen)
return;
chrome.send('showLoadingTimeoutError');
- this.loadingTimer_ = window.setTimeout(
+ this.loadingTimer_ = setTimeout(
this.onLoadingTimeOut_.bind(this),
(MAX_GAIA_LOADING_TIME_SEC - GAIA_LOADING_PORTAL_SUSSPECT_TIME_SEC) *
1000);
@@ -182,7 +277,7 @@ login.createScreen('GaiaSigninScreen', 'gaia-signin', function() {
*/
clearLoadingTimer_: function() {
if (this.loadingTimer_) {
- window.clearTimeout(this.loadingTimer_);
+ clearTimeout(this.loadingTimer_);
this.loadingTimer_ = undefined;
}
},
@@ -193,12 +288,43 @@ login.createScreen('GaiaSigninScreen', 'gaia-signin', function() {
*/
startLoadingTimer_: function() {
this.clearLoadingTimer_();
- this.loadingTimer_ = window.setTimeout(
+ this.loadingTimer_ = setTimeout(
this.onLoadingSuspiciouslyLong_.bind(this),
GAIA_LOADING_PORTAL_SUSSPECT_TIME_SEC * 1000);
},
/**
+ * Handler for GAIA animation guard timer.
+ * @private
+ */
+ onLoadAnimationGuardTimer_: function() {
+ this.loadAnimationGuardTimer_ = undefined;
+ this.onShowView_();
+ },
+
+ /**
+ * Clears GAIA animation guard timer.
+ * @private
+ */
+ clearLoadAnimationGuardTimer_: function() {
+ if (this.loadAnimationGuardTimer_) {
+ clearTimeout(this.loadAnimationGuardTimer_);
+ this.loadAnimationGuardTimer_ = undefined;
+ }
+ },
+
+ /**
+ * Sets up GAIA animation guard timer.
+ * @private
+ */
+ startLoadAnimationGuardTimer_: function() {
+ this.clearLoadAnimationGuardTimer_();
+ this.loadAnimationGuardTimer_ = setTimeout(
+ this.onLoadAnimationGuardTimer_.bind(this),
+ GAIA_ANIMATION_GUARD_MILLISEC);
+ },
+
+ /**
* Whether Gaia is loading.
* @type {boolean}
*/
@@ -228,12 +354,25 @@ login.createScreen('GaiaSigninScreen', 'gaia-signin', function() {
window.requestAnimationFrame(function() {
chrome.send('loginVisible', ['gaia-loading']);
});
+ $('back-button-item').disabled = false;
+ $('back-button-item').hidden = true;
+ $('close-button-item').disabled = false;
+ this.classList.toggle('loading', this.loading);
// Button header is always visible when sign in is presented.
// Header is hidden once GAIA reports on successful sign in.
Oobe.getInstance().headerHidden = false;
},
+ onAfterShow: function(data) {
+ if (!this.loading && this.isWebviewSignin) {
+ if (this.isLocal)
+ $('offline-gaia').focus();
+ else
+ $('signin-frame').focus();
+ }
+ },
+
/**
* Event handler that is invoked just before the screen is hidden.
*/
@@ -248,11 +387,12 @@ login.createScreen('GaiaSigninScreen', 'gaia-signin', function() {
* @private
*/
loadAuthExtension: function(data) {
+ this.isNewGaiaFlow = data.useNewGaiaFlow;
this.isLocal = data.isLocal;
this.email = '';
// Reset SAML
- this.classList.toggle('saml', false);
+ this.classList.toggle('full-width', false);
this.samlPasswordConfirmAttempt_ = 0;
this.updateAuthExtension(data);
@@ -267,8 +407,22 @@ login.createScreen('GaiaSigninScreen', 'gaia-signin', function() {
if (data.localizedStrings)
params.localizedStrings = data.localizedStrings;
- if (data.useEmbedded)
- params.gaiaPath = 'EmbeddedSignIn';
+ if (this.isNewGaiaFlow) {
+ $('inner-container').classList.add('new-gaia-flow');
+ $('progress-dots').hidden = true;
+ params.chromeType = data.chromeType;
+ params.isNewGaiaFlowChromeOS = true;
+ }
+
+ if (data.gaiaEndpoint)
+ params.gaiaPath = data.gaiaEndpoint;
+
+ $('login-header-bar').newGaiaFlow = this.isNewGaiaFlow;
+
+ // Screen size could have been changed because of 'new-gaia-flow' or
+ // 'full-width' classes.
+ if (Oobe.getInstance().currentScreen === this)
+ Oobe.getInstance().updateScreenSize(this);
if (data.forceReload ||
JSON.stringify(this.gaiaAuthParams_) != JSON.stringify(params)) {
@@ -277,16 +431,19 @@ login.createScreen('GaiaSigninScreen', 'gaia-signin', function() {
var authMode = cr.login.GaiaAuthHost.AuthMode.DEFAULT;
if (data.useOffline)
authMode = cr.login.GaiaAuthHost.AuthMode.OFFLINE;
- else if (data.useEmbedded)
- authMode = cr.login.GaiaAuthHost.AuthMode.DESKTOP;
- this.gaiaAuthHost_.load(authMode,
- params,
- this.onAuthCompleted_.bind(this));
this.gaiaAuthParams_ = params;
-
this.loading = true;
this.startLoadingTimer_();
+
+ if (this.isLocal && this.isNewGaiaFlow) {
+ this.loadOffline(params);
+ this.onAuthReady_();
+ } else {
+ this.gaiaAuthHost_.load(authMode,
+ params,
+ this.onAuthCompleted_.bind(this));
+ }
} else if (this.loading && this.error_) {
// An error has occurred, so trying to reload.
this.doReload();
@@ -308,16 +465,22 @@ login.createScreen('GaiaSigninScreen', 'gaia-signin', function() {
reasonLabel.hidden = true;
}
- $('createAccount').hidden = !data.createAccount;
- $('guestSignin').hidden = !data.guestSignin;
- $('createSupervisedUserPane').hidden = !data.supervisedUsersEnabled;
-
- $('createSupervisedUserLinkPlaceholder').hidden =
- !data.supervisedUsersCanCreate;
- $('createSupervisedUserNoManagerText').hidden =
- data.supervisedUsersCanCreate;
- $('createSupervisedUserNoManagerText').textContent =
- data.supervisedUsersRestrictionReason;
+ if (this.isNewGaiaFlow) {
+ $('login-header-bar').showCreateSupervisedButton =
+ data.supervisedUsersCanCreate;
+ $('login-header-bar').showGuestButton = data.guestSignin;
+ } else {
+ $('createAccount').hidden = !data.createAccount;
+ $('guestSignin').hidden = !data.guestSignin;
+ $('createSupervisedUserPane').hidden = !data.supervisedUsersEnabled;
+
+ $('createSupervisedUserLinkPlaceholder').hidden =
+ !data.supervisedUsersCanCreate;
+ $('createSupervisedUserNoManagerText').hidden =
+ data.supervisedUsersCanCreate;
+ $('createSupervisedUserNoManagerText').textContent =
+ data.supervisedUsersRestrictionReason;
+ }
var isEnrollingConsumerManagement = data.isEnrollingConsumerManagement;
$('consumerManagementEnrollment').hidden = !isEnrollingConsumerManagement;
@@ -334,6 +497,7 @@ login.createScreen('GaiaSigninScreen', 'gaia-signin', function() {
$('createSupervisedUserPane').hidden &&
$('consumerManagementEnrollment').hidden;
this.classList.toggle('no-right-panel', noRightPanel);
+ this.classList.toggle('full-width', false);
if (Oobe.getInstance().currentScreen === this)
Oobe.getInstance().updateScreenSize(this);
},
@@ -343,8 +507,11 @@ login.createScreen('GaiaSigninScreen', 'gaia-signin', function() {
* user pods can be displayed.
*/
updateCancelButtonState: function() {
- this.cancelAllowed_ = this.isShowUsers_ && $('pod-row').pods.length;
+ this.cancelAllowed_ = this.isLocal ||
+ (this.isShowUsers_ && $('pod-row').pods.length);
$('login-header-bar').allowCancel = this.cancelAllowed_;
+ if (this.isNewGaiaFlow)
+ $('close-button-item').hidden = !this.cancelAllowed_;
},
/**
@@ -356,24 +523,30 @@ login.createScreen('GaiaSigninScreen', 'gaia-signin', function() {
},
/**
- * Invoked when the authFlow property is changed no the gaia host.
+ * Invoked when the authDomain property is changed on the GAIA host.
+ */
+ onAuthDomainChange_: function() {
+ $('saml-notice-message').textContent = loadTimeData.getStringF(
+ 'samlNotice',
+ this.gaiaAuthHost_.authDomain);
+ },
+
+ /**
+ * Invoked when the authFlow property is changed on the GAIA host.
* @param {Event} e Property change event.
*/
onAuthFlowChange_: function(e) {
var isSAML = this.isSAML();
- if (isSAML) {
- $('saml-notice-message').textContent = loadTimeData.getStringF(
- 'samlNotice',
- this.gaiaAuthHost_.authDomain);
- }
-
- this.classList.toggle('saml', isSAML);
+ this.classList.toggle('no-right-panel', isSAML);
+ this.classList.toggle('full-width', isSAML);
$('saml-notice-container').hidden = !isSAML;
if (Oobe.getInstance().currentScreen === this) {
Oobe.getInstance().updateScreenSize(this);
$('login-header-bar').allowCancel = isSAML || this.cancelAllowed_;
+ if (this.isNewGaiaFlow)
+ $('close-button-item').hidden = !(isSAML || this.cancelAllowed_);
}
},
@@ -382,9 +555,67 @@ login.createScreen('GaiaSigninScreen', 'gaia-signin', function() {
* @private
*/
onAuthReady_: function() {
- this.loading = false;
+ showViewProcessed_ = false;
+ if (this.isNewGaiaFlow)
+ this.startLoadAnimationGuardTimer_();
+
this.clearLoadingTimer_();
+ this.loading = false;
+
+ if (!this.isNewGaiaFlow)
+ this.onLoginUIVisible_();
+
+ // Warm up the user images screen.
+ Oobe.getInstance().preloadScreen({id: SCREEN_USER_IMAGE_PICKER});
+ },
+
+ /**
+ * Invoked when the auth host emits 'dialogShown' event.
+ * @private
+ */
+ onDialogShown_: function() {
+ $('back-button-item').disabled = true;
+ $('close-button-item').disabled = true;
+ },
+
+ /**
+ * Invoked when the auth host emits 'dialogHidden' event.
+ * @private
+ */
+ onDialogHidden_: function() {
+ $('back-button-item').disabled = false;
+ $('close-button-item').disabled = false;
+ },
+
+ /**
+ * Invoked when the auth host emits 'backButton' event.
+ * @private
+ */
+ onBackButton_: function(e) {
+ $('back-button-item').hidden = !e.detail;
+ $('login-header-bar').updateUI_();
+ },
+ /**
+ * Invoked when the auth host emits 'showView' event or when corresponding
+ * guard time fires.
+ * @private
+ */
+ onShowView_: function(e) {
+ if (showViewProcessed_)
+ return;
+
+ showViewProcessed_ = true;
+ this.clearLoadAnimationGuardTimer_();
+ $('signin-frame').classList.add('show');
+ this.onLoginUIVisible_();
+ },
+
+ /**
+ * Called when UI is shown.
+ * @private
+ */
+ onLoginUIVisible_: function() {
// Show deferred error bubble.
if (this.errorBubble_) {
this.showErrorBubble(this.errorBubble_[0], this.errorBubble_[1]);
@@ -393,19 +624,17 @@ login.createScreen('GaiaSigninScreen', 'gaia-signin', function() {
chrome.send('loginWebuiReady');
chrome.send('loginVisible', ['gaia-signin']);
-
- // Warm up the user images screen.
- Oobe.getInstance().preloadScreen({id: SCREEN_USER_IMAGE_PICKER});
},
/**
* Invoked when the user has successfully authenticated via SAML, the
* principals API was not used and the auth host needs the user to confirm
* the scraped password.
+ * @param {string} email The authenticated user's e-mail.
* @param {number} passwordCount The number of passwords that were scraped.
* @private
*/
- onAuthConfirmPassword_: function(passwordCount) {
+ onAuthConfirmPassword_: function(email, passwordCount) {
this.loading = true;
Oobe.getInstance().headerHidden = false;
@@ -414,6 +643,7 @@ login.createScreen('GaiaSigninScreen', 'gaia-signin', function() {
if (this.samlPasswordConfirmAttempt_ < 2) {
login.ConfirmPasswordScreen.show(
+ email,
this.samlPasswordConfirmAttempt_,
this.onConfirmPasswordCollected_.bind(this));
} else {
@@ -491,15 +721,22 @@ login.createScreen('GaiaSigninScreen', 'gaia-signin', function() {
if (credentials.useOffline) {
this.email = credentials.email;
chrome.send('authenticateUser',
- [credentials.gaiaId,
- credentials.email,
+ [credentials.email,
credentials.password]);
} else if (credentials.authCode) {
- chrome.send('completeAuthentication',
- [credentials.gaiaId,
- credentials.email,
- credentials.password,
- credentials.authCode]);
+ if (credentials.hasOwnProperty('authCodeOnly') &&
+ credentials.authCodeOnly) {
+ chrome.send('completeAuthenticationAuthCodeOnly',
+ [credentials.authCode]);
+ } else {
+ chrome.send('completeAuthentication', [
+ credentials.gaiaId,
+ credentials.email,
+ credentials.password,
+ credentials.authCode,
+ credentials.usingSAML
+ ]);
+ }
} else {
chrome.send('completeLogin',
[credentials.gaiaId,
@@ -509,6 +746,7 @@ login.createScreen('GaiaSigninScreen', 'gaia-signin', function() {
}
this.loading = true;
+ this.classList.add('auth-completed');
// Now that we're in logged in state header should be hidden.
Oobe.getInstance().headerHidden = true;
// Clear any error messages that were shown before login.
@@ -516,6 +754,24 @@ login.createScreen('GaiaSigninScreen', 'gaia-signin', function() {
},
/**
+ * Invoked when onAuthCompleted message received.
+ * @param {!Object} e Payload of the received HTML5 message.
+ * @private
+ */
+ onAuthCompletedMessage_: function(e) {
+ this.onAuthCompleted_(e.detail);
+ },
+
+ /**
+ * Invoked when onLoadAbort message received.
+ * @param {!Object} e Payload of the received HTML5 message.
+ * @private
+ */
+ onLoadAbortMessage_: function(e) {
+ this.onWebviewError(e.detail);
+ },
+
+ /**
* Clears input fields and switches to input mode.
* @param {boolean} takeFocus True to take focus.
* @param {boolean} forceOnline Whether online sign-in should be forced.
@@ -624,6 +880,7 @@ login.createScreen('GaiaSigninScreen', 'gaia-signin', function() {
$('pod-row').loadLastWallpaper();
Oobe.showScreen({id: SCREEN_ACCOUNT_PICKER});
+ this.classList.remove('whitelist-error');
Oobe.resetSigninUI(true);
},
@@ -637,5 +894,51 @@ login.createScreen('GaiaSigninScreen', 'gaia-signin', function() {
this.error_ = error;
chrome.send('frameLoadingCompleted', [this.error_]);
},
+
+ /**
+ * Handler for webview error handling.
+ * @param {!Object} data Additional information about error event like:
+ * {string} error Error code such as "ERR_INTERNET_DISCONNECTED".
+ * {string} url The URL that failed to load.
+ */
+ onWebviewError: function(data) {
+ chrome.send('webviewLoadAborted', [data.error]);
+ },
+
+ /**
+ * Sets welcome and enterpriseinfo strings for offline gaia.
+ * Also sets callback and sends message whether we already have email and
+ * should switch to the password screen with error.
+ */
+ loadOffline: function(params) {
+ var offlineLogin = $('offline-gaia');
+ var strings = params.localizedStrings;
+ if ('stringEnterpriseInfo' in strings)
+ offlineLogin.enterpriseInfo = strings['stringEnterpriseInfo'];
+ if ('emailDomain' in params)
+ offlineLogin.emailDomain = '@' + params['emailDomain'];
+ offlineLogin.setEmail(params.email);
+ },
+
+ /**
+ * Show/Hide error when user is not in whitelist. When UI is hidden
+ * GAIA is reloaded.
+ * @param {boolean} show Show/hide error UI.
+ * @param {!Object} opt_data Optional additional information.
+ */
+ showWhitelistCheckFailedError: function(show, opt_data) {
+ if (show) {
+ var isManaged = opt_data && opt_data.enterpriseManaged;
+ $('gaia-whitelist-error').textContent =
+ loadTimeData.getValue(isManaged ? 'whitelistErrorEnterprise' :
+ 'whitelistErrorConsumer');
+ }
+
+ this.classList.toggle('whitelist-error', show);
+ this.loading = !show;
+
+ if (!show)
+ Oobe.showSigninUI();
+ }
};
});
diff --git a/chromium/chrome/browser/resources/chromeos/login/screen_password_changed.css b/chromium/chrome/browser/resources/chromeos/login/screen_password_changed.css
index f1d34e3f490..ca97922024d 100644
--- a/chromium/chrome/browser/resources/chromeos/login/screen_password_changed.css
+++ b/chromium/chrome/browser/resources/chromeos/login/screen_password_changed.css
@@ -63,7 +63,7 @@
#password-changed-proceed-anyway-warning {
-webkit-padding-start: 30px;
- background: url('chrome://theme/IDR_WARNING') left top /24px no-repeat;
+ background: url(chrome://theme/IDR_WARNING) left top /24px no-repeat;
margin-top: 50px;
}
@@ -88,3 +88,14 @@
#password-changed.migrate.password-error #old-password-error {
display: block;
}
+
+.password-changed .new-gaia-flow #step-logo {
+ display: none;
+}
+
+.new-gaia-flow #gaia-password-changed,
+.new-gaia-flow #password-changed {
+ height: 528px;
+ padding: 0;
+ width: 448px;
+}
diff --git a/chromium/chrome/browser/resources/chromeos/login/screen_password_changed.html b/chromium/chrome/browser/resources/chromeos/login/screen_password_changed.html
index 1d80eed7480..9f72357b81a 100644
--- a/chromium/chrome/browser/resources/chromeos/login/screen_password_changed.html
+++ b/chromium/chrome/browser/resources/chromeos/login/screen_password_changed.html
@@ -1,3 +1,4 @@
+<link rel="import" href="chrome://oobe/custom_elements.html">
<div id="password-changed" class="step faded hidden migrate" hidden>
<div id="password-changed-contents" class="step-contents">
<div id="password-changed-title" i18n-content="passwordChangedTitle">
@@ -14,9 +15,11 @@
<div id="password-changed-link-block">
<a id="password-changed-cant-remember-link" href="#"
i18n-content="passwordChangedCantRemember"></a>
- </div>
+ </div>
<div id="password-changed-proceed-anyway-warning"
i18n-content="passwordChangedProceedAnyway"></div>
</div>
<div id="password-changed-controls" class="step-controls"></div>
+ <gaia-password-changed id="gaia-password-changed" hidden>
+ </gaia-password-changed>
</div>
diff --git a/chromium/chrome/browser/resources/chromeos/login/screen_password_changed.js b/chromium/chrome/browser/resources/chromeos/login/screen_password_changed.js
index aadc459ff89..8001a670d23 100644
--- a/chromium/chrome/browser/resources/chromeos/login/screen_password_changed.js
+++ b/chromium/chrome/browser/resources/chromeos/login/screen_password_changed.js
@@ -42,6 +42,21 @@ login.createScreen('PasswordChangedScreen', 'password-changed', function() {
$('password-changed-ok-button').disabled = true;
}
});
+
+ var gaiaPasswordChanged = $('gaia-password-changed');
+ gaiaPasswordChanged.addEventListener('cancel', function(e) {
+ chrome.send('cancelPasswordChangedFlow',
+ [$('gaia-password-changed').email]);
+ gaiaPasswordChanged.reset();
+ });
+
+ gaiaPasswordChanged.addEventListener('passwordEnter', function(e) {
+ chrome.send('migrateUserData', [e.detail.password]);
+ });
+
+ gaiaPasswordChanged.addEventListener('proceedAnyway', function() {
+ chrome.send('resyncUserData');
+ });
},
/**
@@ -124,7 +139,7 @@ login.createScreen('PasswordChangedScreen', 'password-changed', function() {
*/
cancel: function() {
this.disabled = true;
- chrome.send('cancelPasswordChangedFlow');
+ chrome.send('cancelPasswordChangedFlow', ['']);
},
/**
@@ -139,6 +154,11 @@ login.createScreen('PasswordChangedScreen', 'password-changed', function() {
chrome.send('migrateUserData', [$('old-password').value]);
},
+ onAfterShow: function(data) {
+ if (Oobe.isNewGaiaFlow())
+ $('gaia-password-changed').focus();
+ },
+
/**
* Event handler that is invoked just before the screen is hidden.
*/
@@ -158,19 +178,32 @@ login.createScreen('PasswordChangedScreen', 'password-changed', function() {
* Show password changed screen.
* @param {boolean} showError Whether to show the incorrect password error.
*/
- show: function(showError) {
+ show: function(showError, email) {
+ if (Oobe.isNewGaiaFlow()) {
+ $('password-changed-contents').hidden = true;
+ $('password-changed-controls').hidden = true;
+ var gaiaPasswordChanged = $('gaia-password-changed');
+ gaiaPasswordChanged.reset();
+ gaiaPasswordChanged.hidden = false;
+ if (showError)
+ gaiaPasswordChanged.invalidate();
+ if (email)
+ gaiaPasswordChanged.email = email;
+ } else {
+ var screen = $('password-changed');
+ screen.classList.toggle('password-error', showError);
+ screen.classList.add('migrate');
+ screen.classList.remove('resync');
+ $('old-password').value = '';
+ $('password-changed').disabled = false;
+ }
// We'll get here after the successful online authentication.
// It assumes session is about to start so hides login screen controls.
Oobe.getInstance().headerHidden = false;
- var screen = $('password-changed');
- screen.classList.toggle('password-error', showError);
- screen.classList.add('migrate');
- screen.classList.remove('resync');
- $('old-password').value = '';
- $('password-changed').disabled = false;
-
Oobe.showScreen({id: SCREEN_PASSWORD_CHANGED});
- $('password-changed-ok-button').disabled = true;
+ $('login-header-bar').signinUIState = SIGNIN_UI_STATE.PASSWORD_CHANGED;
+ if (!Oobe.isNewGaiaFlow())
+ $('password-changed-ok-button').disabled = true;
}
};
});
diff --git a/chromium/chrome/browser/resources/chromeos/login/screen_supervised_user_creation.css b/chromium/chrome/browser/resources/chromeos/login/screen_supervised_user_creation.css
index e68f486938a..30416a668c7 100644
--- a/chromium/chrome/browser/resources/chromeos/login/screen_supervised_user_creation.css
+++ b/chromium/chrome/browser/resources/chromeos/login/screen_supervised_user_creation.css
@@ -479,7 +479,7 @@ html[dir=rtl] #supervised-user-creation-image-preview {
#supervised-user-creation-flip-photo {
-webkit-transition: opacity 75ms linear;
- background: url('chrome://theme/IDR_MIRROR_FLIP') no-repeat;
+ background: url(chrome://theme/IDR_MIRROR_FLIP) no-repeat;
border: none;
bottom: 44px; /* 8px + image bottom. */
display: block;
@@ -516,13 +516,13 @@ html[dir=rtl] #supervised-user-creation-flip-photo {
}
.camera:not(.live) #supervised-user-creation-discard-photo {
- background: url('chrome://theme/IDR_USER_IMAGE_RECYCLE')
+ background: url(chrome://theme/IDR_USER_IMAGE_RECYCLE)
no-repeat center center;
display: block;
}
.camera.live.online #supervised-user-creation-take-photo {
- background: url('chrome://theme/IDR_USER_IMAGE_CAPTURE')
+ background: url(chrome://theme/IDR_USER_IMAGE_CAPTURE)
no-repeat center -1px;
display: block;
}
@@ -549,3 +549,16 @@ html[dir=rtl] #supervised-user-creation-flip-photo {
.camera.live:not(.online) .supervised-user-creation-image-stream-area .spinner {
display: block;
}
+
+#supervised-user-creation-close-button-item {
+ color: rgba(0, 0, 0, .54);
+ position: absolute;
+ right: 10px;
+ top: 10px;
+ z-index: 1;
+}
+
+html[dir=rtl] #supervised-user-creation-close-button-item {
+ left: 10px;
+ right: auto;
+}
diff --git a/chromium/chrome/browser/resources/chromeos/login/screen_supervised_user_creation.html b/chromium/chrome/browser/resources/chromeos/login/screen_supervised_user_creation.html
index 993e2abddbb..8be6fc77ba7 100644
--- a/chromium/chrome/browser/resources/chromeos/login/screen_supervised_user_creation.html
+++ b/chromium/chrome/browser/resources/chromeos/login/screen_supervised_user_creation.html
@@ -1,9 +1,10 @@
+<link rel="import" href="chrome://oobe/custom_elements.html">
<div class="step hidden" id="supervised-user-creation" hidden>
<div class="step-contents">
<div id="supervised-user-creation-intro" class="step-no-logo">
<div id="supervised-user-creation-marketing-intro"
class="marketing">
- <img src="chrome://theme/IDR_SUPERVISED_ILLUSTRATION_START">
+ <img src="chrome://theme/IDR_SUPERVISED_ILLUSTRATION_START" alt="">
</div>
<div class="below-marketing">
<div class="page-title"
@@ -102,7 +103,7 @@
<div id="supervised-user-creation-created" class="step-no-logo" hidden>
<div class="marketing">
- <img src="chrome://theme/IDR_SUPERVISED_ILLUSTRATION_DONE">
+ <img src="chrome://theme/IDR_SUPERVISED_ILLUSTRATION_DONE" alt="">
</div>
<div class="below-marketing">
<div>
@@ -142,7 +143,7 @@
<div id="supervised-user-creation-manager-template" hidden
class="manager-pod">
<div class="supervised-user-creation-manager-info-block">
- <img class="supervised-user-creation-manager-image">
+ <img class="supervised-user-creation-manager-image" alt="">
<div class="supervised-user-creation-manager-info-text">
<div class="supervised-user-creation-manager-name"></div>
<div class="supervised-user-creation-manager-email"></div>
@@ -157,4 +158,7 @@
<img class="import-pod-image"></img>
<div class="import-pod-name"></div>
</div>
-</div> \ No newline at end of file
+ <button id="supervised-user-creation-close-button-item" is="gaia-icon-button"
+ icon="close" i18n-values="aria-label:closeButton" tabindex="0">
+ </button>
+</div>
diff --git a/chromium/chrome/browser/resources/chromeos/login/screen_supervised_user_creation.js b/chromium/chrome/browser/resources/chromeos/login/screen_supervised_user_creation.js
index 95039ea753a..c7d44f88d90 100644
--- a/chromium/chrome/browser/resources/chromeos/login/screen_supervised_user_creation.js
+++ b/chromium/chrome/browser/resources/chromeos/login/screen_supervised_user_creation.js
@@ -3,7 +3,7 @@
// found in the LICENSE file.
/**
- * @fileoverview Supervised user creation flow screen.
+ * @fileoverview Legacy supervised user creation flow screen.
*/
login.createScreen('SupervisedUserCreationScreen',
@@ -616,6 +616,12 @@ login.createScreen('SupervisedUserCreationScreen',
'webkitTransitionEnd', function(e) {
previewElement.classList.remove('animation');
});
+
+ $('supervised-user-creation-close-button-item').addEventListener(
+ 'click', function(e) {
+ this.cancel();
+ e.preventDefault();
+ }.bind(this));
},
buttonIds: [],
@@ -1123,7 +1129,7 @@ login.createScreen('SupervisedUserCreationScreen',
var pagesWithCancel = ['intro', 'manager', 'username', 'import-password',
'error', 'import'];
$('login-header-bar').allowCancel =
- pagesWithCancel.indexOf(visiblePage) > 0;
+ pagesWithCancel.indexOf(visiblePage) > -1;
$('cancel-add-user-button').disabled = false;
this.getScreenElement('import-link').hidden = true;
@@ -1139,6 +1145,7 @@ login.createScreen('SupervisedUserCreationScreen',
'password-error');
if (this.managerList_.pods.length > 0)
this.managerList_.selectPod(this.managerList_.pods[0]);
+ $('login-header-bar').updateUI_();
}
if (visiblePage == 'username' || visiblePage == 'import-password') {
@@ -1192,6 +1199,9 @@ login.createScreen('SupervisedUserCreationScreen',
!this.importList_.selectedPod_ ||
this.importList_.selectedPod_.user.exists;
}
+ $('supervised-user-creation-close-button-item').hidden =
+ (visiblePage == 'created');
+
chrome.send('currentSupervisedUserPage', [this.currentPage_]);
},
@@ -1342,6 +1352,8 @@ login.createScreen('SupervisedUserCreationScreen',
var notSignedInPages = ['intro', 'manager'];
var postCreationPages = ['created'];
if (notSignedInPages.indexOf(this.currentPage_) >= 0) {
+ chrome.send('hideLocalSupervisedUserCreation');
+
// Make sure no manager password is kept:
this.managerList_.clearPods();
@@ -1450,12 +1462,7 @@ login.createScreen('SupervisedUserCreationScreen',
setDefaultImages: function(imagesData) {
var imageGrid = this.getScreenElement('image-grid');
- for (var i = 0, data; data = imagesData[i]; i++) {
- var item = imageGrid.addItem(data.url, data.title);
- item.type = 'default';
- item.author = data.author || '';
- item.website = data.website || '';
- }
+ imageGrid.setDefaultImages(imagesData);
this.imagesData_ = imagesData;
},
diff --git a/chromium/chrome/browser/resources/chromeos/login/test_util.js b/chromium/chrome/browser/resources/chromeos/login/test_util.js
new file mode 100644
index 00000000000..5a0399ef236
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/login/test_util.js
@@ -0,0 +1,28 @@
+// 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.
+
+cr.define('cr', function() {
+ function ErrorStore() {
+ var self = this;
+ window.addEventListener('error', function(e) {
+ self.store_.push(e);
+ });
+ }
+
+ cr.addSingletonGetter(ErrorStore);
+
+ ErrorStore.prototype = {
+ store_: [],
+
+ get length() {
+ return this.store_.length;
+ },
+ };
+
+ return {
+ ErrorStore: ErrorStore,
+ };
+});
+
+cr.ErrorStore.getInstance();
diff --git a/chromium/chrome/browser/resources/chromeos/login/throbber_notice.css b/chromium/chrome/browser/resources/chromeos/login/throbber_notice.css
new file mode 100644
index 00000000000..7b4a683ad56
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/login/throbber_notice.css
@@ -0,0 +1,22 @@
+/* Copyright 2015 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.
+ */
+
+paper-spinner::shadow .circle {
+ border-color: rgb(66, 133, 244);
+}
+
+paper-spinner {
+ height: 38px;
+ width: 38px;
+}
+
+#spinner-container {
+ margin-bottom: 28px;
+}
+
+#spinner-comment {
+ color: #747474;
+ font-size: 13px;
+}
diff --git a/chromium/chrome/browser/resources/chromeos/login/throbber_notice.html b/chromium/chrome/browser/resources/chromeos/login/throbber_notice.html
new file mode 100644
index 00000000000..243432de07a
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/login/throbber_notice.html
@@ -0,0 +1,12 @@
+<link rel="import" href="chrome://resources/polymer/polymer/polymer.html">
+<link rel="import" href="chrome://resources/polymer/paper-spinner/paper-spinner.html">
+
+<polymer-element name="throbber-notice" attributes="text" noscript>
+ <template>
+ <link rel="stylesheet" href="throbber_notice.css">
+ <div id="spinner-container" vertical layout center>
+ <paper-spinner dir="ltr" active></paper-spinner>
+ </div>
+ <div id="spinner-comment">{{text}}</div>
+ </template>
+</polymer-element>
diff --git a/chromium/chrome/browser/resources/chromeos/login/version.html b/chromium/chrome/browser/resources/chromeos/login/version.html
index 0f01a5dcfdf..5078d7a7b79 100644
--- a/chromium/chrome/browser/resources/chromeos/login/version.html
+++ b/chromium/chrome/browser/resources/chromeos/login/version.html
@@ -1,3 +1,4 @@
<div id="version-labels" hidden>
<div id="version"></div>
+ <div id="asset-id"></div>
</div>
diff --git a/chromium/chrome/browser/resources/chromeos/merge_session_load.html b/chromium/chrome/browser/resources/chromeos/merge_session_load.html
index 4c05fc5d325..b2b308977c7 100644
--- a/chromium/chrome/browser/resources/chromeos/merge_session_load.html
+++ b/chromium/chrome/browser/resources/chromeos/merge_session_load.html
@@ -1,8 +1,9 @@
-<!DOCTYPE html>
-<html i18n-values="dir:textdirection">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
<head>
<title i18n-content="title">
</title>
+<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<style>
html {
height: 100%;
@@ -12,7 +13,6 @@ body {
background: white;
color: #000;
display: -webkit-box;
- font-family: arial, sans-serif;
height: 100%;
margin: 0;
padding: 0;
@@ -21,7 +21,6 @@ body {
}
.header {
- font-family: Sans-serif;
padding: 3px;
width: 80%;
}
diff --git a/chromium/chrome/browser/resources/chromeos/mobile_setup.css b/chromium/chrome/browser/resources/chromeos/mobile_setup.css
index 8c840090b8a..2ce8a8ed302 100644
--- a/chromium/chrome/browser/resources/chromeos/mobile_setup.css
+++ b/chromium/chrome/browser/resources/chromeos/mobile_setup.css
@@ -45,10 +45,10 @@ iframe {
}
.overlay > div {
- -webkit-box-shadow: 1px 1px 12px rgba(0, 0, 0, 0.15);
background: white;
border: 1px solid #bdbdbd;
border-radius: 5px;
+ box-shadow: 1px 1px 12px rgba(0, 0, 0, 0.15);
padding: 15px;
}
@@ -63,7 +63,7 @@ iframe {
.logo {
background:
- url('chrome-extension://iadeocfgjdjdmpenejdbfeaocpbikmab/carrier_logo.png')
+ url(chrome-extension://iadeocfgjdjdmpenejdbfeaocpbikmab/carrier_logo.png)
no-repeat;
background-position: 85% 50%;
height: 58px;
@@ -104,11 +104,11 @@ iframe {
}
#banner {
- -webkit-box-shadow: 1px 1px 6px rgba(0, 0, 0, 0.2);
-webkit-padding-end: 5px;
-webkit-padding-start: 70px;
background-color: rgb(252, 246, 224);
border: 1px solid #ccc;
+ box-shadow: 1px 1px 6px rgba(0, 0, 0, 0.2);
display: -webkit-box;
margin-left: -22px;
padding-bottom: 5px;
diff --git a/chromium/chrome/browser/resources/chromeos/mobile_setup.html b/chromium/chrome/browser/resources/chromeos/mobile_setup.html
index 2f2a7f2f76b..1e9fbd0e8f5 100644
--- a/chromium/chrome/browser/resources/chromeos/mobile_setup.html
+++ b/chromium/chrome/browser/resources/chromeos/mobile_setup.html
@@ -1,8 +1,9 @@
-<!DOCTYPE HTML>
+<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title i18n-content="title"></title>
+<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="chrome://resources/css/dialogs.css"></link>
<link rel="stylesheet" href="mobile_setup.css"></link>
<script src="chrome://resources/js/cr.js"></script>
@@ -10,7 +11,7 @@
<script src="chrome://resources/js/cr/ui/dialogs.js"></script>
<script src="mobile_setup.js"></script>
</head>
-<body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<body>
<div id="mainDiv" class="dialog-body">
<iframe class="hidden" id="paymentForm" name="paymentForm" frameborder="0"></iframe>
<div id="systemStatus" class="overlay opaque-overlay hidden">
diff --git a/chromium/chrome/browser/resources/chromeos/mobile_setup_portal.html b/chromium/chrome/browser/resources/chromeos/mobile_setup_portal.html
index 32c0d4542a6..671137dbb34 100644
--- a/chromium/chrome/browser/resources/chromeos/mobile_setup_portal.html
+++ b/chromium/chrome/browser/resources/chromeos/mobile_setup_portal.html
@@ -1,14 +1,15 @@
-<!DOCTYPE HTML>
+<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title i18n-content="title"></title>
+<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="mobile_setup.css"></link>
<script src="chrome://resources/js/cr.js"></script>
<script src="chrome://resources/js/util.js"></script>
<script src="mobile_setup_portal.js"></script>
</head>
-<body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<body>
<div id="mainDiv" class="dialog-body">
<iframe id="portalFrame" name="portalFrame" frameborder="0" hidden></iframe>
<div id="systemStatus" class="overlay opaque-overlay" hidden>
diff --git a/chromium/chrome/browser/resources/chromeos/neterror.css b/chromium/chrome/browser/resources/chromeos/neterror.css
index 71254955969..234c7cc1bcb 100644
--- a/chromium/chrome/browser/resources/chromeos/neterror.css
+++ b/chromium/chrome/browser/resources/chromeos/neterror.css
@@ -40,14 +40,14 @@ a {
* renderer process, so embed the resource manually.
*/
content: -webkit-image-set(
- url('../../../renderer/resources/default_100_percent/common/error_network_generic.png') 1x,
- url('../../../renderer/resources/default_200_percent/common/error_network_generic.png') 2x);
+ url(../../../renderer/resources/default_100_percent/common/error_network_generic.png) 1x,
+ url(../../../renderer/resources/default_200_percent/common/error_network_generic.png) 2x);
}
.icon-offline {
content: -webkit-image-set(
- url('../../../renderer/resources/default_100_percent/offline/100-error-offline.png') 1x,
- url('../../../renderer/resources/default_200_percent/offline/200-error-offline.png') 2x);
+ url(../../../renderer/resources/default_100_percent/offline/100-error-offline.png) 1x,
+ url(../../../renderer/resources/default_200_percent/offline/200-error-offline.png) 2x);
}
#help-box-outer {
diff --git a/chromium/chrome/browser/resources/chromeos/neterror.js b/chromium/chrome/browser/resources/chromeos/neterror.js
index 5dd4d3f8640..2dad8fa7c21 100644
--- a/chromium/chrome/browser/resources/chromeos/neterror.js
+++ b/chromium/chrome/browser/resources/chromeos/neterror.js
@@ -3,13 +3,13 @@
// found in the LICENSE file.
function toggleHelpBox() {
- var helpBoxOuter = $('help-box-outer');
+ var helpBoxOuter = $('details');
helpBoxOuter.classList.toggle('hidden');
- var moreLessButton = $('details-button');
+ var detailsButton = $('details-button');
if (helpBoxOuter.classList.contains('hidden')) {
- moreLessButton.innerText = moreLessButton.moreText;
+ detailsButton.innerText = detailsButton.detailsText;
} else {
- moreLessButton.innerText = moreLessButton.lessText;
+ detailsButton.innerText = detailsButton.hideDetailsText;
}
}
diff --git a/chromium/chrome/browser/resources/chromeos/network/network_config.js b/chromium/chrome/browser/resources/chromeos/network/network_config.js
deleted file mode 100644
index 29ac7a7c1cc..00000000000
--- a/chromium/chrome/browser/resources/chromeos/network/network_config.js
+++ /dev/null
@@ -1,147 +0,0 @@
-// 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 This object provides a similar API to chrome.networkingPrivate.
- * It simulates the extension callback model by storing callbacks in a member
- * object and invoking them when the corresponding method is called by Chrome in
- * response to a chrome.send call.
- */
-
-var networkConfig = {
- /**
- * Return the property associated with a key (which may reference a
- * sub-object).
- *
- * @param {Object} properties The object containing the network properties.
- * @param {string} key The ONC key for the property. May refer to a nested
- * propety, e.g. 'WiFi.Security'.
- * @return {*} The value associated with the property.
- */
- getValueFromProperties: function(properties, key) {
- if (!key) {
- console.error('Empty key');
- return undefined;
- }
- var dot = key.indexOf('.');
- if (dot > 0) {
- var key1 = key.substring(0, dot);
- var key2 = key.substring(dot + 1);
- var subobject = properties[key1];
- if (subobject)
- return this.getValueFromProperties(subobject, key2);
- }
- return properties[key];
- },
-
- /**
- * Generate a unique id for 'callback' and store it for future retrieval.
- *
- * @param {function} callback The associated callback.
- * @return {integer} The id of the callback.
- * @private
- */
- callbackId: 1,
- callbackMap: {},
- storeCallback_: function(callback) {
- var id = this.callbackId++;
- this.callbackMap[id] = callback;
- return id;
- },
-
- /**
- * Retrieve the callback associated with |id| and remove it from the map.
- *
- * @param {integer} id The id of the callback.
- * @return {function} The associated callback.
- * @private
- */
- retrieveCallback_: function(id) {
- var callback = this.callbackMap[id];
- delete this.callbackMap[id];
- return callback;
- },
-
- /**
- * Callback invoked by Chrome.
- *
- * @param {Array} args A list of arguments passed to the callback. The first
- * entry must be the callbackId passed to chrome.send.
- */
- chromeCallbackSuccess: function(args) {
- var callbackId = args.shift();
- var callback = this.retrieveCallback_(callbackId);
- this.lastError = '';
- if (callback)
- callback.apply(null, args);
- else
- console.error('Callback not found for id: ' + callbackId);
- },
-
- /**
- * Error Callback invoked by Chrome. Sets lastError and logs to the console.
- *
- * @param {Args} args A list of arguments. The first entry must be the
- * callbackId passed to chrome.send.
- */
- lastError: '',
- chromeCallbackError: function(args) {
- var callbackId = args.shift();
- this.lastError = args.shift();
- console.error('Callback error: "' + this.lastError);
- // We still invoke the callback, but with null args. The callback should
- // check this.lastError and handle that.
- var callback = this.retrieveCallback_(callbackId);
- if (callback)
- callback.apply(null, null);
- },
-
- /**
- * Implement networkingPrivate.getProperties. See networking_private.json.
- *
- * @param {string} guid The guid identifying the network.
- * @param {function()} callback The callback to call on completion.
- */
- getProperties: function(guid, callback) {
- var callbackId = this.storeCallback_(callback);
- chrome.send('networkConfig.getProperties', [callbackId, guid]);
- },
-
- /**
- * Implement networkingPrivate.getManagedProperties. See
- * networking_private.json.
- *
- * @param {string} guid The guid identifying the network.
- * @param {function()} callback The callback to call on completion.
- */
- getManagedProperties: function(guid, callback) {
- var callbackId = this.storeCallback_(callback);
- chrome.send('networkConfig.getManagedProperties', [callbackId, guid]);
- },
-
- /**
- * Implement networkingPrivate.getNetworks. See networking_private.json.
- *
- * @param {string} guid The guid identifying the network.
- * @param {function()} callback The callback to call on completion.
- */
- getNetworks: function(filter, callback) {
- var callbackId = this.storeCallback_(callback);
- chrome.send('networkConfig.getNetworks', [callbackId, filter]);
- },
-
- /**
- * Debugging method to get raw Shill properties
- *
- * @param {string} guid The guid identifying the network.
- * @param {function()} callback The callback to call on completion.
- */
- getShillProperties: function(guid, callback) {
- var callbackId = this.storeCallback_(callback);
- chrome.send('networkConfig.getShillProperties', [callbackId, guid]);
- },
-
-};
diff --git a/chromium/chrome/browser/resources/chromeos/network_configuration/js/network_status.js b/chromium/chrome/browser/resources/chromeos/network_configuration/js/network_status.js
index 8fa37d91b1c..e7127527c41 100644
--- a/chromium/chrome/browser/resources/chromeos/network_configuration/js/network_status.js
+++ b/chromium/chrome/browser/resources/chromeos/network_configuration/js/network_status.js
@@ -24,10 +24,10 @@ cr.define('network.status', function() {
/**
* Calculates both set difference of |a| and |b| and returns them in an array:
* [ a - b, b - a ].
- * @param {Array.<T>} a .
- * @param {Array.<T>} b .
+ * @param {Array<T>} a .
+ * @param {Array<T>} b .
* @param {function(T): K} toKey .
- * @return {Array.<Array.<T>>} .
+ * @return {Array<Array<T>>} .
*/
function differenceBy(a, b, toKey) {
var inA = {};
@@ -72,9 +72,9 @@ cr.define('network.status', function() {
/**
* Creates a map of the entries of |array|. Each entry is associated to the
* key getKey(entry).
- * @param {Array.<T>} array .
+ * @param {Array<T>} array .
* @param {function(T): K} getKey .
- * @return {Object.<K, T>} .
+ * @return {Object<K, T>} .
*/
function createMapFromList(array, getKey) {
var result = {};
@@ -87,8 +87,8 @@ cr.define('network.status', function() {
/**
* Wraps each entry in |array| into an array. The result contains rows with
* one entry each.
- * @param {Array.<T>} array .
- * @return {Array.<Array.<T>>} .
+ * @param {Array<T>} array .
+ * @return {Array<Array<T>>} .
*/
function arrayToTable(array) {
return array.map(function(e) {
@@ -232,7 +232,7 @@ cr.define('network.status', function() {
/**
* Returns the list of identifiers for which nested buttons will be created.
* To be overridden by subclasses.
- * @return {Array.<string>} .
+ * @return {Array<string>} .
*/
getEntries: function() {
return [];
@@ -410,9 +410,9 @@ cr.define('network.status', function() {
/**
* Groups networks by type.
- * @param {Object.<string, Object>} networkByID A map from network ID to
+ * @param {Object<string, Object>} networkByID A map from network ID to
* network properties.
- * @return {Object.<string, Array.<string>>} A map from network type to the
+ * @return {Object<string, Array<string>>} A map from network type to the
* list of IDs of networks of that type.
*/
function createNetworkIDsByType(networkByID) {
@@ -454,26 +454,26 @@ cr.define('network.status', function() {
/**
* The set of technologies shown to the user.
- * @type {Object.<string, boolean>}
+ * @type {Object<string, boolean>}
*/
this.technologies_ = {};
/**
* A map from network type to the array of IDs of network of that type.
- * @type {Object.<string, Array.<string>>}
+ * @type {Object<string, Array<string>>}
*/
this.networkIDsByType_ = {};
/**
* A map from network ID to the network's properties.
- * @type {Object.<string, Object>}
+ * @type {Object<string, Object>}
*/
this.networkByID_ = {};
/**
* A map from network ID to the network's position in the last received
* network list.
- * @type {Object.<string, number>}
+ * @type {Object<string, number>}
*/
this.networkIndexByID_ = {};
@@ -533,7 +533,7 @@ cr.define('network.status', function() {
/**
* @param {string} technology .
- * @return {Array.<string>} Array of network IDs.
+ * @return {Array<string>} Array of network IDs.
*/
getNetworkIDsOfType: function(technology) {
var networkIDs = this.networkIDsByType_[technology];
@@ -599,7 +599,7 @@ cr.define('network.status', function() {
},
/**
- * @param {Array.<string>} networkIDs .
+ * @param {Array<string>} networkIDs .
*/
updateIndexes_: function(networkIDs) {
var newNetworkIndexByID = {};
@@ -610,7 +610,7 @@ cr.define('network.status', function() {
},
/**
- * @param {Array.<string>} networkIDs .
+ * @param {Array<string>} networkIDs .
*/
onNetworkListChanged_: function(networkIDs) {
var diff = differenceBy(Object.keys(this.networkByID_),
@@ -635,7 +635,7 @@ cr.define('network.status', function() {
},
/**
- * @param {Array.<string>} networkIDs .
+ * @param {Array<string>} networkIDs .
*/
onNetworksChanged_: function(networkIDs) {
var updateCallback = this.updateNetworkCallback_.bind(this);
@@ -662,7 +662,7 @@ cr.define('network.status', function() {
},
/**
- * @param {Array.<Object>} networks .
+ * @param {Array<Object>} networks .
*/
setVisibleNetworks: function(networks) {
this.networkByID_ = createMapFromList(
diff --git a/chromium/chrome/browser/resources/chromeos/network_ui/compiled_resources.gyp b/chromium/chrome/browser/resources/chromeos/network_ui/compiled_resources.gyp
new file mode 100644
index 00000000000..ead34ba25d9
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/network_ui/compiled_resources.gyp
@@ -0,0 +1,24 @@
+# Copyright 2015 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.
+{
+ 'targets': [
+ {
+ 'target_name': 'network_ui',
+
+ 'variables': {
+ 'depends': [
+ '../../../../../ui/webui/resources/cr_elements/cr_onc/cr_onc_types.js',
+ '../../../../../ui/webui/resources/js/compiled_resources.gyp:util',
+ '../../../../../ui/webui/resources/js/compiled_resources.gyp:load_time_data',
+ ],
+ 'externs': [
+ '../../../../../ui/webui/resources/cr_elements/cr_network_icon/cr_network_icon_externs.js',
+ '../../../../../ui/webui/resources/cr_elements/cr_onc/cr_onc_data_externs.js',
+ '../../../../../third_party/closure_compiler/externs/chrome_extensions.js'
+ ],
+ },
+ 'includes': ['../../../../../third_party/closure_compiler/compile_js.gypi'],
+ },
+ ],
+}
diff --git a/chromium/chrome/browser/resources/chromeos/network_ui/network_ui.css b/chromium/chrome/browser/resources/chromeos/network_ui/network_ui.css
index 305897709ee..c3fbf873664 100644
--- a/chromium/chrome/browser/resources/chromeos/network_ui/network_ui.css
+++ b/chromium/chrome/browser/resources/chromeos/network_ui/network_ui.css
@@ -1,35 +1,40 @@
-/*
- * Copyright 2013 The Chromium Authors. All rights reserved.
+/* Copyright 2013 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.
- */
+ * found in the LICENSE file. */
-#log-level-container {
+paper-button[raised].colored {
+ background: rgb(66, 133, 244);
+ color: white;
+}
+
+#header {
margin: 5px;
}
-#log-level-container label {
- vertical-align: middle;
+#header a {
+ padding: 0 4px;
}
-#log-level-container input {
- margin-bottom: 1px;
- vertical-align: middle;
+#header cr-network-icon {
+ height: 40px;
+ width: 40px;
}
-#network-log-container {
- border: 1px solid rgb(220, 220, 220);
- font-size: 12px;
- height: 350px;
- overflow: scroll;
- padding: 10px;
- width: 100%;
+#default-network {
+ align-items: center;
+ display: flex;
+ flex-direction: row;
+ padding: 4px;
+}
+
+#default-network-text {
+ -webkit-padding-start: 8px;
+ font-size: 14px;
+ font-weight: bold;
}
-#network-log-container p {
- font-family: monospace;
- line-height: 20px;
- margin: 2px;
+#refresh {
+ margin: 4px;
}
.state-table {
@@ -63,23 +68,9 @@
white-space: pre-wrap;
}
-.network-level-tag {
- -webkit-margin-end: 5px;
- border: 1px solid;
- border-radius: 2px;
- padding: 0 4px;
-}
-
-.network-log-level-event {
- color: orange;
-}
-
-.network-log-level-error {
- color: red;
-}
-
-.network-log-level-debug {
- color: blue;
+.state-table cr-network-icon {
+ height: 25px;
+ width: 25px;
}
#advanced-options {
diff --git a/chromium/chrome/browser/resources/chromeos/network_ui/network_ui.html b/chromium/chrome/browser/resources/chromeos/network_ui/network_ui.html
index 699978dc0c0..6b512f98b24 100644
--- a/chromium/chrome/browser/resources/chromeos/network_ui/network_ui.html
+++ b/chromium/chrome/browser/resources/chromeos/network_ui/network_ui.html
@@ -1,48 +1,53 @@
-<!DOCTYPE HTML>
-<html i18n-values="dir:textdirection;">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
+
<head>
<meta charset="utf-8">
<title id="network" i18n-content="titleText"></title>
+ <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="chrome://network/network_ui.css">
+ <link rel="import" href="chrome://resources/cr_elements/cr_network_icon/cr_network_icon.html">
+ <link rel="import" href="chrome://resources/cr_elements/cr_onc/cr_onc_data.html">
+ <link rel="import" href="chrome://resources/polymer/paper-button/paper-button.html">
+
<script src="chrome://resources/js/load_time_data.js"></script>
<script src="chrome://resources/js/util.js"></script>
<script src="chrome://network/strings.js"></script>
- <script src="chrome://network/network_config.js"></script>
<script src="chrome://network/network_ui.js"></script>
</head>
-<body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
- <p i18n-content="autoRefreshText"></p>
- <h3>Event Log (Newest first):</h3>
- <div id="log-level-container">
- <button id="log-refresh" i18n-content="logRefreshText"></button>
- <label i18n-content="logLevelShowText"></label>
- <input id="log-error" type="checkbox">
- <label for="log-error" i18n-content="logLevelErrorText"></label>
- <input id="log-user" type="checkbox">
- <label for="log-user" i18n-content="logLevelUserText"></label>
- <input id="log-event" type="checkbox">
- <label for="log-event" i18n-content="logLevelEventText"></label>
- <input id="log-debug" type="checkbox">
- <label for="log-debug" i18n-content="logLevelDebugText"></label>
- <input id="log-fileinfo" type="checkbox">
- <label for="log-fileinfo" i18n-content="logLevelFileinfoText"></label>
- <input id="log-timedetail" type="checkbox">
- <label for="log-timedetail" i18n-content="logLevelTimeDetailText"></label>
+
+<body>
+ <div id="header">
+ <p i18n-content="autoRefreshText"></p>
+ <span i18n-values=".innerHTML:deviceLogLinkText"></span>
+ <div id="advanced-options">
+ <span i18n-content="clickToExpandText"></span>
+ <span i18n-content="propertyFormatText"></span>
+ <select id="get-property-format">
+ <option value="normal" i18n-content="normalFormatOption"></option>
+ <option value="managed" i18n-content="managedFormatOption"></option>
+ <option value="state" i18n-content="stateFormatOption"></option>
+ <option value="shill" i18n-content="shillFormatOption"></option>
+ </select>
+ </div>
+
+ <div id="default-network">
+ <cr-network-icon id="default-network-icon"></cr-network-icon>
+ <span id="default-network-text"></span>
+ </div>
+
+ <div>
+ <paper-button raised class="colored" id="refresh"
+ i18n-content="networkRefreshText">
+ </paper-button>
+ </div>
</div>
- <div id="network-log-container">
- </div>
- <div id="advanced-options">
- <span>Click '+' to get network properties of type: </span>
- <select id="get-network-type">
- <option id="normal" selected>ONC</option>
- <option id="managed">Managed ONC</option>
- <option id="shill">Shill</option>
- </select>
- </div>
- <h3>Visible Networks: </h3>
+
+ <h3 i18n-content="visibleNetworksLabel"></h3>
<table id="network-state-table" class="state-table">
<tr class="state-table-header">
<td></td>
+ <td></td>
<td>GUID</td>
<td>Path</td>
<td>Name</td>
@@ -54,14 +59,15 @@
<td>Tech</td>
<td>Activation</td>
<td>Roam</td>
- <td>OOC</td>
<td>Strength</td>
</tr>
</table>
- <h3>Favorites: </h3>
+
+ <h3 i18n-content="favoriteNetworksLabel"></h3>
<table id="favorite-state-table" class="state-table">
<tr class="state-table-header">
<td></td>
+ <td></td>
<td>GUID</td>
<td>Path</td>
<td>Name</td>
@@ -71,6 +77,7 @@
<td>ONC Source</td>
</tr>
</table>
- <script src="chrome://resources/js/i18n_template2.js"></script>
+
+ <script src="chrome://resources/js/i18n_template.js"></script>
</body>
</html>
diff --git a/chromium/chrome/browser/resources/chromeos/network_ui/network_ui.js b/chromium/chrome/browser/resources/chromeos/network_ui/network_ui.js
index 34e95729eff..8fcf934222c 100644
--- a/chromium/chrome/browser/resources/chromeos/network_ui/network_ui.js
+++ b/chromium/chrome/browser/resources/chromeos/network_ui/network_ui.js
@@ -21,7 +21,6 @@ var NetworkUI = (function() {
'EAP.EAP'],
'Cellular.ActivationState',
'Cellular.RoamingState',
- 'Cellular.OutOfCredits',
'WiFi.SignalStrength'
];
@@ -32,97 +31,64 @@ var NetworkUI = (function() {
'Type',
'profile_path',
'visible',
- 'onc_source'
+ 'Source'
];
- var LOG_LEVEL_CLASSNAME = {
- 'Error': 'network-log-level-error',
- 'User': 'network-log-level-user',
- 'Event': 'network-log-level-event',
- 'Debug': 'network-log-level-debug'
- };
-
- var LOG_LEVEL_CHECKBOX = {
- 'Error': 'log-error',
- 'User': 'log-user',
- 'Event': 'log-event',
- 'Debug': 'log-debug'
- };
-
/**
- * Create a tag of log level.
+ * Creates and returns a typed HTMLTableCellElement.
*
- * @param {string} level A string that represents log level.
- * @return {DOMElement} The created span element.
+ * @return {!HTMLTableCellElement} A new td element.
*/
- var createLevelTag = function(level) {
- var tag = document.createElement('span');
- tag.className = 'network-level-tag';
- tag.textContent = level;
- tag.classList.add(LOG_LEVEL_CLASSNAME[level]);
- return tag;
+ var createTableCellElement = function() {
+ return /** @type {!HTMLTableCellElement} */(document.createElement('td'));
};
/**
- * Creates an element that contains the time, the event, the level and
- * the description of the given log entry.
+ * Creates and returns a typed HTMLTableRowElement.
*
- * @param {Object} logEntry An object that represents a single line of log.
- * @return {DOMElement} The created p element that represents the log entry.
+ * @return {!HTMLTableRowElement} A new tr element.
*/
- var createLogEntryText = function(logEntry) {
- var level = logEntry['level'];
- if (!$(LOG_LEVEL_CHECKBOX[level]).checked)
- return null;
- var res = document.createElement('p');
- var textWrapper = document.createElement('span');
- var fileinfo = '';
- if ($('log-fileinfo').checked)
- fileinfo = logEntry['file'];
- var timestamp = '';
- if ($('log-timedetail').checked)
- timestamp = logEntry['timestamp'];
- else
- timestamp = logEntry['timestampshort'];
- textWrapper.textContent = loadTimeData.getStringF(
- 'logEntryFormat',
- timestamp,
- fileinfo,
- logEntry['event'],
- logEntry['description']);
- res.appendChild(createLevelTag(level));
- res.appendChild(textWrapper);
- return res;
+ var createTableRowElement = function() {
+ return /** @type {!HTMLTableRowElement} */(document.createElement('tr'));
};
/**
- * Create event log entries.
+ * Returns the ONC data property for networkState associated with a key. Used
+ * to access properties in the networkState by |key| which may may refer to a
+ * nested property, e.g. 'WiFi.Security'. If any part of a nested key is
+ * missing, this will return undefined.
*
- * @param {Array.<string>} logEntries A array of strings that each string
- * represents a log event in JSON format.
+ * @param {!chrome.networkingPrivate.NetworkStateProperties} networkState The
+ * network state property dictionary.
+ * @param {string} key The ONC key for the property.
+ * @return {*} The value associated with the property or undefined if the
+ * key (any part of it) is not defined.
*/
- var createEventLog = function(logEntries) {
- var container = $('network-log-container');
- container.textContent = '';
- for (var i = 0; i < logEntries.length; ++i) {
- var entry = createLogEntryText(JSON.parse(logEntries[i]));
- if (entry)
- container.appendChild(entry);
+ var getOncProperty = function(networkState, key) {
+ var dict = /** @type {!Object} */(networkState);
+ var keys = key.split('.');
+ while (keys.length > 1) {
+ var k = keys.shift();
+ dict = dict[k];
+ if (!dict || typeof dict != 'object')
+ return undefined;
}
+ return dict[keys.shift()];
};
/**
- * Create a cell with a button for expanding a network state table row.
+ * Creates a cell with a button for expanding a network state table row.
*
* @param {string} guid The GUID identifying the network.
- * @return {DOMElement} The created td element that displays the given value.
+ * @return {!HTMLTableCellElement} The created td element that displays the
+ * given value.
*/
var createStateTableExpandButton = function(guid) {
- var cell = document.createElement('td');
+ var cell = createTableCellElement();
cell.className = 'state-table-expand-button-cell';
var button = document.createElement('button');
button.addEventListener('click', function(event) {
- toggleExpandRow(event.target, guid);
+ toggleExpandRow(/** @type {!HTMLElement} */(event.target), guid);
});
button.className = 'state-table-expand-button';
button.textContent = '+';
@@ -131,39 +97,61 @@ var NetworkUI = (function() {
};
/**
- * Create a cell in network state table.
+ * Creates a cell with an icon representing the network state.
*
- * @param {string} value Content in the cell.
- * @return {DOMElement} The created td element that displays the given value.
+ * @param {!chrome.networkingPrivate.NetworkStateProperties} networkState The
+ * network state properties.
+ * @return {!HTMLTableCellElement} The created td element that displays the
+ * icon.
+ */
+ var createStateTableIcon = function(networkState) {
+ var cell = createTableCellElement();
+ cell.className = 'state-table-icon-cell';
+ var icon = /** @type {!CrNetworkIconElement} */(
+ document.createElement('cr-network-icon'));
+ icon.isListItem = true;
+ icon.networkState = CrOncDataElement.create(networkState);
+ cell.appendChild(icon);
+ return cell;
+ };
+
+ /**
+ * Creates a cell in the network state table.
+ *
+ * @param {*} value Content in the cell.
+ * @return {!HTMLTableCellElement} The created td element that displays the
+ * given value.
*/
var createStateTableCell = function(value) {
- var cell = document.createElement('td');
+ var cell = createTableCellElement();
cell.textContent = value || '';
return cell;
};
/**
- * Create a row in the network state table.
+ * Creates a row in the network state table.
*
* @param {Array} stateFields The state fields to use for the row.
- * @param {Object} state Property values for the network or favorite.
- * @return {DOMElement} The created tr element that contains the network
- * state information.
+ * @param {!chrome.networkingPrivate.NetworkStateProperties} networkState The
+ * network state properties.
+ * @return {!HTMLTableRowElement} The created tr element that contains the
+ * network state information.
*/
- var createStateTableRow = function(stateFields, state) {
- var row = document.createElement('tr');
+ var createStateTableRow = function(stateFields, networkState) {
+ var row = createTableRowElement();
row.className = 'state-table-row';
- var guid = state.GUID;
+ var guid = networkState.GUID;
row.appendChild(createStateTableExpandButton(guid));
+ row.appendChild(createStateTableIcon(networkState));
for (var i = 0; i < stateFields.length; ++i) {
var field = stateFields[i];
- var value = '';
+ var value;
if (typeof field == 'string') {
- value = networkConfig.getValueFromProperties(state, field);
+ value = getOncProperty(networkState, field);
} else {
for (var j = 0; j < field.length; ++j) {
- value = networkConfig.getValueFromProperties(state, field[j]);
- if (value)
+ value = getOncProperty(networkState, field[j]);
+ if (value != undefined)
break;
}
}
@@ -175,11 +163,12 @@ var NetworkUI = (function() {
};
/**
- * Create table for networks or favorites.
+ * Creates a table for networks or favorites.
*
* @param {string} tablename The name of the table to be created.
- * @param {Array} stateFields The list of fields for the table.
- * @param {Array} states An array of network or favorite states.
+ * @param {!Array<string>} stateFields The list of fields for the table.
+ * @param {!Array<!chrome.networkingPrivate.NetworkStateProperties>} states
+ * An array of network or favorite states.
*/
var createStateTable = function(tablename, stateFields, states) {
var table = $(tablename);
@@ -192,44 +181,63 @@ var NetworkUI = (function() {
};
/**
- * This callback function is triggered when the network log is received.
+ * Returns a valid HTMLElement id from |guid|.
*
- * @param {Object} data A JSON structure of event log entries.
+ * @param {string} guid A GUID which may start with a digit.
+ * @return {string} A valid HTMLElement id.
*/
- var getNetworkLogCallback = function(data) {
- createEventLog(JSON.parse(data));
+ var idFromGuid = function(guid) {
+ return '_' + guid.replace(/[{}]/g, '');
};
/**
* This callback function is triggered when visible networks are received.
*
- * @param {Array} data A list of network state information for each
- * visible network.
+ * @param {!Array<!chrome.networkingPrivate.NetworkStateProperties>} states
+ * A list of network state information for each visible network.
*/
var onVisibleNetworksReceived = function(states) {
+ /** @type {chrome.networkingPrivate.NetworkStateProperties} */ var
+ defaultState;
+ if (states.length > 0)
+ defaultState = states[0];
+ var icon = /** @type {CrNetworkIconElement} */($('default-network-icon'));
+ if (defaultState && defaultState.Type != CrOnc.Type.VPN) {
+ $('default-network-text').textContent =
+ loadTimeData.getStringF('defaultNetworkText',
+ defaultState.Name,
+ defaultState.ConnectionState);
+ icon.networkState = CrOncDataElement.create(defaultState);
+ } else {
+ $('default-network-text').textContent =
+ loadTimeData.getString('noNetworkText');
+ // Show the disconnected wifi icon if there are no networks.
+ icon.networkType = CrOnc.Type.WIFI;
+ }
+
createStateTable('network-state-table', NETWORK_STATE_FIELDS, states);
};
/**
* This callback function is triggered when favorite networks are received.
*
- * @param {Object} data A list of network state information for each
- * favorite network.
+ * @param {!Array<!chrome.networkingPrivate.NetworkStateProperties>} states
+ * A list of network state information for each favorite network.
*/
var onFavoriteNetworksReceived = function(states) {
createStateTable('favorite-state-table', FAVORITE_STATE_FIELDS, states);
};
/**
- * Toggle the button state and add or remove a row displaying the complete
+ * Toggles the button state and add or remove a row displaying the complete
* state information for a row.
*
- * @param {DOMElement} btn The button that was clicked.
+ * @param {!HTMLElement} btn The button that was clicked.
* @param {string} guid GUID identifying the network.
*/
var toggleExpandRow = function(btn, guid) {
var cell = btn.parentNode;
- var row = cell.parentNode;
+ var row = /** @type {!HTMLTableRowElement} */(cell.parentNode);
if (btn.textContent == '-') {
btn.textContent = '+';
row.parentNode.removeChild(row.nextSibling);
@@ -243,54 +251,80 @@ var NetworkUI = (function() {
/**
* Creates the expanded row for displaying the complete state as JSON.
*
- * @param {Object} state Property values for the network or favorite.
- * @param {DOMElement} baseRow The unexpanded row associated with the new row.
- * @return {DOMElement} The created tr element for the expanded row.
+ * @param {string} guid The GUID identifying the network.
+ * @param {!HTMLTableRowElement} baseRow The unexpanded row associated with
+ * the new row.
+ * @return {!HTMLTableRowElement} The created tr element for the expanded row.
*/
var createExpandedRow = function(guid, baseRow) {
- var expandedRow = document.createElement('tr');
+ var expandedRow = createTableRowElement();
expandedRow.className = 'state-table-row';
- var emptyCell = document.createElement('td');
+ var emptyCell = createTableCellElement();
emptyCell.style.border = 'none';
expandedRow.appendChild(emptyCell);
- var detailCell = document.createElement('td');
+ var detailCell = createTableCellElement();
+ detailCell.id = idFromGuid(guid);
detailCell.className = 'state-table-expanded-cell';
detailCell.colSpan = baseRow.childNodes.length - 1;
expandedRow.appendChild(detailCell);
- var showDetail = function(state) {
- if (networkConfig.lastError)
- detailCell.textContent = networkConfig.lastError;
+ var showDetail = function(state, error) {
+ if (error && error.message)
+ detailCell.textContent = error.message;
else
detailCell.textContent = JSON.stringify(state, null, '\t');
};
- var selected = $('get-network-type').selectedIndex;
- var selectedId = $('get-network-type').options[selected].id;
- if (selectedId == 'shill')
- networkConfig.getShillProperties(guid, showDetail);
- else if (selectedId == 'managed')
- networkConfig.getManagedProperties(guid, showDetail);
- else
- networkConfig.getProperties(guid, showDetail);
+ var selected = $('get-property-format').selectedIndex;
+ var selectedId = $('get-property-format').options[selected].value;
+ if (selectedId == 'shill') {
+ chrome.send('getShillProperties', [guid]);
+ } else if (selectedId == 'state') {
+ chrome.networkingPrivate.getState(guid, function(properties) {
+ showDetail(properties, chrome.runtime.lastError); });
+ } else if (selectedId == 'managed') {
+ chrome.networkingPrivate.getManagedProperties(guid, function(properties) {
+ showDetail(properties, chrome.runtime.lastError); });
+ } else {
+ chrome.networkingPrivate.getProperties(guid, function(properties) {
+ showDetail(properties, chrome.runtime.lastError); });
+ }
return expandedRow;
};
/**
- * Requests a network log update.
+ * Callback invoked by Chrome after a getShillProperties call.
+ *
+ * @param {Array} args The requested Shill properties. Will contain
+ * just the 'GUID' and 'ShillError' properties if the call failed.
*/
- var requestLog = function() {
- chrome.send('NetworkUI.getNetworkLog');
+ var getShillPropertiesResult = function(args) {
+ var properties = args.shift();
+ var guid = properties['GUID'];
+ if (!guid) {
+ console.error('No GUID in getShillPropertiesResult');
+ return;
+ }
+
+ var detailCell = document.querySelector('td#' + idFromGuid(guid));
+ if (!detailCell) {
+ console.error('No cell for GUID: ' + guid);
+ return;
+ }
+
+ if (properties['ShillError'])
+ detailCell.textContent = properties['ShillError'];
+ else
+ detailCell.textContent = JSON.stringify(properties, null, '\t');
+
};
/**
* Requests an update of all network info.
*/
var requestNetworks = function() {
- networkConfig.getNetworks(
- { 'type': 'All', 'visible': true },
- onVisibleNetworksReceived);
- networkConfig.getNetworks(
- { 'type': 'All', 'configured': true },
- onFavoriteNetworksReceived);
+ chrome.networkingPrivate.getNetworks(
+ {'networkType': 'All', 'visible': true}, onVisibleNetworksReceived);
+ chrome.networkingPrivate.getNetworks(
+ {'networkType': 'All', 'configured': true}, onFavoriteNetworksReceived);
};
/**
@@ -299,32 +333,19 @@ var NetworkUI = (function() {
var setRefresh = function() {
var interval = parseQueryParams(window.location)['refresh'];
if (interval && interval != '')
- setInterval(requestNetworks, parseInt(interval) * 1000);
+ setInterval(requestNetworks, parseInt(interval, 10) * 1000);
};
/**
- * Get network information from WebUI.
+ * Gets network information from WebUI.
*/
document.addEventListener('DOMContentLoaded', function() {
- $('log-refresh').onclick = requestLog;
- $('log-error').checked = true;
- $('log-error').onclick = requestLog;
- $('log-user').checked = true;
- $('log-user').onclick = requestLog;
- $('log-event').checked = true;
- $('log-event').onclick = requestLog;
- $('log-debug').checked = false;
- $('log-debug').onclick = requestLog;
- $('log-fileinfo').checked = false;
- $('log-fileinfo').onclick = requestLog;
- $('log-timedetail').checked = false;
- $('log-timedetail').onclick = requestLog;
+ $('refresh').onclick = requestNetworks;
setRefresh();
- requestLog();
requestNetworks();
});
return {
- getNetworkLogCallback: getNetworkLogCallback
+ getShillPropertiesResult: getShillPropertiesResult
};
})();
diff --git a/chromium/chrome/browser/resources/chromeos/nfc_debug.css b/chromium/chrome/browser/resources/chromeos/nfc_debug.css
index ee23a5cb8d6..9ffa968f66a 100644
--- a/chromium/chrome/browser/resources/chromeos/nfc_debug.css
+++ b/chromium/chrome/browser/resources/chromeos/nfc_debug.css
@@ -20,7 +20,6 @@ h1 {
}
div.entity-div {
- -webkit-box-shadow: 0 0 20px 2px #aaa;
box-shadow: 0 0 20px 2px #aaa;
display: inline-block;
margin: 10px 5px;
diff --git a/chromium/chrome/browser/resources/chromeos/nfc_debug.html b/chromium/chrome/browser/resources/chromeos/nfc_debug.html
index 5601d361202..6e5563e929d 100644
--- a/chromium/chrome/browser/resources/chromeos/nfc_debug.html
+++ b/chromium/chrome/browser/resources/chromeos/nfc_debug.html
@@ -1,8 +1,9 @@
-<!DOCTYPE html>
-<html i18n-values="dir:textdirection;">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
<head>
<meta charset="utf-8">
<title i18n-content="titleText"></title>
+ <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" type="text/css" href="nfc_debug.css">
<script src="chrome://resources/js/cr.js"></script>
<script src="chrome://resources/js/load_time_data.js"></script>
@@ -10,7 +11,7 @@
<script src="strings.js"></script>
<script src="nfc_debug.js"></script>
</head>
-<body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<body>
<h1 i18n-content="titleText"></h1>
<p id="nfc-not-supported" i18n-content="notSupportedText"></p>
<div id="wrapper">
@@ -125,6 +126,6 @@
</div>
</div>
</div>
- <script src="chrome://resources/js/i18n_template2.js"></script>
+ <script src="chrome://resources/js/i18n_template.js"></script>
</body>
</html>
diff --git a/chromium/chrome/browser/resources/chromeos/offline_app_load.html b/chromium/chrome/browser/resources/chromeos/offline_app_load.html
index dd02891a35e..4d84c310aa9 100644
--- a/chromium/chrome/browser/resources/chromeos/offline_app_load.html
+++ b/chromium/chrome/browser/resources/chromeos/offline_app_load.html
@@ -1,16 +1,16 @@
-<!DOCTYPE html>
-<html i18n-values="dir:textdirection">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
<head>
<meta charset="utf-8">
<title i18n-content="title">
</title>
+<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<style>
html {
height: 100%;
}
body {
color: #000;
- font-family: Helvetica, Arial, sans-serif;
background-image: -webkit-linear-gradient(rgb(255, 255, 255) 50%,
rgb(236, 244, 255));
height: 100%;
diff --git a/chromium/chrome/browser/resources/chromeos/offline_net_load.html b/chromium/chrome/browser/resources/chromeos/offline_net_load.html
index f70a931e337..496abd14ec6 100644
--- a/chromium/chrome/browser/resources/chromeos/offline_net_load.html
+++ b/chromium/chrome/browser/resources/chromeos/offline_net_load.html
@@ -1,47 +1,47 @@
-<!DOCTYPE html>
-<html i18n-values="dir:textdirection;.style.fontSize:fontsize">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0,
maximum-scale=1.0, user-scalable=no">
<title i18n-content="title"></title>
+ <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="../security_warnings/interstitial_v2.css">
<link rel="stylesheet" href="../../../renderer/resources/neterror.css">
<script src="../../../../ui/webui/resources/js/util.js"></script>
<script src="neterror.js"></script>
<script src="../../../renderer/resources/offline.js"></script>
</head>
-<body id="t" i18n-values=".style.fontFamily:fontfamily">
+<body id="t">
<div id="main-frame-error" class="interstitial-wrapper">
<img class="icon"
jseval="updateIconClass(this.classList, iconClass)">
<div id="main-message">
<h1 i18n-content="heading"></h1>
<p i18n-values=".innerHTML:primaryParagraph"></p>
- <div id="buttons" class="nav-wrapper">
- <button id="reload-button" onclick="location = this.url"
- jsselect="reload" jsvalues=".url:reloadUrl"
- jscontent="msg"></button>
+ <div id="buttons" class="nav-wrapper suggested-right">
+ <div id="control-buttons">
+ <button id="reload-button" onclick="location = this.url"
+ jsselect="reload" jsvalues=".url:reloadUrl"
+ jscontent="msg"></button>
+ </div>
<button id="details-button" class="text-button small-link"
- onclick="toggleHelpBox()" jsdisplay="more"
- jsvalues=".moreText:more; .lessText:less;"
- jscontent="more"></button>
+ onclick="toggleHelpBox()" jscontent="details"
+ jsvalues=".detailsText:details; .hideDetailsText:hideDetails;"
+ jsdisplay="details"></button>
</div>
</div>
- <!-- Outer and inner divs are needed both for margins and sizing. -->
- <div id="help-box-outer" class="hidden">
- <div id="details">
- <div jsselect="summary">
- <span jsvalues=".innerHTML:msg"></span>
- </div>
- <div class="suggestions" jsselect="suggestions">
- <div class="suggestion-header" jsvalues=".innerHTML:header"></div>
- <div class="suggestion-body" jsvalues=".innerHTML:body"></div>
- </div>
- <button id="diagnose-button" onclick="diagnoseErrors()"
- jscontent="diagnose" jsdisplay="diagnose"></button>
- <div class="error-code" jscontent="errorCode"></div>
+ <div id="details" class="hidden">
+ <div jsselect="summary">
+ <span jsvalues=".innerHTML:msg"></span>
</div>
+ <div class="suggestions" jsselect="suggestions">
+ <div class="suggestion-header" jsvalues=".innerHTML:header"></div>
+ <div class="suggestion-body" jsvalues=".innerHTML:body"></div>
+ </div>
+ <button id="diagnose-button" onclick="diagnoseErrors()"
+ jscontent="diagnose" jsdisplay="diagnose"></button>
+ <div class="error-code" jscontent="errorCode"></div>
</div>
</div>
<div id="sub-frame-error">
@@ -52,24 +52,8 @@
<div id="sub-frame-error-details" jsvalues=".innerHTML:errorDetails"></div>
</div>
<div id="offline-resources">
- <div id="offline-resources-1x">
- <img id="1x-obstacle-large" src="../../../renderer/resources/default_100_percent/offline/100-obstacle-large-sprite.png">
- <img id="1x-obstacle-small" src="../../../renderer/resources/default_100_percent/offline/100-obstacle-small-sprite.png">
- <img id="1x-cloud" src="../../../renderer/resources/default_100_percent/offline/100-cloud.png">
- <img id="1x-text" src="../../../renderer/resources/default_100_percent/offline/100-text-sprite.png">
- <img id="1x-horizon" src="../../../renderer/resources/default_100_percent/offline/100-horizon.png">
- <img id="1x-trex" src="../../../renderer/resources/default_100_percent/offline/100-offline-trex.png">
- <img id="1x-restart" src="../../../renderer/resources/default_100_percent/offline/100-restart.png">
- </div>
- <div id="offline-resources-2x">
- <img id="2x-obstacle-large" src="../../../renderer/resources/default_200_percent/offline/200-obstacle-large-sprite.png">
- <img id="2x-obstacle-small" src="../../../renderer/resources/default_200_percent/offline/200-obstacle-small-sprite.png">
- <img id="2x-cloud" src="../../../renderer/resources/default_200_percent/offline/200-cloud.png">
- <img id="2x-text" src="../../../renderer/resources/default_200_percent/offline/200-text-sprite.png">
- <img id="2x-horizon" src="../../../renderer/resources/default_200_percent/offline/200-horizon.png">
- <img id="2x-trex" src="../../../renderer/resources/default_200_percent/offline/200-offline-trex.png">
- <img id="2x-restart" src="../../../renderer/resources/default_200_percent/offline/200-restart.png">
- </div>
+ <img id="offline-resources-1x" src="../../../renderer/resources/default_100_percent/offline/100-offline-sprite.png">
+ <img id="offline-resources-2x" src="../../../renderer/resources/default_200_percent/offline/200-offline-sprite.png">
<template id="audio-resources">
<audio id="offline-sound-press" src="../../../renderer/resources/sounds/button-press.mp3"></audio>
<audio id="offline-sound-hit" src="../../../renderer/resources/sounds/hit.mp3"></audio>
diff --git a/chromium/chrome/browser/resources/chromeos/power.html b/chromium/chrome/browser/resources/chromeos/power.html
index 1ea443e04ac..4cebe5ac99b 100644
--- a/chromium/chrome/browser/resources/chromeos/power.html
+++ b/chromium/chrome/browser/resources/chromeos/power.html
@@ -1,15 +1,16 @@
-<!DOCTYPE HTML>
+<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title i18n-content="titleText"></title>
+ <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="chrome://power/power.css">
<script src="chrome://resources/js/load_time_data.js"></script>
<script src="chrome://resources/js/util.js"></script>
<script src="chrome://power/strings.js"></script>
<script src="chrome://power/power.js"></script>
</head>
-<body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<body>
<table id="main-table">
<tr class="section-row">
<td class="title-cell">
@@ -76,6 +77,6 @@
</td>
</tr>
</table>
- <script src="chrome://resources/js/i18n_template2.js"></script>
+ <script src="chrome://resources/js/i18n_template.js"></script>
</body>
</html>
diff --git a/chromium/chrome/browser/resources/chromeos/power.js b/chromium/chrome/browser/resources/chromeos/power.js
index 3249e860876..98c38de54c0 100644
--- a/chromium/chrome/browser/resources/chromeos/power.js
+++ b/chromium/chrome/browser/resources/chromeos/power.js
@@ -9,9 +9,9 @@
* drawn.
* @param {HTMLCanvasElement} legendCanvas The canvas on which the legend for
* the line graph is drawn.
- * @param {Array.<number>} tData The time (in seconds) in the past when the
+ * @param {Array<number>} tData The time (in seconds) in the past when the
* corresponding data in plots was sampled.
- * @param {Array.<{data: Array.<number>, color: string}>} plots An
+ * @param {Array<{data: Array<number>, color: string}>} plots An
* array of plots to plot on the canvas. The field 'data' of a plot is an
* array of samples to be plotted as a line graph with color speficied by
* the field 'color'. The elements in the 'data' array are ordered
@@ -397,7 +397,7 @@ var plotColors = ['Red', 'Blue', 'Green', 'Gold', 'CadetBlue', 'LightCoral',
* Add canvases for plotting to |plotsDiv|. For every header in |headerArray|,
* one canvas for the plot and one for its legend are added.
*
- * @param {Array.<string>} headerArray Headers for the different plots to be
+ * @param {Array<string>} headerArray Headers for the different plots to be
* added to |plotsDiv|.
* @param {HTMLDivElement} plotsDiv The div element into which the canvases
* are added.
@@ -443,22 +443,23 @@ function addCanvases(headerArray, plotsDiv) {
* resumed from a sleep/suspend, then "suspended" sleep samples are added to
* the plot for the sleep duration.
*
- * @param {Array.<{data: Array.<number>, color: string}>} plots An
+ * @param {Array<{data: Array<number>, color: string}>} plots An
* array of plots to plot on the canvas. The field 'data' of a plot is an
* array of samples to be plotted as a line graph with color speficied by
* the field 'color'. The elements in the 'data' array are ordered
* corresponding to their sampling time in the argument 'tData'. Also, the
* number of elements in the 'data' array should be the same as in the time
* array 'tData' below.
- * @param {Array.<number>} tData The time (in seconds) in the past when the
+ * @param {Array<number>} tData The time (in seconds) in the past when the
* corresponding data in plots was sampled.
- * @param {Array.<number>} sampleArray The array of samples wherein each
+ * @param {Array<number>} absTime
+ * @param {Array<number>} sampleArray The array of samples wherein each
* element corresponds to the individual plot in |plots|.
* @param {number} sampleTime Time in milliseconds since the epoch when the
* samples in |sampleArray| were captured.
* @param {number} previousSampleTime Time in milliseconds since the epoch
* when the sample prior to the current sample was captured.
- * @param {Array.<{time: number, sleepDuration: number}>} systemResumedArray An
+ * @param {Array<{time: number, sleepDuration: number}>} systemResumedArray An
* array objects corresponding to system resume events. The 'time' field is
* for the time in milliseconds since the epoch when the system resumed. The
* 'sleepDuration' field is for the time in milliseconds the system spent
@@ -519,14 +520,14 @@ function addTimeDataSample(plots, tData, absTime, sampleArray,
/**
* Display the battery charge vs time on a line graph.
*
- * @param {Array.<{time: number,
+ * @param {Array<{time: number,
* batteryPercent: number,
* batteryDischargeRate: number,
* externalPower: number}>} powerSupplyArray An array of objects
* with fields representing the battery charge, time when the charge
* measurement was taken, and whether there was external power connected at
* that time.
- * @param {Array.<{time: ?, sleepDuration: ?}>} systemResumedArray An array
+ * @param {Array<{time: ?, sleepDuration: ?}>} systemResumedArray An array
* objects with fields 'time' and 'sleepDuration'. Each object corresponds
* to a system resume event. The 'time' field is for the time in
* milliseconds since the epoch when the system resumed. The 'sleepDuration'
@@ -649,13 +650,13 @@ function showBatteryChargeData(powerSupplyArray, systemResumedArray) {
* Shows state occupancy data (CPU idle or CPU freq state occupancy) on a set of
* plots on the about:power UI.
*
- * @param {Array.<{Array.<{
+ * @param {Array<Array<{
* time: number,
- * cpuOnline:boolean,
- * timeInState: {<string>: number}>}>} timeInStateData Array of arrays
+ * cpuOnline: boolean,
+ * timeInState: Object<number>}>} timeInStateData Array of arrays
* where each array corresponds to a CPU on the system. The elements of the
* individual arrays contain state occupancy samples.
- * @param {Array.<{time: ?, sleepDuration: ?}>} systemResumedArray An array
+ * @param {Array<{time: ?, sleepDuration: ?}>} systemResumedArray An array
* objects with fields 'time' and 'sleepDuration'. Each object corresponds
* to a system resume event. The 'time' field is for the time in
* milliseconds since the epoch when the system resumed. The 'sleepDuration'
diff --git a/chromium/chrome/browser/resources/chromeos/provided_file_systems.css b/chromium/chrome/browser/resources/chromeos/provided_file_systems.css
index 6247fa90bfa..3a62bb30184 100644
--- a/chromium/chrome/browser/resources/chromeos/provided_file_systems.css
+++ b/chromium/chrome/browser/resources/chromeos/provided_file_systems.css
@@ -5,7 +5,6 @@
body {
background-color: rgba(0, 0, 0, 0.05);
- font-family: sans-serif;
font-size: 14px;
margin: 20px;
}
diff --git a/chromium/chrome/browser/resources/chromeos/provided_file_systems.html b/chromium/chrome/browser/resources/chromeos/provided_file_systems.html
index cd67f8eb99e..384464815e6 100644
--- a/chromium/chrome/browser/resources/chromeos/provided_file_systems.html
+++ b/chromium/chrome/browser/resources/chromeos/provided_file_systems.html
@@ -3,6 +3,7 @@
<head>
<title>Provided File Systems</title>
<meta charset="utf-8">
+ <link rel="stylesheet" href="chrome://resources/css/chrome_shared.css">
<link rel="stylesheet" href="provided_file_systems.css">
<script src="chrome://provided-file-systems/provided_file_systems.js">
</script>
diff --git a/chromium/chrome/browser/resources/chromeos/provided_file_systems.js b/chromium/chrome/browser/resources/chromeos/provided_file_systems.js
index afdde09462e..007bf00e947 100644
--- a/chromium/chrome/browser/resources/chromeos/provided_file_systems.js
+++ b/chromium/chrome/browser/resources/chromeos/provided_file_systems.js
@@ -2,8 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-<include src="../../../../third_party/polymer_legacy/platform/platform.js">
-<include src="../../../../third_party/polymer_legacy/polymer/polymer.js">
+<include src="../../../../third_party/polymer/components/polymer/polymer.js">
/**
* Formats size to a human readable form.
@@ -47,7 +46,7 @@ Polymer('file-systems', {
/**
* List of provided file system information maps.
- * @type {Array.<Object>}
+ * @type {Array<Object>}
*/
model: []
});
@@ -107,7 +106,7 @@ Polymer('request-events', {
/**
* List of events.
- * @type {Array.<Object>}
+ * @type {Array<Object>}
*/
model: []
});
@@ -130,7 +129,7 @@ Polymer('request-timeline', {
/**
* Observes changes in the model.
- * @type {Object.<string, string>}
+ * @type {Object<string, string>}
*/
observe: {
'model.length': 'chartUpdate'
@@ -318,26 +317,26 @@ Polymer('request-timeline', {
/**
* Map of selected requests.
- * @type {Object.<number, boolean>}
+ * @type {Object<number, boolean>}
*/
selected: {},
/**
* Map of requests which has started, but are not completed yet, from
* a request id to the chart element index.
- * @type {Object.<number, number>}}
+ * @type {Object<number, number>}}
*/
active: {},
/**
* List of chart elements, calculated from the model.
- * @type {Array.<Object>}
+ * @type {Array<Object>}
*/
chart: [],
/**
* List of rows in the chart, with the last endTime value on it.
- * @type {Array.<Object>}
+ * @type {Array<Object>}
*/
rows: [],
@@ -368,14 +367,14 @@ Polymer('request-timeline', {
/**
* List of requests information maps.
- * @type {Array.<Object>}
+ * @type {Array<Object>}
*/
model: []
});
/*
* Updates the mounted file system list.
- * @param {Array.<Object>} fileSystems Array containing provided file system
+ * @param {Array<Object>} fileSystems Array containing provided file system
* information.
*/
function updateFileSystems(fileSystems) {
diff --git a/chromium/chrome/browser/resources/chromeos/proxy_settings.html b/chromium/chrome/browser/resources/chromeos/proxy_settings.html
index a5153aac2e4..061eaf803ee 100644
--- a/chromium/chrome/browser/resources/chromeos/proxy_settings.html
+++ b/chromium/chrome/browser/resources/chromeos/proxy_settings.html
@@ -1,5 +1,5 @@
-<!DOCTYPE HTML>
-<html i18n-values="dir:textdirection;highlight:highlightStrength">
+<!doctype html>
+<html i18n-values="dir:textdirection;highlight:highlightStrength;lang:language">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="chrome://resources/css/chrome_shared.css">
@@ -35,7 +35,7 @@
<script src="proxy_settings.js"></script>
</head>
-<body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<body>
<include src="../options/chromeos/internet_detail.html">
</body>
</html>
diff --git a/chromium/chrome/browser/resources/chromeos/salsa.css b/chromium/chrome/browser/resources/chromeos/salsa.css
index f2c348526e6..b4a2b3eee29 100644
--- a/chromium/chrome/browser/resources/chromeos/salsa.css
+++ b/chromium/chrome/browser/resources/chromeos/salsa.css
@@ -11,15 +11,13 @@
width: 100%;
}
-.entry-key {
- font-size: small;
-}
-
+.entry-key,
.entry-value {
font-size: small;
}
-.bold {
+p,
+span {
font-weight: bold;
}
diff --git a/chromium/chrome/browser/resources/chromeos/salsa.html b/chromium/chrome/browser/resources/chromeos/salsa.html
index 22cbfb10e81..50d6775f452 100644
--- a/chromium/chrome/browser/resources/chromeos/salsa.html
+++ b/chromium/chrome/browser/resources/chromeos/salsa.html
@@ -1,8 +1,9 @@
-<!DOCTYPE HTML>
+<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Salsa</title>
+ <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="salsa.css">
<script src="salsa.js"></script>
</head>
@@ -11,24 +12,24 @@
<div id="bounding-box">
<h1 class="title">Salsa</h1>
- <div id="invalid_treatment_info" hidden>
+ <div id="invalid-treatment-info" hidden>
I'm afraid there has been some problem determining which treatments to use
for your experiment. Please try to copy/paste the URL again and refresh
this page. If the problem persists please contact the person running the
experiment.
</div>
- <div id="valid_treatment_info">
+ <div id="valid-treatment-info">
<div id="treatment-list">
<div id="treatment-header">Available Treatments:</div>
<div id="treatment-template" class="treatment" hidden></div>
</div>
- <p class='bold'>Instructions</p>
+ <p>Instructions</p>
<ul id="instructions">
- <li> <span class='bold'>Click</span> or use your <span class='bold'>arrow
- keys</span> to change the selected treatment.
+ <li> <span>Click</span> or use your <span>arrow keys</span> to change the
+ selected treatment.
<li> Treatments are ordered randomly, so there is no special significance
to their labels.
@@ -36,10 +37,10 @@
<li> When you navigate away from this page your settings will be returned
to normal. If the browser crashes, and/or you experience unusual behavior
after this experiment, you can manually reset your settings to their
- default values at <a href="chrome://gesture">http://gesture</a>.
+ default values at <a href="chrome://gesture">chrome://gesture</a>.
</ul>
- <p class='bold'>Thank you for your participation!</p>
+ <p>Thank you for your participation!</p>
</div>
</div>
diff --git a/chromium/chrome/browser/resources/chromeos/salsa.js b/chromium/chrome/browser/resources/chromeos/salsa.js
index 4525b1c8402..ef036928ec8 100644
--- a/chromium/chrome/browser/resources/chromeos/salsa.js
+++ b/chromium/chrome/browser/resources/chromeos/salsa.js
@@ -91,19 +91,19 @@ function handleKeyPress(e) {
applyTreatment(selectedTreatment);
}
-function applyTreatment(treatment_number) {
- if (treatment_number < 0)
- treatment_number = 0;
- if (treatment_number >= treatments.length)
- treatment_number = treatments.length;
+function applyTreatment(treatmentNumber) {
+ if (treatmentNumber < 0)
+ treatmentNumber = 0;
+ if (treatmentNumber >= treatments.length)
+ treatmentNumber = treatments.length;
$('treatment' + currentTreatment).className = 'treatment';
- currentTreatment = treatment_number;
+ currentTreatment = treatmentNumber;
$('treatment' + currentTreatment).className = 'selected treatment';
- for (var i = 0; i < treatments[treatment_number].length; i++) {
- var key = treatments[treatment_number][i].key;
- var value = treatments[treatment_number][i].value;
+ for (var i = 0; i < treatments[treatmentNumber].length; i++) {
+ var key = treatments[treatmentNumber][i].key;
+ var value = treatments[treatmentNumber][i].value;
setPreferenceValue(key, value);
}
}
@@ -133,8 +133,8 @@ function initialize() {
applyTreatment(0);
} else {
// Make the error message visible and hide everything else
- $('invalid_treatment_info').removeAttribute('hidden');
- var div = $('valid_treatment_info');
+ $('invalid-treatment-info').hidden = false;
+ var div = $('valid-treatment-info');
div.parentNode.removeChild(div);
}
}
diff --git a/chromium/chrome/browser/resources/chromeos/set_time.html b/chromium/chrome/browser/resources/chromeos/set_time.html
index 590f47e7612..05013522c45 100644
--- a/chromium/chrome/browser/resources/chromeos/set_time.html
+++ b/chromium/chrome/browser/resources/chromeos/set_time.html
@@ -1,5 +1,5 @@
-<!DOCTYPE html>
-<html i18n-values="dir:textdirection;">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
<head>
<meta charset="utf-8">
<title i18n-content="setTimeTitle"></title>
@@ -8,7 +8,7 @@
<link rel="stylesheet" href="set_time.css">
</head>
-<body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<body>
<form id="set-time">
<h2 i18n-content="setTimeTitle"></h2>
<p id="prompt" i18n-content="prompt" class="row"></p>
@@ -34,6 +34,6 @@
<script src="chrome://resources/js/util.js"></script>
<script src="strings.js"></script>
<script src="set_time.js"></script>
-<script src="chrome://resources/js/i18n_template2.js"></script>
+<script src="chrome://resources/js/i18n_template.js"></script>
</body>
</html>
diff --git a/chromium/chrome/browser/resources/chromeos/sim_unlock.css b/chromium/chrome/browser/resources/chromeos/sim_unlock.css
index d552febe98b..9e9d588f419 100644
--- a/chromium/chrome/browser/resources/chromeos/sim_unlock.css
+++ b/chromium/chrome/browser/resources/chromeos/sim_unlock.css
@@ -50,8 +50,8 @@
}
.code-input {
- -webkit-border-radius: 2px;
border: 1px solid #aaa;
+ border-radius: 2px;
font-size: inherit;
padding: 3px;
}
@@ -92,7 +92,7 @@
.choose-pin-input-area {
margin: 5px 5px 5px 5px;
- width: 125px;
+ width: 125px;
}
#choose-pin-action-area {
diff --git a/chromium/chrome/browser/resources/chromeos/sim_unlock.html b/chromium/chrome/browser/resources/chromeos/sim_unlock.html
index d4cc2343e9b..c6c2d368814 100644
--- a/chromium/chrome/browser/resources/chromeos/sim_unlock.html
+++ b/chromium/chrome/browser/resources/chromeos/sim_unlock.html
@@ -1,8 +1,9 @@
-<!DOCTYPE HTML>
-<html i18n-values="dir:textdirection">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
<head>
<meta charset="utf-8">
<title i18n-content="title"></title>
+<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="chrome://resources/css/widgets.css">
<link rel="stylesheet" href="mobile_dialogs.css">
<link rel="stylesheet" href="sim_unlock.css">
@@ -20,7 +21,7 @@ function load() {
document.addEventListener('DOMContentLoaded', load);
</script>
</head>
-<body i18n-values=".style.fontFamily:fontfamily;">
+<body>
<div id="container" class="container">
<div id="locked-pin-overlay" class="dialog" hidden>
<h1 i18n-content="enterPinTitle"></h1>
diff --git a/chromium/chrome/browser/resources/chromeos/slow.css b/chromium/chrome/browser/resources/chromeos/slow.css
index fec23696afe..ceca2c3a468 100644
--- a/chromium/chrome/browser/resources/chromeos/slow.css
+++ b/chromium/chrome/browser/resources/chromeos/slow.css
@@ -5,7 +5,6 @@
body {
background-color: #E6E6E6;
- font-family: Helvetica, Arial, sans-serif;
font-size: 12pt;
margin: 50px 40px 20px 40px;
text-align: center;
diff --git a/chromium/chrome/browser/resources/chromeos/slow.html b/chromium/chrome/browser/resources/chromeos/slow.html
index e4e3bc2e745..8ed682e44d0 100644
--- a/chromium/chrome/browser/resources/chromeos/slow.html
+++ b/chromium/chrome/browser/resources/chromeos/slow.html
@@ -1,9 +1,10 @@
-<!DOCTYPE HTML>
-<html i18n-values="dir:textdirection">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0,
maximum-scale=1.0, user-scalable=no">
+<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="chrome://resources/css/widgets.css">
<link rel="stylesheet" href="slow.css">
<script src="chrome://resources/js/load_time_data.js"></script>
@@ -13,7 +14,7 @@
<script src="chrome://slow/slow.js"></script>
<script src="chrome://slow/strings.js"></script>
</head>
-<body i18n-values=".style.fontFamily:fontfamily;">
+<body>
<div id="container" class="container">
<div i18n-content="slowDescription"></div>
<br>
@@ -23,7 +24,7 @@
<button id="slow-enable" i18n-content="slowEnable" hidden></button>
</div>
</div>
-<script src="chrome://resources/js/i18n_template2.js"></script>
+<script src="chrome://resources/js/i18n_template.js"></script>
<script src="chrome://resources/js/jstemplate_compiled.js"></script>
</body>
</html>
diff --git a/chromium/chrome/browser/resources/chromeos/user_images_grid.js b/chromium/chrome/browser/resources/chromeos/user_images_grid.js
index c0fd526804a..aaecf7af964 100644
--- a/chromium/chrome/browser/resources/chromeos/user_images_grid.js
+++ b/chromium/chrome/browser/resources/chromeos/user_images_grid.js
@@ -54,6 +54,7 @@ cr.define('options', function() {
else
imageEl.src = this.dataItem.url;
imageEl.title = this.dataItem.title || '';
+ imageEl.alt = imageEl.title;
if (typeof this.dataItem.clickHandler == 'function')
imageEl.addEventListener('mousedown', this.dataItem.clickHandler);
// Remove any garbage added by GridItem and ListItem decorators.
@@ -634,6 +635,22 @@ cr.define('options', function() {
this.columns = 0;
this.redraw();
this.focus();
+ },
+
+ /**
+ * Appends default images to the image grid. Should only be called once.
+ * @param {Array<{url: string, author: string,
+ * website: string, title: string}>} imagesData
+ * An array of default images data, including URL, author, title and
+ * website.
+ */
+ setDefaultImages: function(imagesData) {
+ for (var i = 0, data; data = imagesData[i]; i++) {
+ var item = this.addItem(data.url, data.title);
+ item.type = 'default';
+ item.author = data.author || '';
+ item.website = data.website || '';
+ }
}
};
diff --git a/chromium/chrome/browser/resources/chromeos/wallpaper_manager/css/wallpaper_manager.css b/chromium/chrome/browser/resources/chromeos/wallpaper_manager/css/wallpaper_manager.css
index 6ed4ada916f..65683e38e06 100644
--- a/chromium/chrome/browser/resources/chromeos/wallpaper_manager/css/wallpaper_manager.css
+++ b/chromium/chrome/browser/resources/chromeos/wallpaper_manager/css/wallpaper_manager.css
@@ -101,11 +101,11 @@ body {
}
#window-close-button {
- background-image: url('../images/ui/close-white.png');
+ background-image: url(../images/ui/close-white.png);
}
#window-close-button:hover {
- background-image: url('../images/ui/close-white-hover.png');
+ background-image: url(../images/ui/close-white-hover.png);
}
#bar {
@@ -122,9 +122,7 @@ body {
}
#category-container {
- display: flex;
flex: 1;
- flex-direction: column;
position: relative;
}
@@ -154,7 +152,7 @@ body {
-webkit-animation-iteration-count: infinite;
-webkit-animation-name: spin;
-webkit-animation-timing-function: linear;
- background-image: url('../images/ui/spinner.png');
+ background-image: url(../images/ui/spinner.png);
background-position: center;
background-repeat: no-repeat;
height: 16px;
@@ -175,12 +173,12 @@ body {
-webkit-padding-end: 5px;
-webkit-padding-start: 15px;
display: block;
- flex: 1;
/* Set font size to 0 to remove the extra vertical margin between two rows of
* thumbnails.
* TODO(bshe): Find the root cause of the margin.
*/
font-size: 0;
+ height: 287px;
outline: none;
overflow-y: auto;
padding-bottom: 15px;
@@ -212,7 +210,7 @@ body {
}
.image-picker .check {
- background-image: url('../images/ui/check.png');
+ background-image: url(../images/ui/check.png);
height: 32px;
left: 38px;
position: absolute;
@@ -326,9 +324,9 @@ body:not([custom]) [visibleif~='custom'] {
}
.overlay-container .page {
- -webkit-border-radius: 3px;
-webkit-box-orient: vertical;
background: rgb(255, 255, 255);
+ border-radius: 3px;
box-shadow: 0 4px 23px 5px rgba(0, 0, 0, 0.2), 0 2px 6px rgba(0, 0, 0, 0.15);
color: #333;
display: -webkit-box;
@@ -342,20 +340,20 @@ body:not([custom]) [visibleif~='custom'] {
}
.overlay-container .close {
- background-image: url('../images/ui/close-overlay.png');
+ background-image: url(../images/ui/close-overlay.png);
}
.overlay-container .close:hover {
- background-image: url('../images/ui/close-overlay-hover.png');
+ background-image: url(../images/ui/close-overlay-hover.png);
}
/* Custom wallpaper thumbnail container. */
#add-new img {
- content: url('../images/ui/add-wallpaper-thumbnail.png');
+ content: url(../images/ui/add-wallpaper-thumbnail.png);
}
.image-picker:not([disabled]) #add-new:hover img {
- content: url('../images/ui/add-wallpaper-thumbnail-hover.png');
+ content: url(../images/ui/add-wallpaper-thumbnail-hover.png);
}
#wallpaper-selection-container #content div {
@@ -375,11 +373,11 @@ body:not([custom]) [visibleif~='custom'] {
}
#wallpaper-selection-container .remember-icon {
- content: url('../images/ui/remember.png');
+ content: url(../images/ui/remember.png);
}
#surprise-me #checkbox {
- background-image: url('../images/ui/checkbox_unchecked.png');
+ background-image: url(../images/ui/checkbox_unchecked.png);
display: inline-block;
height: 17px;
margin-right: 4px;
@@ -390,5 +388,5 @@ body:not([custom]) [visibleif~='custom'] {
}
#surprise-me #checkbox.checked {
- background-image: url('../images/ui/checkbox_checked.png');
+ background-image: url(../images/ui/checkbox_checked.png);
}
diff --git a/chromium/chrome/browser/resources/chromeos/wallpaper_manager/images/icon128.png b/chromium/chrome/browser/resources/chromeos/wallpaper_manager/images/icon128.png
index dfba48ceb8b..7e9e04ffb30 100644
--- a/chromium/chrome/browser/resources/chromeos/wallpaper_manager/images/icon128.png
+++ b/chromium/chrome/browser/resources/chromeos/wallpaper_manager/images/icon128.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/chromeos/wallpaper_manager/images/icon16.png b/chromium/chrome/browser/resources/chromeos/wallpaper_manager/images/icon16.png
new file mode 100644
index 00000000000..cfb31c1985b
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/wallpaper_manager/images/icon16.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/chromeos/wallpaper_manager/images/icon256.png b/chromium/chrome/browser/resources/chromeos/wallpaper_manager/images/icon256.png
new file mode 100644
index 00000000000..2829e20bdc6
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/wallpaper_manager/images/icon256.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/chromeos/wallpaper_manager/images/icon32.png b/chromium/chrome/browser/resources/chromeos/wallpaper_manager/images/icon32.png
new file mode 100644
index 00000000000..51dd168dc1e
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/wallpaper_manager/images/icon32.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/chromeos/wallpaper_manager/images/icon48.png b/chromium/chrome/browser/resources/chromeos/wallpaper_manager/images/icon48.png
new file mode 100644
index 00000000000..e310aa1a0a3
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/wallpaper_manager/images/icon48.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/chromeos/wallpaper_manager/images/icon64.png b/chromium/chrome/browser/resources/chromeos/wallpaper_manager/images/icon64.png
new file mode 100644
index 00000000000..d87afbf773c
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/wallpaper_manager/images/icon64.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/chromeos/wallpaper_manager/images/icon96.png b/chromium/chrome/browser/resources/chromeos/wallpaper_manager/images/icon96.png
new file mode 100644
index 00000000000..6c7863ae1e7
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/wallpaper_manager/images/icon96.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/chromeos/wallpaper_manager/js/constants.js b/chromium/chrome/browser/resources/chromeos/wallpaper_manager/js/constants.js
index 93de962b9a1..8001c1621ee 100644
--- a/chromium/chrome/browser/resources/chromeos/wallpaper_manager/js/constants.js
+++ b/chromium/chrome/browser/resources/chromeos/wallpaper_manager/js/constants.js
@@ -5,14 +5,14 @@
/** @const */ var Constants = {
/**
- * Key to access wallpaper rss in chrome.local.storage.
+ * Key to access wallpaper rss in chrome.storage.local.
*/
- AccessRssKey: 'wallpaper-picker-surprise-rss-key',
+ AccessLocalRssKey: 'wallpaper-picker-surprise-rss-key',
/**
* Key to access wallpaper manifest in chrome.storage.local.
*/
- AccessManifestKey: 'wallpaper-picker-manifest-key',
+ AccessLocalManifestKey: 'wallpaper-picker-manifest-key',
/**
* Key to access user wallpaper info in chrome.storage.local.
@@ -25,15 +25,22 @@
AccessSyncWallpaperInfoKey: 'wallpaper-sync-info-key',
/**
- * Key to access last changed date of a surprise wallpaper.
+ * Key to access last changed date of a surprise wallpaper in
+ * chrome.storage.local or chrome.storage.sync.
*/
AccessLastSurpriseWallpaperChangedDate: 'wallpaper-last-changed-date-key',
/**
* Key to access if surprise me feature is enabled or not in
- * chrome.local.storage.
+ * chrome.storage.local.
*/
- AccessSurpriseMeEnabledKey: 'surprise-me-enabled-key',
+ AccessLocalSurpriseMeEnabledKey: 'surprise-me-enabled-key',
+
+ /**
+ * Key to access if surprise me feature is enabled or not in
+ * chrome.storage.sync.
+ */
+ AccessSyncSurpriseMeEnabledKey: 'sync-surprise-me-enabled-key',
/**
* Suffix to append to baseURL if requesting high resoultion wallpaper.
diff --git a/chromium/chrome/browser/resources/chromeos/wallpaper_manager/js/event_page.js b/chromium/chrome/browser/resources/chromeos/wallpaper_manager/js/event_page.js
index 2a6b9a9581a..ec17c1fe2ec 100644
--- a/chromium/chrome/browser/resources/chromeos/wallpaper_manager/js/event_page.js
+++ b/chromium/chrome/browser/resources/chromeos/wallpaper_manager/js/event_page.js
@@ -29,15 +29,19 @@ SurpriseWallpaper.getInstance = function() {
*/
SurpriseWallpaper.prototype.tryChangeWallpaper = function() {
var self = this;
- var onFailure = function() {
- self.retryLater_();
- self.fallbackToLocalRss_();
+ var onFailure = function(status) {
+ if (status != 404)
+ self.fallbackToLocalRss_();
+ else
+ self.updateRandomWallpaper_();
};
- // Try to fetch newest rss as document from server first. If any error occurs,
- // proceed with local copy of rss.
+ // Try to fetch newest rss as document from server first. If the requested
+ // URL is not found (404 error), set a random wallpaper displayed in the
+ // wallpaper picker. If any other error occurs, proceed with local copy of
+ // rss.
WallpaperUtil.fetchURL(Constants.WallpaperRssURL, 'document', function(xhr) {
- WallpaperUtil.saveToStorage(Constants.AccessRssKey,
- new XMLSerializer().serializeToString(xhr.responseXML), false);
+ WallpaperUtil.saveToLocalStorage(Constants.AccessLocalRssKey,
+ new XMLSerializer().serializeToString(xhr.responseXML));
self.updateSurpriseWallpaper(xhr.responseXML);
}, onFailure);
};
@@ -58,8 +62,9 @@ SurpriseWallpaper.prototype.retryLater_ = function() {
*/
SurpriseWallpaper.prototype.fallbackToLocalRss_ = function() {
var self = this;
- Constants.WallpaperLocalStorage.get(Constants.AccessRssKey, function(items) {
- var rssString = items[Constants.AccessRssKey];
+ Constants.WallpaperLocalStorage.get(Constants.AccessLocalRssKey,
+ function(items) {
+ var rssString = items[Constants.AccessLocalRssKey];
if (rssString) {
self.updateSurpriseWallpaper(new DOMParser().parseFromString(rssString,
'text/xml'));
@@ -94,9 +99,11 @@ SurpriseWallpaper.prototype.updateSurpriseWallpaper = function(opt_rss) {
var self = this;
this.setWallpaperFromRssItem_(item,
function() {},
- function() {
- self.retryLater_();
- self.updateRandomWallpaper_();
+ function(status) {
+ if (status != 404)
+ self.retryLater_();
+ else
+ self.updateRandomWallpaper_();
});
return;
}
@@ -113,13 +120,21 @@ SurpriseWallpaper.prototype.updateSurpriseWallpaper = function(opt_rss) {
*/
SurpriseWallpaper.prototype.updateRandomWallpaper_ = function() {
var self = this;
- Constants.WallpaperSyncStorage.get(
- Constants.AccessLastSurpriseWallpaperChangedDate, function(items) {
+ var onSuccess = function(items) {
var dateString = new Date().toDateString();
// At most one random wallpaper per day.
if (items[Constants.AccessLastSurpriseWallpaperChangedDate] != dateString) {
self.setRandomWallpaper_(dateString);
}
+ };
+ WallpaperUtil.enabledSyncThemesCallback(function(syncEnabled) {
+ if (syncEnabled) {
+ Constants.WallpaperSyncStorage.get(
+ Constants.AccessLastSurpriseWallpaperChangedDate, onSuccess);
+ } else {
+ Constants.WallpaperLocalStorage.get(
+ Constants.AccessLastSurpriseWallpaperChangedDate, onSuccess);
+ }
});
};
@@ -132,9 +147,9 @@ SurpriseWallpaper.prototype.updateRandomWallpaper_ = function() {
*/
SurpriseWallpaper.prototype.setRandomWallpaper_ = function(dateString) {
var self = this;
- Constants.WallpaperLocalStorage.get(Constants.AccessManifestKey,
+ Constants.WallpaperLocalStorage.get(Constants.AccessLocalManifestKey,
function(items) {
- var manifest = items[Constants.AccessManifestKey];
+ var manifest = items[Constants.AccessLocalManifestKey];
if (manifest && manifest.wallpaper_list) {
var filtered = manifest.wallpaper_list.filter(function(element) {
// Older version manifest do not have available_for_surprise_me field.
@@ -146,10 +161,15 @@ SurpriseWallpaper.prototype.setRandomWallpaper_ = function(dateString) {
var wallpaper = filtered[index];
var wallpaperURL = wallpaper.base_url + Constants.HighResolutionSuffix;
var onSuccess = function() {
- WallpaperUtil.saveToStorage(
+ WallpaperUtil.saveWallpaperInfo(wallpaperURL, wallpaper.default_layout,
+ Constants.WallpaperSourceEnum.Online);
+ WallpaperUtil.saveToLocalStorage(
Constants.AccessLastSurpriseWallpaperChangedDate,
- dateString,
- true);
+ dateString, function() {
+ WallpaperUtil.saveToSyncStorage(
+ Constants.AccessLastSurpriseWallpaperChangedDate,
+ dateString);
+ });
};
WallpaperUtil.setOnlineWallpaper(wallpaperURL, wallpaper.default_layout,
onSuccess, self.retryLater_.bind(self));
@@ -177,8 +197,17 @@ SurpriseWallpaper.prototype.setWallpaperFromRssItem_ = function(item,
chrome.wallpaperPrivate.setCustomWallpaper(xhr.response, layout, false,
'surprise_wallpaper',
onSuccess);
+ WallpaperUtil.saveWallpaperInfo(url, layout,
+ Constants.WallpaperSourceEnum.Online);
+ var dateString = new Date().toDateString();
+ WallpaperUtil.saveToLocalStorage(
+ Constants.AccessLastSurpriseWallpaperChangedDate,
+ dateString, function() {
+ WallpaperUtil.saveToSyncStorage(
+ Constants.AccessLastSurpriseWallpaperChangedDate, dataString);
+ });
} else {
- onFailure();
+ self.updateRandomWallpaper_();
}
}, onFailure);
};
@@ -189,8 +218,11 @@ SurpriseWallpaper.prototype.setWallpaperFromRssItem_ = function(item,
SurpriseWallpaper.prototype.disable = function() {
chrome.alarms.clearAll();
// Makes last changed date invalid.
- WallpaperUtil.saveToStorage(Constants.AccessLastSurpriseWallpaperChangedDate,
- '', true);
+ WallpaperUtil.saveToLocalStorage(
+ Constants.AccessLastSurpriseWallpaperChangedDate, '', function() {
+ WallpaperUtil.saveToSyncStorage(
+ Constants.AccessLastSurpriseWallpaperChangedDate, '');
+ });
};
/**
@@ -237,80 +269,116 @@ chrome.app.runtime.onLaunched.addListener(function() {
});
chrome.syncFileSystem.onFileStatusChanged.addListener(function(detail) {
- WallpaperUtil.enabledSyncThemesCallback(function() {
- WallpaperUtil.enabledExperimentalFeatureCallback(function() {
- if (detail.status == 'synced' &&
- detail.direction == 'remote_to_local') {
- if (detail.action == 'added') {
- Constants.WallpaperLocalStorage.get(
- Constants.AccessLocalWallpaperInfoKey,
- function(items) {
- var localData = items[Constants.AccessLocalWallpaperInfoKey];
- if (localData && localData.url == detail.fileEntry.name &&
- localData.source == Constants.WallpaperSourceEnum.Custom) {
- WallpaperUtil.setCustomWallpaperFromSyncFS(localData.url,
- localData.layout);
- } else if (localData.url !=
- detail.fileEntry.name.replace(
- Constants.CustomWallpaperThumbnailSuffix, '')) {
- WallpaperUtil.storeWallpaperFromSyncFSToLocalFS(
- detail.fileEntry);
- }
- });
- } else if (detail.action == 'deleted') {
- var fileName = detail.fileEntry.name.replace(
- Constants.CustomWallpaperThumbnailSuffix, '');
- WallpaperUtil.deleteWallpaperFromLocalFS(fileName);
- }
+ WallpaperUtil.enabledSyncThemesCallback(function(syncEnabled) {
+ if (!syncEnabled)
+ return;
+ if (detail.status == 'synced' &&
+ detail.direction == 'remote_to_local') {
+ if (detail.action == 'added') {
+ Constants.WallpaperLocalStorage.get(
+ Constants.AccessLocalWallpaperInfoKey,
+ function(items) {
+ var localData = items[Constants.AccessLocalWallpaperInfoKey];
+ if (localData && localData.url == detail.fileEntry.name &&
+ localData.source == Constants.WallpaperSourceEnum.Custom) {
+ WallpaperUtil.setCustomWallpaperFromSyncFS(localData.url,
+ localData.layout);
+ } else if (localData.url !=
+ detail.fileEntry.name.replace(
+ Constants.CustomWallpaperThumbnailSuffix, '')) {
+ WallpaperUtil.storeWallpaperFromSyncFSToLocalFS(
+ detail.fileEntry);
+ }
+ });
+ } else if (detail.action == 'deleted') {
+ var fileName = detail.fileEntry.name.replace(
+ Constants.CustomWallpaperThumbnailSuffix, '');
+ WallpaperUtil.deleteWallpaperFromLocalFS(fileName);
}
- });
+ }
});
});
chrome.storage.onChanged.addListener(function(changes, namespace) {
- WallpaperUtil.enabledSyncThemesCallback(function() {
- WallpaperUtil.enabledExperimentalFeatureCallback(function() {
+ WallpaperUtil.enabledSyncThemesCallback(function(syncEnabled) {
+ if (syncEnabled) {
+ // If sync theme is enabled, use values from chrome.storage.sync to sync
+ // wallpaper changes.
WallpaperUtil.requestSyncFS(function() {});
- });
- if (changes[Constants.AccessSurpriseMeEnabledKey]) {
- if (changes[Constants.AccessSurpriseMeEnabledKey].newValue) {
- SurpriseWallpaper.getInstance().next();
- } else {
- SurpriseWallpaper.getInstance().disable();
+ if (changes[Constants.AccessSyncSurpriseMeEnabledKey]) {
+ if (changes[Constants.AccessSyncSurpriseMeEnabledKey].newValue) {
+ SurpriseWallpaper.getInstance().next();
+ } else {
+ SurpriseWallpaper.getInstance().disable();
+ }
}
- }
- if (changes[Constants.AccessSyncWallpaperInfoKey]) {
- var newValue = changes[Constants.AccessSyncWallpaperInfoKey].newValue;
- Constants.WallpaperLocalStorage.get(Constants.AccessLocalWallpaperInfoKey,
- function(items) {
- // Normally, the wallpaper info saved in local storage and sync storage
- // are the same. If the synced value changed by sync service, they may
- // different. In that case, change wallpaper to the one saved in sync
- // storage and update the local value.
- var localValue = items[Constants.AccessLocalWallpaperInfoKey];
- if (localValue == undefined ||
- localValue.url != newValue.url ||
- localValue.layout != newValue.layout ||
- localValue.source != newValue.source) {
- if (newValue.source == Constants.WallpaperSourceEnum.Online) {
- // TODO(bshe): Consider schedule an alarm to set online wallpaper
- // later when failed. Note that we need to cancel the retry if user
- // set another wallpaper before retry alarm invoked.
- WallpaperUtil.setOnlineWallpaper(newValue.url, newValue.layout,
- function() {}, function() {});
- } else if (newValue.source == Constants.WallpaperSourceEnum.Custom) {
- WallpaperUtil.enabledExperimentalFeatureCallback(function() {
- WallpaperUtil.setCustomWallpaperFromSyncFS(newValue.url,
- newValue.layout);
- });
- } else if (newValue.source == Constants.WallpaperSourceEnum.Default) {
- chrome.wallpaperPrivate.resetWallpaper();
- }
- WallpaperUtil.saveToStorage(Constants.AccessLocalWallpaperInfoKey,
- newValue, false);
+ if (changes[Constants.AccessSyncWallpaperInfoKey]) {
+ var syncInfo = changes[Constants.AccessSyncWallpaperInfoKey].newValue;
+
+ Constants.WallpaperSyncStorage.get(
+ Constants.AccessSyncSurpriseMeEnabledKey, function(enabledItems) {
+ var syncSurpriseMeEnabled =
+ enabledItems[Constants.AccessSyncSurpriseMeEnabledKey];
+
+ Constants.WallpaperSyncStorage.get(
+ Constants.AccessLastSurpriseWallpaperChangedDate,
+ function(items) {
+ var syncLastSurpriseMeChangedDate =
+ items[Constants.AccessLastSurpriseWallpaperChangedDate];
+
+ var today = new Date().toDateString();
+ // If SurpriseMe is enabled and surprise wallpaper hasn't been
+ // changed today, we should not sync the change, instead onAlarm()
+ // will be triggered to update a surprise me wallpaper.
+ if (!syncSurpriseMeEnabled ||
+ (syncSurpriseMeEnabled &&
+ syncLastSurpriseMeChangedDate == today)) {
+ Constants.WallpaperLocalStorage.get(
+ Constants.AccessLocalWallpaperInfoKey, function(infoItems) {
+ var localInfo =
+ infoItems[Constants.AccessLocalWallpaperInfoKey];
+ // Normally, the wallpaper info saved in local storage and sync
+ // storage are the same. If the synced value changed by sync
+ // service, they may different. In that case, change wallpaper
+ // to the one saved in sync storage and update the local value.
+ if (localInfo == undefined ||
+ localInfo.url != syncInfo.url ||
+ localInfo.layout != syncInfo.layout ||
+ localInfo.source != syncInfo.source) {
+ if (syncInfo.source == Constants.WallpaperSourceEnum.Online) {
+ // TODO(bshe): Consider schedule an alarm to set online
+ // wallpaper later when failed. Note that we need to cancel
+ // the retry if user set another wallpaper before retry
+ // alarm invoked.
+ WallpaperUtil.setOnlineWallpaper(syncInfo.url,
+ syncInfo.layout, function() {}, function() {});
+ } else if (syncInfo.source ==
+ Constants.WallpaperSourceEnum.Custom) {
+ WallpaperUtil.setCustomWallpaperFromSyncFS(syncInfo.url,
+ syncInfo.layout);
+ } else if (syncInfo.source ==
+ Constants.WallpaperSourceEnum.Default) {
+ chrome.wallpaperPrivate.resetWallpaper();
+ }
+ WallpaperUtil.saveToLocalStorage(
+ Constants.AccessLocalWallpaperInfoKey, syncInfo);
+ }
+ });
+ }
+ });
+ });
+ }
+ } else {
+ // If sync theme is disabled, use values from chrome.storage.local to
+ // track wallpaper changes.
+ if (changes[Constants.AccessLocalSurpriseMeEnabledKey]) {
+ if (changes[Constants.AccessLocalSurpriseMeEnabledKey].newValue) {
+ SurpriseWallpaper.getInstance().next();
+ } else {
+ SurpriseWallpaper.getInstance().disable();
}
- });
+ }
}
});
});
@@ -318,3 +386,12 @@ chrome.storage.onChanged.addListener(function(changes, namespace) {
chrome.alarms.onAlarm.addListener(function() {
SurpriseWallpaper.getInstance().next();
});
+
+chrome.wallpaperPrivate.onWallpaperChangedBy3rdParty.addListener(function() {
+ WallpaperUtil.saveToLocalStorage(
+ Constants.AccessLocalSurpriseMeEnabledKey, false, function() {
+ WallpaperUtil.saveToSyncStorage(Constants.AccessSyncSurpriseMeEnabledKey,
+ false);
+ });
+ SurpriseWallpaper.getInstance().disable();
+});
diff --git a/chromium/chrome/browser/resources/chromeos/wallpaper_manager/js/main_scripts.js b/chromium/chrome/browser/resources/chromeos/wallpaper_manager/js/main_scripts.js
index d7533a3357e..57de792b0cf 100644
--- a/chromium/chrome/browser/resources/chromeos/wallpaper_manager/js/main_scripts.js
+++ b/chromium/chrome/browser/resources/chromeos/wallpaper_manager/js/main_scripts.js
@@ -27,6 +27,7 @@
//<include src="util.js">
//<include src="progress_manager.js">
//<include src="wallpaper_directories.js">
+//<include src="wallpaper_categories_list.js">
//<include src="wallpaper_images_grid.js">
//<include src="wallpaper_manager.js">
//<include src="main.js">
diff --git a/chromium/chrome/browser/resources/chromeos/wallpaper_manager/js/util.js b/chromium/chrome/browser/resources/chromeos/wallpaper_manager/js/util.js
index 777a2362897..de8c5187886 100644
--- a/chromium/chrome/browser/resources/chromeos/wallpaper_manager/js/util.js
+++ b/chromium/chrome/browser/resources/chromeos/wallpaper_manager/js/util.js
@@ -89,32 +89,12 @@ WallpaperUtil.deleteWallpaperFromSyncFS = function(wallpaperFilename) {
};
/**
- * Executes callback if experimental flag is set to true.
- * @param {function} callback The callback will be executed if 'isExperimental'
- * flag is set to true.
- */
-WallpaperUtil.enabledExperimentalFeatureCallback = function(callback) {
- if (WallpaperUtil.strings) {
- if (WallpaperUtil.strings.isExperimental)
- callback();
- } else {
- chrome.wallpaperPrivate.getStrings(function(strings) {
- WallpaperUtil.strings = strings;
- if (WallpaperUtil.strings.isExperimental)
- callback();
- });
- }
-};
-
-/**
- * Executes callback if sync theme is enabled.
- * @param {function} callback The callback will be executed if sync themes is
- * enabled.
+ * Executes callback after requesting the sync settings.
+ * @param {function} callback The callback will be executed.
*/
WallpaperUtil.enabledSyncThemesCallback = function(callback) {
chrome.wallpaperPrivate.getSyncSetting(function(setting) {
- if (setting.syncThemes)
- callback();
+ callback(setting.syncThemes);
});
};
@@ -124,7 +104,9 @@ WallpaperUtil.enabledSyncThemesCallback = function(callback) {
* handler is available.
*/
WallpaperUtil.requestSyncFS = function(callback) {
- WallpaperUtil.enabledSyncThemesCallback(function() {
+ WallpaperUtil.enabledSyncThemesCallback(function(syncEnabled) {
+ if (!syncEnabled)
+ return;
if (WallpaperUtil.syncFs) {
callback(WallpaperUtil.syncFs);
} else {
@@ -282,18 +264,28 @@ WallpaperUtil.setCustomWallpaperFromSyncFS = function(
* Saves value to local storage that associates with key.
* @param {string} key The key that associates with value.
* @param {string} value The value to save to local storage.
- * @param {boolen} sync True if the value is saved to sync storage.
- * @param {function=} opt_callback The callback on success, or on failure.
+ * @param {function=} opt_callback The callback on success.
*/
-WallpaperUtil.saveToStorage = function(key, value, sync, opt_callback) {
+WallpaperUtil.saveToLocalStorage = function(key, value, opt_callback) {
var items = {};
items[key] = value;
- if (sync)
- WallpaperUtil.enabledSyncThemesCallback(function() {
+ Constants.WallpaperLocalStorage.set(items, opt_callback);
+};
+
+/**
+ * Saves value to sync storage that associates with key if sync theme is
+ * enabled.
+ * @param {string} key The key that associates with value.
+ * @param {string} value The value to save to sync storage.
+ * @param {function=} opt_callback The callback on success.
+ */
+WallpaperUtil.saveToSyncStorage = function(key, value, opt_callback) {
+ var items = {};
+ items[key] = value;
+ WallpaperUtil.enabledSyncThemesCallback(function(syncEnabled) {
+ if (syncEnabled)
Constants.WallpaperSyncStorage.set(items, opt_callback);
- });
- else
- Constants.WallpaperLocalStorage.set(items, opt_callback);
+ });
};
/**
@@ -310,10 +302,10 @@ WallpaperUtil.saveWallpaperInfo = function(url, layout, source) {
layout: layout,
source: source
};
- WallpaperUtil.saveToStorage(Constants.AccessLocalWallpaperInfoKey,
- wallpaperInfo, false, function() {
- WallpaperUtil.saveToStorage(Constants.AccessSyncWallpaperInfoKey,
- wallpaperInfo, true);
+ WallpaperUtil.saveToLocalStorage(Constants.AccessLocalWallpaperInfoKey,
+ wallpaperInfo, function() {
+ WallpaperUtil.saveToSyncStorage(Constants.AccessSyncWallpaperInfoKey,
+ wallpaperInfo);
});
};
@@ -341,7 +333,7 @@ WallpaperUtil.fetchURL = function(url, type, onSuccess, onFailure, opt_xhr) {
if (this.status == 200) {
onSuccess(this);
} else {
- onFailure();
+ onFailure(this.status);
}
});
xhr.addEventListener('error', onFailure);
diff --git a/chromium/chrome/browser/resources/chromeos/wallpaper_manager/js/wallpaper_categories_list.js b/chromium/chrome/browser/resources/chromeos/wallpaper_manager/js/wallpaper_categories_list.js
new file mode 100644
index 00000000000..fa064a04cd3
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/wallpaper_manager/js/wallpaper_categories_list.js
@@ -0,0 +1,88 @@
+// Copyright (c) 2015 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.
+
+cr.define('wallpapers', function() {
+ /** @const */ var List = cr.ui.List;
+ /** @const */ var ListSingleSelectionModel = cr.ui.ListSingleSelectionModel;
+ /** @const */ var ArrayDataModel = cr.ui.ArrayDataModel;
+ /** @const */ var ListSelectionController = cr.ui.ListSelectionController;
+
+ /**
+ * Create a selection controller for the wallpaper categories list.
+ * @param {cr.ui.ListSelectionModel} selectionModel
+ * @constructor
+ * @extends {cr.ui.ListSelectionController}
+ */
+ function WallpaperCategoriesListSelectionController(selectionModel) {
+ ListSelectionController.call(this, selectionModel);
+ }
+
+ WallpaperCategoriesListSelectionController.prototype = {
+ __proto__: ListSelectionController.prototype,
+
+ /**
+ * @override
+ */
+ getIndexBefore: function(index) {
+ return this.getIndexAbove(index);
+ },
+
+ /**
+ * @override
+ */
+ getIndexAfter: function(index) {
+ return this.getIndexBelow(index);
+ },
+ };
+
+ /**
+ * Create a new wallpaper categories list (horizontal list).
+ * @constructor
+ * @extends {cr.ui.List}
+ */
+ var WallpaperCategoriesList = cr.ui.define('list');
+
+ WallpaperCategoriesList.prototype = {
+ __proto__: List.prototype,
+
+ /**
+ * @override
+ */
+ decorate: function() {
+ List.prototype.decorate.call(this);
+ this.selectionModel = new ListSingleSelectionModel();
+ this.dataModel = new ArrayDataModel([]);
+
+ // cr.ui.list calculates items in view port based on client height and
+ // item height. However, categories list is displayed horizontally. So we
+ // should not calculate visible items here. Sets autoExpands to true to
+ // show every item in the list.
+ this.autoExpands = true;
+
+ var self = this;
+ this.itemConstructor = function(entry) {
+ var li = self.ownerDocument.createElement('li');
+ cr.defineProperty(li, 'custom', cr.PropertyKind.BOOL_ATTR);
+ li.custom = (entry == loadTimeData.getString('customCategoryLabel'));
+ cr.defineProperty(li, 'lead', cr.PropertyKind.BOOL_ATTR);
+ cr.defineProperty(li, 'selected', cr.PropertyKind.BOOL_ATTR);
+ var div = self.ownerDocument.createElement('div');
+ div.textContent = entry;
+ li.appendChild(div);
+ return li;
+ };
+ },
+
+ /**
+ * @override
+ */
+ createSelectionController: function(sm) {
+ return new WallpaperCategoriesListSelectionController(assert(sm));
+ },
+ };
+
+ return {
+ WallpaperCategoriesList: WallpaperCategoriesList
+ };
+});
diff --git a/chromium/chrome/browser/resources/chromeos/wallpaper_manager/js/wallpaper_images_grid.js b/chromium/chrome/browser/resources/chromeos/wallpaper_manager/js/wallpaper_images_grid.js
index 80ad11338ed..a473875bc0f 100644
--- a/chromium/chrome/browser/resources/chromeos/wallpaper_manager/js/wallpaper_images_grid.js
+++ b/chromium/chrome/browser/resources/chromeos/wallpaper_manager/js/wallpaper_images_grid.js
@@ -13,21 +13,28 @@ cr.define('wallpapers', function() {
/**
* Creates a new wallpaper thumbnails grid item.
- * @param {{baseURL: string, layout: string, source: string,
- * availableOffline: boolean, opt_dynamicURL: string,
- * opt_author: string, opt_authorWebsite: string}}
+ * @param {{wallpaperId: number, baseURL: string, layout: string,
+ * source: string, availableOffline: boolean,
+ * opt_dynamicURL: string, opt_author: string,
+ * opt_authorWebsite: string}}
* wallpaperInfo Wallpaper data item in WallpaperThumbnailsGrid's data
* model.
* @param {number} dataModelId A unique ID that this item associated to.
+ * @param {object} thumbnail The thumbnail image Object associated with this
+ * grid item.
* @param {function} callback The callback function when decoration finished.
* @constructor
* @extends {cr.ui.GridItem}
*/
- function WallpaperThumbnailsGridItem(wallpaperInfo, dataModelId, callback) {
+ function WallpaperThumbnailsGridItem(wallpaperInfo,
+ dataModelId,
+ thumbnail,
+ callback) {
var el = new GridItem(wallpaperInfo);
el.__proto__ = WallpaperThumbnailsGridItem.prototype;
- el.dataModelId = dataModelId;
- el.callback = callback;
+ el.dataModelId_ = dataModelId;
+ el.thumbnail_ = thumbnail;
+ el.callback_ = callback;
return el;
}
@@ -38,7 +45,12 @@ cr.define('wallpapers', function() {
* The unique ID this thumbnail grid associated to.
* @type {number}
*/
- dataModelId: null,
+ dataModelId_: null,
+
+ /**
+ * The thumbnail image associated with the current grid item.
+ */
+ thumbnail_: null,
/**
* Called when the WallpaperThumbnailsGridItem is decorated or failed to
@@ -46,13 +58,20 @@ cr.define('wallpapers', function() {
* be called after image loaded.
* @type {function}
*/
- callback: null,
+ callback_: null,
/** @override */
decorate: function() {
GridItem.prototype.decorate.call(this);
// Removes garbage created by GridItem.
this.innerText = '';
+
+ if (this.thumbnail_) {
+ this.appendChild(this.thumbnail_);
+ this.callback_(this.dataModelId_);
+ return;
+ }
+
var imageEl = cr.doc.createElement('img');
imageEl.classList.add('thumbnail');
cr.defineProperty(imageEl, 'offline', cr.PropertyKind.BOOL_ATTR);
@@ -70,18 +89,20 @@ cr.define('wallpapers', function() {
});
// Delay dispatching the completion callback until all items have
// begun loading and are tracked.
- window.setTimeout(this.callback.bind(this, this.dataModelId), 0);
+ window.setTimeout(this.callback_.bind(this, this.dataModelId_), 0);
break;
case Constants.WallpaperSourceEnum.Custom:
var errorHandler = function(e) {
- self.callback(self.dataModelId);
+ self.callback_(self.dataModelId_);
console.error('Can not access file system.');
};
var wallpaperDirectories = WallpaperDirectories.getInstance();
var getThumbnail = function(fileName) {
var setURL = function(fileEntry) {
imageEl.src = fileEntry.toURL();
- self.callback(self.dataModelId);
+ self.callback_(self.dataModelId_,
+ self.dataItem.wallpaperId,
+ imageEl);
};
var fallback = function() {
wallpaperDirectories.getDirectory(
@@ -108,7 +129,9 @@ cr.define('wallpapers', function() {
{'type': 'image\/png'});
imageEl.src = window.URL.createObjectURL(blob);
imageEl.addEventListener('load', function(e) {
- self.callback(self.dataModelId);
+ self.callback_(self.dataModelId_,
+ self.dataItem.wallpaperId,
+ imageEl);
window.URL.revokeObjectURL(this.src);
});
} else if (self.dataItem.source ==
@@ -128,11 +151,13 @@ cr.define('wallpapers', function() {
// thumbnail. Use a placeholder like "loading" image may
// better.
imageEl.addEventListener('load', function(e) {
- self.callback(self.dataModelId);
+ self.callback_(self.dataModelId_,
+ self.dataItem.wallpaperId,
+ this);
window.URL.revokeObjectURL(this.src);
});
} else {
- self.callback(self.dataModelId);
+ self.callback_(self.dataModelId_);
}
});
}
@@ -142,7 +167,7 @@ cr.define('wallpapers', function() {
console.error('Unsupported image source.');
// Delay dispatching the completion callback until all items have
// begun loading and are tracked.
- window.setTimeout(this.callback.bind(this, this.dataModelId), 0);
+ window.setTimeout(this.callback_.bind(this, this.dataModelId_), 0);
}
},
};
@@ -223,6 +248,10 @@ cr.define('wallpapers', function() {
}
},
+ get activeItem() {
+ return this.activeItem_;
+ },
+
/**
* A unique ID that assigned to each set dataModel operation. Note that this
* id wont increase if the new dataModel is null or empty.
@@ -235,6 +264,12 @@ cr.define('wallpapers', function() {
*/
pendingItems_: 0,
+ /**
+ * Maintains all grid items' thumbnail images for quickly switching between
+ * different categories.
+ */
+ thumbnailList_: undefined,
+
/** @override */
set dataModel(dataModel) {
if (this.dataModel_ == dataModel)
@@ -278,11 +313,19 @@ cr.define('wallpapers', function() {
* it does not reduce the count on a previous |dataModelId|.
* @param {number} dataModelId A unique ID that a thumbnail item is
* associated to.
+ * @param {number} opt_wallpaperId The unique wallpaper ID that associated
+ * with this thumbnail gird item.
+ * @param {object} opt_thumbnail The thumbnail image that associated with
+ * the opt_wallpaperId.
*/
- pendingItemComplete: function(dataModelId) {
+ pendingItemComplete: function(dataModelId,
+ opt_wallpaperId,
+ opt_thumbnail) {
if (dataModelId != this.dataModelId_)
return;
this.pendingItems_--;
+ if (opt_wallpaperId != null)
+ this.thumbnailList_[opt_wallpaperId] = opt_thumbnail;
if (this.pendingItems_ == 0) {
this.style.visibility = 'visible';
window.clearTimeout(this.spinnerTimeout_);
@@ -300,11 +343,13 @@ cr.define('wallpapers', function() {
this.checkmark_ = cr.doc.createElement('div');
this.checkmark_.classList.add('check');
this.dataModel = new ArrayDataModel([]);
+ this.thumbnailList_ = new ArrayDataModel([]);
var self = this;
this.itemConstructor = function(value) {
var dataModelId = self.dataModelId_;
self.pendingItems_++;
return WallpaperThumbnailsGridItem(value, dataModelId,
+ self.thumbnailList_[value.wallpaperId],
self.pendingItemComplete.bind(self));
};
this.selectionModel = new ListSingleSelectionModel();
diff --git a/chromium/chrome/browser/resources/chromeos/wallpaper_manager/js/wallpaper_manager.js b/chromium/chrome/browser/resources/chromeos/wallpaper_manager/js/wallpaper_manager.js
index 1d4f346044c..f490701afb1 100644
--- a/chromium/chrome/browser/resources/chromeos/wallpaper_manager/js/wallpaper_manager.js
+++ b/chromium/chrome/browser/resources/chromeos/wallpaper_manager/js/wallpaper_manager.js
@@ -17,7 +17,6 @@ function WallpaperManager(dialogDom) {
this.dialogDom_ = dialogDom;
this.document_ = dialogDom.ownerDocument;
this.enableOnlineWallpaper_ = loadTimeData.valueExists('manifestBaseURL');
- this.selectedCategory = null;
this.selectedItem_ = null;
this.progressManager_ = new ProgressManager();
this.customWallpaperData_ = null;
@@ -172,17 +171,19 @@ function WallpaperManager(dialogDom) {
*/
WallpaperManager.prototype.onLoadManifestSuccess_ = function(manifest) {
this.manifest_ = manifest;
- WallpaperUtil.saveToStorage(Constants.AccessManifestKey, manifest, false);
+ WallpaperUtil.saveToLocalStorage(Constants.AccessLocalManifestKey,
+ manifest);
this.postManifestDomInit_();
};
// Sets manifest to previously saved object if any and shows connection error.
// Called after manifest failed to load.
WallpaperManager.prototype.onLoadManifestFailed_ = function() {
- var accessManifestKey = Constants.AccessManifestKey;
+ var accessManifestKey = Constants.AccessLocalManifestKey;
var self = this;
Constants.WallpaperLocalStorage.get(accessManifestKey, function(items) {
- self.manifest_ = items[accessManifestKey] ? items[accessManifestKey] : {};
+ self.manifest_ = items[accessManifestKey] ?
+ items[accessManifestKey] : null;
self.showError_(str('connectionFailed'));
self.postManifestDomInit_();
$('wallpaper-grid').classList.add('image-picker-offline');
@@ -195,22 +196,43 @@ function WallpaperManager(dialogDom) {
* @private
*/
WallpaperManager.prototype.toggleSurpriseMe_ = function() {
+ var self = this;
var checkbox = $('surprise-me').querySelector('#checkbox');
var shouldEnable = !checkbox.classList.contains('checked');
- WallpaperUtil.saveToStorage(Constants.AccessSurpriseMeEnabledKey,
- shouldEnable, true, function() {
+ var onSuccess = function() {
if (chrome.runtime.lastError == null) {
- if (shouldEnable) {
- checkbox.classList.add('checked');
- } else {
- checkbox.classList.remove('checked');
- }
- $('categories-list').disabled = shouldEnable;
- $('wallpaper-grid').disabled = shouldEnable;
+ if (shouldEnable) {
+ checkbox.classList.add('checked');
+ // Hides the wallpaper set by message if there is any.
+ $('wallpaper-set-by-message').textContent = '';
} else {
- // TODO(bshe): show error message to user.
- console.error('Failed to save surprise me option to chrome storage.');
+ // Unchecking the "Surprise me" checkbox falls back to the previous
+ // wallpaper before "Surprise me" was turned on.
+ if (self.wallpaperGrid_.activeItem) {
+ self.setSelectedWallpaper_(self.wallpaperGrid_.activeItem);
+ self.onWallpaperChanged_(self.wallpaperGrid_.activeItem,
+ self.currentWallpaper_);
+ }
+ checkbox.classList.remove('checked');
}
+ $('categories-list').disabled = shouldEnable;
+ $('wallpaper-grid').disabled = shouldEnable;
+ } else {
+ // TODO(bshe): show error message to user.
+ console.error('Failed to save surprise me option to chrome storage.');
+ }
+ };
+
+ // To prevent the onChanged event being fired twice, we only save the value
+ // to sync storage if the sync theme is enabled, otherwise save it to local
+ // storage.
+ WallpaperUtil.enabledSyncThemesCallback(function(syncEnabled) {
+ if (syncEnabled)
+ WallpaperUtil.saveToSyncStorage(
+ Constants.AccessSyncSurpriseMeEnabledKey, shouldEnable, onSuccess);
+ else
+ WallpaperUtil.saveToLocalStorage(
+ Constants.AccessLocalSurpriseMeEnabledKey, shouldEnable, onSuccess);
});
};
@@ -262,29 +284,49 @@ function WallpaperManager(dialogDom) {
$('surprise-me').hidden = false;
$('surprise-me').addEventListener('click',
this.toggleSurpriseMe_.bind(this));
- Constants.WallpaperSyncStorage.get(Constants.AccessSurpriseMeEnabledKey,
- function(items) {
+ var onSurpriseMeEnabled = function() {
+ $('surprise-me').querySelector('#checkbox').classList.add('checked');
+ $('categories-list').disabled = true;
+ $('wallpaper-grid').disabled = true;
+ };
+
+ WallpaperUtil.enabledSyncThemesCallback(function(syncEnabled) {
// Surprise me has been moved from local to sync storage, prefer
// values from sync, but if unset check local and update synced pref
// if applicable.
- if (!items.hasOwnProperty(Constants.AccessSurpriseMeEnabledKey)) {
- Constants.WallpaperLocalStorage.get(
- Constants.AccessSurpriseMeEnabledKey, function(values) {
- if (values.hasOwnProperty(Constants.AccessSurpriseMeEnabledKey)) {
- WallpaperUtil.saveToStorage(Constants.AccessSurpriseMeEnabledKey,
- values[Constants.AccessSurpriseMeEnabledKey], true);
+ if (syncEnabled) {
+ Constants.WallpaperSyncStorage.get(
+ Constants.AccessSyncSurpriseMeEnabledKey, function(items) {
+ if (items.hasOwnProperty(
+ Constants.AccessSyncSurpriseMeEnabledKey)) {
+ if (items[Constants.AccessSyncSurpriseMeEnabledKey]) {
+ onSurpriseMeEnabled();
+ }
+ } else {
+ Constants.WallpaperLocalStorage.get(
+ Constants.AccessLocalSurpriseMeEnabledKey, function(items) {
+ if (items.hasOwnProperty(
+ Constants.AccessLocalSurpriseMeEnabledKey)) {
+ WallpaperUtil.saveToSyncStorage(
+ Constants.AccessSyncSurpriseMeEnabledKey,
+ items[Constants.AccessLocalSurpriseMeEnabledKey]);
+ if (items[Constants.AccessLocalSurpriseMeEnabledKey]) {
+ onSurpriseMeEnabled();
+ }
+ }
+ });
}
- if (values[Constants.AccessSurpriseMeEnabledKey]) {
- $('surprise-me').querySelector('#checkbox').classList.add(
- 'checked');
- $('categories-list').disabled = true;
- $('wallpaper-grid').disabled = true;
+ });
+ } else {
+ Constants.WallpaperLocalStorage.get(
+ Constants.AccessLocalSurpriseMeEnabledKey, function(items) {
+ if (items.hasOwnProperty(
+ Constants.AccessLocalSurpriseMeEnabledKey)) {
+ if (items[Constants.AccessLocalSurpriseMeEnabledKey]) {
+ onSurpriseMeEnabled();
+ }
}
});
- } else if (items[Constants.AccessSurpriseMeEnabledKey]) {
- $('surprise-me').querySelector('#checkbox').classList.add('checked');
- $('categories-list').disabled = true;
- $('wallpaper-grid').disabled = true;
}
});
@@ -445,7 +487,6 @@ function WallpaperManager(dialogDom) {
WallpaperManager.prototype.initThumbnailsGrid_ = function() {
this.wallpaperGrid_ = $('wallpaper-grid');
wallpapers.WallpaperThumbnailsGrid.decorate(this.wallpaperGrid_);
- this.wallpaperGrid_.autoExpands = true;
this.wallpaperGrid_.addEventListener('change', this.onChange_.bind(this));
this.wallpaperGrid_.addEventListener('dblclick', this.onClose_.bind(this));
@@ -522,15 +563,12 @@ function WallpaperManager(dialogDom) {
function(thumbnailData) {
self.onWallpaperChanged_(selectedItem,
selectedItem.baseURL, thumbnailData);
- WallpaperUtil.enabledExperimentalFeatureCallback(
- function() {
- WallpaperUtil.storeWallpaperToSyncFS(
- selectedItem.baseURL, e.target.result);
- WallpaperUtil.storeWallpaperToSyncFS(
- selectedItem.baseURL +
- Constants.CustomWallpaperThumbnailSuffix,
- thumbnailData);
- });
+ WallpaperUtil.storeWallpaperToSyncFS(
+ selectedItem.baseURL, e.target.result);
+ WallpaperUtil.storeWallpaperToSyncFS(
+ selectedItem.baseURL +
+ Constants.CustomWallpaperThumbnailSuffix,
+ thumbnailData);
},
errorHandler);
});
@@ -588,7 +626,7 @@ function WallpaperManager(dialogDom) {
selectedItem.source);
self.wallpaperRequest_ = null;
};
- var onFailure = function() {
+ var onFailure = function(status) {
self.progressManager_.hideProgressBar(selectedGridItem);
self.showError_(str('downloadFailed'));
self.wallpaperRequest_ = null;
@@ -729,7 +767,7 @@ function WallpaperManager(dialogDom) {
};
/**
- * Close the last opened overlay on pressing the Escape key.
+ * Close the last opened overlay or app window on pressing the Escape key.
* @param {Event} event A keydown event.
*/
WallpaperManager.prototype.onKeyDown_ = function(event) {
@@ -743,6 +781,8 @@ function WallpaperManager(dialogDom) {
if (closeButton) {
closeButton.click();
event.preventDefault();
+ } else {
+ this.onClose_();
}
}
};
@@ -752,50 +792,20 @@ function WallpaperManager(dialogDom) {
*/
WallpaperManager.prototype.initCategoriesList_ = function() {
this.categoriesList_ = $('categories-list');
- cr.ui.List.decorate(this.categoriesList_);
- // cr.ui.list calculates items in view port based on client height and item
- // height. However, categories list is displayed horizontally. So we should
- // not calculate visible items here. Sets autoExpands to true to show every
- // item in the list.
- // TODO(bshe): Use ul to replace cr.ui.list for category list.
- this.categoriesList_.autoExpands = true;
-
- var self = this;
- this.categoriesList_.itemConstructor = function(entry) {
- return self.renderCategory_(entry);
- };
+ wallpapers.WallpaperCategoriesList.decorate(this.categoriesList_);
- this.categoriesList_.selectionModel = new cr.ui.ListSingleSelectionModel();
this.categoriesList_.selectionModel.addEventListener(
'change', this.onCategoriesChange_.bind(this));
- var categoriesDataModel = new cr.ui.ArrayDataModel([]);
- if (this.enableOnlineWallpaper_) {
+ if (this.enableOnlineWallpaper_ && this.manifest_) {
// Adds all category as first category.
- categoriesDataModel.push(str('allCategoryLabel'));
+ this.categoriesList_.dataModel.push(str('allCategoryLabel'));
for (var key in this.manifest_.categories) {
- categoriesDataModel.push(this.manifest_.categories[key]);
+ this.categoriesList_.dataModel.push(this.manifest_.categories[key]);
}
}
// Adds custom category as last category.
- categoriesDataModel.push(str('customCategoryLabel'));
- this.categoriesList_.dataModel = categoriesDataModel;
- };
-
- /**
- * Constructs the element in categories list.
- * @param {string} entry Text content of a category.
- */
- WallpaperManager.prototype.renderCategory_ = function(entry) {
- var li = this.document_.createElement('li');
- cr.defineProperty(li, 'custom', cr.PropertyKind.BOOL_ATTR);
- li.custom = (entry == str('customCategoryLabel'));
- cr.defineProperty(li, 'lead', cr.PropertyKind.BOOL_ATTR);
- cr.defineProperty(li, 'selected', cr.PropertyKind.BOOL_ATTR);
- var div = this.document_.createElement('div');
- div.textContent = entry;
- li.appendChild(div);
- return li;
+ this.categoriesList_.dataModel.push(str('customCategoryLabel'));
};
/**
@@ -830,8 +840,8 @@ function WallpaperManager(dialogDom) {
self.wallpaperGrid_.dataModel.splice(0, 0, wallpaperInfo);
self.wallpaperGrid_.selectedItem = wallpaperInfo;
self.onWallpaperChanged_(wallpaperInfo, fileName);
- WallpaperUtil.saveToStorage(self.currentWallpaper_, layout,
- false);
+ WallpaperUtil.saveToLocalStorage(self.currentWallpaper_,
+ layout);
};
fileWriter.onerror = errorHandler;
@@ -846,12 +856,10 @@ function WallpaperManager(dialogDom) {
Constants.WallpaperDirNameEnum.THUMBNAIL, success, errorHandler);
};
var onCustomWallpaperSuccess = function(thumbnailData, wallpaperData) {
- WallpaperUtil.enabledExperimentalFeatureCallback(function() {
- WallpaperUtil.storeWallpaperToSyncFS(fileName, wallpaperData);
- WallpaperUtil.storeWallpaperToSyncFS(
- fileName + Constants.CustomWallpaperThumbnailSuffix,
- thumbnailData);
- });
+ WallpaperUtil.storeWallpaperToSyncFS(fileName, wallpaperData);
+ WallpaperUtil.storeWallpaperToSyncFS(
+ fileName + Constants.CustomWallpaperThumbnailSuffix,
+ thumbnailData);
saveThumbnail(thumbnailData);
};
var success = function(dirEntry) {
@@ -896,9 +904,7 @@ function WallpaperManager(dialogDom) {
var success = function(dirEntry) {
dirEntry.getFile(fileName, {create: false}, function(fileEntry) {
fileEntry.remove(function() {
- WallpaperUtil.enabledExperimentalFeatureCallback(function() {
- WallpaperUtil.deleteWallpaperFromSyncFS(fileName);
- });
+ WallpaperUtil.deleteWallpaperFromSyncFS(fileName);
}, errorHandler);
}, errorHandler);
};
@@ -968,7 +974,7 @@ function WallpaperManager(dialogDom) {
self.removeCustomWallpaper(fileName);
$('set-wallpaper-layout').disabled = true;
} else {
- WallpaperUtil.saveToStorage(self.currentWallpaper_, layout, false);
+ WallpaperUtil.saveToLocalStorage(self.currentWallpaper_, layout);
self.onWallpaperChanged_(self.wallpaperGrid_.activeItem,
self.currentWallpaper_);
}
@@ -989,7 +995,7 @@ function WallpaperManager(dialogDom) {
bar.style.width = selectedListItem.offsetWidth + 'px';
var wallpapersDataModel = new cr.ui.ArrayDataModel([]);
- var selectedItem;
+ var selectedItem = null;
if (selectedListItem.custom) {
this.document_.body.setAttribute('custom', '');
var errorHandler = this.onFileSystemError_.bind(this);
@@ -998,10 +1004,14 @@ function WallpaperManager(dialogDom) {
};
var self = this;
+ // Need this check for test purpose.
+ var numOnlineWallpaper = (this.enableOnlineWallpaper_ && this.manifest_) ?
+ this.manifest_.wallpaper_list.length : 0;
var processResults = function(entries) {
for (var i = 0; i < entries.length; i++) {
var entry = entries[i];
var wallpaperInfo = {
+ wallpaperId: numOnlineWallpaper + i,
baseURL: entry.name,
// The layout will be replaced by the actual value saved in
// local storage when requested later. Layout is not important
@@ -1016,6 +1026,7 @@ function WallpaperManager(dialogDom) {
}
if (loadTimeData.getBoolean('isOEMDefaultWallpaper')) {
var oemDefaultWallpaperElement = {
+ wallpaperId: numOnlineWallpaper + entries.length,
baseURL: 'OemDefaultWallpaper',
layout: 'CENTER_CROPPED',
source: Constants.WallpaperSourceEnum.OEM,
@@ -1035,8 +1046,10 @@ function WallpaperManager(dialogDom) {
};
wallpapersDataModel.push(lastElement);
self.wallpaperGrid_.dataModel = wallpapersDataModel;
- self.wallpaperGrid_.selectedItem = selectedItem;
- self.wallpaperGrid_.activeItem = selectedItem;
+ if (selectedItem) {
+ self.wallpaperGrid_.selectedItem = selectedItem;
+ self.wallpaperGrid_.activeItem = selectedItem;
+ }
};
var success = function(dirEntry) {
@@ -1060,18 +1073,22 @@ function WallpaperManager(dialogDom) {
success, errorHandler);
} else {
this.document_.body.removeAttribute('custom');
- for (var key in this.manifest_.wallpaper_list) {
+ // Need this check for test purpose.
+ var numOnlineWallpaper = (this.enableOnlineWallpaper_ && this.manifest_) ?
+ this.manifest_.wallpaper_list.length : 0;
+ for (var i = 0; i < numOnlineWallpaper; i++) {
if (selectedIndex == AllCategoryIndex ||
- this.manifest_.wallpaper_list[key].categories.indexOf(
+ this.manifest_.wallpaper_list[i].categories.indexOf(
selectedIndex - OnlineCategoriesOffset) != -1) {
var wallpaperInfo = {
- baseURL: this.manifest_.wallpaper_list[key].base_url,
- layout: this.manifest_.wallpaper_list[key].default_layout,
+ wallpaperId: i,
+ baseURL: this.manifest_.wallpaper_list[i].base_url,
+ layout: this.manifest_.wallpaper_list[i].default_layout,
source: Constants.WallpaperSourceEnum.Online,
availableOffline: false,
- author: this.manifest_.wallpaper_list[key].author,
- authorWebsite: this.manifest_.wallpaper_list[key].author_website,
- dynamicURL: this.manifest_.wallpaper_list[key].dynamic_url
+ author: this.manifest_.wallpaper_list[i].author,
+ authorWebsite: this.manifest_.wallpaper_list[i].author_website,
+ dynamicURL: this.manifest_.wallpaper_list[i].dynamic_url
};
var startIndex = wallpaperInfo.baseURL.lastIndexOf('/') + 1;
var fileName = wallpaperInfo.baseURL.substring(startIndex) +
@@ -1081,7 +1098,7 @@ function WallpaperManager(dialogDom) {
wallpaperInfo.availableOffline = true;
}
wallpapersDataModel.push(wallpaperInfo);
- var url = this.manifest_.wallpaper_list[key].base_url +
+ var url = this.manifest_.wallpaper_list[i].base_url +
Constants.HighResolutionSuffix;
if (url == this.currentWallpaper_) {
selectedItem = wallpaperInfo;
@@ -1089,8 +1106,10 @@ function WallpaperManager(dialogDom) {
}
}
this.wallpaperGrid_.dataModel = wallpapersDataModel;
- this.wallpaperGrid_.selectedItem = selectedItem;
- this.wallpaperGrid_.activeItem = selectedItem;
+ if (selectedItem) {
+ this.wallpaperGrid_.selectedItem = selectedItem;
+ this.wallpaperGrid_.activeItem = selectedItem;
+ }
}
};
diff --git a/chromium/chrome/browser/resources/chromeos/wallpaper_manager/main.html b/chromium/chrome/browser/resources/chromeos/wallpaper_manager/main.html
index dbe6dc2bbbc..d5faab80cc3 100644
--- a/chromium/chrome/browser/resources/chromeos/wallpaper_manager/main.html
+++ b/chromium/chrome/browser/resources/chromeos/wallpaper_manager/main.html
@@ -1,14 +1,15 @@
-<!DOCTYPE HTML>
+<!doctype html>
<!--
------
Copyright (c) 2013 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.
-->
-<html i18n-values="dir:textdirection;">
+<html i18n-values="dir:textdirection;lang:language">
<head>
<meta charset="utf-8">
<title>Wallpaper Picker</title>
+ <link rel="stylesheet" href="../../../../../ui/webui/resources/css/text_defaults.css">
<link rel="stylesheet" href="../../../../../ui/webui/resources/css/widgets.css">
<link rel="stylesheet" href="../../../../../ui/webui/resources/css/menu.css"></link>
@@ -43,21 +44,21 @@ found in the LICENSE file.
<script src="js/util.js"></script>
<script src="js/progress_manager.js"></script>
<script src="js/wallpaper_directories.js"></script>
+ <script src="js/wallpaper_categories_list.js"></script>
<script src="js/wallpaper_images_grid.js"></script>
<script src="js/wallpaper_manager.js"></script>
<script src="js/main.js"></script>
</if>
</head>
-<body i18n-values=".style.fontFamily:webFontFamily;
- .style.fontSize:webFontSize">
+<body>
<commands>
<command id="delete" i18n-values="label:deleteCommandLabel"
shortcut="U+007F"></command>
</commands>
- <menu id="wallpaper-context-menu" class="chrome-menu" visibleif="custom">
- <menuitem command="#delete"></menuitem>
- </menu>
+ <cr-menu id="wallpaper-context-menu" class="chrome-menu" visibleif="custom">
+ <cr-menu-item command="#delete"></cr-menu-item>
+ </cr-menu>
<div id="error-container" class="overlay-container" hidden>
<div class="page">
diff --git a/chromium/chrome/browser/resources/chromeos/wallpaper_manager/manifest.json b/chromium/chrome/browser/resources/chromeos/wallpaper_manager/manifest.json
index 77c2e104933..c52ada0dd67 100644
--- a/chromium/chrome/browser/resources/chromeos/wallpaper_manager/manifest.json
+++ b/chromium/chrome/browser/resources/chromeos/wallpaper_manager/manifest.json
@@ -8,7 +8,13 @@
"manifest_version": 2,
"description": "An experimental wallpaper picker UI",
"icons": {
- "128": "images/icon128.png"
+ "16": "images/icon16.png",
+ "32": "images/icon32.png",
+ "48": "images/icon48.png",
+ "64": "images/icon64.png",
+ "96": "images/icon96.png",
+ "128": "images/icon128.png",
+ "256": "images/icon256.png"
},
"permissions": [
"alarms",
diff --git a/chromium/chrome/browser/resources/component_extension_resources.grd b/chromium/chrome/browser/resources/component_extension_resources.grd
index 2779d01fb1e..49f0b64b614 100644
--- a/chromium/chrome/browser/resources/component_extension_resources.grd
+++ b/chromium/chrome/browser/resources/component_extension_resources.grd
@@ -59,39 +59,39 @@
<include name="IDR_HANGOUT_SERVICES_THUNK_JS" file="hangout_services/thunk.js" type="BINDATA" />
</if>
<if expr="enable_extensions">
- <!-- Hotword Audio Verification extension -->
+ <!-- Hotword Audio Verification app -->
<include name="IDR_HOTWORD_AUDIO_VERIFICATION_BACKGROUND_JS" file="hotword_audio_verification/event_page.js" type="BINDATA" />
<include name="IDR_HOTWORD_AUDIO_VERIFICATION_MAIN_JS" file="hotword_audio_verification/main.js" type="BINDATA" />
<include name="IDR_HOTWORD_AUDIO_VERIFICATION_FLOW_JS" file="hotword_audio_verification/flow.js" type="BINDATA" />
- <include name="IDR_HOTWORD_ONLY_STEP_HTML" file="hotword_audio_verification/steps/hotword_only_step.html" flattenhtml="true" type="BINDATA" />
- <include name="IDR_HOTWORD_AUDIO_HISTORY_STEP_HTML" file="hotword_audio_verification/steps/hotword_audio_history_step.html" flattenhtml="true" type="BINDATA" />
+ <include name="IDR_START_STEP_HTML" file="hotword_audio_verification/steps/start_step.html" flattenhtml="true" type="BINDATA" />
+ <include name="IDR_AUDIO_HISTORY_STEP_HTML" file="hotword_audio_verification/steps/audio_history_step.html" flattenhtml="true" type="BINDATA" />
<include name="IDR_SPEECH_TRAINING_STEP_HTML" file="hotword_audio_verification/steps/speech_training_step.html" flattenhtml="true" type="BINDATA" />
<include name="IDR_FINISHED_STEP_HTML" file="hotword_audio_verification/steps/finished_step.html" flattenhtml="true" type="BINDATA" />
<include name="IDR_HOTWORD_AUDIO_VERIFICATION_STYLE_CSS" file="hotword_audio_verification/style.css" type="BINDATA" />
- <include name="IDR_HOTWORD_AUDIO_VERIFICATION_IMAGE_CLOSE_1X" file="hotword_audio_verification/images/bt-close-1x.png" type="BINDATA" />
- <include name="IDR_HOTWORD_AUDIO_VERIFICATION_IMAGE_CLOSE_2X" file="hotword_audio_verification/images/bt-close-2x.png" type="BINDATA" />
- <include name="IDR_HOTWORD_AUDIO_VERIFICATION_IMAGE_HEADER_1X" file="hotword_audio_verification/images/header-optin-1x.png" type="BINDATA" />
- <include name="IDR_HOTWORD_AUDIO_VERIFICATION_IMAGE_HEADER_2X" file="hotword_audio_verification/images/header-optin-2x.png" type="BINDATA" />
+ <include name="IDR_HOTWORD_AUDIO_VERIFICATION_IMAGE_CLOSE_1X" file="hotword_audio_verification/images/ic-x-white-1x.png" type="BINDATA" />
+ <include name="IDR_HOTWORD_AUDIO_VERIFICATION_IMAGE_CLOSE_2X" file="hotword_audio_verification/images/ic-x-white-2x.png" type="BINDATA" />
+ <include name="IDR_HOTWORD_AUDIO_VERIFICATION_IMAGE_INTRO_1X" file="hotword_audio_verification/images/intro-1x.png" type="BINDATA" />
+ <include name="IDR_HOTWORD_AUDIO_VERIFICATION_IMAGE_INTRO_2X" file="hotword_audio_verification/images/intro-2x.png" type="BINDATA" />
+ <include name="IDR_HOTWORD_AUDIO_VERIFICATION_IMAGE_HEADER_1X" file="hotword_audio_verification/images/gradient-1x.png" type="BINDATA" />
+ <include name="IDR_HOTWORD_AUDIO_VERIFICATION_IMAGE_HEADER_2X" file="hotword_audio_verification/images/gradient-2x.png" type="BINDATA" />
<include name="IDR_HOTWORD_AUDIO_VERIFICATION_IMAGE_CHECK_BLUE_1X" file="hotword_audio_verification/images/ic-check-blue-1x.png" type="BINDATA" />
<include name="IDR_HOTWORD_AUDIO_VERIFICATION_IMAGE_CHECK_BLUE_2X" file="hotword_audio_verification/images/ic-check-blue-2x.png" type="BINDATA" />
<include name="IDR_HOTWORD_AUDIO_VERIFICATION_IMAGE_CHECK_GRAY_1X" file="hotword_audio_verification/images/ic-check-gray-1x.png" type="BINDATA" />
<include name="IDR_HOTWORD_AUDIO_VERIFICATION_IMAGE_CHECK_GRAY_2X" file="hotword_audio_verification/images/ic-check-gray-2x.png" type="BINDATA" />
- <include name="IDR_HOTWORD_AUDIO_VERIFICATION_IMAGE_ERROR_1X" file="hotword_audio_verification/images/ic-error-1x.png" type="BINDATA" />
- <include name="IDR_HOTWORD_AUDIO_VERIFICATION_IMAGE_ERROR_2X" file="hotword_audio_verification/images/ic-error-2x.png" type="BINDATA" />
- <include name="IDR_HOTWORD_AUDIO_VERIFICATION_IMAGE_MIC_1X" file="hotword_audio_verification/images/mic-1x.png" type="BINDATA" />
- <include name="IDR_HOTWORD_AUDIO_VERIFICATION_IMAGE_MIC_2X" file="hotword_audio_verification/images/mic-2x.png" type="BINDATA" />
<include name="IDR_HOTWORD_AUDIO_VERIFICATION_IMAGE_LOADER_1X" file="hotword_audio_verification/images/placeholder-loader-1x.png" type="BINDATA" />
<include name="IDR_HOTWORD_AUDIO_VERIFICATION_IMAGE_LOADER_2X" file="hotword_audio_verification/images/placeholder-loader-2x.png" type="BINDATA" />
+ <include name="IDR_HOTWORD_AUDIO_VERIFICATION_IMAGE_ERROR_1X" file="hotword_audio_verification/images/ic-error-1x.png" type="BINDATA" />
+ <include name="IDR_HOTWORD_AUDIO_VERIFICATION_IMAGE_ERROR_2X" file="hotword_audio_verification/images/ic-error-2x.png" type="BINDATA" />
+ <include name="IDR_HOTWORD_AUDIO_VERIFICATION_IMAGE_ICON_16" file="hotword_audio_verification/images/icon-16.png" type="BINDATA" />
+ <include name="IDR_HOTWORD_AUDIO_VERIFICATION_IMAGE_ICON_48" file="hotword_audio_verification/images/icon-48.png" type="BINDATA" />
+ <include name="IDR_HOTWORD_AUDIO_VERIFICATION_IMAGE_ICON_128" file="hotword_audio_verification/images/icon-128.png" type="BINDATA" />
- <!-- Hotword Helper extension -->
- <include name="IDR_HOTWORD_HELPER_AUDIO_CLIENT_JS" file="hotword_helper/audio_client.js" type="BINDATA" />
- <include name="IDR_HOTWORD_HELPER_MANAGER_JS" file="hotword_helper/manager.js" type="BINDATA" />
-
- <!-- (Experimental) hotword triggering extension -->
+ <!-- Hotword extension -->
<include name="IDR_HOTWORD_ALWAYS_ON_MANAGER_JS" file="hotword/always_on_manager.js" type="BINDATA" />
<include name="IDR_HOTWORD_AUDIO_CLIENT_JS" file="hotword/audio_client.js" type="BINDATA" />
<include name="IDR_HOTWORD_BASE_SESSION_MANAGER_JS" file="hotword/base_session_manager.js" type="BINDATA" />
<include name="IDR_HOTWORD_CONSTANTS_JS" file="hotword/constants.js" type="BINDATA" />
+ <include name="IDR_HOTWORD_KEEP_ALIVE_JS" file="hotword/keep_alive.js" type="BINDATA" />
<include name="IDR_HOTWORD_LAUNCHER_MANAGER_JS" file="hotword/launcher_manager.js" type="BINDATA" />
<include name="IDR_HOTWORD_LOGGING_JS" file="hotword/logging.js" type="BINDATA" />
<include name="IDR_HOTWORD_MANAGER_JS" file="hotword/manager.js" type="BINDATA" />
@@ -126,7 +126,13 @@
<if expr="chromeos">
<include name="IDR_WALLPAPER_MANAGER_CONSTANTS_JS" file="chromeos/wallpaper_manager/js/constants.js" flattenhtml="true" type="BINDATA" />
<include name="IDR_WALLPAPER_MANAGER_EVENT_JS" file="chromeos/wallpaper_manager/js/event_page.js" flattenhtml="true" type="BINDATA" />
+ <include name="IDR_WALLPAPER_MANAGER_ICON_16" file="chromeos/wallpaper_manager/images/icon16.png" type="BINDATA" />
+ <include name="IDR_WALLPAPER_MANAGER_ICON_32" file="chromeos/wallpaper_manager/images/icon32.png" type="BINDATA" />
+ <include name="IDR_WALLPAPER_MANAGER_ICON_48" file="chromeos/wallpaper_manager/images/icon48.png" type="BINDATA" />
+ <include name="IDR_WALLPAPER_MANAGER_ICON_64" file="chromeos/wallpaper_manager/images/icon64.png" type="BINDATA" />
+ <include name="IDR_WALLPAPER_MANAGER_ICON_96" file="chromeos/wallpaper_manager/images/icon96.png" type="BINDATA" />
<include name="IDR_WALLPAPER_MANAGER_ICON_128" file="chromeos/wallpaper_manager/images/icon128.png" type="BINDATA" />
+ <include name="IDR_WALLPAPER_MANAGER_ICON_256" file="chromeos/wallpaper_manager/images/icon256.png" type="BINDATA" />
<include name="IDR_WALLPAPER_MANAGER_MAIN_JS" file="chromeos/wallpaper_manager/js/main_scripts.js" flattenhtml="true" type="BINDATA" />
<include name="IDR_WALLPAPER_MANAGER_UTIL_JS" file="chromeos/wallpaper_manager/js/util.js" flattenhtml="true" type="BINDATA" />
<include name="IDR_FIRST_RUN_DIALOG_BACKGROUND_JS" file="chromeos/first_run/app/background.js" flattenhtml="true" type="BINDATA" />
@@ -155,24 +161,70 @@
<include name="IDR_SETTINGS_APP_JS" file="settings_app/settings_app.js" type="BINDATA" />
</if>
<if expr="enable_plugins">
- <include name="IDR_PDF_INDEX_HTML" file="pdf/index.html" allowexternalscript="true" type="BINDATA" flattenhtml="true" />
- <include name="IDR_PDF_MAIN_JS" file="pdf/main.js" type="BINDATA" flattenhtml="true" />
- <include name="IDR_PDF_BACKGROUND_JS" file="pdf/background.js" type="BINDATA" />
- <include name="IDR_PDF_INCLUDES_JS" file="pdf/includes.js" type="BINDATA" flattenhtml="true" />
+ <include name="IDR_PDF_INDEX_CSS" file="pdf/index.css" allowexternalscript="true" type="BINDATA" />
+ <include name="IDR_PDF_INDEX_HTML" file="pdf/index.html" allowexternalscript="true" type="BINDATA" />
+ <include name="IDR_PDF_INDEX_MATERIAL_CSS" file="pdf/index-material.css" allowexternalscript="true" type="BINDATA" />
+ <include name="IDR_PDF_INDEX_MATERIAL_HTML" file="pdf/index-material.html" allowexternalscript="true" type="BINDATA" />
+ <include name="IDR_PDF_MAIN_JS" file="pdf/main.js" type="BINDATA" />
+ <include name="IDR_PDF_PDF_JS" file="pdf/pdf.js" type="BINDATA" />
+ <include name="IDR_PDF_UI_MANAGER_JS" file="pdf/ui_manager.js" type="BINDATA" />
+ <include name="IDR_PDF_VIEWPORT_JS" file="pdf/viewport.js" type="BINDATA" />
+ <include name="IDR_PDF_OPEN_PDF_PARAMS_PARSER_JS" file="pdf/open_pdf_params_parser.js" type="BINDATA" />
+ <include name="IDR_PDF_NAVIGATOR_JS" file="pdf/navigator.js" type="BINDATA" />
+ <include name="IDR_PDF_VIEWPORT_SCROLLER_JS" file="pdf/viewport_scroller.js" type="BINDATA" />
+ <include name="IDR_PDF_PDF_SCRIPTING_API_JS" file="pdf/pdf_scripting_api.js" type="BINDATA" />
+ <include name="IDR_PDF_ZOOM_MANAGER_JS" file="pdf/zoom_manager.js" type="BINDATA" />
+ <include name="IDR_PDF_BROWSER_API_JS" file="pdf/browser_api.js" type="BINDATA" />
+ <include name="IDR_PDF_CONTENT_SCRIPT_JS" file="pdf/content_script.js" type="BINDATA" />
+
+ <include name="IDR_PDF_VIEWER_BOOKMARK_CSS" file="pdf/elements/viewer-bookmark/viewer-bookmark.css" type="BINDATA" />
+ <include name="IDR_PDF_VIEWER_BOOKMARK_HTML" file="pdf/elements/viewer-bookmark/viewer-bookmark.html" type="BINDATA" />
+ <include name="IDR_PDF_VIEWER_BOOKMARK_JS" file="pdf/elements/viewer-bookmark/viewer-bookmark.js" type="BINDATA" />
+ <include name="IDR_PDF_VIEWER_BOOKMARKS_CONTENT_HTML" file="pdf/elements/viewer-bookmarks-content/viewer-bookmarks-content.html" type="BINDATA" />
+ <include name="IDR_PDF_VIEWER_BUTTON_CSS" file="pdf/elements/viewer-button/viewer-button.css" type="BINDATA" />
+ <include name="IDR_PDF_VIEWER_BUTTON_HTML" file="pdf/elements/viewer-button/viewer-button.html" type="BINDATA" />
+ <include name="IDR_PDF_VIEWER_BUTTON_JS" file="pdf/elements/viewer-button/viewer-button.js" type="BINDATA" />
+ <include name="IDR_PDF_VIEWER_ERROR_SCREEN_CSS" file="pdf/elements/viewer-error-screen/viewer-error-screen.css" type="BINDATA" />
+ <include name="IDR_PDF_VIEWER_ERROR_SCREEN_HTML" file="pdf/elements/viewer-error-screen/viewer-error-screen.html" type="BINDATA" />
+ <include name="IDR_PDF_VIEWER_ERROR_SCREEN_JS" file="pdf/elements/viewer-error-screen/viewer-error-screen.js" type="BINDATA" />
+ <include name="IDR_PDF_VIEWER_PAGE_INDICATOR_CSS" file="pdf/elements/viewer-page-indicator/viewer-page-indicator.css" type="BINDATA" />
+ <include name="IDR_PDF_VIEWER_PAGE_INDICATOR_HTML" file="pdf/elements/viewer-page-indicator/viewer-page-indicator.html" type="BINDATA" />
+ <include name="IDR_PDF_VIEWER_PAGE_INDICATOR_JS" file="pdf/elements/viewer-page-indicator/viewer-page-indicator.js" type="BINDATA" />
+ <include name="IDR_PDF_VIEWER_PAGE_SELECTOR_CSS" file="pdf/elements/viewer-page-selector/viewer-page-selector.css" type="BINDATA" />
+ <include name="IDR_PDF_VIEWER_PAGE_SELECTOR_HTML" file="pdf/elements/viewer-page-selector/viewer-page-selector.html" type="BINDATA" />
+ <include name="IDR_PDF_VIEWER_PAGE_SELECTOR_JS" file="pdf/elements/viewer-page-selector/viewer-page-selector.js" type="BINDATA" />
+ <include name="IDR_PDF_VIEWER_PASSWORD_SCREEN_CSS" file="pdf/elements/viewer-password-screen/viewer-password-screen.css" type="BINDATA" />
+ <include name="IDR_PDF_VIEWER_PASSWORD_SCREEN_HTML" file="pdf/elements/viewer-password-screen/viewer-password-screen.html" type="BINDATA" />
+ <include name="IDR_PDF_VIEWER_PASSWORD_SCREEN_JS" file="pdf/elements/viewer-password-screen/viewer-password-screen.js" type="BINDATA" />
+ <include name="IDR_PDF_VIEWER_PDF_TOOLBAR_CSS" file="pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.css" type="BINDATA" />
+ <include name="IDR_PDF_VIEWER_PDF_TOOLBAR_HTML" file="pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.html" type="BINDATA" />
+ <include name="IDR_PDF_VIEWER_PDF_TOOLBAR_JS" file="pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.js" type="BINDATA" />
+ <include name="IDR_PDF_VIEWER_PROGRESS_BAR_CSS" file="pdf/elements/viewer-progress-bar/viewer-progress-bar.css" type="BINDATA" />
+ <include name="IDR_PDF_VIEWER_PROGRESS_BAR_HTML" file="pdf/elements/viewer-progress-bar/viewer-progress-bar.html" type="BINDATA" />
+ <include name="IDR_PDF_VIEWER_PROGRESS_BAR_JS" file="pdf/elements/viewer-progress-bar/viewer-progress-bar.js" type="BINDATA" />
+ <include name="IDR_PDF_VIEWER_TOOLBAR_CSS" file="pdf/elements/viewer-toolbar/viewer-toolbar.css" type="BINDATA" />
+ <include name="IDR_PDF_VIEWER_TOOLBAR_HTML" file="pdf/elements/viewer-toolbar/viewer-toolbar.html" type="BINDATA" />
+ <include name="IDR_PDF_VIEWER_TOOLBAR_JS" file="pdf/elements/viewer-toolbar/viewer-toolbar.js" type="BINDATA" />
+ <include name="IDR_PDF_VIEWER_ZOOM_BUTTON_CSS" file="pdf/elements/viewer-zoom-toolbar/viewer-zoom-button.css" type="BINDATA" />
+ <include name="IDR_PDF_VIEWER_ZOOM_BUTTON_HTML" file="pdf/elements/viewer-zoom-toolbar/viewer-zoom-button.html" type="BINDATA" />
+ <include name="IDR_PDF_VIEWER_ZOOM_BUTTON_JS" file="pdf/elements/viewer-zoom-toolbar/viewer-zoom-button.js" type="BINDATA" />
+ <include name="IDR_PDF_VIEWER_ZOOM_SELECTOR_CSS" file="pdf/elements/viewer-zoom-toolbar/viewer-zoom-toolbar.css" type="BINDATA" />
+ <include name="IDR_PDF_VIEWER_ZOOM_SELECTOR_HTML" file="pdf/elements/viewer-zoom-toolbar/viewer-zoom-toolbar.html" type="BINDATA" />
+ <include name="IDR_PDF_VIEWER_ZOOM_SELECTOR_JS" file="pdf/elements/viewer-zoom-toolbar/viewer-zoom-toolbar.js" type="BINDATA" />
<!-- Button images. -->
- <include name="IDR_PDF_BUTTON_LOW_1" file="pdf/html_office/elements/viewer-button/img/lowDPI/button_fit_page.png" type="BINDATA" />
- <include name="IDR_PDF_BUTTON_LOW_2" file="pdf/html_office/elements/viewer-button/img/lowDPI/button_fit_width.png" type="BINDATA" />
- <include name="IDR_PDF_BUTTON_LOW_3" file="pdf/html_office/elements/viewer-button/img/lowDPI/button_zoom_out.png" type="BINDATA" />
- <include name="IDR_PDF_BUTTON_LOW_4" file="pdf/html_office/elements/viewer-button/img/lowDPI/button_zoom_in.png" type="BINDATA" />
- <include name="IDR_PDF_BUTTON_LOW_5" file="pdf/html_office/elements/viewer-button/img/lowDPI/button_save.png" type="BINDATA" />
- <include name="IDR_PDF_BUTTON_LOW_6" file="pdf/html_office/elements/viewer-button/img/lowDPI/button_print.png" type="BINDATA" />
- <include name="IDR_PDF_BUTTON_HIGH_1" file="pdf/html_office/elements/viewer-button/img/hiDPI/button_fit_page.png" type="BINDATA" />
- <include name="IDR_PDF_BUTTON_HIGH_2" file="pdf/html_office/elements/viewer-button/img/hiDPI/button_fit_width.png" type="BINDATA" />
- <include name="IDR_PDF_BUTTON_HIGH_3" file="pdf/html_office/elements/viewer-button/img/hiDPI/button_zoom_out.png" type="BINDATA" />
- <include name="IDR_PDF_BUTTON_HIGH_4" file="pdf/html_office/elements/viewer-button/img/hiDPI/button_zoom_in.png" type="BINDATA" />
- <include name="IDR_PDF_BUTTON_HIGH_5" file="pdf/html_office/elements/viewer-button/img/hiDPI/button_save.png" type="BINDATA" />
- <include name="IDR_PDF_BUTTON_HIGH_6" file="pdf/html_office/elements/viewer-button/img/hiDPI/button_print.png" type="BINDATA" />
+ <include name="IDR_PDF_BUTTON_LOW_1" file="pdf/elements/viewer-button/img/lowDPI/button_fit_page.png" type="BINDATA" />
+ <include name="IDR_PDF_BUTTON_LOW_2" file="pdf/elements/viewer-button/img/lowDPI/button_fit_width.png" type="BINDATA" />
+ <include name="IDR_PDF_BUTTON_LOW_3" file="pdf/elements/viewer-button/img/lowDPI/button_zoom_out.png" type="BINDATA" />
+ <include name="IDR_PDF_BUTTON_LOW_4" file="pdf/elements/viewer-button/img/lowDPI/button_zoom_in.png" type="BINDATA" />
+ <include name="IDR_PDF_BUTTON_LOW_5" file="pdf/elements/viewer-button/img/lowDPI/button_save.png" type="BINDATA" />
+ <include name="IDR_PDF_BUTTON_LOW_6" file="pdf/elements/viewer-button/img/lowDPI/button_print.png" type="BINDATA" />
+ <include name="IDR_PDF_BUTTON_HIGH_1" file="pdf/elements/viewer-button/img/hiDPI/button_fit_page.png" type="BINDATA" />
+ <include name="IDR_PDF_BUTTON_HIGH_2" file="pdf/elements/viewer-button/img/hiDPI/button_fit_width.png" type="BINDATA" />
+ <include name="IDR_PDF_BUTTON_HIGH_3" file="pdf/elements/viewer-button/img/hiDPI/button_zoom_out.png" type="BINDATA" />
+ <include name="IDR_PDF_BUTTON_HIGH_4" file="pdf/elements/viewer-button/img/hiDPI/button_zoom_in.png" type="BINDATA" />
+ <include name="IDR_PDF_BUTTON_HIGH_5" file="pdf/elements/viewer-button/img/hiDPI/button_save.png" type="BINDATA" />
+ <include name="IDR_PDF_BUTTON_HIGH_6" file="pdf/elements/viewer-button/img/hiDPI/button_print.png" type="BINDATA" />
</if>
<include name="IDR_CRYPTOTOKEN_UTIL_JS" file="cryptotoken/util.js" type="BINDATA" />
<include name="IDR_CRYPTOTOKEN_B64_JS" file="cryptotoken/b64.js" type="BINDATA" />
@@ -209,14 +261,14 @@
<include name="IDR_CRYPTOTOKEN_FACTORYREGISTRY_JS" file="cryptotoken/factoryregistry.js" type="BINDATA" />
<include name="IDR_CRYPTOTOKEN_ERRORCODES_JS" file="cryptotoken/errorcodes.js" type="BINDATA" />
<include name="IDR_CRYPTOTOKEN_DEVICEFACTORYREGISTRY_JS" file="cryptotoken/devicefactoryregistry.js" type="BINDATA" />
- <include name="IDR_CRYPTOTOKEN_GSTATICORIGINCHECK_JS" file="cryptotoken/gstaticorigincheck.js" type="BINDATA" />
<include name="IDR_CRYPTOTOKEN_ORIGINCHECK_JS" file="cryptotoken/origincheck.js" type="BINDATA" />
<include name="IDR_CRYPTOTOKEN_INDIVIDUALATTEST_JS" file="cryptotoken/individualattest.js" type="BINDATA" />
<include name="IDR_CRYPTOTOKEN_GOOGLECORPINDIVIDUALATTEST_JS" file="cryptotoken/googlecorpindividualattest.js" type="BINDATA" />
<include name="IDR_CRYPTOTOKEN_APPROVEDORIGINS_JS" file="cryptotoken/approvedorigins.js" type="BINDATA" />
- <include name="IDR_CRYPTOTOKEN_GOOGLEAPPROVEDORIGINS_JS" file="cryptotoken/googleapprovedorigins.js" type="BINDATA" />
<include name="IDR_CRYPTOTOKEN_WEBREQUESTSENDER_JS" file="cryptotoken/webrequestsender.js" type="BINDATA" />
<include name="IDR_CRYPTOTOKEN_WATCHDOG_JS" file="cryptotoken/watchdog.js" type="BINDATA" />
+ <include name="IDR_CRYPTOTOKEN_CRYPTOTOKENAPPROVEDORIGIN_JS" file="cryptotoken/cryptotokenapprovedorigins.js" type="BINDATA" />
+ <include name="IDR_CRYPTOTOKEN_CRYPTOTOKENORIGINCHECK_JS" file="cryptotoken/cryptotokenorigincheck.js" type="BINDATA" />
<include name="IDR_CRYPTOTOKEN_CRYPTOTOKENBACKGROUND_JS" file="cryptotoken/cryptotokenbackground.js" type="BINDATA" />
<include name="IDR_WHISPERNET_PROXY_BACKGROUND_HTML" file="whispernet_proxy/background.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" />
<include name="IDR_WHISPERNET_PROXY_INIT_JS" file="whispernet_proxy/js/init.js" type="BINDATA" />
diff --git a/chromium/chrome/browser/resources/components.css b/chromium/chrome/browser/resources/components.css
index 450bb172d6e..22855b63ce5 100644
--- a/chromium/chrome/browser/resources/components.css
+++ b/chromium/chrome/browser/resources/components.css
@@ -26,8 +26,8 @@ a {
#header > h1 {
background: -webkit-image-set(
- url('../../app/theme/default_100_percent/common/extensions_section.png') 1x,
- url('../../app/theme/default_200_percent/common/extensions_section.png') 2x)
+ url(../../app/theme/default_100_percent/common/extensions_section.png) 1x,
+ url(../../app/theme/default_200_percent/common/extensions_section.png) 2x)
0 20px no-repeat;
display: inline;
margin: 0;
@@ -38,8 +38,8 @@ a {
html[dir=rtl] #header > h1 {
background: -webkit-image-set(
- url('../../app/theme/default_100_percent/common/extensions_section.png') 1x,
- url('../../app/theme/default_200_percent/common/extensions_section.png') 2x)
+ url(../../app/theme/default_100_percent/common/extensions_section.png) 1x,
+ url(../../app/theme/default_200_percent/common/extensions_section.png) 2x)
right no-repeat;
padding-left: 0;
padding-right: 95px;
diff --git a/chromium/chrome/browser/resources/components.html b/chromium/chrome/browser/resources/components.html
index b7dfded3e85..aef3c973218 100644
--- a/chromium/chrome/browser/resources/components.html
+++ b/chromium/chrome/browser/resources/components.html
@@ -1,15 +1,16 @@
-<!DOCTYPE HTML>
-<html i18n-values="dir:textdirection;">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
<head>
<meta charset="utf-8">
<title i18n-content="componentsTitle"></title>
+<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="components.css">
<if expr="chromeos">
<link rel="stylesheet"
href="chrome://resources/css/chromeos/ui_account_tweaks.css">
</if>
</head>
-<body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<body>
<div id="body-container" style="visibility:hidden">
<div id="header"><h1 i18n-content="componentsTitle">TITLE</h1></div>
@@ -79,6 +80,6 @@
<script src="chrome://resources/js/load_time_data.js"></script>
<script src="chrome://resources/js/jstemplate_compiled.js"></script>
<script src="chrome://components/strings.js"></script>
-<script src="chrome://resources/js/i18n_template2.js"></script>
+<script src="chrome://resources/js/i18n_template.js"></script>
</body>
</html>
diff --git a/chromium/chrome/browser/resources/contextual_search/header.svg b/chromium/chrome/browser/resources/contextual_search/header.svg
index 714a55dc7db..3fb9aa3585d 100644
--- a/chromium/chrome/browser/resources/contextual_search/header.svg
+++ b/chromium/chrome/browser/resources/contextual_search/header.svg
@@ -1,64 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 355.5 352.5" enable-background="new 0 0 355.5 352.5" xml:space="preserve">
+<svg version="1.1" id="Layer_2" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 156 99" enable-background="new 0 0 156 99" xml:space="preserve">
<g>
- <circle fill="#4788F4" cx="182.8" cy="176" r="163.9"/>
- <circle fill="#3F87FF" cx="182.8" cy="176" r="163.9"/>
- <g opacity="0.5">
- <defs>
- <path id="SVGID_1_" opacity="0.5" d="M286.1,17.8l-252,357.7c-9.3,69.8,187.6,72.8,238.1,43.7c47-27.2,125.7-73.5,124-158.1
- C394.5,176.4,348.5,15.2,286.1,17.8z"/>
- </defs>
- <clipPath id="SVGID_2_">
- <use xlink:href="#SVGID_1_" overflow="visible"/>
- </clipPath>
- <linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="303.0323" y1="-12.4325" x2="116.4693" y2="279.9136">
- <stop offset="0" style="stop-color:#4788F4"/>
- <stop offset="1" style="stop-color:#3360AD"/>
- </linearGradient>
- <circle clip-path="url(#SVGID_2_)" fill="url(#SVGID_3_)" cx="182.8" cy="176" r="163.9"/>
- </g>
- <circle fill="#9DBEF2" stroke="#29508F" stroke-width="15.5552" stroke-miterlimit="10" cx="182.6" cy="173.6" r="54.9"/>
- <linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="225.0402" y1="213.4908" x2="309.5199" y2="286.2732">
- <stop offset="0" style="stop-color:#1A1A1A"/>
- <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0"/>
- </linearGradient>
- <polygon opacity="0.1" fill="url(#SVGID_4_)" points="460.4,342.1 296.8,350.5 144.1,223.3 186.5,230.3 219.1,214.8 236,185.1
- 238.4,145.5 "/>
-
- <line fill="none" stroke="#214175" stroke-width="15.5552" stroke-linecap="round" stroke-miterlimit="10" x1="224.5" y1="211.6" x2="268.6" y2="251.6"/>
- <circle fill="none" stroke="#29508F" stroke-width="15.5552" stroke-miterlimit="10" cx="182.6" cy="173.6" r="54.9"/>
- <path fill="#214175" d="M150.1,185.4c1.5,14,15.2,25,31.8,25c16.6,0,30.2-11,31.8-25H150.1z"/>
- <path fill="none" stroke="#222222" stroke-width="15.5552" stroke-linecap="round" stroke-miterlimit="10" d="M239.8,198.8"/>
- <g>
- <g>
- <g>
- <path fill="#214175" d="M157.3,152.5c-4.1,0-7.5,5.6-7.5,12.6c0,7,3.4,12.6,7.5,12.6c4.1,0,7.5-5.6,7.5-12.6
- C164.8,158.2,161.5,152.5,157.3,152.5z M207.4,152.5c-4.1,0-7.5,5.6-7.5,12.6c0,7,3.4,12.6,7.5,12.6c4.1,0,7.5-5.6,7.5-12.6
- C214.9,158.2,211.5,152.5,207.4,152.5z"/>
- </g>
- </g>
- </g>
- <linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="97.7798" y1="99.6319" x2="182.315" y2="172.4622">
- <stop offset="0" style="stop-color:#1A1A1A"/>
- <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0"/>
- </linearGradient>
- <polygon opacity="0.1" fill="url(#SVGID_5_)" points="339.5,221.1 171.9,245.8 13.2,113.5 119.9,45.7 "/>
+ <rect x="48.2" y="55" fill="#51C1FF" width="108" height="26.7"/>
+ <path fill="#D4DDE1" d="M64,44c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4v-1c0-2.2,1.8-4,4-4h56c2.2,0,4,1.8,4,4V44z"/>
+ <path fill="#D4DDE1" d="M138,44c0,2.2-1.8,4-4,4H78c-2.2,0-4-1.8-4-4v-1c0-2.2,1.8-4,4-4h56c2.2,0,4,1.8,4,4V44z"/>
+ <path fill="#17F9FF" d="M145.9,69c0,2.2-1.8,4-4,4H62.2c-2.2,0-4-1.8-4-4v-1c0-2.2,1.8-4,4-4h79.7c2.2,0,4,1.8,4,4V69z"/>
+ <path fill="#D4DDE1" d="M37.8,69c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4v-1c0-2.2,1.8-4,4-4h29.8c2.2,0,4,1.8,4,4V69z"/>
+ <path fill="#D4DDE1" d="M79,95c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4v-1c0-2.2,1.8-4,4-4h71c2.2,0,4,1.8,4,4V95z"/>
+ <path fill="#D4DDE1" d="M119.1,95c0,2.2-1.8,4-4,4H93c-2.2,0-4-1.8-4-4v-1c0-2.2,1.8-4,4-4h22.1c2.2,0,4,1.8,4,4V95z"/>
<g>
- <path fill="#E8E8E8" d="M127.4,108.3c0,7.8-6.3,14.1-14.1,14.1H26.5c-7.8,0-14.1-6.3-14.1-14.1V60.5c0-7.8,6.3-14.1,14.1-14.1
- h86.8c7.8,0,14.1,6.3,14.1,14.1V108.3z"/>
+ <path fill="#448AFF" d="M122.2,21c0-11-9-20-20-20s-20,9-20,20c0,4.8,1.7,9.1,4.5,12.6c0,0,0,0,0,0l15.5,20l15.9-20.4c0,0,0,0,0,0
+ C120.6,29.8,122.2,25.6,122.2,21z"/>
</g>
- <path fill="#E8E8E8" d="M106.8,116.9c0,0-3.5,31.7,7.9,33.5c11.5,1.8-40.6,4.4-40.6-29.1s29.1-4.4,29.1-4.4"/>
- <path opacity="0.2" fill="none" stroke="#F2F2F2" stroke-width="14.1051" stroke-linecap="round" d="M174.8,140.5
- c0,0-19.4,3.1-22.7,19.2"/>
- <path fill="#4788F4" d="M317.5,61"/>
-
- <line opacity="0.4" fill="none" stroke="#4788F4" stroke-width="6" stroke-linecap="round" stroke-miterlimit="10" x1="28.4" y1="67.4" x2="101.4" y2="67.4"/>
-
- <line opacity="0.4" fill="none" stroke="#4788F4" stroke-width="6" stroke-linecap="round" stroke-miterlimit="10" x1="28.4" y1="84.4" x2="91.4" y2="84.4"/>
-
- <line opacity="0.4" fill="none" stroke="#4788F4" stroke-width="6" stroke-linecap="round" stroke-miterlimit="10" x1="28.4" y1="101.4" x2="91.4" y2="101.4"/>
+ <path fill="#FFFFFF" d="M106.2,23.1l-0.9-0.7c-0.3-0.3-0.7-0.6-0.7-1.2c0-0.6,0.4-1.1,0.9-1.4c1.1-0.9,2.2-1.8,2.2-3.7
+ c0-1.8-1.1-2.8-1.7-3.3h1.5l1-1.2h-5.3c-3.8,0-5.6,2.4-5.6,5c0,2,1.5,4.1,4.3,4.1h0.7c-0.1,0.3-0.3,0.7-0.3,1.1
+ c0,0.9,0.3,1.2,0.8,1.7c-1.2,0.1-3.4,0.3-5,1.4c-1.5,0.9-2,2.2-2,3.2c0,2,1.8,3.8,5.6,3.8c4.6,0,6.8-2.6,6.8-5
+ C108.4,25.1,107.4,24.1,106.2,23.1z M99.7,15.3c0-1.9,1.1-2.7,2.3-2.7c2.2,0,3.4,3,3.4,4.7c0,2.2-1.8,2.7-2.5,2.7
+ C100.9,19.9,99.7,17.3,99.7,15.3z M102.8,30.7c-2.8,0-4.6-1.3-4.6-3.2c0-1.9,1.7-2.5,2.2-2.7c1.1-0.3,2.6-0.4,2.8-0.4h0.6
+ c2.1,1.5,2.9,2.1,2.9,3.4C106.8,29.4,105.1,30.7,102.8,30.7z"/>
</g>
</svg>
diff --git a/chromium/chrome/browser/resources/contextual_search/promo.css b/chromium/chrome/browser/resources/contextual_search/promo.css
index c82d0bc42bd..63fe3392ee4 100644
--- a/chromium/chrome/browser/resources/contextual_search/promo.css
+++ b/chromium/chrome/browser/resources/contextual_search/promo.css
@@ -1,16 +1,31 @@
/* 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.
-*/
+ found in the LICENSE file. */
+
+/* TODO: Need to clean up @font-face after we remove font-family from body. */
@font-face {
font-family: 'Roboto2';
font-weight: 400;
src: local('Roboto'), local('Roboto2-Regular'),
- url(/roboto.woff2) format('woff2'), url(/roboto.woff) format('woff');
+ url(chrome://resources/roboto/roboto.woff2) format('woff2'),
+ url(chrome://resources/roboto/roboto.woff) format('woff');
+}
+
+/* TODO(pedrosimonetti): Find a better way to fix the problem when the
+ * text scaling preference is set to a high value.
+ *
+ * This CSS rule prevents the promo text from scaling as explained here:
+ * https://code.google.com/p/chromium/issues/detail?id=252828#c10
+ *
+ * For for background about the problem, see:
+ * https://code.google.com/p/chromium/issues/detail?id=466773
+ */
+#heading,
+#description {
+ max-height: 999999px;
}
body {
- color: #414141;
font-family: 'Roboto2', sans-serif;
margin: 0;
}
@@ -23,34 +38,51 @@ a.colored-link {
color: rgb(66, 133, 244);
}
+#container {
+ /* NOTE(pedrosimonetti): There's an implicit extra top margin that is
+ * rendered natively (currently using 24dp). So, the total padding will
+ * be 38dp (24dp + 14dp). For more info, see SEARCH_BAR_HEIGHT_STATE_PROMO
+ * in ContextualSearchPanelBase.java.
+ *
+ * We're also setting the side and bottom paddings to ensure to make sure
+ * that when computing the height of the container all margins/paddings will
+ * be considered.
+ */
+ padding: 14px 16px 12px;
+}
+
#button-container {
+ margin-top: 24px;
text-align: end;
width: 100%;
}
#container.hide {
-webkit-transform: scale(0.95);
- -webkit-transition-duration: 218ms;
+ -webkit-transition-duration: 130ms;
-webkit-transition-property: opacity, -webkit-transform;
opacity: 0;
}
#description {
+ color: #7E7E7E;
font-size: 16px;
- line-height: 1.4em;
- margin: 24px 16px 4px 16px;
+ line-height: 1.38em;
+ margin: 12px 0 24px;
}
/* Some properties below can be overridden in landscape orientation. */
#heading {
- font-size: 24px;
- letter-spacing: 1px;
- margin-top: 0;
+ font-size: 23px;
+ margin: 20px 0 12px;
text-align: center;
}
-#header-image {
- height: 100px;
- margin: 0 auto 24px auto;
+.header-image {
+ background-image: url(header.svg);
+ background-repeat: no-repeat;
+ height: 98px;
+ margin: 0 auto 38px auto;
+ width: 156px;
}
.portrait {
display: block;
@@ -62,10 +94,27 @@ a.colored-link {
/* Landscape */
@media screen and (orientation:landscape) {
#heading {
- padding-top: 16px;
+ margin-top: 0;
+ /* The heading text and description text should be aligned, therefore
+ * the left margin here will be equal to the header image width (156px)
+ * plus its right margin (24px). Therefore the total left should be
+ * 156px + 24px = 180px.
+ */
+ margin-left: 180px;
+ padding-top: 8px;
+ text-align: left;
}
- #header-image {
- margin: 0 24px;
+ .header-image {
+ /* The header image is supposed to be vertically centered when the promo
+ * is in landscape mode. For now, we're forcefully moving the image 4
+ * pixels up to make it centered. A better approach would be using CSS
+ * flexbox to properly center it, but this will require changing the
+ * markup and styling of the whole promo, and it could be tricky coming
+ * up with a single markup that works in both portrait and lanscape modes.
+ */
+ margin: 0 24px 0 0;
+ position: relative;
+ top: -4px;
}
.portrait {
display: none;
@@ -79,32 +128,35 @@ a.colored-link {
}
}
-.label {
+button {
+ background: none;
+ border: none;
display: inline-block;
+ font-family: 'Roboto2', sans-serif;
font-size: 14px;
- margin: 14px 0;
+ margin: 6px 0;
/* We use a slightly different top-bottom padding because Roboto has a
- rendering bug which makes an extra padding to be rendered at the bottom of
- text. */
- padding: 17px 14px 13px 14px;
+ * rendering bug which makes an extra padding to be rendered at the bottom of
+ * text.
+ */
+ padding: 12px 16px 8px;
white-space: nowrap;
}
-.label a {
+button .caption {
text-transform: uppercase;
}
-#optin-label {
+#optin-button {
background: rgb(66, 133, 244);
background-clip: padding-box;
border-radius: 3px;
- margin-right: 24px;
}
-#optin-label a {
+#optin-button .caption {
color: white;
}
-#optout-label a {
+#optout-button .caption {
color: rgb(66, 133, 244);
}
diff --git a/chromium/chrome/browser/resources/contextual_search/promo.html b/chromium/chrome/browser/resources/contextual_search/promo.html
index 03c79c8d861..b5bbd1bcda2 100644
--- a/chromium/chrome/browser/resources/contextual_search/promo.html
+++ b/chromium/chrome/browser/resources/contextual_search/promo.html
@@ -1,4 +1,4 @@
-<!DOCTYPE html>
+<!doctype html>
<html>
<!-- Copyright 2014 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
@@ -7,25 +7,30 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no">
<title>Contextual Search First-Run</title>
+ <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="promo.css">
<script src="chrome://contextual-search-promo/config.js"></script>
<script src="chrome://contextual-search-promo/promo.js"></script>
</head>
<body>
<div id="container">
- <img id="header-image" class="portrait" src="header.svg">
- <p i18n-content="heading" id="heading"></p>
- <div>
- <img id="header-image" class="landscape" src="header.svg">
- <p id="description">
- <span i18n-content="description-1"></span>
- <a class="colored-link" href="#learn-more" i18n-content="feature-name"></a>
- <span i18n-content="description-2"></span>
- </p>
+ <div class="header-image portrait"></div>
+ <div id="heading" i18n-content="heading"></div>
+ <div class="header-image landscape"></div>
+ <div id="description">
+ <span i18n-content="description-1"></span>
+ <a class="colored-link" href="#learn-more"
+ i18n-content="feature-name">
+ </a>
+ <span i18n-content="description-2"></span>
</div>
<div id="button-container">
- <span id="optout-label" class="label"><a href="#optout" i18n-content="optOut"></a></span>
- <span id="optin-label" class="label"><a href="#optin" i18n-content="optIn"></a></span>
+ <button id="optout-button">
+ <span class="caption" i18n-content="optOut"></span>
+ </button>
+ <button id="optin-button">
+ <span class="caption" i18n-content="optIn"></span>
+ </button>
</div>
</div>
</body>
diff --git a/chromium/chrome/browser/resources/contextual_search/promo.js b/chromium/chrome/browser/resources/contextual_search/promo.js
index 616acac3b8d..8e17d909aea 100644
--- a/chromium/chrome/browser/resources/contextual_search/promo.js
+++ b/chromium/chrome/browser/resources/contextual_search/promo.js
@@ -7,16 +7,30 @@
<include src="../../../../ui/webui/resources/js/load_time_data.js">
/**
+ * The amount of delay to use in the opt-in action in order to give time for
+ * the fade-out animation to execute, before navigating to the opt-in URL,
+ * in milliseconds.
+ * @const
+ */
+var OPT_IN_DELAY_MS = 65;
+
+/**
* Once the DOM is loaded, determine if the header image is to be kept and
* register a handler to add the 'hide' class to the container element in order
* to hide it.
*/
document.addEventListener('DOMContentLoaded', function(event) {
if (config['hideHeader']) {
- $('container').removeChild($('header-image'));
+ removeHeaderImages();
}
- $('optin-label').addEventListener('click', function() {
+ $('optin-button').addEventListener('click', function() {
$('container').classList.add('hide');
+ setTimeout(function() {
+ location.hash = 'optin';
+ }, OPT_IN_DELAY_MS);
+ });
+ $('optout-button').addEventListener('click', function() {
+ location.hash = 'optout';
});
});
@@ -28,3 +42,17 @@ document.addEventListener('DOMContentLoaded', function(event) {
function getContentHeight() {
return $('container').clientHeight;
}
+
+/**
+ * Removes all header images from the promo.
+ */
+function removeHeaderImages() {
+ var images = document.querySelectorAll('.header-image');
+ for (var i = 0, length = images.length; i < length; i++) {
+ var image = images[i];
+ var parent = image.parentElement;
+ if (parent) {
+ parent.removeChild(image);
+ }
+ }
+}
diff --git a/chromium/chrome/browser/resources/copresence.css b/chromium/chrome/browser/resources/copresence.css
new file mode 100644
index 00000000000..6d7b58065e2
--- /dev/null
+++ b/chromium/chrome/browser/resources/copresence.css
@@ -0,0 +1,69 @@
+/* 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. */
+
+body {
+ margin: 10px 20px;
+}
+
+section {
+ margin-top: 40px;
+}
+
+table {
+ border: 1px solid gray;
+ border-spacing: 0;
+ margin-top: 15px;
+ width: 600px;
+}
+
+thead {
+ background-color: rgb(220, 240, 255);
+ font-weight: bold;
+}
+
+tbody {
+ border-top: 1px solid gray;
+ height: 200px;
+ overflow-x: hidden;
+ overflow-y: scroll;
+}
+
+td {
+ padding: 5px;
+ text-align: left;
+}
+
+td::first-letter {
+ text-transform: capitalize;
+}
+
+.confirmed,
+.valid {
+ color: green;
+}
+
+.done {
+ color: darkGray;
+}
+
+.invalid {
+ color: red;
+}
+
+.directives td {
+ width: 190px;
+}
+
+.tokens td {
+ width: 140px;
+}
+
+table td.spacer {
+ width: 5px;
+}
+
+tbody,
+thead > tr {
+ display: block;
+}
diff --git a/chromium/chrome/browser/resources/copresence.html b/chromium/chrome/browser/resources/copresence.html
new file mode 100644
index 00000000000..985a64e617c
--- /dev/null
+++ b/chromium/chrome/browser/resources/copresence.html
@@ -0,0 +1,75 @@
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
+
+<head>
+ <meta charset="utf-8">
+ <title i18n-content="title"></title>
+ <link rel="stylesheet" href="chrome://resources/css/chrome_shared.css">
+ <link rel="stylesheet" href="chrome://copresence/copresence.css">
+</head>
+
+<body>
+ <header>
+ <h1 i18n-content="title"></h1>
+ </header>
+
+ <main>
+ <section>
+ <h2 i18n-content="directives_title"></h2>
+ <table class="directives" id="directives-table">
+ <thead>
+ <tr>
+ <td i18n-content="directive_type">
+ <td i18n-content="token_medium">
+ <td i18n-content="duration">
+ <td class="spacer">
+ <tbody>
+ </table>
+ </section>
+
+ <section>
+ <h2 i18n-content="transmitted_tokens_title"></h2>
+ <table class="tokens" id="transmitted-tokens-table">
+ <thead>
+ <tr>
+ <td i18n-content="token_id">
+ <td i18n-content="token_status">
+ <td i18n-content="token_medium">
+ <td i18n-content="token_transmit_time">
+ <td class="spacer">
+ <tbody>
+ </table>
+ </section>
+
+ <section>
+ <h2 i18n-content="received_tokens_title"></h2>
+ <table class="tokens" id="received-tokens-table">
+ <thead>
+ <tr>
+ <td i18n-content="token_id">
+ <td i18n-content="token_status">
+ <td i18n-content="token_medium">
+ <td i18n-content="token_receive_time">
+ <td class="spacer">
+ <tbody>
+ </table>
+ </section>
+
+ <!-- TODO(ckehoe): Add server calls. -->
+
+ <!-- TODO(ckehoe): Add GCM pings. -->
+
+ <section>
+ <button i18n-content="clear_state" id="reset-button">
+ </section>
+
+ </main>
+
+ <script src="chrome://resources/js/load_time_data.js"></script>
+ <script src="chrome://resources/js/util.js"></script>
+ <script src="chrome://copresence/copresence.js"></script>
+ <script src="chrome://copresence/strings.js"></script>
+ <script src="chrome://resources/js/i18n_template.js"></script>
+</body>
+
+</html>
diff --git a/chromium/chrome/browser/resources/copresence.js b/chromium/chrome/browser/resources/copresence.js
new file mode 100644
index 00000000000..ce38e8896a8
--- /dev/null
+++ b/chromium/chrome/browser/resources/copresence.js
@@ -0,0 +1,130 @@
+// 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.
+
+/**
+ * Debug information about an active copresence directive.
+ * @typedef {{
+ * type: string,
+ * medium: string,
+ * duration: string
+ * }}
+ */
+var Directive;
+
+/**
+ * Debug information about a recent copresence token.
+ * @typedef {{
+ * id: string,
+ * statuses: string,
+ * medium: string,
+ * time: string
+ * }}
+ */
+var Token;
+
+/**
+ * Callback to refresh the list of directives.
+ * @param {Array<Directive>} directives
+ */
+function refreshDirectives(directives) {
+ var table = $('directives-table').tBodies[0];
+
+ // Fix the directives table to have the correct number of rows.
+ while (table.rows.length < directives.length)
+ table.insertRow();
+ while (table.rows.length > directives.length)
+ table.deleteRow();
+
+ // Populate the directives into the table.
+ directives.forEach(function(directive, index) {
+ var row = table.rows.item(index);
+ while (row.cells.length < 3)
+ row.insertCell();
+
+ row.cells.item(0).textContent = directive.type;
+ row.cells.item(1).textContent = directive.medium;
+ row.cells.item(2).textContent = directive.duration;
+ });
+}
+
+/**
+ * Callback to add or update transmitted tokens.
+ * @param {Token} token
+ */
+function updateTransmittedToken(token) {
+ updateTokenTable($('transmitted-tokens-table'), token);
+}
+
+/**
+ * Callback to add or update received tokens.
+ * @param {Token} token
+ */
+function updateReceivedToken(token) {
+ updateTokenTable($('received-tokens-table'), token);
+}
+
+/**
+ * Callback to clear out the token tables.
+ */
+function clearTokens() {
+ clearTable($('transmitted-tokens-table'));
+ clearTable($('received-tokens-table'));
+}
+
+/**
+ * Add or update a token in the specified table.
+ * @param {HTMLTableElement} table
+ * @param {Token} token
+ */
+function updateTokenTable(table, token) {
+ var rows = table.tBodies[0].rows;
+
+ var index;
+ for (index = 0; index < rows.length; index++) {
+ var row = rows.item(index);
+ if (row.cells[0].textContent == token.id) {
+ updateTokenRow(row, token);
+ break;
+ }
+ }
+
+ if (index == rows.length)
+ updateTokenRow(table.tBodies[0].insertRow(), token);
+}
+
+/**
+ * Update a token on the specified row.
+ * @param {HTMLTableRowElement} row
+ * @param {Token} token
+ */
+function updateTokenRow(row, token) {
+ while (row.cells.length < 4)
+ row.insertCell();
+ row.className = token.statuses;
+
+ row.cells[0].textContent = token.id;
+ row.cells[1].textContent =
+ token.statuses.replace('confirmed', '(Confirmed)');
+ row.cells[2].textContent = token.medium;
+ row.cells[3].textContent = token.time;
+}
+
+/**
+ * Delete all the rows in a table.
+ * @param {HTMLTableElement} table
+ */
+function clearTable(table) {
+ var body = table.tBodies[0];
+ while (body.rows.length > 0)
+ body.deleteRow();
+}
+
+document.addEventListener('DOMContentLoaded', function() {
+ chrome.send('populateCopresenceState');
+
+ $('reset-button').addEventListener('click', function() {
+ if (confirm(loadTimeData.getString('confirm_delete')))
+ chrome.send('clearCopresenceState');
+ });
+});
diff --git a/chromium/chrome/browser/resources/crashes.css b/chromium/chrome/browser/resources/crashes.css
index 1d8d548a4fe..49815193c95 100644
--- a/chromium/chrome/browser/resources/crashes.css
+++ b/chromium/chrome/browser/resources/crashes.css
@@ -9,9 +9,9 @@ body {
h1 {
-webkit-padding-start: 75px;
background-image: -webkit-image-set(
- url('../../app/theme/default_100_percent/common/sadtab.png') 1x,
- url('../../app/theme/default_200_percent/common/sadtab.png') 2x,
- url('../../app/theme/default_300_percent/common/sadtab.png') 3x);
+ url(../../app/theme/default_100_percent/common/sadtab.png) 1x,
+ url(../../app/theme/default_200_percent/common/sadtab.png) 2x,
+ url(../../app/theme/default_300_percent/common/sadtab.png) 3x);
background-position: left;
background-repeat: no-repeat;
font-size: 156%;
diff --git a/chromium/chrome/browser/resources/crashes.html b/chromium/chrome/browser/resources/crashes.html
index 1f749622d57..b848e68e59d 100644
--- a/chromium/chrome/browser/resources/crashes.html
+++ b/chromium/chrome/browser/resources/crashes.html
@@ -1,8 +1,9 @@
-<!DOCTYPE HTML>
-<html i18n-values="dir:textdirection;">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
<head>
<meta charset="utf-8">
<title i18n-content="crashesTitle"></title>
+ <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="chrome://resources/css/widgets.css">
<link rel="stylesheet" href="crashes.css">
<script src="chrome://resources/js/action_link.js"></script>
@@ -11,7 +12,7 @@
<script src="chrome://crashes/strings.js"></script>
<script src="chrome://crashes/crashes.js"></script>
</head>
-<body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<body>
<header><h1 i18n-content="crashesTitle"></h1></header>
<div id="crashUploadStatus" hidden>
<a is="action-link" role="button" id="uploadCrashes"
@@ -26,7 +27,7 @@
<h2 i18n-content="disabledHeader"></h2>
<p i18n-values=".innerHTML:disabledMessage"></p>
</div>
- <script src="chrome://resources/js/i18n_template2.js"></script>
+ <script src="chrome://resources/js/i18n_template.js"></script>
<script src="chrome://resources/js/jstemplate_compiled.js"></script>
</body>
</html>
diff --git a/chromium/chrome/browser/resources/cryptotoken/OWNERS b/chromium/chrome/browser/resources/cryptotoken/OWNERS
new file mode 100644
index 00000000000..aac50e0e8f2
--- /dev/null
+++ b/chromium/chrome/browser/resources/cryptotoken/OWNERS
@@ -0,0 +1,2 @@
+arnarb@chromium.org
+juanlang@chromium.org
diff --git a/chromium/chrome/browser/resources/cryptotoken/appid.js b/chromium/chrome/browser/resources/cryptotoken/appid.js
index fcaafac6cd6..b5f8ed5faba 100644
--- a/chromium/chrome/browser/resources/cryptotoken/appid.js
+++ b/chromium/chrome/browser/resources/cryptotoken/appid.js
@@ -10,13 +10,35 @@
/**
* Parses the text as JSON and returns it as an array of strings.
* @param {string} text Input JSON
- * @return {!Array.<string>} Array of origins
+ * @return {!Array<string>} Array of origins
*/
function getOriginsFromJson(text) {
try {
- var urls = JSON.parse(text);
+ var urls, i;
+ var appIdData = JSON.parse(text);
+ if (Array.isArray(appIdData)) {
+ // Older format where it is a simple list of facets
+ urls = appIdData;
+ } else {
+ var trustedFacets = appIdData['trustedFacets'];
+ if (trustedFacets) {
+ var versionBlock;
+ for (i = 0; versionBlock = trustedFacets[i]; i++) {
+ if (versionBlock['version'] &&
+ versionBlock['version']['major'] == 1 &&
+ versionBlock['version']['minor'] == 0) {
+ urls = versionBlock['ids'];
+ break;
+ }
+ }
+ }
+ if (typeof urls == 'undefined') {
+ throw Error('Could not find trustedFacets for version 1.0');
+ }
+ }
var origins = {};
- for (var i = 0, url; url = urls[i]; i++) {
+ var url;
+ for (i = 0; url = urls[i]; i++) {
var origin = getOriginFromUrl(url);
if (origin) {
origins[origin] = origin;
@@ -24,15 +46,15 @@ function getOriginsFromJson(text) {
}
return Object.keys(origins);
} catch (e) {
- console.log(UTIL_fmt('could not parse ' + text));
+ console.error(UTIL_fmt('could not parse ' + text));
return [];
}
}
/**
* Retrieves a set of distinct app ids from the sign challenges.
- * @param {Array.<SignChallenge>=} signChallenges Input sign challenges.
- * @return {Array.<string>} array of distinct app ids.
+ * @param {Array<SignChallenge>=} signChallenges Input sign challenges.
+ * @return {Array<string>} array of distinct app ids.
*/
function getDistinctAppIds(signChallenges) {
if (!signChallenges) {
@@ -53,7 +75,7 @@ function getDistinctAppIds(signChallenges) {
* @param {!TextFetcher} fetcher A URL fetcher.
* @param {!Countdown} timer A timer by which to resolve all provided app ids.
* @param {string} origin The origin to check.
- * @param {!Array.<string>} appIds The app ids to check.
+ * @param {!Array<string>} appIds The app ids to check.
* @param {boolean} allowHttp Whether to allow http:// URLs.
* @param {string=} opt_logMsgUrl A log message URL.
* @constructor
@@ -72,7 +94,7 @@ function AppIdChecker(fetcher, timer, origin, appIds, allowHttp, opt_logMsgUrl)
appIdsMap[appIds[i]] = appIds[i];
}
}
- /** @private {Array.<string>} */
+ /** @private {Array<string>} */
this.distinctAppIds_ = Object.keys(appIdsMap);
/** @private {boolean} */
this.allowHttp_ = allowHttp;
@@ -89,7 +111,7 @@ function AppIdChecker(fetcher, timer, origin, appIds, allowHttp, opt_logMsgUrl)
/**
* Checks whether all the app ids provided can be asserted by the given origin.
- * @return {Promise.<boolean>} A promise for the result of the check
+ * @return {Promise<boolean>} A promise for the result of the check
*/
AppIdChecker.prototype.doCheck = function() {
if (!this.distinctAppIds_.length)
@@ -115,7 +137,7 @@ AppIdChecker.prototype.doCheck = function() {
/**
* Checks if a single appId can be asserted by the given origin.
* @param {string} appId The appId to check
- * @return {Promise.<boolean>} A promise for the result of the check
+ * @return {Promise<boolean>} A promise for the result of the check
* @private
*/
AppIdChecker.prototype.checkAppId_ = function(appId) {
@@ -157,7 +179,7 @@ AppIdChecker.prototype.allAppIdsEqualOrigin_ = function() {
/**
* Fetches the allowed origins for an appId.
* @param {string} appId Application id
- * @return {Promise.<!Array.<string>>} A promise for a list of allowed origins
+ * @return {Promise<!Array<string>>} A promise for a list of allowed origins
* for appId
* @private
*/
diff --git a/chromium/chrome/browser/resources/cryptotoken/approvedorigins.js b/chromium/chrome/browser/resources/cryptotoken/approvedorigins.js
index f1027c1e1fb..41c854ac934 100644
--- a/chromium/chrome/browser/resources/cryptotoken/approvedorigins.js
+++ b/chromium/chrome/browser/resources/cryptotoken/approvedorigins.js
@@ -22,6 +22,6 @@ function ApprovedOrigins() {}
* @param {string} origin The origin to approve.
* @param {number=} opt_tabId A tab id to display approval prompt in, if
* necessary.
- * @return {Promise.<boolean>} A promise for the result of the check.
+ * @return {Promise<boolean>} A promise for the result of the check.
*/
ApprovedOrigins.prototype.isApprovedOrigin = function(origin, opt_tabId) {};
diff --git a/chromium/chrome/browser/resources/cryptotoken/cryptotokenapprovedorigins.js b/chromium/chrome/browser/resources/cryptotoken/cryptotokenapprovedorigins.js
new file mode 100644
index 00000000000..8b4f838c047
--- /dev/null
+++ b/chromium/chrome/browser/resources/cryptotoken/cryptotokenapprovedorigins.js
@@ -0,0 +1,57 @@
+// 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 Provides an implementation of approved origins that relies
+ * on the chrome.cryptotokenPrivate.requestPermission API.
+ * (and only) allows google.com to use security keys.
+ *
+ */
+'use strict';
+
+/**
+ * Allows the caller to check whether the user has approved the use of
+ * security keys from an origin.
+ * @constructor
+ * @implements {ApprovedOrigins}
+ */
+function CryptoTokenApprovedOrigin() {}
+
+/**
+ * Checks whether the origin is approved to use security keys. (If not, an
+ * approval prompt may be shown.)
+ * @param {string} origin The origin to approve.
+ * @param {number=} opt_tabId A tab id to display approval prompt in.
+ * For this implementation, the tabId is always necessary, even though
+ * the type allows undefined.
+ * @return {Promise<boolean>} A promise for the result of the check.
+ */
+CryptoTokenApprovedOrigin.prototype.isApprovedOrigin =
+ function(origin, opt_tabId) {
+ return new Promise(function(resolve, reject) {
+ if (opt_tabId === undefined) {
+ resolve(false);
+ return;
+ }
+ var tabId = /** @type {number} */ (opt_tabId);
+ tabInForeground(tabId).then(function(result) {
+ if (!result) {
+ resolve(false);
+ return;
+ }
+ if (!chrome.tabs || !chrome.tabs.get) {
+ reject();
+ return;
+ }
+ chrome.tabs.get(tabId, function(tab) {
+ if (chrome.runtime.lastError) {
+ resolve(false);
+ return;
+ }
+ var tabOrigin = getOriginFromUrl(tab.url);
+ resolve(tabOrigin == origin);
+ });
+ });
+ });
+};
diff --git a/chromium/chrome/browser/resources/cryptotoken/cryptotokenbackground.js b/chromium/chrome/browser/resources/cryptotoken/cryptotokenbackground.js
index 0973cc1d634..1ac8ee38115 100644
--- a/chromium/chrome/browser/resources/cryptotoken/cryptotokenbackground.js
+++ b/chromium/chrome/browser/resources/cryptotoken/cryptotokenbackground.js
@@ -12,6 +12,9 @@
var BROWSER_SUPPORTS_TLS_CHANNEL_ID = true;
/** @const */
+var HTTP_ORIGINS_ALLOWED = false;
+
+/** @const */
var LOG_SAVER_EXTENSION_ID = 'fjajfjhkeibgmiggdfehjplbhmfkialk';
// Singleton tracking available devices.
@@ -22,9 +25,9 @@ UsbGnubbyDevice.register(gnubbies);
var TIMER_FACTORY = new CountdownTimerFactory();
var FACTORY_REGISTRY = new FactoryRegistry(
- new GoogleApprovedOrigins(),
+ new CryptoTokenApprovedOrigin(),
TIMER_FACTORY,
- new GstaticOriginChecker(),
+ new CryptoTokenOriginChecker(),
new UsbHelper(),
new XhrTextFetcher());
@@ -34,6 +37,81 @@ var DEVICE_FACTORY_REGISTRY = new DeviceFactoryRegistry(
new GoogleCorpIndividualAttestation());
/**
+ * @param {*} request The received request
+ * @return {boolean} Whether the request is a register/enroll request.
+ */
+function isRegisterRequest(request) {
+ if (!request) {
+ return false;
+ }
+ switch (request.type) {
+ case GnubbyMsgTypes.ENROLL_WEB_REQUEST:
+ return true;
+
+ case MessageTypes.U2F_REGISTER_REQUEST:
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+/**
+ * Default response callback to deliver a response to a request.
+ * @param {*} request The received request.
+ * @param {function(*): void} sendResponse A callback that delivers a response.
+ * @param {*} response The response to return.
+ */
+function defaultResponseCallback(request, sendResponse, response) {
+ response['requestId'] = request['requestId'];
+ try {
+ sendResponse(response);
+ } catch (e) {
+ console.warn(UTIL_fmt('caught: ' + e.message));
+ }
+}
+
+/**
+ * Response callback that delivers a response to a request only when the
+ * sender is a foreground tab.
+ * @param {*} request The received request.
+ * @param {!MessageSender} sender The message sender.
+ * @param {function(*): void} sendResponse A callback that delivers a response.
+ * @param {*} response The response to return.
+ */
+function sendResponseToActiveTabOnly(request, sender, sendResponse, response) {
+ tabInForeground(sender.tab.id).then(function(result) {
+ // If the tab is no longer in the foreground, drop the result: the user
+ // is no longer interacting with the tab that originated the request.
+ if (result) {
+ defaultResponseCallback(request, sendResponse, response);
+ }
+ });
+}
+
+/**
+ * Common handler for messages received from chrome.runtime.sendMessage and
+ * chrome.runtime.connect + postMessage.
+ * @param {*} request The received request
+ * @param {!MessageSender} sender The message sender
+ * @param {function(*): void} sendResponse A callback that delivers a response
+ * @return {Closeable} A Closeable request handler.
+ */
+function messageHandler(request, sender, sendResponse) {
+ var responseCallback;
+ if (isRegisterRequest(request)) {
+ responseCallback =
+ sendResponseToActiveTabOnly.bind(null, request, sender, sendResponse);
+ } else {
+ responseCallback =
+ defaultResponseCallback.bind(null, request, sendResponse);
+ }
+ var closeable = handleWebPageRequest(/** @type {Object} */(request),
+ sender, responseCallback);
+ return closeable;
+}
+
+/**
* Listen to individual messages sent from (whitelisted) webpages via
* chrome.runtime.sendMessage
* @param {*} request The received request
@@ -45,28 +123,22 @@ function messageHandlerExternal(request, sender, sendResponse) {
if (sender.id && sender.id === LOG_SAVER_EXTENSION_ID) {
return handleLogSaverMessage(request);
}
- var closeable = handleWebPageRequest(/** @type {Object} */(request),
- sender, function(response) {
- response['requestId'] = request['requestId'];
- try {
- sendResponse(response);
- } catch (e) {
- console.warn(UTIL_fmt('caught: ' + e.message));
- }
- });
+
+ messageHandler(request, sender, sendResponse);
return true; // Tell Chrome not to destroy sendResponse yet
}
chrome.runtime.onMessageExternal.addListener(messageHandlerExternal);
// Listen to direct connection events, and wire up a message handler on the port
chrome.runtime.onConnectExternal.addListener(function(port) {
+ function sendResponse(response) {
+ port.postMessage(response);
+ }
+
var closeable;
port.onMessage.addListener(function(request) {
- closeable = handleWebPageRequest(request, port.sender,
- function(response) {
- response['requestId'] = request['requestId'];
- port.postMessage(response);
- });
+ var sender = /** @type {!MessageSender} */ (port.sender);
+ closeable = messageHandler(request, sender, sendResponse);
});
port.onDisconnect.addListener(function() {
if (closeable) {
diff --git a/chromium/chrome/browser/resources/cryptotoken/cryptotokenorigincheck.js b/chromium/chrome/browser/resources/cryptotoken/cryptotokenorigincheck.js
new file mode 100644
index 00000000000..3e4ac5e0559
--- /dev/null
+++ b/chromium/chrome/browser/resources/cryptotoken/cryptotokenorigincheck.js
@@ -0,0 +1,53 @@
+// Copyright 2015 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 Implements a check whether an origin is allowed to assert an
+ * app id based on whether they share the same effective TLD + 1.
+ *
+ */
+'use strict';
+
+/**
+ * Implements half of the app id policy: whether an origin is allowed to claim
+ * an app id. For checking whether the app id also lists the origin,
+ * @see AppIdChecker.
+ * @implements OriginChecker
+ * @constructor
+ */
+function CryptoTokenOriginChecker() {
+}
+
+/**
+ * Checks whether the origin is allowed to claim the app ids.
+ * @param {string} origin The origin claiming the app id.
+ * @param {!Array<string>} appIds The app ids being claimed.
+ * @return {Promise<boolean>} A promise for the result of the check.
+ */
+CryptoTokenOriginChecker.prototype.canClaimAppIds = function(origin, appIds) {
+ var appIdChecks = appIds.map(this.checkAppId_.bind(this, origin));
+ return Promise.all(appIdChecks).then(function(results) {
+ return results.every(function(result) {
+ return result;
+ });
+ });
+};
+
+/**
+ * Checks if a single appId can be asserted by the given origin.
+ * @param {string} origin The origin.
+ * @param {string} appId The appId to check
+ * @return {Promise<boolean>} A promise for the result of the check
+ * @private
+ */
+CryptoTokenOriginChecker.prototype.checkAppId_ =
+ function(origin, appId) {
+ return new Promise(function(resolve, reject) {
+ if (!chrome.cryptotokenPrivate) {
+ reject();
+ return;
+ }
+ chrome.cryptotokenPrivate.canOriginAssertAppId(origin, appId, resolve);
+ });
+};
diff --git a/chromium/chrome/browser/resources/cryptotoken/devicestatuscodes.js b/chromium/chrome/browser/resources/cryptotoken/devicestatuscodes.js
index 0f6f1436637..dc037c0bae5 100644
--- a/chromium/chrome/browser/resources/cryptotoken/devicestatuscodes.js
+++ b/chromium/chrome/browser/resources/cryptotoken/devicestatuscodes.js
@@ -1,4 +1,4 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
+// Copyright 2015 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.
@@ -55,4 +55,3 @@ DeviceStatusCodes.BUSY_STATUS = -6;
* @const
*/
DeviceStatusCodes.GONE_STATUS = -8;
-
diff --git a/chromium/chrome/browser/resources/cryptotoken/enroller.js b/chromium/chrome/browser/resources/cryptotoken/enroller.js
index 1ee758e6635..57706a136d6 100644
--- a/chromium/chrome/browser/resources/cryptotoken/enroller.js
+++ b/chromium/chrome/browser/resources/cryptotoken/enroller.js
@@ -41,22 +41,48 @@ function handleWebEnrollRequest(messageSender, request, sendResponse) {
sendResponseOnce(sentResponse, closeable, response, sendResponse);
}
+ function timeout() {
+ sendErrorResponse({errorCode: ErrorCodes.TIMEOUT});
+ }
+
var sender = createSenderFromMessageSender(messageSender);
if (!sender) {
sendErrorResponse({errorCode: ErrorCodes.BAD_REQUEST});
return null;
}
+ if (sender.origin.indexOf('http://') == 0 && !HTTP_ORIGINS_ALLOWED) {
+ sendErrorResponse({errorCode: ErrorCodes.BAD_REQUEST});
+ return null;
+ }
- var enroller =
- validateEnrollRequest(
- sender, request, 'enrollChallenges', 'signData',
- sendErrorResponse, sendSuccessResponse);
- if (enroller) {
- var registerRequests = request['enrollChallenges'];
- var signRequests = getSignRequestsFromEnrollRequest(request, 'signData');
- closeable = /** @type {Closeable} */ (enroller);
- enroller.doEnroll(registerRequests, signRequests, request['appId']);
+ if (!isValidEnrollRequest(request, 'enrollChallenges', 'signData')) {
+ sendErrorResponse({errorCode: ErrorCodes.BAD_REQUEST});
+ return null;
}
+
+ var timeoutValueSeconds = getTimeoutValueFromRequest(request);
+ // Attenuate watchdog timeout value less than the enroller's timeout, so the
+ // watchdog only fires after the enroller could reasonably have called back,
+ // not before.
+ var watchdogTimeoutValueSeconds = attenuateTimeoutInSeconds(
+ timeoutValueSeconds, MINIMUM_TIMEOUT_ATTENUATION_SECONDS / 2);
+ var watchdog = new WatchdogRequestHandler(watchdogTimeoutValueSeconds,
+ timeout);
+ var wrappedErrorCb = watchdog.wrapCallback(sendErrorResponse);
+ var wrappedSuccessCb = watchdog.wrapCallback(sendSuccessResponse);
+
+ var timer = createAttenuatedTimer(
+ FACTORY_REGISTRY.getCountdownFactory(), timeoutValueSeconds);
+ var logMsgUrl = request['logMsgUrl'];
+ var enroller = new Enroller(timer, sender, wrappedErrorCb, wrappedSuccessCb,
+ logMsgUrl);
+ watchdog.setCloseable(/** @type {!Closeable} */ (enroller));
+ closeable = watchdog;
+
+ var registerRequests = request['enrollChallenges'];
+ var signRequests = getSignRequestsFromEnrollRequest(request, 'signData');
+ enroller.doEnroll(registerRequests, signRequests, request['appId']);
+
return closeable;
}
@@ -93,57 +119,51 @@ function handleU2fEnrollRequest(messageSender, request, sendResponse) {
sendResponseOnce(sentResponse, closeable, response, sendResponse);
}
+ function timeout() {
+ sendErrorResponse({errorCode: ErrorCodes.TIMEOUT});
+ }
+
var sender = createSenderFromMessageSender(messageSender);
if (!sender) {
sendErrorResponse({errorCode: ErrorCodes.BAD_REQUEST});
return null;
}
-
- var enroller =
- validateEnrollRequest(
- sender, request, 'registerRequests', 'signRequests',
- sendErrorResponse, sendSuccessResponse, 'registeredKeys');
- if (enroller) {
- var registerRequests = request['registerRequests'];
- var signRequests = getSignRequestsFromEnrollRequest(request,
- 'signRequests', 'registeredKeys');
- closeable = /** @type {Closeable} */ (enroller);
- enroller.doEnroll(registerRequests, signRequests, request['appId']);
+ if (sender.origin.indexOf('http://') == 0 && !HTTP_ORIGINS_ALLOWED) {
+ sendErrorResponse({errorCode: ErrorCodes.BAD_REQUEST});
+ return null;
}
- return closeable;
-}
-/**
- * Validates an enroll request using the given parameters.
- * @param {WebRequestSender} sender The sender of the message.
- * @param {Object} request The web page's enroll request.
- * @param {string} enrollChallengesName The name of the enroll challenges value
- * in the request.
- * @param {string} signChallengesName The name of the sign challenges value in
- * the request.
- * @param {function(U2fError)} errorCb Error callback.
- * @param {function(string, string, (string|undefined))} successCb Success
- * callback.
- * @param {string=} opt_registeredKeysName The name of the registered keys
- * value in the request.
- * @return {Enroller} Enroller object representing the request, if the request
- * is valid, or null if the request is invalid.
- */
-function validateEnrollRequest(sender, request,
- enrollChallengesName, signChallengesName, errorCb, successCb,
- opt_registeredKeysName) {
- if (!isValidEnrollRequest(request, enrollChallengesName,
- signChallengesName, opt_registeredKeysName)) {
- errorCb({errorCode: ErrorCodes.BAD_REQUEST});
+ if (!isValidEnrollRequest(request, 'registerRequests', 'signRequests',
+ 'registeredKeys')) {
+ sendErrorResponse({errorCode: ErrorCodes.BAD_REQUEST});
return null;
}
var timeoutValueSeconds = getTimeoutValueFromRequest(request);
+ // Attenuate watchdog timeout value less than the enroller's timeout, so the
+ // watchdog only fires after the enroller could reasonably have called back,
+ // not before.
+ var watchdogTimeoutValueSeconds = attenuateTimeoutInSeconds(
+ timeoutValueSeconds, MINIMUM_TIMEOUT_ATTENUATION_SECONDS / 2);
+ var watchdog = new WatchdogRequestHandler(watchdogTimeoutValueSeconds,
+ timeout);
+ var wrappedErrorCb = watchdog.wrapCallback(sendErrorResponse);
+ var wrappedSuccessCb = watchdog.wrapCallback(sendSuccessResponse);
+
var timer = createAttenuatedTimer(
FACTORY_REGISTRY.getCountdownFactory(), timeoutValueSeconds);
var logMsgUrl = request['logMsgUrl'];
- var enroller = new Enroller(timer, sender, errorCb, successCb, logMsgUrl);
- return enroller;
+ var enroller = new Enroller(timer, sender, sendErrorResponse,
+ sendSuccessResponse, logMsgUrl);
+ watchdog.setCloseable(/** @type {!Closeable} */ (enroller));
+ closeable = watchdog;
+
+ var registerRequests = request['registerRequests'];
+ var signRequests = getSignRequestsFromEnrollRequest(request,
+ 'signRequests', 'registeredKeys');
+ enroller.doEnroll(registerRequests, signRequests, request['appId']);
+
+ return closeable;
}
/**
@@ -195,7 +215,7 @@ function isValidEnrollRequest(request, enrollChallengesName,
var EnrollChallenge;
/**
- * @param {Array.<EnrollChallenge>} enrollChallenges The enroll challenges to
+ * @param {Array<EnrollChallenge>} enrollChallenges The enroll challenges to
* validate.
* @param {boolean} appIdRequired Whether the appId property is required on
* each challenge.
@@ -233,7 +253,7 @@ function isValidEnrollChallengeArray(enrollChallenges, appIdRequired) {
/**
* Finds the enroll challenge of the given version in the enroll challlenge
* array.
- * @param {Array.<EnrollChallenge>} enrollChallenges The enroll challenges to
+ * @param {Array<EnrollChallenge>} enrollChallenges The enroll challenges to
* search.
* @param {string} version Version to search for.
* @return {?EnrollChallenge} The enroll challenge with the given versions, or
@@ -287,7 +307,7 @@ function makeEnrollResponseData(enrollChallenge, u2fVersion, enrollDataName,
* the request.
* @param {string=} opt_registeredKeysName The name of the registered keys
* value in the request.
- * @return {Array.<SignChallenge>}
+ * @return {Array<SignChallenge>}
*/
function getSignRequestsFromEnrollRequest(request, signChallengesName,
opt_registeredKeysName) {
@@ -336,11 +356,11 @@ function Enroller(timer, sender, errorCb, successCb, opt_logMsgUrl) {
/** @private {boolean} */
this.done_ = false;
- /** @private {Object.<string, string>} */
+ /** @private {Object<string, string>} */
this.browserData_ = {};
- /** @private {Array.<EnrollHelperChallenge>} */
+ /** @private {Array<EnrollHelperChallenge>} */
this.encodedEnrollChallenges_ = [];
- /** @private {Array.<SignHelperChallenge>} */
+ /** @private {Array<SignHelperChallenge>} */
this.encodedSignChallenges_ = [];
// Allow http appIds for http origins. (Broken, but the caller deserves
// what they get.)
@@ -358,16 +378,16 @@ Enroller.DEFAULT_TIMEOUT_MILLIS = 30 * 1000;
/**
* Performs an enroll request with the given enroll and sign challenges.
- * @param {Array.<EnrollChallenge>} enrollChallenges A set of enroll challenges.
- * @param {Array.<SignChallenge>} signChallenges A set of sign challenges for
+ * @param {Array<EnrollChallenge>} enrollChallenges A set of enroll challenges.
+ * @param {Array<SignChallenge>} signChallenges A set of sign challenges for
* existing enrollments for this user and appId.
* @param {string=} opt_appId The app id for the entire request.
*/
Enroller.prototype.doEnroll = function(enrollChallenges, signChallenges,
opt_appId) {
- /** @private {Array.<EnrollChallenge>} */
+ /** @private {Array<EnrollChallenge>} */
this.enrollChallenges_ = enrollChallenges;
- /** @private {Array.<SignChallenge>} */
+ /** @private {Array<SignChallenge>} */
this.signChallenges_ = signChallenges;
/** @private {(string|undefined)} */
this.appId_ = opt_appId;
@@ -393,8 +413,15 @@ Enroller.prototype.approveOrigin_ = function() {
.then(function(result) {
if (self.done_) return;
if (!result) {
- // Origin not approved: fail the result.
- self.notifyError_({errorCode: ErrorCodes.BAD_REQUEST});
+ // Origin not approved: rather than give an explicit indication to
+ // the web page, let a timeout occur.
+ if (self.timer_.expired()) {
+ self.notifyTimeout_();
+ return;
+ }
+ var newTimer = self.timer_.clone(self.notifyTimeout_.bind(self));
+ self.timer_.clearTimeout();
+ self.timer_ = newTimer;
return;
}
self.sendEnrollRequestToHelper_();
@@ -402,6 +429,14 @@ Enroller.prototype.approveOrigin_ = function() {
};
/**
+ * Notifies the caller of a timeout error.
+ * @private
+ */
+Enroller.prototype.notifyTimeout_ = function() {
+ this.notifyError_({errorCode: ErrorCodes.TIMEOUT});
+};
+
+/**
* Performs an enroll request with this instance's enroll and sign challenges,
* by encoding them into a helper request and passing the resulting request to
* the factory registry's helper.
@@ -496,9 +531,9 @@ Enroller.encodeEnrollChallenge_ = function(enrollChallenge, opt_appId) {
/**
* Encodes the given enroll challenges using this enroller's state.
- * @param {Array.<EnrollChallenge>} enrollChallenges The enroll challenges.
+ * @param {Array<EnrollChallenge>} enrollChallenges The enroll challenges.
* @param {string=} opt_appId The app id for the entire request.
- * @return {!Array.<EnrollHelperChallenge>} The encoded enroll challenges.
+ * @return {!Array<EnrollHelperChallenge>} The encoded enroll challenges.
* @private
*/
Enroller.prototype.encodeEnrollChallenges_ = function(enrollChallenges,
@@ -541,7 +576,7 @@ Enroller.prototype.encodeEnrollChallenges_ = function(enrollChallenges,
/**
* Checks the app ids associated with this enroll request, and calls a callback
* with the result of the check.
- * @param {!Array.<string>} enrollAppIds The app ids in the enroll challenge
+ * @param {!Array<string>} enrollAppIds The app ids in the enroll challenge
* portion of the enroll request.
* @param {function(boolean)} cb Called with the result of the check.
* @private
@@ -558,7 +593,7 @@ Enroller.prototype.checkAppIds_ = function(enrollAppIds, cb) {
* Called with the result of checking the origin. When the origin is allowed
* to claim the app ids, begins checking whether the app ids also list the
* origin.
- * @param {!Array.<string>} appIds The app ids.
+ * @param {!Array<string>} appIds The app ids.
* @param {function(boolean)} cb Called with the result of the check.
* @param {boolean} result Whether the origin could claim the app ids.
* @private
diff --git a/chromium/chrome/browser/resources/cryptotoken/generichelper.js b/chromium/chrome/browser/resources/cryptotoken/generichelper.js
index cbd35a23b8a..29446105f2f 100644
--- a/chromium/chrome/browser/resources/cryptotoken/generichelper.js
+++ b/chromium/chrome/browser/resources/cryptotoken/generichelper.js
@@ -20,7 +20,7 @@ var RequestHandlerFactory;
* @implements {RequestHelper}
*/
function GenericRequestHelper() {
- /** @private {Object.<string, RequestHandlerFactory>} */
+ /** @private {Object<string, RequestHandlerFactory>} */
this.handlerFactories_ = {};
}
diff --git a/chromium/chrome/browser/resources/cryptotoken/gnubbies.js b/chromium/chrome/browser/resources/cryptotoken/gnubbies.js
index a1b30301361..2425cad4c3d 100644
--- a/chromium/chrome/browser/resources/cryptotoken/gnubbies.js
+++ b/chromium/chrome/browser/resources/cryptotoken/gnubbies.js
@@ -30,20 +30,20 @@ var GnubbyNamespaceImpl;
* @constructor
*/
function Gnubbies() {
- /** @private {Object.<string, Array>} */
+ /** @private {Object<string, Array>} */
this.devs_ = {};
this.pendingEnumerate = []; // clients awaiting an enumerate
/**
* The distinct namespaces registered in this Gnubbies instance, in order of
* registration.
- * @private {Array.<string>}
+ * @private {Array<string>}
*/
this.namespaces_ = [];
- /** @private {Object.<string, GnubbyNamespaceImpl>} */
+ /** @private {Object<string, GnubbyNamespaceImpl>} */
this.impl_ = {};
- /** @private {Object.<string, Object.<number, !GnubbyDevice>>} */
+ /** @private {Object<string, Object<number, !GnubbyDevice>>} */
this.openDevs_ = {};
- /** @private {Object.<string, Object.<number, *>>} */
+ /** @private {Object<string, Object<number, *>>} */
this.pendingOpens_ = {}; // clients awaiting an open
}
@@ -97,7 +97,7 @@ Gnubbies.prototype.closeAll = function() {
};
/**
- * @param {function(number, Array.<GnubbyDeviceId>)} cb Called back with the
+ * @param {function(number, Array<GnubbyDeviceId>)} cb Called back with the
* result of enumerating.
*/
Gnubbies.prototype.enumerate = function(cb) {
@@ -126,7 +126,7 @@ Gnubbies.prototype.enumerate = function(cb) {
/**
* @param {string} namespace The namespace that was enumerated.
- * @param {Array.<GnubbyDeviceId>} existingDeviceIds Previously enumerated
+ * @param {Array<GnubbyDeviceId>} existingDeviceIds Previously enumerated
* device IDs (from other namespaces), if any.
* @param {Array} devs The devices in the namespace.
*/
diff --git a/chromium/chrome/browser/resources/cryptotoken/gnubby-u2f.js b/chromium/chrome/browser/resources/cryptotoken/gnubby-u2f.js
index d5134151a9b..a5ae8b56666 100644
--- a/chromium/chrome/browser/resources/cryptotoken/gnubby-u2f.js
+++ b/chromium/chrome/browser/resources/cryptotoken/gnubby-u2f.js
@@ -35,8 +35,8 @@ Gnubby.U2F_V1 = 'U2F_V1';
Gnubby.U2F_V2 = 'U2F_V2';
/** Perform enrollment
- * @param {Array.<number>|ArrayBuffer|Uint8Array} challenge Enrollment challenge
- * @param {Array.<number>|ArrayBuffer|Uint8Array} appIdHash Hashed application
+ * @param {Array<number>|ArrayBuffer|Uint8Array} challenge Enrollment challenge
+ * @param {Array<number>|ArrayBuffer|Uint8Array} appIdHash Hashed application
* id
* @param {function(...)} cb Result callback
* @param {boolean=} opt_individualAttestation Request the individual
@@ -66,11 +66,11 @@ Gnubby.prototype.enroll = function(challenge, appIdHash, cb,
};
/** Request signature
- * @param {Array.<number>|ArrayBuffer|Uint8Array} challengeHash Hashed
+ * @param {Array<number>|ArrayBuffer|Uint8Array} challengeHash Hashed
* signature challenge
- * @param {Array.<number>|ArrayBuffer|Uint8Array} appIdHash Hashed application
+ * @param {Array<number>|ArrayBuffer|Uint8Array} appIdHash Hashed application
* id
- * @param {Array.<number>|ArrayBuffer|Uint8Array} keyHandle Key handle to use
+ * @param {Array<number>|ArrayBuffer|Uint8Array} keyHandle Key handle to use
* @param {function(...)} cb Result callback
* @param {boolean=} opt_nowink Request signature without winking
* (e.g. during enroll)
diff --git a/chromium/chrome/browser/resources/cryptotoken/gnubby.js b/chromium/chrome/browser/resources/cryptotoken/gnubby.js
index cc9bc5abea9..f3d449be8c9 100644
--- a/chromium/chrome/browser/resources/cryptotoken/gnubby.js
+++ b/chromium/chrome/browser/resources/cryptotoken/gnubby.js
@@ -25,7 +25,7 @@ function Gnubby(opt_busySeconds) {
this.closed = false;
this.commandPending = false;
this.notifyOnClose = [];
- this.busyMillis = (opt_busySeconds ? opt_busySeconds * 1000 : 2500);
+ this.busyMillis = (opt_busySeconds ? opt_busySeconds * 1000 : 9500);
}
/**
@@ -44,6 +44,19 @@ Gnubby.setGnubbies = function(gnubbies) {
};
/**
+ * Return cid as hex string.
+ * @param {number} cid to convert.
+ * @return {string} hexadecimal string.
+ */
+Gnubby.hexCid = function(cid) {
+ var tmp = [(cid >>> 24) & 255,
+ (cid >>> 16) & 255,
+ (cid >>> 8) & 255,
+ (cid >>> 0) & 255];
+ return UTIL_BytesToHex(tmp);
+};
+
+/**
* Opens the gnubby with the given index, or the first found gnubby if no
* index is specified.
* @param {GnubbyDeviceId} which The device to open. If null, the first
@@ -262,7 +275,7 @@ Gnubby.prototype.read_ = function(cmd, timeout, cb) {
if (!callback || !tid) return; // Already done.
console.error(UTIL_fmt(
- '[' + self.cid.toString(16) + '] timeout!'));
+ '[' + Gnubby.hexCid(self.cid) + '] timeout!'));
if (self.dev) {
self.dev.destroy(); // Stop pretending this thing works.
@@ -283,7 +296,7 @@ Gnubby.prototype.read_ = function(cmd, timeout, cb) {
if (rcmd == GnubbyDevice.CMD_ERROR && totalLen == 1) {
// Error from device; forward.
console.log(UTIL_fmt(
- '[' + self.cid.toString(16) + '] error frame ' +
+ '[' + Gnubby.hexCid(self.cid) + '] error frame ' +
UTIL_BytesToHex(f)));
if (f[7] == GnubbyDevice.GONE) {
self.closed = true;
@@ -295,7 +308,7 @@ Gnubby.prototype.read_ = function(cmd, timeout, cb) {
if ((rcmd & 0x80)) {
// Not an CONT frame, ignore.
console.log(UTIL_fmt(
- '[' + self.cid.toString(16) + '] ignoring non-cont frame ' +
+ '[' + Gnubby.hexCid(self.cid) + '] ignoring non-cont frame ' +
UTIL_BytesToHex(f)));
self.notifyFrame_(cont_frame);
return;
@@ -304,7 +317,7 @@ Gnubby.prototype.read_ = function(cmd, timeout, cb) {
var seq = (rcmd & 0x7f);
if (seq != seqno++) {
console.log(UTIL_fmt(
- '[' + self.cid.toString(16) + '] bad cont frame ' +
+ '[' + Gnubby.hexCid(self.cid) + '] bad cont frame ' +
UTIL_BytesToHex(f)));
schedule_cb(-GnubbyDevice.INVALID_SEQ);
return;
@@ -337,7 +350,7 @@ Gnubby.prototype.read_ = function(cmd, timeout, cb) {
// Don't log busy frames, they're "normal".
if (f[7] != GnubbyDevice.BUSY) {
console.log(UTIL_fmt(
- '[' + self.cid.toString(16) + '] error frame ' +
+ '[' + Gnubby.hexCid(self.cid) + '] error frame ' +
UTIL_BytesToHex(f)));
}
if (f[7] == GnubbyDevice.GONE) {
@@ -350,7 +363,7 @@ Gnubby.prototype.read_ = function(cmd, timeout, cb) {
if (!(rcmd & 0x80)) {
// Not an init frame, ignore.
console.log(UTIL_fmt(
- '[' + self.cid.toString(16) + '] ignoring non-init frame ' +
+ '[' + Gnubby.hexCid(self.cid) + '] ignoring non-init frame ' +
UTIL_BytesToHex(f)));
self.notifyFrame_(init_frame);
return;
@@ -359,7 +372,7 @@ Gnubby.prototype.read_ = function(cmd, timeout, cb) {
if (rcmd != cmd) {
// Not expected ack, read more.
console.log(UTIL_fmt(
- '[' + self.cid.toString(16) + '] ignoring non-ack frame ' +
+ '[' + Gnubby.hexCid(self.cid) + '] ignoring non-ack frame ' +
UTIL_BytesToHex(f)));
self.notifyFrame_(init_frame);
return;
@@ -409,8 +422,7 @@ Gnubby.prototype.checkCID_ = function(frame) {
(f[2] << 8) |
(f[3]);
return c === this.cid ||
- c === Gnubby.NOTIFICATION_CID ||
- c === Gnubby.BROADCAST_CID;
+ c === Gnubby.NOTIFICATION_CID;
};
/**
@@ -458,7 +470,7 @@ Gnubby.prototype.exchange_ = function(cmd, data, timeout, cb) {
/** Default callback for commands. Simply logs to console.
* @param {number} rc Result status code
- * @param {(ArrayBuffer|Uint8Array|Array.<number>|null)} data Result data
+ * @param {(ArrayBuffer|Uint8Array|Array<number>|null)} data Result data
*/
Gnubby.defaultCallback = function(rc, data) {
var msg = 'defaultCallback(' + rc;
@@ -491,9 +503,7 @@ Gnubby.prototype.sync = function(cb) {
function returnValue(rc) {
done = true;
- // Wait a bit to cater to devices being slow coming out of suspend.
- // TODO: remove when usb fw gets fixed.
- window.setTimeout(cb.bind(null, rc), 200);
+ window.setTimeout(cb.bind(null, rc), 0);
if (self.closingWhenIdle) self.idleClose_();
}
@@ -526,8 +536,10 @@ Gnubby.prototype.sync = function(cb) {
function sendInitSentinel() {
var cid = self.cid;
- if (cid == Gnubby.defaultChannelId_(self.gnubbyInstance, self.which)) {
- cid = Gnubby.BROADCAST_CID;
+ // If we do not have a specific CID yet, reset to BROADCAST for init.
+ if (self.cid == Gnubby.defaultChannelId_(self.gnubbyInstance, self.which)) {
+ self.cid = Gnubby.BROADCAST_CID;
+ cid = self.cid;
}
var cmd = GnubbyDevice.CMD_INIT;
self.dev.queueCommand(cid, cmd, nonce);
@@ -560,6 +572,7 @@ Gnubby.prototype.sync = function(cb) {
// INIT command not supported or is missing the returned channel id:
// Pick a random cid to try to prevent collisions on the USB bus.
var rnd = UTIL_getRandom(2);
+ self.cid = Gnubby.defaultChannelId_(self.gnubbyInstance, self.which);
self.cid ^= (rnd[0] << 16) | (rnd[1] << 8);
// Now sync with that cid, to make sure we've got it.
setSync();
@@ -581,6 +594,12 @@ Gnubby.prototype.sync = function(cb) {
// Stop on errors and return them.
if (f[4] == GnubbyDevice.CMD_ERROR &&
f[5] == 0 && f[6] == 1) {
+ if (f[7] == GnubbyDevice.BUSY) {
+ // Not spec but some devices do this; retry.
+ sendSentinel();
+ self.notifyFrame_(checkSentinel);
+ return;
+ }
if (f[7] == GnubbyDevice.GONE) {
// Device disappeared on us.
self.closed = true;
diff --git a/chromium/chrome/browser/resources/cryptotoken/gnubbycodetypes.js b/chromium/chrome/browser/resources/cryptotoken/gnubbycodetypes.js
index d6eda06615e..94b8ab5283b 100644
--- a/chromium/chrome/browser/resources/cryptotoken/gnubbycodetypes.js
+++ b/chromium/chrome/browser/resources/cryptotoken/gnubbycodetypes.js
@@ -29,5 +29,4 @@ var GnubbyCodeTypes = {
/** Bad request. */
'BAD_REQUEST': 12
-
};
diff --git a/chromium/chrome/browser/resources/cryptotoken/gnubbydevice.js b/chromium/chrome/browser/resources/cryptotoken/gnubbydevice.js
index a350ded3273..77c3eb58f05 100644
--- a/chromium/chrome/browser/resources/cryptotoken/gnubbydevice.js
+++ b/chromium/chrome/browser/resources/cryptotoken/gnubbydevice.js
@@ -118,31 +118,3 @@ GnubbyDevice.prototype.queueCommand = function(cid, cmd, data) {};
* }}
*/
var UsbDeviceSpec;
-
-/**
- * Gets the list of USB devices permitted by this app.
- * @param {function(!Array.<!UsbDeviceSpec>)} cb Called back with a list of USB
- * device specifiers.
- */
-GnubbyDevice.getPermittedUsbDevices = function(cb) {
- chrome.permissions.getAll(function(perms) {
- if (!perms.hasOwnProperty('permissions')) {
- cb([]);
- return;
- }
- var devs = [];
- var permissions = perms['permissions'];
- for (var i = 0; i < permissions.length; i++) {
- var permission = permissions[i];
- if (typeof permission === 'object' &&
- permission.hasOwnProperty('usbDevices')) {
- for (var j = 0; j < permission['usbDevices'].length; j++) {
- var dev = permission['usbDevices'][j];
- devs.push(
- {'vendorId': dev['vendorId'], 'productId': dev['productId']});
- }
- }
- }
- cb(devs);
- });
-};
diff --git a/chromium/chrome/browser/resources/cryptotoken/gnubbyfactory.js b/chromium/chrome/browser/resources/cryptotoken/gnubbyfactory.js
index 494602b9a23..ffe386cac14 100644
--- a/chromium/chrome/browser/resources/cryptotoken/gnubbyfactory.js
+++ b/chromium/chrome/browser/resources/cryptotoken/gnubbyfactory.js
@@ -15,7 +15,7 @@ function GnubbyFactory() {}
/**
* Enumerates gnubbies.
- * @param {function(number, Array.<GnubbyDeviceId>)} cb Enumerate callback
+ * @param {function(number, Array<GnubbyDeviceId>)} cb Enumerate callback
*/
GnubbyFactory.prototype.enumerate = function(cb) {
};
@@ -28,9 +28,12 @@ var FactoryOpenCallback;
* @param {GnubbyDeviceId} which The device to open.
* @param {boolean} forEnroll Whether this gnubby is being opened for enrolling.
* @param {FactoryOpenCallback} cb Called with result of opening the gnubby.
- * @param {string=} logMsgUrl the url to post log messages to
+ * @param {string=} opt_appIdHash The base64-encoded hash of the app id for
+ * which the gnubby being opened.
+ * @param {string=} opt_logMsgUrl The url to post log messages to.
*/
-GnubbyFactory.prototype.openGnubby = function(which, forEnroll, cb, logMsgUrl) {
+GnubbyFactory.prototype.openGnubby =
+ function(which, forEnroll, cb, opt_appIdHash, opt_logMsgUrl) {
};
/**
diff --git a/chromium/chrome/browser/resources/cryptotoken/googleapprovedorigins.js b/chromium/chrome/browser/resources/cryptotoken/googleapprovedorigins.js
deleted file mode 100644
index 9b345d5fc6a..00000000000
--- a/chromium/chrome/browser/resources/cryptotoken/googleapprovedorigins.js
+++ /dev/null
@@ -1,32 +0,0 @@
-// 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 Provides an implementation of approved origins that always
- * (and only) allows google.com to use security keys.
- *
- */
-'use strict';
-
-/**
- * Allows the caller to check whether the user has approved the use of
- * security keys from an origin.
- * @constructor
- * @implements {ApprovedOrigins}
- */
-function GoogleApprovedOrigins() {}
-
-/**
- * Checks whether the origin is approved to use security keys. (If not, an
- * approval prompt may be shown.)
- * @param {string} origin The origin to approve.
- * @param {number=} opt_tabId A tab id to display approval prompt in, if
- * necessary.
- * @return {Promise.<boolean>} A promise for the result of the check.
- */
-GoogleApprovedOrigins.prototype.isApprovedOrigin = function(origin, opt_tabId) {
- var anchor = document.createElement('a');
- anchor.href = origin;
- return Promise.resolve(/google.com$/.test(anchor.hostname));
-};
diff --git a/chromium/chrome/browser/resources/cryptotoken/gstaticorigincheck.js b/chromium/chrome/browser/resources/cryptotoken/gstaticorigincheck.js
deleted file mode 100644
index 8ea48b55fdf..00000000000
--- a/chromium/chrome/browser/resources/cryptotoken/gstaticorigincheck.js
+++ /dev/null
@@ -1,51 +0,0 @@
-// 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 Implements a check whether an origin is allowed to assert an
- * app id based on a fixed set of allowed app ids for the google.com domain.
- *
- */
-'use strict';
-
-/**
- * Implements half of the app id policy: whether an origin is allowed to claim
- * an app id. For checking whether the app id also lists the origin,
- * @see AppIdChecker.
- * @implements OriginChecker
- * @constructor
- */
-function GstaticOriginChecker() {
-}
-
-/**
- * Checks whether the origin is allowed to claim the app ids.
- * @param {string} origin The origin claiming the app id.
- * @param {!Array.<string>} appIds The app ids being claimed.
- * @return {Promise.<boolean>} A promise for the result of the check.
- */
-GstaticOriginChecker.prototype.canClaimAppIds = function(origin, appIds) {
- return Promise.resolve(appIds.every(this.checkAppId_.bind(this, origin)));
-};
-
-/**
- * Checks if a single appId can be asserted by the given origin.
- * @param {string} origin The origin.
- * @param {string} appId The appId to check.
- * @return {boolean} Whether the given origin can assert the app id.
- * @private
- */
-GstaticOriginChecker.prototype.checkAppId_ = function(origin, appId) {
- if (appId == origin) {
- // Trivially allowed
- return true;
- }
- var anchor = document.createElement('a');
- anchor.href = origin;
- if (/google.com$/.test(anchor.hostname)) {
- return (appId.indexOf('https://www.gstatic.com') == 0 ||
- appId.indexOf('https://static.corp.google.com') == 0);
- }
- return false;
-};
diff --git a/chromium/chrome/browser/resources/cryptotoken/hidgnubbydevice.js b/chromium/chrome/browser/resources/cryptotoken/hidgnubbydevice.js
index 9c59323b660..331402363d9 100644
--- a/chromium/chrome/browser/resources/cryptotoken/hidgnubbydevice.js
+++ b/chromium/chrome/browser/resources/cryptotoken/hidgnubbydevice.js
@@ -99,7 +99,7 @@ HidGnubbyDevice.prototype.publishFrame_ = function(f) {
} else {
changes = true;
console.log(UTIL_fmt(
- '[' + client.cid.toString(16) + '] left?'));
+ '[' + Gnubby.hexCid(client.cid) + '] left?'));
}
}
if (changes) this.clients = remaining;
@@ -277,7 +277,7 @@ HidGnubbyDevice.prototype.updateLock_ = function(cid, cmd, arg) {
this.lockTID = window.setTimeout(
function() {
console.warn(UTIL_fmt(
- 'lock for CID ' + cid.toString(16) + ' expired!'));
+ 'lock for CID ' + Gnubby.hexCid(cid) + ' expired!'));
self.lockTID = null;
self.lockCID = 0;
},
@@ -405,31 +405,55 @@ HidGnubbyDevice.prototype.writePump_ = function() {
};
/**
+ * List of legacy HID devices that do not support the F1D0 usage page as
+ * mandated by the spec, but still need to be supported.
+ * TODO: remove when these devices no longer need to be supported.
+ * @const
+ */
+HidGnubbyDevice.HID_VID_PIDS = [
+ {'vendorId': 4176, 'productId': 512} // Google-specific Yubico HID
+];
+
+/**
* @param {function(Array)} cb Enumeration callback
*/
HidGnubbyDevice.enumerate = function(cb) {
- var permittedDevs;
+ /**
+ * One pass using getDevices, and one for each of the hardcoded vid/pids.
+ * @const
+ */
+ var ENUMERATE_PASSES = 1 + HidGnubbyDevice.HID_VID_PIDS.length;
var numEnumerated = 0;
var allDevs = [];
function enumerated(devs) {
- allDevs = allDevs.concat(devs);
- if (++numEnumerated == permittedDevs.length) {
+ // Don't double-add a device, it'll just confuse things.
+ for (var i = 0; i < devs.length; i++) {
+ var dev = devs[i];
+ // Unfortunately indexOf is not usable, since the two calls produce
+ // different objects. Compare their deviceIds instead.
+ var found = false;
+ for (var j = 0; j < allDevs.length; j++) {
+ if (allDevs[j].deviceId == dev.deviceId) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ allDevs.push(dev);
+ }
+ }
+ if (++numEnumerated == ENUMERATE_PASSES) {
cb(allDevs);
}
}
- try {
- chrome.hid.getDevices({filters: [{usagePage: 0xf1d0}]}, cb);
- } catch (e) {
- console.log(e);
- console.log(UTIL_fmt('falling back to vid/pid enumeration'));
- GnubbyDevice.getPermittedUsbDevices(function(devs) {
- permittedDevs = devs;
- for (var i = 0; i < devs.length; i++) {
- chrome.hid.getDevices(devs[i], enumerated);
- }
- });
+ // Pass 1: usagePage-based enumeration.
+ chrome.hid.getDevices({filters: [{usagePage: 0xf1d0}]}, enumerated);
+ // Pass 2: vid/pid-based enumeration, for legacy devices.
+ for (var i = 0; i < HidGnubbyDevice.HID_VID_PIDS.length; i++) {
+ var dev = HidGnubbyDevice.HID_VID_PIDS[i];
+ chrome.hid.getDevices({filters: [dev]}, enumerated);
}
};
diff --git a/chromium/chrome/browser/resources/cryptotoken/manifest.json b/chromium/chrome/browser/resources/cryptotoken/manifest.json
index ac7386203aa..194100bdb5b 100644
--- a/chromium/chrome/browser/resources/cryptotoken/manifest.json
+++ b/chromium/chrome/browser/resources/cryptotoken/manifest.json
@@ -1,14 +1,18 @@
{
"name": "CryptoTokenExtension",
"description": "CryptoToken Component Extension",
- "version": "0.9.6",
+ "version": "0.9.22",
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq7zRobvA+AVlvNqkHSSVhh1sEWsHSqz4oR/XptkDe/Cz3+gW9ZGumZ20NCHjaac8j1iiesdigp8B1LJsd/2WWv2Dbnto4f8GrQ5MVphKyQ9WJHwejEHN2K4vzrTcwaXqv5BSTXwxlxS/mXCmXskTfryKTLuYrcHEWK8fCHb+0gvr8b/kvsi75A1aMmb6nUnFJvETmCkOCPNX5CHTdy634Ts/x0fLhRuPlahk63rdf7agxQv5viVjQFk+tbgv6aa9kdSd11Js/RZ9yZjrFgHOBWgP4jTBqud4+HUglrzu8qynFipyNRLCZsaxhm+NItTyNgesxLdxZcwOz56KD1Q4IQIDAQAB",
"manifest_version": 2,
"permissions": [
"hid",
- "usb",
"u2fDevices",
- "https://www.gstatic.com/",
+ "usb",
+ "cryptotokenPrivate",
+ "externally_connectable.all_urls",
+ "tabs",
+ "https://*/*",
+ "http://*/*",
{
"usbDevices": [
{
@@ -20,10 +24,7 @@
],
"externally_connectable": {
"matches": [
- "https://login.corp.google.com/*",
- "https://accounts.google.com/*",
- "https://myaccount.google.com/*",
- "https://security.google.com/*"
+ "<all_urls>"
],
"ids": [
"fjajfjhkeibgmiggdfehjplbhmfkialk"
@@ -56,8 +57,8 @@
"textfetcher.js",
"appid.js",
"watchdog.js",
- "gstaticorigincheck.js",
- "googleapprovedorigins.js",
+ "cryptotokenorigincheck.js",
+ "cryptotokenapprovedorigins.js",
"gnubbydevice.js",
"hidgnubbydevice.js",
"usbgnubbydevice.js",
@@ -78,5 +79,6 @@
"googlecorpindividualattest.js",
"cryptotokenbackground.js"
]
- }
+ },
+ "incognito": "split"
}
diff --git a/chromium/chrome/browser/resources/cryptotoken/multiplesigner.js b/chromium/chrome/browser/resources/cryptotoken/multiplesigner.js
index 44bd7979082..1c4ffc29b18 100644
--- a/chromium/chrome/browser/resources/cryptotoken/multiplesigner.js
+++ b/chromium/chrome/browser/resources/cryptotoken/multiplesigner.js
@@ -52,7 +52,7 @@ function MultipleGnubbySigner(forEnroll, allCompleteCb, gnubbyCompleteCb,
/** @private {string|undefined} */
this.logMsgUrl_ = opt_logMsgUrl;
- /** @private {Array.<SignHelperChallenge>} */
+ /** @private {Array<SignHelperChallenge>} */
this.challenges_ = [];
/** @private {boolean} */
this.challengesSet_ = false;
@@ -60,7 +60,7 @@ function MultipleGnubbySigner(forEnroll, allCompleteCb, gnubbyCompleteCb,
this.complete_ = false;
/** @private {number} */
this.numComplete_ = 0;
- /** @private {!Object.<string, GnubbyTracker>} */
+ /** @private {!Object<string, GnubbyTracker>} */
this.gnubbies_ = {};
/** @private {Countdown} */
this.timer_ = DEVICE_FACTORY_REGISTRY.getCountdownFactory()
@@ -96,7 +96,7 @@ MultipleGnubbySigner.prototype.close = function() {
/**
* Begins signing the given challenges.
- * @param {Array.<SignHelperChallenge>} challenges The challenges to sign.
+ * @param {Array<SignHelperChallenge>} challenges The challenges to sign.
* @return {boolean} whether the challenges were successfully added.
*/
MultipleGnubbySigner.prototype.doSign = function(challenges) {
@@ -148,7 +148,7 @@ MultipleGnubbySigner.prototype.enumerateGnubbies_ = function() {
/**
* Called with the result of enumerating gnubbies.
* @param {number} rc The return code from enumerating.
- * @param {Array.<GnubbyDeviceId>} ids The gnubbies enumerated.
+ * @param {Array<GnubbyDeviceId>} ids The gnubbies enumerated.
* @private
*/
MultipleGnubbySigner.prototype.enumerateCallback_ = function(rc, ids) {
diff --git a/chromium/chrome/browser/resources/cryptotoken/origincheck.js b/chromium/chrome/browser/resources/cryptotoken/origincheck.js
index ddab33e5589..97077e08a8a 100644
--- a/chromium/chrome/browser/resources/cryptotoken/origincheck.js
+++ b/chromium/chrome/browser/resources/cryptotoken/origincheck.js
@@ -20,7 +20,7 @@ function OriginChecker() {}
/**
* Checks whether the origin is allowed to claim the app ids.
* @param {string} origin The origin claiming the app id.
- * @param {!Array.<string>} appIds The app ids being claimed.
- * @return {Promise.<boolean>} A promise for the result of the check.
+ * @param {!Array<string>} appIds The app ids being claimed.
+ * @return {Promise<boolean>} A promise for the result of the check.
*/
OriginChecker.prototype.canClaimAppIds = function(origin, appIds) {};
diff --git a/chromium/chrome/browser/resources/cryptotoken/requestqueue.js b/chromium/chrome/browser/resources/cryptotoken/requestqueue.js
index 9c523eaf7f3..a377f4c4270 100644
--- a/chromium/chrome/browser/resources/cryptotoken/requestqueue.js
+++ b/chromium/chrome/browser/resources/cryptotoken/requestqueue.js
@@ -163,7 +163,7 @@ RequestQueue.prototype.queueRequest = function(beginCb, timer) {
* @constructor
*/
function OriginKeyedRequestQueue() {
- /** @private {Object.<string, !RequestQueue>} */
+ /** @private {Object<string, !RequestQueue>} */
this.requests_ = {};
}
diff --git a/chromium/chrome/browser/resources/cryptotoken/sha256.js b/chromium/chrome/browser/resources/cryptotoken/sha256.js
index 4d3c93eda6d..63b62fb11bb 100644
--- a/chromium/chrome/browser/resources/cryptotoken/sha256.js
+++ b/chromium/chrome/browser/resources/cryptotoken/sha256.js
@@ -51,7 +51,7 @@ SHA256.prototype.reset = function() {
};
/** Hash the next block of 64 bytes
- * @param {Array.<number>} buf A 64 byte buffer
+ * @param {Array<number>} buf A 64 byte buffer
*/
SHA256.prototype._compress = function(buf) {
var W = this._W;
@@ -113,7 +113,7 @@ SHA256.prototype._compress = function(buf) {
};
/** Update the hash with additional data
- * @param {Array.<number>|Uint8Array} bytes The data
+ * @param {Array<number>|Uint8Array} bytes The data
* @param {number=} opt_length How many bytes to hash, if not all */
SHA256.prototype.update = function(bytes, opt_length) {
if (!opt_length) opt_length = bytes.length;
@@ -129,7 +129,7 @@ SHA256.prototype.update = function(bytes, opt_length) {
};
/** Update the hash with a specified range from a data buffer
- * @param {Array.<number>} bytes The data buffer
+ * @param {Array<number>} bytes The data buffer
* @param {number} start Starting index of the range in bytes
* @param {number} end End index, will not be included in range
*/
@@ -148,7 +148,7 @@ SHA256.prototype.updateRange = function(bytes, start, end) {
* Optionally update the hash with additional arguments, and return the
* resulting hash value.
* @param {...*} var_args Data buffers to hash
- * @return {Array.<number>} the SHA256 hash value.
+ * @return {Array<number>} the SHA256 hash value.
*/
SHA256.prototype.digest = function(var_args) {
for (var i = 0; i < arguments.length; ++i)
diff --git a/chromium/chrome/browser/resources/cryptotoken/signer.js b/chromium/chrome/browser/resources/cryptotoken/signer.js
index f0af2a17ed4..df91d3944d5 100644
--- a/chromium/chrome/browser/resources/cryptotoken/signer.js
+++ b/chromium/chrome/browser/resources/cryptotoken/signer.js
@@ -43,6 +43,10 @@ function handleWebSignRequest(messageSender, request, sendResponse) {
sendErrorResponse({errorCode: ErrorCodes.BAD_REQUEST});
return null;
}
+ if (sender.origin.indexOf('http://') == 0 && !HTTP_ORIGINS_ALLOWED) {
+ sendErrorResponse({errorCode: ErrorCodes.BAD_REQUEST});
+ return null;
+ }
queuedSignRequest =
validateAndEnqueueSignRequest(
@@ -82,6 +86,10 @@ function handleU2fSignRequest(messageSender, request, sendResponse) {
sendErrorResponse({errorCode: ErrorCodes.BAD_REQUEST});
return null;
}
+ if (sender.origin.indexOf('http://') == 0 && !HTTP_ORIGINS_ALLOWED) {
+ sendErrorResponse({errorCode: ErrorCodes.BAD_REQUEST});
+ return null;
+ }
queuedSignRequest =
validateAndEnqueueSignRequest(
@@ -215,7 +223,7 @@ function isValidSignRequest(request, signChallengesName) {
/**
* Adapter class representing a queued sign request.
- * @param {!Array.<SignChallenge>} signChallenges The sign challenges.
+ * @param {!Array<SignChallenge>} signChallenges The sign challenges.
* @param {Countdown} timer Timeout timer
* @param {WebRequestSender} sender Message sender.
* @param {function(U2fError)} errorCb Error callback
@@ -229,7 +237,7 @@ function isValidSignRequest(request, signChallengesName) {
*/
function QueuedSignRequest(signChallenges, timer, sender, errorCb,
successCb, opt_defaultChallenge, opt_appId, opt_logMsgUrl) {
- /** @private {!Array.<SignChallenge>} */
+ /** @private {!Array<SignChallenge>} */
this.signChallenges_ = signChallenges;
/** @private {Countdown} */
this.timer_ = timer.clone(this.close.bind(this));
@@ -353,9 +361,9 @@ function Signer(timer, sender, errorCb, successCb, opt_logMsgUrl) {
/** @private {boolean} */
this.done_ = false;
- /** @private {Object.<string, string>} */
+ /** @private {Object<string, string>} */
this.browserData_ = {};
- /** @private {Object.<string, SignChallenge>} */
+ /** @private {Object<string, SignChallenge>} */
this.serverChallenges_ = {};
// Allow http appIds for http origins. (Broken, but the caller deserves
// what they get.)
@@ -368,7 +376,7 @@ function Signer(timer, sender, errorCb, successCb, opt_logMsgUrl) {
/**
* Sets the challenges to be signed.
- * @param {Array.<SignChallenge>} signChallenges The challenges to set.
+ * @param {Array<SignChallenge>} signChallenges The challenges to set.
* @param {string=} opt_defaultChallenge A default sign challenge
* value, if a request does not provide one.
* @param {string=} opt_appId The app id for the entire request.
@@ -382,7 +390,7 @@ Signer.prototype.setChallenges = function(signChallenges, opt_defaultChallenge,
this.notifyError_({errorCode: ErrorCodes.TIMEOUT});
return true;
}
- /** @private {Array.<SignChallenge>} */
+ /** @private {Array<SignChallenge>} */
this.signChallenges_ = signChallenges;
/** @private {string|undefined} */
this.defaultChallenge_ = opt_defaultChallenge;
@@ -421,7 +429,7 @@ Signer.prototype.checkAppIds_ = function() {
* Called with the result of checking the origin. When the origin is allowed
* to claim the app ids, begins checking whether the app ids also list the
* origin.
- * @param {!Array.<string>} appIds The app ids.
+ * @param {!Array<string>} appIds The app ids.
* @param {boolean} result Whether the origin could claim the app ids.
* @private
*/
@@ -437,7 +445,7 @@ Signer.prototype.originChecked_ = function(appIds, result) {
/** @private {!AppIdChecker} */
this.appIdChecker_ = new AppIdChecker(FACTORY_REGISTRY.getTextFetcher(),
this.timer_.clone(), this.sender_.origin,
- /** @type {!Array.<string>} */ (appIds), this.allowHttp_,
+ /** @type {!Array<string>} */ (appIds), this.allowHttp_,
this.logMsgUrl_);
this.appIdChecker_.doCheck().then(this.appIdChecked_.bind(this));
};
diff --git a/chromium/chrome/browser/resources/cryptotoken/singlesigner.js b/chromium/chrome/browser/resources/cryptotoken/singlesigner.js
index d2f2333a813..445f96d698e 100644
--- a/chromium/chrome/browser/resources/cryptotoken/singlesigner.js
+++ b/chromium/chrome/browser/resources/cryptotoken/singlesigner.js
@@ -65,14 +65,14 @@ function SingleGnubbySigner(gnubbyId, forEnroll, completeCb, timer,
/** @private {string|undefined} */
this.logMsgUrl_ = opt_logMsgUrl;
- /** @private {!Array.<!SignHelperChallenge>} */
+ /** @private {!Array<!SignHelperChallenge>} */
this.challenges_ = [];
/** @private {number} */
this.challengeIndex_ = 0;
/** @private {boolean} */
this.challengesSet_ = false;
- /** @private {!Object.<string, number>} */
+ /** @private {!Object<string, number>} */
this.cachedError_ = [];
}
@@ -104,21 +104,6 @@ SingleGnubbySigner.prototype.getDeviceId = function() {
};
/**
- * Attempts to open this signer's gnubby, if it's not already open.
- * (This is implicitly done by addChallenges.)
- */
-SingleGnubbySigner.prototype.open = function() {
- if (this.state_ == SingleGnubbySigner.State.INIT) {
- this.state_ = SingleGnubbySigner.State.OPENING;
- DEVICE_FACTORY_REGISTRY.getGnubbyFactory().openGnubby(
- this.gnubbyId_,
- this.forEnroll_,
- this.openCallback_.bind(this),
- this.logMsgUrl_);
- }
-};
-
-/**
* Closes this signer's gnubby, if it's held.
*/
SingleGnubbySigner.prototype.close = function() {
@@ -138,7 +123,7 @@ SingleGnubbySigner.prototype.closed_ = function() {
/**
* Begins signing the given challenges.
- * @param {Array.<SignHelperChallenge>} challenges The challenges to sign.
+ * @param {Array<SignHelperChallenge>} challenges The challenges to sign.
* @return {boolean} Whether the challenges were accepted.
*/
SingleGnubbySigner.prototype.doSign = function(challenges) {
@@ -158,7 +143,7 @@ SingleGnubbySigner.prototype.doSign = function(challenges) {
switch (this.state_) {
case SingleGnubbySigner.State.INIT:
- this.open();
+ this.open_();
break;
case SingleGnubbySigner.State.OPENING:
// The open has already commenced, so accept the challenges, but don't do
@@ -189,6 +174,27 @@ SingleGnubbySigner.prototype.doSign = function(challenges) {
};
/**
+ * Attempts to open this signer's gnubby, if it's not already open.
+ * @private
+ */
+SingleGnubbySigner.prototype.open_ = function() {
+ var appIdHash;
+ if (this.challenges_.length) {
+ // Assume the first challenge's appId is representative of all of them.
+ appIdHash = B64_encode(this.challenges_[0].appIdHash);
+ }
+ if (this.state_ == SingleGnubbySigner.State.INIT) {
+ this.state_ = SingleGnubbySigner.State.OPENING;
+ DEVICE_FACTORY_REGISTRY.getGnubbyFactory().openGnubby(
+ this.gnubbyId_,
+ this.forEnroll_,
+ this.openCallback_.bind(this),
+ appIdHash,
+ this.logMsgUrl_);
+ }
+};
+
+/**
* How long to delay retrying a failed open.
*/
SingleGnubbySigner.OPEN_DELAY_MILLIS = 200;
@@ -253,6 +259,22 @@ SingleGnubbySigner.prototype.openCallback_ = function(rc, gnubby) {
* @private
*/
SingleGnubbySigner.prototype.versionCallback_ = function(rc, opt_data) {
+ if (rc == DeviceStatusCodes.BUSY_STATUS) {
+ if (this.timer_ && this.timer_.expired()) {
+ this.goToError_(DeviceStatusCodes.TIMEOUT_STATUS);
+ return;
+ }
+ // There's still time: resync and retry.
+ var self = this;
+ this.gnubby_.sync(function(code) {
+ if (code) {
+ self.goToError_(code, true);
+ return;
+ }
+ self.gnubby_.version(self.versionCallback_.bind(self));
+ });
+ return;
+ }
if (rc) {
this.goToError_(rc, true);
return;
@@ -310,6 +332,17 @@ SingleGnubbySigner.prototype.doSign_ = function(challengeIndex) {
};
/**
+ * @param {number} code The result of a sign operation.
+ * @return {boolean} Whether the error indicates the key handle is invalid
+ * for this gnubby.
+ */
+SingleGnubbySigner.signErrorIndicatesInvalidKeyHandle = function(code) {
+ return (code == DeviceStatusCodes.WRONG_DATA_STATUS ||
+ code == DeviceStatusCodes.WRONG_LENGTH_STATUS ||
+ code == DeviceStatusCodes.INVALID_DATA_STATUS);
+};
+
+/**
* Called with the result of a single sign operation.
* @param {number} challengeIndex the index of the challenge just attempted
* @param {number} code the result of the sign operation
@@ -326,10 +359,9 @@ SingleGnubbySigner.prototype.signCallback_ =
return;
}
- // Cache wrong data or wrong length results, re-asking the gnubby to sign it
+ // Cache certain idempotent errors, re-asking the gnubby to sign it
// won't produce different results.
- if (code == DeviceStatusCodes.WRONG_DATA_STATUS ||
- code == DeviceStatusCodes.WRONG_LENGTH_STATUS) {
+ if (SingleGnubbySigner.signErrorIndicatesInvalidKeyHandle(code)) {
if (challengeIndex < this.challenges_.length) {
var challenge = this.challenges_[challengeIndex];
if (!this.cachedError_.hasOwnProperty(challenge.keyHandle)) {
@@ -374,6 +406,7 @@ SingleGnubbySigner.prototype.signCallback_ =
case DeviceStatusCodes.WRONG_DATA_STATUS:
case DeviceStatusCodes.WRONG_LENGTH_STATUS:
+ case DeviceStatusCodes.INVALID_DATA_STATUS:
if (this.challengeIndex_ < this.challenges_.length - 1) {
this.doSign_(++this.challengeIndex_);
} else if (this.forEnroll_) {
diff --git a/chromium/chrome/browser/resources/cryptotoken/textfetcher.js b/chromium/chrome/browser/resources/cryptotoken/textfetcher.js
index 4135b0e12cb..d6e18844acb 100644
--- a/chromium/chrome/browser/resources/cryptotoken/textfetcher.js
+++ b/chromium/chrome/browser/resources/cryptotoken/textfetcher.js
@@ -19,7 +19,7 @@ function TextFetcher() {}
* @param {string} url The URL to fetch.
* @param {string?} opt_method The HTTP method to use (default GET)
* @param {string?} opt_body The request body
- * @return {!Promise.<string>} A promise for the fetched text. In case of an
+ * @return {!Promise<string>} A promise for the fetched text. In case of an
* error, this promise is rejected with an HTTP status code.
*/
TextFetcher.prototype.fetch = function(url, opt_method, opt_body) {};
@@ -35,7 +35,7 @@ function XhrTextFetcher() {
* @param {string} url The URL to fetch.
* @param {string?} opt_method The HTTP method to use (default GET)
* @param {string?} opt_body The request body
- * @return {!Promise.<string>} A promise for the fetched text. In case of an
+ * @return {!Promise<string>} A promise for the fetched text. In case of an
* error, this promise is rejected with an HTTP status code.
*/
XhrTextFetcher.prototype.fetch = function(url, opt_method, opt_body) {
diff --git a/chromium/chrome/browser/resources/cryptotoken/usbenrollhandler.js b/chromium/chrome/browser/resources/cryptotoken/usbenrollhandler.js
index 55f5d06586c..cf113d2ae22 100644
--- a/chromium/chrome/browser/resources/cryptotoken/usbenrollhandler.js
+++ b/chromium/chrome/browser/resources/cryptotoken/usbenrollhandler.js
@@ -16,7 +16,7 @@ function UsbEnrollHandler(request) {
/** @private {!EnrollHelperRequest} */
this.request_ = request;
- /** @private {Array.<Gnubby>} */
+ /** @private {Array<Gnubby>} */
this.waitingForTouchGnubbies_ = [];
/** @private {boolean} */
@@ -98,14 +98,17 @@ UsbEnrollHandler.prototype.signerFoundGnubby_ =
// caller, as the gnubby is already enrolled. Map ok to WRONG_DATA, so the
// caller knows what to do.
this.notifyError_(DeviceStatusCodes.WRONG_DATA_STATUS);
- } else if (signResult.code == DeviceStatusCodes.WRONG_DATA_STATUS ||
- signResult.code == DeviceStatusCodes.WRONG_LENGTH_STATUS) {
+ } else if (SingleGnubbySigner.signErrorIndicatesInvalidKeyHandle(
+ signResult.code)) {
var gnubby = signResult['gnubby'];
// A valid helper request contains at least one enroll challenge, so use
// the app id hash from the first challenge.
var appIdHash = this.request_.enrollChallenges[0].appIdHash;
DEVICE_FACTORY_REGISTRY.getGnubbyFactory().notEnrolledPrerequisiteCheck(
gnubby, appIdHash, this.gnubbyPrerequisitesChecked_.bind(this));
+ } else {
+ // Unexpected error in signing? Send this immediately to the caller.
+ this.notifyError_(signResult.code);
}
};
diff --git a/chromium/chrome/browser/resources/cryptotoken/usbgnubbydevice.js b/chromium/chrome/browser/resources/cryptotoken/usbgnubbydevice.js
index 91c0dd19d7f..723d1252f23 100644
--- a/chromium/chrome/browser/resources/cryptotoken/usbgnubbydevice.js
+++ b/chromium/chrome/browser/resources/cryptotoken/usbgnubbydevice.js
@@ -114,7 +114,7 @@ UsbGnubbyDevice.prototype.publishFrame_ = function(f) {
} else {
changes = true;
console.log(UTIL_fmt(
- '[' + client.cid.toString(16) + '] left?'));
+ '[' + Gnubby.hexCid(client.cid) + '] left?'));
}
}
if (changes) this.clients = remaining;
@@ -346,7 +346,7 @@ UsbGnubbyDevice.prototype.updateLock_ = function(cid, cmd, arg) {
this.lockTID = window.setTimeout(
function() {
console.warn(UTIL_fmt(
- 'lock for CID ' + cid.toString(16) + ' expired!'));
+ 'lock for CID ' + Gnubby.hexCid(cid) + ' expired!'));
self.lockTID = null;
self.lockCID = 0;
},
@@ -436,7 +436,7 @@ var InterfaceEndpoint;
* interfaceSubclass: number,
* interfaceProtocol: number,
* description: (string|undefined),
- * endpoints: !Array.<!InterfaceEndpoint>
+ * endpoints: !Array<!InterfaceEndpoint>
* }}
* @see http://developer.chrome.com/apps/usb.html#method-listInterfaces
*/
diff --git a/chromium/chrome/browser/resources/cryptotoken/usbgnubbyfactory.js b/chromium/chrome/browser/resources/cryptotoken/usbgnubbyfactory.js
index 4d3271294ba..9b110a6c53c 100644
--- a/chromium/chrome/browser/resources/cryptotoken/usbgnubbyfactory.js
+++ b/chromium/chrome/browser/resources/cryptotoken/usbgnubbyfactory.js
@@ -24,11 +24,13 @@ function UsbGnubbyFactory(gnubbies) {
* @param {GnubbyDeviceId} which The device to open.
* @param {boolean} forEnroll Whether this gnubby is being opened for enrolling.
* @param {FactoryOpenCallback} cb Called with result of opening the gnubby.
- * @param {string=} logMsgUrl the url to post log messages to
+ * @param {string=} opt_appIdHash The base64-encoded hash of the app id for
+ * which the gnubby being opened.
+ * @param {string=} opt_logMsgUrl The url to post log messages to.
* @override
*/
UsbGnubbyFactory.prototype.openGnubby =
- function(which, forEnroll, cb, logMsgUrl) {
+ function(which, forEnroll, cb, opt_appIdHash, opt_logMsgUrl) {
var gnubby = new Gnubby();
gnubby.open(which, function(rc) {
if (rc) {
@@ -43,7 +45,7 @@ UsbGnubbyFactory.prototype.openGnubby =
/**
* Enumerates gnubbies.
- * @param {function(number, Array.<GnubbyDeviceId>)} cb Enumerate callback
+ * @param {function(number, Array<GnubbyDeviceId>)} cb Enumerate callback
*/
UsbGnubbyFactory.prototype.enumerate = function(cb) {
this.gnubbies_.enumerate(cb);
diff --git a/chromium/chrome/browser/resources/cryptotoken/usbsignhandler.js b/chromium/chrome/browser/resources/cryptotoken/usbsignhandler.js
index e5c227e572a..d06b529708e 100644
--- a/chromium/chrome/browser/resources/cryptotoken/usbsignhandler.js
+++ b/chromium/chrome/browser/resources/cryptotoken/usbsignhandler.js
@@ -22,7 +22,7 @@ function UsbSignHandler(request) {
this.notified_ = false;
/** @private {boolean} */
this.anyGnubbiesFound_ = false;
- /** @private {!Array.<!Gnubby>} */
+ /** @private {!Array<!Gnubby>} */
this.notEnrolledGnubbies_ = [];
}
@@ -47,7 +47,6 @@ UsbSignHandler.prototype.run = function(cb) {
this.cb_ = cb;
if (!this.request_.signData || !this.request_.signData.length) {
// Fail a sign request with an empty set of challenges.
- this.notifyError_(DeviceStatusCodes.INVALID_DATA_STATUS);
return false;
}
var timeoutMillis =
@@ -168,7 +167,7 @@ UsbSignHandler.prototype.sendBogusEnroll_ = function(gnubby) {
self.notifyError_(DeviceStatusCodes.INVALID_DATA_STATUS);
}
gnubby.enroll(
- /** @type {Array.<number>} */ (enrollChallenge),
+ /** @type {Array<number>} */ (enrollChallenge),
UsbSignHandler.BOGUS_APP_ID_HASH,
self.enrollCallback_.bind(self, gnubby));
});
diff --git a/chromium/chrome/browser/resources/cryptotoken/util.js b/chromium/chrome/browser/resources/cryptotoken/util.js
index b7c968baca2..9c2cb7a39a4 100644
--- a/chromium/chrome/browser/resources/cryptotoken/util.js
+++ b/chromium/chrome/browser/resources/cryptotoken/util.js
@@ -21,7 +21,7 @@ function UTIL_StringToBytes(s, bytes) {
/**
* Converts a byte array to a string.
- * @param {(Uint8Array|Array.<number>)} b input byte array.
+ * @param {(Uint8Array|Array<number>)} b input byte array.
* @return {string} result.
*/
function UTIL_BytesToString(b) {
@@ -30,7 +30,7 @@ function UTIL_BytesToString(b) {
/**
* Converts a byte array to a hex string.
- * @param {(Uint8Array|Array.<number>)} b input byte array.
+ * @param {(Uint8Array|Array<number>)} b input byte array.
* @return {string} result.
*/
function UTIL_BytesToHex(b) {
@@ -165,8 +165,8 @@ var UTIL_ASN_SEQUENCE = 0x30;
/**
* Parse SEQ(INT, INT) from ASN1 byte array.
- * @param {(Uint8Array|Array.<number>)} a input to parse from.
- * @return {{'r': !Array.<number>, 's': !Array.<number>}|null}
+ * @param {(Uint8Array|Array<number>)} a input to parse from.
+ * @return {{'r': !Array<number>, 's': !Array<number>}|null}
*/
function UTIL_Asn1SignatureToJson(a) {
if (a.length < 6) return null; // Too small to be valid
@@ -194,7 +194,7 @@ function UTIL_Asn1SignatureToJson(a) {
/**
* Encode a JSON signature {r,s} as an ASN1 SEQ(INT, INT). May modify sig
- * @param {{'r': (!Array.<number>|undefined), 's': !Array.<number>}} sig
+ * @param {{'r': (!Array<number>|undefined), 's': !Array<number>}} sig
* @return {!Uint8Array}
*/
function UTIL_JsonSignatureToAsn1(sig) {
diff --git a/chromium/chrome/browser/resources/cryptotoken/webrequest.js b/chromium/chrome/browser/resources/cryptotoken/webrequest.js
index d336e429437..6919644fc14 100644
--- a/chromium/chrome/browser/resources/cryptotoken/webrequest.js
+++ b/chromium/chrome/browser/resources/cryptotoken/webrequest.js
@@ -49,7 +49,7 @@ function isValidRegisteredKey(registeredKey, appIdRequired) {
/**
* Returns whether the array of registered keys appears to be valid.
- * @param {Array.<Object>} registeredKeys The array of registered keys.
+ * @param {Array<Object>} registeredKeys The array of registered keys.
* @param {boolean} appIdRequired Whether the appId property is required on
* each challenge.
* @return {boolean} Whether the array appears valid.
@@ -62,7 +62,7 @@ function isValidRegisteredKeyArray(registeredKeys, appIdRequired) {
/**
* Returns whether the array of SignChallenges appears to be valid.
- * @param {Array.<SignChallenge>} signChallenges The array of sign challenges.
+ * @param {Array<SignChallenge>} signChallenges The array of sign challenges.
* @param {boolean} challengeValueRequired Whether each challenge object
* requires a challenge value.
* @param {boolean} appIdRequired Whether the appId property is required on
@@ -258,7 +258,6 @@ function mapErrorCodeToGnubbyCodeType(errorCode, forSign) {
case ErrorCodes.TIMEOUT:
return GnubbyCodeTypes.WAIT_TOUCH;
-
}
return GnubbyCodeTypes.UNKNOWN_ERROR;
}
@@ -278,7 +277,6 @@ function mapDeviceStatusCodeToU2fError(code) {
case DeviceStatusCodes.WAIT_TOUCH_STATUS:
return {errorCode: ErrorCodes.TIMEOUT};
-
default:
var reportedError = {
errorCode: ErrorCodes.OTHER_ERROR,
@@ -316,7 +314,7 @@ function sendResponseOnce(sentResponse, closeable, response, sendResponse) {
/**
* @param {!string} string Input string
- * @return {Array.<number>} SHA256 hash value of string.
+ * @return {Array<number>} SHA256 hash value of string.
*/
function sha256HashOfString(string) {
var s = new SHA256();
@@ -409,7 +407,7 @@ function makeSignBrowserData(serverChallenge, origin, opt_tlsChannelId) {
/**
* Encodes the sign data as an array of sign helper challenges.
- * @param {Array.<SignChallenge>} signChallenges The sign challenges to encode.
+ * @param {Array<SignChallenge>} signChallenges The sign challenges to encode.
* @param {string|undefined} opt_defaultChallenge A default sign challenge
* value, if a request does not provide one.
* @param {string=} opt_defaultAppId The app id to use for each challenge, if
@@ -418,7 +416,7 @@ function makeSignBrowserData(serverChallenge, origin, opt_tlsChannelId) {
* A function that produces, from a key handle and a raw challenge, a hash
* of the raw challenge. If none is provided, a default hash function is
* used.
- * @return {!Array.<SignHelperChallenge>} The sign challenges, encoded.
+ * @return {!Array<SignHelperChallenge>} The sign challenges, encoded.
*/
function encodeSignChallenges(signChallenges, opt_defaultChallenge,
opt_defaultAppId, opt_challengeHashFunction) {
@@ -458,7 +456,7 @@ function encodeSignChallenges(signChallenges, opt_defaultChallenge,
/**
* Makes a sign helper request from an array of challenges.
- * @param {Array.<SignHelperChallenge>} challenges The sign challenges.
+ * @param {Array<SignHelperChallenge>} challenges The sign challenges.
* @param {number=} opt_timeoutSeconds Timeout value.
* @param {string=} opt_logMsgUrl URL to log to.
* @return {SignHelperRequest} The sign helper request.
diff --git a/chromium/chrome/browser/resources/cryptotoken/webrequestsender.js b/chromium/chrome/browser/resources/cryptotoken/webrequestsender.js
index d3fdf3d239a..96db4ca07b0 100644
--- a/chromium/chrome/browser/resources/cryptotoken/webrequestsender.js
+++ b/chromium/chrome/browser/resources/cryptotoken/webrequestsender.js
@@ -131,3 +131,35 @@ function getTabIdWhenPossible(sender) {
});
}
}
+
+/**
+ * Checks whether the given tab is in the foreground, i.e. is the active tab
+ * of the focused window.
+ * @param {number} tabId The tab id to check.
+ * @return {Promise<boolean>} A promise for the result of the check.
+ */
+function tabInForeground(tabId) {
+ return new Promise(function(resolve, reject) {
+ if (!chrome.tabs || !chrome.tabs.get) {
+ reject();
+ return;
+ }
+ if (!chrome.windows || !chrome.windows.get) {
+ reject();
+ return;
+ }
+ chrome.tabs.get(tabId, function(tab) {
+ if (chrome.runtime.lastError) {
+ resolve(false);
+ return;
+ }
+ if (!tab.active) {
+ resolve(false);
+ return;
+ }
+ chrome.windows.get(tab.windowId, function(aWindow) {
+ resolve(aWindow && aWindow.focused);
+ });
+ });
+ });
+}
diff --git a/chromium/chrome/browser/resources/device_log_ui/OWNERS b/chromium/chrome/browser/resources/device_log_ui/OWNERS
new file mode 100644
index 00000000000..aa215c7d505
--- /dev/null
+++ b/chromium/chrome/browser/resources/device_log_ui/OWNERS
@@ -0,0 +1 @@
+stevenjb@chromium.org
diff --git a/chromium/chrome/browser/resources/device_log_ui/device_log_ui.css b/chromium/chrome/browser/resources/device_log_ui/device_log_ui.css
new file mode 100644
index 00000000000..3a8ea55aa53
--- /dev/null
+++ b/chromium/chrome/browser/resources/device_log_ui/device_log_ui.css
@@ -0,0 +1,115 @@
+/*
+ * 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.
+ */
+
+html {
+ height: 100%;
+}
+
+body {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ margin: 0;
+}
+
+#header {
+ margin: 5px;
+}
+
+/* Checkboxes */
+
+#log-checkbox-container {
+ margin: 5px;
+}
+
+#log-checkbox-container button {
+ -webkit-margin-end: 8px;
+}
+
+#log-checkbox-container label {
+ vertical-align: middle;
+}
+
+#log-checkbox-show {
+ font-weight: bold;
+}
+
+#log-checkbox-container input {
+ margin-bottom: 1px;
+ vertical-align: middle;
+}
+
+/* Log */
+
+#log-container {
+ border: 1px solid rgb(220, 220, 220);
+ flex: 1 1 100%;
+ font-size: 12px;
+ margin: 5px;
+ overflow: auto;
+ padding: 10px;
+}
+
+#log-container p {
+ font-family: monospace;
+ line-height: 20px;
+ margin: 2px;
+}
+
+/* Log Level tags */
+
+.level-tag {
+ -webkit-margin-end: 5px;
+ border: 1px solid;
+ border-radius: 2px;
+ float: left;
+ height: 14px;
+ margin-top: 2px;
+ padding: 0 4px 2px 4px;
+ width: 50px;
+}
+
+.log-level-error {
+ color: red;
+}
+
+.log-level-user {
+ color: blue;
+}
+
+.log-level-event {
+ color: black;
+}
+
+.log-level-debug {
+ color: grey;
+}
+
+/* Log Type tags */
+
+.type-tag {
+ -webkit-margin-end: 5px;
+ border: 1px solid;
+ border-radius: 2px;
+ float: left;
+ font-weight: bold;
+ height: 14px;
+ margin-top: 2px;
+ padding: 0 4px 2px 4px;
+ width: 50px;
+}
+
+.log-type-login {
+ color: darkgreen;
+}
+
+.log-type-network {
+ color: darkblue;
+}
+
+.log-type-power {
+ color: purple;
+}
diff --git a/chromium/chrome/browser/resources/device_log_ui/device_log_ui.html b/chromium/chrome/browser/resources/device_log_ui/device_log_ui.html
new file mode 100644
index 00000000000..fbf99dfa2d8
--- /dev/null
+++ b/chromium/chrome/browser/resources/device_log_ui/device_log_ui.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<html i18n-values="dir:textdirection;lang:language">
+<head>
+ <meta charset="utf-8">
+ <title id="device-log-title" i18n-content="titleText"></title>
+ <link rel="stylesheet" href="chrome://device-log/device_log_ui.css">
+ <script src="chrome://resources/js/load_time_data.js"></script>
+ <script src="chrome://resources/js/util.js"></script>
+ <script src="chrome://device-log/strings.js"></script>
+ <script src="chrome://device-log/device_log_ui.js"></script>
+</head>
+<body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+ <div id="header">
+ <p i18n-content="autoRefreshText"></p>
+ </div>
+ <div id="log-checkbox-container">
+ <button id="log-refresh" i18n-content="logRefreshText"></button>
+ <label id="log-checkbox-show" i18n-content="logLevelShowText"></label>
+
+ <label>
+ <input id="log-level-error" type="checkbox">
+ <span i18n-content="logLevelErrorText"></span>
+ </label>
+ <label>
+ <input id="log-level-user" type="checkbox">
+ <span i18n-content="logLevelUserText"></span>
+ </label>
+ <label>
+ <input id="log-level-event" type="checkbox">
+ <span i18n-content="logLevelEventText"></span>
+ </label>
+ <label>
+ <input id="log-level-debug" type="checkbox">
+ <span i18n-content="logLevelDebugText"></span>
+ </label>
+ <label>
+ <input id="log-type-login" type="checkbox">
+ <span i18n-content="logTypeLoginText"></span>
+ </label>
+ <label>
+ <input id="log-type-network" type="checkbox">
+ <span i18n-content="logTypeNetworkText"></span>
+ </label>
+ <label>
+ <input id="log-type-power" type="checkbox">
+ <span i18n-content="logTypePowerText"></span>
+ </label>
+ <label>
+ <input id="log-type-usb" type="checkbox">
+ <span i18n-content="logTypeUsbText"></span>
+ </label>
+ <label>
+ <input id="log-type-hid" type="checkbox">
+ <span i18n-content="logTypeHidText"></span>
+ </label>
+ <label>
+ <input id="log-fileinfo" type="checkbox">
+ <span i18n-content="logLevelFileinfoText"></span>
+ </label>
+ <label>
+ <input id="log-timedetail" type="checkbox">
+ <span i18n-content="logLevelTimeDetailText"></span>
+ </label>
+ </div>
+ <div id="log-container"></div>
+ <script src="chrome://resources/js/i18n_template.js"></script>
+</body>
+</html>
diff --git a/chromium/chrome/browser/resources/device_log_ui/device_log_ui.js b/chromium/chrome/browser/resources/device_log_ui/device_log_ui.js
new file mode 100644
index 00000000000..a562ed7eddb
--- /dev/null
+++ b/chromium/chrome/browser/resources/device_log_ui/device_log_ui.js
@@ -0,0 +1,149 @@
+// 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.
+
+var DeviceLogUI = (function() {
+ 'use strict';
+
+ /**
+ * Creates a tag for the log level.
+ *
+ * @param {string} level A string that represents log level.
+ * @return {HTMLSpanElement} The created span element.
+ */
+ var createLevelTag = function(level) {
+ var levelClassName = 'log-level-' + level.toLowerCase();
+ var tag = document.createElement('span');
+ tag.textContent = level;
+ tag.className = 'level-tag ' + levelClassName;
+ return tag;
+ };
+
+ /**
+ * Creates a tag for the log type.
+ *
+ * @param {string} level A string that represents log type.
+ * @return {HTMLSpanElement} The created span element.
+ */
+ var createTypeTag = function(type) {
+ var typeClassName = 'log-type-' + type.toLowerCase();
+ var tag = document.createElement('span');
+ tag.textContent = type;
+ tag.className = 'type-tag ' + typeClassName;
+ return tag;
+ };
+
+ /**
+ * Creates an element that contains the time, the event, the level and
+ * the description of the given log entry.
+ *
+ * @param {Object} logEntry An object that represents a single line of log.
+ * @return {?HTMLParagraphElement} The created p element that represents
+ * the log entry, or null if the entry should be skipped.
+ */
+ var createLogEntryText = function(logEntry) {
+ var level = logEntry['level'];
+ var levelCheckbox = 'log-level-' + level.toLowerCase();
+ if ($(levelCheckbox) && !$(levelCheckbox).checked)
+ return null;
+
+ var type = logEntry['type'];
+ var typeCheckbox = 'log-type-' + type.toLowerCase();
+ if ($(typeCheckbox) && !$(typeCheckbox).checked)
+ return null;
+
+ var res = document.createElement('p');
+ var textWrapper = document.createElement('span');
+ var fileinfo = '';
+ if ($('log-fileinfo').checked)
+ fileinfo = logEntry['file'];
+ var timestamp = '';
+ if ($('log-timedetail').checked)
+ timestamp = logEntry['timestamp'];
+ else
+ timestamp = logEntry['timestampshort'];
+ textWrapper.textContent = loadTimeData.getStringF(
+ 'logEntryFormat',
+ timestamp,
+ fileinfo,
+ logEntry['event']);
+ res.appendChild(createTypeTag(type));
+ res.appendChild(createLevelTag(level));
+ res.appendChild(textWrapper);
+ return res;
+ };
+
+ /**
+ * Creates event log entries.
+ *
+ * @param {Array<string>} logEntries An array of strings that represent log
+ * log events in JSON format.
+ */
+ var createEventLog = function(logEntries) {
+ var container = $('log-container');
+ container.textContent = '';
+ for (var i = 0; i < logEntries.length; ++i) {
+ var entry = createLogEntryText(JSON.parse(logEntries[i]));
+ if (entry)
+ container.appendChild(entry);
+ }
+ };
+
+ /**
+ * Callback function, triggered when the log is received.
+ *
+ * @param {Object} data A JSON structure of event log entries.
+ */
+ var getLogCallback = function(data) {
+ createEventLog(JSON.parse(data));
+ };
+
+ /**
+ * Requests a log update.
+ */
+ var requestLog = function() {
+ chrome.send('DeviceLog.getLog');
+ };
+
+ /**
+ * Sets refresh rate if the interval is found in the url.
+ */
+ var setRefresh = function() {
+ var interval = parseQueryParams(window.location)['refresh'];
+ if (interval && interval != '')
+ setInterval(requestLog, parseInt(interval) * 1000);
+ };
+
+ /**
+ * Gets log information from WebUI.
+ */
+ document.addEventListener('DOMContentLoaded', function() {
+ // Show all levels except 'debug' by default.
+ $('log-level-error').checked = true;
+ $('log-level-user').checked = true;
+ $('log-level-event').checked = true;
+ $('log-level-debug').checked = false;
+
+ // Show all types by default.
+ var checkboxes = document.querySelectorAll(
+ '#log-checkbox-container input[type="checkbox"][id*="log-type"]');
+ for (var i = 0; i < checkboxes.length; ++i)
+ checkboxes[i].checked = true;
+
+ $('log-fileinfo').checked = false;
+ $('log-timedetail').checked = false;
+
+ $('log-refresh').onclick = requestLog;
+ checkboxes = document.querySelectorAll(
+ '#log-checkbox-container input[type="checkbox"]');
+ for (var i = 0; i < checkboxes.length; ++i)
+ checkboxes[i].onclick = requestLog;
+
+ setRefresh();
+ requestLog();
+ });
+
+ return {
+ getLogCallback: getLogCallback
+ };
+})();
diff --git a/chromium/chrome/browser/resources/domain_reliability_internals.html b/chromium/chrome/browser/resources/domain_reliability_internals.html
index 3e95f946595..446bcbf9612 100644
--- a/chromium/chrome/browser/resources/domain_reliability_internals.html
+++ b/chromium/chrome/browser/resources/domain_reliability_internals.html
@@ -1,8 +1,9 @@
-<!DOCTYPE HTML>
-<html i18n-values="dir:textdirection">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
<head>
<meta charset="utf-8">
<title i18n-content="title"></title>
+ <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="domain_reliability_internals.css" />
<script src="chrome://resources/js/cr.js"></script>
<script src="chrome://resources/js/util.js"></script>
@@ -10,9 +11,9 @@
<script src="strings.js"></script>
<script src="domain_reliability_internals.js"></script>
</head>
-<body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<body>
<h1 i18n-content="title"></h1>
- <script src="chrome://resources/js/i18n_template2.js"></script>
+ <script src="chrome://resources/js/i18n_template.js"></script>
<div id="template">
<div jsdisplay="error">
Error loading Domain Reliability data:
diff --git a/chromium/chrome/browser/resources/downloads/OWNERS b/chromium/chrome/browser/resources/downloads/OWNERS
index df2ad7b4f3d..ba74b648a51 100644
--- a/chromium/chrome/browser/resources/downloads/OWNERS
+++ b/chromium/chrome/browser/resources/downloads/OWNERS
@@ -1,2 +1,3 @@
asanka@chromium.org
benjhayden@chromium.org
+dbeam@chromium.org
diff --git a/chromium/chrome/browser/resources/downloads/compiled_resources.gyp b/chromium/chrome/browser/resources/downloads/compiled_resources.gyp
index 1941a665e0d..c6c8b08a8c2 100644
--- a/chromium/chrome/browser/resources/downloads/compiled_resources.gyp
+++ b/chromium/chrome/browser/resources/downloads/compiled_resources.gyp
@@ -4,15 +4,27 @@
{
'targets': [
{
- 'target_name': 'downloads',
+ 'target_name': 'manager',
'variables': {
'depends': [
'../../../../ui/webui/resources/js/action_link.js',
- '../../../../ui/webui/resources/js/cr.js',
+ '../../../../ui/webui/resources/js/assert.js',
'../../../../ui/webui/resources/js/compiled_resources.gyp:load_time_data',
+ '../../../../ui/webui/resources/js/cr.js',
+ '../../../../ui/webui/resources/js/cr/ui.js',
+ '../../../../ui/webui/resources/js/cr/ui/command.js',
+ '../../../../ui/webui/resources/js/cr/ui/focus_grid.js',
+ '../../../../ui/webui/resources/js/cr/ui/focus_row.js',
+ '../../../../ui/webui/resources/js/event_tracker.js',
'../../../../ui/webui/resources/js/util.js',
+ 'item.js',
+ 'item_view.js',
+ 'focus_row.js',
+ ],
+ 'externs': [
+ '<(CLOSURE_DIR)/externs/chrome_send_externs.js',
+ 'externs.js',
],
- 'externs': ['<(CLOSURE_DIR)/externs/chrome_send_externs.js'],
},
'includes': ['../../../../third_party/closure_compiler/compile_js.gypi'],
}
diff --git a/chromium/chrome/browser/resources/downloads/downloads.css b/chromium/chrome/browser/resources/downloads/downloads.css
index 2a3303f7c53..b505853f990 100644
--- a/chromium/chrome/browser/resources/downloads/downloads.css
+++ b/chromium/chrome/browser/resources/downloads/downloads.css
@@ -8,24 +8,14 @@
font-weight: bold;
}
-.disabled-link {
- color: #999;
- text-decoration: none;
+#download-actions {
+ -webkit-margin-start: 16px;
}
-#downloads-actions > a,
-#downloads-actions > .disabled-link {
+#downloads-actions > * ~ * {
-webkit-margin-start: 10px;
}
-#downloads-actions > a:first-child {
- -webkit-margin-start: 0;
-}
-
-#open-downloads-folder {
- -webkit-margin-end: 16px;
-}
-
#downloads-display,
#no-downloads-or-results {
max-width: 740px;
@@ -76,25 +66,22 @@ html[dir=rtl] .icon {
right: 9px;
}
-.download.otr > .safe,
-.download.otr > .show-dangerous {
+.download.otr > :-webkit-any(.safe, .dangerous) {
-webkit-transition: opacity 150ms;
- background: url('../../../../ui/webui/resources/images/otr_icon_standalone.png')
+ background: url(../../../../ui/webui/resources/images/otr_icon_standalone.png)
no-repeat right bottom;
opacity: .66;
}
-html[dir=rtl] .download.otr > .safe,
-html[dir=rtl] .download.otr > .show-dangerous {
+html[dir=rtl] .download.otr > :-webkit-any(.safe, .dangerous) {
background-position: left bottom;
}
-.download.otr > .safe:hover,
-.download.otr > .show-dangerous:hover {
+.download.otr > :-webkit-any(.safe, .dangerous):hover {
opacity: 1;
}
-.malware-description {
+.description.malware {
color: rgb(196, 42, 23);
}
@@ -112,20 +99,29 @@ html[dir=rtl] .progress {
}
.progress.background {
- background: url('chrome://theme/IDR_DOWNLOAD_PROGRESS_BACKGROUND_32');
+ background: url(chrome://theme/IDR_DOWNLOAD_PROGRESS_BACKGROUND_32);
background-size: 48px;
}
+.title-area {
+ display: flex;
+}
+
+.title-area > * {
+ flex-shrink: 0;
+}
+
.name,
.src-url,
-.controls a {
+.controls a,
+.description {
-webkit-padding-end: 4px;
-webkit-padding-start: 4px;
}
.name {
-webkit-padding-end: 16px;
- display: none;
+ display: inline-block;
max-width: 450px;
word-break: break-all;
}
@@ -156,6 +152,10 @@ html[dir=rtl] .progress {
color: #777;
}
+.safe .controls a {
+ display: inline;
+}
+
#downloads-pagination {
-webkit-margin-start: 18px;
padding-top: 24px;
diff --git a/chromium/chrome/browser/resources/downloads/downloads.html b/chromium/chrome/browser/resources/downloads/downloads.html
index c42ef5d126c..3e7e2021cbd 100644
--- a/chromium/chrome/browser/resources/downloads/downloads.html
+++ b/chromium/chrome/browser/resources/downloads/downloads.html
@@ -1,5 +1,5 @@
-<!DOCTYPE HTML>
-<html i18n-values="dir:textdirection;">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
<head>
<meta charset="utf-8">
<title i18n-content="title"></title>
@@ -7,11 +7,21 @@
<!-- This has to come after chrome_shared.css -->
<link rel="stylesheet" href="downloads.css">
<script src="chrome://resources/js/action_link.js"></script>
+ <script src="chrome://resources/js/cr.js"></script>
+ <script src="chrome://resources/js/cr/ui.js"></script>
+ <script src="chrome://resources/js/cr/ui/command.js"></script>
<script src="chrome://resources/js/load_time_data.js"></script>
<script src="chrome://resources/js/util.js"></script>
- <script src="chrome://downloads/downloads.js"></script>
+ <script src="chrome://resources/js/assert.js"></script>
+ <script src="chrome://resources/js/event_tracker.js"></script>
+ <script src="chrome://resources/js/cr/ui/focus_row.js"></script>
+ <script src="chrome://resources/js/cr/ui/focus_grid.js"></script>
+ <script src="chrome://downloads/item.js"></script>
+ <script src="chrome://downloads/item_view.js"></script>
+ <script src="chrome://downloads/focus_row.js"></script>
+ <script src="chrome://downloads/manager.js"></script>
</head>
-<body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<body>
<header>
<h1 i18n-content="downloads"></h1>
<input id="term" type="search" tabindex=1
@@ -23,13 +33,71 @@
<span id="downloads-actions">
<a is="action-link" id="open-downloads-folder"
i18n-content="open_downloads_folder"></a>
- <span id="clear-all-holder"></span>
+ <a is="action-link" id="clear-all" i18n-content="clear_all" hidden></a>
</span>
</div>
<div id="downloads-display"></div>
<div id="no-downloads-or-results"></div>
</div>
+ <command id="clear-all-command" shortcut="Alt-U+0043"><!-- Alt+C -->
+<if expr="is_macosx">
+ <command id="undo-command" shortcut="Meta-U+005A"><!-- Command+Z -->
+</if>
+<if expr="not is_macosx">
+ <command id="undo-command" shortcut="Ctrl-U+005A"><!-- Ctrl+Z -->
+</if>
+ <div id="templates" hidden>
+ <div class="download">
+ <div class="date-container">
+ <div class="since"></div>
+ <div class="date"></div>
+ </div>
+ <div class="safe">
+ <div class="progress background"></div>
+ <canvas class="progress"></canvas>
+ <img class="icon" alt="">
+ <div class="title-area">
+ <a is="action-link" class="name" column-type="name"></a>
+ <span class="name"></span>
+ <span class="status"></span>
+ </div>
+ <div class="url-container">
+ <a class="src-url" target="_blank" column-type="url"></a>
+ </div>
+ <div class="controls">
+ <a is="action-link" class="show" column-type="show"
+ i18n-content="control_showinfolder"></a>
+ <a class="retry" column-type="retry" i18n-content="control_retry"
+ download></a>
+ <a is="action-link" class="pause" column-type="pause"
+ i18n-content="control_pause"></a>
+ <a is="action-link" class="resume" column-type="resume"
+ i18n-content="control_resume"></a>
+ <a is="action-link" class="remove" column-type="remove"
+ i18n-content="control_removefromlist"></a>
+ <a is="action-link" class="cancel" column-type="cancel"
+ i18n-content="control_cancel"></a>
+ <span class="controlled-by"
+ i18n-values=".innerHTML:control_by_extension"></span>
+ </div>
+ </div>
+ <div class="dangerous">
+ <img class="icon" alt="">
+ <div class="description"></div>
+ <div class="controls">
+ <a is="action-link" class="restore" column-type="save"
+ i18n-content="danger_restore"></a>
+ <a is="action-link" class="remove" column-type="discard"
+ i18n-content="control_removefromlist"></a>
+ </div>
+ <button class="save" column-type="save"
+ i18n-content="danger_save"></button>
+ <button class="discard" column-type="discard"
+ i18n-content="danger_discard"></button>
+ </div>
+ </div>
+ </div>
<script src="chrome://downloads/strings.js"></script>
- <script src="chrome://resources/js/i18n_template2.js"></script>
+ <script src="chrome://resources/js/i18n_template.js"></script>
</body>
</html>
diff --git a/chromium/chrome/browser/resources/downloads/downloads.js b/chromium/chrome/browser/resources/downloads/downloads.js
deleted file mode 100644
index 98a3c6ee0a8..00000000000
--- a/chromium/chrome/browser/resources/downloads/downloads.js
+++ /dev/null
@@ -1,991 +0,0 @@
-// Copyright (c) 2012 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.
-
-// TODO(jhawkins): Use hidden instead of showInline* and display:none.
-
-/**
- * The type of the download object. The definition is based on
- * chrome/browser/ui/webui/downloads_dom_handler.cc:CreateDownloadItemValue()
- * @typedef {{by_ext_id: (string|undefined),
- * by_ext_name: (string|undefined),
- * danger_type: (string|undefined),
- * date_string: string,
- * file_externally_removed: boolean,
- * file_name: string,
- * file_path: string,
- * file_url: string,
- * id: string,
- * last_reason_text: (string|undefined),
- * otr: boolean,
- * percent: (number|undefined),
- * progress_status_text: (string|undefined),
- * received: (number|undefined),
- * resume: boolean,
- * retry: boolean,
- * since_string: string,
- * started: number,
- * state: string,
- * total: number,
- * url: string}}
- */
-var BackendDownloadObject;
-
-/**
- * Sets the display style of a node.
- * @param {!Element} node The target element to show or hide.
- * @param {boolean} isShow Should the target element be visible.
- */
-function showInline(node, isShow) {
- node.style.display = isShow ? 'inline' : 'none';
-}
-
-/**
- * Sets the display style of a node.
- * @param {!Element} node The target element to show or hide.
- * @param {boolean} isShow Should the target element be visible.
- */
-function showInlineBlock(node, isShow) {
- node.style.display = isShow ? 'inline-block' : 'none';
-}
-
-/**
- * Creates a link with a specified onclick handler and content.
- * @param {function()} onclick The onclick handler.
- * @param {string=} opt_text The link text.
- * @return {!Element} The created link element.
- */
-function createActionLink(onclick, opt_text) {
- var link = new ActionLink;
- link.onclick = onclick;
- if (opt_text) link.textContent = opt_text;
- return link;
-}
-
-/**
- * Creates a button with a specified onclick handler and content.
- * @param {function()} onclick The onclick handler.
- * @param {string} value The button text.
- * @return {Element} The created button.
- */
-function createButton(onclick, value) {
- var button = document.createElement('input');
- button.type = 'button';
- button.value = value;
- button.onclick = onclick;
- return button;
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// Downloads
-/**
- * Class to hold all the information about the visible downloads.
- * @constructor
- */
-function Downloads() {
- /**
- * @type {!Object.<string, Download>}
- * @private
- */
- this.downloads_ = {};
- this.node_ = $('downloads-display');
- this.noDownloadsOrResults_ = $('no-downloads-or-results');
- this.summary_ = $('downloads-summary-text');
- this.searchText_ = '';
-
- // Keep track of the dates of the newest and oldest downloads so that we
- // know where to insert them.
- this.newestTime_ = -1;
-
- // Icon load request queue.
- this.iconLoadQueue_ = [];
- this.isIconLoading_ = false;
-
- this.progressForeground1_ = new Image();
- this.progressForeground1_.src =
- 'chrome://theme/IDR_DOWNLOAD_PROGRESS_FOREGROUND_32@1x';
- this.progressForeground2_ = new Image();
- this.progressForeground2_.src =
- 'chrome://theme/IDR_DOWNLOAD_PROGRESS_FOREGROUND_32@2x';
-
- window.addEventListener('keydown', this.onKeyDown_.bind(this));
-
- this.onDownloadListChanged_();
-}
-
-/**
- * Called when a download has been updated or added.
- * @param {BackendDownloadObject} download A backend download object
- */
-Downloads.prototype.updated = function(download) {
- var id = download.id;
- if (!!this.downloads_[id]) {
- this.downloads_[id].update(download);
- } else {
- this.downloads_[id] = new Download(download);
- // We get downloads in display order, so we don't have to worry about
- // maintaining correct order - we can assume that any downloads not in
- // display order are new ones and so we can add them to the top of the
- // list.
- if (download.started > this.newestTime_) {
- this.node_.insertBefore(this.downloads_[id].node, this.node_.firstChild);
- this.newestTime_ = download.started;
- } else {
- this.node_.appendChild(this.downloads_[id].node);
- }
- }
- // Download.prototype.update may change its nodeSince_ and nodeDate_, so
- // update all the date displays.
- // TODO(benjhayden) Only do this if its nodeSince_ or nodeDate_ actually did
- // change since this may touch 150 elements and Downloads.prototype.updated
- // may be called 150 times.
- this.onDownloadListChanged_();
-};
-
-/**
- * Set our display search text.
- * @param {string} searchText The string we're searching for.
- */
-Downloads.prototype.setSearchText = function(searchText) {
- this.searchText_ = searchText;
-};
-
-/**
- * Update the summary block above the results
- */
-Downloads.prototype.updateSummary = function() {
- if (this.searchText_) {
- this.summary_.textContent = loadTimeData.getStringF('searchresultsfor',
- this.searchText_);
- } else {
- this.summary_.textContent = '';
- }
-};
-
-/**
- * Returns the number of downloads in the model. Used by tests.
- * @return {number} Returns the number of downloads shown on the page.
- */
-Downloads.prototype.size = function() {
- return Object.keys(this.downloads_).length;
-};
-
-/**
- * Called whenever the downloads lists items have changed (either by being
- * updated, added, or removed).
- * @private
- */
-Downloads.prototype.onDownloadListChanged_ = function() {
- // Update the date visibility in our nodes so that no date is repeated.
- var dateContainers = document.getElementsByClassName('date-container');
- var displayed = {};
- for (var i = 0, container; container = dateContainers[i]; i++) {
- var dateString = container.getElementsByClassName('date')[0].innerHTML;
- if (!!displayed[dateString]) {
- container.style.display = 'none';
- } else {
- displayed[dateString] = true;
- container.style.display = 'block';
- }
- }
-
- this.noDownloadsOrResults_.textContent = loadTimeData.getString(
- this.searchText_ ? 'no_search_results' : 'no_downloads');
-
- var hasDownloads = this.size() > 0;
- this.node_.hidden = !hasDownloads;
- this.noDownloadsOrResults_.hidden = hasDownloads;
-};
-
-/**
- * Remove a download.
- * @param {string} id The id of the download to remove.
- */
-Downloads.prototype.remove = function(id) {
- this.node_.removeChild(this.downloads_[id].node);
- delete this.downloads_[id];
- this.onDownloadListChanged_();
-};
-
-/**
- * Clear all downloads and reset us back to a null state.
- */
-Downloads.prototype.clear = function() {
- for (var id in this.downloads_) {
- this.downloads_[id].clear();
- this.remove(id);
- }
-};
-
-/**
- * Schedule icon load.
- * @param {HTMLImageElement} elem Image element that should contain the icon.
- * @param {string} iconURL URL to the icon.
- */
-Downloads.prototype.scheduleIconLoad = function(elem, iconURL) {
- var self = this;
-
- // Sends request to the next icon in the queue and schedules
- // call to itself when the icon is loaded.
- function loadNext() {
- self.isIconLoading_ = true;
- while (self.iconLoadQueue_.length > 0) {
- var request = self.iconLoadQueue_.shift();
- var oldSrc = request.element.src;
- request.element.onabort = request.element.onerror =
- request.element.onload = loadNext;
- request.element.src = request.url;
- if (oldSrc != request.element.src)
- return;
- }
- self.isIconLoading_ = false;
- }
-
- // Create new request
- var loadRequest = {element: elem, url: iconURL};
- this.iconLoadQueue_.push(loadRequest);
-
- // Start loading if none scheduled yet
- if (!this.isIconLoading_)
- loadNext();
-};
-
-/**
- * Returns whether the displayed list needs to be updated or not.
- * @param {Array} downloads Array of download nodes.
- * @return {boolean} Returns true if the displayed list is to be updated.
- */
-Downloads.prototype.isUpdateNeeded = function(downloads) {
- var size = 0;
- for (var i in this.downloads_)
- size++;
- if (size != downloads.length)
- return true;
- // Since there are the same number of items in the incoming list as
- // |this.downloads_|, there won't be any removed downloads without some
- // downloads having been inserted. So check only for new downloads in
- // deciding whether to update.
- for (var i = 0; i < downloads.length; i++) {
- if (!this.downloads_[downloads[i].id])
- return true;
- }
- return false;
-};
-
-/**
- * Handles shortcut keys.
- * @param {Event} evt The keyboard event.
- * @private
- */
-Downloads.prototype.onKeyDown_ = function(evt) {
- var keyEvt = /** @type {KeyboardEvent} */(evt);
- if (keyEvt.keyCode == 67 && keyEvt.altKey) { // alt + c.
- clearAll();
- keyEvt.preventDefault();
- }
-};
-
-///////////////////////////////////////////////////////////////////////////////
-// Download
-/**
- * A download and the DOM representation for that download.
- * @param {BackendDownloadObject} download A backend download object
- * @constructor
- */
-function Download(download) {
- // Create DOM
- this.node = createElementWithClassName(
- 'div', 'download' + (download.otr ? ' otr' : ''));
-
- // Dates
- this.dateContainer_ = createElementWithClassName('div', 'date-container');
- this.node.appendChild(this.dateContainer_);
-
- this.nodeSince_ = createElementWithClassName('div', 'since');
- this.nodeDate_ = createElementWithClassName('div', 'date');
- this.dateContainer_.appendChild(this.nodeSince_);
- this.dateContainer_.appendChild(this.nodeDate_);
-
- // Container for all 'safe download' UI.
- this.safe_ = createElementWithClassName('div', 'safe');
- this.safe_.ondragstart = this.drag_.bind(this);
- this.node.appendChild(this.safe_);
-
- if (download.state != Download.States.COMPLETE) {
- this.nodeProgressBackground_ =
- createElementWithClassName('div', 'progress background');
- this.safe_.appendChild(this.nodeProgressBackground_);
-
- this.nodeProgressForeground_ =
- createElementWithClassName('canvas', 'progress');
- this.nodeProgressForeground_.width = Download.Progress.width;
- this.nodeProgressForeground_.height = Download.Progress.height;
- this.canvasProgress_ = this.nodeProgressForeground_.getContext('2d');
-
- this.safe_.appendChild(this.nodeProgressForeground_);
- }
-
- this.nodeImg_ = createElementWithClassName('img', 'icon');
- this.nodeImg_.alt = '';
- this.safe_.appendChild(this.nodeImg_);
-
- // FileLink is used for completed downloads, otherwise we show FileName.
- this.nodeTitleArea_ = createElementWithClassName('div', 'title-area');
- this.safe_.appendChild(this.nodeTitleArea_);
-
- this.nodeFileLink_ = createActionLink(this.openFile_.bind(this));
- this.nodeFileLink_.className = 'name';
- this.nodeFileLink_.style.display = 'none';
- this.nodeTitleArea_.appendChild(this.nodeFileLink_);
-
- this.nodeFileName_ = createElementWithClassName('span', 'name');
- this.nodeFileName_.style.display = 'none';
- this.nodeTitleArea_.appendChild(this.nodeFileName_);
-
- this.nodeStatus_ = createElementWithClassName('span', 'status');
- this.nodeTitleArea_.appendChild(this.nodeStatus_);
-
- var nodeURLDiv = createElementWithClassName('div', 'url-container');
- this.safe_.appendChild(nodeURLDiv);
-
- this.nodeURL_ = createElementWithClassName('a', 'src-url');
- this.nodeURL_.target = '_blank';
- nodeURLDiv.appendChild(this.nodeURL_);
-
- // Controls.
- this.nodeControls_ = createElementWithClassName('div', 'controls');
- this.safe_.appendChild(this.nodeControls_);
-
- // We don't need 'show in folder' in chromium os. See download_ui.cc and
- // http://code.google.com/p/chromium-os/issues/detail?id=916.
- if (loadTimeData.valueExists('control_showinfolder')) {
- this.controlShow_ = createActionLink(this.show_.bind(this),
- loadTimeData.getString('control_showinfolder'));
- this.nodeControls_.appendChild(this.controlShow_);
- } else {
- this.controlShow_ = null;
- }
-
- this.controlRetry_ = document.createElement('a');
- this.controlRetry_.download = '';
- this.controlRetry_.textContent = loadTimeData.getString('control_retry');
- this.nodeControls_.appendChild(this.controlRetry_);
-
- // Pause/Resume are a toggle.
- this.controlPause_ = createActionLink(this.pause_.bind(this),
- loadTimeData.getString('control_pause'));
- this.nodeControls_.appendChild(this.controlPause_);
-
- this.controlResume_ = createActionLink(this.resume_.bind(this),
- loadTimeData.getString('control_resume'));
- this.nodeControls_.appendChild(this.controlResume_);
-
- // Anchors <a> don't support the "disabled" property.
- if (loadTimeData.getBoolean('allow_deleting_history')) {
- this.controlRemove_ = createActionLink(this.remove_.bind(this),
- loadTimeData.getString('control_removefromlist'));
- this.controlRemove_.classList.add('control-remove-link');
- } else {
- this.controlRemove_ = document.createElement('span');
- this.controlRemove_.classList.add('disabled-link');
- var text = document.createTextNode(
- loadTimeData.getString('control_removefromlist'));
- this.controlRemove_.appendChild(text);
- }
- if (!loadTimeData.getBoolean('show_delete_history'))
- this.controlRemove_.hidden = true;
-
- this.nodeControls_.appendChild(this.controlRemove_);
-
- this.controlCancel_ = createActionLink(this.cancel_.bind(this),
- loadTimeData.getString('control_cancel'));
- this.nodeControls_.appendChild(this.controlCancel_);
-
- this.controlByExtension_ = document.createElement('span');
- this.nodeControls_.appendChild(this.controlByExtension_);
-
- // Container for 'unsafe download' UI.
- this.danger_ = createElementWithClassName('div', 'show-dangerous');
- this.node.appendChild(this.danger_);
-
- this.dangerNodeImg_ = createElementWithClassName('img', 'icon');
- this.dangerNodeImg_.alt = '';
- this.danger_.appendChild(this.dangerNodeImg_);
-
- this.dangerDesc_ = document.createElement('div');
- this.danger_.appendChild(this.dangerDesc_);
-
- // Buttons for the malicious case.
- this.malwareNodeControls_ = createElementWithClassName('div', 'controls');
- this.malwareSave_ = createActionLink(
- this.saveDangerous_.bind(this),
- loadTimeData.getString('danger_restore'));
- this.malwareNodeControls_.appendChild(this.malwareSave_);
- this.malwareDiscard_ = createActionLink(
- this.discardDangerous_.bind(this),
- loadTimeData.getString('control_removefromlist'));
- this.malwareNodeControls_.appendChild(this.malwareDiscard_);
- this.danger_.appendChild(this.malwareNodeControls_);
-
- // Buttons for the dangerous but not malicious case.
- this.dangerSave_ = createButton(
- this.saveDangerous_.bind(this),
- loadTimeData.getString('danger_save'));
- this.danger_.appendChild(this.dangerSave_);
-
- this.dangerDiscard_ = createButton(
- this.discardDangerous_.bind(this),
- loadTimeData.getString('danger_discard'));
- this.danger_.appendChild(this.dangerDiscard_);
-
- // Update member vars.
- this.update(download);
-}
-
-/**
- * The states a download can be in. These correspond to states defined in
- * DownloadsDOMHandler::CreateDownloadItemValue
- * @enum {string}
- */
-Download.States = {
- IN_PROGRESS: 'IN_PROGRESS',
- CANCELLED: 'CANCELLED',
- COMPLETE: 'COMPLETE',
- PAUSED: 'PAUSED',
- DANGEROUS: 'DANGEROUS',
- INTERRUPTED: 'INTERRUPTED',
-};
-
-/**
- * Explains why a download is in DANGEROUS state.
- * @enum {string}
- */
-Download.DangerType = {
- NOT_DANGEROUS: 'NOT_DANGEROUS',
- DANGEROUS_FILE: 'DANGEROUS_FILE',
- DANGEROUS_URL: 'DANGEROUS_URL',
- DANGEROUS_CONTENT: 'DANGEROUS_CONTENT',
- UNCOMMON_CONTENT: 'UNCOMMON_CONTENT',
- DANGEROUS_HOST: 'DANGEROUS_HOST',
- POTENTIALLY_UNWANTED: 'POTENTIALLY_UNWANTED',
-};
-
-/**
- * @param {number} a Some float.
- * @param {number} b Some float.
- * @param {number=} opt_pct Percent of min(a,b).
- * @return {boolean} true if a is within opt_pct percent of b.
- */
-function floatEq(a, b, opt_pct) {
- return Math.abs(a - b) < (Math.min(a, b) * (opt_pct || 1.0) / 100.0);
-}
-
-/**
- * Constants and "constants" for the progress meter.
- */
-Download.Progress = {
- START_ANGLE: -0.5 * Math.PI,
- SIDE: 48,
-};
-
-/***/
-Download.Progress.HALF = Download.Progress.SIDE / 2;
-
-function computeDownloadProgress() {
- if (floatEq(Download.Progress.scale, window.devicePixelRatio)) {
- // Zooming in or out multiple times then typing Ctrl+0 resets the zoom level
- // directly to 1x, which fires the matchMedia event multiple times.
- return;
- }
- Download.Progress.scale = window.devicePixelRatio;
- Download.Progress.width = Download.Progress.SIDE * Download.Progress.scale;
- Download.Progress.height = Download.Progress.SIDE * Download.Progress.scale;
- Download.Progress.radius = Download.Progress.HALF * Download.Progress.scale;
- Download.Progress.centerX = Download.Progress.HALF * Download.Progress.scale;
- Download.Progress.centerY = Download.Progress.HALF * Download.Progress.scale;
-}
-computeDownloadProgress();
-
-// Listens for when device-pixel-ratio changes between any zoom level.
-[0.3, 0.4, 0.6, 0.7, 0.8, 0.95, 1.05, 1.2, 1.4, 1.6, 1.9, 2.2, 2.7, 3.5, 4.5
-].forEach(function(scale) {
- var media = '(-webkit-min-device-pixel-ratio:' + scale + ')';
- window.matchMedia(media).addListener(computeDownloadProgress);
-});
-
-/**
- * Updates the download to reflect new data.
- * @param {BackendDownloadObject} download A backend download object
- */
-Download.prototype.update = function(download) {
- this.id_ = download.id;
- this.filePath_ = download.file_path;
- this.fileUrl_ = download.file_url;
- this.fileName_ = download.file_name;
- this.url_ = download.url;
- this.state_ = download.state;
- this.fileExternallyRemoved_ = download.file_externally_removed;
- this.dangerType_ = download.danger_type;
- this.lastReasonDescription_ = download.last_reason_text;
- this.byExtensionId_ = download.by_ext_id;
- this.byExtensionName_ = download.by_ext_name;
-
- this.since_ = download.since_string;
- this.date_ = download.date_string;
-
- // See DownloadItem::PercentComplete
- this.percent_ = Math.max(download.percent, 0);
- this.progressStatusText_ = download.progress_status_text;
- this.received_ = download.received;
-
- if (this.state_ == Download.States.DANGEROUS) {
- this.updateDangerousFile();
- } else {
- downloads.scheduleIconLoad(this.nodeImg_,
- 'chrome://fileicon/' +
- encodeURIComponent(this.filePath_) +
- '?scale=' + window.devicePixelRatio + 'x');
-
- if (this.state_ == Download.States.COMPLETE &&
- !this.fileExternallyRemoved_) {
- this.nodeFileLink_.textContent = this.fileName_;
- this.nodeFileLink_.href = this.fileUrl_;
- this.nodeFileLink_.oncontextmenu = null;
- } else if (this.nodeFileName_.textContent != this.fileName_) {
- this.nodeFileName_.textContent = this.fileName_;
- }
- if (this.state_ == Download.States.INTERRUPTED) {
- this.nodeFileName_.classList.add('interrupted');
- } else if (this.nodeFileName_.classList.contains('interrupted')) {
- this.nodeFileName_.classList.remove('interrupted');
- }
-
- showInline(this.nodeFileLink_,
- this.state_ == Download.States.COMPLETE &&
- !this.fileExternallyRemoved_);
- // nodeFileName_ has to be inline-block to avoid the 'interaction' with
- // nodeStatus_. If both are inline, it appears that their text contents
- // are merged before the bidi algorithm is applied leading to an
- // undesirable reordering. http://crbug.com/13216
- showInlineBlock(this.nodeFileName_,
- this.state_ != Download.States.COMPLETE ||
- this.fileExternallyRemoved_);
-
- if (this.state_ == Download.States.IN_PROGRESS) {
- this.nodeProgressForeground_.style.display = 'block';
- this.nodeProgressBackground_.style.display = 'block';
- this.nodeProgressForeground_.width = Download.Progress.width;
- this.nodeProgressForeground_.height = Download.Progress.height;
-
- var foregroundImage = (window.devicePixelRatio < 2) ?
- downloads.progressForeground1_ : downloads.progressForeground2_;
-
- // Draw a pie-slice for the progress.
- this.canvasProgress_.globalCompositeOperation = 'copy';
- this.canvasProgress_.drawImage(
- foregroundImage,
- 0, 0, // sx, sy
- foregroundImage.width,
- foregroundImage.height,
- 0, 0, // x, y
- Download.Progress.width, Download.Progress.height);
- this.canvasProgress_.globalCompositeOperation = 'destination-in';
- this.canvasProgress_.beginPath();
- this.canvasProgress_.moveTo(Download.Progress.centerX,
- Download.Progress.centerY);
-
- // Draw an arc CW for both RTL and LTR. http://crbug.com/13215
- this.canvasProgress_.arc(Download.Progress.centerX,
- Download.Progress.centerY,
- Download.Progress.radius,
- Download.Progress.START_ANGLE,
- Download.Progress.START_ANGLE + Math.PI * 0.02 *
- Number(this.percent_),
- false);
-
- this.canvasProgress_.lineTo(Download.Progress.centerX,
- Download.Progress.centerY);
- this.canvasProgress_.fill();
- this.canvasProgress_.closePath();
- } else if (this.nodeProgressBackground_) {
- this.nodeProgressForeground_.style.display = 'none';
- this.nodeProgressBackground_.style.display = 'none';
- }
-
- if (this.controlShow_) {
- showInline(this.controlShow_,
- this.state_ == Download.States.COMPLETE &&
- !this.fileExternallyRemoved_);
- }
- showInline(this.controlRetry_, download.retry);
- this.controlRetry_.href = this.url_;
- showInline(this.controlPause_, this.state_ == Download.States.IN_PROGRESS);
- showInline(this.controlResume_, download.resume);
- var showCancel = this.state_ == Download.States.IN_PROGRESS ||
- this.state_ == Download.States.PAUSED;
- showInline(this.controlCancel_, showCancel);
- showInline(this.controlRemove_, !showCancel);
-
- if (this.byExtensionId_ && this.byExtensionName_) {
- // Format 'control_by_extension' with a link instead of plain text by
- // splitting the formatted string into pieces.
- var slug = 'XXXXX';
- var formatted = loadTimeData.getStringF('control_by_extension', slug);
- var slugIndex = formatted.indexOf(slug);
- this.controlByExtension_.textContent = formatted.substr(0, slugIndex);
- this.controlByExtensionLink_ = document.createElement('a');
- this.controlByExtensionLink_.href =
- 'chrome://extensions#' + this.byExtensionId_;
- this.controlByExtensionLink_.textContent = this.byExtensionName_;
- this.controlByExtension_.appendChild(this.controlByExtensionLink_);
- if (slugIndex < (formatted.length - slug.length))
- this.controlByExtension_.appendChild(document.createTextNode(
- formatted.substr(slugIndex + 1)));
- }
-
- this.nodeSince_.textContent = this.since_;
- this.nodeDate_.textContent = this.date_;
- // Don't unnecessarily update the url, as doing so will remove any
- // text selection the user has started (http://crbug.com/44982).
- if (this.nodeURL_.textContent != this.url_) {
- this.nodeURL_.textContent = this.url_;
- this.nodeURL_.href = this.url_;
- }
- this.nodeStatus_.textContent = this.getStatusText_();
-
- this.danger_.style.display = 'none';
- this.safe_.style.display = 'block';
- }
-};
-
-/**
- * Decorates the icons, strings, and buttons for a download to reflect the
- * danger level of a file. Dangerous & malicious files are treated differently.
- */
-Download.prototype.updateDangerousFile = function() {
- switch (this.dangerType_) {
- case Download.DangerType.DANGEROUS_FILE: {
- this.dangerDesc_.textContent = loadTimeData.getStringF(
- 'danger_file_desc', this.fileName_);
- break;
- }
- case Download.DangerType.DANGEROUS_URL: {
- this.dangerDesc_.textContent = loadTimeData.getString('danger_url_desc');
- break;
- }
- case Download.DangerType.DANGEROUS_CONTENT: // Fall through.
- case Download.DangerType.DANGEROUS_HOST: {
- this.dangerDesc_.textContent = loadTimeData.getStringF(
- 'danger_content_desc', this.fileName_);
- break;
- }
- case Download.DangerType.UNCOMMON_CONTENT: {
- this.dangerDesc_.textContent = loadTimeData.getStringF(
- 'danger_uncommon_desc', this.fileName_);
- break;
- }
- case Download.DangerType.POTENTIALLY_UNWANTED: {
- this.dangerDesc_.textContent = loadTimeData.getStringF(
- 'danger_settings_desc', this.fileName_);
- break;
- }
- }
-
- if (this.dangerType_ == Download.DangerType.DANGEROUS_FILE) {
- downloads.scheduleIconLoad(
- this.dangerNodeImg_,
- 'chrome://theme/IDR_WARNING?scale=' + window.devicePixelRatio + 'x');
- } else {
- downloads.scheduleIconLoad(
- this.dangerNodeImg_,
- 'chrome://theme/IDR_SAFEBROWSING_WARNING?scale=' +
- window.devicePixelRatio + 'x');
- this.dangerDesc_.className = 'malware-description';
- }
-
- if (this.dangerType_ == Download.DangerType.DANGEROUS_CONTENT ||
- this.dangerType_ == Download.DangerType.DANGEROUS_HOST ||
- this.dangerType_ == Download.DangerType.DANGEROUS_URL ||
- this.dangerType_ == Download.DangerType.POTENTIALLY_UNWANTED) {
- this.malwareNodeControls_.style.display = 'block';
- this.dangerDiscard_.style.display = 'none';
- this.dangerSave_.style.display = 'none';
- } else {
- this.malwareNodeControls_.style.display = 'none';
- this.dangerDiscard_.style.display = 'inline';
- this.dangerSave_.style.display = 'inline';
- }
-
- this.danger_.style.display = 'block';
- this.safe_.style.display = 'none';
-};
-
-/**
- * Removes applicable bits from the DOM in preparation for deletion.
- */
-Download.prototype.clear = function() {
- this.safe_.ondragstart = null;
- this.nodeFileLink_.onclick = null;
- if (this.controlShow_) {
- this.controlShow_.onclick = null;
- }
- this.controlCancel_.onclick = null;
- this.controlPause_.onclick = null;
- this.controlResume_.onclick = null;
- this.dangerDiscard_.onclick = null;
- this.dangerSave_.onclick = null;
- this.malwareDiscard_.onclick = null;
- this.malwareSave_.onclick = null;
-
- this.node.innerHTML = '';
-};
-
-/**
- * @private
- * @return {string} User-visible status update text.
- */
-Download.prototype.getStatusText_ = function() {
- switch (this.state_) {
- case Download.States.IN_PROGRESS:
- return this.progressStatusText_;
- case Download.States.CANCELLED:
- return loadTimeData.getString('status_cancelled');
- case Download.States.PAUSED:
- return loadTimeData.getString('status_paused');
- case Download.States.DANGEROUS:
- // danger_url_desc is also used by DANGEROUS_CONTENT.
- var desc = this.dangerType_ == Download.DangerType.DANGEROUS_FILE ?
- 'danger_file_desc' : 'danger_url_desc';
- return loadTimeData.getString(desc);
- case Download.States.INTERRUPTED:
- return this.lastReasonDescription_;
- case Download.States.COMPLETE:
- return this.fileExternallyRemoved_ ?
- loadTimeData.getString('status_removed') : '';
- }
- assertNotReached();
- return '';
-};
-
-/**
- * Tells the backend to initiate a drag, allowing users to drag
- * files from the download page and have them appear as native file
- * drags.
- * @return {boolean} Returns false to prevent the default action.
- * @private
- */
-Download.prototype.drag_ = function() {
- chrome.send('drag', [this.id_.toString()]);
- return false;
-};
-
-/**
- * Tells the backend to open this file.
- * @return {boolean} Returns false to prevent the default action.
- * @private
- */
-Download.prototype.openFile_ = function() {
- chrome.send('openFile', [this.id_.toString()]);
- return false;
-};
-
-/**
- * Tells the backend that the user chose to save a dangerous file.
- * @return {boolean} Returns false to prevent the default action.
- * @private
- */
-Download.prototype.saveDangerous_ = function() {
- chrome.send('saveDangerous', [this.id_.toString()]);
- return false;
-};
-
-/**
- * Tells the backend that the user chose to discard a dangerous file.
- * @return {boolean} Returns false to prevent the default action.
- * @private
- */
-Download.prototype.discardDangerous_ = function() {
- chrome.send('discardDangerous', [this.id_.toString()]);
- downloads.remove(this.id_);
- return false;
-};
-
-/**
- * Tells the backend to show the file in explorer.
- * @return {boolean} Returns false to prevent the default action.
- * @private
- */
-Download.prototype.show_ = function() {
- chrome.send('show', [this.id_.toString()]);
- return false;
-};
-
-/**
- * Tells the backend to pause this download.
- * @return {boolean} Returns false to prevent the default action.
- * @private
- */
-Download.prototype.pause_ = function() {
- chrome.send('pause', [this.id_.toString()]);
- return false;
-};
-
-/**
- * Tells the backend to resume this download.
- * @return {boolean} Returns false to prevent the default action.
- * @private
- */
-Download.prototype.resume_ = function() {
- chrome.send('resume', [this.id_.toString()]);
- return false;
-};
-
-/**
- * Tells the backend to remove this download from history and download shelf.
- * @return {boolean} Returns false to prevent the default action.
- * @private
- */
- Download.prototype.remove_ = function() {
- if (loadTimeData.getBoolean('allow_deleting_history')) {
- chrome.send('remove', [this.id_.toString()]);
- }
- return false;
-};
-
-/**
- * Tells the backend to cancel this download.
- * @return {boolean} Returns false to prevent the default action.
- * @private
- */
-Download.prototype.cancel_ = function() {
- chrome.send('cancel', [this.id_.toString()]);
- return false;
-};
-
-///////////////////////////////////////////////////////////////////////////////
-// Page:
-var downloads, resultsTimeout;
-
-// TODO(benjhayden): Rename Downloads to DownloadManager, downloads to
-// downloadManager or theDownloadManager or DownloadManager.get() to prevent
-// confusing Downloads with Download.
-
-/**
- * The FIFO array that stores updates of download files to be appeared
- * on the download page. It is guaranteed that the updates in this array
- * are reflected to the download page in a FIFO order.
-*/
-var fifoResults;
-
-function load() {
- chrome.send('onPageLoaded');
- fifoResults = [];
- downloads = new Downloads();
- $('term').focus();
- setSearch('');
-
- var clearAllHolder = $('clear-all-holder');
- var clearAllElement;
- if (loadTimeData.getBoolean('allow_deleting_history')) {
- clearAllElement = createActionLink(
- clearAll, loadTimeData.getString('clear_all'));
- clearAllElement.classList.add('clear-all-link');
- clearAllHolder.classList.remove('disabled-link');
- } else {
- clearAllElement = document.createTextNode(
- loadTimeData.getString('clear_all'));
- clearAllHolder.classList.add('disabled-link');
- }
- if (!loadTimeData.getBoolean('show_delete_history'))
- clearAllHolder.hidden = true;
-
- clearAllHolder.appendChild(clearAllElement);
-
- $('open-downloads-folder').onclick = function() {
- chrome.send('openDownloadsFolder');
- };
-
- $('term').onsearch = function(e) {
- setSearch($('term').value);
- };
-}
-
-function setSearch(searchText) {
- fifoResults.length = 0;
- downloads.setSearchText(searchText);
- searchText = searchText.toString().match(/(?:[^\s"]+|"[^"]*")+/g);
- if (searchText) {
- searchText = searchText.map(function(term) {
- // strip quotes
- return (term.match(/\s/) &&
- term[0].match(/["']/) &&
- term[term.length - 1] == term[0]) ?
- term.substr(1, term.length - 2) : term;
- });
- } else {
- searchText = [];
- }
- chrome.send('getDownloads', searchText);
-}
-
-function clearAll() {
- if (!loadTimeData.getBoolean('allow_deleting_history'))
- return;
-
- fifoResults.length = 0;
- downloads.clear();
- downloads.setSearchText('');
- chrome.send('clearAll');
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// Chrome callbacks:
-/**
- * Our history system calls this function with results from searches or when
- * downloads are added or removed.
- * @param {Array.<Object>} results List of updates.
- */
-function downloadsList(results) {
- if (downloads && downloads.isUpdateNeeded(results)) {
- if (resultsTimeout)
- clearTimeout(resultsTimeout);
- fifoResults.length = 0;
- downloads.clear();
- downloadUpdated(results);
- }
- downloads.updateSummary();
-}
-
-/**
- * When a download is updated (progress, state change), this is called.
- * @param {Array.<Object>} results List of updates for the download process.
- */
-function downloadUpdated(results) {
- // Sometimes this can get called too early.
- if (!downloads)
- return;
-
- fifoResults = fifoResults.concat(results);
- tryDownloadUpdatedPeriodically();
-}
-
-/**
- * Try to reflect as much updates as possible within 50ms.
- * This function is scheduled again and again until all updates are reflected.
- */
-function tryDownloadUpdatedPeriodically() {
- var start = Date.now();
- while (fifoResults.length) {
- var result = fifoResults.shift();
- downloads.updated(result);
- // Do as much as we can in 50ms.
- if (Date.now() - start > 50) {
- clearTimeout(resultsTimeout);
- resultsTimeout = setTimeout(tryDownloadUpdatedPeriodically, 5);
- break;
- }
- }
-}
-
-// Add handlers to HTML elements.
-window.addEventListener('DOMContentLoaded', load);
diff --git a/chromium/chrome/browser/resources/downloads/externs.js b/chromium/chrome/browser/resources/downloads/externs.js
new file mode 100644
index 00000000000..4b94b85104b
--- /dev/null
+++ b/chromium/chrome/browser/resources/downloads/externs.js
@@ -0,0 +1,37 @@
+// Copyright 2015 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 objects sent from C++ to JS for chrome://downloads.
+ * @externs
+ */
+
+var downloads = {};
+
+/**
+ * The type of the download object. The definition is based on
+ * chrome/browser/ui/webui/downloads_dom_handler.cc:CreateDownloadItemValue()
+ * @typedef {{by_ext_id: (string|undefined),
+ * by_ext_name: (string|undefined),
+ * danger_type: (string|undefined),
+ * date_string: string,
+ * file_externally_removed: boolean,
+ * file_name: string,
+ * file_path: string,
+ * file_url: string,
+ * id: string,
+ * last_reason_text: (string|undefined),
+ * otr: boolean,
+ * percent: (number|undefined),
+ * progress_status_text: (string|undefined),
+ * received: (number|undefined),
+ * resume: boolean,
+ * retry: boolean,
+ * since_string: string,
+ * started: number,
+ * state: string,
+ * total: number,
+ * url: string}}
+ */
+downloads.Data;
diff --git a/chromium/chrome/browser/resources/downloads/focus_row.js b/chromium/chrome/browser/resources/downloads/focus_row.js
new file mode 100644
index 00000000000..088098f6804
--- /dev/null
+++ b/chromium/chrome/browser/resources/downloads/focus_row.js
@@ -0,0 +1,85 @@
+// Copyright 2015 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.
+
+cr.define('downloads', function() {
+ /**
+ * Provides an implementation for a single column grid.
+ * @constructor
+ * @extends {cr.ui.FocusRow}
+ */
+ function FocusRow() {}
+
+ /**
+ * Decorates |focusRow| so that it can be treated as a FocusRow.
+ * @param {Element} focusRow The element that has all the columns represented
+ * by |itemView|.
+ * @param {!downloads.ItemView} itemView The item view this row cares about.
+ * @param {Node} boundary Focus events are ignored outside of this node.
+ */
+ FocusRow.decorate = function(focusRow, itemView, boundary) {
+ focusRow.__proto__ = FocusRow.prototype;
+ focusRow.decorate(boundary);
+ focusRow.addFocusableElements_();
+ };
+
+ /**
+ * Determines if element should be focusable.
+ * @param {Element} element
+ * @return {boolean}
+ */
+ FocusRow.shouldFocus = function(element) {
+ if (!element)
+ return false;
+
+ // Hidden elements are not focusable.
+ var style = window.getComputedStyle(element);
+ if (style.visibility == 'hidden' || style.display == 'none')
+ return false;
+
+ // Verify all ancestors are focusable.
+ return !element.parentElement ||
+ FocusRow.shouldFocus(element.parentElement);
+ };
+
+ FocusRow.prototype = {
+ __proto__: cr.ui.FocusRow.prototype,
+
+ /** @override */
+ getEquivalentElement: function(element) {
+ if (this.focusableElements.indexOf(element) > -1)
+ return assert(element);
+
+ // All elements default to another element with the same type.
+ var columnType = element.getAttribute('column-type');
+ var equivalent = this.querySelector('[column-type=' + columnType + ']');
+
+ if (this.focusableElements.indexOf(equivalent) < 0) {
+ equivalent = null;
+ var equivalentTypes =
+ ['show', 'retry', 'pause', 'resume', 'remove', 'cancel'];
+ if (equivalentTypes.indexOf(columnType) != -1) {
+ var allTypes = equivalentTypes.map(function(type) {
+ return '[column-type=' + type + ']:not([hidden])';
+ }).join(', ');
+ equivalent = this.querySelector(allTypes);
+ }
+ }
+
+ // Return the first focusable element if no equivalent element is found.
+ return assert(equivalent || this.focusableElements[0]);
+ },
+
+ /** @private */
+ addFocusableElements_: function() {
+ var possiblyFocusableElements = this.querySelectorAll('[column-type]');
+ for (var i = 0; i < possiblyFocusableElements.length; ++i) {
+ var possiblyFocusableElement = possiblyFocusableElements[i];
+ if (FocusRow.shouldFocus(possiblyFocusableElement))
+ this.addFocusableElement(possiblyFocusableElement);
+ }
+ },
+ };
+
+ return {FocusRow: FocusRow};
+});
diff --git a/chromium/chrome/browser/resources/downloads/item.js b/chromium/chrome/browser/resources/downloads/item.js
new file mode 100644
index 00000000000..348be58a8af
--- /dev/null
+++ b/chromium/chrome/browser/resources/downloads/item.js
@@ -0,0 +1,58 @@
+// Copyright 2015 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.
+
+cr.define('downloads', function() {
+ /** @constructor */
+ function Item() {}
+
+ /**
+ * The states a download can be in. These correspond to states defined in
+ * DownloadsDOMHandler::CreateDownloadItemValue
+ * @enum {string}
+ */
+ Item.States = {
+ IN_PROGRESS: 'IN_PROGRESS',
+ CANCELLED: 'CANCELLED',
+ COMPLETE: 'COMPLETE',
+ PAUSED: 'PAUSED',
+ DANGEROUS: 'DANGEROUS',
+ INTERRUPTED: 'INTERRUPTED',
+ };
+
+ /**
+ * Explains why a download is in DANGEROUS state.
+ * @enum {string}
+ */
+ Item.DangerType = {
+ NOT_DANGEROUS: 'NOT_DANGEROUS',
+ DANGEROUS_FILE: 'DANGEROUS_FILE',
+ DANGEROUS_URL: 'DANGEROUS_URL',
+ DANGEROUS_CONTENT: 'DANGEROUS_CONTENT',
+ UNCOMMON_CONTENT: 'UNCOMMON_CONTENT',
+ DANGEROUS_HOST: 'DANGEROUS_HOST',
+ POTENTIALLY_UNWANTED: 'POTENTIALLY_UNWANTED',
+ };
+
+ Item.prototype = {
+ /** @type {downloads.ItemView} */
+ view: null,
+
+ /**
+ * @param {!downloads.Data} data Info about the download.
+ */
+ render: function(data) {
+ this.view = this.view || new downloads.ItemView;
+ this.view.update(data);
+ },
+
+ unrender: function() {
+ if (this.view) {
+ this.view.destroy();
+ this.view = null;
+ }
+ },
+ };
+
+ return {Item: Item};
+});
diff --git a/chromium/chrome/browser/resources/downloads/item_view.js b/chromium/chrome/browser/resources/downloads/item_view.js
new file mode 100644
index 00000000000..bf2a09ad609
--- /dev/null
+++ b/chromium/chrome/browser/resources/downloads/item_view.js
@@ -0,0 +1,431 @@
+// Copyright 2015 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.
+
+cr.define('downloads', function() {
+ /** @const */ var Item = downloads.Item;
+
+ /**
+ * Creates and updates the DOM representation for a download.
+ * @constructor
+ */
+ function ItemView() {
+ this.node = $('templates').querySelector('.download').cloneNode(true);
+
+ this.safe_ = this.queryRequired_('.safe');
+ this.since_ = this.queryRequired_('.since');
+ this.dateContainer = this.queryRequired_('.date-container');
+ this.date_ = this.queryRequired_('.date');
+ this.save_ = this.queryRequired_('.save');
+ this.backgroundProgress_ = this.queryRequired_('.progress.background');
+ this.foregroundProgress_ = /** @type !HTMLCanvasElement */(
+ this.queryRequired_('canvas.progress'));
+ this.safeImg_ = /** @type !HTMLImageElement */(
+ this.queryRequired_('.safe img'));
+ this.fileName_ = this.queryRequired_('span.name');
+ this.fileLink_ = this.queryRequired_('[is="action-link"].name');
+ this.status_ = this.queryRequired_('.status');
+ this.srcUrl_ = this.queryRequired_('.src-url');
+ this.show_ = this.queryRequired_('.show');
+ this.retry_ = this.queryRequired_('.retry');
+ this.pause_ = this.queryRequired_('.pause');
+ this.resume_ = this.queryRequired_('.resume');
+ this.safeRemove_ = this.queryRequired_('.safe .remove');
+ this.cancel_ = this.queryRequired_('.cancel');
+ this.controlledBy_ = this.queryRequired_('.controlled-by');
+
+ this.dangerous_ = this.queryRequired_('.dangerous');
+ this.dangerImg_ = /** @type {!HTMLImageElement} */(
+ this.queryRequired_('.dangerous img'));
+ this.description_ = this.queryRequired_('.description');
+ this.malwareControls_ = this.queryRequired_('.dangerous .controls');
+ this.restore_ = this.queryRequired_('.restore');
+ this.dangerRemove_ = this.queryRequired_('.dangerous .remove');
+ this.save_ = this.queryRequired_('.save');
+ this.discard_ = this.queryRequired_('.discard');
+
+ // Event handlers (bound once on creation).
+ this.safe_.ondragstart = this.onSafeDragstart_.bind(this);
+ this.fileLink_.onclick = this.onFileLinkClick_.bind(this);
+ this.show_.onclick = this.onShowClick_.bind(this);
+ this.pause_.onclick = this.onPauseClick_.bind(this);
+ this.resume_.onclick = this.onResumeClick_.bind(this);
+ this.safeRemove_.onclick = this.onSafeRemoveClick_.bind(this);
+ this.cancel_.onclick = this.onCancelClick_.bind(this);
+ this.restore_.onclick = this.onRestoreClick_.bind(this);
+ this.save_.onclick = this.onSaveClick_.bind(this);
+ this.dangerRemove_.onclick = this.onDangerRemoveClick_.bind(this);
+ this.discard_.onclick = this.onDiscardClick_.bind(this);
+ }
+
+ /** Progress meter constants. */
+ ItemView.Progress = {
+ /** @const {number} */
+ START_ANGLE: -0.5 * Math.PI,
+ /** @const {number} */
+ SIDE: 48,
+ };
+
+ /** @const {number} */
+ ItemView.Progress.HALF = ItemView.Progress.SIDE / 2;
+
+ ItemView.computeDownloadProgress = function() {
+ /**
+ * @param {number} a Some float.
+ * @param {number} b Some float.
+ * @param {number=} opt_pct Percent of min(a,b).
+ * @return {boolean} true if a is within opt_pct percent of b.
+ */
+ function floatEq(a, b, opt_pct) {
+ return Math.abs(a - b) < (Math.min(a, b) * (opt_pct || 1.0) / 100.0);
+ }
+
+ if (floatEq(ItemView.Progress.scale, window.devicePixelRatio)) {
+ // Zooming in or out multiple times then typing Ctrl+0 resets the zoom
+ // level directly to 1x, which fires the matchMedia event multiple times.
+ return;
+ }
+ var Progress = ItemView.Progress;
+ Progress.scale = window.devicePixelRatio;
+ Progress.width = Progress.SIDE * Progress.scale;
+ Progress.height = Progress.SIDE * Progress.scale;
+ Progress.radius = Progress.HALF * Progress.scale;
+ Progress.centerX = Progress.HALF * Progress.scale;
+ Progress.centerY = Progress.HALF * Progress.scale;
+ };
+ ItemView.computeDownloadProgress();
+
+ // Listens for when device-pixel-ratio changes between any zoom level.
+ [0.3, 0.4, 0.6, 0.7, 0.8, 0.95, 1.05, 1.2, 1.4, 1.6, 1.9, 2.2, 2.7, 3.5, 4.5].
+ forEach(function(scale) {
+ var media = '(-webkit-min-device-pixel-ratio:' + scale + ')';
+ window.matchMedia(media).addListener(ItemView.computeDownloadProgress);
+ });
+
+ /**
+ * @return {!HTMLImageElement} The correct <img> to show when an item is
+ * progressing in the foreground.
+ */
+ ItemView.getForegroundProgressImage = function() {
+ var x = window.devicePixelRatio >= 2 ? '2x' : '1x';
+ ItemView.foregroundImages_ = ItemView.foregroundImages_ || {};
+ if (!ItemView.foregroundImages_[x]) {
+ ItemView.foregroundImages_[x] = new Image;
+ var IMAGE_URL = 'chrome://theme/IDR_DOWNLOAD_PROGRESS_FOREGROUND_32';
+ ItemView.foregroundImages_[x].src = IMAGE_URL + '@' + x;
+ }
+ return ItemView.foregroundImages_[x];
+ };
+
+ /** @private {Array<{img: HTMLImageElement, url: string}>} */
+ ItemView.iconsToLoad_ = [];
+
+ /**
+ * Load the provided |url| into |img.src| after appending ?scale=.
+ * @param {!HTMLImageElement} img An <img> to show the loaded image in.
+ * @param {string} url A remote image URL to load.
+ */
+ ItemView.loadScaledIcon = function(img, url) {
+ var scale = '?scale=' + window.devicePixelRatio + 'x';
+ ItemView.iconsToLoad_.push({img: img, url: url + scale});
+ ItemView.loadNextIcon_();
+ };
+
+ /** @private */
+ ItemView.loadNextIcon_ = function() {
+ if (ItemView.isIconLoading_)
+ return;
+
+ ItemView.isIconLoading_ = true;
+
+ while (ItemView.iconsToLoad_.length) {
+ var request = ItemView.iconsToLoad_.shift();
+ var img = request.img;
+
+ if (img.src == request.url)
+ continue;
+
+ img.onabort = img.onerror = img.onload = function() {
+ ItemView.isIconLoading_ = false;
+ ItemView.loadNextIcon_();
+ };
+
+ img.src = request.url;
+ return;
+ }
+
+ // If we reached here, there's no more work to do.
+ ItemView.isIconLoading_ = false;
+ };
+
+ ItemView.prototype = {
+ /** @param {!downloads.Data} data */
+ update: function(data) {
+ assert(!this.id_ || data.id == this.id_);
+ this.id_ = data.id; // This is the only thing saved from |data|.
+
+ this.node.classList.toggle('otr', data.otr);
+
+ var dangerText = this.getDangerText_(data);
+ this.dangerous_.hidden = !dangerText;
+ this.safe_.hidden = !!dangerText;
+
+ this.ensureTextIs_(this.since_, data.since_string);
+ this.ensureTextIs_(this.date_, data.date_string);
+
+ if (dangerText) {
+ this.ensureTextIs_(this.description_, dangerText);
+
+ var dangerousFile = data.danger_type == Item.DangerType.DANGEROUS_FILE;
+ this.description_.classList.toggle('malware', !dangerousFile);
+
+ var idr = dangerousFile ? 'IDR_WARNING' : 'IDR_SAFEBROWSING_WARNING';
+ ItemView.loadScaledIcon(this.dangerImg_, 'chrome://theme/' + idr);
+
+ var showMalwareControls =
+ data.danger_type == Item.DangerType.DANGEROUS_CONTENT ||
+ data.danger_type == Item.DangerType.DANGEROUS_HOST ||
+ data.danger_type == Item.DangerType.DANGEROUS_URL ||
+ data.danger_type == Item.DangerType.POTENTIALLY_UNWANTED;
+
+ this.malwareControls_.hidden = !showMalwareControls;
+ this.discard_.hidden = showMalwareControls;
+ this.save_.hidden = showMalwareControls;
+ } else {
+ var path = encodeURIComponent(data.file_path);
+ ItemView.loadScaledIcon(this.safeImg_, 'chrome://fileicon/' + path);
+
+ /** @const */ var isInProgress = data.state == Item.States.IN_PROGRESS;
+ this.node.classList.toggle('in-progress', isInProgress);
+
+ /** @const */ var completelyOnDisk =
+ data.state == Item.States.COMPLETE && !data.file_externally_removed;
+
+ this.fileLink_.href = data.url;
+ this.ensureTextIs_(this.fileLink_, data.file_name);
+ this.fileLink_.hidden = !completelyOnDisk;
+
+ /** @const */ var isInterrupted = data.state == Item.States.INTERRUPTED;
+ this.fileName_.classList.toggle('interrupted', isInterrupted);
+ this.ensureTextIs_(this.fileName_, data.file_name);
+ this.fileName_.hidden = completelyOnDisk;
+
+ this.show_.hidden = !completelyOnDisk;
+
+ this.retry_.href = data.url;
+ this.retry_.hidden = !data.retry;
+
+ this.pause_.hidden = !isInProgress;
+
+ this.resume_.hidden = !data.resume;
+
+ /** @const */ var isPaused = data.state == Item.States.PAUSED;
+ /** @const */ var showCancel = isPaused || isInProgress;
+ this.cancel_.hidden = !showCancel;
+
+ this.safeRemove_.hidden = showCancel ||
+ !loadTimeData.getBoolean('allow_deleting_history');
+
+ /** @const */ var controlledByExtension = data.by_ext_id &&
+ data.by_ext_name;
+ this.controlledBy_.hidden = !controlledByExtension;
+ if (controlledByExtension) {
+ var link = this.controlledBy_.querySelector('a');
+ link.href = 'chrome://extensions#' + data.by_ext_id;
+ link.setAttribute('column-type', 'controlled-by');
+ link.textContent = data.by_ext_name;
+ }
+
+ this.ensureTextIs_(this.srcUrl_, data.url);
+ this.srcUrl_.href = data.url;
+ this.ensureTextIs_(this.status_, this.getStatusText_(data));
+
+ this.foregroundProgress_.hidden = !isInProgress;
+ this.backgroundProgress_.hidden = !isInProgress;
+
+ if (isInProgress) {
+ this.foregroundProgress_.width = ItemView.Progress.width;
+ this.foregroundProgress_.height = ItemView.Progress.height;
+
+ if (!this.progressContext_) {
+ /** @private */
+ this.progressContext_ = /** @type !CanvasRenderingContext2D */(
+ this.foregroundProgress_.getContext('2d'));
+ }
+
+ var foregroundImage = ItemView.getForegroundProgressImage();
+
+ // Draw a pie-slice for the progress.
+ this.progressContext_.globalCompositeOperation = 'copy';
+ this.progressContext_.drawImage(
+ foregroundImage,
+ 0, 0, // sx, sy
+ foregroundImage.width,
+ foregroundImage.height,
+ 0, 0, // x, y
+ ItemView.Progress.width, ItemView.Progress.height);
+
+ this.progressContext_.globalCompositeOperation = 'destination-in';
+ this.progressContext_.beginPath();
+ this.progressContext_.moveTo(ItemView.Progress.centerX,
+ ItemView.Progress.centerY);
+
+ // Draw an arc CW for both RTL and LTR. http://crbug.com/13215
+ this.progressContext_.arc(
+ ItemView.Progress.centerX,
+ ItemView.Progress.centerY,
+ ItemView.Progress.radius,
+ ItemView.Progress.START_ANGLE,
+ ItemView.Progress.START_ANGLE + Math.PI * 0.02 * data.percent,
+ false);
+
+ this.progressContext_.lineTo(ItemView.Progress.centerX,
+ ItemView.Progress.centerY);
+ this.progressContext_.fill();
+ this.progressContext_.closePath();
+ }
+ }
+ },
+
+ destroy: function() {
+ if (this.node.parentNode)
+ this.node.parentNode.removeChild(this.node);
+ },
+
+ /**
+ * @param {string} selector A CSS selector (e.g. '.class-name').
+ * @return {!Element} The element found by querying for |selector|.
+ * @private
+ */
+ queryRequired_: function(selector) {
+ return assert(this.node.querySelector(selector));
+ },
+
+ /**
+ * Overwrite |el|'s textContent if it differs from |text|.
+ * @param {!Element} el
+ * @param {string} text
+ * @private
+ */
+ ensureTextIs_: function(el, text) {
+ if (el.textContent != text)
+ el.textContent = text;
+ },
+
+ /**
+ * @param {!downloads.Data} data
+ * @return {string} Text describing the danger of a download. Empty if not
+ * dangerous.
+ */
+ getDangerText_: function(data) {
+ switch (data.danger_type) {
+ case Item.DangerType.DANGEROUS_FILE:
+ return loadTimeData.getStringF('danger_file_desc', data.file_name);
+ case Item.DangerType.DANGEROUS_URL:
+ return loadTimeData.getString('danger_url_desc');
+ case Item.DangerType.DANGEROUS_CONTENT: // Fall through.
+ case Item.DangerType.DANGEROUS_HOST:
+ return loadTimeData.getStringF('danger_content_desc', data.file_name);
+ case Item.DangerType.UNCOMMON_CONTENT:
+ return loadTimeData.getStringF('danger_uncommon_desc',
+ data.file_name);
+ case Item.DangerType.POTENTIALLY_UNWANTED:
+ return loadTimeData.getStringF('danger_settings_desc',
+ data.file_name);
+ default:
+ return '';
+ }
+ },
+
+ /**
+ * @param {!downloads.Data} data
+ * @return {string} User-visible status update text.
+ * @private
+ */
+ getStatusText_: function(data) {
+ switch (data.state) {
+ case Item.States.IN_PROGRESS:
+ case Item.States.PAUSED: // Fallthrough.
+ assert(typeof data.progress_status_text == 'string');
+ return data.progress_status_text;
+ case Item.States.CANCELLED:
+ return loadTimeData.getString('status_cancelled');
+ case Item.States.DANGEROUS:
+ break; // Intentionally hit assertNotReached(); at bottom.
+ case Item.States.INTERRUPTED:
+ assert(typeof data.last_reason_text == 'string');
+ return data.last_reason_text;
+ case Item.States.COMPLETE:
+ return data.file_externally_removed ?
+ loadTimeData.getString('status_removed') : '';
+ }
+ assertNotReached();
+ return '';
+ },
+
+ /**
+ * @private
+ * @param {Event} e
+ */
+ onSafeDragstart_: function(e) {
+ e.preventDefault();
+ chrome.send('drag', [this.id_]);
+ },
+
+ /**
+ * @param {Event} e
+ * @private
+ */
+ onFileLinkClick_: function(e) {
+ e.preventDefault();
+ chrome.send('openFile', [this.id_]);
+ },
+
+ /** @private */
+ onShowClick_: function() {
+ chrome.send('show', [this.id_]);
+ },
+
+ /** @private */
+ onPauseClick_: function() {
+ chrome.send('pause', [this.id_]);
+ },
+
+ /** @private */
+ onResumeClick_: function() {
+ chrome.send('resume', [this.id_]);
+ },
+
+ /** @private */
+ onSafeRemoveClick_: function() {
+ chrome.send('remove', [this.id_]);
+ },
+
+ /** @private */
+ onCancelClick_: function() {
+ chrome.send('cancel', [this.id_]);
+ },
+
+ /** @private */
+ onRestoreClick_: function() {
+ this.onSaveClick_();
+ },
+
+ /** @private */
+ onSaveClick_: function() {
+ chrome.send('saveDangerous', [this.id_]);
+ },
+
+ /** @private */
+ onDangerRemoveClick_: function() {
+ this.onDiscardClick_();
+ },
+
+ /** @private */
+ onDiscardClick_: function() {
+ chrome.send('discardDangerous', [this.id_]);
+ },
+ };
+
+ return {ItemView: ItemView};
+});
diff --git a/chromium/chrome/browser/resources/downloads/manager.js b/chromium/chrome/browser/resources/downloads/manager.js
new file mode 100644
index 00000000000..1c5ca2855fb
--- /dev/null
+++ b/chromium/chrome/browser/resources/downloads/manager.js
@@ -0,0 +1,228 @@
+// Copyright 2015 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.
+
+cr.define('downloads', function() {
+ /**
+ * Class to own and manage download items.
+ * @constructor
+ */
+ function Manager() {}
+
+ cr.addSingletonGetter(Manager);
+
+ Manager.prototype = {
+ /** @private {string} */
+ searchText_: '',
+
+ /**
+ * Sets the search text, updates related UIs, and tells the browser.
+ * @param {string} searchText Text we're searching for.
+ */
+ setSearchText: function(searchText) {
+ this.searchText_ = searchText;
+
+ $('downloads-summary-text').textContent = this.searchText_ ?
+ loadTimeData.getStringF('searchresultsfor', this.searchText_) : '';
+
+ // Split quoted terms (e.g., 'The "lazy" dog' => ['The', 'lazy', 'dog']).
+ function trim(s) { return s.trim(); }
+ chrome.send('getDownloads', searchText.split(/"([^"]*)"/).map(trim));
+ },
+
+ /**
+ * Called when all items need to be updated.
+ * @param {!Array<!downloads.Data>} list A list of new download data.
+ */
+ updateAll: function(list) {
+ var oldIdMap = this.idMap_ || {};
+
+ /** @private {!Object<!downloads.Item>} */
+ this.idMap_ = {};
+
+ /** @private {!Array<!downloads.Item>} */
+ this.items_ = [];
+
+ for (var i = 0; i < list.length; ++i) {
+ var data = list[i];
+ var id = data.id;
+
+ // Re-use old items when possible (saves work, preserves focus).
+ var item = oldIdMap[id] || new downloads.Item;
+
+ this.idMap_[id] = item; // Associated by ID for fast lookup.
+ this.items_.push(item); // Add to sorted list for order.
+
+ // Render |item| but don't actually add to the DOM yet. |this.items_|
+ // must be fully created to be able to find the right spot to insert.
+ item.render(data);
+
+ // Collapse redundant dates.
+ var prev = list[i - 1];
+ item.view.dateContainer.hidden =
+ prev && prev.date_string == data.date_string;
+
+ delete oldIdMap[id];
+ }
+
+ // Remove stale, previously rendered items from the DOM.
+ for (var id in oldIdMap) {
+ oldIdMap[id].unrender();
+ delete oldIdMap[id];
+ }
+
+ for (var i = 0; i < this.items_.length; ++i) {
+ var item = this.items_[i];
+ if (item.view.node.parentNode) // Already in the DOM; skip.
+ continue;
+
+ var before = null;
+ // Find the next rendered item after this one, and insert before it.
+ for (var j = i + 1; !before && j < this.items_.length; ++j) {
+ if (this.items_[j].view.node.parentNode)
+ before = this.items_[j].view.node;
+ }
+ // If |before| is null, |item| will just get added at the end.
+ this.node_.insertBefore(item.view.node, before);
+ }
+
+ var noDownloadsOrResults = $('no-downloads-or-results');
+ noDownloadsOrResults.textContent = loadTimeData.getString(
+ this.searchText_ ? 'no_search_results' : 'no_downloads');
+
+ var hasDownloads = this.size() > 0;
+ this.node_.hidden = !hasDownloads;
+ noDownloadsOrResults.hidden = hasDownloads;
+
+ if (loadTimeData.getBoolean('allow_deleting_history'))
+ $('clear-all').hidden = !hasDownloads || this.searchText_.length > 0;
+
+ this.rebuildFocusGrid_();
+ },
+
+ /** @param {!downloads.Data} data Info about the item to update. */
+ updateItem: function(data) {
+ var activeElement = document.activeElement;
+
+ var item = this.idMap_[data.id];
+ item.render(data);
+ var focusRow = this.decorateItem_(item);
+
+ if (focusRow.contains(activeElement) &&
+ !downloads.FocusRow.shouldFocus(activeElement)) {
+ focusRow.getEquivalentElement(activeElement).focus();
+ }
+ },
+
+ /**
+ * Rebuild the focusGrid_ using the elements that each download will have.
+ * @private
+ */
+ rebuildFocusGrid_: function() {
+ var activeElement = document.activeElement;
+
+ /** @private {!cr.ui.FocusGrid} */
+ this.focusGrid_ = this.focusGrid_ || new cr.ui.FocusGrid();
+ this.focusGrid_.destroy();
+
+ this.items_.forEach(function(item) {
+ var focusRow = this.decorateItem_(item);
+ this.focusGrid_.addRow(focusRow);
+
+ if (focusRow.contains(activeElement) &&
+ !downloads.FocusRow.shouldFocus(activeElement)) {
+ focusRow.getEquivalentElement(activeElement).focus();
+ }
+ }, this);
+ this.focusGrid_.ensureRowActive();
+ },
+
+ /**
+ * @param {!downloads.Item} item An item to decorate as a FocusRow.
+ * @return {!downloads.FocusRow} |item| decorated as a FocusRow.
+ */
+ decorateItem_: function(item) {
+ downloads.FocusRow.decorate(item.view.node, item.view, this.node_);
+ return assertInstanceof(item.view.node, downloads.FocusRow);
+ },
+
+ /** @return {number} The number of downloads shown on the page. */
+ size: function() {
+ return this.items_.length;
+ },
+
+ clearAll: function() {
+ if (loadTimeData.getBoolean('allow_deleting_history')) {
+ chrome.send('clearAll');
+ this.setSearchText('');
+ }
+ },
+
+ onLoad: function() {
+ this.node_ = $('downloads-display');
+
+ $('clear-all').onclick = function() {
+ this.clearAll();
+ }.bind(this);
+
+ $('open-downloads-folder').onclick = function() {
+ chrome.send('openDownloadsFolder');
+ };
+
+ $('term').onsearch = function(e) {
+ this.setSearchText($('term').value);
+ }.bind(this);
+
+ cr.ui.decorate('command', cr.ui.Command);
+ document.addEventListener('canExecute', this.onCanExecute_.bind(this));
+ document.addEventListener('command', this.onCommand_.bind(this));
+
+ this.setSearchText('');
+ },
+
+ /**
+ * @param {Event} e
+ * @private
+ */
+ onCanExecute_: function(e) {
+ e = /** @type {cr.ui.CanExecuteEvent} */(e);
+ e.canExecute = e.command.id != 'undo-command' ||
+ document.activeElement != $('term');
+ },
+
+ /**
+ * @param {Event} e
+ * @private
+ */
+ onCommand_: function(e) {
+ if (e.command.id == 'undo-command')
+ chrome.send('undo');
+ else if (e.command.id == 'clear-all-command')
+ this.clearAll();
+ },
+ };
+
+ Manager.updateAll = function(list) {
+ Manager.getInstance().updateAll(list);
+ };
+
+ Manager.updateItem = function(item) {
+ Manager.getInstance().updateItem(item);
+ };
+
+ Manager.setSearchText = function(searchText) {
+ Manager.getInstance().setSearchText(searchText);
+ };
+
+ Manager.onLoad = function() {
+ Manager.getInstance().onLoad();
+ };
+
+ Manager.size = function() {
+ return Manager.getInstance().size();
+ };
+
+ return {Manager: Manager};
+});
+
+window.addEventListener('DOMContentLoaded', downloads.Manager.onLoad);
diff --git a/chromium/chrome/browser/resources/easy_unlock/manifest.json b/chromium/chrome/browser/resources/easy_unlock/manifest.json
index b4e6a592928..526067b1de9 100644
--- a/chromium/chrome/browser/resources/easy_unlock/manifest.json
+++ b/chromium/chrome/browser/resources/easy_unlock/manifest.json
@@ -14,21 +14,24 @@
},
"permissions": [
- "bluetoothPrivate",
- "screenlockPrivate",
- "feedbackPrivate",
- "metricsPrivate",
- "storage",
- "identity",
- "notifications",
- "easyUnlockPrivate",
- "systemPrivate",
+ // Public APIs:
"alarms",
+ "browser",
"gcm",
+ "identity",
+ "notifications",
+ "storage",
"system.display",
+
+ // Private APIs:
+ "bluetoothPrivate",
"chromeosInfoPrivate",
- "browser",
- "preferencesPrivate"
+ "easyUnlockPrivate",
+ "feedbackPrivate",
+ "metricsPrivate",
+ "preferencesPrivate",
+ "screenlockPrivate",
+ "systemPrivate"
],
"app": {
diff --git a/chromium/chrome/browser/resources/easy_unlock/manifest_signin.json b/chromium/chrome/browser/resources/easy_unlock/manifest_signin.json
new file mode 100644
index 00000000000..61ff3a8968e
--- /dev/null
+++ b/chromium/chrome/browser/resources/easy_unlock/manifest_signin.json
@@ -0,0 +1,56 @@
+{
+ "name": "Smart Lock",
+ "description": "This app allows you to sign-in to a device when in proximity to your phone.",
+ "version": "1.1",
+ "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqOUeUl1nC6qTz6WwVUIaAJ4ukXVzgeCAumX4TZlCHFk5DLHImHLDBxakyVGaQFLS9iEQ3tDTsJLIoA+FkbWKNX7bvDW/qM89CeVNZsIZRGw898m8J78N6dJHwP9aZSI8CpoMK2KvjANpuj1tdWs1OM6v65zRUu6y4Mq876dr5AcPiuznGxl8jekagBwGu8jqMySsJxLazj/EfQ3W1E7mpyHd0Z4C1qNwJoFlUQeMjn6gfPZqa06BLU6YznzCUesiyjFK3d1vzbN54ZkVxhcA6ekwLKYLqKykBFLmIQG0gkNNePzcGXju8p34dGJgkcZw0sOXrtNaLSe1su0zfcniIwIDAQAB",
+
+ "permissions": [
+ // Public APIs:
+ "alarms",
+ "browser",
+ "gcm",
+ "identity",
+ "notifications",
+ "storage",
+ "system.display",
+
+ // Private APIs:
+ "bluetoothPrivate",
+ "chromeosInfoPrivate",
+ "easyUnlockPrivate",
+ "feedbackPrivate",
+ "metricsPrivate",
+ "preferencesPrivate",
+ "screenlockPrivate",
+ "systemPrivate"
+ ],
+
+ "app": {
+ "background": {
+ "scripts": ["easy_unlock_background.js"]
+ }
+ },
+
+ "bluetooth": {
+ "socket" : true,
+ "low_energy" : true,
+ "uuids": [
+ "704EE561-3782-405A-A14B-2D47A2DDCDDF" // Unlock UUID
+ ]
+ },
+
+ "offline_enabled": true,
+
+ "display_in_launcher": false,
+
+ "incognito": "split",
+
+ "icons": {
+ "32": "icons/easyunlock_app_icon_32.png",
+ "48": "icons/easyunlock_app_icon_48.png",
+ "64": "icons/easyunlock_app_icon_64.png",
+ "96": "icons/easyunlock_app_icon_96.png",
+ "128": "icons/easyunlock_app_icon_128.png",
+ "256": "icons/easyunlock_app_icon_256.png"
+ }
+}
diff --git a/chromium/chrome/browser/resources/extensions/chromeos/kiosk_app_list.js b/chromium/chrome/browser/resources/extensions/chromeos/kiosk_app_list.js
index 28b0b3a921f..d92784097d7 100644
--- a/chromium/chrome/browser/resources/extensions/chromeos/kiosk_app_list.js
+++ b/chromium/chrome/browser/resources/extensions/chromeos/kiosk_app_list.js
@@ -53,7 +53,7 @@ cr.define('extensions', function() {
/**
* Loads the given list of apps.
- * @param {!Array.<!Object>} apps An array of app info objects.
+ * @param {!Array<!Object>} apps An array of app info objects.
*/
setApps: function(apps) {
this.dataModel = new ArrayDataModel(apps);
diff --git a/chromium/chrome/browser/resources/extensions/chromeos/kiosk_apps.css b/chromium/chrome/browser/resources/extensions/chromeos/kiosk_apps.css
index 33c752bb100..18b0168ed94 100644
--- a/chromium/chrome/browser/resources/extensions/chromeos/kiosk_apps.css
+++ b/chromium/chrome/browser/resources/extensions/chromeos/kiosk_apps.css
@@ -26,8 +26,8 @@ list .row-delete-button {
background-color: transparent;
/* TODO(stuartmorgan): Replace with real images once they are available. */
background-image: -webkit-image-set(
- url('../../../../../ui/resources/default_100_percent/close_2.png') 1x,
- url('../../../../../ui/resources/default_200_percent/close_2.png') 2x);
+ url(../../../../../ui/resources/default_100_percent/close_2.png) 1x,
+ url(../../../../../ui/resources/default_200_percent/close_2.png) 2x);
border: none;
display: block;
height: 16px;
@@ -46,17 +46,17 @@ list .row-delete-button[disabled] {
list .row-delete-button:hover {
background-image: -webkit-image-set(
- url('../../../../../ui/resources/default_100_percent/close_2_hover.png')
+ url(../../../../../ui/resources/default_100_percent/close_2_hover.png)
1x,
- url('../../../../../ui/resources/default_200_percent/close_2_hover.png')
+ url(../../../../../ui/resources/default_200_percent/close_2_hover.png)
2x);
}
list .row-delete-button:active {
background-image: -webkit-image-set(
- url('../../../../../ui/resources/default_100_percent/close_2_pressed.png')
+ url(../../../../../ui/resources/default_100_percent/close_2_pressed.png)
1x,
- url('../../../../../ui/resources/default_200_percent/close_2_pressed.png')
+ url(../../../../../ui/resources/default_200_percent/close_2_pressed.png)
2x);
}
@@ -118,7 +118,7 @@ list .row-delete-button:active {
}
.kiosk-app-icon.spinner {
- background-image: url('chrome://resources/images/spinner.svg') !important;
+ background-image: url(chrome://resources/images/throbber.svg) !important;
}
.kiosk-app-name,
diff --git a/chromium/chrome/browser/resources/extensions/chromeos/kiosk_apps.js b/chromium/chrome/browser/resources/extensions/chromeos/kiosk_apps.js
index 30187087fff..4697bcaef6e 100644
--- a/chromium/chrome/browser/resources/extensions/chromeos/kiosk_apps.js
+++ b/chromium/chrome/browser/resources/extensions/chromeos/kiosk_apps.js
@@ -100,7 +100,7 @@ cr.define('extensions', function() {
/**
* Sets apps to be displayed in kiosk-app-list.
- * @param {!Object.<{apps: !Array.<AppDict>, disableBailout: boolean,
+ * @param {!Object<{apps: !Array<AppDict>, disableBailout: boolean,
* hasAutoLaunchApp: boolean}>} settings An object containing an array of
* app info objects and disable bailout shortcut flag.
*/
diff --git a/chromium/chrome/browser/resources/extensions/compiled_resources.gyp b/chromium/chrome/browser/resources/extensions/compiled_resources.gyp
index 5ff1fb854e8..23f5dd0f3d3 100644
--- a/chromium/chrome/browser/resources/extensions/compiled_resources.gyp
+++ b/chromium/chrome/browser/resources/extensions/compiled_resources.gyp
@@ -15,6 +15,9 @@
'../../../../ui/webui/resources/js/cr/ui.js',
'../../../../ui/webui/resources/js/cr/ui/alert_overlay.js',
'../../../../ui/webui/resources/js/cr/ui/array_data_model.js',
+ '../../../../ui/webui/resources/js/cr/ui/bubble.js',
+ '../../../../ui/webui/resources/js/cr/ui/bubble_button.js',
+ '../../../../ui/webui/resources/js/cr/ui/controlled_indicator.js',
'../../../../ui/webui/resources/js/cr/ui/drag_wrapper.js',
'../../../../ui/webui/resources/js/cr/ui/focus_manager.js',
'../../../../ui/webui/resources/js/cr/ui/focus_outline_manager.js',
@@ -23,10 +26,15 @@
'../../../../ui/webui/resources/js/cr/ui/list_selection_controller.js',
'../../../../ui/webui/resources/js/cr/ui/list_selection_model.js',
'../../../../ui/webui/resources/js/cr/ui/overlay.js',
+ '../../../../ui/webui/resources/js/event_tracker.js',
'../../../../ui/webui/resources/js/load_time_data.js',
'../../../../ui/webui/resources/js/util.js',
],
- 'externs': ['<(CLOSURE_DIR)/externs/chrome_send_externs.js'],
+ 'externs': [
+ '<(CLOSURE_DIR)/externs/chrome_extensions.js',
+ '<(CLOSURE_DIR)/externs/chrome_send_externs.js',
+ '<(CLOSURE_DIR)/externs/developer_private.js',
+ ],
},
'includes': ['../../../../third_party/closure_compiler/compile_js.gypi'],
}
diff --git a/chromium/chrome/browser/resources/extensions/extension_code.js b/chromium/chrome/browser/resources/extensions/extension_code.js
index 826625422ce..255909d6352 100644
--- a/chromium/chrome/browser/resources/extensions/extension_code.js
+++ b/chromium/chrome/browser/resources/extensions/extension_code.js
@@ -30,7 +30,7 @@ cr.define('extensions', function() {
/**
* Populate the content area of the code div with the given code. This will
* highlight the erroneous section (if any).
- * @param {ExtensionHighlight} code The 'highlight' strings represent the
+ * @param {?ExtensionHighlight} code The 'highlight' strings represent the
* three portions of the file's content to display - the portion which
* is most relevant and should be emphasized (highlight), and the parts
* both before and after this portion. The title is the error message,
@@ -43,19 +43,20 @@ cr.define('extensions', function() {
// Clear any remnant content, so we don't have multiple code listed.
this.clear();
- var sourceDiv = document.createElement('div');
- sourceDiv.classList.add('extension-code-source');
- this.appendChild(sourceDiv);
-
// If there's no code, then display an appropriate message.
if (!code ||
(!code.highlight && !code.beforeHighlight && !code.afterHighlight)) {
var span = document.createElement('span');
+ span.classList.add('extension-code-empty');
span.textContent = emptyMessage;
- sourceDiv.appendChild(span);
+ this.appendChild(span);
return;
}
+ var sourceDiv = document.createElement('div');
+ sourceDiv.classList.add('extension-code-source');
+ this.appendChild(sourceDiv);
+
var lineCount = 0;
var createSpan = function(source, isHighlighted) {
lineCount += source.split('\n').length - 1;
diff --git a/chromium/chrome/browser/resources/extensions/extension_command_list.js b/chromium/chrome/browser/resources/extensions/extension_command_list.js
index 1f92c19cfda..0a184dc8330 100644
--- a/chromium/chrome/browser/resources/extensions/extension_command_list.js
+++ b/chromium/chrome/browser/resources/extensions/extension_command_list.js
@@ -39,6 +39,7 @@ cr.define('options', function() {
/** @const */ var keyPageUp = 33;
/** @const */ var keyPeriod = 190;
/** @const */ var keyRight = 39;
+ /** @const */ var keySpace = 32;
/** @const */ var keyTab = 9;
/** @const */ var keyUp = 38;
@@ -74,6 +75,7 @@ cr.define('options', function() {
keyCode == keyPageUp ||
keyCode == keyPeriod ||
keyCode == keyRight ||
+ keyCode == keySpace ||
keyCode == keyTab ||
keyCode == keyUp ||
(keyCode >= 'A'.charCodeAt(0) && keyCode <= 'Z'.charCodeAt(0)) ||
@@ -87,62 +89,66 @@ cr.define('options', function() {
* @return {string} The keystroke as a string.
*/
function keystrokeToString(event) {
- var output = '';
+ var output = [];
if (cr.isMac && event.metaKey)
- output = 'Command+';
+ output.push('Command');
+ if (cr.isChromeOS && event.metaKey)
+ output.push('Search');
if (event.ctrlKey)
- output = 'Ctrl+';
+ output.push('Ctrl');
if (!event.ctrlKey && event.altKey)
- output += 'Alt+';
+ output.push('Alt');
if (event.shiftKey)
- output += 'Shift+';
+ output.push('Shift');
var keyCode = event.keyCode;
if (validChar(keyCode)) {
if ((keyCode >= 'A'.charCodeAt(0) && keyCode <= 'Z'.charCodeAt(0)) ||
(keyCode >= '0'.charCodeAt(0) && keyCode <= '9'.charCodeAt(0))) {
- output += String.fromCharCode('A'.charCodeAt(0) + keyCode - 65);
+ output.push(String.fromCharCode('A'.charCodeAt(0) + keyCode - 65));
} else {
switch (keyCode) {
case keyComma:
- output += 'Comma'; break;
+ output.push('Comma'); break;
case keyDel:
- output += 'Delete'; break;
+ output.push('Delete'); break;
case keyDown:
- output += 'Down'; break;
+ output.push('Down'); break;
case keyEnd:
- output += 'End'; break;
+ output.push('End'); break;
case keyHome:
- output += 'Home'; break;
+ output.push('Home'); break;
case keyIns:
- output += 'Insert'; break;
+ output.push('Insert'); break;
case keyLeft:
- output += 'Left'; break;
+ output.push('Left'); break;
case keyMediaNextTrack:
- output += 'MediaNextTrack'; break;
+ output.push('MediaNextTrack'); break;
case keyMediaPlayPause:
- output += 'MediaPlayPause'; break;
+ output.push('MediaPlayPause'); break;
case keyMediaPrevTrack:
- output += 'MediaPrevTrack'; break;
+ output.push('MediaPrevTrack'); break;
case keyMediaStop:
- output += 'MediaStop'; break;
+ output.push('MediaStop'); break;
case keyPageDown:
- output += 'PageDown'; break;
+ output.push('PageDown'); break;
case keyPageUp:
- output += 'PageUp'; break;
+ output.push('PageUp'); break;
case keyPeriod:
- output += 'Period'; break;
+ output.push('Period'); break;
case keyRight:
- output += 'Right'; break;
+ output.push('Right'); break;
+ case keySpace:
+ output.push('Space'); break;
case keyTab:
- output += 'Tab'; break;
+ output.push('Tab'); break;
case keyUp:
- output += 'Up'; break;
+ output.push('Up'); break;
}
}
}
- return output;
+ return output.join('+');
}
/**
@@ -174,6 +180,7 @@ cr.define('options', function() {
*/
function hasModifier(event, countShiftAsModifier) {
return event.ctrlKey || event.altKey || (cr.isMac && event.metaKey) ||
+ (cr.isChromeOS && event.metaKey) ||
(countShiftAsModifier && event.shiftKey);
}
@@ -205,7 +212,6 @@ cr.define('options', function() {
*/
capturingElement_: null,
- /** @override */
decorate: function() {
this.textContent = '';
@@ -437,7 +443,8 @@ cr.define('options', function() {
// you have a valid combination, we won't change it until the next
// KeyDown message arrives).
if (!this.currentKeyEvent_ || !validChar(this.currentKeyEvent_.keyCode)) {
- if (!event.ctrlKey && !event.altKey) {
+ if (!event.ctrlKey && !event.altKey ||
+ ((cr.isMac || cr.isChromeOS) && !event.metaKey)) {
// If neither Ctrl nor Alt is pressed then it is not a valid shortcut.
// That means we're back at the starting point so we should restart
// capture.
diff --git a/chromium/chrome/browser/resources/extensions/extension_commands_overlay.js b/chromium/chrome/browser/resources/extensions/extension_commands_overlay.js
index 7d2546d2f81..a96ef44780b 100644
--- a/chromium/chrome/browser/resources/extensions/extension_commands_overlay.js
+++ b/chromium/chrome/browser/resources/extensions/extension_commands_overlay.js
@@ -51,7 +51,7 @@ cr.define('extensions', function() {
/**
* Called by the dom_ui_ to re-populate the page with data representing
* the current state of extension commands.
- * @param {!{commands: Array.<{name: string, id: string, commands: ?Array}>}}
+ * @param {!{commands: Array<{name: string, id: string, commands: ?Array}>}}
* extensionsData
*/
ExtensionCommandsOverlay.returnExtensionsData = function(extensionsData) {
diff --git a/chromium/chrome/browser/resources/extensions/extension_error.css b/chromium/chrome/browser/resources/extensions/extension_error.css
index db8b9f9a3a5..d6514114bec 100644
--- a/chromium/chrome/browser/resources/extensions/extension_error.css
+++ b/chromium/chrome/browser/resources/extensions/extension_error.css
@@ -2,96 +2,108 @@
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file. */
+.extension-error-list-heading {
+ align-content: flex-start;
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ padding: 3px;
+}
+
+.extension-error-list-heading span {
+ font-weight: bold;
+}
+
.extension-error-list a {
cursor: pointer;
}
.extension-error-list-contents {
- -webkit-transition: max-height 150ms;
+ -webkit-padding-start: 0;
+ cursor: pointer;
list-style-type: none;
- overflow-y: hidden;
+ margin-bottom: 0;
+ margin-top: 0;
}
-.extension-error-list-contents.active {
- max-height: 200px;
-}
-
-.extension-error-list-contents,
-.extension-error-list-contents.deactivating {
- /* Simply toggling .active on and off doesn't transition both ways as it
- * changes the display type of some li elements. To fix this, .deactivating
- * is added while the list is closing to change only the max-height. */
- max-height: 50px;
+#no-errors-span {
+ -webkit-margin-start: 10px;
}
.extension-error-list-contents.scrollable {
overflow-y: auto;
}
-/* These next three rules hide all except for the most recent three entries in
- * the list, unless the list is active. */
-.extension-error-list-contents li {
- display: none;
-}
-.extension-error-list-contents.active li,
-.extension-error-list ul li:nth-last-child(-n + 3) {
- display: initial;
-}
-
-.extension-error-list-contents {
- -webkit-padding-start: 20px;
+.extension-error-list-contents .extension-error-metadata:hover {
+ background-color: #eee;
}
-.extension-error-list-show-more {
- text-align: center;
- width: 100%;
-}
-
-.extension-error-list-show-more button {
- width: auto;
+.extension-error-list-contents
+ .extension-error-metadata.extension-error-active {
+ background-color: rgba(0, 100, 255, 0.1);
}
.extension-error-metadata {
-webkit-padding-end: 1px;
+ -webkit-padding-start: 3px;
display: flex;
flex-direction: row;
- margin-bottom: 1px;
- margin-top: 1px;
}
.extension-error-icon {
-webkit-margin-end: 3px;
- -webkit-margin-start: 3px;
height: 15px;
- vertical-align: middle;
width: 15px;
}
.extension-error-message {
+ -webkit-margin-end: 15px;
flex: 1;
+ margin-bottom: 0;
+ margin-top: 0;
overflow: hidden;
- text-overflow: ellipsis;
- vertical-align: middle;
- white-space: nowrap;
+}
+
+.extension-error-metadata {
+ align-items: center;
+ display: flex;
+}
+
+.extension-error-metadata > .error-delete-button {
+ background: url(chrome://theme/IDR_CLOSE_DIALOG) center no-repeat;
+ height: 14px;
+ opacity: 0.6;
+ width: 14px;
+}
+
+.extension-error-metadata > .error-delete-button:hover {
+ opacity: 0.8;
+}
+
+.extension-error-metadata > .error-delete-button:active {
+ opacity: 1.0;
}
.extension-error-severity-info .extension-error-message {
color: #333;
}
-.extension-error-severity-info .extension-error-icon {
- content: url('extension_error_severity_info.png');
+.extension-error-severity-info .extension-error-icon,
+.extension-error-info-icon {
+ content: url(extension_error_severity_info.png);
}
.extension-error-severity-warning .extension-error-message {
color: rgba(250, 145, 0, 255);
}
-.extension-error-severity-warning .extension-error-icon {
- content: url('extension_error_severity_warning.png');
+.extension-error-severity-warning .extension-error-icon,
+.extension-error-warning-icon {
+ content: url(extension_error_severity_warning.png);
}
.extension-error-severity-fatal .extension-error-message {
color: rgba(200, 50, 50, 255);
}
-.extension-error-severity-fatal .extension-error-icon {
- content: url('extension_error_severity_fatal.png');
+.extension-error-severity-fatal .extension-error-icon,
+.extension-error-fatal-icon {
+ content: url(extension_error_severity_fatal.png);
}
diff --git a/chromium/chrome/browser/resources/extensions/extension_error.html b/chromium/chrome/browser/resources/extensions/extension_error.html
index 041994b241b..29615777711 100644
--- a/chromium/chrome/browser/resources/extensions/extension_error.html
+++ b/chromium/chrome/browser/resources/extensions/extension_error.html
@@ -5,13 +5,17 @@ in the LICENSE file.
-->
<div id="template-collection-extension-error" hidden>
<div class="extension-error-list">
- <ul class="extension-error-list-contents"></ul>
- <div class="extension-error-list-show-more">
- <a is="action-link" i18n-content="extensionErrorsShowMore" hidden></a>
+ <div class="extension-error-list-heading">
+ <span i18n-content="extensionErrorHeading"></span>
+ <a id="extension-error-list-clear" is="action-link"
+ i18n-content="extensionErrorClearAll"></a>
</div>
+ <ul class="extension-error-list-contents"></ul>
+ <span id="no-errors-span" i18n-content="extensionErrorNoErrors" hidden>
+ </span>
</div>
<div class="extension-error-metadata">
- <span class="extension-error-message"></span>
- <a is="action-link" class="extension-error-view-details"></a>
+ <p class="extension-error-message"></p>
+ <div class="error-delete-button"></div>
</div>
</div>
diff --git a/chromium/chrome/browser/resources/extensions/extension_error.js b/chromium/chrome/browser/resources/extensions/extension_error.js
index ef242f21f61..c9296554d9a 100644
--- a/chromium/chrome/browser/resources/extensions/extension_error.js
+++ b/chromium/chrome/browser/resources/extensions/extension_error.js
@@ -26,144 +26,356 @@ cr.define('extensions', function() {
}
/**
+ * @param {!Array<(ManifestError|RuntimeError)>} errors
+ * @param {number} id
+ * @return {number} The index of the error with |id|, or -1 if not found.
+ */
+ function findErrorById(errors, id) {
+ for (var i = 0; i < errors.length; ++i) {
+ if (errors[i].id == id)
+ return i;
+ }
+ return -1;
+ }
+
+ /**
* Creates a new ExtensionError HTMLElement; this is used to show a
* notification to the user when an error is caused by an extension.
- * @param {Object} error The error the element should represent.
+ * @param {(RuntimeError|ManifestError)} error The error the element should
+ * represent.
+ * @param {Element} boundary The boundary for the focus grid.
* @constructor
- * @extends {HTMLDivElement}
+ * @extends {cr.ui.FocusRow}
*/
- function ExtensionError(error) {
+ function ExtensionError(error, boundary) {
var div = cloneTemplate('extension-error-metadata');
div.__proto__ = ExtensionError.prototype;
- div.decorate(error);
+ div.decorateWithError_(error, boundary);
return div;
}
ExtensionError.prototype = {
- __proto__: HTMLDivElement.prototype,
+ __proto__: cr.ui.FocusRow.prototype,
+
+ /** @override */
+ getEquivalentElement: function(element) {
+ if (element.classList.contains('extension-error-metadata'))
+ return this;
+ if (element.classList.contains('error-delete-button')) {
+ return /** @type {!HTMLElement} */ (this.querySelector(
+ '.error-delete-button'));
+ }
+ assertNotReached();
+ return element;
+ },
/**
- * @param {RuntimeError} error
- * @override
+ * @param {(RuntimeError|ManifestError)} error The error the element should
+ * represent.
+ * @param {Element} boundary The boundary for the FocusGrid.
+ * @private
*/
- decorate: function(error) {
+ decorateWithError_: function(error, boundary) {
+ this.decorate(boundary);
+
+ /**
+ * The backing error.
+ * @type {(ManifestError|RuntimeError)}
+ */
+ this.error = error;
+
// Add an additional class for the severity level.
- if (error.level == 0)
- this.classList.add('extension-error-severity-info');
- else if (error.level == 1)
+ if (error.type == chrome.developerPrivate.ErrorType.RUNTIME) {
+ switch (error.severity) {
+ case chrome.developerPrivate.ErrorLevel.LOG:
+ this.classList.add('extension-error-severity-info');
+ break;
+ case chrome.developerPrivate.ErrorLevel.WARN:
+ this.classList.add('extension-error-severity-warning');
+ break;
+ case chrome.developerPrivate.ErrorLevel.ERROR:
+ this.classList.add('extension-error-severity-fatal');
+ break;
+ default:
+ assertNotReached();
+ }
+ } else {
+ // We classify manifest errors as "warnings".
this.classList.add('extension-error-severity-warning');
- else
- this.classList.add('extension-error-severity-fatal');
+ }
var iconNode = document.createElement('img');
iconNode.className = 'extension-error-icon';
+ // TODO(hcarmona): Populate alt text with a proper description since this
+ // icon conveys the severity of the error. (info, warning, fatal).
+ iconNode.alt = '';
this.insertBefore(iconNode, this.firstChild);
var messageSpan = this.querySelector('.extension-error-message');
messageSpan.textContent = error.message;
- messageSpan.title = error.message;
- var extensionUrl = 'chrome-extension://' + error.extensionId + '/';
- var viewDetailsLink = this.querySelector('.extension-error-view-details');
+ var deleteButton = this.querySelector('.error-delete-button');
+ deleteButton.addEventListener('click', function(e) {
+ this.dispatchEvent(
+ new CustomEvent('deleteExtensionError',
+ {bubbles: true, detail: this.error}));
+ }.bind(this));
- // If we cannot open the file source and there are no external frames in
- // the stack, then there are no details to display.
- if (!extensions.ExtensionErrorOverlay.canShowOverlayForError(
- error, extensionUrl)) {
- viewDetailsLink.hidden = true;
- } else {
- var stringId = extensionUrl.toLowerCase() == 'manifest.json' ?
- 'extensionErrorViewManifest' : 'extensionErrorViewDetails';
- viewDetailsLink.textContent = loadTimeData.getString(stringId);
+ this.addEventListener('click', function(e) {
+ if (e.target != deleteButton)
+ this.requestActive_();
+ }.bind(this));
+ this.addEventListener('keydown', function(e) {
+ if (e.keyIdentifier == 'Enter' && e.target != deleteButton)
+ this.requestActive_();
+ });
- viewDetailsLink.addEventListener('click', function(e) {
- extensions.ExtensionErrorOverlay.getInstance().setErrorAndShowOverlay(
- error, extensionUrl);
- });
- }
+ this.addFocusableElement(this);
+ this.addFocusableElement(this.querySelector('.error-delete-button'));
+ },
+
+ /**
+ * Bubble up an event to request to become active.
+ * @private
+ */
+ requestActive_: function() {
+ this.dispatchEvent(
+ new CustomEvent('highlightExtensionError',
+ {bubbles: true, detail: this.error}));
},
};
/**
* A variable length list of runtime or manifest errors for a given extension.
- * @param {Array.<Object>} errors The list of extension errors with which
- * to populate the list.
+ * @param {Array<(RuntimeError|ManifestError)>} errors The list of extension
+ * errors with which to populate the list.
+ * @param {string} extensionId The id of the extension.
* @constructor
* @extends {HTMLDivElement}
*/
- function ExtensionErrorList(errors) {
+ function ExtensionErrorList(errors, extensionId) {
var div = cloneTemplate('extension-error-list');
div.__proto__ = ExtensionErrorList.prototype;
- div.errors_ = errors;
- div.decorate();
+ div.extensionId_ = extensionId;
+ div.decorate(errors);
return div;
}
- /**
- * @private
- * @const
- * @type {number}
- */
- ExtensionErrorList.MAX_ERRORS_TO_SHOW_ = 3;
-
ExtensionErrorList.prototype = {
__proto__: HTMLDivElement.prototype,
- /** @override */
- decorate: function() {
- this.contents_ = this.querySelector('.extension-error-list-contents');
- this.errors_.forEach(function(error) {
- if (idIsValid(error.extensionId)) {
- this.contents_.appendChild(document.createElement('li')).appendChild(
- new ExtensionError(error));
+ /**
+ * Initializes the extension error list.
+ * @param {Array<(RuntimeError|ManifestError)>} errors The list of errors.
+ */
+ decorate: function(errors) {
+ /**
+ * @private {!Array<(ManifestError|RuntimeError)>}
+ */
+ this.errors_ = [];
+
+ this.focusGrid_ = new cr.ui.FocusGrid();
+ this.gridBoundary_ = this.querySelector('.extension-error-list-contents');
+ this.gridBoundary_.addEventListener('focus', this.onFocus_.bind(this));
+ this.gridBoundary_.addEventListener('focusin',
+ this.onFocusin_.bind(this));
+ errors.forEach(this.addError_, this);
+
+ this.addEventListener('highlightExtensionError', function(e) {
+ this.setActiveErrorNode_(e.target);
+ });
+ this.addEventListener('deleteExtensionError', function(e) {
+ this.removeError_(e.detail);
+ });
+
+ this.querySelector('#extension-error-list-clear').addEventListener(
+ 'click', function(e) {
+ this.clear(true);
+ }.bind(this));
+
+ /**
+ * The callback for the extension changed event.
+ * @private {function(EventData):void}
+ */
+ this.onItemStateChangedListener_ = function(data) {
+ var type = chrome.developerPrivate.EventType;
+ if ((data.event_type == type.ERRORS_REMOVED ||
+ data.event_type == type.ERROR_ADDED) &&
+ data.extensionInfo.id == this.extensionId_) {
+ var newErrors = data.extensionInfo.runtimeErrors.concat(
+ data.extensionInfo.manifestErrors);
+ this.updateErrors_(newErrors);
}
- }, this);
+ }.bind(this);
+
+ chrome.developerPrivate.onItemStateChanged.addListener(
+ this.onItemStateChangedListener_);
+
+ /**
+ * The active error element in the list.
+ * @private {?}
+ */
+ this.activeError_ = null;
- var numShowing = this.contents_.children.length;
- if (numShowing > ExtensionErrorList.MAX_ERRORS_TO_SHOW_)
- this.initShowMoreLink_();
+ this.setActiveError(0);
},
/**
- * Initialize the "Show More" link for the error list. If there are more
- * than |MAX_ERRORS_TO_SHOW_| errors in the list.
+ * Adds an error to the list.
+ * @param {(RuntimeError|ManifestError)} error The error to add.
* @private
*/
- initShowMoreLink_: function() {
- var link = this.querySelector(
- '.extension-error-list-show-more [is="action-link"]');
- link.hidden = false;
- link.isShowingAll = false;
-
- var listContents = this.querySelector('.extension-error-list-contents');
-
- // TODO(dbeam/kalman): trade all this transition voodoo for .animate()?
- listContents.addEventListener('webkitTransitionEnd', function(e) {
- if (listContents.classList.contains('deactivating'))
- listContents.classList.remove('deactivating', 'active');
- else
- listContents.classList.add('scrollable');
+ addError_: function(error) {
+ this.querySelector('#no-errors-span').hidden = true;
+ this.errors_.push(error);
+ var focusRow = new ExtensionError(error, this.gridBoundary_);
+ this.gridBoundary_.appendChild(document.createElement('li')).
+ appendChild(focusRow);
+ this.focusGrid_.addRow(focusRow);
+ },
+
+ /**
+ * Removes an error from the list.
+ * @param {(RuntimeError|ManifestError)} error The error to remove.
+ * @private
+ */
+ removeError_: function(error) {
+ var index = 0;
+ for (; index < this.errors_.length; ++index) {
+ if (this.errors_[index].id == error.id)
+ break;
+ }
+ assert(index != this.errors_.length);
+ var errorList = this.querySelector('.extension-error-list-contents');
+
+ var wasActive =
+ this.activeError_ && this.activeError_.error.id == error.id;
+
+ this.errors_.splice(index, 1);
+ var listElement = errorList.children[index];
+ listElement.parentNode.removeChild(listElement);
+
+ if (wasActive) {
+ index = Math.min(index, this.errors_.length - 1);
+ this.setActiveError(index); // Gracefully handles the -1 case.
+ }
+
+ chrome.developerPrivate.deleteExtensionErrors({
+ extensionId: error.extensionId,
+ errorIds: [error.id]
});
- link.addEventListener('click', function(e) {
- link.isShowingAll = !link.isShowingAll;
+ if (this.errors_.length == 0)
+ this.querySelector('#no-errors-span').hidden = false;
+ },
+
+ /**
+ * Updates the list of errors.
+ * @param {!Array<(ManifestError|RuntimeError)>} newErrors The new list of
+ * errors.
+ * @private
+ */
+ updateErrors_: function(newErrors) {
+ this.errors_.forEach(function(error) {
+ if (findErrorById(newErrors, error.id) == -1)
+ this.removeError_(error);
+ }, this);
+ newErrors.forEach(function(error) {
+ var index = findErrorById(this.errors_, error.id);
+ if (index == -1)
+ this.addError_(error);
+ else
+ this.errors_[index] = error; // Update the existing reference.
+ }, this);
+ },
- var message = link.isShowingAll ? 'extensionErrorsShowFewer' :
- 'extensionErrorsShowMore';
- link.textContent = loadTimeData.getString(message);
+ /**
+ * Called when the list is being removed.
+ */
+ onRemoved: function() {
+ chrome.developerPrivate.onItemStateChanged.removeListener(
+ this.onItemStateChangedListener_);
+ this.clear(false);
+ },
- // Disable scrolling while transitioning. If the element is active,
- // scrolling is enabled when the transition ends.
- listContents.classList.remove('scrollable');
+ /**
+ * Sets the active error in the list.
+ * @param {number} index The index to set to be active.
+ */
+ setActiveError: function(index) {
+ var errorList = this.querySelector('.extension-error-list-contents');
+ var item = errorList.children[index];
+ this.setActiveErrorNode_(
+ item ? item.querySelector('.extension-error-metadata') : null);
+ var node = null;
+ if (index >= 0 && index < errorList.children.length) {
+ node = errorList.children[index].querySelector(
+ '.extension-error-metadata');
+ }
+ this.setActiveErrorNode_(node);
+ },
- if (link.isShowingAll) {
- listContents.classList.add('active');
- listContents.classList.remove('deactivating');
- } else {
- listContents.classList.add('deactivating');
- }
- }.bind(this));
- }
+ /**
+ * Clears the list of all errors.
+ * @param {boolean} deleteErrors Whether or not the errors should be deleted
+ * on the backend.
+ */
+ clear: function(deleteErrors) {
+ if (this.errors_.length == 0)
+ return;
+
+ if (deleteErrors) {
+ var ids = this.errors_.map(function(error) { return error.id; });
+ chrome.developerPrivate.deleteExtensionErrors({
+ extensionId: this.extensionId_,
+ errorIds: ids
+ });
+ }
+
+ this.setActiveErrorNode_(null);
+ this.errors_.length = 0;
+ var errorList = this.querySelector('.extension-error-list-contents');
+ while (errorList.firstChild)
+ errorList.removeChild(errorList.firstChild);
+ },
+
+ /**
+ * Sets the active error in the list.
+ * @param {?} node The error to make active.
+ * @private
+ */
+ setActiveErrorNode_: function(node) {
+ if (this.activeError_)
+ this.activeError_.classList.remove('extension-error-active');
+
+ if (node)
+ node.classList.add('extension-error-active');
+
+ this.activeError_ = node;
+
+ this.dispatchEvent(
+ new CustomEvent('activeExtensionErrorChanged',
+ {bubbles: true, detail: node ? node.error : null}));
+ },
+
+ /**
+ * The grid should not be focusable once it or an element inside it is
+ * focused. This is necessary to allow tabbing out of the grid in reverse.
+ * @private
+ */
+ onFocusin_: function() {
+ this.gridBoundary_.tabIndex = -1;
+ },
+
+ /**
+ * Focus the first focusable row when tabbing into the grid for the
+ * first time.
+ * @private
+ */
+ onFocus_: function() {
+ var activeRow = this.gridBoundary_.querySelector('.focus-row-active');
+ activeRow.getEquivalentElement(null).focus();
+ },
};
return {
diff --git a/chromium/chrome/browser/resources/extensions/extension_error_overlay.css b/chromium/chrome/browser/resources/extensions/extension_error_overlay.css
index 0091e946a53..e6fe5c472da 100644
--- a/chromium/chrome/browser/resources/extensions/extension_error_overlay.css
+++ b/chromium/chrome/browser/resources/extensions/extension_error_overlay.css
@@ -11,6 +11,12 @@
flex-direction: column;
}
+#extension-error-overlay .extension-error-list {
+ border: 1px solid #ccc;
+ margin-bottom: 3px;
+ overflow-y: auto;
+}
+
.extension-error-overlay-runtime-content {
flex: none;
}
diff --git a/chromium/chrome/browser/resources/extensions/extension_error_overlay.html b/chromium/chrome/browser/resources/extensions/extension_error_overlay.html
index 212583f8dd9..ce95cc11bb3 100644
--- a/chromium/chrome/browser/resources/extensions/extension_error_overlay.html
+++ b/chromium/chrome/browser/resources/extensions/extension_error_overlay.html
@@ -21,6 +21,7 @@ in the LICENSE file.
<div class="close-button"></div>
<h1 class="extension-error-overlay-title"></h1>
<div class="content-area">
+ <div class="extension-error-list"></div>
<div id="extension-error-overlay-code" class="extension-code"></div>
</div>
<div class="action-area">
diff --git a/chromium/chrome/browser/resources/extensions/extension_error_overlay.js b/chromium/chrome/browser/resources/extensions/extension_error_overlay.js
index 5d5978c046e..84e32a9f63b 100644
--- a/chromium/chrome/browser/resources/extensions/extension_error_overlay.js
+++ b/chromium/chrome/browser/resources/extensions/extension_error_overlay.js
@@ -2,35 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-/**
- * The type of the stack trace object. The definition is based on
- * extensions/browser/extension_error.cc:RuntimeError::ToValue().
- * @typedef {{columnNumber: number,
- * functionName: string,
- * lineNumber: number,
- * url: string}}
- */
-var StackTrace;
-
-/**
- * The type of the extension error trace object. The definition is based on
- * extensions/browser/extension_error.cc:RuntimeError::ToValue().
- * @typedef {{canInspect: (boolean|undefined),
- * contextUrl: (string|undefined),
- * extensionId: string,
- * fromIncognito: boolean,
- * level: number,
- * manifestKey: string,
- * manifestSpecific: string,
- * message: string,
- * renderProcessId: (number|undefined),
- * renderViewId: (number|undefined),
- * source: string,
- * stackTrace: (Array.<StackTrace>|undefined),
- * type: number}}
- */
-var RuntimeError;
-
cr.define('extensions', function() {
'use strict';
@@ -91,30 +62,12 @@ cr.define('extensions', function() {
return !/^extensions::/.test(url);
};
- /**
- * Send a call to chrome to open the developer tools for an error.
- * This will call either the bound function in ExtensionErrorHandler or the
- * API function from developerPrivate, depending on whether this is being
- * used in the native chrome:extensions page or the Apps Developer Tool.
- * @see chrome/browser/ui/webui/extensions/extension_error_ui_util.h
- * @param {Object} args The arguments to pass to openDevTools.
- * @private
- */
- RuntimeErrorContent.openDevtools_ = function(args) {
- if (chrome.send)
- chrome.send('extensionErrorOpenDevTools', [args]);
- else if (chrome.developerPrivate)
- chrome.developerPrivate.openDevTools(args);
- else
- assertNotReached('Cannot call either openDevTools function.');
- };
-
RuntimeErrorContent.prototype = {
__proto__: HTMLDivElement.prototype,
/**
* The underlying error whose details are being displayed.
- * @type {?RuntimeError}
+ * @type {?(RuntimeError|ManifestError)}
* @private
*/
error_: null,
@@ -158,11 +111,13 @@ cr.define('extensions', function() {
/**
* Sets the error for the content.
- * @param {RuntimeError} error The error whose content should
- * be displayed.
+ * @param {(RuntimeError|ManifestError)} error The error whose content
+ * should be displayed.
* @param {string} extensionUrl The URL associated with this extension.
*/
setError: function(error, extensionUrl) {
+ this.clearError();
+
this.error_ = error;
this.extensionUrl_ = extensionUrl;
this.contextUrl_.textContent = error.contextUrl ?
@@ -236,13 +191,12 @@ cr.define('extensions', function() {
frameNode.addEventListener('click', function(frame, frameNode, e) {
this.setActiveFrame_(frameNode);
- // Request the file source with the section highlighted; this will
- // call ExtensionErrorOverlay.requestFileSourceResponse() when
- // completed, which in turn calls setCode().
- ExtensionErrorOverlay.requestFileSource(
+ // Request the file source with the section highlighted.
+ extensions.ExtensionErrorOverlay.getInstance().requestFileSource(
{extensionId: this.error_.extensionId,
message: this.error_.message,
- pathSuffix: getRelativeUrl(frame.url, this.extensionUrl_),
+ pathSuffix: getRelativeUrl(frame.url,
+ assert(this.extensionUrl_)),
lineNumber: frame.lineNumber});
}.bind(this, frame, frameNode));
@@ -267,9 +221,9 @@ cr.define('extensions', function() {
var stackFrame =
this.error_.stackTrace[this.currentFrameNode_.indexIntoTrace];
- RuntimeErrorContent.openDevtools_(
- {renderProcessId: this.error_.renderProcessId,
- renderViewId: this.error_.renderViewId,
+ chrome.developerPrivate.openDevTools(
+ {renderProcessId: this.error_.renderProcessId || -1,
+ renderViewId: this.error_.renderViewId || -1,
url: stackFrame.url,
lineNumber: stackFrame.lineNumber || 0,
columnNumber: stackFrame.columnNumber || 0});
@@ -294,15 +248,6 @@ cr.define('extensions', function() {
}
/**
- * Value of ExtensionError::RUNTIME_ERROR enum.
- * @see extensions/browser/extension_error.h
- * @type {number}
- * @const
- * @private
- */
- ExtensionErrorOverlay.RUNTIME_ERROR_TYPE_ = 1;
-
- /**
* The manifest filename.
* @type {string}
* @const
@@ -324,58 +269,15 @@ cr.define('extensions', function() {
file.toLowerCase() == ExtensionErrorOverlay.MANIFEST_FILENAME_;
};
- /**
- * Determine whether or not we can show an overlay with more details for
- * the given extension error.
- * @param {Object} error The extension error.
- * @param {string} extensionUrl The url for the extension, in the form
- * "chrome-extension://<extension-id>/".
- * @return {boolean} True if we can show an overlay for the error,
- * false otherwise.
- */
- ExtensionErrorOverlay.canShowOverlayForError = function(error, extensionUrl) {
- if (ExtensionErrorOverlay.canLoadFileSource(error.source, extensionUrl))
- return true;
-
- if (error.stackTrace) {
- for (var i = 0; i < error.stackTrace.length; ++i) {
- if (RuntimeErrorContent.shouldDisplayForUrl(error.stackTrace[i].url))
- return true;
- }
- }
-
- return false;
- };
-
- /**
- * Send a call to chrome to request the source of a given file.
- * This will call either the bound function in ExtensionErrorHandler or the
- * API function from developerPrivate, depending on whether this is being
- * used in the native chrome:extensions page or the Apps Developer Tool.
- * @see chrome/browser/ui/webui/extensions/extension_error_ui_util.h
- * @param {Object} args The arguments to pass to requestFileSource.
- */
- ExtensionErrorOverlay.requestFileSource = function(args) {
- if (chrome.send) {
- chrome.send('extensionErrorRequestFileSource', [args]);
- } else if (chrome.developerPrivate) {
- chrome.developerPrivate.requestFileSource(args, function(result) {
- extensions.ExtensionErrorOverlay.requestFileSourceResponse(result);
- });
- } else {
- assertNotReached('Cannot call either requestFileSource function.');
- }
- };
-
cr.addSingletonGetter(ExtensionErrorOverlay);
ExtensionErrorOverlay.prototype = {
/**
* The underlying error whose details are being displayed.
- * @type {?RuntimeError}
+ * @type {?(RuntimeError|ManifestError)}
* @private
*/
- error_: null,
+ selectedError_: null,
/**
* Initialize the page.
@@ -444,99 +346,146 @@ cr.define('extensions', function() {
// There's a chance that the overlay receives multiple dismiss events; in
// this case, handle it gracefully and return (since all necessary work
// will already have been done).
- if (!this.error_)
+ if (!this.selectedError_)
return;
// Remove all previous content.
this.codeDiv_.clear();
- this.openDevtoolsButton_.hidden = true;
+ this.overlayDiv_.querySelector('.extension-error-list').onRemoved();
+
+ this.clearRuntimeContent_();
+
+ this.selectedError_ = null;
+ },
- if (this.error_.type == ExtensionErrorOverlay.RUNTIME_ERROR_TYPE_) {
- this.overlayDiv_.querySelector('.content-area').removeChild(
+ /**
+ * Clears the current content.
+ * @private
+ */
+ clearRuntimeContent_: function() {
+ if (this.runtimeErrorContent_.parentNode) {
+ this.runtimeErrorContent_.parentNode.removeChild(
this.runtimeErrorContent_);
this.runtimeErrorContent_.clearError();
}
-
- this.error_ = null;
+ this.openDevtoolsButton_.hidden = true;
},
/**
- * Associate an error with the overlay. This will set the error for the
- * overlay, and, if possible, will populate the code section of the overlay
- * with the relevant file, load the stack trace, and generate links for
- * opening devtools (the latter two only happen for runtime errors).
- * @param {RuntimeError} error The error to show in the overlay.
- * @param {string} extensionUrl The URL of the extension, in the form
- * "chrome-extension://<extension_id>".
+ * Sets the active error for the overlay.
+ * @param {?(ManifestError|RuntimeError)} error The error to make active.
+ * TODO(dbeam): add URL externs and re-enable typechecking in this method.
+ * @suppress {missingProperties}
+ * @private
*/
- setErrorAndShowOverlay: function(error, extensionUrl) {
- this.error_ = error;
+ setActiveError_: function(error) {
+ this.selectedError_ = error;
+
+ // If there is no error (this can happen if, e.g., the user deleted all
+ // the errors), then clear the content.
+ if (!error) {
+ this.codeDiv_.populate(
+ null, loadTimeData.getString('extensionErrorNoErrorsCodeMessage'));
+ this.clearRuntimeContent_();
+ return;
+ }
- if (this.error_.type == ExtensionErrorOverlay.RUNTIME_ERROR_TYPE_) {
- this.runtimeErrorContent_.setError(this.error_, extensionUrl);
+ var extensionUrl = 'chrome-extension://' + error.extensionId + '/';
+ // Set or hide runtime content.
+ if (error.type == chrome.developerPrivate.ErrorType.RUNTIME) {
+ this.runtimeErrorContent_.setError(error, extensionUrl);
this.overlayDiv_.querySelector('.content-area').insertBefore(
this.runtimeErrorContent_,
this.codeDiv_.nextSibling);
this.openDevtoolsButton_.hidden = false;
this.openDevtoolsButton_.disabled = !error.canInspect;
+ } else {
+ this.clearRuntimeContent_();
}
+ // Read the file source to populate the code section, or set it to null if
+ // the file is unreadable.
if (ExtensionErrorOverlay.canLoadFileSource(error.source, extensionUrl)) {
- var relativeUrl = getRelativeUrl(error.source, extensionUrl);
-
+ // Use pathname instead of relativeUrl.
var requestFileSourceArgs = {extensionId: error.extensionId,
- message: error.message,
- pathSuffix: relativeUrl};
-
- if (relativeUrl.toLowerCase() ==
- ExtensionErrorOverlay.MANIFEST_FILENAME_) {
- requestFileSourceArgs.manifestKey = error.manifestKey;
- requestFileSourceArgs.manifestSpecific = error.manifestSpecific;
- } else {
- requestFileSourceArgs.lineNumber =
- error.stackTrace && error.stackTrace[0] ?
- error.stackTrace[0].lineNumber : 0;
+ message: error.message};
+ switch (error.type) {
+ case chrome.developerPrivate.ErrorType.MANIFEST:
+ requestFileSourceArgs.pathSuffix = error.source;
+ requestFileSourceArgs.manifestKey = error.manifestKey;
+ requestFileSourceArgs.manifestSpecific = error.manifestSpecific;
+ break;
+ case chrome.developerPrivate.ErrorType.RUNTIME:
+ // slice(1) because pathname starts with a /.
+ var pathname = new URL(error.source).pathname.slice(1);
+ requestFileSourceArgs.pathSuffix = pathname;
+ requestFileSourceArgs.lineNumber =
+ error.stackTrace && error.stackTrace[0] ?
+ error.stackTrace[0].lineNumber : 0;
+ break;
+ default:
+ assertNotReached();
}
- ExtensionErrorOverlay.requestFileSource(requestFileSourceArgs);
+ this.requestFileSource(requestFileSourceArgs);
} else {
- ExtensionErrorOverlay.requestFileSourceResponse(null);
+ this.onFileSourceResponse_(null);
}
},
-
/**
- * Set the code to be displayed in the code portion of the overlay.
- * @see ExtensionErrorOverlay.requestFileSourceResponse().
- * @param {?ExtensionHighlight} code The code to be displayed. If |code| is
- * null, then
- * a "Could not display code" message will be displayed instead.
+ * Associate an error with the overlay. This will set the error for the
+ * overlay, and, if possible, will populate the code section of the overlay
+ * with the relevant file, load the stack trace, and generate links for
+ * opening devtools (the latter two only happen for runtime errors).
+ * @param {Array<(RuntimeError|ManifestError)>} errors The error to show in
+ * the overlay.
+ * @param {string} extensionId The id of the extension.
+ * @param {string} extensionName The name of the extension.
*/
- setCode: function(code) {
+ setErrorsAndShowOverlay: function(errors, extensionId, extensionName) {
document.querySelector(
'#extension-error-overlay .extension-error-overlay-title').
- textContent = code.title;
+ textContent = extensionName;
+ var errorsDiv = this.overlayDiv_.querySelector('.extension-error-list');
+ var extensionErrors =
+ new extensions.ExtensionErrorList(errors, extensionId);
+ errorsDiv.parentNode.replaceChild(extensionErrors, errorsDiv);
+ extensionErrors.addEventListener('activeExtensionErrorChanged',
+ function(e) {
+ this.setActiveError_(e.detail);
+ }.bind(this));
+
+ if (errors.length > 0)
+ this.setActiveError_(errors[0]);
+ this.setVisible(true);
+ },
+
+ /**
+ * Requests a file's source.
+ * @param {RequestFileSourceProperties} args The arguments for the call.
+ */
+ requestFileSource: function(args) {
+ chrome.developerPrivate.requestFileSource(
+ args, this.onFileSourceResponse_.bind(this));
+ },
+ /**
+ * Set the code to be displayed in the code portion of the overlay.
+ * @see ExtensionErrorOverlay.requestFileSourceResponse().
+ * @param {?RequestFileSourceResponse} response The response from the
+ * request file source call, which will be shown as code. If |response|
+ * is null, then a "Could not display code" message will be displayed
+ * instead.
+ */
+ onFileSourceResponse_: function(response) {
this.codeDiv_.populate(
- code,
+ response, // ExtensionCode can handle a null response.
loadTimeData.getString('extensionErrorOverlayNoCodeToDisplay'));
+ this.setVisible(true);
},
};
- /**
- * Called by the ExtensionErrorHandler responding to the request for a file's
- * source. Populate the content area of the overlay and display the overlay.
- * @param {?ExtensionHighlight} result The three 'highlight' strings represent
- * three portions of the file's content to display - the portion which is
- * most relevant and should be emphasized (highlight), and the parts both
- * before and after this portion. These may be empty.
- */
- ExtensionErrorOverlay.requestFileSourceResponse = function(result) {
- var overlay = extensions.ExtensionErrorOverlay.getInstance();
- overlay.setCode(result);
- overlay.setVisible(true);
- };
-
// Export
return {
ExtensionErrorOverlay: ExtensionErrorOverlay
diff --git a/chromium/chrome/browser/resources/extensions/extension_focus_manager.js b/chromium/chrome/browser/resources/extensions/extension_focus_manager.js
index b2200245b6e..377744caaeb 100644
--- a/chromium/chrome/browser/resources/extensions/extension_focus_manager.js
+++ b/chromium/chrome/browser/resources/extensions/extension_focus_manager.js
@@ -3,16 +3,16 @@
// found in the LICENSE file.
cr.define('extensions', function() {
- var FocusManager = cr.ui.FocusManager;
-
- function ExtensionFocusManager() {
- FocusManager.disableMouseFocusOnButtons();
- }
+ /**
+ * @constructor
+ * @extends {cr.ui.FocusManager}
+ */
+ function ExtensionFocusManager() {}
cr.addSingletonGetter(ExtensionFocusManager);
ExtensionFocusManager.prototype = {
- __proto__: FocusManager.prototype,
+ __proto__: cr.ui.FocusManager.prototype,
/** @override */
getFocusParent: function() {
diff --git a/chromium/chrome/browser/resources/extensions/extension_info.css b/chromium/chrome/browser/resources/extensions/extension_info.css
deleted file mode 100644
index c5106095eca..00000000000
--- a/chromium/chrome/browser/resources/extensions/extension_info.css
+++ /dev/null
@@ -1,44 +0,0 @@
-/* Copyright (c) 2012 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. */
-
-body {
- font-family: 'DejaVu Sans', Arial, sans-serif;
- margin: 8px;
- max-width: 480px;
- min-width: 360px;
-}
-
-#extension-item {
- background-repeat: no-repeat;
- display: -webkit-box;
- min-height: 48px;
-}
-
-#extension-title-running {
- -webkit-padding-end: 5px;
- font-size: 1.2em;
- font-weight: 500;
- padding-bottom: 5px;
-}
-
-#extension-last-updated,
-#extension-update-time {
- -webkit-padding-end: 7px;
- color: rgb(78, 83, 86);
- font-size: 1em;
- font-weight: 400;
-}
-
-#extension-description {
- -webkit-padding-end: 5px;
- color: rgb(121, 126, 130);
- font-size: 1em;
- margin: 5px 0;
- white-space: normal;
-}
-
-#extension-details {
- -webkit-box-flex: 1;
- -webkit-padding-start: 57px;
-}
diff --git a/chromium/chrome/browser/resources/extensions/extension_info.html b/chromium/chrome/browser/resources/extensions/extension_info.html
deleted file mode 100644
index 43583f1ad39..00000000000
--- a/chromium/chrome/browser/resources/extensions/extension_info.html
+++ /dev/null
@@ -1,31 +0,0 @@
-<!DOCTYPE html>
-<html i18n-values="dir:textdirection;">
-<head>
-<meta charset="utf-8">
-<link rel="stylesheet" href="chrome://resources/css/chrome_shared.css">
-<link rel="stylesheet" href="extension_info.css">
-
-<script src="chrome://resources/js/cr.js"></script>
-<script src="chrome://resources/js/load_time_data.js"></script>
-<script src="chrome://resources/js/util.js"></script>
-
-<script src="chrome://extension-info/extension_info.js"></script>
-</head>
-
-<body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
-
-<div id="extension-item">
- <div id="extension-details">
- <div id="extension-title-running"></div>
- <div>
- <span id="extension-last-updated" i18n-content="lastUpdated"></span>
- <span id="extension-update-time" i18n-content="installTime"></span>
- </div>
- <p id="extension-description" i18n-content="description"></p>
- </div>
-</div>
-
-<script src="chrome://extension-info/strings.js"></script>
-<script src="chrome://resources/js/i18n_template2.js"></script>
-</body>
-</html>
diff --git a/chromium/chrome/browser/resources/extensions/extension_info.js b/chromium/chrome/browser/resources/extensions/extension_info.js
deleted file mode 100644
index 6bf0062b0f8..00000000000
--- a/chromium/chrome/browser/resources/extensions/extension_info.js
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright (c) 2012 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.
-
-cr.define('extension_info', function() {
- 'use strict';
-
- /**
- * Initialize the info popup.
- */
- function load() {
- $('extension-item').style.backgroundImage =
- 'url(' + loadTimeData.getString('icon') + ')';
- $('extension-title-running').textContent =
- loadTimeData.getStringF('isRunning', loadTimeData.getString('name'));
- }
-
- return {
- load: load
- };
-});
-
-window.addEventListener('DOMContentLoaded', extension_info.load);
diff --git a/chromium/chrome/browser/resources/extensions/extension_list.js b/chromium/chrome/browser/resources/extensions/extension_list.js
index 7546db1fbb9..b3343913c9f 100644
--- a/chromium/chrome/browser/resources/extensions/extension_list.js
+++ b/chromium/chrome/browser/resources/extensions/extension_list.js
@@ -4,92 +4,197 @@
<include src="extension_error.js">
+///////////////////////////////////////////////////////////////////////////////
+// ExtensionFocusRow:
+
/**
- * The type of the extension data object. The definition is based on
- * chrome/browser/ui/webui/extensions/extension_basic_info.cc
- * and
- * chrome/browser/ui/webui/extensions/extension_settings_handler.cc
- * ExtensionSettingsHandler::CreateExtensionDetailValue()
- * @typedef {{allow_reload: boolean,
- * allowAllUrls: boolean,
- * allowFileAccess: boolean,
- * blacklistText: string,
- * corruptInstall: boolean,
- * dependentExtensions: Array,
- * description: string,
- * detailsUrl: string,
- * enableExtensionInfoDialog: boolean,
- * enable_show_button: boolean,
- * enabled: boolean,
- * enabledIncognito: boolean,
- * errorCollectionEnabled: (boolean|undefined),
- * hasPopupAction: boolean,
- * homepageProvided: boolean,
- * homepageUrl: string,
- * icon: string,
- * id: string,
- * incognitoCanBeEnabled: boolean,
- * installWarnings: (Array|undefined),
- * is_hosted_app: boolean,
- * is_platform_app: boolean,
- * isFromStore: boolean,
- * isUnpacked: boolean,
- * kioskEnabled: boolean,
- * kioskOnly: boolean,
- * locationText: string,
- * managedInstall: boolean,
- * manifestErrors: (Array.<RuntimeError>|undefined),
- * name: string,
- * offlineEnabled: boolean,
- * optionsOpenInTab: boolean,
- * optionsPageHref: string,
- * optionsUrl: string,
- * order: number,
- * packagedApp: boolean,
- * path: (string|undefined),
- * prettifiedPath: (string|undefined),
- * recommendedInstall: boolean,
- * runtimeErrors: (Array.<RuntimeError>|undefined),
- * suspiciousInstall: boolean,
- * terminated: boolean,
- * version: string,
- * views: Array.<{renderViewId: number, renderProcessId: number,
- * path: string, incognito: boolean,
- * generatedBackgroundPage: boolean}>,
- * wantsAllUrls: boolean,
- * wantsErrorCollection: boolean,
- * wantsFileAccess: boolean,
- * warnings: (Array|undefined)}}
+ * Provides an implementation for a single column grid.
+ * @constructor
+ * @extends {cr.ui.FocusRow}
*/
-var ExtensionData;
+function ExtensionFocusRow() {}
-cr.define('options', function() {
- 'use strict';
+/**
+ * Decorates |focusRow| so that it can be treated as a ExtensionFocusRow.
+ * @param {Element} focusRow The element that has all the columns.
+ * @param {Node} boundary Focus events are ignored outside of this node.
+ */
+ExtensionFocusRow.decorate = function(focusRow, boundary) {
+ focusRow.__proto__ = ExtensionFocusRow.prototype;
+ focusRow.decorate(boundary);
+};
+
+ExtensionFocusRow.prototype = {
+ __proto__: cr.ui.FocusRow.prototype,
+
+ /** @override */
+ getEquivalentElement: function(element) {
+ if (this.focusableElements.indexOf(element) > -1)
+ return element;
+
+ // All elements default to another element with the same type.
+ var columnType = element.getAttribute('column-type');
+ var equivalent = this.querySelector('[column-type=' + columnType + ']');
+
+ if (!equivalent || !this.canAddElement_(equivalent)) {
+ var actionLinks = ['options', 'website', 'launch', 'localReload'];
+ var optionalControls = ['showButton', 'incognito', 'dev-collectErrors',
+ 'allUrls', 'localUrls'];
+ var removeStyleButtons = ['trash', 'enterprise'];
+ var enableControls = ['terminatedReload', 'repair', 'enabled'];
+
+ if (actionLinks.indexOf(columnType) > -1)
+ equivalent = this.getFirstFocusableByType_(actionLinks);
+ else if (optionalControls.indexOf(columnType) > -1)
+ equivalent = this.getFirstFocusableByType_(optionalControls);
+ else if (removeStyleButtons.indexOf(columnType) > -1)
+ equivalent = this.getFirstFocusableByType_(removeStyleButtons);
+ else if (enableControls.indexOf(columnType) > -1)
+ equivalent = this.getFirstFocusableByType_(enableControls);
+ }
+
+ // Return the first focusable element if no equivalent type is found.
+ return equivalent || this.focusableElements[0];
+ },
+
+ /** @override */
+ makeActive: function(active) {
+ cr.ui.FocusRow.prototype.makeActive.call(this, active);
+
+ // Only highlight if the row has focus.
+ this.classList.toggle('extension-highlight',
+ active && this.contains(document.activeElement));
+ },
+
+ /** Updates the list of focusable elements. */
+ updateFocusableElements: function() {
+ this.focusableElements.length = 0;
+
+ var focusableCandidates = this.querySelectorAll('[column-type]');
+ for (var i = 0; i < focusableCandidates.length; ++i) {
+ var element = focusableCandidates[i];
+ if (this.canAddElement_(element))
+ this.addFocusableElement(element);
+ }
+ },
/**
- * Creates a new list of extensions.
- * @param {Object=} opt_propertyBag Optional properties.
- * @constructor
- * @extends {HTMLDivElement}
+ * Get the first focusable element that matches a list of types.
+ * @param {Array<string>} types An array of types to match from.
+ * @return {?Element} Return the first element that matches a type in |types|.
+ * @private
+ */
+ getFirstFocusableByType_: function(types) {
+ for (var i = 0; i < this.focusableElements.length; ++i) {
+ var element = this.focusableElements[i];
+ if (types.indexOf(element.getAttribute('column-type')) > -1)
+ return element;
+ }
+ return null;
+ },
+
+ /**
+ * Setup a typical column in the ExtensionFocusRow. A column can be any
+ * element and should have an action when clicked/toggled. This function
+ * adds a listener and a handler for an event. Also adds the "column-type"
+ * attribute to make the element focusable in |updateFocusableElements|.
+ * @param {string} query A query to select the element to set up.
+ * @param {string} columnType A tag used to identify the column when
+ * changing focus.
+ * @param {string} eventType The type of event to listen to.
+ * @param {function(Event)} handler The function that should be called
+ * by the event.
+ * @private
*/
- var ExtensionsList = cr.ui.define('div');
+ setupColumn: function(query, columnType, eventType, handler) {
+ var element = this.querySelector(query);
+ element.addEventListener(eventType, handler);
+ element.setAttribute('column-type', columnType);
+ },
+
+ /**
+ * @param {Element} element
+ * @return {boolean}
+ * @private
+ */
+ canAddElement_: function(element) {
+ if (!element || element.disabled)
+ return false;
+
+ var developerMode = $('extension-settings').classList.contains('dev-mode');
+ if (this.isDeveloperOption_(element) && !developerMode)
+ return false;
+
+ for (var el = element; el; el = el.parentElement) {
+ if (el.hidden)
+ return false;
+ }
+
+ return true;
+ },
/**
- * @type {Object.<string, boolean>} A map from extension id to a boolean
- * indicating whether the incognito warning is showing. This persists
- * between calls to decorate.
+ * Returns true if the element should only be shown in developer mode.
+ * @param {Element} element
+ * @return {boolean}
+ * @private
*/
- var butterBarVisibility = {};
+ isDeveloperOption_: function(element) {
+ return /^dev-/.test(element.getAttribute('column-type'));
+ },
+};
+
+cr.define('extensions', function() {
+ 'use strict';
/**
- * @type {Object.<string, number>} A map from extension id to last reloaded
- * timestamp. The timestamp is recorded when the user click the 'Reload'
- * link. It is used to refresh the icon of an unpacked extension.
- * This persists between calls to decorate.
+ * Compares two extensions for the order they should appear in the list.
+ * @param {ExtensionInfo} a The first extension.
+ * @param {ExtensionInfo} b The second extension.
+ * returns {number} -1 if A comes before B, 1 if A comes after B, 0 if equal.
*/
- var extensionReloadedTimestamp = {};
+ function compareExtensions(a, b) {
+ function compare(x, y) {
+ return x < y ? -1 : (x > y ? 1 : 0);
+ }
+ function compareLocation(x, y) {
+ if (x.location == y.location)
+ return 0;
+ if (x.location == chrome.developerPrivate.Location.UNPACKED)
+ return -1;
+ if (y.location == chrome.developerPrivate.Location.UNPACKED)
+ return 1;
+ return 0;
+ }
+ return compareLocation(a, b) ||
+ compare(a.name.toLowerCase(), b.name.toLowerCase()) ||
+ compare(a.id, b.id);
+ }
+
+ /** @interface */
+ function ExtensionListDelegate() {}
- ExtensionsList.prototype = {
+ ExtensionListDelegate.prototype = {
+ /**
+ * Called when the number of extensions in the list has changed.
+ */
+ onExtensionCountChanged: assertNotReached,
+ };
+
+ /**
+ * Creates a new list of extensions.
+ * @param {extensions.ExtensionListDelegate} delegate
+ * @constructor
+ * @extends {HTMLDivElement}
+ */
+ function ExtensionList(delegate) {
+ var div = document.createElement('div');
+ div.__proto__ = ExtensionList.prototype;
+ div.initialize(delegate);
+ return div;
+ }
+
+ ExtensionList.prototype = {
__proto__: HTMLDivElement.prototype,
/**
@@ -101,11 +206,175 @@ cr.define('options', function() {
*/
optionsShown_: false,
- /** @override */
- decorate: function() {
- this.textContent = '';
+ /** @private {!cr.ui.FocusGrid} */
+ focusGrid_: new cr.ui.FocusGrid(),
+
+ /**
+ * Indicates whether an uninstall dialog is being shown to prevent multiple
+ * dialogs from being displayed.
+ * @private {boolean}
+ */
+ uninstallIsShowing_: false,
+
+ /**
+ * Indicates whether a permissions prompt is showing.
+ * @private {boolean}
+ */
+ permissionsPromptIsShowing_: false,
+
+ /**
+ * Necessary to only show the butterbar once.
+ * @private {boolean}
+ */
+ butterbarShown_: false,
+
+ /**
+ * Whether or not incognito mode is available.
+ * @private {boolean}
+ */
+ incognitoAvailable_: false,
+
+ /**
+ * Whether or not the app info dialog is enabled.
+ * @private {boolean}
+ */
+ enableAppInfoDialog_: false,
+
+ /**
+ * Initializes the list.
+ * @param {!extensions.ExtensionListDelegate} delegate
+ */
+ initialize: function(delegate) {
+ /** @private {!Array<ExtensionInfo>} */
+ this.extensions_ = [];
+
+ /** @private {!extensions.ExtensionListDelegate} */
+ this.delegate_ = delegate;
+
+ /**
+ * |loadFinished| should be used for testing purposes and will be
+ * fulfilled when this list has finished loading the first time.
+ * @type {Promise}
+ * */
+ this.loadFinished = new Promise(function(resolve, reject) {
+ /** @private {function(?)} */
+ this.resolveLoadFinished_ = resolve;
+ }.bind(this));
+
+ chrome.developerPrivate.onItemStateChanged.addListener(
+ function(eventData) {
+ var EventType = chrome.developerPrivate.EventType;
+ switch (eventData.event_type) {
+ case EventType.VIEW_REGISTERED:
+ case EventType.VIEW_UNREGISTERED:
+ case EventType.INSTALLED:
+ case EventType.LOADED:
+ case EventType.UNLOADED:
+ case EventType.ERROR_ADDED:
+ case EventType.ERRORS_REMOVED:
+ case EventType.PREFS_CHANGED:
+ if (eventData.extensionInfo) {
+ this.updateExtension_(eventData.extensionInfo);
+ this.focusGrid_.ensureRowActive();
+ }
+ break;
+ case EventType.UNINSTALLED:
+ var index = this.getIndexOfExtension_(eventData.item_id);
+ this.extensions_.splice(index, 1);
+ this.removeNode_(getRequiredElement(eventData.item_id));
+ break;
+ default:
+ assertNotReached();
+ }
+
+ if (eventData.event_type == EventType.UNLOADED)
+ this.hideEmbeddedExtensionOptions_(eventData.item_id);
+
+ if (eventData.event_type == EventType.INSTALLED ||
+ eventData.event_type == EventType.UNINSTALLED) {
+ this.delegate_.onExtensionCountChanged();
+ }
+ }.bind(this));
+ },
+
+ /**
+ * Updates the extensions on the page.
+ * @param {boolean} incognitoAvailable Whether or not incognito is allowed.
+ * @param {boolean} enableAppInfoDialog Whether or not the app info dialog
+ * is enabled.
+ * @return {Promise} A promise that is resolved once the extensions data is
+ * fully updated.
+ */
+ updateExtensionsData: function(incognitoAvailable, enableAppInfoDialog) {
+ // If we start to need more information about the extension configuration,
+ // consider passing in the full object from the ExtensionSettings.
+ this.incognitoAvailable_ = incognitoAvailable;
+ this.enableAppInfoDialog_ = enableAppInfoDialog;
+ /** @private {Promise} */
+ this.extensionsUpdated_ = new Promise(function(resolve, reject) {
+ chrome.developerPrivate.getExtensionsInfo(
+ {includeDisabled: true, includeTerminated: true},
+ function(extensions) {
+ // Sort in order of unpacked vs. packed, followed by name, followed by
+ // id.
+ extensions.sort(compareExtensions);
+ this.extensions_ = extensions;
+ this.showExtensionNodes_();
+ resolve();
+
+ // |resolve| is async so it's necessary to use |then| here in order to
+ // do work after other |then|s have finished. This is important so
+ // elements are visible when these updates happen.
+ this.extensionsUpdated_.then(function() {
+ this.onUpdateFinished_();
+ this.resolveLoadFinished_();
+ }.bind(this));
+ }.bind(this));
+ }.bind(this));
+ return this.extensionsUpdated_;
+ },
+
+ /**
+ * Updates elements that need to be visible in order to update properly.
+ * @private
+ */
+ onUpdateFinished_: function() {
+ // Cannot focus or highlight a extension if there are none.
+ if (this.extensions_.length == 0)
+ return;
+
+ assert(!this.hidden);
+ assert(!this.parentElement.hidden);
+
+ this.updateFocusableElements();
- this.showExtensionNodes_();
+ var idToHighlight = this.getIdQueryParam_();
+ if (idToHighlight && $(idToHighlight)) {
+ this.scrollToNode_(idToHighlight);
+ this.setInitialFocus_(idToHighlight);
+ }
+
+ var idToOpenOptions = this.getOptionsQueryParam_();
+ if (idToOpenOptions && $(idToOpenOptions))
+ this.showEmbeddedExtensionOptions_(idToOpenOptions, true);
+ },
+
+ /** @return {number} The number of extensions being displayed. */
+ getNumExtensions: function() {
+ return this.extensions_.length;
+ },
+
+ /**
+ * @param {string} id The id of the extension.
+ * @return {number} The index of the extension with the given id.
+ * @private
+ */
+ getIndexOfExtension_: function(id) {
+ for (var i = 0; i < this.extensions_.length; ++i) {
+ if (this.extensions_[i].id == id)
+ return i;
+ }
+ return -1;
},
getIdQueryParam_: function() {
@@ -117,25 +386,58 @@ cr.define('options', function() {
},
/**
- * Creates all extension items from scratch.
+ * Creates or updates all extension items from scratch.
* @private
*/
showExtensionNodes_: function() {
+ // Any node that is not updated will be removed.
+ var seenIds = [];
+
// Iterate over the extension data and add each item to the list.
- this.data_.extensions.forEach(this.createNode_, this);
+ this.extensions_.forEach(function(extension) {
+ seenIds.push(extension.id);
+ this.updateExtension_(extension);
+ }, this);
+ this.focusGrid_.ensureRowActive();
- var idToHighlight = this.getIdQueryParam_();
- if (idToHighlight && $(idToHighlight))
- this.scrollToNode_(idToHighlight);
+ // Remove extensions that are no longer installed.
+ var nodes = document.querySelectorAll('.extension-list-item-wrapper[id]');
+ Array.prototype.forEach.call(nodes, function(node) {
+ if (seenIds.indexOf(node.id) < 0)
+ this.removeNode_(node);
+ }, this);
+ },
- var idToOpenOptions = this.getOptionsQueryParam_();
- if (idToOpenOptions && $(idToOpenOptions))
- this.showEmbeddedExtensionOptions_(idToOpenOptions, true);
+ /** Updates each row's focusable elements without rebuilding the grid. */
+ updateFocusableElements: function() {
+ var rows = document.querySelectorAll('.extension-list-item-wrapper[id]');
+ for (var i = 0; i < rows.length; ++i) {
+ assertInstanceof(rows[i], ExtensionFocusRow).updateFocusableElements();
+ }
+ },
- if (this.data_.extensions.length == 0)
- this.classList.add('empty-extension-list');
- else
- this.classList.remove('empty-extension-list');
+ /**
+ * Removes the node from the DOM, and updates the focused element if needed.
+ * @param {!HTMLElement} node
+ * @private
+ */
+ removeNode_: function(node) {
+ if (node.contains(document.activeElement)) {
+ var nodes =
+ document.querySelectorAll('.extension-list-item-wrapper[id]');
+ var index = Array.prototype.indexOf.call(nodes, node);
+ assert(index != -1);
+ var focusableNode = nodes[index + 1] || nodes[index - 1];
+ if (focusableNode)
+ focusableNode.getEquivalentElement(document.activeElement).focus();
+ }
+ node.parentNode.removeChild(node);
+ this.focusGrid_.removeRow(assertInstanceof(node, ExtensionFocusRow));
+
+ // Unregister the removed node from events.
+ assertInstanceof(node, ExtensionFocusRow).destroy();
+
+ this.focusGrid_.ensureRowActive();
},
/**
@@ -155,368 +457,619 @@ cr.define('options', function() {
},
/**
+ * @param {string} extensionId The id of the extension that should have
+ * initial focus
+ * @private
+ */
+ setInitialFocus_: function(extensionId) {
+ var focusRow = assertInstanceof($(extensionId), ExtensionFocusRow);
+ var columnTypePriority = ['enabled', 'enterprise', 'website', 'details'];
+ var elementToFocus = null;
+ var elementPriority = columnTypePriority.length;
+
+ for (var i = 0; i < focusRow.focusableElements.length; ++i) {
+ var element = focusRow.focusableElements[i];
+ var priority =
+ columnTypePriority.indexOf(element.getAttribute('column-type'));
+ if (priority > -1 && priority < elementPriority) {
+ elementToFocus = element;
+ elementPriority = priority;
+ }
+ }
+
+ focusRow.getEquivalentElement(elementToFocus).focus();
+ },
+
+ /**
* Synthesizes and initializes an HTML element for the extension metadata
* given in |extension|.
- * @param {ExtensionData} extension A dictionary of extension metadata.
+ * @param {!ExtensionInfo} extension A dictionary of extension metadata.
+ * @param {?Element} nextNode |node| should be inserted before |nextNode|.
+ * |node| will be appended to the end if |nextNode| is null.
* @private
*/
- createNode_: function(extension) {
+ createNode_: function(extension, nextNode) {
var template = $('template-collection').querySelector(
'.extension-list-item-wrapper');
var node = template.cloneNode(true);
- node.id = extension.id;
-
- if (!extension.enabled || extension.terminated)
- node.classList.add('inactive-extension');
-
- if (extension.managedInstall ||
- extension.dependentExtensions.length > 0) {
- node.classList.add('may-not-modify');
- node.classList.add('may-not-remove');
- } else if (extension.recommendedInstall) {
- node.classList.add('may-not-remove');
- } else if (extension.suspiciousInstall || extension.corruptInstall) {
- node.classList.add('may-not-modify');
- }
+ ExtensionFocusRow.decorate(node, $('extension-settings-list'));
- var idToHighlight = this.getIdQueryParam_();
- if (node.id == idToHighlight)
- node.classList.add('extension-highlight');
-
- var item = node.querySelector('.extension-list-item');
- // Prevent the image cache of extension icon by using the reloaded
- // timestamp as a query string. The timestamp is recorded when the user
- // clicks the 'Reload' link. http://crbug.com/159302.
- if (extensionReloadedTimestamp[extension.id]) {
- item.style.backgroundImage =
- 'url(' + extension.icon + '?' +
- extensionReloadedTimestamp[extension.id] + ')';
- } else {
- item.style.backgroundImage = 'url(' + extension.icon + ')';
- }
-
- var title = node.querySelector('.extension-title');
- title.textContent = extension.name;
-
- var version = node.querySelector('.extension-version');
- version.textContent = extension.version;
-
- var locationText = node.querySelector('.location-text');
- locationText.textContent = extension.locationText;
-
- var blacklistText = node.querySelector('.blacklist-text');
- blacklistText.textContent = extension.blacklistText;
-
- var description = document.createElement('span');
- description.textContent = extension.description;
- node.querySelector('.extension-description').appendChild(description);
+ var row = assertInstanceof(node, ExtensionFocusRow);
+ row.id = extension.id;
// The 'Show Browser Action' button.
- if (extension.enable_show_button) {
- var showButton = node.querySelector('.show-button');
- showButton.addEventListener('click', function(e) {
- chrome.send('extensionSettingsShowButton', [extension.id]);
+ row.setupColumn('.show-button', 'showButton', 'click', function(e) {
+ chrome.developerPrivate.updateExtensionConfiguration({
+ extensionId: extension.id,
+ showActionButton: true
});
- showButton.hidden = false;
- }
+ });
// The 'allow in incognito' checkbox.
- node.querySelector('.incognito-control').hidden =
- !this.data_.incognitoAvailable;
- var incognito = node.querySelector('.incognito-control input');
- incognito.disabled = !extension.incognitoCanBeEnabled;
- incognito.checked = extension.enabledIncognito;
- if (!incognito.disabled) {
- incognito.addEventListener('change', function(e) {
- var checked = e.target.checked;
- butterBarVisibility[extension.id] = checked;
- butterBar.hidden = !checked || extension.is_hosted_app;
- chrome.send('extensionSettingsEnableIncognito',
- [extension.id, String(checked)]);
+ row.setupColumn('.incognito-control input', 'incognito', 'change',
+ function(e) {
+ var butterBar = row.querySelector('.butter-bar');
+ var checked = e.target.checked;
+ if (!this.butterbarShown_) {
+ butterBar.hidden = !checked ||
+ extension.type ==
+ chrome.developerPrivate.ExtensionType.HOSTED_APP;
+ this.butterbarShown_ = !butterBar.hidden;
+ } else {
+ butterBar.hidden = true;
+ }
+ chrome.developerPrivate.updateExtensionConfiguration({
+ extensionId: extension.id,
+ incognitoAccess: e.target.checked
});
- }
- var butterBar = node.querySelector('.butter-bar');
- butterBar.hidden = !butterBarVisibility[extension.id];
+ }.bind(this));
// The 'collect errors' checkbox. This should only be visible if the
// error console is enabled - we can detect this by the existence of the
// |errorCollectionEnabled| property.
- if (extension.wantsErrorCollection) {
- node.querySelector('.error-collection-control').hidden = false;
- var errorCollection =
- node.querySelector('.error-collection-control input');
- errorCollection.checked = extension.errorCollectionEnabled;
- errorCollection.addEventListener('change', function(e) {
- chrome.send('extensionSettingsEnableErrorCollection',
- [extension.id, String(e.target.checked)]);
+ row.setupColumn('.error-collection-control input', 'dev-collectErrors',
+ 'change', function(e) {
+ chrome.developerPrivate.updateExtensionConfiguration({
+ extensionId: extension.id,
+ errorCollection: e.target.checked
});
- }
+ });
// The 'allow on all urls' checkbox. This should only be visible if
// active script restrictions are enabled. If they are not enabled, no
// extensions should want all urls.
- if (extension.wantsAllUrls) {
- var allUrls = node.querySelector('.all-urls-control');
- allUrls.addEventListener('click', function(e) {
- chrome.send('extensionSettingsAllowOnAllUrls',
- [extension.id, String(e.target.checked)]);
+ row.setupColumn('.all-urls-control input', 'allUrls', 'click',
+ function(e) {
+ chrome.developerPrivate.updateExtensionConfiguration({
+ extensionId: extension.id,
+ runOnAllUrls: e.target.checked
});
- allUrls.querySelector('input').checked = extension.allowAllUrls;
- allUrls.hidden = false;
- }
+ });
// The 'allow file:// access' checkbox.
- if (extension.wantsFileAccess) {
- var fileAccess = node.querySelector('.file-access-control');
- fileAccess.addEventListener('click', function(e) {
- chrome.send('extensionSettingsAllowFileAccess',
- [extension.id, String(e.target.checked)]);
+ row.setupColumn('.file-access-control input', 'localUrls', 'click',
+ function(e) {
+ chrome.developerPrivate.updateExtensionConfiguration({
+ extensionId: extension.id,
+ fileAccess: e.target.checked
});
- fileAccess.querySelector('input').checked = extension.allowFileAccess;
- fileAccess.hidden = false;
- }
+ });
// The 'Options' button or link, depending on its behaviour.
- if (extension.enabled && extension.optionsUrl) {
- var options, optionsClickListener;
- if (extension.optionsOpenInTab) {
- options = node.querySelector('.options-link');
- // Set an href to get the correct mouse-over appearance (link,
- // footer) - but the actual link opening is done through chrome.send
- // with a preventDefault().
- options.setAttribute('href', extension.optionsPageHref);
- optionsClickListener = function() {
- chrome.send('extensionSettingsOptions', [extension.id]);
- };
- } else {
- options = node.querySelector('.options-button');
- optionsClickListener = function() {
- this.showEmbeddedExtensionOptions_(extension.id, false);
- }.bind(this);
- }
- options.addEventListener('click', function(e) {
- optionsClickListener();
- e.preventDefault();
- });
- options.hidden = false;
- }
+ // Set an href to get the correct mouse-over appearance (link,
+ // footer) - but the actual link opening is done through developerPrivate
+ // API with a preventDefault().
+ row.querySelector('.options-link').href =
+ extension.optionsPage ? extension.optionsPage.url : '';
+ row.setupColumn('.options-link', 'options', 'click', function(e) {
+ chrome.developerPrivate.showOptions(extension.id);
+ e.preventDefault();
+ });
+
+ row.setupColumn('.options-button', 'options', 'click', function(e) {
+ this.showEmbeddedExtensionOptions_(extension.id, false);
+ e.preventDefault();
+ }.bind(this));
+
+ // The 'View in Web Store/View Web Site' link.
+ row.querySelector('.site-link').setAttribute('column-type', 'website');
// The 'Permissions' link.
- var permissions = node.querySelector('.permissions-link');
- permissions.addEventListener('click', function(e) {
- chrome.send('extensionSettingsPermissions', [extension.id]);
+ row.setupColumn('.permissions-link', 'details', 'click', function(e) {
+ if (!this.permissionsPromptIsShowing_) {
+ chrome.developerPrivate.showPermissionsDialog(extension.id,
+ function() {
+ this.permissionsPromptIsShowing_ = false;
+ }.bind(this));
+ this.permissionsPromptIsShowing_ = true;
+ }
e.preventDefault();
});
- // The 'View in Web Store/View Web Site' link.
- if (extension.homepageUrl && !extension.enableExtensionInfoDialog) {
- var siteLink = node.querySelector('.site-link');
- siteLink.href = extension.homepageUrl;
- siteLink.textContent = loadTimeData.getString(
- extension.homepageProvided ? 'extensionSettingsVisitWebsite' :
- 'extensionSettingsVisitWebStore');
- siteLink.hidden = false;
- }
+ // The 'Reload' link.
+ row.setupColumn('.reload-link', 'localReload', 'click', function(e) {
+ chrome.developerPrivate.reload(extension.id, {failQuietly: true});
+ });
- if (extension.allow_reload) {
- // The 'Reload' link.
- var reload = node.querySelector('.reload-link');
- reload.addEventListener('click', function(e) {
- chrome.send('extensionSettingsReload', [extension.id]);
- extensionReloadedTimestamp[extension.id] = Date.now();
- });
- reload.hidden = false;
+ // The 'Launch' link.
+ row.setupColumn('.launch-link', 'launch', 'click', function(e) {
+ chrome.management.launchApp(extension.id);
+ });
- if (extension.is_platform_app) {
- // The 'Launch' link.
- var launch = node.querySelector('.launch-link');
- launch.addEventListener('click', function(e) {
- chrome.send('extensionSettingsLaunch', [extension.id]);
- });
- launch.hidden = false;
- }
- }
+ row.setupColumn('.errors-link', 'errors', 'click', function(e) {
+ var extensionId = extension.id;
+ assert(this.extensions_.length > 0);
+ var newEx = this.extensions_.filter(function(e) {
+ return e.state == chrome.developerPrivate.ExtensionState.ENABLED &&
+ e.id == extensionId;
+ })[0];
+ var errors = newEx.manifestErrors.concat(newEx.runtimeErrors);
+ extensions.ExtensionErrorOverlay.getInstance().setErrorsAndShowOverlay(
+ errors, extensionId, newEx.name);
+ }.bind(this));
- if (extension.terminated) {
- var terminatedReload = node.querySelector('.terminated-reload-link');
- terminatedReload.hidden = false;
- terminatedReload.onclick = function() {
- chrome.send('extensionSettingsReload', [extension.id]);
- };
- } else if (extension.corruptInstall && extension.isFromStore) {
- var repair = node.querySelector('.corrupted-repair-button');
- repair.hidden = false;
- repair.onclick = function() {
- chrome.send('extensionSettingsRepair', [extension.id]);
- };
- } else {
- // The 'Enabled' checkbox.
- var enable = node.querySelector('.enable-checkbox');
- enable.hidden = false;
- var enableCheckboxDisabled = extension.managedInstall ||
- extension.suspiciousInstall ||
- extension.corruptInstall ||
- extension.dependentExtensions.length > 0;
- enable.querySelector('input').disabled = enableCheckboxDisabled;
-
- if (!enableCheckboxDisabled) {
- enable.addEventListener('click', function(e) {
- // When e.target is the label instead of the checkbox, it doesn't
- // have the checked property and the state of the checkbox is
- // left unchanged.
- var checked = e.target.checked;
- if (checked == undefined)
- checked = !e.currentTarget.querySelector('input').checked;
- chrome.send('extensionSettingsEnable',
- [extension.id, checked ? 'true' : 'false']);
-
- // This may seem counter-intuitive (to not set/clear the checkmark)
- // but this page will be updated asynchronously if the extension
- // becomes enabled/disabled. It also might not become enabled or
- // disabled, because the user might e.g. get prompted when enabling
- // and choose not to.
- e.preventDefault();
- });
- }
+ // The 'Reload' terminated link.
+ row.setupColumn('.terminated-reload-link', 'terminatedReload', 'click',
+ function(e) {
+ chrome.developerPrivate.reload(extension.id, {failQuietly: true});
+ });
- enable.querySelector('input').checked = extension.enabled;
- }
+ // The 'Repair' corrupted link.
+ row.setupColumn('.corrupted-repair-button', 'repair', 'click',
+ function(e) {
+ chrome.developerPrivate.repairExtension(extension.id);
+ });
+
+ // The 'Enabled' checkbox.
+ row.setupColumn('.enable-checkbox input', 'enabled', 'change',
+ function(e) {
+ var checked = e.target.checked;
+ // TODO(devlin): What should we do if this fails?
+ chrome.management.setEnabled(extension.id, checked);
+
+ // This may seem counter-intuitive (to not set/clear the checkmark)
+ // but this page will be updated asynchronously if the extension
+ // becomes enabled/disabled. It also might not become enabled or
+ // disabled, because the user might e.g. get prompted when enabling
+ // and choose not to.
+ e.preventDefault();
+ });
// 'Remove' button.
var trashTemplate = $('template-collection').querySelector('.trash');
var trash = trashTemplate.cloneNode(true);
trash.title = loadTimeData.getString('extensionUninstall');
+ trash.hidden = !extension.userMayModify;
+ trash.setAttribute('column-type', 'trash');
trash.addEventListener('click', function(e) {
- butterBarVisibility[extension.id] = false;
- chrome.send('extensionSettingsUninstall', [extension.id]);
+ trash.classList.add('open');
+ trash.classList.toggle('mouse-clicked', e.detail > 0);
+ if (this.uninstallIsShowing_)
+ return;
+ this.uninstallIsShowing_ = true;
+ chrome.management.uninstall(extension.id,
+ {showConfirmDialog: true},
+ function() {
+ // TODO(devlin): What should we do if the uninstall fails?
+ this.uninstallIsShowing_ = false;
+
+ if (trash.classList.contains('mouse-clicked'))
+ trash.blur();
+
+ if (chrome.runtime.lastError) {
+ // The uninstall failed (e.g. a cancel). Allow the trash to close.
+ trash.classList.remove('open');
+ } else {
+ // Leave the trash open if the uninstall succeded. Otherwise it can
+ // half-close right before it's removed when the DOM is modified.
+ }
+ }.bind(this));
+ }.bind(this));
+ row.querySelector('.enable-controls').appendChild(trash);
+
+ // Developer mode ////////////////////////////////////////////////////////
+
+ // The path, if provided by unpacked extension.
+ row.setupColumn('.load-path a:first-of-type', 'dev-loadPath', 'click',
+ function(e) {
+ chrome.developerPrivate.showPath(extension.id);
+ e.preventDefault();
+ });
+
+ // Maintain the order that nodes should be in when creating as well as
+ // when adding only one new row.
+ this.insertBefore(row, nextNode);
+ this.updateNode_(extension, row);
+
+ var nextRow = null;
+ if (nextNode)
+ nextRow = assertInstanceof(nextNode, ExtensionFocusRow);
+
+ this.focusGrid_.addRowBefore(row, nextRow);
+ },
+
+ /**
+ * Updates an HTML element for the extension metadata given in |extension|.
+ * @param {!ExtensionInfo} extension A dictionary of extension metadata.
+ * @param {!ExtensionFocusRow} row The node that is being updated.
+ * @private
+ */
+ updateNode_: function(extension, row) {
+ var isActive =
+ extension.state == chrome.developerPrivate.ExtensionState.ENABLED;
+ row.classList.toggle('inactive-extension', !isActive);
+
+ // Hack to keep the closure compiler happy about |remove|.
+ // TODO(hcarmona): Remove this hack when the closure compiler is updated.
+ var node = /** @type {Element} */ (row);
+ node.classList.remove('policy-controlled', 'may-not-modify',
+ 'may-not-remove');
+ var classes = [];
+ if (!extension.userMayModify) {
+ classes.push('policy-controlled', 'may-not-modify');
+ } else if (extension.dependentExtensions.length > 0) {
+ classes.push('may-not-remove', 'may-not-modify');
+ } else if (extension.mustRemainInstalled) {
+ classes.push('may-not-remove');
+ } else if (extension.disableReasons.suspiciousInstall ||
+ extension.disableReasons.corruptInstall ||
+ extension.disableReasons.updateRequired) {
+ classes.push('may-not-modify');
+ }
+ row.classList.add.apply(row.classList, classes);
+
+ var item = row.querySelector('.extension-list-item');
+ item.style.backgroundImage = 'url(' + extension.iconUrl + ')';
+
+ this.setText_(row, '.extension-title', extension.name);
+ this.setText_(row, '.extension-version', extension.version);
+ this.setText_(row, '.location-text', extension.locationText || '');
+ this.setText_(row, '.blacklist-text', extension.blacklistText || '');
+ this.setText_(row, '.extension-description', extension.description);
+
+ // The 'Show Browser Action' button.
+ this.updateVisibility_(row, '.show-button',
+ isActive && extension.actionButtonHidden);
+
+ // The 'allow in incognito' checkbox.
+ this.updateVisibility_(row, '.incognito-control',
+ isActive && this.incognitoAvailable_,
+ function(item) {
+ var incognito = item.querySelector('input');
+ incognito.disabled = !extension.incognitoAccess.isEnabled;
+ incognito.checked = extension.incognitoAccess.isActive;
+ });
+
+ // Hide butterBar if incognito is not enabled for the extension.
+ var butterBar = row.querySelector('.butter-bar');
+ butterBar.hidden =
+ butterBar.hidden || !extension.incognitoAccess.isEnabled;
+
+ // The 'collect errors' checkbox. This should only be visible if the
+ // error console is enabled - we can detect this by the existence of the
+ // |errorCollectionEnabled| property.
+ this.updateVisibility_(
+ row, '.error-collection-control',
+ isActive && extension.errorCollection.isEnabled,
+ function(item) {
+ item.querySelector('input').checked =
+ extension.errorCollection.isActive;
+ });
+
+ // The 'allow on all urls' checkbox. This should only be visible if
+ // active script restrictions are enabled. If they are not enabled, no
+ // extensions should want all urls.
+ this.updateVisibility_(
+ row, '.all-urls-control',
+ isActive && extension.runOnAllUrls.isEnabled,
+ function(item) {
+ item.querySelector('input').checked = extension.runOnAllUrls.isActive;
+ });
+
+ // The 'allow file:// access' checkbox.
+ this.updateVisibility_(row, '.file-access-control',
+ isActive && extension.fileAccess.isEnabled,
+ function(item) {
+ item.querySelector('input').checked = extension.fileAccess.isActive;
+ });
+
+ // The 'Options' button or link, depending on its behaviour.
+ var optionsEnabled = isActive && !!extension.optionsPage;
+ this.updateVisibility_(row, '.options-link', optionsEnabled &&
+ extension.optionsPage.openInTab);
+ this.updateVisibility_(row, '.options-button', optionsEnabled &&
+ !extension.optionsPage.openInTab);
+
+ // The 'View in Web Store/View Web Site' link.
+ var siteLinkEnabled = !!extension.homePage.url &&
+ !this.enableAppInfoDialog_;
+ this.updateVisibility_(row, '.site-link', siteLinkEnabled,
+ function(item) {
+ item.href = extension.homePage.url;
+ item.textContent = loadTimeData.getString(
+ extension.homePage.specified ? 'extensionSettingsVisitWebsite' :
+ 'extensionSettingsVisitWebStore');
+ });
+
+ var isUnpacked =
+ extension.location == chrome.developerPrivate.Location.UNPACKED;
+ // The 'Reload' link.
+ this.updateVisibility_(row, '.reload-link', isUnpacked);
+
+ // The 'Launch' link.
+ this.updateVisibility_(
+ row, '.launch-link',
+ isUnpacked && extension.type ==
+ chrome.developerPrivate.ExtensionType.PLATFORM_APP);
+
+ // The 'Errors' link.
+ var hasErrors = extension.runtimeErrors.length > 0 ||
+ extension.manifestErrors.length > 0;
+ this.updateVisibility_(row, '.errors-link', hasErrors, function(item) {
+ var Level = chrome.developerPrivate.ErrorLevel;
+
+ var map = {};
+ map[Level.LOG] = {weight: 0, name: 'extension-error-info-icon'};
+ map[Level.WARN] = {weight: 1, name: 'extension-error-warning-icon'};
+ map[Level.ERROR] = {weight: 2, name: 'extension-error-fatal-icon'};
+
+ // Find the highest severity of all the errors; manifest errors all have
+ // a 'warning' level severity.
+ var highestSeverity = extension.runtimeErrors.reduce(
+ function(prev, error) {
+ return map[error.severity].weight > map[prev].weight ?
+ error.severity : prev;
+ }, extension.manifestErrors.length ? Level.WARN : Level.LOG);
+
+ // Adjust the class on the icon.
+ var icon = item.querySelector('.extension-error-icon');
+ // TODO(hcarmona): Populate alt text with a proper description since
+ // this icon conveys the severity of the error. (info, warning, fatal).
+ icon.alt = '';
+ icon.className = 'extension-error-icon'; // Remove other classes.
+ icon.classList.add(map[highestSeverity].name);
+ });
+
+ // The 'Reload' terminated link.
+ var isTerminated =
+ extension.state == chrome.developerPrivate.ExtensionState.TERMINATED;
+ this.updateVisibility_(row, '.terminated-reload-link', isTerminated);
+
+ // The 'Repair' corrupted link.
+ var canRepair = !isTerminated &&
+ extension.disableReasons.corruptInstall &&
+ extension.location ==
+ chrome.developerPrivate.Location.FROM_STORE;
+ this.updateVisibility_(row, '.corrupted-repair-button', canRepair);
+
+ // The 'Enabled' checkbox.
+ var isOK = !isTerminated && !canRepair;
+ this.updateVisibility_(row, '.enable-checkbox', isOK, function(item) {
+ var enableCheckboxDisabled =
+ !extension.userMayModify ||
+ extension.disableReasons.suspiciousInstall ||
+ extension.disableReasons.corruptInstall ||
+ extension.disableReasons.updateRequired ||
+ extension.installedByCustodian ||
+ extension.dependentExtensions.length > 0;
+ item.querySelector('input').disabled = enableCheckboxDisabled;
+ item.querySelector('input').checked = isActive;
});
- node.querySelector('.enable-controls').appendChild(trash);
+
+ // Button for extensions controlled by policy.
+ var controlNode = row.querySelector('.enable-controls');
+ var indicator =
+ controlNode.querySelector('.controlled-extension-indicator');
+ var needsIndicator = isOK &&
+ !extension.userMayModify &&
+ extension.policyText;
+ // TODO(treib): If userMayModify is false, but policyText is empty, that
+ // indicates this extension is controlled by something else than
+ // enterprise policy (such as the profile being supervised). For now, just
+ // don't show the indicator in this case. We should really handle this
+ // better though (ie use a different text and icon).
+
+ if (needsIndicator && !indicator) {
+ indicator = new cr.ui.ControlledIndicator();
+ indicator.classList.add('controlled-extension-indicator');
+ indicator.setAttribute('controlled-by', 'policy');
+ var textPolicy = extension.policyText || '';
+ indicator.setAttribute('textpolicy', textPolicy);
+ indicator.image.setAttribute('aria-label', textPolicy);
+ controlNode.appendChild(indicator);
+ indicator.querySelector('div').setAttribute('column-type',
+ 'enterprise');
+ } else if (!needsIndicator && indicator) {
+ controlNode.removeChild(indicator);
+ }
// Developer mode ////////////////////////////////////////////////////////
// First we have the id.
- var idLabel = node.querySelector('.extension-id');
+ var idLabel = row.querySelector('.extension-id');
idLabel.textContent = ' ' + extension.id;
// Then the path, if provided by unpacked extension.
- if (extension.isUnpacked) {
- var loadPath = node.querySelector('.load-path');
- loadPath.hidden = false;
- var pathLink = loadPath.querySelector('a:nth-of-type(1)');
- pathLink.textContent = ' ' + extension.prettifiedPath;
- pathLink.addEventListener('click', function(e) {
- chrome.send('extensionSettingsShowPath', [String(extension.id)]);
- e.preventDefault();
- });
- }
+ this.updateVisibility_(row, '.load-path', isUnpacked,
+ function(item) {
+ item.querySelector('a:first-of-type').textContent =
+ ' ' + extension.prettifiedPath;
+ });
// Then the 'managed, cannot uninstall/disable' message.
- if (extension.managedInstall || extension.recommendedInstall) {
- node.querySelector('.managed-message').hidden = false;
- } else {
- if (extension.suspiciousInstall) {
- // Then the 'This isn't from the webstore, looks suspicious' message.
- node.querySelector('.suspicious-install-message').hidden = false;
- }
- if (extension.corruptInstall) {
- // Then the 'This is a corrupt extension' message.
- node.querySelector('.corrupt-install-message').hidden = false;
- }
- }
+ // We would like to hide managed installed message since this
+ // extension is disabled.
+ var isRequired =
+ !extension.userMayModify || extension.mustRemainInstalled;
+ this.updateVisibility_(row, '.managed-message', isRequired &&
+ !extension.disableReasons.updateRequired);
+
+ // Then the 'This isn't from the webstore, looks suspicious' message.
+ this.updateVisibility_(row, '.suspicious-install-message', !isRequired &&
+ extension.disableReasons.suspiciousInstall);
+
+ // Then the 'This is a corrupt extension' message.
+ this.updateVisibility_(row, '.corrupt-install-message', !isRequired &&
+ extension.disableReasons.corruptInstall);
+
+ // Then the 'An update required by enterprise policy' message. Note that
+ // a force-installed extension might be disabled due to being outdated
+ // as well.
+ this.updateVisibility_(row, '.update-required-message',
+ extension.disableReasons.updateRequired);
- if (extension.dependentExtensions.length > 0) {
- var dependentMessage =
- node.querySelector('.dependent-extensions-message');
- dependentMessage.hidden = false;
- var dependentList = dependentMessage.querySelector('ul');
+ // The 'following extensions depend on this extension' list.
+ var hasDependents = extension.dependentExtensions.length > 0;
+ row.classList.toggle('developer-extras', hasDependents);
+ this.updateVisibility_(row, '.dependent-extensions-message',
+ hasDependents, function(item) {
+ var dependentList = item.querySelector('ul');
+ dependentList.textContent = '';
var dependentTemplate = $('template-collection').querySelector(
'.dependent-list-item');
- extension.dependentExtensions.forEach(function(elem) {
+ extension.dependentExtensions.forEach(function(dependentId) {
+ var dependentExtension = null;
+ for (var i = 0; i < this.extensions_.length; ++i) {
+ if (this.extensions_[i].id == dependentId) {
+ dependentExtension = this.extensions_[i];
+ break;
+ }
+ }
+ if (!dependentExtension)
+ return;
+
var depNode = dependentTemplate.cloneNode(true);
- depNode.querySelector('.dep-extension-title').textContent = elem.name;
- depNode.querySelector('.dep-extension-id').textContent = elem.id;
+ depNode.querySelector('.dep-extension-title').textContent =
+ dependentExtension.name;
+ depNode.querySelector('.dep-extension-id').textContent =
+ dependentExtension.id;
dependentList.appendChild(depNode);
- });
- }
+ }, this);
+ }.bind(this));
+
+ // The active views.
+ this.updateVisibility_(row, '.active-views', extension.views.length > 0,
+ function(item) {
+ var link = item.querySelector('a');
+
+ // Link needs to be an only child before the list is updated.
+ while (link.nextElementSibling)
+ item.removeChild(link.nextElementSibling);
- // Then active views.
- if (extension.views.length > 0) {
- var activeViews = node.querySelector('.active-views');
- activeViews.hidden = false;
- var link = activeViews.querySelector('a');
+ // Link needs to be cleaned up if it was used before.
+ link.textContent = '';
+ if (link.clickHandler)
+ link.removeEventListener('click', link.clickHandler);
extension.views.forEach(function(view, i) {
- var displayName = view.generatedBackgroundPage ?
- loadTimeData.getString('backgroundPage') : view.path;
+ if (view.type == chrome.developerPrivate.ViewType.EXTENSION_DIALOG ||
+ view.type == chrome.developerPrivate.ViewType.EXTENSION_POPUP) {
+ return;
+ }
+ var displayName;
+ if (view.url.indexOf('chrome-extension://') == 0) {
+ var pathOffset = 'chrome-extension://'.length + 32 + 1;
+ displayName = view.url.substring(pathOffset);
+ if (displayName == '_generated_background_page.html')
+ displayName = loadTimeData.getString('backgroundPage');
+ } else {
+ displayName = view.url;
+ }
var label = displayName +
(view.incognito ?
' ' + loadTimeData.getString('viewIncognito') : '') +
(view.renderProcessId == -1 ?
' ' + loadTimeData.getString('viewInactive') : '');
link.textContent = label;
- link.addEventListener('click', function(e) {
- // TODO(estade): remove conversion to string?
- chrome.send('extensionSettingsInspect', [
- String(extension.id),
- String(view.renderProcessId),
- String(view.renderViewId),
- view.incognito
- ]);
- });
+ link.clickHandler = function(e) {
+ chrome.developerPrivate.openDevTools({
+ extensionId: extension.id,
+ renderProcessId: view.renderProcessId,
+ renderViewId: view.renderViewId,
+ incognito: view.incognito
+ });
+ };
+ link.addEventListener('click', link.clickHandler);
if (i < extension.views.length - 1) {
link = link.cloneNode(true);
- activeViews.appendChild(link);
+ item.appendChild(link);
}
});
- }
- // The extension warnings (describing runtime issues).
- if (extension.warnings) {
- var panel = node.querySelector('.extension-warnings');
- panel.hidden = false;
- var list = panel.querySelector('ul');
- extension.warnings.forEach(function(warning) {
- list.appendChild(document.createElement('li')).innerText = warning;
- });
- }
+ var allLinks = item.querySelectorAll('a');
+ for (var i = 0; i < allLinks.length; ++i) {
+ allLinks[i].setAttribute('column-type', 'dev-activeViews' + i);
+ }
+ });
- // If the ErrorConsole is enabled, we should have manifest and/or runtime
- // errors. Otherwise, we may have install warnings. We should not have
- // both ErrorConsole errors and install warnings.
- if (extension.manifestErrors) {
- var panel = node.querySelector('.manifest-errors');
- panel.hidden = false;
- panel.appendChild(new extensions.ExtensionErrorList(
- extension.manifestErrors));
- }
- if (extension.runtimeErrors) {
- var panel = node.querySelector('.runtime-errors');
- panel.hidden = false;
- panel.appendChild(new extensions.ExtensionErrorList(
- extension.runtimeErrors));
- }
- if (extension.installWarnings) {
- var panel = node.querySelector('.install-warnings');
- panel.hidden = false;
- var list = panel.querySelector('ul');
- extension.installWarnings.forEach(function(warning) {
+ // The extension warnings (describing runtime issues).
+ this.updateVisibility_(row, '.extension-warnings',
+ extension.runtimeWarnings.length > 0,
+ function(item) {
+ var warningList = item.querySelector('ul');
+ warningList.textContent = '';
+ extension.runtimeWarnings.forEach(function(warning) {
var li = document.createElement('li');
- li.innerText = warning.message;
- list.appendChild(li);
+ warningList.appendChild(li).innerText = warning;
});
- }
+ });
+
+ // Install warnings.
+ this.updateVisibility_(row, '.install-warnings',
+ extension.installWarnings.length > 0,
+ function(item) {
+ var installWarningList = item.querySelector('ul');
+ installWarningList.textContent = '';
+ if (extension.installWarnings) {
+ extension.installWarnings.forEach(function(warning) {
+ var li = document.createElement('li');
+ li.innerText = warning;
+ installWarningList.appendChild(li);
+ });
+ }
+ });
- this.appendChild(node);
if (location.hash.substr(1) == extension.id) {
// Scroll beneath the fixed header so that the extension is not
// obscured.
- var topScroll = node.offsetTop - $('page-header').offsetHeight;
- var pad = parseInt(window.getComputedStyle(node, null).marginTop, 10);
+ var topScroll = row.offsetTop - $('page-header').offsetHeight;
+ var pad = parseInt(window.getComputedStyle(row, null).marginTop, 10);
if (!isNaN(pad))
topScroll -= pad / 2;
setScrollTopForDocument(document, topScroll);
}
+
+ row.updateFocusableElements();
+ },
+
+ /**
+ * Updates an element's textContent.
+ * @param {Element} node Ancestor of the element specified by |query|.
+ * @param {string} query A query to select an element in |node|.
+ * @param {string} textContent
+ * @private
+ */
+ setText_: function(node, query, textContent) {
+ node.querySelector(query).textContent = textContent;
+ },
+
+ /**
+ * Updates an element's visibility and calls |shownCallback| if it is
+ * visible.
+ * @param {Element} node Ancestor of the element specified by |query|.
+ * @param {string} query A query to select an element in |node|.
+ * @param {boolean} visible Whether the element should be visible or not.
+ * @param {function(Element)=} opt_shownCallback Callback if the element is
+ * visible. The element passed in will be the element specified by
+ * |query|.
+ * @private
+ */
+ updateVisibility_: function(node, query, visible, opt_shownCallback) {
+ var item = assert(node.querySelector(query));
+ item.hidden = !visible;
+ if (visible && opt_shownCallback)
+ opt_shownCallback(item);
},
/**
@@ -531,8 +1084,10 @@ cr.define('options', function() {
return;
// Get the extension from the given id.
- var extension = this.data_.extensions.filter(function(extension) {
- return extension.enabled && extension.id == extensionId;
+ var extension = this.extensions_.filter(function(extension) {
+ return extension.state ==
+ chrome.developerPrivate.ExtensionState.ENABLED &&
+ extension.id == extensionId;
})[0];
if (!extension)
@@ -540,24 +1095,85 @@ cr.define('options', function() {
if (scroll)
this.scrollToNode_(extensionId);
+
// Add the options query string. Corner case: the 'options' query string
// will clobber the 'id' query string if the options link is clicked when
// 'id' is in the URL, or if both query strings are in the URL.
uber.replaceState({}, '?options=' + extensionId);
- extensions.ExtensionOptionsOverlay.getInstance().
- setExtensionAndShowOverlay(extensionId,
- extension.name,
- extension.icon);
-
+ var overlay = extensions.ExtensionOptionsOverlay.getInstance();
+ var shownCallback = function() {
+ // This overlay doesn't get focused automatically as <extensionoptions>
+ // is created after the overlay is shown.
+ if (cr.ui.FocusOutlineManager.forDocument(document).visible)
+ overlay.setInitialFocus();
+ };
+ overlay.setExtensionAndShow(extensionId, extension.name,
+ extension.iconUrl, shownCallback);
this.optionsShown_ = true;
- $('overlay').addEventListener('cancelOverlay', function() {
- this.optionsShown_ = false;
- }.bind(this));
+
+ var self = this;
+ $('overlay').addEventListener('cancelOverlay', function f() {
+ self.optionsShown_ = false;
+ $('overlay').removeEventListener('cancelOverlay', f);
+
+ // Remove the options query string.
+ uber.replaceState({}, '');
+ });
+
+ // TODO(dbeam): why do we need to focus <extensionoptions> before and
+ // after its showing animation? Makes very little sense to me.
+ overlay.setInitialFocus();
+ },
+
+ /**
+ * Hides the extension options overlay for the extension with id
+ * |extensionId|. If there is an overlay showing for a different extension,
+ * nothing happens.
+ * @param {string} extensionId ID of the extension to hide.
+ * @private
+ */
+ hideEmbeddedExtensionOptions_: function(extensionId) {
+ if (!this.optionsShown_)
+ return;
+
+ var overlay = extensions.ExtensionOptionsOverlay.getInstance();
+ if (overlay.getExtensionId() == extensionId)
+ overlay.close();
},
+
+ /**
+ * Updates the node for the extension.
+ * @param {!ExtensionInfo} extension The information about the extension to
+ * update.
+ * @private
+ */
+ updateExtension_: function(extension) {
+ var currIndex = this.getIndexOfExtension_(extension.id);
+ if (currIndex != -1) {
+ // If there is a current version of the extension, update it with the
+ // new version.
+ this.extensions_[currIndex] = extension;
+ } else {
+ // If the extension isn't found, push it back and sort. Technically, we
+ // could optimize by inserting it at the right location, but since this
+ // only happens on extension install, it's not worth it.
+ this.extensions_.push(extension);
+ this.extensions_.sort(compareExtensions);
+ }
+
+ var node = /** @type {ExtensionFocusRow} */ ($(extension.id));
+ if (node) {
+ this.updateNode_(extension, node);
+ } else {
+ var nextExt = this.extensions_[this.extensions_.indexOf(extension) + 1];
+ this.createNode_(extension, nextExt ? $(nextExt.id) : null);
+ }
+ }
};
return {
- ExtensionsList: ExtensionsList
+ ExtensionList: ExtensionList,
+ ExtensionListDelegate: ExtensionListDelegate
};
});
diff --git a/chromium/chrome/browser/resources/extensions/extension_load_error.html b/chromium/chrome/browser/resources/extensions/extension_load_error.html
index 6feec95b289..9aed931ce17 100644
--- a/chromium/chrome/browser/resources/extensions/extension_load_error.html
+++ b/chromium/chrome/browser/resources/extensions/extension_load_error.html
@@ -1,4 +1,4 @@
-<!DOCTYPE html>
+<!doctype html>
<!--
Copyright 2014 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be found
diff --git a/chromium/chrome/browser/resources/extensions/extension_loader.js b/chromium/chrome/browser/resources/extensions/extension_loader.js
index a71baafaec7..95def83d8bc 100644
--- a/chromium/chrome/browser/resources/extensions/extension_loader.js
+++ b/chromium/chrome/browser/resources/extensions/extension_loader.js
@@ -80,7 +80,7 @@ cr.define('extensions', function() {
/**
* An array of Failures for keeping track of multiple active failures.
- * @type {Array.<Failure>}
+ * @type {Array<Failure>}
* @private
*/
this.failures_ = [];
@@ -103,7 +103,7 @@ cr.define('extensions', function() {
/**
* Add a failure to failures_ array. If there is already a displayed
* failure, display the additional failures element.
- * @param {Array.<Object>} failures Array of failures containing paths,
+ * @param {Array<Object>} failures Array of failures containing paths,
* errors, and manifests.
* @private
*/
@@ -186,17 +186,31 @@ cr.define('extensions', function() {
ExtensionLoader.prototype = {
/**
+ * Whether or not we are currently loading an unpacked extension.
+ * @private {boolean}
+ */
+ isLoading_: false,
+
+ /**
* Begin the sequence of loading an unpacked extension. If an error is
* encountered, this object will get notified via notifyFailed().
*/
loadUnpacked: function() {
- chrome.send('extensionLoaderLoadUnpacked');
+ if (this.isLoading_) // Only one running load at a time.
+ return;
+ this.isLoading_ = true;
+ chrome.developerPrivate.loadUnpacked({failQuietly: true}, function() {
+ // Check lastError to avoid the log, but don't do anything with it -
+ // error-handling is done on the C++ side.
+ var lastError = chrome.runtime.lastError;
+ this.isLoading_ = false;
+ }.bind(this));
},
/**
* Notify the ExtensionLoader that loading an unpacked extension failed.
* Add the failure to failures_ and show the ExtensionLoadError.
- * @param {Array.<Object>} failures Array of failures containing paths,
+ * @param {Array<Object>} failures Array of failures containing paths,
* errors, and manifests.
*/
notifyFailed: function(failures) {
@@ -206,7 +220,7 @@ cr.define('extensions', function() {
/**
* A static forwarding function for ExtensionLoader.notifyFailed.
- * @param {Array.<Object>} failures Array of failures containing paths,
+ * @param {Array<Object>} failures Array of failures containing paths,
* errors, and manifests.
* @see ExtensionLoader.notifyFailed
*/
diff --git a/chromium/chrome/browser/resources/extensions/extension_options_overlay.js b/chromium/chrome/browser/resources/extensions/extension_options_overlay.js
index 11f05637cca..8c09d153afc 100644
--- a/chromium/chrome/browser/resources/extensions/extension_options_overlay.js
+++ b/chromium/chrome/browser/resources/extensions/extension_options_overlay.js
@@ -24,6 +24,13 @@ cr.define('extensions', function() {
showOverlay_: null,
/**
+ * The id of the extension that this options page display.
+ * @type {string}
+ * @private
+ */
+ extensionId_: '',
+
+ /**
* Initialize the page.
* @param {function(HTMLDivElement)} showOverlay The function to show or
* hide the ExtensionOptionsOverlay; this should take a single parameter
@@ -40,6 +47,19 @@ cr.define('extensions', function() {
this.showOverlay_ = showOverlay;
},
+ setInitialFocus: function() {
+ this.getExtensionOptions_().focus();
+ },
+
+ /**
+ * @return {?Element}
+ * @private
+ */
+ getExtensionOptions_: function() {
+ return $('extension-options-overlay-guest').querySelector(
+ 'extensionoptions');
+ },
+
/**
* Handles a click on the close button.
* @param {Event} event The click event.
@@ -47,17 +67,11 @@ cr.define('extensions', function() {
*/
handleDismiss_: function(event) {
this.setVisible_(false);
- var extensionoptions =
- $('extension-options-overlay-guest')
- .querySelector('extensionoptions');
-
+ var extensionoptions = this.getExtensionOptions_();
if (extensionoptions)
$('extension-options-overlay-guest').removeChild(extensionoptions);
$('extension-options-overlay-icon').removeAttribute('src');
-
- // Remove the options query string.
- uber.replaceState({}, '');
},
/**
@@ -67,14 +81,22 @@ cr.define('extensions', function() {
* @param {string} extensionName The name of the extension, which is used
* as the header of the overlay.
* @param {string} extensionIcon The URL of the extension's icon.
+ * @param {function():void} shownCallback A function called when
+ * showing completes.
* @suppress {checkTypes}
* TODO(vitalyp): remove the suppression after adding
* chrome/renderer/resources/extensions/extension_options.js
* to dependencies.
*/
- setExtensionAndShowOverlay: function(extensionId,
- extensionName,
- extensionIcon) {
+ setExtensionAndShow: function(extensionId,
+ extensionName,
+ extensionIcon,
+ shownCallback) {
+ var overlay = $('extension-options-overlay');
+ var overlayHeader = $('extension-options-overlay-header');
+ var overlayGuest = $('extension-options-overlay-guest');
+ var overlayStyle = window.getComputedStyle(overlay);
+
$('extension-options-overlay-title').textContent = extensionName;
$('extension-options-overlay-icon').src = extensionIcon;
@@ -82,76 +104,111 @@ cr.define('extensions', function() {
var extensionoptions = new window.ExtensionOptions();
extensionoptions.extension = extensionId;
- extensionoptions.autosize = 'on';
+ this.extensionId_ = extensionId;
// The <extensionoptions> content's size needs to be restricted to the
- // bounds of the overlay window. The overlay gives a min width and
- // max height, but the maxheight does not include our header height
- // (title and close button), so we need to subtract that to get the
- // max height for the extension options.
- var headerHeight = $('extension-options-overlay-header').offsetHeight;
- var overlayMaxHeight =
- parseInt($('extension-options-overlay').style.maxHeight, 10);
- extensionoptions.maxheight = overlayMaxHeight - headerHeight;
-
- extensionoptions.minwidth =
- parseInt(window.getComputedStyle($('extension-options-overlay'))
- .minWidth, 10);
-
- extensionoptions.setDeferAutoSize(true);
+ // bounds of the overlay window. The overlay gives a minWidth and
+ // maxHeight, but the maxHeight does not include our header height (title
+ // and close button), so we need to subtract that to get the maxHeight
+ // for the extension options.
+ var maxHeight = parseInt(overlayStyle.maxHeight, 10) -
+ overlayHeader.offsetHeight;
+ var minWidth = parseInt(overlayStyle.minWidth, 10);
extensionoptions.onclose = function() {
cr.dispatchSimpleEvent($('overlay'), 'cancelOverlay');
}.bind(this);
+ // Track the current animation (used to grow/shrink the overlay content),
+ // if any. Preferred size changes can fire more rapidly than the
+ // animation speed, and multiple animations running at the same time has
+ // undesirable effects.
+ var animation = null;
+
/**
- * Resize the overlay if the <extensionoptions> changes size.
- * @param {{newHeight: number,
- * newWidth: number,
- * oldHeight: number,
- * oldWidth: number}} evt
+ * Resize the overlay if the <extensionoptions> changes preferred size.
+ * @param {{width: number, height: number}} evt
*/
- extensionoptions.onsizechanged = function(evt) {
- var overlayStyle =
- window.getComputedStyle($('extension-options-overlay'));
- var oldWidth = parseInt(overlayStyle.width, 10);
- var oldHeight = parseInt(overlayStyle.height, 10);
+ extensionoptions.onpreferredsizechanged = function(evt) {
+ var oldOverlayWidth = parseInt(overlayStyle.width, 10);
+ var oldOverlayHeight = parseInt(overlayStyle.height, 10);
+ var newOverlayWidth = Math.max(evt.width, minWidth);
+ // |evt.height| is just the new overlay guest height, and does not
+ // include the overlay header height, so it needs to be added.
+ var newOverlayHeight =
+ Math.min(evt.height + overlayHeader.offsetHeight, maxHeight);
// animationTime is the amount of time in ms that will be used to resize
// the overlay. It is calculated by multiplying the pythagorean distance
// between old and the new size (in px) with a constant speed of
// 0.25 ms/px.
- var animationTime = 0.25 * Math.sqrt(
- Math.pow(evt.newWidth - oldWidth, 2) +
- Math.pow(evt.newHeight - oldHeight, 2));
-
- var player = $('extension-options-overlay').animate([
- {width: oldWidth + 'px', height: oldHeight + 'px'},
- {width: evt.newWidth + 'px', height: evt.newHeight + 'px'}
+ var loading = document.documentElement.classList.contains('loading');
+ var animationTime = loading ? 0 :
+ 0.25 * Math.sqrt(Math.pow(newOverlayWidth - oldOverlayWidth, 2) +
+ Math.pow(newOverlayHeight - oldOverlayHeight, 2));
+
+ if (animation)
+ animation.cancel();
+
+ // The header height must be added to the (old and new) preferred
+ // heights to get the full overlay heights.
+ animation = overlay.animate([
+ {width: oldOverlayWidth + 'px', height: oldOverlayHeight + 'px'},
+ {width: newOverlayWidth + 'px', height: newOverlayHeight + 'px'}
], {
duration: animationTime,
delay: 0
});
- player.onfinish = function(e) {
- // Allow the <extensionoptions> to autosize now that the overlay
- // has resized, and move it back on-screen.
- extensionoptions.resumeDeferredAutoSize();
- $('extension-options-overlay-guest').style.position = 'static';
- $('extension-options-overlay-guest').style.left = 'auto';
+ animation.onfinish = function(e) {
+ animation = null;
+
+ // The <extensionoptions> element is ready to place back in the
+ // overlay. Make sure that it's sized to take up the full width/height
+ // of the overlay.
+ overlayGuest.style.position = '';
+ overlayGuest.style.left = '';
+ overlayGuest.style.width = newOverlayWidth + 'px';
+ // |newOverlayHeight| includes the header height, so it needs to be
+ // subtracted to get the new guest height.
+ overlayGuest.style.height =
+ (newOverlayHeight - overlayHeader.offsetHeight) + 'px';
+
+ if (shownCallback) {
+ shownCallback();
+ shownCallback = null;
+ }
};
}.bind(this);
- // Don't allow the <extensionoptions> to autosize until the overlay
- // animation is complete.
- extensionoptions.setDeferAutoSize(true);
+ // Move the <extensionoptions> off screen until the overlay is ready.
+ overlayGuest.style.position = 'fixed';
+ overlayGuest.style.left = window.outerWidth + 'px';
+ // Begin rendering at the default dimensions. This is also necessary to
+ // cancel any width/height set on a previous render.
+ // TODO(kalman): This causes a visual jag where the overlay guest shows
+ // up briefly. It would be better to render this off-screen first, then
+ // swap it in place. See crbug.com/408274.
+ // This may also solve crbug.com/431001 (width is 0 on initial render).
+ overlayGuest.style.width = '';
+ overlayGuest.style.height = '';
+
+ overlayGuest.appendChild(extensionoptions);
+ },
- // Move the <extensionoptions> off screen until the overlay is ready
- $('extension-options-overlay-guest').style.position = 'fixed';
- $('extension-options-overlay-guest').style.left =
- window.outerWidth + 'px';
+ /**
+ * Dispatches a 'cancelOverlay' event on the $('overlay') element.
+ */
+ close: function() {
+ cr.dispatchSimpleEvent($('overlay'), 'cancelOverlay');
+ },
- $('extension-options-overlay-guest').appendChild(extensionoptions);
+ /**
+ * Returns extension id that this options page set.
+ * @return {string}
+ */
+ getExtensionId: function() {
+ return this.extensionId_;
},
/**
diff --git a/chromium/chrome/browser/resources/extensions/extensions.css b/chromium/chrome/browser/resources/extensions/extensions.css
index 81d1d2137c1..47119b78412 100644
--- a/chromium/chrome/browser/resources/extensions/extensions.css
+++ b/chromium/chrome/browser/resources/extensions/extensions.css
@@ -3,7 +3,7 @@
* found in the LICENSE file. */
html.loading * {
- -webkit-transition-duration: 0 !important;
+ transition-duration: 0ms !important;
}
html:not(.focus-outline-visible)
@@ -16,85 +16,47 @@ html:not(.focus-outline-visible)
overflow-y: hidden;
}
+#extension-settings.showing-banner {
+ margin-top: 45px;
+}
+
/* Developer mode */
#dev-controls {
- -webkit-margin-end: 20px;
- -webkit-transition: padding 100ms, height 100ms, opacity 100ms;
- border-bottom: 1px solid #eee;
+ -webkit-margin-end: 0;
height: 0;
- opacity: 0;
overflow: hidden;
}
-#dev-controls .buttons-container {
- -webkit-padding-end: 3px;
- -webkit-padding-start: 4px;
+#dev-controls.animated {
+ transition: height 150ms;
}
-#dev-controls .buttons-container {
- display: -webkit-box;
- height: 32px; /* height + padding-top matches #dev-controls height. */
- padding-top: 13px;
+.dev-mode #dev-controls {
+ border-bottom: 1px solid #eee;
}
-#dev-controls button {
- white-space: nowrap;
+#dev-controls > * {
+ padding: 8px 3px;
}
-#apps-developer-tools-promo {
- -webkit-padding-end: 3px;
- align-items: center;
- border-top: 1px solid #eee;
+#dev-controls .button-container {
+ -webkit-padding-end: 12px;
+ -webkit-padding-start: 12px;
display: flex;
- font-size: 13px;
- margin-top: 7px; /* This matches #dev-controls padding-bottom. */
- padding-top: 5px;
-}
-
-#apps-developer-tools-promo img {
- content: url(apps_developer_tools_promo_48.png);
-}
-
-#apps-developer-tools-promo-text {
- -webkit-margin-start: 5px;
+ flex-wrap: wrap;
}
-#apps-developer-tools-promo-close-wrapper {
- display: flex;
- flex-grow: 1;
- justify-content: flex-end;
-}
-
-#apps-developer-tools-promo .close-button {
- background: url(chrome://theme/IDR_CLOSE_DIALOG) no-repeat center center;
- border: 0;
- height: 14px;
- width: 14px;
- z-index: 1;
-}
-
-#apps-developer-tools-promo .close-button:hover {
- background-image: url(chrome://theme/IDR_CLOSE_DIALOG_H);
-}
-
-#apps-developer-tools-promo .close-button:active {
- background-image: url(chrome://theme/IDR_CLOSE_DIALOG_P);
-}
-
-#extension-settings.dev-mode #dev-controls {
- -webkit-transition-duration: 250ms;
- height: 45px;
- opacity: 1;
- padding-bottom: 7px;
+#dev-controls button {
+ white-space: nowrap;
}
-#extension-settings.dev-mode.adt-promo #dev-controls {
- height: 105px; /* Allow more height for the Apps Developer Tools promo. */
+#dev-controls .button-container button:not(:last-of-type) {
+ -webkit-margin-end: 5px;
}
#dev-controls-spacer {
- -webkit-box-flex: 1;
+ flex: 1;
}
#dev-toggle {
@@ -123,18 +85,44 @@ html:not(.focus-outline-visible)
text-align: right;
}
-#extension-settings:not(.dev-mode) .developer-extras {
- display: none;
+.extension-code-empty {
+ background-color: #eee;
+ display: inline-block;
+ line-height: 100px; /* Vertically centers text and serves as min-height. */
+ text-align: center;
+ width: 100%;
}
-.developer-extras > div,
-.permanent-warnings > div {
+.extension-details > .developer-extras > div,
+.extension-details > .permanent-warnings > div {
margin: 5px 0;
}
+.dependent-extensions-message,
+.suspicious-install-message {
+ line-height: 150%;
+}
+
+#page-header > .page-banner > .page-banner-gradient {
+ -webkit-margin-end: 0;
+}
+
+#header-controls {
+ right: 13px;
+}
+
+html[dir='rtl'] #header-controls {
+ left: 13px;
+ right: auto;
+}
+
+#page-header > h1::after {
+ -webkit-margin-end: 0;
+}
+
#extension-settings #page-header {
/* These values match the .page values. */
- -webkit-margin-end: 24px;
+ -webkit-margin-end: 0;
min-width: 576px;
}
@@ -148,6 +136,10 @@ html:not(.focus-outline-visible)
font-weight: bold;
}
+#no-extensions {
+ margin-top: 3em;
+}
+
#suggest-gallery {
-webkit-padding-start: 10px;
}
@@ -156,6 +148,7 @@ html:not(.focus-outline-visible)
background: url(chrome://theme/IDR_WEBSTORE_ICON_32) no-repeat left center;
background-size: 32px 32px;
font-size: 1.25em;
+ margin: 24px 12px 12px 12px;
}
html[dir=rtl] #footer-section {
@@ -167,23 +160,14 @@ html[dir=rtl] #footer-section {
line-height: 32px;
}
-.empty-extension-list {
- height: 3em;
-}
-
-.loading #no-extensions,
-.loading #footer-section,
-#extension-settings-list:not(.empty-extension-list) ~ #no-extensions,
-.empty-extension-list ~ #footer-section {
- display: none;
-}
-
.extension-list-item-wrapper {
- margin: 23px 0;
+ margin: 12px 0;
+ padding: 12px;
}
.extension-list-item {
background-repeat: no-repeat;
+ background-size: 48px 48px;
display: -webkit-box;
min-height: 48px;
}
@@ -194,7 +178,7 @@ html[dir=rtl] #footer-section {
}
html[dir='rtl'] .extension-list-item {
- background-position: right;
+ background-position-x: 100%;
}
.extension-title {
@@ -235,10 +219,17 @@ html[dir='rtl'] .extension-list-item {
-webkit-margin-start: 0;
}
+.action-links .errors-link {
+ align-items: center;
+ display: inline-flex;
+ vertical-align: bottom;
+}
+
.extension-details {
-webkit-box-flex: 1;
-webkit-padding-end: 7px;
- -webkit-padding-start: 55px;
+ -webkit-padding-start: 60px;
+ padding-top: 6px;
}
.extension-description,
@@ -247,15 +238,24 @@ html[dir='rtl'] .extension-list-item {
.location-text,
.blacklist-text,
.enable-checkbox input:disabled + .enable-checkbox-text {
- color: rgb(151, 156, 160);
+ color: rgb(115, 119, 122);
}
.enable-controls {
/* Matches right: position of dev controls toggle. */
- -webkit-margin-end: 20px;
+ -webkit-margin-end: 0;
position: relative;
}
+.enable-controls > .controlled-setting-indicator {
+ width: 23px;
+}
+
+.enable-controls > .controlled-setting-indicator > div {
+ left: 11px;
+ right: 11px;
+}
+
/* We use x[is='action-link'] here so that we get higher specifity than the
* action link rules without resorting to the Dark Side (!IMPORTANT). */
.terminated-reload-link[is='action-link'],
@@ -279,6 +279,10 @@ html[dir='rtl'] .extension-list-item {
display: none;
}
+.optional-controls .checkbox {
+ -webkit-margin-end: 12px;
+}
+
.load-path > span {
word-wrap: break-word;
}
@@ -297,8 +301,9 @@ html[dir='rtl'] .extension-list-item {
.install-warnings,
.extension-warnings {
border-radius: 3px;
- margin-top: 5px;
- padding: 2px 5px;
+ line-height: 150%;
+ margin: 8px 0;
+ padding: 8px 12px;
}
.butter-bar {
@@ -317,11 +322,11 @@ html[dir='rtl'] .extension-list-item {
.error-collection-control {
-webkit-margin-start: 5px;
- display: none;
}
-#extension-settings.dev-mode .error-collection-control {
- display: initial;
+#extension-settings:not(.dev-mode) .developer-extras,
+#extension-settings:not(.dev-mode) .error-collection-control {
+ display: none;
}
#font-measuring-div {
@@ -367,16 +372,16 @@ html[dir=rtl] .extension-commands-config {
/* Trash */
#extension-settings .trash {
- -webkit-transition: opacity 200ms;
height: 22px;
opacity: 0.8;
position: relative;
- right: 0;
+ right: -8px;
top: 6px;
+ transition: opacity 200ms;
}
html[dir='rtl'] #extension-settings .trash {
- left: 0;
+ left: -8px;
right: auto;
}
@@ -388,10 +393,13 @@ html[dir='rtl'] #extension-settings .trash {
visibility: hidden;
}
-.extension-highlight {
- background: rgb(250, 250, 250);
- border-radius: 3px;
- padding: 5px 0 5px 5px;
+/* In case the extension is policy controlled the trash icon must be hidden by
+ * setting display:none rather than only setting visibility:hidden to completely
+ * remove it from the layout and make space for the controlled indicator.
+ * TODO(cschuet): rather than hide always replace it with something meaningful.
+ */
+.extension-list-item-wrapper.policy-controlled .trash {
+ display: none;
}
/* Supervised users */
@@ -415,3 +423,7 @@ html[dir='rtl'] #extension-settings .trash {
-webkit-padding-start: 8px;
background-image: none;
}
+
+.extension-highlight {
+ background-color: rgba(0, 0, 0, .05);
+}
diff --git a/chromium/chrome/browser/resources/extensions/extensions.html b/chromium/chrome/browser/resources/extensions/extensions.html
index b7b2ff88c83..fede5ebdc7a 100644
--- a/chromium/chrome/browser/resources/extensions/extensions.html
+++ b/chromium/chrome/browser/resources/extensions/extensions.html
@@ -1,5 +1,5 @@
-<!DOCTYPE html>
-<html i18n-values="dir:textdirection;" class="loading">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
<head>
<meta charset="utf-8">
@@ -11,6 +11,9 @@
<link rel="stylesheet" href="extension_options_overlay.css">
<link rel="stylesheet" href="pack_extension_overlay.css">
<link rel="stylesheet" href="chrome://resources/css/alert_overlay.css">
+<link rel="stylesheet" href="chrome://resources/css/bubble.css">
+<link rel="stylesheet" href="chrome://resources/css/bubble_button.css">
+<link rel="stylesheet" href="chrome://resources/css/controlled_indicator.css">
<link rel="stylesheet" href="chrome://resources/css/chrome_shared.css">
<link rel="stylesheet" href="chrome://resources/css/overlay.css">
<link rel="stylesheet" href="chrome://resources/css/trash.css">
@@ -18,13 +21,18 @@
<script src="chrome://resources/js/action_link.js"></script>
<script src="chrome://resources/js/cr.js"></script>
+<script src="chrome://resources/js/event_tracker.js"></script>
<script src="chrome://resources/js/load_time_data.js"></script>
<script src="chrome://resources/js/util.js"></script>
<script src="chrome://resources/js/cr/ui.js"></script>
<script src="chrome://resources/js/cr/ui/alert_overlay.js"></script>
+<script src="chrome://resources/js/cr/ui/bubble.js"></script>
+<script src="chrome://resources/js/cr/ui/bubble_button.js"></script>
+<script src="chrome://resources/js/cr/ui/controlled_indicator.js"></script>
<script src="chrome://resources/js/cr/ui/drag_wrapper.js"></script>
<script src="chrome://resources/js/cr/ui/focus_manager.js"></script>
<script src="chrome://resources/js/cr/ui/focus_outline_manager.js"></script>
+<script src="chrome://resources/js/cr/ui/node_utils.js"></script>
<script src="chrome://resources/js/cr/ui/overlay.js"></script>
<if expr="chromeos">
@@ -43,8 +51,7 @@
<script src="chrome://extensions-frame/extensions.js"></script>
</head>
-<body class="uber-frame"
- i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<body class="uber-frame">
<div id="overlay" class="overlay" hidden>
<include src="extension_commands_overlay.html">
@@ -62,7 +69,8 @@
</div>
<div class="page" id="extension-settings">
- <header id="page-header"><h1 i18n-content="extensionSettings"></h1>
+ <header id="page-header">
+ <h1 i18n-content="extensionSettings"></h1>
<div id="header-controls" class="header-extras">
<div id="dev-toggle" class="checkbox">
<label>
@@ -78,8 +86,8 @@
</div>
</div>
</header>
- <div id="dev-controls" hidden>
- <div class="buttons-container">
+ <div id="dev-controls">
+ <div class="button-container">
<button id="load-unpacked"
i18n-content="extensionSettingsLoadUnpackedButton"></button>
<button id="pack-extension"
@@ -92,33 +100,24 @@
<button id="update-extensions-now"
i18n-content="extensionSettingsUpdateButton"></button>
</div>
- <div id="apps-developer-tools-promo">
- <img></img>
- <span id="apps-developer-tools-promo-text"
- i18n-values=".innerHTML:extensionSettingsAppsDevToolsPromoHTML">
- </span>
- <div id="apps-developer-tools-promo-close-wrapper">
- <button i18n-values="title:extensionSettingsAppDevToolsPromoClose"
- class="custom-appearance close-button"></button>
- </div>
- </div>
</div>
<include src="extension_load_error.html">
- <div id="extension-settings-list" class="empty-extension-list"></div>
- <div id="no-extensions">
+ <div id="extension-list-wrapper" hidden>
+ <div id="footer-section">
+ <a target="_blank" class="more-extensions-link"
+ i18n-values="href:extensionSettingsGetMoreExtensionsUrl"
+ i18n-content="extensionSettingsGetMoreExtensions"></a>
+ <a is="action-link" class="extension-commands-config"
+ i18n-content="extensionSettingsCommandsLink" hidden></a>
+ </div>
+ </div>
+ <div id="no-extensions" hidden>
<span id="no-extensions-message"
i18n-content="extensionSettingsNoExtensions"></span>
<span id="suggest-gallery" class="more-extensions-link"
i18n-values=".innerHTML:extensionSettingsSuggestGallery">
</span>
</div>
- <div id="footer-section">
- <a target="_blank" class="more-extensions-link"
- i18n-values="href:extensionSettingsGetMoreExtensionsUrl"
- i18n-content="extensionSettingsGetMoreExtensions"></a>
- <a is="action-link" class="extension-commands-config"
- i18n-content="extensionSettingsCommandsLink" hidden></a>
- </div>
</div>
<span id="font-measuring-div"></span>
@@ -147,6 +146,10 @@
i18n-content="extensionSettingsLaunch" hidden></a>
<a is="action-link" role="button" class="reload-link"
i18n-content="extensionSettingsReloadUnpacked" hidden></a>
+ <a is="action-link" role="button" class="errors-link">
+ <img class="extension-error-icon"></img>
+ <span i18n-content="extensionErrorHeading"></span>
+ </a>
</div>
<div class="permanent-warnings">
<div class="extension-warnings" hidden>
@@ -178,12 +181,13 @@
<div class="managed-message"
i18n-content="extensionSettingsPolicyControlled" hidden>
</div>
+ <div class="update-required-message"
+ i18n-content="extensionSettingsUpdateRequiredBePolicy" hidden>
+ </div>
<div class="active-views" hidden>
<span i18n-content="extensionSettingsInspectViews"></span>
<a is="action-link"></a>
</div>
- <div class="manifest-errors" hidden></div>
- <div class="runtime-errors" hidden></div>
<div class="install-warnings" hidden>
<span i18n-content="extensionSettingsInstallWarnings"></span>
<ul></ul>
@@ -244,7 +248,7 @@
</div>
<li class="dependent-list-item">
<span class="dep-extension-title"></span>
- <ul class="developer-extras">
+ <ul>
<li>
<span i18n-content="extensionSettingsExtensionId"></span>
<span class="dep-extension-id"></span>
@@ -259,7 +263,7 @@
</div>
<script src="chrome://extensions-frame/strings.js"></script>
-<script src="chrome://resources/js/i18n_template2.js"></script>
+<script src="chrome://resources/js/i18n_template.js"></script>
</body>
</html>
diff --git a/chromium/chrome/browser/resources/extensions/extensions.js b/chromium/chrome/browser/resources/extensions/extensions.js
index 42ad4a43770..5e1812dbf25 100644
--- a/chromium/chrome/browser/resources/extensions/extensions.js
+++ b/chromium/chrome/browser/resources/extensions/extensions.js
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+<include src="../../../../ui/webui/resources/js/cr/ui/focus_row.js">
+<include src="../../../../ui/webui/resources/js/cr/ui/focus_grid.js">
<include src="../uber/uber_utils.js">
<include src="extension_code.js">
<include src="extension_commands_overlay.js">
@@ -16,30 +18,22 @@
<include src="chromeos/kiosk_apps.js">
</if>
-/**
- * The type of the extension data object. The definition is based on
- * chrome/browser/ui/webui/extensions/extension_settings_handler.cc:
- * ExtensionSettingsHandler::HandleRequestExtensionsData()
- * @typedef {{developerMode: boolean,
- * extensions: Array,
- * incognitoAvailable: boolean,
- * loadUnpackedDisabled: boolean,
- * profileIsSupervised: boolean,
- * promoteAppsDevTools: boolean}}
- */
-var ExtensionDataResponse;
-
// Used for observing function of the backend datasource for this page by
// tests.
var webuiResponded = false;
cr.define('extensions', function() {
- var ExtensionsList = options.ExtensionsList;
+ var ExtensionList = extensions.ExtensionList;
// Implements the DragWrapper handler interface.
var dragWrapperHandler = {
/** @override */
shouldAcceptDrag: function(e) {
+ // External Extension installation can be disabled globally, e.g. while a
+ // different overlay is already showing.
+ if (!ExtensionSettings.getInstance().dragEnabled_)
+ return false;
+
// We can't access filenames during the 'dragenter' event, so we have to
// wait until 'drop' to decide whether to do something with the file or
// not.
@@ -50,12 +44,11 @@ cr.define('extensions', function() {
/** @override */
doDragEnter: function() {
chrome.send('startDrag');
- ExtensionSettings.showOverlay(null);
ExtensionSettings.showOverlay($('drop-target-overlay'));
},
/** @override */
doDragLeave: function() {
- ExtensionSettings.showOverlay(null);
+ this.hideDropTargetOverlay_();
chrome.send('stopDrag');
},
/** @override */
@@ -64,7 +57,7 @@ cr.define('extensions', function() {
},
/** @override */
doDrop: function(e) {
- ExtensionSettings.showOverlay(null);
+ this.hideDropTargetOverlay_();
if (e.dataTransfer.files.length != 1)
return;
@@ -89,12 +82,24 @@ cr.define('extensions', function() {
e.preventDefault();
chrome.send(toSend);
}
+ },
+
+ /**
+ * Hide the current overlay if it is the drop target overlay.
+ * @private
+ */
+ hideDropTargetOverlay_: function() {
+ var currentOverlay = ExtensionSettings.getCurrentOverlay();
+ if (currentOverlay && currentOverlay.id === 'drop-target-overlay')
+ ExtensionSettings.showOverlay(null);
}
};
/**
* ExtensionSettings class
* @class
+ * @constructor
+ * @implements {extensions.ExtensionListDelegate}
*/
function ExtensionSettings() {}
@@ -104,11 +109,28 @@ cr.define('extensions', function() {
__proto__: HTMLDivElement.prototype,
/**
- * Whether or not to try to display the Apps Developer Tools promotion.
+ * The drag-drop wrapper for installing external Extensions, if available.
+ * null if external Extension installation is not available.
+ * @type {cr.ui.DragWrapper}
+ * @private
+ */
+ dragWrapper_: null,
+
+ /**
+ * True if drag-drop is both available and currently enabled - it can be
+ * temporarily disabled while overlays are showing.
* @type {boolean}
* @private
*/
- displayPromo_: false,
+ dragEnabled_: false,
+
+ /**
+ * Callback for testing purposes. This is called after the "Developer mode"
+ * checkbox is toggled and the div containing developer buttons' height has
+ * been set.
+ * @type {function()?}
+ */
+ testingDeveloperModeCallback: null,
/**
* Perform initial setup.
@@ -121,37 +143,47 @@ cr.define('extensions', function() {
// Set the title.
uber.setTitle(loadTimeData.getString('extensionSettings'));
- // This will request the data to show on the page and will get a response
- // back in returnExtensionsData.
- chrome.send('extensionSettingsRequestExtensionsData');
+ var extensionList = new ExtensionList(this);
+ extensionList.id = 'extension-settings-list';
+ var wrapper = $('extension-list-wrapper');
+ wrapper.insertBefore(extensionList, wrapper.firstChild);
+
+ this.update_();
+ // TODO(devlin): Remove this once all notifications are moved to events on
+ // the developerPrivate api.
+ chrome.send('extensionSettingsRegister');
var extensionLoader = extensions.ExtensionLoader.getInstance();
- $('toggle-dev-on').addEventListener('change',
- this.handleToggleDevMode_.bind(this));
- $('dev-controls').addEventListener('webkitTransitionEnd',
- this.handleDevControlsTransitionEnd_.bind(this));
+ $('toggle-dev-on').addEventListener('change', function(e) {
+ this.updateDevControlsVisibility_(true);
+ extensionList.updateFocusableElements();
+ chrome.developerPrivate.updateProfileConfiguration(
+ {inDeveloperMode: e.target.checked});
+ var suffix = $('toggle-dev-on').checked ? 'Enabled' : 'Disabled';
+ chrome.send('metricsHandler:recordAction',
+ ['Options_ToggleDeveloperMode_' + suffix]);
+ }.bind(this));
+
+ window.addEventListener('resize', function() {
+ this.updateDevControlsVisibility_(false);
+ }.bind(this));
// Set up the three dev mode buttons (load unpacked, pack and update).
$('load-unpacked').addEventListener('click', function(e) {
- extensionLoader.loadUnpacked();
+ chrome.send('metricsHandler:recordAction',
+ ['Options_LoadUnpackedExtension']);
+ extensionLoader.loadUnpacked();
});
$('pack-extension').addEventListener('click',
this.handlePackExtension_.bind(this));
$('update-extensions-now').addEventListener('click',
this.handleUpdateExtensionNow_.bind(this));
- // Set up the close dialog for the apps developer tools promo.
- $('apps-developer-tools-promo').querySelector('.close-button').
- addEventListener('click', function(e) {
- this.displayPromo_ = false;
- this.updatePromoVisibility_();
- chrome.send('extensionSettingsDismissADTPromo');
- }.bind(this));
-
if (!loadTimeData.getBoolean('offStoreInstallEnabled')) {
this.dragWrapper_ = new cr.ui.DragWrapper(document.documentElement,
dragWrapperHandler);
+ this.dragEnabled_ = true;
}
extensions.PackExtensionOverlay.getInstance().initializePage();
@@ -170,6 +202,16 @@ cr.define('extensions', function() {
extensions.ExtensionOptionsOverlay.getInstance().initializePage(
extensions.ExtensionSettings.showOverlay);
+ // Add user action logging for bottom links.
+ var moreExtensionLink =
+ document.getElementsByClassName('more-extensions-link');
+ for (var i = 0; i < moreExtensionLink.length; i++) {
+ moreExtensionLink[i].addEventListener('click', function(e) {
+ chrome.send('metricsHandler:recordAction',
+ ['Options_GetMoreExtensions']);
+ });
+ }
+
// Initialize the kiosk overlay.
if (cr.isChromeOS) {
var kioskOverlay = extensions.KioskAppsOverlay.getInstance();
@@ -198,23 +240,52 @@ cr.define('extensions', function() {
},
/**
- * Updates the Chrome Apps and Extensions Developer Tools promotion's
- * visibility.
+ * Updates the extensions page to the latest profile and extensions
+ * configuration.
* @private
*/
- updatePromoVisibility_: function() {
- var extensionSettings = $('extension-settings');
- var visible = extensionSettings.classList.contains('dev-mode') &&
- this.displayPromo_;
-
- var adtPromo = $('apps-developer-tools-promo');
- var controls = adtPromo.querySelectorAll('a, button');
- Array.prototype.forEach.call(controls, function(control) {
- control[visible ? 'removeAttribute' : 'setAttribute']('tabindex', '-1');
- });
+ update_: function() {
+ chrome.developerPrivate.getProfileConfiguration(
+ this.returnProfileConfiguration_.bind(this));
+ },
+
+ /**
+ * [Re]-Populates the page with data representing the current state of
+ * installed extensions.
+ * @param {ProfileInfo} profileInfo
+ * @private
+ */
+ returnProfileConfiguration_: function(profileInfo) {
+ webuiResponded = true;
+ /** @const */
+ var supervised = profileInfo.isSupervised;
+
+ var pageDiv = $('extension-settings');
+ pageDiv.classList.toggle('profile-is-supervised', supervised);
+ pageDiv.classList.toggle('showing-banner', supervised);
+
+ var devControlsCheckbox = $('toggle-dev-on');
+ devControlsCheckbox.checked = profileInfo.inDeveloperMode;
+ devControlsCheckbox.disabled = supervised;
+
+ this.updateDevControlsVisibility_(false);
+
+ $('load-unpacked').disabled = !profileInfo.canLoadUnpacked;
+ var extensionList = $('extension-settings-list');
+ extensionList.updateExtensionsData(
+ profileInfo.isIncognitoAvailable,
+ profileInfo.appInfoDialogEnabled).then(function() {
+ // We can get called many times in short order, thus we need to
+ // be careful to remove the 'finished loading' timeout.
+ if (this.loadingTimeout_)
+ window.clearTimeout(this.loadingTimeout_);
+ document.documentElement.classList.add('loading');
+ this.loadingTimeout_ = window.setTimeout(function() {
+ document.documentElement.classList.remove('loading');
+ }, 0);
- adtPromo.setAttribute('aria-hidden', !visible);
- extensionSettings.classList.toggle('adt-promo', visible);
+ this.onExtensionCountChanged();
+ }.bind(this));
},
/**
@@ -229,10 +300,9 @@ cr.define('extensions', function() {
/**
* Shows the Extension Commands configuration UI.
- * @param {Event} e Change event.
* @private
*/
- showExtensionCommandsConfigUi_: function(e) {
+ showExtensionCommandsConfigUi_: function() {
ExtensionSettings.showOverlay($('extension-commands-overlay'));
chrome.send('metricsHandler:recordAction',
['Options_ExtensionCommands']);
@@ -253,130 +323,55 @@ cr.define('extensions', function() {
* @private
*/
handleUpdateExtensionNow_: function(e) {
- chrome.send('extensionSettingsAutoupdate');
+ chrome.developerPrivate.autoUpdate();
+ chrome.send('metricsHandler:recordAction',
+ ['Options_UpdateExtensions']);
},
/**
- * Handles the Toggle Dev Mode button.
- * @param {Event} e Change event.
+ * Updates the visibility of the developer controls based on whether the
+ * [x] Developer mode checkbox is checked.
+ * @param {boolean} animated Whether to animate any updates.
* @private
*/
- handleToggleDevMode_: function(e) {
- if ($('toggle-dev-on').checked) {
- $('dev-controls').hidden = false;
- window.setTimeout(function() {
- $('extension-settings').classList.add('dev-mode');
- }, 0);
- } else {
- $('extension-settings').classList.remove('dev-mode');
- }
- window.setTimeout(this.updatePromoVisibility_.bind(this), 0);
+ updateDevControlsVisibility_: function(animated) {
+ var showDevControls = $('toggle-dev-on').checked;
+ $('extension-settings').classList.toggle('dev-mode', showDevControls);
- chrome.send('extensionSettingsToggleDeveloperMode');
- },
+ var devControls = $('dev-controls');
+ devControls.classList.toggle('animated', animated);
- /**
- * Called when a transition has ended for #dev-controls.
- * @param {Event} e webkitTransitionEnd event.
- * @private
- */
- handleDevControlsTransitionEnd_: function(e) {
- if (e.propertyName == 'height' &&
- !$('extension-settings').classList.contains('dev-mode')) {
- $('dev-controls').hidden = true;
- }
- },
- };
-
- /**
- * Called by the dom_ui_ to re-populate the page with data representing
- * the current state of installed extensions.
- * @param {ExtensionDataResponse} extensionsData
- */
- ExtensionSettings.returnExtensionsData = function(extensionsData) {
- // We can get called many times in short order, thus we need to
- // be careful to remove the 'finished loading' timeout.
- if (this.loadingTimeout_)
- window.clearTimeout(this.loadingTimeout_);
- document.documentElement.classList.add('loading');
- this.loadingTimeout_ = window.setTimeout(function() {
- document.documentElement.classList.remove('loading');
- }, 0);
-
- webuiResponded = true;
-
- if (extensionsData.extensions.length > 0) {
- // Enforce order specified in the data or (if equal) then sort by
- // extension name (case-insensitive) followed by their ID (in the case
- // where extensions have the same name).
- extensionsData.extensions.sort(function(a, b) {
- function compare(x, y) {
- return x < y ? -1 : (x > y ? 1 : 0);
- }
- return compare(a.order, b.order) ||
- compare(a.name.toLowerCase(), b.name.toLowerCase()) ||
- compare(a.id, b.id);
+ var buttons = devControls.querySelector('.button-container');
+ Array.prototype.forEach.call(buttons.querySelectorAll('a, button'),
+ function(control) {
+ control.tabIndex = showDevControls ? 0 : -1;
});
- }
-
- var pageDiv = $('extension-settings');
- var marginTop = 0;
- if (extensionsData.profileIsSupervised) {
- pageDiv.classList.add('profile-is-supervised');
- } else {
- pageDiv.classList.remove('profile-is-supervised');
- }
- if (extensionsData.profileIsSupervised) {
- pageDiv.classList.add('showing-banner');
- $('toggle-dev-on').disabled = true;
- marginTop += 45;
- } else {
- pageDiv.classList.remove('showing-banner');
- $('toggle-dev-on').disabled = false;
- }
-
- pageDiv.style.marginTop = marginTop + 'px';
-
- if (extensionsData.developerMode) {
- pageDiv.classList.add('dev-mode');
- $('toggle-dev-on').checked = true;
- $('dev-controls').hidden = false;
- } else {
- pageDiv.classList.remove('dev-mode');
- $('toggle-dev-on').checked = false;
- }
+ buttons.setAttribute('aria-hidden', !showDevControls);
- ExtensionSettings.getInstance().displayPromo_ =
- extensionsData.promoteAppsDevTools;
- ExtensionSettings.getInstance().updatePromoVisibility_();
+ window.requestAnimationFrame(function() {
+ devControls.style.height = !showDevControls ? '' :
+ buttons.offsetHeight + 'px';
- $('load-unpacked').disabled = extensionsData.loadUnpackedDisabled;
+ if (this.testingDeveloperModeCallback)
+ this.testingDeveloperModeCallback();
+ }.bind(this));
+ },
- ExtensionsList.prototype.data_ = extensionsData;
- var extensionList = $('extension-settings-list');
- ExtensionsList.decorate(extensionList);
+ /** @override */
+ onExtensionCountChanged: function() {
+ /** @const */
+ var hasExtensions = $('extension-settings-list').getNumExtensions() != 0;
+ $('no-extensions').hidden = hasExtensions;
+ $('extension-list-wrapper').hidden = !hasExtensions;
+ },
};
- // Indicate that warning |message| has occured for pack of |crx_path| and
- // |pem_path| files. Ask if user wants override the warning. Send
- // |overrideFlags| to repeated 'pack' call to accomplish the override.
- ExtensionSettings.askToOverrideWarning =
- function(message, crx_path, pem_path, overrideFlags) {
- var closeAlert = function() {
- ExtensionSettings.showOverlay(null);
- };
-
- alertOverlay.setValues(
- loadTimeData.getString('packExtensionWarningTitle'),
- message,
- loadTimeData.getString('packExtensionProceedAnyway'),
- loadTimeData.getString('cancel'),
- function() {
- chrome.send('pack', [crx_path, pem_path, overrideFlags]);
- closeAlert();
- },
- closeAlert);
- ExtensionSettings.showOverlay($('alertOverlay'));
+ /**
+ * Called by the WebUI when something has changed and the extensions UI needs
+ * to be updated.
+ */
+ ExtensionSettings.onExtensionsChanged = function() {
+ ExtensionSettings.getInstance().update_();
};
/**
@@ -388,27 +383,31 @@ cr.define('extensions', function() {
};
/**
- * Sets the given overlay to show. This hides whatever overlay is currently
- * showing, if any.
- * @param {HTMLElement} node The overlay page to show. If falsey, all overlays
+ * Sets the given overlay to show. If the overlay is already showing, this is
+ * a no-op; otherwise, hides any currently-showing overlay.
+ * @param {HTMLElement} node The overlay page to show. If null, all overlays
* are hidden.
*/
ExtensionSettings.showOverlay = function(node) {
var pageDiv = $('extension-settings');
- if (node) {
- pageDiv.style.width = window.getComputedStyle(pageDiv).width;
- document.body.classList.add('no-scroll');
- } else {
- document.body.classList.remove('no-scroll');
- pageDiv.style.width = '';
- }
+ pageDiv.style.width = node ? window.getComputedStyle(pageDiv).width : '';
+ document.body.classList.toggle('no-scroll', !!node);
var currentlyShowingOverlay = ExtensionSettings.getCurrentOverlay();
- if (currentlyShowingOverlay)
+ if (currentlyShowingOverlay) {
+ if (currentlyShowingOverlay == node) // Already displayed.
+ return;
currentlyShowingOverlay.classList.remove('showing');
+ }
- if (node)
+ if (node) {
+ var lastFocused = document.activeElement;
+ $('overlay').addEventListener('cancelOverlay', function f() {
+ lastFocused.focus();
+ $('overlay').removeEventListener('cancelOverlay', f);
+ });
node.classList.add('showing');
+ }
var pages = document.querySelectorAll('.page');
for (var i = 0; i < pages.length; i++) {
@@ -416,10 +415,34 @@ cr.define('extensions', function() {
}
$('overlay').hidden = !node;
+
+ if (node)
+ ExtensionSettings.focusOverlay();
+
+ // If drag-drop for external Extension installation is available, enable
+ // drag-drop when there is any overlay showing other than the usual overlay
+ // shown when drag-drop is started.
+ var settings = ExtensionSettings.getInstance();
+ if (settings.dragWrapper_)
+ settings.dragEnabled_ = !node || node == $('drop-target-overlay');
+
uber.invokeMethodOnParent(node ? 'beginInterceptingEvents' :
'stopInterceptingEvents');
};
+ ExtensionSettings.focusOverlay = function() {
+ var currentlyShowingOverlay = ExtensionSettings.getCurrentOverlay();
+ assert(currentlyShowingOverlay);
+
+ if (cr.ui.FocusOutlineManager.forDocument(document).visible)
+ cr.ui.setInitialFocus(currentlyShowingOverlay);
+
+ if (!currentlyShowingOverlay.contains(document.activeElement)) {
+ // Make sure focus isn't stuck behind the overlay.
+ document.activeElement.blur();
+ }
+ };
+
/**
* Utility function to find the width of various UI strings and synchronize
* the width of relevant spans. This is crucial for making sure the
@@ -460,5 +483,6 @@ cr.define('extensions', function() {
});
window.addEventListener('load', function(e) {
+ document.documentElement.classList.add('loading');
extensions.ExtensionSettings.getInstance().initialize();
});
diff --git a/chromium/chrome/browser/resources/extensions/pack_extension_overlay.html b/chromium/chrome/browser/resources/extensions/pack_extension_overlay.html
index a7794bdfa57..be2394c69d3 100644
--- a/chromium/chrome/browser/resources/extensions/pack_extension_overlay.html
+++ b/chromium/chrome/browser/resources/extensions/pack_extension_overlay.html
@@ -1,4 +1,5 @@
<div id="pack-extension-overlay" class="page">
+ <div class="close-button"></div>
<h1 i18n-content="packExtensionOverlay"></h1>
<div id="cbd-content-area" class="content-area">
<div class="pack-extension-heading" i18n-content="packExtensionHeading">
diff --git a/chromium/chrome/browser/resources/extensions/pack_extension_overlay.js b/chromium/chrome/browser/resources/extensions/pack_extension_overlay.js
index 186f84102e6..03e6180a03d 100644
--- a/chromium/chrome/browser/resources/extensions/pack_extension_overlay.js
+++ b/chromium/chrome/browser/resources/extensions/pack_extension_overlay.js
@@ -48,24 +48,26 @@ cr.define('extensions', function() {
handleCommit_: function(e) {
var extensionPath = $('extension-root-dir').value;
var privateKeyPath = $('extension-private-key').value;
- chrome.send('pack', [extensionPath, privateKeyPath, 0]);
+ chrome.developerPrivate.packDirectory(
+ extensionPath, privateKeyPath, 0, this.onPackResponse_.bind(this));
},
/**
* Utility function which asks the C++ to show a platform-specific file
- * select dialog, and fire |callback| with the |filePath| that resulted.
- * |selectType| can be either 'file' or 'folder'. |operation| can be 'load'
- * or 'pem' which are signals to the C++ to do some operation-specific
- * configuration.
+ * select dialog, and set the value property of |node| to the selected path.
+ * @param {chrome.developerPrivate.SelectType} selectType
+ * The type of selection to use.
+ * @param {chrome.developerPrivate.FileType} fileType
+ * The type of file to select.
+ * @param {HTMLInputElement} node The node to set the value of.
* @private
*/
- showFileDialog_: function(selectType, operation, callback) {
- window.handleFilePathSelected = function(filePath) {
- callback(filePath);
- window.handleFilePathSelected = function() {};
- };
-
- chrome.send('packExtensionSelectFilePath', [selectType, operation]);
+ showFileDialog_: function(selectType, fileType, node) {
+ chrome.developerPrivate.choosePath(selectType, fileType, function(path) {
+ // Last error is set if the user canceled the dialog.
+ if (!chrome.runtime.lastError && path)
+ node.value = path;
+ });
},
/**
@@ -74,9 +76,10 @@ cr.define('extensions', function() {
* @private
*/
handleBrowseExtensionDir_: function(e) {
- this.showFileDialog_('folder', 'load', function(filePath) {
- $('extension-root-dir').value = filePath;
- });
+ this.showFileDialog_(
+ chrome.developerPrivate.SelectType.FOLDER,
+ chrome.developerPrivate.FileType.LOAD,
+ /** @type {HTMLInputElement} */ ($('extension-root-dir')));
},
/**
@@ -85,44 +88,76 @@ cr.define('extensions', function() {
* @private
*/
handleBrowsePrivateKey_: function(e) {
- this.showFileDialog_('file', 'pem', function(filePath) {
- $('extension-private-key').value = filePath;
- });
+ this.showFileDialog_(
+ chrome.developerPrivate.SelectType.FILE,
+ chrome.developerPrivate.FileType.PEM,
+ /** @type {HTMLInputElement} */ ($('extension-private-key')));
},
- };
- /**
- * Wrap up the pack process by showing the success |message| and closing
- * the overlay.
- * @param {string} message The message to show to the user.
- */
- PackExtensionOverlay.showSuccessMessage = function(message) {
- alertOverlay.setValues(
- loadTimeData.getString('packExtensionOverlay'),
- message,
- loadTimeData.getString('ok'),
- '',
- function() {
- extensions.ExtensionSettings.showOverlay(null);
- });
- extensions.ExtensionSettings.showOverlay($('alertOverlay'));
- };
+ /**
+ * Handles a response from a packDirectory call.
+ * @param {PackDirectoryResponse} response The response of the pack call.
+ * @private
+ */
+ onPackResponse_: function(response) {
+ /** @type {string} */
+ var alertTitle;
+ /** @type {string} */
+ var alertOk;
+ /** @type {string} */
+ var alertCancel;
+ /** @type {function()} */
+ var alertOkCallback;
+ /** @type {function()} */
+ var alertCancelCallback;
- /**
- * Post an alert overlay showing |message|, and upon acknowledgement, close
- * the alert overlay and return to showing the PackExtensionOverlay.
- * @param {string} message The error message.
- */
- PackExtensionOverlay.showError = function(message) {
- alertOverlay.setValues(
- loadTimeData.getString('packExtensionErrorTitle'),
- message,
- loadTimeData.getString('ok'),
- '',
- function() {
- extensions.ExtensionSettings.showOverlay($('pack-extension-overlay'));
- });
- extensions.ExtensionSettings.showOverlay($('alertOverlay'));
+ var closeAlert = function() {
+ extensions.ExtensionSettings.showOverlay(null);
+ };
+
+ switch (response.status) {
+ case chrome.developerPrivate.PackStatus.SUCCESS:
+ alertTitle = loadTimeData.getString('packExtensionOverlay');
+ alertOk = loadTimeData.getString('ok');
+ alertOkCallback = closeAlert;
+ // No 'Cancel' option.
+ break;
+ case chrome.developerPrivate.PackStatus.WARNING:
+ alertTitle = loadTimeData.getString('packExtensionWarningTitle');
+ alertOk = loadTimeData.getString('packExtensionProceedAnyway');
+ alertCancel = loadTimeData.getString('cancel');
+ alertOkCallback = function() {
+ chrome.developerPrivate.packDirectory(
+ response.item_path,
+ response.pem_path,
+ response.override_flags,
+ this.onPackResponse_.bind(this));
+ closeAlert();
+ }.bind(this);
+ alertCancelCallback = closeAlert;
+ break;
+ case chrome.developerPrivate.PackStatus.ERROR:
+ alertTitle = loadTimeData.getString('packExtensionErrorTitle');
+ alertOk = loadTimeData.getString('ok');
+ alertOkCallback = function() {
+ extensions.ExtensionSettings.showOverlay(
+ $('pack-extension-overlay'));
+ };
+ // No 'Cancel' option.
+ break;
+ default:
+ assertNotReached();
+ return;
+ }
+
+ alertOverlay.setValues(alertTitle,
+ response.message,
+ alertOk,
+ alertCancel,
+ alertOkCallback,
+ alertCancelCallback);
+ extensions.ExtensionSettings.showOverlay($('alertOverlay'));
+ },
};
// Export
diff --git a/chromium/chrome/browser/resources/extensions_infobar.css b/chromium/chrome/browser/resources/extensions_infobar.css
deleted file mode 100644
index 7b8cc42ae37..00000000000
--- a/chromium/chrome/browser/resources/extensions_infobar.css
+++ /dev/null
@@ -1,16 +0,0 @@
-/* Copyright (c) 2012 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 following style rules affect Extension Infobars.
- */
-
-body {
- background: -webkit-linear-gradient(#E9E9E9, #DADADA);
- font-family: Segoe UI, Tahoma;
- font-size: 11px;
- height: 36px; /* Infobars are limited to 36-72px */
- margin: 0;
- overflow: hidden;
-}
diff --git a/chromium/chrome/browser/resources/extensions_infobar_mac.css b/chromium/chrome/browser/resources/extensions_infobar_mac.css
deleted file mode 100644
index bf8c51214d4..00000000000
--- a/chromium/chrome/browser/resources/extensions_infobar_mac.css
+++ /dev/null
@@ -1,16 +0,0 @@
-/* Copyright (c) 2012 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 following style rules affect Extension Infobars on the Mac.
- */
-
-body {
- background: -webkit-linear-gradient(#ebebeb, #cfcfcf);
- font-family: 'Lucida Grande', Helvetica, sans-serif;
- font-size: 12px;
- height: 36px; /* Infobars are limited to 36-72px */
- margin: 0 38px;
- overflow: hidden;
-}
diff --git a/chromium/chrome/browser/resources/feedback/OWNERS b/chromium/chrome/browser/resources/feedback/OWNERS
new file mode 100644
index 00000000000..6a2cb03fd3e
--- /dev/null
+++ b/chromium/chrome/browser/resources/feedback/OWNERS
@@ -0,0 +1 @@
+rkc@chromium.org
diff --git a/chromium/chrome/browser/resources/feedback/css/feedback.css b/chromium/chrome/browser/resources/feedback/css/feedback.css
index addbff71582..0e09874bba3 100644
--- a/chromium/chrome/browser/resources/feedback/css/feedback.css
+++ b/chromium/chrome/browser/resources/feedback/css/feedback.css
@@ -130,8 +130,8 @@ body {
-webkit-margin-start: 5px;
background-color: transparent;
background-image: -webkit-image-set(
- url('chrome://resources/images/apps/button_butter_bar_close.png') 1x,
- url('chrome://resources/images/2x/apps/button_butter_bar_close.png') 2x);
+ url(chrome://resources/images/apps/button_butter_bar_close.png) 1x,
+ url(chrome://resources/images/2x/apps/button_butter_bar_close.png) 2x);
background-position: 50% 80%;
background-repeat: no-repeat;
border: none;
@@ -142,14 +142,14 @@ body {
.content .remove-file-button:hover {
background-image: -webkit-image-set(
- url('chrome://resources/images/apps/button_butter_bar_close_hover.png') 1x,
- url('chrome://resources/images/2x/apps/button_butter_bar_close_hover.png') 2x);
+ url(chrome://resources/images/apps/button_butter_bar_close_hover.png) 1x,
+ url(chrome://resources/images/2x/apps/button_butter_bar_close_hover.png) 2x);
}
.content .remove-file-button:active {
background-image: -webkit-image-set(
- url('chrome://resources/images/apps/button_butter_bar_close_pressed.png') 1x,
- url('chrome://resources/images/2x/apps/button_butter_bar_close_pressed.png') 2x);
+ url(chrome://resources/images/apps/button_butter_bar_close_pressed.png) 1x,
+ url(chrome://resources/images/2x/apps/button_butter_bar_close_pressed.png) 2x);
}
.content #attach-file-note {
diff --git a/chromium/chrome/browser/resources/feedback/html/default.html b/chromium/chrome/browser/resources/feedback/html/default.html
index dc1e6297d42..204850a3659 100644
--- a/chromium/chrome/browser/resources/feedback/html/default.html
+++ b/chromium/chrome/browser/resources/feedback/html/default.html
@@ -1,7 +1,8 @@
-<!DOCTYPE HTML>
-<html i18n-values="dir:textdirection;">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
<head>
<meta charset="utf-8">
+<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="chrome://resources/css/apps/common.css"></link>
<link rel="stylesheet" href="chrome://resources/css/apps/topbutton_bar.css"></link>
<link rel="stylesheet" href="../css/feedback.css">
@@ -13,7 +14,7 @@
<script src="../js/topbar_handlers.js"></script>
<script src="../js/feedback.js"></script>
</head>
-<body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<body>
<div id="title-bar" class="title-bar">
<span id="page-title" i18n-content="page-title"></span>
<span class="topbutton-bar">
diff --git a/chromium/chrome/browser/resources/feedback/js/event_handler.js b/chromium/chrome/browser/resources/feedback/js/event_handler.js
index 8e168cba329..d212c49df87 100644
--- a/chromium/chrome/browser/resources/feedback/js/event_handler.js
+++ b/chromium/chrome/browser/resources/feedback/js/event_handler.js
@@ -15,49 +15,82 @@ var FEEDBACK_HEIGHT = 585;
var initialFeedbackInfo = null;
+// To generate a hashed extension ID, use a sha-256 hash, all in lower case.
+// Example:
+// echo -n 'abcdefghijklmnopqrstuvwxyzabcdef' | sha1sum | \
+// awk '{print toupper($1)}'
var whitelistedExtensionIds = [
- 'bpmcpldpdmajfigpchkicefoigmkfalc', // QuickOffice
- 'ehibbfinohgbchlgdbfpikodjaojhccn', // QuickOffice
- 'gbkeegbaiigmenfmjfclcdgdpimamgkj', // QuickOffice
- 'efjnaogkjbogokcnohkmnjdojkikgobo', // G+ Photos
- 'ebpbnabdhheoknfklmpddcdijjkmklkp', // G+ Photos
- 'endkpmfloggdajndjpoekmkjnkolfdbf', // Feedback Extension
- 'mlocfejafidcakdddnndjdngfmncfbeg', // Connectivity Diagnostics
- 'ganomidahfnpdchomfgdoppjmmedlhia', // Connectivity Diagnostics
- 'eemlkeanncmjljgehlbplemhmdmalhdc', // Connectivity Diagnostics
- 'kodldpbjkkmmnilagfdheibampofhaom', // Connectivity Diagnostics
- 'kkebgepbbgbcmghedmmdfcbdcodlkngh', // Chrome OS Recovery Tool
- 'jndclpdbaamdhonoechobihbbiimdgai', // Chrome OS Recovery Tool
- 'ljoammodoonkhnehlncldjelhidljdpi', // GetHelp app.
- 'ljacajndfccfgnfohlgkdphmbnpkjflk', // Chrome Remote Desktop Dev
- 'gbchcmhmhahfdphkhkmpfmihenigjmpp', // Chrome Remote Desktop Stable
- 'odkaodonbgfohohmklejpjiejmcipmib', // Chrome Remote Desktop QA
- 'dokpleeekgeeiehdhmdkeimnkmoifgdd', // Chrome Remote Desktop QA backup
- 'ajoainacpilcemgiakehflpbkbfipojk', // Chrome Remote Desktop Apps V2
- 'llohocloplkbhgcfnplnoficdkiechcn', // Play Movies Dev
- 'icljpnebmoleodmchaaajbkpoipfoahp', // Play Movies Nightly
- 'mjekoljodoiapgkggnlmbecndfpbbcch', // Play Movies Beta
- 'gdijeikdkaembjbdobgfkoidjkpbmlkd', // Play Movies Stable
- 'andfmajejfpjojledngpdaibbhkffipo', // Hangouts Extension
- 'jfjjdfefebklmdbmenmlehlopoocnoeh', // Hangouts Extension
- 'dhcmpocobclokhifdkgcjbnfdaneoojd', // Hangouts Extension
- 'ppleadejekpmccmnpjdimmlfljlkdfej', // Hangouts Extension
- 'eggnbpckecmjlblplehfpjjdhhidfdoj', // Hangouts Extension
- 'ljclpkphhpbpinifbeabbhlfddcpfdde', // Hangouts Extension
- 'nckgahadagoaajjgafhacjanaoiihapd', // Hangouts Extension
- 'knipolnnllmklapflnccelgolnpehhpl', // Hangouts Extension
- 'dogkdgiahcdchbabhdmpbhlfoddjined', // GLS nightly
- 'khkjfddibboofomnlkndfedpoccieiee', // GLS stable
+ '12E618C3C6E97495AAECF2AC12DEB082353241C6', // QuickOffice
+ '3727DD3E564B6055387425027AD74C58784ACC15', // QuickOffice
+ '2FC374607C2DF285634B67C64A2E356C607091C3', // QuickOffice
+ '2843C1E82A9B6C6FB49308FDDF4E157B6B44BC2B', // G+ Photos
+ '5B5DA6D054D10DB917AF7D9EAE3C56044D1B0B03', // G+ Photos
+ '986913085E3E3C3AFDE9B7A943149C4D3F4C937B', // Feedback Extension
+ '7AE714FFD394E073F0294CFA134C9F91DB5FBAA4', // Connectivity Diagnostics
+ 'C7DA3A55C2355F994D3FDDAD120B426A0DF63843', // Connectivity Diagnostics
+ '75E3CFFFC530582C583E4690EF97C70B9C8423B7', // Connectivity Diagnostics
+ '32A1BA997F8AB8DE29ED1BA94AAF00CF2A3FEFA7', // Connectivity Diagnostics
+ 'A291B26E088FA6BA53FFD72F0916F06EBA7C585A', // Chrome OS Recovery Tool
+ 'D7986543275120831B39EF28D1327552FC343960', // Chrome OS Recovery Tool
+ '8EBDF73405D0B84CEABB8C7513C9B9FA9F1DC2CE', // GetHelp app.
+ '97B23E01B2AA064E8332EE43A7A85C628AADC3F2', // Chrome Remote Desktop Dev
+ '9E527CDA9D7C50844E8A5DB964A54A640AE48F98', // Chrome Remote Desktop Stable
+ 'DF52618D0B040D8A054D8348D2E84DDEEE5974E7', // Chrome Remote Desktop QA
+ '269D721F163E587BC53C6F83553BF9CE2BB143CD', // Chrome Remote Desktop QA backup
+ 'C449A798C495E6CF7D6AF10162113D564E67AD12', // Chrome Remote Desktop Apps V2
+ '981974CD1832B87BE6B21BE78F7249BB501E0DE6', // Play Movies Dev
+ '32FD7A816E47392C92D447707A89EB07EEDE6FF7', // Play Movies Nightly
+ '3F3CEC4B9B2B5DC2F820CE917AABDF97DB2F5B49', // Play Movies Beta
+ 'F92FAC70AB68E1778BF62D9194C25979596AA0E6', // Play Movies Stable
+ '0F585FB1D0FDFBEBCE1FEB5E9DFFB6DA476B8C9B', // Hangouts Extension
+ '2D22CDB6583FD0A13758AEBE8B15E45208B4E9A7', // Hangouts Extension
+ '49DA0B9CCEEA299186C6E7226FD66922D57543DC', // Hangouts Extension
+ 'E7E2461CE072DF036CF9592740196159E2D7C089', // Hangouts Extension
+ 'A74A4D44C7CFCD8844830E6140C8D763E12DD8F3', // Hangouts Extension
+ '312745D9BF916161191143F6490085EEA0434997', // Hangouts Extension
+ '53041A2FA309EECED01FFC751E7399186E860B2C', // Hangouts Extension
+ '0F42756099D914A026DADFA182871C015735DD95', // Hangouts Extension
+ '1B7734733E207CCE5C33BFAA544CA89634BF881F', // GLS nightly
+ 'E2ACA3D943A3C96310523BCDFD8C3AF68387E6B7', // GLS stable
+ '11B478CEC461C766A2DC1E5BEEB7970AE06DC9C2', // http://crbug.com/463552
+ '0EFB879311E9EFBB7C45251F89EC655711B1F6ED', // http://crbug.com/463552
+ '9193D3A51E2FE33B496CDA53EA330423166E7F02', // http://crbug.com/463552
+ 'F9119B8B18C7C82B51E7BC6FF816B694F2EC3E89', // http://crbug.com/463552
+ 'BA007D8D52CC0E2632EFCA03ACD003B0F613FD71', // http://crbug.com/470411
+ '5260FA31DE2007A837B7F7B0EB4A47CE477018C8', // http://crbug.com/470411
+ '4F4A25F31413D9B9F80E61D096DEB09082515267', // http://crbug.com/470411
+ 'FBA0DE4D3EFB5485FC03760F01F821466907A743', // http://crbug.com/470411
+ 'E216473E4D15C5FB14522D32C5F8DEAAB2CECDC6', // http://crbug.com/470411
+ '676A08383D875E51CE4C2308D875AE77199F1413', // http://crbug.com/473845
+ '869A23E11B308AF45A68CC386C36AADA4BE44A01', // http://crbug.com/473845
+ 'A4577D8C2AF4CF26F40CBCA83FFA4251D6F6C8F8', // http://crbug.com/478929
+ 'A8208CCC87F8261AFAEB6B85D5E8D47372DDEA6B', // http://crbug.com/478929
];
+
/**
* Function to determine whether or not a given extension id is whitelisted to
- * invoke the feedback UI.
+ * invoke the feedback UI. If the extension is whitelisted, the callback to
+ * start the Feedback UI will be called.
* @param {string} id the id of the sender extension.
- * @return {boolean} Whether or not this sender is whitelisted.
+ * @param {Function} startFeedbackCallback The callback function that will
+ * will start the feedback UI.
+ * @param {Object} feedbackInfo The feedback info object to pass to the
+ * start feedback UI callback.
*/
-function senderWhitelisted(id) {
- return id && whitelistedExtensionIds.indexOf(id) != -1;
+function senderWhitelisted(id, startFeedbackCallback, feedbackInfo) {
+ crypto.subtle.digest('SHA-1', new TextEncoder().encode(id)).then(
+ function(hashBuffer) {
+ var hashString = '';
+ var hashView = new Uint8Array(hashBuffer);
+ for (var i = 0; i < hashView.length; ++i) {
+ var n = hashView[i];
+ hashString += n < 0x10 ? '0' : '';
+ hashString += n.toString(16);
+ }
+ if (whitelistedExtensionIds.indexOf(hashString.toUpperCase()) != -1)
+ startFeedbackCallback(feedbackInfo);
+ });
}
/**
@@ -82,8 +115,8 @@ function feedbackReadyHandler(request, sender, sendResponse) {
* @param {function(Object)} sendResponse Callback for sending a response.
*/
function requestFeedbackHandler(request, sender, sendResponse) {
- if (request.requestFeedback && senderWhitelisted(sender.id))
- startFeedbackUI(request.feedbackInfo);
+ if (request.requestFeedback)
+ senderWhitelisted(sender.id, startFeedbackUI, request.feedbackInfo);
}
/**
@@ -92,6 +125,11 @@ function requestFeedbackHandler(request, sender, sendResponse) {
*/
function startFeedbackUI(feedbackInfo) {
initialFeedbackInfo = feedbackInfo;
+ var win = chrome.app.window.get('default_window');
+ if (win) {
+ win.show();
+ return;
+ }
chrome.app.window.create('html/default.html', {
frame: 'none',
id: 'default_window',
diff --git a/chromium/chrome/browser/resources/flags.css b/chromium/chrome/browser/resources/flags.css
index 30f749b0e3d..6bae533168f 100644
--- a/chromium/chrome/browser/resources/flags.css
+++ b/chromium/chrome/browser/resources/flags.css
@@ -7,8 +7,8 @@ body {
<if expr="not is_android and not is_ios">
min-width: 47em;
</if>
- /* Should match needs-restart.height + 5 */
- padding-bottom: 75px;
+ /* Should be larger than the evaluated height of needs-restart. */
+ padding-bottom: 100px;
}
a {
@@ -23,8 +23,8 @@ a {
#header {
-webkit-padding-start: 75px;
background: -webkit-image-set(
- url('../../app/theme/default_100_percent/flags_section.png') 1x,
- url('../../app/theme/default_200_percent/flags_section.png') 2x)
+ url(../../app/theme/default_100_percent/flags_section.png) 1x,
+ url(../../app/theme/default_200_percent/flags_section.png) 2x)
no-repeat left center;
box-sizing: border-box;
display: table;
@@ -164,14 +164,15 @@ html[dir=rtl] #experiment-reset-all {
}
div.needs-restart {
+ /* If you modify properties that change the height of this,
+ * update body.padding-bottom. */
background: #FFF;
border-top: 1px solid rgb(181, 199, 222);
bottom: 0;
+ box-shadow: 0 -2px 2px #ddd;
box-sizing: border-box;
- /* If you change this, update body.padding-bottom */
- height: 70px;
left: 0;
- padding-bottom: 25px;
+ padding-bottom: 15px;
padding-left: 15px;
padding-right: 15px;
padding-top: 15px;
@@ -179,6 +180,21 @@ div.needs-restart {
width: 100%;
}
+.experiment-restart-button {
+ -webkit-user-select: none;
+ background: rgb(76, 142, 250);
+ border: 0;
+ border-radius: 2px;
+ box-sizing: border-box;
+ color: #fff;
+ cursor: pointer;
+ font-weight: 700;
+ margin-top: 10px;
+ padding: 10px 24px;
+ text-transform: uppercase;
+ transition: box-shadow 200ms cubic-bezier(0.4, 0, 0.2, 1);
+}
+
button {
font-size: 104%;
}
diff --git a/chromium/chrome/browser/resources/flags.html b/chromium/chrome/browser/resources/flags.html
index e0f75c7440b..9a4cd3c71a6 100644
--- a/chromium/chrome/browser/resources/flags.html
+++ b/chromium/chrome/browser/resources/flags.html
@@ -1,17 +1,18 @@
-<!DOCTYPE HTML>
-<html i18n-values="dir:textdirection;">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
<head>
<meta charset="utf-8">
<if expr="is_android">
<meta name="viewport" content="width=device-width, user-scalable=no">
</if>
+<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="flags.css">
<script src="chrome://resources/js/load_time_data.js"></script>
<script src="chrome://resources/js/util.js"></script>
<script src="chrome://flags/flags.js"></script>
<script src="chrome://flags/strings.js"></script>
</head>
-<body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<body>
<div id="body-container" style="visibility:hidden">
<div id="header">
@@ -171,7 +172,7 @@
</div>
</div>
</div>
-<script src="chrome://resources/js/i18n_template2.js"></script>
+<script src="chrome://resources/js/i18n_template.js"></script>
<script src="chrome://resources/js/jstemplate_compiled.js"></script>
</body>
</html>
diff --git a/chromium/chrome/browser/resources/gaia_auth/background.js b/chromium/chrome/browser/resources/gaia_auth/background.js
index 09be3427a24..3b529f33463 100644
--- a/chromium/chrome/browser/resources/gaia_auth/background.js
+++ b/chromium/chrome/browser/resources/gaia_auth/background.js
@@ -109,6 +109,13 @@ BackgroundBridge.prototype = {
// The associated tab ID. Only used for debugging now.
tabId: null,
+ // The initial URL loaded in the gaia iframe. We only want to handle
+ // onCompleted() for the frame that loaded this URL.
+ initialFrameUrlWithoutParams: null,
+
+ // On process onCompleted() requests that come from this frame Id.
+ frameId: -1,
+
isDesktopFlow_: false,
// Whether the extension is loaded in a constrained window.
@@ -202,6 +209,7 @@ BackgroundBridge.prototype = {
this.isDesktopFlow_ = true;
this.gaiaUrl_ = msg.gaiaUrl;
this.isConstrainedWindow_ = msg.isConstrainedWindow;
+ this.initialFrameUrlWithoutParams = msg.initialFrameUrlWithoutParams;
},
/**
@@ -211,10 +219,21 @@ BackgroundBridge.prototype = {
* script of switching to full tab if necessary.
*/
onCompleted: function(details) {
- // Only monitors requests in the gaia frame whose parent frame ID must be
- // positive.
- if (details.parentFrameId <= 0)
+ // Only monitors requests in the gaia frame. The gaia frame is the one
+ // where the initial frame URL completes.
+ if (details.url.lastIndexOf(
+ this.initialFrameUrlWithoutParams, 0) == 0) {
+ this.frameId = details.frameId;
+ }
+ if (this.frameId == -1) {
+ // If for some reason the frameId could not be set above, just make sure
+ // the frame is more than two levels deep (since the gaia frame is at
+ // least three levels deep).
+ if (details.parentFrameId <= 0)
+ return;
+ } else if (details.frameId != this.frameId) {
return;
+ }
if (details.url.lastIndexOf(backgroundBridgeManager.CONTINUE_URL_BASE, 0) ==
0) {
@@ -383,6 +402,7 @@ BackgroundBridge.prototype = {
onResetAuth_: function() {
this.authStarted_ = false;
this.passwordStore_ = {};
+ this.isSAML_ = false;
},
/**
@@ -391,11 +411,12 @@ BackgroundBridge.prototype = {
onAuthStarted_: function() {
this.authStarted_ = true;
this.passwordStore_ = {};
+ this.isSAML_ = false;
},
/**
* Handler for 'getScrapedPasswords' request sent from the main script.
- * @return {Array.<string>} The array with de-duped scraped passwords.
+ * @return {Array<string>} The array with de-duped scraped passwords.
*/
onGetScrapedPasswords_: function() {
var passwords = {};
diff --git a/chromium/chrome/browser/resources/gaia_auth/channel.js b/chromium/chrome/browser/resources/gaia_auth/channel.js
index e52e2c8c415..52094b9d925 100644
--- a/chromium/chrome/browser/resources/gaia_auth/channel.js
+++ b/chromium/chrome/browser/resources/gaia_auth/channel.js
@@ -109,3 +109,11 @@ Channel.prototype = {
}
}
};
+
+/**
+ * Class factory.
+ * @return {Channel}
+ */
+Channel.create = function() {
+ return new Channel();
+};
diff --git a/chromium/chrome/browser/resources/gaia_auth/main.html b/chromium/chrome/browser/resources/gaia_auth/main.html
index a53b3fe919c..20d079ea250 100644
--- a/chromium/chrome/browser/resources/gaia_auth/main.html
+++ b/chromium/chrome/browser/resources/gaia_auth/main.html
@@ -1,4 +1,4 @@
-<!DOCTYPE html>
+<!doctype html>
<html>
<head>
<link rel="stylesheet" href="main.css">
@@ -9,7 +9,7 @@
</head>
<body>
<iframe id="gaia-frame" name="gaia-frame" src="about:blank" frameborder="0"
- sandbox="allow-same-origin allow-scripts allow-popups allow-forms
- allow-pointer-lock"></iframe>
+ sandbox="allow-same-origin allow-scripts allow-popups allow-forms
+ allow-pointer-lock"></iframe>
</body>
</html>
diff --git a/chromium/chrome/browser/resources/gaia_auth/main.js b/chromium/chrome/browser/resources/gaia_auth/main.js
index 7523ceb65c2..e00869c9f52 100644
--- a/chromium/chrome/browser/resources/gaia_auth/main.js
+++ b/chromium/chrome/browser/resources/gaia_auth/main.js
@@ -36,6 +36,15 @@ Authenticator.API_KEY_TYPES = [
];
/**
+ * Allowed origins of the hosting page.
+ * @type {Array<string>}
+ */
+Authenticator.ALLOWED_PARENT_ORIGINS = [
+ 'chrome://oobe',
+ 'chrome://chrome-signin'
+];
+
+/**
* Singleton getter of Authenticator.
* @return {Object} The singleton instance of Authenticator.
*/
@@ -57,6 +66,7 @@ Authenticator.prototype = {
// when support for key types other than plain text password is added.
passwordBytes_: null,
+ needPassword_: false,
chooseWhatToSync_: false,
skipForNow_: false,
sessionIndex_: null,
@@ -70,16 +80,37 @@ Authenticator.prototype = {
gaiaLoaded_: false,
supportChannel_: null,
+ useEafe_: false,
+ clientId_: '',
+
GAIA_URL: 'https://accounts.google.com/',
GAIA_PAGE_PATH: 'ServiceLogin?skipvpage=true&sarp=1&rm=hide',
- PARENT_PAGE: 'chrome://oobe/',
SERVICE_ID: 'chromeoslogin',
CONTINUE_URL: Authenticator.THIS_EXTENSION_ORIGIN + '/success.html',
CONSTRAINED_FLOW_SOURCE: 'chrome',
initialize: function() {
- var params = getUrlSearchParams(location.search);
- this.parentPage_ = params.parentPage || this.PARENT_PAGE;
+ var handleInitializeMessage = function(e) {
+ if (Authenticator.ALLOWED_PARENT_ORIGINS.indexOf(e.origin) == -1) {
+ console.error('Unexpected parent message, origin=' + e.origin);
+ return;
+ }
+ window.removeEventListener('message', handleInitializeMessage);
+
+ var params = e.data;
+ params.parentPage = e.origin;
+ this.initializeFromParent_(params);
+ this.onPageLoad_();
+ }.bind(this);
+
+ document.addEventListener('DOMContentLoaded', function() {
+ window.addEventListener('message', handleInitializeMessage);
+ window.parent.postMessage({'method': 'loginUIDOMContentLoaded'}, '*');
+ });
+ },
+
+ initializeFromParent_: function(params) {
+ this.parentPage_ = params.parentPage;
this.gaiaUrl_ = params.gaiaUrl || this.GAIA_URL;
this.gaiaPath_ = params.gaiaPath || this.GAIA_PAGE_PATH;
this.inputLang_ = params.hl;
@@ -88,8 +119,11 @@ Authenticator.prototype = {
this.continueUrl_ = params.continueUrl || this.CONTINUE_URL;
this.desktopMode_ = params.desktopMode == '1';
this.isConstrainedWindow_ = params.constrained == '1';
+ this.useEafe_ = params.useEafe || false;
+ this.clientId_ = params.clientId || '';
this.initialFrameUrl_ = params.frameUrl || this.constructInitialFrameUrl_();
this.initialFrameUrlWithoutParams_ = stripParams(this.initialFrameUrl_);
+ this.needPassword_ = params.needPassword == '1';
// For CrOS 'ServiceLogin' we assume that Gaia is loaded if we recieved
// 'clearOldAttempts' message. For other scenarios Gaia doesn't send this
@@ -97,9 +131,8 @@ Authenticator.prototype = {
// TODO(dzhioev): Do not rely on 'load' event after b/16313327 is fixed.
this.assumeLoadedOnLoadEvent_ =
this.gaiaPath_.indexOf('ServiceLogin') !== 0 ||
- this.service_ !== 'chromeoslogin';
-
- document.addEventListener('DOMContentLoaded', this.onPageLoad_.bind(this));
+ this.service_ !== 'chromeoslogin' ||
+ this.useEafe_;
},
isGaiaMessage_: function(msg) {
@@ -116,7 +149,10 @@ Authenticator.prototype = {
var url = this.gaiaUrl_ + this.gaiaPath_;
url = appendParam(url, 'service', this.service_);
- url = appendParam(url, 'continue', this.continueUrl_);
+ // Easy bootstrap use auth_code message as success signal instead of
+ // continue URL.
+ if (!this.useEafe_)
+ url = appendParam(url, 'continue', this.continueUrl_);
if (this.inputLang_)
url = appendParam(url, 'hl', this.inputLang_);
if (this.inputEmail_)
@@ -130,15 +166,21 @@ Authenticator.prototype = {
window.addEventListener('message', this.onMessage.bind(this), false);
this.initSupportChannel_();
- var gaiaFrame = $('gaia-frame');
- gaiaFrame.src = this.initialFrameUrl_;
-
if (this.assumeLoadedOnLoadEvent_) {
+ var gaiaFrame = $('gaia-frame');
var handler = function() {
gaiaFrame.removeEventListener('load', handler);
if (!this.gaiaLoaded_) {
this.gaiaLoaded_ = true;
this.maybeInitialized_();
+
+ if (this.useEafe_ && this.clientId_) {
+ // Sends initial handshake message to EAFE. Note this fails with
+ // SSO redirect because |gaiaFrame| sits on a different origin.
+ gaiaFrame.contentWindow.postMessage({
+ clientId: this.clientId_
+ }, this.gaiaUrl_);
+ }
}
}.bind(this);
gaiaFrame.addEventListener('load', handler);
@@ -150,6 +192,11 @@ Authenticator.prototype = {
supportChannel.connect('authMain');
supportChannel.registerMessage('channelConnected', function() {
+ // Load the gaia frame after the background page indicates that it is
+ // ready, so that the webRequest handlers are all setup first.
+ var gaiaFrame = $('gaia-frame');
+ gaiaFrame.src = this.initialFrameUrl_;
+
if (this.supportChannel_) {
console.error('Support channel is already initialized.');
return;
@@ -161,14 +208,17 @@ Authenticator.prototype = {
name: 'initDesktopFlow',
gaiaUrl: this.gaiaUrl_,
continueUrl: stripParams(this.continueUrl_),
- isConstrainedWindow: this.isConstrainedWindow_
+ isConstrainedWindow: this.isConstrainedWindow_,
+ initialFrameUrlWithoutParams: this.initialFrameUrlWithoutParams_
});
+
this.supportChannel_.registerMessage(
'switchToFullTab', this.switchToFullTab_.bind(this));
}
this.supportChannel_.registerMessage(
'completeLogin', this.onCompleteLogin_.bind(this));
this.initSAML_();
+ this.supportChannel_.send({name: 'resetAuth'});
this.maybeInitialized_();
}.bind(this));
@@ -330,6 +380,13 @@ Authenticator.prototype = {
}
},
+ onGotAuthCode_: function(authCode) {
+ window.parent.postMessage({
+ 'method': 'completeAuthenticationAuthCodeOnly',
+ 'authCode': authCode
+ }, this.parentPage_);
+ },
+
sendInitializationSuccess_: function() {
this.supportChannel_.send({name: 'apiResponse', response: {
result: 'initialized',
@@ -351,9 +408,16 @@ Authenticator.prototype = {
*/
onCompleteLogin_: function(msg) {
if (!msg.email || !msg.gaiaId || !msg.sessionIndex) {
- console.error('Missing fields to complete login.');
- window.parent.postMessage({method: 'missingGaiaInfo'}, this.parentPage_);
- return;
+ // On desktop, if the skipForNow message field is set, send it to handler.
+ // This does not require the email, gaiaid or session to be valid.
+ if (this.desktopMode_ && msg.skipForNow) {
+ this.completeLogin_(msg);
+ } else {
+ console.error('Missing fields to complete login.');
+ window.parent.postMessage({method: 'missingGaiaInfo'},
+ this.parentPage_);
+ return;
+ }
}
// Skip SAML extra steps for desktop flow and non-SAML flow.
@@ -370,8 +434,15 @@ Authenticator.prototype = {
this.sessionIndex_ = msg.sessionIndex;
if (this.passwordBytes_) {
+ // If the credentials passing API was used, login is complete.
window.parent.postMessage({method: 'samlApiUsed'}, this.parentPage_);
this.completeLogin_(msg);
+ } else if (!this.needPassword_) {
+ // If the credentials passing API was not used, the password was obtained
+ // by scraping. It must be verified before use. However, the host may not
+ // be interested in the password at all. In that case, verification is
+ // unnecessary and login is complete.
+ this.completeLogin_(msg);
} else {
this.supportChannel_.sendWithCallback(
{name: 'getScrapedPasswords'},
@@ -412,6 +483,26 @@ Authenticator.prototype = {
onMessage: function(e) {
var msg = e.data;
+
+ if (this.useEafe_) {
+ if (msg == '!_{h:\'gaia-frame\'}' && this.isGaiaMessage_(e)) {
+ // Sends client ID again on the hello message to work around the SSO
+ // signin issue.
+ // TODO(xiyuan): Revisit this when EAFE is integrated or for webview.
+ $('gaia-frame').contentWindow.postMessage({
+ clientId: this.clientId_
+ }, this.gaiaUrl_);
+ } else if (typeof msg == 'object' &&
+ msg.type == 'authorizationCode' && this.isGaiaMessage_(e)) {
+ this.onGotAuthCode_(msg.authorizationCode);
+ } else {
+ console.error('Authenticator.onMessage: unknown message' +
+ ', msg=' + JSON.stringify(msg));
+ }
+
+ return;
+ }
+
if (msg.method == 'attemptLogin' && this.isGaiaMessage_(e)) {
// At this point GAIA does not yet know the gaiaId, so its not set here.
this.email_ = msg.email;
@@ -436,8 +527,14 @@ Authenticator.prototype = {
this.isSAMLFlow_ = false;
this.skipForNow_ = false;
this.chooseWhatToSync_ = false;
- if (this.supportChannel_)
+ if (this.supportChannel_) {
this.supportChannel_.send({name: 'resetAuth'});
+ // This message is for clearing saml properties in gaia_auth_host and
+ // oobe_screen_oauth_enrollment.
+ window.parent.postMessage({
+ 'method': 'resetAuthFlow',
+ }, this.parentPage_);
+ }
} else if (msg.method == 'verifyConfirmedPassword' &&
this.isParentMessage_(e)) {
this.onVerifyConfirmedPassword_(msg.password);
@@ -445,7 +542,7 @@ Authenticator.prototype = {
this.isParentMessage_(e)) {
$('gaia-frame').src = this.constructInitialFrameUrl_();
} else {
- console.error('Authenticator.onMessage: unknown message + origin!?');
+ console.error('Authenticator.onMessage: unknown message + origin!?');
}
}
};
diff --git a/chromium/chrome/browser/resources/gaia_auth/manifest.json b/chromium/chrome/browser/resources/gaia_auth/manifest.json
index 7295c01de73..e62cc451130 100644
--- a/chromium/chrome/browser/resources/gaia_auth/manifest.json
+++ b/chromium/chrome/browser/resources/gaia_auth/manifest.json
@@ -5,7 +5,7 @@
"version": "0.0.1",
"manifest_version": 2,
"background" : {
- "scripts": ["background.js", "channel.js"]
+ "scripts": ["channel.js", "background.js"]
},
"content_scripts": [
{
@@ -17,8 +17,9 @@
"all_frames": true
}
],
- "content_security_policy": "default-src 'self'; script-src 'self'; frame-src *; style-src 'self' 'unsafe-inline'",
+ "content_security_policy": "default-src 'self'; script-src 'self'; frame-src 'self' http: https:; style-src 'self'",
"description": "GAIA Component Extension",
+ "incognito": "split",
"web_accessible_resources": [
"main.css",
"main.html",
diff --git a/chromium/chrome/browser/resources/gaia_auth/manifest_keyboard.json b/chromium/chrome/browser/resources/gaia_auth/manifest_keyboard.json
index 68d59a1f36e..676bb98c1e6 100644
--- a/chromium/chrome/browser/resources/gaia_auth/manifest_keyboard.json
+++ b/chromium/chrome/browser/resources/gaia_auth/manifest_keyboard.json
@@ -5,7 +5,7 @@
"version": "0.0.1",
"manifest_version": 2,
"background" : {
- "scripts": ["background.js", "channel.js"]
+ "scripts": ["channel.js", "background.js"]
},
"content_scripts": [
{
@@ -25,8 +25,9 @@
"all_frames": true
}
],
- "content_security_policy": "default-src 'self'; script-src 'self'; frame-src *; style-src 'self' 'unsafe-inline'",
+ "content_security_policy": "default-src 'self'; script-src 'self'; frame-src 'self' http: https:; style-src 'self'",
"description": "GAIA Component Extension",
+ "incognito": "split",
"web_accessible_resources": [
"main.css",
"main.html",
diff --git a/chromium/chrome/browser/resources/gaia_auth/offline.css b/chromium/chrome/browser/resources/gaia_auth/offline.css
index b36d3c942b6..5bf69db67d0 100644
--- a/chromium/chrome/browser/resources/gaia_auth/offline.css
+++ b/chromium/chrome/browser/resources/gaia_auth/offline.css
@@ -1,6 +1,36 @@
-body,div, dl, h1, h2, h3, h4, h5, h6, html, img,
-dd, dt, embed, form, object, td, tr, canvas, command, group, mark, meter,
-output, progress, summary, audio, time, video {
+/* 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. */
+/* TODO(dbeam): what's wrong with * here? Specificity issues? */
+audio,
+body,
+canvas,
+command,
+dd,
+div,
+dl,
+dt,
+embed,
+form,
+group,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+html,
+img,
+mark,
+meter,
+object,
+output,
+progress,
+summary,
+td,
+time,
+tr,
+video {
border: 0;
margin: 0;
padding: 0;
@@ -12,7 +42,12 @@ html {
font: 81.25% arial, helvetica, sans-serif;
line-height: 1;
}
-h1, h2, h3, h4, h5, h6 {
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
color: #222;
font-size: 1.54em;
font-weight: normal;
@@ -22,7 +57,8 @@ h1, h2, h3, h4, h5, h6 {
strong {
color: #222;
}
-body, html {
+body,
+html {
height: 100%;
min-width: 100%;
position: absolute;
@@ -40,7 +76,10 @@ body, html {
padding-top: 23px;
width: 650px;
}
-button, input, select, textarea {
+button,
+input,
+select,
+textarea {
font-family: inherit;
font-size: inherit;
}
@@ -49,7 +88,6 @@ input[type=number],
input[type=password],
input[type=text],
input[type=url] {
- -webkit-border-radius: 1px;
-webkit-box-sizing: border-box;
background: #fff;
border: 1px solid #d9d9d9;
@@ -66,7 +104,6 @@ input[type=number]:hover,
input[type=password]:hover,
input[type=text]:hover,
input[type=url]:hover {
- -webkit-box-shadow: inset 0 1px 2px rgba(0,0,0,0.1);
border: 1px solid #b9b9b9;
border-top: 1px solid #a0a0a0;
box-shadow: inset 0 1px 2px rgba(0,0,0,0.1);
@@ -76,8 +113,7 @@ input[type=number]:focus,
input[type=password]:focus,
input[type=text]:focus,
input[type=url]:focus {
- -webkit-box-shadow: inset 0 1px 2px rgba(0,0,0,0.3);
- border: 1px solid #4d90fe;
+ border: 1px solid rgb(77, 144, 254);
box-shadow: inset 0 1px 2px rgba(0,0,0,0.3);
outline: none;
}
@@ -94,11 +130,9 @@ input[type=number][disabled=disabled]:hover,
input[type=password][disabled=disabled]:hover,
input[type=text][disabled=disabled]:hover,
input[type=url][disabled=disabled]:hover {
- -webkit-box-shadow: none;
box-shadow: none;
}
.g-button {
- -webkit-border-radius: 2px;
-webkit-transition: all 218ms;
-webkit-user-select: none;
background-color: #f5f5f5;
@@ -133,18 +167,16 @@ input[type=submit].g-button {
overflow: visible;
}
.g-button:hover {
- -webkit-box-shadow: 0 1px 1px rgba(0,0,0,0.1);
- -webkit-transition: all 0;
+ -webkit-transition: all 0ms;
background-color: #f8f8f8;
background-image: linear-gradient(to bottom, #f8f8f8, #f1f1f1);
border: 1px solid #c6c6c6;
box-shadow: 0 1px 1px rgba(0,0,0,0.1);
color: #333;
text-decoration: none;
- transition: all 0;
+ transition: all 0ms;
}
.g-button:active {
- -webkit-box-shadow: inset 0 1px 2px rgba(0,0,0,0.1);
background-color: #f6f6f6;
background-image: linear-gradient(to bottom, #f6f6f6, #f1f1f1);
box-shadow: inset 0 1px 2px rgba(0,0,0,0.1);
@@ -153,32 +185,31 @@ input[type=submit].g-button {
color: #666;
}
.g-button-submit {
- background-color: #4d90fe;
- background-image: linear-gradient(to bottom, #4d90fe, #4787ed);
- border: 1px solid #3079ed;
+ background-color: rgb(77, 144, 254);
+ background-image:
+ linear-gradient(to bottom, rgb(77, 144, 254), rgb(71, 135, 237));
+ border: 1px solid rgb(48, 121, 237);
color: #fff;
text-shadow: 0 1px rgba(0,0,0,0.1);
}
.g-button-submit:hover {
- background-color: #357ae8;
- background-image: linear-gradient(to bottom, #4d90fe, #357ae8);
- border: 1px solid #2f5bb7;
+ background-color: rgb(53, 122, 232);
+ background-image:
+ linear-gradient(to bottom, rgb(77, 144, 254), rgb(53, 122, 232));
+ border: 1px solid rgb(47, 91, 183);
color: #fff;
text-shadow: 0 1px rgba(0,0,0,0.3);
}
.g-button-submit:active {
- -webkit-box-shadow: inset 0 1px 2px rgba(0,0,0,0.3);
box-shadow: inset 0 1px 2px rgba(0,0,0,0.3);
}
.g-button-submit:visited {
color: #fff;
}
.g-button-submit:focus {
- -webkit-box-shadow: inset 0 0 0 1px #fff;
box-shadow: inset 0 0 0 1px #fff;
}
.g-button-submit:focus:hover {
- -webkit-box-shadow: inset 0 0 0 1px #fff, 0 1px 1px rgba(0,0,0,0.1);
box-shadow: inset 0 0 0 1px #fff, 0 1px 1px rgba(0,0,0,0.1);
}
.g-button:hover img {
@@ -188,7 +219,7 @@ input[type=submit].g-button {
opacity: 1;
}
.errormsg {
- color: #dd4b39;
+ color: rgb(221, 75, 57);
display: block;
line-height: 17px;
margin: .5em 0 0;
@@ -200,7 +231,7 @@ input[type=text].form-error,
input[type=url].form-error,
input[type=text].field-error,
input[type=password].field-error {
- border: 1px solid #dd4b39;
+ border: 1px solid rgb(221, 75, 57);
}
html {
background: transparent;
diff --git a/chromium/chrome/browser/resources/gaia_auth/offline.html b/chromium/chrome/browser/resources/gaia_auth/offline.html
index e0173a944b0..a9afb7c8035 100644
--- a/chromium/chrome/browser/resources/gaia_auth/offline.html
+++ b/chromium/chrome/browser/resources/gaia_auth/offline.html
@@ -1,4 +1,4 @@
-<!DOCTYPE html>
+<!doctype html>
<html>
<head>
<meta charset="utf-8">
diff --git a/chromium/chrome/browser/resources/gaia_auth/offline.js b/chromium/chrome/browser/resources/gaia_auth/offline.js
index 7b4da1f5285..aaf60450da8 100644
--- a/chromium/chrome/browser/resources/gaia_auth/offline.js
+++ b/chromium/chrome/browser/resources/gaia_auth/offline.js
@@ -6,9 +6,11 @@
* @fileoverview Offline login implementation.
*/
-function load() {
- var params = getUrlSearchParams(location.search);
-
+/**
+ * Initialize the offline page.
+ * @param {Object} params Intialization params passed from parent page.
+ */
+function load(params) {
// Setup localized strings.
$('sign-in-title').textContent = decodeURIComponent(params['stringSignIn']);
$('email-label').textContent = decodeURIComponent(params['stringEmail']);
@@ -59,4 +61,27 @@ function load() {
window.parent.postMessage({'method': 'loginUILoaded'}, 'chrome://oobe/');
}
-document.addEventListener('DOMContentLoaded', load);
+/**
+ * Handles initialization message from parent page.
+ * @param {MessageEvent} e
+ */
+function handleInitializeMessage(e) {
+ var ALLOWED_PARENT_ORIGINS = [
+ 'chrome://oobe',
+ 'chrome://chrome-signin'
+ ];
+
+ if (ALLOWED_PARENT_ORIGINS.indexOf(e.origin) == -1)
+ return;
+
+ window.removeEventListener('message', handleInitializeMessage);
+
+ var params = e.data;
+ params.parentPage = e.origin;
+ load(params);
+}
+
+document.addEventListener('DOMContentLoaded', function() {
+ window.addEventListener('message', handleInitializeMessage);
+ window.parent.postMessage({'method': 'loginUIDOMContentLoaded'}, '*');
+});
diff --git a/chromium/chrome/browser/resources/gaia_auth/saml_injected.js b/chromium/chrome/browser/resources/gaia_auth/saml_injected.js
index fd08bd54896..ae56f5cc537 100644
--- a/chromium/chrome/browser/resources/gaia_auth/saml_injected.js
+++ b/chromium/chrome/browser/resources/gaia_auth/saml_injected.js
@@ -78,6 +78,9 @@
// An array to hold cached password values.
passwordValues_: null,
+ // A MutationObserver to watch for dynamic password field creation.
+ passwordFieldsObserver: null,
+
/**
* Initialize the scraper with given channel and docRoot. Note that the
* scanning for password fields happens inside the function and does not
@@ -91,15 +94,59 @@
this.pageURL_ = pageURL;
this.channel_ = channel;
- this.passwordFields_ = docRoot.querySelectorAll('input[type=password]');
+ this.passwordFields_ = [];
this.passwordValues_ = [];
- for (var i = 0; i < this.passwordFields_.length; ++i) {
- this.passwordFields_[i].addEventListener(
- 'input', this.onPasswordChanged_.bind(this, i));
+ this.findAndTrackChildren(docRoot);
+
+ this.passwordFieldsObserver = new MutationObserver(function(mutations) {
+ mutations.forEach(function(mutation) {
+ Array.prototype.forEach.call(
+ mutation.addedNodes,
+ function(addedNode) {
+ if (addedNode.nodeType != Node.ELEMENT_NODE)
+ return;
+
+ if (addedNode.matches('input[type=password]')) {
+ this.trackPasswordField(addedNode);
+ } else {
+ this.findAndTrackChildren(addedNode);
+ }
+ }.bind(this));
+ }.bind(this));
+ }.bind(this));
+ this.passwordFieldsObserver.observe(docRoot,
+ {subtree: true, childList: true});
+ },
- this.passwordValues_[i] = this.passwordFields_[i].value;
- }
+ /**
+ * Find and track password fields that are descendants of the given element.
+ * @param {!HTMLElement} element The parent element to search from.
+ */
+ findAndTrackChildren: function(element) {
+ Array.prototype.forEach.call(
+ element.querySelectorAll('input[type=password]'), function(field) {
+ this.trackPasswordField(field);
+ }.bind(this));
+ },
+
+ /**
+ * Start tracking value changes of the given password field if it is
+ * not being tracked yet.
+ * @param {!HTMLInputElement} passworField The password field to track.
+ */
+ trackPasswordField: function(passwordField) {
+ var existing = this.passwordFields_.filter(function(element) {
+ return element === passwordField;
+ });
+ if (existing.length != 0)
+ return;
+
+ var index = this.passwordFields_.length;
+ passwordField.addEventListener(
+ 'input', this.onPasswordChanged_.bind(this, index));
+ this.passwordFields_.push(passwordField);
+ this.passwordValues_.push(passwordField.value);
},
/**
@@ -115,7 +162,7 @@
// Use an invalid char for URL as delimiter to concatenate page url and
// password field index to construct a unique ID for the password field.
- var passwordId = this.pageURL_ + '|' + index;
+ var passwordId = this.pageURL_.split('#')[0].split('?')[0] + '|' + index;
this.channel_.send({
name: 'updatePassword',
id: passwordId,
@@ -140,16 +187,28 @@
channel.send({name: 'pageLoaded', url: pageURL});
- var apiCallForwarder = new APICallForwarder();
- apiCallForwarder.init(channel);
-
- var passwordScraper = new PasswordInputScraper();
- passwordScraper.init(channel, pageURL, document.documentElement);
+ var initPasswordScraper = function() {
+ var passwordScraper = new PasswordInputScraper();
+ passwordScraper.init(channel, pageURL, document.documentElement);
+ };
+
+ if (document.readyState == 'loading') {
+ window.addEventListener('readystatechange', function listener(event) {
+ if (document.readyState == 'loading')
+ return;
+ initPasswordScraper();
+ window.removeEventListener(event.type, listener, true);
+ }, true);
+ } else {
+ initPasswordScraper();
+ }
}
- var channel = new Channel();
+ var channel = Channel.create();
channel.connect('injected');
channel.sendWithCallback({name: 'getSAMLFlag'},
onGetSAMLFlag.bind(undefined, channel));
+ var apiCallForwarder = new APICallForwarder();
+ apiCallForwarder.init(channel);
})();
diff --git a/chromium/chrome/browser/resources/gaia_auth/success.html b/chromium/chrome/browser/resources/gaia_auth/success.html
index fa5b1e6cd92..bdbf41a69ba 100644
--- a/chromium/chrome/browser/resources/gaia_auth/success.html
+++ b/chromium/chrome/browser/resources/gaia_auth/success.html
@@ -1,4 +1,4 @@
-<!DOCTYPE html>
+<!doctype html>
<head>
<meta charset="utf-8">
</head>
diff --git a/chromium/chrome/browser/resources/gaia_auth/util.js b/chromium/chrome/browser/resources/gaia_auth/util.js
index 9d8ebd7237d..1ad55136f41 100644
--- a/chromium/chrome/browser/resources/gaia_auth/util.js
+++ b/chromium/chrome/browser/resources/gaia_auth/util.js
@@ -12,31 +12,6 @@ function $(id) {
}
/**
- * Extract query params from given search string of an URL.
- * @param {string} search The search portion of an URL to extract params.
- * @return {Object} The key value pairs of the extracted params.
- */
-function getUrlSearchParams(search) {
- var params = {};
-
- if (search) {
- // Strips leading '?'
- search = search.substring(1);
- var pairs = search.split('&');
- for (var i = 0; i < pairs.length; ++i) {
- var pair = pairs[i].split('=');
- if (pair.length == 2) {
- params[pair[0]] = decodeURIComponent(pair[1]);
- } else {
- params[pair] = true;
- }
- }
- }
-
- return params;
-}
-
-/**
* Creates a new URL which is the old URL with a GET param of key=value.
* Copied from ui/webui/resources/js/util.js.
* @param {string} url The base URL. There is not sanity checking on the URL so
diff --git a/chromium/chrome/browser/resources/gaia_auth_host/authenticator.js b/chromium/chrome/browser/resources/gaia_auth_host/authenticator.js
index 4a1bf5aa7ff..870b368258f 100644
--- a/chromium/chrome/browser/resources/gaia_auth_host/authenticator.js
+++ b/chromium/chrome/browser/resources/gaia_auth_host/authenticator.js
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+<include src="saml_handler.js">
+
/**
* @fileoverview An UI component to authenciate to Chrome. The component hosts
* IdP web pages in a webview. A client who is interested in monitoring
@@ -9,16 +11,24 @@
* cr.login.GaiaAuthHost.Listener as defined in this file. After initialization,
* call {@code load} to start the authentication flow.
*/
+
cr.define('cr.login', function() {
'use strict';
+ // TODO(rogerta): should use gaia URL from GaiaUrls::gaia_url() instead
+ // of hardcoding the prod URL here. As is, this does not work with staging
+ // environments.
var IDP_ORIGIN = 'https://accounts.google.com/';
var IDP_PATH = 'ServiceLogin?skipvpage=true&sarp=1&rm=hide';
var CONTINUE_URL =
'chrome-extension://mfffpogegjflfpflabcdkioaeobkgjik/success.html';
var SIGN_IN_HEADER = 'google-accounts-signin';
var EMBEDDED_FORM_HEADER = 'google-accounts-embedded';
- var SAML_HEADER = 'google-accounts-saml';
+ var LOCATION_HEADER = 'location';
+ var SET_COOKIE_HEADER = 'set-cookie';
+ var OAUTH_CODE_COOKIE = 'oauth_code';
+ var SERVICE_ID = 'chromeoslogin';
+ var EMBEDDED_SETUP_CHROMEOS_ENDPOINT = 'embedded/setup/chromeos';
/**
* The source URL parameter for the constrained signin flow.
@@ -46,79 +56,128 @@ cr.define('cr.login', function() {
};
/**
+ * Supported Authenticator params.
+ * @type {!Array<string>}
+ * @const
+ */
+ var SUPPORTED_PARAMS = [
+ 'gaiaId', // Obfuscated GAIA ID to skip the email prompt page
+ // during the re-auth flow.
+ 'gaiaUrl', // Gaia url to use.
+ 'gaiaPath', // Gaia path to use without a leading slash.
+ 'hl', // Language code for the user interface.
+ 'email', // Pre-fill the email field in Gaia UI.
+ 'service', // Name of Gaia service.
+ 'continueUrl', // Continue url to use.
+ 'frameUrl', // Initial frame URL to use. If empty defaults to
+ // gaiaUrl.
+ 'constrained', // Whether the extension is loaded in a constrained
+ // window.
+ 'clientId', // Chrome client id.
+ 'useEafe', // Whether to use EAFE.
+ 'needPassword', // Whether the host is interested in getting a password.
+ // If this set to |false|, |confirmPasswordCallback| is
+ // not called before dispatching |authCopleted|.
+ // Default is |true|.
+ 'flow', // One of 'default', 'enterprise', or 'theftprotection'.
+ 'enterpriseDomain', // Domain in which hosting device is (or should be)
+ // enrolled.
+ 'emailDomain', // Value used to prefill domain for email.
+ 'clientVersion', // Version of the Chrome build.
+ 'platformVersion', // Version of the OS build.
+ 'releaseChannel', // Installation channel.
+ 'endpointGen', // Current endpoint generation.
+ ];
+
+ /**
* Initializes the authenticator component.
* @param {webview|string} webview The webview element or its ID to host IdP
* web pages.
- * @param {Authenticator.Listener=} opt_listener An optional listener for
- * authentication events.
* @constructor
- * @extends {cr.EventTarget}
*/
- function Authenticator(webview, opt_listener) {
+ function Authenticator(webview) {
this.webview_ = typeof webview == 'string' ? $(webview) : webview;
assert(this.webview_);
- this.listener_ = opt_listener || null;
-
this.email_ = null;
this.password_ = null;
+ this.gaiaId_ = null,
this.sessionIndex_ = null;
this.chooseWhatToSync_ = false;
this.skipForNow_ = false;
- this.authFlow_ = AuthFlow.DEFAULT;
+ this.authFlow = AuthFlow.DEFAULT;
+ this.authDomain = '';
this.loaded_ = false;
this.idpOrigin_ = null;
this.continueUrl_ = null;
this.continueUrlWithoutParams_ = null;
this.initialFrameUrl_ = null;
this.reloadUrl_ = null;
+ this.trusted_ = true;
+ this.oauth_code_ = null;
+
+ this.useEafe_ = false;
+ this.clientId_ = null;
+
+ this.samlHandler_ = new cr.login.SamlHandler(this.webview_);
+ this.confirmPasswordCallback = null;
+ this.noPasswordCallback = null;
+ this.insecureContentBlockedCallback = null;
+ this.samlApiUsedCallback = null;
+ this.missingGaiaInfoCallback = null;
+ this.needPassword = true;
+ this.samlHandler_.addEventListener(
+ 'insecureContentBlocked',
+ this.onInsecureContentBlocked_.bind(this));
+ this.samlHandler_.addEventListener(
+ 'authPageLoaded',
+ this.onAuthPageLoaded_.bind(this));
+
+ this.webview_.addEventListener('droplink', this.onDropLink_.bind(this));
+ this.webview_.addEventListener(
+ 'newwindow', this.onNewWindow_.bind(this));
+ this.webview_.addEventListener(
+ 'contentload', this.onContentLoad_.bind(this));
+ this.webview_.addEventListener(
+ 'loadabort', this.onLoadAbort_.bind(this));
+ this.webview_.addEventListener(
+ 'loadstop', this.onLoadStop_.bind(this));
+ this.webview_.addEventListener(
+ 'loadcommit', this.onLoadCommit_.bind(this));
+ this.webview_.request.onCompleted.addListener(
+ this.onRequestCompleted_.bind(this),
+ {urls: ['<all_urls>'], types: ['main_frame']},
+ ['responseHeaders']);
+ this.webview_.request.onHeadersReceived.addListener(
+ this.onHeadersReceived_.bind(this),
+ {urls: ['<all_urls>'], types: ['main_frame', 'xmlhttprequest']},
+ ['responseHeaders']);
+ window.addEventListener(
+ 'message', this.onMessageFromWebview_.bind(this), false);
+ window.addEventListener(
+ 'focus', this.onFocus_.bind(this), false);
+ window.addEventListener(
+ 'popstate', this.onPopState_.bind(this), false);
}
- // TODO(guohui,xiyuan): no need to inherit EventTarget once we deprecate the
- // old event-based signin flow.
Authenticator.prototype = Object.create(cr.EventTarget.prototype);
/**
- * An interface for receiving notifications upon authentication events.
- * @interface
- */
- Authenticator.Listener = function() {};
-
- /**
- * Invoked when authentication UI is ready.
- */
- Authenticator.Listener.prototype.onReady = function(e) {};
-
- /**
- * Invoked when authentication is completed successfully with credential data.
- * A credential data object looks like this:
- * <pre>
- * {@code
- * {
- * email: 'xx@gmail.com',
- * password: 'xxxx', // May be null or empty.
- * usingSAML: false,
- * chooseWhatToSync: false,
- * skipForNow: false,
- * sessionIndex: '0'
- * }
- * }
- * </pre>
- * @param {Object} credentials A credential data object.
- */
- Authenticator.Listener.prototype.onSuccess = function(credentials) {};
-
- /**
- * Invoked when the requested URL does not fit the container.
- * @param {string} url Request URL.
- */
- Authenticator.Listener.prototype.onResize = function(url) {};
-
- /**
- * Invoked when a new window event is fired.
- * @param {Event} e Event object.
+ * Reinitializes authentication parameters so that a failed login attempt
+ * would not result in an infinite loop.
*/
- Authenticator.Listener.prototype.onNewWindow = function(e) {};
+ Authenticator.prototype.clearCredentials_ = function() {
+ this.email_ = null;
+ this.gaiaId_ = null;
+ this.password_ = null;
+ this.oauth_code_ = null;
+ this.chooseWhatToSync_ = false;
+ this.skipForNow_ = false;
+ this.sessionIndex_ = null;
+ this.trusted_ = true;
+ this.authFlow = AuthFlow.DEFAULT;
+ this.samlHandler_.reset();
+ };
/**
* Loads the authenticator component with the given parameters.
@@ -126,52 +185,84 @@ cr.define('cr.login', function() {
* @param {Object} data Parameters for the authorization flow.
*/
Authenticator.prototype.load = function(authMode, data) {
+ this.authMode = authMode;
+ this.clearCredentials_();
+ this.loaded_ = false;
this.idpOrigin_ = data.gaiaUrl || IDP_ORIGIN;
this.continueUrl_ = data.continueUrl || CONTINUE_URL;
this.continueUrlWithoutParams_ =
this.continueUrl_.substring(0, this.continueUrl_.indexOf('?')) ||
this.continueUrl_;
this.isConstrainedWindow_ = data.constrained == '1';
+ this.isNewGaiaFlowChromeOS = data.isNewGaiaFlowChromeOS;
+ this.useEafe_ = data.useEafe || false;
+ this.clientId_ = data.clientId;
this.initialFrameUrl_ = this.constructInitialFrameUrl_(data);
this.reloadUrl_ = data.frameUrl || this.initialFrameUrl_;
- this.authFlow_ = AuthFlow.DEFAULT;
+ // Don't block insecure content for desktop flow because it lands on
+ // http. Otherwise, block insecure content as long as gaia is https.
+ this.samlHandler_.blockInsecureContent = authMode != AuthMode.DESKTOP &&
+ this.idpOrigin_.indexOf('https://') == 0;
+ this.needPassword = !('needPassword' in data) || data.needPassword;
+
+ if (this.isNewGaiaFlowChromeOS) {
+ this.webview_.contextMenus.onShow.addListener(function(e) {
+ e.preventDefault();
+ });
+ }
this.webview_.src = this.reloadUrl_;
- this.webview_.addEventListener(
- 'newwindow', this.onNewWindow_.bind(this));
- this.webview_.request.onCompleted.addListener(
- this.onRequestCompleted_.bind(this),
- {urls: ['*://*/*', this.continueUrlWithoutParams_ + '*'],
- types: ['main_frame']},
- ['responseHeaders']);
- this.webview_.request.onHeadersReceived.addListener(
- this.onHeadersReceived_.bind(this),
- {urls: [this.idpOrigin_ + '*'], types: ['main_frame']},
- ['responseHeaders']);
- window.addEventListener(
- 'message', this.onMessage_.bind(this), false);
};
/**
* Reloads the authenticator component.
*/
Authenticator.prototype.reload = function() {
+ this.clearCredentials_();
+ this.loaded_ = false;
this.webview_.src = this.reloadUrl_;
- this.authFlow_ = AuthFlow.DEFAULT;
};
Authenticator.prototype.constructInitialFrameUrl_ = function(data) {
- var url = this.idpOrigin_ + (data.gaiaPath || IDP_PATH);
-
- url = appendParam(url, 'continue', this.continueUrl_);
- url = appendParam(url, 'service', data.service);
+ var path = data.gaiaPath;
+ if (!path && this.isNewGaiaFlowChromeOS)
+ path = EMBEDDED_SETUP_CHROMEOS_ENDPOINT;
+ if (!path)
+ path = IDP_PATH;
+ var url = this.idpOrigin_ + path;
+
+ if (this.isNewGaiaFlowChromeOS) {
+ if (data.chromeType)
+ url = appendParam(url, 'chrometype', data.chromeType);
+ if (data.clientId)
+ url = appendParam(url, 'client_id', data.clientId);
+ if (data.enterpriseDomain)
+ url = appendParam(url, 'manageddomain', data.enterpriseDomain);
+ if (data.clientVersion)
+ url = appendParam(url, 'client_version', data.clientVersion);
+ if (data.platformVersion)
+ url = appendParam(url, 'platform_version', data.platformVersion);
+ if (data.releaseChannel)
+ url = appendParam(url, 'release_channel', data.releaseChannel);
+ if (data.endpointGen)
+ url = appendParam(url, 'endpoint_gen', data.endpointGen);
+ } else {
+ url = appendParam(url, 'continue', this.continueUrl_);
+ url = appendParam(url, 'service', data.service || SERVICE_ID);
+ }
if (data.hl)
url = appendParam(url, 'hl', data.hl);
+ if (data.gaiaId)
+ url = appendParam(url, 'user_id', data.gaiaId);
if (data.email)
url = appendParam(url, 'Email', data.email);
if (this.isConstrainedWindow_)
url = appendParam(url, 'source', CONSTRAINED_FLOW_SOURCE);
+ if (data.flow)
+ url = appendParam(url, 'flow', data.flow);
+ if (data.emailDomain)
+ url = appendParam(url, 'emaildomain', data.emailDomain);
return url;
};
@@ -181,14 +272,18 @@ cr.define('cr.login', function() {
*/
Authenticator.prototype.onRequestCompleted_ = function(details) {
var currentUrl = details.url;
+
if (currentUrl.lastIndexOf(this.continueUrlWithoutParams_, 0) == 0) {
- if (currentUrl.indexOf('ntp=1') >= 0) {
+ if (currentUrl.indexOf('ntp=1') >= 0)
this.skipForNow_ = true;
- }
- this.onAuthCompleted_();
+
+ this.maybeCompleteAuth_();
return;
}
+ if (currentUrl.indexOf('https') != 0)
+ this.trusted_ = false;
+
if (this.isConstrainedWindow_) {
var isEmbeddedPage = false;
if (this.idpOrigin_ && currentUrl.lastIndexOf(this.idpOrigin_) == 0) {
@@ -200,22 +295,47 @@ cr.define('cr.login', function() {
}
}
}
- if (!isEmbeddedPage && this.listener_) {
- this.listener_.onResize(currentUrl);
+ if (!isEmbeddedPage) {
+ this.dispatchEvent(new CustomEvent('resize', {detail: currentUrl}));
return;
}
}
- if (currentUrl.lastIndexOf(this.idpOrigin_) == 0) {
- this.webview_.contentWindow.postMessage({}, currentUrl);
- }
+ this.updateHistoryState_(currentUrl);
+ };
- if (!this.loaded_) {
- this.loaded_ = true;
- if (this.listener_) {
- this.listener_.onReady();
- }
- }
+ /**
+ * Manually updates the history. Invoked upon completion of a webview
+ * navigation.
+ * @param {string} url Request URL.
+ * @private
+ */
+ Authenticator.prototype.updateHistoryState_ = function(url) {
+ if (history.state && history.state.url != url)
+ history.pushState({url: url}, '');
+ else
+ history.replaceState({url: url}, '');
+ };
+
+ /**
+ * Invoked when the sign-in page takes focus.
+ * @param {object} e The focus event being triggered.
+ * @private
+ */
+ Authenticator.prototype.onFocus_ = function(e) {
+ if (this.authMode == AuthMode.DESKTOP)
+ this.webview_.focus();
+ };
+
+ /**
+ * Invoked when the history state is changed.
+ * @param {object} e The popstate event being triggered.
+ * @private
+ */
+ Authenticator.prototype.onPopState_ = function(e) {
+ var state = e.state;
+ if (state && state.url)
+ this.webview_.src = state.url;
};
/**
@@ -226,6 +346,10 @@ cr.define('cr.login', function() {
* @private
*/
Authenticator.prototype.onHeadersReceived_ = function(details) {
+ var currentUrl = details.url;
+ if (currentUrl.lastIndexOf(this.idpOrigin_, 0) != 0)
+ return;
+
var headers = details.responseHeaders;
for (var i = 0; headers && i < headers.length; ++i) {
var header = headers[i];
@@ -238,58 +362,220 @@ cr.define('cr.login', function() {
signinDetails[pair[0].trim()] = pair[1].trim();
});
// Removes "" around.
- var email = signinDetails['email'].slice(1, -1);
- if (this.email_ != email) {
- this.email_ = email;
- // Clears the scraped password if the email has changed.
- this.password_ = null;
- }
+ this.email_ = signinDetails['email'].slice(1, -1);
+ this.gaiaId_ = signinDetails['obfuscatedid'].slice(1, -1);
this.sessionIndex_ = signinDetails['sessionindex'];
- } else if (headerName == SAML_HEADER) {
- this.authFlow_ = AuthFlow.SAML;
+ } else if (headerName == LOCATION_HEADER) {
+ // If the "choose what to sync" checkbox was clicked, then the continue
+ // URL will contain a source=3 field.
+ var location = decodeURIComponent(header.value);
+ this.chooseWhatToSync_ = !!location.match(/(\?|&)source=3($|&)/);
+ } else if (
+ this.isNewGaiaFlowChromeOS && headerName == SET_COOKIE_HEADER) {
+ var headerValue = header.value;
+ if (headerValue.indexOf(OAUTH_CODE_COOKIE + '=', 0) == 0) {
+ this.oauth_code_ =
+ headerValue.substring(OAUTH_CODE_COOKIE.length + 1).split(';')[0];
+ }
}
}
};
/**
- * Invoked when an HTML5 message is received.
+ * Returns true if given HTML5 message is received from the webview element.
* @param {object} e Payload of the received HTML5 message.
- * @private
*/
- Authenticator.prototype.onMessage_ = function(e) {
- if (e.origin != this.idpOrigin_) {
+ Authenticator.prototype.isGaiaMessage = function(e) {
+ if (!this.isWebviewEvent_(e))
+ return false;
+
+ // The event origin does not have a trailing slash.
+ if (e.origin != this.idpOrigin_.substring(0, this.idpOrigin_.length - 1)) {
+ return false;
+ }
+
+ // EAFE passes back auth code via message.
+ if (this.useEafe_ &&
+ typeof e.data == 'object' &&
+ e.data.hasOwnProperty('authorizationCode')) {
+ assert(!this.oauth_code_);
+ this.oauth_code_ = e.data.authorizationCode;
+ this.dispatchEvent(
+ new CustomEvent('authCompleted',
+ {
+ detail: {
+ authCodeOnly: true,
+ authCode: this.oauth_code_
+ }
+ }));
return;
}
- var msg = e.data;
+ // Gaia messages must be an object with 'method' property.
+ if (typeof e.data != 'object' || !e.data.hasOwnProperty('method')) {
+ return false;
+ }
+ return true;
+ };
+
+ /**
+ * Invoked when an HTML5 message is received from the webview element.
+ * @param {object} e Payload of the received HTML5 message.
+ * @private
+ */
+ Authenticator.prototype.onMessageFromWebview_ = function(e) {
+ if (!this.isGaiaMessage(e))
+ return;
+ var msg = e.data;
if (msg.method == 'attemptLogin') {
this.email_ = msg.email;
this.password_ = msg.password;
this.chooseWhatToSync_ = msg.chooseWhatToSync;
+ // We need to dispatch only first event, before user enters password.
+ if (!msg.password) {
+ this.dispatchEvent(
+ new CustomEvent('attemptLogin', {detail: msg.email}));
+ }
+ } else if (msg.method == 'dialogShown') {
+ this.dispatchEvent(new Event('dialogShown'));
+ } else if (msg.method == 'dialogHidden') {
+ this.dispatchEvent(new Event('dialogHidden'));
+ } else if (msg.method == 'backButton') {
+ this.dispatchEvent(new CustomEvent('backButton', {detail: msg.show}));
+ } else if (msg.method == 'showView') {
+ this.dispatchEvent(new Event('showView'));
+ } else {
+ console.warn('Unrecognized message from GAIA: ' + msg.method);
}
};
/**
- * Invoked to process authentication completion.
- * @private
+ * Invoked by the hosting page to verify the Saml password.
*/
- Authenticator.prototype.onAuthCompleted_ = function() {
- if (!this.listener_) {
+ Authenticator.prototype.verifyConfirmedPassword = function(password) {
+ if (!this.samlHandler_.verifyConfirmedPassword(password)) {
+ // Invoke confirm password callback asynchronously because the
+ // verification was based on messages and caller (GaiaSigninScreen)
+ // does not expect it to be called immediately.
+ // TODO(xiyuan): Change to synchronous call when iframe based code
+ // is removed.
+ var invokeConfirmPassword = (function() {
+ this.confirmPasswordCallback(this.email_,
+ this.samlHandler_.scrapedPasswordCount);
+ }).bind(this);
+ window.setTimeout(invokeConfirmPassword, 0);
return;
}
- if (!this.email_ && !this.skipForNow_) {
+ this.password_ = password;
+ this.onAuthCompleted_();
+ };
+
+ /**
+ * Check Saml flow and start password confirmation flow if needed. Otherwise,
+ * continue with auto completion.
+ * @private
+ */
+ Authenticator.prototype.maybeCompleteAuth_ = function() {
+ var missingGaiaInfo = !this.email_ || !this.gaiaId_ || !this.sessionIndex_;
+ if (missingGaiaInfo && !this.skipForNow_) {
+ if (this.missingGaiaInfoCallback)
+ this.missingGaiaInfoCallback();
+
this.webview_.src = this.initialFrameUrl_;
return;
}
- this.listener_.onSuccess({email: this.email_,
- password: this.password_,
- usingSAML: this.authFlow_ == AuthFlow.SAML,
- chooseWhatToSync: this.chooseWhatToSync_,
- skipForNow: this.skipForNow_,
- sessionIndex: this.sessionIndex_ || ''});
+ if (this.authFlow != AuthFlow.SAML) {
+ this.onAuthCompleted_();
+ return;
+ }
+
+ if (this.samlHandler_.samlApiUsed) {
+ if (this.samlApiUsedCallback) {
+ this.samlApiUsedCallback();
+ }
+ this.password_ = this.samlHandler_.apiPasswordBytes;
+ } else if (this.samlHandler_.scrapedPasswordCount == 0) {
+ if (this.noPasswordCallback) {
+ this.noPasswordCallback(this.email_);
+ return;
+ }
+
+ // Fall through to finish the auth flow even if this.needPassword
+ // is true. This is because the flag is used as an intention to get
+ // password when it is available but not a mandatory requirement.
+ console.warn('Authenticator: No password scraped for SAML.');
+ } else if (this.needPassword) {
+ if (this.confirmPasswordCallback) {
+ // Confirm scraped password. The flow follows in
+ // verifyConfirmedPassword.
+ this.confirmPasswordCallback(this.email_,
+ this.samlHandler_.scrapedPasswordCount);
+ return;
+ }
+ }
+
+ this.onAuthCompleted_();
+ };
+
+ /**
+ * Invoked to process authentication completion.
+ * @private
+ */
+ Authenticator.prototype.onAuthCompleted_ = function() {
+ assert(this.skipForNow_ ||
+ (this.email_ && this.gaiaId_ && this.sessionIndex_));
+ this.dispatchEvent(
+ new CustomEvent('authCompleted',
+ // TODO(rsorokin): get rid of the stub values.
+ {
+ detail: {
+ email: this.email_ || '',
+ gaiaId: this.gaiaId_ || '',
+ password: this.password_ || '',
+ authCode: this.oauth_code_,
+ usingSAML: this.authFlow == AuthFlow.SAML,
+ chooseWhatToSync: this.chooseWhatToSync_,
+ skipForNow: this.skipForNow_,
+ sessionIndex: this.sessionIndex_ || '',
+ trusted: this.trusted_
+ }
+ }));
+ this.clearCredentials_();
+ };
+
+ /**
+ * Invoked when |samlHandler_| fires 'insecureContentBlocked' event.
+ * @private
+ */
+ Authenticator.prototype.onInsecureContentBlocked_ = function(e) {
+ if (this.insecureContentBlockedCallback) {
+ this.insecureContentBlockedCallback(e.detail.url);
+ } else {
+ console.error('Authenticator: Insecure content blocked.');
+ }
+ };
+
+ /**
+ * Invoked when |samlHandler_| fires 'authPageLoaded' event.
+ * @private
+ */
+ Authenticator.prototype.onAuthPageLoaded_ = function(e) {
+ if (!e.detail.isSAMLPage)
+ return;
+
+ this.authDomain = this.samlHandler_.authDomain;
+ this.authFlow = AuthFlow.SAML;
+ };
+
+ /**
+ * Invoked when a link is dropped on the webview.
+ * @private
+ */
+ Authenticator.prototype.onDropLink_ = function(e) {
+ this.dispatchEvent(new CustomEvent('dropLink', {detail: e.url}));
};
/**
@@ -297,19 +583,114 @@ cr.define('cr.login', function() {
* @private
*/
Authenticator.prototype.onNewWindow_ = function(e) {
- if (!this.listener_) {
- return;
+ this.dispatchEvent(new CustomEvent('newWindow', {detail: e}));
+ };
+
+ /**
+ * Invoked when a new document is loaded.
+ * @private
+ */
+ Authenticator.prototype.onContentLoad_ = function(e) {
+ if (this.isConstrainedWindow_) {
+ // Signin content in constrained windows should not zoom. Isolate the
+ // webview from the zooming of other webviews using the 'per-view' zoom
+ // mode, and then set it to 100% zoom.
+ this.webview_.setZoomMode('per-view');
+ this.webview_.setZoom(1);
+ }
+
+ // Posts a message to IdP pages to initiate communication.
+ var currentUrl = this.webview_.src;
+ if (currentUrl.lastIndexOf(this.idpOrigin_) == 0) {
+ var msg = {
+ 'method': 'handshake',
+ };
+
+ this.webview_.contentWindow.postMessage(msg, currentUrl);
+ }
+ };
+
+ /**
+ * Invoked when the webview fails loading a page.
+ * @private
+ */
+ Authenticator.prototype.onLoadAbort_ = function(e) {
+ this.dispatchEvent(new CustomEvent('loadAbort',
+ {detail: {error: e.reason,
+ src: this.webview_.src}}));
+ };
+
+ /**
+ * Invoked when the webview finishes loading a page.
+ * @private
+ */
+ Authenticator.prototype.onLoadStop_ = function(e) {
+ if (!this.loaded_) {
+ this.loaded_ = true;
+ this.dispatchEvent(new Event('ready'));
+ // Focus webview after dispatching event when webview is already visible.
+ this.webview_.focus();
+ }
+
+ // Sends client id to EAFE on every loadstop after a small timeout. This is
+ // needed because EAFE sits behind SSO and initialize asynchrounouly
+ // and we don't know for sure when it is loaded and ready to listen
+ // for message. The postMessage is guarded by EAFE's origin.
+ if (this.useEafe_) {
+ // An arbitrary small timeout for delivering the initial message.
+ var EAFE_INITIAL_MESSAGE_DELAY_IN_MS = 500;
+ window.setTimeout((function() {
+ var msg = {
+ 'clientId': this.clientId_
+ };
+ this.webview_.contentWindow.postMessage(msg, this.idpOrigin_);
+ }).bind(this), EAFE_INITIAL_MESSAGE_DELAY_IN_MS);
}
+ };
- this.listener_.onNewWindow(e);
+ /**
+ * Invoked when the webview navigates withing the current document.
+ * @private
+ */
+ Authenticator.prototype.onLoadCommit_ = function(e) {
+ if (this.oauth_code_) {
+ this.skipForNow_ = true;
+ this.maybeCompleteAuth_();
+ }
};
+ /**
+ * Returns |true| if event |e| was sent from the hosted webview.
+ * @private
+ */
+ Authenticator.prototype.isWebviewEvent_ = function(e) {
+ // Note: <webview> prints error message to console if |contentWindow| is not
+ // defined.
+ // TODO(dzhioev): remove the message. http://crbug.com/469522
+ var webviewWindow = this.webview_.contentWindow;
+ return !!webviewWindow && webviewWindow === e.source;
+ };
+
+ /**
+ * The current auth flow of the hosted auth page.
+ * @type {AuthFlow}
+ */
+ cr.defineProperty(Authenticator, 'authFlow');
+
+ /**
+ * The domain name of the current auth page.
+ * @type {string}
+ */
+ cr.defineProperty(Authenticator, 'authDomain');
+
Authenticator.AuthFlow = AuthFlow;
Authenticator.AuthMode = AuthMode;
+ Authenticator.SUPPORTED_PARAMS = SUPPORTED_PARAMS;
return {
// TODO(guohui, xiyuan): Rename GaiaAuthHost to Authenticator once the old
// iframe-based flow is deprecated.
- GaiaAuthHost: Authenticator
+ GaiaAuthHost: Authenticator,
+ Authenticator: Authenticator
};
});
diff --git a/chromium/chrome/browser/resources/gaia_auth_host/gaia_auth_host.js b/chromium/chrome/browser/resources/gaia_auth_host/gaia_auth_host.js
index cf870cb53ba..e568fc7d81e 100644
--- a/chromium/chrome/browser/resources/gaia_auth_host/gaia_auth_host.js
+++ b/chromium/chrome/browser/resources/gaia_auth_host/gaia_auth_host.js
@@ -43,7 +43,7 @@ cr.define('cr.login', function() {
/**
* Supported params of auth extension. For a complete list, check out the
* auth extension's main.js.
- * @type {!Array.<string>}
+ * @type {!Array<string>}
* @const
*/
var SUPPORTED_PARAMS = [
@@ -54,13 +54,15 @@ cr.define('cr.login', function() {
'service', // Name of Gaia service;
'continueUrl', // Continue url to use;
'frameUrl', // Initial frame URL to use. If empty defaults to gaiaUrl.
+ 'useEafe', // Whether to use EAFE.
+ 'clientId', // Chrome's client id.
'constrained' // Whether the extension is loaded in a constrained window;
];
/**
* Supported localized strings. For a complete list, check out the auth
* extension's offline.js
- * @type {!Array.<string>}
+ * @type {!Array<string>}
* @const
*/
var LOCALIZED_STRING_PARAMS = [
@@ -110,6 +112,12 @@ cr.define('cr.login', function() {
__proto__: cr.EventTarget.prototype,
/**
+ * Auth extension params
+ * @type {Object}
+ */
+ authParams_: {},
+
+ /**
* An url to use with {@code reload}.
* @type {?string}
* @private
@@ -117,12 +125,6 @@ cr.define('cr.login', function() {
reloadUrl_: null,
/**
- * The domain name of the current auth page.
- * @type {string}
- */
- authDomain: '',
-
- /**
* Invoked when authentication is completed successfully with credential
* data. A credential data object looks like this:
* <pre>
@@ -237,7 +239,7 @@ cr.define('cr.login', function() {
* invoked with a credential object.
*/
load: function(authMode, data, successCallback) {
- var params = [];
+ var params = {};
var populateParams = function(nameList, values) {
if (!values)
@@ -246,13 +248,13 @@ cr.define('cr.login', function() {
for (var i in nameList) {
var name = nameList[i];
if (values[name])
- params.push(name + '=' + encodeURIComponent(values[name]));
+ params[name] = values[name];
}
};
populateParams(SUPPORTED_PARAMS, data);
populateParams(LOCALIZED_STRING_PARAMS, data.localizedStrings);
- params.push('parentPage=' + encodeURIComponent(window.location.origin));
+ params['needPassword'] = true;
var url;
switch (authMode) {
@@ -261,17 +263,17 @@ cr.define('cr.login', function() {
break;
case AuthMode.DESKTOP:
url = AUTH_URL;
- params.push('desktopMode=1');
+ params['desktopMode'] = true;
break;
default:
url = AUTH_URL;
}
- url += '?' + params.join('&');
- this.frame_.src = url;
+ this.authParams_ = params;
this.reloadUrl_ = url;
this.successCallback_ = successCallback;
- this.authFlow = AuthFlow.GAIA;
+
+ this.reload();
},
/**
@@ -328,6 +330,11 @@ cr.define('cr.login', function() {
if (!this.isAuthExtMessage_(e))
return;
+ if (msg.method == 'loginUIDOMContentLoaded') {
+ this.frame_.contentWindow.postMessage(this.authParams_, AUTH_URL_BASE);
+ return;
+ }
+
if (msg.method == 'loginUILoaded') {
cr.dispatchSimpleEvent(this, 'ready');
return;
@@ -350,9 +357,21 @@ cr.define('cr.login', function() {
return;
}
+ if (msg.method == 'completeAuthenticationAuthCodeOnly') {
+ if (!msg.authCode) {
+ console.error(
+ 'GaiaAuthHost: completeAuthentication without auth code.');
+ var msg = {method: 'redirectToSignin'};
+ this.frame_.contentWindow.postMessage(msg, AUTH_URL_BASE);
+ return;
+ }
+ this.onAuthSuccess_({authCodeOnly: true, authCode: msg.authCode});
+ return;
+ }
+
if (msg.method == 'confirmPassword') {
if (this.confirmPasswordCallback_)
- this.confirmPasswordCallback_(msg.passwordCount);
+ this.confirmPasswordCallback_(msg.email, msg.passwordCount);
else
console.error('GaiaAuthHost: Invalid confirmPasswordCallback_.');
return;
@@ -372,6 +391,11 @@ cr.define('cr.login', function() {
return;
}
+ if (msg.method == 'resetAuthFlow') {
+ this.authFlow = AuthFlow.GAIA;
+ return;
+ }
+
if (msg.method == 'insecureContentBlocked') {
if (this.insecureContentBlockedCallback_) {
this.insecureContentBlockedCallback_(msg.url);
@@ -410,6 +434,12 @@ cr.define('cr.login', function() {
};
/**
+ * The domain name of the current auth page.
+ * @type {string}
+ */
+ cr.defineProperty(GaiaAuthHost, 'authDomain');
+
+ /**
* The current auth flow of the hosted gaia_auth extension.
* @type {AuthFlow}
*/
diff --git a/chromium/chrome/browser/resources/gaia_auth_host/post_message_channel.js b/chromium/chrome/browser/resources/gaia_auth_host/post_message_channel.js
new file mode 100644
index 00000000000..b63f93b2039
--- /dev/null
+++ b/chromium/chrome/browser/resources/gaia_auth_host/post_message_channel.js
@@ -0,0 +1,372 @@
+// Copyright 2015 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
+ * Provides a HTML5 postMessage channel to the injected JS to talk back
+ * to Authenticator.
+ */
+'use strict';
+
+<include src="../gaia_auth/channel.js">
+
+var PostMessageChannel = (function() {
+ /**
+ * Allowed origins of the hosting page.
+ * @type {Array.<string>}
+ */
+ var ALLOWED_ORIGINS = [
+ 'chrome://oobe',
+ 'chrome://chrome-signin'
+ ];
+
+ /** @const */
+ var PORT_MESSAGE = 'post-message-port-message';
+
+ /** @const */
+ var CHANNEL_INIT_MESSAGE = 'post-message-channel-init';
+
+ /** @const */
+ var CHANNEL_CONNECT_MESSAGE = 'post-message-channel-connect';
+
+ /**
+ * Whether the script runs in a top level window.
+ */
+ function isTopLevel() {
+ return window === window.top;
+ }
+
+ /**
+ * A simple event target.
+ */
+ function EventTarget() {
+ this.listeners_ = [];
+ }
+
+ EventTarget.prototype = {
+ /**
+ * Add an event listener.
+ */
+ addListener: function(listener) {
+ this.listeners_.push(listener);
+ },
+
+ /**
+ * Dispatches a given event to all listeners.
+ */
+ dispatch: function(e) {
+ for (var i = 0; i < this.listeners_.length; ++i) {
+ this.listeners_[i].call(undefined, e);
+ }
+ }
+ };
+
+ /**
+ * ChannelManager handles window message events by dispatching them to
+ * PostMessagePorts or forwarding to other windows (up/down the hierarchy).
+ * @constructor
+ */
+ function ChannelManager() {
+ /**
+ * Window and origin to forward message up the hierarchy. For subframes,
+ * they defaults to window.parent and any origin. For top level window,
+ * this would be set to the hosting webview on CHANNEL_INIT_MESSAGE.
+ */
+ this.upperWindow = isTopLevel() ? null : window.parent;
+ this.upperOrigin = isTopLevel() ? '' : '*';
+
+ /**
+ * Channle Id to port map.
+ * @type {Object.<number, PostMessagePort>}
+ */
+ this.channels_ = {};
+
+ /**
+ * Deferred messages to be posted to |upperWindow|.
+ * @type {Array}
+ */
+ this.deferredUpperWindowMessages_ = [];
+
+ /**
+ * Ports that depend on upperWindow and need to be setup when its available.
+ */
+ this.deferredUpperWindowPorts_ = [];
+
+ /**
+ * Whether the ChannelManager runs in daemon mode and accepts connections.
+ */
+ this.isDaemon = false;
+
+ /**
+ * Fires when ChannelManager is in listening mode and a
+ * CHANNEL_CONNECT_MESSAGE is received.
+ */
+ this.onConnect = new EventTarget();
+
+ window.addEventListener('message', this.onMessage_.bind(this));
+ }
+
+ ChannelManager.prototype = {
+ /**
+ * Gets a global unique id to use.
+ * @return {number}
+ */
+ createChannelId_: function() {
+ return (new Date()).getTime();
+ },
+
+ /**
+ * Posts data to upperWindow. Queue it if upperWindow is not available.
+ */
+ postToUpperWindow: function(data) {
+ if (this.upperWindow == null) {
+ this.deferredUpperWindowMessages_.push(data);
+ return;
+ }
+
+ this.upperWindow.postMessage(data, this.upperOrigin);
+ },
+
+ /**
+ * Creates a port and register it in |channels_|.
+ * @param {number} channelId
+ * @param {string} channelName
+ * @param {DOMWindow=} opt_targetWindow
+ * @param {string=} opt_targetOrigin
+ */
+ createPort: function(
+ channelId, channelName, opt_targetWindow, opt_targetOrigin) {
+ var port = new PostMessagePort(channelId, channelName);
+ if (opt_targetWindow)
+ port.setTarget(opt_targetWindow, opt_targetOrigin);
+ this.channels_[channelId] = port;
+ return port;
+ },
+
+ /*
+ * Returns a message forward handler for the given proxy port.
+ * @private
+ */
+ getProxyPortForwardHandler_: function(proxyPort) {
+ return function(msg) { proxyPort.postMessage(msg); };
+ },
+
+ /**
+ * Creates a forwarding porxy port.
+ * @param {number} channelId
+ * @param {string} channelName
+ * @param {!DOMWindow} targetWindow
+ * @param {!string} targetOrigin
+ */
+ createProxyPort: function(
+ channelId, channelName, targetWindow, targetOrigin) {
+ var port = this.createPort(
+ channelId, channelName, targetWindow, targetOrigin);
+ port.onMessage.addListener(this.getProxyPortForwardHandler_(port));
+ return port;
+ },
+
+ /**
+ * Creates a connecting port to the daemon and request connection.
+ * @param {string} name
+ * @return {PostMessagePort}
+ */
+ connectToDaemon: function(name) {
+ if (this.isDaemon) {
+ console.error(
+ 'Error: Connecting from the daemon page is not supported.');
+ return;
+ }
+
+ var port = this.createPort(this.createChannelId_(), name);
+ if (this.upperWindow) {
+ port.setTarget(this.upperWindow, this.upperOrigin);
+ } else {
+ this.deferredUpperWindowPorts_.push(port);
+ }
+
+ this.postToUpperWindow({
+ type: CHANNEL_CONNECT_MESSAGE,
+ channelId: port.channelId,
+ channelName: port.name
+ });
+ return port;
+ },
+
+ /**
+ * Dispatches a 'message' event to port.
+ * @private
+ */
+ dispatchMessageToPort_: function(e) {
+ var channelId = e.data.channelId;
+ var port = this.channels_[channelId];
+ if (!port) {
+ console.error('Error: Unable to dispatch message. Unknown channel.');
+ return;
+ }
+
+ port.handleWindowMessage(e);
+ },
+
+ /**
+ * Window 'message' handler.
+ */
+ onMessage_: function(e) {
+ if (typeof e.data != 'object' ||
+ !e.data.hasOwnProperty('type')) {
+ return;
+ }
+
+ if (e.data.type === PORT_MESSAGE) {
+ // Dispatch port message to ports if this is the daemon page or
+ // the message is from upperWindow. In case of null upperWindow,
+ // the message is assumed to be forwarded to upperWindow and queued.
+ if (this.isDaemon ||
+ (this.upperWindow && e.source === this.upperWindow)) {
+ this.dispatchMessageToPort_(e);
+ } else {
+ this.postToUpperWindow(e.data);
+ }
+ } else if (e.data.type === CHANNEL_CONNECT_MESSAGE) {
+ var channelId = e.data.channelId;
+ var channelName = e.data.channelName;
+
+ if (this.isDaemon) {
+ var port = this.createPort(
+ channelId, channelName, e.source, e.origin);
+ this.onConnect.dispatch(port);
+ } else {
+ this.createProxyPort(channelId, channelName, e.source, e.origin);
+ this.postToUpperWindow(e.data);
+ }
+ } else if (e.data.type === CHANNEL_INIT_MESSAGE) {
+ if (ALLOWED_ORIGINS.indexOf(e.origin) == -1)
+ return;
+
+ this.upperWindow = e.source;
+ this.upperOrigin = e.origin;
+
+ for (var i = 0; i < this.deferredUpperWindowMessages_.length; ++i) {
+ this.upperWindow.postMessage(this.deferredUpperWindowMessages_[i],
+ this.upperOrigin);
+ }
+ this.deferredUpperWindowMessages_ = [];
+
+ for (var i = 0; i < this.deferredUpperWindowPorts_.length; ++i) {
+ this.deferredUpperWindowPorts_[i].setTarget(this.upperWindow,
+ this.upperOrigin);
+ }
+ this.deferredUpperWindowPorts_ = [];
+ }
+ }
+ };
+
+ /**
+ * Singleton instance of ChannelManager.
+ * @type {ChannelManager}
+ */
+ var channelManager = new ChannelManager();
+
+ /**
+ * A HTML5 postMessage based port that provides the same port interface
+ * as the messaging API port.
+ * @param {number} channelId
+ * @param {string} name
+ */
+ function PostMessagePort(channelId, name) {
+ this.channelId = channelId;
+ this.name = name;
+ this.targetWindow = null;
+ this.targetOrigin = '';
+ this.deferredMessages_ = [];
+
+ this.onMessage = new EventTarget();
+ };
+
+ PostMessagePort.prototype = {
+ /**
+ * Sets the target window and origin.
+ * @param {DOMWindow} targetWindow
+ * @param {string} targetOrigin
+ */
+ setTarget: function(targetWindow, targetOrigin) {
+ this.targetWindow = targetWindow;
+ this.targetOrigin = targetOrigin;
+
+ for (var i = 0; i < this.deferredMessages_.length; ++i) {
+ this.postMessage(this.deferredMessages_[i]);
+ }
+ this.deferredMessages_ = [];
+ },
+
+ postMessage: function(msg) {
+ if (!this.targetWindow) {
+ this.deferredMessages_.push(msg);
+ return;
+ }
+
+ this.targetWindow.postMessage({
+ type: PORT_MESSAGE,
+ channelId: this.channelId,
+ payload: msg
+ }, this.targetOrigin);
+ },
+
+ handleWindowMessage: function(e) {
+ this.onMessage.dispatch(e.data.payload);
+ }
+ };
+
+ /**
+ * A message channel based on PostMessagePort.
+ * @extends {Channel}
+ * @constructor
+ */
+ function PostMessageChannel() {
+ };
+
+ PostMessageChannel.prototype = {
+ __proto__: Channel.prototype,
+
+ /** @override */
+ connect: function(name) {
+ this.port_ = channelManager.connectToDaemon(name);
+ this.port_.onMessage.addListener(this.onMessage_.bind(this));
+ },
+ };
+
+ /**
+ * Initialize webview content window for postMessage channel.
+ * @param {DOMWindow} webViewContentWindow Content window of the webview.
+ */
+ PostMessageChannel.init = function(webViewContentWindow) {
+ webViewContentWindow.postMessage({
+ type: CHANNEL_INIT_MESSAGE
+ }, '*');
+ };
+
+ /**
+ * Run in daemon mode and listen for incoming connections. Note that the
+ * current implementation assumes the daemon runs in the hosting page
+ * at the upper layer of the DOM tree. That is, all connect requests go
+ * up the DOM tree instead of going into sub frames.
+ * @param {function(PostMessagePort)} callback Invoked when a connection is
+ * made.
+ */
+ PostMessageChannel.runAsDaemon = function(callback) {
+ channelManager.isDaemon = true;
+
+ var onConnect = function(port) {
+ callback(port);
+ };
+ channelManager.onConnect.addListener(onConnect);
+ };
+
+ return PostMessageChannel;
+})();
+
+/** @override */
+Channel.create = function() {
+ return new PostMessageChannel();
+};
diff --git a/chromium/chrome/browser/resources/gaia_auth_host/saml_handler.js b/chromium/chrome/browser/resources/gaia_auth_host/saml_handler.js
new file mode 100644
index 00000000000..bd8603613d3
--- /dev/null
+++ b/chromium/chrome/browser/resources/gaia_auth_host/saml_handler.js
@@ -0,0 +1,453 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+<include src="post_message_channel.js">
+
+/**
+ * @fileoverview Saml support for webview based auth.
+ */
+
+cr.define('cr.login', function() {
+ 'use strict';
+
+ /**
+ * The lowest version of the credentials passing API supported.
+ * @type {number}
+ */
+ var MIN_API_VERSION_VERSION = 1;
+
+ /**
+ * The highest version of the credentials passing API supported.
+ * @type {number}
+ */
+ var MAX_API_VERSION_VERSION = 1;
+
+ /**
+ * The key types supported by the credentials passing API.
+ * @type {Array} Array of strings.
+ */
+ var API_KEY_TYPES = [
+ 'KEY_TYPE_PASSWORD_PLAIN',
+ ];
+
+ /** @const */
+ var SAML_HEADER = 'google-accounts-saml';
+
+ /**
+ * The script to inject into webview and its sub frames.
+ * @type {string}
+ */
+ var injectedJs = String.raw`
+ <include src="webview_saml_injected.js">
+ `;
+
+ /**
+ * Creates a new URL by striping all query parameters.
+ * @param {string} url The original URL.
+ * @return {string} The new URL with all query parameters stripped.
+ */
+ function stripParams(url) {
+ return url.substring(0, url.indexOf('?')) || url;
+ }
+
+ /**
+ * Extract domain name from an URL.
+ * @param {string} url An URL string.
+ * @return {string} The host name of the URL.
+ */
+ function extractDomain(url) {
+ var a = document.createElement('a');
+ a.href = url;
+ return a.hostname;
+ }
+
+ /**
+ * A handler to provide saml support for the given webview that hosts the
+ * auth IdP pages.
+ * @extends {cr.EventTarget}
+ * @param {webview} webview
+ * @constructor
+ */
+ function SamlHandler(webview) {
+ /**
+ * The webview that serves IdP pages.
+ * @type {webview}
+ */
+ this.webview_ = webview;
+
+ /**
+ * Whether a Saml IdP page is display in the webview.
+ * @type {boolean}
+ */
+ this.isSamlPage_ = false;
+
+ /**
+ * Pending Saml IdP page flag that is set when a SAML_HEADER is received
+ * and is copied to |isSamlPage_| in loadcommit.
+ * @type {boolean}
+ */
+ this.pendingIsSamlPage_ = false;
+
+ /**
+ * The last aborted top level url. It is recorded in loadabort event and
+ * used to skip injection into Chrome's error page in the following
+ * loadcommit event.
+ * @type {string}
+ */
+ this.abortedTopLevelUrl_ = null;
+
+ /**
+ * The domain of the Saml IdP.
+ * @type {string}
+ */
+ this.authDomain = '';
+
+ /**
+ * Scraped password stored in an id to password field value map.
+ * @type {Object.<string, string>}
+ * @private
+ */
+ this.passwordStore_ = {};
+
+ /**
+ * Whether Saml API is initialized.
+ * @type {boolean}
+ */
+ this.apiInitialized_ = false;
+
+ /**
+ * Saml API version to use.
+ * @type {number}
+ */
+ this.apiVersion_ = 0;
+
+ /**
+ * Saml API token received.
+ * @type {string}
+ */
+ this.apiToken_ = null;
+
+ /**
+ * Saml API password bytes.
+ * @type {string}
+ */
+ this.apiPasswordBytes_ = null;
+
+ /*
+ * Whether to abort the authentication flow and show an error messagen when
+ * content served over an unencrypted connection is detected.
+ * @type {boolean}
+ */
+ this.blockInsecureContent = false;
+
+ this.webview_.addEventListener(
+ 'contentload', this.onContentLoad_.bind(this));
+ this.webview_.addEventListener(
+ 'loadabort', this.onLoadAbort_.bind(this));
+ this.webview_.addEventListener(
+ 'loadcommit', this.onLoadCommit_.bind(this));
+
+ this.webview_.request.onBeforeRequest.addListener(
+ this.onInsecureRequest.bind(this),
+ {urls: ['http://*/*', 'file://*/*', 'ftp://*/*']},
+ ['blocking']);
+ this.webview_.request.onHeadersReceived.addListener(
+ this.onHeadersReceived_.bind(this),
+ {urls: ['<all_urls>'], types: ['main_frame', 'xmlhttprequest']},
+ ['blocking', 'responseHeaders']);
+
+ this.webview_.addContentScripts([{
+ name: 'samlInjected',
+ matches: ['http://*/*', 'https://*/*'],
+ js: {
+ code: injectedJs
+ },
+ all_frames: true,
+ run_at: 'document_start'
+ }]);
+
+ PostMessageChannel.runAsDaemon(this.onConnected_.bind(this));
+ }
+
+ SamlHandler.prototype = {
+ __proto__: cr.EventTarget.prototype,
+
+ /**
+ * Whether Saml API is used during auth.
+ * @return {boolean}
+ */
+ get samlApiUsed() {
+ return !!this.apiPasswordBytes_;
+ },
+
+ /**
+ * Returns the Saml API password bytes.
+ * @return {string}
+ */
+ get apiPasswordBytes() {
+ return this.apiPasswordBytes_;
+ },
+
+ /**
+ * Returns the number of scraped passwords.
+ * @return {number}
+ */
+ get scrapedPasswordCount() {
+ return this.getConsolidatedScrapedPasswords_().length;
+ },
+
+ /**
+ * Gets the de-duped scraped passwords.
+ * @return {Array.<string>}
+ * @private
+ */
+ getConsolidatedScrapedPasswords_: function() {
+ var passwords = {};
+ for (var property in this.passwordStore_) {
+ passwords[this.passwordStore_[property]] = true;
+ }
+ return Object.keys(passwords);
+ },
+
+ /**
+ * Resets all auth states
+ */
+ reset: function() {
+ this.isSamlPage_ = false;
+ this.pendingIsSamlPage_ = false;
+ this.passwordStore_ = {};
+
+ this.apiInitialized_ = false;
+ this.apiVersion_ = 0;
+ this.apiToken_ = null;
+ this.apiPasswordBytes_ = null;
+ },
+
+ /**
+ * Check whether the given |password| is in the scraped passwords.
+ * @return {boolean} True if the |password| is found.
+ */
+ verifyConfirmedPassword: function(password) {
+ return this.getConsolidatedScrapedPasswords_().indexOf(password) >= 0;
+ },
+
+ /**
+ * Invoked on the webview's contentload event.
+ * @private
+ */
+ onContentLoad_: function(e) {
+ PostMessageChannel.init(this.webview_.contentWindow);
+ },
+
+ /**
+ * Invoked on the webview's loadabort event.
+ * @private
+ */
+ onLoadAbort_: function(e) {
+ if (e.isTopLevel)
+ this.abortedTopLevelUrl_ = e.url;
+ },
+
+ /**
+ * Invoked on the webview's loadcommit event for both main and sub frames.
+ * @private
+ */
+ onLoadCommit_: function(e) {
+ // Skip this loadcommit if the top level load is just aborted.
+ if (e.isTopLevel && e.url === this.abortedTopLevelUrl_) {
+ this.abortedTopLevelUrl_ = null;
+ return;
+ }
+
+ // Skip for none http/https url.
+ if (e.url.indexOf('https://') != 0 &&
+ e.url.indexOf('http://') != 0) {
+ return;
+ }
+
+ this.isSamlPage_ = this.pendingIsSamlPage_;
+ },
+
+ /**
+ * Handler for webRequest.onBeforeRequest, invoked when content served over
+ * an unencrypted connection is detected. Determines whether the request
+ * should be blocked and if so, signals that an error message needs to be
+ * shown.
+ * @param {Object} details
+ * @return {!Object} Decision whether to block the request.
+ */
+ onInsecureRequest: function(details) {
+ if (!this.blockInsecureContent)
+ return {};
+ var strippedUrl = stripParams(details.url);
+ this.dispatchEvent(new CustomEvent('insecureContentBlocked',
+ {detail: {url: strippedUrl}}));
+ return {cancel: true};
+ },
+
+ /**
+ * Invoked when headers are received for the main frame.
+ * @private
+ */
+ onHeadersReceived_: function(details) {
+ var headers = details.responseHeaders;
+
+ // Check whether GAIA headers indicating the start or end of a SAML
+ // redirect are present. If so, synthesize cookies to mark these points.
+ for (var i = 0; headers && i < headers.length; ++i) {
+ var header = headers[i];
+ var headerName = header.name.toLowerCase();
+
+ if (headerName == SAML_HEADER) {
+ var action = header.value.toLowerCase();
+ if (action == 'start') {
+ this.pendingIsSamlPage_ = true;
+
+ // GAIA is redirecting to a SAML IdP. Any cookies contained in the
+ // current |headers| were set by GAIA. Any cookies set in future
+ // requests will be coming from the IdP. Append a cookie to the
+ // current |headers| that marks the point at which the redirect
+ // occurred.
+ headers.push({name: 'Set-Cookie',
+ value: 'google-accounts-saml-start=now'});
+ return {responseHeaders: headers};
+ } else if (action == 'end') {
+ this.pendingIsSamlPage_ = false;
+
+ // The SAML IdP has redirected back to GAIA. Add a cookie that marks
+ // the point at which the redirect occurred occurred. It is
+ // important that this cookie be prepended to the current |headers|
+ // because any cookies contained in the |headers| were already set
+ // by GAIA, not the IdP. Due to limitations in the webRequest API,
+ // it is not trivial to prepend a cookie:
+ //
+ // The webRequest API only allows for deleting and appending
+ // headers. To prepend a cookie (C), three steps are needed:
+ // 1) Delete any headers that set cookies (e.g., A, B).
+ // 2) Append a header which sets the cookie (C).
+ // 3) Append the original headers (A, B).
+ //
+ // Due to a further limitation of the webRequest API, it is not
+ // possible to delete a header in step 1) and append an identical
+ // header in step 3). To work around this, a trailing semicolon is
+ // added to each header before appending it. Trailing semicolons are
+ // ignored by Chrome in cookie headers, causing the modified headers
+ // to actually set the original cookies.
+ var otherHeaders = [];
+ var cookies = [{name: 'Set-Cookie',
+ value: 'google-accounts-saml-end=now'}];
+ for (var j = 0; j < headers.length; ++j) {
+ if (headers[j].name.toLowerCase().indexOf('set-cookie') == 0) {
+ var header = headers[j];
+ header.value += ';';
+ cookies.push(header);
+ } else {
+ otherHeaders.push(headers[j]);
+ }
+ }
+ return {responseHeaders: otherHeaders.concat(cookies)};
+ }
+ }
+ }
+
+ return {};
+ },
+
+ /**
+ * Invoked when the injected JS makes a connection.
+ */
+ onConnected_: function(port) {
+ if (port.targetWindow != this.webview_.contentWindow)
+ return;
+
+ var channel = Channel.create();
+ channel.init(port);
+
+ channel.registerMessage(
+ 'apiCall', this.onAPICall_.bind(this, channel));
+ channel.registerMessage(
+ 'updatePassword', this.onUpdatePassword_.bind(this, channel));
+ channel.registerMessage(
+ 'pageLoaded', this.onPageLoaded_.bind(this, channel));
+ channel.registerMessage(
+ 'getSAMLFlag', this.onGetSAMLFlag_.bind(this, channel));
+ },
+
+ sendInitializationSuccess_: function(channel) {
+ channel.send({name: 'apiResponse', response: {
+ result: 'initialized',
+ version: this.apiVersion_,
+ keyTypes: API_KEY_TYPES
+ }});
+ },
+
+ sendInitializationFailure_: function(channel) {
+ channel.send({
+ name: 'apiResponse',
+ response: {result: 'initialization_failed'}
+ });
+ },
+
+ /**
+ * Handlers for channel messages.
+ * @param {Channel} channel A channel to send back response.
+ * @param {Object} msg Received message.
+ * @private
+ */
+ onAPICall_: function(channel, msg) {
+ var call = msg.call;
+ if (call.method == 'initialize') {
+ if (!Number.isInteger(call.requestedVersion) ||
+ call.requestedVersion < MIN_API_VERSION_VERSION) {
+ this.sendInitializationFailure_(channel);
+ return;
+ }
+
+ this.apiVersion_ = Math.min(call.requestedVersion,
+ MAX_API_VERSION_VERSION);
+ this.apiInitialized_ = true;
+ this.sendInitializationSuccess_(channel);
+ return;
+ }
+
+ if (call.method == 'add') {
+ if (API_KEY_TYPES.indexOf(call.keyType) == -1) {
+ console.error('SamlHandler.onAPICall_: unsupported key type');
+ return;
+ }
+ // Not setting |email_| and |gaiaId_| because this API call will
+ // eventually be followed by onCompleteLogin_() which does set it.
+ this.apiToken_ = call.token;
+ this.apiPasswordBytes_ = call.passwordBytes;
+ } else if (call.method == 'confirm') {
+ if (call.token != this.apiToken_)
+ console.error('SamlHandler.onAPICall_: token mismatch');
+ } else {
+ console.error('SamlHandler.onAPICall_: unknown message');
+ }
+ },
+
+ onUpdatePassword_: function(channel, msg) {
+ if (this.isSamlPage_)
+ this.passwordStore_[msg.id] = msg.password;
+ },
+
+ onPageLoaded_: function(channel, msg) {
+ this.authDomain = extractDomain(msg.url);
+ this.dispatchEvent(new CustomEvent(
+ 'authPageLoaded',
+ {detail: {url: url,
+ isSAMLPage: this.isSamlPage_,
+ domain: this.authDomain}}));
+ },
+
+ onGetSAMLFlag_: function(channel, msg) {
+ return this.isSamlPage_;
+ },
+ };
+
+ return {
+ SamlHandler: SamlHandler
+ };
+});
diff --git a/chromium/chrome/browser/resources/gaia_auth_host/webview_saml_injected.js b/chromium/chrome/browser/resources/gaia_auth_host/webview_saml_injected.js
new file mode 100644
index 00000000000..84dcb2a51c3
--- /dev/null
+++ b/chromium/chrome/browser/resources/gaia_auth_host/webview_saml_injected.js
@@ -0,0 +1,6 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+<include src="post_message_channel.js">
+<include src="../gaia_auth/saml_injected.js">
diff --git a/chromium/chrome/browser/resources/gcm_internals.html b/chromium/chrome/browser/resources/gcm_internals.html
index d2dd1af0e43..48fb41b28ce 100644
--- a/chromium/chrome/browser/resources/gcm_internals.html
+++ b/chromium/chrome/browser/resources/gcm_internals.html
@@ -1,8 +1,9 @@
-<!DOCTYPE HTML>
-<html i18n-values="dir:textdirection">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
<head>
<meta charset="utf-8">
<title>GCM Internals</title>
+ <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="gcm_internals.css">
<script src="chrome://resources/js/cr.js"></script>
<script src="chrome://resources/js/load_time_data.js"></script>
@@ -11,7 +12,7 @@
<script src="strings.js"></script>
<script src="gcm_internals.js"></script>
</head>
-<body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<body>
<h1>GCM Internals</h1>
<div class="flexbar">
<button id="refresh">Refresh</button>
@@ -45,13 +46,6 @@
</tr>
<tr>
<td>
- Signed In Username
- </td>
- <td id="signed-in-username">
- </td>
- </tr>
- <tr>
- <td>
GCM Client Created
</td>
<td id="gcm-client-created">
@@ -175,6 +169,6 @@
</tbody>
</table>
-<script src="chrome://resources/js/i18n_template2.js"></script>
+<script src="chrome://resources/js/i18n_template.js"></script>
</body>
-</html> \ No newline at end of file
+</html>
diff --git a/chromium/chrome/browser/resources/gcm_internals.js b/chromium/chrome/browser/resources/gcm_internals.js
index a08ff83e2ce..2bddedf531d 100644
--- a/chromium/chrome/browser/resources/gcm_internals.js
+++ b/chromium/chrome/browser/resources/gcm_internals.js
@@ -30,7 +30,6 @@ cr.define('gcmInternals', function() {
setIfExists(info, 'androidId', 'android-id');
setIfExists(info, 'profileServiceCreated', 'profile-service-created');
setIfExists(info, 'gcmEnabled', 'gcm-enabled');
- setIfExists(info, 'signedInUserName', 'signed-in-username');
setIfExists(info, 'gcmClientCreated', 'gcm-client-created');
setIfExists(info, 'gcmClientState', 'gcm-client-state');
setIfExists(info, 'connectionClientCreated', 'connection-client-created');
diff --git a/chromium/chrome/browser/resources/gesture_config.css b/chromium/chrome/browser/resources/gesture_config.css
index 5b70596c77b..71030769fd3 100644
--- a/chromium/chrome/browser/resources/gesture_config.css
+++ b/chromium/chrome/browser/resources/gesture_config.css
@@ -3,7 +3,6 @@
* found in the LICENSE file. */
body {
- font-family: 'Noto Sans UI', sans-serif;
font-size: 12px;
}
diff --git a/chromium/chrome/browser/resources/gesture_config.html b/chromium/chrome/browser/resources/gesture_config.html
index c478bb0212d..4a2d575a0f9 100644
--- a/chromium/chrome/browser/resources/gesture_config.html
+++ b/chromium/chrome/browser/resources/gesture_config.html
@@ -1,4 +1,4 @@
-<!DOCTYPE HTML>
+<!doctype html>
<html>
<head>
<meta charset="utf-8">
diff --git a/chromium/chrome/browser/resources/get_salient_image_url.js b/chromium/chrome/browser/resources/get_salient_image_url.js
new file mode 100644
index 00000000000..cb3020e6d5d
--- /dev/null
+++ b/chromium/chrome/browser/resources/get_salient_image_url.js
@@ -0,0 +1,67 @@
+// Copyright 2015 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.
+
+/*
+ * Looks on the page to find the image that could be the most representative
+ * one.
+ */
+(function() {
+ // Extract the referrer policy from the page.
+ var referrerPolicy = 'default';
+ var metaTags = document.getElementsByTagName('meta');
+ for (var i = 0; i < metaTags.length; ++i) {
+ if (metaTags[i].name.toLowerCase() == 'referrer') {
+ referrerPolicy = metaTags[i].content.toLowerCase();
+ break;
+ }
+ }
+
+ // See what to use for JSON. Some pages use a library that overrides JSON.
+ var jsonEncoder = JSON.stringify;
+ if (!jsonEncoder)
+ jsonEncoder = JSON.encode;
+
+ // First look if there is an Open Graph Image property available.
+ var ogImage = document.querySelector('meta[property=\"og:image\"]');
+ if (ogImage) {
+ // Checks that the url in ogImage has a path that contains more than just a
+ // simple '/'.
+ var url = ogImage.content;
+ var location = document.createElement('a');
+ location.href = url;
+ if (location.pathname.length > 1) {
+ return jsonEncoder({
+ 'imageUrl': url,
+ 'referrerPolicy': referrerPolicy
+ });
+ }
+ }
+
+ // Iterates through the images on the page, find the largest one that doesn't
+ // look like a banner.
+ var maxPointSize = 0;
+ var maxImage = null;
+
+ var images = document.getElementsByTagName('img');
+ for (var i = 0; i < images.length; i++) {
+ var currentImage = images[i];
+ var aspectRatio = currentImage.width / currentImage.height;
+ if (aspectRatio >= 2.4 || aspectRatio <= 0.5)
+ continue; // Skip weirdly shaped images. Those are ads or headers.
+ var pointSize = currentImage.width * currentImage.height;
+ if (pointSize > maxPointSize) {
+ maxPointSize = pointSize;
+ maxImage = currentImage;
+ }
+ }
+
+ // Only keep images larger than 320*160.
+ if (maxPointSize <= 51200.0 || maxImage === null)
+ return '';
+
+ return jsonEncoder({
+ 'imageUrl': maxImage.src,
+ 'referrerPolicy': referrerPolicy
+ });
+})();
diff --git a/chromium/chrome/browser/resources/google_now/background.js b/chromium/chrome/browser/resources/google_now/background.js
index 5d3eaf091c5..2db2d1e6f8c 100644
--- a/chromium/chrome/browser/resources/google_now/background.js
+++ b/chromium/chrome/browser/resources/google_now/background.js
@@ -95,6 +95,17 @@ var DEFAULT_OPTIN_CHECK_PERIOD_SECONDS = 60 * 60 * 24 * 7; // 1 week
var SETTINGS_URL = 'https://support.google.com/chrome/?p=ib_google_now_welcome';
/**
+ * GCM registration URL.
+ */
+var GCM_REGISTRATION_URL =
+ 'https://android.googleapis.com/gcm/googlenotification';
+
+/**
+ * DevConsole project ID for GCM API use.
+ */
+var GCM_PROJECT_ID = '437902709571';
+
+/**
* Number of cards that need an explanatory link.
*/
var EXPLANATORY_CARDS_LINK_THRESHOLD = 4;
@@ -125,8 +136,8 @@ var ReceivedGroup;
*
* @typedef {{
* googleNowDisabled: (boolean|undefined),
- * groups: Object.<string, ReceivedGroup>,
- * notifications: Array.<ReceivedNotification>
+ * groups: Object<string, ReceivedGroup>,
+ * notifications: Array<ReceivedNotification>
* }}
*/
var ServerResponse;
@@ -139,7 +150,7 @@ var ServerResponse;
* cards update and all the times after that.
*
* @typedef {{
- * cards: Array.<ReceivedNotification>,
+ * cards: Array<ReceivedNotification>,
* cardsTimestamp: (number|undefined),
* nextPollTime: (number|undefined),
* rank: (number|undefined)
@@ -189,6 +200,9 @@ function areTasksConflicting(newTaskName, scheduledTaskName) {
var tasks = buildTaskManager(areTasksConflicting);
// Add error processing to API calls.
+wrapper.instrumentChromeApiFunction('gcm.onMessage.addListener', 0);
+wrapper.instrumentChromeApiFunction('gcm.register', 1);
+wrapper.instrumentChromeApiFunction('gcm.unregister', 0);
wrapper.instrumentChromeApiFunction('metricsPrivate.getVariationParams', 1);
wrapper.instrumentChromeApiFunction('notifications.clear', 1);
wrapper.instrumentChromeApiFunction('notifications.create', 2);
@@ -204,10 +218,9 @@ wrapper.instrumentChromeApiFunction(
wrapper.instrumentChromeApiFunction(
'notifications.onShowSettings.addListener', 0);
wrapper.instrumentChromeApiFunction('permissions.contains', 1);
-wrapper.instrumentChromeApiFunction('pushMessaging.onMessage.addListener', 0);
-wrapper.instrumentChromeApiFunction('storage.onChanged.addListener', 0);
wrapper.instrumentChromeApiFunction('runtime.onInstalled.addListener', 0);
wrapper.instrumentChromeApiFunction('runtime.onStartup.addListener', 0);
+wrapper.instrumentChromeApiFunction('storage.onChanged.addListener', 0);
wrapper.instrumentChromeApiFunction('tabs.create', 1);
var updateCardsAttempts = buildAttemptManager(
@@ -342,14 +355,14 @@ function requestFromServer(method, handlerName, opt_contentType) {
/**
* Shows the notification groups as notification cards.
- * @param {Object.<string, StoredNotificationGroup>} notificationGroups Map from
+ * @param {Object<string, StoredNotificationGroup>} notificationGroups Map from
* group name to group information.
* @param {function(ReceivedNotification)=} opt_onCardShown Optional parameter
* called when each card is shown.
* @return {Promise} A promise to show the notification groups as cards.
*/
function showNotificationGroups(notificationGroups, opt_onCardShown) {
- /** @type {Object.<ChromeNotificationId, CombinedCard>} */
+ /** @type {Object<ChromeNotificationId, CombinedCard>} */
var cards = combineCardsFromGroups(notificationGroups);
console.log('showNotificationGroups ' + JSON.stringify(cards));
@@ -365,7 +378,7 @@ function showNotificationGroups(notificationGroups, opt_onCardShown) {
cards[chromeNotificationId] = cards[chromeNotificationId] || [];
}
- /** @type {Object.<ChromeNotificationId, NotificationDataEntry>} */
+ /** @type {Object<ChromeNotificationId, NotificationDataEntry>} */
var notificationsData = {};
// Create/update/delete notifications.
@@ -402,7 +415,7 @@ function removeAllCards() {
/**
* Adds a card group into a set of combined cards.
- * @param {Object.<ChromeNotificationId, CombinedCard>} combinedCards Map from
+ * @param {Object<ChromeNotificationId, CombinedCard>} combinedCards Map from
* chromeNotificationId to a combined card.
* This is an input/output parameter.
* @param {StoredNotificationGroup} storedGroup Group to combine into the
@@ -432,7 +445,7 @@ function combineGroup(combinedCards, storedGroup) {
/**
* Calculates the soonest poll time from a map of groups as an absolute time.
- * @param {Object.<string, StoredNotificationGroup>} groups Map from group name
+ * @param {Object<string, StoredNotificationGroup>} groups Map from group name
* to group information.
* @return {number} The next poll time based off of the groups.
*/
@@ -454,7 +467,7 @@ function calculateNextPollTimeMilliseconds(groups) {
/**
* Schedules next cards poll.
- * @param {Object.<string, StoredNotificationGroup>} groups Map from group name
+ * @param {Object<string, StoredNotificationGroup>} groups Map from group name
* to group information.
*/
function scheduleNextCardsPoll(groups) {
@@ -481,13 +494,13 @@ function scheduleOptInCheckPoll() {
/**
* Combines notification groups into a set of Chrome notifications.
- * @param {Object.<string, StoredNotificationGroup>} notificationGroups Map from
+ * @param {Object<string, StoredNotificationGroup>} notificationGroups Map from
* group name to group information.
- * @return {Object.<ChromeNotificationId, CombinedCard>} Cards to show.
+ * @return {Object<ChromeNotificationId, CombinedCard>} Cards to show.
*/
function combineCardsFromGroups(notificationGroups) {
console.log('combineCardsFromGroups ' + JSON.stringify(notificationGroups));
- /** @type {Object.<ChromeNotificationId, CombinedCard>} */
+ /** @type {Object<ChromeNotificationId, CombinedCard>} */
var combinedCards = {};
for (var groupName in notificationGroups)
@@ -514,16 +527,16 @@ function processServerResponse(response) {
var receivedGroups = response.groups;
return fillFromChromeLocalStorage({
- /** @type {Object.<string, StoredNotificationGroup>} */
+ /** @type {Object<string, StoredNotificationGroup>} */
notificationGroups: {},
- /** @type {Object.<ServerNotificationId, number>} */
+ /** @type {Object<ServerNotificationId, number>} */
recentDismissals: {}
}).then(function(items) {
console.log('processServerResponse-get ' + JSON.stringify(items));
// Build a set of non-expired recent dismissals. It will be used for
// client-side filtering of cards.
- /** @type {Object.<ServerNotificationId, number>} */
+ /** @type {Object<ServerNotificationId, number>} */
var updatedRecentDismissals = {};
var now = Date.now();
for (var serverNotificationId in items.recentDismissals) {
@@ -612,7 +625,7 @@ function shouldShowExplanatoryCard() {
/**
* Requests notification cards from the server for specified groups.
- * @param {Array.<string>} groupNames Names of groups that need to be refreshed.
+ * @param {Array<string>} groupNames Names of groups that need to be refreshed.
* @return {Promise} A promise to request the specified notification groups.
*/
function requestNotificationGroupsFromServer(groupNames) {
@@ -694,7 +707,7 @@ function requestAndUpdateOptedIn() {
*/
function getGroupsToRequest() {
return fillFromChromeLocalStorage({
- /** @type {Object.<string, StoredNotificationGroup>} */
+ /** @type {Object<string, StoredNotificationGroup>} */
notificationGroups: {}
}).then(function(items) {
console.log('getGroupsToRequest-storage-get ' + JSON.stringify(items));
@@ -739,7 +752,7 @@ function requestNotificationCards() {
* Determines if an immediate retry should occur based off of the given groups.
* The NOR group is expected most often and less latency sensitive, so we will
* simply wait MAXIMUM_POLLING_PERIOD_SECONDS before trying again.
- * @param {Array.<string>} groupNames Names of groups that need to be refreshed.
+ * @param {Array<string>} groupNames Names of groups that need to be refreshed.
* @return {boolean} Whether a retry should occur.
*/
function shouldScheduleRetryFromGroupList(groupNames) {
@@ -833,9 +846,9 @@ function requestCardDismissal(
*/
function processPendingDismissals() {
return fillFromChromeLocalStorage({
- /** @type {Array.<PendingDismissal>} */
+ /** @type {Array<PendingDismissal>} */
pendingDismissals: [],
- /** @type {Object.<ServerNotificationId, number>} */
+ /** @type {Object<ServerNotificationId, number>} */
recentDismissals: {}
}).then(function(items) {
console.log(
@@ -913,7 +926,7 @@ function openUrl(url) {
*/
function onNotificationClicked(chromeNotificationId, selector) {
fillFromChromeLocalStorage({
- /** @type {Object.<ChromeNotificationId, NotificationDataEntry>} */
+ /** @type {Object<ChromeNotificationId, NotificationDataEntry>} */
notificationsData: {}
}).then(function(items) {
/** @type {(NotificationDataEntry|undefined)} */
@@ -946,11 +959,11 @@ function onNotificationClosed(chromeNotificationId, byUser) {
dismissalAttempts.start();
fillFromChromeLocalStorage({
- /** @type {Array.<PendingDismissal>} */
+ /** @type {Array<PendingDismissal>} */
pendingDismissals: [],
- /** @type {Object.<ChromeNotificationId, NotificationDataEntry>} */
+ /** @type {Object<ChromeNotificationId, NotificationDataEntry>} */
notificationsData: {},
- /** @type {Object.<string, StoredNotificationGroup>} */
+ /** @type {Object<string, StoredNotificationGroup>} */
notificationGroups: {}
}).then(function(items) {
/** @type {NotificationDataEntry} */
@@ -1015,6 +1028,8 @@ function stopPollingCards() {
*/
function initialize() {
recordEvent(GoogleNowEvent.EXTENSION_START);
+ // TODO(skare): Reenable, after signin.
+ unregisterFromGcm();
onStateChange();
}
@@ -1204,6 +1219,118 @@ function isGoogleNowEnabled() {
}
/**
+ * Ensures the extension is ready to listen for GCM messages.
+ */
+function registerForGcm() {
+ // We don't need to use the key yet, just ensure the channel is set up.
+ getGcmNotificationKey();
+}
+
+/**
+ * Returns a Promise resolving to either a cached or new GCM notification key.
+ * Rejects if registration fails.
+ * @return {Promise} A Promise that resolves to a potentially-cached GCM key.
+ */
+function getGcmNotificationKey() {
+ return fillFromChromeLocalStorage({gcmNotificationKey: undefined})
+ .then(function(items) {
+ if (items.gcmNotificationKey) {
+ console.log('Reused gcm key from storage.');
+ return Promise.resolve(items.gcmNotificationKey);
+ }
+ return requestNewGcmNotificationKey();
+ });
+}
+
+/**
+ * Returns a promise resolving to a GCM Notificaiton Key. May call
+ * chrome.gcm.register() first if required. Rejects on registration failure.
+ * @return {Promise} A Promise that resolves to a fresh GCM Notification key.
+ */
+function requestNewGcmNotificationKey() {
+ return getGcmRegistrationId().then(function(gcmId) {
+ authenticationManager.getAuthToken().then(function(token) {
+ authenticationManager.getLogin().then(function(username) {
+ return new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.responseType = 'application/json';
+ xhr.open('POST', GCM_REGISTRATION_URL, true);
+ xhr.setRequestHeader('Content-Type', 'application/json');
+ xhr.setRequestHeader('Authorization', 'Bearer ' + token);
+ xhr.setRequestHeader('project_id', GCM_PROJECT_ID);
+ var payload = {
+ 'operation': 'add',
+ 'notification_key_name': username,
+ 'registration_ids': [gcmId]
+ };
+ xhr.onloadend = function() {
+ if (xhr.status != 200) {
+ reject();
+ }
+ var obj = JSON.parse(xhr.responseText);
+ var key = obj && obj.notification_key;
+ if (!key) {
+ reject();
+ }
+ console.log('gcm notification key POST: ' + key);
+ chrome.storage.local.set({gcmNotificationKey: key});
+ resolve(key);
+ };
+ xhr.send(JSON.stringify(payload));
+ });
+ });
+ }).catch(function() {
+ // Couldn't obtain a GCM ID. Ignore and fallback to polling.
+ });
+ });
+}
+
+/**
+ * Returns a promise resolving to either a cached or new GCM registration ID.
+ * Rejects if registration fails.
+ * @return {Promise} A Promise that resolves to a GCM registration ID.
+ */
+function getGcmRegistrationId() {
+ return fillFromChromeLocalStorage({gcmRegistrationId: undefined})
+ .then(function(items) {
+ if (items.gcmRegistrationId) {
+ console.log('Reused gcm registration id from storage.');
+ return Promise.resolve(items.gcmRegistrationId);
+ }
+
+ return new Promise(function(resolve, reject) {
+ instrumented.gcm.register([GCM_PROJECT_ID], function(registrationId) {
+ console.log('gcm.register(): ' + registrationId);
+ if (registrationId) {
+ chrome.storage.local.set({gcmRegistrationId: registrationId});
+ resolve(registrationId);
+ } else {
+ reject();
+ }
+ });
+ });
+ });
+}
+
+/**
+ * Unregisters from GCM if previously registered.
+ */
+function unregisterFromGcm() {
+ fillFromChromeLocalStorage({gcmRegistrationId: undefined})
+ .then(function(items) {
+ if (items.gcmRegistrationId) {
+ console.log('Unregistering from gcm.');
+ instrumented.gcm.unregister(function() {
+ if (!chrome.runtime.lastError) {
+ chrome.storage.local.remove(
+ ['gcmNotificationKey', 'gcmRegistrationId']);
+ }
+ });
+ }
+ });
+}
+
+/**
* Polls the optin state.
* Sometimes we get the response to the opted in result too soon during
* push messaging. We'll recheck the optin state a few times before giving up.
@@ -1258,7 +1385,7 @@ instrumented.runtime.onStartup.addListener(function() {
// persistent notifications will work.
tasks.add(SHOW_ON_START_TASK_NAME, function() {
fillFromChromeLocalStorage({
- /** @type {Object.<string, StoredNotificationGroup>} */
+ /** @type {Object<string, StoredNotificationGroup>} */
notificationGroups: {}
}).then(function(items) {
console.log('onStartup-get ' + JSON.stringify(items));
@@ -1334,26 +1461,27 @@ instrumented.storage.onChanged.addListener(function(changes, areaName) {
}
});
-instrumented.pushMessaging.onMessage.addListener(function(message) {
- // message.payload will be '' when the extension first starts.
- // Each time after signing in, we'll get latest payload for all channels.
- // So, we need to poll the server only when the payload is non-empty and has
- // changed.
- console.log('pushMessaging.onMessage ' + JSON.stringify(message));
- if (message.payload.indexOf('REQUEST_CARDS') == 0) {
+instrumented.gcm.onMessage.addListener(function(message) {
+ console.log('gcm.onMessage ' + JSON.stringify(message));
+ if (!message || !message.data) {
+ return;
+ }
+
+ var payload = message.data.payload;
+ var tag = message.data.tag;
+ if (payload.indexOf('REQUEST_CARDS') == 0) {
tasks.add(ON_PUSH_MESSAGE_START_TASK_NAME, function() {
// Accept promise rejection on failure since it's safer to do nothing,
// preventing polling the server when the payload really didn't change.
fillFromChromeLocalStorage({
lastPollNowPayloads: {},
- /** @type {Object.<string, StoredNotificationGroup>} */
+ /** @type {Object<string, StoredNotificationGroup>} */
notificationGroups: {}
}, PromiseRejection.ALLOW).then(function(items) {
- if (items.lastPollNowPayloads[message.subchannelId] !=
- message.payload) {
- items.lastPollNowPayloads[message.subchannelId] = message.payload;
+ if (items.lastPollNowPayloads[tag] != payload) {
+ items.lastPollNowPayloads[tag] = payload;
- items.notificationGroups['PUSH' + message.subchannelId] = {
+ items.notificationGroups['PUSH' + tag] = {
cards: [],
nextPollTime: Date.now()
};
diff --git a/chromium/chrome/browser/resources/google_now/background_test_util.js b/chromium/chrome/browser/resources/google_now/background_test_util.js
index 52d77dafff3..7f6e4aa0d21 100644
--- a/chromium/chrome/browser/resources/google_now/background_test_util.js
+++ b/chromium/chrome/browser/resources/google_now/background_test_util.js
@@ -18,12 +18,12 @@ var buildAttemptManager = emptyMock;
var buildCardSet = emptyMock;
var instrumented = {};
+mockChromeEvent(instrumented, 'gcm.onMessage');
mockChromeEvent(instrumented, 'notifications.onButtonClicked');
mockChromeEvent(instrumented, 'notifications.onClicked');
mockChromeEvent(instrumented, 'notifications.onClosed');
mockChromeEvent(instrumented, 'notifications.onPermissionLevelChanged');
mockChromeEvent(instrumented, 'notifications.onShowSettings');
-mockChromeEvent(instrumented, 'pushMessaging.onMessage');
mockChromeEvent(instrumented, 'runtime.onInstalled');
mockChromeEvent(instrumented, 'runtime.onStartup');
mockChromeEvent(instrumented, 'storage.onChanged');
diff --git a/chromium/chrome/browser/resources/google_now/background_unittest.gtestjs b/chromium/chrome/browser/resources/google_now/background_unittest.gtestjs
index 6a6f1259b98..a9578a890bd 100644
--- a/chromium/chrome/browser/resources/google_now/background_unittest.gtestjs
+++ b/chromium/chrome/browser/resources/google_now/background_unittest.gtestjs
@@ -713,6 +713,10 @@ function expectInitialization(fixture) {
fixture.mockGlobals.stubs().recordEvent(ANYTHING);
fixture.mockGlobals.
expects(once()).recordEvent(GoogleNowEvent.EXTENSION_START);
+ fixture.mockApis.expects(once())
+ .fillFromChromeLocalStorage(eqJSON({gcmNotificationKey: undefined}))
+ .will(returnValue(Promise.resolve({gcmNotificationKey: 'gcmkey'})));
+
}
TEST_F(TEST_NAME,'Initialize_SignedOut', function() {
diff --git a/chromium/chrome/browser/resources/google_now/cards.js b/chromium/chrome/browser/resources/google_now/cards.js
index dd4e95072d0..0ecd50144d7 100644
--- a/chromium/chrome/browser/resources/google_now/cards.js
+++ b/chromium/chrome/browser/resources/google_now/cards.js
@@ -37,7 +37,7 @@ var DismissalData;
*
* @typedef {{
* messageUrl: (string|undefined),
- * buttonUrls: (Array.<string>|undefined)
+ * buttonUrls: (Array<string>|undefined)
* }}
*/
var ActionUrls;
@@ -82,7 +82,7 @@ var UncombinedNotification;
/**
* Card combined from potentially multiple groups.
*
- * @typedef {Array.<UncombinedNotification>}
+ * @typedef {Array<UncombinedNotification>}
*/
var CombinedCard;
@@ -192,7 +192,7 @@ function buildCardSet() {
* of the card.
* @param {CombinedCard} combinedCard Combined cards with
* |chromeNotificationId|.
- * @param {Object.<string, StoredNotificationGroup>} notificationGroups
+ * @param {Object<string, StoredNotificationGroup>} notificationGroups
* Map from group name to group information.
* @param {function(ReceivedNotification)=} onCardShown Optional parameter
* called when each card is shown.
@@ -283,18 +283,18 @@ function buildCardSet() {
* of the card.
* @param {NotificationDataEntry} notificationData Stored notification entry
* for this card.
- * @param {Object.<string, StoredNotificationGroup>} notificationGroups
+ * @param {Object<string, StoredNotificationGroup>} notificationGroups
* Map from group name to group information.
* @return {{
- * dismissals: Array.<DismissalData>,
+ * dismissals: Array<DismissalData>,
* notificationData: (NotificationDataEntry|undefined)
* }}
*/
function onDismissal(
chromeNotificationId, notificationData, notificationGroups) {
- /** @type {Array.<DismissalData>} */
+ /** @type {Array<DismissalData>} */
var dismissals = [];
- /** @type {Array.<UncombinedNotification>} */
+ /** @type {Array<UncombinedNotification>} */
var newCombinedCard = [];
// Determine which parts of the combined card need to be dismissed or to be
@@ -325,7 +325,7 @@ function buildCardSet() {
* Removes card information from |notificationGroups|.
* @param {ChromeNotificationId} chromeNotificationId chrome.notifications ID
* of the card.
- * @param {Object.<string, StoredNotificationGroup>} notificationGroups
+ * @param {Object<string, StoredNotificationGroup>} notificationGroups
* Map from group name to group information.
*/
function clearCardFromGroups(chromeNotificationId, notificationGroups) {
@@ -350,9 +350,9 @@ function buildCardSet() {
/** @type {ChromeNotificationId} */
var chromeNotificationId = alarm.name.substring(alarmPrefix.length);
fillFromChromeLocalStorage({
- /** @type {Object.<ChromeNotificationId, NotificationDataEntry>} */
+ /** @type {Object<ChromeNotificationId, NotificationDataEntry>} */
notificationsData: {},
- /** @type {Object.<string, StoredNotificationGroup>} */
+ /** @type {Object<string, StoredNotificationGroup>} */
notificationGroups: {}
}).then(function(items) {
console.log('cardManager.onAlarm.get ' + JSON.stringify(items));
diff --git a/chromium/chrome/browser/resources/google_now/common_test_util.js b/chromium/chrome/browser/resources/google_now/common_test_util.js
index c090c9545bb..fe5708234b6 100644
--- a/chromium/chrome/browser/resources/google_now/common_test_util.js
+++ b/chromium/chrome/browser/resources/google_now/common_test_util.js
@@ -60,7 +60,7 @@ function mockChromeEvent(topLevelContainer, eventIdentifier) {
* Gets the array of event handlers added by a mocked 'addListener' function.
* @param {string} eventIdentifier Event identifier, such as
* 'runtime.onSuspend'.
- * @return {Array.<Function>} Array of handlers.
+ * @return {Array<Function>} Array of handlers.
*/
function getMockHandlerContainer(eventIdentifier) {
var eventIdentifierParts = eventIdentifier.split('.');
diff --git a/chromium/chrome/browser/resources/google_now/manifest.json b/chromium/chrome/browser/resources/google_now/manifest.json
index 1eb6e911107..bdca32298b8 100644
--- a/chromium/chrome/browser/resources/google_now/manifest.json
+++ b/chromium/chrome/browser/resources/google_now/manifest.json
@@ -11,16 +11,17 @@
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkhqJr32OFD/bMXW4Md7jMfd7LbwHXVc6x5bBQG5U+dloofoxrICDR20yur/40mQ8O//0sS1b8srvbab1CRlSrxoNCr9T80NAkfzx0gHyVS+p1Zow+1FzLMu9PiGwwFyN80HIB7GI/dIa0wC9K/2OrrzcHEhVH96DacTtWQqjfDVtZPjT7Xwv23dgoWcpbkRC86jMJot3dmX9xnn0KzoVc9gDOHSIkBLbkkr6Sp3LGXCCM4L0DJgxdFwaLr5WBzgC3y5x0/wwPIwN4PtIaK3BhH6njlksfnKwwIJ9iRT41V4BqbWu4mszO/7VJ3HJyw2DBpIc2grU9ZRRxrV3fRQG4wIDAQAB",
"permissions": [
"alarms",
+ "gcm",
"identity",
"metricsPrivate",
"notifications",
- "pushMessaging",
"storage",
"tabs",
"webstorePrivate",
"*://*.google.com/*",
"*://*.gstatic.com/*",
"https://*.googleapis.com/chromenow/v1/*",
+ "https://*.googleapis.com/gcm/*",
"https://*.googleusercontent.com/*"
],
"optional_permissions": ["background"],
@@ -31,6 +32,9 @@
},
"oauth2": {
"auto_approve": true,
- "scopes": ["https://www.googleapis.com/auth/googlenow"]
+ "scopes": [
+ "https://www.googleapis.com/auth/gcm",
+ "https://www.googleapis.com/auth/googlenow"
+ ]
}
}
diff --git a/chromium/chrome/browser/resources/google_now/utility.js b/chromium/chrome/browser/resources/google_now/utility.js
index a02e07dc1c1..c5b3d57c1f3 100644
--- a/chromium/chrome/browser/resources/google_now/utility.js
+++ b/chromium/chrome/browser/resources/google_now/utility.js
@@ -266,7 +266,7 @@ var wrapper = (function() {
* callbacks. This is a map from unique callback id to the stack at the moment
* when the callback was wrapped. This stack identifies the callback.
* Used only for diagnostics.
- * @type {Object.<number, string>}
+ * @type {Object<number, string>}
*/
var pendingCallbacks = {};
@@ -345,7 +345,7 @@ var wrapper = (function() {
/**
* Returns an instrumented function.
- * @param {!Array.<string>} functionIdentifierParts Path to the chrome.*
+ * @param {!Array<string>} functionIdentifierParts Path to the chrome.*
* function.
* @param {string} functionName Name of the chrome API function.
* @param {number} callbackParameter Index of the callback parameter to this
@@ -515,7 +515,7 @@ function registerPromiseAdapter() {
* The indirection allows quick checks against the array and clearing the
* array without ugly splicing and copying.
* @typedef {{
- * callback: array.<Function>=
+ * callback: array<Function>=
* }}
*/
var CallbackTracker;
@@ -696,7 +696,7 @@ function buildTaskManager(areConflicting) {
/**
* Queue of scheduled tasks. The first element, if present, corresponds to the
* currently running task.
- * @type {Array.<Object.<string, function()>>}
+ * @type {Array<Object<string, function()>>}
*/
var queue = [];
@@ -982,15 +982,25 @@ function buildAuthenticationManager() {
}
/**
+ * Determines the active account's login (username).
+ * @return {Promise} A promise to determine the current account's login.
+ */
+ function getLogin() {
+ return new Promise(function(resolve) {
+ instrumented.webstorePrivate.getBrowserLogin(function(accountInfo) {
+ resolve(accountInfo.login);
+ });
+ });
+ }
+
+ /**
* Determines whether there is an account attached to the profile.
* @return {Promise} A promise to determine if there is an account attached
* to the profile.
*/
function isSignedIn() {
- return new Promise(function(resolve) {
- instrumented.webstorePrivate.getBrowserLogin(function(accountInfo) {
- resolve(!!accountInfo.login);
- });
+ return getLogin().then(function(login) {
+ return Promise.resolve(!!login);
});
}
@@ -1055,6 +1065,7 @@ function buildAuthenticationManager() {
return {
addListener: addListener,
getAuthToken: getAuthToken,
+ getLogin: getLogin,
isSignedIn: isSignedIn,
removeToken: removeToken
};
diff --git a/chromium/chrome/browser/resources/hangout_services/OWNERS b/chromium/chrome/browser/resources/hangout_services/OWNERS
index f85f4351ecc..b6caf7a0aa2 100644
--- a/chromium/chrome/browser/resources/hangout_services/OWNERS
+++ b/chromium/chrome/browser/resources/hangout_services/OWNERS
@@ -1,2 +1 @@
bemasc@chromium.org
-vrk@chromium.org
diff --git a/chromium/chrome/browser/resources/hangout_services/background.html b/chromium/chrome/browser/resources/hangout_services/background.html
index 1d0e34ac21a..681d7012f68 100644
--- a/chromium/chrome/browser/resources/hangout_services/background.html
+++ b/chromium/chrome/browser/resources/hangout_services/background.html
@@ -10,6 +10,7 @@ faster.
-->
<html>
<head>
+<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<script src="startup.js"></script>
<script src="thunk.js"></script>
</head>
diff --git a/chromium/chrome/browser/resources/hangout_services/manifest.json b/chromium/chrome/browser/resources/hangout_services/manifest.json
index e9ef7aa08c5..95d5464f5e8 100644
--- a/chromium/chrome/browser/resources/hangout_services/manifest.json
+++ b/chromium/chrome/browser/resources/hangout_services/manifest.json
@@ -7,7 +7,10 @@
"manifest_version": 2,
"externally_connectable": {
"matches": [
- "https://*.google.com/hangouts*",
+ "https://hangouts.google.com/*",
+ "https://talkgadget.google.com/*",
+ "https://*.talkgadget.google.com/*",
+ "https://plus.google.com/hangouts*",
// For tests.
"*://localhost/*"
]
diff --git a/chromium/chrome/browser/resources/hangout_services/thunk.js b/chromium/chrome/browser/resources/hangout_services/thunk.js
index bda691165f7..b07d7ea3e24 100644
--- a/chromium/chrome/browser/resources/hangout_services/thunk.js
+++ b/chromium/chrome/browser/resources/hangout_services/thunk.js
@@ -4,7 +4,14 @@
chrome.runtime.onMessageExternal.addListener(
function(message, sender, sendResponse) {
- function doSendResponse(value, error) {
+ function doSendResponse(value, errorString) {
+ var error = null;
+ if (errorString) {
+ error = {};
+ error['name'] = 'ComponentExtensonError';
+ error['message'] = errorString;
+ }
+
var errorMessage = error || chrome.extension.lastError;
sendResponse({'value': value, 'error': errorMessage});
}
@@ -25,6 +32,15 @@ chrome.runtime.onMessageExternal.addListener(
}
try {
+ var requestInfo = {};
+ if (sender.tab) {
+ requestInfo['tabId'] = sender.tab.id;
+ }
+
+ if (sender.guestProcessId) {
+ requestInfo['guestProcessId'] = sender.guestProcessId;
+ }
+
var method = message['method'];
var origin = getHost(sender.url);
if (method == 'chooseDesktopMedia') {
@@ -50,53 +66,53 @@ chrome.runtime.onMessageExternal.addListener(
} else if (method == 'logging.setMetadata') {
var metaData = message['metaData'];
chrome.webrtcLoggingPrivate.setMetaData(
- sender.tab.id, origin, metaData, doSendResponse);
+ requestInfo, origin, metaData, doSendResponse);
return true;
} else if (method == 'logging.start') {
chrome.webrtcLoggingPrivate.start(
- sender.tab.id, origin, doSendResponse);
+ requestInfo, origin, doSendResponse);
return true;
} else if (method == 'logging.uploadOnRenderClose') {
chrome.webrtcLoggingPrivate.setUploadOnRenderClose(
- sender.tab.id, origin, true);
+ requestInfo, origin, true);
doSendResponse();
return false;
} else if (method == 'logging.noUploadOnRenderClose') {
chrome.webrtcLoggingPrivate.setUploadOnRenderClose(
- sender.tab.id, origin, false);
+ requestInfo, origin, false);
doSendResponse();
return false;
} else if (method == 'logging.stop') {
chrome.webrtcLoggingPrivate.stop(
- sender.tab.id, origin, doSendResponse);
+ requestInfo, origin, doSendResponse);
return true;
} else if (method == 'logging.upload') {
chrome.webrtcLoggingPrivate.upload(
- sender.tab.id, origin, doSendResponse);
+ requestInfo, origin, doSendResponse);
return true;
} else if (method == 'logging.stopAndUpload') {
- stopAllRtpDump(sender.tab.id, origin, function() {
- chrome.webrtcLoggingPrivate.stop(sender.tab.id, origin, function() {
+ stopAllRtpDump(requestInfo, origin, function() {
+ chrome.webrtcLoggingPrivate.stop(requestInfo, origin, function() {
chrome.webrtcLoggingPrivate.upload(
- sender.tab.id, origin, doSendResponse);
+ requestInfo, origin, doSendResponse);
});
});
return true;
} else if (method == 'logging.discard') {
chrome.webrtcLoggingPrivate.discard(
- sender.tab.id, origin, doSendResponse);
+ requestInfo, origin, doSendResponse);
return true;
} else if (method == 'getSinks') {
chrome.webrtcAudioPrivate.getSinks(doSendResponse);
return true;
} else if (method == 'getActiveSink') {
chrome.webrtcAudioPrivate.getActiveSink(
- sender.tab.id, doSendResponse);
+ requestInfo, doSendResponse);
return true;
} else if (method == 'setActiveSink') {
var sinkId = message['sinkId'];
chrome.webrtcAudioPrivate.setActiveSink(
- sender.tab.id, sinkId, doSendResponse);
+ requestInfo, sinkId, doSendResponse);
return true;
} else if (method == 'getAssociatedSink') {
var sourceId = message['sourceId'];
@@ -121,13 +137,13 @@ chrome.runtime.onMessageExternal.addListener(
var incoming = message['incoming'] || false;
var outgoing = message['outgoing'] || false;
chrome.webrtcLoggingPrivate.startRtpDump(
- sender.tab.id, origin, incoming, outgoing, doSendResponse);
+ requestInfo, origin, incoming, outgoing, doSendResponse);
return true;
} else if (method == 'logging.stopRtpDump') {
var incoming = message['incoming'] || false;
var outgoing = message['outgoing'] || false;
chrome.webrtcLoggingPrivate.stopRtpDump(
- sender.tab.id, origin, incoming, outgoing, doSendResponse);
+ requestInfo, origin, incoming, outgoing, doSendResponse);
return true;
}
throw new Error('Unknown method: ' + method);
@@ -180,7 +196,7 @@ function onChooseDesktopMediaPort(port) {
// A port for continuously reporting relevant CPU usage information to the page.
function onProcessCpu(port) {
- var tabPid;
+ var tabPid = port.sender.guestProcessId || undefined;
function processListener(processes) {
if (tabPid == undefined) {
// getProcessIdForTab sometimes fails, and does not call the callback.
@@ -222,14 +238,14 @@ function onProcessCpu(port) {
});
}
-function stopAllRtpDump(tabId, origin, callback) {
+function stopAllRtpDump(requestInfo, origin, callback) {
// Stops incoming and outgoing separately, otherwise stopRtpDump will fail if
// either type of dump has not been started.
chrome.webrtcLoggingPrivate.stopRtpDump(
- tabId, origin, true, false,
+ requestInfo, origin, true, false,
function() {
chrome.webrtcLoggingPrivate.stopRtpDump(
- tabId, origin, false, true, callback);
+ requestInfo, origin, false, true, callback);
});
}
diff --git a/chromium/chrome/browser/resources/help/help.html b/chromium/chrome/browser/resources/help/help.html
index 5f637b60477..b5103b87e6f 100644
--- a/chromium/chrome/browser/resources/help/help.html
+++ b/chromium/chrome/browser/resources/help/help.html
@@ -1,5 +1,5 @@
-<!DOCTYPE html>
-<html i18n-values="dir:textdirection;">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
<head>
<meta charset="utf-8">
<title i18n-content="aboutTitle"></title>
@@ -32,8 +32,7 @@
<script src="chrome://help-frame/help_page.js"></script>
<script src="chrome://help-frame/help.js"></script>
</head>
-<body class="uber-frame"
- i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<body class="uber-frame">
<header>
<h1 i18n-content="aboutTitle"></h1>
</header>
@@ -51,5 +50,5 @@
</div>
</body>
<script src="chrome://help-frame/strings.js"></script>
-<script src="chrome://resources/js/i18n_template2.js"></script>
+<script src="chrome://resources/js/i18n_template.js"></script>
</html>
diff --git a/chromium/chrome/browser/resources/help/help.js b/chromium/chrome/browser/resources/help/help.js
index 5407b6a9e50..b213a750508 100644
--- a/chromium/chrome/browser/resources/help/help.js
+++ b/chromium/chrome/browser/resources/help/help.js
@@ -19,7 +19,6 @@
PageManager.registerOverlay(help.ChannelChangePage.getInstance(),
HelpPage.getInstance());
}
- cr.ui.FocusManager.disableMouseFocusOnButtons();
PageManager.addObserver(new uber.PageManagerObserver());
PageManager.initialize(HelpPage.getInstance());
uber.onContentFrameLoaded();
diff --git a/chromium/chrome/browser/resources/help/help_content.css b/chromium/chrome/browser/resources/help/help_content.css
index ed37aa34775..b43f6e8dbac 100644
--- a/chromium/chrome/browser/resources/help/help_content.css
+++ b/chromium/chrome/browser/resources/help/help_content.css
@@ -62,41 +62,33 @@
margin-top: 30px;
}
-.help-page-icon,
-.help-page-icon-large {
+.help-page-icon {
+ background-position: center;
background-repeat: no-repeat;
display: inline-block;
- margin-right: 4px;
- vertical-align: top;
-}
-
-.help-page-icon {
- height: 17px;
- width: 17px;
-}
-
-.help-page-icon-large {
- height: 22px;
- width: 22px;
+ height: 18px;
+ vertical-align: middle;
+ width: 18px;
}
#update-status-icon.up-to-date {
- background-image: url('chrome://theme/IDR_UPDATE_UPTODATE');
- background-size: 17px;
+ background-image: url(chrome://theme/IDR_CHECKMARK);
+ background-size: 18px;
}
#update-status-icon.working {
- background-image: url('chrome://resources/images/throbber.svg');
+ background-image: url(chrome://resources/images/throbber.svg);
+ background-size: 16px;
}
#update-status-icon.failed {
- background-image: url('chrome://theme/IDR_UPDATE_FAILED');
- background-size: 17px;
+ background-image: url(chrome://theme/IDR_ERROR);
+ background-size: 18px;
}
#update-status-message-container {
display: inline-block;
- vertical-align: top;
+ vertical-align: middle;
}
#more-info-expander {
@@ -126,21 +118,18 @@
margin-top: 8px;
}
-#channel-change-disallowed-icon {
+#channel-change-disallowed-icon,
+.channel-change-error-icon {
-webkit-margin-start: 4px;
- background-image: url('chrome://theme/IDR_CONTROLLED_SETTING_MANDATORY');
- background-size: 17px;
- vertical-align: middle;
+ background-image: url(chrome://theme/IDR_CONTROLLED_SETTING_MANDATORY);
+ background-size: 16px;
}
.channel-change-error-bubble {
display: flex;
}
-.channel-change-error-icon {
- -webkit-margin-start: 4px;
- background-image: url('chrome://theme/IDR_CONTROLLED_SETTING_MANDATORY');
- background-size: 22px;
+.channel-change-error-bubble .channel-change-error-icon {
vertical-align: top;
}
@@ -152,14 +141,10 @@
}
#product-label-container {
- border: 1px solid #d9d9d9;
- border-radius: 2px;
- margin-top: 4px;
- padding: 10px;
+ padding-top: 32px;
}
#product-label {
display: block;
- margin: 0 auto;
width: 330px;
}
diff --git a/chromium/chrome/browser/resources/help/help_page.js b/chromium/chrome/browser/resources/help/help_page.js
index 050a98c0f49..7dbc848064d 100644
--- a/chromium/chrome/browser/resources/help/help_page.js
+++ b/chromium/chrome/browser/resources/help/help_page.js
@@ -139,8 +139,8 @@ cr.define('help', function() {
channelChangeDisallowedError.className = 'channel-change-error-bubble';
var channelChangeDisallowedIcon = document.createElement('div');
- channelChangeDisallowedIcon.classList.add('help-page-icon-large');
- channelChangeDisallowedIcon.classList.add('channel-change-error-icon');
+ channelChangeDisallowedIcon.className =
+ 'help-page-icon channel-change-error-icon';
channelChangeDisallowedError.appendChild(channelChangeDisallowedIcon);
var channelChangeDisallowedText = document.createElement('div');
@@ -187,7 +187,7 @@ cr.define('help', function() {
*/
setMoreInfoVisible_: function(visible) {
var moreInfo = $('more-info-container');
- if (visible == moreInfo.classList.contains('visible'))
+ if (!moreInfo || visible == moreInfo.classList.contains('visible'))
return;
moreInfo.classList.toggle('visible', visible);
diff --git a/chromium/chrome/browser/resources/help_app/OWNERS b/chromium/chrome/browser/resources/help_app/OWNERS
index c78d452bc12..8bba85f874e 100644
--- a/chromium/chrome/browser/resources/help_app/OWNERS
+++ b/chromium/chrome/browser/resources/help_app/OWNERS
@@ -1,2 +1,5 @@
dpolukhin@chromium.org
nkostylev@chromium.org
+cnwan@chromium.org
+cylee@chromium.org
+davidyu@chromium.org
diff --git a/chromium/chrome/browser/resources/help_app/manifest.json b/chromium/chrome/browser/resources/help_app/manifest.json
index cd755f2d15f..34de5e7ad74 100644
--- a/chromium/chrome/browser/resources/help_app/manifest.json
+++ b/chromium/chrome/browser/resources/help_app/manifest.json
@@ -27,8 +27,5 @@
"permissions": [
"storage"
],
- "web_accessible_resources": [
- "embedded_device_content/*"
- ],
"incognito": "split"
}
diff --git a/chromium/chrome/browser/resources/history/compiled_resources.gyp b/chromium/chrome/browser/resources/history/compiled_resources.gyp
index de4c86bed52..cbeb2d5cb5c 100644
--- a/chromium/chrome/browser/resources/history/compiled_resources.gyp
+++ b/chromium/chrome/browser/resources/history/compiled_resources.gyp
@@ -15,6 +15,7 @@
'../../../../ui/webui/resources/js/cr/ui/command.js',
'../../../../ui/webui/resources/js/cr/ui/focus_grid.js',
'../../../../ui/webui/resources/js/cr/ui/focus_manager.js',
+ '../../../../ui/webui/resources/js/cr/ui/focus_outline_manager.js',
'../../../../ui/webui/resources/js/cr/ui/focus_row.js',
'../../../../ui/webui/resources/js/cr/ui/menu.js',
'../../../../ui/webui/resources/js/cr/ui/menu_button.js',
diff --git a/chromium/chrome/browser/resources/history/history.css b/chromium/chrome/browser/resources/history/history.css
index 1ac53bd8d65..7b7f7fe39f9 100644
--- a/chromium/chrome/browser/resources/history/history.css
+++ b/chromium/chrome/browser/resources/history/history.css
@@ -25,8 +25,8 @@ body.uber-frame > .page.big-topbar-page {
}
#spinner {
- position: relative;
- top: 3px;
+ -webkit-margin-end: 5px;
+ vertical-align: bottom;
}
#notification-bar {
@@ -79,7 +79,7 @@ html[dir='rtl'] #editing-controls {
#range-next,
#range-previous {
- background-image: url('../disclosure_triangle_small.png'),
+ background-image: url(../disclosure_triangle_small.png),
-webkit-linear-gradient(rgb(241, 241, 241),
rgb(241, 241, 241) 38%,
rgb(230, 230, 230));
@@ -92,7 +92,7 @@ html[dir='rtl'] #editing-controls {
#range-previous:disabled {
/* Change the gradient manually in order to make it look like the other
* disabled buttons since you can't set opacity on background images only. */
- background-image: url('../disclosure_triangle_small.png'),
+ background-image: url(../disclosure_triangle_small.png),
-webkit-linear-gradient(rgb(231, 231, 231),
rgb(231, 231, 231) 38%,
rgb(220, 220, 220));
@@ -417,6 +417,11 @@ html[dir='rtl'] .site-domain {
border-radius: 2px;
}
+.entry-box > div,
+.site-domain-row > div {
+ min-width: 0;
+}
+
.active :-webkit-any(.entry-box, .site-domain-row) {
background-color: rgba(0, 0, 0, .05);
}
@@ -458,7 +463,6 @@ html[dir='rtl'] .blocked-indicator {
background-position-x: right;
}
-<if expr="not is_android">
/* TODO(sergiu): If this is the final icon replace it with a separate resource.
*/
.entry .blocked-indicator {
@@ -469,7 +473,6 @@ html[dir='rtl'] .blocked-indicator {
.blocked-indicator .title {
color: rgb(151, 156, 160);
}
-</if>
.site-domain button:hover {
text-decoration: none;
diff --git a/chromium/chrome/browser/resources/history/history.html b/chromium/chrome/browser/resources/history/history.html
index bbe5546a594..f8ea179f815 100644
--- a/chromium/chrome/browser/resources/history/history.html
+++ b/chromium/chrome/browser/resources/history/history.html
@@ -1,5 +1,5 @@
-<!DOCTYPE HTML>
-<html i18n-values="dir:textdirection;">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
<head>
<meta charset="utf-8">
<if expr="is_android or is_ios">
@@ -35,6 +35,7 @@
<script src="chrome://resources/js/cr/ui.js"></script>
<script src="chrome://resources/js/cr/ui/command.js"></script>
<script src="chrome://resources/js/cr/ui/focus_manager.js"></script>
+<script src="chrome://resources/js/cr/ui/focus_outline_manager.js"></script>
<script src="chrome://resources/js/cr/ui/focus_row.js"></script>
<script src="chrome://resources/js/cr/ui/focus_grid.js"></script>
<script src="chrome://resources/js/cr/ui/menu_item.js"></script>
@@ -60,11 +61,10 @@
</head>
<if expr="not is_android and not is_ios">
-<body class="uber-frame"
- i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<body class="uber-frame">
</if>
<if expr="is_android or is_ios">
-<body i18n-values=".style.fontFamily:fontfamily">
+<body>
</if>
<if expr="not is_android and not is_ios">
@@ -81,7 +81,7 @@
<input type="search" id="search-field"
i18n-values="aria-label:searchButton">
<input type="submit" id="search-button"
- i18n-values="value:searchButton">
+ i18n-values="value:searchButton" aria-controls="results-header">
</div>
<div id="filter-controls" hidden>
<div id="range-controls">
@@ -94,11 +94,14 @@
</div>
<div id="timeframe-controls">
<input type="radio" name="timeframe-filter" value="0" checked
- i18n-values="aria-label:rangeAllTime">
+ i18n-values="aria-label:rangeAllTime"
+ aria-controls="results-header">
<input type="radio" name="timeframe-filter" value="1"
- i18n-values="aria-label:rangeWeek">
+ i18n-values="aria-label:rangeWeek"
+ aria-controls="results-header">
<input type="radio" name="timeframe-filter" value="2"
- i18n-values="aria-label:rangeMonth">
+ i18n-values="aria-label:rangeMonth"
+ aria-controls="results-header">
</div>
</div>
</header>
@@ -115,7 +118,9 @@
<div id="notification-bar" hidden></div>
</div>
- <div id="results-display"></div>
+ <div id="results-display">
+ <h3 id="results-header" aria-live="polite"></h3>
+ </div>
<div id="loading-spinner" hidden>
<span id="loading">
<div id="spinner" class="inline-spinner"></div>
@@ -130,12 +135,14 @@
</div>
</div>
-<menu id="action-menu" hidden>
+<command id="remove-visit-command"></command>
+<cr-menu id="action-menu" hidden>
<button id="more-from-site" i18n-content="moreFromSite"></button>
- <button id="remove-visit" i18n-content="removeFromHistory"></button>
-</menu>
+ <button id="remove-visit" i18n-content="removeFromHistory"
+ command="#remove-visit-command"></button>
+</cr-menu>
<script src="chrome://history-frame/strings.js"></script>
-<script src="chrome://resources/js/i18n_template2.js"></script>
+<script src="chrome://resources/js/i18n_template.js"></script>
</body>
</html>
diff --git a/chromium/chrome/browser/resources/history/history.js b/chromium/chrome/browser/resources/history/history.js
index edfcd913ff7..8cfb2f81744 100644
--- a/chromium/chrome/browser/resources/history/history.js
+++ b/chromium/chrome/browser/resources/history/history.js
@@ -29,8 +29,10 @@ var selectionAnchor = -1;
var activeVisit = null;
/** @const */ var Command = cr.ui.Command;
+/** @const */ var FocusOutlineManager = cr.ui.FocusOutlineManager;
/** @const */ var Menu = cr.ui.Menu;
/** @const */ var MenuButton = cr.ui.MenuButton;
+/** @const */ var MenuItem = cr.ui.MenuItem;
/**
* Enum that shows the filtering behavior for a host or URL to a supervised
@@ -48,7 +50,7 @@ var SupervisedUserFilteringBehavior = {
* The type of the history result object. The definition is based on
* chrome/browser/ui/webui/history_ui.cc:
* BrowsingHistoryHandler::HistoryEntry::ToValue()
- * @typedef {{allTimestamps: Array.<number>,
+ * @typedef {{allTimestamps: Array<number>,
* blockedVisit: (boolean|undefined),
* dateRelativeDay: (string|undefined),
* dateShort: string,
@@ -190,8 +192,7 @@ Visit.prototype.getResultDOM = function(propertyBag) {
this.id_ = this.model_.getNextVisitId();
var self = this;
- // Only create the checkbox if it can be used either to delete an entry or to
- // block/allow it.
+ // Only create the checkbox if it can be used to delete an entry.
if (this.model_.editingEntriesAllowed) {
var checkbox = document.createElement('input');
checkbox.type = 'checkbox';
@@ -248,6 +249,10 @@ Visit.prototype.getResultDOM = function(propertyBag) {
e.preventDefault();
}.bind(this));
}
+
+ if (focusless)
+ bookmarkSection.tabIndex = -1;
+
entryBox.appendChild(bookmarkSection);
var visitEntryWrapper = /** @type {HTMLElement} */(
@@ -271,19 +276,23 @@ Visit.prototype.getResultDOM = function(propertyBag) {
}
if (isMobileVersion()) {
- var removeButton = createElementWithClassName('button', 'remove-entry');
- removeButton.setAttribute('aria-label',
- loadTimeData.getString('removeFromHistory'));
- removeButton.classList.add('custom-appearance');
- removeButton.addEventListener(
- 'click', this.removeEntryFromHistory_.bind(this));
- entryBox.appendChild(removeButton);
-
- // Support clicking anywhere inside the entry box.
- entryBox.addEventListener('click', function(e) {
- if (!e.defaultPrevented)
- self.titleLink.click();
- });
+ if (this.model_.editingEntriesAllowed) {
+ var removeButton = createElementWithClassName('button', 'remove-entry');
+ removeButton.setAttribute('aria-label',
+ loadTimeData.getString('removeFromHistory'));
+ removeButton.classList.add('custom-appearance');
+ removeButton.addEventListener(
+ 'click', this.removeEntryFromHistory_.bind(this));
+ entryBox.appendChild(removeButton);
+
+ // Support clicking anywhere inside the entry box.
+ entryBox.addEventListener('click', function(e) {
+ if (!e.defaultPrevented) {
+ self.titleLink.focus();
+ self.titleLink.click();
+ }
+ });
+ }
} else {
var dropDown = createElementWithClassName('button', 'drop-down');
dropDown.value = 'Open action menu';
@@ -625,7 +634,7 @@ HistoryModel.prototype.requestPage = function(page) {
/**
* Receiver for history query.
* @param {HistoryQuery} info An object containing information about the query.
- * @param {Array.<HistoryEntry>} results A list of results.
+ * @param {Array<HistoryEntry>} results A list of results.
*/
HistoryModel.prototype.addResults = function(info, results) {
// If no requests are in flight then this was an old request so we drop the
@@ -669,7 +678,7 @@ HistoryModel.prototype.getSize = function() {
* Get a list of visits between specified index positions.
* @param {number} start The start index.
* @param {number} end The end index.
- * @return {Array.<Visit>} A list of visits.
+ * @return {Array<Visit>} A list of visits.
*/
HistoryModel.prototype.getNumberedRange = function(start, end) {
return this.visits_.slice(start, end);
@@ -687,7 +696,7 @@ HistoryModel.prototype.hasMoreResults = function() {
/**
* Removes a list of visits from the history, and calls |callback| when the
* removal has successfully completed.
- * @param {Array.<Visit>} visits The visits to remove.
+ * @param {Array<Visit>} visits The visits to remove.
* @param {Function} callback The function to call after removal succeeds.
*/
HistoryModel.prototype.removeVisitsFromHistory = function(visits, callback) {
@@ -701,8 +710,8 @@ HistoryModel.prototype.removeVisitsFromHistory = function(visits, callback) {
});
}
- chrome.send('removeVisits', toBeRemoved);
this.deleteCompleteCallback_ = callback;
+ chrome.send('removeVisits', toBeRemoved);
};
/** @return {boolean} Whether the model is currently deleting a visit. */
@@ -882,55 +891,87 @@ HistoryModel.prototype.getGroupByDomain = function() {
};
///////////////////////////////////////////////////////////////////////////////
-// HistoryFocusObserver:
+// HistoryFocusRow:
/**
+ * Provides an implementation for a single column grid.
* @constructor
- * @implements {cr.ui.FocusRow.Observer}
+ * @extends {cr.ui.FocusRow}
*/
-function HistoryFocusObserver() {}
+function HistoryFocusRow() {}
+
+/**
+ * Decorates |rowElement| so that it can be treated as a HistoryFocusRow item.
+ * @param {Element} rowElement The element representing this row.
+ * @param {Node} boundary Focus events are ignored outside of this node.
+ */
+HistoryFocusRow.decorate = function(rowElement, boundary) {
+ rowElement.__proto__ = HistoryFocusRow.prototype;
+ rowElement.decorate(boundary);
+
+ rowElement.addElementIfPresent_('.entry-box input', 'checkbox');
+ rowElement.addElementIfPresent_('.domain-checkbox', 'checkbox');
+ rowElement.addElementIfPresent_('.bookmark-section.starred', 'star');
+ rowElement.addElementIfPresent_('[is="action-link"]', 'domain');
+ rowElement.addElementIfPresent_('.title a', 'title');
+ rowElement.addElementIfPresent_('.drop-down', 'menu');
+};
+
+HistoryFocusRow.prototype = {
+ __proto__: cr.ui.FocusRow.prototype,
-HistoryFocusObserver.prototype = {
/** @override */
- onActivate: function(row) {
- this.getActiveRowElement_(row).classList.add('active');
+ onActiveStateChanged: function(state) {
+ this.classList.toggle('active', state);
},
/** @override */
- onDeactivate: function(row) {
- this.getActiveRowElement_(row).classList.remove('active');
+ getEquivalentElement: function(element) {
+ if (this.contains(element))
+ return element;
+
+ // All elements default to another element with the same type.
+ var equivalent = this.getColumn_(element.getAttribute('column-type'));
+
+ if (!equivalent) {
+ switch (element.getAttribute('column-type')) {
+ case 'star':
+ equivalent = this.getColumn_('title') || this.getColumn_('domain');
+ break;
+ case 'domain':
+ equivalent = this.getColumn_('title');
+ break;
+ case 'title':
+ equivalent = this.getColumn_('domain');
+ break;
+ case 'menu':
+ return this.focusableElements[this.focusableElements.length - 1];
+ }
+ }
+
+ return equivalent || this.focusableElements[0];
},
/**
- * @param {cr.ui.FocusRow} row The row to find an element for.
- * @return {Element} |row|'s "active" element.
+ * @param {string} type The type of column to return.
+ * @return {?Element} The column matching the type.
* @private
*/
- getActiveRowElement_: function(row) {
- return findAncestorByClass(row.items[0], 'entry') ||
- findAncestorByClass(row.items[0], 'site-domain-wrapper');
+ getColumn_: function(type) {
+ return this.querySelector('[column-type=' + type + ']');
},
-};
-
-///////////////////////////////////////////////////////////////////////////////
-// HistoryFocusGrid:
-
-/**
- * @param {Node=} opt_boundary
- * @param {cr.ui.FocusRow.Observer=} opt_observer
- * @constructor
- * @extends {cr.ui.FocusGrid}
- */
-function HistoryFocusGrid(opt_boundary, opt_observer) {
- cr.ui.FocusGrid.apply(this, arguments);
-}
-HistoryFocusGrid.prototype = {
- __proto__: cr.ui.FocusGrid.prototype,
-
- /** @override */
- onMousedown: function(row, e) {
- return !!findAncestorByClass(e.target, 'menu-button');
+ /**
+ * @param {string} query A query to select the appropriate element.
+ * @param {string} type The type to use for the element.
+ * @private
+ */
+ addElementIfPresent_: function(query, type) {
+ var element = this.querySelector(query);
+ if (element) {
+ this.addFocusableElement(element);
+ element.setAttribute('column-type', type);
+ }
},
};
@@ -948,8 +989,7 @@ function HistoryView(model) {
this.editButtonTd_ = $('edit-button');
this.editingControlsDiv_ = $('editing-controls');
this.resultDiv_ = $('results-display');
- this.focusGrid_ = new HistoryFocusGrid(this.resultDiv_,
- new HistoryFocusObserver);
+ this.focusGrid_ = new cr.ui.FocusGrid();
this.pageDiv_ = $('results-pagination');
this.model_ = model;
this.pageIndex_ = 0;
@@ -985,7 +1025,7 @@ function HistoryView(model) {
$('timeframe-controls').onchange = function(e) {
var value = parseInt(e.target.value, 10);
- self.setRangeInDays(/** @type {HistoryModel.Range.<number>} */(value));
+ self.setRangeInDays(/** @type {HistoryModel.Range<number>} */(value));
};
$('range-previous').addEventListener('click', function(e) {
@@ -1181,14 +1221,14 @@ HistoryView.prototype.showNotification = function(innerHTML, isWarning) {
HistoryView.prototype.onBeforeRemove = function(visit) {
assert(this.currentVisits_.indexOf(visit) >= 0);
- var pos = this.focusGrid_.getPositionForTarget(document.activeElement);
- if (!pos)
+ var rowIndex = this.focusGrid_.getRowIndexForTarget(document.activeElement);
+ if (rowIndex == -1)
return;
- var row = this.focusGrid_.rows[pos.row + 1] ||
- this.focusGrid_.rows[pos.row - 1];
- if (row)
- row.focusIndex(Math.min(pos.col, row.items.length - 1));
+ var rowToFocus = this.focusGrid_.rows[rowIndex + 1] ||
+ this.focusGrid_.rows[rowIndex - 1];
+ if (rowToFocus)
+ rowToFocus.getEquivalentElement(document.activeElement).focus();
};
/** @param {Visit} visit The visit about to be unstarred. */
@@ -1196,9 +1236,12 @@ HistoryView.prototype.onBeforeUnstarred = function(visit) {
assert(this.currentVisits_.indexOf(visit) >= 0);
assert(visit.bookmarkStar == document.activeElement);
- var pos = this.focusGrid_.getPositionForTarget(document.activeElement);
- var row = this.focusGrid_.rows[pos.row];
- row.focusIndex(Math.min(pos.col + 1, row.items.length - 1));
+ var rowIndex = this.focusGrid_.getRowIndexForTarget(document.activeElement);
+ var row = this.focusGrid_.rows[rowIndex];
+
+ // Focus the title or domain when the bookmarked star is removed because the
+ // star will no longer be focusable.
+ row.querySelector('[column-type=title], [column-type=domain]').focus();
};
/** @param {Visit} visit The visit that was just unstarred. */
@@ -1296,7 +1339,7 @@ HistoryView.prototype.positionNotificationBar = function() {
* @return {boolean} Whether |el| is in |this.focusGrid_|.
*/
HistoryView.prototype.isInFocusGrid = function(el) {
- return !!this.focusGrid_.getPositionForTarget(el);
+ return this.focusGrid_.getRowIndexForTarget(el) != -1;
};
// HistoryView, private: ------------------------------------------------------
@@ -1311,7 +1354,11 @@ HistoryView.prototype.clear_ = function() {
if (alertOverlay && alertOverlay.classList.contains('showing'))
hideConfirmationOverlay();
- this.resultDiv_.textContent = '';
+ // Remove everything but <h3 id="results-header"> (the first child).
+ while (this.resultDiv_.children.length > 1) {
+ this.resultDiv_.removeChild(this.resultDiv_.lastElementChild);
+ }
+ $('results-header').textContent = '';
this.currentVisits_.forEach(function(visit) {
visit.isRendered = false;
@@ -1573,13 +1620,19 @@ HistoryView.prototype.displayResults_ = function(doneLoading) {
var groupByDomain = this.model_.getGroupByDomain();
if (searchText) {
- // Add a header for the search results, if there isn't already one.
- if (!this.resultDiv_.querySelector('h3')) {
- var header = document.createElement('h3');
- header.textContent = loadTimeData.getStringF('searchResultsFor',
- searchText);
- this.resultDiv_.appendChild(header);
+ var headerText;
+ if (!doneLoading) {
+ headerText = loadTimeData.getStringF('searchResultsFor', searchText);
+ } else if (results.length == 0) {
+ headerText = loadTimeData.getString('noSearchResults');
+ } else {
+ var resultId = results.length == 1 ? 'searchResult' : 'searchResults';
+ headerText = loadTimeData.getStringF('foundSearchResults',
+ results.length,
+ loadTimeData.getString(resultId),
+ searchText);
}
+ $('results-header').textContent = headerText;
this.addTimeframeInterval_(this.resultDiv_);
@@ -1589,11 +1642,7 @@ HistoryView.prototype.displayResults_ = function(doneLoading) {
if (!this.model_.editingEntriesAllowed)
searchResults.classList.add('no-checkboxes');
- if (results.length == 0 && doneLoading) {
- var noSearchResults = searchResults.appendChild(
- createElementWithClassName('div', 'no-results-message'));
- noSearchResults.textContent = loadTimeData.getString('noSearchResults');
- } else {
+ if (doneLoading) {
for (var i = 0, visit; visit = results[i]; i++) {
if (!visit.isRendered) {
searchResults.appendChild(visit.getResultDOM({
@@ -1610,13 +1659,12 @@ HistoryView.prototype.displayResults_ = function(doneLoading) {
this.addTimeframeInterval_(resultsFragment);
- if (results.length == 0 && doneLoading) {
- var noResults = resultsFragment.appendChild(
- createElementWithClassName('div', 'no-results-message'));
- noResults.textContent = loadTimeData.getString('noResults');
- this.resultDiv_.appendChild(resultsFragment);
+ var noResults = results.length == 0 && doneLoading;
+ $('results-header').textContent = noResults ?
+ loadTimeData.getString('noResults') : '';
+
+ if (noResults)
return;
- }
if (this.getRangeInDays() == HistoryModel.Range.MONTH &&
groupByDomain) {
@@ -1653,26 +1701,17 @@ var focusGridRowSelector = [
'.site-domain-wrapper'
].join(', ');
-var focusGridColumnSelector = [
- '.entry-box input',
- '.bookmark-section.starred',
- '.title a',
- '.drop-down',
- '.domain-checkbox',
- '[is="action-link"]',
-].join(', ');
-
/** @private */
HistoryView.prototype.updateFocusGrid_ = function() {
var rows = this.resultDiv_.querySelectorAll(focusGridRowSelector);
- var grid = [];
+ this.focusGrid_.destroy();
for (var i = 0; i < rows.length; ++i) {
assert(rows[i].parentNode);
- grid.push(rows[i].querySelectorAll(focusGridColumnSelector));
+ HistoryFocusRow.decorate(rows[i], this.resultDiv_);
+ this.focusGrid_.addRow(rows[i]);
}
-
- this.focusGrid_.setGrid(grid);
+ this.focusGrid_.ensureRowActive();
};
/**
@@ -1682,9 +1721,9 @@ HistoryView.prototype.updateFocusGrid_ = function() {
HistoryView.prototype.updateNavBar_ = function() {
this.updateRangeButtons_();
- // Supervised users have the control bar on top, don't show it on the bottom
- // as well.
- if (!loadTimeData.getBoolean('isSupervisedProfile')) {
+ // If grouping by domain is enabled, there's a control bar on top, don't show
+ // the one on the bottom as well.
+ if (!loadTimeData.getBoolean('groupByDomain')) {
$('newest-button').hidden = this.pageIndex_ == 0;
$('newer-button').hidden = this.pageIndex_ == 0;
$('older-button').hidden =
@@ -1763,6 +1802,16 @@ HistoryView.prototype.toggleGroupedVisits_ = function(e) {
}
entry.classList.toggle('expand');
+
+ var row = entry.querySelector('.site-domain-wrapper');
+ var activeRows =
+ this.resultDiv_.getElementsByClassName(cr.ui.FocusRow.ACTIVE_CLASS);
+ for (var i = 0; i < activeRows.length; ++i) {
+ if (activeRows[i] != row) // Ignore |row| to avoid flicker.
+ activeRows[i].makeActive(false);
+ }
+ row.makeActive(true);
+
this.updateFocusGrid_();
};
@@ -1885,6 +1934,7 @@ PageState.getHashString = function(term, page, range, offset) {
*/
function load() {
uber.onContentFrameLoaded();
+ FocusOutlineManager.forDocument(document);
var searchField = $('search-field');
@@ -1914,18 +1964,14 @@ function load() {
searchField.blur(); // Dismiss the keyboard.
};
- var mayRemoveVisits = loadTimeData.getBoolean('allowDeletingHistory');
- $('remove-visit').disabled = !mayRemoveVisits;
-
- if (mayRemoveVisits) {
- $('remove-visit').addEventListener('activate', function(e) {
- activeVisit.removeFromHistory();
- activeVisit = null;
- });
- }
+ var removeMenu = getRequiredElement('remove-visit');
+ // Decorate remove-visit before disabling/hiding because the values are
+ // overwritten when decorating a MenuItem that has a Command.
+ cr.ui.decorate(removeMenu, MenuItem);
+ removeMenu.disabled = !loadTimeData.getBoolean('allowDeletingHistory');
+ removeMenu.hidden = loadTimeData.getBoolean('hideDeleteVisitUI');
- if (!loadTimeData.getBoolean('showDeleteVisitUI'))
- $('remove-visit').hidden = true;
+ document.addEventListener('command', handleCommand);
searchField.addEventListener('search', doSearch);
$('search-button').addEventListener('click', doSearch);
@@ -1935,15 +1981,16 @@ function load() {
activeVisit = null;
});
- // Only show the controls if the command line switch is activated.
- if (loadTimeData.getBoolean('groupByDomain') ||
- loadTimeData.getBoolean('isSupervisedProfile')) {
- // Hide the top container which has the "Clear browsing data" and "Remove
- // selected entries" buttons since they're unavailable for supervised users.
- $('top-container').hidden = true;
+ // Only show the controls if the command line switch is activated or the user
+ // is supervised.
+ if (loadTimeData.getBoolean('groupByDomain')) {
$('history-page').classList.add('big-topbar-page');
$('filter-controls').hidden = false;
}
+ // Hide the top container which has the "Clear browsing data" and "Remove
+ // selected entries" buttons if deleting history is not allowed.
+ if (!loadTimeData.getBoolean('allowDeletingHistory'))
+ $('top-container').hidden = true;
uber.setTitle(loadTimeData.getString('title'));
@@ -1970,7 +2017,7 @@ function load() {
$('history-page').appendChild($('clear-browsing-data'));
} else {
window.addEventListener('message', function(e) {
- e = /** @type {!MessageEvent.<!{method: string}>} */(e);
+ e = /** @type {!MessageEvent<!{method: string}>} */(e);
if (e.data.method == 'frameSelected')
searchField.focus();
});
@@ -1995,6 +2042,26 @@ function load() {
}
/**
+ * Handle all commands in the history page.
+ * @param {!Event} e is a command event.
+ */
+function handleCommand(e) {
+ switch (e.command.id) {
+ case 'remove-visit-command':
+ // Removing visited items needs to be done with a command in order to have
+ // proper focus. This is because the command event is handled after the
+ // menu dialog is no longer visible and focus has returned to the history
+ // items. The activate event is handled when the menu dialog is still
+ // visible and focus is lost.
+ // removeEntryFromHistory_ will update activeVisit to the newly focused
+ // history item.
+ assert(!$('remove-visit').disabled);
+ activeVisit.removeEntryFromHistory_(e);
+ break;
+ }
+}
+
+/**
* Updates the filter status labels of a host/URL entry to the current value.
* @param {Element} statusElement The div which contains the status labels.
* @param {SupervisedUserFilteringBehavior} newStatus The filter status of the
@@ -2032,10 +2099,23 @@ function showConfirmationOverlay() {
$('history-page').setAttribute('aria-hidden', 'true');
uber.invokeMethodOnParent('beginInterceptingEvents');
- // If an element is focused behind the confirm overlay, blur it so focus
- // doesn't accidentally get stuck behind it.
+ // Change focus to the overlay if any other control was focused by keyboard
+ // before. Otherwise, no one should have focus.
+ var focusOverlay = FocusOutlineManager.forDocument(document).visible &&
+ document.activeElement != document.body;
if ($('history-page').contains(document.activeElement))
document.activeElement.blur();
+
+ if (focusOverlay) {
+ // Wait until the browser knows the button has had a chance to become
+ // visible.
+ window.requestAnimationFrame(function() {
+ var button = cr.ui.overlay.getDefaultButton($('overlay'));
+ if (button)
+ button.focus();
+ });
+ }
+ $('alertOverlay').classList.toggle('focus-on-hide', focusOverlay);
}
/**
@@ -2112,6 +2192,10 @@ function removeItems() {
historyView.reload.bind(historyView));
$('overlay').removeEventListener('cancelOverlay', onCancelRemove);
hideConfirmationOverlay();
+ if ($('alertOverlay').classList.contains('focus-on-hide') &&
+ FocusOutlineManager.forDocument(document).visible) {
+ $('search-field').focus();
+ }
}
function onCancelRemove() {
@@ -2126,6 +2210,10 @@ function removeItems() {
}
$('overlay').removeEventListener('cancelOverlay', onCancelRemove);
hideConfirmationOverlay();
+ if ($('alertOverlay').classList.contains('focus-on-hide') &&
+ FocusOutlineManager.forDocument(document).visible) {
+ $('remove-selected').focus();
+ }
}
if (checked.length) {
@@ -2290,7 +2378,7 @@ function getFilteringStatusDOM(filteringBehavior) {
/**
* Our history system calls this function with results from searches.
* @param {HistoryQuery} info An object containing information about the query.
- * @param {Array.<HistoryEntry>} results A list of results.
+ * @param {Array<HistoryEntry>} results A list of results.
*/
function historyResult(info, results) {
historyModel.addResults(info, results);
diff --git a/chromium/chrome/browser/resources/history/history_focus_manager.js b/chromium/chrome/browser/resources/history/history_focus_manager.js
index 84b56c5140e..610107c418b 100644
--- a/chromium/chrome/browser/resources/history/history_focus_manager.js
+++ b/chromium/chrome/browser/resources/history/history_focus_manager.js
@@ -22,7 +22,7 @@ HistoryFocusManager.prototype = {
/** @override */
getFocusParent: function() {
return document.querySelector('#overlay .showing') ||
- document.querySelector('menu:not([hidden])') ||
+ document.querySelector('cr-menu:not([hidden])') ||
$('history-page');
},
};
diff --git a/chromium/chrome/browser/resources/history/history_mobile.css b/chromium/chrome/browser/resources/history/history_mobile.css
index fef07f30413..4a4bfbfb021 100644
--- a/chromium/chrome/browser/resources/history/history_mobile.css
+++ b/chromium/chrome/browser/resources/history/history_mobile.css
@@ -4,12 +4,17 @@
/* This file contains styles specific to Android and iOS. */
+html:not(.focus-outline-visible) :focus {
+ outline: none;
+}
+
html {
height: 100%;
}
body {
color: rgb(76, 76, 76);
+ font-size: initial;
height: 100%;
margin: 0;
}
@@ -67,7 +72,7 @@ h3 {
#search-field {
-webkit-padding-start: 64px;
background-image:
- url('../../../../ui/webui/resources/images/2x/search.png');
+ url(../../../../ui/webui/resources/images/2x/search.png);
background-position: 16px center;
background-repeat: no-repeat;
background-size: 32px;
@@ -82,16 +87,6 @@ html[dir='rtl'] #search-field {
background-position: right 16px center;
}
-.no-results-message {
- margin-bottom: 1em;
- padding-left: 16px;
- padding-right: 16px;
-}
-
-.search-results .no-results-message {
- margin-top: 1em;
-}
-
#notification-bar.alone {
float: none;
font-size: 75%;
@@ -111,7 +106,7 @@ html[dir='rtl'] #search-field {
}
button.remove-entry {
- background: url('../../../../ui/webui/resources/images/2x/x-thin.png')
+ background: url(../../../../ui/webui/resources/images/2x/x-thin.png)
no-repeat center center;
background-size: 13px;
border: 0;
@@ -206,8 +201,27 @@ input {
line-height: 1.3;
}
+.entry .visit-entry.blocked-indicator {
+ line-height: 2;
+}
+
+.entry .visit-entry :-webkit-any(a, .domain) {
+ display: block;
+ margin-left: 0;
+ margin-right: 0;
+ min-width: 0;
+ overflow: hidden;
+ padding-left: 0;
+ padding-right: 0;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.entry .visit-entry.blocked-indicator a {
+ display: inline;
+}
+
.entry .domain {
- -webkit-padding-start: 0;
font-size: 14px;
}
@@ -297,8 +311,7 @@ input {
h1,
#notification-bar,
- #loading-spinner,
- .no-results-message {
+ #loading-spinner {
padding-left: 0;
padding-right: 0;
}
@@ -314,4 +327,8 @@ input {
/* Should be 0, but that breaks scrolling -- see crbug.com/292715. */
bottom: -1px;
}
+
+#results-header:empty {
+ display: none;
+}
</if> /* is_ios */
diff --git a/chromium/chrome/browser/resources/history/other_devices.js b/chromium/chrome/browser/resources/history/other_devices.js
index 398a0b5b3f1..3ac878ef774 100644
--- a/chromium/chrome/browser/resources/history/other_devices.js
+++ b/chromium/chrome/browser/resources/history/other_devices.js
@@ -137,6 +137,7 @@ DeviceContextMenuController.prototype.onOpenAll_ = function(e) {
DeviceContextMenuController.prototype.updateMenuItems_ = function() {
this.collapseItem_.hidden = this.session_.collapsed;
this.expandItem_.hidden = !this.session_.collapsed;
+ this.menu.selectedItem = this.menu.querySelector(':not([hidden])');
};
@@ -426,6 +427,33 @@ DevicesView.prototype.increaseRowHeight = function(row, height) {
// DevicesView, Private -------------------------------------------------------
/**
+ * Provides an implementation for a single column grid.
+ * @constructor
+ * @extends {cr.ui.FocusRow}
+ */
+function DevicesViewFocusRow() {}
+
+/**
+ * Decorates |rowElement| so that it can be treated as a DevicesViewFocusRow.
+ * @param {Element} rowElement The element representing this row.
+ * @param {Node} boundary Focus events are ignored outside of this node.
+ */
+DevicesViewFocusRow.decorate = function(rowElement, boundary) {
+ rowElement.__proto__ = DevicesViewFocusRow.prototype;
+ rowElement.decorate(boundary);
+ rowElement.addFocusableElement(rowElement);
+};
+
+DevicesViewFocusRow.prototype = {
+ __proto__: cr.ui.FocusRow.prototype,
+
+ /** @override */
+ getEquivalentElement: function(element) {
+ return this;
+ },
+};
+
+/**
* Update the page with results.
* @private
*/
@@ -478,16 +506,18 @@ DevicesView.prototype.displayResults_ = function() {
this.focusGrids_.forEach(function(grid) { grid.destroy(); });
this.focusGrids_.length = 0;
- var singleColumn = function(e) { return [e]; };
-
var devices = this.resultDiv_.querySelectorAll('.device-contents');
for (var i = 0; i < devices.length; ++i) {
var rows = devices[i].querySelectorAll('.device-tab-entry, button');
if (!rows.length)
continue;
- var grid = new cr.ui.FocusGrid(devices[i]);
- grid.setGrid(Array.prototype.map.call(rows, singleColumn));
+ var grid = new cr.ui.FocusGrid();
+ for (var j = 0; j < rows.length; ++j) {
+ DevicesViewFocusRow.decorate(rows[j], devices[i]);
+ grid.addRow(rows[j]);
+ }
+ grid.ensureRowActive();
this.focusGrids_.push(grid);
}
};
diff --git a/chromium/chrome/browser/resources/hotword/always_on_manager.js b/chromium/chrome/browser/resources/hotword/always_on_manager.js
index c656c9ce86c..042a8d2d7e1 100644
--- a/chromium/chrome/browser/resources/hotword/always_on_manager.js
+++ b/chromium/chrome/browser/resources/hotword/always_on_manager.js
@@ -31,7 +31,7 @@ cr.define('hotword', function() {
updateListeners: function() {
hotword.BaseSessionManager.prototype.updateListeners.call(this);
if (this.enabled())
- this.startSession_();
+ this.startSession();
}
};
diff --git a/chromium/chrome/browser/resources/hotword/audio_client.js b/chromium/chrome/browser/resources/hotword/audio_client.js
index 60ee57943bc..f74f84b15f3 100644
--- a/chromium/chrome/browser/resources/hotword/audio_client.js
+++ b/chromium/chrome/browser/resources/hotword/audio_client.js
@@ -30,7 +30,7 @@
/**
* Keeps track of the effects of different commands. Used to verify that
* proper UIs are shown to the user.
- * @private {Object.<AudioClient.CommandToPage, Object>}
+ * @private {Object<AudioClient.CommandToPage, Object>}
*/
this.uiStatus_ = null;
@@ -355,8 +355,18 @@
this.port_.onMessage.addListener(
this.handleCommandFromExtension_.bind(this));
- if (this.speechActive_)
+ if (this.speechActive_) {
this.sendCommandToExtension_(AudioClient.CommandFromPage.SPEECH_START);
+ } else {
+ // It's possible for this script to be injected into the page after it has
+ // completed loaded (i.e. when prerendering). In this case, this script
+ // won't receive a SPEECH_RESET from the page to forward onto the
+ // extension. To make up for this, always send a SPEECH_RESET. This means
+ // in most cases, the extension will receive SPEECH_RESET twice, one from
+ // this sendCommandToExtension_ and the one forwarded from the page. But
+ // that's OK and the extension can handle it.
+ this.sendCommandToExtension_(AudioClient.CommandFromPage.SPEECH_RESET);
+ }
};
// Initializes as soon as the code is ready, do not wait for the page.
diff --git a/chromium/chrome/browser/resources/hotword/base_session_manager.js b/chromium/chrome/browser/resources/hotword/base_session_manager.js
index 7ea30069ba6..d1ee2bfbff9 100644
--- a/chromium/chrome/browser/resources/hotword/base_session_manager.js
+++ b/chromium/chrome/browser/resources/hotword/base_session_manager.js
@@ -61,15 +61,17 @@ cr.define('hotword', function() {
/**
* Starts a launcher hotwording session.
- * @private
+ * @param {hotword.constants.TrainingMode=} opt_mode The mode to start the
+ * recognizer in.
*/
- startSession_: function() {
+ startSession: function(opt_mode) {
this.stateManager.startSession(
this.sessionSource_,
function() {
chrome.hotwordPrivate.setHotwordSessionState(true, function() {});
},
- this.handleHotwordTrigger.bind(this));
+ this.handleHotwordTrigger.bind(this),
+ opt_mode);
},
/**
@@ -83,11 +85,14 @@ cr.define('hotword', function() {
/**
* Handles a hotword triggered event.
+ * @param {?Object} log Audio log data, if audio logging is enabled.
* @protected
*/
- handleHotwordTrigger: function() {
- hotword.debug('Hotword triggered: ' + this.sessionSource_);
- chrome.hotwordPrivate.notifyHotwordRecognition('search', function() {});
+ handleHotwordTrigger: function(log) {
+ hotword.debug('Hotword triggered: ' + this.sessionSource_, log);
+ chrome.hotwordPrivate.notifyHotwordRecognition('search',
+ log,
+ function() {});
},
/**
@@ -96,7 +101,7 @@ cr.define('hotword', function() {
*/
handleSessionRequested_: function() {
hotword.debug('handleSessionRequested_: ' + this.sessionSource_);
- this.startSession_();
+ this.startSession();
},
/**
diff --git a/chromium/chrome/browser/resources/hotword/constants.js b/chromium/chrome/browser/resources/hotword/constants.js
index 88dbcb53e77..988f9b87d89 100644
--- a/chromium/chrome/browser/resources/hotword/constants.js
+++ b/chromium/chrome/browser/resources/hotword/constants.js
@@ -6,6 +6,18 @@ cr.define('hotword.constants', function() {
'use strict';
/**
+ * Number of seconds of audio to record when logging is enabled.
+ * @const {number}
+ */
+var AUDIO_LOG_SECONDS = 2;
+
+/**
+ * Timeout in seconds, for detecting false positives with a hotword stream.
+ * @const {number}
+ */
+var HOTWORD_STREAM_TIMEOUT_SECONDS = 2;
+
+/**
* Hotword data shared module extension's ID.
* @const {string}
*/
@@ -30,6 +42,38 @@ var CLIENT_PORT_NAME = 'chwcpn';
var COMMAND_FIELD_NAME = 'cmd';
/**
+ * The speaker model file name.
+ * @const {string}
+ */
+var SPEAKER_MODEL_FILE_NAME = 'speaker_model.data';
+
+/**
+ * The training utterance file name prefix.
+ * @const {string}
+ */
+var UTTERANCE_FILE_PREFIX = 'utterance-';
+
+/**
+ * The training utterance file extension.
+ * @const {string}
+ */
+var UTTERANCE_FILE_EXTENSION = '.raw';
+
+/**
+ * The number of training utterances required to train the speaker model.
+ * @const {number}
+ */
+var NUM_TRAINING_UTTERANCES = 3;
+
+/**
+ * The size of the file system requested for reading the speaker model and
+ * utterances. This number should always be larger than the combined file size,
+ * currently 576338 bytes as of February 2015.
+ * @const {number}
+ */
+var FILE_SYSTEM_SIZE_BYTES = 1048576;
+
+/**
* Time to wait for expected messages, in milliseconds.
* @enum {number}
*/
@@ -63,7 +107,9 @@ var Error = {
var Event = {
READY: 'ready',
TRIGGER: 'trigger',
+ SPEAKER_MODEL_SAVED: 'speaker model saved',
ERROR: 'error',
+ TIMEOUT: 'timeout',
};
/**
@@ -76,12 +122,19 @@ var NaClPlugin = {
SAMPLE_RATE_PREFIX: 'h',
MODEL_PREFIX: 'm',
STOP: 's',
+ LOG: 'l',
+ DSP: 'd',
+ BEGIN_SPEAKER_MODEL: 'b',
+ ADAPT_SPEAKER_MODEL: 'a',
+ FINISH_SPEAKER_MODEL: 'f',
+ SPEAKER_MODEL_SAVED: 'sm_saved',
REQUEST_MODEL: 'model',
MODEL_LOADED: 'model_loaded',
READY_FOR_AUDIO: 'audio',
STOPPED: 'stopped',
HOTWORD_DETECTED: 'hotword',
- MS_CONFIGURED: 'ms_configured'
+ MS_CONFIGURED: 'ms_configured',
+ TIMEOUT: 'timeout'
};
/**
@@ -97,8 +150,8 @@ var CommandToPage = {
};
/**
- * Messages sent from the Google page to the injected scripts
- * and then passed to the extension.
+ * Messages sent from the Google page to the extension or to the
+ * injected script and then passed to the extension.
* @enum {string}
*/
var CommandFromPage = {
@@ -111,7 +164,14 @@ var CommandFromPage = {
CLICKED_RESUME: 'hcc',
CLICKED_RESTART: 'hcr',
CLICKED_DEBUG: 'hcd',
- WAKE_UP_HELPER: 'wuh'
+ WAKE_UP_HELPER: 'wuh',
+ // Command specifically for the opt-in promo below this line.
+ // User has explicitly clicked 'no'.
+ CLICKED_NO_OPTIN: 'hcno',
+ // User has opted in.
+ CLICKED_OPTIN: 'hco',
+ // User clicked on the microphone.
+ PAGE_WAKEUP: 'wu'
};
/**
@@ -126,6 +186,16 @@ var SessionSource = {
};
/**
+ * The mode to start the hotword recognizer in.
+ * @enum {string}
+ */
+var RecognizerStartMode = {
+ NORMAL: 'normal',
+ NEW_MODEL: 'new model',
+ ADAPT_MODEL: 'adapt model'
+};
+
+/**
* MediaStream open success/errors to be reported via UMA.
* DO NOT remove or renumber values in this enum. Only add new ones.
* @enum {number}
@@ -156,7 +226,8 @@ var UmaMetrics = {
TRIGGER: 'Hotword.HotwordTrigger',
MEDIA_STREAM_RESULT: 'Hotword.HotwordMediaStreamResult',
NACL_PLUGIN_LOAD_RESULT: 'Hotword.HotwordNaClPluginLoadResult',
- NACL_MESSAGE_TIMEOUT: 'Hotword.HotwordNaClMessageTimeout'
+ NACL_MESSAGE_TIMEOUT: 'Hotword.HotwordNaClMessageTimeout',
+ TRIGGER_SOURCE: 'Hotword.HotwordTriggerSource'
};
/**
@@ -188,6 +259,19 @@ var UmaNaClPluginLoadResult = {
};
/**
+ * Source of hotword triggering, to be reported via UMA.
+ * DO NOT remove or renumber values in this enum. Only add new ones.
+ * @enum {number}
+ */
+var UmaTriggerSource = {
+ LAUNCHER: 0,
+ NTP_GOOGLE_COM: 1,
+ ALWAYS_ON: 2,
+ TRAINING: 3,
+ MAX: 3
+};
+
+/**
* The browser UI language.
* @const {string}
*/
@@ -195,23 +279,32 @@ var UI_LANGUAGE = (chrome.i18n && chrome.i18n.getUILanguage) ?
chrome.i18n.getUILanguage() : '';
return {
+ AUDIO_LOG_SECONDS: AUDIO_LOG_SECONDS,
CLIENT_PORT_NAME: CLIENT_PORT_NAME,
COMMAND_FIELD_NAME: COMMAND_FIELD_NAME,
+ FILE_SYSTEM_SIZE_BYTES: FILE_SYSTEM_SIZE_BYTES,
+ HOTWORD_STREAM_TIMEOUT_SECONDS: HOTWORD_STREAM_TIMEOUT_SECONDS,
+ NUM_TRAINING_UTTERANCES: NUM_TRAINING_UTTERANCES,
SHARED_MODULE_ID: SHARED_MODULE_ID,
SHARED_MODULE_ROOT: SHARED_MODULE_ROOT,
+ SPEAKER_MODEL_FILE_NAME: SPEAKER_MODEL_FILE_NAME,
UI_LANGUAGE: UI_LANGUAGE,
+ UTTERANCE_FILE_EXTENSION: UTTERANCE_FILE_EXTENSION,
+ UTTERANCE_FILE_PREFIX: UTTERANCE_FILE_PREFIX,
CommandToPage: CommandToPage,
CommandFromPage: CommandFromPage,
Error: Error,
Event: Event,
File: File,
NaClPlugin: NaClPlugin,
+ RecognizerStartMode: RecognizerStartMode,
SessionSource: SessionSource,
TimeoutMs: TimeoutMs,
UmaMediaStreamOpenResult: UmaMediaStreamOpenResult,
UmaMetrics: UmaMetrics,
UmaNaClMessageTimeout: UmaNaClMessageTimeout,
- UmaNaClPluginLoadResult: UmaNaClPluginLoadResult
+ UmaNaClPluginLoadResult: UmaNaClPluginLoadResult,
+ UmaTriggerSource: UmaTriggerSource
};
});
diff --git a/chromium/chrome/browser/resources/hotword/keep_alive.js b/chromium/chrome/browser/resources/hotword/keep_alive.js
new file mode 100644
index 00000000000..06ddf237df3
--- /dev/null
+++ b/chromium/chrome/browser/resources/hotword/keep_alive.js
@@ -0,0 +1,54 @@
+// 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.
+
+cr.define('hotword', function() {
+ 'use strict';
+
+ /**
+ * Class used to keep this extension alive. When started, this calls an
+ * extension API on a regular basis which resets the event page keep-alive
+ * timer.
+ * @constructor
+ */
+ function KeepAlive() {
+ this.timeoutId_ = null;
+ }
+
+ KeepAlive.prototype = {
+ /**
+ * Start the keep alive process. Safe to call multiple times.
+ */
+ start: function() {
+ if (this.timeoutId_ == null)
+ this.timeoutId_ = setTimeout(this.handleTimeout_.bind(this), 1000);
+ },
+
+ /**
+ * Stops the keep alive process. Safe to call multiple times.
+ */
+ stop: function() {
+ if (this.timeoutId_ != null) {
+ clearTimeout(this.timeoutId_);
+ this.timeoutId_ = null;
+ }
+ },
+
+ /**
+ * Handle the timer timeout. Calls an extension API and schedules the next
+ * timeout.
+ * @private
+ */
+ handleTimeout_: function() {
+ // Dummy extensions API call used to keep this event page alive by
+ // resetting the shutdown timer.
+ chrome.runtime.getPlatformInfo(function(info) {});
+
+ this.timeoutId_ = setTimeout(this.handleTimeout_.bind(this), 1000);
+ }
+ };
+
+ return {
+ KeepAlive: KeepAlive
+ };
+});
diff --git a/chromium/chrome/browser/resources/hotword/manager.js b/chromium/chrome/browser/resources/hotword/manager.js
index 83b6f105cd9..54f3fcecef8 100644
--- a/chromium/chrome/browser/resources/hotword/manager.js
+++ b/chromium/chrome/browser/resources/hotword/manager.js
@@ -24,16 +24,21 @@
var launcherManager = new hotword.LauncherManager(stateManager);
var trainingManager = new hotword.TrainingManager(stateManager);
- // Detect Chrome startup and make sure we get a chance to run.
- chrome.runtime.onStartup.addListener(function() {
- stateManager.updateStatus();
- });
-
// Detect when hotword settings have changed.
chrome.hotwordPrivate.onEnabledChanged.addListener(function() {
stateManager.updateStatus();
});
+ // Detect a request to delete the speaker model.
+ chrome.hotwordPrivate.onDeleteSpeakerModel.addListener(function() {
+ hotword.TrainingManager.handleDeleteSpeakerModel();
+ });
+
+ // Detect a request for the speaker model existence.
+ chrome.hotwordPrivate.onSpeakerModelExists.addListener(function() {
+ hotword.TrainingManager.handleSpeakerModelExists();
+ });
+
// Detect when the shared module containing the NaCL module and language model
// is installed.
chrome.management.onInstalled.addListener(function(info) {
diff --git a/chromium/chrome/browser/resources/hotword/manifest.json b/chromium/chrome/browser/resources/hotword/manifest.json
index 211b2c0db57..f3c17681cce 100644
--- a/chromium/chrome/browser/resources/hotword/manifest.json
+++ b/chromium/chrome/browser/resources/hotword/manifest.json
@@ -3,7 +3,7 @@
"key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDbHXRPiq2De9EJ+4pvNN6uE/D2avxrqyLSpA/Hq3II+btkPl1gboY3oUPTfevpVOFa90Y1c1b3/W682dXqybT0klIvFLKhdQx0LiVqSUQyIaDrwOCSo/ZcukbEwDRojegWymCjHvX6WZk4kKZzTJYzY1vrp0TWKLhttEMN9KFmowIDAQAB",
"name": "Hotword triggering",
- "version": "0.0.1.3",
+ "version": "0.0.1.4",
"manifest_version": 2,
"background": {
@@ -12,6 +12,7 @@
"chrome://resources/js/util.js",
"chrome://resources/js/cr/event_target.js",
"constants.js",
+ "keep_alive.js",
"logging.js",
"metrics.js",
"nacl_manager.js",
@@ -27,11 +28,22 @@
},
"permissions": [
+ "*://*.google.at/*",
+ "*://*.google.ca/*",
"*://*.google.com/*",
- "*://*.google.ru/*",
+ "*://*.google.com.au/*",
+ "*://*.google.com.mx/*",
+ "*://*.google.com.br/*",
+ "*://*.google.co.jp/*",
+ "*://*.google.co.kr/*",
+ "*://*.google.co.nz/*",
"*://*.google.co.uk/*",
- "*://*.google.fr/*",
+ "*://*.google.co.za/*",
"*://*.google.de/*",
+ "*://*.google.es/*",
+ "*://*.google.fr/*",
+ "*://*.google.it/*",
+ "*://*.google.ru/*",
"chrome://newtab/",
"chrome://resources/",
"audioCapture",
@@ -39,9 +51,32 @@
"idle",
"management",
"metricsPrivate",
- "tabs"
+ "tabs",
+ "unlimitedStorage"
],
+ "externally_connectable": {
+ "matches": [
+ "*://*.google.at/*",
+ "*://*.google.ca/*",
+ "*://*.google.com/*",
+ "*://*.google.com.au/*",
+ "*://*.google.com.mx/*",
+ "*://*.google.com.br/*",
+ "*://*.google.co.jp/*",
+ "*://*.google.co.kr/*",
+ "*://*.google.co.nz/*",
+ "*://*.google.co.uk/*",
+ "*://*.google.co.za/*",
+ "*://*.google.de/*",
+ "*://*.google.es/*",
+ "*://*.google.fr/*",
+ "*://*.google.it/*",
+ "*://*.google.ru/*",
+ "chrome://newtab/"
+ ]
+ },
+
"import": [
{
"id": "lccekmodgklaepjeofjdjpbminllajkg"
diff --git a/chromium/chrome/browser/resources/hotword/nacl_manager.js b/chromium/chrome/browser/resources/hotword/nacl_manager.js
index db23ece082e..fef432d409b 100644
--- a/chromium/chrome/browser/resources/hotword/nacl_manager.js
+++ b/chromium/chrome/browser/resources/hotword/nacl_manager.js
@@ -10,10 +10,13 @@ cr.define('hotword', function() {
* control of the NaCl plugin, including creation, start, stop, trigger, and
* shutdown.
*
+ * @param {boolean} loggingEnabled Whether audio logging is enabled.
+ * @param {boolean} hotwordStream Whether the audio input stream is from a
+ * hotword stream.
* @constructor
* @extends {cr.EventTarget}
*/
-function NaClManager() {
+function NaClManager(loggingEnabled, hotwordStream) {
/**
* Current state of this manager.
* @private {hotword.NaClManager.ManagerState_}
@@ -55,6 +58,30 @@ function NaClManager() {
* @private {?MediaStream}
*/
this.stream_ = null;
+
+ /**
+ * The mode to start the recognizer in.
+ * @private {?chrome.hotwordPrivate.RecognizerStartMode}
+ */
+ this.startMode_ = hotword.constants.RecognizerStartMode.NORMAL;
+
+ /**
+ * Whether audio logging is enabled.
+ * @private {boolean}
+ */
+ this.loggingEnabled_ = loggingEnabled;
+
+ /**
+ * Whether the audio input stream is from a hotword stream.
+ * @private {boolean}
+ */
+ this.hotwordStream_ = hotwordStream;
+
+ /**
+ * Audio log of X seconds before hotword triggered.
+ * @private {?Object}
+ */
+ this.preambleLog_ = null;
};
/**
@@ -106,13 +133,6 @@ NaClManager.prototype.logPluginLoadResult_ = function(error) {
};
/**
- * @return {boolean} True if the recognizer is in a running state.
- */
-NaClManager.prototype.isRunning = function() {
- return this.recognizerState_ == ManagerState_.RUNNING;
-};
-
-/**
* Set a timeout. Only allow one timeout to exist at any given time.
* @param {!function()} func
* @param {number} timeout
@@ -138,13 +158,31 @@ NaClManager.prototype.clearTimeout_ = function() {
/**
* Starts a stopped or stopping hotword recognizer (NaCl plugin).
+ * @param {hotword.constants.RecognizerStartMode} mode The mode to start the
+ * recognizer in.
*/
-NaClManager.prototype.startRecognizer = function() {
+NaClManager.prototype.startRecognizer = function(mode) {
+ this.startMode_ = mode;
if (this.recognizerState_ == ManagerState_.STOPPED) {
+ this.preambleLog_ = null;
this.recognizerState_ = ManagerState_.STARTING;
- this.sendDataToPlugin_(hotword.constants.NaClPlugin.RESTART);
- this.waitForMessage_(hotword.constants.TimeoutMs.LONG,
- hotword.constants.NaClPlugin.READY_FOR_AUDIO);
+ if (mode == hotword.constants.RecognizerStartMode.NEW_MODEL) {
+ hotword.debug('Starting Recognizer in START training mode');
+ this.sendDataToPlugin_(hotword.constants.NaClPlugin.BEGIN_SPEAKER_MODEL);
+ } else if (mode == hotword.constants.RecognizerStartMode.ADAPT_MODEL) {
+ hotword.debug('Starting Recognizer in ADAPT training mode');
+ this.sendDataToPlugin_(hotword.constants.NaClPlugin.ADAPT_SPEAKER_MODEL);
+ } else {
+ hotword.debug('Starting Recognizer in NORMAL mode');
+ this.sendDataToPlugin_(hotword.constants.NaClPlugin.RESTART);
+ }
+ // Normally, there would be a waitForMessage_(READY_FOR_AUDIO) here.
+ // However, this message is sent the first time audio data is read and in
+ // some cases (ie. using the hotword stream), this won't happen until a
+ // potential hotword trigger is seen. Having a waitForMessage_() would time
+ // out in this case, so just leave it out. This ends up sacrificing a bit of
+ // error detection in the non-hotword-stream case, but I think we can live
+ // with that.
} else if (this.recognizerState_ == ManagerState_.STOPPING) {
// Wait until the plugin is stopped before trying to start it.
this.restartOnStop_ = true;
@@ -158,6 +196,14 @@ NaClManager.prototype.startRecognizer = function() {
* Stops the hotword recognizer.
*/
NaClManager.prototype.stopRecognizer = function() {
+ if (this.recognizerState_ == ManagerState_.STARTING) {
+ // If the recognizer is stopped before it finishes starting, it causes an
+ // assertion to be raised in waitForMessage_() since we're waiting for the
+ // READY_FOR_AUDIO message. Clear the current timeout and expecting message
+ // since we no longer expect it and may never receive it.
+ this.clearTimeout_();
+ this.expectingMessage_ = null;
+ }
this.sendDataToPlugin_(hotword.constants.NaClPlugin.STOP);
this.recognizerState_ = ManagerState_.STOPPING;
this.waitForMessage_(hotword.constants.TimeoutMs.NORMAL,
@@ -165,6 +211,19 @@ NaClManager.prototype.stopRecognizer = function() {
};
/**
+ * Saves the speaker model.
+ */
+NaClManager.prototype.finalizeSpeakerModel = function() {
+ if (this.recognizerState_ == ManagerState_.UNINITIALIZED ||
+ this.recognizerState_ == ManagerState_.ERROR ||
+ this.recognizerState_ == ManagerState_.SHUTDOWN ||
+ this.recognizerState_ == ManagerState_.LOADING) {
+ return;
+ }
+ this.sendDataToPlugin_(hotword.constants.NaClPlugin.FINISH_SPEAKER_MODEL);
+};
+
+/**
* Checks whether the file at the given path exists.
* @param {!string} path Path to a file. Can be any valid URL.
* @return {boolean} True if the patch exists.
@@ -187,7 +246,7 @@ NaClManager.prototype.fileExists_ = function(path) {
/**
* Creates and returns a list of possible languages to check for hotword
* support.
- * @return {!Array.<string>} Array of languages.
+ * @return {!Array<string>} Array of languages.
* @private
*/
NaClManager.prototype.getPossibleLanguages_ = function() {
@@ -287,6 +346,8 @@ NaClManager.prototype.shutdown = function() {
}
this.clearTimeout_();
this.recognizerState_ = ManagerState_.SHUTDOWN;
+ if (this.stream_)
+ this.stream_.stop();
this.stream_ = null;
};
@@ -310,8 +371,8 @@ NaClManager.prototype.sendDataToPlugin_ = function(data) {
* @private
*/
NaClManager.prototype.waitForMessage_ = function(timeout, message) {
- assert(this.expectingMessage_ == null,
- 'Already waiting for message ' + this.expectingMessage_);
+ assert(this.expectingMessage_ == null, 'Cannot wait for message: ' +
+ message + ', already waiting for message ' + this.expectingMessage_);
this.setTimeout_(
function() {
this.recognizerState_ = ManagerState_.ERROR;
@@ -371,6 +432,21 @@ NaClManager.prototype.handleRequestModel_ = function() {
hotword.constants.NaClPlugin.MODEL_PREFIX + this.modelUrl_);
this.waitForMessage_(hotword.constants.TimeoutMs.LONG,
hotword.constants.NaClPlugin.MODEL_LOADED);
+
+ // Configure logging in the plugin. This can be configured any time before
+ // starting the recognizer, and now is as good a time as any.
+ if (this.loggingEnabled_) {
+ this.sendDataToPlugin_(
+ hotword.constants.NaClPlugin.LOG + ':' +
+ hotword.constants.AUDIO_LOG_SECONDS);
+ }
+
+ // If the audio stream is from a hotword stream, tell the plugin.
+ if (this.hotwordStream_) {
+ this.sendDataToPlugin_(
+ hotword.constants.NaClPlugin.DSP + ':' +
+ hotword.constants.HOTWORD_STREAM_TIMEOUT_SECONDS);
+ }
};
/**
@@ -427,7 +503,9 @@ NaClManager.prototype.handleHotwordDetected_ = function() {
this.recognizerState_ = ManagerState_.STOPPING;
this.waitForMessage_(hotword.constants.TimeoutMs.NORMAL,
hotword.constants.NaClPlugin.STOPPED);
- this.dispatchEvent(new Event(hotword.constants.Event.TRIGGER));
+ var event = new Event(hotword.constants.Event.TRIGGER);
+ event.log = this.preambleLog_;
+ this.dispatchEvent(event);
};
/**
@@ -440,17 +518,47 @@ NaClManager.prototype.handleStopped_ = function() {
this.recognizerState_ = ManagerState_.STOPPED;
if (this.restartOnStop_) {
this.restartOnStop_ = false;
- this.startRecognizer();
+ this.startRecognizer(this.startMode_);
}
};
/**
+ * Handle a TIMEOUT message from the plugin.
+ * The plugin sends this message when it thinks the stream is from a DSP and
+ * a hotword wasn't detected within a timeout period after arrival of the first
+ * audio samples.
+ * @private
+ */
+NaClManager.prototype.handleTimeout_ = function() {
+ if (this.recognizerState_ != ManagerState_.RUNNING) {
+ return;
+ }
+ this.recognizerState_ = ManagerState_.STOPPED;
+ this.dispatchEvent(new Event(hotword.constants.Event.TIMEOUT));
+};
+
+/**
+ * Handle a SPEAKER_MODEL_SAVED message from the plugin.
+ * The plugin sends this message after writing the model to a file.
+ * @private
+ */
+NaClManager.prototype.handleSpeakerModelSaved_ = function() {
+ this.dispatchEvent(new Event(hotword.constants.Event.SPEAKER_MODEL_SAVED));
+};
+
+/**
* Handles a message from the NaCl plugin.
* @param {!Event} msg Message from NaCl plugin.
* @private
*/
NaClManager.prototype.handlePluginMessage_ = function(msg) {
if (msg['data']) {
+ if (typeof(msg['data']) == 'object') {
+ // Save the preamble for delivery to the trigger handler when the trigger
+ // message arrives.
+ this.preambleLog_ = msg['data'];
+ return;
+ }
this.receivedMessage_(msg['data']);
switch (msg['data']) {
case hotword.constants.NaClPlugin.REQUEST_MODEL:
@@ -471,6 +579,12 @@ NaClManager.prototype.handlePluginMessage_ = function(msg) {
case hotword.constants.NaClPlugin.STOPPED:
this.handleStopped_();
break;
+ case hotword.constants.NaClPlugin.TIMEOUT:
+ this.handleTimeout_();
+ break;
+ case hotword.constants.NaClPlugin.SPEAKER_MODEL_SAVED:
+ this.handleSpeakerModelSaved_();
+ break;
}
}
};
diff --git a/chromium/chrome/browser/resources/hotword/page_audio_manager.js b/chromium/chrome/browser/resources/hotword/page_audio_manager.js
index 5eac1f02eab..067ff4ec235 100644
--- a/chromium/chrome/browser/resources/hotword/page_audio_manager.js
+++ b/chromium/chrome/browser/resources/hotword/page_audio_manager.js
@@ -23,7 +23,7 @@ cr.define('hotword', function() {
/**
* Mapping between tab ID and port that is connected from the injected
* content script.
- * @private {!Object.<number, Port>}
+ * @private {!Object<number, Port>}
*/
this.portMap_ = {};
@@ -35,7 +35,10 @@ cr.define('hotword', function() {
this.tabCreatedListener_ = this.handleCreatedTab_.bind(this);
this.tabUpdatedListener_ = this.handleUpdatedTab_.bind(this);
this.tabActivatedListener_ = this.handleActivatedTab_.bind(this);
+ this.microphoneStateChangedListener_ =
+ this.handleMicrophoneStateChanged_.bind(this);
this.windowFocusChangedListener_ = this.handleChangedWindow_.bind(this);
+ this.messageListener_ = this.handleMessageFromPage_.bind(this);
// Need to setup listeners on startup, otherwise events that caused the
// event page to start up, will be lost.
@@ -43,6 +46,7 @@ cr.define('hotword', function() {
this.stateManager_.onStatusChanged.addListener(function() {
this.updateListeners_();
+ this.updateTabState_();
}.bind(this));
};
@@ -87,10 +91,21 @@ cr.define('hotword', function() {
];
// TODO(amistry): Get this list from a file in the shared module instead.
var tlds = [
+ 'at',
+ 'ca',
'com',
+ 'com.au',
+ 'com.mx',
+ 'com.br',
+ 'co.jp',
+ 'co.kr',
+ 'co.nz',
'co.uk',
+ 'co.za',
'de',
+ 'es',
'fr',
+ 'it',
'ru'
];
@@ -216,7 +231,7 @@ cr.define('hotword', function() {
},
/**
- * Handles a tab that was just became active.
+ * Handles a tab that has just become active.
* @param {{tabId: number}} info Information about the activated tab.
* @private
*/
@@ -224,6 +239,19 @@ cr.define('hotword', function() {
this.updateTabState_();
},
+ /**
+ * Handles the microphone state changing.
+ * @param {boolean} enabled Whether the microphone is now enabled.
+ * @private
+ */
+ handleMicrophoneStateChanged_: function(enabled) {
+ if (enabled) {
+ this.updateTabState_();
+ return;
+ }
+
+ this.stopHotwording_();
+ },
/**
* Handles a change in Chrome windows.
@@ -337,7 +365,7 @@ cr.define('hotword', function() {
/**
* Starts hotwording if the currently active tab is eligible for hotwording
- * (i.e. google.com).
+ * (e.g. google.com).
* @private
*/
startHotwordingIfEligible_: function() {
@@ -397,6 +425,74 @@ cr.define('hotword', function() {
}
},
+
+ /**
+ * Handles a message directly from the NTP/HP/SERP.
+ * @param {!Object} request Message from the sender.
+ * @param {!MessageSender} sender Information about the sender.
+ * @param {!function(HotwordStatus)} sendResponse Callback to respond
+ * to sender.
+ * @return {boolean} Whether to maintain the port open to call sendResponse.
+ * @private
+ */
+ handleMessageFromPage_: function(request, sender, sendResponse) {
+ switch (request.type) {
+ case CommandFromPage.PAGE_WAKEUP:
+ if (sender.tab && this.isEligibleUrl_(sender.tab.url)) {
+ chrome.hotwordPrivate.getStatus(
+ true /* getOptionalFields */,
+ this.statusDone_.bind(
+ this,
+ request.tab || sender.tab || {incognito: true},
+ sendResponse));
+ return true;
+ }
+ break;
+ case CommandFromPage.CLICKED_OPTIN:
+ chrome.hotwordPrivate.setEnabled(true);
+ break;
+ // User has explicitly clicked 'no thanks'.
+ case CommandFromPage.CLICKED_NO_OPTIN:
+ chrome.hotwordPrivate.setEnabled(false);
+ break;
+ }
+ return false;
+ },
+
+ /**
+ * Sends the response to the tab.
+ * @param {Tab} tab The tab that the request was sent from.
+ * @param {function(HotwordStatus)} sendResponse Callback function to
+ * respond to sender.
+ * @param {HotwordStatus} hotwordStatus Status of the hotword extension.
+ * @private
+ */
+ statusDone_: function(tab, sendResponse, hotwordStatus) {
+ var response = {'doNotShowOptinMessage': true};
+
+ // If always-on is available, then we do not show the promo, as the promo
+ // only works with the sometimes-on pref.
+ if (!tab.incognito && hotwordStatus.available &&
+ !hotwordStatus.enabledSet && !hotwordStatus.alwaysOnAvailable) {
+ response = hotwordStatus;
+ }
+
+ try {
+ sendResponse(response);
+ } catch (err) {
+ // Suppress the exception thrown by sendResponse() when the page doesn't
+ // specify a response callback in the call to
+ // chrome.runtime.sendMessage().
+ // Unfortunately, there doesn't appear to be a way to detect one-way
+ // messages without explicitly saying in the message itself. This
+ // message is defined as a constant in
+ // extensions/renderer/messaging_bindings.cc
+ if (err.message == 'Attempting to use a disconnected port object')
+ return;
+ throw err;
+ }
+ },
+
/**
* Set up event listeners.
* @private
@@ -411,6 +507,12 @@ cr.define('hotword', function() {
chrome.tabs.onActivated.addListener(this.tabActivatedListener_);
chrome.windows.onFocusChanged.addListener(
this.windowFocusChangedListener_);
+ chrome.hotwordPrivate.onMicrophoneStateChanged.addListener(
+ this.microphoneStateChangedListener_);
+ if (chrome.runtime.onMessage.hasListener(this.messageListener_))
+ return;
+ chrome.runtime.onMessageExternal.addListener(
+ this.messageListener_);
},
/**
@@ -424,6 +526,10 @@ cr.define('hotword', function() {
chrome.tabs.onActivated.removeListener(this.tabActivatedListener_);
chrome.windows.onFocusChanged.removeListener(
this.windowFocusChangedListener_);
+ chrome.hotwordPrivate.onMicrophoneStateChanged.removeListener(
+ this.microphoneStateChangedListener_);
+ // Don't remove the Message listener, as we want them listening all
+ // the time,
},
/**
diff --git a/chromium/chrome/browser/resources/hotword/state_manager.js b/chromium/chrome/browser/resources/hotword/state_manager.js
index 5f7824587ec..782939a6ea8 100644
--- a/chromium/chrome/browser/resources/hotword/state_manager.js
+++ b/chromium/chrome/browser/resources/hotword/state_manager.js
@@ -13,11 +13,13 @@ cr.define('hotword', function() {
* triggered.
* @param {!function()} startedCb Callback invoked when the session has
* been started successfully.
+ * @param {function()=} opt_modelSavedCb Callback invoked when the speaker
+ * model has been saved successfully.
* @constructor
* @struct
* @private
*/
- function Session_(source, triggerCb, startedCb) {
+ function Session_(source, triggerCb, startedCb, opt_modelSavedCb) {
/**
* Source of the hotword session request.
* @private {!hotword.constants.SessionSource}
@@ -35,6 +37,12 @@ cr.define('hotword', function() {
* @private {?function()}
*/
this.startedCb_ = startedCb;
+
+ /**
+ * Callback invoked when the session has been started successfully.
+ * @private {?function()}
+ */
+ this.speakerModelSavedCb_ = opt_modelSavedCb;
}
/**
@@ -64,11 +72,17 @@ cr.define('hotword', function() {
/**
* Currently active hotwording sessions.
- * @private {!Array.<Session_>}
+ * @private {!Array<Session_>}
*/
this.sessions_ = [];
/**
+ * The mode to start the recognizer in.
+ * @private {!hotword.constants.RecognizerStartMode}
+ */
+ this.startMode_ = hotword.constants.RecognizerStartMode.NORMAL;
+
+ /**
* Event that fires when the hotwording status has changed.
* @type {!ChromeEvent}
*/
@@ -87,6 +101,7 @@ cr.define('hotword', function() {
* @private
*/
this.idleStateChangedListener_ = this.handleIdleStateChanged_.bind(this);
+ this.startupListener_ = this.handleStartup_.bind(this);
/**
* Whether this user is locked.
@@ -94,13 +109,51 @@ cr.define('hotword', function() {
*/
this.isLocked_ = false;
+ /**
+ * Current state of audio logging.
+ * This is tracked separately from hotwordStatus_ because we need to restart
+ * the hotword detector when this value changes.
+ * @private {boolean}
+ */
+ this.loggingEnabled_ = false;
+
+ /**
+ * Current state of training.
+ * This is tracked separately from |hotwordStatus_| because we need to
+ * restart the hotword detector when this value changes.
+ * @private {!boolean}
+ */
+ this.trainingEnabled_ = false;
+
+ /**
+ * Helper class to keep this extension alive while the hotword detector is
+ * running in always-on mode.
+ * @private {!hotword.KeepAlive}
+ */
+ this.keepAlive_ = new hotword.KeepAlive();
+
// Get the initial status.
chrome.hotwordPrivate.getStatus(this.handleStatus_.bind(this));
// Setup the chime and insert into the page.
+ // Set preload=none to prevent an audio output stream from being created
+ // when the extension loads.
+ this.chime_.preload = 'none';
this.chime_.src = chrome.extension.getURL(
hotword.constants.SHARED_MODULE_ROOT + '/audio/chime.wav');
document.body.appendChild(this.chime_);
+
+ // In order to remove this listener, it must first be added. This handles
+ // the case on first Chrome startup where this event is never registered,
+ // so can't be removed when it's determined that hotwording is disabled.
+ // Why not only remove the listener if it exists? Extension API events have
+ // two parts to them, the Javascript listeners, and a browser-side component
+ // that wakes up the extension if it's an event page. The browser-side
+ // wake-up event is only removed when the number of javascript listeners
+ // becomes 0. To clear the browser wake-up event, a listener first needs to
+ // be added, then removed in order to drop the count to 0 and remove the
+ // event.
+ chrome.runtime.onStartup.addListener(this.startupListener_);
}
/**
@@ -143,6 +196,13 @@ cr.define('hotword', function() {
hotword.constants.UmaMediaStreamOpenResult.INVALID_SECURITY_ORIGIN
};
+ var UmaTriggerSources_ = {
+ 'launcher': hotword.constants.UmaTriggerSource.LAUNCHER,
+ 'ntp': hotword.constants.UmaTriggerSource.NTP_GOOGLE_COM,
+ 'always': hotword.constants.UmaTriggerSource.ALWAYS_ON,
+ 'training': hotword.constants.UmaTriggerSource.TRAINING
+ };
+
StateManager.prototype = {
/**
* Request status details update. Intended to be called from the
@@ -169,7 +229,8 @@ cr.define('hotword', function() {
*/
isAlwaysOnEnabled: function() {
assert(this.hotwordStatus_, 'No hotword status (isAlwaysOnEnabled)');
- return this.hotwordStatus_.alwaysOnEnabled;
+ return this.hotwordStatus_.alwaysOnEnabled &&
+ !this.hotwordStatus_.trainingEnabled;
},
/**
@@ -205,24 +266,44 @@ cr.define('hotword', function() {
if (this.hotwordStatus_.enabled ||
this.hotwordStatus_.alwaysOnEnabled ||
this.hotwordStatus_.trainingEnabled) {
+ // Detect changes to audio logging and kill the detector if that setting
+ // has changed.
+ if (this.hotwordStatus_.audioLoggingEnabled != this.loggingEnabled_)
+ this.shutdownDetector_();
+ this.loggingEnabled_ = this.hotwordStatus_.audioLoggingEnabled;
+
+ // If the training state has changed, we need to first shut down the
+ // detector so that we can restart in a different mode.
+ if (this.hotwordStatus_.trainingEnabled != this.trainingEnabled_)
+ this.shutdownDetector_();
+ this.trainingEnabled_ = this.hotwordStatus_.trainingEnabled;
+
// Start the detector if there's a session and the user is unlocked, and
- // shut it down otherwise.
- if (this.sessions_.length && !this.isLocked_)
+ // stops it otherwise.
+ if (this.sessions_.length && !this.isLocked_ &&
+ this.hotwordStatus_.userIsActive) {
this.startDetector_();
- else
+ } else {
this.shutdownDetector_();
+ }
if (!chrome.idle.onStateChanged.hasListener(
this.idleStateChangedListener_)) {
chrome.idle.onStateChanged.addListener(
this.idleStateChangedListener_);
}
+ if (!chrome.runtime.onStartup.hasListener(this.startupListener_))
+ chrome.runtime.onStartup.addListener(this.startupListener_);
} else {
// Not enabled. Shut down if running.
this.shutdownDetector_();
chrome.idle.onStateChanged.removeListener(
this.idleStateChangedListener_);
+ // If hotwording isn't enabled, don't start this component extension on
+ // Chrome startup. If a user enables hotwording, the status change
+ // event will be fired and the onStartup event will be registered.
+ chrome.runtime.onStartup.removeListener(this.startupListener_);
}
},
@@ -239,21 +320,36 @@ cr.define('hotword', function() {
if (!this.pluginManager_) {
this.state_ = State_.STARTING;
- this.pluginManager_ = new hotword.NaClManager();
+ var isHotwordStream = this.isAlwaysOnEnabled() &&
+ this.hotwordStatus_.hotwordHardwareAvailable;
+ this.pluginManager_ = new hotword.NaClManager(this.loggingEnabled_,
+ isHotwordStream);
this.pluginManager_.addEventListener(hotword.constants.Event.READY,
this.onReady_.bind(this));
this.pluginManager_.addEventListener(hotword.constants.Event.ERROR,
this.onError_.bind(this));
this.pluginManager_.addEventListener(hotword.constants.Event.TRIGGER,
this.onTrigger_.bind(this));
+ this.pluginManager_.addEventListener(hotword.constants.Event.TIMEOUT,
+ this.onTimeout_.bind(this));
+ this.pluginManager_.addEventListener(
+ hotword.constants.Event.SPEAKER_MODEL_SAVED,
+ this.onSpeakerModelSaved_.bind(this));
chrome.runtime.getPlatformInfo(function(platform) {
var naclArch = platform.nacl_arch;
// googDucking set to false so that audio output level from other tabs
// is not affected when hotword is enabled. https://crbug.com/357773
// content/common/media/media_stream_options.cc
+ // When always-on is enabled, request the hotword stream.
+ // Optional because we allow power users to bypass the hardware
+ // detection via a flag, and hence the hotword stream may not be
+ // available.
var constraints = /** @type {googMediaStreamConstraints} */
- ({audio: {optional: [{googDucking: false}]}});
+ ({audio: {optional: [
+ { googDucking: false },
+ { googHotword: this.isAlwaysOnEnabled() }
+ ]}});
navigator.webkitGetUserMedia(
/** @type {MediaStreamConstraints} */ (constraints),
function(stream) {
@@ -261,6 +357,15 @@ cr.define('hotword', function() {
hotword.constants.UmaMetrics.MEDIA_STREAM_RESULT,
hotword.constants.UmaMediaStreamOpenResult.SUCCESS,
hotword.constants.UmaMediaStreamOpenResult.MAX);
+ // The detector could have been shut down before the stream
+ // finishes opening.
+ if (this.pluginManager_ == null) {
+ stream.getAudioTracks()[0].stop();
+ return;
+ }
+
+ if (this.isAlwaysOnEnabled())
+ this.keepAlive_.start();
if (!this.pluginManager_.initialize(naclArch, stream)) {
this.state_ = State_.ERROR;
this.shutdownPluginManager_();
@@ -296,7 +401,9 @@ cr.define('hotword', function() {
assert(this.pluginManager_, 'No NaCl plugin loaded');
if (this.state_ != State_.RUNNING) {
this.state_ = State_.RUNNING;
- this.pluginManager_.startRecognizer();
+ if (this.isAlwaysOnEnabled())
+ this.keepAlive_.start();
+ this.pluginManager_.startRecognizer(this.startMode_);
}
for (var i = 0; i < this.sessions_.length; i++) {
var session = this.sessions_[i];
@@ -308,10 +415,23 @@ cr.define('hotword', function() {
},
/**
+ * Stops the hotword detector, if it's running.
+ * @private
+ */
+ stopDetector_: function() {
+ this.keepAlive_.stop();
+ if (this.pluginManager_ && this.state_ == State_.RUNNING) {
+ this.state_ = State_.STOPPED;
+ this.pluginManager_.stopRecognizer();
+ }
+ },
+
+ /**
* Shuts down and removes the plugin manager, if it exists.
* @private
*/
shutdownPluginManager_: function() {
+ this.keepAlive_.stop();
if (this.pluginManager_) {
this.pluginManager_.shutdown();
this.pluginManager_ = null;
@@ -328,6 +448,20 @@ cr.define('hotword', function() {
},
/**
+ * Finalizes the speaker model. Assumes the plugin has been loaded and
+ * started.
+ */
+ finalizeSpeakerModel: function() {
+ assert(this.pluginManager_,
+ 'Cannot finalize speaker model: No NaCl plugin loaded');
+ if (this.state_ != State_.RUNNING) {
+ hotword.debug('Cannot finalize speaker model: NaCl plugin not started');
+ return;
+ }
+ this.pluginManager_.finalizeSpeakerModel();
+ },
+
+ /**
* Handle the hotword plugin being ready to start.
* @private
*/
@@ -353,9 +487,11 @@ cr.define('hotword', function() {
/**
* Handle hotword triggering.
+ * @param {!Event} event Event containing audio log data.
* @private
*/
- onTrigger_: function() {
+ onTrigger_: function(event) {
+ this.keepAlive_.stop();
hotword.debug('Hotword triggered!');
chrome.metricsPrivate.recordUserAction(
hotword.constants.UmaMetrics.TRIGGER);
@@ -370,8 +506,47 @@ cr.define('hotword', function() {
// order to restart the detector.
if (this.sessions_.length) {
var session = this.sessions_.pop();
- if (session.triggerCb_)
- session.triggerCb_();
+ session.triggerCb_(event.log);
+
+ hotword.metrics.recordEnum(
+ hotword.constants.UmaMetrics.TRIGGER_SOURCE,
+ UmaTriggerSources_[session.source_],
+ hotword.constants.UmaTriggerSource.MAX);
+ }
+
+ // If we're in always-on mode, shut down the hotword detector. The hotword
+ // stream requires that we close and re-open it after a trigger, and the
+ // only way to accomplish this is to shut everything down.
+ if (this.isAlwaysOnEnabled())
+ this.shutdownDetector_();
+ },
+
+ /**
+ * Handle hotword timeout.
+ * @private
+ */
+ onTimeout_: function() {
+ hotword.debug('Hotword timeout!');
+
+ // We get this event when the hotword detector thinks there's a false
+ // trigger. In this case, we need to shut down and restart the detector to
+ // re-arm the DSP.
+ this.shutdownDetector_();
+ this.updateStateFromStatus_();
+ },
+
+ /**
+ * Handle speaker model saved.
+ * @private
+ */
+ onSpeakerModelSaved_: function() {
+ hotword.debug('Speaker model saved!');
+
+ if (this.sessions_.length) {
+ // Only call the callback of the the top session.
+ var session = this.sessions_[this.sessions_.length - 1];
+ if (session.speakerModelSavedCb_)
+ session.speakerModelSavedCb_();
}
},
@@ -397,12 +572,22 @@ cr.define('hotword', function() {
* @param {!function()} startedCb Callback invoked when the session has
* been started successfully.
* @param {!function()} triggerCb Callback invoked when the hotword has
- * triggered.
+ * @param {function()=} modelSavedCb Callback invoked when the speaker model
+ * has been saved.
+ * @param {hotword.constants.RecognizerStartMode=} opt_mode The mode to
+ * start the recognizer in.
*/
- startSession: function(source, startedCb, triggerCb) {
+ startSession: function(source, startedCb, triggerCb,
+ opt_modelSavedCb, opt_mode) {
+ if (this.isTrainingEnabled() && opt_mode) {
+ this.startMode_ = opt_mode;
+ } else {
+ this.startMode_ = hotword.constants.RecognizerStartMode.NORMAL;
+ }
hotword.debug('Starting session for source: ' + source);
this.removeSession_(source);
- this.sessions_.push(new Session_(source, triggerCb, startedCb));
+ this.sessions_.push(new Session_(source, triggerCb, startedCb,
+ opt_modelSavedCb));
this.updateStateFromStatus_();
},
@@ -414,6 +599,10 @@ cr.define('hotword', function() {
stopSession: function(source) {
hotword.debug('Stopping session for source: ' + source);
this.removeSession_(source);
+ // If this is a training session then switch the start mode back to
+ // normal.
+ if (source == hotword.constants.SessionSource.TRAINING)
+ this.startMode_ = hotword.constants.RecognizerStartMode.NORMAL;
this.updateStateFromStatus_();
},
@@ -432,6 +621,14 @@ cr.define('hotword', function() {
if (oldLocked != this.isLocked_)
this.updateStateFromStatus_();
+ },
+
+ /**
+ * Handles a chrome.runtime.onStartup event.
+ * @private
+ */
+ handleStartup_: function() {
+ updateStatus();
}
};
diff --git a/chromium/chrome/browser/resources/hotword/training_manager.js b/chromium/chrome/browser/resources/hotword/training_manager.js
index 9130e2d0249..15752eb1590 100644
--- a/chromium/chrome/browser/resources/hotword/training_manager.js
+++ b/chromium/chrome/browser/resources/hotword/training_manager.js
@@ -14,11 +14,124 @@ cr.define('hotword', function() {
* @extends {hotword.BaseSessionManager}
*/
function TrainingManager(stateManager) {
+ /**
+ * Chrome event listeners. Saved so that they can be de-registered when
+ * hotwording is disabled.
+ * @private
+ */
+ this.finalizedSpeakerModelListener_ =
+ this.handleFinalizeSpeakerModel_.bind(this);
+
hotword.BaseSessionManager.call(this,
stateManager,
hotword.constants.SessionSource.TRAINING);
}
+ /**
+ * Handles a success event on mounting the file system event and deletes
+ * the user data files.
+ * @param {FileSystem} fs The FileSystem object.
+ * @private
+ */
+ TrainingManager.deleteFiles_ = function(fs) {
+ fs.root.getFile(hotword.constants.SPEAKER_MODEL_FILE_NAME, {create: false},
+ TrainingManager.deleteFile_, TrainingManager.fileErrorHandler_);
+
+ for (var i = 0; i < hotword.constants.NUM_TRAINING_UTTERANCES; ++i) {
+ fs.root.getFile(hotword.constants.UTTERANCE_FILE_PREFIX + i +
+ hotword.constants.UTTERANCE_FILE_EXTENSION,
+ {create: false},
+ TrainingManager.deleteFile_, TrainingManager.fileErrorHandler_);
+ }
+ };
+
+ /**
+ * Deletes a file.
+ * @param {FileEntry} fileEntry The FileEntry object.
+ * @private
+ */
+ TrainingManager.deleteFile_ = function(fileEntry) {
+ if (fileEntry.isFile) {
+ hotword.debug('File found: ' + fileEntry.fullPath);
+ if (hotword.DEBUG || window.localStorage['hotword.DEBUG']) {
+ fileEntry.getMetadata(function(md) {
+ hotword.debug('File size: ' + md.size);
+ });
+ }
+ fileEntry.remove(function() {
+ hotword.debug('File removed: ' + fileEntry.fullPath);
+ }, TrainingManager.fileErrorHandler_);
+ }
+ };
+
+ /**
+ * Handles a failure event on mounting the file system event.
+ * @param {FileError} e The FileError object.
+ * @private
+ */
+ TrainingManager.fileErrorHandler_ = function(e) {
+ hotword.debug('File error: ' + e.code);
+ };
+
+ /**
+ * Handles a failure event on checking for the existence of the speaker model.
+ * @param {FileError} e The FileError object.
+ * @private
+ */
+ TrainingManager.sendNoSpeakerModelResponse_ = function(e) {
+ chrome.hotwordPrivate.speakerModelExistsResult(false);
+ };
+
+ /**
+ * Handles a success event on mounting the file system and checks for the
+ * existence of the speaker model.
+ * @param {FileSystem} fs The FileSystem object.
+ * @private
+ */
+ TrainingManager.speakerModelExists_ = function(fs) {
+ fs.root.getFile(hotword.constants.SPEAKER_MODEL_FILE_NAME, {create: false},
+ TrainingManager.sendSpeakerModelExistsResponse_,
+ TrainingManager.sendNoSpeakerModelResponse_);
+ };
+
+ /**
+ * Sends a response through the HotwordPrivateApi indicating whether
+ * the speaker model exists.
+ * @param {FileEntry} fileEntry The FileEntry object.
+ * @private
+ */
+ TrainingManager.sendSpeakerModelExistsResponse_ = function(fileEntry) {
+ if (fileEntry.isFile) {
+ hotword.debug('File found: ' + fileEntry.fullPath);
+ if (hotword.DEBUG || window.localStorage['hotword.DEBUG']) {
+ fileEntry.getMetadata(function(md) {
+ hotword.debug('File size: ' + md.size);
+ });
+ }
+ }
+ chrome.hotwordPrivate.speakerModelExistsResult(fileEntry.isFile);
+ };
+
+ /**
+ * Handles a request to delete the speaker model.
+ */
+ TrainingManager.handleDeleteSpeakerModel = function() {
+ window.webkitRequestFileSystem(PERSISTENT,
+ hotword.constants.FILE_SYSTEM_SIZE_BYTES,
+ TrainingManager.deleteFiles_,
+ TrainingManager.fileErrorHandler_);
+ };
+
+ /**
+ * Handles a request for the speaker model existence.
+ */
+ TrainingManager.handleSpeakerModelExists = function() {
+ window.webkitRequestFileSystem(PERSISTENT,
+ hotword.constants.FILE_SYSTEM_SIZE_BYTES,
+ TrainingManager.speakerModelExists_,
+ TrainingManager.fileErrorHandler_);
+ };
+
TrainingManager.prototype = {
__proto__: hotword.BaseSessionManager.prototype,
@@ -30,17 +143,59 @@ cr.define('hotword', function() {
/** @override */
updateListeners: function() {
hotword.BaseSessionManager.prototype.updateListeners.call(this);
- if (this.enabled())
- this.startSession_();
+
+ if (this.enabled()) {
+ // Detect when the speaker model needs to be finalized.
+ if (!chrome.hotwordPrivate.onFinalizeSpeakerModel.hasListener(
+ this.finalizedSpeakerModelListener_)) {
+ chrome.hotwordPrivate.onFinalizeSpeakerModel.addListener(
+ this.finalizedSpeakerModelListener_);
+ }
+ this.startSession(hotword.constants.RecognizerStartMode.NEW_MODEL);
+ } else {
+ chrome.hotwordPrivate.onFinalizeSpeakerModel.removeListener(
+ this.finalizedSpeakerModelListener_);
+ }
},
/** @override */
- handleHotwordTrigger: function() {
+ handleHotwordTrigger: function(log) {
if (this.enabled()) {
- hotword.BaseSessionManager.prototype.handleHotwordTrigger.call(this);
- this.startSession_();
+ hotword.BaseSessionManager.prototype.handleHotwordTrigger.call(
+ this, log);
+ this.startSession(hotword.constants.RecognizerStartMode.ADAPT_MODEL);
}
- }
+ },
+
+ /** @override */
+ startSession: function(opt_mode) {
+ this.stateManager.startSession(
+ this.sessionSource_,
+ function() {
+ chrome.hotwordPrivate.setHotwordSessionState(true, function() {});
+ },
+ this.handleHotwordTrigger.bind(this),
+ this.handleSpeakerModelSaved_.bind(this),
+ opt_mode);
+ },
+
+ /**
+ * Handles a hotwordPrivate.onFinalizeSpeakerModel event.
+ * @private
+ */
+ handleFinalizeSpeakerModel_: function() {
+ if (this.enabled())
+ this.stateManager.finalizeSpeakerModel();
+ },
+
+ /**
+ * Handles a hotwordPrivate.onFinalizeSpeakerModel event.
+ * @private
+ */
+ handleSpeakerModelSaved_: function() {
+ if (this.enabled())
+ chrome.hotwordPrivate.notifySpeakerModelSaved();
+ },
};
return {
diff --git a/chromium/chrome/browser/resources/hotword_audio_verification/OWNERS b/chromium/chrome/browser/resources/hotword_audio_verification/OWNERS
new file mode 100644
index 00000000000..99e28241baa
--- /dev/null
+++ b/chromium/chrome/browser/resources/hotword_audio_verification/OWNERS
@@ -0,0 +1 @@
+kcarattini@chromium.org
diff --git a/chromium/chrome/browser/resources/hotword_audio_verification/event_page.js b/chromium/chrome/browser/resources/hotword_audio_verification/event_page.js
index 45d0d773820..f63ae57d767 100644
--- a/chromium/chrome/browser/resources/hotword_audio_verification/event_page.js
+++ b/chromium/chrome/browser/resources/hotword_audio_verification/event_page.js
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-var appId = 'hotword_audio_verification_app';
+var appId = 'hotword_audio_verification';
chrome.app.runtime.onLaunched.addListener(function() {
// We need to focus the window if it already exists, since it
@@ -22,9 +22,9 @@ chrome.app.runtime.onLaunched.addListener(function() {
'resizable': false,
'hidden': true,
'id': appId,
- 'bounds': {
- 'width': 800,
- 'height': 600
+ 'innerBounds': {
+ 'width': 784,
+ 'height': 448
}
});
});
diff --git a/chromium/chrome/browser/resources/hotword_audio_verification/flow.js b/chromium/chrome/browser/resources/hotword_audio_verification/flow.js
index abf7429d037..9c0c4b01600 100644
--- a/chromium/chrome/browser/resources/hotword_audio_verification/flow.js
+++ b/chromium/chrome/browser/resources/hotword_audio_verification/flow.js
@@ -5,10 +5,10 @@
(function() {
// Correspond to steps in the hotword opt-in flow.
- /** @const */ var HOTWORD_AUDIO_HISTORY = 'hotword-audio-history-container';
- /** @const */ var HOTWORD_ONLY_START = 'hotword-only-container';
+ /** @const */ var START = 'start-container';
+ /** @const */ var AUDIO_HISTORY = 'audio-history-container';
/** @const */ var SPEECH_TRAINING = 'speech-training-container';
- /** @const */ var FINISHED = 'finished-container';
+ /** @const */ var FINISH = 'finish-container';
/**
* These flows correspond to the three LaunchModes as defined in
@@ -17,9 +17,9 @@
* @const
*/
var FLOWS = [
- [HOTWORD_ONLY_START, FINISHED],
- [HOTWORD_AUDIO_HISTORY, SPEECH_TRAINING, FINISHED],
- [SPEECH_TRAINING, FINISHED]
+ [START, SPEECH_TRAINING, FINISH],
+ [START, AUDIO_HISTORY, SPEECH_TRAINING, FINISH],
+ [SPEECH_TRAINING, FINISH]
];
/**
@@ -34,6 +34,16 @@
};
/**
+ * The training state.
+ * @enum {string}
+ */
+ var TrainingState = {
+ RESET: 'reset',
+ TIMEOUT: 'timeout',
+ ERROR: 'error',
+ };
+
+ /**
* Class to control the page flow of the always-on hotword and
* Audio History opt-in process.
* @constructor
@@ -43,7 +53,7 @@
this.currentFlow_ = [];
/**
- * Whether this flow is currently in the process of training a voice model.
+ * The mode that this app was launched in.
* @private {LaunchMode}
*/
this.launchMode_ = LaunchMode.HOTWORD_AND_AUDIO_HISTORY;
@@ -55,19 +65,73 @@
this.training_ = false;
/**
+ * The current training state.
+ * @private {?TrainingState}
+ */
+ this.trainingState_ = null;
+
+ /**
+ * Whether an expected hotword trigger has been received, indexed by
+ * training step.
+ * @private {boolean[]}
+ */
+ this.hotwordTriggerReceived_ = [];
+
+ /**
* Prefix of the element ids for the page that is currently training.
* @private {string}
*/
- this.trainingPagePrefix_ = '';
+ this.trainingPagePrefix_ = 'speech-training';
+
+ /**
+ * Whether the speaker model for this flow has been finalized.
+ * @private {boolean}
+ */
+ this.speakerModelFinalized_ = false;
+
+ /**
+ * ID of the currently active timeout.
+ * @private {?number}
+ */
+ this.timeoutId_ = null;
+
+ /**
+ * Listener for the speakerModelSaved event.
+ * @private {Function}
+ */
+ this.speakerModelFinalizedListener_ =
+ this.onSpeakerModelFinalized_.bind(this);
+
+ /**
+ * Listener for the hotword trigger event.
+ * @private {Function}
+ */
+ this.hotwordTriggerListener_ =
+ this.handleHotwordTrigger_.bind(this);
+
+ // Listen for the user locking the screen.
+ chrome.idle.onStateChanged.addListener(
+ this.handleIdleStateChanged_.bind(this));
+
+ // Listen for hotword settings changes. This used to detect when the user
+ // switches to a different profile.
+ if (chrome.hotwordPrivate.onEnabledChanged) {
+ chrome.hotwordPrivate.onEnabledChanged.addListener(
+ this.handleEnabledChanged_.bind(this));
+ }
}
/**
- * Advances the current step.
+ * Advances the current step. Begins training if the speech-training
+ * page has been reached.
*/
Flow.prototype.advanceStep = function() {
this.currentStepIndex_++;
- if (this.currentStepIndex_ < this.currentFlow_.length)
+ if (this.currentStepIndex_ < this.currentFlow_.length) {
+ if (this.currentFlow_[this.currentStepIndex_] == SPEECH_TRAINING)
+ this.startTraining();
this.showStep_.apply(this);
+ }
};
/**
@@ -87,17 +151,15 @@
return;
this.training_ = true;
- if (this.launchMode_ == LaunchMode.HOTWORD_ONLY) {
- this.trainingPagePrefix_ = 'hotword-only';
- } else if (this.launchMode_ == LaunchMode.HOTWORD_AND_AUDIO_HISTORY ||
- this.launchMode_ == LaunchMode.RETRAIN) {
- this.trainingPagePrefix_ = 'speech-training';
- }
- if (chrome.hotwordPrivate.onHotwordTriggered) {
+ if (chrome.hotwordPrivate.onHotwordTriggered &&
+ !chrome.hotwordPrivate.onHotwordTriggered.hasListener(
+ this.hotwordTriggerListener_)) {
chrome.hotwordPrivate.onHotwordTriggered.addListener(
- this.handleHotwordTrigger_.bind(this));
+ this.hotwordTriggerListener_);
}
+
+ this.waitForHotwordTrigger_(0);
if (chrome.hotwordPrivate.startTraining)
chrome.hotwordPrivate.startTraining();
};
@@ -112,27 +174,116 @@
this.training_ = false;
if (chrome.hotwordPrivate.onHotwordTriggered) {
chrome.hotwordPrivate.onHotwordTriggered.
- removeListener(this.handleHotwordTrigger_);
+ removeListener(this.hotwordTriggerListener_);
}
if (chrome.hotwordPrivate.stopTraining)
chrome.hotwordPrivate.stopTraining();
};
/**
- * Handles the speaker model finalized event.
+ * Attempts to enable audio history for the signed-in account.
+ */
+ Flow.prototype.enableAudioHistory = function() {
+ // Update UI
+ $('audio-history-agree').disabled = true;
+ $('audio-history-cancel').disabled = true;
+
+ $('audio-history-error').hidden = true;
+ $('audio-history-wait').hidden = false;
+
+ if (chrome.hotwordPrivate.setAudioHistoryEnabled) {
+ chrome.hotwordPrivate.setAudioHistoryEnabled(
+ true, this.onAudioHistoryRequestCompleted_.bind(this));
+ }
+ };
+
+ // ---- private methods:
+
+ /**
+ * Shows an error if the audio history setting was not enabled successfully.
+ * @private
*/
- Flow.prototype.onSpeakerModelFinalized = function() {
+ Flow.prototype.handleAudioHistoryError_ = function() {
+ $('audio-history-agree').disabled = false;
+ $('audio-history-cancel').disabled = false;
+
+ $('audio-history-wait').hidden = true;
+ $('audio-history-error').hidden = false;
+
+ // Set a timeout before focusing the Enable button so that screenreaders
+ // have time to announce the error first.
+ this.setTimeout_(function() {
+ $('audio-history-agree').focus();
+ }.bind(this), 50);
+ };
+
+ /**
+ * Callback for when an audio history request completes.
+ * @param {chrome.hotwordPrivate.AudioHistoryState} state The audio history
+ * request state.
+ * @private
+ */
+ Flow.prototype.onAudioHistoryRequestCompleted_ = function(state) {
+ if (!state.success || !state.enabled) {
+ this.handleAudioHistoryError_();
+ return;
+ }
+
+ this.advanceStep();
+ };
+
+ /**
+ * Shows an error if the speaker model has not been finalized.
+ * @private
+ */
+ Flow.prototype.handleSpeakerModelFinalizedError_ = function() {
+ if (!this.training_)
+ return;
+
+ if (this.speakerModelFinalized_)
+ return;
+
+ this.updateTrainingState_(TrainingState.ERROR);
this.stopTraining();
+ };
- if (chrome.hotwordPrivate.setAudioLoggingEnabled)
- chrome.hotwordPrivate.setAudioLoggingEnabled(true, function() {});
+ /**
+ * Handles the speaker model finalized event.
+ * @private
+ */
+ Flow.prototype.onSpeakerModelFinalized_ = function() {
+ this.speakerModelFinalized_ = true;
+ if (chrome.hotwordPrivate.onSpeakerModelSaved) {
+ chrome.hotwordPrivate.onSpeakerModelSaved.removeListener(
+ this.speakerModelFinalizedListener_);
+ }
+ this.stopTraining();
+ this.setTimeout_(this.finishFlow_.bind(this), 2000);
+ };
+ /**
+ * Completes the training process.
+ * @private
+ */
+ Flow.prototype.finishFlow_ = function() {
if (chrome.hotwordPrivate.setHotwordAlwaysOnSearchEnabled) {
chrome.hotwordPrivate.setHotwordAlwaysOnSearchEnabled(true,
this.advanceStep.bind(this));
}
};
+ /**
+ * Handles a user clicking on the retry button.
+ */
+ Flow.prototype.handleRetry = function() {
+ if (!(this.trainingState_ == TrainingState.TIMEOUT ||
+ this.trainingState_ == TrainingState.ERROR))
+ return;
+
+ this.startTraining();
+ this.updateTrainingState_(TrainingState.RESET);
+ };
+
// ---- private methods:
/**
@@ -143,12 +294,176 @@
if (!this.training_)
return;
+ // Listen for the success event from the NaCl module.
+ if (chrome.hotwordPrivate.onSpeakerModelSaved &&
+ !chrome.hotwordPrivate.onSpeakerModelSaved.hasListener(
+ this.speakerModelFinalizedListener_)) {
+ chrome.hotwordPrivate.onSpeakerModelSaved.addListener(
+ this.speakerModelFinalizedListener_);
+ }
+
+ this.speakerModelFinalized_ = false;
+ this.setTimeout_(this.handleSpeakerModelFinalizedError_.bind(this), 30000);
if (chrome.hotwordPrivate.finalizeSpeakerModel)
chrome.hotwordPrivate.finalizeSpeakerModel();
+ };
- // TODO(kcarattini): Implement a notification that speaker model has been
- // finalized instead of setting a timeout.
- setTimeout(this.onSpeakerModelFinalized.bind(this), 2000);
+ /**
+ * Returns the current training step.
+ * @param {string} curStepClassName The name of the class of the current
+ * training step.
+ * @return {Object} The current training step, its index, and an array of
+ * all training steps. Any of these can be undefined.
+ * @private
+ */
+ Flow.prototype.getCurrentTrainingStep_ = function(curStepClassName) {
+ var steps =
+ $(this.trainingPagePrefix_ + '-training').querySelectorAll('.train');
+ var curStep =
+ $(this.trainingPagePrefix_ + '-training').querySelector('.listening');
+
+ return {current: curStep,
+ index: Array.prototype.indexOf.call(steps, curStep),
+ steps: steps};
+ };
+
+ /**
+ * Updates the training state.
+ * @param {TrainingState} state The training state.
+ * @private
+ */
+ Flow.prototype.updateTrainingState_ = function(state) {
+ this.trainingState_ = state;
+ this.updateErrorUI_();
+ };
+
+ /**
+ * Waits two minutes and then checks for a training error.
+ * @param {number} index The index of the training step.
+ * @private
+ */
+ Flow.prototype.waitForHotwordTrigger_ = function(index) {
+ if (!this.training_)
+ return;
+
+ this.hotwordTriggerReceived_[index] = false;
+ this.setTimeout_(this.handleTrainingTimeout_.bind(this, index), 120000);
+ };
+
+ /**
+ * Checks for and handles a training error.
+ * @param {number} index The index of the training step.
+ * @private
+ */
+ Flow.prototype.handleTrainingTimeout_ = function(index) {
+ if (this.hotwordTriggerReceived_[index])
+ return;
+
+ this.timeoutTraining_();
+ };
+
+ /**
+ * Times out training and updates the UI to show a "retry" message, if
+ * currently training.
+ * @private
+ */
+ Flow.prototype.timeoutTraining_ = function() {
+ if (!this.training_)
+ return;
+
+ this.clearTimeout_();
+ this.updateTrainingState_(TrainingState.TIMEOUT);
+ this.stopTraining();
+ };
+
+ /**
+ * Sets a timeout. If any timeout is active, clear it.
+ * @param {Function} func The function to invoke when the timeout occurs.
+ * @param {number} delay Timeout delay in milliseconds.
+ * @private
+ */
+ Flow.prototype.setTimeout_ = function(func, delay) {
+ this.clearTimeout_();
+ this.timeoutId_ = setTimeout(function() {
+ this.timeoutId_ = null;
+ func();
+ }, delay);
+ };
+
+ /**
+ * Clears any currently active timeout.
+ * @private
+ */
+ Flow.prototype.clearTimeout_ = function() {
+ if (this.timeoutId_ != null) {
+ clearTimeout(this.timeoutId_);
+ this.timeoutId_ = null;
+ }
+ };
+
+ /**
+ * Updates the training error UI.
+ * @private
+ */
+ Flow.prototype.updateErrorUI_ = function() {
+ if (!this.training_)
+ return;
+
+ var trainingSteps = this.getCurrentTrainingStep_('listening');
+ var steps = trainingSteps.steps;
+
+ $(this.trainingPagePrefix_ + '-toast').hidden =
+ this.trainingState_ != TrainingState.TIMEOUT;
+ if (this.trainingState_ == TrainingState.RESET) {
+ // We reset the training to begin at the first step.
+ // The first step is reset to 'listening', while the rest
+ // are reset to 'not-started'.
+ var prompt = loadTimeData.getString('trainingFirstPrompt');
+ for (var i = 0; i < steps.length; ++i) {
+ steps[i].classList.remove('recorded');
+ if (i == 0) {
+ steps[i].classList.remove('not-started');
+ steps[i].classList.add('listening');
+ } else {
+ steps[i].classList.add('not-started');
+ if (i == steps.length - 1)
+ prompt = loadTimeData.getString('trainingLastPrompt');
+ else
+ prompt = loadTimeData.getString('trainingMiddlePrompt');
+ }
+ steps[i].querySelector('.text').textContent = prompt;
+ }
+
+ // Reset the buttonbar.
+ $(this.trainingPagePrefix_ + '-processing').hidden = true;
+ $(this.trainingPagePrefix_ + '-wait').hidden = false;
+ $(this.trainingPagePrefix_ + '-error').hidden = true;
+ $(this.trainingPagePrefix_ + '-retry').hidden = true;
+ } else if (this.trainingState_ == TrainingState.TIMEOUT) {
+ var curStep = trainingSteps.current;
+ if (curStep) {
+ curStep.classList.remove('listening');
+ curStep.classList.add('not-started');
+ }
+
+ // Set a timeout before focusing the Retry button so that screenreaders
+ // have time to announce the timeout first.
+ this.setTimeout_(function() {
+ $(this.trainingPagePrefix_ + '-toast').children[1].focus();
+ }.bind(this), 50);
+ } else if (this.trainingState_ == TrainingState.ERROR) {
+ // Update the buttonbar.
+ $(this.trainingPagePrefix_ + '-wait').hidden = true;
+ $(this.trainingPagePrefix_ + '-error').hidden = false;
+ $(this.trainingPagePrefix_ + '-retry').hidden = false;
+ $(this.trainingPagePrefix_ + '-processing').hidden = false;
+
+ // Set a timeout before focusing the Retry button so that screenreaders
+ // have time to announce the error first.
+ this.setTimeout_(function() {
+ $(this.trainingPagePrefix_ + '-retry').children[0].focus();
+ }.bind(this), 50);
+ }
};
/**
@@ -156,33 +471,59 @@
* @private
*/
Flow.prototype.handleHotwordTrigger_ = function() {
- var curStep =
- $(this.trainingPagePrefix_ + '-training').querySelector('.listening');
- // TODO(kcarattini): Localize this string.
- curStep.querySelector('.text').textContent = 'Recorded';
- curStep.classList.remove('listening');
- curStep.classList.add('recorded');
+ var trainingSteps = this.getCurrentTrainingStep_('listening');
- var steps =
- $(this.trainingPagePrefix_ + '-training').querySelectorAll('.train');
- var index = Array.prototype.indexOf.call(steps, curStep);
- if (steps[index + 1]) {
- steps[index + 1].classList.remove('not-started');
- steps[index + 1].classList.add('listening');
+ if (!trainingSteps.current)
+ return;
+
+ var index = trainingSteps.index;
+ this.hotwordTriggerReceived_[index] = true;
+
+ trainingSteps.current.querySelector('.text').textContent =
+ loadTimeData.getString('trainingRecorded');
+ trainingSteps.current.classList.remove('listening');
+ trainingSteps.current.classList.add('recorded');
+
+ if (trainingSteps.steps[index + 1]) {
+ trainingSteps.steps[index + 1].classList.remove('not-started');
+ trainingSteps.steps[index + 1].classList.add('listening');
+ this.waitForHotwordTrigger_(index + 1);
return;
}
// Only the last step makes it here.
- var buttonElem = $(this.trainingPagePrefix_ + '-cancel-button');
- // TODO(kcarattini): Localize this string.
- buttonElem.textContent = 'Please wait ...';
- buttonElem.classList.add('grayed-out');
- buttonElem.classList.remove('finish-button');
-
+ var buttonElem = $(this.trainingPagePrefix_ + '-processing').hidden = false;
this.finalizeSpeakerModel_();
};
/**
+ * Handles a chrome.idle.onStateChanged event and times out the training if
+ * the state is "locked".
+ * @param {!string} state State, one of "active", "idle", or "locked".
+ * @private
+ */
+ Flow.prototype.handleIdleStateChanged_ = function(state) {
+ if (state == 'locked')
+ this.timeoutTraining_();
+ };
+
+ /**
+ * Handles a chrome.hotwordPrivate.onEnabledChanged event and times out
+ * training if the user is no longer the active user (user switches profiles).
+ * @private
+ */
+ Flow.prototype.handleEnabledChanged_ = function() {
+ if (chrome.hotwordPrivate.getStatus) {
+ chrome.hotwordPrivate.getStatus(function(status) {
+ if (status.userIsActive)
+ return;
+
+ this.timeoutTraining_();
+ }.bind(this));
+ }
+ };
+
+ /**
* Gets and starts the appropriate flow for the launch mode.
* @param {chrome.hotwordPrivate.LaunchState} state Launch state of the
* Hotword Audio Verification App.
@@ -193,22 +534,26 @@
assert(state.launchMode >= 0 && state.launchMode < FLOWS.length,
'Invalid Launch Mode.');
this.currentFlow_ = FLOWS[state.launchMode];
- this.advanceStep();
- // If the flow begins with a a training step, then start the training flow.
- if (state.launchMode == LaunchMode.HOTWORD_ONLY ||
- state.launchMode == LaunchMode.RETRAIN) {
- this.startTraining();
+ if (state.launchMode == LaunchMode.HOTWORD_ONLY) {
+ $('intro-description-audio-history-enabled').hidden = false;
+ } else if (state.launchMode == LaunchMode.HOTWORD_AND_AUDIO_HISTORY) {
+ $('intro-description').hidden = false;
}
+
+ this.advanceStep();
};
/**
* Displays the current step. If the current step is not the first step,
- * also hides the previous step.
+ * also hides the previous step. Focuses the current step's first button.
* @private
*/
Flow.prototype.showStep_ = function() {
- var currentStep = this.currentFlow_[this.currentStepIndex_];
- document.getElementById(currentStep).hidden = false;
+ var currentStepId = this.currentFlow_[this.currentStepIndex_];
+ var currentStep = document.getElementById(currentStepId);
+ currentStep.hidden = false;
+
+ cr.ui.setInitialFocus(currentStep);
var previousStep = null;
if (this.currentStepIndex_ > 0)
diff --git a/chromium/chrome/browser/resources/hotword_audio_verification/images/bt-close-1x.png b/chromium/chrome/browser/resources/hotword_audio_verification/images/bt-close-1x.png
deleted file mode 100644
index f32b9b11cec..00000000000
--- a/chromium/chrome/browser/resources/hotword_audio_verification/images/bt-close-1x.png
+++ /dev/null
Binary files differ
diff --git a/chromium/chrome/browser/resources/hotword_audio_verification/images/bt-close-2x.png b/chromium/chrome/browser/resources/hotword_audio_verification/images/bt-close-2x.png
deleted file mode 100644
index ae7ad924147..00000000000
--- a/chromium/chrome/browser/resources/hotword_audio_verification/images/bt-close-2x.png
+++ /dev/null
Binary files differ
diff --git a/chromium/chrome/browser/resources/hotword_audio_verification/images/gradient-1x.png b/chromium/chrome/browser/resources/hotword_audio_verification/images/gradient-1x.png
new file mode 100644
index 00000000000..6ff742000e8
--- /dev/null
+++ b/chromium/chrome/browser/resources/hotword_audio_verification/images/gradient-1x.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/hotword_audio_verification/images/gradient-2x.png b/chromium/chrome/browser/resources/hotword_audio_verification/images/gradient-2x.png
new file mode 100644
index 00000000000..3c39792f23c
--- /dev/null
+++ b/chromium/chrome/browser/resources/hotword_audio_verification/images/gradient-2x.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/hotword_audio_verification/images/header-optin-1x.png b/chromium/chrome/browser/resources/hotword_audio_verification/images/header-optin-1x.png
deleted file mode 100644
index 8206cfca15c..00000000000
--- a/chromium/chrome/browser/resources/hotword_audio_verification/images/header-optin-1x.png
+++ /dev/null
Binary files differ
diff --git a/chromium/chrome/browser/resources/hotword_audio_verification/images/header-optin-2x.png b/chromium/chrome/browser/resources/hotword_audio_verification/images/header-optin-2x.png
deleted file mode 100644
index 43e11e02dcf..00000000000
--- a/chromium/chrome/browser/resources/hotword_audio_verification/images/header-optin-2x.png
+++ /dev/null
Binary files differ
diff --git a/chromium/chrome/browser/resources/hotword_audio_verification/images/ic-check-blue-1x.png b/chromium/chrome/browser/resources/hotword_audio_verification/images/ic-check-blue-1x.png
index f8d3eeff79c..c04541c591b 100644
--- a/chromium/chrome/browser/resources/hotword_audio_verification/images/ic-check-blue-1x.png
+++ b/chromium/chrome/browser/resources/hotword_audio_verification/images/ic-check-blue-1x.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/hotword_audio_verification/images/ic-check-blue-2x.png b/chromium/chrome/browser/resources/hotword_audio_verification/images/ic-check-blue-2x.png
index f565f46b23a..7fca135f4b6 100644
--- a/chromium/chrome/browser/resources/hotword_audio_verification/images/ic-check-blue-2x.png
+++ b/chromium/chrome/browser/resources/hotword_audio_verification/images/ic-check-blue-2x.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/hotword_audio_verification/images/ic-check-gray-1x.png b/chromium/chrome/browser/resources/hotword_audio_verification/images/ic-check-gray-1x.png
index f8b6d1313ef..2bab2be21f5 100644
--- a/chromium/chrome/browser/resources/hotword_audio_verification/images/ic-check-gray-1x.png
+++ b/chromium/chrome/browser/resources/hotword_audio_verification/images/ic-check-gray-1x.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/hotword_audio_verification/images/ic-check-gray-2x.png b/chromium/chrome/browser/resources/hotword_audio_verification/images/ic-check-gray-2x.png
index bf3a811fda1..a73190c2abf 100644
--- a/chromium/chrome/browser/resources/hotword_audio_verification/images/ic-check-gray-2x.png
+++ b/chromium/chrome/browser/resources/hotword_audio_verification/images/ic-check-gray-2x.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/hotword_audio_verification/images/ic-error-1x.png b/chromium/chrome/browser/resources/hotword_audio_verification/images/ic-error-1x.png
index 1e4a26c5f9b..f8d75841375 100644
--- a/chromium/chrome/browser/resources/hotword_audio_verification/images/ic-error-1x.png
+++ b/chromium/chrome/browser/resources/hotword_audio_verification/images/ic-error-1x.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/hotword_audio_verification/images/ic-error-2x.png b/chromium/chrome/browser/resources/hotword_audio_verification/images/ic-error-2x.png
index 24bc588552d..dc0bf4f1c93 100644
--- a/chromium/chrome/browser/resources/hotword_audio_verification/images/ic-error-2x.png
+++ b/chromium/chrome/browser/resources/hotword_audio_verification/images/ic-error-2x.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/hotword_audio_verification/images/ic-x-white-1x.png b/chromium/chrome/browser/resources/hotword_audio_verification/images/ic-x-white-1x.png
new file mode 100644
index 00000000000..462bd332f1f
--- /dev/null
+++ b/chromium/chrome/browser/resources/hotword_audio_verification/images/ic-x-white-1x.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/hotword_audio_verification/images/ic-x-white-2x.png b/chromium/chrome/browser/resources/hotword_audio_verification/images/ic-x-white-2x.png
new file mode 100644
index 00000000000..ed0dd03f270
--- /dev/null
+++ b/chromium/chrome/browser/resources/hotword_audio_verification/images/ic-x-white-2x.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/hotword_audio_verification/images/icon-128.png b/chromium/chrome/browser/resources/hotword_audio_verification/images/icon-128.png
new file mode 100644
index 00000000000..6f6343b55be
--- /dev/null
+++ b/chromium/chrome/browser/resources/hotword_audio_verification/images/icon-128.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/hotword_audio_verification/images/icon-16.png b/chromium/chrome/browser/resources/hotword_audio_verification/images/icon-16.png
new file mode 100644
index 00000000000..b1f28873a23
--- /dev/null
+++ b/chromium/chrome/browser/resources/hotword_audio_verification/images/icon-16.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/hotword_audio_verification/images/icon-48.png b/chromium/chrome/browser/resources/hotword_audio_verification/images/icon-48.png
new file mode 100644
index 00000000000..de75078f507
--- /dev/null
+++ b/chromium/chrome/browser/resources/hotword_audio_verification/images/icon-48.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/hotword_audio_verification/images/intro-1x.png b/chromium/chrome/browser/resources/hotword_audio_verification/images/intro-1x.png
new file mode 100644
index 00000000000..3190980fbf9
--- /dev/null
+++ b/chromium/chrome/browser/resources/hotword_audio_verification/images/intro-1x.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/hotword_audio_verification/images/intro-2x.png b/chromium/chrome/browser/resources/hotword_audio_verification/images/intro-2x.png
new file mode 100644
index 00000000000..6465825b0ae
--- /dev/null
+++ b/chromium/chrome/browser/resources/hotword_audio_verification/images/intro-2x.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/hotword_audio_verification/images/mic-1x.png b/chromium/chrome/browser/resources/hotword_audio_verification/images/mic-1x.png
deleted file mode 100644
index 03c2d486c6a..00000000000
--- a/chromium/chrome/browser/resources/hotword_audio_verification/images/mic-1x.png
+++ /dev/null
Binary files differ
diff --git a/chromium/chrome/browser/resources/hotword_audio_verification/images/mic-2x.png b/chromium/chrome/browser/resources/hotword_audio_verification/images/mic-2x.png
deleted file mode 100644
index 958052c9e59..00000000000
--- a/chromium/chrome/browser/resources/hotword_audio_verification/images/mic-2x.png
+++ /dev/null
Binary files differ
diff --git a/chromium/chrome/browser/resources/hotword_audio_verification/images/placeholder-loader-1x.png b/chromium/chrome/browser/resources/hotword_audio_verification/images/placeholder-loader-1x.png
index 98eb363fdf1..0bb41bc244e 100644
--- a/chromium/chrome/browser/resources/hotword_audio_verification/images/placeholder-loader-1x.png
+++ b/chromium/chrome/browser/resources/hotword_audio_verification/images/placeholder-loader-1x.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/hotword_audio_verification/images/placeholder-loader-2x.png b/chromium/chrome/browser/resources/hotword_audio_verification/images/placeholder-loader-2x.png
index 63b62395a82..cd1ffa7205b 100644
--- a/chromium/chrome/browser/resources/hotword_audio_verification/images/placeholder-loader-2x.png
+++ b/chromium/chrome/browser/resources/hotword_audio_verification/images/placeholder-loader-2x.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/hotword_audio_verification/main.html b/chromium/chrome/browser/resources/hotword_audio_verification/main.html
index e791cba4fef..2572c9464c9 100644
--- a/chromium/chrome/browser/resources/hotword_audio_verification/main.html
+++ b/chromium/chrome/browser/resources/hotword_audio_verification/main.html
@@ -1,19 +1,23 @@
-<!DOCTYPE HTML>
-<html i18n-values="dir:textdirection">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
<head>
<meta charset=utf-8>
- <title></title>
+ <title i18n-content="introTitle"></title>
+ <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link type="text/css" rel="stylesheet" href="style.css">
<script src="chrome://resources/js/action_link.js"></script>
+ <script src="chrome://resources/js/cr.js"></script>
+ <script src="chrome://resources/js/cr/ui/node_utils.js"></script>
+ <script src="chrome://resources/js/load_time_data.js"></script>
<script src="chrome://resources/js/util.js"></script>
+ <script src="chrome://resources/js/i18n_template_no_process.js"></script>
<script src="flow.js"></script>
<script src="main.js"></script>
</head>
<body>
<div id="steps">
- <!-- TODO(kcarattini): Localize the strings in the following files. -->
- <include src="steps/hotword_only_step.html">
- <include src="steps/hotword_audio_history_step.html">
+ <include src="steps/start_step.html">
+ <include src="steps/audio_history_step.html">
<include src="steps/speech_training_step.html">
<include src="steps/finished_step.html">
</div>
diff --git a/chromium/chrome/browser/resources/hotword_audio_verification/main.js b/chromium/chrome/browser/resources/hotword_audio_verification/main.js
index 78838e3c8c1..1adff67fa39 100644
--- a/chromium/chrome/browser/resources/hotword_audio_verification/main.js
+++ b/chromium/chrome/browser/resources/hotword_audio_verification/main.js
@@ -5,28 +5,46 @@
var appWindow = chrome.app.window.current();
document.addEventListener('DOMContentLoaded', function() {
- var flow = new Flow();
- flow.startFlow();
-
- var closeAppWindow = function(e) {
- var classes = e.target.classList;
- if (classes.contains('close') || classes.contains('finish-button')) {
- flow.stopTraining();
- appWindow.close();
- e.preventDefault();
- }
- };
+ chrome.hotwordPrivate.getLocalizedStrings(function(strings) {
+ loadTimeData.data = strings;
+ i18nTemplate.process(document, loadTimeData);
- $('steps').addEventListener('click', closeAppWindow);
+ var flow = new Flow();
+ flow.startFlow();
- $('hw-agree-button').addEventListener('click', function(e) {
- flow.advanceStep();
- flow.startTraining();
- e.preventDefault();
- });
+ var pressFunction = function(e) {
+ // Only respond to 'Enter' key presses.
+ if (e.type == 'keyup' && e.keyIdentifier != 'Enter')
+ return;
+
+ var classes = e.target.classList;
+ if (classes.contains('close') || classes.contains('finish-button')) {
+ flow.stopTraining();
+ appWindow.close();
+ e.preventDefault();
+ }
+ if (classes.contains('retry-button')) {
+ flow.handleRetry();
+ e.preventDefault();
+ }
+ };
- $('settings-link').addEventListener('click', function(e) {
- chrome.browser.openTab({'url': 'chrome://settings'}, function() {});
- e.preventDefault();
+ $('steps').addEventListener('click', pressFunction);
+ $('steps').addEventListener('keyup', pressFunction);
+
+ $('audio-history-agree').addEventListener('click', function(e) {
+ flow.enableAudioHistory();
+ e.preventDefault();
+ });
+
+ $('hotword-start').addEventListener('click', function(e) {
+ flow.advanceStep();
+ e.preventDefault();
+ });
+
+ $('settings-link').addEventListener('click', function(e) {
+ chrome.browser.openTab({'url': 'chrome://settings'}, function() {});
+ e.preventDefault();
+ });
});
});
diff --git a/chromium/chrome/browser/resources/hotword_audio_verification/manifest.json b/chromium/chrome/browser/resources/hotword_audio_verification/manifest.json
index 83a60ecedd0..134180d13fd 100644
--- a/chromium/chrome/browser/resources/hotword_audio_verification/manifest.json
+++ b/chromium/chrome/browser/resources/hotword_audio_verification/manifest.json
@@ -1,21 +1,27 @@
{
// chrome-extension://abjokfonkihficiokmkfboogholifghn/
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtfVZc80kw93gaZwHGhOLpxGKil8n/x1IQ2L/Oj76YeICJk5w4YKw+0N2IYA0gJnwqJdTu3ijrWqO2oQTWoZ3lHmAv9NHsUXHdrWuoGRuVqo0dakLnS+AB3rLGPSerZZNExpdK0Yd3wR6GMOJAoAQM9H6Zpo7LNYjPebx31QJZ6OgdYZA+Eu4fGIJkWIPY1LMsVO1jzFJ4JSPyNWmhxL4fHfQXVM5p1cgJSVxTXsB1ZGaRc4HF2kwSYMOimIiWqfU0VInTXVU7IS3hJaKzm/LExW/ABTGejf2sGIa725EQTavGFsQ07jFZdVzKGAjHCU/0Jy8PxDIg2B+ixlM2QXP/wIDAQAB",
- "name": "Hotword Audio Verification",
+ "name": "Ok Google",
"version": "0.1",
"manifest_version": 2,
+ "icons": {
+ "16": "images/icon-16.png",
+ "48": "images/icon-48.png",
+ "128": "images/icon-128.png"
+ },
"app": {
"background": {
"scripts": ["event_page.js"],
"persistent": false
},
- "content_security_policy": "default-src 'self'; script-src 'self' chrome://resources chrome://settings"
+ "content_security_policy": "default-src 'self'; script-src 'self' chrome://resources chrome://settings; style-src 'self' chrome://resources"
},
"permissions": [
"chrome://resources/",
"chrome://settings/",
"browser",
- "hotwordPrivate"
+ "hotwordPrivate",
+ "idle"
],
"display_in_launcher": false
}
diff --git a/chromium/chrome/browser/resources/hotword_audio_verification/steps/audio_history_step.html b/chromium/chrome/browser/resources/hotword_audio_verification/steps/audio_history_step.html
new file mode 100644
index 00000000000..8c2304ba9ab
--- /dev/null
+++ b/chromium/chrome/browser/resources/hotword_audio_verification/steps/audio_history_step.html
@@ -0,0 +1,38 @@
+<div id="audio-history-container" hidden>
+ <div class="container">
+ <span class="close" tabindex="0" role="button"
+ i18n-values="aria-label:close"></span>
+ <div class="header">
+ <h1 i18n-content="audioHistoryTitle" aria-live="polite"></h1>
+ </div>
+ <div class="content">
+ <div i18n-content="audioHistoryDescription1"></div>
+ <div class="v-spacing"></div>
+ <div i18n-values=".innerHTML:audioHistoryDescription2"></div>
+ <div class="v-spacing"></div>
+ <div i18n-values=".innerHTML:audioHistoryDescription3"></div>
+ </div>
+ <div class="buttonbar">
+ <div class="right">
+ <div>
+ <button id="audio-history-agree" i18n-content="audioHistoryAgree"
+ class="primary">
+ </button>
+ <button id="audio-history-cancel" i18n-content="cancel"
+ class="finish-button">
+ </button>
+ </div>
+ </div>
+ <div class="left">
+ <div id="audio-history-wait" class="message wait" hidden>
+ <span class="icon"></span>
+ <span i18n-content="audioHistoryWait" class="text"></span>
+ </div>
+ <div id="audio-history-error" class="message error" role="alert" hidden>
+ <span class="icon"></span>
+ <span i18n-content="error" class="text"></span>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/chromium/chrome/browser/resources/hotword_audio_verification/steps/finished_step.html b/chromium/chrome/browser/resources/hotword_audio_verification/steps/finished_step.html
index 3f2bcc56f5b..71537dd45aa 100644
--- a/chromium/chrome/browser/resources/hotword_audio_verification/steps/finished_step.html
+++ b/chromium/chrome/browser/resources/hotword_audio_verification/steps/finished_step.html
@@ -1,33 +1,30 @@
-<div id="finished-container" hidden>
+<div id="finish-container" hidden>
<div class="container">
+ <span class="close" tabindex="0" role="button"
+ i18n-values="aria-label:close"></span>
<div class="header">
- <span class="close"></span>
- <div class="header-text">
- <div class="v-spacing-for-no-subheading"></div>
- <h1>All set!</h1>
- </div>
+ <h1 i18n-content="finishedTitle" aria-live="polite"></h1>
</div>
<div class="content">
- <div class="col-3">
- <h4>Your Chromebook can now:</h4>
- <div class="check">
- <span class="icon"></span>
- <span class="text">
- Recognize your voice when you say 'Ok Google'
- </span>
- </div>
- <div class="check">
- <span class="icon"></span>
- <span class="text">
- Respond to 'Ok Google' when the screen is on
- </span>
- </div>
- You can change these settings at any time in the
- <a id="settings-link" href="#">Chrome Settings</a>
+ <h3 i18n-content="finishedListIntro"></h3>
+ <div class="check">
+ <span class="icon"></span>
+ <span i18n-content="finishedListItem1" class="text">
+ </span>
+ </div>
+ <div class="check">
+ <span class="icon"></span>
+ <span i18n-content="finishedListItem2" class="text">
+ </span>
</div>
+ <div i18n-values=".innerHTML:finishedSettings"></div>
+ <div i18n-values=".innerHTML:finishedAudioHistory"></div>
</div>
<div class="buttonbar">
- <button id="done-button" class="primary finish-button">DONE</button>
+ <div class="right">
+ <button id="done-button" i18n-content="finish"
+ class="primary finish-button"></button>
+ </div>
</div>
</div>
</div>
diff --git a/chromium/chrome/browser/resources/hotword_audio_verification/steps/hotword_audio_history_step.html b/chromium/chrome/browser/resources/hotword_audio_verification/steps/hotword_audio_history_step.html
deleted file mode 100644
index bb5fdc13c89..00000000000
--- a/chromium/chrome/browser/resources/hotword_audio_verification/steps/hotword_audio_history_step.html
+++ /dev/null
@@ -1,29 +0,0 @@
-<div id="hotword-audio-history-container" hidden>
- <div class="container">
- <div class="header">
- <span class="close"></span>
- <div class="header-text">
- <h1>Voice search at any time</h1>
- <h2>Say 'Ok Google' any time your screen is on</h2>
- </div>
- </div>
- <div class="content">
- <div class="col-3">
- <h4>To make this happen, you agree that</h4>
- When you use voice activation commands, such as "Ok Google" or
- touching a microphone icon, a recording of the next thing you say,
- plus a few seconds before, may be used and stored by Google and
- associated with your Google Account to help recognize your voice and
- improve speech recognition.
- <br>
- <br>
- <a href="https://support.google.com/websearch?p=chromebook_audiohistory"
- target="_blank">LEARN MORE</a>
- </div>
- </div>
- <div class="buttonbar">
- <button id="hw-agree-button" class="primary">I AGREE</button>
- <button id="hw-cancel-button" class="finish-button">NO THANKS</button>
- </div>
- </div>
-</div>
diff --git a/chromium/chrome/browser/resources/hotword_audio_verification/steps/hotword_only_step.html b/chromium/chrome/browser/resources/hotword_audio_verification/steps/hotword_only_step.html
deleted file mode 100644
index b405ba7a056..00000000000
--- a/chromium/chrome/browser/resources/hotword_audio_verification/steps/hotword_only_step.html
+++ /dev/null
@@ -1,41 +0,0 @@
-<div id="hotword-only-container" hidden>
- <div class="mic"></div>
- <div class="container">
- <div class="header">
- <span class="close"></span>
- <div class="header-text">
- <h1>Voice search at any time</h1>
- <h2>Say 'Ok Google' any time your screen is on</h2>
- </div>
- </div>
- <div id="hotword-only-training" class="content">
- <h4>Let's train your Chromebook</h4>
- <div class="col-2">
- <br>
- To help Google respond to you and for reliable and easy access to
- voice search, you need to teach Google the sound of your voice.
- <br>
- <h4>Just say 'Ok Google' three times</h4>
- </div>
- <div class="col-spacing"></div>
- <div class="col-2">
- <div class="train listening">
- <span class="icon"></span>
- <span class="text">Say 'Ok Google'</span>
- </div>
- <div class="train not-started">
- <span class="icon"></span>
- <span class="text">Say 'Ok Google' again</span>
- </div>
- <div class="train not-started">
- <span class="icon"></span>
- <span class="text">Say 'Ok Google' one last time</span>
- </div>
- </div>
- </div>
- <div class="buttonbar">
- <button id="hotword-only-cancel-button"
- class="finish-button">CANCEL</button>
- </div>
- </div>
-</div>
diff --git a/chromium/chrome/browser/resources/hotword_audio_verification/steps/speech_training_step.html b/chromium/chrome/browser/resources/hotword_audio_verification/steps/speech_training_step.html
index 769ac41feaa..fd37331147a 100644
--- a/chromium/chrome/browser/resources/hotword_audio_verification/steps/speech_training_step.html
+++ b/chromium/chrome/browser/resources/hotword_audio_verification/steps/speech_training_step.html
@@ -1,39 +1,56 @@
<div id="speech-training-container" hidden>
- <div class="mic"></div>
<div class="container">
+ <span class="close" tabindex="0" role="button"
+ i18n-values="aria-label:close"></span>
<div class="header">
- <span class="close"></span>
- <div class="header-text">
- <div class="v-spacing-for-no-subheading"></div>
- <h1>Let's train your Chromebook</h1>
- </div>
+ <h1 i18n-content="trainingTitle" aria-live="polite"></h1>
</div>
<div id="speech-training-training" class="content">
<div class="col-2">
- To help Google respond to you and for reliable and easy access to
- voice search, you need to teach Google the sound of your voice.
+ <div i18n-content="trainingDescription"></div>
<br>
- <h4>Just say 'Ok Google' three times</h4>
+ <h3 i18n-content="trainingSpeak"></h3>
</div>
<div class="col-spacing"></div>
<div class="col-2">
<div class="train listening">
<span class="icon"></span>
- <span class="text">Say 'Ok Google'</span>
+ <span i18n-content="trainingFirstPrompt"
+ class="text"></span>
</div>
<div class="train not-started">
<span class="icon"></span>
- <span class="text">Say 'Ok Google' again</span>
+ <span i18n-content="trainingMiddlePrompt"
+ class="text"></span>
</div>
<div class="train not-started">
<span class="icon"></span>
- <span class="text">Say 'Ok Google' one last time</span>
+ <span i18n-content="trainingLastPrompt"
+ class="text"></span>
</div>
</div>
</div>
- <div class="buttonbar">
- <button id="speech-training-cancel-button"
- class="finish-button">CANCEL</button>
+ <div id="speech-training-toast" class="toast" hidden>
+ <span i18n-content="trainingTimeout" class="message" role="alert"></span>
+ <button i18n-content="trainingRetry" class="retry-button" tabindex="0">
+ </button>
+ </div>
+ <div id="speech-training-processing" class="buttonbar" hidden>
+ <div id="speech-training-retry" class="right" hidden>
+ <button i18n-content="trainingRetry" class="primary retry-button">
+ </button>
+ </div>
+ <div class="left">
+ <div id="speech-training-wait" class="message wait">
+ <span class="icon"></span>
+ <span i18n-content="finishedWait" class="text"></span>
+ </div>
+ <div id="speech-training-error" class="message error"
+ role="alert" hidden>
+ <span class="icon"></span>
+ <span i18n-content="error" class="text"></span>
+ </div>
+ </div>
</div>
</div>
</div>
diff --git a/chromium/chrome/browser/resources/hotword_audio_verification/steps/start_step.html b/chromium/chrome/browser/resources/hotword_audio_verification/steps/start_step.html
new file mode 100644
index 00000000000..441ae6b86aa
--- /dev/null
+++ b/chromium/chrome/browser/resources/hotword_audio_verification/steps/start_step.html
@@ -0,0 +1,17 @@
+<div id="start-container" hidden>
+ <div class="container">
+ <span class="close" tabindex="0" role="button"
+ i18n-values="aria-label:close"></span>
+ <div class="intro-image"></div>
+ <div class="intro-text">
+ <h1 i18n-content="introTitle"></h1>
+ <h2 i18n-content="introSubtitle"></h2>
+ <h3 id="intro-description" i18n-content="introDescription" hidden></h3>
+ <h3 id="intro-description-audio-history-enabled"
+ i18n-content="introDescriptionAudioHistoryEnabled" hidden></h3>
+ </div>
+ <div class="buttonbar">
+ <button id="hotword-start" i18n-content="introStart"></button>
+ </div>
+ </div>
+</div>
diff --git a/chromium/chrome/browser/resources/hotword_audio_verification/style.css b/chromium/chrome/browser/resources/hotword_audio_verification/style.css
index 16d4e70ed69..664ad5cf963 100644
--- a/chromium/chrome/browser/resources/hotword_audio_verification/style.css
+++ b/chromium/chrome/browser/resources/hotword_audio_verification/style.css
@@ -2,117 +2,142 @@
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file. */
+/* TODO(xdai): Remove hard-coded font-family for 'Roboto'. */
+
* {
box-sizing: border-box;
- color: rgba(0, 0, 0, .87);
- font-family: Helvetica, sans-serif;
- font-size: 14px;
+ color: rgba(0, 0, 0, .54);
+ font-family: Roboto, 'Noto Sans', sans-serif;
+ font-size: 13px;
margin: 0;
padding: 0;
}
-a {
- color: rgb(67, 133, 244);
- text-decoration: none;
+#start-container * {
+ color: #fff;
}
-body {
- background: #ddd;
+#start-container h2 {
+ font-size: 15px;
+ font-weight: normal;
+ line-height: 24px;
+ margin-top: 16px;
}
-h1 {
- font-size: 34px;
+#start-container h3 {
font-weight: normal;
- line-height: 40px;
+ margin: 42px 16px 24px 16px;
}
-h2 {
- font-size: 16px;
- font-weight: normal;
+#start-container div.container {
+ background: rgb(66, 133, 244);
}
-h4 {
- font-size: 14px;
+div.intro-image {
+ background: -webkit-image-set(
+ url(../images/intro-1x.png) 1x,
+ url(../images/intro-2x.png) 2x)
+ no-repeat;
+ height: 152px;
+ left: 24px;
+ position: absolute;
+ top: 122px;
+ width: 304px;
}
-div.container {
- background: #fff;
- box-shadow: 0 0 30px rgba(0, 0, 0, 0.3);
- height: 600px;
- width: 800px;
+div.intro-text {
+ left: 328px;
+ position: absolute;
+ text-align: center;
+ top: 116px;
+ width: 432px;
}
-div.header {
- background: -webkit-image-set(
- url(../images/header-optin-1x.png) 1x,
- url(../images/header-optin-2x.png) 2x)
- no-repeat;
- height: 240px;
- vertical-align: bottom;
+#start-container div.buttonbar {
+ background-color: rgb(51, 103, 214);
+ height: 56px;
+ padding: 0;
+ text-align: center;
}
-div.header-text {
- height: 240px;
- padding: 60px;
+#start-container .buttonbar button {
+ height: 100%;
+ margin: 0;
+ padding: 0 8px;
+ width: 100%;
}
-div.header-text h1 {
- color: #fff;
- margin-top: 44px;
+a {
+ -webkit-app-region: no-drag;
+ color: rgb(51, 103, 214);
+ text-decoration: none;
}
-div.header-text h2 {
- color: #fff;
- margin-top: 20px;
+button {
+ -webkit-app-region: no-drag;
}
-div.content {
- height: 240px;
- margin: 60px 60px auto 60px;
+body {
+ -webkit-app-region: drag;
+ background: #ddd;
}
-.close {
- background: -webkit-image-set(
- url(../images/bt-close-1x.png) 1x,
- url(../images/bt-close-2x.png) 2x);
- float: right;
- height: 60px;
- width: 60px;
+h1 {
+ font-size: 20px;
+ font-weight: normal;
+ line-height: 32px;
}
+h3 {
+ font-size: 13px;
+ line-height: 20px;
+}
-.buttonbar button.grayed-out {
- color: rgba(0, 0, 0, .28);
- text-transform: none;
+div.container {
+ background: #fff;
+ height: 448px;
+ position: relative;
+ width: 784px;
}
-.col-3 {
- color: rgba(0, 0, 0, .54);
- line-height: 24px;
- width: 504px;
+div.header {
+ background: -webkit-image-set(
+ url(../images/gradient-1x.png) 1x,
+ url(../images/gradient-2x.png) 2x)
+ no-repeat;
+ height: 128px;
+ padding: 70px 42px 0 42px;
+}
+
+div.header h1 {
+ color: #fff;
+}
+
+div.content {
+ height: 264px;
+ line-height: 20px;
+ padding: 32px 42px 0 42px;
}
-.col-3 h4 {
- line-height: 100%;
- margin-bottom: 27px;
+div.content h3 {
+ color: rgba(0, 0, 0, .87);
+ margin-bottom: 16px;
}
-.col-2 {
+div.col-2 {
color: rgba(0, 0, 0, .54);
float: left;
- line-height: 24px;
- width: 332px;
+ width: 320px;
}
-.col-2 h4 {
- line-height: 100%;
- margin-top: 27px;
+div.col-spacing {
+ float: left;
+ height: 216px;
+ width: 60px;
}
-.col-spacing {
- float: left;
- height: 160px;
- width: 16px;
+div.v-spacing {
+ height: 8px;
}
a[is='action-link'] {
@@ -123,69 +148,32 @@ a[is='action-link'] {
text-transform: uppercase;
}
-div.buttonbar {
- background: rgba(0, 0, 0, .06);
- height: 60px;
- padding: 12px 60px;
-}
-
-.buttonbar button {
- background: none;
- border: none;
- border-radius: 2px;
- float: right;
- height: 36px;
- margin-left: 8px;
- min-width: 88px;
- padding: 8px;
- text-transform: uppercase;
-}
-
-button.primary {
- color: rgb(67, 133, 244);
-}
-
-.v-spacing-for-no-subheading {
- height: 43px;
-}
-
.train {
clear: both;
+ line-height: 18px;
margin-bottom: 24px;
- white-space: nowrap;
}
.train .icon {
display: inline-block;
- height: 24px;
- margin-right: 16px;
+ height: 18px;
+ margin-right: 8px;
vertical-align: top;
- width: 24px;
+ width: 18px;
}
.train .text {
+ color: rgba(0, 0, 0, .54);
display: inline-block;
- line-height: 24px;
+ line-height: 13px;
+ padding-top: 3px;
vertical-align: top;
- width: 292px;
-}
-
-.train.listening .text {
- color: rgba(0, 0, 0, .87);
-}
-
-.train.not-started .text {
- color: rgba(0, 0, 0, .54);
}
.train.recorded .text {
color: rgba(66, 133, 244, 1);
}
-.train.error .text {
- color: rgb(213, 0, 0);
-}
-
@-webkit-keyframes rotate {
from { -webkit-transform: rotate(0); }
to { -webkit-transform: rotate(359deg); }
@@ -213,28 +201,9 @@ button.primary {
no-repeat;
}
-.train.error .icon {
- background: -webkit-image-set(
- url(../images/ic-error-1x.png) 1x,
- url(../images/ic-error-2x.png) 2x)
- no-repeat;
-}
-
-.mic {
- background: -webkit-image-set(
- url(../images/mic-1x.png) 1x,
- url(../images/mic-2x.png) 2x)
- no-repeat;
- height: 80px;
- left: 666px;
- position: absolute;
- top: 200px;
- width: 80px;
-}
-
.check {
clear: both;
- height: 24px;
+ height: 18px;
margin-bottom: 24px;
}
@@ -244,16 +213,149 @@ button.primary {
url(../images/ic-check-blue-2x.png) 2x)
no-repeat;
display: inline-block;
- height: 24px;
- margin-right: 16px;
+ height: 18px;
+ margin-right: 8px;
vertical-align: top;
- width: 24px;
+ width: 18px;
}
.check .text {
color: rgba(0, 0, 0, .54);
display: inline-block;
- height: 24px;
- line-height: 24px;
+ height: 18px;
+ line-height: 18px;
+ padding-top: 2px;
vertical-align: top;
}
+
+div.buttonbar {
+ background-color: rgba(236,239, 241, 1);
+ bottom: 0;
+ height: 56px;
+ padding: 12px;
+ position: absolute;
+ width: 100%;
+}
+
+.buttonbar button {
+ background: none;
+ border: none;
+ display: inline-block;
+ font-weight: 700;
+ height: 32px;
+ line-height: 32px;
+ margin-left: 8px;
+ min-width: 56px;
+ padding: 1px 8px 0 8px;
+ text-transform: uppercase;
+}
+
+.buttonbar button:disabled {
+ opacity: .5;
+}
+
+.buttonbar button.grayed-out {
+ color: rgba(0, 0, 0, .28);
+ text-transform: none;
+}
+
+.buttonbar button.primary {
+ color: rgb(51, 103, 214);
+}
+
+.buttonbar .left {
+ float: left;
+ text-align: left;
+}
+
+.buttonbar .left button:first-child {
+ margin-left: 0;
+}
+
+.buttonbar .right {
+ float: right;
+ text-align: right;
+}
+
+.buttonbar .message {
+ margin: 7px 0 0 2px;
+}
+
+.buttonbar .message .icon {
+ display: inline-block;
+ height: 18px;
+ margin-right: 8px;
+ vertical-align: top;
+ width: 18px;
+}
+
+.buttonbar .message.wait .icon {
+ -webkit-animation: rotate 2s linear infinite;
+ background: -webkit-image-set(
+ url(../images/placeholder-loader-1x.png) 1x,
+ url(../images/placeholder-loader-2x.png) 2x)
+ no-repeat;
+}
+
+.buttonbar .message.error .icon {
+ background: -webkit-image-set(
+ url(../images/ic-error-1x.png) 1x,
+ url(../images/ic-error-2x.png) 2x)
+ no-repeat;
+}
+
+.buttonbar .message .text {
+ color: rgba(0, 0, 0, .54);
+ display: inline-block;
+ line-height: 18px;
+ padding-top: 2px;
+ vertical-align: top;
+}
+
+.buttonbar .message.error .text {
+ color: rgb(213, 0, 0);
+}
+
+.close {
+ -webkit-app-region: no-drag;
+ background: -webkit-image-set(
+ url(../images/ic-x-white-1x.png) 1x,
+ url(../images/ic-x-white-2x.png) 2x)
+ center center no-repeat;
+ border: none;
+ float: right;
+ height: 42px;
+ opacity: .54;
+ width: 42px;
+}
+
+.close:hover {
+ opacity: 1;
+}
+
+.toast {
+ background-color: rgb(38, 50, 56);
+ bottom: 0;
+ height: 52px;
+ padding: 10px 12px 0 42px;
+ position: absolute;
+ width: 100%;
+}
+
+.toast .message {
+ color: #fff;
+ float: left;
+ padding: 9px 0 0 0;
+}
+
+.toast button {
+ background: none;
+ border: none;
+ color: rgb(58, 218, 255);
+ float: right;
+ height: 32px;
+ margin-left: 18px;
+ min-width: 56px;
+ padding: 0 8px 0 8px;
+ text-transform: uppercase;
+}
diff --git a/chromium/chrome/browser/resources/hotword_helper/audio_client.js b/chromium/chrome/browser/resources/hotword_helper/audio_client.js
deleted file mode 100644
index 927e0f7892c..00000000000
--- a/chromium/chrome/browser/resources/hotword_helper/audio_client.js
+++ /dev/null
@@ -1,387 +0,0 @@
-// Copyright (c) 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 This is the audio client content script injected into eligible
- * Google.com and New tab pages for interaction between the Webpage and the
- * Hotword extension.
- */
-
-
-
-(function() {
- /**
- * @constructor
- */
- var AudioClient = function() {
- /** @private {Element} */
- this.speechOverlay_ = null;
-
- /** @private {number} */
- this.checkSpeechUiRetries_ = 0;
-
- /**
- * Port used to communicate with the audio manager.
- * @private {?Port}
- */
- this.port_ = null;
-
- /**
- * Keeps track of the effects of different commands. Used to verify that
- * proper UIs are shown to the user.
- * @private {Object.<AudioClient.CommandToPage, Object>}
- */
- this.uiStatus_ = null;
-
- /**
- * Bound function used to handle commands sent from the page to this script.
- * @private {Function}
- */
- this.handleCommandFromPageFunc_ = null;
- };
-
-
- /**
- * Messages sent to the page to control the voice search UI.
- * @enum {string}
- */
- AudioClient.CommandToPage = {
- HOTWORD_VOICE_TRIGGER: 'vt',
- HOTWORD_STARTED: 'hs',
- HOTWORD_ENDED: 'hd',
- HOTWORD_TIMEOUT: 'ht',
- HOTWORD_ERROR: 'he'
- };
-
-
- /**
- * Messages received from the page used to indicate voice search state.
- * @enum {string}
- */
- AudioClient.CommandFromPage = {
- SPEECH_START: 'ss',
- SPEECH_END: 'se',
- SPEECH_RESET: 'sr',
- SHOWING_HOTWORD_START: 'shs',
- SHOWING_ERROR_MESSAGE: 'sem',
- SHOWING_TIMEOUT_MESSAGE: 'stm',
- CLICKED_RESUME: 'hcc',
- CLICKED_RESTART: 'hcr',
- CLICKED_DEBUG: 'hcd'
- };
-
-
- /**
- * Errors that are sent to the hotword extension.
- * @enum {string}
- */
- AudioClient.Error = {
- NO_SPEECH_UI: 'ac1',
- NO_HOTWORD_STARTED_UI: 'ac2',
- NO_HOTWORD_TIMEOUT_UI: 'ac3',
- NO_HOTWORD_ERROR_UI: 'ac4'
- };
-
-
- /**
- * @const {string}
- * @private
- */
- AudioClient.HOTWORD_EXTENSION_ID_ = 'bepbmhgboaologfdajaanbcjmnhjmhfn';
-
-
- /**
- * Number of times to retry checking a transient error.
- * @const {number}
- * @private
- */
- AudioClient.MAX_RETRIES = 3;
-
-
- /**
- * Delay to wait in milliseconds before rechecking for any transient errors.
- * @const {number}
- * @private
- */
- AudioClient.RETRY_TIME_MS_ = 2000;
-
-
- /**
- * DOM ID for the speech UI overlay.
- * @const {string}
- * @private
- */
- AudioClient.SPEECH_UI_OVERLAY_ID_ = 'spch';
-
-
- /**
- * @const {string}
- * @private
- */
- AudioClient.HELP_CENTER_URL_ =
- 'https://support.google.com/chrome/?p=ui_hotword_search';
-
-
- /**
- * @const {string}
- * @private
- */
- AudioClient.CLIENT_PORT_NAME_ = 'chwcpn';
-
- /**
- * Existence of the Audio Client.
- * @const {string}
- * @private
- */
- AudioClient.EXISTS_ = 'chwace';
-
-
- /**
- * Checks for the presence of speech overlay UI DOM elements.
- * @private
- */
- AudioClient.prototype.checkSpeechOverlayUi_ = function() {
- if (!this.speechOverlay_) {
- window.setTimeout(this.delayedCheckSpeechOverlayUi_.bind(this),
- AudioClient.RETRY_TIME_MS_);
- } else {
- this.checkSpeechUiRetries_ = 0;
- }
- };
-
-
- /**
- * Function called to check for the speech UI overlay after some time has
- * passed since an initial check. Will either retry triggering the speech
- * or sends an error message depending on the number of retries.
- * @private
- */
- AudioClient.prototype.delayedCheckSpeechOverlayUi_ = function() {
- this.speechOverlay_ = document.getElementById(
- AudioClient.SPEECH_UI_OVERLAY_ID_);
- if (!this.speechOverlay_) {
- if (this.checkSpeechUiRetries_++ < AudioClient.MAX_RETRIES) {
- this.sendCommandToPage_(AudioClient.CommandToPage.VOICE_TRIGGER);
- this.checkSpeechOverlayUi_();
- } else {
- this.sendCommandToExtension_(AudioClient.Error.NO_SPEECH_UI);
- }
- } else {
- this.checkSpeechUiRetries_ = 0;
- }
- };
-
-
- /**
- * Checks that the triggered UI is actually displayed.
- * @param {AudioClient.CommandToPage} command Command that was send.
- * @private
- */
- AudioClient.prototype.checkUi_ = function(command) {
- this.uiStatus_[command].timeoutId =
- window.setTimeout(this.failedCheckUi_.bind(this, command),
- AudioClient.RETRY_TIME_MS_);
- };
-
-
- /**
- * Function called when the UI verification is not called in time. Will either
- * retry the command or sends an error message, depending on the number of
- * retries for the command.
- * @param {AudioClient.CommandToPage} command Command that was sent.
- * @private
- */
- AudioClient.prototype.failedCheckUi_ = function(command) {
- if (this.uiStatus_[command].tries++ < AudioClient.MAX_RETRIES) {
- this.sendCommandToPage_(command);
- this.checkUi_(command);
- } else {
- this.sendCommandToExtension_(this.uiStatus_[command].error);
- }
- };
-
-
- /**
- * Confirm that an UI element has been shown.
- * @param {AudioClient.CommandToPage} command UI to confirm.
- * @private
- */
- AudioClient.prototype.verifyUi_ = function(command) {
- if (this.uiStatus_[command].timeoutId) {
- window.clearTimeout(this.uiStatus_[command].timeoutId);
- this.uiStatus_[command].timeoutId = null;
- this.uiStatus_[command].tries = 0;
- }
- };
-
-
- /**
- * Sends a command to the audio manager.
- * @param {string} commandStr command to send to plugin.
- * @private
- */
- AudioClient.prototype.sendCommandToExtension_ = function(commandStr) {
- if (this.port_)
- this.port_.postMessage({'cmd': commandStr});
- };
-
-
- /**
- * Handles a message from the audio manager.
- * @param {{cmd: string}} commandObj Command from the audio manager.
- * @private
- */
- AudioClient.prototype.handleCommandFromExtension_ = function(commandObj) {
- var command = commandObj['cmd'];
- if (command) {
- switch (command) {
- case AudioClient.CommandToPage.HOTWORD_VOICE_TRIGGER:
- this.sendCommandToPage_(command);
- this.checkSpeechOverlayUi_();
- break;
- case AudioClient.CommandToPage.HOTWORD_STARTED:
- this.sendCommandToPage_(command);
- this.checkUi_(command);
- break;
- case AudioClient.CommandToPage.HOTWORD_ENDED:
- this.sendCommandToPage_(command);
- break;
- case AudioClient.CommandToPage.HOTWORD_TIMEOUT:
- this.sendCommandToPage_(command);
- this.checkUi_(command);
- break;
- case AudioClient.CommandToPage.HOTWORD_ERROR:
- this.sendCommandToPage_(command);
- this.checkUi_(command);
- break;
- }
- }
- };
-
-
- /**
- * @param {AudioClient.CommandToPage} commandStr Command to send.
- * @private
- */
- AudioClient.prototype.sendCommandToPage_ = function(commandStr) {
- window.postMessage({'type': commandStr}, '*');
- };
-
-
- /**
- * Handles a message from the html window.
- * @param {!MessageEvent} messageEvent Message event from the window.
- * @private
- */
- AudioClient.prototype.handleCommandFromPage_ = function(messageEvent) {
- if (messageEvent.source == window && messageEvent.data.type) {
- var command = messageEvent.data.type;
- switch (command) {
- case AudioClient.CommandFromPage.SPEECH_START:
- this.speechActive_ = true;
- this.sendCommandToExtension_(command);
- break;
- case AudioClient.CommandFromPage.SPEECH_END:
- this.speechActive_ = false;
- this.sendCommandToExtension_(command);
- break;
- case AudioClient.CommandFromPage.SPEECH_RESET:
- this.speechActive_ = false;
- this.sendCommandToExtension_(command);
- break;
- case 'SPEECH_RESET': // Legacy, for embedded NTP.
- this.speechActive_ = false;
- this.sendCommandToExtension_(AudioClient.CommandFromPage.SPEECH_END);
- break;
- case AudioClient.CommandFromPage.CLICKED_RESUME:
- this.sendCommandToExtension_(command);
- break;
- case AudioClient.CommandFromPage.CLICKED_RESTART:
- this.sendCommandToExtension_(command);
- break;
- case AudioClient.CommandFromPage.CLICKED_DEBUG:
- window.open(AudioClient.HELP_CENTER_URL_, '_blank');
- break;
- case AudioClient.CommandFromPage.SHOWING_HOTWORD_START:
- this.verifyUi_(AudioClient.CommandToPage.HOTWORD_STARTED);
- break;
- case AudioClient.CommandFromPage.SHOWING_ERROR_MESSAGE:
- this.verifyUi_(AudioClient.CommandToPage.HOTWORD_ERROR);
- break;
- case AudioClient.CommandFromPage.SHOWING_TIMEOUT_MESSAGE:
- this.verifyUi_(AudioClient.CommandToPage.HOTWORD_TIMEOUT);
- break;
- }
- }
- };
-
-
- /**
- * Initialize the content script.
- */
- AudioClient.prototype.initialize = function() {
- if (AudioClient.EXISTS_ in window)
- return;
- window[AudioClient.EXISTS_] = true;
-
- // UI verification object.
- this.uiStatus_ = {};
- this.uiStatus_[AudioClient.CommandToPage.HOTWORD_STARTED] = {
- timeoutId: null,
- tries: 0,
- error: AudioClient.Error.NO_HOTWORD_STARTED_UI
- };
- this.uiStatus_[AudioClient.CommandToPage.HOTWORD_TIMEOUT] = {
- timeoutId: null,
- tries: 0,
- error: AudioClient.Error.NO_HOTWORD_TIMEOUT_UI
- };
- this.uiStatus_[AudioClient.CommandToPage.HOTWORD_ERROR] = {
- timeoutId: null,
- tries: 0,
- error: AudioClient.Error.NO_HOTWORD_ERROR_UI
- };
-
- this.handleCommandFromPageFunc_ = this.handleCommandFromPage_.bind(this);
- window.addEventListener('message', this.handleCommandFromPageFunc_, false);
- this.initPort_();
- };
-
-
- /**
- * Initialize the communications port with the audio manager. This
- * function will be also be called again if the audio-manager
- * disconnects for some reason (such as the extension
- * background.html page being reloaded).
- * @private
- */
- AudioClient.prototype.initPort_ = function() {
- this.port_ = chrome.runtime.connect(
- AudioClient.HOTWORD_EXTENSION_ID_,
- {'name': AudioClient.CLIENT_PORT_NAME_});
- // Note that this listen may have to be destroyed manually if AudioClient
- // is ever destroyed on this tab.
- this.port_.onDisconnect.addListener(
- (function(e) {
- if (this.handleCommandFromPageFunc_) {
- window.removeEventListener(
- 'message', this.handleCommandFromPageFunc_, false);
- }
- delete window[AudioClient.EXISTS_];
- }).bind(this));
-
- // See note above.
- this.port_.onMessage.addListener(
- this.handleCommandFromExtension_.bind(this));
-
- if (this.speechActive_)
- this.sendCommandToExtension_(AudioClient.CommandFromPage.SPEECH_START);
- };
-
-
- // Initializes as soon as the code is ready, do not wait for the page.
- new AudioClient().initialize();
-})();
diff --git a/chromium/chrome/browser/resources/hotword_helper/manager.js b/chromium/chrome/browser/resources/hotword_helper/manager.js
deleted file mode 100644
index edaecaef6c9..00000000000
--- a/chromium/chrome/browser/resources/hotword_helper/manager.js
+++ /dev/null
@@ -1,232 +0,0 @@
-// Copyright (c) 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 This extension manages communications between Chrome,
- * Google.com pages and the Chrome Hotword extension.
- *
- * This helper extension is required due to the depoyment plan for Chrome M34:
- *
- * - The hotword extension will be distributed as an externally loaded
- * component extension.
- * - Settings for enabling and disabling the hotword extension has moved to
- * Chrome settings.
- * - Newtab page is served via chrome://newtab/
- *
- */
-
-
-
-/** @constructor */
-var OptInManager = function() {};
-
-
-/**
- * @const {string}
- * @private
- */
-OptInManager.HOTWORD_EXTENSION_ID_ = 'bepbmhgboaologfdajaanbcjmnhjmhfn';
-
-
-/**
- * Test extension ID.
- * @const {string}
- * @private
- */
-OptInManager.TEST_EXTENSION_ID_ = 'cpfhkdbjfdgdebcjlifoldbijinjfifp';
-
-
-/**
- * Commands sent from the page to this content script.
- * @enum {string}
- */
-OptInManager.CommandFromPage = {
- // User has explicitly clicked 'no'.
- CLICKED_NO_OPTIN: 'hcno',
- // User has opted in.
- CLICKED_OPTIN: 'hco',
- // Audio logging is opted in.
- AUDIO_LOGGING_ON: 'alon',
- // Audio logging is opted out.
- AUDIO_LOGGING_OFF: 'aloff',
- // User visited an eligible page.
- PAGE_WAKEUP: 'wu'
-};
-
-
-/**
- * @param {Tab} tab Tab to inject.
- * @param {function(HotwordStatus)} sendResponse Callback function to respond
- * to sender.
- * @param {HotwordStatus} hotwordStatus Status of the hotword extension.
- * @private
- */
-OptInManager.prototype.injectTab_ = function(
- tab, sendResponse, hotwordStatus) {
- var response = {'doNotShowOptinMessage': true};
-
- if (!tab.incognito && hotwordStatus.available) {
- if (!hotwordStatus.enabledSet)
- response = hotwordStatus;
- else if (hotwordStatus.enabled)
- chrome.tabs.executeScript(tab.id, {'file': 'audio_client.js'});
- }
-
- try {
- sendResponse(response);
- } catch (err) {
- // Suppress the exception thrown by sendResponse() when the page doesn't
- // specify a response callback in the call to chrome.runtime.sendMessage().
- // Unfortunately, there doesn't appear to be a way to detect one-way
- // messages without explicitly saying in the message itself. This message
- // is defined as a constant in extensions/renderer/messaging_bindings.cc
- if (err.message == 'Attempting to use a disconnected port object')
- return;
- throw err;
- }
-};
-
-
-/**
- * Handles messages from the helper content script.
- * @param {*} request Message from the sender.
- * @param {MessageSender} sender Information about the sender.
- * @param {function(HotwordStatus)} sendResponse Callback function to respond
- * to sender.
- * @return {boolean} Whether to maintain the port open to call sendResponse.
- * @private
- */
-OptInManager.prototype.handleMessage_ = function(
- request, sender, sendResponse) {
- switch (request.type) {
- case OptInManager.CommandFromPage.PAGE_WAKEUP:
- if (((sender.tab && this.isEligibleUrl(sender.tab.url)) ||
- sender.id == OptInManager.HOTWORD_EXTENSION_ID_ ||
- sender.id == OptInManager.TEST_EXTENSION_ID_) &&
- chrome.hotwordPrivate && chrome.hotwordPrivate.getStatus) {
- chrome.hotwordPrivate.getStatus(
- this.injectTab_.bind(
- this,
- request.tab || sender.tab || {incognito: true},
- sendResponse));
- return true;
- }
- break;
- case OptInManager.CommandFromPage.CLICKED_OPTIN:
- if (chrome.hotwordPrivate && chrome.hotwordPrivate.setEnabled &&
- chrome.hotwordPrivate.getStatus) {
- chrome.hotwordPrivate.setEnabled(true);
- chrome.hotwordPrivate.getStatus(
- this.injectTab_.bind(this, sender.tab, sendResponse));
- return true;
- }
- break;
- // User has explicitly clicked 'no thanks'.
- case OptInManager.CommandFromPage.CLICKED_NO_OPTIN:
- if (chrome.hotwordPrivate && chrome.hotwordPrivate.setEnabled) {
- chrome.hotwordPrivate.setEnabled(false);
- }
- break;
- // Information regarding the audio logging preference was sent.
- case OptInManager.CommandFromPage.AUDIO_LOGGING_ON:
- if (chrome.hotwordPrivate &&
- chrome.hotwordPrivate.setAudioLoggingEnabled) {
- chrome.hotwordPrivate.setAudioLoggingEnabled(true);
- }
- break;
- case OptInManager.CommandFromPage.AUDIO_LOGGING_OFF:
- if (chrome.hotwordPrivate &&
- chrome.hotwordPrivate.setAudioLoggingEnabled) {
- chrome.hotwordPrivate.setAudioLoggingEnabled(false);
- }
- break;
- default:
- break;
- }
- return false;
-};
-
-/**
- * Helper function to test URLs as being valid for running the
- * hotwording extension. It's used by isEligibleUrl to make that
- * function clearer.
- * @param {string} url URL to check.
- * @param {string} base Base URL to compare against..
- * @return {boolean} True if url is an eligible hotword URL.
- */
-OptInManager.prototype.checkEligibleUrl = function(url, base) {
- if (!url)
- return false;
-
- if (url === base ||
- url === base + '/' ||
- url.indexOf(base + '/_/chrome/newtab?') === 0 || // Appcache NTP.
- url.indexOf(base + '/?') === 0 ||
- url.indexOf(base + '/#') === 0 ||
- url.indexOf(base + '/webhp') === 0 ||
- url.indexOf(base + '/search') === 0) {
- return true;
- }
- return false;
-
-};
-
-/**
- * Determines if a URL is eligible for hotwording. For now, the
- * valid pages are the Google HP and SERP (this will include the NTP).
- * @param {string} url URL to check.
- * @return {boolean} True if url is an eligible hotword URL.
- */
-OptInManager.prototype.isEligibleUrl = function(url) {
- if (!url)
- return false;
-
- // More URLs will be added in the future so leaving this as an array.
- var baseUrls = [
- 'chrome://newtab'
- ];
- var baseGoogleUrls = [
- 'https://www.google.',
- 'https://encrypted.google.'
- ];
- var tlds = [
- 'com',
- 'co.uk',
- 'de',
- 'fr',
- 'ru'
- ];
-
- // Check URLs which do not have locale-based TLDs first.
- if (this.checkEligibleUrl(url, baseUrls[0]))
- return true;
-
- // Check URLs with each type of local-based TLD.
- for (var i = 0; i < baseGoogleUrls.length; i++) {
- for (var j = 0; j < tlds.length; j++) {
- var base = baseGoogleUrls[i] + tlds[j];
- if (this.checkEligibleUrl(url, base))
- return true;
- }
- }
- return false;
-};
-
-
-/**
- * Initializes the extension.
- */
-OptInManager.prototype.initialize = function() {
- // TODO(rlp): Possibly remove the next line. It's proably not used, but
- // leaving for now to be safe. We should remove it once all messsage
- // relaying is removed form the content scripts.
- chrome.runtime.onMessage.addListener(this.handleMessage_.bind(this));
- chrome.runtime.onMessageExternal.addListener(
- this.handleMessage_.bind(this));
-};
-
-
-new OptInManager().initialize();
diff --git a/chromium/chrome/browser/resources/hotword_helper/manifest.json b/chromium/chrome/browser/resources/hotword_helper/manifest.json
deleted file mode 100644
index 9439be30ef2..00000000000
--- a/chromium/chrome/browser/resources/hotword_helper/manifest.json
+++ /dev/null
@@ -1,41 +0,0 @@
-{
- // Extension ID: dnhpdliibojhegemfjheidglijccjfmc
- "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDagiQy1VGkO2CHJSjVh7eU5GtuBuOlg2/cTZt7203AcevqpcDd+65S2/yd9KAELYcU6pK8nHVGYBMI6s0u+0RgXfIJ0eFOlTlgfAQWHvg8ovHtJlFJd1COrOkbntD9+s9Jobr3ldmow87aZF1bVHUY4khVP56cZe6adlVw2wK31QIDAQAB",
-
- "name": "hotword helper",
- "version": "0.0.2.0",
- "manifest_version": 2,
-
- "background": {
- "scripts": ["manager.js"],
- "persistent": false
- },
-
- "permissions": [
- "*://*.google.com/*",
- "*://*.google.ru/*",
- "*://*.google.co.uk/*",
- "*://*.google.fr/*",
- "*://*.google.de/*",
- "chrome://newtab/",
- "hotwordPrivate",
- "tabs"
- ],
-
- "externally_connectable": {
- "matches": [
- "*://*.google.com/*",
- "*://*.google.ru/*",
- "*://*.google.co.uk/*",
- "*://*.google.fr/*",
- "*://*.google.de/*",
- "chrome://newtab/"
- ],
- "ids": [
- // Test extension.
- "cpfhkdbjfdgdebcjlifoldbijinjfifp"
- ]
- },
-
- "minimum_chrome_version": "32"
-}
diff --git a/chromium/chrome/browser/resources/identity_internals.html b/chromium/chrome/browser/resources/identity_internals.html
index b6d1d613485..fcfcc431028 100644
--- a/chromium/chrome/browser/resources/identity_internals.html
+++ b/chromium/chrome/browser/resources/identity_internals.html
@@ -1,8 +1,9 @@
-<!DOCTYPE HTML>
-<html i18n-values="dir:textdirection">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
<head>
<meta charset="utf-8">
<title i18n-content="tokenCacheHeader"></title>
+ <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="identity_internals.css">
<script src="chrome://resources/js/cr.js"></script>
<script src="chrome://resources/js/load_time_data.js"></script>
@@ -11,9 +12,9 @@
<script src="strings.js"></script>
<script src="identity_internals.js"></script>
</head>
-<body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<body>
<h2 class="header" i18n-content="tokenCacheHeader"></h2>
<div id="token-list"></div>
- <script src="chrome://resources/js/i18n_template2.js"></script>
+ <script src="chrome://resources/js/i18n_template.js"></script>
</body>
</html>
diff --git a/chromium/chrome/browser/resources/identity_internals.js b/chromium/chrome/browser/resources/identity_internals.js
index 7e7a01f0b29..ae036234e6b 100644
--- a/chromium/chrome/browser/resources/identity_internals.js
+++ b/chromium/chrome/browser/resources/identity_internals.js
@@ -197,7 +197,7 @@ cr.define('identity_internals', function() {
/**
* Callback function that removes a token from UI once it has been revoked.
- * @param {!Array.<string>} accessTokens Array with a single element, which is
+ * @param {!Array<string>} accessTokens Array with a single element, which is
* an access token to be removed.
*/
function tokenRevokeDone(accessTokens) {
diff --git a/chromium/chrome/browser/resources/identity_scope_approval_dialog/scope_approval_dialog.css b/chromium/chrome/browser/resources/identity_scope_approval_dialog/scope_approval_dialog.css
index ae5495cc597..8e3da19818a 100644
--- a/chromium/chrome/browser/resources/identity_scope_approval_dialog/scope_approval_dialog.css
+++ b/chromium/chrome/browser/resources/identity_scope_approval_dialog/scope_approval_dialog.css
@@ -21,8 +21,8 @@ body {
.titlebar-close-button {
background-image: -webkit-image-set(
- url('chrome://theme/IDR_CLOSE_DIALOG') 1x,
- url('chrome://theme/IDR_CLOSE_DIALOG@2x') 2x);
+ url(chrome://theme/IDR_CLOSE_DIALOG) 1x,
+ url(chrome://theme/IDR_CLOSE_DIALOG@2x) 2x);
-webkit-app-region: no-drag;
height: 14px;
margin: 6px;
@@ -33,14 +33,14 @@ body {
.titlebar-close-button:active {
background-image: -webkit-image-set(
- url('chrome://theme/IDR_CLOSE_DIALOG_P') 1x,
- url('chrome://theme/IDR_CLOSE_DIALOG_P@2x') 2x);
+ url(chrome://theme/IDR_CLOSE_DIALOG_P) 1x,
+ url(chrome://theme/IDR_CLOSE_DIALOG_P@2x) 2x);
}
.titlebar-close-button:hover {
background-image: -webkit-image-set(
- url('chrome://theme/IDR_CLOSE_DIALOG_H') 1x,
- url('chrome://theme/IDR_CLOSE_DIALOG_H@2x') 2x);
+ url(chrome://theme/IDR_CLOSE_DIALOG_H) 1x,
+ url(chrome://theme/IDR_CLOSE_DIALOG_H@2x) 2x);
}
.content {
diff --git a/chromium/chrome/browser/resources/identity_scope_approval_dialog/scope_approval_dialog.html b/chromium/chrome/browser/resources/identity_scope_approval_dialog/scope_approval_dialog.html
index f1322e4a879..a5946ae4dbf 100644
--- a/chromium/chrome/browser/resources/identity_scope_approval_dialog/scope_approval_dialog.html
+++ b/chromium/chrome/browser/resources/identity_scope_approval_dialog/scope_approval_dialog.html
@@ -1,5 +1,6 @@
<html>
<head>
+ <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" type="text/css" href="scope_approval_dialog.css">
<script src="scope_approval_dialog.js"></script>
</head>
diff --git a/chromium/chrome/browser/resources/inline_login/inline_login.css b/chromium/chrome/browser/resources/inline_login/inline_login.css
index 10bda6fc30b..0c8c5f20e04 100644
--- a/chromium/chrome/browser/resources/inline_login/inline_login.css
+++ b/chromium/chrome/browser/resources/inline_login/inline_login.css
@@ -30,7 +30,6 @@ body,
display: -webkit-box;
}
-#contents.loading #signin-frame,
#contents:not(.loading) #spinner-container {
display: none;
}
diff --git a/chromium/chrome/browser/resources/inline_login/inline_login.html b/chromium/chrome/browser/resources/inline_login/inline_login.html
index 71430c61efb..3699b9dc7a9 100644
--- a/chromium/chrome/browser/resources/inline_login/inline_login.html
+++ b/chromium/chrome/browser/resources/inline_login/inline_login.html
@@ -1,7 +1,8 @@
-<!DOCTYPE HTML>
-<html i18n-values="dir:textdirection">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
<head>
<title i18n-content="title"></title>
+ <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="chrome://resources/css/spinner.css">
<link rel="stylesheet" href="chrome://chrome-signin/inline_login.css">
<script src="chrome://resources/js/cr.js"></script>
@@ -12,13 +13,14 @@
<script src="chrome://chrome-signin/inline_login.js"></script>
<script src="chrome://chrome-signin/strings.js"></script>
</head>
-<body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<body>
<div id="contents" class="loading">
- <iframe id="signin-frame" name="signin-frame" frameborder="0" scrolling="no"></iframe>
+ <iframe id="signin-frame" name="signin-frame" frameborder="0"
+ scrolling="no"></iframe>
<div id="spinner-container">
<div class="spinner"></div>
</div>
</div>
- <script src="chrome://resources/js/i18n_template2.js"></script>
+ <script src="chrome://resources/js/i18n_template.js"></script>
</body>
</html>
diff --git a/chromium/chrome/browser/resources/inline_login/inline_login.js b/chromium/chrome/browser/resources/inline_login/inline_login.js
index fe41e0b1b93..684216a9b2b 100644
--- a/chromium/chrome/browser/resources/inline_login/inline_login.js
+++ b/chromium/chrome/browser/resources/inline_login/inline_login.js
@@ -20,47 +20,30 @@ cr.define('inline.login', function() {
*/
var authReadyFired;
- /**
- * A listener class for authentication events from GaiaAuthHost.
- * @constructor
- * @implements {cr.login.GaiaAuthHost.Listener}
- */
- function GaiaAuthHostListener() {}
-
- /** @override */
- GaiaAuthHostListener.prototype.onSuccess = function(credentials) {
- onAuthCompleted(credentials);
- };
+ function onResize(e) {
+ chrome.send('switchToFullTab', [e.detail]);
+ }
- /** @override */
- GaiaAuthHostListener.prototype.onReady = function(e) {
- onAuthReady();
- };
+ function onAuthReady(e) {
+ $('contents').classList.toggle('loading', false);
+ authReadyFired = true;
+ }
- /** @override */
- GaiaAuthHostListener.prototype.onResize = function(url) {
- chrome.send('switchToFullTab', url);
- };
+ function onDropLink(e) {
+ // Navigate to the dropped link.
+ window.location.href = e.detail;
+ }
- /** @override */
- GaiaAuthHostListener.prototype.onNewWindow = function(e) {
- window.open(e.targetUrl, '_blank');
- e.window.discard();
- };
+ function onNewWindow(e) {
+ window.open(e.detail.targetUrl, '_blank');
+ e.detail.window.discard();
+ }
- /**
- * Handler of auth host 'ready' event.
- */
- function onAuthReady() {
- $('contents').classList.toggle('loading', false);
- authReadyFired = true;
+ function onAuthCompleted(e) {
+ completeLogin(e.detail);
}
- /**
- * Handler of auth host 'completed' event.
- * @param {!Object} credentials Credentials of the completed authentication.
- */
- function onAuthCompleted(credentials) {
+ function completeLogin(credentials) {
chrome.send('completeLogin', [credentials]);
$('contents').classList.toggle('loading', true);
}
@@ -69,10 +52,12 @@ cr.define('inline.login', function() {
* Initialize the UI.
*/
function initialize() {
- authExtHost = new cr.login.GaiaAuthHost(
- 'signin-frame', new GaiaAuthHostListener());
+ authExtHost = new cr.login.GaiaAuthHost('signin-frame');
+ authExtHost.addEventListener('dropLink', onDropLink);
authExtHost.addEventListener('ready', onAuthReady);
-
+ authExtHost.addEventListener('newWindow', onNewWindow);
+ authExtHost.addEventListener('resize', onResize);
+ authExtHost.addEventListener('authCompleted', onAuthCompleted);
chrome.send('initialize');
}
@@ -81,7 +66,9 @@ cr.define('inline.login', function() {
* @param {Object} data Parameters for auth extension.
*/
function loadAuthExtension(data) {
- authExtHost.load(data.authMode, data, onAuthCompleted);
+ // TODO(rogerta): in when using webview, the |completeLogin| argument
+ // is ignored. See addEventListener() call above.
+ authExtHost.load(data.authMode, data, completeLogin);
$('contents').classList.toggle('loading',
data.authMode != cr.login.GaiaAuthHost.AuthMode.DESKTOP ||
data.constrained == '1');
diff --git a/chromium/chrome/browser/resources/inline_login/new_inline_login.html b/chromium/chrome/browser/resources/inline_login/new_inline_login.html
index 68da6c223cd..2acb5c5944f 100644
--- a/chromium/chrome/browser/resources/inline_login/new_inline_login.html
+++ b/chromium/chrome/browser/resources/inline_login/new_inline_login.html
@@ -1,7 +1,8 @@
-<!DOCTYPE HTML>
-<html i18n-values="dir:textdirection">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
<head>
<title i18n-content="title"></title>
+ <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="chrome://resources/css/spinner.css">
<link rel="stylesheet" href="chrome://chrome-signin/inline_login.css">
<script src="chrome://resources/js/cr.js"></script>
@@ -12,13 +13,13 @@
<script src="chrome://chrome-signin/inline_login.js"></script>
<script src="chrome://chrome-signin/strings.js"></script>
</head>
-<body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<body>
<div id="contents" class="loading">
- <webview id="signin-frame" name="signin-frame"></webview>
+ <webview id="signin-frame" name="signin-frame" allowscaling></webview>
<div id="spinner-container">
<div class="spinner"></div>
</div>
</div>
- <script src="chrome://resources/js/i18n_template2.js"></script>
+ <script src="chrome://resources/js/i18n_template.js"></script>
</body>
</html>
diff --git a/chromium/chrome/browser/resources/inspect/inspect.css b/chromium/chrome/browser/resources/inspect/inspect.css
index 49bd50080eb..0398c2959d5 100644
--- a/chromium/chrome/browser/resources/inspect/inspect.css
+++ b/chromium/chrome/browser/resources/inspect/inspect.css
@@ -1,16 +1,20 @@
-/*
- * Copyright (c) 2012 The Chromium Authors. All rights reserved.
+/* Copyright (c) 2012 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.
- */
+ * found in the LICENSE file. */
+
+* {
+ box-sizing: border-box;
+}
body {
color: rgb(48, 57, 66);
- font-family: Arial, sans-serif;
font-size: 13px;
margin: 0;
min-width: 47em;
- padding: 20px 20px 65px 0;
+}
+
+.hidden {
+ display: none !important;
}
img {
@@ -19,7 +23,7 @@ img {
padding-left: 2px;
padding-right: 5px;
vertical-align: top;
- width: 16px;
+ width: 23px;
}
#container {
@@ -28,11 +32,16 @@ img {
}
#navigation {
+ max-height: 100vh;
+ overflow: auto;
+ padding-top: 20px;
width: 150px;
}
#content {
-webkit-flex: 1;
+ max-height: 100vh;
+ overflow: auto;
}
#caption {
@@ -69,14 +78,23 @@ img {
color: #999;
}
+#content > div {
+ padding: 54px 20px 65px 0;
+}
#content > div:not(.selected) {
display: none;
}
.content-header {
+ background: linear-gradient(white, white 40%, rgba(255, 255, 255, 0.92));
border-bottom: 1px solid #eee;
font-size: 150%;
- padding-bottom: 10px;
+ left: 150px;
+ padding: 20px 0 10px 0;
+ position: fixed;
+ right: 20px;
+ top: 0;
+ z-index: 1;
}
#devices-help {
@@ -96,7 +114,7 @@ img {
}
.device-serial {
- color: #888;
+ color: #999;
font-size: 80%;
margin-left: 6px;
}
@@ -108,9 +126,9 @@ img {
}
.port-icon {
- -webkit-border-radius: 6px;
background-color: rgb(64, 192, 64);
border: 0 solid transparent;
+ border-radius: 6px;
height: 12px;
margin: 2px;
width: 12px;
@@ -120,11 +138,6 @@ img {
background-color: rgb(224, 32, 32);
}
-.port-icon.connected {
- -webkit-transform: scale(1.2);
- background-color: rgb(0, 255, 0);
-}
-
.port-icon.transient {
-webkit-transform: scale(1.2);
background-color: orange;
@@ -139,7 +152,7 @@ img {
align-items: center;
display: flex;
flex-flow: row wrap;
- min-height: 23px;
+ min-height: 33px;
padding-top: 10px;
}
@@ -148,9 +161,14 @@ img {
font-weight: bold;
}
+.browser-header > .browser-user {
+ color: #999;
+ margin-left: 6px;
+}
+
.used-for-port-forwarding {
- background-image: -webkit-image-set(url('chrome://theme/IDR_INFO') 1x,
- url('chrome://theme/IDR_INFO@2x') 2x);
+ background-image: -webkit-image-set(url(chrome://theme/IDR_INFO) 1x,
+ url(chrome://theme/IDR_INFO@2x) 2x);
height: 15px;
margin-left: 20px;
width: 15px;
@@ -181,6 +199,7 @@ img {
.webview-thumbnail {
display: inline-block;
+ flex-shrink: 0;
margin-right: 5px;
overflow: hidden;
position: relative;
@@ -212,7 +231,7 @@ img {
}
.url {
- color: #A0A0A0;
+ color: #999;
}
.list {
@@ -271,21 +290,9 @@ img {
margin-left: 15px;
}
-#port-forwarding-overlay {
- -webkit-box-align: center;
- -webkit-box-pack: center;
- background-color: rgba(255, 255, 255, 0.75);
- bottom: 0;
- display: -webkit-box;
- left: 0;
- position: absolute;
- right: 0;
- top: 0;
-}
-
.warning {
- background-image: -webkit-image-set(url('chrome://theme/IDR_WARNING') 1x,
- url('chrome://theme/IDR_WARNING@2x') 2x);
+ background-image: -webkit-image-set(url(chrome://theme/IDR_WARNING) 1x,
+ url(chrome://theme/IDR_WARNING@2x) 2x);
background-position: 0 center;
background-repeat: no-repeat;
background-size: 24px 21px;
@@ -293,31 +300,36 @@ img {
padding-left: 25px;
}
-#port-forwarding-overlay:not(.open) {
- display: none;
+#port-forwarding-config::backdrop {
+ background-color: rgba(255, 255, 255, 0.75);
}
#port-forwarding-config {
- -webkit-border-radius: 3px;
background: white;
+ border: 0;
+ border-radius: 3px;
box-shadow: 0 4px 23px 5px rgba(0, 0, 0, 0.2), 0 2px 6px rgba(0,0,0,0.15);
color: #333;
padding: 17px 17px 12px;
position: relative;
}
+#port-forwarding-enable {
+ vertical-align: middle;
+}
+
.close-button {
- background-image: url('chrome://theme/IDR_CLOSE_DIALOG');
+ background-image: url(chrome://theme/IDR_CLOSE_DIALOG);
height: 14px;
width: 14px;
}
.close-button:active {
- background-image: url('chrome://theme/IDR_CLOSE_DIALOG_P');
+ background-image: url(chrome://theme/IDR_CLOSE_DIALOG_P);
}
.close-button:hover {
- background-image: url('chrome://theme/IDR_CLOSE_DIALOG_H');
+ background-image: url(chrome://theme/IDR_CLOSE_DIALOG_H);
}
#port-forwarding-config > .close-button {
diff --git a/chromium/chrome/browser/resources/inspect/inspect.html b/chromium/chrome/browser/resources/inspect/inspect.html
index b2f5aad6ea9..8b90983c53e 100644
--- a/chromium/chrome/browser/resources/inspect/inspect.html
+++ b/chromium/chrome/browser/resources/inspect/inspect.html
@@ -1,4 +1,4 @@
-<!DOCTYPE HTML>
+<!doctype html>
<html>
<!--
Copyright (c) 2012 The Chromium Authors. All rights reserved.
@@ -8,7 +8,7 @@ found in the LICENSE file.
<head>
<meta charset="utf-8">
<title>Inspect with Chrome Developer Tools</title>
-
+<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="inspect.css">
<script src="chrome://resources/js/util.js"></script>
<script src="inspect.js"></script>
@@ -69,24 +69,23 @@ found in the LICENSE file.
</div>
</div>
</div>
-<div id="port-forwarding-overlay">
- <div id="port-forwarding-config">
- <div id="port-forwarding-config-close" class="close-button"></div>
- <div id="port-forwarding-config-title">Port forwarding settings</div>
- <div id="port-forwarding-config-list">
- </div>
- <div id="port-forwarding-message">
- Define the listening port on your device that maps to a port accessible
- from your development machine.
- <a href="https://developers.google.com/chrome-developer-tools/docs/remote-debugging#reverse-port-forwarding"
- target="_blank">Learn more</a>
- </div>
- <div id="port-forwarding-config-buttons">
- <input id="port-forwarding-enable" type="checkbox" disabled/>
- <label for="port-forwarding-enable">Enable port forwarding</label>
- <button id="port-forwarding-config-done">Done</button>
- </div>
+<dialog id="port-forwarding-config">
+ <div id="port-forwarding-config-close" class="close-button"></div>
+ <div id="port-forwarding-config-title">Port forwarding settings</div>
+ <div id="port-forwarding-config-list">
</div>
-</div>
+ <div id="port-forwarding-message">
+ Define the listening port on your device that maps to a port accessible
+ from your development machine.
+ <a href="https://developer.chrome.com/devtools/docs/remote-debugging#reverse-port-forwarding"
+ target="_blank">Learn more</a>
+ </div>
+ <div id="port-forwarding-config-buttons">
+ <label>
+ <input id="port-forwarding-enable" type="checkbox" disabled>Enable port forwarding
+ </label>
+ <button id="port-forwarding-config-done">Done</button>
+ </div>
+</dialog>
</body>
</html>
diff --git a/chromium/chrome/browser/resources/inspect/inspect.js b/chromium/chrome/browser/resources/inspect/inspect.js
index a99d3c44fd5..f456b66c0e2 100644
--- a/chromium/chrome/browser/resources/inspect/inspect.js
+++ b/chromium/chrome/browser/resources/inspect/inspect.js
@@ -6,8 +6,11 @@ var MIN_VERSION_TAB_CLOSE = 25;
var MIN_VERSION_TARGET_ID = 26;
var MIN_VERSION_NEW_TAB = 29;
var MIN_VERSION_TAB_ACTIVATE = 30;
+var WEBRTC_SERIAL = 'WEBRTC';
var queryParamsObject = {};
+var browserInspector;
+var browserInspectorTitle;
(function() {
var queryParams = window.location.search;
@@ -19,6 +22,13 @@ for (var i = 0; i < params.length; ++i) {
queryParamsObject[pair[0]] = pair[1];
}
+if ('trace' in queryParamsObject || 'tracing' in queryParamsObject) {
+ browserInspector = 'chrome://tracing';
+ browserInspectorTitle = 'trace';
+} else {
+ browserInspector = queryParamsObject['browser-inspector'];
+ browserInspectorTitle = 'inspect';
+}
})();
function sendCommand(command, args) {
@@ -132,25 +142,60 @@ function alreadyDisplayed(element, data) {
return false;
}
+function updateBrowserVisibility(browserSection) {
+ var icon = browserSection.querySelector('.used-for-port-forwarding');
+ browserSection.hidden = !browserSection.querySelector('.open') &&
+ !browserSection.querySelector('.row') &&
+ !browserInspector &&
+ (!icon || icon.hidden);
+}
+
+function updateUsernameVisibility(deviceSection) {
+ var users = new Set();
+ var browsers = deviceSection.querySelectorAll('.browser');
+
+ Array.prototype.forEach.call(browsers, function(browserSection) {
+ if (!browserSection.hidden) {
+ var browserUser = browserSection.querySelector('.browser-user');
+ if (browserUser)
+ users.add(browserUser.textContent);
+ }
+ });
+ var hasSingleUser = users.size <= 1;
+
+ Array.prototype.forEach.call(browsers, function(browserSection) {
+ var browserUser = browserSection.querySelector('.browser-user');
+ if (browserUser)
+ browserUser.hidden = hasSingleUser;
+ });
+}
+
function populateRemoteTargets(devices) {
if (!devices)
return;
- if (window.modal) {
+ if ($('port-forwarding-config').open) {
window.holdDevices = devices;
return;
}
- function insertChildSortedById(parent, child) {
- for (var sibling = parent.firstElementChild;
- sibling;
- sibling = sibling.nextElementSibling) {
- if (sibling.id > child.id) {
- parent.insertBefore(child, sibling);
+ function browserCompare(a, b) {
+ if (a.adbBrowserName != b.adbBrowserName)
+ return a.adbBrowserName < b.adbBrowserName;
+ if (a.adbBrowserVersion != b.adbBrowserVersion)
+ return a.adbBrowserVersion < b.adbBrowserVersion;
+ return a.id < b.id;
+ }
+
+ function insertBrowser(browserList, browser) {
+ for (var sibling = browserList.firstElementChild; sibling;
+ sibling = sibling.nextElementSibling) {
+ if (browserCompare(browser, sibling)) {
+ browserList.insertBefore(browser, sibling);
return;
}
}
- parent.appendChild(child);
+ browserList.appendChild(browser);
}
var deviceList = $('devices-list');
@@ -189,9 +234,13 @@ function populateRemoteTargets(devices) {
var deviceSerial = document.createElement('div');
deviceSerial.className = 'device-serial';
- deviceSerial.textContent = '#' + device.adbSerial.toUpperCase();
+ var serial = device.adbSerial.toUpperCase();
+ deviceSerial.textContent = '#' + serial;
deviceHeader.appendChild(deviceSerial);
+ if (serial === WEBRTC_SERIAL)
+ deviceHeader.classList.add('hidden');
+
var devicePorts = document.createElement('div');
devicePorts.className = 'device-ports';
deviceHeader.appendChild(devicePorts);
@@ -222,11 +271,7 @@ function populateRemoteTargets(devices) {
for (var b = 0; b < device.browsers.length; b++) {
var browser = device.browsers[b];
-
var majorChromeVersion = browser.adbBrowserChromeVersion;
-
- var incompatibleVersion = browser.hasOwnProperty('compatibleVersion') &&
- !browser.compatibleVersion;
var pageList;
var browserSection = $(browser.id);
if (browserSection) {
@@ -235,7 +280,7 @@ function populateRemoteTargets(devices) {
browserSection = document.createElement('div');
browserSection.id = browser.id;
browserSection.className = 'browser';
- insertChildSortedById(browserList, browserSection);
+ insertBrowser(browserList, browserSection);
var browserHeader = document.createElement('div');
browserHeader.className = 'browser-header';
@@ -246,9 +291,15 @@ function populateRemoteTargets(devices) {
browserName.textContent = browser.adbBrowserName;
if (browser.adbBrowserVersion)
browserName.textContent += ' (' + browser.adbBrowserVersion + ')';
+ if (browser.adbBrowserUser) {
+ var browserUser = document.createElement('div');
+ browserUser.className = 'browser-user';
+ browserUser.textContent = browser.adbBrowserUser;
+ browserHeader.appendChild(browserUser);
+ }
browserSection.appendChild(browserHeader);
- if (!incompatibleVersion && majorChromeVersion >= MIN_VERSION_NEW_TAB) {
+ if (majorChromeVersion >= MIN_VERSION_NEW_TAB) {
var newPage = document.createElement('div');
newPage.className = 'open';
@@ -282,24 +333,6 @@ function populateRemoteTargets(devices) {
'forwarding. Closing it will drop current connections.';
browserHeader.appendChild(portForwardingInfo);
- if (incompatibleVersion) {
- var warningSection = document.createElement('div');
- warningSection.className = 'warning';
- warningSection.textContent =
- 'You may need a newer version of desktop Chrome. ' +
- 'Please try Chrome ' + browser.adbBrowserVersion + ' or later.';
- browserSection.appendChild(warningSection);
- }
-
- var browserInspector;
- var browserInspectorTitle;
- if ('trace' in queryParamsObject || 'tracing' in queryParamsObject) {
- browserInspector = 'chrome://tracing';
- browserInspectorTitle = 'trace';
- } else {
- browserInspector = queryParamsObject['browser-inspector'];
- browserInspectorTitle = 'inspect';
- }
if (browserInspector) {
var link = document.createElement('span');
link.classList.add('action');
@@ -317,35 +350,36 @@ function populateRemoteTargets(devices) {
browserSection.appendChild(pageList);
}
- if (incompatibleVersion || alreadyDisplayed(browserSection, browser))
- continue;
-
- pageList.textContent = '';
- for (var p = 0; p < browser.pages.length; p++) {
- var page = browser.pages[p];
- // Attached targets have no unique id until Chrome 26. For such targets
- // it is impossible to activate existing DevTools window.
- page.hasNoUniqueId = page.attached &&
- (majorChromeVersion && majorChromeVersion < MIN_VERSION_TARGET_ID);
- var row = addTargetToList(page, pageList, ['name', 'url']);
- if (page['description'])
- addWebViewDetails(row, page);
- else
- addFavicon(row, page);
- if (majorChromeVersion >= MIN_VERSION_TAB_ACTIVATE) {
- addActionLink(row, 'focus tab',
- sendTargetCommand.bind(null, 'activate', page), false);
- }
- if (majorChromeVersion) {
- addActionLink(row, 'reload',
- sendTargetCommand.bind(null, 'reload', page), page.attached);
- }
- if (majorChromeVersion >= MIN_VERSION_TAB_CLOSE) {
- addActionLink(row, 'close',
- sendTargetCommand.bind(null, 'close', page), false);
+ if (!alreadyDisplayed(browserSection, browser)) {
+ pageList.textContent = '';
+ for (var p = 0; p < browser.pages.length; p++) {
+ var page = browser.pages[p];
+ // Attached targets have no unique id until Chrome 26. For such
+ // targets it is impossible to activate existing DevTools window.
+ page.hasNoUniqueId = page.attached &&
+ majorChromeVersion && majorChromeVersion < MIN_VERSION_TARGET_ID;
+ var row = addTargetToList(page, pageList, ['name', 'url']);
+ if (page['description'])
+ addWebViewDetails(row, page);
+ else
+ addFavicon(row, page);
+ if (majorChromeVersion >= MIN_VERSION_TAB_ACTIVATE) {
+ addActionLink(row, 'focus tab',
+ sendTargetCommand.bind(null, 'activate', page), false);
+ }
+ if (majorChromeVersion) {
+ addActionLink(row, 'reload',
+ sendTargetCommand.bind(null, 'reload', page), page.attached);
+ }
+ if (majorChromeVersion >= MIN_VERSION_TAB_CLOSE) {
+ addActionLink(row, 'close',
+ sendTargetCommand.bind(null, 'close', page), false);
+ }
}
}
+ updateBrowserVisibility(browserSection);
}
+ updateUsernameVisibility(deviceSection);
}
}
@@ -576,7 +610,7 @@ function initSettings() {
$('port-forwarding-config-close').addEventListener(
'click', closePortForwardingConfig);
$('port-forwarding-config-done').addEventListener(
- 'click', commitPortForwardingConfig.bind(true));
+ 'click', commitPortForwardingConfig.bind(null, true));
}
function enableDiscoverUsbDevices(event) {
@@ -603,68 +637,37 @@ function handleKey(event) {
commitPortForwardingConfig(true);
}
break;
-
- case 27:
- commitPortForwardingConfig(true);
- break;
}
}
-function setModal(dialog) {
- dialog.deactivatedNodes = Array.prototype.filter.call(
- document.querySelectorAll('*'),
- function(n) {
- return n != dialog && !dialog.contains(n) && n.tabIndex >= 0;
- });
-
- dialog.tabIndexes = dialog.deactivatedNodes.map(
- function(n) { return n.getAttribute('tabindex'); });
-
- dialog.deactivatedNodes.forEach(function(n) { n.tabIndex = -1; });
- window.modal = dialog;
-}
-
-function unsetModal(dialog) {
- for (var i = 0; i < dialog.deactivatedNodes.length; i++) {
- var node = dialog.deactivatedNodes[i];
- if (dialog.tabIndexes[i] === null)
- node.removeAttribute('tabindex');
- else
- node.setAttribute('tabindex', dialog.tabIndexes[i]);
- }
-
- if (window.holdDevices) {
- populateRemoteTargets(window.holdDevices);
- delete window.holdDevices;
- }
-
- delete dialog.deactivatedNodes;
- delete dialog.tabIndexes;
- delete window.modal;
-}
-
function openPortForwardingConfig() {
loadPortForwardingConfig(window.portForwardingConfig);
- $('port-forwarding-overlay').classList.add('open');
+ $('port-forwarding-config').showModal();
document.addEventListener('keyup', handleKey);
+ $('port-forwarding-config').onclose = function() {
+ commitPortForwardingConfig(true);
+ };
var freshPort = document.querySelector('.fresh .port');
if (freshPort)
freshPort.focus();
else
$('port-forwarding-config-done').focus();
-
- setModal($('port-forwarding-overlay'));
}
function closePortForwardingConfig() {
- if (!$('port-forwarding-overlay').classList.contains('open'))
+ if (!$('port-forwarding-config').open)
return;
- $('port-forwarding-overlay').classList.remove('open');
+ $('port-forwarding-config').onclose = null;
+ $('port-forwarding-config').close();
document.removeEventListener('keyup', handleKey);
- unsetModal($('port-forwarding-overlay'));
+
+ if (window.holdDevices) {
+ populateRemoteTargets(window.holdDevices);
+ delete window.holdDevices;
+ }
}
function loadPortForwardingConfig(config) {
@@ -883,11 +886,7 @@ function populatePortStatus(devicesStatusMap) {
var portIcon = document.createElement('div');
portIcon.className = 'port-icon';
// status === 0 is the default (connected) state.
- // Positive values correspond to the tunnelling connection count
- // (in DEBUG_DEVTOOLS mode).
- if (status > 0)
- portIcon.classList.add('connected');
- else if (status === -1 || status === -2)
+ if (status === -1 || status === -2)
portIcon.classList.add('transient');
else if (status < 0)
portIcon.classList.add('error');
@@ -896,8 +895,6 @@ function populatePortStatus(devicesStatusMap) {
var portNumber = document.createElement('div');
portNumber.className = 'port-number';
portNumber.textContent = ':' + port;
- if (status > 0)
- portNumber.textContent += '(' + status + ')';
devicePorts.appendChild(portNumber);
}
@@ -905,10 +902,13 @@ function populatePortStatus(devicesStatusMap) {
var icon = browserSection.querySelector('.used-for-port-forwarding');
if (icon)
icon.hidden = (browserSection.id !== deviceStatus.browserId);
+ updateBrowserVisibility(browserSection);
}
Array.prototype.forEach.call(
deviceSection.querySelectorAll('.browser'), updatePortForwardingInfo);
+
+ updateUsernameVisibility(deviceSection);
}
function clearPorts(deviceSection) {
diff --git a/chromium/chrome/browser/resources/instant/instant.css b/chromium/chrome/browser/resources/instant/instant.css
index 4e47245295a..4c39e13104a 100644
--- a/chromium/chrome/browser/resources/instant/instant.css
+++ b/chromium/chrome/browser/resources/instant/instant.css
@@ -5,7 +5,6 @@
*/
body {
- font-family: Arial, sans-serif;
font-size: 12px;
margin: 10px;
min-width: 47em;
diff --git a/chromium/chrome/browser/resources/instant/instant.html b/chromium/chrome/browser/resources/instant/instant.html
index 5187db7e088..0482eca02db 100644
--- a/chromium/chrome/browser/resources/instant/instant.html
+++ b/chromium/chrome/browser/resources/instant/instant.html
@@ -1,4 +1,4 @@
-<!DOCTYPE HTML>
+<!doctype html>
<html>
<!--
Copyright 2012 The Chromium Authors. All rights reserved.
@@ -8,6 +8,7 @@ found in the LICENSE file.
<head>
<meta charset="utf-8">
<title>Instant preferences</title>
+<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="instant.css">
<script src="instant.js"></script>
</head>
diff --git a/chromium/chrome/browser/resources/local_discovery/local_discovery.css b/chromium/chrome/browser/resources/local_discovery/local_discovery.css
index dcef6e55a3d..93c656a9758 100644
--- a/chromium/chrome/browser/resources/local_discovery/local_discovery.css
+++ b/chromium/chrome/browser/resources/local_discovery/local_discovery.css
@@ -115,7 +115,7 @@ section {
}
#back-link {
- background: url('chrome://theme/IDR_CHEVRON_LEFT') left no-repeat;
+ background: url(chrome://theme/IDR_CHEVRON_LEFT) left no-repeat;
margin-bottom: 25px;
margin-top: 6px;
padding-left: 23px;
diff --git a/chromium/chrome/browser/resources/local_discovery/local_discovery.html b/chromium/chrome/browser/resources/local_discovery/local_discovery.html
index f0d3cb5ebcd..efa8485418e 100644
--- a/chromium/chrome/browser/resources/local_discovery/local_discovery.html
+++ b/chromium/chrome/browser/resources/local_discovery/local_discovery.html
@@ -1,5 +1,5 @@
-<!DOCTYPE HTML>
-<html i18n-values="dir:textdirection">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
<head>
<meta charset="utf-8">
<title i18n-content="devicesTitle"></title>
@@ -17,8 +17,7 @@
<script src="local_discovery.js"></script>
<script src="strings.js"></script>
</head>
-<body class="uber-frame"
- i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<body class="uber-frame">
<div class="page" id="main-page">
<div class="overlay" id="overlay" hidden>
<div id="register-overlay" class="page">
@@ -160,7 +159,7 @@
</div>
</div>
- <script src="chrome://resources/js/i18n_template2.js"></script>
+ <script src="chrome://resources/js/i18n_template.js"></script>
<script src="chrome://resources/js/jstemplate_compiled.js"></script>
</body>
</html>
diff --git a/chromium/chrome/browser/resources/local_discovery/local_discovery.js b/chromium/chrome/browser/resources/local_discovery/local_discovery.js
index a372f8e597c..9d437aa7217 100644
--- a/chromium/chrome/browser/resources/local_discovery/local_discovery.js
+++ b/chromium/chrome/browser/resources/local_discovery/local_discovery.js
@@ -35,7 +35,7 @@ cr.define('local_discovery', function() {
/**
* Map of service names to corresponding service objects.
- * @type {Object.<string,Service>}
+ * @type {Object<string,Service>}
*/
var devices = {};
@@ -341,7 +341,7 @@ cr.define('local_discovery', function() {
/**
* Create the DOM for a cloud device described by the device section.
- * @param {Array.<Object>} devices_list List of devices.
+ * @param {Array<Object>} devices_list List of devices.
*/
function createCloudDeviceDOM(device) {
var devicesDomElement = document.createElement('div');
@@ -366,7 +366,7 @@ cr.define('local_discovery', function() {
/**
* Handle a list of cloud devices available to the user globally.
- * @param {Array.<Object>} devices_list List of devices.
+ * @param {Array<Object>} devices_list List of devices.
*/
function onCloudDeviceListAvailable(devices_list) {
var devicesListLength = devices_list.length;
diff --git a/chromium/chrome/browser/resources/local_ntp/OWNERS b/chromium/chrome/browser/resources/local_ntp/OWNERS
index 391a7be49c3..a7215af69ac 100644
--- a/chromium/chrome/browser/resources/local_ntp/OWNERS
+++ b/chromium/chrome/browser/resources/local_ntp/OWNERS
@@ -2,3 +2,4 @@ jered@chromium.org
jeremycho@chromium.org
samarth@chromium.org
mathp@chromium.org
+huangs@chromium.org
diff --git a/chromium/chrome/browser/resources/local_ntp/instant_iframe_validation.js b/chromium/chrome/browser/resources/local_ntp/instant_iframe_validation.js
index 3e4c12812fb..822350da69c 100644
--- a/chromium/chrome/browser/resources/local_ntp/instant_iframe_validation.js
+++ b/chromium/chrome/browser/resources/local_ntp/instant_iframe_validation.js
@@ -38,7 +38,7 @@ function isValidRBGAComponent(component) {
/**
* Converts an Array of color components into RGBA format "rgba(R,G,B,A)".
- * @param {Array.<number>} rgbaColor Array of rgba color components.
+ * @param {Array<number>} rgbaColor Array of rgba color components.
* @return {?string} CSS color in RGBA format or null if invalid.
*/
function convertArrayToRGBAColor(rgbaColor) {
diff --git a/chromium/chrome/browser/resources/local_ntp/local_ntp.css b/chromium/chrome/browser/resources/local_ntp/local_ntp.css
index c3de096d648..2ee882513a7 100644
--- a/chromium/chrome/browser/resources/local_ntp/local_ntp.css
+++ b/chromium/chrome/browser/resources/local_ntp/local_ntp.css
@@ -1,6 +1,9 @@
/* Copyright 2013 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. */
+
+/* TODO: Need to discuss with NTP folks before we remove font-family from the
+ * body tag. */
body {
background-attachment: fixed !important;
background-color: white;
@@ -36,7 +39,7 @@ body.fakebox-disable #fakebox > input {
}
#logo {
- background-image: url('images/google_logo.png@2x');
+ background-image: url(images/google_logo.png@2x);
background-repeat: no-repeat;
background-size: 269px 95px;
height: 95px;
@@ -46,7 +49,7 @@ body.fakebox-disable #fakebox > input {
}
body.alternate-logo #logo {
- -webkit-mask-image: url('images/google_logo.png@2x');
+ -webkit-mask-image: url(images/google_logo.png@2x);
-webkit-mask-repeat: no-repeat;
-webkit-mask-size: 100%;
background: #eee;
@@ -76,10 +79,6 @@ body.alternate-logo #logo {
border-top-color: rgb(144, 144, 144);
}
-.classical #fakebox {
- box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
-}
-
body.fakebox-focused #fakebox {
border: 1px solid rgb(77, 144, 254);
}
@@ -96,30 +95,31 @@ body.fakebox-focused #fakebox {
width: 100%;
}
-body[dir=rtl] #fakebox > input {
+html[dir=rtl] #fakebox > input {
padding-left: 0;
padding-right: 8px;
right: 0;
}
#fakebox-text {
+ bottom: 0;
color: #bbb;
font-family: arial, sans-serif;
font-size: 16px;
- height: 100%;
left: 9px;
margin-top: 1px;
overflow: hidden;
position: absolute;
+ right: 9px;
text-align: initial;
text-overflow: ellipsis;
+ top: 0;
vertical-align: middle;
visibility: inherit;
white-space: nowrap;
- width: calc(100% - 2 * 9px);
}
-body[dir=rtl] #fakebox-text {
+html[dir=rtl] #fakebox-text {
left: auto;
right: 9px;
}
@@ -134,7 +134,7 @@ body[dir=rtl] #fakebox-text {
width: 1px;
}
-body[dir=rtl] #cursor {
+html[dir=rtl] #cursor {
left: auto;
right: 9px;
}
@@ -167,12 +167,18 @@ body.fakebox-focused #cursor {
text-align: -webkit-center;
}
-.classical #most-visited {
- margin-top: 51px;
+/* For Google page, add space from Fakebox. */
+.thumb-ntp #most-visited {
+ margin-top: 64px;
}
-.md #most-visited {
- margin-top: 64px;
+.icon-ntp #most-visited {
+ margin-top: calc(100px - 36px);
+}
+
+/* Non-Google pages have no Fakebox, so don't need top margin. */
+.non-google-page #most-visited {
+ margin-top: 0;
}
#mv-tiles {
@@ -181,20 +187,31 @@ body.fakebox-focused #cursor {
text-align: left;
}
-body[dir=rtl] #mv-tiles {
+html[dir=rtl] #mv-tiles {
text-align: right;
}
-.classical #mv-tiles {
- height: calc(2 * 138px);
- line-height: 138px;
-}
-
-.md #mv-tiles {
+.thumb-ntp #mv-tiles {
height: calc(2 * 146px);
line-height: 146px;
}
+.icon-ntp #mv-tiles {
+ background: rgba(255,255,255,0.2);
+ border-radius: 4px;
+ height: calc(2 * 112px);
+ line-height: 112px;
+ padding: calc(36px - 18px) calc(36px - 18px - 12px);
+}
+
+.icon-ntp.dark #mv-tiles {
+ background: rgba(0,0,0,0.4);
+}
+
+.default-theme.icon-ntp #mv-tiles {
+ background: none;
+}
+
.mv-tile {
display: inline-block;
position: relative;
@@ -206,36 +223,28 @@ body[dir=rtl] #mv-tiles {
outline: none;
}
-.classical .mv-tile {
- background: linear-gradient(#f2f2f2, #e8e8e8);
- border-radius: 3px;
- box-shadow: inset 0 2px 3px rgba(0, 0, 0, .09);
- height: 85px;
- margin-left: 10px;
- margin-right: 10px; /* Total horizontal margins add to TILE_MARGIN. */
- width: 140px;
-}
-
-.classical .mv-page-ready {
- -webkit-transition-duration: 200ms;
- -webkit-transition-property: -webkit-transform, margin, opacity, width;
-}
-
-.md .mv-tile {
+.thumb-ntp .mv-tile {
background: rgb(242,242,242);
- border-radius: 1px;
+ border-radius: 2px;
height: 130px;
margin-left: 8px;
margin-right: 8px;
width: 156px;
}
-.md .mv-page-ready {
+.icon-ntp .mv-tile {
+ border-radius: 2px;
+ height: calc(102px + 18px - 12px);
+ margin: 0 12px 4px 12px;
+ width: calc(48px + 2 * 18px);
+}
+
+.mv-page-ready {
-webkit-transition-duration: 200ms;
-webkit-transition-property: -webkit-transform, margin, opacity, width;
}
-.md.dark .mv-tile {
+.thumb-ntp.dark .mv-tile {
background: rgb(51,51,51);
}
@@ -252,18 +261,21 @@ body[dir=rtl] #mv-tiles {
opacity: 0;
}
-.classical .mv-tile.mv-blacklist {
- -webkit-transform: scale(0.5);
-}
-
-.md .mv-tile.mv-blacklist {
- -webkit-transform: scale(0, 0);
- -webkit-transform-origin: 0 41px;
+.mv-tile.mv-blacklist {
+ -webkit-transform: scale(0);
margin-left: 0;
margin-right: 0;
width: 0;
}
+.thumb-ntp .mv-tile.mv-blacklist {
+ -webkit-transform-origin: 0 65px;
+}
+
+.icon-ntp .mv-tile.mv-blacklist {
+ -webkit-transform-origin: 0 41px;
+}
+
/* .mv-mask is used to alter tile appearance, including borders, shadows, */
/* and backgrounds. */
.mv-mask {
@@ -275,71 +287,58 @@ body[dir=rtl] #mv-tiles {
position: absolute;
}
-.classical .mv-mask {
- box-shadow: inset 0 2px 3px rgba(0, 0, 0, 0.09);
-}
-
-.md .mv-mask {
+.thumb-ntp .mv-mask {
border-color: transparent;
border-radius: 2px;
- height: calc(130px - 2px);
- width: calc(156px - 2px);
-}
-
-/* Styling border. */
-.classical .mv-page-ready .mv-mask {
- border-style: solid;
-}
-
-.default-theme.classical .mv-page-ready .mv-mask {
- border-color: #c0c0c0;
-}
-
-.default-theme.classical .mv-page-ready:hover .mv-mask,
-.default-theme.classical .mv-page-ready .mv-focused ~ .mv-mask {
- border-color: #7f7f7f;
+ height: calc(100% - 2px);
+ width: calc(100% - 2px);
}
-.default-theme.md.old-hover .mv-page-ready:hover .mv-mask,
-.default-theme.md.old-hover .mv-page-ready .mv-focused ~ .mv-mask {
- border-color: #999;
+.icon-ntp .mv-mask {
+ border: none !important;
+ border-radius: 0;
+ height: 100%;
+ width: 100%;
+ z-index: 5;
}
-.default-theme.md.dark .mv-page-ready:hover .mv-mask,
-.default-theme.md.dark .mv-page-ready .mv-focused ~ .mv-mask,
-.default-theme.md.old-hover.dark .mv-page-ready:hover .mv-mask,
-.default-theme.md.old-hover.dark .mv-page-ready .mv-focused ~ .mv-mask {
+/* Styling border. */
+.default-theme.thumb-ntp.dark .mv-page-ready:hover .mv-mask,
+.default-theme.thumb-ntp.dark .mv-page-ready .mv-focused ~ .mv-mask {
border-color: #888;
}
/* Styling shadow. */
-.default-theme.md .mv-page-ready .mv-mask {
+.default-theme.thumb-ntp .mv-page-ready .mv-mask {
-webkit-transition: box-shadow 200ms, border 200ms;
}
-.default-theme.md .mv-page-ready:hover .mv-mask,
-.default-theme.md .mv-page-ready .mv-focused ~ .mv-mask {
+.default-theme.thumb-ntp .mv-page-ready:hover .mv-mask,
+.default-theme.thumb-ntp .mv-page-ready .mv-focused ~ .mv-mask {
box-shadow: 0 1px 2px 0 rgba(0,0,0,0.1), 0 4px 8px 0 rgba(0,0,0,0.2);
}
-.default-theme.md.dark .mv-page-ready:hover .mv-mask,
-.default-theme.md.old-hover .mv-page-ready:hover .mv-mask {
+.default-theme.thumb-ntp.dark .mv-page-ready:hover .mv-mask {
box-shadow: none;
}
/* Styling background. */
-.classical .mv-page .mv-focused ~ .mv-mask {
- -webkit-transition: background-color 100ms ease-in-out;
- background: linear-gradient(rgba(255, 255, 255, 0),
- rgba(255, 255, 255, 0) 80%, rgba(255, 255, 255, 0.9));
- background-color: rgba(0, 0, 0, 0.35);
-}
-
-.md .mv-page .mv-focused ~ .mv-mask {
+.thumb-ntp .mv-page:not(:hover) .mv-focused ~ .mv-mask {
+ /*-webkit-filter: brightness(75%);*/
-webkit-transition: box-shadow 200ms, border 200ms,
background-color 100ms ease-in-out;
- background: rgba(0, 0, 0, 0.3);
- border-color: rgba(0, 0, 0, 0.3);
+ background: rgba(0, 0, 0, 0.25);
+}
+
+.icon-ntp .mv-page .mv-focused ~ .mv-mask {
+ -webkit-transition: none;
+ background: rgba(0,0,0,0.2);
+ border-radius: 2px;
+ box-shadow: none;
+}
+
+.icon-ntp.dark .mv-page .mv-focused ~ .mv-mask {
+ background: rgba(255,255,255,0.2);
}
.mv-title {
@@ -347,14 +346,7 @@ body[dir=rtl] #mv-tiles {
position: absolute;
}
-.classical .mv-title {
- bottom: -27px;
- height: 18px;
- left: 0;
- width: 140px;
-}
-
-.md .mv-title {
+.thumb-ntp .mv-title {
bottom: auto;
height: 15px;
left: 32px;
@@ -362,43 +354,36 @@ body[dir=rtl] #mv-tiles {
width: calc(156px - 32px - 4px);
}
-@media (-webkit-min-device-pixel-ratio: 2) {
- .md .mv-title {
- top: 8px;
- }
+html[dir=rtl] .thumb-ntp .mv-title[style*='direction: rtl'] {
+ -webkit-mask-image:
+ linear-gradient(to left, black, black, 100px, transparent);
+ right: 31px;
+ text-align: right;
}
-body[dir=rtl] .md .mv-title {
+html[dir=rtl] .thumb-ntp .mv-title {
left: auto;
right: 32px;
}
+.icon-ntp .mv-title {
+ bottom: auto;
+ height: 28px;
+ left: auto;
+ right: auto;
+ top: 76px;
+ width: 100%;
+ z-index: 10;
+}
+
.mv-thumb {
border: none;
cursor: pointer;
position: absolute;
}
-.classical .mv-thumb,
-.classical .mv-mask {
- height: 83px;
- width: 138px;
-}
-
-.classical .mv-thumb {
- border-radius: 2px;
- left: 1px;
- top: 1px;
-}
-
-.classical .mv-mask {
- border-radius: 3px;
- left: 0;
- top: 0;
-}
-
-.md .mv-thumb,
-.md .mv-thumb-fallback {
+.thumb-ntp .mv-thumb,
+.thumb-ntp .mv-thumb-fallback {
border-radius: 0;
height: 94px;
left: 4px;
@@ -406,22 +391,32 @@ body[dir=rtl] .md .mv-title {
width: 148px;
}
-body[dir=rtl] .md .mv-thumb,
-body[dir=rtl] .md .mv-thumb-fallback {
+.icon-ntp .mv-thumb,
+.icon-ntp .mv-thumb-fallback {
+ height: 48px;
+ left: 50%;
+ margin-left: -24px;
+ top: 18px;
+ width: 48px;
+ z-index: 10;
+}
+
+html[dir=rtl] .thumb-ntp .mv-thumb,
+html[dir=rtl] .thumb-ntp .mv-thumb-fallback {
left: auto;
right: 4px;
}
-.md .mv-thumb-fallback {
+.thumb-ntp .mv-thumb-fallback {
background-color: #fff;
position: absolute;
}
-.md.dark .mv-thumb-fallback {
+.thumb-ntp.dark .mv-thumb-fallback {
background-color: #555;
}
-.md .mv-thumb-fallback .dot {
+.thumb-ntp .mv-thumb-fallback .dot {
background-color: #f2f2f2;
border-radius: 8px;
display: block;
@@ -434,7 +429,7 @@ body[dir=rtl] .md .mv-thumb-fallback {
width: 16px;
}
-.md.dark .mv-thumb-fallback .dot {
+.thumb-ntp.dark .mv-thumb-fallback .dot {
background-color: #333;
}
@@ -455,50 +450,34 @@ body[dir=rtl] .md .mv-thumb-fallback {
position: absolute;
}
-.classical .mv-x {
- background-image: url('images/close_2.png');
- height: 16px;
- width: 16px;
-}
-
-.classical .mv-x:hover,
-.classical #mv-notice-x:focus {
- background-image: url('images/close_2_hover.png');
-}
-
-.classical .mv-x:active,
-.classical #mv-notice-x:active {
- background-image: url('images/close_2_active.png');
-}
-
-.classical .mv-page .mv-x {
- right: 2px;
- top: 2px;
-}
-
-body[dir=rtl] .classical .mv-page .mv-x {
- left: 2px;
- right: auto;
-}
-
#mv-notice-x {
+ -webkit-transform: translate(0,-8px);
display: inline-block;
position: relative;
}
-.md #mv-notice-x {
- -webkit-transform: translate(0,-8px);
-}
-
-.md .mv-x {
+.thumb-ntp .mv-x,
+.icon-ntp #mv-notice-x.mv-x {
+ border-radius: 2px;
height: 32px;
width: 32px;
}
-.md .mv-x .mv-x-inner {
+.icon-ntp .mv-page .mv-x {
+ border-radius: 0;
+ height: 16px;
+ width: 16px;
+ z-index: 15;
+}
+
+.icon-ntp .mv-page .mv-x .mv-x-inner {
+ display: none;
+}
+
+.mv-x .mv-x-inner {
-webkit-mask-image: -webkit-image-set(
- url('images/close_3_mask.png') 1x,
- url('images/close_3_mask.png@2x') 2x);
+ url(images/close_3_mask.png) 1x,
+ url(images/close_3_mask.png@2x) 2x);
-webkit-mask-repeat: no-repeat;
-webkit-mask-size: 10px 10px;
background-color: rgba(90,90,90,0.7);
@@ -511,59 +490,83 @@ body[dir=rtl] .classical .mv-page .mv-x {
width: 10px;
}
-.md.dark .mv-x .mv-x-inner {
+.dark .mv-x .mv-x-inner {
background-color: rgba(255,255,255,0.7);
}
-.md .mv-x:hover .mv-x-inner,
-.md #mv-notice-x:focus .mv-x-inner {
+.mv-x:hover .mv-x-inner,
+#mv-notice-x:focus .mv-x-inner {
background-color: rgb(90,90,90);
}
-.md.dark .mv-x:hover .mv-x-inner,
-.md.dark #mv-notice-x:focus .mv-x-inner {
+.dark .mv-x:hover .mv-x-inner,
+.dark #mv-notice-x:focus .mv-x-inner {
background-color: rgb(255,255,255);
}
-.md .mv-x:active .mv-x-inner,
-.md #mv-notice-x:active .mv-x-inner {
+.mv-x:active .mv-x-inner,
+#mv-notice-x:active .mv-x-inner {
background-color: rgb(66,133,244);
}
-.md.dark .mv-x:active .mv-x-inner,
-.md.dark #mv-notice-x:active .mv-x-inner {
+.dark .mv-x:active .mv-x-inner,
+.dark #mv-notice-x:active .mv-x-inner {
background-color: rgba(255,255,255,0.5);
}
-.md .mv-page .mv-x {
- /* background color needs to match .md .mv-tile */
+.thumb-ntp .mv-page .mv-x {
+ /* background color needs to match .thumb-ntp .mv-tile */
background: linear-gradient(to right, transparent, rgb(242,242,242) 10%);
right: 0;
top: 0;
}
-body[dir=rtl] .md .mv-page .mv-x {
- /* background color needs to match .md .mv-tile */
+html[dir=rtl] .thumb-ntp .mv-page .mv-x {
+ /* background color needs to match .thumb-ntp .mv-tile */
background: linear-gradient(to left, transparent, rgb(242,242,242) 10%);
left: 0;
right: auto;
}
-.md.dark .mv-page .mv-x {
- /* background color needs to match .md.dark .mv-tile */
+.thumb-ntp.dark .mv-page .mv-x {
+ /* background color needs to match .thumb-ntp.dark .mv-tile */
background: linear-gradient(to right, transparent, rgba(51,51,51,0.9) 30%);
}
-body[dir=rtl] .md.dark .mv-page .mv-x {
- /* background color needs to match .md.dark .mv-tile */
+html[dir=rtl] .thumb-ntp.dark .mv-page .mv-x {
+ /* background color needs to match .thumb-ntp.dark .mv-tile */
background: linear-gradient(to left, transparent, rgba(51,51,51,0.9) 30%);
}
+.icon-ntp .mv-page .mv-x,
+.icon-ntp.dark .mv-page .mv-x,
+html[dir=rtl] .icon-ntp .mv-page .mv-x,
+html[dir=rtl] .icon-ntp.dark .mv-page .mv-x {
+ background-color: none;
+ background-image: -webkit-image-set(
+ url(images/close_4_button.png) 1x,
+ url(images/close_4_button.png@2x) 2x);
+ top: 10px;
+}
+
+.icon-ntp .mv-page .mv-x {
+ right: 10px;
+}
+
+html[dir=rtl] .icon-ntp .mv-page .mv-x {
+ left: 10px;
+ right: auto;
+}
+
.mv-page-ready:hover .mv-x {
-webkit-transition-delay: 500ms;
opacity: 1;
}
+.icon-ntp .mv-page-ready:hover .mv-x {
+ -webkit-transition-delay: 800ms;
+}
+
.mv-page-ready .mv-x:hover {
-webkit-transition: none;
}
@@ -571,35 +574,36 @@ body[dir=rtl] .md.dark .mv-page .mv-x {
.mv-favicon {
background-size: 16px;
height: 16px;
+ line-height: 16px;
pointer-events: none;
position: absolute;
width: 16px;
}
-.classical .mv-favicon {
- bottom: -7px;
- left: 62px;
-}
-
-.md .mv-favicon {
+.thumb-ntp .mv-favicon {
left: 8px;
top: 8px;
}
-body[dir=rtl] .md .mv-favicon {
+html[dir=rtl] .thumb-ntp .mv-favicon {
left: auto;
right: 8px;
top: 8px;
}
-.md .mv-favicon-fallback {
+.thumb-ntp .mv-favicon-fallback {
background-image: -webkit-image-set(
- url('images/ntp_default_favicon.png') 1x,
- url('images/ntp_default_favicon.png@2x') 2x);
+ url(images/ntp_default_favicon.png) 1x,
+ url(images/ntp_default_favicon.png@2x) 2x);
background-repeat: no-repeat;
background-size: 16px 16px;
}
+.mv-favicon img {
+ height: 100%;
+ width: 100%;
+}
+
/* The notification shown when a tile is blacklisted. */
#mv-notice {
font-size: 12px;
@@ -608,6 +612,10 @@ body[dir=rtl] .md .mv-favicon {
padding: 10px 0;
}
+.icon-ntp #mv-notice {
+ margin-top: 30px;
+}
+
#mv-notice span {
cursor: default;
display: inline-block;
@@ -668,7 +676,7 @@ body[dir=rtl] .md .mv-favicon {
z-index: -1;
}
-body[dir=rtl] #attribution {
+html[dir=rtl] #attribution {
text-align: right;
}
@@ -687,7 +695,8 @@ body[dir=rtl] #attribution {
right: 8px;
}
-body[dir=rtl] #attribution,body[dir=rtl] #recent-tabs {
+html[dir=rtl] #attribution,
+html[dir=rtl] #recent-tabs {
left: 8px;
right: auto;
}
diff --git a/chromium/chrome/browser/resources/local_ntp/local_ntp.html b/chromium/chrome/browser/resources/local_ntp/local_ntp.html
index dd430631ef5..3655bd391d8 100644
--- a/chromium/chrome/browser/resources/local_ntp/local_ntp.html
+++ b/chromium/chrome/browser/resources/local_ntp/local_ntp.html
@@ -1,4 +1,4 @@
-<!DOCTYPE html>
+<!doctype html>
<html>
<!-- Copyright 2013 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
@@ -11,7 +11,7 @@
<meta name="google" value="notranslate">
</head>
<body>
- <div id="ntp-contents" class="{{CLASS}}">
+ <div id="ntp-contents" class="des-mat">
<div id="most-visited">
<div id="mv-tiles"></div>
<!-- Notification shown when a tile is blacklisted. -->
diff --git a/chromium/chrome/browser/resources/local_ntp/local_ntp.js b/chromium/chrome/browser/resources/local_ntp/local_ntp.js
index 3db2d43f07c..4bf0dd2e707 100644
--- a/chromium/chrome/browser/resources/local_ntp/local_ntp.js
+++ b/chromium/chrome/browser/resources/local_ntp/local_ntp.js
@@ -111,13 +111,6 @@ var MIDDLE_MOUSE_BUTTON = 1;
/**
- * Specifications for the NTP design.
- * @const {NtpDesign}
- */
-var NTP_DESIGN = getNtpDesign(configData.ntpDesignName);
-
-
-/**
* The container for the tile elements.
* @type {Element}
*/
@@ -155,7 +148,7 @@ var ntpContents;
/**
* The array of rendered tiles, ordered by appearance.
- * @type {!Array.<Tile>}
+ * @type {!Array<Tile>}
*/
var tiles = [];
@@ -176,8 +169,7 @@ var focusedIframe = null;
/**
* True if a page has been blacklisted and we're waiting on the
- * onmostvisitedchange callback. See onMostVisitedChange() for how this
- * is used.
+ * onmostvisitedchange callback. See renderAllTiles() for how this is used.
* @type {boolean}
*/
var isBlacklisting = false;
@@ -193,7 +185,7 @@ var numColumnsShown = 0;
/**
* A flag to indicate Most Visited changed caused by user action. If true, then
- * in onMostVisitedChange() tiles remain visible so no flickering occurs.
+ * in renderAllTiles() tiles remain visible so no flickering occurs.
* @type {boolean}
*/
var userInitiatedMostVisitedChange = false;
@@ -338,8 +330,7 @@ function renderTheme() {
var fakeboxText = $(IDS.FAKEBOX_TEXT);
if (fakeboxText) {
fakeboxText.innerHTML = '';
- if (NTP_DESIGN.showFakeboxHint &&
- configData.translatedStrings.searchboxPlaceholder) {
+ if (configData.translatedStrings.searchboxPlaceholder) {
fakeboxText.textContent =
configData.translatedStrings.searchboxPlaceholder;
}
@@ -349,15 +340,15 @@ function renderTheme() {
var isThemeDark = getIsThemeDark(info);
ntpContents.classList.toggle(CLASSES.DARK, isThemeDark);
if (!info) {
- titleColor = NTP_DESIGN.titleColor;
+ titleColor = convertToRRGGBBAAColor(NTP_DESIGN.titleColor);
return;
}
if (!info.usingDefaultTheme && info.textColorRgba) {
titleColor = convertToRRGGBBAAColor(info.textColorRgba);
} else {
- titleColor = isThemeDark ?
- NTP_DESIGN.titleColorAgainstDark : NTP_DESIGN.titleColor;
+ titleColor = convertToRRGGBBAAColor(isThemeDark ?
+ NTP_DESIGN.titleColorAgainstDark : NTP_DESIGN.titleColor);
}
var background = [convertToRGBAColor(info.backgroundColorRgba),
@@ -380,7 +371,7 @@ function renderTheme() {
function onThemeChange() {
renderTheme();
tilesContainer.innerHTML = '';
- renderAndShowTiles();
+ renderAllTiles();
}
@@ -471,7 +462,7 @@ function setAttributionVisibility_(show) {
/**
* Converts an Array of color components into RRGGBBAA format.
- * @param {Array.<number>} color Array of rgba color components.
+ * @param {Array<number>} color Array of rgba color components.
* @return {string} Color string in RRGGBBAA format.
* @private
*/
@@ -484,7 +475,7 @@ function convertToRRGGBBAAColor(color) {
/**
* Converts an Array of color components into RGBA format "rgba(R,G,B,A)".
- * @param {Array.<number>} color Array of rgba color components.
+ * @param {Array<number>} color Array of rgba color components.
* @return {string} CSS color in RGBA format.
* @private
*/
@@ -495,9 +486,17 @@ function convertToRGBAColor(color) {
/**
- * Handles a new set of Most Visited page data.
+ * Called when page data change.
*/
function onMostVisitedChange() {
+ renderAllTiles();
+}
+
+
+/**
+ * Rerenders all tiles based on Most Visited page data.
+ */
+function renderAllTiles() {
if (isBlacklisting) {
// Trigger the blacklist animation, which then triggers reloadAllTiles().
var lastBlacklistedTileElem = lastBlacklistedTile.elem;
@@ -513,16 +512,20 @@ function onMostVisitedChange() {
/**
* Handles the end of the blacklist animation by showing the notification and
* re-rendering the new set of tiles.
+ * @param {Event} e The associated event.
*/
-function blacklistAnimationDone() {
+function blacklistAnimationDone(e) {
+ if (e.propertyName != 'width') {
+ return;
+ }
showNotification();
isBlacklisting = false;
tilesContainer.classList.remove(CLASSES.HIDE_BLACKLIST_BUTTON);
lastBlacklistedTile.elem.removeEventListener(
'webkitTransitionEnd', blacklistAnimationDone);
// Need to call explicitly to re-render the tiles, since the initial
- // onmostvisitedchange issued by the blacklist function only triggered
- // the animation.
+ // renderAllTiles() issued by the blacklist function only triggered the
+ // animation.
reloadAllTiles();
}
@@ -543,7 +546,7 @@ function reloadAllTiles() {
/**
- * Binds onload events for a tile's internal <iframe> elements.
+ * Binds onload events for a tile's internal iframe elements.
* @param {Tile} tile The main tile to bind events to.
* @param {Barrier} tileVisibilityBarrier A barrier to make all tiles visible
* the moment all tiles are loaded.
@@ -577,7 +580,7 @@ function renderAndShowTiles() {
var numDesired = Math.min(tiles.length, numColumnsShown * NUM_ROWS);
// If we need to render new tiles, manage the visibility to hide intermediate
- // load states of the <iframe>s.
+ // load states of the iframes.
if (numExisting < numDesired) {
var showAll = function() {
for (var i = 0; i < numDesired; ++i) {
@@ -634,6 +637,8 @@ function getMostVisitedTitleIframeUrl(rid, position) {
params.push('ta=' + encodeURIComponent(NTP_DESIGN.titleTextAlign));
if (NTP_DESIGN.titleTextFade)
params.push('tf=' + encodeURIComponent(NTP_DESIGN.titleTextFade));
+ if (NTP_DESIGN.numTitleLines > 1)
+ params.push('ntl=' + NTP_DESIGN.numTitleLines);
return url + '?' + params.join('&');
}
@@ -647,14 +652,17 @@ function getMostVisitedTitleIframeUrl(rid, position) {
function getMostVisitedThumbnailIframeUrl(rid, position) {
var url = 'chrome-search://most-visited/' +
encodeURIComponent(MOST_VISITED_THUMBNAIL_IFRAME);
+ var colorString = convertToRRGGBBAAColor(NTP_DESIGN.thumbnailTextColor);
var params = [
'rid=' + encodeURIComponent(rid),
'f=' + encodeURIComponent(NTP_DESIGN.fontFamily),
'fs=' + encodeURIComponent(NTP_DESIGN.fontSize),
- 'c=' + encodeURIComponent(NTP_DESIGN.thumbnailTextColor),
+ 'c=' + encodeURIComponent(colorString),
'pos=' + encodeURIComponent(position)];
if (NTP_DESIGN.thumbnailFallback)
params.push('etfb=1');
+ if (configData.useIcons)
+ params.push('icons=1');
return url + '?' + params.join('&');
}
@@ -662,7 +670,7 @@ function getMostVisitedThumbnailIframeUrl(rid, position) {
/**
* Creates a Tile with the specified page data. If no data is provided, a
* filler Tile is created.
- * @param {Object} page The page data.
+ * @param {?Object} page The page data.
* @param {number} position The position of the tile.
* @return {Tile} The new Tile.
*/
@@ -670,93 +678,109 @@ function createTile(page, position) {
var tileElem = document.createElement('div');
tileElem.classList.add(CLASSES.TILE);
// Prevent tile from being selected (and highlighted) when areas outside the
- // <iframe>s are clicked.
+ // iframes are clicked.
tileElem.addEventListener('mousedown', function(e) {
e.preventDefault();
});
- var innerElem = createAndAppendElement(tileElem, 'div', CLASSES.TILE_INNER);
- if (page) {
- var rid = page.rid;
- tileElem.classList.add(CLASSES.PAGE);
+ if (!page) {
+ return new Tile(tileElem);
+ }
- var navigateFunction = function(e) {
- e.preventDefault();
- ntpApiHandle.navigateContentWindow(rid, getDispositionFromEvent(e));
- };
+ var rid = page.rid;
+ tileElem.classList.add(CLASSES.PAGE);
- // The click handler for navigating to the page identified by the RID.
- tileElem.addEventListener('click', navigateFunction);
-
- // The iframe which renders the page title.
- var titleElem = document.createElement('iframe');
- // Enable tab navigation on the iframe, which will move the selection to the
- // link element (which also has a tabindex).
- titleElem.tabIndex = '0';
-
- // Why iframes have IDs:
- //
- // On navigating back to the NTP we see several onmostvisitedchange() events
- // in series with incrementing RIDs. After the first event, a set of iframes
- // begins loading RIDs n, n+1, ..., n+k-1; after the second event, these get
- // destroyed and a new set begins loading RIDs n+k, n+k+1, ..., n+2k-1.
- // Now due to crbug.com/68841, Chrome incorrectly loads the content for the
- // first set of iframes into the most recent set of iframes.
- //
- // Giving iframes distinct ids seems to cause some invalidation and prevent
- // associating the incorrect data.
- //
- // TODO(jered): Find and fix the root (probably Blink) bug.
-
- // Keep this ID here. See comment above.
- titleElem.id = 'title-' + rid;
- titleElem.className = CLASSES.TITLE;
- titleElem.src = getMostVisitedTitleIframeUrl(rid, position);
- innerElem.appendChild(titleElem);
-
- // A fallback element for missing thumbnails.
- if (NTP_DESIGN.thumbnailFallback) {
- var fallbackElem = createAndAppendElement(
- innerElem, 'div', CLASSES.THUMBNAIL_FALLBACK);
- if (NTP_DESIGN.thumbnailFallback === THUMBNAIL_FALLBACK.DOT)
- createAndAppendElement(fallbackElem, 'div', CLASSES.DOT);
- }
+ var navigateFunction = function(e) {
+ e.preventDefault();
+ ntpApiHandle.navigateContentWindow(rid, getDispositionFromEvent(e));
+ };
+
+ // The click handler for navigating to the page identified by the RID.
+ tileElem.addEventListener('click', navigateFunction);
+
+ // Container of tile contents.
+ var innerElem = createAndAppendElement(tileElem, 'div', CLASSES.TILE_INNER);
- // The iframe which renders either a thumbnail or domain element.
- var thumbnailElem = document.createElement('iframe');
- thumbnailElem.tabIndex = '-1';
- thumbnailElem.setAttribute('aria-hidden', 'true');
- // Keep this ID here. See comment above.
- thumbnailElem.id = 'thumb-' + rid;
- thumbnailElem.className = CLASSES.THUMBNAIL;
- thumbnailElem.src = getMostVisitedThumbnailIframeUrl(rid, position);
- innerElem.appendChild(thumbnailElem);
-
- // The button used to blacklist this page.
- var blacklistButton = createAndAppendElement(
- innerElem, 'div', CLASSES.BLACKLIST_BUTTON);
- createAndAppendElement(
- blacklistButton, 'div', CLASSES.BLACKLIST_BUTTON_INNER);
- var blacklistFunction = generateBlacklistFunction(rid);
- blacklistButton.addEventListener('click', blacklistFunction);
- blacklistButton.title = configData.translatedStrings.removeThumbnailTooltip;
-
- // A helper mask on top of the tile that is used to create hover border
- // and/or to darken the thumbnail on focus.
- var maskElement = createAndAppendElement(
- innerElem, 'div', CLASSES.THUMBNAIL_MASK);
-
- // The page favicon, or a fallback.
+ // The iframe which renders the page title.
+ var titleElem = document.createElement('iframe');
+ // Enable tab navigation on the iframe, which will move the selection to the
+ // link element (which also has a tabindex).
+ titleElem.tabIndex = '0';
+
+ // Make the iframe presentational for accessibility so screen readers perceive
+ // the iframe content as just part of the same page.
+ titleElem.setAttribute('role', 'presentation');
+
+ // Why iframes have IDs:
+ //
+ // On navigating back to the NTP we see several onmostvisitedchange() events
+ // in series with incrementing RIDs. After the first event, a set of iframes
+ // begins loading RIDs n, n+1, ..., n+k-1; after the second event, these get
+ // destroyed and a new set begins loading RIDs n+k, n+k+1, ..., n+2k-1.
+ // Now due to crbug.com/68841, Chrome incorrectly loads the content for the
+ // first set of iframes into the most recent set of iframes.
+ //
+ // Giving iframes distinct ids seems to cause some invalidation and prevent
+ // associating the incorrect data.
+ //
+ // TODO(jered): Find and fix the root (probably Blink) bug.
+
+ // Keep this ID here. See comment above.
+ titleElem.id = 'title-' + rid;
+ titleElem.className = CLASSES.TITLE;
+ titleElem.src = getMostVisitedTitleIframeUrl(rid, position);
+ innerElem.appendChild(titleElem);
+
+ // A fallback element for missing thumbnails.
+ if (NTP_DESIGN.thumbnailFallback) {
+ var fallbackElem = createAndAppendElement(
+ innerElem, 'div', CLASSES.THUMBNAIL_FALLBACK);
+ if (NTP_DESIGN.thumbnailFallback === THUMBNAIL_FALLBACK.DOT)
+ createAndAppendElement(fallbackElem, 'div', CLASSES.DOT);
+ }
+
+ // The iframe which renders either a thumbnail or domain element.
+ var thumbnailElem = document.createElement('iframe');
+ thumbnailElem.tabIndex = '-1';
+ thumbnailElem.setAttribute('aria-hidden', 'true');
+ // Keep this ID here. See comment above.
+ thumbnailElem.id = 'thumb-' + rid;
+ thumbnailElem.className = CLASSES.THUMBNAIL;
+ thumbnailElem.src = getMostVisitedThumbnailIframeUrl(rid, position);
+ innerElem.appendChild(thumbnailElem);
+
+ // The button used to blacklist this page.
+ var blacklistButton = createAndAppendElement(
+ innerElem, 'div', CLASSES.BLACKLIST_BUTTON);
+ createAndAppendElement(
+ blacklistButton, 'div', CLASSES.BLACKLIST_BUTTON_INNER);
+ var blacklistFunction = generateBlacklistFunction(rid);
+ blacklistButton.addEventListener('click', blacklistFunction);
+ blacklistButton.title = configData.translatedStrings.removeThumbnailTooltip;
+
+ // A helper mask on top of the tile that is used to create hover border
+ // and/or to darken the thumbnail on focus.
+ var maskElement = createAndAppendElement(
+ innerElem, 'div', CLASSES.THUMBNAIL_MASK);
+
+ // The page favicon, or a fallback.
+ if (NTP_DESIGN.showFavicon) {
var favicon = createAndAppendElement(innerElem, 'div', CLASSES.FAVICON);
if (page.faviconUrl) {
- favicon.style.backgroundImage = 'url(' + page.faviconUrl + ')';
+ var fi = document.createElement('img');
+ fi.src = page.faviconUrl;
+ // Set the title to empty so screen readers won't say the image name.
+ fi.title = '';
+ fi.addEventListener('error', function(ev) {
+ favicon.removeChild(fi);
+ favicon.classList.add(CLASSES.FAVICON_FALLBACK);
+ });
+ favicon.appendChild(fi);
} else {
favicon.classList.add(CLASSES.FAVICON_FALLBACK);
}
- return new Tile(tileElem, innerElem, titleElem, thumbnailElem, rid);
- } else {
- return new Tile(tileElem);
}
+ return new Tile(tileElem, innerElem, titleElem, thumbnailElem, rid);
}
@@ -839,7 +863,7 @@ function updateContentWidth() {
var innerWidth = window.innerWidth || maxSnapSize;
// Each tile has left and right margins that sum to NTP_DESIGN.tileMargin.
var availableWidth = innerWidth + NTP_DESIGN.tileMargin -
- MIN_TOTAL_HORIZONTAL_PADDING;
+ NTP_DESIGN.fakeboxWingSize * 2 - MIN_TOTAL_HORIZONTAL_PADDING;
var newNumColumns = Math.floor(availableWidth / tileRequiredWidth);
if (newNumColumns < MIN_NUM_COLUMNS)
newNumColumns = MIN_NUM_COLUMNS;
@@ -855,6 +879,7 @@ function updateContentWidth() {
if (fakebox) {
// -2 to account for border.
var fakeboxWidth = (tilesContainerWidth - NTP_DESIGN.tileMargin - 2);
+ fakeboxWidth += NTP_DESIGN.fakeboxWingSize * 2;
fakebox.style.width = fakeboxWidth + 'px';
}
return true;
@@ -1077,6 +1102,7 @@ function init() {
if (configData.isGooglePage) {
var logo = document.createElement('div');
logo.id = IDS.LOGO;
+ logo.title = 'Google';
fakebox = document.createElement('div');
fakebox.id = IDS.FAKEBOX;
@@ -1093,6 +1119,11 @@ function init() {
document.body.classList.add(CLASSES.NON_GOOGLE_PAGE);
}
+ // Modify design for experimental icon NTP, if specified.
+ if (configData.useIcons)
+ modifyNtpDesignForIcons();
+ document.querySelector('#ntp-contents').classList.add(NTP_DESIGN.mainClass);
+
// Hide notifications after fade out, so we can't focus on links via keyboard.
notification.addEventListener('webkitTransitionEnd', hideNotification);
@@ -1135,7 +1166,7 @@ function init() {
onInputStart();
renderTheme();
- onMostVisitedChange();
+ renderAllTiles();
searchboxApiHandle = topLevelHandle.searchBox;
@@ -1165,6 +1196,7 @@ function init() {
if (text) {
searchboxApiHandle.paste(text);
}
+ setFakeboxDragFocus(false);
};
inputbox.ondragenter = function() {
setFakeboxDragFocus(true);
@@ -1180,9 +1212,10 @@ function init() {
if (searchboxApiHandle.rtl) {
$(IDS.NOTIFICATION).dir = 'rtl';
- document.body.setAttribute('dir', 'rtl');
+ // Grabbing the root HTML element.
+ document.documentElement.setAttribute('dir', 'rtl');
// Add class for setting alignments based on language directionality.
- document.body.classList.add(CLASSES.RTL);
+ document.documentElement.classList.add(CLASSES.RTL);
$(IDS.TILES).dir = 'rtl';
}
diff --git a/chromium/chrome/browser/resources/local_ntp/local_ntp_design.js b/chromium/chrome/browser/resources/local_ntp/local_ntp_design.js
index 74342090b93..5c0bb021a13 100644
--- a/chromium/chrome/browser/resources/local_ntp/local_ntp_design.js
+++ b/chromium/chrome/browser/resources/local_ntp/local_ntp_design.js
@@ -4,90 +4,87 @@
/**
- * @fileoverview Specifications for NTP design, and an accessor to presets.
+ * @fileoverview Specifications for NTP design.
*/
var THUMBNAIL_FALLBACK = {
DOT: 'dot' // Draw single dot.
};
+
/**
* Specifications for an NTP design (not comprehensive).
*
- * name: A unique identifier for the style.
- * fontFamily: Font family to use for title and thumbnail <iframe>s.
- * fontSize: Font size to use for the <iframe>s, in px.
+ * fakeboxWingSize: Extra distance for fakebox to extend beyond beyond the list
+ * of tiles.
+ * fontFamily: Font family to use for title and thumbnail iframes.
+ * fontSize: Font size to use for the iframes, in px.
+ * mainClass: Class applied to #ntp-contents to control CSS.
+ * numTitleLines: Number of lines to display in titles.
+ * showFavicon: Whether to show favicon.
+ * thumbnailTextColor: The 4-component color that thumbnail iframe may use to
+ * display text message in place of missing thumbnail.
+ * thumbnailFallback: (Optional) A value in THUMBNAIL_FALLBACK to specify the
+ * thumbnail fallback strategy. If unassigned, then the thumbnail.html
+ * iframe would handle the fallback.
* tileWidth: The width of each suggestion tile, in px.
* tileMargin: Spacing between successive tiles, in px.
- * titleColor: The RRGGBBAA color of title text.
- * titleColorAgainstDark: The RRGGBBAA color of title text against a dark theme.
+ * titleColor: The 4-component color of title text.
+ * titleColorAgainstDark: The 4-component color of title text against a dark
+ * theme.
* titleTextAlign: (Optional) The alignment of title text. If unspecified, the
* default value is 'center'.
* titleTextFade: (Optional) The number of pixels beyond which title
* text begins to fade. This overrides the default ellipsis style.
- * thumbnailTextColor: The RRGGBBAA color that thumbnail <iframe> may use to
- * display text message in place of missing thumbnail.
- * thumbnailFallback: (Optional) A value in THUMBNAIL_FALLBACK to specify the
- * thumbnail fallback strategy. If unassigned, then the thumbnail.html
- * <iframe> would handle the fallback.
- * showFakeboxHint: Whether to display text in the fakebox.
*
- * @typedef {{
- * name: string,
+ * @const {{
+ * fakeboxWingSize: number,
* fontFamily: string,
* fontSize: number,
+ * mainClass: string,
+ * numTitleLines: number,
+ * showFavicon: boolean,
+ * thumbnailTextColor: string,
+ * thumbnailFallback: string|null|undefined,
* tileWidth: number,
* tileMargin: number,
* titleColor: string,
* titleColorAgainstDark: string,
* titleTextAlign: string|null|undefined,
- * titleTextFade: string|null|undefined,
- * thumbnailTextColor: string,
- * thumbnailFallback: string|null|undefined
- * showFakeboxHint: string|null|undefined
+ * titleTextFade: number|null|undefined
* }}
*/
-var NtpDesign;
+var NTP_DESIGN = {
+ fakeboxWingSize: 0,
+ fontFamily: 'arial, sans-serif',
+ fontSize: 12,
+ mainClass: 'thumb-ntp',
+ numTitleLines: 1,
+ showFavicon: true,
+ thumbnailTextColor: [50, 50, 50, 255],
+ thumbnailFallback: THUMBNAIL_FALLBACK.DOT,
+ tileWidth: 156,
+ tileMargin: 16,
+ titleColor: [50, 50, 50, 255],
+ titleColorAgainstDark: [210, 210, 210, 255],
+ titleTextAlign: 'inherit',
+ titleTextFade: 122 - 36 // 112px wide title with 32 pixel fade at end.
+};
+
/**
- * Returns an NTP design corresponding to the given name.
- * @param {string|undefined} opt_name The name of the design. If undefined, then
- * the default design is specified.
- * @return {NtpDesign} The NTP design corresponding to name.
+ * Modifies NTP_DESIGN parameters for icon NTP.
*/
-function getNtpDesign(opt_name) {
- var ntpDesign = null;
-
- if (opt_name === 'md') {
- ntpDesign = {
- name: opt_name,
- fontFamily: 'arial, sans-serif',
- fontSize: 12,
- tileWidth: 156,
- tileMargin: 16,
- titleColor: '323232ff',
- titleColorAgainstDark: 'd2d2d2ff',
- titleTextAlign: 'inherit',
- titleTextFade: 122 - 36, // 112px wide title with 32 pixel fade at end.
- thumbnailTextColor: '323232ff', // Unused.
- thumbnailFallback: THUMBNAIL_FALLBACK.DOT,
- showFakeboxHint: true
- };
- } else {
- ntpDesign = {
- name: 'classical',
- fontFamily: 'arial, sans-serif',
- fontSize: 11,
- tileWidth: 140,
- tileMargin: 20,
- titleColor: '777777ff',
- titleColorAgainstDark: '777777ff',
- titleTextAlign: 'center',
- titleTextFade: null, // Default to ellipsis.
- thumbnailTextColor: '777777ff',
- thumbnailFallback: null, // Default to false.
- showFakeboxHint: false
- };
- }
- return ntpDesign;
+function modifyNtpDesignForIcons() {
+ NTP_DESIGN.fakeboxWingSize = 132;
+ NTP_DESIGN.mainClass = 'icon-ntp';
+ NTP_DESIGN.numTitleLines = 2;
+ NTP_DESIGN.showFavicon = false;
+ NTP_DESIGN.thumbnailFallback = null;
+ NTP_DESIGN.tileWidth = 48 + 2 * 18;
+ NTP_DESIGN.tileMargin = 60 - 18 * 2;
+ NTP_DESIGN.titleColor = [120, 120, 120, 255];
+ NTP_DESIGN.titleColorAgainstDark = [210, 210, 210, 255];
+ NTP_DESIGN.titleTextAlign = 'center';
+ delete NTP_DESIGN.titleTextFade;
}
diff --git a/chromium/chrome/browser/resources/local_ntp/local_ntp_fast.css b/chromium/chrome/browser/resources/local_ntp/local_ntp_fast.css
new file mode 100644
index 00000000000..5026e218ae9
--- /dev/null
+++ b/chromium/chrome/browser/resources/local_ntp/local_ntp_fast.css
@@ -0,0 +1,330 @@
+/* Copyright 2015 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. */
+
+/* TODO: Need to discuss with NTP folks before we remove font-family from the
+ * body tag. */
+body {
+ background-attachment: fixed !important;
+ background-color: white;
+ cursor: default;
+ font-family: arial, sans-serif;
+ font-size: small;
+ margin: 0;
+ overflow-x: hidden;
+}
+
+#ntp-contents {
+ text-align: -webkit-center;
+}
+
+.non-google-page #ntp-contents {
+ position: absolute;
+ top: calc(50% - 155px);
+ width: 100%;
+}
+
+body.hide-fakebox-logo #logo,
+body.hide-fakebox-logo #fakebox {
+ visibility: hidden;
+}
+
+body.fakebox-disable #fakebox {
+ border-color: rgb(238, 238, 238);
+ cursor: default;
+}
+
+body.fakebox-disable #fakebox > input {
+ cursor: default;
+}
+
+#logo {
+ background-image: url(images/google_logo.png@2x);
+ background-repeat: no-repeat;
+ background-size: 269px 95px;
+ height: 95px;
+ margin-bottom: 24px;
+ margin-top: 157px;
+ width: 269px;
+}
+
+body.alternate-logo #logo {
+ -webkit-mask-image: url(images/google_logo.png@2x);
+ -webkit-mask-repeat: no-repeat;
+ -webkit-mask-size: 100%;
+ background: #eee;
+}
+
+#fakebox {
+ -webkit-transform: translate3d(0, 0, 0);
+ -webkit-transition: -webkit-transform 100ms linear, border-color 100ms linear;
+ background-color: #fff;
+ border: 1px solid rgb(185, 185, 185);
+ border-radius: 1px;
+ border-top-color: rgb(160, 160, 160);
+ cursor: text;
+ font-size: 18px;
+ height: 36px;
+ line-height: 36px;
+ max-width: 672px;
+ position: relative;
+ /* #fakebox width (here and below) should be 2px less than #mv-tiles
+ to account for its border. */
+ width: 298px;
+}
+
+#fakebox:hover {
+ border: 1px solid rgb(169, 169, 169);
+ border-top-color: rgb(144, 144, 144);
+}
+
+body.fakebox-focused #fakebox {
+ border: 1px solid rgb(77, 144, 254);
+}
+
+#fakebox > input {
+ bottom: 0;
+ box-sizing: border-box;
+ left: 0;
+ margin: 0;
+ opacity: 0;
+ padding-left: 8px;
+ position: absolute;
+ top: 0;
+ width: 100%;
+}
+
+html[dir=rtl] #fakebox > input {
+ padding-left: 0;
+ padding-right: 8px;
+ right: 0;
+}
+
+#fakebox-text {
+ bottom: 0;
+ color: #bbb;
+ font-family: arial, sans-serif;
+ font-size: 16px;
+ left: 9px;
+ margin-top: 1px;
+ overflow: hidden;
+ position: absolute;
+ right: 9px;
+ text-align: initial;
+ text-overflow: ellipsis;
+ top: 0;
+ vertical-align: middle;
+ visibility: inherit;
+ white-space: nowrap;
+}
+
+html[dir=rtl] #fakebox-text {
+ left: auto;
+ right: 9px;
+}
+
+#cursor {
+ background: #333;
+ bottom: 5px;
+ left: 9px;
+ position: absolute;
+ top: 5px;
+ visibility: hidden;
+ width: 1px;
+}
+
+html[dir=rtl] #cursor {
+ left: auto;
+ right: 9px;
+}
+
+@-webkit-keyframes blink {
+ 0% {
+ opacity: 1;
+ }
+ 61.55% {
+ opacity: 0;
+ }
+}
+
+body.fakebox-drag-focused #fakebox-text,
+body.fakebox-focused #fakebox-text {
+ visibility: hidden;
+}
+
+body.fakebox-drag-focused #cursor {
+ visibility: inherit;
+}
+
+body.fakebox-focused #cursor {
+ -webkit-animation: blink 1.3s step-end infinite;
+ visibility: inherit;
+}
+
+#most-visited {
+ -webkit-user-select: none;
+ margin-top: 64px;
+ text-align: -webkit-center;
+}
+
+.icon-ntp #most-visited {
+ margin-top: calc(100px - 36px);
+}
+
+/* Non-Google pages have no Fakebox, so don't need top margin. */
+.non-google-page #most-visited {
+ margin-top: 0;
+}
+
+#mv-tiles {
+ margin: 0;
+ position: relative;
+ text-align: -webkit-auto;
+}
+
+.thumb-ntp #mv-tiles {
+ height: calc(2 * 146px);
+ line-height: 146px;
+}
+
+.icon-ntp #mv-tiles {
+ background: rgba(255,255,255,0.2);
+ border-radius: 4px;
+ height: calc(2 * 112px);
+ padding: calc(36px - 18px) calc(36px - 18px - 12px);
+}
+
+.icon-ntp.dark #mv-tiles {
+ background: rgba(0,0,0,0.4);
+}
+
+.default-theme.icon-ntp #mv-tiles {
+ background: none;
+}
+
+#mv-notice-x {
+ -webkit-mask-image: -webkit-image-set(
+ url(chrome-search://local-ntp/images/close_3_mask.png) 1x,
+ url(chrome-search://local-ntp/images/close_3_mask.png@2x) 2x);
+ -webkit-mask-position: 3px 3px;
+ -webkit-mask-repeat: no-repeat;
+ -webkit-mask-size: 10px 10px;
+ background-color: rgba(90,90,90,0.7);
+ cursor: pointer;
+ display: inline-block;
+ height: 16px;
+ margin-left: 20px;
+ outline: none;
+ vertical-align: middle;
+ width: 16px;
+}
+
+html[dir=rtl] #mv-notice-x {
+ margin-left: 0;
+ margin-right: 20px;
+}
+
+#mv-notice-x:hover {
+ background-color: rgba(90,90,90,1.0);
+}
+
+#mv-notice-x:active {
+ background-color: rgb(66,133,244);
+}
+
+/* The notification shown when a tile is blacklisted. */
+#mv-notice {
+ font-size: 12px;
+ font-weight: bold;
+ opacity: 1;
+ padding: 10px 0;
+}
+
+.icon-ntp #mv-notice {
+ margin-top: 30px;
+}
+
+#mv-notice span {
+ cursor: default;
+ display: inline-block;
+ height: 16px;
+ line-height: 16px;
+ vertical-align: top;
+}
+
+/* Links in the notification. */
+#mv-notice-links span {
+ -webkit-margin-start: 6px;
+ color: rgb(17, 85, 204);
+ cursor: pointer;
+ outline: none;
+ padding: 0 4px;
+}
+
+#mv-notice-links span:hover,
+#mv-notice-links span:focus,
+#recent-tabs:hover {
+ text-decoration: underline;
+}
+
+.default-theme.dark #mv-msg {
+ color: #fff;
+}
+
+.default-theme.dark #mv-notice-links span {
+ color: #fff;
+}
+
+#mv-notice.mv-notice-delayed-hide {
+ -webkit-transition-delay: 10s;
+ -webkit-transition-property: opacity;
+ opacity: 0;
+}
+
+#mv-notice.mv-notice-hide {
+ display: none;
+}
+
+#attribution {
+ -webkit-user-select: none;
+ bottom: 0;
+ color: #fff;
+ cursor: default;
+ display: inline-block;
+ font-size: 13px;
+ position: fixed;
+ right: 8px;
+ text-align: left;
+ z-index: -1;
+}
+
+html[dir=rtl] #attribution {
+ text-align: right;
+}
+
+#recent-tabs {
+ background: #fff;
+ border: 1px solid #c0c0c0;
+ border-radius: 2px;
+ bottom: 0;
+ color: rgb(17, 85, 204);
+ cursor: pointer;
+ font-family: Arial;
+ font-size: 14px;
+ opacity: 0.9;
+ padding: 3px;
+ position: fixed;
+ right: 8px;
+}
+
+html[dir=rtl] #attribution,
+html[dir=rtl] #recent-tabs {
+ left: 8px;
+ right: auto;
+}
+
+#mv-single {
+ border: none;
+ height: 100%;
+ width: 100%;
+}
diff --git a/chromium/chrome/browser/resources/local_ntp/local_ntp_fast.html b/chromium/chrome/browser/resources/local_ntp/local_ntp_fast.html
new file mode 100644
index 00000000000..2b3e1e4f7a3
--- /dev/null
+++ b/chromium/chrome/browser/resources/local_ntp/local_ntp_fast.html
@@ -0,0 +1,31 @@
+<!doctype html>
+<html>
+<!-- Copyright 2015 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. -->
+<head>
+ <script src="chrome-search://local-ntp/config.js"></script>
+ <script src="chrome-search://local-ntp/local-ntp.js"></script>
+ <link rel="stylesheet" href="chrome-search://local-ntp/local-ntp.css"></link>
+ <meta charset="utf-8">
+ <meta name="google" value="notranslate">
+</head>
+<body>
+ <div id="ntp-contents">
+ <div id="most-visited">
+ <div id="mv-tiles"></div>
+ <!-- Notification shown when a tile is blacklisted. -->
+ <div id="mv-notice" class="mv-notice-hide">
+ <span id="mv-msg"></span>
+ <!-- Links in the notification. -->
+ <span id="mv-notice-links">
+ <span id="mv-undo" tabIndex="1"></span>
+ <span id="mv-restore" tabIndex="1"></span>
+ <div id="mv-notice-x" tabIndex="1"></div>
+ </span>
+ </div>
+ </div>
+ <div id="attribution"><div id="attribution-text"></div></div>
+ </div>
+</body>
+</html>
diff --git a/chromium/chrome/browser/resources/local_ntp/local_ntp_fast.js b/chromium/chrome/browser/resources/local_ntp/local_ntp_fast.js
new file mode 100644
index 00000000000..42c646d84a5
--- /dev/null
+++ b/chromium/chrome/browser/resources/local_ntp/local_ntp_fast.js
@@ -0,0 +1,808 @@
+// Copyright 2015 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 The local InstantExtended NTP.
+ */
+
+
+/**
+ * Controls rendering the new tab page for InstantExtended.
+ * @return {Object} A limited interface for testing the local NTP.
+ */
+function LocalNTP() {
+ 'use strict';
+
+<include src="../../../../ui/webui/resources/js/util.js">
+<include src="local_ntp_design.js">
+
+/**
+ * Enum for classnames.
+ * @enum {string}
+ * @const
+ */
+var CLASSES = {
+ ALTERNATE_LOGO: 'alternate-logo', // Shows white logo if required by theme
+ DARK: 'dark',
+ DEFAULT_THEME: 'default-theme',
+ DELAYED_HIDE_NOTIFICATION: 'mv-notice-delayed-hide',
+ FAKEBOX_DISABLE: 'fakebox-disable', // Makes fakebox non-interactive
+ FAKEBOX_FOCUS: 'fakebox-focused', // Applies focus styles to the fakebox
+ // Applies drag focus style to the fakebox
+ FAKEBOX_DRAG_FOCUS: 'fakebox-drag-focused',
+ HIDE_FAKEBOX_AND_LOGO: 'hide-fakebox-logo',
+ HIDE_NOTIFICATION: 'mv-notice-hide',
+ // Vertically centers the most visited section for a non-Google provided page.
+ NON_GOOGLE_PAGE: 'non-google-page',
+ RTL: 'rtl' // Right-to-left language text.
+};
+
+
+/**
+ * Enum for HTML element ids.
+ * @enum {string}
+ * @const
+ */
+var IDS = {
+ ATTRIBUTION: 'attribution',
+ ATTRIBUTION_TEXT: 'attribution-text',
+ CUSTOM_THEME_STYLE: 'ct-style',
+ FAKEBOX: 'fakebox',
+ FAKEBOX_INPUT: 'fakebox-input',
+ FAKEBOX_TEXT: 'fakebox-text',
+ LOGO: 'logo',
+ NOTIFICATION: 'mv-notice',
+ NOTIFICATION_CLOSE_BUTTON: 'mv-notice-x',
+ NOTIFICATION_MESSAGE: 'mv-msg',
+ NTP_CONTENTS: 'ntp-contents',
+ RESTORE_ALL_LINK: 'mv-restore',
+ TILES: 'mv-tiles',
+ UNDO_LINK: 'mv-undo'
+};
+
+
+/**
+ * Enum for keycodes.
+ * @enum {number}
+ * @const
+ */
+var KEYCODE = {
+ ENTER: 13
+};
+
+
+/**
+ * Enum for the state of the NTP when it is disposed.
+ * @enum {number}
+ * @const
+ */
+var NTP_DISPOSE_STATE = {
+ NONE: 0, // Preserve the NTP appearance and functionality
+ DISABLE_FAKEBOX: 1,
+ HIDE_FAKEBOX_AND_LOGO: 2
+};
+
+
+/**
+ * The notification displayed when a page is blacklisted.
+ * @type {Element}
+ */
+var notification;
+
+
+/**
+ * The container for the theme attribution.
+ * @type {Element}
+ */
+var attribution;
+
+
+/**
+ * The "fakebox" - an input field that looks like a regular searchbox. When it
+ * is focused, any text the user types goes directly into the omnibox.
+ * @type {Element}
+ */
+var fakebox;
+
+
+/**
+ * The container for NTP elements.
+ * @type {Element}
+ */
+var ntpContents;
+
+
+/**
+ * The last blacklisted tile rid if any, which by definition should not be
+ * filler.
+ * @type {?number}
+ */
+var lastBlacklistedTile = null;
+
+
+/**
+ * Current number of tiles columns shown based on the window width, including
+ * those that just contain filler.
+ * @type {number}
+ */
+var numColumnsShown = 0;
+
+
+/**
+ * The browser embeddedSearch.newTabPage object.
+ * @type {Object}
+ */
+var ntpApiHandle;
+
+
+/**
+ * The browser embeddedSearch.searchBox object.
+ * @type {Object}
+ */
+var searchboxApiHandle;
+
+
+/**
+ * The state of the NTP when a query is entered into the Omnibox.
+ * @type {NTP_DISPOSE_STATE}
+ */
+var omniboxInputBehavior = NTP_DISPOSE_STATE.NONE;
+
+
+/**
+ * The state of the NTP when a query is entered into the Fakebox.
+ * @type {NTP_DISPOSE_STATE}
+ */
+var fakeboxInputBehavior = NTP_DISPOSE_STATE.HIDE_FAKEBOX_AND_LOGO;
+
+
+/** @type {number} @const */
+var MAX_NUM_TILES_TO_SHOW = 8;
+
+
+/** @type {number} @const */
+var MIN_NUM_COLUMNS = 2;
+
+
+/** @type {number} @const */
+var MAX_NUM_COLUMNS = 4;
+
+
+/** @type {number} @const */
+var NUM_ROWS = 2;
+
+
+/**
+ * Minimum total padding to give to the left and right of the most visited
+ * section. Used to determine how many tiles to show.
+ * @type {number}
+ * @const
+ */
+var MIN_TOTAL_HORIZONTAL_PADDING = 200;
+
+
+/**
+ * Heuristic to determine whether a theme should be considered to be dark, so
+ * the colors of various UI elements can be adjusted.
+ * @param {ThemeBackgroundInfo|undefined} info Theme background information.
+ * @return {boolean} Whether the theme is dark.
+ * @private
+ */
+function getIsThemeDark(info) {
+ if (!info)
+ return false;
+ // Heuristic: light text implies dark theme.
+ var rgba = info.textColorRgba;
+ var luminance = 0.3 * rgba[0] + 0.59 * rgba[1] + 0.11 * rgba[2];
+ return luminance >= 128;
+}
+
+
+/**
+ * Updates the NTP based on the current theme.
+ * @private
+ */
+function renderTheme() {
+ var fakeboxText = $(IDS.FAKEBOX_TEXT);
+ if (fakeboxText) {
+ fakeboxText.innerHTML = '';
+ if (configData.translatedStrings.searchboxPlaceholder) {
+ fakeboxText.textContent =
+ configData.translatedStrings.searchboxPlaceholder;
+ }
+ }
+
+ var info = ntpApiHandle.themeBackgroundInfo;
+ var isThemeDark = getIsThemeDark(info);
+ ntpContents.classList.toggle(CLASSES.DARK, isThemeDark);
+ if (!info) {
+ return;
+ }
+
+ var background = [convertToRGBAColor(info.backgroundColorRgba),
+ info.imageUrl,
+ info.imageTiling,
+ info.imageHorizontalAlignment,
+ info.imageVerticalAlignment].join(' ').trim();
+
+ document.body.style.background = background;
+ document.body.classList.toggle(CLASSES.ALTERNATE_LOGO, info.alternateLogo);
+ updateThemeAttribution(info.attributionUrl);
+ setCustomThemeStyle(info);
+
+ var themeinfo = {cmd: 'updateTheme'};
+ if (!info.usingDefaultTheme) {
+ themeinfo.tileBorderColor = convertToRGBAColor(info.sectionBorderColorRgba);
+ themeinfo.tileHoverBorderColor = convertToRGBAColor(info.headerColorRgba);
+ }
+ themeinfo.isThemeDark = isThemeDark;
+
+ var titleColor = NTP_DESIGN.titleColor;
+ if (!info.usingDefaultTheme && info.textColorRgba) {
+ titleColor = info.textColorRgba;
+ } else if (isThemeDark) {
+ titleColor = NTP_DESIGN.titleColorAgainstDark;
+ }
+ themeinfo.tileTitleColor = convertToRGBAColor(titleColor);
+
+ $('mv-single').contentWindow.postMessage(themeinfo, '*');
+}
+
+
+/**
+ * Updates the NTP based on the current theme, then rerenders all tiles.
+ * @private
+ */
+function onThemeChange() {
+ renderTheme();
+}
+
+
+/**
+ * Updates the NTP style according to theme.
+ * @param {Object=} opt_themeInfo The information about the theme. If it is
+ * omitted the style will be reverted to the default.
+ * @private
+ */
+function setCustomThemeStyle(opt_themeInfo) {
+ var customStyleElement = $(IDS.CUSTOM_THEME_STYLE);
+ var head = document.head;
+ if (opt_themeInfo && !opt_themeInfo.usingDefaultTheme) {
+ ntpContents.classList.remove(CLASSES.DEFAULT_THEME);
+ var themeStyle =
+ '#attribution {' +
+ ' color: ' + convertToRGBAColor(opt_themeInfo.textColorLightRgba) + ';' +
+ '}' +
+ '#mv-msg {' +
+ ' color: ' + convertToRGBAColor(opt_themeInfo.textColorRgba) + ';' +
+ '}' +
+ '#mv-notice-links span {' +
+ ' color: ' + convertToRGBAColor(opt_themeInfo.textColorLightRgba) + ';' +
+ '}' +
+ '#mv-notice-x {' +
+ ' -webkit-filter: drop-shadow(0 0 0 ' +
+ convertToRGBAColor(opt_themeInfo.textColorRgba) + ');' +
+ '}' +
+ '.mv-page-ready .mv-mask {' +
+ ' border: 1px solid ' +
+ convertToRGBAColor(opt_themeInfo.sectionBorderColorRgba) + ';' +
+ '}' +
+ '.mv-page-ready:hover .mv-mask, .mv-page-ready .mv-focused ~ .mv-mask {' +
+ ' border-color: ' +
+ convertToRGBAColor(opt_themeInfo.headerColorRgba) + ';' +
+ '}';
+
+ if (customStyleElement) {
+ customStyleElement.textContent = themeStyle;
+ } else {
+ customStyleElement = document.createElement('style');
+ customStyleElement.type = 'text/css';
+ customStyleElement.id = IDS.CUSTOM_THEME_STYLE;
+ customStyleElement.textContent = themeStyle;
+ head.appendChild(customStyleElement);
+ }
+
+ } else {
+ ntpContents.classList.add(CLASSES.DEFAULT_THEME);
+ if (customStyleElement)
+ head.removeChild(customStyleElement);
+ }
+}
+
+
+/**
+ * Renders the attribution if the URL is present, otherwise hides it.
+ * @param {string} url The URL of the attribution image, if any.
+ * @private
+ */
+function updateThemeAttribution(url) {
+ if (!url) {
+ setAttributionVisibility_(false);
+ return;
+ }
+
+ var attributionImage = attribution.querySelector('img');
+ if (!attributionImage) {
+ attributionImage = new Image();
+ attribution.appendChild(attributionImage);
+ }
+ attributionImage.style.content = url;
+ setAttributionVisibility_(true);
+}
+
+
+/**
+ * Sets the visibility of the theme attribution.
+ * @param {boolean} show True to show the attribution.
+ * @private
+ */
+function setAttributionVisibility_(show) {
+ if (attribution) {
+ attribution.style.display = show ? '' : 'none';
+ }
+}
+
+
+ /**
+ * Converts an Array of color components into RRGGBBAA format.
+ * @param {Array<number>} color Array of rgba color components.
+ * @return {string} Color string in RRGGBBAA format.
+ * @private
+ */
+function convertToRRGGBBAAColor(color) {
+ return color.map(function(t) {
+ return ('0' + t.toString(16)).slice(-2); // To 2-digit, 0-padded hex.
+ }).join('');
+}
+
+
+ /**
+ * Converts an Array of color components into RGBA format "rgba(R,G,B,A)".
+ * @param {Array<number>} color Array of rgba color components.
+ * @return {string} CSS color in RGBA format.
+ * @private
+ */
+function convertToRGBAColor(color) {
+ return 'rgba(' + color[0] + ',' + color[1] + ',' + color[2] + ',' +
+ color[3] / 255 + ')';
+}
+
+
+/**
+ * Called when page data change.
+ */
+function onMostVisitedChange() {
+ reloadTiles();
+}
+
+
+/**
+ * Fetches new data, creates, and renders tiles.
+ */
+function reloadTiles() {
+ var pages = ntpApiHandle.mostVisited;
+ var cmds = [];
+ for (var i = 0; i < Math.min(MAX_NUM_TILES_TO_SHOW, pages.length); ++i) {
+ cmds.push({cmd: 'tile', rid: pages[i].rid});
+ }
+ cmds.push({cmd: 'show', maxVisible: numColumnsShown * NUM_ROWS});
+
+ $('mv-single').contentWindow.postMessage(cmds, '*');
+}
+
+
+/**
+ * Shows the blacklist notification and triggers a delay to hide it.
+ */
+function showNotification() {
+ notification.classList.remove(CLASSES.HIDE_NOTIFICATION);
+ notification.classList.remove(CLASSES.DELAYED_HIDE_NOTIFICATION);
+ notification.scrollTop;
+ notification.classList.add(CLASSES.DELAYED_HIDE_NOTIFICATION);
+}
+
+
+/**
+ * Hides the blacklist notification.
+ */
+function hideNotification() {
+ notification.classList.add(CLASSES.HIDE_NOTIFICATION);
+ notification.classList.remove(CLASSES.DELAYED_HIDE_NOTIFICATION);
+}
+
+
+/**
+ * Handles a click on the notification undo link by hiding the notification and
+ * informing Chrome.
+ */
+function onUndo() {
+ hideNotification();
+ if (lastBlacklistedTile != null) {
+ ntpApiHandle.undoMostVisitedDeletion(lastBlacklistedTile);
+ }
+}
+
+
+/**
+ * Handles a click on the restore all notification link by hiding the
+ * notification and informing Chrome.
+ */
+function onRestoreAll() {
+ hideNotification();
+ ntpApiHandle.undoAllMostVisitedDeletions();
+}
+
+
+/**
+ * Recomputes the number of tile columns, and width of various contents based
+ * on the width of the window.
+ * @return {boolean} Whether the number of tile columns has changed.
+ */
+function updateContentWidth() {
+ var tileRequiredWidth = NTP_DESIGN.tileWidth + NTP_DESIGN.tileMargin;
+ // If innerWidth is zero, then use the maximum snap size.
+ var maxSnapSize = MAX_NUM_COLUMNS * tileRequiredWidth -
+ NTP_DESIGN.tileMargin + MIN_TOTAL_HORIZONTAL_PADDING;
+ var innerWidth = window.innerWidth || maxSnapSize;
+ // Each tile has left and right margins that sum to NTP_DESIGN.tileMargin.
+ var availableWidth = innerWidth + NTP_DESIGN.tileMargin -
+ NTP_DESIGN.fakeboxWingSize * 2 - MIN_TOTAL_HORIZONTAL_PADDING;
+ var newNumColumns = Math.floor(availableWidth / tileRequiredWidth);
+ if (newNumColumns < MIN_NUM_COLUMNS)
+ newNumColumns = MIN_NUM_COLUMNS;
+ else if (newNumColumns > MAX_NUM_COLUMNS)
+ newNumColumns = MAX_NUM_COLUMNS;
+
+ if (numColumnsShown === newNumColumns)
+ return false;
+
+ numColumnsShown = newNumColumns;
+ var tilesContainerWidth = numColumnsShown * tileRequiredWidth;
+ $(IDS.TILES).style.width = tilesContainerWidth + 'px';
+ if (fakebox) {
+ // -2 to account for border.
+ var fakeboxWidth = (tilesContainerWidth - NTP_DESIGN.tileMargin - 2);
+ fakeboxWidth += NTP_DESIGN.fakeboxWingSize * 2;
+ fakebox.style.width = fakeboxWidth + 'px';
+ }
+ return true;
+}
+
+
+/**
+ * Resizes elements because the number of tile columns may need to change in
+ * response to resizing. Also shows or hides extra tiles tiles according to the
+ * new width of the page.
+ */
+function onResize() {
+ updateContentWidth();
+ $('mv-single').contentWindow.postMessage(
+ {cmd: 'tilesVisible', maxVisible: numColumnsShown * NUM_ROWS}, '*');
+}
+
+
+/**
+ * Handles new input by disposing the NTP, according to where the input was
+ * entered.
+ */
+function onInputStart() {
+ if (fakebox && isFakeboxFocused()) {
+ setFakeboxFocus(false);
+ setFakeboxDragFocus(false);
+ disposeNtp(true);
+ } else if (!isFakeboxFocused()) {
+ disposeNtp(false);
+ }
+}
+
+
+/**
+ * Disposes the NTP, according to where the input was entered.
+ * @param {boolean} wasFakeboxInput True if the input was in the fakebox.
+ */
+function disposeNtp(wasFakeboxInput) {
+ var behavior = wasFakeboxInput ? fakeboxInputBehavior : omniboxInputBehavior;
+ if (behavior == NTP_DISPOSE_STATE.DISABLE_FAKEBOX)
+ setFakeboxActive(false);
+ else if (behavior == NTP_DISPOSE_STATE.HIDE_FAKEBOX_AND_LOGO)
+ setFakeboxAndLogoVisibility(false);
+}
+
+
+/**
+ * Restores the NTP (re-enables the fakebox and unhides the logo.)
+ */
+function restoreNtp() {
+ setFakeboxActive(true);
+ setFakeboxAndLogoVisibility(true);
+}
+
+
+/**
+ * @param {boolean} focus True to focus the fakebox.
+ */
+function setFakeboxFocus(focus) {
+ document.body.classList.toggle(CLASSES.FAKEBOX_FOCUS, focus);
+}
+
+/**
+ * @param {boolean} focus True to show a dragging focus to the fakebox.
+ */
+function setFakeboxDragFocus(focus) {
+ document.body.classList.toggle(CLASSES.FAKEBOX_DRAG_FOCUS, focus);
+}
+
+/**
+ * @return {boolean} True if the fakebox has focus.
+ */
+function isFakeboxFocused() {
+ return document.body.classList.contains(CLASSES.FAKEBOX_FOCUS) ||
+ document.body.classList.contains(CLASSES.FAKEBOX_DRAG_FOCUS);
+}
+
+
+/**
+ * @param {boolean} enable True to enable the fakebox.
+ */
+function setFakeboxActive(enable) {
+ document.body.classList.toggle(CLASSES.FAKEBOX_DISABLE, !enable);
+}
+
+
+/**
+ * @param {!Event} event The click event.
+ * @return {boolean} True if the click occurred in an enabled fakebox.
+ */
+function isFakeboxClick(event) {
+ return fakebox.contains(event.target) &&
+ !document.body.classList.contains(CLASSES.FAKEBOX_DISABLE);
+}
+
+
+/**
+ * @param {boolean} show True to show the fakebox and logo.
+ */
+function setFakeboxAndLogoVisibility(show) {
+ document.body.classList.toggle(CLASSES.HIDE_FAKEBOX_AND_LOGO, !show);
+}
+
+
+/**
+ * Shortcut for document.getElementById.
+ * @param {string} id of the element.
+ * @return {HTMLElement} with the id.
+ */
+function $(id) {
+ return document.getElementById(id);
+}
+
+
+/**
+ * Utility function which creates an element with an optional classname and
+ * appends it to the specified parent.
+ * @param {Element} parent The parent to append the new element.
+ * @param {string} name The name of the new element.
+ * @param {string=} opt_class The optional classname of the new element.
+ * @return {Element} The new element.
+ */
+function createAndAppendElement(parent, name, opt_class) {
+ var child = document.createElement(name);
+ if (opt_class)
+ child.classList.add(opt_class);
+ parent.appendChild(child);
+ return child;
+}
+
+
+/**
+ * @param {!Element} element The element to register the handler for.
+ * @param {number} keycode The keycode of the key to register.
+ * @param {!Function} handler The key handler to register.
+ */
+function registerKeyHandler(element, keycode, handler) {
+ element.addEventListener('keydown', function(event) {
+ if (event.keyCode == keycode)
+ handler(event);
+ });
+}
+
+
+/**
+ * @return {Object} the handle to the embeddedSearch API.
+ */
+function getEmbeddedSearchApiHandle() {
+ if (window.cideb)
+ return window.cideb;
+ if (window.chrome && window.chrome.embeddedSearch)
+ return window.chrome.embeddedSearch;
+ return null;
+}
+
+
+/**
+ * Event handler for the focus changed and blacklist messages on link elements.
+ * Used to toggle visual treatment on the tiles (depending on the message).
+ * @param {Event} event Event received.
+ */
+function handlePostMessage(event) {
+ var cmd = event.data.cmd;
+ var args = event.data;
+ if (cmd == 'tileBlacklisted') {
+ showNotification();
+ lastBlacklistedTile = args.tid;
+
+ ntpApiHandle.deleteMostVisitedItem(args.tid);
+ }
+}
+
+
+/**
+ * Prepares the New Tab Page by adding listeners, rendering the current
+ * theme, the most visited pages section, and Google-specific elements for a
+ * Google-provided page.
+ */
+function init() {
+ notification = $(IDS.NOTIFICATION);
+ attribution = $(IDS.ATTRIBUTION);
+ ntpContents = $(IDS.NTP_CONTENTS);
+
+ if (configData.isGooglePage) {
+ var logo = document.createElement('div');
+ logo.id = IDS.LOGO;
+ logo.title = 'Google';
+
+ fakebox = document.createElement('div');
+ fakebox.id = IDS.FAKEBOX;
+ var fakeboxHtml = [];
+ fakeboxHtml.push('<div id="' + IDS.FAKEBOX_TEXT + '"></div>');
+ fakeboxHtml.push('<input id="' + IDS.FAKEBOX_INPUT +
+ '" autocomplete="off" tabindex="-1" type="url" aria-hidden="true">');
+ fakeboxHtml.push('<div id="cursor"></div>');
+ fakebox.innerHTML = fakeboxHtml.join('');
+
+ ntpContents.insertBefore(fakebox, ntpContents.firstChild);
+ ntpContents.insertBefore(logo, ntpContents.firstChild);
+ } else {
+ document.body.classList.add(CLASSES.NON_GOOGLE_PAGE);
+ }
+
+ // Modify design for experimental icon NTP, if specified.
+ if (configData.useIcons)
+ modifyNtpDesignForIcons();
+ document.querySelector('#ntp-contents').classList.add(NTP_DESIGN.mainClass);
+
+ // Hide notifications after fade out, so we can't focus on links via keyboard.
+ notification.addEventListener('webkitTransitionEnd', hideNotification);
+
+ var notificationMessage = $(IDS.NOTIFICATION_MESSAGE);
+ notificationMessage.textContent =
+ configData.translatedStrings.thumbnailRemovedNotification;
+
+ var undoLink = $(IDS.UNDO_LINK);
+ undoLink.addEventListener('click', onUndo);
+ registerKeyHandler(undoLink, KEYCODE.ENTER, onUndo);
+ undoLink.textContent = configData.translatedStrings.undoThumbnailRemove;
+
+ var restoreAllLink = $(IDS.RESTORE_ALL_LINK);
+ restoreAllLink.addEventListener('click', onRestoreAll);
+ registerKeyHandler(restoreAllLink, KEYCODE.ENTER, onUndo);
+ restoreAllLink.textContent =
+ configData.translatedStrings.restoreThumbnailsShort;
+
+ $(IDS.ATTRIBUTION_TEXT).textContent =
+ configData.translatedStrings.attributionIntro;
+
+ var notificationCloseButton = $(IDS.NOTIFICATION_CLOSE_BUTTON);
+ createAndAppendElement(
+ notificationCloseButton, 'div', CLASSES.BLACKLIST_BUTTON_INNER);
+ notificationCloseButton.addEventListener('click', hideNotification);
+
+ window.addEventListener('resize', onResize);
+ updateContentWidth();
+
+ var topLevelHandle = getEmbeddedSearchApiHandle();
+
+ ntpApiHandle = topLevelHandle.newTabPage;
+ ntpApiHandle.onthemechange = onThemeChange;
+ ntpApiHandle.onmostvisitedchange = onMostVisitedChange;
+
+ ntpApiHandle.oninputstart = onInputStart;
+ ntpApiHandle.oninputcancel = restoreNtp;
+
+ if (ntpApiHandle.isInputInProgress)
+ onInputStart();
+
+ searchboxApiHandle = topLevelHandle.searchBox;
+
+ if (fakebox) {
+ // Listener for updating the key capture state.
+ document.body.onmousedown = function(event) {
+ if (isFakeboxClick(event))
+ searchboxApiHandle.startCapturingKeyStrokes();
+ else if (isFakeboxFocused())
+ searchboxApiHandle.stopCapturingKeyStrokes();
+ };
+ searchboxApiHandle.onkeycapturechange = function() {
+ setFakeboxFocus(searchboxApiHandle.isKeyCaptureEnabled);
+ };
+ var inputbox = $(IDS.FAKEBOX_INPUT);
+ if (inputbox) {
+ inputbox.onpaste = function(event) {
+ event.preventDefault();
+ // Send pasted text to Omnibox.
+ var text = event.clipboardData.getData('text/plain');
+ if (text)
+ searchboxApiHandle.paste(text);
+ };
+ inputbox.ondrop = function(event) {
+ event.preventDefault();
+ var text = event.dataTransfer.getData('text/plain');
+ if (text) {
+ searchboxApiHandle.paste(text);
+ }
+ setFakeboxDragFocus(false);
+ };
+ inputbox.ondragenter = function() {
+ setFakeboxDragFocus(true);
+ };
+ inputbox.ondragleave = function() {
+ setFakeboxDragFocus(false);
+ };
+ }
+
+ // Update the fakebox style to match the current key capturing state.
+ setFakeboxFocus(searchboxApiHandle.isKeyCaptureEnabled);
+ }
+
+ if (searchboxApiHandle.rtl) {
+ $(IDS.NOTIFICATION).dir = 'rtl';
+ // Grabbing the root HTML element.
+ document.documentElement.setAttribute('dir', 'rtl');
+ // Add class for setting alignments based on language directionality.
+ document.documentElement.classList.add(CLASSES.RTL);
+ }
+
+ var iframe = document.createElement('iframe');
+ iframe.id = 'mv-single';
+ var args = [];
+
+ if (searchboxApiHandle.rtl)
+ args.push('rtl=1');
+ if (window.configData.useIcons)
+ args.push('icons=1');
+ if (NTP_DESIGN.numTitleLines > 1)
+ args.push('ntl=' + NTP_DESIGN.numTitleLines);
+
+ args.push('removeTooltip=' +
+ encodeURIComponent(configData.translatedStrings.removeThumbnailTooltip));
+
+ iframe.src = '//most-visited/single.html?' + args.join('&');
+ $(IDS.TILES).appendChild(iframe);
+
+ iframe.onload = function() {
+ reloadTiles();
+ renderTheme();
+ };
+
+ window.addEventListener('message', handlePostMessage);
+}
+
+
+/**
+ * Binds event listeners.
+ */
+function listen() {
+ document.addEventListener('DOMContentLoaded', init);
+}
+
+return {
+ init: init,
+ listen: listen
+};
+}
+
+if (!window.localNTPUnitTest) {
+ LocalNTP().listen();
+}
diff --git a/chromium/chrome/browser/resources/local_ntp/most_visited_single.css b/chromium/chrome/browser/resources/local_ntp/most_visited_single.css
new file mode 100644
index 00000000000..96f6f6d581a
--- /dev/null
+++ b/chromium/chrome/browser/resources/local_ntp/most_visited_single.css
@@ -0,0 +1,382 @@
+/* Copyright 2015 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. */
+
+body {
+ -webkit-user-select: none;
+ background: none transparent;
+ color: #323232;
+ margin: 0;
+ overflow: hidden;
+ padding: 0;
+}
+
+a {
+ display: block;
+}
+
+a,
+a:active,
+a:hover,
+a:visited {
+ color: inherit;
+ text-decoration: none;
+}
+
+#most-visited {
+ -webkit-user-select: none;
+ margin: 0;
+ text-align: -webkit-center;
+}
+
+#mv-tiles,
+.mv-tiles-old {
+ -webkit-user-select: none;
+ font-size: 0;
+ margin: 0;
+ opacity: 0;
+ position: absolute;
+ /* This align correctly for both LTR and RTL */
+ text-align: -webkit-auto;
+ transition: opacity 1s;
+}
+
+.thumb-ntp #mv-tiles,
+.thumb-ntp .mv-tiles-old {
+ height: calc(2 * 146px);
+ line-height: 146px;
+}
+
+.icon-ntp #mv-tiles,
+.icon-ntp .mv-tiles-old {
+ height: calc(2 * 112px);
+ line-height: 112px;
+ width: 100%;
+}
+
+.mv-tile,
+.mv-empty-tile {
+ display: inline-block;
+ font-family: arial, sans-serif;
+ font-size: 12px;
+ opacity: 1;
+ outline: 0;
+ overflow: hidden;
+ position: relative;
+ vertical-align: top;
+ white-space: nowrap;
+}
+
+.mv-tile.hidden,
+.mv-empty-tile.hidden {
+ display: none;
+}
+
+.thumb-ntp .mv-tile,
+.thumb-ntp .mv-empty-tile {
+ background: rgb(242,242,242);
+ border: 1px solid transparent;
+ border-radius: 2px;
+ height: calc(130px - 2px);
+ line-height: 100%;
+ margin: 0 8px;
+ width: calc(156px - 2px);
+}
+
+.icon-ntp .mv-tile,
+.icon-ntp .mv-empty-tile {
+ border: none;
+ border-radius: 2px;
+ height: calc(102px + 18px - 12px);
+ margin: 0 12px 4px 12px;
+ width: calc(48px + 2 * 18px);
+}
+
+.mv-tile {
+ -webkit-transition-duration: 200ms;
+ -webkit-transition-property: -webkit-transform, border,
+ box-shadow, margin, opacity, width;
+ cursor: pointer;
+}
+
+.thumb-ntp .mv-tile:focus:not(:hover) {
+ -webkit-filter: brightness(75%);
+ box-shadow: 0 1px 2px 0 rgba(0,0,0,0.1), 0 4px 8px 0 rgba(0,0,0,0.2);
+}
+
+.icon-ntp .mv-tile:focus {
+ background: rgba(0,0,0,0.2);
+}
+
+.icon-ntp.dark .mv-tile:focus {
+ background: rgba(255,255,255,0.2);
+}
+
+.mv-tile.blacklisted {
+ -webkit-transform: scale(0, 0);
+ border: none;
+ margin: 0;
+ width: 0;
+}
+
+.thumb-ntp .mv-tile:hover {
+ box-shadow: 0 1px 2px 0 rgba(0,0,0,0.1), 0 4px 8px 0 rgba(0,0,0,0.2);
+}
+
+.mv-tile.mv-blacklist {
+ opacity: 0;
+}
+
+.mv-tile.mv-blacklist {
+ -webkit-transform: scale(0, 0);
+ -webkit-transform-origin: 0 41px;
+ margin-left: 0;
+ margin-right: 0;
+ width: 0;
+}
+
+.mv-title {
+ border: none;
+ overflow: hidden;
+ position: absolute;
+ text-overflow: clip;
+}
+
+.mv-title.multiline {
+ white-space: pre-wrap;
+ word-wrap: break-word;
+}
+
+.thumb-ntp .mv-title {
+ -webkit-mask-image:
+ linear-gradient(to right, #000, #000, 100px, transparent);
+ height: 15px;
+ left: 31px;
+ line-height: 14px;
+ padding: 0;
+ top: 8px;
+ width: calc(156px - 32px - 4px);
+}
+
+html:not([dir=rtl]) .thumb-ntp .mv-title[style*='direction: rtl'] {
+ -webkit-mask-image:
+ linear-gradient(to left, black, black, 100px, transparent);
+ left: auto;
+ right: 8px;
+ text-align: right;
+}
+
+html[dir=rtl] .mv-title {
+ left: 8px;
+ text-align: left;
+}
+
+html[dir=rtl] .thumb-ntp .mv-title[style*='direction: rtl'] {
+ -webkit-mask-image:
+ linear-gradient(to left, black, black, 100px, transparent);
+ right: 31px;
+ text-align: right;
+}
+
+.icon-ntp .mv-title {
+ height: 28px;
+ left: auto;
+ line-height: 117%;
+ right: auto;
+ text-align: center;
+ top: 76px;
+ width: 100%;
+ z-index: 5;
+}
+
+.mv-thumb {
+ border: none;
+ cursor: pointer;
+ display: block;
+ overflow: hidden;
+ position: absolute;
+}
+
+.thumb-ntp .mv-thumb {
+ border-radius: 0;
+ height: 94px;
+ left: 3px;
+ top: 31px;
+ width: 148px;
+}
+
+.mv-thumb img.thumbnail {
+ height: auto;
+ min-height: 100%;
+ width: 100%;
+}
+
+.mv-thumb img.large-icon {
+ -webkit-clip-path: inset(0 0 0 0 round 4px);
+ height: 48px;
+ left: 50%;
+ margin-left: -24px;
+ margin-top: -24px;
+ position: absolute;
+ top: 50%;
+ width: 48px;
+}
+
+.mv-thumb.failed-img,
+.mv-thumb.large-icon-outer {
+ background-color: #fff;
+ height: 94px;
+ width: 148px;
+}
+
+.icon-ntp .mv-thumb,
+.icon-ntp .mv-thumb-fallback {
+ background: transparent;
+ height: 48px;
+ left: 50%;
+ margin-left: -24px;
+ top: 18px;
+ width: 48px;
+}
+
+/*
+ We use ::after without content to provide an aditional element on top of
+ the thumbnail.
+*/
+.mv-thumb.failed-img::after {
+ border: 8px solid #f2f2f2;
+ border-radius: 50%;
+ content: '';
+ display: block;
+ height: 0;
+ margin: 39px 66px;
+ width: 0;
+}
+
+.mv-x {
+ -webkit-transition: opacity 150ms;
+ border: none;
+ cursor: pointer;
+ opacity: 0;
+ position: absolute;
+}
+
+.thumb-ntp .mv-x {
+ background: linear-gradient(to left, rgb(242,242,242) 60%, transparent);
+ height: 30px;
+ right: 0;
+ width: 40px;
+}
+
+.icon-ntp .mv-x {
+ background: none;
+ height: 16px;
+ right: 10px;
+ top: 10px;
+ width: 16px;
+}
+
+/*
+ We use ::after without content to provide the masked X element.
+ The "bottom" div is actually just the gradient.
+*/
+.mv-x::after {
+ -webkit-mask-image: -webkit-image-set(
+ url(chrome-search://local-ntp/images/close_3_mask.png) 1x,
+ url(chrome-search://local-ntp/images/close_3_mask.png@2x) 2x);
+ -webkit-mask-position: 12px 10px;
+ -webkit-mask-repeat: no-repeat;
+ -webkit-mask-size: 10px 10px;
+ background-color: rgba(90,90,90,0.7);
+ content: '';
+ display: block;
+ height: 32px;
+ position: absolute;
+ right: 0;
+ width: 32px;
+}
+
+.icon-ntp .mv-x::after {
+ -webkit-mask: none;
+ background-color: inherit;
+ background-image: -webkit-image-set(
+ url(chrome-search://local-ntp/images/close_4_button.png) 1x,
+ url(chrome-search://local-ntp/images/close_4_button.png@2x) 2x);
+ height: 16px;
+ width: 16px;
+}
+
+html[dir=rtl] .thumb-ntp .mv-x {
+ background: linear-gradient(to right, rgb(242,242,242) 60%, transparent);
+ left: -1px;
+ right: auto;
+}
+
+html[dir=rtl] .thumb-ntp .mv-x::after {
+ left: -1px;
+ right: auto;
+}
+
+html[dir=rtl] .icon-ntp .mv-x {
+ left: 10px;
+ right: auto;
+}
+
+.thumb-ntp .mv-x:hover::after {
+ background-color: rgb(90,90,90);
+}
+
+.thumb-ntp .mv-x:active::after {
+ background-color: rgb(66,133,244);
+}
+
+.icon-ntp .mv-x:hover::after,
+.icon-ntp .mv-x:active::after {
+ background-color: inherit;
+}
+
+.mv-tile:hover .mv-x {
+ -webkit-transition-delay: 500ms;
+ opacity: 1;
+}
+
+.icon-ntp .mv-tile:hover .mv-x {
+ -webkit-transition-delay: 800ms;
+}
+
+.mv-x:hover {
+ -webkit-transition: none;
+}
+
+.mv-favicon {
+ background-size: 16px;
+ height: 16px;
+ left: 7px;
+ margin: 0;
+ pointer-events: none;
+ position: absolute;
+ top: 7px;
+ width: 16px;
+}
+
+html[dir=rtl] .mv-favicon {
+ left: auto;
+ right: 7px;
+}
+
+.mv-favicon.failed-favicon {
+ background-image: -webkit-image-set(
+ url(chrome-search://local-ntp/images/ntp_default_favicon.png) 1x,
+ url(chrome-search://local-ntp/images/ntp_default_favicon.png@2x) 2x);
+ background-repeat: no-repeat;
+ background-size: 16px 16px;
+}
+
+.mv-favicon img {
+ height: 100%;
+ width: 100%;
+}
+
+.mv-favicon.failed-favicon img {
+ display: none;
+}
diff --git a/chromium/chrome/browser/resources/local_ntp/most_visited_single.html b/chromium/chrome/browser/resources/local_ntp/most_visited_single.html
new file mode 100644
index 00000000000..9c20f45e8ee
--- /dev/null
+++ b/chromium/chrome/browser/resources/local_ntp/most_visited_single.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<html>
+<!-- Copyright 2015 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. -->
+<head>
+ <base target="_top">
+ <meta charset="utf-8">
+ <link rel="stylesheet" type="text/css" href="single.css">
+ <style type="text/css" id="custom-theme"></style>
+ <script src="single.js"></script>
+</head>
+<body>
+ <div id="most-visited">
+ <div id="mv-tiles"></div>
+ </div>
+</body>
+</html>
diff --git a/chromium/chrome/browser/resources/local_ntp/most_visited_single.js b/chromium/chrome/browser/resources/local_ntp/most_visited_single.js
new file mode 100644
index 00000000000..46d2897eb3a
--- /dev/null
+++ b/chromium/chrome/browser/resources/local_ntp/most_visited_single.js
@@ -0,0 +1,533 @@
+/* Copyright 2015 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. */
+
+ // Single iframe for NTP tiles.
+(function() {
+'use strict';
+
+
+/**
+ * The different types of events that are logged from the NTP. This enum is
+ * used to transfer information from the NTP JavaScript to the renderer and is
+ * not used as a UMA enum histogram's logged value.
+ * Note: Keep in sync with common/ntp_logging_events.h
+ * @enum {number}
+ * @const
+ */
+var LOG_TYPE = {
+ // The suggestion is coming from the server. Unused here.
+ NTP_SERVER_SIDE_SUGGESTION: 0,
+ // The suggestion is coming from the client.
+ NTP_CLIENT_SIDE_SUGGESTION: 1,
+ // Indicates a tile was rendered, no matter if it's a thumbnail, a gray tile
+ // or an external tile.
+ NTP_TILE: 2,
+ // The tile uses a local thumbnail image.
+ NTP_THUMBNAIL_TILE: 3,
+ // Used when no thumbnail is specified and a gray tile with the domain is used
+ // as the main tile. Unused here.
+ NTP_GRAY_TILE: 4,
+ // The visuals of that tile are handled externally by the page itself.
+ // Unused here.
+ NTP_EXTERNAL_TILE: 5,
+ // There was an error in loading both the thumbnail image and the fallback
+ // (if it was provided), resulting in a gray tile.
+ NTP_THUMBNAIL_ERROR: 6,
+ // Used a gray tile with the domain as the fallback for a failed thumbnail.
+ // Unused here.
+ NTP_GRAY_TILE_FALLBACK: 7,
+ // The visuals of that tile's fallback are handled externally. Unused here.
+ NTP_EXTERNAL_TILE_FALLBACK: 8,
+ // The user moused over an NTP tile.
+ NTP_MOUSEOVER: 9,
+ // A NTP Tile has finished loading (successfully or failing).
+ NTP_TILE_LOADED: 10,
+};
+
+
+/**
+ * Total number of tiles to show at any time. If the host page doesn't send
+ * enough tiles, we fill them blank.
+ * @const {number}
+ */
+var NUMBER_OF_TILES = 8;
+
+
+/**
+ * Whether to use icons instead of thumbnails.
+ * @type {boolean}
+ */
+var USE_ICONS = false;
+
+
+/**
+ * Number of lines to display in titles.
+ * @type {number}
+ */
+var NUM_TITLE_LINES = 1;
+
+
+/**
+ * The origin of this request.
+ * @const {string}
+ */
+var DOMAIN_ORIGIN = '{{ORIGIN}}';
+
+
+/**
+ * Counter for DOM elements that we are waiting to finish loading.
+ * @type {number}
+ */
+var loadedCounter = 1;
+
+
+/**
+ * DOM element containing the tiles we are going to present next.
+ * Works as a double-buffer that is shown when we receive a "show" postMessage.
+ * @type {Element}
+ */
+var tiles = null;
+
+
+/**
+ * List of parameters passed by query args.
+ * @type {Object}
+ */
+var queryArgs = {};
+
+
+/**
+ * Log an event on the NTP.
+ * @param {number} eventType Event from LOG_TYPE.
+ */
+var logEvent = function(eventType) {
+ chrome.embeddedSearch.newTabPage.logEvent(eventType);
+};
+
+
+/**
+ * Down counts the DOM elements that we are waiting for the page to load.
+ * When we get to 0, we send a message to the parent window.
+ * This is usually used as an EventListener of onload/onerror.
+ */
+var countLoad = function() {
+ loadedCounter -= 1;
+ if (loadedCounter <= 0) {
+ showTiles();
+ logEvent(LOG_TYPE.NTP_TILE_LOADED);
+ window.parent.postMessage({cmd: 'loaded'}, DOMAIN_ORIGIN);
+ loadedCounter = 1;
+ }
+};
+
+
+/**
+ * Handles postMessages coming from the host page to the iframe.
+ * Mostly, it dispatches every command to handleCommand.
+ **/
+var handlePostMessage = function(event) {
+ if (event.data instanceof Array) {
+ for (var i = 0; i < event.data.length; ++i) {
+ handleCommand(event.data[i]);
+ }
+ } else {
+ handleCommand(event.data);
+ }
+};
+
+
+/**
+ * Handles a single command coming from the host page to the iframe.
+ * We try to keep the logic here to a minimum and just dispatch to the relevant
+ * functions.
+ **/
+var handleCommand = function(data) {
+ var cmd = data.cmd;
+
+ if (cmd == 'tile') {
+ addTile(data);
+ } else if (cmd == 'show') {
+ countLoad();
+ hideOverflowTiles(data);
+ } else if (cmd == 'updateTheme') {
+ updateTheme(data);
+ } else if (cmd == 'tilesVisible') {
+ hideOverflowTiles(data);
+ } else {
+ console.error('Unknown command: ' + JSON.stringify(data));
+ }
+};
+
+
+var updateTheme = function(info) {
+ var themeStyle = [];
+
+ if (info.tileBorderColor) {
+ themeStyle.push('.thumb-ntp .mv-tile {' +
+ 'border: 1px solid ' + info.tileBorderColor + '; }');
+ }
+ if (info.tileHoverBorderColor) {
+ themeStyle.push('.thumb-ntp .mv-tile:hover {' +
+ 'border-color: ' + info.tileHoverBorderColor + '; }');
+ }
+ if (info.isThemeDark) {
+ themeStyle.push('.thumb-ntp .mv-tile, .thumb-ntp .mv-empty-tile { ' +
+ 'background: rgb(51,51,51); }');
+ themeStyle.push('.thumb-ntp .mv-thumb.failed-img { ' +
+ 'background-color: #555; }');
+ themeStyle.push('.thumb-ntp .mv-thumb.failed-img::after { ' +
+ 'border-color: #333; }');
+ themeStyle.push('.thumb-ntp .mv-x { ' +
+ 'background: linear-gradient(to left, ' +
+ 'rgb(51,51,51) 60%, transparent); }');
+ themeStyle.push('html[dir=rtl] .thumb-ntp .mv-x { ' +
+ 'background: linear-gradient(to right, ' +
+ 'rgb(51,51,51) 60%, transparent); }');
+ themeStyle.push('.thumb-ntp .mv-x::after { ' +
+ 'background-color: rgba(255,255,255,0.7); }');
+ themeStyle.push('.thumb-ntp .mv-x:hover::after { ' +
+ 'background-color: #fff; }');
+ themeStyle.push('.thumb-ntp .mv-x:active::after { ' +
+ 'background-color: rgba(255,255,255,0.5); }');
+ themeStyle.push('.icon-ntp .mv-tile:focus { ' +
+ 'background: rgba(255,255,255,0.2); }');
+ }
+ if (info.tileTitleColor) {
+ themeStyle.push('body { color: ' + info.tileTitleColor + '; }');
+ }
+
+ document.querySelector('#custom-theme').textContent = themeStyle.join('\n');
+};
+
+
+/**
+ * Hides extra tiles that don't fit on screen.
+ */
+var hideOverflowTiles = function(data) {
+ var tileAndEmptyTileList = document.querySelectorAll(
+ '#mv-tiles .mv-tile,#mv-tiles .mv-empty-tile');
+ for (var i = 0; i < tileAndEmptyTileList.length; ++i) {
+ tileAndEmptyTileList[i].classList.toggle('hidden', i >= data.maxVisible);
+ }
+};
+
+
+/**
+ * Removes all old instances of #mv-tiles that are pending for deletion.
+ */
+var removeAllOldTiles = function() {
+ var parent = document.querySelector('#most-visited');
+ var oldList = parent.querySelectorAll('.mv-tiles-old');
+ for (var i = 0; i < oldList.length; ++i) {
+ parent.removeChild(oldList[i]);
+ }
+};
+
+
+/**
+ * Called when the host page has finished sending us tile information and
+ * we are ready to show the new tiles and drop the old ones.
+ */
+var showTiles = function() {
+ removeAllOldTiles();
+
+ // Store the tiles on the current closure.
+ var cur = tiles;
+
+ // Create empty tiles until we have NUMBER_OF_TILES.
+ while (cur.childNodes.length < NUMBER_OF_TILES) {
+ addTile({});
+ }
+
+ var parent = document.querySelector('#most-visited');
+
+ // Mark old tile DIV for removal after the transition animation is done.
+ var old = parent.querySelector('#mv-tiles');
+ if (old) {
+ old.removeAttribute('id');
+ old.classList.add('mv-tiles-old');
+ cur.addEventListener('webkitTransitionEnd', function(ev) {
+ if (ev.target === cur) {
+ removeAllOldTiles();
+ }
+ });
+ }
+
+ // Add new tileset.
+ cur.id = 'mv-tiles';
+ parent.appendChild(cur);
+ // We want the CSS transition to trigger, so need to add to the DOM before
+ // setting the style.
+ setTimeout(function() {
+ cur.style.opacity = 1.0;
+ }, 0);
+
+ // Make sure the tiles variable contain the next tileset we may use.
+ tiles = document.createElement('div');
+};
+
+
+/**
+ * Called when the host page wants to add a suggestion tile.
+ * For Most Visited, it grabs the data from Chrome and pass on.
+ * For host page generated it just passes the data.
+ * @param {object} args Data for the tile to be rendered.
+ */
+var addTile = function(args) {
+ if (args.rid) {
+ var data = chrome.embeddedSearch.searchBox.getMostVisitedItemData(args.rid);
+ data.tid = data.rid;
+ data.thumbnailUrls = [data.thumbnailUrl];
+ data.faviconUrl = 'chrome-search://favicon/size/16@' +
+ window.devicePixelRatio + 'x/' + data.renderViewId + '/' + data.tid;
+ tiles.appendChild(renderTile(data));
+ logEvent(LOG_TYPE.NTP_CLIENT_SIDE_SUGGESTION);
+ } else if (args.id) {
+ tiles.appendChild(renderTile(args));
+ logEvent(LOG_TYPE.NTP_SERVER_SIDE_SUGGESTION);
+ } else {
+ tiles.appendChild(renderTile(null));
+ }
+};
+
+
+/**
+ * Called when the user decided to add a tile to the blacklist.
+ * It sets of the animation for the blacklist and sends the blacklisted id
+ * to the host page.
+ * @param {Element} tile DOM node of the tile we want to remove.
+ */
+var blacklistTile = function(tile) {
+ tile.classList.add('blacklisted');
+ tile.addEventListener('webkitTransitionEnd', function(ev) {
+ if (ev.propertyName != 'width') return;
+
+ window.parent.postMessage({cmd: 'tileBlacklisted',
+ tid: Number(tile.getAttribute('data-tid'))},
+ DOMAIN_ORIGIN);
+ });
+};
+
+
+/**
+ * Renders a MostVisited tile to the DOM.
+ * @param {object} data Object containing rid, url, title, favicon, thumbnail.
+ * data is null if you want to construct an empty tile.
+ */
+var renderTile = function(data) {
+ var tile = document.createElement('a');
+
+ if (data == null) {
+ tile.className = 'mv-empty-tile';
+ return tile;
+ }
+
+ logEvent(LOG_TYPE.NTP_TILE);
+
+ tile.className = 'mv-tile';
+ tile.setAttribute('data-tid', data.tid);
+ var tooltip = queryArgs['removeTooltip'] || '';
+ var html = [];
+ if (!USE_ICONS) {
+ html.push('<div class="mv-favicon"></div>');
+ }
+ html.push('<div class="mv-title"></div><div class="mv-thumb"></div>');
+ html.push('<div title="' + tooltip + '" class="mv-x"></div>');
+ tile.innerHTML = html.join('');
+
+ tile.href = data.url;
+ tile.title = data.title;
+ tile.addEventListener('keydown', function(event) {
+ if (event.keyCode == 46 /* DELETE */ ||
+ event.keyCode == 8 /* BACKSPACE */) {
+ event.preventDefault();
+ event.stopPropagation();
+ blacklistTile(tile);
+ } else if (event.keyCode == 13 /* ENTER */ ||
+ event.keyCode == 32 /* SPACE */) {
+ event.preventDefault();
+ tile.click();
+ }
+ });
+ // TODO(fserb): remove this or at least change to mouseenter.
+ tile.addEventListener('mouseover', function() {
+ logEvent(LOG_TYPE.NTP_MOUSEOVER);
+ });
+
+ var title = tile.querySelector('.mv-title');
+ title.innerText = data.title;
+ title.style.direction = data.direction || 'ltr';
+ if (NUM_TITLE_LINES > 1) {
+ title.classList.add('multiline');
+ }
+
+ if (USE_ICONS) {
+ var thumb = tile.querySelector('.mv-thumb');
+ if (data.largeIconUrl) {
+ var img = document.createElement('img');
+ img.title = data.title;
+ img.src = data.largeIconUrl;
+ img.classList.add('large-icon');
+ loadedCounter += 1;
+ img.addEventListener('load', countLoad);
+ img.addEventListener('load', function(ev) {
+ thumb.classList.add('large-icon-outer');
+ });
+ img.addEventListener('error', countLoad);
+ img.addEventListener('error', function(ev) {
+ thumb.classList.add('failed-img');
+ thumb.removeChild(img);
+ logEvent(LOG_TYPE.NTP_THUMBNAIL_ERROR);
+ });
+ thumb.appendChild(img);
+ logEvent(LOG_TYPE.NTP_THUMBNAIL_TILE);
+ } else {
+ thumb.classList.add('failed-img');
+ }
+ } else { // THUMBNAILS
+ // We keep track of the outcome of loading possible thumbnails for this
+ // tile. Possible values:
+ // - null: waiting for load/error
+ // - false: error
+ // - a string: URL that loaded correctly.
+ // This is populated by acceptImage/rejectImage and loadBestImage
+ // decides the best one to load.
+ var results = [];
+ var thumb = tile.querySelector('.mv-thumb');
+ var img = document.createElement('img');
+ var loaded = false;
+
+ var loadBestImage = function() {
+ if (loaded) {
+ return;
+ }
+ for (var i = 0; i < results.length; ++i) {
+ if (results[i] === null) {
+ return;
+ }
+ if (results[i] != false) {
+ img.src = results[i];
+ loaded = true;
+ return;
+ }
+ }
+ thumb.classList.add('failed-img');
+ thumb.removeChild(img);
+ logEvent(LOG_TYPE.NTP_THUMBNAIL_ERROR);
+ countLoad();
+ };
+
+ var acceptImage = function(idx, url) {
+ return function(ev) {
+ results[idx] = url;
+ loadBestImage();
+ };
+ };
+
+ var rejectImage = function(idx) {
+ return function(ev) {
+ results[idx] = false;
+ loadBestImage();
+ };
+ };
+
+ img.title = data.title;
+ img.classList.add('thumbnail');
+ loadedCounter += 1;
+ img.addEventListener('load', countLoad);
+ img.addEventListener('error', countLoad);
+ img.addEventListener('error', function(ev) {
+ thumb.classList.add('failed-img');
+ thumb.removeChild(img);
+ logEvent(LOG_TYPE.NTP_THUMBNAIL_ERROR);
+ });
+ thumb.appendChild(img);
+ logEvent(LOG_TYPE.NTP_THUMBNAIL_TILE);
+
+ // Get all thumbnailUrls for the tile.
+ // They are ordered from best one to be used to worst.
+ for (var i = 0; i < data.thumbnailUrls.length; ++i) {
+ results.push(null);
+ }
+ for (var i = 0; i < data.thumbnailUrls.length; ++i) {
+ if (data.thumbnailUrls[i]) {
+ var image = new Image();
+ image.src = data.thumbnailUrls[i];
+ image.onload = acceptImage(i, data.thumbnailUrls[i]);
+ image.onerror = rejectImage(i);
+ } else {
+ rejectImage(i)(null);
+ }
+ }
+
+ var favicon = tile.querySelector('.mv-favicon');
+ if (data.faviconUrl) {
+ var fi = document.createElement('img');
+ fi.src = data.faviconUrl;
+ // Set the title to empty so screen readers won't say the image name.
+ fi.title = '';
+ loadedCounter += 1;
+ fi.addEventListener('load', countLoad);
+ fi.addEventListener('error', countLoad);
+ fi.addEventListener('error', function(ev) {
+ favicon.classList.add('failed-favicon');
+ });
+ favicon.appendChild(fi);
+ } else {
+ favicon.classList.add('failed-favicon');
+ }
+ }
+
+ var mvx = tile.querySelector('.mv-x');
+ mvx.addEventListener('click', function(ev) {
+ removeAllOldTiles();
+ blacklistTile(tile);
+ ev.preventDefault();
+ ev.stopPropagation();
+ });
+
+ return tile;
+};
+
+
+/**
+ * Do some initialization and parses the query arguments passed to the iframe.
+ */
+var init = function() {
+ // Creates a new DOM element to hold the tiles.
+ tiles = document.createElement('div');
+
+ // Parse query arguments.
+ var query = window.location.search.substring(1).split('&');
+ queryArgs = {};
+ for (var i = 0; i < query.length; ++i) {
+ var val = query[i].split('=');
+ if (val[0] == '') continue;
+ queryArgs[decodeURIComponent(val[0])] = decodeURIComponent(val[1]);
+ }
+
+ // Apply class for icon NTP, if specified.
+ USE_ICONS = queryArgs['icons'] == '1';
+ if ('ntl' in queryArgs) {
+ var ntl = parseInt(queryArgs['ntl'], 10);
+ if (isFinite(ntl))
+ NUM_TITLE_LINES = ntl;
+ }
+
+ // Duplicating NTP_DESIGN.mainClass.
+ document.querySelector('#most-visited').classList.add(
+ USE_ICONS ? 'icon-ntp' : 'thumb-ntp');
+
+ // Enable RTL.
+ if (queryArgs['rtl'] == '1') {
+ var html = document.querySelector('html');
+ html.dir = 'rtl';
+ }
+
+ window.addEventListener('message', handlePostMessage);
+};
+
+
+window.addEventListener('DOMContentLoaded', init);
+})();
diff --git a/chromium/chrome/browser/resources/local_ntp/most_visited_thumbnail.css b/chromium/chrome/browser/resources/local_ntp/most_visited_thumbnail.css
index 52eb4159791..765d070e02a 100644
--- a/chromium/chrome/browser/resources/local_ntp/most_visited_thumbnail.css
+++ b/chromium/chrome/browser/resources/local_ntp/most_visited_thumbnail.css
@@ -35,8 +35,19 @@ span.blocker {
width: 100%;
}
-img {
+img.thumbnail {
height: auto;
min-height: 100%;
width: 100%;
}
+
+img.large-icon {
+ -webkit-clip-path: inset(0 0 0 0 round 4px);
+ height: 48px;
+ left: 50%;
+ margin-left: -24px;
+ margin-top: -24px;
+ position: absolute;
+ top: 50%;
+ width: 48px;
+}
diff --git a/chromium/chrome/browser/resources/local_ntp/most_visited_thumbnail.html b/chromium/chrome/browser/resources/local_ntp/most_visited_thumbnail.html
index 170beaeb29b..6912ac34fea 100644
--- a/chromium/chrome/browser/resources/local_ntp/most_visited_thumbnail.html
+++ b/chromium/chrome/browser/resources/local_ntp/most_visited_thumbnail.html
@@ -1,4 +1,4 @@
-<!DOCTYPE html>
+<!doctype html>
<html>
<head>
<meta charset="utf-8">
diff --git a/chromium/chrome/browser/resources/local_ntp/most_visited_thumbnail.js b/chromium/chrome/browser/resources/local_ntp/most_visited_thumbnail.js
index 2c3e770702b..3bdfd612e36 100644
--- a/chromium/chrome/browser/resources/local_ntp/most_visited_thumbnail.js
+++ b/chromium/chrome/browser/resources/local_ntp/most_visited_thumbnail.js
@@ -39,8 +39,11 @@ window.addEventListener('DOMContentLoaded', function() {
data.provider));
}
// Creates and adds an image.
- function createThumbnail(src) {
+ function createThumbnail(src, imageClass) {
var image = document.createElement('img');
+ if (imageClass) {
+ image.classList.add(imageClass);
+ }
image.onload = function() {
var link = createMostVisitedLink(
params, data.url, data.title, undefined, data.direction,
@@ -51,6 +54,7 @@ window.addEventListener('DOMContentLoaded', function() {
link.appendChild(blocker);
link.appendChild(image);
displayLink(link);
+ logEvent(NTP_LOGGING_EVENT_TYPE.NTP_TILE_LOADED);
};
image.onerror = function() {
// If no external thumbnail fallback (etfb), and have domain.
@@ -62,15 +66,20 @@ window.addEventListener('DOMContentLoaded', function() {
logEvent(NTP_LOGGING_EVENT_TYPE.NTP_EXTERNAL_TILE_FALLBACK);
}
logEvent(NTP_LOGGING_EVENT_TYPE.NTP_THUMBNAIL_ERROR);
+ logEvent(NTP_LOGGING_EVENT_TYPE.NTP_TILE_LOADED);
};
image.src = src;
}
+ var useIcons = params['icons'] == '1';
if (data.dummy) {
showEmptyTile();
logEvent(NTP_LOGGING_EVENT_TYPE.NTP_EXTERNAL_TILE);
- } else if (data.thumbnailUrl) {
- createThumbnail(data.thumbnailUrl);
+ } else if (useIcons && data.largeIconUrl) {
+ createThumbnail(data.largeIconUrl, 'large-icon');
+ // TODO(huangs): Log event for large icons.
+ } else if (!useIcons && data.thumbnailUrl) {
+ createThumbnail(data.thumbnailUrl, 'thumbnail');
logEvent(NTP_LOGGING_EVENT_TYPE.NTP_THUMBNAIL_TILE);
} else if (data.domain) {
showDomainElement();
diff --git a/chromium/chrome/browser/resources/local_ntp/most_visited_title.css b/chromium/chrome/browser/resources/local_ntp/most_visited_title.css
index a4e464fbc45..41e1b06e23e 100644
--- a/chromium/chrome/browser/resources/local_ntp/most_visited_title.css
+++ b/chromium/chrome/browser/resources/local_ntp/most_visited_title.css
@@ -1,15 +1,28 @@
/* Copyright 2013 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. */
+html {
+ height: 100%;
+}
+
body {
+ height: 100%;
width: 100%;
}
a {
+ height: 100%;
+ line-height: 117%;
overflow: hidden;
text-align: center; /* Can be overridden in JS. */
- text-overflow: ellipsis; /* Can be overridden in JS. */
- white-space: nowrap;
+ text-overflow: ellipsis; /* Can be overridden in JS. */
+ white-space: nowrap; /* Can be overridden in JS. */
+}
+
+a.multiline {
+ text-overflow: clip;
+ white-space: pre-wrap;
+ word-wrap: break-word;
}
a:focus {
diff --git a/chromium/chrome/browser/resources/local_ntp/most_visited_title.html b/chromium/chrome/browser/resources/local_ntp/most_visited_title.html
index a7a0193a206..5420eea02f9 100644
--- a/chromium/chrome/browser/resources/local_ntp/most_visited_title.html
+++ b/chromium/chrome/browser/resources/local_ntp/most_visited_title.html
@@ -1,4 +1,4 @@
-<!DOCTYPE html>
+<!doctype html>
<html>
<head>
<meta charset="utf-8">
diff --git a/chromium/chrome/browser/resources/local_ntp/most_visited_title.js b/chromium/chrome/browser/resources/local_ntp/most_visited_title.js
index 223afeee026..75be33b791d 100644
--- a/chromium/chrome/browser/resources/local_ntp/most_visited_title.js
+++ b/chromium/chrome/browser/resources/local_ntp/most_visited_title.js
@@ -17,3 +17,8 @@ window.addEventListener('DOMContentLoaded', function() {
data.provider));
});
});
+
+window.addEventListener('load', function() {
+ chrome.embeddedSearch.newTabPage.logEvent(
+ NTP_LOGGING_EVENT_TYPE.NTP_TILE_LOADED);
+});
diff --git a/chromium/chrome/browser/resources/local_ntp/most_visited_util.js b/chromium/chrome/browser/resources/local_ntp/most_visited_util.js
index ecb48210b40..dfa9c235def 100644
--- a/chromium/chrome/browser/resources/local_ntp/most_visited_util.js
+++ b/chromium/chrome/browser/resources/local_ntp/most_visited_util.js
@@ -42,7 +42,9 @@ var NTP_LOGGING_EVENT_TYPE = {
// The visuals of that tile's fallback are handled externally.
NTP_EXTERNAL_TILE_FALLBACK: 8,
// The user moused over an NTP tile or title.
- NTP_MOUSEOVER: 9
+ NTP_MOUSEOVER: 9,
+ // A NTP Tile has finished loading (successfully or failing).
+ NTP_TILE_LOADED: 10,
};
/**
@@ -119,6 +121,9 @@ function createMostVisitedLink(params, href, title, text, direction, provider) {
link.style.textOverflow = 'clip';
link.style.webkitMask = mask;
}
+ if (styles.numTitleLines && styles.numTitleLines > 1) {
+ link.classList.add('multiline');
+ }
link.href = href;
link.title = title;
@@ -126,8 +131,12 @@ function createMostVisitedLink(params, href, title, text, direction, provider) {
// Include links in the tab order. The tabIndex is necessary for
// accessibility.
link.tabIndex = '0';
- if (text)
- link.textContent = text;
+ if (text) {
+ // Wrap text with span so ellipsis will appear at the end of multiline.
+ var spanWrap = document.createElement('span');
+ spanWrap.textContent = text;
+ link.appendChild(spanWrap);
+ }
link.addEventListener('mouseover', function() {
var ntpApiHandle = chrome.embeddedSearch.newTabPage;
ntpApiHandle.logEvent(NTP_LOGGING_EVENT_TYPE.NTP_MOUSEOVER);
@@ -171,7 +180,10 @@ function createMostVisitedLink(params, href, title, text, direction, provider) {
window.parent.postMessage('tileBlacklisted,' + params.pos, DOMAIN_ORIGIN);
} else if (event.keyCode == 13 /* ENTER */ ||
event.keyCode == 32 /* SPACE */) {
- navigateFunction(event);
+ // Event target is the <a> tag. Send a click event on it, which will
+ // trigger the 'click' event registered above.
+ event.preventDefault();
+ event.target.click();
}
});
@@ -182,7 +194,7 @@ function createMostVisitedLink(params, href, title, text, direction, provider) {
/**
* Returns the color to display string with, depending on whether title is
* displayed, the current theme, and URL parameters.
- * @param {Object.<string, string>} params URL parameters specifying style.
+ * @param {Object<string, string>} params URL parameters specifying style.
* @param {boolean} isTitle if the style is for the Most Visited Title.
* @return {string} The color to use, in "rgba(#,#,#,#)" format.
*/
@@ -217,8 +229,9 @@ function getTextColor(params, isTitle) {
* - f: font-family.
* - fs: font-size as a number in pixels.
* - ta: text-align property, as a string.
- * - tf: specifying a text fade starting position, in pixels.
- * @param {Object.<string, string>} params URL parameters specifying style.
+ * - tf: text fade starting position, in pixels.
+ * - ntl: number of lines in the title.
+ * @param {Object<string, string>} params URL parameters specifying style.
* @param {boolean} isTitle if the style is for the Most Visited Title.
* @return {Object} Styles suitable for CSS interpolation.
*/
@@ -239,6 +252,11 @@ function getMostVisitedStyles(params, isTitle) {
if (isFinite(tf))
styles.textFadePos = tf;
}
+ if ('ntl' in params) {
+ var ntl = parseInt(params.ntl, 10);
+ if (isFinite(ntl))
+ styles.numTitleLines = ntl;
+ }
return styles;
}
@@ -262,15 +280,13 @@ function fillMostVisited(location, fill) {
// Means that the suggestion data comes from the server. Create data object.
data = {
url: params.url,
+ largeIconUrl: params.liu || '',
thumbnailUrl: params.tu || '',
title: params.ti || '',
direction: params.di || '',
domain: params.dom || '',
provider: params.pr || SERVER_PROVIDER_NAME
};
- // Log the fact that suggestion was obtained from the server.
- var ntpApiHandle = chrome.embeddedSearch.newTabPage;
- ntpApiHandle.logEvent(NTP_LOGGING_EVENT_TYPE.NTP_SERVER_SIDE_SUGGESTION);
} else {
var apiHandle = chrome.embeddedSearch.searchBox;
data = apiHandle.getMostVisitedItemData(params.rid);
@@ -279,6 +295,7 @@ function fillMostVisited(location, fill) {
// Allow server-side provider override.
data.provider = params.pr || CLIENT_PROVIDER_NAME;
}
+
if (isFinite(params.dummy) && parseInt(params.dummy, 10)) {
data.dummy = true;
}
diff --git a/chromium/chrome/browser/resources/local_state/local_state.html b/chromium/chrome/browser/resources/local_state/local_state.html
new file mode 100644
index 00000000000..0bb9e3ffdea
--- /dev/null
+++ b/chromium/chrome/browser/resources/local_state/local_state.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Local State Debug Page</title>
+ <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
+ <script src="chrome://resources/js/cr.js"></script>
+ <script src="chrome://resources/js/util.js"></script>
+ <script src="local_state.js"></script>
+</head>
+<body>
+ <pre id="content">
+ Loading Local State file...
+ </pre>
+</body>
+</html>
diff --git a/chromium/chrome/browser/resources/local_state/local_state.js b/chromium/chrome/browser/resources/local_state/local_state.js
new file mode 100644
index 00000000000..3011af54f9d
--- /dev/null
+++ b/chromium/chrome/browser/resources/local_state/local_state.js
@@ -0,0 +1,31 @@
+// 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.
+
+/**
+ * Javascript for local_state.html, served from chrome://local-state/
+ * This is used to debug the contents of the Local State file.
+ */
+
+cr.define('localState', function() {
+ 'use strict';
+
+ /**
+ * Sets the page content to the specified |localState| string, called
+ * from C++.
+ * @param {string} localState the JSON-formatted local state data,
+ * or an error message.
+ */
+ function setLocalState(localState) {
+ $('content').textContent = localState;
+ }
+
+ return {
+ setLocalState: setLocalState
+ };
+});
+
+// When the page loads, request the JSON local state data from C++.
+document.addEventListener('DOMContentLoaded', function() {
+ chrome.send('requestJson');
+});
diff --git a/chromium/chrome/browser/resources/md_settings/OWNERS b/chromium/chrome/browser/resources/md_settings/OWNERS
new file mode 100644
index 00000000000..057813143a1
--- /dev/null
+++ b/chromium/chrome/browser/resources/md_settings/OWNERS
@@ -0,0 +1,2 @@
+michaelpg@chromium.org
+stevenjb@chromium.org
diff --git a/chromium/chrome/browser/resources/md_settings/md_settings.css b/chromium/chrome/browser/resources/md_settings/md_settings.css
new file mode 100644
index 00000000000..ebd05b25add
--- /dev/null
+++ b/chromium/chrome/browser/resources/md_settings/md_settings.css
@@ -0,0 +1,23 @@
+/* Copyright 2015 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. */
+
+@font-face {
+ font-family: 'Roboto';
+ font-style: normal;
+ font-weight: 400;
+ src: local('Roboto Regular'), local('Roboto-Regular'), url(chrome://resources/roboto/roboto.woff2) format('woff2');
+ unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
+}
+
+body {
+ font-family: Roboto;
+}
+
+section {
+ margin-top: 50px;
+}
+
+cr-dropdown-menu {
+ width: 200px;
+}
diff --git a/chromium/chrome/browser/resources/md_settings/md_settings.html b/chromium/chrome/browser/resources/md_settings/md_settings.html
new file mode 100644
index 00000000000..d47aa97a3a5
--- /dev/null
+++ b/chromium/chrome/browser/resources/md_settings/md_settings.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Settings</title>
+ <script src="chrome://md-settings/polymer_config.js"></script>
+ <script src="chrome://resources/js/load_time_data.js"></script>
+ <script src="chrome://md-settings/strings.js"></script>
+ <link rel="import" href="chrome://md-settings/settings.html">
+ <link rel="stylesheet" href="md_settings.css">
+</head>
+<body>
+ <cr-settings></cr-settings>
+ <script src="chrome://resources/js/i18n_template.js"></script>
+</body>
+</html>
diff --git a/chromium/chrome/browser/resources/media/webrtc_logs.html b/chromium/chrome/browser/resources/media/webrtc_logs.html
index 0e6241778c6..99e54d08602 100644
--- a/chromium/chrome/browser/resources/media/webrtc_logs.html
+++ b/chromium/chrome/browser/resources/media/webrtc_logs.html
@@ -1,20 +1,21 @@
-<!DOCTYPE HTML>
-<html i18n-values="dir:textdirection;">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
<head>
<meta charset="utf-8">
<title i18n-content="webrtcLogsTitle"></title>
+ <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="webrtc_logs.css">
<script src="chrome://resources/js/load_time_data.js"></script>
<script src="chrome://resources/js/util.js"></script>
<script src="chrome://webrtc-logs/strings.js"></script>
<script src="chrome://webrtc-logs/webrtc_logs.js"></script>
</head>
-<body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<body>
<header><h1 i18n-content="webrtcLogsTitle"></h1></header>
<h2 id="log-banner"></h2>
<div id="log-list"></div>
<p id="no-logs" i18n-content="noLogsMessage" hidden></p>
- <script src="chrome://resources/js/i18n_template2.js"></script>
+ <script src="chrome://resources/js/i18n_template.js"></script>
<script src="chrome://resources/js/jstemplate_compiled.js"></script>
</body>
</html>
diff --git a/chromium/chrome/browser/resources/media_router/OWNERS b/chromium/chrome/browser/resources/media_router/OWNERS
new file mode 100644
index 00000000000..febfa7e3c3a
--- /dev/null
+++ b/chromium/chrome/browser/resources/media_router/OWNERS
@@ -0,0 +1,9 @@
+# Owners with committer privileges
+mfoltz@chromium.org
+wez@chromium.org
+
+# Team members without committer privileges, for potential FYI reviews.
+apacible@chromium.org
+haibinlu@chromium.org
+imcheng@chromium.org
+kmarshall@chromium.org
diff --git a/chromium/chrome/browser/resources/media_router/elements/icon/sad-face.png b/chromium/chrome/browser/resources/media_router/elements/icon/sad-face.png
new file mode 100644
index 00000000000..bb71fbe41c0
--- /dev/null
+++ b/chromium/chrome/browser/resources/media_router/elements/icon/sad-face.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/media_router/elements/icon/sad-face2x.png b/chromium/chrome/browser/resources/media_router/elements/icon/sad-face2x.png
new file mode 100644
index 00000000000..972b7f0c2cd
--- /dev/null
+++ b/chromium/chrome/browser/resources/media_router/elements/icon/sad-face2x.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/media_router/elements/issue_banner/issue_banner.css b/chromium/chrome/browser/resources/media_router/elements/issue_banner/issue_banner.css
new file mode 100644
index 00000000000..04ecb39adf5
--- /dev/null
+++ b/chromium/chrome/browser/resources/media_router/elements/issue_banner/issue_banner.css
@@ -0,0 +1,57 @@
+/* Copyright 2015 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. */
+
+.action {
+ /* TODO(apacible): Update to use Chromium paper color. */
+ background-color: rgb(82, 147, 242);
+ color: white;
+}
+
+.blocking-issue-header {
+ display: flex;
+ overflow: hidden;
+ padding-top: 10px;
+}
+
+.blocking-issue-message {
+ -webkit-padding-start: 8px;
+ color: rgb(125, 125, 125);
+ line-height: 1.125em;
+ vertical-align: middle;
+}
+
+.blocking-issue-title {
+ -webkit-padding-start: 8px;
+ font-weight: bold;
+ line-height: 1.125em;
+ vertical-align: top;
+}
+
+.issue-action {
+ color: black;
+ cursor: pointer;
+ text-decoration: underline;
+}
+
+#issue-buttons {
+ display: flex;
+}
+
+.non-blocking-issue {
+ -webkit-padding-start: 12px;
+ background-color: rgb(249, 236, 191);
+ line-height: 1.25em;
+ padding-bottom: 6px;
+ padding-top: 6px;
+}
+
+.sad-face {
+ -webkit-margin-start: 20px;
+ background-image: -webkit-image-set(
+ url(../icon/sad-face.png) 1x,
+ url(../icon/sad-face2x.png) 2x);
+ background-repeat: no-repeat;
+ height: 32px;
+ width: 32px;
+}
diff --git a/chromium/chrome/browser/resources/media_router/elements/issue_banner/issue_banner.html b/chromium/chrome/browser/resources/media_router/elements/issue_banner/issue_banner.html
new file mode 100644
index 00000000000..36b35af0c67
--- /dev/null
+++ b/chromium/chrome/browser/resources/media_router/elements/issue_banner/issue_banner.html
@@ -0,0 +1,46 @@
+<link rel="import" href="chrome://resources/polymer/polymer/polymer.html">
+<link rel="import" href="chrome://resources/polymer/paper-button/paper-button.html">
+<polymer-element name="issue-banner">
+<template>
+ <link rel="stylesheet" href="../../media_router_common.css">
+ <link rel="stylesheet" href="issue_banner.css">
+ <template if="{{issue.isBlocking}}">
+ <div class="blocking-issue-header">
+ <div class="sad-face"></div>
+ <div>
+ <div class="blocking-issue-title">{{issue.title}}</div>
+ <div class="blocking-issue-message">{{issue.message}}</div>
+ </div>
+ </div>
+ <div id="issue-buttons">
+ <template if="{{issue.optActionText}}">
+ <paper-button raised class="button" title="{{issue.optActionType}}"
+ on-click="{{onClickAction}}">
+ {{issue.optActionText}}
+ </paper-button>
+ </template>
+ <paper-button raised class="button action"
+ title="{{issue.defaultActionType}}"
+ on-click="{{onClickAction}}">
+ {{issue.defaultActionText}}
+ </paper-button>
+ </div>
+ </template>
+ <template if="{{!issue.isBlocking}}">
+ <div class="non-blocking-issue">
+ <div>{{issue.message}}</div>
+ <template if="{{issue.optActionText}}">
+ <span class="issue-action" title="{{issue.optActionType}}"
+ on-click="{{onClickAction}}">
+ {{issue.optActionText}}
+ </span>
+ </template>
+ <span class="issue-action" title="{{issue.defaultActionType}}"
+ on-click="{{onClickAction}}">
+ {{issue.defaultActionText}}
+ </span>
+ </div>
+ </template>
+</template>
+<script src="issue_banner.js"></script>
+</polymer-element> \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/media_router/elements/issue_banner/issue_banner.js b/chromium/chrome/browser/resources/media_router/elements/issue_banner/issue_banner.js
new file mode 100644
index 00000000000..145afac398c
--- /dev/null
+++ b/chromium/chrome/browser/resources/media_router/elements/issue_banner/issue_banner.js
@@ -0,0 +1,34 @@
+// Copyright 2015 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.
+
+// This Polymer element is used to show information about issues related
+// to casting.
+Polymer('issue-banner', {
+ publish: {
+ /**
+ * The issue to show.
+ *
+ * @attribute issue
+ * @type {media_router.Issue}
+ * @default null
+ */
+ issue: null,
+ },
+
+ /**
+ * Fires an issue-action-click event. This is called when an issue action
+ * is clicked.
+ *
+ * @param {!Event} event The event object.
+ * @param {Object} detail The details of the event.
+ * @param {!Element} sender Reference to clicked node.
+ */
+ onClickAction: function(event, detail, sender) {
+ this.fire('issue-action-click', {
+ id: this.issue.id,
+ actionType: parseInt(sender.title),
+ helpURL: this.issue.helpURL
+ });
+ },
+});
diff --git a/chromium/chrome/browser/resources/media_router/elements/media_router_container/media_router_container.css b/chromium/chrome/browser/resources/media_router/elements/media_router_container/media_router_container.css
new file mode 100644
index 00000000000..edf4c1e96c0
--- /dev/null
+++ b/chromium/chrome/browser/resources/media_router/elements/media_router_container/media_router_container.css
@@ -0,0 +1,56 @@
+/* Copyright 2015 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. */
+
+.cast-mode {
+ -webkit-padding-start: 5px;
+ cursor: pointer;
+ font-size: 1.0em;
+}
+
+.cast-mode:hover {
+ background-color: rgb(187, 206, 236);
+ border: 0;
+}
+
+#cast-mode-list {
+ -webkit-user-select: none;
+ color: rgb(44, 44, 44);
+ margin: 0;
+}
+
+#container-header {
+ background-color: rgb(214, 214, 214);
+}
+
+.route {
+ color: rgb(119, 119, 119);
+}
+
+#header-text {
+ -webkit-padding-start: 20px;
+ flex-grow: 1;
+ font-size: 1.25em;
+ margin: 0;
+}
+
+.sink {
+ -webkit-user-select: none;
+ cursor: pointer;
+ font-size: 1.0em;
+}
+
+.sink:hover {
+ background-color: rgb(187, 206, 236);
+ border: 0;
+}
+
+.sink-icon {
+ -webkit-padding-end: 10px;
+}
+
+#sink-list {
+ margin: 0;
+ max-height: 275px;
+ overflow-y: scroll;
+}
diff --git a/chromium/chrome/browser/resources/media_router/elements/media_router_container/media_router_container.html b/chromium/chrome/browser/resources/media_router/elements/media_router_container/media_router_container.html
new file mode 100644
index 00000000000..5df70ff579b
--- /dev/null
+++ b/chromium/chrome/browser/resources/media_router/elements/media_router_container/media_router_container.html
@@ -0,0 +1,70 @@
+<link rel="import" href="chrome://resources/polymer/polymer/polymer.html">
+<link rel="import" href="chrome://resources/polymer/core-icon/core-icon.html">
+<link rel="import" href="chrome://resources/polymer/core-icons/hardware-icons.html">
+<link rel="import" href="chrome://resources/polymer/core-menu/core-menu.html">
+<link rel="import" href="chrome://resources/polymer/core-toolbar/core-toolbar.html">
+<link rel="import" href="chrome://resources/polymer/paper-icon-button/paper-icon-button.html">
+<link rel="import" href="chrome://resources/polymer/paper-item/paper-item.html">
+<link rel="import" href="../issue_banner/issue_banner.html">
+<link rel="import" href="../route_details/route_details.html">
+<polymer-element name="media-router-container">
+<template>
+ <link rel="stylesheet" href="media_router_container.css">
+ <core-toolbar id="container-header"
+ hidden?="{{ {state: currentView_, castModeHidden: castModeHidden_} | isHeaderHidden}}">
+ <div id="header-text">
+ <span hidden?="{{ {state: currentView_, castModeHidden: castModeHidden_} | isSinkPickerHidden}}">
+ {{headerText}}
+ </span>
+ <span i18n-content="selectCastModeHeader"
+ hidden?="{{ {castModeHidden: castModeHidden_} | isCastModeHidden }}">
+ </span>
+ </div>
+ <paper-icon-button on-click="{{toggleCastMode}}"
+ icon="{{ {castModeHidden: castModeHidden_} | getDropDownIcon}}">
+ </paper-icon-button>
+ <paper-icon-button icon="close" on-click="{{closeButtonClicked}}">
+ </paper-icon-button>
+ </core-toolbar>
+ <issue-banner id="issue-banner" issue="{{issue}}"
+ hidden?="{{ {state: currentView_, issue: issue, castModeHidden: castModeHidden_ } | isIssueBannerHidden}}">
+ </issue-banner>
+ <!-- TODO(apacible): selectedAttribute here is a workaround for
+ https://github.com/Polymer/polymer/issues/946. Remove here and elsewhere when
+ core-menu is updated. -->
+ <core-menu id="cast-mode-list" selectedAttribute=""
+ hidden?="{{ {castModeHidden: castModeHidden_} | isCastModeHidden }}">
+ <template repeat="{{mode in castModeList}}">
+ <paper-item class="cast-mode" on-click="{{onCastModeSelected}}">
+ {{mode.description}}
+ </paper-item>
+ </template>
+ </core-menu>
+ <route-details id="route-details"
+ hidden?="{{ {state: currentView_} | isRouteDetailsHidden}}"
+ route="{{currentRoute_}}" sink="{{sinkMap_[[currentRoute_.sinkId]]}}"
+ on-back-click="{{showSinkPickerView}}"
+ on-close-route-click="{{showSinkPickerView}}">
+ </route-details>
+ <core-menu id="sink-list" selectedAttribute=""
+ hidden?="{{ {state: currentView_, castModeHidden: castModeHidden_} | isSinkPickerHidden}}">
+ <template repeat="{{sink in sinkList}}">
+ <paper-item class="sink" on-click="{{onSinkClick}}">
+ <!-- TODO(apacible): Show device icon based on device type. -->
+ <core-icon class="sink-icon" icon="hardware:chromecast"></core-icon>
+ <div>
+ {{sink.name}}
+ <template bind="{{sinkToRouteMap_[sink.id] as route}}">
+ <template if="{{route}}">
+ <div class="route">
+ {{route.title}}
+ </div>
+ </template>
+ </template>
+ </div>
+ </paper-item>
+ </template>
+ </core-menu>
+</template>
+<script src="media_router_container.js"></script>
+</polymer-element>
diff --git a/chromium/chrome/browser/resources/media_router/elements/media_router_container/media_router_container.js b/chromium/chrome/browser/resources/media_router/elements/media_router_container/media_router_container.js
new file mode 100644
index 00000000000..7561a7c43ab
--- /dev/null
+++ b/chromium/chrome/browser/resources/media_router/elements/media_router_container/media_router_container.js
@@ -0,0 +1,370 @@
+// Copyright 2015 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.
+
+(function() {
+ /**
+ * The possible states of media-router-container. Used to determine which
+ * components of media-router-container to show.
+ *
+ * @enum {number}
+ */
+ var MediaRouterContainerView = {
+ BLOCKING_ISSUE: 0,
+ ROUTE_DETAILS: 1,
+ SINK_PICKER: 2,
+ };
+
+// This Polymer element contains the entire media router interface. It handles
+// hiding and showing specific components.
+Polymer('media-router-container', {
+ publish: {
+ /**
+ * The list of CastModes to show.
+ *
+ * @attribute castModeList
+ * @type {?Array<!media_router.CastMode>}
+ * @default null
+ */
+ castModeList: null,
+
+ /**
+ * The header text.
+ *
+ * @attribute headerText
+ * @type {?string}
+ * @default null
+ */
+ headerText: null,
+
+ /**
+ * The issue to show.
+ *
+ * @attribute issue
+ * @type {?media_router.Issue}
+ * @default null
+ */
+ issue: null,
+
+ /**
+ * List of current routes.
+ *
+ * @attribute routeList
+ * @type {?Array<!media_router.Route>}
+ * @default null
+ */
+ routeList: null,
+
+ /**
+ * List of discovered sinks.
+ *
+ * @attribute sinkList
+ * @type {?Array<!media_router.Sink>}
+ * @default null
+ */
+ sinkList: null,
+ },
+
+ observe: {
+ routeList: 'rebuildRouteMaps',
+ selectedCastModeValue_: 'hideCastMode',
+ sinkList: 'rebuildSinkMap',
+ },
+
+ created: function() {
+ this.castModeList = [];
+ this.routeList = [];
+ this.routeMap_ = {};
+ this.sinkList = [];
+ this.sinkMap_ = {};
+ this.sinkToRouteMap_ = {};
+ },
+
+ /**
+ * Whether or not the cast-mode-picker is currently hidden.
+ * @private {boolean}
+ * @default true
+ */
+ castModeHidden_: true,
+
+ /**
+ * The current route.
+ * @private {?media_router.Route}
+ * @default null
+ */
+ currentRoute_: null,
+
+ /**
+ * The current view to be shown.
+ * @private {!MediaRouterContainerView}
+ * @default MediaRouterContainerView.SINK_PICKER
+ */
+ currentView_: MediaRouterContainerView.SINK_PICKER,
+
+ /**
+ * The previous view that was shown.
+ * @private {!MediaRouterContainerView}
+ * @default MediaRouterContainerView.SINK_PICKER
+ */
+ previousView_: MediaRouterContainerView.SINK_PICKER,
+
+ /**
+ * Maps media_router.Route.id to corresponding media_router.Route.
+ * @private {?Object<!string, !media_router.Route>}
+ * @default null
+ */
+ routeMap_: null,
+
+ /**
+ * The value of the selected cast mode in |castModeList|, or -1 if the
+ * user has not explicitly selected a mode.
+ * @private {number}
+ * @default -1
+ */
+ selectedCastModeValue_: -1,
+
+ /**
+ * Maps media_router.Sink.id to corresponding media_router.Sink.
+ * @private {?Object<!string, !media_router.Sink>}
+ * @default null
+ */
+ sinkMap_: null,
+
+ /**
+ * Maps media_router.Sink.id to corresponding media_router.Route.
+ * @private {?Object<!string, ?media_router.Route>}
+ * @default null
+ */
+ sinkToRouteMap_: null,
+
+ /**
+ * Adds |route| to |routeList|.
+ *
+ * @param {!media_router.Route} route The route to add.
+ */
+ addRoute: function(route) {
+ // Check if |route| already exists or if its associated sink
+ // does not exist.
+ if (this.routeMap_[route.id] || !this.sinkMap_[route.sinkId])
+ return;
+
+ // If there is an existing route associated with the same sink, its
+ // |sinkToRouteMap_| entry will be overwritten with that of the new route,
+ // which results in the correct sink to route mapping.
+ this.routeList.push(route);
+ },
+
+ /**
+ * Filter that returns whether or not the cast modes should be hidden.
+ *
+ * @param {{castModeHidden: boolean}} value The parameters passed into this
+ * filter.
+ * Parameters in |value|:
+ * castModeHidden - Whether or not the cast modes are currently hidden.
+ */
+ isCastModeHidden: function(value) {
+ return value['castModeHidden'];
+ },
+
+ /**
+ * Filter that returns whether or not the header should be hidden.
+ *
+ * @param {{state: !MediaRouterContainerView, castModeHidden: boolean}} value
+ * The parameters passed into this filter.
+ * Parameters in |value|:
+ * state - The current state of media-router-container.
+ * castModeHidden - Whether or not the cast modes are currently hidden.
+ */
+ isHeaderHidden: function(value) {
+ return value['state'] != MediaRouterContainerView.SINK_PICKER &&
+ value['castModeHidden'];
+ },
+
+ /**
+ * Filter that returns whether or not the issue banner should be hidden.
+ *
+ * @param {{state: !MediaRouterContainerView,
+ * issue: !media_router.Issue,
+ * castModeHidden: boolean}}
+ * value The parameters passed into this filter.
+ * Parameters in |value|:
+ * state - The current state of media-router-container.
+ * issue - The current value of |issue|.
+ * castModeHidden - Whether or not the cast modes are currently hidden.
+ */
+ isIssueBannerHidden: function(value) {
+ return (!value['issue'] &&
+ value['state'] != MediaRouterContainerView.BLOCKING_ISSUE) ||
+ !value['castModeHidden'];
+ },
+
+ /**
+ * Filter that returns whether or not the route details should be hidden.
+ *
+ * @param {{state: !MediaRouterContainerView}} value The parameters passed
+ * into this filter.
+ * Parameters in |value|:
+ * state - The current state of media-router-container.
+ */
+ isRouteDetailsHidden: function(value) {
+ return value['state'] != MediaRouterContainerView.ROUTE_DETAILS;
+ },
+
+ /**
+ * Filter that returns whether or not the sink picker should be hidden.
+ *
+ * @param {{state: !MediaRouterContainerView, castModeHidden: boolean}} value
+ * The parameters passed into this filter.
+ * Parameters in |value|:
+ * state - The current state of media-router-container.
+ * castModeHidden - Whether or not the cast modes are currently hidden.
+ */
+ isSinkPickerHidden: function(value) {
+ return value['state'] != MediaRouterContainerView.SINK_PICKER ||
+ !value['castModeHidden'];
+ },
+
+ /**
+ * Creates a new route if |route| is null.
+ *
+ * @param {!media_router.Sink} sink The sink to use.
+ * @param {!media_router.Route} route The current route tied to |sink|.
+ */
+ maybeCreateRoute: function(sink, route) {
+ if (route) {
+ this.showRouteDetailsView();
+ } else {
+ this.fire('create-route', {
+ sinkId: sink.id,
+ selectedCastModeValue: this.selectedCastModeValue_
+ });
+ }
+ },
+
+ /**
+ * Filter that returns the arrow-drop-* icon to show.
+ *
+ * @param {{castModeHidden: boolean}} value The parameters passed into this
+ * filter.
+ * Parameters in |value|:
+ * castModeHidden - Whether or not the cast mode is currently hidden.
+ */
+ getDropDownIcon: function(value) {
+ return value['castModeHidden'] ? 'arrow-drop-down' : 'arrow-drop-up';
+ },
+
+ /**
+ * Hides cast-mode-picker.
+ */
+ hideCastMode: function() {
+ this.castModeHidden_ = true;
+ },
+
+ /**
+ * Called when |issue| has changed. Shows issue-banner if there exists an
+ * issue to show. If |newValue| is null, then show the previous view.
+ *
+ * @param {?media_router.Issue} oldValue The previous value for |issue|.
+ * @param {?media_router.Issue} newValue The new value for |issue|.
+ */
+ issueChanged: function(oldValue, newValue) {
+ if (newValue) {
+ // Checks that |newValue| is blocking. Also checks that previous issue
+ // either did not exist or was not a blocking issue.
+ if (newValue.isBlocking && (!oldValue || !oldValue.isBlocking)) {
+ this.previousView_ = this.currentView_;
+ this.currentView_ = MediaRouterContainerView.BLOCKING_ISSUE;
+ }
+ } else {
+ // Return to the previous view.
+ this.currentView_ = this.previousView_;
+ }
+ },
+
+ /**
+ * Fires a close-button-click event. Called when the close button is clicked.
+ */
+ closeButtonClicked: function() {
+ this.fire('close-button-click');
+ },
+
+ /**
+ * Updates |headerText| and |selectedCastModeValue_|. This is called when one
+ * of the cast modes has been selected.
+ *
+ * @param {!Event} event The event object.
+ * @param {!Object} detail The details of the event.
+ * @param {!Element} sender Reference to clicked node.
+ */
+ onCastModeSelected: function(event, detail, sender) {
+ var clickedMode = event.target.templateInstance.model.mode;
+ this.headerText = clickedMode.title;
+ this.selectedCastModeValue_ = clickedMode.type;
+ },
+
+ /**
+ * Called when a sink is clicked. Updates |currentRoute_|.
+ *
+ * @param {!Event} event The event object.
+ * @param {Object} detail The details of the event.
+ * @param {!Element} sender Reference to clicked node.
+ */
+ onSinkClick: function(event, detail, sender) {
+ var clickedSink = event.target.templateInstance.model.sink;
+ this.currentRoute_ = this.sinkToRouteMap_[clickedSink.id];
+ this.maybeCreateRoute(clickedSink, this.currentRoute_);
+ },
+
+ /**
+ * Called when |routeList| is updated. Rebuilds |routeMap_| and
+ * |sinkToRouteMap_|.
+ */
+ rebuildRouteMaps: function() {
+ // Reset |routeMap_| and |sinkToRouteMap_|.
+ this.routeMap_ = {};
+ this.sinkToRouteMap_ = {};
+
+ // Rebuild |routeMap_| and |sinkToRouteMap_|.
+ this.routeList.forEach(function(route) {
+ this.routeMap_[route.id] = route;
+ this.sinkToRouteMap_[route.sinkId] = route;
+ }, this);
+ },
+
+ /**
+ * Called when |sinkList| is updated. Rebuilds |sinkMap_|.
+ */
+ rebuildSinkMap: function() {
+ // Reset |sinkMap_|.
+ this.sinkMap_ = {};
+
+ // Rebuild |sinkMap_|.
+ this.sinkList.forEach(function(sink) {
+ this.sinkMap_[sink.id] = sink;
+ }, this);
+ },
+
+ /**
+ * Updates |currentView_| to ROUTE_DETAILS.
+ */
+ showRouteDetailsView: function() {
+ this.previousView_ = this.currentView_;
+ this.currentView_ = MediaRouterContainerView.ROUTE_DETAILS;
+ },
+
+ /**
+ * Updates |currentView_| to SINK_PICKER.
+ */
+ showSinkPickerView: function() {
+ this.previousView_ = this.currentView_;
+ this.currentView_ = MediaRouterContainerView.SINK_PICKER;
+ },
+
+ /**
+ * Toggles |castModeHidden_|.
+ */
+ toggleCastMode: function() {
+ this.castModeHidden_ = !this.castModeHidden_;
+ },
+});
+})();
diff --git a/chromium/chrome/browser/resources/media_router/elements/route_details/route_details.css b/chromium/chrome/browser/resources/media_router/elements/route_details/route_details.css
new file mode 100644
index 00000000000..d53c854e41d
--- /dev/null
+++ b/chromium/chrome/browser/resources/media_router/elements/route_details/route_details.css
@@ -0,0 +1,35 @@
+/* Copyright 2015 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. */
+
+#back-to-devices {
+ -webkit-padding-start: 10px;
+ color: rgb(20, 86, 218);
+ font-weight: bold;
+ line-height: 2em;
+}
+
+#back-to-devices:hover {
+ cursor: pointer;
+ text-decoration: underline;
+}
+
+hr {
+ margin: 0;
+}
+
+.route {
+ -webkit-padding-start: 10px;
+ line-height: 1.125em;
+}
+
+.route-status {
+ color: rgb(125, 125, 125);
+ margin: 8px 0;
+}
+
+.route-title {
+ font-weight: bold;
+ margin: 8px 0;
+ overflow: hidden;
+}
diff --git a/chromium/chrome/browser/resources/media_router/elements/route_details/route_details.html b/chromium/chrome/browser/resources/media_router/elements/route_details/route_details.html
new file mode 100644
index 00000000000..9c7baad7701
--- /dev/null
+++ b/chromium/chrome/browser/resources/media_router/elements/route_details/route_details.html
@@ -0,0 +1,28 @@
+<link rel="import" href="chrome://resources/polymer/polymer/polymer.html">
+<link rel="import" href="chrome://resources/polymer/paper-button/paper-button.html">
+<polymer-element name="route-details">
+<template>
+ <link rel="stylesheet" href="../../media_router_common.css">
+ <link rel="stylesheet" href="route_details.css">
+ <div>
+ <div id="back-to-devices" on-click="{{back}}">
+ <span i18n-content="backToSinkPicker"></span>
+ </div>
+ <hr>
+ <div class="route">
+ <div class="route-title">
+ {{route.title}}
+ </div>
+ <div class="route-status">
+ {{activityStatus_}}
+ </div>
+ </div>
+ </div>
+ <div>
+ <paper-button raised class="button" on-click="{{closeRoute}}"
+ i18n-content="stopCastingButton">
+ </paper-button>
+ </div>
+</template>
+<script src="route_details.js"></script>
+</polymer-element>
diff --git a/chromium/chrome/browser/resources/media_router/elements/route_details/route_details.js b/chromium/chrome/browser/resources/media_router/elements/route_details/route_details.js
new file mode 100644
index 00000000000..5454d0e211c
--- /dev/null
+++ b/chromium/chrome/browser/resources/media_router/elements/route_details/route_details.js
@@ -0,0 +1,61 @@
+// Copyright 2015 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.
+
+// This Polymer element shows information from media that is currently cast
+// to a device. It is assumed that |route| and |sink| correspond to each other.
+Polymer('route-details', {
+ publish: {
+ /**
+ * The route to show.
+ *
+ * @attribute route
+ * @type {media_router.Route}
+ * @default null
+ */
+ route: null,
+
+ /**
+ * The sink to show.
+ *
+ * @attribute sink
+ * @type {media_router.Sink}
+ * @default null
+ */
+ sink: null,
+ },
+
+ observe: {
+ sink: 'updateActivityStatus',
+ },
+
+ /**
+ * The current casting activity status.
+ * @private {string}
+ * @default ''
+ */
+ activityStatus_: '',
+
+ /**
+ * Fires a back-click event. This is called when the back link is clicked.
+ */
+ back: function() {
+ this.fire('back-click');
+ },
+
+ /**
+ * Fires a close-route-click event. This is called when the button to close
+ * the current route is clicked.
+ */
+ closeRoute: function() {
+ this.fire('close-route-click', {route: this.route});
+ },
+
+ /**
+ * Updates |activityStatus_| with the name of |sink|.
+ */
+ updateActivityStatus: function() {
+ this.activityStatus_ = this.sink ?
+ loadTimeData.getStringF('castingActivityStatus', this.sink.name) : '';
+ }
+});
diff --git a/chromium/chrome/browser/resources/media_router/media_router.css b/chromium/chrome/browser/resources/media_router/media_router.css
new file mode 100644
index 00000000000..ad52aa1f186
--- /dev/null
+++ b/chromium/chrome/browser/resources/media_router/media_router.css
@@ -0,0 +1,15 @@
+/* Copyright 2015 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. */
+
+body {
+ font-family: sans-serif;
+ font-size: 0.75em;
+ margin: 0;
+}
+
+#media-router-container {
+ background-color: rgb(246, 246, 246);
+ display: flex;
+ flex-direction: column;
+}
diff --git a/chromium/chrome/browser/resources/media_router/media_router.html b/chromium/chrome/browser/resources/media_router/media_router.html
new file mode 100644
index 00000000000..bd31f3a7d81
--- /dev/null
+++ b/chromium/chrome/browser/resources/media_router/media_router.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html i18n-values="dir:textdirection;lang:language">
+<head>
+ <meta charset="utf-8">
+ <title i18n-content="mediaRouterTitle"></title>
+
+ <link rel="stylesheet" href="media_router.css">
+ <link rel="import" href="chrome://media-router/elements/media_router_container/media_router_container.html">
+
+ <script src="chrome://resources/js/cr.js"></script>
+ <script src="chrome://resources/js/load_time_data.js"></script>
+ <script src="chrome://resources/js/util.js"></script>
+ <script src="chrome://media-router/media_router.js"></script>
+ <script src="chrome://media-router/strings.js"></script>
+</head>
+<body>
+ <media-router-container id="media-router-container"></media-router-container>
+ <script src="chrome://resources/js/i18n_template_polymer.js"></script>
+</body>
+</html>
diff --git a/chromium/chrome/browser/resources/media_router/media_router.js b/chromium/chrome/browser/resources/media_router/media_router.js
new file mode 100644
index 00000000000..82cb1649e8c
--- /dev/null
+++ b/chromium/chrome/browser/resources/media_router/media_router.js
@@ -0,0 +1,88 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+<include src="media_router_data.js">
+<include src="media_router_ui_interface.js">
+
+// Handles user events for the Media Router UI.
+cr.define('media_router', function() {
+ 'use strict';
+
+ // The media-router-container element. Initialized after polymer is ready.
+ var container = null;
+
+ /**
+ * Initializes the Media Router WebUI and requests initial media
+ * router content, such as the media sink and media route lists.
+ */
+ function initialize() {
+ // TODO(apacible): Add chrome.send call when browser WebUI message
+ // handler is implemented.
+
+ container = $('media-router-container');
+ media_router.ui.setContainer(container);
+
+ container.addEventListener('close-button-click', onCloseDialogClick);
+ container.addEventListener('close-route-click', onCloseRouteClick);
+ container.addEventListener('create-route', onCreateRoute);
+ container.addEventListener('issue-action-click', onIssueActionClick);
+ }
+
+ /**
+ * Closes the dialog.
+ * Called when the user clicks the close button on the dialog.
+ */
+ function onCloseDialogClick() {
+ media_router.browserApi.closeDialog();
+ }
+
+ /**
+ * Acts on an issue and dismisses it from the UI.
+ * Called when the user performs an action on an issue.
+ *
+ * @param {{detail: {id: string, actionType: number, helpURL: string}}} data
+ * Parameters in |data|.detail:
+ * id - issue ID.
+ * actionType - type of action performed by the user.
+ * helpURL - the help URL for the issue.
+ */
+ function onIssueActionClick(data) {
+ media_router.browserApi.actOnIssue(data.detail.id,
+ data.detail.actionType,
+ data.detail.helpURL);
+ container.issue = null;
+ }
+
+ /**
+ * Creates a media route.
+ * Called when the user requests to create a media route.
+ *
+ * @param {{detail: {sinkId: string, selectedCastModeValue: number}}} data
+ * Parameters in |data|.detail:
+ * sinkId - sink ID selected by the user.
+ * selectedCastModeValue - cast mode selected by the user.
+ */
+ function onCreateRoute(data) {
+ media_router.browserApi.requestRoute(data.detail.sinkId,
+ data.detail.selectedCastModeValue);
+ }
+
+ /**
+ * Stops a route.
+ * Called when the user requests to stop a media route.
+ *
+ * @param {{detail: {route: string}}} data
+ * Parameters in |data|.detail:
+ * route - route ID.
+ */
+ function onCloseRouteClick(data) {
+ media_router.browserApi.closeRoute(data.detail.route);
+ }
+
+ return {
+ initialize: initialize,
+ };
+});
+
+window.addEventListener('polymer-ready', media_router.initialize);
diff --git a/chromium/chrome/browser/resources/media_router/media_router_common.css b/chromium/chrome/browser/resources/media_router/media_router_common.css
new file mode 100644
index 00000000000..47d72ae207c
--- /dev/null
+++ b/chromium/chrome/browser/resources/media_router/media_router_common.css
@@ -0,0 +1,15 @@
+/* Copyright 2015 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. */
+
+.button {
+ /* TODO(apacible): Update to use Chromium paper color. */
+ background-color: rgb(238, 238, 238);
+ color: rgb(44, 44, 44);
+ cursor: pointer;
+ font-size: 1em;
+ font-weight: bold;
+ overflow: hidden;
+ text-align: center;
+ width: 100%;
+}
diff --git a/chromium/chrome/browser/resources/media_router/media_router_data.js b/chromium/chrome/browser/resources/media_router/media_router_data.js
new file mode 100644
index 00000000000..1cc6562fad2
--- /dev/null
+++ b/chromium/chrome/browser/resources/media_router/media_router_data.js
@@ -0,0 +1,164 @@
+// Copyright 2015 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.
+
+// Any strings used here will already be localized. Values such as
+// CastMode.type or IDs will be defined elsewhere and determined later.
+cr.define('media_router', function() {
+ 'use strict';
+
+ /**
+ * @enum {string}
+ */
+ var SinkStatus = {
+ IDLE: 'idle',
+ ACTIVE: 'active',
+ REQUEST_PENDING: 'request_pending'
+ };
+
+
+ /**
+ * @param {number} type The type of cast mode. This corresponds to the
+ * C++ MediaCastMode.
+ * @param {string} title The title of the cast mode.
+ * @param {string} description The description of the cast mode.
+ * @constructor
+ * @struct
+ */
+ var CastMode = function(type, title, description) {
+ /** @type {number} */
+ this.type = type;
+
+ /** @type {string} */
+ this.title = title;
+
+ /** @type {string} */
+ this.description = description;
+ };
+
+
+ /**
+ * @param {string} id The ID of this issue.
+ * @param {string} title The issue title.
+ * @param {string} message The issue message.
+ * @param {string} defaultActionText The button text of default action.
+ * @param {number} defaultActionType The type of default action.
+ * @param {?string} secondaryActionText The button text of optional action.
+ * @param {?number} secondaryActionType The type of optional action.
+ * @param {?string} mediaRouteId The route ID to which this issue
+ * pertains. If not set, this is a global issue.
+ * @param {boolean} isBlocking True if this issue blocks other UI.
+ * @param {?string} helpURL The URL to be opened if learn more is clicked.
+ * @constructor
+ * @struct
+ */
+ var Issue = function(id, title, message, defaultActionText,
+ defaultActionType, secondaryActionText,
+ secondaryActionType, mediaRouteId, isBlocking,
+ helpURL) {
+ /** @type {string} */
+ this.id = id;
+
+ /** @type {string} */
+ this.title = title;
+
+ /** @type {string} */
+ this.message = message;
+
+ /** @type {string} */
+ this.defaultActionText = defaultActionText;
+
+ /** @type {number} */
+ this.defaultActionType = defaultActionType;
+
+ /** @type {?string} */
+ this.secondaryActionText = secondaryActionText;
+
+ /** @type {?number} */
+ this.secondaryActionType = secondaryActionType;
+
+ /** @type {?string} */
+ this.mediaRouteId = mediaRouteId;
+
+ /** @type {boolean} */
+ this.isBlocking = isBlocking;
+
+ /** @type {?string} */
+ this.helpURL = helpURL;
+ };
+
+
+ /**
+ * @param {string} id The media route ID.
+ * @param {string} sinkId The ID of the media sink running this route.
+ * @param {string} title The short description of this route.
+ * @param {?number} tabId The ID of the tab in which web app is running and
+ * accessing the route.
+ * @param {boolean} isLocal True if this is a locally created route.
+ * @constructor
+ * @struct
+ */
+ var Route = function(id, sinkId, title, tabId, isLocal) {
+ /** @type {string} */
+ this.id = id;
+
+ /** @type {string} */
+ this.sinkId = sinkId;
+
+ /** @type {string} */
+ this.title = title;
+
+ /** @type {?number} */
+ this.tabId = tabId;
+
+ /** @type {boolean} */
+ this.isLocal = isLocal;
+ };
+
+
+ /**
+ * @param {string} id The ID of the media sink.
+ * @param {string} name The name of the sink.
+ * @param {media_router.SinkStatus} status The readiness state of the sink.
+ * @param {!Array<number>} castModes Cast modes compatible with the sink.
+ * @constructor
+ * @struct
+ */
+ var Sink = function(id, name, status) {
+ /** @type {string} */
+ this.id = id;
+
+ /** @type {string} */
+ this.name = name;
+
+ /** @type {media_router.SinkStatus} */
+ this.status = status;
+
+ /** @type {!Array<number>} */
+ this.castModes = castModes;
+ };
+
+
+ /**
+ * @param {number} tabId The current tab ID.
+ * @param {string} domain The domain of the current tab.
+ * @constructor
+ * @struct
+ */
+ var TabInfo = function(tabId, domain) {
+ /** @type {number} */
+ this.tabId = tabId;
+
+ /** @type {string} */
+ this.domain = domain;
+ };
+
+ return {
+ SinkStatus: SinkStatus,
+ CastMode: CastMode,
+ Issue: Issue,
+ Route: Route,
+ Sink: Sink,
+ TabInfo: TabInfo,
+ };
+});
diff --git a/chromium/chrome/browser/resources/media_router/media_router_ui_interface.js b/chromium/chrome/browser/resources/media_router/media_router_ui_interface.js
new file mode 100644
index 00000000000..1ee4200e79f
--- /dev/null
+++ b/chromium/chrome/browser/resources/media_router/media_router_ui_interface.js
@@ -0,0 +1,125 @@
+// Copyright 2015 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.
+
+// API invoked by the browser MediaRouterWebUIMessageHandler to communicate
+// with this UI.
+cr.define('media_router.ui', function() {
+ 'use strict';
+
+ // The media-router-container element.
+ var container = null;
+
+ /**
+ * Adds a new route.
+ *
+ * @param {!media_router.Route} route
+ */
+ function addRoute(route) {
+ container.addRoute(route);
+ }
+
+ /**
+ * Sets the cast mode list.
+ *
+ * @param {!Array<!media_router.CastMode>} castModeList
+ */
+ function setCastModeList(castModeList) {
+ container.castModeList = castModeList;
+ }
+
+ /**
+ * Sets |container|.
+ *
+ * @param {!MediaRouterContainerElement} mediaRouterContainer
+ */
+ function setContainer(mediaRouterContainer) {
+ container = mediaRouterContainer;
+ }
+
+ /**
+ * Sets the current issue.
+ *
+ * @param {!media_router.Issue} issue
+ */
+ function setIssue(issue) {
+ container.issue = issue;
+ }
+
+ /**
+ * Sets the list of currently active routes.
+ *
+ * @param {!Array<!media_router.Route>} routeList
+ */
+ function setRouteList(routeList) {
+ container.routeList = routeList;
+ }
+
+ /**
+ * Sets the list of discovered sinks.
+ *
+ * @param {!Array<!media_router.Sink>} sinkList
+ */
+ function setSinkList(sinkList) {
+ container.sinkList = sinkList;
+ }
+
+ return {
+ addRoute: addRoute,
+ setCastModeList: setCastModeList,
+ setContainer: setContainer,
+ setIssue: setIssue,
+ setRouteList: setRouteList,
+ setSinkList: setSinkList,
+ };
+});
+
+// API invoked by this UI to communicate with the browser WebUI message handler.
+cr.define('media_router.browserApi', function() {
+ 'use strict';
+
+ /**
+ * Acts on the given issue.
+ *
+ * @param {string} issueId
+ * @param {number} actionType Type of action that the user clicked.
+ * @param {?string} helpURL URL to open if the action is to learn more.
+ */
+ function actOnIssue(issueId, actionType, helpURL) {
+ // TODO(imcheng): Implement.
+ }
+
+ /**
+ * Closes the given route.
+ *
+ * @param {!media_router.Route} route
+ */
+ function closeRoute(route) {
+ // TODO(imcheng): Implement.
+ }
+
+ /**
+ * Requests that a media route be started with the given sink.
+ *
+ * @param {string} sinkId The sink ID.
+ * @param {number} selectedCastMode The value of the cast mode the user
+ * selected, or -1 if the user has not explicitly selected a mode.
+ */
+ function requestRoute(sinkId, selectedCastMode) {
+ // TODO(imcheng): Implement.
+ }
+
+ /**
+ * Closes the dialog.
+ */
+ function closeDialog() {
+ chrome.send('closeDialog');
+ }
+
+ return {
+ actOnIssue: actOnIssue,
+ closeRoute: closeRoute,
+ requestRoute: requestRoute,
+ closeDialog: closeDialog,
+ };
+});
diff --git a/chromium/chrome/browser/resources/memory_internals/memory_internals.html b/chromium/chrome/browser/resources/memory_internals/memory_internals.html
index 0e659176268..94a8ea89413 100644
--- a/chromium/chrome/browser/resources/memory_internals/memory_internals.html
+++ b/chromium/chrome/browser/resources/memory_internals/memory_internals.html
@@ -1,7 +1,8 @@
-<!DOCTYPE html>
+<!doctype html>
<!-- Copyright 2013 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. -->
+<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="memory_internals.css">
<link rel="stylesheet" href="list.css">
<link rel="stylesheet" href="extension_view.css">
diff --git a/chromium/chrome/browser/resources/net_export/net_export.css b/chromium/chrome/browser/resources/net_export/net_export.css
index 0078257144c..0fae6394a3d 100644
--- a/chromium/chrome/browser/resources/net_export/net_export.css
+++ b/chromium/chrome/browser/resources/net_export/net_export.css
@@ -4,7 +4,6 @@
*/
body {
- font-family: sans-serif;
font-size: 80%;
}
@@ -17,6 +16,10 @@ button {
width: 200px;
}
+.radio-button-div {
+ margin: 7px auto;
+}
+
.warning {
color: red;
font-size: 90%;
diff --git a/chromium/chrome/browser/resources/net_export/net_export.html b/chromium/chrome/browser/resources/net_export/net_export.html
index abe55783f3b..fde4370d73a 100644
--- a/chromium/chrome/browser/resources/net_export/net_export.html
+++ b/chromium/chrome/browser/resources/net_export/net_export.html
@@ -1,4 +1,4 @@
-<!DOCTYPE HTML>
+<!doctype html>
<html>
<head>
<meta charset="utf-8">
@@ -8,24 +8,18 @@
<script src="chrome://resources/js/util.js"></script>
<script src="chrome://resources/js/cr.js"></script>
<script src="chrome://net-export/net_export.js"></script>
+<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="net_export.css">
</head>
<body>
- <h2>NetLog Export</h2>
+ <h2>Network Log Export</h2>
<div id="net-export-main">
<div>
- <label>
- <input id="export-view-private-data-toggle"
- type="checkbox" checked disabled>
- Strip private information (cookies and credentials)
- </label>
- </div>
- <div>
<button id="export-view-start-data" disabled>
Start Logging to Disk
- <div class="warning" id="export-view-deletes-log-text" hidden>
+ <div class="warning" id="export-view-deletes-log-text" hidden>
Deletes old log
- </div>
+ </div>
</button>
</div>
<div>
@@ -42,7 +36,6 @@
</div>
</button>
</div>
- <pre id="export-view-file-path-text"></pre>
<p>
<b>INSTRUCTIONS</b>: Start logging, reproduce the problem,
and then stop logging. Make sure to send the email before
@@ -54,12 +47,38 @@
of desktop Chrome.
</p>
<p>
- <span class="warning">WARNING</span>: Logs contain a list of sites visited
- from when logging started to when logging stopped. They may also contain
- general network configuration information, such as DNS and proxy
+ <b><span class="warning">WARNING</span></b>: Logs contain a list of sites
+ visited from when logging started to when logging stopped. They may also
+ contain general network configuration information, such as DNS and proxy
configuration. If private information is not stripped, the logs also
contain cookies and credentials.
</p>
+ <p>
+ <b>ADVANCED</b>:
+ <span class="warning">This section should normally be left alone.</span>
+ <div class="radio-button-div">
+ <label>
+ <input id="export-view-strip-private-data-button" type="radio"
+ name="log-mode" value="STRIP_PRIVATE_DATA" checked disabled>
+ Strip private information
+ </label>
+ </div>
+ <div class="radio-button-div">
+ <label>
+ <input id="export-view-include-private-data-button" type="radio"
+ name="log-mode" value="NORMAL" disabled>
+ Include cookies and credentials
+ </label>
+ </div>
+ <div class="radio-button-div">
+ <label>
+ <input id="export-view-log-bytes-button" type="radio"
+ name="log-mode" value="LOG_BYTES" disabled>
+ Include raw bytes (will include cookies and credentials)
+ </label>
+ </div>
+ </p>
</div>
+ <pre id="export-view-file-path-text"></pre>
</body>
</html>
diff --git a/chromium/chrome/browser/resources/net_export/net_export.js b/chromium/chrome/browser/resources/net_export/net_export.js
index c9521b42f39..702b22e5835 100644
--- a/chromium/chrome/browser/resources/net_export/net_export.js
+++ b/chromium/chrome/browser/resources/net_export/net_export.js
@@ -46,8 +46,9 @@ var NetExportView = (function() {
* Starts saving NetLog data to a file.
*/
onStartData_: function() {
- var stripPrivateData = $('export-view-private-data-toggle').checked;
- chrome.send('startNetLog', [stripPrivateData]);
+ var logMode =
+ document.querySelector('input[name="log-mode"]:checked').value;
+ chrome.send('startNetLog', [logMode]);
},
/**
@@ -81,16 +82,20 @@ var NetExportView = (function() {
$('export-view-file-path-text').textContent = '';
}
- $('export-view-private-data-toggle').disabled = true;
- $('export-view-start-data').disabled = true;
+ // Disable all controls. Useable controls are enabled below.
+ var controls = document.querySelectorAll('button, input');
+ for (var i = 0; i < controls.length; ++i) {
+ controls[i].disabled = true;
+ }
+
$('export-view-deletes-log-text').hidden = true;
- $('export-view-stop-data').disabled = true;
- $('export-view-send-data').disabled = true;
$('export-view-private-data-text').hidden = true;
$('export-view-send-old-log-text').hidden = true;
if (exportNetLogInfo.state == 'NOT_LOGGING') {
// Allow making a new log.
- $('export-view-private-data-toggle').disabled = false;
+ $('export-view-strip-private-data-button').disabled = false;
+ $('export-view-include-private-data-button').disabled = false;
+ $('export-view-log-bytes-button').disabled = false;
$('export-view-start-data').disabled = false;
// If there's an existing log, allow sending it.
@@ -104,9 +109,9 @@ var NetExportView = (function() {
}
}
} else if (exportNetLogInfo.state == 'LOGGING') {
- // Only possible to stop logging. Checkbox reflects current state.
- $('export-view-private-data-toggle').checked =
- (exportNetLogInfo.logType == 'STRIP_PRIVATE_DATA');
+ // Only possible to stop logging. Radio buttons reflects current state.
+ document.querySelector('input[name="log-mode"][value="' +
+ exportNetLogInfo.logType + '"]').checked = true;
$('export-view-stop-data').disabled = false;
} else if (exportNetLogInfo.state == 'UNINITIALIZED') {
$('export-view-file-path-text').textContent =
diff --git a/chromium/chrome/browser/resources/net_internals/bandwidth_view.html b/chromium/chrome/browser/resources/net_internals/bandwidth_view.html
index db30bebe9a2..01e804e558e 100644
--- a/chromium/chrome/browser/resources/net_internals/bandwidth_view.html
+++ b/chromium/chrome/browser/resources/net_internals/bandwidth_view.html
@@ -1,6 +1,45 @@
<!-- Bandwidth info -->
+<style>
+.data-reduction-proxy-view-events-details {
+ background-color: rgb(220,220,220);
+}
+table.borderless-table,
+.borderless-table th,
+.borderless-table td {
+ border: 0px;
+ padding-left: 4px;
+ padding-right: 4px;
+}
+</style>
<div id=bandwidth-view-tab-content class=content-box>
- <table class="styled-table">
+ <h2>Data Reduction Proxy Status</h2>
+ <ul id=data-reduction-proxy-view-status>
+ <li>Status: <span id="data-reduction-proxy-enabled"></span></li>
+ <li>Proxy configuration: <span id="data-reduction-proxy-config"></span></li>
+ <li>Probe status: <span id="data-reduction-proxy-probe-status"></span></li>
+ <li id="data-reduction-proxy-bypass-state-container">Bypass details:
+ <span id="data-reduction-proxy-bypass-state-details"></span></li>
+ </ul>
+ <a href="#proxy">View current proxy configuration</a>
+
+ <h4>Recent events</h4>
+ <div id=data-reduction-proxy-view-events-content>
+ <table class="styled-table">
+ <thead>
+ <tr>
+ <th>Time</th>
+ <th>Action</th>
+ </tr>
+ <tr>
+ <th colspan=2>Details</th>
+ </tr>
+ </thead>
+ <tbody id=data-reduction-proxy-view-events-tbody>
+ </tbody>
+ </table>
+ </div>
+ <h4>Bandwidth Savings</h4>
+ <table class="styled-table" id="bandwidth-stats-table">
<thead>
<tr>
<th></th>
diff --git a/chromium/chrome/browser/resources/net_internals/bandwidth_view.js b/chromium/chrome/browser/resources/net_internals/bandwidth_view.js
index 9f0e4e61cb9..067151c22c0 100644
--- a/chromium/chrome/browser/resources/net_internals/bandwidth_view.js
+++ b/chromium/chrome/browser/resources/net_internals/bandwidth_view.js
@@ -21,6 +21,12 @@ var BandwidthView = (function() {
g_browser.addSessionNetworkStatsObserver(this, true);
g_browser.addHistoricNetworkStatsObserver(this, true);
+ // Register to receive data reduction proxy info.
+ g_browser.addDataReductionProxyInfoObserver(this, true);
+
+ // Register to receive bad proxy info.
+ g_browser.addBadProxiesObserver(this, true);
+
this.sessionNetworkStats_ = null;
this.historicNetworkStats_ = null;
}
@@ -31,6 +37,15 @@ var BandwidthView = (function() {
// IDs for special HTML elements in bandwidth_view.html
BandwidthView.MAIN_BOX_ID = 'bandwidth-view-tab-content';
+ BandwidthView.ENABLED_ID = 'data-reduction-proxy-enabled';
+ BandwidthView.PROXY_CONFIG_ID = 'data-reduction-proxy-config';
+ BandwidthView.PROBE_STATUS_ID = 'data-reduction-proxy-probe-status';
+ BandwidthView.BYPASS_STATE_CONTAINER_ID =
+ 'data-reduction-proxy-bypass-state-container';
+ BandwidthView.BYPASS_STATE_ID = 'data-reduction-proxy-bypass-state-details';
+ BandwidthView.EVENTS_TBODY_ID = 'data-reduction-proxy-view-events-tbody';
+ BandwidthView.EVENTS_UL = 'data-reduction-proxy-view-events-list';
+ BandwidthView.STATS_BOX_ID = 'bandwidth-stats-table';
cr.addSingletonGetter(BandwidthView);
@@ -38,10 +53,15 @@ var BandwidthView = (function() {
// Inherit the superclass's methods.
__proto__: superClass.prototype,
+ data_reduction_proxy_config_: null,
+ last_bypass_: null,
+ bad_proxy_config_: null,
+
onLoadLogFinish: function(data) {
- // Even though this information is included in log dumps, there's no real
- // reason to display it when debugging a loaded log file.
- return false;
+ return this.onBadProxiesChanged(data.badProxies) &&
+ this.onDataReductionProxyInfoChanged(data.dataReductionProxyInfo) &&
+ (this.onSessionNetworkStatsChanged(data.sessionNetworkStats) ||
+ this.onHistoricNetworkStatsChanged(data.historicNetworkStats));
},
/**
@@ -62,6 +82,72 @@ var BandwidthView = (function() {
},
/**
+ * Updates the UI based on receiving changes in information about the
+ * data reduction proxy summary.
+ */
+ onDataReductionProxyInfoChanged: function(info) {
+ $(BandwidthView.EVENTS_TBODY_ID).innerHTML = '';
+
+ if (!info)
+ return false;
+
+ if (info.enabled) {
+ $(BandwidthView.ENABLED_ID).innerText = 'Enabled';
+ $(BandwidthView.PROBE_STATUS_ID).innerText =
+ info.probe != null ? info.probe : 'N/A';
+ this.last_bypass_ = info.last_bypass;
+ this.data_reduction_proxy_config_ = info.proxy_config.params;
+ } else {
+ $(BandwidthView.ENABLED_ID).innerText = 'Disabled';
+ $(BandwidthView.PROBE_STATUS_ID).innerText = 'N/A';
+ this.data_reduction_proxy_config_ = null;
+ }
+
+ this.updateDataReductionProxyConfig_();
+
+ for (var eventIndex = info.events.length - 1; eventIndex >= 0;
+ --eventIndex) {
+ var event = info.events[eventIndex];
+ var headerRow = addNode($(BandwidthView.EVENTS_TBODY_ID), 'tr');
+ var detailsRow = addNode($(BandwidthView.EVENTS_TBODY_ID), 'tr');
+
+ var timeCell = addNode(headerRow, 'td');
+ var actionCell = addNode(headerRow, 'td');
+ var detailsCell = addNode(detailsRow, 'td');
+ detailsCell.colSpan = 2;
+ detailsCell.className = 'data-reduction-proxy-view-events-details';
+ var eventTime = timeutil.convertTimeTicksToDate(event.time);
+ timeutil.addNodeWithDate(timeCell, eventTime);
+ this.buildEventRow_(event, actionCell, detailsCell);
+ }
+
+ return true;
+ },
+
+ /**
+ * Updates the UI based on receiving changes in information about bad
+ * proxy servers.
+ */
+ onBadProxiesChanged: function(badProxies) {
+ if (!badProxies)
+ return false;
+
+ var newBadProxies = [];
+ if (badProxies.length == 0) {
+ this.last_bypass_ = null;
+ } else {
+ for (var i = 0; i < badProxies.length; ++i) {
+ var entry = badProxies[i];
+ newBadProxies[entry.proxy_uri] = entry.bad_until;
+ }
+ }
+ this.bad_proxy_config_ = newBadProxies;
+ this.updateDataReductionProxyConfig_();
+
+ return true;
+ },
+
+ /**
* Update the bandwidth usage table. Returns false on failure.
*/
updateBandwidthUsageTable_: function() {
@@ -103,8 +189,145 @@ var BandwidthView = (function() {
});
var input = new JsEvalContext({rows: rows});
- jstProcess(input, $(BandwidthView.MAIN_BOX_ID));
+ jstProcess(input, $(BandwidthView.STATS_BOX_ID));
return true;
+ },
+
+ /**
+ * Renders a Data Reduction Proxy event into the event tbody
+ */
+ buildEventRow_: function(event, actionCell, detailsCell) {
+ if (event.type == EventType.DATA_REDUCTION_PROXY_ENABLED &&
+ event.params.enabled == 0) {
+ addTextNode(actionCell, 'DISABLED');
+ } else {
+ var actionText =
+ EventTypeNames[event.type].replace('DATA_REDUCTION_PROXY_', '');
+ if (event.phase == EventPhase.PHASE_BEGIN ||
+ event.phase == EventPhase.PHASE_END) {
+ actionText = actionText + ' (' +
+ getKeyWithValue(EventPhase, event.phase)
+ .replace('PHASE_', '') + ')';
+ }
+
+ addTextNode(actionCell, actionText);
+ this.createEventTable_(event.params, detailsCell);
+ }
+ },
+
+ /**
+ * Updates the data reduction proxy summary block.
+ */
+ updateDataReductionProxyConfig_: function() {
+ $(BandwidthView.PROXY_CONFIG_ID).innerHTML = '';
+ $(BandwidthView.BYPASS_STATE_ID).innerHTML = '';
+ setNodeDisplay($(BandwidthView.BYPASS_STATE_CONTAINER_ID), false);
+
+ if (this.data_reduction_proxy_config_) {
+ var hasBypassedProxy = false;
+ var now = timeutil.getCurrentTimeTicks();
+
+ if (this.last_bypass_ &&
+ this.hasTimePassedLogTime_(+this.last_bypass_.params.expiration)) {
+ // Best effort on iterating the config to search for a bad proxy.
+ // A server could exist in a string member of
+ // data_reduction_proxy_config_ or within an array of servers in an
+ // array member of data_reduction_proxy_config_. As such, search
+ // through all string members and string arrays.
+ for (var key in this.data_reduction_proxy_config_) {
+ var value = this.data_reduction_proxy_config_[key];
+ if (typeof value == 'string') {
+ if (this.isMarkedAsBad_(value)) {
+ hasBypassedProxy = true;
+ break;
+ }
+ } else if (value instanceof Array) {
+ for (var index = 1; index < value.length; index++) {
+ if (this.isMarkedAsBad_(value[index])) {
+ hasBypassedProxy = true;
+ }
+ }
+
+ if (hasBypassedProxy) {
+ break;
+ }
+ }
+ }
+ }
+
+ if (hasBypassedProxy) {
+ this.createEventTable_(this.last_bypass_.params,
+ $(BandwidthView.BYPASS_STATE_ID));
+ }
+
+ this.createEventTable_(this.data_reduction_proxy_config_,
+ $(BandwidthView.PROXY_CONFIG_ID));
+ setNodeDisplay($(BandwidthView.BYPASS_STATE_CONTAINER_ID),
+ hasBypassedProxy);
+ }
+ },
+
+ /**
+ * Checks to see if a proxy server is in marked as bad.
+ */
+ isMarkedAsBad_: function(proxy) {
+ for (var entry in this.bad_proxy_config_) {
+ if (entry == proxy &&
+ this.hasTimePassedLogTime_(this.bad_proxy_config_[entry])) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ /**
+ * Checks to see if a given time in ticks has passed the time of the
+ * the log. For real time viewing, this is "now", but for loaded logs, it
+ * is the time at which the logs were taken.
+ */
+ hasTimePassedLogTime_: function(timeTicks) {
+ var logTime;
+ if (MainView.isViewingLoadedLog() && ClientInfo.numericDate) {
+ logTime = ClientInfo.numericDate;
+ } else {
+ logTime = timeutil.getCurrentTime();
+ }
+
+ return timeutil.convertTimeTicksToTime(timeTicks) > logTime;
+ },
+
+ /**
+ * Creates a table of the object obj. Certain keys are special cased for
+ * ease of readability.
+ */
+ createEventTable_: function(obj, parentNode) {
+ if (Object.keys(obj).length > 0) {
+ var tableNode = addNode(parentNode, 'table');
+ tableNode.className = 'borderless-table';
+ for (var key in obj) {
+ var value = obj[key];
+ if (value != null && value.toString() != '') {
+ if (key == 'net_error') {
+ if (value == 0) {
+ value = 'OK';
+ } else {
+ value = netErrorToString(value);
+ }
+ } else if (key == 'bypass_type') {
+ value = getKeyWithValue(DataReductionProxyBypassEventType, value);
+ } else if (key == 'bypass_action_type') {
+ value = getKeyWithValue(DataReductionProxyBypassActionType,
+ value);
+ } else if (key == 'expiration') {
+ value = timeutil.convertTimeTicksToDate(value);
+ }
+ var tableRow = addNode(tableNode, 'tr');
+ addNodeWithText(tableRow, 'td', key);
+ addNodeWithText(tableRow, 'td', value);
+ }
+ }
+ }
}
};
diff --git a/chromium/chrome/browser/resources/net_internals/browser_bridge.js b/chromium/chrome/browser/resources/net_internals/browser_bridge.js
index 09ce829b447..2b976a4cd48 100644
--- a/chromium/chrome/browser/resources/net_internals/browser_bridge.js
+++ b/chromium/chrome/browser/resources/net_internals/browser_bridge.js
@@ -52,6 +52,7 @@ var BrowserBridge = (function() {
this.addNetInfoPollableDataHelper('spdyAlternateProtocolMappings',
'onSpdyAlternateProtocolMappingsChanged');
this.addNetInfoPollableDataHelper('quicInfo', 'onQuicInfoChanged');
+ this.addNetInfoPollableDataHelper('sdchInfo', 'onSdchInfoChanged');
this.addNetInfoPollableDataHelper('httpCacheInfo',
'onHttpCacheInfoChanged');
@@ -73,11 +74,9 @@ var BrowserBridge = (function() {
this.pollableDataHelpers_.extensionInfo =
new PollableDataHelper('onExtensionInfoChanged',
this.sendGetExtensionInfo.bind(this));
- if (cr.isChromeOS) {
- this.pollableDataHelpers_.systemLog =
- new PollableDataHelper('onSystemLogChanged',
- this.getSystemLog.bind(this, 'syslog'));
- }
+ this.pollableDataHelpers_.dataReductionProxyInfo =
+ new PollableDataHelper('onDataReductionProxyInfoChanged',
+ this.sendGetDataReductionProxyInfo.bind(this));
// Setting this to true will cause messages from the browser to be ignored,
// and no messages will be sent to the browser, either. Intended for use
@@ -208,20 +207,16 @@ var BrowserBridge = (function() {
this.send('getExtensionInfo');
},
- enableIPv6: function() {
- this.send('enableIPv6');
- },
-
- setLogLevel: function(logLevel) {
- this.send('setLogLevel', ['' + logLevel]);
+ sendGetDataReductionProxyInfo: function() {
+ this.send('getDataReductionProxyInfo');
},
- refreshSystemLogs: function() {
- this.send('refreshSystemLogs');
+ enableIPv6: function() {
+ this.send('enableIPv6');
},
- getSystemLog: function(log_key, cellId) {
- this.send('getSystemLog', [log_key, cellId]);
+ setCaptureMode: function(captureMode) {
+ this.send('setCaptureMode', ['' + captureMode]);
},
importONCFile: function(fileContent, passcode) {
@@ -353,8 +348,9 @@ var BrowserBridge = (function() {
this.pollableDataHelpers_.extensionInfo.update(extensionInfo);
},
- getSystemLogCallback: function(systemLog) {
- this.pollableDataHelpers_.systemLog.update(systemLog);
+ receivedDataReductionProxyInfo: function(dataReductionProxyInfo) {
+ this.pollableDataHelpers_.dataReductionProxyInfo.update(
+ dataReductionProxyInfo);
},
//--------------------------------------------------------------------------
@@ -618,16 +614,25 @@ var BrowserBridge = (function() {
},
/**
- * Adds a listener of system log information. |observer| will be called
+ * Adds a listener of the data reduction proxy info. |observer| will be
+ * called back when data is received, through:
+ *
+ * observer.onDataReductionProxyInfoChanged(dataReductionProxyInfo)
+ */
+ addDataReductionProxyInfoObserver: function(observer, ignoreWhenUnchanged) {
+ this.pollableDataHelpers_.dataReductionProxyInfo.addObserver(
+ observer, ignoreWhenUnchanged);
+ },
+
+ /**
+ * Adds a listener of SDCH information. |observer| will be called
* back when data is received, through:
*
- * observer.onSystemLogChanged(systemLogInfo)
+ * observer.onSdchInfoChanged(sdchInfo)
*/
- addSystemLogObserver: function(observer, ignoreWhenUnchanged) {
- if (this.pollableDataHelpers_.systemLog) {
- this.pollableDataHelpers_.systemLog.addObserver(
- observer, ignoreWhenUnchanged);
- }
+ addSdchInfoObserver: function(observer, ignoreWhenUnchanged) {
+ this.pollableDataHelpers_.sdchInfo.addObserver(
+ observer, ignoreWhenUnchanged);
},
/**
diff --git a/chromium/chrome/browser/resources/net_internals/capture_view.js b/chromium/chrome/browser/resources/net_internals/capture_view.js
index 4b0a20f60b2..244dc1ced89 100644
--- a/chromium/chrome/browser/resources/net_internals/capture_view.js
+++ b/chromium/chrome/browser/resources/net_internals/capture_view.js
@@ -85,7 +85,7 @@ var CaptureView = (function() {
var byteLoggingCheckbox = $(CaptureView.BYTE_LOGGING_CHECKBOX_ID);
if (byteLoggingCheckbox.checked) {
- g_browser.setLogLevel(LogLevelType.LOG_ALL);
+ g_browser.setCaptureMode('IncludeSocketBytes');
// Once we enable byte logging, all bets are off on what gets captured.
// Have the export view warn that the "strip cookies" option is
@@ -97,7 +97,7 @@ var CaptureView = (function() {
// reload.
ExportView.getInstance().showPrivacyWarning();
} else {
- g_browser.setLogLevel(LogLevelType.LOG_ALL_BUT_BYTES);
+ g_browser.setCaptureMode('IncludeCookiesAndCredentials');
}
},
diff --git a/chromium/chrome/browser/resources/net_internals/chromeos_view.html b/chromium/chrome/browser/resources/net_internals/chromeos_view.html
index 8d221ed294e..5bce68eb52b 100644
--- a/chromium/chrome/browser/resources/net_internals/chromeos_view.html
+++ b/chromium/chrome/browser/resources/net_internals/chromeos_view.html
@@ -1,7 +1,9 @@
<div id="chromeos-view-tab-content" class=content-box>
<h4 style='margin-top:0'>Import ONC file</h4>
<div id="chromeos-view-import-div">
- <input type="file" id="chromeos-view-import-onc">
+ <label>Import ONC File
+ <input type="file" id="chromeos-view-import-onc">
+ </label>
</div>
<div id="chromeos-view-password-div" hidden>
This onc file appears to be encrypted. Please provide the decryption key:
diff --git a/chromium/chrome/browser/resources/net_internals/cros_log_analyzer_view.html b/chromium/chrome/browser/resources/net_internals/cros_log_analyzer_view.html
deleted file mode 100644
index 19c76ffe51b..00000000000
--- a/chromium/chrome/browser/resources/net_internals/cros_log_analyzer_view.html
+++ /dev/null
@@ -1,57 +0,0 @@
-<!-- Log Analyzer -->
-<div id="cros-log-analyzer-tab-content" class="content-box">
- <div id="cros-log-analyzer-header">
- Network Log
- </div>
- <div id='cros-log-analyzer-search-container'
- class='cros-log-analyzer-container'>
- <label>
- Search:
- <input type='text' id='cros-log-analyzer-search-input'>
- </label>
- <span id='cros-log-analyzer-save-btn'>Save</span>
- </div>
- <table id="cros-log-analyzer-log-header-table"
- class="cros-log-analyzer-container">
- <tr>
- <td class="cros-log-analyzer-td-level">Level</td>
- <td class="cros-log-analyzer-td-time">Time</td>
- <td class="cros-log-analyzer-td-pname">Process</td>
- <td class="cros-log-analyzer-td-pid">PID</td>
- <td class="cros-log-analyzer-td-description">Description</td>
- </tr>
- </table>
- <div id="cros-log-analyzer-log-content">
- <table id="cros-log-analyzer-log-table">
- </table>
- </div>
- <div id='cros-log-analyzer-marker-container'>
- </div>
- <div id='cros-log-analyzer-filter-container'
- class='cros-log-analyzer-container'>
- <div id='cros-log-analyzer-filter-pname'>
- </div>
- <div id='cros-log-analyzer-filter-level'>
- Level:
- <span class="cros-log-analyzer-filter-level-block">
- <input type='checkbox' id='checkbox-error' checked=true>
- <label for="checkbox-error">Error</label>
- </span>
- <span class="cros-log-analyzer-filter-level-block">
- <input type='checkbox' id='checkbox-warning' checked=true>
- <label for="checkbox-warning">Warning</label>
- </span>
- <span class="cros-log-analyzer-filter-level-block">
- <input type='checkbox' id='checkbox-info' checked=true>
- <label for="checkbox-info">Info</label>
- </span>
- <span class="cros-log-analyzer-filter-level-block">
- <input type='checkbox' id='checkbox-unknown' checked=true>
- <label for="checkbox-unknown">Unknown</label>
- </span>
- </div>
- </div>
- <div id="cros-log-analyzer-visualizer-container">
- <div id="cros-log-analyzer-visualizer-tracking-layer"></div>
- </div>
-</div>
diff --git a/chromium/chrome/browser/resources/net_internals/cros_log_entry.js b/chromium/chrome/browser/resources/net_internals/cros_log_entry.js
deleted file mode 100644
index 70822a060cf..00000000000
--- a/chromium/chrome/browser/resources/net_internals/cros_log_entry.js
+++ /dev/null
@@ -1,102 +0,0 @@
-// Copyright 2013 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.
-
-/**
- * This class stores information of one single entry of log
- */
-
-var CrosLogEntry = function() {
-
- /**
- * @constructor
- */
- function CrosLogEntry() {
- // The entry is visible by default
- this.visibility = true;
- }
-
- CrosLogEntry.prototype = {
- //------------------------------------------------------------------------
- // Log input text parser
- // Parses network log into tokens like time, name, pid
- // and description.
- //--------------------------------------------------------------------------
- tokenizeNetworkLog: function(NetworkLogEntry) {
- var tokens = NetworkLogEntry.split(' ');
- var timeTokens = tokens[0].split(/[\s|\:|\-|T|\.]/);
-
- // List of all parameters for Date Object
- var year = timeTokens[0];
- var month = timeTokens[1];
- var day = timeTokens[2];
- var hour = timeTokens[3];
- var minute = timeTokens[4];
- var second = timeTokens[5];
- var millisecond = (parseInt(timeTokens[6]) / 1000).toFixed(0);
- this.time = new Date(year, month, day, hour, minute,
- second, millisecond);
-
- // Parses for process name and ID.
- var process = tokens[2];
- if (hasProcessID(process)) {
- var processTokens = process.split(/[\[|\]]/);
- this.processName = processTokens[0];
- this.processID = processTokens[1];
- } else {
- this.processName = process.split(/\:/)[0];
- this.processID = 'Unknown';
- }
-
- // Gets level of the log: error|warning|info|unknown if failed.
- this.level = hasLevelInfo(tokens[3]);
-
- // Treats the rest of the entry as description.
- var descriptionStartPoint = NetworkLogEntry.indexOf(tokens[2]) +
- tokens[2].length;
- this.description = NetworkLogEntry.substr(descriptionStartPoint);
- },
-
- // Represents the Date object as a string.
- getTime: function() {
- return this.time.getMonth() + '/' + this.time.getDate() +
- ' ' + this.time.getHours() + ':' + this.time.getMinutes() +
- ':' + this.time.getSeconds() + ':' + this.time.getMilliseconds();
- }
- };
-
- /**
- * Helper function
- * Takes a token as input and searches for '['.
- * We assume if the token contains '[' it contains a process ID.
- *
- * @param {string} token A token from log
- * @return {boolean} true if '[' is found
- */
- var hasProcessID = function(token) {
- return token != undefined && token.indexOf('[') != -1;
- }
-
- /**
- * Helper function
- * Checks if the input token contains level information.
- *
- * @param {string} token A token from log
- * @return {string} Level found in the token
- */
- var hasLevelInfo = function(token) {
- if (token == undefined)
- return 'Unknown';
- if (token.toLowerCase().indexOf('err') != -1) {
- return 'Error';
- } else if (token.toLowerCase().indexOf('warn') != -1) {
- return 'Warning';
- } else if (token.toLowerCase().indexOf('info') != -1) {
- return 'Info';
- } else {
- return 'Unknown';
- }
- }
-
- return CrosLogEntry;
-}();
diff --git a/chromium/chrome/browser/resources/net_internals/cros_log_marker.js b/chromium/chrome/browser/resources/net_internals/cros_log_marker.js
deleted file mode 100644
index d67a00c8dfb..00000000000
--- a/chromium/chrome/browser/resources/net_internals/cros_log_marker.js
+++ /dev/null
@@ -1,395 +0,0 @@
-// Copyright 2013 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.
-/**
- * This class stores the filter queries as history and highlight the log text
- * to increase the readability of the log
- *
- * - Enable / Disable highlights
- * - Highlights text with multiple colors
- * - Resolve hghlight conficts (A text highlighted by multiple colors) so that
- * the latest added highlight always has highest priority to display.
- *
- */
-var CrosLogMarker = (function() {
- 'use strict';
-
- // Special classes (defined in log_visualizer_view.css)
- var LOG_MARKER_HIGHLIGHT_CLASS = 'cros-log-visualizer-marker-highlight';
- var LOG_MARKER_CONTAINER_ID = 'cros-log-visualizer-marker-container';
- var LOG_MARKER_HISTORY_ENTRY_CLASS =
- 'cros-log-visualizer-marker-history-entry';
- var LOG_MARKER_HISTORY_COLOR_TAG_CLASS =
- 'cros-log-visualizer-marker-history-color-tag';
-
- /**
- * Colors used for highlighting. (Current we support 6 colors)
- * TODO(shinfan): Add more supoorted colors.
- */
- var COLOR_USAGE_SET = {
- 'Crimson': false,
- 'DeepSkyBlue': false,
- 'DarkSeaGreen': false,
- 'GoldenRod': false,
- 'IndianRed': false,
- 'Orange': false
- };
- var COLOR_NUMBER = Object.keys(COLOR_USAGE_SET).length;
-
-
- /**
- * CrosHighlightTag represents a single highlight tag in text.
- */
- var CrosHighlightTag = (function() {
- /**
- * @constructor
- */
- function CrosHighlightTag(color, field, range, priority) {
- this.color = color;
- this.field = field;
- this.range = range;
- this.priority = priority;
- this.enabled = true;
- }
-
- return CrosHighlightTag;
- })();
-
- /**
- * @constructor
- * @param {CrosLogVisualizerView} logVisualizerView A reference to
- * CrosLogVisualizerView.
- */
- function CrosLogMarker(logVisualizerView) {
- this.container = $(LOG_MARKER_CONTAINER_ID);
- // Stores highlight objects for each entry.
- this.entryHighlights = [];
- // Stores all the filter queries.
- this.markHistory = {};
- // Object references from CrosLogVisualizerView.
- this.logEntries = logVisualizerView.logEntries;
- this.logVisualizerView = logVisualizerView;
- // Counts how many highlights are created.
- this.markCount = 0;
- for (var i = 0; i < this.logEntries.length; i++) {
- this.entryHighlights.push([]);
- }
- }
-
- CrosLogMarker.prototype = {
- /**
- * Saves the query to the mark history and highlights the text
- * based on the query.
- */
- addMarkHistory: function(query) {
- // Increases the counter
- this.markCount += 1;
-
- // Find an avaiable color.
- var color = this.pickColor();
- if (!color) {
- // If all colors are occupied.
- alert('You can only add at most ' + COLOR_NUMBER + 'markers.');
- return;
- }
-
- // Updates HTML elements.
- var historyEntry = addNode(this.container, 'div');
- historyEntry.className = LOG_MARKER_HISTORY_ENTRY_CLASS;
-
- // A color tag that indicats the color used.
- var colorTag = addNode(historyEntry, 'div');
- colorTag.className = LOG_MARKER_HISTORY_COLOR_TAG_CLASS;
- colorTag.style.background = color;
-
- // Displays the query text.
- var queryText = addNodeWithText(historyEntry, 'p', query);
- queryText.style.color = color;
-
- // Adds a button to remove the marker.
- var removeBtn = addNodeWithText(historyEntry, 'a', 'Remove');
- removeBtn.addEventListener(
- 'click', this.onRemoveBtnClicked_.bind(this, historyEntry, color));
-
- // A checkbox that lets user enable and disable the marker.
- var enableCheckbox = addNode(historyEntry, 'input');
- enableCheckbox.type = 'checkbox';
- enableCheckbox.checked = true;
- enableCheckbox.color = color;
- enableCheckbox.addEventListener('change',
- this.onEnableCheckboxChange_.bind(this, enableCheckbox), false);
-
- // Searches log text for matched patterns and highlights them.
- this.patternMatch(query, color);
- },
-
- /**
- * Search the text for matched strings
- */
- patternMatch: function(query, color) {
- var pattern = new RegExp(query, 'i');
- for (var i = 0; i < this.logEntries.length; i++) {
- var entry = this.logEntries[i];
- // Search description of each log entry
- // TODO(shinfan): Add more search fields
- var positions = this.findPositions(
- pattern, entry.description);
- for (var j = 0; j < positions.length; j++) {
- var pos = positions[j];
- this.mark(entry, pos, 'description', color);
- }
- this.sortHighlightsByStartPosition_(this.entryHighlights[i]);
- }
- },
-
- /**
- * Highlights the text.
- * @param {CrosLogEntry} entry The log entry to be highlighted
- * @param {int|Array} position [start, end]
- * @param {string} field The field of entry to be highlighted
- * @param {string} color color used for highlighting
- */
- mark: function(entry, position, field, color) {
- // Creates the highlight object
- var tag = new CrosHighlightTag(color, field, position, this.markCount);
- // Add the highlight into entryHighlights
- this.entryHighlights[entry.rowNum].push(tag);
- },
-
- /**
- * Find the highlight objects that covers the given position
- * @param {CrosHighlightTag|Array} highlights highlights of a log entry
- * @param {int} position The target index
- * @param {string} field The target field
- * @return {CrosHighlightTag|Array} Highlights that cover the position
- */
- getHighlight: function(highlights, index, field) {
- var res = [];
- for (var j = 0; j < highlights.length; j++) {
- var highlight = highlights[j];
- if (highlight.range[0] <= index &&
- highlight.range[1] > index &&
- highlight.field == field &&
- highlight.enabled) {
- res.push(highlight);
- }
- }
- /**
- * Sorts the result by priority so that the highlight with
- * highest priority comes first.
- */
- this.sortHighlightsByPriority_(res);
- return res;
- },
-
- /**
- * This function highlights the entry by going through the text from left
- * to right and searching for "key" positions.
- * A "key" position is a position that one (or more) highlight
- * starts or ends. We only care about "key" positions because this is where
- * the text highlight status changes.
- * At each key position, the function decides if the text between this
- * position and previous position need to be highlighted and resolves
- * highlight conflicts.
- *
- * @param {CrosLogEntry} entry The entry going to be highlighted.
- * @param {string} field The specified field of the entry.
- * @param {DOMElement} parent Parent node.
- */
- getHighlightedEntry: function(entry, field, parent) {
- var rowNum = entry.rowNum;
- // Get the original text content of the entry (without any highlights).
- var content = this.logEntries[rowNum][field];
- var index = 0;
- while (index < content.length) {
- var nextIndex = this.getNextIndex(
- this.entryHighlights[rowNum], index, field, content);
- // Searches for highlights that have the position in range.
- var highlights = this.getHighlight(
- this.entryHighlights[rowNum], index, field);
- var text = content.substr(index, nextIndex - index);
- if (highlights.length > 0) {
- // Always picks the highlight with highest priority.
- this.addSpan(text, highlights[0].color, parent);
- } else {
- addNodeWithText(parent, 'span', text);
- }
- index = nextIndex;
- }
- },
-
- /**
- * A helper function that is used by this.getHightlightedEntry
- * It returns the first index where a highlight begins or ends from
- * the given index.
- * @param {CrosHighlightTag|Array} highlights An array of highlights
- * of a log entry.
- * @param {int} index The start position.
- * @param {string} field The specified field of entry.
- * Other fields are ignored.
- * @param {string} content The text content of the log entry.
- * @return {int} The first index where a highlight begins or ends.
- */
- getNextIndex: function(highlights, index, field, content) {
- var minGap = Infinity;
- var res = -1;
- for (var i = 0; i < highlights.length; i++) {
- if (highlights[i].field != field || !highlights[i].enabled)
- continue;
- // Distance between current index and the start index of highlight.
- var gap1 = highlights[i].range[0] - index;
- // Distance between current index and the end index of highlight.
- var gap2 = highlights[i].range[1] - index;
- if (gap1 > 0 && gap1 < minGap) {
- minGap = gap1;
- res = highlights[i].range[0];
- }
- if (gap2 > 0 && gap2 < minGap) {
- minGap = gap2;
- res = highlights[i].range[1];
- }
- }
- // Returns |res| if found. Otherwise returns the end position of the text.
- return res > 0 ? res : content.length;
- },
-
- /**
- * A helper function that is used by this.getHightlightedEntry.
- * It adds the HTML label to the text.
- */
- addSpan: function(text, color, parent) {
- var span = addNodeWithText(parent, 'span', text);
- span.style.color = color;
- span.className = LOG_MARKER_HIGHLIGHT_CLASS;
- },
-
- /**
- * A helper function that is used by this.getHightlightedEntry.
- * It adds the HTML label to the text.
- */
- pickColor: function() {
- for (var color in COLOR_USAGE_SET) {
- if (!COLOR_USAGE_SET[color]) {
- COLOR_USAGE_SET[color] = true;
- return color;
- }
- }
- return false;
- },
-
- /**
- * A event handler that enables and disables the corresponding marker.
- * @private
- */
- onEnableCheckboxChange_: function(checkbox) {
- for (var i = 0; i < this.entryHighlights.length; i++) {
- for (var j = 0; j < this.entryHighlights[i].length; j++) {
- if (this.entryHighlights[i][j].color == checkbox.color) {
- this.entryHighlights[i][j].enabled = checkbox.checked;
- }
- }
- }
- this.refreshLogTable();
- },
-
- /**
- * A event handlier that removes the marker from history.
- * @private
- */
- onRemoveBtnClicked_: function(entry, color) {
- entry.parentNode.removeChild(entry);
- COLOR_USAGE_SET[color] = false;
- for (var i = 0; i < this.entryHighlights.length; i++) {
- var highlights = this.entryHighlights[i];
- while (true) {
- var index = this.findHighlightByColor_(highlights, color);
- if (index == -1)
- break;
- highlights.splice(index, 1);
- }
- }
- this.refreshLogTable();
- },
-
- /**
- * A helper function that returns the index of first highlight that
- * has the target color. Otherwise returns -1.
- * @private
- */
- findHighlightByColor_: function(highlights, color) {
- for (var i = 0; i < highlights.length; i++) {
- if (highlights[i].color == color)
- return i;
- }
- return -1;
- },
-
- /**
- * Refresh the log table in the CrosLogVisualizerView.
- */
- refreshLogTable: function() {
- this.logVisualizerView.populateTable();
- this.logVisualizerView.filterLog();
- },
-
- /**
- * A pattern can appear multiple times in a string.
- * Returns positions of all the appearance.
- */
- findPositions: function(pattern, str) {
- var res = [];
- str = str.toLowerCase();
- var match = str.match(pattern);
- if (!match)
- return res;
- for (var i = 0; i < match.length; i++) {
- var index = 0;
- while (true) {
- var start = str.indexOf(match[i].toLowerCase(), index);
- if (start == -1)
- break;
- var end = start + match[i].length;
- res.push([start, end]);
- index = end + 1;
- }
- }
- return res;
- },
-
- /**
- * A helper function used in sorting highlights by start position.
- * @param {HighlightTag} h1, h2 Two highlight tags in the array.
- * @private
- */
- compareStartPosition_: function(h1, h2) {
- return h1.range[0] - h2.range[0];
- },
-
- /**
- * A helper function used in sorting highlights by priority.
- * @param {HighlightTag} h1, h2 Two highlight tags in the array.
- * @private
- */
- comparePriority_: function(h1, h2) {
- return h2.priority - h1.priority;
- },
-
- /**
- * A helper function that sorts the highlights array by start position.
- * @private
- */
- sortHighlightsByStartPosition_: function(highlights) {
- highlights.sort(this.compareStartPosition_);
- },
-
- /**
- * A helper function that sorts the highlights array by priority.
- * @private
- */
- sortHighlightsByPriority_: function(highlights) {
- highlights.sort(this.comparePriority_);
- }
- };
-
- return CrosLogMarker;
-})();
diff --git a/chromium/chrome/browser/resources/net_internals/cros_log_visualizer.js b/chromium/chrome/browser/resources/net_internals/cros_log_visualizer.js
deleted file mode 100644
index 73db4c367c4..00000000000
--- a/chromium/chrome/browser/resources/net_internals/cros_log_visualizer.js
+++ /dev/null
@@ -1,376 +0,0 @@
-// Copyright 2013 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.
-
-/**
- * This visualizer displays the log in a timeline graph
- *
- * - Use HTML5 canvas
- * - Can zoom in result by select time range
- * - Display different levels of logs in different layers of canvases
- *
- */
-var CrosLogVisualizer = (function() {
- 'use strict';
-
- // HTML attributes of canvas
- var LOG_VISUALIZER_CANVAS_CLASS = 'cros-log-visualizer-visualizer-canvas';
- var LOG_VISUALIZER_CANVAS_WIDTH = 980;
- var LOG_VISUALIZER_CANVAS_HEIGHT = 100;
-
- // Special HTML classes
- var LOG_VISUALIZER_TIMELINE_ID = 'cros-log-visualizer-visualizer-timeline';
- var LOG_VISUALIZER_TIME_DISPLAY_CLASS =
- 'cros-log-visualizer-visualizer-time-display';
- var LOG_VISUALIZER_RESET_BTN_ID =
- 'cros-log-visualizer-visualizer-reset-btn';
- var LOG_VISUALIZER_TRACKING_LAYER_ID =
- 'cros-log-visualizer-visualizer-tracking-layer';
-
- /**
- * Event level list
- * This list is used for intialization of canvases. And the canvas
- * with lowest priority should be created first. Hence the list is
- * sorted in decreasing order.
- */
- var LOG_EVENT_LEVEL_PRIORITY_LIST = {
- 'Unknown': 4,
- 'Warning': 2,
- 'Info': 3,
- 'Error': 1
- };
-
- // Color mapping of different levels
- var LOG_EVENT_COLORS_LIST = {
- 'Error': '#FF99A3',
- 'Warning': '#FAE5C3',
- 'Info': '#C3E3FA',
- 'Unknown': 'gray'
- };
-
- /**
- * @constructor
- */
- function CrosLogVisualizer(logVisualizer, containerID) {
- /**
- * Pass the LogVisualizer in as a reference so the visualizer can
- * synchrous with the log filter.
- */
- this.logVisualizer = logVisualizer;
-
- // If the data is initialized
- this.dataIntialized = false;
- // Stores all the log entries as events
- this.events = [];
- // A front layer that handles control events
- this.trackingLayer = this.createTrackingLayer();
-
- // References to HTML elements
- this.container = document.getElementById(containerID);
- this.timeline = this.createTimeline();
- this.timeDisplay = this.createTimeDisplay();
- this.btnReset = this.createBtnReset();
- // Canvases
- this.canvases = {};
- for (var level in LOG_EVENT_LEVEL_PRIORITY_LIST) {
- this.canvases[level] = this.createCanvas();
- this.container.appendChild(this.canvases[level]);
- }
-
- // Append all the elements to the container
- this.container.appendChild(this.timeline);
- this.container.appendChild(this.timeDisplay);
- this.container.appendChild(this.trackingLayer);
- this.container.appendChild(this.btnReset);
-
- this.container.addEventListener('webkitAnimationEnd', function() {
- this.container.classList.remove('cros-log-visualizer-flash');
- }.bind(this), false);
- }
-
- CrosLogVisualizer.prototype = {
- /**
- * Called during the initialization of the View. Create a overlay
- * DIV on top of the canvas that handles the mouse events
- */
- createTrackingLayer: function() {
- var trackingLayer = document.createElement('div');
- trackingLayer.setAttribute('id', LOG_VISUALIZER_TRACKING_LAYER_ID);
- trackingLayer.addEventListener('mousemove', this.onHovered_.bind(this));
- trackingLayer.addEventListener('mousedown', this.onMouseDown_.bind(this));
- trackingLayer.addEventListener('mouseup', this.onMouseUp_.bind(this));
- return trackingLayer;
- },
-
- /**
- * This function is called during the initialization of the view.
- * It creates the timeline that moves along with the mouse on canvas.
- * When user click, a rectangle can be dragged out to select the range
- * to zoom.
- */
- createTimeline: function() {
- var timeline = document.createElement('div');
- timeline.setAttribute('id', LOG_VISUALIZER_TIMELINE_ID);
- timeline.style.height = LOG_VISUALIZER_CANVAS_HEIGHT + 'px';
- timeline.addEventListener('mousedown', function(event) { return false; });
- return timeline;
- },
-
- /**
- * This function is called during the initialization of the view.
- * It creates a time display that moves with the timeline
- */
- createTimeDisplay: function() {
- var timeDisplay = document.createElement('p');
- timeDisplay.className = LOG_VISUALIZER_TIME_DISPLAY_CLASS;
- timeDisplay.style.top = LOG_VISUALIZER_CANVAS_HEIGHT + 'px';
- return timeDisplay;
- },
-
- /**
- * Called during the initialization of the View. Create a button that
- * resets the canvas to initial status (without zoom)
- */
- createBtnReset: function() {
- var btnReset = document.createElement('input');
- btnReset.setAttribute('type', 'button');
- btnReset.setAttribute('value', 'Reset');
- btnReset.setAttribute('id', LOG_VISUALIZER_RESET_BTN_ID);
- btnReset.addEventListener('click', this.reset.bind(this));
- return btnReset;
- },
-
- /**
- * Called during the initialization of the View. Create a empty canvas
- * that visualizes log when the data is ready
- */
- createCanvas: function() {
- var canvas = document.createElement('canvas');
- canvas.width = LOG_VISUALIZER_CANVAS_WIDTH;
- canvas.height = LOG_VISUALIZER_CANVAS_HEIGHT;
- canvas.className = LOG_VISUALIZER_CANVAS_CLASS;
- return canvas;
- },
-
- /**
- * Returns the context of corresponding canvas based on level
- */
- getContext: function(level) {
- return this.canvases[level].getContext('2d');
- },
-
- /**
- * Erases everything from all the canvases
- */
- clearCanvas: function() {
- for (var level in LOG_EVENT_LEVEL_PRIORITY_LIST) {
- var ctx = this.getContext(level);
- ctx.clearRect(0, 0, LOG_VISUALIZER_CANVAS_WIDTH,
- LOG_VISUALIZER_CANVAS_HEIGHT);
- }
- },
-
- /**
- * Initializes the parameters needed for drawing:
- * - lower/upperBound: Time range (Events out of range will be skipped)
- * - totalDuration: The length of time range
- * - unitDuration: The unit time length per pixel
- */
- initialize: function() {
- if (this.events.length == 0)
- return;
- this.dragMode = false;
- this.dataIntialized = true;
- this.events.sort(this.compareTime);
- this.lowerBound = this.events[0].time;
- this.upperBound = this.events[this.events.length - 1].time;
- this.totalDuration = Math.abs(this.upperBound.getTime() -
- this.lowerBound.getTime());
- this.unitDuration = this.totalDuration / LOG_VISUALIZER_CANVAS_WIDTH;
- },
-
- /**
- * CSS3 fadeIn/fadeOut effects
- */
- flashEffect: function() {
- this.container.classList.add('cros-log-visualizer-flash');
- },
-
- /**
- * Reset the canvas to the initial time range
- * Redraw everything on the canvas
- * Fade in/out effects while redrawing
- */
- reset: function() {
- // Reset all the parameters as initial
- this.initialize();
- // Reset the visibility of the entries in the log table
- this.logVisualizer.filterLog();
- this.flashEffect();
- },
-
- /**
- * A wrapper function for drawing
- */
- drawEvents: function() {
- if (this.events.length == 0)
- return;
- for (var i in this.events) {
- this.drawEvent(this.events[i]);
- }
- },
-
- /**
- * The main function that handles drawing on the canvas.
- * Every event is represented as a vertical line.
- */
- drawEvent: function(event) {
- if (!event.visibility) {
- // Skip hidden events
- return;
- }
- var ctx = this.getContext(event.level);
- ctx.beginPath();
- // Get the x-coordinate of the line
- var startPosition = this.getPosition(event.time);
- if (startPosition != this.old) {
- this.old = startPosition;
- }
- ctx.rect(startPosition, 0, 2, LOG_VISUALIZER_CANVAS_HEIGHT);
- // Get the color of the line
- ctx.fillStyle = LOG_EVENT_COLORS_LIST[event.level];
- ctx.fill();
- ctx.closePath();
- },
-
- /**
- * This function is called every time the graph is zoomed.
- * It recalculates all the parameters based on the distance and direction
- * of dragging.
- */
- reCalculate: function() {
- if (this.dragDistance >= 0) {
- // if user drags to right
- this.upperBound = new Date((this.timelineLeft + this.dragDistance) *
- this.unitDuration + this.lowerBound.getTime());
- this.lowerBound = new Date(this.timelineLeft * this.unitDuration +
- this.lowerBound.getTime());
- } else {
- // if user drags to left
- this.upperBound = new Date(this.timelineLeft * this.unitDuration +
- this.lowerBound.getTime());
- this.lowerBound = new Date((this.timelineLeft + this.dragDistance) *
- this.unitDuration + this.lowerBound.getTime());
- }
- this.totalDuration = this.upperBound.getTime() -
- this.lowerBound.getTime();
- this.unitDuration = this.totalDuration / LOG_VISUALIZER_CANVAS_WIDTH;
- },
-
- /**
- * Check if the time of a event is out of bound
- */
- isOutOfBound: function(event) {
- return event.time.getTime() < this.lowerBound.getTime() ||
- event.time.getTime() > this.upperBound.getTime();
- },
-
- /**
- * This function returns the offset on x-coordinate of canvas based on
- * the time
- */
- getPosition: function(time) {
- return (time.getTime() - this.lowerBound.getTime()) / this.unitDuration;
- },
-
- /**
- * This function updates the events array and refresh the canvas.
- */
- updateEvents: function(newEvents) {
- this.events.length = 0;
- for (var i in newEvents) {
- this.events.push(newEvents[i]);
- }
- if (!this.dataIntialized) {
- this.initialize();
- }
- this.clearCanvas();
- this.drawEvents();
- },
-
- /**
- * This is a helper function that returns the time object based on the
- * offset of x-coordinate on the canvs.
- */
- getOffsetTime: function(offset) {
- return new Date(this.lowerBound.getTime() + offset * this.unitDuration);
- },
-
- /**
- * This function is triggered when the hovering event is detected
- * When the mouse is hovering we have two control mode:
- * - If it is in drag mode, we need to resize the width of the timeline
- * - If not, we need to move the timeline and time display to the
- * x-coordinate position of the mouse
- */
- onHovered_: function(event) {
- var offsetX = event.offsetX;
- if (this.lastOffsetX == offsetX) {
- // If the mouse does not move, we just skip the event
- return;
- }
-
- if (this.dragMode == true) {
- // If the mouse is in drag mode
- this.dragDistance = offsetX - this.timelineLeft;
- if (this.dragDistance >= 0) {
- // If the mouse is moving right
- this.timeline.style.width = this.dragDistance + 'px';
- } else {
- // If the mouse is moving left
- this.timeline.style.width = -this.dragDistance + 'px';
- this.timeline.style.left = offsetX + 'px';
- }
- } else {
- // If the mouse is not in drag mode we just move the timeline
- this.timeline.style.width = '2px';
- this.timeline.style.left = offsetX + 'px';
- }
-
- // update time display
- this.timeDisplay.style.left = offsetX + 'px';
- this.timeDisplay.textContent =
- this.getOffsetTime(offsetX).toTimeString().substr(0, 8);
- // update the last offset
- this.lastOffsetX = offsetX;
- },
-
- /**
- * This function is the handler for the onMouseDown event on the canvas
- */
- onMouseDown_: function(event) {
- // Enter drag mode which let user choose a time range to zoom in
- this.dragMode = true;
- this.timelineLeft = event.offsetX;
- // Create a duration display to indicate the duration of range.
- this.timeDurationDisplay = this.createTimeDisplay();
- this.container.appendChild(this.timeDurationDisplay);
- },
-
- /**
- * This function is the handler for the onMouseUp event on the canvas
- */
- onMouseUp_: function(event) {
- // Remove the duration display
- this.container.removeChild(this.timeDurationDisplay);
- // End the drag mode
- this.dragMode = false;
- // Recalculate the pamameter based on the range user select
- this.reCalculate();
- // Filter the log table and hide the entries that are not in the range
- this.logVisualizer.filterLog();
- },
- };
-
- return CrosLogVisualizer;
-})();
diff --git a/chromium/chrome/browser/resources/net_internals/cros_log_visualizer_view.css b/chromium/chrome/browser/resources/net_internals/cros_log_visualizer_view.css
deleted file mode 100644
index 8f49da37a8f..00000000000
--- a/chromium/chrome/browser/resources/net_internals/cros_log_visualizer_view.css
+++ /dev/null
@@ -1,234 +0,0 @@
- /* Copyright 2013 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.
- */
-
- #cros-log-visualizer-header {
- margin-bottom: 20px;
- }
-
-.cros-log-visualizer-container {
- background-color: rgb(242, 242, 242);
- border: 1px solid rgb(220, 220, 220);
-}
-
-#cros-log-visualizer-log-content {
- border: 1px solid rgb(220, 220, 220);
- font-size: 11px;
- height: 300px;
- line-height: 18px;
- margin-bottom: -1px;
- overflow: scroll;
- padding: 10px;
- width: 1000px;
- }
-
-#cros-log-visualizer-filter-container {
- font-size: 12px;
- margin-bottom: 5px;
- padding: 5px 10px;
- width: 1000px;
-}
-
-#cros-log-visualizer-filter-container p {
- margin: 10px 0;
-}
-
-#cros-log-visualizer-log-table {
- table-layout: fixed;
- width: 950px;
-}
-
-#cros-log-visualizer-log-table td {
- overflow: hidden;
- table-layout: fixed;
- word-wrap: break-word;
-}
-
-.cros-log-visualizer-td-time {
- width: 100px;
-}
-
-#cros-log-visualizer-log-header-table {
- font-size: 12px;
- font-weight: bold;
- margin-bottom: -1px;
- text-align: center;
- width: 1000px;
-}
-
-.cros-log-visualizer-td-pname {
- text-align: center;
- width: 120px;
-}
-
-.cros-log-visualizer-td-level {
- width: 70px;
-}
-
-.cros-log-visualizer-td-level p {
- border: 1px solid;
- border-radius: 5px;
- margin: 0 3px;
- text-align: center;
- width: 58px;
-}
-
-.cros-log-visualizer-td-level-error {
- background-color: rgb(255, 153, 163);
- color: rgb(238, 0, 0);
-}
-
-.cros-log-visualizer-td-level-info {
- background-color: rgb(195, 227, 250);
- color: rgb(10, 147, 245);
- width: 120px;
-}
-
-.cros-log-visualizer-td-level-warning {
- background-color: rgb(250, 229, 195);
- color: darkorange;
-}
-
-.cros-log-visualizer-td-level-unknown {
- color: gray;
-}
-
-.cros-log-visualizer-td-pid {
- width: 60px;
-}
-
-#cros-log-visualizer-filter-pname {
- border-bottom: 1px solid rgb(209, 209, 209);
- padding: 5px 0 10px 0;
-}
-
-#cros-log-visualizer-filter-level {
- padding: 5px 0 10px 0;
-}
-
-#cros-log-visualizer-search-container {
- -webkit-margin-start: 760px;
- border-radius: 5px;
- font-size: 12px;
- margin-bottom: 5px;
- margin-left: 720px;
- margin-top: -40px;
- padding: 5px;
- width: 280px;
-}
-
-#cros-log-visualizer-search-container input {
- border: 1px solid rgb(220, 220, 220);
-}
-
-#cros-log-visualizer-visualizer-container {
- border: 1px solid rgb(211, 211, 211);
- height: 100px;
- position: relative;
- width: 1002px;
-}
-
-#cros-log-visualizer-visualizer-timeline {
- background: gray;
- opacity: 0.5;
- position: absolute;
- top: 0;
- width: 2px;
-}
-
-.cros-log-visualizer-visualizer-time-display {
- font-size: 10px;
- position: absolute;
- width: 50px;
-}
-
-#cros-log-visualizer-visualizer-reset-btn {
- border: 1px solid rgb(211, 211, 211);
- border-radius: 2px;
- position: absolute;
- right: 5px;
- top: 5px;
-}
-
-#cros-log-visualizer-visualizer-tracking-layer {
- background: none;
- height: 100%;
- left: 0;
- position: absolute;
- top: 0;
- width: 100%;
-}
-
-.cros-log-visualizer-visualizer-canvas {
- left: 0;
- position: absolute;
- top: 0;
-}
-
-.cros-log-visualizer-flash {
- -webkit-animation: fade 1s linear 1;
-}
-
-#cros-log-visualizer-marker-container {
- background-color: rgb(242, 242, 242);
- border: 1px solid rgb(211, 211, 211);
- left: 1030px;
- min-height: 320px;
- padding: 10px;
- position: absolute;
- top: 63px;
- width: 200px;
-}
-
-.cros-log-visualizer-marker-history-entry {
- display: -webkit-flex;
- height: 20px;
-}
-
-
-.cros-log-visualizer-marker-history-entry * {
- display: block;
-}
-
-.cros-log-visualizer-marker-history-entry p {
- font-size: 13px;
- height: 15px;
- margin: 0;
- width: 98px;
-}
-
-.cros-log-visualizer-marker-history-entry a {
- color: grey;
- font-size: 11px;
-}
-
-.cros-log-visualizer-marker-history-color-tag {
- border-radius: 10px;
- height: 10px;
- margin: 4px;
- width: 10px;
-}
-
-.cros-log-visualizer-marker-highlight {
- font-weight: bold;
-}
-
-#cros-log-visualizer-save-btn {
- background-color: rgb(211, 211, 211);
-}
-
-@-webkit-keyframes fade {
- 0%
- {
- opacity: 1;
- }
- 50%
- {
- opacity: 0;
- }
- 100%
- {
- opacity: 1;
- }
-}
diff --git a/chromium/chrome/browser/resources/net_internals/cros_log_visualizer_view.html b/chromium/chrome/browser/resources/net_internals/cros_log_visualizer_view.html
deleted file mode 100644
index ee6aa32dff8..00000000000
--- a/chromium/chrome/browser/resources/net_internals/cros_log_visualizer_view.html
+++ /dev/null
@@ -1,56 +0,0 @@
-<!-- Log Visualizer -->
-<div id="cros-log-visualizer-tab-content" class="content-box">
- <div id="cros-log-visualizer-header">
- Network Log
- </div>
- <div id='cros-log-visualizer-search-container'
- class='cros-log-visualizer-container'>
- Search:
- <input type='text' placeholder="Enter your keyword"
- id='cros-log-visualizer-search-input'>
- <span id='cros-log-visualizer-save-btn'>Save</span>
- </div>
- <table id="cros-log-visualizer-log-header-table"
- class="cros-log-visualizer-container">
- <tr>
- <td class="cros-log-visualizer-td-level">Level</td>
- <td class="cros-log-visualizer-td-time">Time</td>
- <td class="cros-log-visualizer-td-pname">Process</td>
- <td class="cros-log-visualizer-td-pid">PID</td>
- <td class="cros-log-visualizer-td-description">Description</td>
- </tr>
- </table>
- <div id="cros-log-visualizer-log-content">
- <table id="cros-log-visualizer-log-table">
- </table>
- </div>
- <div id='cros-log-visualizer-marker-container'>
- </div>
- <div id='cros-log-visualizer-filter-container'
- class='cros-log-visualizer-container'>
- <div id='cros-log-visualizer-filter-pname'>
- </div>
- <div id='cros-log-visualizer-filter-level'>
- Level:
- <span class="cros-log-visualizer-filter-level-block">
- <input type='checkbox' id='checkbox-error' checked=true>
- <label for="checkbox-error">Error</label>
- </span>
- <span class="cros-log-visualizer-filter-level-block">
- <input type='checkbox' id='checkbox-warning' checked=true>
- <label for="checkbox-warning">Warning</label>
- </span>
- <span class="cros-log-visualizer-filter-level-block">
- <input type='checkbox' id='checkbox-info' checked=true>
- <label for="checkbox-info">Info</label>
- </span>
- <span class="cros-log-visualizer-filter-level-block">
- <input type='checkbox' id='checkbox-unknown' checked=true>
- <label for="checkbox-unknown">Unknown</label>
- </span>
- </div>
- </div>
- <div id="cros-log-visualizer-visualizer-container">
- <div id="cros-log-visualizer-visualizer-tracking-layer"></div>
- </div>
-</div>
diff --git a/chromium/chrome/browser/resources/net_internals/cros_log_visualizer_view.js b/chromium/chrome/browser/resources/net_internals/cros_log_visualizer_view.js
deleted file mode 100644
index 6a0bf4840f3..00000000000
--- a/chromium/chrome/browser/resources/net_internals/cros_log_visualizer_view.js
+++ /dev/null
@@ -1,374 +0,0 @@
-// Copyright 2013 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.
-
-/**
- * This view displays the log messages from various resources in an
- * interactive log visualizer
- *
- * - Filter checkboxes
- * - Filter text inputs
- * - Display the log by different sections: time|level|process|description
- *
- */
-var CrosLogVisualizerView = (function() {
- 'use strict';
-
- // Inherits from DivView.
- var superClass = DivView;
-
- // Special classes (defined in log_visualizer_view.css)
- var LOG_CONTAINER_CLASSNAME = 'cros-log-visualizer-container';
- var LOG_FILTER_PNAME_BLOCK_CLASSNAME =
- 'cros-log-visualizer-filter-pname-block';
- var LOG_CELL_HEADER_CLASSNAME = 'cros-log-visualizer-td-head';
- var LOG_CELL_TIME_CLASSNAME = 'cros-log-visualizer-td-time';
- var LOG_CELL_PNAME_CLASSNAME = 'cros-log-visualizer-td-pname';
- var LOG_CELL_PID_CLASSNAME = 'cros-log-visualizer-td-pid';
- var LOG_CELL_DESCRIPTION_CLASSNAME = 'cros-log-visualizer-td-description';
- var LOG_CELL_LEVEL_CLASSNAME = 'cros-log-visualizer-td-level';
- var LOG_CELL_LEVEL_CLASSNAME_LIST = {
- 'Error': 'cros-log-visualizer-td-level-error',
- 'Warning': 'cros-log-visualizer-td-level-warning',
- 'Info': 'cros-log-visualizer-td-level-info',
- 'Unknown': 'cros-log-visualizer-td-level-unknown'
- };
-
- /**
- * @constructor
- */
- function CrosLogVisualizerView() {
- assertFirstConstructorCall(CrosLogVisualizerView);
-
- // Call superclass's constructor.
- superClass.call(this, CrosLogVisualizerView.MAIN_BOX_ID);
-
- // Stores log entry objects
- this.logEntries = [];
- // Stores current search query
- this.currentQuery = '';
- // Stores raw text data of log
- this.logData = '';
- // Stores all the unique process names
- this.pNames = [];
- // References to special HTML elements in log_visualizer_view.html
- this.pNameCheckboxes = {};
- this.levelCheckboxes = {};
- this.tableEntries = [];
-
- this.initialize();
- }
-
- CrosLogVisualizerView.TAB_ID = 'tab-handle-cros-log-visualizer';
- CrosLogVisualizerView.TAB_NAME = 'Log Visualizer';
- CrosLogVisualizerView.TAB_HASH = '#visualizer';
-
- // IDs for special HTML elements in log_visualizer_view.html
- CrosLogVisualizerView.MAIN_BOX_ID = 'cros-log-visualizer-tab-content';
- CrosLogVisualizerView.LOG_TABLE_ID = 'cros-log-visualizer-log-table';
- CrosLogVisualizerView.LOG_FILTER_PNAME_ID =
- 'cros-log-visualizer-filter-pname';
- CrosLogVisualizerView.LOG_SEARCH_INPUT_ID =
- 'cros-log-visualizer-search-input';
- CrosLogVisualizerView.LOG_SEARCH_SAVE_BTN_ID = 'cros-log-visualizer-save-btn';
- CrosLogVisualizerView.LOG_VISUALIZER_CONTAINER_ID =
- 'cros-log-visualizer-visualizer-container';
-
- cr.addSingletonGetter(CrosLogVisualizerView);
-
- /**
- * Contains types of logs we are interested in
- */
- var LOGS_LIST = {
- 'NETWORK_LOG': 1,
- 'SYSTEM_LOG': 2
- };
-
- /**
- * Contains headers of the log table
- */
- var TABLE_HEADERS_LIST = ['Level', 'Time', 'Process', 'PID', 'Description'];
-
- CrosLogVisualizerView.prototype = {
- // Inherit the superclass's methods.
- __proto__: superClass.prototype,
-
- /**
- * Called during the initialization of the View. Adds the system log
- * listener into Browser_Bridge so that the system log can be retrieved.
- */
- initialize: function() {
- g_browser.addSystemLogObserver(this);
- $(CrosLogVisualizerView.LOG_SEARCH_INPUT_ID).addEventListener('keyup',
- this.onSearchQueryChange_.bind(this));
- $(CrosLogVisualizerView.LOG_SEARCH_SAVE_BTN_ID).addEventListener(
- 'click', this.onSaveBtnClicked_.bind(this));
- },
-
- /**
- * Called when the save button is clicked. Saves the current filter query
- * to the mark history. And highlights the matched text with colors.
- */
- onSaveBtnClicked_: function() {
- this.marker.addMarkHistory(this.currentQuery);
- // Clears the filter query
- $(CrosLogVisualizerView.LOG_SEARCH_INPUT_ID).value = '';
- this.currentQuery = '';
- // Refresh the table
- this.populateTable();
- this.filterLog();
- },
-
- onSearchQueryChange_: function() {
- var inputField = $(CrosLogVisualizerView.LOG_SEARCH_INPUT_ID);
- this.currentQuery = inputField.value;
- this.filterLog();
- },
-
- /**
- * Creates the log table where each row represents a entry of log.
- * This function is called if and only if the log is received from system
- * level.
- */
- populateTable: function() {
- var logTable = $(CrosLogVisualizerView.LOG_TABLE_ID);
- logTable.innerHTML = '';
- this.tableEntries.length = 0;
- // Create entries
- for (var i = 0; i < this.logEntries.length; i++) {
- this.logEntries[i].rowNum = i;
- var row = this.createTableRow(this.logEntries[i]);
- logTable.appendChild(row);
- }
- },
-
- /**
- * Creates the single row of the table where each row is a representation
- * of the logEntry object.
- */
- createTableRow: function(entry) {
- var row = document.createElement('tr');
- for (var i = 0; i < 5; i++) {
- // Creates rows
- addNode(row, 'td');
- }
- var cells = row.childNodes;
- // Level cell
- cells[0].className = LOG_CELL_LEVEL_CLASSNAME;
- var levelTag = addNodeWithText(cells[0], 'p', entry.level);
- levelTag.className = LOG_CELL_LEVEL_CLASSNAME_LIST[entry.level];
-
- // Time cell
- cells[1].className = LOG_CELL_TIME_CLASSNAME;
- cells[1].textContent = entry.getTime();
-
- // Process name cell
- cells[2].className = LOG_CELL_PNAME_CLASSNAME;
- this.marker.getHighlightedEntry(entry, 'processName', cells[2]);
-
- // Process ID cell
- cells[3].className = LOG_CELL_PID_CLASSNAME;
- this.marker.getHighlightedEntry(entry, 'processID', cells[3]);
-
- // Description cell
- cells[4].className = LOG_CELL_DESCRIPTION_CLASSNAME;
- this.marker.getHighlightedEntry(entry, 'description', cells[4]);
-
- // Add the row into this.tableEntries for future reference
- this.tableEntries.push(row);
- return row;
- },
-
- /**
- * Regenerates the table and filter.
- */
- refresh: function() {
- this.createFilter();
- this.createLogMaker();
- this.populateTable();
- this.createVisualizer();
- },
-
- /**
- * Uses the search query to match the pattern in different fields of entry.
- */
- patternMatch: function(entry, pattern) {
- return entry.processID.match(pattern) ||
- entry.processName.match(pattern) ||
- entry.level.match(pattern) ||
- entry.description.match(pattern);
- },
-
- /**
- * Filters the log to show/hide the rows in the table.
- * Each logEntry instance has a visibility property. This function
- * shows or hides the row only based on this property.
- */
- filterLog: function() {
- // Supports regular expression
- var pattern = new RegExp(this.currentQuery, 'i');
- for (var i = 0; i < this.logEntries.length; i++) {
- var entry = this.logEntries[i];
- // Filters the result by pname and level
- var pNameCheckbox = this.pNameCheckboxes[entry.processName];
- var levelCheckbox = this.levelCheckboxes[entry.level];
- entry.visibility = pNameCheckbox.checked && levelCheckbox.checked &&
- !this.visualizer.isOutOfBound(entry);
- if (this.currentQuery) {
- // If the search query is not empty, filter the result by query
- entry.visibility = entry.visibility &&
- this.patternMatch(entry, pattern);
- }
- // Changes style of HTML row based on the visibility of logEntry
- if (entry.visibility) {
- this.tableEntries[i].style.display = 'table-row';
- } else {
- this.tableEntries[i].style.display = 'none';
- }
- }
- this.filterVisualizer();
- },
-
- /**
- * Initializes filter tags and checkboxes. There are two types of filters:
- * Level and Process. Level filters are static that we have only 4 levels
- * in total but process filters are dynamically changing based on the log.
- * The filter layout looks like:
- * |-----------------------------------------------------------------|
- * | |
- * | Section of process filter |
- * | |
- * |-----------------------------------------------------------------|
- * | |
- * | Section of level filter |
- * | |
- * |-----------------------------------------------------------------|
- */
- createFilter: function() {
- this.createFilterByPName();
- this.levelCheckboxes = {
- 'Error': $('checkbox-error'),
- 'Warning': $('checkbox-warning'),
- 'Info': $('checkbox-info'),
- 'Unknown': $('checkbox-unknown')
- };
-
- for (var level in this.levelCheckboxes) {
- this.levelCheckboxes[level].addEventListener(
- 'change', this.onFilterChange_.bind(this));
- }
- },
-
- /**
- * Helper function of createFilter(). Create filter section of
- * process filters.
- */
- createFilterByPName: function() {
- var filterContainerDiv = $(CrosLogVisualizerView.LOG_FILTER_PNAME_ID);
- filterContainerDiv.innerHTML = 'Process: ';
- for (var i = 0; i < this.pNames.length; i++) {
- var pNameBlock = this.createPNameBlock(this.pNames[i]);
- filterContainerDiv.appendChild(pNameBlock);
- }
- },
-
- /**
- * Helper function of createFilterByPName(). Create a single filter block in
- * the section of process filters.
- */
- createPNameBlock: function(pName) {
- var block = document.createElement('span');
- block.className = LOG_FILTER_PNAME_BLOCK_CLASSNAME;
-
- var tag = document.createElement('label');
- var span = document.createElement('span');
- span.textContent = pName;
-
- var checkbox = document.createElement('input');
- checkbox.type = 'checkbox';
- checkbox.name = pName;
- checkbox.value = pName;
- checkbox.checked = true;
- checkbox.addEventListener('change', this.onFilterChange_.bind(this));
- this.pNameCheckboxes[pName] = checkbox;
-
- tag.appendChild(checkbox);
- tag.appendChild(span);
- block.appendChild(tag);
-
- return block;
- },
-
- /**
- * Click handler for filter checkboxes. Everytime a checkbox is clicked,
- * the visibility of related logEntries are changed.
- */
- onFilterChange_: function() {
- this.filterLog();
- },
-
- /**
- * Creates a visualizer that visualizes the logs as a timeline graph
- * during the initialization of the View.
- */
- createVisualizer: function() {
- this.visualizer = new CrosLogVisualizer(this,
- CrosLogVisualizerView.LOG_VISUALIZER_CONTAINER_ID);
- this.visualizer.updateEvents(this.logEntries);
- },
-
- /**
- * Sync the visibility of log entries with the visualizer.
- */
- filterVisualizer: function() {
- this.visualizer.updateEvents(this.logEntries);
- },
-
- /**
- * Called during the initialization. It creates the log marker that
- * highlights log text.
- */
- createLogMaker: function() {
- this.marker = new CrosLogMarker(this);
- },
-
- /**
- * Given a row text line of log, a logEntry instance is initialized and used
- * for parsing. After the text is parsed, we put the instance into
- * logEntries which is an array for storing. This function is called when
- * the data is received from Browser Bridge.
- */
- addLogEntry: function(logType, textEntry) {
- var newEntry = new CrosLogEntry();
- if (logType == LOGS_LIST.NETWORK_LOG) {
- newEntry.tokenizeNetworkLog(textEntry);
- } else {
- //TODO(shinfan): Add more if cases here
- }
- this.logEntries.push(newEntry);
-
- // Record pname
- var pName = newEntry.processName;
- if (this.pNames.indexOf(pName) == -1) {
- this.pNames.push(pName);
- }
- },
-
- /*
- * Asynchronous call back function from Browser Bridge.
- */
- onSystemLogChanged: function(callback) {
- if (callback.log == this.logData) return;
- this.logData = callback.log;
- // Clear the old array by setting length to zero
- this.logEntries.length = 0;
- var entries = callback.log.split('\n');
- for (var i = 1; i < entries.length; i++) {
- this.addLogEntry(LOGS_LIST.NETWORK_LOG, entries[i]);
- }
- this.refresh();
- }
- };
-
- return CrosLogVisualizerView;
-})();
diff --git a/chromium/chrome/browser/resources/net_internals/dns_view.html b/chromium/chrome/browser/resources/net_internals/dns_view.html
index 37c61a79557..69b94f745ff 100644
--- a/chromium/chrome/browser/resources/net_internals/dns_view.html
+++ b/chromium/chrome/browser/resources/net_internals/dns_view.html
@@ -1,7 +1,7 @@
<!-- Host resolver info -->
<div id=dns-view-tab-content class=content-box>
<ul style='margin-top: 0'>
- <li><a href='#events&q=type:HOST_RESOLVER_IMPL_REQUEST,HOST_RESOLVER_IMPL_JOB%20is:active'>View pending lookups</a></li>
+ <li><a href='#events&q=type:HOST_RESOLVER_IMPL_JOB%20is:active'>View pending lookups</a></li>
<li>Default address family: <span id=dns-view-default-family></span>
<span id=dns-view-ipv6-disabled class=warning-text style="display: none;">
(IPv6 disabled)
diff --git a/chromium/chrome/browser/resources/net_internals/dns_view.js b/chromium/chrome/browser/resources/net_internals/dns_view.js
index 806668f3232..ce507efa167 100644
--- a/chromium/chrome/browser/resources/net_internals/dns_view.js
+++ b/chromium/chrome/browser/resources/net_internals/dns_view.js
@@ -202,7 +202,7 @@ var DnsView = (function() {
* Takes a last of strings and adds them all to a DOM node, displaying them
* on separate lines.
* @param {DomNode} node The parent node.
- * @param {Array.<string>} list List of strings to add to the node.
+ * @param {Array<string>} list List of strings to add to the node.
*/
function addListToNode_(node, list) {
for (var i = 0; i < list.length; ++i)
diff --git a/chromium/chrome/browser/resources/net_internals/events_view.css b/chromium/chrome/browser/resources/net_internals/events_view.css
index 58a327ae431..80b4d65cc62 100644
--- a/chromium/chrome/browser/resources/net_internals/events_view.css
+++ b/chromium/chrome/browser/resources/net_internals/events_view.css
@@ -6,8 +6,6 @@
#events-view-filter-box {
background: #efefef;
border-bottom: 1px solid #aaa;
- font-family: sans-serif;
- font-size: 75%;
overflow: hidden;
padding: 5px;
white-space: nowrap;
@@ -42,7 +40,6 @@
#events-view-source-list-table td {
border-bottom: 1px solid #afafaf;
border-left: 1px solid #afafaf;
- font-size: 75%;
padding: 3px;
text-overflow: ellipsis;
white-space: nowrap;
diff --git a/chromium/chrome/browser/resources/net_internals/hsts_view.js b/chromium/chrome/browser/resources/net_internals/hsts_view.js
index 76e1dd1eab6..2310b9f7907 100644
--- a/chromium/chrome/browser/resources/net_internals/hsts_view.js
+++ b/chromium/chrome/browser/resources/net_internals/hsts_view.js
@@ -119,11 +119,14 @@ var HSTSView = (function() {
s.innerHTML = '<b>Found:</b><br/>';
var keys = [
- 'domain', 'static_upgrade_mode', 'static_sts_include_subdomains',
- 'static_pkp_include_subdomains', 'static_sts_observed',
- 'static_pkp_observed', 'static_spki_hashes', 'dynamic_upgrade_mode',
- 'dynamic_sts_include_subdomains', 'dynamic_pkp_include_subdomains',
- 'dynamic_sts_observed', 'dynamic_pkp_observed', 'dynamic_spki_hashes'
+ 'static_sts_domain', 'static_upgrade_mode',
+ 'static_sts_include_subdomains', 'static_sts_observed',
+ 'static_pkp_domain', 'static_pkp_include_subdomains',
+ 'static_pkp_observed', 'static_spki_hashes', 'dynamic_sts_domain',
+ 'dynamic_upgrade_mode', 'dynamic_sts_include_subdomains',
+ 'dynamic_sts_observed', 'dynamic_pkp_domain',
+ 'dynamic_pkp_include_subdomains', 'dynamic_pkp_observed',
+ 'dynamic_spki_hashes',
];
var kStaticHashKeys = [
diff --git a/chromium/chrome/browser/resources/net_internals/index.html b/chromium/chrome/browser/resources/net_internals/index.html
index db186f75ee0..5cf974e2e0e 100644
--- a/chromium/chrome/browser/resources/net_internals/index.html
+++ b/chromium/chrome/browser/resources/net_internals/index.html
@@ -1,5 +1,5 @@
<!DOCTYPE HTML>
-<html i18n-values=".style.fontFamily: fontfamily;">
+<html>
<!--
Copyright (c) 2012 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
@@ -10,14 +10,13 @@ found in the LICENSE file.
<!-- Don't use automatic scaling on mobile -->
<meta name="viewport" content="width=device-width, initial-scale=1.0,
maximum-scale=1.0, user-scalable=no">
+ <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="main.css">
<link rel="stylesheet" href="status_view.css">
<link rel="stylesheet" href="events_view.css">
<link rel="stylesheet" href="waterfall_view.css">
<link rel="stylesheet" href="timeline_view.css">
- <link rel="stylesheet" href="logs_view.css">
<link rel="stylesheet" href="chromeos_view.css">
- <link rel="stylesheet" href="cros_log_visualizer_view.css">
<script src="chrome://resources/js/util.js"></script>
<script src="chrome://resources/js/cr.js"></script>
<script src="chrome://resources/js/load_time_data.js"></script>
@@ -40,17 +39,15 @@ found in the LICENSE file.
<include src="import_view.html">
<include src="export_view.html">
<include src="capture_view.html">
- <include src="test_view.html">
<include src="hsts_view.html">
<include src="events_view.html">
<include src="waterfall_view.html">
<include src="timeline_view.html">
- <include src="logs_view.html">
+ <include src="sdch_view.html">
<include src="chromeos_view.html">
- <include src="cros_log_visualizer_view.html">
</div>
- <script src="chrome://resources/js/i18n_template2.js"></script>
+ <script src="chrome://resources/js/i18n_template.js"></script>
<script src="chrome://resources/js/jstemplate_compiled.js"></script>
</body>
</html>
diff --git a/chromium/chrome/browser/resources/net_internals/index.js b/chromium/chrome/browser/resources/net_internals/index.js
index 990a773bad1..55b9abf7a76 100644
--- a/chromium/chrome/browser/resources/net_internals/index.js
+++ b/chromium/chrome/browser/resources/net_internals/index.js
@@ -11,7 +11,6 @@
<include src="capture_view.js">
<include src="export_view.js">
<include src="http_cache_view.js">
-<include src="test_view.js">
<include src="hsts_view.js">
<include src="browser_bridge.js">
<include src="events_tracker.js">
@@ -45,14 +44,10 @@
<include src="sockets_view.js">
<include src="spdy_view.js">
<include src="modules_view.js">
-<include src="logs_view.js">
<include src="prerender_view.js">
<include src="chromeos_view.js">
<include src="bandwidth_view.js">
-<include src="cros_log_visualizer_view.js">
-<include src="cros_log_entry.js">
-<include src="cros_log_visualizer.js" >
-<include src="cros_log_marker.js" >
+<include src="sdch_view.js">
document.addEventListener('DOMContentLoaded', function() {
MainView.getInstance(); // from main.js
diff --git a/chromium/chrome/browser/resources/net_internals/log_util.js b/chromium/chrome/browser/resources/net_internals/log_util.js
index 7b36b26d95c..5438c648510 100644
--- a/chromium/chrome/browser/resources/net_internals/log_util.js
+++ b/chromium/chrome/browser/resources/net_internals/log_util.js
@@ -275,6 +275,7 @@ log_util = (function() {
// it's most likely a full log dump. Otherwise, it may be a dump created by
// --log-net-log.
var parsedDump = null;
+ var errorString = '';
try {
parsedDump = JSON.parse(logFileContents);
} catch (error) {
@@ -283,8 +284,10 @@ log_util = (function() {
// after the final good entry, and add the necessary close brackets.
var end = Math.max(logFileContents.lastIndexOf(',\n'),
logFileContents.lastIndexOf(',\r'));
- if (end != -1)
+ if (end != -1) {
parsedDump = JSON.parse(logFileContents.substring(0, end) + ']}');
+ errorString += 'Log file truncated. Events may be missing.\n';
+ }
}
catch (error2) {
}
@@ -292,7 +295,7 @@ log_util = (function() {
if (!parsedDump)
return 'Unable to parse log dump as JSON file.';
- return loadLogDump(parsedDump, fileName);
+ return errorString + loadLogDump(parsedDump, fileName);
}
// Exports.
diff --git a/chromium/chrome/browser/resources/net_internals/log_view_painter.js b/chromium/chrome/browser/resources/net_internals/log_view_painter.js
index 069a5a94815..05d85eb040f 100644
--- a/chromium/chrome/browser/resources/net_internals/log_view_painter.js
+++ b/chromium/chrome/browser/resources/net_internals/log_view_painter.js
@@ -87,7 +87,7 @@ createLogEntryTablePrinter = function(logEntries, privacyStripping,
}
return tablePrinter;
-}
+};
/**
* Adds a new row to the given TablePrinter, and adds five cells containing
@@ -273,6 +273,7 @@ function getParamaterWriterForEventType(eventType) {
switch (eventType) {
case EventType.HTTP_TRANSACTION_SEND_REQUEST_HEADERS:
case EventType.HTTP_TRANSACTION_SEND_TUNNEL_HEADERS:
+ case EventType.TYPE_HTTP_CACHE_CALLER_REQUEST_HEADERS:
return writeParamsForRequestHeaders;
case EventType.PROXY_CONFIG_CHANGED:
@@ -281,6 +282,8 @@ function getParamaterWriterForEventType(eventType) {
case EventType.CERT_VERIFIER_JOB:
case EventType.SSL_CERTIFICATES_RECEIVED:
return writeParamsForCertificates;
+ case EventType.EV_CERT_CT_COMPLIANCE_CHECKED:
+ return writeParamsForCheckedEVCertificates;
case EventType.SSL_VERSION_FALLBACK:
return writeParamsForSSLVersionFallback;
@@ -350,6 +353,12 @@ function defaultWriteParameter(key, value, out) {
return;
}
+ if (key == 'sdch_problem_code' && typeof value == 'number') {
+ var valueStr = value + ' (' + sdchProblemCodeToString(value) + ')';
+ out.writeArrowKeyValue(key, valueStr);
+ return;
+ }
+
// Otherwise just default to JSON formatting of the value.
out.writeArrowKeyValue(key, JSON.stringify(value));
}
@@ -558,7 +567,7 @@ stripCookiesAndLoginInfo = function(entry) {
entry.params.headers = entry.params.headers.map(stripCookieOrLoginInfo);
return entry;
-}
+};
/**
* Outputs the request header parameters of |entry| to |out|.
@@ -579,30 +588,28 @@ function writeParamsForRequestHeaders(entry, out, consumedParams) {
consumedParams.headers = true;
}
+function writeCertificateParam(
+ certs_container, out, consumedParams, paramName) {
+ if (certs_container.certificates instanceof Array) {
+ var certs = certs_container.certificates.reduce(
+ function(previous, current) {
+ return previous.concat(current.split('\n'));
+ }, new Array());
+ out.writeArrowKey(paramName);
+ out.writeSpaceIndentedLines(8, certs);
+ consumedParams[paramName] = true;
+ }
+}
+
/**
* Outputs the certificate parameters of |entry| to |out|.
*/
function writeParamsForCertificates(entry, out, consumedParams) {
- if (entry.params.certificates instanceof Array) {
- var certs = entry.params.certificates.reduce(function(previous, current) {
- return previous.concat(current.split('\n'));
- }, new Array());
- out.writeArrowKey('certificates');
- out.writeSpaceIndentedLines(8, certs);
- consumedParams.certificates = true;
- }
-
- if (typeof(entry.params.verified_cert) == 'object') {
- if (entry.params.verified_cert.certificates instanceof Array) {
- var certs = entry.params.verified_cert.certificates.reduce(
- function(previous, current) {
- return previous.concat(current.split('\n'));
- }, new Array());
- out.writeArrowKey('verified_cert');
- out.writeSpaceIndentedLines(8, certs);
- consumedParams.verified_cert = true;
- }
- }
+ writeCertificateParam(entry.params, out, consumedParams, 'certificates');
+
+ if (typeof(entry.params.verified_cert) == 'object')
+ writeCertificateParam(
+ entry.params.verified_cert, out, consumedParams, 'verified_cert');
if (typeof(entry.params.cert_status) == 'number') {
var valueStr = entry.params.cert_status + ' (' +
@@ -613,6 +620,12 @@ function writeParamsForCertificates(entry, out, consumedParams) {
}
+function writeParamsForCheckedEVCertificates(entry, out, consumedParams) {
+ if (typeof(entry.params.certificate) == 'object')
+ writeCertificateParam(
+ entry.params.certificate, out, consumedParams, 'certificate');
+}
+
/**
* Outputs the SSL version fallback parameters of |entry| to |out|.
*/
diff --git a/chromium/chrome/browser/resources/net_internals/logs_view.css b/chromium/chrome/browser/resources/net_internals/logs_view.css
deleted file mode 100644
index 4a1d3750b60..00000000000
--- a/chromium/chrome/browser/resources/net_internals/logs_view.css
+++ /dev/null
@@ -1,45 +0,0 @@
-/* Copyright (c) 2012 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.
- */
-
-.logs-view-log-cell-text,
-.logs-view-log-cell-log {
- background-color: white;
-}
-
-.logs-view-log-row-collapsed .logs-view-log-cell-log {
- display: none;
-}
-
-.logs-view-log-row-expanded .logs-view-log-cell-log {
- display: table-cell;
-}
-
-.logs-view-log-table-button-column {
- text-align: center;
-}
-
-.logs-view-log-button {
- width: 85px;
-}
-
-.logs-view-global-button {
- float: left;
- margin: 5px;
- margin-top: 0;
- width: 135px;
-}
-
-#logs-view-log-table td {
- font-family: 'Courier New', monospace;
- font-size: 12px;
- line-height: 17px;
- padding: 3px 5px 0;
- vertical-align: top;
- white-space: pre;
-}
-
-#logs-view-log-table-header-row th {
- text-align: left;
-}
diff --git a/chromium/chrome/browser/resources/net_internals/logs_view.html b/chromium/chrome/browser/resources/net_internals/logs_view.html
deleted file mode 100644
index fdae5e23efa..00000000000
--- a/chromium/chrome/browser/resources/net_internals/logs_view.html
+++ /dev/null
@@ -1,19 +0,0 @@
-<!-- ============================ Logs View ============================ -->
-
-<div id=logs-view-tab-content style="display: none;" class=content-box>
- <button id=logs-view-global-show-btn class=logs-view-global-button>Show all...</button>
- <button id=logs-view-global-hide-btn class=logs-view-global-button>Hide all...</button>
- <button id=logs-view-refresh-btn class=logs-view-global-button>Refresh logs...</button>
- <div style="clear: both"></div>
- <table width=100% class=styled-table>
- <thead>
- <tr id=logs-view-log-table-header-row>
- <th width=10%>Log Name</th>
- <th width=8%></th>
- <th width=82%>Log</th>
- </tr>
- </thead>
- <tbody id=logs-view-log-table>
- </tbody>
- </table>
-</div>
diff --git a/chromium/chrome/browser/resources/net_internals/logs_view.js b/chromium/chrome/browser/resources/net_internals/logs_view.js
deleted file mode 100644
index 3c73150f263..00000000000
--- a/chromium/chrome/browser/resources/net_internals/logs_view.js
+++ /dev/null
@@ -1,215 +0,0 @@
-// Copyright (c) 2012 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.
-
-/**
- * This view displays network related log data and is specific fo ChromeOS.
- * We get log data from chrome by filtering system logs for network related
- * keywords. Logs are not fetched until we actually need them.
- */
-var LogsView = (function() {
- 'use strict';
-
- // Special classes (defined in logs_view.css).
- var LOG_ROW_COLLAPSED_CLASSNAME = 'logs-view-log-row-collapsed';
- var LOG_ROW_EXPANDED_CLASSNAME = 'logs-view-log-row-expanded';
- var LOG_CELL_TEXT_CLASSNAME = 'logs-view-log-cell-text';
- var LOG_CELL_LOG_CLASSNAME = 'logs-view-log-cell-log';
- var LOG_TABLE_BUTTON_COLUMN_CLASSNAME = 'logs-view-log-table-button-column';
- var LOG_BUTTON_CLASSNAME = 'logs-view-log-button';
-
- // We inherit from DivView.
- var superClass = DivView;
-
- /**
- * @constructor
- */
- function LogsView() {
- assertFirstConstructorCall(LogsView);
-
- // Call superclass's constructor.
- superClass.call(this, LogsView.MAIN_BOX_ID);
-
- var tableDiv = $(LogsView.TABLE_ID);
- this.rows = [];
- this.populateTable(tableDiv, LOG_FILTER_LIST);
- $(LogsView.GLOBAL_SHOW_BUTTON_ID).addEventListener('click',
- this.onGlobalChangeVisibleClick_.bind(this, true));
- $(LogsView.GLOBAL_HIDE_BUTTON_ID).addEventListener('click',
- this.onGlobalChangeVisibleClick_.bind(this, false));
- $(LogsView.REFRESH_LOGS_BUTTON_ID).addEventListener('click',
- this.onLogsRefresh_.bind(this));
- }
-
- LogsView.TAB_ID = 'tab-handle-logs';
- LogsView.TAB_NAME = 'Logs';
- LogsView.TAB_HASH = '#logs';
-
- // IDs for special HTML elements in logs_view.html
- LogsView.MAIN_BOX_ID = 'logs-view-tab-content';
- LogsView.TABLE_ID = 'logs-view-log-table';
- LogsView.GLOBAL_SHOW_BUTTON_ID = 'logs-view-global-show-btn';
- LogsView.GLOBAL_HIDE_BUTTON_ID = 'logs-view-global-hide-btn';
- LogsView.REFRESH_LOGS_BUTTON_ID = 'logs-view-refresh-btn';
-
- cr.addSingletonGetter(LogsView);
-
- /**
- * Contains log keys we are interested in.
- */
- var LOG_FILTER_LIST = [
- {
- key: 'syslog',
- },
- {
- key: 'ui_log',
- },
- {
- key: 'chrome_system_log',
- },
- {
- key: 'chrome_log',
- }
- ];
-
- LogsView.prototype = {
- // Inherit the superclass's methods.
- __proto__: superClass.prototype,
-
- /**
- * Called during View's initialization. Creates the row of a table logs will
- * be shown in. Each row has 4 cells.
- *
- * First cell's content will be set to |logKey|, second will contain a
- * button that will be used to show or hide third cell, which will contain
- * the filtered log.
- * |logKey| also tells us which log we are getting data from.
- */
- createTableRow: function(logKey) {
- var row = document.createElement('tr');
-
- var cells = [];
- for (var i = 0; i < 3; i++) {
- var rowCell = document.createElement('td');
- cells.push(rowCell);
- row.appendChild(rowCell);
- }
- // Log key cell.
- cells[0].className = LOG_CELL_TEXT_CLASSNAME;
- cells[0].textContent = logKey;
- // Cell log is displayed in. Log content is in div element that is
- // initially hidden and empty.
- cells[2].className = LOG_CELL_TEXT_CLASSNAME;
- var logDiv = document.createElement('div');
- logDiv.textContent = '';
- logDiv.className = LOG_CELL_LOG_CLASSNAME;
- logDiv.id = 'logs-view.log-cell.' + this.rows.length;
- cells[2].appendChild(logDiv);
-
- // Button that we use to show or hide div element with log content. Logs
- // are not visible initially, so we initialize button accordingly.
- var expandButton = document.createElement('button');
- expandButton.textContent = 'Show...';
- expandButton.className = LOG_BUTTON_CLASSNAME;
- expandButton.addEventListener('click',
- this.onButtonClicked_.bind(this, row));
-
- // Cell that contains show/hide button.
- cells[1].appendChild(expandButton);
- cells[1].className = LOG_TABLE_BUTTON_COLUMN_CLASSNAME;
-
- // Initially, log is not visible.
- row.className = LOG_ROW_COLLAPSED_CLASSNAME;
-
- // We will need those to process row buttons' onclick events.
- row.logKey = logKey;
- row.expandButton = expandButton;
- row.logDiv = logDiv;
- row.logVisible = false;
- this.rows.push(row);
-
- return row;
- },
-
- /**
- * Initializes |tableDiv| to represent data from |logList| which should be
- * of type LOG_FILTER_LIST.
- */
- populateTable: function(tableDiv, logList) {
- for (var i = 0; i < logList.length; i++) {
- var logSource = this.createTableRow(logList[i].key);
- tableDiv.appendChild(logSource);
- }
- },
-
- /**
- * Processes clicks on buttons that show or hide log contents in log row.
- * Row containing the clicked button is given to the method since it
- * contains all data we need to process the click (unlike button object
- * itself).
- */
- onButtonClicked_: function(containingRow) {
- if (!containingRow.logVisible) {
- containingRow.className = LOG_ROW_EXPANDED_CLASSNAME;
- containingRow.expandButton.textContent = 'Hide...';
- var logDiv = containingRow.logDiv;
- if (logDiv.textContent == '') {
- logDiv.textContent = 'Getting logs...';
- // Callback will be executed by g_browser.
- g_browser.getSystemLog(containingRow.logKey,
- containingRow.logDiv.id);
- }
- } else {
- containingRow.className = LOG_ROW_COLLAPSED_CLASSNAME;
- containingRow.expandButton.textContent = 'Show...';
- }
- containingRow.logVisible = !containingRow.logVisible;
- },
-
- /**
- * Processes click on one of the buttons that are used to show or hide all
- * logs we care about.
- */
- onGlobalChangeVisibleClick_: function(isShowAll) {
- for (var row in this.rows) {
- if (isShowAll != this.rows[row].logVisible) {
- this.onButtonClicked_(this.rows[row]);
- }
- }
- },
-
- /**
- * Processes click event on the button we use to refresh fetched logs. We
- * get the newest logs from libcros, and refresh content of the visible log
- * cells.
- */
- onLogsRefresh_: function() {
- g_browser.refreshSystemLogs();
-
- var visibleLogRows = [];
- var hiddenLogRows = [];
- for (var row in this.rows) {
- if (this.rows[row].logVisible) {
- visibleLogRows.push(this.rows[row]);
- } else {
- hiddenLogRows.push(this.rows[row]);
- }
- }
-
- // We have to refresh text content in visible rows.
- for (row in visibleLogRows) {
- visibleLogRows[row].logDiv.textContent = 'Getting logs...';
- g_browser.getSystemLog(visibleLogRows[row].logKey,
- visibleLogRows[row].logDiv.id);
- }
-
- // In hidden rows we just clear potential log text, so we know we have to
- // get new contents when we show the row next time.
- for (row in hiddenLogRows) {
- hiddenLogRows[row].logDiv.textContent = '';
- }
- }
- };
-
- return LogsView;
-})();
diff --git a/chromium/chrome/browser/resources/net_internals/main.css b/chromium/chrome/browser/resources/net_internals/main.css
index 822e40f957b..5f80612a418 100644
--- a/chromium/chrome/browser/resources/net_internals/main.css
+++ b/chromium/chrome/browser/resources/net_internals/main.css
@@ -13,8 +13,10 @@ ul {
padding-left: 2em;
}
-/* This class is used to create the splitter widget in
- ResizbleVerticalSplitView */
+/**
+ * This class is used to create the splitter widget in
+ * ResizbleVerticalSplitView.
+ */
.vertical-splitter {
-webkit-user-select: none;
border-left: 1px solid #afafaf;
@@ -32,16 +34,11 @@ ul {
margin: 5px;
}
-.log-source-entry * p {
- font-size: 75%;
- font-weight: bold;
+pre {
+ font-size: 133%;
}
-.log-source-entry * td {
- font-size: 62%;
-}
-
-/*
+/**
* This class should be given to top-level content boxes (like the view's main
* DIV). It gives them a consistent padding, and makes them scrollable.
*/
@@ -60,7 +57,7 @@ ul {
margin-left: -10px;
}
-/*
+/**
* Styles for TABLE that uses a thin collapsed border.
*/
table.styled-table {
@@ -112,7 +109,6 @@ table.styled-table,
.mouse-over-help {
background: #EEE;
border: 1px solid black;
- font-size: 80%;
padding: 5px;
z-index: 1;
}
diff --git a/chromium/chrome/browser/resources/net_internals/main.js b/chromium/chrome/browser/resources/net_internals/main.js
index 05e9e7b9bac..7c5d6551b5f 100644
--- a/chromium/chrome/browser/resources/net_internals/main.js
+++ b/chromium/chrome/browser/resources/net_internals/main.js
@@ -12,7 +12,6 @@ var EventTypeNames = null;
var EventPhase = null;
var EventSourceType = null;
var EventSourceTypeNames = null;
-var LogLevelType = null;
var ClientInfo = null;
var NetError = null;
var QuicError = null;
@@ -21,6 +20,8 @@ var LoadFlag = null;
var CertStatusFlag = null;
var LoadState = null;
var AddressFamily = null;
+var SdchProblemCode = null;
+var DataReductionProxyBypassEventType = null;
/**
* Dictionary of all constants, used for saving log files.
@@ -192,20 +193,15 @@ var MainView = (function() {
addTab(SocketsView);
addTab(SpdyView);
addTab(QuicView);
+ addTab(SdchView);
addTab(HttpCacheView);
addTab(ModulesView);
- addTab(TestView);
- addTab(CrosLogVisualizerView);
addTab(HSTSView);
- addTab(LogsView);
addTab(BandwidthView);
addTab(PrerenderView);
addTab(CrosView);
- this.tabSwitcher_.showMenuItem(LogsView.TAB_ID, cr.isChromeOS);
this.tabSwitcher_.showMenuItem(CrosView.TAB_ID, cr.isChromeOS);
- this.tabSwitcher_.showMenuItem(CrosLogVisualizerView.TAB_ID,
- cr.isChromeOS);
},
/**
@@ -303,7 +299,6 @@ ConstantsObserver.prototype.onReceivedConstants = function(receivedConstants) {
EventPhase = Constants.logEventPhase;
EventSourceType = Constants.logSourceType;
EventSourceTypeNames = makeInverseMap(EventSourceType);
- LogLevelType = Constants.logLevelType;
ClientInfo = Constants.clientInfo;
LoadFlag = Constants.loadFlag;
NetError = Constants.netError;
@@ -311,6 +306,11 @@ ConstantsObserver.prototype.onReceivedConstants = function(receivedConstants) {
QuicRstStreamError = Constants.quicRstStreamError;
AddressFamily = Constants.addressFamily;
LoadState = Constants.loadState;
+ SdchProblemCode = Constants.sdchProblemCode;
+ DataReductionProxyBypassEventType =
+ Constants.dataReductionProxyBypassEventType;
+ DataReductionProxyBypassActionType =
+ Constants.dataReductionProxyBypassActionType;
// certStatusFlag may not be present when loading old log Files
if (typeof(Constants.certStatusFlag) == 'object')
CertStatusFlag = Constants.certStatusFlag;
@@ -331,7 +331,6 @@ function areValidConstants(receivedConstants) {
typeof(receivedConstants.clientInfo) == 'object' &&
typeof(receivedConstants.logEventPhase) == 'object' &&
typeof(receivedConstants.logSourceType) == 'object' &&
- typeof(receivedConstants.logLevelType) == 'object' &&
typeof(receivedConstants.loadFlag) == 'object' &&
typeof(receivedConstants.netError) == 'object' &&
typeof(receivedConstants.addressFamily) == 'object' &&
@@ -386,3 +385,15 @@ function addressFamilyToString(family) {
// Strip that prefix since it is redundant and only clutters the output.
return str.replace(/^ADDRESS_FAMILY_/, '');
}
+
+/**
+ * Returns the name for sdchProblemCode.
+ *
+ * Example: sdchProblemCodeToString(5) should return
+ * "DECODE_BODY_ERROR".
+ * @param {number} sdchProblemCode The SDCH problem code.
+ * @return {string} The name of the given problem code.
+ */
+function sdchProblemCodeToString(sdchProblemCode) {
+ return getKeyWithValue(SdchProblemCode, sdchProblemCode);
+}
diff --git a/chromium/chrome/browser/resources/net_internals/quic_view.html b/chromium/chrome/browser/resources/net_internals/quic_view.html
index f4d5d4fa505..d90ebee15ee 100644
--- a/chromium/chrome/browser/resources/net_internals/quic_view.html
+++ b/chromium/chrome/browser/resources/net_internals/quic_view.html
@@ -1,7 +1,12 @@
<div id=quic-view-tab-content class=content-box>
<ul style='margin-top:0'>
<li>QUIC Enabled: <span jscontent="!!quic_enabled"></span></li>
- <li>Alternate Protocol Probability Threshold: <span jscontent="alternate_protocol_probability_threshold"></span></li>
+ <!-- "alternative_service_probability_threshold" is used since release 44,
+ see https://crrev.com/1091283007.
+ "alternate_protocol_probability_threshold" is here to support importing
+ netlog json files from earlier browsers.
+ TODO(bnc): Deprecate around 2016 January. --!>
+ <li>Alternative Service Probability Threshold: <span jscontent="$this.alternative_service_probability_threshold || $this.alternate_protocol_probability_threshold"></span></li>
<li>Origin To Force QUIC On: <span jscontent="origin_to_force_quic_on"></span></li>
<li>QUIC connection options: <span jscontent="connection_options"></span></li>
<li>Consistent Port Selection Enabled: <span jscontent="!!enable_quic_port_selection"></span></li>
diff --git a/chromium/chrome/browser/resources/net_internals/sdch_view.html b/chromium/chrome/browser/resources/net_internals/sdch_view.html
new file mode 100644
index 00000000000..2cbfca5292a
--- /dev/null
+++ b/chromium/chrome/browser/resources/net_internals/sdch_view.html
@@ -0,0 +1,73 @@
+<div id=sdch-view-tab-content class=content-box>
+ <ul style="margin-top:0">
+ <li>SDCH Enabled:
+ <span jscontent="!!sdch_enabled" id=sdch-view-sdch-enabled></span>
+ </li>
+ <li>Secure Scheme Support Enabled:
+ <span jscontent="!!secure_scheme_support"
+ id=sdch-view-secure-scheme-support></span>
+ </li>
+ </ul>
+
+ <p>SDCH Errors:
+ <a href="#events&q=type:URL_REQUEST%20SDCH_DECODING_ERROR"
+ style="padding-right:2em">Decoding</a>
+ <a href="#events&q=type:URL_REQUEST%20SDCH_DICTIONARY_ERROR">Dictionary</a>
+ </p>
+
+ <p>
+ <a href="#events&q=type:URL_REQUEST%20SDCH_DICTIONARY_FETCH">
+ SDCH Dictionary Fetches
+ </a>
+ </p>
+
+ <p>
+ <a href="#events&q=type:URL_REQUEST%20Avail-Dictionary">
+ SDCH Requests
+ </a>
+ </p>
+
+ <h4>
+ Dictionaries loaded: <span jscontent="dictionaries.length"></span>
+ </h4>
+ <table class="styled-table">
+ <thead>
+ <tr>
+ <th>Domain</th>
+ <th>Path</th>
+ <th>Ports</th>
+ <th>Server Hash</th>
+ <th>Client Hash</th>
+ <th>Url</th>
+ </tr>
+ </thead>
+ <tbody id=sdch-view-dictionaries-body>
+ <tr jsselect="dictionaries">
+ <td jscontent="domain"></td>
+ <td jscontent="path"></td>
+ <td jscontent="$this.ports ? $this.ports.join(', ') : ''"></td>
+ <td jscontent="server_hash"></td>
+ <td jscontent="client_hash"></td>
+ <td jscontent="url"></td>
+ </tr>
+ </tbody>
+ </table>
+
+ <h4>Blacklisted domains</h4>
+ <table class="styled-table">
+ <thead>
+ <tr>
+ <th>Domain</th>
+ <th>Reason</th>
+ <th>Tries to back off</th>
+ </tr>
+ </thead>
+ <tbody id=sdch-view-blacklist-body>
+ <tr jsselect="blacklisted">
+ <td jscontent="domain"></td>
+ <td jscontent="sdchProblemCodeToString(reason)"></td>
+ <td jscontent="tries"></td>
+ </tr>
+ </tbody>
+ </table>
+</div>
diff --git a/chromium/chrome/browser/resources/net_internals/sdch_view.js b/chromium/chrome/browser/resources/net_internals/sdch_view.js
new file mode 100644
index 00000000000..f42ec4609dd
--- /dev/null
+++ b/chromium/chrome/browser/resources/net_internals/sdch_view.js
@@ -0,0 +1,60 @@
+// 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.
+
+/**
+ * This view displays information related to SDCH.
+ *
+ * Shows loaded dictionaries, blacklisted domains and SDCH errors.
+ */
+var SdchView = (function() {
+ 'use strict';
+
+ // We inherit from DivView.
+ var superClass = DivView;
+
+ /**
+ * @constructor
+ */
+ function SdchView() {
+ assertFirstConstructorCall(SdchView);
+
+ // Call superclass's constructor.
+ superClass.call(this, SdchView.MAIN_BOX_ID);
+
+ // Register to receive changes to the SDCH info.
+ g_browser.addSdchInfoObserver(this, false);
+ }
+
+ SdchView.TAB_ID = 'tab-handle-sdch';
+ SdchView.TAB_NAME = 'SDCH';
+ SdchView.TAB_HASH = '#sdch';
+
+ // IDs for special HTML elements in sdch_view.html
+ SdchView.MAIN_BOX_ID = 'sdch-view-tab-content';
+ SdchView.SDCH_ENABLED_SPAN_ID = 'sdch-view-sdch-enabled';
+ SdchView.SECURE_SCHEME_SUPPORT_SPAN_ID = 'sdch-view-secure-scheme-support';
+ SdchView.BLACKLIST_TBODY_ID = 'sdch-view-blacklist-body';
+ SdchView.DICTIONARIES_TBODY_ID = 'sdch-view-dictionaries-body';
+
+ cr.addSingletonGetter(SdchView);
+
+ SdchView.prototype = {
+ // Inherit the superclass's methods.
+ __proto__: superClass.prototype,
+
+ onLoadLogFinish: function(data) {
+ return this.onSdchInfoChanged(data.sdchInfo);
+ },
+
+ onSdchInfoChanged: function(sdchInfo) {
+ if (!sdchInfo || typeof(sdchInfo.sdch_enabled) === 'undefined')
+ return false;
+ var input = new JsEvalContext(sdchInfo);
+ jstProcess(input, $(SdchView.MAIN_BOX_ID));
+ return true;
+ },
+ };
+
+ return SdchView;
+})();
diff --git a/chromium/chrome/browser/resources/net_internals/sockets_view.html b/chromium/chrome/browser/resources/net_internals/sockets_view.html
index b7ffce26839..ce448d3aece 100644
--- a/chromium/chrome/browser/resources/net_internals/sockets_view.html
+++ b/chromium/chrome/browser/resources/net_internals/sockets_view.html
@@ -1,3 +1,10 @@
+<style>
+#sockets-view-close-idle-button,
+#sockets-view-flush-button {
+ margin-bottom: 2px;
+}
+</style>
+
<div id=sockets-view-tab-content class=content-box>
<ul style='margin-top:0'>
<li class="hide-when-not-capturing">
diff --git a/chromium/chrome/browser/resources/net_internals/source_entry.js b/chromium/chrome/browser/resources/net_internals/source_entry.js
index fafd4e3dd52..bf4259d7049 100644
--- a/chromium/chrome/browser/resources/net_internals/source_entry.js
+++ b/chromium/chrome/browser/resources/net_internals/source_entry.js
@@ -79,6 +79,7 @@ var SourceEntry = (function() {
switch (e.source.type) {
case EventSourceType.URL_REQUEST:
+ // TODO(ricea): Remove SOCKET_STREAM after M41 is released.
case EventSourceType.SOCKET_STREAM:
case EventSourceType.HTTP_STREAM_JOB:
case EventSourceType.ASYNC_REVALIDATION:
@@ -87,7 +88,6 @@ var SourceEntry = (function() {
case EventSourceType.CONNECT_JOB:
this.description_ = e.params.group_name;
break;
- case EventSourceType.HOST_RESOLVER_IMPL_REQUEST:
case EventSourceType.HOST_RESOLVER_IMPL_JOB:
case EventSourceType.HOST_RESOLVER_IMPL_PROC_TASK:
this.description_ = e.params.host;
@@ -100,7 +100,7 @@ var SourceEntry = (function() {
if (e.params.host != undefined)
this.description_ = e.params.host;
break;
- case EventSourceType.SPDY_SESSION:
+ case EventSourceType.HTTP2_SESSION:
if (e.params.host)
this.description_ = e.params.host + ' (' + e.params.proxy + ')';
break;
@@ -181,8 +181,8 @@ var SourceEntry = (function() {
/**
* Returns the starting entry for this source. Conceptually this is the
* first entry that was logged to this source. However, we skip over the
- * TYPE_REQUEST_ALIVE entries which wrap TYPE_URL_REQUEST_START_JOB /
- * TYPE_SOCKET_STREAM_CONNECT.
+ * TYPE_REQUEST_ALIVE entries which wrap TYPE_URL_REQUEST_START_JOB
+ * entries.
*/
getStartEntry_: function() {
if (this.entries_.length < 1)
diff --git a/chromium/chrome/browser/resources/net_internals/spdy_view.html b/chromium/chrome/browser/resources/net_internals/spdy_view.html
index 6ff166911a7..4168785648a 100644
--- a/chromium/chrome/browser/resources/net_internals/spdy_view.html
+++ b/chromium/chrome/browser/resources/net_internals/spdy_view.html
@@ -1,18 +1,16 @@
<div id=spdy-view-tab-content class=content-box>
<ul id=spdy-view-status style='margin-top:0'>
- <li>SPDY Enabled: <span jscontent="spdy_enabled"></span></li>
- <li>Use Alternate Protocol: <span jscontent="use_alternate_protocols"></span></li>
- <li>Force SPDY Always: <span jscontent="force_spdy_always"></span></li>
- <li>Force SPDY Over SSL: <span jscontent="force_spdy_over_ssl"></span></li>
+ <li>HTTP/2 Enabled: <span jscontent="spdy_enabled"></span></li>
+ <li>Use Alternative Service: <span jscontent="use_alternate_protocols"></span></li>
<li>Next Protocols: <span jscontent="next_protos"></span></li>
</ul>
- <h4>SPDY sessions</h4>
+ <h4>HTTP/2 sessions</h4>
<div id=spdy-view-session-info>
<!-- Only one of these two are shown -->
<div jsdisplay="spdySessionInfo.length == 0">None</div>
<div jsdisplay="spdySessionInfo.length != 0">
- <a href="#events&q=type:SPDY_SESSION%20is:active">View live SPDY sessions</a>
+ <a href="#events&q=type:HTTP2_SESSION%20is:active">View live HTTP/2 sessions</a>
<p>
<table class="styled-table">
<thead>
@@ -68,7 +66,7 @@
</div>
</div>
- <h4>Alternate Protocol Mappings</h4>
+ <h4>Alternative Service Mappings</h4>
<div id=spdy-view-alternate-protocol-mappings>
<div jsdisplay="spdyAlternateProtocolMappings.length == 0">None</div>
<div jsdisplay="spdyAlternateProtocolMappings.length != 0">
@@ -76,13 +74,17 @@
<thead>
<tr>
<th>Host</th>
- <th>Alternate Protocol</th>
+ <th>Alternative Service</th>
</tr>
</thead>
<tbody>
<tr jsselect="spdyAlternateProtocolMappings">
<td jscontent="host_port_pair"></td>
- <td jscontent="alternate_protocol"></td>
+ <!-- "alternative_service" is used since release 43, see
+ https://crrev.com/1043973002. "alternate_protocol" is here
+ to support importing netlog json files from earlier browsers.
+ TODO(bnc): Deprecate around 2015 October. -->
+ <td jscontent="$this.alternative_service || $this.alternate_protocol"></td>
</tr>
</tbody>
</table>
diff --git a/chromium/chrome/browser/resources/net_internals/spdy_view.js b/chromium/chrome/browser/resources/net_internals/spdy_view.js
index b997122eaa5..e7146807e29 100644
--- a/chromium/chrome/browser/resources/net_internals/spdy_view.js
+++ b/chromium/chrome/browser/resources/net_internals/spdy_view.js
@@ -27,8 +27,8 @@ var SpdyView = (function() {
}
SpdyView.TAB_ID = 'tab-handle-spdy';
- SpdyView.TAB_NAME = 'SPDY';
- SpdyView.TAB_HASH = '#spdy';
+ SpdyView.TAB_NAME = 'HTTP/2';
+ SpdyView.TAB_HASH = '#http2';
// IDs for special HTML elements in spdy_view.html
SpdyView.MAIN_BOX_ID = 'spdy-view-tab-content';
diff --git a/chromium/chrome/browser/resources/net_internals/status_view.css b/chromium/chrome/browser/resources/net_internals/status_view.css
index 8e7e76ae1a0..b9efa8e4037 100644
--- a/chromium/chrome/browser/resources/net_internals/status_view.css
+++ b/chromium/chrome/browser/resources/net_internals/status_view.css
@@ -5,6 +5,7 @@
.capture-status-view {
background: rgb(238, 0, 0);
+ font-size: 133%;
}
.halted-status-view {
diff --git a/chromium/chrome/browser/resources/net_internals/test_view.html b/chromium/chrome/browser/resources/net_internals/test_view.html
deleted file mode 100644
index 9f1839c7b16..00000000000
--- a/chromium/chrome/browser/resources/net_internals/test_view.html
+++ /dev/null
@@ -1,13 +0,0 @@
-<!-- Connection tests -->
-<div id="test-view-tab-content" class="content-box">
- <div style='margin-bottom: 20px'>
- Input a URL which failed to load, and then click the button to run some
- tests for why it failed.
- </div>
- <form id="test-view-connection-tests-form">
- <label>URL: <input id="test-view-url-input" type="text"></label>
- <input id="test-view-connection-tests-submit" type="submit"
- value="Start tests">
- </form>
- <div id="test-view-summary"></div>
-</div>
diff --git a/chromium/chrome/browser/resources/net_internals/test_view.js b/chromium/chrome/browser/resources/net_internals/test_view.js
deleted file mode 100644
index a902bd0c2fa..00000000000
--- a/chromium/chrome/browser/resources/net_internals/test_view.js
+++ /dev/null
@@ -1,163 +0,0 @@
-// Copyright (c) 2012 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.
-
-/**
- * This view displays the progress and results from the "connection tester".
- *
- * - Has an input box to specify the URL.
- * - Has a button to start running the tests.
- * - Shows the set of experiments that have been run so far, and their
- * result.
- */
-var TestView = (function() {
- 'use strict';
-
- // We inherit from DivView.
- var superClass = DivView;
-
- /**
- * @constructor
- */
- function TestView() {
- assertFirstConstructorCall(TestView);
-
- // Call superclass's constructor.
- superClass.call(this, TestView.MAIN_BOX_ID);
-
- this.urlInput_ = $(TestView.URL_INPUT_ID);
- this.summaryDiv_ = $(TestView.SUMMARY_DIV_ID);
-
- var form = $(TestView.FORM_ID);
- form.addEventListener('submit', this.onSubmitForm_.bind(this), false);
-
- // Register to test information as it's received.
- g_browser.addConnectionTestsObserver(this);
- }
-
- TestView.TAB_ID = 'tab-handle-tests';
- TestView.TAB_NAME = 'Tests';
- TestView.TAB_HASH = '#tests';
-
- // IDs for special HTML elements in test_view.html
- TestView.MAIN_BOX_ID = 'test-view-tab-content';
- TestView.FORM_ID = 'test-view-connection-tests-form';
- TestView.URL_INPUT_ID = 'test-view-url-input';
- TestView.SUMMARY_DIV_ID = 'test-view-summary';
- // Needed by tests.
- TestView.SUBMIT_BUTTON_ID = 'test-view-connection-tests-submit';
-
-
- cr.addSingletonGetter(TestView);
-
- TestView.prototype = {
- // Inherit the superclass's methods.
- __proto__: superClass.prototype,
-
- onSubmitForm_: function(event) {
- g_browser.sendStartConnectionTests(this.urlInput_.value);
- event.preventDefault();
- },
-
- /**
- * Callback for when the connection tests have begun.
- */
- onStartedConnectionTestSuite: function() {
- this.summaryDiv_.innerHTML = '';
-
- var p = addNode(this.summaryDiv_, 'p');
- addTextNode(p, 'Started connection test suite suite on ');
- timeutil.addNodeWithDate(p, new Date());
-
- // Add a table that will hold the individual test results.
- var table = addNode(this.summaryDiv_, 'table');
- table.className = 'styled-table';
- var thead = addNode(table, 'thead');
- thead.innerHTML = '<tr><th>Result</th><th>Experiment</th>' +
- '<th>Error</th><th>Time (ms)</th></tr>';
-
- this.tbody_ = addNode(table, 'tbody');
- },
-
- /**
- * Callback for when an individual test in the suite has begun.
- */
- onStartedConnectionTestExperiment: function(experiment) {
- var tr = addNode(this.tbody_, 'tr');
-
- var passFailCell = addNode(tr, 'td');
-
- var experimentCell = addNode(tr, 'td');
-
- var resultCell = addNode(tr, 'td');
- addTextNode(resultCell, '?');
-
- var dtCell = addNode(tr, 'td');
- addTextNode(dtCell, '?');
-
- // We will fill in result cells with actual values (to replace the
- // placeholder '?') once the test has completed. For now we just
- // save references to these cells.
- this.currentExperimentRow_ = {
- experimentCell: experimentCell,
- dtCell: dtCell,
- resultCell: resultCell,
- passFailCell: passFailCell,
- startTime: timeutil.getCurrentTime()
- };
-
- addTextNode(experimentCell, 'Fetch ' + experiment.url);
-
- if (experiment.proxy_settings_experiment ||
- experiment.host_resolver_experiment) {
- var ul = addNode(experimentCell, 'ul');
-
- if (experiment.proxy_settings_experiment) {
- var li = addNode(ul, 'li');
- addTextNode(li, experiment.proxy_settings_experiment);
- }
-
- if (experiment.host_resolver_experiment) {
- var li = addNode(ul, 'li');
- addTextNode(li, experiment.host_resolver_experiment);
- }
- }
- },
-
- /**
- * Callback for when an individual test in the suite has finished.
- */
- onCompletedConnectionTestExperiment: function(experiment, result) {
- var r = this.currentExperimentRow_;
-
- var endTime = timeutil.getCurrentTime();
-
- r.dtCell.innerHTML = '';
- addTextNode(r.dtCell, (endTime - r.startTime));
-
- r.resultCell.innerHTML = '';
-
- if (result == 0) {
- r.passFailCell.style.color = 'green';
- addTextNode(r.passFailCell, 'PASS');
- } else {
- addTextNode(r.resultCell,
- netErrorToString(result) + ' (' + result + ')');
- r.passFailCell.style.color = '#e00';
- addTextNode(r.passFailCell, 'FAIL');
- }
-
- this.currentExperimentRow_ = null;
- },
-
- /**
- * Callback for when the last test in the suite has finished.
- */
- onCompletedConnectionTestSuite: function() {
- var p = addNode(this.summaryDiv_, 'p');
- addTextNode(p, 'Completed connection test suite suite');
- }
- };
-
- return TestView;
-})();
diff --git a/chromium/chrome/browser/resources/net_internals/timeline_view.css b/chromium/chrome/browser/resources/net_internals/timeline_view.css
index 565117a5db7..5ce199452f1 100644
--- a/chromium/chrome/browser/resources/net_internals/timeline_view.css
+++ b/chromium/chrome/browser/resources/net_internals/timeline_view.css
@@ -51,10 +51,6 @@
color: black;
}
-#timeline-view-dns-requests {
- color: rgb(102, 187, 187);
-}
-
#timeline-view-dns-jobs {
color: rgb(150, 20, 29);
}
diff --git a/chromium/chrome/browser/resources/net_internals/timeline_view.html b/chromium/chrome/browser/resources/net_internals/timeline_view.html
index 10ada2206f0..797f4900f1b 100644
--- a/chromium/chrome/browser/resources/net_internals/timeline_view.html
+++ b/chromium/chrome/browser/resources/net_internals/timeline_view.html
@@ -14,9 +14,6 @@
<li id=timeline-view-url-requests><label><input type=checkbox checked>
&#9608; <span class=timeline-view-text>URL requests</span></label>
</li>
- <li id=timeline-view-dns-requests><label><input type=checkbox checked>
- &#9608; <span class=timeline-view-text>DNS requests</span></label>
- </li>
<li id=timeline-view-dns-jobs><label><input type=checkbox checked>
&#9608; <span class=timeline-view-text>DNS jobs</span></label>
</li>
diff --git a/chromium/chrome/browser/resources/net_internals/timeline_view.js b/chromium/chrome/browser/resources/net_internals/timeline_view.js
index c4183d34a4c..083000bc00b 100644
--- a/chromium/chrome/browser/resources/net_internals/timeline_view.js
+++ b/chromium/chrome/browser/resources/net_internals/timeline_view.js
@@ -71,7 +71,6 @@ var TimelineView = (function() {
TimelineView.OPEN_SOCKETS_ID = 'timeline-view-open-sockets';
TimelineView.IN_USE_SOCKETS_ID = 'timeline-view-in-use-sockets';
TimelineView.URL_REQUESTS_ID = 'timeline-view-url-requests';
- TimelineView.DNS_REQUESTS_ID = 'timeline-view-dns-requests';
TimelineView.DNS_JOBS_ID = 'timeline-view-dns-jobs';
TimelineView.BYTES_RECEIVED_ID = 'timeline-view-bytes-received';
TimelineView.BYTES_SENT_ID = 'timeline-view-bytes-sent';
@@ -205,11 +204,6 @@ var TimelineView = (function() {
TimelineView.URL_REQUESTS_ID);
this.addDataSeries_(new SourceCountDataSeries(
- EventSourceType.HOST_RESOLVER_IMPL_REQUEST,
- EventType.HOST_RESOLVER_IMPL_REQUEST),
- TimelineView.DNS_REQUESTS_ID);
-
- this.addDataSeries_(new SourceCountDataSeries(
EventSourceType.HOST_RESOLVER_IMPL_JOB,
EventType.HOST_RESOLVER_IMPL_JOB),
TimelineView.DNS_JOBS_ID);
diff --git a/chromium/chrome/browser/resources/net_internals/waterfall_row.js b/chromium/chrome/browser/resources/net_internals/waterfall_row.js
index ee922cb6f50..6e6abe511bf 100644
--- a/chromium/chrome/browser/resources/net_internals/waterfall_row.js
+++ b/chromium/chrome/browser/resources/net_internals/waterfall_row.js
@@ -252,19 +252,14 @@ var WaterfallRow = (function() {
for (var i = 0; i < httpStreamJobSources.length; ++i) {
// Two levels down from URL Requests.
- var hostResolverImplSources = findDependenciesOfType_(
- httpStreamJobSources[i], EventType.HOST_RESOLVER_IMPL);
-
var socketSources = findDependenciesOfType_(
httpStreamJobSources[i], EventType.SOCKET_POOL_BOUND_TO_SOCKET);
- // Three levels down from URL Requests.
-
// TODO(mmenke): Some of these may be nested in the PROXY_SERVICE
// event, resulting in incorrect display, since nested
// events aren't handled.
- var hostResolverImplRequestPairs = findEntryPairsFromSourceEntries_(
- hostResolverImplSources, EventType.HOST_RESOLVER_IMPL_REQUEST);
+ var hostResolverImplRequestPairs = findEntryPairsByType_(
+ httpStreamJobSources[i], EventType.HOST_RESOLVER_IMPL_REQUEST);
eventPairs = eventPairs.concat(hostResolverImplRequestPairs);
// Truncate times of connection events such that they don't occur before
@@ -310,7 +305,7 @@ var WaterfallRow = (function() {
return a.startEntry.time - b.startEntry.time;
});
return eventPairs;
- }
+ };
function eventTypeToCssClass_(eventType) {
return eventType.toLowerCase().replace(/_/g, '-');
diff --git a/chromium/chrome/browser/resources/network_speech_synthesis/tts_extension.js b/chromium/chrome/browser/resources/network_speech_synthesis/tts_extension.js
index ebe9227f728..905fbfe3d71 100644
--- a/chromium/chrome/browser/resources/network_speech_synthesis/tts_extension.js
+++ b/chromium/chrome/browser/resources/network_speech_synthesis/tts_extension.js
@@ -38,7 +38,7 @@ TtsExtension.prototype = {
* A mapping from language and gender to voice name, hardcoded for now
* until the speech synthesis server capabilities response provides this.
* The key of this map is of the form '<lang>-<gender>'.
- * @type {Object.<string, string>}
+ * @type {Object<string, string>}
* @private
*/
LANG_AND_GENDER_TO_VOICE_NAME_: {
@@ -68,7 +68,7 @@ TtsExtension.prototype = {
* A mapping from voice name to language and gender, derived from the
* manifest file. This is used in case the speech synthesis request
* specifies a voice name but doesn't specify a language code or gender.
- * @type {Object.<string, {lang: string, gender: string}>}
+ * @type {Object<string, {lang: string, gender: string}>}
* @private
*/
voiceNameToLangAndGender_: {},
diff --git a/chromium/chrome/browser/resources/notification_1line.html b/chromium/chrome/browser/resources/notification_1line.html
index 3cac48e444b..5a7ebfddc5e 100644
--- a/chromium/chrome/browser/resources/notification_1line.html
+++ b/chromium/chrome/browser/resources/notification_1line.html
@@ -1,11 +1,11 @@
-<!DOCTYPE html>
+<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>$2</title>
+ <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<style>
body {
- font-family: helvetica, arial, sans-serif;
font-size: 13px;
}
</style>
diff --git a/chromium/chrome/browser/resources/notification_2line.html b/chromium/chrome/browser/resources/notification_2line.html
index 6fd858a02a4..a089a510d5a 100644
--- a/chromium/chrome/browser/resources/notification_2line.html
+++ b/chromium/chrome/browser/resources/notification_2line.html
@@ -1,18 +1,17 @@
-<!DOCTYPE html>
+<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>$1</title>
+ <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<style type="text/css">
- #title {
- font-weight: bold;
- font-size: 13px;
- font-family: helvetica, arial, sans-serif;
- }
+ #title,
#description {
- font-family: helvetica, arial, sans-serif;
font-size: 13px;
}
+ #title {
+ font-weight: bold;
+ }
</style>
</head>
<body dir="$3">
diff --git a/chromium/chrome/browser/resources/notification_icon.html b/chromium/chrome/browser/resources/notification_icon.html
index 0bc60e40bfa..2357a49d545 100644
--- a/chromium/chrome/browser/resources/notification_icon.html
+++ b/chromium/chrome/browser/resources/notification_icon.html
@@ -1,8 +1,9 @@
-<!DOCTYPE html>
+<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>$2</title>
+ <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<style>
body {
direction: $5;
@@ -16,11 +17,9 @@
margin-$4: 38px;
font-weight: bold;
font-size: 13px;
- font-family: helvetica, arial, sans-serif;
}
#description {
margin-$4: 38px;
- font-family: helvetica, arial, sans-serif;
font-size: 13px;
}
</style>
diff --git a/chromium/chrome/browser/resources/ntp4/apps_page.css b/chromium/chrome/browser/resources/ntp4/apps_page.css
index bd2e9ccc6c5..de0f83a4681 100644
--- a/chromium/chrome/browser/resources/ntp4/apps_page.css
+++ b/chromium/chrome/browser/resources/ntp4/apps_page.css
@@ -23,7 +23,7 @@
/* Active gets applied right before .suppress-active, so to avoid flicker
* we need to make the scale go back to normal without an animation. */
.app-contents.suppress-active {
- -webkit-transition-duration: 0;
+ -webkit-transition-duration: 0ms;
}
.app-contents > span {
@@ -107,7 +107,6 @@
border-radius: 2px;
border-top-width: 2px;
bottom: 90px;
- font-family: Arial, Helvetica, sans-serif;
height: 120px;
left: 50%;
margin-left: -300px;
diff --git a/chromium/chrome/browser/resources/ntp4/apps_page.js b/chromium/chrome/browser/resources/ntp4/apps_page.js
index 952619a4ec9..bdbefcb66f4 100644
--- a/chromium/chrome/browser/resources/ntp4/apps_page.js
+++ b/chromium/chrome/browser/resources/ntp4/apps_page.js
@@ -11,7 +11,6 @@ cr.define('ntp', function() {
NTP_APPS_COLLAPSED: 1,
NTP_APPS_MENU: 2,
NTP_MOST_VISITED: 3,
- NTP_RECENTLY_CLOSED: 4,
NTP_APP_RE_ENABLE: 16,
NTP_WEBSTORE_FOOTER: 18,
NTP_WEBSTORE_PLUS_ICON: 19,
@@ -49,12 +48,9 @@ cr.define('ntp', function() {
this.launch_.addEventListener('activate', this.onLaunch_.bind(this));
menu.appendChild(cr.ui.MenuItem.createSeparator());
- if (loadTimeData.getBoolean('enableStreamlinedHostedApps'))
- this.launchRegularTab_ = this.appendMenuItem_('applaunchtypetab');
- else
- this.launchRegularTab_ = this.appendMenuItem_('applaunchtyperegular');
+ this.launchRegularTab_ = this.appendMenuItem_('applaunchtyperegular');
this.launchPinnedTab_ = this.appendMenuItem_('applaunchtypepinned');
- if (!cr.isMac)
+ if (loadTimeData.getBoolean('enableNewBookmarkApps') || !cr.isMac)
this.launchNewWindow_ = this.appendMenuItem_('applaunchtypewindow');
this.launchFullscreen_ = this.appendMenuItem_('applaunchtypefullscreen');
@@ -67,12 +63,20 @@ cr.define('ntp', function() {
this.launchTypeMenuSeparator_ = cr.ui.MenuItem.createSeparator();
menu.appendChild(this.launchTypeMenuSeparator_);
this.options_ = this.appendMenuItem_('appoptions');
- this.details_ = this.appendMenuItem_('appdetails');
this.uninstall_ = this.appendMenuItem_('appuninstall');
+
+ if (loadTimeData.getBoolean('canShowAppInfoDialog')) {
+ this.appinfo_ = this.appendMenuItem_('appinfodialog');
+ this.appinfo_.addEventListener('activate',
+ this.onShowAppInfo_.bind(this));
+ } else {
+ this.details_ = this.appendMenuItem_('appdetails');
+ this.details_.addEventListener('activate',
+ this.onShowDetails_.bind(this));
+ }
+
this.options_.addEventListener('activate',
this.onShowOptions_.bind(this));
- this.details_.addEventListener('activate',
- this.onShowDetails_.bind(this));
this.uninstall_.addEventListener('activate',
this.onUninstall_.bind(this));
@@ -131,20 +135,21 @@ cr.define('ntp', function() {
this.launch_.textContent = app.appData.title;
- var launchTypeRegularTab = this.launchRegularTab_;
+ var launchTypeWindow = this.launchNewWindow_;
this.forAllLaunchTypes_(function(launchTypeButton, id) {
launchTypeButton.disabled = false;
launchTypeButton.checked = app.appData.launch_type == id;
- // Streamlined hosted apps should only show the "Open as tab" button.
+ // If bookmark apps are enabled, only show the "Open as window" button.
launchTypeButton.hidden = app.appData.packagedApp ||
- (loadTimeData.getBoolean('enableStreamlinedHostedApps') &&
- launchTypeButton != launchTypeRegularTab);
+ (loadTimeData.getBoolean('enableNewBookmarkApps') &&
+ launchTypeButton != launchTypeWindow);
});
this.launchTypeMenuSeparator_.hidden = app.appData.packagedApp;
this.options_.disabled = !app.appData.optionsUrl || !app.appData.enabled;
- this.details_.disabled = !app.appData.detailsUrl;
+ if (this.details_)
+ this.details_.disabled = !app.appData.detailsUrl;
this.uninstall_.disabled = !app.appData.mayDisable;
if (cr.isMac) {
@@ -168,11 +173,11 @@ cr.define('ntp', function() {
var pressed = e.currentTarget;
var app = this.app_;
var targetLaunchType = pressed;
- // Streamlined hosted apps can only toggle between open as window and open
- // as tab.
- if (loadTimeData.getBoolean('enableStreamlinedHostedApps')) {
- targetLaunchType = this.launchRegularTab_.checked ?
- this.launchNewWindow_ : this.launchRegularTab_;
+ // When bookmark apps are enabled, hosted apps can only toggle between
+ // open as window and open as tab.
+ if (loadTimeData.getBoolean('enableNewBookmarkApps')) {
+ targetLaunchType = this.launchNewWindow_.checked ?
+ this.launchRegularTab_ : this.launchNewWindow_;
}
this.forAllLaunchTypes_(function(launchTypeButton, id) {
if (launchTypeButton == targetLaunchType) {
@@ -197,6 +202,9 @@ cr.define('ntp', function() {
onCreateShortcut_: function(e) {
chrome.send('createAppShortcut', [this.app_.appData.id]);
},
+ onShowAppInfo_: function(e) {
+ chrome.send('showAppInfo', [this.app_.appData.id]);
+ }
};
/**
diff --git a/chromium/chrome/browser/resources/ntp4/compiled_resources.gyp b/chromium/chrome/browser/resources/ntp4/compiled_resources.gyp
index 455c47e876e..ed029528de0 100644
--- a/chromium/chrome/browser/resources/ntp4/compiled_resources.gyp
+++ b/chromium/chrome/browser/resources/ntp4/compiled_resources.gyp
@@ -39,9 +39,7 @@
'page_list_view.js',
'nav_dot.js',
'other_sessions.js',
- 'suggestions_page.js',
'new_tab.js',
- 'recently_closed.js',
],
'externs': ['<(CLOSURE_DIR)/externs/chrome_send_externs.js'],
},
diff --git a/chromium/chrome/browser/resources/ntp4/dot_list.js b/chromium/chrome/browser/resources/ntp4/dot_list.js
index f445ed30add..da6ef01c28a 100644
--- a/chromium/chrome/browser/resources/ntp4/dot_list.js
+++ b/chromium/chrome/browser/resources/ntp4/dot_list.js
@@ -25,7 +25,6 @@ cr.define('ntp', function() {
DotList.prototype = {
__proto__: HTMLUListElement.prototype,
- /** @override */
decorate: function() {
this.addEventListener('keydown', this.onKeyDown_.bind(this));
navDots = this.getElementsByClassName('dot');
diff --git a/chromium/chrome/browser/resources/ntp4/footer_menu.css b/chromium/chrome/browser/resources/ntp4/footer_menu.css
index 7e0f6689253..a4ecd586fd2 100644
--- a/chromium/chrome/browser/resources/ntp4/footer_menu.css
+++ b/chromium/chrome/browser/resources/ntp4/footer_menu.css
@@ -136,7 +136,7 @@ html[dir='rtl'] .footer-menu-item {
}
.recent-window {
- background-image: url('images/closed_window.png');
+ background-image: url(images/closed_window.png);
}
/* TODO(estade): find a better color for active. */
@@ -148,7 +148,7 @@ html[dir='rtl'] .footer-menu-item {
.disclosure-triangle {
-webkit-margin-start: 2px;
- -webkit-mask-image: url('images/disclosure_triangle_mask.png');
+ -webkit-mask-image: url(images/disclosure_triangle_mask.png);
background-color: #7F7F7F;
display: inline-block;
height: 9px;
diff --git a/chromium/chrome/browser/resources/ntp4/guest_tab.html b/chromium/chrome/browser/resources/ntp4/guest_tab.html
index 1530af1cb6b..9c1150a09e6 100644
--- a/chromium/chrome/browser/resources/ntp4/guest_tab.html
+++ b/chromium/chrome/browser/resources/ntp4/guest_tab.html
@@ -1,19 +1,19 @@
-<!DOCTYPE html>
-<html i18n-values="dir:textdirection">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
<head>
<meta charset="utf-8">
<title i18n-content="title"></title>
+<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="incognito_and_guest_tab.css">
<script>
// Until themes can clear the cache, force-reload the theme stylesheet.
document.write('<link id="guestthemecss" rel="stylesheet" ' +
- 'href="chrome://theme/css/guest_new_tab_theme.css?' +
+ 'href="chrome://theme/css/incognito_new_tab_theme.css?' +
Date.now() + '">');
</script>
</head>
<body>
-<div class="content"
- i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<div class="content">
<h1 i18n-content="guestTabHeading"></h1>
<p>
<span i18n-content="guestTabDescription"></span>
@@ -25,7 +25,7 @@ document.write('<link id="guestthemecss" rel="stylesheet" ' +
<script>
function themeChanged() {
document.getElementById('guestthemecss').href =
- 'chrome://theme/css/guest_new_tab_theme.css?' + Date.now();
+ 'chrome://theme/css/incognito_new_tab_theme.css?' + Date.now();
}
</script>
</html>
diff --git a/chromium/chrome/browser/resources/ntp4/incognito_tab.css b/chromium/chrome/browser/resources/ntp4/incognito_tab.css
index e3b44f1857a..875d0b55651 100644
--- a/chromium/chrome/browser/resources/ntp4/incognito_tab.css
+++ b/chromium/chrome/browser/resources/ntp4/incognito_tab.css
@@ -9,8 +9,8 @@
.icon {
-webkit-margin-start: 25px;
content: -webkit-image-set(
- url('../../../browser/resources/ntp4/images/incognito_icon.png') 1x,
- url('../../../browser/resources/ntp4/images/2x/incognito_icon.png') 2x);
+ url(../../../browser/resources/ntp4/images/incognito_icon.png) 1x,
+ url(../../../browser/resources/ntp4/images/2x/incognito_icon.png) 2x);
float: right;
height: 128px;
margin-bottom: 10px;
diff --git a/chromium/chrome/browser/resources/ntp4/incognito_tab.html b/chromium/chrome/browser/resources/ntp4/incognito_tab.html
index 7a4c8e34eae..276d854b1d9 100644
--- a/chromium/chrome/browser/resources/ntp4/incognito_tab.html
+++ b/chromium/chrome/browser/resources/ntp4/incognito_tab.html
@@ -1,8 +1,11 @@
-<!DOCTYPE html>
-<html i18n-values="dir:textdirection;bookmarkbarattached:bookmarkbarattached">
+<!doctype html>
+<html i18n-values="dir:textdirection;
+ bookmarkbarattached:bookmarkbarattached;
+ lang:language">
<head>
<meta charset="utf-8">
<title i18n-content="title"></title>
+<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="incognito_and_guest_tab.css">
<link rel="stylesheet" href="incognito_tab.css">
<script>
@@ -13,8 +16,7 @@ document.write('<link id="incognitothemecss" rel="stylesheet" ' +
</script>
</head>
<body>
-<div class="content"
- i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<div class="content">
<div class="icon"></div>
<span>
<h1 i18n-content="incognitoTabHeading"></h1>
diff --git a/chromium/chrome/browser/resources/ntp4/most_visited_page.css b/chromium/chrome/browser/resources/ntp4/most_visited_page.css
index d1f15d17349..0bc0abce378 100644
--- a/chromium/chrome/browser/resources/ntp4/most_visited_page.css
+++ b/chromium/chrome/browser/resources/ntp4/most_visited_page.css
@@ -156,7 +156,7 @@ html[dir='rtl'] .most-visited .favicon {
/* The thumbnail gets a shadow when clicked, but not when the click is on the
* close button. */
.most-visited:active .close-button:not(:active) + .thumbnail .thumbnail-shield {
- -webkit-box-shadow: inset 0 1px 10px rgba(0, 0, 0, 0.2);
+ box-shadow: inset 0 1px 10px rgba(0, 0, 0, 0.2);
}
.thumbnail-wrapper {
diff --git a/chromium/chrome/browser/resources/ntp4/most_visited_page.js b/chromium/chrome/browser/resources/ntp4/most_visited_page.js
index 0edb54af4a4..4f6cbd17a38 100644
--- a/chromium/chrome/browser/resources/ntp4/most_visited_page.js
+++ b/chromium/chrome/browser/resources/ntp4/most_visited_page.js
@@ -370,7 +370,7 @@ cr.define('ntp', function() {
/**
* Array of most visited data objects.
- * @type {Array.<PageData>}
+ * @type {Array<PageData>}
*/
get data() {
return this.data_;
diff --git a/chromium/chrome/browser/resources/ntp4/new_guest_tab_theme.css b/chromium/chrome/browser/resources/ntp4/new_guest_tab_theme.css
deleted file mode 100644
index b4d1df12dbd..00000000000
--- a/chromium/chrome/browser/resources/ntp4/new_guest_tab_theme.css
+++ /dev/null
@@ -1,22 +0,0 @@
-/* Copyright 2013 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.
- */
-
-html {
- background-attachment: fixed;
- background-color: $2;
- background-image: url(chrome://theme/IDR_THEME_NTP_BACKGROUND?$1);
- background-position: $3;
- background-repeat: $5;
- height: 100%;
- overflow: auto;
-}
-
-#attribution-img {
- content: url(chrome://theme/IDR_THEME_NTP_ATTRIBUTION?$1);
-}
-
-html[bookmarkbarattached='true'] {
- background-position: $4;
-}
diff --git a/chromium/chrome/browser/resources/ntp4/new_tab.css b/chromium/chrome/browser/resources/ntp4/new_tab.css
index ca7caf500a1..3aa2ac2d869 100644
--- a/chromium/chrome/browser/resources/ntp4/new_tab.css
+++ b/chromium/chrome/browser/resources/ntp4/new_tab.css
@@ -80,8 +80,8 @@ html[dir='rtl'] #notification-container {
background-color: transparent;
/* TODO(estade): this should animate between states. */
background-image: -webkit-image-set(
- url('../../../../ui/resources/default_100_percent/close_2.png') 1x,
- url('../../../../ui/resources/default_200_percent/close_2.png') 2x);
+ url(../../../../ui/resources/default_100_percent/close_2.png) 1x,
+ url(../../../../ui/resources/default_200_percent/close_2.png) 2x);
border: 0;
cursor: default;
display: inline-block;
@@ -93,15 +93,15 @@ html[dir='rtl'] #notification-container {
.close-button:hover,
.close-button:focus {
background-image: -webkit-image-set(
- url('../../../../ui/resources/default_100_percent/close_2_hover.png') 1x,
- url('../../../../ui/resources/default_200_percent/close_2_hover.png') 2x);
+ url(../../../../ui/resources/default_100_percent/close_2_hover.png) 1x,
+ url(../../../../ui/resources/default_200_percent/close_2_hover.png) 2x);
}
.close-button:active {
background-image: -webkit-image-set(
- url('../../../../ui/resources/default_100_percent/close_2_pressed.png')
+ url(../../../../ui/resources/default_100_percent/close_2_pressed.png)
1x,
- url('../../../../ui/resources/default_200_percent/close_2_pressed.png')
+ url(../../../../ui/resources/default_200_percent/close_2_pressed.png)
2x);
}
@@ -143,8 +143,7 @@ body.bare-minimum #card-slider-frame {
}
/* For themes that right-align their images, we flip the attribution to the
- * left to avoid conflicts. We also do this for bare-minimum mode since there
- * can be conflicts with the recently closed menu. */
+ * left to avoid conflicts. */
html[themegravity='right'] #attribution,
body.bare-minimum #attribution,
html[dir='rtl'] #attribution {
@@ -230,9 +229,9 @@ body.bare-minimum #dot-list {
/* Login Status. **************************************************************/
#login-container {
- -webkit-box-shadow: none;
background: transparent none;
border: none;
+ box-shadow: none;
color: inherit;
cursor: pointer;
font: inherit;
@@ -253,6 +252,10 @@ html[dir='rtl'] #login-container {
right: auto;
}
+#login-container [is='action-link'] {
+ -webkit-margin-start: 0;
+}
+
.login-status-icon {
-webkit-padding-end: 37px;
background-position: right center;
@@ -264,11 +267,6 @@ html[dir='rtl'] .login-status-icon {
background-position-x: left;
}
-.profile-name:hover,
-.link-span {
- text-decoration: underline;
-}
-
#login-status-bubble-contents {
font-size: 1.1em;
}
@@ -300,8 +298,8 @@ html[dir='rtl'] .login-status-icon {
#trash {
-webkit-padding-start: 10px;
- -webkit-transition: top 200ms, opacity 0;
- -webkit-transition-delay: 0, 200ms;
+ -webkit-transition: top 200ms, opacity 0ms;
+ -webkit-transition-delay: 0ms, 200ms;
color: #222;
height: 100%;
opacity: 0;
@@ -317,8 +315,8 @@ html[dir='rtl'] #trash {
}
#footer.showing-trash-mode #trash {
- -webkit-transition-delay: 0, 0;
- -webkit-transition-duration: 0, 200ms;
+ -webkit-transition-delay: 0ms, 0ms;
+ -webkit-transition-duration: 0ms, 200ms;
opacity: 0.75;
top: 0;
}
@@ -390,7 +388,6 @@ html[dir='rtl'] #footer.showing-trash-mode #trash.drag-target .lid {
#chrome-web-store-link {
-webkit-order: 3;
-webkit-padding-end: 12px;
- /* Match transition delay of recently closed button. */
-webkit-transition-delay: 100ms;
color: inherit;
cursor: pointer;
@@ -403,7 +400,7 @@ html[dir='rtl'] #footer.showing-trash-mode #trash.drag-target .lid {
#chrome-web-store-title {
-webkit-padding-end: 36px;
-webkit-padding-start: 15px;
- background: url('chrome://theme/IDR_WEBSTORE_ICON_24') right 50% no-repeat;
+ background: url(chrome://theme/IDR_WEBSTORE_ICON_24) right 50% no-repeat;
display: inline-block;
line-height: 49px;
}
@@ -434,7 +431,7 @@ html[dir='rtl'] #chrome-web-store-title {
/* In trash mode, hide the menus and web store link. */
#footer.showing-trash-mode .menu-container {
- -webkit-transition-delay: 0;
+ -webkit-transition-delay: 0ms;
opacity: 0;
visibility: hidden;
}
@@ -450,10 +447,6 @@ html[dir='rtl'] #chrome-web-store-title {
min-width: -webkit-min-content;
}
-#recently-closed-menu-button {
- -webkit-order: 1;
-}
-
#other-sessions-menu-button {
-webkit-order: 0;
}
diff --git a/chromium/chrome/browser/resources/ntp4/new_tab.html b/chromium/chrome/browser/resources/ntp4/new_tab.html
index bb8b82b9595..4cac17a1381 100644
--- a/chromium/chrome/browser/resources/ntp4/new_tab.html
+++ b/chromium/chrome/browser/resources/ntp4/new_tab.html
@@ -1,10 +1,9 @@
-<!DOCTYPE html>
-<html i18n-values="
- dir:textdirection;
- hasattribution:hasattribution;
- themegravity:themegravity;
- bookmarkbarattached:bookmarkbarattached;"
- class="starting-up">
+<!doctype html>
+<html class="starting-up" i18n-values="dir:textdirection;
+ hasattribution:hasattribution;
+ themegravity:themegravity;
+ bookmarkbarattached:bookmarkbarattached;
+ lang:language">
<head>
<meta charset="utf-8">
<title i18n-content="title"></title>
@@ -20,6 +19,7 @@
<!-- It's important that this be the first script loaded. -->
<script src="logging.js"></script>
+<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="../../../../ui/webui/resources/css/bubble.css">
<link rel="stylesheet" href="../../../../ui/webui/resources/css/expandable_bubble.css">
<link rel="stylesheet" href="../../../../ui/webui/resources/css/menu.css">
@@ -27,7 +27,6 @@
<link rel="stylesheet" href="../../../../ui/webui/resources/css/trash.css">
<link rel="stylesheet" href="../../../../ui/webui/resources/css/widgets.css">
<link rel="stylesheet" href="apps_page.css">
-<link rel="stylesheet" href="chrome://newtab/suggestions_page.css">
<link rel="stylesheet" href="most_visited_page.css">
<link rel="stylesheet" href="nav_dot.css">
<link rel="stylesheet" href="new_tab.css">
@@ -66,11 +65,10 @@
<script src="nav_dot.js"></script>
<script src="new_tab.js"></script>
-<script src="recently_closed.js"></script>
<script src="other_sessions.js"></script>
</head>
-<body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<body>
<div id="notification-container" class="inactive" hidden>
<div id="notification">
<span></span>
@@ -123,11 +121,6 @@
<span i18n-content="otherSessions"></span>
<div class="disclosure-triangle"></div>
</button>
- <button id="recently-closed-menu-button"
- class="footer-menu-button custom-appearance" hidden>
- <span i18n-content="recentlyclosed"></span>
- <div class="disclosure-triangle"></div>
- </button>
<a id="chrome-web-store-link">
<span id="chrome-web-store-title" i18n-content="webStoreTitleShort">
</span>
@@ -143,12 +136,12 @@
</div>
</div>
- <button id="login-container" class="custom-appearance" hidden>
+ <div id="login-container" hidden>
<div id="login-status-header-container" class="login-status-row">
<div id="login-status-header"></div>
</div>
<div id="login-status-sub-header"></div>
- </button>
+ </div>
</body>
<!-- A div to hold all the templates, and in the darkness bind them. -->
diff --git a/chromium/chrome/browser/resources/ntp4/new_tab.js b/chromium/chrome/browser/resources/ntp4/new_tab.js
index e77f4e8a0e9..4c1ac76f31e 100644
--- a/chromium/chrome/browser/resources/ntp4/new_tab.js
+++ b/chromium/chrome/browser/resources/ntp4/new_tab.js
@@ -137,8 +137,6 @@ cr.define('ntp', function() {
function() { chrome.send('onLearnMore'); });
}
}
- if (loadTimeData.getBoolean('isDiscoveryInNTPEnabled'))
- sectionsToWaitFor++;
measureNavDots();
// Load the current theme colors.
@@ -150,14 +148,6 @@ cr.define('ntp', function() {
notificationContainer.addEventListener(
'webkitTransitionEnd', onNotificationTransitionEnd);
- if (loadTimeData.getBoolean('showRecentlyClosed')) {
- cr.ui.decorate(getRequiredElement('recently-closed-menu-button'),
- ntp.RecentMenuButton);
- chrome.send('getRecentlyClosedTabs');
- } else {
- $('recently-closed-menu-button').hidden = true;
- }
-
if (loadTimeData.getBoolean('showOtherSessionsMenu')) {
otherSessionsButton = /** @type {!ntp.OtherSessionsMenuButton} */(
getRequiredElement('other-sessions-menu-button'));
@@ -179,21 +169,6 @@ cr.define('ntp', function() {
chrome.send('getMostVisited');
}
- if (loadTimeData.getBoolean('isDiscoveryInNTPEnabled')) {
- var suggestionsScript = document.createElement('script');
- suggestionsScript.src = 'suggestions_page.js';
- suggestionsScript.onload = function() {
- newTabView.appendTilePage(new ntp.SuggestionsPage(),
- loadTimeData.getString('suggestions'),
- false,
- (newTabView.appsPages.length > 0) ?
- newTabView.appsPages[0] : null);
- chrome.send('getSuggestions');
- cr.dispatchSimpleEvent(document, 'sectionready', true, true);
- };
- document.querySelector('head').appendChild(suggestionsScript);
- }
-
if (!loadTimeData.getBoolean('showWebStoreIcon')) {
var webStoreIcon = $('chrome-web-store-link');
// Not all versions of the NTP have a footer, so this may not exist.
@@ -257,8 +232,7 @@ cr.define('ntp', function() {
chrome.send('bubblePromoViewed');
}
- var loginContainer = getRequiredElement('login-container');
- loginContainer.addEventListener('click', showSyncLoginUI);
+ $('login-container').addEventListener('click', showSyncLoginUI);
if (loadTimeData.getBoolean('shouldShowSyncLogin'))
chrome.send('initializeSyncLogin');
@@ -298,8 +272,6 @@ cr.define('ntp', function() {
startTime = Date.now();
});
-
- cr.ui.FocusManager.disableMouseFocusOnButtons();
}
/**
@@ -440,7 +412,7 @@ cr.define('ntp', function() {
* Shows the notification bubble.
* @param {string|Node} message The notification message or node to use as
* message.
- * @param {Array.<{text: string, action: function()}>} links An array of
+ * @param {Array<{text: string, action: function()}>} links An array of
* records describing the links in the notification. Each record should
* have a 'text' attribute (the display string) and an 'action' attribute
* (a function to run when the link is activated).
@@ -549,13 +521,8 @@ cr.define('ntp', function() {
notificationContainer.hidden = true;
}
- function setRecentlyClosedTabs(dataItems) {
- $('recently-closed-menu-button').dataItems = dataItems;
- layoutFooter();
- }
-
/**
- * @param {Array.<PageData>} data
+ * @param {Array<PageData>} data
* @param {boolean} hasBlacklistedUrls
*/
function setMostVisitedPages(data, hasBlacklistedUrls) {
@@ -563,10 +530,6 @@ cr.define('ntp', function() {
cr.dispatchSimpleEvent(document, 'sectionready', true, true);
}
- function setSuggestionsPages(data, hasBlacklistedUrls) {
- newTabView.suggestionsPage.data = data;
- }
-
/**
* Set the dominant color for a node. This will be called in response to
* getFaviconDominantColor. The node represented by |id| better have a setter
@@ -590,24 +553,21 @@ cr.define('ntp', function() {
* @param {boolean} isUserSignedIn Indicates if the user is signed in or not.
*/
function updateLogin(loginHeader, loginSubHeader, iconURL, isUserSignedIn) {
- if (loginHeader || loginSubHeader) {
- $('login-container').hidden = false;
+ /** @const */ var showLogin = loginHeader || loginSubHeader;
+
+ $('login-container').hidden = !showLogin;
+ $('card-slider-frame').classList.toggle('showing-login-area', !!showLogin);
+
+ if (showLogin) {
+ // TODO(dbeam): we should use .textContent instead to mitigate XSS.
$('login-status-header').innerHTML = loginHeader;
$('login-status-sub-header').innerHTML = loginSubHeader;
- $('card-slider-frame').classList.add('showing-login-area');
-
- if (iconURL) {
- $('login-status-header-container').style.backgroundImage = url(iconURL);
- $('login-status-header-container').classList.add('login-status-icon');
- } else {
- $('login-status-header-container').style.backgroundImage = 'none';
- $('login-status-header-container').classList.remove(
- 'login-status-icon');
- }
- } else {
- $('login-container').hidden = true;
- $('card-slider-frame').classList.remove('showing-login-area');
+
+ var headerContainer = $('login-status-header-container');
+ headerContainer.classList.toggle('login-status-icon', !!iconURL);
+ headerContainer.style.backgroundImage = iconURL ? url(iconURL) : 'none';
}
+
if (shouldShowLoginBubble) {
window.setTimeout(loginBubble.show.bind(loginBubble), 0);
chrome.send('loginMessageSeen');
@@ -778,8 +738,6 @@ cr.define('ntp', function() {
setBookmarkBarAttached: setBookmarkBarAttached,
setForeignSessions: setForeignSessions,
setMostVisitedPages: setMostVisitedPages,
- setSuggestionsPages: setSuggestionsPages,
- setRecentlyClosedTabs: setRecentlyClosedTabs,
setFaviconDominantColor: setFaviconDominantColor,
showNotification: showNotification,
themeChanged: themeChanged,
diff --git a/chromium/chrome/browser/resources/ntp4/new_tab_theme.css b/chromium/chrome/browser/resources/ntp4/new_tab_theme.css
index 888eebd6a7c..a7b1888784f 100644
--- a/chromium/chrome/browser/resources/ntp4/new_tab_theme.css
+++ b/chromium/chrome/browser/resources/ntp4/new_tab_theme.css
@@ -25,8 +25,7 @@ body {
}
#attribution,
-[is='action-link'],
-.link-span {
+[is='action-link'] {
color: $21; /* COLOR_NTP_TEXT_LIGHT */
}
diff --git a/chromium/chrome/browser/resources/ntp4/other_sessions.js b/chromium/chrome/browser/resources/ntp4/other_sessions.js
index 38fec9d8752..bc118185082 100644
--- a/chromium/chrome/browser/resources/ntp4/other_sessions.js
+++ b/chromium/chrome/browser/resources/ntp4/other_sessions.js
@@ -12,7 +12,7 @@
* modifiedTime: string,
* name: string,
* tag: string,
- * windows: Array.<WindowData>}}
+ * windows: Array<WindowData>}}
* @see chrome/browser/ui/webui/ntp/foreign_session_handler.cc
*/
var SessionData;
@@ -72,8 +72,8 @@ cr.define('ntp', function() {
decorate: function() {
MenuButton.prototype.decorate.call(this);
this.menu = new Menu;
+ this.menu.menuItemSelector = '[role=menuitem]'; // before decoration
cr.ui.decorate(this.menu, Menu);
- this.menu.menuItemSelector = '[role=menuitem]';
this.menu.classList.add('footer-menu');
this.menu.addEventListener('contextmenu',
this.onContextMenu_.bind(this), true);
@@ -257,7 +257,7 @@ cr.define('ntp', function() {
* foreign sessions, or tab sync is disabled for this profile.
* |isTabSyncEnabled| makes it possible to distinguish between the cases.
*
- * @param {Array.<SessionData>} sessionList Array of objects describing the
+ * @param {Array<SessionData>} sessionList Array of objects describing the
* sessions from other devices.
* @param {boolean} isTabSyncEnabled Is tab sync enabled for this profile?
*/
diff --git a/chromium/chrome/browser/resources/ntp4/page_list_view.js b/chromium/chrome/browser/resources/ntp4/page_list_view.js
index 0fac274274d..fa1513b9bd3 100644
--- a/chromium/chrome/browser/resources/ntp4/page_list_view.js
+++ b/chromium/chrome/browser/resources/ntp4/page_list_view.js
@@ -86,12 +86,6 @@ cr.define('ntp', function() {
appsPages: undefined,
/**
- * The Suggestions page.
- * @type {!Element|undefined}
- */
- suggestionsPage: undefined,
-
- /**
* The Most Visited page.
* @type {!Element|undefined}
*/
@@ -273,11 +267,6 @@ cr.define('ntp', function() {
this.mostVisitedPage = page;
}
- if (typeof ntp.SuggestionsPage != 'undefined' &&
- page instanceof ntp.SuggestionsPage) {
- this.suggestionsPage = page;
- }
-
// If we're appending an AppsPage and it's a temporary page, animate it.
var animate = page instanceof ntp.AppsPage &&
page.classList.contains('temporary');
@@ -350,7 +339,7 @@ cr.define('ntp', function() {
* Note that calls to this function can occur at any time, not just in
* response to a getApps request. For example, when a user
* installs/uninstalls an app on another synchronized devices.
- * @param {{apps: Array.<AppInfo>, appPageNames: Array.<string>}} data
+ * @param {{apps: Array<AppInfo>, appPageNames: Array<string>}} data
* An object with all the data on available applications.
*/
getAppsCallback: function(data) {
@@ -536,19 +525,6 @@ cr.define('ntp', function() {
this.tilePages.length - 1));
this.cardSlider.setCards(Array.prototype.slice.call(this.tilePages),
pageNo);
- // The shownPage property was potentially saved from a previous webui that
- // didn't have the same set of pages as the current one. So we cascade
- // from suggestions, to most visited and then to apps because we can have
- // an page with apps only (e.g., chrome://apps) or one with only the most
- // visited, but not one with only suggestions. And we alwayd default to
- // most visited first when previously shown page is not availabel anymore.
- // If most visited isn't there either, we go to apps.
- if (this.shownPage == loadTimeData.getInteger('suggestions_page_id')) {
- if (this.suggestionsPage)
- this.cardSlider.selectCardByValue(this.suggestionsPage);
- else
- this.shownPage = loadTimeData.getInteger('most_visited_page_id');
- }
if (this.shownPage == loadTimeData.getInteger('most_visited_page_id')) {
if (this.mostVisitedPage)
this.cardSlider.selectCardByValue(this.mostVisitedPage);
@@ -692,8 +668,6 @@ cr.define('ntp', function() {
} else if (page.classList.contains('most-visited-page')) {
this.setShownPage_(
loadTimeData.getInteger('most_visited_page_id'), 0);
- } else if (page.classList.contains('suggestions-page')) {
- this.setShownPage_(loadTimeData.getInteger('suggestions_page_id'), 0);
} else {
console.error('unknown page selected');
}
diff --git a/chromium/chrome/browser/resources/ntp4/recently_closed.js b/chromium/chrome/browser/resources/ntp4/recently_closed.js
deleted file mode 100644
index cd804df0d29..00000000000
--- a/chromium/chrome/browser/resources/ntp4/recently_closed.js
+++ /dev/null
@@ -1,122 +0,0 @@
-// Copyright (c) 2012 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 The recently closed menu: button, model data, and menu.
- */
-
-cr.define('ntp', function() {
- 'use strict';
-
- /**
- * Returns the text used for a recently closed window.
- * @param {number} numTabs Number of tabs in the window.
- * @return {string} The text to use.
- */
- function formatTabsText(numTabs) {
- if (numTabs == 1)
- return loadTimeData.getString('closedwindowsingle');
- return loadTimeData.getStringF('closedwindowmultiple', numTabs);
- }
-
- var Menu = cr.ui.Menu;
- var MenuItem = cr.ui.MenuItem;
- var MenuButton = cr.ui.MenuButton;
- var RecentMenuButton = cr.ui.define('button');
-
- RecentMenuButton.prototype = {
- __proto__: MenuButton.prototype,
-
- decorate: function() {
- MenuButton.prototype.decorate.call(this);
- this.menu = new Menu;
- cr.ui.decorate(this.menu, Menu);
- this.menu.classList.add('footer-menu');
- document.body.appendChild(this.menu);
-
- this.needsRebuild_ = true;
- this.anchorType = cr.ui.AnchorType.ABOVE;
- this.invertLeftRight = true;
- },
-
- /**
- * Shows the menu, first rebuilding it if necessary.
- * TODO(estade): the right of the menu should align with the right of the
- * button.
- * @override
- */
- showMenu: function(shouldSetFocus) {
- if (this.needsRebuild_) {
- this.menu.textContent = '';
- this.dataItems_.forEach(this.addItem_, this);
- this.needsRebuild_ = false;
- }
-
- MenuButton.prototype.showMenu.apply(this, arguments);
- },
-
- /**
- * Sets the menu model data.
- * @param {Array} dataItems Array of objects that describe the apps.
- */
- set dataItems(dataItems) {
- this.dataItems_ = dataItems;
- this.needsRebuild_ = true;
- this.hidden = !dataItems.length;
- },
-
- /**
- * Adds an app to the menu.
- * @param {Object} data An object encapsulating all data about the app.
- * @private
- */
- addItem_: function(data) {
- var isWindow = data.type == 'window';
- var a = this.ownerDocument.createElement('a');
- a.className = 'footer-menu-item';
- if (isWindow) {
- a.href = '';
- a.classList.add('recent-window');
- a.textContent = formatTabsText(data.tabs.length);
- a.title = data.tabs.map(function(tab) { return tab.title; }).join('\n');
- } else {
- a.href = data.url;
- a.style.backgroundImage = getFaviconImageSet(data.url);
- a.textContent = data.title;
- }
-
- function onActivated(e) {
- ntp.logTimeToClick('RecentlyClosed');
- chrome.send('recordAppLaunchByURL',
- [encodeURIComponent(data.url),
- ntp.APP_LAUNCH.NTP_RECENTLY_CLOSED]);
- var index = Array.prototype.indexOf.call(a.parentNode.children, a);
- var orig = e.originalEvent;
- var button = 0;
- if (orig instanceof MouseEvent)
- button = orig.button;
- var params = [data.sessionId,
- index,
- button,
- orig.altKey,
- orig.ctrlKey,
- orig.metaKey,
- orig.shiftKey];
- chrome.send('reopenTab', params);
-
- e.preventDefault();
- e.stopPropagation();
- }
- a.addEventListener('activate', onActivated);
- a.addEventListener('click', function(e) { e.preventDefault(); });
-
- this.menu.appendChild(a);
- cr.ui.decorate(a, MenuItem);
- },
- };
-
- return {
- RecentMenuButton: RecentMenuButton,
- };
-});
diff --git a/chromium/chrome/browser/resources/ntp4/suggestions_page.css b/chromium/chrome/browser/resources/ntp4/suggestions_page.css
deleted file mode 100644
index 747d206777b..00000000000
--- a/chromium/chrome/browser/resources/ntp4/suggestions_page.css
+++ /dev/null
@@ -1,109 +0,0 @@
-/* Copyright (c) 2012 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. */
-
-.suggestions {
- position: absolute;
- z-index: 0;
-}
-
-.suggestions {
- -webkit-box-orient: vertical;
- display: -webkit-box;
- position: absolute;
- text-decoration: none;
-}
-
-.suggestions:focus {
- outline: none;
-}
-
-.suggestions .close-button {
- -webkit-transition: opacity 150ms;
- opacity: 0;
- position: absolute;
- right: 0;
- top: 0;
- z-index: 5;
-}
-
-html[dir=rtl] .suggestions .close-button {
- left: 0;
- right: auto;
-}
-
-.suggestions:hover .close-button {
- -webkit-transition-delay: 500ms;
- opacity: 1;
-}
-
-.suggestions .close-button:hover {
- -webkit-transition: none;
-}
-
-.suggestions .favicon {
- -webkit-margin-start: 5px;
- background: no-repeat left 50%;
- bottom: 7px;
- box-sizing: border-box;
- display: block;
- height: 16px;
- position: absolute;
- width: 16px;
-}
-
-html[dir='rtl'] .suggestions .favicon {
- background-position-x: right;
-}
-
-.suggestions .color-stripe {
- border-bottom-left-radius: 3px 3px;
- border-bottom-right-radius: 3px 3px;
- /* Matches height of title plus height of score. */
- bottom: 36px;
- height: 3px;
- position: absolute;
- width: 100%;
- z-index: 10;
-}
-
-.suggestions .title {
- display: block;
- height: 18px;
- overflow: hidden;
- text-align: center;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.suggestions .score {
- display: block;
- height: 18px;
- overflow: hidden;
- text-align: center;
-}
-
-.suggestions:focus .thumbnail,
-.suggestions:hover .thumbnail {
- opacity: 0.95;
-}
-
-.suggestions:focus .thumbnail-shield,
-.suggestions:hover .thumbnail-shield,
-.suggestions:active .thumbnail-shield {
- background: -webkit-linear-gradient(rgba(255, 255, 255, 0),
- rgba(255, 255, 255, 0) 80%,
- rgba(255, 255, 255, 0.9));
-}
-
-/* The thumbnail gets lighter when clicked, but not when the click is on the
- * close button. */
-.suggestions:active .close-button:not(:active) + .thumbnail {
- opacity: 0.9;
-}
-
-/* The thumbnail gets a shadow when clicked, but not when the click is on the
- * close button. */
-.suggestions:active .close-button:not(:active) + .thumbnail .thumbnail-shield {
- -webkit-box-shadow: inset 0 1px 10px rgba(0, 0, 0, 0.2);
-}
diff --git a/chromium/chrome/browser/resources/ntp4/suggestions_page.js b/chromium/chrome/browser/resources/ntp4/suggestions_page.js
deleted file mode 100644
index 6dc5ecdd14b..00000000000
--- a/chromium/chrome/browser/resources/ntp4/suggestions_page.js
+++ /dev/null
@@ -1,478 +0,0 @@
-// Copyright (c) 2012 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.
-
-cr.define('ntp', function() {
- 'use strict';
-
- var TilePage = ntp.TilePage;
-
- /**
- * A counter for generating unique tile IDs.
- */
- var tileID = 0;
-
- /**
- * Creates a new Suggestions page object for tiling.
- * @constructor
- * @extends {HTMLAnchorElement}
- */
- function Suggestion() {
- var el = cr.doc.createElement('a');
- el.__proto__ = Suggestion.prototype;
- el.initialize();
-
- return el;
- }
-
- Suggestion.prototype = {
- __proto__: HTMLAnchorElement.prototype,
-
- initialize: function() {
- this.reset();
-
- this.addEventListener('click', this.handleClick_);
- this.addEventListener('keydown', this.handleKeyDown_);
- },
-
- get index() {
- assert(this.tile);
- return this.tile.index;
- },
-
- get data() {
- return this.data_;
- },
-
- /**
- * Clears the DOM hierarchy for this node, setting it back to the default
- * for a blank thumbnail. TODO(georgey) make it a template.
- */
- reset: function() {
- this.className = 'suggestions filler real';
- this.innerHTML =
- '<span class="thumbnail-wrapper fills-parent">' +
- '<div class="close-button"></div>' +
- '<span class="thumbnail fills-parent">' +
- // thumbnail-shield provides a gradient fade effect.
- '<div class="thumbnail-shield fills-parent"></div>' +
- '</span>' +
- '<span class="favicon"></span>' +
- '</span>' +
- '<div class="color-stripe"></div>' +
- '<span class="title"></span>' +
- '<span class="score"></span>';
-
- this.querySelector('.close-button').title =
- loadTimeData.getString('removethumbnailtooltip');
-
- this.tabIndex = -1;
- this.data_ = null;
- this.removeAttribute('id');
- this.title = '';
- },
-
- /**
- * Update the appearance of this tile according to |data|.
- * @param {{score: number, url: string, title: string, direction: string,
- * filler: boolean}} data A dictionary of relevant data for the page.
- * @see chrome/browser/ui/webui/ntp/suggestions_source_top_sites.cc
- * @see chrome/browser/ui/webui/ntp/most_visited_handler.cc
- */
- updateForData: function(data) {
- if (this.classList.contains('blacklisted') && data) {
- // Animate appearance of new tile.
- this.classList.add('new-tile-contents');
- }
- this.classList.remove('blacklisted');
-
- if (!data || data.filler) {
- if (this.data_)
- this.reset();
- return;
- }
-
- var id = tileID++;
- this.id = 'suggestions-tile-' + id;
- this.data_ = data;
- this.classList.add('focusable');
-
- var faviconDiv = this.querySelector('.favicon');
- var faviconUrl = 'chrome://favicon/size/16@1x/' + data.url;
- faviconDiv.style.backgroundImage = url(faviconUrl);
- chrome.send('getFaviconDominantColor', [faviconUrl, this.id]);
-
- var title = this.querySelector('.title');
- title.textContent = data.title;
- title.dir = data.direction;
-
- var score = this.querySelector('.score');
- score.textContent = data.score;
-
- // Sets the tooltip.
- this.title = data.title;
-
- var thumbnailUrl;
- thumbnailUrl = data.urlImage ? data.urlImage :
- 'chrome://thumb/' + data.url;
-
- this.querySelector('.thumbnail').style.backgroundImage =
- url(thumbnailUrl);
-
- this.href = data.url;
-
- this.classList.remove('filler');
- },
-
- /**
- * Sets the color of the favicon dominant color bar.
- * @param {string} color The css-parsable value for the color.
- */
- set stripeColor(color) {
- this.querySelector('.color-stripe').style.backgroundColor = color;
- },
-
- /**
- * Handles a click on the tile.
- * @param {Event} e The click event.
- */
- handleClick_: function(e) {
- if (e.target.classList.contains('close-button')) {
- this.blacklist_();
- e.preventDefault();
- } else {
- // Records the index of this tile.
- chrome.send('metricsHandler:recordInHistogram',
- ['NewTabPage.SuggestedSite', this.index, 8]);
- chrome.send('suggestedSitesAction',
- [ntp.NtpFollowAction.CLICKED_TILE]);
- }
- },
-
- /**
- * Allow blacklisting suggestions site using the keyboard.
- * @param {Event} e The keydown event.
- */
- handleKeyDown_: function(e) {
- if (!cr.isMac && e.keyCode == 46 || // Del
- cr.isMac && e.metaKey && e.keyCode == 8) { // Cmd + Backspace
- this.blacklist_();
- }
- },
-
- /**
- * Permanently removes a page from Suggestions.
- */
- blacklist_: function() {
- this.showUndoNotification_();
- chrome.send('blacklistURLFromSuggestions', [this.data_.url]);
- this.reset();
- chrome.send('getSuggestions');
- this.classList.add('blacklisted');
- },
-
- /**
- * Shows notification that you can undo blacklisting.
- */
- showUndoNotification_: function() {
- var data = this.data_;
- var self = this;
- var doUndo = function() {
- chrome.send('removeURLsFromSuggestionsBlacklist', [data.url]);
- self.updateForData(data);
- };
-
- var undo = {
- action: doUndo,
- text: loadTimeData.getString('undothumbnailremove'),
- };
-
- var undoAll = {
- action: function() {
- chrome.send('clearSuggestionsURLsBlacklist');
- },
- text: loadTimeData.getString('restoreThumbnailsShort'),
- };
-
- ntp.showNotification(
- loadTimeData.getString('thumbnailremovednotification'),
- [undo, undoAll]);
- },
-
- /**
- * Set the size and position of the suggestions tile.
- * @param {number} size The total size of |this|.
- * @param {number} x The x-position.
- * @param {number} y The y-position.
- */
- setBounds: function(size, x, y) {
- this.style.width = size + 'px';
- this.style.height = heightForWidth(size) + 'px';
-
- this.style.left = x + 'px';
- this.style.right = x + 'px';
- this.style.top = y + 'px';
- },
-
- /**
- * Returns whether this element can be 'removed' from chrome (i.e. whether
- * the user can drag it onto the trash and expect something to happen).
- * @return {boolean} True, since suggestions pages can always be
- * blacklisted.
- */
- canBeRemoved: function() {
- return true;
- },
-
- /**
- * Removes this element from chrome, i.e. blacklists it.
- */
- removeFromChrome: function() {
- this.blacklist_();
- this.parentNode.classList.add('finishing-drag');
- },
-
- /**
- * Called when a drag of this tile has ended (after all animations have
- * finished).
- */
- finalizeDrag: function() {
- this.parentNode.classList.remove('finishing-drag');
- },
-
- /**
- * Called when a drag is starting on the tile. Updates dataTransfer with
- * data for this tile (for dragging outside of the NTP).
- * @param {DataTransfer} dataTransfer The drag event data store.
- */
- setDragData: function(dataTransfer) {
- dataTransfer.setData('Text', this.data_.title);
- dataTransfer.setData('URL', this.data_.url);
- },
- };
-
- var suggestionsPageGridValues = {
- // The fewest tiles we will show in a row.
- minColCount: 2,
- // The suggestions we will show in a row.
- maxColCount: 4,
-
- // The smallest a tile can be.
- minTileWidth: 122,
- // The biggest a tile can be. 212 (max thumbnail width) + 2.
- maxTileWidth: 214,
-
- // The padding between tiles, as a fraction of the tile width.
- tileSpacingFraction: 1 / 8,
- };
- TilePage.initGridValues(suggestionsPageGridValues);
-
- /**
- * Calculates the height for a Suggestion tile for a given width. The size
- * is based on the thumbnail, which should have a 212:132 ratio.
- * @return {number} The height.
- */
- function heightForWidth(width) {
- // The 2s are for borders, the 36 is for the title and score.
- return (width - 2) * 132 / 212 + 2 + 36;
- }
-
- var THUMBNAIL_COUNT = 8;
-
- /**
- * Creates a new SuggestionsPage object.
- * @constructor
- * @extends {TilePage}
- */
- function SuggestionsPage() {
- var el = new TilePage(suggestionsPageGridValues);
- el.__proto__ = SuggestionsPage.prototype;
- el.initialize();
-
- return el;
- }
-
- SuggestionsPage.prototype = {
- __proto__: TilePage.prototype,
-
- initialize: function() {
- this.classList.add('suggestions-page');
- this.data_ = null;
- this.suggestionsTiles_ = this.getElementsByClassName('suggestions real');
-
- this.addEventListener('carddeselected', this.handleCardDeselected_);
- this.addEventListener('cardselected', this.handleCardSelected_);
- },
-
- /**
- * Create blank (filler) tiles.
- * @private
- */
- createTiles_: function() {
- for (var i = 0; i < THUMBNAIL_COUNT; i++) {
- this.appendTile(new Suggestion(), false);
- }
- },
-
- /**
- * Update the tiles after a change to |this.data_|.
- */
- updateTiles_: function() {
- for (var i = 0; i < THUMBNAIL_COUNT; i++) {
- var page = this.data_[i];
- var tile = this.suggestionsTiles_[i];
-
- if (i >= this.data_.length)
- tile.reset();
- else
- tile.updateForData(page);
- }
- },
-
- /**
- * Handles the 'card deselected' event (i.e. the user clicked to another
- * pane).
- * @param {Event} e The CardChanged event.
- */
- handleCardDeselected_: function(e) {
- if (!document.documentElement.classList.contains('starting-up')) {
- chrome.send('suggestedSitesAction',
- [ntp.NtpFollowAction.CLICKED_OTHER_NTP_PANE]);
- }
- },
-
- /**
- * Handles the 'card selected' event (i.e. the user clicked to select the
- * Suggested pane).
- * @param {Event} e The CardChanged event.
- */
- handleCardSelected_: function(e) {
- if (!document.documentElement.classList.contains('starting-up'))
- chrome.send('suggestedSitesSelected');
- },
-
- /**
- * Array of suggestions data objects.
- * @type {Array}
- */
- get data() {
- return this.data_;
- },
- set data(data) {
- var startTime = Date.now();
-
- // The first time data is set, create the tiles.
- if (!this.data_) {
- this.createTiles_();
- this.data_ = data.slice(0, THUMBNAIL_COUNT);
- } else {
- this.data_ = refreshData(this.data_, data);
- }
-
- this.updateTiles_();
- this.updateFocusableElement();
- logEvent('suggestions.layout: ' + (Date.now() - startTime));
- },
-
- /** @override */
- shouldAcceptDrag: function(e) {
- return false;
- },
-
- /** @override */
- heightForWidth: heightForWidth,
- };
-
- /**
- * Executed once the NTP has loaded. Checks if the Suggested pane is
- * shown or not. If it is shown, the 'suggestedSitesSelected' message is sent
- * to the C++ code, to record the fact that the user has seen this pane.
- */
- SuggestionsPage.onLoaded = function() {
- if (ntp.getCardSlider() &&
- ntp.getCardSlider().currentCardValue &&
- ntp.getCardSlider().currentCardValue.classList
- .contains('suggestions-page')) {
- chrome.send('suggestedSitesSelected');
- }
- };
-
- /**
- * We've gotten additional data for Suggestions page. Update our old data with
- * the new data. The ordering of the new data is not important, except when a
- * page is pinned. Thus we try to minimize re-ordering.
- * @param {Array} oldData The current Suggestions page list.
- * @param {Array} newData The new Suggestions page list.
- * @return {Array} The merged page list that should replace the current page
- * list.
- */
- function refreshData(oldData, newData) {
- oldData = oldData.slice(0, THUMBNAIL_COUNT);
- newData = newData.slice(0, THUMBNAIL_COUNT);
-
- // Copy over pinned sites directly.
- for (var i = 0; i < newData.length; i++) {
- if (newData[i].pinned) {
- oldData[i] = newData[i];
- // Mark the entry as 'updated' so we don't try to update again.
- oldData[i].updated = true;
- // Mark the newData page as 'used' so we don't try to re-use it.
- newData[i].used = true;
- }
- }
-
- // Look through old pages; if they exist in the newData list, keep them
- // where they are.
- for (var i = 0; i < oldData.length; i++) {
- if (!oldData[i] || oldData[i].updated)
- continue;
-
- for (var j = 0; j < newData.length; j++) {
- if (newData[j].used)
- continue;
-
- if (newData[j].url == oldData[i].url) {
- // The background image and other data may have changed.
- oldData[i] = newData[j];
- oldData[i].updated = true;
- newData[j].used = true;
- break;
- }
- }
- }
-
- // Look through old pages that haven't been updated yet; replace them.
- for (var i = 0; i < oldData.length; i++) {
- if (oldData[i] && oldData[i].updated)
- continue;
-
- for (var j = 0; j < newData.length; j++) {
- if (newData[j].used)
- continue;
-
- oldData[i] = newData[j];
- oldData[i].updated = true;
- newData[j].used = true;
- break;
- }
-
- if (oldData[i] && !oldData[i].updated)
- oldData[i] = null;
- }
-
- // Clear 'updated' flags so this function will work next time it's called.
- for (var i = 0; i < THUMBNAIL_COUNT; i++) {
- if (oldData[i])
- oldData[i].updated = false;
- }
-
- return oldData;
- }
-
- return {
- SuggestionsPage: SuggestionsPage,
- refreshData: refreshData,
- };
-});
-
-document.addEventListener('ntpLoaded', ntp.SuggestionsPage.onLoaded);
diff --git a/chromium/chrome/browser/resources/ntp4/tile_page.js b/chromium/chrome/browser/resources/ntp4/tile_page.js
index 0f7d430072b..bc773672823 100644
--- a/chromium/chrome/browser/resources/ntp4/tile_page.js
+++ b/chromium/chrome/browser/resources/ntp4/tile_page.js
@@ -287,7 +287,7 @@ cr.define('ntp', function() {
onDragCloneTransitionEnd_: function(e) {
if (this.classList.contains('dragging') &&
(e.propertyName == 'left' || e.propertyName == 'top' ||
- e.propertyName == '-webkit-transform')) {
+ e.propertyName == 'transform')) {
this.finalizeDrag_();
}
},
@@ -1132,7 +1132,9 @@ cr.define('ntp', function() {
* @param {Event} e The mousewheel event.
*/
handleMouseWheel: function(e) {
- if (e.wheelDeltaY == 0)
+ // The ctrl-wheel should triggle the zoom in/out actions in Chromium for
+ // all pages.
+ if (e.wheelDeltaY == 0 || e.ctrlKey)
return false;
this.content_.scrollTop -= e.wheelDeltaY / 3;
diff --git a/chromium/chrome/browser/resources/omnibox/omnibox.html b/chromium/chrome/browser/resources/omnibox/omnibox.html
index df2c0acff1f..aff0ab51204 100644
--- a/chromium/chrome/browser/resources/omnibox/omnibox.html
+++ b/chromium/chrome/browser/resources/omnibox/omnibox.html
@@ -1,8 +1,9 @@
-<!DOCTYPE HTML>
+<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Omnibox Debug Page</title>
+ <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="omnibox.css">
<script src="chrome://resources/js/cr.js"></script>
<script src="chrome://resources/js/util.js"></script>
diff --git a/chromium/chrome/browser/resources/omnibox/omnibox.js b/chromium/chrome/browser/resources/omnibox/omnibox.js
index 8ce8fe00647..d2758490c01 100644
--- a/chromium/chrome/browser/resources/omnibox/omnibox.js
+++ b/chromium/chrome/browser/resources/omnibox/omnibox.js
@@ -17,13 +17,14 @@
* are available, the Javascript formats them and displays them.
*/
define('main', [
+ 'mojo/public/js/bindings',
+ 'mojo/public/js/core',
'mojo/public/js/connection',
'chrome/browser/ui/webui/omnibox/omnibox.mojom',
'content/public/renderer/service_provider',
-], function(connector, browser, serviceProvider) {
+], function(bindings, core, connection, browser, serviceProvider) {
'use strict';
- var connection;
var page;
/**
@@ -71,12 +72,17 @@ define('main', [
// - forth element: the value of prefer-keyword
// - fifth element: the value of page-classification
cursorPositionUsed = $('input-text').selectionEnd;
+ var pipe = core.createMessagePipe();
+ var stub = connection.bindHandleToStub(pipe.handle0, browser.OmniboxPage);
+ bindings.StubBindings(stub).delegate = page;
+ page.stub_ = stub;
page.browser_.startOmniboxQuery(
$('input-text').value,
cursorPositionUsed,
$('prevent-inline-autocomplete').checked,
$('prefer-keyword').checked,
- parseInt($('page-classification').value));
+ parseInt($('page-classification').value),
+ pipe.handle1);
// Cancel the submit action. i.e., don't submit the form. (We handle
// display the results solely with Javascript.)
event.preventDefault();
@@ -110,7 +116,7 @@ define('main', [
* properties to output in what order. This is an array of
* PresentationInfoRecord() objects; for details see that
* function.
- * @type {Array.<Object>}
+ * @type {Array<Object>}
* @const
*/
var PROPERTY_OUTPUT_ORDER = [
@@ -421,7 +427,6 @@ define('main', [
function OmniboxPageImpl(browser) {
this.browser_ = browser;
- page = this;
initialize();
}
@@ -434,10 +439,10 @@ define('main', [
};
return function() {
- connection = new connector.Connection(
+ var browserProxy = connection.bindHandleToProxy(
serviceProvider.connectToService(
browser.OmniboxUIHandlerMojo.name),
- OmniboxPageImpl,
- browser.OmniboxUIHandlerMojo.proxyClass);
+ browser.OmniboxUIHandlerMojo);
+ page = new OmniboxPageImpl(browserProxy);
};
});
diff --git a/chromium/chrome/browser/resources/options/autofill_edit_address_overlay.js b/chromium/chrome/browser/resources/options/autofill_edit_address_overlay.js
index b5aec2d1561..5dbe25e336c 100644
--- a/chromium/chrome/browser/resources/options/autofill_edit_address_overlay.js
+++ b/chromium/chrome/browser/resources/options/autofill_edit_address_overlay.js
@@ -242,12 +242,19 @@ cr.define('options', function() {
inputFields['state'] || '',
inputFields['postalCode'] || '',
inputFields['sortingCode'] || '',
- inputFields['country'] || '',
+ inputFields['country'] || loadTimeData.getString('defaultCountryCode'),
inputFields['phone'] || [],
inputFields['email'] || [],
this.languageCode_,
];
chrome.send('setAddress', address);
+
+ // If the GUID is empty, this form is being used to add a new address,
+ // rather than edit an existing one.
+ if (!this.guid_.length) {
+ chrome.send('coreOptionsUserMetricsAction',
+ ['Options_AutofillAddressAdded']);
+ }
},
/**
@@ -353,7 +360,7 @@ cr.define('options', function() {
* Takes a snapshot of the input values, clears the input values, loads the
* address input layout from |input.components|, restores the input values
* from snapshot, and stores the |input.languageCode| for the address.
- * @param {{languageCode: string, components: Array.<Array.<Object>>}} input
+ * @param {{languageCode: string, components: Array<Array<Object>>}} input
* Info about how to layout inputs fields in this dialog.
* @private
*/
@@ -373,7 +380,7 @@ cr.define('options', function() {
/**
* Clears address inputs and rebuilds the input fields according to
* |components|.
- * @param {Array.<Array.<Object>>} components A list of information about
+ * @param {Array<Array<Object>>} components A list of information about
* each input field.
* @private
*/
diff --git a/chromium/chrome/browser/resources/options/autofill_edit_creditcard_overlay.js b/chromium/chrome/browser/resources/options/autofill_edit_creditcard_overlay.js
index d3e82cbc458..369b7d941ff 100644
--- a/chromium/chrome/browser/resources/options/autofill_edit_creditcard_overlay.js
+++ b/chromium/chrome/browser/resources/options/autofill_edit_creditcard_overlay.js
@@ -74,6 +74,13 @@ cr.define('options', function() {
creditCard[3] = $('expiration-month').value;
creditCard[4] = $('expiration-year').value;
chrome.send('setCreditCard', creditCard);
+
+ // If the GUID is empty, this form is being used to add a new card,
+ // rather than edit an existing one.
+ if (!this.guid_.length) {
+ chrome.send('coreOptionsUserMetricsAction',
+ ['Options_AutofillCreditCardAdded']);
+ }
},
/**
@@ -96,7 +103,8 @@ cr.define('options', function() {
* @private
*/
inputFieldChanged_: function(opt_event) {
- var disabled = !$('name-on-card').value && !$('credit-card-number').value;
+ var disabled = !$('name-on-card').value.trim() &&
+ !$('credit-card-number').value.trim();
$('autofill-edit-credit-card-apply-button').disabled = disabled;
},
diff --git a/chromium/chrome/browser/resources/options/autofill_edit_overlay.css b/chromium/chrome/browser/resources/options/autofill_edit_overlay.css
index 24500e6a2fb..9f78131956a 100644
--- a/chromium/chrome/browser/resources/options/autofill_edit_overlay.css
+++ b/chromium/chrome/browser/resources/options/autofill_edit_overlay.css
@@ -35,11 +35,11 @@
}
#autofill-edit-address-overlay list div.static-text {
- -webkit-border-radius: 2px;
-webkit-box-flex: 1;
-webkit-padding-end: 4px;
-webkit-padding-start: 4px;
border: 1px solid darkGray;
+ border-radius: 2px;
/* Border should go "inside" the height. */
box-sizing: border-box;
/* Set the line-height and min-height to match the height of an input element,
@@ -48,6 +48,17 @@
line-height: 2em;
}
+#autofill-edit-address-overlay list:not([has-element-focus]) >
+ [selected]:not(:hover) {
+ background-color: transparent;
+}
+
+#autofill-edit-address-overlay list:not([has-element-focus]) > *:not(:hover)
+ .row-delete-button {
+ opacity: 0;
+ pointer-events: none;
+}
+
:-webkit-any(#autofill-edit-credit-card-overlay, #autofill-edit-address-overlay)
.settings-row label > :-webkit-any(input, select, textarea, list) {
margin-top: 4px;
diff --git a/chromium/chrome/browser/resources/options/autofill_options.css b/chromium/chrome/browser/resources/options/autofill_options.css
index 1e08bfb5c13..815c58c4079 100644
--- a/chromium/chrome/browser/resources/options/autofill_options.css
+++ b/chromium/chrome/browser/resources/options/autofill_options.css
@@ -11,21 +11,26 @@
}
.autofill-list-item {
- -webkit-box-flex: 1;
-webkit-padding-start: 8px;
+ max-width: 50%;
overflow: hidden;
text-overflow: ellipsis;
}
-.autofill-list-item + img {
- -webkit-padding-end: 20px;
- vertical-align: top;
+.autofill-list-item + .deemphasized {
+ -webkit-box-flex: 1;
+ overflow: hidden;
+ text-overflow: ellipsis;
}
#autofill-options > div:last-child {
margin-top: 15px;
}
+#autofill-options .deemphasized {
+ color: #777;
+}
+
#autofill-options > div.settings-list > div:last-child {
border-top: 1px solid #d9d9d9;
padding: 5px 10px;
@@ -37,11 +42,17 @@
}
#autofill-options .list-inline-button {
+ -webkit-margin-start: 12px;
margin-top: 0;
vertical-align: top;
}
#autofill-options div[role='listitem']:not(:hover):not([selected])
- .list-inline-button {
+ .hide-until-hover {
display: none;
-} \ No newline at end of file
+}
+
+#autofill-options div[role='listitem']:-webkit-any(:hover,[selected])
+ .hides-on-hover {
+ display: none;
+}
diff --git a/chromium/chrome/browser/resources/options/autofill_options.html b/chromium/chrome/browser/resources/options/autofill_options.html
index 28374bc2057..bb7b70b5b33 100644
--- a/chromium/chrome/browser/resources/options/autofill_options.html
+++ b/chromium/chrome/browser/resources/options/autofill_options.html
@@ -2,6 +2,14 @@
<div class="close-button"></div>
<h1 i18n-content="autofillOptionsPage"></h1>
<div class="content-area">
+ <!-- TODO(estade): This checkbox shouldn't show if you're not signed into
+ sync. Or perhaps it should show with a link to go sign in with sync?
+ -->
+ <div id="autofill-wallet-setting-area" class="checkbox">
+ <label>
+ <input pref="autofill.wallet_import_enabled" type="checkbox">
+ <span i18n-content="autofillWalletOption"></span>
+ </div>
<if expr="is_macosx">
<div class="checkbox">
<label>
@@ -20,6 +28,7 @@
</button>
</div>
</div>
+
<h3 i18n-content="autofillCreditCards"></h3>
<div class="settings-list">
<list id="creditcard-list"></list>
@@ -29,6 +38,7 @@
</div>
</div>
</div>
+
<div class="action-area">
<a id="autofill-help" target="_blank" i18n-values="href:helpUrl"
i18n-content="helpButton">
diff --git a/chromium/chrome/browser/resources/options/autofill_options.js b/chromium/chrome/browser/resources/options/autofill_options.js
index 98c78b3c71b..d90469280d8 100644
--- a/chromium/chrome/browser/resources/options/autofill_options.js
+++ b/chromium/chrome/browser/resources/options/autofill_options.js
@@ -83,6 +83,15 @@ cr.define('options', function() {
};
</if>
+ $('autofill-help').onclick = function(event) {
+ chrome.send('coreOptionsUserMetricsAction',
+ ['Options_AutofillShowAbout']);
+ return true; // Always follow the href
+ };
+
+ this.walletIntegrationAvailableStateChanged_(
+ loadTimeData.getBoolean('autofillWalletIntegrationAvailable'));
+
// TODO(jhawkins): What happens when Autofill is disabled whilst on the
// Autofill options page?
},
@@ -156,32 +165,13 @@ cr.define('options', function() {
/**
* Removes the Autofill address or credit card represented by |guid|.
* @param {string} guid The GUID of the address to remove.
+ * @param {string=} metricsAction The name of the action to log for metrics.
* @private
*/
- removeData_: function(guid) {
+ removeData_: function(guid, metricsAction) {
chrome.send('removeData', [guid]);
- },
-
- /**
- * Requests profile data for the address represented by |guid| from the
- * PersonalDataManager. Once the data is loaded, the AutofillOptionsHandler
- * calls showEditAddressOverlay().
- * @param {string} guid The GUID of the address to edit.
- * @private
- */
- loadAddressEditor_: function(guid) {
- chrome.send('loadAddressEditor', [guid]);
- },
-
- /**
- * Requests profile data for the credit card represented by |guid| from the
- * PersonalDataManager. Once the data is loaded, the AutofillOptionsHandler
- * calls showEditCreditCardOverlay().
- * @param {string} guid The GUID of the credit card to edit.
- * @private
- */
- loadCreditCardEditor_: function(guid) {
- chrome.send('loadCreditCardEditor', [guid]);
+ if (metricsAction)
+ chrome.send('coreOptionsUserMetricsAction', [metricsAction]);
},
/**
@@ -210,6 +200,16 @@ cr.define('options', function() {
AutofillEditCreditCardOverlay.loadCreditCard(creditCard);
PageManager.showPageByName('autofillEditCreditCard');
},
+
+ /**
+ * Toggles the visibility of the Wallet integration checkbox.
+ * @param {boolean} available Whether the user has the option of using
+ * Wallet data.
+ * @private
+ */
+ walletIntegrationAvailableStateChanged_: function(available) {
+ $('autofill-wallet-setting-area').hidden = !available;
+ },
};
AutofillOptions.setAddressList = function(entries) {
@@ -220,22 +220,19 @@ cr.define('options', function() {
AutofillOptions.getInstance().setCreditCardList_(entries);
};
- AutofillOptions.removeData = function(guid) {
- AutofillOptions.getInstance().removeData_(guid);
- };
-
- AutofillOptions.loadAddressEditor = function(guid) {
- AutofillOptions.getInstance().loadAddressEditor_(guid);
- };
-
- AutofillOptions.loadCreditCardEditor = function(guid) {
- AutofillOptions.getInstance().loadCreditCardEditor_(guid);
+ AutofillOptions.removeData = function(guid, metricsAction) {
+ AutofillOptions.getInstance().removeData_(guid, metricsAction);
};
AutofillOptions.editAddress = function(address) {
AutofillOptions.getInstance().showEditAddressOverlay_(address);
};
+ AutofillOptions.walletIntegrationAvailableStateChanged = function(available) {
+ AutofillOptions.getInstance().
+ walletIntegrationAvailableStateChanged_(available);
+ };
+
/**
* @param {CreditCardData} creditCard
*/
diff --git a/chromium/chrome/browser/resources/options/autofill_options_list.js b/chromium/chrome/browser/resources/options/autofill_options_list.js
index 565a62e6d35..7db223b43c2 100644
--- a/chromium/chrome/browser/resources/options/autofill_options_list.js
+++ b/chromium/chrome/browser/resources/options/autofill_options_list.js
@@ -2,6 +2,18 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+/**
+ * @typedef {{
+ * guid: string,
+ * label: string,
+ * sublabel: string,
+ * isLocal: boolean,
+ * isCached: boolean
+ * }}
+ * @see chrome/browser/ui/webui/options/autofill_options_handler.cc
+ */
+var AutofillEntityMetadata;
+
cr.define('options.autofillOptions', function() {
/** @const */ var DeletableItem = options.DeletableItem;
/** @const */ var DeletableItemList = options.DeletableItemList;
@@ -11,13 +23,14 @@ cr.define('options.autofillOptions', function() {
/**
* @return {!HTMLButtonElement}
*/
- function AutofillEditProfileButton(guid, edit) {
+ function AutofillEditProfileButton(edit) {
var editButtonEl = /** @type {HTMLButtonElement} */(
document.createElement('button'));
- editButtonEl.className = 'list-inline-button custom-appearance';
+ editButtonEl.className =
+ 'list-inline-button hide-until-hover custom-appearance';
editButtonEl.textContent =
loadTimeData.getString('autofillEditProfileButton');
- editButtonEl.onclick = function(e) { edit(guid); };
+ editButtonEl.onclick = edit;
editButtonEl.onmousedown = function(e) {
// Don't select the row when clicking the button.
@@ -29,17 +42,26 @@ cr.define('options.autofillOptions', function() {
return editButtonEl;
}
+ /** @return {!Element} */
+ function CreateGoogleAccountLabel() {
+ var label = document.createElement('div');
+ label.className = 'deemphasized hides-on-hover';
+ label.textContent = loadTimeData.getString('autofillFromGoogleAccount');
+ return label;
+ }
+
/**
* Creates a new address list item.
- * @param {Array} entry An array of the form [guid, label].
* @constructor
+ * @param {AutofillEntityMetadata} metadata Details about an address profile.
* @extends {options.DeletableItem}
+ * @see chrome/browser/ui/webui/options/autofill_options_handler.cc
*/
- function AddressListItem(entry) {
+ function AddressListItem(metadata) {
var el = cr.doc.createElement('div');
- el.guid = entry[0];
- el.label = entry[1];
el.__proto__ = AddressListItem.prototype;
+ /** @private */
+ el.metadata_ = metadata;
el.decorate();
return el;
@@ -52,33 +74,52 @@ cr.define('options.autofillOptions', function() {
decorate: function() {
DeletableItem.prototype.decorate.call(this);
- // The stored label.
var label = this.ownerDocument.createElement('div');
label.className = 'autofill-list-item';
- label.textContent = this.label;
+ label.textContent = this.metadata_.label;
this.contentElement.appendChild(label);
+ var sublabel = this.ownerDocument.createElement('div');
+ sublabel.className = 'deemphasized';
+ sublabel.textContent = this.metadata_.sublabel;
+ this.contentElement.appendChild(sublabel);
+
+ if (!this.metadata_.isLocal) {
+ this.deletable = false;
+ this.contentElement.appendChild(CreateGoogleAccountLabel());
+ }
+
// The 'Edit' button.
+ var metadata = this.metadata_;
var editButtonEl = AutofillEditProfileButton(
- this.guid,
- AutofillOptions.loadAddressEditor);
+ AddressListItem.prototype.loadAddressEditor.bind(this));
this.contentElement.appendChild(editButtonEl);
},
+
+ /**
+ * For local Autofill data, this function causes the AutofillOptionsHandler
+ * to call showEditAddressOverlay(). For Wallet data, the user is
+ * redirected to the Wallet web interface.
+ */
+ loadAddressEditor: function() {
+ if (this.metadata_.isLocal)
+ chrome.send('loadAddressEditor', [this.metadata_.guid]);
+ else
+ window.open(loadTimeData.getString('manageWalletAddressesUrl'));
+ },
};
/**
* Creates a new credit card list item.
- * @param {Array} entry An array of the form [guid, label, icon].
+ * @param {AutofillEntityMetadata} metadata Details about a credit card.
* @constructor
* @extends {options.DeletableItem}
*/
- function CreditCardListItem(entry) {
+ function CreditCardListItem(metadata) {
var el = cr.doc.createElement('div');
- el.guid = entry[0];
- el.label = entry[1];
- el.icon = entry[2];
- el.description = entry[3];
el.__proto__ = CreditCardListItem.prototype;
+ /** @private */
+ el.metadata_ = metadata;
el.decorate();
return el;
@@ -91,24 +132,54 @@ cr.define('options.autofillOptions', function() {
decorate: function() {
DeletableItem.prototype.decorate.call(this);
- // The stored label.
var label = this.ownerDocument.createElement('div');
label.className = 'autofill-list-item';
- label.textContent = this.label;
+ label.textContent = this.metadata_.label;
this.contentElement.appendChild(label);
- // The credit card icon.
- var icon = this.ownerDocument.createElement('img');
- icon.src = this.icon;
- icon.alt = this.description;
- this.contentElement.appendChild(icon);
+ var sublabel = this.ownerDocument.createElement('div');
+ sublabel.className = 'deemphasized';
+ sublabel.textContent = this.metadata_.sublabel;
+ this.contentElement.appendChild(sublabel);
+
+ if (!this.metadata_.isLocal) {
+ this.deletable = false;
+ this.contentElement.appendChild(CreateGoogleAccountLabel());
+ }
+
+ var guid = this.metadata_.guid;
+ if (this.metadata_.isCached) {
+ var localCopyText = this.ownerDocument.createElement('span');
+ localCopyText.className = 'hide-until-hover deemphasized';
+ localCopyText.textContent =
+ loadTimeData.getString('autofillDescribeLocalCopy');
+ this.contentElement.appendChild(localCopyText);
+
+ var clearLocalCopyButton = AutofillEditProfileButton(
+ function() { chrome.send('clearLocalCardCopy', [guid]); });
+ clearLocalCopyButton.textContent =
+ loadTimeData.getString('autofillClearLocalCopyButton');
+ this.contentElement.appendChild(clearLocalCopyButton);
+ }
// The 'Edit' button.
+ var metadata = this.metadata_;
var editButtonEl = AutofillEditProfileButton(
- this.guid,
- AutofillOptions.loadCreditCardEditor);
+ CreditCardListItem.prototype.loadCreditCardEditor.bind(this));
this.contentElement.appendChild(editButtonEl);
},
+
+ /**
+ * For local Autofill data, this function causes the AutofillOptionsHandler
+ * to call showEditCreditCardOverlay(). For Wallet data, the user is
+ * redirected to the Wallet web interface.
+ */
+ loadCreditCardEditor: function() {
+ if (this.metadata_.isLocal)
+ chrome.send('loadCreditCardEditor', [this.metadata_.guid]);
+ else
+ window.open(loadTimeData.getString('manageWalletPaymentMethodsUrl'));
+ },
};
/**
@@ -150,6 +221,9 @@ cr.define('options.autofillOptions', function() {
}
this.addEventListener('commitedit', this.onEditCommitted_);
+ this.closeButtonFocusAllowed = true;
+ this.setFocusableColumnIndex(this.input, 0);
+ this.setFocusableColumnIndex(this.closeButtonElement, 1);
},
/**
@@ -207,17 +281,23 @@ cr.define('options.autofillOptions', function() {
if (this.isPlaceholder) {
// It is important that updateIndex is done before validateAndSave.
// Otherwise we can not be sure about AddRow index.
- this.list.dataModel.updateIndex(i);
+ this.list.ignoreChangeEvents(function() {
+ this.list.dataModel.updateIndex(i);
+ }.bind(this));
this.list.validateAndSave(i, 0, value);
} else {
this.list.validateAndSave(i, 1, value);
}
} else {
// Reject empty values and duplicates.
- if (!this.isPlaceholder)
- this.list.dataModel.splice(i, 1);
- else
+ if (!this.isPlaceholder) {
+ this.list.ignoreChangeEvents(function() {
+ this.list.dataModel.splice(i, 1);
+ }.bind(this));
+ this.list.selectIndexWithoutFocusing(i);
+ } else {
this.clearValue_();
+ }
}
},
};
@@ -226,7 +306,7 @@ cr.define('options.autofillOptions', function() {
* Creates a new name value list item.
* @param {options.autofillOptions.AutofillNameValuesList} list The parent
* list of this item.
- * @param {Array.<string>} entry An array of [first, middle, last] names.
+ * @param {Array<string>} entry An array of [first, middle, last] names.
* @constructor
* @extends {options.autofillOptions.ValuesListItem}
*/
@@ -353,20 +433,21 @@ cr.define('options.autofillOptions', function() {
/** @override */
activateItemAtIndex: function(index) {
- AutofillOptions.loadAddressEditor(this.dataModel.item(index)[0]);
+ this.getListItemByIndex(index).loadAddressEditor();
},
/**
* @override
- * @param {Array} entry
+ * @param {AutofillEntityMetadata} metadata
*/
- createItem: function(entry) {
- return new AddressListItem(entry);
+ createItem: function(metadata) {
+ return new AddressListItem(metadata);
},
/** @override */
deleteItemAtIndex: function(index) {
- AutofillOptions.removeData(this.dataModel.item(index)[0]);
+ AutofillOptions.removeData(this.dataModel.item(index).guid,
+ 'Options_AutofillAddressDeleted');
},
};
@@ -386,20 +467,21 @@ cr.define('options.autofillOptions', function() {
/** @override */
activateItemAtIndex: function(index) {
- AutofillOptions.loadCreditCardEditor(this.dataModel.item(index)[0]);
+ this.getListItemByIndex(index).loadCreditCardEditor();
},
/**
* @override
- * @param {Array} entry
+ * @param {AutofillEntityMetadata} metadata
*/
- createItem: function(entry) {
- return new CreditCardListItem(entry);
+ createItem: function(metadata) {
+ return new CreditCardListItem(metadata);
},
/** @override */
deleteItemAtIndex: function(index) {
- AutofillOptions.removeData(this.dataModel.item(index)[0]);
+ AutofillOptions.removeData(this.dataModel.item(index).guid,
+ 'Options_AutofillCreditCardDeleted');
},
};
@@ -413,11 +495,9 @@ cr.define('options.autofillOptions', function() {
AutofillValuesList.prototype = {
__proto__: InlineEditableItemList.prototype,
- /**
- * @override
- * @param {string} entry
- */
+ /** @override */
createItem: function(entry) {
+ assert(entry === null || typeof entry == 'string');
return new ValuesListItem(this, entry);
},
@@ -427,39 +507,11 @@ cr.define('options.autofillOptions', function() {
},
/** @override */
- shouldFocusPlaceholder: function() {
+ shouldFocusPlaceholderOnEditCommit: function() {
return false;
},
/**
- * Called when the list hierarchy as a whole loses or gains focus.
- * If the list was focused in response to a mouse click, call into the
- * superclass's implementation. If the list was focused in response to a
- * keyboard navigation, focus the first item.
- * If the list loses focus, unselect all the elements.
- * @param {Event} e The change event.
- * @private
- */
- handleListFocusChange_: function(e) {
- // We check to see whether there is a selected item as a proxy for
- // distinguishing between mouse- and keyboard-originated focus events.
- var selectedItem = this.selectedItem;
- if (selectedItem)
- InlineEditableItemList.prototype.handleListFocusChange_.call(this, e);
-
- if (!e.newValue) {
- // When the list loses focus, unselect all the elements.
- this.selectionModel.unselectAll();
- } else {
- // When the list gains focus, select the first item if nothing else is
- // selected.
- var firstItem = this.getListItemByIndex(0);
- if (!selectedItem && firstItem && e.newValue)
- firstItem.handleFocus_();
- }
- },
-
- /**
* Called when a new list item should be validated; subclasses are
* responsible for implementing if validation is required.
* @param {number} index The index of the item that was inserted or changed.
@@ -467,7 +519,10 @@ cr.define('options.autofillOptions', function() {
* @param {string} value The value of the item to insert.
*/
validateAndSave: function(index, remove, value) {
- this.dataModel.splice(index, remove, value);
+ this.ignoreChangeEvents(function() {
+ this.dataModel.splice(index, remove, value);
+ }.bind(this));
+ this.selectIndexWithoutFocusing(index);
},
};
@@ -483,10 +538,11 @@ cr.define('options.autofillOptions', function() {
/**
* @override
- * @param {Array.<string>} entry
+ * @param {?string|Array<string>} entry
*/
createItem: function(entry) {
- return new NameListItem(this, entry);
+ var arrayOrNull = entry ? assertInstanceof(entry, Array) : null;
+ return new NameListItem(this, arrayOrNull);
},
};
@@ -522,7 +578,7 @@ cr.define('options.autofillOptions', function() {
/**
* Pending Promise resolver functions.
- * @type {Array.<!Function>}
+ * @type {Array<!Function>}
* @private
*/
validationPromiseResolvers_: [],
diff --git a/chromium/chrome/browser/resources/options/browser_options.css b/chromium/chrome/browser/resources/options/browser_options.css
index 933eb91e5e3..cca6c1e45e5 100644
--- a/chromium/chrome/browser/resources/options/browser_options.css
+++ b/chromium/chrome/browser/resources/options/browser_options.css
@@ -182,21 +182,24 @@ input[type='range'] {
padding-bottom: 0;
}
+.setting-extra-description {
+ -webkit-margin-start: 1.8em;
+ color: #999;
+}
+
.hotword-settings {
-webkit-margin-start: 22px;
}
-.hotword-description {
+.hotword-audio-history {
-webkit-margin-start: 1.8em;
- color: gray;
}
-.hotword-learn-more-link {
- -webkit-padding-start: 8px;
+#audio-history {
+ margin-top: 1.5em;
}
#hotword-retrain-link {
- -webkit-padding-start: 8px;
text-decoration: none;
}
@@ -236,17 +239,19 @@ list:not([disabled]) > .network-group[selected] {
padding-top: 3px;
}
-.network-icon,
-.network-menu-item-icon {
+.network-icon {
-webkit-margin-end: 8px;
background-position: left top;
background-size: 25px;
height: 25px;
+ line-height: normal;
+ white-space: normal;
width: 25px;
}
-.other-cellulars > .network-menu-item-icon {
- background-position: left top;
+.network-icon cr-network-icon {
+ height: 100%;
+ width: 100%;
}
@-webkit-keyframes connecting-animation {
@@ -284,7 +289,7 @@ list:not([disabled]) > .network-group[selected] {
}
.network-add-connection {
- background-image: url('chrome://theme/IDR_NETWORK_ADD_CONNECTION');
+ background-image: url(chrome://theme/IDR_NETWORK_ADD_CONNECTION);
background-size: 16px;
}
@@ -293,7 +298,7 @@ list:not([disabled]) > .network-group[selected] {
}
.network-control-active {
- background-image: url('chrome://theme/IDR_PROFILE_SELECTED');
+ background-image: url(chrome://theme/IDR_PROFILE_SELECTED);
background-size: 16px;
}
@@ -353,17 +358,17 @@ list:not([disabled]) > .network-group[selected] {
.network-selector {
background: right center no-repeat;
- background-image: url('../../../../ui/webui/resources/images/select.png');
+ background-image: url(../../../../ui/webui/resources/images/select.png);
padding-right: 20px;
}
.network-menu {
- -webkit-box-shadow:
+ background: #fff;
+ box-shadow:
0 0 0 1px rgba(0,0,0,0.1),
0 5px 1px 1px rgba(0,0,0,0.1),
0 5px 2px 1px rgba(0,0,0,0.1),
0 5px 12px 1px rgba(0,0,0,0.5);
- background: #fff;
display: block;
position: absolute;
width: 320px;
@@ -406,7 +411,8 @@ list:not([disabled]) > .network-group[selected] {
text-overflow: ellipsis;
}
-.network-menu-item:hover {
+.network-menu-item:hover,
+.network-menu-item[selected] {
background-color: #eee;
}
@@ -433,6 +439,11 @@ list:not([disabled]) > .network-group[selected] {
vertical-align: baseline;
}
+.loading #timezone-value select {
+ color: transparent;
+ text-shadow: none;
+}
+
#privacy-explanation {
line-height: 1.8em;
}
diff --git a/chromium/chrome/browser/resources/options/browser_options.html b/chromium/chrome/browser/resources/options/browser_options.html
index 868281bb015..2f6aad49c4f 100644
--- a/chromium/chrome/browser/resources/options/browser_options.html
+++ b/chromium/chrome/browser/resources/options/browser_options.html
@@ -5,6 +5,8 @@
<include src="reset_profile_settings_banner.html">
<include src="automatic_settings_reset_banner.html">
<if expr="chromeos">
+ <link rel="import" href="chrome://resources/cr_elements/cr_network_icon/cr_network_icon.html">
+ <link rel="import" href="chrome://resources/cr_elements/cr_onc/cr_onc_data.html">
<include src="secondary_user_banner.html">
<section>
<div id="network-section-header" class="section-header">
@@ -157,6 +159,14 @@
i18n-content="defaultSearchManageEngines">
</button>
</div>
+ <div id="google-now-launcher" hidden>
+ <div class="checkbox">
+ <label>
+ <input pref="google_now_launcher.enabled" type="checkbox">
+ <span i18n-content="googleNowLauncherEnable"></span>
+ </label>
+ </div>
+ </div>
<div id="hotword-always-on-search" hidden>
<div class="checkbox controlled-setting-with-label">
<label>
@@ -164,14 +174,14 @@
pref="hotword.always_on_search_enabled" type="checkbox">
<span i18n-content="hotwordSearchEnable"></span>
</label>
- <a target="_blank" class="hotword-learn-more-link"
+ <a target="_blank" class="hotword-link"
i18n-content="learnMore"
i18n-values="href:hotwordLearnMoreURL">
</a>
<span id="hotword-always-on-search-setting-indicator"
pref="hotword.always_on_search_enabled" dialog-pref></span>
<div>
- <span class="hotword-description"
+ <span class="setting-extra-description"
i18n-content="hotwordAlwaysOnDesc">
</span>
<a id="hotword-retrain-link" is="action-link"
@@ -187,25 +197,33 @@
pref="hotword.search_enabled_2" type="checkbox">
<span i18n-content="hotwordSearchEnable"></span>
</label>
- <a target="_blank" class="hotword-learn-more-link"
+ <a target="_blank" class="hotword-link"
i18n-content="learnMore"
i18n-values="href:hotwordLearnMoreURL">
</a>
<span id="hotword-no-dsp-search-setting-indicator"
pref="hotword.search_enabled_2" dialog-pref></span>
<div>
- <span class="hotword-description"
+ <span class="setting-extra-description"
i18n-content="hotwordNoDSPDesc">
</span>
</div>
</div>
</div>
- <div id="audio-logging" hidden>
- <div class="checkbox controlled-setting-with-label">
- <label>
- <input pref="hotword.audio_history_enabled" type="checkbox">
- <span i18n-content="hotwordAudioHistoryEnable"></span>
+ <div id="audio-history" hidden>
+ <div class="settings-row">
+ <label class="hotword-audio-history">
+ <span id="audio-history-label"></span>
</label>
+ <a target="_blank" class="hotword-link"
+ i18n-content="hotwordAudioHistoryManage"
+ i18n-values="href:hotwordManageAudioHistoryURL">
+ </a>
+ </div>
+ <div class="settings-row" id="audio-history-always-on-description">
+ <span class="setting-extra-description"
+ i18n-content="hotwordAlwaysOnAudioHistoryDescription">
+ </span>
</div>
</div>
</div>
@@ -223,13 +241,17 @@
<if expr="not chromeos">
<div id="profiles-enable-guest" class="checkbox">
<label>
- <input pref="profile.browser_guest_enabled" type="checkbox">
+ <input pref="profile.browser_guest_enabled"
+ type="checkbox"
+ metric="Options_BrowserGuestEnabled">
<span i18n-content="profileBrowserGuestEnable"></span>
</label>
</div>
<div id="profiles-enable-add-person" class="checkbox">
<label>
- <input pref="profile.add_person_enabled" type="checkbox">
+ <input pref="profile.add_person_enabled"
+ type="checkbox"
+ metric="Options_AddPersonEnabled">
<span i18n-content="profileAddPersonEnable"></span>
</label>
</div>
@@ -282,20 +304,28 @@
i18n-content="timezone"></span>
<div id="timezone-value">
<select class="control"
+ id="timezone-value-select"
i18n-options="timezoneList" data-type="string"
pref="cros.system.timezone"
- aria-labelledby="timezone-value-label"></select>
+ aria-labelledby="timezone-value-label"
+ metric="Options_TimezoneSelect"></select>
</div>
</div>
<div class="checkbox settings-row">
+ <div id="resolve-timezone-by-geolocation-selection" hidden>
+ <label>
+ <input id="resolve-timezone-by-geolocation"
+ pref="settings.resolve_timezone_by_geolocation"
+ metric="Options_ResolveTimezoneByGeoLocation" type="checkbox">
+ <span i18n-content="resolveTimezoneByGeoLocation"></span>
+ </label>
+ </div>
<label>
<input id="use-24hour-clock" pref="settings.clock.use_24hour_clock"
- type="checkbox">
+ metric="Options_Use24HourClockCheckbox" type="checkbox">
<span i18n-content="use24HourClock"></span>
</label>
</div>
- <div id="time-synced-explanation" class="settings-row"
- i18n-content="timeSyncedExplanation"></div>
<div id="set-time" class="settings-row" hidden>
<button id="set-time-button"
i18n-content="setTimeButton"></button>
@@ -405,11 +435,11 @@
</span>
</label>
</div>
- <div id="metricsReportingSetting"
+ <div id="metrics-reporting-setting"
class="checkbox controlled-setting-with-label">
<if expr="chromeos">
<label>
- <input id="metricsReportingEnabled"
+ <input id="metrics-reporting-enabled"
pref="cros.metrics.reportingEnabled" type="checkbox">
<span>
<span i18n-content="enableLogging"></span>
@@ -420,14 +450,14 @@
</if>
<if expr="not chromeos">
<label>
- <input id="metricsReportingEnabled" type="checkbox">
+ <input id="metrics-reporting-enabled" type="checkbox">
<span>
<span i18n-content="enableLogging"></span>
<span id="metrics-reporting-disabled-icon"
class="controlled-setting-indicator"></span>
</span>
</label>
- <span id="metrics-reporting-reset-restart">
+ <span id="metrics-reporting-reset-restart" hidden>
<!-- Text filled by JavaScript -->
<span></span><a is="action-link" role="button"
class="standalone-action-link"></a><span></span>
@@ -472,6 +502,22 @@
</label>
</div>
</div>
+<if expr="chromeos">
+ <div id="wake-on-wifi" hidden
+ class="checkbox controlled-setting-with-label">
+ <label>
+ <input id="wake-on-wifi-checkbox" type="checkbox"
+ metric="Options_WakeOnWifiSsid"
+ pref="settings.internet.wake_on_wifi_ssid">
+ <span>
+ <span i18n-content="wakeOnWifiLabel"></span>
+ <span id="wake-on-wifi-indicator"
+ class="controlled-setting-indicator"
+ pref="settings.internet.wake_on_wifi_ssid"></span>
+ </span>
+ </label>
+ </div>
+</if>
</div>
</section>
<if expr="chromeos">
@@ -482,7 +528,8 @@
<div id="bluetooth-options-div">
<div class="checkbox">
<label>
- <input type="checkbox" id="enable-bluetooth">
+ <input type="checkbox" id="enable-bluetooth"
+ metric="Options_BluetoothEnabled">
<span i18n-content="enableBluetooth">
</label>
</div>
@@ -539,7 +586,7 @@
<section id="easy-unlock-section" guest-visibility="hidden" hidden>
<h3 i18n-content="easyUnlockSectionTitle"></h3>
<!-- Options shown when the user has not set up Easy Unlock -->
- <div id="easy-unlock-setup" hidden>
+ <div id="easy-unlock-disabled" hidden>
<div class="settings-row">
<span i18n-content="easyUnlockSetupIntro"></span>
<a target="_blank" i18n-content="learnMore"
@@ -549,7 +596,7 @@
i18n-content="easyUnlockSetupButton"></button>
</div>
<!-- Options shown when the user has set up Easy Unlock -->
- <div id="easy-unlock-enable" hidden>
+ <div id="easy-unlock-enabled" hidden>
<div class="settings-row">
<span i18n-content="easyUnlockDescription"></span>
<a target="_blank" i18n-content="learnMore"
@@ -710,7 +757,7 @@
<section>
<h3 i18n-content="advancedSectionTitleCertificates"></h3>
<div>
-<if expr="use_nss or is_win or is_macosx">
+<if expr="use_nss_certs or is_win or is_macosx">
<div class="settings-row">
<button id="certificatesManageButton"
i18n-content="certificatesManageButton"></button>
@@ -932,7 +979,6 @@
</div>
</section>
</if>
-</if>
<if expr="not chromeos">
<section id="system-section">
<h3 i18n-content="advancedSectionTitleSystem"></h3>
@@ -971,7 +1017,7 @@
</section>
</div> <!-- advanced-settings-container -->
</div> <!-- advanced-settings -->
- <footer>
+ <footer id="advanced-settings-footer">
<a is="action-link" id="advanced-settings-expander"
i18n-content="showAdvancedSettings"></a>
</footer>
diff --git a/chromium/chrome/browser/resources/options/browser_options.js b/chromium/chrome/browser/resources/options/browser_options.js
index c92fa1df42b..d9456b7f888 100644
--- a/chromium/chrome/browser/resources/options/browser_options.js
+++ b/chromium/chrome/browser/resources/options/browser_options.js
@@ -6,6 +6,7 @@ cr.exportPath('options');
/**
* @typedef {{actionLinkText: (string|undefined),
+ * childUser: (boolean|undefined),
* hasError: (boolean|undefined),
* hasUnrecoverableError: (boolean|undefined),
* managed: (boolean|undefined),
@@ -15,6 +16,7 @@ cr.exportPath('options');
* signinAllowed: (boolean|undefined),
* signoutAllowed: (boolean|undefined),
* statusText: (string|undefined),
+ * supervisedUser: (boolean|undefined),
* syncSystemEnabled: (boolean|undefined)}}
* @see chrome/browser/ui/webui/options/browser_options_handler.cc
*/
@@ -25,6 +27,17 @@ options.SyncStatus;
*/
options.ExtensionData;
+/**
+ * @typedef {{name: string,
+ * filePath: string,
+ * isCurrentProfile: boolean,
+ * isSupervised: boolean,
+ * isChild: boolean,
+ * iconUrl: string}}
+ * @see chrome/browser/ui/webui/options/browser_options_handler.cc
+ */
+options.Profile;
+
cr.define('options', function() {
var OptionsPage = options.OptionsPage;
var Page = cr.ui.pageManager.Page;
@@ -116,7 +129,7 @@ cr.define('options', function() {
window.addEventListener('message', this.handleWindowMessage_.bind(this));
if (loadTimeData.getBoolean('allowAdvancedSettings')) {
- $('advanced-settings-expander').onclick = function() {
+ $('advanced-settings-expander').onclick = function(e) {
var showAdvanced =
BrowserOptions.shouldShowSection_($('advanced-settings'));
if (showAdvanced) {
@@ -127,11 +140,10 @@ cr.define('options', function() {
$('advanced-settings'),
$('advanced-settings-container'));
- // If the link was focused (i.e., it was activated using the keyboard)
- // and it was used to show the section (rather than hiding it), focus
- // the first element in the container.
- if (document.activeElement === $('advanced-settings-expander') &&
- showAdvanced) {
+ // If the click was triggered using the keyboard and it showed the
+ // section (rather than hiding it), focus the first element in the
+ // container.
+ if (e.detail == 0 && showAdvanced) {
var focusElement = $('advanced-settings-container').querySelector(
'button, input, list, select, a[href]');
if (focusElement)
@@ -139,7 +151,7 @@ cr.define('options', function() {
}
};
} else {
- $('advanced-settings-expander').hidden = true;
+ $('advanced-settings-footer').hidden = true;
$('advanced-settings').hidden = true;
}
@@ -192,8 +204,6 @@ cr.define('options', function() {
networkIndicator.setAttribute('controlled-by', 'shared');
networkIndicator.location = cr.ui.ArrowLocation.TOP_START;
}
- options.network.NetworkList.refreshNetworkData(
- loadTimeData.getValue('networkData'));
}
// On Startup section.
@@ -228,10 +238,14 @@ cr.define('options', function() {
$('hotword-search-setting-indicator'));
HotwordSearchSettingIndicator.decorate(
$('hotword-no-dsp-search-setting-indicator'));
- HotwordSearchSettingIndicator.decorate(
- $('hotword-always-on-search-setting-indicator'));
+ var hotwordIndicator = $('hotword-always-on-search-setting-indicator');
+ HotwordSearchSettingIndicator.decorate(hotwordIndicator);
+ hotwordIndicator.disabledOnErrorSection =
+ $('hotword-always-on-search-checkbox');
chrome.send('requestHotwordAvailable');
+ chrome.send('requestGoogleNowAvailable');
+
if ($('set-wallpaper')) {
$('set-wallpaper').onclick = function(event) {
chrome.send('openWallpaperManager');
@@ -329,6 +343,8 @@ cr.define('options', function() {
profilesList.addEventListener('change',
this.setProfileViewButtonsStatus_);
$('profiles-create').onclick = function(event) {
+ chrome.send('metricsHandler:recordAction',
+ ['Options_ShowCreateProfileDlg']);
ManageProfileOverlay.showCreateDialog();
};
if (OptionsPage.isSettingsApp()) {
@@ -338,15 +354,22 @@ cr.define('options', function() {
};
}
$('profiles-manage').onclick = function(event) {
+ chrome.send('metricsHandler:recordAction',
+ ['Options_ShowEditProfileDlg']);
ManageProfileOverlay.showManageDialog();
};
$('profiles-delete').onclick = function(event) {
var selectedProfile = self.getSelectedProfileItem_();
- if (selectedProfile)
+ if (selectedProfile) {
+ chrome.send('metricsHandler:recordAction',
+ ['Options_ShowDeleteProfileDlg']);
ManageProfileOverlay.showDeleteDialog(selectedProfile);
+ }
};
if (loadTimeData.getBoolean('profileIsSupervised')) {
$('profiles-create').disabled = true;
+ }
+ if (!loadTimeData.getBoolean('allowProfileDeletion')) {
$('profiles-delete').disabled = true;
$('profiles-list').canDeleteItems = false;
}
@@ -381,8 +404,18 @@ cr.define('options', function() {
}
// Date and time section (CrOS only).
- if ($('set-time-button'))
- $('set-time-button').onclick = this.handleSetTime_.bind(this);
+ if (cr.isChromeOS) {
+ if ($('set-time-button'))
+ $('set-time-button').onclick = this.handleSetTime_.bind(this);
+
+ // Timezone
+ if (loadTimeData.getBoolean('enableTimeZoneTrackingOption')) {
+ $('resolve-timezone-by-geolocation-selection').hidden = false;
+ this.setSystemTimezoneManaged_(false);
+ $('timezone-value-select').disabled = loadTimeData.getBoolean(
+ 'resolveTimezoneByGeolocationInitialValue');
+ }
+ }
// Default browser section.
if (!cr.isChromeOS) {
@@ -411,13 +444,13 @@ cr.define('options', function() {
// 'metricsReportingEnabled' element is only present on Chrome branded
// builds, and the 'metricsReportingCheckboxAction' message is only
// handled on ChromeOS.
- if ($('metricsReportingEnabled') && cr.isChromeOS) {
- $('metricsReportingEnabled').onclick = function(event) {
+ if ($('metrics-reporting-enabled') && cr.isChromeOS) {
+ $('metrics-reporting-enabled').onclick = function(event) {
chrome.send('metricsReportingCheckboxAction',
[String(event.currentTarget.checked)]);
};
}
- if ($('metricsReportingEnabled') && !cr.isChromeOS) {
+ if ($('metrics-reporting-enabled') && !cr.isChromeOS) {
// The localized string has the | symbol on each side of the text that
// needs to be made into a button to restart Chrome. We parse the text
// and build the button from that.
@@ -434,20 +467,21 @@ cr.define('options', function() {
restartElements[1].onclick = function(event) {
chrome.send('restartBrowser');
};
- // Attach the listener for updating the checkbox and restart button.
- var updateMetricsRestartButton = function() {
- $('metrics-reporting-reset-restart').hidden =
- loadTimeData.getBoolean('metricsReportingEnabledAtStart') ==
- $('metricsReportingEnabled').checked;
- };
- $('metricsReportingEnabled').onclick = function(event) {
+ $('metrics-reporting-enabled').onclick = function(event) {
chrome.send('metricsReportingCheckboxChanged',
[Boolean(event.currentTarget.checked)]);
- updateMetricsRestartButton();
+ if (cr.isMac) {
+ // A browser restart is never needed to toggle metrics reporting,
+ // and is only needed to toggle crash reporting when using Breakpad.
+ // Crashpad, used on Mac, does not require a browser restart.
+ return;
+ }
+ $('metrics-reporting-reset-restart').hidden =
+ loadTimeData.getBoolean('metricsReportingEnabledAtStart') ==
+ $('metrics-reporting-enabled').checked;
};
- $('metricsReportingEnabled').checked =
+ $('metrics-reporting-enabled').checked =
loadTimeData.getBoolean('metricsReportingEnabledAtStart');
- updateMetricsRestartButton();
}
$('networkPredictionOptions').onchange = function(event) {
var value = (event.target.checked ?
@@ -460,6 +494,10 @@ cr.define('options', function() {
true,
metric);
};
+ if (loadTimeData.valueExists('showWakeOnWifi') &&
+ loadTimeData.getBoolean('showWakeOnWifi')) {
+ $('wake-on-wifi').hidden = false;
+ }
// Bluetooth (CrOS only).
if (cr.isChromeOS) {
@@ -475,6 +513,8 @@ cr.define('options', function() {
};
$('bluetooth-reconnect-device').onclick = function(event) {
+ chrome.send('coreOptionsUserMetricsAction',
+ ['Options_BluetoothConnectPairedDevice']);
var device = $('bluetooth-paired-devices-list').selectedItem;
var address = device.address;
chrome.send('updateBluetoothDevice', [address, 'connect']);
@@ -563,7 +603,8 @@ cr.define('options', function() {
// Web Content section.
$('fontSettingsCustomizeFontsButton').onclick = function(event) {
PageManager.showPageByName('fonts');
- chrome.send('coreOptionsUserMetricsAction', ['Options_FontSettings']);
+ chrome.send('coreOptionsUserMetricsAction',
+ ['Options_ShowFontSettings']);
};
$('defaultFontSize').onchange = function(event) {
var value = event.target.options[event.target.selectedIndex].value;
@@ -637,20 +678,10 @@ cr.define('options', function() {
$('accessibility-settings-button').onclick = function(unused_event) {
window.open(loadTimeData.getString('accessibilitySettingsURL'));
};
- $('accessibility-spoken-feedback-check').onchange = function(
- unused_event) {
- chrome.send('spokenFeedbackChange',
- [$('accessibility-spoken-feedback-check').checked]);
- updateAccessibilitySettingsButton();
- };
+ $('accessibility-spoken-feedback-check').onchange =
+ updateAccessibilitySettingsButton;
updateAccessibilitySettingsButton();
- $('accessibility-high-contrast-check').onchange = function(
- unused_event) {
- chrome.send('highContrastChange',
- [$('accessibility-high-contrast-check').checked]);
- };
-
var updateDelayDropdown = function() {
$('accessibility-autoclick-dropdown').disabled =
!$('accessibility-autoclick-check').checked;
@@ -813,6 +844,10 @@ cr.define('options', function() {
* @private
*/
showSection_: function(section, container, animate) {
+ if (section == $('advanced-settings') &&
+ !loadTimeData.getBoolean('allowAdvancedSettings')) {
+ return;
+ }
// Delay starting the transition if animating so that hidden change will
// be processed.
if (animate) {
@@ -981,7 +1016,7 @@ cr.define('options', function() {
$('sync-section').hidden = false;
this.maybeShowUserSection_();
- if (cr.isChromeOS && syncData.supervisedUser) {
+ if (cr.isChromeOS && syncData.supervisedUser && !syncData.childUser) {
var subSection = $('sync-section').firstChild;
while (subSection) {
if (subSection.nodeType == Node.ELEMENT_NODE)
@@ -1087,14 +1122,15 @@ cr.define('options', function() {
},
/**
- * Update the UI depending on whether the current profile has a pairing for
- * Easy Unlock.
- * @param {boolean} hasPairing True if the current profile has a pairing.
+ * Update the UI depending on whether Easy Unlock is enabled for the current
+ * profile.
+ * @param {boolean} isEnabled True if the feature is enabled for the current
+ * profile.
*/
- updateEasyUnlock_: function(hasPairing) {
- $('easy-unlock-setup').hidden = hasPairing;
- $('easy-unlock-enable').hidden = !hasPairing;
- if (!hasPairing && EasyUnlockTurnOffOverlay.getInstance().visible) {
+ updateEasyUnlock_: function(isEnabled) {
+ $('easy-unlock-disabled').hidden = isEnabled;
+ $('easy-unlock-enabled').hidden = !isEnabled;
+ if (!isEnabled && EasyUnlockTurnOffOverlay.getInstance().visible) {
EasyUnlockTurnOffOverlay.dismiss();
}
},
@@ -1167,7 +1203,7 @@ cr.define('options', function() {
},
/**
- * Activates the Audio History and Always-On Hotword sections from the
+ * Activates the Always-On Hotword sections from the
* System settings page.
* @param {string=} opt_error The error message to display.
* @private
@@ -1177,7 +1213,6 @@ cr.define('options', function() {
'hotword-always-on-search',
'hotword-always-on-search-setting-indicator',
opt_error);
- $('audio-logging').hidden = false;
},
/**
@@ -1194,6 +1229,18 @@ cr.define('options', function() {
},
/**
+ * Controls the visibility of all the hotword sections.
+ * @param {boolean} visible Whether to show hotword sections.
+ * @private
+ */
+ setAllHotwordSectionsVisible_: function(visible) {
+ $('hotword-search').hidden = !visible;
+ $('hotword-always-on-search').hidden = !visible;
+ $('hotword-no-dsp-search').hidden = !visible;
+ $('audio-history').hidden = !visible;
+ },
+
+ /**
* Shows or hides the hotword retrain link
* @param {boolean} visible Whether to show the link.
* @private
@@ -1213,6 +1260,26 @@ cr.define('options', function() {
},
/**
+ * Controls the visibility of the Now settings.
+ * @param {boolean} visible Whether to show Now settings.
+ * @private
+ */
+ setNowSectionVisible_: function(visible) {
+ $('google-now-launcher').hidden = !visible;
+ },
+
+ /**
+ * Activates the Audio History section of the Settings page.
+ * @param {boolean} visible Whether the audio history section is visible.
+ * @param {string} labelText Text describing current audio history state.
+ * @private
+ */
+ setAudioHistorySectionVisible_: function(visible, labelText) {
+ $('audio-history').hidden = !visible;
+ $('audio-history-label').textContent = labelText;
+ },
+
+ /**
* Event listener for the 'homepage is NTP' preference. Updates the label
* next to the 'Change' button.
* @param {Event} event The preference change event.
@@ -1382,14 +1449,13 @@ cr.define('options', function() {
var selectedProfile = profilesList.selectedItem;
var hasSelection = selectedProfile != null;
var hasSingleProfile = profilesList.dataModel.length == 1;
- var isSupervised = loadTimeData.getBoolean('profileIsSupervised');
$('profiles-manage').disabled = !hasSelection ||
!selectedProfile.isCurrentProfile;
if (hasSelection && !selectedProfile.isCurrentProfile)
$('profiles-manage').title = loadTimeData.getString('currentUserOnly');
else
$('profiles-manage').title = '';
- $('profiles-delete').disabled = isSupervised ||
+ $('profiles-delete').disabled = !profilesList.canDeleteItems ||
(!hasSelection && !hasSingleProfile);
if (OptionsPage.isSettingsApp()) {
$('profiles-app-list-switch').disabled = !hasSelection ||
@@ -1425,9 +1491,8 @@ cr.define('options', function() {
/**
* Adds all |profiles| to the list.
- * @param {Array.<{name: string, filePath: string,
- * isCurrentProfile: boolean, isSupervised: boolean}>} profiles An array
- * of profile info objects.
+ * @param {Array<!options.Profile>} profiles An array of profile info
+ * objects.
* @private
*/
setProfilesInfo_: function(profiles) {
@@ -1486,7 +1551,7 @@ cr.define('options', function() {
/**
* Reports successful profile creation to the "create" overlay.
- * @param {Object} profileInfo An object of the form:
+ * @param {options.Profile} profileInfo An object of the form:
* profileInfo = {
* name: "Profile Name",
* filePath: "/path/to/profile/data/on/disk"
@@ -1500,7 +1565,7 @@ cr.define('options', function() {
/**
* Returns the currently active profile for this browser window.
- * @return {Object} A profile info object.
+ * @return {options.Profile} A profile info object.
* @private
*/
getCurrentProfile_: function() {
@@ -1589,10 +1654,35 @@ cr.define('options', function() {
},
/**
+ * This is called from chromium code when system timezone "managed" state
+ * is changed. Enables or disables dependent settings.
+ * @param {boolean} managed Is true when system Timezone is managed by
+ * enterprise policy. False otherwize.
+ */
+ setSystemTimezoneManaged_: function(managed) {
+ if (loadTimeData.getBoolean('enableTimeZoneTrackingOption')) {
+ if (managed) {
+ $('resolve-timezone-by-geolocation-selection').disabled = true;
+ $('resolve-timezone-by-geolocation').onclick = function(event) {};
+ } else {
+ this.enableElementIfPossible_(
+ getRequiredElement('resolve-timezone-by-geolocation-selection'));
+ $('resolve-timezone-by-geolocation').onclick = function(event) {
+ $('timezone-value-select').disabled = event.currentTarget.checked;
+ };
+ $('timezone-value-select').disabled =
+ $('resolve-timezone-by-geolocation').checked;
+ }
+ }
+ },
+
+ /**
* Handle the 'add device' button click.
* @private
*/
handleAddBluetoothDevice_: function() {
+ chrome.send('coreOptionsUserMetricsAction',
+ ['Options_BluetoothShowAddDevice']);
chrome.send('findBluetoothDevices');
PageManager.showPageByName('bluetooth', false);
},
@@ -1606,12 +1696,14 @@ cr.define('options', function() {
},
/**
- * Enables or disables the ChromeOS display settings button.
+ * Enables or disables the Chrome OS display settings button and overlay.
* @private
*/
- enableDisplayButton_: function(enabled) {
- if (cr.isChromeOS)
+ enableDisplaySettings_: function(enabled, showUnifiedDesktop) {
+ if (cr.isChromeOS) {
$('display-options').disabled = !enabled;
+ DisplayOptions.getInstance().setEnabled(enabled, showUnifiedDesktop);
+ }
},
/**
@@ -1627,8 +1719,8 @@ cr.define('options', function() {
* @private
*/
setMetricsReportingCheckboxState_: function(checked, disabled) {
- $('metricsReportingEnabled').checked = checked;
- $('metricsReportingEnabled').disabled = disabled;
+ $('metrics-reporting-enabled').checked = checked;
+ $('metrics-reporting-enabled').disabled = disabled;
// If checkbox gets disabled then add an attribute for displaying the
// special icon. Otherwise remove the indicator attribute.
@@ -1645,9 +1737,9 @@ cr.define('options', function() {
*/
setMetricsReportingSettingVisibility_: function(visible) {
if (visible)
- $('metricsReportingSetting').style.display = 'block';
+ $('metrics-reporting-setting').style.display = 'block';
else
- $('metricsReportingSetting').style.display = 'none';
+ $('metrics-reporting-setting').style.display = 'none';
},
/**
@@ -2026,7 +2118,6 @@ cr.define('options', function() {
*/
setCanSetTime_: function(canSetTime) {
// If the time has been network-synced, it cannot be set manually.
- $('time-synced-explanation').hidden = canSetTime;
$('set-time').hidden = !canSetTime;
},
@@ -2079,7 +2170,7 @@ cr.define('options', function() {
'addBluetoothDevice',
'deleteCurrentProfile',
'enableCertificateButton',
- 'enableDisplayButton',
+ 'enableDisplaySettings',
'enableFactoryResetSection',
'getCurrentProfile',
'getStartStopSyncButton',
@@ -2098,15 +2189,19 @@ cr.define('options', function() {
'setHotwordRetrainLinkVisible',
'setNativeThemeButtonEnabled',
'setNetworkPredictionValue',
+ 'setNowSectionVisible',
'setHighContrastCheckboxState',
+ 'setAllHotwordSectionsVisible',
'setMetricsReportingCheckboxState',
'setMetricsReportingSettingVisibility',
'setProfilesInfo',
'setSpokenFeedbackCheckboxState',
+ 'setSystemTimezoneManaged',
'setThemesResetButtonEnabled',
'setVirtualKeyboardCheckboxState',
'setupPageZoomSelector',
'setupProxySettingsButton',
+ 'setAudioHistorySectionVisible',
'showBluetoothSettings',
'showCreateProfileError',
'showCreateProfileSuccess',
diff --git a/chromium/chrome/browser/resources/options/browser_options_profile_list.js b/chromium/chrome/browser/resources/options/browser_options_profile_list.js
index 87542ae06e0..96adf2fec7b 100644
--- a/chromium/chrome/browser/resources/options/browser_options_profile_list.js
+++ b/chromium/chrome/browser/resources/options/browser_options_profile_list.js
@@ -51,6 +51,7 @@ cr.define('options.browser_options', function() {
var iconEl = this.ownerDocument.createElement('img');
iconEl.className = 'profile-img';
iconEl.style.content = getProfileAvatarIcon(profileInfo.iconURL);
+ iconEl.alt = '';
containerEl.appendChild(iconEl);
var nameEl = this.ownerDocument.createElement('div');
@@ -69,7 +70,8 @@ cr.define('options.browser_options', function() {
if (profileInfo.isSupervised) {
var supervisedEl = this.ownerDocument.createElement('div');
supervisedEl.className = 'profile-supervised';
- supervisedEl.textContent =
+ supervisedEl.textContent = profileInfo.isChild ?
+ loadTimeData.getString('childLabel') :
loadTimeData.getString('supervisedUserLabel');
containerEl.appendChild(supervisedEl);
}
@@ -101,8 +103,6 @@ cr.define('options.browser_options', function() {
/** @override */
deleteItemAtIndex: function(index) {
- if (loadTimeData.getBoolean('profileIsSupervised'))
- return;
ManageProfileOverlay.showDeleteDialog(this.dataModel.item(index));
},
@@ -122,6 +122,13 @@ cr.define('options.browser_options', function() {
},
/**
+ * @type {boolean} whether the items in this list are deletable.
+ */
+ get canDeleteItems() {
+ return this.canDeleteItems_;
+ },
+
+ /**
* If false, items in this list will not be deletable.
* @private
*/
diff --git a/chromium/chrome/browser/resources/options/browser_options_startup_page_list.js b/chromium/chrome/browser/resources/options/browser_options_startup_page_list.js
index aac5dae6df8..329c3e781ab 100644
--- a/chromium/chrome/browser/resources/options/browser_options_startup_page_list.js
+++ b/chromium/chrome/browser/resources/options/browser_options_startup_page_list.js
@@ -84,7 +84,7 @@ cr.define('options.browser_options', function() {
});
if (!this.isPlaceholder)
- this.draggable = true;
+ titleEl.draggable = true;
},
/** @override */
@@ -178,7 +178,7 @@ cr.define('options.browser_options', function() {
return false;
}
- var target = e.target;
+ var target = this.getTargetFromDropEvent_(e);
// StartupPageListItem should be the only draggable element type in the
// page but let's make sure.
if (target instanceof StartupPageListItem) {
diff --git a/chromium/chrome/browser/resources/options/certificate_manager.js b/chromium/chrome/browser/resources/options/certificate_manager.js
index 8b8287a4d16..8e9cad61158 100644
--- a/chromium/chrome/browser/resources/options/certificate_manager.js
+++ b/chromium/chrome/browser/resources/options/certificate_manager.js
@@ -172,7 +172,7 @@ cr.define('options', function() {
* @constructor
* @extends {cr.ui.pageManager.Page}
*/
- function CertificateManager(model) {
+ function CertificateManager() {
Page.call(this, 'certificates',
loadTimeData.getString('certificateManagerPageTabTitle'),
'certificateManagerPage');
@@ -183,15 +183,24 @@ cr.define('options', function() {
CertificateManager.prototype = {
__proto__: Page.prototype,
+ /** @private {boolean} */
+ isKiosk_: false,
+
+ /** @param {boolean} isKiosk */
+ setIsKiosk: function(isKiosk) {
+ this.isKiosk_ = isKiosk;
+ },
+
/** @override */
- initializePage: function(isKiosk) {
+ initializePage: function() {
Page.prototype.initializePage.call(this);
this.personalTab = new CertificateManagerTab('personalCertsTab',
- !!isKiosk);
- this.serverTab = new CertificateManagerTab('serverCertsTab', !!isKiosk);
- this.caTab = new CertificateManagerTab('caCertsTab', !!isKiosk);
- this.otherTab = new CertificateManagerTab('otherCertsTab', !!isKiosk);
+ this.isKiosk_);
+ this.serverTab = new CertificateManagerTab('serverCertsTab',
+ this.isKiosk_);
+ this.caTab = new CertificateManagerTab('caCertsTab', this.isKiosk_);
+ this.otherTab = new CertificateManagerTab('otherCertsTab', this.isKiosk_);
this.addEventListener('visibleChange', this.handleVisibleChange_);
diff --git a/chromium/chrome/browser/resources/options/certificate_tree.js b/chromium/chrome/browser/resources/options/certificate_tree.js
index e7752c8858d..6be4eeb2413 100644
--- a/chromium/chrome/browser/resources/options/certificate_tree.js
+++ b/chromium/chrome/browser/resources/options/certificate_tree.js
@@ -6,7 +6,7 @@
* @typedef {{
* id: string,
* name: string,
- * subnodes: Array.<{id: string, name: string, readonly: boolean,
+ * subnodes: Array<{id: string, name: string, readonly: boolean,
* untrusted: boolean, extractable: boolean,
* policy: boolean}>
* }}
@@ -143,7 +143,7 @@ cr.define('options', function() {
/**
* Populate the tree.
- * @param {Array.<CertificateData>} nodesData Nodes data array.
+ * @param {Array<CertificateData>} nodesData Nodes data array.
*/
populate: function(nodesData) {
this.clear();
diff --git a/chromium/chrome/browser/resources/options/chromeos/accounts_options_page.css b/chromium/chrome/browser/resources/options/chromeos/accounts_options_page.css
index bba94eacfa5..365775759e5 100644
--- a/chromium/chrome/browser/resources/options/chromeos/accounts_options_page.css
+++ b/chromium/chrome/browser/resources/options/chromeos/accounts_options_page.css
@@ -24,13 +24,9 @@
width: 26px;
}
-.user-email-label {
- -webkit-margin-start: 10px;
-}
-
+.user-email-label,
.user-name-label {
-webkit-margin-start: 10px;
- color: darkgray;
}
.user-email-name-block {
@@ -43,17 +39,17 @@
.remove-user-button {
background-image: -webkit-image-set(
- url('../../../../../ui/resources/default_100_percent/close_2.png') 1x,
- url('../../../../../ui/resources/default_200_percent/close_2.png') 2x);
+ url(../../../../../ui/resources/default_100_percent/close_2.png) 1x,
+ url(../../../../../ui/resources/default_200_percent/close_2.png) 2x);
height: 16px;
width: 16px;
}
.remove-user-button:hover {
background-image: -webkit-image-set(
- url('../../../../../ui/resources/default_100_percent/close_2_hover.png')
+ url(../../../../../ui/resources/default_100_percent/close_2_hover.png)
1x,
- url('../../../../../ui/resources/default_200_percent/close_2_hover.png')
+ url(../../../../../ui/resources/default_200_percent/close_2_hover.png)
2x);
}
@@ -85,7 +81,7 @@
#ownerOnlyWarning {
-webkit-padding-start: 20px;
- background-image: url('warning.png');
+ background-image: url(warning.png);
background-repeat: no-repeat;
margin-bottom: 10px;
margin-top: 10px;
diff --git a/chromium/chrome/browser/resources/options/chromeos/accounts_user_list.js b/chromium/chrome/browser/resources/options/chromeos/accounts_user_list.js
index 018f63cf768..8bb3e3f0fbe 100644
--- a/chromium/chrome/browser/resources/options/chromeos/accounts_user_list.js
+++ b/chromium/chrome/browser/resources/options/chromeos/accounts_user_list.js
@@ -81,7 +81,7 @@ cr.define('options.accounts', function() {
/**
* Loads given user list.
- * @param {!Array.<Object>} users An array of user info objects.
+ * @param {!Array<Object>} users An array of user info objects.
* @private
*/
load_: function(users) {
diff --git a/chromium/chrome/browser/resources/options/chromeos/bluetooth_add_device_overlay.js b/chromium/chrome/browser/resources/options/chromeos/bluetooth_add_device_overlay.js
index fa91e6919c2..675e08772c9 100644
--- a/chromium/chrome/browser/resources/options/chromeos/bluetooth_add_device_overlay.js
+++ b/chromium/chrome/browser/resources/options/chromeos/bluetooth_add_device_overlay.js
@@ -42,6 +42,8 @@ cr.define('options', function() {
var self = this;
$('bluetooth-add-device-apply-button').onclick = function(event) {
+ chrome.send('coreOptionsUserMetricsAction',
+ ['Options_BluetoothConnectNewDevice']);
var device = self.deviceList_.selectedItem;
var address = device.address;
PageManager.closeOverlay();
diff --git a/chromium/chrome/browser/resources/options/chromeos/bluetooth_device_list.js b/chromium/chrome/browser/resources/options/chromeos/bluetooth_device_list.js
index 8aa6f88018e..7db87b0e94d 100644
--- a/chromium/chrome/browser/resources/options/chromeos/bluetooth_device_list.js
+++ b/chromium/chrome/browser/resources/options/chromeos/bluetooth_device_list.js
@@ -299,6 +299,9 @@ cr.define('options.system.bluetooth', function() {
// forgetting the device.
chrome.send('updateBluetoothDevice',
[item.data.address, item.connected ? 'disconnect' : 'forget']);
+
+ chrome.send('coreOptionsUserMetricsAction',
+ ['Options_BluetoothRemoveDevice']);
}
},
diff --git a/chromium/chrome/browser/resources/options/chromeos/bluetooth_pair_device_overlay.js b/chromium/chrome/browser/resources/options/chromeos/bluetooth_pair_device_overlay.js
index 35c5b20e91d..63b9ee4a708 100644
--- a/chromium/chrome/browser/resources/options/chromeos/bluetooth_pair_device_overlay.js
+++ b/chromium/chrome/browser/resources/options/chromeos/bluetooth_pair_device_overlay.js
@@ -27,7 +27,7 @@ cr.define('options', function() {
/**
* List of IDs for conditionally visible elements in the dialog.
- * @type {Array.<string>}
+ * @type {Array<string>}
* @const
*/
var ELEMENTS = ['bluetooth-pairing-passkey-display',
@@ -233,7 +233,7 @@ cr.define('options', function() {
/**
* Updates the visibility of elements in the dialog.
- * @param {Array.<string>} list List of conditionally visible elements that
+ * @param {Array<string>} list List of conditionally visible elements that
* are to be made visible.
* @private
*/
diff --git a/chromium/chrome/browser/resources/options/chromeos/change_picture_options.css b/chromium/chrome/browser/resources/options/chromeos/change_picture_options.css
index d68291a6e9c..9ca91f86f46 100644
--- a/chromium/chrome/browser/resources/options/chromeos/change_picture_options.css
+++ b/chromium/chrome/browser/resources/options/chromeos/change_picture_options.css
@@ -141,7 +141,7 @@
#flip-photo {
-webkit-transition: opacity 75ms linear;
- background: url('chrome://theme/IDR_MIRROR_FLIP') no-repeat;
+ background: url(chrome://theme/IDR_MIRROR_FLIP) no-repeat;
border: none;
bottom: 44px; /* 8px + image bottom. */
display: block;
@@ -178,13 +178,13 @@ html[dir=rtl] #flip-photo {
}
.camera:not(.live) #discard-photo {
- background: url('chrome://theme/IDR_USER_IMAGE_RECYCLE')
+ background: url(chrome://theme/IDR_USER_IMAGE_RECYCLE)
no-repeat center center;
display: block;
}
.camera.live.online #take-photo {
- background: url('chrome://theme/IDR_USER_IMAGE_CAPTURE')
+ background: url(chrome://theme/IDR_USER_IMAGE_CAPTURE)
no-repeat center -1px;
display: block;
}
diff --git a/chromium/chrome/browser/resources/options/chromeos/change_picture_options.js b/chromium/chrome/browser/resources/options/chromeos/change_picture_options.js
index 506e483c12d..de6670a4c7b 100644
--- a/chromium/chrome/browser/resources/options/chromeos/change_picture_options.js
+++ b/chromium/chrome/browser/resources/options/chromeos/change_picture_options.js
@@ -10,7 +10,7 @@ cr.define('options', function() {
/**
* Array of button URLs used on this page.
- * @type {Array.<string>}
+ * @type {Array<string>}
* @const
*/
var ButtonImageUrls = [
@@ -303,19 +303,13 @@ cr.define('options', function() {
/**
* Appends default images to the image grid. Should only be called once.
- * @param {Array.<{url: string, author: string, website: string}>}
+ * @param {Array<{url: string, author: string, website: string}>}
* imagesData An array of default images data, including URL, author and
* website.
* @private
*/
setDefaultImages_: function(imagesData) {
- var imageGrid = $('user-image-grid');
- for (var i = 0, data; data = imagesData[i]; i++) {
- var item = imageGrid.addItem(data.url, data.title);
- item.type = 'default';
- item.author = data.author || '';
- item.website = data.website || '';
- }
+ $('user-image-grid').setDefaultImages(imagesData);
},
};
diff --git a/chromium/chrome/browser/resources/options/chromeos/display_options.css b/chromium/chrome/browser/resources/options/chromeos/display_options.css
index 3f738791e5c..73a78e45833 100644
--- a/chromium/chrome/browser/resources/options/chromeos/display_options.css
+++ b/chromium/chrome/browser/resources/options/chromeos/display_options.css
@@ -20,16 +20,10 @@
width: 100%;
}
-#display-options-displays-view-mirroring {
- margin: 20px 0 20px 0;
-}
-
#display-configurations {
-webkit-padding-end: 0;
-webkit-padding-start: 15px;
- background-color: white;
border-top: 1px solid lightgrey;
- padding-bottom: 15px;
padding-top: 15px;
}
@@ -38,7 +32,6 @@
* upper-half, which were left/top before the rotation. */
#display-configuration-arrow {
-webkit-transform: rotate(45deg);
- background-color: white;
border-left: 1px solid lightgrey;
border-top: 1px solid lightgrey;
height: 20px;
@@ -47,6 +40,19 @@
z-index: 1;
}
+#display-configurations,
+#display-configuration-arrow,
+#display-options-page .action-area {
+ background-color: white;
+}
+
+#display-options-page .action-area {
+ /* Because this element has a background-color, we need to emulate the
+ * parent's border-radius (otherwise there's sharp corners). */
+ border-bottom-left-radius: inherit;
+ border-bottom-right-radius: inherit;
+}
+
#selected-display-data-container {
z-index: 2;
}
diff --git a/chromium/chrome/browser/resources/options/chromeos/display_options.html b/chromium/chrome/browser/resources/options/chromeos/display_options.html
index 87d23de74c5..8e1f9d049b6 100644
--- a/chromium/chrome/browser/resources/options/chromeos/display_options.html
+++ b/chromium/chrome/browser/resources/options/chromeos/display_options.html
@@ -16,6 +16,18 @@
</button>
<button id="display-options-set-primary"
class="display-options-button" i18n-content="setPrimary">
+ </button>
+ </div>
+ <div id="display-options-unified-desktop"
+ class="checkbox selected-display-option-row" hidden>
+ <!-- intentionally blank for the title column space. -->
+ <div class="selected-display-option-title">
+ </div>
+ <label>
+ <input id="display-options-toggle-unified-desktop" type="checkbox">
+ <span class="controlled-setting-with-label"
+ i18n-content="enableUnifiedDesktop"></span>
+ </label>
</div>
<div class="selected-display-option-row">
<div class="selected-display-option-title"
@@ -47,7 +59,7 @@
</button>
</div>
<div class="selected-display-option-row"
- id="selected-display-color-profile-row">
+ id="selected-display-color-profile-row" hidden>
<div class="selected-display-option-title"
i18n-content="selectedDisplayColorProfile">
</div>
@@ -62,4 +74,10 @@
<div id="display-configuration-arrow">
</div>
</div>
+ <div class="action-area">
+ <div class="button-strip">
+ <button id="display-options-done" i18n-content="done"
+ class="default-button"></button>
+ </div>
+ </div>
</div>
diff --git a/chromium/chrome/browser/resources/options/chromeos/display_options.js b/chromium/chrome/browser/resources/options/chromeos/display_options.js
index 756e564534b..bf316214fe8 100644
--- a/chromium/chrome/browser/resources/options/chromeos/display_options.js
+++ b/chromium/chrome/browser/resources/options/chromeos/display_options.js
@@ -6,13 +6,13 @@ cr.exportPath('options');
/**
* @typedef {{
- * availableColorProfiles: Array.<{profileId: number, name: string}>,
+ * availableColorProfiles: Array<{profileId: number, name: string}>,
* colorProfile: number,
* height: number,
* id: string,
* isInternal: boolean,
* isPrimary: boolean,
- * resolutions: Array.<{width: number, height: number, originalWidth: number,
+ * resolutions: Array<{width: number, height: number, originalWidth: number,
* originalHeight: number, deviceScaleFactor: number, scale: number,
* refreshRate: number, isBest: boolean, selected: boolean}>,
* name: string,
@@ -36,6 +36,17 @@ options.SecondaryDisplayLayout = {
LEFT: 3
};
+/**
+ * Enumeration of multi display mode. The value has to be same as the
+ * values in ash/display/display_manager..
+ * @enum {number}
+ */
+options.MultiDisplayMode = {
+ EXTENDED: 0,
+ MIRRORING: 1,
+ UNIFIED: 2,
+};
+
cr.define('options', function() {
var Page = cr.ui.pageManager.Page;
var PageManager = cr.ui.pageManager.PageManager;
@@ -116,6 +127,18 @@ cr.define('options', function() {
*/
mirroring_: false,
+ /*
+ * Whether the unified desktop is enable or not.
+ * @private
+ */
+ unifiedDesktopEnabled_: false,
+
+ /*
+ * Whether the unified desktop option should be present.
+ * @private
+ */
+ showUnifiedDesktopOption_: false,
+
/**
* The current secondary display layout.
* @private
@@ -125,7 +148,7 @@ cr.define('options', function() {
/**
* The array of current output displays. It also contains the display
* rectangles currently rendered on screen.
- * @type {Array.<options.DisplayInfo>}
+ * @type {Array<options.DisplayInfo>}
* @private
*/
displays_: [],
@@ -170,6 +193,12 @@ cr.define('options', function() {
*/
lastTouchLocation_: null,
+ /**
+ * Whether the display settings can be shown.
+ * @private
+ */
+ enabled_: true,
+
/** @override */
initializePage: function() {
Page.prototype.initializePage.call(this);
@@ -210,6 +239,16 @@ cr.define('options', function() {
chrome.send('coreOptionsUserMetricsAction',
['Options_DisplaySetOverscan']);
}).bind(this);
+
+ $('display-options-done').onclick = function() {
+ PageManager.closeOverlay();
+ };
+
+ $('display-options-toggle-unified-desktop').onclick = (function() {
+ this.unifiedDesktopEnabled_ = !this.unifiedDesktopEnabled_;
+ chrome.send('setUnifiedDesktopEnabled',
+ [this.unifiedDesktopEnabled_]);
+ }).bind(this);
},
/** @override */
@@ -224,6 +263,29 @@ cr.define('options', function() {
chrome.send('getDisplayInfo');
},
+ /** @override */
+ canShowPage: function() {
+ return this.enabled_;
+ },
+
+ /**
+ * Enables or disables the page. When disabled, the page will not be able to
+ * open, and will close if currently opened.
+ * @param {boolean} enabled Whether the page should be enabled.
+ * @param {boolean} showUnifiedDesktop Whether the unified desktop option
+ * should be present.
+ */
+ setEnabled: function(enabled, showUnifiedDesktop) {
+ if (this.enabled_ == enabled &&
+ this.showUnifiedDesktopOption_ == showUnifiedDesktop) {
+ return;
+ }
+ this.enabled_ = enabled;
+ this.showUnifiedDesktopOption_ = showUnifiedDesktop;
+ if (!enabled && this.visible)
+ PageManager.closeOverlay();
+ },
+
/**
* Mouse move handler for dragging display rectangle.
* @param {Event} e The mouse move event.
@@ -743,8 +805,6 @@ cr.define('options', function() {
var totalHeight = height + numDisplays * MIRRORING_OFFSET_PIXELS;
this.displaysView_.style.height = totalHeight + 'px';
- this.displaysView_.classList.add(
- 'display-options-displays-view-mirroring');
// The displays should be centered.
var offsetX =
@@ -766,6 +826,37 @@ cr.define('options', function() {
},
/**
+ * Creates a div element representing the specified display.
+ * @param {Object} display The display object.
+ * @param {boolean} focused True if it's focused.
+ * @private
+ */
+ createDisplayRectangle_: function(display, focused) {
+ var div = document.createElement('div');
+ display.div = div;
+ div.className = 'displays-display';
+ if (focused)
+ div.classList.add('displays-focused');
+
+ // div needs to be added to the DOM tree first, otherwise offsetHeight for
+ // nameContainer below cannot be computed.
+ this.displaysView_.appendChild(div);
+
+ var nameContainer = document.createElement('div');
+ nameContainer.textContent = display.name;
+ div.appendChild(nameContainer);
+ div.style.width = Math.floor(display.width * this.visualScale_) + 'px';
+ var newHeight = Math.floor(display.height * this.visualScale_);
+ div.style.height = newHeight + 'px';
+ nameContainer.style.marginTop =
+ (newHeight - nameContainer.offsetHeight) / 2 + 'px';
+
+ div.onmousedown = this.onMouseDown_.bind(this);
+ div.ontouchstart = this.onTouchStart_.bind(this);
+ return div;
+ },
+
+ /**
* Layouts the display rectangles according to the current layout_.
* @private
*/
@@ -773,8 +864,18 @@ cr.define('options', function() {
var maxWidth = 0;
var maxHeight = 0;
var boundingBox = {left: 0, right: 0, top: 0, bottom: 0};
+ this.primaryDisplay_ = null;
+ this.secondaryDisplay_ = null;
+ var focusedDisplay = null;
for (var i = 0; i < this.displays_.length; i++) {
var display = this.displays_[i];
+ if (display.isPrimary)
+ this.primaryDisplay_ = display;
+ else
+ this.secondaryDisplay_ = display;
+ if (i == this.focusedIndex_)
+ focusedDisplay = display;
+
boundingBox.left = Math.min(boundingBox.left, display.x);
boundingBox.right = Math.max(
boundingBox.right, display.x + display.width);
@@ -784,6 +885,8 @@ cr.define('options', function() {
maxWidth = Math.max(maxWidth, display.width);
maxHeight = Math.max(maxHeight, display.height);
}
+ if (!this.primaryDisplay_)
+ return;
// Make the margin around the bounding box.
var areaWidth = boundingBox.right - boundingBox.left + maxWidth;
@@ -798,13 +901,6 @@ cr.define('options', function() {
this.displaysView_.style.height =
Math.ceil(areaHeight * this.visualScale_) + 'px';
- var boundingCenter = {
- x: Math.floor((boundingBox.right + boundingBox.left) *
- this.visualScale_ / 2),
- y: Math.floor((boundingBox.bottom + boundingBox.top) *
- this.visualScale_ / 2)
- };
-
// Centering the bounding box of the display rectangles.
var offset = {
x: Math.floor(this.displaysView_.offsetWidth / 2 -
@@ -813,59 +909,70 @@ cr.define('options', function() {
(boundingBox.bottom + boundingBox.top) * this.visualScale_ / 2)
};
- for (var i = 0; i < this.displays_.length; i++) {
- var display = this.displays_[i];
- var div = document.createElement('div');
- display.div = div;
-
- div.className = 'displays-display';
- if (i == this.focusedIndex_)
- div.classList.add('displays-focused');
-
- if (display.isPrimary) {
- this.primaryDisplay_ = display;
- } else {
- this.secondaryDisplay_ = display;
+ // Layouting the display rectangles. First layout the primaryDisplay and
+ // then layout the secondary which is attaching to the primary.
+ var primaryDiv = this.createDisplayRectangle_(
+ this.primaryDisplay_, this.primaryDisplay_ == focusedDisplay);
+ primaryDiv.style.left =
+ Math.floor(this.primaryDisplay_.x * this.visualScale_) +
+ offset.x + 'px';
+ primaryDiv.style.top =
+ Math.floor(this.primaryDisplay_.y * this.visualScale_) +
+ offset.y + 'px';
+ this.primaryDisplay_.originalPosition = {
+ x: primaryDiv.offsetLeft, y: primaryDiv.offsetTop};
+
+ if (this.secondaryDisplay_) {
+ var secondaryDiv = this.createDisplayRectangle_(
+ this.secondaryDisplay_, this.secondaryDisplay_ == focusedDisplay);
+ // Don't trust the secondary display's x or y, because it may cause a
+ // 1px gap due to rounding, which will create a fake update on end
+ // dragging. See crbug.com/386401
+ switch (this.layout_) {
+ case options.SecondaryDisplayLayout.TOP:
+ secondaryDiv.style.left =
+ Math.floor(this.secondaryDisplay_.x * this.visualScale_) +
+ offset.x + 'px';
+ secondaryDiv.style.top =
+ primaryDiv.offsetTop - secondaryDiv.offsetHeight + 'px';
+ break;
+ case options.SecondaryDisplayLayout.RIGHT:
+ secondaryDiv.style.left =
+ primaryDiv.offsetLeft + primaryDiv.offsetWidth + 'px';
+ secondaryDiv.style.top =
+ Math.floor(this.secondaryDisplay_.y * this.visualScale_) +
+ offset.y + 'px';
+ break;
+ case options.SecondaryDisplayLayout.BOTTOM:
+ secondaryDiv.style.left =
+ Math.floor(this.secondaryDisplay_.x * this.visualScale_) +
+ offset.x + 'px';
+ secondaryDiv.style.top =
+ primaryDiv.offsetTop + primaryDiv.offsetHeight + 'px';
+ break;
+ case options.SecondaryDisplayLayout.LEFT:
+ secondaryDiv.style.left =
+ primaryDiv.offsetLeft - secondaryDiv.offsetWidth + 'px';
+ secondaryDiv.style.top =
+ Math.floor(this.secondaryDisplay_.y * this.visualScale_) +
+ offset.y + 'px';
+ break;
}
- var displayNameContainer = document.createElement('div');
- displayNameContainer.textContent = display.name;
- div.appendChild(displayNameContainer);
- display.nameContainer = displayNameContainer;
- display.div.style.width =
- Math.floor(display.width * this.visualScale_) + 'px';
- var newHeight = Math.floor(display.height * this.visualScale_);
- display.div.style.height = newHeight + 'px';
- div.style.left =
- Math.floor(display.x * this.visualScale_) + offset.x + 'px';
- div.style.top =
- Math.floor(display.y * this.visualScale_) + offset.y + 'px';
- display.nameContainer.style.marginTop =
- (newHeight - display.nameContainer.offsetHeight) / 2 + 'px';
-
- div.onmousedown = this.onMouseDown_.bind(this);
- div.ontouchstart = this.onTouchStart_.bind(this);
-
- this.displaysView_.appendChild(div);
-
- // Set the margin top to place the display name at the middle of the
- // rectangle. Note that this has to be done after it's added into the
- // |displaysView_|. Otherwise its offsetHeight is yet 0.
- displayNameContainer.style.marginTop =
- (div.offsetHeight - displayNameContainer.offsetHeight) / 2 + 'px';
- display.originalPosition = {x: div.offsetLeft, y: div.offsetTop};
+ this.secondaryDisplay_.originalPosition = {
+ x: secondaryDiv.offsetLeft, y: secondaryDiv.offsetTop};
}
},
/**
* Called when the display arrangement has changed.
- * @param {boolean} mirroring Whether current mode is mirroring or not.
- * @param {Array.<options.DisplayInfo>} displays The list of the display
+ * @param {options.MultiDisplayMode} multi display mode.
+ * @param {Array<options.DisplayInfo>} displays The list of the display
* information.
* @param {options.SecondaryDisplayLayout} layout The layout strategy.
* @param {number} offset The offset of the secondary display.
* @private
*/
- onDisplayChanged_: function(mirroring, displays, layout, offset) {
+ onDisplayChanged_: function(mode, displays, layout, offset) {
if (!this.visible)
return;
@@ -879,20 +986,25 @@ cr.define('options', function() {
this.layout_ = layout;
+ var mirroring = mode == options.MultiDisplayMode.MIRRORING;
+ var unifiedDesktopEnabled = mode == options.MultiDisplayMode.UNIFIED;
+
$('display-options-toggle-mirroring').textContent =
loadTimeData.getString(
mirroring ? 'stopMirroring' : 'startMirroring');
// Focus to the first display next to the primary one when |displays| list
// is updated.
- if (mirroring) {
+ if (mirroring || unifiedDesktopEnabled) {
this.focusedIndex_ = null;
} else if (this.mirroring_ != mirroring ||
+ this.unifiedDesktopEnabled_ != unifiedDesktopEnabled ||
this.displays_.length != displays.length) {
this.focusedIndex_ = 0;
}
this.mirroring_ = mirroring;
+ this.unifiedDesktopEnabled_ = unifiedDesktopEnabled;
this.displays_ = displays;
this.resetDisplaysView_();
@@ -901,14 +1013,28 @@ cr.define('options', function() {
else
this.layoutDisplays_();
+ $('display-options-unified-desktop').hidden =
+ !this.showUnifiedDesktopOption_;
+
+ $('display-options-toggle-unified-desktop').checked =
+ this.unifiedDesktopEnabled_;
+
+ var disableUnifiedDesktopOption =
+ (this.mirroring_ ||
+ (!this.unifiedDesktopEnabled_ &&
+ this.displays_.length == 1));
+
+ $('display-options-toggle-unified-desktop').disabled =
+ disableUnifiedDesktopOption;
+
this.updateSelectedDisplayDescription_();
}
};
DisplayOptions.setDisplayInfo = function(
- mirroring, displays, layout, offset) {
+ mode, displays, layout, offset) {
DisplayOptions.getInstance().onDisplayChanged_(
- mirroring, displays, layout, offset);
+ mode, displays, layout, offset);
};
// Export
diff --git a/chromium/chrome/browser/resources/options/chromeos/display_overscan.css b/chromium/chrome/browser/resources/options/chromeos/display_overscan.css
index 3a09e408e12..6069a806044 100644
--- a/chromium/chrome/browser/resources/options/chromeos/display_overscan.css
+++ b/chromium/chrome/browser/resources/options/chromeos/display_overscan.css
@@ -28,8 +28,8 @@
#display-overscan-operation-arrows {
background-image: -webkit-image-set(
- url('overscan_arrows.png') 1x,
- url('overscan_arrows_2x.png') 2x);
+ url(overscan_arrows.png) 1x,
+ url(overscan_arrows_2x.png) 2x);
background-position: center;
background-repeat: no-repeat;
height: 51px;
@@ -38,8 +38,8 @@
#display-overscan-operation-shift {
background-image: -webkit-image-set(
- url('overscan_shift.png') 1x,
- url('overscan_shift_2x.png') 2x);
+ url(overscan_shift.png) 1x,
+ url(overscan_shift_2x.png) 2x);
background-position: center;
background-repeat: no-repeat;
height: 23px;
@@ -48,8 +48,8 @@
html[dir=rtl] #display-overscan-operation-shift {
background-image: -webkit-image-set(
- url('overscan_shift_rtl.png') 1x,
- url('overscan_shift_rtl_2x.png') 2x);
+ url(overscan_shift_rtl.png) 1x,
+ url(overscan_shift_rtl_2x.png) 2x);
}
#display-overscan-button-strip {
diff --git a/chromium/chrome/browser/resources/options/chromeos/internet_detail.css b/chromium/chrome/browser/resources/options/chromeos/internet_detail.css
index a027a56259f..1528bc08781 100644
--- a/chromium/chrome/browser/resources/options/chromeos/internet_detail.css
+++ b/chromium/chrome/browser/resources/options/chromeos/internet_detail.css
@@ -53,6 +53,14 @@
padding: 4px;
}
+#vpn-tab.third-party-vpn-provider tr.built-in-vpn-provider-only {
+ display: none;
+}
+
+#vpn-tab:not(.third-party-vpn-provider) tr.third-party-vpn-provider-only {
+ display: none;
+}
+
#ip-config-list {
min-height: 96px !important;
}
diff --git a/chromium/chrome/browser/resources/options/chromeos/internet_detail.html b/chromium/chrome/browser/resources/options/chromeos/internet_detail.html
index 788c41ae149..09e25672699 100644
--- a/chromium/chrome/browser/resources/options/chromeos/internet_detail.html
+++ b/chromium/chrome/browser/resources/options/chromeos/internet_detail.html
@@ -73,7 +73,8 @@
<span>
<span i18n-content="inetPreferredNetwork"></span>
<span class="controlled-setting-indicator"
- managed="Priority"></span>
+ managed="Priority"
+ internet-detail-for="prefer-network-wifi"></span>
</span>
</label>
</div>
@@ -87,7 +88,9 @@
<span>
<span i18n-content="inetAutoConnectNetwork"></span>
<span class="controlled-setting-indicator"
- managed="WiFi.AutoConnect"></span>
+ managed="WiFi.AutoConnect"
+ internet-detail-for="auto-connect-network-wifi">
+ </span>
</span>
</label>
</div>
@@ -157,7 +160,9 @@
<span>
<span i18n-content="inetAutoConnectNetwork"></span>
<span class="controlled-setting-indicator"
- managed="WiMAX.AutoConnect"></span>
+ managed="WiMAX.AutoConnect"
+ internet-detail-for="auto-connect-network-wimax">
+ </span>
</span>
</label>
</div>
@@ -198,7 +203,7 @@
<div id="vpn-tab" class="subpages-tab-contents vpn-details">
<section>
<table class="option-control-table">
- <tr class="auto-connect-network">
+ <tr class="auto-connect-network built-in-vpn-provider-only">
<td>
<div class="checkbox controlled-setting-with-label">
<label>
@@ -206,7 +211,8 @@
<span>
<span i18n-content="inetAutoConnectNetwork"></span>
<span class="controlled-setting-indicator"
- managed="VPN.AutoConnect"></span>
+ managed="VPN.AutoConnect"
+ internet-detail-for="auto-connect-network-vpn"></span>
</span>
</label>
</div>
@@ -216,19 +222,24 @@
<td class="option-name" i18n-content="inetServiceName"></td>
<td id="inet-service-name" class="option-value"></td>
</tr>
- <tr>
+ <tr class="built-in-vpn-provider-only">
<td class="option-name" i18n-content="inetServerHostname"></td>
<td>
<input class="option-value" id="inet-server-hostname"></input>
<span class="controlled-setting-indicator"
- managed="VPN.Host"></span>
+ managed="VPN.Host"
+ internet-detail-for="inet-server-hostname"></span>
</td>
</tr>
<tr>
<td class="option-name" i18n-content="inetProviderType"></td>
<td id="inet-provider-type" class="option-value"></td>
</tr>
- <tr>
+ <tr class="third-party-vpn-provider-only">
+ <td class="option-name" i18n-content="inetProviderName"></td>
+ <td id="inet-provider-name" class="option-value"></td>
+ </tr>
+ <tr class="built-in-vpn-provider-only">
<td class="option-name" i18n-content="inetUsername"></td>
<td id="inet-username" class="option-value"></td>
</tr>
@@ -283,7 +294,8 @@
</option>
</select>
<span class="controlled-setting-indicator"
- managed="Cellular.APN"></span>
+ managed="Cellular.APN"
+ internet-detail-for="select-apn"></span>
</td>
</tr>
<tr class="gsm-only apn-details-view">
@@ -323,7 +335,9 @@
<span>
<span i18n-content="inetAutoConnectNetwork"></span>
<span class="controlled-setting-indicator"
- managed="Cellular.AutoConnect"></span>
+ managed="Cellular.AutoConnect"
+ internet-detail-for="auto-connect-network-cellular">
+ </span>
</span>
</label>
</div>
@@ -409,7 +423,7 @@
<span>
<span i18n-content="ipAutomaticConfiguration"></span>
<span class="controlled-setting-indicator"
- managed="StaticIPConfig.IPAddress"></span>
+ managed="IPAddressConfigType"></span>
</span>
</label>
</div>
@@ -438,7 +452,11 @@
<label>
<input id="automatic-dns-radio" type="radio" name="dnstype"
value="automatic">
- <span i18n-content="automaticNameServers"></span>
+ <span>
+ <span i18n-content="automaticNameServers"></span>
+ <span class="controlled-setting-indicator"
+ managed="NameServersConfigType"></span>
+ </span>
</label>
</div>
<div id="automatic-dns-display" class="dns-display"></div>
@@ -497,7 +515,8 @@
<span>
<span i18n-content="lockSimCard"></span>
<span class="controlled-setting-indicator"
- managed="Cellular.SIMLockStatus.LockEnabled"></span>
+ managed="Cellular.SIMLockStatus.LockEnabled"
+ internet-detail-for="sim-card-lock-enabled"></span>
</span>
</label>
</div>
@@ -506,7 +525,8 @@
<div id="change-pin-area">
<button id="change-pin" i18n-content="changePinButton"></button>
<span class="controlled-setting-indicator"
- managed="Cellular.SIMLockStatus.LockType"></span>
+ managed="Cellular.SIMLockStatus.LockType"
+ internet-detail-for="change-pin"></span>
</div>
</section>
</div>
diff --git a/chromium/chrome/browser/resources/options/chromeos/internet_detail.js b/chromium/chrome/browser/resources/options/chromeos/internet_detail.js
index 3fdc203184e..fa636df9ad9 100644
--- a/chromium/chrome/browser/resources/options/chromeos/internet_detail.js
+++ b/chromium/chrome/browser/resources/options/chromeos/internet_detail.js
@@ -7,27 +7,19 @@
// NOTE(stevenjb): This code is in the process of being converted to be
// compatible with the networkingPrivate extension API:
// * The network property dictionaries are being converted to use ONC values.
-// * chrome.send calls will be replaced with an API object that simulates the
-// networkingPrivate API. See network_config.js.
+// * chrome.send calls will be replaced with chrome.networkingPrivate calls.
// See crbug.com/279351 for more info.
-/** @typedef {{address: (string|undefined),
- * gateway: (string|undefined),
- * nameServers: (string|undefined),
- * netmask: (string|undefined),
- * prefixLength: (number|undefined)}}
- * @see chrome/browser/ui/webui/options/chromeos/internet_options_handler.cc
- */
-var IPInfo;
-
cr.define('options.internet', function() {
var OncData = cr.onc.OncData;
var Page = cr.ui.pageManager.Page;
var PageManager = cr.ui.pageManager.PageManager;
/** @const */ var IPAddressField = options.internet.IPAddressField;
- /** @const */ var GoogleNameServersString = '8.8.4.4,8.8.8.8';
+ /** @const */ var GoogleNameServers = ['8.8.4.4', '8.8.8.8'];
/** @const */ var CarrierGenericUMTS = 'Generic UMTS';
+ /** @const */ var CarrierSprint = 'Sprint';
+ /** @const */ var CarrierVerizon = 'Verizon Wireless';
/**
* Helper function to set hidden attribute for elements matching a selector.
@@ -88,22 +80,6 @@ cr.define('options.internet', function() {
}
/**
- * Sends the 'checked' state of a control to chrome for a network.
- * @param {string} path The service path of the network.
- * @param {string} message The message to send to chrome.
- * @param {string} checkboxId The id of the checkbox with the value to send.
- * @param {string=} opt_action Optional action to record.
- */
- function sendCheckedIfEnabled(path, message, checkboxId, opt_action) {
- var checkbox = assertInstanceof($(checkboxId), HTMLInputElement);
- if (!checkbox.hidden && !checkbox.disabled) {
- chrome.send(message, [path, !!checkbox.checked]);
- if (opt_action)
- sendChromeMetricsAction(opt_action);
- }
- }
-
- /**
* Send metrics to Chrome when the detailed page is opened.
* @param {string} type The ONC type of the network being shown.
* @param {string} state The ONC network state.
@@ -152,6 +128,78 @@ cr.define('options.internet', function() {
return netmask;
}
+ /**
+ * Returns the prefix length from the netmask string.
+ * @param {string} netmask The netmask string, e.g. 255.255.255.0.
+ * @return {number} The corresponding netmask or -1 if invalid.
+ */
+ function netmaskToPrefixLength(netmask) {
+ var prefixLength = 0;
+ var tokens = netmask.split('.');
+ if (tokens.length != 4)
+ return -1;
+ for (var i = 0; i < tokens.length; ++i) {
+ var token = tokens[i];
+ // If we already found the last mask and the current one is not
+ // '0' then the netmask is invalid. For example, 255.224.255.0
+ if (prefixLength / 8 != i) {
+ if (token != '0')
+ return -1;
+ } else if (token == '255') {
+ prefixLength += 8;
+ } else if (token == '254') {
+ prefixLength += 7;
+ } else if (token == '252') {
+ prefixLength += 6;
+ } else if (token == '248') {
+ prefixLength += 5;
+ } else if (token == '240') {
+ prefixLength += 4;
+ } else if (token == '224') {
+ prefixLength += 3;
+ } else if (token == '192') {
+ prefixLength += 2;
+ } else if (token == '128') {
+ prefixLength += 1;
+ } else if (token == '0') {
+ prefixLength += 0;
+ } else {
+ // mask is not a valid number.
+ return -1;
+ }
+ }
+ return prefixLength;
+ }
+
+ // Returns true if we should show the 'View Account' button for |onc|.
+ // TODO(stevenjb): We should query the Mobile Config API for whether or not to
+ // show the 'View Account' button once it is integrated with Settings.
+ function shouldShowViewAccountButton(onc) {
+ var activationState = onc.getActiveValue('Cellular.ActivationState');
+ if (activationState != 'Activating' && activationState != 'Activated')
+ return false;
+
+ // If no online payment URL was provided by Shill, only show 'View Account'
+ // for Verizon Wireless.
+ if (!onc.getActiveValue('Cellular.PaymentPortal.Url') &&
+ onc.getActiveValue('Cellular.Carrier') != CarrierVerizon) {
+ return false;
+ }
+
+ // 'View Account' should only be shown for connected networks, or
+ // disconnected LTE networks with a valid MDN.
+ var connectionState = onc.getActiveValue('ConnectionState');
+ if (connectionState != 'Connected') {
+ var technology = onc.getActiveValue('Cellular.NetworkTechnology');
+ if (technology != 'LTE' && technology != 'LTEAdvanced')
+ return false;
+ if (!onc.getActiveValue('Cellular.MDN'))
+ return false;
+ }
+
+ return true;
+ }
+
/////////////////////////////////////////////////////////////////////////////
// DetailsInternetPage class:
@@ -161,10 +209,16 @@ cr.define('options.internet', function() {
* @extends {cr.ui.pageManager.Page}
*/
function DetailsInternetPage() {
- // Cached Apn properties
+ // If non-negative, indicates a custom entry in select-apn.
this.userApnIndex_ = -1;
- this.selectedApnIndex_ = -1;
+
+ // The custom APN properties associated with entry |userApnIndex_|.
this.userApn_ = {};
+
+ // The currently selected APN entry in $('select-apn') (which may or may not
+ // == userApnIndex_).
+ this.selectedApnIndex_ = -1;
+
// We show the Proxy configuration tab for remembered networks and when
// configuring a proxy from the login screen.
this.showProxy_ = false;
@@ -181,20 +235,40 @@ cr.define('options.internet', function() {
initializePage: function() {
Page.prototype.initializePage.call(this);
this.initializePageContents_();
+
+ chrome.networkingPrivate.onNetworksChanged.addListener(
+ this.onNetworksChanged_.bind(this));
+
this.showNetworkDetails_();
},
/**
- * Auto-activates the network details dialog if network information
+ * Automatically shows the network details dialog if network information
* is included in the URL.
*/
showNetworkDetails_: function() {
- var servicePath = parseQueryParams(window.location).servicePath;
- if (!servicePath || !servicePath.length)
+ var guid = parseQueryParams(window.location).guid;
+ if (!guid || !guid.length)
+ return;
+ chrome.send('loadVPNProviders');
+ chrome.networkingPrivate.getManagedProperties(
+ guid, DetailsInternetPage.initializeDetailsPage);
+ },
+
+ /**
+ * networkingPrivate callback when networks change.
+ * @param {Array<string>} changes List of GUIDs whose properties have
+ * changed.
+ * @private
+ */
+ onNetworksChanged_: function(changes) {
+ if (!this.onc_)
return;
- // TODO(stevenjb): chrome.networkingPrivate.getManagedProperties
- // with initializeDetailsPage as the callback.
- chrome.send('getManagedProperties', [servicePath]);
+ var guid = this.onc_.guid();
+ if (changes.indexOf(guid) != -1) {
+ chrome.networkingPrivate.getManagedProperties(
+ guid, DetailsInternetPage.updateConnectionData);
+ }
},
/**
@@ -228,7 +302,7 @@ cr.define('options.internet', function() {
$('view-account-details').addEventListener('click', function(event) {
chrome.send('showMorePlanInfo',
- [DetailsInternetPage.getInstance().servicePath_]);
+ [DetailsInternetPage.getInstance().onc_.guid()]);
PageManager.closeOverlay();
});
@@ -370,33 +444,51 @@ cr.define('options.internet', function() {
},
/**
- * Sends the IP Config info to chrome.
+ * Gets the IPConfig ONC Object.
* @param {string} nameServerType The selected name server type:
* 'automatic', 'google', or 'user'.
+ * @return {Object} The IPConfig ONC object.
* @private
*/
- sendIpConfig_: function(nameServerType) {
- var userNameServerString = '';
- if (nameServerType == 'user') {
+ getIpConfig_: function(nameServerType) {
+ var ipConfig = {};
+ // If 'ip-address' is empty, automatic configuration will be used.
+ if (!$('ip-automatic-configuration-checkbox').checked &&
+ $('ip-address').model.value) {
+ ipConfig['IPAddress'] = $('ip-address').model.value;
+ var netmask = $('ip-netmask').model.value;
+ var routingPrefix = 0;
+ if (netmask) {
+ routingPrefix = netmaskToPrefixLength(netmask);
+ if (routingPrefix == -1) {
+ console.error('Invalid netmask: ' + netmask);
+ routingPrefix = 0;
+ }
+ }
+ ipConfig['RoutingPrefix'] = routingPrefix;
+ ipConfig['Gateway'] = $('ip-gateway').model.value || '';
+ }
+
+ // Note: If no nameserver fields are set, automatic configuration will be
+ // used. TODO(stevenjb): Validate input fields.
+ if (nameServerType != 'automatic') {
var userNameServers = [];
- for (var i = 1; i <= 4; ++i) {
- var nameServerField = $('ipconfig-dns' + i);
- // Skip empty values.
- if (nameServerField && nameServerField.model &&
- nameServerField.model.value) {
- userNameServers.push(nameServerField.model.value);
+ if (nameServerType == 'google') {
+ userNameServers = GoogleNameServers.slice();
+ } else if (nameServerType == 'user') {
+ for (var i = 1; i <= 4; ++i) {
+ var nameServerField = $('ipconfig-dns' + i);
+ // Skip empty values.
+ if (nameServerField && nameServerField.model &&
+ nameServerField.model.value) {
+ userNameServers.push(nameServerField.model.value);
+ }
}
}
- userNameServerString = userNameServers.sort().join(',');
+ if (userNameServers.length)
+ ipConfig['NameServers'] = userNameServers.sort();
}
- chrome.send('setIPConfig',
- [this.servicePath_,
- Boolean($('ip-automatic-configuration-checkbox').checked),
- $('ip-address').model.value || '',
- $('ip-netmask').model.value || '',
- $('ip-gateway').model.value || '',
- nameServerType,
- userNameServerString]);
+ return ipConfig;
},
/**
@@ -645,27 +737,33 @@ cr.define('options.internet', function() {
return;
}
+ var connectable = onc.getActiveValue('Connectable');
var connectState = onc.getActiveValue('ConnectionState');
if (connectState == 'NotConnected') {
+ $('details-internet-disconnect').hidden = true;
$('details-internet-login').hidden = false;
// Connecting to an unconfigured network might trigger certificate
// installation UI. Until that gets handled here, always enable the
- // Connect button.
- $('details-internet-login').disabled = false;
- $('details-internet-disconnect').hidden = true;
+ // Connect button for built-in networks.
+ var enabled = (this.type_ != 'VPN') ||
+ (onc.getActiveValue('VPN.Type') != 'ThirdPartyVPN') ||
+ connectable;
+ $('details-internet-login').disabled = !enabled;
} else {
$('details-internet-login').hidden = true;
$('details-internet-disconnect').hidden = false;
}
- var connectable = onc.getActiveValue('Connectable');
- if (connectState != 'Connected' &&
- (!connectable || onc.getWiFiSecurity() != 'None' ||
- (this.type_ == 'WiMAX' || this.type_ == 'VPN'))) {
- $('details-internet-configure').hidden = false;
- } else {
- $('details-internet-configure').hidden = true;
+ var showConfigure = false;
+ if (this.type_ == 'VPN') {
+ showConfigure = true;
+ } else if (this.type_ == 'WiMAX' && connectState == 'NotConnected') {
+ showConfigure = true;
+ } else if (this.type_ == 'WiFi') {
+ showConfigure = (connectState == 'NotConnected' &&
+ (!connectable || onc.getWiFiSecurity() != 'None'));
}
+ $('details-internet-configure').hidden = !showConfigure;
},
/**
@@ -695,10 +793,10 @@ cr.define('options.internet', function() {
$('sim-card-lock-enabled').checked = lockEnabled;
$('change-pin').hidden = !lockEnabled;
}
- showViewAccount = onc.getActiveValue('showViewAccountButton');
+ showViewAccount = shouldShowViewAccountButton(onc);
var activationState = onc.getActiveValue('Cellular.ActivationState');
- showActivate = activationState == 'NotActivated' ||
- activationState == 'PartiallyActivated';
+ showActivate = (activationState == 'NotActivated' ||
+ activationState == 'PartiallyActivated');
}
$('view-account-details').hidden = !showViewAccount;
@@ -716,8 +814,9 @@ cr.define('options.internet', function() {
populateHeader_: function() {
var onc = this.onc_;
- $('network-details-title').textContent = onc.getTranslatedValue('Name');
- var connectionState = onc.getActiveValue('ConnectionState');
+ $('network-details-title').textContent =
+ this.networkTitle_ || onc.getTranslatedValue('Name');
+
var connectionStateString = onc.getTranslatedValue('ConnectionState');
$('network-details-subtitle-status').textContent = connectionStateString;
@@ -748,18 +847,29 @@ cr.define('options.internet', function() {
},
/**
- * Helper method called from initializeDetailsPage to initialize the Apn
- * list.
+ * Helper method to insert a 'user' option into the Apn list.
+ * @param {Object} userOption The 'user' apn dictionary
* @private
*/
- initializeApnList_: function() {
- var onc = this.onc_;
+ insertApnUserOption_: function(userOption) {
+ // Add the 'user' option before the last option ('other')
+ var apnSelector = $('select-apn');
+ assert(apnSelector.length > 0);
+ var otherOption = apnSelector[apnSelector.length - 1];
+ apnSelector.add(userOption, otherOption);
+ this.userApnIndex_ = apnSelector.length - 2;
+ this.selectedApnIndex_ = this.userApnIndex_;
+ },
+ /**
+ * Helper method called from initializeApnList to populate the Apn list.
+ * @param {Array} apnList List of available APNs.
+ * @private
+ */
+ populateApnList_: function(apnList) {
+ var onc = this.onc_;
var apnSelector = $('select-apn');
- // Clear APN lists, keep only last element that "other".
- while (apnSelector.length != 1) {
- apnSelector.remove(0);
- }
+ assert(apnSelector.length == 1);
var otherOption = apnSelector[0];
var activeApn = onc.getActiveValue('Cellular.APN.AccessPointName');
var activeUsername = onc.getActiveValue('Cellular.APN.Username');
@@ -770,7 +880,6 @@ cr.define('options.internet', function() {
onc.getActiveValue('Cellular.LastGoodAPN.Username');
var lastGoodPassword =
onc.getActiveValue('Cellular.LastGoodAPN.Password');
- var apnList = onc.getActiveValue('Cellular.APNList');
for (var i = 0; i < apnList.length; i++) {
var apnDict = apnList[i];
var option = document.createElement('option');
@@ -780,30 +889,58 @@ cr.define('options.internet', function() {
option.textContent =
name ? (name + ' (' + accessPointName + ')') : accessPointName;
option.value = i;
- // Insert new option before "other" option.
+ // Insert new option before 'other' option.
apnSelector.add(option, otherOption);
if (this.selectedApnIndex_ != -1)
continue;
- // If this matches the active Apn, or LastGoodApn (or there is no last
- // good APN), set it as the selected Apn.
- if ((activeApn == accessPointName &&
- activeUsername == apnDict['Username'] &&
- activePassword == apnDict['Password']) ||
- (!activeApn && !lastGoodApn) ||
- (!activeApn &&
- lastGoodApn == accessPointName &&
- lastGoodUsername == apnDict['Username'] &&
- lastGoodPassword == apnDict['Password'])) {
+ // If this matches the active Apn name, or LastGoodApn name (or there
+ // is no last good APN), set it as the selected Apn.
+ if ((activeApn == accessPointName) ||
+ (!activeApn && (!lastGoodApn || lastGoodApn == accessPointName))) {
this.selectedApnIndex_ = i;
}
}
if (this.selectedApnIndex_ == -1 && activeApn) {
- var activeOption = document.createElement('option');
- activeOption.textContent = activeApn;
- activeOption.value = -1;
- apnSelector.add(activeOption, otherOption);
- this.selectedApnIndex_ = apnSelector.length - 2;
- this.userApnIndex_ = this.selectedApnIndex_;
+ this.userApn_ = activeApn;
+ // Create a 'user' entry for any active apn not in the list.
+ var userOption = document.createElement('option');
+ userOption.textContent = activeApn;
+ userOption.value = -1;
+ this.insertApnUserOption_(userOption);
+ }
+ },
+
+ /**
+ * Helper method called from initializeDetailsPage to initialize the Apn
+ * list.
+ * @private
+ */
+ initializeApnList_: function() {
+ this.selectedApnIndex_ = -1;
+ this.userApnIndex_ = -1;
+
+ var onc = this.onc_;
+ var apnSelector = $('select-apn');
+
+ // Clear APN lists, keep only last element, 'other'.
+ while (apnSelector.length != 1)
+ apnSelector.remove(0);
+
+ var apnList = onc.getActiveValue('Cellular.APNList');
+ if (apnList) {
+ // Populate the list with the existing APNs.
+ this.populateApnList_(apnList);
+ } else {
+ // Create a single 'default' entry.
+ var otherOption = apnSelector[0];
+ var defaultOption = document.createElement('option');
+ defaultOption.textContent =
+ loadTimeData.getString('cellularApnUseDefault');
+ defaultOption.value = -1;
+ // Add 'default' entry before 'other' option
+ apnSelector.add(defaultOption, otherOption);
+ assert(apnSelector.length == 2); // 'default', 'other'
+ this.selectedApnIndex_ = 0; // Select 'default'
}
assert(this.selectedApnIndex_ >= 0);
apnSelector.selectedIndex = this.selectedApnIndex_;
@@ -812,37 +949,49 @@ cr.define('options.internet', function() {
},
/**
+ * Helper function for setting APN properties.
+ * @param {Object} apnValue Dictionary of APN properties.
+ * @private
+ */
+ setActiveApn_: function(apnValue) {
+ var activeApn = {};
+ var apnName = apnValue['AccessPointName'];
+ if (apnName) {
+ activeApn['AccessPointName'] = apnName;
+ activeApn['Username'] = stringFromValue(apnValue['Username']);
+ activeApn['Password'] = stringFromValue(apnValue['Password']);
+ }
+ // Set the cached ONC data.
+ this.onc_.setProperty('Cellular.APN', activeApn);
+ // Set an ONC object with just the APN values.
+ var oncData = new OncData({});
+ oncData.setProperty('Cellular.APN', activeApn);
+ chrome.networkingPrivate.setProperties(this.onc_.guid(),
+ oncData.getData());
+ },
+
+ /**
* Event Listener for the cellular-apn-use-default button.
* @private
*/
setDefaultApn_: function() {
- var onc = this.onc_;
var apnSelector = $('select-apn');
+ // Remove the 'user' entry if it exists.
if (this.userApnIndex_ != -1) {
+ assert(this.userApnIndex_ < apnSelector.length - 1);
apnSelector.remove(this.userApnIndex_);
this.userApnIndex_ = -1;
}
- var iApn = -1;
- var apnList = onc.getActiveValue('Cellular.APNList');
- if (apnList != undefined && apnList.length > 0) {
- iApn = 0;
- var defaultApn = apnList[iApn];
- var activeApn = {};
- activeApn['AccessPointName'] =
- stringFromValue(defaultApn['AccessPointName']);
- activeApn['Username'] = stringFromValue(defaultApn['Username']);
- activeApn['Password'] = stringFromValue(defaultApn['Password']);
- onc.setManagedProperty('Cellular.APN', activeApn);
- chrome.send('setApn', [this.servicePath_,
- activeApn['AccessPointName'],
- activeApn['Username'],
- activeApn['Password']]);
- }
+ var apnList = this.onc_.getActiveValue('Cellular.APNList');
+ var iApn = (apnList != undefined && apnList.length > 0) ? 0 : -1;
apnSelector.selectedIndex = iApn;
this.selectedApnIndex_ = iApn;
+ // Clear any user APN entry to inform Chrome to use the default APN.
+ this.setActiveApn_({});
+
updateHidden('.apn-list-view', false);
updateHidden('.apn-details-view', true);
},
@@ -855,32 +1004,29 @@ cr.define('options.internet', function() {
if (apnValue == '')
return;
- var onc = this.onc_;
var apnSelector = $('select-apn');
var activeApn = {};
activeApn['AccessPointName'] = stringFromValue(apnValue);
activeApn['Username'] = stringFromValue($('cellular-apn-username').value);
activeApn['Password'] = stringFromValue($('cellular-apn-password').value);
- onc.setManagedProperty('Cellular.APN', activeApn);
+ this.setActiveApn_(activeApn);
+ // Set the user selected APN.
this.userApn_ = activeApn;
- chrome.send('setApn', [this.servicePath_,
- activeApn['AccessPointName'],
- activeApn['Username'],
- activeApn['Password']]);
+ // Remove any existing 'user' entry.
if (this.userApnIndex_ != -1) {
+ assert(this.userApnIndex_ < apnSelector.length - 1);
apnSelector.remove(this.userApnIndex_);
this.userApnIndex_ = -1;
}
+ // Create a new 'user' entry with the new active apn.
var option = document.createElement('option');
option.textContent = activeApn['AccessPointName'];
option.value = -1;
option.selected = true;
- apnSelector.add(option, apnSelector[apnSelector.length - 1]);
- this.userApnIndex_ = apnSelector.length - 2;
- this.selectedApnIndex_ = this.userApnIndex_;
+ this.insertApnUserOption_(option);
updateHidden('.apn-list-view', false);
updateHidden('.apn-details-view', true);
@@ -890,11 +1036,7 @@ cr.define('options.internet', function() {
* Event Listener for the cellular-apn-cancel button.
* @private
*/
- cancelApn_: function() {
- $('select-apn').selectedIndex = this.selectedApnIndex_;
- updateHidden('.apn-list-view', false);
- updateHidden('.apn-details-view', true);
- },
+ cancelApn_: function() { this.initializeApnList_(); },
/**
* Event Listener for the select-apn button.
@@ -903,31 +1045,31 @@ cr.define('options.internet', function() {
selectApn_: function() {
var onc = this.onc_;
var apnSelector = $('select-apn');
- var apnDict;
if (apnSelector[apnSelector.selectedIndex].value != -1) {
var apnList = onc.getActiveValue('Cellular.APNList');
var apnIndex = apnSelector.selectedIndex;
assert(apnIndex < apnList.length);
- apnDict = apnList[apnIndex];
- chrome.send('setApn', [this.servicePath_,
- stringFromValue(apnDict['AccessPointName']),
- stringFromValue(apnDict['Username']),
- stringFromValue(apnDict['Password'])]);
this.selectedApnIndex_ = apnIndex;
+ this.setActiveApn_(apnList[apnIndex]);
} else if (apnSelector.selectedIndex == this.userApnIndex_) {
- apnDict = this.userApn_;
- chrome.send('setApn', [this.servicePath_,
- stringFromValue(apnDict['AccessPointName']),
- stringFromValue(apnDict['Username']),
- stringFromValue(apnDict['Password'])]);
this.selectedApnIndex_ = apnSelector.selectedIndex;
- } else {
- $('cellular-apn').value =
- stringFromValue(onc.getActiveValue('Cellular.APN.AccessPointName'));
- $('cellular-apn-username').value =
- stringFromValue(onc.getActiveValue('Cellular.APN.Username'));
- $('cellular-apn-password').value =
- stringFromValue(onc.getActiveValue('Cellular.APN.Password'));
+ this.setActiveApn_(this.userApn_);
+ } else { // 'Other'
+ var apnDict;
+ if (this.userApn_['AccessPointName']) {
+ // Fill in the details fields with the existing 'user' config.
+ apnDict = this.userApn_;
+ } else {
+ // No 'user' config, use the current values.
+ apnDict = {};
+ apnDict['AccessPointName'] =
+ onc.getActiveValue('Cellular.APN.AccessPointName');
+ apnDict['Username'] = onc.getActiveValue('Cellular.APN.Username');
+ apnDict['Password'] = onc.getActiveValue('Cellular.APN.Password');
+ }
+ $('cellular-apn').value = stringFromValue(apnDict['AccessPointName']);
+ $('cellular-apn-username').value = stringFromValue(apnDict['Username']);
+ $('cellular-apn-password').value = stringFromValue(apnDict['Password']);
updateHidden('.apn-list-view', true);
updateHidden('.apn-details-view', false);
}
@@ -956,6 +1098,8 @@ cr.define('options.internet', function() {
* Shows a spinner while the carrier is changed.
*/
DetailsInternetPage.showCarrierChangeSpinner = function(visible) {
+ if (!DetailsInternetPage.getInstance().visible)
+ return;
$('switch-carrier-spinner').hidden = !visible;
// Disable any buttons that allow us to operate on cellular networks.
DetailsInternetPage.changeCellularButtonsState(visible);
@@ -968,8 +1112,37 @@ cr.define('options.internet', function() {
var carrierSelector = $('select-carrier');
var carrier = carrierSelector[carrierSelector.selectedIndex].textContent;
DetailsInternetPage.showCarrierChangeSpinner(true);
- chrome.send('setCarrier', [
- DetailsInternetPage.getInstance().servicePath_, carrier]);
+ var guid = DetailsInternetPage.getInstance().onc_.guid();
+ var oncData = new OncData({});
+ oncData.setProperty('Cellular.Carrier', carrier);
+ chrome.networkingPrivate.setProperties(guid, oncData.getData(), function() {
+ // Start activation or show the activation UI after changing carriers.
+ DetailsInternetPage.activateCellular(guid);
+ });
+ };
+
+ /**
+ * If the network is not already activated, starts the activation process or
+ * shows the activation UI. Otherwise does nothing.
+ */
+ DetailsInternetPage.activateCellular = function(guid) {
+ chrome.networkingPrivate.getProperties(guid, function(properties) {
+ var oncData = new OncData(properties);
+ if (oncData.getActiveValue('Cellular.ActivationState') == 'Activated') {
+ DetailsInternetPage.showCarrierChangeSpinner(false);
+ return;
+ }
+ var carrier = oncData.getActiveValue('Cellular.Carrier');
+ if (carrier == CarrierSprint) {
+ // Sprint is directly ativated, call startActivate().
+ chrome.networkingPrivate.startActivate(guid, '', function() {
+ DetailsInternetPage.showCarrierChangeSpinner(false);
+ });
+ } else {
+ DetailsInternetPage.showCarrierChangeSpinner(false);
+ chrome.send('showMorePlanInfo', [guid]);
+ }
+ });
};
/**
@@ -1038,19 +1211,65 @@ cr.define('options.internet', function() {
}
};
- DetailsInternetPage.updateCarrier = function() {
- DetailsInternetPage.showCarrierChangeSpinner(false);
+ DetailsInternetPage.loginFromDetails = function() {
+ DetailsInternetPage.configureOrConnect();
+ PageManager.closeOverlay();
};
- DetailsInternetPage.loginFromDetails = function() {
+ /**
+ * This function identifies unconfigured networks and networks that are
+ * likely to fail (e.g. due to a bad passphrase on a previous connect
+ * attempt). For such networks a configure dialog will be opened. Otherwise
+ * a connection will be attempted.
+ */
+ DetailsInternetPage.configureOrConnect = function() {
var detailsPage = DetailsInternetPage.getInstance();
if (detailsPage.type_ == 'WiFi')
sendChromeMetricsAction('Options_NetworkConnectToWifi');
else if (detailsPage.type_ == 'VPN')
sendChromeMetricsAction('Options_NetworkConnectToVPN');
- // TODO(stevenjb): chrome.networkingPrivate.disableNetworkType
- chrome.send('startConnect', [detailsPage.servicePath_]);
- PageManager.closeOverlay();
+
+ var onc = detailsPage.onc_;
+ var guid = onc.guid();
+ var type = onc.getActiveValue('Type');
+
+ // Built-in VPNs do not correctly set 'Connectable', so we always show the
+ // configuration UI.
+ if (type == 'VPN') {
+ if (onc.getActiveValue('VPN.Type') != 'ThirdPartyVPN') {
+ chrome.send('configureNetwork', [guid]);
+ return;
+ }
+ }
+
+ // If 'Connectable' is false for WiFi or WiMAX, Shill requires
+ // additional configuration to connect, so show the configuration UI.
+ if ((type == 'WiFi' || type == 'WiMAX') &&
+ !onc.getActiveValue('Connectable')) {
+ chrome.send('configureNetwork', [guid]);
+ return;
+ }
+
+ // Secure WiFi networks with ErrorState set most likely require
+ // configuration (e.g. a correct passphrase) before connecting.
+ if (type == 'WiFi' && onc.getWiFiSecurity() != 'None') {
+ var errorState = onc.getActiveValue('ErrorState');
+ if (errorState && errorState != 'Unknown') {
+ chrome.send('configureNetwork', [guid]);
+ return;
+ }
+ }
+
+ // Cellular networks need to be activated before they can be connected to.
+ if (type == 'Cellular') {
+ var activationState = onc.getActiveValue('Cellular.ActivationState');
+ if (activationState != 'Activated' && activationState != 'Unknown') {
+ DetailsInternetPage.activateCellular(guid);
+ return;
+ }
+ }
+
+ chrome.networkingPrivate.startConnect(guid);
};
DetailsInternetPage.disconnectNetwork = function() {
@@ -1059,22 +1278,20 @@ cr.define('options.internet', function() {
sendChromeMetricsAction('Options_NetworkDisconnectWifi');
else if (detailsPage.type_ == 'VPN')
sendChromeMetricsAction('Options_NetworkDisconnectVPN');
- // TODO(stevenjb): chrome.networkingPrivate.startDisconnect
- chrome.send('startDisconnect', [detailsPage.servicePath_]);
+ chrome.networkingPrivate.startDisconnect(detailsPage.onc_.guid());
PageManager.closeOverlay();
};
DetailsInternetPage.configureNetwork = function() {
var detailsPage = DetailsInternetPage.getInstance();
- chrome.send('configureNetwork', [detailsPage.servicePath_]);
+ chrome.send('configureNetwork', [detailsPage.onc_.guid()]);
PageManager.closeOverlay();
};
DetailsInternetPage.activateFromDetails = function() {
var detailsPage = DetailsInternetPage.getInstance();
- if (detailsPage.type_ == 'Cellular') {
- chrome.send('activateNetwork', [detailsPage.servicePath_]);
- }
+ if (detailsPage.type_ == 'Cellular')
+ DetailsInternetPage.activateCellular(detailsPage.onc_.guid());
PageManager.closeOverlay();
};
@@ -1085,33 +1302,38 @@ cr.define('options.internet', function() {
DetailsInternetPage.setDetails = function() {
var detailsPage = DetailsInternetPage.getInstance();
var type = detailsPage.type_;
- var servicePath = detailsPage.servicePath_;
+ var oncData = new OncData({});
+ var autoConnectCheckboxId = '';
if (type == 'WiFi') {
- sendCheckedIfEnabled(servicePath,
- 'setPreferNetwork',
- 'prefer-network-wifi',
- 'Options_NetworkSetPrefer');
- sendCheckedIfEnabled(servicePath,
- 'setAutoConnect',
- 'auto-connect-network-wifi',
- 'Options_NetworkAutoConnect');
+ var preferredCheckbox =
+ assertInstanceof($('prefer-network-wifi'), HTMLInputElement);
+ if (!preferredCheckbox.hidden && !preferredCheckbox.disabled) {
+ var kPreferredPriority = 1;
+ var priority = preferredCheckbox.checked ? kPreferredPriority : 0;
+ oncData.setProperty('Priority', priority);
+ sendChromeMetricsAction('Options_NetworkSetPrefer');
+ }
+ autoConnectCheckboxId = 'auto-connect-network-wifi';
} else if (type == 'WiMAX') {
- sendCheckedIfEnabled(servicePath,
- 'setAutoConnect',
- 'auto-connect-network-wimax',
- 'Options_NetworkAutoConnect');
+ autoConnectCheckboxId = 'auto-connect-network-wimax';
} else if (type == 'Cellular') {
- sendCheckedIfEnabled(servicePath,
- 'setAutoConnect',
- 'auto-connect-network-cellular',
- 'Options_NetworkAutoConnect');
+ autoConnectCheckboxId = 'auto-connect-network-cellular';
} else if (type == 'VPN') {
- chrome.send('setServerHostname',
- [servicePath, $('inet-server-hostname').value]);
- sendCheckedIfEnabled(servicePath,
- 'setAutoConnect',
- 'auto-connect-network-vpn',
- 'Options_NetworkAutoConnect');
+ var providerType = detailsPage.onc_.getActiveValue('VPN.Type');
+ if (providerType != 'ThirdPartyVPN') {
+ oncData.setProperty('VPN.Type', providerType);
+ oncData.setProperty('VPN.Host', $('inet-server-hostname').value);
+ autoConnectCheckboxId = 'auto-connect-network-vpn';
+ }
+ }
+ if (autoConnectCheckboxId != '') {
+ var autoConnectCheckbox =
+ assertInstanceof($(autoConnectCheckboxId), HTMLInputElement);
+ if (!autoConnectCheckbox.hidden && !autoConnectCheckbox.disabled) {
+ var autoConnectKey = type + '.AutoConnect';
+ oncData.setProperty(autoConnectKey, !!autoConnectCheckbox.checked);
+ sendChromeMetricsAction('Options_NetworkAutoConnect');
+ }
}
var nameServerTypes = ['automatic', 'google', 'user'];
@@ -1122,7 +1344,18 @@ cr.define('options.internet', function() {
break;
}
}
- detailsPage.sendIpConfig_(nameServerType);
+ var ipConfig = detailsPage.getIpConfig_(nameServerType);
+ var ipAddressType = ('IPAddress' in ipConfig) ? 'Static' : 'DHCP';
+ var nameServersType = ('NameServers' in ipConfig) ? 'Static' : 'DHCP';
+ oncData.setProperty('IPAddressConfigType', ipAddressType);
+ oncData.setProperty('NameServersConfigType', nameServersType);
+ oncData.setProperty('StaticIPConfig', ipConfig);
+
+ var data = oncData.getData();
+ if (Object.keys(data).length > 0) {
+ // TODO(stevenjb): Only set changed properties.
+ chrome.networkingPrivate.setProperties(detailsPage.onc_.guid(), data);
+ }
PageManager.closeOverlay();
};
@@ -1174,7 +1407,7 @@ cr.define('options.internet', function() {
if (!detailsPage.visible)
return;
- if (oncData.servicePath != detailsPage.servicePath_)
+ if (oncData.GUID != detailsPage.onc_.guid())
return;
// Update our cached data object.
@@ -1186,17 +1419,6 @@ cr.define('options.internet', function() {
};
/**
- * Method called from Chrome in response to getManagedProperties.
- * We only use this when we want to call initializeDetailsPage.
- * TODO(stevenjb): Eliminate when we switch to networkingPrivate
- * (initializeDetailsPage will be provided as the callback).
- * @param {Object} oncData Dictionary of ONC properties.
- */
- DetailsInternetPage.getManagedPropertiesResult = function(oncData) {
- DetailsInternetPage.initializeDetailsPage(oncData);
- };
-
- /**
* Initializes the details page with the provided ONC data.
* @param {Object} oncData Dictionary of ONC properties.
*/
@@ -1204,13 +1426,22 @@ cr.define('options.internet', function() {
var onc = new OncData(oncData);
var detailsPage = DetailsInternetPage.getInstance();
- detailsPage.servicePath_ = oncData.servicePath;
detailsPage.onc_ = onc;
var type = onc.getActiveValue('Type');
detailsPage.type_ = type;
sendShowDetailsMetrics(type, onc.getActiveValue('ConnectionState'));
+ if (type == 'VPN') {
+ // Cache the dialog title, which will contain the provider name in the
+ // case of a third-party VPN provider. This caching is important as the
+ // provider may go away while the details dialog is being shown, causing
+ // subsequent updates to be unable to determine the correct title.
+ detailsPage.networkTitle_ = options.VPNProviders.formatNetworkName(onc);
+ } else {
+ delete detailsPage.networkTitle_;
+ }
+
detailsPage.populateHeader_();
detailsPage.updateConnectionButtonVisibilty_();
detailsPage.updateDetails_();
@@ -1223,7 +1454,7 @@ cr.define('options.internet', function() {
if (remembered) {
detailsPage.showProxy_ = true;
// Inform Chrome which network to use for proxy configuration.
- chrome.send('selectNetwork', [detailsPage.servicePath_]);
+ chrome.send('selectNetwork', [detailsPage.onc_.guid()]);
} else {
detailsPage.showProxy_ = false;
}
@@ -1234,6 +1465,10 @@ cr.define('options.internet', function() {
var restrictedString = loadTimeData.getString(
restricted ? 'restrictedYes' : 'restrictedNo');
+ // These objects contain an 'automatic' property that is displayed when
+ // ip-automatic-configuration-checkbox is checked, and a 'value' property
+ // that is displayed when unchecked and used to set the associated ONC
+ // property for StaticIPConfig on commit.
var inetAddress = {};
var inetNetmask = {};
var inetGateway = {};
@@ -1272,13 +1507,14 @@ cr.define('options.internet', function() {
}
}
- // Override the "automatic" values with the real saved DHCP values,
- // if they are set.
+ // Override the 'automatic' properties with the saved DHCP values if the
+ // saved value is set, and set any unset 'value' properties.
var savedNameServersString;
var savedIpAddress = onc.getActiveValue('SavedIPConfig.IPAddress');
if (savedIpAddress != undefined) {
inetAddress.automatic = savedIpAddress;
- inetAddress.value = savedIpAddress;
+ if (!inetAddress.value)
+ inetAddress.value = savedIpAddress;
}
var savedPrefix = onc.getActiveValue('SavedIPConfig.RoutingPrefix');
if (savedPrefix != undefined) {
@@ -1286,13 +1522,16 @@ cr.define('options.internet', function() {
var savedNetmask = prefixLengthToNetmask(
/** @type {number} */(savedPrefix));
inetNetmask.automatic = savedNetmask;
- inetNetmask.value = savedNetmask;
+ if (!inetNetmask.value)
+ inetNetmask.value = savedNetmask;
}
var savedGateway = onc.getActiveValue('SavedIPConfig.Gateway');
if (savedGateway != undefined) {
inetGateway.automatic = savedGateway;
- inetGateway.value = savedGateway;
+ if (!inetGateway.value)
+ inetGateway.value = savedGateway;
}
+
var savedNameServers = onc.getActiveValue('SavedIPConfig.NameServers');
if (savedNameServers) {
savedNameServers = savedNameServers.sort();
@@ -1300,29 +1539,28 @@ cr.define('options.internet', function() {
}
var ipAutoConfig = 'automatic';
-
- var staticNameServersString;
- var staticIpAddress = onc.getActiveValue('StaticIPConfig.IPAddress');
- if (staticIpAddress != undefined) {
+ if (onc.getActiveValue('IPAddressConfigType') == 'Static') {
ipAutoConfig = 'user';
+ var staticIpAddress = onc.getActiveValue('StaticIPConfig.IPAddress');
inetAddress.user = staticIpAddress;
inetAddress.value = staticIpAddress;
- }
- var staticPrefix = onc.getActiveValue('StaticIPConfig.RoutingPrefix');
- if (staticPrefix != undefined) {
- assert(typeof staticPrefix == 'number');
+
+ var staticPrefix = onc.getActiveValue('StaticIPConfig.RoutingPrefix');
+ if (typeof staticPrefix != 'number')
+ staticPrefix = 0;
var staticNetmask = prefixLengthToNetmask(
- /** @type {number} */(staticPrefix));
+ /** @type {number} */ (staticPrefix));
inetNetmask.user = staticNetmask;
inetNetmask.value = staticNetmask;
- }
- var staticGateway = onc.getActiveValue('StaticIPConfig.Gateway');
- if (staticGateway != undefined) {
+
+ var staticGateway = onc.getActiveValue('StaticIPConfig.Gateway');
inetGateway.user = staticGateway;
inetGateway.value = staticGateway;
}
- var staticNameServers = onc.getActiveValue('StaticIPConfig.NameServers');
- if (staticNameServers) {
+
+ var staticNameServersString;
+ if (onc.getActiveValue('NameServersConfigType') == 'Static') {
+ var staticNameServers = onc.getActiveValue('StaticIPConfig.NameServers');
staticNameServers = staticNameServers.sort();
staticNameServersString = staticNameServers.join(',');
}
@@ -1343,21 +1581,26 @@ cr.define('options.internet', function() {
configureAddressField($('ip-netmask'), inetNetmask);
configureAddressField($('ip-gateway'), inetGateway);
- // Set Nameserver fields.
+ // Set Nameserver fields. Nameservers are 'automatic' by default. If a
+ // static namerserver is set, use that unless it does not match a non
+ // empty 'NameServers' value (indicating that the custom nameservers are
+ // invalid or not being applied for some reason). TODO(stevenjb): Only
+ // set these properites if they change so that invalid custom values do
+ // not get lost.
var nameServerType = 'automatic';
- if (staticNameServersString) {
- // If static nameservers are defined and match the google name servers,
- // show that in the UI, otherwise show the custom static nameservers.
- if (staticNameServersString == GoogleNameServersString)
+ if (staticNameServersString &&
+ (!inetNameServersString ||
+ staticNameServersString == inetNameServersString)) {
+ if (staticNameServersString == GoogleNameServers.join(','))
nameServerType = 'google';
- else if (staticNameServersString == inetNameServersString)
+ else
nameServerType = 'user';
}
if (nameServerType == 'automatic')
$('automatic-dns-display').textContent = inetNameServersString;
else
$('automatic-dns-display').textContent = savedNameServersString;
- $('google-dns-display').textContent = GoogleNameServersString;
+ $('google-dns-display').textContent = GoogleNameServers.join(',');
var nameServersUser = [];
if (staticNameServers) {
@@ -1476,13 +1719,13 @@ cr.define('options.internet', function() {
if (currentCarrierIndex == -1)
$('service-name').textContent = networkName;
+ // TODO(stevenjb): Ideally many of these should be localized.
$('network-technology').textContent =
onc.getActiveValue('Cellular.NetworkTechnology');
$('roaming-state').textContent =
onc.getTranslatedValue('Cellular.RoamingState');
$('cellular-restricted-connectivity').textContent = restrictedString;
- // 'errorMessage' is a non ONC property added by Chrome.
- $('error-state').textContent = onc.getActiveValue('errorMessage');
+ $('error-state').textContent = onc.getActiveValue('ErrorState');
$('manufacturer').textContent =
onc.getActiveValue('Cellular.Manufacturer');
$('model-id').textContent = onc.getActiveValue('Cellular.ModelID');
@@ -1527,33 +1770,50 @@ cr.define('options.internet', function() {
$('auto-connect-network-cellular').disabled = false;
} else if (type == 'VPN') {
OptionsPage.showTab($('vpn-nav-tab'));
+ var providerType = onc.getActiveValue('VPN.Type');
+ var isThirdPartyVPN = providerType == 'ThirdPartyVPN';
+ $('vpn-tab').classList.toggle('third-party-vpn-provider',
+ isThirdPartyVPN);
+
$('inet-service-name').textContent = networkName;
$('inet-provider-type').textContent =
onc.getTranslatedValue('VPN.Type');
- var providerType = onc.getActiveValue('VPN.Type');
- var usernameKey;
- if (providerType == 'OpenVPN')
- usernameKey = 'VPN.OpenVPN.Username';
- else if (providerType == 'L2TP-IPsec')
- usernameKey = 'VPN.L2TP.Username';
-
- if (usernameKey) {
- $('inet-username').parentElement.hidden = false;
- $('inet-username').textContent = onc.getActiveValue(usernameKey);
+
+ if (isThirdPartyVPN) {
+ $('inet-provider-name').textContent = '';
+ var extensionID = onc.getActiveValue('VPN.ThirdPartyVPN.ExtensionID');
+ var providers = options.VPNProviders.getProviders();
+ for (var i = 0; i < providers.length; ++i) {
+ if (extensionID == providers[i].extensionID) {
+ $('inet-provider-name').textContent = providers[i].name;
+ break;
+ }
+ }
} else {
- $('inet-username').parentElement.hidden = true;
+ var usernameKey;
+ if (providerType == 'OpenVPN')
+ usernameKey = 'VPN.OpenVPN.Username';
+ else if (providerType == 'L2TP-IPsec')
+ usernameKey = 'VPN.L2TP.Username';
+
+ if (usernameKey) {
+ $('inet-username').parentElement.hidden = false;
+ $('inet-username').textContent = onc.getActiveValue(usernameKey);
+ } else {
+ $('inet-username').parentElement.hidden = true;
+ }
+ var inetServerHostname = $('inet-server-hostname');
+ inetServerHostname.value = onc.getActiveValue('VPN.Host');
+ inetServerHostname.resetHandler = function() {
+ PageManager.hideBubble();
+ var recommended = onc.getRecommendedValue('VPN.Host');
+ if (recommended != undefined)
+ inetServerHostname.value = recommended;
+ };
+ $('auto-connect-network-vpn').checked =
+ onc.getActiveValue('VPN.AutoConnect');
+ $('auto-connect-network-vpn').disabled = false;
}
- var inetServerHostname = $('inet-server-hostname');
- inetServerHostname.value = onc.getActiveValue('VPN.Host');
- inetServerHostname.resetHandler = function() {
- PageManager.hideBubble();
- var recommended = onc.getRecommendedValue('VPN.Host');
- if (recommended != undefined)
- inetServerHostname.value = recommended;
- };
- $('auto-connect-network-vpn').checked =
- onc.getActiveValue('VPN.AutoConnect');
- $('auto-connect-network-vpn').disabled = false;
} else {
OptionsPage.showTab($('internet-nav-tab'));
}
@@ -1582,7 +1842,7 @@ cr.define('options.internet', function() {
/** @type {{value: *, controlledBy: *, recommendedValue: *}} */(
propValue));
indicators[i].handlePrefChange(event);
- var forElement = $(indicators[i].getAttribute('for'));
+ var forElement = $(indicators[i].getAttribute('internet-detail-for'));
if (forElement) {
if (event.value.controlledBy == 'policy')
forElement.disabled = true;
diff --git a/chromium/chrome/browser/resources/options/chromeos/keyboard_overlay.js b/chromium/chrome/browser/resources/options/chromeos/keyboard_overlay.js
index ae4baa03efe..b18af10166e 100644
--- a/chromium/chrome/browser/resources/options/chromeos/keyboard_overlay.js
+++ b/chromium/chrome/browser/resources/options/chromeos/keyboard_overlay.js
@@ -8,7 +8,7 @@ cr.define('options', function() {
* Auto-repeat delays (in ms) for the corresponding slider values, from
* long to short. The values were chosen to provide a large range while giving
* several options near the defaults.
- * @type {!Array.<number>}
+ * @type {!Array<number>}
* @const
*/
var AUTO_REPEAT_DELAYS =
@@ -18,7 +18,7 @@ cr.define('options', function() {
* Auto-repeat intervals (in ms) for the corresponding slider values, from
* long to short. The slider itself is labeled "rate", the inverse of
* interval, and goes from slow (long interval) to fast (short interval).
- * @type {!Array.<number>}
+ * @type {!Array<number>}
* @const
*/
var AUTO_REPEAT_INTERVALS =
@@ -138,7 +138,7 @@ cr.define('options', function() {
* |value|.
* @param {string} id The slider's ID.
* @param {number} value The value to find.
- * @param {!Array.<number>} values The array to search.
+ * @param {!Array<number>} values The array to search.
* @private
*/
updateSliderFromValue_: function(id, value, values) {
diff --git a/chromium/chrome/browser/resources/options/chromeos/network_list.js b/chromium/chrome/browser/resources/options/chromeos/network_list.js
index 4c65b342717..bcd947ad165 100644
--- a/chromium/chrome/browser/resources/options/chromeos/network_list.js
+++ b/chromium/chrome/browser/resources/options/chromeos/network_list.js
@@ -3,15 +3,23 @@
// found in the LICENSE file.
/**
+ * Partial definition of the result of networkingPrivate.getProperties()).
* @typedef {{
* ConnectionState: string,
- * iconURL: string,
- * policyManaged: boolean,
- * servicePath: string
+ * Cellular: {
+ * Family: ?string,
+ * SIMPresent: ?boolean,
+ * SIMLockStatus: { LockType: ?string },
+ * SupportNetworkScan: ?boolean
+ * },
+ * GUID: string,
+ * Name: string,
+ * Source: string,
+ * Type: string
* }}
- * @see chrome/browser/ui/webui/options/chromeos/internet_options_handler.cc
+ * @see extensions/common/api/networking_private.idl
*/
-var NetworkInfo;
+var NetworkProperties;
cr.define('options.network', function() {
var ArrayDataModel = cr.ui.ArrayDataModel;
@@ -28,12 +36,19 @@ cr.define('options.network', function() {
*/
function Constants() {}
- // Cellular activation states:
- Constants.ACTIVATION_STATE_UNKNOWN = 0;
- Constants.ACTIVATION_STATE_ACTIVATED = 1;
- Constants.ACTIVATION_STATE_ACTIVATING = 2;
- Constants.ACTIVATION_STATE_NOT_ACTIVATED = 3;
- Constants.ACTIVATION_STATE_PARTIALLY_ACTIVATED = 4;
+ /**
+ * Valid network type names.
+ */
+ Constants.NETWORK_TYPES = ['Ethernet', 'WiFi', 'WiMAX', 'Cellular', 'VPN'];
+
+ /**
+ * Helper function to check whether |type| is a valid network type.
+ * @param {string} type A string that may contain a valid network type.
+ * @return {boolean} Whether the string represents a valid network type.
+ */
+ function isNetworkType(type) {
+ return (Constants.NETWORK_TYPES.indexOf(type) != -1);
+ }
/**
* Order in which controls are to appear in the network list sorted by key.
@@ -53,53 +68,39 @@ cr.define('options.network', function() {
var activeMenu_ = null;
/**
- * Indicates if cellular networks are available.
- * @type {boolean}
+ * The state of the cellular device or undefined if not available.
+ * @type {string|undefined}
* @private
*/
- var cellularAvailable_ = false;
+ var cellularDeviceState_ = undefined;
/**
- * Indicates if cellular networks are enabled.
- * @type {boolean}
+ * The active cellular network or null if none.
+ * @type {?NetworkProperties}
* @private
*/
- var cellularEnabled_ = false;
+ var cellularNetwork_ = null;
/**
- * Indicates if cellular device supports network scanning.
- * @type {boolean}
+ * The active ethernet network or null if none.
+ * @type {?NetworkProperties}
* @private
*/
- var cellularSupportsScan_ = false;
+ var ethernetNetwork_ = null;
/**
- * Indicates the current SIM lock type of the cellular device.
- * @type {string}
+ * The state of the WiFi device or undefined if not available.
+ * @type {string|undefined}
* @private
*/
- var cellularSimLockType_ = '';
+ var wifiDeviceState_ = undefined;
/**
- * Indicates whether the SIM card is absent on the cellular device.
- * @type {boolean}
+ * The state of the WiMAX device or undefined if not available.
+ * @type {string|undefined}
* @private
*/
- var cellularSimAbsent_ = false;
-
- /**
- * Indicates if WiMAX networks are available.
- * @type {boolean}
- * @private
- */
- var wimaxAvailable_ = false;
-
- /**
- * Indicates if WiMAX networks are enabled.
- * @type {boolean}
- * @private
- */
- var wimaxEnabled_ = false;
+ var wimaxDeviceState_ = undefined;
/**
* Indicates if mobile data roaming is enabled.
@@ -109,19 +110,14 @@ cr.define('options.network', function() {
var enableDataRoaming_ = false;
/**
- * Icon to use when not connected to a particular type of network.
- * @type {!Object.<string, string>} Mapping of network type to icon data url.
- * @private
- */
- var defaultIcons_ = {};
-
- /**
* Returns the display name for 'network'.
- * @param {Object} data The network data dictionary.
+ * @param {NetworkProperties} data The network data dictionary.
*/
function getNetworkName(data) {
if (data.Type == 'Ethernet')
return loadTimeData.getString('ethernetName');
+ if (data.Type == 'VPN')
+ return options.VPNProviders.formatNetworkName(new cr.onc.OncData(data));
return data.Name;
}
@@ -149,13 +145,11 @@ cr.define('options.network', function() {
}
/**
- * @param {string} servicePath The network service path.
+ * @param {string} guid The network GUID.
*/
- function showDetails(servicePath) {
- // TODO(stevenjb): chrome.networkingPrivate.getManagedProperties
- // (Note: we will need to provide DetailsInternetPage.initializeDetailsPage
- // as the callback).
- chrome.send('getManagedProperties', [servicePath]);
+ function showDetails(guid) {
+ chrome.networkingPrivate.getManagedProperties(
+ guid, DetailsInternetPage.initializeDetailsPage);
}
/**
@@ -172,7 +166,7 @@ cr.define('options.network', function() {
/**
* Description of the network group or control.
- * @type {Object.<string,Object>}
+ * @type {Object<string,Object>}
* @private
*/
data_: null,
@@ -185,18 +179,11 @@ cr.define('options.network', function() {
subtitle_: null,
/**
- * Icon for the network control.
+ * Div containing the list item icon.
* @type {?Element}
* @private
*/
- icon_: null,
-
- /**
- * Indicates if in the process of connecting to a network.
- * @type {boolean}
- * @private
- */
- connecting_: false,
+ iconDiv_: null,
/**
* Description of the network control.
@@ -217,42 +204,52 @@ cr.define('options.network', function() {
},
/**
- * URL for the network icon.
- * @type {string}
+ * Sets the icon based on a network state object.
+ * @param {!NetworkProperties} data Network state properties.
*/
- set iconURL(iconURL) {
- this.icon_.style.backgroundImage = url(iconURL);
+ set iconData(data) {
+ if (!isNetworkType(data.Type))
+ return;
+ var networkIcon = this.getNetworkIcon();
+ networkIcon.networkState = CrOncDataElement.create(
+ /** @type {chrome.networkingPrivate.NetworkStateProperties} */ (
+ data));
},
/**
- * Type of network icon. Each type corresponds to a CSS rule.
+ * Sets the icon based on a network type or a special type indecator, e.g.
+ * 'add-connection'
* @type {string}
*/
set iconType(type) {
- if (defaultIcons_[type])
- this.iconURL = defaultIcons_[type];
- else
- this.icon_.classList.add('network-' + type.toLowerCase());
- },
-
- /**
- * Indicates if the network is in the process of being connected.
- * @type {boolean}
- */
- set connecting(state) {
- this.connecting_ = state;
- if (state)
- this.icon_.classList.add('network-connecting');
- else
- this.icon_.classList.remove('network-connecting');
+ if (isNetworkType(type)) {
+ var networkIcon = this.getNetworkIcon();
+ networkIcon.networkType = type;
+ } else {
+ // Special cases. e.g. 'add-connection'. Background images are
+ // defined in browser_options.css.
+ var oldIcon = /** @type {CrNetworkIconElement} */ (
+ this.iconDiv_.querySelector('cr-network-icon'));
+ if (oldIcon)
+ this.iconDiv_.removeChild(oldIcon);
+ this.iconDiv_.classList.add('network-' + type.toLowerCase());
+ }
},
/**
- * Indicates if the network is in the process of being connected.
- * @type {boolean}
+ * Returns any existing network icon for the list item or creates a new one.
+ * @return {!CrNetworkIconElement} The network icon for the list item.
*/
- get connecting() {
- return this.connecting_;
+ getNetworkIcon: function() {
+ var networkIcon = /** @type {CrNetworkIconElement} */ (
+ this.iconDiv_.querySelector('cr-network-icon'));
+ if (!networkIcon) {
+ networkIcon = /** @type {!CrNetworkIconElement} */ (
+ document.createElement('cr-network-icon'));
+ networkIcon.isListItem = false;
+ this.iconDiv_.appendChild(networkIcon);
+ }
+ return networkIcon;
},
/**
@@ -281,9 +278,9 @@ cr.define('options.network', function() {
decorate: function() {
ListItem.prototype.decorate.call(this);
this.className = 'network-group';
- this.icon_ = this.ownerDocument.createElement('div');
- this.icon_.className = 'network-icon';
- this.appendChild(this.icon_);
+ this.iconDiv_ = this.ownerDocument.createElement('div');
+ this.iconDiv_.className = 'network-icon';
+ this.appendChild(this.iconDiv_);
var textContent = this.ownerDocument.createElement('div');
textContent.className = 'network-group-labels';
this.appendChild(textContent);
@@ -330,9 +327,9 @@ cr.define('options.network', function() {
this.subtitle = null;
if (this.data.iconType)
this.iconType = this.data.iconType;
- this.addEventListener('click', function() {
+ this.addEventListener('click', (function() {
this.showMenu();
- });
+ }).bind(this));
},
/**
@@ -353,6 +350,7 @@ cr.define('options.network', function() {
menu.className = 'network-menu';
menu.hidden = true;
Menu.decorate(menu);
+ menu.menuItemSelector = '.network-menu-item';
for (var i = 0; i < this.data.menu.length; i++) {
var entry = this.data.menu[i];
createCallback_(menu, null, entry.label, entry.command);
@@ -362,11 +360,29 @@ cr.define('options.network', function() {
return null;
},
+ /**
+ * Determines if a menu can be updated on the fly. Menus that cannot be
+ * updated are fully regenerated using createMenu. The advantage of
+ * updating a menu is that it can preserve ordering of networks avoiding
+ * entries from jumping around after an update.
+ * @return {boolean} Whether the menu can be updated on the fly.
+ */
canUpdateMenu: function() {
return false;
},
/**
+ * Removes the current menu contents, causing it to be regenerated when the
+ * menu is shown the next time. If the menu is showing right now, its
+ * contents are regenerated immediately and the menu remains visible.
+ */
+ refreshMenu: function() {
+ this.menu_ = null;
+ if (activeMenu_ == this.getMenuName())
+ this.showMenu();
+ },
+
+ /**
* Displays a popup menu.
*/
showMenu: function() {
@@ -380,7 +396,7 @@ cr.define('options.network', function() {
rebuild = true;
var existing = $(this.getMenuName());
if (existing) {
- if (this.updateMenu())
+ if (this.canUpdateMenu() && this.updateMenu())
return;
closeMenu_();
}
@@ -404,8 +420,7 @@ cr.define('options.network', function() {
this.menu_.hidden = false;
}
if (rescan) {
- // TODO(stevenjb): chrome.networkingPrivate.requestNetworkScan
- chrome.send('requestNetworkScan');
+ chrome.networkingPrivate.requestNetworkScan();
}
}
};
@@ -413,8 +428,8 @@ cr.define('options.network', function() {
/**
* Creates a control for selecting or configuring a network connection based
* on the type of connection (e.g. wifi versus vpn).
- * @param {{key: string, networkList: Array.<NetworkInfo>}} data Description
- * of the network.
+ * @param {{key: string, networkList: Array<!NetworkProperties>}} data
+ * An object containing the network type (key) and an array of networks.
* @constructor
* @extends {NetworkMenuItem}
*/
@@ -425,42 +440,84 @@ cr.define('options.network', function() {
return el;
}
+ /**
+ * Returns true if |source| is a policy managed source.
+ * @param {string} source The ONC source of a network.
+ * @return {boolean} Whether |source| is a managed source.
+ */
+ function isManaged(source) {
+ return (source == 'DevicePolicy' || source == 'UserPolicy');
+ }
+
+ /**
+ * Returns true if |network| is visible.
+ * @param {!chrome.networkingPrivate.NetworkStateProperties} network The
+ * network state properties.
+ * @return {boolean} Whether |network| is visible.
+ */
+ function networkIsVisible(network) {
+ if (network.Type == 'WiFi')
+ return !!(network.WiFi && (network.WiFi.SignalStrength > 0));
+ if (network.Type == 'WiMAX')
+ return !!(network.WiMAX && (network.WiMAX.SignalStrength > 0));
+ // Other network types are always considered 'visible'.
+ return true;
+ }
+
+ /**
+ * Returns true if |cellular| is a GSM network with no sim present.
+ * @param {?NetworkProperties} cellular The network state properties.
+ * @return {boolean} Whether |network| is missing a SIM card.
+ */
+ function isCellularSimAbsent(cellular) {
+ if (!cellular || !cellular.Cellular)
+ return false;
+ return cellular.Cellular.Family == 'GSM' && !cellular.Cellular.SIMPresent;
+ }
+
+ /**
+ * Returns true if |cellular| has a locked SIM card.
+ * @param {?NetworkProperties} cellular The network state properties.
+ * @return {boolean} Whether |network| has a locked SIM card.
+ */
+ function isCellularSimLocked(cellular) {
+ if (!cellular || !cellular.Cellular)
+ return false;
+ var simLockStatus = cellular.Cellular.SIMLockStatus;
+ return !!(simLockStatus && simLockStatus.LockType);
+ }
+
NetworkSelectorItem.prototype = {
__proto__: NetworkMenuItem.prototype,
/** @override */
decorate: function() {
// TODO(kevers): Generalize method of setting default label.
- var policyManaged = false;
this.subtitle = loadTimeData.getString('OncConnectionStateNotConnected');
var list = this.data_.networkList;
- var candidateURL = null;
+ var candidateData = null;
for (var i = 0; i < list.length; i++) {
var networkDetails = list[i];
if (networkDetails.ConnectionState == 'Connecting' ||
networkDetails.ConnectionState == 'Connected') {
this.subtitle = getNetworkName(networkDetails);
this.setSubtitleDirection('ltr');
- policyManaged = networkDetails.policyManaged;
- candidateURL = networkDetails.iconURL;
+ candidateData = networkDetails;
// Only break when we see a connecting network as it is possible to
// have a connected network and a connecting network at the same
// time.
- if (networkDetails.ConnectionState == 'Connecting') {
- this.connecting = true;
- candidateURL = null;
+ if (networkDetails.ConnectionState == 'Connecting')
break;
- }
}
}
- if (candidateURL)
- this.iconURL = candidateURL;
+ if (candidateData)
+ this.iconData = candidateData;
else
this.iconType = this.data.key;
this.showSelector();
- if (policyManaged)
+ if (candidateData && isManaged(candidateData.Source))
this.showManagedNetworkIndicator();
if (activeMenu_ == this.getMenuName()) {
@@ -484,18 +541,21 @@ cr.define('options.network', function() {
menu.className = 'network-menu';
menu.hidden = true;
Menu.decorate(menu);
+ menu.menuItemSelector = '.network-menu-item';
var addendum = [];
if (this.data_.key == 'WiFi') {
addendum.push({
label: loadTimeData.getString('joinOtherNetwork'),
- command: createAddConnectionCallback_('WiFi'),
+ command: createAddNonVPNConnectionCallback_('WiFi'),
data: {}
});
} else if (this.data_.key == 'Cellular') {
- if (cellularEnabled_ && cellularSupportsScan_) {
+ if (cellularDeviceState_ == 'Enabled' &&
+ cellularNetwork_ && cellularNetwork_.Cellular &&
+ cellularNetwork_.Cellular.SupportNetworkScan) {
addendum.push({
label: loadTimeData.getString('otherCellularNetworks'),
- command: createAddConnectionCallback_('Cellular'),
+ command: createAddNonVPNConnectionCallback_('Cellular'),
addClass: ['other-cellulars'],
data: {}
});
@@ -522,11 +582,7 @@ cr.define('options.network', function() {
}
addendum.push(entry);
} else if (this.data_.key == 'VPN') {
- addendum.push({
- label: loadTimeData.getString('joinOtherNetwork'),
- command: createAddConnectionCallback_('VPN'),
- data: {}
- });
+ addendum = addendum.concat(createAddVPNConnectionEntries_());
}
var list = this.data.rememberedNetworks;
@@ -548,22 +604,21 @@ cr.define('options.network', function() {
list = this.data.networkList;
var empty = !list || list.length == 0;
if (list) {
- var connectedVpnServicePath = '';
+ var connectedVpnGuid = '';
for (var i = 0; i < list.length; i++) {
var data = list[i];
this.createNetworkOptionsCallback_(networkGroup, data);
// For VPN only, append a 'Disconnect' item to the dropdown menu.
- if (!connectedVpnServicePath && data.Type == 'VPN' &&
+ if (!connectedVpnGuid && data.Type == 'VPN' &&
(data.ConnectionState == 'Connected' ||
data.ConnectionState == 'Connecting')) {
- connectedVpnServicePath = data.servicePath;
+ connectedVpnGuid = data.GUID;
}
}
- if (connectedVpnServicePath) {
+ if (connectedVpnGuid) {
var disconnectCallback = function() {
sendChromeMetricsAction('Options_NetworkDisconnectVPN');
- // TODO(stevenjb): chrome.networkingPrivate.startDisconnect
- chrome.send('startDisconnect', [connectedVpnServicePath]);
+ chrome.networkingPrivate.startDisconnect(connectedVpnGuid);
};
// Add separator
addendum.push({});
@@ -580,24 +635,21 @@ cr.define('options.network', function() {
label: loadTimeData.getString('turnOffWifi'),
command: function() {
sendChromeMetricsAction('Options_NetworkWifiToggle');
- // TODO(stevenjb): chrome.networkingPrivate.disableNetworkType
- chrome.send('disableNetworkType', ['WiFi']);
+ chrome.networkingPrivate.disableNetworkType('WiFi');
},
data: {}});
} else if (this.data_.key == 'WiMAX') {
addendum.push({
label: loadTimeData.getString('turnOffWimax'),
command: function() {
- // TODO(stevenjb): chrome.networkingPrivate.disableNetworkType
- chrome.send('disableNetworkType', ['WiMAX']);
+ chrome.networkingPrivate.disableNetworkType('WiMAX');
},
data: {}});
} else if (this.data_.key == 'Cellular') {
addendum.push({
label: loadTimeData.getString('turnOffCellular'),
command: function() {
- // TODO(stevenjb): chrome.networkingPrivate.disableNetworkType
- chrome.send('disableNetworkType', ['Cellular']);
+ chrome.networkingPrivate.disableNetworkType('Cellular');
},
data: {}});
}
@@ -629,12 +681,7 @@ cr.define('options.network', function() {
return menu;
},
- /**
- * Determines if a menu can be updated on the fly. Menus that cannot be
- * updated are fully regenerated using createMenu. The advantage of
- * updating a menu is that it can preserve ordering of networks avoiding
- * entries from jumping around after an update.
- */
+ /** @override */
canUpdateMenu: function() {
return this.data_.key == 'WiFi' && activeMenu_ == this.getMenuName();
},
@@ -645,12 +692,11 @@ cr.define('options.network', function() {
* preferred ordering as determined by the network library. If the
* ordering becomes potentially out of sync, then the updated menu is
* marked for disposal on close. Reopening the menu will force a
- * regeneration, which will in turn fix the ordering.
+ * regeneration, which will in turn fix the ordering. This method must only
+ * be called if canUpdateMenu() returned |true|.
* @return {boolean} True if successfully updated.
*/
updateMenu: function() {
- if (!this.canUpdateMenu())
- return false;
var oldMenu = $(this.getMenuName());
var group = oldMenu.getElementsByClassName('network-menu-group')[0];
if (!group)
@@ -687,7 +733,7 @@ cr.define('options.network', function() {
/**
* Extracts a mapping of network names to menu element and position.
* @param {!Element} menu The menu to process.
- * @return {Object.<string, ?{index: number, button: Element}>}
+ * @return {Object<string, ?{index: number, button: Element}>}
* Network mapping.
* @private
*/
@@ -707,17 +753,15 @@ cr.define('options.network', function() {
/**
* Adds a menu item for showing network details.
* @param {!Element} parent The parent element.
- * @param {Object} data Description of the network.
+ * @param {NetworkProperties} data Description of the network.
* @private
*/
createNetworkOptionsCallback_: function(parent, data) {
- var servicePath = data.servicePath;
var menuItem = createCallback_(parent,
data,
getNetworkName(data),
- showDetails.bind(null, servicePath),
- data.iconURL);
- if (data.policyManaged)
+ showDetails.bind(null, data.GUID));
+ if (isManaged(data.Source))
menuItem.appendChild(new ManagedNetworkIndicator());
if (data.ConnectionState == 'Connected' ||
data.ConnectionState == 'Connecting') {
@@ -753,11 +797,11 @@ cr.define('options.network', function() {
this.subtitle = null;
if (this.data.command)
this.addEventListener('click', this.data.command);
- if (this.data.iconURL)
- this.iconURL = this.data.iconURL;
+ if (this.data.iconData)
+ this.iconData = this.data.iconData;
else if (this.data.iconType)
this.iconType = this.data.iconType;
- if (this.data.policyManaged)
+ if (isManaged(this.data.Source))
this.showManagedNetworkIndicator();
},
};
@@ -765,22 +809,26 @@ cr.define('options.network', function() {
/**
* Adds a command to a menu for modifying network settings.
* @param {!Element} menu Parent menu.
- * @param {Object} data Description of the network.
+ * @param {?NetworkProperties} data Description of the network.
* @param {!string} label Display name for the menu item.
* @param {!Function} command Callback function.
- * @param {string=} opt_iconURL Optional URL to an icon for the menu item.
* @return {!Element} The created menu item.
* @private
*/
- function createCallback_(menu, data, label, command, opt_iconURL) {
+ function createCallback_(menu, data, label, command) {
var button = menu.ownerDocument.createElement('div');
button.className = 'network-menu-item';
- var buttonIcon = menu.ownerDocument.createElement('div');
- buttonIcon.className = 'network-menu-item-icon';
- button.appendChild(buttonIcon);
- if (opt_iconURL)
- buttonIcon.style.backgroundImage = url(opt_iconURL);
+ var buttonIconDiv = menu.ownerDocument.createElement('div');
+ buttonIconDiv.className = 'network-icon';
+ button.appendChild(buttonIconDiv);
+ if (data && isNetworkType(data.Type)) {
+ var networkIcon = /** @type {!CrNetworkIconElement} */ (
+ document.createElement('cr-network-icon'));
+ buttonIconDiv.appendChild(networkIcon);
+ networkIcon.isListItem = true;
+ networkIcon.networkState = CrOncDataElement.create(data);
+ }
var buttonLabel = menu.ownerDocument.createElement('span');
buttonLabel.className = 'network-menu-item-label';
@@ -801,7 +849,7 @@ cr.define('options.network', function() {
}
}
if (callback != null)
- button.addEventListener('click', callback);
+ button.addEventListener('activate', callback);
else
buttonLabel.classList.add('network-disabled-control');
@@ -835,18 +883,7 @@ cr.define('options.network', function() {
// Wi-Fi control is always visible.
this.update({key: 'WiFi', networkList: []});
- var entryAddWifi = {
- label: loadTimeData.getString('addConnectionWifi'),
- command: createAddConnectionCallback_('WiFi')
- };
- var entryAddVPN = {
- label: loadTimeData.getString('addConnectionVPN'),
- command: createAddConnectionCallback_('VPN')
- };
- this.update({key: 'addConnection',
- iconType: 'add-connection',
- menu: [entryAddWifi, entryAddVPN]
- });
+ this.updateAddConnectionMenuEntries_();
var prefs = options.Preferences.getInstance();
prefs.addEventListener('cros.signed.data_roaming_enabled',
@@ -854,6 +891,65 @@ cr.define('options.network', function() {
enableDataRoaming_ = event.value.value;
});
this.endBatchUpdates();
+
+ this.onNetworkListChanged_(); // Trigger an initial network update
+
+ chrome.networkingPrivate.onNetworkListChanged.addListener(
+ this.onNetworkListChanged_.bind(this));
+ chrome.networkingPrivate.onDeviceStateListChanged.addListener(
+ this.onNetworkListChanged_.bind(this));
+
+ chrome.networkingPrivate.requestNetworkScan();
+
+ options.VPNProviders.addObserver(this.onVPNProvidersChanged_.bind(this));
+ },
+
+ /**
+ * networkingPrivate event called when the network list has changed.
+ */
+ onNetworkListChanged_: function() {
+ var networkList = this;
+ chrome.networkingPrivate.getDeviceStates(function(deviceStates) {
+ var filter = { networkType: 'All' };
+ chrome.networkingPrivate.getNetworks(filter, function(networkStates) {
+ networkList.updateNetworkStates(deviceStates, networkStates);
+ });
+ });
+ },
+
+ /**
+ * Called when the list of VPN providers changes. Refreshes the contents of
+ * menus that list VPN providers.
+ * @private
+ */
+ onVPNProvidersChanged_: function() {
+ // Refresh the contents of the VPN menu.
+ var index = this.indexOf('VPN');
+ if (index != undefined)
+ this.getListItemByIndex(index).refreshMenu();
+
+ // Refresh the contents of the "add connection" menu.
+ this.updateAddConnectionMenuEntries_();
+ index = this.indexOf('addConnection');
+ if (index != undefined)
+ this.getListItemByIndex(index).refreshMenu();
+ },
+
+ /**
+ * Updates the entries in the "add connection" menu, based on the VPN
+ * providers currently enabled in the primary user's profile.
+ * @private
+ */
+ updateAddConnectionMenuEntries_: function() {
+ var entries = [{
+ label: loadTimeData.getString('addConnectionWifi'),
+ command: createAddNonVPNConnectionCallback_('WiFi')
+ }];
+ entries = entries.concat(createAddVPNConnectionEntries_());
+ this.update({key: 'addConnection',
+ iconType: 'add-connection',
+ menu: entries
+ });
},
/**
@@ -866,6 +962,35 @@ cr.define('options.network', function() {
closeMenu_();
},
+ /** @override */
+ handleKeyDown: function(e) {
+ if (activeMenu_) {
+ // keyIdentifier does not report 'Esc' correctly
+ if (e.keyCode == 27 /* Esc */) {
+ closeMenu_();
+ return;
+ }
+
+ if ($(activeMenu_).handleKeyDown(e)) {
+ e.preventDefault();
+ e.stopPropagation();
+ }
+ return;
+ }
+
+ if (e.keyIdentifier == 'Enter' ||
+ e.keyIdentifier == 'U+0020' /* Space */) {
+ var selectedListItem = this.getListItemByIndex(
+ this.selectionModel.selectedIndex);
+ if (selectedListItem) {
+ selectedListItem.click();
+ return;
+ }
+ }
+
+ List.prototype.handleKeyDown.call(this, e);
+ },
+
/**
* Close bubble and menu when a different list item is selected.
* @param {Event} event Event detailing the selection change.
@@ -908,7 +1033,7 @@ cr.define('options.network', function() {
/**
* Updates a network control.
- * @param {Object.<string,string>} data Description of the entry.
+ * @param {Object} data Description of the entry.
*/
update: function(data) {
this.startBatchUpdates();
@@ -956,8 +1081,8 @@ cr.define('options.network', function() {
createItem: function(entry) {
if (entry.networkList)
return new NetworkSelectorItem(
- /** @type {{key: string, networkList: Array.<NetworkInfo>}} */(
- entry));
+ /** @type {{key: string, networkList: Array<!NetworkProperties>}} */
+ (entry));
if (entry.command)
return new NetworkButtonItem(
/** @type {{key: string, subtitle: string, command: Function}} */(
@@ -986,100 +1111,125 @@ cr.define('options.network', function() {
var index = this.indexOf(key);
if (index != undefined) {
var entry = this.dataModel.item(index);
- entry.iconType = active ? 'control-active' :
- 'control-inactive';
+ entry.iconType = active ? 'control-active' : 'control-inactive';
this.update(entry);
}
- }
- };
+ },
- /**
- * Sets the default icon to use for each network type if disconnected.
- * @param {!Object.<string, string>} data Mapping of network type to icon
- * data url.
- */
- NetworkList.setDefaultNetworkIcons = function(data) {
- defaultIcons_ = Object.create(data);
- };
+ /**
+ * Updates the state of network devices and services.
+ * @param {!Array<{State: string, Type: string}>} deviceStates The result
+ * from networkingPrivate.getDeviceStates.
+ * @param {!Array<!chrome.networkingPrivate.NetworkStateProperties>}
+ * networkStates The result from networkingPrivate.getNetworks.
+ */
+ updateNetworkStates: function(deviceStates, networkStates) {
+ // Update device states.
+ cellularDeviceState_ = undefined;
+ wifiDeviceState_ = undefined;
+ wimaxDeviceState_ = undefined;
+ for (var i = 0; i < deviceStates.length; ++i) {
+ var device = deviceStates[i];
+ var type = device.Type;
+ var state = device.State;
+ if (type == 'Cellular')
+ cellularDeviceState_ = cellularDeviceState_ || state;
+ else if (type == 'WiFi')
+ wifiDeviceState_ = wifiDeviceState_ || state;
+ else if (type == 'WiMAX')
+ wimaxDeviceState_ = wimaxDeviceState_ || state;
+ }
- /**
- * Chrome callback for updating network controls.
- * @param {{cellularAvailable: boolean,
- * cellularEnabled: boolean,
- * cellularSimAbsent: boolean,
- * cellularSimLockType: string,
- * cellularSupportsScan: boolean,
- * rememberedList: Array.<NetworkInfo>,
- * vpnList: Array.<NetworkInfo>,
- * wifiAvailable: boolean,
- * wifiEnabled: boolean,
- * wimaxAvailable: boolean,
- * wimaxEnabled: boolean,
- * wiredList: Array.<NetworkInfo>,
- * wirelessList: Array.<NetworkInfo>}} data Description of available
- * network devices and their corresponding state.
- */
- NetworkList.refreshNetworkData = function(data) {
- var networkList = $('network-list');
- networkList.startBatchUpdates();
- cellularAvailable_ = data.cellularAvailable;
- cellularEnabled_ = data.cellularEnabled;
- cellularSupportsScan_ = data.cellularSupportsScan;
- cellularSimAbsent_ = data.cellularSimAbsent;
- cellularSimLockType_ = data.cellularSimLockType;
- wimaxAvailable_ = data.wimaxAvailable;
- wimaxEnabled_ = data.wimaxEnabled;
-
- // Only show Ethernet control if connected.
- var ethernetConnection = getConnection_(data.wiredList);
- if (ethernetConnection) {
- var type = String('Ethernet');
- var path = ethernetConnection.servicePath;
- var ethernetOptions = function() {
- showDetails(path);
- };
- networkList.update(
+ // Update active network states.
+ cellularNetwork_ = null;
+ ethernetNetwork_ = null;
+ for (var i = 0; i < networkStates.length; i++) {
+ // Note: This cast is valid since
+ // networkingPrivate.NetworkStateProperties is a subset of
+ // NetworkProperties and all missing properties are optional.
+ var entry = /** @type {NetworkProperties} */ (networkStates[i]);
+ switch (entry.Type) {
+ case 'Cellular':
+ cellularNetwork_ = cellularNetwork_ || entry;
+ break;
+ case 'Ethernet':
+ ethernetNetwork_ = ethernetNetwork_ || entry;
+ break;
+ }
+ if (cellularNetwork_ && ethernetNetwork_)
+ break;
+ }
+
+ if (cellularNetwork_ && cellularNetwork_.GUID) {
+ // Get the complete set of cellular properties which includes SIM and
+ // Scan properties.
+ var networkList = this;
+ chrome.networkingPrivate.getProperties(
+ cellularNetwork_.GUID, function(cellular) {
+ cellularNetwork_ = /** @type {NetworkProperties} */ (cellular);
+ networkList.updateControls(networkStates);
+ });
+ } else {
+ this.updateControls(networkStates);
+ }
+ },
+
+ /**
+ * Updates network controls.
+ * @param {!Array<!chrome.networkingPrivate.NetworkStateProperties>}
+ * networkStates The result from networkingPrivate.getNetworks.
+ */
+ updateControls: function(networkStates) {
+ this.startBatchUpdates();
+
+ // Only show Ethernet control if connected.
+ if (ethernetNetwork_ && ethernetNetwork_.ConnectionState == 'Connected') {
+ var ethernetOptions = showDetails.bind(null, ethernetNetwork_.GUID);
+ this.update(
{ key: 'Ethernet',
subtitle: loadTimeData.getString('OncConnectionStateConnected'),
- iconURL: ethernetConnection.iconURL,
+ iconData: ethernetNetwork_,
command: ethernetOptions,
- policyManaged: ethernetConnection.policyManaged }
- );
- } else {
- networkList.deleteItem('Ethernet');
- }
-
- if (data.wifiEnabled)
- loadData_('WiFi', data.wirelessList, data.rememberedList);
- else
- addEnableNetworkButton_('WiFi');
+ Source: ethernetNetwork_.Source }
+ );
+ } else {
+ this.deleteItem('Ethernet');
+ }
- // Only show cellular control if available.
- if (data.cellularAvailable) {
- if (data.cellularEnabled)
- loadData_('Cellular', data.wirelessList, data.rememberedList);
+ if (wifiDeviceState_ == 'Enabled')
+ loadData_('WiFi', networkStates);
else
- addEnableNetworkButton_('Cellular');
- } else {
- networkList.deleteItem('Cellular');
- }
+ addEnableNetworkButton_('WiFi');
+
+ // Only show cellular control if available.
+ if (cellularDeviceState_) {
+ if (cellularDeviceState_ == 'Enabled' &&
+ !isCellularSimLocked(cellularNetwork_) &&
+ !isCellularSimAbsent(cellularNetwork_)) {
+ loadData_('Cellular', networkStates);
+ } else {
+ addEnableNetworkButton_('Cellular');
+ }
+ } else {
+ this.deleteItem('Cellular');
+ }
- // Only show wimax control if available. Uses cellular icons.
- if (data.wimaxAvailable) {
- if (data.wimaxEnabled)
- loadData_('WiMAX', data.wirelessList, data.rememberedList);
- else
- addEnableNetworkButton_('WiMAX');
- } else {
- networkList.deleteItem('WiMAX');
- }
+ // Only show wimax control if available. Uses cellular icons.
+ if (wimaxDeviceState_) {
+ if (wimaxDeviceState_ == 'Enabled')
+ loadData_('WiMAX', networkStates);
+ else
+ addEnableNetworkButton_('WiMAX');
+ } else {
+ this.deleteItem('WiMAX');
+ }
- // Only show VPN control if there is at least one VPN configured.
- if (data.vpnList.length > 0)
- loadData_('VPN', data.vpnList, data.rememberedList);
- else
- networkList.deleteItem('VPN');
- networkList.endBatchUpdates();
+ // Only show VPN control if there is at least one VPN configured.
+ if (loadData_('VPN', networkStates) == 0)
+ this.deleteItem('VPN');
+
+ this.endBatchUpdates();
+ }
};
/**
@@ -1089,25 +1239,23 @@ cr.define('options.network', function() {
*/
function addEnableNetworkButton_(type) {
var subtitle = loadTimeData.getString('networkDisabled');
- var icon = (type == 'WiMAX') ? 'Cellular' : type;
var enableNetwork = function() {
if (type == 'WiFi')
sendChromeMetricsAction('Options_NetworkWifiToggle');
if (type == 'Cellular') {
- if (cellularSimLockType_) {
+ if (isCellularSimLocked(cellularNetwork_)) {
chrome.send('simOperation', ['unlock']);
return;
- } else if (cellularEnabled_ && cellularSimAbsent_) {
+ } else if (isCellularSimAbsent(cellularNetwork_)) {
chrome.send('simOperation', ['configure']);
return;
}
}
- // TODO(stevenjb): chrome.networkingPrivate.enableNetworkType
- chrome.send('enableNetworkType', [type]);
+ chrome.networkingPrivate.enableNetworkType(type);
};
$('network-list').update({key: type,
subtitle: subtitle,
- iconType: icon,
+ iconType: type,
command: enableNetwork});
}
@@ -1171,26 +1319,34 @@ cr.define('options.network', function() {
* Updates the list of available networks and their status, filtered by
* network type.
* @param {string} type The type of network.
- * @param {Array} available The list of available networks and their status.
- * @param {Array} remembered The list of remmebered networks.
+ * @param {Array<!chrome.networkingPrivate.NetworkStateProperties>} networks
+ * The list of network objects.
+ * @return {number} The number of visible networks matching |type|.
*/
- function loadData_(type, available, remembered) {
- var data = {key: type};
+ function loadData_(type, networks) {
+ var res = 0;
var availableNetworks = [];
- for (var i = 0; i < available.length; i++) {
- if (available[i].Type == type)
- availableNetworks.push(available[i]);
- }
- data.networkList = availableNetworks;
- if (remembered) {
- var rememberedNetworks = [];
- for (var i = 0; i < remembered.length; i++) {
- if (remembered[i].Type == type)
- rememberedNetworks.push(remembered[i]);
+ var rememberedNetworks = [];
+ for (var i = 0; i < networks.length; i++) {
+ var network = networks[i];
+ if (network.Type != type)
+ continue;
+ if (networkIsVisible(network)) {
+ availableNetworks.push(network);
+ ++res;
+ }
+ if ((type == 'WiFi' || type == 'VPN') && network.Source &&
+ network.Source != 'None') {
+ rememberedNetworks.push(network);
}
- data.rememberedNetworks = rememberedNetworks;
}
+ var data = {
+ key: type,
+ networkList: availableNetworks,
+ rememberedNetworks: rememberedNetworks
+ };
$('network-list').update(data);
+ return res;
}
/**
@@ -1208,40 +1364,63 @@ cr.define('options.network', function() {
}
/**
- * Fetches the active connection.
- * @param {Array.<Object>} networkList List of networks.
- * @return {Object}
+ * Creates a callback function that adds a new connection of the given type.
+ * This method may be used for all network types except VPN.
+ * @param {string} type An ONC network type
+ * @return {function()} The created callback.
* @private
*/
- function getConnection_(networkList) {
- if (!networkList)
- return null;
- for (var i = 0; i < networkList.length; i++) {
- var entry = networkList[i];
- if (entry.ConnectionState == 'Connected' ||
- entry.ConnectionState == 'Connecting')
- return entry;
- }
- return null;
+ function createAddNonVPNConnectionCallback_(type) {
+ return function() {
+ if (type == 'WiFi')
+ sendChromeMetricsAction('Options_NetworkJoinOtherWifi');
+ chrome.send('addNonVPNConnection', [type]);
+ };
}
/**
- * Create a callback function that adds a new connection of the given type.
- * @param {string} type An ONC network type
+ * Creates a callback function that shows the "add network" dialog for a VPN
+ * provider. If |opt_extensionID| is omitted, the dialog for the built-in
+ * OpenVPN/L2TP provider is shown. Otherwise, |opt_extensionID| identifies the
+ * third-party provider for which the dialog should be shown.
+ * @param {string=} opt_extensionID Extension ID identifying the third-party
+ * VPN provider for which the dialog should be shown.
* @return {function()} The created callback.
* @private
*/
- function createAddConnectionCallback_(type) {
+ function createVPNConnectionCallback_(opt_extensionID) {
return function() {
- if (type == 'WiFi')
- sendChromeMetricsAction('Options_NetworkJoinOtherWifi');
- else if (type == 'VPN')
- sendChromeMetricsAction('Options_NetworkJoinOtherVPN');
- chrome.send('addConnection', [type]);
+ sendChromeMetricsAction(opt_extensionID ?
+ 'Options_NetworkAddVPNThirdParty' :
+ 'Options_NetworkAddVPNBuiltIn');
+ chrome.send('addVPNConnection',
+ opt_extensionID ? [opt_extensionID] : undefined);
};
}
/**
+ * Generates an "add network" entry for each VPN provider currently enabled in
+ * the primary user's profile.
+ * @return {!Array<{label: string, command: function(), data: !Object}>} The
+ * list of entries.
+ * @private
+ */
+ function createAddVPNConnectionEntries_() {
+ var entries = [];
+ var providers = options.VPNProviders.getProviders();
+ for (var i = 0; i < providers.length; ++i) {
+ entries.push({
+ label: loadTimeData.getStringF('addConnectionVPNTemplate',
+ providers[i].name),
+ command: createVPNConnectionCallback_(
+ providers[i].extensionID || undefined),
+ data: {}
+ });
+ }
+ return entries;
+ }
+
+ /**
* Whether the Network list is disabled. Only used for display purpose.
*/
cr.defineProperty(NetworkList, 'disabled', cr.PropertyKind.BOOL_ATTR);
diff --git a/chromium/chrome/browser/resources/options/chromeos/onc_data.js b/chromium/chrome/browser/resources/options/chromeos/onc_data.js
index d6dfcf647fb..8114dbd2a33 100644
--- a/chromium/chrome/browser/resources/options/chromeos/onc_data.js
+++ b/chromium/chrome/browser/resources/options/chromeos/onc_data.js
@@ -21,6 +21,8 @@ cr.define('cr.onc', function() {
}
OncData.prototype = {
+ /** @return {string} The GUID of the network. */
+ guid: function() { return this.data_['GUID']; },
/**
* Returns either a managed property dictionary or an unmanaged value.
@@ -47,9 +49,9 @@ cr.define('cr.onc', function() {
* Sets the value of a property. Currently only supports unmanaged
* properties.
* @param {string} key The property key.
- * @param {Object} value The property value to set.
+ * @param {?} value The property value to set.
*/
- setManagedProperty: function(key, value) {
+ setProperty: function(key, value) {
var data = this.data_;
while (true) {
var index = key.indexOf('.');
@@ -177,6 +179,13 @@ cr.define('cr.onc', function() {
return property[effective];
}
return undefined;
+ },
+
+ /**
+ * Returns the complete ONC dictionary.
+ */
+ getData: function() {
+ return this.data_;
}
};
diff --git a/chromium/chrome/browser/resources/options/chromeos/preferred_networks.js b/chromium/chrome/browser/resources/options/chromeos/preferred_networks.js
index 8dd24ecf06c..4f5a152b03a 100644
--- a/chromium/chrome/browser/resources/options/chromeos/preferred_networks.js
+++ b/chromium/chrome/browser/resources/options/chromeos/preferred_networks.js
@@ -5,7 +5,7 @@
cr.exportPath('options');
/**
- * @typedef {{Name: string, Type: string, servicePath: string}}
+ * @typedef {{GUID: string, Name: string, Source: string, Type: string}}
*/
options.PreferredNetwork;
@@ -83,8 +83,10 @@ cr.define('options', function() {
DeletableItem.prototype.decorate.call(this);
var label = this.ownerDocument.createElement('div');
label.textContent = this.data.Name;
- if (this.data.policyManaged)
+ if (this.data.Source == 'DevicePolicy' ||
+ this.data.Source == 'UserPolicy') {
this.deletable = false;
+ }
this.contentElement.appendChild(label);
}
};
@@ -125,11 +127,8 @@ cr.define('options', function() {
/** @override */
deleteItemAtIndex: function(index) {
var item = this.dataModel.item(index);
- if (item) {
- // TODO(stevenjb): Add removeNetwork to chrome.networkingPrivate and
- // use that here.
- chrome.send('removeNetwork', [item.servicePath]);
- }
+ if (item)
+ chrome.networkingPrivate.forgetNetwork(item.GUID);
this.dataModel.splice(index, 1);
// Invalidate the list since it has a stale cache after a splice
// involving a deletion.
diff --git a/chromium/chrome/browser/resources/options/chromeos/vpn_providers.js b/chromium/chrome/browser/resources/options/chromeos/vpn_providers.js
new file mode 100644
index 00000000000..06da41b5339
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/chromeos/vpn_providers.js
@@ -0,0 +1,123 @@
+// Copyright 2015 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 A singleton that keeps track of the VPN providers enabled in
+ * the primary user's profile.
+ */
+
+cr.define('options', function() {
+ /**
+ * @constructor
+ */
+ function VPNProviders() {
+ }
+
+ cr.addSingletonGetter(VPNProviders);
+
+ VPNProviders.prototype = {
+ /**
+ * The VPN providers enabled in the primary user's profile. Each provider
+ * has a name. Third-party VPN providers additionally have an extension ID.
+ * @type {!Array<{name: string, extensionID: ?string}>}
+ * @private
+ */
+ providers_: [],
+
+ /**
+ * Observers who will be called when the list of VPN providers changes.
+ * @type {!Array<!function()>}
+ */
+ observers_: [],
+
+ /**
+ * The VPN providers enabled in the primary user's profile.
+ * @type {!Array<{name: string, extensionID: ?string}>}
+ */
+ get providers() {
+ return this.providers_;
+ },
+ set providers(providers) {
+ this.providers_ = providers;
+ for (var i = 0; i < this.observers_.length; ++i)
+ this.observers_[i]();
+ },
+
+ /**
+ * Adds an observer to be called when the list of VPN providers changes.
+ * @param {!function()} observer The observer to add.
+ * @private
+ */
+ addObserver_: function(observer) {
+ this.observers_.push(observer);
+ },
+
+ /**
+ * Formats a network name for display purposes. If the network belongs to
+ * a third-party VPN provider, the provider name is added to the network
+ * name.
+ * @param {cr.onc.OncData} onc ONC data describing this network.
+ * @return {string} The resulting display name.
+ * @private
+ */
+ formatNetworkName_: function(onc) {
+ var networkName = onc.getTranslatedValue('Name');
+ if (onc.getActiveValue('VPN.Type') != 'ThirdPartyVPN')
+ return networkName;
+ var extensionID = onc.getActiveValue('VPN.ThirdPartyVPN.ExtensionID');
+ for (var i = 0; i < this.providers_.length; ++i) {
+ if (extensionID == this.providers_[i].extensionID) {
+ return loadTimeData.getStringF('vpnNameTemplate',
+ this.providers_[i].name,
+ networkName);
+ }
+ }
+ return networkName;
+ },
+ };
+
+ /**
+ * Adds an observer to be called when the list of VPN providers changes. Note
+ * that an observer may in turn call setProviders() but should be careful not
+ * to get stuck in an infinite loop as every change to the list of VPN
+ * providers will cause the observers to be called again.
+ * @param {!function()} observer The observer to add.
+ */
+ VPNProviders.addObserver = function(observer) {
+ VPNProviders.getInstance().addObserver_(observer);
+ };
+
+ /**
+ * Returns the list of VPN providers enabled in the primary user's profile.
+ * @return {!Array<{name: string, extensionID: ?string}>} The list of VPN
+ * providers enabled in the primary user's profile.
+ */
+ VPNProviders.getProviders = function() {
+ return VPNProviders.getInstance().providers;
+ };
+
+ /**
+ * Replaces the list of VPN providers enabled in the primary user's profile.
+ * @param {!Array<{name: string, extensionID: ?string}>} providers The list
+ * of VPN providers enabled in the primary user's profile.
+ */
+ VPNProviders.setProviders = function(providers) {
+ VPNProviders.getInstance().providers = providers;
+ };
+
+ /**
+ * Formats a network name for display purposes. If the network belongs to a
+ * third-party VPN provider, the provider name is added to the network name.
+ * @param {cr.onc.OncData} onc ONC data describing this network.
+ * @return {string} The resulting display name.
+ */
+ VPNProviders.formatNetworkName = function(onc) {
+ return VPNProviders.getInstance().formatNetworkName_(onc);
+ };
+
+ // Export
+ return {
+ VPNProviders: VPNProviders
+ };
+});
diff --git a/chromium/chrome/browser/resources/options/clear_browser_data_overlay.css b/chromium/chrome/browser/resources/options/clear_browser_data_overlay.css
index 171d42a415b..0820188ceed 100644
--- a/chromium/chrome/browser/resources/options/clear_browser_data_overlay.css
+++ b/chromium/chrome/browser/resources/options/clear_browser_data_overlay.css
@@ -12,8 +12,10 @@
}
#cbd-throbber {
- margin: 4px 10px;
- vertical-align: middle;
+ /* This should be changed to align-self: center; when the parent is changed to
+ * display: flex; */
+ position: relative;
+ top: 5px;
visibility: hidden;
}
diff --git a/chromium/chrome/browser/resources/options/clear_browser_data_overlay.html b/chromium/chrome/browser/resources/options/clear_browser_data_overlay.html
index b018b03bbed..001bfdc81f6 100644
--- a/chromium/chrome/browser/resources/options/clear_browser_data_overlay.html
+++ b/chromium/chrome/browser/resources/options/clear_browser_data_overlay.html
@@ -74,8 +74,8 @@
</div>
</div>
<div id="flash-storage-settings" class="flash-plugin-area">
- <a target="_blank" i18n-content="flash_storage_settings"
- i18n-values="href:flash_storage_url"></a>
+ <a target="_blank" i18n-content="flashStorageSettings"
+ i18n-values="href:flashStorageUrl"></a>
</div>
</div>
<div class="action-area">
diff --git a/chromium/chrome/browser/resources/options/compiled_resources.gyp b/chromium/chrome/browser/resources/options/compiled_resources.gyp
index 7ee9a23caff..28ff732b0e2 100644
--- a/chromium/chrome/browser/resources/options/compiled_resources.gyp
+++ b/chromium/chrome/browser/resources/options/compiled_resources.gyp
@@ -8,6 +8,7 @@
'variables': {
'depends': [
'../../../../third_party/jstemplate/compiled_resources.gyp:jstemplate',
+ '../../../../ui/webui/resources/cr_elements/cr_onc/cr_onc_types.js',
'../../../../ui/webui/resources/css/tree.css.js',
'../../../../ui/webui/resources/js/action_link.js',
'../../../../ui/webui/resources/js/cr.js',
@@ -18,6 +19,7 @@
'../../../../ui/webui/resources/js/cr/ui/bubble.js',
'../../../../ui/webui/resources/js/cr/ui/bubble_button.js',
'../../../../ui/webui/resources/js/cr/ui/command.js',
+ '../../../../ui/webui/resources/js/cr/ui/controlled_indicator.js',
'../../../../ui/webui/resources/js/cr/ui/focus_manager.js',
'../../../../ui/webui/resources/js/cr/ui/focus_outline_manager.js',
'../../../../ui/webui/resources/js/cr/ui/grid.js',
@@ -44,7 +46,12 @@
# options_bundle is included as a complex dependency. Currently there is
# no possibility to use gyp variable expansion to it, so we don't use
# <(CLOSURE_DIR) in the "externs" line.
- 'externs': ['../../../../third_party/closure_compiler/externs/chrome_send_externs.js'],
+ 'externs': [
+ '../../../../third_party/closure_compiler/externs/chrome_extensions.js',
+ '../../../../third_party/closure_compiler/externs/chrome_send_externs.js',
+ '../../../../ui/webui/resources/cr_elements/cr_network_icon/cr_network_icon_externs.js',
+ '../../../../ui/webui/resources/cr_elements/cr_onc/cr_onc_data_externs.js',
+ ],
},
'includes': ['../../../../third_party/closure_compiler/compile_js.gypi'],
}
diff --git a/chromium/chrome/browser/resources/options/content_settings.html b/chromium/chrome/browser/resources/options/content_settings.html
index ff039804b75..4eec2313a58 100644
--- a/chromium/chrome/browser/resources/options/content_settings.html
+++ b/chromium/chrome/browser/resources/options/content_settings.html
@@ -20,7 +20,7 @@
<label>
<input type="radio" name="cookies" value="session">
<span>
- <span i18n-content="cookies_session_only"></span>
+ <span i18n-content="cookiesSession"></span>
<span class="controlled-setting-indicator"
content-setting="cookies" value="session"></span>
</span>
@@ -40,7 +40,7 @@
<label>
<input pref="profile.block_third_party_cookies" type="checkbox">
<span>
- <span i18n-content="cookies_block_3rd_party"></span>
+ <span i18n-content="cookiesBlock3rdParty"></span>
<span class="controlled-setting-indicator"
pref="profile.block_third_party_cookies"></span>
</label>
@@ -51,9 +51,9 @@
<label>
<input id="clear-cookies-on-exit"
pref="profile.clear_site_data_on_exit" type="checkbox">
- <span i18n-content="cookies_lso_clear_when_close"
+ <span i18n-content="cookiesLsoClearWhenClose"
class="clear-plugin-lso-data-enabled"></span>
- <span i18n-content="cookies_clear_when_close"
+ <span i18n-content="cookiesClearWhenClose"
class="clear-plugin-lso-data-disabled"></span>
</label>
</div>
@@ -61,7 +61,7 @@
<button class="exceptions-list-button" contentType="cookies"
i18n-content="manageExceptions"></button>
<button id="show-cookies-button"
- i18n-content="cookies_show_cookies"></button>
+ i18n-content="cookiesShowCookies"></button>
</div>
</div>
<div class="experimental-website-settings" hidden>
@@ -70,7 +70,7 @@
<button class="website-settings-permission-button"
contentType="cookies" i18n-content="websitesManage"></button>
<button id="show-cookies-button"
- i18n-content="cookies_show_cookies"></button>
+ i18n-content="cookiesShowCookies"></button>
</div>
</div>
</section>
@@ -150,35 +150,36 @@
</section>
<!-- Handlers settings -->
<section id="handlers-section">
- <h3 i18n-content="handlers_tab_label"></h3>
+ <h3 i18n-content="handlersTabLabel"></h3>
<div>
<div class="radio">
<label>
<input type="radio" name="handlers" value="allow"
class="handler-radio">
- <span i18n-content="handlers_allow"></span>
+ <span i18n-content="handlersAllow"></span>
</label>
</div>
<div class="radio">
<label>
<input type="radio" name="handlers" value="block"
class="handler-radio">
- <span i18n-content="handlers_block"></span>
+ <span i18n-content="handlersBlock"></span>
</label>
</div>
<div class="settings-row">
<button id="manage-handlers-button" contentType="handlers"
- i18n-content="manage_handlers"></button>
+ i18n-content="manageHandlers"></button>
</div>
</div>
</section>
- <!-- Plug-ins filter -->
+ <!-- Plugins filter -->
<section>
<h3 i18n-content="pluginsTabLabel"></h3>
<div class="replace-with-website-settings">
<div class="radio controlled-setting-with-label">
<label>
- <input type="radio" name="plugins" value="allow">
+ <input id="plugins-allow-radio" type="radio" name="plugins"
+ value="allow">
<span>
<span i18n-content="pluginsAllow"></span>
<span class="controlled-setting-indicator"
@@ -186,13 +187,13 @@
</span>
</label>
</div>
- <div id="click_to_play" class="radio controlled-setting-with-label">
+ <div class="radio controlled-setting-with-label">
<label>
- <input type="radio" name="plugins" value="ask">
+ <input type="radio" name="plugins" value="detect">
<span>
- <span i18n-content="pluginsAsk"></span>
+ <span i18n-content="pluginsDetect"></span>
<span class="controlled-setting-indicator"
- content-setting="plugins" value="ask"></span>
+ content-setting="plugins" value="detect"></span>
</span>
</label>
</div>
@@ -211,7 +212,7 @@
i18n-content="manageExceptions"></button>
</div>
<div id="disable-plugins-container">
- <a href="chrome://plugins" i18n-content="disableIndividualPlugins"
+ <a href="chrome://plugins" i18n-content="manageIndividualPlugins"
target="_blank"></a>
</div>
</div>
@@ -386,19 +387,31 @@
<div class="radio">
<label>
<input type="radio" name="mouselock" value="allow">
- <span i18n-content="mouselockAllow"></span>
+ <span>
+ <span i18n-content="mouselockAllow"></span>
+ <span class="controlled-setting-indicator"
+ content-setting="mouselock" value="allow"></span>
+ </span>
</label>
</div>
<div class="radio">
<label>
<input type="radio" name="mouselock" value="ask">
- <span i18n-content="mouselockAsk"></span>
+ <span>
+ <span i18n-content="mouselockAsk"></span>
+ <span class="controlled-setting-indicator"
+ content-setting="mouselock" value="ask"></span>
+ </span>
</label>
</div>
<div class="radio">
<label>
<input type="radio" name="mouselock" value="block">
- <span i18n-content="mouselockBlock"></span>
+ <span>
+ <span i18n-content="mouselockBlock"></span>
+ <span class="controlled-setting-indicator"
+ content-setting="mouselock" value="block"></span>
+ </span>
</label>
</div>
<div class="settings-row">
@@ -441,12 +454,16 @@
</div>
<div>
<div class="media-device-control">
- <span i18n-content="mediaSelectMicLabel"></span>
- <select id="media-select-mic" class="weakrtl"></select>
+ <span id="media-select-mic-label" i18n-content="mediaSelectMicLabel">
+ </span>
+ <select id="media-select-mic" class="weakrtl"
+ aria-labelledby="media-select-mic-label"></select>
</div>
<div class="media-device-control">
- <span i18n-content="mediaSelectCameraLabel"></span>
- <select id="media-select-camera" class="weakrtl"></select>
+ <span id="media-select-camera-label"
+ i18n-content="mediaSelectCameraLabel"></span>
+ <select id="media-select-camera" class="weakrtl"
+ aria-labelledby="media-select-camera-label"></select>
</div>
<div class="radio controlled-setting-with-label">
<label>
@@ -495,19 +512,31 @@
<div class="radio">
<label>
<input type="radio" name="ppapi-broker" value="allow">
- <span i18n-content="ppapiBrokerAllow"></span>
+ <span>
+ <span i18n-content="ppapiBrokerAllow"></span>
+ <span class="controlled-setting-indicator"
+ content-setting="ppapi-broker" value="allow"></span>
+ </span>
</label>
</div>
<div class="radio">
<label>
<input type="radio" name="ppapi-broker" value="ask">
- <span i18n-content="ppapiBrokerAsk"></span>
+ <span>
+ <span i18n-content="ppapiBrokerAsk"></span>
+ <span class="controlled-setting-indicator"
+ content-setting="ppapi-broker" value="ask"></span>
+ </span>
</label>
</div>
<div class="radio">
<label>
<input type="radio" name="ppapi-broker" value="block">
- <span i18n-content="ppapiBrokerBlock"></span>
+ <span>
+ <span i18n-content="ppapiBrokerBlock"></span>
+ <span class="controlled-setting-indicator"
+ content-setting="ppapi-broker" value="block"></span>
+ </span>
</label>
</div>
<div class="settings-row">
@@ -571,8 +600,8 @@
</div>
</section>
<!-- MIDI system exclusive messages filter -->
- <section id="experimental-web-midi-settings" hidden="true">
- <h3 i18n-content="midi-sysex_header"></h3>
+ <section>
+ <h3 i18n-content="midiSysexHeader"></h3>
<div>
<div class="radio">
<label>
@@ -600,7 +629,7 @@
</section>
<!-- Push messaging filter -->
<section id="experimental-push-messaging-settings" hidden="true">
- <h3 i18n-content="push-messaging_header"></h3>
+ <h3 i18n-content="pushMessagingHeader"></h3>
<div>
<div class="radio">
<label>
@@ -628,7 +657,7 @@
</section>
<!-- Page zoom levels -->
<section id="page-zoom-levels">
- <h3 i18n-content="zoomlevels_header"></h3>
+ <h3 i18n-content="zoomlevelsHeader"></h3>
<div>
<div class="settings-row">
<button class="exceptions-list-button" contentType="zoomlevels"
diff --git a/chromium/chrome/browser/resources/options/content_settings.js b/chromium/chrome/browser/resources/options/content_settings.js
index df2ba0886ad..db6fbf6b928 100644
--- a/chromium/chrome/browser/resources/options/content_settings.js
+++ b/chromium/chrome/browser/resources/options/content_settings.js
@@ -131,7 +131,7 @@ cr.define('options', function() {
/**
* Sets the values for all the content settings radios and labels.
- * @param {Object.<string, {managedBy: string, value: string}>} dict A mapping
+ * @param {Object<string, {managedBy: string, value: string}>} dict A mapping
* from radio groups to the checked value for that group.
*/
ContentSettings.setContentFilterSettingsValue = function(dict) {
@@ -218,7 +218,7 @@ cr.define('options', function() {
/**
* Initializes an exceptions list.
* @param {string} type The content type that we are setting exceptions for.
- * @param {Array.<options.Exception>} exceptions An array of pairs, where the
+ * @param {Array<options.Exception>} exceptions An array of pairs, where the
* first element of each pair is the filter string, and the second is the
* setting (allow/block).
*/
@@ -293,14 +293,6 @@ cr.define('options', function() {
};
/**
- * Shows/hides the whole Web MIDI settings.
- * @param {boolean} show Wether to show the whole Web MIDI settings.
- */
- ContentSettings.showExperimentalWebMIDISettings = function(show) {
- $('experimental-web-midi-settings').hidden = !show;
- };
-
- /**
* Updates the microphone/camera devices menu with the given entries.
* @param {string} type The device type.
* @param {Array} devices List of available devices.
diff --git a/chromium/chrome/browser/resources/options/content_settings_exceptions_area.html b/chromium/chrome/browser/resources/options/content_settings_exceptions_area.html
index 45a1ccd7066..3c3daefcf8a 100644
--- a/chromium/chrome/browser/resources/options/content_settings_exceptions_area.html
+++ b/chromium/chrome/browser/resources/options/content_settings_exceptions_area.html
@@ -17,20 +17,20 @@
<div contentType="cookies">
<list mode="normal"></list>
<div>
- <span class="otr-explanation" i18n-content="otr_exceptions_explanation">
+ <span class="otr-explanation" i18n-content="otrExceptionsExplanation">
</span>
<list mode="otr"></list>
</div>
<div class="flash-plugin-area">
- <a i18n-values="href:flash_storage_url" target="_blank"
- i18n-content="flash_storage_settings">
+ <a i18n-values="href:flashStorageUrl" target="_blank"
+ i18n-content="flashStorageSettings">
</a>
</div>
</div>
<div contentType="images">
<list mode="normal"></list>
<div>
- <span class="otr-explanation" i18n-content="otr_exceptions_explanation">
+ <span class="otr-explanation" i18n-content="otrExceptionsExplanation">
</span>
<list mode="otr"></list>
</div>
@@ -38,7 +38,7 @@
<div contentType="javascript">
<list mode="normal"></list>
<div>
- <span class="otr-explanation" i18n-content="otr_exceptions_explanation">
+ <span class="otr-explanation" i18n-content="otrExceptionsExplanation">
</span>
<list mode="otr"></list>
</div>
@@ -46,7 +46,7 @@
<div contentType="plugins">
<list mode="normal"></list>
<div>
- <span class="otr-explanation" i18n-content="otr_exceptions_explanation">
+ <span class="otr-explanation" i18n-content="otrExceptionsExplanation">
</span>
<list mode="otr"></list>
</div>
@@ -54,7 +54,7 @@
<div contentType="popups">
<list mode="normal"></list>
<div>
- <span class="otr-explanation" i18n-content="otr_exceptions_explanation">
+ <span class="otr-explanation" i18n-content="otrExceptionsExplanation">
</span>
<list mode="otr"></list>
</div>
@@ -68,7 +68,7 @@
<div contentType="fullscreen">
<list mode="normal"></list>
<div>
- <span class="otr-explanation" i18n-content="otr_exceptions_explanation">
+ <span class="otr-explanation" i18n-content="otrExceptionsExplanation">
</span>
<list mode="otr"></list>
</div>
@@ -76,7 +76,7 @@
<div contentType="mouselock">
<list mode="normal"></list>
<div>
- <span class="otr-explanation" i18n-content="otr_exceptions_explanation">
+ <span class="otr-explanation" i18n-content="otrExceptionsExplanation">
</span>
<list mode="otr"></list>
</div>
@@ -84,7 +84,7 @@
<div contentType="protectedContent">
<list mode="normal"></list>
<div>
- <span class="otr-explanation" i18n-content="otr_exceptions_explanation">
+ <span class="otr-explanation" i18n-content="otrExceptionsExplanation">
</span>
<list mode="otr"></list>
</div>
@@ -99,7 +99,7 @@
<div contentType="media-stream">
<list mode="normal"></list>
<div>
- <span class="otr-explanation" i18n-content="otr_exceptions_explanation">
+ <span class="otr-explanation" i18n-content="otrExceptionsExplanation">
</span>
<list mode="otr"></list>
</div>
@@ -112,7 +112,7 @@
<div contentType="ppapi-broker">
<list mode="normal"></list>
<div>
- <span class="otr-explanation" i18n-content="otr_exceptions_explanation">
+ <span class="otr-explanation" i18n-content="otrExceptionsExplanation">
</span>
<list mode="otr"></list>
</div>
@@ -123,7 +123,7 @@
<div contentType="midi-sysex">
<list mode="normal"></list>
<div>
- <span class="otr-explanation" i18n-content="otr_exceptions_explanation">
+ <span class="otr-explanation" i18n-content="otrExceptionsExplanation">
</span>
<list mode="otr"></list>
</div>
@@ -131,7 +131,7 @@
<div contentType="push-messaging">
<list mode="normal"></list>
<div>
- <span class="otr-explanation" i18n-content="otr_exceptions_explanation">
+ <span class="otr-explanation" i18n-content="otrExceptionsExplanation">
</span>
<list mode="otr"></list>
</div>
@@ -139,7 +139,7 @@
<div contentType="zoomlevels">
<list mode="normal"></list>
<div>
- <span class="otr-explanation" i18n-content="otr_exceptions_explanation">
+ <span class="otr-explanation" i18n-content="otrExceptionsExplanation">
</span>
<list mode="otr"></list>
</div>
diff --git a/chromium/chrome/browser/resources/options/content_settings_exceptions_area.js b/chromium/chrome/browser/resources/options/content_settings_exceptions_area.js
index f92d0050ee4..97b6f43d071 100644
--- a/chromium/chrome/browser/resources/options/content_settings_exceptions_area.js
+++ b/chromium/chrome/browser/resources/options/content_settings_exceptions_area.js
@@ -10,22 +10,34 @@ cr.define('options.contentSettings', function() {
/** @const */ var ArrayDataModel = cr.ui.ArrayDataModel;
/**
+ * Returns whether exceptions list for the type is editable.
+ *
+ * @param {string} contentType The type of the list.
+ */
+ function IsEditableType(contentType) {
+ // Exceptions of the following lists are not editable for now.
+ return !(contentType == 'notifications' ||
+ contentType == 'location' ||
+ contentType == 'fullscreen' ||
+ contentType == 'media-stream' ||
+ contentType == 'midi-sysex' ||
+ contentType == 'zoomlevels');
+ }
+
+ /**
* Creates a new exceptions list item.
*
* @param {string} contentType The type of the list.
* @param {string} mode The browser mode, 'otr' or 'normal'.
- * @param {boolean} enableAskOption Whether to show an 'ask every time'
- * option in the select.
* @param {Object} exception A dictionary that contains the data of the
* exception.
* @constructor
* @extends {options.InlineEditableItem}
*/
- function ExceptionsListItem(contentType, mode, enableAskOption, exception) {
+ function ExceptionsListItem(contentType, mode, exception) {
var el = cr.doc.createElement('div');
el.mode = mode;
el.contentType = contentType;
- el.enableAskOption = enableAskOption;
el.dataItem = exception;
el.__proto__ = ExceptionsListItem.prototype;
el.decorate();
@@ -71,11 +83,11 @@ cr.define('options.contentSettings', function() {
optionAllow.value = 'allow';
select.appendChild(optionAllow);
- if (this.enableAskOption) {
- var optionAsk = cr.doc.createElement('option');
- optionAsk.textContent = loadTimeData.getString('askException');
- optionAsk.value = 'ask';
- select.appendChild(optionAsk);
+ if (this.contentType == 'plugins') {
+ var optionDetect = cr.doc.createElement('option');
+ optionDetect.textContent = loadTimeData.getString('detectException');
+ optionDetect.value = 'detect';
+ select.appendChild(optionDetect);
}
if (this.contentType == 'cookies') {
@@ -127,7 +139,6 @@ cr.define('options.contentSettings', function() {
if (this.contentType == 'zoomlevels') {
this.deletable = true;
- this.editable = false;
var zoomLabel = cr.doc.createElement('span');
zoomLabel.textContent = this.dataItem.zoom;
@@ -154,14 +165,7 @@ cr.define('options.contentSettings', function() {
this.select = select;
this.updateEditables();
-
- // Editing notifications, geolocation and media-stream is disabled for
- // now.
- if (this.contentType == 'notifications' ||
- this.contentType == 'location' ||
- this.contentType == 'media-stream') {
- this.editable = false;
- }
+ this.editable = this.editable && IsEditableType(this.contentType);
// If the source of the content setting exception is not a user
// preference, that source controls the exception and the user cannot edit
@@ -192,7 +196,7 @@ cr.define('options.contentSettings', function() {
// icon of the app.
if (controlledBy == 'HostedApp') {
this.title =
- loadTimeData.getString('set_by') + ' ' + this.dataItem.appName;
+ loadTimeData.getString('setBy') + ' ' + this.dataItem.appName;
var button = this.querySelector('.row-delete-button');
// Use the host app's favicon (16px, match bigger size).
// See c/b/ui/webui/extensions/extension_icon_source.h
@@ -284,6 +288,8 @@ cr.define('options.contentSettings', function() {
return loadTimeData.getString('askException');
else if (setting == 'session')
return loadTimeData.getString('sessionException');
+ else if (setting == 'detect')
+ return loadTimeData.getString('detectException');
else if (setting == 'default')
return '';
@@ -394,16 +400,13 @@ cr.define('options.contentSettings', function() {
*
* @param {string} contentType The type of the list.
* @param {string} mode The browser mode, 'otr' or 'normal'.
- * @param {boolean} enableAskOption Whether to show an 'ask every time' option
- * in the select.
* @constructor
* @extends {options.contentSettings.ExceptionsListItem}
*/
- function ExceptionsAddRowListItem(contentType, mode, enableAskOption) {
+ function ExceptionsAddRowListItem(contentType, mode) {
var el = cr.doc.createElement('div');
el.mode = mode;
el.contentType = contentType;
- el.enableAskOption = enableAskOption;
el.dataItem = [];
el.__proto__ = ExceptionsAddRowListItem.prototype;
el.decorate();
@@ -478,10 +481,6 @@ cr.define('options.contentSettings', function() {
}
this.mode = this.getAttribute('mode');
-
- // Whether the exceptions in this list allow an 'Ask every time' option.
- this.enableAskOption = this.contentType == 'plugins';
-
this.autoExpands = true;
this.reset();
},
@@ -495,12 +494,10 @@ cr.define('options.contentSettings', function() {
if (entry) {
return new ExceptionsListItem(this.contentType,
this.mode,
- this.enableAskOption,
entry);
} else {
var addRowItem = new ExceptionsAddRowListItem(this.contentType,
- this.mode,
- this.enableAskOption);
+ this.mode);
addRowItem.deletable = false;
return addRowItem;
}
@@ -509,7 +506,7 @@ cr.define('options.contentSettings', function() {
/**
* Sets the exceptions in the js model.
*
- * @param {Array.<options.Exception>} entries A list of dictionaries of
+ * @param {Array<options.Exception>} entries A list of dictionaries of
* values, each dictionary represents an exception.
*/
setExceptions: function(entries) {
@@ -550,11 +547,7 @@ cr.define('options.contentSettings', function() {
*/
isEditable: function() {
// Exceptions of the following lists are not editable for now.
- return !(this.contentType == 'notifications' ||
- this.contentType == 'location' ||
- this.contentType == 'fullscreen' ||
- this.contentType == 'media-stream' ||
- this.contentType == 'zoomlevels');
+ return IsEditableType(this.contentType);
},
/**
@@ -631,7 +624,10 @@ cr.define('options.contentSettings', function() {
this.title = loadTimeData.getString(type + 'TabTitle');
var header = this.pageDiv.querySelector('h1');
- header.textContent = loadTimeData.getString(type + '_header');
+ var camelCasedType = type.replace(/-([a-z])/g, function(g) {
+ return g[1].toUpperCase();
+ });
+ header.textContent = loadTimeData.getString(camelCasedType + 'Header');
var divs = this.pageDiv.querySelectorAll('div[contentType]');
for (var i = 0; i < divs.length; i++) {
diff --git a/chromium/chrome/browser/resources/options/controlled_setting.css b/chromium/chrome/browser/resources/options/controlled_setting.css
deleted file mode 100644
index f7f46d2de46..00000000000
--- a/chromium/chrome/browser/resources/options/controlled_setting.css
+++ /dev/null
@@ -1,88 +0,0 @@
-/* Copyright 2013 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. */
-
-/* Controlled setting indicator and bubble. */
-
-.controlled-setting-with-label input:disabled + span {
- color: #999;
-}
-
-.controlled-setting-indicator {
- -webkit-margin-end: 0.3em;
-}
-
-.controlled-setting-indicator:not([controlled-by]) {
- display: none;
-}
-
-.controlled-setting-indicator[controlled-by='policy'] > div {
- background-image: url('chrome://theme/IDR_CONTROLLED_SETTING_MANDATORY');
-}
-
-.controlled-setting-indicator[controlled-by='owner'] > div {
- background-image: url('chrome://theme/IDR_CONTROLLED_SETTING_OWNER');
-}
-
-.controlled-setting-indicator[controlled-by='extension'] > div {
- background-image: url('chrome://theme/IDR_CONTROLLED_SETTING_EXTENSION');
-}
-
-.controlled-setting-indicator[controlled-by='shared'] > div {
- background-image: url('chrome://theme/IDR_CONTROLLED_SETTING_SHARED');
-}
-
-.controlled-setting-indicator:-webkit-any([controlled-by='recommended'],
- [controlled-by='hasRecommendation']) > div {
- background-image: url('chrome://theme/IDR_CONTROLLED_SETTING_MANDATORY');
-}
-
-.controlled-setting-bubble-action {
- padding: 0 !important;
-}
-
-.controlled-setting-bubble-header {
- margin-top: 3px;
-}
-
-.controlled-setting-bubble-content-row {
- height: 35px;
- position: relative;
-}
-
-.controlled-setting-bubble-extension-name {
- -webkit-padding-start: 30px;
- background-repeat: no-repeat;
- font-weight: bold;
- height: 24px;
- margin-top: -9px;
- overflow: hidden;
- padding-top: 3px;
- position: absolute;
- text-overflow: ellipsis;
- top: 50%;
- white-space: nowrap;
- width: 215px;
-}
-
-html[dir='rtl'] .controlled-setting-bubble-extension-name {
- background-position: right top;
-}
-
-.controlled-setting-bubble-extension-manage-link {
- margin-left: -0.35em;
- margin-top: -0.30em;
- position: absolute;
- top: 50%;
-}
-
-.controlled-setting-bubble-extension-disable-button {
- bottom: 0;
- position: absolute;
- right: 0;
-}
-
-html[dir='rtl'] .controlled-setting-bubble-extension-disable-button {
- left: 0;
- right: auto;
-}
diff --git a/chromium/chrome/browser/resources/options/controlled_setting.js b/chromium/chrome/browser/resources/options/controlled_setting.js
index fdaa294ae46..fc5b960ab33 100644
--- a/chromium/chrome/browser/resources/options/controlled_setting.js
+++ b/chromium/chrome/browser/resources/options/controlled_setting.js
@@ -3,26 +3,25 @@
// found in the LICENSE file.
cr.define('options', function() {
- var Preferences = options.Preferences;
+ /** @const */ var Preferences = options.Preferences;
/**
* A controlled setting indicator that can be placed on a setting as an
* indicator that the value is controlled by some external entity such as
* policy or an extension.
* @constructor
- * @extends {cr.ui.BubbleButton}
+ * @extends {cr.ui.ControlledIndicator}
*/
var ControlledSettingIndicator = cr.ui.define('span');
ControlledSettingIndicator.prototype = {
- __proto__: cr.ui.BubbleButton.prototype,
+ __proto__: cr.ui.ControlledIndicator.prototype,
/**
* Decorates the base element to show the proper icon.
*/
decorate: function() {
- cr.ui.BubbleButton.prototype.decorate.call(this);
- this.classList.add('controlled-setting-indicator');
+ cr.ui.ControlledIndicator.prototype.decorate.call(this);
// If there is a pref, track its controlledBy and recommendedValue
// properties in order to be able to bring up the correct bubble.
@@ -85,108 +84,108 @@ cr.define('options', function() {
},
/**
- * Open or close a bubble with further information about the pref.
+ * Uses the page's PageManager to display an informational bubble.
* @override
*/
- toggleBubble: function() {
- if (this.showingBubble) {
- PageManager.hideBubble();
- } else {
- var self = this;
-
- // Construct the bubble text.
- if (this.hasAttribute('plural')) {
- var defaultStrings = {
- 'policy': loadTimeData.getString('controlledSettingsPolicy'),
- 'extension': loadTimeData.getString('controlledSettingsExtension'),
- 'extensionWithName': loadTimeData.getString(
- 'controlledSettingsExtensionWithName'),
- };
- if (cr.isChromeOS) {
- defaultStrings.shared =
- loadTimeData.getString('controlledSettingsShared');
- }
- } else {
- var defaultStrings = {
- 'policy': loadTimeData.getString('controlledSettingPolicy'),
- 'extension': loadTimeData.getString('controlledSettingExtension'),
- 'extensionWithName': loadTimeData.getString(
- 'controlledSettingExtensionWithName'),
- 'recommended':
- loadTimeData.getString('controlledSettingRecommended'),
- 'hasRecommendation':
- loadTimeData.getString('controlledSettingHasRecommendation'),
- };
- if (cr.isChromeOS) {
- defaultStrings.owner =
- loadTimeData.getString('controlledSettingOwner');
- defaultStrings.shared =
- loadTimeData.getString('controlledSettingShared');
- }
- }
+ showBubble: function(content) {
+ PageManager.showBubble(content, this.image, this, this.location);
+ },
- // No controller, no bubble.
- if (!this.controlledBy || !(this.controlledBy in defaultStrings))
- return;
-
- var text = defaultStrings[this.controlledBy];
- if (this.controlledBy == 'extension' && this.extensionName)
- text = defaultStrings.extensionWithName;
-
- // Apply text overrides.
- if (this.hasAttribute('text' + this.controlledBy))
- text = this.getAttribute('text' + this.controlledBy);
-
- // Create the DOM tree.
- var content = document.createElement('div');
- content.classList.add('controlled-setting-bubble-header');
- content.textContent = text;
-
- if (this.controlledBy == 'hasRecommendation' && this.resetHandler_ &&
- !this.readOnly) {
- var container = document.createElement('div');
- var action = new ActionLink;
- action.classList.add('controlled-setting-bubble-action');
- action.textContent =
- loadTimeData.getString('controlledSettingFollowRecommendation');
- action.addEventListener('click', function(event) {
- self.resetHandler_();
- });
- container.appendChild(action);
- content.appendChild(container);
- } else if (this.controlledBy == 'extension' && this.extensionName) {
- var extensionContainer =
- $('extension-controlled-settings-bubble-template').
- cloneNode(true);
- // No need for an id anymore, and thus remove to avoid id collision.
- extensionContainer.removeAttribute('id');
- extensionContainer.hidden = false;
-
- var extensionName = extensionContainer.querySelector(
- '.controlled-setting-bubble-extension-name');
- extensionName.textContent = this.extensionName;
- extensionName.style.backgroundImage =
- 'url("' + this.extensionIcon + '")';
-
- var manageLink = extensionContainer.querySelector(
- '.controlled-setting-bubble-extension-manage-link');
- var extensionId = this.extensionId;
- manageLink.onclick = function() {
- uber.invokeMethodOnWindow(
- window.top, 'showPage', {pageId: 'extensions',
- path: '?id=' + extensionId});
- };
-
- var disableButton = extensionContainer.querySelector(
- '.controlled-setting-bubble-extension-disable-button');
- disableButton.onclick = function() {
- chrome.send('disableExtension', [extensionId]);
- };
- content.appendChild(extensionContainer);
+ /**
+ * Uses the page's PageManager to hide the currently visible bubble, if
+ * any.
+ * @override
+ */
+ hideBubble: function() {
+ PageManager.hideBubble();
+ },
+
+ /**
+ * Queries the |loadTimeData| singleton for the default bubble text strings.
+ * @override
+ */
+ getDefaultStrings: function() {
+ // Construct the bubble text.
+ if (this.hasAttribute('plural')) {
+ var defaultStrings = {
+ 'policy': loadTimeData.getString('controlledSettingsPolicy'),
+ 'extension': loadTimeData.getString('controlledSettingsExtension'),
+ 'extensionWithName':
+ loadTimeData.getString('controlledSettingsExtensionWithName'),
+ };
+ if (cr.isChromeOS) {
+ defaultStrings.shared =
+ loadTimeData.getString('controlledSettingsShared');
+ }
+ } else {
+ var defaultStrings = {
+ 'policy': loadTimeData.getString('controlledSettingPolicy'),
+ 'extension': loadTimeData.getString('controlledSettingExtension'),
+ 'extensionWithName':
+ loadTimeData.getString('controlledSettingExtensionWithName'),
+ 'recommended': loadTimeData.getString('controlledSettingRecommended'),
+ 'hasRecommendation':
+ loadTimeData.getString('controlledSettingHasRecommendation'),
+ };
+ if (cr.isChromeOS) {
+ defaultStrings.owner =
+ loadTimeData.getString('controlledSettingOwner');
+ defaultStrings.shared =
+ loadTimeData.getString('controlledSettingShared');
}
+ }
+ return defaultStrings;
+ },
- PageManager.showBubble(content, this.image, this, this.location);
+ /**
+ * Returns the DOM tree for a showing the message |text|.
+ * @param {string} text to be shown in the bubble.
+ * @override
+ */
+ createDomTree: function(text) {
+ var content = document.createElement('div');
+ content.classList.add('controlled-setting-bubble-header');
+ content.textContent = text;
+
+ if (this.controlledBy == 'hasRecommendation' && this.resetHandler_ &&
+ !this.readOnly) {
+ var container = document.createElement('div');
+ var action = new ActionLink;
+ action.classList.add('controlled-setting-bubble-action');
+ action.textContent =
+ loadTimeData.getString('controlledSettingFollowRecommendation');
+ action.addEventListener('click', this.resetHandler_.bind(this));
+ container.appendChild(action);
+ content.appendChild(container);
+ } else if (this.controlledBy == 'extension' && this.extensionName) {
+ var extensionContainer =
+ $('extension-controlled-settings-bubble-template').cloneNode(true);
+ // No need for an id anymore, and thus remove to avoid id collision.
+ extensionContainer.removeAttribute('id');
+ extensionContainer.hidden = false;
+
+ var extensionName = extensionContainer.querySelector(
+ '.controlled-setting-bubble-extension-name');
+ extensionName.textContent = this.extensionName;
+ extensionName.style.backgroundImage =
+ 'url("' + this.extensionIcon + '")';
+
+ var manageLink = extensionContainer.querySelector(
+ '.controlled-setting-bubble-extension-manage-link');
+ var extensionId = this.extensionId;
+ manageLink.onclick = function() {
+ uber.invokeMethodOnWindow(
+ window.top, 'showPage',
+ {pageId: 'extensions', path: '?id=' + extensionId});
+ };
+
+ var disableButton = extensionContainer.querySelector(
+ '.controlled-setting-bubble-extension-disable-button');
+ disableButton.onclick =
+ function() { chrome.send('disableExtension', [extensionId]); };
+ content.appendChild(extensionContainer);
}
+ return content;
},
};
@@ -216,23 +215,6 @@ cr.define('options', function() {
cr.defineProperty(ControlledSettingIndicator, 'value',
cr.PropertyKind.ATTR);
- /**
- * The status of the associated preference:
- * - 'policy': A specific value is enforced by policy.
- * - 'extension': A specific value is enforced by an extension.
- * - 'recommended': A value is recommended by policy. The user could
- * override this recommendation but has not done so.
- * - 'hasRecommendation': A value is recommended by policy. The user has
- * overridden this recommendation.
- * - 'owner': A value is controlled by the owner of the device
- * (Chrome OS only).
- * - 'shared': A value belongs to the primary user but can be
- * modified (Chrome OS only).
- * - unset: The value is controlled by the user alone.
- */
- cr.defineProperty(ControlledSettingIndicator, 'controlledBy',
- cr.PropertyKind.ATTR);
-
// Export.
return {
ControlledSettingIndicator: ControlledSettingIndicator
diff --git a/chromium/chrome/browser/resources/options/cookies_list.js b/chromium/chrome/browser/resources/options/cookies_list.js
index 962c1fceffc..ddaaa617578 100644
--- a/chromium/chrome/browser/resources/options/cookies_list.js
+++ b/chromium/chrome/browser/resources/options/cookies_list.js
@@ -41,6 +41,7 @@ cr.define('options', function() {
['certType', 'label_channel_id_type'],
['created', 'label_channel_id_created']],
'service_worker': [['origin', 'label_service_worker_origin'],
+ ['size', 'label_service_worker_size'],
['scopes', 'label_service_worker_scopes']],
'flash_lso': [['domain', 'label_cookie_domain']],
};
@@ -64,9 +65,9 @@ cr.define('options', function() {
/**
* Create tree nodes for the objects in the data array, and insert them all
* into the given list using its @{code splice} method at the given index.
- * @param {Array.<Object>} data The data objects for the nodes to add.
+ * @param {Array<Object>} data The data objects for the nodes to add.
* @param {number} start The index at which to start inserting the nodes.
- * @return {Array.<options.CookieTreeNode>} An array of CookieTreeNodes added.
+ * @return {Array<options.CookieTreeNode>} An array of CookieTreeNodes added.
*/
function spliceTreeNodes(data, start, list) {
var nodes = data.map(function(x) { return new CookieTreeNode(x); });
@@ -395,7 +396,7 @@ cr.define('options', function() {
/**
* Insert the given list of cookie tree nodes at the given index.
* Both CookiesList and CookieTreeNode implement this API.
- * @param {Array.<Object>} data The data objects for the nodes to add.
+ * @param {Array<Object>} data The data objects for the nodes to add.
* @param {number} start The index at which to start inserting the nodes.
*/
insertAt: function(data, start) {
@@ -553,7 +554,7 @@ cr.define('options', function() {
* Uses preallocated DOM elements for each cookie node type from @{code
* infoNodes}, and inserts the appropriate elements to @{code element}.
* @param {Element} element The DOM element to insert elements to.
- * @param {Object.<string, {table: Element, info: Object.<string,
+ * @param {Object<string, {table: Element, info: Object<string,
* Element>}>} infoNodes The map from cookie node types to maps from
* cookie attribute names to DOM elements to display cookie attribute
* values, created by @{code CookiesList.decorate}.
@@ -733,6 +734,8 @@ cr.define('options', function() {
*/
handleKeyLeftRight_: function(e) {
var id = e.keyIdentifier;
+ if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey)
+ return;
if ((id == 'Left' || id == 'Right') && this.expandedItem) {
var cs = this.ownerDocument.defaultView.getComputedStyle(this);
var rtl = cs.direction == 'rtl';
@@ -828,7 +831,7 @@ cr.define('options', function() {
/**
* Insert the given list of cookie tree nodes at the given index.
* Both CookiesList and CookieTreeNode implement this API.
- * @param {Array.<Object>} data The data objects for the nodes to add.
+ * @param {Array<Object>} data The data objects for the nodes to add.
* @param {number} start The index at which to start inserting the nodes.
*/
insertAt: function(data, start) {
diff --git a/chromium/chrome/browser/resources/options/deletable_item_list.js b/chromium/chrome/browser/resources/options/deletable_item_list.js
index 0d52ff07bab..57b684bfe3a 100644
--- a/chromium/chrome/browser/resources/options/deletable_item_list.js
+++ b/chromium/chrome/browser/resources/options/deletable_item_list.js
@@ -38,6 +38,14 @@ cr.define('options', function() {
*/
deletable_: true,
+ /**
+ * Whether or not the close button can ever be navigated to using the
+ * keyboard.
+ * @type {boolean}
+ * @protected
+ */
+ closeButtonFocusAllowed: false,
+
/** @override */
decorate: function() {
ListItem.prototype.decorate.call(this);
@@ -57,7 +65,7 @@ cr.define('options', function() {
this.closeButtonElement_.addEventListener('mouseup',
this.handleMouseDownUpOnClose_);
this.closeButtonElement_.addEventListener('focus',
- this.handleFocus_.bind(this));
+ this.handleFocus.bind(this));
this.closeButtonElement_.tabIndex = -1;
this.closeButtonElement_.title =
loadTimeData.getString('deletableItemDeleteButtonTitle');
@@ -94,9 +102,9 @@ cr.define('options', function() {
/**
* Called when a focusable child element receives focus. Selects this item
* in the list selection model.
- * @private
+ * @protected
*/
- handleFocus_: function() {
+ handleFocus: function() {
// This handler is also fired when the child receives focus as a result of
// the item getting selected by the customized mouse/keyboard handling in
// SelectionController. Take care not to destroy a potential multiple
@@ -144,7 +152,7 @@ cr.define('options', function() {
/**
* Callback for onclick events.
* @param {Event} e The click event object.
- * @override
+ * @protected
*/
handleClick: function(e) {
if (this.disabled)
diff --git a/chromium/chrome/browser/resources/options/editable_text_field.js b/chromium/chrome/browser/resources/options/editable_text_field.js
index c1f51a68925..9c04b62da7c 100644
--- a/chromium/chrome/browser/resources/options/editable_text_field.js
+++ b/chromium/chrome/browser/resources/options/editable_text_field.js
@@ -9,15 +9,6 @@ cr.define('options', function() {
*/
var EditableTextField = cr.ui.define('div');
- /**
- * Decorates an element as an editable text field.
- * @param {!HTMLElement} el The element to decorate.
- */
- EditableTextField.decorate = function(el) {
- el.__proto__ = EditableTextField.prototype;
- el.decorate();
- };
-
EditableTextField.prototype = {
__proto__: HTMLDivElement.prototype,
@@ -50,7 +41,7 @@ cr.define('options', function() {
*/
editCanceled_: true,
- /** @override */
+ /** @protected */
decorate: function() {
this.classList.add('editable-text-field');
diff --git a/chromium/chrome/browser/resources/options/handler_options.html b/chromium/chrome/browser/resources/options/handler_options.html
index 4b4e8e55b13..bfe856baffc 100644
--- a/chromium/chrome/browser/resources/options/handler_options.html
+++ b/chromium/chrome/browser/resources/options/handler_options.html
@@ -2,26 +2,26 @@
<div class="close-button"></div>
<h1 i18n-content="handlersPage"></h1>
<div class="content-area">
- <h3 i18n-content="handlers_active_heading"></h3>
+ <h3 i18n-content="handlersActiveHeading"></h3>
<div class="handlers-column-headers">
<div class="handlers-type-column">
- <div i18n-content="handlers_type_column_header"></div>
+ <div i18n-content="handlersTypeColumnHeader"></div>
</div>
<div class="handlers-site-column">
- <div i18n-content="handlers_site_column_header"></div>
+ <div i18n-content="handlersSiteColumnHeader"></div>
</div>
<div class="handlers-remove-column"></div>
</div>
<list id="handlers-list"></list>
<div id="ignored-handlers-section">
- <h3 i18n-content="handlers_ignored_heading"></h3>
+ <h3 i18n-content="handlersIgnoredHeading"></h3>
<div class="handlers-column-headers">
<div class="handlers-type-column">
- <h3 i18n-content="handlers_type_column_header"></h3>
+ <h3 i18n-content="handlersTypeColumnHeader"></h3>
</div>
<div class="handlers-site-column">
- <h3 i18n-content="handlers_site_column_header"></h3>
+ <h3 i18n-content="handlersSiteColumnHeader"></h3>
</div>
<div class="handlers-remove-column"></div>
</div>
@@ -31,7 +31,7 @@
<div class="action-area">
<div class="hbox stretch">
<a target="_blank" i18n-content="learnMore"
- i18n-values="href:handlers_learn_more_url"></a>
+ i18n-values="href:handlersLearnMoreUrl"></a>
</div>
<div class="action-area-right">
<div class="button-strip">
diff --git a/chromium/chrome/browser/resources/options/handler_options.js b/chromium/chrome/browser/resources/options/handler_options.js
index 85978343272..a01261f199f 100644
--- a/chromium/chrome/browser/resources/options/handler_options.js
+++ b/chromium/chrome/browser/resources/options/handler_options.js
@@ -76,7 +76,7 @@ cr.define('options', function() {
/**
* Sets the list of handlers shown by the view.
- * @param {Array.<Handlers>} handlers Handlers to be shown in the view.
+ * @param {Array<Handlers>} handlers Handlers to be shown in the view.
*/
HandlerOptions.setHandlers = function(handlers) {
$('handlers-list').setHandlers(handlers);
diff --git a/chromium/chrome/browser/resources/options/handler_options_list.js b/chromium/chrome/browser/resources/options/handler_options_list.js
index a3e4fa1fd9b..283e21b12c1 100644
--- a/chromium/chrome/browser/resources/options/handler_options_list.js
+++ b/chromium/chrome/browser/resources/options/handler_options_list.js
@@ -133,7 +133,7 @@ cr.define('options', function() {
var defaultOptionElement = document.createElement('option');
defaultOptionElement.selected = data.default_handler == -1;
defaultOptionElement.textContent =
- loadTimeData.getString('handlers_none_handler');
+ loadTimeData.getString('handlersNoneHandler');
defaultOptionElement.value = -1;
selectElement.appendChild(defaultOptionElement);
@@ -188,11 +188,11 @@ cr.define('options', function() {
this.appendChild(indicator);
}
- if (data.registered_by_user) {
+ if (data.is_default_handler_set_by_user) {
// Remove link.
var removeElement = document.createElement('div');
removeElement.textContent =
- loadTimeData.getString('handlers_remove_link');
+ loadTimeData.getString('handlersRemoveLink');
removeElement.addEventListener('click', function(e) {
var value = selectElement ? selectElement.value : 0;
delegate.removeHandler(value, data.handlers[value]);
diff --git a/chromium/chrome/browser/resources/options/hotword_search_setting_indicator.js b/chromium/chrome/browser/resources/options/hotword_search_setting_indicator.js
index 697e63f7b21..40e1b170798 100644
--- a/chromium/chrome/browser/resources/options/hotword_search_setting_indicator.js
+++ b/chromium/chrome/browser/resources/options/hotword_search_setting_indicator.js
@@ -38,6 +38,17 @@ cr.define('options', function() {
},
/**
+ * Sets the variable tracking the section which becomes disabled if an
+ * error exists.
+ * @param {HTMLElement} section The section to disable.
+ */
+ set disabledOnErrorSection(section) {
+ // TODO(kcarattini): Instead of this, add a cr.EventTarget and have
+ // the element to disable register an event on it.
+ this.disabledOnErrorSection_ = section;
+ },
+
+ /**
* Returns the current error.
* @return {string} The error message to be displayed. May be undefined if
* there is no error.
@@ -63,6 +74,8 @@ cr.define('options', function() {
updateBasedOnError: function() {
if (this.errorText_)
this.hidden = false;
+ if (this.disabledOnErrorSection_)
+ this.disabledOnErrorSection_.disabled = !!this.errorText_;
},
/**
diff --git a/chromium/chrome/browser/resources/options/inline_editable_list.js b/chromium/chrome/browser/resources/options/inline_editable_list.js
index 1461fca292f..c2db84aab27 100644
--- a/chromium/chrome/browser/resources/options/inline_editable_list.js
+++ b/chromium/chrome/browser/resources/options/inline_editable_list.js
@@ -31,6 +31,12 @@ cr.define('options', function() {
__proto__: DeletableItem.prototype,
/**
+ * Index of currently focused column, or -1 for none.
+ * @type {number}
+ */
+ focusedColumnIndex: -1,
+
+ /**
* Whether or not this item can be edited.
* @type {boolean}
* @private
@@ -74,21 +80,34 @@ cr.define('options', function() {
this.editFields_ = [];
this.addEventListener('mousedown', this.handleMouseDown_);
this.addEventListener('keydown', this.handleKeyDown_);
- this.addEventListener('leadChange', this.handleLeadChange_);
+ this.addEventListener('focusin', this.handleFocusIn_);
},
/** @override */
selectionChanged: function() {
- this.updateEditState();
+ if (!this.parentNode.ignoreChangeEvents_)
+ this.updateEditState();
},
/**
* Called when this element gains or loses 'lead' status. Updates editing
* mode accordingly.
- * @private
*/
- handleLeadChange_: function() {
+ updateLeadState: function() {
+ // Add focusability before call to updateEditState.
+ if (this.lead) {
+ this.setEditableValuesFocusable(true);
+ this.setCloseButtonFocusable(true);
+ }
+
this.updateEditState();
+
+ // Remove focusability after call to updateEditState.
+ this.setStaticValuesFocusable(false);
+ if (!this.lead) {
+ this.setEditableValuesFocusable(false);
+ this.setCloseButtonFocusable(false);
+ }
},
/**
@@ -120,25 +139,14 @@ cr.define('options', function() {
cr.dispatchSimpleEvent(this, 'edit', true);
- var focusElement = this.editClickTarget_ || this.initialFocusElement;
- this.editClickTarget_ = null;
-
+ var isMouseClick = this.editClickTarget_;
+ var focusElement = this.getEditFocusElement_();
if (focusElement) {
- var self = this;
- // We should delay to give focus on |focusElement| if this is called
- // in mousedown event handler. If we did give focus immediately, Blink
- // would try to focus on an ancestor of the mousedown target element,
- // and remove focus from |focusElement|.
- if (focusElement.staticVersion &&
- focusElement.staticVersion.hasAttribute('tabindex')) {
+ if (isMouseClick) {
+ // Delay focus to fix http://crbug.com/436789
setTimeout(function() {
- if (self.editing) {
- if (focusElement.disabled)
- self.parentNode.focus();
- self.focusAndMaybeSelect_(focusElement);
- }
- focusElement.staticVersion.removeAttribute('tabindex');
- }, 0);
+ this.focusAndMaybeSelect_(focusElement);
+ }.bind(this), 0);
} else {
this.focusAndMaybeSelect_(focusElement);
}
@@ -146,12 +154,12 @@ cr.define('options', function() {
} else {
if (!this.editCancelled_ && this.hasBeenEdited &&
this.currentInputIsValid) {
- if (this.isPlaceholder)
- this.parentNode.focusPlaceholder = true;
-
+ this.parentNode.needsToFocusPlaceholder_ = this.isPlaceholder &&
+ this.parentNode.shouldFocusPlaceholderOnEditCommit();
this.updateStaticValues_();
cr.dispatchSimpleEvent(this, 'commitedit', true);
} else {
+ this.parentNode.needsToFocusPlaceholder_ = false;
this.resetEditableValues_();
cr.dispatchSimpleEvent(this, 'canceledit', true);
}
@@ -159,6 +167,42 @@ cr.define('options', function() {
},
/**
+ * Return editable element that should be focused, or null for none.
+ * @private
+ */
+ getEditFocusElement_: function() {
+ // If an edit field was clicked on then use the clicked element.
+ if (this.editClickTarget_) {
+ var result = this.editClickTarget_;
+ this.editClickTarget_ = null;
+ return result;
+ }
+
+ // If focusedColumnIndex is valid then use the element in that column.
+ if (this.focusedColumnIndex != -1) {
+ var nearestColumn =
+ this.getNearestColumnByIndex_(this.focusedColumnIndex);
+ if (nearestColumn)
+ return nearestColumn;
+ }
+
+ // It's possible that focusedColumnIndex hasn't been updated yet.
+ // Check getFocusedColumnIndex_ directly.
+ // This can't completely replace the above focusedColumnIndex check
+ // because InlineEditableItemList may have set focusedColumnIndex to a
+ // different value.
+ var columnIndex = this.getFocusedColumnIndex_();
+ if (columnIndex != -1) {
+ var nearestColumn = this.getNearestColumnByIndex_(columnIndex);
+ if (nearestColumn)
+ return nearestColumn;
+ }
+
+ // Everything else failed so return the default.
+ return this.initialFocusElement;
+ },
+
+ /**
* Focus on the specified element, and select the editable text in it
* if possible.
* @param {!Element} control An element to be focused.
@@ -229,6 +273,48 @@ cr.define('options', function() {
},
/**
+ * Sets whether the editable values can be given focus using the keyboard.
+ * @param {boolean} focusable The desired focusable state.
+ */
+ setEditableValuesFocusable: function(focusable) {
+ focusable = focusable && this.editable;
+ var editFields = this.editFields_;
+ for (var i = 0; i < editFields.length; i++) {
+ editFields[i].tabIndex = focusable ? 0 : -1;
+ }
+ },
+
+ /**
+ * Sets whether the static values can be given focus using the keyboard.
+ * @param {boolean} focusable The desired focusable state.
+ */
+ setStaticValuesFocusable: function(focusable) {
+ var editFields = this.editFields_;
+ for (var i = 0; i < editFields.length; i++) {
+ var staticVersion = editFields[i].staticVersion;
+ if (!staticVersion)
+ continue;
+ if (this.editable) {
+ staticVersion.tabIndex = focusable ? 0 : -1;
+ } else {
+ // staticVersion remains visible when !this.editable. Remove
+ // tabindex so that it will not become focused by clicking on it and
+ // have selection box drawn around it.
+ staticVersion.removeAttribute('tabindex');
+ }
+ }
+ },
+
+ /**
+ * Sets whether the close button can be focused using the keyboard.
+ * @param {boolean} focusable The desired focusable state.
+ */
+ setCloseButtonFocusable: function(focusable) {
+ this.closeButtonElement.tabIndex =
+ focusable && this.closeButtonFocusAllowed ? 0 : -1;
+ },
+
+ /**
* Returns a div containing an <input>, as well as static text if
* isPlaceholder is not true.
* @param {string} text The text of the cell.
@@ -250,25 +336,12 @@ cr.define('options', function() {
var inputEl = this.ownerDocument.createElement('input');
inputEl.type = 'text';
inputEl.value = text;
- if (!this.isPlaceholder) {
+ if (!this.isPlaceholder)
inputEl.setAttribute('displaymode', 'edit');
- } else {
- // At this point |this| is not attached to the parent list yet, so give
- // a short timeout in order for the attachment to occur.
- var self = this;
- window.setTimeout(function() {
- var list = self.parentNode;
- if (list && list.focusPlaceholder) {
- list.focusPlaceholder = false;
- if (list.shouldFocusPlaceholder())
- inputEl.focus();
- }
- }, 50);
- }
// In some cases 'focus' event may arrive before 'input'.
// To make sure revalidation is triggered we postpone 'focus' handling.
- var handler = this.handleFocus_.bind(this);
+ var handler = this.handleFocus.bind(this);
inputEl.addEventListener('focus', function() {
window.setTimeout(function() {
if (inputEl.ownerDocument.activeElement == inputEl)
@@ -290,10 +363,39 @@ cr.define('options', function() {
*/
addEditField: function(control, staticElement) {
control.staticVersion = staticElement;
+ if (this.editable)
+ control.tabIndex = -1;
+
+ if (control.staticVersion) {
+ if (this.editable)
+ control.staticVersion.tabIndex = -1;
+ control.staticVersion.editableVersion = control;
+ control.staticVersion.addEventListener('focus',
+ this.handleFocus.bind(this));
+ }
this.editFields_.push(control);
},
/**
+ * Set the column index for a child element of this InlineEditableItem.
+ * Only elements with a column index will be keyboard focusable, e.g. by
+ * pressing the tab key.
+ * @param {Element} element Element whose column index to set. Method does
+ * nothing if element is null.
+ * @param {number} columnIndex The new column index to set on the element.
+ * -1 removes the column index.
+ */
+ setFocusableColumnIndex: function(element, columnIndex) {
+ if (!element)
+ return;
+
+ if (columnIndex >= 0)
+ element.setAttribute('inlineeditable-column', columnIndex);
+ else
+ element.removeAttribute('inlineeditable-column');
+ },
+
+ /**
* Resets the editable version of any controls created by createEditable*
* to match the static text.
* @private
@@ -335,6 +437,39 @@ cr.define('options', function() {
},
/**
+ * Returns the index of the column that currently has focus, or -1 if no
+ * column has focus.
+ * @return {number}
+ * @private
+ */
+ getFocusedColumnIndex_: function() {
+ var element = document.activeElement.editableVersion ||
+ document.activeElement;
+
+ if (element.hasAttribute('inlineeditable-column'))
+ return parseInt(element.getAttribute('inlineeditable-column'), 10);
+ return -1;
+ },
+
+ /**
+ * Returns the element from the column that has the largest index where:
+ * where:
+ * + index <= startIndex, and
+ * + the element exists, and
+ * + the element is not disabled
+ * @return {Element}
+ * @private
+ */
+ getNearestColumnByIndex_: function(startIndex) {
+ for (var i = startIndex; i >= 0; --i) {
+ var el = this.querySelector('[inlineeditable-column="' + i + '"]');
+ if (el && !el.disabled)
+ return el;
+ }
+ return null;
+ },
+
+ /**
* Called when a key is pressed. Handles committing and canceling edits.
* @param {Event} e The key down event.
* @private
@@ -375,20 +510,45 @@ cr.define('options', function() {
* @private
*/
handleMouseDown_: function(e) {
- if (!this.editable || this.editing)
+ if (!this.editable)
return;
var clickTarget = e.target;
var editFields = this.editFields_;
+ var editClickTarget;
for (var i = 0; i < editFields.length; i++) {
- if (editFields[i].staticVersion == clickTarget)
- clickTarget.tabIndex = 0;
if (editFields[i] == clickTarget ||
editFields[i].staticVersion == clickTarget) {
- this.editClickTarget_ = editFields[i];
- return;
+ editClickTarget = editFields[i];
+ break;
}
}
+
+ if (this.editing) {
+ if (!editClickTarget) {
+ // Clicked on the list item outside of an edit field. Don't lose focus
+ // from currently selected edit field.
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ return;
+ }
+
+ if (editClickTarget && !editClickTarget.disabled)
+ this.editClickTarget_ = editClickTarget;
+ },
+
+ /**
+ * Called when this InlineEditableItem or any of its children are given
+ * focus. Updates focusedColumnIndex with the index of the newly focused
+ * column, or -1 if the focused element does not have a column index.
+ * @param {Event} e The focusin event.
+ * @private
+ */
+ handleFocusIn_: function(e) {
+ var target = e.target.editableVersion || e.target;
+ this.focusedColumnIndex = target.hasAttribute('inlineeditable-column') ?
+ parseInt(target.getAttribute('inlineeditable-column'), 10) : -1;
},
};
@@ -417,10 +577,20 @@ cr.define('options', function() {
__proto__: DeletableItemList.prototype,
/**
+ * Whether to ignore list change events.
+ * Used to modify the list without processing selection change and lead
+ * change events.
+ * @type {boolean}
+ * @private
+ */
+ ignoreChangeEvents_: false,
+
+ /**
* Focuses the input element of the placeholder if true.
* @type {boolean}
+ * @private
*/
- focusPlaceholder: false,
+ needsToFocusPlaceholder_: false,
/** @override */
decorate: function() {
@@ -442,10 +612,136 @@ cr.define('options', function() {
handleListFocusChange_: function(e) {
var leadItem = this.getListItemByIndex(this.selectionModel.leadIndex);
if (leadItem) {
- if (e.newValue)
+ if (e.newValue) {
+ // Add focusability before making other changes.
+ leadItem.setEditableValuesFocusable(true);
+ leadItem.setCloseButtonFocusable(true);
+ leadItem.focusedColumnIndex = -1;
leadItem.updateEditState();
- else
+ // Remove focusability after making other changes.
+ leadItem.setStaticValuesFocusable(false);
+ } else {
+ // Add focusability before making other changes.
+ leadItem.setStaticValuesFocusable(true);
+ leadItem.setCloseButtonFocusable(true);
leadItem.editing = false;
+ // Remove focusability after making other changes.
+ if (!leadItem.isPlaceholder)
+ leadItem.setEditableValuesFocusable(false);
+ }
+ }
+ },
+
+ /** @override */
+ handleLeadChange: function(e) {
+ if (this.ignoreChangeEvents_)
+ return;
+
+ DeletableItemList.prototype.handleLeadChange.call(this, e);
+
+ var focusedColumnIndex = -1;
+ if (e.oldValue != -1) {
+ var element = this.getListItemByIndex(e.oldValue);
+ if (element) {
+ focusedColumnIndex = element.focusedColumnIndex;
+ element.updateLeadState();
+ }
+ }
+
+ if (e.newValue != -1) {
+ var element = this.getListItemByIndex(e.newValue);
+ if (element) {
+ element.focusedColumnIndex = focusedColumnIndex;
+ element.updateLeadState();
+ }
+ }
+ },
+
+ /** @override */
+ onSetDataModelComplete: function() {
+ DeletableItemList.prototype.onSetDataModelComplete.call(this);
+
+ if (this.needsToFocusPlaceholder_) {
+ this.focusPlaceholder();
+ } else {
+ var item = this.getInitialFocusableItem();
+ if (item) {
+ item.setStaticValuesFocusable(true);
+ item.setCloseButtonFocusable(true);
+ if (item.isPlaceholder)
+ item.setEditableValuesFocusable(true);
+ }
+ }
+ },
+
+ /**
+ * Execute |callback| with list change events disabled. Selection change and
+ * lead change events will not be processed.
+ * @param {!Function} callback The function to execute.
+ * @protected
+ */
+ ignoreChangeEvents: function(callback) {
+ assert(!this.ignoreChangeEvents_);
+ this.ignoreChangeEvents_ = true;
+ callback();
+ this.ignoreChangeEvents_ = false;
+ },
+
+ /**
+ * Set the selected index without changing the focused element on the page.
+ * Used to change the selected index when the list doesn't have focus (and
+ * doesn't want to take focus).
+ * @param {number} index The index to select.
+ */
+ selectIndexWithoutFocusing: function(index) {
+ // Remove focusability from old item.
+ var oldItem = this.getListItemByIndex(this.selectionModel.leadIndex) ||
+ this.getInitialFocusableItem();
+ if (oldItem) {
+ oldItem.setEditableValuesFocusable(false);
+ oldItem.setStaticValuesFocusable(false);
+ oldItem.setCloseButtonFocusable(false);
+ oldItem.lead = false;
+ }
+
+ // Select the new item.
+ this.ignoreChangeEvents(function() {
+ this.selectionModel.selectedIndex = index;
+ }.bind(this));
+
+ // Add focusability to new item.
+ var newItem = this.getListItemByIndex(index);
+ if (newItem) {
+ if (newItem.isPlaceholder)
+ newItem.setEditableValuesFocusable(true);
+ else
+ newItem.setStaticValuesFocusable(true);
+
+ newItem.setCloseButtonFocusable(true);
+ newItem.lead = true;
+ }
+ },
+
+ /**
+ * Focus the placeholder's first input field.
+ * Should only be called immediately after the list has been repopulated.
+ */
+ focusPlaceholder: function() {
+ // Remove focusability from initial item.
+ var item = this.getInitialFocusableItem();
+ if (item) {
+ item.setStaticValuesFocusable(false);
+ item.setCloseButtonFocusable(false);
+ }
+ // Find placeholder and focus it.
+ for (var i = 0; i < this.dataModel.length; i++) {
+ var item = this.getListItemByIndex(i);
+ if (item.isPlaceholder) {
+ item.setEditableValuesFocusable(true);
+ item.setCloseButtonFocusable(true);
+ item.querySelector('input').focus();
+ return;
+ }
}
},
@@ -453,10 +749,21 @@ cr.define('options', function() {
* May be overridden by subclasses to disable focusing the placeholder.
* @return {boolean} True if the placeholder element should be focused on
* edit commit.
+ * @protected
*/
- shouldFocusPlaceholder: function() {
+ shouldFocusPlaceholderOnEditCommit: function() {
return true;
},
+
+ /**
+ * Override to change which item is initially focusable.
+ * @return {options.InlineEditableItem} Initially focusable item or null.
+ * @protected
+ */
+ getInitialFocusableItem: function() {
+ return /** @type {options.InlineEditableItem} */(
+ this.getListItemByIndex(0));
+ },
};
// Export
diff --git a/chromium/chrome/browser/resources/options/language_add_language_overlay.js b/chromium/chrome/browser/resources/options/language_add_language_overlay.js
index 313feeefc32..9197ecdb5bc 100644
--- a/chromium/chrome/browser/resources/options/language_add_language_overlay.js
+++ b/chromium/chrome/browser/resources/options/language_add_language_overlay.js
@@ -38,7 +38,7 @@ cr.define('options', function() {
// Create the language list with which users can add a language.
var addLanguageList = $('add-language-overlay-language-list');
/**
- * @type {Array.<{code: string, displayName: string,
+ * @type {Array<{code: string, displayName: string,
* textDirection: string, nativeDisplayName: string}>}
* @see chrome/browser/ui/webui/options/language_options_handler.cc
*/
diff --git a/chromium/chrome/browser/resources/options/language_dictionary_overlay_word_list.js b/chromium/chrome/browser/resources/options/language_dictionary_overlay_word_list.js
index c16f34e9fc2..fbc868e1b36 100644
--- a/chromium/chrome/browser/resources/options/language_dictionary_overlay_word_list.js
+++ b/chromium/chrome/browser/resources/options/language_dictionary_overlay_word_list.js
@@ -62,6 +62,24 @@ cr.define('options.dictionary_words', function() {
return this.querySelector('input').value.length > 0;
},
+ /** @override */
+ updateLeadState: function() {
+ InlineEditableItem.prototype.updateLeadState.call(this);
+
+ // Allow focusing the list item itself if not editable.
+ if (!this.editable)
+ this.tabIndex = this.lead ? 0 : -1;
+ },
+
+ /** @override */
+ updateEditState: function() {
+ InlineEditableItem.prototype.updateEditState.call(this);
+
+ // Focus the list item itself if not editable.
+ if (!this.editable && this.selected && this.lead)
+ this.focus();
+ },
+
/**
* Adds a word to the dictionary.
* @param {Event} e Edit committed event.
@@ -230,9 +248,15 @@ cr.define('options.dictionary_words', function() {
},
/** @override */
- shouldFocusPlaceholder: function() {
+ shouldFocusPlaceholderOnEditCommit: function() {
return false;
},
+
+ /** @override */
+ getInitialFocusableItem: function() {
+ return /** @type {options.InlineEditableItem} */(
+ this.getListItemByIndex(this.selectionModel.length - 1));
+ },
};
return {
diff --git a/chromium/chrome/browser/resources/options/language_options.js b/chromium/chrome/browser/resources/options/language_options.js
index 721ffb239d6..9ab66bf8d28 100644
--- a/chromium/chrome/browser/resources/options/language_options.js
+++ b/chromium/chrome/browser/resources/options/language_options.js
@@ -205,6 +205,8 @@ cr.define('options', function() {
this.addBlockedLanguage_(addLanguageCode);
} else {
PageManager.showPageByName('addLanguage');
+ chrome.send('coreOptionsUserMetricsAction',
+ ['Options_Languages_Add']);
}
};
$('language-options-add-button').onclick = onclick.bind(this);
@@ -272,9 +274,9 @@ cr.define('options', function() {
// on the selected language. Note that we only have less than 100
// input methods, so creating DOM nodes at once here should be ok.
this.appendInputMethodElement_(loadTimeData.getValue('inputMethodList'));
- this.appendInputMethodElement_(loadTimeData.getValue('extensionImeList'));
this.appendComponentExtensionIme_(
loadTimeData.getValue('componentExtensionImeList'));
+ this.appendInputMethodElement_(loadTimeData.getValue('extensionImeList'));
// Listen to pref change once the input method list is initialized.
Preferences.getInstance().addEventListener(
@@ -846,6 +848,10 @@ cr.define('options', function() {
} else {
this.handleCheckboxUpdate_(checkbox);
}
+
+ chrome.send('coreOptionsUserMetricsAction',
+ ['Options_Languages_InputMethodCheckbox' +
+ (checkbox.checked ? '_Enable' : '_Disable')]);
},
/**
@@ -967,6 +973,8 @@ cr.define('options', function() {
Preferences.setStringPref(SPELL_CHECK_DICTIONARY_PREF,
languageCode, true);
chrome.send('spellCheckLanguageChange', [languageCode]);
+ chrome.send('coreOptionsUserMetricsAction',
+ ['Options_Languages_SpellCheck']);
},
/**
@@ -1316,12 +1324,15 @@ cr.define('options', function() {
var tokens = languageCode.split('-');
var main = tokens[0];
- // See also: chrome/renderer/translate/translate_helper.cc.
+ // See also: components/translate/core/browser/common/translate_util.cc
var synonyms = {
'nb': 'no',
'he': 'iw',
'jv': 'jw',
'fil': 'tl',
+ 'zh-HK': 'zh-TW',
+ 'zh-MO': 'zh-TW',
+ 'zh-SG': 'zh-CN',
};
if (main in synonyms) {
@@ -1338,7 +1349,7 @@ cr.define('options', function() {
/**
* Shows the node at |index| in |nodes|, hides all others.
- * @param {Array.<HTMLElement>} nodes The nodes to be shown or hidden.
+ * @param {Array<HTMLElement>} nodes The nodes to be shown or hidden.
* @param {number} index The index of |nodes| to show.
*/
function showMutuallyExclusiveNodes(nodes, index) {
diff --git a/chromium/chrome/browser/resources/options/manage_profile_overlay.css b/chromium/chrome/browser/resources/options/manage_profile_overlay.css
index 8abddd06832..b34789481a6 100644
--- a/chromium/chrome/browser/resources/options/manage_profile_overlay.css
+++ b/chromium/chrome/browser/resources/options/manage_profile_overlay.css
@@ -3,7 +3,7 @@
* found in the LICENSE file. */
#manage-profile-overlay {
- width: 612px;
+ width: 616px;
}
.profile-icon-grid-item {
diff --git a/chromium/chrome/browser/resources/options/manage_profile_overlay.js b/chromium/chrome/browser/resources/options/manage_profile_overlay.js
index 0a4a5b56a41..5b3e41f813d 100644
--- a/chromium/chrome/browser/resources/options/manage_profile_overlay.js
+++ b/chromium/chrome/browser/resources/options/manage_profile_overlay.js
@@ -71,8 +71,6 @@ cr.define('options', function() {
};
$('delete-profile-ok').onclick = function(event) {
PageManager.closeOverlay();
- if (BrowserOptions.getCurrentProfile().isSupervised)
- return;
chrome.send('deleteProfile', [self.profileInfo_.filePath]);
options.SupervisedUserListData.resetPromise();
};
@@ -148,7 +146,7 @@ cr.define('options', function() {
}
var manageNameField = $('manage-profile-name');
- // Supervised users cannot edit their names.
+ // Legacy supervised users cannot edit their names.
if (manageNameField.disabled)
$('manage-profile-ok').focus();
else
@@ -218,8 +216,8 @@ cr.define('options', function() {
* the user will use to choose their profile icon.
* @param {string} mode A label that specifies the type of dialog box which
* is currently being viewed (i.e. 'create' or 'manage').
- * @param {!Array.<string>} iconURLs An array of icon URLs.
- * @param {Array.<string>} names An array of default names
+ * @param {!Array<string>} iconURLs An array of icon URLs.
+ * @param {Array<string>} names An array of default names
* corresponding to the icons.
* @private
*/
@@ -382,7 +380,7 @@ cr.define('options', function() {
* user. If yes, the user is prompted to import the existing supervised
* user, and the create button is disabled.
* If the received list is empty, hides the "import" link.
- * @param {Array.<Object>} supervisedUsers The list of existing supervised
+ * @param {Array<Object>} supervisedUsers The list of existing supervised
* users.
* @private
*/
@@ -438,19 +436,10 @@ cr.define('options', function() {
updateOkButton_: function(mode) {
var oldName = this.profileInfo_.name;
var newName = $(mode + '-profile-name').value;
- var nameIsDuplicate = this.existingProfileNames_[newName] != undefined;
- if (mode == 'manage' && oldName == newName)
- nameIsDuplicate = false;
- if (nameIsDuplicate) {
- var errorHtml =
- loadTimeData.getString('manageProfilesDuplicateNameError');
- this.showErrorBubble_(errorHtml, mode, true);
- } else {
- this.hideErrorBubble_(mode);
+ this.hideErrorBubble_(mode);
- var nameIsValid = $(mode + '-profile-name').validity.valid;
- $(mode + '-profile-ok').disabled = !nameIsValid;
- }
+ var nameIsValid = $(mode + '-profile-name').validity.valid;
+ $(mode + '-profile-ok').disabled = !nameIsValid;
},
/**
@@ -545,7 +534,8 @@ cr.define('options', function() {
$('manage-profile-overlay-manage').hidden = false;
$('manage-profile-overlay-delete').hidden = true;
$('manage-profile-overlay-disconnect-managed').hidden = true;
- $('manage-profile-name').disabled = profileInfo.isSupervised;
+ $('manage-profile-name').disabled =
+ profileInfo.isSupervised && !profileInfo.isChild;
this.hideErrorBubble_('manage');
},
@@ -567,9 +557,6 @@ cr.define('options', function() {
* @private
*/
showDeleteDialog_: function(profileInfo) {
- if (BrowserOptions.getCurrentProfile().isSupervised)
- return;
-
ManageProfileOverlay.setProfileInfo(profileInfo, 'manage');
$('manage-profile-overlay-create').hidden = true;
$('manage-profile-overlay-manage').hidden = true;
@@ -581,11 +568,12 @@ cr.define('options', function() {
loadTimeData.getStringF('deleteProfileMessage',
elide(profileInfo.name, /* maxLength */ 50));
$('delete-supervised-profile-addendum').hidden =
- !profileInfo.isSupervised;
+ !profileInfo.isSupervised || profileInfo.isChild;
// Because this dialog isn't useful when refreshing or as part of the
// history, don't create a history entry for it when showing.
PageManager.showPageByName('manageProfile', false);
+ chrome.send('logDeleteUserDialogShown');
},
/**
diff --git a/chromium/chrome/browser/resources/options/options.html b/chromium/chrome/browser/resources/options/options.html
index e37929718c0..662762cf65b 100644
--- a/chromium/chrome/browser/resources/options/options.html
+++ b/chromium/chrome/browser/resources/options/options.html
@@ -1,11 +1,12 @@
-<!DOCTYPE HTML>
-<html id="t" i18n-values="dir:textdirection">
+<!doctype html>
+<html id="t" i18n-values="dir:textdirection;lang:language">
<head>
<meta charset="utf-8">
<title i18n-content="optionsPageTitle"></title>
<link rel="stylesheet" href="chrome://resources/css/bubble.css">
<link rel="stylesheet" href="chrome://resources/css/bubble_button.css">
<link rel="stylesheet" href="chrome://resources/css/chrome_shared.css">
+<link rel="stylesheet" href="chrome://resources/css/controlled_indicator.css">
<link rel="stylesheet" href="chrome://resources/css/list.css">
<link rel="stylesheet" href="chrome://resources/css/overlay.css">
<link rel="stylesheet" href="chrome://resources/css/spinner.css">
@@ -22,7 +23,6 @@
</if>
<link rel="stylesheet" href="clear_browser_data_overlay.css">
<link rel="stylesheet" href="content_settings.css">
-<link rel="stylesheet" href="controlled_setting.css">
<link rel="stylesheet" href="cookies_view.css">
<link rel="stylesheet" href="do_not_track_confirm_overlay.css">
<link rel="stylesheet" href="easy_unlock_turn_off_overlay.css">
@@ -65,7 +65,7 @@
<link rel="stylesheet" href="factory_reset_overlay.css">
<link rel="stylesheet" href="../help/channel_change_page.css">
</if>
-<if expr="use_nss">
+<if expr="use_nss_certs">
<link rel="stylesheet" href="certificate_manager.css">
<link rel="stylesheet" href="certificate_tree.css">
</if>
@@ -82,6 +82,7 @@
<script src="chrome://resources/js/cr/ui/array_data_model.js"></script>
<script src="chrome://resources/js/cr/ui/bubble.js"></script>
<script src="chrome://resources/js/cr/ui/bubble_button.js"></script>
+<script src="chrome://resources/js/cr/ui/controlled_indicator.js"></script>
<script src="chrome://resources/js/cr/ui/focus_manager.js"></script>
<script src="chrome://resources/js/cr/ui/focus_outline_manager.js"></script>
<script src="chrome://resources/js/cr/ui/list_selection_model.js"></script>
@@ -108,7 +109,7 @@
<script src="chrome://settings-frame/options_bundle.js"></script>
</head>
-<body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<body>
<div id="overlay-container-1" class="overlay transparent" hidden>
<include src="autofill_options.html">
<include src="clear_browser_data_overlay.html">
@@ -141,7 +142,7 @@
<include src="chromeos/power_overlay.html">
<include src="factory_reset_overlay.html">
</if>
-<if expr="use_nss">
+<if expr="use_nss_certs">
<include src="certificate_manager.html">
</if>
</div>
@@ -209,6 +210,6 @@
</div>
</div>
-<script src="chrome://resources/js/i18n_template2.js"></script>
+<script src="chrome://resources/js/i18n_template.js"></script>
</body>
</html>
diff --git a/chromium/chrome/browser/resources/options/options.js b/chromium/chrome/browser/resources/options/options.js
index 7b46bec2390..e0600241439 100644
--- a/chromium/chrome/browser/resources/options/options.js
+++ b/chromium/chrome/browser/resources/options/options.js
@@ -239,7 +239,6 @@ function load() {
CertificateManager.getInstance());
}
- cr.ui.FocusManager.disableMouseFocusOnButtons();
OptionsFocusManager.getInstance().initialize();
Preferences.getInstance().initialize();
ResetProfileSettingsBanner.getInstance().initialize();
diff --git a/chromium/chrome/browser/resources/options/options_bundle.js b/chromium/chrome/browser/resources/options/options_bundle.js
index dbdbccef828..6b90d4f5d5a 100644
--- a/chromium/chrome/browser/resources/options/options_bundle.js
+++ b/chromium/chrome/browser/resources/options/options_bundle.js
@@ -20,6 +20,7 @@
<include src="../help/channel_change_page.js">
<include src="../../../../ui/webui/resources/js/chromeos/ui_account_tweaks.js">
<include src="chromeos/onc_data.js">
+<include src="chromeos/vpn_providers.js">
<include src="chromeos/change_picture_options.js">
<include src="chromeos/internet_detail_ip_address_field.js">
<include src="chromeos/internet_detail.js">
@@ -52,7 +53,7 @@ var PointerOverlay = options.PointerOverlay;
var PowerOverlay = options.PowerOverlay;
var UIAccountTweaks = uiAccountTweaks.UIAccountTweaks;
</if>
-<if expr="use_nss">
+<if expr="use_nss_certs">
<include src="certificate_tree.js">
<include src="certificate_manager.js">
<include src="certificate_restore_overlay.js">
diff --git a/chromium/chrome/browser/resources/options/options_page.css b/chromium/chrome/browser/resources/options/options_page.css
index e122a1bb881..22790a3d6e0 100644
--- a/chromium/chrome/browser/resources/options/options_page.css
+++ b/chromium/chrome/browser/resources/options/options_page.css
@@ -72,10 +72,10 @@ div.disabled {
.raw-button,
.raw-button:hover,
.raw-button:active {
- -webkit-box-shadow: none;
background-color: transparent;
background-repeat: no-repeat;
border: none;
+ box-shadow: none;
min-width: 0;
padding: 1px 6px;
}
@@ -245,6 +245,7 @@ list > .heading:hover {
list .deletable-item {
-webkit-box-align: center;
+ outline: none;
}
list .deletable-item > :first-child {
@@ -259,8 +260,8 @@ list .row-delete-button {
background-color: transparent;
/* TODO(stuartmorgan): Replace with real images once they are available. */
background-image: -webkit-image-set(
- url('../../../../ui/resources/default_100_percent/close_2.png') 1x,
- url('../../../../ui/resources/default_200_percent/close_2.png') 2x);
+ url(../../../../ui/resources/default_100_percent/close_2.png) 1x,
+ url(../../../../ui/resources/default_200_percent/close_2.png) 2x);
border: none;
display: block;
height: 16px;
@@ -286,15 +287,15 @@ list div[role='listitem'][managedby='HostedApp'] .row-delete-button {
list .row-delete-button:hover {
background-image: -webkit-image-set(
- url('../../../../ui/resources/default_100_percent/close_2_hover.png') 1x,
- url('../../../../ui/resources/default_200_percent/close_2_hover.png') 2x);
+ url(../../../../ui/resources/default_100_percent/close_2_hover.png) 1x,
+ url(../../../../ui/resources/default_200_percent/close_2_hover.png) 2x);
}
list .row-delete-button:active {
background-image: -webkit-image-set(
- url('../../../../ui/resources/default_100_percent/close_2_pressed.png')
+ url(../../../../ui/resources/default_100_percent/close_2_pressed.png)
1x,
- url('../../../../ui/resources/default_200_percent/close_2_pressed.png')
+ url(../../../../ui/resources/default_200_percent/close_2_pressed.png)
2x);
}
diff --git a/chromium/chrome/browser/resources/options/password_manager.html b/chromium/chrome/browser/resources/options/password_manager.html
index b085197ab07..888fb5cf2ec 100644
--- a/chromium/chrome/browser/resources/options/password_manager.html
+++ b/chromium/chrome/browser/resources/options/password_manager.html
@@ -2,6 +2,15 @@
<div class="close-button"></div>
<h1 i18n-content="passwordsPage"></h1>
<div class="content-area">
+ <div id="auto-signin-block" class="checkbox" hidden>
+ <label>
+ <input pref="profile.password_manager_auto_signin" type="checkbox">
+ <span i18n-content="autoSigninTitle"></span>
+ </label>
+ <div class="setting-extra-description">
+ <span i18n-content="autoSigninDescription"></span>
+ </div>
+ </div>
<div id="password-list-headers">
<div id="passwords-title">
<h3 i18n-content="savedPasswordsTitle"></h3>
@@ -23,7 +32,7 @@
<div id="password-exceptions-list-empty-placeholder" hidden
class="settings-list-empty">
<span i18n-content="passwordsNoExceptionsDescription"></span>
- <a target="_blank" i18n-content="learnMore"
+ <a id="exceptions-learn-more" target="_blank" i18n-content="learnMore"
i18n-values="href:passwordManagerLearnMoreURL"></a>
</div>
</div>
diff --git a/chromium/chrome/browser/resources/options/password_manager.js b/chromium/chrome/browser/resources/options/password_manager.js
index 55a76235d73..6e16ebac5e1 100644
--- a/chromium/chrome/browser/resources/options/password_manager.js
+++ b/chromium/chrome/browser/resources/options/password_manager.js
@@ -59,6 +59,9 @@ cr.define('options', function() {
initializePage: function() {
Page.prototype.initializePage.call(this);
+ $('auto-signin-block').hidden =
+ !loadTimeData.getBoolean('enableCredentialManagerAPI');
+
$('password-manager-confirm').onclick = function() {
PageManager.closeOverlay();
};
@@ -66,6 +69,12 @@ cr.define('options', function() {
$('password-search-box').addEventListener('search',
this.handleSearchQueryChange_.bind(this));
+ $('exceptions-learn-more').onclick = function() {
+ chrome.send('coreOptionsUserMetricsAction',
+ ['Options_PasswordManagerExceptionsLearnMore']);
+ return true; // Always follow the href
+ };
+
this.createSavedPasswordsList_();
this.createPasswordExceptionsList_();
},
@@ -122,6 +131,9 @@ cr.define('options', function() {
// snappier since users will expect that it's "less work."
this.queryDelayTimerId_ = window.setTimeout(
this.searchPasswords_.bind(this), 250);
+
+ chrome.send('coreOptionsUserMetricsAction',
+ ['Options_PasswordManagerSearch']);
},
/**
@@ -168,7 +180,7 @@ cr.define('options', function() {
entry[1].toLowerCase().indexOf(query.toLowerCase()) >= 0) {
// Keep the original index so we can delete correctly. See also
// deleteItemAtIndex() in password_manager_list.js that uses this.
- entry[3] = index;
+ entry[4] = index;
return true;
}
return false;
@@ -202,7 +214,7 @@ cr.define('options', function() {
// index in the model, but each entry stores its original index, so
// we can find the item using a linear search.
for (var i = 0; i < model.length; ++i) {
- if (model.item(i)[3] == index) {
+ if (model.item(i)[4] == index) {
index = i;
break;
}
@@ -229,6 +241,8 @@ cr.define('options', function() {
*/
PasswordManager.removeSavedPassword = function(rowIndex) {
chrome.send('removeSavedPassword', [String(rowIndex)]);
+ chrome.send('coreOptionsUserMetricsAction',
+ ['Options_PasswordManagerDeletePassword']);
};
/**
diff --git a/chromium/chrome/browser/resources/options/password_manager_list.css b/chromium/chrome/browser/resources/options/password_manager_list.css
index 8688619c287..ee17a12d31e 100644
--- a/chromium/chrome/browser/resources/options/password_manager_list.css
+++ b/chromium/chrome/browser/resources/options/password_manager_list.css
@@ -7,6 +7,8 @@
background: rgb(138, 170, 237);
font-size: 0.9em;
height: 18px;
+ margin-left: 2px;
+ margin-right: 2px;
padding: 0 2px;
position: absolute;
top: 3px;
@@ -32,12 +34,14 @@ input[type='password'].inactive-password {
#saved-passwords-list .name {
-webkit-box-flex: 1;
- width: 20%;
+ width: 30%;
}
-#saved-passwords-list .password {
+#saved-passwords-list .password,
+#saved-passwords-list .federation {
-webkit-box-flex: 1;
position: relative;
+ width: 30%;
}
#saved-passwords-list .password input[type='password'],
@@ -52,6 +56,7 @@ input[type='password'].inactive-password {
#saved-passwords-list .url,
#saved-passwords-list .name,
+#saved-passwords-list .federation,
#password-exceptions-list .url {
overflow: hidden;
text-overflow: ellipsis;
diff --git a/chromium/chrome/browser/resources/options/password_manager_list.js b/chromium/chrome/browser/resources/options/password_manager_list.js
index 78c9912b629..f8ed9e8a5fd 100644
--- a/chromium/chrome/browser/resources/options/password_manager_list.js
+++ b/chromium/chrome/browser/resources/options/password_manager_list.js
@@ -8,12 +8,19 @@ cr.define('options.passwordManager', function() {
/** @const */ var DeletableItem = options.DeletableItem;
/** @const */ var List = cr.ui.List;
+ /** @const */ var URL_DATA_INDEX = 0;
+ /** @const */ var USERNAME_DATA_INDEX = 1;
+ /** @const */ var PASSWORD_DATA_INDEX = 2;
+ /** @const */ var FEDERATION_DATA_INDEX = 3;
+ /** @const */ var ORIGINAL_DATA_INDEX = 4;
+
/**
* Creates a new passwords list item.
* @param {cr.ui.ArrayDataModel} dataModel The data model that contains this
* item.
- * @param {Array} entry An array of the form [url, username, password]. When
- * the list has been filtered, a fourth element [index] may be present.
+ * @param {Array} entry An array of the form [url, username, password,
+ * federation]. When the list has been filtered, a fifth element [index]
+ * may be present.
* @param {boolean} showPasswords If true, add a button to the element to
* allow the user to reveal the saved password.
* @constructor
@@ -24,7 +31,8 @@ cr.define('options.passwordManager', function() {
el.dataItem = entry;
el.dataModel = dataModel;
el.__proto__ = PasswordListItem.prototype;
- el.decorate(showPasswords);
+ el.showPasswords_ = showPasswords;
+ el.decorate();
return el;
}
@@ -33,7 +41,7 @@ cr.define('options.passwordManager', function() {
__proto__: DeletableItem.prototype,
/** @override */
- decorate: function(showPasswords) {
+ decorate: function() {
DeletableItem.prototype.decorate.call(this);
// The URL of the site.
@@ -60,37 +68,53 @@ cr.define('options.passwordManager', function() {
usernameLabel.title = this.username;
this.contentElement.appendChild(usernameLabel);
- // The stored password.
- var passwordInputDiv = this.ownerDocument.createElement('div');
- passwordInputDiv.className = 'password';
-
- // The password input field.
- var passwordInput = this.ownerDocument.createElement('input');
- passwordInput.type = 'password';
- passwordInput.className = 'inactive-password';
- passwordInput.readOnly = true;
- passwordInput.value = showPasswords ? this.password : '********';
- passwordInputDiv.appendChild(passwordInput);
- this.passwordField = passwordInput;
-
- // The show/hide button.
- if (showPasswords) {
- var button = this.ownerDocument.createElement('button');
- button.hidden = true;
- button.className = 'list-inline-button custom-appearance';
- button.textContent = loadTimeData.getString('passwordShowButton');
- button.addEventListener('click', this.onClick_.bind(this), true);
- button.addEventListener('mousedown', function(event) {
- // Don't focus on this button by mousedown.
- event.preventDefault();
- // Don't handle list item selection. It causes focus change.
- event.stopPropagation();
- }, false);
- passwordInputDiv.appendChild(button);
- this.passwordShowButton = button;
+ if (this.federation) {
+ // The federation.
+ var federationDiv = this.ownerDocument.createElement('div');
+ federationDiv.className = 'federation';
+ federationDiv.textContent = this.federation;
+ this.contentElement.appendChild(federationDiv);
+ } else {
+ // The stored password.
+ var passwordInputDiv = this.ownerDocument.createElement('div');
+ passwordInputDiv.className = 'password';
+
+ // The password input field.
+ var passwordInput = this.ownerDocument.createElement('input');
+ passwordInput.type = 'password';
+ passwordInput.className = 'inactive-password';
+ passwordInput.readOnly = true;
+ passwordInput.value = this.showPasswords_ ? this.password : '********';
+ passwordInputDiv.appendChild(passwordInput);
+ var deletableItem = this;
+ passwordInput.addEventListener('focus', function() {
+ deletableItem.handleFocus();
+ });
+ this.passwordField = passwordInput;
+ this.setFocusable_(false);
+
+ // The show/hide button.
+ if (this.showPasswords_) {
+ var button = this.ownerDocument.createElement('button');
+ button.hidden = true;
+ button.className = 'list-inline-button custom-appearance';
+ button.textContent = loadTimeData.getString('passwordShowButton');
+ button.addEventListener('click', this.onClick_.bind(this), true);
+ button.addEventListener('mousedown', function(event) {
+ // Don't focus on this button by mousedown.
+ event.preventDefault();
+ // Don't handle list item selection. It causes focus change.
+ event.stopPropagation();
+ }, false);
+ button.addEventListener('focus', function() {
+ deletableItem.handleFocus();
+ });
+ passwordInputDiv.appendChild(button);
+ this.passwordShowButton = button;
+ }
+ this.contentElement.appendChild(passwordInputDiv);
}
- this.contentElement.appendChild(passwordInputDiv);
},
/** @override */
@@ -103,14 +127,27 @@ cr.define('options.passwordManager', function() {
if (this.selected) {
input.classList.remove('inactive-password');
+ this.setFocusable_(true);
button.hidden = false;
+ input.focus();
} else {
input.classList.add('inactive-password');
+ this.setFocusable_(false);
button.hidden = true;
}
},
/**
+ * Set the focusability of this row.
+ * @param {boolean} focusable
+ * @private
+ */
+ setFocusable_: function(focusable) {
+ var tabIndex = focusable ? 0 : -1;
+ this.passwordField.tabIndex = this.closeButtonElement.tabIndex = tabIndex;
+ },
+
+ /**
* Reveals the plain text password of this entry.
*/
showPassword: function(password) {
@@ -139,7 +176,7 @@ cr.define('options.passwordManager', function() {
* @private
*/
getOriginalIndex_: function() {
- var index = this.dataItem[3];
+ var index = this.dataItem[ORIGINAL_DATA_INDEX];
return index ? index : this.dataModel.indexOf(this.dataItem);
},
@@ -162,10 +199,10 @@ cr.define('options.passwordManager', function() {
* @type {string}
*/
get url() {
- return this.dataItem[0];
+ return this.dataItem[URL_DATA_INDEX];
},
set url(url) {
- this.dataItem[0] = url;
+ this.dataItem[URL_DATA_INDEX] = url;
},
/**
@@ -173,10 +210,10 @@ cr.define('options.passwordManager', function() {
* @type {string}
*/
get username() {
- return this.dataItem[1];
+ return this.dataItem[USERNAME_DATA_INDEX];
},
set username(username) {
- this.dataItem[1] = username;
+ this.dataItem[USERNAME_DATA_INDEX] = username;
},
/**
@@ -184,10 +221,21 @@ cr.define('options.passwordManager', function() {
* @type {string}
*/
get password() {
- return this.dataItem[2];
+ return this.dataItem[PASSWORD_DATA_INDEX];
},
set password(password) {
- this.dataItem[2] = password;
+ this.dataItem[PASSWORD_DATA_INDEX] = password;
+ },
+
+ /**
+ * Get and set the federation for the entry.
+ * @type {string}
+ */
+ get federation() {
+ return this.dataItem[FEDERATION_DATA_INDEX];
+ },
+ set federation(federation) {
+ this.dataItem[FEDERATION_DATA_INDEX] = federation;
},
};
@@ -267,6 +315,7 @@ cr.define('options.passwordManager', function() {
Preferences.getInstance().addEventListener(
'profile.password_manager_allow_show_passwords',
this.onPreferenceChanged_.bind(this));
+ this.addEventListener('focus', this.onFocus_.bind(this));
},
/**
@@ -295,9 +344,9 @@ cr.define('options.passwordManager', function() {
/** @override */
deleteItemAtIndex: function(index) {
var item = this.dataModel.item(index);
- if (item && item.length > 3) {
- // The fourth element, if present, is the original index to delete.
- index = item[3];
+ if (item && item[ORIGINAL_DATA_INDEX] != undefined) {
+ // The fifth element, if present, is the original index to delete.
+ index = item[ORIGINAL_DATA_INDEX];
}
PasswordManager.removeSavedPassword(index);
},
@@ -308,12 +357,23 @@ cr.define('options.passwordManager', function() {
get length() {
return this.dataModel.length;
},
+
+ /**
+ * Will make to first row focusable if none are selected. This makes it
+ * possible to tab into the rows without pressing up/down first.
+ * @param {Event} e The focus event.
+ * @private
+ */
+ onFocus_: function(e) {
+ if (!this.selectedItem && this.items)
+ this.items[0].setFocusable_(true);
+ },
};
/**
* Create a new passwords list.
* @constructor
- * @extends {cr.ui.List}
+ * @extends {options.DeletableItemList}
*/
var PasswordExceptionsList = cr.ui.define('list');
diff --git a/chromium/chrome/browser/resources/options/pref_ui.js b/chromium/chrome/browser/resources/options/pref_ui.js
index 73247de0e0d..c0776fee447 100644
--- a/chromium/chrome/browser/resources/options/pref_ui.js
+++ b/chromium/chrome/browser/resources/options/pref_ui.js
@@ -98,6 +98,13 @@ cr.define('options', function() {
},
/**
+ * An abstract method for all subclasses to override to update their
+ * preference from existing state.
+ * @protected
+ */
+ updatePrefFromState: assertNotReached,
+
+ /**
* See |updateDisabledState| above.
*/
setDisabled: function(reason, disabled) {
@@ -383,7 +390,13 @@ cr.define('options', function() {
PrefSelect.prototype = {
// Set up the prototype chain
- __proto__: PrefInputElement.prototype,
+ __proto__: HTMLSelectElement.prototype,
+
+ /** @override */
+ decorate: PrefInputElement.prototype.decorate,
+
+ /** @override */
+ handleChange: PrefInputElement.prototype.handleChange,
/**
* Update the associated pref when when the user selects an item.
@@ -442,8 +455,47 @@ cr.define('options', function() {
if (this.onchange)
this.onchange(event);
},
+
+ /** @override */
+ setDisabled: PrefInputElement.prototype.setDisabled,
+
+ /** @override */
+ customChangeHandler: PrefInputElement.prototype.customChangeHandler,
+
+ /** @override */
+ customPrefChangeHandler: PrefInputElement.prototype.customPrefChangeHandler,
};
+ /**
+ * The name of the associated preference.
+ */
+ cr.defineProperty(PrefSelect, 'pref', cr.PropertyKind.ATTR);
+
+ /**
+ * The data type of the associated preference, only relevant for derived
+ * classes that support different data types.
+ */
+ cr.defineProperty(PrefSelect, 'dataType', cr.PropertyKind.ATTR);
+
+ /**
+ * Whether this input element is part of a dialog. If so, changes take effect
+ * in the settings UI immediately but are only actually committed when the
+ * user confirms the dialog. If the user cancels the dialog instead, the
+ * changes are rolled back in the settings UI and never committed.
+ */
+ cr.defineProperty(PrefSelect, 'dialogPref', cr.PropertyKind.BOOL_ATTR);
+
+ /**
+ * Whether the associated preference is controlled by a source other than the
+ * user's setting (can be 'policy', 'extension', 'recommended' or unset).
+ */
+ cr.defineProperty(PrefSelect, 'controlledBy', cr.PropertyKind.ATTR);
+
+ /**
+ * The user metric string.
+ */
+ cr.defineProperty(PrefSelect, 'metric', cr.PropertyKind.ATTR);
+
/////////////////////////////////////////////////////////////////////////////
// PrefTextField class:
diff --git a/chromium/chrome/browser/resources/options/reset_profile_settings_overlay.css b/chromium/chrome/browser/resources/options/reset_profile_settings_overlay.css
index e0d269766c4..62ceed961d7 100644
--- a/chromium/chrome/browser/resources/options/reset_profile_settings_overlay.css
+++ b/chromium/chrome/browser/resources/options/reset_profile_settings_overlay.css
@@ -48,7 +48,7 @@
}
#expand-feedback {
- background: url('chrome://theme/IDR_QUESTION_MARK') no-repeat center;
+ background: url(chrome://theme/IDR_QUESTION_MARK) no-repeat center;
display: inline-block;
height: 14px;
opacity: 0.33;
diff --git a/chromium/chrome/browser/resources/options/search_engine_manager_engine_list.js b/chromium/chrome/browser/resources/options/search_engine_manager_engine_list.js
index f94a01bb160..b2c6c1169d0 100644
--- a/chromium/chrome/browser/resources/options/search_engine_manager_engine_list.js
+++ b/chromium/chrome/browser/resources/options/search_engine_manager_engine_list.js
@@ -111,7 +111,7 @@ cr.define('options.search_engines', function() {
this.classList.add('default');
this.deletable = engine.canBeRemoved;
- this.closeButtonElement.tabIndex = 0;
+ this.closeButtonFocusAllowed = true;
// Construct the name column.
var nameColEl = this.ownerDocument.createElement('div');
@@ -140,6 +140,7 @@ cr.define('options.search_engines', function() {
// And the URL column.
var urlEl = this.createEditableTextCell(engine.url);
+ var makeDefaultButtonEl = null;
// Extensions should not display a URL column.
if (!engine.isOmniboxExtension) {
var urlWithButtonEl = this.ownerDocument.createElement('div');
@@ -151,7 +152,7 @@ cr.define('options.search_engines', function() {
// re-ordering is implemented. When this is removed, remove the extra
// div above.
if (engine.canBeDefault) {
- var makeDefaultButtonEl = this.ownerDocument.createElement('button');
+ makeDefaultButtonEl = this.ownerDocument.createElement('button');
makeDefaultButtonEl.className =
'custom-appearance list-inline-button';
makeDefaultButtonEl.textContent =
@@ -190,6 +191,12 @@ cr.define('options.search_engines', function() {
loadTimeData.getString('searchEngineTableURLPlaceholder');
}
+ this.setFocusableColumnIndex(this.nameField_, 0);
+ this.setFocusableColumnIndex(this.keywordField_, 1);
+ this.setFocusableColumnIndex(this.urlField_, 2);
+ this.setFocusableColumnIndex(makeDefaultButtonEl, 3);
+ this.setFocusableColumnIndex(this.closeButtonElement, 4);
+
var fields = [this.nameField_, this.keywordField_, this.urlField_];
for (var i = 0; i < fields.length; i++) {
fields[i].oninput = this.startFieldValidation_.bind(this);
diff --git a/chromium/chrome/browser/resources/options/search_page.js b/chromium/chrome/browser/resources/options/search_page.js
index 27cd9a53e70..971546dd56d 100644
--- a/chromium/chrome/browser/resources/options/search_page.js
+++ b/chromium/chrome/browser/resources/options/search_page.js
@@ -383,7 +383,7 @@ cr.define('options', function() {
},
/**
- * @return {!Array.<HTMLElement>} all the associated controls for |subpage|,
+ * @return {!Array<HTMLElement>} all the associated controls for |subpage|,
* including |subpage.associatedControls| as well as any controls on parent
* pages that are indirectly necessary to get to the subpage.
* @private
diff --git a/chromium/chrome/browser/resources/options/settings_banner.css b/chromium/chrome/browser/resources/options/settings_banner.css
index 621ed204e3b..c4290ef1273 100644
--- a/chromium/chrome/browser/resources/options/settings_banner.css
+++ b/chromium/chrome/browser/resources/options/settings_banner.css
@@ -18,7 +18,7 @@
}
.settings-banner > .close-button {
- background-image: url('chrome://theme/IDR_CLOSE_DIALOG');
+ background-image: url(chrome://theme/IDR_CLOSE_DIALOG);
background-position: center;
background-repeat: no-repeat;
height: 14px;
@@ -35,11 +35,11 @@ html[dir='rtl'] .settings-banner > .close-button {
}
.settings-banner > .close-button:hover {
- background-image: url('chrome://theme/IDR_CLOSE_DIALOG_H');
+ background-image: url(chrome://theme/IDR_CLOSE_DIALOG_H);
}
.settings-banner > .close-button:active {
- background-image: url('chrome://theme/IDR_CLOSE_DIALOG_P');
+ background-image: url(chrome://theme/IDR_CLOSE_DIALOG_P);
}
.settings-banner .content-area {
diff --git a/chromium/chrome/browser/resources/options/startup_overlay.js b/chromium/chrome/browser/resources/options/startup_overlay.js
index cad9c20b430..ec3d5b9f678 100644
--- a/chromium/chrome/browser/resources/options/startup_overlay.js
+++ b/chromium/chrome/browser/resources/options/startup_overlay.js
@@ -86,7 +86,6 @@ cr.define('options', function() {
setControlsDisabled: function(disable) {
var startupPagesList = $('startupPagesList');
startupPagesList.disabled = disable;
- startupPagesList.setAttribute('tabindex', disable ? -1 : 0);
// Explicitly set disabled state for input text elements.
var inputs = startupPagesList.querySelectorAll("input[type='text']");
for (var i = 0; i < inputs.length; i++)
diff --git a/chromium/chrome/browser/resources/options/subpages_tab_controls.css b/chromium/chrome/browser/resources/options/subpages_tab_controls.css
index c8b6c970b0c..45f5f5c1e1b 100644
--- a/chromium/chrome/browser/resources/options/subpages_tab_controls.css
+++ b/chromium/chrome/browser/resources/options/subpages_tab_controls.css
@@ -8,12 +8,12 @@
}
.subpages-nav-tabs .active-tab {
- -webkit-box-shadow: 8px -8px 12px -6px rgb(240, 240, 240);
background: white;
border: 1px solid #ddd;
border-bottom: 2px solid white;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
+ box-shadow: 8px -8px 12px -6px rgb(240, 240, 240);
position: relative;
}
diff --git a/chromium/chrome/browser/resources/options/supervised_user_create_confirm.css b/chromium/chrome/browser/resources/options/supervised_user_create_confirm.css
index 20ede68855d..828448a8622 100644
--- a/chromium/chrome/browser/resources/options/supervised_user_create_confirm.css
+++ b/chromium/chrome/browser/resources/options/supervised_user_create_confirm.css
@@ -21,11 +21,11 @@
} /* @media only screen and (max-height:400px) */
#supervised-user-created-image {
- -webkit-border-radius: 3px 3px 0 0;
background-image: -webkit-image-set(
- url('../../../../ui/resources/default_100_percent/supervised_illustration_done.png') 1x,
- url('../../../../ui/resources/default_200_percent/supervised_illustration_done.png') 2x);
+ url(../../../../ui/resources/default_100_percent/supervised_illustration_done.png) 1x,
+ url(../../../../ui/resources/default_200_percent/supervised_illustration_done.png) 2x);
background-position: center;
+ border-radius: 3px 3px 0 0;
flex: 5;
height: 344px;
}
diff --git a/chromium/chrome/browser/resources/options/supervised_user_import.css b/chromium/chrome/browser/resources/options/supervised_user_import.css
index 47e2acbf076..564c023ef3f 100644
--- a/chromium/chrome/browser/resources/options/supervised_user_import.css
+++ b/chromium/chrome/browser/resources/options/supervised_user_import.css
@@ -19,13 +19,25 @@
margin-bottom: 10px;
}
+#supervised-user-list .list-item {
+ align-items: center;
+ display: flex;
+}
+
+#supervised-user-list .profile-img {
+ flex-shrink: 0;
+}
+
#supervised-user-list .profile-name {
- flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
+#supervised-user-list .already-on-this-device {
+ flex-shrink: 0;
+}
+
#supervised-user-list > * {
height: 40px;
}
diff --git a/chromium/chrome/browser/resources/options/supervised_user_import.js b/chromium/chrome/browser/resources/options/supervised_user_import.js
index 43226e2da07..2eb2156b735 100644
--- a/chromium/chrome/browser/resources/options/supervised_user_import.js
+++ b/chromium/chrome/browser/resources/options/supervised_user_import.js
@@ -45,11 +45,8 @@ cr.define('options', function() {
supervisedUserList.addEventListener('change', function(event) {
var supervisedUser = supervisedUserList.selectedItem;
- if (!supervisedUser)
- return;
-
$('supervised-user-import-ok').disabled =
- supervisedUserList.selectedItem.onCurrentDevice;
+ !supervisedUser || supervisedUser.onCurrentDevice;
});
var self = this;
@@ -175,7 +172,7 @@ cr.define('options', function() {
/**
* Sets the data model of the supervised user list to |supervisedUsers|.
- * @param {Array.<{id: string, name: string, iconURL: string,
+ * @param {Array<{id: string, name: string, iconURL: string,
* onCurrentDevice: boolean, needAvatar: boolean}>} supervisedUsers
* Array of supervised user objects.
* @private
diff --git a/chromium/chrome/browser/resources/options/supervised_user_learn_more.css b/chromium/chrome/browser/resources/options/supervised_user_learn_more.css
index dfd55339d4a..e54d68b89a3 100644
--- a/chromium/chrome/browser/resources/options/supervised_user_learn_more.css
+++ b/chromium/chrome/browser/resources/options/supervised_user_learn_more.css
@@ -28,11 +28,11 @@
} /* @media only screen and (max-height:500px) */
#supervised-user-learn-more-image {
- -webkit-border-radius: 3px 3px 0 0;
background-image: -webkit-image-set(
url(../../../../ui/resources/default_100_percent/supervised_illustration_start.png) 1x,
url(../../../../ui/resources/default_200_percent/supervised_illustration_start.png) 2x);
background-position: center;
+ border-radius: 3px 3px 0 0;
height: 344px;
}
diff --git a/chromium/chrome/browser/resources/options/supervised_user_list.js b/chromium/chrome/browser/resources/options/supervised_user_list.js
index dd86ead6236..2777a3659a9 100644
--- a/chromium/chrome/browser/resources/options/supervised_user_list.js
+++ b/chromium/chrome/browser/resources/options/supervised_user_list.js
@@ -23,6 +23,7 @@ cr.define('options.supervisedUserOptions', function() {
*/
function SupervisedUserListItem(entry) {
var el = cr.doc.createElement('div');
+ el.className = 'list-item';
el.supervisedUser_ = entry;
el.__proto__ = SupervisedUserListItem.prototype;
el.decorate();
diff --git a/chromium/chrome/browser/resources/options/supervised_user_list_data.js b/chromium/chrome/browser/resources/options/supervised_user_list_data.js
index 812620e4e27..67ac67e6ba0 100644
--- a/chromium/chrome/browser/resources/options/supervised_user_list_data.js
+++ b/chromium/chrome/browser/resources/options/supervised_user_list_data.js
@@ -20,7 +20,7 @@ cr.define('options', function() {
/**
* Receives a list of supervised users and resolves the promise.
- * @param {Array.<Object>} supervisedUsers Array of supervised user objects.
+ * @param {Array<Object>} supervisedUsers Array of supervised user objects.
* Each object is of the form:
* supervisedUser = {
* id: "Supervised User ID",
@@ -97,7 +97,7 @@ cr.define('options', function() {
/**
* Initializes |promise| with the new data and also passes the new data to
* observers.
- * @param {Array.<Object>} supervisedUsers Array of supervised user objects.
+ * @param {Array<Object>} supervisedUsers Array of supervised user objects.
* For the format of the objects, see receiveExistingSupervisedUsers_().
* @private
*/
diff --git a/chromium/chrome/browser/resources/options/sync_setup_overlay.css b/chromium/chrome/browser/resources/options/sync_setup_overlay.css
index 173066e6faa..c04dc00cc04 100644
--- a/chromium/chrome/browser/resources/options/sync_setup_overlay.css
+++ b/chromium/chrome/browser/resources/options/sync_setup_overlay.css
@@ -92,12 +92,6 @@
width: 100%;
}
-#sync-setup-overlay * input[type='button'],
-#sync-setup-overlay * input[type='submit'] {
- min-height: 26px;
- min-width: 87px;
-}
-
#sync-setup-delete-profile {
margin: 10px 0 0 0;
}
@@ -159,25 +153,6 @@
margin-bottom: 5px;
}
-#captcha-div {
- background: white;
- border: 1px solid #e5e5e5;
- overflow: hidden;
- padding: 1em 1em;
-}
-
-#captcha-wrapper {
- background: no-repeat;
- background-position: center;
- background-size: 200px 70px;
- margin: 0 0 1em;
-}
-
-#captcha-image {
- height: 70px;
- width: 200px;
-}
-
#asp-warning-div {
text-align: left;
}
@@ -195,10 +170,6 @@
margin: 1em 0;
}
-#sign-in {
- margin: 0;
-}
-
#sync-setup-configure {
background: white;
}
@@ -231,243 +202,3 @@ html[dir='rtl'] #learn-more-link {
#use-default-link {
-webkit-transition: opacity 250ms;
}
-
-
-/* Sign in box. */
-
-.sign-in {
- margin: 20px auto;
- width: 335px;
-}
-
-.signin-box {
- background: #f5f5f5;
- border: 1px solid #e5e5e5;
- padding: 20px 25px 15px;
-}
-
-#signin-header {
- position: relative;
-}
-
-#signin-header h2 {
- color: #222;
- font-size: 16px;
- font-weight: normal;
- height: 16px;
- line-height: 16px;
- margin-top: 0;
-}
-
-#signin-header-logo {
- background: transparent
- url('chrome://resources/images/google-transparent.png') no-repeat;
- display: inline-block;
- height: 19px;
- position: absolute;
- right: 0;
- top: 1px;
- width: 52px;
-}
-
-html[dir='rtl'] #signin-header-logo {
- left: 0;
- right: auto;
-}
-
-#email-row,
-#email-readonly-row,
-#password-row {
- margin: 0 0 1.5em;
-}
-
-/* Sign in buttons. */
-
-.signin-box input[type=submit] {
- -webkit-margin-end: 0.4em;
- -webkit-margin-start: 0;
- -webkit-transition: all 218ms;
- -webkit-user-select: none;
- background-image: -webkit-linear-gradient(top, rgb(77, 144, 254),
- rgb(71, 135, 237));
- border: 1px solid rgb(48, 121, 237);
- border-radius: 2px;
- color: white;
- display: inline-block;
- font-size: 13px;
- font-weight: bold;
- height: 32px;
- line-height: 27px;
- margin-bottom: 1.2em;
- margin-top: 0;
- min-width: 54px !important;
- padding: 0 8px;
-}
-
-.signin-box input[type=submit]:hover {
- -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
- -webkit-transition: all 0;
- background-image: -webkit-linear-gradient(top, rgb(77, 144, 254),
- rgb(53, 122, 232));
- border-color: rgb(47, 91, 183);
- color: white;
-}
-
-.signin-box input[type=submit]:focus {
- -webkit-box-shadow: inset 0 0 0 1px white;
- border-color: rgb(77, 144, 254);
- outline: none;
- z-index: 4 !important;
-}
-
-.signin-box input[type=submit]:active,
-.signin-box input[type=submit]:focus:active {
- -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.3);
-}
-
-.signin-box input[type=submit]:focus:hover {
- -webkit-box-shadow: inset 0 0 0 1px white, 0 1px 1px rgba(0, 0, 0, 0.1);
-}
-
-.signin-box input[type=submit][disabled],
-.signin-box input[type=submit][disabled]:hover,
-.signin-box input[type=submit][disabled]:active {
- -webkit-box-shadow: none;
- background-color: rgb(77, 144, 254);
- border: 1px solid rgb(48, 121, 237);
- color: white;
- opacity: 0.5;
-}
-
-/* Sign in text fields. */
-
-.signin-box input[type=text],
-.signin-box input[type=password] {
- -webkit-border-radius: 1px;
- -webkit-box-sizing: border-box;
- background-color: white;
- border: 1px solid #d9d9d9;
- border-top: 1px solid #c0c0c0;
- color: #333;
- display: inline-block;
- font-size: 15px;
- height: 32px;
- line-height: 27px;
- margin-top: 0.5em;
- padding-left: 8px;
- vertical-align: top;
- width: 100%;
-}
-
-html[dir='rtl'] .signin-box input[type=text],
-html[dir='rtl'] .signin-box input[type=password] {
- padding-left: 0;
- padding-right: 8px;
-}
-
-.signin-box input[type=text]:hover,
-.signin-box input[type=password]:hover {
- -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
- border-color: #b9b9b9;
- border-top: 1px solid #a0a0a0;
-}
-
-.signin-box input[type=text]:focus,
-.signin-box input[type=password]:focus {
- -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.3);
- border-color: rgb(77, 144, 254);
- outline: none;
-}
-
-.signin-box input[type=text][disabled],
-.signin-box input[type=password][disabled] {
- -webkit-box-shadow: none;
- background: #f5f5f5;
- border: 1px solid #e5e5e5;
-}
-
-.signin-box input[type=text][disabled]:hover,
-.signin-box input[type=password][disabled]:hover {
- -webkit-box-shadow: none;
-}
-
-
-/* Sign in links. */
-
-.signin-box .account-link {
- color: rgb(17, 85, 204) !important;
- text-decoration: none;
-}
-
-.signin-box .account-link:visited {
- color: rgb(102, 17, 204) !important;
- text-decoration: none;
-}
-
-.signin-box .account-link:hover {
- text-decoration: underline;
-}
-
-.signin-box .account-link:active {
- color: rgb(209, 72, 54) !important;
- text-decoration: underline;
-}
-
-/* Sign in text. */
-
-.signin-box strong {
- color: #222;
- display: block;
-}
-
-.signin-box label {
- display: block;
- font-weight: bold;
-}
-
-/* The help links inside the signin box labels should use a normal font. */
-.signin-box label a {
- font-weight: normal;
-}
-
-/* Sign in miscellaneous. */
-
-.signin-box .throbber {
- float: right;
-}
-
-html[dir='rtl'] .signin-box .throbber {
- float: left;
-}
-
-#create-account-div {
- display: inline-block;
-}
-
-.signin-box .errormsg {
- color: rgb(221, 75, 57) !important;
- font-size: 13px !important;
- line-height: 17px;
- margin: 0.5em 0 1.5em;
-}
-
-.signin-box .help-link {
- -webkit-border-radius: 1em;
- background: rgb(221, 75, 57);
- color: white !important;
- display: inline-block;
- font-weight: bold;
- padding: 0 5px;
- position: relative;
- text-decoration: none;
- top: 0;
-}
-
-.signin-box .help-link:visited {
- color: white !important;
-}
-
-.signin-box .help-link:hover {
- color: white !important;
- opacity: .7;
-}
diff --git a/chromium/chrome/browser/resources/options/sync_setup_overlay.html b/chromium/chrome/browser/resources/options/sync_setup_overlay.html
index 11030d79c89..eee31994829 100644
--- a/chromium/chrome/browser/resources/options/sync_setup_overlay.html
+++ b/chromium/chrome/browser/resources/options/sync_setup_overlay.html
@@ -17,10 +17,9 @@
</div>
<div id="confirm-everything-throbber" class="throbber"></div>
<div class="button-strip">
- <input id="confirm-everything-cancel" type="button"
- i18n-values="value:cancel">
- <input id="confirm-everything-ok" type="button"
- class="default-button" i18n-values="value:syncEverything">
+ <button id="confirm-everything-cancel" i18n-content="cancel">
+ <button id="confirm-everything-ok" class="default-button"
+ i18n-content="syncEverything">
</div>
</div>
</div>
@@ -34,10 +33,6 @@
metric="Options_SyncSelectDataTypes">
<option i18n-content="syncAllDataTypes" selected></option>
<option i18n-content="chooseDataTypes"></option>
- <!-- The syncNothing element is to be removed for M29.
- TODO(rsimha): Revisit this for M30.
- See http://crbug.com/252049.
- -->
</select>
<div id="choose-data-types-body">
<div id="apps-item" class="sync-type-checkbox checkbox">
@@ -208,8 +203,8 @@
<div id="sync-setup-spinner" hidden>
<h1 i18n-content="syncSetupSpinnerTitle"></h1>
<div class="action-area button-strip">
- <input id="sync-spinner-cancel" type="button" class="default-button"
- i18n-values="value:cancel">
+ <button id="sync-spinner-cancel" class="default-button"
+ i18n-content="cancel">
</div>
</div>
<div id="sync-setup-timeout" hidden>
@@ -235,10 +230,9 @@
</if>
</div>
<div class="action-area button-strip">
- <input id="stop-syncing-cancel" type="button"
- i18n-values="value:cancel">
- <input id="stop-syncing-ok" type="button" class="default-button"
- i18n-values="value:stopSyncingConfirm">
+ <button id="stop-syncing-cancel" i18n-content="cancel">
+ <button id="stop-syncing-ok" class="default-button"
+ i18n-content="stopSyncingConfirm">
</div>
</div>
</div>
diff --git a/chromium/chrome/browser/resources/options/sync_setup_overlay.js b/chromium/chrome/browser/resources/options/sync_setup_overlay.js
index 79e15da966e..4b5944987f3 100644
--- a/chromium/chrome/browser/resources/options/sync_setup_overlay.js
+++ b/chromium/chrome/browser/resources/options/sync_setup_overlay.js
@@ -29,7 +29,6 @@ cr.exportPath('options');
* preferencesRegistered: boolean,
* preferencesSynced: boolean,
* showPassphrase: boolean,
- * showSyncEverythingPage: boolean,
* syncAllDataTypes: boolean,
* syncNothing: boolean,
* tabsEnforced: boolean,
@@ -157,12 +156,17 @@ cr.define('options', function() {
chrome.send('SyncSetupStopSyncing', [deleteProfile]);
self.closeOverlay_();
};
+ $('use-default-link').onclick = function() {
+ self.showSyncEverythingPage_();
+ };
},
+ /** @private */
showOverlay_: function() {
PageManager.showPageByName('syncSetup');
},
+ /** @private */
closeOverlay_: function() {
this.syncConfigureArgs_ = null;
this.dataTypeBoxesChecked_ = {};
@@ -183,8 +187,10 @@ cr.define('options', function() {
chrome.send('SyncSetupDidClosePage');
},
+ /** @private */
onEncryptionRadioChanged_: function() {
var visible = $('full-encryption-option').checked;
+ // TODO(dbeam): should sync-custom-passphrase-container be hidden instead?
$('sync-custom-passphrase').hidden = !visible;
chrome.send('coreOptionsUserMetricsAction',
['Options_SyncSetEncryption']);
@@ -255,13 +261,13 @@ cr.define('options', function() {
}
},
+ /** @private */
checkPassphraseMatch_: function() {
var emptyError = $('empty-error');
var mismatchError = $('mismatch-error');
emptyError.hidden = true;
mismatchError.hidden = true;
- var f = $('choose-data-types-form');
if (!$('full-encryption-option').checked ||
$('basic-encryption-option').disabled) {
return true;
@@ -282,6 +288,7 @@ cr.define('options', function() {
return true;
},
+ /** @private */
sendConfiguration_: function() {
var encryptAllData = $('full-encryption-option').checked;
@@ -316,8 +323,6 @@ cr.define('options', function() {
// configuration to the backend.
this.setInputElementsDisabledState_(true);
$('use-default-link').hidden = true;
- $('use-default-link').disabled = true;
- $('use-default-link').onclick = null;
// These values need to be kept in sync with where they are read in
// sync_setup_handler.cc:GetConfiguration().
@@ -390,26 +395,22 @@ cr.define('options', function() {
this.dataTypeBoxesChecked_['bookmarks-checkbox'] = args.bookmarksSynced;
this.dataTypeBoxesDisabled_['bookmarks-checkbox'] =
args.bookmarksEnforced;
- $('bookmarks-checkbox').onclick = this.handleDataTypeClick_;
$('preferences-checkbox').checked = args.preferencesSynced;
this.dataTypeBoxesChecked_['preferences-checkbox'] =
args.preferencesSynced;
this.dataTypeBoxesDisabled_['preferences-checkbox'] =
args.preferencesEnforced;
- $('preferences-checkbox').onclick = this.handleDataTypeClick_;
$('themes-checkbox').checked = args.themesSynced;
this.dataTypeBoxesChecked_['themes-checkbox'] = args.themesSynced;
this.dataTypeBoxesDisabled_['themes-checkbox'] = args.themesEnforced;
- $('themes-checkbox').onclick = this.handleDataTypeClick_;
if (args.passwordsRegistered) {
$('passwords-checkbox').checked = args.passwordsSynced;
this.dataTypeBoxesChecked_['passwords-checkbox'] = args.passwordsSynced;
this.dataTypeBoxesDisabled_['passwords-checkbox'] =
args.passwordsEnforced;
- $('passwords-checkbox').onclick = this.handleDataTypeClick_;
$('passwords-item').hidden = false;
} else {
$('passwords-item').hidden = true;
@@ -419,7 +420,6 @@ cr.define('options', function() {
this.dataTypeBoxesChecked_['autofill-checkbox'] = args.autofillSynced;
this.dataTypeBoxesDisabled_['autofill-checkbox'] =
args.autofillEnforced;
- $('autofill-checkbox').onclick = this.handleDataTypeClick_;
$('autofill-item').hidden = false;
} else {
$('autofill-item').hidden = true;
@@ -430,7 +430,6 @@ cr.define('options', function() {
args.extensionsSynced;
this.dataTypeBoxesDisabled_['extensions-checkbox'] =
args.extensionsEnforced;
- $('extensions-checkbox').onclick = this.handleDataTypeClick_;
$('extensions-item').hidden = false;
} else {
$('extensions-item').hidden = true;
@@ -441,7 +440,6 @@ cr.define('options', function() {
args.typedUrlsSynced;
this.dataTypeBoxesDisabled_['typed-urls-checkbox'] =
args.typedUrlsEnforced;
- $('typed-urls-checkbox').onclick = this.handleDataTypeClick_;
$('omnibox-item').hidden = false;
} else {
$('omnibox-item').hidden = true;
@@ -450,7 +448,6 @@ cr.define('options', function() {
$('apps-checkbox').checked = args.appsSynced;
this.dataTypeBoxesChecked_['apps-checkbox'] = args.appsSynced;
this.dataTypeBoxesDisabled_['apps-checkbox'] = args.appsEnforced;
- $('apps-checkbox').onclick = this.handleDataTypeClick_;
$('apps-item').hidden = false;
} else {
$('apps-item').hidden = true;
@@ -459,7 +456,6 @@ cr.define('options', function() {
$('tabs-checkbox').checked = args.tabsSynced;
this.dataTypeBoxesChecked_['tabs-checkbox'] = args.tabsSynced;
this.dataTypeBoxesDisabled_['tabs-checkbox'] = args.tabsEnforced;
- $('tabs-checkbox').onclick = this.handleDataTypeClick_;
$('tabs-item').hidden = false;
} else {
$('tabs-item').hidden = true;
@@ -470,12 +466,14 @@ cr.define('options', function() {
args.wifiCredentialsSynced;
this.dataTypeBoxesDisabled_['wifi-credentials-checkbox'] =
args.wifiCredentialsEnforced;
- $('wifi-credentials-checkbox').onclick = this.handleDataTypeClick_;
$('wifi-credentials-item').hidden = false;
} else {
$('wifi-credentials-item').hidden = true;
}
+ $('choose-data-types-body').onchange =
+ this.handleDataTypeChange_.bind(this);
+
this.setDataTypeCheckboxes_(datatypeSelect.selectedIndex);
},
@@ -483,16 +481,20 @@ cr.define('options', function() {
* Updates the cached values of the sync data type checkboxes stored in
* |this.dataTypeBoxesChecked_|. Used as an onclick handler for each data
* type checkbox.
+ * @param {Event} e The change event.
* @private
*/
- handleDataTypeClick_: function() {
- this.dataTypeBoxesChecked_[this.id] = this.checked;
+ handleDataTypeChange_: function(e) {
+ var input = assertInstanceof(e.target, HTMLInputElement);
+ assert(input.type == 'checkbox');
+ this.dataTypeBoxesChecked_[input.id] = input.checked;
chrome.send('coreOptionsUserMetricsAction',
['Options_SyncToggleDataType']);
},
/**
* @param {SyncConfig} args
+ * @private
*/
setEncryptionRadios_: function(args) {
if (!args.encryptAllData && !args.usePassphrase) {
@@ -506,6 +508,7 @@ cr.define('options', function() {
/**
* @param {SyncConfig} args
+ * @private
*/
setCheckboxesAndErrors_: function(args) {
this.setChooseDataTypesCheckboxes_(args);
@@ -514,6 +517,7 @@ cr.define('options', function() {
/**
* @param {SyncConfig} args
+ * @private
*/
showConfigure_: function(args) {
var datatypeSelect = $('sync-select-datatypes');
@@ -564,28 +568,26 @@ cr.define('options', function() {
// dialog or the advanced sync settings dialog, and assign focus to the
// OK button, or to the passphrase field if a passphrase is required.
this.usePassphrase_ = args.usePassphrase;
- if (args.showSyncEverythingPage == false || this.usePassphrase_ ||
- args.syncAllDataTypes == false || args.showPassphrase) {
- var index = args.syncAllDataTypes ?
- options.DataTypeSelection.SYNC_EVERYTHING :
- options.DataTypeSelection.CHOOSE_WHAT_TO_SYNC;
- this.showCustomizePage_(args, index);
- } else {
- this.showSyncEverythingPage_();
- }
+ var index = args.syncAllDataTypes ?
+ options.DataTypeSelection.SYNC_EVERYTHING :
+ options.DataTypeSelection.CHOOSE_WHAT_TO_SYNC;
+ this.showCustomizePage_(args, index);
}
},
+ /** @private */
showSpinner_: function() {
this.resetPage_('sync-setup-spinner');
$('sync-setup-spinner').hidden = false;
},
+ /** @private */
showTimeoutPage_: function() {
this.resetPage_('sync-setup-timeout');
$('sync-setup-timeout').hidden = false;
},
+ /** @private */
showSyncEverythingPage_: function() {
chrome.send('coreOptionsUserMetricsAction',
['Options_SyncSetDefault']);
@@ -599,6 +601,7 @@ cr.define('options', function() {
// The default state is to sync everything.
this.setDataTypeCheckboxes_(options.DataTypeSelection.SYNC_EVERYTHING);
+ // TODO(dbeam): should hide sync-custom-passphrase-container instead?
if (!this.usePassphrase_)
$('sync-custom-passphrase').hidden = true;
@@ -628,8 +631,6 @@ cr.define('options', function() {
// Hide the "use default settings" link.
$('use-default-link').hidden = true;
- $('use-default-link').disabled = true;
- $('use-default-link').onclick = null;
},
/**
@@ -644,8 +645,6 @@ cr.define('options', function() {
// Once we require a passphrase, we prevent the user from returning to
// the Sync Everything pane.
$('use-default-link').hidden = true;
- $('use-default-link').disabled = true;
- $('use-default-link').onclick = null;
$('sync-custom-passphrase-container').hidden = true;
$('sync-existing-passphrase-container').hidden = false;
@@ -695,15 +694,12 @@ cr.define('options', function() {
if (args.showPassphrase) {
this.showPassphraseContainer_(args);
+ // TODO(dbeam): add an #updatePassphrase and only focus with that hash?
+ $('passphrase').focus();
} else {
// We only show the 'Use Default' link if we're not prompting for an
// existing passphrase.
$('use-default-link').hidden = false;
- $('use-default-link').disabled = false;
- $('use-default-link').onclick = function() {
- SyncSetupOverlay.showSyncEverythingPage();
- return false;
- };
}
},
@@ -869,10 +865,6 @@ cr.define('options', function() {
SyncSetupOverlay.getInstance().showCustomizePage_(args, index);
};
- SyncSetupOverlay.showSyncEverythingPage = function() {
- SyncSetupOverlay.getInstance().showSyncEverythingPage_();
- };
-
SyncSetupOverlay.showStopSyncingUI = function() {
SyncSetupOverlay.getInstance().showStopSyncingUI_();
};
diff --git a/chromium/chrome/browser/resources/options/website_settings.css b/chromium/chrome/browser/resources/options/website_settings.css
index f2ca626c7a5..52c2b7d3086 100644
--- a/chromium/chrome/browser/resources/options/website_settings.css
+++ b/chromium/chrome/browser/resources/options/website_settings.css
@@ -8,7 +8,6 @@
}
#website-settings-edit-page {
- max-height: 445px;
min-width: 540px;
}
diff --git a/chromium/chrome/browser/resources/options_test_resources.grd b/chromium/chrome/browser/resources/options_test_resources.grd
new file mode 100644
index 00000000000..9e79550089b
--- /dev/null
+++ b/chromium/chrome/browser/resources/options_test_resources.grd
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<grit latest_public_release="0" current_release="1">
+ <outputs>
+ <output filename="grit/options_test_resources.h" type="rc_header">
+ <emit emit_type='prepend'></emit>
+ </output>
+ <output filename="options_test_resources.pak" type="data_package" />
+ </outputs>
+ <release seq="1">
+ <structures>
+ <structure name="IDR_OPTIONS_DELETABLE_ITEM_LIST"
+ file="options/deletable_item_list.js"
+ type="chrome_html" />
+ <structure name="IDR_OPTIONS_INLINE_EDITABLE_LIST"
+ file="options/inline_editable_list.js"
+ type="chrome_html" />
+ </structures>
+ </release>
+</grit>
diff --git a/chromium/chrome/browser/resources/password_manager_internals/password_manager_internals.html b/chromium/chrome/browser/resources/password_manager_internals/password_manager_internals.html
index df22c9808eb..c88b3681a95 100644
--- a/chromium/chrome/browser/resources/password_manager_internals/password_manager_internals.html
+++ b/chromium/chrome/browser/resources/password_manager_internals/password_manager_internals.html
@@ -1,5 +1,5 @@
-<!DOCTYPE html>
-<html i18n-values="dir:textdirection;">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
<head>
<!-- Copyright 2014 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
diff --git a/chromium/chrome/browser/resources/pdf/background.js b/chromium/chrome/browser/resources/pdf/background.js
deleted file mode 100644
index 1bf748a8ac1..00000000000
--- a/chromium/chrome/browser/resources/pdf/background.js
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2013 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.
-
-(function() {
- 'use strict';
-
- /**
- * A map of view ID (which identifies a particular PDF viewer instance) to
- * stream object.
- * @type {Object.<string, Object>}
- */
- var streams = {};
-
- /**
- * A map of view ID (which identifies a particular PDF viewer instance) to
- * initialization function for that view.
- * @type {Object.<string, Function>}
- */
- var pluginInitFunctions = {};
-
- /**
- * If we have received a stream object and an initialization function for a
- * particular PDF viewer instance we know that the extension has loaded in
- * and we can pass it the stream. We can then delete the corresponding map
- * entries.
- * @param {string} viewId The ID of the view to initialize with a stream.
- */
- function flush(viewId) {
- if (viewId in streams && viewId in pluginInitFunctions) {
- pluginInitFunctions[viewId](streams[viewId]);
- delete streams[viewId];
- delete pluginInitFunctions[viewId];
- }
- }
-
- /**
- * This is called when loading a document with the PDF mime type and passes a
- * stream that points to the PDF file. This may be run before or after we
- * receive a message from the PDF viewer with its initialization function.
- */
- chrome.streamsPrivate.onExecuteMimeTypeHandler.addListener(
- function(streamDetails) {
- // Store the stream until we are contacted by the PDF viewer that owns the
- // stream.
- streams[streamDetails.viewId] = streamDetails;
- flush(streamDetails.viewId);
- }
- );
-
- /**
- * This is called when we receive a message from the PDF viewer indicating
- * it has loaded and is ready to receive a stream of the data.
- */
- chrome.runtime.onMessage.addListener(
- function(request, sender, responseFunction) {
- // Store the initialization function until we receive the stream which
- // corresponds to the PDF viewer.
- pluginInitFunctions[request.viewId] = responseFunction;
- flush(request.viewId);
- }
- );
-}());
diff --git a/chromium/chrome/browser/resources/pdf/browser_api.js b/chromium/chrome/browser/resources/pdf/browser_api.js
new file mode 100644
index 00000000000..52ce4c6b76b
--- /dev/null
+++ b/chromium/chrome/browser/resources/pdf/browser_api.js
@@ -0,0 +1,159 @@
+// Copyright 2015 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';
+
+let defaultZoomPromise = (function() {
+ if (!chrome.tabs)
+ return Promise.resolve(1);
+
+ return new Promise(function(resolve, reject) {
+ chrome.tabs.getZoomSettings(function(zoomSettings) {
+ resolve(zoomSettings.defaultZoomFactor);
+ }.bind(this));
+ }.bind(this));
+})();
+
+/**
+ * A class providing an interface to the browser.
+ */
+class BrowserApi {
+ /**
+ * @constructor
+ * @param {!Object} streamInfo The stream object which points to the data
+ * contained in the PDF.
+ * @param {number} defaultZoom The default browser zoom.
+ * @param {boolean} manageZoom Whether to manage zoom.
+ */
+ constructor(streamInfo, defaultZoom, manageZoom) {
+ this.streamInfo_ = streamInfo;
+ this.defaultZoom_ = defaultZoom;
+ this.manageZoom_ = manageZoom;
+ }
+
+ /**
+ * Returns a promise to a BrowserApi.
+ * @param {!Promise.<Object>} streamInfoPromise A promise that will resolve
+ * the stream object pointing to the data contained in the PDF.
+ * @param {boolean} manageZoom Whether to manage zoom.
+ */
+ static create(streamInfoPromise, manageZoom) {
+ return Promise.all([streamInfoPromise, defaultZoomPromise]).then(
+ function(results) {
+ return new BrowserApi(results[0], results[1], manageZoom);
+ });
+ }
+
+ /**
+ * Returns the stream info pointing to the data contained in the PDF.
+ * @return {Object} The stream info object.
+ */
+ getStreamInfo() {
+ return this.streamInfo_;
+ }
+
+ /**
+ * Aborts the stream.
+ */
+ abortStream() {
+ if (chrome.mimeHandlerPrivate)
+ chrome.mimeHandlerPrivate.abortStream();
+ }
+
+ /**
+ * Sets the browser zoom.
+ * @param {number} zoom The zoom factor to send to the browser.
+ * @return {Promise} A promise that will be resolved when the browser zoom
+ * has been updated.
+ */
+ setZoom(zoom) {
+ if (!this.manageZoom_)
+ return Promise.resolve();
+ return new Promise(function(resolve, reject) {
+ chrome.tabs.setZoom(this.streamInfo_.tabId, zoom, resolve);
+ }.bind(this));
+ }
+
+ /**
+ * Returns the default browser zoom factor.
+ * @return {number} The default browser zoom factor.
+ */
+ getDefaultZoom() {
+ return this.defaultZoom_;
+ }
+
+ /**
+ * Adds an event listener to be notified when the browser zoom changes.
+ * @param {function} listener The listener to be called with the new zoom
+ * factor.
+ */
+ addZoomEventListener(listener) {
+ if (!this.manageZoom_)
+ return;
+
+ chrome.tabs.onZoomChange.addListener(function(zoomChangeInfo) {
+ if (zoomChangeInfo.tabId != this.streamInfo_.tabId)
+ return;
+ listener(zoomChangeInfo.newZoomFactor);
+ }.bind(this));
+ }
+};
+
+/**
+ * Creates a BrowserApi for an extension running as a mime handler.
+ * @return {Promise.<BrowserApi>} A promise to a BrowserApi instance constructed
+ * using the mimeHandlerPrivate API.
+ */
+function createBrowserApiForMimeHandlerView() {
+ return new Promise(function(resolve, reject) {
+ chrome.mimeHandlerPrivate.getStreamInfo(resolve);
+ }).then(function(streamInfo) {
+ if (streamInfo.embedded || streamInfo.tabId == -1)
+ return BrowserApi.create(streamInfo, false);
+
+ return BrowserApi.create(new Promise(function(resolve, reject) {
+ chrome.tabs.setZoomSettings(
+ streamInfo.tabId, {mode: 'manual', scope: 'per-tab'}, resolve);
+ }).then(function() {
+ return streamInfo;
+ }), true);
+ });
+}
+
+/**
+ * Creates a BrowserApi instance for an extension not running as a mime handler.
+ * @return {Promise.<BrowserApi>} A promise to a BrowserApi instance constructed
+ * from the URL.
+ */
+function createBrowserApiForStandaloneExtension() {
+ let url = window.location.search.substring(1);
+ let streamInfo = {
+ streamUrl: url,
+ originalUrl: url,
+ responseHeaders: {},
+ embedded: window.parent != window,
+ tabId: -1,
+ };
+ if (!chrome.tabs)
+ return BrowserApi.create(streamInfo, false);
+
+ return BrowserApi.create(new Promise(function(resolve, reject) {
+ chrome.tabs.getCurrent(function(tab) {
+ streamInfo.tabId = tab.id;
+ resolve(streamInfo);
+ });
+ }), false);
+}
+
+/**
+ * Returns a promise that will resolve to a BrowserApi instance.
+ * @return {Promise.<BrowserApi>} A promise to a BrowserApi instance for the
+ * current environment.
+ */
+function createBrowserApi() {
+ if (window.location.search)
+ return createBrowserApiForStandaloneExtension();
+
+ return createBrowserApiForMimeHandlerView();
+}
diff --git a/chromium/chrome/browser/resources/pdf/content_script.js b/chromium/chrome/browser/resources/pdf/content_script.js
new file mode 100644
index 00000000000..dce77e6fdd7
--- /dev/null
+++ b/chromium/chrome/browser/resources/pdf/content_script.js
@@ -0,0 +1,7 @@
+// Copyright 2015 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.
+
+// This is to work-around an issue where this extension is not granted
+// permission to access chrome://resources when iframed for print preview.
+// See https://crbug.com/444752.
diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-bookmark/viewer-bookmark.css b/chromium/chrome/browser/resources/pdf/elements/viewer-bookmark/viewer-bookmark.css
new file mode 100644
index 00000000000..f20997dd5ee
--- /dev/null
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-bookmark/viewer-bookmark.css
@@ -0,0 +1,7 @@
+/* Copyright 2015 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. */
+
+.sub-bookmark {
+ margin-left: 1em;
+}
diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-bookmark/viewer-bookmark.html b/chromium/chrome/browser/resources/pdf/elements/viewer-bookmark/viewer-bookmark.html
new file mode 100644
index 00000000000..66e1a942430
--- /dev/null
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-bookmark/viewer-bookmark.html
@@ -0,0 +1,17 @@
+<link rel="import" href="chrome://resources/polymer/polymer/polymer.html">
+<link rel="import" href="chrome://resources/polymer/paper-item/paper-item.html">
+
+<polymer-element name="viewer-bookmark" attributes="bookmark">
+<template>
+ <link rel="stylesheet" href="viewer-bookmark.css">
+ <paper-item on-click="{{onClick}}">{{bookmark.title}}</paper-item>
+ <template if="{{bookmark.children.length !== 0}}">
+ <div class="sub-bookmark">
+ <template repeat="{{child in bookmark.children}}">
+ <viewer-bookmark bookmark="{{child}}"></viewer-bookmark>
+ </template>
+ </div>
+ </template>
+</template>
+<script src="viewer-bookmark.js"></script>
+</polymer-element>
diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-bookmark/viewer-bookmark.js b/chromium/chrome/browser/resources/pdf/elements/viewer-bookmark/viewer-bookmark.js
new file mode 100644
index 00000000000..43f9de649e5
--- /dev/null
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-bookmark/viewer-bookmark.js
@@ -0,0 +1,21 @@
+// Copyright 2015 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.
+
+Polymer('viewer-bookmark', {
+
+ /**
+ * @type {Object}
+ * A bookmark object, each containing a:
+ * - title
+ * - page (optional)
+ * - children (an array of bookmarks)
+ */
+ bookmark: null,
+
+ onClick: function() {
+ if (this.bookmark.hasOwnProperty('page'))
+ this.fire('change-page', {page: this.bookmark.page});
+ },
+
+});
diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-bookmarks-content/viewer-bookmarks-content.html b/chromium/chrome/browser/resources/pdf/elements/viewer-bookmarks-content/viewer-bookmarks-content.html
new file mode 100644
index 00000000000..d0f35e8c320
--- /dev/null
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-bookmarks-content/viewer-bookmarks-content.html
@@ -0,0 +1,10 @@
+<link rel="import" href="chrome://resources/polymer/polymer/polymer.html">
+<link rel="import" href="../viewer-bookmark/viewer-bookmark.html">
+
+<polymer-element name="viewer-bookmarks-content" noscript>
+<template>
+ <template repeat="{{bookmarkNode in bookmarks}}">
+ <viewer-bookmark bookmark="{{bookmarkNode}}"></viewer-bookmark>
+ </template>
+</template>
+</polymer-element>
diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/hiDPI/button_fit_page.png b/chromium/chrome/browser/resources/pdf/elements/viewer-button/img/hiDPI/button_fit_page.png
index 6977d2b8796..6977d2b8796 100644
--- a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/hiDPI/button_fit_page.png
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-button/img/hiDPI/button_fit_page.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/hiDPI/button_fit_width.png b/chromium/chrome/browser/resources/pdf/elements/viewer-button/img/hiDPI/button_fit_width.png
index d9dea06a74f..d9dea06a74f 100644
--- a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/hiDPI/button_fit_width.png
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-button/img/hiDPI/button_fit_width.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/hiDPI/button_play.png b/chromium/chrome/browser/resources/pdf/elements/viewer-button/img/hiDPI/button_play.png
index ac73ffc1724..ac73ffc1724 100644
--- a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/hiDPI/button_play.png
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-button/img/hiDPI/button_play.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/hiDPI/button_print.png b/chromium/chrome/browser/resources/pdf/elements/viewer-button/img/hiDPI/button_print.png
index 7fa75a3df72..7fa75a3df72 100644
--- a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/hiDPI/button_print.png
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-button/img/hiDPI/button_print.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/hiDPI/button_save.png b/chromium/chrome/browser/resources/pdf/elements/viewer-button/img/hiDPI/button_save.png
index ea125115ed7..ea125115ed7 100644
--- a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/hiDPI/button_save.png
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-button/img/hiDPI/button_save.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/hiDPI/button_zoom_in.png b/chromium/chrome/browser/resources/pdf/elements/viewer-button/img/hiDPI/button_zoom_in.png
index 2cedf813153..2cedf813153 100644
--- a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/hiDPI/button_zoom_in.png
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-button/img/hiDPI/button_zoom_in.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/hiDPI/button_zoom_out.png b/chromium/chrome/browser/resources/pdf/elements/viewer-button/img/hiDPI/button_zoom_out.png
index e27c272fd80..e27c272fd80 100644
--- a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/hiDPI/button_zoom_out.png
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-button/img/hiDPI/button_zoom_out.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/lowDPI/button_fit_page.png b/chromium/chrome/browser/resources/pdf/elements/viewer-button/img/lowDPI/button_fit_page.png
index e8e7a7039cf..e8e7a7039cf 100644
--- a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/lowDPI/button_fit_page.png
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-button/img/lowDPI/button_fit_page.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/lowDPI/button_fit_width.png b/chromium/chrome/browser/resources/pdf/elements/viewer-button/img/lowDPI/button_fit_width.png
index 700f4861ea5..700f4861ea5 100644
--- a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/lowDPI/button_fit_width.png
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-button/img/lowDPI/button_fit_width.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/lowDPI/button_play.png b/chromium/chrome/browser/resources/pdf/elements/viewer-button/img/lowDPI/button_play.png
index d5ceae76ac8..d5ceae76ac8 100644
--- a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/lowDPI/button_play.png
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-button/img/lowDPI/button_play.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/lowDPI/button_print.png b/chromium/chrome/browser/resources/pdf/elements/viewer-button/img/lowDPI/button_print.png
index e044a28837f..e044a28837f 100644
--- a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/lowDPI/button_print.png
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-button/img/lowDPI/button_print.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/lowDPI/button_save.png b/chromium/chrome/browser/resources/pdf/elements/viewer-button/img/lowDPI/button_save.png
index e732a442f35..e732a442f35 100644
--- a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/lowDPI/button_save.png
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-button/img/lowDPI/button_save.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/lowDPI/button_zoom_in.png b/chromium/chrome/browser/resources/pdf/elements/viewer-button/img/lowDPI/button_zoom_in.png
index e05ae81589a..e05ae81589a 100644
--- a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/lowDPI/button_zoom_in.png
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-button/img/lowDPI/button_zoom_in.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/lowDPI/button_zoom_out.png b/chromium/chrome/browser/resources/pdf/elements/viewer-button/img/lowDPI/button_zoom_out.png
index b76b54c95fc..b76b54c95fc 100644
--- a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/lowDPI/button_zoom_out.png
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-button/img/lowDPI/button_zoom_out.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/viewer-button.css b/chromium/chrome/browser/resources/pdf/elements/viewer-button/viewer-button.css
index d4aff70c49d..d4aff70c49d 100644
--- a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/viewer-button.css
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-button/viewer-button.css
diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/viewer-button.html b/chromium/chrome/browser/resources/pdf/elements/viewer-button/viewer-button.html
index e1eee711067..4c19bf3338c 100644
--- a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/viewer-button.html
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-button/viewer-button.html
@@ -1,3 +1,5 @@
+<link rel="import" href="chrome://resources/polymer/polymer/polymer.html">
+
<polymer-element name="viewer-button" attributes="img latchable">
<template>
<link rel="stylesheet" href="viewer-button.css">
diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/viewer-button.js b/chromium/chrome/browser/resources/pdf/elements/viewer-button/viewer-button.js
index 73bd53eff1e..73bd53eff1e 100644
--- a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/viewer-button.js
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-button/viewer-button.js
diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-error-screen/viewer-error-screen.css b/chromium/chrome/browser/resources/pdf/elements/viewer-error-screen/viewer-error-screen.css
index 9c7c7c82676..07ee91dd908 100644
--- a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-error-screen/viewer-error-screen.css
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-error-screen/viewer-error-screen.css
@@ -1,3 +1,7 @@
+/* Copyright 2015 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. */
+
:host {
background-color: #ccc;
color: #555;
diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-error-screen/viewer-error-screen.html b/chromium/chrome/browser/resources/pdf/elements/viewer-error-screen/viewer-error-screen.html
index 81199fe7d3a..0d8f267c490 100644
--- a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-error-screen/viewer-error-screen.html
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-error-screen/viewer-error-screen.html
@@ -1,3 +1,5 @@
+<link rel="import" href="chrome://resources/polymer/polymer/polymer.html">
+
<polymer-element name="viewer-error-screen" attributes="text">
<template>
<link rel="stylesheet" href="viewer-error-screen.css">
diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-error-screen/viewer-error-screen.js b/chromium/chrome/browser/resources/pdf/elements/viewer-error-screen/viewer-error-screen.js
index c63874dd2d7..c63874dd2d7 100644
--- a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-error-screen/viewer-error-screen.js
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-error-screen/viewer-error-screen.js
diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-page-indicator/viewer-page-indicator.css b/chromium/chrome/browser/resources/pdf/elements/viewer-page-indicator/viewer-page-indicator.css
index ec90ec99f53..ec90ec99f53 100644
--- a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-page-indicator/viewer-page-indicator.css
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-page-indicator/viewer-page-indicator.css
diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-page-indicator/viewer-page-indicator.html b/chromium/chrome/browser/resources/pdf/elements/viewer-page-indicator/viewer-page-indicator.html
index de59e1a9eca..86bd26ca2f5 100644
--- a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-page-indicator/viewer-page-indicator.html
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-page-indicator/viewer-page-indicator.html
@@ -1,3 +1,5 @@
+<link rel="import" href="chrome://resources/polymer/polymer/polymer.html">
+
<polymer-element name="viewer-page-indicator" attributes="index label">
<template>
<link rel="stylesheet" href="viewer-page-indicator.css">
diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-page-indicator/viewer-page-indicator.js b/chromium/chrome/browser/resources/pdf/elements/viewer-page-indicator/viewer-page-indicator.js
index 8612e170348..8612e170348 100644
--- a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-page-indicator/viewer-page-indicator.js
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-page-indicator/viewer-page-indicator.js
diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-page-selector/viewer-page-selector.css b/chromium/chrome/browser/resources/pdf/elements/viewer-page-selector/viewer-page-selector.css
new file mode 100644
index 00000000000..33a2e8bc898
--- /dev/null
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-page-selector/viewer-page-selector.css
@@ -0,0 +1,34 @@
+/* Copyright 2015 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. */
+
+#pageselector {
+ display: inline-block;
+ width: 0.6em;
+}
+
+paper-input-decorator /deep/ ::-webkit-input-placeholder {
+ color: rgb(241, 241, 241);
+}
+
+paper-input-decorator /deep/ .focused-underline {
+ background-color: rgb(241, 241, 241);
+}
+
+paper-input-decorator /deep/ .unfocused-underline {
+ background-color: transparent;
+}
+
+paper-input-decorator:hover /deep/ .unfocused-underline {
+ background-color: rgb(241, 241, 241);
+}
+
+#input {
+ margin: 0;
+ text-align: right;
+}
+
+#pagelength {
+ color: rgb(144, 202, 251);
+ font-size: 0.7em;
+}
diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-page-selector/viewer-page-selector.html b/chromium/chrome/browser/resources/pdf/elements/viewer-page-selector/viewer-page-selector.html
new file mode 100644
index 00000000000..1901375faad
--- /dev/null
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-page-selector/viewer-page-selector.html
@@ -0,0 +1,14 @@
+<link rel="import" href="chrome://resources/polymer/polymer/polymer.html">
+<link rel="import" href="chrome://resources/polymer/paper-input/paper-input-decorator.html">
+
+<polymer-element name="viewer-page-selector" attributes="index docLength">
+<template>
+ <link rel="stylesheet" href="viewer-page-selector.css">
+ <paper-input-decorator id="pageselector">
+ <input id="input" is="core-input"
+ value="{{pageNo}}" committedvalue="{{chosenPageNo}}">
+ </paper-input-decorator>
+ <span id="pagelength">/ {{docLength}}</span>
+</template>
+<script src="viewer-page-selector.js"></script>
+</polymer-element>
diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-page-selector/viewer-page-selector.js b/chromium/chrome/browser/resources/pdf/elements/viewer-page-selector/viewer-page-selector.js
new file mode 100644
index 00000000000..97713d53eb8
--- /dev/null
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-page-selector/viewer-page-selector.js
@@ -0,0 +1,56 @@
+// Copyright 2015 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.
+
+var DIGIT_LENGTH = 0.6;
+
+Polymer('viewer-page-selector', {
+ /**
+ * @type {string}
+ * The current entry in the input field (1-based).
+ */
+ pageNo: '1',
+
+ /**
+ * @type {number}
+ * The index of the current page being viewed (0-based).
+ */
+ index: 0,
+
+ /**
+ * @type {number}
+ * The number of pages the document contains.
+ */
+ docLength: 1,
+
+ /**
+ * @type {string}
+ * The submitted input from the user (1-based).
+ */
+ chosenPageNo: '1',
+
+ chosenPageNoChanged: function() {
+ var page = parseInt(this.chosenPageNo);
+ if (!isNaN(page)) {
+ this.fire('change-page', {page: page - 1});
+ } else {
+ // Repopulate the input.
+ this.indexChanged();
+ // Change the chosenPageNo so if it is '' again we can repopulate it.
+ this.chosenPageNo = this.pageNo;
+ }
+ },
+
+ indexChanged: function() {
+ this.pageNo = String(this.index + 1);
+ },
+
+ docLengthChanged: function() {
+ var numDigits = this.docLength.toString().length;
+ this.$.pageselector.style.width = (numDigits * DIGIT_LENGTH) + 'em';
+ },
+
+ select: function() {
+ this.$.input.select();
+ }
+});
diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-password-screen/viewer-password-screen.css b/chromium/chrome/browser/resources/pdf/elements/viewer-password-screen/viewer-password-screen.css
index 183f439f989..83c3fc47961 100644
--- a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-password-screen/viewer-password-screen.css
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-password-screen/viewer-password-screen.css
@@ -1,3 +1,7 @@
+/* Copyright 2015 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. */
+
:host {
-webkit-transition: opacity 400ms ease-in-out;
background-color: #ccc;
@@ -29,12 +33,6 @@
width: 300px;
}
-#successMessage {
- display: inline-block;
- padding-left: 5px;
- pointer-events: none;
-}
-
input {
color: #333;
pointer-events: all;
diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-password-screen/viewer-password-screen.html b/chromium/chrome/browser/resources/pdf/elements/viewer-password-screen/viewer-password-screen.html
index 24a82330987..747adb570c6 100644
--- a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-password-screen/viewer-password-screen.html
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-password-screen/viewer-password-screen.html
@@ -1,3 +1,5 @@
+<link rel="import" href="chrome://resources/polymer/polymer/polymer.html">
+
<polymer-element name="viewer-password-screen" attributes="text active">
<template>
<link rel="stylesheet" href="viewer-password-screen.css">
@@ -6,7 +8,6 @@
<div id="message">{{text}}</div>
<input id="password" type="password" size="20"></input>
<input id="submit" type="submit" on-click={{submit}}></input>
- <div id="successMessage">{{successMessage}}</div>
</form>
</div>
</template>
diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-password-screen/viewer-password-screen.js b/chromium/chrome/browser/resources/pdf/elements/viewer-password-screen/viewer-password-screen.js
index 9ba949f09b6..83e8a23d075 100644
--- a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-password-screen/viewer-password-screen.js
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-password-screen/viewer-password-screen.js
@@ -6,17 +6,13 @@ Polymer('viewer-password-screen', {
text: 'This document is password protected. Please enter a password.',
active: false,
timerId: undefined,
- ready: function () {
+ ready: function() {
this.activeChanged();
},
accept: function() {
- this.successMessage = '&#x2714;' // Tick.
- this.$.successMessage.style.color = 'rgb(0,125,0)';
this.active = false;
},
deny: function() {
- this.successMessage = '&#x2718;'; // Cross.
- this.$.successMessage.style.color = 'rgb(255,0,0)';
this.$.password.disabled = false;
this.$.submit.disabled = false;
this.$.password.focus();
@@ -27,8 +23,6 @@ Polymer('viewer-password-screen', {
e.preventDefault();
if (this.$.password.value.length == 0)
return;
- this.successMessage = '...';
- this.$.successMessage.style.color = 'rgb(0,0,0)';
this.$.password.disabled = true;
this.$.submit.disabled = true;
this.fire('password-submitted', {password: this.$.password.value});
@@ -39,13 +33,12 @@ Polymer('viewer-password-screen', {
if (this.active) {
this.style.visibility = 'visible';
this.style.opacity = 1;
- this.successMessage = '';
this.$.password.focus();
} else {
this.style.opacity = 0;
this.timerId = setTimeout(function() {
- this.style.visibility = 'hidden'
+ this.style.visibility = 'hidden';
}.bind(this), 400);
}
}
-}); \ No newline at end of file
+});
diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.css b/chromium/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.css
new file mode 100644
index 00000000000..bafc784a800
--- /dev/null
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.css
@@ -0,0 +1,66 @@
+/* Copyright 2015 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. */
+
+/* We introduce a wrapper animatable element and set the overflow since
+ * core-transition interferes with the box-shadow from paper-shadow. */
+#animatable {
+ overflow: visible;
+}
+
+/* We introduce a wrapper aligner element as setting the relevant attributes
+ * (horizontal justified layout center) have no effect on the core-toolbar. */
+#aligner {
+ width: 100%;
+}
+
+#title {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+#pageselector-container {
+ text-align: center;
+ /* The container resizes according to the width of the toolbar. On small
+ * screens with large numbers of pages, overflow page numbers without
+ * wrapping. */
+ white-space: nowrap;
+}
+
+#buttons {
+ text-align: right;
+}
+
+paper-progress {
+ height: 56px;
+ position: absolute;
+ width: 100%;
+ z-index: 3;
+}
+
+paper-progress::shadow #activeProgress {
+ background-color: rgb(27, 168, 243);
+}
+
+paper-progress::shadow #progressContainer {
+ background-color: rgb(100, 181, 246);
+}
+
+core-toolbar {
+ background-color: rgb(27, 168, 243);
+ color: rgb(241, 241, 241);
+ font-size: 1.5em;
+ height: 56px;
+ padding-left: 1em;
+ padding-right: 1em;
+ z-index: 3;
+}
+
+core-toolbar /deep/ ::selection {
+ background: rgb(187, 222, 251);
+}
+
+.invisible {
+ visibility: hidden;
+}
diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.html b/chromium/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.html
new file mode 100644
index 00000000000..2127522d708
--- /dev/null
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.html
@@ -0,0 +1,47 @@
+<link rel="import" href="chrome://resources/polymer/polymer/polymer.html">
+<link rel="import" href="../viewer-page-selector/viewer-page-selector.html">
+<link rel="import" href="chrome://resources/polymer/core-icons/image-icons.html">
+<link rel="import" href="chrome://resources/polymer/core-toolbar/core-toolbar.html">
+<link rel="import" href="chrome://resources/polymer/paper-icon-button/paper-icon-button.html">
+<link rel="import" href="chrome://resources/polymer/paper-shadow/paper-shadow.html">
+
+<polymer-element name="viewer-pdf-toolbar">
+<template>
+ <link rel="stylesheet" href="viewer-pdf-toolbar.css">
+ <div id="animatable">
+ <paper-shadow fit></paper-shadow>
+
+ <!-- TODO(tsergeant): Re-enable once paper-progress is available in Polymer
+ 0.8 -->
+ <!--<paper-progress value="{{loadProgress}}"></paper-progress>-->
+
+ <core-toolbar class="core-narrow">
+ <div id="aligner" horizontal layout center>
+ <span id="title" class="invisible" title="{{docTitle}}" flex five>
+ {{docTitle}}
+ </span>
+
+ <div flex one id="pageselector-container">
+ <viewer-page-selector id="pageselector" class="invisible"
+ index="{{pageIndex}}" docLength="{{docLength}}">
+ </viewer-page-selector>
+ </div>
+
+ <div id="buttons" class="invisible" flex five>
+ <paper-icon-button icon="image:rotate-right"
+ on-click="{{rotateRight}}"></paper-icon-button>
+ <template if="{{hasBookmarks}}">
+ <paper-icon-button icon="bookmark-outline"
+ on-click="{{toggleBookmarks}}" hidden></paper-icon-button>
+ </template>
+ <paper-icon-button icon="save"
+ on-click="{{save}}"></paper-icon-button>
+ <paper-icon-button icon="print"
+ on-click="{{print}}"></paper-icon-button>
+ </div>
+ </div>
+ </core-toolbar>
+ </div>
+</template>
+<script src="viewer-pdf-toolbar.js"></script>
+</polymer-element>
diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.js b/chromium/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.js
new file mode 100644
index 00000000000..b291f62348e
--- /dev/null
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.js
@@ -0,0 +1,88 @@
+// Copyright 2015 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.
+(function() {
+ Polymer('viewer-pdf-toolbar', {
+ /**
+ * @type {string}
+ * The title of the PDF document.
+ */
+ docTitle: '',
+
+ /**
+ * @type {number}
+ * The current index of the page being viewed (0-based).
+ */
+ pageIndex: 0,
+
+ /**
+ * @type {number}
+ * The current loading progress of the PDF document (0 - 100).
+ */
+ loadProgress: 0,
+
+ /**
+ * @type {boolean}
+ * Whether the document has bookmarks.
+ */
+ hasBookmarks: false,
+
+ /**
+ * @type {number}
+ * The number of pages in the PDF document.
+ */
+ docLength: 1,
+
+ ready: function() {
+ /**
+ * @type {Object}
+ * Used in core-transition to determine whether the animatable is open.
+ * TODO(tsergeant): Add core-transition back in once it is in Polymer 0.8.
+ */
+ this.state_ = { opened: false };
+ this.show();
+ },
+
+ loadProgressChanged: function() {
+ if (this.loadProgress >= 100) {
+ this.$.title.classList.toggle('invisible', false);
+ this.$.pageselector.classList.toggle('invisible', false);
+ this.$.buttons.classList.toggle('invisible', false);
+ }
+ },
+
+ hide: function() {
+ if (this.state_.opened)
+ this.toggleVisibility();
+ },
+
+ show: function() {
+ if (!this.state_.opened)
+ this.toggleVisibility();
+ },
+
+ toggleVisibility: function() {
+ this.state_.opened = !this.state_.opened;
+ },
+
+ selectPageNumber: function() {
+ this.$.pageselector.select();
+ },
+
+ rotateRight: function() {
+ this.fire('rotate-right');
+ },
+
+ toggleBookmarks: function() {
+ this.fire('toggle-bookmarks');
+ },
+
+ save: function() {
+ this.fire('save');
+ },
+
+ print: function() {
+ this.fire('print');
+ }
+ });
+})();
diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-progress-bar/viewer-progress-bar.css b/chromium/chrome/browser/resources/pdf/elements/viewer-progress-bar/viewer-progress-bar.css
index f5c13e30114..ecc142b59b7 100644
--- a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-progress-bar/viewer-progress-bar.css
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-progress-bar/viewer-progress-bar.css
@@ -51,11 +51,11 @@
background-color: rgb(29, 39, 57);
border-radius: 50%;
height: 80px;
- left: 44px;
+ left: 48px;
margin: 0;
padding: 0;
position: absolute;
- top: 44px;
+ top: 48px;
width: 80px;
}
diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-progress-bar/viewer-progress-bar.html b/chromium/chrome/browser/resources/pdf/elements/viewer-progress-bar/viewer-progress-bar.html
index bc16ef7a383..d8c23fa403b 100644
--- a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-progress-bar/viewer-progress-bar.html
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-progress-bar/viewer-progress-bar.html
@@ -1,3 +1,5 @@
+<link rel="import" href="chrome://resources/polymer/polymer/polymer.html">
+
<polymer-element name="viewer-progress-bar"
attributes="progress text numSegments">
<template>
diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-progress-bar/viewer-progress-bar.js b/chromium/chrome/browser/resources/pdf/elements/viewer-progress-bar/viewer-progress-bar.js
index 7c4932dec92..97987cc0eeb 100644
--- a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-progress-bar/viewer-progress-bar.js
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-progress-bar/viewer-progress-bar.js
@@ -14,7 +14,7 @@ Polymer('viewer-progress-bar', {
var numVisible = this.progress * this.segments.length / 100.0;
for (var i = 0; i < this.segments.length; i++) {
this.segments[i].style.visibility =
- i < numVisible ? 'visible' : 'hidden';
+ i < numVisible ? 'inherit' : 'hidden';
}
if (this.progress >= 100 || this.progress < 0)
diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-toolbar/viewer-toolbar.css b/chromium/chrome/browser/resources/pdf/elements/viewer-toolbar/viewer-toolbar.css
index 64be0e9f12c..64be0e9f12c 100644
--- a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-toolbar/viewer-toolbar.css
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-toolbar/viewer-toolbar.css
diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-toolbar/viewer-toolbar.html b/chromium/chrome/browser/resources/pdf/elements/viewer-toolbar/viewer-toolbar.html
index a22bf997418..f0f60bcdfb8 100644
--- a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-toolbar/viewer-toolbar.html
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-toolbar/viewer-toolbar.html
@@ -1,3 +1,5 @@
+<link rel="import" href="chrome://resources/polymer/polymer/polymer.html">
+
<polymer-element name="viewer-toolbar" attributes="fadingIn">
<template>
<link rel="stylesheet" href="viewer-toolbar.css">
diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-toolbar/viewer-toolbar.js b/chromium/chrome/browser/resources/pdf/elements/viewer-toolbar/viewer-toolbar.js
index c28b45b670b..c28b45b670b 100644
--- a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-toolbar/viewer-toolbar.js
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-toolbar/viewer-toolbar.js
diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-zoom-toolbar/viewer-zoom-button.css b/chromium/chrome/browser/resources/pdf/elements/viewer-zoom-toolbar/viewer-zoom-button.css
new file mode 100644
index 00000000000..b147376eb85
--- /dev/null
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-zoom-toolbar/viewer-zoom-button.css
@@ -0,0 +1,9 @@
+/* Copyright 2015 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. */
+
+:host {
+ background-color: #fff;
+ color: rgb(158, 158, 158);
+ overflow: visible;
+}
diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-zoom-toolbar/viewer-zoom-button.html b/chromium/chrome/browser/resources/pdf/elements/viewer-zoom-toolbar/viewer-zoom-button.html
new file mode 100644
index 00000000000..59017bc2234
--- /dev/null
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-zoom-toolbar/viewer-zoom-button.html
@@ -0,0 +1,11 @@
+<link rel="import" href="chrome://resources/polymer/polymer/polymer.html">
+<link rel="import" href="chrome://resources/polymer/core-icons/core-icons.html">
+<link rel="import" href="chrome://resources/polymer/paper-fab/paper-fab.html">
+
+<polymer-element name="viewer-zoom-button" extends="paper-fab" mini>
+<template>
+ <link rel="stylesheet" href="viewer-zoom-button.css">
+ <shadow></shadow>
+</template>
+<script src="viewer-zoom-button.js"></script>
+</polymer-element>
diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-zoom-toolbar/viewer-zoom-button.js b/chromium/chrome/browser/resources/pdf/elements/viewer-zoom-toolbar/viewer-zoom-button.js
new file mode 100644
index 00000000000..48a518bb020
--- /dev/null
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-zoom-toolbar/viewer-zoom-button.js
@@ -0,0 +1,30 @@
+// Copyright 2015 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.
+
+Polymer('viewer-zoom-button', {
+ ready: function() {
+ this.super();
+ this.state_ = { opened: true };
+ },
+
+ show: function(delay) {
+ if (!this.state_.opened)
+ this.toggle_(delay);
+ },
+
+ hide: function(delay) {
+ if (this.state_.opened)
+ this.toggle_(delay);
+ },
+
+ toggle_: function(delay) {
+ delay = delay || 0;
+ this.state_.opened = !this.state_.opened;
+ },
+
+ activeChanged: function() {
+ if (this.active)
+ this.active = false;
+ }
+});
diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-zoom-toolbar/viewer-zoom-toolbar.css b/chromium/chrome/browser/resources/pdf/elements/viewer-zoom-toolbar/viewer-zoom-toolbar.css
new file mode 100644
index 00000000000..b4731d53265
--- /dev/null
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-zoom-toolbar/viewer-zoom-toolbar.css
@@ -0,0 +1,44 @@
+/* Copyright 2015 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. */
+
+:host {
+ bottom: 0;
+ padding: 24px;
+ position: fixed;
+ right: 0;
+ z-index: 3;
+}
+
+#zoom-buttons {
+ display: inline-block;
+}
+
+#slider {
+ display: inline-block;
+ margin-bottom: 0.8em;
+ margin-right: 2em;
+ overflow: visible;
+}
+
+#main-zoom {
+ background-color: rgb(27, 168, 243);
+ display: block;
+ margin: auto;
+ margin-top: 2em;
+ overflow: visible;
+}
+
+#fit-to-width-button {
+ display: block;
+ margin: auto;
+ margin-top: 2em;
+ overflow: visible;
+}
+
+#fit-to-page-button {
+ display: block;
+ margin: auto;
+ overflow: visible;
+}
+
diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-zoom-toolbar/viewer-zoom-toolbar.html b/chromium/chrome/browser/resources/pdf/elements/viewer-zoom-toolbar/viewer-zoom-toolbar.html
new file mode 100644
index 00000000000..90f70a5e088
--- /dev/null
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-zoom-toolbar/viewer-zoom-toolbar.html
@@ -0,0 +1,20 @@
+<link rel="import" href="chrome://resources/polymer/polymer/polymer.html">
+<link rel="import" href="chrome://resources/polymer/core-icons/core-icons.html">
+<link rel="import" href="viewer-zoom-button.html">
+
+<polymer-element name="viewer-zoom-toolbar">
+<template>
+ <link rel="stylesheet" href="viewer-zoom-toolbar.css">
+
+ <div id="zoom-buttons">
+ <div id="buttons">
+ <!-- TODO(alexandrec): Replace with custom icons. -->
+ <viewer-zoom-button id="fit-to-page-button" icon="fullscreen-exit"
+ on-click="{{fitToPage}}"></viewer-zoom-button>
+ <viewer-zoom-button id="fit-to-width-button" icon="fullscreen"
+ on-click="{{fitToWidth}}"></viewer-zoom-button>
+ </div>
+ </div>
+</template>
+<script src="viewer-zoom-toolbar.js"></script>
+</polymer-element>
diff --git a/chromium/chrome/browser/resources/pdf/elements/viewer-zoom-toolbar/viewer-zoom-toolbar.js b/chromium/chrome/browser/resources/pdf/elements/viewer-zoom-toolbar/viewer-zoom-toolbar.js
new file mode 100644
index 00000000000..7fa16a7be9b
--- /dev/null
+++ b/chromium/chrome/browser/resources/pdf/elements/viewer-zoom-toolbar/viewer-zoom-toolbar.js
@@ -0,0 +1,61 @@
+// Copyright 2015 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.
+
+ANIMATION_INTERVAL = 50;
+
+Polymer('viewer-zoom-toolbar', {
+ /**
+ * @type {number}
+ * The minimum zoom percentage allowed.
+ */
+ zoomMin: 25,
+
+ /**
+ * @type {number}
+ * The maximum zoom percentage allowed.
+ */
+ zoomMax: 500,
+
+ /**
+ * @type {number}
+ * The default zoom percentage.
+ */
+ zoomValue: 100,
+
+ get visible() {
+ return this.visible_;
+ },
+
+ ready: function() {
+ this.visible_ = false;
+ },
+
+ zoomValueChanged: function() {
+ this.fire('zoom', { zoom: this.zoomValue / 100 });
+ },
+
+ fitToPage: function() {
+ this.fire('fit-to-page');
+ },
+
+ fitToWidth: function() {
+ this.fire('fit-to-width');
+ },
+
+ show: function() {
+ if (!this.visible) {
+ this.visible_ = true;
+ this.$['fit-to-width-button'].show();
+ this.$['fit-to-page-button'].show(ANIMATION_INTERVAL);
+ }
+ },
+
+ hide: function() {
+ if (this.visible) {
+ this.visible_ = false;
+ this.$['fit-to-page-button'].hide();
+ this.$['fit-to-width-button'].hide(ANIMATION_INTERVAL);
+ }
+ },
+});
diff --git a/chromium/chrome/browser/resources/pdf/html_office/OWNERS b/chromium/chrome/browser/resources/pdf/html_office/OWNERS
deleted file mode 100644
index feedefa65bd..00000000000
--- a/chromium/chrome/browser/resources/pdf/html_office/OWNERS
+++ /dev/null
@@ -1,3 +0,0 @@
-ganetsky@chromium.org
-jliebrand@chromium.org
-raymes@chromium.org
diff --git a/chromium/chrome/browser/resources/pdf/html_office/codereview.settings b/chromium/chrome/browser/resources/pdf/html_office/codereview.settings
deleted file mode 100644
index ca62ae98a60..00000000000
--- a/chromium/chrome/browser/resources/pdf/html_office/codereview.settings
+++ /dev/null
@@ -1,7 +0,0 @@
-# This file is used by gcl to get repository specific information.
-CODE_REVIEW_SERVER: codereview.chromium.org
-CC_LIST: raymes@chromium.org,ganetsky@chromium.org,jliebrand@chromium.org
-STATUS: http://chromium-status.appspot.com/status
-TRY_ON_UPLOAD: False
-GITCL_PREUPLOAD: http://src.chromium.org/viewvc/trunk/tools/depot_tools/git-cl-upload-hook?revision=HEAD&root=chrome
-GITCL_PREDCOMMIT: http://src.chromium.org/viewvc/trunk/tools/depot_tools/git-cl-upload-hook?revision=HEAD&root=chrome
diff --git a/chromium/chrome/browser/resources/pdf/includes.js b/chromium/chrome/browser/resources/pdf/includes.js
deleted file mode 100644
index 168c8962ea3..00000000000
--- a/chromium/chrome/browser/resources/pdf/includes.js
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-<include src="../../../../third_party/polymer/components/platform/platform.js">
-<include src="../../../../third_party/polymer/components/polymer/polymer.js">
-<include src="html_office/elements/viewer-toolbar/viewer-toolbar.js">
-<include src="html_office/elements/viewer-progress-bar/viewer-progress-bar.js">
-<include src="html_office/elements/viewer-password-screen/viewer-password-screen.js">
-<include src="html_office/elements/viewer-page-indicator/viewer-page-indicator.js">
-<include src="html_office/elements/viewer-error-screen/viewer-error-screen.js">
-<include src="html_office/elements/viewer-button/viewer-button.js">
diff --git a/chromium/chrome/browser/resources/pdf/index-material.css b/chromium/chrome/browser/resources/pdf/index-material.css
new file mode 100644
index 00000000000..5d7784eec29
--- /dev/null
+++ b/chromium/chrome/browser/resources/pdf/index-material.css
@@ -0,0 +1,62 @@
+/* Copyright 2015 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. */
+
+@font-face {
+ font-family: 'Roboto';
+ font-style: normal;
+ font-weight: 400;
+ src: local('Roboto Regular'), local('Roboto-Regular'), url(chrome://resources/roboto/roboto.woff2) format('woff2');
+ unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
+}
+
+body {
+ background-color: #424242;
+ font-family: Roboto, 'Helvetica Neue', Helvetica, Arial;
+ margin: 0;
+}
+
+viewer-pdf-toolbar {
+ position: fixed;
+ width: 100%;
+ /* TODO(alexandrec): the viewer-pane's paper-dialog is set to z-index: 12
+ * internally by the polymer element. Figure out why that is happening. */
+ z-index: 1000;
+}
+
+#plugin {
+ height: 100%;
+ position: fixed;
+ width: 100%;
+ z-index: 1;
+}
+
+#sizer {
+ position: absolute;
+ z-index: 0;
+}
+
+bookmarks-pane {
+ z-index: 2;
+}
+
+viewer-toolbar {
+ visibility: hidden;
+ white-space: nowrap;
+ z-index: 3;
+}
+
+viewer-page-indicator {
+ visibility: hidden;
+ z-index: 3;
+}
+
+viewer-error-screen {
+ visibility: hidden;
+ z-index: 2;
+}
+
+viewer-password-screen {
+ visibility: hidden;
+ z-index: 2;
+}
diff --git a/chromium/chrome/browser/resources/pdf/index-material.html b/chromium/chrome/browser/resources/pdf/index-material.html
new file mode 100644
index 00000000000..f08040e8dcb
--- /dev/null
+++ b/chromium/chrome/browser/resources/pdf/index-material.html
@@ -0,0 +1,41 @@
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
+<head>
+ <meta charset="utf-8">
+ <link rel="import" href="elements/viewer-bookmarks-content/viewer-bookmarks-content.html">
+ <link rel="import" href="elements/viewer-button/viewer-button.html">
+ <link rel="import" href="elements/viewer-error-screen/viewer-error-screen.html">
+ <link rel="import" href="elements/viewer-page-selector/viewer-page-selector.html">
+ <link rel="import" href="elements/viewer-password-screen/viewer-password-screen.html">
+ <link rel="import" href="elements/viewer-pdf-toolbar/viewer-pdf-toolbar.html">
+ <link rel="import" href="elements/viewer-zoom-toolbar/viewer-zoom-toolbar.html">
+
+ <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
+ <link rel="stylesheet" type="text/css" href="index-material.css">
+</head>
+<body>
+
+<viewer-pdf-toolbar id="material-toolbar"></viewer-pdf-toolbar>
+
+<viewer-bookmarks-content id="bookmarks-container" hidden></viewer-bookmarks-content>
+
+<div id="sizer"></div>
+<viewer-password-screen id="password-screen"></viewer-password-screen>
+
+<viewer-zoom-toolbar id="zoom-toolbar"></viewer-zoom-toolbar>
+
+<viewer-error-screen id="error-screen"></viewer-error-screen>
+
+</body>
+<script src="ui_manager.js"></script>
+<script src="viewport.js"></script>
+<script src="open_pdf_params_parser.js"></script>
+<script src="navigator.js"></script>
+<script src="viewport_scroller.js"></script>
+<script src="zoom_manager.js"></script>
+<script src="pdf_scripting_api.js"></script>
+<script src="chrome://resources/js/util.js"></script>
+<script src="browser_api.js"></script>
+<script src="pdf.js"></script>
+<script src="main.js"></script>
+</html>
diff --git a/chromium/chrome/browser/resources/pdf/index.css b/chromium/chrome/browser/resources/pdf/index.css
index 4186344c81d..06ca8fc2c12 100644
--- a/chromium/chrome/browser/resources/pdf/index.css
+++ b/chromium/chrome/browser/resources/pdf/index.css
@@ -9,6 +9,7 @@ body {
viewer-toolbar {
visibility: hidden;
+ white-space: nowrap;
z-index: 3;
}
@@ -18,6 +19,7 @@ viewer-page-indicator {
}
viewer-progress-bar {
+ visibility: hidden;
z-index: 3;
}
diff --git a/chromium/chrome/browser/resources/pdf/index.html b/chromium/chrome/browser/resources/pdf/index.html
index c6f64f0df09..2ecf2c8fade 100644
--- a/chromium/chrome/browser/resources/pdf/index.html
+++ b/chromium/chrome/browser/resources/pdf/index.html
@@ -1,17 +1,14 @@
-<!DOCTYPE HTML>
-<html i18n-values="dir:textdirection">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
<head>
<meta charset="utf-8">
- <!-- TODO(raymes): Turn these <include>s into HTML imports once they are
- fully implemented. At that point includes.js can be removed
- as the scripts will be pulled in automatically. -->
- <script src="includes.js"></script>
- <include src="html_office/elements/viewer-button/viewer-button.html">
- <include src="html_office/elements/viewer-error-screen/viewer-error-screen.html">
- <include src="html_office/elements/viewer-page-indicator/viewer-page-indicator.html">
- <include src="html_office/elements/viewer-password-screen/viewer-password-screen.html">
- <include src="html_office/elements/viewer-progress-bar/viewer-progress-bar.html">
- <include src="html_office/elements/viewer-toolbar/viewer-toolbar.html">
+ <link rel="import" href="elements/viewer-button/viewer-button.html">
+ <link rel="import" href="elements/viewer-error-screen/viewer-error-screen.html">
+ <link rel="import" href="elements/viewer-page-indicator/viewer-page-indicator.html">
+ <link rel="import" href="elements/viewer-password-screen/viewer-password-screen.html">
+ <link rel="import" href="elements/viewer-progress-bar/viewer-progress-bar.html">
+ <link rel="import" href="elements/viewer-toolbar/viewer-toolbar.html">
+ <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" type="text/css" href="index.css">
</head>
<body>
@@ -23,35 +20,42 @@
<viewer-toolbar id="toolbar">
<viewer-button id="fit-to-page-button"
- assetpath="html_office/elements/viewer-button/"
+ assetpath="elements/viewer-button/"
img="button_fit_page.png" latchable>
</viewer-button>
<viewer-button id="fit-to-width-button"
- assetpath="html_office/elements/viewer-button/"
+ assetpath="elements/viewer-button/"
img="button_fit_width.png" latchable>
</viewer-button>
+ <viewer-button id="zoom-out-button"
+ assetpath="elements/viewer-button/"
+ img="button_zoom_out.png">
+ </viewer-button>
<viewer-button id="zoom-in-button"
- assetpath="html_office/elements/viewer-button/"
+ assetpath="elements/viewer-button/"
img="button_zoom_in.png">
</viewer-button>
- <viewer-button id="zoom-out-button"
- assetpath="html_office/elements/viewer-button/"
- img="button_zoom_out.png">
+ <viewer-button id="save-button"
+ assetpath="elements/viewer-button/"
+ img="button_save.png">
</viewer-button>
- <a id="save-button-link" download>
- <viewer-button id="save-button"
- assetpath="html_office/elements/viewer-button/"
- img="button_save.png">
- </viewer-button>
- </a>
<viewer-button id="print-button"
- assetpath="html_office/elements/viewer-button/"
- img="button_print.png">
+ assetpath="elements/viewer-button/"
+ img="button_print.png">
</viewer-button>
</viewer-toolbar>
<viewer-error-screen id="error-screen"></viewer-error-screen>
</body>
+<script src="viewport.js"></script>
+<script src="open_pdf_params_parser.js"></script>
+<script src="navigator.js"></script>
+<script src="viewport_scroller.js"></script>
+<script src="zoom_manager.js"></script>
+<script src="pdf_scripting_api.js"></script>
+<script src="chrome://resources/js/util.js"></script>
+<script src="browser_api.js"></script>
+<script src="pdf.js"></script>
<script src="main.js"></script>
</html>
diff --git a/chromium/chrome/browser/resources/pdf/main.js b/chromium/chrome/browser/resources/pdf/main.js
index 92171567eab..e6938bc55cd 100644
--- a/chromium/chrome/browser/resources/pdf/main.js
+++ b/chromium/chrome/browser/resources/pdf/main.js
@@ -4,49 +4,52 @@
'use strict';
-<include src="../../../../ui/webui/resources/js/util.js">
-<include src="open_pdf_params_parser.js">
-<include src="pdf.js">
-<include src="pdf_scripting_api.js">
-<include src="viewport.js">
-
/**
* Global PDFViewer object, accessible for testing.
* @type Object
*/
var viewer;
-/**
- * Entrypoint for starting the PDF viewer. This function obtains the details
- * of the PDF 'stream' (the data that points to the PDF) and constructs a
- * PDFViewer object with it.
- */
-(function main() {
- // If the viewer is started from the browser plugin, the view ID will be
- // passed in which identifies the instance of the plugin.
- var params = window.location.search.substring(1).split('=');
- if (params.length == 2 && params[0] == 'id') {
- var viewId = params[1];
-
- // Send a message to the background page to obtain the stream details. It
- // will run the callback function passed in to initialize the viewer.
- chrome.runtime.sendMessage(
- 'mhjfbmdgcfjbbpaeojofohoefgiehjai',
- {viewId: viewId},
- function(streamDetails) { viewer = new PDFViewer(streamDetails); });
- return;
+
+(function() {
+ /**
+ * Stores any pending messages received which should be passed to the
+ * PDFViewer when it is created.
+ * @type Array
+ */
+ var pendingMessages = [];
+
+ /**
+ * Handles events that are received prior to the PDFViewer being created.
+ * @param {Object} message A message event received.
+ */
+ function handleScriptingMessage(message) {
+ pendingMessages.push(message);
}
- // The viewer may be started directly by passing in the URL of the PDF to load
- // as the query string. This is used for print preview in particular. The URL
- // of this page will be of the form
- // 'chrome-extension://<extension id>?<pdf url>'. We pull out the <pdf url>
- // part here.
- var url = window.location.search.substring(1);
- var streamDetails = {
- streamUrl: url,
- originalUrl: url,
- responseHeaders: ''
+ /**
+ * Initialize the global PDFViewer and pass any outstanding messages to it.
+ * @param {Object} browserApi An object providing an API to the browser.
+ */
+ function initViewer(browserApi) {
+ // PDFViewer will handle any messages after it is created.
+ window.removeEventListener('message', handleScriptingMessage, false);
+ viewer = new PDFViewer(browserApi);
+ while (pendingMessages.length > 0)
+ viewer.handleScriptingMessage(pendingMessages.shift());
+ }
+
+ /**
+ * Entrypoint for starting the PDF viewer. This function obtains the browser
+ * API for the PDF and constructs a PDFViewer object with it.
+ */
+ function main() {
+ // Set up an event listener to catch scripting messages which are sent prior
+ // to the PDFViewer being created.
+ window.addEventListener('message', handleScriptingMessage, false);
+
+ createBrowserApi().then(initViewer);
};
- viewer = new PDFViewer(streamDetails);
+
+ main();
})();
diff --git a/chromium/chrome/browser/resources/pdf/manifest.json b/chromium/chrome/browser/resources/pdf/manifest.json
index 8dca0e160da..168515f124e 100644
--- a/chromium/chrome/browser/resources/pdf/manifest.json
+++ b/chromium/chrome/browser/resources/pdf/manifest.json
@@ -2,29 +2,30 @@
// chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai
"manifest_version": 2,
"key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDN6hM0rsDYGbzQPQfOygqlRtQgKUXMfnSjhIBL7LnReAVBEd7ZmKtyN2qmSasMl4HZpMhVe2rPWVVwBDl6iyNE/Kok6E6v6V3vCLGsOpQAuuNVye/3QxzIldzG/jQAdWZiyXReRVapOhZtLjGfywCvlWq7Sl/e3sbc0vWybSDI2QIDAQAB",
- "name": "Chrome PDF Viewer",
+ "name": "<NAME>",
"version": "1",
- "description": "Chrome PDF Viewer",
+ "description": "",
"offline_enabled": true,
"incognito": "split",
"permissions": [
- "file://*/",
- "ftp://*/",
- "http://*/",
- "https://*/",
- "chrome://*/",
- "streamsPrivate"
+ "<all_urls>"
],
- "background": {
- "scripts": ["background.js"],
- "transient": true,
- "persistent": false
- },
- "mime_types_handler": "index.html",
"mime_types": [
"application/pdf"
],
+ "content_security_policy": "script-src 'self' chrome://resources; object-src *; plugin-types application/x-google-chrome-pdf",
+ // This is to work-around an issue where this extension is not granted
+ // permission to access chrome://resources when iframed for print preview.
+ // See https://crbug.com/444752.
+ "content_scripts": [
+ {
+ "matches": ["chrome://print/*"],
+ "js": ["content_script.js"]
+ }
+ ],
+ "mime_types_handler": "<INDEX>",
"web_accessible_resources": [
- "index.html"
+ "index.html",
+ "<INDEX>"
]
}
diff --git a/chromium/chrome/browser/resources/pdf/navigator.js b/chromium/chrome/browser/resources/pdf/navigator.js
new file mode 100644
index 00000000000..bf86d5d371f
--- /dev/null
+++ b/chromium/chrome/browser/resources/pdf/navigator.js
@@ -0,0 +1,95 @@
+// Copyright 2015 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';
+
+/**
+ * Creates a new Navigator for navigating to links inside or outside the PDF.
+ * @param {string} originalUrl The original page URL.
+ * @param {Object} viewport The viewport info of the page.
+ * @param {Object} paramsParser The object for URL parsing.
+ * @param {Function} navigateInCurrentTabCallback The Callback function that
+ * gets called when navigation happens in the current tab.
+ * @param {Function} navigateInNewTabCallback The Callback function that gets
+ * called when navigation happens in the new tab.
+ */
+function Navigator(originalUrl,
+ viewport,
+ paramsParser,
+ navigateInCurrentTabCallback,
+ navigateInNewTabCallback) {
+ this.originalUrl_ = originalUrl;
+ this.viewport_ = viewport;
+ this.paramsParser_ = paramsParser;
+ this.navigateInCurrentTabCallback_ = navigateInCurrentTabCallback;
+ this.navigateInNewTabCallback_ = navigateInNewTabCallback;
+}
+
+Navigator.prototype = {
+ /**
+ * @private
+ * Function to navigate to the given URL. This might involve navigating
+ * within the PDF page or opening a new url (in the same tab or a new tab).
+ * @param {string} url The URL to navigate to.
+ * @param {boolean} newTab Whether to perform the navigation in a new tab or
+ * in the current tab.
+ */
+ navigate: function(url, newTab) {
+ if (url.length == 0)
+ return;
+ // If |urlFragment| starts with '#', then it's for the same URL with a
+ // different URL fragment.
+ if (url.charAt(0) == '#') {
+ // if '#' is already present in |originalUrl| then remove old fragment
+ // and add new url fragment.
+ var hashIndex = this.originalUrl_.search('#');
+ if (hashIndex != -1)
+ url = this.originalUrl_.substring(0, hashIndex) + url;
+ else
+ url = this.originalUrl_ + url;
+ }
+
+ // If there's no scheme, add http.
+ if (url.indexOf('://') == -1 && url.indexOf('mailto:') == -1)
+ url = 'http://' + url;
+
+ // Make sure inputURL starts with a valid scheme.
+ if (url.indexOf('http://') != 0 &&
+ url.indexOf('https://') != 0 &&
+ url.indexOf('ftp://') != 0 &&
+ url.indexOf('file://') != 0 &&
+ url.indexOf('mailto:') != 0) {
+ return;
+ }
+ // Make sure inputURL is not only a scheme.
+ if (url == 'http://' ||
+ url == 'https://' ||
+ url == 'ftp://' ||
+ url == 'file://' ||
+ url == 'mailto:') {
+ return;
+ }
+
+ if (newTab) {
+ this.navigateInNewTabCallback_(url);
+ } else {
+ this.paramsParser_.getViewportFromUrlParams(
+ url, this.onViewportReceived_.bind(this));
+ }
+ },
+
+ /**
+ * @private
+ * Called when the viewport position is received.
+ * @param {Object} viewportPosition Dictionary containing the viewport
+ * position.
+ */
+ onViewportReceived_: function(viewportPosition) {
+ var pageNumber = viewportPosition.page;
+ if (pageNumber != undefined)
+ this.viewport_.goToPage(pageNumber);
+ else
+ this.navigateInCurrentTabCallback_(viewportPosition['url']);
+ }
+};
diff --git a/chromium/chrome/browser/resources/pdf/open_pdf_params_parser.js b/chromium/chrome/browser/resources/pdf/open_pdf_params_parser.js
index f4fe8e7354a..ddd18be4164 100644
--- a/chromium/chrome/browser/resources/pdf/open_pdf_params_parser.js
+++ b/chromium/chrome/browser/resources/pdf/open_pdf_params_parser.js
@@ -7,12 +7,12 @@
/**
* Creates a new OpenPDFParamsParser. This parses the open pdf parameters
* passed in the url to set initial viewport settings for opening the pdf.
- * @param {string} url to be parsed.
+ * @param {Object} getNamedDestinationsFunction The function called to fetch
+ * the page number for a named destination.
*/
-function OpenPDFParamsParser(url) {
- this.url_ = url;
- this.urlParams = {};
- this.parseOpenPDFParams_();
+function OpenPDFParamsParser(getNamedDestinationsFunction) {
+ this.outstandingRequests_ = [];
+ this.getNamedDestinationsFunction_ = getNamedDestinationsFunction;
}
OpenPDFParamsParser.prototype = {
@@ -22,8 +22,9 @@ OpenPDFParamsParser.prototype = {
* parameter is passed while opening PDF then PDF should be opened
* at the specified zoom level.
* @param {number} zoom value.
+ * @param {Object} viewportPosition to store zoom and position value.
*/
- parseZoomParam_: function(paramValue) {
+ parseZoomParam_: function(paramValue, viewportPosition) {
var paramValueSplit = paramValue.split(',');
if ((paramValueSplit.length != 1) && (paramValueSplit.length != 3))
return;
@@ -35,31 +36,48 @@ OpenPDFParamsParser.prototype = {
// Handle #zoom=scale.
if (paramValueSplit.length == 1) {
- this.urlParams['zoom'] = zoomFactor;
+ viewportPosition['zoom'] = zoomFactor;
return;
}
// Handle #zoom=scale,left,top.
var position = {x: parseFloat(paramValueSplit[1]),
y: parseFloat(paramValueSplit[2])};
- this.urlParams['position'] = position;
- this.urlParams['zoom'] = zoomFactor;
+ viewportPosition['position'] = position;
+ viewportPosition['zoom'] = zoomFactor;
},
/**
* @private
- * Parse open PDF parameters. These parameters are mentioned in the url
+ * Parse PDF url parameters. These parameters are mentioned in the url
* and specify actions to be performed when opening pdf files.
* See http://www.adobe.com/content/dam/Adobe/en/devnet/acrobat/
* pdfs/pdf_open_parameters.pdf for details.
+ * @param {string} url that needs to be parsed.
+ * @param {Function} callback function to be called with viewport info.
*/
- parseOpenPDFParams_: function() {
- var originalUrl = this.url_;
- var paramIndex = originalUrl.search('#');
- if (paramIndex == -1)
+ getViewportFromUrlParams: function(url, callback) {
+ var viewportPosition = {};
+ viewportPosition['url'] = url;
+ var paramIndex = url.search('#');
+ if (paramIndex == -1) {
+ callback(viewportPosition);
return;
+ }
+
+ var paramTokens = url.substring(paramIndex + 1).split('&');
+ if ((paramTokens.length == 1) && (paramTokens[0].search('=') == -1)) {
+ // Handle the case of http://foo.com/bar#NAMEDDEST. This is not
+ // explicitly mentioned except by example in the Adobe
+ // "PDF Open Parameters" document.
+ this.outstandingRequests_.push({
+ callback: callback,
+ viewportPosition: viewportPosition
+ });
+ this.getNamedDestinationsFunction_(paramTokens[0]);
+ return;
+ }
- var paramTokens = originalUrl.substring(paramIndex + 1).split('&');
var paramsDictionary = {};
for (var i = 0; i < paramTokens.length; ++i) {
var keyValueSplit = paramTokens[i].split('=');
@@ -71,11 +89,35 @@ OpenPDFParamsParser.prototype = {
if ('page' in paramsDictionary) {
// |pageNumber| is 1-based, but goToPage() take a zero-based page number.
var pageNumber = parseInt(paramsDictionary['page']);
- if (!isNaN(pageNumber))
- this.urlParams['page'] = pageNumber - 1;
+ if (!isNaN(pageNumber) && pageNumber > 0)
+ viewportPosition['page'] = pageNumber - 1;
}
if ('zoom' in paramsDictionary)
- this.parseZoomParam_(paramsDictionary['zoom']);
- }
+ this.parseZoomParam_(paramsDictionary['zoom'], viewportPosition);
+
+ if (viewportPosition.page === undefined &&
+ 'nameddest' in paramsDictionary) {
+ this.outstandingRequests_.push({
+ callback: callback,
+ viewportPosition: viewportPosition
+ });
+ this.getNamedDestinationsFunction_(paramsDictionary['nameddest']);
+ } else {
+ callback(viewportPosition);
+ }
+ },
+
+ /**
+ * This is called when a named destination is received and the page number
+ * corresponding to the request for which a named destination is passed.
+ * @param {number} pageNumber The page corresponding to the named destination
+ * requested.
+ */
+ onNamedDestinationReceived: function(pageNumber) {
+ var outstandingRequest = this.outstandingRequests_.shift();
+ if (pageNumber != -1)
+ outstandingRequest.viewportPosition.page = pageNumber;
+ outstandingRequest.callback(outstandingRequest.viewportPosition);
+ },
};
diff --git a/chromium/chrome/browser/resources/pdf/pdf.js b/chromium/chrome/browser/resources/pdf/pdf.js
index 2b22e600f58..97b7fa095ae 100644
--- a/chromium/chrome/browser/resources/pdf/pdf.js
+++ b/chromium/chrome/browser/resources/pdf/pdf.js
@@ -21,6 +21,37 @@ function getScrollbarWidth() {
}
/**
+ * Return the filename component of a URL.
+ * @param {string} url The URL to get the filename from.
+ * @return {string} The filename component.
+ */
+function getFilenameFromURL(url) {
+ var components = url.split(/\/|\\/);
+ return components[components.length - 1];
+}
+
+/**
+ * Called when navigation happens in the current tab.
+ * @param {string} url The url to be opened in the current tab.
+ */
+function onNavigateInCurrentTab(url) {
+ window.location.href = url;
+}
+
+/**
+ * Called when navigation happens in the new tab.
+ * @param {string} url The url to be opened in the new tab.
+ */
+function onNavigateInNewTab(url) {
+ // Prefer the tabs API because it guarantees we can just open a new tab.
+ // window.open doesn't have this guarantee.
+ if (chrome.tabs)
+ chrome.tabs.create({ url: url});
+ else
+ window.open(url);
+}
+
+/**
* The minimum number of pixels to offset the toolbar by from the bottom and
* right side of the screen.
*/
@@ -29,12 +60,19 @@ PDFViewer.MIN_TOOLBAR_OFFSET = 15;
/**
* Creates a new PDFViewer. There should only be one of these objects per
* document.
- * @param {Object} streamDetails The stream object which points to the data
- * contained in the PDF.
+ * @constructor
+ * @param {!BrowserApi} browserApi An object providing an API to the browser.
*/
-function PDFViewer(streamDetails) {
- this.streamDetails = streamDetails;
- this.loaded = false;
+function PDFViewer(browserApi) {
+ this.browserApi_ = browserApi;
+ this.loadState_ = LoadState.LOADING;
+ this.parentWindow_ = null;
+
+ this.delayedScriptingMessages_ = [];
+
+ this.isPrintPreview_ = this.browserApi_.getStreamInfo().originalUrl.indexOf(
+ 'chrome://print') == 0;
+ this.isMaterial_ = location.pathname.substring(1) === 'index-material.html';
// The sizer element is placed behind the plugin element to cause scrollbars
// to be displayed in the window. It is sized according to the document size
@@ -54,13 +92,14 @@ function PDFViewer(streamDetails) {
this.viewportChanged_.bind(this),
this.beforeZoom_.bind(this),
this.afterZoom_.bind(this),
- getScrollbarWidth());
+ getScrollbarWidth(),
+ this.browserApi_.getDefaultZoom());
// Create the plugin object dynamically so we can set its src. The plugin
// element is sized to fill the entire window and is set to be fixed
// positioning, acting as a viewport. The plugin renders into this viewport
// according to the scroll position of the window.
- this.plugin_ = document.createElement('object');
+ this.plugin_ = document.createElement('embed');
// NOTE: The plugin's 'id' field must be set to 'plugin' since
// chrome/renderer/printing/print_web_view_helper.cc actually references it.
this.plugin_.id = 'plugin';
@@ -71,69 +110,88 @@ function PDFViewer(streamDetails) {
// Handle scripting messages from outside the extension that wish to interact
// with it. We also send a message indicating that extension has loaded and
// is ready to receive messages.
- window.addEventListener('message', this.handleScriptingMessage_.bind(this),
+ window.addEventListener('message', this.handleScriptingMessage.bind(this),
false);
- this.sendScriptingMessage_({type: 'readyToReceive'});
- this.plugin_.setAttribute('src', this.streamDetails.originalUrl);
- this.plugin_.setAttribute('stream-url', this.streamDetails.streamUrl);
+ document.title = decodeURIComponent(
+ getFilenameFromURL(this.browserApi_.getStreamInfo().originalUrl));
+ this.plugin_.setAttribute('src',
+ this.browserApi_.getStreamInfo().originalUrl);
+ this.plugin_.setAttribute('stream-url',
+ this.browserApi_.getStreamInfo().streamUrl);
var headers = '';
- for (var header in this.streamDetails.responseHeaders) {
+ for (var header in this.browserApi_.getStreamInfo().responseHeaders) {
headers += header + ': ' +
- this.streamDetails.responseHeaders[header] + '\n';
+ this.browserApi_.getStreamInfo().responseHeaders[header] + '\n';
}
this.plugin_.setAttribute('headers', headers);
- if (window.top == window)
+ if (this.isMaterial_)
+ this.plugin_.setAttribute('is-material', '');
+
+ if (!this.browserApi_.getStreamInfo().embedded)
this.plugin_.setAttribute('full-frame', '');
document.body.appendChild(this.plugin_);
- // TODO(raymes): Remove this spurious message once crbug.com/388606 is fixed.
- // This is a hack to initialize pepper sync scripting and avoid re-entrancy.
- this.plugin_.postMessage({
- type: 'viewport',
- zoom: 1,
- xOffset: 0,
- yOffset: 0
- });
-
// Setup the button event listeners.
- $('fit-to-width-button').addEventListener('click',
- this.viewport_.fitToWidth.bind(this.viewport_));
- $('fit-to-page-button').addEventListener('click',
- this.viewport_.fitToPage.bind(this.viewport_));
- $('zoom-in-button').addEventListener('click',
- this.viewport_.zoomIn.bind(this.viewport_));
- $('zoom-out-button').addEventListener('click',
- this.viewport_.zoomOut.bind(this.viewport_));
- $('save-button-link').href = this.streamDetails.originalUrl;
- $('print-button').addEventListener('click', this.print_.bind(this));
-
- // Setup the keyboard event listener.
- document.onkeydown = this.handleKeyEvent_.bind(this);
+ if (!this.isMaterial_) {
+ $('fit-to-width-button').addEventListener('click',
+ this.viewport_.fitToWidth.bind(this.viewport_));
+ $('fit-to-page-button').addEventListener('click',
+ this.viewport_.fitToPage.bind(this.viewport_));
+ $('zoom-in-button').addEventListener('click',
+ this.viewport_.zoomIn.bind(this.viewport_));
+ $('zoom-out-button').addEventListener('click',
+ this.viewport_.zoomOut.bind(this.viewport_));
+ $('save-button').addEventListener('click', this.save_.bind(this));
+ $('print-button').addEventListener('click', this.print_.bind(this));
+ }
- // Set up the zoom API.
- if (chrome.tabs) {
- chrome.tabs.setZoomSettings({mode: 'manual', scope: 'per-tab'},
- this.afterZoom_.bind(this));
- chrome.tabs.onZoomChange.addListener(function(zoomChangeInfo) {
- // If the zoom level is close enough to the current zoom level, don't
- // change it. This avoids us getting into an infinite loop of zoom changes
- // due to floating point error.
- var MIN_ZOOM_DELTA = 0.01;
- var zoomDelta = Math.abs(this.viewport_.zoom -
- zoomChangeInfo.newZoomFactor);
- // We should not change zoom level when we are responsible for initiating
- // the zoom. onZoomChange() is called before setZoomComplete() callback
- // when we initiate the zoom.
- if ((zoomDelta > MIN_ZOOM_DELTA) && !this.setZoomInProgress_)
- this.viewport_.setZoom(zoomChangeInfo.newZoomFactor);
+ if (this.isMaterial_) {
+ this.zoomToolbar_ = $('zoom-toolbar');
+ this.zoomToolbar_.zoomMin = Viewport.ZOOM_FACTOR_RANGE.min * 100;
+ this.zoomToolbar_.zoomMax = Viewport.ZOOM_FACTOR_RANGE.max * 100;
+ this.zoomToolbar_.addEventListener('zoom', function(e) {
+ this.viewport_.setZoom(e.detail.zoom);
}.bind(this));
+ this.zoomToolbar_.addEventListener('fit-to-width',
+ this.viewport_.fitToWidth.bind(this.viewport_));
+ this.zoomToolbar_.addEventListener('fit-to-page',
+ this.viewport_.fitToPage.bind(this.viewport_));
+
+ this.materialToolbar_ = $('material-toolbar');
+ this.materialToolbar_.docTitle = document.title;
+ this.materialToolbar_.addEventListener('save', this.save_.bind(this));
+ this.materialToolbar_.addEventListener('print', this.print_.bind(this));
+ this.materialToolbar_.addEventListener('rotate-right',
+ this.rotateClockwise_.bind(this));
+
+ document.body.addEventListener('change-page', function(e) {
+ this.viewport_.goToPage(e.detail.page);
+ }.bind(this));
+
+ this.uiManager_ =
+ new UiManager(window, this.materialToolbar_, this.zoomToolbar_);
}
+ // Set up the ZoomManager.
+ this.zoomManager_ = new ZoomManager(
+ this.viewport_, this.browserApi_.setZoom.bind(this.browserApi_),
+ this.browserApi_.getDefaultZoom());
+ this.browserApi_.addZoomEventListener(
+ this.zoomManager_.onBrowserZoomChange.bind(this.zoomManager_));
+
+ // Setup the keyboard event listener.
+ document.onkeydown = this.handleKeyEvent_.bind(this);
+
// Parse open pdf parameters.
- var paramsParser = new OpenPDFParamsParser(this.streamDetails.originalUrl);
- this.urlParams_ = paramsParser.urlParams;
+ this.paramsParser_ =
+ new OpenPDFParamsParser(this.getNamedDestination_.bind(this));
+ this.navigator_ = new Navigator(this.browserApi_.getStreamInfo().originalUrl,
+ this.viewport_, this.paramsParser_,
+ onNavigateInCurrentTab, onNavigateInNewTab);
+ this.viewportScroller_ =
+ new ViewportScroller(this.viewport_, this.plugin_, window);
}
PDFViewer.prototype = {
@@ -146,7 +204,7 @@ PDFViewer.prototype = {
handleKeyEvent_: function(e) {
var position = this.viewport_.position;
// Certain scroll events may be sent from outside of the extension.
- var fromScriptingAPI = e.type == 'scriptingKeypress';
+ var fromScriptingAPI = e.fromScriptingAPI;
var pageUpHandler = function() {
// Go to the previous page if we are fit-to-page.
@@ -185,14 +243,16 @@ PDFViewer.prototype = {
pageDownHandler();
return;
case 37: // Left arrow key.
- // Go to the previous page if there are no horizontal scrollbars.
- if (!this.viewport_.documentHasScrollbars().x) {
- this.viewport_.goToPage(this.viewport_.getMostVisiblePage() - 1);
- // Since we do the movement of the page.
- e.preventDefault();
- } else if (fromScriptingAPI) {
- position.x -= Viewport.SCROLL_INCREMENT;
- this.viewport.position = position;
+ if (!(e.altKey || e.ctrlKey || e.metaKey || e.shiftKey)) {
+ // Go to the previous page if there are no horizontal scrollbars.
+ if (!this.viewport_.documentHasScrollbars().horizontal) {
+ this.viewport_.goToPage(this.viewport_.getMostVisiblePage() - 1);
+ // Since we do the movement of the page.
+ e.preventDefault();
+ } else if (fromScriptingAPI) {
+ position.x -= Viewport.SCROLL_INCREMENT;
+ this.viewport.position = position;
+ }
}
return;
case 38: // Up arrow key.
@@ -202,14 +262,16 @@ PDFViewer.prototype = {
}
return;
case 39: // Right arrow key.
- // Go to the next page if there are no horizontal scrollbars.
- if (!this.viewport_.documentHasScrollbars().x) {
- this.viewport_.goToPage(this.viewport_.getMostVisiblePage() + 1);
- // Since we do the movement of the page.
- e.preventDefault();
- } else if (fromScriptingAPI) {
- position.x += Viewport.SCROLL_INCREMENT;
- this.viewport.position = position;
+ if (!(e.altKey || e.ctrlKey || e.metaKey || e.shiftKey)) {
+ // Go to the next page if there are no horizontal scrollbars.
+ if (!this.viewport_.documentHasScrollbars().horizontal) {
+ this.viewport_.goToPage(this.viewport_.getMostVisiblePage() + 1);
+ // Since we do the movement of the page.
+ e.preventDefault();
+ } else if (fromScriptingAPI) {
+ position.x += Viewport.SCROLL_INCREMENT;
+ this.viewport.position = position;
+ }
}
return;
case 40: // Down arrow key.
@@ -221,41 +283,56 @@ PDFViewer.prototype = {
case 65: // a key.
if (e.ctrlKey || e.metaKey) {
this.plugin_.postMessage({
- type: 'selectAll',
+ type: 'selectAll'
});
- }
- return;
- case 83: // s key.
- if (e.ctrlKey || e.metaKey) {
- // Simulate a click on the button so that the <a download ...>
- // attribute is used.
- $('save-button-link').click();
- // Since we do the saving of the page.
+ // Since we do selection ourselves.
e.preventDefault();
}
return;
- case 80: // p key.
- if (e.ctrlKey || e.metaKey) {
- this.print_();
- // Since we do the printing of the page.
+ case 71: // g key.
+ if (this.isMaterial_ && (e.ctrlKey || e.metaKey)) {
+ this.materialToolbar_.selectPageNumber();
+ // To prevent the default "find text" behaviour in Chrome.
e.preventDefault();
}
return;
case 219: // left bracket.
- if (e.ctrlKey) {
- this.plugin_.postMessage({
- type: 'rotateCounterclockwise',
- });
- }
+ if (e.ctrlKey)
+ this.rotateCounterClockwise_();
return;
case 221: // right bracket.
- if (e.ctrlKey) {
- this.plugin_.postMessage({
- type: 'rotateClockwise',
- });
- }
+ if (e.ctrlKey)
+ this.rotateClockwise_();
return;
}
+
+ // Give print preview a chance to handle the key event.
+ if (!fromScriptingAPI && this.isPrintPreview_) {
+ this.sendScriptingMessage_({
+ type: 'sendKeyEvent',
+ keyEvent: SerializeKeyEvent(e)
+ });
+ }
+ },
+
+ /**
+ * @private
+ * Rotate the plugin clockwise.
+ */
+ rotateClockwise_: function() {
+ this.plugin_.postMessage({
+ type: 'rotateClockwise'
+ });
+ },
+
+ /**
+ * @private
+ * Rotate the plugin counter-clockwise.
+ */
+ rotateCounterClockwise_: function() {
+ this.plugin_.postMessage({
+ type: 'rotateCounterclockwise'
+ });
},
/**
@@ -264,7 +341,43 @@ PDFViewer.prototype = {
*/
print_: function() {
this.plugin_.postMessage({
- type: 'print',
+ type: 'print'
+ });
+ },
+
+ /**
+ * @private
+ * Notify the plugin to save.
+ */
+ save_: function() {
+ this.plugin_.postMessage({
+ type: 'save'
+ });
+ },
+
+ /**
+ * Fetches the page number corresponding to the given named destination from
+ * the plugin.
+ * @param {string} name The namedDestination to fetch page number from plugin.
+ */
+ getNamedDestination_: function(name) {
+ this.plugin_.postMessage({
+ type: 'getNamedDestination',
+ namedDestination: name
+ });
+ },
+
+ /**
+ * @private
+ * Sends a 'documentLoaded' message to the PDFScriptingAPI if the document has
+ * finished loading.
+ */
+ sendDocumentLoadedMessage_: function() {
+ if (this.loadState_ == LoadState.LOADING)
+ return;
+ this.sendScriptingMessage_({
+ type: 'documentLoaded',
+ load_state: this.loadState_
});
},
@@ -273,19 +386,21 @@ PDFViewer.prototype = {
* Handle open pdf parameters. This function updates the viewport as per
* the parameters mentioned in the url while opening pdf. The order is
* important as later actions can override the effects of previous actions.
+ * @param {Object} viewportPosition The initial position of the viewport to be
+ * displayed.
*/
- handleURLParams_: function() {
- if (this.urlParams_.page)
- this.viewport_.goToPage(this.urlParams_.page);
- if (this.urlParams_.position) {
+ handleURLParams_: function(viewportPosition) {
+ if (viewportPosition.page != undefined)
+ this.viewport_.goToPage(viewportPosition.page);
+ if (viewportPosition.position) {
// Make sure we don't cancel effect of page parameter.
this.viewport_.position = {
- x: this.viewport_.position.x + this.urlParams_.position.x,
- y: this.viewport_.position.y + this.urlParams_.position.y
+ x: this.viewport_.position.x + viewportPosition.position.x,
+ y: this.viewport_.position.y + viewportPosition.position.y
};
}
- if (this.urlParams_.zoom)
- this.viewport_.setZoom(this.urlParams_.zoom);
+ if (viewportPosition.zoom)
+ this.viewport_.setZoom(viewportPosition.zoom);
},
/**
@@ -295,27 +410,37 @@ PDFViewer.prototype = {
* @param {number} progress the progress as a percentage.
*/
updateProgress_: function(progress) {
- this.progressBar_.progress = progress;
+ if (this.isMaterial_)
+ this.materialToolbar_.loadProgress = progress;
+ else
+ this.progressBar_.progress = progress;
+
if (progress == -1) {
// Document load failed.
this.errorScreen_.style.visibility = 'visible';
this.sizer_.style.display = 'none';
- this.toolbar_.style.visibility = 'hidden';
+ if (!this.isMaterial_)
+ this.toolbar_.style.visibility = 'hidden';
if (this.passwordScreen_.active) {
this.passwordScreen_.deny();
this.passwordScreen_.active = false;
}
+ this.loadState_ = LoadState.FAILED;
+ this.sendDocumentLoadedMessage_();
} else if (progress == 100) {
// Document load complete.
if (this.lastViewportPosition_)
this.viewport_.position = this.lastViewportPosition_;
- this.handleURLParams_();
- this.loaded = true;
- var loadEvent = new Event('pdfload');
- window.dispatchEvent(loadEvent);
- this.sendScriptingMessage_({
- type: 'documentLoaded'
- });
+ this.paramsParser_.getViewportFromUrlParams(
+ this.browserApi_.getStreamInfo().originalUrl,
+ this.handleURLParams_.bind(this));
+ this.loadState_ = LoadState.SUCCESS;
+ this.sendDocumentLoadedMessage_();
+ while (this.delayedScriptingMessages_.length > 0)
+ this.handleScriptingMessage(this.delayedScriptingMessages_.shift());
+
+ if (this.isMaterial_)
+ this.uiManager_.hideUiAfterTimeout();
}
},
@@ -342,22 +467,24 @@ PDFViewer.prototype = {
case 'documentDimensions':
this.documentDimensions_ = message.data;
this.viewport_.setDocumentDimensions(this.documentDimensions_);
- this.toolbar_.style.visibility = 'visible';
// If we received the document dimensions, the password was good so we
// can dismiss the password screen.
if (this.passwordScreen_.active)
this.passwordScreen_.accept();
- this.pageIndicator_.initialFadeIn();
- this.toolbar_.initialFadeIn();
+ if (this.isMaterial_) {
+ this.materialToolbar_.docLength =
+ this.documentDimensions_.pageDimensions.length;
+ } else {
+ this.pageIndicator_.initialFadeIn();
+ this.toolbar_.initialFadeIn();
+ }
break;
case 'email':
var href = 'mailto:' + message.data.to + '?cc=' + message.data.cc +
'&bcc=' + message.data.bcc + '&subject=' + message.data.subject +
'&body=' + message.data.body;
- var w = window.open(href, '_blank', 'width=1,height=1');
- if (w)
- w.close();
+ window.location.href = href;
break;
case 'getAccessibilityJSONReply':
this.sendScriptingMessage_(message.data);
@@ -370,6 +497,9 @@ PDFViewer.prototype = {
else
this.passwordScreen_.deny();
break;
+ case 'getSelectedTextReply':
+ this.sendScriptingMessage_(message.data);
+ break;
case 'goToPage':
this.viewport_.goToPage(message.data.page);
break;
@@ -377,26 +507,45 @@ PDFViewer.prototype = {
this.updateProgress_(message.data.progress);
break;
case 'navigate':
- if (message.data.newTab)
- window.open(message.data.url);
+ // If in print preview, always open a new tab.
+ if (this.isPrintPreview_)
+ this.navigator_.navigate(message.data.url, true);
else
- window.location.href = message.data.url;
+ this.navigator_.navigate(message.data.url, message.data.newTab);
break;
case 'setScrollPosition':
var position = this.viewport_.position;
- if (message.data.x != undefined)
+ if (message.data.x !== undefined)
position.x = message.data.x;
- if (message.data.y != undefined)
+ if (message.data.y !== undefined)
position.y = message.data.y;
this.viewport_.position = position;
break;
case 'setTranslatedStrings':
this.passwordScreen_.text = message.data.getPasswordString;
- this.progressBar_.text = message.data.loadingString;
+ if (!this.isMaterial_) {
+ this.progressBar_.text = message.data.loadingString;
+ if (!this.isPrintPreview_)
+ this.progressBar_.style.visibility = 'visible';
+ }
this.errorScreen_.text = message.data.loadFailedString;
break;
case 'cancelStreamUrl':
- chrome.streamsPrivate.abort(this.streamDetails.streamUrl);
+ chrome.mimeHandlerPrivate.abortStream();
+ break;
+ case 'bookmarks':
+ this.bookmarks_ = message.data.bookmarks;
+ if (this.isMaterial_ && this.bookmarks_.length !== 0) {
+ $('bookmarks-container').bookmarks = this.bookmarks;
+ this.materialToolbar_.hasBookmarks = true;
+ }
+ break;
+ case 'setIsSelecting':
+ this.viewportScroller_.setEnableScrolling(message.data.isSelecting);
+ break;
+ case 'getNamedDestinationReply':
+ this.paramsParser_.onNamedDestinationReceived(
+ message.data.pageNumber);
break;
}
},
@@ -420,32 +569,15 @@ PDFViewer.prototype = {
afterZoom_: function() {
var position = this.viewport_.position;
var zoom = this.viewport_.zoom;
- if (chrome.tabs && !this.setZoomInProgress_) {
- this.setZoomInProgress_ = true;
- chrome.tabs.setZoom(zoom, this.setZoomComplete_.bind(this, zoom));
- }
+ if (this.isMaterial_)
+ this.zoomToolbar_.zoomValue = 100 * zoom;
this.plugin_.postMessage({
type: 'viewport',
zoom: zoom,
xOffset: position.x,
yOffset: position.y
});
- },
-
- /**
- * @private
- * A callback that's called after chrome.tabs.setZoom is complete. This will
- * call chrome.tabs.setZoom again if the zoom level has changed since it was
- * last called.
- * @param {number} lastZoom the zoom level that chrome.tabs.setZoom was called
- * with.
- */
- setZoomComplete_: function(lastZoom) {
- var zoom = this.viewport_.zoom;
- if (zoom != lastZoom)
- chrome.tabs.setZoom(zoom, this.setZoomComplete_.bind(this, zoom));
- else
- this.setZoomInProgress_ = false;
+ this.zoomManager_.onPdfZoomChange();
},
/**
@@ -457,35 +589,49 @@ PDFViewer.prototype = {
return;
// Update the buttons selected.
- $('fit-to-page-button').classList.remove('polymer-selected');
- $('fit-to-width-button').classList.remove('polymer-selected');
- if (this.viewport_.fittingType == Viewport.FittingType.FIT_TO_PAGE) {
- $('fit-to-page-button').classList.add('polymer-selected');
- } else if (this.viewport_.fittingType ==
- Viewport.FittingType.FIT_TO_WIDTH) {
- $('fit-to-width-button').classList.add('polymer-selected');
+ if (!this.isMaterial_) {
+ $('fit-to-page-button').classList.remove('polymer-selected');
+ $('fit-to-width-button').classList.remove('polymer-selected');
+ if (this.viewport_.fittingType == Viewport.FittingType.FIT_TO_PAGE) {
+ $('fit-to-page-button').classList.add('polymer-selected');
+ } else if (this.viewport_.fittingType ==
+ Viewport.FittingType.FIT_TO_WIDTH) {
+ $('fit-to-width-button').classList.add('polymer-selected');
+ }
}
+ // Offset the toolbar position so that it doesn't move if scrollbars appear.
var hasScrollbars = this.viewport_.documentHasScrollbars();
var scrollbarWidth = this.viewport_.scrollbarWidth;
- // Offset the toolbar position so that it doesn't move if scrollbars appear.
+ var verticalScrollbarWidth = hasScrollbars.vertical ? scrollbarWidth : 0;
+ var horizontalScrollbarWidth =
+ hasScrollbars.horizontal ? scrollbarWidth : 0;
var toolbarRight = Math.max(PDFViewer.MIN_TOOLBAR_OFFSET, scrollbarWidth);
var toolbarBottom = Math.max(PDFViewer.MIN_TOOLBAR_OFFSET, scrollbarWidth);
- if (hasScrollbars.vertical)
- toolbarRight -= scrollbarWidth;
- if (hasScrollbars.horizontal)
- toolbarBottom -= scrollbarWidth;
- this.toolbar_.style.right = toolbarRight + 'px';
- this.toolbar_.style.bottom = toolbarBottom + 'px';
+ toolbarRight -= verticalScrollbarWidth;
+ toolbarBottom -= horizontalScrollbarWidth;
+ if (!this.isMaterial_) {
+ this.toolbar_.style.right = toolbarRight + 'px';
+ this.toolbar_.style.bottom = toolbarBottom + 'px';
+ // Hide the toolbar if it doesn't fit in the viewport.
+ if (this.toolbar_.offsetLeft < 0 || this.toolbar_.offsetTop < 0)
+ this.toolbar_.style.visibility = 'hidden';
+ else
+ this.toolbar_.style.visibility = 'visible';
+ }
// Update the page indicator.
var visiblePage = this.viewport_.getMostVisiblePage();
- this.pageIndicator_.index = visiblePage;
- if (this.documentDimensions_.pageDimensions.length > 1 &&
- hasScrollbars.vertical) {
- this.pageIndicator_.style.visibility = 'visible';
+ if (this.isMaterial_) {
+ this.materialToolbar_.pageIndex = visiblePage;
} else {
- this.pageIndicator_.style.visibility = 'hidden';
+ this.pageIndicator_.index = visiblePage;
+ if (this.documentDimensions_.pageDimensions.length > 1 &&
+ hasScrollbars.vertical) {
+ this.pageIndicator_.style.visibility = 'visible';
+ } else {
+ this.pageIndicator_.style.visibility = 'hidden';
+ }
}
var visiblePageDimensions = this.viewport_.getPageScreenRect(visiblePage);
@@ -496,24 +642,60 @@ PDFViewer.prototype = {
pageY: visiblePageDimensions.y,
pageWidth: visiblePageDimensions.width,
viewportWidth: size.width,
- viewportHeight: size.height,
+ viewportHeight: size.height
});
},
/**
- * @private
* Handle a scripting message from outside the extension (typically sent by
* PDFScriptingAPI in a page containing the extension) to interact with the
* plugin.
* @param {MessageObject} message the message to handle.
*/
- handleScriptingMessage_: function(message) {
+ handleScriptingMessage: function(message) {
+ if (this.parentWindow_ != message.source) {
+ this.parentWindow_ = message.source;
+ // Ensure that we notify the embedder if the document is loaded.
+ if (this.loadState_ != LoadState.LOADING)
+ this.sendDocumentLoadedMessage_();
+ }
+
+ if (this.handlePrintPreviewScriptingMessage_(message))
+ return;
+
+ // Delay scripting messages from users of the scripting API until the
+ // document is loaded. This simplifies use of the APIs.
+ if (this.loadState_ != LoadState.SUCCESS) {
+ this.delayedScriptingMessages_.push(message);
+ return;
+ }
+
switch (message.data.type.toString()) {
case 'getAccessibilityJSON':
- case 'loadPreviewPage':
+ case 'getSelectedText':
+ case 'print':
+ case 'selectAll':
this.plugin_.postMessage(message.data);
break;
+ }
+ },
+
+ /**
+ * @private
+ * Handle scripting messages specific to print preview.
+ * @param {MessageObject} message the message to handle.
+ * @return {boolean} true if the message was handled, false otherwise.
+ */
+ handlePrintPreviewScriptingMessage_: function(message) {
+ if (!this.isPrintPreview_)
+ return false;
+
+ switch (message.data.type.toString()) {
+ case 'loadPreviewPage':
+ this.plugin_.postMessage(message.data);
+ return true;
case 'resetPrintPreviewMode':
+ this.loadState_ = LoadState.LOADING;
if (!this.inPrintPreviewMode_) {
this.inPrintPreviewMode_ = true;
this.viewport_.fitToPage();
@@ -531,7 +713,8 @@ PDFViewer.prototype = {
if (saveButton)
saveButton.parentNode.removeChild(saveButton);
- this.pageIndicator_.pageLabels = message.data.pageNumbers;
+ if (!this.isMaterial_)
+ this.pageIndicator_.pageLabels = message.data.pageNumbers;
this.plugin_.postMessage({
type: 'resetPrintPreviewMode',
@@ -542,15 +725,13 @@ PDFViewer.prototype = {
pageCount: (message.data.modifiable ?
message.data.pageNumbers.length : 0)
});
- break;
+ return true;
case 'sendKeyEvent':
- var e = document.createEvent('Event');
- e.initEvent('scriptingKeypress');
- e.keyCode = message.data.keyCode;
- this.handleKeyEvent_(e);
- break;
+ this.handleKeyEvent_(DeserializeKeyEvent(message.data.keyEvent));
+ return true;
}
+ return false;
},
/**
@@ -560,13 +741,26 @@ PDFViewer.prototype = {
* @param {Object} message the message to send.
*/
sendScriptingMessage_: function(message) {
- window.parent.postMessage(message, '*');
+ if (this.parentWindow_)
+ this.parentWindow_.postMessage(message, '*');
},
+
/**
* @type {Viewport} the viewport of the PDF viewer.
*/
get viewport() {
return this.viewport_;
+ },
+
+ /**
+ * Each bookmark is an Object containing a:
+ * - title
+ * - page (optional)
+ * - array of children (themselves bookmarks)
+ * @type {Array} the top-level bookmarks of the PDF.
+ */
+ get bookmarks() {
+ return this.bookmarks_;
}
};
diff --git a/chromium/chrome/browser/resources/pdf/pdf_extension_test.cc b/chromium/chrome/browser/resources/pdf/pdf_extension_test.cc
deleted file mode 100644
index bdfea359605..00000000000
--- a/chromium/chrome/browser/resources/pdf/pdf_extension_test.cc
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "base/base_paths.h"
-#include "base/files/file_util.h"
-#include "base/path_service.h"
-#include "chrome/browser/extensions/component_loader.h"
-#include "chrome/browser/extensions/extension_apitest.h"
-#include "chrome/browser/extensions/extension_service.h"
-#include "chrome/browser/ui/tabs/tab_strip_model.h"
-#include "chrome/common/chrome_switches.h"
-#include "chrome/common/extensions/manifest_handlers/mime_types_handler.h"
-#include "chrome/test/base/ui_test_utils.h"
-#include "content/public/test/browser_test_utils.h"
-#include "extensions/test/result_catcher.h"
-#include "grit/browser_resources.h"
-#include "net/test/embedded_test_server/embedded_test_server.h"
-
-class PDFExtensionTest : public ExtensionApiTest {
- public:
- virtual ~PDFExtensionTest() {}
-
- virtual void SetUpCommandLine(CommandLine* command_line) override {
- ExtensionApiTest::SetUpCommandLine(command_line);
- command_line->AppendSwitch(switches::kOutOfProcessPdf);
- }
-
- virtual void SetUpOnMainThread() override {
- ExtensionApiTest::SetUpOnMainThread();
- ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
- }
-
-
- virtual void TearDownOnMainThread() override {
- ASSERT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete());
- ExtensionApiTest::TearDownOnMainThread();
- }
-
- void RunTestsInFile(std::string filename, bool requiresPlugin) {
- base::FilePath pdf_plugin_src;
- PathService::Get(base::DIR_SOURCE_ROOT, &pdf_plugin_src);
- pdf_plugin_src = pdf_plugin_src.AppendASCII("pdf");
- if (requiresPlugin && !base::DirectoryExists(pdf_plugin_src)) {
- LOG(WARNING) << "Not running " << filename <<
- " because it requires the PDF plugin which is not available.";
- return;
- }
- ExtensionService* service = extensions::ExtensionSystem::Get(
- profile())->extension_service();
- service->component_loader()->Add(IDR_PDF_MANIFEST,
- base::FilePath(FILE_PATH_LITERAL("pdf")));
- const extensions::Extension* extension =
- service->extensions()->GetByID("mhjfbmdgcfjbbpaeojofohoefgiehjai");
- ASSERT_TRUE(extension);
- ASSERT_TRUE(MimeTypesHandler::GetHandler(
- extension)->CanHandleMIMEType("application/pdf"));
-
- extensions::ResultCatcher catcher;
-
- GURL url(embedded_test_server()->GetURL("/pdf/test.pdf"));
- GURL extension_url(
- "chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/index.html?" +
- url.spec());
- ui_test_utils::NavigateToURL(browser(), extension_url);
- content::WebContents* contents =
- browser()->tab_strip_model()->GetActiveWebContents();
- content::WaitForLoadStop(contents);
-
- base::FilePath test_data_dir;
- PathService::Get(base::DIR_SOURCE_ROOT, &test_data_dir);
- test_data_dir = test_data_dir.Append(
- FILE_PATH_LITERAL("chrome/test/data/pdf"));
- test_data_dir = test_data_dir.AppendASCII(filename);
-
- std::string test_js;
- ASSERT_TRUE(base::ReadFileToString(test_data_dir, &test_js));
- ASSERT_TRUE(content::ExecuteScript(contents, test_js));
-
- if (!catcher.GetNextResult())
- FAIL() << catcher.message();
- }
-};
-
-IN_PROC_BROWSER_TEST_F(PDFExtensionTest, Basic) {
- RunTestsInFile("basic_test.js", false);
-}
-
-IN_PROC_BROWSER_TEST_F(PDFExtensionTest, BasicPlugin) {
- RunTestsInFile("basic_plugin_test.js", true);
-}
-
-IN_PROC_BROWSER_TEST_F(PDFExtensionTest, Viewport) {
- RunTestsInFile("viewport_test.js", false);
-}
diff --git a/chromium/chrome/browser/resources/pdf/pdf_scripting_api.js b/chromium/chrome/browser/resources/pdf/pdf_scripting_api.js
index 564467f9c81..4a08195c83c 100644
--- a/chromium/chrome/browser/resources/pdf/pdf_scripting_api.js
+++ b/chromium/chrome/browser/resources/pdf/pdf_scripting_api.js
@@ -3,24 +3,65 @@
// found in the LICENSE file.
/**
+ * Turn a dictionary received from postMessage into a key event.
+ * @param {Object} dict A dictionary representing the key event.
+ * @return {Event} A key event.
+ */
+function DeserializeKeyEvent(dict) {
+ var e = document.createEvent('Event');
+ e.initEvent('keydown');
+ e.keyCode = dict.keyCode;
+ e.shiftKey = dict.shiftKey;
+ e.ctrlKey = dict.ctrlKey;
+ e.altKey = dict.altKey;
+ e.metaKey = dict.metaKey;
+ e.fromScriptingAPI = true;
+ return e;
+}
+
+/**
+ * Turn a key event into a dictionary which can be sent over postMessage.
+ * @param {Event} event A key event.
+ * @return {Object} A dictionary representing the key event.
+ */
+function SerializeKeyEvent(event) {
+ return {
+ keyCode: event.keyCode,
+ shiftKey: event.shiftKey,
+ ctrlKey: event.ctrlKey,
+ altKey: event.altKey,
+ metaKey: event.metaKey
+ };
+}
+
+/**
+ * An enum containing a value specifying whether the PDF is currently loading,
+ * has finished loading or failed to load.
+ */
+var LoadState = {
+ LOADING: 'loading',
+ SUCCESS: 'success',
+ FAILED: 'failed'
+};
+
+/**
* Create a new PDFScriptingAPI. This provides a scripting interface to
* the PDF viewer so that it can be customized by things like print preview.
* @param {Window} window the window of the page containing the pdf viewer.
- * @param {string} extensionUrl the url of the PDF extension.
+ * @param {Object} plugin the plugin element containing the pdf viewer.
*/
-function PDFScriptingAPI(window, extensionUrl) {
- this.extensionUrl_ = extensionUrl;
- this.messageQueue_ = [];
+function PDFScriptingAPI(window, plugin) {
+ this.loadState_ = LoadState.LOADING;
+ this.pendingScriptingMessages_ = [];
+ this.setPlugin(plugin);
+
window.addEventListener('message', function(event) {
- if (event.origin != this.extensionUrl_) {
+ if (event.origin != 'chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai') {
console.error('Received message that was not from the extension: ' +
event);
return;
}
switch (event.data.type) {
- case 'readyToReceive':
- this.setDestinationWindow(event.source);
- break;
case 'viewport':
if (this.viewportChangedCallback_)
this.viewportChangedCallback_(event.data.pageX,
@@ -30,8 +71,9 @@ function PDFScriptingAPI(window, extensionUrl) {
event.data.viewportHeight);
break;
case 'documentLoaded':
+ this.loadState_ = event.data.load_state;
if (this.loadCallback_)
- this.loadCallback_();
+ this.loadCallback_(this.loadState_ == LoadState.SUCCESS);
break;
case 'getAccessibilityJSONReply':
if (this.accessibilityCallback_) {
@@ -39,6 +81,16 @@ function PDFScriptingAPI(window, extensionUrl) {
this.accessibilityCallback_ = null;
}
break;
+ case 'getSelectedTextReply':
+ if (this.selectedTextCallback_) {
+ this.selectedTextCallback_(event.data.selectedText);
+ this.selectedTextCallback_ = null;
+ }
+ break;
+ case 'sendKeyEvent':
+ if (this.keyEventCallback_)
+ this.keyEventCallback_(DeserializeKeyEvent(event.data.keyEvent));
+ break;
}
}.bind(this), false);
}
@@ -46,31 +98,35 @@ function PDFScriptingAPI(window, extensionUrl) {
PDFScriptingAPI.prototype = {
/**
* @private
- * Send a message to the extension. If we try to send messages prior to the
- * extension being ready to receive messages (i.e. before it has finished
- * loading) we queue up the messages and flush them later.
+ * Send a message to the extension. If messages try to get sent before there
+ * is a plugin element set, then we queue them up and send them later (this
+ * can happen in print preview).
* @param {Object} message The message to send.
*/
sendMessage_: function(message) {
- if (!this.pdfWindow_) {
- this.messageQueue_.push(message);
- return;
- }
-
- this.pdfWindow_.postMessage(message, this.extensionUrl_);
+ if (this.plugin_)
+ this.plugin_.postMessage(message, '*');
+ else
+ this.pendingScriptingMessages_.push(message);
},
- /**
- * Sets the destination window containing the PDF viewer. This will be called
- * when a 'readyToReceive' message is received from the PDF viewer or it can
- * be called during tests. It then flushes any pending messages to the window.
- * @param {Window} pdfWindow the window containing the PDF viewer.
- */
- setDestinationWindow: function(pdfWindow) {
- this.pdfWindow_ = pdfWindow;
- while (this.messageQueue_.length != 0) {
- this.pdfWindow_.postMessage(this.messageQueue_.shift(),
- this.extensionUrl_);
+ /**
+ * Sets the plugin element containing the PDF viewer. The element will usually
+ * be passed into the PDFScriptingAPI constructor but may also be set later.
+ * @param {Object} plugin the plugin element containing the PDF viewer.
+ */
+ setPlugin: function(plugin) {
+ this.plugin_ = plugin;
+
+ if (this.plugin_) {
+ // Send a message to ensure the postMessage channel is initialized which
+ // allows us to receive messages.
+ this.sendMessage_({
+ type: 'initialize'
+ });
+ // Flush pending messages.
+ while (this.pendingScriptingMessages_.length > 0)
+ this.sendMessage_(this.pendingScriptingMessages_.shift());
}
},
@@ -84,21 +140,32 @@ PDFScriptingAPI.prototype = {
/**
* Sets the callback which will be run when the PDF document has finished
- * loading.
+ * loading. If the document is already loaded, it will be run immediately.
* @param {Function} callback the callback to be called.
*/
setLoadCallback: function(callback) {
this.loadCallback_ = callback;
+ if (this.loadState_ != LoadState.LOADING && this.loadCallback_)
+ this.loadCallback_(this.loadState_ == LoadState.SUCCESS);
+ },
+
+ /**
+ * Sets a callback that gets run when a key event is fired in the PDF viewer.
+ * @param {Function} callback the callback to be called with a key event.
+ */
+ setKeyEventCallback: function(callback) {
+ this.keyEventCallback_ = callback;
},
/**
* Resets the PDF viewer into print preview mode.
* @param {string} url the url of the PDF to load.
* @param {boolean} grayscale whether or not to display the PDF in grayscale.
- * @param {Array.<number>} pageNumbers an array of the page numbers.
+ * @param {Array<number>} pageNumbers an array of the page numbers.
* @param {boolean} modifiable whether or not the document is modifiable.
*/
resetPrintPreviewMode: function(url, grayscale, pageNumbers, modifiable) {
+ this.loadState_ = LoadState.LOADING;
this.sendMessage_({
type: 'resetPrintPreviewMode',
url: url,
@@ -122,7 +189,8 @@ PDFScriptingAPI.prototype = {
},
/**
- * Get accessibility JSON for the document.
+ * Get accessibility JSON for the document. May only be called after document
+ * load.
* @param {Function} callback a callback to be called with the accessibility
* json that has been retrieved.
* @param {number} [page] the 0-indexed page number to get accessibility data
@@ -145,13 +213,49 @@ PDFScriptingAPI.prototype = {
},
/**
+ * Select all the text in the document. May only be called after document
+ * load.
+ */
+ selectAll: function() {
+ this.sendMessage_({
+ type: 'selectAll'
+ });
+ },
+
+ /**
+ * Get the selected text in the document. The callback will be called with the
+ * text that is selected. May only be called after document load.
+ * @param {Function} callback a callback to be called with the selected text.
+ * @return {boolean} true if the function is successful, false if there is an
+ * outstanding request for selected text that has not been answered.
+ */
+ getSelectedText: function(callback) {
+ if (this.selectedTextCallback_)
+ return false;
+ this.selectedTextCallback_ = callback;
+ this.sendMessage_({
+ type: 'getSelectedText'
+ });
+ return true;
+ },
+
+ /**
+ * Print the document. May only be called after document load.
+ */
+ print: function() {
+ this.sendMessage_({
+ type: 'print'
+ });
+ },
+
+ /**
* Send a key event to the extension.
- * @param {number} keyCode the key code to send to the extension.
+ * @param {Event} keyEvent the key event to send to the extension.
*/
- sendKeyEvent: function(keyCode) {
+ sendKeyEvent: function(keyEvent) {
this.sendMessage_({
type: 'sendKeyEvent',
- keyCode: keyCode
+ keyEvent: SerializeKeyEvent(keyEvent)
});
},
};
@@ -165,15 +269,21 @@ PDFScriptingAPI.prototype = {
* @return {HTMLIFrameElement} the iframe element containing the PDF viewer.
*/
function PDFCreateOutOfProcessPlugin(src) {
- var EXTENSION_URL = 'chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai';
var iframe = window.document.createElement('iframe');
- iframe.setAttribute('src', EXTENSION_URL + '/index.html?' + src);
- var client = new PDFScriptingAPI(window, EXTENSION_URL);
-
+ iframe.setAttribute(
+ 'src',
+ 'chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/index.html?' + src);
+ // Prevent the frame from being tab-focusable.
+ iframe.setAttribute('tabindex', '-1');
+ var client = new PDFScriptingAPI(window);
+ iframe.onload = function() {
+ client.setPlugin(iframe.contentWindow);
+ };
// Add the functions to the iframe so that they can be called directly.
iframe.setViewportChangedCallback =
client.setViewportChangedCallback.bind(client);
iframe.setLoadCallback = client.setLoadCallback.bind(client);
+ iframe.setKeyEventCallback = client.setKeyEventCallback.bind(client);
iframe.resetPrintPreviewMode = client.resetPrintPreviewMode.bind(client);
iframe.loadPreviewPage = client.loadPreviewPage.bind(client);
iframe.sendKeyEvent = client.sendKeyEvent.bind(client);
diff --git a/chromium/chrome/browser/resources/pdf/ui_manager.js b/chromium/chrome/browser/resources/pdf/ui_manager.js
new file mode 100644
index 00000000000..2f06a414877
--- /dev/null
+++ b/chromium/chrome/browser/resources/pdf/ui_manager.js
@@ -0,0 +1,57 @@
+// Copyright 2015 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';
+
+var HIDE_TIMEOUT = 2000;
+
+/**
+ * Creates a UI Manager to handle transitioning of toolbars.
+ * @constructor
+ * @param {Object} window The window containing the UI.
+ * @param {Object} toolbar The top toolbar element.
+ * @param {Object} zoomToolbar The zoom toolbar element.
+ */
+function UiManager(window, toolbar, zoomToolbar) {
+ this.window_ = window;
+ this.toolbar_ = toolbar;
+ this.zoomToolbar_ = zoomToolbar;
+
+ this.uiTimeout_ = null;
+
+ var userInputs = ['click', 'keydown', 'mousemove', 'scroll'];
+ for (var i = 0; i < userInputs.length; i++)
+ this.window_.addEventListener(userInputs[i], this.showUi_.bind(this));
+}
+
+UiManager.prototype = {
+ /**
+ * @private
+ * Display the toolbar and any pane that was previously opened.
+ */
+ showUi_: function() {
+ this.toolbar_.show();
+ this.zoomToolbar_.show();
+
+ this.hideUiAfterTimeout();
+ },
+
+ /**
+ * @private
+ * Hide the toolbar and any pane that was previously opened.
+ */
+ hideUi_: function() {
+ this.toolbar_.hide();
+ this.zoomToolbar_.hide();
+ },
+
+ /**
+ * Hide the toolbar after the HIDE_TIMEOUT has elapsed.
+ */
+ hideUiAfterTimeout: function() {
+ if (this.uiTimeout_)
+ clearTimeout(this.uiTimeout_);
+ this.uiTimeout_ = setTimeout(this.hideUi_.bind(this), HIDE_TIMEOUT);
+ }
+};
diff --git a/chromium/chrome/browser/resources/pdf/viewport.js b/chromium/chrome/browser/resources/pdf/viewport.js
index 6f095b8b93a..47c25079faa 100644
--- a/chromium/chrome/browser/resources/pdf/viewport.js
+++ b/chromium/chrome/browser/resources/pdf/viewport.js
@@ -3,23 +3,20 @@
// found in the LICENSE file.
/**
- * Returns the area of the intersection of two rectangles.
+ * Returns the height of the intersection of two rectangles.
* @param {Object} rect1 the first rect
* @param {Object} rect2 the second rect
- * @return {number} the area of the intersection of the rects
+ * @return {number} the height of the intersection of the rects
*/
-function getIntersectionArea(rect1, rect2) {
- var xOverlap = Math.max(0,
- Math.min(rect1.x + rect1.width, rect2.x + rect2.width) -
- Math.max(rect1.x, rect2.x));
- var yOverlap = Math.max(0,
+function getIntersectionHeight(rect1, rect2) {
+ return Math.max(0,
Math.min(rect1.y + rect1.height, rect2.y + rect2.height) -
Math.max(rect1.y, rect2.y));
- return xOverlap * yOverlap;
}
/**
* Create a new viewport.
+ * @constructor
* @param {Window} window the window
* @param {Object} sizer is the element which represents the size of the
* document in the viewport
@@ -27,13 +24,15 @@ function getIntersectionArea(rect1, rect2) {
* @param {Function} beforeZoomCallback is run before a change in zoom
* @param {Function} afterZoomCallback is run after a change in zoom
* @param {number} scrollbarWidth the width of scrollbars on the page
+ * @param {number} defaultZoom The default zoom level.
*/
function Viewport(window,
sizer,
viewportChangedCallback,
beforeZoomCallback,
afterZoomCallback,
- scrollbarWidth) {
+ scrollbarWidth,
+ defaultZoom) {
this.window_ = window;
this.sizer_ = sizer;
this.viewportChangedCallback_ = viewportChangedCallback;
@@ -45,6 +44,7 @@ function Viewport(window,
this.pageDimensions_ = [];
this.scrollbarWidth_ = scrollbarWidth;
this.fittingType_ = Viewport.FittingType.NONE;
+ this.defaultZoom_ = defaultZoom;
window.addEventListener('scroll', this.updateViewport_.bind(this));
window.addEventListener('resize', this.resize_.bind(this));
@@ -71,7 +71,7 @@ Viewport.SCROLL_INCREMENT = 40;
/**
* Predefined zoom factors to be used when zooming in/out. These are in
* ascending order. This should match the list in
- * chrome/browser/chrome_page_zoom_constants.cc.
+ * components/ui/zoom/page_zoom_constants.h
*/
Viewport.ZOOM_FACTORS = [0.25, 0.333, 0.5, 0.666, 0.75, 0.9, 1,
1.1, 1.25, 1.5, 1.75, 2, 2.5, 3, 4, 5];
@@ -107,6 +107,14 @@ Viewport.prototype = {
}
var documentWidth = this.documentDimensions_.width * zoom;
var documentHeight = this.documentDimensions_.height * zoom;
+
+ // If scrollbars are required for one direction, expand the document in the
+ // other direction to take the width of the scrollbars into account when
+ // deciding whether the other direction needs scrollbars.
+ if (documentWidth > this.window_.innerWidth)
+ documentHeight += this.scrollbarWidth_;
+ else if (documentHeight > this.window_.innerHeight)
+ documentWidth += this.scrollbarWidth_;
return {
horizontal: documentWidth > this.window_.innerWidth,
vertical: documentHeight > this.window_.innerHeight
@@ -150,7 +158,7 @@ Viewport.prototype = {
*/
resize_: function() {
if (this.fittingType_ == Viewport.FittingType.FIT_TO_PAGE)
- this.fitToPage();
+ this.fitToPageInternal_(false);
else if (this.fittingType_ == Viewport.FittingType.FIT_TO_WIDTH)
this.fitToWidth();
else
@@ -237,6 +245,7 @@ Viewport.prototype = {
* @param {number} newZoom the zoom level to zoom to.
*/
setZoom: function(newZoom) {
+ this.fittingType_ = Viewport.FittingType.NONE;
newZoom = Math.max(Viewport.ZOOM_FACTOR_RANGE.min,
Math.min(newZoom, Viewport.ZOOM_FACTOR_RANGE.max));
this.mightZoom_(function() {
@@ -290,31 +299,30 @@ Viewport.prototype = {
},
/**
- * Returns the page with the most pixels in the current viewport.
+ * Returns the page with the greatest proportion of its height in the current
+ * viewport.
* @return {int} the index of the most visible page.
*/
getMostVisiblePage: function() {
var firstVisiblePage = this.getPageAtY_(this.position.y / this.zoom_);
- var mostVisiblePage = {number: 0, area: 0};
+ if (firstVisiblePage == this.pageDimensions_.length - 1)
+ return firstVisiblePage;
+
var viewportRect = {
x: this.position.x / this.zoom_,
y: this.position.y / this.zoom_,
width: this.size.width / this.zoom_,
height: this.size.height / this.zoom_
};
- for (var i = firstVisiblePage; i < this.pageDimensions_.length; i++) {
- var area = getIntersectionArea(this.pageDimensions_[i],
- viewportRect);
- // If we hit a page with 0 area overlap, we must have gone past the
- // pages visible in the viewport so we can break.
- if (area == 0)
- break;
- if (area > mostVisiblePage.area) {
- mostVisiblePage.area = area;
- mostVisiblePage.number = i;
- }
- }
- return mostVisiblePage.number;
+ var firstVisiblePageVisibility = getIntersectionHeight(
+ this.pageDimensions_[firstVisiblePage], viewportRect) /
+ this.pageDimensions_[firstVisiblePage].height;
+ var nextPageVisibility = getIntersectionHeight(
+ this.pageDimensions_[firstVisiblePage + 1], viewportRect) /
+ this.pageDimensions_[firstVisiblePage + 1].height;
+ if (nextPageVisibility > firstVisiblePageVisibility)
+ return firstVisiblePage + 1;
+ return firstVisiblePage;
},
/**
@@ -331,10 +339,11 @@ Viewport.prototype = {
// First compute the zoom without scrollbars.
var zoomWidth = this.window_.innerWidth / pageDimensions.width;
var zoom;
+ var zoomHeight;
if (widthOnly) {
zoom = zoomWidth;
} else {
- var zoomHeight = this.window_.innerHeight / pageDimensions.height;
+ zoomHeight = this.window_.innerHeight / pageDimensions.height;
zoom = Math.min(zoomWidth, zoomHeight);
}
// Check if there needs to be any scrollbars.
@@ -375,7 +384,7 @@ Viewport.prototype = {
if (widthOnly) {
zoom = zoomWidth;
} else {
- var zoomHeight = windowWithScrollbars.height / pageDimensions.height;
+ zoomHeight = windowWithScrollbars.height / pageDimensions.height;
zoom = Math.min(zoomWidth, zoomHeight);
}
return zoom;
@@ -389,23 +398,23 @@ Viewport.prototype = {
this.fittingType_ = Viewport.FittingType.FIT_TO_WIDTH;
if (!this.documentDimensions_)
return;
- // Track the last y-position to stay at the same position after zooming.
- var oldY = this.window_.pageYOffset / this.zoom_;
// When computing fit-to-width, the maximum width of a page in the
// document is used, which is equal to the size of the document width.
this.setZoomInternal_(this.computeFittingZoom_(this.documentDimensions_,
true));
var page = this.getMostVisiblePage();
- this.window_.scrollTo(0, oldY * this.zoom_);
this.updateViewport_();
}.bind(this));
},
/**
- * Zoom the viewport so that a page consumes the entire viewport. Also scrolls
- * to the top of the most visible page.
+ * @private
+ * Zoom the viewport so that a page consumes the entire viewport.
+ * @param {boolean} scrollToTopOfPage Set to true if the viewport should be
+ * scrolled to the top of the current page. Set to false if the viewport
+ * should remain at the current scroll position.
*/
- fitToPage: function() {
+ fitToPageInternal_: function(scrollToTopOfPage) {
this.mightZoom_(function() {
this.fittingType_ = Viewport.FittingType.FIT_TO_PAGE;
if (!this.documentDimensions_)
@@ -417,12 +426,21 @@ Viewport.prototype = {
height: this.pageDimensions_[page].height,
};
this.setZoomInternal_(this.computeFittingZoom_(dimensions, false));
- this.window_.scrollTo(0, this.pageDimensions_[page].y * this.zoom_);
+ if (scrollToTopOfPage)
+ this.window_.scrollTo(0, this.pageDimensions_[page].y * this.zoom_);
this.updateViewport_();
}.bind(this));
},
/**
+ * Zoom the viewport so that a page consumes the entire viewport. Also scrolls
+ * the viewport to the top of the current page.
+ */
+ fitToPage: function() {
+ this.fitToPageInternal_(true);
+ },
+
+ /**
* Zoom out to the next predefined zoom level.
*/
zoomOut: function() {
@@ -460,7 +478,7 @@ Viewport.prototype = {
*/
goToPage: function(page) {
this.mightZoom_(function() {
- if (this.pageDimensions_.length == 0)
+ if (this.pageDimensions_.length === 0)
return;
if (page < 0)
page = 0;
@@ -483,10 +501,9 @@ Viewport.prototype = {
this.documentDimensions_ = documentDimensions;
this.pageDimensions_ = this.documentDimensions_.pageDimensions;
if (initialDimensions) {
- this.setZoomInternal_(this.computeFittingZoom_(this.documentDimensions_,
- true));
- if (this.zoom_ > 1)
- this.setZoomInternal_(1);
+ this.setZoomInternal_(
+ Math.min(this.defaultZoom_,
+ this.computeFittingZoom_(this.documentDimensions_, true)));
this.window_.scrollTo(0, 0);
}
this.contentSizeChanged_();
diff --git a/chromium/chrome/browser/resources/pdf/viewport_scroller.js b/chromium/chrome/browser/resources/pdf/viewport_scroller.js
new file mode 100644
index 00000000000..f46ef4db412
--- /dev/null
+++ b/chromium/chrome/browser/resources/pdf/viewport_scroller.js
@@ -0,0 +1,135 @@
+// Copyright 2015 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';
+
+/**
+ * @private
+ * The period of time in milliseconds to wait between updating the viewport
+ * position by the scroll velocity.
+ */
+ViewportScroller.DRAG_TIMER_INTERVAL_MS_ = 100;
+
+/**
+ * @private
+ * The maximum drag scroll distance per DRAG_TIMER_INTERVAL in pixels.
+ */
+ViewportScroller.MAX_DRAG_SCROLL_DISTANCE_ = 100;
+
+/**
+ * Creates a new ViewportScroller.
+ * A ViewportScroller scrolls the page in response to drag selection with the
+ * mouse.
+ * @param {Object} viewport The viewport info of the page.
+ * @param {Object} plugin The PDF plugin element.
+ * @param {Object} window The window containing the viewer.
+ */
+function ViewportScroller(viewport, plugin, window) {
+ this.viewport_ = viewport;
+ this.plugin_ = plugin;
+ this.window_ = window;
+ this.mousemoveCallback_ = null;
+ this.timerId_ = null;
+ this.scrollVelocity_ = null;
+ this.lastFrameTime_ = 0;
+}
+
+ViewportScroller.prototype = {
+ /**
+ * @private
+ * Start scrolling the page by |scrollVelocity_| every
+ * |DRAG_TIMER_INTERVAL_MS_|.
+ */
+ startDragScrollTimer_: function() {
+ if (this.timerId_ === null) {
+ this.timerId_ =
+ this.window_.setInterval(this.dragScrollPage_.bind(this),
+ ViewportScroller.DRAG_TIMER_INTERVAL_MS_);
+ this.lastFrameTime_ = Date.now();
+ }
+ },
+
+ /**
+ * @private
+ * Stops the drag scroll timer if it is active.
+ */
+ stopDragScrollTimer_: function() {
+ if (this.timerId_ !== null) {
+ this.window_.clearInterval(this.timerId_);
+ this.timerId_ = null;
+ this.lastFrameTime_ = 0;
+ }
+ },
+
+ /**
+ * @private
+ * Scrolls the viewport by the current scroll velocity.
+ */
+ dragScrollPage_: function() {
+ var position = this.viewport_.position;
+ var currentFrameTime = Date.now();
+ var timeAdjustment = (currentFrameTime - this.lastFrameTime_) /
+ ViewportScroller.DRAG_TIMER_INTERVAL_MS_;
+ position.y += (this.scrollVelocity_.y * timeAdjustment);
+ position.x += (this.scrollVelocity_.x * timeAdjustment);
+ this.viewport_.position = position;
+ this.lastFrameTime_ = currentFrameTime;
+ },
+
+ /**
+ * @private
+ * Calculate the velocity to scroll while dragging using the distance of the
+ * cursor outside the viewport.
+ * @param {Object} event The mousemove event.
+ * @return {Object} Object with x and y direction scroll velocity.
+ */
+ calculateVelocity_: function(event) {
+ var x = Math.min(Math.max(-event.offsetX,
+ event.offsetX - this.plugin_.offsetWidth, 0),
+ ViewportScroller.MAX_DRAG_SCROLL_DISTANCE_) *
+ Math.sign(event.offsetX);
+ var y = Math.min(Math.max(-event.offsetY,
+ event.offsetY - this.plugin_.offsetHeight, 0),
+ ViewportScroller.MAX_DRAG_SCROLL_DISTANCE_) *
+ Math.sign(event.offsetY);
+ return {
+ x: x,
+ y: y
+ };
+ },
+
+ /**
+ * @private
+ * Handles mousemove events. It updates the scroll velocity and starts and
+ * stops timer based on scroll velocity.
+ * @param {Object} event The mousemove event.
+ */
+ onMousemove_: function(event) {
+ this.scrollVelocity_ = this.calculateVelocity_(event);
+ if (!this.scrollVelocity_.x && !this.scrollVelocity_.y)
+ this.stopDragScrollTimer_();
+ else if (!this.timerId_)
+ this.startDragScrollTimer_();
+ },
+
+ /**
+ * Sets whether to scroll the viewport when the mouse is outside the
+ * viewport.
+ * @param {boolean} isSelecting Represents selection status.
+ */
+ setEnableScrolling: function(isSelecting) {
+ if (isSelecting) {
+ if (!this.mousemoveCallback_)
+ this.mousemoveCallback_ = this.onMousemove_.bind(this);
+ this.plugin_.addEventListener('mousemove', this.mousemoveCallback_,
+ false);
+ } else {
+ this.stopDragScrollTimer_();
+ if (this.mousemoveCallback_) {
+ this.plugin_.removeEventListener('mousemove', this.mousemoveCallback_,
+ false);
+ }
+ }
+ }
+};
diff --git a/chromium/chrome/browser/resources/pdf/zoom_manager.js b/chromium/chrome/browser/resources/pdf/zoom_manager.js
new file mode 100644
index 00000000000..14c57a061d8
--- /dev/null
+++ b/chromium/chrome/browser/resources/pdf/zoom_manager.js
@@ -0,0 +1,83 @@
+// Copyright 2015 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';
+
+/**
+ * A class that manages updating the browser with zoom changes.
+ */
+class ZoomManager {
+ /**
+ * Constructs a ZoomManager
+ * @param {!Viewport} viewport A Viewport for which to manage zoom.
+ * @param {Function} setBrowserZoomFunction A function that sets the browser
+ * zoom to the provided value.
+ * @param {number} defaultZoom The default browser zoom level.
+ */
+ constructor(viewport, setBrowserZoomFunction, defaultZoom) {
+ this.viewport_ = viewport;
+ this.setBrowserZoomFunction_ = setBrowserZoomFunction;
+ this.browserZoom_ = defaultZoom;
+ this.changingBrowserZoom_ = null;
+ }
+
+ /**
+ * Invoked when a browser-initiated zoom-level change occurs.
+ * @param {number} newZoom the zoom level to zoom to.
+ */
+ onBrowserZoomChange(newZoom) {
+ // If we are changing the browser zoom level, ignore any browser zoom level
+ // change events. Either, the change occurred before our update and will be
+ // overwritten, or the change being reported is the change we are making,
+ // which we have already handled.
+ if (this.changingBrowserZoom_)
+ return;
+
+ if (this.floatingPointEquals(this.browserZoom_, newZoom))
+ return;
+
+ this.browserZoom_ = newZoom;
+ this.viewport_.setZoom(newZoom);
+ }
+
+ /**
+ * Invoked when an extension-initiated zoom-level change occurs.
+ */
+ onPdfZoomChange() {
+ // If we are already changing the browser zoom level in response to a
+ // previous extension-initiated zoom-level change, ignore this zoom change.
+ // Once the browser zoom level is changed, we check whether the extension's
+ // zoom level matches the most recently sent zoom level.
+ if (this.changingBrowserZoom_)
+ return;
+
+ let zoom = this.viewport_.zoom;
+ if (this.floatingPointEquals(this.browserZoom_, zoom))
+ return;
+
+ this.changingBrowserZoom_ = this.setBrowserZoomFunction_(zoom).then(
+ function() {
+ this.browserZoom_ = zoom;
+ this.changingBrowserZoom_ = null;
+
+ // The extension's zoom level may have changed while the browser zoom
+ // change was in progress. We call back into onPdfZoomChange to ensure the
+ // browser zoom is up to date.
+ this.onPdfZoomChange();
+ }.bind(this));
+ }
+
+ /**
+ * Returns whether two numbers are approximately equal.
+ * @param {number} a The first number.
+ * @param {number} b The second number.
+ */
+ floatingPointEquals(a, b) {
+ let MIN_ZOOM_DELTA = 0.01;
+ // If the zoom level is close enough to the current zoom level, don't
+ // change it. This avoids us getting into an infinite loop of zoom changes
+ // due to floating point error.
+ return Math.abs(a - b) <= MIN_ZOOM_DELTA;
+ }
+};
diff --git a/chromium/chrome/browser/resources/plugin_metadata/OWNERS b/chromium/chrome/browser/resources/plugin_metadata/OWNERS
index e7e17284144..981dd0a7364 100644
--- a/chromium/chrome/browser/resources/plugin_metadata/OWNERS
+++ b/chromium/chrome/browser/resources/plugin_metadata/OWNERS
@@ -1,2 +1,3 @@
bauerb@chromium.org
jschuh@chromium.org
+wfh@chromium.org
diff --git a/chromium/chrome/browser/resources/plugin_metadata/plugins_chromeos.json b/chromium/chrome/browser/resources/plugin_metadata/plugins_chromeos.json
index 57e36857e75..86c4bae45ed 100644
--- a/chromium/chrome/browser/resources/plugin_metadata/plugins_chromeos.json
+++ b/chromium/chrome/browser/resources/plugin_metadata/plugins_chromeos.json
@@ -42,5 +42,18 @@
],
"name": "Chrome PDF Viewer",
"group_name_matcher": "*Chrome PDF Viewer*"
+ },
+ "chromium-pdf": {
+ "mime_types": [
+ ],
+ "versions": [
+ {
+ "version": "0",
+ "status": "up_to_date",
+ "comment": "Chrome PDF Viewer has no version information."
+ }
+ ],
+ "name": "Chromium PDF Viewer",
+ "group_name_matcher": "*Chromium PDF Viewer*"
}
}
diff --git a/chromium/chrome/browser/resources/plugin_metadata/plugins_linux.json b/chromium/chrome/browser/resources/plugin_metadata/plugins_linux.json
index 7ddd998578a..b040204ea91 100644
--- a/chromium/chrome/browser/resources/plugin_metadata/plugins_linux.json
+++ b/chromium/chrome/browser/resources/plugin_metadata/plugins_linux.json
@@ -123,5 +123,18 @@
],
"name": "Chrome PDF Viewer",
"group_name_matcher": "*Chrome PDF Viewer*"
+ },
+ "chromium-pdf": {
+ "mime_types": [
+ ],
+ "versions": [
+ {
+ "version": "0",
+ "status": "up_to_date",
+ "comment": "Chrome PDF Viewer has no version information."
+ }
+ ],
+ "name": "Chromium PDF Viewer",
+ "group_name_matcher": "*Chromium PDF Viewer*"
}
}
diff --git a/chromium/chrome/browser/resources/plugin_metadata/plugins_mac.json b/chromium/chrome/browser/resources/plugin_metadata/plugins_mac.json
index cb424a9b4a7..8e8bc7011dc 100644
--- a/chromium/chrome/browser/resources/plugin_metadata/plugins_mac.json
+++ b/chromium/chrome/browser/resources/plugin_metadata/plugins_mac.json
@@ -1,5 +1,5 @@
{
- "x-version": 6,
+ "x-version": 8,
"google-talk": {
"mime_types": [
],
@@ -99,7 +99,7 @@
{
"version": "0",
"status": "requires_authorization",
- "comment": "The Real Player plug-in for mac doesn't expose a version at all."
+ "comment": "The Real Player plugin for mac doesn't expose a version at all."
}
],
"lang": "en-US",
@@ -115,9 +115,9 @@
],
"versions": [
{
- "version": "15.0.0.189",
- "status": "up_to_date",
- "reference": "http://helpx.adobe.com/security/products/flash-player/apsb14-22.html"
+ "version": "17.0.0.134",
+ "status": "requires_authorization",
+ "reference": "https://helpx.adobe.com/security/products/flash-player/apsb15-05.html"
}
],
"lang": "en-US",
@@ -198,8 +198,9 @@
],
"versions": [
{
- "version": "7.6.6",
- "status": "requires_authorization"
+ "version": "7.7.1",
+ "status": "requires_authorization",
+ "reference": "http://support.apple.com/en-us/HT5016"
}
],
"lang": "en-US",
@@ -283,6 +284,19 @@
"name": "Chrome PDF Viewer",
"group_name_matcher": "*Chrome PDF Viewer*"
},
+ "chromium-pdf": {
+ "mime_types": [
+ ],
+ "versions": [
+ {
+ "version": "0",
+ "status": "up_to_date",
+ "comment": "Chrome PDF Viewer has no version information."
+ }
+ ],
+ "name": "Chromium PDF Viewer",
+ "group_name_matcher": "*Chromium PDF Viewer*"
+ },
"facebook-video-calling": {
"mime_types": [
"application/skypesdk-plugin"
diff --git a/chromium/chrome/browser/resources/plugin_metadata/plugins_win.json b/chromium/chrome/browser/resources/plugin_metadata/plugins_win.json
index 5d2229c0823..106f73a1843 100644
--- a/chromium/chrome/browser/resources/plugin_metadata/plugins_win.json
+++ b/chromium/chrome/browser/resources/plugin_metadata/plugins_win.json
@@ -1,5 +1,5 @@
{
- "x-version": 15,
+ "x-version": 17,
"google-talk": {
"mime_types": [
],
@@ -137,9 +137,9 @@
],
"versions": [
{
- "version": "15.0.0.189",
- "status": "up_to_date",
- "reference": "http://helpx.adobe.com/security/products/flash-player/apsb14-22.html"
+ "version": "17.0.0.134",
+ "status": "requires_authorization",
+ "reference": "https://helpx.adobe.com/security/products/flash-player/apsb15-05.html"
}
],
"lang": "en-US",
@@ -155,9 +155,9 @@
],
"versions": [
{
- "version": "12.0.4.144",
+ "version": "12.1.0.150",
"status": "requires_authorization",
- "reference": "https://www.adobe.com/support/security/bulletins/apsb13-23.html"
+ "reference": "https://helpx.adobe.com/security/products/shockwave/apsb14-10.html"
}
],
"lang": "en-US",
@@ -177,18 +177,18 @@
],
"versions": [
{
- "version": "10.1.12",
+ "version": "10.1.13",
"status": "requires_authorization",
- "reference": "https://helpx.adobe.com/security/products/reader/apsb14-20.html"
+ "reference": "https://helpx.adobe.com/security/products/reader/apsb14-28.html"
},
{
"version": "11",
"status": "out_of_date"
},
{
- "version": "11.0.9",
+ "version": "11.0.10",
"status": "requires_authorization",
- "reference": "https://helpx.adobe.com/security/products/reader/apsb14-20.html"
+ "reference": "https://helpx.adobe.com/security/products/reader/apsb14-28.html"
}
],
"lang": "en-US",
@@ -252,9 +252,9 @@
],
"versions": [
{
- "version": "7.7.5",
+ "version": "7.7.6",
"status": "requires_authorization",
- "reference": "http://support.apple.com/kb/HT6151"
+ "reference": "http://support.apple.com/kb/HT203092"
}
],
"lang": "en-US",
@@ -346,6 +346,19 @@
"name": "Chrome PDF Viewer",
"group_name_matcher": "*Chrome PDF Viewer*"
},
+ "chromium-pdf": {
+ "mime_types": [
+ ],
+ "versions": [
+ {
+ "version": "0",
+ "status": "up_to_date",
+ "comment": "Chrome PDF Viewer has no version information."
+ }
+ ],
+ "name": "Chromium PDF Viewer",
+ "group_name_matcher": "*Chromium PDF Viewer*"
+ },
"google-update": {
"mime-types": [
],
@@ -353,7 +366,7 @@
{
"version": "0",
"status": "requires_authorization",
- "comment": "Google Update plug-in is versioned but kept automatically up-to-date"
+ "comment": "Google Update plugin is versioned but kept automatically up-to-date"
}
],
"name": "Google Update",
diff --git a/chromium/chrome/browser/resources/plugins.css b/chromium/chrome/browser/resources/plugins.css
index 7b12d3da902..e90700596dd 100644
--- a/chromium/chrome/browser/resources/plugins.css
+++ b/chromium/chrome/browser/resources/plugins.css
@@ -26,8 +26,8 @@ div#header {
#header h1 {
background: -webkit-image-set(
- url('../../app/theme/default_100_percent/common/extensions_section.png') 1x,
- url('../../app/theme/default_200_percent/common/extensions_section.png') 2x)
+ url(../../app/theme/default_100_percent/common/extensions_section.png) 1x,
+ url(../../app/theme/default_200_percent/common/extensions_section.png) 2x)
0 20px no-repeat;
display: inline;
margin: 0;
@@ -38,8 +38,8 @@ div#header {
html[dir=rtl] #header h1 {
background: -webkit-image-set(
- url('../../app/theme/default_100_percent/common/extensions_section.png') 1x,
- url('../../app/theme/default_200_percent/common/extensions_section.png') 2x)
+ url(../../app/theme/default_100_percent/common/extensions_section.png) 1x,
+ url(../../app/theme/default_200_percent/common/extensions_section.png) 2x)
right no-repeat;
padding-left: 0;
padding-right: 95px;
diff --git a/chromium/chrome/browser/resources/plugins.html b/chromium/chrome/browser/resources/plugins.html
index 02204e10f81..92e92992db0 100644
--- a/chromium/chrome/browser/resources/plugins.html
+++ b/chromium/chrome/browser/resources/plugins.html
@@ -1,15 +1,16 @@
-<!DOCTYPE HTML>
-<html i18n-values="dir:textdirection;">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
<head>
<meta charset="utf-8">
<title i18n-content="pluginsTitle"></title>
+<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="plugins.css">
<if expr="chromeos">
<link rel="stylesheet"
href="chrome://resources/css/chromeos/ui_account_tweaks.css">
</if>
</head>
-<body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<body>
<div id="body-container" style="visibility:hidden">
<div id="header"><h1 i18n-content="pluginsTitle">TITLE</h1></div>
@@ -212,6 +213,6 @@
<script src="chrome://resources/js/load_time_data.js"></script>
<script src="chrome://resources/js/jstemplate_compiled.js"></script>
<script src="chrome://plugins/strings.js"></script>
-<script src="chrome://resources/js/i18n_template2.js"></script>
+<script src="chrome://resources/js/i18n_template.js"></script>
</body>
</html>
diff --git a/chromium/chrome/browser/resources/policy.html b/chromium/chrome/browser/resources/policy.html
index 0aff4e6af09..5b8c4d317cc 100644
--- a/chromium/chrome/browser/resources/policy.html
+++ b/chromium/chrome/browser/resources/policy.html
@@ -1,5 +1,5 @@
-<!DOCTYPE HTML>
-<html i18n-values="dir:textdirection">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no">
@@ -18,8 +18,7 @@
<script src="chrome://policy/uber_utils.js"></script>
</head>
-<body class="uber-frame"
- i18n-values=".style.fontFamily:fontfamily; .style.fontSize:fontsize">
+<body class="uber-frame">
<div id="filter-overlay" class="page">
<header>
<input id="filter" class="search-field-container" type="search"
@@ -66,6 +65,18 @@
<div class="label" i18n-content="labelClientId"></div>
<div class="client-id"></div>
</div>
+ <div class="status-entry" hidden>
+ <div class="label" i18n-content="labelAssetId"></div>
+ <div class="asset-id"></div>
+ </div>
+ <div class="status-entry" hidden>
+ <div class="label" i18n-content="labelLocation"></div>
+ <div class="location"></div>
+ </div>
+ <div class="status-entry" hidden>
+ <div class="label" i18n-content="labelDirectoryApiId"></div>
+ <div class="directory-api-id"></div>
+ </div>
<div class="status-entry">
<div class="label" i18n-content="labelTimeSinceLastRefresh"></div>
<div class="time-since-last-refresh"></div>
@@ -110,6 +121,6 @@
</body>
<script src="chrome://policy/strings.js"></script>
-<script src="chrome://resources/js/i18n_template2.js"></script>
+<script src="chrome://resources/js/i18n_template.js"></script>
<script src="chrome://policy/policy.js"></script>
</html>
diff --git a/chromium/chrome/browser/resources/policy.js b/chromium/chrome/browser/resources/policy.js
index fd58297e8bd..bec30a7caae 100644
--- a/chromium/chrome/browser/resources/policy.js
+++ b/chromium/chrome/browser/resources/policy.js
@@ -11,7 +11,7 @@ cr.define('policy', function() {
var isMobilePage = function() {
return document.defaultView.getComputedStyle(document.querySelector(
'.scope-column')).display == 'none';
- }
+ };
/**
* A box that shows the status of cloud policy for a device or user.
@@ -48,6 +48,25 @@ cr.define('policy', function() {
var domain = this.querySelector('.domain');
domain.textContent = status.domain;
domain.parentElement.hidden = false;
+
+ // Populate the device naming information.
+ // Populate the asset identifier.
+ var assetId = this.querySelector('.asset-id');
+ assetId.textContent = status.assetId ||
+ loadTimeData.getString('notSpecified');
+ assetId.parentElement.hidden = false;
+
+ // Populate the device location.
+ var location = this.querySelector('.location');
+ location.textContent = status.location ||
+ loadTimeData.getString('notSpecified');
+ location.parentElement.hidden = false;
+
+ // Populate the directory API ID.
+ var directoryApiId = this.querySelector('.directory-api-id');
+ directoryApiId.textContent = status.directoryApiId ||
+ loadTimeData.getString('notSpecified');
+ directoryApiId.parentElement.hidden = false;
} else {
// For user policy, set the appropriate title and populate the topmost
// status item with the username that policies apply to.
@@ -207,13 +226,13 @@ cr.define('policy', function() {
/**
* A table of policies and their values.
* @constructor
- * @extends {HTMLTableSectionElement}
+ * @extends {HTMLTableElement}
*/
var PolicyTable = cr.ui.define('tbody');
PolicyTable.prototype = {
// Set up the prototype chain.
- __proto__: HTMLTableSectionElement.prototype,
+ __proto__: HTMLTableElement.prototype,
/**
* Initialization function for the cr.ui framework.
diff --git a/chromium/chrome/browser/resources/predictors/predictors.html b/chromium/chrome/browser/resources/predictors/predictors.html
index 3b00b41c582..24e91602f15 100644
--- a/chromium/chrome/browser/resources/predictors/predictors.html
+++ b/chromium/chrome/browser/resources/predictors/predictors.html
@@ -1,10 +1,11 @@
-<!DOCTYPE HTML>
-<html i18n-values="dir:textdirection;">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
<head>
<meta charset="utf-8">
<title>Predictors</title>
<link rel="stylesheet" href="chrome://resources/css/list.css">
<link rel="stylesheet" href="chrome://resources/css/tabs.css">
+ <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="chrome://resources/css/tree.css">
<style>
diff --git a/chromium/chrome/browser/resources/prerender_url_whitelist.dat b/chromium/chrome/browser/resources/prerender_url_whitelist.dat
deleted file mode 100644
index 573541ac970..00000000000
--- a/chromium/chrome/browser/resources/prerender_url_whitelist.dat
+++ /dev/null
@@ -1 +0,0 @@
-0
diff --git a/chromium/chrome/browser/resources/print_preview/OWNERS b/chromium/chrome/browser/resources/print_preview/OWNERS
index b595d198396..193b90b2abf 100644
--- a/chromium/chrome/browser/resources/print_preview/OWNERS
+++ b/chromium/chrome/browser/resources/print_preview/OWNERS
@@ -1,4 +1,4 @@
-abodenha@chromium.org
+alekseys@chromium.org
dpapad@chromium.org
gene@chromium.org
kmadhusu@chromium.org
diff --git a/chromium/chrome/browser/resources/print_preview/cloud_print_interface.js b/chromium/chrome/browser/resources/print_preview/cloud_print_interface.js
index e918cda7d67..32ac59f93c9 100644
--- a/chromium/chrome/browser/resources/print_preview/cloud_print_interface.js
+++ b/chromium/chrome/browser/resources/print_preview/cloud_print_interface.js
@@ -52,7 +52,7 @@ cr.define('cloudprint', function() {
/**
* Currently logged in users (identified by email) mapped to the Google
* session index.
- * @type {!Object.<string, number>}
+ * @type {!Object<string, number>}
* @private
*/
this.userSessionIndex_ = {};
@@ -60,21 +60,21 @@ cr.define('cloudprint', function() {
/**
* Stores last received XSRF tokens for each user account. Sent as
* a parameter with every request.
- * @type {!Object.<string, string>}
+ * @type {!Object<string, string>}
* @private
*/
this.xsrfTokens_ = {};
/**
* Pending requests delayed until we get access token.
- * @type {!Array.<!CloudPrintRequest>}
+ * @type {!Array<!CloudPrintRequest>}
* @private
*/
this.requestQueue_ = [];
/**
* Outstanding cloud destination search requests.
- * @type {!Array.<!CloudPrintRequest>}
+ * @type {!Array<!CloudPrintRequest>}
* @private
*/
this.outstandingCloudSearchRequests_ = [];
@@ -157,7 +157,7 @@ cr.define('cloudprint', function() {
/**
* Could Print origins used to search printers.
- * @type {!Array.<!print_preview.Destination.Origin>}
+ * @type {!Array<!print_preview.Destination.Origin>}
* @const
* @private
*/
@@ -212,7 +212,7 @@ cr.define('cloudprint', function() {
* @param {string} account Account the search is sent for. It matters for
* COOKIES origin only, and can be empty (sent on behalf of the primary
* user in this case).
- * @param {!Array.<!print_preview.Destination.Origin>} origins Origins to
+ * @param {!Array<!print_preview.Destination.Origin>} origins Origins to
* search printers for.
* @private
*/
@@ -374,7 +374,7 @@ cr.define('cloudprint', function() {
* Builds request to the Google Cloud Print API.
* @param {string} method HTTP method of the request.
* @param {string} action Google Cloud Print action to perform.
- * @param {Array.<!HttpParam>} params HTTP parameters to include in the
+ * @param {Array<!HttpParam>} params HTTP parameters to include in the
* request.
* @param {!print_preview.Destination.Origin} origin Origin for destination.
* @param {?string} account Account the request is sent for. Can be
@@ -502,7 +502,7 @@ cr.define('cloudprint', function() {
/**
* Terminates search requests for requested {@code origins}.
- * @param {!Array.<print_preview.Destination.Origin>} origins Origins
+ * @param {!Array<print_preview.Destination.Origin>} origins Origins
* to terminate search requests for.
* @private
*/
diff --git a/chromium/chrome/browser/resources/print_preview/component.js b/chromium/chrome/browser/resources/print_preview/component.js
index 2238ab0119b..3c18829a22e 100644
--- a/chromium/chrome/browser/resources/print_preview/component.js
+++ b/chromium/chrome/browser/resources/print_preview/component.js
@@ -31,7 +31,7 @@ cr.define('print_preview', function() {
/**
* Child components of the component.
- * @type {!Array.<!print_preview.Component>}
+ * @type {!Array<!print_preview.Component>}
* @private
*/
this.children_ = [];
@@ -119,7 +119,7 @@ cr.define('print_preview', function() {
},
/**
- * @return {!Array.<!print_preview.Component>} Child components of this
+ * @return {!Array<!print_preview.Component>} Child components of this
* component.
*/
get children() {
diff --git a/chromium/chrome/browser/resources/print_preview/data/app_state.js b/chromium/chrome/browser/resources/print_preview/data/app_state.js
index a8ebb063211..a604e9d8e44 100644
--- a/chromium/chrome/browser/resources/print_preview/data/app_state.js
+++ b/chromium/chrome/browser/resources/print_preview/data/app_state.js
@@ -39,6 +39,8 @@ cr.define('print_preview', function() {
SELECTED_DESTINATION_ORIGIN: 'selectedDestinationOrigin',
SELECTED_DESTINATION_CAPABILITIES: 'selectedDestinationCapabilities',
SELECTED_DESTINATION_NAME: 'selectedDestinationName',
+ SELECTED_DESTINATION_EXTENSION_ID: 'selectedDestinationExtensionId',
+ SELECTED_DESTINATION_EXTENSION_NAME: 'selectedDestinationExtensionName',
IS_GCP_PROMO_DISMISSED: 'isGcpPromoDismissed',
DPI: 'dpi',
MEDIA_SIZE: 'mediaSize',
@@ -82,7 +84,7 @@ cr.define('print_preview', function() {
},
/**
- * @return {?print_preview.Destination.Origin.<string>} Origin of the
+ * @return {?print_preview.Destination.Origin<string>} Origin of the
* selected destination.
*/
get selectedDestinationOrigin() {
@@ -99,6 +101,21 @@ cr.define('print_preview', function() {
return this.state_[AppState.Field.SELECTED_DESTINATION_NAME];
},
+ /**
+ * @return {?string} Extension ID associated with the selected destination.
+ */
+ get selectedDestinationExtensionId() {
+ return this.state_[AppState.Field.SELECTED_DESTINATION_EXTENSION_ID];
+ },
+
+ /**
+ * @return {?string} Extension name associated with the selected
+ * destination.
+ */
+ get selectedDestinationExtensionName() {
+ return this.state_[AppState.Field.SELECTED_DESTINATION_EXTENSION_NAME];
+ },
+
/** @return {boolean} Whether the GCP promotion has been dismissed. */
get isGcpPromoDismissed() {
return this.state_[AppState.Field.IS_GCP_PROMO_DISMISSED];
@@ -193,6 +210,10 @@ cr.define('print_preview', function() {
this.state_[AppState.Field.SELECTED_DESTINATION_CAPABILITIES] =
dest.capabilities;
this.state_[AppState.Field.SELECTED_DESTINATION_NAME] = dest.displayName;
+ this.state_[AppState.Field.SELECTED_DESTINATION_EXTENSION_ID] =
+ dest.extensionId;
+ this.state_[AppState.Field.SELECTED_DESTINATION_EXTENSION_NAME] =
+ dest.extensionName;
this.persist_();
},
diff --git a/chromium/chrome/browser/resources/print_preview/data/destination.js b/chromium/chrome/browser/resources/print_preview/data/destination.js
index 4bbaeb12f28..edce220a5ee 100644
--- a/chromium/chrome/browser/resources/print_preview/data/destination.js
+++ b/chromium/chrome/browser/resources/print_preview/data/destination.js
@@ -11,10 +11,10 @@ cr.exportPath('print_preview');
* @typedef {{
* version: string,
* printer: {
- * vendor_capability: !Array.<{Object}>,
+ * vendor_capability: !Array<{Object}>,
* collate: ({default: (boolean|undefined)}|undefined),
* color: ({
- * option: !Array.<{
+ * option: !Array<{
* type: (string|undefined),
* vendor_id: (string|undefined),
* custom_display_name: (string|undefined),
@@ -23,10 +23,10 @@ cr.exportPath('print_preview');
* }|undefined),
* copies: ({default: (number|undefined),
* max: (number|undefined)}|undefined),
- * duplex: ({option: !Array.<{type: (string|undefined),
+ * duplex: ({option: !Array<{type: (string|undefined),
* is_default: (boolean|undefined)}>}|undefined),
* page_orientation: ({
- * option: !Array.<{type: (string|undefined),
+ * option: !Array<{type: (string|undefined),
* is_default: (boolean|undefined)}>
* }|undefined)
* }
@@ -48,12 +48,14 @@ cr.define('print_preview', function() {
* @param {boolean} isRecent Whether the destination has been used recently.
* @param {!print_preview.Destination.ConnectionStatus} connectionStatus
* Connection status of the print destination.
- * @param {{tags: (Array.<string>|undefined),
+ * @param {{tags: (Array<string>|undefined),
* isOwned: (boolean|undefined),
* account: (string|undefined),
* lastAccessTime: (number|undefined),
* isTosAccepted: (boolean|undefined),
* cloudID: (string|undefined),
+ * extensionId: (string|undefined),
+ * extensionName: (string|undefined),
* description: (string|undefined)}=} opt_params Optional parameters
* for the destination.
* @constructor
@@ -92,7 +94,7 @@ cr.define('print_preview', function() {
/**
* Tags associated with the destination.
- * @private {!Array.<string>}
+ * @private {!Array<string>}
*/
this.tags_ = (opt_params && opt_params.tags) || [];
@@ -153,11 +155,23 @@ cr.define('print_preview', function() {
* @private {string}
*/
this.cloudID_ = (opt_params && opt_params.cloudID) || '';
+
+ /**
+ * Extension ID for extension managed printers.
+ * @private {string}
+ */
+ this.extensionId_ = (opt_params && opt_params.extensionId) || '';
+
+ /**
+ * Extension name for extension managed printers.
+ * @private {string}
+ */
+ this.extensionName_ = (opt_params && opt_params.extensionName) || '';
};
/**
* Prefix of the location destination tag.
- * @type {!Array.<string>}
+ * @type {!Array<string>}
* @const
*/
Destination.LOCATION_TAG_PREFIXES = [
@@ -195,7 +209,8 @@ cr.define('print_preview', function() {
COOKIES: 'cookies',
PROFILE: 'profile',
DEVICE: 'device',
- PRIVET: 'privet'
+ PRIVET: 'privet',
+ EXTENSION: 'extension'
};
/**
@@ -280,6 +295,7 @@ cr.define('print_preview', function() {
/** @return {boolean} Whether the destination is local or cloud-based. */
get isLocal() {
return this.origin_ == Destination.Origin.LOCAL ||
+ this.origin_ == Destination.Origin.EXTENSION ||
(this.origin_ == Destination.Origin.PRIVET &&
this.connectionStatus_ !=
Destination.ConnectionStatus.UNREGISTERED);
@@ -291,6 +307,14 @@ cr.define('print_preview', function() {
},
/**
+ * @return {boolean} Whether the destination is an extension managed
+ * printer.
+ */
+ get isExtension() {
+ return this.origin_ == Destination.Origin.EXTENSION;
+ },
+
+ /**
* @return {string} The location of the destination, or an empty string if
* the location is unknown.
*/
@@ -326,10 +350,10 @@ cr.define('print_preview', function() {
this.id_ == Destination.GooglePromotedId.FEDEX) {
return this.account_;
}
- return this.location || this.description;
+ return this.location || this.extensionName || this.description;
},
- /** @return {!Array.<string>} Tags associated with the destination. */
+ /** @return {!Array<string>} Tags associated with the destination. */
get tags() {
return this.tags_.slice(0);
},
@@ -339,6 +363,22 @@ cr.define('print_preview', function() {
return this.cloudID_;
},
+ /**
+ * @return {string} Extension ID associated with the destination. Non-empty
+ * only for extension managed printers.
+ */
+ get extensionId() {
+ return this.extensionId_;
+ },
+
+ /**
+ * @return {string} Extension name associated with the destination.
+ * Non-empty only for extension managed printers.
+ */
+ get extensionName() {
+ return this.extensionName_;
+ },
+
/** @return {?print_preview.Cdd} Print capabilities of the destination. */
get capabilities() {
return this.capabilities_;
@@ -442,7 +482,7 @@ cr.define('print_preview', function() {
},
/**
- * @return {!Array.<string>} Properties (besides display name) to match
+ * @return {!Array<string>} Properties (besides display name) to match
* search queries against.
*/
get extraPropertiesToMatch() {
@@ -457,6 +497,7 @@ cr.define('print_preview', function() {
*/
matches: function(query) {
return !!this.displayName_.match(query) ||
+ !!this.extensionName_.match(query) ||
this.extraPropertiesToMatch.some(function(property) {
return property.match(query);
});
diff --git a/chromium/chrome/browser/resources/print_preview/data/destination_store.js b/chromium/chrome/browser/resources/print_preview/data/destination_store.js
index 8eb788bdd75..b261ce27879 100644
--- a/chromium/chrome/browser/resources/print_preview/data/destination_store.js
+++ b/chromium/chrome/browser/resources/print_preview/data/destination_store.js
@@ -48,14 +48,14 @@ cr.define('print_preview', function() {
/**
* Internal backing store for the data store.
- * @type {!Array.<!print_preview.Destination>}
+ * @type {!Array<!print_preview.Destination>}
* @private
*/
this.destinations_ = [];
/**
* Cache used for constant lookup of destinations by origin and id.
- * @type {Object.<string, !print_preview.Destination>}
+ * @type {Object<string, !print_preview.Destination>}
* @private
*/
this.destinationMap_ = {};
@@ -100,7 +100,7 @@ cr.define('print_preview', function() {
/**
* Maps user account to the list of origins for which destinations are
* already loaded.
- * @type {!Object.<string, Array.<print_preview.Destination.Origin>>}
+ * @type {!Object<string, Array<print_preview.Destination.Origin>>}
* @private
*/
this.loadedCloudOrigins_ = {};
@@ -154,6 +154,29 @@ cr.define('print_preview', function() {
this.privetSearchTimeout_ = null;
/**
+ * Whether a search for extension destinations is in progress.
+ * @type {boolean}
+ * @private
+ */
+ this.isExtensionDestinationSearchInProgress_ = false;
+
+ /**
+ * Whether the destination store has already loaded all extension
+ * destinations.
+ * @type {boolean}
+ * @private
+ */
+ this.hasLoadedAllExtensionDestinations_ = false;
+
+ /**
+ * ID of a timeout set at the start of an extension destination search. The
+ * timeout ends the search.
+ * @type {?number}
+ * @private
+ */
+ this.extensionSearchTimeout_ = null;
+
+ /**
* MDNS service name of destination that we are waiting to register.
* @type {?string}
* @private
@@ -198,7 +221,16 @@ cr.define('print_preview', function() {
* @const
* @private
*/
- DestinationStore.PRIVET_SEARCH_DURATION_ = 2000;
+ DestinationStore.PRIVET_SEARCH_DURATION_ = 5000;
+
+ /**
+ * Maximum amount of time spent searching for extension destinations, in
+ * milliseconds.
+ * @type {number}
+ * @const
+ * @private
+ */
+ DestinationStore.EXTENSION_SEARCH_DURATION_ = 5000;
/**
* Localizes printer capabilities.
@@ -217,10 +249,13 @@ cr.define('print_preview', function() {
'NA_LEDGER': 'Tabloid'
};
for (var i = 0, media; media = mediaSize.option[i]; i++) {
- media.custom_display_name =
- media.custom_display_name ||
- mediaDisplayNames[media.name] ||
- media.name;
+ // No need to patch capabilities with localized names provided.
+ if (!media.custom_display_name_localized) {
+ media.custom_display_name =
+ media.custom_display_name ||
+ mediaDisplayNames[media.name] ||
+ media.name;
+ }
}
}
return capabilities;
@@ -232,7 +267,7 @@ cr.define('print_preview', function() {
/**
* @param {string=} opt_account Account to filter destinations by. When
* omitted, all destinations are returned.
- * @return {!Array.<!print_preview.Destination>} List of destinations
+ * @return {!Array<!print_preview.Destination>} List of destinations
* accessible by the {@code account}.
*/
destinations: function(opt_account) {
@@ -264,7 +299,8 @@ cr.define('print_preview', function() {
*/
get isLocalDestinationSearchInProgress() {
return this.isLocalDestinationSearchInProgress_ ||
- this.isPrivetDestinationSearchInProgress_;
+ this.isPrivetDestinationSearchInProgress_ ||
+ this.isExtensionDestinationSearchInProgress_;
},
/**
@@ -291,11 +327,10 @@ cr.define('print_preview', function() {
!this.appState_.selectedDestinationOrigin) {
this.selectDefaultDestination_();
} else {
- assert(typeof this.appState_.selectedDestinationAccount == 'string');
var key = this.getDestinationKey_(
this.appState_.selectedDestinationOrigin,
this.appState_.selectedDestinationId,
- this.appState_.selectedDestinationAccount);
+ this.appState_.selectedDestinationAccount || '');
var candidate = this.destinationMap_[key];
if (candidate != null) {
this.selectDestination(candidate);
@@ -311,7 +346,7 @@ cr.define('print_preview', function() {
this.cloudPrintInterface_.printer(
this.appState_.selectedDestinationId,
this.appState_.selectedDestinationOrigin,
- this.appState_.selectedDestinationAccount);
+ this.appState_.selectedDestinationAccount || '');
} else if (this.appState_.selectedDestinationOrigin ==
print_preview.Destination.Origin.PRIVET) {
// TODO(noamsml): Resolve a specific printer instead of listing all
@@ -336,6 +371,29 @@ cr.define('print_preview', function() {
cr.dispatchSimpleEvent(
this,
DestinationStore.EventType.CACHED_SELECTED_DESTINATION_INFO_READY);
+ } else if (this.appState_.selectedDestinationOrigin ==
+ print_preview.Destination.Origin.EXTENSION) {
+ // TODO(tbarzic): Add support for requesting a single extension's
+ // printer list.
+ this.startLoadExtensionDestinations();
+
+ this.selectedDestination_ =
+ print_preview.ExtensionDestinationParser.parse({
+ extensionId: this.appState_.selectedDestinationExtensionId,
+ extensionName: this.appState_.selectedDestinationExtensionName,
+ id: this.appState_.selectedDestinationId,
+ name: this.appState_.selectedDestinationName || ''
+ });
+
+ if (this.appState_.selectedDestinationCapabilities) {
+ this.selectedDestination_.capabilities =
+ this.appState_.selectedDestinationCapabilities;
+
+ cr.dispatchSimpleEvent(
+ this,
+ DestinationStore.EventType
+ .CACHED_SELECTED_DESTINATION_INFO_READY);
+ }
} else {
this.selectDefaultDestination_();
}
@@ -434,8 +492,10 @@ cr.define('print_preview', function() {
if (destination.isPrivet) {
this.nativeLayer_.startGetPrivetDestinationCapabilities(
destination.id);
- }
- else if (destination.isLocal) {
+ } else if (destination.isExtension) {
+ this.nativeLayer_.startGetExtensionDestinationCapabilities(
+ destination.id);
+ } else if (destination.isLocal) {
this.nativeLayer_.startGetLocalDestinationCapabilities(
destination.id);
} else {
@@ -478,6 +538,8 @@ cr.define('print_preview', function() {
/** Initiates loading of privet print destinations. */
startLoadPrivetDestinations: function() {
if (!this.hasLoadedAllPrivetDestinations_) {
+ if (this.privetDestinationSearchInProgress_)
+ clearTimeout(this.privetSearchTimeout_);
this.isPrivetDestinationSearchInProgress_ = true;
this.nativeLayer_.startGetPrivetDestinations();
cr.dispatchSimpleEvent(
@@ -488,6 +550,23 @@ cr.define('print_preview', function() {
}
},
+ /** Initializes loading of extension managed print destinations. */
+ startLoadExtensionDestinations: function() {
+ if (this.hasLoadedAllExtensionDestinations_)
+ return;
+
+ if (this.isExtensionDestinationSearchInProgress_)
+ clearTimeout(this.extensionSearchTimeout_);
+
+ this.isExtensionDestinationSearchInProgress_ = true;
+ this.nativeLayer_.startGetExtensionDestinations();
+ cr.dispatchSimpleEvent(
+ this, DestinationStore.EventType.DESTINATION_SEARCH_STARTED);
+ this.extensionSearchTimeout_ = setTimeout(
+ this.endExtensionPrinterSearch_.bind(this),
+ DestinationStore.EXTENSION_SEARCH_DURATION_);
+ },
+
/**
* Initiates loading of cloud destinations.
* @param {print_preview.Destination.Origin=} opt_origin Search destinations
@@ -523,6 +602,7 @@ cr.define('print_preview', function() {
this.startLoadCloudDestinations();
this.startLoadLocalDestinations();
this.startLoadPrivetDestinations();
+ this.startLoadExtensionDestinations();
},
/**
@@ -549,7 +629,7 @@ cr.define('print_preview', function() {
/**
* Inserts multiple {@code destinations} to the data store and dispatches
* single DESTINATIONS_INSERTED event.
- * @param {!Array.<print_preview.Destination>} destinations Print
+ * @param {!Array<print_preview.Destination>} destinations Print
* destinations to insert.
* @private
*/
@@ -627,6 +707,21 @@ cr.define('print_preview', function() {
},
/**
+ * Called when loading of extension managed printers is done.
+ * @private
+ */
+ endExtensionPrinterSearch_: function() {
+ this.isExtensionDestinationSearchInProgress_ = false;
+ this.hasLoadedAllExtensionDestinations_ = true;
+ cr.dispatchSimpleEvent(
+ this, DestinationStore.EventType.DESTINATION_SEARCH_DONE);
+ // Clear initially selected (cached) extension destination if it hasn't
+ // been found among reported extension destinations.
+ if (this.isInAutoSelectMode_ && this.selectedDestination_.isExtension)
+ this.selectDefaultDestination_();
+ },
+
+ /**
* Inserts a destination into the store without dispatching any events.
* @return {boolean} Whether the inserted destination was not already in the
* store.
@@ -679,6 +774,14 @@ cr.define('print_preview', function() {
this.nativeLayer_,
print_preview.NativeLayer.EventType.PRIVET_CAPABILITIES_SET,
this.onPrivetCapabilitiesSet_.bind(this));
+ this.tracker_.add(
+ this.nativeLayer_,
+ print_preview.NativeLayer.EventType.EXTENSION_PRINTERS_ADDED,
+ this.onExtensionPrintersAdded_.bind(this));
+ this.tracker_.add(
+ this.nativeLayer_,
+ print_preview.NativeLayer.EventType.EXTENSION_CAPABILITIES_SET,
+ this.onExtensionCapabilitiesSet_.bind(this));
},
/**
@@ -710,6 +813,8 @@ cr.define('print_preview', function() {
this.selectDestination(null);
this.loadedCloudOrigins_ = {};
this.hasLoadedAllLocalDestinations_ = false;
+ this.hasLoadedAllPrivetDestinations_ = false;
+ this.hasLoadedAllExtensionDestinations_ = false;
clearTimeout(this.autoSelectTimeout_);
this.autoSelectTimeout_ = setTimeout(
@@ -885,7 +990,6 @@ cr.define('print_preview', function() {
* @private
*/
onPrivetCapabilitiesSet_: function(event) {
- var destinationId = event.printerId;
var destinations =
print_preview.PrivetDestinationParser.parse(event.printer);
destinations.forEach(function(dest) {
@@ -895,6 +999,43 @@ cr.define('print_preview', function() {
},
/**
+ * Called when an extension responds to a getExtensionDestinations
+ * request.
+ * @param {Object} event Contains information about list of printers
+ * reported by the extension.
+ * {@code done} parameter is set iff this is the final list of printers
+ * returned as part of getExtensionDestinations request.
+ * @private
+ */
+ onExtensionPrintersAdded_: function(event) {
+ this.insertDestinations_(event.printers.map(function(printer) {
+ return print_preview.ExtensionDestinationParser.parse(printer);
+ }));
+
+ if (event.done && this.isExtensionDestinationSearchInProgress_) {
+ clearTimeout(this.extensionSearchTimeout_);
+ this.endExtensionPrinterSearch_();
+ }
+ },
+
+ /**
+ * Called when capabilities for an extension managed printer are set.
+ * @param {Object} event Contains the printer's capabilities and ID.
+ * @private
+ */
+ onExtensionCapabilitiesSet_: function(event) {
+ var destinationKey = this.getDestinationKey_(
+ print_preview.Destination.Origin.EXTENSION,
+ event.printerId,
+ '' /* account */);
+ var destination = this.destinationMap_[destinationKey];
+ if (!destination)
+ return;
+ destination.capabilities = event.capabilities;
+ this.updateDestination_(destination);
+ },
+
+ /**
* Called from native layer after the user was requested to sign in, and did
* so successfully.
* @private
diff --git a/chromium/chrome/browser/resources/print_preview/data/invitation_store.js b/chromium/chrome/browser/resources/print_preview/data/invitation_store.js
index 80720362764..86ba438ecf1 100644
--- a/chromium/chrome/browser/resources/print_preview/data/invitation_store.js
+++ b/chromium/chrome/browser/resources/print_preview/data/invitation_store.js
@@ -22,14 +22,14 @@ cr.define('print_preview', function() {
/**
* Maps user account to the list of invitations for this account.
- * @private {!Object.<string, !Array.<!print_preview.Invitation>>}
+ * @private {!Object<string, !Array<!print_preview.Invitation>>}
*/
this.invitations_ = {};
/**
* Maps user account to the flag whether the invitations for this account
* were successfully loaded.
- * @private {!Object.<string, print_preview.InvitationStore.LoadStatus_>}
+ * @private {!Object<string, print_preview.InvitationStore.LoadStatus_>}
*/
this.loadStatus_ = {};
@@ -87,7 +87,7 @@ cr.define('print_preview', function() {
/**
* @param {string} account Account to filter invitations by.
- * @return {!Array.<!print_preview.Invitation>} List of invitations for the
+ * @return {!Array<!print_preview.Invitation>} List of invitations for the
* {@code account}.
*/
invitations: function(account) {
diff --git a/chromium/chrome/browser/resources/print_preview/data/local_parsers.js b/chromium/chrome/browser/resources/print_preview/data/local_parsers.js
index c15fb593490..d367134e04e 100644
--- a/chromium/chrome/browser/resources/print_preview/data/local_parsers.js
+++ b/chromium/chrome/browser/resources/print_preview/data/local_parsers.js
@@ -37,7 +37,7 @@ cr.define('print_preview', function() {
/**
* Parses a privet destination as one or more local printers.
* @param {!Object} destinationInfo Object that describes a privet printer.
- * @return {!Array.<!print_preview.Destination>} Parsed destination info.
+ * @return {!Array<!print_preview.Destination>} Parsed destination info.
*/
PrivetDestinationParser.parse = function(destinationInfo) {
var returnedPrinters = [];
@@ -66,9 +66,31 @@ cr.define('print_preview', function() {
return returnedPrinters;
};
+ function ExtensionDestinationParser() {}
+
+ /**
+ * Parses an extension destination from an extension supplied printer
+ * description.
+ * @param {!Object} destinationInfo Object describing an extension printer.
+ * @return {!print_preview.Destination} Parsed destination.
+ */
+ ExtensionDestinationParser.parse = function(destinationInfo) {
+ return new print_preview.Destination(
+ destinationInfo.id,
+ print_preview.Destination.Type.LOCAL,
+ print_preview.Destination.Origin.EXTENSION,
+ destinationInfo.name,
+ false /* isRecent */,
+ print_preview.Destination.ConnectionStatus.ONLINE,
+ {description: destinationInfo.description || '',
+ extensionId: destinationInfo.extensionId,
+ extensionName: destinationInfo.extensionName || ''});
+ };
+
// Export
return {
LocalDestinationParser: LocalDestinationParser,
- PrivetDestinationParser: PrivetDestinationParser
+ PrivetDestinationParser: PrivetDestinationParser,
+ ExtensionDestinationParser: ExtensionDestinationParser
};
});
diff --git a/chromium/chrome/browser/resources/print_preview/data/margins.js b/chromium/chrome/browser/resources/print_preview/data/margins.js
index 2b5497151c8..c09c0d5197d 100644
--- a/chromium/chrome/browser/resources/print_preview/data/margins.js
+++ b/chromium/chrome/browser/resources/print_preview/data/margins.js
@@ -16,7 +16,7 @@ cr.define('print_preview', function() {
function Margins(top, right, bottom, left) {
/**
* Backing store for the margin values in points.
- * @type {!Object.<
+ * @type {!Object<
* !print_preview.ticket_items.CustomMargins.Orientation, number>}
* @private
*/
diff --git a/chromium/chrome/browser/resources/print_preview/data/measurement_system.js b/chromium/chrome/browser/resources/print_preview/data/measurement_system.js
index ef28f86f4d1..6acab16743f 100644
--- a/chromium/chrome/browser/resources/print_preview/data/measurement_system.js
+++ b/chromium/chrome/browser/resources/print_preview/data/measurement_system.js
@@ -24,7 +24,7 @@ cr.define('print_preview', function() {
* Parses |numberFormat| and extracts the symbols used for the thousands point
* and decimal point.
* @param {string} numberFormat The formatted version of the number 12345678.
- * @return {!Array.<string>} The extracted symbols in the order
+ * @return {!Array<string>} The extracted symbols in the order
* [thousandsSymbol, decimalSymbol]. For example,
* parseNumberFormat("123,456.78") returns [",", "."].
*/
@@ -48,7 +48,7 @@ cr.define('print_preview', function() {
/**
* Maximum resolution of local unit values.
- * @type {!Object.<!print_preview.MeasurementSystem.UnitType, number>}
+ * @type {!Object<!print_preview.MeasurementSystem.UnitType, number>}
* @private
*/
MeasurementSystem.Precision_ = {};
@@ -57,7 +57,7 @@ cr.define('print_preview', function() {
/**
* Maximum number of decimal places to keep for local unit.
- * @type {!Object.<!print_preview.MeasurementSystem.UnitType, number>}
+ * @type {!Object<!print_preview.MeasurementSystem.UnitType, number>}
* @private
*/
MeasurementSystem.DecimalPlaces_ = {};
diff --git a/chromium/chrome/browser/resources/print_preview/data/page_number_set.js b/chromium/chrome/browser/resources/print_preview/data/page_number_set.js
index 3dd7827f7a9..2b081a64ec3 100644
--- a/chromium/chrome/browser/resources/print_preview/data/page_number_set.js
+++ b/chromium/chrome/browser/resources/print_preview/data/page_number_set.js
@@ -7,14 +7,14 @@ cr.define('print_preview', function() {
/**
* An immutable ordered set of page numbers.
- * @param {!Array.<number>} pageNumberList A list of page numbers to include
+ * @param {!Array<number>} pageNumberList A list of page numbers to include
* in the set.
* @constructor
*/
function PageNumberSet(pageNumberList) {
/**
* Internal data store for the page number set.
- * @type {!Array.<number>}
+ * @type {!Array<number>}
* @private
*/
this.pageNumberSet_ = pageListToPageSet(pageNumberList);
@@ -51,7 +51,7 @@ cr.define('print_preview', function() {
return this.pageNumberSet_.indexOf(pageNumber);
},
- /** @return {!Array.<number>} Array representation of the set. */
+ /** @return {!Array<number>} Array representation of the set. */
asArray: function() {
return this.pageNumberSet_.slice(0);
},
diff --git a/chromium/chrome/browser/resources/print_preview/data/print_ticket_store.js b/chromium/chrome/browser/resources/print_preview/data/print_ticket_store.js
index 0ca75a4ac5a..66414b45a8b 100644
--- a/chromium/chrome/browser/resources/print_preview/data/print_ticket_store.js
+++ b/chromium/chrome/browser/resources/print_preview/data/print_ticket_store.js
@@ -376,7 +376,7 @@ cr.define('print_preview', function() {
if (this.appState_.hasField(
print_preview.AppState.Field.VENDOR_OPTIONS)) {
this.vendorItems_.updateValue(
- /** @type {!Object.<string, string>} */(this.appState_.getField(
+ /** @type {!Object<string, string>} */(this.appState_.getField(
print_preview.AppState.Field.VENDOR_OPTIONS)));
}
},
@@ -406,9 +406,10 @@ cr.define('print_preview', function() {
* @return {!Object} Google Cloud Print print ticket.
*/
createPrintTicket: function(destination) {
- assert(!destination.isLocal || destination.isPrivet,
+ assert(!destination.isLocal ||
+ destination.isPrivet || destination.isExtension,
'Trying to create a Google Cloud Print print ticket for a local ' +
- ' non-privet destination');
+ ' non-privet and non-extension destination');
assert(destination.capabilities,
'Trying to create a Google Cloud Print print ticket for a ' +
diff --git a/chromium/chrome/browser/resources/print_preview/data/ticket_items/color.js b/chromium/chrome/browser/resources/print_preview/data/ticket_items/color.js
index 11ddd7fc223..b1cbe3838fa 100644
--- a/chromium/chrome/browser/resources/print_preview/data/ticket_items/color.js
+++ b/chromium/chrome/browser/resources/print_preview/data/ticket_items/color.js
@@ -24,13 +24,13 @@ cr.define('print_preview.ticket_items', function() {
};
/**
- * @private {!Array.<string>} List of capability types considered color.
+ * @private {!Array<string>} List of capability types considered color.
* @const
*/
Color.COLOR_TYPES_ = ['STANDARD_COLOR', 'CUSTOM_COLOR'];
/**
- * @private {!Array.<string>} List of capability types considered monochrome.
+ * @private {!Array<string>} List of capability types considered monochrome.
* @const
*/
Color.MONOCHROME_TYPES_ = ['STANDARD_MONOCHROME', 'CUSTOM_MONOCHROME'];
@@ -114,9 +114,9 @@ cr.define('print_preview.ticket_items', function() {
},
/**
- * @param {!Array.<!Object.<{type: (string|undefined),
+ * @param {!Array<!Object<{type: (string|undefined),
* is_default: (boolean|undefined)}>>} options
- * @return {Object.<{type: (string|undefined),
+ * @return {Object<{type: (string|undefined),
* is_default: (boolean|undefined)}>} Default color
* option of the given list.
* @private
diff --git a/chromium/chrome/browser/resources/print_preview/data/ticket_items/custom_margins.js b/chromium/chrome/browser/resources/print_preview/data/ticket_items/custom_margins.js
index 9e4fc4f38d5..38ffc00b5fe 100644
--- a/chromium/chrome/browser/resources/print_preview/data/ticket_items/custom_margins.js
+++ b/chromium/chrome/browser/resources/print_preview/data/ticket_items/custom_margins.js
@@ -37,7 +37,7 @@ cr.define('print_preview.ticket_items', function() {
/**
* Mapping of a margin orientation to its opposite.
- * @type {!Object.<!print_preview.ticket_items.CustomMargins.Orientation,
+ * @type {!Object<!print_preview.ticket_items.CustomMargins.Orientation,
* !print_preview.ticket_items.CustomMargins.Orientation>}
* @private
*/
diff --git a/chromium/chrome/browser/resources/print_preview/data/ticket_items/page_range.js b/chromium/chrome/browser/resources/print_preview/data/ticket_items/page_range.js
index 013263bbdc5..bbb3f8229a2 100644
--- a/chromium/chrome/browser/resources/print_preview/data/ticket_items/page_range.js
+++ b/chromium/chrome/browser/resources/print_preview/data/ticket_items/page_range.js
@@ -65,7 +65,7 @@ cr.define('print_preview.ticket_items', function() {
},
/**
- * @return {!Array.<Object.<{from: number, to: number}>>} A list of page
+ * @return {!Array<Object<{from: number, to: number}>>} A list of page
* ranges.
*/
getPageRanges: function() {
@@ -73,7 +73,7 @@ cr.define('print_preview.ticket_items', function() {
},
/**
- * @return {!Array.<object.<{from: number, to: number}>>} A list of page
+ * @return {!Array<object<{from: number, to: number}>>} A list of page
* ranges suitable for use in the native layer.
* TODO(vitalybuka): this should be removed when native layer switched to
* page ranges.
diff --git a/chromium/chrome/browser/resources/print_preview/data/ticket_items/vendor_items.js b/chromium/chrome/browser/resources/print_preview/data/ticket_items/vendor_items.js
index b347ff8a2b3..e3cf19f8ace 100644
--- a/chromium/chrome/browser/resources/print_preview/data/ticket_items/vendor_items.js
+++ b/chromium/chrome/browser/resources/print_preview/data/ticket_items/vendor_items.js
@@ -37,7 +37,7 @@ cr.define('print_preview.ticket_items', function() {
/**
* Vendor ticket items store, maps item id to the item value.
- * @private {!Object.<string, string>}
+ * @private {!Object<string, string>}
*/
this.items_ = {};
};
@@ -78,14 +78,14 @@ cr.define('print_preview.ticket_items', function() {
/**
* Vendor ticket items store, maps item id to the item value.
- * @return {!Object.<string, string>}
+ * @return {!Object<string, string>}
*/
get ticketItems() {
return this.items_;
},
/**
- * @param {!Object.<string, string>} values Values to set as the values of
+ * @param {!Object<string, string>} values Values to set as the values of
* vendor ticket items. Maps vendor item id to the value.
*/
updateValue: function(values) {
diff --git a/chromium/chrome/browser/resources/print_preview/data/user_info.js b/chromium/chrome/browser/resources/print_preview/data/user_info.js
index 88967cefded..0d7ade2238d 100644
--- a/chromium/chrome/browser/resources/print_preview/data/user_info.js
+++ b/chromium/chrome/browser/resources/print_preview/data/user_info.js
@@ -24,7 +24,7 @@ cr.define('print_preview', function() {
/**
* Email addresses of the logged in users or empty array if no user is
* logged in. {@code null} if not known yet.
- * @private {?Array.<string>}
+ * @private {?Array<string>}
*/
this.users_ = null;
};
@@ -68,7 +68,7 @@ cr.define('print_preview', function() {
},
/**
- * @return {?Array.<string>} Email addresses of the logged in users or
+ * @return {?Array<string>} Email addresses of the logged in users or
* empty array if no user is logged in. {@code null} if not known yet.
*/
get users() {
@@ -78,7 +78,7 @@ cr.define('print_preview', function() {
/**
* Sets logged in user accounts info.
* @param {string} activeUser Active user account (email).
- * @param {!Array.<string>} users List of currently logged in accounts.
+ * @param {!Array<string>} users List of currently logged in accounts.
*/
setUsers: function(activeUser, users) {
this.activeUser_ = activeUser;
diff --git a/chromium/chrome/browser/resources/print_preview/native_layer.js b/chromium/chrome/browser/resources/print_preview/native_layer.js
index ca7ff32c84f..8c72a538284 100644
--- a/chromium/chrome/browser/resources/print_preview/native_layer.js
+++ b/chromium/chrome/browser/resources/print_preview/native_layer.js
@@ -36,6 +36,8 @@ cr.define('print_preview', function() {
this.onFailedToGetPrinterCapabilities_.bind(this);
global.failedToGetPrivetPrinterCapabilities =
this.onFailedToGetPrivetPrinterCapabilities_.bind(this);
+ global.failedToGetExtensionPrinterCapabilities =
+ this.onFailedToGetExtensionPrinterCapabilities_.bind(this);
global.reloadPrintersList = this.onReloadPrintersList_.bind(this);
global.printToCloud = this.onPrintToCloud_.bind(this);
global.fileSelectionCancelled =
@@ -51,16 +53,20 @@ cr.define('print_preview', function() {
this.onDidGetPreviewPageCount_.bind(this);
global.onDidPreviewPage = this.onDidPreviewPage_.bind(this);
global.updatePrintPreview = this.onUpdatePrintPreview_.bind(this);
- global.printScalingDisabledForSourcePDF =
- this.onPrintScalingDisabledForSourcePDF_.bind(this);
global.onDidGetAccessToken = this.onDidGetAccessToken_.bind(this);
global.autoCancelForTesting = this.autoCancelForTesting_.bind(this);
global.onPrivetPrinterChanged = this.onPrivetPrinterChanged_.bind(this);
global.onPrivetCapabilitiesSet =
this.onPrivetCapabilitiesSet_.bind(this);
global.onPrivetPrintFailed = this.onPrivetPrintFailed_.bind(this);
+ global.onExtensionPrintersAdded =
+ this.onExtensionPrintersAdded_.bind(this);
+ global.onExtensionCapabilitiesSet =
+ this.onExtensionCapabilitiesSet_.bind(this);
global.onEnableManipulateSettingsForTest =
this.onEnableManipulateSettingsForTest_.bind(this);
+ global.printPresetOptionsFromDocument =
+ this.onPrintPresetOptionsFromDocument_.bind(this);
};
/**
@@ -95,6 +101,11 @@ cr.define('print_preview', function() {
PRIVET_CAPABILITIES_SET:
'print_preview.NativeLayer.PRIVET_CAPABILITIES_SET',
PRIVET_PRINT_FAILED: 'print_preview.NativeLayer.PRIVET_PRINT_FAILED',
+ EXTENSION_PRINTERS_ADDED:
+ 'print_preview.NativeLayer.EXTENSION_PRINTERS_ADDED',
+ EXTENSION_CAPABILITIES_SET:
+ 'print_preview.NativeLayer.EXTENSION_CAPABILITIES_SET',
+ PRINT_PRESET_OPTIONS: 'print_preview.NativeLayer.PRINT_PRESET_OPTIONS',
};
/**
@@ -176,6 +187,24 @@ cr.define('print_preview', function() {
},
/**
+ * Requests that extension system dispatches an event requesting the list of
+ * extension managed printers.
+ */
+ startGetExtensionDestinations: function() {
+ chrome.send('getExtensionPrinters');
+ },
+
+ /**
+ * Requests an extension destination's printing capabilities. A
+ * EXTENSION_CAPABILITIES_SET event will be dispatched in response.
+ * @param {string} destinationId The ID of the destination whose
+ * capabilities are requested.
+ */
+ startGetExtensionDestinationCapabilities: function(destinationId) {
+ chrome.send('getExtensionPrinterCapabilities', [destinationId]);
+ },
+
+ /**
* Requests the destination's printing capabilities. A CAPABILITIES_SET
* event will be dispatched in response.
* @param {string} destinationId ID of the destination.
@@ -236,6 +265,7 @@ cr.define('print_preview', function() {
print_preview.Destination.GooglePromotedId.SAVE_AS_PDF,
'printWithCloudPrint': destination != null && !destination.isLocal,
'printWithPrivet': destination != null && destination.isPrivet,
+ 'printWithExtension': destination != null && destination.isExtension,
'deviceName': destination == null ? 'foo' : destination.id,
'generateDraftData': documentInfo.isModifiable,
'fitToPageEnabled': printTicketStore.fitToPage.getValue(),
@@ -318,6 +348,7 @@ cr.define('print_preview', function() {
print_preview.Destination.GooglePromotedId.SAVE_AS_PDF,
'printWithCloudPrint': !destination.isLocal,
'printWithPrivet': destination.isPrivet,
+ 'printWithExtension': destination.isExtension,
'deviceName': destination.id,
'isFirstRequest': false,
'requestID': -1,
@@ -348,7 +379,7 @@ cr.define('print_preview', function() {
};
}
- if (destination.isPrivet) {
+ if (destination.isPrivet || destination.isExtension) {
ticket['ticket'] = printTicketStore.createPrintTicket(destination);
ticket['capabilities'] = JSON.stringify(destination.capabilities);
}
@@ -397,9 +428,14 @@ cr.define('print_preview', function() {
chrome.send('manageLocalPrinters');
},
- /** Navigates the user to the Google Cloud Print management page. */
- startManageCloudDestinations: function() {
- chrome.send('manageCloudPrinters');
+ /**
+ * Navigates the user to the Google Cloud Print management page.
+ * @param {?string} user Email address of the user to open the management
+ * page for (user must be currently logged in, indeed) or {@code null}
+ * to open this page for the primary user.
+ */
+ startManageCloudDestinations: function(user) {
+ chrome.send('manageCloudPrinters', [user || '']);
},
/** Forces browser to open a new tab with the given URL address. */
@@ -508,6 +544,21 @@ cr.define('print_preview', function() {
this.dispatchEvent(getCapsFailEvent);
},
+ /**
+ * Called when native layer fails to get settings information for a
+ * requested extension destination.
+ * @param {string} destinationId Printer affected by error.
+ * @private
+ */
+ onFailedToGetExtensionPrinterCapabilities_: function(destinationId) {
+ var getCapsFailEvent = new Event(
+ NativeLayer.EventType.GET_CAPABILITIES_FAIL);
+ getCapsFailEvent.destinationId = destinationId;
+ getCapsFailEvent.destinationOrigin =
+ print_preview.Destination.Origin.EXTENSION;
+ this.dispatchEvent(getCapsFailEvent);
+ },
+
/** Reloads the printer list. */
onReloadPrintersList_: function() {
cr.dispatchSimpleEvent(this, NativeLayer.EventType.DESTINATIONS_RELOAD);
@@ -652,15 +703,18 @@ cr.define('print_preview', function() {
},
/**
- * Updates the fit to page option state based on the print scaling option of
- * source pdf. PDF's have an option to enable/disable print scaling. When we
- * find out that the print scaling option is disabled for the source pdf, we
- * uncheck the fitToPage_ to page checkbox. This function is called from C++
- * code.
+ * Updates print preset options from source PDF document.
+ * Called from PrintPreviewUI::OnSetOptionsFromDocument().
+ * @param {{disableScaling: boolean, copies: number,
+ * duplex: number}} options Specifies
+ * printing options according to source document presets.
* @private
*/
- onPrintScalingDisabledForSourcePDF_: function() {
- cr.dispatchSimpleEvent(this, NativeLayer.EventType.DISABLE_SCALING);
+ onPrintPresetOptionsFromDocument_: function(options) {
+ var printPresetOptionsEvent = new Event(
+ NativeLayer.EventType.PRINT_PRESET_OPTIONS);
+ printPresetOptionsEvent.optionsFromDocument = options;
+ this.dispatchEvent(printPresetOptionsEvent);
},
/**
@@ -712,6 +766,37 @@ cr.define('print_preview', function() {
},
/**
+ * @param {Array<!{extensionId: string,
+ * extensionName: string,
+ * id: string,
+ * name: string,
+ * description: (string|undefined)}>} printers The list
+ * containing information about printers added by an extension.
+ * @param {boolean} done Whether this is the final list of extension
+ * managed printers.
+ */
+ onExtensionPrintersAdded_: function(printers, done) {
+ var event = new Event(NativeLayer.EventType.EXTENSION_PRINTERS_ADDED);
+ event.printers = printers;
+ event.done = done;
+ this.dispatchEvent(event);
+ },
+
+ /**
+ * Called when an extension responds to a request for an extension printer
+ * capabilities.
+ * @param {string} printerId The printer's ID.
+ * @param {!Object} capabilities The reported printer capabilities.
+ */
+ onExtensionCapabilitiesSet_: function(printerId,
+ capabilities) {
+ var event = new Event(NativeLayer.EventType.EXTENSION_CAPABILITIES_SET);
+ event.printerId = printerId;
+ event.capabilities = capabilities;
+ this.dispatchEvent(event);
+ },
+
+ /**
* Allows for onManipulateSettings to be called
* from the native layer.
* @private
diff --git a/chromium/chrome/browser/resources/print_preview/preview_generator.js b/chromium/chrome/browser/resources/print_preview/preview_generator.js
index bcbe95fe64a..597300f9fe3 100644
--- a/chromium/chrome/browser/resources/print_preview/preview_generator.js
+++ b/chromium/chrome/browser/resources/print_preview/preview_generator.js
@@ -94,7 +94,7 @@ cr.define('print_preview', function() {
/**
* Page ranges setting used used to generate the last preview.
- * @type {!Array.<object.<{from: number, to: number}>>}
+ * @type {!Array<object<{from: number, to: number}>>}
* @private
*/
this.pageRanges_ = null;
diff --git a/chromium/chrome/browser/resources/print_preview/previewarea/margin_control.css b/chromium/chrome/browser/resources/print_preview/previewarea/margin_control.css
index 7eb6a1ff019..747be8993ae 100644
--- a/chromium/chrome/browser/resources/print_preview/previewarea/margin_control.css
+++ b/chromium/chrome/browser/resources/print_preview/previewarea/margin_control.css
@@ -56,7 +56,6 @@
box-sizing: border-box;
color: white;
cursor: auto;
- font-family: arial;
font-size: 0.8em;
height: 25px;
padding: 5px 0;
diff --git a/chromium/chrome/browser/resources/print_preview/previewarea/margin_control_container.js b/chromium/chrome/browser/resources/print_preview/previewarea/margin_control_container.js
index ceab20b7c7f..5615cddb93c 100644
--- a/chromium/chrome/browser/resources/print_preview/previewarea/margin_control_container.js
+++ b/chromium/chrome/browser/resources/print_preview/previewarea/margin_control_container.js
@@ -14,11 +14,15 @@ cr.define('print_preview', function() {
* Used to read and write custom margin values.
* @param {!print_preview.MeasurementSystem} measurementSystem Used to convert
* between the system's local units and points.
+ * @param {function(boolean)} dragChangedCallback A function which is called
+ * when dragging margins starts or stops. True is passed to the function
+ * if the margin is currently being dragged and false otherwise.
* @constructor
* @extends {print_preview.Component}
*/
function MarginControlContainer(documentInfo, marginsTypeTicketItem,
- customMarginsTicketItem, measurementSystem) {
+ customMarginsTicketItem, measurementSystem,
+ dragChangedCallback) {
print_preview.Component.call(this);
/**
@@ -48,8 +52,17 @@ cr.define('print_preview', function() {
this.measurementSystem_ = measurementSystem;
/**
+ * Called in response to dragging the margins starting or stopping. True is
+ * passed to the function if the margin is currently being dragged and false
+ * otherwise.
+ * @type {function(boolean)}
+ * @private
+ */
+ this.dragChangedCallback_ = dragChangedCallback;
+
+ /**
* Convenience array that contains all of the margin controls.
- * @type {!Object.<
+ * @type {!Object<
* !print_preview.ticket_items.CustomMargins.Orientation,
* !print_preview.MarginControl>}
* @private
@@ -307,6 +320,7 @@ cr.define('print_preview', function() {
MarginControlContainer.isTopOrBottom_(control.getOrientation()) ?
MarginControlContainer.Classes_.DRAGGING_VERTICAL :
MarginControlContainer.Classes_.DRAGGING_HORIZONTAL);
+ this.dragChangedCallback_(true);
},
/**
@@ -348,6 +362,7 @@ cr.define('print_preview', function() {
this.draggedControl_.getOrientation(),
this.draggedControl_.getPositionInPts());
this.draggedControl_ = null;
+ this.dragChangedCallback_(false);
}
},
diff --git a/chromium/chrome/browser/resources/print_preview/previewarea/preview_area.css b/chromium/chrome/browser/resources/print_preview/previewarea/preview_area.css
index 78725eefdd5..ef12a1c2f9e 100644
--- a/chromium/chrome/browser/resources/print_preview/previewarea/preview_area.css
+++ b/chromium/chrome/browser/resources/print_preview/previewarea/preview_area.css
@@ -78,14 +78,3 @@
#preview-area .preview-area-open-system-dialog-button-throbber {
vertical-align: middle;
}
-
-.preview-area-overlayed {
- /**
- * The 100 ms delay here is intended to match that of
- * #preview-area .preview-area-overlay-layer.invisible
- * This is important because without this delay there will be a noticeable
- * flicker whenever the overlay is displayed for a short period of time.
- */
- transition: visibility 100ms linear 100ms;
- visibility: hidden;
-}
diff --git a/chromium/chrome/browser/resources/print_preview/previewarea/preview_area.js b/chromium/chrome/browser/resources/print_preview/previewarea/preview_area.js
index 8a33f4ec1d2..9b5aa86ec78 100644
--- a/chromium/chrome/browser/resources/print_preview/previewarea/preview_area.js
+++ b/chromium/chrome/browser/resources/print_preview/previewarea/preview_area.js
@@ -108,7 +108,8 @@ cr.define('print_preview', function() {
this.documentInfo_,
this.printTicketStore_.marginsType,
this.printTicketStore_.customMargins,
- this.printTicketStore_.measurementSystem);
+ this.printTicketStore_.measurementSystem,
+ this.onMarginDragChanged_.bind(this));
this.addChild(this.marginControlContainer_);
/**
@@ -200,7 +201,6 @@ cr.define('print_preview', function() {
OPEN_SYSTEM_DIALOG_BUTTON_THROBBER:
'preview-area-open-system-dialog-button-throbber',
OVERLAY: 'preview-area-overlay-layer',
- OVERLAYED: 'preview-area-overlayed',
MARGIN_CONTROL: 'margin-control',
PREVIEW_AREA: 'preview-area-plugin-wrapper'
};
@@ -231,7 +231,7 @@ cr.define('print_preview', function() {
/**
* Maps message IDs to the CSS class that contains them.
- * @type {Object.<print_preview.PreviewArea.MessageId_, string>}
+ * @type {Object<print_preview.PreviewArea.MessageId_, string>}
* @private
*/
PreviewArea.MessageIdClassMap_ = {};
@@ -300,11 +300,21 @@ cr.define('print_preview', function() {
// No scroll bar anywhere, or the active element is something else, like a
// button. Note: buttons have a bigger scrollHeight than clientHeight.
- this.plugin_.sendKeyEvent(e.keyCode);
+ this.plugin_.sendKeyEvent(e);
e.preventDefault();
},
/**
+ * Set a callback that gets called when a key event is received that
+ * originates in the plugin.
+ * @param {function(Event)} callback The callback to be called with a key
+ * event.
+ */
+ setPluginKeyEventCallback: function(callback) {
+ this.keyEventCallback_ = callback;
+ },
+
+ /**
* Shows a custom message on the preview area's overlay.
* @param {string} message Custom message to show.
*/
@@ -525,14 +535,12 @@ cr.define('print_preview', function() {
var marginControls = this.getElement().getElementsByClassName(
PreviewArea.Classes_.MARGIN_CONTROL);
for (var i = 0; i < marginControls.length; ++i) {
- marginControls[i].classList.toggle(PreviewArea.Classes_.OVERLAYED,
- visible);
+ marginControls[i].setAttribute('aria-hidden', visible);
}
var previewAreaControls = this.getElement().getElementsByClassName(
PreviewArea.Classes_.PREVIEW_AREA);
for (var i = 0; i < previewAreaControls.length; ++i) {
- previewAreaControls[i].classList.toggle(PreviewArea.Classes_.OVERLAYED,
- visible);
+ previewAreaControls[i].setAttribute('aria-hidden', visible);
}
if (!visible) {
@@ -563,6 +571,7 @@ cr.define('print_preview', function() {
} else {
this.plugin_ = /** @type {print_preview.PDFPlugin} */(
PDFCreateOutOfProcessPlugin(srcUrl));
+ this.plugin_.setKeyEventCallback(this.keyEventCallback_);
}
this.plugin_.setAttribute('class', 'preview-area-plugin');
@@ -793,6 +802,22 @@ cr.define('print_preview', function() {
this.marginControlContainer_.updateClippingMask(
new print_preview.Size(viewportWidth, viewportHeight));
}
+ },
+
+ /**
+ * Called when dragging margins starts or stops.
+ * @param {boolean} isDragging True if the margin is currently being dragged
+ * and false otherwise.
+ */
+ onMarginDragChanged_: function(isDragging) {
+ if (!this.plugin_)
+ return;
+
+ // When hovering over the plugin (which may be in a separate iframe)
+ // pointer events will be sent to the frame. When dragging the margins,
+ // we don't want this to happen as it can cause the margin to stop
+ // being draggable.
+ this.plugin_.style.pointerEvents = isDragging ? 'none' : 'auto';
}
};
diff --git a/chromium/chrome/browser/resources/print_preview/print_header.js b/chromium/chrome/browser/resources/print_preview/print_header.js
index cc0895643fa..33dcdb8bbbb 100644
--- a/chromium/chrome/browser/resources/print_preview/print_header.js
+++ b/chromium/chrome/browser/resources/print_preview/print_header.js
@@ -83,6 +83,8 @@ cr.define('print_preview', function() {
var summaryEl = this.getChildElement('.summary');
summaryEl.innerHTML = '';
summaryEl.textContent = message;
+ this.getChildElement('button.print').classList.toggle('loading', false);
+ this.getChildElement('button.cancel').classList.toggle('loading', false);
},
/** @override */
diff --git a/chromium/chrome/browser/resources/print_preview/print_preview.html b/chromium/chrome/browser/resources/print_preview/print_preview.html
index b697e2e4cb2..2c8e44eb417 100644
--- a/chromium/chrome/browser/resources/print_preview/print_preview.html
+++ b/chromium/chrome/browser/resources/print_preview/print_preview.html
@@ -1,5 +1,6 @@
-<!DOCTYPE html>
-<html i18n-values="dir:textdirection;" id="print-preview" class="focus-outline-visible">
+<!doctype html>
+<html id="print-preview" class="focus-outline-visible"
+ i18n-values="dir:textdirection;lang:language">
<head>
<meta charset="utf-8">
@@ -50,7 +51,7 @@
<script src="chrome://print/strings.js"></script>
</head>
-<body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<body>
<div id="navbar-container">
<header>
<h1 id="navbar-content-title" i18n-content="title"></h1>
@@ -103,7 +104,7 @@
<include src="search/destination_list_item.html">
<include src="search/fedex_tos.html">
- <script src="chrome://resources/js/i18n_template2.js"></script>
+ <script src="chrome://resources/js/i18n_template.js"></script>
</body>
</html>
diff --git a/chromium/chrome/browser/resources/print_preview/print_preview.js b/chromium/chrome/browser/resources/print_preview/print_preview.js
index 673b8d89ef1..cb2434132c1 100644
--- a/chromium/chrome/browser/resources/print_preview/print_preview.js
+++ b/chromium/chrome/browser/resources/print_preview/print_preview.js
@@ -347,8 +347,8 @@ cr.define('print_preview', function() {
this.onSettingsInvalid_.bind(this));
this.tracker.add(
this.nativeLayer_,
- print_preview.NativeLayer.EventType.DISABLE_SCALING,
- this.onDisableScaling_.bind(this));
+ print_preview.NativeLayer.EventType.PRINT_PRESET_OPTIONS,
+ this.onPrintPresetOptionsFromDocument_.bind(this));
this.tracker.add(
this.nativeLayer_,
print_preview.NativeLayer.EventType.PRIVET_PRINT_FAILED,
@@ -412,6 +412,7 @@ cr.define('print_preview', function() {
this.onCancelButtonClick_.bind(this));
this.tracker.add(window, 'keydown', this.onKeyDown_.bind(this));
+ this.previewArea_.setPluginKeyEventCallback(this.onKeyDown_.bind(this));
this.tracker.add(
this.destinationSettings_,
@@ -528,6 +529,7 @@ cr.define('print_preview', function() {
PrintPreview.PrintAttemptResult_.READY_WAITING_FOR_PREVIEW) {
if ((this.destinationStore_.selectedDestination.isLocal &&
!this.destinationStore_.selectedDestination.isPrivet &&
+ !this.destinationStore_.selectedDestination.isExtension &&
this.destinationStore_.selectedDestination.id !=
print_preview.Destination.GooglePromotedId.SAVE_AS_PDF) ||
this.uiState_ == PrintPreview.UiState_.OPENING_PDF_PREVIEW) {
@@ -870,15 +872,12 @@ cr.define('print_preview', function() {
// Escape key closes the dialog.
if (e.keyCode == 27 && !e.shiftKey && !e.ctrlKey && !e.altKey &&
!e.metaKey) {
-<if expr="toolkit_views">
- // On the toolkit_views environment, ESC key is handled by C++-side
- // instead of JS-side.
- return;
-</if>
-<if expr="not toolkit_views">
- this.close_();
-</if>
- e.preventDefault();
+ // On non-mac with toolkit-views, ESC key is handled by C++-side instead
+ // of JS-side.
+ if (cr.isMac) {
+ this.close_();
+ e.preventDefault();
+ }
return;
}
@@ -955,7 +954,7 @@ cr.define('print_preview', function() {
* @private
*/
onManageCloudDestinationsActivated_: function() {
- this.nativeLayer_.startManageCloudDestinations();
+ this.nativeLayer_.startManageCloudDestinations(this.userInfo_.activeUser);
},
/**
@@ -979,13 +978,27 @@ cr.define('print_preview', function() {
},
/**
- * Called when the native layer dispatches a DISABLE_SCALING event. Resets
- * fit-to-page selection and updates document info.
+ * Updates printing options according to source document presets.
+ * @param {Event} event Contains options from source document.
* @private
*/
- onDisableScaling_: function() {
- this.printTicketStore_.fitToPage.updateValue(null);
- this.documentInfo_.updateIsScalingDisabled(true);
+ onPrintPresetOptionsFromDocument_: function(event) {
+ if (event.optionsFromDocument.disableScaling) {
+ this.printTicketStore_.fitToPage.updateValue(null);
+ this.documentInfo_.updateIsScalingDisabled(true);
+ }
+
+ if (event.optionsFromDocument.copies > 0 &&
+ this.printTicketStore_.copies.isCapabilityAvailable()) {
+ this.printTicketStore_.copies.updateValue(
+ event.optionsFromDocument.copies);
+ }
+
+ if (event.optionsFromDocument.duplex >= 0 &&
+ this.printTicketStore_.duplex.isCapabilityAvailable()) {
+ this.printTicketStore_.duplex.updateValue(
+ event.optionsFromDocument.duplex);
+ }
},
/**
diff --git a/chromium/chrome/browser/resources/print_preview/print_preview_page.html b/chromium/chrome/browser/resources/print_preview/print_preview_page.html
deleted file mode 100644
index d7a9de8e723..00000000000
--- a/chromium/chrome/browser/resources/print_preview/print_preview_page.html
+++ /dev/null
@@ -1,117 +0,0 @@
-<!DOCTYPE html>
-<head>
-<style>
- body {
- margin: 0px;
- width: 0px;
- }
- .row {
- display: table-row;
- vertical-align: inherit;
- }
- #header, #footer {
- display: table;
- table-layout:fixed;
- width: inherit;
- }
- #header {
- vertical-align: top;
- }
- #footer {
- vertical-align: bottom;
- }
- .text {
- display: table-cell;
- font-family: sans-serif;
- font-size: 8px;
- vertical-align: inherit;
- white-space: nowrap;
- }
- #page_number {
- text-align: right;
- }
- #title {
- text-align: center;
- }
- #date, #url {
- padding-left: 0.7cm;
- padding-right: 0.1cm;
- }
- #title, #page_number {
- padding-left: 0.1cm;
- padding-right: 0.7cm;
- }
- #title, #url {
- overflow: hidden;
- text-overflow: ellipsis;
- }
- #title, #date {
- padding-bottom: 0cm;
- padding-top: 0.4cm;
- }
- #page_number, #url {
- padding-bottom: 0.4cm;
- padding-top: 0cm;
- }
-</style>
-<script>
-
-function pixels(value) {
- return value + 'px';
-}
-
-function setup(options) {
- var body = document.querySelector('body');
- var header = document.querySelector('#header');
- var content = document.querySelector('#content');
- var footer = document.querySelector('#footer');
-
- body.style.width = pixels(options['width']);
- body.style.height = pixels(options['height']);
- header.style.height = pixels(options['topMargin']);
- content.style.height = pixels(options['height'] - options['topMargin'] -
- options['bottomMargin']);
- footer.style.height = pixels(options['bottomMargin']);
-
- document.querySelector('#date span').innerText =
- new Date(options['date']).toLocaleDateString();
- document.querySelector('#title span').innerText = options['title'];
-
- document.querySelector('#url span').innerText = options['url'];
- document.querySelector('#page_number span').innerText = options['pageNumber'];
-
- // Reduce date and page number space to give more space to title and url.
- document.querySelector('#date').style.width =
- pixels(document.querySelector('#date span').offsetWidth);
- document.querySelector('#page_number').style.width =
- pixels(document.querySelector('#page_number span').offsetWidth);
-
- // Hide text if it doesn't fit into expected margins.
- if (header.offsetHeight > options['topMargin'] + 1) {
- header.style.display = 'none';
- content.style.height = pixels(options['height'] - options['bottomMargin']);
- }
- if (footer.offsetHeight > options['bottomMargin'] + 1) {
- footer.style.display = 'none';
- }
-}
-
-</script>
-</head>
-<body>
- <div id="header">
- <div class="row">
- <div id="date" class="text"><span/></div>
- <div id="title" class="text"><span/></div>
- </div>
- </div>
- <div id="content">
- </div>
- <div id="footer">
- <div class="row">
- <div id="url" class="text"><span/></div>
- <div id="page_number" class="text"><span/></div>
- </div>
- </div>
-</body>
-</html>
diff --git a/chromium/chrome/browser/resources/print_preview/print_preview_utils.js b/chromium/chrome/browser/resources/print_preview/print_preview_utils.js
index 18740b9dd5e..49a367b51b5 100644
--- a/chromium/chrome/browser/resources/print_preview/print_preview_utils.js
+++ b/chromium/chrome/browser/resources/print_preview/print_preview_utils.js
@@ -23,8 +23,8 @@ function isPositiveInteger(value) {
/**
* Returns true if the contents of the two arrays are equal.
- * @param {Array.<{from: number, to: number}>} array1 The first array.
- * @param {Array.<{from: number, to: number}>} array2 The second array.
+ * @param {Array<{from: number, to: number}>} array1 The first array.
+ * @param {Array<{from: number, to: number}>} array2 The second array.
* @return {boolean} true if the arrays are equal.
*/
function areArraysEqual(array1, array2) {
@@ -56,8 +56,8 @@ function areRangesEqual(array1, array2) {
/**
* Removes duplicate elements from |inArray| and returns a new array.
* |inArray| is not affected. It assumes that |inArray| is already sorted.
- * @param {!Array.<number>} inArray The array to be processed.
- * @return {!Array.<number>} The array after processing.
+ * @param {!Array<number>} inArray The array to be processed.
+ * @return {!Array<number>} The array after processing.
*/
function removeDuplicates(inArray) {
var out = [];
@@ -94,7 +94,7 @@ function removeDuplicates(inArray) {
* Example: "1-4dsf, 11" is invalid regardless of |totalPageCount|.
* @param {string} pageRangeText The text to be checked.
* @param {number=} opt_totalPageCount The total number of pages.
- * @return {Array.<{from: number, to: number}>} An array of page range objects.
+ * @return {Array<{from: number, to: number}>} An array of page range objects.
*/
function pageRangeTextToPageRanges(pageRangeText, opt_totalPageCount) {
if (pageRangeText == '') {
@@ -141,7 +141,7 @@ function pageRangeTextToPageRanges(pageRangeText, opt_totalPageCount) {
* See pageRangeTextToPageRanges for details.
* @param {string} pageRangeText The text to be checked.
* @param {number} totalPageCount The total number of pages.
- * @return {Array.<number>} A list of all pages.
+ * @return {Array<number>} A list of all pages.
*/
function pageRangeTextToPageList(pageRangeText, totalPageCount) {
var pageRanges = pageRangeTextToPageRanges(pageRangeText, totalPageCount);
@@ -162,8 +162,8 @@ function pageRangeTextToPageList(pageRangeText, totalPageCount) {
}
/**
- * @param {!Array.<number>} pageList The list to be processed.
- * @return {!Array.<number>} The contents of |pageList| in ascending order and
+ * @param {!Array<number>} pageList The list to be processed.
+ * @return {!Array<number>} The contents of |pageList| in ascending order and
* without any duplicates. |pageList| is not affected.
*/
function pageListToPageSet(pageList) {
@@ -203,3 +203,32 @@ function setIsVisible(element, isVisible) {
function arrayContains(array, item) {
return array.indexOf(item) != -1;
}
+
+/**
+ * @param {!goog.array.ArrayLike<!{locale: string, value: string}>}
+ * localizedStrings An array of strings with corresponding locales.
+ * @param {string} locale Locale to look the string up for.
+ * @return {string} A string for the requested {@code locale}. An empty string
+ * if there's no string for the specified locale found.
+ */
+function getStringForLocale(localizedStrings, locale) {
+ locale = locale.toLowerCase();
+ for (var i = 0; i < localizedStrings.length; i++) {
+ if (localizedStrings[i].locale.toLowerCase() == locale)
+ return localizedStrings[i].value;
+ }
+ return '';
+}
+
+/**
+ * @param {!goog.array.ArrayLike<!{locale: string, value: string}>}
+ * localizedStrings An array of strings with corresponding locales.
+ * @return {string} A string for the current locale. An empty string if there's
+ * no string for the current locale found.
+ */
+function getStringForCurrentLocale(localizedStrings) {
+ // First try to find an exact match and then look for the language only.
+ return getStringForLocale(localizedStrings, navigator.language) ||
+ getStringForLocale(localizedStrings,
+ navigator.language.split('-')[0]);
+}
diff --git a/chromium/chrome/browser/resources/print_preview/search/destination_list.js b/chromium/chrome/browser/resources/print_preview/search/destination_list.js
index 4bb18d0ea00..861125801ef 100644
--- a/chromium/chrome/browser/resources/print_preview/search/destination_list.js
+++ b/chromium/chrome/browser/resources/print_preview/search/destination_list.js
@@ -45,14 +45,14 @@ cr.define('print_preview', function() {
/**
* Backing store for the destination list.
- * @type {!Array.<print_preview.Destination>}
+ * @type {!Array<print_preview.Destination>}
* @private
*/
this.destinations_ = [];
/**
* Set of destination ids.
- * @type {!Object.<string, boolean>}
+ * @type {!Object<string, boolean>}
* @private
*/
this.destinationIds_ = {};
@@ -80,7 +80,7 @@ cr.define('print_preview', function() {
/**
* List items representing destinations.
- * @type {!Array.<!print_preview.DestinationListItem>}
+ * @type {!Array<!print_preview.DestinationListItem>}
* @private
*/
this.listItems_ = [];
@@ -151,6 +151,14 @@ cr.define('print_preview', function() {
DestinationList.HEIGHT_OF_ITEM_);
},
+ /**
+ * @return {Element} The element that contains this one. Used for height
+ * calculations.
+ */
+ getContainerElement: function() {
+ return this.getElement().parentNode;
+ },
+
/** @param {boolean} isVisible Whether the throbber is visible. */
setIsThrobberVisible: function(isVisible) {
setIsVisible(this.getChildElement('.throbber-container'), isVisible);
@@ -198,7 +206,7 @@ cr.define('print_preview', function() {
/**
* Updates the destinations to render in the destination list.
- * @param {!Array.<print_preview.Destination>} destinations Destinations to
+ * @param {!Array<print_preview.Destination>} destinations Destinations to
* render.
*/
updateDestinations: function(destinations) {
@@ -251,7 +259,7 @@ cr.define('print_preview', function() {
/**
* Renders all destinations in the given list.
- * @param {!Array.<print_preview.Destination>} destinations List of
+ * @param {!Array<print_preview.Destination>} destinations List of
* destinations to render.
* @private
*/
@@ -290,10 +298,13 @@ cr.define('print_preview', function() {
var focusedEl = listEl.querySelector(':focus');
for (var i = 0; i < numItems; i++) {
var listItem = visibleListItems[destinations[i].id];
- if (listItem)
- this.updateListItem_(listEl, listItem, focusedEl);
- else
+ if (listItem) {
+ // Destination ID is the same, but it can be registered to a different
+ // user account, hence passing it to the item update.
+ this.updateListItem_(listEl, listItem, focusedEl, destinations[i]);
+ } else {
this.renderListItem_(listEl, destinations[i]);
+ }
}
},
@@ -301,10 +312,11 @@ cr.define('print_preview', function() {
* @param {Element} listEl List element.
* @param {!print_preview.DestinationListItem} listItem List item to update.
* @param {Element} focusedEl Currently focused element within the listEl.
+ * @param {!print_preview.Destination} destination Destination to render.
* @private
*/
- updateListItem_: function(listEl, listItem, focusedEl) {
- listItem.update(this.query_);
+ updateListItem_: function(listEl, listItem, focusedEl, destination) {
+ listItem.update(destination, this.query_);
var itemEl = listItem.getElement();
// Preserve focused inner element, if there's one.
diff --git a/chromium/chrome/browser/resources/print_preview/search/destination_list_item.css b/chromium/chrome/browser/resources/print_preview/search/destination_list_item.css
index 22663fd0b99..3501f28cf02 100644
--- a/chromium/chrome/browser/resources/print_preview/search/destination_list_item.css
+++ b/chromium/chrome/browser/resources/print_preview/search/destination_list_item.css
@@ -72,3 +72,29 @@
.destination-list-item-query-highlight {
background-color: rgba(255, 240, 120, 0.9);
}
+
+.extension-controlled-indicator {
+ display: flex;
+ flex: 1;
+ justify-content: flex-end;
+ min-width: 150px;
+}
+
+.extension-name {
+ -webkit-margin-start: 1em;
+ color: #999;
+ line-height: 24px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.extension-icon {
+ background-position: center;
+ background-repeat: no-repeat;
+ cursor: pointer;
+ flex: 0 0 auto;
+ height: 24px;
+ margin: 0 3px;
+ width: 24px;
+}
diff --git a/chromium/chrome/browser/resources/print_preview/search/destination_list_item.html b/chromium/chrome/browser/resources/print_preview/search/destination_list_item.html
index 1168e82b328..2348e0e6455 100644
--- a/chromium/chrome/browser/resources/print_preview/search/destination_list_item.html
+++ b/chromium/chrome/browser/resources/print_preview/search/destination_list_item.html
@@ -9,5 +9,9 @@
class="register-promo-button">
</button>
</span>
+ <span class="extension-controlled-indicator" hidden>
+ <span class="extension-name"></span>
+ <span class="extension-icon" role="button" tabindex="0"></span>
+ </span>
</span>
</li>
diff --git a/chromium/chrome/browser/resources/print_preview/search/destination_list_item.js b/chromium/chrome/browser/resources/print_preview/search/destination_list_item.js
index cf5358281ab..48b5b8174f7 100644
--- a/chromium/chrome/browser/resources/print_preview/search/destination_list_item.js
+++ b/chromium/chrome/browser/resources/print_preview/search/destination_list_item.js
@@ -88,9 +88,12 @@ cr.define('print_preview', function() {
/**
* Updates the list item UI state.
+ * @param {!print_preview.Destination} destination Destination data object
+ * to render.
* @param {RegExp} query Active filter query.
*/
- update: function(query) {
+ update: function(destination, query) {
+ this.destination_ = destination;
this.query_ = query;
this.updateUi_();
},
@@ -129,6 +132,34 @@ cr.define('print_preview', function() {
}
nameEl.title = textContent;
+ if (this.destination_.isExtension) {
+ var extensionNameEl = this.getChildElement('.extension-name');
+ var extensionName = this.destination_.extensionName;
+ extensionNameEl.title = this.destination_.extensionName;
+ if (this.query_) {
+ extensionNameEl.textContent = '';
+ this.addTextWithHighlight_(extensionNameEl, extensionName);
+ } else {
+ extensionNameEl.textContent = this.destination_.extensionName;
+ }
+
+ var extensionIconEl = this.getChildElement('.extension-icon');
+ extensionIconEl.style.backgroundImage = '-webkit-image-set(' +
+ 'url(chrome://extension-icon/' +
+ this.destination_.extensionId + '/24/1) 1x,' +
+ 'url(chrome://extension-icon/' +
+ this.destination_.extensionId + '/48/1) 2x)';
+ extensionIconEl.title = loadTimeData.getStringF(
+ 'extensionDestinationIconTooltip',
+ this.destination_.extensionName);
+ extensionIconEl.onclick = this.onExtensionIconClicked_.bind(this);
+ extensionIconEl.onkeydown = this.onExtensionIconKeyDown_.bind(this);
+ }
+
+ var extensionIndicatorEl =
+ this.getChildElement('.extension-controlled-indicator');
+ setIsVisible(extensionIndicatorEl, this.destination_.isExtension);
+
// Initialize the element which renders the destination's offline status.
this.getElement().classList.toggle('stale', this.destination_.isOffline);
var offlineStatusEl = this.getChildElement('.offline-status');
@@ -229,6 +260,32 @@ cr.define('print_preview', function() {
DestinationListItem.EventType.REGISTER_PROMO_CLICKED);
promoClickedEvent.destination = this.destination_;
this.eventTarget_.dispatchEvent(promoClickedEvent);
+ },
+
+ /**
+ * Handles click and 'Enter' key down events for the extension icon element.
+ * It opens extensions page with the extension associated with the
+ * destination highlighted.
+ * @param {MouseEvent|KeyboardEvent} e The event to handle.
+ * @private
+ */
+ onExtensionIconClicked_: function(e) {
+ e.stopPropagation();
+ window.open('chrome://extensions?id=' + this.destination_.extensionId);
+ },
+
+ /**
+ * Handles key down event for the extensin icon element. Keys different than
+ * 'Enter' are ignored.
+ * @param {KeyboardEvent} e The event to handle.
+ * @private
+ */
+ onExtensionIconKeyDown_: function(e) {
+ if (e.shiftKey || e.ctrlKey || e.altKey || e.metaKey)
+ return;
+ if (e.keyCode != 13 /* Enter */)
+ return;
+ this.onExtensionIconClicked_(event);
}
};
diff --git a/chromium/chrome/browser/resources/print_preview/search/destination_search.css b/chromium/chrome/browser/resources/print_preview/search/destination_search.css
index 5d014d2cdfd..ea76b84d8da 100644
--- a/chromium/chrome/browser/resources/print_preview/search/destination_search.css
+++ b/chromium/chrome/browser/resources/print_preview/search/destination_search.css
@@ -64,11 +64,14 @@
padding: 0 14px 18px;
}
+#destination-search .lists > :last-child {
+ padding-bottom: 0;
+}
+
#destination-search .invitation-container {
-webkit-animation: invitation-fadein 500ms;
- background-color: rgb(249, 237, 190);
- border-top: solid 1px rgb(245, 233, 183);
- padding: 12px;
+ -webkit-box-align: center;
+ -webkit-box-orient: vertical;
}
@-webkit-keyframes invitation-fadein {
@@ -82,7 +85,6 @@
#destination-search .invitation-text {
padding-bottom: 12px;
- text-align: center;
}
#destination-search .invitation-buttons {
@@ -102,9 +104,6 @@
#destination-search .cloudprint-promo {
-webkit-box-align: center;
-webkit-user-select: none;
- background-color: rgb(249, 237, 190);
- display: -webkit-box;
- padding: 12px;
}
#destination-search .cloudprint-promo .sign-in[is='action-link'] {
@@ -125,8 +124,8 @@
#destination-search .cloudprint-promo .close-button {
-webkit-margin-start: 12px;
background-image: -webkit-image-set(
- url('chrome://theme/IDR_CLOSE_DIALOG') 1x,
- url('chrome://theme/IDR_CLOSE_DIALOG@2x') 2x);
+ url(chrome://theme/IDR_CLOSE_DIALOG) 1x,
+ url(chrome://theme/IDR_CLOSE_DIALOG@2x) 2x);
background-repeat: no-repeat;
background-size: 14px;
height: 14px;
@@ -135,12 +134,12 @@
#destination-search .cloudprint-promo .close-button:hover {
background-image: -webkit-image-set(
- url('chrome://theme/IDR_CLOSE_DIALOG_H') 1x,
- url('chrome://theme/IDR_CLOSE_DIALOG_H@2x') 2x);
+ url(chrome://theme/IDR_CLOSE_DIALOG_H) 1x,
+ url(chrome://theme/IDR_CLOSE_DIALOG_H@2x) 2x);
}
#destination-search .cloudprint-promo .close-button:active {
background-image: -webkit-image-set(
- url('chrome://theme/IDR_CLOSE_DIALOG_P') 1x,
- url('chrome://theme/IDR_CLOSE_DIALOG_P@2x') 2x);
+ url(chrome://theme/IDR_CLOSE_DIALOG_P) 1x,
+ url(chrome://theme/IDR_CLOSE_DIALOG_P@2x) 2x);
}
diff --git a/chromium/chrome/browser/resources/print_preview/search/destination_search.html b/chromium/chrome/browser/resources/print_preview/search/destination_search.html
index 8cbc6f80841..556c61190df 100644
--- a/chromium/chrome/browser/resources/print_preview/search/destination_search.html
+++ b/chromium/chrome/browser/resources/print_preview/search/destination_search.html
@@ -12,7 +12,12 @@
<div class="local-list"></div>
<div class="cloud-list" hidden></div>
</div>
- <div class="invitation-container" hidden>
+ <div class="action-area">
+ <div class="button-strip">
+ <button class="cancel-button" i18n-content="cancel"></button>
+ </div>
+ </div>
+ <div class="invitation-container gray-bottom-bar" hidden>
<div class="invitation-text"></div>
<div class="invitation-buttons">
<button class="invitation-accept-button"></button>
@@ -21,8 +26,8 @@
<div id="invitation-process-throbber" class="throbber" hidden></div>
</div>
</div>
- <div class="cloudprint-promo" hidden>
- <img src="../images/cloud.png" class="icon">
+ <div class="cloudprint-promo gray-bottom-bar" hidden>
+ <img src="../images/cloud.png" class="icon" alt="">
<div class="promo-text"></div>
<div class="close-button"></div>
</div>
diff --git a/chromium/chrome/browser/resources/print_preview/search/destination_search.js b/chromium/chrome/browser/resources/print_preview/search/destination_search.js
index 9ebd9d49890..601022408df 100644
--- a/chromium/chrome/browser/resources/print_preview/search/destination_search.js
+++ b/chromium/chrome/browser/resources/print_preview/search/destination_search.js
@@ -123,14 +123,6 @@ cr.define('print_preview', function() {
};
/**
- * Padding at the bottom of a destination list in pixels.
- * @type {number}
- * @const
- * @private
- */
- DestinationSearch.LIST_BOTTOM_PADDING_ = 18;
-
- /**
* Number of unregistered destinations that may be promoted to the top.
* @type {number}
* @const
@@ -264,6 +256,11 @@ cr.define('print_preview', function() {
print_preview.UserInfo.EventType.USERS_CHANGED,
this.onUsersChanged_.bind(this));
+ this.tracker.add(
+ this.getChildElement('.button-strip .cancel-button'),
+ 'click',
+ this.cancel.bind(this));
+
this.tracker.add(window, 'resize', this.onWindowResize_.bind(this));
this.updateThrobbers_();
@@ -297,7 +294,8 @@ cr.define('print_preview', function() {
parseInt(elStyle.getPropertyValue('padding-bottom'), 10) -
this.getChildElement('.lists').offsetTop -
this.getChildElement('.invitation-container').offsetHeight -
- this.getChildElement('.cloudprint-promo').offsetHeight;
+ this.getChildElement('.cloudprint-promo').offsetHeight -
+ this.getChildElement('.action-area').offsetHeight;
},
/**
@@ -384,8 +382,9 @@ cr.define('print_preview', function() {
var getListsTotalHeight = function(lists, counts) {
return lists.reduce(function(sum, list, index) {
+ var container = list.getContainerElement();
return sum + list.getEstimatedHeightInPixels(counts[index]) +
- DestinationSearch.LIST_BOTTOM_PADDING_;
+ parseInt(window.getComputedStyle(container).paddingBottom, 10);
}, 0);
};
var getCounts = function(lists, count) {
@@ -482,14 +481,14 @@ cr.define('print_preview', function() {
if (invitation.asGroupManager) {
invitationText = loadTimeData.getStringF(
'groupPrinterSharingInviteText',
- invitation.sender,
- invitation.destination.displayName,
- invitation.receiver);
+ HTMLEscape(invitation.sender),
+ HTMLEscape(invitation.destination.displayName),
+ HTMLEscape(invitation.receiver));
} else {
invitationText = loadTimeData.getStringF(
'printerSharingInviteText',
- invitation.sender,
- invitation.destination.displayName);
+ HTMLEscape(invitation.sender),
+ HTMLEscape(invitation.destination.displayName));
}
this.getChildElement('.invitation-text').innerHTML = invitationText;
@@ -679,6 +678,7 @@ cr.define('print_preview', function() {
*/
onCloudprintPromoCloseButtonClick_: function() {
setIsVisible(this.getChildElement('.cloudprint-promo'), false);
+ this.reflowLists_();
},
/**
diff --git a/chromium/chrome/browser/resources/print_preview/settings/advanced_settings/advanced_settings.html b/chromium/chrome/browser/resources/print_preview/settings/advanced_settings/advanced_settings.html
index a947d0766fb..d3a72a8afb5 100644
--- a/chromium/chrome/browser/resources/print_preview/settings/advanced_settings/advanced_settings.html
+++ b/chromium/chrome/browser/resources/print_preview/settings/advanced_settings/advanced_settings.html
@@ -11,9 +11,9 @@
</div>
<div class="action-area">
<div class="button-strip">
- <button id="cancel-button" i18n-content="cancel"></button>
- <button id="done-button" i18n-content="advancedSettingsDialogConfirm"
- class="default-button"></button>
+ <button class="cancel-button" i18n-content="cancel"></button>
+ <button class="done-button default-button"
+ i18n-content="advancedSettingsDialogConfirm"></button>
</div>
</div>
</div>
diff --git a/chromium/chrome/browser/resources/print_preview/settings/advanced_settings/advanced_settings.js b/chromium/chrome/browser/resources/print_preview/settings/advanced_settings/advanced_settings.js
index 969b19dc52f..892dbc82e46 100644
--- a/chromium/chrome/browser/resources/print_preview/settings/advanced_settings/advanced_settings.js
+++ b/chromium/chrome/browser/resources/print_preview/settings/advanced_settings/advanced_settings.js
@@ -35,7 +35,7 @@ cr.define('print_preview', function() {
/** @private {print_preview.Destination} */
this.destination_ = null;
- /** @private {!Array.<!print_preview.AdvancedSettingsItem>} */
+ /** @private {!Array<!print_preview.AdvancedSettingsItem>} */
this.items_ = [];
};
@@ -70,12 +70,12 @@ cr.define('print_preview', function() {
print_preview.Overlay.prototype.enterDocument.call(this);
this.tracker.add(
- this.getChildElement('#cancel-button'),
+ this.getChildElement('.button-strip .cancel-button'),
'click',
this.cancel.bind(this));
this.tracker.add(
- this.getChildElement('#done-button'),
+ this.getChildElement('.button-strip .done-button'),
'click',
this.onApplySettings_.bind(this));
@@ -110,7 +110,7 @@ cr.define('print_preview', function() {
/** @override */
onEnterPressedInternal: function() {
- var doneButton = this.getChildElement('#done-button');
+ var doneButton = this.getChildElement('.button-strip .done-button');
if (!doneButton.disabled)
doneButton.click();
return !doneButton.disabled;
diff --git a/chromium/chrome/browser/resources/print_preview/settings/advanced_settings/advanced_settings_item.js b/chromium/chrome/browser/resources/print_preview/settings/advanced_settings/advanced_settings_item.js
index 71cff811f59..b8ae300e5e6 100644
--- a/chromium/chrome/browser/resources/print_preview/settings/advanced_settings/advanced_settings_item.js
+++ b/chromium/chrome/browser/resources/print_preview/settings/advanced_settings/advanced_settings_item.js
@@ -151,35 +151,50 @@ cr.define('print_preview', function() {
if (this.query_) {
var optionMatches = (this.selectedValue_ || '').match(this.query_);
// Even if there's no match anymore, keep the item visible to do not
- // surprise user.
- if (optionMatches)
- this.showSearchBubble_(optionMatches[0]);
- else
+ // surprise user. Even if there's a match, do not show the bubble, user
+ // is already aware that this option is visible and matches the search.
+ // Showing the bubble will only create a distraction by moving UI
+ // elements around.
+ if (!optionMatches)
this.hideSearchBubble_();
}
},
/**
+ * @param {!Object} entity Entity to get the display name for. Entity in
+ * is either a vendor capability or vendor capability option.
+ * @return {string} The entity display name.
+ * @private
+ */
+ getEntityDisplayName_: function(entity) {
+ var displayName = entity.display_name;
+ if (!displayName && entity.display_name_localized)
+ displayName = getStringForCurrentLocale(entity.display_name_localized);
+ return displayName || '';
+ },
+
+ /**
* Renders capability properties according to the current state.
* @private
*/
renderCapability_: function() {
- var textContent = this.capability_.display_name;
+ var textContent = this.getEntityDisplayName_(this.capability_);
+ // Whether capability name matches the query.
var nameMatches = this.query_ ? !!textContent.match(this.query_) : true;
+ // An array of text segments of the capability value matching the query.
var optionMatches = null;
if (this.query_) {
if (this.capability_.type == 'SELECT') {
- this.capability_.select_cap.option.some(function(option) {
- optionMatches = (option.display_name || '').match(this.query_);
- return !!optionMatches;
- }.bind(this));
+ // Look for the first option that matches the query.
+ for (var i = 0; i < this.select_.length && !optionMatches; i++)
+ optionMatches = this.select_.options[i].text.match(this.query_);
} else {
optionMatches = (this.text_.value || '').match(this.query_);
}
}
- var matches = nameMatches || optionMatches;
+ var matches = nameMatches || !!optionMatches;
- if (!matches || !optionMatches)
+ if (!optionMatches)
this.hideSearchBubble_();
setIsVisible(this.getElement(), matches);
@@ -246,17 +261,17 @@ cr.define('print_preview', function() {
*/
initializeSelectValue_: function() {
setIsVisible(
- this.getChildElement('.advanced-settings-item-value-select'), true);
+ this.getChildElement('.advanced-settings-item-value-select'), true);
var selectEl = this.select_;
var indexToSelect = 0;
this.capability_.select_cap.option.forEach(function(option, index) {
var item = document.createElement('option');
- item.text = option.display_name;
+ item.text = this.getEntityDisplayName_(option);
item.value = option.value;
if (option.is_default)
indexToSelect = index;
selectEl.appendChild(item);
- });
+ }, this);
for (var i = 0, option; option = selectEl.options[i]; i++) {
if (option.value == this.selectedValue_) {
indexToSelect = i;
diff --git a/chromium/chrome/browser/resources/print_preview/settings/copies_settings.html b/chromium/chrome/browser/resources/print_preview/settings/copies_settings.html
index 29036e018f5..42c7bff00c1 100644
--- a/chromium/chrome/browser/resources/print_preview/settings/copies_settings.html
+++ b/chromium/chrome/browser/resources/print_preview/settings/copies_settings.html
@@ -5,9 +5,13 @@
<div class="right-column">
<div id="copies-settings-box">
<input class="copies" type="text" value="1" maxlength="3"
- aria-labelledby="copies-label">
- <button class="increment" i18n-values="title:incrementTitle;">+</button>
- <button class="decrement" i18n-values="title:decrementTitle;">–</button>
+ aria-labelledby="copies-label" aria-live="polite" id="copies">
+ <button class="increment"
+ i18n-values="title:incrementTitle;aria-label:incrementTitle"
+ aria-controls="copies">+</button>
+ <button class="decrement"
+ i18n-values="title:decrementTitle;aria-label:decrementTitle"
+ aria-controls="copies">–</button>
<div class="collate-container checkbox" aria-live="polite" hidden><label>
<input class="collate" type="checkbox" checked
aria-labelledby="copies-collate-label">
diff --git a/chromium/chrome/browser/resources/print_preview/settings/copies_settings.js b/chromium/chrome/browser/resources/print_preview/settings/copies_settings.js
index 10814f9b811..ce5d5ef92e1 100644
--- a/chromium/chrome/browser/resources/print_preview/settings/copies_settings.js
+++ b/chromium/chrome/browser/resources/print_preview/settings/copies_settings.js
@@ -216,7 +216,11 @@ cr.define('print_preview', function() {
*/
onTextfieldBlur_: function() {
if (this.getChildElement('input.copies').value == '') {
- this.copiesTicketItem_.updateValue('1');
+ // Do it asynchronously to avoid moving focus to Print button in
+ // PrintHeader.onTicketChange_.
+ setTimeout((function() {
+ this.copiesTicketItem_.updateValue('1');
+ }).bind(this), 0);
}
},
diff --git a/chromium/chrome/browser/resources/print_preview/settings/destination_settings.css b/chromium/chrome/browser/resources/print_preview/settings/destination_settings.css
index 31fe304dbb6..1962f1d7b79 100644
--- a/chromium/chrome/browser/resources/print_preview/settings/destination_settings.css
+++ b/chromium/chrome/browser/resources/print_preview/settings/destination_settings.css
@@ -8,9 +8,7 @@
}
#destination-settings .throbber {
- display: block;
- margin-bottom: 4px;
- margin-top: 8px;
+ margin-top: 6px;
}
#destination-settings .destination-settings-box {
diff --git a/chromium/chrome/browser/resources/print_preview/settings/dpi_settings.css b/chromium/chrome/browser/resources/print_preview/settings/dpi_settings.css
index e436d225cd7..e029a64e906 100644
--- a/chromium/chrome/browser/resources/print_preview/settings/dpi_settings.css
+++ b/chromium/chrome/browser/resources/print_preview/settings/dpi_settings.css
@@ -2,7 +2,7 @@
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file. */
-#dpi-settings .dpi-settings-select {
+#dpi-settings .settings-select {
height: 28px;
margin: 10px 0;
}
diff --git a/chromium/chrome/browser/resources/print_preview/settings/media_size_settings.css b/chromium/chrome/browser/resources/print_preview/settings/media_size_settings.css
index b95dc49184f..bc69a775db4 100644
--- a/chromium/chrome/browser/resources/print_preview/settings/media_size_settings.css
+++ b/chromium/chrome/browser/resources/print_preview/settings/media_size_settings.css
@@ -2,7 +2,7 @@
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file. */
-#media-size-settings .media-size-settings-select {
+#media-size-settings .settings-select {
height: 28px;
margin: 10px 0;
}
diff --git a/chromium/chrome/browser/resources/print_preview/settings/more_settings.css b/chromium/chrome/browser/resources/print_preview/settings/more_settings.css
index c35ec1000d9..0358173d521 100644
--- a/chromium/chrome/browser/resources/print_preview/settings/more_settings.css
+++ b/chromium/chrome/browser/resources/print_preview/settings/more_settings.css
@@ -27,12 +27,12 @@
#more-settings .more-settings-icon-minus {
background-image: -webkit-image-set(
- url('../images/1x/minus.png') 1x,
- url('../images/2x/minus.png') 2x);
+ url(../images/1x/minus.png) 1x,
+ url(../images/2x/minus.png) 2x);
}
#more-settings .more-settings-icon-plus {
background-image: -webkit-image-set(
- url('../images/1x/plus.png') 1x,
- url('../images/2x/plus.png') 2x);
+ url(../images/1x/plus.png) 1x,
+ url(../images/2x/plus.png) 2x);
}
diff --git a/chromium/chrome/browser/resources/print_preview/settings/more_settings.js b/chromium/chrome/browser/resources/print_preview/settings/more_settings.js
index 580b56ae5ab..c8e366286cd 100644
--- a/chromium/chrome/browser/resources/print_preview/settings/more_settings.js
+++ b/chromium/chrome/browser/resources/print_preview/settings/more_settings.js
@@ -9,7 +9,7 @@ cr.define('print_preview', function() {
* Toggles visibility of the specified printing options sections.
* @param {!print_preview.DestinationStore} destinationStore To listen for
* destination changes.
- * @param {!Array.<print_preview.SettingsSection>} settingsSections Sections
+ * @param {!Array<print_preview.SettingsSection>} settingsSections Sections
* to toggle by this component.
* @constructor
* @extends {print_preview.Component}
@@ -20,7 +20,7 @@ cr.define('print_preview', function() {
/** @private {!print_preview.DestinationStore} */
this.destinationStore_ = destinationStore;
- /** @private {!Array.<print_preview.SettingsSection>} */
+ /** @private {!Array<print_preview.SettingsSection>} */
this.settingsSections_ = settingsSections;
/** @private {MoreSettings.SettingsToShow} */
diff --git a/chromium/chrome/browser/resources/print_preview/settings/settings_section_select.js b/chromium/chrome/browser/resources/print_preview/settings/settings_section_select.js
index b32ce4d1535..0799c94e1c0 100644
--- a/chromium/chrome/browser/resources/print_preview/settings/settings_section_select.js
+++ b/chromium/chrome/browser/resources/print_preview/settings/settings_section_select.js
@@ -79,28 +79,7 @@ cr.define('print_preview', function() {
select.innerHTML = '';
this.ticketItem_.capability.option.forEach(function(option, index) {
var selectOption = document.createElement('option');
- var displayName = option.custom_display_name;
- if (!displayName && option.custom_display_name_localized) {
- var getLocaleToCompare =
- /** @type {function(string, boolean=): string} */
- (function(locale, opt_languageOnly) {
- var code = opt_languageOnly ? locale.split('-')[0] : locale;
- return code.toLowerCase();
- });
- var getItemForLocale = function(items, locale, languageOnly) {
- locale = getLocaleToCompare(locale, languageOnly);
- for (var i = 0; i < items.length; i++) {
- if (getLocaleToCompare(items[i].locale) == locale)
- return items[i].value;
- }
- return '';
- };
- var items = option.custom_display_name_localized;
- displayName =
- getItemForLocale(items, navigator.language, false) ||
- getItemForLocale(items, navigator.language, true);
- }
- selectOption.text = displayName ||
+ selectOption.text = this.getCustomDisplayName_(option) ||
this.getDefaultDisplayName_(option);
selectOption.value = JSON.stringify(option);
select.appendChild(selectOption);
@@ -121,6 +100,20 @@ cr.define('print_preview', function() {
},
/**
+ * @param {!Object} option Option to get the custom display name for.
+ * @return {string} Custom display name for the option.
+ * @private
+ */
+ getCustomDisplayName_: function(option) {
+ var displayName = option.custom_display_name;
+ if (!displayName && option.custom_display_name_localized) {
+ displayName =
+ getStringForCurrentLocale(option.custom_display_name_localized);
+ }
+ return displayName;
+ },
+
+ /**
* @param {!Object} option Option to get the default display name for.
* @return {string} Default option display name.
* @private
diff --git a/chromium/chrome/browser/resources/profile_signin_confirmation.html b/chromium/chrome/browser/resources/profile_signin_confirmation.html
index c2097f0c311..bc081eae9c3 100644
--- a/chromium/chrome/browser/resources/profile_signin_confirmation.html
+++ b/chromium/chrome/browser/resources/profile_signin_confirmation.html
@@ -1,5 +1,5 @@
-<!DOCTYPE HTML>
-<html i18n-values="dir:textdirection">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
<head>
<meta charset="utf-8">
<title i18n-content="dialogTitle"></title>
@@ -8,9 +8,10 @@
<script src="chrome://resources/js/util.js"></script>
<script src="strings.js"></script>
<script src="profile_signin_confirmation.js"></script>
+ <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="profile_signin_confirmation.css">
</head>
-<body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<body>
<h1 id="dialog-title" i18n-content="dialogTitle"></h1>
<p id="dialog-message"></p>
<a i18n-content="learnMoreText"
@@ -25,6 +26,6 @@
</div>
<!-- Must be last in the DOM: processes elements and inserts i18n strings -->
- <script src="chrome://resources/js/i18n_template2.js"></script>
+ <script src="chrome://resources/js/i18n_template.js"></script>
</body>
</html>
diff --git a/chromium/chrome/browser/resources/profiler/profiler.html b/chromium/chrome/browser/resources/profiler/profiler.html
index 4a5e5fcb09f..84275334eba 100644
--- a/chromium/chrome/browser/resources/profiler/profiler.html
+++ b/chromium/chrome/browser/resources/profiler/profiler.html
@@ -1,14 +1,14 @@
-<!DOCTYPE HTML>
+<!doctype html>
<html>
<head>
<meta charset="utf-8">
+<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<script src="profiler.js"></script>
<script src="chrome://resources/js/util.js"></script>
<style>
body {
- font-family: sans-serif;
font-size: 80%;
}
@@ -147,10 +147,6 @@ table.results-table,
<div id='results-div'></div>
- <!-- TODO(eroman): This should only be a short-lived solution,
- which will eventually be superceded by snapshotting -->
- <span id=reset-data-link class=pseudo-link>[Reset tracking data]</span>
-
<a style="display: none" id="download-anchor" download="profile.json"></a>
</body>
</html>
diff --git a/chromium/chrome/browser/resources/profiler/profiler.js b/chromium/chrome/browser/resources/profiler/profiler.js
index a9c44c4fa3a..fd7a65bdf61 100644
--- a/chromium/chrome/browser/resources/profiler/profiler.js
+++ b/chromium/chrome/browser/resources/profiler/profiler.js
@@ -42,10 +42,6 @@ var BrowserBridge = (function() {
chrome.send('getData');
},
- sendResetData: function() {
- chrome.send('resetData');
- },
-
//--------------------------------------------------------------------------
// Messages received from the browser.
//--------------------------------------------------------------------------
@@ -99,8 +95,6 @@ var MainView = (function() {
// "PAC threads" will be merged together.
var MERGE_SIMILAR_THREADS_CHECKBOX_ID = 'merge-similar-threads-checkbox';
- var RESET_DATA_LINK_ID = 'reset-data-link';
-
var TOGGLE_SNAPSHOTS_LINK_ID = 'snapshots-link';
var SNAPSHOTS_ROW = 'snapshots-row';
var SNAPSHOT_SELECTION_SUMMARY_ID = 'snapshot-selection-summary';
@@ -905,7 +899,7 @@ var MainView = (function() {
value = m[1] + '*';
}
return value;
- }
+ };
} else {
propertyGetterFunc = function(row, key) { return row[key]; };
}
@@ -1076,12 +1070,10 @@ var MainView = (function() {
var linenumber = m[2];
var link = addNode(td, 'a', filename + ' [' + linenumber + ']');
- // http://chromesrc.appspot.com is a server I wrote specifically for
- // this task. It redirects to the appropriate source file; the file
- // paths given by the compiler can be pretty crazy and different
- // between platforms.
- link.href = 'http://chromesrc.appspot.com/?path=' +
- encodeURIComponent(filepath) + '&line=' + linenumber;
+
+ link.href = 'https://code.google.com/p/chromium/codesearch#search/&q=' +
+ encodeURIComponent(filename) + ':' + linenumber +
+ '&sq=package:chromium&type=cs';
link.target = '_blank';
return;
}
@@ -1762,9 +1754,6 @@ var MainView = (function() {
$(MERGE_SIMILAR_THREADS_CHECKBOX_ID).onchange =
this.onMergeSimilarThreadsCheckboxChanged_.bind(this);
- $(RESET_DATA_LINK_ID).onclick =
- g_browserBridge.sendResetData.bind(g_browserBridge);
-
$(TAKE_SNAPSHOT_BUTTON_ID).onclick = this.takeSnapshot_.bind(this);
$(SAVE_SNAPSHOTS_BUTTON_ID).onclick = this.saveSnapshots_.bind(this);
diff --git a/chromium/chrome/browser/resources/quota_internals/event_handler.js b/chromium/chrome/browser/resources/quota_internals/event_handler.js
index d92926fbb8a..89fe3559a8c 100644
--- a/chromium/chrome/browser/resources/quota_internals/event_handler.js
+++ b/chromium/chrome/browser/resources/quota_internals/event_handler.js
@@ -74,7 +74,7 @@ function stringToText_(value) {
* e.g. separateBackward_('abcdefghijk', 4) == ['abc','defg','hijk'];
* @param {string} value String to be separated.
* @param {number} maxLength Max length of segments.
- * @return {Array.<string>} Array of segments.
+ * @return {Array<string>} Array of segments.
* @private
*/
function separateBackward_(value, maxLength) {
@@ -399,7 +399,7 @@ function handlePerOriginInfo(event) {
*/
function handleStatistics(event) {
/**
- * @type {Object.<string>}
+ * @type {Object<string>}
*/
var data = event.detail;
for (var key in data) {
diff --git a/chromium/chrome/browser/resources/quota_internals/main.css b/chromium/chrome/browser/resources/quota_internals/main.css
index 5434daa36b3..ef8dc11a092 100644
--- a/chromium/chrome/browser/resources/quota_internals/main.css
+++ b/chromium/chrome/browser/resources/quota_internals/main.css
@@ -20,5 +20,5 @@ tr:nth-child(odd) {
}
.tree-item:not([may-have-children]) > .tree-row > .tree-label {
- background-image: url('../../../../ui/webui/resources/images/icon_file.png');
+ background-image: url(../../../../ui/webui/resources/images/icon_file.png);
}
diff --git a/chromium/chrome/browser/resources/quota_internals/main.html b/chromium/chrome/browser/resources/quota_internals/main.html
index 0efc415bdc1..8cab997368e 100644
--- a/chromium/chrome/browser/resources/quota_internals/main.html
+++ b/chromium/chrome/browser/resources/quota_internals/main.html
@@ -1,12 +1,13 @@
-<!DOCTYPE html>
+<!doctype html>
<!--
Copyright (c) 2011 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.
-->
-<html i18n-values="dir:textdirection;">
+<html i18n-values="dir:textdirection;lang:language">
<title>Quota Internals</title>
<meta charset="utf-8">
+<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="main.css">
<script src="chrome://resources/js/util.js"></script>
@@ -26,7 +27,7 @@ found in the LICENSE file.
<script src="chrome://quota-internals/event_handler.js"></script>
<script src="chrome://quota-internals/strings.js"></script>
-<body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<body>
<tabbox>
<tabs>
@@ -73,6 +74,6 @@ found in the LICENSE file.
</tabpanels>
</tabbox>
-<script src="chrome://resources/js/i18n_template2.js"></script>
+<script src="chrome://resources/js/i18n_template.js"></script>
</body>
</html>
diff --git a/chromium/chrome/browser/resources/reader_out_of_date.html b/chromium/chrome/browser/resources/reader_out_of_date.html
index c531efd7560..e5131cf5a60 100644
--- a/chromium/chrome/browser/resources/reader_out_of_date.html
+++ b/chromium/chrome/browser/resources/reader_out_of_date.html
@@ -1,65 +1,63 @@
-<!DOCTYPE html>
-<html>
+<!doctype html>
+<html><!-- TODO(dbeam): RTL? -->
<head>
<meta charset="utf-8">
<title i18n-content="title"></title>
+<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<style type="text/css">
body {
- background-color:#500;
- font-family:Helvetica,Arial,sans-serif;
- margin:0px;
+ background-color: #500;
+ margin: 0;
}
.background {
- position:absolute;
- width:100%;
- height:100%;
+ height: 100%;
+ position: absolute;
+ width: 100%;
}
.cell {
- padding:40px;
+ padding: 40px;
}
.box {
- width:80%;
- background-color:white;
- color:black;
- font-size:10pt;
- line-height:16pt;
- text-align:left;
- padding:20px;
- position:relative;
- -webkit-box-shadow:3px 3px 8px #200;
- border-radius:5px;
+ background-color: white;
+ border-radius: 5px;
+ box-shadow: 3px 3px 8px #200;
+ color: black;
+ font-size: 10pt;
+ line-height: 16pt;
+ padding: 20px;
+ position: relative;
+ text-align: left;
+ width: 80%;
}
html[dir='rtl'] .box {
- text-align:right;
+ text-align: right;
}
.icon {
- background-image: url('ssl/images/roadblock.png');
+ background-image: url(ssl/images/roadblock.png);
background-repeat: no-repeat;
height: 53px;
position: absolute;
width: 64px;
}
.title {
- margin: 0px 77px 0px;
- font-size:18pt;
+ color: #660000;
+ font-size: 18pt;
+ font-weight: bold;
line-height: 140%;
- margin-bottom:6pt;
- font-weight:bold;
- color:#660000;
+ margin-bottom: 6pt;
+ margin: 0 77px 0;
}
.main {
- margin:0px 80px 0px;
+ margin: 0 80px 0;
}
.submission {
- margin:15px 5px 15px 0px;
- padding:0px;
+ margin: 15px 5px 15px 0;
+ padding: 0;
}
input {
- margin:0px;
-}
-.proceedbutton {
+ margin: 0;
}
</style>
@@ -79,7 +77,8 @@ function getChecked() {
</script>
</head>
<body oncontextmenu="return false;">
-<div class="background"><img src="ssl/roadblock_background.png" width="100%" height="100%" alt="background" onmousedown="return false;"></div>
+<div class="background"><img src="ssl/roadblock_background.png" width="100%"
+ height="100%" alt="background" onmousedown="return false;"></div>
<table width="100%" cellspacing="0" cellpadding="0">
<td class="cell" valign="middle" align="center">
<div class="box">
@@ -87,11 +86,17 @@ function getChecked() {
<div class="title" i18n-content="headLine"></div>
<div class="main">
<form name="form" class="submission">
- <input type="radio" name="group" value="1" checked> <span i18n-content="update"></span><br>
- <input type="radio" name="group" value="2"> <span i18n-content="open_with_reader"></span><br>
+ <input type="radio" name="group" value="1" checked>
+ <span i18n-content="update"></span>
+ <br>
+ <input type="radio" name="group" value="2">
+ <span i18n-content="open_with_reader"></span>
+ <br>
<br>
- <input type="button" i18n-values="value:ok" name="ok" class="proceedbutton" onClick="sendCommand(getChecked());">
- <input type="button" i18n-values="value:cancel" name="cancel" onClick="sendCommand(0);">
+ <input type="button" i18n-values="value:ok" name="ok"
+ class="proceedbutton" onclick="sendCommand(getChecked());">
+ <input type="button" i18n-values="value:cancel" name="cancel"
+ onclick="sendCommand(0);">
</form>
</div>
</td>
diff --git a/chromium/chrome/browser/resources/roboto/OWNERS b/chromium/chrome/browser/resources/roboto/OWNERS
deleted file mode 100644
index 07a31a62aca..00000000000
--- a/chromium/chrome/browser/resources/roboto/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-mathp@chromium.org
diff --git a/chromium/chrome/browser/resources/roboto/roboto.woff b/chromium/chrome/browser/resources/roboto/roboto.woff
deleted file mode 100644
index 49500877148..00000000000
--- a/chromium/chrome/browser/resources/roboto/roboto.woff
+++ /dev/null
Binary files differ
diff --git a/chromium/chrome/browser/resources/roboto/roboto.woff2 b/chromium/chrome/browser/resources/roboto/roboto.woff2
deleted file mode 100644
index 96827532be3..00000000000
--- a/chromium/chrome/browser/resources/roboto/roboto.woff2
+++ /dev/null
Binary files differ
diff --git a/chromium/chrome/browser/resources/search_header.css b/chromium/chrome/browser/resources/search_header.css
index 24e1723b514..ce09f8276e7 100644
--- a/chromium/chrome/browser/resources/search_header.css
+++ b/chromium/chrome/browser/resources/search_header.css
@@ -50,7 +50,7 @@ header > :-webkit-any(form, input[type='search']) {
flex: none;
}
-.summary :first-child {
+.summary > :first-child {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
diff --git a/chromium/chrome/browser/resources/security_warnings/captive_portal.js b/chromium/chrome/browser/resources/security_warnings/captive_portal.js
new file mode 100644
index 00000000000..2c7c460b896
--- /dev/null
+++ b/chromium/chrome/browser/resources/security_warnings/captive_portal.js
@@ -0,0 +1,5 @@
+// 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.
+
+var CAPTIVEPORTAL_CMD_OPEN_LOGIN_PAGE = 'openLoginPage';
diff --git a/chromium/chrome/browser/resources/security_warnings/extended_reporting.js b/chromium/chrome/browser/resources/security_warnings/extended_reporting.js
new file mode 100644
index 00000000000..51b4b21c4f9
--- /dev/null
+++ b/chromium/chrome/browser/resources/security_warnings/extended_reporting.js
@@ -0,0 +1,40 @@
+// Copyright 2015 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';
+
+// Other constants defined in security_interstitial_page.h.
+var SB_BOX_CHECKED = 'boxchecked';
+var SB_DISPLAY_CHECK_BOX = 'displaycheckbox';
+
+// This sets up the Extended Safe Browsing Reporting opt-in, either for
+// reporting malware or invalid certificate chains. Does nothing if the
+// interstitial type is not SAFEBROWSING or SSL.
+function setupExtendedReportingCheckbox() {
+ var interstitialType = loadTimeData.getString('type');
+ if (interstitialType != 'SAFEBROWSING' && interstitialType != 'SSL') {
+ return;
+ }
+
+ if (!loadTimeData.getBoolean(SB_DISPLAY_CHECK_BOX)) {
+ return;
+ }
+
+ $('opt-in-label').innerHTML = loadTimeData.getString('optInLink');
+ $('opt-in-checkbox').checked = loadTimeData.getBoolean(SB_BOX_CHECKED);
+ $('extended-reporting-opt-in').classList.remove('hidden');
+
+ var className = interstitialType == 'SAFEBROWSING' ?
+ 'safe-browsing-opt-in' :
+ 'ssl-opt-in';
+ $('extended-reporting-opt-in').classList.add(className);
+
+ $('body').classList.add('extended-reporting-has-checkbox');
+
+ $('opt-in-checkbox').addEventListener('click', function() {
+ sendCommand($('opt-in-checkbox').checked ?
+ CMD_DO_REPORT :
+ CMD_DONT_REPORT);
+ });
+}
diff --git a/chromium/chrome/browser/resources/security_warnings/images/1x/brokenssl_red.png b/chromium/chrome/browser/resources/security_warnings/images/1x/brokenssl_red.png
index d25c3a0dbc7..ddaabb1970e 100644
--- a/chromium/chrome/browser/resources/security_warnings/images/1x/brokenssl_red.png
+++ b/chromium/chrome/browser/resources/security_warnings/images/1x/brokenssl_red.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/security_warnings/images/1x/captive_portal_page_icon.png b/chromium/chrome/browser/resources/security_warnings/images/1x/captive_portal_page_icon.png
new file mode 100644
index 00000000000..7fb63e73e1e
--- /dev/null
+++ b/chromium/chrome/browser/resources/security_warnings/images/1x/captive_portal_page_icon.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/security_warnings/images/1x/clock.png b/chromium/chrome/browser/resources/security_warnings/images/1x/clock.png
index 98e2ed36e1b..247068a6d90 100644
--- a/chromium/chrome/browser/resources/security_warnings/images/1x/clock.png
+++ b/chromium/chrome/browser/resources/security_warnings/images/1x/clock.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/security_warnings/images/1x/stop_sign.png b/chromium/chrome/browser/resources/security_warnings/images/1x/stop_sign.png
index c8bab1a627f..901c6f4db5f 100644
--- a/chromium/chrome/browser/resources/security_warnings/images/1x/stop_sign.png
+++ b/chromium/chrome/browser/resources/security_warnings/images/1x/stop_sign.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/security_warnings/images/2x/brokenssl_red.png b/chromium/chrome/browser/resources/security_warnings/images/2x/brokenssl_red.png
index 2914e437cd0..b3d53b19b2c 100644
--- a/chromium/chrome/browser/resources/security_warnings/images/2x/brokenssl_red.png
+++ b/chromium/chrome/browser/resources/security_warnings/images/2x/brokenssl_red.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/security_warnings/images/2x/captive_portal_page_icon.png b/chromium/chrome/browser/resources/security_warnings/images/2x/captive_portal_page_icon.png
new file mode 100644
index 00000000000..571b3fc49a1
--- /dev/null
+++ b/chromium/chrome/browser/resources/security_warnings/images/2x/captive_portal_page_icon.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/security_warnings/images/2x/clock.png b/chromium/chrome/browser/resources/security_warnings/images/2x/clock.png
index 6d11c7f8fdd..7a10d5df354 100644
--- a/chromium/chrome/browser/resources/security_warnings/images/2x/clock.png
+++ b/chromium/chrome/browser/resources/security_warnings/images/2x/clock.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/security_warnings/images/2x/stop_sign.png b/chromium/chrome/browser/resources/security_warnings/images/2x/stop_sign.png
index 2e8e568f0d7..2e5db6a1177 100644
--- a/chromium/chrome/browser/resources/security_warnings/images/2x/stop_sign.png
+++ b/chromium/chrome/browser/resources/security_warnings/images/2x/stop_sign.png
Binary files differ
diff --git a/chromium/chrome/browser/resources/security_warnings/interstitial_ui.html b/chromium/chrome/browser/resources/security_warnings/interstitial_ui.html
new file mode 100644
index 00000000000..a2fc0275f41
--- /dev/null
+++ b/chromium/chrome/browser/resources/security_warnings/interstitial_ui.html
@@ -0,0 +1,21 @@
+<html>
+<head>
+ <title>Interstitials</title>
+</head>
+<body>
+ <h2>Choose an interstitial<h2>
+ <h3>SSL</h3>
+ <a href='ssl'>example.com</a><br>
+ <a href='ssl?url=https://google.com'>SSL (google.com)</a><br>
+ <a href='ssl?overridable=1&strict_enforcement=0'>example.com (Overridable)</a>
+ <br>
+ <a href='ssl?clock_manipulation=2'>Clock is ahead</a><br>
+ <a href='ssl?clock_manipulation=-2'>Clock is behind</a><br>
+ <br><br>
+ <h3>SafeBrowsing</h3>
+ <a href='safebrowsing?type=malware'>Malware</a><br>
+ <a href='safebrowsing?type=phishing'>Phishing</a><br>
+ <a href='safebrowsing?type=clientside_malware'>Client Side Malware</a><br>
+ <a href='safebrowsing?type=clientside_phishing'>Client Side Phishing</a><br>
+</body>
+</html>
diff --git a/chromium/chrome/browser/resources/security_warnings/interstitial_v2.css b/chromium/chrome/browser/resources/security_warnings/interstitial_v2.css
index 07bf7565fb1..9713d87d431 100644
--- a/chromium/chrome/browser/resources/security_warnings/interstitial_v2.css
+++ b/chromium/chrome/browser/resources/security_warnings/interstitial_v2.css
@@ -14,8 +14,7 @@ a {
body {
background-color: #f7f7f7;
- color: #585858;
- font-size: 125%;
+ color: #646464;
}
body.safe-browsing {
@@ -24,6 +23,7 @@ body.safe-browsing {
}
button {
+ -webkit-user-select: none;
background: rgb(76, 142, 250);
border: 0;
border-radius: 2px;
@@ -32,9 +32,8 @@ button {
cursor: pointer;
float: right;
font-size: .875em;
- height: 36px;
- margin: -6px 0 0;
- padding: 8px 24px;
+ margin: 0;
+ padding: 10px 24px;
transition: box-shadow 200ms cubic-bezier(0.4, 0, 0.2, 1);
}
@@ -51,10 +50,15 @@ button:hover {
box-shadow: 0 1px 3px rgba(0, 0, 0, .50);
}
+#debugging {
+ display: inline;
+ overflow: auto;
+}
+
.debugging-content {
line-height: 1em;
margin-bottom: 0;
- margin-top: 0;
+ margin-top: 1em;
}
.debugging-title {
@@ -70,9 +74,25 @@ button:hover {
margin-top: 20px;
}
-#error-code {
- color: black;
- opacity: .35;
+#details-button {
+ background: inherit;
+ border: 0;
+ float: none;
+ margin: 0;
+ padding: 10px 0;
+ text-decoration: underline;
+}
+
+#details-button:hover {
+ box-shadow: inherit;
+}
+
+.error-code {
+ color: #777;
+ display: inline;
+ font-size: .86667em;
+ margin-top: 15px;
+ opacity: .5;
text-transform: uppercase;
}
@@ -81,7 +101,7 @@ button:hover {
}
h1 {
- color: #585858;
+ color: #333;
font-size: 1.6em;
font-weight: normal;
line-height: 1.25em;
@@ -97,6 +117,11 @@ h2 {
display: none;
}
+html {
+ -webkit-text-size-adjust: 100%;
+ font-size: 125%;
+}
+
.icon {
background-repeat: no-repeat;
background-size: 100%;
@@ -106,7 +131,11 @@ h2 {
}
input[type=checkbox] {
- visibility: hidden;
+ opacity: 0;
+}
+
+input[type=checkbox]:focus ~ .checkbox {
+ outline: -webkit-focus-ring-color auto 5px;
}
.interstitial-wrapper {
@@ -118,11 +147,19 @@ input[type=checkbox] {
width: 100%;
}
-#malware-opt-in {
+#main-message > p {
+ display: inline;
+}
+
+#extended-reporting-opt-in {
font-size: .875em;
margin-top: 39px;
}
+#extended-reporting-opt-in label {
+ position: relative;
+}
+
.nav-wrapper {
margin-top: 51px;
}
@@ -134,29 +171,27 @@ input[type=checkbox] {
width: 100%;
}
-#opt-in-label {
- -webkit-margin-start: 32px;
-}
-
.safe-browsing :-webkit-any(
a, #details, #details-button, h1, h2, p, .small-link) {
color: white;
}
.safe-browsing button {
- background-color: rgb(206, 52, 38);
- border: 1px solid white;
+ background-color: rgba(255, 255, 255, .15);
}
.safe-browsing button:active {
- background-color: rgb(206, 52, 38);
- border-color: rgba(255, 255, 255, .6);
+ background-color: rgba(255, 255, 255, .25);
}
.safe-browsing button:hover {
box-shadow: 0 2px 3px rgba(0, 0, 0, .5);
}
+.safe-browsing .error-code {
+ display: none;
+}
+
.safe-browsing .icon {
background-image: -webkit-image-set(
url(images/1x/stop_sign.png) 1x,
@@ -174,31 +209,26 @@ input[type=checkbox] {
url(images/2x/brokenssl_red.png) 2x);
}
-.styled-checkbox {
- float: left;
- height: 16px;
- margin-top: .36em;
- position: relative;
- width: 16px;
-}
-
-[dir='rtl'] .styled-checkbox {
- float: right;
+.captive-portal .icon {
+ background-image: -webkit-image-set(
+ url(images/1x/captive_portal_page_icon.png) 1x,
+ url(images/2x/captive_portal_page_icon.png) 2x);
}
-.styled-checkbox label {
+.checkbox {
background: transparent;
- border: white solid 1px;
+ border: 1px solid white;
border-radius: 2px;
+ display: block;
height: 14px;
left: 0;
position: absolute;
right: 0;
- top: 0;
+ top: -1px;
width: 14px;
}
-.styled-checkbox label::after {
+.checkbox::before {
background: transparent;
border: 2px solid white;
border-right-width: 0;
@@ -206,14 +236,22 @@ input[type=checkbox] {
content: '';
height: 4px;
left: 2px;
- opacity: 0.3;
+ opacity: 0;
position: absolute;
top: 3px;
transform: rotate(-45deg);
width: 9px;
}
-.styled-checkbox input[type=checkbox]:checked + label::after {
+.ssl-opt-in .checkbox {
+ border-color: #696969;
+}
+
+.ssl-opt-in .checkbox::before {
+ border-color: #696969;
+}
+
+input[type=checkbox]:checked ~ .checkbox::before {
opacity: 1;
}
@@ -221,19 +259,27 @@ input[type=checkbox] {
.interstitial-wrapper {
padding: 0 10%;
}
+
+ #error-debugging-info {
+ overflow: auto;
+ }
}
@media (max-height: 600px) {
- .interstitial-wrapper {
- margin-top: 13%;
+ .error-code {
+ margin-top: 10px;
}
}
-@media (max-width: 400px) {
+@media (max-width: 420px) {
button,
- [dir='rtl'] button {
+ [dir='rtl'] button,
+ .small-link {
float: none;
- font-size: 1em;
+ font-size: .825em;
+ font-weight: 400;
+ margin: 0;
+ text-transform: uppercase;
width: 100%;
}
@@ -247,7 +293,7 @@ input[type=checkbox] {
#details-button {
display: block;
- padding-top: 14px;
+ margin-top: 20px;
text-align: center;
width: 100%;
}
@@ -256,15 +302,368 @@ input[type=checkbox] {
padding: 0 5%;
}
- #malware-opt-in {
+ #extended-reporting-opt-in {
margin-top: 24px;
}
.nav-wrapper {
margin-top: 30px;
}
+}
+
+/**
+ * Mobile specific styling.
+ * Navigation buttons are anchored to the bottom of the screen.
+ * Details message replaces the top content in its own scrollable area.
+ */
+
+@media (max-width: 420px) and (max-height: 736px) and (orientation: portrait) {
+ #details-button {
+ border: 0;
+ margin: 8px 0 0;
+ }
+
+ .secondary-button {
+ -webkit-margin-end: 0;
+ margin-top: 16px;
+ }
+}
+
+/* Fixed nav. */
+@media (min-width: 240px) and (max-width: 420px) and
+ (min-height: 401px) and (max-height: 736px) and (orientation:portrait),
+ (min-width: 421px) and (max-width: 736px) and (min-height: 240px) and
+ (max-height: 420px) and (orientation:landscape) {
+ body .nav-wrapper {
+ background: #f7f7f7;
+ bottom: 0;
+ box-shadow: 0 -22px 40px rgb(247, 247, 247);
+ left: 0;
+ margin: 0;
+ max-width: 736px;
+ padding-left: 24px;
+ padding-right: 24px;
+ position: fixed;
+ z-index: 1;
+ }
+
+ body.safe-browsing .nav-wrapper {
+ background: rgb(206, 52, 38);
+ box-shadow: 0 -22px 40px rgb(206, 52, 38);
+ }
+
+ .interstitial-wrapper {
+ max-width: 736px;
+ }
+
+ #details,
+ #main-content {
+ padding-bottom: 40px;
+ }
+}
+
+@media (max-width: 420px) and (max-height: 736px) and (orientation: portrait),
+ (max-width: 736px) and (max-height: 420px) and (orientation: landscape) {
+ body {
+ margin: 0 auto;
+ }
+
+ button,
+ [dir='rtl'] button,
+ button.small-link {
+ font-family: Roboto-Regular,Helvetica;
+ font-size: .933em;
+ font-weight: 600;
+ margin: 6px 0;
+ text-transform: uppercase;
+ }
+
+ .nav-wrapper {
+ box-sizing: border-box;
+ padding-bottom: 8px;
+ width: 100%;
+ }
+
+ .error-code {
+ margin-top: 0;
+ }
+
+ #details {
+ box-sizing: border-box;
+ height: auto;
+ margin: 0;
+ opacity: 1;
+ transition: opacity 250ms cubic-bezier(0.4, 0, 0.2, 1);
+ }
+
+ #details.hidden,
+ #main-content.hidden {
+ display: block;
+ height: 0;
+ opacity: 0;
+ overflow: hidden;
+ }
+
+ #details-button {
+ padding-bottom: 16px;
+ padding-top: 16px;
+ }
+
+ h1 {
+ font-size: 1.5em;
+ margin-bottom: 8px;
+ }
+
+ .icon {
+ margin-bottom: 12px;
+ }
+
+ .interstitial-wrapper {
+ box-sizing: border-box;
+ margin: 24px auto 12px;
+ padding: 0 24px;
+ position: relative;
+ }
+
+ .interstitial-wrapper p {
+ font-size: .95em;
+ line-height: 1.61em;
+ margin-top: 8px;
+ }
+
+ #main-content {
+ margin: 0;
+ transition: opacity 100ms cubic-bezier(0.4, 0, 0.2, 1);
+ }
.small-link {
+ border: 0;
+ }
+
+ .suggested-left > #control-buttons,
+ .suggested-right > #control-buttons {
+ float: none;
+ margin: 0;
+ }
+}
+
+@media (min-height: 400px) and (orientation:portrait) {
+ body:not(.extended-reporting-has-checkbox) .interstitial-wrapper {
+ margin-bottom: 145px;
+ }
+}
+
+@media (min-height: 299px) and (orientation:portrait) {
+ .nav-wrapper {
+ padding-bottom: 16px;
+ }
+}
+
+@media (min-height: 405px) and (max-height: 736px) and
+ (max-width: 420px) and (orientation:portrait) {
+ .icon {
+ margin-bottom: 24px;
+ }
+
+ .interstitial-wrapper {
+ margin-top: 64px;
+ }
+}
+
+@media (min-height: 480px) and (max-width: 420px) and
+ (max-height: 736px) and (orientation: portrait),
+ (min-height: 338px) and (max-height: 420px) and (max-width: 736px) and
+ (orientation: landscape) {
+ .icon {
+ margin-bottom: 24px;
+ }
+
+ .nav-wrapper {
+ padding-bottom: 24px;
+ }
+}
+
+@media (min-height: 500px) and (max-width: 414px) and (orientation: portrait) {
+ :not(.extended-reporting-has-checkbox) .interstitial-wrapper {
+ margin-top: 96px;
+ }
+}
+
+/* Phablet sizing */
+@media (min-width: 375px) and (min-height: 641px) and (max-height: 736px) and
+ (max-width: 414px) and (orientation: portrait) {
+ button,
+ [dir='rtl'] button,
+ .small-link {
font-size: 1em;
+ padding-bottom: 12px;
+ padding-top: 12px;
+ }
+
+ body:not(.offline) .icon {
+ height: 80px;
+ width: 80px;
+ }
+
+ #details-button {
+ margin-top: 28px;
+ }
+
+ h1 {
+ font-size: 1.7em;
+ }
+
+ .icon {
+ margin-bottom: 28px;
+ }
+
+ .interstitial-wrapper {
+ padding: 28px;
+ }
+
+ .interstitial-wrapper p {
+ font-size: 1.05em;
+ }
+
+ .nav-wrapper {
+ padding: 28px;
+ }
+}
+
+@media (min-width: 420px) and (max-width: 736px) and
+ (min-height: 240px) and (max-height: 298px) and
+ (orientation:landscape) {
+ body:not(.offline) .icon {
+ height: 50px;
+ width: 50px;
+ }
+
+ .icon {
+ padding-top: 0;
+ }
+
+ .interstitial-wrapper {
+ margin-top: 16px;
+ }
+
+ .nav-wrapper {
+ padding: 0 24px 8px;
+ }
+}
+
+@media (min-width: 420px) and (max-width: 736px) and
+ (min-height: 240px) and (max-height: 420px) and
+ (orientation:landscape) {
+ #details-button {
+ margin: 0;
+ }
+
+ .interstitial-wrapper {
+ margin-bottom: 70px;
+ }
+
+ .nav-wrapper {
+ margin-top: 0;
+ }
+
+ #extended-reporting-opt-in {
+ margin-top: 0;
+ }
+}
+
+/* Phablet landscape */
+@media (min-width: 680px) and (max-height: 414px) {
+ .interstitial-wrapper {
+ margin: 24px auto;
+ }
+
+ .nav-wrapper {
+ margin: 16px auto 0;
+ }
+}
+
+@media (max-height: 240px) and (orientation: landscape),
+ (max-height: 480px) and (orientation: portrait),
+ (max-width: 419px) and (max-height: 323px) {
+ body:not(.offline) .icon {
+ height: 56px;
+ width: 56px;
+ }
+
+ .icon {
+ margin-bottom: 16px;
+ }
+}
+
+/* Small mobile screens. No fixed nav. */
+@media (max-height: 400px) and (orientation: portrait),
+ (max-height: 239px) and (orientation: landscape),
+ (max-width: 419px) and (max-height: 399px) {
+ .interstitial-wrapper {
+ display: flex;
+ flex-direction: column;
+ margin-bottom: 0;
+ }
+
+ #details {
+ flex: 1 1 auto;
+ order: 0;
+ }
+
+ #main-content {
+ flex: 1 1 auto;
+ order: 0;
+ }
+
+ .nav-wrapper {
+ flex: 0 1 auto;
+ margin-top: 8px;
+ order: 1;
+ padding-left: 0;
+ padding-right: 0;
+ position: relative;
+ width: 100%;
+ }
+}
+
+/* Extended reporting opt-in. No fixed nav. */
+@media (max-height: 736px) and (orientation: portrait),
+ (max-height: 360px) and (max-width: 680px) and (orientation: landscape) {
+ .extended-reporting-has-checkbox .interstitial-wrapper {
+ display: flex;
+ flex-direction: column;
+ margin-bottom: 0;
+ }
+
+ .extended-reporting-has-checkbox #details {
+ flex: 1 1 auto;
+ order: 0;
+ }
+
+ .extended-reporting-has-checkbox #main-content {
+ flex: 1 1 auto;
+ order: 0;
+ padding-bottom: 0;
+ }
+
+ .extended-reporting-has-checkbox #extended-reporting-opt-in {
+ margin-bottom: 8px;
+ }
+
+ body.extended-reporting-has-checkbox .nav-wrapper {
+ flex: 0 1 auto;
+ margin-top: 0;
+ order: 1;
+ padding-left: 0;
+ padding-right: 0;
+ position: relative;
+ width: 100%;
+ }
+}
+
+@media (max-width: 239px) and (orientation: portrait) {
+ .nav-wrapper {
+ padding-left: 0;
+ padding-right: 0;
}
}
diff --git a/chromium/chrome/browser/resources/security_warnings/interstitial_v2.html b/chromium/chrome/browser/resources/security_warnings/interstitial_v2.html
index 9ba78e608b6..18c1a3e4fff 100644
--- a/chromium/chrome/browser/resources/security_warnings/interstitial_v2.html
+++ b/chromium/chrome/browser/resources/security_warnings/interstitial_v2.html
@@ -1,43 +1,47 @@
-<!DOCTYPE html>
-<html i18n-values="dir:textdirection;.style.fontSize:fontsize">
- <head>
- <meta charset="utf-8">
- <meta name="viewport"
- content="initial-scale=1, minimum-scale=1, width=device-width">
- <title i18n-content="tabTitle"></title>
- <link rel="stylesheet" href="interstitial_v2.css">
- <script src="../../../../ui/webui/resources/js/util.js"></script>
- <script src="ssl.js"></script>
- <script src="safe_browsing.js"></script>
- <script src="interstitial_v2.js"></script>
- </head>
-<body id="body" i18n-values=".style.fontFamily:fontfamily">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport"
+ content="initial-scale=1, minimum-scale=1, width=device-width">
+ <title i18n-content="tabTitle"></title>
+ <link rel="stylesheet" href="interstitial_v2.css">
+ <script src="../../../../ui/webui/resources/js/util.js"></script>
+ <script src="captive_portal.js"></script>
+ <script src="ssl.js"></script>
+ <script src="extended_reporting.js"></script>
+ <script src="interstitial_v2_mobile.js"></script>
+ <script src="interstitial_v2.js"></script>
+</head>
+<body id="body">
<div class="interstitial-wrapper">
- <div class="icon" id="icon"></div>
- <div id="main-message">
- <h1 i18n-content="heading"></h1>
- <p i18n-values=".innerHTML:primaryParagraph"></p>
- </div>
- <div id="malware-opt-in" class="hidden">
- <div class="styled-checkbox">
- <input type="checkbox" id="opt-in-checkbox">
- <label for="opt-in-checkbox"></label>
+ <div id="main-content">
+ <div class="icon" id="icon"></div>
+ <div id="main-message">
+ <h1 i18n-content="heading"></h1>
+ <p i18n-values=".innerHTML:primaryParagraph"></p>
+ <div id="debugging">
+ <div id="error-code" class="error-code"></div>
+ <div id="error-debugging-info" class="hidden"></div>
+ </div>
+ </div>
+ <div id="extended-reporting-opt-in" class="hidden">
+ <label>
+ <input type="checkbox" id="opt-in-checkbox">
+ <span class="checkbox"></span>
+ <span id="opt-in-label"></span>
+ </label>
</div>
- <div id="opt-in-label"></div>
</div>
<div class="nav-wrapper">
<button i18n-content="primaryButtonText" id="primary-button"></button>
- <a href="#" id="details-button" class="small-link"
- i18n-content="openDetails"></a>
+ <button id="details-button" class="small-link"
+ i18n-content="openDetails"></button>
</div>
<div id="details" class="hidden">
<p i18n-values=".innerHTML:explanationParagraph"></p>
<p i18n-values=".innerHTML:finalParagraph" id="final-paragraph"></p>
</div>
- <div id="debugging">
- <p id="error-code"></p>
- <div id="error-debugging-info" class="hidden"></div>
- </div>
</div>
</body>
</html>
diff --git a/chromium/chrome/browser/resources/security_warnings/interstitial_v2.js b/chromium/chrome/browser/resources/security_warnings/interstitial_v2.js
index e3ac1b065ee..bd192725247 100644
--- a/chromium/chrome/browser/resources/security_warnings/interstitial_v2.js
+++ b/chromium/chrome/browser/resources/security_warnings/interstitial_v2.js
@@ -8,6 +8,22 @@
var expandedDetails = false;
var keyPressState = 0;
+// Should match SecurityInterstitialCommands in security_interstitial_page.h
+var CMD_DONT_PROCEED = 0;
+var CMD_PROCEED = 1;
+// Ways for user to get more information
+var CMD_SHOW_MORE_SECTION = 2;
+var CMD_OPEN_HELP_CENTER = 3;
+var CMD_OPEN_DIAGNOSTIC = 4;
+// Primary button actions
+var CMD_RELOAD = 5;
+var CMD_OPEN_DATE_SETTINGS = 6;
+var CMD_OPEN_LOGIN = 7;
+// Safe Browsing Extended Reporting
+var CMD_DO_REPORT = 8;
+var CMD_DONT_REPORT = 9;
+var CMD_OPEN_REPORTING_PRIVACY = 10;
+
/**
* A convenience method for sending commands to the parent page.
* @param {string} cmd The command to send.
@@ -26,7 +42,7 @@ function handleKeypress(e) {
if (BYPASS_SEQUENCE.charCodeAt(keyPressState) == e.keyCode) {
keyPressState++;
if (keyPressState == BYPASS_SEQUENCE.length) {
- sendCommand(SSL_CMD_PROCEED);
+ sendCommand(CMD_PROCEED);
keyPressState = 0;
}
} else {
@@ -65,7 +81,9 @@ function toggleDebuggingInfo() {
function setupEvents() {
var overridable = loadTimeData.getBoolean('overridable');
- var ssl = loadTimeData.getString('type') === 'SSL';
+ var interstitialType = loadTimeData.getString('type');
+ var ssl = interstitialType == 'SSL';
+ var captivePortal = interstitialType == 'CAPTIVE_PORTAL';
var badClock = ssl && loadTimeData.getBoolean('bad_clock');
var hidePrimaryButton = badClock && loadTimeData.getBoolean(
'hide_primary_button');
@@ -74,6 +92,8 @@ function setupEvents() {
$('body').classList.add(badClock ? 'bad-clock' : 'ssl');
$('error-code').textContent = loadTimeData.getString('errorCode');
$('error-code').classList.remove('hidden');
+ } else if (captivePortal) {
+ $('body').classList.add('captive-portal');
} else {
$('body').classList.add('safe-browsing');
}
@@ -82,20 +102,34 @@ function setupEvents() {
$('primary-button').classList.add('hidden');
} else {
$('primary-button').addEventListener('click', function() {
- if (!ssl)
- sendCommand(SB_CMD_TAKE_ME_BACK);
- else if (badClock)
- sendCommand(SSL_CMD_CLOCK);
- else if (overridable)
- sendCommand(SSL_CMD_DONT_PROCEED);
- else
- sendCommand(SSL_CMD_RELOAD);
+ switch (interstitialType) {
+ case 'CAPTIVE_PORTAL':
+ sendCommand(CMD_OPEN_LOGIN);
+ break;
+
+ case 'SSL':
+ if (badClock)
+ sendCommand(CMD_OPEN_DATE_SETTINGS);
+ else if (overridable)
+ sendCommand(CMD_DONT_PROCEED);
+ else
+ sendCommand(CMD_RELOAD);
+ break;
+
+ case 'SAFEBROWSING':
+ sendCommand(CMD_DONT_PROCEED);
+ break;
+
+ default:
+ throw 'Invalid interstitial type';
+ }
});
}
if (overridable) {
+ // Captive portal page isn't overridable.
$('proceed-link').addEventListener('click', function(event) {
- sendCommand(ssl ? SSL_CMD_PROCEED : SB_CMD_PROCEED);
+ sendCommand(CMD_PROCEED);
});
} else if (!ssl) {
$('final-paragraph').classList.add('hidden');
@@ -106,29 +140,40 @@ function setupEvents() {
} else if ($('help-link')) {
// Overridable SSL page doesn't have this link.
$('help-link').addEventListener('click', function(event) {
- if (ssl)
- sendCommand(SSL_CMD_HELP);
- else if (loadTimeData.getBoolean('phishing'))
- sendCommand(SB_CMD_LEARN_MORE_2);
+ if (ssl || loadTimeData.getBoolean('phishing'))
+ sendCommand(CMD_OPEN_HELP_CENTER);
else
- sendCommand(SB_CMD_SHOW_DIAGNOSTIC);
+ sendCommand(CMD_OPEN_DIAGNOSTIC);
});
}
- $('details-button').addEventListener('click', function(event) {
- var hiddenDetails = $('details').classList.toggle('hidden');
- $('details-button').innerText = hiddenDetails ?
- loadTimeData.getString('openDetails') :
- loadTimeData.getString('closeDetails');
- if (!expandedDetails) {
- // Record a histogram entry only the first time that details is opened.
- sendCommand(ssl ? SSL_CMD_MORE : SB_CMD_EXPANDED_SEE_MORE);
- expandedDetails = true;
- }
- });
+ if (captivePortal) {
+ // Captive portal page doesn't have details button.
+ $('details-button').classList.add('hidden');
+ } else {
+ $('details-button').addEventListener('click', function(event) {
+ var hiddenDetails = $('details').classList.toggle('hidden');
+
+ if (mobileNav) {
+ // Details appear over the main content on small screens.
+ $('main-content').classList.toggle('hidden', !hiddenDetails);
+ } else {
+ $('main-content').classList.remove('hidden');
+ }
+
+ $('details-button').innerText = hiddenDetails ?
+ loadTimeData.getString('openDetails') :
+ loadTimeData.getString('closeDetails');
+ if (!expandedDetails) {
+ // Record a histogram entry only the first time that details is opened.
+ sendCommand(CMD_SHOW_MORE_SECTION);
+ expandedDetails = true;
+ }
+ });
+ }
preventDefaultOnPoundLinkClicks();
- setupCheckbox();
+ setupExtendedReportingCheckbox();
setupSSLDebuggingInfo();
document.addEventListener('keypress', handleKeypress);
}
diff --git a/chromium/chrome/browser/resources/security_warnings/interstitial_v2_mobile.js b/chromium/chrome/browser/resources/security_warnings/interstitial_v2_mobile.js
new file mode 100644
index 00000000000..cb75d9f282b
--- /dev/null
+++ b/chromium/chrome/browser/resources/security_warnings/interstitial_v2_mobile.js
@@ -0,0 +1,47 @@
+// Copyright 2015 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.
+
+var mobileNav = false;
+
+/**
+ * For small screen mobile the navigation buttons are moved
+ * below the advanced text.
+ */
+function onResize() {
+ var helpOuterBox = document.querySelector('#details');
+ var mainContent = document.querySelector('#main-content');
+ var mediaQuery = '(min-width: 240px) and (max-width: 420px) and ' +
+ '(max-height: 736px) and (orientation: portrait),' +
+ '(max-width: 736px) and (max-height: 420px) and (orientation: landscape)';
+ var detailsHidden = helpOuterBox.classList.contains('hidden');
+ var runnerContainer = document.querySelector('.runner-container');
+
+ // Check for change in nav status.
+ if (mobileNav != window.matchMedia(mediaQuery).matches) {
+ mobileNav = !mobileNav;
+
+ // Handle showing the top content / details sections according to state.
+ if (mobileNav) {
+ mainContent.classList.toggle('hidden', !detailsHidden);
+ helpOuterBox.classList.toggle('hidden', detailsHidden);
+ if (runnerContainer) {
+ runnerContainer.classList.toggle('hidden', !detailsHidden);
+ }
+ } else if (!detailsHidden) {
+ // Non mobile nav with visible details.
+ mainContent.classList.remove('hidden');
+ helpOuterBox.classList.remove('hidden');
+ if (runnerContainer) {
+ runnerContainer.classList.remove('hidden');
+ }
+ }
+ }
+}
+
+function setupMobileNav() {
+ window.addEventListener('resize', onResize);
+ onResize();
+}
+
+document.addEventListener('DOMContentLoaded', setupMobileNav);
diff --git a/chromium/chrome/browser/resources/security_warnings/safe_browsing.js b/chromium/chrome/browser/resources/security_warnings/safe_browsing.js
deleted file mode 100644
index bb154f95bbc..00000000000
--- a/chromium/chrome/browser/resources/security_warnings/safe_browsing.js
+++ /dev/null
@@ -1,35 +0,0 @@
-// 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.
-
-// Must match the commands handled by SafeBrowsingBlockingPage::CommandReceived.
-var SB_CMD_DO_REPORT = 'doReport';
-var SB_CMD_DONT_REPORT = 'dontReport';
-var SB_CMD_EXPANDED_SEE_MORE = 'expandedSeeMore';
-var SB_CMD_LEARN_MORE_2 = 'learnMore2';
-var SB_CMD_PROCEED = 'proceed';
-var SB_CMD_SHOW_DIAGNOSTIC = 'showDiagnostic';
-var SB_CMD_SHOW_PRIVACY = 'showPrivacy';
-var SB_CMD_TAKE_ME_BACK = 'takeMeBack';
-
-// Other constants defined in safe_browsing_blocking_page.cc.
-var SB_BOX_CHECKED = 'boxchecked';
-var SB_DISPLAY_CHECK_BOX = 'displaycheckbox';
-
-// This sets up the Extended Safe Browsing Reporting opt-in.
-function setupCheckbox() {
- if (loadTimeData.getString('type') != 'SAFEBROWSING' ||
- loadTimeData.getBoolean('phishing') ||
- !loadTimeData.getBoolean(SB_DISPLAY_CHECK_BOX)) {
- return;
- }
-
- $('opt-in-label').innerHTML = loadTimeData.getString('optInLink');
- $('opt-in-checkbox').checked = loadTimeData.getBoolean(SB_BOX_CHECKED);
- $('malware-opt-in').classList.remove('hidden');
-
- $('opt-in-checkbox').addEventListener('click', function() {
- sendCommand(
- $('opt-in-checkbox').checked ? SB_CMD_DO_REPORT : SB_CMD_DONT_REPORT);
- });
-}
diff --git a/chromium/chrome/browser/resources/security_warnings/ssl.js b/chromium/chrome/browser/resources/security_warnings/ssl.js
index b135bed38f7..2c1a2b572bd 100644
--- a/chromium/chrome/browser/resources/security_warnings/ssl.js
+++ b/chromium/chrome/browser/resources/security_warnings/ssl.js
@@ -2,14 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-// Should match SSLBlockingPageCommands in ssl_blocking_page.cc.
-var SSL_CMD_DONT_PROCEED = 0;
-var SSL_CMD_PROCEED = 1;
-var SSL_CMD_MORE = 2;
-var SSL_CMD_RELOAD = 3;
-var SSL_CMD_HELP = 4;
-var SSL_CMD_CLOCK = 5;
-
function setupSSLDebuggingInfo() {
if (loadTimeData.getString('type') != 'SSL')
return;
diff --git a/chromium/chrome/browser/resources/set_as_default_browser.css b/chromium/chrome/browser/resources/set_as_default_browser.css
index 7466b836c5b..21d7a9ded06 100644
--- a/chromium/chrome/browser/resources/set_as_default_browser.css
+++ b/chromium/chrome/browser/resources/set_as_default_browser.css
@@ -20,11 +20,11 @@
/* The page block within the outer container. */
#metro-setup-outer-container .page {
- -webkit-border-radius: 3px;
-webkit-box-orient: vertical;
-webkit-user-select: none;
background: white;
background-color: white;
+ border-radius: 3px;
color: #333;
display: -webkit-box;
min-width: 40px;
@@ -95,31 +95,31 @@
}
#metro-action-box button:hover {
- -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
- -webkit-transition: all 0;
+ -webkit-transition: all 0ms;
+ box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
}
#metro-action-box button:focus {
- -webkit-box-shadow: inset 0 0 0 1px white;
+ box-shadow: inset 0 0 0 1px white;
outline: none;
z-index: 4 !important;
}
#metro-action-box button:active,
#metro-action-box button:focus:active {
- -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.3);
+ box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.3);
}
#metro-action-box button:focus:hover {
- -webkit-box-shadow: inset 0 0 0 1px white, 0 1px 1px rgba(0, 0, 0, 0.1);
+ box-shadow: inset 0 0 0 1px white, 0 1px 1px rgba(0, 0, 0, 0.1);
}
#metro-action-box button[disabled],
#metro-action-box button[disabled]:hover,
#metro-action-box button[disabled]:active {
- -webkit-box-shadow: none;
background-color: rgb(77, 144, 254);
border: 1px solid rgb(48, 121, 237);
+ box-shadow: none;
color: white;
opacity: 0.5;
}
diff --git a/chromium/chrome/browser/resources/set_as_default_browser.html b/chromium/chrome/browser/resources/set_as_default_browser.html
index 2f1b0830f1c..945ec326dd5 100644
--- a/chromium/chrome/browser/resources/set_as_default_browser.html
+++ b/chromium/chrome/browser/resources/set_as_default_browser.html
@@ -1,8 +1,8 @@
-<!DOCTYPE HTML>
+<!doctype html>
<!-- Copyright (c) 2012 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. -->
-<html i18n-values="dir:textdirection">
+<html i18n-values="dir:textdirection;lang:language">
<head>
<meta charset="utf-8">
<title i18n-content="page-title"></title>
@@ -16,7 +16,7 @@
<link rel="stylesheet" href="chrome://resources/css/widgets.css">
<link rel="stylesheet" href="set_as_default_browser.css">
</head>
-<body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<body>
<div id="metro-setup-outer-container">
<div id="metro-setup-overlay" class="page">
<div>
@@ -37,5 +37,5 @@
</div>
</div>
</body>
-<script src="chrome://resources/js/i18n_template2.js"></script>
+<script src="chrome://resources/js/i18n_template.js"></script>
</html>
diff --git a/chromium/chrome/browser/resources/settings/OWNERS b/chromium/chrome/browser/resources/settings/OWNERS
new file mode 100644
index 00000000000..f8f796966bc
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/OWNERS
@@ -0,0 +1,5 @@
+michaelpg@chromium.org
+stevenjb@chromium.org
+khorimoto@chromium.org
+orenb@chromium.org
+jlklein@chromium.org
diff --git a/chromium/chrome/browser/resources/settings/a11y_page/a11y_page.css b/chromium/chrome/browser/resources/settings/a11y_page/a11y_page.css
new file mode 100644
index 00000000000..65537ee02ff
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/a11y_page/a11y_page.css
@@ -0,0 +1,20 @@
+/* Copyright 2015 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. */
+
+.autoclick-delay-label {
+ -webkit-margin-end: 0;
+ -webkit-margin-start: 40px;
+ align-items: center;
+ display: flex;
+ margin-bottom: 10px;
+ margin-top: 0;
+}
+
+#autoclickDropdown {
+ -webkit-margin-start: 10px;
+}
+
+.more-a11y-link {
+ margin-bottom: 10px;
+}
diff --git a/chromium/chrome/browser/resources/settings/a11y_page/a11y_page.html b/chromium/chrome/browser/resources/settings/a11y_page/a11y_page.html
new file mode 100644
index 00000000000..17395b6bc2d
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/a11y_page/a11y_page.html
@@ -0,0 +1,82 @@
+<link rel="import" href="chrome://resources/polymer/v0_8/polymer/polymer.html">
+<link rel="import" href="chrome://resources/polymer/v0_8/paper-material/paper-material.html">
+<link rel="import" href="chrome://md-settings/checkbox/checkbox.html">
+
+<dom-module id="cr-settings-a11y-page">
+ <link rel="import" type="css"
+ href="chrome://md-settings/settings_page/settings_page.css">
+ <link rel="import" type="css" href="a11y_page.css">
+ <template>
+ <paper-material>
+ <div class="more-a11y-link">
+ <a href="https://chrome.google.com/webstore/category/collection/accessibility"
+ target="_blank" i18n-content="accessibilityMoreFeaturesLink"></a>
+ </div>
+
+<if expr="chromeos">
+ <cr-settings-checkbox
+ pref="{{prefs.settings.a11y.enable_menu}}"
+ i18n-values="label:accessibilityOptionsInMenuLabel">
+ </cr-settings-checkbox>
+ <cr-settings-checkbox
+ pref="{{prefs.settings.a11y.large_cursor_enabled}}"
+ i18n-values="label:accessibilityLargeMouseCursorLabel">
+ </cr-settings-checkbox>
+ <cr-settings-checkbox
+ pref="{{prefs.settings.a11y.high_contrast_enabled}}"
+ i18n-values="label:accessibilityHighContrastLabel">
+ </cr-settings-checkbox>
+ <cr-settings-checkbox
+ pref="{{prefs.settings.a11y.sticky_keys_enabled}}"
+ i18n-values="label:accessibilityStickyKeysLabel;
+ subLabel:accessibilityStickyKeysSublabel">
+ </cr-settings-checkbox>
+ <cr-settings-checkbox pref="{{prefs.settings.accessibility}}"
+ i18n-values="label:accessibilityChromeVoxLabel;
+ subLabel:accessibilityChromeVoxSublabel">
+ </cr-settings-checkbox>
+ <cr-settings-checkbox
+ pref="{{prefs.settings.a11y.screen_magnifier}}"
+ i18n-values="label:accessibilityScreenMagnifierLabel">
+ </cr-settings-checkbox>
+ <cr-settings-checkbox
+ pref="{{prefs.settings.touchpad.enable_tap_dragging}}"
+ i18n-values="label:accessibilityTapDraggingLabel">
+ </cr-settings-checkbox>
+ <cr-settings-checkbox pref="{{prefs.settings.a11y.autoclick}}"
+ i18n-values="label:accessibilityClickOnStopLabel">
+ </cr-settings-checkbox>
+
+ <div class="autoclick-delay-label"
+ hidden$="{{!prefs.settings.a11y.autoclick}}">
+ <span i18n-content="accessibilityDelayBeforeClickLabel"></span>
+ <select id="autoclickDropdown"
+ value="{{prefs.settings.a11y.autoclick_delay_ms::change}}">
+ <option value="200"
+ i18n-content="accessibilityDelayBeforeClickExtremelyShort">
+ </option>
+ <option value="400"
+ i18n-content="accessibilityDelayBeforeClickVeryShort">
+ </option>
+ <option value="600"
+ i18n-content="accessibilityDelayBeforeClickShort">
+ </option>
+ <option value="800"
+ i18n-content="accessibilityDelayBeforeClickLong">
+ </option>
+ <option value="1000"
+ i18n-content="accessibilityDelayBeforeClickVeryLong">
+ </option>
+ </select>
+ </div>
+
+ <cr-settings-checkbox
+ pref="{{prefs.settings.a11y.virtual_keyboard}}"
+ i18n-values="label:accessibilityOnScreenKeyboardLabel">
+ </cr-settings-checkbox>
+</if>
+
+ </paper-material>
+ </template>
+ <script src="a11y_page.js"></script>
+</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/a11y_page/a11y_page.js b/chromium/chrome/browser/resources/settings/a11y_page/a11y_page.js
new file mode 100644
index 00000000000..119f1c75a8e
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/a11y_page/a11y_page.js
@@ -0,0 +1,72 @@
+// Copyright 2015 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
+ * 'cr-settings-a11y-page' is the settings page containing accessibility
+ * settings.
+ *
+ * Example:
+ *
+ * <iron-animated-pages>
+ * <cr-settings-a11y-page prefs="{{prefs}}"></cr-settings-a11y-page>
+ * ... other pages ...
+ * </iron-animated-pages>
+ *
+ * @group Chrome Settings Elements
+ * @element cr-settings-a11y-page
+ */
+Polymer({
+ is: 'cr-settings-a11y-page',
+
+ properties: {
+ /**
+ * Preferences state.
+ */
+ prefs: {
+ type: Object,
+ notify: true,
+ },
+
+ /**
+ * Route for the page.
+ */
+ route: String,
+
+ /**
+ * Whether the page is a subpage.
+ */
+ subpage: {
+ type: Boolean,
+ value: false,
+ readOnly: true,
+ },
+
+ /**
+ * ID of the page.
+ */
+ PAGE_ID: {
+ type: String,
+ value: 'a11y',
+ readOnly: true,
+ },
+
+ /**
+ * Title for the page header and navigation menu.
+ */
+ pageTitle: {
+ type: String,
+ value: function() { return loadTimeData.getString('a11yPageTitle'); },
+ },
+
+ /**
+ * Name of the 'iron-icon' to show.
+ */
+ icon: {
+ type: String,
+ value: 'accessibility',
+ readOnly: true,
+ },
+ },
+});
diff --git a/chromium/chrome/browser/resources/settings/checkbox/checkbox.css b/chromium/chrome/browser/resources/settings/checkbox/checkbox.css
new file mode 100644
index 00000000000..0acbdbd9744
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/checkbox/checkbox.css
@@ -0,0 +1,19 @@
+/* Copyright 2015 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. */
+
+#checkbox {
+ -webkit-margin-end: 10px;
+}
+
+core-label {
+ -webkit-margin-end: 10px;
+ -webkit-margin-start: 0;
+ margin-bottom: 10px;
+ margin-top: 10px;
+}
+
+.sub-label {
+ -webkit-margin-start: 10px;
+ color: rgba(0, 0, 0, .5);
+}
diff --git a/chromium/chrome/browser/resources/settings/checkbox/checkbox.html b/chromium/chrome/browser/resources/settings/checkbox/checkbox.html
new file mode 100644
index 00000000000..ea3784d66f4
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/checkbox/checkbox.html
@@ -0,0 +1,19 @@
+<link rel="import" href="chrome://resources/polymer/v0_8/polymer/polymer.html">
+<link rel="import" href="chrome://resources/cr_elements/v0_8/cr_checkbox/cr_checkbox.html">
+<link rel="import" href="chrome://resources/cr_elements/v0_8/cr_events/cr_events.html">
+<link rel="import" href="chrome://md-settings/pref_tracker/pref_tracker.html">
+
+<dom-module id="cr-settings-checkbox">
+ <link rel="import" type="css" href="checkbox.css">
+ <template>
+ <cr-events id="events"></cr-events>
+ <cr-settings-pref-tracker pref="[[pref]]"></cr-settings-pref-tracker>
+
+ <cr-checkbox id="checkbox" checked="{{pref.value}}"
+ disabled="[[pref.disabled]]">
+ <span>{{label}}</span>
+ <span class="sub-label">{{subLabel}}</span>
+ </cr-checkbox>
+ </template>
+ <script src="checkbox.js"></script>
+</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/checkbox/checkbox.js b/chromium/chrome/browser/resources/settings/checkbox/checkbox.js
new file mode 100644
index 00000000000..6e0860cf00b
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/checkbox/checkbox.js
@@ -0,0 +1,44 @@
+// Copyright 2015 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
+ * `cr-settings-checkbox` is a checkbox that controls a supplied preference.
+ *
+ * Example:
+ * <cr-settings-checkbox pref="{{prefs.settings.enableFoo}}"
+ * label="Enable foo setting." subLabel="(bar also)">
+ * </cr-settings-checkbox>
+ *
+ * @element cr-settings-checkbox
+ */
+Polymer({
+ is: 'cr-settings-checkbox',
+
+ properties: {
+ /**
+ * The boolean preference object to control.
+ * @type {?chrome.settingsPrivate.PrefObject}
+ */
+ pref: {
+ type: Object,
+ notify: true,
+ },
+
+ label: {
+ type: String,
+ value: '',
+ },
+
+ subLabel: {
+ type: String,
+ value: '',
+ },
+ },
+
+ /** @override */
+ ready: function() {
+ this.$.events.forward(this.$.checkbox, ['change']);
+ },
+});
diff --git a/chromium/chrome/browser/resources/settings/date_time_page/date_time_page.css b/chromium/chrome/browser/resources/settings/date_time_page/date_time_page.css
new file mode 100644
index 00000000000..64b2a421354
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/date_time_page/date_time_page.css
@@ -0,0 +1,14 @@
+/* Copyright 2015 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. */
+
+.time-zone-label {
+ -webkit-margin-end: 20px;
+ -webkit-margin-start: 10px;
+ margin-bottom: 10px;
+ margin-top: 10px;
+}
+
+#setAutomatically {
+ margin: 10px;
+}
diff --git a/chromium/chrome/browser/resources/settings/date_time_page/date_time_page.html b/chromium/chrome/browser/resources/settings/date_time_page/date_time_page.html
new file mode 100644
index 00000000000..f3c177efdb2
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/date_time_page/date_time_page.html
@@ -0,0 +1,22 @@
+<link rel="import" href="chrome://md-settings/checkbox/checkbox.html">
+<link rel="import" href="chrome://resources/polymer/v0_8/polymer/polymer.html">
+<link rel="import" href="chrome://resources/polymer/v0_8/iron-flex-layout/classes/iron-flex-layout.html">
+<link rel="import" href="chrome://resources/polymer/v0_8/paper-material/paper-material.html">
+
+<dom-module id="cr-settings-date-time-page">
+ <link rel="import" type="css" href="date_time_page.css">
+ <link rel="import" type="css" href="chrome://md-settings/settings_page/settings_page.css">
+ <template>
+ <paper-material class="layout veritcal">
+ <div class="horizontal layout center">
+ <span class="time-zone-label" i18n-content="dateTimeTimeZoneLabel"></span>
+ </div>
+ <cr-settings-checkbox
+ pref="{{prefs.settings.clock.use_24hour_clock}}"
+ i18n-values="label:dateTime24HourClockLabel">
+ </cr-settings-checkbox>
+ <span id="setAutomatically" i18n-content="dateTimeAutomaticallySet"></span>
+ </paper-material>
+ </template>
+ <script src="date_time_page.js"></script>
+</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/date_time_page/date_time_page.js b/chromium/chrome/browser/resources/settings/date_time_page/date_time_page.js
new file mode 100644
index 00000000000..a5cc610e1c6
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/date_time_page/date_time_page.js
@@ -0,0 +1,76 @@
+// Copyright 2015 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
+ * 'cr-settings-date-time-page' is the settings page containing date-time
+ * settings.
+ *
+ * Example:
+ *
+ * <core-animated-pages>
+ * <cr-settings-date-time-page prefs="{{prefs}}">
+ * </cr-settings-date-time-page>
+ * ... other pages ...
+ * </core-animated-pages>
+ *
+ * @group Chrome Settings Elements
+ * @element cr-settings-date-time-page
+ */
+Polymer({
+ is: 'cr-settings-date-time-page',
+
+ properties: {
+ /**
+ * Preferences state.
+ */
+ prefs: {
+ type: Object,
+ notify: true
+ },
+
+ /**
+ * Route for the page.
+ */
+ route: {
+ type: String,
+ value: ''
+ },
+
+ /**
+ * Whether the page is a subpage.
+ */
+ subpage: {
+ type: Boolean,
+ value: false,
+ readOnly: true
+ },
+
+ /**
+ * ID of the page.
+ */
+ PAGE_ID: {
+ type: String,
+ value: 'date-time',
+ readOnly: true
+ },
+
+ /**
+ * Title for the page header and navigation menu.
+ */
+ pageTitle: {
+ type: String,
+ value: function() { return loadTimeData.getString('dateTimePageTitle'); }
+ },
+
+ /**
+ * Name of the 'core-icon' to show.
+ */
+ icon: {
+ type: String,
+ value: 'device:access-time',
+ readOnly: true
+ },
+ },
+});
diff --git a/chromium/chrome/browser/resources/settings/date_time_page/demo.html b/chromium/chrome/browser/resources/settings/date_time_page/demo.html
new file mode 100644
index 00000000000..919675d929a
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/date_time_page/demo.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<html>
+<head>
+ <link href="date_time_page.html" rel="import">
+ <link href="../prefs/prefs.html" rel="import">
+ <script src="demo.js"></script>
+</head>
+<body unresolved>
+ <cr-settings-prefs></cr-settings-prefs>
+ <cr-settings-date-time-page></cr-settings-date-time-page>
+</body>
+</html>
diff --git a/chromium/chrome/browser/resources/settings/date_time_page/demo.js b/chromium/chrome/browser/resources/settings/date_time_page/demo.js
new file mode 100644
index 00000000000..612cabe4e39
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/date_time_page/demo.js
@@ -0,0 +1,9 @@
+// Copyright 2015 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.
+
+// Wire up the prefs to the date/time page.
+window.addEventListener('polymer-ready', function() {
+ var page = document.querySelector('cr-settings-date-time-page');
+ page.prefs = document.querySelector('cr-settings-prefs');
+});
diff --git a/chromium/chrome/browser/resources/settings/downloads_page/downloads_page.css b/chromium/chrome/browser/resources/settings/downloads_page/downloads_page.css
new file mode 100644
index 00000000000..5a47a431dee
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/downloads_page/downloads_page.css
@@ -0,0 +1,14 @@
+/* Copyright 2015 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. */
+
+cr-input {
+ /* This ensures that the "Download location" label and the text in the input
+ * itself are vertically aligned. */
+ margin-bottom: 1em;
+ width: 300px;
+}
+
+#locationLabel {
+ -webkit-margin-end: 10px;
+}
diff --git a/chromium/chrome/browser/resources/settings/downloads_page/downloads_page.html b/chromium/chrome/browser/resources/settings/downloads_page/downloads_page.html
new file mode 100644
index 00000000000..b270b5de5cc
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/downloads_page/downloads_page.html
@@ -0,0 +1,33 @@
+<link rel="import" href="chrome://resources/polymer/v0_8/polymer/polymer.html">
+<link rel="import" href="chrome://resources/polymer/v0_8/iron-flex-layout/classes/iron-flex-layout.html">
+<link rel="import" href="chrome://resources/polymer/v0_8/paper-input/paper-input.html">
+<link rel="import" href="chrome://resources/polymer/v0_8/paper-material/paper-material.html">
+<link rel="import" href="chrome://resources/cr_elements/v0_8/cr_button/cr_button.html">
+<link rel="import" href="chrome://resources/cr_elements/v0_8/cr_input/cr_input.html">
+<link rel="import" href="chrome://md-settings/checkbox/checkbox.html">
+
+<dom-module id="cr-settings-downloads-page">
+ <link rel="import" type="css" href="chrome://md-settings/settings_page/settings_page.css">
+ <link rel="import" type="css" href="downloads_page.css">
+ <template>
+ <paper-material>
+ <div class="horizontal layout center">
+ <div class="layout horizontal center">
+ <div id="locationLabel" i18n-content="downloadsLocationLabel"></div>
+ <cr-input id="downloadsPath" floating-label="false"
+ value="{{prefs.download.default_directory.value}}"
+ readonly aria-labelledby="locationLabel">
+ </cr-input>
+ </div>
+ <cr-button id="changeDownloadsPath" on-click="selectDownloadLocation_"
+ i18n-content="downloadsChangeLocationButton">
+ </cr-button>
+ </div>
+ <cr-settings-checkbox
+ pref="{{prefs.download.prompt_for_download}}"
+ i18n-values="label:downloadsPromptForDownloadLabel">
+ </cr-settings-checkbox>
+ </paper-material>
+ </template>
+ <script src="downloads_page.js"></script>
+</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/downloads_page/downloads_page.js b/chromium/chrome/browser/resources/settings/downloads_page/downloads_page.js
new file mode 100644
index 00000000000..c252bc58470
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/downloads_page/downloads_page.js
@@ -0,0 +1,82 @@
+// Copyright 2015 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
+ * 'cr-settings-downloads-page' is the settings page containing downloads
+ * settings.
+ *
+ * Example:
+ *
+ * <iron-animated-pages>
+ * <cr-settings-downloads-page prefs="{{prefs}}">
+ * </cr-settings-downloads-page>
+ * ... other pages ...
+ * </iron-animated-pages>
+ *
+ * @group Chrome Settings Elements
+ * @element cr-settings-downloads-page
+ */
+Polymer({
+ is: 'cr-settings-downloads-page',
+
+ properties: {
+ /**
+ * Preferences state.
+ */
+ prefs: {
+ type: Object,
+ notify: true,
+ },
+
+ /**
+ * Route for the page.
+ */
+ route: String,
+
+ /**
+ * Whether the page is a subpage.
+ */
+ subpage: {
+ type: Boolean,
+ value: false,
+ readOnly: true,
+ },
+
+ /**
+ * ID of the page.
+ */
+ PAGE_ID: {
+ type: String,
+ value: 'downloads',
+ readOnly: true,
+ },
+
+ /**
+ * Title for the page header and navigation menu.
+ */
+ pageTitle: {
+ type: String,
+ value: function() {
+ return loadTimeData.getString('downloadsPageTitle');
+ },
+ },
+
+ /**
+ * Name of the 'iron-icon' to show.
+ */
+ icon: {
+ type: String,
+ value: 'file-download',
+ readOnly: true,
+ },
+ },
+
+ /** @private */
+ selectDownloadLocation_: function() {
+ // TODO(orenb): Communicate with the C++ to actually display a folder
+ // picker.
+ this.$.downloadsPath.value = '/Downloads';
+ },
+});
diff --git a/chromium/chrome/browser/resources/settings/internet_page/internet_detail_page.html b/chromium/chrome/browser/resources/settings/internet_page/internet_detail_page.html
new file mode 100644
index 00000000000..dd32da2924a
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/internet_page/internet_detail_page.html
@@ -0,0 +1,49 @@
+<link rel="import" href="chrome://resources/polymer/polymer/polymer.html">
+<link rel="import" href="chrome://resources/cr_elements/cr_onc/cr_onc_data.html">
+<link rel="import" href="internet_detail_page_style.html">
+
+<polymer-element name="cr-settings-internet-detail-page">
+ <template>
+ <link rel="stylesheet" href="chrome://md-settings/settings_page/settings_page.css">
+ <core-style ref="internetDetailPageStyle"></core-style>
+ <paper-shadow layout vertical cross-fade>
+ <div vertical layout>
+ <div id="titleDiv" horizontal layout>
+ <div center horizontal layout flex>
+ <cr-network-icon id="networkIcon" networkState="{{networkState}}">
+ </cr-network-icon>
+ <span id="networkName">{{getStateName_(networkState)}}</span>
+ <span id="networkState"
+ class="{{ {connected: isConnectedState_(networkState)} | tokenList }}">
+ {{getStateText_(networkState)}}
+ </span>
+ </div>
+ <div center horizontal layout center-justified
+ id="connectionButtonDiv">
+ <cr-button hidden?="{{!canDisconnect_(networkState)}}"
+ on-click="{{onDisconnectClicked_}}">
+ Disconnect
+ </cr-button>
+ <cr-button hidden?="{{!canConnect_(networkState)}}"
+ on-click="{{onConnectClicked_}}">
+ Connect
+ </cr-button>
+ </div>
+ </div>
+ <div id="detailDiv" vertical layout>
+ <div vertical layout hidden?="{{!isConnectedState_(networkState)}}">
+ <span>{{getProperty_(networkState, 'MacAddress')}}</span>
+ </div>
+ <div vertical layout hidden?="{{networkState.data.Type != 'WiFi'}}">
+ <span>{{getProperty_(networkState, 'WiFi.Security')}}</span>
+ <span>{{getProperty_(networkState, 'WiFi.SSID')}}</span>
+ <span>{{getProperty_(networkState, 'WiFi.BSSID')}}</span>
+ <span>{{getProperty_(networkState, 'WiFi.SignalStrength')}}</span>
+ <span>{{getProperty_(networkState, 'WiFi.Frequency')}}</span>
+ </div>
+ </div>
+ </div>
+ </paper-shadow>
+ </template>
+ <script src="internet_detail_page.js"></script>
+</polymer-element>
diff --git a/chromium/chrome/browser/resources/settings/internet_page/internet_detail_page.js b/chromium/chrome/browser/resources/settings/internet_page/internet_detail_page.js
new file mode 100644
index 00000000000..9e7d359a3a1
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/internet_page/internet_detail_page.js
@@ -0,0 +1,216 @@
+// Copyright 2015 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
+ * 'cr-settings-internet-detail' is the settings subpage containing details
+ * for a network.
+ *
+ * @group Chrome Settings Elements
+ * @element cr-settings-internet-detail
+ */
+(function() {
+
+/** @typedef {chrome.networkingPrivate.NetworkStateProperties} */
+var NetworkStateProperties;
+
+Polymer('cr-settings-internet-detail-page', {
+ publish: {
+ /**
+ * ID of the page.
+ *
+ * @attribute PAGE_ID
+ * @const {string}
+ */
+ PAGE_ID: 'internet-detail',
+
+ /**
+ * Route for the page.
+ *
+ * @attribute route
+ * @type {string}
+ * @default ''
+ */
+ route: '',
+
+ /**
+ * Whether the page is a subpage.
+ *
+ * @attribute subpage
+ * @type {boolean}
+ * @default false
+ */
+ subpage: false,
+
+ /**
+ * Title for the page header and navigation menu.
+ *
+ * @attribute pageTitle
+ * @type {string}
+ */
+ pageTitle: loadTimeData.getString('internetDetailPageTitle'),
+
+ /**
+ * Name of the 'core-icon' to show. TODO(stevenjb): Update this with the
+ * icon for the network.
+ *
+ * @attribute icon
+ * @type {string}
+ * @default 'settings-ethernet'
+ */
+ icon: 'settings-ethernet',
+
+ /**
+ * The network GUID to display details for.
+ *
+ * @attribute guid
+ * @type {string}
+ * @default ''
+ */
+ guid: '',
+
+ /**
+ * The current state for the network matching |guid|.
+ *
+ * @attribute networkState
+ * @type {?CrOncDataElement}
+ * @default null
+ */
+ networkState: null,
+ },
+
+ /**
+ * Listener function for chrome.networkingPrivate.onNetworksChanged event.
+ * @type {?function(!Array<string>)}
+ * @private
+ */
+ networksChangedListener_: null,
+
+ /** @override */
+ attached: function() {
+ this.networksChangedListener_ = this.onNetworksChangedEvent_.bind(this);
+ chrome.networkingPrivate.onNetworksChanged.addListener(
+ this.networksChangedListener_);
+ },
+
+ /** @override */
+ detached: function() {
+ chrome.networkingPrivate.onNetworksChanged.removeListener(
+ this.networksChangedListener_);
+ },
+
+ /**
+ * Polymer guid changed method.
+ */
+ guidChanged: function() {
+ this.getNetworkDetails_();
+ },
+
+ /**
+ * networkingPrivate.onNetworksChanged event callback.
+ * @param {!Array<string>} networkIds The list of changed network GUIDs.
+ * @private
+ */
+ onNetworksChangedEvent_: function(networkIds) {
+ if (networkIds.indexOf(this.guid) != -1)
+ this.getNetworkDetails_();
+ },
+
+ /**
+ * Calls networkingPrivate.getState for this.guid.
+ * @private
+ */
+ getNetworkDetails_: function() {
+ if (!this.guid)
+ return;
+ chrome.networkingPrivate.getProperties(
+ this.guid, this.getPropertiesCallback_.bind(this));
+ },
+
+ /**
+ * networkingPrivate.getProperties callback.
+ * @param {!NetworkStateProperties} state The network state properties.
+ * @private
+ */
+ getPropertiesCallback_: function(state) {
+ this.networkState = CrOncDataElement.create(state);
+ },
+
+ /**
+ * @param {?CrOncDataElement} state The network state properties.
+ * @return {string} The text to display for the network name.
+ * @private
+ */
+ getStateName_: function(state) {
+ return state && state.data.Name;
+ },
+
+ /**
+ * @param {?CrOncDataElement} state The network state properties.
+ * @return {string} The text to display for the network connection state.
+ * @private
+ */
+ getStateText_: function(state) {
+ // TODO(stevenjb): Localize.
+ return state && state.data.ConnectionState;
+ },
+
+ /**
+ * @param {?CrOncDataElement} state The network state properties.
+ * @param {string} property The property name.
+ * @return {string} The text to display for the property, including the label.
+ * @private
+ */
+ getProperty_: function(state, property) {
+ if (!state)
+ return '';
+ // TODO(stevenjb): Localize.
+ var value = state.getProperty(property) || '';
+ return property + ': ' + value;
+ },
+
+ /**
+ * @param {?CrOncDataElement} state The network state properties.
+ * @return {boolean} Whether or not the state is connected.
+ * @private
+ */
+ isConnectedState_: function(state) {
+ return state && state.connected();
+ },
+
+ /**
+ * @param {?CrOncDataElement} state The network state properties.
+ * @return {boolean} Whether or not the network can be connected.
+ * @private
+ */
+ canConnect_: function(state) {
+ return state && state.data.Type != 'Ethernet' && state.disconnected();
+ },
+
+ /**
+ * @param {?CrOncDataElement} state The network state properties.
+ * @return {boolean} Whether or not the network can be disconnected.
+ * @private
+ */
+ canDisconnect_: function(state) {
+ return state && state.data.Type != 'Ethernet' && !state.disconnected();
+ },
+
+ /**
+ * Callback when the Connect button is clicked.
+ * @private
+ */
+ onConnectClicked_: function() {
+ chrome.networkingPrivate.startConnect(this.guid);
+ },
+
+ /**
+ * Callback when the Disconnect button is clicked.
+ * @private
+ */
+ onDisconnectClicked_: function() {
+ chrome.networkingPrivate.startDisconnect(this.guid);
+ },
+});
+})();
diff --git a/chromium/chrome/browser/resources/settings/internet_page/internet_detail_page_style.html b/chromium/chrome/browser/resources/settings/internet_page/internet_detail_page_style.html
new file mode 100644
index 00000000000..19c8c8ee019
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/internet_page/internet_detail_page_style.html
@@ -0,0 +1,38 @@
+<link rel="import" href="chrome://resources/polymer/polymer/polymer.html">
+<link rel="import" href="chrome://resources/polymer/core-style/core-style.html">
+
+<core-style id="internetDetailPageStyle">
+
+#networkIcon {
+ height: 32px;
+ width: 32px;
+}
+
+#networkName {
+ -webkit-margin-start: 10px;
+ font-size: 20px;
+ font-weight: bold;
+}
+
+#networkState {
+ -webkit-margin-start: 10px;
+ font-size: 20px;
+}
+
+#networkState.connected {
+ color: green;
+}
+
+#connectionButtonDiv {
+ -webkit-margin-end: 30px;
+}
+
+#detailDiv {
+ margin: 10px 0 0 45px;
+}
+
+#detailDiv span {
+ margin-bottom: 10px;
+}
+
+</core-style>
diff --git a/chromium/chrome/browser/resources/settings/internet_page/internet_page.html b/chromium/chrome/browser/resources/settings/internet_page/internet_page.html
new file mode 100644
index 00000000000..0b1b285cff4
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/internet_page/internet_page.html
@@ -0,0 +1,12 @@
+<link rel="import" href="chrome://resources/polymer/polymer/polymer.html">
+<link rel="import" href="network_summary.html">
+
+<polymer-element name="cr-settings-internet-page">
+ <template>
+ <link rel="stylesheet" href="chrome://md-settings/settings_page/settings_page.css">
+ <paper-shadow layout vertical cross-fade>
+ <cr-network-summary></cr-network-summary>
+ </paper-shadow>
+ </template>
+ <script src="internet_page.js"></script>
+</polymer-element>
diff --git a/chromium/chrome/browser/resources/settings/internet_page/internet_page.js b/chromium/chrome/browser/resources/settings/internet_page/internet_page.js
new file mode 100644
index 00000000000..6c3c8721dcc
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/internet_page/internet_page.js
@@ -0,0 +1,67 @@
+// Copyright 2015 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
+ * 'cr-settings-internet-page' is the settings page containing internet
+ * settings.
+ *
+ * Example:
+ *
+ * <core-animated-pages>
+ * <cr-settings-internet-page prefs="{{prefs}}">
+ * </cr-settings-internet-page>
+ * ... other pages ...
+ * </core-animated-pages>
+ *
+ * @group Chrome Settings Elements
+ * @element cr-settings-internet-page
+ */
+Polymer('cr-settings-internet-page', {
+ publish: {
+ /**
+ * ID of the page.
+ *
+ * @attribute PAGE_ID
+ * @const {string}
+ */
+ PAGE_ID: 'internet',
+
+ /**
+ * Route for the page.
+ *
+ * @attribute route
+ * @type {string}
+ * @default ''
+ */
+ route: '',
+
+ /**
+ * Whether the page is a subpage.
+ *
+ * @attribute subpage
+ * @type {boolean}
+ * @default false
+ */
+ subpage: false,
+
+ /**
+ * Title for the page header and navigation menu.
+ *
+ * @attribute pageTitle
+ * @type {string}
+ */
+ pageTitle: loadTimeData.getString('internetPageTitle'),
+
+ /**
+ * Name of the 'core-icon' to show. TODO(stevenjb): Update this with the
+ * icon for the active internet connection.
+ *
+ * @attribute icon
+ * @type {string}
+ * @default 'settings-ethernet'
+ */
+ icon: 'settings-ethernet',
+ },
+});
diff --git a/chromium/chrome/browser/resources/settings/internet_page/network_summary.html b/chromium/chrome/browser/resources/settings/internet_page/network_summary.html
new file mode 100644
index 00000000000..8feb964e7ef
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/internet_page/network_summary.html
@@ -0,0 +1,49 @@
+<link rel="import" href="chrome://resources/polymer/polymer/polymer.html">
+<link rel="import" href="chrome://resources/polymer/core-style/core-style.html">
+<link rel="import" href="chrome://resources/cr_elements/cr_onc/cr_onc_data.html">
+<link rel="import" href="chrome://resources/polymer/v0_8/more-routing/more-routing.html">
+<link rel="import" href="network_summary_item.html">
+<link rel="import" href="network_summary_style.html">
+
+<polymer-element name="cr-network-summary">
+ <template>
+ <core-style ref="networkSummaryStyle"></core-style>
+ <div id="summary" vertical layout>
+ <cr-network-summary-item
+ deviceState="{{deviceStates.Ethernet}}"
+ networkState="{{networkStates.Ethernet}}"
+ networkStateList="{{networkStateLists.Ethernet}}"
+ on-selected="{{onSelected_}}">
+ </cr-network-summary-item>
+ <cr-network-summary-item
+ deviceState="{{deviceStates.WiFi}}"
+ networkState="{{networkStates.WiFi}}"
+ networkStateList="{{networkStateLists.WiFi}}"
+ on-expanded="{{onWiFiExpanded_}}"
+ on-selected="{{onSelected_}}"
+ on-device-enabled-toggled="{{onDeviceEnabledToggled_}}">
+ </cr-network-summary-item>
+ <cr-network-summary-item
+ deviceState="{{deviceStates.Cellular}}"
+ networkState="{{networkStates.Cellular}}"
+ networkStateList="{{networkStateLists.Cellular}}"
+ on-selected="{{onSelected_}}"
+ on-device-enabled-toggled="{{onDeviceEnabledToggled_}}">
+ </cr-network-summary-item>
+ <cr-network-summary-item
+ deviceState="{{deviceStates.WiMAX}}"
+ networkState="{{networkStates.WiMAX}}"
+ networkStateList="{{networkStateLists.WiMAX}}"
+ on-selected="{{onSelected_}}"
+ on-device-enabled-toggled="{{onDeviceEnabledToggled_}}">
+ </cr-network-summary-item>
+ <cr-network-summary-item
+ deviceState="{{deviceStates.VPN}}"
+ networkState="{{networkStates.VPN}}"
+ networkStateList="{{networkStateLists.VPN}}"
+ on-selected="{{onSelected_}}">
+ </cr-network-summary-item>
+ </div>
+ </template>
+ <script src="network_summary.js"></script>
+</polymer-element>
diff --git a/chromium/chrome/browser/resources/settings/internet_page/network_summary.js b/chromium/chrome/browser/resources/settings/internet_page/network_summary.js
new file mode 100644
index 00000000000..186b885c93d
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/internet_page/network_summary.js
@@ -0,0 +1,343 @@
+// Copyright 2015 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 Polymer element for displaying a summary of network states
+ * by type: Ethernet, WiFi, Cellular, WiMAX, and VPN.
+ */
+(function() {
+
+/** @typedef {chrome.networkingPrivate.DeviceStateProperties} */
+var DeviceStateProperties;
+
+/** @typedef {chrome.networkingPrivate.NetworkStateProperties} */
+var NetworkStateProperties;
+
+/**
+ * @typedef {{
+ * Ethernet: (DeviceStateProperties|undefined),
+ * WiFi: (DeviceStateProperties|undefined),
+ * Cellular: (DeviceStateProperties|undefined),
+ * WiMAX: (DeviceStateProperties|undefined),
+ * VPN: (DeviceStateProperties|undefined)
+ * }}
+ */
+var DeviceStateObject;
+
+/**
+ * @typedef {{
+ * Ethernet: (CrOncDataElement|undefined),
+ * WiFi: (CrOncDataElement|undefined),
+ * Cellular: (CrOncDataElement|undefined),
+ * WiMAX: (CrOncDataElement|undefined),
+ * VPN: (CrOncDataElement|undefined)
+ * }}
+ */
+var NetworkStateObject;
+
+/**
+ * @typedef {{
+ * Ethernet: (Array<CrOncDataElement>|undefined),
+ * WiFi: (Array<CrOncDataElement>|undefined),
+ * Cellular: (Array<CrOncDataElement>|undefined),
+ * WiMAX: (Array<CrOncDataElement>|undefined),
+ * VPN: (Array<CrOncDataElement>|undefined)
+ * }}
+ */
+var NetworkStateListObject;
+
+/** @const {!Array<string>} */
+var NETWORK_TYPES = ['Ethernet', 'WiFi', 'Cellular', 'WiMAX', 'VPN'];
+
+Polymer('cr-network-summary', {
+ publish: {
+ /**
+ * The device state for each network device type.
+ *
+ * @attribute deviceStates
+ * @type {?DeviceStateObject}
+ * @default null
+ */
+ deviceStates: null,
+
+ /**
+ * Network state data for each network type.
+ *
+ * @attribute networkStates
+ * @type {?NetworkStateObject}
+ * @default null
+ */
+ networkStates: null,
+
+ /**
+ * List of network state data for each network type.
+ *
+ * @attribute networkStateLists
+ * @type {?NetworkStateListObject}
+ * @default null
+ */
+ networkStateLists: null,
+ },
+
+ /**
+ * Listener function for chrome.networkingPrivate.onNetworkListChanged event.
+ * @type {?function(!Array<string>)}
+ * @private
+ */
+ networkListChangedListener_: null,
+
+ /**
+ * Listener function for chrome.networkingPrivate.onDeviceStateListChanged
+ * event.
+ * @type {?function(!Array<string>)}
+ * @private
+ */
+ deviceStateListChangedListener_: null,
+
+ /**
+ * Listener function for chrome.networkingPrivate.onNetworksChanged event.
+ * @type {?function(!Array<string>)}
+ * @private
+ */
+ networksChangedListener_: null,
+
+ /**
+ * Dictionary of GUIDs identifying primary (active) networks for each type.
+ * @type {?Object}
+ * @private
+ */
+ networkIds_: null,
+
+ /** @override */
+ created: function() {
+ this.deviceStates = {};
+ this.networkStates = {};
+ this.networkStateLists = {};
+ this.networkIds_ = {};
+ },
+
+ /** @override */
+ attached: function() {
+ this.getNetworkLists_();
+
+ this.networkListChangedListener_ =
+ this.onNetworkListChangedEvent_.bind(this);
+ chrome.networkingPrivate.onNetworkListChanged.addListener(
+ this.networkListChangedListener_);
+
+ this.deviceStateListChangedListener_ =
+ this.onDeviceStateListChangedEvent_.bind(this);
+ chrome.networkingPrivate.onDeviceStateListChanged.addListener(
+ this.deviceStateListChangedListener_);
+
+ this.networksChangedListener_ = this.onNetworksChangedEvent_.bind(this);
+ chrome.networkingPrivate.onNetworksChanged.addListener(
+ this.networksChangedListener_);
+ },
+
+ /** @override */
+ detached: function() {
+ chrome.networkingPrivate.onNetworkListChanged.removeListener(
+ this.networkListChangedListener_);
+
+ chrome.networkingPrivate.onDeviceStateListChanged.removeListener(
+ this.deviceStateListChangedListener_);
+
+ chrome.networkingPrivate.onNetworksChanged.removeListener(
+ this.networksChangedListener_);
+ },
+
+ /**
+ * Event triggered when the WiFi cr-network-summary-item is expanded.
+ * @param {!{detail: {expanded: boolean, type: string}}} event
+ * @private
+ */
+ onWiFiExpanded_: function(event) {
+ this.getNetworkStates_(); // Get the latest network states (only).
+ chrome.networkingPrivate.requestNetworkScan();
+ },
+
+ /**
+ * Event triggered when a cr-network-summary-item is selected.
+ * @param {!{detail: !CrOncDataElement}} event
+ * @private
+ */
+ onSelected_: function(event) {
+ var onc = event.detail;
+ if (onc.disconnected()) {
+ this.connectToNetwork_(onc);
+ return;
+ }
+ MoreRouting.navigateTo('internet-detail', {guid: onc.data.GUID});
+ },
+
+ /**
+ * Event triggered when the enabled state of a cr-network-summary-item is
+ * toggled.
+ * @param {!{detail: {enabled: boolean, type: string}}} event
+ * @private
+ */
+ onDeviceEnabledToggled_: function(event) {
+ if (event.detail.enabled)
+ chrome.networkingPrivate.enableNetworkType(event.detail.type);
+ else
+ chrome.networkingPrivate.disableNetworkType(event.detail.type);
+ },
+
+ /**
+ * networkingPrivate.onNetworkListChanged event callback.
+ * @private
+ */
+ onNetworkListChangedEvent_: function() { this.getNetworkLists_(); },
+
+ /**
+ * networkingPrivate.onDeviceStateListChanged event callback.
+ * @private
+ */
+ onDeviceStateListChangedEvent_: function() { this.getNetworkLists_(); },
+
+ /**
+ * networkingPrivate.onNetworksChanged event callback.
+ * @param {!Array<string>} networkIds The list of changed network GUIDs.
+ * @private
+ */
+ onNetworksChangedEvent_: function(networkIds) {
+ networkIds.forEach(function(id) {
+ if (id in this.networkIds_) {
+ chrome.networkingPrivate.getState(id,
+ this.getStateCallback_.bind(this));
+ }
+ }, this);
+ },
+
+ /**
+ * Handles UI requests to connect to a network.
+ * TODO(stevenjb): Handle Cellular activation, etc.
+ * @param {!CrOncDataElement} state The network state.
+ * @private
+ */
+ connectToNetwork_: function(state) {
+ chrome.networkingPrivate.startConnect(state.data.GUID);
+ },
+
+ /**
+ * Requests the list of device states and network states from Chrome.
+ * Updates deviceStates, networkStates, and networkStateLists once the
+ * results are returned from Chrome.
+ * @private
+ */
+ getNetworkLists_: function() {
+ // First get the device states.
+ chrome.networkingPrivate.getDeviceStates(
+ function(states) {
+ this.getDeviceStatesCallback_(states);
+ // Second get the network states.
+ this.getNetworkStates_();
+ }.bind(this));
+ },
+
+ /**
+ * Requests the list of network states from Chrome. Updates networkStates and
+ * networkStateLists once the results are returned from Chrome.
+ * @private
+ */
+ getNetworkStates_: function() {
+ var filter = {
+ networkType: 'All',
+ visible: true,
+ configured: false
+ };
+ chrome.networkingPrivate.getNetworks(
+ filter, this.getNetworksCallback_.bind(this));
+ },
+
+ /**
+ * networkingPrivate.getDeviceStates callback.
+ * @param {!Array<!DeviceStateProperties>} states The state properties for all
+ * available devices.
+ * @private
+ */
+ getDeviceStatesCallback_: function(states) {
+ /** @type {!DeviceStateObject} */ var newStates = {};
+ states.forEach(function(state) { newStates[state.Type] = state; });
+ this.deviceStates = newStates;
+ },
+
+ /**
+ * networkingPrivate.getNetworksState callback.
+ * @param {!Array<!NetworkStateProperties>} states The state properties for
+ * all visible networks.
+ * @private
+ */
+ getNetworksCallback_: function(states) {
+ // Clear any current networks.
+ this.networkIds_ = {};
+
+ // Get the first (active) state for each type.
+ var foundTypes = {};
+ /** @type {!NetworkStateListObject} */ var oncNetworks = {
+ Ethernet: [],
+ WiFi: [],
+ Cellular: [],
+ WiMAX: [],
+ VPN: []
+ };
+ states.forEach(function(state) {
+ var type = state.Type;
+ if (!foundTypes[type]) {
+ foundTypes[type] = true;
+ this.updateNetworkState_(type, state);
+ }
+ oncNetworks[type].push(CrOncDataElement.create(state));
+ }, this);
+
+ // Set any types not found to a default value or null.
+ NETWORK_TYPES.forEach(function(type) {
+ if (!foundTypes[type]) {
+ /** @type {NetworkStateProperties} */ var defaultState = null;
+ if (this.deviceStates[type])
+ defaultState = { GUID: '', Type: 'WiFi' };
+ this.updateNetworkState_(type, defaultState);
+ }
+ }, this);
+
+ // Set the network list for each type.
+ NETWORK_TYPES.forEach(function(type) {
+ this.networkStateLists[type] = oncNetworks[type];
+ }, this);
+
+ // Create a VPN entry in deviceStates if there are any VPN networks.
+ if (this.networkStateLists.VPN && this.networkStateLists.VPN.length > 0)
+ this.deviceStates.VPN = { Type: 'VPN', State: 'Enabled' };
+ },
+
+ /**
+ * networkingPrivate.getState callback.
+ * @param {!NetworkStateProperties} state The network state properties.
+ * @private
+ */
+ getStateCallback_: function(state) {
+ var id = state.GUID;
+ if (!this.networkIds_[id])
+ return;
+ this.updateNetworkState_(state.Type, state);
+ },
+
+ /**
+ * Creates a CrOncDataElement from the network state (if not null) for 'type'.
+ * Sets 'networkStates[type]' which will update the cr-network-list-item
+ * associated with 'type'.
+ * @param {string} type The network type.
+ * @param {?NetworkStateProperties} state The state properties for the network
+ * to associate with |type|. May be null if there are no networks matching
+ * |type|.
+ * @private
+ */
+ updateNetworkState_: function(type, state) {
+ this.networkStates[type] = state ? CrOncDataElement.create(state) : null;
+ if (state)
+ this.networkIds_[state.GUID] = true;
+ },
+});
+})();
diff --git a/chromium/chrome/browser/resources/settings/internet_page/network_summary_item.html b/chromium/chrome/browser/resources/settings/internet_page/network_summary_item.html
new file mode 100644
index 00000000000..ea2dffd5912
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/internet_page/network_summary_item.html
@@ -0,0 +1,38 @@
+<link rel="import" href="chrome://resources/polymer/polymer/polymer.html">
+<link rel="import" href="chrome://resources/cr_elements/cr_network_list/cr_network_list.html">
+<link rel="import" href="chrome://resources/cr_elements/cr_network_list_item/cr_network_list_item.html">
+<link rel="import" href="chrome://resources/cr_elements/cr_onc/cr_onc_data.html">
+<link rel="import" href="chrome://resources/cr_elements/cr_expand_button/cr_expand_button.html">
+<link rel="import" href="chrome://resources/cr_elements/cr_toggle_button/cr_toggle_button.html">
+<link rel="import" href="network_summary_item_style.html">
+
+<polymer-element name="cr-network-summary-item">
+ <template>
+ <core-style ref="networkSummaryStyle"></core-style>
+ <div vertical layout hidden?="{{!deviceState}}">
+ <div id="details" horizontal layout on-click="{{onDetailsClicked_}}">
+ <cr-network-list-item id="detailsItem" flex
+ networkState="{{networkState}}">
+ </cr-network-list-item>
+ <div id="buttons" horizontal layout>
+ <cr-expand-button id="expandListButton"
+ class="{{ {invisible: !expandIsVisible_(deviceState, networkStateList)} | tokenList }}"
+ expanded="{{expanded}}">
+ </cr-expand-button>
+ <cr-toggle-button id="deviceEnabledButton"
+ checked="{{deviceIsEnabled_(deviceState)}}"
+ class="{{ {invisible: !deviceEnabledIsVisible_(deviceState)} | tokenList }}"
+ on-click="{{onDeviceEnabledToggled_}}">
+ </cr-toggle-button>
+ </div>
+ </div>
+ <cr-network-list id="networkList" vertical layout
+ maxHeight="{{maxHeight}}"
+ networks="{{networkStateList}}"
+ on-selected="{{onListItemSelected_}}"
+ opened="{{expanded}}">
+ </cr-network-list>
+ </div>
+ </template>
+ <script src="network_summary_item.js"></script>
+</polymer-element>
diff --git a/chromium/chrome/browser/resources/settings/internet_page/network_summary_item.js b/chromium/chrome/browser/resources/settings/internet_page/network_summary_item.js
new file mode 100644
index 00000000000..c81256c2271
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/internet_page/network_summary_item.js
@@ -0,0 +1,171 @@
+// Copyright 2015 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 Polymer element for displaying the network state for a specific
+ * type and a list of networks for that type.
+ */
+(function() {
+
+/** @typedef {chrome.networkingPrivate.DeviceStateProperties} */
+var DeviceStateProperties;
+
+Polymer('cr-network-summary-item', {
+ publish: {
+ /**
+ * True if the list is expanded.
+ *
+ * @attribute expanded
+ * @type {boolean}
+ * @default false
+ */
+ expanded: false,
+
+ /**
+ * The maximum height in pixels for the list of networks.
+ *
+ * @attribute maxHeight
+ * @type {number}
+ * @default 200
+ */
+ maxHeight: 200,
+
+ /**
+ * Device state for the network type.
+ *
+ * @attribute deviceState
+ * @type {?DeviceStateProperties}
+ * @default null
+ */
+ deviceState: null,
+
+ /**
+ * Network state for the active network.
+ *
+ * @attribute networkState
+ * @type {?CrOncDataElement}
+ * @default null
+ */
+ networkState: null,
+
+ /**
+ * List of all network state data for the network type.
+ *
+ * @attribute networkStateList
+ * @type {?Array<!CrOncDataElement>}
+ * @default null
+ */
+ networkStateList: null,
+ },
+
+ /**
+ * Polymer expanded changed method.
+ */
+ expandedChanged: function() {
+ var type = this.deviceState ? this.deviceState.Type : '';
+ this.fire('expanded', {expanded: this.expanded, type: type});
+ },
+
+ /**
+ * Polymer deviceState changed method.
+ */
+ deviceStateChanged: function() {
+ this.updateSelectable_();
+ if (!this.deviceIsEnabled_(this.deviceState))
+ this.expanded = false;
+ },
+
+ /**
+ * Polymer networkStateList changed method.
+ */
+ networkStateListChanged: function() {
+ this.updateSelectable_();
+ },
+
+ /**
+ * @param {?DeviceStateProperties} deviceState The state of a device.
+ * @return {boolean} Whether or not the device state is enabled.
+ * @private
+ */
+ deviceIsEnabled_: function(deviceState) {
+ return deviceState && deviceState.State == 'Enabled';
+ },
+
+ /**
+ * @param {?DeviceStateProperties} deviceState The device state.
+ * @return {boolean} Whether or not to show the UI to enable the network.
+ * @private
+ */
+ deviceEnabledIsVisible_: function(deviceState) {
+ return deviceState &&
+ deviceState.Type != 'Ethernet' && deviceState.Type != 'VPN';
+ },
+
+ /**
+ * @param {?DeviceStateProperties} deviceState The device state.
+ * @param {?Array<!CrOncDataElement>} networkList A list of networks.
+ * @return {boolean} Whether or not to show the UI to expand the list.
+ * @private
+ */
+ expandIsVisible_: function(deviceState, networkList) {
+ if (!this.deviceIsEnabled_(deviceState) || !networkList)
+ return false;
+ var minLength = (this.type == 'WiFi') ? 1 : 2;
+ return networkList.length >= minLength;
+ },
+
+ /**
+ * Event triggered when the details div is clicked on.
+ * @param {!Object} event The enable button event.
+ * @private
+ */
+ onDetailsClicked_: function(event) {
+ if ((event.target.id == 'expandListButton') ||
+ (this.deviceState && !this.deviceIsEnabled_(this.deviceState))) {
+ // Already handled or disabled, do nothing.
+ return;
+ }
+ if (this.expandIsVisible_(this.deviceState, this.networkStateList)) {
+ // Expandable, toggle expand.
+ this.expanded = !this.expanded;
+ return;
+ }
+ // Not expandable, fire 'selected' with |networkState|.
+ this.fire('selected', this.networkState);
+ },
+
+ /**
+ * Event triggered when a cr-network-item is the network list is selected.
+ * @param {!{detail: CrNetworkListItem}} event
+ * @private
+ */
+ onListItemSelected_: function(event) {
+ var onc = event.detail;
+ this.fire('selected', onc);
+ },
+
+ /**
+ * Event triggered when the enable button is toggled.
+ * @param {!Object} event The enable button event.
+ * @private
+ */
+ onDeviceEnabledToggled_: function(event) {
+ var deviceIsEnabled = this.deviceIsEnabled_(this.deviceState);
+ var type = this.deviceState ? this.deviceState.Type : '';
+ this.fire('device-enabled-toggled',
+ {enabled: !deviceIsEnabled, type: type});
+ // Make sure this does not propagate to onDetailsClicked_.
+ event.stopPropagation();
+ },
+
+ /**
+ * Called whenever the 'selectable' state might change.
+ * @private
+ */
+ updateSelectable_: function() {
+ var selectable = this.deviceIsEnabled_(this.deviceState);
+ this.$.details.classList.toggle('selectable', selectable);
+ },
+});
+})();
diff --git a/chromium/chrome/browser/resources/settings/internet_page/network_summary_item_style.html b/chromium/chrome/browser/resources/settings/internet_page/network_summary_item_style.html
new file mode 100644
index 00000000000..8792048aa46
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/internet_page/network_summary_item_style.html
@@ -0,0 +1,31 @@
+<link rel="import" href="chrome://resources/polymer/polymer/polymer.html">
+<link rel="import" href="chrome://resources/polymer/core-style/core-style.html">
+
+<core-style id="networkSummaryStyle">
+
+#details.selectable:hover {
+ background-color: lightgrey;
+}
+
+#detailsItem {
+ margin-bottom: 10px;
+}
+
+#buttons {
+ align-items: center;
+}
+
+.invisible {
+ visibility: hidden;
+}
+
+#deviceEnabledButton {
+ margin: 0px 10px;
+}
+
+#networkList {
+ max-height: 400px;
+ margin: 0px 70px 10px 40px;
+}
+
+</core-style>
diff --git a/chromium/chrome/browser/resources/settings/internet_page/network_summary_style.html b/chromium/chrome/browser/resources/settings/internet_page/network_summary_style.html
new file mode 100644
index 00000000000..562199655a0
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/internet_page/network_summary_style.html
@@ -0,0 +1,10 @@
+<link rel="import" href="chrome://resources/polymer/polymer/polymer.html">
+<link rel="import" href="chrome://resources/polymer/core-style/core-style.html">
+
+<core-style id="networkSummaryStyle">
+
+#summary {
+ padding-right: 40px;
+}
+
+</core-style>
diff --git a/chromium/chrome/browser/resources/settings/polymer_config.js b/chromium/chrome/browser/resources/settings/polymer_config.js
new file mode 100644
index 00000000000..3b20eae9d9f
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/polymer_config.js
@@ -0,0 +1,5 @@
+// Copyright 2015 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.
+
+Polymer = {dom: 'shadow'};
diff --git a/chromium/chrome/browser/resources/settings/pref_tracker/pref_tracker.html b/chromium/chrome/browser/resources/settings/pref_tracker/pref_tracker.html
new file mode 100644
index 00000000000..2636139dfc8
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/pref_tracker/pref_tracker.html
@@ -0,0 +1,6 @@
+<link rel="import" href="chrome://resources/polymer/v0_8/polymer/polymer.html">
+
+<dom-module id="cr-settings-pref-tracker">
+ <script src="chrome://md-settings/prefs/prefs_types.js"></script>
+ <script src="pref_tracker.js"></script>
+</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/pref_tracker/pref_tracker.js b/chromium/chrome/browser/resources/settings/pref_tracker/pref_tracker.js
new file mode 100644
index 00000000000..b042ecafb33
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/pref_tracker/pref_tracker.js
@@ -0,0 +1,83 @@
+// Copyright 2015 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
+ * `cr-settings-pref-tracker` is a utility element used to track the
+ * initialization of a specified preference and throw an error if the pref
+ * is not defined after prefs have all been fetched.
+ *
+ * Example:
+ *
+ * <cr-settings-pref-tracker pref="{{prefs.settings.foo.bar}}">
+ * </cr-settings-pref-tracker>
+ *
+ * @element cr-settings-pref-tracker
+ */
+(function() {
+
+ /**
+ * An array of all the tracker instances.
+ * @type {!Array<!CrSettingsPrefTrackerElement>}
+ */
+ var instances = [];
+
+ /**
+ * Validates all tracker instances.
+ * @private
+ */
+ var validateAll_ = function() {
+ instances.forEach(function(tracker) {
+ tracker.validate_();
+ });
+ };
+
+ document.addEventListener(CrSettingsPrefs.INITIALIZED, validateAll_);
+
+ Polymer({
+ is: 'cr-settings-pref-tracker',
+
+ properties: {
+ /**
+ * The Preference object being tracked.
+ * @type {?chrome.settingsPrivate.PrefObject}
+ */
+ pref: {
+ type: Object,
+ observer: 'validate_',
+ },
+ },
+
+ /** @override */
+ ready: function() {
+ this.validate_();
+
+ instances.push(this);
+ },
+
+ /**
+ * Throws an error if prefs are initialized and the tracked pref is not
+ * found.
+ * @private
+ */
+ validate_: function() {
+ this.async(function() {
+ // Note that null == undefined.
+ if (CrSettingsPrefs.isInitialized && this.pref == null) {
+ // HACK ALERT: This is the best clue we have as to the pref key for
+ // this tracker. This value should not be relied upon anywhere or
+ // actually used besides for this error message.
+ var keyHint = '';
+ var parentPrefString = this.parentNode && this.parentNode.host &&
+ this.parentNode.host.getAttribute('pref');
+ if (parentPrefString) {
+ keyHint = parentPrefString.match(/{{([a-z0-9._]+)}}/)[1];
+ }
+
+ throw new Error('Pref not found. Key Hint: ' + keyHint);
+ }
+ });
+ },
+ });
+})();
diff --git a/chromium/chrome/browser/resources/settings/prefs/prefs.html b/chromium/chrome/browser/resources/settings/prefs/prefs.html
new file mode 100644
index 00000000000..2b5323003d0
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/prefs/prefs.html
@@ -0,0 +1,8 @@
+<link rel="import" href="chrome://resources/polymer/v0_8/polymer/polymer.html">
+<link rel="import" href="chrome://resources/html/assert.html">
+<link rel="import" href="chrome://resources/html/cr.html">
+
+<dom-module id="cr-settings-prefs">
+ <script src="prefs_types.js"></script>
+ <script src="prefs.js"></script>
+</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/prefs/prefs.js b/chromium/chrome/browser/resources/settings/prefs/prefs.js
new file mode 100644
index 00000000000..febc22d41b1
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/prefs/prefs.js
@@ -0,0 +1,136 @@
+/* Copyright 2015 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
+ * 'cr-settings-prefs' is an element which serves as a model for
+ * interaction with settings which are stored in Chrome's
+ * Preferences.
+ *
+ * Example:
+ *
+ * <cr-settings-prefs id="prefs"></cr-settings-prefs>
+ * <cr-settings-a11y-page prefs="{{this.$.prefs}}"></cr-settings-a11y-page>
+ *
+ * @group Chrome Settings Elements
+ * @element cr-settings-a11y-page
+ */
+(function() {
+ 'use strict';
+
+ Polymer({
+ is: 'cr-settings-prefs',
+
+ properties: {
+ /**
+ * Object containing all preferences.
+ */
+ prefStore: {
+ type: Object,
+ value: function() { return {}; },
+ notify: true,
+ },
+ },
+
+ /** @override */
+ created: function() {
+ CrSettingsPrefs.isInitialized = false;
+
+ chrome.settingsPrivate.onPrefsChanged.addListener(
+ this.onPrefsChanged_.bind(this));
+ chrome.settingsPrivate.getAllPrefs(this.onPrefsFetched_.bind(this));
+ },
+
+ /**
+ * Called when prefs in the underlying Chrome pref store are changed.
+ * @param {!Array<!chrome.settingsPrivate.PrefObject>} prefs The prefs that
+ * changed.
+ * @private
+ */
+ onPrefsChanged_: function(prefs) {
+ this.updatePrefs_(prefs, false);
+ },
+
+ /**
+ * Called when prefs are fetched from settingsPrivate.
+ * @param {!Array<!chrome.settingsPrivate.PrefObject>} prefs
+ * @private
+ */
+ onPrefsFetched_: function(prefs) {
+ this.updatePrefs_(prefs, true);
+
+ CrSettingsPrefs.isInitialized = true;
+ document.dispatchEvent(new Event(CrSettingsPrefs.INITIALIZED));
+ },
+
+
+ /**
+ * Updates the settings model with the given prefs.
+ * @param {!Array<!chrome.settingsPrivate.PrefObject>} prefs
+ * @param {boolean} shouldObserve Whether to add an ObjectObserver for each
+ * of the prefs.
+ * @private
+ */
+ updatePrefs_: function(prefs, shouldObserve) {
+ prefs.forEach(function(prefObj) {
+ let root = this.prefStore;
+ let tokens = prefObj.key.split('.');
+
+ assert(tokens.length > 0);
+
+ for (let i = 0; i < tokens.length; i++) {
+ let token = tokens[i];
+
+ if (!root.hasOwnProperty(token)) {
+ let path = 'prefStore.' + tokens.slice(0, i + 1).join('.');
+ this.setPathValue(path, {});
+ }
+ root = root[token];
+ }
+
+ // NOTE: Do this copy rather than just a re-assignment, so that the
+ // ObjectObserver fires.
+ for (let objKey in prefObj) {
+ let path = 'prefStore.' + prefObj.key + '.' + objKey;
+ this.setPathValue(path, prefObj[objKey]);
+ }
+
+ if (shouldObserve) {
+ let keyObserver = new ObjectObserver(root);
+ keyObserver.open(
+ this.propertyChangeCallback_.bind(this, prefObj.key));
+ }
+ }, this);
+ },
+
+ /**
+ * Called when a property of a pref changes.
+ * @param {string} propertyPath The path before the property names.
+ * @param {!Array<string>} added An array of keys which were added.
+ * @param {!Array<string>} removed An array of keys which were removed.
+ * @param {!Array<string>} changed An array of keys of properties whose
+ * values changed.
+ * @param {function(string) : *} getOldValueFn A function which takes a
+ * property name and returns the old value for that property.
+ * @private
+ */
+ propertyChangeCallback_: function(
+ propertyPath, added, removed, changed, getOldValueFn) {
+ for (let property in changed) {
+ // UI should only be able to change the value of a setting for now, not
+ // disabled, etc.
+ assert(property == 'value');
+
+ let newValue = changed[property];
+ assert(newValue !== undefined);
+
+ chrome.settingsPrivate.setPref(
+ propertyPath,
+ newValue,
+ /* pageId */ '',
+ /* callback */ function() {});
+ }
+ },
+ });
+})();
diff --git a/chromium/chrome/browser/resources/settings/prefs/prefs_types.js b/chromium/chrome/browser/resources/settings/prefs/prefs_types.js
new file mode 100644
index 00000000000..d757fc03dcd
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/prefs/prefs_types.js
@@ -0,0 +1,23 @@
+// Copyright 2015 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 Types for CrSettingsPrefsElement.
+ */
+
+if (CrSettingsPrefs === undefined) {
+ var CrSettingsPrefs = {};
+
+ /**
+ * The type of the event fired when prefs have been fetched and initialized.
+ * @const {string}
+ */
+ CrSettingsPrefs.INITIALIZED = 'cr-settings-prefs-initialized';
+
+ /**
+ * Global boolean set to true when all settings have been initialized.
+ * @type {boolean}
+ */
+ CrSettingsPrefs.isInitialized = false;
+}
diff --git a/chromium/chrome/browser/resources/settings/routes.html b/chromium/chrome/browser/resources/settings/routes.html
new file mode 100644
index 00000000000..9d53c9b67a4
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/routes.html
@@ -0,0 +1,17 @@
+<link rel="import" href="chrome://resources/polymer/v0_8/more-routing/more-routing.html">
+
+<!-- Drivers manage how the URL is read, and how to navigate to URLs. "hash"
+ based driver means URLs like /#!/a11y are generated. This is preferred to
+ ensure that navigating between pages via links does not trigger a page
+ reload. For more details, see http://goo.gl/maQU6V. -->
+<more-routing-config driver="hash"></more-routing-config>
+
+<more-route name="a11y" path="/a11y"></more-route>
+<more-route name="dateTime" path="/dateTime"></more-route>
+<more-route name="downloads" path="/downloads"></more-route>
+<more-route name="internet" path="/internet">
+ <more-route name="internet-detail" path="/detail/:guid"></more-route>
+</more-route>
+<more-route name="search" path="/search">
+ <more-route name="search-engines" path="/engines"></more-route>
+</more-route>
diff --git a/chromium/chrome/browser/resources/settings/search_engines_page/search_engine_adder.css b/chromium/chrome/browser/resources/settings/search_engines_page/search_engine_adder.css
new file mode 100644
index 00000000000..b4cb0bb3c50
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/search_engines_page/search_engine_adder.css
@@ -0,0 +1,20 @@
+/* Copyright (c) 2015 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. */
+
+.input-label {
+ font-size: 0.75em;
+}
+
+.container {
+ margin-top: 14px;
+ width: 660px;
+}
+
+.add-label {
+ width: 660px;
+}
+
+.input-field {
+ -webkit-margin-end: 20px;
+}
diff --git a/chromium/chrome/browser/resources/settings/search_engines_page/search_engine_adder.html b/chromium/chrome/browser/resources/settings/search_engines_page/search_engine_adder.html
new file mode 100644
index 00000000000..2fc5af0fe74
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/search_engines_page/search_engine_adder.html
@@ -0,0 +1,43 @@
+<link rel="import" href="chrome://resources/polymer/v0_8/polymer/polymer.html">
+<link rel="import" href="chrome://resources/polymer/v0_8/iron-flex-layout/classes/iron-flex-layout.html">
+<link rel="import" href="chrome://resources/cr_elements/v0_8/cr_button/cr_button.html">
+<link rel="import" href="chrome://resources/cr_elements/v0_8/cr_collapse/cr_collapse.html">
+<link rel="import" href="chrome://resources/cr_elements/v0_8/cr_input/cr_input.html">
+
+<dom-module id="cr-search-engine-adder">
+ <link rel="import" type="css" href="search_engine_adder.css">
+ <template>
+ <div class="add-label layout horizontal center justified">
+ <span i18n-content="searchEnginesAddSearchEngineLabel"></span>
+ <cr-expand-button expanded="{{opened}}"></cr-expand-button>
+ </div>
+ <cr-collapse opened="{{opened}}">
+ <div class="container layout horizontal">
+ <div class="input-field flex two">
+ <div class="input-label" i18n-content="searchEnginesDomainLabel">
+ </div>
+ <cr-input id="domainField" auto-validate="false" required>
+ </cr-input>
+ </div>
+ <div class="input-field flex two">
+ <div class="input-label" i18n-content="searchEnginesKeywordLabel">
+ </div>
+ <cr-input id="keywordField" auto-validate="false" required>
+ </cr-input>
+ </div>
+ <div class="input-field flex four">
+ <div class="input-label" i18n-content="searchEnginesQueryURLLabel">
+ </div>
+ <cr-input id="queryURLField" auto-validate="false" required>
+ </cr-input>
+ </div>
+ <div class="layout horizontal center">
+ <cr-button i18n-content="searchEnginesAddButtonLabel"
+ on-click="{{add_}}" raised>
+ </cr-button>
+ </div>
+ </div>
+ </cr-collapse>
+ </template>
+ <script src="search_engine_adder.js"></script>
+</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/search_engines_page/search_engine_adder.js b/chromium/chrome/browser/resources/settings/search_engines_page/search_engine_adder.js
new file mode 100644
index 00000000000..080a2dedc5f
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/search_engines_page/search_engine_adder.js
@@ -0,0 +1,29 @@
+// Copyright 2015 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 'cr-search-engine-adder' is a component for adding a new search
+ * engine.
+ *
+ * @group Chrome Settings Elements
+ * @element cr-settings-search-engine-adder
+ */
+Polymer({
+ is: 'cr-search-engine-adder',
+
+ /** @private */
+ add_: function() {
+ if (!this.$.domainField.isInvalid &&
+ !this.$.keywordField.isInvalid &&
+ !this.$.queryURLField.isInvalid) {
+ chrome.searchEnginesPrivate.addOtherSearchEngine(
+ /* name */ this.$.domainField.value,
+ /* keyword */ this.$.keywordField.value,
+ /* url */ this.$.queryURLField.value);
+ this.$.domainField.value = '';
+ this.$.keywordField.value = '';
+ this.$.queryURLField.value = '';
+ }
+ },
+});
diff --git a/chromium/chrome/browser/resources/settings/search_engines_page/search_engines_page.html b/chromium/chrome/browser/resources/settings/search_engines_page/search_engines_page.html
new file mode 100644
index 00000000000..b58eb1f6dd7
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/search_engines_page/search_engines_page.html
@@ -0,0 +1,17 @@
+<link rel="import" href="chrome://resources/polymer/v0_8/polymer/polymer.html">
+<link rel="import" href="chrome://resources/polymer/v0_8/paper-material/paper-material.html">
+<link rel="import" href="search_engine_adder.html">
+
+<dom-module id="cr-settings-search-engines-page">
+ <link rel="import" type="css" href="chrome://md-settings/settings_page/settings_page.css">
+ <template>
+ <paper-material>
+ <div class="content">
+ <cr-search-engine-adder></cr-search-engine-adder>
+ <!-- TODO(orenb): Add search engine lists with defaultSearchEngines and
+ otherSearchEngines. -->
+ </div>
+ </paper-material>
+ </template>
+ <script src="search_engines_page.js"></script>
+</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/search_engines_page/search_engines_page.js b/chromium/chrome/browser/resources/settings/search_engines_page/search_engines_page.js
new file mode 100644
index 00000000000..b47c20d4d39
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/search_engines_page/search_engines_page.js
@@ -0,0 +1,68 @@
+// Copyright 2015 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 'cr-settings-search-engines-page' is the settings page
+ * containing search engines settings.
+ *
+ * Example:
+ *
+ * <core-animated-pages>
+ * <cr-settings-search-engines-page prefs="{{prefs}}">
+ * </cr-settings-search-engines-page>
+ * ... other pages ...
+ * </core-animated-pages>
+ *
+ * @group Chrome Settings Elements
+ * @element cr-settings-search-engines-page
+ */
+Polymer({
+ is: 'cr-settings-search-engines-page',
+
+ properties: {
+ /**
+ * Route for the page.
+ */
+ route: {
+ type: String,
+ value: ''
+ },
+
+ /**
+ * Whether the page is a subpage.
+ */
+ subpage: {
+ type: Boolean,
+ value: true,
+ readOnly: true
+ },
+
+ /**
+ * ID of the page.
+ */
+ PAGE_ID: {
+ type: String,
+ value: 'search_engines',
+ readOnly: true
+ },
+
+ /**
+ * Title for the page header and navigation menu.
+ */
+ pageTitle: {
+ type: String,
+ value: loadTimeData.getString('searchEnginesPageTitle'),
+ readOnly: true
+ },
+
+ /**
+ * Name of the 'core-icon' to be shown in the settings-page-header.
+ */
+ icon: {
+ type: String,
+ value: 'search',
+ readOnly: true
+ },
+ },
+});
diff --git a/chromium/chrome/browser/resources/settings/search_page/search_page.css b/chromium/chrome/browser/resources/settings/search_page/search_page.css
new file mode 100644
index 00000000000..30e00a148c5
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/search_page/search_page.css
@@ -0,0 +1,17 @@
+/* Copyright 2015 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. */
+
+.manage-search-engines {
+ -webkit-margin-start: 10px;
+}
+
+.search-engines {
+ align-items: center;
+ display: flex;
+ margin-top: 20px;
+}
+
+#searchEnginesMenu {
+ min-width: 150px;
+} \ No newline at end of file
diff --git a/chromium/chrome/browser/resources/settings/search_page/search_page.html b/chromium/chrome/browser/resources/settings/search_page/search_page.html
new file mode 100644
index 00000000000..9c457c13b38
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/search_page/search_page.html
@@ -0,0 +1,29 @@
+<link rel="import" href="chrome://resources/polymer/v0_8/polymer/polymer.html">
+<link rel="import" href="chrome://resources/polymer/v0_8/paper-material/paper-material.html">
+<link rel="import" href="chrome://resources/polymer/v0_8/more-routing/more-routing.html">
+<link rel="import" href="chrome://resources/cr_elements/v0_8/cr_button/cr_button.html">
+<link rel="import" href="chrome://md-settings/checkbox/checkbox.html">
+
+<dom-module id="cr-settings-search-page">
+ <link rel="import" type="css" href="chrome://md-settings/settings_page/settings_page.css">
+ <link rel="import" type="css" href="search_page.css">
+ <template>
+ <paper-material>
+ <p i18n-content="searchExplanation"></p>
+
+ <div class="search-engines">
+ <select id="searchEnginesMenu" on-change="defaultEngineGuidChanged_">
+ <template is="x-repeat" items="[[searchEngines]]">
+ <option value="[[item.guid]]">{{item.name}}</option>
+ </template>
+ </select>
+
+ <cr-button class="manage-search-engines"
+ i18n-content="searchManageButtonLabel"
+ on-click="manageSearchEngines_" raised>
+ </cr-button>
+ </div>
+ </paper-material>
+ </template>
+ <script src="search_page.js"></script>
+</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/search_page/search_page.js b/chromium/chrome/browser/resources/settings/search_page/search_page.js
new file mode 100644
index 00000000000..6ddbfc7985d
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/search_page/search_page.js
@@ -0,0 +1,114 @@
+// Copyright 2015 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
+ * 'cr-settings-search-page' is the settings page containing search settings.
+ *
+ * Example:
+ *
+ * <iron-animated-pages>
+ * <cr-settings-search-page prefs="{{prefs}}"></cr-settings-search-page>
+ * ... other pages ...
+ * </iron-animated-pages>
+ *
+ * @group Chrome Settings Elements
+ * @element cr-settings-search-page
+ */
+Polymer({
+ is: 'cr-settings-search-page',
+
+ properties: {
+ /**
+ * Route for the page.
+ */
+ route: String,
+
+ /**
+ * Whether the page is a subpage.
+ */
+ subpage: {
+ type: Boolean,
+ value: false,
+ },
+
+ /**
+ * ID of the page.
+ */
+ PAGE_ID: {
+ type: String,
+ value: 'search',
+ },
+
+ /**
+ * Title for the page header and navigation menu.
+ */
+ pageTitle: {
+ type: String,
+ value: function() { return loadTimeData.getString('searchPageTitle'); },
+ },
+
+ /**
+ * Name of the 'iron-icon' to be shown in the settings-page-header.
+ */
+ icon: {
+ type: Boolean,
+ value: 'search',
+ },
+
+ /**
+ * List of default search engines available.
+ * @type {?Array<!SearchEngine>}
+ */
+ searchEngines: {
+ type: Array,
+ value: function() { return []; },
+ },
+ },
+
+ /** @override */
+ created: function() {
+ chrome.searchEnginesPrivate.onSearchEnginesChanged.addListener(
+ this.updateSearchEngines_.bind(this));
+ chrome.searchEnginesPrivate.getSearchEngines(
+ this.updateSearchEngines_.bind(this));
+ },
+
+ /**
+ * Persists the new default search engine back to Chrome. Called when the
+ * user selects a new default in the search engines dropdown.
+ * @private
+ */
+ defaultEngineGuidChanged_: function() {
+ chrome.searchEnginesPrivate.setSelectedSearchEngine(
+ this.$.searchEnginesMenu.value);
+ },
+
+
+ /**
+ * Updates the list of default search engines based on the given |engines|.
+ * @param {!Array<!SearchEngine>} engines All the search engines.
+ * @private
+ */
+ updateSearchEngines_: function(engines) {
+ var defaultEngines = [];
+
+ engines.forEach(function(engine) {
+ if (engine.type ==
+ chrome.searchEnginesPrivate.SearchEngineType.DEFAULT) {
+ defaultEngines.push(engine);
+ if (engine.isSelected) {
+ this.$.searchEnginesMenu.value = engine.guid;
+ }
+ }
+ }, this);
+
+ this.searchEngines = defaultEngines;
+ },
+
+ /** @private */
+ manageSearchEngines_: function() {
+ MoreRouting.navigateTo('search-engines');
+ },
+});
diff --git a/chromium/chrome/browser/resources/settings/settings.html b/chromium/chrome/browser/resources/settings/settings.html
new file mode 100644
index 00000000000..e8f0544604d
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/settings.html
@@ -0,0 +1,12 @@
+<link rel="import" href="chrome://resources/polymer/v0_8/polymer/polymer.html">
+<link rel="import" href="chrome://md-settings/settings_ui/settings_ui.html">
+<link rel="import" href="chrome://md-settings/prefs/prefs.html">
+
+<dom-module id="cr-settings">
+ <template>
+ <cr-settings-prefs id="prefs" pref-store="{{prefs_}}"></cr-settings-prefs>
+ <cr-settings-ui prefs="{{prefs_}}"></cr-settings-ui>
+ </template>
+ <script src="settings.js"></script>
+</dom-module>
+
diff --git a/chromium/chrome/browser/resources/settings/settings.js b/chromium/chrome/browser/resources/settings/settings.js
new file mode 100644
index 00000000000..6995f58e39d
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/settings.js
@@ -0,0 +1,16 @@
+// Copyright 2015 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
+ * 'cr-settings' is the main MD-Settings element, combining the UI and models.
+ *
+ * Example:
+ *
+ * <cr-settings></cr-settings>
+ *
+ * @group Chrome Settings Elements
+ * @element cr-settings
+ */
+Polymer({is: 'cr-settings'});
diff --git a/chromium/chrome/browser/resources/settings/settings_drawer/settings_drawer.css b/chromium/chrome/browser/resources/settings/settings_drawer/settings_drawer.css
new file mode 100644
index 00000000000..f3b32ec25a6
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/settings_drawer/settings_drawer.css
@@ -0,0 +1,32 @@
+/* Copyright 2015 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. */
+
+:host {
+ background-color: rgb(38, 50, 56);
+ color: white;
+ display: flex;
+ flex-direction: column;
+ overflow: auto;
+}
+
+#user-card {
+ -webkit-padding-start: 40px;
+ background-color: rgb(28, 37, 42);
+ color: white;
+ height: 95px;
+}
+
+#user-card img {
+ -webkit-margin-end: 15px;
+ height: 50px;
+ width: 50px;
+}
+
+#user-card .name {
+ font-size: 18px;
+}
+
+#user-card .email {
+ font-size: 14px;
+}
diff --git a/chromium/chrome/browser/resources/settings/settings_drawer/settings_drawer.html b/chromium/chrome/browser/resources/settings/settings_drawer/settings_drawer.html
new file mode 100644
index 00000000000..14a943f0e1e
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/settings_drawer/settings_drawer.html
@@ -0,0 +1,21 @@
+<link rel="import" href="chrome://resources/polymer/v0_8/polymer/polymer.html">
+<link rel="import" href="chrome://resources/polymer/v0_8/iron-flex-layout/classes/iron-flex-layout.html">
+<link rel="import" href="chrome://md-settings/settings_menu/settings_menu.html">
+
+<dom-module id="cr-settings-drawer">
+ <link rel="import" type="css" href="settings_drawer.css">
+ <template>
+ <!-- TODO(michaelpg): Create a new user card component. -->
+ <div id="user-card" class="layout horizontal center">
+ <img src$="[[user_.iconUrl]]">
+ <div class="flex">
+ <div class="name">[[user_.name]]</div>
+ <div class="email">[[user_.email]]</div>
+ </div>
+ </div>
+ <cr-settings-menu class="flex" selected-id="{{selectedId}}"
+ pages="[[pages]]">
+ </cr-settings-menu>
+ </template>
+ <script src="settings_drawer.js"></script>
+</polymer-element>
diff --git a/chromium/chrome/browser/resources/settings/settings_drawer/settings_drawer.js b/chromium/chrome/browser/resources/settings/settings_drawer/settings_drawer.js
new file mode 100644
index 00000000000..16875d7bfd3
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/settings_drawer/settings_drawer.js
@@ -0,0 +1,61 @@
+// Copyright 2015 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
+ * 'cr-settings-drawer' holds the user card and navigation menu for settings
+ * pages.
+ *
+ * Example:
+ *
+ * <paper-drawer-panel>
+ * <cr-settings-drawer drawer selected-id="{{selectedId}}"
+ * pages="[[pages]]">
+ * </cr-settings-drawer>
+ * <cr-settings-main main selected-id="{{selectedId}}" pages="[[pages]]">
+ * </cr-settings-main>
+ * </paper-drawer-panel>
+ *
+ * @group Chrome Settings Elements
+ * @element cr-settings-drawer
+ */
+Polymer({
+ is: 'cr-settings-drawer',
+
+ properties: {
+ /**
+ * Pages to include in the navigation.
+ * @type {!Array<!HTMLElement>}
+ */
+ pages: {
+ type: Array,
+ value: function() { return []; },
+ },
+
+ /**
+ * ID of the currently selected page.
+ * @type {string}
+ */
+ selectedId: {
+ type: String,
+ notify: true,
+ },
+
+ /**
+ * @private {!Object}
+ * TODO(michaelpg): Create custom element and data source for user card.
+ */
+ user_: {
+ type: Object,
+ value: function() {
+ return {
+ name: 'Chrome User',
+ email: 'user@example.com',
+ iconUrl: 'chrome://theme/IDR_PROFILE_AVATAR_23@1x',
+ };
+ },
+ readOnly: true,
+ },
+ },
+});
diff --git a/chromium/chrome/browser/resources/settings/settings_main/settings_main.css b/chromium/chrome/browser/resources/settings/settings_main/settings_main.css
new file mode 100644
index 00000000000..b40e1409dc1
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/settings_main/settings_main.css
@@ -0,0 +1,24 @@
+/* Copyright 2015 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. */
+
+:host {
+ background-color: rgb(219, 222, 224);
+ color: black;
+ display: block;
+}
+
+#mainContainer {
+ box-sizing: border-box;
+ height: 100%;
+ overflow: auto;
+}
+
+#pageContainer {
+ -webkit-margin-end: 20px;
+ -webkit-margin-start: 20px;
+}
+
+::content core-icon-button {
+ position: absolute;
+}
diff --git a/chromium/chrome/browser/resources/settings/settings_main/settings_main.html b/chromium/chrome/browser/resources/settings/settings_main/settings_main.html
new file mode 100644
index 00000000000..c74180e18c8
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/settings_main/settings_main.html
@@ -0,0 +1,54 @@
+<link rel="import" href="chrome://resources/polymer/v0_8/polymer/polymer.html">
+<link rel="import" href="chrome://resources/polymer/v0_8/iron-pages/iron-pages.html">
+<link rel="import" href="chrome://md-settings/routes.html">
+<link rel="import" href="chrome://md-settings/settings_page/settings_page_header.html">
+<link rel="import" href="chrome://md-settings/a11y_page/a11y_page.html">
+<link rel="import" href="chrome://md-settings/date_time_page/date_time_page.html">
+<link rel="import" href="chrome://md-settings/downloads_page/downloads_page.html">
+<link rel="import" href="chrome://md-settings/search_engines_page/search_engines_page.html">
+<link rel="import" href="chrome://md-settings/search_page/search_page.html">
+
+<!-- TODO: Uncomment pages as they are upgraded.
+<link rel="import" href="chrome://md-settings/internet_page/internet_page.html">
+<link rel="import" href="chrome://md-settings/internet_page/internet_detail_page.html">
+-->
+
+<dom-module id="cr-settings-main">
+ <link rel="import" type="css" href="settings_main.css">
+ <template>
+ <div id="mainContainer">
+ <cr-settings-page-header selected-page="[[selectedPage]]">
+ </cr-settings-page-header>
+ <content select="paper-icon-button"></content>
+ <more-route-selector selected-params="{{params}}">
+ <iron-pages id="pageContainer" selected-item="{{selectedPage}}"
+ on-iron-select="onIronSelect_" attr-for-selected="PAGE_ID">
+ <!-- TODO: Uncomment pages as they are upgraded. -->
+<if expr="chromeos">
+ <!--
+ <cr-settings-internet-page route="internet">
+ </cr-settings-internet-page>
+ -->
+ <!--
+ <cr-settings-internet-detail-page route="internet-detail"
+ subpage="true" guid="{{params.guid}}">
+ </cr-settings-internet-detail-page>
+ -->
+</if>
+ <cr-settings-a11y-page prefs="{{prefs}}" route="a11y">
+ </cr-settings-a11y-page>
+<if expr="chromeos">
+ <cr-settings-date-time-page prefs="{{prefs}}" route="dateTime">
+ </cr-settings-date-time-page>
+</if>
+ <cr-settings-downloads-page prefs="{{prefs}}" route="downloads">
+ </cr-settings-downloads-page>
+ <cr-settings-search-page route="search"></cr-settings-search-page>
+ <cr-settings-search-engines-page route="search-engines">
+ </cr-settings-search-engines-page>
+ </iron-pages>
+ </more-route-selector>
+ </div>
+ </template>
+ <script src="settings_main.js"></script>
+</polymer-element>
diff --git a/chromium/chrome/browser/resources/settings/settings_main/settings_main.js b/chromium/chrome/browser/resources/settings/settings_main/settings_main.js
new file mode 100644
index 00000000000..e0bdc4fd1c1
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/settings_main/settings_main.js
@@ -0,0 +1,121 @@
+// Copyright 2015 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
+ * 'cr-settings-main' displays the selected settings page.
+ *
+ * Example:
+ *
+ * <cr-settings-main pages="[[pages]]" selected-page-id="{{selectedId}}">
+ * </cr-settings-main>
+ *
+ * See cr-settings-drawer for example of use in 'paper-drawer-panel'.
+ *
+ * @group Chrome Settings Elements
+ * @element cr-settings-main
+ */
+Polymer({
+ is: 'cr-settings-main',
+
+ properties: {
+ /**
+ * Preferences state.
+ *
+ * @type {?CrSettingsPrefsElement}
+ */
+ prefs: {
+ type: Object,
+ notify: true,
+ },
+
+ /**
+ * Pages that may be shown.
+ * @type {!Array<!HTMLElement>}
+ */
+ pages: {
+ type: Array,
+ value: function() { return []; },
+ notify: true,
+ readOnly: true,
+ },
+
+ /**
+ * Currently selected page.
+ * @type {?HTMLElement}
+ */
+ selectedPage: {
+ type: Object,
+ notify: true,
+ },
+
+ /**
+ * ID of the currently selected page.
+ */
+ selectedPageId: {
+ type: String,
+ notify: true,
+ value: '',
+ observer: 'selectedPageIdChanged_',
+ },
+ },
+
+ /** @override */
+ ready: function() {
+ var observer = new MutationObserver(this.pageContainerUpdated_.bind(this));
+ observer.observe(this.$.pageContainer,
+ /** @type {MutationObserverInit} */ {
+ childList: true,
+ });
+ this.pageContainerUpdated_();
+ },
+
+ /**
+ * Polymer changed event for selectedPageId. See note for onIronSelect_ below.
+ * @private
+ */
+ selectedPageIdChanged_: function() {
+ this.$.pageContainer.selected = this.selectedPageId;
+ },
+
+ /**
+ * We observe $.pageContainer.on-iron-select instead of using data binding
+ * for two reasons:
+ * 1) We need to exclude subpages
+ * 2) There is a bug with data binding or our useage of it here causing
+ * this.selectedPage to get set to the index of $.pageContainer instead of
+ * the valueattr identifier (PAGE_ID). TODO(stevenjb/jlklien): Investigate
+ * fixing this and using filters once we switch to Polymer 0.8.
+ * @private
+ */
+ onIronSelect_: function(event) {
+ if (event.target != this.$.pageContainer || event.detail.item.subpage) {
+ return;
+ }
+ this.selectedPageId = event.detail.item.PAGE_ID;
+ },
+
+ /**
+ * If no page is selected, selects the first page. This happens on load and
+ * when a selected page is removed.
+ * @private
+ */
+ ensureSelection_: function() {
+ if (!this.pages.length)
+ return;
+ if (this.selectedPageId == '')
+ this.selectedPageId = this.pages[0].PAGE_ID;
+ },
+
+ /**
+ * Updates the list of pages using the pages in iron-pages.
+ * @private
+ */
+ pageContainerUpdated_: function() {
+ this._setPages(this.$.pageContainer.items.filter(function(item) {
+ return !item.subpage;
+ }));
+ this.ensureSelection_();
+ },
+});
diff --git a/chromium/chrome/browser/resources/settings/settings_menu/settings_menu.css b/chromium/chrome/browser/resources/settings/settings_menu/settings_menu.css
new file mode 100644
index 00000000000..a37b7f151a1
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/settings_menu/settings_menu.css
@@ -0,0 +1,23 @@
+/* Copyright 2015 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. */
+
+:host {
+ display: block;
+}
+
+paper-menu {
+ margin: 0;
+ padding-top: 20px;
+}
+
+paper-menu > * {
+ -webkit-padding-start: 40px;
+ font-size: 15px;
+ margin-bottom: 5px;
+ min-height: 45px;
+}
+
+paper-menu > *.iron-selected {
+ background-color: rgb(38, 166, 154);
+}
diff --git a/chromium/chrome/browser/resources/settings/settings_menu/settings_menu.html b/chromium/chrome/browser/resources/settings/settings_menu/settings_menu.html
new file mode 100644
index 00000000000..fd502b275b7
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/settings_menu/settings_menu.html
@@ -0,0 +1,20 @@
+<link rel="import" href="chrome://resources/polymer/v0_8/polymer/polymer.html">
+<link rel="import" href="chrome://resources/polymer/v0_8/iron-icon/iron-icon.html">
+<link rel="import" href="chrome://resources/polymer/v0_8/iron-icons/iron-icons.html">
+<link rel="import" href="chrome://resources/polymer/v0_8/paper-menu/paper-menu.html">
+<link rel="import" href="chrome://resources/polymer/v0_8/paper-item/paper-icon-item.html">
+
+<dom-module id="cr-settings-menu">
+ <link rel="import" type="css" href="settings_menu.css">
+ <template>
+ <paper-menu selected="{{selectedId}}" attr-for-selected="data-id">
+ <template is="x-repeat" items="[[pages]]">
+ <paper-icon-item data-id$="[[item.PAGE_ID]]" icon-width="34px">
+ <iron-icon icon="[[item.icon]]" item-icon></iron-icon>
+ <span>[[item.pageTitle]]</span>
+ </paper-icon-item>
+ </template>
+ </paper-menu>
+ </template>
+ <script src="settings_menu.js"></script>
+</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/settings_menu/settings_menu.js b/chromium/chrome/browser/resources/settings/settings_menu/settings_menu.js
new file mode 100644
index 00000000000..acb05046f5d
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/settings_menu/settings_menu.js
@@ -0,0 +1,39 @@
+// Copyright 2015 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
+ * 'cr-settings-menu' shows a menu with the given pages.
+ *
+ * Example:
+ *
+ * <cr-settings-menu pages="[[pages]]" selected-id="{{selectedId}}">
+ * </cr-settings-menu>
+ *
+ * @group Chrome Settings Elements
+ * @element cr-settings-menu
+ */
+Polymer({
+ is: 'cr-settings-menu',
+
+ properties: {
+ /**
+ * Pages to show menu items for.
+ * @type {!Array<!HTMLElement>}
+ */
+ pages: {
+ type: Array,
+ value: function() { return []; },
+ },
+
+ /**
+ * ID of the currently selected page.
+ */
+ selectedId: {
+ type: String,
+ value: '',
+ notify: true,
+ },
+ },
+});
diff --git a/chromium/chrome/browser/resources/settings/settings_page/settings_page.css b/chromium/chrome/browser/resources/settings/settings_page/settings_page.css
new file mode 100644
index 00000000000..86a5e93af19
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/settings_page/settings_page.css
@@ -0,0 +1,28 @@
+/* Copyright 2015 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
+ * Common styles for Settings pages.
+ */
+:host > paper-material {
+ -webkit-padding-start: 80px;
+ background-color: white;
+ display: flex;
+ flex-direction: column;
+ padding-bottom: 40px;
+ padding-top: 40px;
+}
+
+cr-settings-page-header {
+ margin-bottom: 30px;
+ min-height: 24px;
+}
+
+cr-settings-checkbox {
+ -webkit-margin-end: 10px;
+ -webkit-margin-start: 0;
+ margin-bottom: 10px;
+ margin-top: 10px;
+}
diff --git a/chromium/chrome/browser/resources/settings/settings_page/settings_page_header.css b/chromium/chrome/browser/resources/settings/settings_page/settings_page_header.css
new file mode 100644
index 00000000000..895817256bf
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/settings_page/settings_page_header.css
@@ -0,0 +1,28 @@
+/* Copyright 2015 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. */
+
+:host,
+.breadcrumbs {
+ align-items: center;
+ display: flex;
+}
+
+h1 {
+ font-size: 18px;
+ margin: 0;
+}
+
+paper-material {
+ -webkit-padding-start: 80px;
+ background-color: white;
+ display: flex;
+ height: 95px;
+ width: 100%;
+ z-index: 10;
+}
+
+.icon-container {
+ -webkit-margin-start: -40px;
+ width: 40px;
+}
diff --git a/chromium/chrome/browser/resources/settings/settings_page/settings_page_header.html b/chromium/chrome/browser/resources/settings/settings_page/settings_page_header.html
new file mode 100644
index 00000000000..00361f9a36d
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/settings_page/settings_page_header.html
@@ -0,0 +1,23 @@
+<link rel="import" href="chrome://resources/polymer/v0_8/polymer/polymer.html">
+<link rel="import" href="chrome://resources/polymer/v0_8/iron-icon/iron-icon.html">
+<link rel="import" href="chrome://resources/polymer/v0_8/iron-icons/device-icons.html">
+
+<dom-module id="cr-settings-page-header">
+ <link rel="import" type="css" href="settings_page_header.css">
+ <template>
+ <paper-material>
+ <div class="breadcrumbs">
+ <div class="icon-container">
+ <iron-icon icon="{{topPageIcon_}}"><iron-icon>
+ </div>
+
+ <template is="x-repeat" items="{{parentPages_}}">
+ <a href="{{urlFor_(item)}}">{{item.pageTitle}}</a>&nbsp;&gt;
+ </template>
+
+ <span>{{currentPageTitle_}}</span>
+ </div>
+ </paper-material>
+ </template>
+ <script src="settings_page_header.js"></script>
+</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/settings_page/settings_page_header.js b/chromium/chrome/browser/resources/settings/settings_page/settings_page_header.js
new file mode 100644
index 00000000000..7a151a8a980
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/settings_page/settings_page_header.js
@@ -0,0 +1,126 @@
+// Copyright 2015 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
+ * 'cr-settings-page-header' shows a basic heading with a 'iron-icon'.
+ *
+ * Example:
+ *
+ * <cr-settings-page-header page="{{page}}">
+ * </cr-settings-page-header>
+ *
+ * @group Chrome Settings Elements
+ * @element cr-settings-page-header
+ */
+Polymer({
+ is: 'cr-settings-page-header',
+
+ properties: {
+ /**
+ * The current stack of pages being viewed. I.e., may contain subpages.
+ * @type {!Array<!HTMLElement>}
+ */
+ pageStack: {
+ type: Array,
+ value: function() { return []; },
+ },
+
+ /**
+ * The currently selected page.
+ * @type {?HTMLElement}
+ */
+ selectedPage: {
+ type: Object,
+ observer: 'selectedPageChanged_',
+ },
+
+ /**
+ * The icon of the page at the root of the stack.
+ * @private
+ */
+ topPageIcon_: {
+ type: String,
+ computed: 'getTopPageIcon_(pageStack)',
+ },
+
+ /**
+ * The parent pages of the current page.
+ * @private {!Array<!HTMLElement>}
+ */
+ parentPages_: {
+ type: Array,
+ computed: 'getParentPages_(pageStack)',
+ },
+
+ /**
+ * The title of the current page.
+ * @private
+ */
+ currentPageTitle_: {
+ type: String,
+ computed: 'getCurrentPageTitle_(pageStack)',
+ },
+ },
+
+ /**
+ * @param {!Array<!HTMLElement>} pageStack
+ * @return {string} The icon for the top page in the stack.
+ * @private
+ */
+ getTopPageIcon_: function(pageStack) {
+ if (pageStack.length == 0)
+ return '';
+ return pageStack[0].icon;
+ },
+
+ /**
+ * @param {!HTMLElement} page
+ * @return {string} A url link to the given page.
+ * @private
+ */
+ urlFor_: function(page) {
+ return page.route ? urlFor(page.route) : '';
+ },
+
+ /**
+ * @param {!Array<!HTMLElement>} pageStack
+ * @return {string} The title of the current page.
+ * @private
+ */
+ getCurrentPageTitle_: function(pageStack) {
+ if (pageStack.length == 0)
+ return '';
+ return pageStack[0].pageTitle;
+ },
+
+ /** @private */
+ selectedPageChanged_: function() {
+ if (this.selectedPage && this.selectedPage.subpage) {
+ // NOTE: Must reassign pageStack rather than doing push() so that the
+ // computed property (parentPages) will be notified of the update.
+ this.pageStack = this.pageStack.concat(this.selectedPage);
+ } else if (this.selectedPage) {
+ this.pageStack = [this.selectedPage];
+ } else {
+ this.pageStack = [];
+ }
+ },
+
+ /**
+ * Gets the parent pages in the given page stack; i.e. returns all pages but
+ * the topmost subpage.
+ *
+ * @param {Array<HTMLElement>} stack
+ * @return {!Array<HTMLElement>}
+ * @private
+ */
+ getParentPages_: function(stack) {
+ if (!stack || stack.length <= 1) {
+ return [];
+ }
+
+ return stack.slice(0, stack.length - 1);
+ },
+});
diff --git a/chromium/chrome/browser/resources/settings/settings_resources.grd b/chromium/chrome/browser/resources/settings/settings_resources.grd
new file mode 100644
index 00000000000..384ba67e9bd
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/settings_resources.grd
@@ -0,0 +1,197 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<grit latest_public_release="0" current_release="1">
+ <outputs>
+ <output filename="grit/settings_resources.h" type="rc_header">
+ <emit emit_type='prepend'></emit>
+ </output>
+ <output filename="grit/settings_resources_map.cc"
+ type="resource_file_map_source" />
+ <output filename="grit/settings_resources_map.h"
+ type="resource_map_header" />
+ <output filename="settings_resources.pak" type="data_package" />
+ </outputs>
+ <release seq="1">
+ <structures>
+ <structure name="IDR_SETTINGS_A11Y_PAGE_JS"
+ file="a11y_page/a11y_page.js"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_A11Y_PAGE_HTML"
+ file="a11y_page/a11y_page.html"
+ type="chrome_html"
+ flattenhtml="true"
+ allowexternalscript="true" />
+ <structure name="IDR_SETTINGS_A11Y_PAGE_CSS"
+ file="a11y_page/a11y_page.css"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_CHECKBOX_HTML"
+ file="checkbox/checkbox.html"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_CHECKBOX_JS"
+ file="checkbox/checkbox.js"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_CHECKBOX_CSS"
+ file="checkbox/checkbox.css"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_CR_SETTINGS_DRAWER_CSS"
+ file="settings_drawer/settings_drawer.css"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_CR_SETTINGS_DRAWER_HTML"
+ file="settings_drawer/settings_drawer.html"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_CR_SETTINGS_DRAWER_JS"
+ file="settings_drawer/settings_drawer.js"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_CR_SETTINGS_MAIN_CSS"
+ file="settings_main/settings_main.css"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_CR_SETTINGS_MAIN_HTML"
+ file="settings_main/settings_main.html"
+ type="chrome_html"
+ flattenhtml="true"
+ allowexternalscript="true" />
+ <structure name="IDR_SETTINGS_CR_SETTINGS_MAIN_JS"
+ file="settings_main/settings_main.js"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_CR_SETTINGS_MENU_CSS"
+ file="settings_menu/settings_menu.css"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_CR_SETTINGS_MENU_HTML"
+ file="settings_menu/settings_menu.html"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_CR_SETTINGS_MENU_JS"
+ file="settings_menu/settings_menu.js"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_CR_SETTINGS_PAGE_CSS"
+ file="settings_page/settings_page.css"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_CR_SETTINGS_PAGE_HEADER_CSS"
+ file="settings_page/settings_page_header.css"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_CR_SETTINGS_PAGE_HEADER_HTML"
+ file="settings_page/settings_page_header.html"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_CR_SETTINGS_PAGE_HEADER_JS"
+ file="settings_page/settings_page_header.js"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_CR_SETTINGS_UI_CSS"
+ file="settings_ui/settings_ui.css"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_CR_SETTINGS_UI_HTML"
+ file="settings_ui/settings_ui.html"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_CR_SETTINGS_UI_JS"
+ file="settings_ui/settings_ui.js"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_DATE_TIME_PAGE_CSS"
+ file="date_time_page/date_time_page.css"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_DATE_TIME_PAGE_HTML"
+ file="date_time_page/date_time_page.html"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_DATE_TIME_PAGE_JS"
+ file="date_time_page/date_time_page.js"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_DOWNLOADS_PAGE_CSS"
+ file="downloads_page/downloads_page.css"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_DOWNLOADS_PAGE_HTML"
+ file="downloads_page/downloads_page.html"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_DOWNLOADS_PAGE_JS"
+ file="downloads_page/downloads_page.js"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_PREFS_HTML"
+ file="prefs/prefs.html"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_PREFS_JS"
+ file="prefs/prefs.js"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_PREFS_TYPES_JS"
+ file="prefs/prefs_types.js"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_PREF_TRACKER_HTML"
+ file="pref_tracker/pref_tracker.html"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_PREF_TRACKER_JS"
+ file="pref_tracker/pref_tracker.js"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_POLYMER_CONFIG_JS"
+ file="polymer_config.js"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_ROUTES_HTML"
+ file="routes.html"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_SEARCH_ENGINES_PAGE_SEARCH_ENGINE_ADDER_JS"
+ file="search_engines_page/search_engine_adder.js"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_SEARCH_ENGINES_PAGE_SEARCH_ENGINE_ADDER_HTML"
+ file="search_engines_page/search_engine_adder.html"
+ type="chrome_html"
+ flattenhtml="true"
+ allowexternalscript="true" />
+ <structure name="IDR_SETTINGS_SEARCH_ENGINES_PAGE_SEARCH_ENGINE_ADDER_CSS"
+ file="search_engines_page/search_engine_adder.css"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_SEARCH_ENGINES_PAGE_SEARCH_ENGINES_PAGE_JS"
+ file="search_engines_page/search_engines_page.js"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_SEARCH_ENGINES_PAGE_SEARCH_ENGINES_PAGE_HTML"
+ file="search_engines_page/search_engines_page.html"
+ type="chrome_html"
+ flattenhtml="true"
+ allowexternalscript="true" />
+ <structure name="IDR_SETTINGS_SEARCH_PAGE_JS"
+ file="search_page/search_page.js"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_SEARCH_PAGE_HTML"
+ file="search_page/search_page.html"
+ type="chrome_html"
+ flattenhtml="true"
+ allowexternalscript="true" />
+ <structure name="IDR_SETTINGS_SEARCH_PAGE_CSS"
+ file="search_page/search_page.css"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_SETTINGS_HTML"
+ file="settings.html"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_SETTINGS_JS"
+ file="settings.js"
+ type="chrome_html" />
+ <if expr="chromeos">
+ <structure name="IDR_SETTINGS_INTERNET_DETAIL_PAGE_STYLE_HTML"
+ file="internet_page/internet_detail_page_style.html"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_INTERNET_DETAIL_PAGE_HTML"
+ file="internet_page/internet_detail_page.html"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_INTERNET_DETAIL_PAGE_JS"
+ file="internet_page/internet_detail_page.js"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_INTERNET_PAGE_HTML"
+ file="internet_page/internet_page.html"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_INTERNET_PAGE_JS"
+ file="internet_page/internet_page.js"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_NETWORK_SUMMARY_STYLE_HTML"
+ file="internet_page/network_summary_style.html"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_NETWORK_SUMMARY_HTML"
+ file="internet_page/network_summary.html"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_NETWORK_SUMMARY_JS"
+ file="internet_page/network_summary.js"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_NETWORK_SUMMARY_ITEM_STYLE_HTML"
+ file="internet_page/network_summary_item_style.html"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_NETWORK_SUMMARY_ITEM_HTML"
+ file="internet_page/network_summary_item.html"
+ type="chrome_html" />
+ <structure name="IDR_SETTINGS_NETWORK_SUMMARY_ITEM_JS"
+ file="internet_page/network_summary_item.js"
+ type="chrome_html" />
+ </if>
+ </structures>
+ </release>
+</grit>
diff --git a/chromium/chrome/browser/resources/settings/settings_ui/settings_ui.css b/chromium/chrome/browser/resources/settings/settings_ui/settings_ui.css
new file mode 100644
index 00000000000..24c7646cf4f
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/settings_ui/settings_ui.css
@@ -0,0 +1,15 @@
+/* Copyright 2015 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. */
+
+:host {
+ -webkit-user-select: none;
+ background-color: rgb(219, 222, 224);
+ display: block;
+ font-family: Roboto;
+ height: 100%;
+}
+
+cr-settings-main paper-icon-button {
+ z-index: 10;
+}
diff --git a/chromium/chrome/browser/resources/settings/settings_ui/settings_ui.html b/chromium/chrome/browser/resources/settings/settings_ui/settings_ui.html
new file mode 100644
index 00000000000..0a3a44a8b82
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/settings_ui/settings_ui.html
@@ -0,0 +1,21 @@
+<link rel="import" href="chrome://resources/polymer/v0_8/polymer/polymer.html">
+<link rel="import" href="chrome://resources/polymer/v0_8/paper-drawer-panel/paper-drawer-panel.html">
+<link rel="import" href="chrome://resources/polymer/v0_8/paper-icon-button/paper-icon-button.html">
+<link rel="import" href="chrome://md-settings/settings_drawer/settings_drawer.html">
+<link rel="import" href="chrome://md-settings/settings_main/settings_main.html">
+
+<dom-module id="cr-settings-ui">
+ <link rel="import" type="css" href="settings_ui.css">
+ <template>
+ <paper-drawer-panel drawer-width="280px">
+ <cr-settings-drawer drawer id="drawer" pages="[[pages]]"
+ selected-id="{{selectedPageId}}">
+ </cr-settings-drawer>
+ <cr-settings-main main pages="{{pages}}" prefs="{{prefs}}"
+ selected-page-id="{{selectedPageId}}">
+ <paper-icon-button icon="menu" paper-drawer-toggle></paper-icon-button>
+ </cr-settings-main>
+ </paper-drawer-panel>
+ </template>
+ <script src="settings_ui.js"></script>
+</dom-module>
diff --git a/chromium/chrome/browser/resources/settings/settings_ui/settings_ui.js b/chromium/chrome/browser/resources/settings/settings_ui/settings_ui.js
new file mode 100644
index 00000000000..d0c05c41c47
--- /dev/null
+++ b/chromium/chrome/browser/resources/settings/settings_ui/settings_ui.js
@@ -0,0 +1,37 @@
+// Copyright 2015 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
+ * 'cr-settings-ui' implements the UI for the Settings page.
+ *
+ * Example:
+ *
+ * <cr-settings-ui prefs="{{prefs}}"></cr-settings-ui>
+ *
+ * @group Chrome Settings Elements
+ * @element cr-settings-ui
+ */
+Polymer({
+ is: 'cr-settings-ui',
+
+ properties: {
+ /**
+ * Preferences state.
+ * @type {?CrSettingsPrefsElement}
+ */
+ prefs: Object,
+
+ /**
+ * Ordered list of settings pages available to the user. Do not edit
+ * this variable directly.
+ * @type {!Array<!HTMLElement>}
+ */
+ pages: {
+ type: Array,
+ value: function() { return []; },
+ notify: true,
+ },
+ },
+});
diff --git a/chromium/chrome/browser/resources/signin_internals/signin_index.html b/chromium/chrome/browser/resources/signin_internals/signin_index.html
index 699bf783ac1..cca049fb7d9 100644
--- a/chromium/chrome/browser/resources/signin_internals/signin_index.html
+++ b/chromium/chrome/browser/resources/signin_internals/signin_index.html
@@ -1,5 +1,5 @@
-<!DOCTYPE HTML>
-<html i18n-values="dir:textdirection;">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
<head>
<meta charset="utf-8">
<title>Signin Internals</title>
@@ -7,9 +7,10 @@
<script src="chrome://resources/js/util.js"></script>
<script src="chrome://resources/js/load_time_data.js"></script>
<script src="chrome://signin-internals/strings.js"></script>
+ <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" type="text/css" href="signin_index.css">
</head>
-<body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<body>
<div id='signin-info'>
<div class="section" jsselect="signin_info">
<h2 jscontent="title"></h2>
@@ -66,7 +67,20 @@
</table>
</div>
</div>
- <script src="chrome://resources/js/i18n_template2.js"></script>
+ <div id="account-info">
+ <h2>Accounts in Token Service</h2>
+ <div class="account-section">
+ <table class="signin-details">
+ <tr class="header">
+ <td>Accound Id</td>
+ </tr>
+ <tr jsselect="accountInfo">
+ <td jscontent="accountId"></td>
+ </tr>
+ </table>
+ </div>
+ </div>
+ <script src="chrome://resources/js/i18n_template.js"></script>
<script src="chrome://resources/js/jstemplate_compiled.js"></script>
<script src="chrome://signin-internals/signin_internals.js"></script>
</body>
diff --git a/chromium/chrome/browser/resources/signin_internals/signin_internals.js b/chromium/chrome/browser/resources/signin_internals/signin_internals.js
index 4f3080442e1..5ac1b25eeb8 100644
--- a/chromium/chrome/browser/resources/signin_internals/signin_internals.js
+++ b/chromium/chrome/browser/resources/signin_internals/signin_internals.js
@@ -46,7 +46,7 @@ function setClassFromValue(value) {
}
// Allow signin_index.html to access the functions above using the
-// corresponding chrome.signin.<method> calls.
+// corresponding chrome.signin<method> calls.
chrome.signin['highlightIfChanged'] = highlightIfChanged;
chrome.signin['highlightIfAnyChanged'] = highlightIfAnyChanged;
chrome.signin['setClassFromValue'] = setClassFromValue;
@@ -168,6 +168,7 @@ function refreshSigninInfo(signinInfo) {
chrome.signin.internalsInfo = signinInfo;
jstProcess(new JsEvalContext(signinInfo), $('signin-info'));
jstProcess(new JsEvalContext(signinInfo), $('token-info'));
+ jstProcess(new JsEvalContext(signinInfo), $('account-info'));
}
// Replace the cookie information with the fetched values.
diff --git a/chromium/chrome/browser/resources/suggestions_internals/suggestions_internals.css b/chromium/chrome/browser/resources/suggestions_internals/suggestions_internals.css
deleted file mode 100644
index 1996edcdf7a..00000000000
--- a/chromium/chrome/browser/resources/suggestions_internals/suggestions_internals.css
+++ /dev/null
@@ -1,37 +0,0 @@
-/* Copyright (c) 2012 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. */
-
-.suggestions-debug-table {
- border: 1px solid grey;
- margin-bottom: 1.5em;
-}
-
-.suggestions-debug-table th {
- border: 1px solid grey;
-}
-
-.suggestions-debug-table td {
- border: 1px solid grey;
-}
-
-p {
- margin: 0;
-}
-
-.input-section {
- margin-bottom: 1em;
-}
-
-.boolean-property-true {
- background-color: rgb(146, 205, 0);
-}
-
-.boolean-property-false {
- background-color: rgb(205, 74, 0);
-}
-
-.boolean-property-true,
-.boolean-property-false {
- text-align: center;
-}
diff --git a/chromium/chrome/browser/resources/suggestions_internals/suggestions_internals.html b/chromium/chrome/browser/resources/suggestions_internals/suggestions_internals.html
deleted file mode 100644
index c06690a5926..00000000000
--- a/chromium/chrome/browser/resources/suggestions_internals/suggestions_internals.html
+++ /dev/null
@@ -1,25 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
- <meta charset="utf-8">
- <title>Suggestions Internals</title>
- <link rel="stylesheet" href="suggestions_internals.css">
- <script src="chrome://resources/js/cr.js"></script>
- <script src="chrome://resources/js/util.js"></script>
- <script src="suggestions_internals.js"></script>
-</head>
-<body>
- <div class="input-section">
- <form id="suggestions-form" action="">
- <p>
- <input id="refresh-data" type="submit" value="Refresh data">
- </p>
- <p>
- <input id="show-discarded" type="checkbox">
- <label for="show-discarded">Show discarded items</label>
- </p>
- </form>
- </div>
- <div id="suggestions-debug-text"></div>
-</body>
-</html>
diff --git a/chromium/chrome/browser/resources/suggestions_internals/suggestions_internals.js b/chromium/chrome/browser/resources/suggestions_internals/suggestions_internals.js
deleted file mode 100644
index 35988303db0..00000000000
--- a/chromium/chrome/browser/resources/suggestions_internals/suggestions_internals.js
+++ /dev/null
@@ -1,186 +0,0 @@
-// Copyright (c) 2012 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.
-
-/**
- * Javascript for suggestions_internals.html, served from
- * chrome://suggestions-internals/. This is used to debug suggestions ranking.
- * When loaded, the page will show the current set of suggestions, along with a
- * large set of information (e.g. all the signals that were taken into
- * consideration for deciding which pages were selected to be shown to the user)
- * that will aid in debugging and optimizing the algorithms.
- */
-cr.define('suggestionsInternals', function() {
- 'use strict';
-
- /**
- * Register our event handlers.
- */
- function initialize() {
- $('suggestions-form').addEventListener('submit', onRefreshClicked);
- $('show-discarded').addEventListener('change', refresh);
- refresh();
- }
-
- /**
- * Called when the 'Refresh' button is clicked. Reloads the suggestions data.
- */
- function onRefreshClicked(event) {
- refresh();
- event.preventDefault();
- }
-
- /**
- * Reloads the suggestions data by sending a 'getSuggestions' message to
- * Chrome. The C++ code is then expected to call 'setSuggestions' when there
- * are suggestions ready.
- */
- function refresh() {
- chrome.send('getSuggestions');
- }
-
- /**
- * A list of columns that we do not want to display.
- * @type {Array.<string>}
- * @const
- */
- var IGNORED_COLUMNS = [
- 'direction'
- ];
-
- /**
- * A list specifying the name of the first columns to be displayed. If
- * present, they will be displayed in this order, followed by the remaining
- * columns.
- * @type {Array.<string>}
- * @const
- */
- var PREFERRED_COLUMN_ORDER = [
- 'title',
- 'url',
- 'score'
- ];
-
- function setBooleanColumn(column, value) {
- if (value) {
- column.innerText = 'Y';
- column.classList.add('boolean-property-true');
- } else {
- column.innerText = 'N';
- column.classList.add('boolean-property-false');
- }
- }
-
- /**
- * Called by Chrome code, with a ranked list of suggestions. The columns
- * to be displayed are calculated automatically from the properties of the
- * elements in the list, such that all properties have a column.
- */
- function setSuggestions(list) {
- // Build a list of all the columns that will be displayed.
- var columns = [];
- list.forEach(function(entry) {
- for (var column in entry) {
- if (typeof entry[column] == 'object') {
- // Expand one level deep
- for (var subColumn in entry[column]) {
- var path = column + '.' + subColumn;
- if (columns.indexOf(path) < 0)
- columns.push(path);
- }
- } else if (columns.indexOf(column) < 0) {
- columns.push(column);
- }
- }
- });
-
- // Remove columns that we don't want to display.
- columns = columns.filter(function(column) {
- return IGNORED_COLUMNS.indexOf(column) < 0;
- });
-
- // Move the preferred columns to the start of the column list.
- for (var i = PREFERRED_COLUMN_ORDER.length - 1; i >= 0; i--) {
- var index = columns.indexOf(PREFERRED_COLUMN_ORDER[i]);
- if (index >= 0)
- columns.unshift(columns.splice(index, 1)[0]);
- }
-
- // Special columns.
- columns.unshift('favicon');
- columns.unshift('screenshot');
- columns.unshift('rank');
-
- // Erase whatever is currently being displayed.
- var output = $('suggestions-debug-text');
- output.innerHTML = '';
-
- // Create the container table and add the header row.
- var table = document.createElement('table');
- table.className = 'suggestions-debug-table';
- var header = document.createElement('tr');
- columns.forEach(function(entry) {
- var column = document.createElement('th');
- column.innerText = entry;
- header.appendChild(column);
- });
- table.appendChild(header);
-
- // Add all the suggestions to the table.
- var rank = 1;
- list.forEach(function(entry) {
- var row = document.createElement('tr');
- columns.forEach(function(columnName) {
- var column = document.createElement('td');
- // Expand the path and find the data if it's there.
- var path = columnName.split('.');
- var data = entry;
- for (var i = 0; i < path.length; ++i) {
- if (data && data.hasOwnProperty(path[i]))
- data = data[path[i]];
- else
- data = undefined;
- }
- // Only add the column if the current suggestion has this property
- // (otherwise, leave the cell empty).
- if (typeof(data) != 'undefined') {
- if (typeof(data) == 'boolean') {
- setBooleanColumn(column, data);
- } else if (/^https?:\/\/.+$/.test(data)) {
- // If the text is a URL, make it an anchor element.
- var anchor = document.createElement('a');
- anchor.href = data;
- anchor.innerText = data;
- column.appendChild(anchor);
- } else {
- column.innerText = data;
- }
- } else if (columnName == 'rank') {
- column.innerText = rank++;
- } else if (columnName == 'screenshot') {
- var thumbnailUrl = 'chrome://thumb/' + entry.url;
- var img = document.createElement('img');
- img.onload = function() { setBooleanColumn(column, true); }
- img.onerror = function() { setBooleanColumn(column, false); }
- img.src = thumbnailUrl;
- } else if (columnName == 'favicon') {
- var faviconUrl = 'chrome://favicon/size/16@1x/' + entry.url;
- column.style.backgroundImage = url(faviconUrl);
- column.style.backgroundRepeat = 'no-repeat';
- column.style.backgroundPosition = 'center center';
- }
- row.appendChild(column);
- });
- table.appendChild(row);
- });
-
- output.appendChild(table);
- }
-
- return {
- initialize: initialize,
- setSuggestions: setSuggestions
- };
-});
-
-document.addEventListener('DOMContentLoaded', suggestionsInternals.initialize);
diff --git a/chromium/chrome/browser/resources/supervised_user_block_interstitial.css b/chromium/chrome/browser/resources/supervised_user_block_interstitial.css
index 1e9998e30f7..ba71f7d364e 100644
--- a/chromium/chrome/browser/resources/supervised_user_block_interstitial.css
+++ b/chromium/chrome/browser/resources/supervised_user_block_interstitial.css
@@ -3,7 +3,6 @@
* found in the LICENSE file. */
body {
background-color: rgb(230, 230, 230);
- font-family: Helvetica, Arial, sans-serif;
font-size: 10pt;
margin: 50px 40px 20px 40px;
text-align: center;
@@ -41,8 +40,8 @@ a {
* the resource manually. */
-webkit-user-select: none;
content: -webkit-image-set(
- url('../../app/theme/default_100_percent/common/error_managed_mode_blocked_page.png') 1x,
- url('../../app/theme/default_200_percent/common/error_managed_mode_blocked_page.png') 2x);
+ url(../../app/theme/default_100_percent/common/error_managed_mode_blocked_page.png) 1x,
+ url(../../app/theme/default_200_percent/common/error_managed_mode_blocked_page.png) 2x);
margin-bottom: 15px;
margin-top: 10px;
}
@@ -64,6 +63,15 @@ a {
margin: 20px 25px;
}
+#block-reason-message {
+ font-size: 80%;
+ margin-top: 5px;
+}
+
+#feedback-link {
+ font-size: 80%;
+}
+
button {
-webkit-user-select: none;
background-image: linear-gradient(rgb(246, 246, 246) 5%,
diff --git a/chromium/chrome/browser/resources/supervised_user_block_interstitial.html b/chromium/chrome/browser/resources/supervised_user_block_interstitial.html
index e04fccc7321..5ca62ebf769 100644
--- a/chromium/chrome/browser/resources/supervised_user_block_interstitial.html
+++ b/chromium/chrome/browser/resources/supervised_user_block_interstitial.html
@@ -1,9 +1,10 @@
-<!DOCTYPE html>
-<html i18n-values="dir:textdirection">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
<head>
<meta name="viewport" content="width=device-width">
<meta charset="utf-8">
<title i18n-content="blockPageTitle"></title>
+<link rel="stylesheet" href="../../../ui/webui/resources/css/widgets.css">
<link rel="stylesheet" href="supervised_user_block_interstitial.css">
<script src="../../../ui/webui/resources/js/cr.js"></script>
<script src="../../../ui/webui/resources/js/util.js"></script>
@@ -12,26 +13,32 @@
<body>
<div id="main-frame-blocked">
- <div id="box">
- <div id="content-top">
- <img id="error-img">
- <div id="avatar-container">
- <img id="avatar-img" class="avatar-img" hidden>
- <img id="second-avatar-img" class="avatar-img" hidden>
- </div>
- <h1>
- <div id="block-page-message" i18n-content="blockPageMessage"></div>
- <div id="request-sent-message" i18n-content="requestSentMessage" hidden>
- </div>
- </h1>
-
- <div id="button-container">
- <button id="back-button" i18n-content="backButton"></button>
- <button id="request-access-button" i18n-content="requestAccessButton">
- </button>
+ <div id="box">
+ <div id="content-top">
+ <img id="error-img">
+ <div id="avatar-container">
+ <img id="avatar-img" class="avatar-img" hidden>
+ <img id="second-avatar-img" class="avatar-img" hidden>
+ </div>
+ <h1>
+ <div id="block-page-message" i18n-content="blockPageMessage"></div>
+ <div id="request-failed-message" i18n-content="requestFailedMessage"
+ hidden></div>
+ <div id="request-sent-message" i18n-content="requestSentMessage" hidden>
+ </div>
+ <div id="block-reason-message" i18n-content="blockReasonMessage"></div>
+ <a id="feedback-link" is="action-link" i18n-content="feedbackLink"></a>
+ </h1>
+ <div id="button-container">
+ <button id="back-button" class="custom-appearance"
+ i18n-content="backButton">
+ </button>
+ <button id="request-access-button" class="custom-appearance"
+ i18n-content="requestAccessButton">
+ </button>
+ </div>
</div>
</div>
-
</div>
</body>
</html>
diff --git a/chromium/chrome/browser/resources/supervised_user_block_interstitial.js b/chromium/chrome/browser/resources/supervised_user_block_interstitial.js
index 05e0427c83b..55b652c34a4 100644
--- a/chromium/chrome/browser/resources/supervised_user_block_interstitial.js
+++ b/chromium/chrome/browser/resources/supervised_user_block_interstitial.js
@@ -14,7 +14,7 @@ function makeImageSet(url1x, url2x) {
function initialize() {
if (loadTimeData.getBoolean('allowAccessRequests')) {
$('request-access-button').onclick = function(event) {
- updateAfterRequestSent();
+ $('request-access-button').hidden = true;
sendCommand('request');
};
} else {
@@ -41,18 +41,31 @@ function initialize() {
$('back-button').onclick = function(event) {
sendCommand('back');
};
+ if (loadTimeData.getBoolean('showFeedbackLink')) {
+ $('feedback-link').onclick = function(event) {
+ sendCommand('feedback');
+ };
+ } else {
+ $('feedback-link').style.display = 'none';
+ }
}
/**
- * Updates the interstitial to show that the request was sent.
+ * Updates the interstitial to show that the request failed or was sent.
+ * @param {boolean} isSuccessful Whether the request was successful or not.
*/
-function updateAfterRequestSent() {
+function setRequestStatus(isSuccessful) {
$('error-img').hidden = true;
- $('request-access-button').hidden = true;
$('block-page-message').hidden = true;
- $('request-sent-message').hidden = false;
- if ($('avatar-img').hidden) {
- $('request-sent-message').style.marginTop = '40px';
+ if (isSuccessful) {
+ $('request-failed-message').hidden = true;
+ $('request-sent-message').hidden = false;
+ if ($('avatar-img').hidden) {
+ $('request-sent-message').style.marginTop = '40px';
+ }
+ } else {
+ $('request-failed-message').hidden = false;
+ $('request-access-button').hidden = false;
}
}
diff --git a/chromium/chrome/browser/resources/sync_file_system_internals/main.css b/chromium/chrome/browser/resources/sync_file_system_internals/main.css
index 444051050da..4e6923ce6db 100644
--- a/chromium/chrome/browser/resources/sync_file_system_internals/main.css
+++ b/chromium/chrome/browser/resources/sync_file_system_internals/main.css
@@ -2,10 +2,6 @@
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file. */
-* {
- font-family: Ubuntu, Arial, sans-serif;
-}
-
tabbox {
min-height: 650px;
}
@@ -63,14 +59,14 @@ tbody tr:nth-child(odd) {
}
.file-icon {
- background-image: url('chrome://syncfs-internals/file.png');
+ background-image: url(chrome://syncfs-internals/file.png);
background-position: 0 2px;
background-repeat: no-repeat;
padding-left: 18px;
}
.folder-icon {
- background-image: url('chrome://syncfs-internals/folder_closed.png');
+ background-image: url(chrome://syncfs-internals/folder_closed.png);
background-position: 0 2px;
background-repeat: no-repeat;
padding-left: 18px;
diff --git a/chromium/chrome/browser/resources/sync_file_system_internals/main.html b/chromium/chrome/browser/resources/sync_file_system_internals/main.html
index d396a888c2d..2b3ede1a5a0 100644
--- a/chromium/chrome/browser/resources/sync_file_system_internals/main.html
+++ b/chromium/chrome/browser/resources/sync_file_system_internals/main.html
@@ -1,12 +1,13 @@
-<!DOCTYPE html>
+<!doctype html>
<!--
Copyright 2013 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.
-->
-<html i18n-values="dir:textdirection;">
+<html i18n-values="dir:textdirection;lang:language">
<meta charset="utf-8">
<title>Sync File System Internals</title>
+<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="main.css">
<script src="chrome://resources/js/util.js"></script>
@@ -21,7 +22,7 @@ found in the LICENSE file.
<script src="chrome://syncfs-internals/strings.js"></script>
<script src="chrome://syncfs-internals/utils.js"></script>
-<body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<body>
<tabbox>
<tabs>
@@ -50,6 +51,6 @@ found in the LICENSE file.
</tabpanels>
</tabbox>
-<script src="chrome://resources/js/i18n_template2.js"></script>
+<script src="chrome://resources/js/i18n_template.js"></script>
</body>
</html>
diff --git a/chromium/chrome/browser/resources/sync_file_system_internals/utils.js b/chromium/chrome/browser/resources/sync_file_system_internals/utils.js
index 5e7a35160c3..2cec4fcbe28 100644
--- a/chromium/chrome/browser/resources/sync_file_system_internals/utils.js
+++ b/chromium/chrome/browser/resources/sync_file_system_internals/utils.js
@@ -22,7 +22,7 @@ function createElementFromText(elementName, text, opt_attributes) {
/**
* Creates an element with |tagName| containing the content |dict|.
* @param {string} elementName Name of the new element to be created.
- * @param {Object.<string, string>} dict Dictionary to be contained in the new
+ * @param {Object<string, string>} dict Dictionary to be contained in the new
* element.
* @return {HTMLElement} The newly created HTML element.
*/
diff --git a/chromium/chrome/browser/resources/sync_internals/OWNERS b/chromium/chrome/browser/resources/sync_internals/OWNERS
index 8e0a3a1efb8..74fee612e33 100644
--- a/chromium/chrome/browser/resources/sync_internals/OWNERS
+++ b/chromium/chrome/browser/resources/sync_internals/OWNERS
@@ -2,5 +2,6 @@ atwilson@chromium.org
maniscalco@chromium.org
nick@chromium.org
pavely@chromium.org
+stanisc@chromium.org
tim@chromium.org
zea@chromium.org
diff --git a/chromium/chrome/browser/resources/sync_internals/about.css b/chromium/chrome/browser/resources/sync_internals/about.css
index 53410875cb3..50357f648cc 100644
--- a/chromium/chrome/browser/resources/sync_internals/about.css
+++ b/chromium/chrome/browser/resources/sync_internals/about.css
@@ -1,7 +1,6 @@
/* Copyright 2013 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.
- */
+ * found in the LICENSE file. */
#about-info {
-webkit-column-width: 350px;
@@ -119,11 +118,9 @@
.traffic-event-entry .time {
color: #222;
- font-family: sans-serif;
}
.traffic-event-entry .type {
- font-family: sans-serif;
font-weight: bold;
margin: 0.5em;
white-space: nowrap;
diff --git a/chromium/chrome/browser/resources/sync_internals/data.js b/chromium/chrome/browser/resources/sync_internals/data.js
index 6de42c706a7..f3729e745ed 100644
--- a/chromium/chrome/browser/resources/sync_internals/data.js
+++ b/chromium/chrome/browser/resources/sync_internals/data.js
@@ -126,7 +126,7 @@ function makeDateUserAgentHeader() {
/**
* Builds a summary of current state and exports it as a downloaded file.
*
- * @param {!Array.<{type: string, nodes: !Array<!Object>}>} nodesMap
+ * @param {!Array<{type: string, nodes: !Array<!Object>}>} nodesMap
* Summary of local state by model type.
*/
function triggerDataDownload(nodesMap) {
diff --git a/chromium/chrome/browser/resources/sync_internals/index.html b/chromium/chrome/browser/resources/sync_internals/index.html
index c67d831be91..b8472c4e191 100644
--- a/chromium/chrome/browser/resources/sync_internals/index.html
+++ b/chromium/chrome/browser/resources/sync_internals/index.html
@@ -1,10 +1,11 @@
-<!DOCTYPE HTML>
-<html i18n-values="dir:textdirection;">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
<head>
<!-- If you change the title, make sure you also update
chrome/test/functional/special_tabs.py. -->
<meta charset="utf-8">
<title>Sync Internals</title>
+<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="chrome://resources/css/list.css">
<link rel="stylesheet" href="chrome://resources/css/tabs.css">
<link rel="stylesheet" href="chrome://resources/css/tree.css">
@@ -43,7 +44,7 @@ chrome/test/functional/special_tabs.py. -->
<script src="chrome://sync-internals/sync_search.js"></script>
<script src="chrome://sync-internals/strings.js"></script>
</head>
-<body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<body>
<style>
#sync-page {
@@ -84,7 +85,7 @@ chrome/test/functional/special_tabs.py. -->
</tabpanels>
</tabbox>
-<script src="chrome://resources/js/i18n_template2.js"></script>
+<script src="chrome://resources/js/i18n_template.js"></script>
<script src="chrome://resources/js/jstemplate_compiled.js"></script>
<script src="chrome://sync-internals/sync_index.js"></script>
</body>
diff --git a/chromium/chrome/browser/resources/sync_internals/sync_node_browser.css b/chromium/chrome/browser/resources/sync_internals/sync_node_browser.css
index c669647e871..fc442ea7450 100644
--- a/chromium/chrome/browser/resources/sync_internals/sync_node_browser.css
+++ b/chromium/chrome/browser/resources/sync_internals/sync_node_browser.css
@@ -34,7 +34,7 @@
/* TODO(akalin): Find a better icon to use for leaf nodes. */
#sync-node-tree .leaf .tree-label {
- background-image: url('../../../../ui/webui/resources/images/star_small.png');
+ background-image: url(../../../../ui/webui/resources/images/star_small.png);
}
#sync-node-splitter {
diff --git a/chromium/chrome/browser/resources/translate_internals/translate_internals.html b/chromium/chrome/browser/resources/translate_internals/translate_internals.html
index 5024303f9f1..e8c3429c221 100644
--- a/chromium/chrome/browser/resources/translate_internals/translate_internals.html
+++ b/chromium/chrome/browser/resources/translate_internals/translate_internals.html
@@ -1,5 +1,5 @@
-<!DOCTYPE HTML>
-<html i18n-values="dir:textdirection">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
<!--
Copyright 2013 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
@@ -8,6 +8,7 @@ found in the LICENSE file.
<head>
<meta charset="utf-8">
<title>Translate Internals</title>
+ <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="chrome://resources/css/tabs.css">
<link rel="stylesheet" href="./translate_internals.css">
<script src="chrome://resources/js/util.js"></script>
@@ -21,7 +22,7 @@ found in the LICENSE file.
<script src="./translate_internals.js"></script>
</head>
- <body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+ <body>
<tabbox>
<tabs>
@@ -59,7 +60,7 @@ found in the LICENSE file.
</tabpanels>
</tabbox>
- <script src="chrome://resources/js/i18n_template2.js"></script>
+ <script src="chrome://resources/js/i18n_template.js"></script>
</body>
</html>
diff --git a/chromium/chrome/browser/resources/uber/uber.html b/chromium/chrome/browser/resources/uber/uber.html
index 1e81cf3be53..e9f04adf4cf 100644
--- a/chromium/chrome/browser/resources/uber/uber.html
+++ b/chromium/chrome/browser/resources/uber/uber.html
@@ -1,5 +1,5 @@
-<!DOCTYPE html>
-<html i18n-values="dir:textdirection;" id="uber" class="loading">
+<!doctype html>
+<html id="uber" class="loading" i18n-values="dir:textdirection;lang:language">
<head>
<meta charset="utf-8">
<title i18n-content="pageTitle"></title>
@@ -20,7 +20,7 @@
<body>
-<div id="navigation"><iframe src="chrome://uber-frame/" name="chrome"></iframe></div>
+<div id="navigation"><iframe src="chrome://uber-frame/" name="chrome" role="presentation"></iframe></div>
<div class="iframe-container"
i18n-values="id:historyHost; data-url:historyFrameURL;"
@@ -36,7 +36,7 @@
data-favicon="IDR_PRODUCT_LOGO_16"></div>
<script src="chrome://chrome/strings.js"></script>
-<script src="chrome://resources/js/i18n_template2.js"></script>
+<script src="chrome://resources/js/i18n_template.js"></script>
</body>
</html>
diff --git a/chromium/chrome/browser/resources/uber/uber.js b/chromium/chrome/browser/resources/uber/uber.js
index 19d3b611701..98618065a27 100644
--- a/chromium/chrome/browser/resources/uber/uber.js
+++ b/chromium/chrome/browser/resources/uber/uber.js
@@ -156,7 +156,7 @@ cr.define('uber', function() {
* @param {Event} e The posted object.
*/
function handleWindowMessage(e) {
- e = /** @type{!MessageEvent.<!{method: string, params: *}>} */(e);
+ e = /** @type{!MessageEvent<!{method: string, params: *}>} */(e);
if (e.data.method === 'beginInterceptingEvents') {
backgroundNavigation();
} else if (e.data.method === 'stopInterceptingEvents') {
@@ -337,6 +337,7 @@ cr.define('uber', function() {
if (!frame) {
frame = container.ownerDocument.createElement('iframe');
frame.name = pageId;
+ frame.setAttribute('role', 'presentation');
container.appendChild(frame);
frame.src = sourceUrl;
} else {
diff --git a/chromium/chrome/browser/resources/uber/uber_frame.css b/chromium/chrome/browser/resources/uber/uber_frame.css
index 76477ddc398..12a399695ce 100644
--- a/chromium/chrome/browser/resources/uber/uber_frame.css
+++ b/chromium/chrome/browser/resources/uber/uber_frame.css
@@ -2,6 +2,10 @@
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file. */
+html:not(.focus-outline-visible) :focus {
+ outline: none;
+}
+
body {
overflow: hidden;
padding-top: 20px;
diff --git a/chromium/chrome/browser/resources/uber/uber_frame.html b/chromium/chrome/browser/resources/uber/uber_frame.html
index 2af35443216..9992b7a41b3 100644
--- a/chromium/chrome/browser/resources/uber/uber_frame.html
+++ b/chromium/chrome/browser/resources/uber/uber_frame.html
@@ -1,5 +1,5 @@
-<!DOCTYPE html>
-<html i18n-values="dir:textdirection;" id="uber">
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language" id="uber">
<head>
<meta charset="utf-8">
@@ -8,35 +8,38 @@
<script src="chrome://resources/js/cr.js"></script>
<script src="chrome://resources/js/cr/ui/focus_manager.js"></script>
+<script src="chrome://resources/js/cr/ui/focus_outline_manager.js"></script>
<script src="chrome://resources/js/load_time_data.js"></script>
<script src="chrome://uber-frame/uber_frame.js"></script>
</head>
-<body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<body>
<h1 i18n-content="shortProductName"></h1>
-<ul>
+<ul role="tablist">
<li i18n-values="controls:historyHost;override:overridesHistory;
- group:historyGroup" hidden>
+ group:historyGroup" role="tab" hidden>
<button class="custom-appearance"
i18n-content="historyDisplayName"></button>
</li>
- <li i18n-values="controls:extensionsHost;group:extensionsGroup" hidden>
+ <li i18n-values="controls:extensionsHost;group:extensionsGroup" role="tab"
+ hidden>
<button class="custom-appearance"
i18n-content="extensionsDisplayName"></button>
</li>
- <li i18n-values="controls:settingsHost;group:settingsGroup" hidden>
+ <li i18n-values="controls:settingsHost;group:settingsGroup" role="tab" hidden>
<button class="custom-appearance"
i18n-content="settingsDisplayName"></button>
</li>
- <li id="helpNavItem" i18n-values="controls:helpHost;group:helpGroup" hidden>
+ <li id="helpNavItem" i18n-values="controls:helpHost;group:helpGroup"
+ role="tab" hidden>
<button class="custom-appearance"
i18n-content="helpDisplayName"></button>
</li>
</ul>
<script src="chrome://uber-frame/strings.js"></script>
-<script src="chrome://resources/js/i18n_template2.js"></script>
+<script src="chrome://resources/js/i18n_template.js"></script>
</body>
</html>
diff --git a/chromium/chrome/browser/resources/uber/uber_frame.js b/chromium/chrome/browser/resources/uber/uber_frame.js
index 62303040bb2..bef246caafb 100644
--- a/chromium/chrome/browser/resources/uber/uber_frame.js
+++ b/chromium/chrome/browser/resources/uber/uber_frame.js
@@ -22,12 +22,13 @@ cr.define('uber_frame', function() {
navigationItems[i].addEventListener('click', onNavItemClicked);
}
+ cr.ui.FocusOutlineManager.forDocument(this);
+
window.addEventListener('message', handleWindowMessage);
uber.invokeMethodOnParent('navigationControlsLoaded');
document.documentElement.addEventListener('mousewheel', onMouseWheel);
document.documentElement.addEventListener('mousedown', onMouseDown);
- cr.ui.FocusManager.disableMouseFocusOnButtons();
}
/**
@@ -93,11 +94,10 @@ cr.define('uber_frame', function() {
* @param {Element} newSelection The item to be selected.
*/
function setSelection(newSelection) {
- var lastSelectedNavItem = getSelectedNavItem();
- if (lastSelectedNavItem !== newSelection) {
- newSelection.classList.add('selected');
- if (lastSelectedNavItem)
- lastSelectedNavItem.classList.remove('selected');
+ var items = document.querySelectorAll('li');
+ for (var i = 0; i < items.length; ++i) {
+ items[i].classList.toggle('selected', items[i] == newSelection);
+ items[i].setAttribute('aria-selected', items[i] == newSelection);
}
}
@@ -133,8 +133,7 @@ cr.define('uber_frame', function() {
*/
function setContentChanging(enabled) {
assert(isRTL());
- document.documentElement.classList[enabled ? 'add' : 'remove'](
- 'changing-content');
+ document.documentElement.classList.toggle('changing-content', enabled);
}
/**
diff --git a/chromium/chrome/browser/resources/uber/uber_shared.css b/chromium/chrome/browser/resources/uber/uber_shared.css
index 5e45fa6c533..1ae995a4f55 100644
--- a/chromium/chrome/browser/resources/uber/uber_shared.css
+++ b/chromium/chrome/browser/resources/uber/uber_shared.css
@@ -174,7 +174,7 @@ body .section-header > h3 {
.page-banner-text {
-webkit-padding-end: 8px;
-webkit-padding-start: 26px;
- background-image: url('chrome://theme/IDR_CONTROLLED_SETTING_MANDATORY');
+ background-image: url(chrome://theme/IDR_CONTROLLED_SETTING_MANDATORY);
background-position: 5px center;
background-repeat: no-repeat;
background-size: 16px;
diff --git a/chromium/chrome/browser/resources/uber/uber_utils.js b/chromium/chrome/browser/resources/uber/uber_utils.js
index d4f30db61d3..9964321b151 100644
--- a/chromium/chrome/browser/resources/uber/uber_utils.js
+++ b/chromium/chrome/browser/resources/uber/uber_utils.js
@@ -67,7 +67,7 @@ cr.define('uber', function() {
* @param {Event} e The message event.
*/
function handleWindowMessage(e) {
- e = /** @type {!MessageEvent.<!{method: string, params: *}>} */(e);
+ e = /** @type {!MessageEvent<!{method: string, params: *}>} */(e);
if (e.data.method === 'frameSelected') {
handleFrameSelected();
} else if (e.data.method === 'mouseWheel') {
diff --git a/chromium/chrome/browser/resources/user_actions/user_actions.html b/chromium/chrome/browser/resources/user_actions/user_actions.html
index 9e1b2501461..6085f5e3d3d 100644
--- a/chromium/chrome/browser/resources/user_actions/user_actions.html
+++ b/chromium/chrome/browser/resources/user_actions/user_actions.html
@@ -1,8 +1,9 @@
-<!DOCTYPE HTML>
+<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>User Actions Debug Page</title>
+ <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="user_actions.css">
<script src="chrome://resources/js/cr.js"></script>
<script src="chrome://resources/js/util.js"></script>
diff --git a/chromium/chrome/browser/resources/user_actions/user_actions.js b/chromium/chrome/browser/resources/user_actions/user_actions.js
index dde9d1ed201..0064d66b862 100644
--- a/chromium/chrome/browser/resources/user_actions/user_actions.js
+++ b/chromium/chrome/browser/resources/user_actions/user_actions.js
@@ -13,7 +13,7 @@
*/
cr.define('userActions', function() {
- 'user strict';
+ 'use strict';
/**
* Appends a row to the output table listing the user action observed
diff --git a/chromium/chrome/browser/resources/user_manager/control_bar.css b/chromium/chrome/browser/resources/user_manager/control_bar.css
index 378ee911418..77347ff4234 100644
--- a/chromium/chrome/browser/resources/user_manager/control_bar.css
+++ b/chromium/chrome/browser/resources/user_manager/control_bar.css
@@ -37,14 +37,14 @@ html[dir=rtl] #login-header-bar button {
#login-header-bar #add-user-button {
background-image: -webkit-image-set(
- url('chrome://theme/IDR_ICON_PROFILES_ADD_USER') 1x,
- url('chrome://theme/IDR_ICON_PROFILES_ADD_USER@2x') 2x);
+ url(chrome://theme/IDR_ICON_PROFILES_ADD_USER) 1x,
+ url(chrome://theme/IDR_ICON_PROFILES_ADD_USER@2x) 2x);
}
#login-header-bar #guest-user-button {
background-image: -webkit-image-set(
- url('chrome://theme/IDR_ICON_PROFILES_BROWSE_GUEST') 1x,
- url('chrome://theme/IDR_ICON_PROFILES_BROWSE_GUEST@2x') 2x);
+ url(chrome://theme/IDR_ICON_PROFILES_BROWSE_GUEST) 1x,
+ url(chrome://theme/IDR_ICON_PROFILES_BROWSE_GUEST@2x) 2x);
border-right: 1px solid #e2e2e2;
margin-right: 10px;
}
@@ -78,8 +78,8 @@ html[dir=rtl] #login-header-bar #guest-user-button {
#logo {
content: -webkit-image-set(
- url('../../../app/theme/%DISTRIBUTION%/product_logo_name_48.png') 1x,
- url('../../../app/theme/%DISTRIBUTION%/product_logo_name_96.png') 2x);
+ url(../../../app/theme/%DISTRIBUTION%/product_logo_name_48.png) 1x,
+ url(../../../app/theme/%DISTRIBUTION%/product_logo_name_96.png) 2x);
height: 18px;
position: absolute;
right: 16px;
diff --git a/chromium/chrome/browser/resources/user_manager/user_manager.css b/chromium/chrome/browser/resources/user_manager/user_manager.css
index 1e553ae0653..e3fc7c32183 100644
--- a/chromium/chrome/browser/resources/user_manager/user_manager.css
+++ b/chromium/chrome/browser/resources/user_manager/user_manager.css
@@ -29,7 +29,15 @@ podrow[ncolumns='6'] .pod {
transform: scale(0.8);
}
+/* Because of crbug.com/406529, the text in the .name div is janky if there's
+both a transform:scale and a transition:opacity applied to a div, so we must
+apply the opacity change to the children instead. */
.pod.faded {
+ opacity: 1;
+}
+
+.pod.faded .user-image-pane,
+.pod.faded .main-pane {
opacity: .4;
}
@@ -99,15 +107,22 @@ html[dir=rtl] .pod .indicators {
.pod.locked .locked-indicator {
background-image: -webkit-image-set(
- url('chrome://theme/IDR_ICON_PROFILES_LOCKED') 1x,
- url('chrome://theme/IDR_ICON_PROFILES_LOCKED@2x') 2x);
+ url(chrome://theme/IDR_ICON_PROFILES_LOCKED) 1x,
+ url(chrome://theme/IDR_ICON_PROFILES_LOCKED@2x) 2x);
+ display: initial;
+}
+
+.pod.legacy-supervised .legacy-supervised-indicator {
+ background-image: -webkit-image-set(
+ url(chrome://theme/IDR_ICON_PROFILES_LEGACY_SUPERVISED) 1x,
+ url(chrome://theme/IDR_ICON_PROFILES_LEGACY_SUPERVISED@2x) 2x);
display: initial;
}
-.pod.supervised-user .supervised-indicator {
+.pod.child .child-indicator {
background-image: -webkit-image-set(
- url('chrome://theme/IDR_ICON_PROFILES_SUPERVISED') 1x,
- url('chrome://theme/IDR_ICON_PROFILES_SUPERVISED@2x') 2x);
+ url(chrome://theme/IDR_ICON_PROFILES_CHILD) 1x,
+ url(chrome://theme/IDR_ICON_PROFILES_CHILD@2x) 2x);
display: initial;
}
diff --git a/chromium/chrome/browser/resources/user_manager/user_manager.html b/chromium/chrome/browser/resources/user_manager/user_manager.html
index d89e4a61798..c884632f13e 100644
--- a/chromium/chrome/browser/resources/user_manager/user_manager.html
+++ b/chromium/chrome/browser/resources/user_manager/user_manager.html
@@ -1,5 +1,8 @@
-<!DOCTYPE HTML>
-<html i18n-values="dir:textdirection;screen:screenType;build:buildType">
+<!doctype html>
+<html i18n-values="dir:textdirection;
+ screen:screenType;
+ build:buildType;
+ lang:language">
<head>
<meta charset="utf-8">
<meta name="google" value="notranslate">
@@ -36,7 +39,7 @@
<script src="user_manager.js"></script>
<script src="chrome://user-manager/strings.js"></script>
</head>
-<body class="oobe-display" i18n-values=".style.fontFamily:fontfamily;">
+<body class="oobe-display">
<div id="outer-container">
<include src="user_manager_tutorial.html">
<div id="oobe" class="faded">
@@ -51,6 +54,6 @@
<div id="bubble" class="bubble faded" hidden></div>
<include src="control_bar.html">
<include src="../../../../ui/login/account_picker/user_pod_template.html">
- <script src="chrome://resources/js/i18n_template2.js"></script>
+ <script src="chrome://resources/js/i18n_template.js"></script>
</body>
</html>
diff --git a/chromium/chrome/browser/resources/user_manager/user_manager.js b/chromium/chrome/browser/resources/user_manager/user_manager.js
index 3cca699c7bc..39d76d4764a 100644
--- a/chromium/chrome/browser/resources/user_manager/user_manager.js
+++ b/chromium/chrome/browser/resources/user_manager/user_manager.js
@@ -58,11 +58,10 @@ cr.define('cr.ui', function() {
/**
* Open a new browser for the given profile.
- * @param {string} email The user's email, if signed in.
- * @param {string} displayName The user's display name.
+ * @param {string} profilePath The profile's path.
*/
- Oobe.launchUser = function(email, displayName) {
- chrome.send('launchUser', [email, displayName]);
+ Oobe.launchUser = function(profilePath) {
+ chrome.send('launchUser', [profilePath]);
};
/**
diff --git a/chromium/chrome/browser/resources/user_manager/user_manager_tutorial.css b/chromium/chrome/browser/resources/user_manager/user_manager_tutorial.css
index b0c6c60368e..eb0e9ced624 100644
--- a/chromium/chrome/browser/resources/user_manager/user_manager_tutorial.css
+++ b/chromium/chrome/browser/resources/user_manager/user_manager_tutorial.css
@@ -150,29 +150,29 @@ html[dir=rtl] #slide-guests .arrow-down {
#slide-your-chrome .slide-image {
background-color: rgb(241, 202, 58);
- background-image: url(
- 'chrome://theme/IDR_ICON_USER_MANAGER_TUTORIAL_YOUR_CHROME');
+ background-image:
+ url(chrome://theme/IDR_ICON_USER_MANAGER_TUTORIAL_YOUR_CHROME);
}
#slide-guests .slide-image {
background-color: rgb(90, 196, 144);
- background-image: url('chrome://theme/IDR_ICON_USER_MANAGER_TUTORIAL_GUESTS');
+ background-image: url(chrome://theme/IDR_ICON_USER_MANAGER_TUTORIAL_GUESTS);
}
#slide-friends .slide-image {
background-color: rgb(179, 229, 252);
- background-image: url(
- 'chrome://theme/IDR_ICON_USER_MANAGER_TUTORIAL_FRIENDS');
+ background-image:
+ url(chrome://theme/IDR_ICON_USER_MANAGER_TUTORIAL_FRIENDS);
}
#slide-complete .slide-image {
background-color: white;
- background-image: url(
- 'chrome://theme/IDR_ICON_USER_MANAGER_TUTORIAL_COMPLETE');
+ background-image:
+ url(chrome://theme/IDR_ICON_USER_MANAGER_TUTORIAL_COMPLETE);
}
#slide-not-you #dismiss-bubble-button {
- background-image: url('chrome://theme/IDR_CLOSE_1');
+ background-image: url(chrome://theme/IDR_CLOSE_1);
cursor: pointer;
height: 16px;
position: absolute;
diff --git a/chromium/chrome/browser/resources/user_manager/user_manager_tutorial.html b/chromium/chrome/browser/resources/user_manager/user_manager_tutorial.html
index c1da41d2afd..16b9a063c6e 100644
--- a/chromium/chrome/browser/resources/user_manager/user_manager_tutorial.html
+++ b/chromium/chrome/browser/resources/user_manager/user_manager_tutorial.html
@@ -49,6 +49,7 @@
<div id="dismiss-bubble-button"></div>
<div class="slide-buttons">
<div class="slide-text" i18n-content="slideCompleteUserNotFound"></div>
+ <br>
<a is="action-link" id="slide-add-user"
i18n-content="slideCompleteAddUser"></a>
</div>
diff --git a/chromium/chrome/browser/resources/webstore_app/OWNERS b/chromium/chrome/browser/resources/webstore_app/OWNERS
index 6fcb250e99e..63d9eb65573 100644
--- a/chromium/chrome/browser/resources/webstore_app/OWNERS
+++ b/chromium/chrome/browser/resources/webstore_app/OWNERS
@@ -1,7 +1,12 @@
# Extensions / Apps team members.
-asargent@chromium.org
-bolms@chromium.org
+kalman@chromium.org
finnur@chromium.org
-jstritar@chromium.org
+mek@chromium.org
+jyasskin@chromium.org
+asargent@chromium.org
+benwells@chromium.org
+reillyg@chromium.org
+scheib@chromium.org
+rockot@chromium.org
miket@chromium.org
-yoz@chromium.org
+rdevlin.cronin@chromium.org
diff --git a/chromium/chrome/browser/resources/whispernet_proxy/background.html b/chromium/chrome/browser/resources/whispernet_proxy/background.html
index 06a84761252..b14945d6b92 100644
--- a/chromium/chrome/browser/resources/whispernet_proxy/background.html
+++ b/chromium/chrome/browser/resources/whispernet_proxy/background.html
@@ -1,7 +1,8 @@
-<!DOCTYPE HTML>
+<!doctype html>
<html>
<head>
<meta charset="utf-8">
+<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<script src="js/nacl.js"></script>
<script src="js/wrapper.js"></script>
<script src="js/init.js"></script>
diff --git a/chromium/chrome/browser/resources/whispernet_proxy/js/init.js b/chromium/chrome/browser/resources/whispernet_proxy/js/init.js
index 693b7d4830b..93f60ab9387 100644
--- a/chromium/chrome/browser/resources/whispernet_proxy/js/init.js
+++ b/chromium/chrome/browser/resources/whispernet_proxy/js/init.js
@@ -4,87 +4,71 @@
'use strict';
-// Globals holding our encoder and decoder. We will never have more than one
-// Global variable that will be used to access this Nacl bridge.
+// Global holding our NaclBridge.
var whispernetNacl = null;
-// copy of an encoder or a decoder at a time.
-var whisperEncoder = null;
-var whisperDecoder = null;
+// Encoders and decoders for each client.
+var whisperEncoders = {};
+var whisperDecoders = {};
/**
* Initialize the whispernet encoder and decoder.
- * @param {Object} audioParams Audio parameters used to initialize the encoder
- * and decoder.
+ * Call this before any other functions.
+ * @param {string} clientId A string identifying the requester.
+ * @param {Object} audioParams Audio parameters for token encoding and decoding.
*/
-function initialize(audioParams) {
+function audioConfig(clientId, audioParams) {
if (!whispernetNacl) {
chrome.copresencePrivate.sendInitialized(false);
return;
}
- console.log('init: creating encoder!');
- whisperEncoder = new WhisperEncoder(audioParams.play, whispernetNacl);
- whisperEncoder.setAudioDataCallback(chrome.copresencePrivate.sendSamples);
-
- console.log('init: creating decoder!');
- whisperDecoder = new WhisperDecoder(audioParams.record, whispernetNacl);
- whisperDecoder.setReceiveCallback(chrome.copresencePrivate.sendFound);
- whisperDecoder.onDetectBroadcast(chrome.copresencePrivate.sendDetect);
-
- chrome.copresencePrivate.sendInitialized(true);
+ console.log('Configuring encoder and decoder for client ' + clientId);
+ whisperEncoders[clientId] =
+ new WhisperEncoder(audioParams.paramData, whispernetNacl, clientId);
+ whisperDecoders[clientId] =
+ new WhisperDecoder(audioParams.paramData, whispernetNacl, clientId);
}
/**
* Sends a request to whispernet to encode a token.
- * @param {string} token Token to encode. This needs to be a base64 string.
- * @param {boolean} audible Whether we should use encode audible samples.
+ * @param {string} clientId A string identifying the requester.
+ * @param {Object} params Encode token parameters object.
*/
-function encodeTokenRequest(token, audible) {
- if (whisperEncoder) {
- whisperEncoder.encode(atob(token), audible, true);
+function encodeTokenRequest(clientId, params) {
+ if (whisperEncoders[clientId]) {
+ whisperEncoders[clientId].encode(params);
} else {
- console.error('encodeTokenRequest: Whisper not initialized!');
+ console.error('encodeTokenRequest: Whisper not initialized for client ' +
+ clientId);
}
}
/**
* Sends a request to whispernet to decode samples.
- * @param {ArrayBuffer} samples Array of samples to process.
- * @param {Object} type Type of decoding to perform.
- */
-function decodeSamplesRequest(samples, type) {
- if (whisperDecoder) {
- whisperDecoder.processSamples(samples, type);
- } else {
- console.error('decodeSamplesRequest: Whisper not initialized!');
- }
-}
-
-/**
- * Sends a request to whispernet to detect broadcast.
+ * @param {string} clientId A string identifying the requester.
+ * @param {Object} params Process samples parameters object.
*/
-function detectBroadcastRequest() {
- if (whisperDecoder) {
- whisperDecoder.detectBroadcast();
+function decodeSamplesRequest(clientId, params) {
+ if (whisperDecoders[clientId]) {
+ whisperDecoders[clientId].processSamples(params);
} else {
- console.error('detectBroadcastRequest: Whisper not initialized!');
+ console.error('decodeSamplesRequest: Whisper not initialized for client ' +
+ clientId);
}
}
/**
- * Initialize our listerners and signal that the extension is loaded.
+ * Initialize our listeners and signal that the extension is loaded.
*/
function onWhispernetLoaded() {
console.log('init: Nacl ready!');
// Setup all the listeners for the private API.
- chrome.copresencePrivate.onInitialize.addListener(initialize);
+ chrome.copresencePrivate.onConfigAudio.addListener(audioConfig);
chrome.copresencePrivate.onEncodeTokenRequest.addListener(encodeTokenRequest);
chrome.copresencePrivate.onDecodeSamplesRequest.addListener(
decodeSamplesRequest);
- chrome.copresencePrivate.onDetectBroadcastRequest.addListener(
- detectBroadcastRequest);
// This first initialized is sent to indicate that the library is loaded.
// Every other time, it will be sent only when Chrome wants to reinitialize
diff --git a/chromium/chrome/browser/resources/whispernet_proxy/js/nacl.js b/chromium/chrome/browser/resources/whispernet_proxy/js/nacl.js
index 65057285d52..da8e48ca726 100644
--- a/chromium/chrome/browser/resources/whispernet_proxy/js/nacl.js
+++ b/chromium/chrome/browser/resources/whispernet_proxy/js/nacl.js
@@ -20,7 +20,7 @@ function NaclBridge(nmf, readyCallback) {
/**
* Method to send generic byte data to the whispernet wrapper.
- * @param {string} data Raw data to send to the whispernet wrapper.
+ * @param {Object} data Raw data to send to the whispernet wrapper.
*/
NaclBridge.prototype.send = function(data) {
if (this.isEnabled_) {
@@ -81,14 +81,13 @@ NaclBridge.prototype.loadNacl_ = function(manifestUrl) {
};
/**
- * Callback that is called when the Whispernet wrapper is loaded and forward
- * that status to the callback registered with us in the constructor.
+ * Called when the Whispernet wrapper is loaded.
* @private
*/
NaclBridge.prototype.onNaclReady_ = function() {
+ this.isEnabled_ = true;
if (this.readyCallback_)
this.readyCallback_();
- this.isEnabled_ = true;
};
/**
diff --git a/chromium/chrome/browser/resources/whispernet_proxy/js/wrapper.js b/chromium/chrome/browser/resources/whispernet_proxy/js/wrapper.js
index 1b5b495702d..29b57f7fc2f 100644
--- a/chromium/chrome/browser/resources/whispernet_proxy/js/wrapper.js
+++ b/chromium/chrome/browser/resources/whispernet_proxy/js/wrapper.js
@@ -14,7 +14,7 @@ function bytesToBase64(bytes) {
var bstr = '';
for (var i = 0; i < bytes.length; ++i)
bstr += String.fromCharCode(bytes[i]);
- return btoa(bstr);
+ return btoa(bstr).replace(/=/g, '');
}
/**
@@ -32,56 +32,50 @@ function stringToArray(str) {
/**
* Creates a whispernet encoder.
* @constructor
- * @param {Object} params Dictionary of parameters used to initialize the
- * whispernet encoder.
- * @param {Object} whisperNacl The NaclBridge object to use to communicate with
- * the whispernet wrapper.
+ * @param {Object} params Audio parameters for the whispernet encoder.
+ * @param {Object} whisperNacl The NaclBridge object, used to communicate with
+ * the whispernet wrapper.
+ * @param {string} clientId A string identifying the requester.
*/
-function WhisperEncoder(params, whisperNacl) {
- params = params || {};
- this.repetitions_ = params.repetitions || 3;
-
+function WhisperEncoder(params, whisperNacl, clientId) {
this.whisperNacl_ = whisperNacl;
this.whisperNacl_.addListener(this.onNaclMessage_.bind(this));
+ this.clientId_ = clientId;
var msg = {
type: 'initialize_encoder',
- sample_rate: params.sampleRate || 48000.0,
- upsampling_factor: params.bitsPerSample || 16,
+ client_id: clientId,
+ params: params
};
+
this.whisperNacl_.send(msg);
}
/**
* Method to encode a token.
- * @param {string} token Token to encode.
- * @param {boolean} audible Whether we should use encode audible samples.
- * @param {boolean} raw Whether we should return the encoded samples in raw
- * format or as a Wave file.
+ * @param {Object} params Encode token parameters object.
*/
-WhisperEncoder.prototype.encode = function(token, audible, raw) {
+WhisperEncoder.prototype.encode = function(params) {
+ // Pad the token before decoding it.
+ var token = params.token.token;
+ while (token.length % 4 > 0)
+ token += '=';
+
var msg = {
type: 'encode_token',
+ client_id: this.clientId_,
// Trying to send the token in binary form to Nacl doesn't work correctly.
// We end up with the correct string + a bunch of extra characters. This is
// true of returning a binary string too; hence we communicate back and
// forth by converting the bytes into an array of integers.
- token: stringToArray(token),
- repetitions: this.repetitions_,
- use_dtmf: audible,
- return_raw_samples: raw
+ token: stringToArray(atob(token)),
+ repetitions: params.repetitions,
+ use_dtmf: params.token.audible,
+ use_crc: params.tokenParams.crc,
+ use_parity: params.tokenParams.parity
};
- this.whisperNacl_.send(msg);
-};
-/**
- * Method to set the callback for encoded audio data received from the encoder
- * when we finish encoding a token.
- * @param {function(string, ArrayBuffer)} callback Callback which will receive
- * the audio samples.
- */
-WhisperEncoder.prototype.setAudioDataCallback = function(callback) {
- this.audioDataCallback_ = callback;
+ this.whisperNacl_.send(msg);
};
/**
@@ -91,8 +85,8 @@ WhisperEncoder.prototype.setAudioDataCallback = function(callback) {
*/
WhisperEncoder.prototype.onNaclMessage_ = function(e) {
var msg = e.data;
- if (msg.type == 'encode_token_response') {
- this.audioDataCallback_(
+ if (msg.type == 'encode_token_response' && msg.client_id == this.clientId_) {
+ chrome.copresencePrivate.sendSamples(this.clientId_,
{ token: bytesToBase64(msg.token), audible: msg.audible }, msg.samples);
}
};
@@ -100,94 +94,57 @@ WhisperEncoder.prototype.onNaclMessage_ = function(e) {
/**
* Creates a whispernet decoder.
* @constructor
- * @param {Object} params Dictionary of parameters used to initialize the
- * whispernet decoder.
- * @param {Object} whisperNacl The NaclBridge object to use to communicate with
- * the whispernet wrapper.
+ * @param {Object} params Audio parameters for the whispernet decoder.
+ * @param {Object} whisperNacl The NaclBridge object, used to communicate with
+ * the whispernet wrapper.
+ * @param {string} clientId A string identifying the requester.
*/
-function WhisperDecoder(params, whisperNacl) {
- params = params || {};
-
+function WhisperDecoder(params, whisperNacl, clientId) {
this.whisperNacl_ = whisperNacl;
this.whisperNacl_.addListener(this.onNaclMessage_.bind(this));
+ this.clientId_ = clientId;
var msg = {
type: 'initialize_decoder',
- channels: params.channels || 1,
- sample_rate: params.sampleRate || 48000.0,
- upsampling_factor: params.bitsPerSample || 16,
- max_candidates: 1,
- max_buffer_duration_in_seconds: 3
+ client_id: clientId,
+ params: params
};
this.whisperNacl_.send(msg);
}
/**
- * Method to request the decoder to wipe its internal buffer.
- */
-WhisperDecoder.prototype.wipeDecoder = function() {
- var msg = {
- type: 'wipe_decode_buffer'
- };
- this.whisperNacl_.send(msg);
-};
-
-/**
- * Method to request the decoder to detect a broadcast.
- */
-WhisperDecoder.prototype.detectBroadcast = function() {
- var msg = {
- type: 'detect_broadcast'
- };
- this.whisperNacl_.send(msg);
-};
-
-/**
* Method to request the decoder to process samples.
- * @param {ArrayBuffer} samples Array of samples to process.
- * @param {Object} type Type of decoding to perform.
+ * @param {Object} params Process samples parameters object.
*/
-WhisperDecoder.prototype.processSamples = function(samples, type) {
+WhisperDecoder.prototype.processSamples = function(params) {
var msg = {
type: 'decode_tokens',
- decode_audible: type.decodeAudible,
- decode_inaudible: type.decodeInaudible,
- data: samples,
+ client_id: this.clientId_,
+ data: params.samples,
+
+ decode_audible: params.decodeAudible,
+ token_length_dtmf: params.audibleTokenParams.length,
+ crc_dtmf: params.audibleTokenParams.crc,
+ parity_dtmf: params.audibleTokenParams.parity,
+
+ decode_inaudible: params.decodeInaudible,
+ token_length_dsss: params.inaudibleTokenParams.length,
+ crc_dsss: params.inaudibleTokenParams.crc,
+ parity_dsss: params.inaudibleTokenParams.parity,
};
this.whisperNacl_.send(msg);
};
/**
- * Method to set the callback for decoded tokens received from the decoder.
- * @param {function(!Array.string)} callback Callback to receive the list of
- * decoded tokens.
- */
-WhisperDecoder.prototype.setReceiveCallback = function(callback) {
- this.tokenCallback_ = callback;
-};
-
-/**
- * Method to set the callback for receiving the detect callback status received
- * from the decoder.
- * @param {function()} callback Callback to set to receive the detect broadcast
- * status.
- */
-WhisperDecoder.prototype.onDetectBroadcast = function(callback) {
- this.detectBroadcastCallback_ = callback;
-};
-
-/**
* Method to handle messages from the whispernet NaCl wrapper.
* @param {Event} e Event from the whispernet wrapper.
* @private
*/
WhisperDecoder.prototype.onNaclMessage_ = function(e) {
var msg = e.data;
- if (msg.type == 'decode_tokens_response') {
+ if (msg.type == 'decode_tokens_response' && msg.client_id == this.clientId_) {
this.handleCandidates_(msg.tokens, msg.audible);
- } else if (msg.type == 'detect_broadcast_response') {
- this.detectBroadcastCallback_(msg.detected);
}
};
@@ -200,7 +157,7 @@ WhisperDecoder.prototype.onNaclMessage_ = function(e) {
* @private
*/
WhisperDecoder.prototype.handleCandidates_ = function(candidates, audible) {
- if (!this.tokenCallback_ || !candidates || candidates.length == 0)
+ if (!candidates || candidates.length == 0)
return;
var returnCandidates = [];
@@ -208,6 +165,5 @@ WhisperDecoder.prototype.handleCandidates_ = function(candidates, audible) {
returnCandidates[i] = { token: bytesToBase64(candidates[i]),
audible: audible };
}
- this.tokenCallback_(returnCandidates);
+ chrome.copresencePrivate.sendFound(this.clientId_, returnCandidates);
};
-
diff --git a/chromium/chrome/browser/resources/whispernet_proxy/whispernet_proxy.nmf.png b/chromium/chrome/browser/resources/whispernet_proxy/whispernet_proxy.nmf.png
index bda9ba471c9..a552308c53e 100644
--- a/chromium/chrome/browser/resources/whispernet_proxy/whispernet_proxy.nmf.png
+++ b/chromium/chrome/browser/resources/whispernet_proxy/whispernet_proxy.nmf.png
@@ -1,7 +1,7 @@
{
"program": {
"portable": {
- "pnacl-translate": { "url": "whispernet_proxy_pnacl.pexe.png?v00002" }
+ "pnacl-translate": { "url": "whispernet_proxy_pnacl.pexe.png?v00008" }
}
}
}
diff --git a/chromium/chrome/browser/resources/whispernet_proxy/whispernet_proxy_pnacl.pexe.png b/chromium/chrome/browser/resources/whispernet_proxy/whispernet_proxy_pnacl.pexe.png
index 605f0d0cdba..fe8e69e7feb 100644
--- a/chromium/chrome/browser/resources/whispernet_proxy/whispernet_proxy_pnacl.pexe.png
+++ b/chromium/chrome/browser/resources/whispernet_proxy/whispernet_proxy_pnacl.pexe.png
Binary files differ
diff --git a/chromium/chrome/browser/safe_browsing/verifier_test/verifier_unittest.gyp b/chromium/chrome/browser/safe_browsing/incident_reporting/verifier_test/verifier_unittest.gyp
index 55ae89170a6..bf93f8e96bd 100644
--- a/chromium/chrome/browser/safe_browsing/verifier_test/verifier_unittest.gyp
+++ b/chromium/chrome/browser/safe_browsing/incident_reporting/verifier_test/verifier_unittest.gyp
@@ -1,4 +1,10 @@
{
+ 'variables': {
+ # Unconditionally disable incremental linking for these modules so that
+ # their exports do not go through an ILT jmp stub.
+ 'incremental_chrome_dll': '0', # 0 means no
+ 'msvs_debug_link_incremental': '1', # 1 means /INCREMENTAL:NO
+ },
'targets': [
{
'target_name': 'verifier_test_dll_1',
diff --git a/chromium/chrome/browser/ui/libgtk2ui/libgtk2ui.gyp b/chromium/chrome/browser/ui/libgtk2ui/libgtk2ui.gyp
index ffe341e3294..d3e3004bed8 100644
--- a/chromium/chrome/browser/ui/libgtk2ui/libgtk2ui.gyp
+++ b/chromium/chrome/browser/ui/libgtk2ui/libgtk2ui.gyp
@@ -18,11 +18,22 @@
'../../../../build/linux/system.gyp:gconf',
'../../../../build/linux/system.gyp:gtk',
'../../../../build/linux/system.gyp:gtkprint',
+ '../../../../build/linux/system.gyp:x11',
'../../../../components/components_resources.gyp:components_resources',
+ '../../../../content/content.gyp:content',
'../../../../printing/printing.gyp:cups',
+ '../../../../printing/printing.gyp:printing',
'../../../../skia/skia.gyp:skia',
+ '../../../../ui/aura/aura.gyp:aura',
+ '../../../../ui/base/ime/ui_base_ime.gyp:ui_base_ime',
'../../../../ui/base/ui_base.gyp:ui_base',
+ '../../../../ui/events/events.gyp:events',
+ '../../../../ui/events/events.gyp:events_base',
+ '../../../../ui/gfx/gfx.gyp:gfx',
+ '../../../../ui/gfx/x/gfx_x11.gyp:gfx_x11',
+ '../../../../ui/native_theme/native_theme.gyp:native_theme',
'../../../../ui/resources/ui_resources.gyp:ui_resources',
+ '../../../../ui/shell_dialogs/shell_dialogs.gyp:shell_dialogs',
'../../../../ui/strings/ui_strings.gyp:ui_strings',
'../../../../ui/views/views.gyp:views',
'../../../chrome_resources.gyp:chrome_extra_resources',
@@ -96,10 +107,13 @@
],
}],
[ 'clang==1', {
- # G_DEFINE_TYPE automatically generates a *get_instance_private inline function after glib 2.37.
- # That's unused. Prevent to complain about it.
'cflags': [
+ # G_DEFINE_TYPE automatically generates a *get_instance_private inline function after glib 2.37.
+ # That's unused. Prevent to complain about it.
'-Wno-unused-function',
+
+ # G_STATIC_ASSERT uses a typedef as a static_assert.
+ '-Wno-unused-local-typedef',
],
}],
],