summaryrefslogtreecommitdiffstats
path: root/third_party/google_input_tools
diff options
context:
space:
mode:
authorkevers <kevers@chromium.org>2014-10-27 07:34:45 -0700
committerCommit bot <commit-bot@chromium.org>2014-10-27 14:35:14 +0000
commit9d9b00dc7bc6a38448ff0d53738d311fa5df4df4 (patch)
treeb348f60d6bc4de9261c2704cfe84338d9094e268 /third_party/google_input_tools
parent48e356db5b69123ce7178a51778feef47ee687c0 (diff)
downloadchromium_src-9d9b00dc7bc6a38448ff0d53738d311fa5df4df4.zip
chromium_src-9d9b00dc7bc6a38448ff0d53738d311fa5df4df4.tar.gz
chromium_src-9d9b00dc7bc6a38448ff0d53738d311fa5df4df4.tar.bz2
Add third_party/google-input-tools: Take 2
BUG=401729 Abandoning Issue 451873002 in favor of directly adding google-input-tools to src/third_party. A subset of the open sourced library depends on third_party code where the licensing is unclear. The required pieces strictly support Apache 2.0 licensing. Review URL: https://codereview.chromium.org/674153004 Cr-Commit-Position: refs/heads/master@{#301361}
Diffstat (limited to 'third_party/google_input_tools')
-rw-r--r--third_party/google_input_tools/LICENSE201
-rw-r--r--third_party/google_input_tools/Makefile24
-rw-r--r--third_party/google_input_tools/OWNERS4
-rw-r--r--third_party/google_input_tools/README.chromium22
-rw-r--r--third_party/google_input_tools/src/chrome/os/datasource.js189
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/adapter.js667
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/candidatesinfo.js42
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/canvas.js425
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/conditionname.js30
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/config/compact_letter_characters.js512
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/config/compact_more_characters.js160
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/config/compact_numberpad_characters.js69
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/config/compact_symbol_characters.js169
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/config/compact_util.js378
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/config/constants.js81
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/config/contextlayoututil.js52
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/config/util.js605
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/controller.js1658
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/covariance.js111
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/css.js164
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/direction.js28
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/dom.js229
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/elements/content/altdataview.js340
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/elements/content/candidate.js113
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/elements/content/candidatebutton.js117
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/elements/content/candidateview.js367
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/elements/content/canvasview.js312
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/elements/content/character.js196
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/elements/content/characterkey.js299
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/elements/content/charactermodel.js254
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/elements/content/compactkey.js252
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/elements/content/emojikey.js117
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/elements/content/emojiview.js423
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/elements/content/enswitcherkey.js102
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/elements/content/expandedcandidateview.js326
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/elements/content/functionalkey.js197
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/elements/content/gaussianestimator.js87
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/elements/content/handwritingview.js92
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/elements/content/indicator.js99
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/elements/content/keyboardview.js208
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/elements/content/keysetview.js727
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/elements/content/menuitem.js138
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/elements/content/menukey.js63
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/elements/content/menuview.js372
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/elements/content/modifierkey.js146
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/elements/content/morekeysshiftoperation.js27
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/elements/content/softkey.js127
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/elements/content/spacekey.js120
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/elements/content/switcherkey.js83
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/elements/content/tabbarkey.js137
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/elements/element.js187
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/elements/elementtype.js67
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/elements/layout/extendedlayout.js229
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/elements/layout/handwritinglayout.js121
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/elements/layout/linearlayout.js137
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/elements/layout/softkeyview.js150
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/elements/layout/verticallayout.js136
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/elements/weightable.js44
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/emojitype.js33
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/events.js253
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/events/keycodes.js313
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/globalsettings.js235
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/handler/pointeractionbundle.js447
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/handler/pointerhandler.js184
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/handler/swipestate.js61
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/hwt_css.js65
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/hwt_eventtype.js96
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/hwt_util.js74
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/inputtoolcode.js369
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/keyboardcontainer.js311
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/layouts/compactspacerow.js72
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/layouts/rowsof101.js90
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/layouts/rowsof102.js108
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/layouts/rowsofcompact.js179
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/layouts/rowsofjp.js109
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/layouts/spacerow.js76
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/layouts/util.js221
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/m17nmodel.js157
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/model.js166
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/pointerconfig.js76
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/readystate.js87
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/settings.js96
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/sizespec.js70
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/soundcontroller.js226
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/sounds.js29
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/specnodename.js70
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/statemanager.js233
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/statetype.js30
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/statistics.js147
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/strokehandler.js252
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/swipedirection.js29
-rw-r--r--third_party/google_input_tools/src/chrome/os/inputview/util.js296
-rw-r--r--third_party/google_input_tools/src/chrome/os/keyboard/eventtype.js42
-rw-r--r--third_party/google_input_tools/src/chrome/os/keyboard/keycode.js86
-rw-r--r--third_party/google_input_tools/src/chrome/os/keyboard/layoutevent.js64
-rw-r--r--third_party/google_input_tools/src/chrome/os/keyboard/model.js424
-rw-r--r--third_party/google_input_tools/src/chrome/os/keyboard/parsedlayout.js423
-rw-r--r--third_party/google_input_tools/src/chrome/os/message/contenttype.js31
-rw-r--r--third_party/google_input_tools/src/chrome/os/message/event.js40
-rw-r--r--third_party/google_input_tools/src/chrome/os/message/name.js62
-rw-r--r--third_party/google_input_tools/src/chrome/os/message/type.js57
-rw-r--r--third_party/google_input_tools/third_party/closure_library/AUTHORS17
-rw-r--r--third_party/google_input_tools/third_party/closure_library/LICENSE201
-rw-r--r--third_party/google_input_tools/third_party/closure_library/README.chromium18
-rwxr-xr-xthird_party/google_input_tools/third_party/closure_library/closure/bin/build/closurebuilder.py274
-rwxr-xr-xthird_party/google_input_tools/third_party/closure_library/closure/bin/build/depstree.py190
-rwxr-xr-xthird_party/google_input_tools/third_party/closure_library/closure/bin/build/depstree_test.py127
-rwxr-xr-xthird_party/google_input_tools/third_party/closure_library/closure/bin/build/depswriter.py202
-rwxr-xr-xthird_party/google_input_tools/third_party/closure_library/closure/bin/build/jscompiler.py136
-rwxr-xr-xthird_party/google_input_tools/third_party/closure_library/closure/bin/build/source.py119
-rwxr-xr-xthird_party/google_input_tools/third_party/closure_library/closure/bin/build/source_test.py134
-rwxr-xr-xthird_party/google_input_tools/third_party/closure_library/closure/bin/build/treescan.py78
-rwxr-xr-xthird_party/google_input_tools/third_party/closure_library/closure/bin/calcdeps.py590
-rwxr-xr-xthird_party/google_input_tools/third_party/closure_library/closure/bin/scopify.py221
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/a11y/aria/announcer.js111
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/a11y/aria/aria.js364
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/a11y/aria/attributes.js389
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/a11y/aria/datatables.js68
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/a11y/aria/roles.js216
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/array/array.js1570
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/asserts/asserts.js344
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/async/delay.js179
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/async/nexttick.js203
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/async/run.js150
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/base.js1875
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/debug/debug.js590
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/debug/entrypointregistry.js158
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/debug/error.js54
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/debug/errorhandlerweakdep.js38
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/debug/logbuffer.js148
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/debug/logger.js885
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/debug/logrecord.js271
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/disposable/disposable.js299
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/disposable/idisposable.js45
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/dom/browserfeature.js72
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/dom/classes.js227
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/dom/classlist.js277
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/dom/dom.js2917
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/dom/nodetype.js48
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/dom/tagname.js159
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/dom/vendor.js96
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/events/browserevent.js390
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/events/browserfeature.js85
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/events/event.js161
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/events/eventhandler.js461
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/events/eventid.js47
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/events/events.js995
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/events/eventtarget.js395
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/events/eventtype.js226
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/events/eventwrapper.js68
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/events/keycodes.js415
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/events/keyhandler.js556
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/events/listenable.js334
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/events/listener.js131
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/events/listenermap.js308
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/functions/functions.js311
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/i18n/bidi.js883
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/iter/iter.js1314
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/labs/useragent/browser.js271
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/labs/useragent/engine.js130
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/labs/useragent/util.js154
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/log/log.js197
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/math/box.js388
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/math/coordinate.js267
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/math/math.js435
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/math/rect.js463
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/math/size.js206
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/net/jsloader.js367
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/object/object.js637
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/positioning/positioning.js557
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/promise/promise.js985
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/promise/resolver.js48
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/promise/thenable.js111
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/reflect/reflect.js77
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/string/string.js1476
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/structs/collection.js56
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/structs/map.js461
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/structs/set.js280
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/structs/structs.js354
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/style/bidi.js184
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/style/style.js2105
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/testing/watchers.js46
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/timer/timer.js292
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/ui/component.js1291
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/ui/container.js1324
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/ui/containerrenderer.js374
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/ui/control.js1388
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/ui/controlcontent.js28
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/ui/controlrenderer.js927
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/ui/decorate.js38
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/ui/idgenerator.js48
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/ui/menuseparatorrenderer.js113
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/ui/registry.js172
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/ui/separator.js80
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/uri/utils.js1076
-rw-r--r--third_party/google_input_tools/third_party/closure_library/closure/goog/useragent/useragent.js553
-rw-r--r--third_party/google_input_tools/third_party/closure_library/third_party/closure/goog/mochikit/async/deferred.js922
-rwxr-xr-xthird_party/google_input_tools/update.py243
198 files changed, 59485 insertions, 0 deletions
diff --git a/third_party/google_input_tools/LICENSE b/third_party/google_input_tools/LICENSE
new file mode 100644
index 0000000..806b547
--- /dev/null
+++ b/third_party/google_input_tools/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2013 Google Inc.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License. \ No newline at end of file
diff --git a/third_party/google_input_tools/Makefile b/third_party/google_input_tools/Makefile
new file mode 100644
index 0000000..a648436
--- /dev/null
+++ b/third_party/google_input_tools/Makefile
@@ -0,0 +1,24 @@
+# Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+# limitations under the License.
+# See the License for the specific language governing permissions and
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# distributed under the License is distributed on an "AS-IS" BASIS,
+# Unless required by applicable law or agreed to in writing, software
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# You may obtain a copy of the License at
+# you may not use this file except in compliance with the License.
+# Licensed under the Apache License, Version 2.0 (the "License");
+#
+SHELL:=/bin/bash
+BUILD_DIR:=src/chrome/os/inputview/build
+
+all: inputview.js
+
+inputview.js: clean
+ @make -C $(BUILD_DIR)
+ @cp $(BUILD_DIR)/inputview.js .
+
+clean:
+ $(RM) $(BUILD_DIR)/inputview.js
diff --git a/third_party/google_input_tools/OWNERS b/third_party/google_input_tools/OWNERS
new file mode 100644
index 0000000..aaea42c
--- /dev/null
+++ b/third_party/google_input_tools/OWNERS
@@ -0,0 +1,4 @@
+bshe@chromium.org
+kevers@chromium.org
+rsadam@chromium.org
+shuchen@chromium.org
diff --git a/third_party/google_input_tools/README.chromium b/third_party/google_input_tools/README.chromium
new file mode 100644
index 0000000..e5f351b
--- /dev/null
+++ b/third_party/google_input_tools/README.chromium
@@ -0,0 +1,22 @@
+Name: Google Input Tools
+Short Name: google_input_tools
+URL: https://github.com/googlei18n/google-input-tools.git
+Version: 1.0.4.0
+Revision: @b99e802e53613e07f24a779695c1c6f3bd2f864a
+License: Apache 2.0
+License File: LICENSE
+Security Critical: yes
+
+Description:
+This directory contains source for the google-input-tools project, which
+provides multi-lingual input support. In particular, google-input-tools is
+used in ChromeOS to provide a fallback virtual keyboard for IMEs that are not
+VK-aware.
+
+To update to a newer version of google-input-tools, run the following script:
+
+update.py --input=_path_to_google_input_tools_ --lib=_path_to_closure_lib_
+
+Local Modifications:
+Only includes the portion of google-input-tools required to build an inputview-
+based virtual keyboard. \ No newline at end of file
diff --git a/third_party/google_input_tools/src/chrome/os/datasource.js b/third_party/google_input_tools/src/chrome/os/datasource.js
new file mode 100644
index 0000000..5c8f14e
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/datasource.js
@@ -0,0 +1,189 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.DataSource');
+
+goog.require('goog.events');
+goog.require('goog.events.Event');
+goog.require('goog.events.EventTarget');
+goog.require('goog.functions');
+
+
+/**
+ * The data source.
+ *
+ * @param {number} numOfCanddiate The number of canddiate to fetch.
+ * @param {function(string, !Array.<!Object>)} callback .
+ * @constructor
+ * @extends {goog.events.EventTarget}
+ */
+i18n.input.chrome.DataSource = function(numOfCanddiate, callback) {
+ goog.base(this);
+
+ /**
+ * The number of candidates to fetch.
+ *
+ * @type {number}
+ */
+ this.numOfCandidate = numOfCanddiate;
+
+ /** @protected {function(string, !Array.<!Object>)} */
+ this.callback = callback;
+};
+goog.inherits(i18n.input.chrome.DataSource, goog.events.EventTarget);
+
+
+/** @type {boolean} */
+i18n.input.chrome.DataSource.prototype.ready = false;
+
+
+/**
+ * The correction level.
+ *
+ * @protected {number}
+ */
+i18n.input.chrome.DataSource.prototype.correctionLevel = 0;
+
+
+/**
+ * The language code.
+ *
+ * @type {string}
+ * @protected
+ */
+i18n.input.chrome.DataSource.prototype.language;
+
+
+/**
+ * Sets the langauge code.
+ *
+ * @param {string} language The language code.
+ */
+i18n.input.chrome.DataSource.prototype.setLanguage = function(
+ language) {
+ this.language = language;
+};
+
+
+/**
+ * True if the datasource is ready.
+ *
+ * @return {boolean} .
+ */
+i18n.input.chrome.DataSource.prototype.isReady = function() {
+ return this.ready;
+};
+
+
+/**
+ * Creates the common payload for completion or prediction request.
+ *
+ * @return {!Object} The payload.
+ * @protected
+ */
+i18n.input.chrome.DataSource.prototype.createCommonPayload =
+ function() {
+ return {
+ 'itc': this.getInputToolCode(),
+ 'num': this.numOfCandidate
+ };
+};
+
+
+/**
+ * Gets the input tool code.
+ *
+ * @return {string} .
+ */
+i18n.input.chrome.DataSource.prototype.getInputToolCode = function() {
+ return this.language + '-t-i0-und';
+};
+
+
+/**
+ * Sends completion request.
+ *
+ * @param {string} query The query .
+ * @param {string} context The context .
+ * @param {!Object=} opt_spatialData .
+ */
+i18n.input.chrome.DataSource.prototype.sendCompletionRequest =
+ goog.functions.NULL;
+
+
+/**
+ * Sends prediciton request.
+ *
+ * @param {string} context The context.
+ */
+i18n.input.chrome.DataSource.prototype.sendPredictionRequest =
+ goog.functions.NULL;
+
+
+/**
+ * Sets the correction level.
+ *
+ * @param {number} level .
+ */
+i18n.input.chrome.DataSource.prototype.setCorrectionLevel = function(level) {
+ this.correctionLevel = level;
+};
+
+
+/**
+ * Clears the data source.
+ */
+i18n.input.chrome.DataSource.prototype.clear = goog.functions.NULL;
+
+
+/**
+ * The event type.
+ *
+ * @enum {string}
+ */
+i18n.input.chrome.DataSource.EventType = {
+ CANDIDATES_BACK: goog.events.getUniqueId('cb'),
+ READY: goog.events.getUniqueId('r')
+};
+
+
+
+/**
+ * The candidates is fetched back.
+ *
+ * @param {string} source The source.
+ * @param {!Array.<!Object>} candidates The candidates.
+ * @constructor
+ * @extends {goog.events.Event}
+ */
+i18n.input.chrome.DataSource.CandidatesBackEvent =
+ function(source, candidates) {
+ goog.base(this, i18n.input.chrome.DataSource.EventType.CANDIDATES_BACK);
+
+ /**
+ * The source.
+ *
+ * @type {string}
+ */
+ this.source = source;
+
+ /**
+ * The candidate list.
+ *
+ * @type {!Array.<!Object>}
+ */
+ this.candidates = candidates;
+};
+goog.inherits(i18n.input.chrome.DataSource.CandidatesBackEvent,
+ goog.events.Event);
+
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/adapter.js b/third_party/google_input_tools/src/chrome/os/inputview/adapter.js
new file mode 100644
index 0000000..dd7335b
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/adapter.js
@@ -0,0 +1,667 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.Adapter');
+
+goog.require('goog.events.Event');
+goog.require('goog.events.EventHandler');
+goog.require('goog.events.EventTarget');
+goog.require('goog.events.EventType');
+goog.require('goog.object');
+goog.require('i18n.input.chrome.DataSource');
+goog.require('i18n.input.chrome.inputview.ReadyState');
+goog.require('i18n.input.chrome.inputview.StateType');
+goog.require('i18n.input.chrome.inputview.events.EventType');
+goog.require('i18n.input.chrome.inputview.events.SurroundingTextChangedEvent');
+goog.require('i18n.input.chrome.message.ContextType');
+goog.require('i18n.input.chrome.message.Event');
+goog.require('i18n.input.chrome.message.Name');
+goog.require('i18n.input.chrome.message.Type');
+
+goog.scope(function() {
+var CandidatesBackEvent = i18n.input.chrome.DataSource.CandidatesBackEvent;
+var ContextType = i18n.input.chrome.message.ContextType;
+var Type = i18n.input.chrome.message.Type;
+var Name = i18n.input.chrome.message.Name;
+
+
+
+/**
+ * The adapter for interview.
+ *
+ * @param {!i18n.input.chrome.inputview.ReadyState} readyState .
+ * @extends {goog.events.EventTarget}
+ * @constructor
+ */
+i18n.input.chrome.inputview.Adapter = function(readyState) {
+ goog.base(this);
+
+ /**
+ * Whether the keyboard is visible.
+ *
+ * @type {boolean}
+ */
+ this.isVisible = !document.webkitHidden;
+
+ /**
+ * The modifier state map.
+ *
+ * @type {!Object.<i18n.input.chrome.inputview.StateType, boolean>}
+ * @private
+ */
+ this.modifierState_ = {};
+
+ /**
+ * The system ready state.
+ *
+ * @private {!i18n.input.chrome.inputview.ReadyState}
+ */
+ this.readyState_ = readyState;
+
+ chrome.runtime.onMessage.addListener(this.onMessage_.bind(this));
+
+ /** @private {!goog.events.EventHandler} */
+ this.handler_ = new goog.events.EventHandler(this);
+ this.handler_.listen(document, 'webkitvisibilitychange',
+ this.onVisibilityChange_);
+};
+goog.inherits(i18n.input.chrome.inputview.Adapter,
+ goog.events.EventTarget);
+var Adapter = i18n.input.chrome.inputview.Adapter;
+
+
+/** @type {boolean} */
+Adapter.prototype.isA11yMode = false;
+
+
+/** @type {boolean} */
+Adapter.prototype.isExperimental = false;
+
+
+/** @type {boolean} */
+Adapter.prototype.showGlobeKey = false;
+
+
+/** @protected {string} */
+Adapter.prototype.contextType = ContextType.DEFAULT;
+
+
+/** @type {string} */
+Adapter.prototype.screen = '';
+
+
+/** @type {boolean} */
+Adapter.prototype.isChromeVoxOn = false;
+
+
+/** @type {string} */
+Adapter.prototype.textBeforeCursor = '';
+
+
+/**
+ * Callback for updating settings.
+ *
+ * @param {!Object} message .
+ * @private
+ */
+Adapter.prototype.onUpdateSettings_ = function(message) {
+ this.contextType = message['contextType'];
+ this.screen = message['screen'];
+ this.dispatchEvent(new i18n.input.chrome.message.Event(Type.UPDATE_SETTINGS,
+ message));
+};
+
+
+/**
+ * Sets the modifier states.
+ *
+ * @param {i18n.input.chrome.inputview.StateType} stateType .
+ * @param {boolean} enable True to enable the state, false otherwise.
+ */
+Adapter.prototype.setModifierState = function(stateType, enable) {
+ this.modifierState_[stateType] = enable;
+};
+
+
+/**
+ * Clears the modifier states.
+ */
+Adapter.prototype.clearModifierStates = function() {
+ this.modifierState_ = {};
+};
+
+
+/**
+ * Simulates to send 'keydown' and 'keyup' event.
+ *
+ * @param {string} key
+ * @param {string} code
+ * @param {number=} opt_keyCode The key code.
+ * @param {!Object=} opt_spatialData .
+ */
+Adapter.prototype.sendKeyDownAndUpEvent = function(key, code, opt_keyCode,
+ opt_spatialData) {
+ this.sendKeyEvent_([
+ this.generateKeyboardEvent_(
+ goog.events.EventType.KEYDOWN, key, code, opt_keyCode, opt_spatialData),
+ this.generateKeyboardEvent_(
+ goog.events.EventType.KEYUP, key, code, opt_keyCode, opt_spatialData)
+ ]);
+};
+
+
+/**
+ * Simulates to send 'keydown' event.
+ *
+ * @param {string} key
+ * @param {string} code
+ * @param {number=} opt_keyCode The key code.
+ * @param {!Object=} opt_spatialData .
+ */
+Adapter.prototype.sendKeyDownEvent = function(key, code, opt_keyCode,
+ opt_spatialData) {
+ this.sendKeyEvent_([this.generateKeyboardEvent_(
+ goog.events.EventType.KEYDOWN, key, code, opt_keyCode,
+ opt_spatialData)]);
+};
+
+
+/**
+ * Simulates to send 'keyup' event.
+ *
+ * @param {string} key
+ * @param {string} code
+ * @param {number=} opt_keyCode The key code.
+ * @param {!Object=} opt_spatialData .
+ */
+Adapter.prototype.sendKeyUpEvent = function(key, code, opt_keyCode,
+ opt_spatialData) {
+ this.sendKeyEvent_([this.generateKeyboardEvent_(
+ goog.events.EventType.KEYUP, key, code, opt_keyCode, opt_spatialData)]);
+};
+
+
+/**
+ * Use {@code chrome.input.ime.sendKeyEvents} to simulate key events.
+ *
+ * @param {!Array.<!Object.<string, string|boolean>>} keyData .
+ * @private
+ */
+Adapter.prototype.sendKeyEvent_ = function(keyData) {
+ chrome.runtime.sendMessage(
+ goog.object.create(Name.MSG_TYPE, Type.SEND_KEY_EVENT, Name.KEY_DATA,
+ keyData));
+};
+
+
+/**
+ * Generates a {@code ChromeKeyboardEvent} by given values.
+ *
+ * @param {string} type .
+ * @param {string} key The key.
+ * @param {string} code The code.
+ * @param {number=} opt_keyCode The key code.
+ * @param {!Object=} opt_spatialData .
+ * @return {!Object.<string, string|boolean>}
+ * @private
+ */
+Adapter.prototype.generateKeyboardEvent_ = function(
+ type, key, code, opt_keyCode, opt_spatialData) {
+ var StateType = i18n.input.chrome.inputview.StateType;
+ var ctrl = !!this.modifierState_[StateType.CTRL];
+ var alt = !!this.modifierState_[StateType.ALT];
+
+ if (ctrl || alt) {
+ key = '';
+ }
+ var result = {
+ 'type': type,
+ 'key': key,
+ 'code': code,
+ 'keyCode': opt_keyCode || 0,
+ 'spatialData': opt_spatialData
+ };
+
+ result['altKey'] = alt;
+ result['ctrlKey'] = ctrl;
+ result['shiftKey'] = !!this.modifierState_[StateType.SHIFT];
+ result['capsLock'] = !!this.modifierState_[StateType.CAPSLOCK];
+
+ return result;
+};
+
+
+/**
+ * Callback when surrounding text is changed.
+ *
+ * @param {string} text .
+ * @private
+ */
+Adapter.prototype.onSurroundingTextChanged_ = function(text) {
+ this.textBeforeCursor = text;
+ this.dispatchEvent(new i18n.input.chrome.inputview.events.
+ SurroundingTextChangedEvent(this.textBeforeCursor));
+};
+
+
+/**
+ * Gets the context.
+ *
+ * @return {string} .
+ */
+Adapter.prototype.getContext = function() {
+ var matches = this.textBeforeCursor.match(/([a-zA-Z'-Ḁ-ỹÀ-ȳ]+)\s+$/);
+ var text = matches ? matches[1] : '';
+ return text;
+};
+
+
+/**
+ * Gets the context type.
+ *
+ * @return {string} .
+ */
+Adapter.prototype.getContextType = function() {
+ return this.contextType || ContextType.DEFAULT;
+};
+
+
+/**
+ * Sends request for handwriting.
+ *
+ * @param {!Object} payload .
+ */
+Adapter.prototype.sendHwtRequest = function(payload) {
+ chrome.runtime.sendMessage(goog.object.create(
+ Name.MSG_TYPE, Type.HWT_REQUEST, Name.MSG, payload
+ ));
+};
+
+
+/**
+ * True if it is a password box.
+ *
+ * @return {boolean} .
+ */
+Adapter.prototype.isPasswordBox = function() {
+ return this.contextType == 'password';
+};
+
+
+/**
+ * Callback when blurs in the context.
+ *
+ * @private
+ */
+Adapter.prototype.onContextBlur_ = function() {
+ this.contextType = '';
+ this.dispatchEvent(new goog.events.Event(i18n.input.chrome.inputview.events.
+ EventType.CONTEXT_BLUR));
+};
+
+
+/**
+ * Callback when focus on a context.
+ *
+ * @param {string} contextType .
+ * @private
+ */
+Adapter.prototype.onContextFocus_ = function(contextType) {
+ this.contextType = contextType;
+ this.dispatchEvent(new goog.events.Event(i18n.input.chrome.inputview.events.
+ EventType.CONTEXT_FOCUS));
+};
+
+
+/**
+ * Intializes the communication to background page.
+ *
+ * @param {string} languageCode The language code.
+ * @private
+ */
+Adapter.prototype.initBackground_ = function(languageCode) {
+ chrome.runtime.getBackgroundPage((function() {
+ chrome.runtime.sendMessage(
+ goog.object.create(Name.MSG_TYPE, Type.CONNECT));
+ chrome.runtime.sendMessage(goog.object.create(Name.MSG_TYPE,
+ Type.VISIBILITY_CHANGE, Name.VISIBILITY, !document.webkitHidden));
+ if (languageCode) {
+ this.setLanguage(languageCode);
+ }
+ }).bind(this));
+};
+
+
+/**
+ * Loads the keyboard settings.
+ *
+ * @param {string} languageCode The language code.
+ */
+Adapter.prototype.initialize = function(languageCode) {
+ if (chrome.accessibilityFeatures &&
+ chrome.accessibilityFeatures.spokenFeedback) {
+ chrome.accessibilityFeatures.spokenFeedback.get({}, (function(details) {
+ this.isChromeVoxOn = details['value'];
+ }).bind(this));
+ chrome.accessibilityFeatures.spokenFeedback.onChange.addListener((function(
+ details) {
+ this.isChromeVoxOn = details['value'];
+ }).bind(this));
+ }
+
+ this.initBackground_(languageCode);
+
+ var StateType = i18n.input.chrome.inputview.ReadyState.StateType;
+ if (window.inputview) {
+ if (inputview.getKeyboardConfig) {
+ inputview.getKeyboardConfig((function(config) {
+ this.isA11yMode = !!config['a11ymode'];
+ this.isExperimental = !!config['experimental'];
+ this.readyState_.markStateReady(StateType.KEYBOARD_CONFIG_READY);
+ if (this.readyState_.isReady(StateType.IME_LIST_READY)) {
+ this.dispatchEvent(new goog.events.Event(
+ i18n.input.chrome.inputview.events.EventType.SETTINGS_READY));
+ }
+ }).bind(this));
+ } else {
+ this.readyState_.markStateReady(StateType.KEYBOARD_CONFIG_READY);
+ }
+ if (inputview.getInputMethods) {
+ inputview.getInputMethods((function(inputMethods) {
+ // Only show globe key to switching between IMEs when there are more
+ // than one IME.
+ this.showGlobeKey = inputMethods.length > 1;
+ this.readyState_.markStateReady(StateType.IME_LIST_READY);
+ if (this.readyState_.isReady(StateType.KEYBOARD_CONFIG_READY)) {
+ this.dispatchEvent(new goog.events.Event(
+ i18n.input.chrome.inputview.events.EventType.SETTINGS_READY));
+ }
+ }).bind(this));
+ } else {
+ this.readyState_.markStateReady(StateType.IME_LIST_READY);
+ }
+ } else {
+ this.readyState_.markStateReady(StateType.IME_LIST_READY);
+ this.readyState_.markStateReady(StateType.KEYBOARD_CONFIG_READY);
+ }
+
+ if (this.readyState_.isReady(StateType.KEYBOARD_CONFIG_READY) &&
+ this.readyState_.isReady(StateType.IME_LIST_READY)) {
+ window.setTimeout((function() {
+ this.dispatchEvent(new goog.events.Event(
+ i18n.input.chrome.inputview.events.EventType.SETTINGS_READY));
+ }).bind(this), 0);
+ }
+};
+
+
+/**
+ * Gets the currently activated input method.
+ *
+ * @param {function(string)} callback .
+ */
+Adapter.prototype.getCurrentInputMethod = function(callback) {
+ if (window.inputview && inputview.getCurrentInputMethod) {
+ inputview.getCurrentInputMethod(callback);
+ } else {
+ callback('DU');
+ }
+};
+
+
+/**
+ * Gets the list of all activated input methods.
+ *
+ * @param {function(Array.<Object>)} callback .
+ */
+Adapter.prototype.getInputMethods = function(callback) {
+ if (window.inputview && inputview.getInputMethods) {
+ inputview.getInputMethods(callback);
+ } else {
+ // Provides a dummy IME item to enable IME switcher UI.
+ callback([
+ {'indicator': 'DU', 'id': 'DU', 'name': 'Dummy IME', 'command': 1}]);
+ }
+};
+
+
+/**
+ * Switches to the input method with id equals |inputMethodId|.
+ *
+ * @param {!string} inputMethodId .
+ */
+Adapter.prototype.switchToInputMethod = function(inputMethodId) {
+ if (window.inputview && inputview.switchToInputMethod) {
+ inputview.switchToInputMethod(inputMethodId);
+ }
+};
+
+
+/**
+ * Callback for visibility change on the input view window.
+ *
+ * @private
+ */
+Adapter.prototype.onVisibilityChange_ = function() {
+ this.isVisible = !document.webkitHidden;
+ this.dispatchEvent(new goog.events.Event(i18n.input.chrome.inputview.
+ events.EventType.VISIBILITY_CHANGE));
+ chrome.runtime.sendMessage(goog.object.create(Name.MSG_TYPE,
+ Type.VISIBILITY_CHANGE, Name.VISIBILITY, !document.webkitHidden));
+};
+
+
+/**
+ * Sends request for completion.
+ *
+ * @param {string} query .
+ * @param {!Object=} opt_spatialData .
+ */
+Adapter.prototype.sendCompletionRequest = function(query, opt_spatialData) {
+ var spatialData = {};
+ if (opt_spatialData) {
+ spatialData[Name.SOURCES] = opt_spatialData.sources;
+ spatialData[Name.POSSIBILITIES] = opt_spatialData.possibilities;
+ }
+ chrome.runtime.sendMessage(goog.object.create(Name.MSG_TYPE,
+ Type.COMPLETION, Name.TEXT, query, Name.SPATIAL_DATA, spatialData));
+};
+
+
+/**
+ * Selects the candidate.
+ *
+ * @param {!Object} candidate .
+ */
+Adapter.prototype.selectCandidate = function(candidate) {
+ chrome.runtime.sendMessage(goog.object.create(
+ Name.MSG_TYPE, Type.SELECT_CANDIDATE, Name.CANDIDATE, candidate));
+};
+
+
+/**
+ * Commits the text.
+ *
+ * @param {string} text .
+ */
+Adapter.prototype.commitText = function(text) {
+ chrome.runtime.sendMessage(goog.object.create(
+ Name.MSG_TYPE, Type.COMMIT_TEXT, Name.TEXT, text));
+};
+
+
+/**
+ * Sets the language.
+ *
+ * @param {string} language .
+ */
+Adapter.prototype.setLanguage = function(language) {
+ chrome.runtime.sendMessage(goog.object.create(
+ Name.MSG_TYPE, Type.SET_LANGUAGE, Name.LANGUAGE, language));
+};
+
+
+/**
+ * Callbck when completion is back.
+ *
+ * @param {!Object} message .
+ * @private
+ */
+Adapter.prototype.onCandidatesBack_ = function(message) {
+ var source = message['source'] || '';
+ var candidates = message['candidates'] || [];
+ this.dispatchEvent(new CandidatesBackEvent(source, candidates));
+};
+
+
+/**
+ * Hides the keyboard.
+ */
+Adapter.prototype.hideKeyboard = function() {
+ chrome.input.ime.hideInputView();
+};
+
+
+/**
+ * Sends Input Tool code to background.
+ *
+ * @param {string} inputToolCode .
+ */
+Adapter.prototype.setInputToolCode = function(inputToolCode) {
+ chrome.runtime.sendMessage(
+ goog.object.create(
+ Name.MSG_TYPE,
+ Type.HWT_SET_INPUTTOOL,
+ Name.MSG,
+ inputToolCode));
+};
+
+
+/**
+ * Sends DOUBLE_CLICK_ON_SPACE_KEY message.
+ */
+Adapter.prototype.doubleClickOnSpaceKey = function() {
+ chrome.runtime.sendMessage(
+ goog.object.create(
+ Name.MSG_TYPE,
+ Type.DOUBLE_CLICK_ON_SPACE_KEY));
+};
+
+
+/**
+ * Sends message to the background when switch to emoji.
+ *
+ */
+Adapter.prototype.setEmojiInputToolCode = function() {
+ chrome.runtime.sendMessage(
+ goog.object.create(
+ Name.MSG_TYPE,
+ Type.EMOJI_SET_INPUTTOOL));
+};
+
+
+/**
+ * Sends message to the background when do internal inputtool switch.
+ *
+ * @param {boolean} inputToolValue The value of the language flag.
+ */
+Adapter.prototype.toggleLanguageState = function(inputToolValue) {
+ chrome.runtime.sendMessage(
+ goog.object.create(
+ Name.MSG_TYPE,
+ Type.TOGGLE_LANGUAGE_STATE,
+ Name.MSG,
+ inputToolValue));
+};
+
+
+/**
+ * Sends unset Input Tool code to background.
+ */
+Adapter.prototype.unsetInputToolCode = function() {
+ chrome.runtime.sendMessage(
+ goog.object.create(
+ Name.MSG_TYPE,
+ Type.HWT_UNSET_INPUTTOOL));
+};
+
+
+/**
+ * Sends message to the background when switch to other mode from emoji.
+ *
+ */
+Adapter.prototype.unsetEmojiInputToolCode = function() {
+ chrome.runtime.sendMessage(
+ goog.object.create(
+ Name.MSG_TYPE,
+ Type.EMOJI_UNSET_INPUTTOOL));
+};
+
+
+/**
+ * Processes incoming message from option page or inputview window.
+ *
+ * @param {*} request Message from option page or inputview window.
+ * @param {*} sender Information about the script
+ * context that sent the message.
+ * @param {function(*): void} sendResponse Function to call to send a response.
+ * @return {boolean|undefined} {@code true} to keep the message channel open in
+ * order to send a response asynchronously.
+ * @private
+ */
+Adapter.prototype.onMessage_ = function(request, sender, sendResponse) {
+ var type = request[Name.MSG_TYPE];
+ var msg = request[Name.MSG];
+ switch (type) {
+ case Type.CANDIDATES_BACK:
+ this.onCandidatesBack_(msg);
+ break;
+ case Type.CONTEXT_FOCUS:
+ this.onContextFocus_(request[Name.CONTEXT_TYPE]);
+ break;
+ case Type.CONTEXT_BLUR:
+ this.onContextBlur_();
+ break;
+ case Type.SURROUNDING_TEXT_CHANGED:
+ this.onSurroundingTextChanged_(request[Name.TEXT]);
+ break;
+ case Type.UPDATE_SETTINGS:
+ this.onUpdateSettings_(msg);
+ break;
+ case Type.HWT_NETWORK_ERROR:
+ case Type.HWT_PRIVACY_INFO:
+ this.dispatchEvent(new i18n.input.chrome.message.Event(type, msg));
+ break;
+ }
+};
+
+
+/**
+ * Sends the privacy confirmed message to background and broadcasts it.
+ */
+Adapter.prototype.sendHwtPrivacyConfirmMessage = function() {
+ chrome.runtime.sendMessage(
+ goog.object.create(Name.MSG_TYPE, Type.HWT_PRIVACY_GOT_IT));
+ this.dispatchEvent(
+ new goog.events.Event(Type.HWT_PRIVACY_GOT_IT));
+};
+
+
+/** @override */
+Adapter.prototype.disposeInternal = function() {
+ goog.dispose(this.handler_);
+
+ goog.base(this, 'disposeInternal');
+};
+}); // goog.scope
+
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/candidatesinfo.js b/third_party/google_input_tools/src/chrome/os/inputview/candidatesinfo.js
new file mode 100644
index 0000000..13a071b
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/candidatesinfo.js
@@ -0,0 +1,42 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.CandidatesInfo');
+
+
+
+/**
+ * The candidates information.
+ *
+ * @param {string} source .
+ * @param {!Array.<!Object>} candidates .
+ * @constructor
+ */
+i18n.input.chrome.inputview.CandidatesInfo = function(source, candidates) {
+ /** @type {string} */
+ this.source = source;
+
+ /** @type {!Array.<!Object>} */
+ this.candidates = candidates;
+};
+
+
+/**
+ * Gets an empty suggestions instance.
+ *
+ * @return {!i18n.input.chrome.inputview.CandidatesInfo} .
+ */
+i18n.input.chrome.inputview.CandidatesInfo.getEmpty = function() {
+ return new i18n.input.chrome.inputview.CandidatesInfo('', []);
+};
+
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/canvas.js b/third_party/google_input_tools/src/chrome/os/inputview/canvas.js
new file mode 100644
index 0000000..b417047
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/canvas.js
@@ -0,0 +1,425 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+
+/**
+ * @fileoverview Handwriting input canvas.
+ * @author har@google.com (Henry A. Rowley)
+ */
+
+goog.provide('i18n.input.hwt.Canvas');
+
+goog.require('goog.a11y.aria.Announcer');
+goog.require('goog.a11y.aria.LivePriority');
+goog.require('goog.dom.TagName');
+goog.require('goog.dom.classlist');
+goog.require('goog.events.Event');
+goog.require('goog.events.EventHandler');
+goog.require('goog.events.EventType');
+goog.require('goog.log');
+goog.require('goog.ui.Container');
+goog.require('i18n.input.hwt.EventType');
+goog.require('i18n.input.hwt.StrokeHandler');
+goog.require('i18n.input.hwt.css');
+
+
+
+/**
+ * The handwriting canvas UI element.
+ *
+ * @constructor
+ * @param {!Document=} opt_topDocument The top document for MOUSEUP event.
+ * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper.
+ * @param {goog.events.EventTarget=} opt_eventTarget The event target.
+ * @param {number=} opt_inkWidth The ink width.
+ * @param {string=} opt_inkColor The ink color.
+ * @extends {goog.ui.Container}
+ */
+i18n.input.hwt.Canvas = function(opt_topDocument, opt_domHelper,
+ opt_eventTarget, opt_inkWidth, opt_inkColor) {
+ goog.base(this, undefined, undefined, opt_domHelper);
+ this.setParentEventTarget(opt_eventTarget || null);
+
+ /**
+ * The stroke handler.
+ *
+ * @type {!i18n.input.hwt.StrokeHandler}
+ * @private
+ */
+ this.strokeHandler_;
+
+ /**
+ * The top document.
+ *
+ * @type {!Document}
+ * @private
+ */
+ this.topDocument_ = opt_topDocument || document;
+
+ /**
+ * Canvas which is the area showing the ink. Note that since we're
+ * using a canvas here probably this code won't work in IE.
+ *
+ * @type {!Element}
+ * @private
+ */
+ this.writingCanvas_;
+
+ /**
+ * Context for drawing into the writing canvas.
+ *
+ * @type {!CanvasRenderingContext2D}
+ * @private
+ */
+ this.writingContext_;
+
+ /**
+ * An array of strokes for passing to the recognizer.
+ *
+ * @type {!Array}
+ */
+ this.strokeList = [];
+
+ /**
+ * The current stroke.
+ *
+ * @type {!Array}
+ * @private
+ */
+ this.stroke_ = [];
+
+ /**
+ * Starting time of the stroke in milliseconds.
+ *
+ * @type {number}
+ * @private
+ */
+ this.startTime_ = 0;
+
+ /**
+ * Event handler.
+ *
+ * @type {!goog.events.EventHandler}
+ * @private
+ */
+ this.handler_ = new goog.events.EventHandler(this);
+
+ /**
+ * The annoucer for screen reader.
+ *
+ * @type {!goog.a11y.aria.Announcer}
+ * @private
+ */
+ this.announcer_ = new goog.a11y.aria.Announcer(this.getDomHelper());
+
+ /**
+ * Width of the ink line.
+ *
+ * @type {number}
+ * @private
+ */
+ this.inkWidth_ = opt_inkWidth || 6;
+
+ /**
+ * Color of the ink before it has been recognized.
+ *
+ * @type {string}
+ * @private
+ */
+ this.inkColor_ = opt_inkColor || '#4D90FE';
+};
+goog.inherits(i18n.input.hwt.Canvas, goog.ui.Container);
+
+
+/**
+ * The class logger.
+ *
+ * @type {goog.log.Logger}
+ * @private
+ */
+i18n.input.hwt.Canvas.prototype.logger_ =
+ goog.log.getLogger('i18n.input.hwt.Canvas');
+
+
+/**
+ * @desc Label for handwriting panel used to draw strokes.
+ */
+i18n.input.hwt.Canvas.MSG_INPUTTOOLS_HWT_PANEL = goog.getMsg('panel');
+
+
+/**
+ * @desc The hint on the canvas to indicate users they can draw here.
+ */
+i18n.input.hwt.Canvas.MSG_HANDWRITING_HINT = goog.getMsg('Draw a symbol here');
+
+
+/** @override */
+i18n.input.hwt.Canvas.prototype.createDom = function() {
+ goog.base(this, 'createDom');
+
+ var dom = this.getDomHelper();
+ this.writingCanvas_ = dom.createDom(goog.dom.TagName.CANVAS,
+ i18n.input.hwt.css.CANVAS);
+ this.writingCanvas_.width = 425;
+ this.writingCanvas_.height = 194;
+ dom.appendChild(this.getElement(), this.writingCanvas_);
+ this.writingContext_ = this.writingCanvas_.getContext('2d');
+};
+
+
+/** @override */
+i18n.input.hwt.Canvas.prototype.enterDocument = function() {
+ goog.base(this, 'enterDocument');
+
+ this.setFocusable(false);
+ this.setFocusableChildrenAllowed(false);
+ // Sets up stroke handler.
+ this.strokeHandler_ = new i18n.input.hwt.StrokeHandler(this.writingCanvas_,
+ this.topDocument_);
+ this.handler_.
+ listen(this.strokeHandler_,
+ i18n.input.hwt.StrokeHandler.EventType.STROKE_START,
+ this.onStrokeStart_).
+ listen(this.strokeHandler_,
+ i18n.input.hwt.StrokeHandler.EventType.STROKE,
+ this.onStroke_).
+ listen(this.strokeHandler_,
+ i18n.input.hwt.StrokeHandler.EventType.STROKE_END,
+ this.onStrokeEnd_).
+ listen(this.writingCanvas_, goog.events.EventType.MOUSEOVER,
+ this.onMouseOver_).
+ listen(this.writingCanvas_, goog.events.EventType.MOUSEDOWN,
+ goog.events.Event.stopPropagation);
+};
+
+
+/**
+ * Shows the hint message for the canvas.
+ */
+i18n.input.hwt.Canvas.prototype.showHint = function() {
+ this.writingContext_.font = '20px aria,sans-serif';
+ this.writingContext_.fontWeight = 'bold';
+ this.writingContext_.fillStyle = '#CCC';
+ this.writingContext_.fillText(i18n.input.hwt.Canvas.MSG_HANDWRITING_HINT,
+ 30, 120);
+};
+
+
+/**
+ * Draw a point with the given color.
+ *
+ * @param {string} color Color to draw the point.
+ * @param {!i18n.input.hwt.StrokeHandler.Point} point The point.
+ * @private
+ */
+i18n.input.hwt.Canvas.prototype.drawPoint_ = function(color, point) {
+ this.writingContext_.beginPath();
+ this.writingContext_.strokeStyle = color;
+ this.writingContext_.fillStyle = color;
+ this.writingContext_.arc(point.x, point.y,
+ this.inkWidth_ / 2, 0, Math.PI * 2, true);
+ this.writingContext_.fill();
+};
+
+
+/**
+ * Draw a poly-line given the list of array of points.
+ *
+ * @param {string} color Color to draw the line.
+ * @param {!Array.<!i18n.input.hwt.StrokeHandler.Point>} points The array of
+ * points.
+ * @param {number=} opt_start The start point to draw.
+ * @private
+ */
+i18n.input.hwt.Canvas.prototype.drawLine_ = function(color, points, opt_start) {
+ var start = opt_start || 0;
+ if (start == (points.length - 1)) {
+ // If there's only one point.
+ this.drawPoint_(color, points[0]);
+ } else {
+ this.writingContext_.beginPath();
+ this.writingContext_.strokeStyle = color;
+ this.writingContext_.fillStyle = 'none';
+ this.writingContext_.lineWidth = this.inkWidth_;
+ this.writingContext_.lineCap = 'round';
+ this.writingContext_.lineJoin = 'round';
+ this.writingContext_.moveTo(points[start].x, points[start].y);
+ for (var i = start + 1; i < points.length; i++) {
+ this.writingContext_.lineTo(points[i].x, points[i].y);
+ }
+ this.writingContext_.stroke();
+ }
+};
+
+
+/**
+ * Add a point to the current stroke.
+ *
+ * @param {!i18n.input.hwt.StrokeHandler.Point} point The point.
+ * @private
+ */
+i18n.input.hwt.Canvas.prototype.addPoint_ = function(point) {
+ point.time -= this.startTime_;
+ this.stroke_.push(point);
+ var len = this.stroke_.length;
+ var start = Math.max(len - 2, 0);
+ this.drawLine_(this.inkColor_, this.stroke_, start);
+};
+
+
+/**
+ * Callback for the start of a stroke.
+ *
+ * @param {!i18n.input.hwt.StrokeHandler.StrokeEvent} e The stroke event.
+ * @private
+ */
+i18n.input.hwt.Canvas.prototype.onStrokeStart_ = function(e) {
+ this.stroke_ = [];
+ var point = e.point;
+ if (this.strokeList.length == 0) {
+ this.startTime_ = point.time;
+ }
+ this.addPoint_(e.point);
+ e.preventDefault();
+
+ goog.dom.classlist.add(this.getElement(), i18n.input.hwt.css.IME_INIT_OPAQUE);
+};
+
+
+/**
+ * Callback for the stroke event.
+ *
+ * @param {!i18n.input.hwt.StrokeHandler.StrokeEvent} e The stroke event.
+ * @private
+ */
+i18n.input.hwt.Canvas.prototype.onStroke_ = function(e) {
+ this.addPoint_(e.point);
+ e.preventDefault();
+};
+
+
+/**
+ * Callback for the end of a stroke.
+ *
+ * @param {i18n.input.hwt.StrokeHandler.StrokeEvent} e The stroke event.
+ * @private
+ */
+i18n.input.hwt.Canvas.prototype.onStrokeEnd_ = function(e) {
+ this.strokeList.push(this.stroke_);
+ e.preventDefault();
+ this.dispatchEvent(new goog.events.Event(
+ i18n.input.hwt.EventType.RECOGNITION_READY));
+};
+
+
+/**
+ * Clears the writing canvas.
+ */
+i18n.input.hwt.Canvas.prototype.reset = function() {
+ goog.log.info(this.logger_, 'clear ' + this.writingCanvas_.width + 'x' +
+ this.writingCanvas_.height);
+ this.writingContext_.clearRect(
+ 0, 0, this.writingCanvas_.width, this.writingCanvas_.height);
+ this.strokeList = [];
+ this.stroke_ = [];
+ this.strokeHandler_.reset();
+};
+
+
+/** @override */
+i18n.input.hwt.Canvas.prototype.disposeInternal = function() {
+ goog.dispose(this.handler_);
+ goog.base(this, 'disposeInternal');
+};
+
+
+/**
+ * Gets the width of the canvas.
+ *
+ * @return {number} The width of the canvas.
+ */
+i18n.input.hwt.Canvas.prototype.getWidth = function() {
+ return this.writingCanvas_.width;
+};
+
+
+/**
+ * Gets the height of the canvas.
+ *
+ * @return {number} The height of the canvas.
+ */
+i18n.input.hwt.Canvas.prototype.getHeight = function() {
+ return this.writingCanvas_.height;
+};
+
+
+/**
+ * True if user is drawing.
+ *
+ * @return {boolean} True if is drawing.
+ */
+i18n.input.hwt.Canvas.prototype.isDrawing = function() {
+ return this.strokeHandler_.drawing;
+};
+
+
+/**
+ * True if the canvas is clean without strokes.
+ *
+ * @return {boolean} True if the canvas is clean.
+ */
+i18n.input.hwt.Canvas.prototype.isClean = function() {
+ return !this.strokeHandler_.drawing && this.strokeList.length == 0;
+};
+
+
+/**
+ * Sets the size of the canvas.
+ *
+ * @param {number=} opt_height The height.
+ * @param {number=} opt_width The width.
+ */
+i18n.input.hwt.Canvas.prototype.setSize = function(opt_height, opt_width) {
+ if (opt_height && this.writingCanvas_.height != opt_height ||
+ opt_width && this.writingCanvas_.width != opt_width) {
+ this.reset();
+ }
+ if (opt_height) {
+ this.writingCanvas_.height = opt_height;
+ }
+ if (opt_width) {
+ this.writingCanvas_.width = opt_width;
+ }
+};
+
+
+/**
+ * Gets the stroke handler.
+ *
+ * @return {!i18n.input.hwt.StrokeHandler} The stroke handler.
+ */
+i18n.input.hwt.Canvas.prototype.getStrokeHandler = function() {
+ return this.strokeHandler_;
+};
+
+
+/**
+ * Exports message to screen reader.
+ *
+ * @param {!goog.events.BrowserEvent} e .
+ * @private
+ */
+i18n.input.hwt.Canvas.prototype.onMouseOver_ = function(e) {
+ this.announcer_.say(i18n.input.hwt.Canvas.MSG_INPUTTOOLS_HWT_PANEL,
+ goog.a11y.aria.LivePriority.ASSERTIVE);
+};
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/conditionname.js b/third_party/google_input_tools/src/chrome/os/inputview/conditionname.js
new file mode 100644
index 0000000..46eb31f
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/conditionname.js
@@ -0,0 +1,30 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.ConditionName');
+
+
+/**
+ * The conditions used in layout files.
+ *
+ * @enum {string}
+ */
+i18n.input.chrome.inputview.ConditionName = {
+ SHOW_COMPACT_LAYOUT_SWITCHER: 'showCompactLayoutSwitcher',
+ SHOW_ALTGR: 'showAltGr',
+ SHOW_HANDWRITING_SWITCHER: 'showHandwritingSwitcher',
+ SHOW_MENU: 'showMenu',
+ SHOW_GLOBE_OR_SYMBOL: 'showGlobeOrSymbol',
+ SHOW_EN_SWITCHER_KEY: 'showEnSwitcherKey'
+};
+
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/config/compact_letter_characters.js b/third_party/google_input_tools/src/chrome/os/inputview/config/compact_letter_characters.js
new file mode 100644
index 0000000..5a53cf2
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/config/compact_letter_characters.js
@@ -0,0 +1,512 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.content.compact.letter');
+
+goog.require('i18n.input.chrome.inputview.MoreKeysShiftOperation');
+goog.require('i18n.input.chrome.inputview.content.constants');
+
+goog.scope(function() {
+var NonLetterKeys = i18n.input.chrome.inputview.content.constants.NonLetterKeys;
+var MoreKeysShiftOperation = i18n.input.chrome.inputview.MoreKeysShiftOperation;
+
+
+/**
+ * Common qwerty Letter keyset characters.
+ *
+ * @return {!Array.<!Object>}
+ */
+i18n.input.chrome.inputview.content.compact.letter.keyQwertyCharacters =
+ function() {
+ return [
+ /* 0 */ { 'text': 'q', 'hintText': '1' },
+ /* 1 */ { 'text': 'w', 'hintText': '2' },
+ /* 2 */ { 'text': 'e', 'hintText': '3',
+ 'moreKeys': ['\u00E8', '\u00E9', '\u00EA', '\u00EB', '\u0113']},
+ /* 3 */ { 'text': 'r', 'hintText': '4' },
+ /* 4 */ { 'text': 't', 'hintText': '5' },
+ /* 5 */ { 'text': 'y', 'hintText': '6' },
+ /* 6 */ { 'text': 'u', 'hintText': '7',
+ 'moreKeys': ['\u00FB', '\u00FC', '\u00F9', '\u00FA', '\u016B']},
+ /* 7 */ { 'text': 'i', 'hintText': '8',
+ 'moreKeys': ['\u00EE', '\u00EF', '\u00ED', '\u012B', '\u00EC']},
+ /* 8 */ { 'text': 'o', 'hintText': '9',
+ 'moreKeys': ['\u00F4', '\u00F6', '\u00F2', '\u00F3', '\u0153', '\u00F8',
+ '\u014D', '\u00F5']},
+ /* 9 */ { 'text': 'p', 'hintText': '0' },
+ /* 10 */ NonLetterKeys.BACKSPACE,
+ /* 11 */ { 'text': 'a', 'marginLeftPercent': 0.33,
+ 'moreKeys': ['\u00E0', '\u00E1', '\u00E2', '\u00E4', '\u00E6', '\u00E3',
+ '\u00E5', '\u0101']},
+ /* 12 */ { 'text': 's',
+ 'moreKeys': ['\u00DF']},
+ /* 13 */ { 'text': 'd' },
+ /* 14 */ { 'text': 'f' },
+ /* 15 */ { 'text': 'g' },
+ /* 16 */ { 'text': 'h' },
+ /* 17 */ { 'text': 'j' },
+ /* 18 */ { 'text': 'k' },
+ /* 19 */ { 'text': 'l' },
+ /* 20 */ NonLetterKeys.ENTER,
+ /* 21 */ NonLetterKeys.LEFT_SHIFT,
+ /* 22 */ { 'text': 'z' },
+ /* 23 */ { 'text': 'x' },
+ /* 24 */ { 'text': 'c',
+ 'moreKeys': ['\u00E7']},
+ /* 25 */ { 'text': 'v' },
+ /* 26 */ { 'text': 'b' },
+ /* 27 */ { 'text': 'n',
+ 'moreKeys': ['\u00F1']},
+ /* 28 */ { 'text': 'm' },
+ /* 29 */ { 'text': '!',
+ 'moreKeys': ['\u00A1']},
+ /* 30 */ { 'text': '?',
+ 'moreKeys': ['\u00BF']},
+ /* 31 */ NonLetterKeys.RIGHT_SHIFT,
+ /* 32 */ NonLetterKeys.SWITCHER,
+ /* 33 */ NonLetterKeys.GLOBE,
+ /* 34 */ NonLetterKeys.MENU,
+ /* 35 */ { 'text': '/', 'isGrey': true },
+ /* 36 */ NonLetterKeys.SPACE,
+ /* 37 */ { 'text': ',', 'isGrey': true },
+ /* 38 */ { 'text': '.', 'isGrey': true,
+ 'moreKeys': ['\u0023', '\u0021', '\u005C', '\u003F', '\u002D', '\u003A',
+ '\u0027', '\u0040']},
+ /* 39 */ NonLetterKeys.HIDE
+ ];
+};
+
+
+/**
+ * Belgian Letter keyset characters.
+ *
+ * @return {!Array.<!Object>}
+ */
+i18n.input.chrome.inputview.content.compact.letter.keyBelgianCharacters =
+ function() {
+ var data =
+ i18n.input.chrome.inputview.content.compact.letter.keyQwertyCharacters();
+ data[2]['moreKeys'] =
+ ['\u0117', '\u00E8', '\u0119', '\u00E9', '\u00EA', '\u00EB', '\u0113'];
+ data[5]['moreKeys'] = ['\u0133'];
+ data[7]['moreKeys'] =
+ ['\u00EE', '\u00EF', '\u012F', '\u0133', '\u00ED', '\u012B', '\u00EC'];
+ data[12]['moreKeys'] = undefined;
+ data[24]['moreKeys'] = undefined;
+ data[27]['moreKeys'] = ['\u00F1', '\u0144'];
+ return data;
+};
+
+
+/**
+ * Icelandic Letter keyset characters.
+ *
+ * @return {!Array.<!Object>}
+ */
+i18n.input.chrome.inputview.content.compact.letter.keyIcelandicCharacters =
+ function() {
+ var data =
+ i18n.input.chrome.inputview.content.compact.letter.keyQwertyCharacters();
+ data[2]['moreKeys'] = ['\u00E9', '\u00EB', '\u00E8', '\u00EA', '\u0119',
+ '\u0117', '\u0113']; // e
+ data[4]['moreKeys'] = ['\u00FE']; // t
+ data[5]['moreKeys'] = ['\u00FD', '\u00FF']; // y
+ data[7]['moreKeys'] = ['\u00ED', '\u00EF', '\u00EE', '\u00EC', '\u012F',
+ '\u012B']; // i
+ data[12]['moreKeys'] = undefined;
+ data[13]['moreKeys'] = ['\u00F0'];
+ data[24]['moreKeys'] = undefined;
+ return data;
+};
+
+
+/**
+ * Qwertz Germany Letter keyset characters.
+ *
+ * @return {!Array.<!Object>}
+ */
+i18n.input.chrome.inputview.content.compact.letter.keyQwertzCharacters =
+ function() {
+ var data =
+ i18n.input.chrome.inputview.content.compact.letter.keyQwertyCharacters();
+ data[2]['moreKeys'] = ['\u00E9', '\u00E8', '\u00EA', '\u00EB', '\u0117'];
+ data[7]['moreKeys'] = undefined;
+ data[12]['moreKeys'] = ['\u00DF', '\u015B', '\u0161'];
+ data[24]['moreKeys'] = undefined;
+ data[27]['moreKeys'] = ['\u00F1', '\u0144'];
+
+ data[5].text = 'z';
+ data[22].text = 'y';
+ return data;
+};
+
+
+/**
+ * Common azerty Letter keyset characters.
+ *
+ * @return {!Array.<!Object>}
+ */
+i18n.input.chrome.inputview.content.compact.letter.keyAzertyCharacters =
+ function() {
+ return [
+ /* 0 */ { 'text': 'a', 'hintText': '1',
+ 'moreKeys': ['\u00E0', '\u00E2', '\u00E6', '\u00E1', '\u00E4',
+ '\u00E3', '\u00E5', '\u0101', '\u00AA']},
+ /* 1 */ { 'text': 'z', 'hintText': '2' },
+ /* 2 */ { 'text': 'e', 'hintText': '3',
+ 'moreKeys': ['\u00E9', '\u00E8', '\u00EA', '\u00EB', '\u0119',
+ '\u0117', '\u0113']},
+ /* 3 */ { 'text': 'r', 'hintText': '4' },
+ /* 4 */ { 'text': 't', 'hintText': '5' },
+ /* 5 */ { 'text': 'y', 'hintText': '6',
+ 'moreKeys': ['\u00FF']},
+ /* 6 */ { 'text': 'u', 'hintText': '7',
+ 'moreKeys': ['\u00F9', '\u00FB', '\u00FC', '\u00FA', '\u016B']},
+ /* 7 */ { 'text': 'i', 'hintText': '8',
+ 'moreKeys': ['\u00EE', '\u00EF', '\u00EC', '\u00ED', '\u012F',
+ '\u012B']},
+ /* 8 */ { 'text': 'o', 'hintText': '9',
+ 'moreKeys': ['\u00F4', '\u0153', '\u00F6', '\u00F2', '\u00F3', '\u00F5',
+ '\u00F8', '\u014D', '\u00BA']},
+ /* 9 */ { 'text': 'p', 'hintText': '0' },
+ /* 10 */ NonLetterKeys.BACKSPACE,
+ /* 11 */ { 'text': 'q' },
+ /* 12 */ { 'text': 's' },
+ /* 13 */ { 'text': 'd' },
+ /* 14 */ { 'text': 'f' },
+ /* 15 */ { 'text': 'g' },
+ /* 16 */ { 'text': 'h' },
+ /* 17 */ { 'text': 'j' },
+ /* 18 */ { 'text': 'k' },
+ /* 19 */ { 'text': 'l' },
+ /* 20 */ { 'text': 'm' },
+ /* 21 */ NonLetterKeys.ENTER,
+ /* 22 */ NonLetterKeys.LEFT_SHIFT,
+ /* 23 */ { 'text': 'w' },
+ /* 24 */ { 'text': 'x' },
+ /* 25 */ { 'text': 'c',
+ 'moreKeys': ['\u00E7', '\u0107', '\u010D']},
+ /* 26 */ { 'text': 'v' },
+ /* 27 */ { 'text': 'b' },
+ /* 28 */ { 'text': 'n' },
+ /* 29 */ { 'text': '\'',
+ 'moreKeys': ['\u2018', '\u201A', '\u2019', '\u2039', '\u203A']},
+ /* 30 */ { 'text': '!',
+ 'moreKeys': ['\u00A1']},
+ /* 31 */ { 'text': '?',
+ 'moreKeys': ['\u00BF']},
+ /* 32 */ NonLetterKeys.RIGHT_SHIFT,
+ /* 33 */ NonLetterKeys.SWITCHER,
+ /* 34 */ NonLetterKeys.GLOBE,
+ /* 35 */ NonLetterKeys.MENU,
+ /* 36 */ { 'text': '/', 'isGrey': true },
+ /* 37 */ NonLetterKeys.SPACE,
+ /* 38 */ { 'text': ',', 'isGrey': true },
+ /* 39 */ { 'text': '.', 'isGrey': true,
+ 'moreKeys': ['\u0023', '\u0021', '\u005C', '\u003F', '\u002D', '\u003A',
+ '\u0027', '\u0040']},
+ /* 40 */ NonLetterKeys.HIDE
+ ];
+};
+
+
+/**
+ * Basic nordic Letter keyset characters(based on Finish).
+ *
+ * @return {!Array.<!Object>}
+ */
+i18n.input.chrome.inputview.content.compact.letter.keyNordicCharacters =
+ function() {
+ return [
+ /* 0 */ { 'text': 'q', 'hintText': '1' },
+ /* 1 */ { 'text': 'w', 'hintText': '2' },
+ /* 2 */ { 'text': 'e', 'hintText': '3' },
+ /* 3 */ { 'text': 'r', 'hintText': '4' },
+ /* 4 */ { 'text': 't', 'hintText': '5' },
+ /* 5 */ { 'text': 'y', 'hintText': '6' },
+ /* 6 */ { 'text': 'u', 'hintText': '7',
+ 'moreKeys': ['\u00FC']},
+ /* 7 */ { 'text': 'i', 'hintText': '8' },
+ /* 8 */ { 'text': 'o', 'hintText': '9',
+ 'moreKeys': ['\u00F8', '\u00F4', '\u00F2', '\u00F3', '\u00F5', '\u0153',
+ '\u014D']},
+ /* 9 */ { 'text': 'p', 'hintText': '0' },
+ /* 10 */ { 'text': '\u00e5' },
+ /* 11 */ NonLetterKeys.BACKSPACE,
+ /* 12 */ { 'text': 'a',
+ 'moreKeys': ['\u00E6', '\u00E0', '\u00E1', '\u00E2', '\u00E3',
+ '\u0101']},
+ /* 13 */ { 'text': 's',
+ 'moreKeys': ['\u0161', '\u00DF', '\u015B']},
+ /* 14 */ { 'text': 'd' },
+ /* 15 */ { 'text': 'f' },
+ /* 16 */ { 'text': 'g' },
+ /* 17 */ { 'text': 'h' },
+ /* 18 */ { 'text': 'j' },
+ /* 19 */ { 'text': 'k' },
+ /* 20 */ { 'text': 'l' },
+ /* 21 */ { 'text': '\u00f6',
+ 'moreKeys': ['\u00F8']},
+ /* 22 */ { 'text': '\u00e4',
+ 'moreKeys': ['\u00E6']},
+ /* 23 */ NonLetterKeys.ENTER,
+ /* 24 */ NonLetterKeys.LEFT_SHIFT,
+ /* 25 */ { 'text': 'z', 'marginLeftPercent': 0.33,
+ 'moreKeys': ['\u017E', '\u017A', '\u017C']},
+ /* 26 */ { 'text': 'x' },
+ /* 27 */ { 'text': 'c' },
+ /* 28 */ { 'text': 'v' },
+ /* 29 */ { 'text': 'b' },
+ /* 30 */ { 'text': 'n' },
+ /* 31 */ { 'text': 'm' },
+ /* 32 */ { 'text': '!',
+ 'moreKeys': ['\u00A1']},
+ /* 33 */ { 'text': '?', 'marginRightPercent': 0.33,
+ 'moreKeys': ['\u00BF']},
+ /* 34 */ NonLetterKeys.RIGHT_SHIFT,
+ /* 35 */ NonLetterKeys.SWITCHER,
+ /* 36 */ NonLetterKeys.GLOBE,
+ /* 37 */ NonLetterKeys.MENU,
+ /* 38 */ { 'text': '/', 'isGrey': true },
+ /* 39 */ NonLetterKeys.SPACE,
+ /* 40 */ { 'text': ',', 'isGrey': true },
+ /* 41 */ { 'text': '.', 'isGrey': true,
+ 'moreKeys': ['\u0023', '\u0021', '\u005C', '\u003F', '\u002D', '\u003A',
+ '\u0027', '\u0040']},
+ /* 42 */ NonLetterKeys.HIDE
+ ];
+};
+
+
+/**
+ * Sweden nordic Letter keyset characters.
+ *
+ * @return {!Array.<!Object>}
+ */
+i18n.input.chrome.inputview.content.compact.letter.keySwedenCharacters =
+ function() {
+ var data =
+ i18n.input.chrome.inputview.content.compact.letter.keyNordicCharacters();
+ data[2]['moreKeys'] = ['\u00E9', '\u00E8', '\u00EA', '\u00EB',
+ '\u0119']; // e
+ data[3]['moreKeys'] = ['\u0159']; // r
+ data[4]['moreKeys'] = ['\u0165', '\u00FE']; // t
+ data[5]['moreKeys'] = ['\u00FD', '\u00FF']; // y
+ data[6]['moreKeys'] = ['\u00FC', '\u00FA', '\u00F9', '\u00FB',
+ '\u016B']; // u
+ data[7]['moreKeys'] = ['\u00ED', '\u00EC', '\u00EE', '\u00EF']; // i
+ data[8]['moreKeys'] = ['\u00F3', '\u00F2', '\u00F4', '\u00F5',
+ '\u014D']; // o
+ data[12]['moreKeys'] = ['\u00E1', '\u00E0', '\u00E2', '\u0105',
+ '\u00E3']; // a
+ data[13]['moreKeys'] = ['\u015B', '\u0161', '\u015F', '\u00DF']; // s
+ data[14]['moreKeys'] = ['\u00F0', '\u010F']; // d
+ data[20]['moreKeys'] = ['\u0142']; // l
+ data[21]['moreKeys'] = ['\u00F8', '\u0153'];
+ data[22]['moreKeys'] = ['\u00E6'];
+ data[25]['moreKeys'] = ['\u017A', '\u017E', '\u017C']; //z
+ data[27]['moreKeys'] = ['\u00E7', '\u0107', '\u010D']; //c
+ data[30]['moreKeys'] = ['\u0144', '\u00F1', '\u0148']; //n
+
+ return data;
+};
+
+
+/**
+ * Norway nordic Letter keyset characters.
+ *
+ * @return {!Array.<!Object>}
+ */
+i18n.input.chrome.inputview.content.compact.letter.keyNorwayCharacters =
+ function() {
+ var data =
+ i18n.input.chrome.inputview.content.compact.letter.keyNordicCharacters();
+ data[2]['moreKeys'] = ['\u00E9', '\u00E8', '\u00EA', '\u00EB', '\u0119',
+ '\u0117', '\u0113']; // e
+ data[6]['moreKeys'] = ['\u00FC', '\u00FB', '\u00F9', '\u00FA',
+ '\u016B']; // u
+ data[8]['moreKeys'] = ['\u00F4', '\u00F2', '\u00F3', '\u00F6', '\u00F5',
+ '\u0153', '\u014D']; // o
+ data[12]['moreKeys'] = ['\u00E0', '\u00E4', '\u00E1', '\u00E2', '\u00E3',
+ '\u0101']; // a
+ data[13]['moreKeys'] = undefined; //s
+ data[21]['moreKeys'] = ['\u00F6'];
+ data[22]['moreKeys'] = ['\u00E4'];
+ data[25]['moreKeys'] = undefined; //z
+
+ data[21]['text'] = '\u00f8';
+ data[22]['text'] = '\u00e6';
+
+ return data;
+};
+
+
+/**
+ * Denmark nordic Letter keyset characters.
+ *
+ * @return {!Array.<!Object>}
+ */
+i18n.input.chrome.inputview.content.compact.letter.keyDenmarkCharacters =
+ function() {
+ var data =
+ i18n.input.chrome.inputview.content.compact.letter.keyNordicCharacters();
+ data[2]['moreKeys'] = ['\u00E9', '\u00EB']; // e
+ data[5]['moreKeys'] = ['\u00FD', '\u00FF']; // y
+ data[6]['moreKeys'] = ['\u00FA', '\u00FC', '\u00FB', '\u00F9',
+ '\u016B']; // u
+ data[7]['moreKeys'] = ['\u00ED', '\u00EF']; // i
+ data[8]['moreKeys'] = ['\u00F3', '\u00F4', '\u00F2', '\u00F5',
+ '\u0153', '\u014D']; // o
+ data[12]['moreKeys'] = ['\u00E1', '\u00E4', '\u00E0', '\u00E2', '\u00E3',
+ '\u0101']; // a
+ data[13]['moreKeys'] = ['\u00DF', '\u015B', '\u0161']; // s
+ data[14]['moreKeys'] = ['\u00F0']; // d
+ data[20]['moreKeys'] = ['\u0142']; // l
+ data[21]['moreKeys'] = ['\u00E4'];
+ data[22]['moreKeys'] = ['\u00F6'];
+ data[25]['moreKeys'] = undefined; //z
+ data[30]['moreKeys'] = ['\u00F1', '\u0144']; //n
+
+ data[21]['text'] = '\u00e6';
+ data[22]['text'] = '\u00f8';
+
+ return data;
+};
+
+
+/**
+ * Pinyin keyset characters.
+ *
+ * @return {!Array.<!Object>}
+ */
+i18n.input.chrome.inputview.content.compact.letter.keyPinyinCharacters =
+ function() {
+ var data = [
+ /* 0 */ { 'text': 'q', 'hintText': '1',
+ 'moreKeys': ['\u0051', '\u0071']},
+ /* 1 */ { 'text': 'w', 'hintText': '2',
+ 'moreKeys': ['\u0057', '\u0077']},
+ /* 2 */ { 'text': 'e', 'hintText': '3',
+ 'moreKeys': ['\u0045', '\u0065']},
+ /* 3 */ { 'text': 'r', 'hintText': '4',
+ 'moreKeys': ['\u0052', '\u0072']},
+ /* 4 */ { 'text': 't', 'hintText': '5',
+ 'moreKeys': ['\u0054', '\u0074']},
+ /* 5 */ { 'text': 'y', 'hintText': '6',
+ 'moreKeys': ['\u0059', '\u0079']},
+ /* 6 */ { 'text': 'u', 'hintText': '7',
+ 'moreKeys': ['\u0055', '\u0075']},
+ /* 7 */ { 'text': 'i', 'hintText': '8',
+ 'moreKeys': ['\u0049', '\u0069'] },
+ /* 8 */ { 'text': 'o', 'hintText': '9',
+ 'moreKeys': ['\u004F', '\u006F']},
+ /* 9 */ { 'text': 'p', 'hintText': '0',
+ 'moreKeys': ['\u0050', '\u0070']},
+ /* 10 */ NonLetterKeys.BACKSPACE,
+ /* 11 */ { 'text': 'a', 'hintText': '@', 'marginLeftPercent': 0.33,
+ 'moreKeys': ['\u0041', '\u0061']},
+ /* 12 */ { 'text': 's', 'hintText': '\u00D7',
+ 'moreKeys': ['\u0053', '\u0073']},
+ /* 13 */ { 'text': 'd', 'hintText': '+',
+ 'moreKeys': ['\u0044', '\u0064']},
+ /* 14 */ { 'text': 'f', 'hintText': '\uff0d',
+ 'moreKeys': ['\u0046', '\u0066']},
+ /* 15 */ { 'text': 'g', 'hintText': '=',
+ 'moreKeys': ['\u0047', '\u0067']},
+ /* 16 */ { 'text': 'h', 'hintText': '\uff0f',
+ 'moreKeys': ['\u0048', '\u0068']},
+ /* 17 */ { 'text': 'j', 'hintText': '#',
+ 'moreKeys': ['\u004a', '\u006a']},
+ /* 18 */ { 'text': 'k', 'hintText': '\uff08',
+ 'moreKeys': ['\u004b', '\u006b']},
+ /* 19 */ { 'text': 'l', 'hintText': '\uff09',
+ 'moreKeys': ['\u004c', '\u006c']},
+ /* 20 */ NonLetterKeys.ENTER,
+ /* 21 */ NonLetterKeys.LEFT_SHIFT,
+ /* 22 */ { 'text': 'z', 'hintText': '\u3001',
+ 'moreKeys': ['\u005a', '\u007a']},
+ /* 23 */ { 'text': 'x', 'hintText': '\uff1a',
+ 'moreKeys': ['\u0058', '\u0078']},
+ /* 24 */ { 'text': 'c', 'hintText': '\"',
+ 'moreKeys': ['\u0043', '\u0063']},
+ /* 25 */ { 'text': 'v', 'hintText': '\uff1f',
+ 'moreKeys': ['\u0056', '\u0076']},
+ /* 26 */ { 'text': 'b', 'hintText': '\uff01',
+ 'moreKeys': ['\u0042', '\u0062']},
+ /* 27 */ { 'text': 'n', 'hintText': '\uff5e',
+ 'moreKeys': ['\u004e', '\u006e']},
+ /* 28 */ { 'text': 'm', 'hintText': '.',
+ 'moreKeys': ['\u004d', '\u006d']},
+ /* 29 */ { 'text': '\uff01',
+ 'moreKeys': ['\u00A1']},
+ /* 30 */ { 'text': '\uff1f',
+ 'moreKeys': ['\u00BF']},
+ /* 31 */ NonLetterKeys.RIGHT_SHIFT,
+ /* 32 */ NonLetterKeys.SWITCHER,
+ /* 33 */ NonLetterKeys.GLOBE,
+ /* 34 */ NonLetterKeys.MENU,
+ /* 35 */ { 'text': '\uff0c', 'isGrey': true },
+ /* 36 */ NonLetterKeys.SPACE,
+ /* 37 */ { 'text': '\u3002', 'isGrey': true },
+ /* 38 */ NonLetterKeys.SWITCHER,
+ /* 39 */ NonLetterKeys.HIDE
+ ];
+ for (var i = 0; i <= 9; i++) {
+ data[i]['moreKeysShiftOperation'] = MoreKeysShiftOperation.TO_LOWER_CASE;
+ }
+ for (var i = 11; i <= 19; i++) {
+ data[i]['moreKeysShiftOperation'] = MoreKeysShiftOperation.TO_LOWER_CASE;
+ }
+ for (var i = 22; i <= 28; i++) {
+ data[i]['moreKeysShiftOperation'] = MoreKeysShiftOperation.TO_LOWER_CASE;
+ }
+ return data;
+};
+
+
+/**
+ * English mode of pinyin keyset characters.
+ *
+ * @return {!Array.<!Object>}
+ */
+i18n.input.chrome.inputview.content.compact.letter.keyEnCharacters =
+ function() {
+ var data =
+ i18n.input.chrome.inputview.content.compact.letter.keyPinyinCharacters();
+ for (var i = 0; i <= 9; i++) {
+ data[i]['moreKeys'].pop();
+ }
+ for (var i = 11; i <= 19; i++) {
+ data[i]['moreKeys'].pop();
+ }
+ for (var i = 22; i <= 28; i++) {
+ data[i]['moreKeys'].pop();
+ }
+ data[12]['hintText'] = '*';
+ data[14]['hintText'] = '\u002d';
+ data[16]['hintText'] = '/';
+ data[18]['hintText'] = '\u0028';
+ data[19]['hintText'] = '\u0029';
+ data[22]['hintText'] = '\u0027';
+ data[23]['hintText'] = '\u003a';
+ data[25]['hintText'] = '\u003f';
+ data[26]['hintText'] = '\u0021';
+ data[27]['hintText'] = '\u007e';
+ data[28]['hintText'] = '\u2026';
+ data[29]['text'] = '\u0021';
+ data[30]['text'] = '\u003f';
+ data[35]['text'] = '\u002c';
+ data[37]['text'] = '.';
+ return data;
+};
+}); // goog.scope
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/config/compact_more_characters.js b/third_party/google_input_tools/src/chrome/os/inputview/config/compact_more_characters.js
new file mode 100644
index 0000000..81cdb95
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/config/compact_more_characters.js
@@ -0,0 +1,160 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.content.compact.more');
+
+goog.require('i18n.input.chrome.inputview.content.constants');
+
+goog.scope(function() {
+var NonLetterKeys = i18n.input.chrome.inputview.content.constants.NonLetterKeys;
+
+
+/**
+ * North American More keyset characters.
+ *
+ * @return {!Array.<!Object>}
+ */
+i18n.input.chrome.inputview.content.compact.more.keyNAMoreCharacters =
+ function() {
+ return [
+ /* 0 */ { 'text': '~' },
+ /* 1 */ { 'text': '`' },
+ /* 2 */ { 'text': '|' },
+ // Keep in sync with rowkeys_symbols_shift1.xml in android input tool.
+ /* 3 */ { 'text': '\u2022',
+ 'moreKeys': ['\u266A', '\u2665', '\u2660', '\u2666', '\u2663']},
+ /* 4 */ { 'text': '\u23B7' },
+ // Keep in sync with rowkeys_symbols_shift1.xml in android input tool.
+ /* 5 */ { 'text': '\u03C0',
+ 'moreKeys': ['\u03A0']},
+ /* 6 */ { 'text': '\u00F7' },
+ /* 7 */ { 'text': '\u00D7' },
+ /* 8 */ { 'text': '\u00B6',
+ 'moreKeys': ['\u00A7']},
+ /* 9 */ { 'text': '\u0394' },
+ /* 10 */ NonLetterKeys.BACKSPACE,
+ /* 11 */ { 'text': '\u00A3', 'marginLeftPercent': 0.33 },
+ /* 12 */ { 'text': '\u00A2' },
+ /* 13 */ { 'text': '\u20AC' },
+ /* 14 */ { 'text': '\u00A5' },
+ // Keep in sync with rowkeys_symbols_shift2.xml in android input tool.
+ /* 15 */ { 'text': '^',
+ 'moreKeys': ['\u2191', '\u2193', '\u2190', '\u2192']},
+ /* 16 */ { 'text': '\u00B0',
+ 'moreKeys': ['\u2032', '\u2033']},
+ /* 17 */ { 'text': '=',
+ 'moreKeys': ['\u2260', '\u2248', '\u221E']},
+ /* 18 */ { 'text': '{' },
+ /* 19 */ { 'text': '}' },
+ /* 20 */ NonLetterKeys.ENTER,
+ /* 21 */ NonLetterKeys.SWITCHER,
+ /* 22 */ { 'text': '\\' },
+ /* 23 */ { 'text': '\u00A9' },
+ /* 24 */ { 'text': '\u00AE' },
+ /* 25 */ { 'text': '\u2122' },
+ /* 26 */ { 'text': '\u2105' },
+ /* 27 */ { 'text': '[' },
+ /* 28 */ { 'text': ']' },
+ /* 29 */ { 'text': '\u00A1' },
+ /* 30 */ { 'text': '\u00BF' },
+ /* 31 */ NonLetterKeys.SWITCHER,
+ /* 32 */ NonLetterKeys.SWITCHER,
+ /* 33 */ { 'text': '<', 'isGrey': true,
+ 'moreKeys': ['\u2039', '\u2264', '\u00AB']},
+ /* 34 */ NonLetterKeys.MENU,
+ /* 35 */ { 'text': '>', 'isGrey': true,
+ 'moreKeys': ['\u203A', '\u2265', '\u00BB']},
+ /* 36 */ NonLetterKeys.SPACE,
+ /* 37 */ { 'text': ',', 'isGrey': true },
+ /* 38 */ { 'text': '.', 'isGrey': true,
+ 'moreKeys': ['\u2026']},
+ /* 39 */ NonLetterKeys.HIDE
+ ];
+};
+
+
+/**
+ * Gets United Kingdom More keyset characters.
+ *
+ * @return {!Array.<!Object>}
+ */
+i18n.input.chrome.inputview.content.compact.more.keyUKMoreCharacters =
+ function() {
+ // Copy North America more characters.
+ var data = i18n.input.chrome.inputview.content.compact.more.
+ keyNAMoreCharacters();
+ data[11]['text'] = '\u20AC'; // pound -> euro
+ data[12]['text'] = '\u00A5'; // cent -> yen
+ data[13]['text'] = '$'; // euro -> dollar
+ data[13]['moreKeys'] = ['\u00A2'];
+ data[14]['text'] = '\u00A2'; // yen -> cent
+ return data;
+};
+
+
+/**
+ * Gets European More keyset characters.
+ *
+ * @return {!Array.<!Object>}
+ */
+i18n.input.chrome.inputview.content.compact.more.keyEUMoreCharacters =
+ function() {
+ // Copy UK more characters.
+ var data = i18n.input.chrome.inputview.content.compact.more.
+ keyUKMoreCharacters();
+ data[11]['text'] = '\u00A3'; // euro -> pound
+ return data;
+};
+
+
+/**
+ * Gets Pinyin More keyset characters.
+ *
+ * @return {!Array.<!Object>}
+ */
+i18n.input.chrome.inputview.content.compact.more.keyPinyinMoreCharacters =
+ function() {
+ var data = i18n.input.chrome.inputview.content.compact.more.
+ keyNAMoreCharacters();
+ data[0]['text'] = '\uff5e';
+ data[1]['text'] = '\u2194';
+ data[1]['moreKeys'] = ['\u2191', '\u2193', '\u2190', '\u2192'];
+ data[11]['text'] = '\u00a5';
+ data[11]['moreKeys'] = ['\u00a2', '\u00a3', '\u20ac'];
+ data[12]['text'] = '\u3010';
+ data[13]['text'] = '\u3011';
+ data[14]['text'] = '\u300e';
+ data[15]['text'] = '\u300f';
+ data[15]['moreKeys'] = undefined;
+ data[17]['text'] = '\u2026';
+ data[17]['moreKeys'] = undefined;
+ data[18]['text'] = '\uff5b';
+ data[19]['text'] = '\uff5d';
+ data[22]['text'] = '\u003d';
+ data[22]['moreKeys'] = ['\u2260', '\u2248', '\u221E'];
+ data[27]['text'] = '\uff3b';
+ data[28]['text'] = '\uff3d';
+ data[29]['text'] = '\u300a';
+ data[29]['moreKeys'] = ['\u2039', '\u2264', '\u00AB'];
+ data[30]['text'] = '\u300b';
+ data[30]['moreKeys'] = ['\u203A', '\u2265', '\u00BB'];
+ data[33]['text'] = '\uff01';
+ data[33]['moreKeys'] = undefined;
+ data[35]['text'] = '\uff1f';
+ data[35]['moreKeys'] = undefined;
+ data[37]['text'] = '\uff0c';
+ data[38]['text'] = '\u3002';
+ return data;
+};
+
+}); // goog.scope
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/config/compact_numberpad_characters.js b/third_party/google_input_tools/src/chrome/os/inputview/config/compact_numberpad_characters.js
new file mode 100644
index 0000000..61cb7fc
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/config/compact_numberpad_characters.js
@@ -0,0 +1,69 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.content.compact.numberpad');
+
+goog.require('i18n.input.chrome.inputview.content.constants');
+
+goog.scope(function() {
+var NonLetterKeys = i18n.input.chrome.inputview.content.constants.NonLetterKeys;
+
+/**
+ * Generic numberpad keyset characters.
+ *
+ * @return {!Array.<!Object>}
+ */
+i18n.input.chrome.inputview.content.compact.numberpad.keyNumberpadCharacters =
+ function() {
+ return [
+ /* 0 */ { 'text': '-',
+ 'isGrey': true},
+ /* 1 */ { 'text': '+',
+ 'isGrey': true},
+ /* 2 */ { 'text': '.',
+ 'isGrey': true},
+ /* 3 */ { 'text': '1'},
+ /* 4 */ { 'text': '2'},
+ /* 5 */ { 'text': '3'},
+ /* 6 */ NonLetterKeys.BACKSPACE,
+ /* 7 */ { 'text': '*',
+ 'isGrey': true},
+ /* 8 */ { 'text': '/',
+ 'isGrey': true},
+ /* 9 */ { 'text': ',',
+ 'isGrey': true},
+ /* 10 */ { 'text': '4'},
+ /* 11 */ { 'text': '5'},
+ /* 12 */ { 'text': '6'},
+ /* 13 */ NonLetterKeys.ENTER,
+ /* 14 */ { 'text': '(',
+ 'isGrey': true},
+ /* 15 */ { 'text': ')',
+ 'isGrey': true},
+ /* 16 */ { 'text': '=',
+ 'isGrey': true},
+ /* 17 */ { 'text': '7'},
+ /* 18 */ { 'text': '8'},
+ /* 19 */ { 'text': '9',
+ 'marginRightPercent': 0.545454},
+ /* 20 */ NonLetterKeys.SPACE,
+ /* 21 */ { 'text': '*'},
+ /* 22 */ { 'text': '0'},
+ /* 23 */ { 'text': '#'},
+ /* 24 */ NonLetterKeys.HIDE
+ ];
+};
+
+// TODO(rsadam): Add phone numberpad keyset here.
+
+}); // goog.scope
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/config/compact_symbol_characters.js b/third_party/google_input_tools/src/chrome/os/inputview/config/compact_symbol_characters.js
new file mode 100644
index 0000000..d6ef0cb4
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/config/compact_symbol_characters.js
@@ -0,0 +1,169 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.content.compact.symbol');
+
+goog.require('i18n.input.chrome.inputview.content.constants');
+
+goog.scope(function() {
+var NonLetterKeys = i18n.input.chrome.inputview.content.constants.NonLetterKeys;
+
+
+/**
+ * Gets North American Symbol keyset characters.
+ *
+ * @return {!Array.<!Object>}
+ */
+i18n.input.chrome.inputview.content.compact.symbol.keyNASymbolCharacters =
+ function() {
+ return [
+ /* 0 */ { 'text': '1',
+ 'moreKeys': ['\u00B9', '\u00BD', '\u2153', '\u00BC', '\u215B']},
+ /* 1 */ { 'text': '2',
+ 'moreKeys': ['\u00B2', '\u2154']},
+ /* 2 */ { 'text': '3',
+ 'moreKeys': ['\u00B3', '\u00BE', '\u215C']},
+ /* 3 */ { 'text': '4',
+ 'moreKeys': ['\u2074']},
+ /* 4 */ { 'text': '5',
+ 'moreKeys': ['\u215D']},
+ /* 5 */ { 'text': '6' },
+ /* 6 */ { 'text': '7',
+ 'moreKeys': ['\u215E']},
+ /* 7 */ { 'text': '8' },
+ /* 8 */ { 'text': '9' },
+ /* 9 */ { 'text': '0',
+ 'moreKeys': ['\u207F', '\u2205']},
+ /* 10 */ NonLetterKeys.BACKSPACE,
+ /* 11 */ { 'text': '@', 'marginLeftPercent': 0.33 },
+ /* 12 */ { 'text': '#' },
+ /* 13 */ { 'text': '$',
+ 'moreKeys': ['\u00A2', '\u00A3', '\u20AC', '\u00A5', '\u20B1']},
+ /* 14 */ { 'text': '%',
+ 'moreKeys': ['\u2030']},
+ /* 15 */ { 'text': '&' },
+ // Keep in sync with rowkeys_symbols2.xml in android input tool.
+ /* 16 */ { 'text': '-',
+ 'moreKeys': ['_', '\u2013', '\u2014', '\u00B7']},
+ /* 17 */ { 'text': '+',
+ 'moreKeys': ['\u00B1']},
+ /* 18 */ { 'text': '(',
+ 'moreKeys': ['\u007B', '\u003C', '\u005B']},
+ /* 19 */ { 'text': ')',
+ 'moreKeys': ['\u007D', '\u003E', '\u005D']},
+ /* 20 */ NonLetterKeys.ENTER,
+ /* 21 */ NonLetterKeys.SWITCHER,
+ /* 22 */ { 'text': '\\' },
+ /* 23 */ { 'text': '=' },
+ /* 24 */ { 'text': '*',
+ 'moreKeys': ['\u2020', '\u2021', '\u2605']},
+ // keep in sync with double_lqm_rqm and double_laqm_raqm in android input
+ // tool.
+ /* 25 */ { 'text': '"',
+ 'moreKeys': ['\u201C', '\u201E', '\u201D', '\u00AB', '\u00BB']},
+ // keep in sync with single_lqm_rqm and single_laqm_raqm in android input
+ // tool
+ /* 26 */ { 'text': '\'',
+ 'moreKeys': ['\u2018', '\u201A', '\u2019', '\u2039', '\u203A']},
+ /* 27 */ { 'text': ':' },
+ /* 28 */ { 'text': ';' },
+ /* 29 */ { 'text': '!',
+ 'moreKeys': ['\u00A1']},
+ /* 30 */ { 'text': '?',
+ 'moreKeys': ['\u00BF']},
+ /* 31 */ NonLetterKeys.SWITCHER,
+ /* 32 */ NonLetterKeys.SWITCHER,
+ /* 33 */ { 'text': '_', 'isGrey': true },
+ /* 34 */ NonLetterKeys.MENU,
+ /* 35 */ { 'text': '/', 'isGrey': true },
+ /* 36 */ NonLetterKeys.SPACE,
+ /* 37 */ { 'text': ',', 'isGrey': true },
+ /* 38 */ { 'text': '.', 'isGrey': true,
+ 'moreKeys': ['\u2026']},
+ /* 39 */ NonLetterKeys.HIDE
+ ];
+};
+
+
+/**
+ * Gets United Kingdom Symbol keyset characters.
+ *
+ * @return {!Array.<!Object>}
+ */
+i18n.input.chrome.inputview.content.compact.symbol.keyUKSymbolCharacters =
+ function() {
+ // Copy North America symbol characters.
+ var data = i18n.input.chrome.inputview.content.compact.symbol.
+ keyNASymbolCharacters();
+ // UK uses pound sign instead of dollar sign.
+ data[13] = { 'text': '\u00A3',
+ 'moreKeys': ['\u00A2', '$', '\u20AC', '\u00A5', '\u20B1']};
+ return data;
+};
+
+
+/**
+ * Gets European Symbol keyset characters.
+ *
+ * @return {!Array.<!Object>}
+ */
+i18n.input.chrome.inputview.content.compact.symbol.keyEUSymbolCharacters =
+ function() {
+ // Copy UK symbol characters.
+ var data = i18n.input.chrome.inputview.content.compact.symbol.
+ keyUKSymbolCharacters();
+ // European uses euro sign instead of pound sign.
+ data[13] = { 'text': '\u20AC',
+ 'moreKeys': ['\u00A2', '$', '\u00A3', '\u00A5', '\u20B1']};
+ return data;
+};
+
+
+/**
+ * Gets Pinyin Symbol keyset characters.
+ *
+ * @return {!Array.<!Object>}
+ */
+i18n.input.chrome.inputview.content.compact.symbol.keyPinyinSymbolCharacters =
+ function() {
+ var data = i18n.input.chrome.inputview.content.compact.symbol.
+ keyNASymbolCharacters();
+ data[13]['text'] = '\u00A5';
+ data[13]['moreKeys'] = ['\u0024', '\u00A2', '\u00A3', '\u20AC', '\u20B1'];
+ data[15]['text'] = '\uff0f';
+ data[16]['text'] = '\uff0d';
+ data[18]['text'] = '\uff08';
+ data[18]['moreKeys'] = ['\uff5b', '\u300a', '\uff3b', '\u3010'];
+ data[19]['text'] = '\uff09';
+ data[19]['moreKeys'] = ['\uff5d', '\u300b', '\uff3d', '\u3001'];
+ data[22]['text'] = '\u3001';
+ data[24]['text'] = '\u00D7';
+ data[25]['text'] = '\u201C';
+ data[25]['moreKeys'] = ['\u0022', '\u00AB'];
+ data[26]['text'] = '\u201D';
+ data[26]['moreKeys'] = ['\u0022', '\u00BB'];
+ data[27]['text'] = '\uff1a';
+ data[28]['text'] = '\uff1b';
+ data[29]['text'] = '\u2018';
+ data[29]['moreKeys'] = ['\u0027', '\u2039'];
+ data[30]['text'] = '\u2019';
+ data[30]['moreKeys'] = ['\u0027', '\u203a'];
+ data[33]['text'] = '\uff01';
+ data[33]['moreKeys'] = ['\u00a1'];
+ data[35]['text'] = '\uff1f';
+ data[35]['moreKeys'] = ['\u00bf'];
+ data[37]['text'] = '\uff0c';
+ data[38]['text'] = '\u3002';
+ return data;
+};
+}); // goog.scope
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/config/compact_util.js b/third_party/google_input_tools/src/chrome/os/inputview/config/compact_util.js
new file mode 100644
index 0000000..3257a04
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/config/compact_util.js
@@ -0,0 +1,378 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.content.compact.util');
+goog.provide('i18n.input.chrome.inputview.content.compact.util.CompactKeysetSpec');
+
+goog.require('i18n.input.chrome.inputview.Css');
+goog.require('i18n.input.chrome.inputview.content.constants');
+goog.require('i18n.input.chrome.inputview.elements.ElementType');
+
+goog.scope(function() {
+var util = i18n.input.chrome.inputview.content.compact.util;
+var Css = i18n.input.chrome.inputview.Css;
+
+
+/**
+ * The compact layout keyset type.
+ *
+ * @enum {string}
+ */
+util.CompactKeysetSpec = {
+ ID: 'id',
+ LAYOUT: 'layout',
+ DATA: 'data'
+};
+
+
+/**
+ * @desc The name of the layout providing numbers from 0-9 and common
+ * symbols.
+ */
+util.MSG_NUMBER_AND_SYMBOL =
+ goog.getMsg('number and symbol layout');
+
+
+/**
+ * @desc The name of the layout providing more symbols.
+ */
+util.MSG_MORE_SYMBOLS =
+ goog.getMsg('more symbols layout');
+
+
+/**
+ * @desc The name of the main layout.
+ */
+util.MSG_MAIN_LAYOUT = goog.getMsg('main layout');
+
+
+/**
+ * @desc The name of the english main layout.
+ */
+util.MSG_ENG_MAIN_LAYOUT = goog.getMsg('english main layout');
+
+
+/**
+ * @desc The name of the english layout providing numbers from 0-9 and common.
+ */
+util.MSG_ENG_MORE_SYMBOLS =
+ goog.getMsg('english more symbols layout');
+
+
+/**
+ * @desc The name of the english layout providing more symbols.
+ */
+util.MSG_ENG_NUMBER_AND_SYMBOL =
+ goog.getMsg('english number and symbol layout');
+
+
+/**
+ * Creates the compact key data.
+ *
+ * @param {!Object} keysetSpec The keyset spec.
+ * @param {string} viewIdPrefix The prefix of the view.
+ * @param {string} keyIdPrefix The prefix of the key.
+ * @return {!Object} The key data.
+ */
+util.createCompactData = function(keysetSpec, viewIdPrefix, keyIdPrefix) {
+ var keyList = [];
+ var mapping = {};
+ var keysetSpecNode = util.CompactKeysetSpec;
+ for (var i = 0; i < keysetSpec[keysetSpecNode.DATA].length; i++) {
+ var keySpec = keysetSpec[keysetSpecNode.DATA][i];
+ if (keySpec ==
+ i18n.input.chrome.inputview.content.constants.NonLetterKeys.MENU) {
+ keySpec['toKeyset'] = keysetSpec[keysetSpecNode.ID].split('.')[0];
+ }
+ var id = keySpec['id'] ? keySpec['id'] : keyIdPrefix + i;
+ var key = util.createCompactKey(id, keySpec);
+ keyList.push(key);
+ mapping[key['spec']['id']] = viewIdPrefix + i;
+ }
+
+ return {
+ 'keyList': keyList,
+ 'mapping': mapping,
+ 'layout': keysetSpec[keysetSpecNode.LAYOUT]
+ };
+};
+
+
+/**
+ * Creates a key in the compact keyboard.
+ *
+ * @param {string} id The id.
+ * @param {!Object} keySpec The specification.
+ * @return {!Object} The compact key.
+ */
+util.createCompactKey = function(id, keySpec) {
+ var spec = keySpec;
+ spec['id'] = id;
+ if (!spec['type'])
+ spec['type'] = i18n.input.chrome.inputview.elements.ElementType.COMPACT_KEY;
+
+ var newSpec = {};
+ for (var key in spec) {
+ newSpec[key] = spec[key];
+ }
+
+ return {
+ 'spec': newSpec
+ };
+};
+
+
+/**
+ * Customize the switcher keys in key characters.
+ *
+ * @param {!Array.<!Object>} keyCharacters The key characters.
+ * @param {!Array.<!Object>} switcherKeys The customized switcher keys.
+ */
+util.customizeSwitchers = function(keyCharacters, switcherKeys) {
+ var j = 0;
+ for (var i = 0; i < keyCharacters.length; i++) {
+ if (keyCharacters[i] ==
+ i18n.input.chrome.inputview.content.constants.NonLetterKeys.SWITCHER) {
+ if (j >= switcherKeys.length) {
+ console.error('The number of switcher key spec is less than' +
+ ' the number of switcher keys in the keyset.');
+ return;
+ }
+ var newSpec = switcherKeys[j++];
+ for (var key in keyCharacters[i]) {
+ newSpec[key] = keyCharacters[i][key];
+ }
+ keyCharacters[i] = newSpec;
+ }
+ }
+ if (j < switcherKeys.length - 1) {
+ console.error('The number of switcher key spec is more than' +
+ ' the number of switcher keys in the keyset.');
+ }
+};
+
+
+/**
+ * Generates letter, symbol and more compact keysets and load them.
+ *
+ * @param {!Object} letterKeysetSpec The spec of letter keyset.
+ * @param {!Object} symbolKeysetSpec The spec of symbol keyset.
+ * @param {!Object} moreKeysetSpec The spec of more keyset.
+ * @param {!function(!Object): void} onLoaded The function to call once a keyset
+ * data is ready.
+ */
+util.generateCompactKeyboard =
+ function(letterKeysetSpec, symbolKeysetSpec, moreKeysetSpec, onLoaded) {
+ // Creates compacty qwerty keyset.
+ var keysetSpecNode = util.CompactKeysetSpec;
+ util.customizeSwitchers(
+ letterKeysetSpec[keysetSpecNode.DATA],
+ [{ 'name': '?123',
+ 'toKeyset': symbolKeysetSpec[keysetSpecNode.ID],
+ 'toKeysetName': util.MSG_NUMBER_AND_SYMBOL
+ }]);
+
+ var data = util.createCompactData(
+ letterKeysetSpec, 'compactkbd-k-', 'compactkbd-k-key-');
+ data['id'] = letterKeysetSpec[keysetSpecNode.ID];
+ data['showMenuKey'] = true;
+ onLoaded(data);
+
+ // Creates compacty symbol keyset.
+ util.customizeSwitchers(
+ symbolKeysetSpec[keysetSpecNode.DATA],
+ [{ 'name': '~[<',
+ 'toKeyset': moreKeysetSpec[keysetSpecNode.ID],
+ 'toKeysetName': util.MSG_MORE_SYMBOLS
+ },
+ { 'name': '~[<',
+ 'toKeyset': moreKeysetSpec[keysetSpecNode.ID],
+ 'toKeysetName': util.MSG_MORE_SYMBOLS
+ },
+ { 'name': 'abc',
+ 'toKeyset': letterKeysetSpec[keysetSpecNode.ID],
+ 'toKeysetName': util.MSG_MAIN_LAYOUT
+ }]);
+ data = util.createCompactData(
+ symbolKeysetSpec, 'compactkbd-k-', 'compactkbd-k-key-');
+ data['id'] = symbolKeysetSpec[keysetSpecNode.ID];
+ data['showMenuKey'] = false;
+ data['noShift'] = true;
+ onLoaded(data);
+
+ // Creates compact more keyset.
+ util.customizeSwitchers(
+ moreKeysetSpec[keysetSpecNode.DATA],
+ [{ 'name': '?123',
+ 'toKeyset': symbolKeysetSpec[keysetSpecNode.ID],
+ 'toKeysetName': util.MSG_NUMBER_AND_SYMBOL
+ },
+ { 'name': '?123',
+ 'toKeyset': symbolKeysetSpec[keysetSpecNode.ID],
+ 'toKeysetName': util.MSG_NUMBER_AND_SYMBOL
+ },
+ { 'name': 'abc',
+ 'toKeyset': letterKeysetSpec[keysetSpecNode.ID],
+ 'toKeysetName': util.MSG_MAIN_LAYOUT
+ }]);
+ data = util.createCompactData(moreKeysetSpec, 'compactkbd-k-',
+ 'compactkbd-k-key-');
+ data['id'] = moreKeysetSpec[keysetSpecNode.ID];
+ data['showMenuKey'] = false;
+ data['noShift'] = true;
+ onLoaded(data);
+};
+
+
+/**
+ * Generates letter, symbol and more compact keysets for
+ * pinyin's chinese and english mode and load them.
+ *
+ * @param {!Object} letterKeysetSpec The spec of letter keyset.
+ * @param {!Object} engKeysetSpec The english spec of letter keyset
+ * @param {!Object} symbolKeysetSpec The spec of symbol keyset.
+ * @param {!Object} engSymbolKeysetSpec The spec of engish symbol keyset.
+ * @param {!Object} moreKeysetSpec The spec of more keyset.
+ * @param {!Object} engMoreKeysetSpec The spec of english more keyset.
+ * @param {!function(!Object): void} onLoaded The function to call once a keyset
+ * data is ready.
+ */
+util.generatePinyinCompactKeyboard = function(letterKeysetSpec, engKeysetSpec,
+ symbolKeysetSpec, engSymbolKeysetSpec,
+ moreKeysetSpec, engMoreKeysetSpec, onLoaded) {
+ // Creates compacty qwerty keyset for pinyin.
+ var keysetSpecNode = util.CompactKeysetSpec;
+ util.customizeSwitchers(
+ letterKeysetSpec[keysetSpecNode.DATA],
+ [{ 'name': '?123',
+ 'toKeyset': symbolKeysetSpec[keysetSpecNode.ID],
+ 'toKeysetName': util.MSG_NUMBER_AND_SYMBOL
+ },
+ { 'toKeyset': engKeysetSpec[keysetSpecNode.ID],
+ 'toKeysetName': util.MSG_ENG_MAIN_LAYOUT,
+ 'iconCssClass': Css.SWITCHER_CHINESE
+ }]);
+
+ var data = util.createCompactData(
+ letterKeysetSpec, 'compactkbd-k-', 'compactkbd-k-key-');
+ data['id'] = letterKeysetSpec[keysetSpecNode.ID];
+ data['showMenuKey'] = true;
+ onLoaded(data);
+
+ // Creates the compacty qwerty keyset for pinyin's English mode.
+ util.customizeSwitchers(
+ engKeysetSpec[keysetSpecNode.DATA],
+ [{ 'name': '?123',
+ 'toKeyset': engSymbolKeysetSpec[keysetSpecNode.ID],
+ 'toKeysetName': util.MSG_ENG_NUMBER_AND_SYMBOL
+ },
+ { 'toKeyset': letterKeysetSpec[keysetSpecNode.ID],
+ 'toKeysetName': util.MSG_MAIN_LAYOUT,
+ 'iconCssClass': Css.SWITCHER_ENGLISH
+ }]);
+
+ data = util.createCompactData(
+ engKeysetSpec, 'compactkbd-k-', 'compactkbd-k-key-');
+ data['id'] = engKeysetSpec[keysetSpecNode.ID];
+ data['showMenuKey'] = true;
+ onLoaded(data);
+
+ // Creates compacty symbol keyset for pinyin.
+ util.customizeSwitchers(
+ symbolKeysetSpec[keysetSpecNode.DATA],
+ [{ 'name': '~[<',
+ 'toKeyset': moreKeysetSpec[keysetSpecNode.ID],
+ 'toKeysetName': util.MSG_MORE_SYMBOLS
+ },
+ { 'name': '~[<',
+ 'toKeyset': moreKeysetSpec[keysetSpecNode.ID],
+ 'toKeysetName': util.MSG_MORE_SYMBOLS
+ },
+ { 'name': 'abc',
+ 'toKeyset': letterKeysetSpec[keysetSpecNode.ID],
+ 'toKeysetName': util.MSG_MAIN_LAYOUT
+ }]);
+ data = util.createCompactData(
+ symbolKeysetSpec, 'compactkbd-k-', 'compactkbd-k-key-');
+ data['id'] = symbolKeysetSpec[keysetSpecNode.ID];
+ data['showMenuKey'] = false;
+ data['noShift'] = true;
+ onLoaded(data);
+
+ // Creates compacty symbol keyset for English mode.
+ util.customizeSwitchers(
+ engSymbolKeysetSpec[keysetSpecNode.DATA],
+ [{ 'name': '~[<',
+ 'toKeyset': engMoreKeysetSpec[keysetSpecNode.ID],
+ 'toKeysetName': util.MSG_ENG_MORE_SYMBOLS
+ },
+ { 'name': '~[<',
+ 'toKeyset': engMoreKeysetSpec[keysetSpecNode.ID],
+ 'toKeysetName': util.MSG_ENG_MORE_SYMBOLS
+ },
+ { 'name': 'abc',
+ 'toKeyset': engKeysetSpec[keysetSpecNode.ID],
+ 'toKeysetName': util.MSG_ENG_MAIN_LAYOUT
+ }]);
+ data = util.createCompactData(
+ engSymbolKeysetSpec, 'compactkbd-k-', 'compactkbd-k-key-');
+ data['id'] = engSymbolKeysetSpec[keysetSpecNode.ID];
+ data['showMenuKey'] = false;
+ data['noShift'] = true;
+ onLoaded(data);
+
+ // Creates compact more keyset for pinyin.
+ util.customizeSwitchers(
+ moreKeysetSpec[keysetSpecNode.DATA],
+ [{ 'name': '?123',
+ 'toKeyset': symbolKeysetSpec[keysetSpecNode.ID],
+ 'toKeysetName': util.MSG_NUMBER_AND_SYMBOL
+ },
+ { 'name': '?123',
+ 'toKeyset': symbolKeysetSpec[keysetSpecNode.ID],
+ 'toKeysetName': util.MSG_NUMBER_AND_SYMBOL
+ },
+ { 'name': 'abc',
+ 'toKeyset': letterKeysetSpec[keysetSpecNode.ID],
+ 'toKeysetName': util.MSG_MAIN_LAYOUT
+ }]);
+ data = util.createCompactData(moreKeysetSpec, 'compactkbd-k-',
+ 'compactkbd-k-key-');
+ data['id'] = moreKeysetSpec[keysetSpecNode.ID];
+ data['showMenuKey'] = false;
+ data['noShift'] = true;
+ onLoaded(data);
+
+ // Creates the compact more keyset of english mode.
+ util.customizeSwitchers(
+ engMoreKeysetSpec[keysetSpecNode.DATA],
+ [{ 'name': '?123',
+ 'toKeyset': engSymbolKeysetSpec[keysetSpecNode.ID],
+ 'toKeysetName': util.MSG_ENG_NUMBER_AND_SYMBOL
+ },
+ { 'name': '?123',
+ 'toKeyset': engSymbolKeysetSpec[keysetSpecNode.ID],
+ 'toKeysetName': util.MSG_ENG_NUMBER_AND_SYMBOL
+ },
+ { 'name': 'abc',
+ 'toKeyset': engKeysetSpec[keysetSpecNode.ID],
+ 'toKeysetName': util.MSG_ENG_MAIN_LAYOUT
+ }]);
+ data = util.createCompactData(engMoreKeysetSpec, 'compactkbd-k-',
+ 'compactkbd-k-key-');
+ data['id'] = engMoreKeysetSpec[keysetSpecNode.ID];
+ data['showMenuKey'] = false;
+ data['noShift'] = true;
+ onLoaded(data);
+};
+}); // goog.scope
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/config/constants.js b/third_party/google_input_tools/src/chrome/os/inputview/config/constants.js
new file mode 100644
index 0000000..bf4e688
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/config/constants.js
@@ -0,0 +1,81 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.content.constants');
+
+goog.require('i18n.input.chrome.inputview.Css');
+goog.require('i18n.input.chrome.inputview.StateType');
+goog.require('i18n.input.chrome.inputview.elements.ElementType');
+
+goog.scope(function() {
+
+var ElementType = i18n.input.chrome.inputview.elements.ElementType;
+
+/**
+ * The non letter keys.
+ *
+ * @const
+ * @enum {Object}
+ */
+i18n.input.chrome.inputview.content.constants.NonLetterKeys = {
+ BACKSPACE: {
+ 'iconCssClass': i18n.input.chrome.inputview.Css.BACKSPACE_ICON,
+ 'type': ElementType.BACKSPACE_KEY,
+ 'id': 'Backspace'
+ },
+ ENTER: {
+ 'iconCssClass': i18n.input.chrome.inputview.Css.ENTER_ICON,
+ 'type': ElementType.ENTER_KEY,
+ 'id': 'Enter'
+ },
+ HIDE: {
+ 'iconCssClass': i18n.input.chrome.inputview.Css.HIDE_KEYBOARD_ICON,
+ 'type': ElementType.HIDE_KEYBOARD_KEY,
+ 'id': 'HideKeyboard'
+ },
+ LEFT_SHIFT: {
+ 'toState': i18n.input.chrome.inputview.StateType.SHIFT,
+ 'iconCssClass': i18n.input.chrome.inputview.Css.SHIFT_ICON,
+ 'type': ElementType.MODIFIER_KEY,
+ 'id': 'ShiftLeft',
+ 'supportSticky': true
+ },
+ RIGHT_SHIFT: {
+ 'toState': i18n.input.chrome.inputview.StateType.SHIFT,
+ 'iconCssClass': i18n.input.chrome.inputview.Css.SHIFT_ICON,
+ 'type': ElementType.MODIFIER_KEY,
+ 'id': 'ShiftRight',
+ 'supportSticky': true
+ },
+ SPACE: {
+ 'name': ' ',
+ 'type': ElementType.SPACE_KEY,
+ 'id': 'Space'
+ },
+ SWITCHER: {
+ 'type': ElementType.SWITCHER_KEY
+ },
+ MENU: {
+ 'iconCssClass': i18n.input.chrome.inputview.Css.MENU_ICON,
+ 'type': ElementType.MENU_KEY,
+ 'id': 'Menu'
+ },
+ GLOBE: {
+ 'iconCssClass': i18n.input.chrome.inputview.Css.GLOBE_ICON,
+ 'type': ElementType.GLOBE_KEY,
+ 'id': 'Globe'
+ }
+};
+
+
+}); // goog.scope
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/config/contextlayoututil.js b/third_party/google_input_tools/src/chrome/os/inputview/config/contextlayoututil.js
new file mode 100644
index 0000000..3cc5de1
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/config/contextlayoututil.js
@@ -0,0 +1,52 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.content.ContextlayoutUtil');
+
+goog.require('i18n.input.chrome.inputview.content.compact.util');
+goog.require('i18n.input.chrome.inputview.content.compact.util.CompactKeysetSpec');
+goog.require('i18n.input.chrome.message.ContextType');
+
+goog.scope(function() {
+var ContextType = i18n.input.chrome.message.ContextType;
+var util = i18n.input.chrome.inputview.content.ContextlayoutUtil;
+var compact = i18n.input.chrome.inputview.content.compact.util;
+var keysetSpecNode = compact.CompactKeysetSpec;
+
+
+/**
+ * Generates custom layouts to be displayed on specified input type contexts.
+ *
+ *
+ * @param {Object.<ContextType, !Object>} inputTypeToKeysetSpecMap Map from
+ * input types to context-specific keysets.
+ * @param {!function(!Object): void} onLoaded The function to call once a keyset
+ * data is ready.
+ */
+util.generateContextLayouts = function(inputTypeToKeysetSpecMap, onLoaded) {
+ // Creates context-specific keysets.
+ if (inputTypeToKeysetSpecMap) {
+ for (var inputType in inputTypeToKeysetSpecMap) {
+ var spec = inputTypeToKeysetSpecMap[inputType];
+ var data = compact.createCompactData(
+ spec, 'compactkbd-k-', 'compactkbd-k-key-');
+ data['id'] = spec[keysetSpecNode.ID];
+ data['showMenuKey'] = false;
+ data['noShift'] = true;
+ data['onContext'] = inputType;
+ onLoaded(data);
+ }
+ }
+};
+
+}); // goog.scope
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/config/util.js b/third_party/google_input_tools/src/chrome/os/inputview/config/util.js
new file mode 100644
index 0000000..098e4aa
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/config/util.js
@@ -0,0 +1,605 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.content.util');
+
+goog.require('goog.array');
+goog.require('i18n.input.chrome.inputview.Css');
+goog.require('i18n.input.chrome.inputview.Direction');
+goog.require('i18n.input.chrome.inputview.SpecNodeName');
+goog.require('i18n.input.chrome.inputview.StateType');
+goog.require('i18n.input.chrome.inputview.elements.ElementType');
+
+goog.scope(function() {
+var ElementType = i18n.input.chrome.inputview.elements.ElementType;
+var SpecNodeName = i18n.input.chrome.inputview.SpecNodeName;
+
+
+/**
+ * The prefix of the key id.
+ *
+ * @type {string}
+ * @private
+ */
+i18n.input.chrome.inputview.content.util.keyIdPrefix_ = 'sk-';
+
+
+/**
+ * Creates the hide keyboard key.
+ *
+ * @return {!Object} The hide keyboard key.
+ */
+i18n.input.chrome.inputview.content.util.createHideKeyboardKey = function() {
+ var spec = {};
+ spec[SpecNodeName.ICON_CSS_CLASS] =
+ i18n.input.chrome.inputview.Css.HIDE_KEYBOARD_ICON;
+ spec[SpecNodeName.TYPE] = ElementType.HIDE_KEYBOARD_KEY;
+ spec[SpecNodeName.ID] = 'HideKeyboard';
+ return i18n.input.chrome.inputview.content.util.createKey(spec);
+};
+
+
+/**
+ * Creates a shift key.
+ *
+ * @param {boolean} isLeft True if this is the left shift key.
+ * @param {boolean=} opt_supportSticky True if support sticky shift key.
+ * @return {!Object} The shift key.
+ */
+i18n.input.chrome.inputview.content.util.createShiftKey = function(isLeft,
+ opt_supportSticky) {
+ var spec = {};
+ spec[SpecNodeName.TO_STATE] = i18n.input.chrome.inputview.StateType.SHIFT;
+ spec[SpecNodeName.ICON_CSS_CLASS] =
+ i18n.input.chrome.inputview.Css.SHIFT_ICON;
+ spec[SpecNodeName.TYPE] = ElementType.MODIFIER_KEY;
+ spec[SpecNodeName.ID] = isLeft ? 'ShiftLeft' : 'ShiftRight';
+ spec[SpecNodeName.SUPPORT_STICKY] = !!opt_supportSticky;
+ return i18n.input.chrome.inputview.content.util.createKey(spec);
+};
+
+
+/**
+ * Creates a globe key.
+ *
+ * @return {!Object} The globe key.
+ */
+i18n.input.chrome.inputview.content.util.createGlobeKey = function() {
+ var spec = {};
+ spec[SpecNodeName.ICON_CSS_CLASS] =
+ i18n.input.chrome.inputview.Css.GLOBE_ICON;
+ spec[SpecNodeName.TYPE] = ElementType.GLOBE_KEY;
+ spec[SpecNodeName.ID] = 'Globe';
+ return i18n.input.chrome.inputview.content.util.createKey(spec);
+};
+
+
+/**
+ * Creates a menu key.
+ *
+ * @param {string=} opt_toKeyset The compact keyboard id.
+ * @return {!Object} The menu key.
+ */
+i18n.input.chrome.inputview.content.util.createMenuKey = function(
+ opt_toKeyset) {
+ var spec = {};
+ spec[SpecNodeName.ICON_CSS_CLASS] =
+ i18n.input.chrome.inputview.Css.MENU_ICON;
+ spec[SpecNodeName.TO_KEYSET] = opt_toKeyset;
+ spec[SpecNodeName.TYPE] = ElementType.MENU_KEY;
+ spec[SpecNodeName.ID] = 'Menu';
+ return i18n.input.chrome.inputview.content.util.createKey(spec);
+};
+
+
+/**
+ * Create the Emoji switch key.
+ *
+ * @param {string} id The emoji key id.
+ * @param {number} toKeyset The keyset that the tabbar represents.
+ * @param {i18n.input.chrome.inputview.Css}
+ * iconCssClass The icon css for the tabbar.
+ * @return {!Object} The emoji key.
+ */
+i18n.input.chrome.inputview.content.util.createTabBarKey =
+ function(id, toKeyset, iconCssClass) {
+ var spec = {};
+ spec[SpecNodeName.ICON_CSS_CLASS] = iconCssClass;
+ spec[SpecNodeName.TYPE] = ElementType.TAB_BAR_KEY;
+ spec[SpecNodeName.ID] = id;
+ spec[SpecNodeName.TO_KEYSET] = toKeyset;
+ return i18n.input.chrome.inputview.content.util.createKey(spec);
+};
+
+
+/**
+ * Create the indicator
+ *
+ * @param {string} id The indicator id.
+ * @return {!Object} The indicator.
+ */
+i18n.input.chrome.inputview.content.util.createPageIndicator =
+ function(id) {
+ var spec = {};
+ spec[SpecNodeName.TYPE] = ElementType.PAGE_INDICATOR;
+ spec[SpecNodeName.ID] = id;
+ return i18n.input.chrome.inputview.content.util.createKey(spec);
+};
+
+
+/**
+ * Create the back key for emoji
+ *
+ * @return {!Object} The back key.
+ */
+i18n.input.chrome.inputview.content.util.createBackKey = function() {
+ var spec = {};
+ spec[SpecNodeName.ICON_CSS_CLASS] =
+ i18n.input.chrome.inputview.Css.EMOJI_BACK;
+ spec[SpecNodeName.TYPE] = ElementType.BACK_BUTTON;
+ spec[SpecNodeName.ID] = 'backkey';
+ return i18n.input.chrome.inputview.content.util.createKey(spec);
+};
+
+
+/**
+ * Creates a ctrl key.
+ *
+ * @return {!Object} The ctrl key.
+ */
+i18n.input.chrome.inputview.content.util.createCtrlKey = function() {
+ var spec = {};
+ spec[SpecNodeName.TO_STATE] = i18n.input.chrome.inputview.StateType.CTRL;
+ spec[SpecNodeName.NAME] = 'ctrl';
+ spec[SpecNodeName.TYPE] = ElementType.MODIFIER_KEY;
+ spec[SpecNodeName.ID] = 'ControlLeft';
+ return i18n.input.chrome.inputview.content.util.createKey(spec);
+};
+
+
+/**
+ * Creates a alt key.
+ *
+ * @return {!Object} The alt key.
+ */
+i18n.input.chrome.inputview.content.util.createAltKey = function() {
+ var spec = {};
+ spec[SpecNodeName.TO_STATE] = i18n.input.chrome.inputview.StateType.ALT;
+ spec[SpecNodeName.NAME] = 'alt';
+ spec[SpecNodeName.TYPE] = ElementType.MODIFIER_KEY;
+ spec[SpecNodeName.ID] = 'AltLeft';
+ return i18n.input.chrome.inputview.content.util.createKey(spec);
+};
+
+
+/**
+ * Creates a altgr key.
+ *
+ * @return {!Object} The altgr key.
+ */
+i18n.input.chrome.inputview.content.util.createAltgrKey = function() {
+ var spec = {};
+ spec[SpecNodeName.TO_STATE] = i18n.input.chrome.inputview.StateType.ALTGR;
+ spec[SpecNodeName.NAME] = 'alt gr';
+ spec[SpecNodeName.TYPE] = ElementType.MODIFIER_KEY;
+ spec[SpecNodeName.ID] = 'AltRight';
+ return i18n.input.chrome.inputview.content.util.createKey(spec);
+};
+
+
+/**
+ * Creates a key used to switch to english.
+ *
+ * @return {!Object} The enSwitcher key.
+ */
+i18n.input.chrome.inputview.content.util.createEnSwitcherKey =
+ function() {
+ var spec = {};
+ spec[SpecNodeName.TYPE] = ElementType.EN_SWITCHER;
+ spec[SpecNodeName.ID] = 'enSwitcher';
+ return i18n.input.chrome.inputview.content.util.createKey(spec);
+};
+
+
+/**
+ * Creates a capslock key.
+ *
+ * @return {!Object} The capslock key.
+ */
+i18n.input.chrome.inputview.content.util.createCapslockKey = function() {
+ var spec = {};
+ spec[SpecNodeName.TO_STATE] = i18n.input.chrome.inputview.StateType.CAPSLOCK;
+ spec[SpecNodeName.NAME] = 'caps lock';
+ spec[SpecNodeName.TYPE] = ElementType.MODIFIER_KEY;
+ spec[SpecNodeName.ID] = 'OsLeft';
+ return i18n.input.chrome.inputview.content.util.createKey(spec);
+};
+
+
+/**
+ * Creates a enter key.
+ *
+ * @return {!Object} The enter key.
+ */
+i18n.input.chrome.inputview.content.util.createEnterKey = function() {
+ var spec = {};
+ spec[SpecNodeName.ICON_CSS_CLASS] =
+ i18n.input.chrome.inputview.Css.ENTER_ICON;
+ spec[SpecNodeName.TYPE] = ElementType.ENTER_KEY;
+ spec[SpecNodeName.ID] = 'Enter';
+ return i18n.input.chrome.inputview.content.util.createKey(spec);
+};
+
+
+/**
+ * Creates a tab key.
+ *
+ * @return {!Object} The tab key.
+ */
+i18n.input.chrome.inputview.content.util.createTabKey = function() {
+ var spec = {};
+ spec[SpecNodeName.ICON_CSS_CLASS] = i18n.input.chrome.inputview.Css.TAB_ICON;
+ spec[SpecNodeName.TYPE] = ElementType.TAB_KEY;
+ spec[SpecNodeName.ID] = 'Tab';
+ return i18n.input.chrome.inputview.content.util.createKey(spec);
+};
+
+
+/**
+ * Creates a backspace key.
+ *
+ * @return {!Object} The backspace key.
+ */
+i18n.input.chrome.inputview.content.util.createBackspaceKey = function() {
+ var spec = {};
+ spec[SpecNodeName.ICON_CSS_CLASS] =
+ i18n.input.chrome.inputview.Css.BACKSPACE_ICON;
+ spec[SpecNodeName.TYPE] = ElementType.BACKSPACE_KEY;
+ spec[SpecNodeName.ID] = 'Backspace';
+ return i18n.input.chrome.inputview.content.util.createKey(spec);
+};
+
+
+/**
+ * Creates a space key.
+ *
+ * @return {!Object} The space key.
+ */
+i18n.input.chrome.inputview.content.util.createSpaceKey = function() {
+ var spec = {};
+ spec[SpecNodeName.NAME] = ' ';
+ spec[SpecNodeName.TYPE] = ElementType.SPACE_KEY;
+ spec[SpecNodeName.ID] = 'Space';
+ return i18n.input.chrome.inputview.content.util.createKey(spec);
+};
+
+
+/**
+ * Create an IME switch key.
+ *
+ * @param {string} id .
+ * @param {string} name .
+ * @param {string} css .
+ * @return {!Object} The JP IME switch key.
+ */
+i18n.input.chrome.inputview.content.util.createIMESwitchKey =
+ function(id, name, css) {
+ var spec = {};
+ spec[SpecNodeName.NAME] = name;
+ spec[SpecNodeName.TYPE] = ElementType.IME_SWITCH;
+ spec[SpecNodeName.ID] = id;
+ spec[SpecNodeName.TEXT_CSS_CLASS] = css;
+ return i18n.input.chrome.inputview.content.util.createKey(spec);
+};
+
+
+/**
+ * Creates a normal key.
+ *
+ * @param {!Object} spec The specification.
+ * @return {!Object} The normal key.
+ */
+i18n.input.chrome.inputview.content.util.createNormalKey = function(spec) {
+ spec[SpecNodeName.TYPE] = ElementType.CHARACTER_KEY;
+ return i18n.input.chrome.inputview.content.util.createKey(spec);
+};
+
+
+/**
+ * Creates an arrow key.
+ *
+ * @param {!i18n.input.chrome.inputview.Direction} direction The direction.
+ * @return {!Object} The arrow key.
+ */
+i18n.input.chrome.inputview.content.util.createArrowKey = function(direction) {
+ var spec = {};
+ spec[SpecNodeName.ICON_CSS_CLASS] =
+ i18n.input.chrome.inputview.Css.ARROW_KEY + ' ';
+ if (direction == i18n.input.chrome.inputview.Direction.UP) {
+ spec[SpecNodeName.ID] = 'ArrowUp';
+ spec[SpecNodeName.ICON_CSS_CLASS] += i18n.input.chrome.inputview.Css.UP_KEY;
+ spec[SpecNodeName.TYPE] = ElementType.ARROW_UP;
+ } else if (direction == i18n.input.chrome.inputview.Direction.DOWN) {
+ spec[SpecNodeName.ID] = 'ArrowDown';
+ spec[SpecNodeName.ICON_CSS_CLASS] +=
+ i18n.input.chrome.inputview.Css.DOWN_KEY;
+ spec[SpecNodeName.TYPE] = ElementType.ARROW_DOWN;
+ } else if (direction == i18n.input.chrome.inputview.Direction.LEFT) {
+ spec[SpecNodeName.ID] = 'ArrowLeft';
+ spec[SpecNodeName.ICON_CSS_CLASS] +=
+ i18n.input.chrome.inputview.Css.LEFT_KEY;
+ spec[SpecNodeName.TYPE] = ElementType.ARROW_LEFT;
+ } else if (direction == i18n.input.chrome.inputview.Direction.RIGHT) {
+ spec[SpecNodeName.ID] = 'ArrowRight';
+ spec[SpecNodeName.ICON_CSS_CLASS] +=
+ i18n.input.chrome.inputview.Css.RIGHT_KEY;
+ spec[SpecNodeName.TYPE] = ElementType.ARROW_RIGHT;
+ }
+ return i18n.input.chrome.inputview.content.util.createKey(spec);
+};
+
+
+/**
+ * Creates a soft key.
+ *
+ * @param {!Object} spec The specification.
+ * @return {!Object} The soft key.
+ */
+i18n.input.chrome.inputview.content.util.createKey = function(spec) {
+ var newSpec = {};
+ for (var key in spec) {
+ newSpec[key] = spec[key];
+ }
+ return {
+ 'spec': newSpec
+ };
+};
+
+
+/**
+ * The physical key codes.
+ *
+ * @type {!Array.<string>}
+ */
+i18n.input.chrome.inputview.content.util.KEY_CODES_101 = [
+ 'Backquote',
+ 'Digit1',
+ 'Digit2',
+ 'Digit3',
+ 'Digit4',
+ 'Digit5',
+ 'Digit6',
+ 'Digit7',
+ 'Digit8',
+ 'Digit9',
+ 'Digit0',
+ 'Minus',
+ 'Equal',
+ 'KeyQ',
+ 'KeyW',
+ 'KeyE',
+ 'KeyR',
+ 'KeyT',
+ 'KeyY',
+ 'KeyU',
+ 'KeyI',
+ 'KeyO',
+ 'KeyP',
+ 'BracketLeft',
+ 'BracketRight',
+ 'Backslash',
+ 'KeyA',
+ 'KeyS',
+ 'KeyD',
+ 'KeyF',
+ 'KeyG',
+ 'KeyH',
+ 'KeyJ',
+ 'KeyK',
+ 'KeyL',
+ 'Semicolon',
+ 'Quote',
+ 'KeyZ',
+ 'KeyX',
+ 'KeyC',
+ 'KeyV',
+ 'KeyB',
+ 'KeyN',
+ 'KeyM',
+ 'Comma',
+ 'Period',
+ 'Slash'
+];
+
+
+/**
+ * The physical key codes for 102 keyboard.
+ *
+ * @type {!Array.<string>}
+ */
+i18n.input.chrome.inputview.content.util.KEY_CODES_102 = [
+ 'Backquote',
+ 'Digit1',
+ 'Digit2',
+ 'Digit3',
+ 'Digit4',
+ 'Digit5',
+ 'Digit6',
+ 'Digit7',
+ 'Digit8',
+ 'Digit9',
+ 'Digit0',
+ 'Minus',
+ 'Equal',
+ 'KeyQ',
+ 'KeyW',
+ 'KeyE',
+ 'KeyR',
+ 'KeyT',
+ 'KeyY',
+ 'KeyU',
+ 'KeyI',
+ 'KeyO',
+ 'KeyP',
+ 'BracketLeft',
+ 'BracketRight',
+ 'KeyA',
+ 'KeyS',
+ 'KeyD',
+ 'KeyF',
+ 'KeyG',
+ 'KeyH',
+ 'KeyJ',
+ 'KeyK',
+ 'KeyL',
+ 'Semicolon',
+ 'Quote',
+ 'Backslash',
+ 'IntlBackslash',
+ 'KeyZ',
+ 'KeyX',
+ 'KeyC',
+ 'KeyV',
+ 'KeyB',
+ 'KeyN',
+ 'KeyM',
+ 'Comma',
+ 'Period',
+ 'Slash'
+];
+
+
+/**
+ * Creates the key data.
+ *
+ * @param {!Array.<!Array.<string>>} keyCharacters The key characters.
+ * @param {string} viewIdPrefix The prefix of the view.
+ * @param {boolean} is102 True if it is a 102 keyboard.
+ * @param {boolean} hasAltGrKey True if there is altgr key.
+ * @param {!Array.<!Array.<number>>=} opt_keyCodes The key codes.
+ * @param {string=} opt_compactKeyboardId The compact keyboard id.
+ * @return {!Object} The key data.
+ */
+i18n.input.chrome.inputview.content.util.createData = function(keyCharacters,
+ viewIdPrefix, is102, hasAltGrKey, opt_keyCodes, opt_compactKeyboardId) {
+ var keyList = [];
+ var mapping = {};
+ var keyCodes = opt_keyCodes || [];
+ var keyIds = is102 ? i18n.input.chrome.inputview.content.util.KEY_CODES_102 :
+ i18n.input.chrome.inputview.content.util.KEY_CODES_101;
+ for (var i = 0; i < keyCharacters.length - 1; i++) {
+ var spec = {};
+ spec[SpecNodeName.ID] = keyIds[i];
+ spec[SpecNodeName.TYPE] = ElementType.CHARACTER_KEY;
+ spec[SpecNodeName.CHARACTERS] = keyCharacters[i];
+ spec[SpecNodeName.KEY_CODE] = keyCodes[i];
+ var key = i18n.input.chrome.inputview.content.util.createKey(spec);
+ keyList.push(key);
+ }
+
+ i18n.input.chrome.inputview.content.util.insertModifierKeys_(keyList,
+ is102, opt_compactKeyboardId);
+ for (var i = 0; i < keyList.length; i++) {
+ var key = keyList[i];
+ mapping[key['spec'][SpecNodeName.ID]] = viewIdPrefix + i;
+ }
+ var layout = is102 ? '102kbd' : '101kbd';
+ var result = [];
+ result[SpecNodeName.KEY_LIST] = keyList;
+ result[SpecNodeName.MAPPING] = mapping;
+ result[SpecNodeName.LAYOUT] = layout;
+ result[SpecNodeName.HAS_ALTGR_KEY] = hasAltGrKey;
+ result[SpecNodeName.HAS_COMPACT_KEYBOARD] = !!opt_compactKeyboardId;
+ result[SpecNodeName.SHOW_MENU_KEY] = true;
+ return result;
+};
+
+
+/**
+ * Creates a switcher key which will switch between keyboards.
+ *
+ * @param {string} id The id.
+ * @param {string} name The name.
+ * @param {string|undefined} toKeyset The id of keyset this swicher key should
+ * switch to.
+ * @param {string} toKeysetName The name of the keyset.
+ * @param {string=} opt_iconCssClass The css class for the icon.
+ * @param {boolean=} opt_record True to record and next time the keyset will
+ * be recalled.
+ * @return {!Object} The switcher key.
+ */
+i18n.input.chrome.inputview.content.util.createSwitcherKey = function(
+ id, name, toKeyset, toKeysetName, opt_iconCssClass, opt_record) {
+ var spec = {};
+ spec[SpecNodeName.ID] = id;
+ spec[SpecNodeName.NAME] = name;
+ spec[SpecNodeName.TO_KEYSET] = toKeyset;
+ spec[SpecNodeName.TO_KEYSET_NAME] = toKeysetName;
+ spec[SpecNodeName.ICON_CSS_CLASS] = opt_iconCssClass;
+ spec[SpecNodeName.TYPE] = ElementType.SWITCHER_KEY;
+ spec[SpecNodeName.RECORD] = !!opt_record;
+ return i18n.input.chrome.inputview.content.util.createKey(spec);
+};
+
+
+/**
+ * Inserts modifier keys into the key list.
+ *
+ * @param {!Array.<!Object>} keyList The key list.
+ * @param {boolean} is102 True if it is a 102 keyboard.
+ * @param {string=} opt_compactKeyboardId The compact keyboard id.
+ * @private
+ */
+i18n.input.chrome.inputview.content.util.insertModifierKeys_ = function(
+ keyList, is102, opt_compactKeyboardId) {
+ goog.array.insertAt(keyList, i18n.input.chrome.inputview.content.util.
+ createBackspaceKey(), 13);
+ goog.array.insertAt(keyList, i18n.input.chrome.inputview.content.util.
+ createTabKey(), 14);
+ goog.array.insertAt(keyList, i18n.input.chrome.inputview.content.util.
+ createCapslockKey(), is102 ? 27 : 28);
+ goog.array.insertAt(keyList, i18n.input.chrome.inputview.content.util.
+ createEnterKey(), 40);
+ goog.array.insertAt(keyList, i18n.input.chrome.inputview.content.util.
+ createShiftKey(true), 41);
+ keyList.push(i18n.input.chrome.inputview.content.util.createShiftKey(false));
+ i18n.input.chrome.inputview.content.util.addLastRowKeys(
+ keyList, is102, opt_compactKeyboardId);
+};
+
+
+/**
+ * Inserts modifier keys into the key list.
+ *
+ * @param {!Array.<!Object>} keyList The key list.
+ * @param {boolean} is102 True if it is a 102 keyboard.
+ * @param {string=} opt_compactKeyboardId The compact keyboard id.
+ */
+i18n.input.chrome.inputview.content.util.addLastRowKeys =
+ function(keyList, is102, opt_compactKeyboardId) {
+ keyList.push(i18n.input.chrome.inputview.content.util.createGlobeKey());
+ keyList.push(i18n.input.chrome.inputview.content.util.createMenuKey(
+ opt_compactKeyboardId));
+ keyList.push(i18n.input.chrome.inputview.content.util.createCtrlKey());
+ keyList.push(i18n.input.chrome.inputview.content.util.createAltKey());
+ keyList.push(i18n.input.chrome.inputview.content.util.createSpaceKey());
+ keyList.push(i18n.input.chrome.inputview.content.util.createEnSwitcherKey());
+ keyList.push(i18n.input.chrome.inputview.content.util.createAltgrKey());
+ keyList.push(i18n.input.chrome.inputview.content.util.createArrowKey(
+ i18n.input.chrome.inputview.Direction.LEFT));
+ keyList.push(i18n.input.chrome.inputview.content.util.createArrowKey(
+ i18n.input.chrome.inputview.Direction.RIGHT));
+ keyList.push(i18n.input.chrome.inputview.content.util.
+ createHideKeyboardKey());
+};
+}); // goog.scope
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/controller.js b/third_party/google_input_tools/src/chrome/os/inputview/controller.js
new file mode 100644
index 0000000..27f305b
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/controller.js
@@ -0,0 +1,1658 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.Controller');
+
+goog.require('goog.Disposable');
+goog.require('goog.array');
+goog.require('goog.async.Delay');
+goog.require('goog.dom');
+goog.require('goog.dom.classlist');
+goog.require('goog.events.EventHandler');
+goog.require('goog.events.EventType');
+goog.require('goog.i18n.bidi');
+goog.require('goog.object');
+goog.require('i18n.input.chrome.DataSource');
+goog.require('i18n.input.chrome.inputview.Adapter');
+goog.require('i18n.input.chrome.inputview.CandidatesInfo');
+goog.require('i18n.input.chrome.inputview.ConditionName');
+goog.require('i18n.input.chrome.inputview.Css');
+goog.require('i18n.input.chrome.inputview.KeyboardContainer');
+goog.require('i18n.input.chrome.inputview.M17nModel');
+goog.require('i18n.input.chrome.inputview.Model');
+goog.require('i18n.input.chrome.inputview.PerfTracker');
+goog.require('i18n.input.chrome.inputview.ReadyState');
+goog.require('i18n.input.chrome.inputview.Settings');
+goog.require('i18n.input.chrome.inputview.SizeSpec');
+goog.require('i18n.input.chrome.inputview.SoundController');
+goog.require('i18n.input.chrome.inputview.SpecNodeName');
+goog.require('i18n.input.chrome.inputview.StateType');
+goog.require('i18n.input.chrome.inputview.Statistics');
+goog.require('i18n.input.chrome.inputview.SwipeDirection');
+goog.require('i18n.input.chrome.inputview.elements.ElementType');
+goog.require('i18n.input.chrome.inputview.elements.content.Candidate');
+goog.require('i18n.input.chrome.inputview.elements.content.CandidateView');
+goog.require('i18n.input.chrome.inputview.elements.content.ExpandedCandidateView');
+goog.require('i18n.input.chrome.inputview.elements.content.MenuView');
+goog.require('i18n.input.chrome.inputview.events.EventType');
+goog.require('i18n.input.chrome.inputview.events.KeyCodes');
+goog.require('i18n.input.chrome.inputview.handler.PointerHandler');
+goog.require('i18n.input.chrome.inputview.util');
+goog.require('i18n.input.chrome.message.ContextType');
+goog.require('i18n.input.chrome.message.Name');
+goog.require('i18n.input.chrome.message.Type');
+goog.require('i18n.input.lang.InputToolCode');
+
+
+
+goog.scope(function() {
+var CandidateType = i18n.input.chrome.inputview.elements.content.Candidate.Type;
+var Candidate = i18n.input.chrome.inputview.elements.content.Candidate;
+var CandidateView = i18n.input.chrome.inputview.elements.content.CandidateView;
+var ConditionName = i18n.input.chrome.inputview.ConditionName;
+var ContextType = i18n.input.chrome.message.ContextType;
+var Css = i18n.input.chrome.inputview.Css;
+var ElementType = i18n.input.chrome.inputview.elements.ElementType;
+var EventType = i18n.input.chrome.inputview.events.EventType;
+var ExpandedCandidateView = i18n.input.chrome.inputview.elements.content.
+ ExpandedCandidateView;
+var InputToolCode = i18n.input.lang.InputToolCode;
+var KeyCodes = i18n.input.chrome.inputview.events.KeyCodes;
+var MenuView = i18n.input.chrome.inputview.elements.content.MenuView;
+var Name = i18n.input.chrome.message.Name;
+var PerfTracker = i18n.input.chrome.inputview.PerfTracker;
+var SizeSpec = i18n.input.chrome.inputview.SizeSpec;
+var SpecNodeName = i18n.input.chrome.inputview.SpecNodeName;
+var StateType = i18n.input.chrome.inputview.StateType;
+var content = i18n.input.chrome.inputview.elements.content;
+var SoundController = i18n.input.chrome.inputview.SoundController;
+var Sounds = i18n.input.chrome.inputview.Sounds;
+var util = i18n.input.chrome.inputview.util;
+
+
+
+/**
+ * The controller of the input view keyboard.
+ *
+ * @param {string} keyset The keyboard keyset.
+ * @param {string} languageCode The language code for this keyboard.
+ * @param {string} passwordLayout The layout for password box.
+ * @param {string} name The input tool name.
+ * @constructor
+ * @extends {goog.Disposable}
+ */
+i18n.input.chrome.inputview.Controller = function(keyset, languageCode,
+ passwordLayout, name) {
+ /**
+ * The model.
+ *
+ * @type {!i18n.input.chrome.inputview.Model}
+ * @private
+ */
+ this.model_ = new i18n.input.chrome.inputview.Model();
+
+ /** @private {!i18n.input.chrome.inputview.PerfTracker} */
+ this.perfTracker_ = new i18n.input.chrome.inputview.PerfTracker();
+
+ /**
+ * The layout map.
+ *
+ * @type {!Object.<string, !Object>}
+ * @private
+ */
+ this.layoutDataMap_ = {};
+
+ /**
+ * The keyset data map.
+ *
+ * @type {!Object.<string, !Object>}
+ * @private
+ */
+ this.keysetDataMap_ = {};
+
+ /**
+ * The event handler.
+ *
+ * @type {!goog.events.EventHandler}
+ * @private
+ */
+ this.handler_ = new goog.events.EventHandler(this);
+
+ /**
+ * The m17n model.
+ *
+ * @type {!i18n.input.chrome.inputview.M17nModel}
+ * @private
+ */
+ this.m17nModel_ = new i18n.input.chrome.inputview.M17nModel();
+
+ /**
+ * The pointer handler.
+ *
+ * @type {!i18n.input.chrome.inputview.handler.PointerHandler}
+ * @private
+ */
+ this.pointerHandler_ = new i18n.input.chrome.inputview.handler.
+ PointerHandler();
+
+ /**
+ * The statistics object for recording metrics values.
+ *
+ * @type {!i18n.input.chrome.inputview.Statistics}
+ * @private
+ */
+ this.statistics_ = i18n.input.chrome.inputview.Statistics.getInstance();
+
+ /** @private {!i18n.input.chrome.inputview.ReadyState} */
+ this.readyState_ = new i18n.input.chrome.inputview.ReadyState();
+
+ /** @private {!i18n.input.chrome.inputview.Adapter} */
+ this.adapter_ = new i18n.input.chrome.inputview.Adapter(this.readyState_);
+
+ /** @private {!i18n.input.chrome.inputview.KeyboardContainer} */
+ this.container_ = new i18n.input.chrome.inputview.KeyboardContainer(
+ this.adapter_);
+ this.container_.render();
+
+ /** @private {!i18n.input.chrome.inputview.SoundController} */
+ this.soundController_ = new SoundController(false);
+
+ /**
+ * The context type and keyset mapping group by input method id.
+ * key: input method id.
+ * value: Object
+ * key: context type string.
+ * value: keyset string.
+ *
+ * @private {!Object.<string, !Object.<string, string>>}
+ */
+ this.contextTypeToKeysetMap_ = {};
+
+ this.initialize(keyset, languageCode, passwordLayout, name);
+ /**
+ * The suggestions.
+ * Note: sets a default empty result to avoid null check.
+ *
+ * @private {!i18n.input.chrome.inputview.CandidatesInfo}
+ */
+ this.candidatesInfo_ = i18n.input.chrome.inputview.CandidatesInfo.getEmpty();
+
+ this.registerEventHandler_();
+};
+goog.inherits(i18n.input.chrome.inputview.Controller,
+ goog.Disposable);
+var Controller = i18n.input.chrome.inputview.Controller;
+
+
+/**
+ * @define {boolean} Flag to disable handwriting.
+ */
+Controller.DISABLE_HWT = false;
+
+
+/**
+ * A flag to indicate whether the shift is for auto capital.
+ *
+ * @private {boolean}
+ */
+Controller.prototype.shiftForAutoCapital_ = false;
+
+
+/**
+ * @define {boolean} Flag to indicate whether it is debugging.
+ */
+Controller.DEV = false;
+
+
+/**
+ * The handwriting view code, use the code can switch handwriting panel view.
+ *
+ * @const {string}
+ * @private
+ */
+Controller.HANDWRITING_VIEW_CODE_ = 'hwt';
+
+
+/**
+ * The emoji view code, use the code can switch to emoji.
+ *
+ * @const {string}
+ * @private
+ */
+Controller.EMOJI_VIEW_CODE_ = 'emoji';
+
+
+/**
+ * The limitation for backspace repeat time to avoid unexpected
+ * problem that backspace is held all the time.
+ *
+ * @private {number}
+ */
+Controller.BACKSPACE_REPEAT_LIMIT_ = 255;
+
+
+/**
+ * The repeated times of the backspace.
+ *
+ * @private {number}
+ */
+Controller.prototype.backspaceRepeated_ = 0;
+
+
+/**
+ * The handwriting input tool code suffix.
+ *
+ * @const {string}
+ * @private
+ */
+Controller.HANDWRITING_CODE_SUFFIX_ = '-t-i0-handwrit';
+
+
+/**
+ * True if the settings is loaded.
+ *
+ * @type {boolean}
+ */
+Controller.prototype.isSettingReady = false;
+
+
+/**
+ * True if the keyboard is set up.
+ * Note: This flag is only used for automation testing.
+ *
+ * @type {boolean}
+ */
+Controller.prototype.isKeyboardReady = false;
+
+
+/**
+ * The auto repeat timer for backspace hold.
+ *
+ * @type {goog.async.Delay}
+ * @private
+ */
+Controller.prototype.backspaceAutoRepeat_;
+
+
+/**
+ * The active keyboard code.
+ *
+ * @type {string}
+ * @private
+ */
+Controller.prototype.currentKeyset_ = '';
+
+
+/**
+ * The current input method id.
+ *
+ * @private {string}
+ */
+Controller.prototype.currentInputmethod_ = '';
+
+
+/**
+ * The operations on candidates.
+ *
+ * @enum {number}
+ */
+Controller.CandidatesOperation = {
+ NONE: 0,
+ EXPAND: 1,
+ SHRINK: 2
+};
+
+
+/**
+ * The active language code.
+ *
+ * @type {string}
+ * @private
+ */
+Controller.prototype.lang_;
+
+
+/**
+ * The password keyset.
+ *
+ * @private {string}
+ */
+Controller.prototype.passwordKeyset_ = '';
+
+
+/**
+ * The soft key map, because key configuration is loaded before layout,
+ * controller needs this varaible to save it and hook into keyboard view.
+ *
+ * @type {!Array.<!content.SoftKey>}
+ * @private
+ */
+Controller.prototype.softKeyList_;
+
+
+/**
+ * The mapping from soft key id to soft key view id.
+ *
+ * @type {!Object.<string, string>}
+ * @private
+ */
+Controller.prototype.mapping_;
+
+
+/**
+ * The dead key.
+ *
+ * @type {string}
+ * @private
+ */
+Controller.prototype.deadKey_ = '';
+
+
+/**
+ * The input tool name.
+ *
+ * @type {string}
+ * @private
+ */
+Controller.prototype.title_;
+
+
+/**
+ * Registers event handlers.
+ * @private
+ */
+Controller.prototype.registerEventHandler_ = function() {
+ this.handler_.
+ listen(this.model_,
+ EventType.LAYOUT_LOADED,
+ this.onLayoutLoaded_).
+ listen(this.model_,
+ EventType.CONFIG_LOADED,
+ this.onConfigLoaded_).
+ listen(this.m17nModel_,
+ EventType.CONFIG_LOADED,
+ this.onConfigLoaded_).
+ listen(this.pointerHandler_, [
+ EventType.LONG_PRESS,
+ EventType.CLICK,
+ EventType.DOUBLE_CLICK,
+ EventType.DOUBLE_CLICK_END,
+ EventType.POINTER_UP,
+ EventType.POINTER_DOWN,
+ EventType.POINTER_OVER,
+ EventType.POINTER_OUT,
+ EventType.SWIPE
+ ], this.onPointerEvent_).
+ listen(window, goog.events.EventType.RESIZE, this.resize).
+ listen(this.adapter_,
+ i18n.input.chrome.inputview.events.EventType.
+ SURROUNDING_TEXT_CHANGED,
+ this.onSurroundingTextChanged_).
+ listen(this.adapter_,
+ i18n.input.chrome.DataSource.EventType.CANDIDATES_BACK,
+ this.onCandidatesBack_).
+ listen(this.adapter_,
+ i18n.input.chrome.inputview.events.EventType.CONTEXT_FOCUS,
+ this.onContextFocus_).
+ listen(this.adapter_,
+ i18n.input.chrome.inputview.events.EventType.CONTEXT_BLUR,
+ this.onContextBlur_).
+ listen(this.adapter_,
+ i18n.input.chrome.inputview.events.EventType.VISIBILITY_CHANGE,
+ this.onVisibilityChange_).
+ listen(this.adapter_,
+ i18n.input.chrome.inputview.events.EventType.SETTINGS_READY,
+ this.onSettingsReady_).
+ listen(this.adapter_,
+ i18n.input.chrome.message.Type.UPDATE_SETTINGS,
+ this.onUpdateSettings_);
+};
+
+
+/**
+ * Callback for updating settings.
+ *
+ * @param {!i18n.input.chrome.message.Event} e .
+ * @private
+ */
+Controller.prototype.onUpdateSettings_ = function(e) {
+ var settings = this.model_.settings;
+ if (goog.isDef(e.msg['autoSpace'])) {
+ settings.autoSpace = e.msg['autoSpace'];
+ }
+ if (goog.isDef(e.msg['autoCapital'])) {
+ settings.autoCapital = e.msg['autoCapital'];
+ }
+ if (goog.isDef(e.msg['candidatesNavigation'])) {
+ settings.candidatesNavigation = e.msg['candidatesNavigation'];
+ }
+ if (goog.isDef(e.msg['supportCompact'])) {
+ settings.supportCompact = e.msg['supportCompact'];
+ }
+ if (goog.isDef(e.msg[Name.KEYSET])) {
+ this.contextTypeToKeysetMap_[this.currentInputMethod_][
+ ContextType.DEFAULT] = e.msg[Name.KEYSET];
+ }
+ if (goog.isDef(e.msg['enableLongPress'])) {
+ settings.enableLongPress = e.msg['enableLongPress'];
+ }
+ if (goog.isDef(e.msg['doubleSpacePeriod'])) {
+ settings.doubleSpacePeriod = e.msg['doubleSpacePeriod'];
+ }
+ if (goog.isDef(e.msg['soundOnKeypress'])) {
+ settings.soundOnKeypress = e.msg['soundOnKeypress'];
+ this.soundController_.setEnabled(settings.soundOnKeypress);
+ }
+ this.perfTracker_.tick(PerfTracker.TickName.BACKGROUND_SETTINGS_FETCHED);
+ var isPassword = this.adapter_.isPasswordBox();
+ this.switchToKeySet(this.getActiveKeyset_());
+};
+
+
+/**
+ * Callback for setting ready.
+ *
+ * @private
+ */
+Controller.prototype.onSettingsReady_ = function() {
+ if (this.isSettingReady) {
+ return;
+ }
+
+ this.isSettingReady = true;
+ var keysetMap = this.contextTypeToKeysetMap_[this.currentInputMethod_];
+ if (this.adapter_.isA11yMode) {
+ keysetMap[ContextType.PASSWORD] = keysetMap[ContextType.DEFAULT] =
+ util.getConfigName(keysetMap[ContextType.DEFAULT]);
+ } else {
+ var preferredKeyset = /** @type {string} */ (this.model_.settings.
+ getPreference(util.getConfigName(
+ keysetMap[ContextType.DEFAULT])));
+ if (preferredKeyset) {
+ keysetMap[ContextType.PASSWORD] = keysetMap[ContextType.DEFAULT] =
+ preferredKeyset;
+ }
+ }
+ this.maybeCreateViews_();
+ this.switchToKeySet(this.getActiveKeyset_());
+};
+
+
+/**
+ * Gets the data for spatial module.
+ *
+ * @param {!content.SoftKey} key .
+ * @param {number} x The x-offset of the touch point.
+ * @param {number} y The y-offset of the touch point.
+ * @return {!Object} .
+ * @private
+ */
+Controller.prototype.getSpatialData_ = function(key, x, y) {
+ var items = [];
+ items.push([this.getKeyContent_(key), key.estimator.estimateInLogSpace(x, y)
+ ]);
+ for (var i = 0; i < key.nearbyKeys.length; i++) {
+ var nearByKey = key.nearbyKeys[i];
+ var content = this.getKeyContent_(nearByKey);
+ if (content && util.REGEX_LANGUAGE_MODEL_CHARACTERS.test(content)) {
+ items.push([content, nearByKey.estimator.estimateInLogSpace(x, y)]);
+ }
+ }
+ goog.array.sort(items, function(item1, item2) {
+ return item1[1] - item2[1];
+ });
+ var sources = items.map(function(item) {
+ return item[0].toLowerCase();
+ });
+ var possibilities = items.map(function(item) {
+ return item[1];
+ });
+ return {
+ 'sources': sources,
+ 'possibilities': possibilities
+ };
+};
+
+
+/**
+ * Gets the key content.
+ *
+ * @param {!content.SoftKey} key .
+ * @return {string} .
+ * @private
+ */
+Controller.prototype.getKeyContent_ = function(key) {
+ if (key.type == i18n.input.chrome.inputview.elements.ElementType.
+ CHARACTER_KEY) {
+ key = /** @type {!content.CharacterKey} */ (key);
+ return key.getActiveCharacter();
+ }
+ if (key.type == i18n.input.chrome.inputview.elements.ElementType.
+ COMPACT_KEY) {
+ key = /** @type {!content.FunctionalKey} */ (key);
+ return key.text;
+ }
+ return '';
+};
+
+
+/**
+ * Callback for pointer event.
+ *
+ * @param {!i18n.input.chrome.inputview.events.PointerEvent} e .
+ * @private
+ */
+Controller.prototype.onPointerEvent_ = function(e) {
+ if ((this.adapter_.isChromeVoxOn || !this.model_.settings.enableLongPress) &&
+ e.type == EventType.LONG_PRESS) {
+ return;
+ }
+
+ if (e.view) {
+ this.handlePointerAction_(e.view, e);
+ } else {
+ var tabbableKeysets = [
+ Controller.HANDWRITING_VIEW_CODE_,
+ Controller.EMOJI_VIEW_CODE_];
+ if (goog.array.contains(tabbableKeysets, this.currentKeyset_)) {
+ this.resetAll_();
+ this.switchToKeySet(this.container_.currentKeysetView.fromKeyset);
+ }
+ }
+};
+
+
+/**
+ * Handles the swipe action.
+ *
+ * @param {!i18n.input.chrome.inputview.elements.Element} view The view, for
+ * swipe event, the view would be the soft key which starts the swipe.
+ * @param {!i18n.input.chrome.inputview.events.SwipeEvent} e The swipe event.
+ * @private
+ */
+Controller.prototype.handleSwipeAction_ = function(view, e) {
+ var direction = e.direction;
+ if (this.container_.altDataView.isVisible()) {
+ this.container_.altDataView.highlightItem(e.x, e.y);
+ return;
+ }
+
+ if (view.type == ElementType.CHARACTER_KEY) {
+ view = /** @type {!content.CharacterKey} */ (view);
+ if (direction & i18n.input.chrome.inputview.SwipeDirection.UP ||
+ direction & i18n.input.chrome.inputview.SwipeDirection.DOWN) {
+ var ch = view.getCharacterByGesture(!!(direction &
+ i18n.input.chrome.inputview.SwipeDirection.UP));
+ if (ch) {
+ view.flickerredCharacter = ch;
+ }
+ }
+ }
+
+ if (view.type == ElementType.COMPACT_KEY) {
+ view = /** @type {!content.CompactKey} */ (view);
+ if ((direction & i18n.input.chrome.inputview.SwipeDirection.UP) &&
+ view.hintText) {
+ view.flickerredCharacter = view.hintText;
+ }
+ }
+};
+
+
+/**
+ * Execute a command.
+ *
+ * @param {!i18n.input.chrome.inputview.elements.content.MenuView.Command}
+ * command The command that about to be executed.
+ * @param {string=} opt_arg The optional command argument.
+ * @private
+ */
+Controller.prototype.executeCommand_ = function(command, opt_arg) {
+ var CommandEnum = MenuView.Command;
+ switch (command) {
+ case CommandEnum.SWITCH_IME:
+ var inputMethodId = opt_arg;
+ if (inputMethodId) {
+ this.adapter_.switchToInputMethod(inputMethodId);
+ }
+ break;
+
+ case CommandEnum.SWITCH_KEYSET:
+ var keyset = opt_arg;
+ if (keyset) {
+ this.statistics_.recordSwitch(keyset);
+ this.switchToKeySet(keyset);
+ }
+ break;
+ case CommandEnum.OPEN_EMOJI:
+ this.switchToKeySet(Controller.EMOJI_VIEW_CODE_);
+ break;
+
+ case CommandEnum.OPEN_HANDWRITING:
+ // TODO: remember handwriting keyset.
+ this.switchToKeySet(Controller.HANDWRITING_VIEW_CODE_);
+ break;
+
+ case CommandEnum.OPEN_SETTING:
+ if (window.inputview) {
+ inputview.openSettings();
+ }
+ break;
+ }
+};
+
+
+/**
+ * Handles the pointer action.
+ *
+ * @param {!i18n.input.chrome.inputview.elements.Element} view The view.
+ * @param {!i18n.input.chrome.inputview.events.PointerEvent} e .
+ * @private
+ */
+Controller.prototype.handlePointerAction_ = function(view, e) {
+ if (e.type == i18n.input.chrome.inputview.events.EventType.SWIPE) {
+ e = /** @type {!i18n.input.chrome.inputview.events.SwipeEvent} */ (e);
+ this.handleSwipeAction_(view, e);
+ }
+ switch (view.type) {
+ case ElementType.BACK_BUTTON:
+ if (e.type == EventType.POINTER_UP) {
+ this.switchToKeySet(this.container_.currentKeysetView.fromKeyset);
+ }
+ return;
+ case ElementType.EXPAND_CANDIDATES:
+ if (e.type == EventType.POINTER_UP) {
+ this.showCandidates_(this.candidatesInfo_.source,
+ this.candidatesInfo_.candidates,
+ Controller.CandidatesOperation.EXPAND);
+ }
+ return;
+ case ElementType.SHRINK_CANDIDATES:
+ if (e.type == EventType.POINTER_UP) {
+ this.showCandidates_(this.candidatesInfo_.source,
+ this.candidatesInfo_.candidates,
+ Controller.CandidatesOperation.SHRINK);
+ }
+ return;
+ case ElementType.CANDIDATE:
+ view = /** @type {!Candidate} */ (view);
+ if (e.type == EventType.POINTER_UP) {
+ if (view.candidateType == CandidateType.CANDIDATE) {
+ this.adapter_.selectCandidate(view.candidate);
+ } else if (view.candidateType == CandidateType.NUMBER) {
+ this.adapter_.commitText(view.candidate[Name.CANDIDATE]);
+ }
+ this.container_.cleanStroke();
+ }
+ if (e.type == EventType.POINTER_OUT || e.type == EventType.POINTER_UP) {
+ view.setHighlighted(false);
+ } else if (e.type == EventType.POINTER_DOWN ||
+ e.type == EventType.POINTER_OVER) {
+ view.setHighlighted(true);
+ }
+ return;
+
+ case ElementType.ALTDATA_VIEW:
+ view = /** @type {!content.AltDataView} */ (view);
+ if (e.type == EventType.POINTER_DOWN &&
+ e.target == view.getCoverElement()) {
+ view.hide();
+ } else if (e.type == EventType.POINTER_UP) {
+ var ch = view.getHighlightedCharacter();
+ this.adapter_.sendKeyDownAndUpEvent(ch, view.triggeredBy.id,
+ view.triggeredBy.keyCode,
+ {'sources': [ch.toLowerCase()], 'possibilities': [1]});
+ view.hide();
+ this.clearUnstickyState_();
+ }
+ return;
+
+ case ElementType.MENU_ITEM:
+ view = /** @type {!content.MenuItem} */ (view);
+ if (e.type == EventType.CLICK) {
+ this.resetAll_();
+ this.executeCommand_.apply(this, view.getCommand());
+ this.container_.menuView.hide();
+ }
+ view.setHighlighted(e.type == EventType.POINTER_DOWN ||
+ e.type == EventType.POINTER_OVER);
+ // TODO: Add chrome vox support.
+ return;
+
+ case ElementType.MENU_VIEW:
+ view = /** @type {!MenuView} */ (view);
+
+ if (e.type == EventType.POINTER_DOWN &&
+ e.target == view.getCoverElement()) {
+ view.hide();
+ }
+ return;
+
+ case ElementType.EMOJI_KEY:
+ if (e.type == EventType.POINTER_UP) {
+ if (!this.container_.currentKeysetView.isDragging && view.text != '') {
+ this.adapter_.commitText(view.text);
+ }
+ }
+ return;
+
+ case ElementType.HWT_PRIVACY_GOT_IT:
+ this.adapter_.sendHwtPrivacyConfirmMessage();
+ return;
+
+ case ElementType.SOFT_KEY_VIEW:
+ // Delegates the events on the soft key view to its soft key.
+ view = /** @type {!i18n.input.chrome.inputview.elements.layout.
+ SoftKeyView} */ (view);
+ if (!view.softKey) {
+ return;
+ }
+ view = view.softKey;
+ }
+
+ if (view.type != ElementType.MODIFIER_KEY &&
+ !this.container_.altDataView.isVisible() &&
+ !this.container_.menuView.isVisible()) {
+ // The highlight of the modifier key is depending on the state instead
+ // of the key down or up.
+ if (e.type == EventType.POINTER_OVER || e.type == EventType.POINTER_DOWN ||
+ e.type == EventType.DOUBLE_CLICK) {
+ view.setHighlighted(true);
+ } else if (e.type == EventType.POINTER_OUT ||
+ e.type == EventType.POINTER_UP ||
+ e.type == EventType.DOUBLE_CLICK_END) {
+ view.setHighlighted(false);
+ }
+ }
+ this.handlePointerEventForSoftKey_(
+ /** @type {!content.SoftKey} */ (view), e);
+ this.updateContextModifierState_();
+};
+
+
+/**
+ * Handles softkey of the pointer action.
+ *
+ * @param {!content.SoftKey} softKey .
+ * @param {!i18n.input.chrome.inputview.events.PointerEvent} e .
+ * @private
+ */
+Controller.prototype.handlePointerEventForSoftKey_ = function(softKey, e) {
+ var key;
+ switch (softKey.type) {
+ case ElementType.CANDIDATES_PAGE_UP:
+ if (e.type == EventType.POINTER_UP) {
+ this.container_.expandedCandidateView.pageUp();
+ }
+ break;
+ case ElementType.CANDIDATES_PAGE_DOWN:
+ if (e.type == EventType.POINTER_UP) {
+ this.container_.expandedCandidateView.pageDown();
+ }
+ break;
+ case ElementType.CHARACTER_KEY:
+ key = /** @type {!content.CharacterKey} */ (softKey);
+ if (e.type == EventType.LONG_PRESS) {
+ this.container_.altDataView.show(
+ key, goog.i18n.bidi.isRtlLanguage(this.languageCode_));
+ } else if (e.type == EventType.POINTER_UP) {
+ this.model_.stateManager.triggerChording();
+ var ch = key.getActiveCharacter();
+ this.adapter_.sendKeyDownAndUpEvent(ch, key.id, key.keyCode,
+ this.getSpatialData_(key, e.x, e.y));
+ this.clearUnstickyState_();
+ key.flickerredCharacter = '';
+ }
+ break;
+
+ case ElementType.MODIFIER_KEY:
+ key = /** @type {!content.ModifierKey} */ (softKey);
+ var isStateEnabled = this.model_.stateManager.hasState(key.toState);
+ var isChording = this.model_.stateManager.isChording(key.toState);
+ if (e.type == EventType.POINTER_DOWN) {
+ this.changeState_(key.toState, !isStateEnabled, true);
+ this.model_.stateManager.setKeyDown(key.toState, true);
+ } else if (e.type == EventType.POINTER_UP || e.type == EventType.
+ POINTER_OUT) {
+ if (isChording) {
+ this.changeState_(key.toState, false, false);
+ } else if (key.toState != StateType.CAPSLOCK &&
+ this.model_.stateManager.isKeyDown(key.toState)) {
+ this.changeState_(key.toState, isStateEnabled, false);
+ }
+ this.model_.stateManager.setKeyDown(key.toState, false);
+ } else if (e.type == EventType.DOUBLE_CLICK) {
+ this.changeState_(key.toState, isStateEnabled, true);
+ } else if (e.type == EventType.LONG_PRESS) {
+ if (!isChording) {
+ this.changeState_(key.toState, true, true);
+ this.model_.stateManager.setKeyDown(key.toState, false);
+ }
+ }
+ break;
+
+ case ElementType.BACKSPACE_KEY:
+ key = /** @type {!content.FunctionalKey} */ (softKey);
+ if (e.type == EventType.POINTER_DOWN) {
+ this.backspaceTick_();
+ } else if (e.type == EventType.POINTER_UP || e.type == EventType.
+ POINTER_OUT) {
+ this.stopBackspaceAutoRepeat_();
+ this.adapter_.sendKeyUpEvent('\u0008', KeyCodes.BACKSPACE);
+ }
+ break;
+
+ case ElementType.TAB_KEY:
+ key = /** @type {!content.FunctionalKey} */ (softKey);
+ if (e.type == EventType.POINTER_DOWN) {
+ this.adapter_.sendKeyDownEvent('\u0009', KeyCodes.TAB);
+ } else if (e.type == EventType.POINTER_UP) {
+ this.adapter_.sendKeyUpEvent('\u0009', KeyCodes.TAB);
+ }
+ break;
+
+ case ElementType.ENTER_KEY:
+ key = /** @type {!content.FunctionalKey} */ (softKey);
+ if (e.type == EventType.POINTER_DOWN) {
+ this.adapter_.sendKeyDownEvent('\u000D', KeyCodes.ENTER);
+ } else if (e.type == EventType.POINTER_UP) {
+ this.adapter_.sendKeyUpEvent('\u000D', KeyCodes.ENTER);
+ }
+ break;
+
+ case ElementType.ARROW_UP:
+ if (e.type == EventType.POINTER_DOWN) {
+ this.adapter_.sendKeyDownEvent('', KeyCodes.ARROW_UP);
+ } else if (e.type == EventType.POINTER_UP) {
+ this.adapter_.sendKeyUpEvent('', KeyCodes.ARROW_UP);
+ }
+ break;
+
+ case ElementType.ARROW_DOWN:
+ if (e.type == EventType.POINTER_DOWN) {
+ this.adapter_.sendKeyDownEvent('', KeyCodes.ARROW_DOWN);
+ } else if (e.type == EventType.POINTER_UP) {
+ this.adapter_.sendKeyUpEvent('', KeyCodes.ARROW_DOWN);
+ }
+ break;
+
+ case ElementType.ARROW_LEFT:
+ if (e.type == EventType.POINTER_DOWN) {
+ this.adapter_.sendKeyDownEvent('', KeyCodes.ARROW_LEFT);
+ } else if (e.type == EventType.POINTER_UP) {
+ this.adapter_.sendKeyUpEvent('', KeyCodes.ARROW_LEFT);
+ }
+ break;
+
+ case ElementType.ARROW_RIGHT:
+ if (e.type == EventType.POINTER_DOWN) {
+ this.adapter_.sendKeyDownEvent('', KeyCodes.ARROW_RIGHT);
+ } else if (e.type == EventType.POINTER_UP) {
+ this.adapter_.sendKeyUpEvent('', KeyCodes.ARROW_RIGHT);
+ }
+ break;
+ case ElementType.EN_SWITCHER:
+ if (e.type == EventType.POINTER_UP) {
+ key = /** @type {!content.EnSwitcherKey} */ (softKey);
+ this.adapter_.toggleLanguageState(this.model_.stateManager.isEnMode);
+ this.model_.stateManager.isEnMode = !this.model_.stateManager.isEnMode;
+ key.update();
+ }
+ break;
+ case ElementType.SPACE_KEY:
+ key = /** @type {!content.SpaceKey} */ (softKey);
+ var doubleSpacePeriod = this.model_.settings.doubleSpacePeriod;
+ if (e.type == EventType.POINTER_UP || (!doubleSpacePeriod && e.type ==
+ EventType.DOUBLE_CLICK_END)) {
+ this.adapter_.sendKeyDownAndUpEvent(key.getCharacter(),
+ KeyCodes.SPACE);
+ this.clearUnstickyState_();
+ } else if (e.type == EventType.DOUBLE_CLICK && doubleSpacePeriod) {
+ this.adapter_.doubleClickOnSpaceKey();
+ }
+ break;
+
+ case ElementType.SWITCHER_KEY:
+ key = /** @type {!content.SwitcherKey} */ (softKey);
+ if (e.type == EventType.POINTER_UP) {
+ this.statistics_.recordSwitch(key.toKeyset);
+ if (this.isSubKeyset_(key.toKeyset, this.currentKeyset_)) {
+ this.model_.stateManager.reset();
+ this.container_.update();
+ this.updateContextModifierState_();
+ this.container_.menuView.hide();
+ } else {
+ this.resetAll_();
+ }
+ // Switch to the specific keyboard.
+ this.switchToKeySet(key.toKeyset);
+ if (key.record) {
+ this.model_.settings.savePreference(
+ util.getConfigName(key.toKeyset),
+ key.toKeyset);
+ }
+ }
+ break;
+
+ case ElementType.COMPACT_KEY:
+ key = /** @type {!content.CompactKey} */ (softKey);
+ if (e.type == EventType.LONG_PRESS) {
+ this.container_.altDataView.show(
+ key, goog.i18n.bidi.isRtlLanguage(this.languageCode_));
+ } else if (e.type == EventType.POINTER_UP) {
+ this.model_.stateManager.triggerChording();
+ this.adapter_.sendKeyDownAndUpEvent(key.getActiveCharacter(), '', 0,
+ this.getSpatialData_(key, e.x, e.y));
+ this.clearUnstickyState_();
+ key.flickerredCharacter = '';
+ }
+ break;
+
+ case ElementType.HIDE_KEYBOARD_KEY:
+ if (e.type == EventType.POINTER_UP) {
+ this.adapter_.hideKeyboard();
+ }
+ break;
+
+ case ElementType.MENU_KEY:
+ key = /** @type {!content.MenuKey} */ (softKey);
+ if (e.type == EventType.POINTER_DOWN) {
+ var isCompact = this.currentKeyset_.indexOf('compact') != -1;
+ var remappedKeyset = this.getRemappedKeyset_(this.currentKeyset_);
+ var keysetData = this.keysetDataMap_[remappedKeyset];
+ var enableCompact = !this.adapter_.isA11yMode &&
+ !!keysetData[SpecNodeName.HAS_COMPACT_KEYBOARD] &&
+ this.model_.settings.supportCompact;
+ var self = this;
+ var currentKeyset = this.currentKeyset_;
+ var hasHwt = !this.adapter_.isPasswordBox() &&
+ !Controller.DISABLE_HWT && goog.object.contains(
+ InputToolCode, this.getHwtInputToolCode_());
+ var enableSettings = this.shouldEnableSettings() &&
+ window.inputview && inputview.openSettings;
+ this.adapter_.getInputMethods(function(inputMethods) {
+ this.container_.menuView.show(key, currentKeyset, isCompact,
+ enableCompact, this.currentInputMethod_, inputMethods, hasHwt,
+ enableSettings, this.adapter_.isExperimental);
+ }.bind(this));
+ }
+ break;
+
+ case ElementType.GLOBE_KEY:
+ if (e.type == EventType.POINTER_UP) {
+ this.adapter_.clearModifierStates();
+ this.adapter_.setModifierState(
+ i18n.input.chrome.inputview.StateType.CTRL, true);
+ this.adapter_.sendKeyDownAndUpEvent(' ', KeyCodes.SPACE, 0x20);
+ this.adapter_.setModifierState(
+ i18n.input.chrome.inputview.StateType.CTRL, false);
+ }
+ break;
+ case ElementType.IME_SWITCH:
+ key = /** @type {!content.FunctionalKey} */ (softKey);
+ this.adapter_.sendKeyDownAndUpEvent('', key.id);
+ break;
+ }
+ // Play key sound on pointer up.
+ if (e.type == EventType.POINTER_UP)
+ this.soundController_.onKeyUp(softKey.type);
+};
+
+
+/**
+ * Clears unsticky state.
+ *
+ * @private
+ */
+Controller.prototype.clearUnstickyState_ = function() {
+ if (this.model_.stateManager.hasUnStickyState()) {
+ for (var key in StateType) {
+ var value = StateType[key];
+ if (this.model_.stateManager.hasState(value) &&
+ !this.model_.stateManager.isSticky(value)) {
+ this.changeState_(value, false, false);
+ }
+ }
+ }
+ this.container_.update();
+};
+
+
+/**
+ * Stops the auto-repeat for backspace.
+ *
+ * @private
+ */
+Controller.prototype.stopBackspaceAutoRepeat_ = function() {
+ goog.dispose(this.backspaceAutoRepeat_);
+ this.backspaceAutoRepeat_ = null;
+ this.adapter_.sendKeyUpEvent('\u0008', KeyCodes.BACKSPACE);
+ this.backspaceRepeated_ = 0;
+};
+
+
+/**
+ * The tick for the backspace key.
+ *
+ * @private
+ */
+Controller.prototype.backspaceTick_ = function() {
+ if (this.backspaceRepeated_ >= Controller.BACKSPACE_REPEAT_LIMIT_) {
+ this.stopBackspaceAutoRepeat_();
+ return;
+ }
+ this.backspaceRepeated_++;
+ this.backspaceDown_();
+ this.soundController_.onKeyRepeat(ElementType.BACKSPACE_KEY);
+
+ if (this.backspaceAutoRepeat_) {
+ this.backspaceAutoRepeat_.start(75);
+ } else {
+ this.backspaceAutoRepeat_ = new goog.async.Delay(
+ goog.bind(this.backspaceTick_, this), 300);
+ this.backspaceAutoRepeat_.start();
+ }
+};
+
+
+/**
+ * Callback for VISIBILITY_CHANGE.
+ *
+ * @private
+ */
+Controller.prototype.onVisibilityChange_ = function() {
+ if (!this.adapter_.isVisible) {
+ this.resetAll_();
+ }
+};
+
+
+/**
+ * Resets the whole keyboard include clearing candidates,
+ * reset modifier state, etc.
+ *
+ * @private
+ */
+Controller.prototype.resetAll_ = function() {
+ this.clearCandidates_();
+ this.container_.candidateView.hideNumberRow();
+ this.model_.stateManager.reset();
+ this.container_.update();
+ this.updateContextModifierState_();
+ this.deadKey_ = '';
+ this.resize();
+ this.container_.expandedCandidateView.close();
+ this.container_.menuView.hide();
+};
+
+
+/**
+ * Callback when the context is changed.
+ *
+ * @private
+ */
+Controller.prototype.onContextFocus_ = function() {
+ this.resetAll_();
+ this.switchToKeySet(this.getActiveKeyset_());
+};
+
+
+/**
+ * Callback when surrounding text is changed.
+ *
+ * @param {!i18n.input.chrome.inputview.events.SurroundingTextChangedEvent} e .
+ * @private
+ */
+Controller.prototype.onSurroundingTextChanged_ = function(e) {
+ if (!this.model_.settings.autoCapital || !e.text) {
+ return;
+ }
+
+ var isShiftEnabled = this.model_.stateManager.hasState(StateType.SHIFT);
+ var needAutoCap = this.model_.settings.autoCapital &&
+ util.needAutoCap(e.text);
+ if (needAutoCap && !isShiftEnabled) {
+ this.changeState_(StateType.SHIFT, true, false);
+ this.shiftForAutoCapital_ = true;
+ } else if (!needAutoCap && this.shiftForAutoCapital_) {
+ this.changeState_(StateType.SHIFT, false, false);
+ }
+};
+
+
+/**
+ * Callback for Context blurs.
+ *
+ * @private
+ */
+Controller.prototype.onContextBlur_ = function() {
+ this.clearCandidates_();
+ this.deadKey_ = '';
+ this.container_.menuView.hide();
+};
+
+
+/**
+ * Backspace key is down.
+ *
+ * @private
+ */
+Controller.prototype.backspaceDown_ = function() {
+ if (this.container_.hasStrokesOnCanvas()) {
+ this.clearCandidates_();
+ this.container_.cleanStroke();
+ } else {
+ this.adapter_.sendKeyDownEvent('\u0008', KeyCodes.BACKSPACE);
+ }
+};
+
+
+/**
+ * Callback for state change.
+ *
+ * @param {StateType} stateType The state type.
+ * @param {boolean} enable True to enable the state.
+ * @param {boolean} isSticky True to make the state sticky.
+ * @private
+ */
+Controller.prototype.changeState_ = function(stateType, enable, isSticky) {
+ if (stateType == StateType.ALTGR) {
+ var code = KeyCodes.ALT_RIGHT;
+ if (enable) {
+ this.adapter_.sendKeyDownEvent('', code);
+ } else {
+ this.adapter_.sendKeyUpEvent('', code);
+ }
+ }
+ if (stateType == StateType.SHIFT) {
+ this.shiftForAutoCapital_ = false;
+ }
+ var isEnabledBefore = this.model_.stateManager.hasState(stateType);
+ var isStickyBefore = this.model_.stateManager.isSticky(stateType);
+ this.model_.stateManager.setState(stateType, enable);
+ this.model_.stateManager.setSticky(stateType, isSticky);
+ if (isEnabledBefore != enable || isStickyBefore != isSticky) {
+ this.container_.update();
+ }
+};
+
+
+/**
+ * Updates the modifier state for context.
+ *
+ * @private
+ */
+Controller.prototype.updateContextModifierState_ = function() {
+ var stateManager = this.model_.stateManager;
+ this.adapter_.setModifierState(StateType.ALT,
+ stateManager.hasState(StateType.ALT));
+ this.adapter_.setModifierState(StateType.CTRL,
+ stateManager.hasState(StateType.CTRL));
+ this.adapter_.setModifierState(StateType.CAPSLOCK,
+ stateManager.hasState(StateType.CAPSLOCK));
+ if (!this.shiftForAutoCapital_) {
+ // If shift key is automatically on because of feature - autoCapital,
+ // Don't set modifier state to adapter.
+ this.adapter_.setModifierState(StateType.SHIFT,
+ stateManager.hasState(StateType.SHIFT));
+ }
+};
+
+
+/**
+ * Callback for AUTO-COMPLETE event.
+ *
+ * @param {!i18n.input.chrome.DataSource.CandidatesBackEvent} e .
+ * @private
+ */
+Controller.prototype.onCandidatesBack_ = function(e) {
+ this.candidatesInfo_ = new i18n.input.chrome.inputview.CandidatesInfo(
+ e.source, e.candidates);
+ this.showCandidates_(e.source, e.candidates, Controller.CandidatesOperation.
+ NONE);
+};
+
+
+/**
+ * Shows the candidates to the candidate view.
+ *
+ * @param {string} source The source text.
+ * @param {!Array.<!Object>} candidates The candidate text list.
+ * @param {Controller.CandidatesOperation} operation .
+ * @private
+ */
+Controller.prototype.showCandidates_ = function(source, candidates,
+ operation) {
+ var state = !!source ? ExpandedCandidateView.State.COMPLETION_CORRECTION :
+ ExpandedCandidateView.State.PREDICTION;
+ var expandView = this.container_.expandedCandidateView;
+ var expand = false;
+ if (operation == Controller.CandidatesOperation.NONE) {
+ expand = expandView.state == state;
+ } else {
+ expand = operation == Controller.CandidatesOperation.EXPAND;
+ }
+
+ if (candidates.length == 0) {
+ this.clearCandidates_();
+ expandView.state = ExpandedCandidateView.State.NONE;
+ return;
+ }
+
+ // The compact pinyin needs full candidates instead of three candidates.
+ var isThreeCandidates = this.currentKeyset_.indexOf('compact') != -1 &&
+ this.currentKeyset_.indexOf('pinyin-zh-CN') == -1;
+ if (isThreeCandidates) {
+ if (candidates.length > 1) {
+ // Swap the first candidate and the second candidate.
+ var tmp = candidates[0];
+ candidates[0] = candidates[1];
+ candidates[1] = tmp;
+ }
+ }
+ var isHwt = Controller.HANDWRITING_VIEW_CODE_ == this.currentKeyset_;
+ this.container_.candidateView.showCandidates(candidates, isThreeCandidates,
+ this.model_.settings.candidatesNavigation && !isHwt);
+
+ if (expand) {
+ expandView.state = state;
+ this.container_.currentKeysetView.setVisible(false);
+ expandView.showCandidates(candidates,
+ this.container_.candidateView.candidateCount);
+ this.container_.candidateView.switchToIcon(CandidateView.IconType.
+ SHRINK_CANDIDATES, true);
+ } else {
+ expandView.state = ExpandedCandidateView.State.NONE;
+ expandView.setVisible(false);
+ this.container_.currentKeysetView.setVisible(true);
+ }
+};
+
+
+/**
+ * Clears candidates.
+ *
+ * @private
+ */
+Controller.prototype.clearCandidates_ = function() {
+ this.candidatesInfo_ = i18n.input.chrome.inputview.CandidatesInfo.getEmpty();
+ this.container_.candidateView.clearCandidates();
+ this.container_.expandedCandidateView.close();
+ this.container_.expandedCandidateView.state = ExpandedCandidateView.State.
+ NONE;
+ if (this.container_.currentKeysetView) {
+ this.container_.currentKeysetView.setVisible(true);
+ }
+ this.container_.candidateView.switchToIcon(CandidateView.IconType.BACK,
+ Controller.HANDWRITING_VIEW_CODE_ == this.currentKeyset_);
+};
+
+
+/**
+ * Callback when the layout is loaded.
+ *
+ * @param {!i18n.input.chrome.inputview.events.LayoutLoadedEvent} e The event.
+ * @private
+ */
+Controller.prototype.onLayoutLoaded_ = function(e) {
+ var layoutID = e.data['layoutID'];
+ this.layoutDataMap_[layoutID] = e.data;
+ this.perfTracker_.tick(PerfTracker.TickName.LAYOUT_LOADED);
+ this.maybeCreateViews_();
+};
+
+
+/**
+ * Creates the whole view.
+ *
+ * @private
+ */
+Controller.prototype.maybeCreateViews_ = function() {
+ if (!this.isSettingReady) {
+ return;
+ }
+
+ for (var keyset in this.keysetDataMap_) {
+ var keysetData = this.keysetDataMap_[keyset];
+ var layoutId = keysetData[SpecNodeName.LAYOUT];
+ var layoutData = this.layoutDataMap_[layoutId];
+ if (!this.container_.keysetViewMap[keyset] && layoutData) {
+ var conditions = {};
+ conditions[ConditionName.SHOW_ALTGR] =
+ keysetData[SpecNodeName.HAS_ALTGR_KEY];
+
+ conditions[ConditionName.SHOW_MENU] =
+ keysetData[SpecNodeName.SHOW_MENU_KEY];
+ // In symbol and more keysets, we want to show a symbol key in the globe
+ // SoftKeyView. So this view should alway visible in the two keysets.
+ // Currently, SHOW_MENU_KEY is false for the two keysets, so we use
+ // !keysetData[SpecNodeName.SHOW_MENU_KEY] here.
+ conditions[ConditionName.SHOW_GLOBE_OR_SYMBOL] =
+ !keysetData[SpecNodeName.SHOW_MENU_KEY] ||
+ this.adapter_.showGlobeKey;
+ conditions[ConditionName.SHOW_EN_SWITCHER_KEY] = false;
+
+ // If the view for the keyboard code doesn't exist, and the layout
+ // data is ready, then creates the view.
+ this.container_.addKeysetView(keysetData, layoutData, keyset,
+ this.languageCode_, this.model_, this.title_, conditions);
+ this.perfTracker_.tick(PerfTracker.TickName.KEYBOARD_CREATED);
+ }
+ }
+ this.switchToKeySet(this.getActiveKeyset_());
+};
+
+
+/**
+ * Switch to a specific keyboard.
+ *
+ * @param {string} keyset The keyset name.
+ */
+Controller.prototype.switchToKeySet = function(keyset) {
+ if (!this.isSettingReady) {
+ return;
+ }
+
+ var lastKeysetView = this.container_.currentKeysetView;
+ var ret = this.container_.switchToKeyset(this.getRemappedKeyset_(keyset),
+ this.title_, this.adapter_.isPasswordBox(), this.adapter_.isA11yMode,
+ keyset, this.currentKeyset_, this.languageCode_);
+
+ // Update the keyset of current context type.
+ this.contextTypeToKeysetMap_[this.currentInputMethod_][
+ this.adapter_.getContextType()] = keyset;
+
+ if (ret) {
+ this.updateLanguageState_(this.currentKeyset_, keyset);
+ // Deactivate the last keyset view instance.
+ if (lastKeysetView) {
+ lastKeysetView.deactivate(this.currentKeyset_);
+ }
+ this.currentKeyset_ = keyset;
+
+ this.resize(Controller.DEV);
+ this.statistics_.setCurrentLayout(keyset);
+ // Activate the current key set view instance.
+ this.container_.currentKeysetView.activate(keyset);
+ this.perfTracker_.tick(PerfTracker.TickName.KEYBOARD_SHOWN);
+ this.perfTracker_.stop();
+ } else {
+ this.loadResource_(keyset);
+ }
+};
+
+
+/**
+ * Callback when the configuration is loaded.
+ *
+ * @param {!i18n.input.chrome.inputview.events.ConfigLoadedEvent} e The event.
+ * @private
+ */
+Controller.prototype.onConfigLoaded_ = function(e) {
+ var data = e.data;
+ var keyboardCode = data[i18n.input.chrome.inputview.SpecNodeName.ID];
+ this.keysetDataMap_[keyboardCode] = data;
+ this.perfTracker_.tick(PerfTracker.TickName.KEYSET_LOADED);
+ var context = data[i18n.input.chrome.inputview.SpecNodeName.ON_CONTEXT];
+ if (context && !this.adapter_.isA11yMode) {
+ var keySetMap = this.contextTypeToKeysetMap_[this.currentInputMethod_];
+ if (!keySetMap) {
+ keySetMap = this.contextTypeToKeysetMap_[this.currentInputMethod_] = {};
+ }
+ keySetMap[context] = keyboardCode;
+ }
+
+ var layoutId = data[i18n.input.chrome.inputview.SpecNodeName.LAYOUT];
+ var layoutData = this.layoutDataMap_[layoutId];
+ if (layoutData) {
+ this.maybeCreateViews_();
+ } else {
+ this.model_.loadLayout(data[i18n.input.chrome.inputview.SpecNodeName.
+ LAYOUT]);
+ }
+};
+
+
+/**
+ * Resizes the whole UI.
+ *
+ * @param {boolean=} opt_ignoreWindowResize .
+ */
+Controller.prototype.resize = function(opt_ignoreWindowResize) {
+ var height;
+ var widthPercent;
+ var candidateViewHeight;
+ var isHorizontal = screen.width > screen.height;
+ var isWideScreen = (Math.min(screen.width, screen.height) / Math.max(
+ screen.width, screen.height)) < 0.6;
+ this.model_.stateManager.covariance.update(isWideScreen, isHorizontal,
+ this.adapter_.isA11yMode);
+ if (this.adapter_.isA11yMode) {
+ height = SizeSpec.A11Y_HEIGHT;
+ widthPercent = screen.width > screen.height ? SizeSpec.A11Y_WIDTH_PERCENT.
+ HORIZONTAL : SizeSpec.A11Y_WIDTH_PERCENT.VERTICAL;
+ candidateViewHeight = SizeSpec.A11Y_CANDIDATE_VIEW_HEIGHT;
+ } else {
+ var keyset = this.keysetDataMap_[this.currentKeyset_];
+ var layout = keyset && keyset[SpecNodeName.LAYOUT];
+ var data = layout && this.layoutDataMap_[layout];
+ var spec = data && data[SpecNodeName.WIDTH_PERCENT] ||
+ SizeSpec.NON_A11Y_WIDTH_PERCENT;
+ height = SizeSpec.NON_A11Y_HEIGHT;
+ if (isHorizontal) {
+ if (isWideScreen) {
+ widthPercent = spec.HORIZONTAL_WIDE_SCREEN;
+ } else {
+ widthPercent = spec.HORIZONTAL;
+ }
+ } else {
+ widthPercent = spec.VERTICAL;
+ }
+ candidateViewHeight = SizeSpec.NON_A11Y_CANDIDATE_VIEW_HEIGHT;
+ }
+
+ var viewportSize = goog.dom.getViewportSize();
+ if (viewportSize.height != height && !opt_ignoreWindowResize) {
+ window.resizeTo(screen.width, height);
+ return;
+ }
+
+ this.container_.resize(screen.width, height, widthPercent,
+ candidateViewHeight);
+ if (this.container_.currentKeysetView) {
+ this.isKeyboardReady = true;
+ }
+};
+
+
+/**
+ * Loads the resources, for currentKeyset, passwdKeyset, handwriting,
+ * emoji, etc.
+ *
+ * @private
+ */
+Controller.prototype.loadAllResources_ = function() {
+ var keysetMap = this.contextTypeToKeysetMap_[this.currentInputMethod_];
+ goog.array.forEach([keysetMap[ContextType.DEFAULT],
+ Controller.HANDWRITING_VIEW_CODE_,
+ Controller.EMOJI_VIEW_CODE_,
+ keysetMap[ContextType.PASSWORD]], function(keyset) {
+ this.loadResource_(keyset);
+ }, this);
+};
+
+
+/**
+ * Gets the remapped keyset.
+ *
+ * @param {string} keyset .
+ * @return {string} The remapped keyset.
+ * @private
+ */
+Controller.prototype.getRemappedKeyset_ = function(keyset) {
+ if (goog.array.contains(util.KEYSETS_USE_US, keyset)) {
+ return 'us';
+ }
+ return keyset;
+};
+
+
+/**
+ * Loads a single resource.
+ *
+ * @param {string} keyset .
+ * loaded.
+ * @private
+ */
+Controller.prototype.loadResource_ = function(keyset) {
+ var remapped = this.getRemappedKeyset_(keyset);
+ if (!this.keysetDataMap_[remapped]) {
+ if (/^m17n:/.test(remapped)) {
+ this.m17nModel_.loadConfig(remapped);
+ } else {
+ this.model_.loadConfig(remapped);
+ }
+ return;
+ }
+
+ var layoutId = this.keysetDataMap_[remapped][SpecNodeName.LAYOUT];
+ if (!this.layoutDataMap_[layoutId]) {
+ this.model_.loadLayout(layoutId);
+ return;
+ }
+};
+
+
+/**
+ * Sets the keyboard.
+ *
+ * @param {string} keyset The keyboard keyset.
+ * @param {string} languageCode The language code for this keyboard.
+ * @param {string} passwordLayout The layout for password box.
+ * @param {string} title The title for this keyboard.
+ */
+Controller.prototype.initialize = function(keyset, languageCode, passwordLayout,
+ title) {
+ this.perfTracker_.restart();
+ this.adapter_.getCurrentInputMethod(function(currentInputMethod) {
+ this.languageCode_ = languageCode;
+ this.currentInputMethod_ = currentInputMethod;
+ var keySetMap = this.contextTypeToKeysetMap_[this.currentInputMethod_];
+ if (!keySetMap) {
+ keySetMap = this.contextTypeToKeysetMap_[this.currentInputMethod_] = {};
+ }
+ keySetMap[ContextType.PASSWORD] = passwordLayout;
+ keySetMap[ContextType.DEFAULT] = keyset;
+
+ this.title_ = title;
+ this.isSettingReady = false;
+ this.model_.settings = new i18n.input.chrome.inputview.Settings();
+ this.adapter_.initialize(languageCode ? languageCode.split('-')[0] : '');
+ this.loadAllResources_();
+ this.switchToKeySet(this.getActiveKeyset_());
+
+ // Set language attribute and font of body.
+ document.body.setAttribute('lang', this.languageCode_);
+ goog.dom.classlist.add(document.body, Css.FONT);
+ }.bind(this));
+};
+
+
+/** @override */
+Controller.prototype.disposeInternal = function() {
+ goog.dispose(this.container_);
+ goog.dispose(this.adapter_);
+ goog.dispose(this.handler_);
+ goog.dispose(this.soundController_);
+
+ goog.base(this, 'disposeInternal');
+};
+
+
+/**
+ * Gets the handwriting Input Tool code of current language code.
+ *
+ * @return {string} The handwriting Input Tool code.
+ * @private
+ */
+Controller.prototype.getHwtInputToolCode_ = function() {
+ return this.languageCode_.split(/_|-/)[0] +
+ Controller.HANDWRITING_CODE_SUFFIX_;
+};
+
+
+/**
+ * True to enable settings link.
+ *
+ * @return {boolean} .
+ */
+Controller.prototype.shouldEnableSettings = function() {
+ return !this.adapter_.screen || this.adapter_.screen == 'normal';
+};
+
+
+/**
+ * Gets the active keyset, if there is a keyset to switch, return it.
+ * otherwise if it's a password box, return the password keyset,
+ * otherwise return the current keyset.
+ *
+ * @return {string} .
+ * @private
+ */
+Controller.prototype.getActiveKeyset_ = function() {
+ var keySetMap = this.contextTypeToKeysetMap_[this.currentInputMethod_];
+ return keySetMap[this.adapter_.getContextType()] ||
+ keySetMap[ContextType.DEFAULT];
+};
+
+
+/**
+ * True if keysetB is the sub keyset of keysetA.
+ *
+ * @param {string} keysetA .
+ * @param {string} keysetB .
+ * @return {boolean} .
+ * @private
+ */
+Controller.prototype.isSubKeyset_ = function(keysetA, keysetB) {
+ var segmentsA = keysetA.split('.');
+ var segmentsB = keysetB.split('.');
+ return segmentsA.length >= 2 && segmentsB.length >= 2 &&
+ segmentsA[0] == segmentsB[0] && segmentsA[1] == segmentsB[1];
+};
+
+
+/**
+ * Updates the compact pinyin to set the inputcode for english and pinyin.
+ *
+ * @param {string} fromRawKeyset .
+ * @param {string} toRawKeyset .
+ * @private
+ */
+Controller.prototype.updateLanguageState_ =
+ function(fromRawKeyset, toRawKeyset) {
+ if (fromRawKeyset == 'pinyin-zh-CN.en.compact.qwerty' &&
+ toRawKeyset.indexOf('en.compact') == -1) {
+ this.adapter_.toggleLanguageState(true);
+ } else if (fromRawKeyset.indexOf('en.compact') == -1 &&
+ toRawKeyset == 'pinyin-zh-CN.en.compact.qwerty') {
+ this.adapter_.toggleLanguageState(false);
+ } else if (goog.array.contains(
+ i18n.input.chrome.inputview.util.KEYSETS_HAVE_EN_SWTICHER,
+ toRawKeyset)) {
+ this.adapter_.toggleLanguageState(true);
+ this.model_.stateManager.isEnMode = false;
+ this.container_.currentKeysetView.update();
+ }
+};
+}); // goog.scope
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/covariance.js b/third_party/google_input_tools/src/chrome/os/inputview/covariance.js
new file mode 100644
index 0000000..facf5c1
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/covariance.js
@@ -0,0 +1,111 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.Covariance');
+
+goog.require('i18n.input.chrome.inputview.elements.ElementType');
+
+
+goog.scope(function() {
+var ElementType = i18n.input.chrome.inputview.elements.ElementType;
+
+
+
+/**
+ * The covariance used for gaussian model.
+ *
+ * @constructor
+ */
+i18n.input.chrome.inputview.Covariance = function() {
+ /** @private {number} */
+ this.breakDown_ = 0;
+};
+var Covariance = i18n.input.chrome.inputview.Covariance;
+
+
+/**
+ * The break-down for covariance.
+ *
+ * @enum {number}
+ */
+Covariance.BreakDown = {
+ A11Y: 1,
+ HORIZONTAL: 2,
+ WIDE_SCREEN: 4
+};
+
+
+/**
+ * The key type.
+ *
+ * @type {!Object.<ElementType, number>}
+ */
+Covariance.ElementTypeMap = goog.object.create(
+ ElementType.CHARACTER_KEY, 0,
+ ElementType.COMPACT_KEY, 1
+);
+
+
+/**
+ * The value.
+ * Key: the break down value.
+ * Value: A list - first is the covariance for full keyboard, second is for
+ * compact.
+ *
+ * @private {!Object.<!Array.<number>>}
+ */
+Covariance.VALUE_ = {
+ 0: [120, 160],
+ 1: [130, 0],
+ 2: [235, 342],
+ 3: [162, 0],
+ 4: [160, 213],
+ 5: [142, 0],
+ 6: [230, 332],
+ 7: [162, 0]
+};
+
+
+/**
+ * Updates the covariance.
+ *
+ * @param {boolean} isWideScreen .
+ * @param {boolean} isHorizontal .
+ * @param {boolean} isA11y .
+ */
+Covariance.prototype.update = function(isWideScreen, isHorizontal, isA11y) {
+ this.breakDown_ = 0;
+ if (isWideScreen) {
+ this.breakDown_ |= Covariance.BreakDown.WIDE_SCREEN;
+ }
+ if (isHorizontal) {
+ this.breakDown_ |= Covariance.BreakDown.HORIZONTAL;
+ }
+ if (isA11y) {
+ this.breakDown_ |= Covariance.BreakDown.A11Y;
+ }
+};
+
+
+/**
+ * Gets the covariance value.
+ *
+ * @param {ElementType} type .
+ */
+Covariance.prototype.getValue = function(type) {
+ var index = Covariance.ElementTypeMap[type];
+ return Covariance.VALUE_[this.breakDown_][index];
+};
+
+}); // goog.scope
+
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/css.js b/third_party/google_input_tools/src/chrome/os/inputview/css.js
new file mode 100644
index 0000000..bc96129
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/css.js
@@ -0,0 +1,164 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.Css');
+
+
+/**
+ * The css used for input view keyboard.
+ *
+ * @enum {string}
+ */
+i18n.input.chrome.inputview.Css = {
+ A11Y: goog.getCssName('inputview-a11y'),
+ ALTDATA_COVER: goog.getCssName('inputview-altdata-cover'),
+ ALTDATA_KEY: goog.getCssName('inputview-altdata-key'),
+ ALTDATA_SEPARATOR: goog.getCssName('inputview-altdata-separator'),
+ ALTDATA_VIEW: goog.getCssName('inputview-altdata-view'),
+ ALTGR_CONTENT: goog.getCssName('inputview-ac'),
+ ARROW_KEY: goog.getCssName('inputview-arrow-key'),
+ BACKSPACE_ICON: goog.getCssName('inputview-backspace-icon'),
+ CANDIDATE: goog.getCssName('inputview-candidate'),
+ CANDIDATES_LINE: goog.getCssName('inputview-candidates-line'),
+ CANDIDATES_TOP_LINE: goog.getCssName('inputview-candidates-top-line'),
+ CANDIDATE_AUTOCORRECT: goog.getCssName('inputview-candidate-autocorrect'),
+ CANDIDATE_BUTTON: goog.getCssName('inputview-candidate-button'),
+ CANDIDATE_DEFAULT: goog.getCssName('inputview-candidate-default'),
+ CANDIDATE_HIGHLIGHT: goog.getCssName('inputview-candidate-highlight'),
+ CANDIDATE_INTER_CONTAINER: goog.getCssName('inputview-candiate-ic'),
+ CANDIDATE_SEPARATOR: goog.getCssName('inputview-candidate-separator'),
+ CANDIDATE_VIEW: goog.getCssName('inputview-candidate-view'),
+ CANVAS: goog.getCssName('inputview-canvas'),
+ CANVAS_LEFT_COLUMN: goog.getCssName('inputview-canvas-left-column'),
+ CANVAS_RIGHT_COLUMN: goog.getCssName('inputview-canvas-right-column'),
+ CANVAS_VIEW: goog.getCssName('inputview-canvasview'),
+ CAPSLOCK_DOT: goog.getCssName('inputview-capslock-dot'),
+ CAPSLOCK_DOT_HIGHLIGHT: goog.getCssName('inputview-capslock-dot-highlight'),
+ CHARACTER: goog.getCssName('inputview-character'),
+ CHARACTER_HIGHLIGHT: goog.getCssName('inputview-ch'),
+ CHECKED_MENU_LIST: goog.getCssName('inputview-checked-menu-list'),
+ COMPACT_KEY: goog.getCssName('inputview-compact-key'),
+ COMPACT_SWITCHER: goog.getCssName('inputview-compact-switcher'),
+ CONTAINER: goog.getCssName('inputview-container'),
+ DEFAULT_CONTENT: goog.getCssName('inputview-dc'),
+ DIGIT_CHARACTER: goog.getCssName('inputview-digit-character'),
+ DOWN_KEY: goog.getCssName('inputview-down-key'),
+ ELEMENT_HIGHLIGHT: goog.getCssName('inputview-element-highlight'),
+ EMOJI: goog.getCssName('inputview-emoji'),
+ EMOJI_BACK: goog.getCssName('inputview-emoji-back'),
+ EMOJI_FONT: goog.getCssName('inputview-emoji-font'),
+ EMOJI_KEY: goog.getCssName('inputview-emoji-key'),
+ EMOJI_KEY_HIGHLIGHT: goog.getCssName('inputview-emoji-key-highlight'),
+ EMOJI_SWITCH: goog.getCssName('inputview-emoji-switch'),
+ EMOJI_SWITCH_CAR:
+ goog.getCssName('inputview-emoji-switch-car'),
+ EMOJI_SWITCH_EMOJI:
+ goog.getCssName('inputview-emoji-switch-emoji'),
+ EMOJI_SWITCH_EMOTICON:
+ goog.getCssName('inputview-emoji-switch-emoticon'),
+ EMOJI_SWITCH_FAVORITS:
+ goog.getCssName('inputview-emoji-switch-favorits'),
+ EMOJI_SWITCH_FLOWER:
+ goog.getCssName('inputview-emoji-switch-flower'),
+ EMOJI_SWITCH_HIGHLIGHT:
+ goog.getCssName('inputview-emoji-switch-highlight'),
+ EMOJI_SWITCH_RECENT:
+ goog.getCssName('inputview-emoji-switch-recent'),
+ EMOJI_SWITCH_SPECIAL:
+ goog.getCssName('inputview-emoji-switch-special'),
+ EMOJI_SWITCH_SYMBOL:
+ goog.getCssName('inputview-emoji-switch-symbol'),
+ EMOJI_SWITCH_TRIANGLE:
+ goog.getCssName('inputview-emoji-switch-triangle'),
+ EMOJI_TABBAR_KEY: goog.getCssName('inputview-emoji-tabbar-key'),
+ EMOJI_TABBAR_KEY_HIGHLIGHT:
+ goog.getCssName('inputview-emoji-tabbar-key-highlight'),
+ EMOJI_TABBAR_SK: goog.getCssName('inputview-emoji-tabbar-sk'),
+ EMOJI_TEXT: goog.getCssName('inputview-emoji-text'),
+ ENTER_ICON: goog.getCssName('inputview-enter-icon'),
+ EN_SWITCHER_DEFAULT: goog.getCssName('inputview-en-switcher-default'),
+ EN_SWITCHER_ENGLISH: goog.getCssName('inputview-en-switcher-english'),
+ EXPAND_CANDIDATES_ICON: goog.getCssName('inputview-expand-candidates-icon'),
+ EXTENDED_LAYOUT_TRANSITION: goog.getCssName('inputview-extended-transition'),
+ FONT: goog.getCssName('inputview-font'),
+ GLOBE_ICON: goog.getCssName('inputview-globe-icon'),
+ HANDWRITING: goog.getCssName('inputview-handwriting'),
+ HANDWRITING_BACK: goog.getCssName('inputview-handwriting-back'),
+ HANDWRITING_LAYOUT: goog.getCssName('inputview-handwriting-layout'),
+ HANDWRITING_NETWORK_ERROR:
+ goog.getCssName('inputview-handwriting-network-error'),
+ HANDWRITING_SWITCHER: goog.getCssName('inputview-handwriting-switcher'),
+ HANDWRITING_PRIVACY_COVER:
+ goog.getCssName('inputview-handwriting-privacy-cover'),
+ HANDWRITING_PRIVACY_INFO:
+ goog.getCssName('inputview-handwriting-privacy-info'),
+ HANDWRITING_PRIVACY_INFO_HIDDEN:
+ goog.getCssName('inputview-handwriting-privacy-info-hidden'),
+ HIDE_KEYBOARD_ICON: goog.getCssName('inputview-hide-keyboard-icon'),
+ HINT_TEXT: goog.getCssName('inputview-hint-text'),
+ HOLD: goog.getCssName('inputview-hold'),
+ IME_LIST_CONTAINER: goog.getCssName('inputview-ime-list-container'),
+ INDICATOR: goog.getCssName('inputview-indicator'),
+ INDICATOR_BACKGROUND: goog.getCssName('inputview-indicator-background'),
+ INLINE_DIV: goog.getCssName('inputview-inline-div'),
+ JP_IME_SWITCH: goog.getCssName('inputview-jp-ime-switch'),
+ KEY_HOLD: goog.getCssName('inputview-key-hold'),
+ LAYOUT_VIEW: goog.getCssName('inputview-layoutview'),
+ LEFT_KEY: goog.getCssName('inputview-left-key'),
+ LINEAR_LAYOUT: goog.getCssName('inputview-linear'),
+ LINEAR_LAYOUT_BORDER: goog.getCssName('inputview-linear-border'),
+ MENU_LIST_CHECK_MARK: goog.getCssName('inputview-menu-list-check-mark'),
+ MENU_FOOTER: goog.getCssName('inputview-menu-footer'),
+ MENU_FOOTER_EMOJI_BUTTON:
+ goog.getCssName('inputview-menu-footer-emoji-button'),
+ MENU_FOOTER_HANDWRITING_BUTTON:
+ goog.getCssName('inputview-menu-footer-handwriting-button'),
+ MENU_FOOTER_ITEM: goog.getCssName('inputview-menu-footer-item'),
+ MENU_FOOTER_SETTING_BUTTON:
+ goog.getCssName('inputview-menu-footer-setting-button'),
+ MENU_ICON: goog.getCssName('inputview-menu-icon'),
+ MENU_LIST_INDICATOR: goog.getCssName('inputview-menu-list-indicator'),
+ MENU_LIST_INDICATOR_NAME:
+ goog.getCssName('inputview-menu-list-indicator-name'),
+ MENU_LIST_ITEM: goog.getCssName('inputview-menu-list-item'),
+ MENU_LIST_NAME: goog.getCssName('inputview-menu-list-name'),
+ MENU_VIEW: goog.getCssName('inputview-menu-view'),
+ MODIFIER: goog.getCssName('inputview-modifier'),
+ MODIFIER_ON: goog.getCssName('inputview-modifier-on'),
+ MODIFIER_STATE_ICON: goog.getCssName('inputview-modifier-state-icon'),
+ PAGE_DOWN_ICON: goog.getCssName('inputview-page-down-icon'),
+ PAGE_UP_ICON: goog.getCssName('inputview-page-up-icon'),
+ PINYIN: goog.getCssName('inputview-pinyin'),
+ REGULAR_SWITCHER: goog.getCssName('inputview-regular-switcher'),
+ RIGHT_KEY: goog.getCssName('inputview-right-key'),
+ SHIFT_ICON: goog.getCssName('inputview-shift-icon'),
+ SHRINK_CANDIDATES_ICON: goog.getCssName('inputview-shrink-candidates-icon'),
+ SOFT_KEY: goog.getCssName('inputview-sk'),
+ SOFT_KEY_VIEW: goog.getCssName('inputview-skv'),
+ SPACE_ICON: goog.getCssName('inputview-space-icon'),
+ SPECIAL_KEY_BG: goog.getCssName('inputview-special-key-bg'),
+ SPECIAL_KEY_HIGHLIGHT: goog.getCssName('inputview-special-key-highlight'),
+ SPECIAL_KEY_NAME: goog.getCssName('inputview-special-key-name'),
+ SWITCHER_CHINESE: goog.getCssName('inputview-switcher-chinese'),
+ SWITCHER_ENGLISH: goog.getCssName('inputview-switcher-english'),
+ TABLE_CELL: goog.getCssName('inputview-table-cell'),
+ TAB_ICON: goog.getCssName('inputview-tab-icon'),
+ THREE_CANDIDATES: goog.getCssName('inputview-three-candidates'),
+ TITLE: goog.getCssName('inputview-title'),
+ TITLE_BAR: goog.getCssName('inputview-title-bar'),
+ UP_KEY: goog.getCssName('inputview-up-key'),
+ VERTICAL_LAYOUT: goog.getCssName('inputview-vertical'),
+ VIEW: goog.getCssName('inputview-view'),
+ WRAPPER: goog.getCssName('inputview-wrapper')
+};
+
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/direction.js b/third_party/google_input_tools/src/chrome/os/inputview/direction.js
new file mode 100644
index 0000000..4d07482
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/direction.js
@@ -0,0 +1,28 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.Direction');
+
+
+/**
+ * The direction.
+ *
+ * @enum {number}
+ */
+i18n.input.chrome.inputview.Direction = {
+ UP: 0,
+ DOWN: 1,
+ LEFT: 2,
+ RIGHT: 3
+};
+
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/dom.js b/third_party/google_input_tools/src/chrome/os/inputview/dom.js
new file mode 100644
index 0000000..69ab55e
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/dom.js
@@ -0,0 +1,229 @@
+// Copyright 2014 The Cloud Input Tools Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Provides common operation of dom for input tools.
+ */
+
+
+goog.provide('i18n.input.common.dom');
+
+goog.require('goog.array');
+goog.require('goog.dom');
+goog.require('goog.dom.TagName');
+goog.require('goog.dom.classlist');
+goog.require('goog.style');
+goog.require('goog.uri.utils');
+goog.require('i18n.input.common.GlobalSettings');
+
+
+/**
+ * When detects whether the same domain iframe, browser will throw
+ * exceptions on accessing the cross domain iframe. Stores result to avoid to
+ * throws exception twice.
+ * Key is document uid, value is object. ifrmae uid : true/false
+ *
+ * @type {!Object.<number, !Object.<number, boolean>>}
+ * @private
+ */
+i18n.input.common.dom.sameDomainIframes_ = {};
+
+
+/**
+ * Checks the given element whether is editable.
+ *
+ * @param {!Element} element The element.
+ * @return {boolean} Whether the give element is editable.
+ */
+i18n.input.common.dom.isEditable = function(element) {
+ if (!element.tagName) {
+ return false;
+ }
+
+ if (element.readOnly) {
+ return false;
+ }
+
+ switch (element.tagName.toUpperCase()) {
+ case 'TEXTAREA':
+ return true;
+ case 'INPUT':
+ return (element.type.toUpperCase() == 'TEXT' ||
+ element.type.toUpperCase() == 'SEARCH');
+ case 'DIV':
+ return element.isContentEditable;
+ case 'IFRAME':
+ // Accessing iframe's contents or properties throws exception when the
+ // iframe is not hosted on the same domain.
+ // When it happens, ignore it and consider this iframe isn't editable.
+ /** @preserveTry */
+ try {
+ var ifdoc = i18n.input.common.dom.getSameDomainFrameDoc(element);
+ return !!ifdoc && (ifdoc.designMode &&
+ ifdoc.designMode.toUpperCase() == 'ON' ||
+ ifdoc.body && ifdoc.body.isContentEditable);
+ } catch (e) {
+ return false;
+ }
+ }
+
+ return false;
+};
+
+
+/**
+ * Sets class names to an element.
+ *
+ * @param {Element} elem Element to set class names.
+ * @param {Array.<string>} classes Class names.
+ */
+i18n.input.common.dom.setClasses = function(elem, classes) {
+ if (elem) {
+ for (var i = 0; i < classes.length; i++) {
+ if (i == 0) {
+ goog.dom.classlist.set(elem, classes[0]);
+ } else {
+ goog.dom.classlist.add(elem, classes[i]);
+ }
+ }
+ }
+};
+
+
+/**
+ * Check the iframe whether is the same domain as the current domain.
+ * Returns the iframe content document when it's the same domain,
+ * otherwise return null.
+ *
+ * @param {!Element} element The iframe element.
+ * @return {Document} The iframe content document.
+ */
+i18n.input.common.dom.getSameDomainFrameDoc = function(element) {
+ var uid = goog.getUid(document);
+ var frameUid = goog.getUid(element);
+ var states = i18n.input.common.dom.sameDomainIframes_[uid];
+ if (!states) {
+ states = i18n.input.common.dom.sameDomainIframes_[uid] = {};
+ }
+ /** @preserveTry */
+ try {
+ var url = window.location.href || '';
+ //Note: cross-domain IFRAME's src can be:
+ // http://www...
+ // https://www....
+ // //www.
+ // Non-cross-domain IFRAME's src can be:
+ // javascript:...
+ // javascript://...
+ // abc:...
+ // abc://...
+ // abc//...
+ // path/index.html
+ if (!(frameUid in states)) {
+ if (element.src) {
+ var pos = element.src.indexOf('//');
+ var protocol = pos < 0 ? 'N/A' : element.src.slice(0, pos);
+ states[frameUid] = (protocol != '' &&
+ protocol != 'http:' &&
+ protocol != 'https:' ||
+ goog.uri.utils.haveSameDomain(element.src, url));
+ } else {
+ states[frameUid] = true;
+ }
+ }
+ return states[frameUid] ? goog.dom.getFrameContentDocument(element) : null;
+ } catch (e) {
+ states[frameUid] = false;
+ return null;
+ }
+};
+
+
+/**
+ * Gets the same domain iframe or frame document in given document, default
+ * given document is current document.
+ *
+ * @param {Document=} opt_doc The given document.
+ * @return {Array.<!Document>} The same domain iframe document.
+ */
+i18n.input.common.dom.getSameDomainDocuments = function(opt_doc) {
+ var doc = opt_doc || document;
+ var iframes = [];
+ var rets = [];
+ goog.array.extend(iframes,
+ doc.getElementsByTagName(goog.dom.TagName.IFRAME),
+ doc.getElementsByTagName(goog.dom.TagName.FRAME));
+ goog.array.forEach(iframes, function(frame) {
+ var frameDoc = i18n.input.common.dom.getSameDomainFrameDoc(frame);
+ frameDoc && rets.push(frameDoc);
+ });
+ return rets;
+};
+
+
+/**
+ * Create the iframe in given document or default document. Then the input tool
+ * UI element will be create inside the iframe document to avoid CSS conflict.
+ *
+ * @param {Document=} opt_doc The given document.
+ * @return {!Element} The iframe element.
+ */
+i18n.input.common.dom.createIframeWrapper = function(opt_doc) {
+ var doc = opt_doc || document;
+ var dom = goog.dom.getDomHelper();
+ var frame = dom.createDom(goog.dom.TagName.IFRAME, {
+ 'frameborder': '0',
+ 'scrolling': 'no',
+ 'style': 'background-color:transparent;border:0;display:none;'
+ });
+ dom.append(/** @type {!Element} */ (doc.body), frame);
+ var frameDoc = dom.getFrameContentDocument(frame);
+
+ var css = i18n.input.common.GlobalSettings.alternativeImageUrl ?
+ i18n.input.common.GlobalSettings.css.replace(
+ /\/\/ssl.gstatic.com\/inputtools\/images/g,
+ i18n.input.common.GlobalSettings.alternativeImageUrl) :
+ i18n.input.common.GlobalSettings.css;
+ goog.style.installStyles(
+ 'html body{border:0;margin:0;padding:0} html,body{overflow:hidden}' +
+ css, /** @type {!Element} */(frameDoc.body));
+ return frame;
+};
+
+
+/**
+ * The property need to be copied from original element to its iframe wrapper.
+ *
+ * @type {!Array.<string>}
+ * @private
+ */
+i18n.input.common.dom.iframeWrapperProperty_ = ['box-shadow', 'z-index',
+ 'margin', 'position', 'display'];
+
+
+/**
+ * Copies the necessary properties value from original element to its iframe
+ * wrapper element.
+ *
+ * @param {Element} element .
+ * @param {Element} iframe The iframe wrapper element.
+ */
+i18n.input.common.dom.copyNecessaryStyle = function(element, iframe) {
+ goog.style.setContentBoxSize(iframe, goog.style.getSize(element));
+ goog.array.forEach(i18n.input.common.dom.iframeWrapperProperty_,
+ function(property) {
+ goog.style.setStyle(iframe, property,
+ goog.style.getComputedStyle(element, property));
+ });
+};
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/altdataview.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/altdataview.js
new file mode 100644
index 0000000..3dd9499
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/altdataview.js
@@ -0,0 +1,340 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.elements.content.AltDataView');
+
+goog.require('goog.array');
+goog.require('goog.dom');
+goog.require('goog.dom.TagName');
+goog.require('goog.dom.classlist');
+goog.require('goog.style');
+goog.require('i18n.input.chrome.inputview.Css');
+goog.require('i18n.input.chrome.inputview.elements.Element');
+goog.require('i18n.input.chrome.inputview.elements.ElementType');
+goog.require('i18n.input.chrome.inputview.util');
+
+
+
+goog.scope(function() {
+
+
+
+/**
+ * The view for alt data.
+ *
+ * @param {goog.events.EventTarget=} opt_eventTarget The parent event target.
+ * @constructor
+ * @extends {i18n.input.chrome.inputview.elements.Element}
+ */
+i18n.input.chrome.inputview.elements.content.AltDataView = function(
+ opt_eventTarget) {
+ goog.base(this, '', i18n.input.chrome.inputview.elements.ElementType.
+ ALTDATA_VIEW, opt_eventTarget);
+
+ /**
+ * The alternative elements.
+ *
+ * @type {!Array.<!Element>}
+ * @private
+ */
+ this.altdataElements_ = [];
+};
+goog.inherits(i18n.input.chrome.inputview.elements.content.AltDataView,
+ i18n.input.chrome.inputview.elements.Element);
+var AltDataView = i18n.input.chrome.inputview.elements.content.AltDataView;
+
+
+/**
+ * The padding between the alt data view and the key.
+ *
+ * @type {number}
+ * @private
+ */
+AltDataView.PADDING_ = 6;
+
+
+/**
+ * The distance between finger to altdata view which will cancel the altdata
+ * view.
+ *
+ * @type {number}
+ * @private
+ */
+AltDataView.FINGER_DISTANCE_TO_CANCEL_ALTDATA_ = 100;
+
+
+/**
+ * The cover element.
+ * Note: The reason we use a separate cover element instead of the view is
+ * because of the opacity. We can not reassign the opacity in child element.
+ *
+ * @type {!Element}
+ * @private
+ */
+AltDataView.prototype.coverElement_;
+
+
+/**
+ * The index of the alternative element which is highlighted.
+ *
+ * @type {number}
+ * @private
+ */
+AltDataView.prototype.highlightIndex_ = 0;
+
+
+/**
+ * The key which trigger this alternative data view.
+ *
+ * @type {!i18n.input.chrome.inputview.elements.content.SoftKey}
+ */
+AltDataView.prototype.triggeredBy;
+
+
+/** @override */
+AltDataView.prototype.createDom = function() {
+ goog.base(this, 'createDom');
+
+ var dom = this.getDomHelper();
+ var elem = this.getElement();
+ goog.dom.classlist.add(elem, i18n.input.chrome.inputview.Css.ALTDATA_VIEW);
+ this.coverElement_ = dom.createDom(goog.dom.TagName.DIV,
+ i18n.input.chrome.inputview.Css.ALTDATA_COVER);
+ dom.appendChild(document.body, this.coverElement_);
+ goog.style.setElementShown(this.coverElement_, false);
+
+ this.coverElement_['view'] = this;
+};
+
+
+/** @override */
+AltDataView.prototype.enterDocument = function() {
+ goog.base(this, 'enterDocument');
+
+ goog.style.setElementShown(this.getElement(), false);
+};
+
+
+/**
+ * Shows the alt data viwe.
+ *
+ * @param {!i18n.input.chrome.inputview.elements.content.SoftKey} key The key
+ * triggerred this altdata view.
+ * @param {boolean} isRTL Whether to show the key characters as RTL.
+ */
+AltDataView.prototype.show = function(key, isRTL) {
+ this.triggeredBy = key;
+ var coordinate = goog.style.getClientPosition(key.getElement());
+ var x = coordinate.x;
+ var y = coordinate.y;
+ var width = key.availableWidth;
+ var height = key.availableHeight;
+ var ElementType = i18n.input.chrome.inputview.elements.ElementType;
+ var characters;
+ if (key.type == ElementType.CHARACTER_KEY) {
+ key = /** @type {!i18n.input.chrome.inputview.elements.content.
+ CharacterKey} */ (key);
+ characters = key.getAltCharacters();
+ } else if (key.type == ElementType.COMPACT_KEY) {
+ key = /** @type {!i18n.input.chrome.inputview.elements.content.
+ CompactKey} */ (key);
+ characters = key.getMoreCharacters();
+ if (key.hintText) {
+ goog.array.insertAt(characters, key.hintText, 0);
+ }
+ }
+ if (!characters || characters.length == 0) {
+ return;
+ }
+
+ goog.style.setElementShown(this.getElement(), true);
+ this.getDomHelper().removeChildren(this.getElement());
+ // The total width of the characters + the separators, every separator has
+ // width = 1.
+ var altDataWidth = width * characters.length;
+ var showingLeft = (x + altDataWidth) > screen.width;
+ if (showingLeft) {
+ characters.reverse();
+ }
+ for (var i = 0; i < characters.length; i++) {
+ var keyElem = this.addKey_(characters[i], isRTL);
+ goog.style.setSize(keyElem, width, height);
+ this.altdataElements_.push(keyElem);
+ if (i != characters.length - 1) {
+ this.addSeparator_(height);
+ }
+ }
+ var left = x;
+ if (showingLeft) {
+ // If no enough space at the right, then make it to the left.
+ left = x + width - altDataWidth;
+ this.highlightIndex_ = this.altdataElements_.length - 1;
+ this.setElementBackground_(this.altdataElements_[this.highlightIndex_],
+ true);
+ } else {
+ this.setElementBackground_(this.altdataElements_[0], true);
+ }
+ var elemTop = y - height - AltDataView.PADDING_;
+ if (elemTop < 0) {
+ elemTop = y + height + AltDataView.PADDING_;
+ }
+ goog.style.setPosition(this.getElement(), left, elemTop);
+ goog.style.setElementShown(this.coverElement_, true);
+ this.triggeredBy.setHighlighted(true);
+};
+
+
+/**
+ * Hides the alt data view.
+ */
+AltDataView.prototype.hide = function() {
+ this.altdataElements_ = [];
+ this.highlightIndex_ = 0;
+ goog.style.setElementShown(this.getElement(), false);
+ goog.style.setElementShown(this.coverElement_, false);
+ this.triggeredBy.setHighlighted(false);
+};
+
+
+/**
+ * Highlights the item according to the current coordinate of the finger.
+ *
+ * @param {number} x .
+ * @param {number} y .
+ */
+AltDataView.prototype.highlightItem = function(x, y) {
+ for (var i = 0; i < this.altdataElements_.length; i++) {
+ var elem = this.altdataElements_[i];
+ var coordinate = goog.style.getClientPosition(elem);
+ var size = goog.style.getSize(elem);
+ if (coordinate.x < x && (coordinate.x + size.width) > x) {
+ this.highlightIndex_ = i;
+ this.clearAllHighlights_();
+ this.setElementBackground_(elem, true);
+ }
+ var verticalDist = Math.min(Math.abs(y - coordinate.y),
+ Math.abs(y - coordinate.y - size.height));
+ if (verticalDist > AltDataView.
+ FINGER_DISTANCE_TO_CANCEL_ALTDATA_) {
+ this.hide();
+ return;
+ }
+ }
+};
+
+
+/**
+ * Clears all the highlights.
+ *
+ * @private
+ */
+AltDataView.prototype.clearAllHighlights_ =
+ function() {
+ for (var i = 0; i < this.altdataElements_.length; i++) {
+ this.setElementBackground_(this.altdataElements_[i], false);
+ }
+};
+
+
+/**
+ * Sets the background style of the element.
+ *
+ * @param {!Element} element The element.
+ * @param {boolean} highlight True to highlight the element.
+ * @private
+ */
+AltDataView.prototype.setElementBackground_ =
+ function(element, highlight) {
+ if (highlight) {
+ goog.dom.classlist.add(element, i18n.input.chrome.inputview.Css.
+ ELEMENT_HIGHLIGHT);
+ } else {
+ goog.dom.classlist.remove(element, i18n.input.chrome.inputview.Css.
+ ELEMENT_HIGHLIGHT);
+ }
+};
+
+
+/**
+ * Gets the highlighted character.
+ *
+ * @return {string} The character.
+ */
+AltDataView.prototype.getHighlightedCharacter = function() {
+ return goog.dom.getTextContent(this.altdataElements_[this.highlightIndex_]);
+};
+
+
+/**
+ * Adds a alt data key into the view.
+ *
+ * @param {string} character The alt character.
+ * @param {boolean} isRTL Whether to show the character as RTL.
+ * @return {!Element} The create key element.
+ * @private
+ */
+AltDataView.prototype.addKey_ = function(character, isRTL) {
+ var dom = this.getDomHelper();
+ var keyElem = dom.createDom(goog.dom.TagName.DIV,
+ i18n.input.chrome.inputview.Css.ALTDATA_KEY,
+ i18n.input.chrome.inputview.util.getVisibleCharacter(character));
+ keyElem.style.direction = isRTL ? 'rtl' : 'ltr';
+ dom.appendChild(this.getElement(), keyElem);
+ return keyElem;
+};
+
+
+/**
+ * Adds a separator.
+ *
+ * @param {number} height .
+ * @private
+ */
+AltDataView.prototype.addSeparator_ = function(height) {
+ var dom = this.getDomHelper();
+ var tableCell = dom.createDom(goog.dom.TagName.DIV,
+ i18n.input.chrome.inputview.Css.TABLE_CELL);
+ tableCell.style.height = height + 'px';
+ var separator = dom.createDom(goog.dom.TagName.DIV,
+ i18n.input.chrome.inputview.Css.ALTDATA_SEPARATOR);
+ dom.appendChild(tableCell, separator);
+ dom.appendChild(this.getElement(), tableCell);
+};
+
+
+/**
+ * Gets the cover element.
+ *
+ * @return {!Element} The cover element.
+ */
+AltDataView.prototype.getCoverElement = function() {
+ return this.coverElement_;
+};
+
+
+/** @override */
+AltDataView.prototype.resize = function(width, height) {
+ goog.base(this, 'resize', width, height);
+
+ goog.style.setSize(this.coverElement_, width, height);
+};
+
+
+/** @override */
+AltDataView.prototype.disposeInternal = function() {
+ this.getElement()['view'] = null;
+
+ goog.base(this, 'disposeInternal');
+};
+
+}); // goog.scope
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/candidate.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/candidate.js
new file mode 100644
index 0000000..4540938
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/candidate.js
@@ -0,0 +1,113 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.elements.content.Candidate');
+
+goog.require('goog.dom.classlist');
+goog.require('i18n.input.chrome.inputview.Css');
+goog.require('i18n.input.chrome.inputview.elements.Element');
+goog.require('i18n.input.chrome.inputview.elements.ElementType');
+goog.require('i18n.input.chrome.message.Name');
+
+goog.scope(function() {
+var ElementType = i18n.input.chrome.inputview.elements.ElementType;
+var Css = i18n.input.chrome.inputview.Css;
+var TagName = goog.dom.TagName;
+var Name = i18n.input.chrome.message.Name;
+
+
+
+/**
+ * The candidate component.
+ *
+ * @param {string} id .
+ * @param {!Object} candidate .
+ * @param {i18n.input.chrome.inputview.elements.content.Candidate.Type}
+ * candidateType .
+ * @param {number} height .
+ * @param {boolean} isDefault .
+ * @param {number=} opt_width .
+ * @param {goog.events.EventTarget=} opt_eventTarget .
+ * @constructor
+ * @extends {i18n.input.chrome.inputview.elements.Element}
+ */
+i18n.input.chrome.inputview.elements.content.Candidate = function(id,
+ candidate, candidateType, height, isDefault, opt_width, opt_eventTarget) {
+ goog.base(this, id, ElementType.CANDIDATE, opt_eventTarget);
+
+ /** @type {!Object} */
+ this.candidate = candidate;
+
+ /** @type {i18n.input.chrome.inputview.elements.content.Candidate.Type} */
+ this.candidateType = candidateType;
+
+ /** @type {number} */
+ this.width = opt_width || 0;
+
+ /** @type {number} */
+ this.height = height;
+
+ /** @type {boolean} */
+ this.isDefault = isDefault;
+};
+var Candidate = i18n.input.chrome.inputview.elements.content.Candidate;
+goog.inherits(Candidate, i18n.input.chrome.inputview.elements.Element);
+
+
+/**
+ * The type of this candidate.
+ *
+ * @enum {number}
+ */
+Candidate.Type = {
+ CANDIDATE: 0,
+ NUMBER: 1
+};
+
+
+/** @override */
+Candidate.prototype.createDom = function() {
+ goog.base(this, 'createDom');
+
+ var dom = this.getDomHelper();
+ var elem = this.getElement();
+ goog.dom.classlist.add(elem, Css.CANDIDATE);
+ if (this.candidate['isEmoji']) {
+ goog.dom.classlist.add(elem, Css.EMOJI_FONT);
+ }
+ dom.setTextContent(elem, this.candidate[Name.CANDIDATE]);
+ elem.style.height = this.height + 'px';
+ if (this.width > 0) {
+ elem.style.width = this.width + 'px';
+ }
+ if (this.isDefault) {
+ goog.dom.classlist.add(elem, Css.CANDIDATE_DEFAULT);
+ }
+ if (!!this.candidate[Name.IS_AUTOCORRECT]) {
+ goog.dom.classlist.add(elem, Css.CANDIDATE_AUTOCORRECT);
+ }
+};
+
+
+/** @override */
+Candidate.prototype.setHighlighted = function(highlight) {
+ if (highlight) {
+ goog.dom.classlist.add(this.getElement(), Css.CANDIDATE_HIGHLIGHT);
+ } else {
+ goog.dom.classlist.remove(this.getElement(), Css.CANDIDATE_HIGHLIGHT);
+ }
+};
+
+
+}); // goog.scope
+
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/candidatebutton.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/candidatebutton.js
new file mode 100644
index 0000000..b7aa484
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/candidatebutton.js
@@ -0,0 +1,117 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.elements.content.CandidateButton');
+
+goog.require('goog.dom.TagName');
+goog.require('goog.dom.classlist');
+goog.require('goog.style');
+goog.require('i18n.input.chrome.inputview.Css');
+goog.require('i18n.input.chrome.inputview.elements.Element');
+
+
+
+goog.scope(function() {
+var ElementType = i18n.input.chrome.inputview.elements.ElementType;
+var Css = i18n.input.chrome.inputview.Css;
+
+
+
+/**
+ * The icon button in the candidate view.
+ *
+ * @param {string} id .
+ * @param {ElementType} type .
+ * @param {string} iconCss .
+ * @param {string} text .
+ * @param {!goog.events.EventTarget=} opt_eventTarget .
+ * @constructor
+ * @extends {i18n.input.chrome.inputview.elements.Element}
+ */
+i18n.input.chrome.inputview.elements.content.CandidateButton = function(
+ id, type, iconCss, text, opt_eventTarget) {
+ goog.base(this, id, type, opt_eventTarget);
+
+ /** @type {string} */
+ this.text = text;
+
+ /** @type {string} */
+ this.iconCss = iconCss;
+};
+var CandidateButton = i18n.input.chrome.inputview.elements.content.
+ CandidateButton;
+goog.inherits(CandidateButton, i18n.input.chrome.inputview.elements.Element);
+
+
+/** @type {!Element} */
+CandidateButton.prototype.iconCell;
+
+
+/** @type {!Element} */
+CandidateButton.prototype.separatorCell;
+
+
+/** @override */
+CandidateButton.prototype.createDom = function() {
+ goog.base(this, 'createDom');
+
+ var dom = this.getDomHelper();
+ var elem = this.getElement();
+ goog.dom.classlist.addAll(elem, [Css.CANDIDATE_INTER_CONTAINER,
+ Css.CANDIDATE_BUTTON]);
+
+ this.separatorCell = this.createSeparator_();
+
+ this.iconCell = dom.createDom(goog.dom.TagName.DIV, Css.TABLE_CELL);
+ dom.appendChild(elem, this.iconCell);
+
+ var iconElem = dom.createDom(goog.dom.TagName.DIV, Css.INLINE_DIV);
+ if (this.iconCss) {
+ goog.dom.classlist.add(iconElem, this.iconCss);
+ }
+ if (this.text) {
+ dom.setTextContent(iconElem, this.text);
+ }
+ dom.appendChild(this.iconCell, iconElem);
+};
+
+
+/**
+ * Creates a separator.
+ *
+ * @private
+ */
+CandidateButton.prototype.createSeparator_ = function() {
+ var dom = this.getDomHelper();
+ var tableCell = dom.createDom(goog.dom.TagName.DIV,
+ i18n.input.chrome.inputview.Css.TABLE_CELL);
+ var separator = dom.createDom(goog.dom.TagName.DIV,
+ i18n.input.chrome.inputview.Css.CANDIDATE_SEPARATOR);
+ separator.style.height = '32%';
+ dom.appendChild(tableCell, separator);
+ dom.appendChild(this.getElement(), tableCell);
+ return tableCell;
+};
+
+
+/** @override */
+CandidateButton.prototype.resize = function(width, height) {
+ goog.style.setSize(this.separatorCell, 1, height);
+ goog.style.setSize(this.iconCell, width - 1, height);
+
+ goog.base(this, 'resize', width, height);
+};
+
+
+}); // goog.scope
+
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/candidateview.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/candidateview.js
new file mode 100644
index 0000000..98e3d10
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/candidateview.js
@@ -0,0 +1,367 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.elements.content.CandidateView');
+
+goog.require('goog.dom.TagName');
+goog.require('goog.dom.classlist');
+goog.require('goog.object');
+goog.require('goog.style');
+goog.require('i18n.input.chrome.inputview.Css');
+goog.require('i18n.input.chrome.inputview.elements.Element');
+goog.require('i18n.input.chrome.inputview.elements.ElementType');
+goog.require('i18n.input.chrome.inputview.elements.content.Candidate');
+goog.require('i18n.input.chrome.inputview.elements.content.CandidateButton');
+goog.require('i18n.input.chrome.message.Name');
+
+
+
+goog.scope(function() {
+var Css = i18n.input.chrome.inputview.Css;
+var TagName = goog.dom.TagName;
+var Candidate = i18n.input.chrome.inputview.elements.content.Candidate;
+var Type = i18n.input.chrome.inputview.elements.content.Candidate.Type;
+var ElementType = i18n.input.chrome.inputview.elements.ElementType;
+var Element = i18n.input.chrome.inputview.elements.Element;
+var content = i18n.input.chrome.inputview.elements.content;
+var Name = i18n.input.chrome.message.Name;
+
+
+
+/**
+ * The candidate view.
+ *
+ * @param {string} id The id.
+ * @param {goog.events.EventTarget=} opt_eventTarget The event target.
+ * @constructor
+ * @extends {i18n.input.chrome.inputview.elements.Element}
+ */
+i18n.input.chrome.inputview.elements.content.CandidateView = function(id,
+ opt_eventTarget) {
+ goog.base(this, id, ElementType.CANDIDATE_VIEW, opt_eventTarget);
+
+ /**
+ * The icons.
+ *
+ * @private {!Array.<!i18n.input.chrome.inputview.elements.Element>}
+ */
+ this.iconButtons_ = [];
+
+ this.iconButtons_[CandidateView.IconType.BACK] = new content.CandidateButton(
+ '', ElementType.BACK_BUTTON, '',
+ chrome.i18n.getMessage('HANDWRITING_BACK'), this);
+ this.iconButtons_[CandidateView.IconType.SHRINK_CANDIDATES] = new content.
+ CandidateButton('', ElementType.SHRINK_CANDIDATES,
+ Css.SHRINK_CANDIDATES_ICON, '', this);
+ this.iconButtons_[CandidateView.IconType.EXPAND_CANDIDATES] = new content.
+ CandidateButton('', ElementType.EXPAND_CANDIDATES,
+ Css.EXPAND_CANDIDATES_ICON, '', this);
+};
+goog.inherits(i18n.input.chrome.inputview.elements.content.CandidateView,
+ i18n.input.chrome.inputview.elements.Element);
+var CandidateView = i18n.input.chrome.inputview.elements.content.CandidateView;
+
+
+/**
+ * The icon type at the right of the candiate view.
+ *
+ * @enum {number}
+ */
+CandidateView.IconType = {
+ BACK: 0,
+ SHRINK_CANDIDATES: 1,
+ EXPAND_CANDIDATES: 2
+};
+
+
+/**
+ * How many candidates in this view.
+ *
+ * @type {number}
+ */
+CandidateView.prototype.candidateCount = 0;
+
+
+/**
+ * The padding between candidates.
+ *
+ * @type {number}
+ * @private
+ */
+CandidateView.PADDING_ = 50;
+
+
+/**
+ * The width in weight which stands for the entire row. It is used for the
+ * alignment of the number row.
+ *
+ * @private {number}
+ */
+CandidateView.prototype.widthInWeight_ = 0;
+
+
+/**
+ * True if it is showing candidate.
+ *
+ * @type {boolean}
+ */
+CandidateView.prototype.showingCandidates = false;
+
+
+/**
+ * true if it is showing number row.
+ *
+ * @type {boolean}
+ */
+CandidateView.prototype.showingNumberRow = false;
+
+
+/**
+ * The width for a candidate when showing in THREE_CANDIDATE mode.
+ *
+ * @type {number}
+ * @private
+ */
+CandidateView.WIDTH_FOR_THREE_CANDIDATES_ = 235;
+
+
+/**
+ * The width of the icon at the right of the candidate view, it would be back
+ * icon, hide candidates icon, or show candidates icon.
+ *
+ * @private {number}
+ */
+CandidateView.ICON_WIDTH_ = 120;
+
+
+/**
+ * The handwriting keyset code.
+ *
+ * @private {string}
+ */
+CandidateView.HANDWRITING_VIEW_CODE_ = 'hwt';
+
+
+/**
+ * The width of the inter container.
+ *
+ * @private {number}
+ */
+CandidateView.prototype.interContainerWidth_ = 0;
+
+
+/** @override */
+CandidateView.prototype.createDom = function() {
+ goog.base(this, 'createDom');
+
+ var dom = this.getDomHelper();
+ var elem = this.getElement();
+ this.interContainer_ = dom.createDom(TagName.DIV,
+ Css.CANDIDATE_INTER_CONTAINER);
+ dom.appendChild(elem, this.interContainer_);
+
+ for (var i = 0; i < this.iconButtons_.length; i++) {
+ var button = this.iconButtons_[i];
+ button.render(elem);
+ button.setVisible(false);
+ }
+
+ // CandidateView is a container which doesn't handle any event and could be
+ // taken as a empty area, so don't attach the view.
+ elem['view'] = null;
+};
+
+
+/**
+ * Hides the number row.
+ */
+CandidateView.prototype.hideNumberRow = function() {
+ if (this.showingNumberRow) {
+ this.getDomHelper().removeChildren(this.interContainer_);
+ this.showingNumberRow = false;
+ }
+};
+
+
+/**
+ * Shows the number row.
+ */
+CandidateView.prototype.showNumberRow = function() {
+ goog.dom.classlist.remove(this.getElement(),
+ i18n.input.chrome.inputview.Css.THREE_CANDIDATES);
+ var dom = this.getDomHelper();
+ var numberWidth = this.width / this.widthInWeight_ - 1;
+ dom.removeChildren(this.interContainer_);
+ for (var i = 0; i < 10; i++) {
+ var candidateElem = new Candidate(String(i), goog.object.create(
+ Name.CANDIDATE, String((i + 1) % 10)),
+ Type.NUMBER, this.height, false, numberWidth, this);
+ candidateElem.render(this.interContainer_);
+ }
+ this.showingNumberRow = true;
+};
+
+
+/**
+ * Shows the candidates.
+ *
+ * @param {!Array.<!Object>} candidates The candidate list.
+ * @param {boolean} showThreeCandidates .
+ * @param {boolean=} opt_expandable True if the candidates would be shown
+ * in expanded view.
+ */
+CandidateView.prototype.showCandidates = function(candidates,
+ showThreeCandidates, opt_expandable) {
+ this.clearCandidates();
+ if (candidates.length > 0) {
+ if (showThreeCandidates) {
+ this.addThreeCandidates_(candidates);
+ } else {
+ this.addFullCandidates_(candidates);
+ if (opt_expandable) {
+ this.switchToIcon(CandidateView.IconType.EXPAND_CANDIDATES,
+ this.candidateCount < candidates.length);
+ }
+ }
+ this.showingCandidates = true;
+ }
+};
+
+
+/**
+ * Adds the candidates in THREE-CANDIDATE mode.
+ *
+ * @param {!Array.<!Object>} candidates The candidate list.
+ * @private
+ */
+CandidateView.prototype.addThreeCandidates_ = function(candidates) {
+ goog.dom.classlist.add(this.getElement(),
+ i18n.input.chrome.inputview.Css.THREE_CANDIDATES);
+ var num = Math.min(3, candidates.length);
+ var dom = this.getDomHelper();
+ for (var i = 0; i < num; i++) {
+ var candidateElem = new Candidate(String(i), candidates[i], Type.CANDIDATE,
+ this.height, i == 1 || num == 1, CandidateView.
+ WIDTH_FOR_THREE_CANDIDATES_, this);
+ candidateElem.render(this.interContainer_);
+ }
+ this.candidateCount = num;
+};
+
+
+/**
+ * Clears the candidates.
+ */
+CandidateView.prototype.clearCandidates = function() {
+ if (this.showingCandidates) {
+ this.candidateCount = 0;
+ this.getDomHelper().removeChildren(this.interContainer_);
+ this.showingCandidates = false;
+ }
+};
+
+
+/**
+ * Adds candidates into the view, as many as the candidate bar can support.
+ *
+ * @param {!Array.<!Object>} candidates The candidate list.
+ * @private
+ */
+CandidateView.prototype.addFullCandidates_ = function(candidates) {
+ goog.dom.classlist.remove(this.getElement(),
+ i18n.input.chrome.inputview.Css.THREE_CANDIDATES);
+ var totalWidth = Math.floor(this.width - CandidateView.ICON_WIDTH_);
+ var w = 0;
+ var dom = this.getDomHelper();
+ var i;
+ for (i = 0; i < candidates.length; i++) {
+ var candidateElem = new Candidate(String(i), candidates[i], Type.CANDIDATE,
+ this.height, false, undefined, this);
+ candidateElem.render(this.interContainer_);
+ var size = goog.style.getSize(candidateElem.getElement());
+ var candidateWidth = size.width + CandidateView.PADDING_ * 2;
+ // 1px is the width of the separator.
+ w += candidateWidth + 1;
+
+ if (w >= totalWidth) {
+ this.interContainer_.removeChild(candidateElem.getElement());
+ break;
+ }
+ goog.style.setWidth(candidateElem.getElement(), candidateWidth);
+ }
+ this.candidateCount = i;
+};
+
+
+/**
+ * Sets the widthInWeight which equals to a total line in the
+ * keyset view and it is used for alignment of number row.
+ *
+ * @param {number} widthInWeight .
+ */
+CandidateView.prototype.setWidthInWeight = function(widthInWeight) {
+ this.widthInWeight_ = widthInWeight;
+};
+
+
+/** @override */
+CandidateView.prototype.resize = function(width, height) {
+ goog.style.setSize(this.getElement(), width, height);
+ this.interContainer_.style.height = height + 'px';
+ for (var i = 0; i < this.iconButtons_.length; i++) {
+ var button = this.iconButtons_[i];
+ button.resize(CandidateView.ICON_WIDTH_, height);
+ }
+
+ goog.base(this, 'resize', width, height);
+
+ if (this.showingNumberRow) {
+ this.showNumberRow();
+ }
+};
+
+
+/**
+ * Switches to the icon, or hide it.
+ *
+ * @param {number} type .
+ * @param {boolean} visible The visibility of back button.
+ */
+CandidateView.prototype.switchToIcon = function(type, visible) {
+ for (var i = 0; i < this.iconButtons_.length; i++) {
+ this.iconButtons_[i].setVisible(false);
+ }
+
+ this.iconButtons_[type].setVisible(visible);
+};
+
+
+/**
+ * Updates the candidate view by key set changing.
+ *
+ * @param {string} keyset .
+ * @param {boolean} isPasswordBox .
+ * @param {boolean} isRTL .
+ */
+CandidateView.prototype.updateByKeyset = function(
+ keyset, isPasswordBox, isRTL) {
+ this.switchToIcon(CandidateView.IconType.BACK,
+ keyset == CandidateView.HANDWRITING_VIEW_CODE_);
+ if (isPasswordBox && keyset.indexOf('compact') != -1) {
+ this.showNumberRow();
+ } else {
+ this.hideNumberRow();
+ }
+ this.interContainer_.style.direction = isRTL ? 'rtl' : 'ltr';
+};
+}); // goog.scope
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/canvasview.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/canvasview.js
new file mode 100644
index 0000000..e4b8a84
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/canvasview.js
@@ -0,0 +1,312 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.elements.content.CanvasView');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.dom.TagName');
+goog.require('goog.dom.classlist');
+goog.require('goog.style');
+goog.require('i18n.input.chrome.DataSource');
+goog.require('i18n.input.chrome.inputview.Css');
+goog.require('i18n.input.chrome.inputview.elements.Element');
+goog.require('i18n.input.chrome.inputview.elements.ElementType');
+goog.require('i18n.input.chrome.inputview.elements.Weightable');
+goog.require('i18n.input.chrome.inputview.elements.content.FunctionalKey');
+goog.require('i18n.input.chrome.message.Type');
+goog.require('i18n.input.hwt.Canvas');
+goog.require('i18n.input.hwt.StrokeHandler');
+
+
+
+goog.scope(function() {
+var Canvas = i18n.input.hwt.Canvas;
+var Css = i18n.input.chrome.inputview.Css;
+var ElementType = i18n.input.chrome.inputview.elements.ElementType;
+var FunctionalKey = i18n.input.chrome.inputview.elements.content.FunctionalKey;
+var Name = i18n.input.chrome.message.Name;
+var Type = i18n.input.chrome.message.Type;
+
+
+
+/**
+ * The handwriting canvas view.
+ *
+ * @param {string} id The id.
+ * @param {number} widthInWeight The width in weight unit.
+ * @param {number} heightInWeight The height in weight unit.
+ * @param {goog.events.EventTarget=} opt_eventTarget The event target.
+ * @param {i18n.input.chrome.inputview.Adapter=} opt_adapter .
+ * @constructor
+ * @extends {i18n.input.chrome.inputview.elements.Element}
+ * @implements {i18n.input.chrome.inputview.elements.Weightable}
+ */
+i18n.input.chrome.inputview.elements.content.CanvasView = function(id,
+ widthInWeight, heightInWeight, opt_eventTarget, opt_adapter) {
+ goog.base(this, id, ElementType.CANVAS_VIEW, opt_eventTarget);
+
+ /**
+ * @private {!Canvas}
+ */
+ this.canvas_ = new Canvas(document, this.getDomHelper(), opt_eventTarget,
+ CanvasView.INK_WIDTH_, CanvasView.INK_COLOR_);
+
+ /**
+ * The weight of the width.
+ *
+ * @type {number}
+ * @private
+ */
+ this.widthInWeight_ = widthInWeight;
+
+ /**
+ * The weight of the height.
+ *
+ * @type {number}
+ * @private
+ */
+ this.heightInWeight_ = heightInWeight;
+
+ /**
+ * The bus channel to communicate with background.
+ *
+ * @private {i18n.input.chrome.inputview.Adapter}
+ */
+ this.adapter_ = goog.asserts.assertObject(opt_adapter);
+
+ this.pointerConfig.stopEventPropagation = false;
+};
+goog.inherits(i18n.input.chrome.inputview.elements.content.CanvasView,
+ i18n.input.chrome.inputview.elements.Element);
+var CanvasView = i18n.input.chrome.inputview.elements.content.CanvasView;
+
+
+/**
+ * Width of the ink line.
+ *
+ * @type {number}
+ * @private
+ */
+CanvasView.INK_WIDTH_ = 6;
+
+
+/**
+ * Color of the ink before it has been recognized.
+ *
+ * @type {string}
+ * @private
+ */
+CanvasView.INK_COLOR_ = '#111111';
+
+
+/**
+ * The div to show network error message.
+ *
+ * @type {!Element}
+ * @private
+ */
+CanvasView.prototype.networkErrorDiv_;
+
+
+/**
+ * The div to show privacy information message.
+ *
+ * @type {!Element}
+ * @private
+ */
+CanvasView.prototype.privacyDiv_;
+
+
+/**
+ * The confirm button of privacy information.
+ *
+ * @private {!FunctionalKey}
+ */
+CanvasView.prototype.confirmBtn_;
+
+
+/**
+ * The cover mask element.
+ *
+ * @private {!Element}
+ */
+CanvasView.prototype.coverElement_;
+
+
+/** @override */
+CanvasView.prototype.createDom = function() {
+ goog.base(this, 'createDom');
+
+ var container = this.getElement();
+ var dom = this.getDomHelper();
+ goog.dom.classlist.add(container, Css.CANVAS_VIEW);
+ this.coverElement_ = dom.createDom(goog.dom.TagName.DIV,
+ Css. HANDWRITING_PRIVACY_COVER);
+ dom.appendChild(container, this.coverElement_);
+ goog.style.setElementShown(this.coverElement_, false);
+
+
+ this.canvas_.render(container);
+ goog.dom.classlist.add(this.canvas_.getElement(), Css.CANVAS);
+
+ this.networkErrorDiv_ = dom.createDom(
+ goog.dom.TagName.DIV, Css.HANDWRITING_NETWORK_ERROR);
+ dom.setTextContent(this.networkErrorDiv_,
+ chrome.i18n.getMessage('HANDWRITING_NETOWRK_ERROR'));
+ goog.style.setElementShown(this.networkErrorDiv_, false);
+ dom.appendChild(container, this.networkErrorDiv_);
+
+ this.privacyDiv_ = dom.createDom(goog.dom.TagName.DIV,
+ [Css.HANDWRITING_PRIVACY_INFO, Css.HANDWRITING_PRIVACY_INFO_HIDDEN]);
+ var textDiv = dom.createDom(goog.dom.TagName.DIV);
+ dom.setTextContent(textDiv,
+ chrome.i18n.getMessage('HANDWRITING_PRIVACY_INFO'));
+ dom.appendChild(this.privacyDiv_, textDiv);
+ this.confirmBtn_ = new FunctionalKey(
+ '', ElementType.HWT_PRIVACY_GOT_IT, chrome.i18n.getMessage('GOT_IT'), '');
+ this.confirmBtn_.render(this.privacyDiv_);
+
+ dom.appendChild(container, this.privacyDiv_);
+};
+
+
+/** @override */
+CanvasView.prototype.enterDocument = function() {
+ goog.base(this, 'enterDocument');
+ this.getHandler().
+ listen(this.canvas_.getStrokeHandler(),
+ i18n.input.hwt.StrokeHandler.EventType.STROKE_END,
+ this.onStrokeEnd_).
+ listen(this.adapter_,
+ [i18n.input.chrome.DataSource.EventType.CANDIDATES_BACK,
+ Type.HWT_NETWORK_ERROR],
+ this.onNetworkState_).
+ listen(this.adapter_, Type.HWT_PRIVACY_INFO, this.onShowPrivacyInfo_).
+ listen(this.adapter_, Type.HWT_PRIVACY_GOT_IT,
+ this.onConfirmPrivacyInfo_);
+};
+
+
+/** @override */
+CanvasView.prototype.setHighlighted = goog.nullFunction;
+
+
+/** @override */
+CanvasView.prototype.getWidthInWeight = function() {
+ return this.widthInWeight_;
+};
+
+
+/** @override */
+CanvasView.prototype.getHeightInWeight = function() {
+ return this.heightInWeight_;
+};
+
+
+/** @override */
+CanvasView.prototype.resize = function(width, height) {
+ goog.base(this, 'resize', width, height);
+
+ var elem = this.getElement();
+ elem.style.width = this.coverElement_.style.width = width + 'px';
+ elem.style.height = this.coverElement_.style.height = height + 'px';
+
+ this.networkErrorDiv_.style.top = this.privacyDiv_.style.top =
+ Math.round(height / 2 - 50) + 'px';
+ this.networkErrorDiv_.style.left = this.privacyDiv_.style.left =
+ Math.round(width / 2 - 150) + 'px';
+ this.confirmBtn_.resize(100, 60);
+
+ this.canvas_.setSize(height, width);
+};
+
+
+/**
+ * Prepare the input data for a recognition request, including the
+ * ink, context, and language.
+ *
+ * @private
+ */
+CanvasView.prototype.onStrokeEnd_ = function() {
+ // Reformat the ink into the format expected by the input servers.
+ var strokes = goog.array.map(this.canvas_.strokeList,
+ function(stroke) {
+ return [goog.array.map(stroke, function(point) { return point.x; }),
+ goog.array.map(stroke, function(point) { return point.y; }),
+ goog.array.map(stroke, function(point) { return point.time; })];
+ });
+ var elem = this.getElement();
+ var payload = {
+ 'strokes': strokes,
+ 'width': elem.style.width,
+ 'height': elem.style.height
+ };
+
+ this.adapter_.sendHwtRequest(payload);
+};
+
+
+/**
+ * Clears the strokes on canvas.
+ */
+CanvasView.prototype.reset = function() {
+ this.canvas_.reset();
+};
+
+
+/**
+ * Whether there are strokes on canvas.
+ *
+ * @return {boolean} Whether there are strokes on canvas.
+ */
+CanvasView.prototype.hasStrokesOnCanvas = function() {
+ return this.canvas_.strokeList.length > 0;
+};
+
+
+/**
+ * Show or hide network error message div.
+ *
+ * @param {!goog.events.Event} e
+ * @private
+ */
+CanvasView.prototype.onNetworkState_ = function(e) {
+ goog.style.setElementShown(
+ this.networkErrorDiv_, e.type == Type.HWT_NETWORK_ERROR);
+};
+
+
+/**
+ * Shows the privacy information. Show on first time seeing the handwriting UI
+ * for 6 seconds, then fade out over 2 seconds.
+ *
+ * @private
+ */
+CanvasView.prototype.onShowPrivacyInfo_ = function() {
+ goog.style.setElementShown(this.coverElement_, true);
+ goog.dom.classlist.remove(this.privacyDiv_,
+ Css.HANDWRITING_PRIVACY_INFO_HIDDEN);
+};
+
+
+/**
+ * Handler on user confirming the privacy information.
+ *
+ * @private
+ */
+CanvasView.prototype.onConfirmPrivacyInfo_ = function() {
+ goog.style.setElementShown(this.coverElement_, false);
+ goog.dom.classlist.add(this.privacyDiv_, Css.HANDWRITING_PRIVACY_INFO_HIDDEN);
+};
+}); // goog.scope
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/character.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/character.js
new file mode 100644
index 0000000..5e27143
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/character.js
@@ -0,0 +1,196 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.elements.content.Character');
+
+goog.require('goog.dom');
+goog.require('goog.dom.classlist');
+goog.require('goog.style');
+goog.require('i18n.input.chrome.inputview.Css');
+goog.require('i18n.input.chrome.inputview.elements.Element');
+goog.require('i18n.input.chrome.inputview.elements.ElementType');
+goog.require('i18n.input.chrome.inputview.util');
+
+
+
+goog.scope(function() {
+
+
+
+/**
+ * The letter in the soft key.
+ *
+ * @param {string} id The id.
+ * @param {!i18n.input.chrome.inputview.elements.content.CharacterModel} model
+ * The character model.
+ * @param {boolean} isRTL Whether the character is shown in a RTL layout.
+ * @constructor
+ * @extends {i18n.input.chrome.inputview.elements.Element}
+ */
+i18n.input.chrome.inputview.elements.content.Character = function(
+ id, model, isRTL) {
+ goog.base(this, id, i18n.input.chrome.inputview.elements.ElementType.
+ CHARACTER);
+
+ /**
+ * The model.
+ *
+ * @type {!i18n.input.chrome.inputview.elements.content.CharacterModel}
+ * @private
+ */
+ this.characterModel_ = model;
+
+ /**
+ * Whether the character is shown in a RTL layout.
+ *
+ * @type {boolean}
+ * @private
+ */
+ this.isRTL_ = isRTL;
+};
+goog.inherits(i18n.input.chrome.inputview.elements.content.Character,
+ i18n.input.chrome.inputview.elements.Element);
+var Character = i18n.input.chrome.inputview.elements.content.Character;
+
+
+/**
+ * The padding.
+ *
+ * @type {number}
+ * @private
+ */
+Character.PADDING_ = 2;
+
+
+/** @override */
+Character.prototype.createDom = function() {
+ goog.base(this, 'createDom');
+
+ var elem = this.getElement();
+ var dom = this.getDomHelper();
+ this.getElement()['view'] = null;
+ var character = this.characterModel_.getContent();
+ dom.appendChild(elem, dom.createTextNode(character));
+ goog.dom.classlist.add(elem, i18n.input.chrome.inputview.Css.CHARACTER);
+ if (/[0-9]/.test(character)) {
+ // Digits 0-9 looks bigger than it should be, so decrease the font-size for
+ // them.
+ goog.dom.classlist.add(elem,
+ i18n.input.chrome.inputview.Css.DIGIT_CHARACTER);
+ }
+ elem.style.direction = this.isRTL_ ? 'rtl' : 'ltr';
+};
+
+
+/**
+ * Reposition the character.
+ *
+ * @private
+ */
+Character.prototype.reposition_ = function() {
+ var width = this.width;
+ var height = this.height;
+ var size = goog.style.getSize(this.getElement());
+ var paddingVertical;
+ var paddingHorizontal;
+ if (this.characterModel_.isHorizontalAlignCenter()) {
+ paddingHorizontal = Math.floor((width - size.width) / 2);
+ } else {
+ paddingHorizontal = Character.PADDING_;
+ }
+ if (this.characterModel_.isVerticalAlignCenter()) {
+ paddingVertical = Math.floor((height - size.height) / 2);
+ } else {
+ paddingVertical = Character.PADDING_;
+ }
+ var attributes = this.characterModel_.getPositionAttribute();
+ var elem = this.getElement();
+ elem.style[attributes[0]] = paddingVertical + 'px';
+ elem.style[attributes[1]] = paddingHorizontal + 'px';
+};
+
+
+/**
+ * Highlights the letter or not.
+ */
+Character.prototype.highlight = function() {
+ if (this.characterModel_.isHighlighted()) {
+ goog.dom.classlist.add(this.getElement(),
+ i18n.input.chrome.inputview.Css.CHARACTER_HIGHLIGHT);
+ } else {
+ goog.dom.classlist.remove(this.getElement(),
+ i18n.input.chrome.inputview.Css.CHARACTER_HIGHLIGHT);
+ }
+};
+
+
+/**
+ * Updates the content.
+ */
+Character.prototype.updateContent = function() {
+ var ch = this.characterModel_.getContent();
+ goog.dom.setTextContent(this.getElement(),
+ i18n.input.chrome.inputview.util.getVisibleCharacter(ch));
+};
+
+
+/** @override */
+Character.prototype.setVisible = function(visibility) {
+ this.getElement().style.display = visibility ? 'inline-block' : 'none';
+};
+
+
+/** @override */
+Character.prototype.resize = function(width, height) {
+ goog.base(this, 'resize', width, height);
+
+ this.update();
+};
+
+
+/** @override */
+Character.prototype.update = function() {
+ this.highlight();
+ this.reposition_();
+ this.updateContent();
+ this.setVisible(this.characterModel_.isVisible());
+};
+
+
+/**
+ * Gets the letter.
+ *
+ * @return {string} The letter.
+ */
+Character.prototype.getContent = function() {
+ return this.characterModel_.getContent();
+};
+
+
+/** @override */
+Character.prototype.isVisible = function() {
+ return this.characterModel_.isVisible();
+};
+
+
+/**
+ * True if this character is highlighted.
+ *
+ * @return {boolean} True if this character is highlighted.
+ */
+Character.prototype.isHighlighted = function() {
+ return this.characterModel_.isHighlighted();
+};
+
+
+}); // goog.scope
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/characterkey.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/characterkey.js
new file mode 100644
index 0000000..5707e15
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/characterkey.js
@@ -0,0 +1,299 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.elements.content.CharacterKey');
+
+goog.require('goog.array');
+goog.require('goog.math.Coordinate');
+goog.require('i18n.input.chrome.inputview.StateType');
+goog.require('i18n.input.chrome.inputview.SwipeDirection');
+goog.require('i18n.input.chrome.inputview.elements.ElementType');
+goog.require('i18n.input.chrome.inputview.elements.content.Character');
+goog.require('i18n.input.chrome.inputview.elements.content.CharacterModel');
+goog.require('i18n.input.chrome.inputview.elements.content.GaussianEstimator');
+goog.require('i18n.input.chrome.inputview.elements.content.SoftKey');
+
+
+
+goog.scope(function() {
+var CharacterModel = i18n.input.chrome.inputview.elements.content.
+ CharacterModel;
+var Character = i18n.input.chrome.inputview.elements.content.Character;
+
+/**
+ * The class for a character key, it would be symbol or letter key which is
+ * different than modifier key and functional key.
+ *
+ * @param {string} id The id.
+ * @param {number} keyCode The key code.
+ * @param {!Array.<string>} characters The characters.
+ * @param {boolean} isLetterKey True if this is a letter key.
+ * @param {boolean} hasAltGrCharacterInTheKeyset True if there is altgr
+ * character in the keyset.
+ * @param {boolean} alwaysRenderAltGrCharacter True if always renders the altgr
+ * character.
+ * @param {!i18n.input.chrome.inputview.StateManager} stateManager The state
+ * manager.
+ * @param {boolean} isRTL Whether the key shows characters in a RTL layout.
+ * @param {goog.events.EventTarget=} opt_eventTarget The event target.
+ * @constructor
+ * @extends {i18n.input.chrome.inputview.elements.content.SoftKey}
+ */
+i18n.input.chrome.inputview.elements.content.CharacterKey = function(id,
+ keyCode, characters, isLetterKey, hasAltGrCharacterInTheKeyset,
+ alwaysRenderAltGrCharacter, stateManager, isRTL, opt_eventTarget) {
+ goog.base(this, id, i18n.input.chrome.inputview.elements.ElementType.
+ CHARACTER_KEY, opt_eventTarget);
+
+ /**
+ * The key code for sending key events.
+ *
+ * @type {number}
+ */
+ this.keyCode = keyCode;
+
+ /**
+ * The content to shown in the soft key.
+ *
+ * @type {!Array.<string>}
+ */
+ this.characters = characters;
+
+ /**
+ * True if this is a letter key.
+ *
+ * @type {boolean}
+ */
+ this.isLetterKey = isLetterKey;
+
+ /**
+ * True if there is altgr character in the keyset.
+ *
+ * @type {boolean}
+ * @private
+ */
+ this.hasAltGrCharacterInTheKeyset_ = hasAltGrCharacterInTheKeyset;
+
+ /**
+ * The state manager.
+ *
+ * @type {!i18n.input.chrome.inputview.StateManager}
+ * @private
+ */
+ this.stateManager_ = stateManager;
+
+ /**
+ * Whether the key shows characters in a RTL layout.
+ *
+ * @type {boolean}
+ * @private
+ */
+ this.isRTL_ = isRTL;
+
+ /**
+ * Whether to always renders the altgr character..
+ *
+ * @type {boolean}
+ * @private
+ */
+ this.alwaysRenderAltGrCharacter_ = alwaysRenderAltGrCharacter;
+
+ this.pointerConfig.longPressWithPointerUp = true;
+ this.pointerConfig.longPressDelay = 500;
+};
+goog.inherits(i18n.input.chrome.inputview.elements.content.CharacterKey,
+ i18n.input.chrome.inputview.elements.content.SoftKey);
+var CharacterKey = i18n.input.chrome.inputview.elements.content.CharacterKey;
+
+
+/**
+ * The flickerred character.
+ *
+ * @type {string}
+ */
+CharacterKey.prototype.flickerredCharacter = '';
+
+
+/**
+ * The state map.
+ *
+ * @type {!Array.<number>}
+ * @private
+ */
+CharacterKey.STATE_LIST_ = [
+ i18n.input.chrome.inputview.StateType.DEFAULT,
+ i18n.input.chrome.inputview.StateType.SHIFT,
+ i18n.input.chrome.inputview.StateType.ALTGR,
+ i18n.input.chrome.inputview.StateType.ALTGR |
+ i18n.input.chrome.inputview.StateType.SHIFT
+];
+
+
+/** @override */
+CharacterKey.prototype.createDom = function() {
+ goog.base(this, 'createDom');
+
+ var elem = this.getElement();
+ var dom = this.getDomHelper();
+
+ for (var i = 0; i < CharacterKey.STATE_LIST_.length; i++) {
+ var ch = this.characters.length > i ? this.characters[i] : '';
+ if (ch && ch != '\x00') {
+ var model = new CharacterModel(ch, this.isLetterKey,
+ this.hasAltGrCharacterInTheKeyset_,
+ this.alwaysRenderAltGrCharacter_,
+ CharacterKey.STATE_LIST_[i],
+ this.stateManager_,
+ this.getCapslockCharacter_(i));
+ var character = new Character(this.id + '-' + i, model, this.isRTL_);
+ this.addChild(character, true);
+ }
+ }
+};
+
+
+/**
+ * Gets the capslock character if have.
+ *
+ * @param {number} i .
+ * @private
+ * @return {string} .
+ */
+CharacterKey.prototype.getCapslockCharacter_ = function(i) {
+ var capslockCharacterIndex = i + 4;
+ if (this.characters.length > capslockCharacterIndex) {
+ return this.characters[capslockCharacterIndex];
+ }
+
+ return '';
+};
+
+
+/** @override */
+CharacterKey.prototype.resize = function(width,
+ height) {
+ goog.base(this, 'resize', width, height);
+
+ for (var i = 0; i < this.getChildCount(); i++) {
+ var child = /** @type {!i18n.input.chrome.inputview.elements.Element} */ (
+ this.getChildAt(i));
+ child.resize(this.availableWidth, this.availableHeight);
+ }
+
+ var elem = this.getElement();
+ this.topLeftCoordinate = goog.style.getClientPosition(elem);
+ this.centerCoordinate = new goog.math.Coordinate(
+ this.topLeftCoordinate.x + this.availableWidth / 2,
+ this.topLeftCoordinate.y + this.availableHeight / 2);
+ this.estimator = new i18n.input.chrome.inputview.elements.content.
+ GaussianEstimator(this.centerCoordinate,
+ this.stateManager_.covariance.getValue(this.type),
+ this.availableHeight / this.availableWidth);
+};
+
+
+/**
+ * Gets the alternative characters.
+ *
+ * @return {!Array.<string>} The characters.
+ */
+CharacterKey.prototype.getAltCharacters =
+ function() {
+ var altCharacters = [];
+ for (var i = 0; i < this.characters.length; i++) {
+ var ch = this.characters[i];
+ if (ch && ch != '\x00' && ch != this.getActiveCharacter()) {
+ goog.array.insert(altCharacters, ch);
+ }
+ }
+ return altCharacters;
+};
+
+
+/**
+ * The active letter.
+ *
+ * @return {string} The active letter.
+ */
+CharacterKey.prototype.getActiveCharacter =
+ function() {
+ if (this.flickerredCharacter) {
+ return this.flickerredCharacter;
+ }
+
+ for (var i = 0; i < this.getChildCount(); i++) {
+ var child = /** @type {!i18n.input.chrome.inputview.elements.content.
+ Character} */ (this.getChildAt(i));
+ if (child.isHighlighted()) {
+ return child.getContent();
+ }
+ }
+ return this.getChildAt(0).getContent();
+};
+
+
+
+/**
+ * Gets the character by gesture direction.
+ *
+ * @param {boolean} upOrDown True if up, false if down.
+ * @return {string} The character content.
+ */
+CharacterKey.prototype.getCharacterByGesture =
+ function(upOrDown) {
+ var hasAltGrState = this.stateManager_.hasState(
+ i18n.input.chrome.inputview.StateType.ALTGR);
+ var hasShiftState = this.stateManager_.hasState(i18n.input.chrome.inputview.
+ StateType.SHIFT);
+
+ if (upOrDown == hasShiftState) {
+ // When shift is on, we only take swipe down, otherwise we only
+ // take swipe up.
+ return '';
+ }
+
+ // The index is based on the characters in order:
+ // 0: Default
+ // 1: Shift
+ // 2: ALTGR
+ // 3: SHIFT + ALTGR
+ var index = 0;
+ if (upOrDown && hasAltGrState) {
+ index = 3;
+ } else if (upOrDown && !hasAltGrState) {
+ index = 1;
+ } else if (!upOrDown && hasAltGrState) {
+ index = 2;
+ }
+
+ var character = index >= this.getChildCount() ? null :
+ /** @type {!i18n.input.chrome.inputview.elements.content.Character} */
+ (this.getChildAt(index));
+ if (character && character.isVisible()) {
+ return character.getContent();
+ }
+ return '';
+};
+
+
+/** @override */
+CharacterKey.prototype.update = function() {
+ goog.base(this, 'update');
+
+ this.pointerConfig.flickerDirection = this.stateManager_.hasState(
+ i18n.input.chrome.inputview.StateType.SHIFT) ?
+ i18n.input.chrome.inputview.SwipeDirection.DOWN :
+ i18n.input.chrome.inputview.SwipeDirection.UP;
+};
+
+}); // goog.scope
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/charactermodel.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/charactermodel.js
new file mode 100644
index 0000000..76402c1
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/charactermodel.js
@@ -0,0 +1,254 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.elements.content.CharacterModel');
+
+goog.require('i18n.input.chrome.inputview.StateType');
+
+
+
+goog.scope(function() {
+
+
+
+/**
+ * The character model.
+ *
+ * @param {string} character The character.
+ * @param {boolean} belongToLetterKey True if this characters belongs to a
+ * letter key.
+ * @param {boolean} hasAltGrCharacterInTheKeyset True if this kind of key has
+ * altgr character.
+ * @param {boolean} alwaysRenderAltGrCharacter True if always renders the altgr
+ * character.
+ * @param {number} stateType The state type for this character.
+ * @param {!i18n.input.chrome.inputview.StateManager} stateManager The state
+ * manager.
+ * @param {string=} opt_capslockCharacter .
+ * @constructor
+ */
+i18n.input.chrome.inputview.elements.content.CharacterModel = function(
+ character, belongToLetterKey, hasAltGrCharacterInTheKeyset,
+ alwaysRenderAltGrCharacter, stateType, stateManager,
+ opt_capslockCharacter) {
+
+ /**
+ * The character.
+ *
+ * @type {string}
+ * @private
+ */
+ this.character_ = character;
+
+ /**
+ * The character for the capslock state.
+ *
+ * @private {string}
+ */
+ this.capslockCharacter_ = opt_capslockCharacter || '';
+
+ /**
+ * Whether this character is belong to a letter key.
+ *
+ * @type {boolean}
+ * @private
+ */
+ this.belongToLetterKey_ = belongToLetterKey;
+
+ /**
+ * The state.
+ *
+ * @type {number}
+ * @private
+ */
+ this.stateType_ = stateType;
+
+ /**
+ * The state manager.
+ *
+ * @type {!i18n.input.chrome.inputview.StateManager}
+ * @private
+ */
+ this.stateManager_ = stateManager;
+
+ /**
+ * Whether to always renders the altgr character..
+ *
+ * @type {boolean}
+ * @private
+ */
+ this.alwaysRenderAltGrCharacter_ = alwaysRenderAltGrCharacter;
+
+ /**
+ * True if this key set has altgr character.
+ *
+ * @type {boolean}
+ * @private
+ */
+ this.hasAltGrCharacterInTheKeyset_ = hasAltGrCharacterInTheKeyset;
+};
+var CharacterModel = i18n.input.chrome.inputview.elements.content.
+ CharacterModel;
+
+
+/**
+ * The alignment type.
+ *
+ * @enum {number}
+ */
+CharacterModel.AlignType = {
+ CENTER: 0,
+ CORNER: 1
+};
+
+
+/**
+ * The position attributes.
+ *
+ * @type {!Array.<!Array.<string>>}
+ * @private
+ */
+CharacterModel.CORNERS_ = [
+ ['bottom', 'left'],
+ ['top', 'left'],
+ ['bottom', 'right'],
+ ['top', 'right']
+];
+
+
+/**
+ * True if this character is highlighed.
+ *
+ * @return {boolean} True if the character is highlighted.
+ */
+CharacterModel.prototype.isHighlighted = function() {
+ var state = this.stateManager_.getState();
+ state = state & (i18n.input.chrome.inputview.StateType.SHIFT |
+ i18n.input.chrome.inputview.StateType.ALTGR);
+ return this.stateType_ == state;
+};
+
+
+/**
+ * True if this character is visible.
+ *
+ * @return {boolean} True if the character is visible.
+ */
+CharacterModel.prototype.isVisible = function() {
+ if (this.stateType_ == i18n.input.chrome.inputview.StateType.DEFAULT) {
+ return !this.stateManager_.hasState(
+ i18n.input.chrome.inputview.StateType.ALTGR) && (
+ !this.belongToLetterKey_ || !this.stateManager_.hasState(
+ i18n.input.chrome.inputview.StateType.SHIFT));
+ }
+ if (this.stateType_ == i18n.input.chrome.inputview.StateType.SHIFT) {
+ return !this.stateManager_.hasState(
+ i18n.input.chrome.inputview.StateType.ALTGR) && (
+ !this.belongToLetterKey_ || this.stateManager_.hasState(
+ i18n.input.chrome.inputview.StateType.SHIFT));
+ }
+ if ((this.stateType_ & i18n.input.chrome.inputview.StateType.ALTGR) != 0) {
+ // AltGr or AltGr+Shift character.
+ return this.alwaysRenderAltGrCharacter_ || this.stateManager_.
+ hasState(i18n.input.chrome.inputview.StateType.ALTGR);
+ }
+ return false;
+};
+
+
+/**
+ * Gets the reversed case character.
+ *
+ * @return {string} The reversed character
+ * @private
+ */
+CharacterModel.prototype.toReversedCase_ = function() {
+ var reversed;
+ if (this.character_.toUpperCase() == this.character_) {
+ reversed = this.character_.toLowerCase();
+ } else {
+ reversed = this.character_.toUpperCase();
+ }
+ return reversed;
+};
+
+
+/**
+ * Gets the content of this character..
+ *
+ * @return {string} The content.
+ */
+CharacterModel.prototype.getContent = function() {
+ if (this.stateManager_.hasState(
+ i18n.input.chrome.inputview.StateType.CAPSLOCK)) {
+ return this.capslockCharacter_ ? this.capslockCharacter_ :
+ this.toReversedCase_();
+ }
+
+ return this.character_;
+};
+
+
+/**
+ * True if align the character in the center horizontally.
+ *
+ * @return {boolean} True to align in the center.
+ */
+CharacterModel.prototype.isHorizontalAlignCenter = function() {
+ if (this.stateType_ == i18n.input.chrome.inputview.StateType.DEFAULT ||
+ this.stateType_ == i18n.input.chrome.inputview.StateType.SHIFT) {
+ return !this.alwaysRenderAltGrCharacter_ ||
+ !this.hasAltGrCharacterInTheKeyset_;
+ }
+
+ return false;
+};
+
+
+/**
+ * True to align the character in the center vertically.
+ *
+ * @return {boolean} True to be in the center.
+ */
+CharacterModel.prototype.isVerticalAlignCenter = function() {
+ if (this.stateType_ == i18n.input.chrome.inputview.StateType.DEFAULT ||
+ this.stateType_ == i18n.input.chrome.inputview.StateType.SHIFT) {
+ return this.belongToLetterKey_;
+ }
+
+ return false;
+};
+
+
+/**
+ * Gets the attribute for position.
+ *
+ * @return {!Array.<string>} The attributes.
+ */
+CharacterModel.prototype.getPositionAttribute = function() {
+ var index;
+ switch (this.stateType_) {
+ case i18n.input.chrome.inputview.StateType.DEFAULT:
+ return CharacterModel.CORNERS_[0];
+ case i18n.input.chrome.inputview.StateType.SHIFT:
+ return CharacterModel.CORNERS_[1];
+ case i18n.input.chrome.inputview.StateType.ALTGR:
+ return CharacterModel.CORNERS_[2];
+ default:
+ return CharacterModel.CORNERS_[3];
+ }
+};
+
+
+}); // goog.scope
+
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/compactkey.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/compactkey.js
new file mode 100644
index 0000000..aab41f2
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/compactkey.js
@@ -0,0 +1,252 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.elements.content.CompactKey');
+
+goog.require('goog.array');
+goog.require('goog.dom');
+goog.require('goog.dom.TagName');
+goog.require('goog.dom.classlist');
+goog.require('goog.math.Coordinate');
+goog.require('goog.style');
+goog.require('i18n.input.chrome.inputview.Css');
+goog.require('i18n.input.chrome.inputview.MoreKeysShiftOperation');
+goog.require('i18n.input.chrome.inputview.StateType');
+goog.require('i18n.input.chrome.inputview.SwipeDirection');
+goog.require('i18n.input.chrome.inputview.elements.ElementType');
+goog.require('i18n.input.chrome.inputview.elements.content.FunctionalKey');
+goog.require('i18n.input.chrome.inputview.elements.content.GaussianEstimator');
+
+
+
+goog.scope(function() {
+var MoreKeysShiftOperation = i18n.input.chrome.inputview.MoreKeysShiftOperation;
+
+
+
+/**
+ * The key to switch between different key set.
+ *
+ * @param {string} id The id.
+ * @param {string} text The text.
+ * @param {string} hintText The hint text.
+ * @param {!i18n.input.chrome.inputview.StateManager} stateManager The state
+ * manager.
+ * @param {boolean} hasShift True if the compact key has shift.}
+ * @param {number=} opt_marginLeftPercent The percent of the left margin.
+ * @param {number=} opt_marginRightPercent The percent of the right margin.
+ * @param {boolean=} opt_isGrey True if it is grey.
+ * @param {!Array.<string>=} opt_moreKeys The more keys characters.
+ * @param {MoreKeysShiftOperation=} opt_moreKeysShiftType
+ * The type of opearation when the shift key is down.
+ * @param {goog.events.EventTarget=} opt_eventTarget The event target.
+ * @constructor
+ * @extends {i18n.input.chrome.inputview.elements.content.FunctionalKey}
+ */
+i18n.input.chrome.inputview.elements.content.CompactKey = function(id, text,
+ hintText, stateManager, hasShift, opt_marginLeftPercent,
+ opt_marginRightPercent, opt_isGrey, opt_moreKeys,
+ opt_moreKeysShiftType, opt_eventTarget) {
+ goog.base(this, id, i18n.input.chrome.inputview.elements.ElementType.
+ COMPACT_KEY, text, '', opt_eventTarget);
+
+ /**
+ * The hint text.
+ *
+ * @type {string}
+ */
+ this.hintText = hintText;
+
+ /**
+ * The left margin.
+ *
+ * @type {number}
+ * @private
+ */
+ this.marginLeftPercent_ = opt_marginLeftPercent || 0;
+
+ /** @private {boolean} */
+ this.hasShift_ = hasShift;
+
+ /**
+ * The right margin.
+ *
+ * @type {number}
+ * @private
+ */
+ this.marginRightPercent_ = opt_marginRightPercent || 0;
+
+ /**
+ * The state manager.
+ *
+ * @type {!i18n.input.chrome.inputview.StateManager}
+ * @private
+ */
+ this.stateManager_ = stateManager;
+
+ /**
+ * True if it is grey.
+ *
+ * @type {boolean}
+ * @private
+ */
+ this.isGrey_ = !!opt_isGrey;
+
+ /**
+ * The more keys array.
+ *
+ * @type {!Array.<string>}
+ */
+ this.moreKeys = opt_moreKeys || [];
+
+ /**
+ * The type of shift operation of moreKeys.
+ *
+ * @private {MoreKeysShiftOperation}
+ */
+ this.moreKeysShiftOperation_ = goog.isDef(opt_moreKeysShiftType) ?
+ opt_moreKeysShiftType : MoreKeysShiftOperation.TO_UPPER_CASE;
+
+ this.pointerConfig.longPressWithPointerUp = true;
+ this.pointerConfig.flickerDirection =
+ i18n.input.chrome.inputview.SwipeDirection.UP;
+ this.pointerConfig.longPressDelay = 500;
+};
+goog.inherits(i18n.input.chrome.inputview.elements.content.CompactKey,
+ i18n.input.chrome.inputview.elements.content.FunctionalKey);
+var CompactKey = i18n.input.chrome.inputview.elements.content.CompactKey;
+
+
+/**
+ * The flickerred character.
+ *
+ * @type {string}
+ */
+CompactKey.prototype.flickerredCharacter = '';
+
+
+/** @override */
+CompactKey.prototype.createDom = function() {
+ goog.base(this, 'createDom');
+
+ goog.dom.classlist.add(this.tableCell,
+ i18n.input.chrome.inputview.Css.COMPACT_KEY);
+ if (!this.isGrey_) {
+ goog.dom.classlist.remove(this.bgElem,
+ i18n.input.chrome.inputview.Css.SPECIAL_KEY_BG);
+ }
+
+ if (this.hintText) {
+ var dom = this.getDomHelper();
+ dom.removeChildren(this.tableCell);
+ var inlineWrap = dom.createDom(goog.dom.TagName.DIV,
+ i18n.input.chrome.inputview.Css.INLINE_DIV);
+ dom.appendChild(this.tableCell, inlineWrap);
+ this.hintTextElem = dom.createDom(goog.dom.TagName.DIV,
+ i18n.input.chrome.inputview.Css.HINT_TEXT, this.hintText);
+ dom.appendChild(inlineWrap, this.hintTextElem);
+ dom.appendChild(inlineWrap, this.textElem);
+ }
+};
+
+
+/** @override */
+CompactKey.prototype.resize = function(width, height) {
+ var elem = this.getElement();
+ var marginLeft = Math.floor(width * this.marginLeftPercent_);
+ if (marginLeft > 0) {
+ marginLeft -= 5;
+ elem.style.marginLeft = marginLeft + 'px';
+ }
+ var marginRight = Math.floor(width * this.marginRightPercent_);
+ // TODO: Remove this ugly hack. The default margin right is 10px, we
+ // need to add the default margin here to make all the keys have the same
+ // look.
+ if (marginRight > 0) {
+ marginRight += 5;
+ elem.style.marginRight = marginRight + 'px';
+ }
+
+ goog.base(this, 'resize', width, height);
+
+ this.topLeftCoordinate = goog.style.getClientPosition(elem);
+ this.centerCoordinate = new goog.math.Coordinate(
+ this.topLeftCoordinate.x + this.availableWidth / 2,
+ this.topLeftCoordinate.y + this.availableHeight / 2);
+ this.estimator = new i18n.input.chrome.inputview.elements.content.
+ GaussianEstimator(this.centerCoordinate,
+ this.stateManager_.covariance.getValue(this.type),
+ this.availableHeight / this.availableWidth);
+};
+
+
+/**
+ * Get the active character. It may be upper case |text| when shift is pressed
+ * or flickerred character when swipe. Note this should replace Compactkey.text
+ * for compact keys.
+ */
+CompactKey.prototype.getActiveCharacter = function() {
+ if (this.flickerredCharacter) {
+ return this.flickerredCharacter;
+ } else {
+ return this.hasShift_ && this.stateManager_.hasState(
+ i18n.input.chrome.inputview.StateType.SHIFT) ? this.text.toUpperCase() :
+ this.text;
+ }
+};
+
+
+/** @override */
+CompactKey.prototype.update = function() {
+ goog.base(this, 'update');
+
+ var text = this.hasShift_ && this.stateManager_.hasState(
+ i18n.input.chrome.inputview.StateType.SHIFT) ? this.text.toUpperCase() :
+ this.text;
+ goog.dom.setTextContent(this.textElem, text);
+};
+
+
+/**
+ * Gets the more characters.
+ *
+ * @return {!Array.<string>} The characters.
+ */
+CompactKey.prototype.getMoreCharacters = function() {
+ var moreCharacters = goog.array.clone(this.moreKeys);
+ switch (this.moreKeysShiftOperation_) {
+ case MoreKeysShiftOperation.TO_UPPER_CASE:
+ if (this.getActiveCharacter().toLowerCase() !=
+ this.getActiveCharacter()) {
+ for (var i = 0; i < this.moreKeys.length; i++) {
+ moreCharacters[i] = this.moreKeys[i].toUpperCase();
+ }
+ goog.array.removeDuplicates(moreCharacters);
+ }
+ return moreCharacters;
+ case MoreKeysShiftOperation.TO_LOWER_CASE:
+ if (this.hasShift_ && this.stateManager_.hasState(
+ i18n.input.chrome.inputview.StateType.SHIFT)) {
+ for (var i = 0; i < this.moreKeys.length; i++) {
+ moreCharacters[i] = this.moreKeys[i].toLowerCase();
+ }
+ goog.array.removeDuplicates(moreCharacters);
+ }
+ return moreCharacters;
+ case MoreKeysShiftOperation.STABLE:
+ break;
+ }
+ return moreCharacters;
+};
+}); // goog.scope
+
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/emojikey.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/emojikey.js
new file mode 100644
index 0000000..60f32c88
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/emojikey.js
@@ -0,0 +1,117 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.elements.content.EmojiKey');
+goog.require('goog.dom');
+goog.require('goog.dom.TagName');
+goog.require('goog.dom.classlist');
+goog.require('i18n.input.chrome.inputview.Css');
+goog.require('i18n.input.chrome.inputview.elements.content.FunctionalKey');
+goog.require('i18n.input.chrome.message.Name');
+goog.require('i18n.input.chrome.message.Type');
+
+goog.scope(function() {
+
+var Type = i18n.input.chrome.message.Type;
+var Name = i18n.input.chrome.message.Name;
+
+
+
+/**
+ * The emoji key
+ *
+ * @param {string} id The id.
+ * @param {!i18n.input.chrome.inputview.elements.ElementType} type The element
+ * type.
+ * @param {string} text The text.
+ * @param {string} iconCssClass The css class for the icon.
+ * @param {boolean} isEmoticon Wether it is an emoticon.
+ * @param {goog.events.EventTarget=} opt_eventTarget The event target.
+ * @constructor
+ * @extends {i18n.input.chrome.inputview.elements.content.FunctionalKey}
+ */
+i18n.input.chrome.inputview.elements.content.EmojiKey = function(id, type,
+ text, iconCssClass, isEmoticon, opt_eventTarget) {
+ i18n.input.chrome.inputview.elements.content.EmojiKey.base(
+ this, 'constructor', id, type, text, '', opt_eventTarget);
+
+ /**
+ * Wether it is an emoticon.
+ *
+ * @private {boolean}
+ */
+ this.isEmoticon_ = isEmoticon;
+
+ this.pointerConfig.stopEventPropagation = false;
+
+ this.pointerConfig.dblClick = true;
+ this.pointerConfig.longPressWithPointerUp = true;
+ this.pointerConfig.longPressDelay = 200;
+
+};
+goog.inherits(i18n.input.chrome.inputview.elements.content.EmojiKey,
+ i18n.input.chrome.inputview.elements.content.FunctionalKey);
+var EmojiKey = i18n.input.chrome.inputview.elements.content.EmojiKey;
+
+
+/** @override */
+EmojiKey.prototype.createDom = function() {
+ goog.base(this, 'createDom');
+ var dom = this.getDomHelper();
+ var elem = this.getElement();
+ if (!this.textElem) {
+ this.textElem = dom.createDom(goog.dom.TagName.DIV,
+ i18n.input.chrome.inputview.Css.SPECIAL_KEY_NAME, this.text);
+ dom.appendChild(this.tableCell, this.textElem);
+ }
+ // Special size for emojitcon.
+ if (this.isEmoticon_) {
+ this.textElem.style.fontSize = '20px';
+ }
+ goog.dom.classlist.remove(elem, i18n.input.chrome.inputview.Css.SOFT_KEY);
+ goog.dom.classlist.remove(this.bgElem,
+ i18n.input.chrome.inputview.Css.SPECIAL_KEY_BG);
+ goog.dom.classlist.add(this.bgElem,
+ i18n.input.chrome.inputview.Css.EMOJI_KEY);
+};
+
+
+/** @override */
+EmojiKey.prototype.setHighlighted = function(highlight) {
+ if (highlight) {
+ goog.dom.classlist.add(this.bgElem,
+ i18n.input.chrome.inputview.Css.EMOJI_KEY_HIGHLIGHT);
+ } else {
+ goog.dom.classlist.remove(this.bgElem,
+ i18n.input.chrome.inputview.Css.EMOJI_KEY_HIGHLIGHT);
+ }
+};
+
+
+/** @override */
+EmojiKey.prototype.update = function() {
+ goog.base(this, 'update');
+ goog.dom.setTextContent(this.textElem, this.text);
+};
+
+
+/**
+ * Update the emoji's text
+ *
+ * @param {string} text The new text.
+ */
+EmojiKey.prototype.updateText = function(text) {
+ this.text = text;
+ goog.dom.setTextContent(this.textElem, text);
+};
+}); // goog.scope
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/emojiview.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/emojiview.js
new file mode 100644
index 0000000..239650b
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/emojiview.js
@@ -0,0 +1,423 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.elements.content.EmojiView');
+
+goog.require('goog.array');
+goog.require('goog.dom.classlist');
+goog.require('i18n.input.chrome.inputview.Css');
+goog.require('i18n.input.chrome.inputview.SpecNodeName');
+goog.require('i18n.input.chrome.inputview.elements.ElementType');
+goog.require('i18n.input.chrome.inputview.elements.content.KeysetView');
+goog.require('i18n.input.chrome.inputview.events.EventType');
+goog.require('i18n.input.chrome.inputview.handler.PointerHandler');
+
+
+goog.scope(function() {
+var ElementType = i18n.input.chrome.inputview.elements.ElementType;
+var EventType = i18n.input.chrome.inputview.events.EventType;
+var KeysetView = i18n.input.chrome.inputview.elements.content.KeysetView;
+var PointerHandler = i18n.input.chrome.inputview.handler.PointerHandler;
+var Css = i18n.input.chrome.inputview.Css;
+var SpecNodeName = i18n.input.chrome.inputview.SpecNodeName;
+var ExtendedLayout = i18n.input.chrome.inputview.elements.layout.ExtendedLayout;
+var PageIndicator = i18n.input.chrome.inputview.elements.content.PageIndicator;
+
+
+
+/**
+ * The emoji view.
+ *
+ * @param {!Object} keyData The data includes soft key definition and key
+ * mapping.
+ * @param {!Object} layoutData The layout definition.
+ * @param {string} keyboardCode The keyboard code.
+ * @param {string} languageCode The language code.
+ * @param {!i18n.input.chrome.inputview.Model} model The model.
+ * @param {string} name The Input Tool name.
+ * @param {!goog.events.EventTarget=} opt_eventTarget .
+ * @param {i18n.input.chrome.inputview.Adapter=} opt_adapter .
+ * @constructor
+ * @extends {KeysetView}
+ */
+i18n.input.chrome.inputview.elements.content.EmojiView = function(keyData,
+ layoutData, keyboardCode, languageCode, model, name, opt_eventTarget,
+ opt_adapter) {
+ i18n.input.chrome.inputview.elements.content.EmojiView.base(this,
+ 'constructor', keyData, layoutData, keyboardCode, languageCode, model,
+ name, opt_eventTarget, opt_adapter);
+
+ /**
+ * The number of keys per emoji page.
+ *
+ * @private {number}
+ */
+ this.keysPerPage_ = 27;
+
+ /**
+ * The number of tabbar keys.
+ *
+ * @private {number}
+ */
+ this.totalTabbars_ = keyData[SpecNodeName.TEXT].length;
+
+ /**
+ * The first page offset of each category.
+ *
+ * @private {!Array.<number>}
+ */
+ this.pageOffsets_ = [];
+
+ /**
+ * The number of pages for each category.
+ *
+ * @private {!Array.<number>}
+ */
+ this.pagesInCategory_ = [];
+
+ // Calculate the emojiPageoffset_ and totalPages_ according to keydata.
+ var pageNum = 0;
+ for (var i = 0, len = keyData[SpecNodeName.TEXT].length; i < len; ++i) {
+ this.pageOffsets_.push(pageNum);
+ pageNum += Math.ceil(
+ keyData[SpecNodeName.TEXT][i].length / this.keysPerPage_);
+ }
+
+ /**
+ * The category of each emoji page.
+ *
+ * @private {!Array.<number>}
+ */
+ this.pageToCategory_ = [];
+
+ // Calculate the pageToCategory_ according to keydata.
+ for (var i = 0, len = keyData[SpecNodeName.TEXT].length; i < len; ++i) {
+ var lenJ = Math.ceil(
+ keyData[SpecNodeName.TEXT][i].length / this.keysPerPage_);
+ for (var j = 0; j < lenJ; ++j) {
+ this.pageToCategory_.push(i);
+ }
+ this.pagesInCategory_.push(lenJ);
+ }
+
+ /**
+ * The list of recent used emoji.
+ *
+ * @private {Array.<string>}
+ */
+ this.recentEmojiList_ = [];
+
+ /**
+ * The emoji keys on the recent page.
+ *
+ * @private {Array.<i18n.input.chrome.inputview.elements.content.EmojiKey>}
+ */
+ this.recentEmojiKeys_ = [];
+
+ /**
+ * The tabbars of the emoji view.
+ *
+ * @private {Array.<i18n.input.chrome.inputview.elements.content.TabBarKey>}
+ */
+ this.tabbarKeys_ = [];
+};
+var EmojiView = i18n.input.chrome.inputview.elements.content.EmojiView;
+goog.inherits(EmojiView, KeysetView);
+
+
+/**
+ * The emoji rows of the emoji slider.
+ *
+ * @private {!i18n.input.chrome.inputview.elements.layout.ExtendedLayout}
+ */
+EmojiView.prototype.emojiRows_;
+
+
+/**
+ * The indicator of the emoji page index.
+ *
+ * @private {!i18n.input.chrome.inputview.elements.content.PageIndicator}
+ */
+EmojiView.prototype.pageIndicator_;
+
+
+/**
+ * Whether it is a drag event.
+ *
+ * @type {boolean}
+ */
+EmojiView.prototype.isDragging = false;
+
+
+/**
+ * Whether it is a drag event.
+ *
+ * @private {number}
+ */
+EmojiView.prototype.categoryID_ = 0;
+
+
+/**
+ * The timestamp of the last pointer down event.
+ *
+ * @private {number}
+ */
+EmojiView.prototype.pointerDownTimeStamp_ = 0;
+
+
+/**
+ * The drag distance of a drag event.
+ *
+ * @private {number}
+ */
+EmojiView.prototype.dragDistance_ = 0;
+
+
+/**
+ * The maximal required time interval for quick emoji page swipe in ms.
+ *
+ * @private {number}
+ */
+EmojiView.EMOJI_DRAG_INTERVAL_ = 300;
+
+
+/**
+ * The minimal required drag distance for quick emoji page swipe in px.
+ *
+ * @private {number}
+ */
+EmojiView.EMOJI_DRAG_DISTANCE_ = 60;
+
+
+/** @private {!PointerHandler} */
+EmojiView.prototype.pointerHandler_;
+
+
+/** @override */
+EmojiView.prototype.createDom = function() {
+ goog.base(this, 'createDom');
+ var elem = this.getElement();
+ if (elem) {
+ this.pointerHandler_ = new PointerHandler(elem);
+ }
+ this.getHandler().
+ listen(this.pointerHandler_, EventType.POINTER_DOWN,
+ this.onPointerDown_).
+ listen(this.pointerHandler_, EventType.POINTER_UP, this.onPointerUp_).
+ listen(this.pointerHandler_, EventType.DRAG, this.onDragEvent_).
+ listen(this.pointerHandler_, EventType.LONG_PRESS, this.onLongPress_);
+ this.emojiRows_ =
+ /** @type {!ExtendedLayout} */ (this.getChildViewById('emojiRows'));
+ this.pageIndicator_ =
+ /** @type {!PageIndicator} */
+ (this.getChildViewById('indicator-background'));
+ for (var i = 0; i < this.keysPerPage_; i++) {
+ this.recentEmojiKeys_.push(
+ /** @type {!i18n.input.chrome.inputview.elements.content.EmojiKey} */
+ (this.getChildViewById('emojikey' + i)));
+ }
+ for (var i = 0; i < this.totalTabbars_; i++) {
+ this.tabbarKeys_.push(
+ /** @type {!i18n.input.chrome.inputview.elements.content.TabBarKey} */
+ (this.getChildViewById('Tabbar' + i)));
+ }
+};
+
+
+/**
+ * Handles the pointer down event.
+ *
+ * @param {!i18n.input.chrome.inputview.events.PointerEvent} e .
+ * @private
+ */
+EmojiView.prototype.onPointerDown_ = function(e) {
+ var view = e.view;
+ if (view.type == ElementType.EMOJI_KEY) {
+ this.pointerDownTimeStamp_ = e.timestamp;
+ this.dragDistance_ = 0;
+ return;
+ }
+};
+
+
+/**
+ * Handles the pointer up event.
+ *
+ * @param {!i18n.input.chrome.inputview.events.PointerEvent} e .
+ * @private
+ */
+EmojiView.prototype.onPointerUp_ = function(e) {
+ var view = e.view;
+ switch (view.type) {
+ case ElementType.EMOJI_KEY:
+ if (this.isDragging) {
+ var interval = e.timestamp - this.pointerDownTimeStamp_;
+ if (interval < EmojiView.EMOJI_DRAG_INTERVAL_ &&
+ Math.abs(this.dragDistance_) >= EmojiView.EMOJI_DRAG_DISTANCE_) {
+ this.adjustMarginLeft_(this.dragDistance_);
+ } else {
+ this.adjustMarginLeft_();
+ }
+
+ this.isDragging = false;
+ } else if (view.text != '') {
+ this.setRecentEmoji_(view.text);
+ }
+ this.update();
+ return;
+ case ElementType.TAB_BAR_KEY:
+ this.updateCategory_(view);
+ this.updateTabbarBorder_();
+ return;
+ }
+};
+
+
+/**
+ * Handles the drag event.
+ *
+ * @param {!i18n.input.chrome.inputview.events.PointerEvent} e .
+ * @private
+ */
+EmojiView.prototype.onDragEvent_ = function(e) {
+ var view = e.view;
+ this.isDragging = true;
+ if (view.type == ElementType.EMOJI_KEY) {
+ this.setEmojiMarginLeft_(e.deltaX);
+ this.dragDistance_ += e.deltaX;
+ }
+};
+
+
+/**
+ * Handles the long press event.
+ *
+ * @param {!i18n.input.chrome.inputview.events.PointerEvent} e .
+ * @private
+ */
+EmojiView.prototype.onLongPress_ = function(e) {
+ var view = e.view;
+ view.setHighlighted(this.isDragging == false && view.text != '');
+};
+
+
+/** @override */
+EmojiView.prototype.disposeInternal = function() {
+ goog.dispose(this.pointerHandler_);
+
+ goog.base(this, 'disposeInternal');
+};
+
+
+/**
+ * Set the margin left of the emoji slider.
+ *
+ * @param {number} deltaX The margin left value.
+ * @private
+ */
+EmojiView.prototype.setEmojiMarginLeft_ = function(deltaX) {
+ this.emojiRows_.slide(deltaX);
+ this.pageIndicator_.slide(-deltaX,
+ this.pagesInCategory_[this.categoryID_]);
+};
+
+
+/**
+ * Update the current emoji category.
+ *
+ * @param {i18n.input.chrome.inputview.elements.Element} view The view.
+ * @private
+ */
+EmojiView.prototype.updateCategory_ = function(view) {
+ this.categoryID_ = view.toCategory;
+ this.emojiRows_.updateCategory(this.pageOffsets_[this.categoryID_]);
+ this.pageIndicator_.gotoPage(0,
+ this.pagesInCategory_[this.categoryID_]);
+ this.updateTabbarBorder_();
+};
+
+
+/**
+ * Adjust the margin left to the nearest page.
+ *
+ * @param {number=} opt_distance The distance to adjust to.
+ * @private
+ */
+EmojiView.prototype.adjustMarginLeft_ = function(opt_distance) {
+ var pageNum = this.emojiRows_.adjustMarginLeft(opt_distance);
+ this.categoryID_ = this.pageToCategory_[pageNum];
+ this.pageIndicator_.gotoPage(
+ pageNum - this.pageOffsets_[this.categoryID_],
+ this.pagesInCategory_[this.categoryID_]);
+ this.updateTabbarBorder_();
+
+};
+
+
+/**
+ * Clear all the states for the emoji.
+ *
+ */
+EmojiView.prototype.clearEmojiStates = function() {
+ this.categoryID_ = 1;
+ this.emojiRows_.updateCategory(1);
+ this.pageIndicator_.gotoPage(0, this.pagesInCategory_[1]);
+ this.updateTabbarBorder_();
+};
+
+
+/**
+ * Sets the recent emoji.
+ *
+ * @param {string} text The recent emoji text.
+ * @private
+ */
+EmojiView.prototype.setRecentEmoji_ = function(text) {
+ goog.array.insertAt(this.recentEmojiList_, text, 0);
+ goog.array.removeDuplicates(this.recentEmojiList_);
+ var len = this.recentEmojiList_.length;
+ for (var i = 0; i < this.keysPerPage_; i++) {
+ var newText = i < len ? this.recentEmojiList_[i] : '';
+ this.recentEmojiKeys_[i].updateText(newText);
+ }
+};
+
+
+/**
+ * Update the tabbar's border.
+ *
+ * @private
+ */
+EmojiView.prototype.updateTabbarBorder_ = function() {
+ for (var i = 0, len = this.totalTabbars_; i < len; i++) {
+ this.tabbarKeys_[i].updateBorder(this.categoryID_);
+ }
+};
+
+
+/** @override */
+EmojiView.prototype.activate = function(rawKeyset) {
+ this.adapter.setEmojiInputToolCode();
+ goog.dom.classlist.add(this.getElement().parentElement.parentElement,
+ Css.EMOJI);
+ this.clearEmojiStates();
+};
+
+
+/** @override */
+EmojiView.prototype.deactivate = function(rawKeyset) {
+ this.adapter.unsetEmojiInputToolCode();
+ goog.dom.classlist.remove(this.getElement().parentElement.parentElement,
+ Css.EMOJI);
+};
+}); // goog.scope
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/enswitcherkey.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/enswitcherkey.js
new file mode 100644
index 0000000..7ca84e7
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/enswitcherkey.js
@@ -0,0 +1,102 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.elements.content.EnSwitcherKey');
+goog.require('goog.dom');
+goog.require('goog.dom.TagName');
+goog.require('goog.dom.classlist');
+goog.require('i18n.input.chrome.inputview.Css');
+goog.require('i18n.input.chrome.inputview.elements.content.FunctionalKey');
+goog.require('i18n.input.chrome.message.Name');
+goog.require('i18n.input.chrome.message.Type');
+
+
+
+goog.scope(function() {
+
+var Type = i18n.input.chrome.message.Type;
+var Name = i18n.input.chrome.message.Name;
+
+/**
+ * The switcher key to switch to engish.
+ *
+ * @param {string} id The id.
+ * @param {!i18n.input.chrome.inputview.elements.ElementType} type The element
+ * type.
+ * @param {string} text The text.
+ * @param {i18n.input.chrome.inputview.Css} iconCssClass The css for the icon.
+ * @param {!i18n.input.chrome.inputview.StateManager} stateManager The state
+ * manager.
+ * @param {i18n.input.chrome.inputview.Css} defaultCss
+ * The Css for the default icon.
+ * @param {i18n.input.chrome.inputview.Css} englishCss
+ * The Css for the english icon.
+ * @constructor
+ * @extends {i18n.input.chrome.inputview.elements.content.FunctionalKey}
+ */
+i18n.input.chrome.inputview.elements.content.EnSwitcherKey = function(id, type,
+ text, iconCssClass, stateManager, defaultCss, englishCss) {
+ i18n.input.chrome.inputview.elements.content.EnSwitcherKey.base(
+ this, 'constructor', id, type, text, '');
+
+ /**
+ * The current icon css.
+ *
+ * @private {i18n.input.chrome.inputview.Css}
+ */
+ this.currentIconCss_ = defaultCss;
+
+ /**
+ * The state manager.
+ *
+ * @private {i18n.input.chrome.inputview.StateManager}
+ */
+ this.stateManager_ = stateManager;
+
+ /**
+ * The default iconCss for a given keyset.
+ *
+ * @private {i18n.input.chrome.inputview.Css}
+ */
+ this.defaultIconCss_ = defaultCss;
+
+ /**
+ * The iconCss for the english mode.
+ *
+ * @private {i18n.input.chrome.inputview.Css}
+ */
+ this.enIconCss_ = englishCss;
+};
+goog.inherits(i18n.input.chrome.inputview.elements.content.EnSwitcherKey,
+ i18n.input.chrome.inputview.elements.content.FunctionalKey);
+var EnSwitcherKey = i18n.input.chrome.inputview.elements.content.EnSwitcherKey;
+
+
+/** @override */
+EnSwitcherKey.prototype.createDom = function() {
+ goog.base(this, 'createDom');
+ var dom = this.getDomHelper();
+ this.iconElem = dom.createDom(goog.dom.TagName.DIV, this.currentIconCss_);
+ dom.appendChild(this.tableCell, this.iconElem);
+};
+
+
+/** @override */
+EnSwitcherKey.prototype.update = function() {
+ goog.base(this, 'update');
+ var isEnMode = this.stateManager_.isEnMode;
+ goog.dom.classlist.remove(this.iconElem, this.currentIconCss_);
+ this.currentIconCss_ = isEnMode ? this.enIconCss_ : this.defaultIconCss_;
+ goog.dom.classlist.add(this.iconElem, this.currentIconCss_);
+};
+}); // goog.scope
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/expandedcandidateview.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/expandedcandidateview.js
new file mode 100644
index 0000000..8c56df1
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/expandedcandidateview.js
@@ -0,0 +1,326 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.elements.content.ExpandedCandidateView');
+
+goog.require('goog.dom.TagName');
+goog.require('goog.dom.classlist');
+goog.require('goog.style');
+goog.require('i18n.input.chrome.inputview.Css');
+goog.require('i18n.input.chrome.inputview.elements.Element');
+goog.require('i18n.input.chrome.inputview.elements.ElementType');
+goog.require('i18n.input.chrome.inputview.elements.content.Candidate');
+goog.require('i18n.input.chrome.inputview.elements.content.FunctionalKey');
+
+
+goog.scope(function() {
+
+var Css = i18n.input.chrome.inputview.Css;
+var TagName = goog.dom.TagName;
+var Candidate = i18n.input.chrome.inputview.elements.content.Candidate;
+var Type = i18n.input.chrome.inputview.elements.content.Candidate.Type;
+var ElementType = i18n.input.chrome.inputview.elements.ElementType;
+var FunctionalKey = i18n.input.chrome.inputview.elements.content.FunctionalKey;
+
+
+
+/**
+ * The expanded candidate view.
+ *
+ * @param {!goog.events.EventTarget=} opt_eventTarget .
+ * @constructor
+ * @extends {i18n.input.chrome.inputview.elements.Element}
+ */
+i18n.input.chrome.inputview.elements.content.ExpandedCandidateView = function(
+ opt_eventTarget) {
+ goog.base(this, 'expandedCandidateView', ElementType.
+ EXPANDED_CANDIDATE_VIEW, opt_eventTarget);
+
+ /**
+ * The four lines.
+ *
+ * @private {!Array.<!Element>}
+ */
+ this.lines_ = [];
+
+ /**
+ * The functional keys at the right.
+ *
+ * @private {!Array.<FunctionalKey>}
+ */
+ this.keys_ = [];
+
+ /**
+ * Key: page index.
+ * Value: candidate start index.
+ *
+ * @private {!Object.<number, number>}
+ */
+ this.pageIndexMap_ = {};
+};
+var ExpandedCandidateView = i18n.input.chrome.inputview.elements.content.
+ ExpandedCandidateView;
+goog.inherits(ExpandedCandidateView,
+ i18n.input.chrome.inputview.elements.Element);
+
+
+/**
+ * The index of the key.
+ *
+ * @enum {number}
+ */
+ExpandedCandidateView.KeyIndex = {
+ BACKSPACE: 0,
+ ENTER: 1,
+ PAGE_UP: 2,
+ PAGE_DOWN: 3
+};
+
+
+/**
+ * The state of the expanded candidate view.
+ *
+ * @enum {number}
+ */
+ExpandedCandidateView.State = {
+ NONE: 0,
+ COMPLETION_CORRECTION: 1,
+ PREDICTION: 2
+};
+
+
+/**
+ * The state of the expanded candidate view.
+ *
+ * @type {ExpandedCandidateView.State}
+ */
+ExpandedCandidateView.prototype.state = ExpandedCandidateView.State.NONE;
+
+
+/**
+ * The current page index.
+ *
+ * @private {number}
+ */
+ExpandedCandidateView.prototype.pageIndex_ = 0;
+
+
+/** @private {number} */
+ExpandedCandidateView.prototype.candidateStartIndex_ = 0;
+
+
+/** @private {!Array.<!Object>} */
+ExpandedCandidateView.prototype.candidates_;
+
+
+/**
+ * The padding between candidates.
+ *
+ * @private {number}
+ */
+ExpandedCandidateView.RIGHT_KEY_WIDTH_ = 120;
+
+
+/**
+ * How many cells divided in one line.
+ *
+ * @type {number}
+ * @private
+ */
+ExpandedCandidateView.CELLS_PER_LINE_ = 10;
+
+
+/** @private {number} */
+ExpandedCandidateView.LINES_ = 4;
+
+
+/** @private {number} */
+ExpandedCandidateView.prototype.widthPerCell_ = 0;
+
+
+/** @private {number} */
+ExpandedCandidateView.prototype.heightPerCell_ = 0;
+
+
+/** @override */
+ExpandedCandidateView.prototype.createDom = function() {
+ goog.base(this, 'createDom');
+
+ var dom = this.getDomHelper();
+ var line = this.createCandidateLine_(true);
+ this.createKey_(ElementType.BACKSPACE_KEY, Css.BACKSPACE_ICON);
+
+ line = this.createCandidateLine_(false);
+ this.createKey_(ElementType.ENTER_KEY, Css.ENTER_ICON);
+
+ line = this.createCandidateLine_(false);
+ this.createKey_(ElementType.CANDIDATES_PAGE_UP, Css.PAGE_UP_ICON);
+
+ line = this.createCandidateLine_(false);
+ this.createKey_(ElementType.CANDIDATES_PAGE_DOWN, Css.PAGE_DOWN_ICON);
+};
+
+
+/**
+ * Creates a line for the candidates.
+ *
+ * @param {boolean} isTopLine .
+ * @private
+ */
+ExpandedCandidateView.prototype.createCandidateLine_ = function(isTopLine) {
+ var dom = this.getDomHelper();
+ var line = dom.createDom(TagName.DIV, [Css.CANDIDATE_INTER_CONTAINER,
+ Css.CANDIDATES_LINE]);
+ if (isTopLine) {
+ goog.dom.classlist.add(line, Css.CANDIDATES_TOP_LINE);
+ }
+ dom.appendChild(this.getElement(), line);
+ this.lines_.push(line);
+};
+
+
+/**
+ * Creates the right functional key.
+ *
+ * @param {ElementType} type .
+ * @param {string} iconCss .
+ * @private
+ */
+ExpandedCandidateView.prototype.createKey_ = function(type, iconCss) {
+ var key = new FunctionalKey('', type, '', iconCss, this);
+ key.render(this.getElement());
+ goog.dom.classlist.add(key.getElement(), Css.INLINE_DIV);
+ this.keys_.push(key);
+ return key;
+};
+
+
+/**
+ * Pages up to show more candidates.
+ */
+ExpandedCandidateView.prototype.pageUp = function() {
+ if (this.pageIndex_ > 0) {
+ this.pageIndex_--;
+ this.showCandidates(this.candidates_, this.pageIndexMap_[this.pageIndex_]);
+ }
+};
+
+
+/**
+ * Pages down to the previous candidate page.
+ */
+ExpandedCandidateView.prototype.pageDown = function() {
+ if (this.candidates_.length > this.candidateStartIndex_) {
+ this.pageIndex_++;
+ this.showCandidates(this.candidates_, this.candidateStartIndex_);
+ }
+};
+
+
+/**
+ * Closes this view.
+ */
+ExpandedCandidateView.prototype.close = function() {
+ this.candidates_ = [];
+ this.pageIndex_ = 0;
+ this.pageIndexMap_ = {};
+ this.candidateStartIndex_ = 0;
+ this.setVisible(false);
+};
+
+
+/**
+ * Shows the candidates in expanded view.
+ *
+ * @param {!Array.<!Object>} candidates .
+ * @param {number} start .
+ */
+ExpandedCandidateView.prototype.showCandidates = function(candidates,
+ start) {
+ this.setVisible(true);
+ var dom = this.getDomHelper();
+ for (var i = 0; i < this.lines_.length; i++) {
+ dom.removeChildren(this.lines_[i]);
+ }
+
+ this.pageIndexMap_[this.pageIndex_] = start;
+ this.candidates_ = candidates;
+ var lineIndex = 0;
+ var line = this.lines_[lineIndex];
+ var cellsInLine = ExpandedCandidateView.CELLS_PER_LINE_;
+ var previousCandidate = null;
+ var previousCandidateWidth = 0;
+ var i;
+ for (i = start; i < candidates.length; i++) {
+ var candidate = candidates[i];
+ var candidateElem = new Candidate(String(i), candidate, Type.CANDIDATE,
+ this.heightPerCell_, false, undefined, this);
+ candidateElem.render(line);
+ var size = goog.style.getSize(candidateElem.getElement());
+ var cells = Math.ceil(size.width / this.widthPerCell_);
+ if (cellsInLine < cells) {
+ // If there is not enough cells, just put this candidate to a new line
+ // and give the rest cells to the last candidate.
+ line.removeChild(candidateElem.getElement());
+ goog.style.setSize(previousCandidate.getElement(), cellsInLine *
+ this.widthPerCell_ + previousCandidateWidth, this.heightPerCell_);
+ lineIndex++;
+ if (lineIndex == ExpandedCandidateView.LINES_) {
+ break;
+ }
+ cellsInLine = ExpandedCandidateView.CELLS_PER_LINE_ - cells;
+ line = this.lines_[lineIndex];
+ dom.appendChild(line, candidateElem.getElement());
+ } else {
+ cellsInLine -= cells;
+ }
+ var width = cells * this.widthPerCell_;
+ goog.style.setSize(candidateElem.getElement(), width, this.heightPerCell_);
+
+ if (cellsInLine == 0) {
+ lineIndex++;
+ if (lineIndex == ExpandedCandidateView.LINES_) {
+ break;
+ }
+ cellsInLine = ExpandedCandidateView.CELLS_PER_LINE_;
+ line = this.lines_[lineIndex];
+ }
+
+ candidateElem.setVisible(true);
+ previousCandidateWidth = width;
+ previousCandidate = candidateElem;
+ }
+ this.candidateStartIndex_ = i;
+};
+
+
+/** @override */
+ExpandedCandidateView.prototype.resize = function(width, height) {
+ goog.base(this, 'resize', width, height);
+
+ goog.style.setSize(this.getElement(), width, height);
+ this.widthPerCell_ = Math.floor((width - ExpandedCandidateView.
+ RIGHT_KEY_WIDTH_) / ExpandedCandidateView.CELLS_PER_LINE_);
+ this.heightPerCell_ = height / ExpandedCandidateView.LINES_;
+ for (var i = 0; i < this.lines_.length; i++) {
+ var line = this.lines_[i];
+ goog.style.setSize(line, Math.floor(width -
+ ExpandedCandidateView.RIGHT_KEY_WIDTH_), this.heightPerCell_);
+ }
+ for (var i = 0; i < this.keys_.length; i++) {
+ var key = this.keys_[i];
+ key.resize(ExpandedCandidateView.RIGHT_KEY_WIDTH_, this.heightPerCell_);
+ }
+};
+
+}); // goog.scope
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/functionalkey.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/functionalkey.js
new file mode 100644
index 0000000..8c63c13
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/functionalkey.js
@@ -0,0 +1,197 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.elements.content.FunctionalKey');
+
+goog.require('goog.a11y.aria');
+goog.require('goog.a11y.aria.State');
+goog.require('goog.dom.TagName');
+goog.require('goog.dom.classlist');
+goog.require('i18n.input.chrome.inputview.Css');
+goog.require('i18n.input.chrome.inputview.elements.ElementType');
+goog.require('i18n.input.chrome.inputview.elements.content.SoftKey');
+
+
+goog.scope(function() {
+var ElementType = i18n.input.chrome.inputview.elements.ElementType;
+
+
+
+/**
+ * The functional key, it could be modifier keys or keys like
+ * backspace, tab, etc.
+ *
+ * @param {string} id The id.
+ * @param {!i18n.input.chrome.inputview.elements.ElementType} type The element
+ * type.
+ * @param {string} text The text.
+ * @param {string} iconCssClass The css class for the icon.
+ * @param {goog.events.EventTarget=} opt_eventTarget The event target.
+ * @param {string=} opt_textCssClass The css class for the text.
+ * @constructor
+ * @extends {i18n.input.chrome.inputview.elements.content.SoftKey}
+ */
+i18n.input.chrome.inputview.elements.content.FunctionalKey = function(id, type,
+ text, iconCssClass, opt_eventTarget, opt_textCssClass) {
+ goog.base(this, id, type, opt_eventTarget);
+
+ /**
+ * The text in the key.
+ *
+ * @type {string}
+ */
+ this.text = text;
+
+ /**
+ * The css class for the icon.
+ *
+ * @type {string}
+ * @private
+ */
+ this.iconCssClass_ = iconCssClass;
+
+ /**
+ * The css class for the text.
+ *
+ * @type {string}
+ * @private
+ */
+ this.textCssClass_ = opt_textCssClass || '';
+};
+goog.inherits(i18n.input.chrome.inputview.elements.content.FunctionalKey,
+ i18n.input.chrome.inputview.elements.content.SoftKey);
+var FunctionalKey = i18n.input.chrome.inputview.elements.content.FunctionalKey;
+
+
+/**
+ * The table cell.
+ *
+ * @type {!Element}
+ */
+FunctionalKey.prototype.tableCell;
+
+
+/**
+ * The element contains the text.
+ *
+ * @type {!Element}
+ */
+FunctionalKey.prototype.textElem;
+
+
+/**
+ * The element contains the icon.
+ *
+ * @type {!Element}
+ */
+FunctionalKey.prototype.iconElem;
+
+
+/** @override */
+FunctionalKey.prototype.createDom = function() {
+ goog.base(this, 'createDom');
+
+ var dom = this.getDomHelper();
+ var elem = this.getElement();
+ this.bgElem = dom.createDom(goog.dom.TagName.DIV,
+ i18n.input.chrome.inputview.Css.SPECIAL_KEY_BG);
+ dom.appendChild(elem, this.bgElem);
+ this.tableCell = dom.createDom(goog.dom.TagName.DIV);
+ goog.dom.classlist.add(this.tableCell,
+ i18n.input.chrome.inputview.Css.MODIFIER);
+ if (this.text) {
+ this.textElem = dom.createDom(goog.dom.TagName.DIV,
+ i18n.input.chrome.inputview.Css.SPECIAL_KEY_NAME, this.text);
+ if (this.textCssClass_) {
+ goog.dom.classlist.add(this.textElem, this.textCssClass_);
+ }
+ dom.appendChild(this.tableCell, this.textElem);
+ }
+ if (this.iconCssClass_) {
+ this.iconElem = dom.createDom(goog.dom.TagName.DIV,
+ this.iconCssClass_);
+ dom.appendChild(this.tableCell, this.iconElem);
+ }
+ dom.appendChild(this.bgElem, this.tableCell);
+
+ this.setAriaLabel(this.getChromeVoxMessage());
+};
+
+
+/** @override */
+FunctionalKey.prototype.resize = function(width,
+ height) {
+ goog.base(this, 'resize', width, height);
+
+ this.tableCell.style.width = this.availableWidth + 'px';
+ this.tableCell.style.height = this.availableHeight + 'px';
+};
+
+
+/** @override */
+FunctionalKey.prototype.setHighlighted = function(
+ highlight) {
+ if (highlight) {
+ goog.dom.classlist.add(this.bgElem,
+ i18n.input.chrome.inputview.Css.SPECIAL_KEY_HIGHLIGHT);
+ } else {
+ goog.dom.classlist.remove(this.bgElem,
+ i18n.input.chrome.inputview.Css.SPECIAL_KEY_HIGHLIGHT);
+ }
+};
+
+
+/**
+ * Gets the chrome vox message.
+ *
+ * @return {string} .
+ */
+FunctionalKey.prototype.getChromeVoxMessage = function() {
+ switch (this.type) {
+ case ElementType.BACKSPACE_KEY:
+ return chrome.i18n.getMessage('BACKSPACE');
+ case ElementType.ENTER_KEY:
+ return chrome.i18n.getMessage('ENTER');
+ case ElementType.TAB_KEY:
+ return chrome.i18n.getMessage('TAB');
+ case ElementType.ARROW_UP:
+ return chrome.i18n.getMessage('UP_ARROW');
+ case ElementType.ARROW_DOWN:
+ return chrome.i18n.getMessage('DOWN_ARROW');
+ case ElementType.ARROW_LEFT:
+ return chrome.i18n.getMessage('LEFT_ARROW');
+ case ElementType.ARROW_RIGHT:
+ return chrome.i18n.getMessage('RIGHT_ARROW');
+ case ElementType.HIDE_KEYBOARD_KEY:
+ return chrome.i18n.getMessage('HIDE_KEYBOARD');
+ case ElementType.GLOBE_KEY:
+ return chrome.i18n.getMessage('GLOBE');
+ }
+ return '';
+};
+
+
+/**
+ * Sets the aria label.
+ *
+ * @param {string} label .
+ */
+FunctionalKey.prototype.setAriaLabel = function(label) {
+ var elem = this.textElem || this.iconElem;
+ if (elem) {
+ goog.a11y.aria.setState(elem, goog.a11y.aria.State.LABEL, label);
+ }
+};
+
+}); // goog.scope
+
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/gaussianestimator.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/gaussianestimator.js
new file mode 100644
index 0000000..9704c63
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/gaussianestimator.js
@@ -0,0 +1,87 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.elements.content.GaussianEstimator');
+
+
+goog.scope(function() {
+
+/**
+ * A tool class to calculate probability with Gaussian distribution.
+ * Gaussian(x, y) = Norm * exp (-(1/2) * ((x - centerX) ^ 2 * CinvX + (y -
+ * centerY) ^ 2 * CinvY))
+ * where
+ * CinvX = 1 / (AmplitudeX * Covariance)
+ * CinvY = 1 / (AmplitudeY * Covariance)
+ * Norm = 1 / (2 * PI) * Sqrt(CinX * CinY)
+ * LogGaussian(x, y) = LogNorm + (-1/2) * ((x - centerX) ^ 2 * CinvX
+ * + (y - centerY) ^ 2 * CinvY))
+ * In this class we assumes amplitude Y is normalized to 1, so
+ * amplitude X is real amplitude X relative to amplitude Y.
+ *
+ * @param {!goog.math.Coordinate} center .
+ * @param {number} covariance .
+ * @param {!number} amplitude Amplitude on dimension X of the distribution. The
+ * estimator assumes amplitude on dimension Y is 1, so this value is real
+ * amplitude X relative to amplitude Y.
+ * @constructor
+ */
+i18n.input.chrome.inputview.elements.content.GaussianEstimator = function(
+ center, covariance, amplitude) {
+ /**
+ * The center point.
+ *
+ * @private {!goog.math.Coordinate}
+ */
+ this.center_ = center;
+
+ /**
+ * The CinvX.
+ *
+ * @private {number}
+ */
+ this.cinvX_ = 1 / (amplitude * covariance);
+
+ /**
+ * The CinvY.
+ *
+ * @private {number}
+ */
+ this.cinvY_ = 1 / covariance;
+
+ /**
+ * The Norm in log space.
+ *
+ * @private {number}
+ */
+ this.logNorm_ = Math.log(1 / (2 * Math.PI * Math.sqrt(amplitude *
+ covariance * covariance)));
+};
+var GaussianEstimator = i18n.input.chrome.inputview.elements.content.
+ GaussianEstimator;
+
+
+/**
+ * Estimates the possibility in log space.
+ *
+ * @param {number} x .
+ * @param {number} y .
+ */
+GaussianEstimator.prototype.estimateInLogSpace = function(x, y) {
+ var dx = x - this.center_.x;
+ var dy = y - this.center_.y;
+ var exponent = this.cinvX_ * dx * dx + this.cinvY_ * dy * dy;
+ return this.logNorm_ + (-0.5) * exponent;
+};
+
+}); // goog.scope
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/handwritingview.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/handwritingview.js
new file mode 100644
index 0000000..50194bf
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/handwritingview.js
@@ -0,0 +1,92 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.elements.content.HandwritingView');
+
+goog.require('goog.dom.classlist');
+goog.require('i18n.input.chrome.inputview.Css');
+goog.require('i18n.input.chrome.inputview.elements.content.KeysetView');
+
+
+goog.scope(function() {
+var Css = i18n.input.chrome.inputview.Css;
+var KeysetView = i18n.input.chrome.inputview.elements.content.KeysetView;
+
+
+
+/**
+ * The handwriting view.
+ *
+ * @param {!Object} keyData The data includes soft key definition and key
+ * mapping.
+ * @param {!Object} layoutData The layout definition.
+ * @param {string} keyboardCode The keyboard code.
+ * @param {string} languageCode The language code.
+ * @param {!i18n.input.chrome.inputview.Model} model The model.
+ * @param {string} name The Input Tool name.
+ * @param {!goog.events.EventTarget=} opt_eventTarget .
+ * @param {i18n.input.chrome.inputview.Adapter=} opt_adapter .
+ * @constructor
+ * @extends {KeysetView}
+ */
+i18n.input.chrome.inputview.elements.content.HandwritingView = function(keyData,
+ layoutData, keyboardCode, languageCode, model, name, opt_eventTarget,
+ opt_adapter) {
+ i18n.input.chrome.inputview.elements.content.HandwritingView.base(this,
+ 'constructor', keyData, layoutData, keyboardCode, languageCode, model,
+ name, opt_eventTarget, opt_adapter);
+};
+var HandwritingView = i18n.input.chrome.inputview.elements.content.
+ HandwritingView;
+goog.inherits(HandwritingView, KeysetView);
+
+
+/**
+ * The handwriting input tool code suffix.
+ *
+ * @const {string}
+ * @private
+ */
+HandwritingView.HANDWRITING_CODE_SUFFIX_ = '-t-i0-handwrit';
+
+
+/** @override */
+HandwritingView.prototype.activate = function(rawKeyset) {
+ this.adapter.setInputToolCode(this.languageCode.split(/_|-/)[0] +
+ HandwritingView.HANDWRITING_CODE_SUFFIX_);
+ goog.dom.classlist.add(this.getElement().parentElement.parentElement,
+ Css.HANDWRITING);
+ // Clears stroke when switches keyboard.
+ if (this.canvasView.hasStrokesOnCanvas()) {
+ this.canvasView.reset();
+ }
+};
+
+
+/** @override */
+HandwritingView.prototype.deactivate = function(rawKeyset) {
+ this.adapter.unsetInputToolCode();
+ goog.dom.classlist.remove(this.getElement().parentElement.parentElement,
+ Css.HANDWRITING);
+};
+
+
+/**
+ * Updates the language code.
+ *
+ * @param {string} languageCode .
+ */
+HandwritingView.prototype.setLanguagecode = function(languageCode) {
+ this.languageCode = languageCode;
+};
+}); // goog.scope
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/indicator.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/indicator.js
new file mode 100644
index 0000000..a0c8392
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/indicator.js
@@ -0,0 +1,99 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.elements.content.PageIndicator');
+
+goog.require('goog.dom');
+goog.require('goog.dom.TagName');
+goog.require('goog.dom.classlist');
+goog.require('goog.style');
+goog.require('i18n.input.chrome.inputview.Css');
+goog.require('i18n.input.chrome.inputview.elements.Element');
+
+goog.scope(function() {
+var ElementType = i18n.input.chrome.inputview.elements.ElementType;
+
+
+
+/**
+ * The indicator of the current page index.
+ *
+ * @param {string} id The id.
+ * @param {!i18n.input.chrome.inputview.elements.ElementType} type The element
+ * type.
+ * @param {goog.events.EventTarget=} opt_eventTarget The event target.
+ * @constructor
+ * @extends {i18n.input.chrome.inputview.elements.Element}
+ */
+i18n.input.chrome.inputview.elements.content.PageIndicator = function(id, type,
+ opt_eventTarget) {
+ goog.base(this, id, type, opt_eventTarget);
+};
+goog.inherits(i18n.input.chrome.inputview.elements.content.PageIndicator,
+ i18n.input.chrome.inputview.elements.Element);
+var PageIndicator = i18n.input.chrome.inputview.elements.content.PageIndicator;
+
+
+/** @override */
+PageIndicator.prototype.createDom = function() {
+ goog.base(this, 'createDom');
+ var dom = this.getDomHelper();
+ var elem = this.getElement();
+ goog.dom.classlist.add(elem,
+ i18n.input.chrome.inputview.Css.INDICATOR_BACKGROUND);
+ this.bgElem = goog.dom.createDom(goog.dom.TagName.DIV);
+ goog.dom.classlist.add(this.bgElem,
+ i18n.input.chrome.inputview.Css.INDICATOR);
+ dom.appendChild(elem, this.bgElem);
+};
+
+
+/** @override */
+PageIndicator.prototype.resize = function(width, height) {
+ this.bgElem.style.height = height + 'px';
+ this.getElement().style.width = width + 'px';
+ goog.base(this, 'resize', width, height);
+};
+
+
+/**
+ * Slide the indicator.
+ *
+ * @param {number} deltaX The x-coordinate of slide distance.
+ * @param {number} totalPages The total number of pages.
+ */
+PageIndicator.prototype.slide = function(deltaX, totalPages) {
+ var marginLeft = goog.style.getMarginBox(this.bgElem).left +
+ deltaX / totalPages;
+ this.bgElem.style.marginLeft = marginLeft + 'px';
+};
+
+
+/**
+ * Move the indicator to indicate a page.
+ *
+ * @param {number} pageNum The page that needs to be indicated.
+ * @param {number} totalPages The total number of pages.
+ */
+PageIndicator.prototype.gotoPage = function(pageNum, totalPages) {
+ var width = goog.style.getSize(this.getElement()).width;
+ this.bgElem.style.marginLeft = width / totalPages * pageNum + 'px';
+ if (totalPages >= 2) {
+ this.bgElem.style.width = width / totalPages + 'px';
+ } else {
+ this.bgElem.style.width = 0;
+ }
+};
+}); // goog.scope
+
+
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/keyboardview.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/keyboardview.js
new file mode 100644
index 0000000..d7033c9
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/keyboardview.js
@@ -0,0 +1,208 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.elements.content.KeyboardView');
+
+goog.require('goog.dom.classlist');
+goog.require('goog.object');
+goog.require('i18n.input.chrome.inputview.Css');
+goog.require('i18n.input.chrome.inputview.elements.ElementType');
+goog.require('i18n.input.chrome.inputview.elements.layout.VerticalLayout');
+
+
+goog.scope(function() {
+var layout = i18n.input.chrome.inputview.elements.layout;
+var content = i18n.input.chrome.inputview.elements.content;
+var ElementType = i18n.input.chrome.inputview.elements.ElementType;
+
+
+
+/**
+ * The layout view.
+ *
+ * @param {string} id The id.
+ * @param {goog.events.EventTarget=} opt_eventTarget The event target.
+ * @constructor
+ * @extends {layout.VerticalLayout}
+ */
+content.KeyboardView = function(id, opt_eventTarget) {
+ goog.base(this, id, opt_eventTarget, ElementType.LAYOUT_VIEW);
+};
+goog.inherits(content.KeyboardView, layout.VerticalLayout);
+var KeyboardView = content.KeyboardView;
+
+
+/**
+ * The maps of all the soft key view.
+ *
+ * @type {!Object.<string, !layout.SoftKeyView>}
+ * @private
+ */
+KeyboardView.prototype.softKeyViewMap_;
+
+
+/**
+ * The soft key map.
+ * Key: The character belongs to this key.
+ *
+ * @type {!Object.<string, !content.SoftKey>}
+ * @private
+ */
+KeyboardView.prototype.softKeyMap_;
+
+
+/**
+ * The mapping between soft key view id and soft key id.
+ *
+ * @type {!Object.<string, string>}
+ * @private
+ */
+KeyboardView.prototype.mapping_;
+
+
+/**
+ * The squared factor of the key width * key width. Assumes key1 and key2
+ * here, if the squared distance from the center of the key2 to the nearest
+ * edge of key1 is less than this factor * key1 width * key1 width, we
+ * take key2 as the nearby key of key1.
+ *
+ * @type {number}
+ * @private
+ */
+KeyboardView.SQUARED_NEARBY_FACTOR_ = 1.2;
+
+
+/** @override */
+KeyboardView.prototype.createDom = function() {
+ goog.base(this, 'createDom');
+
+ var elem = this.getElement();
+ goog.dom.classlist.add(elem, i18n.input.chrome.inputview.Css.LAYOUT_VIEW);
+};
+
+
+/**
+ * Sets up this keyboard view.
+ *
+ * @param {!Array.<content.SoftKey>} softKeyList The soft
+ * key map.
+ * @param {!Object.<string, layout.SoftKeyView>} softKeyViewMap The soft key
+ * view map.
+ * @param {!Object.<string, string>} mapping The mapping from soft key id
+ * to soft key view id.
+ */
+KeyboardView.prototype.setUp = function(softKeyList, softKeyViewMap, mapping) {
+ this.softKeyMap_ = {};
+ this.softKeyViewMap_ = softKeyViewMap;
+ this.mapping_ = mapping;
+
+ for (var i = 0; i < softKeyList.length; i++) {
+ var sk = softKeyList[i];
+ var skv = this.softKeyViewMap_[mapping[sk.id]];
+ if (skv) {
+ skv.bindSoftKey(sk);
+ }
+ this.softKeyMap_[sk.id] = sk;
+ }
+};
+
+
+/**
+ * Gets the soft key whose id is equal to the code.
+ *
+ * @param {string} code The code of the key.
+ * @return {content.SoftKey} The soft key.
+ */
+KeyboardView.prototype.getViewForKey = function(code) {
+ if (code) {
+ return this.softKeyMap_[code];
+ }
+ return null;
+};
+
+
+/**
+ * Sets up the nearby keys mapping for each non-Functional key.
+ *
+ * @private
+ */
+KeyboardView.prototype.setUpNearbyKeys_ = function() {
+ var softKeys = goog.object.getValues(this.softKeyMap_);
+ for (var i = 0; i < softKeys.length; i++) {
+ var key1 = softKeys[i];
+ if (!this.isQualifiedForSpatial_(key1)) {
+ continue;
+ }
+ for (var j = i + 1; j < softKeys.length; j++) {
+ var key2 = softKeys[j];
+ if (this.isQualifiedForSpatial_(key2) && this.isNearby(key1, key2)) {
+ // We assume that if key2 is a nearby key for key1, then key1 is
+ // also a nearby key for key2.
+ key1.nearbyKeys.push(key2);
+ key2.nearbyKeys.push(key1);
+ }
+ }
+ }
+};
+
+
+/**
+ * We only consider character key or compact key to be qualified for spatial
+ * module.
+ *
+ * @param {!content.SoftKey} key .
+ * @return {boolean} .
+ * @private
+ */
+KeyboardView.prototype.isQualifiedForSpatial_ = function(key) {
+ return key.type == ElementType.CHARACTER_KEY ||
+ key.type == ElementType.COMPACT_KEY;
+};
+
+
+/**
+ * Checks if key2 is near key1, the algorithm is:
+ * 1. Finds out the nearest edge point of key1 to the center of key2.
+ * 2. Calculate the distance from the center of key2 to the nearest edge point.
+ * 3. If the distance is less than the factor(1.2) * key1 width * key1 width,
+ * the key2 is nearby key1.
+ *
+ * @param {!content.SoftKey} key1 .
+ * @param {!content.SoftKey} key2 .
+ * @return {boolean} .
+ */
+KeyboardView.prototype.isNearby = function(key1, key2) {
+ var key2Center = key2.centerCoordinate;
+ var key1Left = key1.topLeftCoordinate.x;
+ var key1Right = key1Left + key1.width;
+ var key1Top = key1.topLeftCoordinate.y;
+ var key1Bottom = key1Top + key1.height;
+ var edgeX = key2Center.x < key1Left ? key1Left : (key2Center.x > key1Right ?
+ key1Right : key2Center.x);
+ var edgeY = key2Center.y < key1Top ? key1Top : (key2Center.y > key1Bottom ?
+ key1Bottom : key2Center.y);
+ var dx = key2Center.x - edgeX;
+ var dy = key2Center.y - edgeY;
+ return (dx * dx + dy * dy) < KeyboardView.
+ SQUARED_NEARBY_FACTOR_ * (key1.availableWidth * key1.availableWidth);
+};
+
+
+/** @override */
+KeyboardView.prototype.resize = function(width, height) {
+ goog.base(this, 'resize', width, height);
+
+ this.setUpNearbyKeys_();
+};
+
+}); // goog.scope
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/keysetview.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/keysetview.js
new file mode 100644
index 0000000..61d936d
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/keysetview.js
@@ -0,0 +1,727 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.elements.content.KeysetView');
+
+goog.require('goog.array');
+goog.require('goog.dom.classlist');
+goog.require('goog.i18n.bidi');
+goog.require('goog.style');
+goog.require('goog.ui.Container');
+goog.require('i18n.input.chrome.inputview.ConditionName');
+goog.require('i18n.input.chrome.inputview.Css');
+goog.require('i18n.input.chrome.inputview.SpecNodeName');
+goog.require('i18n.input.chrome.inputview.elements.ElementType');
+goog.require('i18n.input.chrome.inputview.elements.content.CandidateButton');
+goog.require('i18n.input.chrome.inputview.elements.content.CanvasView');
+goog.require('i18n.input.chrome.inputview.elements.content.CharacterKey');
+goog.require('i18n.input.chrome.inputview.elements.content.CompactKey');
+goog.require('i18n.input.chrome.inputview.elements.content.EmojiKey');
+goog.require('i18n.input.chrome.inputview.elements.content.EnSwitcherKey');
+goog.require('i18n.input.chrome.inputview.elements.content.FunctionalKey');
+goog.require('i18n.input.chrome.inputview.elements.content.KeyboardView');
+goog.require('i18n.input.chrome.inputview.elements.content.MenuKey');
+goog.require('i18n.input.chrome.inputview.elements.content.ModifierKey');
+goog.require('i18n.input.chrome.inputview.elements.content.PageIndicator');
+goog.require('i18n.input.chrome.inputview.elements.content.SpaceKey');
+goog.require('i18n.input.chrome.inputview.elements.content.SwitcherKey');
+goog.require('i18n.input.chrome.inputview.elements.content.TabBarKey');
+goog.require('i18n.input.chrome.inputview.elements.layout.ExtendedLayout');
+goog.require('i18n.input.chrome.inputview.elements.layout.HandwritingLayout');
+goog.require('i18n.input.chrome.inputview.elements.layout.LinearLayout');
+goog.require('i18n.input.chrome.inputview.elements.layout.SoftKeyView');
+goog.require('i18n.input.chrome.inputview.elements.layout.VerticalLayout');
+goog.require('i18n.input.chrome.inputview.util');
+
+
+
+goog.scope(function() {
+
+var ConditionName = i18n.input.chrome.inputview.ConditionName;
+var SpecNodeName = i18n.input.chrome.inputview.SpecNodeName;
+var ElementType = i18n.input.chrome.inputview.elements.ElementType;
+var content = i18n.input.chrome.inputview.elements.content;
+var layout = i18n.input.chrome.inputview.elements.layout;
+var Css = i18n.input.chrome.inputview.Css;
+var util = i18n.input.chrome.inputview.util;
+
+
+
+/**
+ * The keyboard.
+ *
+ * @param {!Object} keyData The data includes soft key definition and key
+ * mapping.
+ * @param {!Object} layoutData The layout definition.
+ * @param {string} keyboardCode The keyboard code.
+ * @param {string} languageCode The language code.
+ * @param {!i18n.input.chrome.inputview.Model} model The model.
+ * @param {string} name The Input Tool name.
+ * @param {!goog.events.EventTarget=} opt_eventTarget .
+ * @param {i18n.input.chrome.inputview.Adapter=} opt_adapter .
+ * @constructor
+ * @extends {goog.ui.Container}
+ */
+i18n.input.chrome.inputview.elements.content.KeysetView = function(keyData,
+ layoutData, keyboardCode, languageCode, model, name, opt_eventTarget,
+ opt_adapter) {
+ goog.base(this);
+ this.setParentEventTarget(opt_eventTarget || null);
+
+ /**
+ * The key configuration data.
+ *
+ * @type {!Object}
+ * @const
+ * @private
+ */
+ this.keyData_ = keyData;
+
+ /**
+ * The layout definition.
+ *
+ * @type {!Object}
+ * @const
+ * @private
+ */
+ this.layoutData_ = layoutData;
+
+ /**
+ * The keyboard code.
+ *
+ * @type {string}
+ * @private
+ */
+ this.keyboardCode_ = keyboardCode;
+
+ /**
+ * The language code.
+ *
+ * @protected {string}
+ */
+ this.languageCode = languageCode;
+
+ /**
+ * The model, the reason use dataModel as its name because model_ will
+ * conflict with the one in goog.ui.Container.
+ *
+ * @type {!i18n.input.chrome.inputview.Model}
+ * @private
+ */
+ this.dataModel_ = model;
+
+ /**
+ * The rows in this view, the reason we don't use getChild is that container
+ * only accepts control as its child, so we have to use
+ * row.render(this.getElement()) style.
+ *
+ * @type {!Array.<layout.LinearLayout>}
+ * @private
+ */
+ this.rows_ = [];
+
+ /**
+ * The maps of all the soft key view.
+ *
+ * @type {!Object.<string, !layout.SoftKeyView>}
+ * @private
+ */
+ this.softKeyViewMap_ = {};
+
+ /**
+ * The map from the condition to the soft key view.
+ *
+ * @type {!Object.<string, !layout.SoftKeyView>}
+ * @private
+ */
+ this.softKeyConditionMap_ = {};
+
+ /**
+ * The on-screen keyboard title.
+ *
+ * @type {string}
+ * @private
+ */
+ this.title_ = name;
+
+ /**
+ * The bus channel to communicate with background.
+ *
+ * @protected {i18n.input.chrome.inputview.Adapter}
+ */
+ this.adapter = opt_adapter || null;
+
+ /**
+ * The conditions.
+ *
+ * @private {!Object.<string, boolean>}
+ */
+ this.conditions_ = {};
+
+ /**
+ * whether to display the candidate view or not.
+ *
+ * @type {boolean}
+ */
+ this.disableCandidateView =
+ goog.isDef(this.layoutData_['disableCandidateView']) ?
+ this.layoutData_['disableCandidateView'] : false;
+
+ /**
+ * The map of the child views.
+ * Key: The id of the child element.
+ * Value: The element.
+ *
+ * @private {!Object.<string, !i18n.input.chrome.inputview.elements.Element>}
+ */
+ this.childMap_ = {};
+};
+var KeysetView = i18n.input.chrome.inputview.elements.content.KeysetView;
+goog.inherits(KeysetView, goog.ui.Container);
+
+
+/**
+ * True if the keyset view has shift state.
+ *
+ * @type {boolean}
+ */
+KeysetView.prototype.hasShift = true;
+
+
+/**
+ * The keyboard.
+ *
+ * @type {!content.KeyboardView}
+ * @private
+ */
+KeysetView.prototype.keyboardView_;
+
+
+/**
+ * The keyset code from which jumps to this keyset view.
+ *
+ * @type {string}
+ */
+KeysetView.prototype.fromKeyset = '';
+
+
+/**
+ * The handwriting canvas view.
+ *
+ * @protected {!content.CanvasView}
+ */
+KeysetView.prototype.canvasView;
+
+
+/**
+ * The space key.
+ *
+ * @type {!content.SpaceKey}
+ */
+KeysetView.prototype.spaceKey;
+
+
+/**
+ * The outer height of the view.
+ *
+ * @type {number}
+ * @private
+ */
+KeysetView.prototype.outerHeight_ = 0;
+
+
+/**
+ * The outer width of the view.
+ *
+ * @type {number}
+ * @private
+ */
+KeysetView.prototype.outerWidth_ = 0;
+
+
+/** @override */
+KeysetView.prototype.createDom = function() {
+ goog.base(this, 'createDom');
+
+ this.hasShift = !this.keyData_[SpecNodeName.NO_SHIFT];
+ var elem = this.getElement();
+ elem.id = this.keyboardCode_.replace(/\./g, '-');
+ goog.dom.classlist.add(elem, i18n.input.chrome.inputview.Css.VIEW);
+
+ var children = this.layoutData_['children'];
+ for (var i = 0; i < children.length; i++) {
+ var child = children[i];
+ var layoutElem = /** @type {!layout.LinearLayout} */
+ (this.createLayoutElement_(child[i18n.input.chrome.inputview.
+ SpecNodeName.SPEC], this));
+ // Can't use addChild here, because container only allow control as its
+ // child.
+ if (layoutElem) {
+ layoutElem.render(elem);
+ this.rows_.push(layoutElem);
+ }
+ }
+
+ var softKeyList = [];
+ var keySpecs = this.keyData_[SpecNodeName.KEY_LIST];
+ var hasAltGrCharacterInTheKeyset = this.hasAltGrCharacterInTheKeyset_(
+ keySpecs);
+ for (var i = 0; i < keySpecs.length; i++) {
+ var softKey = this.createKey_(keySpecs[i][SpecNodeName.SPEC],
+ hasAltGrCharacterInTheKeyset);
+ if (softKey) {
+ softKeyList.push(softKey);
+ }
+ }
+ var mapping = this.keyData_[
+ SpecNodeName.MAPPING];
+ this.keyboardView_.setUp(softKeyList, this.softKeyViewMap_, mapping);
+};
+
+
+/**
+ * Updates the view.
+ */
+KeysetView.prototype.update = function() {
+ this.keyboardView_.update();
+};
+
+
+/**
+ * Resizes the view.
+ *
+ * @param {number} outerWidth The width of the outer space.
+ * @param {number} outerHeight The height of the outer space.
+ * @param {boolean=} opt_force Forces to resize the view.
+ */
+KeysetView.prototype.resize = function(outerWidth, outerHeight, opt_force) {
+ var needResize = !!opt_force || (this.outerHeight_ != outerHeight ||
+ this.outerWidth_ != outerWidth);
+ if (this.getElement() && needResize) {
+ this.outerHeight_ = outerHeight;
+ this.outerWidth_ = outerWidth;
+ var elem = this.getElement();
+ goog.style.setSize(elem, outerWidth, outerHeight);
+
+ var weightArray = [];
+ for (var i = 0; i < this.rows_.length; i++) {
+ var row = this.rows_[i];
+ weightArray.push(row.getHeightInWeight());
+ }
+
+ var splitedHeight = i18n.input.chrome.inputview.util.splitValue(weightArray,
+ outerHeight);
+ for (var i = 0; i < this.rows_.length; i++) {
+ var row = this.rows_[i];
+ row.resize(outerWidth, splitedHeight[i]);
+ }
+ }
+};
+
+
+/**
+ * Gets the total height in weight.
+ *
+ * @return {number} The total height in weight.
+ */
+KeysetView.prototype.getHeightInWeight = function() {
+ var heightInWeight = 0;
+ for (var i = 0; i < this.rows_.length; i++) {
+ var row = this.rows_[i];
+ heightInWeight += row.getHeightInWeight();
+ }
+ return heightInWeight;
+};
+
+
+/**
+ * Apply conditions.
+ *
+ * @param {!Object.<string, boolean>} conditions The conditions.
+ */
+KeysetView.prototype.applyConditions = function(conditions) {
+ this.conditions_ = conditions;
+ for (var condition in conditions) {
+ var softKeyView = this.softKeyConditionMap_[condition];
+ var isConditionEnabled = conditions[condition];
+ if (softKeyView) {
+ softKeyView.setVisible(isConditionEnabled);
+ var softKeyViewGetWeight = this.softKeyViewMap_[softKeyView.
+ giveWeightTo];
+ if (softKeyViewGetWeight) {
+ // Only supports horizontal weight transfer now.
+ softKeyViewGetWeight.dynamicaGrantedWeight += isConditionEnabled ?
+ 0 : softKeyView.widthInWeight;
+ }
+ }
+ }
+
+ // Adjusts the width of globe key and menu key according to the mock when they
+ // both show up.
+ // TODO: This is hacky. Remove the hack once figure out a better way.
+ var showGlobeKey = conditions[ConditionName.SHOW_GLOBE_OR_SYMBOL];
+ var showMenuKey = conditions[ConditionName.SHOW_MENU];
+ var menuKeyView = this.softKeyConditionMap_[ConditionName.SHOW_MENU];
+ var globeKeyView =
+ this.softKeyConditionMap_[ConditionName.SHOW_GLOBE_OR_SYMBOL];
+ if (menuKeyView && globeKeyView) {
+ var softKeyViewGetWeight =
+ this.softKeyViewMap_[menuKeyView.giveWeightTo];
+ if (softKeyViewGetWeight) {
+ if (showGlobeKey && showMenuKey) {
+ globeKeyView.dynamicaGrantedWeight = -0.1;
+ menuKeyView.dynamicaGrantedWeight = -0.4;
+ softKeyViewGetWeight.dynamicaGrantedWeight += 0.5;
+ }
+ }
+ }
+};
+
+
+/**
+ * Updates the condition.
+ *
+ * @param {string} name .
+ * @param {boolean} value .
+ */
+KeysetView.prototype.updateCondition = function(name, value) {
+ for (var id in this.softKeyViewMap_) {
+ var skv = this.softKeyViewMap_[id];
+ skv.dynamicaGrantedWeight = 0;
+ }
+ this.conditions_[name] = value;
+ this.applyConditions(this.conditions_);
+ this.resize(this.outerWidth_, this.outerHeight_, true);
+ this.update();
+};
+
+
+/**
+ * Creates the element according to its type.
+ *
+ * @param {!Object} spec The specification.
+ * @param {!goog.events.EventTarget=} opt_eventTarget The event target.
+ * @return {i18n.input.chrome.inputview.elements.Element} The element.
+ * @private
+ */
+KeysetView.prototype.createElement_ = function(spec, opt_eventTarget) {
+ var type = spec[SpecNodeName.TYPE];
+ var id = spec[SpecNodeName.ID];
+ var widthInWeight = spec[
+ SpecNodeName.WIDTH_IN_WEIGHT];
+ var heightInWeight = spec[
+ SpecNodeName.HEIGHT_IN_WEIGHT];
+ var width = spec[SpecNodeName.WIDTH];
+ var height = spec[SpecNodeName.HEIGHT];
+ var padding = spec[SpecNodeName.PADDING];
+ var widthPercent = spec[SpecNodeName.WIDTH_PERCENT];
+ var heightPercent = spec[SpecNodeName.HEIGHT_PERCENT];
+ var elem = null;
+ switch (type) {
+ case ElementType.SOFT_KEY_VIEW:
+ var condition = spec[SpecNodeName.CONDITION];
+ var giveWeightTo = spec[SpecNodeName.GIVE_WEIGHT_TO];
+ elem = new layout.SoftKeyView(id, widthInWeight,
+ heightInWeight, condition, giveWeightTo, opt_eventTarget);
+ this.softKeyConditionMap_[condition] = elem;
+ break;
+ case ElementType.LINEAR_LAYOUT:
+ var opt_iconCssClass = spec[SpecNodeName.ICON_CSS_CLASS];
+ elem = new layout.LinearLayout(id, opt_eventTarget, opt_iconCssClass);
+ break;
+ case ElementType.EXTENDED_LAYOUT:
+ elem = new layout.ExtendedLayout(id, opt_eventTarget);
+ break;
+ case ElementType.VERTICAL_LAYOUT:
+ elem = new layout.VerticalLayout(id, opt_eventTarget);
+ break;
+ case ElementType.LAYOUT_VIEW:
+ this.keyboardView_ = new content.KeyboardView(id, opt_eventTarget);
+ elem = this.keyboardView_;
+ break;
+ case ElementType.CANVAS_VIEW:
+ this.canvasView = new content.CanvasView(id, widthInWeight,
+ heightInWeight, opt_eventTarget, this.adapter);
+ elem = this.canvasView;
+ break;
+ case ElementType.HANDWRITING_LAYOUT:
+ elem = new layout.HandwritingLayout(id, opt_eventTarget);
+ break;
+ }
+ if (elem) {
+ this.childMap_[id] = elem;
+ }
+ return elem;
+};
+
+
+/**
+ * Creates the layout element.
+ *
+ * @param {!Object} spec The specification for the element.
+ * @param {!goog.events.EventTarget=} opt_parentEventTarget The parent event
+ * target.
+ * @return {i18n.input.chrome.inputview.elements.Element} The element.
+ * @private
+ */
+KeysetView.prototype.createLayoutElement_ = function(spec,
+ opt_parentEventTarget) {
+ var element = this.createElement_(spec, opt_parentEventTarget);
+ if (!element) {
+ return null;
+ }
+
+ var children = spec[SpecNodeName.CHILDREN];
+ if (children) {
+ children = goog.array.flatten(children);
+ for (var i = 0; i < children.length; i++) {
+ var child = children[i];
+ var childElem = this.createLayoutElement_(
+ child[SpecNodeName.SPEC], element);
+ if (childElem) {
+ element.addChild(childElem, true);
+ }
+ }
+ }
+ if (element.type == ElementType.SOFT_KEY_VIEW) {
+ this.softKeyViewMap_[element.id] =
+ /** @type {!layout.SoftKeyView} */ (element);
+ }
+ return element;
+};
+
+
+/**
+ * Checks if there is altgr character.
+ *
+ * @param {!Array.<!Object>} keySpecs The list of key specs.
+ * @return {[boolean, boolean]} A list with two boolean values, the first is
+ * for whether there is altgr character of letter keys, the second is for
+ * symbol keys.
+ * @private
+ */
+KeysetView.prototype.hasAltGrCharacterInTheKeyset_ = function(keySpecs) {
+ var result = [false, false];
+ for (var i = 0; i < keySpecs.length; i++) {
+ var spec = keySpecs[i];
+ var characters = spec[SpecNodeName.CHARACTERS];
+ if (characters && (!!characters[2] || !!characters[3])) {
+ var index = i18n.input.chrome.inputview.util.isLetterKey(
+ characters) ? 0 : 1;
+ result[index] = true;
+ }
+ }
+ return result;
+};
+
+
+/**
+ * Creates a soft key.
+ *
+ * @param {Object} spec The specification.
+ * @param {!Array.<boolean, boolean>} hasAltGrCharacterInTheKeyset The list
+ * of results for whether there is altgr character, the first for letter
+ * key, the second for symbol key.
+ * @return {i18n.input.chrome.inputview.elements.Element} The soft key.
+ * @private
+ */
+KeysetView.prototype.createKey_ = function(spec, hasAltGrCharacterInTheKeyset) {
+ var type = spec[SpecNodeName.TYPE];
+ var id = spec[SpecNodeName.ID];
+ var keyCode = spec[SpecNodeName.KEY_CODE]; // Could be undefined.
+ var name = spec[SpecNodeName.NAME];
+ var characters = spec[SpecNodeName.CHARACTERS];
+ var iconCssClass = spec[SpecNodeName.ICON_CSS_CLASS];
+ var textCssClass = spec[SpecNodeName.TEXT_CSS_CLASS];
+ var toKeyset = spec[SpecNodeName.TO_KEYSET];
+ var toKeysetName = spec[SpecNodeName.
+ TO_KEYSET_NAME];
+ var elem = null;
+ switch (type) {
+ case ElementType.MODIFIER_KEY:
+ var toState = spec[SpecNodeName.TO_STATE];
+ var supportSticky = spec[SpecNodeName.SUPPORT_STICKY];
+ elem = new content.ModifierKey(id, name, iconCssClass, toState,
+ this.dataModel_.stateManager, supportSticky);
+ break;
+ case ElementType.SPACE_KEY:
+ this.spaceKey = new content.SpaceKey(id, this.dataModel_.stateManager,
+ this.title_, characters, undefined, iconCssClass);
+ elem = this.spaceKey;
+ break;
+ case ElementType.EN_SWITCHER:
+ elem = new content.EnSwitcherKey(id, type, name, iconCssClass,
+ this.dataModel_.stateManager, Css.EN_SWITCHER_DEFAULT,
+ Css.EN_SWITCHER_ENGLISH);
+ break;
+ case ElementType.BACKSPACE_KEY:
+ case ElementType.ENTER_KEY:
+ case ElementType.TAB_KEY:
+ case ElementType.ARROW_UP:
+ case ElementType.ARROW_DOWN:
+ case ElementType.ARROW_LEFT:
+ case ElementType.ARROW_RIGHT:
+ case ElementType.HIDE_KEYBOARD_KEY:
+ case ElementType.GLOBE_KEY:
+ elem = new content.FunctionalKey(id, type, name, iconCssClass);
+ break;
+ case ElementType.TAB_BAR_KEY:
+ elem = new content.TabBarKey(id, type, name, iconCssClass,
+ toKeyset, this.dataModel_.stateManager);
+ break;
+ case ElementType.EMOJI_KEY:
+ var text = spec[SpecNodeName.TEXT];
+ var isEmoticon = spec[SpecNodeName.IS_EMOTICON];
+ elem = new content.EmojiKey(id, type, text, iconCssClass, isEmoticon);
+ break;
+ case ElementType.PAGE_INDICATOR:
+ elem = new content.PageIndicator(id, type);
+ break;
+ case ElementType.IME_SWITCH:
+ elem = new content.FunctionalKey(id, type, name, iconCssClass, undefined,
+ textCssClass);
+ break;
+ case ElementType.MENU_KEY:
+ elem = new content.MenuKey(id, type, name, iconCssClass, toKeyset);
+ break;
+ case ElementType.SWITCHER_KEY:
+ var record = spec[SpecNodeName.RECORD];
+ elem = new content.SwitcherKey(id, type, name, iconCssClass, toKeyset,
+ toKeysetName, record);
+ break;
+ case ElementType.COMPACT_KEY:
+ var hintText = spec[SpecNodeName.HINT_TEXT];
+ var text = spec[SpecNodeName.TEXT];
+ var marginLeftPercent = spec[SpecNodeName.MARGIN_LEFT_PERCENT];
+ var marginRightPercent = spec[SpecNodeName.MARGIN_RIGHT_PERCENT];
+ var isGrey = spec[SpecNodeName.IS_GREY];
+ var moreKeys = spec[SpecNodeName.MORE_KEYS];
+ elem = new content.CompactKey(
+ id, text, hintText, this.dataModel_.stateManager, this.hasShift,
+ marginLeftPercent, marginRightPercent, isGrey, moreKeys);
+ break;
+ case ElementType.CHARACTER_KEY:
+ var isLetterKey = i18n.input.chrome.inputview.util.isLetterKey(
+ characters);
+ elem = new content.CharacterKey(id, keyCode || 0,
+ characters, isLetterKey, hasAltGrCharacterInTheKeyset[isLetterKey],
+ this.dataModel_.settings.alwaysRenderAltGrCharacter,
+ this.dataModel_.stateManager,
+ goog.i18n.bidi.isRtlLanguage(this.languageCode));
+ break;
+
+ case ElementType.BACK_BUTTON:
+ elem = new content.CandidateButton(
+ id, ElementType.BACK_BUTTON, iconCssClass,
+ chrome.i18n.getMessage('HANDWRITING_BACK'), this);
+ break;
+ }
+ if (elem) {
+ this.childMap_[id] = elem;
+ }
+ return elem;
+};
+
+
+/**
+ * Gets the view for the key.
+ *
+ * @param {string} code The code of the key.
+ * @return {i18n.input.chrome.inputview.elements.content.SoftKey} The soft key.
+ */
+KeysetView.prototype.getViewForKey = function(code) {
+ return this.keyboardView_.getViewForKey(code);
+};
+
+
+/**
+ * Gets the width in weight for a entire row.
+ *
+ * @return {number} .
+ */
+KeysetView.prototype.getWidthInWeight = function() {
+ if (this.rows_.length > 0) {
+ return this.rows_[0].getWidthInWeight();
+ }
+
+ return 0;
+};
+
+
+/**
+ * Whether there are strokes on canvas.
+ *
+ * @return {boolean} Whether there are strokes on canvas.
+ */
+KeysetView.prototype.hasStrokesOnCanvas = function() {
+ if (this.canvasView) {
+ return this.canvasView.hasStrokesOnCanvas();
+ } else {
+ return false;
+ }
+};
+
+
+/**
+ * Cleans the stokes.
+ */
+KeysetView.prototype.cleanStroke = function() {
+ if (this.canvasView) {
+ this.canvasView.reset();
+ }
+};
+
+
+/**
+ * Checks the view whether is handwriting panel.
+ *
+ * @return {boolean} Whether is handwriting panel.
+ */
+KeysetView.prototype.isHandwriting = function() {
+ return this.keyboardCode_ == 'hwt';
+};
+
+
+/**
+ * Get the subview of the keysetview according to the id.
+ *
+ * @param {string} id The id.
+ * @return {i18n.input.chrome.inputview.elements.Element}
+ */
+KeysetView.prototype.getChildViewById = function(id) {
+ return this.childMap_[id];
+};
+
+
+/**
+ * Activate the current keyset view instance.
+ *
+ * @param {string} rawKeyset The raw keyset.
+ */
+KeysetView.prototype.activate = function(rawKeyset) {
+ if (goog.array.contains(util.KEYSETS_HAVE_EN_SWTICHER, rawKeyset)) {
+ this.updateCondition(ConditionName.SHOW_EN_SWITCHER_KEY, true);
+ var elem = this.getElement();
+ var name = rawKeyset.replace(/\-.*$/, '').toUpperCase();
+ goog.dom.classlist.add(elem, Css[name]);
+ }
+};
+
+
+/**
+ * Deactivate the current keyset view instance.
+ *
+ * @param {string} rawKeyset The raw keyset id map to the instance keyset id.
+ */
+KeysetView.prototype.deactivate = goog.nullFunction;
+}); // goog.scope
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/menuitem.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/menuitem.js
new file mode 100644
index 0000000..f4e884a
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/menuitem.js
@@ -0,0 +1,138 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.elements.content.MenuItem');
+
+goog.require('goog.dom');
+goog.require('goog.dom.TagName');
+goog.require('goog.dom.classlist');
+goog.require('goog.style');
+goog.require('i18n.input.chrome.inputview.Css');
+goog.require('i18n.input.chrome.inputview.elements.Element');
+goog.require('i18n.input.chrome.inputview.elements.ElementType');
+
+
+goog.scope(function() {
+var Css = i18n.input.chrome.inputview.Css;
+var TagName = goog.dom.TagName;
+var ElementType = i18n.input.chrome.inputview.elements.ElementType;
+
+/**
+ * The menu item in the menu.
+ *
+ * @param {string} id .
+ * @param {Object} item The list time to be added.
+ * @param {i18n.input.chrome.inputview.elements.content.MenuItem.Type}
+ * menuItemType .
+ * @param {goog.events.EventTarget=} opt_eventTarget The parent event target.
+ * @constructor
+ * @extends {i18n.input.chrome.inputview.elements.Element}
+ */
+i18n.input.chrome.inputview.elements.content.MenuItem = function(id, item,
+ menuItemType, opt_eventTarget) {
+ goog.base(this, id, ElementType.MENU_ITEM, opt_eventTarget);
+
+ /**
+ * @type {Object}
+ * @private
+ * */
+ this.item_ = item;
+
+ /**
+ * @type {i18n.input.chrome.inputview.elements.content.MenuItem.Type}
+ * @private
+ */
+ this.menuItemType_ = menuItemType;
+
+ this.pointerConfig.stopEventPropagation = false;
+ this.pointerConfig.preventDefault = false;
+};
+var MenuItem = i18n.input.chrome.inputview.elements.content.MenuItem;
+goog.inherits(MenuItem, i18n.input.chrome.inputview.elements.Element);
+
+
+/**
+ * The type of this MenuItem.
+ *
+ * @enum {number}
+ */
+MenuItem.Type = {
+ LIST_ITEM: 0,
+ FOOTER_ITEM: 1
+};
+
+
+/** @override */
+MenuItem.prototype.createDom = function() {
+ goog.base(this, 'createDom');
+
+ var dom = this.getDomHelper();
+ var elem = this.getElement();
+ switch (this.menuItemType_) {
+ case MenuItem.Type.LIST_ITEM:
+ goog.dom.classlist.add(elem,
+ i18n.input.chrome.inputview.Css.MENU_LIST_ITEM);
+ var indicatorDiv = dom.createDom(goog.dom.TagName.DIV,
+ i18n.input.chrome.inputview.Css.MENU_LIST_INDICATOR);
+ if (this.item_['iconURL']) {
+ indicatorDiv.style.backgroundImage =
+ 'url(' + this.item_['iconURL'] + ')';
+ } else {
+ var indicatorTextDiv = dom.createDom(goog.dom.TagName.DIV,
+ i18n.input.chrome.inputview.Css.MENU_LIST_INDICATOR_NAME);
+ indicatorTextDiv.textContent = this.item_['indicator'];
+ dom.appendChild(indicatorDiv, indicatorTextDiv);
+ }
+ dom.appendChild(elem, indicatorDiv);
+
+ var nameDiv = dom.createDom(goog.dom.TagName.DIV,
+ i18n.input.chrome.inputview.Css.MENU_LIST_NAME);
+ var nameText = dom.createDom(goog.dom.TagName.DIV);
+ nameText.innerText = this.item_['name'];
+ dom.appendChild(nameDiv, nameText);
+ dom.appendChild(elem, nameDiv);
+ break;
+ case MenuItem.Type.FOOTER_ITEM:
+ goog.dom.classlist.add(elem,
+ i18n.input.chrome.inputview.Css.MENU_FOOTER_ITEM);
+ goog.dom.classlist.add(elem, this.item_['iconCssClass']);
+ }
+};
+
+
+/**
+ * Gets the command of this menu item.
+ */
+MenuItem.prototype.getCommand = function() {
+ return this.item_['command'];
+};
+
+
+/** @override */
+MenuItem.prototype.setHighlighted = function(highlight) {
+ if (highlight) {
+ goog.dom.classlist.add(this.getElement(), Css.ELEMENT_HIGHLIGHT);
+ } else {
+ goog.dom.classlist.remove(this.getElement(), Css.ELEMENT_HIGHLIGHT);
+ }
+};
+
+
+/**
+ * Append a checkmark to this element.
+ */
+MenuItem.prototype.check = function() {
+ goog.dom.classlist.add(this.getElement(), Css.CHECKED_MENU_LIST);
+};
+
+}); // goog.scope
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/menukey.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/menukey.js
new file mode 100644
index 0000000..62fc950
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/menukey.js
@@ -0,0 +1,63 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.elements.content.MenuKey');
+
+goog.require('i18n.input.chrome.inputview.elements.content.FunctionalKey');
+
+goog.scope(function() {
+
+
+
+/**
+ * The three dots menu key.
+ *
+ * @param {string} id The id.
+ * @param {!i18n.input.chrome.inputview.elements.ElementType} type The element
+ * type.
+ * @param {string} text The text.
+ * @param {string} iconCssClass The css class for the icon.
+ * @param {string} toKeyset The keyset id.
+ * @param {goog.events.EventTarget=} opt_eventTarget The event target.
+ * @constructor
+ * @extends {i18n.input.chrome.inputview.elements.content.FunctionalKey}
+ */
+i18n.input.chrome.inputview.elements.content.MenuKey = function(id, type,
+ text, iconCssClass, toKeyset, opt_eventTarget) {
+ goog.base(this, id, type, text, iconCssClass, opt_eventTarget);
+
+ /**
+ * The id of the key set to go after this switcher key in menu is pressed.
+ *
+ * @type {string}
+ */
+ this.toKeyset = toKeyset;
+
+};
+goog.inherits(i18n.input.chrome.inputview.elements.content.MenuKey,
+ i18n.input.chrome.inputview.elements.content.FunctionalKey);
+var MenuKey = i18n.input.chrome.inputview.elements.content.MenuKey;
+
+
+/** @override */
+MenuKey.prototype.resize = function(width,
+ height) {
+ goog.base(this, 'resize', width, height);
+
+ // TODO: override width to remove space between menu key and the
+ // following key.
+};
+
+
+
+}); // goog.scope
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/menuview.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/menuview.js
new file mode 100644
index 0000000..613df77
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/menuview.js
@@ -0,0 +1,372 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.elements.content.MenuView');
+
+goog.require('goog.dom.TagName');
+goog.require('goog.dom.classlist');
+goog.require('goog.style');
+goog.require('i18n.input.chrome.inputview.Css');
+goog.require('i18n.input.chrome.inputview.elements.Element');
+goog.require('i18n.input.chrome.inputview.elements.ElementType');
+goog.require('i18n.input.chrome.inputview.elements.content.MenuItem');
+goog.require('i18n.input.chrome.inputview.util');
+
+
+
+goog.scope(function() {
+var ElementType = i18n.input.chrome.inputview.elements.ElementType;
+var MenuItem = i18n.input.chrome.inputview.elements.content.MenuItem;
+var Css = i18n.input.chrome.inputview.Css;
+
+
+/**
+ * The view for IME switcher, layout switcher and settings menu popup..
+ *
+ * @param {goog.events.EventTarget=} opt_eventTarget The parent event target.
+ * @constructor
+ * @extends {i18n.input.chrome.inputview.elements.Element}
+ */
+// TODO: MenuView could extend from AltDataView after some refactor.
+i18n.input.chrome.inputview.elements.content.MenuView = function(
+ opt_eventTarget) {
+ goog.base(this, '', ElementType.MENU_VIEW, opt_eventTarget);
+
+ this.pointerConfig.stopEventPropagation = false;
+ this.pointerConfig.preventDefault = false;
+};
+goog.inherits(i18n.input.chrome.inputview.elements.content.MenuView,
+ i18n.input.chrome.inputview.elements.Element);
+var MenuView = i18n.input.chrome.inputview.elements.content.MenuView;
+
+
+/**
+ * The supported command.
+ *
+ * @enum {number}
+ */
+MenuView.Command = {
+ SWITCH_IME: 0,
+ SWITCH_KEYSET: 1,
+ OPEN_EMOJI: 2,
+ OPEN_HANDWRITING: 3,
+ OPEN_SETTING: 4
+};
+
+
+/**
+ * The maximal number of visible input methods in the view. If more than 3 input
+ * methods are enabled, only 3 of them will show and others can be scrolled into
+ * view. The reason we have this limiation is because menu view can not be
+ * higher than the keyboard view.
+ *
+ * @type {number}
+ * @private
+ */
+MenuView.MAXIMAL_VISIBLE_IMES_ = 3;
+
+
+/**
+ * The width of the popup menu.
+ *
+ * @type {number}
+ * @private
+ */
+MenuView.WIDTH_ = 300;
+
+
+/**
+ * The height of the popup menu item.
+ *
+ * @type {number}
+ * @private
+ */
+MenuView.LIST_ITEM_HEIGHT_ = 50;
+
+
+/**
+ * The cover element.
+ * Note: The reason we use a separate cover element instead of the view is
+ * because of the opacity. We can not reassign the opacity in child element.
+ *
+ * @type {!Element}
+ * @private
+ */
+MenuView.prototype.coverElement_;
+
+
+/** @override */
+MenuView.prototype.createDom = function() {
+ goog.base(this, 'createDom');
+
+ var dom = this.getDomHelper();
+ var elem = this.getElement();
+ goog.dom.classlist.add(elem, Css.MENU_VIEW);
+ this.coverElement_ = dom.createDom(goog.dom.TagName.DIV, Css.ALTDATA_COVER);
+ dom.appendChild(document.body, this.coverElement_);
+ goog.style.setElementShown(this.coverElement_, false);
+
+ this.coverElement_['view'] = this;
+};
+
+
+/** @override */
+MenuView.prototype.enterDocument = function() {
+ goog.base(this, 'enterDocument');
+
+ goog.style.setElementShown(this.getElement(), false);
+};
+
+
+/**
+ * Shows the menu view.
+ * @param {!i18n.input.chrome.inputview.elements.content.SoftKey} key The key
+ * triggerred this altdata view.
+ * @param {!string} currentKeysetId The keyset ID that this menu key belongs to.
+ * @param {boolean} isCompact True if the keyset that owns the menu key is a
+ * compact layout.
+ * @param {boolean} enableCompactLayout True if the keyset that owns the menu
+ * key enabled compact layout.
+ * @param {!string} currentInputMethod The current input method ID.
+ * @param {?Array.<Object>} inputMethods The list of activated input methods.
+ * @param {boolean} hasHwt Whether to add handwriting button.
+ * @param {boolean} enableSettings Whether to add a link to settings page.
+ * @param {boolean} hasEmoji Whether to enable emoji.
+ */
+MenuView.prototype.show = function(key, currentKeysetId, isCompact,
+ enableCompactLayout, currentInputMethod, inputMethods, hasHwt,
+ enableSettings, hasEmoji) {
+ var ElementType = i18n.input.chrome.inputview.elements.ElementType;
+ var dom = this.getDomHelper();
+ if (key.type != ElementType.MENU_KEY) {
+ console.error('Only menu key should trigger menu view to show');
+ return;
+ }
+ this.triggeredBy = key;
+ var coordinate = goog.style.getClientPosition(key.getElement());
+ var x = coordinate.x;
+ // y is the maximual height that menu view can have.
+ var y = coordinate.y;
+
+ goog.style.setElementShown(this.getElement(), true);
+ // may not need to remove child.
+ dom.removeChildren(this.getElement());
+
+ var totalHeight = 0;
+ totalHeight += this.addInputMethodItems_(currentInputMethod, inputMethods);
+ totalHeight += this.addLayoutSwitcherItem_(key, currentKeysetId, isCompact,
+ enableCompactLayout);
+ if (hasHwt || enableSettings || hasEmoji) {
+ totalHeight += this.addFooterItems_(hasHwt, enableSettings, hasEmoji);
+ }
+
+
+ var left = x;
+ //TODO: take care of elemTop < 0. A scrollable view is probably needed.
+ var elemTop = y - totalHeight;
+
+ goog.style.setPosition(this.getElement(), left, elemTop);
+ goog.style.setElementShown(this.coverElement_, true);
+ this.triggeredBy.setHighlighted(true);
+};
+
+
+/**
+ * Hides the menu view.
+ */
+MenuView.prototype.hide = function() {
+ goog.style.setElementShown(this.getElement(), false);
+ goog.style.setElementShown(this.coverElement_, false);
+ if (this.triggeredBy) {
+ this.triggeredBy.setHighlighted(false);
+ }
+};
+
+
+/**
+ * Adds the list of activated input methods.
+ *
+ * @param {!string} currentInputMethod The current input method ID.
+ * @param {?Array.<Object>} inputMethods The list of activated input methods.
+ * @return {number} The height of the ime list container.
+ * @private
+ */
+MenuView.prototype.addInputMethodItems_ = function(currentInputMethod,
+ inputMethods) {
+ var dom = this.getDomHelper();
+ var container = dom.createDom(goog.dom.TagName.DIV,
+ Css.IME_LIST_CONTAINER);
+
+ for (var i = 0; i < inputMethods.length; i++) {
+ var inputMethod = inputMethods[i];
+ var listItem = {};
+ listItem['indicator'] = inputMethod['indicator'];
+ listItem['name'] = inputMethod['name'];
+ listItem['command'] =
+ [MenuView.Command.SWITCH_IME, inputMethod['id']];
+ var imeItem = new MenuItem(String(i), listItem, MenuItem.Type.LIST_ITEM);
+ imeItem.render(container);
+ if (currentInputMethod == inputMethod['id']) {
+ imeItem.check();
+ }
+ goog.style.setSize(imeItem.getElement(), MenuView.WIDTH_,
+ MenuView.LIST_ITEM_HEIGHT_);
+ }
+
+ var containerHeight = inputMethods.length > MenuView.MAXIMAL_VISIBLE_IMES_ ?
+ MenuView.LIST_ITEM_HEIGHT_ * MenuView.MAXIMAL_VISIBLE_IMES_ :
+ MenuView.LIST_ITEM_HEIGHT_ * inputMethods.length;
+ goog.style.setSize(container, MenuView.WIDTH_, containerHeight);
+
+ dom.appendChild(this.getElement(), container);
+ return containerHeight;
+};
+
+
+/**
+ * Add the full/compact layout switcher item. If a full layout does not have or
+ * disabled compact layout, this is a noop.
+ *
+ * @param {!i18n.input.chrome.inputview.elements.content.SoftKey} key The key
+ * triggerred this altdata view.
+ * @param {!string} currentKeysetId The keyset ID that this menu key belongs to.
+ * @param {boolean} isCompact True if the keyset that owns the menu key is a
+ * compact layout.
+ * @param {boolean} enableCompactLayout True if the keyset that owns the menu
+ * key enabled compact layout.
+ * @return {number} The height of the layout switcher item.
+ * @private
+ */
+MenuView.prototype.addLayoutSwitcherItem_ = function(key, currentKeysetId,
+ isCompact, enableCompactLayout) {
+ // Some full layouts either disabled compact layout (CJK) or do not have one.
+ // Do not add a layout switcher item in this case.
+ if (!isCompact && !enableCompactLayout) {
+ return 0;
+ }
+ var dom = this.getDomHelper();
+ // Adds layout switcher.
+ var layoutSwitcher = {};
+ if (isCompact) {
+ layoutSwitcher['iconURL'] = 'images/regular_size.png';
+ layoutSwitcher['name'] = chrome.i18n.getMessage('SWITCH_TO_FULL_LAYOUT');
+ var fullLayoutId = currentKeysetId.split('.')[0];
+ layoutSwitcher['command'] =
+ [MenuView.Command.SWITCH_KEYSET, fullLayoutId];
+ } else {
+ layoutSwitcher['iconURL'] = 'images/compact.png';
+ layoutSwitcher['name'] = chrome.i18n.getMessage('SWITCH_TO_COMPACT_LAYOUT');
+ if (goog.array.contains(i18n.input.chrome.inputview.util.KEYSETS_USE_US,
+ currentKeysetId)) {
+ key.toKeyset = currentKeysetId + '.compact.qwerty';
+ }
+ layoutSwitcher['command'] =
+ [MenuView.Command.SWITCH_KEYSET, key.toKeyset];
+
+ }
+ var layoutSwitcherItem = new MenuItem('MenuLayoutSwitcher', layoutSwitcher,
+ MenuItem.Type.LIST_ITEM);
+ layoutSwitcherItem.render(this.getElement());
+ goog.style.setSize(layoutSwitcherItem.getElement(), MenuView.WIDTH_,
+ MenuView.LIST_ITEM_HEIGHT_);
+
+ return MenuView.LIST_ITEM_HEIGHT_;
+};
+
+
+/**
+ * Adds a few items into the menu view. We only support handwriting and settings
+ * now.
+ *
+ * @param {boolean} hasHwt Whether to add handwriting button.
+ * @param {boolean} enableSettings Whether to add settings button.
+ * @param {boolean} hasEmoji Whether to add emoji button.
+ * @return {number} The height of the footer.
+ * @private
+ */
+MenuView.prototype.addFooterItems_ = function(hasHwt, enableSettings,
+ hasEmoji) {
+ var dom = this.getDomHelper();
+ var footer = dom.createDom(goog.dom.TagName.DIV, Css.MENU_FOOTER);
+ if (hasEmoji) {
+ var emoji = {};
+ emoji['iconCssClass'] = Css.MENU_FOOTER_EMOJI_BUTTON;
+ emoji['command'] = [MenuView.Command.OPEN_EMOJI];
+ var emojiFooter = new MenuItem('emoji', emoji,
+ MenuItem.Type.FOOTER_ITEM);
+ emojiFooter.render(footer);
+ }
+
+ if (hasHwt) {
+ var handWriting = {};
+ handWriting['iconCssClass'] = Css.MENU_FOOTER_HANDWRITING_BUTTON;
+ handWriting['command'] = [MenuView.Command.OPEN_HANDWRITING];
+ var handWritingFooter = new MenuItem('handwriting', handWriting,
+ MenuItem.Type.FOOTER_ITEM);
+ handWritingFooter.render(footer);
+ }
+
+ if (enableSettings) {
+ var setting = {};
+ setting['iconCssClass'] = Css.MENU_FOOTER_SETTING_BUTTON;
+ setting['command'] = [MenuView.Command.OPEN_SETTING];
+ var settingFooter = new MenuItem('setting', setting,
+ MenuItem.Type.FOOTER_ITEM);
+ settingFooter.render(footer);
+ }
+
+ // Sets footer itmes' width.
+ var elems = dom.getChildren(footer);
+ var len = elems.length;
+ var subWidth = Math.ceil(MenuView.WIDTH_ / len);
+ var i = 0;
+ for (; i < len - 1; i++) {
+ elems[i].style.width = subWidth + 'px';
+ }
+ elems[i].style.width = (MenuView.WIDTH_ - subWidth * (len - 1)) + 'px';
+
+ dom.appendChild(this.getElement(), footer);
+ goog.style.setSize(footer, MenuView.WIDTH_, MenuView.LIST_ITEM_HEIGHT_);
+
+ return MenuView.LIST_ITEM_HEIGHT_;
+};
+
+
+/**
+ * Gets the cover element.
+ *
+ * @return {!Element} The cover element.
+ */
+MenuView.prototype.getCoverElement = function() {
+ return this.coverElement_;
+};
+
+
+/** @override */
+MenuView.prototype.resize = function(width, height) {
+ goog.base(this, 'resize', width, height);
+
+ goog.style.setSize(this.coverElement_, width, height);
+ // Hides the menu view directly after resize.
+ this.hide();
+};
+
+
+/** @override */
+MenuView.prototype.disposeInternal = function() {
+ this.getElement()['view'] = null;
+
+ goog.base(this, 'disposeInternal');
+};
+}); // goog.scope
+
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/modifierkey.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/modifierkey.js
new file mode 100644
index 0000000..7a973ae
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/modifierkey.js
@@ -0,0 +1,146 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.elements.content.ModifierKey');
+
+goog.require('goog.dom.TagName');
+goog.require('goog.dom.classlist');
+goog.require('i18n.input.chrome.inputview.Css');
+goog.require('i18n.input.chrome.inputview.StateType');
+goog.require('i18n.input.chrome.inputview.elements.ElementType');
+goog.require('i18n.input.chrome.inputview.elements.content.FunctionalKey');
+
+
+
+goog.scope(function() {
+var StateType = i18n.input.chrome.inputview.StateType;
+
+
+
+/**
+ * The modifier key.
+ *
+ * @param {string} id the id.
+ * @param {string} name the name.
+ * @param {string} iconCssClass The css class for the icon.
+ * @param {!i18n.input.chrome.inputview.StateType} toState The state.
+ * @param {!i18n.input.chrome.inputview.StateManager} stateManager The state
+ * manager.
+ * @param {boolean} supportSticky True if this modifier key supports sticky.
+ * @param {goog.events.EventTarget=} opt_eventTarget The event target.
+ * @constructor
+ * @extends {i18n.input.chrome.inputview.elements.content.FunctionalKey}
+ */
+i18n.input.chrome.inputview.elements.content.ModifierKey = function(id, name,
+ iconCssClass, toState, stateManager, supportSticky, opt_eventTarget) {
+ goog.base(this, id,
+ i18n.input.chrome.inputview.elements.ElementType.MODIFIER_KEY,
+ name, iconCssClass, opt_eventTarget);
+
+ /**
+ * The state when click on the key.
+ *
+ * @type {!i18n.input.chrome.inputview.StateType}
+ */
+ this.toState = toState;
+
+ /**
+ * The state manager.
+ *
+ * @type {!i18n.input.chrome.inputview.StateManager}
+ * @private
+ */
+ this.stateManager_ = stateManager;
+
+ /**
+ * True if supports sticky.
+ *
+ * @type {boolean}
+ */
+ this.supportSticky = supportSticky;
+
+ if (supportSticky) {
+ this.pointerConfig.dblClick = true;
+ this.pointerConfig.longPressWithPointerUp = true;
+ this.pointerConfig.longPressDelay = 1200;
+ }
+};
+goog.inherits(i18n.input.chrome.inputview.elements.content.ModifierKey,
+ i18n.input.chrome.inputview.elements.content.FunctionalKey);
+var ModifierKey = i18n.input.chrome.inputview.elements.content.ModifierKey;
+
+
+/**
+ * The dot icon for capslock.
+ *
+ * @type {!Element}
+ * @private
+ */
+ModifierKey.prototype.dotIcon_;
+
+
+/**
+ * True if a double click is just happened.
+ *
+ * @type {boolean}
+ */
+ModifierKey.prototype.isDoubleClicking = false;
+
+
+/** @override */
+ModifierKey.prototype.createDom = function() {
+ goog.base(this, 'createDom');
+
+ if (this.toState == i18n.input.chrome.inputview.StateType.CAPSLOCK ||
+ this.supportSticky) {
+ var dom = this.getDomHelper();
+ this.dotIcon_ = dom.createDom(goog.dom.TagName.DIV,
+ i18n.input.chrome.inputview.Css.CAPSLOCK_DOT);
+ dom.appendChild(this.tableCell, this.dotIcon_);
+ }
+
+ this.setAriaLabel(this.getChromeVoxMessage());
+};
+
+
+/** @override */
+ModifierKey.prototype.update = function() {
+ var isStateEnabled = this.stateManager_.hasState(this.toState);
+ var isSticky = this.stateManager_.isSticky(this.toState);
+ this.setHighlighted(isStateEnabled);
+ if (this.dotIcon_) {
+ if (isStateEnabled && isSticky) {
+ goog.dom.classlist.add(this.dotIcon_,
+ i18n.input.chrome.inputview.Css.CAPSLOCK_DOT_HIGHLIGHT);
+ } else {
+ goog.dom.classlist.remove(this.dotIcon_,
+ i18n.input.chrome.inputview.Css.CAPSLOCK_DOT_HIGHLIGHT);
+ }
+ }
+};
+
+
+/** @override */
+ModifierKey.prototype.getChromeVoxMessage = function() {
+ switch (this.toState) {
+ case StateType.SHIFT:
+ return chrome.i18n.getMessage('SHIFT');
+ case StateType.CAPSLOCK:
+ return chrome.i18n.getMessage('CAPSLOCK');
+ case StateType.ALTGR:
+ return chrome.i18n.getMessage('ALTGR');
+ }
+ return '';
+};
+
+}); // goog.scope
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/morekeysshiftoperation.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/morekeysshiftoperation.js
new file mode 100644
index 0000000..1b9a2da
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/morekeysshiftoperation.js
@@ -0,0 +1,27 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.MoreKeysShiftOperation');
+
+
+/**
+ * The operations on more keys when the shift is down.
+ *
+ * @enum {number}
+ */
+i18n.input.chrome.inputview.MoreKeysShiftOperation = {
+ STABLE: 0,
+ TO_UPPER_CASE: 1,
+ TO_LOWER_CASE: 2
+};
+
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/softkey.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/softkey.js
new file mode 100644
index 0000000..8debbc4
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/softkey.js
@@ -0,0 +1,127 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.elements.content.SoftKey');
+
+goog.require('goog.dom.classlist');
+goog.require('goog.math.Coordinate');
+goog.require('goog.style');
+goog.require('i18n.input.chrome.inputview.Css');
+goog.require('i18n.input.chrome.inputview.elements.Element');
+goog.require('i18n.input.chrome.inputview.util');
+
+
+
+goog.scope(function() {
+
+
+
+/**
+ * The base soft key class.
+ *
+ * @param {string} id The id.
+ * @param {!i18n.input.chrome.inputview.elements.ElementType} type The element
+ * type.
+ * @param {goog.events.EventTarget=} opt_eventTarget The event target.
+ * @constructor
+ * @extends {i18n.input.chrome.inputview.elements.Element}
+ */
+i18n.input.chrome.inputview.elements.content.SoftKey = function(id, type,
+ opt_eventTarget) {
+ goog.base(this, id, type, opt_eventTarget);
+
+ /**
+ * The available width.
+ *
+ * @type {number}
+ */
+ this.availableWidth = 0;
+
+ /**
+ * The available height.
+ *
+ * @type {number}
+ */
+ this.availableHeight = 0;
+
+ /**
+ * The nearby keys.
+ *
+ * @type {!Array.<!SoftKey>}
+ */
+ this.nearbyKeys = [];
+};
+goog.inherits(i18n.input.chrome.inputview.elements.content.SoftKey,
+ i18n.input.chrome.inputview.elements.Element);
+var SoftKey = i18n.input.chrome.inputview.elements.content.SoftKey;
+
+
+/**
+ * The coordinate of the center point.
+ *
+ * @type {!goog.math.Coordinate}
+ */
+SoftKey.prototype.centerCoordinate;
+
+
+/**
+ * The coordinate of the top-left point.
+ *
+ * @type {!goog.math.Coordinate}
+ */
+SoftKey.prototype.topLeftCoordinate;
+
+
+/**
+ * The gaussian estimator.
+ *
+ * @type {!i18n.input.chrome.inputview.elements.content.GaussianEstimator}
+ */
+SoftKey.prototype.estimator;
+
+
+/** @override */
+SoftKey.prototype.createDom = function() {
+ goog.base(this, 'createDom');
+
+ goog.dom.classlist.add(this.getElement(),
+ i18n.input.chrome.inputview.Css.SOFT_KEY);
+};
+
+
+/** @override */
+SoftKey.prototype.resize = function(width,
+ height) {
+ goog.base(this, 'resize', width, height);
+
+ var elem = this.getElement();
+ var borderWidth = i18n.input.chrome.inputview.util.getPropertyValue(
+ elem, 'borderWidth');
+ var marginTop = i18n.input.chrome.inputview.util.getPropertyValue(
+ elem, 'marginTop');
+ var marginBottom = i18n.input.chrome.inputview.util.getPropertyValue(
+ elem, 'marginBottom');
+ var marginLeft = i18n.input.chrome.inputview.util.getPropertyValue(
+ elem, 'marginLeft');
+ var marginRight = i18n.input.chrome.inputview.util.getPropertyValue(
+ elem, 'marginRight');
+ var w = width - borderWidth * 2 - marginLeft - marginRight;
+ var h = height - borderWidth * 2 - marginTop - marginBottom;
+ elem.style.width = w + 'px';
+ elem.style.height = h + 'px';
+
+ this.availableWidth = w;
+ this.availableHeight = h;
+};
+
+}); // goog.scope
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/spacekey.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/spacekey.js
new file mode 100644
index 0000000..38a8cc4
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/spacekey.js
@@ -0,0 +1,120 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.elements.content.SpaceKey');
+
+goog.require('goog.dom');
+goog.require('goog.dom.classlist');
+goog.require('i18n.input.chrome.inputview.Css');
+goog.require('i18n.input.chrome.inputview.StateType');
+goog.require('i18n.input.chrome.inputview.elements.ElementType');
+goog.require('i18n.input.chrome.inputview.elements.content.FunctionalKey');
+
+
+
+goog.scope(function() {
+
+
+
+/**
+ * The space key.
+ *
+ * @param {string} id the id.
+ * @param {!i18n.input.chrome.inputview.StateManager} stateManager The state
+ * manager.
+ * @param {string} title The keyboard title.
+ * @param {!Array.<string>=} opt_characters The characters.
+ * @param {goog.events.EventTarget=} opt_eventTarget The event target.
+ * @param {string=} opt_iconCss The icon CSS class
+ * @constructor
+ * @extends {i18n.input.chrome.inputview.elements.content.FunctionalKey}
+ */
+i18n.input.chrome.inputview.elements.content.SpaceKey = function(id,
+ stateManager, title, opt_characters, opt_eventTarget, opt_iconCss) {
+ goog.base(this, id, i18n.input.chrome.inputview.elements.ElementType.
+ SPACE_KEY, opt_iconCss ? '' : title, opt_iconCss || '', opt_eventTarget);
+
+ /**
+ * The characters.
+ *
+ * @type {!Array.<string>}
+ * @private
+ */
+ this.characters_ = opt_characters || [];
+
+ /**
+ * The state manager.
+ *
+ * @type {!i18n.input.chrome.inputview.StateManager}
+ * @private
+ */
+ this.stateManager_ = stateManager;
+
+ // Double click on space key may convert two spaces to a period followed by a
+ // space.
+ this.pointerConfig.dblClick = true;
+ this.pointerConfig.dblClickDelay = 1000;
+};
+goog.inherits(i18n.input.chrome.inputview.elements.content.SpaceKey,
+ i18n.input.chrome.inputview.elements.content.FunctionalKey);
+var SpaceKey = i18n.input.chrome.inputview.elements.content.SpaceKey;
+
+
+/** @override */
+SpaceKey.prototype.createDom = function() {
+ goog.base(this, 'createDom');
+
+ goog.dom.classlist.remove(this.bgElem,
+ i18n.input.chrome.inputview.Css.SPECIAL_KEY_BG);
+};
+
+
+/**
+ * Gets the character.
+ *
+ * @return {string} The character.
+ */
+SpaceKey.prototype.getCharacter = function() {
+ if (this.characters_) {
+ // The index is based on the characters in order:
+ // 0: Default
+ // 1: Shift
+ // 2: ALTGR
+ // 3: SHIFT + ALTGR
+ var index = this.stateManager_.hasState(i18n.input.chrome.inputview.
+ StateType.SHIFT) ? 1 : 0 + this.stateManager_.hasState(
+ i18n.input.chrome.inputview.StateType.ALTGR) ? 2 : 0;
+ if (this.characters_.length > index && this.characters_[index]) {
+ return this.characters_[index];
+ }
+ }
+ return ' ';
+};
+
+
+/**
+ * Updates the title on the space key.
+ *
+ * @param {string} title .
+ * @param {boolean} visible True to set title visible.
+ */
+SpaceKey.prototype.updateTitle = function(title, visible) {
+ if (this.textElem) {
+ this.text = title;
+ goog.dom.setTextContent(this.textElem, visible ? title : '');
+ goog.dom.classlist.add(this.textElem,
+ i18n.input.chrome.inputview.Css.TITLE);
+ }
+};
+
+}); // goog.scope
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/switcherkey.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/switcherkey.js
new file mode 100644
index 0000000..978887e
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/switcherkey.js
@@ -0,0 +1,83 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.elements.content.SwitcherKey');
+
+goog.require('i18n.input.chrome.inputview.elements.content.FunctionalKey');
+
+
+
+goog.scope(function() {
+var FunctionalKey = i18n.input.chrome.inputview.elements.content.FunctionalKey;
+
+
+
+/**
+ * The switcher key which can lead user to a new keyset.
+ *
+ * @param {string} id The id.
+ * @param {!i18n.input.chrome.inputview.elements.ElementType} type The element
+ * type.
+ * @param {string} text The text.
+ * @param {string} iconCssClass The css class for the icon.
+ * @param {string} toKeyset The keyset id.
+ * @param {string} toKeysetName The name of the keyset.
+ * @param {boolean} record True to record the keyset as the default.
+ * @param {goog.events.EventTarget=} opt_eventTarget The event target.
+ * @constructor
+ * @extends {i18n.input.chrome.inputview.elements.content.FunctionalKey}
+ */
+i18n.input.chrome.inputview.elements.content.SwitcherKey = function(id, type,
+ text, iconCssClass, toKeyset, toKeysetName, record, opt_eventTarget) {
+ goog.base(this, id, type, text, iconCssClass, opt_eventTarget);
+
+ /**
+ * The id of the key set to go after this switcher key is pressed.
+ *
+ * @type {string}
+ */
+ this.toKeyset = toKeyset;
+
+ /**
+ * The name of the keyset.
+ *
+ * @type {string}
+ */
+ this.toKeysetName = toKeysetName;
+
+ /**
+ * True to record this keyset and brings it back next time.
+ *
+ * @type {boolean}
+ */
+ this.record = record;
+};
+goog.inherits(i18n.input.chrome.inputview.elements.content.SwitcherKey,
+ FunctionalKey);
+var SwitcherKey = i18n.input.chrome.inputview.elements.content.SwitcherKey;
+
+
+/** @override */
+SwitcherKey.prototype.createDom = function() {
+ goog.base(this, 'createDom');
+
+ this.setAriaLabel(this.getChromeVoxMessage());
+};
+
+
+/** @override */
+SwitcherKey.prototype.getChromeVoxMessage = function() {
+ return chrome.i18n.getMessage('SWITCH_TO') + this.toKeysetName;
+};
+
+}); // goog.scope
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/tabbarkey.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/tabbarkey.js
new file mode 100644
index 0000000..806ef19
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/tabbarkey.js
@@ -0,0 +1,137 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.elements.content.TabBarKey');
+goog.require('goog.dom.TagName');
+goog.require('goog.dom.classlist');
+goog.require('i18n.input.chrome.inputview.Css');
+goog.require('i18n.input.chrome.inputview.elements.content.FunctionalKey');
+goog.require('i18n.input.chrome.message.Name');
+goog.require('i18n.input.chrome.message.Type');
+
+goog.scope(function() {
+
+var Type = i18n.input.chrome.message.Type;
+var Name = i18n.input.chrome.message.Name;
+var Css = i18n.input.chrome.inputview.Css;
+
+
+
+/**
+ * The Tabbar key
+ *
+ * @param {string} id The id.
+ * @param {!i18n.input.chrome.inputview.elements.ElementType} type The element
+ * type.
+ * @param {string} text The text.
+ * @param {string} iconCssClass The css class for the icon.
+ * @param {number} toCategory The category the tabbar key represents.
+ * @param {i18n.input.chrome.inputview.StateManager} stateManager
+ * The state manager.
+ * @param {goog.events.EventTarget=} opt_eventTarget The event target.
+ * @constructor
+ * @extends {i18n.input.chrome.inputview.elements.content.FunctionalKey}
+ */
+i18n.input.chrome.inputview.elements.content.TabBarKey = function(id, type,
+ text, iconCssClass, toCategory, stateManager, opt_eventTarget) {
+ goog.base(this, id, type, text, iconCssClass, opt_eventTarget);
+
+ /**
+ * The id of the key set to go after this switcher key in menu is pressed.
+ *
+ * @type {number}
+ */
+ this.toCategory = toCategory;
+
+ /**
+ * The state manager.
+ *
+ * @type {i18n.input.chrome.inputview.StateManager}
+ */
+ this.stateManager = stateManager;
+
+ this.pointerConfig.stopEventPropagation = false;
+};
+goog.inherits(i18n.input.chrome.inputview.elements.content.TabBarKey,
+ i18n.input.chrome.inputview.elements.content.FunctionalKey);
+var TabBarKey = i18n.input.chrome.inputview.elements.content.TabBarKey;
+
+
+/**
+ * The height of the bottom border
+ *
+ * @type {number}
+ * @private
+ */
+TabBarKey.prototype.BORDER_HEIGHT_ = 4;
+
+
+/** @override */
+TabBarKey.prototype.createDom = function() {
+ goog.base(this, 'createDom');
+ var dom = this.getDomHelper();
+ var elem = this.getElement();
+ goog.dom.classlist.remove(elem, Css.SOFT_KEY);
+ goog.dom.classlist.add(elem, Css.EMOJI_TABBAR_SK);
+ goog.dom.classlist.remove(this.bgElem, Css.SPECIAL_KEY_BG);
+ goog.dom.classlist.add(this.bgElem, Css.EMOJI_TABBAR_KEY);
+ goog.dom.classlist.add(this.iconElem, Css.EMOJI_SWITCH);
+ this.createSeparator_();
+};
+
+
+/** @override */
+TabBarKey.prototype.resize = function(width,
+ height) {
+ goog.base(this, 'resize', width, height);
+ this.tableCell.style.width = this.availableWidth + 'px';
+ this.tableCell.style.height = this.availableHeight -
+ this.BORDER_HEIGHT_ + 'px';
+ this.sepTableCell.style.height = this.availableHeight -
+ this.BORDER_HEIGHT_ + 'px';
+ this.separator.style.height = this.availableHeight * 0.32 + 'px';
+};
+
+
+/**
+ * Create the separator for the tabbar key.
+ *
+ * @private
+ */
+TabBarKey.prototype.createSeparator_ = function() {
+ var dom = this.getDomHelper();
+ var elem = this.getElement();
+ this.sepTableCell = dom.createDom(goog.dom.TagName.DIV, Css.TABLE_CELL);
+ this.separator = dom.createDom(goog.dom.TagName.DIV,
+ Css.CANDIDATE_SEPARATOR);
+ this.separator.style.height = Math.floor(this.height * 0.32) + 'px';
+ dom.appendChild(this.sepTableCell, this.separator);
+ dom.appendChild(this.bgElem, this.sepTableCell);
+};
+
+
+/**
+ * Update the border.
+ *
+ * @param {number} categoryID the categoryID.
+ */
+TabBarKey.prototype.updateBorder = function(categoryID) {
+ if (categoryID == this.toCategory) {
+ goog.dom.classlist.add(this.bgElem, Css.EMOJI_TABBAR_KEY_HIGHLIGHT);
+ goog.dom.classlist.add(this.iconElem, Css.EMOJI_SWITCH_HIGHLIGHT);
+ } else {
+ goog.dom.classlist.remove(this.bgElem, Css.EMOJI_TABBAR_KEY_HIGHLIGHT);
+ goog.dom.classlist.remove(this.iconElem, Css.EMOJI_SWITCH_HIGHLIGHT);
+ }
+};
+}); // goog.scope
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/element.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/element.js
new file mode 100644
index 0000000..467b596
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/element.js
@@ -0,0 +1,187 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.elements.Element');
+
+goog.require('goog.dom.classlist');
+goog.require('goog.events.EventHandler');
+goog.require('goog.style');
+goog.require('goog.ui.Component');
+goog.require('i18n.input.chrome.inputview.Css');
+goog.require('i18n.input.chrome.inputview.PointerConfig');
+
+
+goog.scope(function() {
+
+
+
+/**
+ * The abstract class for element in input view keyboard.
+ *
+ * @param {string} id The id.
+ * @param {!i18n.input.chrome.inputview.elements.ElementType} type The element
+ * type.
+ * @param {goog.events.EventTarget=} opt_eventTarget The event target.
+ * @constructor
+ * @extends {goog.ui.Component}
+ */
+i18n.input.chrome.inputview.elements.Element = function(id, type,
+ opt_eventTarget) {
+ goog.base(this);
+ this.setParentEventTarget(opt_eventTarget || null);
+
+ /**
+ * The id of the element.
+ *
+ * @type {string}
+ */
+ this.id = id;
+
+ /**
+ * The type of the element.
+ *
+ * @type {!i18n.input.chrome.inputview.elements.ElementType}
+ */
+ this.type = type;
+
+ /**
+ * The display of the element.
+ *
+ * @type {string}
+ * @private
+ */
+ this.display_ = '';
+
+ /**
+ * The event handler.
+ *
+ * @type {!goog.events.EventHandler}
+ */
+ this.handler = new goog.events.EventHandler(this);
+
+ /**
+ * The configuration for the pointer.
+ *
+ * @type {!i18n.input.chrome.inputview.PointerConfig}
+ */
+ this.pointerConfig = new i18n.input.chrome.inputview.PointerConfig(false,
+ false, false);
+};
+goog.inherits(i18n.input.chrome.inputview.elements.Element, goog.ui.Component);
+var Element = i18n.input.chrome.inputview.elements.Element;
+
+
+/**
+ * The width of the element.
+ *
+ * @type {number}
+ */
+Element.prototype.width;
+
+
+/**
+ * The height of the element.
+ *
+ * @type {number}
+ */
+Element.prototype.height;
+
+
+/**
+ * Resizes the element.
+ *
+ * @param {number} width The total width.
+ * @param {number} height The total height.
+ */
+Element.prototype.resize = function(width, height) {
+ this.width = width;
+ this.height = height;
+};
+
+
+/** @override */
+Element.prototype.createDom = function() {
+ goog.base(this, 'createDom');
+
+ this.getElement().id = this.id;
+ this.getElement()['view'] = this;
+};
+
+
+/** @override */
+Element.prototype.enterDocument = function() {
+ goog.base(this, 'enterDocument');
+
+ this.display_ = this.getElement().style.display;
+};
+
+
+/**
+ * Whether the element is visible.
+ *
+ * @return {boolean} True if the element is visible.
+ */
+Element.prototype.isVisible = function() {
+ return goog.style.isElementShown(this.getElement());
+};
+
+
+/**
+ * Sets the visibility of the element.
+ *
+ * @param {boolean} visibility True if the element is visible.
+ */
+Element.prototype.setVisible = function(visibility) {
+ this.getElement().style.display = visibility ? this.display_ : 'none';
+};
+
+
+/**
+ * Updates the element.
+ */
+Element.prototype.update = function() {
+ this.setHighlighted(false);
+ for (var i = 0; i < this.getChildCount(); i++) {
+ var child = /** @type {!Element} */ (
+ this.getChildAt(i));
+ child.update();
+ }
+};
+
+
+/**
+ * Sets the highlight of the soft key.
+ *
+ * @param {boolean} highlight True to set it to be highlighted.
+ */
+Element.prototype.setHighlighted = function(
+ highlight) {
+ if (highlight) {
+ goog.dom.classlist.add(this.getElement(),
+ i18n.input.chrome.inputview.Css.ELEMENT_HIGHLIGHT);
+ } else {
+ goog.dom.classlist.remove(this.getElement(),
+ i18n.input.chrome.inputview.Css.ELEMENT_HIGHLIGHT);
+ }
+};
+
+
+/** @override */
+Element.prototype.disposeInternal = function() {
+ this.getElement()['view'] = null;
+ goog.dispose(this.handler);
+
+ goog.base(this, 'disposeInternal');
+};
+
+}); // goog.scope
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/elementtype.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/elementtype.js
new file mode 100644
index 0000000..ec6b0860
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/elementtype.js
@@ -0,0 +1,67 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.elements.ElementType');
+
+
+/**
+ * The element type.
+ *
+ * @enum {number}
+ */
+i18n.input.chrome.inputview.elements.ElementType = {
+ CHARACTER: 0,
+ FUNCTIONAL_KEY: 1,
+ KEYBOARD: 2,
+ LAYOUT_VIEW: 3,
+ LINEAR_LAYOUT: 4,
+ MODIFIER_KEY: 5,
+ CHARACTER_KEY: 6,
+ SOFT_KEY: 7,
+ SOFT_KEY_VIEW: 8,
+ VERTICAL_LAYOUT: 9,
+ CANDIDATE_VIEW: 10,
+ SPACE_KEY: 11,
+ ENTER_KEY: 12,
+ BACKSPACE_KEY: 13,
+ TAB_KEY: 14,
+ ARROW_UP: 15,
+ ARROW_DOWN: 16,
+ ARROW_LEFT: 17,
+ ARROW_RIGHT: 18,
+ HIDE_KEYBOARD_KEY: 19,
+ ALTDATA_VIEW: 20,
+ SWITCHER_KEY: 21,
+ COMPACT_KEY: 22,
+ CANVAS_VIEW: 23,
+ HANDWRITING_LAYOUT: 24,
+ MENU_VIEW: 25,
+ MENU_KEY: 26,
+ GLOBE_KEY: 27,
+ BACK_BUTTON: 28,
+ SHRINK_CANDIDATES: 29,
+ EXPAND_CANDIDATES: 30,
+ CANDIDATES_PAGE_UP: 31,
+ CANDIDATES_PAGE_DOWN: 32,
+ CANDIDATE: 33,
+ EXPANDED_CANDIDATE_VIEW: 34,
+ IME_SWITCH: 35,
+ HWT_PRIVACY_GOT_IT: 36,
+ MENU_ITEM: 37,
+ EMOJI_KEY: 38,
+ TAB_BAR_KEY: 39,
+ EXTENDED_LAYOUT: 40,
+ PAGE_INDICATOR: 41,
+ EN_SWITCHER: 42
+};
+
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/layout/extendedlayout.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/layout/extendedlayout.js
new file mode 100644
index 0000000..1b7e759
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/layout/extendedlayout.js
@@ -0,0 +1,229 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.elements.layout.ExtendedLayout');
+
+goog.require('goog.dom.classlist');
+goog.require('goog.style');
+goog.require('i18n.input.chrome.inputview.Css');
+goog.require('i18n.input.chrome.inputview.elements.Element');
+goog.require('i18n.input.chrome.inputview.elements.ElementType');
+goog.require('i18n.input.chrome.inputview.elements.Weightable');
+
+
+goog.scope(function() {
+var Css = i18n.input.chrome.inputview.Css;
+
+
+
+/**
+ * The extended layout. Each of its children has the same width as its parent.
+ * It can be wider than its parent.
+ *
+ * @param {string} id The id.
+ * @param {goog.events.EventTarget=} opt_eventTarget The event target.
+ * @param {i18n.input.chrome.inputview.Css=} opt_iconCssClass The css class.
+ * @constructor
+ * @extends {i18n.input.chrome.inputview.elements.Element}
+ * @implements {i18n.input.chrome.inputview.elements.Weightable}
+ */
+i18n.input.chrome.inputview.elements.layout.ExtendedLayout = function(id,
+ opt_eventTarget, opt_iconCssClass) {
+ goog.base(this, id, i18n.input.chrome.inputview.elements.ElementType.
+ EXTENDED_LAYOUT, opt_eventTarget);
+
+ /**
+ * The icon Css class for the extendedlayout.
+ *
+ * @type {i18n.input.chrome.inputview.Css}
+ */
+ this.iconCssClass = Css.LINEAR_LAYOUT;
+};
+goog.inherits(i18n.input.chrome.inputview.elements.layout.ExtendedLayout,
+ i18n.input.chrome.inputview.elements.Element);
+var ExtendedLayout = i18n.input.chrome.inputview.elements.layout.ExtendedLayout;
+
+
+/**
+ * The height in weight unit.
+ *
+ * @type {number}
+ * @private
+ */
+ExtendedLayout.prototype.heightInWeight_ = 0;
+
+
+/**
+ * The width in weight unit.
+ *
+ * @type {number}
+ * @private
+ */
+ExtendedLayout.prototype.widthInWeight_ = 0;
+
+
+/**
+ * The time of transition for every transition distance.
+ *
+ * @private {number}
+ */
+ExtendedLayout.BASE_TRANSITION_DURATION_ = 0.2;
+
+
+/**
+ * The transition distance used to calculate transition time.
+ *
+ * @private {number}
+ */
+ExtendedLayout.BASE_TRANSITION_DISTANCE_ = 100;
+
+
+/** @override */
+ExtendedLayout.prototype.getHeightInWeight = function() {
+ return this.heightInWeight_;
+};
+
+
+/** @override */
+ExtendedLayout.prototype.getWidthInWeight = function() {
+ return this.widthInWeight_;
+};
+
+
+/** @override */
+ExtendedLayout.prototype.createDom = function() {
+ goog.base(this, 'createDom');
+ this.elem = this.getElement();
+ goog.dom.classlist.addAll(this.elem,
+ [this.iconCssClass, Css.EMOJI_FONT]);
+};
+
+
+/** @override */
+ExtendedLayout.prototype.enterDocument = function() {
+ goog.base(this, 'enterDocument');
+ this.calculate_();
+};
+
+
+/** @override */
+ExtendedLayout.prototype.resize = function(width, height) {
+ if (width == this.width && height == this.height) {
+ return;
+ }
+ for (var i = 0, len = this.getChildCount(); i < len; i++) {
+ var child = /** @type {i18n.input.chrome.inputview.elements.Element} */ (
+ this.getChildAt(i));
+ child.resize(width, height);
+ }
+ this.getElement().style.width = width * this.getChildCount();
+ goog.base(this, 'resize', width * this.getChildCount(), height);
+};
+
+
+/**
+ * Calculate the height and weight。
+ *
+ * @private
+ */
+ExtendedLayout.prototype.calculate_ = function() {
+ for (var i = 0; i < this.getChildCount(); i++) {
+ var child = /** @type {i18n.input.chrome.inputview.elements.Weightable} */ (
+ this.getChildAt(i));
+ if (this.heightInWeight_ < child.getHeightInWeight()) {
+ this.heightInWeight_ = child.getHeightInWeight();
+ }
+ this.widthInWeight_ += child.getWidthInWeight();
+ }
+};
+
+
+/**
+ * Switch to a page of the emojiSlider.
+ *
+ * @param {number} pageNum The page to switch to.
+ * @private
+ */
+ExtendedLayout.prototype.gotoPage_ = function(pageNum) {
+ var width = goog.style.getSize(this.getElement()).width;
+ var childNum = this.getChildCount();
+ this.elem.style.marginLeft = 0 - width / childNum * pageNum;
+};
+
+
+/**
+ * Slide to a position.
+ *
+ * @param {number} deltaX The slide distance of x-coordinate.
+ */
+ExtendedLayout.prototype.slide = function(deltaX) {
+ this.elem.style.transition = '';
+ var marginLeft = goog.style.getMarginBox(this.elem).left + deltaX;
+ this.elem.style.marginLeft = marginLeft + 'px';
+};
+
+
+/**
+ * Adjust the marginleft to the beginning of a page.
+ *
+ * @param {number=} opt_distance The distance to adjust to.
+ * @return {number} The page to adjust to after calculation.
+ */
+ExtendedLayout.prototype.adjustMarginLeft = function(opt_distance) {
+ var childNum = this.getChildCount();
+ var width = goog.style.getSize(this.elem).width / childNum;
+ var marginLeft = Math.abs(goog.style.getMarginBox(this.elem).left);
+ var prev = Math.floor(marginLeft / width);
+ var next = prev + 1;
+ var pageNum = 0;
+ if (opt_distance) {
+ if (opt_distance >= 0) {
+ pageNum = prev;
+ } else {
+ pageNum = next;
+ }
+ } else if (marginLeft - prev * width < next * width - marginLeft) {
+ pageNum = prev;
+ } else {
+ pageNum = next;
+ }
+ if (pageNum < 0) {
+ pageNum = 0;
+ } else if (pageNum >= childNum) {
+ pageNum = childNum - 1;
+ }
+ if (opt_distance) {
+ this.elem.style.transition = 'margin-left ' +
+ ExtendedLayout.BASE_TRANSITION_DURATION_ + 's';
+ } else {
+ var transitionDuration = Math.abs(marginLeft - pageNum * width) /
+ ExtendedLayout.BASE_TRANSITION_DISTANCE_ *
+ ExtendedLayout.BASE_TRANSITION_DURATION_;
+ this.elem.style.transition = 'margin-left ' +
+ transitionDuration + 's ease-in';
+ }
+ this.gotoPage_(pageNum);
+ return pageNum;
+};
+
+
+/**
+ * Update the category.
+ *
+ * @param {number} pageNum The page number to switch to.
+ */
+ExtendedLayout.prototype.updateCategory = function(pageNum) {
+ this.elem.style.transition = '';
+ this.gotoPage_(pageNum);
+};
+}); // goog.scope
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/layout/handwritinglayout.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/layout/handwritinglayout.js
new file mode 100644
index 0000000..eb24e72
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/layout/handwritinglayout.js
@@ -0,0 +1,121 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.elements.layout.HandwritingLayout');
+
+goog.require('goog.dom.classlist');
+goog.require('i18n.input.chrome.inputview.Css');
+goog.require('i18n.input.chrome.inputview.elements.Element');
+goog.require('i18n.input.chrome.inputview.elements.ElementType');
+goog.require('i18n.input.chrome.inputview.elements.Weightable');
+
+
+goog.scope(function() {
+
+
+
+/**
+ * The linear layout.
+ *
+ * @param {string} id The id.
+ * @param {goog.events.EventTarget=} opt_eventTarget The event target.
+ * @constructor
+ * @extends {i18n.input.chrome.inputview.elements.Element}
+ * @implements {i18n.input.chrome.inputview.elements.Weightable}
+ */
+i18n.input.chrome.inputview.elements.layout.HandwritingLayout = function(id,
+ opt_eventTarget) {
+ goog.base(this, id, i18n.input.chrome.inputview.elements.ElementType.
+ HANDWRITING_LAYOUT, opt_eventTarget);
+};
+goog.inherits(i18n.input.chrome.inputview.elements.layout.HandwritingLayout,
+ i18n.input.chrome.inputview.elements.Element);
+var HandwritingLayout =
+ i18n.input.chrome.inputview.elements.layout.HandwritingLayout;
+
+
+/**
+ * The height in weight unit.
+ *
+ * @type {number}
+ * @private
+ */
+HandwritingLayout.prototype.heightInWeight_ = 0;
+
+
+/**
+ * The width in weight unit.
+ *
+ * @type {number}
+ * @private
+ */
+HandwritingLayout.prototype.widthInWeight_ = 0;
+
+
+/** @override */
+HandwritingLayout.prototype.createDom = function() {
+ goog.base(this, 'createDom');
+
+ goog.dom.classlist.add(this.getElement(), i18n.input.chrome.inputview.Css.
+ HANDWRITING_LAYOUT);
+};
+
+
+/** @override */
+HandwritingLayout.prototype.enterDocument = function() {
+ goog.base(this, 'enterDocument');
+
+ this.calculate_();
+};
+
+
+/**
+ * Gets the first child.
+ *
+ * @private
+ */
+HandwritingLayout.prototype.calculate_ = function() {
+ var child = /** @type {i18n.input.chrome.inputview.elements.Weightable} */ (
+ this.getChildAt(0));
+ this.heightInWeight_ = child.getHeightInWeight();
+ this.widthInWeight_ = child.getWidthInWeight();
+};
+
+
+/** @override */
+HandwritingLayout.prototype.getHeightInWeight = function() {
+ return this.heightInWeight_;
+};
+
+
+/** @override */
+HandwritingLayout.prototype.getWidthInWeight = function() {
+ return this.widthInWeight_;
+};
+
+
+/** @override */
+HandwritingLayout.prototype.resize = function(width, height) {
+ goog.base(this, 'resize', width, height);
+ for (var i = 0; i < this.getChildCount(); i++) {
+ var child = /** @type {i18n.input.chrome.inputview.elements.Element} */ (
+ this.getChildAt(i));
+ child.resize(
+ Math.ceil(width * child.getWidthInWeight() / this.widthInWeight_),
+ Math.ceil(height * child.getHeightInWeight() / this.heightInWeight_));
+ // 85/140 = 0.6
+ child.getElement().style.top =
+ Math.ceil(height * 0.6 / this.heightInWeight_);
+ }
+};
+}); // goog.scope
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/layout/linearlayout.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/layout/linearlayout.js
new file mode 100644
index 0000000..f583c8e
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/layout/linearlayout.js
@@ -0,0 +1,137 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.elements.layout.LinearLayout');
+
+goog.require('goog.dom.classlist');
+goog.require('i18n.input.chrome.inputview.Css');
+goog.require('i18n.input.chrome.inputview.elements.Element');
+goog.require('i18n.input.chrome.inputview.elements.ElementType');
+goog.require('i18n.input.chrome.inputview.elements.Weightable');
+goog.require('i18n.input.chrome.inputview.util');
+
+
+goog.scope(function() {
+
+
+
+/**
+ * The linear layout.
+ *
+ * @param {string} id The id.
+ * @param {goog.events.EventTarget=} opt_eventTarget The event target.
+ * @param {i18n.input.chrome.inputview.Css=} opt_iconCssClass The css class.
+ * @constructor
+ * @extends {i18n.input.chrome.inputview.elements.Element}
+ * @implements {i18n.input.chrome.inputview.elements.Weightable}
+ */
+i18n.input.chrome.inputview.elements.layout.LinearLayout = function(id,
+ opt_eventTarget, opt_iconCssClass) {
+ goog.base(this, id, i18n.input.chrome.inputview.elements.ElementType.
+ LINEAR_LAYOUT, opt_eventTarget);
+
+ /**
+ * The icon Css class for the linearlayout
+ *
+ * @type {i18n.input.chrome.inputview.Css}
+ */
+ this.iconCssClass = opt_iconCssClass ||
+ i18n.input.chrome.inputview.Css.LINEAR_LAYOUT;
+};
+goog.inherits(i18n.input.chrome.inputview.elements.layout.LinearLayout,
+ i18n.input.chrome.inputview.elements.Element);
+var LinearLayout = i18n.input.chrome.inputview.elements.layout.LinearLayout;
+
+
+/**
+ * The height in weight unit.
+ *
+ * @type {number}
+ * @private
+ */
+LinearLayout.prototype.heightInWeight_ = 0;
+
+
+/**
+ * The width in weight unit.
+ *
+ * @type {number}
+ * @private
+ */
+LinearLayout.prototype.widthInWeight_ = 0;
+
+
+/** @override */
+LinearLayout.prototype.createDom = function() {
+ goog.base(this, 'createDom');
+ goog.dom.classlist.add(this.getElement(), this.iconCssClass);
+};
+
+
+/** @override */
+LinearLayout.prototype.enterDocument = function() {
+ goog.base(this, 'enterDocument');
+
+ this.calculate_();
+};
+
+
+/**
+ * Calculate all necessary information after enters the document.
+ *
+ * @private
+ */
+LinearLayout.prototype.calculate_ = function() {
+ for (var i = 0; i < this.getChildCount(); i++) {
+ var child = /** @type {i18n.input.chrome.inputview.elements.Weightable} */ (
+ this.getChildAt(i));
+ if (this.heightInWeight_ < child.getHeightInWeight()) {
+ this.heightInWeight_ = child.getHeightInWeight();
+ }
+ this.widthInWeight_ += child.getWidthInWeight();
+ }
+};
+
+
+/** @override */
+LinearLayout.prototype.getHeightInWeight = function() {
+ return this.heightInWeight_;
+};
+
+
+/** @override */
+LinearLayout.prototype.getWidthInWeight = function() {
+ return this.widthInWeight_;
+};
+
+
+/** @override */
+LinearLayout.prototype.resize = function(width, height) {
+ goog.base(this, 'resize', width, height);
+
+ var weightArray = [];
+ for (var i = 0; i < this.getChildCount(); i++) {
+ var child = /** @type {i18n.input.chrome.inputview.elements.Weightable} */ (
+ this.getChildAt(i));
+ weightArray.push(child.getWidthInWeight());
+ }
+ var splitedWidth = i18n.input.chrome.inputview.util.splitValue(weightArray,
+ width);
+ for (var i = 0; i < this.getChildCount(); i++) {
+ var child = /** @type {i18n.input.chrome.inputview.elements.Element} */ (
+ this.getChildAt(i));
+ child.resize(splitedWidth[i], height);
+ }
+};
+
+}); // goog.scope
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/layout/softkeyview.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/layout/softkeyview.js
new file mode 100644
index 0000000..c2d1fec
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/layout/softkeyview.js
@@ -0,0 +1,150 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.elements.layout.SoftKeyView');
+
+goog.require('goog.dom.classlist');
+goog.require('i18n.input.chrome.inputview.Css');
+goog.require('i18n.input.chrome.inputview.elements.Element');
+goog.require('i18n.input.chrome.inputview.elements.ElementType');
+goog.require('i18n.input.chrome.inputview.elements.Weightable');
+
+
+
+goog.scope(function() {
+
+
+
+/**
+ * The soft key view.
+ *
+ * @param {string} id The id.
+ * @param {number=} opt_widthInWeight The weight of width.
+ * @param {number=} opt_heightInWeight The weight of height.
+ * @param {string=} opt_condition The condition to control whether to render
+ * this soft key view.
+ * @param {string=} opt_giveWeightTo The id of the soft key view to give the
+ * weight if this one is not shown.
+ * @param {goog.events.EventTarget=} opt_eventTarget The event target.
+ * @constructor
+ * @extends {i18n.input.chrome.inputview.elements.Element}
+ * @implements {i18n.input.chrome.inputview.elements.Weightable}
+ */
+i18n.input.chrome.inputview.elements.layout.SoftKeyView = function(id,
+ opt_widthInWeight, opt_heightInWeight, opt_condition,
+ opt_giveWeightTo, opt_eventTarget) {
+ goog.base(this, id, i18n.input.chrome.inputview.elements.ElementType.
+ SOFT_KEY_VIEW, opt_eventTarget);
+
+ /**
+ * The weight of the width.
+ *
+ * @type {number}
+ */
+ this.widthInWeight = opt_widthInWeight || 1;
+
+ /**
+ * The weight of the height.
+ *
+ * @type {number}
+ */
+ this.heightInWeight = opt_heightInWeight || 1;
+
+ /**
+ * The condition to control the visibility of this soft key view.
+ *
+ * @type {string}
+ */
+ this.condition = opt_condition || '';
+
+ /**
+ * The id of another soft key view to give the weight if this
+ * one is not shown.
+ *
+ * @type {string}
+ */
+ this.giveWeightTo = opt_giveWeightTo || '';
+};
+goog.inherits(i18n.input.chrome.inputview.elements.layout.SoftKeyView,
+ i18n.input.chrome.inputview.elements.Element);
+var SoftKeyView = i18n.input.chrome.inputview.elements.layout.SoftKeyView;
+
+
+/**
+ * The soft key bound to this view.
+ *
+ * @type {!i18n.input.chrome.inputview.elements.content.SoftKey}
+ */
+SoftKeyView.prototype.softKey;
+
+
+/**
+ * The weight granted when certain condition happens and another soft key
+ * view is not shown, so its weight transfer here.
+ *
+ * @type {number}
+ */
+SoftKeyView.prototype.dynamicaGrantedWeight = 0;
+
+
+/** @override */
+SoftKeyView.prototype.createDom = function() {
+ goog.base(this, 'createDom');
+
+ var elem = this.getElement();
+ goog.dom.classlist.add(elem, i18n.input.chrome.inputview.Css.SOFT_KEY_VIEW);
+};
+
+
+/** @override */
+SoftKeyView.prototype.getWidthInWeight =
+ function() {
+ return this.isVisible() ? this.widthInWeight + this.dynamicaGrantedWeight : 0;
+};
+
+
+/** @override */
+SoftKeyView.prototype.getHeightInWeight =
+ function() {
+ return this.isVisible() ? this.heightInWeight : 0;
+};
+
+
+/** @override */
+SoftKeyView.prototype.resize = function(
+ width, height) {
+ goog.base(this, 'resize', width, height);
+
+ var elem = this.getElement();
+ elem.style.width = width + 'px';
+ elem.style.height = height + 'px';
+ if (this.softKey) {
+ this.softKey.resize(width, height);
+ }
+};
+
+
+/**
+ * Binds a soft key to this soft key view.
+ *
+ * @param {!i18n.input.chrome.inputview.elements.content.SoftKey} softKey The
+ * soft key.
+ */
+SoftKeyView.prototype.bindSoftKey = function(
+ softKey) {
+ this.softKey = softKey;
+ this.removeChildren(true);
+ this.addChild(softKey, true);
+};
+
+}); // goog.scope
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/layout/verticallayout.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/layout/verticallayout.js
new file mode 100644
index 0000000..3551ac0
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/layout/verticallayout.js
@@ -0,0 +1,136 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.elements.layout.VerticalLayout');
+
+goog.require('goog.dom.classlist');
+goog.require('i18n.input.chrome.inputview.Css');
+goog.require('i18n.input.chrome.inputview.elements.Element');
+goog.require('i18n.input.chrome.inputview.elements.ElementType');
+goog.require('i18n.input.chrome.inputview.elements.Weightable');
+goog.require('i18n.input.chrome.inputview.util');
+
+
+goog.scope(function() {
+
+
+
+/**
+ * The vertical layout.
+ *
+ * @param {string} id The id.
+ * @param {goog.events.EventTarget=} opt_eventTarget The event target.
+ * @param {!i18n.input.chrome.inputview.elements.ElementType=} opt_type The sub
+ * type.
+ * @constructor
+ * @extends {i18n.input.chrome.inputview.elements.Element}
+ * @implements {i18n.input.chrome.inputview.elements.Weightable}
+ */
+i18n.input.chrome.inputview.elements.layout.VerticalLayout = function(id,
+ opt_eventTarget, opt_type) {
+ var type = opt_type || i18n.input.chrome.inputview.elements.ElementType.
+ VERTICAL_LAYOUT;
+ goog.base(this, id, type, opt_eventTarget);
+};
+goog.inherits(i18n.input.chrome.inputview.elements.layout.VerticalLayout,
+ i18n.input.chrome.inputview.elements.Element);
+var VerticalLayout = i18n.input.chrome.inputview.elements.layout.VerticalLayout;
+
+
+/**
+ * The height in weight unit.
+ *
+ * @type {number}
+ * @private
+ */
+VerticalLayout.prototype.heightInWeight_ = 0;
+
+
+/**
+ * The width in weight unit.
+ *
+ * @type {number}
+ * @private
+ */
+VerticalLayout.prototype.widthInWeight_ = 0;
+
+
+/** @override */
+VerticalLayout.prototype.createDom = function() {
+ goog.base(this, 'createDom');
+
+ goog.dom.classlist.add(this.getElement(),
+ i18n.input.chrome.inputview.Css.VERTICAL_LAYOUT);
+};
+
+
+/** @override */
+VerticalLayout.prototype.enterDocument =
+ function() {
+ goog.base(this, 'enterDocument');
+
+ this.calculate_();
+};
+
+
+/**
+ * Calculate all necessary information after enters the document.
+ *
+ * @private
+ */
+VerticalLayout.prototype.calculate_ = function() {
+ for (var i = 0; i < this.getChildCount(); i++) {
+ var child = /** @type {i18n.input.chrome.inputview.elements.Weightable} */ (
+ this.getChildAt(i));
+ if (this.widthInWeight_ < child.getWidthInWeight()) {
+ this.widthInWeight_ = child.getWidthInWeight();
+ }
+ this.heightInWeight_ += child.getHeightInWeight();
+ }
+};
+
+
+/** @override */
+VerticalLayout.prototype.getHeightInWeight = function() {
+ return this.heightInWeight_;
+};
+
+
+/** @override */
+VerticalLayout.prototype.getWidthInWeight = function() {
+ return this.widthInWeight_;
+};
+
+
+/** @override */
+VerticalLayout.prototype.resize = function(
+ width, height) {
+ goog.base(this, 'resize', width, height);
+
+ this.getElement().style.width = width + 'px';
+ var weightArray = [];
+ for (var i = 0; i < this.getChildCount(); i++) {
+ var child = /** @type {i18n.input.chrome.inputview.elements.Weightable} */ (
+ this.getChildAt(i));
+ weightArray.push(child.getHeightInWeight());
+ }
+ var splitedHeight = i18n.input.chrome.inputview.util.splitValue(weightArray,
+ height);
+ for (var i = 0; i < this.getChildCount(); i++) {
+ var child = /** @type {i18n.input.chrome.inputview.elements.Element} */ (
+ this.getChildAt(i));
+ child.resize(width, splitedHeight[i]);
+ }
+};
+
+}); // goog.scope
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/weightable.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/weightable.js
new file mode 100644
index 0000000..7555c75
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/weightable.js
@@ -0,0 +1,44 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.elements.Weightable');
+
+
+
+/**
+ * Interface defines the weight operation to an element which is computed
+ * with weight.
+ *
+ * @interface
+ */
+i18n.input.chrome.inputview.elements.Weightable = function() {};
+
+
+/**
+ * Gets the width of the element in weight.
+ *
+ * @return {number} The width.
+ */
+i18n.input.chrome.inputview.elements.Weightable.prototype.getWidthInWeight =
+ goog.abstractMethod;
+
+
+/**
+ * Gets the height of the element in weight.
+ *
+ * @return {number} The height.
+ */
+i18n.input.chrome.inputview.elements.Weightable.prototype.getHeightInWeight =
+ goog.abstractMethod;
+
+
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/emojitype.js b/third_party/google_input_tools/src/chrome/os/inputview/emojitype.js
new file mode 100644
index 0000000..aa329a5
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/emojitype.js
@@ -0,0 +1,33 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.EmojiType');
+
+
+/**
+ * The emoji category type.
+ *
+ * @enum {number}
+ */
+i18n.input.chrome.inputview.EmojiType = {
+ RECENT: 0,
+ FAVORITS: 1,
+ FACES: 2,
+ EMOTICON: 3,
+ SYMBOL: 4,
+ NATURE: 5,
+ PLACES: 6,
+ OBJECTS: 7
+};
+
+
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/events.js b/third_party/google_input_tools/src/chrome/os/inputview/events.js
new file mode 100644
index 0000000..558b60d
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/events.js
@@ -0,0 +1,253 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.events.ConfigLoadedEvent');
+goog.provide('i18n.input.chrome.inputview.events.ContextUpdateEvent');
+goog.provide('i18n.input.chrome.inputview.events.DragEvent');
+goog.provide('i18n.input.chrome.inputview.events.EventType');
+goog.provide('i18n.input.chrome.inputview.events.LayoutLoadedEvent');
+goog.provide('i18n.input.chrome.inputview.events.PointerEvent');
+goog.provide('i18n.input.chrome.inputview.events.SurroundingTextChangedEvent');
+goog.provide('i18n.input.chrome.inputview.events.SwipeEvent');
+
+goog.require('goog.events');
+goog.require('goog.events.Event');
+
+
+goog.scope(function() {
+var events = i18n.input.chrome.inputview.events;
+
+
+/**
+ * Event types in input view keyboard.
+ *
+ * @enum {string}
+ */
+events.EventType = {
+ CLICK: goog.events.getUniqueId('c'),
+ CONFIG_LOADED: goog.events.getUniqueId('cl'),
+ DOUBLE_CLICK: goog.events.getUniqueId('dc'),
+ DOUBLE_CLICK_END: goog.events.getUniqueId('dce'),
+ DRAG: goog.events.getUniqueId('dg'),
+ LAYOUT_LOADED: goog.events.getUniqueId('ll'),
+ LONG_PRESS: goog.events.getUniqueId('lp'),
+ LONG_PRESS_END: goog.events.getUniqueId('lpe'),
+ POINTER_DOWN: goog.events.getUniqueId('pd'),
+ POINTER_UP: goog.events.getUniqueId('pu'),
+ POINTER_OVER: goog.events.getUniqueId('po'),
+ POINTER_OUT: goog.events.getUniqueId('po'),
+ SETTINGS_READY: goog.events.getUniqueId('sr'),
+ SURROUNDING_TEXT_CHANGED: goog.events.getUniqueId('stc'),
+ SWIPE: goog.events.getUniqueId('s'),
+ CONTEXT_UPDATE: goog.events.getUniqueId('cu'),
+ CONTEXT_FOCUS: goog.events.getUniqueId('cf'),
+ CONTEXT_BLUR: goog.events.getUniqueId('cb'),
+ VISIBILITY_CHANGE: goog.events.getUniqueId('vc'),
+ MODEL_UPDATE: goog.events.getUniqueId('mu')
+};
+
+
+
+/**
+ * The event when the data is loaded complete.
+ *
+ * @param {!Object} data The layout data.
+ * @constructor
+ * @extends {goog.events.Event}
+ */
+events.LayoutLoadedEvent = function(data) {
+ goog.base(this, events.EventType.LAYOUT_LOADED);
+
+ /**
+ * The layout data.
+ *
+ * @type {!Object}
+ */
+ this.data = data;
+};
+goog.inherits(events.LayoutLoadedEvent, goog.events.Event);
+
+
+
+/**
+ * The event when the configuration is loaded complete.
+ *
+ * @param {!Object} data The configuration data.
+ * @constructor
+ * @extends {goog.events.Event}
+ */
+events.ConfigLoadedEvent = function(data) {
+ goog.base(this, events.EventType.CONFIG_LOADED);
+
+ /**
+ * The configuration data.
+ *
+ * @type {!Object}
+ */
+ this.data = data;
+};
+goog.inherits(events.ConfigLoadedEvent, goog.events.Event);
+
+
+
+/**
+ * The pointer event.
+ *
+ * @param {i18n.input.chrome.inputview.elements.Element} view .
+ * @param {events.EventType} type .
+ * @param {Node} target The event target.
+ * @param {number} x .
+ * @param {number} y .
+ * @param {number=} opt_timestamp The timestamp of a pointer event.
+ * @constructor
+ * @extends {goog.events.Event}
+ */
+events.PointerEvent = function(view, type, target, x, y, opt_timestamp) {
+ goog.base(this, type, target);
+
+ /**
+ * The view.
+ *
+ * @type {i18n.input.chrome.inputview.elements.Element}
+ */
+ this.view = view;
+
+ /**
+ * The x-coordinate.
+ *
+ * @type {number}
+ */
+ this.x = x;
+
+ /**
+ * The y-coordinate.
+ *
+ * @type {number}
+ */
+ this.y = y;
+
+ /**
+ * The timestamp.
+ *
+ * @type {number}
+ */
+ this.timestamp = opt_timestamp || 0;
+};
+goog.inherits(events.PointerEvent, goog.events.Event);
+
+
+
+/**
+ * The swipe event.
+ *
+ * @param {i18n.input.chrome.inputview.elements.Element} view .
+ * @param {number} direction See SwipeDirection in pointer handler.
+ * @param {Node} target The event target.
+ * @param {number} x .
+ * @param {number} y .
+ * @constructor
+ * @extends {events.PointerEvent}
+ */
+events.SwipeEvent = function(view, direction, target, x, y) {
+ goog.base(this, view, events.EventType.SWIPE,
+ target, x, y);
+
+ /**
+ * The direction.
+ *
+ * @type {number}
+ */
+ this.direction = direction;
+};
+goog.inherits(events.SwipeEvent, events.PointerEvent);
+
+
+
+/**
+ * The drag event.
+ *
+ * @param {i18n.input.chrome.inputview.elements.Element} view .
+ * @param {number} direction See SwipeDirection in pointer handler.
+ * @param {Node} target The event target.
+ * @param {number} x .
+ * @param {number} y .
+ * @param {number} deltaX The drag distance of x-coordinate.
+ * @param {number} deltaY The drag distance of y-coordinate.
+ * @constructor
+ * @extends {events.PointerEvent}
+ */
+events.DragEvent = function(view, direction, target, x, y, deltaX, deltaY) {
+ goog.base(this, view, events.EventType.DRAG,
+ target, x, y);
+ /**
+ * The direction
+ *
+ * @type {number}
+ */
+ this.direction = direction;
+
+ /**
+ * The value of deltaX
+ *
+ * @type {number}
+ */
+ this.deltaX = deltaX;
+
+ /**
+ * The value of deltaY
+ *
+ * @type {number}
+ */
+ this.deltaY = deltaY;
+};
+goog.inherits(events.DragEvent, events.PointerEvent);
+
+
+
+/**
+ * The event when the surrounding text is changed.
+ *
+ * @param {string} text The surrounding text.
+ * @constructor
+ * @extends {goog.events.Event}
+ */
+events.SurroundingTextChangedEvent = function(text) {
+ goog.base(this, events.EventType.SURROUNDING_TEXT_CHANGED);
+
+ /** @type {string} */
+ this.text = text;
+};
+goog.inherits(events.SurroundingTextChangedEvent, goog.events.Event);
+
+
+
+/**
+ * The event when context is updated.
+ *
+ * @param {string} compositionText .
+ * @param {string} committedText .
+ * @constructor
+ * @extends {goog.events.Event}
+ */
+events.ContextUpdateEvent = function(compositionText, committedText) {
+ goog.base(this, events.EventType.CONTEXT_UPDATE);
+
+ /** @type {string} */
+ this.compositionText = compositionText;
+
+ /** @type {string} */
+ this.committedText = committedText;
+};
+goog.inherits(events.ContextUpdateEvent, goog.events.Event);
+
+}); // goog.scope
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/events/keycodes.js b/third_party/google_input_tools/src/chrome/os/inputview/events/keycodes.js
new file mode 100644
index 0000000..fc05ac6
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/events/keycodes.js
@@ -0,0 +1,313 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.events.KeyCodes');
+
+
+/**
+ * W3C Document Object Model (DOM) Level 3 Keyboard Events key codes
+ * Specification.
+ * @see http://www.w3.org/TR/DOM-Level-3-Events/#key-value-tables
+ *
+ * @enum {string}
+ */
+i18n.input.chrome.inputview.events.KeyCodes = {
+ // Special Key Values
+ UNIDENTIFIED: 'Unidentified',
+
+ // Letter Key
+ BACK_QUOTE: 'Backquote',
+ KEY_A: 'KeyA',
+ KEY_B: 'KeyB',
+ KEY_C: 'KeyC',
+ KEY_D: 'KeyD',
+ KEY_E: 'KeyE',
+ KEY_F: 'KeyF',
+ KEY_G: 'KeyG',
+ KEY_H: 'KeyH',
+ KEY_I: 'KeyI',
+ KEY_J: 'KeyJ',
+ KEY_K: 'KeyK',
+ KEY_L: 'KeyL',
+ KEY_M: 'KeyM',
+ KEY_N: 'KeyN',
+ KEY_O: 'KeyO',
+ KEY_P: 'KeyP',
+ KEY_Q: 'KeyQ',
+ KEY_R: 'KeyR',
+ KEY_S: 'KeyS',
+ KEY_T: 'KeyT',
+ KEY_U: 'KeyU',
+ KEY_V: 'KeyV',
+ KEY_W: 'KeyW',
+ KEY_X: 'KeyX',
+ KEY_Y: 'KeyY',
+ KEY_Z: 'KeyZ',
+
+ // Number keys
+ DIGIT_0: 'Digit0',
+ DIGIT_1: 'Digit1',
+ DIGIT_2: 'Digit2',
+ DIGIT_3: 'Digit3',
+ DIGIT_4: 'Digit4',
+ DIGIT_5: 'Digit5',
+ DIGIT_6: 'Digit6',
+ DIGIT_7: 'Digit7',
+ DIGIT_8: 'Digit8',
+ DIGIT_9: 'Digit9',
+
+ // Modifier keys
+ ALT: 'Alt',
+ ALT_GRAPH: 'AltGraph',
+ ALT_LEFT: 'AltLeft',
+ ALT_RIGHT: 'AltRight',
+ CAPS_LOCK: 'CapsLock',
+ CONTROL: 'Control',
+ FN: 'Fn',
+ FN_LOCK: 'FnLock',
+ HYPER: 'Hyper',
+ META: 'Meta',
+ NUM_LOCK: 'NumLock',
+ O_S: 'OS',
+ SHIFT: 'Shift',
+ SUPER: 'Super',
+ SYMBOL: 'Symbol',
+ SYMBOL_LOCK: 'SymbolLock',
+
+ // Whitespace keys
+ ENTER: 'Enter',
+ SEPARATOR: 'Separator',
+ TAB: 'Tab',
+ SPACE: 'Space',
+
+ // Navigation keys
+ ARROW_DOWN: 'ArrowDown',
+ ARROW_LEFT: 'ArrowLeft',
+ ARROW_RIGHT: 'ArrowRight',
+ ARROW_UP: 'ArrowUp',
+ END: 'End',
+ HOME: 'Home',
+ PAGE_DOWN: 'PageDown',
+ PAGE_UP: 'PageUp',
+
+ // Editing keys
+ BACKSPACE: 'Backspace',
+ CLEAR: 'Clear',
+ COPY: 'Copy',
+ CR_SEL: 'CrSel',
+ CUT: 'Cut',
+ DELETE: 'Delete',
+ ERASE_EOF: 'EraseEof',
+ EX_SEL: 'ExSel',
+ INSERT: 'Insert',
+ PASTE: 'Paste',
+ REDO: 'Redo',
+ UNDO: 'Undo',
+
+ // UI keys
+ ACCEPT: 'Accept',
+ AGAIN: 'Again',
+ ATTN: 'Attn',
+ CANCEL: 'Cancel',
+ CONTEXT_MENU: 'ContextMenu',
+ ESCAPE: 'Escape',
+ EXECUTE: 'Execute',
+ FIND: 'Find',
+ HELP: 'Help',
+ PAUSE: 'Pause',
+ PLAY: 'Play',
+ PROPS: 'Props',
+ SCROLL_LOCK: 'ScrollLock',
+ ZOOM_IN: 'ZoomIn',
+ ZOOM_OUT: 'ZoomOut',
+
+ // Device keys
+ BRIGHTNESS_DOWN: 'BrightnessDown',
+ BRIGHTNESS_UP: 'BrightnessUp',
+ CAMERA: 'Camera',
+ EJECT: 'Eject',
+ LOG_OFF: 'LogOff',
+ POWER: 'Power',
+ POWER_OFF: 'PowerOff',
+ PRINT_SCREEN: 'PrintScreen',
+ HIBERNATE: 'Hibernate',
+ STANDBY: 'Standby',
+ WAKE_UP: 'WakeUp',
+
+ // IME and composition keys
+ ALL_CANDIDATES: 'AllCandidates',
+ ALPHANUMERIC: 'Alphanumeric',
+ CODE_INPUT: 'CodeInput',
+ COMPOSE: 'Compose',
+ CONVERT: 'Convert',
+ FINAL_MODE: 'FinalMode',
+ GROUP_FIRST: 'GroupFirst',
+ GROUP_LAST: 'GroupLast',
+ GROUP_NEXT: 'GroupNext',
+ GROUP_PREVIOUS: 'GroupPrevious',
+ MODE_CHANGE: 'ModeChange',
+ NEXT_CANDIDATE: 'NextCandidate',
+ NON_CONVERT: 'NonConvert',
+ PREVIOUS_CANDIDATE: 'PreviousCandidate',
+ PROCESS: 'Process',
+ SINGLE_CANDIDATE: 'SingleCandidate',
+
+ // Keys specific to korean keyboards
+ ROMAN_CHARACTERS: 'RomanCharacters',
+ HANGUL_MODE: 'HangulMode',
+ HANJA_MODE: 'HanjaMode',
+ JUNJA_MODE: 'JunjaMode',
+
+ // Keys specific to japanese keyboards
+ ZENKAKU: 'Zenkaku',
+ HANKAKU: 'Hankaku',
+ ZENKAKU_HANKAKU: 'ZenkakuHankaku',
+ KANA_MODE: 'KanaMode',
+ KANJI_MODE: 'KanjiMode',
+ HIRAGANA: 'Hiragana',
+ KATAKANA: 'Katakana',
+ HIRAGANA_KATAKANA: 'HiraganaKatakana',
+ EISU: 'Eisu',
+
+ // General-purpose function keys
+ F1: 'F1',
+ F2: 'F2',
+ F3: 'F3',
+ F4: 'F4',
+ F5: 'F5',
+ F6: 'F6',
+ F7: 'F7',
+ F8: 'F8',
+ F9: 'F9',
+ F10: 'F10',
+ F11: 'F11',
+ F12: 'F12',
+ SOFT1: 'Soft1',
+ SOFT2: 'Soft2',
+ SOFT3: 'Soft3',
+ SOFT4: 'Soft4',
+
+ // Mediamedia keys
+ CLOSE: 'Close',
+ MAIL_FORWARD: 'MailForward',
+ MAIL_REPLY: 'MailReply',
+ MAIL_SEND: 'MailSend',
+ MEDIA_PLAY_PAUSE: 'MediaPlayPause',
+ MEDIA_SELECT: 'MediaSelect',
+ MEDIA_STOP: 'MediaStop',
+ MEDIA_TRACK_NEXT: 'MediaTrackNext',
+ MEDIA_TRACK_PREVIOUS: 'MediaTrackPrevious',
+ NEW: 'New',
+ OPEN: 'Open',
+ PRINT: 'Print',
+ SAVE: 'Save',
+ SPELL_CHECK: 'SpellCheck',
+ VOLUME_DOWN: 'VolumeDown',
+ VOLUME_UP: 'VolumeUp',
+ VOLUME_MUTE: 'VolumeMute',
+
+ // Application keys
+ LAUNCH_CALCULATOR: 'LaunchCalculator',
+ LAUNCH_CALENDAR: 'LaunchCalendar',
+ LAUNCH_MAIL: 'LaunchMail',
+ LAUNCH_MEDIA_PLAYER: 'LaunchMediaPlayer',
+ LAUNCH_MUSIC_PLAYER: 'LaunchMusicPlayer',
+ LAUNCH_MY_COMPUTER: 'LaunchMyComputer',
+ LAUNCH_SCREEN_SAVER: 'LaunchScreenSaver',
+ LAUNCH_SPREADSHEET: 'LaunchSpreadsheet',
+ LAUNCH_WEB_BROWSER: 'LaunchWebBrowser',
+ LAUNCH_WEB_CAM: 'LaunchWebCam',
+ LAUNCH_WORD_PROCESSOR: 'LaunchWordProcessor',
+
+ // Browser keys
+ BROWSER_BACK: 'BrowserBack',
+ BROWSER_FAVORITES: 'BrowserFavorites',
+ BROWSER_FORWARD: 'BrowserForward',
+ BROWSER_HOME: 'BrowserHome',
+ BROWSER_REFRESH: 'BrowserRefresh',
+ BROWSER_SEARCH: 'BrowserSearch',
+ BROWSER_STOP: 'BrowserStop',
+
+ // Media controller keys
+ AUDIO_BALANCE_LEFT: 'AudioBalanceLeft',
+ AUDIO_BALANCE_RIGHT: 'AudioBalanceRight',
+ AUDIO_BASS_BOOST_DOWN: 'AudioBassBoostDown',
+ AUDIO_BASS_BOOST_UP: 'AudioBassBoostUp',
+ AUDIO_FADER_FRONT: 'AudioFaderFront',
+ AUDIO_FADER_REAR: 'AudioFaderRear',
+ AUDIO_SURROUND_MODE_NEXT: 'AudioSurroundModeNext',
+ CHANNEL_DOWN: 'ChannelDown',
+ CHANNEL_UP: 'ChannelUp',
+ COLORF0_RED: 'ColorF0Red',
+ COLORF1_GREEN: 'ColorF1Green',
+ COLORF2_YELLOW: 'ColorF2Yellow',
+ COLORF3_BLUE: 'ColorF3Blue',
+ COLORF4_GREY: 'ColorF4Grey',
+ COLORF5_BROWN: 'ColorF5Brown',
+ CLOSED_CAPTION_TOGGLE: 'ClosedCaptionToggle',
+ DIMMER: 'Dimmer',
+ DISPLAY_SWAP: 'DisplaySwap',
+ EXIT: 'Exit',
+ FAVORITE_CLEAR0: 'FavoriteClear0',
+ FAVORITE_CLEAR1: 'FavoriteClear1',
+ FAVORITE_CLEAR2: 'FavoriteClear2',
+ FAVORITE_CLEAR3: 'FavoriteClear3',
+ FAVORITE_RECALL0: 'FavoriteRecall0',
+ FAVORITE_RECALL1: 'FavoriteRecall1',
+ FAVORITE_RECALL2: 'FavoriteRecall2',
+ FAVORITE_RECALL3: 'FavoriteRecall3',
+ FAVORITE_STORE0: 'FavoriteStore0',
+ FAVORITE_STORE1: 'FavoriteStore1',
+ FAVORITE_STORE2: 'FavoriteStore2',
+ FAVORITE_STORE3: 'FavoriteStore3',
+ GUIDE: 'Guide',
+ GUIDE_NEXT_DAY: 'GuideNextDay',
+ GUIDE_PREVIOUS_DAY: 'GuidePreviousDay',
+ INFO: 'Info',
+ INSTANT_REPLAY: 'InstantReplay',
+ LINK: 'Link',
+ LIST_PROGRAM: 'ListProgram',
+ LIVE_CONTENT: 'LiveContent',
+ LOCK: 'Lock',
+ MEDIA_APPS: 'MediaApps',
+ MEDIA_FAST_FORWARD: 'MediaFastForward',
+ MEDIA_LAST: 'MediaLast',
+ MEDIA_PAUSE: 'MediaPause',
+ MEDIA_PLAY: 'MediaPlay',
+ MEDIA_RECORD: 'MediaRecord',
+ MEDIA_REWIND: 'MediaRewind',
+ MEDIA_SKIP: 'MediaSkip',
+ NEXT_FAVORITE_CHANNEL: 'NextFavoriteChannel',
+ NEXT_USER_PROFILE: 'NextUserProfile',
+ ON_DEMAND: 'OnDemand',
+ PIN_P_DOWN: 'PinPDown',
+ PIN_P_MOVE: 'PinPMove',
+ PIN_P_TOGGLE: 'PinPToggle',
+ PIN_P_UP: 'PinPUp',
+ PLAY_SPEED_DOWN: 'PlaySpeedDown',
+ PLAY_SPEED_RESET: 'PlaySpeedReset',
+ PLAY_SPEED_UP: 'PlaySpeedUp',
+ RANDOM_TOGGLE: 'RandomToggle',
+ RC_LOW_BATTERY: 'RcLowBattery',
+ RECORD_SPEED_NEXT: 'RecordSpeedNext',
+ RF_BYPASS: 'RfBypass',
+ SCAN_CHANNELS_TOGGLE: 'ScanChannelsToggle ',
+ SCREEN_MODE_NEXT: 'ScreenModeNext',
+ SETTINGS: 'Settings',
+ SPLIT_SCREEN_TOGGLE: 'SplitScreenToggle',
+ SUBTITLE: 'Subtitle',
+ TELETEXT: 'Teletext',
+ VIDEO_MODE_NEXT: 'VideoModeNext',
+ WINK: 'Wink',
+ ZOOM_TOGGLE: 'ZoomToggle'
+};
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/globalsettings.js b/third_party/google_input_tools/src/chrome/os/inputview/globalsettings.js
new file mode 100644
index 0000000..43ecf1e
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/globalsettings.js
@@ -0,0 +1,235 @@
+// Copyright 2014 The Cloud Input Tools Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Defines the global settings variables for ITA.
+ */
+
+goog.provide('i18n.input.common.GlobalSettings');
+
+goog.require('goog.positioning.Corner');
+goog.require('goog.userAgent');
+
+
+/**
+ * The application name.
+ *
+ * @type {string}
+ */
+i18n.input.common.GlobalSettings.ApplicationName = 'jsapi';
+
+
+/**
+ * The help url for the help button on keyboard. If empty string, the help
+ * button will not be showed.
+ *
+ * @type {string}
+ */
+i18n.input.common.GlobalSettings.KeyboardHelpUrl = '';
+
+
+/**
+ * Whether show min/max button on keyboard.
+ *
+ * @type {boolean}
+ */
+i18n.input.common.GlobalSettings.KeyboardShowMinMax = false;
+
+
+/**
+ * Whether to show status bar.
+ *
+ * @type {boolean}
+ */
+i18n.input.common.GlobalSettings.ShowStatusBar = true;
+
+
+/**
+ * Whether to show google logo.
+ *
+ * @type {boolean}
+ */
+i18n.input.common.GlobalSettings.showGoogleLogo = false;
+
+
+/**
+ * The shortcut key definition for statusbar toggle language command.
+ *
+ * @type {string}
+ */
+i18n.input.common.GlobalSettings.StatusBarToggleLanguageShortcut = 'shift';
+
+
+/**
+ * The shortcut key definition for statusbar toggle sbc/dbc command.
+ *
+ * @type {string}
+ */
+i18n.input.common.GlobalSettings.StatusBarToggleSbcShortcut = 'shift+space';
+
+
+/**
+ * The shortcut key definition for statusbar punctuation command.
+ *
+ * @type {string}
+ */
+i18n.input.common.GlobalSettings.StatusBarPunctuationShortcut = 'ctrl+.';
+
+
+/**
+ * Keyboard default location.
+ *
+ * @type {!goog.positioning.Corner}
+ */
+i18n.input.common.GlobalSettings.KeyboardDefaultLocation =
+ goog.positioning.Corner.BOTTOM_END;
+
+
+/**
+ * Handwriting panel default location.
+ *
+ * @type {!goog.positioning.Corner}
+ */
+i18n.input.common.GlobalSettings.HandwritingDefaultLocation =
+ goog.positioning.Corner.BOTTOM_END;
+
+
+/**
+ * Whether is offline mode. If true, IME will be switched to offline and all
+ * tracking (server ping, ga, csi, etc.) are disabled.
+ * TODO(shuchen): Later we will use this flag to switch to ITA offline decoder.
+ *
+ * @type {boolean}
+ */
+i18n.input.common.GlobalSettings.isOfflineMode = false;
+
+
+/**
+ * Whether to sends the fake events when input box value is changed.
+ *
+ * @type {boolean}
+ */
+i18n.input.common.GlobalSettings.canSendFakeEvents = true;
+
+
+/**
+ * No need to register handler in capture phase for IE8.
+ *
+ * @type {boolean}
+ */
+i18n.input.common.GlobalSettings.canListenInCaptureForIE8 =
+ !goog.userAgent.IE || goog.userAgent.isVersionOrHigher(9);
+
+
+/**
+ * The chrome extension settings.
+ *
+ * @enum {string}
+ */
+i18n.input.common.GlobalSettings.chromeExtension = {
+ ACT_FLAG: 'IS_INPUT_ACTIVE',
+ ACTIVE_UI_IFRAME_ID: 'GOOGLE_INPUT_ACTIVE_UI',
+ APP_FLAG: 'GOOGLE_INPUT_NON_CHEXT_FLAG',
+ CHEXT_FLAG: 'GOOGLE_INPUT_CHEXT_FLAG',
+ INPUTTOOL: 'input',
+ INPUTTOOL_STAT: 'input_stat',
+ STATUS_BAR_IFRAME_ID: 'GOOGLE_INPUT_STATUS_BAR'
+};
+
+
+/**
+ * @define {string} The name of the product which uses Google Input Tools API.
+ */
+i18n.input.common.GlobalSettings.BUILD_SOURCE = 'jsapi';
+
+
+/**
+ * @define {boolean} Whether uses XMLHttpRequest or not.
+ */
+i18n.input.common.GlobalSettings.ENABLE_XHR = false;
+
+
+/**
+ * Whether enables the statistics for IME's status bar.
+ * TODO(shuchen): Investigates how to make sure status bar won't send
+ * duplicated metrics requests. And then we can remove this flag.
+ *
+ * @type {boolean}
+ */
+i18n.input.common.GlobalSettings.enableStatusBarMetrics = false;
+
+
+/**
+ * Whether to show on-screen keyboard.
+ * false: Hides on-screen keyboard.
+ * true: Shows on-screen keyboard.
+ *
+ * @type {boolean}
+ */
+i18n.input.common.GlobalSettings.onScreenKeyboard = true;
+
+
+/**
+ * Whether to enable personal dictionary or not.
+ *
+ * @type {boolean}
+ */
+i18n.input.common.GlobalSettings.enableUserDict = false;
+
+
+/**
+ * Defines the max int value.
+ *
+ * @type {number}
+ */
+i18n.input.common.GlobalSettings.MAX_INT = 2147483647;
+
+
+/**
+ * Whether enables the user prefs.
+ *
+ * @type {boolean}
+ */
+i18n.input.common.GlobalSettings.enableUserPrefs = true;
+
+
+/**
+ * @define {boolean} UI wrapper by iframe.
+ */
+i18n.input.common.GlobalSettings.IFRAME_WRAPPER = false;
+
+
+/**
+ * The CSS string. When create a new iframe wrapper, need to install css style
+ * in the iframe document.
+ *
+ * @type {string}
+ *
+ */
+i18n.input.common.GlobalSettings.css = '';
+
+
+/**
+ * Alternative image URL in CSS (e.g. Chrome extension needs a local path).
+ *
+ * @type {string}
+ */
+i18n.input.common.GlobalSettings.alternativeImageUrl = '';
+
+
+/**
+ * @define {boolean} Whether to enable Voice input.
+ */
+i18n.input.common.GlobalSettings.enableVoice = false;
+
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/handler/pointeractionbundle.js b/third_party/google_input_tools/src/chrome/os/inputview/handler/pointeractionbundle.js
new file mode 100644
index 0000000..2d83e9f
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/handler/pointeractionbundle.js
@@ -0,0 +1,447 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.handler.PointerActionBundle');
+
+goog.require('goog.Timer');
+goog.require('goog.dom');
+goog.require('goog.events.EventTarget');
+goog.require('goog.events.EventType');
+goog.require('goog.math.Coordinate');
+goog.require('i18n.input.chrome.inputview.SwipeDirection');
+goog.require('i18n.input.chrome.inputview.events.DragEvent');
+goog.require('i18n.input.chrome.inputview.events.EventType');
+goog.require('i18n.input.chrome.inputview.events.PointerEvent');
+goog.require('i18n.input.chrome.inputview.events.SwipeEvent');
+goog.require('i18n.input.chrome.inputview.handler.SwipeState');
+
+
+
+goog.scope(function() {
+
+
+
+/**
+ * The handler for long press.
+ *
+ * @param {!Node} target The target for this pointer event.
+ * @param {goog.events.EventTarget=} opt_parentEventTarget The parent event
+ * target.
+ * @constructor
+ * @extends {goog.events.EventTarget}
+ */
+i18n.input.chrome.inputview.handler.PointerActionBundle = function(target,
+ opt_parentEventTarget) {
+ goog.base(this);
+ this.setParentEventTarget(opt_parentEventTarget || null);
+
+ /**
+ * The target.
+ *
+ * @type {i18n.input.chrome.inputview.elements.Element}
+ * @private
+ */
+ this.view_ = this.getView_(target);
+
+ /**
+ * The swipe offset.
+ *
+ * @type {!i18n.input.chrome.inputview.handler.SwipeState}
+ * @private
+ */
+ this.swipeState_ = new i18n.input.chrome.inputview.handler.SwipeState();
+};
+goog.inherits(i18n.input.chrome.inputview.handler.PointerActionBundle,
+ goog.events.EventTarget);
+var PointerActionBundle = i18n.input.chrome.inputview.handler.
+ PointerActionBundle;
+
+
+/**
+ * The current target after the touch point is moved.
+ *
+ * @type {!Node | Element}
+ * @private
+ */
+PointerActionBundle.prototype.currentTarget_;
+
+
+/**
+ * How many milli-seconds to evaluate a double click event.
+ *
+ * @type {number}
+ * @private
+ */
+PointerActionBundle.DOUBLE_CLICK_INTERVAL_ = 500;
+
+
+/**
+ * The timer ID.
+ *
+ * @type {number}
+ * @private
+ */
+PointerActionBundle.prototype.longPressTimer_;
+
+
+/**
+ * The minimum swipe distance.
+ *
+ * @type {number}
+ * @private
+ */
+PointerActionBundle.MINIMUM_SWIPE_DISTANCE_ = 20;
+
+
+/**
+ * The timestamp of the pointer down.
+ *
+ * @type {number}
+ * @private
+ */
+PointerActionBundle.prototype.pointerDownTimeStamp_ = 0;
+
+
+/**
+ * The timestamp of the pointer up.
+ *
+ * @type {number}
+ * @private
+ */
+PointerActionBundle.prototype.pointerUpTimeStamp_ = 0;
+
+
+/**
+ * True if it is double clicking.
+ *
+ * @type {boolean}
+ * @private
+ */
+PointerActionBundle.prototype.isDBLClicking_ = false;
+
+
+/**
+ * True if it is long pressing.
+ *
+ * @type {boolean}
+ * @private
+ */
+PointerActionBundle.prototype.isLongPressing_ = false;
+
+
+/**
+ * True if it is flickering.
+ *
+ * @type {boolean}
+ * @private
+ */
+PointerActionBundle.prototype.isFlickering_ = false;
+
+
+/**
+ * Gets the view.
+ *
+ * @param {Node} target .
+ * @return {i18n.input.chrome.inputview.elements.Element} .
+ * @private
+ */
+PointerActionBundle.prototype.getView_ = function(target) {
+ if (!target) {
+ return null;
+ }
+ var element = /** @type {!Element} */ (target);
+ var view = element['view'];
+ while (!view && element) {
+ view = element['view'];
+ element = goog.dom.getParentElement(element);
+ }
+ return view;
+};
+
+
+/**
+ * Handles touchmove event for one target.
+ *
+ * @param {!Event} touchEvent .
+ */
+PointerActionBundle.prototype.handleTouchMove = function(touchEvent) {
+ var direction = 0;
+ var deltaX = this.swipeState_.previousX == 0 ? 0 : (touchEvent.pageX -
+ this.swipeState_.previousX);
+ var deltaY = this.swipeState_.previousY == 0 ? 0 :
+ (touchEvent.pageY - this.swipeState_.previousY);
+ this.swipeState_.offsetX += deltaX;
+ this.swipeState_.offsetY += deltaY;
+ this.dispatchEvent(new i18n.input.chrome.inputview.events.DragEvent(
+ this.view_, direction, /** @type {!Node} */ (touchEvent.target),
+ touchEvent.pageX, touchEvent.pageY, deltaX, deltaY));
+
+ var minimumSwipeDist = PointerActionBundle.
+ MINIMUM_SWIPE_DISTANCE_;
+
+ if (this.swipeState_.offsetX > minimumSwipeDist) {
+ direction |= i18n.input.chrome.inputview.SwipeDirection.RIGHT;
+ this.swipeState_.offsetX = 0;
+ } else if (this.swipeState_.offsetX < -minimumSwipeDist) {
+ direction |= i18n.input.chrome.inputview.SwipeDirection.LEFT;
+ this.swipeState_.offsetX = 0;
+ }
+
+ if (Math.abs(deltaY) > Math.abs(deltaX)) {
+ if (this.swipeState_.offsetY > minimumSwipeDist) {
+ direction |= i18n.input.chrome.inputview.SwipeDirection.DOWN;
+ this.swipeState_.offsetY = 0;
+ } else if (this.swipeState_.offsetY < -minimumSwipeDist) {
+ direction |= i18n.input.chrome.inputview.SwipeDirection.UP;
+ this.swipeState_.offsetY = 0;
+ }
+ }
+
+ this.swipeState_.previousX = touchEvent.pageX;
+ this.swipeState_.previousY = touchEvent.pageY;
+
+ if (direction > 0) {
+ // If there is any movement, cancel the longpress timer.
+ goog.Timer.clear(this.longPressTimer_);
+ this.dispatchEvent(new i18n.input.chrome.inputview.events.SwipeEvent(
+ this.view_, direction, /** @type {!Node} */ (touchEvent.target),
+ touchEvent.pageX, touchEvent.pageY));
+ var currentTargetView = this.getView_(this.currentTarget_);
+ if (this.view_) {
+ this.isFlickering_ = !this.isLongPressing_ && !!(this.view_.pointerConfig.
+ flickerDirection & direction) && currentTargetView == this.view_;
+ }
+ }
+
+ this.maybeSwitchTarget_(touchEvent);
+};
+
+
+/**
+ * If the target is switched to a new one, sends out a pointer_over for the new
+ * target and sends out a pointer_out for the old target.
+ *
+ * @param {!Event | !goog.events.BrowserEvent} e .
+ * @private
+ */
+PointerActionBundle.prototype.maybeSwitchTarget_ = function(e) {
+ if (!this.isFlickering_) {
+ var pageOffset = this.getPageOffset_(e);
+ var actualTarget = document.elementFromPoint(pageOffset.x, pageOffset.y);
+ var currentTargetView = this.getView_(this.currentTarget_);
+ var actualTargetView = this.getView_(actualTarget);
+ if (currentTargetView != actualTargetView) {
+ if (currentTargetView) {
+ this.dispatchEvent(new i18n.input.chrome.inputview.events.PointerEvent(
+ currentTargetView,
+ i18n.input.chrome.inputview.events.EventType.POINTER_OUT,
+ this.currentTarget_, pageOffset.x, pageOffset.y));
+ }
+ if (actualTargetView) {
+ this.dispatchEvent(new i18n.input.chrome.inputview.events.PointerEvent(
+ actualTargetView,
+ i18n.input.chrome.inputview.events.EventType.POINTER_OVER,
+ actualTarget, pageOffset.x, pageOffset.y));
+ }
+ this.currentTarget_ = actualTarget;
+ }
+ }
+};
+
+
+/**
+ * Handles pointer up, e.g., mouseup/touchend.
+ *
+ * @param {!goog.events.BrowserEvent} e The event.
+ */
+PointerActionBundle.prototype.handlePointerUp = function(e) {
+ goog.Timer.clear(this.longPressTimer_);
+ var pageOffset = this.getPageOffset_(e);
+ this.dispatchEvent(new i18n.input.chrome.inputview.events.PointerEvent(
+ this.view_, i18n.input.chrome.inputview.events.EventType.LONG_PRESS_END,
+ e.target, pageOffset.x, pageOffset.y));
+ if (this.isDBLClicking_) {
+ this.dispatchEvent(new i18n.input.chrome.inputview.events.PointerEvent(
+ this.view_, i18n.input.chrome.inputview.events.EventType.
+ DOUBLE_CLICK_END, e.target, pageOffset.x, pageOffset.y));
+ } else if (!(this.isLongPressing_ && this.view_.pointerConfig.
+ longPressWithoutPointerUp)) {
+ if (this.isLongPressing_) {
+ // If the finger didn't move but it triggers a longpress, it could cause
+ // a target switch, so checks it here.
+ this.maybeSwitchTarget_(e);
+ }
+ var view = this.getView_(this.currentTarget_);
+ var target = this.currentTarget_;
+ if (this.isFlickering_) {
+ view = this.view_;
+ target = e.target;
+ }
+ if (view) {
+ this.pointerUpTimeStamp_ = new Date().getTime();
+ this.dispatchEvent(new i18n.input.chrome.inputview.events.PointerEvent(
+ view, i18n.input.chrome.inputview.events.EventType.POINTER_UP,
+ target, pageOffset.x, pageOffset.y, this.pointerUpTimeStamp_));
+ }
+ }
+ if (this.getView_(this.currentTarget_) == this.view_) {
+ this.dispatchEvent(new i18n.input.chrome.inputview.events.PointerEvent(
+ this.view_, i18n.input.chrome.inputview.events.EventType.CLICK,
+ e.target, pageOffset.x, pageOffset.y));
+ }
+ this.isDBLClicking_ = false;
+ this.isLongPressing_ = false;
+ this.isFlickering_ = false;
+ this.swipeState_.reset();
+
+ e.preventDefault();
+ if (this.view_ && this.view_.pointerConfig.stopEventPropagation) {
+ e.stopPropagation();
+ }
+};
+
+
+/**
+ * Cancel double click recognition on this target.
+ */
+PointerActionBundle.prototype.cancelDoubleClick = function() {
+ this.pointerDownTimeStamp_ = 0;
+};
+
+
+/**
+ * Handles pointer down, e.g., mousedown/touchstart.
+ *
+ * @param {!goog.events.BrowserEvent} e The event.
+ */
+PointerActionBundle.prototype.handlePointerDown = function(e) {
+ this.currentTarget_ = e.target;
+ goog.Timer.clear(this.longPressTimer_);
+ if (e.type != goog.events.EventType.MOUSEDOWN) {
+ // Don't trigger long press for mouse event.
+ this.maybeTriggerKeyDownLongPress_(e);
+ }
+ this.maybeHandleDBLClick_(e);
+ if (!this.isDBLClicking_) {
+ var pageOffset = this.getPageOffset_(e);
+ this.dispatchEvent(new i18n.input.chrome.inputview.events.PointerEvent(
+ this.view_, i18n.input.chrome.inputview.events.EventType.POINTER_DOWN,
+ e.target, pageOffset.x, pageOffset.y, this.pointerDownTimeStamp_));
+ }
+
+ if (this.view_ && this.view_.pointerConfig.preventDefault) {
+ e.preventDefault();
+ }
+ if (this.view_ && this.view_.pointerConfig.stopEventPropagation) {
+ e.stopPropagation();
+ }
+};
+
+
+/**
+ * Gets the page offset from the event which may be mouse event or touch event.
+ *
+ * @param {!goog.events.BrowserEvent | !TouchEvent | !Event} e .
+ * @return {!goog.math.Coordinate} .
+ * @private
+ */
+PointerActionBundle.prototype.getPageOffset_ = function(e) {
+ if (e.pageX && e.pageY) {
+ return new goog.math.Coordinate(e.pageX, e.pageY);
+ }
+
+ if (!e.getBrowserEvent) {
+ return new goog.math.Coordinate(0, 0);
+ }
+
+ var nativeEvt = e.getBrowserEvent();
+ if (nativeEvt.pageX && nativeEvt.pageY) {
+ return new goog.math.Coordinate(nativeEvt.pageX, nativeEvt.pageY);
+ }
+
+
+ var touchEventList = nativeEvt['changedTouches'];
+ if (!touchEventList || touchEventList.length == 0) {
+ touchEventList = nativeEvt['touches'];
+ }
+ if (touchEventList && touchEventList.length > 0) {
+ var touchEvent = touchEventList[0];
+ return new goog.math.Coordinate(touchEvent.pageX, touchEvent.pageY);
+ }
+
+ return new goog.math.Coordinate(0, 0);
+};
+
+
+/**
+ * Maybe triggers the long press timer when pointer down.
+ *
+ * @param {!goog.events.BrowserEvent} e The event.
+ * @private
+ */
+PointerActionBundle.prototype.maybeTriggerKeyDownLongPress_ = function(e) {
+ if (this.view_ && (this.view_.pointerConfig.longPressWithPointerUp ||
+ this.view_.pointerConfig.longPressWithoutPointerUp)) {
+ this.longPressTimer_ = goog.Timer.callOnce(
+ goog.bind(this.triggerLongPress_, this, e),
+ this.view_.pointerConfig.longPressDelay, this);
+ }
+};
+
+
+/**
+ * Maybe handle the double click.
+ *
+ * @param {!goog.events.BrowserEvent} e .
+ * @private
+ */
+PointerActionBundle.prototype.maybeHandleDBLClick_ = function(e) {
+ if (this.view_ && this.view_.pointerConfig.dblClick) {
+ var timeInMs = new Date().getTime();
+ var interval = this.view_.pointerConfig.dblClickDelay ||
+ PointerActionBundle.DOUBLE_CLICK_INTERVAL_;
+ var nativeEvt = e.getBrowserEvent();
+ if ((timeInMs - this.pointerDownTimeStamp_) < interval) {
+ this.dispatchEvent(new i18n.input.chrome.inputview.events.PointerEvent(
+ this.view_, i18n.input.chrome.inputview.events.EventType.DOUBLE_CLICK,
+ e.target, nativeEvt.pageX, nativeEvt.pageY));
+ this.isDBLClicking_ = true;
+ }
+ this.pointerDownTimeStamp_ = timeInMs;
+ }
+};
+
+
+/**
+ * Triggers long press event.
+ *
+ * @param {!goog.events.BrowserEvent} e The event.
+ * @private
+ */
+PointerActionBundle.prototype.triggerLongPress_ = function(e) {
+ var nativeEvt = e.getBrowserEvent();
+ this.dispatchEvent(new i18n.input.chrome.inputview.events.PointerEvent(
+ this.view_, i18n.input.chrome.inputview.events.EventType.LONG_PRESS,
+ e.target, nativeEvt.pageX, nativeEvt.pageY));
+ this.isLongPressing_ = true;
+};
+
+
+/** @override */
+PointerActionBundle.prototype.disposeInternal = function() {
+ goog.dispose(this.longPressTimer_);
+
+ goog.base(this, 'disposeInternal');
+};
+
+}); // goog.scope
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/handler/pointerhandler.js b/third_party/google_input_tools/src/chrome/os/inputview/handler/pointerhandler.js
new file mode 100644
index 0000000..3e7be71
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/handler/pointerhandler.js
@@ -0,0 +1,184 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.handler.PointerHandler');
+
+goog.require('goog.Timer');
+goog.require('goog.events.EventHandler');
+goog.require('goog.events.EventTarget');
+goog.require('goog.events.EventType');
+goog.require('goog.math.Coordinate');
+goog.require('i18n.input.chrome.inputview.handler.PointerActionBundle');
+
+goog.scope(function() {
+
+
+
+/**
+ * The pointer controller.
+ *
+ * @param {!Element=} opt_target .
+ * @constructor
+ * @extends {goog.events.EventTarget}
+ */
+i18n.input.chrome.inputview.handler.PointerHandler = function(opt_target) {
+ goog.base(this);
+
+ /**
+ * The pointer handlers.
+ *
+ * @type {!Object.<number, !i18n.input.chrome.inputview.handler.PointerActionBundle>}
+ * @private
+ */
+ this.pointerActionBundles_ = {};
+
+ /**
+ * The event handler.
+ *
+ * @type {!goog.events.EventHandler}
+ * @private
+ */
+ this.eventHandler_ = new goog.events.EventHandler(this);
+
+ var target = opt_target || document;
+ this.eventHandler_.
+ listen(target, [goog.events.EventType.MOUSEDOWN,
+ goog.events.EventType.TOUCHSTART], this.onPointerDown_, true).
+ listen(target, [goog.events.EventType.MOUSEUP,
+ goog.events.EventType.TOUCHEND], this.onPointerUp_, true).
+ listen(target, goog.events.EventType.TOUCHMOVE, this.onTouchMove_,
+ true);
+};
+goog.inherits(i18n.input.chrome.inputview.handler.PointerHandler,
+ goog.events.EventTarget);
+var PointerHandler = i18n.input.chrome.inputview.handler.PointerHandler;
+
+
+/**
+ * The canvas class name.
+ * @const {string}
+ * @private
+ */
+PointerHandler.CANVAS_CLASS_NAME_ = 'ita-hwt-canvas';
+
+
+/**
+ * Mouse down tick, which is for delayed pointer up for tap action on touchpad.
+ *
+ * @private {Date}
+ */
+PointerHandler.prototype.mouseDownTick_ = null;
+
+
+/**
+ * Event handler for previous mousedown or touchstart target.
+ *
+ * @private {i18n.input.chrome.inputview.handler.PointerActionBundle}
+ */
+PointerHandler.prototype.previousPointerActionBundle_ = null;
+
+
+/**
+ * Creates a new pointer handler.
+ *
+ * @param {!Node} target .
+ * @return {!i18n.input.chrome.inputview.handler.PointerActionBundle} .
+ * @private
+ */
+PointerHandler.prototype.createPointerActionBundle_ = function(target) {
+ var uid = goog.getUid(target);
+ if (!this.pointerActionBundles_[uid]) {
+ this.pointerActionBundles_[uid] = new i18n.input.chrome.inputview.handler.
+ PointerActionBundle(target, this);
+ }
+ return this.pointerActionBundles_[uid];
+};
+
+
+/**
+ * Callback for mouse/touch down on the target.
+ *
+ * @param {!goog.events.BrowserEvent} e The event.
+ * @private
+ */
+PointerHandler.prototype.onPointerDown_ = function(e) {
+ var pointerActionBundle = this.createPointerActionBundle_(
+ /** @type {!Node} */ (e.target));
+ if (this.previousPointerActionBundle_ &&
+ this.previousPointerActionBundle_ != pointerActionBundle) {
+ this.previousPointerActionBundle_.cancelDoubleClick();
+ }
+ this.previousPointerActionBundle_ = pointerActionBundle;
+ pointerActionBundle.handlePointerDown(e);
+ if (e.type == goog.events.EventType.MOUSEDOWN) {
+ this.mouseDownTick_ = new Date();
+ }
+};
+
+
+/**
+ * Callback for pointer out.
+ *
+ * @param {!goog.events.BrowserEvent} e The event.
+ * @private
+ */
+PointerHandler.prototype.onPointerUp_ = function(e) {
+ if (e.type == goog.events.EventType.MOUSEUP) {
+ // If mouseup happens too fast after mousedown, it may be a tap action on
+ // touchpad, so delay the pointer up action so user can see the visual
+ // flash.
+ if (this.mouseDownTick_ && new Date() - this.mouseDownTick_ < 10) {
+ goog.Timer.callOnce(this.onPointerUp_.bind(this, e), 50);
+ return;
+ }
+ }
+ var uid = goog.getUid(e.target);
+ var pointerActionBundle = this.pointerActionBundles_[uid];
+ if (pointerActionBundle) {
+ pointerActionBundle.handlePointerUp(e);
+ }
+};
+
+
+/**
+ * Callback for touchmove.
+ *
+ * @param {!goog.events.BrowserEvent} e The event.
+ * @private
+ */
+PointerHandler.prototype.onTouchMove_ = function(e) {
+ var touches = e.getBrowserEvent()['touches'];
+ if (!touches || touches.length == 0) {
+ return;
+ }
+ for (var i = 0; i < touches.length; i++) {
+ var uid = goog.getUid(touches[i].target);
+ var pointerActionBundle = this.pointerActionBundles_[uid];
+ if (pointerActionBundle) {
+ pointerActionBundle.handleTouchMove(touches[i]);
+ }
+ }
+};
+
+
+/** @override */
+PointerHandler.prototype.disposeInternal = function() {
+ for (var bundle in this.pointerActionBundles_) {
+ goog.dispose(bundle);
+ }
+ goog.dispose(this.eventHandler_);
+
+ goog.base(this, 'disposeInternal');
+};
+
+}); // goog.scope
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/handler/swipestate.js b/third_party/google_input_tools/src/chrome/os/inputview/handler/swipestate.js
new file mode 100644
index 0000000..4c2ee1e
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/handler/swipestate.js
@@ -0,0 +1,61 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.handler.SwipeState');
+
+
+
+/**
+ * The state for the swipe action.
+ *
+ * @constructor
+ */
+i18n.input.chrome.inputview.handler.SwipeState = function() {
+ /**
+ * The offset in x-axis.
+ *
+ * @type {number}
+ */
+ this.offsetX = 0;
+
+ /**
+ * The offset in y-axis.
+ *
+ * @type {number}
+ */
+ this.offsetY = 0;
+
+ /**
+ * The previous x coordinate.
+ *
+ * @type {number}
+ */
+ this.previousX = 0;
+
+ /**
+ * The previous y coordinate.
+ *
+ * @type {number}
+ */
+ this.previousY = 0;
+};
+
+
+/**
+ * Resets the state.
+ */
+i18n.input.chrome.inputview.handler.SwipeState.prototype.reset =
+ function() {
+ this.offsetX = this.offsetY = this.previousX = this.previousY = 0;
+};
+
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/hwt_css.js b/third_party/google_input_tools/src/chrome/os/inputview/hwt_css.js
new file mode 100644
index 0000000..9b654cc
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/hwt_css.js
@@ -0,0 +1,65 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+
+/**
+ * @fileoverview Defines the class i18n.input.hwt.Css.
+ * @author fengyuan@google.com (Feng Yuan)
+ */
+goog.provide('i18n.input.hwt.css');
+
+
+/**
+ * CSS used for handwriting input pad.
+ *
+ * @enum {string}
+ */
+i18n.input.hwt.css = {
+ RTL: goog.getCssName('ita-hwt-rtl'),
+ LTR: goog.getCssName('ita-hwt-ltr'),
+ IME: goog.getCssName('ita-hwt-ime'),
+ IME_HOVER: goog.getCssName('ita-hwt-ime-hover'),
+ IME_OPAQUE: goog.getCssName('ita-hwt-ime-opaque'),
+ IME_ST: goog.getCssName('ita-hwt-ime-st'),
+ IME_INIT_OPAQUE: goog.getCssName('ita-hwt-ime-init-opaque'),
+ CLOSE: goog.getCssName('ita-hwt-close'),
+ GRIP: goog.getCssName('ita-hwt-grip'),
+ GRIP_HOVER: goog.getCssName('ita-hwt-grip-hover'),
+ CANVAS: goog.getCssName('ita-hwt-canvas'),
+ CANDIDATES: goog.getCssName('ita-hwt-candidates'),
+ CANDIDATE: goog.getCssName('ita-hwt-candidate'),
+ CANDIDATE_HOVER: goog.getCssName('ita-hwt-candidate-hover'),
+ SELECTED: goog.getCssName('ita-hwt-selected'),
+ DISABLED: goog.getCssName('ita-hwt-disabled'),
+ BUTTONS: goog.getCssName('ita-hwt-buttons'),
+ DIVIDER: goog.getCssName('ita-hwt-divider'),
+ BUTTON: goog.getCssName('ita-hwt-button'),
+ BACKSPACE: goog.getCssName('ita-hwt-backspace'),
+ BACKSPACE_IMG: goog.getCssName('ita-hwt-backspace-img'),
+ SPACE: goog.getCssName('ita-hwt-space'),
+ ENTER: goog.getCssName('ita-hwt-enter'),
+ ENTER_IMG: goog.getCssName('ita-hwt-enter-img'),
+ ENTER_IMG_DARK: goog.getCssName('ita-hwt-enter-img-dark'),
+ ENTER_IMG_WHITE: goog.getCssName('ita-hwt-enter-img-white'),
+ LANGUAGE: goog.getCssName('ita-hwt-language'),
+ CLEAR_TIME: goog.getCssName('ita-hwt-clear-time'),
+ INSERT_TIME: goog.getCssName('ita-hwt-insert-time'),
+ REMOTE_SPRITE: goog.getCssName('ita-kd-img'),
+ MAXIMIZED: goog.getCssName('ita-hwt-ime-full'),
+ JFK_BUTTON: goog.getCssName('ita-hwt-jfk'),
+ JFK_STANDARD: goog.getCssName('ita-hwt-jfk-standard'),
+ JFK_ACTION: goog.getCssName('ita-hwt-jfk-action'),
+ JFK_HOVER: goog.getCssName('ita-hwt-jfk-hover'),
+ BUTTER_BAR: goog.getCssName('ita-hwt-butterbar'),
+ SHOWN: goog.getCssName('shown')
+};
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/hwt_eventtype.js b/third_party/google_input_tools/src/chrome/os/inputview/hwt_eventtype.js
new file mode 100644
index 0000000..85b894b
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/hwt_eventtype.js
@@ -0,0 +1,96 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+
+/**
+ * @fileoverview Handwriting event type.
+ * @author wuyingbing@google.com (Yingbing Wu)
+ */
+
+
+goog.provide('i18n.input.hwt.CandidateSelectEvent');
+goog.provide('i18n.input.hwt.CommitEvent');
+goog.provide('i18n.input.hwt.EventType');
+
+goog.require('goog.events');
+goog.require('goog.events.Event');
+
+
+/**
+ * Handwriting event type to expose to external.
+ *
+ * @enum {string}
+ */
+i18n.input.hwt.EventType = {
+ BACKSPACE: goog.events.getUniqueId('b'),
+ CANDIDATE_SELECT: goog.events.getUniqueId('cs'),
+ COMMIT: goog.events.getUniqueId('c'),
+ COMMIT_START: goog.events.getUniqueId('hcs'),
+ COMMIT_END: goog.events.getUniqueId('hce'),
+ RECOGNITION_READY: goog.events.getUniqueId('rr'),
+ ENTER: goog.events.getUniqueId('e'),
+ HANDWRITING_CLOSED: goog.events.getUniqueId('hc'),
+ MOUSEUP: goog.events.getUniqueId('m'),
+ SPACE: goog.events.getUniqueId('s')
+};
+
+
+
+/**
+ * Candidate select event.
+ *
+ * @param {string} candidate The candidate.
+ * @constructor
+ * @extends {goog.events.Event}
+ */
+i18n.input.hwt.CandidateSelectEvent = function(candidate) {
+ goog.base(this, i18n.input.hwt.EventType.CANDIDATE_SELECT);
+
+ /**
+ * The candidate.
+ *
+ * @type {string}
+ */
+ this.candidate = candidate;
+};
+goog.inherits(i18n.input.hwt.CandidateSelectEvent, goog.events.Event);
+
+
+
+/**
+ * Commit event.
+ *
+ * @param {string} text The text to commit.
+ * @param {number=} opt_back The number of characters to be deleted.
+ * @constructor
+ * @extends {goog.events.Event}
+ */
+i18n.input.hwt.CommitEvent = function(text, opt_back) {
+ goog.base(this, i18n.input.hwt.EventType.COMMIT);
+
+ /**
+ * The text.
+ *
+ * @type {string}
+ */
+ this.text = text;
+
+ /**
+ * The number of characters to be deleted.
+ *
+ * @type {number}
+ */
+ this.back = opt_back || 0;
+};
+goog.inherits(i18n.input.hwt.CommitEvent, goog.events.Event);
+
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/hwt_util.js b/third_party/google_input_tools/src/chrome/os/inputview/hwt_util.js
new file mode 100644
index 0000000..be95e26
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/hwt_util.js
@@ -0,0 +1,74 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+
+/**
+ * @fileoverview Defines the class i18n.input.hwt.util.
+ * @author fengyuan@google.com (Feng Yuan)
+ */
+
+goog.provide('i18n.input.hwt.util');
+
+
+goog.require('goog.array');
+goog.require('goog.events.EventType');
+goog.require('i18n.input.common.dom');
+
+
+/**
+ * Listens MOUSEUP on page scope.
+ *
+ * @param {goog.events.EventHandler} eventHandler The event handler.
+ * @param {Document} topDocument The top document.
+ * @param {goog.events.EventType | !Array.<goog.events.EventType>} eventType
+ * The event type to listen.
+ * @param {Function} callback The callback method.
+ */
+i18n.input.hwt.util.listenPageEvent = function(eventHandler, topDocument,
+ eventType, callback) {
+ // Ideally we'd just listen for MOUSEUP on the canvas, but that
+ // doesn't work if the mouseup event happens outside the canvas,
+ // and in particular if it happens in an iframe. So we have to
+ // listen on the document and any embedded iframes. We'll also
+ // use this handler to cancel auto-repeat when holding down
+ // the backspace button, in case the user releases the button
+ // elsewhere.
+ eventHandler.listen(topDocument, goog.events.EventType.MOUSEUP,
+ callback, true);
+ goog.array.forEach(i18n.input.common.dom.getSameDomainDocuments(topDocument),
+ function(frameDoc) {
+ try {
+ // In IE and FF3.5, some iframe is not allowed to access.
+ // It throws exception when access the iframe property.
+ eventHandler.listen(frameDoc, goog.events.EventType.MOUSEUP,
+ callback, true);
+ } catch (e) {}
+ });
+};
+
+
+/**
+ * The default candidate map of handwriting pad.
+ * Key: language code.
+ * Value: candidate list.
+ *
+ * @type {!Object.<string, !Array.<string>>}
+ */
+i18n.input.hwt.util.DEFAULT_CANDIDATE_MAP = {
+ '': [',', '.', '?', '!', ':', '\'', '"', ';', '@'],
+ 'es': [',', '.', '¿', '?', '¡', '!', ':', '\'', '"'],
+ 'ja': [',', '。', '?', '!', ':', '「', '」', ';'],
+ 'zh-Hans': [',', '。', '?', '!', ':', '“', '”', ';'],
+ 'zh-Hant': [',', '。', '?', '!', ':', '「', '」', ';']
+};
+
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/inputtoolcode.js b/third_party/google_input_tools/src/chrome/os/inputview/inputtoolcode.js
new file mode 100644
index 0000000..db3ab03
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/inputtoolcode.js
@@ -0,0 +1,369 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.lang.InputToolCode');
+
+
+/**
+ * Enumeration for input tool codes
+ *
+ * @enum {string}
+ */
+i18n.input.lang.InputToolCode = {
+ // For IME.
+ INPUTMETHOD_ARRAY92_CHINESE_TRADITIONAL: 'zh-hant-t-i0-array-1992',
+ INPUTMETHOD_CANGJIE82_CHINESE_SIMPLIFIED: 'zh-hans-t-i0-cangjie-1982',
+ INPUTMETHOD_CANGJIE82_CHINESE_TRADITIONAL: 'zh-hant-t-i0-cangjie-1982',
+ INPUTMETHOD_CANGJIE87_CHINESE_SIMPLIFIED: 'zh-hans-t-i0-cangjie-1987',
+ INPUTMETHOD_CANGJIE87_CHINESE_TRADITIONAL: 'zh-hant-t-i0-cangjie-1987',
+ INPUTMETHOD_CANGJIE87_QUICK_CHINESE_TRADITIONAL:
+ 'zh-hant-t-i0-cangjie-1987-x-m0-simplified',
+ INPUTMETHOD_CANTONESE_TRADITIONAL: 'yue-hant-t-i0-und',
+ INPUTMETHOD_DAYI88_CHINESE_TRADITIONAL: 'zh-hant-t-i0-dayi-1988',
+ INPUTMETHOD_PINYIN_CHINESE_SIMPLIFIED: 'zh-t-i0-pinyin',
+ INPUTMETHOD_PINYIN_CHINESE_TRADITIONAL: 'zh-hant-t-i0-pinyin',
+ INPUTMETHOD_HANGUL_KOREAN: 'ko-t-i0-und',
+ INPUTMETHOD_TRANSLITERATION_AMHARIC: 'am-t-i0-und',
+ INPUTMETHOD_TRANSLITERATION_ARABIC: 'ar-t-i0-und',
+ INPUTMETHOD_TRANSLITERATION_BELARUSIAN: 'be-t-i0-und',
+ INPUTMETHOD_TRANSLITERATION_BENGALI: 'bn-t-i0-und',
+ INPUTMETHOD_TRANSLITERATION_BULGARIAN: 'bg-t-i0-und',
+ INPUTMETHOD_TRANSLITERATION_DUTCH: 'nl-t-i0-und',
+ INPUTMETHOD_TRANSLITERATION_ENGLISH: 'en-t-i0-und',
+ INPUTMETHOD_TRANSLITERATION_FRENCH: 'fr-t-i0-und',
+ INPUTMETHOD_TRANSLITERATION_GERMAN: 'de-t-i0-und',
+ INPUTMETHOD_TRANSLITERATION_GREEK: 'el-t-i0-und',
+ INPUTMETHOD_TRANSLITERATION_GUJARATI: 'gu-t-i0-und',
+ INPUTMETHOD_TRANSLITERATION_HEBREW: 'he-t-i0-und',
+ INPUTMETHOD_TRANSLITERATION_HINDI: 'hi-t-i0-und',
+ INPUTMETHOD_TRANSLITERATION_HIRAGANA: 'ja-hira-t-i0-und',
+ INPUTMETHOD_TRANSLITERATION_ITALIAN: 'it-t-i0-und',
+ INPUTMETHOD_TRANSLITERATION_JAPANESE: 'ja-t-ja-hira-i0-und',
+ INPUTMETHOD_TRANSLITERATION_KANNADA: 'kn-t-i0-und',
+ INPUTMETHOD_TRANSLITERATION_MALAYALAM: 'ml-t-i0-und',
+ INPUTMETHOD_TRANSLITERATION_MARATHI: 'mr-t-i0-und',
+ INPUTMETHOD_TRANSLITERATION_NEPALI: 'ne-t-i0-und',
+ INPUTMETHOD_TRANSLITERATION_ORIYA: 'or-t-i0-und',
+ INPUTMETHOD_TRANSLITERATION_PERSIAN: 'fa-t-i0-und',
+ INPUTMETHOD_TRANSLITERATION_POLISH: 'pl-t-i0-und',
+ INPUTMETHOD_TRANSLITERATION_PORTUGUESE: 'pt-t-i0-und',
+ INPUTMETHOD_TRANSLITERATION_PORTUGUESE_BRRAZIL: 'pt-br-t-i0-und',
+ INPUTMETHOD_TRANSLITERATION_PORTUGUESE_PORTUGAL: 'pt-pt-t-i0-und',
+ INPUTMETHOD_TRANSLITERATION_PUNJABI: 'pa-t-i0-und',
+ INPUTMETHOD_TRANSLITERATION_RUSSIAN: 'ru-t-i0-und',
+ INPUTMETHOD_TRANSLITERATION_SANSKRIT: 'sa-t-i0-und',
+ INPUTMETHOD_TRANSLITERATION_SERBIAN: 'sr-t-i0-und',
+ INPUTMETHOD_TRANSLITERATION_SINHALESE: 'si-t-i0-und',
+ INPUTMETHOD_TRANSLITERATION_SPANISH: 'es-t-i0-und',
+ INPUTMETHOD_TRANSLITERATION_TAMIL: 'ta-t-i0-und',
+ INPUTMETHOD_TRANSLITERATION_TELUGU: 'te-t-i0-und',
+ INPUTMETHOD_TRANSLITERATION_TIGRINYA: 'ti-t-i0-und',
+ INPUTMETHOD_TRANSLITERATION_TURKISH: 'tr' + '-t-i0-und',
+ INPUTMETHOD_TRANSLITERATION_UKRAINE: 'uk-t-i0-und',
+ INPUTMETHOD_TRANSLITERATION_URDU: 'ur-t-i0-und',
+ INPUTMETHOD_TRANSLITERATION_VIETNAMESE: 'vi-t-i0-und',
+ INPUTMETHOD_WUBI_CHINESE_SIMPLIFIED: 'zh-t-i0-wubi-1986',
+ INPUTMETHOD_ZHUYIN_CHINESE_TRADITIONAL: 'zh-hant-t-i0-und',
+
+ // For keyboard
+ KEYBOARD_ALBANIAN: 'sq-t-k0-und',
+ KEYBOARD_ARABIC: 'ar-t-k0-und',
+ KEYBOARD_ARMENIAN_EASTERN: 'hy-hyr-t-k0-und',
+ KEYBOARD_ARMENIAN_WESTERN: 'hy-hyt-t-k0-und',
+ KEYBOARD_BASQUE: 'eu-t-k0-und',
+ KEYBOARD_BELARUSIAN: 'be-t-k0-und',
+ KEYBOARD_BENGALI_INSCRIPT: 'bn-t-k0-und',
+ KEYBOARD_BENGALI_PHONETIC: 'bn-t-und-latn-k0-und',
+ KEYBOARD_BOSNIAN: 'bs-t-k0-und',
+ KEYBOARD_BRAZILIAN_PORTUGUESE: 'pt-br-t-k0-und',
+ KEYBOARD_BULGARIAN: 'bg-t-k0-und',
+ KEYBOARD_BULGARIAN_PHONETIC: 'bg-t-k0-qwerty',
+ KEYBOARD_CATALAN: 'ca-t-k0-und',
+ KEYBOARD_CHEROKEE: 'chr-t-k0-und',
+ KEYBOARD_CHEROKEE_PHONETIC: 'chr-t-und-latn-k0-und',
+ KEYBOARD_CROATIAN: 'hr-t-k0-und',
+ KEYBOARD_CZECH: 'cs-t-k0-und',
+ KEYBOARD_CZECH_QWERTZ: 'cs-t-k0-qwertz',
+ KEYBOARD_DANISH: 'da-t-k0-und',
+ KEYBOARD_DARI: 'prs-t-k0-und',
+ KEYBOARD_DEVANAGARI_PHONETIC: 'hi-t-k0-qwerty',
+ KEYBOARD_DUTCH: 'nl-t-k0-und',
+ KEYBOARD_DUTCH_INTL: 'nl-t-k0-intl',
+ KEYBOARD_DZONGKHA: 'dz-t-k0-und',
+ KEYBOARD_ENGLISH: 'en-t-k0-und',
+ KEYBOARD_ENGLISH_DVORAK: 'en-t-k0-dvorak',
+ KEYBOARD_ESTONIAN: 'et-t-k0-und',
+ KEYBOARD_ETHIOPIC: 'und-ethi-t-k0-und',
+ KEYBOARD_TIGRINYA_ETHIOPIC: 'ti-ethi-t-k0-und',
+ KEYBOARD_FINNISH: 'fi-t-k0-und',
+ KEYBOARD_FRENCH: 'fr-t-k0-und',
+ KEYBOARD_FRENCH_INTL: 'fr-t-k0-intl',
+ KEYBOARD_GALICIAN: 'gl-t-k0-und',
+ KEYBOARD_GEORGIAN_QWERTY: 'ka-t-k0-und',
+ KEYBOARD_GEORGIAN_TYPEWRITER: 'ka-t-k0-legacy',
+ KEYBOARD_GERMAN: 'de-t-k0-und',
+ KEYBOARD_GERMAN_INTL: 'de-t-k0-intl',
+ KEYBOARD_GREEK: 'el-t-k0-und',
+ KEYBOARD_GUJARATI_INSCRIPT: 'gu-t-k0-und',
+ KEYBOARD_GUJARATI_PHONETIC: 'gu-t-und-latn-k0-qwerty',
+ KEYBOARD_GURMUKHI_INSCRIPT: 'pa-guru-t-k0-und',
+ KEYBOARD_GURMUKHI_PHONETIC: 'pa-guru-t-und-latn-k0-und',
+ KEYBOARD_HAITIAN: 'ht-t-k0-und',
+ KEYBOARD_HEBREW: 'he-t-k0-und',
+ KEYBOARD_HINDI: 'hi-t-k0-und',
+ KEYBOARD_HUNGARIAN_101: 'hu-t-k0-101key',
+ KEYBOARD_ICELANDIC: 'is-t-k0-und',
+ KEYBOARD_INDONESIAN: 'id-t-k0-und',
+ KEYBOARD_INUKTITUT_NUNAVIK: 'iu-t-k0-nunavik',
+ KEYBOARD_INUKTITUT_NUNAVUT: 'iu-t-k0-nunavut',
+ KEYBOARD_IRISH: 'ga-t-k0-und',
+ KEYBOARD_ITALIAN: 'it-t-k0-und',
+ KEYBOARD_ITALIAN_INTL: 'it-t-k0-intl',
+ KEYBOARD_JAVANESE: 'jw-t-k0-und',
+ KEYBOARD_KANNADA_INSCRIPT: 'kn-t-k0-und',
+ KEYBOARD_KANNADA_PHONETIC: 'kn-t-und-latn-k0-und',
+ KEYBOARD_KAZAKH: 'kk-t-k0-und',
+ KEYBOARD_KHMER: 'km-t-k0-und',
+ KEYBOARD_KOREAN: 'ko-t-k0-und',
+ KEYBOARD_KYRGYZ: 'ky-cyrl-t-k0-und',
+ KEYBOARD_LAO: 'lo-t-k0-und',
+ KEYBOARD_LATVIAN: 'lv-t-k0-und',
+ KEYBOARD_LITHUANIAN: 'lt-t-k0-und',
+ KEYBOARD_MACEDONIAN: 'mk-t-k0-und',
+ KEYBOARD_MALAY: 'ms-t-k0-und',
+ KEYBOARD_MALAYALAM_INSCRIPT: 'ml-t-k0-und',
+ KEYBOARD_MALAYALAM_PHONETIC: 'ml-t-und-latn-k0-und',
+ KEYBOARD_MALTESE: 'mt-t-k0-und',
+ KEYBOARD_MAORI: 'mi-t-k0-und',
+ KEYBOARD_MARATHI: 'mr-t-k0-und',
+ KEYBOARD_MONGOLIAN_CYRILLIC: 'mn-cyrl-t-k0-und',
+ KEYBOARD_MONTENEGRIN: 'srp-t-k0-und',
+ KEYBOARD_MYANMAR: 'my-t-k0-und',
+ KEYBOARD_MYANMAR_MYANSAN: 'my-t-k0-myansan',
+ KEYBOARD_NAVAJO: 'nv-t-k0-und',
+ KEYBOARD_NAVAJO_STANDARD: 'nv-t-k0-std',
+ KEYBOARD_NEPALI_INSCRIPT: 'ne-t-k0-und',
+ KEYBOARD_NEPALI_PHONETIC: 'ne-t-und-latn-k0-und',
+ KEYBOARD_NORWEGIAN: 'no-t-k0-und',
+ KEYBOARD_ORIYA_INSCRIPT: 'or-t-k0-und',
+ KEYBOARD_ORIYA_PHONETIC: 'or-t-und-latn-k0-und',
+ KEYBOARD_PAN_AFRICA_LATIN: 'latn-002-t-k0-und',
+ KEYBOARD_PASHTO: 'ps-t-k0-und',
+ KEYBOARD_PERSIAN: 'fa-t-k0-und',
+ KEYBOARD_POLISH: 'pl-t-k0-und',
+ KEYBOARD_PORTUGUESE: 'pt-pt-t-k0-und',
+ KEYBOARD_PORTUGUESE_BRAZIL_INTL: 'pt-br-t-k0-intl',
+ KEYBOARD_PORTUGUESE_PORTUGAL_INTL: 'pt-pt-t-k0-intl',
+ KEYBOARD_ROMANI: 'rom-t-k0-und',
+ KEYBOARD_ROMANIAN: 'ro-t-k0-und',
+ KEYBOARD_ROMANIAN_SR13392_PRIMARY: 'ro-t-k0-legacy',
+ KEYBOARD_ROMANIAN_SR13392_SECONDARY: 'ro-t-k0-extended',
+ KEYBOARD_RUSSIAN: 'ru-t-k0-und',
+ KEYBOARD_RUSSIAN_PHONETIC: 'ru-t-k0-qwerty',
+ KEYBOARD_SANSKRIT_PHONETIC: 'sa-t-und-latn-k0-und',
+ KEYBOARD_SERBIAN_CYRILLIC: 'sr-cyrl-t-k0-und',
+ KEYBOARD_SERBIAN_LATIN: 'sr-latn-t-k0-und',
+ KEYBOARD_SINHALA: 'si-t-k0-und',
+ KEYBOARD_SLOVAK: 'sk-t-k0-und',
+ KEYBOARD_SLOVAK_QWERTY: 'sk-t-k0-qwerty',
+ KEYBOARD_SLOVENIAN: 'sl-t-k0-und',
+ KEYBOARD_SORANI_KURDISH_AR: 'ckb-t-k0-ar', // Arabic-based
+ KEYBOARD_SORANI_KURDISH_EN: 'ckb-t-k0-en', // English-based
+ KEYBOARD_SOUTHERN_UZBEK: 'uzs-t-k0-und',
+ KEYBOARD_SPANISH: 'es-t-k0-und',
+ KEYBOARD_SPANISH_INTL: 'es-t-k0-intl',
+ KEYBOARD_SWAHILI: 'sw-t-k0-und',
+ KEYBOARD_SWEDISH: 'sv-t-k0-und',
+ KEYBOARD_SWISS_GERMAN: 'de-ch-t-k0-und',
+ KEYBOARD_TAGALOG: 'tl-t-k0-und',
+ KEYBOARD_TAMIL_99: 'ta-t-k0-ta99',
+ KEYBOARD_TAMIL_INSCRIPT: 'ta-t-k0-und',
+ KEYBOARD_TAMIL_ITRANS: 'ta-t-k0-itrans',
+ KEYBOARD_TAMIL_PHONETIC: 'ta-t-und-latn-k0-und',
+ KEYBOARD_TAMIL_TYPEWRITER: 'ta-t-k0-typewriter',
+ KEYBOARD_TATAR: 'tt-t-k0-und',
+ KEYBOARD_TELUGU_INSCRIPT: 'te-t-k0-und',
+ KEYBOARD_TELUGU_PHONETIC: 'te-t-und-latn-k0-und',
+ KEYBOARD_THAI: 'th-t-k0-und',
+ KEYBOARD_THAI_PATTAJOTI: 'th-t-k0-pattajoti',
+ KEYBOARD_THAI_TIS: 'th-t-k0-tis',
+ KEYBOARD_TIGRINYA: 'ti-t-k0-und',
+ // Gmail doesn't allow any string prefix is 'tr'.
+ // String with 'tr' prefix will obfuscated in css compiling pharse.
+ KEYBOARD_TURKISH_F: 'tr' + '-t-k0-legacy',
+ KEYBOARD_TURKISH_Q: 'tr' + '-t-k0-und',
+ KEYBOARD_UIGHUR: 'ug-t-k0-und',
+ KEYBOARD_UKRAINIAN_101: 'uk-t-k0-101key',
+ KEYBOARD_URDU: 'ur-t-k0-und',
+ KEYBOARD_US_INTERNATIONAL: 'en-us-t-k0-intl',
+ KEYBOARD_UZBEK_CYRILLIC_PHONETIC: 'uz-cyrl-t-k0-und',
+ KEYBOARD_UZBEK_CYRILLIC_TYPEWRITTER: 'uz-cyrl-t-k0-legacy',
+ KEYBOARD_UZBEK_LATIN: 'uz-latn-t-k0-und',
+ KEYBOARD_VIETNAMESE_TCVN: 'vi-t-k0-und',
+ KEYBOARD_VIETNAMESE_TELEX: 'vi-t-k0-legacy',
+ KEYBOARD_VIETNAMESE_VIQR: 'vi-t-k0-viqr',
+ KEYBOARD_VIETNAMESE_VNI: 'vi-t-k0-vni',
+ KEYBOARD_WELSH: 'cy-t-k0-und',
+ KEYBOARD_YIDDISH: 'yi-t-k0-und',
+
+ // Handwriting codes
+ HANDWRIT_AFRIKAANS: 'af-t-i0-handwrit',
+ HANDWRIT_ALBANIAN: 'sq-t-i0-handwrit',
+ HANDWRIT_ARABIC: 'ar-t-i0-handwrit',
+ HANDWRIT_BASQUE: 'eu-t-i0-handwrit',
+ HANDWRIT_BELARUSIAN: 'be-t-i0-handwrit',
+ HANDWRIT_BOSNIAN: 'bs-t-i0-handwrit',
+ HANDWRIT_BULGARIAN: 'bg-t-i0-handwrit',
+ HANDWRIT_CANTONESE: 'zh-yue-t-i0-handwrit',
+ HANDWRIT_CATALAN: 'ca-t-i0-handwrit',
+ HANDWRIT_CEBUANO: 'ceb-t-i0-handwrit',
+ HANDWRIT_CHINESE: 'zh-t-i0-handwrit',
+ HANDWRIT_CHINESE_SIMPLIFIED: 'zh-hans-t-i0-handwrit',
+ HANDWRIT_CHINESE_TRADITIONAL: 'zh-hant-t-i0-handwrit',
+ HANDWRIT_CROATIAN: 'hr-t-i0-handwrit',
+ HANDWRIT_CZECH: 'cs-t-i0-handwrit',
+ HANDWRIT_DANISH: 'da-t-i0-handwrit',
+ HANDWRIT_DUTCH: 'nl-t-i0-handwrit',
+ HANDWRIT_ENGLISH: 'en-t-i0-handwrit',
+ HANDWRIT_ESPERANTO: 'eo-t-i0-handwrit',
+ HANDWRIT_ESTONIAN: 'et-t-i0-handwrit',
+ HANDWRIT_FILIPINO: 'fil-t-i0-handwrit',
+ HANDWRIT_FINNISH: 'fi-t-i0-handwrit',
+ HANDWRIT_FRENCH: 'fr-t-i0-handwrit',
+ HANDWRIT_GALICIAN: 'gl-t-i0-handwrit',
+ HANDWRIT_GERMAN: 'de-t-i0-handwrit',
+ HANDWRIT_GREEK: 'el-t-i0-handwrit',
+ HANDWRIT_GUJARATI: 'gu-t-i0-handwrit',
+ HANDWRIT_HAITIAN: 'ht-t-i0-handwrit',
+ HANDWRIT_HEBREW: 'he-t-i0-handwrit',
+ HANDWRIT_HINDI: 'hi-t-i0-handwrit',
+ HANDWRIT_HMONG: 'hmn-t-i0-handwrit',
+ HANDWRIT_HUNGARIAN: 'hu-t-i0-handwrit',
+ HANDWRIT_ICELANDIC: 'is-t-i0-handwrit',
+ HANDWRIT_INDONESIAN: 'id-t-i0-handwrit',
+ HANDWRIT_IRISH: 'ga-t-i0-handwrit',
+ HANDWRIT_ITALIAN: 'it-t-i0-handwrit',
+ HANDWRIT_JAPANESE: 'ja-t-i0-handwrit',
+ HANDWRIT_JAVANESE: 'jv-t-i0-handwrit',
+ HANDWRIT_KANNADA: 'kn-t-i0-handwrit',
+ HANDWRIT_KHMER: 'km-t-i0-handwrit',
+ HANDWRIT_KOREAN: 'ko-t-i0-handwrit',
+ HANDWRIT_KURDISH: 'ku-t-i0-handwrit',
+ HANDWRIT_KYRGYZ: 'ky-t-i0-handwrit',
+ HANDWRIT_LAO: 'lo-t-i0-handwrit',
+ HANDWRIT_LATIN: 'la-t-i0-handwrit',
+ HANDWRIT_LATVIAN: 'lv-t-i0-handwrit',
+ HANDWRIT_LITHUANIAN: 'lt-t-i0-handwrit',
+ HANDWRIT_MACEDONIAN: 'mk-t-i0-handwrit',
+ HANDWRIT_MALAGASY: 'mg-t-i0-handwrit',
+ HANDWRIT_MALAY: 'ms-t-i0-handwrit',
+ HANDWRIT_MALTESE: 'mt-t-i0-handwrit',
+ HANDWRIT_MAORI: 'mi-t-i0-handwrit',
+ HANDWRIT_MARATHI: 'mr-t-i0-handwrit',
+ HANDWRIT_MONGOLIAN: 'mn-t-i0-handwrit',
+ HANDWRIT_NORWEGIAN: 'no-t-i0-handwrit',
+ HANDWRIT_NORWEGIAN_BOKMAL: 'nb-t-i0-handwrit',
+ HANDWRIT_NORWEGIAN_NYNORSK: 'nn-t-i0-handwrit',
+ HANDWRIT_NYANJA: 'ny-t-i0-handwrit',
+ HANDWRIT_ORIYA: 'or-t-i0-handwrit',
+ HANDWRIT_PERSIAN: 'fa-t-i0-handwrit',
+ HANDWRIT_POLISH: 'pl-t-i0-handwrit',
+ HANDWRIT_PORTUGUESE: 'pt-t-i0-handwrit',
+ HANDWRIT_PORTUGUESE_BRAZIL: 'pt-br-t-i0-handwrit',
+ HANDWRIT_PORTUGUESE_PORTUGAL: 'pt-pt-t-i0-handwrit',
+ HANDWRIT_PUNJABI: 'pa-t-i0-handwrit',
+ HANDWRIT_ROMANIAN: 'ro-t-i0-handwrit',
+ HANDWRIT_RUSSIAN: 'ru-t-i0-handwrit',
+ HANDWRIT_SERBIAN: 'sr-t-i0-handwrit',
+ HANDWRIT_SLOVAK: 'sk-t-i0-handwrit',
+ HANDWRIT_SLOVENIAN: 'sl-t-i0-handwrit',
+ HANDWRIT_SOMALI: 'so-t-i0-handwrit',
+ HANDWRIT_SPANISH: 'es-t-i0-handwrit',
+ HANDWRIT_SUNDANESE: 'su-t-i0-handwrit',
+ HANDWRIT_SWAHILI: 'sw-t-i0-handwrit',
+ HANDWRIT_SWEDISH: 'sv-t-i0-handwrit',
+ HANDWRIT_TAMIL: 'ta-t-i0-handwrit',
+ HANDWRIT_TELUGU: 'te-t-i0-handwrit',
+ HANDWRIT_THAI: 'th-t-i0-handwrit',
+ HANDWRIT_TURKISH: 'tr' + '-t-i0-handwrit',
+ HANDWRIT_UKRAINIAN: 'uk-t-i0-handwrit',
+ HANDWRIT_VIETNAMESE: 'vi-t-i0-handwrit',
+ HANDWRIT_WELSH: 'cy-t-i0-handwrit',
+ HANDWRIT_XHOSA: 'xh-t-i0-handwrit',
+ HANDWRIT_ZULU: 'zu-t-i0-handwrit',
+
+ // Voice
+ VOICE_ENGLISH: 'en-t-i0-voice',
+
+ // XKB
+ XKB_AM_PHONETIC_ARM: 'xkb:am:phonetic:arm',
+ XKB_BE_FRA: 'xkb:be::fra',
+ XKB_BE_GER: 'xkb:be::ger',
+ XKB_BE_NLD: 'xkb:be::nld',
+ XKB_BG_BUL: 'xkb:bg::bul',
+ XKB_BG_PHONETIC_BUL: 'xkb:bg:phonetic:bul',
+ XKB_BR_POR: 'xkb:br::por',
+ XKB_BY_BEL: 'xkb:by::bel',
+ XKB_CA_FRA: 'xkb:ca::fra',
+ XKB_CA_ENG_ENG: 'xkb:ca:eng:eng',
+ XKB_CA_MULTIX_FRA: 'xkb:ca:multix:fra',
+ XKB_CH_GER: 'xkb:ch::ger',
+ XKB_CH_FR_FRA: 'xkb:ch:fr:fra',
+ XKB_CZ_CZE: 'xkb:cz::cze',
+ XKB_CZ_QWERTY_CZE: 'xkb:cz:qwerty:cze',
+ XKB_DE_GER: 'xkb:de::ger',
+ XKB_DE_NEO_GER: 'xkb:de:neo:ger',
+ XKB_DK_DAN: 'xkb:dk::dan',
+ XKB_EE_EST: 'xkb:ee::est',
+ XKB_ES_SPA: 'xkb:es::spa',
+ XKB_ES_CAT_CAT: 'xkb:es:cat:cat',
+ XKB_FI_FIN: 'xkb:fi::fin',
+ XKB_FR_FRA: 'xkb:fr::fra',
+ XKB_GB_DVORAK_ENG: 'xkb:gb:dvorak:eng',
+ XKB_GB_EXTD_ENG: 'xkb:gb:extd:eng',
+ XKB_GE_GEO: 'xkb:ge::geo',
+ XKB_GR_GRE: 'xkb:gr::gre',
+ XKB_HR_SCR: 'xkb:hr::scr',
+ XKB_HU_HUN: 'xkb:hu::hun',
+ XKB_IE_GA: 'xkb:ie::ga',
+ XKB_IL_HEB: 'xkb:il::heb',
+ XKB_IS_ICE: 'xkb:is::ice',
+ XKB_IT_ITA: 'xkb:it::ita',
+ XKB_JP_JPN: 'xkb:jp::jpn',
+ XKB_LATAM_SPA: 'xkb:latam::spa',
+ XKB_LT_LIT: 'xkb:lt::lit',
+ XKB_LV_APOSTROPHE_LAV: 'xkb:lv:apostrophe:lav',
+ XKB_MN_MON: 'xkb:mn::mon',
+ XKB_NO_NOB: 'xkb:no::nob',
+ XKB_PL_POL: 'xkb:pl::pol',
+ XKB_PT_POR: 'xkb:pt::por',
+ XKB_RO_RUM: 'xkb:ro::rum',
+ XKB_RS_SRP: 'xkb:rs::srp',
+ XKB_RU_RUS: 'xkb:ru::rus',
+ XKB_RU_PHONETIC_RUS: 'xkb:ru:phonetic:rus',
+ XKB_SE_SWE: 'xkb:se::swe',
+ XKB_SI_SLV: 'xkb:si::slv',
+ XKB_SK_SLO: 'xkb:sk::slo',
+ XKB_TR_TUR: 'xkb:tr::tur',
+ XKB_UA_UKR: 'xkb:ua::ukr',
+ XKB_US_ENG: 'xkb:us::eng',
+ XKB_US_FIL: 'xkb:us::fil',
+ XKB_US_IND: 'xkb:us::ind',
+ XKB_US_MSA: 'xkb:us::msa',
+ XKB_US_ALTGR_INTL_ENG: 'xkb:us:altgr-intl:eng',
+ XKB_US_COLEMAK_ENG: 'xkb:us:colemak:eng',
+ XKB_US_DVORAK_ENG: 'xkb:us:dvorak:eng',
+ XKB_US_INTL_ENG: 'xkb:us:intl:eng',
+ XKB_US_INTL_NLD: 'xkb:us:intl:nld',
+ XKB_US_INTL_POR: 'xkb:us:intl:por'
+};
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/keyboardcontainer.js b/third_party/google_input_tools/src/chrome/os/inputview/keyboardcontainer.js
new file mode 100644
index 0000000..824af55
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/keyboardcontainer.js
@@ -0,0 +1,311 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.KeyboardContainer');
+
+goog.require('goog.dom.TagName');
+goog.require('goog.dom.classlist');
+goog.require('goog.ui.Container');
+goog.require('i18n.input.chrome.inputview.Css');
+goog.require('i18n.input.chrome.inputview.elements.content.AltDataView');
+goog.require('i18n.input.chrome.inputview.elements.content.CandidateView');
+goog.require('i18n.input.chrome.inputview.elements.content.EmojiView');
+goog.require('i18n.input.chrome.inputview.elements.content.ExpandedCandidateView');
+goog.require('i18n.input.chrome.inputview.elements.content.HandwritingView');
+goog.require('i18n.input.chrome.inputview.elements.content.KeysetView');
+goog.require('i18n.input.chrome.inputview.elements.content.MenuView');
+
+
+
+goog.scope(function() {
+var Css = i18n.input.chrome.inputview.Css;
+var EmojiView = i18n.input.chrome.inputview.elements.content.EmojiView;
+var HandwritingView = i18n.input.chrome.inputview.elements.content.
+ HandwritingView;
+var KeysetView = i18n.input.chrome.inputview.elements.content.KeysetView;
+var SpecNodeName = i18n.input.chrome.inputview.SpecNodeName;
+var content = i18n.input.chrome.inputview.elements.content;
+
+
+
+/**
+ * The keyboard container.
+ *
+ * @param {i18n.input.chrome.inputview.Adapter=} opt_adapter .
+ * @constructor
+ * @extends {goog.ui.Container}
+ */
+i18n.input.chrome.inputview.KeyboardContainer = function(opt_adapter) {
+ goog.base(this);
+
+ /** @type {!content.CandidateView} */
+ this.candidateView = new content.CandidateView('candidateView', this);
+
+ /** @type {!content.AltDataView} */
+ this.altDataView = new content.AltDataView(this);
+
+ /** @type {!content.MenuView} */
+ this.menuView = new content.MenuView(this);
+
+ /** @type {!content.ExpandedCandidateView} */
+ this.expandedCandidateView = new content.ExpandedCandidateView(this);
+
+ /**
+ * The map of the KeysetViews.
+ * Key: keyboard code.
+ * Value: The view object.
+ *
+ * @type {!Object.<string, !KeysetView>}
+ */
+ this.keysetViewMap = {};
+
+ /**
+ * The bus channel to communicate with background.
+ *
+ * @private {i18n.input.chrome.inputview.Adapter}
+ */
+ this.adapter_ = opt_adapter || null;
+};
+goog.inherits(i18n.input.chrome.inputview.KeyboardContainer,
+ goog.ui.Container);
+var KeyboardContainer = i18n.input.chrome.inputview.KeyboardContainer;
+
+
+/** @type {!KeysetView} */
+KeyboardContainer.prototype.currentKeysetView;
+
+
+/**
+ * The padding bottom of the whole keyboard.
+ *
+ * @private {number}
+ */
+KeyboardContainer.PADDING_BOTTOM_ = 7;
+
+
+/**
+ * The padding value of handwriting panel.
+ *
+ * @type {number}
+ * @private
+ */
+KeyboardContainer.HANDWRITING_PADDING_ = 22;
+
+
+/**
+ * The padding of emoji keyset.
+ *
+ * @type {number}
+ * @private
+ */
+KeyboardContainer.EMOJI_PADDING_ = 22;
+
+
+/**
+ * An div to wrapper candidate view and keyboard set view.
+ *
+ * @private {Element}
+ */
+KeyboardContainer.prototype.wrapperDiv_ = null;
+
+
+/** @override */
+KeyboardContainer.prototype.createDom = function() {
+ goog.base(this, 'createDom');
+
+ var elem = this.getElement();
+ this.wrapperDiv_ = this.getDomHelper().createDom(
+ goog.dom.TagName.DIV, Css.WRAPPER);
+ this.candidateView.render(this.wrapperDiv_);
+ this.getDomHelper().appendChild(elem, this.wrapperDiv_);
+ this.altDataView.render();
+ this.menuView.render();
+ this.expandedCandidateView.render(this.wrapperDiv_);
+ this.expandedCandidateView.setVisible(false);
+ goog.dom.classlist.add(elem, Css.CONTAINER);
+};
+
+
+/** @override */
+KeyboardContainer.prototype.enterDocument = function() {
+ goog.base(this, 'enterDocument');
+
+ this.setFocusable(false);
+ this.setFocusableChildrenAllowed(false);
+};
+
+
+/**
+ * Updates the whole keyboard.
+ */
+KeyboardContainer.prototype.update = function() {
+ this.currentKeysetView && this.currentKeysetView.update();
+};
+
+
+/**
+ * Adds a keyset view.
+ *
+ * @param {!Object} keysetData .
+ * @param {!Object} layoutData .
+ * @param {string} keyset .
+ * @param {string} languageCode .
+ * @param {!i18n.input.chrome.inputview.Model} model .
+ * @param {string} inputToolName .
+ * @param {!Object.<string, boolean>} conditions .
+ */
+KeyboardContainer.prototype.addKeysetView = function(keysetData, layoutData,
+ keyset, languageCode, model, inputToolName, conditions) {
+ var view;
+ if (keyset == 'emoji') {
+ view = new EmojiView(keysetData, layoutData, keyset, languageCode, model,
+ inputToolName, this, this.adapter_);
+ } else if (keyset == 'hwt') {
+ view = new HandwritingView(keysetData, layoutData, keyset, languageCode,
+ model, inputToolName, this, this.adapter_);
+ } else {
+ view = new KeysetView(keysetData, layoutData, keyset, languageCode, model,
+ inputToolName, this, this.adapter_);
+ }
+ view.render(this.wrapperDiv_);
+ view.applyConditions(conditions);
+ view.setVisible(false);
+ this.keysetViewMap[keyset] = view;
+};
+
+
+/**
+ * Switches to a keyset.
+ *
+ * @param {string} keyset .
+ * @param {string} title .
+ * @param {boolean} isPasswordBox .
+ * @param {boolean} isA11yMode .
+ * @param {string} rawKeyset The raw keyset id will switch to.
+ * @param {string} lastRawkeyset .
+ * @param {string} languageCode .
+ * @return {boolean} True if switched successfully.
+ */
+KeyboardContainer.prototype.switchToKeyset = function(keyset, title,
+ isPasswordBox, isA11yMode, rawKeyset, lastRawkeyset, languageCode) {
+ if (!this.keysetViewMap[keyset]) {
+ return false;
+ }
+
+ for (var name in this.keysetViewMap) {
+ var view = this.keysetViewMap[name];
+ if (name == keyset) {
+ this.candidateView.setVisible(!view.disableCandidateView);
+ view.setVisible(true);
+ view.update();
+ if (view.spaceKey) {
+ view.spaceKey.updateTitle(title, !isPasswordBox);
+ }
+ if (isA11yMode) {
+ goog.dom.classlist.add(this.getElement(), Css.A11Y);
+ }
+ // If current raw keyset is changed, record it.
+ if (lastRawkeyset != rawKeyset) {
+ view.fromKeyset = lastRawkeyset;
+ }
+ if (view instanceof HandwritingView) {
+ view.setLanguagecode(languageCode);
+ }
+ this.currentKeysetView = view;
+ this.candidateView.updateByKeyset(rawKeyset, isPasswordBox,
+ goog.i18n.bidi.isRtlLanguage(languageCode));
+ } else {
+ view.setVisible(false);
+ }
+ }
+ return true;
+};
+
+
+/**
+ * Resizes the whole keyboard.
+ *
+ * @param {number} width .
+ * @param {number} height .
+ * @param {number} widthPercent .
+ * @param {number} candidateViewHeight .
+ */
+KeyboardContainer.prototype.resize = function(width, height, widthPercent,
+ candidateViewHeight) {
+ if (!this.currentKeysetView) {
+ return;
+ }
+ var elem = this.getElement();
+
+ var h;
+ if (this.currentKeysetView.isHandwriting()) {
+ h = height - KeyboardContainer.HANDWRITING_PADDING_;
+ elem.style.paddingBottom = '';
+ } else {
+ h = height - KeyboardContainer.PADDING_BOTTOM_;
+ elem.style.paddingBottom = KeyboardContainer.PADDING_BOTTOM_ + 'px';
+ }
+
+ var padding = Math.round((width - width * widthPercent) / 2);
+ elem.style.paddingLeft = elem.style.paddingRight = padding + 'px';
+
+ var w = width - 2 * padding;
+ h = this.currentKeysetView.disableCandidateView ?
+ h - KeyboardContainer.EMOJI_PADDING_ : h - candidateViewHeight;
+
+ this.candidateView.setWidthInWeight(
+ this.currentKeysetView.getWidthInWeight());
+ this.candidateView.resize(w, candidateViewHeight);
+ this.currentKeysetView.resize(w, h);
+ this.expandedCandidateView.resize(w, h);
+ this.altDataView.resize(screen.width, height);
+ this.menuView.resize(screen.width, height);
+};
+
+
+/** @override */
+KeyboardContainer.prototype.disposeInternal = function() {
+ goog.dispose(this.candidateView);
+ goog.dispose(this.altDataView);
+ goog.dispose(this.menuView);
+ for (var key in this.keysetViewMap) {
+ goog.dispose(this.keysetViewMap[key]);
+ }
+
+ goog.base(this, 'disposeInternal');
+};
+
+
+/**
+ * Whether there are strokes on canvas.
+ *
+ * @return {boolean} Whether there are strokes on canvas.
+ */
+KeyboardContainer.prototype.hasStrokesOnCanvas = function() {
+ if (this.currentKeysetView) {
+ return this.currentKeysetView.hasStrokesOnCanvas();
+ } else {
+ return false;
+ }
+};
+
+
+/**
+ * Cleans the stokes.
+ */
+KeyboardContainer.prototype.cleanStroke = function() {
+ if (this.currentKeysetView) {
+ this.currentKeysetView.cleanStroke();
+ }
+};
+}); // goog.scope
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/layouts/compactspacerow.js b/third_party/google_input_tools/src/chrome/os/inputview/layouts/compactspacerow.js
new file mode 100644
index 0000000..4499728
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/layouts/compactspacerow.js
@@ -0,0 +1,72 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.layouts.CompactSpaceRow');
+
+goog.require('i18n.input.chrome.inputview.ConditionName');
+goog.require('i18n.input.chrome.inputview.layouts.util');
+
+
+/**
+ * Creates the compact keyboard spaceKey row.
+ * @param {boolean} isNordic True if the space key row is generated for compact
+ * nordic layout.
+ * @return {!Object} The compact spaceKey row.
+ */
+i18n.input.chrome.inputview.layouts.CompactSpaceRow.create = function(
+ isNordic) {
+ var digitSwitcher = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 1.1
+ });
+ var globeOrSymbolKey = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'condition': i18n.input.chrome.inputview.ConditionName.SHOW_GLOBE_OR_SYMBOL,
+ 'widthInWeight': 1
+ });
+ var menuKey = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'condition': i18n.input.chrome.inputview.ConditionName.SHOW_MENU,
+ 'widthInWeight': 1
+ });
+ var slashKey = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 1
+ });
+ var space;
+ if (isNordic) {
+ space = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 5
+ });
+ } else {
+ space = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 4
+ });
+ }
+ var comma = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 1
+ });
+ var period = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 1
+ });
+ var hide = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 1.1
+ });
+
+ menuKey['spec']['giveWeightTo'] = space['spec']['id'];
+ globeOrSymbolKey['spec']['giveWeightTo'] = space['spec']['id'];
+
+ var spaceKeyRow = i18n.input.chrome.inputview.layouts.util.
+ createLinearLayout({
+ 'id': 'spaceKeyrow',
+ 'children': [digitSwitcher, globeOrSymbolKey, menuKey, slashKey, space,
+ comma, period, hide]
+ });
+ return spaceKeyRow;
+};
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/layouts/rowsof101.js b/third_party/google_input_tools/src/chrome/os/inputview/layouts/rowsof101.js
new file mode 100644
index 0000000..9bce0be
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/layouts/rowsof101.js
@@ -0,0 +1,90 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.layouts.RowsOf101');
+
+goog.require('i18n.input.chrome.inputview.layouts.util');
+
+
+/**
+ * Creates the top four rows for 101 keyboard.
+ *
+ * @return {!Array.<!Object>} The rows.
+ */
+i18n.input.chrome.inputview.layouts.RowsOf101.create = function() {
+ var baseKeySpec = {
+ 'widthInWeight': 1,
+ 'heightInWeight': 1
+ };
+
+ // Row1
+ var backquoteKey = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 0.94
+ });
+ var keySequenceOf12 = i18n.input.chrome.inputview.layouts.util.
+ createKeySequence(baseKeySpec, 12);
+ var backspaceKey = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 1.42
+ });
+ var row1 = i18n.input.chrome.inputview.layouts.util.createLinearLayout({
+ 'id': 'row1',
+ 'children': [backquoteKey, keySequenceOf12, backspaceKey]
+ });
+
+ // Row2
+ var tabKey = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 1.38
+ });
+ keySequenceOf12 = i18n.input.chrome.inputview.layouts.util.
+ createKeySequence(baseKeySpec, 12);
+ var backSlashKey = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 0.98
+ });
+ var row2 = i18n.input.chrome.inputview.layouts.util.createLinearLayout({
+ 'id': 'row2',
+ 'children': [tabKey, keySequenceOf12, backSlashKey]
+ });
+
+ // Row3
+ var capslockKey = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 1.68
+ });
+ var keySequenceOf11 = i18n.input.chrome.inputview.layouts.util.
+ createKeySequence(baseKeySpec, 11);
+ var enterKey = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 1.68
+ });
+ var row3 = i18n.input.chrome.inputview.layouts.util.createLinearLayout({
+ 'id': 'row3',
+ 'children': [capslockKey, keySequenceOf11, enterKey]
+ });
+
+ // Row4
+ var shiftLeftKey = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 2.12
+ });
+ var keySequenceOf9 = i18n.input.chrome.inputview.layouts.util.
+ createKeySequence(baseKeySpec, 9);
+ var slashKey = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 1.08
+ });
+ var shiftRightKey = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 2.16
+ });
+ var row4 = i18n.input.chrome.inputview.layouts.util.createLinearLayout({
+ 'id': 'row4',
+ 'children': [shiftLeftKey, keySequenceOf9, slashKey, shiftRightKey]
+ });
+
+ return [row1, row2, row3, row4];
+};
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/layouts/rowsof102.js b/third_party/google_input_tools/src/chrome/os/inputview/layouts/rowsof102.js
new file mode 100644
index 0000000..4df3e41
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/layouts/rowsof102.js
@@ -0,0 +1,108 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.layouts.RowsOf102');
+
+goog.require('i18n.input.chrome.inputview.layouts.util');
+
+
+/**
+ * Creates the top four rows for 102 keyboard.
+ *
+ * @return {!Array.<!Object>} The rows.
+ */
+i18n.input.chrome.inputview.layouts.RowsOf102.create = function() {
+ var baseKeySpec = {
+ 'widthInWeight': 1,
+ 'heightInWeight': 1
+ };
+
+ // Row1
+ var backquoteKey = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 0.94
+ });
+ var keySequenceOf12 = i18n.input.chrome.inputview.layouts.util.
+ createKeySequence(baseKeySpec, 12);
+ var backspaceKey = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 1.42
+ });
+ var row1 = i18n.input.chrome.inputview.layouts.util.createLinearLayout({
+ 'id': 'row1',
+ 'children': [backquoteKey, keySequenceOf12, backspaceKey]
+ });
+
+
+ // Row2 and row3
+
+ // First linear layout at the left of the enter.
+ var tabKey = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 1.38
+ });
+ keySequenceOf12 = i18n.input.chrome.inputview.layouts.util.
+ createKeySequence(baseKeySpec, 12);
+ var row2 = i18n.input.chrome.inputview.layouts.util.createLinearLayout({
+ 'id': 'row2',
+ 'children': [tabKey, keySequenceOf12]
+ });
+
+ // Second linear layout at the right of the enter.
+ var capslockKey = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 1.74
+ });
+ keySequenceOf12 = i18n.input.chrome.inputview.layouts.util.createKeySequence({
+ 'widthInWeight': 0.97
+ }, 12);
+ var row3 = i18n.input.chrome.inputview.layouts.util.createLinearLayout({
+ 'id': 'row3',
+ 'children': [capslockKey, keySequenceOf12]
+ });
+
+ // Vertical layout contains the two rows at the left of the enter.
+ var vLayout = i18n.input.chrome.inputview.layouts.util.createVerticalLayout({
+ 'id': 'row2-3-left',
+ 'children': [row2, row3]
+ });
+
+ // Vertical layout contains enter key.
+ var enterKey = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 0.98,
+ 'heightInWeight': 2
+ });
+ var enterLayout = i18n.input.chrome.inputview.layouts.util.
+ createVerticalLayout({
+ 'id': 'row2-3-right',
+ 'children': [enterKey]
+ });
+
+ // Linear layout contains the two vertical layout.
+ var row2and3 = i18n.input.chrome.inputview.layouts.util.createLinearLayout({
+ 'id': 'row2-3',
+ 'children': [vLayout, enterLayout]
+ });
+
+ // Row4
+ var shiftLeft = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 1.2
+ });
+ var keySequenceOf11 = i18n.input.chrome.inputview.layouts.util.
+ createKeySequence(baseKeySpec, 11);
+ var shiftRight = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 2.16
+ });
+ var row4 = i18n.input.chrome.inputview.layouts.util.createLinearLayout({
+ 'id': 'row4',
+ 'children': [shiftLeft, keySequenceOf11, shiftRight]
+ });
+
+ return [row1, row2and3, row4];
+};
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/layouts/rowsofcompact.js b/third_party/google_input_tools/src/chrome/os/inputview/layouts/rowsofcompact.js
new file mode 100644
index 0000000..d7bf557
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/layouts/rowsofcompact.js
@@ -0,0 +1,179 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.layouts.RowsOfCompact');
+goog.provide('i18n.input.chrome.inputview.layouts.RowsOfCompactAzerty');
+goog.provide('i18n.input.chrome.inputview.layouts.RowsOfCompactNordic');
+
+goog.require('i18n.input.chrome.inputview.layouts.util');
+
+
+/**
+ * Creates the top three rows for compact qwerty keyboard.
+ *
+ * @return {!Array.<!Object>} The rows.
+ */
+i18n.input.chrome.inputview.layouts.RowsOfCompact.create = function() {
+ var baseKeySpec = {
+ 'widthInWeight': 1,
+ 'heightInWeight': 1
+ };
+
+ // Row1
+ var keySequenceOf10 = i18n.input.chrome.inputview.layouts.util.
+ createKeySequence(baseKeySpec, 10);
+ var backspaceKey = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 1.2
+ });
+ var row1 = i18n.input.chrome.inputview.layouts.util.createLinearLayout({
+ 'id': 'row1',
+ 'children': [keySequenceOf10, backspaceKey]
+ });
+
+ // Row2
+ // How to add padding
+ var leftKeyWithPadding = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 1.5
+ });
+ var keySequenceOf8 = i18n.input.chrome.inputview.layouts.util.
+ createKeySequence(baseKeySpec, 8);
+ var enterKey = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 1.7
+ });
+ var row2 = i18n.input.chrome.inputview.layouts.util.createLinearLayout({
+ 'id': 'row2',
+ 'children': [leftKeyWithPadding, keySequenceOf8, enterKey]
+ });
+
+ // Row3
+ var shiftLeftKey = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 1.1
+ });
+ var keySequenceOf9 = i18n.input.chrome.inputview.layouts.util.
+ createKeySequence(baseKeySpec, 9);
+ var shiftRightKey = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 1.1
+ });
+ var row3 = i18n.input.chrome.inputview.layouts.util.createLinearLayout({
+ 'id': 'row3',
+ 'children': [shiftLeftKey, keySequenceOf9, shiftRightKey]
+ });
+
+ return [row1, row2, row3];
+};
+
+/**
+ * Creates the top three rows for compact azerty keyboard.
+ *
+ * @return {!Array.<!Object>} The rows.
+ */
+i18n.input.chrome.inputview.layouts.RowsOfCompactAzerty.create = function() {
+ var baseKeySpec = {
+ 'widthInWeight': 1,
+ 'heightInWeight': 1
+ };
+
+ // Row1
+ var keySequenceOf10 = i18n.input.chrome.inputview.layouts.util.
+ createKeySequence(baseKeySpec, 10);
+ var backspaceKey = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 1.2
+ });
+ var row1 = i18n.input.chrome.inputview.layouts.util.createLinearLayout({
+ 'id': 'row1',
+ 'children': [keySequenceOf10, backspaceKey]
+ });
+
+ // Row2
+ keySequenceOf10 = i18n.input.chrome.inputview.layouts.util.
+ createKeySequence(baseKeySpec, 10);
+ var enterKey = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 1.2
+ });
+ var row2 = i18n.input.chrome.inputview.layouts.util.createLinearLayout({
+ 'id': 'row2',
+ 'children': [keySequenceOf10, enterKey]
+ });
+
+ // Row3
+ var shiftLeftKey = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 1.1
+ });
+ var keySequenceOf9 = i18n.input.chrome.inputview.layouts.util.
+ createKeySequence(baseKeySpec, 9);
+ var shiftRightKey = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 1.1
+ });
+ var row3 = i18n.input.chrome.inputview.layouts.util.createLinearLayout({
+ 'id': 'row3',
+ 'children': [shiftLeftKey, keySequenceOf9, shiftRightKey]
+ });
+
+ return [row1, row2, row3];
+};
+
+/**
+ * Creates the top three rows for compact nordic keyboard.
+ *
+ * @return {!Array.<!Object>} The rows.
+ */
+i18n.input.chrome.inputview.layouts.RowsOfCompactNordic.create = function() {
+ var baseKeySpec = {
+ 'widthInWeight': 1,
+ 'heightInWeight': 1
+ };
+
+ // Row1
+ var keySequenceOf11 = i18n.input.chrome.inputview.layouts.util.
+ createKeySequence(baseKeySpec, 11);
+ var backspaceKey = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 1.2
+ });
+ var row1 = i18n.input.chrome.inputview.layouts.util.createLinearLayout({
+ 'id': 'row1',
+ 'children': [keySequenceOf11, backspaceKey]
+ });
+
+ // Row2
+ keySequenceOf11 = i18n.input.chrome.inputview.layouts.util.
+ createKeySequence(baseKeySpec, 11);
+ var enterKey = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 1.2
+ });
+ var row2 = i18n.input.chrome.inputview.layouts.util.createLinearLayout({
+ 'id': 'row2',
+ 'children': [keySequenceOf11, enterKey]
+ });
+
+ // Row3
+ var shiftLeftKey = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 1.1
+ });
+ var leftKeyWithPadding = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 1.5
+ });
+ var keySequenceOf7 = i18n.input.chrome.inputview.layouts.util.
+ createKeySequence(baseKeySpec, 7);
+ var rightKeyWithPadding = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 1.5
+ });
+ var shiftRightKey = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 1.1
+ });
+ var row3 = i18n.input.chrome.inputview.layouts.util.createLinearLayout({
+ 'id': 'row3',
+ 'children': [shiftLeftKey, leftKeyWithPadding, keySequenceOf7,
+ rightKeyWithPadding, shiftRightKey]
+ });
+ return [row1, row2, row3];
+};
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/layouts/rowsofjp.js b/third_party/google_input_tools/src/chrome/os/inputview/layouts/rowsofjp.js
new file mode 100644
index 0000000..d254039
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/layouts/rowsofjp.js
@@ -0,0 +1,109 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.layouts.RowsOfJP');
+
+goog.require('i18n.input.chrome.inputview.layouts.util');
+
+
+
+goog.scope(function() {
+var util = i18n.input.chrome.inputview.layouts.util;
+
+
+/**
+ * Creates the top four rows for Japanese keyboard.
+ *
+ * @return {!Array.<!Object>} The rows.
+ */
+i18n.input.chrome.inputview.layouts.RowsOfJP.create = function() {
+ var baseKeySpec = {
+ 'widthInWeight': 1,
+ 'heightInWeight': 1
+ };
+
+ // Row1
+ var keySequenceOf15 = util.createKeySequence(baseKeySpec, 15);
+ var row1 = util.createLinearLayout({
+ 'id': 'row1',
+ 'children': [keySequenceOf15]
+ });
+
+
+ // Row2 and row3
+
+ // First linear layout at the left of the enter.
+ var tabKey = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 1.5
+ });
+ var keySequenceOf11 = i18n.input.chrome.inputview.layouts.util.
+ createKeySequence(baseKeySpec, 11);
+ var slashKey = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 1.25
+ });
+ var row2 = i18n.input.chrome.inputview.layouts.util.createLinearLayout({
+ 'id': 'row2',
+ 'children': [tabKey, keySequenceOf11, slashKey]
+ });
+
+ // Second linear layout at the right of the enter.
+ var capslockKey = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 1.75
+ });
+ var keySequenceOf12 = i18n.input.chrome.inputview.layouts.util.
+ createKeySequence(baseKeySpec, 12);
+ var row3 = i18n.input.chrome.inputview.layouts.util.createLinearLayout({
+ 'id': 'row3',
+ 'children': [capslockKey, keySequenceOf12]
+ });
+
+ // Vertical layout contains the two rows at the left of the enter.
+ var vLayout = i18n.input.chrome.inputview.layouts.util.createVerticalLayout({
+ 'id': 'row2-3-left',
+ 'children': [row2, row3]
+ });
+
+ // Vertical layout contains enter key.
+ var enterKey = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 1.25,
+ 'heightInWeight': 2
+ });
+ var enterLayout = i18n.input.chrome.inputview.layouts.util.
+ createVerticalLayout({
+ 'id': 'row2-3-right',
+ 'children': [enterKey]
+ });
+
+ // Linear layout contains the two vertical layout.
+ var row2and3 = i18n.input.chrome.inputview.layouts.util.createLinearLayout({
+ 'id': 'row2-3',
+ 'children': [vLayout, enterLayout]
+ });
+
+ // Row4
+ var shiftLeft = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 2.25
+ });
+ keySequenceOf11 = i18n.input.chrome.inputview.layouts.util.createKeySequence(
+ baseKeySpec, 11);
+ var shiftRight = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 1.75
+ });
+ var row4 = i18n.input.chrome.inputview.layouts.util.createLinearLayout({
+ 'id': 'row4',
+ 'children': [shiftLeft, keySequenceOf11, shiftRight]
+ });
+
+ return [row1, row2and3, row4];
+};
+}); // goog.scope
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/layouts/spacerow.js b/third_party/google_input_tools/src/chrome/os/inputview/layouts/spacerow.js
new file mode 100644
index 0000000..41e4708
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/layouts/spacerow.js
@@ -0,0 +1,76 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.layouts.SpaceRow');
+
+goog.require('i18n.input.chrome.inputview.ConditionName');
+goog.require('i18n.input.chrome.inputview.layouts.util');
+
+
+/**
+ * Creates the spaceKey row.
+ *
+ * @return {!Object} The spaceKey row.
+ */
+i18n.input.chrome.inputview.layouts.SpaceRow.create = function() {
+ var globeKey = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'condition': i18n.input.chrome.inputview.ConditionName.SHOW_GLOBE_OR_SYMBOL,
+ 'widthInWeight': 1
+ });
+ var menuKey = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'condition': i18n.input.chrome.inputview.ConditionName.SHOW_MENU,
+ 'widthInWeight': 1
+ });
+ var ctrlKey = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 1
+ });
+ var altKey = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 1
+ });
+ var spaceKey = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 4.87
+ });
+ var enSwitcher = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 1,
+ 'condition': i18n.input.chrome.inputview.ConditionName.
+ SHOW_EN_SWITCHER_KEY
+ });
+ var altGrKey = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 1.25,
+ 'condition': i18n.input.chrome.inputview.ConditionName.
+ SHOW_ALTGR
+ });
+ // If globeKey or altGrKey is not shown, give its weight to space key.
+ globeKey['spec']['giveWeightTo'] = spaceKey['spec']['id'];
+ menuKey['spec']['giveWeightTo'] = spaceKey['spec']['id'];
+ altGrKey['spec']['giveWeightTo'] = spaceKey['spec']['id'];
+ enSwitcher['spec']['giveWeightTo'] = spaceKey['spec']['id'];
+
+ var leftKey = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 1.08
+ });
+ var rightKey = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 1.08
+ });
+ var hideKeyboardKey = i18n.input.chrome.inputview.layouts.util.createKey({
+ 'widthInWeight': 1.08
+ });
+ var spaceKeyRow = i18n.input.chrome.inputview.layouts.util.
+ createLinearLayout({
+ 'id': 'spaceKeyrow',
+ 'children': [globeKey, menuKey, ctrlKey, altKey, spaceKey,
+ enSwitcher, altGrKey, leftKey, rightKey, hideKeyboardKey]
+ });
+ return spaceKeyRow;
+};
+
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/layouts/util.js b/third_party/google_input_tools/src/chrome/os/inputview/layouts/util.js
new file mode 100644
index 0000000..00ca944
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/layouts/util.js
@@ -0,0 +1,221 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.layouts.util');
+goog.require('i18n.input.chrome.inputview.Css');
+goog.require('i18n.input.chrome.inputview.elements.ElementType');
+
+
+
+goog.scope(function() {
+var ElementType = i18n.input.chrome.inputview.elements.ElementType;
+
+
+/**
+ * The id for the key.
+ *
+ * @type {number}
+ * @private
+ */
+i18n.input.chrome.inputview.layouts.util.keyId_ = 0;
+
+
+/**
+ * The prefix of the key id, it's overwritten by layout file.
+ *
+ * @type {string}
+ */
+i18n.input.chrome.inputview.layouts.util.keyIdPrefix = '';
+
+/**
+ * Resets id counter for keys in preparation for processing a new layout.
+ * @param {string} prefix The prefix for the key id.
+ */
+i18n.input.chrome.inputview.layouts.util.setPrefix = function(prefix) {
+ i18n.input.chrome.inputview.layouts.util.keyIdPrefix = prefix;
+ i18n.input.chrome.inputview.layouts.util.keyId_ = 0;
+};
+
+/**
+ * Creates a sequence of key with the same specification.
+ *
+ * @param {!Object} spec The specification.
+ * @param {number} num How many keys.
+ * @return {!Array.<Object>} The keys.
+ */
+i18n.input.chrome.inputview.layouts.util.createKeySequence = function(spec,
+ num) {
+ var sequence = [];
+ for (var i = 0; i < num; i++) {
+ sequence.push(i18n.input.chrome.inputview.layouts.util.createKey(spec));
+ }
+ return sequence;
+};
+
+
+/**
+ * Creates a soft key view.
+ *
+ * @param {!Object} spec The specification.
+ * @param {string=} opt_id The id.
+ * @return {!Object} The soft key view.
+ */
+i18n.input.chrome.inputview.layouts.util.createKey = function(spec, opt_id) {
+ var id = i18n.input.chrome.inputview.layouts.util.keyIdPrefix +
+ i18n.input.chrome.inputview.layouts.util.keyId_++;
+ return i18n.input.chrome.inputview.layouts.util.createElem(
+ ElementType.SOFT_KEY_VIEW, spec, id);
+};
+
+
+/**
+ * Creates a linear layout.
+ *
+ * @param {!Object} spec The specification.
+ * @param {string=} opt_id The id.
+ * @return {!Object} The linear layout.
+ */
+i18n.input.chrome.inputview.layouts.util.createLinearLayout = function(spec,
+ opt_id) {
+ return i18n.input.chrome.inputview.layouts.util.createElem(
+ ElementType.LINEAR_LAYOUT, spec, opt_id, spec['iconCssClass']);
+};
+
+
+/**
+ * Creates an extended layout.
+ *
+ * @param {!Object} spec The specification.
+ * @param {string=} opt_id The id.
+ * @return {!Object} The extended layout.
+ */
+i18n.input.chrome.inputview.layouts.util.createExtendedLayout = function(spec,
+ opt_id) {
+ return i18n.input.chrome.inputview.layouts.util.createElem(
+ ElementType.EXTENDED_LAYOUT, spec, opt_id, spec['iconCssClass']);
+};
+
+
+/**
+ * Creates a handwriting layout.
+ *
+ * @param {!Object} spec The specification.
+ * @param {string=} opt_id The id.
+ * @return {!Object} The handwriting layout.
+ */
+i18n.input.chrome.inputview.layouts.util.createHandwritingLayout =
+ function(spec, opt_id) {
+ return i18n.input.chrome.inputview.layouts.util.createElem(
+ ElementType.HANDWRITING_LAYOUT, spec, opt_id);
+};
+
+
+/**
+ * Creates a vertical layout.
+ *
+ * @param {!Object} spec The specification.
+ * @param {string=} opt_id The id.
+ * @return {!Object} The vertical layout.
+ */
+i18n.input.chrome.inputview.layouts.util.createVerticalLayout = function(spec,
+ opt_id) {
+ return i18n.input.chrome.inputview.layouts.util.createElem(
+ ElementType.VERTICAL_LAYOUT, spec, opt_id);
+};
+
+
+/**
+ * Creates a layout view.
+ *
+ * @param {!Object} spec The specification.
+ * @param {string=} opt_id The id.
+ * @return {!Object} The view.
+ */
+i18n.input.chrome.inputview.layouts.util.createLayoutView = function(spec,
+ opt_id) {
+ return i18n.input.chrome.inputview.layouts.util.createElem(
+ ElementType.LAYOUT_VIEW, spec, opt_id);
+};
+
+
+/**
+ * Creates a candidate view.
+ *
+ * @param {!Object} spec The specification.
+ * @param {string=} opt_id The id.
+ * @return {!Object} The view.
+ */
+i18n.input.chrome.inputview.layouts.util.createCandidateView = function(spec,
+ opt_id) {
+ return i18n.input.chrome.inputview.layouts.util.createElem(
+ ElementType.CANDIDATE_VIEW, spec, opt_id);
+};
+
+
+/**
+ * Creates a canvas view.
+ *
+ * @param {!Object} spec The specification.
+ * @param {string=} opt_id The id.
+ * @return {!Object} The view.
+ */
+i18n.input.chrome.inputview.layouts.util.createCanvasView = function(spec,
+ opt_id) {
+ return i18n.input.chrome.inputview.layouts.util.createElem(
+ ElementType.CANVAS_VIEW, spec, opt_id);
+};
+
+
+/**
+ * Creates the keyboard.
+ *
+ * @param {Object} spec The specification.
+ * @param {string=} opt_id The id.
+ * @return {Object} The keyboard.
+ */
+i18n.input.chrome.inputview.layouts.util.createKeyboard = function(spec,
+ opt_id) {
+ return i18n.input.chrome.inputview.layouts.util.createElem(
+ ElementType.KEYBOARD, spec, opt_id);
+};
+
+
+/**
+ * Creates an element which could be any type, such as soft key view, layout,
+ * etc.
+ *
+ * @param {!ElementType} type The type.
+ * @param {Object} spec The specification.
+ * @param {string=} opt_id The id.
+ * @param {i18n.input.chrome.inputview.Css=} opt_iconCssClass The Css class.
+ * @return {!Object} The element.
+ */
+i18n.input.chrome.inputview.layouts.util.createElem = function(type, spec,
+ opt_id, opt_iconCssClass) {
+ var newSpec = {};
+ for (var key in spec) {
+ newSpec[key] = spec[key];
+ }
+ newSpec['type'] = type;
+ if (opt_id) {
+ newSpec['id'] = opt_id;
+ }
+ if (opt_iconCssClass) {
+ newSpec['iconCssClass'] = opt_iconCssClass;
+ }
+ return {
+ 'spec': newSpec
+ };
+};
+
+}); // goog.scope
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/m17nmodel.js b/third_party/google_input_tools/src/chrome/os/inputview/m17nmodel.js
new file mode 100644
index 0000000..3082006
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/m17nmodel.js
@@ -0,0 +1,157 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.M17nModel');
+
+goog.require('goog.events.EventHandler');
+goog.require('goog.events.EventTarget');
+goog.require('i18n.input.chrome.inputview.SpecNodeName');
+goog.require('i18n.input.chrome.inputview.content.util');
+goog.require('i18n.input.chrome.vk.KeyCode');
+goog.require('i18n.input.chrome.vk.Model');
+
+
+
+/**
+ * The model to legacy cloud vk configuration.
+ *
+ * @constructor
+ * @extends {goog.events.EventTarget}
+ */
+i18n.input.chrome.inputview.M17nModel = function() {
+ goog.base(this);
+
+ /**
+ * The event handler.
+ *
+ * @type {!goog.events.EventHandler}
+ * @private
+ */
+ this.handler_ = new goog.events.EventHandler(this);
+
+ /**
+ * The model for cloud vk.
+ *
+ * @type {!i18n.input.chrome.vk.Model}
+ * @private
+ */
+ this.model_ = new i18n.input.chrome.vk.Model();
+ this.handler_.listen(this.model_,
+ i18n.input.chrome.vk.EventType.LAYOUT_LOADED,
+ this.onLayoutLoaded_);
+};
+goog.inherits(i18n.input.chrome.inputview.M17nModel,
+ goog.events.EventTarget);
+
+
+/**
+ * The active layout view.
+ *
+ * @type {!i18n.input.chrome.vk.ParsedLayout}
+ * @private
+ */
+i18n.input.chrome.inputview.M17nModel.prototype.layoutView_;
+
+
+/**
+ * Loads the configuration.
+ *
+ * @param {string} lang The m17n keyboard layout code (with 'm17n:' prefix).
+ */
+i18n.input.chrome.inputview.M17nModel.prototype.loadConfig = function(lang) {
+ var m17nMatches = lang.match(/^m17n:(.*)/);
+ if (m17nMatches && m17nMatches[1]) {
+ this.model_.loadLayout(m17nMatches[1]);
+ }
+};
+
+
+/**
+ * Callback when legacy model is loaded.
+ *
+ * @param {!i18n.input.chrome.vk.LayoutEvent} e The event.
+ * @private
+ */
+i18n.input.chrome.inputview.M17nModel.prototype.onLayoutLoaded_ = function(
+ e) {
+ var layoutView = /** @type {!i18n.input.chrome.vk.ParsedLayout} */
+ (e.layoutView);
+ this.layoutView_ = layoutView;
+ var is102 = layoutView.view.is102;
+ var codes = is102 ? i18n.input.chrome.vk.KeyCode.CODES102 :
+ i18n.input.chrome.vk.KeyCode.CODES101;
+ var keyCount = is102 ? 48 : 47;
+ var keyCharacters = [];
+ for (var i = 0; i < keyCount; i++) {
+ var characters = this.findCharacters_(layoutView.view.mappings,
+ codes[i]);
+ keyCharacters.push(characters);
+ }
+ keyCharacters.push(['\u0020', '\u0020']);
+ var hasAltGrKey = !!layoutView.view.mappings['c'] &&
+ layoutView.view.mappings['c'] != layoutView.view.mappings[''];
+ var skvPrefix = is102 ? '102kbd-k-' : '101kbd-k-';
+ var skPrefix = layoutView.view.id + '-k-';
+ var data = i18n.input.chrome.inputview.content.util.createData(keyCharacters,
+ skvPrefix, is102, hasAltGrKey);
+ if (data) {
+ data[i18n.input.chrome.inputview.SpecNodeName.TITLE] =
+ layoutView.view.title;
+ data[i18n.input.chrome.inputview.SpecNodeName.ID] =
+ 'm17n:' + e.layoutCode;
+ this.dispatchEvent(new i18n.input.chrome.inputview.events.
+ ConfigLoadedEvent(data));
+ }
+};
+
+
+/**
+ * Finds out the characters for the key.
+ *
+ * @param {!Object} mappings The mappings.
+ * @param {string} code The code.
+ * @return {!Array.<string>} The characters for the code.
+ * @private
+ */
+i18n.input.chrome.inputview.M17nModel.prototype.findCharacters_ = function(
+ mappings, code) {
+ var characters = [];
+ var states = [
+ '',
+ 's',
+ 'c',
+ 'sc',
+ 'l',
+ 'sl',
+ 'cl',
+ 'scl'
+ ];
+ for (var i = 0; i < states.length; i++) {
+ if (mappings[states[i]] && mappings[states[i]][code]) {
+ characters[i] = mappings[states[i]][code][1];
+ } else if (code == '\u0020') {
+ characters[i] = '\u0020';
+ } else {
+ characters[i] = '';
+ }
+ }
+ return characters;
+};
+
+
+/** @override */
+i18n.input.chrome.inputview.M17nModel.prototype.disposeInternal = function() {
+ goog.dispose(this.handler_);
+
+ goog.base(this, 'disposeInternal');
+};
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/model.js b/third_party/google_input_tools/src/chrome/os/inputview/model.js
new file mode 100644
index 0000000..d70d9f5
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/model.js
@@ -0,0 +1,166 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.Model');
+
+goog.require('goog.array');
+goog.require('goog.events.EventTarget');
+goog.require('goog.net.jsloader');
+goog.require('i18n.input.chrome.inputview.ConditionName');
+goog.require('i18n.input.chrome.inputview.Settings');
+goog.require('i18n.input.chrome.inputview.SpecNodeName');
+goog.require('i18n.input.chrome.inputview.StateManager');
+goog.require('i18n.input.chrome.inputview.events.ConfigLoadedEvent');
+goog.require('i18n.input.chrome.inputview.events.LayoutLoadedEvent');
+
+goog.scope(function() {
+var SpecNodeName = i18n.input.chrome.inputview.SpecNodeName;
+
+
+
+/**
+ * The model.
+ * @constructor
+ * @extends {goog.events.EventTarget}
+ */
+i18n.input.chrome.inputview.Model = function() {
+ goog.base(this);
+
+ /**
+ * The state manager.
+ *
+ * @type {!i18n.input.chrome.inputview.StateManager}
+ */
+ this.stateManager = new i18n.input.chrome.inputview.StateManager();
+
+ /**
+ * The configuration.
+ *
+ * @type {!i18n.input.chrome.inputview.Settings}
+ */
+ this.settings = new i18n.input.chrome.inputview.Settings();
+
+ /** @private {!Array.<string>} */
+ this.loadingResources_ = [];
+
+ goog.exportSymbol('google.ime.chrome.inputview.onLayoutLoaded',
+ goog.bind(this.onLayoutLoaded_, this));
+ goog.exportSymbol('google.ime.chrome.inputview.onConfigLoaded',
+ goog.bind(this.onConfigLoaded_, this));
+};
+var Model = i18n.input.chrome.inputview.Model;
+goog.inherits(Model, goog.events.EventTarget);
+
+
+/**
+ * The path to the layouts directory.
+ *
+ * @type {string}
+ * @private
+ */
+Model.LAYOUTS_PATH_ =
+ '/inputview_layouts/';
+
+
+/**
+ * The path to the content directory.
+ *
+ * @type {string}
+ * @private
+ */
+Model.CONTENT_PATH_ =
+ '/config/';
+
+
+/**
+ * Callback when configuration is loaded.
+ *
+ * @param {!Object} data The configuration data.
+ * @private
+ */
+Model.prototype.onConfigLoaded_ = function(data) {
+ goog.array.remove(this.loadingResources_, this.getConfigUrl_(
+ data[SpecNodeName.ID]));
+ this.dispatchEvent(new i18n.input.chrome.inputview.events.ConfigLoadedEvent(
+ data));
+};
+
+
+/**
+ * Gets the layout url.
+ *
+ * @param {string} layout .
+ * @private
+ * @return {string} The url of the layout data.
+ */
+Model.prototype.getLayoutUrl_ = function(layout) {
+ return Model.LAYOUTS_PATH_ + layout + '.js';
+};
+
+
+/**
+ * Gets the keyset configuration url.
+ *
+ * @param {string} keyset .
+ * @private
+ * @return {string} .
+ */
+Model.prototype.getConfigUrl_ = function(keyset) {
+ // Strips out all the suffixes in the keyboard code.
+ var configId = keyset.replace(/\..*$/, '');
+ return Model.CONTENT_PATH_ + configId + '.js';
+};
+
+
+/**
+ * Callback when layout is loaded.
+ *
+ * @param {!Object} data The layout data.
+ * @private
+ */
+Model.prototype.onLayoutLoaded_ = function(data) {
+ goog.array.remove(this.loadingResources_, this.getLayoutUrl_(data[
+ SpecNodeName.LAYOUT_ID]));
+ this.dispatchEvent(new i18n.input.chrome.inputview.events.LayoutLoadedEvent(
+ data));
+};
+
+
+/**
+ * Loads a layout.
+ *
+ * @param {string} layout The layout name.
+ */
+Model.prototype.loadLayout = function(layout) {
+ var url = this.getLayoutUrl_(layout);
+ if (!goog.array.contains(this.loadingResources_, url)) {
+ this.loadingResources_.push(url);
+ goog.net.jsloader.load(url);
+ }
+};
+
+
+/**
+ * Loads the configuration for the keyboard code.
+ *
+ * @param {string} keyboardCode The keyboard code.
+ */
+Model.prototype.loadConfig = function(keyboardCode) {
+ var url = this.getConfigUrl_(keyboardCode);
+ if (!goog.array.contains(this.loadingResources_, url)) {
+ this.loadingResources_.push(url);
+ goog.net.jsloader.load(url);
+ }
+};
+
+}); // goog.scope
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/pointerconfig.js b/third_party/google_input_tools/src/chrome/os/inputview/pointerconfig.js
new file mode 100644
index 0000000..3a512b2
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/pointerconfig.js
@@ -0,0 +1,76 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.PointerConfig');
+
+
+/**
+ * The configuration of the pointer.
+ *
+ * @param {boolean} dblClick .
+ * @param {boolean} longPressWithPointerUp .
+ * @param {boolean} longPressWithoutPointerUp .
+ * @constructor
+ */
+i18n.input.chrome.inputview.PointerConfig = function(dblClick,
+ longPressWithPointerUp, longPressWithoutPointerUp) {
+ /**
+ * True to enable double click.
+ *
+ * @type {boolean}
+ */
+ this.dblClick = dblClick;
+
+ /**
+ * The delay of the double click. If not set or is 0, the default delay(500ms)
+ * is used.
+ *
+ * @type {number}
+ */
+ this.dblClickDelay = 0;
+
+ /**
+ * True to enable long press and not cancel the next pointer up.
+ *
+ * @type {boolean}
+ */
+ this.longPressWithPointerUp = longPressWithPointerUp;
+
+ /**
+ * True to enable long press and cancel the next pointer up.
+ *
+ * @type {boolean}
+ */
+ this.longPressWithoutPointerUp = longPressWithoutPointerUp;
+
+ /**
+ * The flicker direction.
+ *
+ * @type {number}
+ */
+ this.flickerDirection = 0;
+
+ /**
+ * The delay of the long press.
+ *
+ * @type {number}
+ */
+ this.longPressDelay = 0;
+
+ /** @type {boolean} */
+ this.stopEventPropagation = true;
+
+ /** @type {boolean} */
+ this.preventDefault = true;
+};
+
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/readystate.js b/third_party/google_input_tools/src/chrome/os/inputview/readystate.js
new file mode 100644
index 0000000..6a633b1
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/readystate.js
@@ -0,0 +1,87 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.ReadyState');
+
+
+
+goog.scope(function() {
+/**
+ * The system ready state which mainains a state bit map.
+ * Inputview controller uses this to determine whether the system is ready to
+ * render the UI.
+ *
+ * @constructor
+ */
+i18n.input.chrome.inputview.ReadyState = function() {
+};
+var ReadyState = i18n.input.chrome.inputview.ReadyState;
+
+
+/**
+ * The state type.
+ *
+ * @enum {number}
+ */
+ReadyState.StateType = {
+ IME_LIST_READY: 1,
+ KEYBOARD_CONFIG_READY: 2,
+ LAYOUT_READY: 4,
+ LAYOUT_CONFIG_READY: 8,
+ M17N_LAYOUT_READY: 16
+};
+
+
+/**
+ * The internal ready state bit map.
+ *
+ * @private {number}
+ */
+ReadyState.prototype.state_ = 0;
+
+
+/**
+ * Gets whether the system is ready.
+ *
+ * @return {boolean} Whether the system is ready.
+ */
+ReadyState.prototype.isAllReady = function() {
+ return !!(this.state_ & (
+ ReadyState.StateType.IME_LIST_READY |
+ ReadyState.StateType.KEYBOARD_CONFIG_READY |
+ ReadyState.StateType.LAYOUT_READY |
+ ReadyState.StateType.LAYOUT_CONFIG_READY |
+ ReadyState.StateType.M17N_LAYOUT_READY));
+};
+
+
+/**
+ * Gets whether a specific state type is ready.
+ *
+ * @param {ReadyState.StateType} stateType .
+ * @return {boolean} Whether is ready.
+ */
+ReadyState.prototype.isReady = function(stateType) {
+ return !!(this.state_ & stateType);
+};
+
+
+/**
+ * Sets state ready for the given state type.
+ *
+ * @param {ReadyState.StateType} stateType .
+ */
+ReadyState.prototype.markStateReady = function(stateType) {
+ this.state_ |= stateType;
+};
+}); // goog.scope
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/settings.js b/third_party/google_input_tools/src/chrome/os/inputview/settings.js
new file mode 100644
index 0000000..640ee14
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/settings.js
@@ -0,0 +1,96 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.Settings');
+
+
+goog.scope(function() {
+
+
+
+/**
+ * The settings.
+ *
+ * @constructor
+ */
+i18n.input.chrome.inputview.Settings = function() {};
+var Settings = i18n.input.chrome.inputview.Settings;
+
+
+/**
+ * True to always render the altgr character in the soft key.
+ *
+ * @type {boolean}
+ */
+Settings.prototype.alwaysRenderAltGrCharacter = false;
+
+
+/** @type {boolean} */
+Settings.prototype.autoSpace = false;
+
+
+/** @type {boolean} */
+Settings.prototype.autoCapital = false;
+
+
+/** @type {boolean} */
+Settings.prototype.autoCorrection = false;
+
+
+/** @type {boolean} */
+Settings.prototype.supportCompact = false;
+
+
+/** @type {boolean} */
+Settings.prototype.enableLongPress = true;
+
+
+/** @type {boolean} */
+Settings.prototype.doubleSpacePeriod = false;
+
+
+/** @type {boolean} */
+Settings.prototype.soundOnKeypress = false;
+
+
+/**
+ * The flag to control whether candidates naviagation feature is enabled.
+ *
+ * @type {boolean}
+ */
+Settings.prototype.candidatesNavigation = false;
+
+
+/**
+ * Saves the preferences.
+ *
+ * @param {string} preference The name of the preference.
+ * @param {*} value The preference value.
+ */
+Settings.prototype.savePreference = function(preference, value) {
+ window.localStorage.setItem(preference, /** @type {string} */(value));
+};
+
+
+/**
+ * Gets the preference value.
+ *
+ * @param {string} preference The name of the preference.
+ * @return {*} The value.
+ */
+Settings.prototype.getPreference = function(preference) {
+ return window.localStorage.getItem(preference);
+};
+
+}); // goog.scope
+
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/sizespec.js b/third_party/google_input_tools/src/chrome/os/inputview/sizespec.js
new file mode 100644
index 0000000..ecb58e9
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/sizespec.js
@@ -0,0 +1,70 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.SizeSpec');
+
+
+
+goog.scope(function() {
+
+var SizeSpec = i18n.input.chrome.inputview.SizeSpec;
+
+
+/**
+ * The height of the keyboard in a11y mode.
+ *
+ * @type {number}
+ */
+SizeSpec.A11Y_HEIGHT = 280;
+
+
+/**
+ * The height of the keyboard in non-a11y mode.
+ *
+ * @type {number}
+ */
+SizeSpec.NON_A11Y_HEIGHT = 372;
+
+
+/** @type {number} */
+SizeSpec.A11Y_CANDIDATE_VIEW_HEIGHT = 30;
+
+
+/** @type {number} */
+SizeSpec.NON_A11Y_CANDIDATE_VIEW_HEIGHT = 45;
+
+
+/**
+ * The width percent of a11y keyboard in horizontal mode or vertical mode.
+ *
+ * @enum {number}
+ */
+SizeSpec.A11Y_WIDTH_PERCENT = {
+ HORIZONTAL: 0.74,
+ VERTICAL: 0.88
+};
+
+
+/**
+ * The width percent of non-a11y keyboard in horizontal mode or vertical mode.
+ *
+ * @enum {number}
+ */
+SizeSpec.NON_A11Y_WIDTH_PERCENT = {
+ HORIZONTAL: 0.84,
+ HORIZONTAL_WIDE_SCREEN: 0.788,
+ VERTICAL: 0.88
+};
+
+}); // goog.scope
+
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/soundcontroller.js b/third_party/google_input_tools/src/chrome/os/inputview/soundcontroller.js
new file mode 100644
index 0000000..19b7024
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/soundcontroller.js
@@ -0,0 +1,226 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.SoundController');
+
+goog.require('goog.Disposable');
+goog.require('goog.dom');
+goog.require('i18n.input.chrome.inputview.Sounds');
+goog.require('i18n.input.chrome.inputview.elements.ElementType');
+
+goog.scope(function() {
+var Sounds = i18n.input.chrome.inputview.Sounds;
+var ElementType = i18n.input.chrome.inputview.elements.ElementType;
+var keyToSoundIdOnKeyUp = {};
+var keyToSoundIdOnKeyRepeat = {};
+
+
+/**
+ * Sound controller for the keyboard.
+ *
+ * @param {!boolean} enabled Whether sounds is enabled by default.
+ * @param {?number} opt_volume The default volume for sound tracks.
+ * @constructor
+ * @extends {goog.Disposable}
+ */
+i18n.input.chrome.inputview.SoundController = function(enabled, opt_volume) {
+
+ /**
+ * Collection of all the sound pools.
+ *
+ * @type {!Object.<string, !Object>}
+ */
+ this.sounds_ = {};
+
+ this.enabled_ = enabled;
+
+ /**
+ * The default volume for all audio tracks. Tracks with volume 0 will be
+ * skipped.
+ *
+ * @type {number}
+ */
+ this.volume_ = opt_volume || this.DEFAULT_VOLUME;
+
+ if (enabled)
+ this.initialize();
+};
+goog.inherits(i18n.input.chrome.inputview.SoundController, goog.Disposable);
+
+
+var Controller = i18n.input.chrome.inputview.SoundController;
+
+
+/**
+ * @define {number} The size of the pool to use for playing audio sounds.
+ */
+Controller.prototype.POOL_SIZE = 10;
+
+
+/**
+ * @define {number} The default audio track volume.
+ */
+Controller.prototype.DEFAULT_VOLUME = 0.6;
+
+
+/**
+ * Initializes the sound controller.
+ */
+Controller.prototype.initialize = function() {
+ for (var sound in Sounds) {
+ this.addSound_(Sounds[sound]);
+ }
+ keyToSoundIdOnKeyUp[ElementType.BACKSPACE_KEY] = Sounds.NONE;
+ keyToSoundIdOnKeyUp[ElementType.ENTER_KEY] = Sounds.RETURN;
+ keyToSoundIdOnKeyUp[ElementType.SPACE_KEY] = Sounds.SPACEBAR;
+ keyToSoundIdOnKeyRepeat[ElementType.BACKSPACE_KEY] = Sounds.DELETE;
+};
+
+
+/**
+ * Caches the specified sound on the keyboard.
+ *
+ * @param {string} soundId The name of the .wav file in the "sounds"
+ directory.
+ * @private
+ */
+Controller.prototype.addSound_ = function(soundId) {
+ if (soundId == Sounds.NONE || this.sounds_[soundId])
+ return;
+ var pool = [];
+ // Create sound pool.
+ for (var i = 0; i < this.POOL_SIZE; i++) {
+ var audio = goog.dom.createDom('audio', {
+ preload: 'auto',
+ id: soundId,
+ src: 'sounds/' + soundId + '.wav',
+ volume: this.volume_
+ });
+ pool.push(audio);
+ }
+ this.sounds_[soundId] = pool;
+};
+
+
+/**
+ * Sets the volume for the specified sound.
+ *
+ * @param {string} soundId The id of the sound.
+ * @param {number} volume The volume to set.
+ */
+Controller.prototype.setVolume = function(soundId, volume) {
+ var pool = this.sounds_[soundId];
+ if (!pool) {
+ console.error('Cannot find sound: ' + soundId);
+ return;
+ }
+ // Change volume for all sounds in the pool.
+ for (var i = 0; i < pool.length; i++) {
+ pool[i].volume = volume;
+ }
+};
+
+
+/**
+ * Enables or disable playing sounds on keypress.
+ * @param {!boolean} enabled
+ */
+Controller.prototype.setEnabled = function(enabled) {
+ this.enabled_ = enabled;
+ if (this.enabled_)
+ this.initialize();
+};
+
+
+/**
+ * Sets the volume for all sounds on the keyboard.
+ *
+ * @param {number} volume The volume of the sounds.
+ */
+Controller.prototype.setMasterVolume = function(volume) {
+ this.volume_ = volume;
+ for (var id in this.sounds_) {
+ this.setVolume(id, volume);
+ }
+};
+
+
+/**
+ * Plays the specified sound.
+ *
+ * @param {string} soundId The id of the audio tag.
+ * @private
+ */
+Controller.prototype.playSound_ = function(soundId) {
+ // If master volume is zero, ignore the request.
+ if (!this.enabled_ || this.volume_ == 0 || soundId == Sounds.NONE)
+ return;
+ var pool = this.sounds_[soundId];
+ if (!pool) {
+ console.error('Cannot find sound: ' + soundId);
+ return;
+ }
+ // Search the sound pool for a free resource.
+ for (var i = 0; i < pool.length; i++) {
+ if (pool[i].paused) {
+ pool[i].play();
+ return;
+ }
+ }
+};
+
+
+/**
+ * On key up.
+ *
+ * @param {ElementType} key The key released.
+ */
+ Controller.prototype.onKeyUp = function(key) {
+ var sound = keyToSoundIdOnKeyUp[key] || Sounds.STANDARD;
+ this.playSound_(sound);
+ };
+
+
+ /**
+ * On key repeat.
+ *
+ * @param {ElementType} key The key that is being repeated.
+ */
+ Controller.prototype.onKeyRepeat = function(key) {
+ var sound = keyToSoundIdOnKeyRepeat[key] || Sounds.NONE;
+ this.playSound_(sound);
+ };
+
+
+ /** @override */
+ Controller.prototype.disposeInternal = function() {
+ for (var soundId in this.sounds_) {
+ var pool = this.sounds_[soundId];
+ for (var i = 0; i < pool.length; i++) {
+ var tag = pool[i];
+ if (tag && tag.loaded) {
+ tag.pause();
+ tag.autoplay = false;
+ tag.loop = false;
+ tag.currentTime = 0;
+ }
+ }
+ delete this.sounds_[soundId];
+ }
+ this.sounds_ = {};
+ keyToSoundIdOnKeyUp = {};
+ keyToSoundIdOnKeyRepeat = {};
+ goog.base(this, 'disposeInternal');
+ };
+
+}); // goog.scope
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/sounds.js b/third_party/google_input_tools/src/chrome/os/inputview/sounds.js
new file mode 100644
index 0000000..ea47003
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/sounds.js
@@ -0,0 +1,29 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.Sounds');
+
+
+/**
+ * The available sounds.
+ *
+ * @enum {string}
+ */
+i18n.input.chrome.inputview.Sounds = {
+ DELETE: 'keypress-delete',
+ RETURN: 'keypress-return',
+ SPACEBAR: 'keypress-spacebar',
+ STANDARD: 'keypress-standard',
+ NONE: 'none'
+};
+
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/specnodename.js b/third_party/google_input_tools/src/chrome/os/inputview/specnodename.js
new file mode 100644
index 0000000..663e1e7
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/specnodename.js
@@ -0,0 +1,70 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.SpecNodeName');
+
+
+/**
+ * The node name in the specification.
+ *
+ * @enum {string}
+ */
+i18n.input.chrome.inputview.SpecNodeName = {
+ ALIGN: 'align',
+ CHARACTERS: 'characters',
+ CHILDREN: 'children',
+ CONDITION: 'condition',
+ DIRECTION: 'direction',
+ GIVE_WEIGHT_TO: 'giveWeightTo',
+ HAS_ALTGR_KEY: 'hasAltGrKey',
+ HAS_COMPACT_KEYBOARD: 'hasCompactKeyboard',
+ HEIGHT: 'height',
+ HEIGHT_IN_WEIGHT: 'heightInWeight',
+ HEIGHT_PERCENT: 'heightPercent',
+ HINT_TEXT: 'hintText',
+ ICON_CSS_CLASS: 'iconCssClass',
+ ID: 'id',
+ IS_GREY: 'isGrey',
+ LAYOUT: 'layout',
+ LAYOUT_ID: 'layoutID',
+ HEIGHT_PERCENT_OF_WIDTH: 'heightPercentOfWidth',
+ MARGIN_LEFT_PERCENT: 'marginLeftPercent',
+ MARGIN_RIGHT_PERCENT: 'marginRightPercent',
+ MINIMUM_HEIGHT: 'minimumHeight',
+ MORE_KEYS: 'moreKeys',
+ NO_SHIFT: 'noShift',
+ NUMBER_ROW_WEIGHT: 'numberRowWeight',
+ KEY_CODE: 'keyCode',
+ KEY_LIST: 'keyList',
+ MAPPING: 'mapping',
+ NAME: 'name',
+ ON_CONTEXT: 'onContext',
+ PADDING: 'padding',
+ RECORD: 'record',
+ SHOW_MENU_KEY: 'showMenuKey',
+ SUPPORT_STICKY: 'supportSticky',
+ SPEC: 'spec',
+ TEXT: 'text',
+ TEXT_CSS_CLASS: 'textCssClass',
+ TITLE: 'title',
+ TYPE: 'type',
+ TO_STATE: 'toState',
+ TO_KEYSET: 'toKeyset',
+ TO_KEYSET_NAME: 'toKeysetName',
+ WIDTH: 'width',
+ WIDTH_IN_WEIGHT: 'widthInWeight',
+ WIDTH_PERCENT: 'widthPercent',
+ IS_EMOTICON: 'isEmoticon',
+ MORE_KEYS_SHIFT_OPERATION: 'moreKeysShiftOperation'
+};
+
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/statemanager.js b/third_party/google_input_tools/src/chrome/os/inputview/statemanager.js
new file mode 100644
index 0000000..a82c6eb
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/statemanager.js
@@ -0,0 +1,233 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.StateManager');
+
+goog.require('i18n.input.chrome.inputview.Covariance');
+
+
+/**
+ * The state for the input view keyboard.
+ *
+ * @constructor
+ */
+i18n.input.chrome.inputview.StateManager = function() {
+ /**
+ * The state of the keyboard.
+ *
+ * @type {number}
+ * @private
+ */
+ this.state_ = 0;
+
+ /**
+ * The sticky state.
+ *
+ * @type {number}
+ * @private
+ */
+ this.sticky_ = 0;
+
+ /**
+ * Bits to indicate which state key is down.
+ *
+ * @type {number}
+ * @private
+ */
+ this.stateKeyDown_ = 0;
+
+ /**
+ * Bits to track which state is in chording.
+ *
+ * @type {number}
+ * @private
+ */
+ this.chording_ = 0;
+
+ /**
+ * Whether the current keyset is in English mode.
+ *
+ * @type {boolean}
+ */
+ this.isEnMode = false;
+
+ /** @type {!i18n.input.chrome.inputview.Covariance} */
+ this.covariance = new i18n.input.chrome.inputview.Covariance();
+};
+
+
+/**
+ * Sets a state to keydown.
+ *
+ * @param {!i18n.input.chrome.inputview.StateType} stateType The state type.
+ * @param {boolean} isKeyDown True if the state key is down.
+ */
+i18n.input.chrome.inputview.StateManager.prototype.setKeyDown = function(
+ stateType, isKeyDown) {
+ if (isKeyDown) {
+ this.stateKeyDown_ |= stateType;
+ } else {
+ this.stateKeyDown_ &= ~stateType;
+ this.chording_ &= ~stateType;
+ }
+};
+
+
+/**
+ * True if the key is down.
+ *
+ * @param {!i18n.input.chrome.inputview.StateType} stateType .
+ * @return {boolean} .
+ */
+i18n.input.chrome.inputview.StateManager.prototype.isKeyDown = function(
+ stateType) {
+ return (this.stateKeyDown_ & stateType) != 0;
+};
+
+
+/**
+ * Triggers chording and record it for each key-downed state.
+ */
+i18n.input.chrome.inputview.StateManager.prototype.triggerChording =
+ function() {
+ this.chording_ |= this.stateKeyDown_;
+};
+
+
+/**
+ * True if the state is chording now.
+ *
+ * @param {!i18n.input.chrome.inputview.StateType} stateType The state type.
+ * @return {boolean} True if the state is chording.
+ */
+i18n.input.chrome.inputview.StateManager.prototype.isChording = function(
+ stateType) {
+ return (this.chording_ & stateType) != 0;
+};
+
+
+/**
+ * Sets a state to be sticky.
+ *
+ * @param {!i18n.input.chrome.inputview.StateType} stateType The state type.
+ * @param {boolean} isSticky True to set it sticky.
+ */
+i18n.input.chrome.inputview.StateManager.prototype.setSticky = function(
+ stateType, isSticky) {
+ if (isSticky) {
+ this.sticky_ |= stateType;
+ } else {
+ this.sticky_ &= ~stateType;
+ }
+};
+
+
+/**
+ * Is a state sticky.
+ *
+ * @param {!i18n.input.chrome.inputview.StateType} stateType The state
+ * type.
+ * @return {boolean} True if it is sticky.
+ */
+i18n.input.chrome.inputview.StateManager.prototype.isSticky = function(
+ stateType) {
+ return (this.sticky_ & stateType) != 0;
+};
+
+
+/**
+ * Sets a state.
+ *
+ * @param {!i18n.input.chrome.inputview.StateType} stateType The state
+ * type.
+ * @param {boolean} enable True to enable the state.
+ */
+i18n.input.chrome.inputview.StateManager.prototype.setState = function(
+ stateType, enable) {
+ if (enable) {
+ this.state_ = this.state_ | stateType;
+ } else {
+ this.state_ = this.state_ & ~stateType;
+ }
+};
+
+
+/**
+ * Toggle the state.
+ *
+ * @param {!i18n.input.chrome.inputview.StateType} stateType The state
+ * type.
+ * @param {boolean} isSticky True to set it sticky.
+ */
+i18n.input.chrome.inputview.StateManager.prototype.toggleState = function(
+ stateType, isSticky) {
+ var enable = !this.hasState(stateType);
+ this.setState(stateType, enable);
+ isSticky = enable ? isSticky : false;
+ this.setSticky(stateType, isSticky);
+};
+
+
+/**
+ * Is the state on.
+ *
+ * @param {!i18n.input.chrome.inputview.StateType} stateType The state
+ * type.
+ * @return {boolean} True if the state is on.
+ */
+i18n.input.chrome.inputview.StateManager.prototype.hasState = function(
+ stateType) {
+ return (this.state_ & stateType) != 0;
+};
+
+
+/**
+ * Gets the state.
+ *
+ * @return {number} The state.
+ */
+i18n.input.chrome.inputview.StateManager.prototype.getState =
+ function() {
+ return this.state_;
+};
+
+
+/**
+ * Clears unsticky state.
+ *
+ */
+i18n.input.chrome.inputview.StateManager.prototype.clearUnstickyState =
+ function() {
+ this.state_ = this.state_ & this.sticky_;
+};
+
+
+/**
+ * True if there is unsticky state.
+ *
+ * @return {boolean} True if there is unsticky state.
+ */
+i18n.input.chrome.inputview.StateManager.prototype.hasUnStickyState =
+ function() {
+ return this.state_ != this.sticky_;
+};
+
+
+/**
+ * Resets the state.
+ *
+ */
+i18n.input.chrome.inputview.StateManager.prototype.reset = function() {
+ this.state_ = 0;
+ this.sticky_ = 0;
+};
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/statetype.js b/third_party/google_input_tools/src/chrome/os/inputview/statetype.js
new file mode 100644
index 0000000..bddf4b9
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/statetype.js
@@ -0,0 +1,30 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.StateType');
+
+
+/**
+ * The state.
+ *
+ * @enum {number}
+ */
+i18n.input.chrome.inputview.StateType = {
+ DEFAULT: 0,
+ SHIFT: 1,
+ ALTGR: 2,
+ CAPSLOCK: 4,
+ CTRL: 8,
+ ALT: 16
+};
+
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/statistics.js b/third_party/google_input_tools/src/chrome/os/inputview/statistics.js
new file mode 100644
index 0000000..d869b73
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/statistics.js
@@ -0,0 +1,147 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.Statistics');
+
+
+
+/**
+ * The statistics util class for input view.
+ *
+ * @constructor
+ */
+i18n.input.chrome.inputview.Statistics = function() {
+};
+goog.addSingletonGetter(i18n.input.chrome.inputview.Statistics);
+
+
+/**
+ * The current layout code.
+ *
+ * @type {string}
+ * @private
+ */
+i18n.input.chrome.inputview.Statistics.prototype.layoutCode_ = '';
+
+
+/**
+ * Sets the current layout code, which will be a part of the metrics name.
+ *
+ * @param {string} layoutCode .
+ */
+i18n.input.chrome.inputview.Statistics.prototype.setCurrentLayout = function(
+ layoutCode) {
+ this.layoutCode_ = layoutCode;
+};
+
+
+/**
+ * Gets the target type based on the given source and target.
+ *
+ * @param {string} source .
+ * @param {string} target .
+ * @return {number} The target type number value.
+ */
+i18n.input.chrome.inputview.Statistics.prototype.getTargetType = function(
+ source, target) {
+ if (source == target) {
+ return 0;
+ }
+ if (!source) {
+ return 3;
+ }
+ if (target.length > source.length) {
+ return 2;
+ }
+ return 1;
+};
+
+
+/**
+ * Records the metrics for each commit.
+ *
+ * @param {number} sourceLen The source length.
+ * @param {number} targetLen The target length.
+ * @param {number} targetIndex The target index.
+ * @param {number} targetType The target type:
+ * 0: Source; 1: Correction; 2: Completion; 3: Prediction.
+ * @param {number} triggerType The trigger type:
+ * 0: BySpace; 1: ByReset; 2: ByCandidate; 3: BySymbolOrNumber;
+ * 4: ByDoubleSpaceToPeriod.
+ */
+i18n.input.chrome.inputview.Statistics.prototype.recordCommit = function(
+ sourceLen, targetLen, targetIndex, targetType, triggerType) {
+ if (!this.layoutCode_) {
+ return;
+ }
+ this.recordCount_('Commit.SourceLength', sourceLen);
+ this.recordCount_('Commit.TargetLength', targetLen);
+ this.recordCount_('Commit.TargetIndex', targetIndex + 1); // 1-based index.
+ this.recordCount_('Commit.TargetType', targetType);
+ this.recordCount_('Commit.TriggerType', triggerType);
+};
+
+
+/**
+ * Records the VK show time for current keyboard layout.
+ *
+ * @param {number} showTimeSeconds The show time in seconds.
+ */
+i18n.input.chrome.inputview.Statistics.prototype.recordShowTime = function(
+ showTimeSeconds) {
+ this.recordCount_('Layout.ShowTime', showTimeSeconds);
+};
+
+
+/**
+ * Records the layout switching action.
+ *
+ * @param {string} layoutCode The layout code to be switched to.
+ */
+i18n.input.chrome.inputview.Statistics.prototype.recordSwitch = function(
+ layoutCode) {
+ this.recordCount_('Layout.SwitchTo.' + layoutCode, 1);
+};
+
+
+/**
+ * Records the special key action, which is function key like BACKSPACE, ENTER,
+ * etc..
+ *
+ * @param {string} keyCode .
+ * @param {boolean} isComposing Whether the key is pressed with composing mode.
+ */
+i18n.input.chrome.inputview.Statistics.prototype.recordSpecialKey = function(
+ keyCode, isComposing) {
+ var name = 'Key.' + isComposing ? 'Composing.' : '' + keyCode;
+ this.recordCount_(name, 1);
+};
+
+
+/**
+ * Records count value to chrome UMA framework.
+ *
+ * @param {string} name .
+ * @param {number} count .
+ * @private
+ */
+i18n.input.chrome.inputview.Statistics.prototype.recordCount_ = function(
+ name, count) {
+ if (!this.layoutCode_) {
+ return;
+ }
+ if (chrome.metricsPrivate && chrome.metricsPrivate.recordCount) {
+ name = 'InputView.' + name + '.' + this.layoutCode_;
+ chrome.metricsPrivate.recordCount(name, count);
+ }
+};
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/strokehandler.js b/third_party/google_input_tools/src/chrome/os/inputview/strokehandler.js
new file mode 100644
index 0000000..520b535
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/strokehandler.js
@@ -0,0 +1,252 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+
+/**
+ * @fileoverview Defines the class i18n.input.hwt.StrokeHandler.
+ * @author fengyuan@google.com (Feng Yuan)
+ */
+
+goog.provide('i18n.input.hwt.StrokeHandler');
+goog.provide('i18n.input.hwt.StrokeHandler.Point');
+goog.provide('i18n.input.hwt.StrokeHandler.StrokeEvent');
+
+
+goog.require('goog.events');
+goog.require('goog.events.Event');
+goog.require('goog.events.EventHandler');
+goog.require('goog.events.EventTarget');
+goog.require('goog.events.EventType');
+goog.require('goog.style');
+goog.require('goog.userAgent');
+goog.require('i18n.input.hwt.util');
+
+
+
+/**
+ * The handler for strokes.
+ *
+ * @param {!Element} canvas The handwriting canvas.
+ * @param {!Document} topDocument The top document.
+ * @constructor
+ * @extends {goog.events.EventTarget}
+ */
+i18n.input.hwt.StrokeHandler = function(canvas, topDocument) {
+ goog.base(this);
+
+ /**
+ * The event handler.
+ *
+ * @type {goog.events.EventHandler}
+ * @private
+ */
+ this.eventHandler_ = new goog.events.EventHandler(this);
+
+ /**
+ * Whether is drawing the stroke.
+ *
+ * @type {boolean}
+ */
+ this.drawing = false;
+
+ /**
+ * The canvas.
+ *
+ * @type {Element}
+ * @private
+ */
+ this.canvas_ = canvas;
+
+ // Always register mouse events. Some devices like Tablet PCs
+ // actually support both touch and mouse events, and the touch
+ // events don't get translated to mouse events on Tablet PCs.
+ this.eventHandler_.
+ listen(canvas, goog.events.EventType.MOUSEDOWN, this.onStrokeStart_).
+ listen(canvas, goog.events.EventType.MOUSEMOVE, this.onStroke_);
+ // Listen for touch events if they are supported.
+ if ('ontouchstart' in window) {
+ this.eventHandler_.
+ listen(canvas, goog.events.EventType.TOUCHSTART, this.onStrokeStart_).
+ listen(canvas, goog.events.EventType.TOUCHEND, this.onStrokeEnd_).
+ listen(canvas, goog.events.EventType.TOUCHCANCEL, this.onStrokeEnd_).
+ listen(canvas, goog.events.EventType.TOUCHMOVE, this.onStroke_);
+ }
+
+ i18n.input.hwt.util.listenPageEvent(this.eventHandler_, topDocument,
+ goog.events.EventType.MOUSEUP,
+ goog.bind(this.onStrokeEnd_, this));
+};
+goog.inherits(i18n.input.hwt.StrokeHandler, goog.events.EventTarget);
+
+
+/**
+ * Callback for the start of one stroke.
+ *
+ * @param {goog.events.BrowserEvent} e Event.
+ * @private
+ */
+i18n.input.hwt.StrokeHandler.prototype.onStrokeStart_ = function(e) {
+ this.drawing = true;
+ this.dispatchEvent(new i18n.input.hwt.StrokeHandler.StrokeEvent(
+ i18n.input.hwt.StrokeHandler.EventType.STROKE_START,
+ this.getPoint_(e)));
+ e.preventDefault();
+};
+
+
+/**
+ * Callback for the end of one stroke.
+ *
+ * @param {goog.events.BrowserEvent} e Event.
+ * @private
+ */
+i18n.input.hwt.StrokeHandler.prototype.onStrokeEnd_ = function(e) {
+ if (this.drawing) {
+ this.drawing = false;
+ this.dispatchEvent(new i18n.input.hwt.StrokeHandler.StrokeEvent(
+ i18n.input.hwt.StrokeHandler.EventType.STROKE_END,
+ this.getPoint_(e)));
+ e.preventDefault();
+ }
+};
+
+
+/**
+ * Callback for stroke.
+ *
+ * @param {goog.events.BrowserEvent} e Event.
+ * @private
+ */
+i18n.input.hwt.StrokeHandler.prototype.onStroke_ = function(e) {
+ if (this.drawing) {
+ this.dispatchEvent(new i18n.input.hwt.StrokeHandler.StrokeEvent(
+ i18n.input.hwt.StrokeHandler.EventType.STROKE,
+ this.getPoint_(e)));
+ }
+ e.preventDefault();
+};
+
+
+/**
+ * Given a mouse or touch event, figure out the coordinates where it occurred.
+ *
+ * @param {goog.events.BrowserEvent} e Event.
+ * @return {!i18n.input.hwt.StrokeHandler.Point} a point.
+ * @private
+ */
+i18n.input.hwt.StrokeHandler.prototype.getPoint_ = function(e) {
+ var pos = goog.style.getPageOffset(this.canvas_);
+ var nativeEvent = e.getBrowserEvent();
+ var x, y;
+ if (!goog.userAgent.IE && nativeEvent.pageX && nativeEvent.pageY) {
+ x = nativeEvent.pageX;
+ y = nativeEvent.pageY;
+ } else {
+ var scrollX = (document.dir == 'rtl' ? -1 : 1) * (
+ document.body.scrollLeft ||
+ document.documentElement.scrollLeft || 0);
+ var scrollY = document.body.scrollTop ||
+ document.documentElement.scrollTop || 0;
+ x = nativeEvent.clientX + scrollX;
+ y = nativeEvent.clientY + scrollY;
+ }
+ if (nativeEvent.touches != null && nativeEvent.touches.length > 0) {
+ x = nativeEvent.touches[0].clientX;
+ y = nativeEvent.touches[0].clientY;
+ }
+ return new i18n.input.hwt.StrokeHandler.Point(x - pos.x, y - pos.y,
+ goog.now());
+};
+
+
+/**
+ * Reset the drawing flag, in case the drawing canvas gets cleared while
+ * stroke is being drawn.
+ */
+i18n.input.hwt.StrokeHandler.prototype.reset = function() {
+ this.drawing = false;
+};
+
+
+/** @override */
+i18n.input.hwt.StrokeHandler.prototype.disposeInternal = function() {
+ goog.dispose(this.eventHandler_);
+ this.eventHandler_ = null;
+};
+
+
+
+/**
+ * One point in the stroke.
+ *
+ * @param {number} x The x.
+ * @param {number} y The y.
+ * @param {number} time The time in milisecond.
+ * @constructor
+ */
+i18n.input.hwt.StrokeHandler.Point = function(x, y, time) {
+ /**
+ * The left offset relative to the canvas.
+ *
+ * @type {number}
+ */
+ this.x = x;
+
+ /**
+ * The top offset relative to the canvas.
+ *
+ * @type {number}
+ */
+ this.y = y;
+
+ /**
+ * The time.
+ *
+ * @type {number}
+ */
+ this.time = time;
+};
+
+
+/**
+ * Stroke events.
+ *
+ * @enum {string}
+ */
+i18n.input.hwt.StrokeHandler.EventType = {
+ STROKE: goog.events.getUniqueId('s'),
+ STROKE_END: goog.events.getUniqueId('se'),
+ STROKE_START: goog.events.getUniqueId('ss')
+};
+
+
+
+/**
+ * The stroke event.
+ *
+ * @param {!i18n.input.hwt.StrokeHandler.EventType} type The event type.
+ * @param {!i18n.input.hwt.StrokeHandler.Point} point The point.
+ * @constructor
+ * @extends {goog.events.Event}
+ */
+i18n.input.hwt.StrokeHandler.StrokeEvent = function(type, point) {
+ goog.base(this, type);
+
+ /**
+ * The point.
+ *
+ * @type {!i18n.input.hwt.StrokeHandler.Point}
+ */
+ this.point = point;
+};
+goog.inherits(i18n.input.hwt.StrokeHandler.StrokeEvent, goog.events.Event);
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/swipedirection.js b/third_party/google_input_tools/src/chrome/os/inputview/swipedirection.js
new file mode 100644
index 0000000..9f466bb
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/swipedirection.js
@@ -0,0 +1,29 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.SwipeDirection');
+
+
+
+/**
+ * The swipe directin.
+ *
+ * @enum {number}
+ */
+i18n.input.chrome.inputview.SwipeDirection = {
+ UP: 0x1,
+ DOWN: 0x2,
+ LEFT: 0x4,
+ RIGHT: 0x8
+};
+
diff --git a/third_party/google_input_tools/src/chrome/os/inputview/util.js b/third_party/google_input_tools/src/chrome/os/inputview/util.js
new file mode 100644
index 0000000..d7667ea
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/inputview/util.js
@@ -0,0 +1,296 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.inputview.util');
+
+goog.require('goog.string');
+goog.require('goog.style');
+
+
+goog.scope(function() {
+var util = i18n.input.chrome.inputview.util;
+
+
+/**
+ * The mapping between the real character and its replacement for display.
+ *
+ * @type {!Object.<string, string>}
+ */
+util.DISPLAY_MAPPING = {
+ '\u0300' : '\u0060',
+ '\u0301' : '\u00B4',
+ '\u0302' : '\u02C6',
+ '\u0303' : '\u02DC',
+ '\u0304' : '\u02c9',
+ '\u0305' : '\u00AF',
+ '\u0306' : '\u02D8',
+ '\u0307' : '\u02D9',
+ '\u0308' : '\u00a8',
+ '\u0309' : '\u02C0',
+ '\u030A' : '\u02DA',
+ '\u030B' : '\u02DD',
+ '\u030C' : '\u02C7',
+ '\u030D' : '\u02C8',
+ '\u030E' : '\u0022',
+ '\u0327' : '\u00B8',
+ '\u0328' : '\u02DB',
+ '\u0345' : '\u037A',
+ '\u030F' : '\u030F\u0020',
+ '\u031B' : '\u031B\u0020',
+ '\u0323' : '\u0323\u0020'
+};
+
+
+/**
+ * The keysets using US keyboard layouts.
+ *
+ * @type {!Array.<string>}
+ */
+util.KEYSETS_USE_US = [
+ 'array',
+ 'cangjie',
+ 'dayi',
+ 'jp_us',
+ 'pinyin-zh-CN',
+ 'pinyin-zh-TW',
+ 'quick',
+ 't13n',
+ 'wubi',
+ 'zhuyin'
+];
+
+
+/**
+ * The keysets that have en switcher key.
+ *
+ * @type {!Array.<string>}
+ */
+util.KEYSETS_HAVE_EN_SWTICHER = [
+ // When other keysets that use us add the enswitcher key,
+ // should move them to this array.
+ 'pinyin-zh-CN'
+];
+
+
+/**
+ * A regular expression for the end of a sentence.
+ *
+ * @private {!RegExp}
+ */
+util.END_SENTENCE_REGEX_ = /[\.\?!] +$/;
+
+
+/**
+ * The regex of characters support dead key.
+ *
+ * @type {!RegExp}
+ * @private
+ */
+util.REGEX_CHARACTER_SUPPORT_DEADKEY_ =
+ /^[a-zA-ZæÆœŒΑΕΗΙΟΥΩαεηιυοωϒ]+$/;
+
+
+/**
+ * The regex of characters supported in language module.
+ *
+ * @type {!RegExp}
+ */
+util.REGEX_LANGUAGE_MODEL_CHARACTERS =
+ /(?=[^\u00d7\u00f7])[a-z\-\'\u00c0-\u017F]/i;
+
+
+/**
+ * Splits a value to pieces according to the weights.
+ *
+ * @param {!Array.<number>} weightArray The weight array.
+ * @param {number} totalValue The total value.
+ * @return {!Array.<number>} The splitted values.
+ */
+util.splitValue = function(weightArray, totalValue) {
+ if (weightArray.length == 0) {
+ return [];
+ }
+
+ if (weightArray.length == 1) {
+ return [totalValue];
+ }
+
+ var totalWeight = 0;
+ for (var i = 0; i < weightArray.length; i++) {
+ totalWeight += weightArray[i];
+ }
+ var tmp = totalValue / totalWeight;
+ var values = [];
+ var totalFlooredValue = 0;
+ var diffs = [];
+ for (var i = 0; i < weightArray.length; i++) {
+ var result = weightArray[i] * tmp;
+ values.push(result);
+ diffs.push(result - Math.floor(result));
+ totalFlooredValue += Math.floor(result);
+ }
+ var diff = totalValue - totalFlooredValue;
+
+ // Distributes the rest pixels to values who lose most.
+ for (var i = 0; i < diff; i++) {
+ var max = 0;
+ var index = 0;
+ for (var j = 0; j < diffs.length; j++) {
+ if (diffs[j] > max) {
+ max = diffs[j];
+ index = j;
+ }
+ }
+ values[index] += 1;
+ diffs[index] = 0;
+ }
+ for (var i = 0; i < values.length; i++) {
+ values[i] = Math.floor(values[i]);
+ }
+ return values;
+};
+
+
+/**
+ * Gets the value of a property.
+ *
+ * @param {Element} elem The element.
+ * @param {string} property The property name.
+ * @return {number} The value.
+ */
+util.getPropertyValue = function(elem, property) {
+ var value = goog.style.getComputedStyle(elem, property);
+ if (value) {
+ return parseInt(value.replace('px', ''), 10);
+ }
+ return 0;
+};
+
+
+/**
+ * To upper case.
+ *
+ * @param {string} character The character.
+ * @return {string} The uppercase of the character.
+ */
+util.toUpper = function(character) {
+ if (character == '\u00b5') {
+ return '\u005b5';
+ } else {
+ return character.toUpperCase();
+ }
+};
+
+
+/**
+ * To lower case.
+ *
+ * @param {string} character The character.
+ * @return {string} The lower case of the character.
+ */
+util.toLower = function(character) {
+ if (character == '\u0049') {
+ return '\u0131';
+ }
+ return character.toLowerCase();
+};
+
+
+/**
+ * Is this character trigger commit.
+ *
+ * @param {string} character The character.
+ * @return {boolean} True to trigger commit.
+ */
+util.isCommitCharacter = function(character) {
+ if (util.DISPLAY_MAPPING[character] ||
+ util.REGEX_LANGUAGE_MODEL_CHARACTERS.test(
+ character)) {
+ return false;
+ }
+
+ return true;
+};
+
+
+/**
+ * Some unicode character can't be shown in the web page, use a replacement
+ * instead.
+ *
+ * @param {string} invisibleCharacter The character can't be shown.
+ * @return {string} The replacement.
+ */
+util.getVisibleCharacter = function(invisibleCharacter) {
+ var map = util.DISPLAY_MAPPING;
+ if (map[invisibleCharacter]) {
+ return map[invisibleCharacter];
+ }
+ return invisibleCharacter;
+};
+
+
+/**
+ * Whether this is a letter key.
+ *
+ * @param {!Array.<string>} characters The characters.
+ * @return {boolean} True if this is a letter key.
+ */
+util.isLetterKey = function(characters) {
+ if (characters[1] == util.toUpper(
+ characters[0]) || characters[1] == util.
+ toLower(characters[0])) {
+ return true;
+ }
+ return false;
+};
+
+
+/**
+ * True if this character supports dead key combination.
+ *
+ * @param {string} character The character.
+ * @return {boolean} True if supports the dead key combination.
+ */
+util.supportDeadKey = function(character) {
+ return util.REGEX_CHARACTER_SUPPORT_DEADKEY_.
+ test(character);
+};
+
+
+/**
+ * True if we need to do the auto-capitalize.
+ *
+ * @param {string} text .
+ * @return {boolean} .
+ */
+util.needAutoCap = function(text) {
+ if (goog.string.isEmpty(text)) {
+ return false;
+ } else {
+ return util.END_SENTENCE_REGEX_.test(text);
+ }
+};
+
+
+/**
+ * Returns the configuration file name from the keyboard code.
+ *
+ * @param {string} keyboardCode The keyboard code.
+ * @return {string} The config file name which contains the keyset.
+ */
+util.getConfigName = function(keyboardCode) {
+ // Strips out all the suffixes in the keyboard code.
+ return keyboardCode.replace(/\..*$/, '');
+};
+
+}); // goog.scope
diff --git a/third_party/google_input_tools/src/chrome/os/keyboard/eventtype.js b/third_party/google_input_tools/src/chrome/os/keyboard/eventtype.js
new file mode 100644
index 0000000..9c0f4f0
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/keyboard/eventtype.js
@@ -0,0 +1,42 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+// Copyright 2013 The ChromeOS VK Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview The event types for keyboard.
+ */
+
+goog.provide('i18n.input.chrome.vk.EventType');
+
+
+/**
+ * The event types.
+ *
+ * @enum {string}
+ */
+i18n.input.chrome.vk.EventType = {
+ LAYOUT_LOADED: 'lld'
+};
diff --git a/third_party/google_input_tools/src/chrome/os/keyboard/keycode.js b/third_party/google_input_tools/src/chrome/os/keyboard/keycode.js
new file mode 100644
index 0000000..4ef33a3
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/keyboard/keycode.js
@@ -0,0 +1,86 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+// Copyright 2013 The ChromeOS VK Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Defines all the key codes and key codes map.
+ */
+
+goog.provide('i18n.input.chrome.vk.KeyCode');
+
+
+/**
+ * The standard 101 keyboard keys. Each char represent the key code of each key.
+ *
+ * @type {string}
+ */
+i18n.input.chrome.vk.KeyCode.CODES101 =
+ '\u00c01234567890\u00bd\u00bb' +
+ 'QWERTYUIOP\u00db\u00dd\u00dc' +
+ 'ASDFGHJKL\u00ba\u00de' +
+ 'ZXCVBNM\u00bc\u00be\u00bf' +
+ '\u0020';
+
+
+/**
+ * The standard 102 keyboard keys.
+ *
+ * @type {string}
+ */
+i18n.input.chrome.vk.KeyCode.CODES102 =
+ '\u00c01234567890\u00bd\u00bb' +
+ 'QWERTYUIOP\u00db\u00dd' +
+ 'ASDFGHJKL\u00ba\u00de\u00dc' +
+ '\u00e2ZXCVBNM\u00bc\u00be\u00bf' +
+ '\u0020';
+
+
+/**
+ * The standard 101 keyboard keys, including the BS/TAB/CAPS/ENTER/SHIFT/ALTGR.
+ * Each char represent the key code of each key.
+ *
+ * @type {string}
+ */
+i18n.input.chrome.vk.KeyCode.ALLCODES101 =
+ '\u00c01234567890\u00bd\u00bb\u0008' +
+ '\u0009QWERTYUIOP\u00db\u00dd\u00dc' +
+ '\u0014ASDFGHJKL\u00ba\u00de\u000d' +
+ '\u0010ZXCVBNM\u00bc\u00be\u00bf\u0010' +
+ '\u0111\u0020\u0111';
+
+
+/**
+ * The standard 102 keyboard keys, including the BS/TAB/CAPS/ENTER/SHIFT/ALTGR.
+ * Each char represent the key code of each key.
+ *
+ * @type {string}
+ */
+i18n.input.chrome.vk.KeyCode.ALLCODES102 =
+ '\u00c01234567890\u00bd\u00bb\u0008' +
+ '\u0009QWERTYUIOP\u00db\u00dd\u000d' +
+ '\u0014ASDFGHJKL\u00ba\u00de\u00dc\u000d' +
+ '\u0010\u00e2ZXCVBNM\u00bc\u00be\u00bf\u0010' +
+ '\u0111\u0020\u0111';
diff --git a/third_party/google_input_tools/src/chrome/os/keyboard/layoutevent.js b/third_party/google_input_tools/src/chrome/os/keyboard/layoutevent.js
new file mode 100644
index 0000000..2bc0a13
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/keyboard/layoutevent.js
@@ -0,0 +1,64 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+// Copyright 2013 The ChromeOS VK Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview The definition of layout event.
+ */
+
+goog.provide('i18n.input.chrome.vk.LayoutEvent');
+
+goog.require('goog.events.Event');
+
+
+
+/**
+ * The Layout Event.
+ *
+ * @param {i18n.input.chrome.vk.EventType} type The event type.
+ * @param {Object} layoutView The layoutView object. See ParsedLayout for
+ * details.
+ * @constructor
+ * @extends {goog.events.Event}
+ */
+i18n.input.chrome.vk.LayoutEvent = function(type, layoutView) {
+ goog.base(this, type);
+
+ /**
+ * The layout view object.
+ *
+ * @type {Object}
+ */
+ this.layoutView = layoutView;
+
+ /**
+ * The layout code.
+ *
+ * @type {?string}
+ */
+ this.layoutCode = layoutView ? layoutView.id : null;
+};
+goog.inherits(i18n.input.chrome.vk.LayoutEvent, goog.events.Event);
diff --git a/third_party/google_input_tools/src/chrome/os/keyboard/model.js b/third_party/google_input_tools/src/chrome/os/keyboard/model.js
new file mode 100644
index 0000000..3f940ed
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/keyboard/model.js
@@ -0,0 +1,424 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+// Copyright 2013 The ChromeOS VK Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Definition of Model class.
+ * It is responsible for dynamically loading the layout JS files. It
+ * interprets the layout info and provides the function of getting
+ * transformed chars and recording history states to Model.
+ * It notifies View via events when layout info changes.
+ * This is the Model of MVC pattern.
+ */
+
+goog.provide('i18n.input.chrome.vk.Model');
+
+goog.require('goog.events.EventTarget');
+goog.require('goog.net.jsloader');
+goog.require('goog.object');
+goog.require('goog.string');
+goog.require('i18n.input.chrome.vk.EventType');
+goog.require('i18n.input.chrome.vk.LayoutEvent');
+goog.require('i18n.input.chrome.vk.ParsedLayout');
+
+
+
+/**
+ * Creates the Model object.
+ *
+ * @constructor
+ * @extends {goog.events.EventTarget}
+ */
+i18n.input.chrome.vk.Model = function() {
+ goog.base(this);
+
+ /**
+ * The registered layouts object.
+ * Its format is {<layout code>: <parsed layout obj>}.
+ *
+ * @type {!Object.<!i18n.input.chrome.vk.ParsedLayout|boolean>}
+ * @private
+ */
+ this.layouts_ = {};
+
+ /**
+ * The active layout code.
+ *
+ * @type {string}
+ * @private
+ */
+ this.activeLayout_ = '';
+
+ /**
+ * The layout code of which the layout is "being activated" when the layout
+ * hasn't been loaded yet.
+ *
+ * @type {string}
+ * @private
+ */
+ this.delayActiveLayout_ = '';
+
+ /**
+ * History state used for ambiguous transforms.
+ *
+ * @type {!Object}
+ * @private
+ */
+ this.historyState_ = {
+ previous: {text: '', transat: -1},
+ ambi: '',
+ current: {text: '', transat: -1}
+ };
+
+ // Exponses the onLayoutLoaded so that the layout JS can call it back.
+ goog.exportSymbol('cros_vk_loadme', goog.bind(this.onLayoutLoaded_, this));
+};
+goog.inherits(i18n.input.chrome.vk.Model, goog.events.EventTarget);
+
+
+/**
+ * Loads the layout in the background.
+ *
+ * @param {string} layoutCode The layout will be loaded.
+ */
+i18n.input.chrome.vk.Model.prototype.loadLayout = function(layoutCode) {
+ if (!layoutCode) return;
+
+ var parsedLayout = this.layouts_[layoutCode];
+ // The layout is undefined means not loaded, false means loading.
+ if (parsedLayout == undefined) {
+ this.layouts_[layoutCode] = false;
+ i18n.input.chrome.vk.Model.loadLayoutScript_(layoutCode);
+ } else if (parsedLayout) {
+ this.dispatchEvent(new i18n.input.chrome.vk.LayoutEvent(
+ i18n.input.chrome.vk.EventType.LAYOUT_LOADED,
+ /** @type {!Object} */ (parsedLayout)));
+ }
+};
+
+
+/**
+ * Activate layout by setting the current layout.
+ *
+ * @param {string} layoutCode The layout will be set as current layout.
+ */
+i18n.input.chrome.vk.Model.prototype.activateLayout = function(
+ layoutCode) {
+ if (!layoutCode) return;
+
+ if (this.activeLayout_ != layoutCode) {
+ var parsedLayout = this.layouts_[layoutCode];
+ if (parsedLayout) {
+ this.activeLayout_ = layoutCode;
+ this.delayActiveLayout_ = '';
+ this.clearHistory();
+ } else if (parsedLayout == false) { // Layout being loaded?
+ this.delayActiveLayout_ = layoutCode;
+ }
+ }
+};
+
+
+/**
+ * Gets the current layout.
+ *
+ * @return {string} The current layout code.
+ */
+i18n.input.chrome.vk.Model.prototype.getCurrentLayout = function() {
+ return this.activeLayout_;
+};
+
+
+/**
+ * Predicts whether there would be future transforms for the history text.
+ *
+ * @return {number} The matched position. Returns -1 for no match.
+ */
+i18n.input.chrome.vk.Model.prototype.predictHistory = function() {
+ if (!this.activeLayout_ || !this.layouts_[this.activeLayout_]) {
+ return -1;
+ }
+ var parsedLayout = this.layouts_[this.activeLayout_];
+ var history = this.historyState_;
+ var text, transat;
+ if (history.ambi) {
+ text = history.previous.text;
+ transat = history.previous.transat;
+ // Tries to predict transform for previous history.
+ if (transat > 0) {
+ text = text.slice(0, transat) + '\u001d' + text.slice(transat) +
+ history.ambi;
+ } else {
+ text += history.ambi;
+ }
+ if (parsedLayout.predictTransform(text) >= 0) {
+ // If matched previous history, always return 0 because outside will use
+ // this to keep the composition text.
+ return 0;
+ }
+ }
+ // Tries to predict transform for current history.
+ text = history.current.text;
+ transat = history.current.transat;
+ if (transat >= 0) {
+ text = text.slice(0, transat) + '\u001d' + text.slice(transat);
+ }
+ var pos = parsedLayout.predictTransform(text);
+ if (transat >= 0 && pos > transat) {
+ // Adjusts the pos for removing the temporary \u001d character.
+ pos--;
+ }
+ return pos;
+};
+
+
+/**
+ * Translates the key code into the chars to put into the active input box.
+ *
+ * @param {string} chars The key commit chars.
+ * @param {string} charsBeforeCaret The chars before the caret in the active
+ * input box. This will be used to compare with the history states.
+ * @return {Object} The replace chars object whose 'back' means delete how many
+ * chars back from the caret, and 'chars' means the string insert after the
+ * deletion. Returns null if no result.
+ */
+i18n.input.chrome.vk.Model.prototype.translate = function(
+ chars, charsBeforeCaret) {
+ if (!this.activeLayout_ || !chars) {
+ return null;
+ }
+ var parsedLayout = this.layouts_[this.activeLayout_];
+ if (!parsedLayout) {
+ return null;
+ }
+
+ this.matchHistory_(charsBeforeCaret);
+ var result, history = this.historyState_;
+ if (history.ambi) {
+ // If ambi is not empty, it means some ambi chars has been typed
+ // before. e.g. ka->k, kaa->K, typed 'ka', and now typing 'a':
+ // history.previous == 'k',1
+ // history.current == 'k',1
+ // history.ambi == 'a'
+ // So now we should get transform of 'k\u001d' + 'aa'.
+ result = parsedLayout.transform(
+ history.previous.text, history.previous.transat,
+ history.ambi + chars);
+ // Note: result.back could be negative number. In such case, we should give
+ // up the transform result. This is to be compatible the old vk behaviors.
+ if (result && result.back < 0) {
+ result = null;
+ }
+ }
+ if (result) {
+ // Because the result is related to previous history, adjust the result so
+ // that it is related to current history.
+ var prev = history.previous.text;
+ prev = prev.slice(0, prev.length - result.back);
+ prev += result.chars;
+ result.back = history.current.text.length;
+ result.chars = prev;
+ } else {
+ // If no ambi chars or no transforms for ambi chars, try to match the
+ // regular transforms. In above case, if now typing 'b', we should get
+ // transform of 'k\u001d' + 'b'.
+ result = parsedLayout.transform(
+ history.current.text, history.current.transat, chars);
+ }
+ // Updates the history state.
+ if (parsedLayout.isAmbiChars(history.ambi + chars)) {
+ if (!history.ambi) {
+ // Empty ambi means chars should be the first ambi chars.
+ // So now we should set the previous.
+ history.previous = goog.object.clone(history.current);
+ }
+ history.ambi += chars;
+ } else if (parsedLayout.isAmbiChars(chars)) {
+ // chars could match ambi regex when ambi+chars cannot.
+ // In this case, record the current history to previous, and set ambi as
+ // chars.
+ history.previous = goog.object.clone(history.current);
+ history.ambi = chars;
+ } else {
+ history.previous.text = '';
+ history.previous.transat = -1;
+ history.ambi = '';
+ }
+ // Updates the history text per transform result.
+ var text = history.current.text;
+ var transat = history.current.transat;
+ if (result) {
+ text = text.slice(0, text.length - result.back);
+ text += result.chars;
+ transat = text.length;
+ } else {
+ text += chars;
+ // This function doesn't return null. So if result is null, fill it.
+ result = {back: 0, chars: chars};
+ }
+ // The history text cannot cannot contain SPACE!
+ var spacePos = text.lastIndexOf(' ');
+ if (spacePos >= 0) {
+ text = text.slice(spacePos + 1);
+ if (transat > spacePos) {
+ transat -= spacePos + 1;
+ } else {
+ transat = -1;
+ }
+ }
+ history.current.text = text;
+ history.current.transat = transat;
+
+ return result;
+};
+
+
+/**
+ * Wether the active layout has transforms defined.
+ *
+ * @return {boolean} True if transforms defined, false otherwise.
+ */
+i18n.input.chrome.vk.Model.prototype.hasTransforms = function() {
+ var parsedLayout = this.layouts_[this.activeLayout_];
+ return !!parsedLayout && !!parsedLayout.transforms;
+};
+
+
+/**
+ * Processes the backspace key. It affects the history state.
+ *
+ * @param {string} charsBeforeCaret The chars before the caret in the active
+ * input box. This will be used to compare with the history states.
+ */
+i18n.input.chrome.vk.Model.prototype.processBackspace = function(
+ charsBeforeCaret) {
+ this.matchHistory_(charsBeforeCaret);
+
+ var history = this.historyState_;
+ // Reverts the current history. If the backspace across over the transat pos,
+ // clean it up.
+ var text = history.current.text;
+ if (text) {
+ text = text.slice(0, text.length - 1);
+ history.current.text = text;
+ if (history.current.transat > text.length) {
+ history.current.transat = text.length;
+ }
+
+ text = history.ambi;
+ if (text) { // If there is ambi text, remove the last char in ambi.
+ history.ambi = text.slice(0, text.length - 1);
+ }
+ // Prev history only exists when ambi is not empty.
+ if (!history.ambi) {
+ history.previous = {text: '', transat: -1};
+ }
+ } else {
+ // Cleans up the previous history.
+ history.previous = {text: '', transat: -1};
+ history.ambi = '';
+ // Cleans up the current history.
+ history.current = goog.object.clone(history.previous);
+ }
+};
+
+
+/**
+ * Callback when layout loaded.
+ *
+ * @param {!Object} layout The layout object passed from the layout JS's loadme
+ * callback.
+ * @private
+ */
+i18n.input.chrome.vk.Model.prototype.onLayoutLoaded_ = function(layout) {
+ var parsedLayout = new i18n.input.chrome.vk.ParsedLayout(layout);
+ if (parsedLayout.id) {
+ this.layouts_[parsedLayout.id] = parsedLayout;
+ }
+ if (this.delayActiveLayout_ == layout.id) {
+ this.activateLayout(this.delayActiveLayout_);
+ this.delayActiveLayout_ = '';
+ }
+ this.dispatchEvent(new i18n.input.chrome.vk.LayoutEvent(
+ i18n.input.chrome.vk.EventType.LAYOUT_LOADED, parsedLayout));
+};
+
+
+/**
+ * Matches the given text to the last transformed text. Clears history if they
+ * are not matched.
+ *
+ * @param {string} text The text to be matched.
+ * @private
+ */
+i18n.input.chrome.vk.Model.prototype.matchHistory_ = function(text) {
+ var hisText = this.historyState_.current.text;
+ if (!hisText || !text || !(goog.string.endsWith(text, hisText) ||
+ goog.string.endsWith(hisText, text))) {
+ this.clearHistory();
+ }
+};
+
+
+/**
+ * Clears the history state.
+ */
+i18n.input.chrome.vk.Model.prototype.clearHistory = function() {
+ this.historyState_.ambi = '';
+ this.historyState_.previous = {text: '', transat: -1};
+ this.historyState_.current = goog.object.clone(this.historyState_.previous);
+};
+
+
+/**
+ * Prunes the history state to remove a number of chars at beginning.
+ *
+ * @param {number} count The count of chars to be removed.
+ */
+i18n.input.chrome.vk.Model.prototype.pruneHistory = function(count) {
+ var pruneFunc = function(his) {
+ his.text = his.text.slice(count);
+ if (his.transat > 0) {
+ his.transat -= count;
+ if (his.transat <= 0) {
+ his.transat = -1;
+ }
+ }
+ };
+ pruneFunc(this.historyState_.previous);
+ pruneFunc(this.historyState_.current);
+};
+
+
+/**
+ * Loads the script for a layout.
+ *
+ * @param {string} layoutCode The layout code.
+ * @private
+ */
+i18n.input.chrome.vk.Model.loadLayoutScript_ = function(layoutCode) {
+ goog.net.jsloader.load('layouts/' + layoutCode + '.js');
+};
diff --git a/third_party/google_input_tools/src/chrome/os/keyboard/parsedlayout.js b/third_party/google_input_tools/src/chrome/os/keyboard/parsedlayout.js
new file mode 100644
index 0000000..d414bea
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/keyboard/parsedlayout.js
@@ -0,0 +1,423 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+// Copyright 2013 The ChromeOS VK Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Defines the parsed layout object which will do layout parsing
+ * and expose the keymappings and the transforms to Model.
+ */
+
+goog.provide('i18n.input.chrome.vk.ParsedLayout');
+
+goog.require('goog.object');
+goog.require('i18n.input.chrome.vk.KeyCode');
+
+
+
+/**
+ * Creates the parsed layout object per the raw layout info.
+ *
+ * @param {!Object} layout The raw layout object defined in the
+ * xxx_layout.js.
+ * @constructor
+ */
+i18n.input.chrome.vk.ParsedLayout = function(layout) {
+ /**
+ * The layout code (a.k.a. id).
+ *
+ * @type {string}
+ */
+ this.id = layout['id'];
+
+ /**
+ * The view object needed by UI rendering, including the key
+ * mappings. Some extra keys are not appear in following, which are
+ * '', 's', 'l', 'sl', 'cl', 'sc', 'scl'. They define the key mappings
+ * for each keyboard mode:
+ * '' means normal;
+ * 's' means SHIFT;
+ * 'l' means CAPSLOCK;
+ * 'c' means CTRL+ALT.
+ * Those modes will be filled when parsing the raw layout.
+ * If certain modes are not defined by the raw layout, this.view.<mode>
+ * won't be filled in.
+ * The mode format is: {
+ * '<keyChar>': ['<disp type(S|P)>', '<disp chars>', '<commit chars>']
+ * }.
+ *
+ * @type {!Object}
+ */
+ this.view = {
+ 'id': layout['id'],
+ 'title': layout['title'],
+ 'isRTL': layout['direction'] == 'rtl',
+ 'is102': !!layout['is102Keyboard'],
+ 'mappings': goog.object.create([
+ '', null,
+ 's', null,
+ 'c', null,
+ 'l', null,
+ 'sc', null,
+ 'cl', null,
+ 'sl', null,
+ 'scl', null
+ ])
+ };
+
+ /**
+ * The parsed layout transforms. There are only 3 elements of this array.
+ * !st is the long exgexp to match, 2nd is the map of:
+ * <match location>: [<regexp>, <replacement>].
+ * 3rd/4th are the regexp for prefix matches.
+ *
+ * @type {Array.<!Object>}
+ */
+ this.transforms = null;
+
+ /**
+ * The parsed layout ambiguous chars.
+ *
+ * @type {Object}
+ * @private
+ */
+ this.ambiRegex_ = null;
+
+ // Parses the key mapping & transforms of the layout.
+ this.parseKeyMappings_(layout);
+ this.parseTransforms_(layout);
+};
+
+
+/**
+ * Parses the key mappings of the given layout.
+ *
+ * @param {!Object} layout The raw layout object. It's format is:
+ * id: <layout id> in {string}
+ * title: <layout title> in {string}
+ * direction: 'rtl' or 'ltr'
+ * is102Keyboard: True if vk is 102, False/undefined for 101
+ * mappings: key map in {Object.<string,string>}
+ * '': keycodes (each char's charCode represents keycode) in normal state
+ * s: keycodes in SHIFT state
+ * c: keycodes in ALTGR state
+ * l: keycodes in CAPSLOCK state
+ * <the states could be combined, e.g. ',s,sc,sl,scl'>
+ * transform: in {Object.<string,string>}
+ * <regexp>: <replacement>
+ * historyPruneRegex: <regexp string to represent the ambiguities>.
+ * @private
+ */
+i18n.input.chrome.vk.ParsedLayout.prototype.parseKeyMappings_ = function(
+ layout) {
+ var codes = this.view['is102'] ? i18n.input.chrome.vk.KeyCode.CODES102 :
+ i18n.input.chrome.vk.KeyCode.CODES101;
+
+ var mappings = layout['mappings'];
+ for (var m in mappings) {
+ var map = mappings[m];
+ var modes = m.split(/,/);
+ if (modes.join(',') != m) {
+ modes.push(''); // IE splits 'a,b,' into ['a','b']
+ }
+ var parsed = {};
+ // Example for map is like:
+ // 1) {'': '\u00c0123456...', ...}
+ // 2) {'QWERT': 'QWERT', ...}
+ // 3) {'A': 'aa', ...}
+ // 4) {'BCD': '{{bb}}cd', ...}
+ // 5) {'EFG': '{{S||e||ee}}FG', ...}
+ // 6) {'HI': '{{P||12||H}}i', ...}
+ for (var from in map) {
+ // In case #1, from is '', to is '\u00c0123456...'.
+ // In case #3, from is 'A', to is 'aa'.
+ var to = map[from];
+ if (from == '') {
+ from = codes;
+ // If is 102 keyboard, modify 'to' to be compatible with the old vk.
+ if (this.view['is102']) {
+ // Moves the 26th char {\} to be the 38th char (after {'}).
+ var normalizedTo = to.slice(0, 25);
+ normalizedTo += to.slice(26, 37);
+ normalizedTo += to.charAt(25);
+ normalizedTo += to.slice(37);
+ to = normalizedTo;
+ }
+ }
+ // Replaces some chars for backward compatibility to old layout
+ // definitions.
+ from = from.replace('m', '\u00bd');
+ from = from.replace('=', '\u00bb');
+ from = from.replace(';', '\u00ba');
+ if (from.length == 1) {
+ // Case #3: single char map to chars.
+ parsed[from] = ['S', to, to];
+ } else {
+ var j = 0;
+ for (var i = 0, c; c = from.charAt(i); ++i) {
+ var t = to.charAt(j++);
+ if (t == to.charAt(j) && t == '{') {
+ // Case #4/5/6: {{}} to define single char map to chars.
+ var k = to.indexOf('}}', j);
+ if (k < j) break;
+ var s = to.slice(j + 1, k);
+ var parts = s.split('||');
+ if (parts.length == 3) {
+ // Case #5/6: button/commit chars seperation.
+ parsed[c] = parts;
+ } else if (parts.length == 1) {
+ // Case #4.
+ parsed[c] = ['S', s, s];
+ }
+ j = k + 2;
+ } else {
+ // Normal case: single char map to according single char.
+ parsed[c] = ['S', t, t];
+ }
+ }
+ }
+ }
+ for (var i = 0, mode; mode = modes[i], mode != undefined; ++i) {
+ this.view['mappings'][mode] = parsed;
+ }
+ }
+};
+
+
+/**
+ * Prefixalizes the regexp string.
+ *
+ * @param {string} re_str The original regexp string.
+ * @return {string} The prefixalized the regexp string.
+ * @private
+ */
+i18n.input.chrome.vk.ParsedLayout.prototype.prefixalizeRegexString_ = function(
+ re_str) {
+ // Makes sure [...\[\]...] won't impact the later replaces.
+ re_str = re_str.replace(/\\./g, function(m) {
+ if (/^\\\[/.test(m)) {
+ return '\u0001';
+ }
+ if (/^\\\]/.test(m)) {
+ return '\u0002';
+ }
+ return m;
+ });
+ // Prefixalizes.
+ re_str = re_str.replace(/\\.|\[[^\[\]]*\]|\{.*\}|[^\|\\\(\)\[\]\{\}\*\+\?]/g,
+ function(m) {
+ if (/^\{/.test(m)) {
+ return m;
+ }
+ return '(?:' + m + '|$)';
+ });
+ // Restores the \[\].
+ re_str = re_str.replace(/\u0001/g, '\\[');
+ re_str = re_str.replace(/\u0002/g, '\\]');
+ return re_str;
+};
+
+
+/**
+ * Parses the transforms of the given layout.
+ *
+ * @param {!Object} layout The raw layout object. It's format is:
+ * id: <layout id> in {string}
+ * title: <layout title> in {string}
+ * direction: 'rtl' or 'ltr'
+ * is102Keyboard: True if vk is 102, False/undefined for 101
+ * mappings: key map in {Object.<string,string>}
+ * '': keycodes (each char's charCode represents keycode) in normal state
+ * s: keycodes in SHIFT state
+ * c: keycodes in ALTGR state
+ * l: keycodes in CAPSLOCK state
+ * <the states could be combined, e.g. ',s,sc,sl,scl'>
+ * transform: in {Object.<string,string>}
+ * <regexp>: <replacement>
+ * historyPruneRegex: <regexp string to represent the ambiguities>.
+ * @private
+ */
+i18n.input.chrome.vk.ParsedLayout.prototype.parseTransforms_ = function(
+ layout) {
+ var transforms = layout['transform'];
+ if (transforms) {
+ // regobjs is RegExp objects of the regexp string.
+ // regexsalone will be used to get the long regexp which concats all the
+ // transform regexp as (...$)|(...$)|...
+ // The long regexp is needed because it is ineffecient to match each regexp
+ // one by one. Instead, we match the long regexp only once. But we need to
+ // know where the match happens and which replacement we need to use.
+ // So regobjs will hold the map between the match location and the
+ // regexp/replacement.
+ var regobjs = [], regexesalone = [], partialRegexs = [];
+ // sum_numgrps is the index of current reg group for future matching.
+ // Don't care about the whole string in array index 0.
+ var sum_numgrps = 1;
+ for (var regex in transforms) {
+ var regobj = new RegExp(regex + '$');
+ var repl = transforms[regex];
+ regobjs[sum_numgrps] = [regobj, repl];
+ regexesalone.push('(' + regex + '$)');
+ partialRegexs.push('^(' + this.prefixalizeRegexString_(regex) + ')');
+ // The match should happen to count braces.
+ var grpCountRegexp = new RegExp(regex + '|.*');
+ // The length attribute would count whole string as well.
+ // However, that extra count 1 is compensated by
+ // extra braces added.
+ var numgrps = grpCountRegexp.exec('').length;
+ sum_numgrps += numgrps;
+ }
+ var longregobj = new RegExp(regexesalone.join('|'));
+ // Saves 2 long regexp objects for later prefix matching.
+ // The reason to save a regexp with '\u0001' is to make sure the whole
+ // string won't match as a prefix for the whole pattern. For example,
+ // 'abc' shouldn't match /abc/.
+ // In above case, /abc/ is prefixalized as re = /(a|$)(b|$)(c|$)/.
+ // 'a', 'ab' & 'abc' can all match re.
+ // So make another re2 = /(a|$)(b|$)(c|$)\u0001/, therefore, 'abc' will
+ // fail to match. Finally, we can use this checks to make sure the prefix
+ // match: "s matches re but it doesn't match re2".
+ var prefixregobj = new RegExp(partialRegexs.join('|'));
+ // Uses reverse-ordered regexp for prefix matching. Details are explained
+ // in predictTransform().
+ var prefixregobj2 = new RegExp(partialRegexs.reverse().join('|'));
+ this.transforms = [longregobj, regobjs, prefixregobj, prefixregobj2];
+ }
+
+ var hisPruReg = layout['historyPruneRegex'];
+ if (hisPruReg) {
+ this.ambiRegex_ = new RegExp('^(' + hisPruReg + ')$');
+ }
+};
+
+
+/**
+ * Predicts whether there would be future transforms for the given string.
+ *
+ * @param {string} text The given string.
+ * @return {number} The matched position in the string. Returns -1 for no match.
+ */
+i18n.input.chrome.vk.ParsedLayout.prototype.predictTransform = function(text) {
+ if (!this.transforms || !text) {
+ return -1;
+ }
+ for (var i = 0; i < text.length; i++) {
+ var s = text.slice(i - text.length);
+ // Uses multiple mathches to make sure the prefix match.
+ // Refers to comments in parseTransforms_() method.
+ var matches = s.match(this.transforms[2]);
+ if (matches && matches[0]) {
+ for (var j = 1; j < matches.length && !matches[j]; j++) {}
+ var matchedIndex = j;
+ // Ties to match the reversed regexp and see whether the matched indexes
+ // are pointed to the same rule.
+ matches = s.match(this.transforms[3]);
+ if (matches && matches[0]) { // This should always match!
+ for (var j = 1; j < matches.length && !matches[j]; j++) {}
+ if (matchedIndex != matches.length - j) {
+ // If the matched and reverse-matched index are not the same, it
+ // means the string must be a prefix, because the layout transforms
+ // shouldn't have duplicated transforms.
+ return i;
+ } else {
+ // Gets the matched rule regexp, and revise it to add a never-matched
+ // char X in the end. And tries to match it with s+X.
+ // If matched, it means the s is a full match instead of a prefix
+ // match.
+ var re = this.transforms[1][matchedIndex][0];
+ re = new RegExp(re.toString().match(/\/(.*)\//)[1] + '\u0001');
+ if (!(s + '\u0001').match(re)) {
+ return i;
+ }
+ }
+ }
+ }
+ }
+ return -1;
+};
+
+
+/**
+ * Applies the layout transform and gets the result.
+ *
+ * @param {string} prevstr The previous text.
+ * @param {number} transat The position of previous transform. If it's -1,
+ * it means no transform happened.
+ * @param {string} ch The new chars currently added to prevstr.
+ * @return {Object} The transform result. It's format is:
+ * {back: <the number of chars to be deleted in the end of the prevstr>,
+ * chars: <the chars to add at the tail after the deletion>}.
+ * If there is no transform applies, return null.
+ */
+i18n.input.chrome.vk.ParsedLayout.prototype.transform = function(
+ prevstr, transat, ch) {
+ if (!this.transforms) return null;
+
+ var str;
+ if (transat > 0) {
+ str = prevstr.slice(0, transat) + '\u001d' +
+ prevstr.slice(transat) + ch;
+ } else {
+ str = prevstr + ch;
+ }
+ var longr = this.transforms[0];
+ var matchArr = longr.exec(str);
+ if (matchArr) {
+ var rs = this.transforms[1];
+
+ for (var i = 1; i < matchArr.length && !matchArr[i]; i++) {}
+ var matchGroup = i;
+
+ var regobj = rs[matchGroup][0];
+ var repl = rs[matchGroup][1];
+ var m = regobj.exec(str);
+
+ // String visible to user does not have LOOK_BEHIND_SEP_ and chars.
+ // So need to discount them in backspace count.
+ var rmstr = str.slice(m.index);
+ var numseps = rmstr.search('\u001d') > -1 ? 1 : 0;
+ var backlen = rmstr.length - numseps - ch.length;
+
+ var newstr = str.replace(regobj, repl);
+ var replstr = newstr.slice(m.index);
+ replstr = replstr.replace('\u001d', '');
+
+ return {back: backlen, chars: replstr};
+ }
+
+ return null;
+};
+
+
+/**
+ * Gets whether the given chars is ambiguious chars.
+ *
+ * @param {string} chars The chars to be judged.
+ * @return {boolean} True if given chars is ambiguious chars, false
+ * otherwise.
+ */
+i18n.input.chrome.vk.ParsedLayout.prototype.isAmbiChars = function(chars) {
+ return this.ambiRegex_ ? !!this.ambiRegex_.exec(chars) : false;
+};
diff --git a/third_party/google_input_tools/src/chrome/os/message/contenttype.js b/third_party/google_input_tools/src/chrome/os/message/contenttype.js
new file mode 100644
index 0000000..0a50da3
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/message/contenttype.js
@@ -0,0 +1,31 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.message.ContextType');
+
+
+/**
+ * The message type.
+ *
+ * @enum {string}
+ */
+i18n.input.chrome.message.ContextType = {
+ DEFAULT: 'text',
+ EMAIL: 'email',
+ PASSWORD: 'password',
+ URL: 'url',
+ NUMBER: 'number'
+};
+
+
+
diff --git a/third_party/google_input_tools/src/chrome/os/message/event.js b/third_party/google_input_tools/src/chrome/os/message/event.js
new file mode 100644
index 0000000..7972399
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/message/event.js
@@ -0,0 +1,40 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.message.Event');
+
+goog.require('goog.events.Event');
+
+
+
+/**
+ * The message event.
+ *
+ * @param {i18n.input.chrome.message.Type} type .
+ * @param {Object} msg .
+ * @param {Function=} opt_sendResponse .
+ * @constructor
+ * @extends {goog.events.Event}
+ */
+i18n.input.chrome.message.Event = function(type, msg, opt_sendResponse) {
+ goog.base(this, type);
+
+ /** @type {Object} */
+ this.msg = msg;
+
+ /** @type {Function} */
+ this.sendResponse = opt_sendResponse || null;
+};
+goog.inherits(i18n.input.chrome.message.Event,
+ goog.events.Event);
+
diff --git a/third_party/google_input_tools/src/chrome/os/message/name.js b/third_party/google_input_tools/src/chrome/os/message/name.js
new file mode 100644
index 0000000..efab8df
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/message/name.js
@@ -0,0 +1,62 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.message.Name');
+
+/**
+ * The message attribute name.
+ *
+ * @enum {string}
+ */
+i18n.input.chrome.message.Name = {
+ ALT_KEY: 'altKey',
+ CANDIDATE: 'candidate',
+ CANDIDATES: 'candidates',
+ CANDIDATE_ID: 'candidateID',
+ CODE: 'code',
+ CONTEXT_ID: 'contextID',
+ CONTEXT_TYPE: 'contextType',
+ CURSOR_VISIBLE: 'cursorVisible',
+ CTRL_KEY: 'ctrlKey',
+ CURSOR: 'cursor',
+ ENGINE_ID: 'engineID',
+ HEIGHT: 'height',
+ ID: 'id',
+ IS_EMOJI: 'isEmoji',
+ IS_AUTOCORRECT: 'isAutoCorrect',
+ KEY: 'key',
+ KEYCODE: 'keyCode',
+ KEYSET: 'keyset',
+ KEY_DATA: 'keyData',
+ LANGUAGE: 'language',
+ MATCHED_LENGTHS: 'matchedLengths',
+ MSG: 'msg',
+ MSG_TYPE: 'type',
+ OPTION_PREFIX: 'optionPrefix',
+ OPTION_TYPE: 'optionType',
+ PAGE_SIZE: 'pageSize',
+ POSSIBILITIES: 'possibilities',
+ PROPERTIES: 'properties',
+ REQUEST_ID: 'requestId',
+ SHIFT_KEY: 'shiftKey',
+ SOURCE: 'source',
+ SOURCES: 'sources',
+ SPATIAL_DATA: 'spatialData',
+ SCREEN: 'screen',
+ STROKES: 'strokes',
+ TEXT: 'text',
+ VERTICAL: 'vertical',
+ VISIBLE: 'visible',
+ VISIBILITY: 'visibility',
+ WIDTH: 'width'
+};
diff --git a/third_party/google_input_tools/src/chrome/os/message/type.js b/third_party/google_input_tools/src/chrome/os/message/type.js
new file mode 100644
index 0000000..24b870c
--- /dev/null
+++ b/third_party/google_input_tools/src/chrome/os/message/type.js
@@ -0,0 +1,57 @@
+// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
+// limitations under the License.
+// See the License for the specific language governing permissions and
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// Unless required by applicable law or agreed to in writing, software
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// You may obtain a copy of the License at
+// you may not use this file except in compliance with the License.
+// Licensed under the Apache License, Version 2.0 (the "License");
+//
+goog.provide('i18n.input.chrome.message.Type');
+
+
+/**
+ * The message type.
+ *
+ * @enum {string}
+ */
+i18n.input.chrome.message.Type = {
+ CANDIDATES_BACK: 'candidates_back',
+ COMMIT_TEXT: 'commit_text',
+ COMPLETION: 'completion',
+ CONNECT: 'connect',
+ CONTEXT_BLUR: 'context_blur',
+ CONTEXT_FOCUS: 'context_focus',
+ DATASOURCE_READY: 'datasource_ready',
+ DISCONNECT: 'disconnect',
+ DOUBLE_CLICK_ON_SPACE_KEY: 'double_click_on_space_key',
+ EMOJI_SET_INPUTTOOL: 'emoji_set_inputtool',
+ EMOJI_UNSET_INPUTTOOL: 'emoji_unset_inputtool',
+ EXEC_ALL: 'exec_all',
+ HWT_NETWORK_ERROR: 'hwt_network_error',
+ HWT_PRIVACY_GOT_IT: 'hwt_privacy_got_it',
+ HWT_PRIVACY_INFO: 'hwt_privacy_info',
+ HWT_REQUEST: 'hwt_request',
+ HWT_SET_INPUTTOOL: 'hwt_set_inputtool',
+ HWT_UNSET_INPUTTOOL: 'hwt_unset_inputtool',
+ KEY_CLICK: 'key_click',
+ KEY_EVENT: 'key_event',
+ OPTION_CHANGE: 'option_change',
+ PREDICTION: 'prediction',
+ SELECT_CANDIDATE: 'select_candidate',
+ SEND_KEY_EVENT: 'send_key_event',
+ SET_COMPOSITION: 'set_composition',
+ SET_LANGUAGE: 'set_language',
+ SURROUNDING_TEXT_CHANGED: 'surrounding_text_changed',
+ SWITCH_KEYSET: 'switch_keyset',
+ TOGGLE_LANGUAGE_STATE: 'toggle_language_state',
+ UPDATE_SETTINGS: 'update_settings',
+ VISIBILITY_CHANGE: 'visibility_change'
+};
+
+
+
diff --git a/third_party/google_input_tools/third_party/closure_library/AUTHORS b/third_party/google_input_tools/third_party/closure_library/AUTHORS
new file mode 100644
index 0000000..c8ad75a
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/AUTHORS
@@ -0,0 +1,17 @@
+# This is a list of contributors to the Closure Library.
+
+# Names should be added to this file like so:
+# Name or Organization <email address>
+
+Google Inc.
+Mohamed Mansour <hello@mohamedmansour.com>
+Bjorn Tipling <bjorn.tipling@gmail.com>
+SameGoal LLC <help@samegoal.com>
+Guido Tapia <guido.tapia@gmail.com>
+Andrew Mattie <amattie@gmail.com>
+Ilia Mirkin <ibmirkin@gmail.com>
+Ivan Kozik <ivan.kozik@gmail.com>
+Rich Dougherty <rich@rd.gen.nz>
+Chad Killingsworth <chadkillingsworth@missouristate.edu>
+Dan Pupius <dan.pupius@gmail.com>
+
diff --git a/third_party/google_input_tools/third_party/closure_library/LICENSE b/third_party/google_input_tools/third_party/closure_library/LICENSE
new file mode 100644
index 0000000..04cb0d7
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2013 Google Inc.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/third_party/google_input_tools/third_party/closure_library/README.chromium b/third_party/google_input_tools/third_party/closure_library/README.chromium
new file mode 100644
index 0000000..768d8bb
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/README.chromium
@@ -0,0 +1,18 @@
+Name: Closure Library
+URL: https://github.com/google/closure-library
+Version: Unknown
+Revision: 946a7d39d4ffe08676c755b21d901e71d9904a3b
+InfoURL: http://developers.google.com/closure/library
+License: Apache 2.0
+Security Critical: Yes
+
+Description:
+Closure Library is a powerful, low level JavaScript library designed
+for building complex and scalable web applications.
+
+Local Modifications:
+Only the files actually used by google-input-tools are kept. This includes
+a number of javaScript source files and a few python modules to scan
+sources for closure dependencies. Adding missing shebang line on python files
+as required.
+
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/bin/build/closurebuilder.py b/third_party/google_input_tools/third_party/closure_library/closure/bin/build/closurebuilder.py
new file mode 100755
index 0000000..1f01e61
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/bin/build/closurebuilder.py
@@ -0,0 +1,274 @@
+#!/usr/bin/env python
+#
+# Copyright 2009 The Closure Library Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS-IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Utility for Closure Library dependency calculation.
+
+ClosureBuilder scans source files to build dependency info. From the
+dependencies, the script can produce a manifest in dependency order,
+a concatenated script, or compiled output from the Closure Compiler.
+
+Paths to files can be expressed as individual arguments to the tool (intended
+for use with find and xargs). As a convenience, --root can be used to specify
+all JS files below a directory.
+
+usage: %prog [options] [file1.js file2.js ...]
+"""
+
+__author__ = 'nnaze@google.com (Nathan Naze)'
+
+
+import logging
+import optparse
+import os
+import sys
+
+import depstree
+import jscompiler
+import source
+import treescan
+
+
+def _GetOptionsParser():
+ """Get the options parser."""
+
+ parser = optparse.OptionParser(__doc__)
+ parser.add_option('-i',
+ '--input',
+ dest='inputs',
+ action='append',
+ default=[],
+ help='One or more input files to calculate dependencies '
+ 'for. The namespaces in this file will be combined with '
+ 'those given with the -n flag to form the set of '
+ 'namespaces to find dependencies for.')
+ parser.add_option('-n',
+ '--namespace',
+ dest='namespaces',
+ action='append',
+ default=[],
+ help='One or more namespaces to calculate dependencies '
+ 'for. These namespaces will be combined with those given '
+ 'with the -i flag to form the set of namespaces to find '
+ 'dependencies for. A Closure namespace is a '
+ 'dot-delimited path expression declared with a call to '
+ 'goog.provide() (e.g. "goog.array" or "foo.bar").')
+ parser.add_option('--root',
+ dest='roots',
+ action='append',
+ default=[],
+ help='The paths that should be traversed to build the '
+ 'dependencies.')
+ parser.add_option('-o',
+ '--output_mode',
+ dest='output_mode',
+ type='choice',
+ action='store',
+ choices=['list', 'script', 'compiled'],
+ default='list',
+ help='The type of output to generate from this script. '
+ 'Options are "list" for a list of filenames, "script" '
+ 'for a single script containing the contents of all the '
+ 'files, or "compiled" to produce compiled output with '
+ 'the Closure Compiler. Default is "list".')
+ parser.add_option('-c',
+ '--compiler_jar',
+ dest='compiler_jar',
+ action='store',
+ help='The location of the Closure compiler .jar file.')
+ parser.add_option('-f',
+ '--compiler_flags',
+ dest='compiler_flags',
+ default=[],
+ action='append',
+ help='Additional flags to pass to the Closure compiler. '
+ 'To pass multiple flags, --compiler_flags has to be '
+ 'specified multiple times.')
+ parser.add_option('-j',
+ '--jvm_flags',
+ dest='jvm_flags',
+ default=[],
+ action='append',
+ help='Additional flags to pass to the JVM compiler. '
+ 'To pass multiple flags, --jvm_flags has to be '
+ 'specified multiple times.')
+ parser.add_option('--output_file',
+ dest='output_file',
+ action='store',
+ help=('If specified, write output to this path instead of '
+ 'writing to standard output.'))
+
+ return parser
+
+
+def _GetInputByPath(path, sources):
+ """Get the source identified by a path.
+
+ Args:
+ path: str, A path to a file that identifies a source.
+ sources: An iterable collection of source objects.
+
+ Returns:
+ The source from sources identified by path, if found. Converts to
+ absolute paths for comparison.
+ """
+ for js_source in sources:
+ # Convert both to absolute paths for comparison.
+ if os.path.abspath(path) == os.path.abspath(js_source.GetPath()):
+ return js_source
+
+
+def _GetClosureBaseFile(sources):
+ """Given a set of sources, returns the one base.js file.
+
+ Note that if zero or two or more base.js files are found, an error message
+ will be written and the program will be exited.
+
+ Args:
+ sources: An iterable of _PathSource objects.
+
+ Returns:
+ The _PathSource representing the base Closure file.
+ """
+ base_files = [
+ js_source for js_source in sources if _IsClosureBaseFile(js_source)]
+
+ if not base_files:
+ logging.error('No Closure base.js file found.')
+ sys.exit(1)
+ if len(base_files) > 1:
+ logging.error('More than one Closure base.js files found at these paths:')
+ for base_file in base_files:
+ logging.error(base_file.GetPath())
+ sys.exit(1)
+ return base_files[0]
+
+
+def _IsClosureBaseFile(js_source):
+ """Returns true if the given _PathSource is the Closure base.js source."""
+ return (os.path.basename(js_source.GetPath()) == 'base.js' and
+ js_source.provides == set(['goog']))
+
+
+class _PathSource(source.Source):
+ """Source file subclass that remembers its file path."""
+
+ def __init__(self, path):
+ """Initialize a source.
+
+ Args:
+ path: str, Path to a JavaScript file. The source string will be read
+ from this file.
+ """
+ super(_PathSource, self).__init__(source.GetFileContents(path))
+
+ self._path = path
+
+ def __str__(self):
+ return 'PathSource %s' % self._path
+
+ def GetPath(self):
+ """Returns the path."""
+ return self._path
+
+
+def main():
+ logging.basicConfig(format=(sys.argv[0] + ': %(message)s'),
+ level=logging.INFO)
+ options, args = _GetOptionsParser().parse_args()
+
+ # Make our output pipe.
+ if options.output_file:
+ out = open(options.output_file, 'w')
+ else:
+ out = sys.stdout
+
+ sources = set()
+
+ logging.info('Scanning paths...')
+ for path in options.roots:
+ for js_path in treescan.ScanTreeForJsFiles(path):
+ sources.add(_PathSource(js_path))
+
+ # Add scripts specified on the command line.
+ for js_path in args:
+ sources.add(_PathSource(js_path))
+
+ logging.info('%s sources scanned.', len(sources))
+
+ # Though deps output doesn't need to query the tree, we still build it
+ # to validate dependencies.
+ logging.info('Building dependency tree..')
+ tree = depstree.DepsTree(sources)
+
+ input_namespaces = set()
+ inputs = options.inputs or []
+ for input_path in inputs:
+ js_input = _GetInputByPath(input_path, sources)
+ if not js_input:
+ logging.error('No source matched input %s', input_path)
+ sys.exit(1)
+ input_namespaces.update(js_input.provides)
+
+ input_namespaces.update(options.namespaces)
+
+ if not input_namespaces:
+ logging.error('No namespaces found. At least one namespace must be '
+ 'specified with the --namespace or --input flags.')
+ sys.exit(2)
+
+ # The Closure Library base file must go first.
+ base = _GetClosureBaseFile(sources)
+ deps = [base] + tree.GetDependencies(input_namespaces)
+
+ output_mode = options.output_mode
+ if output_mode == 'list':
+ out.writelines([js_source.GetPath() + '\n' for js_source in deps])
+ elif output_mode == 'script':
+ out.writelines([js_source.GetSource() for js_source in deps])
+ elif output_mode == 'compiled':
+ logging.warning("""\
+Closure Compiler now natively understands and orders Closure dependencies and
+is prefererred over using this script for performing JavaScript compilation.
+
+Please migrate your codebase.
+
+See:
+https://code.google.com/p/closure-compiler/wiki/ManageClosureDependencies
+""")
+
+ # Make sure a .jar is specified.
+ if not options.compiler_jar:
+ logging.error('--compiler_jar flag must be specified if --output is '
+ '"compiled"')
+ sys.exit(2)
+
+ # Will throw an error if the compilation fails.
+ compiled_source = jscompiler.Compile(
+ options.compiler_jar,
+ [js_source.GetPath() for js_source in deps],
+ jvm_flags=options.jvm_flags,
+ compiler_flags=options.compiler_flags)
+
+ logging.info('JavaScript compilation succeeded.')
+ out.write(compiled_source)
+
+ else:
+ logging.error('Invalid value for --output flag.')
+ sys.exit(2)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/bin/build/depstree.py b/third_party/google_input_tools/third_party/closure_library/closure/bin/build/depstree.py
new file mode 100755
index 0000000..418f601
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/bin/build/depstree.py
@@ -0,0 +1,190 @@
+#!/usr/bin/env python
+# Copyright 2009 The Closure Library Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS-IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+"""Class to represent a full Closure Library dependency tree.
+
+Offers a queryable tree of dependencies of a given set of sources. The tree
+will also do logical validation to prevent duplicate provides and circular
+dependencies.
+"""
+
+__author__ = 'nnaze@google.com (Nathan Naze)'
+
+
+class DepsTree(object):
+ """Represents the set of dependencies between source files."""
+
+ def __init__(self, sources):
+ """Initializes the tree with a set of sources.
+
+ Args:
+ sources: A set of JavaScript sources.
+
+ Raises:
+ MultipleProvideError: A namespace is provided by muplitple sources.
+ NamespaceNotFoundError: A namespace is required but never provided.
+ """
+
+ self._sources = sources
+ self._provides_map = dict()
+
+ # Ensure nothing was provided twice.
+ for source in sources:
+ for provide in source.provides:
+ if provide in self._provides_map:
+ raise MultipleProvideError(
+ provide, [self._provides_map[provide], source])
+
+ self._provides_map[provide] = source
+
+ # Check that all required namespaces are provided.
+ for source in sources:
+ for require in source.requires:
+ if require not in self._provides_map:
+ raise NamespaceNotFoundError(require, source)
+
+ def GetDependencies(self, required_namespaces):
+ """Get source dependencies, in order, for the given namespaces.
+
+ Args:
+ required_namespaces: A string (for one) or list (for one or more) of
+ namespaces.
+
+ Returns:
+ A list of source objects that provide those namespaces and all
+ requirements, in dependency order.
+
+ Raises:
+ NamespaceNotFoundError: A namespace is requested but doesn't exist.
+ CircularDependencyError: A cycle is detected in the dependency tree.
+ """
+ if isinstance(required_namespaces, str):
+ required_namespaces = [required_namespaces]
+
+ deps_sources = []
+
+ for namespace in required_namespaces:
+ for source in DepsTree._ResolveDependencies(
+ namespace, [], self._provides_map, []):
+ if source not in deps_sources:
+ deps_sources.append(source)
+
+ return deps_sources
+
+ @staticmethod
+ def _ResolveDependencies(required_namespace, deps_list, provides_map,
+ traversal_path):
+ """Resolve dependencies for Closure source files.
+
+ Follows the dependency tree down and builds a list of sources in dependency
+ order. This function will recursively call itself to fill all dependencies
+ below the requested namespaces, and then append its sources at the end of
+ the list.
+
+ Args:
+ required_namespace: String of required namespace.
+ deps_list: List of sources in dependency order. This function will append
+ the required source once all of its dependencies are satisfied.
+ provides_map: Map from namespace to source that provides it.
+ traversal_path: List of namespaces of our path from the root down the
+ dependency/recursion tree. Used to identify cyclical dependencies.
+ This is a list used as a stack -- when the function is entered, the
+ current namespace is pushed and popped right before returning.
+ Each recursive call will check that the current namespace does not
+ appear in the list, throwing a CircularDependencyError if it does.
+
+ Returns:
+ The given deps_list object filled with sources in dependency order.
+
+ Raises:
+ NamespaceNotFoundError: A namespace is requested but doesn't exist.
+ CircularDependencyError: A cycle is detected in the dependency tree.
+ """
+
+ source = provides_map.get(required_namespace)
+ if not source:
+ raise NamespaceNotFoundError(required_namespace)
+
+ if required_namespace in traversal_path:
+ traversal_path.append(required_namespace) # do this *after* the test
+
+ # This must be a cycle.
+ raise CircularDependencyError(traversal_path)
+
+ # If we don't have the source yet, we'll have to visit this namespace and
+ # add the required dependencies to deps_list.
+ if source not in deps_list:
+ traversal_path.append(required_namespace)
+
+ for require in source.requires:
+
+ # Append all other dependencies before we append our own.
+ DepsTree._ResolveDependencies(require, deps_list, provides_map,
+ traversal_path)
+ deps_list.append(source)
+
+ traversal_path.pop()
+
+ return deps_list
+
+
+class BaseDepsTreeError(Exception):
+ """Base DepsTree error."""
+
+ def __init__(self):
+ Exception.__init__(self)
+
+
+class CircularDependencyError(BaseDepsTreeError):
+ """Raised when a dependency cycle is encountered."""
+
+ def __init__(self, dependency_list):
+ BaseDepsTreeError.__init__(self)
+ self._dependency_list = dependency_list
+
+ def __str__(self):
+ return ('Encountered circular dependency:\n%s\n' %
+ '\n'.join(self._dependency_list))
+
+
+class MultipleProvideError(BaseDepsTreeError):
+ """Raised when a namespace is provided more than once."""
+
+ def __init__(self, namespace, sources):
+ BaseDepsTreeError.__init__(self)
+ self._namespace = namespace
+ self._sources = sources
+
+ def __str__(self):
+ source_strs = map(str, self._sources)
+
+ return ('Namespace "%s" provided more than once in sources:\n%s\n' %
+ (self._namespace, '\n'.join(source_strs)))
+
+
+class NamespaceNotFoundError(BaseDepsTreeError):
+ """Raised when a namespace is requested but not provided."""
+
+ def __init__(self, namespace, source=None):
+ BaseDepsTreeError.__init__(self)
+ self._namespace = namespace
+ self._source = source
+
+ def __str__(self):
+ msg = 'Namespace "%s" never provided.' % self._namespace
+ if self._source:
+ msg += ' Required in %s' % self._source
+ return msg
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/bin/build/depstree_test.py b/third_party/google_input_tools/third_party/closure_library/closure/bin/build/depstree_test.py
new file mode 100755
index 0000000..eb4c999
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/bin/build/depstree_test.py
@@ -0,0 +1,127 @@
+#!/usr/bin/env python
+#
+# Copyright 2009 The Closure Library Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS-IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+"""Unit test for depstree."""
+
+__author__ = 'nnaze@google.com (Nathan Naze)'
+
+
+import unittest
+
+import depstree
+
+
+def _GetProvides(sources):
+ """Get all namespaces provided by a collection of sources."""
+
+ provides = set()
+ for source in sources:
+ provides.update(source.provides)
+ return provides
+
+
+class MockSource(object):
+ """Mock Source file."""
+
+ def __init__(self, provides, requires):
+ self.provides = set(provides)
+ self.requires = set(requires)
+
+ def __repr__(self):
+ return 'MockSource %s' % self.provides
+
+
+class DepsTreeTestCase(unittest.TestCase):
+ """Unit test for DepsTree. Tests several common situations and errors."""
+
+ def AssertValidDependencies(self, deps_list):
+ """Validates a dependency list.
+
+ Asserts that a dependency list is valid: For every source in the list,
+ ensure that every require is provided by a source earlier in the list.
+
+ Args:
+ deps_list: A list of sources that should be in dependency order.
+ """
+
+ for i in range(len(deps_list)):
+ source = deps_list[i]
+ previous_provides = _GetProvides(deps_list[:i])
+ for require in source.requires:
+ self.assertTrue(
+ require in previous_provides,
+ 'Namespace "%s" not provided before required by %s' % (
+ require, source))
+
+ def testSimpleDepsTree(self):
+ a = MockSource(['A'], ['B', 'C'])
+ b = MockSource(['B'], [])
+ c = MockSource(['C'], ['D'])
+ d = MockSource(['D'], ['E'])
+ e = MockSource(['E'], [])
+
+ tree = depstree.DepsTree([a, b, c, d, e])
+
+ self.AssertValidDependencies(tree.GetDependencies('A'))
+ self.AssertValidDependencies(tree.GetDependencies('B'))
+ self.AssertValidDependencies(tree.GetDependencies('C'))
+ self.AssertValidDependencies(tree.GetDependencies('D'))
+ self.AssertValidDependencies(tree.GetDependencies('E'))
+
+ def testCircularDependency(self):
+ # Circular deps
+ a = MockSource(['A'], ['B'])
+ b = MockSource(['B'], ['C'])
+ c = MockSource(['C'], ['A'])
+
+ tree = depstree.DepsTree([a, b, c])
+
+ self.assertRaises(depstree.CircularDependencyError,
+ tree.GetDependencies, 'A')
+
+ def testRequiresUndefinedNamespace(self):
+ a = MockSource(['A'], ['B'])
+ b = MockSource(['B'], ['C'])
+ c = MockSource(['C'], ['D']) # But there is no D.
+
+ def MakeDepsTree():
+ return depstree.DepsTree([a, b, c])
+
+ self.assertRaises(depstree.NamespaceNotFoundError, MakeDepsTree)
+
+ def testDepsForMissingNamespace(self):
+ a = MockSource(['A'], ['B'])
+ b = MockSource(['B'], [])
+
+ tree = depstree.DepsTree([a, b])
+
+ # There is no C.
+ self.assertRaises(depstree.NamespaceNotFoundError,
+ tree.GetDependencies, 'C')
+
+ def testMultipleRequires(self):
+ a = MockSource(['A'], ['B'])
+ b = MockSource(['B'], ['C'])
+ c = MockSource(['C'], [])
+ d = MockSource(['D'], ['B'])
+
+ tree = depstree.DepsTree([a, b, c, d])
+ self.AssertValidDependencies(tree.GetDependencies(['D', 'A']))
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/bin/build/depswriter.py b/third_party/google_input_tools/third_party/closure_library/closure/bin/build/depswriter.py
new file mode 100755
index 0000000..dfecc4b
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/bin/build/depswriter.py
@@ -0,0 +1,202 @@
+#!/usr/bin/env python
+#
+# Copyright 2009 The Closure Library Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS-IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+"""Generates out a Closure deps.js file given a list of JavaScript sources.
+
+Paths can be specified as arguments or (more commonly) specifying trees
+with the flags (call with --help for descriptions).
+
+Usage: depswriter.py [path/to/js1.js [path/to/js2.js] ...]
+"""
+
+import logging
+import optparse
+import os
+import posixpath
+import shlex
+import sys
+
+import source
+import treescan
+
+
+__author__ = 'nnaze@google.com (Nathan Naze)'
+
+
+def MakeDepsFile(source_map):
+ """Make a generated deps file.
+
+ Args:
+ source_map: A dict map of the source path to source.Source object.
+
+ Returns:
+ str, A generated deps file source.
+ """
+
+ # Write in path alphabetical order
+ paths = sorted(source_map.keys())
+
+ lines = []
+
+ for path in paths:
+ js_source = source_map[path]
+
+ # We don't need to add entries that don't provide anything.
+ if js_source.provides:
+ lines.append(_GetDepsLine(path, js_source))
+
+ return ''.join(lines)
+
+
+def _GetDepsLine(path, js_source):
+ """Get a deps.js file string for a source."""
+
+ provides = sorted(js_source.provides)
+ requires = sorted(js_source.requires)
+
+ return 'goog.addDependency(\'%s\', %s, %s);\n' % (path, provides, requires)
+
+
+def _GetOptionsParser():
+ """Get the options parser."""
+
+ parser = optparse.OptionParser(__doc__)
+
+ parser.add_option('--output_file',
+ dest='output_file',
+ action='store',
+ help=('If specified, write output to this path instead of '
+ 'writing to standard output.'))
+ parser.add_option('--root',
+ dest='roots',
+ default=[],
+ action='append',
+ help='A root directory to scan for JS source files. '
+ 'Paths of JS files in generated deps file will be '
+ 'relative to this path. This flag may be specified '
+ 'multiple times.')
+ parser.add_option('--root_with_prefix',
+ dest='roots_with_prefix',
+ default=[],
+ action='append',
+ help='A root directory to scan for JS source files, plus '
+ 'a prefix (if either contains a space, surround with '
+ 'quotes). Paths in generated deps file will be relative '
+ 'to the root, but preceded by the prefix. This flag '
+ 'may be specified multiple times.')
+ parser.add_option('--path_with_depspath',
+ dest='paths_with_depspath',
+ default=[],
+ action='append',
+ help='A path to a source file and an alternate path to '
+ 'the file in the generated deps file (if either contains '
+ 'a space, surround with whitespace). This flag may be '
+ 'specified multiple times.')
+ return parser
+
+
+def _NormalizePathSeparators(path):
+ """Replaces OS-specific path separators with POSIX-style slashes.
+
+ Args:
+ path: str, A file path.
+
+ Returns:
+ str, The path with any OS-specific path separators (such as backslash on
+ Windows) replaced with URL-compatible forward slashes. A no-op on systems
+ that use POSIX paths.
+ """
+ return path.replace(os.sep, posixpath.sep)
+
+
+def _GetRelativePathToSourceDict(root, prefix=''):
+ """Scans a top root directory for .js sources.
+
+ Args:
+ root: str, Root directory.
+ prefix: str, Prefix for returned paths.
+
+ Returns:
+ dict, A map of relative paths (with prefix, if given), to source.Source
+ objects.
+ """
+ # Remember and restore the cwd when we're done. We work from the root so
+ # that paths are relative from the root.
+ start_wd = os.getcwd()
+ os.chdir(root)
+
+ path_to_source = {}
+ for path in treescan.ScanTreeForJsFiles('.'):
+ prefixed_path = _NormalizePathSeparators(os.path.join(prefix, path))
+ path_to_source[prefixed_path] = source.Source(source.GetFileContents(path))
+
+ os.chdir(start_wd)
+
+ return path_to_source
+
+
+def _GetPair(s):
+ """Return a string as a shell-parsed tuple. Two values expected."""
+ try:
+ # shlex uses '\' as an escape character, so they must be escaped.
+ s = s.replace('\\', '\\\\')
+ first, second = shlex.split(s)
+ return (first, second)
+ except:
+ raise Exception('Unable to parse input line as a pair: %s' % s)
+
+
+def main():
+ """CLI frontend to MakeDepsFile."""
+ logging.basicConfig(format=(sys.argv[0] + ': %(message)s'),
+ level=logging.INFO)
+ options, args = _GetOptionsParser().parse_args()
+
+ path_to_source = {}
+
+ # Roots without prefixes
+ for root in options.roots:
+ path_to_source.update(_GetRelativePathToSourceDict(root))
+
+ # Roots with prefixes
+ for root_and_prefix in options.roots_with_prefix:
+ root, prefix = _GetPair(root_and_prefix)
+ path_to_source.update(_GetRelativePathToSourceDict(root, prefix=prefix))
+
+ # Source paths
+ for path in args:
+ path_to_source[path] = source.Source(source.GetFileContents(path))
+
+ # Source paths with alternate deps paths
+ for path_with_depspath in options.paths_with_depspath:
+ srcpath, depspath = _GetPair(path_with_depspath)
+ path_to_source[depspath] = source.Source(source.GetFileContents(srcpath))
+
+ # Make our output pipe.
+ if options.output_file:
+ out = open(options.output_file, 'w')
+ else:
+ out = sys.stdout
+
+ out.write('// This file was autogenerated by %s.\n' % sys.argv[0])
+ out.write('// Please do not edit.\n')
+
+ out.write(MakeDepsFile(path_to_source))
+
+
+if __name__ == '__main__':
+ main()
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/bin/build/jscompiler.py b/third_party/google_input_tools/third_party/closure_library/closure/bin/build/jscompiler.py
new file mode 100755
index 0000000..783b5d7
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/bin/build/jscompiler.py
@@ -0,0 +1,136 @@
+#!/usr/bin/env python
+# Copyright 2010 The Closure Library Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS-IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Utility to use the Closure Compiler CLI from Python."""
+
+
+import logging
+import os
+import re
+import subprocess
+
+
+# Pulls just the major and minor version numbers from the first line of
+# 'java -version'. Versions are in the format of [0-9]+\.[0-9]+\..* See:
+# http://www.oracle.com/technetwork/java/javase/versioning-naming-139433.html
+_VERSION_REGEX = re.compile(r'"([0-9]+)\.([0-9]+)')
+
+
+class JsCompilerError(Exception):
+ """Raised if there's an error in calling the compiler."""
+ pass
+
+
+def _GetJavaVersionString():
+ """Get the version string from the Java VM."""
+ return subprocess.check_output(['java', '-version'], stderr=subprocess.STDOUT)
+
+
+def _ParseJavaVersion(version_string):
+ """Returns a 2-tuple for the current version of Java installed.
+
+ Args:
+ version_string: String of the Java version (e.g. '1.7.2-ea').
+
+ Returns:
+ The major and minor versions, as a 2-tuple (e.g. (1, 7)).
+ """
+ match = _VERSION_REGEX.search(version_string)
+ if match:
+ version = tuple(int(x, 10) for x in match.groups())
+ assert len(version) == 2
+ return version
+
+
+def _JavaSupports32BitMode():
+ """Determines whether the JVM supports 32-bit mode on the platform."""
+ # Suppresses process output to stderr and stdout from showing up in the
+ # console as we're only trying to determine 32-bit JVM support.
+ supported = False
+ try:
+ devnull = open(os.devnull, 'wb')
+ return subprocess.call(
+ ['java', '-d32', '-version'], stdout=devnull, stderr=devnull) == 0
+ except IOError:
+ pass
+ else:
+ devnull.close()
+ return supported
+
+
+def _GetJsCompilerArgs(compiler_jar_path, java_version, source_paths,
+ jvm_flags, compiler_flags):
+ """Assembles arguments for call to JsCompiler."""
+
+ if java_version < (1, 7):
+ raise JsCompilerError('Closure Compiler requires Java 1.7 or higher. '
+ 'Please visit http://www.java.com/getjava')
+
+ args = ['java']
+
+ # Add JVM flags we believe will produce the best performance. See
+ # https://groups.google.com/forum/#!topic/closure-library-discuss/7w_O9-vzlj4
+
+ # Attempt 32-bit mode if available (Java 7 on Mac OS X does not support 32-bit
+ # mode, for example).
+ if _JavaSupports32BitMode():
+ args += ['-d32']
+
+ # Prefer the "client" VM.
+ args += ['-client']
+
+ # Add JVM flags, if any
+ if jvm_flags:
+ args += jvm_flags
+
+ # Add the application JAR.
+ args += ['-jar', compiler_jar_path]
+
+ for path in source_paths:
+ args += ['--js', path]
+
+ # Add compiler flags, if any.
+ if compiler_flags:
+ args += compiler_flags
+
+ return args
+
+
+def Compile(compiler_jar_path, source_paths,
+ jvm_flags=None,
+ compiler_flags=None):
+ """Prepares command-line call to Closure Compiler.
+
+ Args:
+ compiler_jar_path: Path to the Closure compiler .jar file.
+ source_paths: Source paths to build, in order.
+ jvm_flags: A list of additional flags to pass on to JVM.
+ compiler_flags: A list of additional flags to pass on to Closure Compiler.
+
+ Returns:
+ The compiled source, as a string, or None if compilation failed.
+ """
+
+ java_version = _ParseJavaVersion(_GetJavaVersionString())
+
+ args = _GetJsCompilerArgs(
+ compiler_jar_path, java_version, source_paths, jvm_flags, compiler_flags)
+
+ logging.info('Compiling with the following command: %s', ' '.join(args))
+
+ try:
+ return subprocess.check_output(args)
+ except subprocess.CalledProcessError:
+ raise JsCompilerError('JavaScript compilation failed.')
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/bin/build/source.py b/third_party/google_input_tools/third_party/closure_library/closure/bin/build/source.py
new file mode 100755
index 0000000..d4d1dba
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/bin/build/source.py
@@ -0,0 +1,119 @@
+#!/usr/bin/env python
+# Copyright 2009 The Closure Library Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS-IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+"""Scans a source JS file for its provided and required namespaces.
+
+Simple class to scan a JavaScript file and express its dependencies.
+"""
+
+__author__ = 'nnaze@google.com'
+
+
+import re
+
+_BASE_REGEX_STRING = '^\s*goog\.%s\(\s*[\'"](.+)[\'"]\s*\)'
+_PROVIDE_REGEX = re.compile(_BASE_REGEX_STRING % 'provide')
+_REQUIRES_REGEX = re.compile(_BASE_REGEX_STRING % 'require')
+
+
+class Source(object):
+ """Scans a JavaScript source for its provided and required namespaces."""
+
+ # Matches a "/* ... */" comment.
+ # Note: We can't definitively distinguish a "/*" in a string literal without a
+ # state machine tokenizer. We'll assume that a line starting with whitespace
+ # and "/*" is a comment.
+ _COMMENT_REGEX = re.compile(
+ r"""
+ ^\s* # Start of a new line and whitespace
+ /\* # Opening "/*"
+ .*? # Non greedy match of any characters (including newlines)
+ \*/ # Closing "*/""",
+ re.MULTILINE | re.DOTALL | re.VERBOSE)
+
+ def __init__(self, source):
+ """Initialize a source.
+
+ Args:
+ source: str, The JavaScript source.
+ """
+
+ self.provides = set()
+ self.requires = set()
+
+ self._source = source
+ self._ScanSource()
+
+ def GetSource(self):
+ """Get the source as a string."""
+ return self._source
+
+ @classmethod
+ def _StripComments(cls, source):
+ return cls._COMMENT_REGEX.sub('', source)
+
+ @classmethod
+ def _HasProvideGoogFlag(cls, source):
+ """Determines whether the @provideGoog flag is in a comment."""
+ for comment_content in cls._COMMENT_REGEX.findall(source):
+ if '@provideGoog' in comment_content:
+ return True
+
+ return False
+
+ def _ScanSource(self):
+ """Fill in provides and requires by scanning the source."""
+
+ stripped_source = self._StripComments(self.GetSource())
+
+ source_lines = stripped_source.splitlines()
+ for line in source_lines:
+ match = _PROVIDE_REGEX.match(line)
+ if match:
+ self.provides.add(match.group(1))
+ match = _REQUIRES_REGEX.match(line)
+ if match:
+ self.requires.add(match.group(1))
+
+ # Closure's base file implicitly provides 'goog'.
+ # This is indicated with the @provideGoog flag.
+ if self._HasProvideGoogFlag(self.GetSource()):
+
+ if len(self.provides) or len(self.requires):
+ raise Exception(
+ 'Base file should not provide or require namespaces.')
+
+ self.provides.add('goog')
+
+
+def GetFileContents(path):
+ """Get a file's contents as a string.
+
+ Args:
+ path: str, Path to file.
+
+ Returns:
+ str, Contents of file.
+
+ Raises:
+ IOError: An error occurred opening or reading the file.
+
+ """
+ fileobj = open(path)
+ try:
+ return fileobj.read()
+ finally:
+ fileobj.close()
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/bin/build/source_test.py b/third_party/google_input_tools/third_party/closure_library/closure/bin/build/source_test.py
new file mode 100755
index 0000000..c9e4d12
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/bin/build/source_test.py
@@ -0,0 +1,134 @@
+#!/usr/bin/env python
+#
+# Copyright 2010 The Closure Library Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS-IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+"""Unit test for source."""
+
+__author__ = 'nnaze@google.com (Nathan Naze)'
+
+
+import unittest
+
+import source
+
+
+class SourceTestCase(unittest.TestCase):
+ """Unit test for source. Tests the parser on a known source input."""
+
+ def testSourceScan(self):
+ test_source = source.Source(_TEST_SOURCE)
+
+ self.assertEqual(set(['foo', 'foo.test']),
+ test_source.provides)
+ self.assertEqual(set(['goog.dom', 'goog.events.EventType']),
+ test_source.requires)
+
+ def testSourceScanBase(self):
+ test_source = source.Source(_TEST_BASE_SOURCE)
+
+ self.assertEqual(set(['goog']),
+ test_source.provides)
+ self.assertEqual(test_source.requires, set())
+
+ def testSourceScanBadBase(self):
+
+ def MakeSource():
+ source.Source(_TEST_BAD_BASE_SOURCE)
+
+ self.assertRaises(Exception, MakeSource)
+
+ def testStripComments(self):
+ self.assertEquals(
+ '\nvar foo = function() {}',
+ source.Source._StripComments((
+ '/* This is\n'
+ ' a comment split\n'
+ ' over multiple lines\n'
+ '*/\n'
+ 'var foo = function() {}')))
+
+ def testGoogStatementsInComments(self):
+ test_source = source.Source(_TEST_COMMENT_SOURCE)
+
+ self.assertEqual(set(['foo']),
+ test_source.provides)
+ self.assertEqual(set(['goog.events.EventType']),
+ test_source.requires)
+
+ def testHasProvideGoog(self):
+ self.assertTrue(source.Source._HasProvideGoogFlag(_TEST_BASE_SOURCE))
+ self.assertTrue(source.Source._HasProvideGoogFlag(_TEST_BAD_BASE_SOURCE))
+ self.assertFalse(source.Source._HasProvideGoogFlag(_TEST_COMMENT_SOURCE))
+
+
+_TEST_SOURCE = """// Fake copyright notice
+
+/** Very important comment. */
+
+goog.provide('foo');
+goog.provide('foo.test');
+
+goog.require('goog.dom');
+goog.require('goog.events.EventType');
+
+function foo() {
+ // Set bar to seventeen to increase performance.
+ this.bar = 17;
+}
+"""
+
+_TEST_COMMENT_SOURCE = """// Fake copyright notice
+
+goog.provide('foo');
+
+/*
+goog.provide('foo.test');
+ */
+
+/*
+goog.require('goog.dom');
+*/
+
+// goog.require('goog.dom');
+
+goog.require('goog.events.EventType');
+
+function bar() {
+ this.baz = 55;
+}
+"""
+
+_TEST_BASE_SOURCE = """
+/**
+ * @fileoverview The base file.
+ * @provideGoog
+ */
+
+var goog = goog || {};
+"""
+
+_TEST_BAD_BASE_SOURCE = """
+/**
+ * @fileoverview The base file.
+ * @provideGoog
+ */
+
+goog.provide('goog');
+"""
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/bin/build/treescan.py b/third_party/google_input_tools/third_party/closure_library/closure/bin/build/treescan.py
new file mode 100755
index 0000000..6694593
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/bin/build/treescan.py
@@ -0,0 +1,78 @@
+#!/usr/bin/env python
+#
+# Copyright 2010 The Closure Library Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS-IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+"""Shared utility functions for scanning directory trees."""
+
+import os
+import re
+
+
+__author__ = 'nnaze@google.com (Nathan Naze)'
+
+
+# Matches a .js file path.
+_JS_FILE_REGEX = re.compile(r'^.+\.js$')
+
+
+def ScanTreeForJsFiles(root):
+ """Scans a directory tree for JavaScript files.
+
+ Args:
+ root: str, Path to a root directory.
+
+ Returns:
+ An iterable of paths to JS files, relative to cwd.
+ """
+ return ScanTree(root, path_filter=_JS_FILE_REGEX)
+
+
+def ScanTree(root, path_filter=None, ignore_hidden=True):
+ """Scans a directory tree for files.
+
+ Args:
+ root: str, Path to a root directory.
+ path_filter: A regular expression filter. If set, only paths matching
+ the path_filter are returned.
+ ignore_hidden: If True, do not follow or return hidden directories or files
+ (those starting with a '.' character).
+
+ Yields:
+ A string path to files, relative to cwd.
+ """
+
+ def OnError(os_error):
+ raise os_error
+
+ for dirpath, dirnames, filenames in os.walk(root, onerror=OnError):
+ # os.walk allows us to modify dirnames to prevent decent into particular
+ # directories. Avoid hidden directories.
+ for dirname in dirnames:
+ if ignore_hidden and dirname.startswith('.'):
+ dirnames.remove(dirname)
+
+ for filename in filenames:
+
+ # nothing that starts with '.'
+ if ignore_hidden and filename.startswith('.'):
+ continue
+
+ fullpath = os.path.join(dirpath, filename)
+
+ if path_filter and not path_filter.match(fullpath):
+ continue
+
+ yield os.path.normpath(fullpath)
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/bin/calcdeps.py b/third_party/google_input_tools/third_party/closure_library/closure/bin/calcdeps.py
new file mode 100755
index 0000000..9cb1a6d
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/bin/calcdeps.py
@@ -0,0 +1,590 @@
+#!/usr/bin/env python
+#
+# Copyright 2006 The Closure Library Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS-IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+"""Calculates JavaScript dependencies without requiring Google's build system.
+
+This tool is deprecated and is provided for legacy users.
+See build/closurebuilder.py and build/depswriter.py for the current tools.
+
+It iterates over a number of search paths and builds a dependency tree. With
+the inputs provided, it walks the dependency tree and outputs all the files
+required for compilation.
+"""
+
+
+
+
+
+try:
+ import distutils.version
+except ImportError:
+ # distutils is not available in all environments
+ distutils = None
+
+import logging
+import optparse
+import os
+import re
+import subprocess
+import sys
+
+
+_BASE_REGEX_STRING = '^\s*goog\.%s\(\s*[\'"](.+)[\'"]\s*\)'
+req_regex = re.compile(_BASE_REGEX_STRING % 'require')
+prov_regex = re.compile(_BASE_REGEX_STRING % 'provide')
+ns_regex = re.compile('^ns:((\w+\.)*(\w+))$')
+version_regex = re.compile('[\.0-9]+')
+
+
+def IsValidFile(ref):
+ """Returns true if the provided reference is a file and exists."""
+ return os.path.isfile(ref)
+
+
+def IsJsFile(ref):
+ """Returns true if the provided reference is a Javascript file."""
+ return ref.endswith('.js')
+
+
+def IsNamespace(ref):
+ """Returns true if the provided reference is a namespace."""
+ return re.match(ns_regex, ref) is not None
+
+
+def IsDirectory(ref):
+ """Returns true if the provided reference is a directory."""
+ return os.path.isdir(ref)
+
+
+def ExpandDirectories(refs):
+ """Expands any directory references into inputs.
+
+ Description:
+ Looks for any directories in the provided references. Found directories
+ are recursively searched for .js files, which are then added to the result
+ list.
+
+ Args:
+ refs: a list of references such as files, directories, and namespaces
+
+ Returns:
+ A list of references with directories removed and replaced by any
+ .js files that are found in them. Also, the paths will be normalized.
+ """
+ result = []
+ for ref in refs:
+ if IsDirectory(ref):
+ # Disable 'Unused variable' for subdirs
+ # pylint: disable=unused-variable
+ for (directory, subdirs, filenames) in os.walk(ref):
+ for filename in filenames:
+ if IsJsFile(filename):
+ result.append(os.path.join(directory, filename))
+ else:
+ result.append(ref)
+ return map(os.path.normpath, result)
+
+
+class DependencyInfo(object):
+ """Represents a dependency that is used to build and walk a tree."""
+
+ def __init__(self, filename):
+ self.filename = filename
+ self.provides = []
+ self.requires = []
+
+ def __str__(self):
+ return '%s Provides: %s Requires: %s' % (self.filename,
+ repr(self.provides),
+ repr(self.requires))
+
+
+def BuildDependenciesFromFiles(files):
+ """Build a list of dependencies from a list of files.
+
+ Description:
+ Takes a list of files, extracts their provides and requires, and builds
+ out a list of dependency objects.
+
+ Args:
+ files: a list of files to be parsed for goog.provides and goog.requires.
+
+ Returns:
+ A list of dependency objects, one for each file in the files argument.
+ """
+ result = []
+ filenames = set()
+ for filename in files:
+ if filename in filenames:
+ continue
+
+ # Python 3 requires the file encoding to be specified
+ if (sys.version_info[0] < 3):
+ file_handle = open(filename, 'r')
+ else:
+ file_handle = open(filename, 'r', encoding='utf8')
+
+ try:
+ dep = CreateDependencyInfo(filename, file_handle)
+ result.append(dep)
+ finally:
+ file_handle.close()
+
+ filenames.add(filename)
+
+ return result
+
+
+def CreateDependencyInfo(filename, source):
+ """Create dependency info.
+
+ Args:
+ filename: Filename for source.
+ source: File-like object containing source.
+
+ Returns:
+ A DependencyInfo object with provides and requires filled.
+ """
+ dep = DependencyInfo(filename)
+ for line in source:
+ if re.match(req_regex, line):
+ dep.requires.append(re.search(req_regex, line).group(1))
+ if re.match(prov_regex, line):
+ dep.provides.append(re.search(prov_regex, line).group(1))
+ return dep
+
+
+def BuildDependencyHashFromDependencies(deps):
+ """Builds a hash for searching dependencies by the namespaces they provide.
+
+ Description:
+ Dependency objects can provide multiple namespaces. This method enumerates
+ the provides of each dependency and adds them to a hash that can be used
+ to easily resolve a given dependency by a namespace it provides.
+
+ Args:
+ deps: a list of dependency objects used to build the hash.
+
+ Raises:
+ Exception: If a multiple files try to provide the same namepace.
+
+ Returns:
+ A hash table { namespace: dependency } that can be used to resolve a
+ dependency by a namespace it provides.
+ """
+ dep_hash = {}
+ for dep in deps:
+ for provide in dep.provides:
+ if provide in dep_hash:
+ raise Exception('Duplicate provide (%s) in (%s, %s)' % (
+ provide,
+ dep_hash[provide].filename,
+ dep.filename))
+ dep_hash[provide] = dep
+ return dep_hash
+
+
+def CalculateDependencies(paths, inputs):
+ """Calculates the dependencies for given inputs.
+
+ Description:
+ This method takes a list of paths (files, directories) and builds a
+ searchable data structure based on the namespaces that each .js file
+ provides. It then parses through each input, resolving dependencies
+ against this data structure. The final output is a list of files,
+ including the inputs, that represent all of the code that is needed to
+ compile the given inputs.
+
+ Args:
+ paths: the references (files, directories) that are used to build the
+ dependency hash.
+ inputs: the inputs (files, directories, namespaces) that have dependencies
+ that need to be calculated.
+
+ Raises:
+ Exception: if a provided input is invalid.
+
+ Returns:
+ A list of all files, including inputs, that are needed to compile the given
+ inputs.
+ """
+ deps = BuildDependenciesFromFiles(paths + inputs)
+ search_hash = BuildDependencyHashFromDependencies(deps)
+ result_list = []
+ seen_list = []
+ for input_file in inputs:
+ if IsNamespace(input_file):
+ namespace = re.search(ns_regex, input_file).group(1)
+ if namespace not in search_hash:
+ raise Exception('Invalid namespace (%s)' % namespace)
+ input_file = search_hash[namespace].filename
+ if not IsValidFile(input_file) or not IsJsFile(input_file):
+ raise Exception('Invalid file (%s)' % input_file)
+ seen_list.append(input_file)
+ file_handle = open(input_file, 'r')
+ try:
+ for line in file_handle:
+ if re.match(req_regex, line):
+ require = re.search(req_regex, line).group(1)
+ ResolveDependencies(require, search_hash, result_list, seen_list)
+ finally:
+ file_handle.close()
+ result_list.append(input_file)
+
+ # All files depend on base.js, so put it first.
+ base_js_path = FindClosureBasePath(paths)
+ if base_js_path:
+ result_list.insert(0, base_js_path)
+ else:
+ logging.warning('Closure Library base.js not found.')
+
+ return result_list
+
+
+def FindClosureBasePath(paths):
+ """Given a list of file paths, return Closure base.js path, if any.
+
+ Args:
+ paths: A list of paths.
+
+ Returns:
+ The path to Closure's base.js file including filename, if found.
+ """
+
+ for path in paths:
+ pathname, filename = os.path.split(path)
+
+ if filename == 'base.js':
+ f = open(path)
+
+ is_base = False
+
+ # Sanity check that this is the Closure base file. Check that this
+ # is where goog is defined. This is determined by the @provideGoog
+ # flag.
+ for line in f:
+ if '@provideGoog' in line:
+ is_base = True
+ break
+
+ f.close()
+
+ if is_base:
+ return path
+
+def ResolveDependencies(require, search_hash, result_list, seen_list):
+ """Takes a given requirement and resolves all of the dependencies for it.
+
+ Description:
+ A given requirement may require other dependencies. This method
+ recursively resolves all dependencies for the given requirement.
+
+ Raises:
+ Exception: when require does not exist in the search_hash.
+
+ Args:
+ require: the namespace to resolve dependencies for.
+ search_hash: the data structure used for resolving dependencies.
+ result_list: a list of filenames that have been calculated as dependencies.
+ This variable is the output for this function.
+ seen_list: a list of filenames that have been 'seen'. This is required
+ for the dependency->dependant ordering.
+ """
+ if require not in search_hash:
+ raise Exception('Missing provider for (%s)' % require)
+
+ dep = search_hash[require]
+ if not dep.filename in seen_list:
+ seen_list.append(dep.filename)
+ for sub_require in dep.requires:
+ ResolveDependencies(sub_require, search_hash, result_list, seen_list)
+ result_list.append(dep.filename)
+
+
+def GetDepsLine(dep, base_path):
+ """Returns a JS string for a dependency statement in the deps.js file.
+
+ Args:
+ dep: The dependency that we're printing.
+ base_path: The path to Closure's base.js including filename.
+ """
+ return 'goog.addDependency("%s", %s, %s);' % (
+ GetRelpath(dep.filename, base_path), dep.provides, dep.requires)
+
+
+def GetRelpath(path, start):
+ """Return a relative path to |path| from |start|."""
+ # NOTE: Python 2.6 provides os.path.relpath, which has almost the same
+ # functionality as this function. Since we want to support 2.4, we have
+ # to implement it manually. :(
+ path_list = os.path.abspath(os.path.normpath(path)).split(os.sep)
+ start_list = os.path.abspath(
+ os.path.normpath(os.path.dirname(start))).split(os.sep)
+
+ common_prefix_count = 0
+ for i in range(0, min(len(path_list), len(start_list))):
+ if path_list[i] != start_list[i]:
+ break
+ common_prefix_count += 1
+
+ # Always use forward slashes, because this will get expanded to a url,
+ # not a file path.
+ return '/'.join(['..'] * (len(start_list) - common_prefix_count) +
+ path_list[common_prefix_count:])
+
+
+def PrintLine(msg, out):
+ out.write(msg)
+ out.write('\n')
+
+
+def PrintDeps(source_paths, deps, out):
+ """Print out a deps.js file from a list of source paths.
+
+ Args:
+ source_paths: Paths that we should generate dependency info for.
+ deps: Paths that provide dependency info. Their dependency info should
+ not appear in the deps file.
+ out: The output file.
+
+ Returns:
+ True on success, false if it was unable to find the base path
+ to generate deps relative to.
+ """
+ base_path = FindClosureBasePath(source_paths + deps)
+ if not base_path:
+ return False
+
+ PrintLine('// This file was autogenerated by calcdeps.py', out)
+ excludesSet = set(deps)
+
+ for dep in BuildDependenciesFromFiles(source_paths + deps):
+ if not dep.filename in excludesSet:
+ PrintLine(GetDepsLine(dep, base_path), out)
+
+ return True
+
+
+def PrintScript(source_paths, out):
+ for index, dep in enumerate(source_paths):
+ PrintLine('// Input %d' % index, out)
+ f = open(dep, 'r')
+ PrintLine(f.read(), out)
+ f.close()
+
+
+def GetJavaVersion():
+ """Returns the string for the current version of Java installed."""
+ proc = subprocess.Popen(['java', '-version'], stderr=subprocess.PIPE)
+ proc.wait()
+ version_line = proc.stderr.read().splitlines()[0]
+ return version_regex.search(version_line).group()
+
+
+def FilterByExcludes(options, files):
+ """Filters the given files by the exlusions specified at the command line.
+
+ Args:
+ options: The flags to calcdeps.
+ files: The files to filter.
+ Returns:
+ A list of files.
+ """
+ excludes = []
+ if options.excludes:
+ excludes = ExpandDirectories(options.excludes)
+
+ excludesSet = set(excludes)
+ return [i for i in files if not i in excludesSet]
+
+
+def GetPathsFromOptions(options):
+ """Generates the path files from flag options.
+
+ Args:
+ options: The flags to calcdeps.
+ Returns:
+ A list of files in the specified paths. (strings).
+ """
+
+ search_paths = options.paths
+ if not search_paths:
+ search_paths = ['.'] # Add default folder if no path is specified.
+
+ search_paths = ExpandDirectories(search_paths)
+ return FilterByExcludes(options, search_paths)
+
+
+def GetInputsFromOptions(options):
+ """Generates the inputs from flag options.
+
+ Args:
+ options: The flags to calcdeps.
+ Returns:
+ A list of inputs (strings).
+ """
+ inputs = options.inputs
+ if not inputs: # Parse stdin
+ logging.info('No inputs specified. Reading from stdin...')
+ inputs = filter(None, [line.strip('\n') for line in sys.stdin.readlines()])
+
+ logging.info('Scanning files...')
+ inputs = ExpandDirectories(inputs)
+
+ return FilterByExcludes(options, inputs)
+
+
+def Compile(compiler_jar_path, source_paths, out, flags=None):
+ """Prepares command-line call to Closure compiler.
+
+ Args:
+ compiler_jar_path: Path to the Closure compiler .jar file.
+ source_paths: Source paths to build, in order.
+ flags: A list of additional flags to pass on to Closure compiler.
+ """
+ args = ['java', '-jar', compiler_jar_path]
+ for path in source_paths:
+ args += ['--js', path]
+
+ if flags:
+ args += flags
+
+ logging.info('Compiling with the following command: %s', ' '.join(args))
+ proc = subprocess.Popen(args, stdout=subprocess.PIPE)
+ (stdoutdata, stderrdata) = proc.communicate()
+ if proc.returncode != 0:
+ logging.error('JavaScript compilation failed.')
+ sys.exit(1)
+ else:
+ out.write(stdoutdata)
+
+
+def main():
+ """The entrypoint for this script."""
+
+ logging.basicConfig(format='calcdeps.py: %(message)s', level=logging.INFO)
+
+ usage = 'usage: %prog [options] arg'
+ parser = optparse.OptionParser(usage)
+ parser.add_option('-i',
+ '--input',
+ dest='inputs',
+ action='append',
+ help='The inputs to calculate dependencies for. Valid '
+ 'values can be files, directories, or namespaces '
+ '(ns:goog.net.XhrIo). Only relevant to "list" and '
+ '"script" output.')
+ parser.add_option('-p',
+ '--path',
+ dest='paths',
+ action='append',
+ help='The paths that should be traversed to build the '
+ 'dependencies.')
+ parser.add_option('-d',
+ '--dep',
+ dest='deps',
+ action='append',
+ help='Directories or files that should be traversed to '
+ 'find required dependencies for the deps file. '
+ 'Does not generate dependency information for names '
+ 'provided by these files. Only useful in "deps" mode.')
+ parser.add_option('-e',
+ '--exclude',
+ dest='excludes',
+ action='append',
+ help='Files or directories to exclude from the --path '
+ 'and --input flags')
+ parser.add_option('-o',
+ '--output_mode',
+ dest='output_mode',
+ action='store',
+ default='list',
+ help='The type of output to generate from this script. '
+ 'Options are "list" for a list of filenames, "script" '
+ 'for a single script containing the contents of all the '
+ 'file, "deps" to generate a deps.js file for all '
+ 'paths, or "compiled" to produce compiled output with '
+ 'the Closure compiler.')
+ parser.add_option('-c',
+ '--compiler_jar',
+ dest='compiler_jar',
+ action='store',
+ help='The location of the Closure compiler .jar file.')
+ parser.add_option('-f',
+ '--compiler_flag',
+ '--compiler_flags', # for backwards compatability
+ dest='compiler_flags',
+ action='append',
+ help='Additional flag to pass to the Closure compiler. '
+ 'May be specified multiple times to pass multiple flags.')
+ parser.add_option('--output_file',
+ dest='output_file',
+ action='store',
+ help=('If specified, write output to this path instead of '
+ 'writing to standard output.'))
+
+ (options, args) = parser.parse_args()
+
+ search_paths = GetPathsFromOptions(options)
+
+ if options.output_file:
+ out = open(options.output_file, 'w')
+ else:
+ out = sys.stdout
+
+ if options.output_mode == 'deps':
+ result = PrintDeps(search_paths, ExpandDirectories(options.deps or []), out)
+ if not result:
+ logging.error('Could not find Closure Library in the specified paths')
+ sys.exit(1)
+
+ return
+
+ inputs = GetInputsFromOptions(options)
+
+ logging.info('Finding Closure dependencies...')
+ deps = CalculateDependencies(search_paths, inputs)
+ output_mode = options.output_mode
+
+ if output_mode == 'script':
+ PrintScript(deps, out)
+ elif output_mode == 'list':
+ # Just print out a dep per line
+ for dep in deps:
+ PrintLine(dep, out)
+ elif output_mode == 'compiled':
+ # Make sure a .jar is specified.
+ if not options.compiler_jar:
+ logging.error('--compiler_jar flag must be specified if --output is '
+ '"compiled"')
+ sys.exit(1)
+
+ # User friendly version check.
+ if distutils and not (distutils.version.LooseVersion(GetJavaVersion()) >
+ distutils.version.LooseVersion('1.6')):
+ logging.error('Closure Compiler requires Java 1.6 or higher.')
+ logging.error('Please visit http://www.java.com/getjava')
+ sys.exit(1)
+
+ Compile(options.compiler_jar, deps, out, options.compiler_flags)
+
+ else:
+ logging.error('Invalid value for --output flag.')
+ sys.exit(1)
+
+if __name__ == '__main__':
+ main()
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/bin/scopify.py b/third_party/google_input_tools/third_party/closure_library/closure/bin/scopify.py
new file mode 100755
index 0000000..d8057ef
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/bin/scopify.py
@@ -0,0 +1,221 @@
+#!/usr/bin/python
+#
+# Copyright 2010 The Closure Library Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS-IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+"""Automatically converts codebases over to goog.scope.
+
+Usage:
+cd path/to/my/dir;
+../../../../javascript/closure/bin/scopify.py
+
+Scans every file in this directory, recursively. Looks for existing
+goog.scope calls, and goog.require'd symbols. If it makes sense to
+generate a goog.scope call for the file, then we will do so, and
+try to auto-generate some aliases based on the goog.require'd symbols.
+
+Known Issues:
+
+ When a file is goog.scope'd, the file contents will be indented +2.
+ This may put some lines over 80 chars. These will need to be fixed manually.
+
+ We will only try to create aliases for capitalized names. We do not check
+ to see if those names will conflict with any existing locals.
+
+ This creates merge conflicts for every line of every outstanding change.
+ If you intend to run this on your codebase, make sure your team members
+ know. Better yet, send them this script so that they can scopify their
+ outstanding changes and "accept theirs".
+
+ When an alias is "captured", it can no longer be stubbed out for testing.
+ Run your tests.
+
+"""
+
+__author__ = 'nicksantos@google.com (Nick Santos)'
+
+import os.path
+import re
+import sys
+
+REQUIRES_RE = re.compile(r"goog.require\('([^']*)'\)")
+
+# Edit this manually if you want something to "always" be aliased.
+# TODO(nicksantos): Add a flag for this.
+DEFAULT_ALIASES = {}
+
+def Transform(lines):
+ """Converts the contents of a file into javascript that uses goog.scope.
+
+ Arguments:
+ lines: A list of strings, corresponding to each line of the file.
+ Returns:
+ A new list of strings, or None if the file was not modified.
+ """
+ requires = []
+
+ # Do an initial scan to be sure that this file can be processed.
+ for line in lines:
+ # Skip this file if it has already been scopified.
+ if line.find('goog.scope') != -1:
+ return None
+
+ # If there are any global vars or functions, then we also have
+ # to skip the whole file. We might be able to deal with this
+ # more elegantly.
+ if line.find('var ') == 0 or line.find('function ') == 0:
+ return None
+
+ for match in REQUIRES_RE.finditer(line):
+ requires.append(match.group(1))
+
+ if len(requires) == 0:
+ return None
+
+ # Backwards-sort the requires, so that when one is a substring of another,
+ # we match the longer one first.
+ for val in DEFAULT_ALIASES.values():
+ if requires.count(val) == 0:
+ requires.append(val)
+
+ requires.sort()
+ requires.reverse()
+
+ # Generate a map of requires to their aliases
+ aliases_to_globals = DEFAULT_ALIASES.copy()
+ for req in requires:
+ index = req.rfind('.')
+ if index == -1:
+ alias = req
+ else:
+ alias = req[(index + 1):]
+
+ # Don't scopify lowercase namespaces, because they may conflict with
+ # local variables.
+ if alias[0].isupper():
+ aliases_to_globals[alias] = req
+
+ aliases_to_matchers = {}
+ globals_to_aliases = {}
+ for alias, symbol in aliases_to_globals.items():
+ globals_to_aliases[symbol] = alias
+ aliases_to_matchers[alias] = re.compile('\\b%s\\b' % symbol)
+
+ # Insert a goog.scope that aliases all required symbols.
+ result = []
+
+ START = 0
+ SEEN_REQUIRES = 1
+ IN_SCOPE = 2
+
+ mode = START
+ aliases_used = set()
+ insertion_index = None
+ num_blank_lines = 0
+ for line in lines:
+ if mode == START:
+ result.append(line)
+
+ if re.search(REQUIRES_RE, line):
+ mode = SEEN_REQUIRES
+
+ elif mode == SEEN_REQUIRES:
+ if (line and
+ not re.search(REQUIRES_RE, line) and
+ not line.isspace()):
+ # There should be two blank lines before goog.scope
+ result += ['\n'] * 2
+ result.append('goog.scope(function() {\n')
+ insertion_index = len(result)
+ result += ['\n'] * num_blank_lines
+ mode = IN_SCOPE
+ elif line.isspace():
+ # Keep track of the number of blank lines before each block of code so
+ # that we can move them after the goog.scope line if necessary.
+ num_blank_lines += 1
+ else:
+ # Print the blank lines we saw before this code block
+ result += ['\n'] * num_blank_lines
+ num_blank_lines = 0
+ result.append(line)
+
+ if mode == IN_SCOPE:
+ for symbol in requires:
+ if not symbol in globals_to_aliases:
+ continue
+
+ alias = globals_to_aliases[symbol]
+ matcher = aliases_to_matchers[alias]
+ for match in matcher.finditer(line):
+ # Check to make sure we're not in a string.
+ # We do this by being as conservative as possible:
+ # if there are any quote or double quote characters
+ # before the symbol on this line, then bail out.
+ before_symbol = line[:match.start(0)]
+ if before_symbol.count('"') > 0 or before_symbol.count("'") > 0:
+ continue
+
+ line = line.replace(match.group(0), alias)
+ aliases_used.add(alias)
+
+ if line.isspace():
+ # Truncate all-whitespace lines
+ result.append('\n')
+ else:
+ result.append(line)
+
+ if len(aliases_used):
+ aliases_used = [alias for alias in aliases_used]
+ aliases_used.sort()
+ aliases_used.reverse()
+ for alias in aliases_used:
+ symbol = aliases_to_globals[alias]
+ result.insert(insertion_index,
+ 'var %s = %s;\n' % (alias, symbol))
+ result.append('}); // goog.scope\n')
+ return result
+ else:
+ return None
+
+def TransformFileAt(path):
+ """Converts a file into javascript that uses goog.scope.
+
+ Arguments:
+ path: A path to a file.
+ """
+ f = open(path)
+ lines = Transform(f.readlines())
+ if lines:
+ f = open(path, 'w')
+ for l in lines:
+ f.write(l)
+ f.close()
+
+if __name__ == '__main__':
+ args = sys.argv[1:]
+ if not len(args):
+ args = '.'
+
+ for file_name in args:
+ if os.path.isdir(file_name):
+ for root, dirs, files in os.walk(file_name):
+ for name in files:
+ if name.endswith('.js') and \
+ not os.path.islink(os.path.join(root, name)):
+ TransformFileAt(os.path.join(root, name))
+ else:
+ if file_name.endswith('.js') and \
+ not os.path.islink(file_name):
+ TransformFileAt(file_name)
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/a11y/aria/announcer.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/a11y/aria/announcer.js
new file mode 100644
index 0000000..ee87199
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/a11y/aria/announcer.js
@@ -0,0 +1,111 @@
+// Copyright 2007 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+/**
+ * @fileoverview Announcer that allows messages to be spoken by assistive
+ * technologies.
+ */
+
+goog.provide('goog.a11y.aria.Announcer');
+
+goog.require('goog.Disposable');
+goog.require('goog.a11y.aria');
+goog.require('goog.a11y.aria.LivePriority');
+goog.require('goog.a11y.aria.State');
+goog.require('goog.dom');
+goog.require('goog.object');
+
+
+
+/**
+ * Class that allows messages to be spoken by assistive technologies that the
+ * user may have active.
+ *
+ * @param {goog.dom.DomHelper=} opt_domHelper DOM helper.
+ * @constructor
+ * @extends {goog.Disposable}
+ * @final
+ */
+goog.a11y.aria.Announcer = function(opt_domHelper) {
+ goog.a11y.aria.Announcer.base(this, 'constructor');
+
+ /**
+ * @type {goog.dom.DomHelper}
+ * @private
+ */
+ this.domHelper_ = opt_domHelper || goog.dom.getDomHelper();
+
+ /**
+ * Map of priority to live region elements to use for communicating updates.
+ * Elements are created on demand.
+ * @type {Object.<goog.a11y.aria.LivePriority, !Element>}
+ * @private
+ */
+ this.liveRegions_ = {};
+};
+goog.inherits(goog.a11y.aria.Announcer, goog.Disposable);
+
+
+/** @override */
+goog.a11y.aria.Announcer.prototype.disposeInternal = function() {
+ goog.object.forEach(
+ this.liveRegions_, this.domHelper_.removeNode, this.domHelper_);
+ this.liveRegions_ = null;
+ this.domHelper_ = null;
+ goog.a11y.aria.Announcer.base(this, 'disposeInternal');
+};
+
+
+/**
+ * Announce a message to be read by any assistive technologies the user may
+ * have active.
+ * @param {string} message The message to announce to screen readers.
+ * @param {goog.a11y.aria.LivePriority=} opt_priority The priority of the
+ * message. Defaults to POLITE.
+ */
+goog.a11y.aria.Announcer.prototype.say = function(message, opt_priority) {
+ goog.dom.setTextContent(this.getLiveRegion_(
+ opt_priority || goog.a11y.aria.LivePriority.POLITE), message);
+};
+
+
+/**
+ * Returns an aria-live region that can be used to communicate announcements.
+ * @param {!goog.a11y.aria.LivePriority} priority The required priority.
+ * @return {!Element} A live region of the requested priority.
+ * @private
+ */
+goog.a11y.aria.Announcer.prototype.getLiveRegion_ = function(priority) {
+ var liveRegion = this.liveRegions_[priority];
+ if (liveRegion) {
+ // Make sure the live region is not aria-hidden.
+ goog.a11y.aria.removeState(liveRegion, goog.a11y.aria.State.HIDDEN);
+ return liveRegion;
+ }
+ liveRegion = this.domHelper_.createElement('div');
+ // Note that IE has a habit of declaring things that aren't display:none as
+ // invisible to third-party tools like JAWs, so we can't just use height:0.
+ liveRegion.style.position = 'absolute';
+ liveRegion.style.top = '-1000px';
+ liveRegion.style.height = '1px';
+ liveRegion.style.overflow = 'hidden';
+ goog.a11y.aria.setState(liveRegion, goog.a11y.aria.State.LIVE,
+ priority);
+ goog.a11y.aria.setState(liveRegion, goog.a11y.aria.State.ATOMIC,
+ 'true');
+ this.domHelper_.getDocument().body.appendChild(liveRegion);
+ this.liveRegions_[priority] = liveRegion;
+ return liveRegion;
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/a11y/aria/aria.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/a11y/aria/aria.js
new file mode 100644
index 0000000..2774529
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/a11y/aria/aria.js
@@ -0,0 +1,364 @@
+// Copyright 2007 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+/**
+ * @fileoverview Utilities for adding, removing and setting ARIA roles and
+ * states as defined by W3C ARIA standard: http://www.w3.org/TR/wai-aria/
+ * All modern browsers have some form of ARIA support, so no browser checks are
+ * performed when adding ARIA to components.
+ *
+ */
+
+goog.provide('goog.a11y.aria');
+
+goog.require('goog.a11y.aria.Role');
+goog.require('goog.a11y.aria.State');
+goog.require('goog.a11y.aria.datatables');
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.dom');
+goog.require('goog.dom.TagName');
+goog.require('goog.object');
+
+
+/**
+ * ARIA states/properties prefix.
+ * @private
+ */
+goog.a11y.aria.ARIA_PREFIX_ = 'aria-';
+
+
+/**
+ * ARIA role attribute.
+ * @private
+ */
+goog.a11y.aria.ROLE_ATTRIBUTE_ = 'role';
+
+
+/**
+ * A list of tag names for which we don't need to set ARIA role and states
+ * because they have well supported semantics for screen readers or because
+ * they don't contain content to be made accessible.
+ * @private
+ */
+goog.a11y.aria.TAGS_WITH_ASSUMED_ROLES_ = [
+ goog.dom.TagName.A,
+ goog.dom.TagName.AREA,
+ goog.dom.TagName.BUTTON,
+ goog.dom.TagName.HEAD,
+ goog.dom.TagName.INPUT,
+ goog.dom.TagName.LINK,
+ goog.dom.TagName.MENU,
+ goog.dom.TagName.META,
+ goog.dom.TagName.OPTGROUP,
+ goog.dom.TagName.OPTION,
+ goog.dom.TagName.PROGRESS,
+ goog.dom.TagName.STYLE,
+ goog.dom.TagName.SELECT,
+ goog.dom.TagName.SOURCE,
+ goog.dom.TagName.TEXTAREA,
+ goog.dom.TagName.TITLE,
+ goog.dom.TagName.TRACK
+];
+
+
+/**
+ * Sets the role of an element. If the roleName is
+ * empty string or null, the role for the element is removed.
+ * We encourage clients to call the goog.a11y.aria.removeRole
+ * method instead of setting null and empty string values.
+ * Special handling for this case is added to ensure
+ * backword compatibility with existing code.
+ *
+ * @param {!Element} element DOM node to set role of.
+ * @param {!goog.a11y.aria.Role|string} roleName role name(s).
+ */
+goog.a11y.aria.setRole = function(element, roleName) {
+ if (!roleName) {
+ // Setting the ARIA role to empty string is not allowed
+ // by the ARIA standard.
+ goog.a11y.aria.removeRole(element);
+ } else {
+ if (goog.asserts.ENABLE_ASSERTS) {
+ goog.asserts.assert(goog.object.containsValue(
+ goog.a11y.aria.Role, roleName), 'No such ARIA role ' + roleName);
+ }
+ element.setAttribute(goog.a11y.aria.ROLE_ATTRIBUTE_, roleName);
+ }
+};
+
+
+/**
+ * Gets role of an element.
+ * @param {!Element} element DOM element to get role of.
+ * @return {!goog.a11y.aria.Role} ARIA Role name.
+ */
+goog.a11y.aria.getRole = function(element) {
+ var role = element.getAttribute(goog.a11y.aria.ROLE_ATTRIBUTE_);
+ return /** @type {goog.a11y.aria.Role} */ (role) || null;
+};
+
+
+/**
+ * Removes role of an element.
+ * @param {!Element} element DOM element to remove the role from.
+ */
+goog.a11y.aria.removeRole = function(element) {
+ element.removeAttribute(goog.a11y.aria.ROLE_ATTRIBUTE_);
+};
+
+
+/**
+ * Sets the state or property of an element.
+ * @param {!Element} element DOM node where we set state.
+ * @param {!(goog.a11y.aria.State|string)} stateName State attribute being set.
+ * Automatically adds prefix 'aria-' to the state name if the attribute is
+ * not an extra attribute.
+ * @param {string|boolean|number|!goog.array.ArrayLike.<string>} value Value
+ * for the state attribute.
+ */
+goog.a11y.aria.setState = function(element, stateName, value) {
+ if (goog.isArrayLike(value)) {
+ var array = /** @type {!goog.array.ArrayLike.<string>} */ (value);
+ value = array.join(' ');
+ }
+ var attrStateName = goog.a11y.aria.getAriaAttributeName_(stateName);
+ if (value === '' || value == undefined) {
+ var defaultValueMap = goog.a11y.aria.datatables.getDefaultValuesMap();
+ // Work around for browsers that don't properly support ARIA.
+ // According to the ARIA W3C standard, user agents should allow
+ // setting empty value which results in setting the default value
+ // for the ARIA state if such exists. The exact text from the ARIA W3C
+ // standard (http://www.w3.org/TR/wai-aria/states_and_properties):
+ // "When a value is indicated as the default, the user agent
+ // MUST follow the behavior prescribed by this value when the state or
+ // property is empty or undefined."
+ // The defaultValueMap contains the default values for the ARIA states
+ // and has as a key the goog.a11y.aria.State constant for the state.
+ if (stateName in defaultValueMap) {
+ element.setAttribute(attrStateName, defaultValueMap[stateName]);
+ } else {
+ element.removeAttribute(attrStateName);
+ }
+ } else {
+ element.setAttribute(attrStateName, value);
+ }
+};
+
+
+/**
+ * Remove the state or property for the element.
+ * @param {!Element} element DOM node where we set state.
+ * @param {!goog.a11y.aria.State} stateName State name.
+ */
+goog.a11y.aria.removeState = function(element, stateName) {
+ element.removeAttribute(goog.a11y.aria.getAriaAttributeName_(stateName));
+};
+
+
+/**
+ * Gets value of specified state or property.
+ * @param {!Element} element DOM node to get state from.
+ * @param {!goog.a11y.aria.State|string} stateName State name.
+ * @return {string} Value of the state attribute.
+ */
+goog.a11y.aria.getState = function(element, stateName) {
+ // TODO(user): return properly typed value result --
+ // boolean, number, string, null. We should be able to chain
+ // getState(...) and setState(...) methods.
+
+ var attr =
+ /** @type {string|number|boolean} */ (element.getAttribute(
+ goog.a11y.aria.getAriaAttributeName_(stateName)));
+ var isNullOrUndefined = attr == null || attr == undefined;
+ return isNullOrUndefined ? '' : String(attr);
+};
+
+
+/**
+ * Returns the activedescendant element for the input element by
+ * using the activedescendant ARIA property of the given element.
+ * @param {!Element} element DOM node to get activedescendant
+ * element for.
+ * @return {?Element} DOM node of the activedescendant, if found.
+ */
+goog.a11y.aria.getActiveDescendant = function(element) {
+ var id = goog.a11y.aria.getState(
+ element, goog.a11y.aria.State.ACTIVEDESCENDANT);
+ return goog.dom.getOwnerDocument(element).getElementById(id);
+};
+
+
+/**
+ * Sets the activedescendant ARIA property value for an element.
+ * If the activeElement is not null, it should have an id set.
+ * @param {!Element} element DOM node to set activedescendant ARIA property to.
+ * @param {?Element} activeElement DOM node being set as activedescendant.
+ */
+goog.a11y.aria.setActiveDescendant = function(element, activeElement) {
+ var id = '';
+ if (activeElement) {
+ id = activeElement.id;
+ goog.asserts.assert(id, 'The active element should have an id.');
+ }
+
+ goog.a11y.aria.setState(element, goog.a11y.aria.State.ACTIVEDESCENDANT, id);
+};
+
+
+/**
+ * Gets the label of the given element.
+ * @param {!Element} element DOM node to get label from.
+ * @return {string} label The label.
+ */
+goog.a11y.aria.getLabel = function(element) {
+ return goog.a11y.aria.getState(element, goog.a11y.aria.State.LABEL);
+};
+
+
+/**
+ * Sets the label of the given element.
+ * @param {!Element} element DOM node to set label to.
+ * @param {string} label The label to set.
+ */
+goog.a11y.aria.setLabel = function(element, label) {
+ goog.a11y.aria.setState(element, goog.a11y.aria.State.LABEL, label);
+};
+
+
+/**
+ * Asserts that the element has a role set if it's not an HTML element whose
+ * semantics is well supported by most screen readers.
+ * Only to be used internally by the ARIA library in goog.a11y.aria.*.
+ * @param {!Element} element The element to assert an ARIA role set.
+ * @param {!goog.array.ArrayLike.<string>} allowedRoles The child roles of
+ * the roles.
+ */
+goog.a11y.aria.assertRoleIsSetInternalUtil = function(element, allowedRoles) {
+ if (goog.array.contains(goog.a11y.aria.TAGS_WITH_ASSUMED_ROLES_,
+ element.tagName)) {
+ return;
+ }
+ var elementRole = /** @type {string}*/ (goog.a11y.aria.getRole(element));
+ goog.asserts.assert(elementRole != null,
+ 'The element ARIA role cannot be null.');
+
+ goog.asserts.assert(goog.array.contains(allowedRoles, elementRole),
+ 'Non existing or incorrect role set for element.' +
+ 'The role set is "' + elementRole +
+ '". The role should be any of "' + allowedRoles +
+ '". Check the ARIA specification for more details ' +
+ 'http://www.w3.org/TR/wai-aria/roles.');
+};
+
+
+/**
+ * Gets the boolean value of an ARIA state/property.
+ * @param {!Element} element The element to get the ARIA state for.
+ * @param {!goog.a11y.aria.State|string} stateName the ARIA state name.
+ * @return {?boolean} Boolean value for the ARIA state value or null if
+ * the state value is not 'true', not 'false', or not set.
+ */
+goog.a11y.aria.getStateBoolean = function(element, stateName) {
+ var attr =
+ /** @type {string|boolean} */ (element.getAttribute(
+ goog.a11y.aria.getAriaAttributeName_(stateName)));
+ goog.asserts.assert(
+ goog.isBoolean(attr) || attr == null || attr == 'true' ||
+ attr == 'false');
+ if (attr == null) {
+ return attr;
+ }
+ return goog.isBoolean(attr) ? attr : attr == 'true';
+};
+
+
+/**
+ * Gets the number value of an ARIA state/property.
+ * @param {!Element} element The element to get the ARIA state for.
+ * @param {!goog.a11y.aria.State|string} stateName the ARIA state name.
+ * @return {?number} Number value for the ARIA state value or null if
+ * the state value is not a number or not set.
+ */
+goog.a11y.aria.getStateNumber = function(element, stateName) {
+ var attr =
+ /** @type {string|number} */ (element.getAttribute(
+ goog.a11y.aria.getAriaAttributeName_(stateName)));
+ goog.asserts.assert((attr == null || !isNaN(Number(attr))) &&
+ !goog.isBoolean(attr));
+ return attr == null ? null : Number(attr);
+};
+
+
+/**
+ * Gets the string value of an ARIA state/property.
+ * @param {!Element} element The element to get the ARIA state for.
+ * @param {!goog.a11y.aria.State|string} stateName the ARIA state name.
+ * @return {?string} String value for the ARIA state value or null if
+ * the state value is empty string or not set.
+ */
+goog.a11y.aria.getStateString = function(element, stateName) {
+ var attr = element.getAttribute(
+ goog.a11y.aria.getAriaAttributeName_(stateName));
+ goog.asserts.assert((attr == null || goog.isString(attr)) &&
+ isNaN(Number(attr)) && attr != 'true' && attr != 'false');
+ return attr == null ? null : attr;
+};
+
+
+/**
+ * Gets array of strings value of the specified state or
+ * property for the element.
+ * Only to be used internally by the ARIA library in goog.a11y.aria.*.
+ * @param {!Element} element DOM node to get state from.
+ * @param {!goog.a11y.aria.State} stateName State name.
+ * @return {!goog.array.ArrayLike.<string>} string Array
+ * value of the state attribute.
+ */
+goog.a11y.aria.getStringArrayStateInternalUtil = function(element, stateName) {
+ var attrValue = element.getAttribute(
+ goog.a11y.aria.getAriaAttributeName_(stateName));
+ return goog.a11y.aria.splitStringOnWhitespace_(attrValue);
+};
+
+
+/**
+ * Splits the input stringValue on whitespace.
+ * @param {string} stringValue The value of the string to split.
+ * @return {!goog.array.ArrayLike.<string>} string Array
+ * value as result of the split.
+ * @private
+ */
+goog.a11y.aria.splitStringOnWhitespace_ = function(stringValue) {
+ return stringValue ? stringValue.split(/\s+/) : [];
+};
+
+
+/**
+ * Adds the 'aria-' prefix to ariaName.
+ * @param {string} ariaName ARIA state/property name.
+ * @private
+ * @return {string} The ARIA attribute name with added 'aria-' prefix.
+ * @throws {Error} If no such attribute exists.
+ */
+goog.a11y.aria.getAriaAttributeName_ = function(ariaName) {
+ if (goog.asserts.ENABLE_ASSERTS) {
+ goog.asserts.assert(ariaName, 'ARIA attribute cannot be empty.');
+ goog.asserts.assert(goog.object.containsValue(
+ goog.a11y.aria.State, ariaName),
+ 'No such ARIA attribute ' + ariaName);
+ }
+ return goog.a11y.aria.ARIA_PREFIX_ + ariaName;
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/a11y/aria/attributes.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/a11y/aria/attributes.js
new file mode 100644
index 0000000..f4e0a3d
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/a11y/aria/attributes.js
@@ -0,0 +1,389 @@
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+/**
+ * @fileoverview The file contains generated enumerations for ARIA states
+ * and properties as defined by W3C ARIA standard:
+ * http://www.w3.org/TR/wai-aria/.
+ *
+ * This is auto-generated code. Do not manually edit! For more details
+ * about how to edit it via the generator check go/closure-ariagen.
+ */
+
+goog.provide('goog.a11y.aria.AutoCompleteValues');
+goog.provide('goog.a11y.aria.CheckedValues');
+goog.provide('goog.a11y.aria.DropEffectValues');
+goog.provide('goog.a11y.aria.ExpandedValues');
+goog.provide('goog.a11y.aria.GrabbedValues');
+goog.provide('goog.a11y.aria.InvalidValues');
+goog.provide('goog.a11y.aria.LivePriority');
+goog.provide('goog.a11y.aria.OrientationValues');
+goog.provide('goog.a11y.aria.PressedValues');
+goog.provide('goog.a11y.aria.RelevantValues');
+goog.provide('goog.a11y.aria.SelectedValues');
+goog.provide('goog.a11y.aria.SortValues');
+goog.provide('goog.a11y.aria.State');
+
+
+/**
+ * ARIA states and properties.
+ * @enum {string}
+ */
+goog.a11y.aria.State = {
+ // ARIA property for setting the currently active descendant of an element,
+ // for example the selected item in a list box. Value: ID of an element.
+ ACTIVEDESCENDANT: 'activedescendant',
+
+ // ARIA property that, if true, indicates that all of a changed region should
+ // be presented, instead of only parts. Value: one of {true, false}.
+ ATOMIC: 'atomic',
+
+ // ARIA property to specify that input completion is provided. Value:
+ // one of {'inline', 'list', 'both', 'none'}.
+ AUTOCOMPLETE: 'autocomplete',
+
+ // ARIA state to indicate that an element and its subtree are being updated.
+ // Value: one of {true, false}.
+ BUSY: 'busy',
+
+ // ARIA state for a checked item. Value: one of {'true', 'false', 'mixed',
+ // undefined}.
+ CHECKED: 'checked',
+
+ // ARIA property that identifies the element or elements whose contents or
+ // presence are controlled by this element.
+ // Value: space-separated IDs of other elements.
+ CONTROLS: 'controls',
+
+ // ARIA property that identifies the element or elements that describe
+ // this element. Value: space-separated IDs of other elements.
+ DESCRIBEDBY: 'describedby',
+
+ // ARIA state for a disabled item. Value: one of {true, false}.
+ DISABLED: 'disabled',
+
+ // ARIA property that indicates what functions can be performed when a
+ // dragged object is released on the drop target. Value: one of
+ // {'copy', 'move', 'link', 'execute', 'popup', 'none'}.
+ DROPEFFECT: 'dropeffect',
+
+ // ARIA state for setting whether the element like a tree node is expanded.
+ // Value: one of {true, false, undefined}.
+ EXPANDED: 'expanded',
+
+ // ARIA property that identifies the next element (or elements) in the
+ // recommended reading order of content. Value: space-separated ids of
+ // elements to flow to.
+ FLOWTO: 'flowto',
+
+ // ARIA state that indicates an element's "grabbed" state in drag-and-drop.
+ // Value: one of {true, false, undefined}.
+ GRABBED: 'grabbed',
+
+ // ARIA property indicating whether the element has a popup.
+ // Value: one of {true, false}.
+ HASPOPUP: 'haspopup',
+
+ // ARIA state indicating that the element is not visible or perceivable
+ // to any user. Value: one of {true, false}.
+ HIDDEN: 'hidden',
+
+ // ARIA state indicating that the entered value does not conform. Value:
+ // one of {false, true, 'grammar', 'spelling'}
+ INVALID: 'invalid',
+
+ // ARIA property that provides a label to override any other text, value, or
+ // contents used to describe this element. Value: string.
+ LABEL: 'label',
+
+ // ARIA property for setting the element which labels another element.
+ // Value: space-separated IDs of elements.
+ LABELLEDBY: 'labelledby',
+
+ // ARIA property for setting the level of an element in the hierarchy.
+ // Value: integer.
+ LEVEL: 'level',
+
+ // ARIA property indicating that an element will be updated, and
+ // describes the types of updates the user agents, assistive technologies,
+ // and user can expect from the live region. Value: one of {'off', 'polite',
+ // 'assertive'}.
+ LIVE: 'live',
+
+ // ARIA property indicating whether a text box can accept multiline input.
+ // Value: one of {true, false}.
+ MULTILINE: 'multiline',
+
+ // ARIA property indicating if the user may select more than one item.
+ // Value: one of {true, false}.
+ MULTISELECTABLE: 'multiselectable',
+
+ // ARIA property indicating if the element is horizontal or vertical.
+ // Value: one of {'vertical', 'horizontal'}.
+ ORIENTATION: 'orientation',
+
+ // ARIA property creating a visual, functional, or contextual parent/child
+ // relationship when the DOM hierarchy can't be used to represent it.
+ // Value: Space-separated IDs of elements.
+ OWNS: 'owns',
+
+ // ARIA property that defines an element's number of position in a list.
+ // Value: integer.
+ POSINSET: 'posinset',
+
+ // ARIA state for a pressed item.
+ // Value: one of {true, false, undefined, 'mixed'}.
+ PRESSED: 'pressed',
+
+ // ARIA property indicating that an element is not editable.
+ // Value: one of {true, false}.
+ READONLY: 'readonly',
+
+ // ARIA property indicating that change notifications within this subtree
+ // of a live region should be announced. Value: one of {'additions',
+ // 'removals', 'text', 'all', 'additions text'}.
+ RELEVANT: 'relevant',
+
+ // ARIA property indicating that user input is required on this element
+ // before a form may be submitted. Value: one of {true, false}.
+ REQUIRED: 'required',
+
+ // ARIA state for setting the currently selected item in the list.
+ // Value: one of {true, false, undefined}.
+ SELECTED: 'selected',
+
+ // ARIA property defining the number of items in a list. Value: integer.
+ SETSIZE: 'setsize',
+
+ // ARIA property indicating if items are sorted. Value: one of {'ascending',
+ // 'descending', 'none', 'other'}.
+ SORT: 'sort',
+
+ // ARIA property for slider maximum value. Value: number.
+ VALUEMAX: 'valuemax',
+
+ // ARIA property for slider minimum value. Value: number.
+ VALUEMIN: 'valuemin',
+
+ // ARIA property for slider active value. Value: number.
+ VALUENOW: 'valuenow',
+
+ // ARIA property for slider active value represented as text.
+ // Value: string.
+ VALUETEXT: 'valuetext'
+};
+
+
+/**
+ * ARIA state values for AutoCompleteValues.
+ * @enum {string}
+ */
+goog.a11y.aria.AutoCompleteValues = {
+ // The system provides text after the caret as a suggestion
+ // for how to complete the field.
+ INLINE: 'inline',
+ // A list of choices appears from which the user can choose,
+ // but the edit box retains focus.
+ LIST: 'list',
+ // A list of choices appears and the currently selected suggestion
+ // also appears inline.
+ BOTH: 'both',
+ // No input completion suggestions are provided.
+ NONE: 'none'
+};
+
+
+/**
+ * ARIA state values for DropEffectValues.
+ * @enum {string}
+ */
+goog.a11y.aria.DropEffectValues = {
+ // A duplicate of the source object will be dropped into the target.
+ COPY: 'copy',
+ // The source object will be removed from its current location
+ // and dropped into the target.
+ MOVE: 'move',
+ // A reference or shortcut to the dragged object
+ // will be created in the target object.
+ LINK: 'link',
+ // A function supported by the drop target is
+ // executed, using the drag source as an input.
+ EXECUTE: 'execute',
+ // There is a popup menu or dialog that allows the user to choose
+ // one of the drag operations (copy, move, link, execute) and any other
+ // drag functionality, such as cancel.
+ POPUP: 'popup',
+ // No operation can be performed; effectively
+ // cancels the drag operation if an attempt is made to drop on this object.
+ NONE: 'none'
+};
+
+
+/**
+ * ARIA state values for LivePriority.
+ * @enum {string}
+ */
+goog.a11y.aria.LivePriority = {
+ // Updates to the region will not be presented to the user
+ // unless the assitive technology is currently focused on that region.
+ OFF: 'off',
+ // (Background change) Assistive technologies SHOULD announce
+ // updates at the next graceful opportunity, such as at the end of
+ // speaking the current sentence or when the user pauses typing.
+ POLITE: 'polite',
+ // This information has the highest priority and assistive
+ // technologies SHOULD notify the user immediately.
+ // Because an interruption may disorient users or cause them to not complete
+ // their current task, authors SHOULD NOT use the assertive value unless the
+ // interruption is imperative.
+ ASSERTIVE: 'assertive'
+};
+
+
+/**
+ * ARIA state values for OrientationValues.
+ * @enum {string}
+ */
+goog.a11y.aria.OrientationValues = {
+ // The element is oriented vertically.
+ VERTICAL: 'vertical',
+ // The element is oriented horizontally.
+ HORIZONTAL: 'horizontal'
+};
+
+
+/**
+ * ARIA state values for RelevantValues.
+ * @enum {string}
+ */
+goog.a11y.aria.RelevantValues = {
+ // Element nodes are added to the DOM within the live region.
+ ADDITIONS: 'additions',
+ // Text or element nodes within the live region are removed from the DOM.
+ REMOVALS: 'removals',
+ // Text is added to any DOM descendant nodes of the live region.
+ TEXT: 'text',
+ // Equivalent to the combination of all values, "additions removals text".
+ ALL: 'all'
+};
+
+
+/**
+ * ARIA state values for SortValues.
+ * @enum {string}
+ */
+goog.a11y.aria.SortValues = {
+ // Items are sorted in ascending order by this column.
+ ASCENDING: 'ascending',
+ // Items are sorted in descending order by this column.
+ DESCENDING: 'descending',
+ // There is no defined sort applied to the column.
+ NONE: 'none',
+ // A sort algorithm other than ascending or descending has been applied.
+ OTHER: 'other'
+};
+
+
+/**
+ * ARIA state values for CheckedValues.
+ * @enum {string}
+ */
+goog.a11y.aria.CheckedValues = {
+ // The selectable element is checked.
+ TRUE: 'true',
+ // The selectable element is not checked.
+ FALSE: 'false',
+ // Indicates a mixed mode value for a tri-state
+ // checkbox or menuitemcheckbox.
+ MIXED: 'mixed',
+ // The element does not support being checked.
+ UNDEFINED: 'undefined'
+};
+
+
+/**
+ * ARIA state values for ExpandedValues.
+ * @enum {string}
+ */
+goog.a11y.aria.ExpandedValues = {
+ // The element, or another grouping element it controls, is expanded.
+ TRUE: 'true',
+ // The element, or another grouping element it controls, is collapsed.
+ FALSE: 'false',
+ // The element, or another grouping element
+ // it controls, is neither expandable nor collapsible; all its
+ // child elements are shown or there are no child elements.
+ UNDEFINED: 'undefined'
+};
+
+
+/**
+ * ARIA state values for GrabbedValues.
+ * @enum {string}
+ */
+goog.a11y.aria.GrabbedValues = {
+ // Indicates that the element has been "grabbed" for dragging.
+ TRUE: 'true',
+ // Indicates that the element supports being dragged.
+ FALSE: 'false',
+ // Indicates that the element does not support being dragged.
+ UNDEFINED: 'undefined'
+};
+
+
+/**
+ * ARIA state values for InvalidValues.
+ * @enum {string}
+ */
+goog.a11y.aria.InvalidValues = {
+ // There are no detected errors in the value.
+ FALSE: 'false',
+ // The value entered by the user has failed validation.
+ TRUE: 'true',
+ // A grammatical error was detected.
+ GRAMMAR: 'grammar',
+ // A spelling error was detected.
+ SPELLING: 'spelling'
+};
+
+
+/**
+ * ARIA state values for PressedValues.
+ * @enum {string}
+ */
+goog.a11y.aria.PressedValues = {
+ // The element is pressed.
+ TRUE: 'true',
+ // The element supports being pressed but is not currently pressed.
+ FALSE: 'false',
+ // Indicates a mixed mode value for a tri-state toggle button.
+ MIXED: 'mixed',
+ // The element does not support being pressed.
+ UNDEFINED: 'undefined'
+};
+
+
+/**
+ * ARIA state values for SelectedValues.
+ * @enum {string}
+ */
+goog.a11y.aria.SelectedValues = {
+ // The selectable element is selected.
+ TRUE: 'true',
+ // The selectable element is not selected.
+ FALSE: 'false',
+ // The element is not selectable.
+ UNDEFINED: 'undefined'
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/a11y/aria/datatables.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/a11y/aria/datatables.js
new file mode 100644
index 0000000..05e2fb9
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/a11y/aria/datatables.js
@@ -0,0 +1,68 @@
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+
+/**
+ * @fileoverview The file contains data tables generated from the ARIA
+ * standard schema http://www.w3.org/TR/wai-aria/.
+ *
+ * This is auto-generated code. Do not manually edit!
+ */
+
+goog.provide('goog.a11y.aria.datatables');
+
+goog.require('goog.a11y.aria.State');
+goog.require('goog.object');
+
+
+/**
+ * A map that contains mapping between an ARIA state and the default value
+ * for it. Note that not all ARIA states have default values.
+ *
+ * @type {Object.<!(goog.a11y.aria.State|string), (string|boolean|number)>}
+ */
+goog.a11y.aria.DefaultStateValueMap_;
+
+
+/**
+ * A method that creates a map that contains mapping between an ARIA state and
+ * the default value for it. Note that not all ARIA states have default values.
+ *
+ * @return {!Object.<!(goog.a11y.aria.State|string), (string|boolean|number)>}
+ * The names for each of the notification methods.
+ */
+goog.a11y.aria.datatables.getDefaultValuesMap = function() {
+ if (!goog.a11y.aria.DefaultStateValueMap_) {
+ goog.a11y.aria.DefaultStateValueMap_ = goog.object.create(
+ goog.a11y.aria.State.ATOMIC, false,
+ goog.a11y.aria.State.AUTOCOMPLETE, 'none',
+ goog.a11y.aria.State.DROPEFFECT, 'none',
+ goog.a11y.aria.State.HASPOPUP, false,
+ goog.a11y.aria.State.LIVE, 'off',
+ goog.a11y.aria.State.MULTILINE, false,
+ goog.a11y.aria.State.MULTISELECTABLE, false,
+ goog.a11y.aria.State.ORIENTATION, 'vertical',
+ goog.a11y.aria.State.READONLY, false,
+ goog.a11y.aria.State.RELEVANT, 'additions text',
+ goog.a11y.aria.State.REQUIRED, false,
+ goog.a11y.aria.State.SORT, 'none',
+ goog.a11y.aria.State.BUSY, false,
+ goog.a11y.aria.State.DISABLED, false,
+ goog.a11y.aria.State.HIDDEN, false,
+ goog.a11y.aria.State.INVALID, 'false');
+ }
+
+ return goog.a11y.aria.DefaultStateValueMap_;
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/a11y/aria/roles.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/a11y/aria/roles.js
new file mode 100644
index 0000000..a282cc2
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/a11y/aria/roles.js
@@ -0,0 +1,216 @@
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+/**
+ * @fileoverview The file contains generated enumerations for ARIA roles
+ * as defined by W3C ARIA standard: http://www.w3.org/TR/wai-aria/.
+ *
+ * This is auto-generated code. Do not manually edit! For more details
+ * about how to edit it via the generator check go/closure-ariagen.
+ */
+
+goog.provide('goog.a11y.aria.Role');
+
+
+/**
+ * ARIA role values.
+ * @enum {string}
+ */
+goog.a11y.aria.Role = {
+ // ARIA role for an alert element that doesn't need to be explicitly closed.
+ ALERT: 'alert',
+
+ // ARIA role for an alert dialog element that takes focus and must be closed.
+ ALERTDIALOG: 'alertdialog',
+
+ // ARIA role for an application that implements its own keyboard navigation.
+ APPLICATION: 'application',
+
+ // ARIA role for an article.
+ ARTICLE: 'article',
+
+ // ARIA role for a banner containing mostly site content, not page content.
+ BANNER: 'banner',
+
+ // ARIA role for a button element.
+ BUTTON: 'button',
+
+ // ARIA role for a checkbox button element; use with the CHECKED state.
+ CHECKBOX: 'checkbox',
+
+ // ARIA role for a column header of a table or grid.
+ COLUMNHEADER: 'columnheader',
+
+ // ARIA role for a combo box element.
+ COMBOBOX: 'combobox',
+
+ // ARIA role for a supporting section of the document.
+ COMPLEMENTARY: 'complementary',
+
+ // ARIA role for a large perceivable region that contains information
+ // about the parent document.
+ CONTENTINFO: 'contentinfo',
+
+ // ARIA role for a definition of a term or concept.
+ DEFINITION: 'definition',
+
+ // ARIA role for a dialog, some descendant must take initial focus.
+ DIALOG: 'dialog',
+
+ // ARIA role for a directory, like a table of contents.
+ DIRECTORY: 'directory',
+
+ // ARIA role for a part of a page that's a document, not a web application.
+ DOCUMENT: 'document',
+
+ // ARIA role for a landmark region logically considered one form.
+ FORM: 'form',
+
+ // ARIA role for an interactive control of tabular data.
+ GRID: 'grid',
+
+ // ARIA role for a cell in a grid.
+ GRIDCELL: 'gridcell',
+
+ // ARIA role for a group of related elements like tree item siblings.
+ GROUP: 'group',
+
+ // ARIA role for a heading element.
+ HEADING: 'heading',
+
+ // ARIA role for a container of elements that together comprise one image.
+ IMG: 'img',
+
+ // ARIA role for a link.
+ LINK: 'link',
+
+ // ARIA role for a list of non-interactive list items.
+ LIST: 'list',
+
+ // ARIA role for a listbox.
+ LISTBOX: 'listbox',
+
+ // ARIA role for a list item.
+ LISTITEM: 'listitem',
+
+ // ARIA role for a live region where new information is added.
+ LOG: 'log',
+
+ // ARIA landmark role for the main content in a document. Use only once.
+ MAIN: 'main',
+
+ // ARIA role for a live region of non-essential information that changes.
+ MARQUEE: 'marquee',
+
+ // ARIA role for a mathematical expression.
+ MATH: 'math',
+
+ // ARIA role for a popup menu.
+ MENU: 'menu',
+
+ // ARIA role for a menubar element containing menu elements.
+ MENUBAR: 'menubar',
+
+ // ARIA role for menu item elements.
+ MENU_ITEM: 'menuitem',
+
+ // ARIA role for a checkbox box element inside a menu.
+ MENU_ITEM_CHECKBOX: 'menuitemcheckbox',
+
+ // ARIA role for a radio button element inside a menu.
+ MENU_ITEM_RADIO: 'menuitemradio',
+
+ // ARIA landmark role for a collection of navigation links.
+ NAVIGATION: 'navigation',
+
+ // ARIA role for a section ancillary to the main content.
+ NOTE: 'note',
+
+ // ARIA role for option items that are children of combobox, listbox, menu,
+ // radiogroup, or tree elements.
+ OPTION: 'option',
+
+ // ARIA role for ignorable cosmetic elements with no semantic significance.
+ PRESENTATION: 'presentation',
+
+ // ARIA role for a progress bar element.
+ PROGRESSBAR: 'progressbar',
+
+ // ARIA role for a radio button element.
+ RADIO: 'radio',
+
+ // ARIA role for a group of connected radio button elements.
+ RADIOGROUP: 'radiogroup',
+
+ // ARIA role for an important region of the page.
+ REGION: 'region',
+
+ // ARIA role for a row of cells in a grid.
+ ROW: 'row',
+
+ // ARIA role for a group of one or more rows in a grid.
+ ROWGROUP: 'rowgroup',
+
+ // ARIA role for a row header of a table or grid.
+ ROWHEADER: 'rowheader',
+
+ // ARIA role for a scrollbar element.
+ SCROLLBAR: 'scrollbar',
+
+ // ARIA landmark role for a part of the page providing search functionality.
+ SEARCH: 'search',
+
+ // ARIA role for a menu separator.
+ SEPARATOR: 'separator',
+
+ // ARIA role for a slider.
+ SLIDER: 'slider',
+
+ // ARIA role for a spin button.
+ SPINBUTTON: 'spinbutton',
+
+ // ARIA role for a live region with advisory info less severe than an alert.
+ STATUS: 'status',
+
+ // ARIA role for a tab button.
+ TAB: 'tab',
+
+ // ARIA role for a tab bar (i.e. a list of tab buttons).
+ TAB_LIST: 'tablist',
+
+ // ARIA role for a tab page (i.e. the element holding tab contents).
+ TAB_PANEL: 'tabpanel',
+
+ // ARIA role for a textbox element.
+ TEXTBOX: 'textbox',
+
+ // ARIA role for an element displaying elapsed time or time remaining.
+ TIMER: 'timer',
+
+ // ARIA role for a toolbar element.
+ TOOLBAR: 'toolbar',
+
+ // ARIA role for a tooltip element.
+ TOOLTIP: 'tooltip',
+
+ // ARIA role for a tree.
+ TREE: 'tree',
+
+ // ARIA role for a grid whose rows can be expanded and collapsed like a tree.
+ TREEGRID: 'treegrid',
+
+ // ARIA role for a tree item that sometimes may be expanded or collapsed.
+ TREEITEM: 'treeitem'
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/array/array.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/array/array.js
new file mode 100644
index 0000000..2d8c23f
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/array/array.js
@@ -0,0 +1,1570 @@
+// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Utilities for manipulating arrays.
+ *
+ */
+
+
+goog.provide('goog.array');
+goog.provide('goog.array.ArrayLike');
+
+goog.require('goog.asserts');
+
+
+/**
+ * @define {boolean} NATIVE_ARRAY_PROTOTYPES indicates whether the code should
+ * rely on Array.prototype functions, if available.
+ *
+ * The Array.prototype functions can be defined by external libraries like
+ * Prototype and setting this flag to false forces closure to use its own
+ * goog.array implementation.
+ *
+ * If your javascript can be loaded by a third party site and you are wary about
+ * relying on the prototype functions, specify
+ * "--define goog.NATIVE_ARRAY_PROTOTYPES=false" to the JSCompiler.
+ *
+ * Setting goog.TRUSTED_SITE to false will automatically set
+ * NATIVE_ARRAY_PROTOTYPES to false.
+ */
+goog.define('goog.NATIVE_ARRAY_PROTOTYPES', goog.TRUSTED_SITE);
+
+
+/**
+ * @define {boolean} If true, JSCompiler will use the native implementation of
+ * array functions where appropriate (e.g., {@code Array#filter}) and remove the
+ * unused pure JS implementation.
+ */
+goog.define('goog.array.ASSUME_NATIVE_FUNCTIONS', false);
+
+
+/**
+ * @typedef {Array|NodeList|Arguments|{length: number}}
+ */
+goog.array.ArrayLike;
+
+
+/**
+ * Returns the last element in an array without removing it.
+ * Same as goog.array.last.
+ * @param {Array.<T>|goog.array.ArrayLike} array The array.
+ * @return {T} Last item in array.
+ * @template T
+ */
+goog.array.peek = function(array) {
+ return array[array.length - 1];
+};
+
+
+/**
+ * Returns the last element in an array without removing it.
+ * Same as goog.array.peek.
+ * @param {Array.<T>|goog.array.ArrayLike} array The array.
+ * @return {T} Last item in array.
+ * @template T
+ */
+goog.array.last = goog.array.peek;
+
+
+/**
+ * Reference to the original {@code Array.prototype}.
+ * @private
+ */
+goog.array.ARRAY_PROTOTYPE_ = Array.prototype;
+
+
+// NOTE(arv): Since most of the array functions are generic it allows you to
+// pass an array-like object. Strings have a length and are considered array-
+// like. However, the 'in' operator does not work on strings so we cannot just
+// use the array path even if the browser supports indexing into strings. We
+// therefore end up splitting the string.
+
+
+/**
+ * Returns the index of the first element of an array with a specified value, or
+ * -1 if the element is not present in the array.
+ *
+ * See {@link http://tinyurl.com/developer-mozilla-org-array-indexof}
+ *
+ * @param {Array.<T>|goog.array.ArrayLike} arr The array to be searched.
+ * @param {T} obj The object for which we are searching.
+ * @param {number=} opt_fromIndex The index at which to start the search. If
+ * omitted the search starts at index 0.
+ * @return {number} The index of the first matching array element.
+ * @template T
+ */
+goog.array.indexOf = goog.NATIVE_ARRAY_PROTOTYPES &&
+ (goog.array.ASSUME_NATIVE_FUNCTIONS ||
+ goog.array.ARRAY_PROTOTYPE_.indexOf) ?
+ function(arr, obj, opt_fromIndex) {
+ goog.asserts.assert(arr.length != null);
+
+ return goog.array.ARRAY_PROTOTYPE_.indexOf.call(arr, obj, opt_fromIndex);
+ } :
+ function(arr, obj, opt_fromIndex) {
+ var fromIndex = opt_fromIndex == null ?
+ 0 : (opt_fromIndex < 0 ?
+ Math.max(0, arr.length + opt_fromIndex) : opt_fromIndex);
+
+ if (goog.isString(arr)) {
+ // Array.prototype.indexOf uses === so only strings should be found.
+ if (!goog.isString(obj) || obj.length != 1) {
+ return -1;
+ }
+ return arr.indexOf(obj, fromIndex);
+ }
+
+ for (var i = fromIndex; i < arr.length; i++) {
+ if (i in arr && arr[i] === obj)
+ return i;
+ }
+ return -1;
+ };
+
+
+/**
+ * Returns the index of the last element of an array with a specified value, or
+ * -1 if the element is not present in the array.
+ *
+ * See {@link http://tinyurl.com/developer-mozilla-org-array-lastindexof}
+ *
+ * @param {!Array.<T>|!goog.array.ArrayLike} arr The array to be searched.
+ * @param {T} obj The object for which we are searching.
+ * @param {?number=} opt_fromIndex The index at which to start the search. If
+ * omitted the search starts at the end of the array.
+ * @return {number} The index of the last matching array element.
+ * @template T
+ */
+goog.array.lastIndexOf = goog.NATIVE_ARRAY_PROTOTYPES &&
+ (goog.array.ASSUME_NATIVE_FUNCTIONS ||
+ goog.array.ARRAY_PROTOTYPE_.lastIndexOf) ?
+ function(arr, obj, opt_fromIndex) {
+ goog.asserts.assert(arr.length != null);
+
+ // Firefox treats undefined and null as 0 in the fromIndex argument which
+ // leads it to always return -1
+ var fromIndex = opt_fromIndex == null ? arr.length - 1 : opt_fromIndex;
+ return goog.array.ARRAY_PROTOTYPE_.lastIndexOf.call(arr, obj, fromIndex);
+ } :
+ function(arr, obj, opt_fromIndex) {
+ var fromIndex = opt_fromIndex == null ? arr.length - 1 : opt_fromIndex;
+
+ if (fromIndex < 0) {
+ fromIndex = Math.max(0, arr.length + fromIndex);
+ }
+
+ if (goog.isString(arr)) {
+ // Array.prototype.lastIndexOf uses === so only strings should be found.
+ if (!goog.isString(obj) || obj.length != 1) {
+ return -1;
+ }
+ return arr.lastIndexOf(obj, fromIndex);
+ }
+
+ for (var i = fromIndex; i >= 0; i--) {
+ if (i in arr && arr[i] === obj)
+ return i;
+ }
+ return -1;
+ };
+
+
+/**
+ * Calls a function for each element in an array. Skips holes in the array.
+ * See {@link http://tinyurl.com/developer-mozilla-org-array-foreach}
+ *
+ * @param {Array.<T>|goog.array.ArrayLike} arr Array or array like object over
+ * which to iterate.
+ * @param {?function(this: S, T, number, ?): ?} f The function to call for every
+ * element. This function takes 3 arguments (the element, the index and the
+ * array). The return value is ignored.
+ * @param {S=} opt_obj The object to be used as the value of 'this' within f.
+ * @template T,S
+ */
+goog.array.forEach = goog.NATIVE_ARRAY_PROTOTYPES &&
+ (goog.array.ASSUME_NATIVE_FUNCTIONS ||
+ goog.array.ARRAY_PROTOTYPE_.forEach) ?
+ function(arr, f, opt_obj) {
+ goog.asserts.assert(arr.length != null);
+
+ goog.array.ARRAY_PROTOTYPE_.forEach.call(arr, f, opt_obj);
+ } :
+ function(arr, f, opt_obj) {
+ var l = arr.length; // must be fixed during loop... see docs
+ var arr2 = goog.isString(arr) ? arr.split('') : arr;
+ for (var i = 0; i < l; i++) {
+ if (i in arr2) {
+ f.call(opt_obj, arr2[i], i, arr);
+ }
+ }
+ };
+
+
+/**
+ * Calls a function for each element in an array, starting from the last
+ * element rather than the first.
+ *
+ * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
+ * like object over which to iterate.
+ * @param {?function(this: S, T, number, ?): ?} f The function to call for every
+ * element. This function
+ * takes 3 arguments (the element, the index and the array). The return
+ * value is ignored.
+ * @param {S=} opt_obj The object to be used as the value of 'this'
+ * within f.
+ * @template T,S
+ */
+goog.array.forEachRight = function(arr, f, opt_obj) {
+ var l = arr.length; // must be fixed during loop... see docs
+ var arr2 = goog.isString(arr) ? arr.split('') : arr;
+ for (var i = l - 1; i >= 0; --i) {
+ if (i in arr2) {
+ f.call(opt_obj, arr2[i], i, arr);
+ }
+ }
+};
+
+
+/**
+ * Calls a function for each element in an array, and if the function returns
+ * true adds the element to a new array.
+ *
+ * See {@link http://tinyurl.com/developer-mozilla-org-array-filter}
+ *
+ * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
+ * like object over which to iterate.
+ * @param {?function(this:S, T, number, ?):boolean} f The function to call for
+ * every element. This function
+ * takes 3 arguments (the element, the index and the array) and must
+ * return a Boolean. If the return value is true the element is added to the
+ * result array. If it is false the element is not included.
+ * @param {S=} opt_obj The object to be used as the value of 'this'
+ * within f.
+ * @return {!Array.<T>} a new array in which only elements that passed the test
+ * are present.
+ * @template T,S
+ */
+goog.array.filter = goog.NATIVE_ARRAY_PROTOTYPES &&
+ (goog.array.ASSUME_NATIVE_FUNCTIONS ||
+ goog.array.ARRAY_PROTOTYPE_.filter) ?
+ function(arr, f, opt_obj) {
+ goog.asserts.assert(arr.length != null);
+
+ return goog.array.ARRAY_PROTOTYPE_.filter.call(arr, f, opt_obj);
+ } :
+ function(arr, f, opt_obj) {
+ var l = arr.length; // must be fixed during loop... see docs
+ var res = [];
+ var resLength = 0;
+ var arr2 = goog.isString(arr) ? arr.split('') : arr;
+ for (var i = 0; i < l; i++) {
+ if (i in arr2) {
+ var val = arr2[i]; // in case f mutates arr2
+ if (f.call(opt_obj, val, i, arr)) {
+ res[resLength++] = val;
+ }
+ }
+ }
+ return res;
+ };
+
+
+/**
+ * Calls a function for each element in an array and inserts the result into a
+ * new array.
+ *
+ * See {@link http://tinyurl.com/developer-mozilla-org-array-map}
+ *
+ * @param {Array.<VALUE>|goog.array.ArrayLike} arr Array or array like object
+ * over which to iterate.
+ * @param {function(this:THIS, VALUE, number, ?): RESULT} f The function to call
+ * for every element. This function takes 3 arguments (the element,
+ * the index and the array) and should return something. The result will be
+ * inserted into a new array.
+ * @param {THIS=} opt_obj The object to be used as the value of 'this' within f.
+ * @return {!Array.<RESULT>} a new array with the results from f.
+ * @template THIS, VALUE, RESULT
+ */
+goog.array.map = goog.NATIVE_ARRAY_PROTOTYPES &&
+ (goog.array.ASSUME_NATIVE_FUNCTIONS ||
+ goog.array.ARRAY_PROTOTYPE_.map) ?
+ function(arr, f, opt_obj) {
+ goog.asserts.assert(arr.length != null);
+
+ return goog.array.ARRAY_PROTOTYPE_.map.call(arr, f, opt_obj);
+ } :
+ function(arr, f, opt_obj) {
+ var l = arr.length; // must be fixed during loop... see docs
+ var res = new Array(l);
+ var arr2 = goog.isString(arr) ? arr.split('') : arr;
+ for (var i = 0; i < l; i++) {
+ if (i in arr2) {
+ res[i] = f.call(opt_obj, arr2[i], i, arr);
+ }
+ }
+ return res;
+ };
+
+
+/**
+ * Passes every element of an array into a function and accumulates the result.
+ *
+ * See {@link http://tinyurl.com/developer-mozilla-org-array-reduce}
+ *
+ * For example:
+ * var a = [1, 2, 3, 4];
+ * goog.array.reduce(a, function(r, v, i, arr) {return r + v;}, 0);
+ * returns 10
+ *
+ * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
+ * like object over which to iterate.
+ * @param {?function(this:S, R, T, number, ?) : R} f The function to call for
+ * every element. This function
+ * takes 4 arguments (the function's previous result or the initial value,
+ * the value of the current array element, the current array index, and the
+ * array itself)
+ * function(previousValue, currentValue, index, array).
+ * @param {?} val The initial value to pass into the function on the first call.
+ * @param {S=} opt_obj The object to be used as the value of 'this'
+ * within f.
+ * @return {R} Result of evaluating f repeatedly across the values of the array.
+ * @template T,S,R
+ */
+goog.array.reduce = goog.NATIVE_ARRAY_PROTOTYPES &&
+ (goog.array.ASSUME_NATIVE_FUNCTIONS ||
+ goog.array.ARRAY_PROTOTYPE_.reduce) ?
+ function(arr, f, val, opt_obj) {
+ goog.asserts.assert(arr.length != null);
+ if (opt_obj) {
+ f = goog.bind(f, opt_obj);
+ }
+ return goog.array.ARRAY_PROTOTYPE_.reduce.call(arr, f, val);
+ } :
+ function(arr, f, val, opt_obj) {
+ var rval = val;
+ goog.array.forEach(arr, function(val, index) {
+ rval = f.call(opt_obj, rval, val, index, arr);
+ });
+ return rval;
+ };
+
+
+/**
+ * Passes every element of an array into a function and accumulates the result,
+ * starting from the last element and working towards the first.
+ *
+ * See {@link http://tinyurl.com/developer-mozilla-org-array-reduceright}
+ *
+ * For example:
+ * var a = ['a', 'b', 'c'];
+ * goog.array.reduceRight(a, function(r, v, i, arr) {return r + v;}, '');
+ * returns 'cba'
+ *
+ * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
+ * like object over which to iterate.
+ * @param {?function(this:S, R, T, number, ?) : R} f The function to call for
+ * every element. This function
+ * takes 4 arguments (the function's previous result or the initial value,
+ * the value of the current array element, the current array index, and the
+ * array itself)
+ * function(previousValue, currentValue, index, array).
+ * @param {?} val The initial value to pass into the function on the first call.
+ * @param {S=} opt_obj The object to be used as the value of 'this'
+ * within f.
+ * @return {R} Object returned as a result of evaluating f repeatedly across the
+ * values of the array.
+ * @template T,S,R
+ */
+goog.array.reduceRight = goog.NATIVE_ARRAY_PROTOTYPES &&
+ (goog.array.ASSUME_NATIVE_FUNCTIONS ||
+ goog.array.ARRAY_PROTOTYPE_.reduceRight) ?
+ function(arr, f, val, opt_obj) {
+ goog.asserts.assert(arr.length != null);
+ if (opt_obj) {
+ f = goog.bind(f, opt_obj);
+ }
+ return goog.array.ARRAY_PROTOTYPE_.reduceRight.call(arr, f, val);
+ } :
+ function(arr, f, val, opt_obj) {
+ var rval = val;
+ goog.array.forEachRight(arr, function(val, index) {
+ rval = f.call(opt_obj, rval, val, index, arr);
+ });
+ return rval;
+ };
+
+
+/**
+ * Calls f for each element of an array. If any call returns true, some()
+ * returns true (without checking the remaining elements). If all calls
+ * return false, some() returns false.
+ *
+ * See {@link http://tinyurl.com/developer-mozilla-org-array-some}
+ *
+ * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
+ * like object over which to iterate.
+ * @param {?function(this:S, T, number, ?) : boolean} f The function to call for
+ * for every element. This function takes 3 arguments (the element, the
+ * index and the array) and should return a boolean.
+ * @param {S=} opt_obj The object to be used as the value of 'this'
+ * within f.
+ * @return {boolean} true if any element passes the test.
+ * @template T,S
+ */
+goog.array.some = goog.NATIVE_ARRAY_PROTOTYPES &&
+ (goog.array.ASSUME_NATIVE_FUNCTIONS ||
+ goog.array.ARRAY_PROTOTYPE_.some) ?
+ function(arr, f, opt_obj) {
+ goog.asserts.assert(arr.length != null);
+
+ return goog.array.ARRAY_PROTOTYPE_.some.call(arr, f, opt_obj);
+ } :
+ function(arr, f, opt_obj) {
+ var l = arr.length; // must be fixed during loop... see docs
+ var arr2 = goog.isString(arr) ? arr.split('') : arr;
+ for (var i = 0; i < l; i++) {
+ if (i in arr2 && f.call(opt_obj, arr2[i], i, arr)) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+
+/**
+ * Call f for each element of an array. If all calls return true, every()
+ * returns true. If any call returns false, every() returns false and
+ * does not continue to check the remaining elements.
+ *
+ * See {@link http://tinyurl.com/developer-mozilla-org-array-every}
+ *
+ * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
+ * like object over which to iterate.
+ * @param {?function(this:S, T, number, ?) : boolean} f The function to call for
+ * for every element. This function takes 3 arguments (the element, the
+ * index and the array) and should return a boolean.
+ * @param {S=} opt_obj The object to be used as the value of 'this'
+ * within f.
+ * @return {boolean} false if any element fails the test.
+ * @template T,S
+ */
+goog.array.every = goog.NATIVE_ARRAY_PROTOTYPES &&
+ (goog.array.ASSUME_NATIVE_FUNCTIONS ||
+ goog.array.ARRAY_PROTOTYPE_.every) ?
+ function(arr, f, opt_obj) {
+ goog.asserts.assert(arr.length != null);
+
+ return goog.array.ARRAY_PROTOTYPE_.every.call(arr, f, opt_obj);
+ } :
+ function(arr, f, opt_obj) {
+ var l = arr.length; // must be fixed during loop... see docs
+ var arr2 = goog.isString(arr) ? arr.split('') : arr;
+ for (var i = 0; i < l; i++) {
+ if (i in arr2 && !f.call(opt_obj, arr2[i], i, arr)) {
+ return false;
+ }
+ }
+ return true;
+ };
+
+
+/**
+ * Counts the array elements that fulfill the predicate, i.e. for which the
+ * callback function returns true. Skips holes in the array.
+ *
+ * @param {!(Array.<T>|goog.array.ArrayLike)} arr Array or array like object
+ * over which to iterate.
+ * @param {function(this: S, T, number, ?): boolean} f The function to call for
+ * every element. Takes 3 arguments (the element, the index and the array).
+ * @param {S=} opt_obj The object to be used as the value of 'this' within f.
+ * @return {number} The number of the matching elements.
+ * @template T,S
+ */
+goog.array.count = function(arr, f, opt_obj) {
+ var count = 0;
+ goog.array.forEach(arr, function(element, index, arr) {
+ if (f.call(opt_obj, element, index, arr)) {
+ ++count;
+ }
+ }, opt_obj);
+ return count;
+};
+
+
+/**
+ * Search an array for the first element that satisfies a given condition and
+ * return that element.
+ * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
+ * like object over which to iterate.
+ * @param {?function(this:S, T, number, ?) : boolean} f The function to call
+ * for every element. This function takes 3 arguments (the element, the
+ * index and the array) and should return a boolean.
+ * @param {S=} opt_obj An optional "this" context for the function.
+ * @return {?T} The first array element that passes the test, or null if no
+ * element is found.
+ * @template T,S
+ */
+goog.array.find = function(arr, f, opt_obj) {
+ var i = goog.array.findIndex(arr, f, opt_obj);
+ return i < 0 ? null : goog.isString(arr) ? arr.charAt(i) : arr[i];
+};
+
+
+/**
+ * Search an array for the first element that satisfies a given condition and
+ * return its index.
+ * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
+ * like object over which to iterate.
+ * @param {?function(this:S, T, number, ?) : boolean} f The function to call for
+ * every element. This function
+ * takes 3 arguments (the element, the index and the array) and should
+ * return a boolean.
+ * @param {S=} opt_obj An optional "this" context for the function.
+ * @return {number} The index of the first array element that passes the test,
+ * or -1 if no element is found.
+ * @template T,S
+ */
+goog.array.findIndex = function(arr, f, opt_obj) {
+ var l = arr.length; // must be fixed during loop... see docs
+ var arr2 = goog.isString(arr) ? arr.split('') : arr;
+ for (var i = 0; i < l; i++) {
+ if (i in arr2 && f.call(opt_obj, arr2[i], i, arr)) {
+ return i;
+ }
+ }
+ return -1;
+};
+
+
+/**
+ * Search an array (in reverse order) for the last element that satisfies a
+ * given condition and return that element.
+ * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
+ * like object over which to iterate.
+ * @param {?function(this:S, T, number, ?) : boolean} f The function to call
+ * for every element. This function
+ * takes 3 arguments (the element, the index and the array) and should
+ * return a boolean.
+ * @param {S=} opt_obj An optional "this" context for the function.
+ * @return {?T} The last array element that passes the test, or null if no
+ * element is found.
+ * @template T,S
+ */
+goog.array.findRight = function(arr, f, opt_obj) {
+ var i = goog.array.findIndexRight(arr, f, opt_obj);
+ return i < 0 ? null : goog.isString(arr) ? arr.charAt(i) : arr[i];
+};
+
+
+/**
+ * Search an array (in reverse order) for the last element that satisfies a
+ * given condition and return its index.
+ * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
+ * like object over which to iterate.
+ * @param {?function(this:S, T, number, ?) : boolean} f The function to call
+ * for every element. This function
+ * takes 3 arguments (the element, the index and the array) and should
+ * return a boolean.
+ * @param {Object=} opt_obj An optional "this" context for the function.
+ * @return {number} The index of the last array element that passes the test,
+ * or -1 if no element is found.
+ * @template T,S
+ */
+goog.array.findIndexRight = function(arr, f, opt_obj) {
+ var l = arr.length; // must be fixed during loop... see docs
+ var arr2 = goog.isString(arr) ? arr.split('') : arr;
+ for (var i = l - 1; i >= 0; i--) {
+ if (i in arr2 && f.call(opt_obj, arr2[i], i, arr)) {
+ return i;
+ }
+ }
+ return -1;
+};
+
+
+/**
+ * Whether the array contains the given object.
+ * @param {goog.array.ArrayLike} arr The array to test for the presence of the
+ * element.
+ * @param {*} obj The object for which to test.
+ * @return {boolean} true if obj is present.
+ */
+goog.array.contains = function(arr, obj) {
+ return goog.array.indexOf(arr, obj) >= 0;
+};
+
+
+/**
+ * Whether the array is empty.
+ * @param {goog.array.ArrayLike} arr The array to test.
+ * @return {boolean} true if empty.
+ */
+goog.array.isEmpty = function(arr) {
+ return arr.length == 0;
+};
+
+
+/**
+ * Clears the array.
+ * @param {goog.array.ArrayLike} arr Array or array like object to clear.
+ */
+goog.array.clear = function(arr) {
+ // For non real arrays we don't have the magic length so we delete the
+ // indices.
+ if (!goog.isArray(arr)) {
+ for (var i = arr.length - 1; i >= 0; i--) {
+ delete arr[i];
+ }
+ }
+ arr.length = 0;
+};
+
+
+/**
+ * Pushes an item into an array, if it's not already in the array.
+ * @param {Array.<T>} arr Array into which to insert the item.
+ * @param {T} obj Value to add.
+ * @template T
+ */
+goog.array.insert = function(arr, obj) {
+ if (!goog.array.contains(arr, obj)) {
+ arr.push(obj);
+ }
+};
+
+
+/**
+ * Inserts an object at the given index of the array.
+ * @param {goog.array.ArrayLike} arr The array to modify.
+ * @param {*} obj The object to insert.
+ * @param {number=} opt_i The index at which to insert the object. If omitted,
+ * treated as 0. A negative index is counted from the end of the array.
+ */
+goog.array.insertAt = function(arr, obj, opt_i) {
+ goog.array.splice(arr, opt_i, 0, obj);
+};
+
+
+/**
+ * Inserts at the given index of the array, all elements of another array.
+ * @param {goog.array.ArrayLike} arr The array to modify.
+ * @param {goog.array.ArrayLike} elementsToAdd The array of elements to add.
+ * @param {number=} opt_i The index at which to insert the object. If omitted,
+ * treated as 0. A negative index is counted from the end of the array.
+ */
+goog.array.insertArrayAt = function(arr, elementsToAdd, opt_i) {
+ goog.partial(goog.array.splice, arr, opt_i, 0).apply(null, elementsToAdd);
+};
+
+
+/**
+ * Inserts an object into an array before a specified object.
+ * @param {Array.<T>} arr The array to modify.
+ * @param {T} obj The object to insert.
+ * @param {T=} opt_obj2 The object before which obj should be inserted. If obj2
+ * is omitted or not found, obj is inserted at the end of the array.
+ * @template T
+ */
+goog.array.insertBefore = function(arr, obj, opt_obj2) {
+ var i;
+ if (arguments.length == 2 || (i = goog.array.indexOf(arr, opt_obj2)) < 0) {
+ arr.push(obj);
+ } else {
+ goog.array.insertAt(arr, obj, i);
+ }
+};
+
+
+/**
+ * Removes the first occurrence of a particular value from an array.
+ * @param {Array.<T>|goog.array.ArrayLike} arr Array from which to remove
+ * value.
+ * @param {T} obj Object to remove.
+ * @return {boolean} True if an element was removed.
+ * @template T
+ */
+goog.array.remove = function(arr, obj) {
+ var i = goog.array.indexOf(arr, obj);
+ var rv;
+ if ((rv = i >= 0)) {
+ goog.array.removeAt(arr, i);
+ }
+ return rv;
+};
+
+
+/**
+ * Removes from an array the element at index i
+ * @param {goog.array.ArrayLike} arr Array or array like object from which to
+ * remove value.
+ * @param {number} i The index to remove.
+ * @return {boolean} True if an element was removed.
+ */
+goog.array.removeAt = function(arr, i) {
+ goog.asserts.assert(arr.length != null);
+
+ // use generic form of splice
+ // splice returns the removed items and if successful the length of that
+ // will be 1
+ return goog.array.ARRAY_PROTOTYPE_.splice.call(arr, i, 1).length == 1;
+};
+
+
+/**
+ * Removes the first value that satisfies the given condition.
+ * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
+ * like object over which to iterate.
+ * @param {?function(this:S, T, number, ?) : boolean} f The function to call
+ * for every element. This function
+ * takes 3 arguments (the element, the index and the array) and should
+ * return a boolean.
+ * @param {S=} opt_obj An optional "this" context for the function.
+ * @return {boolean} True if an element was removed.
+ * @template T,S
+ */
+goog.array.removeIf = function(arr, f, opt_obj) {
+ var i = goog.array.findIndex(arr, f, opt_obj);
+ if (i >= 0) {
+ goog.array.removeAt(arr, i);
+ return true;
+ }
+ return false;
+};
+
+
+/**
+ * Returns a new array that is the result of joining the arguments. If arrays
+ * are passed then their items are added, however, if non-arrays are passed they
+ * will be added to the return array as is.
+ *
+ * Note that ArrayLike objects will be added as is, rather than having their
+ * items added.
+ *
+ * goog.array.concat([1, 2], [3, 4]) -> [1, 2, 3, 4]
+ * goog.array.concat(0, [1, 2]) -> [0, 1, 2]
+ * goog.array.concat([1, 2], null) -> [1, 2, null]
+ *
+ * There is bug in all current versions of IE (6, 7 and 8) where arrays created
+ * in an iframe become corrupted soon (not immediately) after the iframe is
+ * destroyed. This is common if loading data via goog.net.IframeIo, for example.
+ * This corruption only affects the concat method which will start throwing
+ * Catastrophic Errors (#-2147418113).
+ *
+ * See http://endoflow.com/scratch/corrupted-arrays.html for a test case.
+ *
+ * Internally goog.array should use this, so that all methods will continue to
+ * work on these broken array objects.
+ *
+ * @param {...*} var_args Items to concatenate. Arrays will have each item
+ * added, while primitives and objects will be added as is.
+ * @return {!Array} The new resultant array.
+ */
+goog.array.concat = function(var_args) {
+ return goog.array.ARRAY_PROTOTYPE_.concat.apply(
+ goog.array.ARRAY_PROTOTYPE_, arguments);
+};
+
+
+/**
+ * Returns a new array that contains the contents of all the arrays passed.
+ * @param {...!Array.<T>} var_args
+ * @return {!Array.<T>}
+ * @template T
+ */
+goog.array.join = function(var_args) {
+ return goog.array.ARRAY_PROTOTYPE_.concat.apply(
+ goog.array.ARRAY_PROTOTYPE_, arguments);
+};
+
+
+/**
+ * Converts an object to an array.
+ * @param {Array.<T>|goog.array.ArrayLike} object The object to convert to an
+ * array.
+ * @return {!Array.<T>} The object converted into an array. If object has a
+ * length property, every property indexed with a non-negative number
+ * less than length will be included in the result. If object does not
+ * have a length property, an empty array will be returned.
+ * @template T
+ */
+goog.array.toArray = function(object) {
+ var length = object.length;
+
+ // If length is not a number the following it false. This case is kept for
+ // backwards compatibility since there are callers that pass objects that are
+ // not array like.
+ if (length > 0) {
+ var rv = new Array(length);
+ for (var i = 0; i < length; i++) {
+ rv[i] = object[i];
+ }
+ return rv;
+ }
+ return [];
+};
+
+
+/**
+ * Does a shallow copy of an array.
+ * @param {Array.<T>|goog.array.ArrayLike} arr Array or array-like object to
+ * clone.
+ * @return {!Array.<T>} Clone of the input array.
+ * @template T
+ */
+goog.array.clone = goog.array.toArray;
+
+
+/**
+ * Extends an array with another array, element, or "array like" object.
+ * This function operates 'in-place', it does not create a new Array.
+ *
+ * Example:
+ * var a = [];
+ * goog.array.extend(a, [0, 1]);
+ * a; // [0, 1]
+ * goog.array.extend(a, 2);
+ * a; // [0, 1, 2]
+ *
+ * @param {Array.<VALUE>} arr1 The array to modify.
+ * @param {...(Array.<VALUE>|VALUE)} var_args The elements or arrays of elements
+ * to add to arr1.
+ * @template VALUE
+ */
+goog.array.extend = function(arr1, var_args) {
+ for (var i = 1; i < arguments.length; i++) {
+ var arr2 = arguments[i];
+ // If we have an Array or an Arguments object we can just call push
+ // directly.
+ var isArrayLike;
+ if (goog.isArray(arr2) ||
+ // Detect Arguments. ES5 says that the [[Class]] of an Arguments object
+ // is "Arguments" but only V8 and JSC/Safari gets this right. We instead
+ // detect Arguments by checking for array like and presence of "callee".
+ (isArrayLike = goog.isArrayLike(arr2)) &&
+ // The getter for callee throws an exception in strict mode
+ // according to section 10.6 in ES5 so check for presence instead.
+ Object.prototype.hasOwnProperty.call(arr2, 'callee')) {
+ arr1.push.apply(arr1, arr2);
+ } else if (isArrayLike) {
+ // Otherwise loop over arr2 to prevent copying the object.
+ var len1 = arr1.length;
+ var len2 = arr2.length;
+ for (var j = 0; j < len2; j++) {
+ arr1[len1 + j] = arr2[j];
+ }
+ } else {
+ arr1.push(arr2);
+ }
+ }
+};
+
+
+/**
+ * Adds or removes elements from an array. This is a generic version of Array
+ * splice. This means that it might work on other objects similar to arrays,
+ * such as the arguments object.
+ *
+ * @param {Array.<T>|goog.array.ArrayLike} arr The array to modify.
+ * @param {number|undefined} index The index at which to start changing the
+ * array. If not defined, treated as 0.
+ * @param {number} howMany How many elements to remove (0 means no removal. A
+ * value below 0 is treated as zero and so is any other non number. Numbers
+ * are floored).
+ * @param {...T} var_args Optional, additional elements to insert into the
+ * array.
+ * @return {!Array.<T>} the removed elements.
+ * @template T
+ */
+goog.array.splice = function(arr, index, howMany, var_args) {
+ goog.asserts.assert(arr.length != null);
+
+ return goog.array.ARRAY_PROTOTYPE_.splice.apply(
+ arr, goog.array.slice(arguments, 1));
+};
+
+
+/**
+ * Returns a new array from a segment of an array. This is a generic version of
+ * Array slice. This means that it might work on other objects similar to
+ * arrays, such as the arguments object.
+ *
+ * @param {Array.<T>|goog.array.ArrayLike} arr The array from
+ * which to copy a segment.
+ * @param {number} start The index of the first element to copy.
+ * @param {number=} opt_end The index after the last element to copy.
+ * @return {!Array.<T>} A new array containing the specified segment of the
+ * original array.
+ * @template T
+ */
+goog.array.slice = function(arr, start, opt_end) {
+ goog.asserts.assert(arr.length != null);
+
+ // passing 1 arg to slice is not the same as passing 2 where the second is
+ // null or undefined (in that case the second argument is treated as 0).
+ // we could use slice on the arguments object and then use apply instead of
+ // testing the length
+ if (arguments.length <= 2) {
+ return goog.array.ARRAY_PROTOTYPE_.slice.call(arr, start);
+ } else {
+ return goog.array.ARRAY_PROTOTYPE_.slice.call(arr, start, opt_end);
+ }
+};
+
+
+/**
+ * Removes all duplicates from an array (retaining only the first
+ * occurrence of each array element). This function modifies the
+ * array in place and doesn't change the order of the non-duplicate items.
+ *
+ * For objects, duplicates are identified as having the same unique ID as
+ * defined by {@link goog.getUid}.
+ *
+ * Alternatively you can specify a custom hash function that returns a unique
+ * value for each item in the array it should consider unique.
+ *
+ * Runtime: N,
+ * Worstcase space: 2N (no dupes)
+ *
+ * @param {Array.<T>|goog.array.ArrayLike} arr The array from which to remove
+ * duplicates.
+ * @param {Array=} opt_rv An optional array in which to return the results,
+ * instead of performing the removal inplace. If specified, the original
+ * array will remain unchanged.
+ * @param {function(T):string=} opt_hashFn An optional function to use to
+ * apply to every item in the array. This function should return a unique
+ * value for each item in the array it should consider unique.
+ * @template T
+ */
+goog.array.removeDuplicates = function(arr, opt_rv, opt_hashFn) {
+ var returnArray = opt_rv || arr;
+ var defaultHashFn = function(item) {
+ // Prefix each type with a single character representing the type to
+ // prevent conflicting keys (e.g. true and 'true').
+ return goog.isObject(current) ? 'o' + goog.getUid(current) :
+ (typeof current).charAt(0) + current;
+ };
+ var hashFn = opt_hashFn || defaultHashFn;
+
+ var seen = {}, cursorInsert = 0, cursorRead = 0;
+ while (cursorRead < arr.length) {
+ var current = arr[cursorRead++];
+ var key = hashFn(current);
+ if (!Object.prototype.hasOwnProperty.call(seen, key)) {
+ seen[key] = true;
+ returnArray[cursorInsert++] = current;
+ }
+ }
+ returnArray.length = cursorInsert;
+};
+
+
+/**
+ * Searches the specified array for the specified target using the binary
+ * search algorithm. If no opt_compareFn is specified, elements are compared
+ * using <code>goog.array.defaultCompare</code>, which compares the elements
+ * using the built in < and > operators. This will produce the expected
+ * behavior for homogeneous arrays of String(s) and Number(s). The array
+ * specified <b>must</b> be sorted in ascending order (as defined by the
+ * comparison function). If the array is not sorted, results are undefined.
+ * If the array contains multiple instances of the specified target value, any
+ * of these instances may be found.
+ *
+ * Runtime: O(log n)
+ *
+ * @param {Array.<VALUE>|goog.array.ArrayLike} arr The array to be searched.
+ * @param {TARGET} target The sought value.
+ * @param {function(TARGET, VALUE): number=} opt_compareFn Optional comparison
+ * function by which the array is ordered. Should take 2 arguments to
+ * compare, and return a negative number, zero, or a positive number
+ * depending on whether the first argument is less than, equal to, or
+ * greater than the second.
+ * @return {number} Lowest index of the target value if found, otherwise
+ * (-(insertion point) - 1). The insertion point is where the value should
+ * be inserted into arr to preserve the sorted property. Return value >= 0
+ * iff target is found.
+ * @template TARGET, VALUE
+ */
+goog.array.binarySearch = function(arr, target, opt_compareFn) {
+ return goog.array.binarySearch_(arr,
+ opt_compareFn || goog.array.defaultCompare, false /* isEvaluator */,
+ target);
+};
+
+
+/**
+ * Selects an index in the specified array using the binary search algorithm.
+ * The evaluator receives an element and determines whether the desired index
+ * is before, at, or after it. The evaluator must be consistent (formally,
+ * goog.array.map(goog.array.map(arr, evaluator, opt_obj), goog.math.sign)
+ * must be monotonically non-increasing).
+ *
+ * Runtime: O(log n)
+ *
+ * @param {Array.<VALUE>|goog.array.ArrayLike} arr The array to be searched.
+ * @param {function(this:THIS, VALUE, number, ?): number} evaluator
+ * Evaluator function that receives 3 arguments (the element, the index and
+ * the array). Should return a negative number, zero, or a positive number
+ * depending on whether the desired index is before, at, or after the
+ * element passed to it.
+ * @param {THIS=} opt_obj The object to be used as the value of 'this'
+ * within evaluator.
+ * @return {number} Index of the leftmost element matched by the evaluator, if
+ * such exists; otherwise (-(insertion point) - 1). The insertion point is
+ * the index of the first element for which the evaluator returns negative,
+ * or arr.length if no such element exists. The return value is non-negative
+ * iff a match is found.
+ * @template THIS, VALUE
+ */
+goog.array.binarySelect = function(arr, evaluator, opt_obj) {
+ return goog.array.binarySearch_(arr, evaluator, true /* isEvaluator */,
+ undefined /* opt_target */, opt_obj);
+};
+
+
+/**
+ * Implementation of a binary search algorithm which knows how to use both
+ * comparison functions and evaluators. If an evaluator is provided, will call
+ * the evaluator with the given optional data object, conforming to the
+ * interface defined in binarySelect. Otherwise, if a comparison function is
+ * provided, will call the comparison function against the given data object.
+ *
+ * This implementation purposefully does not use goog.bind or goog.partial for
+ * performance reasons.
+ *
+ * Runtime: O(log n)
+ *
+ * @param {Array.<VALUE>|goog.array.ArrayLike} arr The array to be searched.
+ * @param {function(TARGET, VALUE): number|
+ * function(this:THIS, VALUE, number, ?): number} compareFn Either an
+ * evaluator or a comparison function, as defined by binarySearch
+ * and binarySelect above.
+ * @param {boolean} isEvaluator Whether the function is an evaluator or a
+ * comparison function.
+ * @param {TARGET=} opt_target If the function is a comparison function, then
+ * this is the target to binary search for.
+ * @param {THIS=} opt_selfObj If the function is an evaluator, this is an
+ * optional this object for the evaluator.
+ * @return {number} Lowest index of the target value if found, otherwise
+ * (-(insertion point) - 1). The insertion point is where the value should
+ * be inserted into arr to preserve the sorted property. Return value >= 0
+ * iff target is found.
+ * @template THIS, VALUE, TARGET
+ * @private
+ */
+goog.array.binarySearch_ = function(arr, compareFn, isEvaluator, opt_target,
+ opt_selfObj) {
+ var left = 0; // inclusive
+ var right = arr.length; // exclusive
+ var found;
+ while (left < right) {
+ var middle = (left + right) >> 1;
+ var compareResult;
+ if (isEvaluator) {
+ compareResult = compareFn.call(opt_selfObj, arr[middle], middle, arr);
+ } else {
+ compareResult = compareFn(opt_target, arr[middle]);
+ }
+ if (compareResult > 0) {
+ left = middle + 1;
+ } else {
+ right = middle;
+ // We are looking for the lowest index so we can't return immediately.
+ found = !compareResult;
+ }
+ }
+ // left is the index if found, or the insertion point otherwise.
+ // ~left is a shorthand for -left - 1.
+ return found ? left : ~left;
+};
+
+
+/**
+ * Sorts the specified array into ascending order. If no opt_compareFn is
+ * specified, elements are compared using
+ * <code>goog.array.defaultCompare</code>, which compares the elements using
+ * the built in < and > operators. This will produce the expected behavior
+ * for homogeneous arrays of String(s) and Number(s), unlike the native sort,
+ * but will give unpredictable results for heterogenous lists of strings and
+ * numbers with different numbers of digits.
+ *
+ * This sort is not guaranteed to be stable.
+ *
+ * Runtime: Same as <code>Array.prototype.sort</code>
+ *
+ * @param {Array.<T>} arr The array to be sorted.
+ * @param {?function(T,T):number=} opt_compareFn Optional comparison
+ * function by which the
+ * array is to be ordered. Should take 2 arguments to compare, and return a
+ * negative number, zero, or a positive number depending on whether the
+ * first argument is less than, equal to, or greater than the second.
+ * @template T
+ */
+goog.array.sort = function(arr, opt_compareFn) {
+ // TODO(arv): Update type annotation since null is not accepted.
+ arr.sort(opt_compareFn || goog.array.defaultCompare);
+};
+
+
+/**
+ * Sorts the specified array into ascending order in a stable way. If no
+ * opt_compareFn is specified, elements are compared using
+ * <code>goog.array.defaultCompare</code>, which compares the elements using
+ * the built in < and > operators. This will produce the expected behavior
+ * for homogeneous arrays of String(s) and Number(s).
+ *
+ * Runtime: Same as <code>Array.prototype.sort</code>, plus an additional
+ * O(n) overhead of copying the array twice.
+ *
+ * @param {Array.<T>} arr The array to be sorted.
+ * @param {?function(T, T): number=} opt_compareFn Optional comparison function
+ * by which the array is to be ordered. Should take 2 arguments to compare,
+ * and return a negative number, zero, or a positive number depending on
+ * whether the first argument is less than, equal to, or greater than the
+ * second.
+ * @template T
+ */
+goog.array.stableSort = function(arr, opt_compareFn) {
+ for (var i = 0; i < arr.length; i++) {
+ arr[i] = {index: i, value: arr[i]};
+ }
+ var valueCompareFn = opt_compareFn || goog.array.defaultCompare;
+ function stableCompareFn(obj1, obj2) {
+ return valueCompareFn(obj1.value, obj2.value) || obj1.index - obj2.index;
+ };
+ goog.array.sort(arr, stableCompareFn);
+ for (var i = 0; i < arr.length; i++) {
+ arr[i] = arr[i].value;
+ }
+};
+
+
+/**
+ * Sorts an array of objects by the specified object key and compare
+ * function. If no compare function is provided, the key values are
+ * compared in ascending order using <code>goog.array.defaultCompare</code>.
+ * This won't work for keys that get renamed by the compiler. So use
+ * {'foo': 1, 'bar': 2} rather than {foo: 1, bar: 2}.
+ * @param {Array.<Object>} arr An array of objects to sort.
+ * @param {string} key The object key to sort by.
+ * @param {Function=} opt_compareFn The function to use to compare key
+ * values.
+ */
+goog.array.sortObjectsByKey = function(arr, key, opt_compareFn) {
+ var compare = opt_compareFn || goog.array.defaultCompare;
+ goog.array.sort(arr, function(a, b) {
+ return compare(a[key], b[key]);
+ });
+};
+
+
+/**
+ * Tells if the array is sorted.
+ * @param {!Array.<T>} arr The array.
+ * @param {?function(T,T):number=} opt_compareFn Function to compare the
+ * array elements.
+ * Should take 2 arguments to compare, and return a negative number, zero,
+ * or a positive number depending on whether the first argument is less
+ * than, equal to, or greater than the second.
+ * @param {boolean=} opt_strict If true no equal elements are allowed.
+ * @return {boolean} Whether the array is sorted.
+ * @template T
+ */
+goog.array.isSorted = function(arr, opt_compareFn, opt_strict) {
+ var compare = opt_compareFn || goog.array.defaultCompare;
+ for (var i = 1; i < arr.length; i++) {
+ var compareResult = compare(arr[i - 1], arr[i]);
+ if (compareResult > 0 || compareResult == 0 && opt_strict) {
+ return false;
+ }
+ }
+ return true;
+};
+
+
+/**
+ * Compares two arrays for equality. Two arrays are considered equal if they
+ * have the same length and their corresponding elements are equal according to
+ * the comparison function.
+ *
+ * @param {goog.array.ArrayLike} arr1 The first array to compare.
+ * @param {goog.array.ArrayLike} arr2 The second array to compare.
+ * @param {Function=} opt_equalsFn Optional comparison function.
+ * Should take 2 arguments to compare, and return true if the arguments
+ * are equal. Defaults to {@link goog.array.defaultCompareEquality} which
+ * compares the elements using the built-in '===' operator.
+ * @return {boolean} Whether the two arrays are equal.
+ */
+goog.array.equals = function(arr1, arr2, opt_equalsFn) {
+ if (!goog.isArrayLike(arr1) || !goog.isArrayLike(arr2) ||
+ arr1.length != arr2.length) {
+ return false;
+ }
+ var l = arr1.length;
+ var equalsFn = opt_equalsFn || goog.array.defaultCompareEquality;
+ for (var i = 0; i < l; i++) {
+ if (!equalsFn(arr1[i], arr2[i])) {
+ return false;
+ }
+ }
+ return true;
+};
+
+
+/**
+ * 3-way array compare function.
+ * @param {!Array.<VALUE>|!goog.array.ArrayLike} arr1 The first array to
+ * compare.
+ * @param {!Array.<VALUE>|!goog.array.ArrayLike} arr2 The second array to
+ * compare.
+ * @param {function(VALUE, VALUE): number=} opt_compareFn Optional comparison
+ * function by which the array is to be ordered. Should take 2 arguments to
+ * compare, and return a negative number, zero, or a positive number
+ * depending on whether the first argument is less than, equal to, or
+ * greater than the second.
+ * @return {number} Negative number, zero, or a positive number depending on
+ * whether the first argument is less than, equal to, or greater than the
+ * second.
+ * @template VALUE
+ */
+goog.array.compare3 = function(arr1, arr2, opt_compareFn) {
+ var compare = opt_compareFn || goog.array.defaultCompare;
+ var l = Math.min(arr1.length, arr2.length);
+ for (var i = 0; i < l; i++) {
+ var result = compare(arr1[i], arr2[i]);
+ if (result != 0) {
+ return result;
+ }
+ }
+ return goog.array.defaultCompare(arr1.length, arr2.length);
+};
+
+
+/**
+ * Compares its two arguments for order, using the built in < and >
+ * operators.
+ * @param {VALUE} a The first object to be compared.
+ * @param {VALUE} b The second object to be compared.
+ * @return {number} A negative number, zero, or a positive number as the first
+ * argument is less than, equal to, or greater than the second.
+ * @template VALUE
+ */
+goog.array.defaultCompare = function(a, b) {
+ return a > b ? 1 : a < b ? -1 : 0;
+};
+
+
+/**
+ * Compares its two arguments for equality, using the built in === operator.
+ * @param {*} a The first object to compare.
+ * @param {*} b The second object to compare.
+ * @return {boolean} True if the two arguments are equal, false otherwise.
+ */
+goog.array.defaultCompareEquality = function(a, b) {
+ return a === b;
+};
+
+
+/**
+ * Inserts a value into a sorted array. The array is not modified if the
+ * value is already present.
+ * @param {Array.<VALUE>|goog.array.ArrayLike} array The array to modify.
+ * @param {VALUE} value The object to insert.
+ * @param {function(VALUE, VALUE): number=} opt_compareFn Optional comparison
+ * function by which the array is ordered. Should take 2 arguments to
+ * compare, and return a negative number, zero, or a positive number
+ * depending on whether the first argument is less than, equal to, or
+ * greater than the second.
+ * @return {boolean} True if an element was inserted.
+ * @template VALUE
+ */
+goog.array.binaryInsert = function(array, value, opt_compareFn) {
+ var index = goog.array.binarySearch(array, value, opt_compareFn);
+ if (index < 0) {
+ goog.array.insertAt(array, value, -(index + 1));
+ return true;
+ }
+ return false;
+};
+
+
+/**
+ * Removes a value from a sorted array.
+ * @param {!Array.<VALUE>|!goog.array.ArrayLike} array The array to modify.
+ * @param {VALUE} value The object to remove.
+ * @param {function(VALUE, VALUE): number=} opt_compareFn Optional comparison
+ * function by which the array is ordered. Should take 2 arguments to
+ * compare, and return a negative number, zero, or a positive number
+ * depending on whether the first argument is less than, equal to, or
+ * greater than the second.
+ * @return {boolean} True if an element was removed.
+ * @template VALUE
+ */
+goog.array.binaryRemove = function(array, value, opt_compareFn) {
+ var index = goog.array.binarySearch(array, value, opt_compareFn);
+ return (index >= 0) ? goog.array.removeAt(array, index) : false;
+};
+
+
+/**
+ * Splits an array into disjoint buckets according to a splitting function.
+ * @param {Array.<T>} array The array.
+ * @param {function(this:S, T,number,Array.<T>):?} sorter Function to call for
+ * every element. This takes 3 arguments (the element, the index and the
+ * array) and must return a valid object key (a string, number, etc), or
+ * undefined, if that object should not be placed in a bucket.
+ * @param {S=} opt_obj The object to be used as the value of 'this' within
+ * sorter.
+ * @return {!Object} An object, with keys being all of the unique return values
+ * of sorter, and values being arrays containing the items for
+ * which the splitter returned that key.
+ * @template T,S
+ */
+goog.array.bucket = function(array, sorter, opt_obj) {
+ var buckets = {};
+
+ for (var i = 0; i < array.length; i++) {
+ var value = array[i];
+ var key = sorter.call(opt_obj, value, i, array);
+ if (goog.isDef(key)) {
+ // Push the value to the right bucket, creating it if necessary.
+ var bucket = buckets[key] || (buckets[key] = []);
+ bucket.push(value);
+ }
+ }
+
+ return buckets;
+};
+
+
+/**
+ * Creates a new object built from the provided array and the key-generation
+ * function.
+ * @param {Array.<T>|goog.array.ArrayLike} arr Array or array like object over
+ * which to iterate whose elements will be the values in the new object.
+ * @param {?function(this:S, T, number, ?) : string} keyFunc The function to
+ * call for every element. This function takes 3 arguments (the element, the
+ * index and the array) and should return a string that will be used as the
+ * key for the element in the new object. If the function returns the same
+ * key for more than one element, the value for that key is
+ * implementation-defined.
+ * @param {S=} opt_obj The object to be used as the value of 'this'
+ * within keyFunc.
+ * @return {!Object.<T>} The new object.
+ * @template T,S
+ */
+goog.array.toObject = function(arr, keyFunc, opt_obj) {
+ var ret = {};
+ goog.array.forEach(arr, function(element, index) {
+ ret[keyFunc.call(opt_obj, element, index, arr)] = element;
+ });
+ return ret;
+};
+
+
+/**
+ * Creates a range of numbers in an arithmetic progression.
+ *
+ * Range takes 1, 2, or 3 arguments:
+ * <pre>
+ * range(5) is the same as range(0, 5, 1) and produces [0, 1, 2, 3, 4]
+ * range(2, 5) is the same as range(2, 5, 1) and produces [2, 3, 4]
+ * range(-2, -5, -1) produces [-2, -3, -4]
+ * range(-2, -5, 1) produces [], since stepping by 1 wouldn't ever reach -5.
+ * </pre>
+ *
+ * @param {number} startOrEnd The starting value of the range if an end argument
+ * is provided. Otherwise, the start value is 0, and this is the end value.
+ * @param {number=} opt_end The optional end value of the range.
+ * @param {number=} opt_step The step size between range values. Defaults to 1
+ * if opt_step is undefined or 0.
+ * @return {!Array.<number>} An array of numbers for the requested range. May be
+ * an empty array if adding the step would not converge toward the end
+ * value.
+ */
+goog.array.range = function(startOrEnd, opt_end, opt_step) {
+ var array = [];
+ var start = 0;
+ var end = startOrEnd;
+ var step = opt_step || 1;
+ if (opt_end !== undefined) {
+ start = startOrEnd;
+ end = opt_end;
+ }
+
+ if (step * (end - start) < 0) {
+ // Sign mismatch: start + step will never reach the end value.
+ return [];
+ }
+
+ if (step > 0) {
+ for (var i = start; i < end; i += step) {
+ array.push(i);
+ }
+ } else {
+ for (var i = start; i > end; i += step) {
+ array.push(i);
+ }
+ }
+ return array;
+};
+
+
+/**
+ * Returns an array consisting of the given value repeated N times.
+ *
+ * @param {VALUE} value The value to repeat.
+ * @param {number} n The repeat count.
+ * @return {!Array.<VALUE>} An array with the repeated value.
+ * @template VALUE
+ */
+goog.array.repeat = function(value, n) {
+ var array = [];
+ for (var i = 0; i < n; i++) {
+ array[i] = value;
+ }
+ return array;
+};
+
+
+/**
+ * Returns an array consisting of every argument with all arrays
+ * expanded in-place recursively.
+ *
+ * @param {...*} var_args The values to flatten.
+ * @return {!Array} An array containing the flattened values.
+ */
+goog.array.flatten = function(var_args) {
+ var result = [];
+ for (var i = 0; i < arguments.length; i++) {
+ var element = arguments[i];
+ if (goog.isArray(element)) {
+ result.push.apply(result, goog.array.flatten.apply(null, element));
+ } else {
+ result.push(element);
+ }
+ }
+ return result;
+};
+
+
+/**
+ * Rotates an array in-place. After calling this method, the element at
+ * index i will be the element previously at index (i - n) %
+ * array.length, for all values of i between 0 and array.length - 1,
+ * inclusive.
+ *
+ * For example, suppose list comprises [t, a, n, k, s]. After invoking
+ * rotate(array, 1) (or rotate(array, -4)), array will comprise [s, t, a, n, k].
+ *
+ * @param {!Array.<T>} array The array to rotate.
+ * @param {number} n The amount to rotate.
+ * @return {!Array.<T>} The array.
+ * @template T
+ */
+goog.array.rotate = function(array, n) {
+ goog.asserts.assert(array.length != null);
+
+ if (array.length) {
+ n %= array.length;
+ if (n > 0) {
+ goog.array.ARRAY_PROTOTYPE_.unshift.apply(array, array.splice(-n, n));
+ } else if (n < 0) {
+ goog.array.ARRAY_PROTOTYPE_.push.apply(array, array.splice(0, -n));
+ }
+ }
+ return array;
+};
+
+
+/**
+ * Moves one item of an array to a new position keeping the order of the rest
+ * of the items. Example use case: keeping a list of JavaScript objects
+ * synchronized with the corresponding list of DOM elements after one of the
+ * elements has been dragged to a new position.
+ * @param {!(Array|Arguments|{length:number})} arr The array to modify.
+ * @param {number} fromIndex Index of the item to move between 0 and
+ * {@code arr.length - 1}.
+ * @param {number} toIndex Target index between 0 and {@code arr.length - 1}.
+ */
+goog.array.moveItem = function(arr, fromIndex, toIndex) {
+ goog.asserts.assert(fromIndex >= 0 && fromIndex < arr.length);
+ goog.asserts.assert(toIndex >= 0 && toIndex < arr.length);
+ // Remove 1 item at fromIndex.
+ var removedItems = goog.array.ARRAY_PROTOTYPE_.splice.call(arr, fromIndex, 1);
+ // Insert the removed item at toIndex.
+ goog.array.ARRAY_PROTOTYPE_.splice.call(arr, toIndex, 0, removedItems[0]);
+ // We don't use goog.array.insertAt and goog.array.removeAt, because they're
+ // significantly slower than splice.
+};
+
+
+/**
+ * Creates a new array for which the element at position i is an array of the
+ * ith element of the provided arrays. The returned array will only be as long
+ * as the shortest array provided; additional values are ignored. For example,
+ * the result of zipping [1, 2] and [3, 4, 5] is [[1,3], [2, 4]].
+ *
+ * This is similar to the zip() function in Python. See {@link
+ * http://docs.python.org/library/functions.html#zip}
+ *
+ * @param {...!goog.array.ArrayLike} var_args Arrays to be combined.
+ * @return {!Array.<!Array>} A new array of arrays created from provided arrays.
+ */
+goog.array.zip = function(var_args) {
+ if (!arguments.length) {
+ return [];
+ }
+ var result = [];
+ for (var i = 0; true; i++) {
+ var value = [];
+ for (var j = 0; j < arguments.length; j++) {
+ var arr = arguments[j];
+ // If i is larger than the array length, this is the shortest array.
+ if (i >= arr.length) {
+ return result;
+ }
+ value.push(arr[i]);
+ }
+ result.push(value);
+ }
+};
+
+
+/**
+ * Shuffles the values in the specified array using the Fisher-Yates in-place
+ * shuffle (also known as the Knuth Shuffle). By default, calls Math.random()
+ * and so resets the state of that random number generator. Similarly, may reset
+ * the state of the any other specified random number generator.
+ *
+ * Runtime: O(n)
+ *
+ * @param {!Array} arr The array to be shuffled.
+ * @param {function():number=} opt_randFn Optional random function to use for
+ * shuffling.
+ * Takes no arguments, and returns a random number on the interval [0, 1).
+ * Defaults to Math.random() using JavaScript's built-in Math library.
+ */
+goog.array.shuffle = function(arr, opt_randFn) {
+ var randFn = opt_randFn || Math.random;
+
+ for (var i = arr.length - 1; i > 0; i--) {
+ // Choose a random array index in [0, i] (inclusive with i).
+ var j = Math.floor(randFn() * (i + 1));
+
+ var tmp = arr[i];
+ arr[i] = arr[j];
+ arr[j] = tmp;
+ }
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/asserts/asserts.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/asserts/asserts.js
new file mode 100644
index 0000000..2bb985f
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/asserts/asserts.js
@@ -0,0 +1,344 @@
+// Copyright 2008 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Utilities to check the preconditions, postconditions and
+ * invariants runtime.
+ *
+ * Methods in this package should be given special treatment by the compiler
+ * for type-inference. For example, <code>goog.asserts.assert(foo)</code>
+ * will restrict <code>foo</code> to a truthy value.
+ *
+ * The compiler has an option to disable asserts. So code like:
+ * <code>
+ * var x = goog.asserts.assert(foo()); goog.asserts.assert(bar());
+ * </code>
+ * will be transformed into:
+ * <code>
+ * var x = foo();
+ * </code>
+ * The compiler will leave in foo() (because its return value is used),
+ * but it will remove bar() because it assumes it does not have side-effects.
+ *
+ */
+
+goog.provide('goog.asserts');
+goog.provide('goog.asserts.AssertionError');
+
+goog.require('goog.debug.Error');
+goog.require('goog.dom.NodeType');
+goog.require('goog.string');
+
+
+/**
+ * @define {boolean} Whether to strip out asserts or to leave them in.
+ */
+goog.define('goog.asserts.ENABLE_ASSERTS', goog.DEBUG);
+
+
+
+/**
+ * Error object for failed assertions.
+ * @param {string} messagePattern The pattern that was used to form message.
+ * @param {!Array.<*>} messageArgs The items to substitute into the pattern.
+ * @constructor
+ * @extends {goog.debug.Error}
+ * @final
+ */
+goog.asserts.AssertionError = function(messagePattern, messageArgs) {
+ messageArgs.unshift(messagePattern);
+ goog.debug.Error.call(this, goog.string.subs.apply(null, messageArgs));
+ // Remove the messagePattern afterwards to avoid permenantly modifying the
+ // passed in array.
+ messageArgs.shift();
+
+ /**
+ * The message pattern used to format the error message. Error handlers can
+ * use this to uniquely identify the assertion.
+ * @type {string}
+ */
+ this.messagePattern = messagePattern;
+};
+goog.inherits(goog.asserts.AssertionError, goog.debug.Error);
+
+
+/** @override */
+goog.asserts.AssertionError.prototype.name = 'AssertionError';
+
+
+/**
+ * The default error handler.
+ * @param {!goog.asserts.AssertionError} e The exception to be handled.
+ */
+goog.asserts.DEFAULT_ERROR_HANDLER = function(e) { throw e; };
+
+
+/**
+ * The handler responsible for throwing or logging assertion errors.
+ * @private {function(!goog.asserts.AssertionError)}
+ */
+goog.asserts.errorHandler_ = goog.asserts.DEFAULT_ERROR_HANDLER;
+
+
+/**
+ * Throws an exception with the given message and "Assertion failed" prefixed
+ * onto it.
+ * @param {string} defaultMessage The message to use if givenMessage is empty.
+ * @param {Array.<*>} defaultArgs The substitution arguments for defaultMessage.
+ * @param {string|undefined} givenMessage Message supplied by the caller.
+ * @param {Array.<*>} givenArgs The substitution arguments for givenMessage.
+ * @throws {goog.asserts.AssertionError} When the value is not a number.
+ * @private
+ */
+goog.asserts.doAssertFailure_ =
+ function(defaultMessage, defaultArgs, givenMessage, givenArgs) {
+ var message = 'Assertion failed';
+ if (givenMessage) {
+ message += ': ' + givenMessage;
+ var args = givenArgs;
+ } else if (defaultMessage) {
+ message += ': ' + defaultMessage;
+ args = defaultArgs;
+ }
+ // The '' + works around an Opera 10 bug in the unit tests. Without it,
+ // a stack trace is added to var message above. With this, a stack trace is
+ // not added until this line (it causes the extra garbage to be added after
+ // the assertion message instead of in the middle of it).
+ var e = new goog.asserts.AssertionError('' + message, args || []);
+ goog.asserts.errorHandler_(e);
+};
+
+
+/**
+ * Sets a custom error handler that can be used to customize the behavior of
+ * assertion failures, for example by turning all assertion failures into log
+ * messages.
+ * @param {function(goog.asserts.AssertionError)} errorHandler
+ */
+goog.asserts.setErrorHandler = function(errorHandler) {
+ if (goog.asserts.ENABLE_ASSERTS) {
+ goog.asserts.errorHandler_ = errorHandler;
+ }
+};
+
+
+/**
+ * Checks if the condition evaluates to true if goog.asserts.ENABLE_ASSERTS is
+ * true.
+ * @template T
+ * @param {T} condition The condition to check.
+ * @param {string=} opt_message Error message in case of failure.
+ * @param {...*} var_args The items to substitute into the failure message.
+ * @return {T} The value of the condition.
+ * @throws {goog.asserts.AssertionError} When the condition evaluates to false.
+ */
+goog.asserts.assert = function(condition, opt_message, var_args) {
+ if (goog.asserts.ENABLE_ASSERTS && !condition) {
+ goog.asserts.doAssertFailure_('', null, opt_message,
+ Array.prototype.slice.call(arguments, 2));
+ }
+ return condition;
+};
+
+
+/**
+ * Fails if goog.asserts.ENABLE_ASSERTS is true. This function is useful in case
+ * when we want to add a check in the unreachable area like switch-case
+ * statement:
+ *
+ * <pre>
+ * switch(type) {
+ * case FOO: doSomething(); break;
+ * case BAR: doSomethingElse(); break;
+ * default: goog.assert.fail('Unrecognized type: ' + type);
+ * // We have only 2 types - "default:" section is unreachable code.
+ * }
+ * </pre>
+ *
+ * @param {string=} opt_message Error message in case of failure.
+ * @param {...*} var_args The items to substitute into the failure message.
+ * @throws {goog.asserts.AssertionError} Failure.
+ */
+goog.asserts.fail = function(opt_message, var_args) {
+ if (goog.asserts.ENABLE_ASSERTS) {
+ goog.asserts.errorHandler_(new goog.asserts.AssertionError(
+ 'Failure' + (opt_message ? ': ' + opt_message : ''),
+ Array.prototype.slice.call(arguments, 1)));
+ }
+};
+
+
+/**
+ * Checks if the value is a number if goog.asserts.ENABLE_ASSERTS is true.
+ * @param {*} value The value to check.
+ * @param {string=} opt_message Error message in case of failure.
+ * @param {...*} var_args The items to substitute into the failure message.
+ * @return {number} The value, guaranteed to be a number when asserts enabled.
+ * @throws {goog.asserts.AssertionError} When the value is not a number.
+ */
+goog.asserts.assertNumber = function(value, opt_message, var_args) {
+ if (goog.asserts.ENABLE_ASSERTS && !goog.isNumber(value)) {
+ goog.asserts.doAssertFailure_('Expected number but got %s: %s.',
+ [goog.typeOf(value), value], opt_message,
+ Array.prototype.slice.call(arguments, 2));
+ }
+ return /** @type {number} */ (value);
+};
+
+
+/**
+ * Checks if the value is a string if goog.asserts.ENABLE_ASSERTS is true.
+ * @param {*} value The value to check.
+ * @param {string=} opt_message Error message in case of failure.
+ * @param {...*} var_args The items to substitute into the failure message.
+ * @return {string} The value, guaranteed to be a string when asserts enabled.
+ * @throws {goog.asserts.AssertionError} When the value is not a string.
+ */
+goog.asserts.assertString = function(value, opt_message, var_args) {
+ if (goog.asserts.ENABLE_ASSERTS && !goog.isString(value)) {
+ goog.asserts.doAssertFailure_('Expected string but got %s: %s.',
+ [goog.typeOf(value), value], opt_message,
+ Array.prototype.slice.call(arguments, 2));
+ }
+ return /** @type {string} */ (value);
+};
+
+
+/**
+ * Checks if the value is a function if goog.asserts.ENABLE_ASSERTS is true.
+ * @param {*} value The value to check.
+ * @param {string=} opt_message Error message in case of failure.
+ * @param {...*} var_args The items to substitute into the failure message.
+ * @return {!Function} The value, guaranteed to be a function when asserts
+ * enabled.
+ * @throws {goog.asserts.AssertionError} When the value is not a function.
+ */
+goog.asserts.assertFunction = function(value, opt_message, var_args) {
+ if (goog.asserts.ENABLE_ASSERTS && !goog.isFunction(value)) {
+ goog.asserts.doAssertFailure_('Expected function but got %s: %s.',
+ [goog.typeOf(value), value], opt_message,
+ Array.prototype.slice.call(arguments, 2));
+ }
+ return /** @type {!Function} */ (value);
+};
+
+
+/**
+ * Checks if the value is an Object if goog.asserts.ENABLE_ASSERTS is true.
+ * @param {*} value The value to check.
+ * @param {string=} opt_message Error message in case of failure.
+ * @param {...*} var_args The items to substitute into the failure message.
+ * @return {!Object} The value, guaranteed to be a non-null object.
+ * @throws {goog.asserts.AssertionError} When the value is not an object.
+ */
+goog.asserts.assertObject = function(value, opt_message, var_args) {
+ if (goog.asserts.ENABLE_ASSERTS && !goog.isObject(value)) {
+ goog.asserts.doAssertFailure_('Expected object but got %s: %s.',
+ [goog.typeOf(value), value],
+ opt_message, Array.prototype.slice.call(arguments, 2));
+ }
+ return /** @type {!Object} */ (value);
+};
+
+
+/**
+ * Checks if the value is an Array if goog.asserts.ENABLE_ASSERTS is true.
+ * @param {*} value The value to check.
+ * @param {string=} opt_message Error message in case of failure.
+ * @param {...*} var_args The items to substitute into the failure message.
+ * @return {!Array} The value, guaranteed to be a non-null array.
+ * @throws {goog.asserts.AssertionError} When the value is not an array.
+ */
+goog.asserts.assertArray = function(value, opt_message, var_args) {
+ if (goog.asserts.ENABLE_ASSERTS && !goog.isArray(value)) {
+ goog.asserts.doAssertFailure_('Expected array but got %s: %s.',
+ [goog.typeOf(value), value], opt_message,
+ Array.prototype.slice.call(arguments, 2));
+ }
+ return /** @type {!Array} */ (value);
+};
+
+
+/**
+ * Checks if the value is a boolean if goog.asserts.ENABLE_ASSERTS is true.
+ * @param {*} value The value to check.
+ * @param {string=} opt_message Error message in case of failure.
+ * @param {...*} var_args The items to substitute into the failure message.
+ * @return {boolean} The value, guaranteed to be a boolean when asserts are
+ * enabled.
+ * @throws {goog.asserts.AssertionError} When the value is not a boolean.
+ */
+goog.asserts.assertBoolean = function(value, opt_message, var_args) {
+ if (goog.asserts.ENABLE_ASSERTS && !goog.isBoolean(value)) {
+ goog.asserts.doAssertFailure_('Expected boolean but got %s: %s.',
+ [goog.typeOf(value), value], opt_message,
+ Array.prototype.slice.call(arguments, 2));
+ }
+ return /** @type {boolean} */ (value);
+};
+
+
+/**
+ * Checks if the value is a DOM Element if goog.asserts.ENABLE_ASSERTS is true.
+ * @param {*} value The value to check.
+ * @param {string=} opt_message Error message in case of failure.
+ * @param {...*} var_args The items to substitute into the failure message.
+ * @return {!Element} The value, likely to be a DOM Element when asserts are
+ * enabled.
+ * @throws {goog.asserts.AssertionError} When the value is not a boolean.
+ */
+goog.asserts.assertElement = function(value, opt_message, var_args) {
+ if (goog.asserts.ENABLE_ASSERTS && (!goog.isObject(value) ||
+ value.nodeType != goog.dom.NodeType.ELEMENT)) {
+ goog.asserts.doAssertFailure_('Expected Element but got %s: %s.',
+ [goog.typeOf(value), value], opt_message,
+ Array.prototype.slice.call(arguments, 2));
+ }
+ return /** @type {!Element} */ (value);
+};
+
+
+/**
+ * Checks if the value is an instance of the user-defined type if
+ * goog.asserts.ENABLE_ASSERTS is true.
+ *
+ * The compiler may tighten the type returned by this function.
+ *
+ * @param {*} value The value to check.
+ * @param {function(new: T, ...)} type A user-defined constructor.
+ * @param {string=} opt_message Error message in case of failure.
+ * @param {...*} var_args The items to substitute into the failure message.
+ * @throws {goog.asserts.AssertionError} When the value is not an instance of
+ * type.
+ * @return {!T}
+ * @template T
+ */
+goog.asserts.assertInstanceof = function(value, type, opt_message, var_args) {
+ if (goog.asserts.ENABLE_ASSERTS && !(value instanceof type)) {
+ goog.asserts.doAssertFailure_('instanceof check failed.', null,
+ opt_message, Array.prototype.slice.call(arguments, 3));
+ }
+ return value;
+};
+
+
+/**
+ * Checks that no enumerable keys are present in Object.prototype. Such keys
+ * would break most code that use {@code for (var ... in ...)} loops.
+ */
+goog.asserts.assertObjectPrototypeIsIntact = function() {
+ for (var key in Object.prototype) {
+ goog.asserts.fail(key + ' should not be enumerable in Object.prototype.');
+ }
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/async/delay.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/async/delay.js
new file mode 100644
index 0000000..1c5916a
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/async/delay.js
@@ -0,0 +1,179 @@
+// Copyright 2007 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Defines a class useful for handling functions that must be
+ * invoked after a delay, especially when that delay is frequently restarted.
+ * Examples include delaying before displaying a tooltip, menu hysteresis,
+ * idle timers, etc.
+ * @author brenneman@google.com (Shawn Brenneman)
+ * @see ../demos/timers.html
+ */
+
+
+goog.provide('goog.Delay');
+goog.provide('goog.async.Delay');
+
+goog.require('goog.Disposable');
+goog.require('goog.Timer');
+
+
+
+/**
+ * A Delay object invokes the associated function after a specified delay. The
+ * interval duration can be specified once in the constructor, or can be defined
+ * each time the delay is started. Calling start on an active delay will reset
+ * the timer.
+ *
+ * @param {Function} listener Function to call when the delay completes.
+ * @param {number=} opt_interval The default length of the invocation delay (in
+ * milliseconds).
+ * @param {Object=} opt_handler The object scope to invoke the function in.
+ * @constructor
+ * @extends {goog.Disposable}
+ * @final
+ */
+goog.async.Delay = function(listener, opt_interval, opt_handler) {
+ goog.Disposable.call(this);
+
+ /**
+ * The function that will be invoked after a delay.
+ * @type {Function}
+ * @private
+ */
+ this.listener_ = listener;
+
+ /**
+ * The default amount of time to delay before invoking the callback.
+ * @type {number}
+ * @private
+ */
+ this.interval_ = opt_interval || 0;
+
+ /**
+ * The object context to invoke the callback in.
+ * @type {Object|undefined}
+ * @private
+ */
+ this.handler_ = opt_handler;
+
+
+ /**
+ * Cached callback function invoked when the delay finishes.
+ * @type {Function}
+ * @private
+ */
+ this.callback_ = goog.bind(this.doAction_, this);
+};
+goog.inherits(goog.async.Delay, goog.Disposable);
+
+
+
+/**
+ * A deprecated alias.
+ * @deprecated Use goog.async.Delay instead.
+ * @constructor
+ * @final
+ */
+goog.Delay = goog.async.Delay;
+
+
+/**
+ * Identifier of the active delay timeout, or 0 when inactive.
+ * @type {number}
+ * @private
+ */
+goog.async.Delay.prototype.id_ = 0;
+
+
+/**
+ * Disposes of the object, cancelling the timeout if it is still outstanding and
+ * removing all object references.
+ * @override
+ * @protected
+ */
+goog.async.Delay.prototype.disposeInternal = function() {
+ goog.async.Delay.superClass_.disposeInternal.call(this);
+ this.stop();
+ delete this.listener_;
+ delete this.handler_;
+};
+
+
+/**
+ * Starts the delay timer. The provided listener function will be called after
+ * the specified interval. Calling start on an active timer will reset the
+ * delay interval.
+ * @param {number=} opt_interval If specified, overrides the object's default
+ * interval with this one (in milliseconds).
+ */
+goog.async.Delay.prototype.start = function(opt_interval) {
+ this.stop();
+ this.id_ = goog.Timer.callOnce(
+ this.callback_,
+ goog.isDef(opt_interval) ? opt_interval : this.interval_);
+};
+
+
+/**
+ * Stops the delay timer if it is active. No action is taken if the timer is not
+ * in use.
+ */
+goog.async.Delay.prototype.stop = function() {
+ if (this.isActive()) {
+ goog.Timer.clear(this.id_);
+ }
+ this.id_ = 0;
+};
+
+
+/**
+ * Fires delay's action even if timer has already gone off or has not been
+ * started yet; guarantees action firing. Stops the delay timer.
+ */
+goog.async.Delay.prototype.fire = function() {
+ this.stop();
+ this.doAction_();
+};
+
+
+/**
+ * Fires delay's action only if timer is currently active. Stops the delay
+ * timer.
+ */
+goog.async.Delay.prototype.fireIfActive = function() {
+ if (this.isActive()) {
+ this.fire();
+ }
+};
+
+
+/**
+ * @return {boolean} True if the delay is currently active, false otherwise.
+ */
+goog.async.Delay.prototype.isActive = function() {
+ return this.id_ != 0;
+};
+
+
+/**
+ * Invokes the callback function after the delay successfully completes.
+ * @private
+ */
+goog.async.Delay.prototype.doAction_ = function() {
+ this.id_ = 0;
+ if (this.listener_) {
+ this.listener_.call(this.handler_);
+ }
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/async/nexttick.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/async/nexttick.js
new file mode 100644
index 0000000..ad2cb9e
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/async/nexttick.js
@@ -0,0 +1,203 @@
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Provides a function to schedule running a function as soon
+ * as possible after the current JS execution stops and yields to the event
+ * loop.
+ *
+ */
+
+goog.provide('goog.async.nextTick');
+goog.provide('goog.async.throwException');
+
+goog.require('goog.debug.entryPointRegistry');
+goog.require('goog.functions');
+
+
+/**
+ * Throw an item without interrupting the current execution context. For
+ * example, if processing a group of items in a loop, sometimes it is useful
+ * to report an error while still allowing the rest of the batch to be
+ * processed.
+ * @param {*} exception
+ */
+goog.async.throwException = function(exception) {
+ // Each throw needs to be in its own context.
+ goog.global.setTimeout(function() { throw exception; }, 0);
+};
+
+
+/**
+ * Fires the provided callbacks as soon as possible after the current JS
+ * execution context. setTimeout(…, 0) always takes at least 5ms for legacy
+ * reasons.
+ *
+ * This will not schedule the callback as a microtask (i.e. a task that can
+ * preempt user input or networking callbacks). It is meant to emulate what
+ * setTimeout(_, 0) would do if it were not throttled. If you desire microtask
+ * behavior, use {@see goog.Promise} instead.
+ *
+ * @param {function(this:SCOPE)} callback Callback function to fire as soon as
+ * possible.
+ * @param {SCOPE=} opt_context Object in whose scope to call the listener.
+ * @template SCOPE
+ */
+goog.async.nextTick = function(callback, opt_context) {
+ var cb = callback;
+ if (opt_context) {
+ cb = goog.bind(callback, opt_context);
+ }
+ cb = goog.async.nextTick.wrapCallback_(cb);
+ // Introduced and currently only supported by IE10.
+ if (goog.isFunction(goog.global.setImmediate)) {
+ goog.global.setImmediate(cb);
+ return;
+ }
+ // Look for and cache the custom fallback version of setImmediate.
+ if (!goog.async.nextTick.setImmediate_) {
+ goog.async.nextTick.setImmediate_ =
+ goog.async.nextTick.getSetImmediateEmulator_();
+ }
+ goog.async.nextTick.setImmediate_(cb);
+};
+
+
+/**
+ * Cache for the setImmediate implementation.
+ * @type {function(function())}
+ * @private
+ */
+goog.async.nextTick.setImmediate_;
+
+
+/**
+ * Determines the best possible implementation to run a function as soon as
+ * the JS event loop is idle.
+ * @return {function(function())} The "setImmediate" implementation.
+ * @private
+ */
+goog.async.nextTick.getSetImmediateEmulator_ = function() {
+ // Create a private message channel and use it to postMessage empty messages
+ // to ourselves.
+ var Channel = goog.global['MessageChannel'];
+ // If MessageChannel is not available and we are in a browser, implement
+ // an iframe based polyfill in browsers that have postMessage and
+ // document.addEventListener. The latter excludes IE8 because it has a
+ // synchronous postMessage implementation.
+ if (typeof Channel === 'undefined' && typeof window !== 'undefined' &&
+ window.postMessage && window.addEventListener) {
+ /** @constructor */
+ Channel = function() {
+ // Make an empty, invisible iframe.
+ var iframe = document.createElement('iframe');
+ iframe.style.display = 'none';
+ iframe.src = '';
+ document.documentElement.appendChild(iframe);
+ var win = iframe.contentWindow;
+ var doc = win.document;
+ doc.open();
+ doc.write('');
+ doc.close();
+ // Do not post anything sensitive over this channel, as the workaround for
+ // pages with file: origin could allow that information to be modified or
+ // intercepted.
+ var message = 'callImmediate' + Math.random();
+ // The same origin policy rejects attempts to postMessage from file: urls
+ // unless the origin is '*'.
+ // TODO(b/16335441): Use '*' origin for data: and other similar protocols.
+ var origin = win.location.protocol == 'file:' ?
+ '*' : win.location.protocol + '//' + win.location.host;
+ var onmessage = goog.bind(function(e) {
+ // Validate origin and message to make sure that this message was
+ // intended for us.
+ if (e.origin != origin && e.data != message) {
+ return;
+ }
+ this['port1'].onmessage();
+ }, this);
+ win.addEventListener('message', onmessage, false);
+ this['port1'] = {};
+ this['port2'] = {
+ postMessage: function() {
+ win.postMessage(message, origin);
+ }
+ };
+ };
+ }
+ if (typeof Channel !== 'undefined') {
+ var channel = new Channel();
+ // Use a fifo linked list to call callbacks in the right order.
+ var head = {};
+ var tail = head;
+ channel['port1'].onmessage = function() {
+ head = head.next;
+ var cb = head.cb;
+ head.cb = null;
+ cb();
+ };
+ return function(cb) {
+ tail.next = {
+ cb: cb
+ };
+ tail = tail.next;
+ channel['port2'].postMessage(0);
+ };
+ }
+ // Implementation for IE6-8: Script elements fire an asynchronous
+ // onreadystatechange event when inserted into the DOM.
+ if (typeof document !== 'undefined' && 'onreadystatechange' in
+ document.createElement('script')) {
+ return function(cb) {
+ var script = document.createElement('script');
+ script.onreadystatechange = function() {
+ // Clean up and call the callback.
+ script.onreadystatechange = null;
+ script.parentNode.removeChild(script);
+ script = null;
+ cb();
+ cb = null;
+ };
+ document.documentElement.appendChild(script);
+ };
+ }
+ // Fall back to setTimeout with 0. In browsers this creates a delay of 5ms
+ // or more.
+ return function(cb) {
+ goog.global.setTimeout(cb, 0);
+ };
+};
+
+
+/**
+ * Helper function that is overrided to protect callbacks with entry point
+ * monitor if the application monitors entry points.
+ * @param {function()} callback Callback function to fire as soon as possible.
+ * @return {function()} The wrapped callback.
+ * @private
+ */
+goog.async.nextTick.wrapCallback_ = goog.functions.identity;
+
+
+// Register the callback function as an entry point, so that it can be
+// monitored for exception handling, etc. This has to be done in this file
+// since it requires special code to handle all browsers.
+goog.debug.entryPointRegistry.register(
+ /**
+ * @param {function(!Function): !Function} transformer The transforming
+ * function.
+ */
+ function(transformer) {
+ goog.async.nextTick.wrapCallback_ = transformer;
+ });
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/async/run.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/async/run.js
new file mode 100644
index 0000000..0010752
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/async/run.js
@@ -0,0 +1,150 @@
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+goog.provide('goog.async.run');
+
+goog.require('goog.async.nextTick');
+goog.require('goog.async.throwException');
+goog.require('goog.testing.watchers');
+
+
+/**
+ * Fires the provided callback just before the current callstack unwinds, or as
+ * soon as possible after the current JS execution context.
+ * @param {function(this:THIS)} callback
+ * @param {THIS=} opt_context Object to use as the "this value" when calling
+ * the provided function.
+ * @template THIS
+ */
+goog.async.run = function(callback, opt_context) {
+ if (!goog.async.run.schedule_) {
+ goog.async.run.initializeRunner_();
+ }
+ if (!goog.async.run.workQueueScheduled_) {
+ // Nothing is currently scheduled, schedule it now.
+ goog.async.run.schedule_();
+ goog.async.run.workQueueScheduled_ = true;
+ }
+
+ goog.async.run.workQueue_.push(
+ new goog.async.run.WorkItem_(callback, opt_context));
+};
+
+
+/**
+ * Initializes the function to use to process the work queue.
+ * @private
+ */
+goog.async.run.initializeRunner_ = function() {
+ // If native Promises are available in the browser, just schedule the callback
+ // on a fulfilled promise, which is specified to be async, but as fast as
+ // possible.
+ if (goog.global.Promise && goog.global.Promise.resolve) {
+ var promise = goog.global.Promise.resolve();
+ goog.async.run.schedule_ = function() {
+ promise.then(goog.async.run.processWorkQueue);
+ };
+ } else {
+ goog.async.run.schedule_ = function() {
+ goog.async.nextTick(goog.async.run.processWorkQueue);
+ };
+ }
+};
+
+
+/**
+ * Forces goog.async.run to use nextTick instead of Promise.
+ *
+ * This should only be done in unit tests. It's useful because MockClock
+ * replaces nextTick, but not the browser Promise implementation, so it allows
+ * Promise-based code to be tested with MockClock.
+ */
+goog.async.run.forceNextTick = function() {
+ goog.async.run.schedule_ = function() {
+ goog.async.nextTick(goog.async.run.processWorkQueue);
+ };
+};
+
+
+/**
+ * The function used to schedule work asynchronousely.
+ * @private {function()}
+ */
+goog.async.run.schedule_;
+
+
+/** @private {boolean} */
+goog.async.run.workQueueScheduled_ = false;
+
+
+/** @private {!Array.<!goog.async.run.WorkItem_>} */
+goog.async.run.workQueue_ = [];
+
+
+if (goog.DEBUG) {
+ /**
+ * Reset the event queue.
+ * @private
+ */
+ goog.async.run.resetQueue_ = function() {
+ goog.async.run.workQueueScheduled_ = false;
+ goog.async.run.workQueue_ = [];
+ };
+
+ // If there is a clock implemenation in use for testing
+ // and it is reset, reset the queue.
+ goog.testing.watchers.watchClockReset(goog.async.run.resetQueue_);
+}
+
+
+/**
+ * Run any pending goog.async.run work items. This function is not intended
+ * for general use, but for use by entry point handlers to run items ahead of
+ * goog.async.nextTick.
+ */
+goog.async.run.processWorkQueue = function() {
+ // NOTE: additional work queue items may be pushed while processing.
+ while (goog.async.run.workQueue_.length) {
+ // Don't let the work queue grow indefinitely.
+ var workItems = goog.async.run.workQueue_;
+ goog.async.run.workQueue_ = [];
+ for (var i = 0; i < workItems.length; i++) {
+ var workItem = workItems[i];
+ try {
+ workItem.fn.call(workItem.scope);
+ } catch (e) {
+ goog.async.throwException(e);
+ }
+ }
+ }
+
+ // There are no more work items, reset the work queue.
+ goog.async.run.workQueueScheduled_ = false;
+};
+
+
+
+/**
+ * @constructor
+ * @final
+ * @struct
+ * @private
+ *
+ * @param {function()} fn
+ * @param {Object|null|undefined} scope
+ */
+goog.async.run.WorkItem_ = function(fn, scope) {
+ /** @const */ this.fn = fn;
+ /** @const */ this.scope = scope;
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/base.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/base.js
new file mode 100644
index 0000000..ead806b
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/base.js
@@ -0,0 +1,1875 @@
+// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Bootstrap for the Google JS Library (Closure).
+ *
+ * In uncompiled mode base.js will write out Closure's deps file, unless the
+ * global <code>CLOSURE_NO_DEPS</code> is set to true. This allows projects to
+ * include their own deps file(s) from different locations.
+ *
+ *
+ * @provideGoog
+ */
+
+
+/**
+ * @define {boolean} Overridden to true by the compiler when --closure_pass
+ * or --mark_as_compiled is specified.
+ */
+var COMPILED = false;
+
+
+/**
+ * Base namespace for the Closure library. Checks to see goog is already
+ * defined in the current scope before assigning to prevent clobbering if
+ * base.js is loaded more than once.
+ *
+ * @const
+ */
+var goog = goog || {};
+
+
+/**
+ * Reference to the global context. In most cases this will be 'window'.
+ */
+goog.global = this;
+
+
+/**
+ * A hook for overriding the define values in uncompiled mode.
+ *
+ * In uncompiled mode, {@code CLOSURE_UNCOMPILED_DEFINES} may be defined before
+ * loading base.js. If a key is defined in {@code CLOSURE_UNCOMPILED_DEFINES},
+ * {@code goog.define} will use the value instead of the default value. This
+ * allows flags to be overwritten without compilation (this is normally
+ * accomplished with the compiler's "define" flag).
+ *
+ * Example:
+ * <pre>
+ * var CLOSURE_UNCOMPILED_DEFINES = {'goog.DEBUG': false};
+ * </pre>
+ *
+ * @type {Object.<string, (string|number|boolean)>|undefined}
+ */
+goog.global.CLOSURE_UNCOMPILED_DEFINES;
+
+
+/**
+ * A hook for overriding the define values in uncompiled or compiled mode,
+ * like CLOSURE_UNCOMPILED_DEFINES but effective in compiled code. In
+ * uncompiled code CLOSURE_UNCOMPILED_DEFINES takes precedence.
+ *
+ * Also unlike CLOSURE_UNCOMPILED_DEFINES the values must be number, boolean or
+ * string literals or the compiler will emit an error.
+ *
+ * While any @define value may be set, only those set with goog.define will be
+ * effective for uncompiled code.
+ *
+ * Example:
+ * <pre>
+ * var CLOSURE_DEFINES = {'goog.DEBUG': false};
+ * </pre>
+ *
+ * @type {Object.<string, (string|number|boolean)>|undefined}
+ */
+goog.global.CLOSURE_DEFINES;
+
+
+/**
+ * Returns true if the specified value is not undefined.
+ * WARNING: Do not use this to test if an object has a property. Use the in
+ * operator instead.
+ *
+ * @param {?} val Variable to test.
+ * @return {boolean} Whether variable is defined.
+ */
+goog.isDef = function(val) {
+ // void 0 always evaluates to undefined and hence we do not need to depend on
+ // the definition of the global variable named 'undefined'.
+ return val !== void 0;
+};
+
+
+/**
+ * Builds an object structure for the provided namespace path, ensuring that
+ * names that already exist are not overwritten. For example:
+ * "a.b.c" -> a = {};a.b={};a.b.c={};
+ * Used by goog.provide and goog.exportSymbol.
+ * @param {string} name name of the object that this file defines.
+ * @param {*=} opt_object the object to expose at the end of the path.
+ * @param {Object=} opt_objectToExportTo The object to add the path to; default
+ * is |goog.global|.
+ * @private
+ */
+goog.exportPath_ = function(name, opt_object, opt_objectToExportTo) {
+ var parts = name.split('.');
+ var cur = opt_objectToExportTo || goog.global;
+
+ // Internet Explorer exhibits strange behavior when throwing errors from
+ // methods externed in this manner. See the testExportSymbolExceptions in
+ // base_test.html for an example.
+ if (!(parts[0] in cur) && cur.execScript) {
+ cur.execScript('var ' + parts[0]);
+ }
+
+ // Certain browsers cannot parse code in the form for((a in b); c;);
+ // This pattern is produced by the JSCompiler when it collapses the
+ // statement above into the conditional loop below. To prevent this from
+ // happening, use a for-loop and reserve the init logic as below.
+
+ // Parentheses added to eliminate strict JS warning in Firefox.
+ for (var part; parts.length && (part = parts.shift());) {
+ if (!parts.length && goog.isDef(opt_object)) {
+ // last part and we have an object; use it
+ cur[part] = opt_object;
+ } else if (cur[part]) {
+ cur = cur[part];
+ } else {
+ cur = cur[part] = {};
+ }
+ }
+};
+
+
+/**
+ * Defines a named value. In uncompiled mode, the value is retreived from
+ * CLOSURE_DEFINES or CLOSURE_UNCOMPILED_DEFINES if the object is defined and
+ * has the property specified, and otherwise used the defined defaultValue.
+ * When compiled, the default can be overridden using compiler command-line
+ * options.
+ *
+ * @param {string} name The distinguished name to provide.
+ * @param {string|number|boolean} defaultValue
+ */
+goog.define = function(name, defaultValue) {
+ var value = defaultValue;
+ if (!COMPILED) {
+ if (goog.global.CLOSURE_UNCOMPILED_DEFINES &&
+ Object.prototype.hasOwnProperty.call(
+ goog.global.CLOSURE_UNCOMPILED_DEFINES, name)) {
+ value = goog.global.CLOSURE_UNCOMPILED_DEFINES[name];
+ } else if (goog.global.CLOSURE_DEFINES &&
+ Object.prototype.hasOwnProperty.call(
+ goog.global.CLOSURE_DEFINES, name)) {
+ value = goog.global.CLOSURE_DEFINES[name];
+ }
+ }
+ goog.exportPath_(name, value);
+};
+
+
+/**
+ * @define {boolean} DEBUG is provided as a convenience so that debugging code
+ * that should not be included in a production js_binary can be easily stripped
+ * by specifying --define goog.DEBUG=false to the JSCompiler. For example, most
+ * toString() methods should be declared inside an "if (goog.DEBUG)" conditional
+ * because they are generally used for debugging purposes and it is difficult
+ * for the JSCompiler to statically determine whether they are used.
+ */
+goog.DEBUG = true;
+
+
+/**
+ * @define {string} LOCALE defines the locale being used for compilation. It is
+ * used to select locale specific data to be compiled in js binary. BUILD rule
+ * can specify this value by "--define goog.LOCALE=<locale_name>" as JSCompiler
+ * option.
+ *
+ * Take into account that the locale code format is important. You should use
+ * the canonical Unicode format with hyphen as a delimiter. Language must be
+ * lowercase, Language Script - Capitalized, Region - UPPERCASE.
+ * There are few examples: pt-BR, en, en-US, sr-Latin-BO, zh-Hans-CN.
+ *
+ * See more info about locale codes here:
+ * http://www.unicode.org/reports/tr35/#Unicode_Language_and_Locale_Identifiers
+ *
+ * For language codes you should use values defined by ISO 693-1. See it here
+ * http://www.w3.org/WAI/ER/IG/ert/iso639.htm. There is only one exception from
+ * this rule: the Hebrew language. For legacy reasons the old code (iw) should
+ * be used instead of the new code (he), see http://wiki/Main/IIISynonyms.
+ */
+goog.define('goog.LOCALE', 'en'); // default to en
+
+
+/**
+ * @define {boolean} Whether this code is running on trusted sites.
+ *
+ * On untrusted sites, several native functions can be defined or overridden by
+ * external libraries like Prototype, Datejs, and JQuery and setting this flag
+ * to false forces closure to use its own implementations when possible.
+ *
+ * If your JavaScript can be loaded by a third party site and you are wary about
+ * relying on non-standard implementations, specify
+ * "--define goog.TRUSTED_SITE=false" to the JSCompiler.
+ */
+goog.define('goog.TRUSTED_SITE', true);
+
+
+/**
+ * @define {boolean} Whether a project is expected to be running in strict mode.
+ *
+ * This define can be used to trigger alternate implementations compatible with
+ * running in EcmaScript Strict mode or warn about unavailable functionality.
+ * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope/Strict_mode
+ */
+goog.define('goog.STRICT_MODE_COMPATIBLE', false);
+
+
+/**
+ * Creates object stubs for a namespace. The presence of one or more
+ * goog.provide() calls indicate that the file defines the given
+ * objects/namespaces. Provided objects must not be null or undefined.
+ * Build tools also scan for provide/require statements
+ * to discern dependencies, build dependency files (see deps.js), etc.
+ * @see goog.require
+ * @param {string} name Namespace provided by this file in the form
+ * "goog.package.part".
+ */
+goog.provide = function(name) {
+ if (!COMPILED) {
+ // Ensure that the same namespace isn't provided twice. This is intended
+ // to teach new developers that 'goog.provide' is effectively a variable
+ // declaration. And when JSCompiler transforms goog.provide into a real
+ // variable declaration, the compiled JS should work the same as the raw
+ // JS--even when the raw JS uses goog.provide incorrectly.
+ if (goog.isProvided_(name)) {
+ throw Error('Namespace "' + name + '" already declared.');
+ }
+ delete goog.implicitNamespaces_[name];
+
+ var namespace = name;
+ while ((namespace = namespace.substring(0, namespace.lastIndexOf('.')))) {
+ if (goog.getObjectByName(namespace)) {
+ break;
+ }
+ goog.implicitNamespaces_[namespace] = true;
+ }
+ }
+
+ goog.exportPath_(name);
+};
+
+
+/**
+ * Marks that the current file should only be used for testing, and never for
+ * live code in production.
+ *
+ * In the case of unit tests, the message may optionally be an exact namespace
+ * for the test (e.g. 'goog.stringTest'). The linter will then ignore the extra
+ * provide (if not explicitly defined in the code).
+ *
+ * @param {string=} opt_message Optional message to add to the error that's
+ * raised when used in production code.
+ */
+goog.setTestOnly = function(opt_message) {
+ if (COMPILED && !goog.DEBUG) {
+ opt_message = opt_message || '';
+ throw Error('Importing test-only code into non-debug environment' +
+ (opt_message ? ': ' + opt_message : '.'));
+ }
+};
+
+
+/**
+ * Forward declares a symbol. This is an indication to the compiler that the
+ * symbol may be used in the source yet is not required and may not be provided
+ * in compilation.
+ *
+ * The most common usage of forward declaration is code that takes a type as a
+ * function parameter but does not need to require it. By forward declaring
+ * instead of requiring, no hard dependency is made, and (if not required
+ * elsewhere) the namespace may never be required and thus, not be pulled
+ * into the JavaScript binary. If it is required elsewhere, it will be type
+ * checked as normal.
+ *
+ *
+ * @param {string} name The namespace to forward declare in the form of
+ * "goog.package.part".
+ */
+goog.forwardDeclare = function(name) {};
+
+
+if (!COMPILED) {
+
+ /**
+ * Check if the given name has been goog.provided. This will return false for
+ * names that are available only as implicit namespaces.
+ * @param {string} name name of the object to look for.
+ * @return {boolean} Whether the name has been provided.
+ * @private
+ */
+ goog.isProvided_ = function(name) {
+ return !goog.implicitNamespaces_[name] &&
+ goog.isDefAndNotNull(goog.getObjectByName(name));
+ };
+
+ /**
+ * Namespaces implicitly defined by goog.provide. For example,
+ * goog.provide('goog.events.Event') implicitly declares that 'goog' and
+ * 'goog.events' must be namespaces.
+ *
+ * @type {Object}
+ * @private
+ */
+ goog.implicitNamespaces_ = {};
+}
+
+
+/**
+ * Returns an object based on its fully qualified external name. The object
+ * is not found if null or undefined. If you are using a compilation pass that
+ * renames property names beware that using this function will not find renamed
+ * properties.
+ *
+ * @param {string} name The fully qualified name.
+ * @param {Object=} opt_obj The object within which to look; default is
+ * |goog.global|.
+ * @return {?} The value (object or primitive) or, if not found, null.
+ */
+goog.getObjectByName = function(name, opt_obj) {
+ var parts = name.split('.');
+ var cur = opt_obj || goog.global;
+ for (var part; part = parts.shift(); ) {
+ if (goog.isDefAndNotNull(cur[part])) {
+ cur = cur[part];
+ } else {
+ return null;
+ }
+ }
+ return cur;
+};
+
+
+/**
+ * Globalizes a whole namespace, such as goog or goog.lang.
+ *
+ * @param {Object} obj The namespace to globalize.
+ * @param {Object=} opt_global The object to add the properties to.
+ * @deprecated Properties may be explicitly exported to the global scope, but
+ * this should no longer be done in bulk.
+ */
+goog.globalize = function(obj, opt_global) {
+ var global = opt_global || goog.global;
+ for (var x in obj) {
+ global[x] = obj[x];
+ }
+};
+
+
+/**
+ * Adds a dependency from a file to the files it requires.
+ * @param {string} relPath The path to the js file.
+ * @param {Array} provides An array of strings with the names of the objects
+ * this file provides.
+ * @param {Array} requires An array of strings with the names of the objects
+ * this file requires.
+ */
+goog.addDependency = function(relPath, provides, requires) {
+ if (goog.DEPENDENCIES_ENABLED) {
+ var provide, require;
+ var path = relPath.replace(/\\/g, '/');
+ var deps = goog.dependencies_;
+ for (var i = 0; provide = provides[i]; i++) {
+ deps.nameToPath[provide] = path;
+ if (!(path in deps.pathToNames)) {
+ deps.pathToNames[path] = {};
+ }
+ deps.pathToNames[path][provide] = true;
+ }
+ for (var j = 0; require = requires[j]; j++) {
+ if (!(path in deps.requires)) {
+ deps.requires[path] = {};
+ }
+ deps.requires[path][require] = true;
+ }
+ }
+};
+
+
+
+
+// NOTE(nnaze): The debug DOM loader was included in base.js as an original way
+// to do "debug-mode" development. The dependency system can sometimes be
+// confusing, as can the debug DOM loader's asynchronous nature.
+//
+// With the DOM loader, a call to goog.require() is not blocking -- the script
+// will not load until some point after the current script. If a namespace is
+// needed at runtime, it needs to be defined in a previous script, or loaded via
+// require() with its registered dependencies.
+// User-defined namespaces may need their own deps file. See http://go/js_deps,
+// http://go/genjsdeps, or, externally, DepsWriter.
+// https://developers.google.com/closure/library/docs/depswriter
+//
+// Because of legacy clients, the DOM loader can't be easily removed from
+// base.js. Work is being done to make it disableable or replaceable for
+// different environments (DOM-less JavaScript interpreters like Rhino or V8,
+// for example). See bootstrap/ for more information.
+
+
+/**
+ * @define {boolean} Whether to enable the debug loader.
+ *
+ * If enabled, a call to goog.require() will attempt to load the namespace by
+ * appending a script tag to the DOM (if the namespace has been registered).
+ *
+ * If disabled, goog.require() will simply assert that the namespace has been
+ * provided (and depend on the fact that some outside tool correctly ordered
+ * the script).
+ */
+goog.define('goog.ENABLE_DEBUG_LOADER', true);
+
+
+/**
+ * Implements a system for the dynamic resolution of dependencies that works in
+ * parallel with the BUILD system. Note that all calls to goog.require will be
+ * stripped by the JSCompiler when the --closure_pass option is used.
+ * @see goog.provide
+ * @param {string} name Namespace to include (as was given in goog.provide()) in
+ * the form "goog.package.part".
+ */
+goog.require = function(name) {
+
+ // If the object already exists we do not need do do anything.
+ // TODO(arv): If we start to support require based on file name this has to
+ // change.
+ // TODO(arv): If we allow goog.foo.* this has to change.
+ // TODO(arv): If we implement dynamic load after page load we should probably
+ // not remove this code for the compiled output.
+ if (!COMPILED) {
+ if (goog.isProvided_(name)) {
+ return;
+ }
+
+ if (goog.ENABLE_DEBUG_LOADER) {
+ var path = goog.getPathFromDeps_(name);
+ if (path) {
+ goog.included_[path] = true;
+ goog.writeScripts_();
+ return;
+ }
+ }
+
+ var errorMessage = 'goog.require could not find: ' + name;
+ if (goog.global.console) {
+ goog.global.console['error'](errorMessage);
+ }
+
+
+ throw Error(errorMessage);
+
+ }
+};
+
+
+/**
+ * Path for included scripts.
+ * @type {string}
+ */
+goog.basePath = '';
+
+
+/**
+ * A hook for overriding the base path.
+ * @type {string|undefined}
+ */
+goog.global.CLOSURE_BASE_PATH;
+
+
+/**
+ * Whether to write out Closure's deps file. By default, the deps are written.
+ * @type {boolean|undefined}
+ */
+goog.global.CLOSURE_NO_DEPS;
+
+
+/**
+ * A function to import a single script. This is meant to be overridden when
+ * Closure is being run in non-HTML contexts, such as web workers. It's defined
+ * in the global scope so that it can be set before base.js is loaded, which
+ * allows deps.js to be imported properly.
+ *
+ * The function is passed the script source, which is a relative URI. It should
+ * return true if the script was imported, false otherwise.
+ * @type {(function(string): boolean)|undefined}
+ */
+goog.global.CLOSURE_IMPORT_SCRIPT;
+
+
+/**
+ * Null function used for default values of callbacks, etc.
+ * @return {void} Nothing.
+ */
+goog.nullFunction = function() {};
+
+
+/**
+ * The identity function. Returns its first argument.
+ *
+ * @param {*=} opt_returnValue The single value that will be returned.
+ * @param {...*} var_args Optional trailing arguments. These are ignored.
+ * @return {?} The first argument. We can't know the type -- just pass it along
+ * without type.
+ * @deprecated Use goog.functions.identity instead.
+ */
+goog.identityFunction = function(opt_returnValue, var_args) {
+ return opt_returnValue;
+};
+
+
+/**
+ * When defining a class Foo with an abstract method bar(), you can do:
+ * Foo.prototype.bar = goog.abstractMethod
+ *
+ * Now if a subclass of Foo fails to override bar(), an error will be thrown
+ * when bar() is invoked.
+ *
+ * Note: This does not take the name of the function to override as an argument
+ * because that would make it more difficult to obfuscate our JavaScript code.
+ *
+ * @type {!Function}
+ * @throws {Error} when invoked to indicate the method should be overridden.
+ */
+goog.abstractMethod = function() {
+ throw Error('unimplemented abstract method');
+};
+
+
+/**
+ * Adds a {@code getInstance} static method that always returns the same
+ * instance object.
+ * @param {!Function} ctor The constructor for the class to add the static
+ * method to.
+ */
+goog.addSingletonGetter = function(ctor) {
+ ctor.getInstance = function() {
+ if (ctor.instance_) {
+ return ctor.instance_;
+ }
+ if (goog.DEBUG) {
+ // NOTE: JSCompiler can't optimize away Array#push.
+ goog.instantiatedSingletons_[goog.instantiatedSingletons_.length] = ctor;
+ }
+ return ctor.instance_ = new ctor;
+ };
+};
+
+
+/**
+ * All singleton classes that have been instantiated, for testing. Don't read
+ * it directly, use the {@code goog.testing.singleton} module. The compiler
+ * removes this variable if unused.
+ * @type {!Array.<!Function>}
+ * @private
+ */
+goog.instantiatedSingletons_ = [];
+
+
+/**
+ * True if goog.dependencies_ is available.
+ * @const {boolean}
+ */
+goog.DEPENDENCIES_ENABLED = !COMPILED && goog.ENABLE_DEBUG_LOADER;
+
+
+if (goog.DEPENDENCIES_ENABLED) {
+ /**
+ * Object used to keep track of urls that have already been added. This record
+ * allows the prevention of circular dependencies.
+ * @type {Object}
+ * @private
+ */
+ goog.included_ = {};
+
+
+ /**
+ * This object is used to keep track of dependencies and other data that is
+ * used for loading scripts.
+ * @private
+ * @type {Object}
+ */
+ goog.dependencies_ = {
+ pathToNames: {}, // 1 to many
+ nameToPath: {}, // 1 to 1
+ requires: {}, // 1 to many
+ // Used when resolving dependencies to prevent us from visiting file twice.
+ visited: {},
+ written: {} // Used to keep track of script files we have written.
+ };
+
+
+ /**
+ * Tries to detect whether is in the context of an HTML document.
+ * @return {boolean} True if it looks like HTML document.
+ * @private
+ */
+ goog.inHtmlDocument_ = function() {
+ var doc = goog.global.document;
+ return typeof doc != 'undefined' &&
+ 'write' in doc; // XULDocument misses write.
+ };
+
+
+ /**
+ * Tries to detect the base path of base.js script that bootstraps Closure.
+ * @private
+ */
+ goog.findBasePath_ = function() {
+ if (goog.global.CLOSURE_BASE_PATH) {
+ goog.basePath = goog.global.CLOSURE_BASE_PATH;
+ return;
+ } else if (!goog.inHtmlDocument_()) {
+ return;
+ }
+ var doc = goog.global.document;
+ var scripts = doc.getElementsByTagName('script');
+ // Search backwards since the current script is in almost all cases the one
+ // that has base.js.
+ for (var i = scripts.length - 1; i >= 0; --i) {
+ var src = scripts[i].src;
+ var qmark = src.lastIndexOf('?');
+ var l = qmark == -1 ? src.length : qmark;
+ if (src.substr(l - 7, 7) == 'base.js') {
+ goog.basePath = src.substr(0, l - 7);
+ return;
+ }
+ }
+ };
+
+
+ /**
+ * Imports a script if, and only if, that script hasn't already been imported.
+ * (Must be called at execution time)
+ * @param {string} src Script source.
+ * @private
+ */
+ goog.importScript_ = function(src) {
+ var importScript = goog.global.CLOSURE_IMPORT_SCRIPT ||
+ goog.writeScriptTag_;
+ if (!goog.dependencies_.written[src] && importScript(src)) {
+ goog.dependencies_.written[src] = true;
+ }
+ };
+
+
+ /**
+ * The default implementation of the import function. Writes a script tag to
+ * import the script.
+ *
+ * @param {string} src The script source.
+ * @return {boolean} True if the script was imported, false otherwise.
+ * @private
+ */
+ goog.writeScriptTag_ = function(src) {
+ if (goog.inHtmlDocument_()) {
+ var doc = goog.global.document;
+
+ // If the user tries to require a new symbol after document load,
+ // something has gone terribly wrong. Doing a document.write would
+ // wipe out the page.
+ if (doc.readyState == 'complete') {
+ // Certain test frameworks load base.js multiple times, which tries
+ // to write deps.js each time. If that happens, just fail silently.
+ // These frameworks wipe the page between each load of base.js, so this
+ // is OK.
+ var isDeps = /\bdeps.js$/.test(src);
+ if (isDeps) {
+ return false;
+ } else {
+ throw Error('Cannot write "' + src + '" after document load');
+ }
+ }
+
+ doc.write(
+ '<script type="text/javascript" src="' + src + '"></' + 'script>');
+ return true;
+ } else {
+ return false;
+ }
+ };
+
+
+ /**
+ * Resolves dependencies based on the dependencies added using addDependency
+ * and calls importScript_ in the correct order.
+ * @private
+ */
+ goog.writeScripts_ = function() {
+ // The scripts we need to write this time.
+ var scripts = [];
+ var seenScript = {};
+ var deps = goog.dependencies_;
+
+ function visitNode(path) {
+ if (path in deps.written) {
+ return;
+ }
+
+ // We have already visited this one. We can get here if we have cyclic
+ // dependencies.
+ if (path in deps.visited) {
+ if (!(path in seenScript)) {
+ seenScript[path] = true;
+ scripts.push(path);
+ }
+ return;
+ }
+
+ deps.visited[path] = true;
+
+ if (path in deps.requires) {
+ for (var requireName in deps.requires[path]) {
+ // If the required name is defined, we assume that it was already
+ // bootstrapped by other means.
+ if (!goog.isProvided_(requireName)) {
+ if (requireName in deps.nameToPath) {
+ visitNode(deps.nameToPath[requireName]);
+ } else {
+ throw Error('Undefined nameToPath for ' + requireName);
+ }
+ }
+ }
+ }
+
+ if (!(path in seenScript)) {
+ seenScript[path] = true;
+ scripts.push(path);
+ }
+ }
+
+ for (var path in goog.included_) {
+ if (!deps.written[path]) {
+ visitNode(path);
+ }
+ }
+
+ for (var i = 0; i < scripts.length; i++) {
+ if (scripts[i]) {
+ goog.importScript_(goog.basePath + scripts[i]);
+ } else {
+ throw Error('Undefined script input');
+ }
+ }
+ };
+
+
+ /**
+ * Looks at the dependency rules and tries to determine the script file that
+ * fulfills a particular rule.
+ * @param {string} rule In the form goog.namespace.Class or project.script.
+ * @return {?string} Url corresponding to the rule, or null.
+ * @private
+ */
+ goog.getPathFromDeps_ = function(rule) {
+ if (rule in goog.dependencies_.nameToPath) {
+ return goog.dependencies_.nameToPath[rule];
+ } else {
+ return null;
+ }
+ };
+
+ goog.findBasePath_();
+
+ // Allow projects to manage the deps files themselves.
+ if (!goog.global.CLOSURE_NO_DEPS) {
+ goog.importScript_(goog.basePath + 'deps.js');
+ }
+}
+
+
+
+//==============================================================================
+// Language Enhancements
+//==============================================================================
+
+
+/**
+ * This is a "fixed" version of the typeof operator. It differs from the typeof
+ * operator in such a way that null returns 'null' and arrays return 'array'.
+ * @param {*} value The value to get the type of.
+ * @return {string} The name of the type.
+ */
+goog.typeOf = function(value) {
+ var s = typeof value;
+ if (s == 'object') {
+ if (value) {
+ // Check these first, so we can avoid calling Object.prototype.toString if
+ // possible.
+ //
+ // IE improperly marshals tyepof across execution contexts, but a
+ // cross-context object will still return false for "instanceof Object".
+ if (value instanceof Array) {
+ return 'array';
+ } else if (value instanceof Object) {
+ return s;
+ }
+
+ // HACK: In order to use an Object prototype method on the arbitrary
+ // value, the compiler requires the value be cast to type Object,
+ // even though the ECMA spec explicitly allows it.
+ var className = Object.prototype.toString.call(
+ /** @type {Object} */ (value));
+ // In Firefox 3.6, attempting to access iframe window objects' length
+ // property throws an NS_ERROR_FAILURE, so we need to special-case it
+ // here.
+ if (className == '[object Window]') {
+ return 'object';
+ }
+
+ // We cannot always use constructor == Array or instanceof Array because
+ // different frames have different Array objects. In IE6, if the iframe
+ // where the array was created is destroyed, the array loses its
+ // prototype. Then dereferencing val.splice here throws an exception, so
+ // we can't use goog.isFunction. Calling typeof directly returns 'unknown'
+ // so that will work. In this case, this function will return false and
+ // most array functions will still work because the array is still
+ // array-like (supports length and []) even though it has lost its
+ // prototype.
+ // Mark Miller noticed that Object.prototype.toString
+ // allows access to the unforgeable [[Class]] property.
+ // 15.2.4.2 Object.prototype.toString ( )
+ // When the toString method is called, the following steps are taken:
+ // 1. Get the [[Class]] property of this object.
+ // 2. Compute a string value by concatenating the three strings
+ // "[object ", Result(1), and "]".
+ // 3. Return Result(2).
+ // and this behavior survives the destruction of the execution context.
+ if ((className == '[object Array]' ||
+ // In IE all non value types are wrapped as objects across window
+ // boundaries (not iframe though) so we have to do object detection
+ // for this edge case.
+ typeof value.length == 'number' &&
+ typeof value.splice != 'undefined' &&
+ typeof value.propertyIsEnumerable != 'undefined' &&
+ !value.propertyIsEnumerable('splice')
+
+ )) {
+ return 'array';
+ }
+ // HACK: There is still an array case that fails.
+ // function ArrayImpostor() {}
+ // ArrayImpostor.prototype = [];
+ // var impostor = new ArrayImpostor;
+ // this can be fixed by getting rid of the fast path
+ // (value instanceof Array) and solely relying on
+ // (value && Object.prototype.toString.vall(value) === '[object Array]')
+ // but that would require many more function calls and is not warranted
+ // unless closure code is receiving objects from untrusted sources.
+
+ // IE in cross-window calls does not correctly marshal the function type
+ // (it appears just as an object) so we cannot use just typeof val ==
+ // 'function'. However, if the object has a call property, it is a
+ // function.
+ if ((className == '[object Function]' ||
+ typeof value.call != 'undefined' &&
+ typeof value.propertyIsEnumerable != 'undefined' &&
+ !value.propertyIsEnumerable('call'))) {
+ return 'function';
+ }
+
+ } else {
+ return 'null';
+ }
+
+ } else if (s == 'function' && typeof value.call == 'undefined') {
+ // In Safari typeof nodeList returns 'function', and on Firefox typeof
+ // behaves similarly for HTML{Applet,Embed,Object}, Elements and RegExps. We
+ // would like to return object for those and we can detect an invalid
+ // function by making sure that the function object has a call method.
+ return 'object';
+ }
+ return s;
+};
+
+
+/**
+ * Returns true if the specified value is null.
+ * @param {?} val Variable to test.
+ * @return {boolean} Whether variable is null.
+ */
+goog.isNull = function(val) {
+ return val === null;
+};
+
+
+/**
+ * Returns true if the specified value is defined and not null.
+ * @param {?} val Variable to test.
+ * @return {boolean} Whether variable is defined and not null.
+ */
+goog.isDefAndNotNull = function(val) {
+ // Note that undefined == null.
+ return val != null;
+};
+
+
+/**
+ * Returns true if the specified value is an array.
+ * @param {?} val Variable to test.
+ * @return {boolean} Whether variable is an array.
+ */
+goog.isArray = function(val) {
+ return goog.typeOf(val) == 'array';
+};
+
+
+/**
+ * Returns true if the object looks like an array. To qualify as array like
+ * the value needs to be either a NodeList or an object with a Number length
+ * property.
+ * @param {?} val Variable to test.
+ * @return {boolean} Whether variable is an array.
+ */
+goog.isArrayLike = function(val) {
+ var type = goog.typeOf(val);
+ return type == 'array' || type == 'object' && typeof val.length == 'number';
+};
+
+
+/**
+ * Returns true if the object looks like a Date. To qualify as Date-like the
+ * value needs to be an object and have a getFullYear() function.
+ * @param {?} val Variable to test.
+ * @return {boolean} Whether variable is a like a Date.
+ */
+goog.isDateLike = function(val) {
+ return goog.isObject(val) && typeof val.getFullYear == 'function';
+};
+
+
+/**
+ * Returns true if the specified value is a string.
+ * @param {?} val Variable to test.
+ * @return {boolean} Whether variable is a string.
+ */
+goog.isString = function(val) {
+ return typeof val == 'string';
+};
+
+
+/**
+ * Returns true if the specified value is a boolean.
+ * @param {?} val Variable to test.
+ * @return {boolean} Whether variable is boolean.
+ */
+goog.isBoolean = function(val) {
+ return typeof val == 'boolean';
+};
+
+
+/**
+ * Returns true if the specified value is a number.
+ * @param {?} val Variable to test.
+ * @return {boolean} Whether variable is a number.
+ */
+goog.isNumber = function(val) {
+ return typeof val == 'number';
+};
+
+
+/**
+ * Returns true if the specified value is a function.
+ * @param {?} val Variable to test.
+ * @return {boolean} Whether variable is a function.
+ */
+goog.isFunction = function(val) {
+ return goog.typeOf(val) == 'function';
+};
+
+
+/**
+ * Returns true if the specified value is an object. This includes arrays and
+ * functions.
+ * @param {?} val Variable to test.
+ * @return {boolean} Whether variable is an object.
+ */
+goog.isObject = function(val) {
+ var type = typeof val;
+ return type == 'object' && val != null || type == 'function';
+ // return Object(val) === val also works, but is slower, especially if val is
+ // not an object.
+};
+
+
+/**
+ * Gets a unique ID for an object. This mutates the object so that further calls
+ * with the same object as a parameter returns the same value. The unique ID is
+ * guaranteed to be unique across the current session amongst objects that are
+ * passed into {@code getUid}. There is no guarantee that the ID is unique or
+ * consistent across sessions. It is unsafe to generate unique ID for function
+ * prototypes.
+ *
+ * @param {Object} obj The object to get the unique ID for.
+ * @return {number} The unique ID for the object.
+ */
+goog.getUid = function(obj) {
+ // TODO(arv): Make the type stricter, do not accept null.
+
+ // In Opera window.hasOwnProperty exists but always returns false so we avoid
+ // using it. As a consequence the unique ID generated for BaseClass.prototype
+ // and SubClass.prototype will be the same.
+ return obj[goog.UID_PROPERTY_] ||
+ (obj[goog.UID_PROPERTY_] = ++goog.uidCounter_);
+};
+
+
+/**
+ * Whether the given object is alreay assigned a unique ID.
+ *
+ * This does not modify the object.
+ *
+ * @param {Object} obj The object to check.
+ * @return {boolean} Whether there an assigned unique id for the object.
+ */
+goog.hasUid = function(obj) {
+ return !!obj[goog.UID_PROPERTY_];
+};
+
+
+/**
+ * Removes the unique ID from an object. This is useful if the object was
+ * previously mutated using {@code goog.getUid} in which case the mutation is
+ * undone.
+ * @param {Object} obj The object to remove the unique ID field from.
+ */
+goog.removeUid = function(obj) {
+ // TODO(arv): Make the type stricter, do not accept null.
+
+ // In IE, DOM nodes are not instances of Object and throw an exception if we
+ // try to delete. Instead we try to use removeAttribute.
+ if ('removeAttribute' in obj) {
+ obj.removeAttribute(goog.UID_PROPERTY_);
+ }
+ /** @preserveTry */
+ try {
+ delete obj[goog.UID_PROPERTY_];
+ } catch (ex) {
+ }
+};
+
+
+/**
+ * Name for unique ID property. Initialized in a way to help avoid collisions
+ * with other closure JavaScript on the same page.
+ * @type {string}
+ * @private
+ */
+goog.UID_PROPERTY_ = 'closure_uid_' + ((Math.random() * 1e9) >>> 0);
+
+
+/**
+ * Counter for UID.
+ * @type {number}
+ * @private
+ */
+goog.uidCounter_ = 0;
+
+
+/**
+ * Adds a hash code field to an object. The hash code is unique for the
+ * given object.
+ * @param {Object} obj The object to get the hash code for.
+ * @return {number} The hash code for the object.
+ * @deprecated Use goog.getUid instead.
+ */
+goog.getHashCode = goog.getUid;
+
+
+/**
+ * Removes the hash code field from an object.
+ * @param {Object} obj The object to remove the field from.
+ * @deprecated Use goog.removeUid instead.
+ */
+goog.removeHashCode = goog.removeUid;
+
+
+/**
+ * Clones a value. The input may be an Object, Array, or basic type. Objects and
+ * arrays will be cloned recursively.
+ *
+ * WARNINGS:
+ * <code>goog.cloneObject</code> does not detect reference loops. Objects that
+ * refer to themselves will cause infinite recursion.
+ *
+ * <code>goog.cloneObject</code> is unaware of unique identifiers, and copies
+ * UIDs created by <code>getUid</code> into cloned results.
+ *
+ * @param {*} obj The value to clone.
+ * @return {*} A clone of the input value.
+ * @deprecated goog.cloneObject is unsafe. Prefer the goog.object methods.
+ */
+goog.cloneObject = function(obj) {
+ var type = goog.typeOf(obj);
+ if (type == 'object' || type == 'array') {
+ if (obj.clone) {
+ return obj.clone();
+ }
+ var clone = type == 'array' ? [] : {};
+ for (var key in obj) {
+ clone[key] = goog.cloneObject(obj[key]);
+ }
+ return clone;
+ }
+
+ return obj;
+};
+
+
+/**
+ * A native implementation of goog.bind.
+ * @param {Function} fn A function to partially apply.
+ * @param {Object|undefined} selfObj Specifies the object which this should
+ * point to when the function is run.
+ * @param {...*} var_args Additional arguments that are partially applied to the
+ * function.
+ * @return {!Function} A partially-applied form of the function bind() was
+ * invoked as a method of.
+ * @private
+ * @suppress {deprecated} The compiler thinks that Function.prototype.bind is
+ * deprecated because some people have declared a pure-JS version.
+ * Only the pure-JS version is truly deprecated.
+ */
+goog.bindNative_ = function(fn, selfObj, var_args) {
+ return /** @type {!Function} */ (fn.call.apply(fn.bind, arguments));
+};
+
+
+/**
+ * A pure-JS implementation of goog.bind.
+ * @param {Function} fn A function to partially apply.
+ * @param {Object|undefined} selfObj Specifies the object which this should
+ * point to when the function is run.
+ * @param {...*} var_args Additional arguments that are partially applied to the
+ * function.
+ * @return {!Function} A partially-applied form of the function bind() was
+ * invoked as a method of.
+ * @private
+ */
+goog.bindJs_ = function(fn, selfObj, var_args) {
+ if (!fn) {
+ throw new Error();
+ }
+
+ if (arguments.length > 2) {
+ var boundArgs = Array.prototype.slice.call(arguments, 2);
+ return function() {
+ // Prepend the bound arguments to the current arguments.
+ var newArgs = Array.prototype.slice.call(arguments);
+ Array.prototype.unshift.apply(newArgs, boundArgs);
+ return fn.apply(selfObj, newArgs);
+ };
+
+ } else {
+ return function() {
+ return fn.apply(selfObj, arguments);
+ };
+ }
+};
+
+
+/**
+ * Partially applies this function to a particular 'this object' and zero or
+ * more arguments. The result is a new function with some arguments of the first
+ * function pre-filled and the value of this 'pre-specified'.
+ *
+ * Remaining arguments specified at call-time are appended to the pre-specified
+ * ones.
+ *
+ * Also see: {@link #partial}.
+ *
+ * Usage:
+ * <pre>var barMethBound = bind(myFunction, myObj, 'arg1', 'arg2');
+ * barMethBound('arg3', 'arg4');</pre>
+ *
+ * @param {?function(this:T, ...)} fn A function to partially apply.
+ * @param {T} selfObj Specifies the object which this should point to when the
+ * function is run.
+ * @param {...*} var_args Additional arguments that are partially applied to the
+ * function.
+ * @return {!Function} A partially-applied form of the function bind() was
+ * invoked as a method of.
+ * @template T
+ * @suppress {deprecated} See above.
+ */
+goog.bind = function(fn, selfObj, var_args) {
+ // TODO(nicksantos): narrow the type signature.
+ if (Function.prototype.bind &&
+ // NOTE(nicksantos): Somebody pulled base.js into the default Chrome
+ // extension environment. This means that for Chrome extensions, they get
+ // the implementation of Function.prototype.bind that calls goog.bind
+ // instead of the native one. Even worse, we don't want to introduce a
+ // circular dependency between goog.bind and Function.prototype.bind, so
+ // we have to hack this to make sure it works correctly.
+ Function.prototype.bind.toString().indexOf('native code') != -1) {
+ goog.bind = goog.bindNative_;
+ } else {
+ goog.bind = goog.bindJs_;
+ }
+ return goog.bind.apply(null, arguments);
+};
+
+
+/**
+ * Like bind(), except that a 'this object' is not required. Useful when the
+ * target function is already bound.
+ *
+ * Usage:
+ * var g = partial(f, arg1, arg2);
+ * g(arg3, arg4);
+ *
+ * @param {Function} fn A function to partially apply.
+ * @param {...*} var_args Additional arguments that are partially applied to fn.
+ * @return {!Function} A partially-applied form of the function bind() was
+ * invoked as a method of.
+ */
+goog.partial = function(fn, var_args) {
+ var args = Array.prototype.slice.call(arguments, 1);
+ return function() {
+ // Clone the array (with slice()) and append additional arguments
+ // to the existing arguments.
+ var newArgs = args.slice();
+ newArgs.push.apply(newArgs, arguments);
+ return fn.apply(this, newArgs);
+ };
+};
+
+
+/**
+ * Copies all the members of a source object to a target object. This method
+ * does not work on all browsers for all objects that contain keys such as
+ * toString or hasOwnProperty. Use goog.object.extend for this purpose.
+ * @param {Object} target Target.
+ * @param {Object} source Source.
+ */
+goog.mixin = function(target, source) {
+ for (var x in source) {
+ target[x] = source[x];
+ }
+
+ // For IE7 or lower, the for-in-loop does not contain any properties that are
+ // not enumerable on the prototype object (for example, isPrototypeOf from
+ // Object.prototype) but also it will not include 'replace' on objects that
+ // extend String and change 'replace' (not that it is common for anyone to
+ // extend anything except Object).
+};
+
+
+/**
+ * @return {number} An integer value representing the number of milliseconds
+ * between midnight, January 1, 1970 and the current time.
+ */
+goog.now = (goog.TRUSTED_SITE && Date.now) || (function() {
+ // Unary plus operator converts its operand to a number which in the case of
+ // a date is done by calling getTime().
+ return +new Date();
+});
+
+
+/**
+ * Evals JavaScript in the global scope. In IE this uses execScript, other
+ * browsers use goog.global.eval. If goog.global.eval does not evaluate in the
+ * global scope (for example, in Safari), appends a script tag instead.
+ * Throws an exception if neither execScript or eval is defined.
+ * @param {string} script JavaScript string.
+ */
+goog.globalEval = function(script) {
+ if (goog.global.execScript) {
+ goog.global.execScript(script, 'JavaScript');
+ } else if (goog.global.eval) {
+ // Test to see if eval works
+ if (goog.evalWorksForGlobals_ == null) {
+ goog.global.eval('var _et_ = 1;');
+ if (typeof goog.global['_et_'] != 'undefined') {
+ delete goog.global['_et_'];
+ goog.evalWorksForGlobals_ = true;
+ } else {
+ goog.evalWorksForGlobals_ = false;
+ }
+ }
+
+ if (goog.evalWorksForGlobals_) {
+ goog.global.eval(script);
+ } else {
+ var doc = goog.global.document;
+ var scriptElt = doc.createElement('script');
+ scriptElt.type = 'text/javascript';
+ scriptElt.defer = false;
+ // Note(user): can't use .innerHTML since "t('<test>')" will fail and
+ // .text doesn't work in Safari 2. Therefore we append a text node.
+ scriptElt.appendChild(doc.createTextNode(script));
+ doc.body.appendChild(scriptElt);
+ doc.body.removeChild(scriptElt);
+ }
+ } else {
+ throw Error('goog.globalEval not available');
+ }
+};
+
+
+/**
+ * Indicates whether or not we can call 'eval' directly to eval code in the
+ * global scope. Set to a Boolean by the first call to goog.globalEval (which
+ * empirically tests whether eval works for globals). @see goog.globalEval
+ * @type {?boolean}
+ * @private
+ */
+goog.evalWorksForGlobals_ = null;
+
+
+/**
+ * Optional map of CSS class names to obfuscated names used with
+ * goog.getCssName().
+ * @type {Object|undefined}
+ * @private
+ * @see goog.setCssNameMapping
+ */
+goog.cssNameMapping_;
+
+
+/**
+ * Optional obfuscation style for CSS class names. Should be set to either
+ * 'BY_WHOLE' or 'BY_PART' if defined.
+ * @type {string|undefined}
+ * @private
+ * @see goog.setCssNameMapping
+ */
+goog.cssNameMappingStyle_;
+
+
+/**
+ * Handles strings that are intended to be used as CSS class names.
+ *
+ * This function works in tandem with @see goog.setCssNameMapping.
+ *
+ * Without any mapping set, the arguments are simple joined with a hyphen and
+ * passed through unaltered.
+ *
+ * When there is a mapping, there are two possible styles in which these
+ * mappings are used. In the BY_PART style, each part (i.e. in between hyphens)
+ * of the passed in css name is rewritten according to the map. In the BY_WHOLE
+ * style, the full css name is looked up in the map directly. If a rewrite is
+ * not specified by the map, the compiler will output a warning.
+ *
+ * When the mapping is passed to the compiler, it will replace calls to
+ * goog.getCssName with the strings from the mapping, e.g.
+ * var x = goog.getCssName('foo');
+ * var y = goog.getCssName(this.baseClass, 'active');
+ * becomes:
+ * var x= 'foo';
+ * var y = this.baseClass + '-active';
+ *
+ * If one argument is passed it will be processed, if two are passed only the
+ * modifier will be processed, as it is assumed the first argument was generated
+ * as a result of calling goog.getCssName.
+ *
+ * @param {string} className The class name.
+ * @param {string=} opt_modifier A modifier to be appended to the class name.
+ * @return {string} The class name or the concatenation of the class name and
+ * the modifier.
+ */
+goog.getCssName = function(className, opt_modifier) {
+ var getMapping = function(cssName) {
+ return goog.cssNameMapping_[cssName] || cssName;
+ };
+
+ var renameByParts = function(cssName) {
+ // Remap all the parts individually.
+ var parts = cssName.split('-');
+ var mapped = [];
+ for (var i = 0; i < parts.length; i++) {
+ mapped.push(getMapping(parts[i]));
+ }
+ return mapped.join('-');
+ };
+
+ var rename;
+ if (goog.cssNameMapping_) {
+ rename = goog.cssNameMappingStyle_ == 'BY_WHOLE' ?
+ getMapping : renameByParts;
+ } else {
+ rename = function(a) {
+ return a;
+ };
+ }
+
+ if (opt_modifier) {
+ return className + '-' + rename(opt_modifier);
+ } else {
+ return rename(className);
+ }
+};
+
+
+/**
+ * Sets the map to check when returning a value from goog.getCssName(). Example:
+ * <pre>
+ * goog.setCssNameMapping({
+ * "goog": "a",
+ * "disabled": "b",
+ * });
+ *
+ * var x = goog.getCssName('goog');
+ * // The following evaluates to: "a a-b".
+ * goog.getCssName('goog') + ' ' + goog.getCssName(x, 'disabled')
+ * </pre>
+ * When declared as a map of string literals to string literals, the JSCompiler
+ * will replace all calls to goog.getCssName() using the supplied map if the
+ * --closure_pass flag is set.
+ *
+ * @param {!Object} mapping A map of strings to strings where keys are possible
+ * arguments to goog.getCssName() and values are the corresponding values
+ * that should be returned.
+ * @param {string=} opt_style The style of css name mapping. There are two valid
+ * options: 'BY_PART', and 'BY_WHOLE'.
+ * @see goog.getCssName for a description.
+ */
+goog.setCssNameMapping = function(mapping, opt_style) {
+ goog.cssNameMapping_ = mapping;
+ goog.cssNameMappingStyle_ = opt_style;
+};
+
+
+/**
+ * To use CSS renaming in compiled mode, one of the input files should have a
+ * call to goog.setCssNameMapping() with an object literal that the JSCompiler
+ * can extract and use to replace all calls to goog.getCssName(). In uncompiled
+ * mode, JavaScript code should be loaded before this base.js file that declares
+ * a global variable, CLOSURE_CSS_NAME_MAPPING, which is used below. This is
+ * to ensure that the mapping is loaded before any calls to goog.getCssName()
+ * are made in uncompiled mode.
+ *
+ * A hook for overriding the CSS name mapping.
+ * @type {Object|undefined}
+ */
+goog.global.CLOSURE_CSS_NAME_MAPPING;
+
+
+if (!COMPILED && goog.global.CLOSURE_CSS_NAME_MAPPING) {
+ // This does not call goog.setCssNameMapping() because the JSCompiler
+ // requires that goog.setCssNameMapping() be called with an object literal.
+ goog.cssNameMapping_ = goog.global.CLOSURE_CSS_NAME_MAPPING;
+}
+
+
+/**
+ * Gets a localized message.
+ *
+ * This function is a compiler primitive. If you give the compiler a localized
+ * message bundle, it will replace the string at compile-time with a localized
+ * version, and expand goog.getMsg call to a concatenated string.
+ *
+ * Messages must be initialized in the form:
+ * <code>
+ * var MSG_NAME = goog.getMsg('Hello {$placeholder}', {'placeholder': 'world'});
+ * </code>
+ *
+ * @param {string} str Translatable string, places holders in the form {$foo}.
+ * @param {Object=} opt_values Map of place holder name to value.
+ * @return {string} message with placeholders filled.
+ */
+goog.getMsg = function(str, opt_values) {
+ if (opt_values) {
+ str = str.replace(/\{\$([^}]+)}/g, function(match, key) {
+ return key in opt_values ? opt_values[key] : match;
+ });
+ }
+ return str;
+};
+
+
+/**
+ * Gets a localized message. If the message does not have a translation, gives a
+ * fallback message.
+ *
+ * This is useful when introducing a new message that has not yet been
+ * translated into all languages.
+ *
+ * This function is a compiler primitive. Must be used in the form:
+ * <code>var x = goog.getMsgWithFallback(MSG_A, MSG_B);</code>
+ * where MSG_A and MSG_B were initialized with goog.getMsg.
+ *
+ * @param {string} a The preferred message.
+ * @param {string} b The fallback message.
+ * @return {string} The best translated message.
+ */
+goog.getMsgWithFallback = function(a, b) {
+ return a;
+};
+
+
+/**
+ * Exposes an unobfuscated global namespace path for the given object.
+ * Note that fields of the exported object *will* be obfuscated, unless they are
+ * exported in turn via this function or goog.exportProperty.
+ *
+ * Also handy for making public items that are defined in anonymous closures.
+ *
+ * ex. goog.exportSymbol('public.path.Foo', Foo);
+ *
+ * ex. goog.exportSymbol('public.path.Foo.staticFunction', Foo.staticFunction);
+ * public.path.Foo.staticFunction();
+ *
+ * ex. goog.exportSymbol('public.path.Foo.prototype.myMethod',
+ * Foo.prototype.myMethod);
+ * new public.path.Foo().myMethod();
+ *
+ * @param {string} publicPath Unobfuscated name to export.
+ * @param {*} object Object the name should point to.
+ * @param {Object=} opt_objectToExportTo The object to add the path to; default
+ * is goog.global.
+ */
+goog.exportSymbol = function(publicPath, object, opt_objectToExportTo) {
+ goog.exportPath_(publicPath, object, opt_objectToExportTo);
+};
+
+
+/**
+ * Exports a property unobfuscated into the object's namespace.
+ * ex. goog.exportProperty(Foo, 'staticFunction', Foo.staticFunction);
+ * ex. goog.exportProperty(Foo.prototype, 'myMethod', Foo.prototype.myMethod);
+ * @param {Object} object Object whose static property is being exported.
+ * @param {string} publicName Unobfuscated name to export.
+ * @param {*} symbol Object the name should point to.
+ */
+goog.exportProperty = function(object, publicName, symbol) {
+ object[publicName] = symbol;
+};
+
+
+/**
+ * Inherit the prototype methods from one constructor into another.
+ *
+ * Usage:
+ * <pre>
+ * function ParentClass(a, b) { }
+ * ParentClass.prototype.foo = function(a) { };
+ *
+ * function ChildClass(a, b, c) {
+ * ChildClass.base(this, 'constructor', a, b);
+ * }
+ * goog.inherits(ChildClass, ParentClass);
+ *
+ * var child = new ChildClass('a', 'b', 'see');
+ * child.foo(); // This works.
+ * </pre>
+ *
+ * @param {Function} childCtor Child class.
+ * @param {Function} parentCtor Parent class.
+ */
+goog.inherits = function(childCtor, parentCtor) {
+ /** @constructor */
+ function tempCtor() {};
+ tempCtor.prototype = parentCtor.prototype;
+ childCtor.superClass_ = parentCtor.prototype;
+ childCtor.prototype = new tempCtor();
+ /** @override */
+ childCtor.prototype.constructor = childCtor;
+
+ /**
+ * Calls superclass constructor/method.
+ *
+ * This function is only available if you use goog.inherits to
+ * express inheritance relationships between classes.
+ *
+ * NOTE: This is a replacement for goog.base and for superClass_
+ * property defined in childCtor.
+ *
+ * @param {!Object} me Should always be "this".
+ * @param {string} methodName The method name to call. Calling
+ * superclass constructor can be done with the special string
+ * 'constructor'.
+ * @param {...*} var_args The arguments to pass to superclass
+ * method/constructor.
+ * @return {*} The return value of the superclass method/constructor.
+ */
+ childCtor.base = function(me, methodName, var_args) {
+ var args = Array.prototype.slice.call(arguments, 2);
+ return parentCtor.prototype[methodName].apply(me, args);
+ };
+};
+
+
+/**
+ * Call up to the superclass.
+ *
+ * If this is called from a constructor, then this calls the superclass
+ * constructor with arguments 1-N.
+ *
+ * If this is called from a prototype method, then you must pass the name of the
+ * method as the second argument to this function. If you do not, you will get a
+ * runtime error. This calls the superclass' method with arguments 2-N.
+ *
+ * This function only works if you use goog.inherits to express inheritance
+ * relationships between your classes.
+ *
+ * This function is a compiler primitive. At compile-time, the compiler will do
+ * macro expansion to remove a lot of the extra overhead that this function
+ * introduces. The compiler will also enforce a lot of the assumptions that this
+ * function makes, and treat it as a compiler error if you break them.
+ *
+ * @param {!Object} me Should always be "this".
+ * @param {*=} opt_methodName The method name if calling a super method.
+ * @param {...*} var_args The rest of the arguments.
+ * @return {*} The return value of the superclass method.
+ * @suppress {es5Strict} This method can not be used in strict mode, but
+ * all Closure Library consumers must depend on this file.
+ */
+goog.base = function(me, opt_methodName, var_args) {
+ var caller = arguments.callee.caller;
+
+ if (goog.STRICT_MODE_COMPATIBLE || (goog.DEBUG && !caller)) {
+ throw Error('arguments.caller not defined. goog.base() cannot be used ' +
+ 'with strict mode code. See ' +
+ 'http://www.ecma-international.org/ecma-262/5.1/#sec-C');
+ }
+
+ if (caller.superClass_) {
+ // This is a constructor. Call the superclass constructor.
+ return caller.superClass_.constructor.apply(
+ me, Array.prototype.slice.call(arguments, 1));
+ }
+
+ var args = Array.prototype.slice.call(arguments, 2);
+ var foundCaller = false;
+ for (var ctor = me.constructor;
+ ctor; ctor = ctor.superClass_ && ctor.superClass_.constructor) {
+ if (ctor.prototype[opt_methodName] === caller) {
+ foundCaller = true;
+ } else if (foundCaller) {
+ return ctor.prototype[opt_methodName].apply(me, args);
+ }
+ }
+
+ // If we did not find the caller in the prototype chain, then one of two
+ // things happened:
+ // 1) The caller is an instance method.
+ // 2) This method was not called by the right caller.
+ if (me[opt_methodName] === caller) {
+ return me.constructor.prototype[opt_methodName].apply(me, args);
+ } else {
+ throw Error(
+ 'goog.base called from a method of one name ' +
+ 'to a method of a different name');
+ }
+};
+
+
+/**
+ * Allow for aliasing within scope functions. This function exists for
+ * uncompiled code - in compiled code the calls will be inlined and the aliases
+ * applied. In uncompiled code the function is simply run since the aliases as
+ * written are valid JavaScript.
+ *
+ *
+ * @param {function()} fn Function to call. This function can contain aliases
+ * to namespaces (e.g. "var dom = goog.dom") or classes
+ * (e.g. "var Timer = goog.Timer").
+ */
+goog.scope = function(fn) {
+ fn.call(goog.global);
+};
+
+
+/*
+ * To support uncompiled, strict mode bundles that use eval to divide source
+ * like so:
+ * eval('someSource;//# sourceUrl sourcefile.js');
+ * We need to export the globally defined symbols "goog" and "COMPILED".
+ * Exporting "goog" breaks the compiler optimizations, so we required that
+ * be defined externally.
+ * NOTE: We don't use goog.exportSymbol here because we don't want to trigger
+ * extern generation when that compiler option is enabled.
+ */
+if (!COMPILED) {
+ goog.global['COMPILED'] = COMPILED;
+}
+
+
+
+//==============================================================================
+// goog.defineClass implementation
+//==============================================================================
+
+/**
+ * Creates a restricted form of a Closure "class":
+ * - from the compiler's perspective, the instance returned from the
+ * constructor is sealed (no new properties may be added). This enables
+ * better checks.
+ * - the compiler will rewrite this definition to a form that is optimal
+ * for type checking and optimization (initially this will be a more
+ * traditional form).
+ *
+ * @param {Function} superClass The superclass, Object or null.
+ * @param {goog.defineClass.ClassDescriptor} def
+ * An object literal describing the
+ * the class. It may have the following properties:
+ * "constructor": the constructor function
+ * "statics": an object literal containing methods to add to the constructor
+ * as "static" methods or a function that will receive the constructor
+ * function as its only parameter to which static properties can
+ * be added.
+ * all other properties are added to the prototype.
+ * @return {!Function} The class constructor.
+ */
+goog.defineClass = function(superClass, def) {
+ // TODO(johnlenz): consider making the superClass an optional parameter.
+ var constructor = def.constructor;
+ var statics = def.statics;
+ // Wrap the constructor prior to setting up the prototype and static methods.
+ if (!constructor || constructor == Object.prototype.constructor) {
+ constructor = function() {
+ throw Error('cannot instantiate an interface (no constructor defined).');
+ };
+ }
+
+ var cls = goog.defineClass.createSealingConstructor_(constructor, superClass);
+ if (superClass) {
+ goog.inherits(cls, superClass);
+ }
+
+ // Remove all the properties that should not be copied to the prototype.
+ delete def.constructor;
+ delete def.statics;
+
+ goog.defineClass.applyProperties_(cls.prototype, def);
+ if (statics != null) {
+ if (statics instanceof Function) {
+ statics(cls);
+ } else {
+ goog.defineClass.applyProperties_(cls, statics);
+ }
+ }
+
+ return cls;
+};
+
+
+/**
+ * @typedef {
+ * !Object|
+ * {constructor:!Function}|
+ * {constructor:!Function, statics:(Object|function(Function):void)}}
+ */
+goog.defineClass.ClassDescriptor;
+
+
+/**
+ * @define {boolean} Whether the instances returned by
+ * goog.defineClass should be sealed when possible.
+ */
+goog.define('goog.defineClass.SEAL_CLASS_INSTANCES', goog.DEBUG);
+
+
+/**
+ * If goog.defineClass.SEAL_CLASS_INSTANCES is enabled and Object.seal is
+ * defined, this function will wrap the constructor in a function that seals the
+ * results of the provided constructor function.
+ *
+ * @param {!Function} ctr The constructor whose results maybe be sealed.
+ * @param {Function} superClass The superclass constructor.
+ * @return {!Function} The replacement constructor.
+ * @private
+ */
+goog.defineClass.createSealingConstructor_ = function(ctr, superClass) {
+ if (goog.defineClass.SEAL_CLASS_INSTANCES &&
+ Object.seal instanceof Function) {
+ // Don't seal subclasses of unsealable-tagged legacy classes.
+ if (superClass && superClass.prototype &&
+ superClass.prototype[goog.UNSEALABLE_CONSTRUCTOR_PROPERTY_]) {
+ return ctr;
+ }
+ /** @this {*} */
+ var wrappedCtr = function() {
+ // Don't seal an instance of a subclass when it calls the constructor of
+ // its super class as there is most likely still setup to do.
+ var instance = ctr.apply(this, arguments) || this;
+ if (this.constructor === wrappedCtr) {
+ Object.seal(instance);
+ }
+ return instance;
+ };
+ return wrappedCtr;
+ }
+ return ctr;
+};
+
+
+// TODO(johnlenz): share these values with the goog.object
+/**
+ * The names of the fields that are defined on Object.prototype.
+ * @type {!Array.<string>}
+ * @private
+ * @const
+ */
+goog.defineClass.OBJECT_PROTOTYPE_FIELDS_ = [
+ 'constructor',
+ 'hasOwnProperty',
+ 'isPrototypeOf',
+ 'propertyIsEnumerable',
+ 'toLocaleString',
+ 'toString',
+ 'valueOf'
+];
+
+
+// TODO(johnlenz): share this function with the goog.object
+/**
+ * @param {!Object} target The object to add properties to.
+ * @param {!Object} source The object to copy properites from.
+ * @private
+ */
+goog.defineClass.applyProperties_ = function(target, source) {
+ // TODO(johnlenz): update this to support ES5 getters/setters
+
+ var key;
+ for (key in source) {
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
+ target[key] = source[key];
+ }
+ }
+
+ // For IE the for-in-loop does not contain any properties that are not
+ // enumerable on the prototype object (for example isPrototypeOf from
+ // Object.prototype) and it will also not include 'replace' on objects that
+ // extend String and change 'replace' (not that it is common for anyone to
+ // extend anything except Object).
+ for (var i = 0; i < goog.defineClass.OBJECT_PROTOTYPE_FIELDS_.length; i++) {
+ key = goog.defineClass.OBJECT_PROTOTYPE_FIELDS_[i];
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
+ target[key] = source[key];
+ }
+ }
+};
+
+
+/**
+ * Sealing classes breaks the older idiom of assigning properties on the
+ * prototype rather than in the constructor. As such, goog.defineClass
+ * must not seal subclasses of these old-style classes until they are fixed.
+ * Until then, this marks a class as "broken", instructing defineClass
+ * not to seal subclasses.
+ * @param {!Function} ctr The legacy constructor to tag as unsealable.
+ */
+goog.tagUnsealableClass = function(ctr) {
+ if (!COMPILED && goog.defineClass.SEAL_CLASS_INSTANCES) {
+ ctr.prototype[goog.UNSEALABLE_CONSTRUCTOR_PROPERTY_] = true;
+ }
+};
+
+
+/**
+ * Name for unsealable tag property.
+ * @const @private {string}
+ */
+goog.UNSEALABLE_CONSTRUCTOR_PROPERTY_ = 'goog_defineClass_legacy_unsealable';
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/debug/debug.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/debug/debug.js
new file mode 100644
index 0000000..e6e28e1
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/debug/debug.js
@@ -0,0 +1,590 @@
+// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Logging and debugging utilities.
+ *
+ * @see ../demos/debug.html
+ */
+
+goog.provide('goog.debug');
+
+goog.require('goog.array');
+goog.require('goog.string');
+goog.require('goog.structs.Set');
+goog.require('goog.userAgent');
+
+
+/** @define {boolean} Whether logging should be enabled. */
+goog.define('goog.debug.LOGGING_ENABLED', goog.DEBUG);
+
+
+/**
+ * Catches onerror events fired by windows and similar objects.
+ * @param {function(Object)} logFunc The function to call with the error
+ * information.
+ * @param {boolean=} opt_cancel Whether to stop the error from reaching the
+ * browser.
+ * @param {Object=} opt_target Object that fires onerror events.
+ */
+goog.debug.catchErrors = function(logFunc, opt_cancel, opt_target) {
+ var target = opt_target || goog.global;
+ var oldErrorHandler = target.onerror;
+ var retVal = !!opt_cancel;
+
+ // Chrome interprets onerror return value backwards (http://crbug.com/92062)
+ // until it was fixed in webkit revision r94061 (Webkit 535.3). This
+ // workaround still needs to be skipped in Safari after the webkit change
+ // gets pushed out in Safari.
+ // See https://bugs.webkit.org/show_bug.cgi?id=67119
+ if (goog.userAgent.WEBKIT &&
+ !goog.userAgent.isVersionOrHigher('535.3')) {
+ retVal = !retVal;
+ }
+
+ /**
+ * New onerror handler for this target. This onerror handler follows the spec
+ * according to
+ * http://www.whatwg.org/specs/web-apps/current-work/#runtime-script-errors
+ * The spec was changed in August 2013 to support receiving column information
+ * and an error object for all scripts on the same origin or cross origin
+ * scripts with the proper headers. See
+ * https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror
+ *
+ * @param {string} message The error message. For cross-origin errors, this
+ * will be scrubbed to just "Script error.". For new browsers that have
+ * updated to follow the latest spec, errors that come from origins that
+ * have proper cross origin headers will not be scrubbed.
+ * @param {string} url The URL of the script that caused the error. The URL
+ * will be scrubbed to "" for cross origin scripts unless the script has
+ * proper cross origin headers and the browser has updated to the latest
+ * spec.
+ * @param {number} line The line number in the script that the error
+ * occurred on.
+ * @param {number=} opt_col The optional column number that the error
+ * occurred on. Only browsers that have updated to the latest spec will
+ * include this.
+ * @param {Error=} opt_error The optional actual error object for this
+ * error that should include the stack. Only browsers that have updated
+ * to the latest spec will inlude this parameter.
+ * @return {boolean} Whether to prevent the error from reaching the browser.
+ */
+ target.onerror = function(message, url, line, opt_col, opt_error) {
+ if (oldErrorHandler) {
+ oldErrorHandler(message, url, line, opt_col, opt_error);
+ }
+ logFunc({
+ message: message,
+ fileName: url,
+ line: line,
+ col: opt_col,
+ error: opt_error
+ });
+ return retVal;
+ };
+};
+
+
+/**
+ * Creates a string representing an object and all its properties.
+ * @param {Object|null|undefined} obj Object to expose.
+ * @param {boolean=} opt_showFn Show the functions as well as the properties,
+ * default is false.
+ * @return {string} The string representation of {@code obj}.
+ */
+goog.debug.expose = function(obj, opt_showFn) {
+ if (typeof obj == 'undefined') {
+ return 'undefined';
+ }
+ if (obj == null) {
+ return 'NULL';
+ }
+ var str = [];
+
+ for (var x in obj) {
+ if (!opt_showFn && goog.isFunction(obj[x])) {
+ continue;
+ }
+ var s = x + ' = ';
+ /** @preserveTry */
+ try {
+ s += obj[x];
+ } catch (e) {
+ s += '*** ' + e + ' ***';
+ }
+ str.push(s);
+ }
+ return str.join('\n');
+};
+
+
+/**
+ * Creates a string representing a given primitive or object, and for an
+ * object, all its properties and nested objects. WARNING: If an object is
+ * given, it and all its nested objects will be modified. To detect reference
+ * cycles, this method identifies objects using goog.getUid() which mutates the
+ * object.
+ * @param {*} obj Object to expose.
+ * @param {boolean=} opt_showFn Also show properties that are functions (by
+ * default, functions are omitted).
+ * @return {string} A string representation of {@code obj}.
+ */
+goog.debug.deepExpose = function(obj, opt_showFn) {
+ var str = [];
+
+ var helper = function(obj, space, parentSeen) {
+ var nestspace = space + ' ';
+ var seen = new goog.structs.Set(parentSeen);
+
+ var indentMultiline = function(str) {
+ return str.replace(/\n/g, '\n' + space);
+ };
+
+ /** @preserveTry */
+ try {
+ if (!goog.isDef(obj)) {
+ str.push('undefined');
+ } else if (goog.isNull(obj)) {
+ str.push('NULL');
+ } else if (goog.isString(obj)) {
+ str.push('"' + indentMultiline(obj) + '"');
+ } else if (goog.isFunction(obj)) {
+ str.push(indentMultiline(String(obj)));
+ } else if (goog.isObject(obj)) {
+ if (seen.contains(obj)) {
+ str.push('*** reference loop detected ***');
+ } else {
+ seen.add(obj);
+ str.push('{');
+ for (var x in obj) {
+ if (!opt_showFn && goog.isFunction(obj[x])) {
+ continue;
+ }
+ str.push('\n');
+ str.push(nestspace);
+ str.push(x + ' = ');
+ helper(obj[x], nestspace, seen);
+ }
+ str.push('\n' + space + '}');
+ }
+ } else {
+ str.push(obj);
+ }
+ } catch (e) {
+ str.push('*** ' + e + ' ***');
+ }
+ };
+
+ helper(obj, '', new goog.structs.Set());
+ return str.join('');
+};
+
+
+/**
+ * Recursively outputs a nested array as a string.
+ * @param {Array} arr The array.
+ * @return {string} String representing nested array.
+ */
+goog.debug.exposeArray = function(arr) {
+ var str = [];
+ for (var i = 0; i < arr.length; i++) {
+ if (goog.isArray(arr[i])) {
+ str.push(goog.debug.exposeArray(arr[i]));
+ } else {
+ str.push(arr[i]);
+ }
+ }
+ return '[ ' + str.join(', ') + ' ]';
+};
+
+
+/**
+ * Exposes an exception that has been caught by a try...catch and outputs the
+ * error with a stack trace.
+ * @param {Object} err Error object or string.
+ * @param {Function=} opt_fn Optional function to start stack trace from.
+ * @return {string} Details of exception.
+ */
+goog.debug.exposeException = function(err, opt_fn) {
+ /** @preserveTry */
+ try {
+ var e = goog.debug.normalizeErrorObject(err);
+
+ // Create the error message
+ var error = 'Message: ' + goog.string.htmlEscape(e.message) +
+ '\nUrl: <a href="view-source:' + e.fileName + '" target="_new">' +
+ e.fileName + '</a>\nLine: ' + e.lineNumber + '\n\nBrowser stack:\n' +
+ goog.string.htmlEscape(e.stack + '-> ') +
+ '[end]\n\nJS stack traversal:\n' + goog.string.htmlEscape(
+ goog.debug.getStacktrace(opt_fn) + '-> ');
+ return error;
+ } catch (e2) {
+ return 'Exception trying to expose exception! You win, we lose. ' + e2;
+ }
+};
+
+
+/**
+ * Normalizes the error/exception object between browsers.
+ * @param {Object} err Raw error object.
+ * @return {!Object} Normalized error object.
+ */
+goog.debug.normalizeErrorObject = function(err) {
+ var href = goog.getObjectByName('window.location.href');
+ if (goog.isString(err)) {
+ return {
+ 'message': err,
+ 'name': 'Unknown error',
+ 'lineNumber': 'Not available',
+ 'fileName': href,
+ 'stack': 'Not available'
+ };
+ }
+
+ var lineNumber, fileName;
+ var threwError = false;
+
+ try {
+ lineNumber = err.lineNumber || err.line || 'Not available';
+ } catch (e) {
+ // Firefox 2 sometimes throws an error when accessing 'lineNumber':
+ // Message: Permission denied to get property UnnamedClass.lineNumber
+ lineNumber = 'Not available';
+ threwError = true;
+ }
+
+ try {
+ fileName = err.fileName || err.filename || err.sourceURL ||
+ // $googDebugFname may be set before a call to eval to set the filename
+ // that the eval is supposed to present.
+ goog.global['$googDebugFname'] || href;
+ } catch (e) {
+ // Firefox 2 may also throw an error when accessing 'filename'.
+ fileName = 'Not available';
+ threwError = true;
+ }
+
+ // The IE Error object contains only the name and the message.
+ // The Safari Error object uses the line and sourceURL fields.
+ if (threwError || !err.lineNumber || !err.fileName || !err.stack ||
+ !err.message || !err.name) {
+ return {
+ 'message': err.message || 'Not available',
+ 'name': err.name || 'UnknownError',
+ 'lineNumber': lineNumber,
+ 'fileName': fileName,
+ 'stack': err.stack || 'Not available'
+ };
+ }
+
+ // Standards error object
+ return err;
+};
+
+
+/**
+ * Converts an object to an Error if it's a String,
+ * adds a stacktrace if there isn't one,
+ * and optionally adds an extra message.
+ * @param {Error|string} err the original thrown object or string.
+ * @param {string=} opt_message optional additional message to add to the
+ * error.
+ * @return {!Error} If err is a string, it is used to create a new Error,
+ * which is enhanced and returned. Otherwise err itself is enhanced
+ * and returned.
+ */
+goog.debug.enhanceError = function(err, opt_message) {
+ var error;
+ if (typeof err == 'string') {
+ error = Error(err);
+ if (Error.captureStackTrace) {
+ // Trim this function off the call stack, if we can.
+ Error.captureStackTrace(error, goog.debug.enhanceError);
+ }
+ } else {
+ error = err;
+ }
+
+ if (!error.stack) {
+ error.stack = goog.debug.getStacktrace(goog.debug.enhanceError);
+ }
+ if (opt_message) {
+ // find the first unoccupied 'messageX' property
+ var x = 0;
+ while (error['message' + x]) {
+ ++x;
+ }
+ error['message' + x] = String(opt_message);
+ }
+ return error;
+};
+
+
+/**
+ * Gets the current stack trace. Simple and iterative - doesn't worry about
+ * catching circular references or getting the args.
+ * @param {number=} opt_depth Optional maximum depth to trace back to.
+ * @return {string} A string with the function names of all functions in the
+ * stack, separated by \n.
+ * @suppress {es5Strict}
+ */
+goog.debug.getStacktraceSimple = function(opt_depth) {
+ if (goog.STRICT_MODE_COMPATIBLE) {
+ var stack = goog.debug.getNativeStackTrace_(goog.debug.getStacktraceSimple);
+ if (stack) {
+ return stack;
+ }
+ // NOTE: browsers that have strict mode support also have native "stack"
+ // properties. Fall-through for legacy browser support.
+ }
+
+ var sb = [];
+ var fn = arguments.callee.caller;
+ var depth = 0;
+
+ while (fn && (!opt_depth || depth < opt_depth)) {
+ sb.push(goog.debug.getFunctionName(fn));
+ sb.push('()\n');
+ /** @preserveTry */
+ try {
+ fn = fn.caller;
+ } catch (e) {
+ sb.push('[exception trying to get caller]\n');
+ break;
+ }
+ depth++;
+ if (depth >= goog.debug.MAX_STACK_DEPTH) {
+ sb.push('[...long stack...]');
+ break;
+ }
+ }
+ if (opt_depth && depth >= opt_depth) {
+ sb.push('[...reached max depth limit...]');
+ } else {
+ sb.push('[end]');
+ }
+
+ return sb.join('');
+};
+
+
+/**
+ * Max length of stack to try and output
+ * @type {number}
+ */
+goog.debug.MAX_STACK_DEPTH = 50;
+
+
+/**
+ * @param {Function} fn The function to start getting the trace from.
+ * @return {?string}
+ * @private
+ */
+goog.debug.getNativeStackTrace_ = function(fn) {
+ var tempErr = new Error();
+ if (Error.captureStackTrace) {
+ Error.captureStackTrace(tempErr, fn);
+ return String(tempErr.stack);
+ } else {
+ // IE10, only adds stack traces when an exception is thrown.
+ try {
+ throw tempErr;
+ } catch (e) {
+ tempErr = e;
+ }
+ var stack = tempErr.stack;
+ if (stack) {
+ return String(stack);
+ }
+ }
+ return null;
+};
+
+
+/**
+ * Gets the current stack trace, either starting from the caller or starting
+ * from a specified function that's currently on the call stack.
+ * @param {Function=} opt_fn Optional function to start getting the trace from.
+ * If not provided, defaults to the function that called this.
+ * @return {string} Stack trace.
+ * @suppress {es5Strict}
+ */
+goog.debug.getStacktrace = function(opt_fn) {
+ var stack;
+ if (goog.STRICT_MODE_COMPATIBLE) {
+ // Try to get the stack trace from the environment if it is available.
+ var contextFn = opt_fn || goog.debug.getStacktrace;
+ stack = goog.debug.getNativeStackTrace_(contextFn);
+ }
+ if (!stack) {
+ // NOTE: browsers that have strict mode support also have native "stack"
+ // properties. This function will throw in strict mode.
+ stack = goog.debug.getStacktraceHelper_(
+ opt_fn || arguments.callee.caller, []);
+ }
+ return stack;
+};
+
+
+/**
+ * Private helper for getStacktrace().
+ * @param {Function} fn Function to start getting the trace from.
+ * @param {Array} visited List of functions visited so far.
+ * @return {string} Stack trace starting from function fn.
+ * @suppress {es5Strict}
+ * @private
+ */
+goog.debug.getStacktraceHelper_ = function(fn, visited) {
+ var sb = [];
+
+ // Circular reference, certain functions like bind seem to cause a recursive
+ // loop so we need to catch circular references
+ if (goog.array.contains(visited, fn)) {
+ sb.push('[...circular reference...]');
+
+ // Traverse the call stack until function not found or max depth is reached
+ } else if (fn && visited.length < goog.debug.MAX_STACK_DEPTH) {
+ sb.push(goog.debug.getFunctionName(fn) + '(');
+ var args = fn.arguments;
+ // Args may be null for some special functions such as host objects or eval.
+ for (var i = 0; args && i < args.length; i++) {
+ if (i > 0) {
+ sb.push(', ');
+ }
+ var argDesc;
+ var arg = args[i];
+ switch (typeof arg) {
+ case 'object':
+ argDesc = arg ? 'object' : 'null';
+ break;
+
+ case 'string':
+ argDesc = arg;
+ break;
+
+ case 'number':
+ argDesc = String(arg);
+ break;
+
+ case 'boolean':
+ argDesc = arg ? 'true' : 'false';
+ break;
+
+ case 'function':
+ argDesc = goog.debug.getFunctionName(arg);
+ argDesc = argDesc ? argDesc : '[fn]';
+ break;
+
+ case 'undefined':
+ default:
+ argDesc = typeof arg;
+ break;
+ }
+
+ if (argDesc.length > 40) {
+ argDesc = argDesc.substr(0, 40) + '...';
+ }
+ sb.push(argDesc);
+ }
+ visited.push(fn);
+ sb.push(')\n');
+ /** @preserveTry */
+ try {
+ sb.push(goog.debug.getStacktraceHelper_(fn.caller, visited));
+ } catch (e) {
+ sb.push('[exception trying to get caller]\n');
+ }
+
+ } else if (fn) {
+ sb.push('[...long stack...]');
+ } else {
+ sb.push('[end]');
+ }
+ return sb.join('');
+};
+
+
+/**
+ * Set a custom function name resolver.
+ * @param {function(Function): string} resolver Resolves functions to their
+ * names.
+ */
+goog.debug.setFunctionResolver = function(resolver) {
+ goog.debug.fnNameResolver_ = resolver;
+};
+
+
+/**
+ * Gets a function name
+ * @param {Function} fn Function to get name of.
+ * @return {string} Function's name.
+ */
+goog.debug.getFunctionName = function(fn) {
+ if (goog.debug.fnNameCache_[fn]) {
+ return goog.debug.fnNameCache_[fn];
+ }
+ if (goog.debug.fnNameResolver_) {
+ var name = goog.debug.fnNameResolver_(fn);
+ if (name) {
+ goog.debug.fnNameCache_[fn] = name;
+ return name;
+ }
+ }
+
+ // Heuristically determine function name based on code.
+ var functionSource = String(fn);
+ if (!goog.debug.fnNameCache_[functionSource]) {
+ var matches = /function ([^\(]+)/.exec(functionSource);
+ if (matches) {
+ var method = matches[1];
+ goog.debug.fnNameCache_[functionSource] = method;
+ } else {
+ goog.debug.fnNameCache_[functionSource] = '[Anonymous]';
+ }
+ }
+
+ return goog.debug.fnNameCache_[functionSource];
+};
+
+
+/**
+ * Makes whitespace visible by replacing it with printable characters.
+ * This is useful in finding diffrences between the expected and the actual
+ * output strings of a testcase.
+ * @param {string} string whose whitespace needs to be made visible.
+ * @return {string} string whose whitespace is made visible.
+ */
+goog.debug.makeWhitespaceVisible = function(string) {
+ return string.replace(/ /g, '[_]')
+ .replace(/\f/g, '[f]')
+ .replace(/\n/g, '[n]\n')
+ .replace(/\r/g, '[r]')
+ .replace(/\t/g, '[t]');
+};
+
+
+/**
+ * Hash map for storing function names that have already been looked up.
+ * @type {Object}
+ * @private
+ */
+goog.debug.fnNameCache_ = {};
+
+
+/**
+ * Resolves functions to their names. Resolved function names will be cached.
+ * @type {function(Function):string}
+ * @private
+ */
+goog.debug.fnNameResolver_;
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/debug/entrypointregistry.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/debug/entrypointregistry.js
new file mode 100644
index 0000000..9394f7f
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/debug/entrypointregistry.js
@@ -0,0 +1,158 @@
+// Copyright 2010 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview A global registry for entry points into a program,
+ * so that they can be instrumented. Each module should register their
+ * entry points with this registry. Designed to be compiled out
+ * if no instrumentation is requested.
+ *
+ * Entry points may be registered before or after a call to
+ * goog.debug.entryPointRegistry.monitorAll. If an entry point is registered
+ * later, the existing monitor will instrument the new entry point.
+ *
+ * @author nicksantos@google.com (Nick Santos)
+ */
+
+goog.provide('goog.debug.EntryPointMonitor');
+goog.provide('goog.debug.entryPointRegistry');
+
+goog.require('goog.asserts');
+
+
+
+/**
+ * @interface
+ */
+goog.debug.EntryPointMonitor = function() {};
+
+
+/**
+ * Instruments a function.
+ *
+ * @param {!Function} fn A function to instrument.
+ * @return {!Function} The instrumented function.
+ */
+goog.debug.EntryPointMonitor.prototype.wrap;
+
+
+/**
+ * Try to remove an instrumentation wrapper created by this monitor.
+ * If the function passed to unwrap is not a wrapper created by this
+ * monitor, then we will do nothing.
+ *
+ * Notice that some wrappers may not be unwrappable. For example, if other
+ * monitors have applied their own wrappers, then it will be impossible to
+ * unwrap them because their wrappers will have captured our wrapper.
+ *
+ * So it is important that entry points are unwrapped in the reverse
+ * order that they were wrapped.
+ *
+ * @param {!Function} fn A function to unwrap.
+ * @return {!Function} The unwrapped function, or {@code fn} if it was not
+ * a wrapped function created by this monitor.
+ */
+goog.debug.EntryPointMonitor.prototype.unwrap;
+
+
+/**
+ * An array of entry point callbacks.
+ * @type {!Array.<function(!Function)>}
+ * @private
+ */
+goog.debug.entryPointRegistry.refList_ = [];
+
+
+/**
+ * Monitors that should wrap all the entry points.
+ * @type {!Array.<!goog.debug.EntryPointMonitor>}
+ * @private
+ */
+goog.debug.entryPointRegistry.monitors_ = [];
+
+
+/**
+ * Whether goog.debug.entryPointRegistry.monitorAll has ever been called.
+ * Checking this allows the compiler to optimize out the registrations.
+ * @type {boolean}
+ * @private
+ */
+goog.debug.entryPointRegistry.monitorsMayExist_ = false;
+
+
+/**
+ * Register an entry point with this module.
+ *
+ * The entry point will be instrumented when a monitor is passed to
+ * goog.debug.entryPointRegistry.monitorAll. If this has already occurred, the
+ * entry point is instrumented immediately.
+ *
+ * @param {function(!Function)} callback A callback function which is called
+ * with a transforming function to instrument the entry point. The callback
+ * is responsible for wrapping the relevant entry point with the
+ * transforming function.
+ */
+goog.debug.entryPointRegistry.register = function(callback) {
+ // Don't use push(), so that this can be compiled out.
+ goog.debug.entryPointRegistry.refList_[
+ goog.debug.entryPointRegistry.refList_.length] = callback;
+ // If no one calls monitorAll, this can be compiled out.
+ if (goog.debug.entryPointRegistry.monitorsMayExist_) {
+ var monitors = goog.debug.entryPointRegistry.monitors_;
+ for (var i = 0; i < monitors.length; i++) {
+ callback(goog.bind(monitors[i].wrap, monitors[i]));
+ }
+ }
+};
+
+
+/**
+ * Configures a monitor to wrap all entry points.
+ *
+ * Entry points that have already been registered are immediately wrapped by
+ * the monitor. When an entry point is registered in the future, it will also
+ * be wrapped by the monitor when it is registered.
+ *
+ * @param {!goog.debug.EntryPointMonitor} monitor An entry point monitor.
+ */
+goog.debug.entryPointRegistry.monitorAll = function(monitor) {
+ goog.debug.entryPointRegistry.monitorsMayExist_ = true;
+ var transformer = goog.bind(monitor.wrap, monitor);
+ for (var i = 0; i < goog.debug.entryPointRegistry.refList_.length; i++) {
+ goog.debug.entryPointRegistry.refList_[i](transformer);
+ }
+ goog.debug.entryPointRegistry.monitors_.push(monitor);
+};
+
+
+/**
+ * Try to unmonitor all the entry points that have already been registered. If
+ * an entry point is registered in the future, it will not be wrapped by the
+ * monitor when it is registered. Note that this may fail if the entry points
+ * have additional wrapping.
+ *
+ * @param {!goog.debug.EntryPointMonitor} monitor The last monitor to wrap
+ * the entry points.
+ * @throws {Error} If the monitor is not the most recently configured monitor.
+ */
+goog.debug.entryPointRegistry.unmonitorAllIfPossible = function(monitor) {
+ var monitors = goog.debug.entryPointRegistry.monitors_;
+ goog.asserts.assert(monitor == monitors[monitors.length - 1],
+ 'Only the most recent monitor can be unwrapped.');
+ var transformer = goog.bind(monitor.unwrap, monitor);
+ for (var i = 0; i < goog.debug.entryPointRegistry.refList_.length; i++) {
+ goog.debug.entryPointRegistry.refList_[i](transformer);
+ }
+ monitors.length--;
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/debug/error.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/debug/error.js
new file mode 100644
index 0000000..6fed470
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/debug/error.js
@@ -0,0 +1,54 @@
+// Copyright 2009 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Provides a base class for custom Error objects such that the
+ * stack is correctly maintained.
+ *
+ * You should never need to throw goog.debug.Error(msg) directly, Error(msg) is
+ * sufficient.
+ *
+ */
+
+goog.provide('goog.debug.Error');
+
+
+
+/**
+ * Base class for custom error objects.
+ * @param {*=} opt_msg The message associated with the error.
+ * @constructor
+ * @extends {Error}
+ */
+goog.debug.Error = function(opt_msg) {
+
+ // Attempt to ensure there is a stack trace.
+ if (Error.captureStackTrace) {
+ Error.captureStackTrace(this, goog.debug.Error);
+ } else {
+ var stack = new Error().stack;
+ if (stack) {
+ this.stack = stack;
+ }
+ }
+
+ if (opt_msg) {
+ this.message = String(opt_msg);
+ }
+};
+goog.inherits(goog.debug.Error, Error);
+
+
+/** @override */
+goog.debug.Error.prototype.name = 'CustomError';
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/debug/errorhandlerweakdep.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/debug/errorhandlerweakdep.js
new file mode 100644
index 0000000..05a2b16
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/debug/errorhandlerweakdep.js
@@ -0,0 +1,38 @@
+// Copyright 2008 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview File which defines dummy object to work around undefined
+ * properties compiler warning for weak dependencies on
+ * {@link goog.debug.ErrorHandler#protectEntryPoint}.
+ *
+ */
+
+goog.provide('goog.debug.errorHandlerWeakDep');
+
+
+/**
+ * Dummy object to work around undefined properties compiler warning.
+ * @type {Object}
+ */
+goog.debug.errorHandlerWeakDep = {
+ /**
+ * @param {Function} fn An entry point function to be protected.
+ * @param {boolean=} opt_tracers Whether to install tracers around the
+ * fn.
+ * @return {Function} A protected wrapper function that calls the
+ * entry point function.
+ */
+ protectEntryPoint: function(fn, opt_tracers) { return fn; }
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/debug/logbuffer.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/debug/logbuffer.js
new file mode 100644
index 0000000..d2ded20
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/debug/logbuffer.js
@@ -0,0 +1,148 @@
+// Copyright 2010 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview A buffer for log records. The purpose of this is to improve
+ * logging performance by re-using old objects when the buffer becomes full and
+ * to eliminate the need for each app to implement their own log buffer. The
+ * disadvantage to doing this is that log handlers cannot maintain references to
+ * log records and expect that they are not overwriten at a later point.
+ *
+ * @author agrieve@google.com (Andrew Grieve)
+ */
+
+goog.provide('goog.debug.LogBuffer');
+
+goog.require('goog.asserts');
+goog.require('goog.debug.LogRecord');
+
+
+
+/**
+ * Creates the log buffer.
+ * @constructor
+ * @final
+ */
+goog.debug.LogBuffer = function() {
+ goog.asserts.assert(goog.debug.LogBuffer.isBufferingEnabled(),
+ 'Cannot use goog.debug.LogBuffer without defining ' +
+ 'goog.debug.LogBuffer.CAPACITY.');
+ this.clear();
+};
+
+
+/**
+ * A static method that always returns the same instance of LogBuffer.
+ * @return {!goog.debug.LogBuffer} The LogBuffer singleton instance.
+ */
+goog.debug.LogBuffer.getInstance = function() {
+ if (!goog.debug.LogBuffer.instance_) {
+ // This function is written with the return statement after the assignment
+ // to avoid the jscompiler StripCode bug described in http://b/2608064.
+ // After that bug is fixed this can be refactored.
+ goog.debug.LogBuffer.instance_ = new goog.debug.LogBuffer();
+ }
+ return goog.debug.LogBuffer.instance_;
+};
+
+
+/**
+ * @define {number} The number of log records to buffer. 0 means disable
+ * buffering.
+ */
+goog.define('goog.debug.LogBuffer.CAPACITY', 0);
+
+
+/**
+ * The array to store the records.
+ * @type {!Array.<!goog.debug.LogRecord|undefined>}
+ * @private
+ */
+goog.debug.LogBuffer.prototype.buffer_;
+
+
+/**
+ * The index of the most recently added record or -1 if there are no records.
+ * @type {number}
+ * @private
+ */
+goog.debug.LogBuffer.prototype.curIndex_;
+
+
+/**
+ * Whether the buffer is at capacity.
+ * @type {boolean}
+ * @private
+ */
+goog.debug.LogBuffer.prototype.isFull_;
+
+
+/**
+ * Adds a log record to the buffer, possibly overwriting the oldest record.
+ * @param {goog.debug.Logger.Level} level One of the level identifiers.
+ * @param {string} msg The string message.
+ * @param {string} loggerName The name of the source logger.
+ * @return {!goog.debug.LogRecord} The log record.
+ */
+goog.debug.LogBuffer.prototype.addRecord = function(level, msg, loggerName) {
+ var curIndex = (this.curIndex_ + 1) % goog.debug.LogBuffer.CAPACITY;
+ this.curIndex_ = curIndex;
+ if (this.isFull_) {
+ var ret = this.buffer_[curIndex];
+ ret.reset(level, msg, loggerName);
+ return ret;
+ }
+ this.isFull_ = curIndex == goog.debug.LogBuffer.CAPACITY - 1;
+ return this.buffer_[curIndex] =
+ new goog.debug.LogRecord(level, msg, loggerName);
+};
+
+
+/**
+ * @return {boolean} Whether the log buffer is enabled.
+ */
+goog.debug.LogBuffer.isBufferingEnabled = function() {
+ return goog.debug.LogBuffer.CAPACITY > 0;
+};
+
+
+/**
+ * Removes all buffered log records.
+ */
+goog.debug.LogBuffer.prototype.clear = function() {
+ this.buffer_ = new Array(goog.debug.LogBuffer.CAPACITY);
+ this.curIndex_ = -1;
+ this.isFull_ = false;
+};
+
+
+/**
+ * Calls the given function for each buffered log record, starting with the
+ * oldest one.
+ * @param {function(!goog.debug.LogRecord)} func The function to call.
+ */
+goog.debug.LogBuffer.prototype.forEachRecord = function(func) {
+ var buffer = this.buffer_;
+ // Corner case: no records.
+ if (!buffer[0]) {
+ return;
+ }
+ var curIndex = this.curIndex_;
+ var i = this.isFull_ ? curIndex : -1;
+ do {
+ i = (i + 1) % goog.debug.LogBuffer.CAPACITY;
+ func(/** @type {!goog.debug.LogRecord} */ (buffer[i]));
+ } while (i != curIndex);
+};
+
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/debug/logger.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/debug/logger.js
new file mode 100644
index 0000000..1e05922
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/debug/logger.js
@@ -0,0 +1,885 @@
+// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Definition of the Logger class. Please minimize dependencies
+ * this file has on other closure classes as any dependency it takes won't be
+ * able to use the logging infrastructure.
+ *
+ * @see ../demos/debug.html
+ */
+
+goog.provide('goog.debug.LogManager');
+goog.provide('goog.debug.Loggable');
+goog.provide('goog.debug.Logger');
+goog.provide('goog.debug.Logger.Level');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.debug');
+goog.require('goog.debug.LogBuffer');
+goog.require('goog.debug.LogRecord');
+
+
+/**
+ * A message value that can be handled by a Logger.
+ *
+ * Functions are treated like callbacks, but are only called when the event's
+ * log level is enabled. This is useful for logging messages that are expensive
+ * to construct.
+ *
+ * @typedef {string|function(): string}
+ */
+goog.debug.Loggable;
+
+
+
+/**
+ * The Logger is an object used for logging debug messages. Loggers are
+ * normally named, using a hierarchical dot-separated namespace. Logger names
+ * can be arbitrary strings, but they should normally be based on the package
+ * name or class name of the logged component, such as goog.net.BrowserChannel.
+ *
+ * The Logger object is loosely based on the java class
+ * java.util.logging.Logger. It supports different levels of filtering for
+ * different loggers.
+ *
+ * The logger object should never be instantiated by application code. It
+ * should always use the goog.debug.Logger.getLogger function.
+ *
+ * @constructor
+ * @param {string} name The name of the Logger.
+ * @final
+ */
+goog.debug.Logger = function(name) {
+ /**
+ * Name of the Logger. Generally a dot-separated namespace
+ * @private {string}
+ */
+ this.name_ = name;
+
+ /**
+ * Parent Logger.
+ * @private {goog.debug.Logger}
+ */
+ this.parent_ = null;
+
+ /**
+ * Level that this logger only filters above. Null indicates it should
+ * inherit from the parent.
+ * @private {goog.debug.Logger.Level}
+ */
+ this.level_ = null;
+
+ /**
+ * Map of children loggers. The keys are the leaf names of the children and
+ * the values are the child loggers.
+ * @private {Object}
+ */
+ this.children_ = null;
+
+ /**
+ * Handlers that are listening to this logger.
+ * @private {Array.<Function>}
+ */
+ this.handlers_ = null;
+};
+
+
+/** @const */
+goog.debug.Logger.ROOT_LOGGER_NAME = '';
+
+
+/**
+ * @define {boolean} Toggles whether loggers other than the root logger can have
+ * log handlers attached to them and whether they can have their log level
+ * set. Logging is a bit faster when this is set to false.
+ */
+goog.define('goog.debug.Logger.ENABLE_HIERARCHY', true);
+
+
+if (!goog.debug.Logger.ENABLE_HIERARCHY) {
+ /**
+ * @type {!Array.<Function>}
+ * @private
+ */
+ goog.debug.Logger.rootHandlers_ = [];
+
+
+ /**
+ * @type {goog.debug.Logger.Level}
+ * @private
+ */
+ goog.debug.Logger.rootLevel_;
+}
+
+
+
+/**
+ * The Level class defines a set of standard logging levels that
+ * can be used to control logging output. The logging Level objects
+ * are ordered and are specified by ordered integers. Enabling logging
+ * at a given level also enables logging at all higher levels.
+ * <p>
+ * Clients should normally use the predefined Level constants such
+ * as Level.SEVERE.
+ * <p>
+ * The levels in descending order are:
+ * <ul>
+ * <li>SEVERE (highest value)
+ * <li>WARNING
+ * <li>INFO
+ * <li>CONFIG
+ * <li>FINE
+ * <li>FINER
+ * <li>FINEST (lowest value)
+ * </ul>
+ * In addition there is a level OFF that can be used to turn
+ * off logging, and a level ALL that can be used to enable
+ * logging of all messages.
+ *
+ * @param {string} name The name of the level.
+ * @param {number} value The numeric value of the level.
+ * @constructor
+ * @final
+ */
+goog.debug.Logger.Level = function(name, value) {
+ /**
+ * The name of the level
+ * @type {string}
+ */
+ this.name = name;
+
+ /**
+ * The numeric value of the level
+ * @type {number}
+ */
+ this.value = value;
+};
+
+
+/**
+ * @return {string} String representation of the logger level.
+ * @override
+ */
+goog.debug.Logger.Level.prototype.toString = function() {
+ return this.name;
+};
+
+
+/**
+ * OFF is a special level that can be used to turn off logging.
+ * This level is initialized to <CODE>Infinity</CODE>.
+ * @type {!goog.debug.Logger.Level}
+ */
+goog.debug.Logger.Level.OFF =
+ new goog.debug.Logger.Level('OFF', Infinity);
+
+
+/**
+ * SHOUT is a message level for extra debugging loudness.
+ * This level is initialized to <CODE>1200</CODE>.
+ * @type {!goog.debug.Logger.Level}
+ */
+goog.debug.Logger.Level.SHOUT = new goog.debug.Logger.Level('SHOUT', 1200);
+
+
+/**
+ * SEVERE is a message level indicating a serious failure.
+ * This level is initialized to <CODE>1000</CODE>.
+ * @type {!goog.debug.Logger.Level}
+ */
+goog.debug.Logger.Level.SEVERE = new goog.debug.Logger.Level('SEVERE', 1000);
+
+
+/**
+ * WARNING is a message level indicating a potential problem.
+ * This level is initialized to <CODE>900</CODE>.
+ * @type {!goog.debug.Logger.Level}
+ */
+goog.debug.Logger.Level.WARNING = new goog.debug.Logger.Level('WARNING', 900);
+
+
+/**
+ * INFO is a message level for informational messages.
+ * This level is initialized to <CODE>800</CODE>.
+ * @type {!goog.debug.Logger.Level}
+ */
+goog.debug.Logger.Level.INFO = new goog.debug.Logger.Level('INFO', 800);
+
+
+/**
+ * CONFIG is a message level for static configuration messages.
+ * This level is initialized to <CODE>700</CODE>.
+ * @type {!goog.debug.Logger.Level}
+ */
+goog.debug.Logger.Level.CONFIG = new goog.debug.Logger.Level('CONFIG', 700);
+
+
+/**
+ * FINE is a message level providing tracing information.
+ * This level is initialized to <CODE>500</CODE>.
+ * @type {!goog.debug.Logger.Level}
+ */
+goog.debug.Logger.Level.FINE = new goog.debug.Logger.Level('FINE', 500);
+
+
+/**
+ * FINER indicates a fairly detailed tracing message.
+ * This level is initialized to <CODE>400</CODE>.
+ * @type {!goog.debug.Logger.Level}
+ */
+goog.debug.Logger.Level.FINER = new goog.debug.Logger.Level('FINER', 400);
+
+/**
+ * FINEST indicates a highly detailed tracing message.
+ * This level is initialized to <CODE>300</CODE>.
+ * @type {!goog.debug.Logger.Level}
+ */
+
+goog.debug.Logger.Level.FINEST = new goog.debug.Logger.Level('FINEST', 300);
+
+
+/**
+ * ALL indicates that all messages should be logged.
+ * This level is initialized to <CODE>0</CODE>.
+ * @type {!goog.debug.Logger.Level}
+ */
+goog.debug.Logger.Level.ALL = new goog.debug.Logger.Level('ALL', 0);
+
+
+/**
+ * The predefined levels.
+ * @type {!Array.<!goog.debug.Logger.Level>}
+ * @final
+ */
+goog.debug.Logger.Level.PREDEFINED_LEVELS = [
+ goog.debug.Logger.Level.OFF,
+ goog.debug.Logger.Level.SHOUT,
+ goog.debug.Logger.Level.SEVERE,
+ goog.debug.Logger.Level.WARNING,
+ goog.debug.Logger.Level.INFO,
+ goog.debug.Logger.Level.CONFIG,
+ goog.debug.Logger.Level.FINE,
+ goog.debug.Logger.Level.FINER,
+ goog.debug.Logger.Level.FINEST,
+ goog.debug.Logger.Level.ALL];
+
+
+/**
+ * A lookup map used to find the level object based on the name or value of
+ * the level object.
+ * @type {Object}
+ * @private
+ */
+goog.debug.Logger.Level.predefinedLevelsCache_ = null;
+
+
+/**
+ * Creates the predefined levels cache and populates it.
+ * @private
+ */
+goog.debug.Logger.Level.createPredefinedLevelsCache_ = function() {
+ goog.debug.Logger.Level.predefinedLevelsCache_ = {};
+ for (var i = 0, level; level = goog.debug.Logger.Level.PREDEFINED_LEVELS[i];
+ i++) {
+ goog.debug.Logger.Level.predefinedLevelsCache_[level.value] = level;
+ goog.debug.Logger.Level.predefinedLevelsCache_[level.name] = level;
+ }
+};
+
+
+/**
+ * Gets the predefined level with the given name.
+ * @param {string} name The name of the level.
+ * @return {goog.debug.Logger.Level} The level, or null if none found.
+ */
+goog.debug.Logger.Level.getPredefinedLevel = function(name) {
+ if (!goog.debug.Logger.Level.predefinedLevelsCache_) {
+ goog.debug.Logger.Level.createPredefinedLevelsCache_();
+ }
+
+ return goog.debug.Logger.Level.predefinedLevelsCache_[name] || null;
+};
+
+
+/**
+ * Gets the highest predefined level <= #value.
+ * @param {number} value Level value.
+ * @return {goog.debug.Logger.Level} The level, or null if none found.
+ */
+goog.debug.Logger.Level.getPredefinedLevelByValue = function(value) {
+ if (!goog.debug.Logger.Level.predefinedLevelsCache_) {
+ goog.debug.Logger.Level.createPredefinedLevelsCache_();
+ }
+
+ if (value in goog.debug.Logger.Level.predefinedLevelsCache_) {
+ return goog.debug.Logger.Level.predefinedLevelsCache_[value];
+ }
+
+ for (var i = 0; i < goog.debug.Logger.Level.PREDEFINED_LEVELS.length; ++i) {
+ var level = goog.debug.Logger.Level.PREDEFINED_LEVELS[i];
+ if (level.value <= value) {
+ return level;
+ }
+ }
+ return null;
+};
+
+
+/**
+ * Finds or creates a logger for a named subsystem. If a logger has already been
+ * created with the given name it is returned. Otherwise a new logger is
+ * created. If a new logger is created its log level will be configured based
+ * on the LogManager configuration and it will configured to also send logging
+ * output to its parent's handlers. It will be registered in the LogManager
+ * global namespace.
+ *
+ * @param {string} name A name for the logger. This should be a dot-separated
+ * name and should normally be based on the package name or class name of the
+ * subsystem, such as goog.net.BrowserChannel.
+ * @return {!goog.debug.Logger} The named logger.
+ * @deprecated use goog.log instead. http://go/goog-debug-logger-deprecated
+ */
+goog.debug.Logger.getLogger = function(name) {
+ return goog.debug.LogManager.getLogger(name);
+};
+
+
+/**
+ * Logs a message to profiling tools, if available.
+ * {@see https://developers.google.com/web-toolkit/speedtracer/logging-api}
+ * {@see http://msdn.microsoft.com/en-us/library/dd433074(VS.85).aspx}
+ * @param {string} msg The message to log.
+ */
+goog.debug.Logger.logToProfilers = function(msg) {
+ // Using goog.global, as loggers might be used in window-less contexts.
+ if (goog.global['console']) {
+ if (goog.global['console']['timeStamp']) {
+ // Logs a message to Firebug, Web Inspector, SpeedTracer, etc.
+ goog.global['console']['timeStamp'](msg);
+ } else if (goog.global['console']['markTimeline']) {
+ // TODO(user): markTimeline is deprecated. Drop this else clause entirely
+ // after Chrome M14 hits stable.
+ goog.global['console']['markTimeline'](msg);
+ }
+ }
+
+ if (goog.global['msWriteProfilerMark']) {
+ // Logs a message to the Microsoft profiler
+ goog.global['msWriteProfilerMark'](msg);
+ }
+};
+
+
+/**
+ * Gets the name of this logger.
+ * @return {string} The name of this logger.
+ */
+goog.debug.Logger.prototype.getName = function() {
+ return this.name_;
+};
+
+
+/**
+ * Adds a handler to the logger. This doesn't use the event system because
+ * we want to be able to add logging to the event system.
+ * @param {Function} handler Handler function to add.
+ */
+goog.debug.Logger.prototype.addHandler = function(handler) {
+ if (goog.debug.LOGGING_ENABLED) {
+ if (goog.debug.Logger.ENABLE_HIERARCHY) {
+ if (!this.handlers_) {
+ this.handlers_ = [];
+ }
+ this.handlers_.push(handler);
+ } else {
+ goog.asserts.assert(!this.name_,
+ 'Cannot call addHandler on a non-root logger when ' +
+ 'goog.debug.Logger.ENABLE_HIERARCHY is false.');
+ goog.debug.Logger.rootHandlers_.push(handler);
+ }
+ }
+};
+
+
+/**
+ * Removes a handler from the logger. This doesn't use the event system because
+ * we want to be able to add logging to the event system.
+ * @param {Function} handler Handler function to remove.
+ * @return {boolean} Whether the handler was removed.
+ */
+goog.debug.Logger.prototype.removeHandler = function(handler) {
+ if (goog.debug.LOGGING_ENABLED) {
+ var handlers = goog.debug.Logger.ENABLE_HIERARCHY ? this.handlers_ :
+ goog.debug.Logger.rootHandlers_;
+ return !!handlers && goog.array.remove(handlers, handler);
+ } else {
+ return false;
+ }
+};
+
+
+/**
+ * Returns the parent of this logger.
+ * @return {goog.debug.Logger} The parent logger or null if this is the root.
+ */
+goog.debug.Logger.prototype.getParent = function() {
+ return this.parent_;
+};
+
+
+/**
+ * Returns the children of this logger as a map of the child name to the logger.
+ * @return {!Object} The map where the keys are the child leaf names and the
+ * values are the Logger objects.
+ */
+goog.debug.Logger.prototype.getChildren = function() {
+ if (!this.children_) {
+ this.children_ = {};
+ }
+ return this.children_;
+};
+
+
+/**
+ * Set the log level specifying which message levels will be logged by this
+ * logger. Message levels lower than this value will be discarded.
+ * The level value Level.OFF can be used to turn off logging. If the new level
+ * is null, it means that this node should inherit its level from its nearest
+ * ancestor with a specific (non-null) level value.
+ *
+ * @param {goog.debug.Logger.Level} level The new level.
+ */
+goog.debug.Logger.prototype.setLevel = function(level) {
+ if (goog.debug.LOGGING_ENABLED) {
+ if (goog.debug.Logger.ENABLE_HIERARCHY) {
+ this.level_ = level;
+ } else {
+ goog.asserts.assert(!this.name_,
+ 'Cannot call setLevel() on a non-root logger when ' +
+ 'goog.debug.Logger.ENABLE_HIERARCHY is false.');
+ goog.debug.Logger.rootLevel_ = level;
+ }
+ }
+};
+
+
+/**
+ * Gets the log level specifying which message levels will be logged by this
+ * logger. Message levels lower than this value will be discarded.
+ * The level value Level.OFF can be used to turn off logging. If the level
+ * is null, it means that this node should inherit its level from its nearest
+ * ancestor with a specific (non-null) level value.
+ *
+ * @return {goog.debug.Logger.Level} The level.
+ */
+goog.debug.Logger.prototype.getLevel = function() {
+ return goog.debug.LOGGING_ENABLED ?
+ this.level_ : goog.debug.Logger.Level.OFF;
+};
+
+
+/**
+ * Returns the effective level of the logger based on its ancestors' levels.
+ * @return {goog.debug.Logger.Level} The level.
+ */
+goog.debug.Logger.prototype.getEffectiveLevel = function() {
+ if (!goog.debug.LOGGING_ENABLED) {
+ return goog.debug.Logger.Level.OFF;
+ }
+
+ if (!goog.debug.Logger.ENABLE_HIERARCHY) {
+ return goog.debug.Logger.rootLevel_;
+ }
+ if (this.level_) {
+ return this.level_;
+ }
+ if (this.parent_) {
+ return this.parent_.getEffectiveLevel();
+ }
+ goog.asserts.fail('Root logger has no level set.');
+ return null;
+};
+
+
+/**
+ * Checks if a message of the given level would actually be logged by this
+ * logger. This check is based on the Loggers effective level, which may be
+ * inherited from its parent.
+ * @param {goog.debug.Logger.Level} level The level to check.
+ * @return {boolean} Whether the message would be logged.
+ */
+goog.debug.Logger.prototype.isLoggable = function(level) {
+ return goog.debug.LOGGING_ENABLED &&
+ level.value >= this.getEffectiveLevel().value;
+};
+
+
+/**
+ * Logs a message. If the logger is currently enabled for the
+ * given message level then the given message is forwarded to all the
+ * registered output Handler objects.
+ * @param {goog.debug.Logger.Level} level One of the level identifiers.
+ * @param {goog.debug.Loggable} msg The message to log.
+ * @param {Error|Object=} opt_exception An exception associated with the
+ * message.
+ */
+goog.debug.Logger.prototype.log = function(level, msg, opt_exception) {
+ // java caches the effective level, not sure it's necessary here
+ if (goog.debug.LOGGING_ENABLED && this.isLoggable(level)) {
+ // Message callbacks can be useful when a log message is expensive to build.
+ if (goog.isFunction(msg)) {
+ msg = msg();
+ }
+
+ this.doLogRecord_(this.getLogRecord(
+ level, msg, opt_exception, goog.debug.Logger.prototype.log));
+ }
+};
+
+
+/**
+ * Creates a new log record and adds the exception (if present) to it.
+ * @param {goog.debug.Logger.Level} level One of the level identifiers.
+ * @param {string} msg The string message.
+ * @param {Error|Object=} opt_exception An exception associated with the
+ * message.
+ * @param {Function=} opt_fnStackContext A function to use as the base
+ * of the stack trace used in the log record.
+ * @return {!goog.debug.LogRecord} A log record.
+ * @suppress {es5Strict}
+ */
+goog.debug.Logger.prototype.getLogRecord = function(
+ level, msg, opt_exception, opt_fnStackContext) {
+ if (goog.debug.LogBuffer.isBufferingEnabled()) {
+ var logRecord =
+ goog.debug.LogBuffer.getInstance().addRecord(level, msg, this.name_);
+ } else {
+ logRecord = new goog.debug.LogRecord(level, String(msg), this.name_);
+ }
+ if (opt_exception) {
+ var context;
+ if (goog.STRICT_MODE_COMPATIBLE) {
+ context = opt_fnStackContext || goog.debug.Logger.prototype.getLogRecord;
+ } else {
+ context = opt_fnStackContext || arguments.callee.caller;
+ }
+
+ logRecord.setException(opt_exception);
+ logRecord.setExceptionText(
+ goog.debug.exposeException(opt_exception,
+ opt_fnStackContext || goog.debug.Logger.prototype.getLogRecord));
+ }
+ return logRecord;
+};
+
+
+/**
+ * Logs a message at the Logger.Level.SHOUT level.
+ * If the logger is currently enabled for the given message level then the
+ * given message is forwarded to all the registered output Handler objects.
+ * @param {goog.debug.Loggable} msg The message to log.
+ * @param {Error=} opt_exception An exception associated with the message.
+ */
+goog.debug.Logger.prototype.shout = function(msg, opt_exception) {
+ if (goog.debug.LOGGING_ENABLED) {
+ this.log(goog.debug.Logger.Level.SHOUT, msg, opt_exception);
+ }
+};
+
+
+/**
+ * Logs a message at the Logger.Level.SEVERE level.
+ * If the logger is currently enabled for the given message level then the
+ * given message is forwarded to all the registered output Handler objects.
+ * @param {goog.debug.Loggable} msg The message to log.
+ * @param {Error=} opt_exception An exception associated with the message.
+ */
+goog.debug.Logger.prototype.severe = function(msg, opt_exception) {
+ if (goog.debug.LOGGING_ENABLED) {
+ this.log(goog.debug.Logger.Level.SEVERE, msg, opt_exception);
+ }
+};
+
+
+/**
+ * Logs a message at the Logger.Level.WARNING level.
+ * If the logger is currently enabled for the given message level then the
+ * given message is forwarded to all the registered output Handler objects.
+ * @param {goog.debug.Loggable} msg The message to log.
+ * @param {Error=} opt_exception An exception associated with the message.
+ */
+goog.debug.Logger.prototype.warning = function(msg, opt_exception) {
+ if (goog.debug.LOGGING_ENABLED) {
+ this.log(goog.debug.Logger.Level.WARNING, msg, opt_exception);
+ }
+};
+
+
+/**
+ * Logs a message at the Logger.Level.INFO level.
+ * If the logger is currently enabled for the given message level then the
+ * given message is forwarded to all the registered output Handler objects.
+ * @param {goog.debug.Loggable} msg The message to log.
+ * @param {Error=} opt_exception An exception associated with the message.
+ */
+goog.debug.Logger.prototype.info = function(msg, opt_exception) {
+ if (goog.debug.LOGGING_ENABLED) {
+ this.log(goog.debug.Logger.Level.INFO, msg, opt_exception);
+ }
+};
+
+
+/**
+ * Logs a message at the Logger.Level.CONFIG level.
+ * If the logger is currently enabled for the given message level then the
+ * given message is forwarded to all the registered output Handler objects.
+ * @param {goog.debug.Loggable} msg The message to log.
+ * @param {Error=} opt_exception An exception associated with the message.
+ */
+goog.debug.Logger.prototype.config = function(msg, opt_exception) {
+ if (goog.debug.LOGGING_ENABLED) {
+ this.log(goog.debug.Logger.Level.CONFIG, msg, opt_exception);
+ }
+};
+
+
+/**
+ * Logs a message at the Logger.Level.FINE level.
+ * If the logger is currently enabled for the given message level then the
+ * given message is forwarded to all the registered output Handler objects.
+ * @param {goog.debug.Loggable} msg The message to log.
+ * @param {Error=} opt_exception An exception associated with the message.
+ */
+goog.debug.Logger.prototype.fine = function(msg, opt_exception) {
+ if (goog.debug.LOGGING_ENABLED) {
+ this.log(goog.debug.Logger.Level.FINE, msg, opt_exception);
+ }
+};
+
+
+/**
+ * Logs a message at the Logger.Level.FINER level.
+ * If the logger is currently enabled for the given message level then the
+ * given message is forwarded to all the registered output Handler objects.
+ * @param {goog.debug.Loggable} msg The message to log.
+ * @param {Error=} opt_exception An exception associated with the message.
+ */
+goog.debug.Logger.prototype.finer = function(msg, opt_exception) {
+ if (goog.debug.LOGGING_ENABLED) {
+ this.log(goog.debug.Logger.Level.FINER, msg, opt_exception);
+ }
+};
+
+
+/**
+ * Logs a message at the Logger.Level.FINEST level.
+ * If the logger is currently enabled for the given message level then the
+ * given message is forwarded to all the registered output Handler objects.
+ * @param {goog.debug.Loggable} msg The message to log.
+ * @param {Error=} opt_exception An exception associated with the message.
+ */
+goog.debug.Logger.prototype.finest = function(msg, opt_exception) {
+ if (goog.debug.LOGGING_ENABLED) {
+ this.log(goog.debug.Logger.Level.FINEST, msg, opt_exception);
+ }
+};
+
+
+/**
+ * Logs a LogRecord. If the logger is currently enabled for the
+ * given message level then the given message is forwarded to all the
+ * registered output Handler objects.
+ * @param {goog.debug.LogRecord} logRecord A log record to log.
+ */
+goog.debug.Logger.prototype.logRecord = function(logRecord) {
+ if (goog.debug.LOGGING_ENABLED && this.isLoggable(logRecord.getLevel())) {
+ this.doLogRecord_(logRecord);
+ }
+};
+
+
+/**
+ * Logs a LogRecord.
+ * @param {goog.debug.LogRecord} logRecord A log record to log.
+ * @private
+ */
+goog.debug.Logger.prototype.doLogRecord_ = function(logRecord) {
+ goog.debug.Logger.logToProfilers('log:' + logRecord.getMessage());
+ if (goog.debug.Logger.ENABLE_HIERARCHY) {
+ var target = this;
+ while (target) {
+ target.callPublish_(logRecord);
+ target = target.getParent();
+ }
+ } else {
+ for (var i = 0, handler; handler = goog.debug.Logger.rootHandlers_[i++]; ) {
+ handler(logRecord);
+ }
+ }
+};
+
+
+/**
+ * Calls the handlers for publish.
+ * @param {goog.debug.LogRecord} logRecord The log record to publish.
+ * @private
+ */
+goog.debug.Logger.prototype.callPublish_ = function(logRecord) {
+ if (this.handlers_) {
+ for (var i = 0, handler; handler = this.handlers_[i]; i++) {
+ handler(logRecord);
+ }
+ }
+};
+
+
+/**
+ * Sets the parent of this logger. This is used for setting up the logger tree.
+ * @param {goog.debug.Logger} parent The parent logger.
+ * @private
+ */
+goog.debug.Logger.prototype.setParent_ = function(parent) {
+ this.parent_ = parent;
+};
+
+
+/**
+ * Adds a child to this logger. This is used for setting up the logger tree.
+ * @param {string} name The leaf name of the child.
+ * @param {goog.debug.Logger} logger The child logger.
+ * @private
+ */
+goog.debug.Logger.prototype.addChild_ = function(name, logger) {
+ this.getChildren()[name] = logger;
+};
+
+
+/**
+ * There is a single global LogManager object that is used to maintain a set of
+ * shared state about Loggers and log services. This is loosely based on the
+ * java class java.util.logging.LogManager.
+ */
+goog.debug.LogManager = {};
+
+
+/**
+ * Map of logger names to logger objects.
+ *
+ * @type {!Object.<string, !goog.debug.Logger>}
+ * @private
+ */
+goog.debug.LogManager.loggers_ = {};
+
+
+/**
+ * The root logger which is the root of the logger tree.
+ * @type {goog.debug.Logger}
+ * @private
+ */
+goog.debug.LogManager.rootLogger_ = null;
+
+
+/**
+ * Initializes the LogManager if not already initialized.
+ */
+goog.debug.LogManager.initialize = function() {
+ if (!goog.debug.LogManager.rootLogger_) {
+ goog.debug.LogManager.rootLogger_ = new goog.debug.Logger(
+ goog.debug.Logger.ROOT_LOGGER_NAME);
+ goog.debug.LogManager.loggers_[goog.debug.Logger.ROOT_LOGGER_NAME] =
+ goog.debug.LogManager.rootLogger_;
+ goog.debug.LogManager.rootLogger_.setLevel(goog.debug.Logger.Level.CONFIG);
+ }
+};
+
+
+/**
+ * Returns all the loggers.
+ * @return {!Object.<string, !goog.debug.Logger>} Map of logger names to logger
+ * objects.
+ */
+goog.debug.LogManager.getLoggers = function() {
+ return goog.debug.LogManager.loggers_;
+};
+
+
+/**
+ * Returns the root of the logger tree namespace, the logger with the empty
+ * string as its name.
+ *
+ * @return {!goog.debug.Logger} The root logger.
+ */
+goog.debug.LogManager.getRoot = function() {
+ goog.debug.LogManager.initialize();
+ return /** @type {!goog.debug.Logger} */ (goog.debug.LogManager.rootLogger_);
+};
+
+
+/**
+ * Finds a named logger.
+ *
+ * @param {string} name A name for the logger. This should be a dot-separated
+ * name and should normally be based on the package name or class name of the
+ * subsystem, such as goog.net.BrowserChannel.
+ * @return {!goog.debug.Logger} The named logger.
+ */
+goog.debug.LogManager.getLogger = function(name) {
+ goog.debug.LogManager.initialize();
+ var ret = goog.debug.LogManager.loggers_[name];
+ return ret || goog.debug.LogManager.createLogger_(name);
+};
+
+
+/**
+ * Creates a function that can be passed to goog.debug.catchErrors. The function
+ * will log all reported errors using the given logger.
+ * @param {goog.debug.Logger=} opt_logger The logger to log the errors to.
+ * Defaults to the root logger.
+ * @return {function(Object)} The created function.
+ */
+goog.debug.LogManager.createFunctionForCatchErrors = function(opt_logger) {
+ return function(info) {
+ var logger = opt_logger || goog.debug.LogManager.getRoot();
+ logger.severe('Error: ' + info.message + ' (' + info.fileName +
+ ' @ Line: ' + info.line + ')');
+ };
+};
+
+
+/**
+ * Creates the named logger. Will also create the parents of the named logger
+ * if they don't yet exist.
+ * @param {string} name The name of the logger.
+ * @return {!goog.debug.Logger} The named logger.
+ * @private
+ */
+goog.debug.LogManager.createLogger_ = function(name) {
+ // find parent logger
+ var logger = new goog.debug.Logger(name);
+ if (goog.debug.Logger.ENABLE_HIERARCHY) {
+ var lastDotIndex = name.lastIndexOf('.');
+ var parentName = name.substr(0, lastDotIndex);
+ var leafName = name.substr(lastDotIndex + 1);
+ var parentLogger = goog.debug.LogManager.getLogger(parentName);
+
+ // tell the parent about the child and the child about the parent
+ parentLogger.addChild_(leafName, logger);
+ logger.setParent_(parentLogger);
+ }
+
+ goog.debug.LogManager.loggers_[name] = logger;
+ return logger;
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/debug/logrecord.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/debug/logrecord.js
new file mode 100644
index 0000000..990af98
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/debug/logrecord.js
@@ -0,0 +1,271 @@
+// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Definition of the LogRecord class. Please minimize
+ * dependencies this file has on other closure classes as any dependency it
+ * takes won't be able to use the logging infrastructure.
+ *
+ */
+
+goog.provide('goog.debug.LogRecord');
+
+
+
+/**
+ * LogRecord objects are used to pass logging requests between
+ * the logging framework and individual log Handlers.
+ * @constructor
+ * @param {goog.debug.Logger.Level} level One of the level identifiers.
+ * @param {string} msg The string message.
+ * @param {string} loggerName The name of the source logger.
+ * @param {number=} opt_time Time this log record was created if other than now.
+ * If 0, we use #goog.now.
+ * @param {number=} opt_sequenceNumber Sequence number of this log record. This
+ * should only be passed in when restoring a log record from persistence.
+ */
+goog.debug.LogRecord = function(level, msg, loggerName,
+ opt_time, opt_sequenceNumber) {
+ this.reset(level, msg, loggerName, opt_time, opt_sequenceNumber);
+};
+
+
+/**
+ * Time the LogRecord was created.
+ * @type {number}
+ * @private
+ */
+goog.debug.LogRecord.prototype.time_;
+
+
+/**
+ * Level of the LogRecord
+ * @type {goog.debug.Logger.Level}
+ * @private
+ */
+goog.debug.LogRecord.prototype.level_;
+
+
+/**
+ * Message associated with the record
+ * @type {string}
+ * @private
+ */
+goog.debug.LogRecord.prototype.msg_;
+
+
+/**
+ * Name of the logger that created the record.
+ * @type {string}
+ * @private
+ */
+goog.debug.LogRecord.prototype.loggerName_;
+
+
+/**
+ * Sequence number for the LogRecord. Each record has a unique sequence number
+ * that is greater than all log records created before it.
+ * @type {number}
+ * @private
+ */
+goog.debug.LogRecord.prototype.sequenceNumber_ = 0;
+
+
+/**
+ * Exception associated with the record
+ * @type {Object}
+ * @private
+ */
+goog.debug.LogRecord.prototype.exception_ = null;
+
+
+/**
+ * Exception text associated with the record
+ * @type {?string}
+ * @private
+ */
+goog.debug.LogRecord.prototype.exceptionText_ = null;
+
+
+/**
+ * @define {boolean} Whether to enable log sequence numbers.
+ */
+goog.define('goog.debug.LogRecord.ENABLE_SEQUENCE_NUMBERS', true);
+
+
+/**
+ * A sequence counter for assigning increasing sequence numbers to LogRecord
+ * objects.
+ * @type {number}
+ * @private
+ */
+goog.debug.LogRecord.nextSequenceNumber_ = 0;
+
+
+/**
+ * Sets all fields of the log record.
+ * @param {goog.debug.Logger.Level} level One of the level identifiers.
+ * @param {string} msg The string message.
+ * @param {string} loggerName The name of the source logger.
+ * @param {number=} opt_time Time this log record was created if other than now.
+ * If 0, we use #goog.now.
+ * @param {number=} opt_sequenceNumber Sequence number of this log record. This
+ * should only be passed in when restoring a log record from persistence.
+ */
+goog.debug.LogRecord.prototype.reset = function(level, msg, loggerName,
+ opt_time, opt_sequenceNumber) {
+ if (goog.debug.LogRecord.ENABLE_SEQUENCE_NUMBERS) {
+ this.sequenceNumber_ = typeof opt_sequenceNumber == 'number' ?
+ opt_sequenceNumber : goog.debug.LogRecord.nextSequenceNumber_++;
+ }
+
+ this.time_ = opt_time || goog.now();
+ this.level_ = level;
+ this.msg_ = msg;
+ this.loggerName_ = loggerName;
+ delete this.exception_;
+ delete this.exceptionText_;
+};
+
+
+/**
+ * Get the source Logger's name.
+ *
+ * @return {string} source logger name (may be null).
+ */
+goog.debug.LogRecord.prototype.getLoggerName = function() {
+ return this.loggerName_;
+};
+
+
+/**
+ * Get the exception that is part of the log record.
+ *
+ * @return {Object} the exception.
+ */
+goog.debug.LogRecord.prototype.getException = function() {
+ return this.exception_;
+};
+
+
+/**
+ * Set the exception that is part of the log record.
+ *
+ * @param {Object} exception the exception.
+ */
+goog.debug.LogRecord.prototype.setException = function(exception) {
+ this.exception_ = exception;
+};
+
+
+/**
+ * Get the exception text that is part of the log record.
+ *
+ * @return {?string} Exception text.
+ */
+goog.debug.LogRecord.prototype.getExceptionText = function() {
+ return this.exceptionText_;
+};
+
+
+/**
+ * Set the exception text that is part of the log record.
+ *
+ * @param {string} text The exception text.
+ */
+goog.debug.LogRecord.prototype.setExceptionText = function(text) {
+ this.exceptionText_ = text;
+};
+
+
+/**
+ * Get the source Logger's name.
+ *
+ * @param {string} loggerName source logger name (may be null).
+ */
+goog.debug.LogRecord.prototype.setLoggerName = function(loggerName) {
+ this.loggerName_ = loggerName;
+};
+
+
+/**
+ * Get the logging message level, for example Level.SEVERE.
+ * @return {goog.debug.Logger.Level} the logging message level.
+ */
+goog.debug.LogRecord.prototype.getLevel = function() {
+ return this.level_;
+};
+
+
+/**
+ * Set the logging message level, for example Level.SEVERE.
+ * @param {goog.debug.Logger.Level} level the logging message level.
+ */
+goog.debug.LogRecord.prototype.setLevel = function(level) {
+ this.level_ = level;
+};
+
+
+/**
+ * Get the "raw" log message, before localization or formatting.
+ *
+ * @return {string} the raw message string.
+ */
+goog.debug.LogRecord.prototype.getMessage = function() {
+ return this.msg_;
+};
+
+
+/**
+ * Set the "raw" log message, before localization or formatting.
+ *
+ * @param {string} msg the raw message string.
+ */
+goog.debug.LogRecord.prototype.setMessage = function(msg) {
+ this.msg_ = msg;
+};
+
+
+/**
+ * Get event time in milliseconds since 1970.
+ *
+ * @return {number} event time in millis since 1970.
+ */
+goog.debug.LogRecord.prototype.getMillis = function() {
+ return this.time_;
+};
+
+
+/**
+ * Set event time in milliseconds since 1970.
+ *
+ * @param {number} time event time in millis since 1970.
+ */
+goog.debug.LogRecord.prototype.setMillis = function(time) {
+ this.time_ = time;
+};
+
+
+/**
+ * Get the sequence number.
+ * <p>
+ * Sequence numbers are normally assigned in the LogRecord
+ * constructor, which assigns unique sequence numbers to
+ * each new LogRecord in increasing order.
+ * @return {number} the sequence number.
+ */
+goog.debug.LogRecord.prototype.getSequenceNumber = function() {
+ return this.sequenceNumber_;
+};
+
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/disposable/disposable.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/disposable/disposable.js
new file mode 100644
index 0000000..c8340db
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/disposable/disposable.js
@@ -0,0 +1,299 @@
+// Copyright 2005 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Implements the disposable interface. The dispose method is used
+ * to clean up references and resources.
+ * @author arv@google.com (Erik Arvidsson)
+ */
+
+
+goog.provide('goog.Disposable');
+/** @suppress {extraProvide} */
+goog.provide('goog.dispose');
+/** @suppress {extraProvide} */
+goog.provide('goog.disposeAll');
+
+goog.require('goog.disposable.IDisposable');
+
+
+
+/**
+ * Class that provides the basic implementation for disposable objects. If your
+ * class holds one or more references to COM objects, DOM nodes, or other
+ * disposable objects, it should extend this class or implement the disposable
+ * interface (defined in goog.disposable.IDisposable).
+ * @constructor
+ * @implements {goog.disposable.IDisposable}
+ */
+goog.Disposable = function() {
+ if (goog.Disposable.MONITORING_MODE != goog.Disposable.MonitoringMode.OFF) {
+ if (goog.Disposable.INCLUDE_STACK_ON_CREATION) {
+ this.creationStack = new Error().stack;
+ }
+ goog.Disposable.instances_[goog.getUid(this)] = this;
+ }
+};
+
+
+/**
+ * @enum {number} Different monitoring modes for Disposable.
+ */
+goog.Disposable.MonitoringMode = {
+ /**
+ * No monitoring.
+ */
+ OFF: 0,
+ /**
+ * Creating and disposing the goog.Disposable instances is monitored. All
+ * disposable objects need to call the {@code goog.Disposable} base
+ * constructor. The PERMANENT mode must be switched on before creating any
+ * goog.Disposable instances.
+ */
+ PERMANENT: 1,
+ /**
+ * INTERACTIVE mode can be switched on and off on the fly without producing
+ * errors. It also doesn't warn if the disposable objects don't call the
+ * {@code goog.Disposable} base constructor.
+ */
+ INTERACTIVE: 2
+};
+
+
+/**
+ * @define {number} The monitoring mode of the goog.Disposable
+ * instances. Default is OFF. Switching on the monitoring is only
+ * recommended for debugging because it has a significant impact on
+ * performance and memory usage. If switched off, the monitoring code
+ * compiles down to 0 bytes.
+ */
+goog.define('goog.Disposable.MONITORING_MODE', 0);
+
+
+/**
+ * @define {boolean} Whether to attach creation stack to each created disposable
+ * instance; This is only relevant for when MonitoringMode != OFF.
+ */
+goog.define('goog.Disposable.INCLUDE_STACK_ON_CREATION', true);
+
+
+/**
+ * Maps the unique ID of every undisposed {@code goog.Disposable} object to
+ * the object itself.
+ * @type {!Object.<number, !goog.Disposable>}
+ * @private
+ */
+goog.Disposable.instances_ = {};
+
+
+/**
+ * @return {!Array.<!goog.Disposable>} All {@code goog.Disposable} objects that
+ * haven't been disposed of.
+ */
+goog.Disposable.getUndisposedObjects = function() {
+ var ret = [];
+ for (var id in goog.Disposable.instances_) {
+ if (goog.Disposable.instances_.hasOwnProperty(id)) {
+ ret.push(goog.Disposable.instances_[Number(id)]);
+ }
+ }
+ return ret;
+};
+
+
+/**
+ * Clears the registry of undisposed objects but doesn't dispose of them.
+ */
+goog.Disposable.clearUndisposedObjects = function() {
+ goog.Disposable.instances_ = {};
+};
+
+
+/**
+ * Whether the object has been disposed of.
+ * @type {boolean}
+ * @private
+ */
+goog.Disposable.prototype.disposed_ = false;
+
+
+/**
+ * Callbacks to invoke when this object is disposed.
+ * @type {Array.<!Function>}
+ * @private
+ */
+goog.Disposable.prototype.onDisposeCallbacks_;
+
+
+/**
+ * If monitoring the goog.Disposable instances is enabled, stores the creation
+ * stack trace of the Disposable instance.
+ * @type {string}
+ */
+goog.Disposable.prototype.creationStack;
+
+
+/**
+ * @return {boolean} Whether the object has been disposed of.
+ * @override
+ */
+goog.Disposable.prototype.isDisposed = function() {
+ return this.disposed_;
+};
+
+
+/**
+ * @return {boolean} Whether the object has been disposed of.
+ * @deprecated Use {@link #isDisposed} instead.
+ */
+goog.Disposable.prototype.getDisposed = goog.Disposable.prototype.isDisposed;
+
+
+/**
+ * Disposes of the object. If the object hasn't already been disposed of, calls
+ * {@link #disposeInternal}. Classes that extend {@code goog.Disposable} should
+ * override {@link #disposeInternal} in order to delete references to COM
+ * objects, DOM nodes, and other disposable objects. Reentrant.
+ *
+ * @return {void} Nothing.
+ * @override
+ */
+goog.Disposable.prototype.dispose = function() {
+ if (!this.disposed_) {
+ // Set disposed_ to true first, in case during the chain of disposal this
+ // gets disposed recursively.
+ this.disposed_ = true;
+ this.disposeInternal();
+ if (goog.Disposable.MONITORING_MODE != goog.Disposable.MonitoringMode.OFF) {
+ var uid = goog.getUid(this);
+ if (goog.Disposable.MONITORING_MODE ==
+ goog.Disposable.MonitoringMode.PERMANENT &&
+ !goog.Disposable.instances_.hasOwnProperty(uid)) {
+ throw Error(this + ' did not call the goog.Disposable base ' +
+ 'constructor or was disposed of after a clearUndisposedObjects ' +
+ 'call');
+ }
+ delete goog.Disposable.instances_[uid];
+ }
+ }
+};
+
+
+/**
+ * Associates a disposable object with this object so that they will be disposed
+ * together.
+ * @param {goog.disposable.IDisposable} disposable that will be disposed when
+ * this object is disposed.
+ */
+goog.Disposable.prototype.registerDisposable = function(disposable) {
+ this.addOnDisposeCallback(goog.partial(goog.dispose, disposable));
+};
+
+
+/**
+ * Invokes a callback function when this object is disposed. Callbacks are
+ * invoked in the order in which they were added.
+ * @param {function(this:T):?} callback The callback function.
+ * @param {T=} opt_scope An optional scope to call the callback in.
+ * @template T
+ */
+goog.Disposable.prototype.addOnDisposeCallback = function(callback, opt_scope) {
+ if (!this.onDisposeCallbacks_) {
+ this.onDisposeCallbacks_ = [];
+ }
+
+ this.onDisposeCallbacks_.push(
+ goog.isDef(opt_scope) ? goog.bind(callback, opt_scope) : callback);
+};
+
+
+/**
+ * Deletes or nulls out any references to COM objects, DOM nodes, or other
+ * disposable objects. Classes that extend {@code goog.Disposable} should
+ * override this method.
+ * Not reentrant. To avoid calling it twice, it must only be called from the
+ * subclass' {@code disposeInternal} method. Everywhere else the public
+ * {@code dispose} method must be used.
+ * For example:
+ * <pre>
+ * mypackage.MyClass = function() {
+ * mypackage.MyClass.base(this, 'constructor');
+ * // Constructor logic specific to MyClass.
+ * ...
+ * };
+ * goog.inherits(mypackage.MyClass, goog.Disposable);
+ *
+ * mypackage.MyClass.prototype.disposeInternal = function() {
+ * // Dispose logic specific to MyClass.
+ * ...
+ * // Call superclass's disposeInternal at the end of the subclass's, like
+ * // in C++, to avoid hard-to-catch issues.
+ * mypackage.MyClass.base(this, 'disposeInternal');
+ * };
+ * </pre>
+ * @protected
+ */
+goog.Disposable.prototype.disposeInternal = function() {
+ if (this.onDisposeCallbacks_) {
+ while (this.onDisposeCallbacks_.length) {
+ this.onDisposeCallbacks_.shift()();
+ }
+ }
+};
+
+
+/**
+ * Returns True if we can verify the object is disposed.
+ * Calls {@code isDisposed} on the argument if it supports it. If obj
+ * is not an object with an isDisposed() method, return false.
+ * @param {*} obj The object to investigate.
+ * @return {boolean} True if we can verify the object is disposed.
+ */
+goog.Disposable.isDisposed = function(obj) {
+ if (obj && typeof obj.isDisposed == 'function') {
+ return obj.isDisposed();
+ }
+ return false;
+};
+
+
+/**
+ * Calls {@code dispose} on the argument if it supports it. If obj is not an
+ * object with a dispose() method, this is a no-op.
+ * @param {*} obj The object to dispose of.
+ */
+goog.dispose = function(obj) {
+ if (obj && typeof obj.dispose == 'function') {
+ obj.dispose();
+ }
+};
+
+
+/**
+ * Calls {@code dispose} on each member of the list that supports it. (If the
+ * member is an ArrayLike, then {@code goog.disposeAll()} will be called
+ * recursively on each of its members.) If the member is not an object with a
+ * {@code dispose()} method, then it is ignored.
+ * @param {...*} var_args The list.
+ */
+goog.disposeAll = function(var_args) {
+ for (var i = 0, len = arguments.length; i < len; ++i) {
+ var disposable = arguments[i];
+ if (goog.isArrayLike(disposable)) {
+ goog.disposeAll.apply(null, disposable);
+ } else {
+ goog.dispose(disposable);
+ }
+ }
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/disposable/idisposable.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/disposable/idisposable.js
new file mode 100644
index 0000000..917d17e
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/disposable/idisposable.js
@@ -0,0 +1,45 @@
+// Copyright 2011 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Definition of the disposable interface. A disposable object
+ * has a dispose method to to clean up references and resources.
+ * @author nnaze@google.com (Nathan Naze)
+ */
+
+
+goog.provide('goog.disposable.IDisposable');
+
+
+
+/**
+ * Interface for a disposable object. If a instance requires cleanup
+ * (references COM objects, DOM notes, or other disposable objects), it should
+ * implement this interface (it may subclass goog.Disposable).
+ * @interface
+ */
+goog.disposable.IDisposable = function() {};
+
+
+/**
+ * Disposes of the object and its resources.
+ * @return {void} Nothing.
+ */
+goog.disposable.IDisposable.prototype.dispose = goog.abstractMethod;
+
+
+/**
+ * @return {boolean} Whether the object has been disposed of.
+ */
+goog.disposable.IDisposable.prototype.isDisposed = goog.abstractMethod;
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/dom/browserfeature.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/dom/browserfeature.js
new file mode 100644
index 0000000..2c70cda
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/dom/browserfeature.js
@@ -0,0 +1,72 @@
+// Copyright 2010 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Browser capability checks for the dom package.
+ *
+ */
+
+
+goog.provide('goog.dom.BrowserFeature');
+
+goog.require('goog.userAgent');
+
+
+/**
+ * Enum of browser capabilities.
+ * @enum {boolean}
+ */
+goog.dom.BrowserFeature = {
+ /**
+ * Whether attributes 'name' and 'type' can be added to an element after it's
+ * created. False in Internet Explorer prior to version 9.
+ */
+ CAN_ADD_NAME_OR_TYPE_ATTRIBUTES: !goog.userAgent.IE ||
+ goog.userAgent.isDocumentModeOrHigher(9),
+
+ /**
+ * Whether we can use element.children to access an element's Element
+ * children. Available since Gecko 1.9.1, IE 9. (IE<9 also includes comment
+ * nodes in the collection.)
+ */
+ CAN_USE_CHILDREN_ATTRIBUTE: !goog.userAgent.GECKO && !goog.userAgent.IE ||
+ goog.userAgent.IE && goog.userAgent.isDocumentModeOrHigher(9) ||
+ goog.userAgent.GECKO && goog.userAgent.isVersionOrHigher('1.9.1'),
+
+ /**
+ * Opera, Safari 3, and Internet Explorer 9 all support innerText but they
+ * include text nodes in script and style tags. Not document-mode-dependent.
+ */
+ CAN_USE_INNER_TEXT: (
+ goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('9')),
+
+ /**
+ * MSIE, Opera, and Safari>=4 support element.parentElement to access an
+ * element's parent if it is an Element.
+ */
+ CAN_USE_PARENT_ELEMENT_PROPERTY: goog.userAgent.IE || goog.userAgent.OPERA ||
+ goog.userAgent.WEBKIT,
+
+ /**
+ * Whether NoScope elements need a scoped element written before them in
+ * innerHTML.
+ * MSDN: http://msdn.microsoft.com/en-us/library/ms533897(VS.85).aspx#1
+ */
+ INNER_HTML_NEEDS_SCOPED_ELEMENT: goog.userAgent.IE,
+
+ /**
+ * Whether we use legacy IE range API.
+ */
+ LEGACY_IE_RANGES: goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9)
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/dom/classes.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/dom/classes.js
new file mode 100644
index 0000000..cc8e794
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/dom/classes.js
@@ -0,0 +1,227 @@
+// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Utilities for adding, removing and setting classes. Prefer
+ * {@link goog.dom.classlist} over these utilities since goog.dom.classlist
+ * conforms closer to the semantics of Element.classList, is faster (uses
+ * native methods rather than parsing strings on every call) and compiles
+ * to smaller code as a result.
+ *
+ * Note: these utilities are meant to operate on HTMLElements and
+ * will not work on elements with differing interfaces (such as SVGElements).
+ *
+ */
+
+
+goog.provide('goog.dom.classes');
+
+goog.require('goog.array');
+
+
+/**
+ * Sets the entire class name of an element.
+ * @param {Node} element DOM node to set class of.
+ * @param {string} className Class name(s) to apply to element.
+ */
+goog.dom.classes.set = function(element, className) {
+ element.className = className;
+};
+
+
+/**
+ * Gets an array of class names on an element
+ * @param {Node} element DOM node to get class of.
+ * @return {!Array} Class names on {@code element}. Some browsers add extra
+ * properties to the array. Do not depend on any of these!
+ */
+goog.dom.classes.get = function(element) {
+ var className = element.className;
+ // Some types of elements don't have a className in IE (e.g. iframes).
+ // Furthermore, in Firefox, className is not a string when the element is
+ // an SVG element.
+ return goog.isString(className) && className.match(/\S+/g) || [];
+};
+
+
+/**
+ * Adds a class or classes to an element. Does not add multiples of class names.
+ * @param {Node} element DOM node to add class to.
+ * @param {...string} var_args Class names to add.
+ * @return {boolean} Whether class was added (or all classes were added).
+ */
+goog.dom.classes.add = function(element, var_args) {
+ var classes = goog.dom.classes.get(element);
+ var args = goog.array.slice(arguments, 1);
+ var expectedCount = classes.length + args.length;
+ goog.dom.classes.add_(classes, args);
+ goog.dom.classes.set(element, classes.join(' '));
+ return classes.length == expectedCount;
+};
+
+
+/**
+ * Removes a class or classes from an element.
+ * @param {Node} element DOM node to remove class from.
+ * @param {...string} var_args Class name(s) to remove.
+ * @return {boolean} Whether all classes in {@code var_args} were found and
+ * removed.
+ */
+goog.dom.classes.remove = function(element, var_args) {
+ var classes = goog.dom.classes.get(element);
+ var args = goog.array.slice(arguments, 1);
+ var newClasses = goog.dom.classes.getDifference_(classes, args);
+ goog.dom.classes.set(element, newClasses.join(' '));
+ return newClasses.length == classes.length - args.length;
+};
+
+
+/**
+ * Helper method for {@link goog.dom.classes.add} and
+ * {@link goog.dom.classes.addRemove}. Adds one or more classes to the supplied
+ * classes array.
+ * @param {Array.<string>} classes All class names for the element, will be
+ * updated to have the classes supplied in {@code args} added.
+ * @param {Array.<string>} args Class names to add.
+ * @private
+ */
+goog.dom.classes.add_ = function(classes, args) {
+ for (var i = 0; i < args.length; i++) {
+ if (!goog.array.contains(classes, args[i])) {
+ classes.push(args[i]);
+ }
+ }
+};
+
+
+/**
+ * Helper method for {@link goog.dom.classes.remove} and
+ * {@link goog.dom.classes.addRemove}. Calculates the difference of two arrays.
+ * @param {!Array.<string>} arr1 First array.
+ * @param {!Array.<string>} arr2 Second array.
+ * @return {!Array.<string>} The first array without the elements of the second
+ * array.
+ * @private
+ */
+goog.dom.classes.getDifference_ = function(arr1, arr2) {
+ return goog.array.filter(arr1, function(item) {
+ return !goog.array.contains(arr2, item);
+ });
+};
+
+
+/**
+ * Switches a class on an element from one to another without disturbing other
+ * classes. If the fromClass isn't removed, the toClass won't be added.
+ * @param {Node} element DOM node to swap classes on.
+ * @param {string} fromClass Class to remove.
+ * @param {string} toClass Class to add.
+ * @return {boolean} Whether classes were switched.
+ */
+goog.dom.classes.swap = function(element, fromClass, toClass) {
+ var classes = goog.dom.classes.get(element);
+
+ var removed = false;
+ for (var i = 0; i < classes.length; i++) {
+ if (classes[i] == fromClass) {
+ goog.array.splice(classes, i--, 1);
+ removed = true;
+ }
+ }
+
+ if (removed) {
+ classes.push(toClass);
+ goog.dom.classes.set(element, classes.join(' '));
+ }
+
+ return removed;
+};
+
+
+/**
+ * Adds zero or more classes to an element and removes zero or more as a single
+ * operation. Unlike calling {@link goog.dom.classes.add} and
+ * {@link goog.dom.classes.remove} separately, this is more efficient as it only
+ * parses the class property once.
+ *
+ * If a class is in both the remove and add lists, it will be added. Thus,
+ * you can use this instead of {@link goog.dom.classes.swap} when you have
+ * more than two class names that you want to swap.
+ *
+ * @param {Node} element DOM node to swap classes on.
+ * @param {?(string|Array.<string>)} classesToRemove Class or classes to
+ * remove, if null no classes are removed.
+ * @param {?(string|Array.<string>)} classesToAdd Class or classes to add, if
+ * null no classes are added.
+ */
+goog.dom.classes.addRemove = function(element, classesToRemove, classesToAdd) {
+ var classes = goog.dom.classes.get(element);
+ if (goog.isString(classesToRemove)) {
+ goog.array.remove(classes, classesToRemove);
+ } else if (goog.isArray(classesToRemove)) {
+ classes = goog.dom.classes.getDifference_(classes, classesToRemove);
+ }
+
+ if (goog.isString(classesToAdd) &&
+ !goog.array.contains(classes, classesToAdd)) {
+ classes.push(classesToAdd);
+ } else if (goog.isArray(classesToAdd)) {
+ goog.dom.classes.add_(classes, classesToAdd);
+ }
+
+ goog.dom.classes.set(element, classes.join(' '));
+};
+
+
+/**
+ * Returns true if an element has a class.
+ * @param {Node} element DOM node to test.
+ * @param {string} className Class name to test for.
+ * @return {boolean} Whether element has the class.
+ */
+goog.dom.classes.has = function(element, className) {
+ return goog.array.contains(goog.dom.classes.get(element), className);
+};
+
+
+/**
+ * Adds or removes a class depending on the enabled argument.
+ * @param {Node} element DOM node to add or remove the class on.
+ * @param {string} className Class name to add or remove.
+ * @param {boolean} enabled Whether to add or remove the class (true adds,
+ * false removes).
+ */
+goog.dom.classes.enable = function(element, className, enabled) {
+ if (enabled) {
+ goog.dom.classes.add(element, className);
+ } else {
+ goog.dom.classes.remove(element, className);
+ }
+};
+
+
+/**
+ * Removes a class if an element has it, and adds it the element doesn't have
+ * it. Won't affect other classes on the node.
+ * @param {Node} element DOM node to toggle class on.
+ * @param {string} className Class to toggle.
+ * @return {boolean} True if class was added, false if it was removed
+ * (in other words, whether element has the class after this function has
+ * been called).
+ */
+goog.dom.classes.toggle = function(element, className) {
+ var add = !goog.dom.classes.has(element, className);
+ goog.dom.classes.enable(element, className, add);
+ return add;
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/dom/classlist.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/dom/classlist.js
new file mode 100644
index 0000000..bca8e6b
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/dom/classlist.js
@@ -0,0 +1,277 @@
+// Copyright 2012 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Utilities for detecting, adding and removing classes. Prefer
+ * this over goog.dom.classes for new code since it attempts to use classList
+ * (DOMTokenList: http://dom.spec.whatwg.org/#domtokenlist) which is faster
+ * and requires less code.
+ *
+ * Note: these utilities are meant to operate on HTMLElements
+ * and may have unexpected behavior on elements with differing interfaces
+ * (such as SVGElements).
+ */
+
+
+goog.provide('goog.dom.classlist');
+
+goog.require('goog.array');
+
+
+/**
+ * Override this define at build-time if you know your target supports it.
+ * @define {boolean} Whether to use the classList property (DOMTokenList).
+ */
+goog.define('goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST', false);
+
+
+/**
+ * Gets an array-like object of class names on an element.
+ * @param {Element} element DOM node to get the classes of.
+ * @return {!goog.array.ArrayLike} Class names on {@code element}.
+ */
+goog.dom.classlist.get = function(element) {
+ if (goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST || element.classList) {
+ return element.classList;
+ }
+
+ var className = element.className;
+ // Some types of elements don't have a className in IE (e.g. iframes).
+ // Furthermore, in Firefox, className is not a string when the element is
+ // an SVG element.
+ return goog.isString(className) && className.match(/\S+/g) || [];
+};
+
+
+/**
+ * Sets the entire class name of an element.
+ * @param {Element} element DOM node to set class of.
+ * @param {string} className Class name(s) to apply to element.
+ */
+goog.dom.classlist.set = function(element, className) {
+ element.className = className;
+};
+
+
+/**
+ * Returns true if an element has a class. This method may throw a DOM
+ * exception for an invalid or empty class name if DOMTokenList is used.
+ * @param {Element} element DOM node to test.
+ * @param {string} className Class name to test for.
+ * @return {boolean} Whether element has the class.
+ */
+goog.dom.classlist.contains = function(element, className) {
+ if (goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST || element.classList) {
+ return element.classList.contains(className);
+ }
+ return goog.array.contains(goog.dom.classlist.get(element), className);
+};
+
+
+/**
+ * Adds a class to an element. Does not add multiples of class names. This
+ * method may throw a DOM exception for an invalid or empty class name if
+ * DOMTokenList is used.
+ * @param {Element} element DOM node to add class to.
+ * @param {string} className Class name to add.
+ */
+goog.dom.classlist.add = function(element, className) {
+ if (goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST || element.classList) {
+ element.classList.add(className);
+ return;
+ }
+
+ if (!goog.dom.classlist.contains(element, className)) {
+ // Ensure we add a space if this is not the first class name added.
+ element.className += element.className.length > 0 ?
+ (' ' + className) : className;
+ }
+};
+
+
+/**
+ * Convenience method to add a number of class names at once.
+ * @param {Element} element The element to which to add classes.
+ * @param {goog.array.ArrayLike.<string>} classesToAdd An array-like object
+ * containing a collection of class names to add to the element.
+ * This method may throw a DOM exception if classesToAdd contains invalid
+ * or empty class names.
+ */
+goog.dom.classlist.addAll = function(element, classesToAdd) {
+ if (goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST || element.classList) {
+ goog.array.forEach(classesToAdd, function(className) {
+ goog.dom.classlist.add(element, className);
+ });
+ return;
+ }
+
+ var classMap = {};
+
+ // Get all current class names into a map.
+ goog.array.forEach(goog.dom.classlist.get(element),
+ function(className) {
+ classMap[className] = true;
+ });
+
+ // Add new class names to the map.
+ goog.array.forEach(classesToAdd,
+ function(className) {
+ classMap[className] = true;
+ });
+
+ // Flatten the keys of the map into the className.
+ element.className = '';
+ for (var className in classMap) {
+ element.className += element.className.length > 0 ?
+ (' ' + className) : className;
+ }
+};
+
+
+/**
+ * Removes a class from an element. This method may throw a DOM exception
+ * for an invalid or empty class name if DOMTokenList is used.
+ * @param {Element} element DOM node to remove class from.
+ * @param {string} className Class name to remove.
+ */
+goog.dom.classlist.remove = function(element, className) {
+ if (goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST || element.classList) {
+ element.classList.remove(className);
+ return;
+ }
+
+ if (goog.dom.classlist.contains(element, className)) {
+ // Filter out the class name.
+ element.className = goog.array.filter(
+ goog.dom.classlist.get(element),
+ function(c) {
+ return c != className;
+ }).join(' ');
+ }
+};
+
+
+/**
+ * Removes a set of classes from an element. Prefer this call to
+ * repeatedly calling {@code goog.dom.classlist.remove} if you want to remove
+ * a large set of class names at once.
+ * @param {Element} element The element from which to remove classes.
+ * @param {goog.array.ArrayLike.<string>} classesToRemove An array-like object
+ * containing a collection of class names to remove from the element.
+ * This method may throw a DOM exception if classesToRemove contains invalid
+ * or empty class names.
+ */
+goog.dom.classlist.removeAll = function(element, classesToRemove) {
+ if (goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST || element.classList) {
+ goog.array.forEach(classesToRemove, function(className) {
+ goog.dom.classlist.remove(element, className);
+ });
+ return;
+ }
+ // Filter out those classes in classesToRemove.
+ element.className = goog.array.filter(
+ goog.dom.classlist.get(element),
+ function(className) {
+ // If this class is not one we are trying to remove,
+ // add it to the array of new class names.
+ return !goog.array.contains(classesToRemove, className);
+ }).join(' ');
+};
+
+
+/**
+ * Adds or removes a class depending on the enabled argument. This method
+ * may throw a DOM exception for an invalid or empty class name if DOMTokenList
+ * is used.
+ * @param {Element} element DOM node to add or remove the class on.
+ * @param {string} className Class name to add or remove.
+ * @param {boolean} enabled Whether to add or remove the class (true adds,
+ * false removes).
+ */
+goog.dom.classlist.enable = function(element, className, enabled) {
+ if (enabled) {
+ goog.dom.classlist.add(element, className);
+ } else {
+ goog.dom.classlist.remove(element, className);
+ }
+};
+
+
+/**
+ * Adds or removes a set of classes depending on the enabled argument. This
+ * method may throw a DOM exception for an invalid or empty class name if
+ * DOMTokenList is used.
+ * @param {!Element} element DOM node to add or remove the class on.
+ * @param {goog.array.ArrayLike.<string>} classesToEnable An array-like object
+ * containing a collection of class names to add or remove from the element.
+ * @param {boolean} enabled Whether to add or remove the classes (true adds,
+ * false removes).
+ */
+goog.dom.classlist.enableAll = function(element, classesToEnable, enabled) {
+ var f = enabled ? goog.dom.classlist.addAll :
+ goog.dom.classlist.removeAll;
+ f(element, classesToEnable);
+};
+
+
+/**
+ * Switches a class on an element from one to another without disturbing other
+ * classes. If the fromClass isn't removed, the toClass won't be added. This
+ * method may throw a DOM exception if the class names are empty or invalid.
+ * @param {Element} element DOM node to swap classes on.
+ * @param {string} fromClass Class to remove.
+ * @param {string} toClass Class to add.
+ * @return {boolean} Whether classes were switched.
+ */
+goog.dom.classlist.swap = function(element, fromClass, toClass) {
+ if (goog.dom.classlist.contains(element, fromClass)) {
+ goog.dom.classlist.remove(element, fromClass);
+ goog.dom.classlist.add(element, toClass);
+ return true;
+ }
+ return false;
+};
+
+
+/**
+ * Removes a class if an element has it, and adds it the element doesn't have
+ * it. Won't affect other classes on the node. This method may throw a DOM
+ * exception if the class name is empty or invalid.
+ * @param {Element} element DOM node to toggle class on.
+ * @param {string} className Class to toggle.
+ * @return {boolean} True if class was added, false if it was removed
+ * (in other words, whether element has the class after this function has
+ * been called).
+ */
+goog.dom.classlist.toggle = function(element, className) {
+ var add = !goog.dom.classlist.contains(element, className);
+ goog.dom.classlist.enable(element, className, add);
+ return add;
+};
+
+
+/**
+ * Adds and removes a class of an element. Unlike
+ * {@link goog.dom.classlist.swap}, this method adds the classToAdd regardless
+ * of whether the classToRemove was present and had been removed. This method
+ * may throw a DOM exception if the class names are empty or invalid.
+ *
+ * @param {Element} element DOM node to swap classes on.
+ * @param {string} classToRemove Class to remove.
+ * @param {string} classToAdd Class to add.
+ */
+goog.dom.classlist.addRemove = function(element, classToRemove, classToAdd) {
+ goog.dom.classlist.remove(element, classToRemove);
+ goog.dom.classlist.add(element, classToAdd);
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/dom/dom.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/dom/dom.js
new file mode 100644
index 0000000..1cb0dca
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/dom/dom.js
@@ -0,0 +1,2917 @@
+// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Utilities for manipulating the browser's Document Object Model
+ * Inspiration taken *heavily* from mochikit (http://mochikit.com/).
+ *
+ * You can use {@link goog.dom.DomHelper} to create new dom helpers that refer
+ * to a different document object. This is useful if you are working with
+ * frames or multiple windows.
+ *
+ */
+
+
+// TODO(arv): Rename/refactor getTextContent and getRawTextContent. The problem
+// is that getTextContent should mimic the DOM3 textContent. We should add a
+// getInnerText (or getText) which tries to return the visible text, innerText.
+
+
+goog.provide('goog.dom');
+goog.provide('goog.dom.Appendable');
+goog.provide('goog.dom.DomHelper');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.dom.BrowserFeature');
+goog.require('goog.dom.NodeType');
+goog.require('goog.dom.TagName');
+goog.require('goog.functions');
+goog.require('goog.math.Coordinate');
+goog.require('goog.math.Size');
+goog.require('goog.object');
+goog.require('goog.string');
+goog.require('goog.userAgent');
+
+
+/**
+ * @define {boolean} Whether we know at compile time that the browser is in
+ * quirks mode.
+ */
+goog.define('goog.dom.ASSUME_QUIRKS_MODE', false);
+
+
+/**
+ * @define {boolean} Whether we know at compile time that the browser is in
+ * standards compliance mode.
+ */
+goog.define('goog.dom.ASSUME_STANDARDS_MODE', false);
+
+
+/**
+ * Whether we know the compatibility mode at compile time.
+ * @type {boolean}
+ * @private
+ */
+goog.dom.COMPAT_MODE_KNOWN_ =
+ goog.dom.ASSUME_QUIRKS_MODE || goog.dom.ASSUME_STANDARDS_MODE;
+
+
+/**
+ * Gets the DomHelper object for the document where the element resides.
+ * @param {(Node|Window)=} opt_element If present, gets the DomHelper for this
+ * element.
+ * @return {!goog.dom.DomHelper} The DomHelper.
+ */
+goog.dom.getDomHelper = function(opt_element) {
+ return opt_element ?
+ new goog.dom.DomHelper(goog.dom.getOwnerDocument(opt_element)) :
+ (goog.dom.defaultDomHelper_ ||
+ (goog.dom.defaultDomHelper_ = new goog.dom.DomHelper()));
+};
+
+
+/**
+ * Cached default DOM helper.
+ * @type {goog.dom.DomHelper}
+ * @private
+ */
+goog.dom.defaultDomHelper_;
+
+
+/**
+ * Gets the document object being used by the dom library.
+ * @return {!Document} Document object.
+ */
+goog.dom.getDocument = function() {
+ return document;
+};
+
+
+/**
+ * Gets an element from the current document by element id.
+ *
+ * If an Element is passed in, it is returned.
+ *
+ * @param {string|Element} element Element ID or a DOM node.
+ * @return {Element} The element with the given ID, or the node passed in.
+ */
+goog.dom.getElement = function(element) {
+ return goog.dom.getElementHelper_(document, element);
+};
+
+
+/**
+ * Gets an element by id from the given document (if present).
+ * If an element is given, it is returned.
+ * @param {!Document} doc
+ * @param {string|Element} element Element ID or a DOM node.
+ * @return {Element} The resulting element.
+ * @private
+ */
+goog.dom.getElementHelper_ = function(doc, element) {
+ return goog.isString(element) ?
+ doc.getElementById(element) :
+ element;
+};
+
+
+/**
+ * Gets an element by id, asserting that the element is found.
+ *
+ * This is used when an element is expected to exist, and should fail with
+ * an assertion error if it does not (if assertions are enabled).
+ *
+ * @param {string} id Element ID.
+ * @return {!Element} The element with the given ID, if it exists.
+ */
+goog.dom.getRequiredElement = function(id) {
+ return goog.dom.getRequiredElementHelper_(document, id);
+};
+
+
+/**
+ * Helper function for getRequiredElementHelper functions, both static and
+ * on DomHelper. Asserts the element with the given id exists.
+ * @param {!Document} doc
+ * @param {string} id
+ * @return {!Element} The element with the given ID, if it exists.
+ * @private
+ */
+goog.dom.getRequiredElementHelper_ = function(doc, id) {
+ // To prevent users passing in Elements as is permitted in getElement().
+ goog.asserts.assertString(id);
+ var element = goog.dom.getElementHelper_(doc, id);
+ element = goog.asserts.assertElement(element,
+ 'No element found with id: ' + id);
+ return element;
+};
+
+
+/**
+ * Alias for getElement.
+ * @param {string|Element} element Element ID or a DOM node.
+ * @return {Element} The element with the given ID, or the node passed in.
+ * @deprecated Use {@link goog.dom.getElement} instead.
+ */
+goog.dom.$ = goog.dom.getElement;
+
+
+/**
+ * Looks up elements by both tag and class name, using browser native functions
+ * ({@code querySelectorAll}, {@code getElementsByTagName} or
+ * {@code getElementsByClassName}) where possible. This function
+ * is a useful, if limited, way of collecting a list of DOM elements
+ * with certain characteristics. {@code goog.dom.query} offers a
+ * more powerful and general solution which allows matching on CSS3
+ * selector expressions, but at increased cost in code size. If all you
+ * need is particular tags belonging to a single class, this function
+ * is fast and sleek.
+ *
+ * Note that tag names are case sensitive in the SVG namespace, and this
+ * function converts opt_tag to uppercase for comparisons. For queries in the
+ * SVG namespace you should use querySelector or querySelectorAll instead.
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=963870
+ * https://bugs.webkit.org/show_bug.cgi?id=83438
+ *
+ * @see {goog.dom.query}
+ *
+ * @param {?string=} opt_tag Element tag name.
+ * @param {?string=} opt_class Optional class name.
+ * @param {(Document|Element)=} opt_el Optional element to look in.
+ * @return { {length: number} } Array-like list of elements (only a length
+ * property and numerical indices are guaranteed to exist).
+ */
+goog.dom.getElementsByTagNameAndClass = function(opt_tag, opt_class, opt_el) {
+ return goog.dom.getElementsByTagNameAndClass_(document, opt_tag, opt_class,
+ opt_el);
+};
+
+
+/**
+ * Returns a static, array-like list of the elements with the provided
+ * className.
+ * @see {goog.dom.query}
+ * @param {string} className the name of the class to look for.
+ * @param {(Document|Element)=} opt_el Optional element to look in.
+ * @return { {length: number} } The items found with the class name provided.
+ */
+goog.dom.getElementsByClass = function(className, opt_el) {
+ var parent = opt_el || document;
+ if (goog.dom.canUseQuerySelector_(parent)) {
+ return parent.querySelectorAll('.' + className);
+ }
+ return goog.dom.getElementsByTagNameAndClass_(
+ document, '*', className, opt_el);
+};
+
+
+/**
+ * Returns the first element with the provided className.
+ * @see {goog.dom.query}
+ * @param {string} className the name of the class to look for.
+ * @param {Element|Document=} opt_el Optional element to look in.
+ * @return {Element} The first item with the class name provided.
+ */
+goog.dom.getElementByClass = function(className, opt_el) {
+ var parent = opt_el || document;
+ var retVal = null;
+ if (goog.dom.canUseQuerySelector_(parent)) {
+ retVal = parent.querySelector('.' + className);
+ } else {
+ retVal = goog.dom.getElementsByTagNameAndClass_(
+ document, '*', className, opt_el)[0];
+ }
+ return retVal || null;
+};
+
+
+/**
+ * Ensures an element with the given className exists, and then returns the
+ * first element with the provided className.
+ * @see {goog.dom.query}
+ * @param {string} className the name of the class to look for.
+ * @param {!Element|!Document=} opt_root Optional element or document to look
+ * in.
+ * @return {!Element} The first item with the class name provided.
+ * @throws {goog.asserts.AssertionError} Thrown if no element is found.
+ */
+goog.dom.getRequiredElementByClass = function(className, opt_root) {
+ var retValue = goog.dom.getElementByClass(className, opt_root);
+ return goog.asserts.assert(retValue,
+ 'No element found with className: ' + className);
+};
+
+
+/**
+ * Prefer the standardized (http://www.w3.org/TR/selectors-api/), native and
+ * fast W3C Selectors API.
+ * @param {!(Element|Document)} parent The parent document object.
+ * @return {boolean} whether or not we can use parent.querySelector* APIs.
+ * @private
+ */
+goog.dom.canUseQuerySelector_ = function(parent) {
+ return !!(parent.querySelectorAll && parent.querySelector);
+};
+
+
+/**
+ * Helper for {@code getElementsByTagNameAndClass}.
+ * @param {!Document} doc The document to get the elements in.
+ * @param {?string=} opt_tag Element tag name.
+ * @param {?string=} opt_class Optional class name.
+ * @param {(Document|Element)=} opt_el Optional element to look in.
+ * @return { {length: number} } Array-like list of elements (only a length
+ * property and numerical indices are guaranteed to exist).
+ * @private
+ */
+goog.dom.getElementsByTagNameAndClass_ = function(doc, opt_tag, opt_class,
+ opt_el) {
+ var parent = opt_el || doc;
+ var tagName = (opt_tag && opt_tag != '*') ? opt_tag.toUpperCase() : '';
+
+ if (goog.dom.canUseQuerySelector_(parent) &&
+ (tagName || opt_class)) {
+ var query = tagName + (opt_class ? '.' + opt_class : '');
+ return parent.querySelectorAll(query);
+ }
+
+ // Use the native getElementsByClassName if available, under the assumption
+ // that even when the tag name is specified, there will be fewer elements to
+ // filter through when going by class than by tag name
+ if (opt_class && parent.getElementsByClassName) {
+ var els = parent.getElementsByClassName(opt_class);
+
+ if (tagName) {
+ var arrayLike = {};
+ var len = 0;
+
+ // Filter for specific tags if requested.
+ for (var i = 0, el; el = els[i]; i++) {
+ if (tagName == el.nodeName) {
+ arrayLike[len++] = el;
+ }
+ }
+ arrayLike.length = len;
+
+ return arrayLike;
+ } else {
+ return els;
+ }
+ }
+
+ var els = parent.getElementsByTagName(tagName || '*');
+
+ if (opt_class) {
+ var arrayLike = {};
+ var len = 0;
+ for (var i = 0, el; el = els[i]; i++) {
+ var className = el.className;
+ // Check if className has a split function since SVG className does not.
+ if (typeof className.split == 'function' &&
+ goog.array.contains(className.split(/\s+/), opt_class)) {
+ arrayLike[len++] = el;
+ }
+ }
+ arrayLike.length = len;
+ return arrayLike;
+ } else {
+ return els;
+ }
+};
+
+
+/**
+ * Alias for {@code getElementsByTagNameAndClass}.
+ * @param {?string=} opt_tag Element tag name.
+ * @param {?string=} opt_class Optional class name.
+ * @param {Element=} opt_el Optional element to look in.
+ * @return { {length: number} } Array-like list of elements (only a length
+ * property and numerical indices are guaranteed to exist).
+ * @deprecated Use {@link goog.dom.getElementsByTagNameAndClass} instead.
+ */
+goog.dom.$$ = goog.dom.getElementsByTagNameAndClass;
+
+
+/**
+ * Sets multiple properties on a node.
+ * @param {Element} element DOM node to set properties on.
+ * @param {Object} properties Hash of property:value pairs.
+ */
+goog.dom.setProperties = function(element, properties) {
+ goog.object.forEach(properties, function(val, key) {
+ if (key == 'style') {
+ element.style.cssText = val;
+ } else if (key == 'class') {
+ element.className = val;
+ } else if (key == 'for') {
+ element.htmlFor = val;
+ } else if (key in goog.dom.DIRECT_ATTRIBUTE_MAP_) {
+ element.setAttribute(goog.dom.DIRECT_ATTRIBUTE_MAP_[key], val);
+ } else if (goog.string.startsWith(key, 'aria-') ||
+ goog.string.startsWith(key, 'data-')) {
+ element.setAttribute(key, val);
+ } else {
+ element[key] = val;
+ }
+ });
+};
+
+
+/**
+ * Map of attributes that should be set using
+ * element.setAttribute(key, val) instead of element[key] = val. Used
+ * by goog.dom.setProperties.
+ *
+ * @type {Object}
+ * @private
+ */
+goog.dom.DIRECT_ATTRIBUTE_MAP_ = {
+ 'cellpadding': 'cellPadding',
+ 'cellspacing': 'cellSpacing',
+ 'colspan': 'colSpan',
+ 'frameborder': 'frameBorder',
+ 'height': 'height',
+ 'maxlength': 'maxLength',
+ 'role': 'role',
+ 'rowspan': 'rowSpan',
+ 'type': 'type',
+ 'usemap': 'useMap',
+ 'valign': 'vAlign',
+ 'width': 'width'
+};
+
+
+/**
+ * Gets the dimensions of the viewport.
+ *
+ * Gecko Standards mode:
+ * docEl.clientWidth Width of viewport excluding scrollbar.
+ * win.innerWidth Width of viewport including scrollbar.
+ * body.clientWidth Width of body element.
+ *
+ * docEl.clientHeight Height of viewport excluding scrollbar.
+ * win.innerHeight Height of viewport including scrollbar.
+ * body.clientHeight Height of document.
+ *
+ * Gecko Backwards compatible mode:
+ * docEl.clientWidth Width of viewport excluding scrollbar.
+ * win.innerWidth Width of viewport including scrollbar.
+ * body.clientWidth Width of viewport excluding scrollbar.
+ *
+ * docEl.clientHeight Height of document.
+ * win.innerHeight Height of viewport including scrollbar.
+ * body.clientHeight Height of viewport excluding scrollbar.
+ *
+ * IE6/7 Standards mode:
+ * docEl.clientWidth Width of viewport excluding scrollbar.
+ * win.innerWidth Undefined.
+ * body.clientWidth Width of body element.
+ *
+ * docEl.clientHeight Height of viewport excluding scrollbar.
+ * win.innerHeight Undefined.
+ * body.clientHeight Height of document element.
+ *
+ * IE5 + IE6/7 Backwards compatible mode:
+ * docEl.clientWidth 0.
+ * win.innerWidth Undefined.
+ * body.clientWidth Width of viewport excluding scrollbar.
+ *
+ * docEl.clientHeight 0.
+ * win.innerHeight Undefined.
+ * body.clientHeight Height of viewport excluding scrollbar.
+ *
+ * Opera 9 Standards and backwards compatible mode:
+ * docEl.clientWidth Width of viewport excluding scrollbar.
+ * win.innerWidth Width of viewport including scrollbar.
+ * body.clientWidth Width of viewport excluding scrollbar.
+ *
+ * docEl.clientHeight Height of document.
+ * win.innerHeight Height of viewport including scrollbar.
+ * body.clientHeight Height of viewport excluding scrollbar.
+ *
+ * WebKit:
+ * Safari 2
+ * docEl.clientHeight Same as scrollHeight.
+ * docEl.clientWidth Same as innerWidth.
+ * win.innerWidth Width of viewport excluding scrollbar.
+ * win.innerHeight Height of the viewport including scrollbar.
+ * frame.innerHeight Height of the viewport exluding scrollbar.
+ *
+ * Safari 3 (tested in 522)
+ *
+ * docEl.clientWidth Width of viewport excluding scrollbar.
+ * docEl.clientHeight Height of viewport excluding scrollbar in strict mode.
+ * body.clientHeight Height of viewport excluding scrollbar in quirks mode.
+ *
+ * @param {Window=} opt_window Optional window element to test.
+ * @return {!goog.math.Size} Object with values 'width' and 'height'.
+ */
+goog.dom.getViewportSize = function(opt_window) {
+ // TODO(arv): This should not take an argument
+ return goog.dom.getViewportSize_(opt_window || window);
+};
+
+
+/**
+ * Helper for {@code getViewportSize}.
+ * @param {Window} win The window to get the view port size for.
+ * @return {!goog.math.Size} Object with values 'width' and 'height'.
+ * @private
+ */
+goog.dom.getViewportSize_ = function(win) {
+ var doc = win.document;
+ var el = goog.dom.isCss1CompatMode_(doc) ? doc.documentElement : doc.body;
+ return new goog.math.Size(el.clientWidth, el.clientHeight);
+};
+
+
+/**
+ * Calculates the height of the document.
+ *
+ * @return {number} The height of the current document.
+ */
+goog.dom.getDocumentHeight = function() {
+ return goog.dom.getDocumentHeight_(window);
+};
+
+
+/**
+ * Calculates the height of the document of the given window.
+ *
+ * Function code copied from the opensocial gadget api:
+ * gadgets.window.adjustHeight(opt_height)
+ *
+ * @private
+ * @param {Window} win The window whose document height to retrieve.
+ * @return {number} The height of the document of the given window.
+ */
+goog.dom.getDocumentHeight_ = function(win) {
+ // NOTE(eae): This method will return the window size rather than the document
+ // size in webkit quirks mode.
+ var doc = win.document;
+ var height = 0;
+
+ if (doc) {
+ // Calculating inner content height is hard and different between
+ // browsers rendering in Strict vs. Quirks mode. We use a combination of
+ // three properties within document.body and document.documentElement:
+ // - scrollHeight
+ // - offsetHeight
+ // - clientHeight
+ // These values differ significantly between browsers and rendering modes.
+ // But there are patterns. It just takes a lot of time and persistence
+ // to figure out.
+
+ // If the window has no contents, it has no height. (In IE10,
+ // document.body & document.documentElement are null in an empty iFrame.)
+ var body = doc.body;
+ var docEl = doc.documentElement;
+ if (!body && !docEl) {
+ return 0;
+ }
+
+ // Get the height of the viewport
+ var vh = goog.dom.getViewportSize_(win).height;
+ if (goog.dom.isCss1CompatMode_(doc) && docEl.scrollHeight) {
+ // In Strict mode:
+ // The inner content height is contained in either:
+ // document.documentElement.scrollHeight
+ // document.documentElement.offsetHeight
+ // Based on studying the values output by different browsers,
+ // use the value that's NOT equal to the viewport height found above.
+ height = docEl.scrollHeight != vh ?
+ docEl.scrollHeight : docEl.offsetHeight;
+ } else {
+ // In Quirks mode:
+ // documentElement.clientHeight is equal to documentElement.offsetHeight
+ // except in IE. In most browsers, document.documentElement can be used
+ // to calculate the inner content height.
+ // However, in other browsers (e.g. IE), document.body must be used
+ // instead. How do we know which one to use?
+ // If document.documentElement.clientHeight does NOT equal
+ // document.documentElement.offsetHeight, then use document.body.
+ var sh = docEl.scrollHeight;
+ var oh = docEl.offsetHeight;
+ if (docEl.clientHeight != oh) {
+ sh = body.scrollHeight;
+ oh = body.offsetHeight;
+ }
+
+ // Detect whether the inner content height is bigger or smaller
+ // than the bounding box (viewport). If bigger, take the larger
+ // value. If smaller, take the smaller value.
+ if (sh > vh) {
+ // Content is larger
+ height = sh > oh ? sh : oh;
+ } else {
+ // Content is smaller
+ height = sh < oh ? sh : oh;
+ }
+ }
+ }
+
+ return height;
+};
+
+
+/**
+ * Gets the page scroll distance as a coordinate object.
+ *
+ * @param {Window=} opt_window Optional window element to test.
+ * @return {!goog.math.Coordinate} Object with values 'x' and 'y'.
+ * @deprecated Use {@link goog.dom.getDocumentScroll} instead.
+ */
+goog.dom.getPageScroll = function(opt_window) {
+ var win = opt_window || goog.global || window;
+ return goog.dom.getDomHelper(win.document).getDocumentScroll();
+};
+
+
+/**
+ * Gets the document scroll distance as a coordinate object.
+ *
+ * @return {!goog.math.Coordinate} Object with values 'x' and 'y'.
+ */
+goog.dom.getDocumentScroll = function() {
+ return goog.dom.getDocumentScroll_(document);
+};
+
+
+/**
+ * Helper for {@code getDocumentScroll}.
+ *
+ * @param {!Document} doc The document to get the scroll for.
+ * @return {!goog.math.Coordinate} Object with values 'x' and 'y'.
+ * @private
+ */
+goog.dom.getDocumentScroll_ = function(doc) {
+ var el = goog.dom.getDocumentScrollElement_(doc);
+ var win = goog.dom.getWindow_(doc);
+ if (goog.userAgent.IE && goog.userAgent.isVersionOrHigher('10') &&
+ win.pageYOffset != el.scrollTop) {
+ // The keyboard on IE10 touch devices shifts the page using the pageYOffset
+ // without modifying scrollTop. For this case, we want the body scroll
+ // offsets.
+ return new goog.math.Coordinate(el.scrollLeft, el.scrollTop);
+ }
+ return new goog.math.Coordinate(win.pageXOffset || el.scrollLeft,
+ win.pageYOffset || el.scrollTop);
+};
+
+
+/**
+ * Gets the document scroll element.
+ * @return {!Element} Scrolling element.
+ */
+goog.dom.getDocumentScrollElement = function() {
+ return goog.dom.getDocumentScrollElement_(document);
+};
+
+
+/**
+ * Helper for {@code getDocumentScrollElement}.
+ * @param {!Document} doc The document to get the scroll element for.
+ * @return {!Element} Scrolling element.
+ * @private
+ */
+goog.dom.getDocumentScrollElement_ = function(doc) {
+ // WebKit needs body.scrollLeft in both quirks mode and strict mode. We also
+ // default to the documentElement if the document does not have a body (e.g.
+ // a SVG document).
+ if (!goog.userAgent.WEBKIT && goog.dom.isCss1CompatMode_(doc)) {
+ return doc.documentElement;
+ }
+ return doc.body || doc.documentElement;
+};
+
+
+/**
+ * Gets the window object associated with the given document.
+ *
+ * @param {Document=} opt_doc Document object to get window for.
+ * @return {!Window} The window associated with the given document.
+ */
+goog.dom.getWindow = function(opt_doc) {
+ // TODO(arv): This should not take an argument.
+ return opt_doc ? goog.dom.getWindow_(opt_doc) : window;
+};
+
+
+/**
+ * Helper for {@code getWindow}.
+ *
+ * @param {!Document} doc Document object to get window for.
+ * @return {!Window} The window associated with the given document.
+ * @private
+ */
+goog.dom.getWindow_ = function(doc) {
+ return doc.parentWindow || doc.defaultView;
+};
+
+
+/**
+ * Returns a dom node with a set of attributes. This function accepts varargs
+ * for subsequent nodes to be added. Subsequent nodes will be added to the
+ * first node as childNodes.
+ *
+ * So:
+ * <code>createDom('div', null, createDom('p'), createDom('p'));</code>
+ * would return a div with two child paragraphs
+ *
+ * @param {string} tagName Tag to create.
+ * @param {(Object|Array.<string>|string)=} opt_attributes If object, then a map
+ * of name-value pairs for attributes. If a string, then this is the
+ * className of the new element. If an array, the elements will be joined
+ * together as the className of the new element.
+ * @param {...(Object|string|Array|NodeList)} var_args Further DOM nodes or
+ * strings for text nodes. If one of the var_args is an array or NodeList,i
+ * its elements will be added as childNodes instead.
+ * @return {!Element} Reference to a DOM node.
+ */
+goog.dom.createDom = function(tagName, opt_attributes, var_args) {
+ return goog.dom.createDom_(document, arguments);
+};
+
+
+/**
+ * Helper for {@code createDom}.
+ * @param {!Document} doc The document to create the DOM in.
+ * @param {!Arguments} args Argument object passed from the callers. See
+ * {@code goog.dom.createDom} for details.
+ * @return {!Element} Reference to a DOM node.
+ * @private
+ */
+goog.dom.createDom_ = function(doc, args) {
+ var tagName = args[0];
+ var attributes = args[1];
+
+ // Internet Explorer is dumb: http://msdn.microsoft.com/workshop/author/
+ // dhtml/reference/properties/name_2.asp
+ // Also does not allow setting of 'type' attribute on 'input' or 'button'.
+ if (!goog.dom.BrowserFeature.CAN_ADD_NAME_OR_TYPE_ATTRIBUTES && attributes &&
+ (attributes.name || attributes.type)) {
+ var tagNameArr = ['<', tagName];
+ if (attributes.name) {
+ tagNameArr.push(' name="', goog.string.htmlEscape(attributes.name),
+ '"');
+ }
+ if (attributes.type) {
+ tagNameArr.push(' type="', goog.string.htmlEscape(attributes.type),
+ '"');
+
+ // Clone attributes map to remove 'type' without mutating the input.
+ var clone = {};
+ goog.object.extend(clone, attributes);
+
+ // JSCompiler can't see how goog.object.extend added this property,
+ // because it was essentially added by reflection.
+ // So it needs to be quoted.
+ delete clone['type'];
+
+ attributes = clone;
+ }
+ tagNameArr.push('>');
+ tagName = tagNameArr.join('');
+ }
+
+ var element = doc.createElement(tagName);
+
+ if (attributes) {
+ if (goog.isString(attributes)) {
+ element.className = attributes;
+ } else if (goog.isArray(attributes)) {
+ element.className = attributes.join(' ');
+ } else {
+ goog.dom.setProperties(element, attributes);
+ }
+ }
+
+ if (args.length > 2) {
+ goog.dom.append_(doc, element, args, 2);
+ }
+
+ return element;
+};
+
+
+/**
+ * Appends a node with text or other nodes.
+ * @param {!Document} doc The document to create new nodes in.
+ * @param {!Node} parent The node to append nodes to.
+ * @param {!Arguments} args The values to add. See {@code goog.dom.append}.
+ * @param {number} startIndex The index of the array to start from.
+ * @private
+ */
+goog.dom.append_ = function(doc, parent, args, startIndex) {
+ function childHandler(child) {
+ // TODO(user): More coercion, ala MochiKit?
+ if (child) {
+ parent.appendChild(goog.isString(child) ?
+ doc.createTextNode(child) : child);
+ }
+ }
+
+ for (var i = startIndex; i < args.length; i++) {
+ var arg = args[i];
+ // TODO(attila): Fix isArrayLike to return false for a text node.
+ if (goog.isArrayLike(arg) && !goog.dom.isNodeLike(arg)) {
+ // If the argument is a node list, not a real array, use a clone,
+ // because forEach can't be used to mutate a NodeList.
+ goog.array.forEach(goog.dom.isNodeList(arg) ?
+ goog.array.toArray(arg) : arg,
+ childHandler);
+ } else {
+ childHandler(arg);
+ }
+ }
+};
+
+
+/**
+ * Alias for {@code createDom}.
+ * @param {string} tagName Tag to create.
+ * @param {(string|Object)=} opt_attributes If object, then a map of name-value
+ * pairs for attributes. If a string, then this is the className of the new
+ * element.
+ * @param {...(Object|string|Array|NodeList)} var_args Further DOM nodes or
+ * strings for text nodes. If one of the var_args is an array, its
+ * children will be added as childNodes instead.
+ * @return {!Element} Reference to a DOM node.
+ * @deprecated Use {@link goog.dom.createDom} instead.
+ */
+goog.dom.$dom = goog.dom.createDom;
+
+
+/**
+ * Creates a new element.
+ * @param {string} name Tag name.
+ * @return {!Element} The new element.
+ */
+goog.dom.createElement = function(name) {
+ return document.createElement(name);
+};
+
+
+/**
+ * Creates a new text node.
+ * @param {number|string} content Content.
+ * @return {!Text} The new text node.
+ */
+goog.dom.createTextNode = function(content) {
+ return document.createTextNode(String(content));
+};
+
+
+/**
+ * Create a table.
+ * @param {number} rows The number of rows in the table. Must be >= 1.
+ * @param {number} columns The number of columns in the table. Must be >= 1.
+ * @param {boolean=} opt_fillWithNbsp If true, fills table entries with nsbps.
+ * @return {!Element} The created table.
+ */
+goog.dom.createTable = function(rows, columns, opt_fillWithNbsp) {
+ return goog.dom.createTable_(document, rows, columns, !!opt_fillWithNbsp);
+};
+
+
+/**
+ * Create a table.
+ * @param {!Document} doc Document object to use to create the table.
+ * @param {number} rows The number of rows in the table. Must be >= 1.
+ * @param {number} columns The number of columns in the table. Must be >= 1.
+ * @param {boolean} fillWithNbsp If true, fills table entries with nsbps.
+ * @return {!Element} The created table.
+ * @private
+ */
+goog.dom.createTable_ = function(doc, rows, columns, fillWithNbsp) {
+ var rowHtml = ['<tr>'];
+ for (var i = 0; i < columns; i++) {
+ rowHtml.push(fillWithNbsp ? '<td>&nbsp;</td>' : '<td></td>');
+ }
+ rowHtml.push('</tr>');
+ rowHtml = rowHtml.join('');
+ var totalHtml = ['<table>'];
+ for (i = 0; i < rows; i++) {
+ totalHtml.push(rowHtml);
+ }
+ totalHtml.push('</table>');
+
+ var elem = doc.createElement(goog.dom.TagName.DIV);
+ elem.innerHTML = totalHtml.join('');
+ return /** @type {!Element} */ (elem.removeChild(elem.firstChild));
+};
+
+
+/**
+ * Converts an HTML string into a document fragment. The string must be
+ * sanitized in order to avoid cross-site scripting. For example
+ * {@code goog.dom.htmlToDocumentFragment('&lt;img src=x onerror=alert(0)&gt;')}
+ * triggers an alert in all browsers, even if the returned document fragment
+ * is thrown away immediately.
+ *
+ * @param {string} htmlString The HTML string to convert.
+ * @return {!Node} The resulting document fragment.
+ */
+goog.dom.htmlToDocumentFragment = function(htmlString) {
+ return goog.dom.htmlToDocumentFragment_(document, htmlString);
+};
+
+
+/**
+ * Helper for {@code htmlToDocumentFragment}.
+ *
+ * @param {!Document} doc The document.
+ * @param {string} htmlString The HTML string to convert.
+ * @return {!Node} The resulting document fragment.
+ * @private
+ */
+goog.dom.htmlToDocumentFragment_ = function(doc, htmlString) {
+ var tempDiv = doc.createElement('div');
+ if (goog.dom.BrowserFeature.INNER_HTML_NEEDS_SCOPED_ELEMENT) {
+ tempDiv.innerHTML = '<br>' + htmlString;
+ tempDiv.removeChild(tempDiv.firstChild);
+ } else {
+ tempDiv.innerHTML = htmlString;
+ }
+ if (tempDiv.childNodes.length == 1) {
+ return /** @type {!Node} */ (tempDiv.removeChild(tempDiv.firstChild));
+ } else {
+ var fragment = doc.createDocumentFragment();
+ while (tempDiv.firstChild) {
+ fragment.appendChild(tempDiv.firstChild);
+ }
+ return fragment;
+ }
+};
+
+
+/**
+ * Returns true if the browser is in "CSS1-compatible" (standards-compliant)
+ * mode, false otherwise.
+ * @return {boolean} True if in CSS1-compatible mode.
+ */
+goog.dom.isCss1CompatMode = function() {
+ return goog.dom.isCss1CompatMode_(document);
+};
+
+
+/**
+ * Returns true if the browser is in "CSS1-compatible" (standards-compliant)
+ * mode, false otherwise.
+ * @param {Document} doc The document to check.
+ * @return {boolean} True if in CSS1-compatible mode.
+ * @private
+ */
+goog.dom.isCss1CompatMode_ = function(doc) {
+ if (goog.dom.COMPAT_MODE_KNOWN_) {
+ return goog.dom.ASSUME_STANDARDS_MODE;
+ }
+
+ return doc.compatMode == 'CSS1Compat';
+};
+
+
+/**
+ * Determines if the given node can contain children, intended to be used for
+ * HTML generation.
+ *
+ * IE natively supports node.canHaveChildren but has inconsistent behavior.
+ * Prior to IE8 the base tag allows children and in IE9 all nodes return true
+ * for canHaveChildren.
+ *
+ * In practice all non-IE browsers allow you to add children to any node, but
+ * the behavior is inconsistent:
+ *
+ * <pre>
+ * var a = document.createElement('br');
+ * a.appendChild(document.createTextNode('foo'));
+ * a.appendChild(document.createTextNode('bar'));
+ * console.log(a.childNodes.length); // 2
+ * console.log(a.innerHTML); // Chrome: "", IE9: "foobar", FF3.5: "foobar"
+ * </pre>
+ *
+ * For more information, see:
+ * http://dev.w3.org/html5/markup/syntax.html#syntax-elements
+ *
+ * TODO(user): Rename shouldAllowChildren() ?
+ *
+ * @param {Node} node The node to check.
+ * @return {boolean} Whether the node can contain children.
+ */
+goog.dom.canHaveChildren = function(node) {
+ if (node.nodeType != goog.dom.NodeType.ELEMENT) {
+ return false;
+ }
+ switch (node.tagName) {
+ case goog.dom.TagName.APPLET:
+ case goog.dom.TagName.AREA:
+ case goog.dom.TagName.BASE:
+ case goog.dom.TagName.BR:
+ case goog.dom.TagName.COL:
+ case goog.dom.TagName.COMMAND:
+ case goog.dom.TagName.EMBED:
+ case goog.dom.TagName.FRAME:
+ case goog.dom.TagName.HR:
+ case goog.dom.TagName.IMG:
+ case goog.dom.TagName.INPUT:
+ case goog.dom.TagName.IFRAME:
+ case goog.dom.TagName.ISINDEX:
+ case goog.dom.TagName.KEYGEN:
+ case goog.dom.TagName.LINK:
+ case goog.dom.TagName.NOFRAMES:
+ case goog.dom.TagName.NOSCRIPT:
+ case goog.dom.TagName.META:
+ case goog.dom.TagName.OBJECT:
+ case goog.dom.TagName.PARAM:
+ case goog.dom.TagName.SCRIPT:
+ case goog.dom.TagName.SOURCE:
+ case goog.dom.TagName.STYLE:
+ case goog.dom.TagName.TRACK:
+ case goog.dom.TagName.WBR:
+ return false;
+ }
+ return true;
+};
+
+
+/**
+ * Appends a child to a node.
+ * @param {Node} parent Parent.
+ * @param {Node} child Child.
+ */
+goog.dom.appendChild = function(parent, child) {
+ parent.appendChild(child);
+};
+
+
+/**
+ * Appends a node with text or other nodes.
+ * @param {!Node} parent The node to append nodes to.
+ * @param {...goog.dom.Appendable} var_args The things to append to the node.
+ * If this is a Node it is appended as is.
+ * If this is a string then a text node is appended.
+ * If this is an array like object then fields 0 to length - 1 are appended.
+ */
+goog.dom.append = function(parent, var_args) {
+ goog.dom.append_(goog.dom.getOwnerDocument(parent), parent, arguments, 1);
+};
+
+
+/**
+ * Removes all the child nodes on a DOM node.
+ * @param {Node} node Node to remove children from.
+ */
+goog.dom.removeChildren = function(node) {
+ // Note: Iterations over live collections can be slow, this is the fastest
+ // we could find. The double parenthesis are used to prevent JsCompiler and
+ // strict warnings.
+ var child;
+ while ((child = node.firstChild)) {
+ node.removeChild(child);
+ }
+};
+
+
+/**
+ * Inserts a new node before an existing reference node (i.e. as the previous
+ * sibling). If the reference node has no parent, then does nothing.
+ * @param {Node} newNode Node to insert.
+ * @param {Node} refNode Reference node to insert before.
+ */
+goog.dom.insertSiblingBefore = function(newNode, refNode) {
+ if (refNode.parentNode) {
+ refNode.parentNode.insertBefore(newNode, refNode);
+ }
+};
+
+
+/**
+ * Inserts a new node after an existing reference node (i.e. as the next
+ * sibling). If the reference node has no parent, then does nothing.
+ * @param {Node} newNode Node to insert.
+ * @param {Node} refNode Reference node to insert after.
+ */
+goog.dom.insertSiblingAfter = function(newNode, refNode) {
+ if (refNode.parentNode) {
+ refNode.parentNode.insertBefore(newNode, refNode.nextSibling);
+ }
+};
+
+
+/**
+ * Insert a child at a given index. If index is larger than the number of child
+ * nodes that the parent currently has, the node is inserted as the last child
+ * node.
+ * @param {Element} parent The element into which to insert the child.
+ * @param {Node} child The element to insert.
+ * @param {number} index The index at which to insert the new child node. Must
+ * not be negative.
+ */
+goog.dom.insertChildAt = function(parent, child, index) {
+ // Note that if the second argument is null, insertBefore
+ // will append the child at the end of the list of children.
+ parent.insertBefore(child, parent.childNodes[index] || null);
+};
+
+
+/**
+ * Removes a node from its parent.
+ * @param {Node} node The node to remove.
+ * @return {Node} The node removed if removed; else, null.
+ */
+goog.dom.removeNode = function(node) {
+ return node && node.parentNode ? node.parentNode.removeChild(node) : null;
+};
+
+
+/**
+ * Replaces a node in the DOM tree. Will do nothing if {@code oldNode} has no
+ * parent.
+ * @param {Node} newNode Node to insert.
+ * @param {Node} oldNode Node to replace.
+ */
+goog.dom.replaceNode = function(newNode, oldNode) {
+ var parent = oldNode.parentNode;
+ if (parent) {
+ parent.replaceChild(newNode, oldNode);
+ }
+};
+
+
+/**
+ * Flattens an element. That is, removes it and replace it with its children.
+ * Does nothing if the element is not in the document.
+ * @param {Element} element The element to flatten.
+ * @return {Element|undefined} The original element, detached from the document
+ * tree, sans children; or undefined, if the element was not in the document
+ * to begin with.
+ */
+goog.dom.flattenElement = function(element) {
+ var child, parent = element.parentNode;
+ if (parent && parent.nodeType != goog.dom.NodeType.DOCUMENT_FRAGMENT) {
+ // Use IE DOM method (supported by Opera too) if available
+ if (element.removeNode) {
+ return /** @type {Element} */ (element.removeNode(false));
+ } else {
+ // Move all children of the original node up one level.
+ while ((child = element.firstChild)) {
+ parent.insertBefore(child, element);
+ }
+
+ // Detach the original element.
+ return /** @type {Element} */ (goog.dom.removeNode(element));
+ }
+ }
+};
+
+
+/**
+ * Returns an array containing just the element children of the given element.
+ * @param {Element} element The element whose element children we want.
+ * @return {!(Array|NodeList)} An array or array-like list of just the element
+ * children of the given element.
+ */
+goog.dom.getChildren = function(element) {
+ // We check if the children attribute is supported for child elements
+ // since IE8 misuses the attribute by also including comments.
+ if (goog.dom.BrowserFeature.CAN_USE_CHILDREN_ATTRIBUTE &&
+ element.children != undefined) {
+ return element.children;
+ }
+ // Fall back to manually filtering the element's child nodes.
+ return goog.array.filter(element.childNodes, function(node) {
+ return node.nodeType == goog.dom.NodeType.ELEMENT;
+ });
+};
+
+
+/**
+ * Returns the first child node that is an element.
+ * @param {Node} node The node to get the first child element of.
+ * @return {Element} The first child node of {@code node} that is an element.
+ */
+goog.dom.getFirstElementChild = function(node) {
+ if (node.firstElementChild != undefined) {
+ return /** @type {Element} */(node).firstElementChild;
+ }
+ return goog.dom.getNextElementNode_(node.firstChild, true);
+};
+
+
+/**
+ * Returns the last child node that is an element.
+ * @param {Node} node The node to get the last child element of.
+ * @return {Element} The last child node of {@code node} that is an element.
+ */
+goog.dom.getLastElementChild = function(node) {
+ if (node.lastElementChild != undefined) {
+ return /** @type {Element} */(node).lastElementChild;
+ }
+ return goog.dom.getNextElementNode_(node.lastChild, false);
+};
+
+
+/**
+ * Returns the first next sibling that is an element.
+ * @param {Node} node The node to get the next sibling element of.
+ * @return {Element} The next sibling of {@code node} that is an element.
+ */
+goog.dom.getNextElementSibling = function(node) {
+ if (node.nextElementSibling != undefined) {
+ return /** @type {Element} */(node).nextElementSibling;
+ }
+ return goog.dom.getNextElementNode_(node.nextSibling, true);
+};
+
+
+/**
+ * Returns the first previous sibling that is an element.
+ * @param {Node} node The node to get the previous sibling element of.
+ * @return {Element} The first previous sibling of {@code node} that is
+ * an element.
+ */
+goog.dom.getPreviousElementSibling = function(node) {
+ if (node.previousElementSibling != undefined) {
+ return /** @type {Element} */(node).previousElementSibling;
+ }
+ return goog.dom.getNextElementNode_(node.previousSibling, false);
+};
+
+
+/**
+ * Returns the first node that is an element in the specified direction,
+ * starting with {@code node}.
+ * @param {Node} node The node to get the next element from.
+ * @param {boolean} forward Whether to look forwards or backwards.
+ * @return {Element} The first element.
+ * @private
+ */
+goog.dom.getNextElementNode_ = function(node, forward) {
+ while (node && node.nodeType != goog.dom.NodeType.ELEMENT) {
+ node = forward ? node.nextSibling : node.previousSibling;
+ }
+
+ return /** @type {Element} */ (node);
+};
+
+
+/**
+ * Returns the next node in source order from the given node.
+ * @param {Node} node The node.
+ * @return {Node} The next node in the DOM tree, or null if this was the last
+ * node.
+ */
+goog.dom.getNextNode = function(node) {
+ if (!node) {
+ return null;
+ }
+
+ if (node.firstChild) {
+ return node.firstChild;
+ }
+
+ while (node && !node.nextSibling) {
+ node = node.parentNode;
+ }
+
+ return node ? node.nextSibling : null;
+};
+
+
+/**
+ * Returns the previous node in source order from the given node.
+ * @param {Node} node The node.
+ * @return {Node} The previous node in the DOM tree, or null if this was the
+ * first node.
+ */
+goog.dom.getPreviousNode = function(node) {
+ if (!node) {
+ return null;
+ }
+
+ if (!node.previousSibling) {
+ return node.parentNode;
+ }
+
+ node = node.previousSibling;
+ while (node && node.lastChild) {
+ node = node.lastChild;
+ }
+
+ return node;
+};
+
+
+/**
+ * Whether the object looks like a DOM node.
+ * @param {?} obj The object being tested for node likeness.
+ * @return {boolean} Whether the object looks like a DOM node.
+ */
+goog.dom.isNodeLike = function(obj) {
+ return goog.isObject(obj) && obj.nodeType > 0;
+};
+
+
+/**
+ * Whether the object looks like an Element.
+ * @param {?} obj The object being tested for Element likeness.
+ * @return {boolean} Whether the object looks like an Element.
+ */
+goog.dom.isElement = function(obj) {
+ return goog.isObject(obj) && obj.nodeType == goog.dom.NodeType.ELEMENT;
+};
+
+
+/**
+ * Returns true if the specified value is a Window object. This includes the
+ * global window for HTML pages, and iframe windows.
+ * @param {?} obj Variable to test.
+ * @return {boolean} Whether the variable is a window.
+ */
+goog.dom.isWindow = function(obj) {
+ return goog.isObject(obj) && obj['window'] == obj;
+};
+
+
+/**
+ * Returns an element's parent, if it's an Element.
+ * @param {Element} element The DOM element.
+ * @return {Element} The parent, or null if not an Element.
+ */
+goog.dom.getParentElement = function(element) {
+ var parent;
+ if (goog.dom.BrowserFeature.CAN_USE_PARENT_ELEMENT_PROPERTY) {
+ var isIe9 = goog.userAgent.IE &&
+ goog.userAgent.isVersionOrHigher('9') &&
+ !goog.userAgent.isVersionOrHigher('10');
+ // SVG elements in IE9 can't use the parentElement property.
+ // goog.global['SVGElement'] is not defined in IE9 quirks mode.
+ if (!(isIe9 && goog.global['SVGElement'] &&
+ element instanceof goog.global['SVGElement'])) {
+ parent = element.parentElement;
+ if (parent) {
+ return parent;
+ }
+ }
+ }
+ parent = element.parentNode;
+ return goog.dom.isElement(parent) ? /** @type {!Element} */ (parent) : null;
+};
+
+
+/**
+ * Whether a node contains another node.
+ * @param {Node} parent The node that should contain the other node.
+ * @param {Node} descendant The node to test presence of.
+ * @return {boolean} Whether the parent node contains the descendent node.
+ */
+goog.dom.contains = function(parent, descendant) {
+ // We use browser specific methods for this if available since it is faster
+ // that way.
+
+ // IE DOM
+ if (parent.contains && descendant.nodeType == goog.dom.NodeType.ELEMENT) {
+ return parent == descendant || parent.contains(descendant);
+ }
+
+ // W3C DOM Level 3
+ if (typeof parent.compareDocumentPosition != 'undefined') {
+ return parent == descendant ||
+ Boolean(parent.compareDocumentPosition(descendant) & 16);
+ }
+
+ // W3C DOM Level 1
+ while (descendant && parent != descendant) {
+ descendant = descendant.parentNode;
+ }
+ return descendant == parent;
+};
+
+
+/**
+ * Compares the document order of two nodes, returning 0 if they are the same
+ * node, a negative number if node1 is before node2, and a positive number if
+ * node2 is before node1. Note that we compare the order the tags appear in the
+ * document so in the tree <b><i>text</i></b> the B node is considered to be
+ * before the I node.
+ *
+ * @param {Node} node1 The first node to compare.
+ * @param {Node} node2 The second node to compare.
+ * @return {number} 0 if the nodes are the same node, a negative number if node1
+ * is before node2, and a positive number if node2 is before node1.
+ */
+goog.dom.compareNodeOrder = function(node1, node2) {
+ // Fall out quickly for equality.
+ if (node1 == node2) {
+ return 0;
+ }
+
+ // Use compareDocumentPosition where available
+ if (node1.compareDocumentPosition) {
+ // 4 is the bitmask for FOLLOWS.
+ return node1.compareDocumentPosition(node2) & 2 ? 1 : -1;
+ }
+
+ // Special case for document nodes on IE 7 and 8.
+ if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9)) {
+ if (node1.nodeType == goog.dom.NodeType.DOCUMENT) {
+ return -1;
+ }
+ if (node2.nodeType == goog.dom.NodeType.DOCUMENT) {
+ return 1;
+ }
+ }
+
+ // Process in IE using sourceIndex - we check to see if the first node has
+ // a source index or if its parent has one.
+ if ('sourceIndex' in node1 ||
+ (node1.parentNode && 'sourceIndex' in node1.parentNode)) {
+ var isElement1 = node1.nodeType == goog.dom.NodeType.ELEMENT;
+ var isElement2 = node2.nodeType == goog.dom.NodeType.ELEMENT;
+
+ if (isElement1 && isElement2) {
+ return node1.sourceIndex - node2.sourceIndex;
+ } else {
+ var parent1 = node1.parentNode;
+ var parent2 = node2.parentNode;
+
+ if (parent1 == parent2) {
+ return goog.dom.compareSiblingOrder_(node1, node2);
+ }
+
+ if (!isElement1 && goog.dom.contains(parent1, node2)) {
+ return -1 * goog.dom.compareParentsDescendantNodeIe_(node1, node2);
+ }
+
+
+ if (!isElement2 && goog.dom.contains(parent2, node1)) {
+ return goog.dom.compareParentsDescendantNodeIe_(node2, node1);
+ }
+
+ return (isElement1 ? node1.sourceIndex : parent1.sourceIndex) -
+ (isElement2 ? node2.sourceIndex : parent2.sourceIndex);
+ }
+ }
+
+ // For Safari, we compare ranges.
+ var doc = goog.dom.getOwnerDocument(node1);
+
+ var range1, range2;
+ range1 = doc.createRange();
+ range1.selectNode(node1);
+ range1.collapse(true);
+
+ range2 = doc.createRange();
+ range2.selectNode(node2);
+ range2.collapse(true);
+
+ return range1.compareBoundaryPoints(goog.global['Range'].START_TO_END,
+ range2);
+};
+
+
+/**
+ * Utility function to compare the position of two nodes, when
+ * {@code textNode}'s parent is an ancestor of {@code node}. If this entry
+ * condition is not met, this function will attempt to reference a null object.
+ * @param {Node} textNode The textNode to compare.
+ * @param {Node} node The node to compare.
+ * @return {number} -1 if node is before textNode, +1 otherwise.
+ * @private
+ */
+goog.dom.compareParentsDescendantNodeIe_ = function(textNode, node) {
+ var parent = textNode.parentNode;
+ if (parent == node) {
+ // If textNode is a child of node, then node comes first.
+ return -1;
+ }
+ var sibling = node;
+ while (sibling.parentNode != parent) {
+ sibling = sibling.parentNode;
+ }
+ return goog.dom.compareSiblingOrder_(sibling, textNode);
+};
+
+
+/**
+ * Utility function to compare the position of two nodes known to be non-equal
+ * siblings.
+ * @param {Node} node1 The first node to compare.
+ * @param {Node} node2 The second node to compare.
+ * @return {number} -1 if node1 is before node2, +1 otherwise.
+ * @private
+ */
+goog.dom.compareSiblingOrder_ = function(node1, node2) {
+ var s = node2;
+ while ((s = s.previousSibling)) {
+ if (s == node1) {
+ // We just found node1 before node2.
+ return -1;
+ }
+ }
+
+ // Since we didn't find it, node1 must be after node2.
+ return 1;
+};
+
+
+/**
+ * Find the deepest common ancestor of the given nodes.
+ * @param {...Node} var_args The nodes to find a common ancestor of.
+ * @return {Node} The common ancestor of the nodes, or null if there is none.
+ * null will only be returned if two or more of the nodes are from different
+ * documents.
+ */
+goog.dom.findCommonAncestor = function(var_args) {
+ var i, count = arguments.length;
+ if (!count) {
+ return null;
+ } else if (count == 1) {
+ return arguments[0];
+ }
+
+ var paths = [];
+ var minLength = Infinity;
+ for (i = 0; i < count; i++) {
+ // Compute the list of ancestors.
+ var ancestors = [];
+ var node = arguments[i];
+ while (node) {
+ ancestors.unshift(node);
+ node = node.parentNode;
+ }
+
+ // Save the list for comparison.
+ paths.push(ancestors);
+ minLength = Math.min(minLength, ancestors.length);
+ }
+ var output = null;
+ for (i = 0; i < minLength; i++) {
+ var first = paths[0][i];
+ for (var j = 1; j < count; j++) {
+ if (first != paths[j][i]) {
+ return output;
+ }
+ }
+ output = first;
+ }
+ return output;
+};
+
+
+/**
+ * Returns the owner document for a node.
+ * @param {Node|Window} node The node to get the document for.
+ * @return {!Document} The document owning the node.
+ */
+goog.dom.getOwnerDocument = function(node) {
+ // TODO(nnaze): Update param signature to be non-nullable.
+ goog.asserts.assert(node, 'Node cannot be null or undefined.');
+ return /** @type {!Document} */ (
+ node.nodeType == goog.dom.NodeType.DOCUMENT ? node :
+ node.ownerDocument || node.document);
+};
+
+
+/**
+ * Cross-browser function for getting the document element of a frame or iframe.
+ * @param {Element} frame Frame element.
+ * @return {!Document} The frame content document.
+ */
+goog.dom.getFrameContentDocument = function(frame) {
+ var doc = frame.contentDocument || frame.contentWindow.document;
+ return doc;
+};
+
+
+/**
+ * Cross-browser function for getting the window of a frame or iframe.
+ * @param {Element} frame Frame element.
+ * @return {Window} The window associated with the given frame.
+ */
+goog.dom.getFrameContentWindow = function(frame) {
+ return frame.contentWindow ||
+ goog.dom.getWindow(goog.dom.getFrameContentDocument(frame));
+};
+
+
+/**
+ * Sets the text content of a node, with cross-browser support.
+ * @param {Node} node The node to change the text content of.
+ * @param {string|number} text The value that should replace the node's content.
+ */
+goog.dom.setTextContent = function(node, text) {
+ goog.asserts.assert(node != null,
+ 'goog.dom.setTextContent expects a non-null value for node');
+
+ if ('textContent' in node) {
+ node.textContent = text;
+ } else if (node.nodeType == goog.dom.NodeType.TEXT) {
+ node.data = text;
+ } else if (node.firstChild &&
+ node.firstChild.nodeType == goog.dom.NodeType.TEXT) {
+ // If the first child is a text node we just change its data and remove the
+ // rest of the children.
+ while (node.lastChild != node.firstChild) {
+ node.removeChild(node.lastChild);
+ }
+ node.firstChild.data = text;
+ } else {
+ goog.dom.removeChildren(node);
+ var doc = goog.dom.getOwnerDocument(node);
+ node.appendChild(doc.createTextNode(String(text)));
+ }
+};
+
+
+/**
+ * Gets the outerHTML of a node, which islike innerHTML, except that it
+ * actually contains the HTML of the node itself.
+ * @param {Element} element The element to get the HTML of.
+ * @return {string} The outerHTML of the given element.
+ */
+goog.dom.getOuterHtml = function(element) {
+ // IE, Opera and WebKit all have outerHTML.
+ if ('outerHTML' in element) {
+ return element.outerHTML;
+ } else {
+ var doc = goog.dom.getOwnerDocument(element);
+ var div = doc.createElement('div');
+ div.appendChild(element.cloneNode(true));
+ return div.innerHTML;
+ }
+};
+
+
+/**
+ * Finds the first descendant node that matches the filter function, using
+ * a depth first search. This function offers the most general purpose way
+ * of finding a matching element. You may also wish to consider
+ * {@code goog.dom.query} which can express many matching criteria using
+ * CSS selector expressions. These expressions often result in a more
+ * compact representation of the desired result.
+ * @see goog.dom.query
+ *
+ * @param {Node} root The root of the tree to search.
+ * @param {function(Node) : boolean} p The filter function.
+ * @return {Node|undefined} The found node or undefined if none is found.
+ */
+goog.dom.findNode = function(root, p) {
+ var rv = [];
+ var found = goog.dom.findNodes_(root, p, rv, true);
+ return found ? rv[0] : undefined;
+};
+
+
+/**
+ * Finds all the descendant nodes that match the filter function, using a
+ * a depth first search. This function offers the most general-purpose way
+ * of finding a set of matching elements. You may also wish to consider
+ * {@code goog.dom.query} which can express many matching criteria using
+ * CSS selector expressions. These expressions often result in a more
+ * compact representation of the desired result.
+
+ * @param {Node} root The root of the tree to search.
+ * @param {function(Node) : boolean} p The filter function.
+ * @return {!Array.<!Node>} The found nodes or an empty array if none are found.
+ */
+goog.dom.findNodes = function(root, p) {
+ var rv = [];
+ goog.dom.findNodes_(root, p, rv, false);
+ return rv;
+};
+
+
+/**
+ * Finds the first or all the descendant nodes that match the filter function,
+ * 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 {boolean} findOne If true we exit after the first found node.
+ * @return {boolean} Whether the search is complete or not. True in case findOne
+ * is true and the node is found. False otherwise.
+ * @private
+ */
+goog.dom.findNodes_ = function(root, p, rv, findOne) {
+ if (root != null) {
+ var child = root.firstChild;
+ while (child) {
+ if (p(child)) {
+ rv.push(child);
+ if (findOne) {
+ return true;
+ }
+ }
+ if (goog.dom.findNodes_(child, p, rv, findOne)) {
+ return true;
+ }
+ child = child.nextSibling;
+ }
+ }
+ return false;
+};
+
+
+/**
+ * Map of tags whose content to ignore when calculating text length.
+ * @type {Object}
+ * @private
+ */
+goog.dom.TAGS_TO_IGNORE_ = {
+ 'SCRIPT': 1,
+ 'STYLE': 1,
+ 'HEAD': 1,
+ 'IFRAME': 1,
+ 'OBJECT': 1
+};
+
+
+/**
+ * Map of tags which have predefined values with regard to whitespace.
+ * @type {Object}
+ * @private
+ */
+goog.dom.PREDEFINED_TAG_VALUES_ = {'IMG': ' ', 'BR': '\n'};
+
+
+/**
+ * Returns true if the element has a tab index that allows it to receive
+ * keyboard focus (tabIndex >= 0), false otherwise. Note that some elements
+ * natively support keyboard focus, even if they have no tab index.
+ * @param {Element} element Element to check.
+ * @return {boolean} Whether the element has a tab index that allows keyboard
+ * focus.
+ * @see http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
+ */
+goog.dom.isFocusableTabIndex = function(element) {
+ return goog.dom.hasSpecifiedTabIndex_(element) &&
+ goog.dom.isTabIndexFocusable_(element);
+};
+
+
+/**
+ * Enables or disables keyboard focus support on the element via its tab index.
+ * Only elements for which {@link goog.dom.isFocusableTabIndex} returns true
+ * (or elements that natively support keyboard focus, like form elements) can
+ * receive keyboard focus. See http://go/tabindex for more info.
+ * @param {Element} element Element whose tab index is to be changed.
+ * @param {boolean} enable Whether to set or remove a tab index on the element
+ * that supports keyboard focus.
+ */
+goog.dom.setFocusableTabIndex = function(element, enable) {
+ if (enable) {
+ element.tabIndex = 0;
+ } else {
+ // Set tabIndex to -1 first, then remove it. This is a workaround for
+ // Safari (confirmed in version 4 on Windows). When removing the attribute
+ // without setting it to -1 first, the element remains keyboard focusable
+ // despite not having a tabIndex attribute anymore.
+ element.tabIndex = -1;
+ element.removeAttribute('tabIndex'); // Must be camelCase!
+ }
+};
+
+
+/**
+ * Returns true if the element can be focused, i.e. it has a tab index that
+ * allows it to receive keyboard focus (tabIndex >= 0), or it is an element
+ * that natively supports keyboard focus.
+ * @param {Element} element Element to check.
+ * @return {boolean} Whether the element allows keyboard focus.
+ */
+goog.dom.isFocusable = function(element) {
+ var focusable;
+ // Some elements can have unspecified tab index and still receive focus.
+ if (goog.dom.nativelySupportsFocus_(element)) {
+ // Make sure the element is not disabled ...
+ focusable = !element.disabled &&
+ // ... and if a tab index is specified, it allows focus.
+ (!goog.dom.hasSpecifiedTabIndex_(element) ||
+ goog.dom.isTabIndexFocusable_(element));
+ } else {
+ focusable = goog.dom.isFocusableTabIndex(element);
+ }
+
+ // IE requires elements to be visible in order to focus them.
+ return focusable && goog.userAgent.IE ?
+ goog.dom.hasNonZeroBoundingRect_(element) : focusable;
+};
+
+
+/**
+ * Returns true if the element has a specified tab index.
+ * @param {Element} element Element to check.
+ * @return {boolean} Whether the element has a specified tab index.
+ * @private
+ */
+goog.dom.hasSpecifiedTabIndex_ = function(element) {
+ // IE returns 0 for an unset tabIndex, so we must use getAttributeNode(),
+ // which returns an object with a 'specified' property if tabIndex is
+ // specified. This works on other browsers, too.
+ var attrNode = element.getAttributeNode('tabindex'); // Must be lowercase!
+ return goog.isDefAndNotNull(attrNode) && attrNode.specified;
+};
+
+
+/**
+ * Returns true if the element's tab index allows the element to be focused.
+ * @param {Element} element Element to check.
+ * @return {boolean} Whether the element's tab index allows focus.
+ * @private
+ */
+goog.dom.isTabIndexFocusable_ = function(element) {
+ var index = element.tabIndex;
+ // NOTE: IE9 puts tabIndex in 16-bit int, e.g. -2 is 65534.
+ return goog.isNumber(index) && index >= 0 && index < 32768;
+};
+
+
+/**
+ * Returns true if the element is focusable even when tabIndex is not set.
+ * @param {Element} element Element to check.
+ * @return {boolean} Whether the element natively supports focus.
+ * @private
+ */
+goog.dom.nativelySupportsFocus_ = function(element) {
+ return element.tagName == goog.dom.TagName.A ||
+ element.tagName == goog.dom.TagName.INPUT ||
+ element.tagName == goog.dom.TagName.TEXTAREA ||
+ element.tagName == goog.dom.TagName.SELECT ||
+ element.tagName == goog.dom.TagName.BUTTON;
+};
+
+
+/**
+ * Returns true if the element has a bounding rectangle that would be visible
+ * (i.e. its width and height are greater than zero).
+ * @param {Element} element Element to check.
+ * @return {boolean} Whether the element has a non-zero bounding rectangle.
+ * @private
+ */
+goog.dom.hasNonZeroBoundingRect_ = function(element) {
+ var rect = goog.isFunction(element['getBoundingClientRect']) ?
+ element.getBoundingClientRect() :
+ {'height': element.offsetHeight, 'width': element.offsetWidth};
+ return goog.isDefAndNotNull(rect) && rect.height > 0 && rect.width > 0;
+};
+
+
+/**
+ * Returns the text content of the current node, without markup and invisible
+ * symbols. New lines are stripped and whitespace is collapsed,
+ * such that each character would be visible.
+ *
+ * In browsers that support it, innerText is used. Other browsers attempt to
+ * simulate it via node traversal. Line breaks are canonicalized in IE.
+ *
+ * @param {Node} node The node from which we are getting content.
+ * @return {string} The text content.
+ */
+goog.dom.getTextContent = function(node) {
+ var textContent;
+ // Note(arv): IE9, Opera, and Safari 3 support innerText but they include
+ // text nodes in script tags. So we revert to use a user agent test here.
+ if (goog.dom.BrowserFeature.CAN_USE_INNER_TEXT && ('innerText' in node)) {
+ textContent = goog.string.canonicalizeNewlines(node.innerText);
+ // Unfortunately .innerText() returns text with &shy; symbols
+ // We need to filter it out and then remove duplicate whitespaces
+ } else {
+ var buf = [];
+ goog.dom.getTextContent_(node, buf, true);
+ textContent = buf.join('');
+ }
+
+ // Strip &shy; entities. goog.format.insertWordBreaks inserts them in Opera.
+ textContent = textContent.replace(/ \xAD /g, ' ').replace(/\xAD/g, '');
+ // Strip &#8203; entities. goog.format.insertWordBreaks inserts them in IE8.
+ textContent = textContent.replace(/\u200B/g, '');
+
+ // Skip this replacement on old browsers with working innerText, which
+ // automatically turns &nbsp; into ' ' and / +/ into ' ' when reading
+ // innerText.
+ if (!goog.dom.BrowserFeature.CAN_USE_INNER_TEXT) {
+ textContent = textContent.replace(/ +/g, ' ');
+ }
+ if (textContent != ' ') {
+ textContent = textContent.replace(/^\s*/, '');
+ }
+
+ return textContent;
+};
+
+
+/**
+ * Returns the text content of the current node, without markup.
+ *
+ * Unlike {@code getTextContent} this method does not collapse whitespaces
+ * or normalize lines breaks.
+ *
+ * @param {Node} node The node from which we are getting content.
+ * @return {string} The raw text content.
+ */
+goog.dom.getRawTextContent = function(node) {
+ var buf = [];
+ goog.dom.getTextContent_(node, buf, false);
+
+ return buf.join('');
+};
+
+
+/**
+ * Recursive support function for text content retrieval.
+ *
+ * @param {Node} node The node from which we are getting content.
+ * @param {Array} buf string buffer.
+ * @param {boolean} normalizeWhitespace Whether to normalize whitespace.
+ * @private
+ */
+goog.dom.getTextContent_ = function(node, buf, normalizeWhitespace) {
+ if (node.nodeName in goog.dom.TAGS_TO_IGNORE_) {
+ // ignore certain tags
+ } else if (node.nodeType == goog.dom.NodeType.TEXT) {
+ if (normalizeWhitespace) {
+ buf.push(String(node.nodeValue).replace(/(\r\n|\r|\n)/g, ''));
+ } else {
+ buf.push(node.nodeValue);
+ }
+ } else if (node.nodeName in goog.dom.PREDEFINED_TAG_VALUES_) {
+ buf.push(goog.dom.PREDEFINED_TAG_VALUES_[node.nodeName]);
+ } else {
+ var child = node.firstChild;
+ while (child) {
+ goog.dom.getTextContent_(child, buf, normalizeWhitespace);
+ child = child.nextSibling;
+ }
+ }
+};
+
+
+/**
+ * Returns the text length of the text contained in a node, without markup. This
+ * is equivalent to the selection length if the node was selected, or the number
+ * of cursor movements to traverse the node. Images & BRs take one space. New
+ * lines are ignored.
+ *
+ * @param {Node} node The node whose text content length is being calculated.
+ * @return {number} The length of {@code node}'s text content.
+ */
+goog.dom.getNodeTextLength = function(node) {
+ return goog.dom.getTextContent(node).length;
+};
+
+
+/**
+ * Returns the text offset of a node relative to one of its ancestors. The text
+ * length is the same as the length calculated by goog.dom.getNodeTextLength.
+ *
+ * @param {Node} node The node whose offset is being calculated.
+ * @param {Node=} opt_offsetParent The node relative to which the offset will
+ * be calculated. Defaults to the node's owner document's body.
+ * @return {number} The text offset.
+ */
+goog.dom.getNodeTextOffset = function(node, opt_offsetParent) {
+ var root = opt_offsetParent || goog.dom.getOwnerDocument(node).body;
+ var buf = [];
+ while (node && node != root) {
+ var cur = node;
+ while ((cur = cur.previousSibling)) {
+ buf.unshift(goog.dom.getTextContent(cur));
+ }
+ node = node.parentNode;
+ }
+ // Trim left to deal with FF cases when there might be line breaks and empty
+ // nodes at the front of the text
+ return goog.string.trimLeft(buf.join('')).replace(/ +/g, ' ').length;
+};
+
+
+/**
+ * Returns the node at a given offset in a parent node. If an object is
+ * provided for the optional third parameter, the node and the remainder of the
+ * offset will stored as properties of this object.
+ * @param {Node} parent The parent node.
+ * @param {number} offset The offset into the parent node.
+ * @param {Object=} opt_result Object to be used to store the return value. The
+ * return value will be stored in the form {node: Node, remainder: number}
+ * if this object is provided.
+ * @return {Node} The node at the given offset.
+ */
+goog.dom.getNodeAtOffset = function(parent, offset, opt_result) {
+ var stack = [parent], pos = 0, cur = null;
+ while (stack.length > 0 && pos < offset) {
+ cur = stack.pop();
+ if (cur.nodeName in goog.dom.TAGS_TO_IGNORE_) {
+ // ignore certain tags
+ } else if (cur.nodeType == goog.dom.NodeType.TEXT) {
+ var text = cur.nodeValue.replace(/(\r\n|\r|\n)/g, '').replace(/ +/g, ' ');
+ pos += text.length;
+ } else if (cur.nodeName in goog.dom.PREDEFINED_TAG_VALUES_) {
+ pos += goog.dom.PREDEFINED_TAG_VALUES_[cur.nodeName].length;
+ } else {
+ for (var i = cur.childNodes.length - 1; i >= 0; i--) {
+ stack.push(cur.childNodes[i]);
+ }
+ }
+ }
+ if (goog.isObject(opt_result)) {
+ opt_result.remainder = cur ? cur.nodeValue.length + offset - pos - 1 : 0;
+ opt_result.node = cur;
+ }
+
+ return cur;
+};
+
+
+/**
+ * Returns true if the object is a {@code NodeList}. To qualify as a NodeList,
+ * the object must have a numeric length property and an item function (which
+ * has type 'string' on IE for some reason).
+ * @param {Object} val Object to test.
+ * @return {boolean} Whether the object is a NodeList.
+ */
+goog.dom.isNodeList = function(val) {
+ // TODO(attila): Now the isNodeList is part of goog.dom we can use
+ // goog.userAgent to make this simpler.
+ // A NodeList must have a length property of type 'number' on all platforms.
+ if (val && typeof val.length == 'number') {
+ // A NodeList is an object everywhere except Safari, where it's a function.
+ if (goog.isObject(val)) {
+ // A NodeList must have an item function (on non-IE platforms) or an item
+ // property of type 'string' (on IE).
+ return typeof val.item == 'function' || typeof val.item == 'string';
+ } else if (goog.isFunction(val)) {
+ // On Safari, a NodeList is a function with an item property that is also
+ // a function.
+ return typeof val.item == 'function';
+ }
+ }
+
+ // Not a NodeList.
+ return false;
+};
+
+
+/**
+ * Walks up the DOM hierarchy returning the first ancestor that has the passed
+ * tag name and/or class name. If the passed element matches the specified
+ * criteria, the element itself is returned.
+ * @param {Node} element The DOM node to start with.
+ * @param {?(goog.dom.TagName|string)=} opt_tag The tag name to match (or
+ * null/undefined to match only based on class name).
+ * @param {?string=} opt_class The class name to match (or null/undefined to
+ * match only based on tag name).
+ * @return {Element} The first ancestor that matches the passed criteria, or
+ * null if no match is found.
+ */
+goog.dom.getAncestorByTagNameAndClass = function(element, opt_tag, opt_class) {
+ if (!opt_tag && !opt_class) {
+ return null;
+ }
+ var tagName = opt_tag ? opt_tag.toUpperCase() : null;
+ return /** @type {Element} */ (goog.dom.getAncestor(element,
+ function(node) {
+ return (!tagName || node.nodeName == tagName) &&
+ (!opt_class || goog.isString(node.className) &&
+ goog.array.contains(node.className.split(/\s+/), opt_class));
+ }, true));
+};
+
+
+/**
+ * Walks up the DOM hierarchy returning the first ancestor that has the passed
+ * class name. If the passed element matches the specified criteria, the
+ * element itself is returned.
+ * @param {Node} element The DOM node to start with.
+ * @param {string} className The class name to match.
+ * @return {Element} The first ancestor that matches the passed criteria, or
+ * null if none match.
+ */
+goog.dom.getAncestorByClass = function(element, className) {
+ return goog.dom.getAncestorByTagNameAndClass(element, null, className);
+};
+
+
+/**
+ * Walks up the DOM hierarchy returning the first ancestor that passes the
+ * matcher function.
+ * @param {Node} element The DOM node to start with.
+ * @param {function(Node) : boolean} matcher A function that returns true if the
+ * passed node matches the desired criteria.
+ * @param {boolean=} opt_includeNode If true, the node itself is included in
+ * the search (the first call to the matcher will pass startElement as
+ * the node to test).
+ * @param {number=} opt_maxSearchSteps Maximum number of levels to search up the
+ * dom.
+ * @return {Node} DOM node that matched the matcher, or null if there was
+ * no match.
+ */
+goog.dom.getAncestor = function(
+ element, matcher, opt_includeNode, opt_maxSearchSteps) {
+ if (!opt_includeNode) {
+ element = element.parentNode;
+ }
+ var ignoreSearchSteps = opt_maxSearchSteps == null;
+ var steps = 0;
+ while (element && (ignoreSearchSteps || steps <= opt_maxSearchSteps)) {
+ if (matcher(element)) {
+ return element;
+ }
+ element = element.parentNode;
+ steps++;
+ }
+ // Reached the root of the DOM without a match
+ return null;
+};
+
+
+/**
+ * Determines the active element in the given document.
+ * @param {Document} doc The document to look in.
+ * @return {Element} The active element.
+ */
+goog.dom.getActiveElement = function(doc) {
+ try {
+ return doc && doc.activeElement;
+ } catch (e) {
+ // NOTE(nicksantos): Sometimes, evaluating document.activeElement in IE
+ // throws an exception. I'm not 100% sure why, but I suspect it chokes
+ // on document.activeElement if the activeElement has been recently
+ // removed from the DOM by a JS operation.
+ //
+ // We assume that an exception here simply means
+ // "there is no active element."
+ }
+
+ return null;
+};
+
+
+/**
+ * @private {number} Cached version of the devicePixelRatio.
+ */
+goog.dom.devicePixelRatio_;
+
+
+/**
+ * Gives the devicePixelRatio, or attempts to determine if not present.
+ *
+ * By default, this is the same value given by window.devicePixelRatio. If
+ * devicePixelRatio is not defined, the ratio is calculated with
+ * window.matchMedia, if present. Otherwise, gives 1.0.
+ *
+ * This function is cached so that the pixel ratio is calculated only once
+ * and only calculated when first requested.
+ *
+ * @return {number} The number of actual pixels per virtual pixel.
+ */
+goog.dom.getPixelRatio = goog.functions.cacheReturnValue(function() {
+ var win = goog.dom.getWindow();
+
+ // devicePixelRatio does not work on Mobile firefox.
+ // TODO(user): Enable this check on a known working mobile Gecko version.
+ // Filed a bug: https://bugzilla.mozilla.org/show_bug.cgi?id=896804
+ var isFirefoxMobile = goog.userAgent.GECKO && goog.userAgent.MOBILE;
+
+ if (goog.isDef(win.devicePixelRatio) && !isFirefoxMobile) {
+ return win.devicePixelRatio;
+ } else if (win.matchMedia) {
+ return goog.dom.matchesPixelRatio_(.75) ||
+ goog.dom.matchesPixelRatio_(1.5) ||
+ goog.dom.matchesPixelRatio_(2) ||
+ goog.dom.matchesPixelRatio_(3) || 1;
+ }
+ return 1;
+});
+
+
+/**
+ * Calculates a mediaQuery to check if the current device supports the
+ * given actual to virtual pixel ratio.
+ * @param {number} pixelRatio The ratio of actual pixels to virtual pixels.
+ * @return {number} pixelRatio if applicable, otherwise 0.
+ * @private
+ */
+goog.dom.matchesPixelRatio_ = function(pixelRatio) {
+ var win = goog.dom.getWindow();
+ var query = ('(-webkit-min-device-pixel-ratio: ' + pixelRatio + '),' +
+ '(min--moz-device-pixel-ratio: ' + pixelRatio + '),' +
+ '(min-resolution: ' + pixelRatio + 'dppx)');
+ return win.matchMedia(query).matches ? pixelRatio : 0;
+};
+
+
+
+/**
+ * Create an instance of a DOM helper with a new document object.
+ * @param {Document=} opt_document Document object to associate with this
+ * DOM helper.
+ * @constructor
+ */
+goog.dom.DomHelper = function(opt_document) {
+ /**
+ * Reference to the document object to use
+ * @type {!Document}
+ * @private
+ */
+ this.document_ = opt_document || goog.global.document || document;
+};
+
+
+/**
+ * Gets the dom helper object for the document where the element resides.
+ * @param {Node=} opt_node If present, gets the DomHelper for this node.
+ * @return {!goog.dom.DomHelper} The DomHelper.
+ */
+goog.dom.DomHelper.prototype.getDomHelper = goog.dom.getDomHelper;
+
+
+/**
+ * Sets the document object.
+ * @param {!Document} document Document object.
+ */
+goog.dom.DomHelper.prototype.setDocument = function(document) {
+ this.document_ = document;
+};
+
+
+/**
+ * Gets the document object being used by the dom library.
+ * @return {!Document} Document object.
+ */
+goog.dom.DomHelper.prototype.getDocument = function() {
+ return this.document_;
+};
+
+
+/**
+ * Alias for {@code getElementById}. If a DOM node is passed in then we just
+ * return that.
+ * @param {string|Element} element Element ID or a DOM node.
+ * @return {Element} The element with the given ID, or the node passed in.
+ */
+goog.dom.DomHelper.prototype.getElement = function(element) {
+ return goog.dom.getElementHelper_(this.document_, element);
+};
+
+
+/**
+ * Gets an element by id, asserting that the element is found.
+ *
+ * This is used when an element is expected to exist, and should fail with
+ * an assertion error if it does not (if assertions are enabled).
+ *
+ * @param {string} id Element ID.
+ * @return {!Element} The element with the given ID, if it exists.
+ */
+goog.dom.DomHelper.prototype.getRequiredElement = function(id) {
+ return goog.dom.getRequiredElementHelper_(this.document_, id);
+};
+
+
+/**
+ * Alias for {@code getElement}.
+ * @param {string|Element} element Element ID or a DOM node.
+ * @return {Element} The element with the given ID, or the node passed in.
+ * @deprecated Use {@link goog.dom.DomHelper.prototype.getElement} instead.
+ */
+goog.dom.DomHelper.prototype.$ = goog.dom.DomHelper.prototype.getElement;
+
+
+/**
+ * Looks up elements by both tag and class name, using browser native functions
+ * ({@code querySelectorAll}, {@code getElementsByTagName} or
+ * {@code getElementsByClassName}) where possible. The returned array is a live
+ * NodeList or a static list depending on the code path taken.
+ *
+ * @see goog.dom.query
+ *
+ * @param {?string=} opt_tag Element tag name or * for all tags.
+ * @param {?string=} opt_class Optional class name.
+ * @param {(Document|Element)=} opt_el Optional element to look in.
+ * @return { {length: number} } Array-like list of elements (only a length
+ * property and numerical indices are guaranteed to exist).
+ */
+goog.dom.DomHelper.prototype.getElementsByTagNameAndClass = function(opt_tag,
+ opt_class,
+ opt_el) {
+ return goog.dom.getElementsByTagNameAndClass_(this.document_, opt_tag,
+ opt_class, opt_el);
+};
+
+
+/**
+ * Returns an array of all the elements with the provided className.
+ * @see {goog.dom.query}
+ * @param {string} className the name of the class to look for.
+ * @param {Element|Document=} opt_el Optional element to look in.
+ * @return { {length: number} } The items found with the class name provided.
+ */
+goog.dom.DomHelper.prototype.getElementsByClass = function(className, opt_el) {
+ var doc = opt_el || this.document_;
+ return goog.dom.getElementsByClass(className, doc);
+};
+
+
+/**
+ * Returns the first element we find matching the provided class name.
+ * @see {goog.dom.query}
+ * @param {string} className the name of the class to look for.
+ * @param {(Element|Document)=} opt_el Optional element to look in.
+ * @return {Element} The first item found with the class name provided.
+ */
+goog.dom.DomHelper.prototype.getElementByClass = function(className, opt_el) {
+ var doc = opt_el || this.document_;
+ return goog.dom.getElementByClass(className, doc);
+};
+
+
+/**
+ * Ensures an element with the given className exists, and then returns the
+ * first element with the provided className.
+ * @see {goog.dom.query}
+ * @param {string} className the name of the class to look for.
+ * @param {(!Element|!Document)=} opt_root Optional element or document to look
+ * in.
+ * @return {!Element} The first item found with the class name provided.
+ * @throws {goog.asserts.AssertionError} Thrown if no element is found.
+ */
+goog.dom.DomHelper.prototype.getRequiredElementByClass = function(className,
+ opt_root) {
+ var root = opt_root || this.document_;
+ return goog.dom.getRequiredElementByClass(className, root);
+};
+
+
+/**
+ * Alias for {@code getElementsByTagNameAndClass}.
+ * @deprecated Use DomHelper getElementsByTagNameAndClass.
+ * @see goog.dom.query
+ *
+ * @param {?string=} opt_tag Element tag name.
+ * @param {?string=} opt_class Optional class name.
+ * @param {Element=} opt_el Optional element to look in.
+ * @return { {length: number} } Array-like list of elements (only a length
+ * property and numerical indices are guaranteed to exist).
+ */
+goog.dom.DomHelper.prototype.$$ =
+ goog.dom.DomHelper.prototype.getElementsByTagNameAndClass;
+
+
+/**
+ * Sets a number of properties on a node.
+ * @param {Element} element DOM node to set properties on.
+ * @param {Object} properties Hash of property:value pairs.
+ */
+goog.dom.DomHelper.prototype.setProperties = goog.dom.setProperties;
+
+
+/**
+ * Gets the dimensions of the viewport.
+ * @param {Window=} opt_window Optional window element to test. Defaults to
+ * the window of the Dom Helper.
+ * @return {!goog.math.Size} Object with values 'width' and 'height'.
+ */
+goog.dom.DomHelper.prototype.getViewportSize = function(opt_window) {
+ // TODO(arv): This should not take an argument. That breaks the rule of a
+ // a DomHelper representing a single frame/window/document.
+ return goog.dom.getViewportSize(opt_window || this.getWindow());
+};
+
+
+/**
+ * Calculates the height of the document.
+ *
+ * @return {number} The height of the document.
+ */
+goog.dom.DomHelper.prototype.getDocumentHeight = function() {
+ return goog.dom.getDocumentHeight_(this.getWindow());
+};
+
+
+/**
+ * Typedef for use with goog.dom.createDom and goog.dom.append.
+ * @typedef {Object|string|Array|NodeList}
+ */
+goog.dom.Appendable;
+
+
+/**
+ * Returns a dom node with a set of attributes. This function accepts varargs
+ * for subsequent nodes to be added. Subsequent nodes will be added to the
+ * first node as childNodes.
+ *
+ * So:
+ * <code>createDom('div', null, createDom('p'), createDom('p'));</code>
+ * would return a div with two child paragraphs
+ *
+ * An easy way to move all child nodes of an existing element to a new parent
+ * element is:
+ * <code>createDom('div', null, oldElement.childNodes);</code>
+ * which will remove all child nodes from the old element and add them as
+ * child nodes of the new DIV.
+ *
+ * @param {string} tagName Tag to create.
+ * @param {Object|string=} opt_attributes If object, then a map of name-value
+ * pairs for attributes. If a string, then this is the className of the new
+ * element.
+ * @param {...goog.dom.Appendable} var_args Further DOM nodes or
+ * strings for text nodes. If one of the var_args is an array or
+ * NodeList, its elements will be added as childNodes instead.
+ * @return {!Element} Reference to a DOM node.
+ */
+goog.dom.DomHelper.prototype.createDom = function(tagName,
+ opt_attributes,
+ var_args) {
+ return goog.dom.createDom_(this.document_, arguments);
+};
+
+
+/**
+ * Alias for {@code createDom}.
+ * @param {string} tagName Tag to create.
+ * @param {(Object|string)=} opt_attributes If object, then a map of name-value
+ * pairs for attributes. If a string, then this is the className of the new
+ * element.
+ * @param {...goog.dom.Appendable} var_args Further DOM nodes or strings for
+ * text nodes. If one of the var_args is an array, its children will be
+ * added as childNodes instead.
+ * @return {!Element} Reference to a DOM node.
+ * @deprecated Use {@link goog.dom.DomHelper.prototype.createDom} instead.
+ */
+goog.dom.DomHelper.prototype.$dom = goog.dom.DomHelper.prototype.createDom;
+
+
+/**
+ * Creates a new element.
+ * @param {string} name Tag name.
+ * @return {!Element} The new element.
+ */
+goog.dom.DomHelper.prototype.createElement = function(name) {
+ return this.document_.createElement(name);
+};
+
+
+/**
+ * Creates a new text node.
+ * @param {number|string} content Content.
+ * @return {!Text} The new text node.
+ */
+goog.dom.DomHelper.prototype.createTextNode = function(content) {
+ return this.document_.createTextNode(String(content));
+};
+
+
+/**
+ * Create a table.
+ * @param {number} rows The number of rows in the table. Must be >= 1.
+ * @param {number} columns The number of columns in the table. Must be >= 1.
+ * @param {boolean=} opt_fillWithNbsp If true, fills table entries with nsbps.
+ * @return {!Element} The created table.
+ */
+goog.dom.DomHelper.prototype.createTable = function(rows, columns,
+ opt_fillWithNbsp) {
+ return goog.dom.createTable_(this.document_, rows, columns,
+ !!opt_fillWithNbsp);
+};
+
+
+/**
+ * Converts an HTML string into a node or a document fragment. A single Node
+ * is used if the {@code htmlString} only generates a single node. If the
+ * {@code htmlString} generates multiple nodes then these are put inside a
+ * {@code DocumentFragment}.
+ *
+ * @param {string} htmlString The HTML string to convert.
+ * @return {!Node} The resulting node.
+ */
+goog.dom.DomHelper.prototype.htmlToDocumentFragment = function(htmlString) {
+ return goog.dom.htmlToDocumentFragment_(this.document_, htmlString);
+};
+
+
+/**
+ * Returns true if the browser is in "CSS1-compatible" (standards-compliant)
+ * mode, false otherwise.
+ * @return {boolean} True if in CSS1-compatible mode.
+ */
+goog.dom.DomHelper.prototype.isCss1CompatMode = function() {
+ return goog.dom.isCss1CompatMode_(this.document_);
+};
+
+
+/**
+ * Gets the window object associated with the document.
+ * @return {!Window} The window associated with the given document.
+ */
+goog.dom.DomHelper.prototype.getWindow = function() {
+ return goog.dom.getWindow_(this.document_);
+};
+
+
+/**
+ * Gets the document scroll element.
+ * @return {!Element} Scrolling element.
+ */
+goog.dom.DomHelper.prototype.getDocumentScrollElement = function() {
+ return goog.dom.getDocumentScrollElement_(this.document_);
+};
+
+
+/**
+ * Gets the document scroll distance as a coordinate object.
+ * @return {!goog.math.Coordinate} Object with properties 'x' and 'y'.
+ */
+goog.dom.DomHelper.prototype.getDocumentScroll = function() {
+ return goog.dom.getDocumentScroll_(this.document_);
+};
+
+
+/**
+ * Determines the active element in the given document.
+ * @param {Document=} opt_doc The document to look in.
+ * @return {Element} The active element.
+ */
+goog.dom.DomHelper.prototype.getActiveElement = function(opt_doc) {
+ return goog.dom.getActiveElement(opt_doc || this.document_);
+};
+
+
+/**
+ * Appends a child to a node.
+ * @param {Node} parent Parent.
+ * @param {Node} child Child.
+ */
+goog.dom.DomHelper.prototype.appendChild = goog.dom.appendChild;
+
+
+/**
+ * Appends a node with text or other nodes.
+ * @param {!Node} parent The node to append nodes to.
+ * @param {...goog.dom.Appendable} var_args The things to append to the node.
+ * If this is a Node it is appended as is.
+ * If this is a string then a text node is appended.
+ * If this is an array like object then fields 0 to length - 1 are appended.
+ */
+goog.dom.DomHelper.prototype.append = goog.dom.append;
+
+
+/**
+ * Determines if the given node can contain children, intended to be used for
+ * HTML generation.
+ *
+ * @param {Node} node The node to check.
+ * @return {boolean} Whether the node can contain children.
+ */
+goog.dom.DomHelper.prototype.canHaveChildren = goog.dom.canHaveChildren;
+
+
+/**
+ * Removes all the child nodes on a DOM node.
+ * @param {Node} node Node to remove children from.
+ */
+goog.dom.DomHelper.prototype.removeChildren = goog.dom.removeChildren;
+
+
+/**
+ * Inserts a new node before an existing reference node (i.e., as the previous
+ * sibling). If the reference node has no parent, then does nothing.
+ * @param {Node} newNode Node to insert.
+ * @param {Node} refNode Reference node to insert before.
+ */
+goog.dom.DomHelper.prototype.insertSiblingBefore = goog.dom.insertSiblingBefore;
+
+
+/**
+ * Inserts a new node after an existing reference node (i.e., as the next
+ * sibling). If the reference node has no parent, then does nothing.
+ * @param {Node} newNode Node to insert.
+ * @param {Node} refNode Reference node to insert after.
+ */
+goog.dom.DomHelper.prototype.insertSiblingAfter = goog.dom.insertSiblingAfter;
+
+
+/**
+ * Insert a child at a given index. If index is larger than the number of child
+ * nodes that the parent currently has, the node is inserted as the last child
+ * node.
+ * @param {Element} parent The element into which to insert the child.
+ * @param {Node} child The element to insert.
+ * @param {number} index The index at which to insert the new child node. Must
+ * not be negative.
+ */
+goog.dom.DomHelper.prototype.insertChildAt = goog.dom.insertChildAt;
+
+
+/**
+ * Removes a node from its parent.
+ * @param {Node} node The node to remove.
+ * @return {Node} The node removed if removed; else, null.
+ */
+goog.dom.DomHelper.prototype.removeNode = goog.dom.removeNode;
+
+
+/**
+ * Replaces a node in the DOM tree. Will do nothing if {@code oldNode} has no
+ * parent.
+ * @param {Node} newNode Node to insert.
+ * @param {Node} oldNode Node to replace.
+ */
+goog.dom.DomHelper.prototype.replaceNode = goog.dom.replaceNode;
+
+
+/**
+ * Flattens an element. That is, removes it and replace it with its children.
+ * @param {Element} element The element to flatten.
+ * @return {Element|undefined} The original element, detached from the document
+ * tree, sans children, or undefined if the element was already not in the
+ * document.
+ */
+goog.dom.DomHelper.prototype.flattenElement = goog.dom.flattenElement;
+
+
+/**
+ * Returns an array containing just the element children of the given element.
+ * @param {Element} element The element whose element children we want.
+ * @return {!(Array|NodeList)} An array or array-like list of just the element
+ * children of the given element.
+ */
+goog.dom.DomHelper.prototype.getChildren = goog.dom.getChildren;
+
+
+/**
+ * Returns the first child node that is an element.
+ * @param {Node} node The node to get the first child element of.
+ * @return {Element} The first child node of {@code node} that is an element.
+ */
+goog.dom.DomHelper.prototype.getFirstElementChild =
+ goog.dom.getFirstElementChild;
+
+
+/**
+ * Returns the last child node that is an element.
+ * @param {Node} node The node to get the last child element of.
+ * @return {Element} The last child node of {@code node} that is an element.
+ */
+goog.dom.DomHelper.prototype.getLastElementChild = goog.dom.getLastElementChild;
+
+
+/**
+ * Returns the first next sibling that is an element.
+ * @param {Node} node The node to get the next sibling element of.
+ * @return {Element} The next sibling of {@code node} that is an element.
+ */
+goog.dom.DomHelper.prototype.getNextElementSibling =
+ goog.dom.getNextElementSibling;
+
+
+/**
+ * Returns the first previous sibling that is an element.
+ * @param {Node} node The node to get the previous sibling element of.
+ * @return {Element} The first previous sibling of {@code node} that is
+ * an element.
+ */
+goog.dom.DomHelper.prototype.getPreviousElementSibling =
+ goog.dom.getPreviousElementSibling;
+
+
+/**
+ * Returns the next node in source order from the given node.
+ * @param {Node} node The node.
+ * @return {Node} The next node in the DOM tree, or null if this was the last
+ * node.
+ */
+goog.dom.DomHelper.prototype.getNextNode = goog.dom.getNextNode;
+
+
+/**
+ * Returns the previous node in source order from the given node.
+ * @param {Node} node The node.
+ * @return {Node} The previous node in the DOM tree, or null if this was the
+ * first node.
+ */
+goog.dom.DomHelper.prototype.getPreviousNode = goog.dom.getPreviousNode;
+
+
+/**
+ * Whether the object looks like a DOM node.
+ * @param {?} obj The object being tested for node likeness.
+ * @return {boolean} Whether the object looks like a DOM node.
+ */
+goog.dom.DomHelper.prototype.isNodeLike = goog.dom.isNodeLike;
+
+
+/**
+ * Whether the object looks like an Element.
+ * @param {?} obj The object being tested for Element likeness.
+ * @return {boolean} Whether the object looks like an Element.
+ */
+goog.dom.DomHelper.prototype.isElement = goog.dom.isElement;
+
+
+/**
+ * Returns true if the specified value is a Window object. This includes the
+ * global window for HTML pages, and iframe windows.
+ * @param {?} obj Variable to test.
+ * @return {boolean} Whether the variable is a window.
+ */
+goog.dom.DomHelper.prototype.isWindow = goog.dom.isWindow;
+
+
+/**
+ * Returns an element's parent, if it's an Element.
+ * @param {Element} element The DOM element.
+ * @return {Element} The parent, or null if not an Element.
+ */
+goog.dom.DomHelper.prototype.getParentElement = goog.dom.getParentElement;
+
+
+/**
+ * Whether a node contains another node.
+ * @param {Node} parent The node that should contain the other node.
+ * @param {Node} descendant The node to test presence of.
+ * @return {boolean} Whether the parent node contains the descendent node.
+ */
+goog.dom.DomHelper.prototype.contains = goog.dom.contains;
+
+
+/**
+ * Compares the document order of two nodes, returning 0 if they are the same
+ * node, a negative number if node1 is before node2, and a positive number if
+ * node2 is before node1. Note that we compare the order the tags appear in the
+ * document so in the tree <b><i>text</i></b> the B node is considered to be
+ * before the I node.
+ *
+ * @param {Node} node1 The first node to compare.
+ * @param {Node} node2 The second node to compare.
+ * @return {number} 0 if the nodes are the same node, a negative number if node1
+ * is before node2, and a positive number if node2 is before node1.
+ */
+goog.dom.DomHelper.prototype.compareNodeOrder = goog.dom.compareNodeOrder;
+
+
+/**
+ * Find the deepest common ancestor of the given nodes.
+ * @param {...Node} var_args The nodes to find a common ancestor of.
+ * @return {Node} The common ancestor of the nodes, or null if there is none.
+ * null will only be returned if two or more of the nodes are from different
+ * documents.
+ */
+goog.dom.DomHelper.prototype.findCommonAncestor = goog.dom.findCommonAncestor;
+
+
+/**
+ * Returns the owner document for a node.
+ * @param {Node} node The node to get the document for.
+ * @return {!Document} The document owning the node.
+ */
+goog.dom.DomHelper.prototype.getOwnerDocument = goog.dom.getOwnerDocument;
+
+
+/**
+ * Cross browser function for getting the document element of an iframe.
+ * @param {Element} iframe Iframe element.
+ * @return {!Document} The frame content document.
+ */
+goog.dom.DomHelper.prototype.getFrameContentDocument =
+ goog.dom.getFrameContentDocument;
+
+
+/**
+ * Cross browser function for getting the window of a frame or iframe.
+ * @param {Element} frame Frame element.
+ * @return {Window} The window associated with the given frame.
+ */
+goog.dom.DomHelper.prototype.getFrameContentWindow =
+ goog.dom.getFrameContentWindow;
+
+
+/**
+ * Sets the text content of a node, with cross-browser support.
+ * @param {Node} node The node to change the text content of.
+ * @param {string|number} text The value that should replace the node's content.
+ */
+goog.dom.DomHelper.prototype.setTextContent = goog.dom.setTextContent;
+
+
+/**
+ * Gets the outerHTML of a node, which islike innerHTML, except that it
+ * actually contains the HTML of the node itself.
+ * @param {Element} element The element to get the HTML of.
+ * @return {string} The outerHTML of the given element.
+ */
+goog.dom.DomHelper.prototype.getOuterHtml = goog.dom.getOuterHtml;
+
+
+/**
+ * Finds the first descendant node that matches the filter function. This does
+ * a depth first search.
+ * @param {Node} root The root of the tree to search.
+ * @param {function(Node) : boolean} p The filter function.
+ * @return {Node|undefined} The found node or undefined if none is found.
+ */
+goog.dom.DomHelper.prototype.findNode = goog.dom.findNode;
+
+
+/**
+ * Finds all the descendant nodes that matches the filter function. This does a
+ * depth first search.
+ * @param {Node} root The root of the tree to search.
+ * @param {function(Node) : boolean} p The filter function.
+ * @return {Array.<Node>} The found nodes or an empty array if none are found.
+ */
+goog.dom.DomHelper.prototype.findNodes = goog.dom.findNodes;
+
+
+/**
+ * Returns true if the element has a tab index that allows it to receive
+ * keyboard focus (tabIndex >= 0), false otherwise. Note that some elements
+ * natively support keyboard focus, even if they have no tab index.
+ * @param {Element} element Element to check.
+ * @return {boolean} Whether the element has a tab index that allows keyboard
+ * focus.
+ */
+goog.dom.DomHelper.prototype.isFocusableTabIndex = goog.dom.isFocusableTabIndex;
+
+
+/**
+ * Enables or disables keyboard focus support on the element via its tab index.
+ * Only elements for which {@link goog.dom.isFocusableTabIndex} returns true
+ * (or elements that natively support keyboard focus, like form elements) can
+ * receive keyboard focus. See http://go/tabindex for more info.
+ * @param {Element} element Element whose tab index is to be changed.
+ * @param {boolean} enable Whether to set or remove a tab index on the element
+ * that supports keyboard focus.
+ */
+goog.dom.DomHelper.prototype.setFocusableTabIndex =
+ goog.dom.setFocusableTabIndex;
+
+
+/**
+ * Returns true if the element can be focused, i.e. it has a tab index that
+ * allows it to receive keyboard focus (tabIndex >= 0), or it is an element
+ * that natively supports keyboard focus.
+ * @param {Element} element Element to check.
+ * @return {boolean} Whether the element allows keyboard focus.
+ */
+goog.dom.DomHelper.prototype.isFocusable = goog.dom.isFocusable;
+
+
+/**
+ * Returns the text contents of the current node, without markup. New lines are
+ * stripped and whitespace is collapsed, such that each character would be
+ * visible.
+ *
+ * In browsers that support it, innerText is used. Other browsers attempt to
+ * simulate it via node traversal. Line breaks are canonicalized in IE.
+ *
+ * @param {Node} node The node from which we are getting content.
+ * @return {string} The text content.
+ */
+goog.dom.DomHelper.prototype.getTextContent = goog.dom.getTextContent;
+
+
+/**
+ * Returns the text length of the text contained in a node, without markup. This
+ * is equivalent to the selection length if the node was selected, or the number
+ * of cursor movements to traverse the node. Images & BRs take one space. New
+ * lines are ignored.
+ *
+ * @param {Node} node The node whose text content length is being calculated.
+ * @return {number} The length of {@code node}'s text content.
+ */
+goog.dom.DomHelper.prototype.getNodeTextLength = goog.dom.getNodeTextLength;
+
+
+/**
+ * Returns the text offset of a node relative to one of its ancestors. The text
+ * length is the same as the length calculated by
+ * {@code goog.dom.getNodeTextLength}.
+ *
+ * @param {Node} node The node whose offset is being calculated.
+ * @param {Node=} opt_offsetParent Defaults to the node's owner document's body.
+ * @return {number} The text offset.
+ */
+goog.dom.DomHelper.prototype.getNodeTextOffset = goog.dom.getNodeTextOffset;
+
+
+/**
+ * Returns the node at a given offset in a parent node. If an object is
+ * provided for the optional third parameter, the node and the remainder of the
+ * offset will stored as properties of this object.
+ * @param {Node} parent The parent node.
+ * @param {number} offset The offset into the parent node.
+ * @param {Object=} opt_result Object to be used to store the return value. The
+ * return value will be stored in the form {node: Node, remainder: number}
+ * if this object is provided.
+ * @return {Node} The node at the given offset.
+ */
+goog.dom.DomHelper.prototype.getNodeAtOffset = goog.dom.getNodeAtOffset;
+
+
+/**
+ * Returns true if the object is a {@code NodeList}. To qualify as a NodeList,
+ * the object must have a numeric length property and an item function (which
+ * has type 'string' on IE for some reason).
+ * @param {Object} val Object to test.
+ * @return {boolean} Whether the object is a NodeList.
+ */
+goog.dom.DomHelper.prototype.isNodeList = goog.dom.isNodeList;
+
+
+/**
+ * Walks up the DOM hierarchy returning the first ancestor that has the passed
+ * tag name and/or class name. If the passed element matches the specified
+ * criteria, the element itself is returned.
+ * @param {Node} element The DOM node to start with.
+ * @param {?(goog.dom.TagName|string)=} opt_tag The tag name to match (or
+ * null/undefined to match only based on class name).
+ * @param {?string=} opt_class The class name to match (or null/undefined to
+ * match only based on tag name).
+ * @return {Element} The first ancestor that matches the passed criteria, or
+ * null if no match is found.
+ */
+goog.dom.DomHelper.prototype.getAncestorByTagNameAndClass =
+ goog.dom.getAncestorByTagNameAndClass;
+
+
+/**
+ * Walks up the DOM hierarchy returning the first ancestor that has the passed
+ * class name. If the passed element matches the specified criteria, the
+ * element itself is returned.
+ * @param {Node} element The DOM node to start with.
+ * @param {string} class The class name to match.
+ * @return {Element} The first ancestor that matches the passed criteria, or
+ * null if none match.
+ */
+goog.dom.DomHelper.prototype.getAncestorByClass =
+ goog.dom.getAncestorByClass;
+
+
+/**
+ * Walks up the DOM hierarchy returning the first ancestor that passes the
+ * matcher function.
+ * @param {Node} element The DOM node to start with.
+ * @param {function(Node) : boolean} matcher A function that returns true if the
+ * passed node matches the desired criteria.
+ * @param {boolean=} opt_includeNode If true, the node itself is included in
+ * the search (the first call to the matcher will pass startElement as
+ * the node to test).
+ * @param {number=} opt_maxSearchSteps Maximum number of levels to search up the
+ * dom.
+ * @return {Node} DOM node that matched the matcher, or null if there was
+ * no match.
+ */
+goog.dom.DomHelper.prototype.getAncestor = goog.dom.getAncestor;
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/dom/nodetype.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/dom/nodetype.js
new file mode 100644
index 0000000..cccb470
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/dom/nodetype.js
@@ -0,0 +1,48 @@
+// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Definition of goog.dom.NodeType.
+ */
+
+goog.provide('goog.dom.NodeType');
+
+
+/**
+ * Constants for the nodeType attribute in the Node interface.
+ *
+ * These constants match those specified in the Node interface. These are
+ * usually present on the Node object in recent browsers, but not in older
+ * browsers (specifically, early IEs) and thus are given here.
+ *
+ * In some browsers (early IEs), these are not defined on the Node object,
+ * so they are provided here.
+ *
+ * See http://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-1950641247
+ * @enum {number}
+ */
+goog.dom.NodeType = {
+ ELEMENT: 1,
+ ATTRIBUTE: 2,
+ TEXT: 3,
+ CDATA_SECTION: 4,
+ ENTITY_REFERENCE: 5,
+ ENTITY: 6,
+ PROCESSING_INSTRUCTION: 7,
+ COMMENT: 8,
+ DOCUMENT: 9,
+ DOCUMENT_TYPE: 10,
+ DOCUMENT_FRAGMENT: 11,
+ NOTATION: 12
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/dom/tagname.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/dom/tagname.js
new file mode 100644
index 0000000..77a9b47
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/dom/tagname.js
@@ -0,0 +1,159 @@
+// Copyright 2007 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Defines the goog.dom.TagName enum. This enumerates
+ * all HTML tag names specified in either the the W3C HTML 4.01 index of
+ * elements or the HTML5 draft specification.
+ *
+ * References:
+ * http://www.w3.org/TR/html401/index/elements.html
+ * http://dev.w3.org/html5/spec/section-index.html
+ *
+ */
+goog.provide('goog.dom.TagName');
+
+
+/**
+ * Enum of all html tag names specified by the W3C HTML4.01 and HTML5
+ * specifications.
+ * @enum {string}
+ */
+goog.dom.TagName = {
+ A: 'A',
+ ABBR: 'ABBR',
+ ACRONYM: 'ACRONYM',
+ ADDRESS: 'ADDRESS',
+ APPLET: 'APPLET',
+ AREA: 'AREA',
+ ARTICLE: 'ARTICLE',
+ ASIDE: 'ASIDE',
+ AUDIO: 'AUDIO',
+ B: 'B',
+ BASE: 'BASE',
+ BASEFONT: 'BASEFONT',
+ BDI: 'BDI',
+ BDO: 'BDO',
+ BIG: 'BIG',
+ BLOCKQUOTE: 'BLOCKQUOTE',
+ BODY: 'BODY',
+ BR: 'BR',
+ BUTTON: 'BUTTON',
+ CANVAS: 'CANVAS',
+ CAPTION: 'CAPTION',
+ CENTER: 'CENTER',
+ CITE: 'CITE',
+ CODE: 'CODE',
+ COL: 'COL',
+ COLGROUP: 'COLGROUP',
+ COMMAND: 'COMMAND',
+ DATA: 'DATA',
+ DATALIST: 'DATALIST',
+ DD: 'DD',
+ DEL: 'DEL',
+ DETAILS: 'DETAILS',
+ DFN: 'DFN',
+ DIALOG: 'DIALOG',
+ DIR: 'DIR',
+ DIV: 'DIV',
+ DL: 'DL',
+ DT: 'DT',
+ EM: 'EM',
+ EMBED: 'EMBED',
+ FIELDSET: 'FIELDSET',
+ FIGCAPTION: 'FIGCAPTION',
+ FIGURE: 'FIGURE',
+ FONT: 'FONT',
+ FOOTER: 'FOOTER',
+ FORM: 'FORM',
+ FRAME: 'FRAME',
+ FRAMESET: 'FRAMESET',
+ H1: 'H1',
+ H2: 'H2',
+ H3: 'H3',
+ H4: 'H4',
+ H5: 'H5',
+ H6: 'H6',
+ HEAD: 'HEAD',
+ HEADER: 'HEADER',
+ HGROUP: 'HGROUP',
+ HR: 'HR',
+ HTML: 'HTML',
+ I: 'I',
+ IFRAME: 'IFRAME',
+ IMG: 'IMG',
+ INPUT: 'INPUT',
+ INS: 'INS',
+ ISINDEX: 'ISINDEX',
+ KBD: 'KBD',
+ KEYGEN: 'KEYGEN',
+ LABEL: 'LABEL',
+ LEGEND: 'LEGEND',
+ LI: 'LI',
+ LINK: 'LINK',
+ MAP: 'MAP',
+ MARK: 'MARK',
+ MATH: 'MATH',
+ MENU: 'MENU',
+ META: 'META',
+ METER: 'METER',
+ NAV: 'NAV',
+ NOFRAMES: 'NOFRAMES',
+ NOSCRIPT: 'NOSCRIPT',
+ OBJECT: 'OBJECT',
+ OL: 'OL',
+ OPTGROUP: 'OPTGROUP',
+ OPTION: 'OPTION',
+ OUTPUT: 'OUTPUT',
+ P: 'P',
+ PARAM: 'PARAM',
+ PRE: 'PRE',
+ PROGRESS: 'PROGRESS',
+ Q: 'Q',
+ RP: 'RP',
+ RT: 'RT',
+ RUBY: 'RUBY',
+ S: 'S',
+ SAMP: 'SAMP',
+ SCRIPT: 'SCRIPT',
+ SECTION: 'SECTION',
+ SELECT: 'SELECT',
+ SMALL: 'SMALL',
+ SOURCE: 'SOURCE',
+ SPAN: 'SPAN',
+ STRIKE: 'STRIKE',
+ STRONG: 'STRONG',
+ STYLE: 'STYLE',
+ SUB: 'SUB',
+ SUMMARY: 'SUMMARY',
+ SUP: 'SUP',
+ SVG: 'SVG',
+ TABLE: 'TABLE',
+ TBODY: 'TBODY',
+ TD: 'TD',
+ TEXTAREA: 'TEXTAREA',
+ TFOOT: 'TFOOT',
+ TH: 'TH',
+ THEAD: 'THEAD',
+ TIME: 'TIME',
+ TITLE: 'TITLE',
+ TR: 'TR',
+ TRACK: 'TRACK',
+ TT: 'TT',
+ U: 'U',
+ UL: 'UL',
+ VAR: 'VAR',
+ VIDEO: 'VIDEO',
+ WBR: 'WBR'
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/dom/vendor.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/dom/vendor.js
new file mode 100644
index 0000000..7c1123e
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/dom/vendor.js
@@ -0,0 +1,96 @@
+// Copyright 2012 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Vendor prefix getters.
+ */
+
+goog.provide('goog.dom.vendor');
+
+goog.require('goog.string');
+goog.require('goog.userAgent');
+
+
+/**
+ * Returns the JS vendor prefix used in CSS properties. Different vendors
+ * use different methods of changing the case of the property names.
+ *
+ * @return {?string} The JS vendor prefix or null if there is none.
+ */
+goog.dom.vendor.getVendorJsPrefix = function() {
+ if (goog.userAgent.WEBKIT) {
+ return 'Webkit';
+ } else if (goog.userAgent.GECKO) {
+ return 'Moz';
+ } else if (goog.userAgent.IE) {
+ return 'ms';
+ } else if (goog.userAgent.OPERA) {
+ return 'O';
+ }
+
+ return null;
+};
+
+
+/**
+ * Returns the vendor prefix used in CSS properties.
+ *
+ * @return {?string} The vendor prefix or null if there is none.
+ */
+goog.dom.vendor.getVendorPrefix = function() {
+ if (goog.userAgent.WEBKIT) {
+ return '-webkit';
+ } else if (goog.userAgent.GECKO) {
+ return '-moz';
+ } else if (goog.userAgent.IE) {
+ return '-ms';
+ } else if (goog.userAgent.OPERA) {
+ return '-o';
+ }
+
+ return null;
+};
+
+
+/**
+ * @param {string} propertyName A property name.
+ * @param {!Object=} opt_object If provided, we verify if the property exists in
+ * the object.
+ * @return {?string} A vendor prefixed property name, or null if it does not
+ * exist.
+ */
+goog.dom.vendor.getPrefixedPropertyName = function(propertyName, opt_object) {
+ // We first check for a non-prefixed property, if available.
+ if (opt_object && propertyName in opt_object) {
+ return propertyName;
+ }
+ var prefix = goog.dom.vendor.getVendorJsPrefix();
+ if (prefix) {
+ prefix = prefix.toLowerCase();
+ var prefixedPropertyName = prefix + goog.string.toTitleCase(propertyName);
+ return (!goog.isDef(opt_object) || prefixedPropertyName in opt_object) ?
+ prefixedPropertyName : null;
+ }
+ return null;
+};
+
+
+/**
+ * @param {string} eventType An event type.
+ * @return {string} A lower-cased vendor prefixed event type.
+ */
+goog.dom.vendor.getPrefixedEventType = function(eventType) {
+ var prefix = goog.dom.vendor.getVendorJsPrefix() || '';
+ return (prefix + eventType).toLowerCase();
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/events/browserevent.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/browserevent.js
new file mode 100644
index 0000000..a04b31f
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/browserevent.js
@@ -0,0 +1,390 @@
+// Copyright 2005 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview A patched, standardized event object for browser events.
+ *
+ * <pre>
+ * The patched event object contains the following members:
+ * - type {string} Event type, e.g. 'click'
+ * - target {Object} The element that actually triggered the event
+ * - currentTarget {Object} The element the listener is attached to
+ * - relatedTarget {Object} For mouseover and mouseout, the previous object
+ * - offsetX {number} X-coordinate relative to target
+ * - offsetY {number} Y-coordinate relative to target
+ * - clientX {number} X-coordinate relative to viewport
+ * - clientY {number} Y-coordinate relative to viewport
+ * - screenX {number} X-coordinate relative to the edge of the screen
+ * - screenY {number} Y-coordinate relative to the edge of the screen
+ * - button {number} Mouse button. Use isButton() to test.
+ * - keyCode {number} Key-code
+ * - ctrlKey {boolean} Was ctrl key depressed
+ * - altKey {boolean} Was alt key depressed
+ * - shiftKey {boolean} Was shift key depressed
+ * - metaKey {boolean} Was meta key depressed
+ * - defaultPrevented {boolean} Whether the default action has been prevented
+ * - state {Object} History state object
+ *
+ * NOTE: The keyCode member contains the raw browser keyCode. For normalized
+ * key and character code use {@link goog.events.KeyHandler}.
+ * </pre>
+ *
+ */
+
+goog.provide('goog.events.BrowserEvent');
+goog.provide('goog.events.BrowserEvent.MouseButton');
+
+goog.require('goog.events.BrowserFeature');
+goog.require('goog.events.Event');
+goog.require('goog.events.EventType');
+goog.require('goog.reflect');
+goog.require('goog.userAgent');
+
+
+
+/**
+ * Accepts a browser event object and creates a patched, cross browser event
+ * object.
+ * The content of this object will not be initialized if no event object is
+ * provided. If this is the case, init() needs to be invoked separately.
+ * @param {Event=} opt_e Browser event object.
+ * @param {EventTarget=} opt_currentTarget Current target for event.
+ * @constructor
+ * @extends {goog.events.Event}
+ */
+goog.events.BrowserEvent = function(opt_e, opt_currentTarget) {
+ goog.events.BrowserEvent.base(this, 'constructor', opt_e ? opt_e.type : '');
+
+ /**
+ * Target that fired the event.
+ * @override
+ * @type {Node}
+ */
+ this.target = null;
+
+ /**
+ * Node that had the listener attached.
+ * @override
+ * @type {Node|undefined}
+ */
+ this.currentTarget = null;
+
+ /**
+ * For mouseover and mouseout events, the related object for the event.
+ * @type {Node}
+ */
+ this.relatedTarget = null;
+
+ /**
+ * X-coordinate relative to target.
+ * @type {number}
+ */
+ this.offsetX = 0;
+
+ /**
+ * Y-coordinate relative to target.
+ * @type {number}
+ */
+ this.offsetY = 0;
+
+ /**
+ * X-coordinate relative to the window.
+ * @type {number}
+ */
+ this.clientX = 0;
+
+ /**
+ * Y-coordinate relative to the window.
+ * @type {number}
+ */
+ this.clientY = 0;
+
+ /**
+ * X-coordinate relative to the monitor.
+ * @type {number}
+ */
+ this.screenX = 0;
+
+ /**
+ * Y-coordinate relative to the monitor.
+ * @type {number}
+ */
+ this.screenY = 0;
+
+ /**
+ * Which mouse button was pressed.
+ * @type {number}
+ */
+ this.button = 0;
+
+ /**
+ * Keycode of key press.
+ * @type {number}
+ */
+ this.keyCode = 0;
+
+ /**
+ * Keycode of key press.
+ * @type {number}
+ */
+ this.charCode = 0;
+
+ /**
+ * Whether control was pressed at time of event.
+ * @type {boolean}
+ */
+ this.ctrlKey = false;
+
+ /**
+ * Whether alt was pressed at time of event.
+ * @type {boolean}
+ */
+ this.altKey = false;
+
+ /**
+ * Whether shift was pressed at time of event.
+ * @type {boolean}
+ */
+ this.shiftKey = false;
+
+ /**
+ * Whether the meta key was pressed at time of event.
+ * @type {boolean}
+ */
+ this.metaKey = false;
+
+ /**
+ * History state object, only set for PopState events where it's a copy of the
+ * state object provided to pushState or replaceState.
+ * @type {Object}
+ */
+ this.state = null;
+
+ /**
+ * Whether the default platform modifier key was pressed at time of event.
+ * (This is control for all platforms except Mac, where it's Meta.)
+ * @type {boolean}
+ */
+ this.platformModifierKey = false;
+
+ /**
+ * The browser event object.
+ * @private {Event}
+ */
+ this.event_ = null;
+
+ if (opt_e) {
+ this.init(opt_e, opt_currentTarget);
+ }
+};
+goog.inherits(goog.events.BrowserEvent, goog.events.Event);
+
+
+/**
+ * Normalized button constants for the mouse.
+ * @enum {number}
+ */
+goog.events.BrowserEvent.MouseButton = {
+ LEFT: 0,
+ MIDDLE: 1,
+ RIGHT: 2
+};
+
+
+/**
+ * Static data for mapping mouse buttons.
+ * @type {!Array.<number>}
+ */
+goog.events.BrowserEvent.IEButtonMap = [
+ 1, // LEFT
+ 4, // MIDDLE
+ 2 // RIGHT
+];
+
+
+/**
+ * Accepts a browser event object and creates a patched, cross browser event
+ * object.
+ * @param {Event} e Browser event object.
+ * @param {EventTarget=} opt_currentTarget Current target for event.
+ */
+goog.events.BrowserEvent.prototype.init = function(e, opt_currentTarget) {
+ var type = this.type = e.type;
+
+ // TODO(nicksantos): Change this.target to type EventTarget.
+ this.target = /** @type {Node} */ (e.target) || e.srcElement;
+
+ // TODO(nicksantos): Change this.currentTarget to type EventTarget.
+ this.currentTarget = /** @type {Node} */ (opt_currentTarget);
+
+ var relatedTarget = /** @type {Node} */ (e.relatedTarget);
+ if (relatedTarget) {
+ // There's a bug in FireFox where sometimes, relatedTarget will be a
+ // chrome element, and accessing any property of it will get a permission
+ // denied exception. See:
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=497780
+ if (goog.userAgent.GECKO) {
+ if (!goog.reflect.canAccessProperty(relatedTarget, 'nodeName')) {
+ relatedTarget = null;
+ }
+ }
+ // TODO(arv): Use goog.events.EventType when it has been refactored into its
+ // own file.
+ } else if (type == goog.events.EventType.MOUSEOVER) {
+ relatedTarget = e.fromElement;
+ } else if (type == goog.events.EventType.MOUSEOUT) {
+ relatedTarget = e.toElement;
+ }
+
+ this.relatedTarget = relatedTarget;
+
+ // Webkit emits a lame warning whenever layerX/layerY is accessed.
+ // http://code.google.com/p/chromium/issues/detail?id=101733
+ this.offsetX = (goog.userAgent.WEBKIT || e.offsetX !== undefined) ?
+ e.offsetX : e.layerX;
+ this.offsetY = (goog.userAgent.WEBKIT || e.offsetY !== undefined) ?
+ e.offsetY : e.layerY;
+
+ this.clientX = e.clientX !== undefined ? e.clientX : e.pageX;
+ this.clientY = e.clientY !== undefined ? e.clientY : e.pageY;
+ this.screenX = e.screenX || 0;
+ this.screenY = e.screenY || 0;
+
+ this.button = e.button;
+
+ this.keyCode = e.keyCode || 0;
+ this.charCode = e.charCode || (type == 'keypress' ? e.keyCode : 0);
+ this.ctrlKey = e.ctrlKey;
+ this.altKey = e.altKey;
+ this.shiftKey = e.shiftKey;
+ this.metaKey = e.metaKey;
+ this.platformModifierKey = goog.userAgent.MAC ? e.metaKey : e.ctrlKey;
+ this.state = e.state;
+ this.event_ = e;
+ if (e.defaultPrevented) {
+ this.preventDefault();
+ }
+};
+
+
+/**
+ * Tests to see which button was pressed during the event. This is really only
+ * useful in IE and Gecko browsers. And in IE, it's only useful for
+ * mousedown/mouseup events, because click only fires for the left mouse button.
+ *
+ * Safari 2 only reports the left button being clicked, and uses the value '1'
+ * instead of 0. Opera only reports a mousedown event for the middle button, and
+ * no mouse events for the right button. Opera has default behavior for left and
+ * middle click that can only be overridden via a configuration setting.
+ *
+ * There's a nice table of this mess at http://www.unixpapa.com/js/mouse.html.
+ *
+ * @param {goog.events.BrowserEvent.MouseButton} button The button
+ * to test for.
+ * @return {boolean} True if button was pressed.
+ */
+goog.events.BrowserEvent.prototype.isButton = function(button) {
+ if (!goog.events.BrowserFeature.HAS_W3C_BUTTON) {
+ if (this.type == 'click') {
+ return button == goog.events.BrowserEvent.MouseButton.LEFT;
+ } else {
+ return !!(this.event_.button &
+ goog.events.BrowserEvent.IEButtonMap[button]);
+ }
+ } else {
+ return this.event_.button == button;
+ }
+};
+
+
+/**
+ * Whether this has an "action"-producing mouse button.
+ *
+ * By definition, this includes left-click on windows/linux, and left-click
+ * without the ctrl key on Macs.
+ *
+ * @return {boolean} The result.
+ */
+goog.events.BrowserEvent.prototype.isMouseActionButton = function() {
+ // Webkit does not ctrl+click to be a right-click, so we
+ // normalize it to behave like Gecko and Opera.
+ return this.isButton(goog.events.BrowserEvent.MouseButton.LEFT) &&
+ !(goog.userAgent.WEBKIT && goog.userAgent.MAC && this.ctrlKey);
+};
+
+
+/**
+ * @override
+ */
+goog.events.BrowserEvent.prototype.stopPropagation = function() {
+ goog.events.BrowserEvent.superClass_.stopPropagation.call(this);
+ if (this.event_.stopPropagation) {
+ this.event_.stopPropagation();
+ } else {
+ this.event_.cancelBubble = true;
+ }
+};
+
+
+/**
+ * @override
+ */
+goog.events.BrowserEvent.prototype.preventDefault = function() {
+ goog.events.BrowserEvent.superClass_.preventDefault.call(this);
+ var be = this.event_;
+ if (!be.preventDefault) {
+ be.returnValue = false;
+ if (goog.events.BrowserFeature.SET_KEY_CODE_TO_PREVENT_DEFAULT) {
+ /** @preserveTry */
+ try {
+ // Most keys can be prevented using returnValue. Some special keys
+ // require setting the keyCode to -1 as well:
+ //
+ // In IE7:
+ // F3, F5, F10, F11, Ctrl+P, Crtl+O, Ctrl+F (these are taken from IE6)
+ //
+ // In IE8:
+ // Ctrl+P, Crtl+O, Ctrl+F (F1-F12 cannot be stopped through the event)
+ //
+ // We therefore do this for all function keys as well as when Ctrl key
+ // is pressed.
+ var VK_F1 = 112;
+ var VK_F12 = 123;
+ if (be.ctrlKey || be.keyCode >= VK_F1 && be.keyCode <= VK_F12) {
+ be.keyCode = -1;
+ }
+ } catch (ex) {
+ // IE throws an 'access denied' exception when trying to change
+ // keyCode in some situations (e.g. srcElement is input[type=file],
+ // or srcElement is an anchor tag rewritten by parent's innerHTML).
+ // Do nothing in this case.
+ }
+ }
+ } else {
+ be.preventDefault();
+ }
+};
+
+
+/**
+ * @return {Event} The underlying browser event object.
+ */
+goog.events.BrowserEvent.prototype.getBrowserEvent = function() {
+ return this.event_;
+};
+
+
+/** @override */
+goog.events.BrowserEvent.prototype.disposeInternal = function() {
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/events/browserfeature.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/browserfeature.js
new file mode 100644
index 0000000..61b9d60
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/browserfeature.js
@@ -0,0 +1,85 @@
+// Copyright 2010 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Browser capability checks for the events package.
+ *
+ */
+
+
+goog.provide('goog.events.BrowserFeature');
+
+goog.require('goog.userAgent');
+
+
+/**
+ * Enum of browser capabilities.
+ * @enum {boolean}
+ */
+goog.events.BrowserFeature = {
+ /**
+ * Whether the button attribute of the event is W3C compliant. False in
+ * Internet Explorer prior to version 9; document-version dependent.
+ */
+ HAS_W3C_BUTTON: !goog.userAgent.IE ||
+ goog.userAgent.isDocumentModeOrHigher(9),
+
+ /**
+ * Whether the browser supports full W3C event model.
+ */
+ HAS_W3C_EVENT_SUPPORT: !goog.userAgent.IE ||
+ goog.userAgent.isDocumentModeOrHigher(9),
+
+ /**
+ * To prevent default in IE7-8 for certain keydown events we need set the
+ * keyCode to -1.
+ */
+ SET_KEY_CODE_TO_PREVENT_DEFAULT: goog.userAgent.IE &&
+ !goog.userAgent.isVersionOrHigher('9'),
+
+ /**
+ * Whether the {@code navigator.onLine} property is supported.
+ */
+ HAS_NAVIGATOR_ONLINE_PROPERTY: !goog.userAgent.WEBKIT ||
+ goog.userAgent.isVersionOrHigher('528'),
+
+ /**
+ * Whether HTML5 network online/offline events are supported.
+ */
+ HAS_HTML5_NETWORK_EVENT_SUPPORT:
+ goog.userAgent.GECKO && goog.userAgent.isVersionOrHigher('1.9b') ||
+ goog.userAgent.IE && goog.userAgent.isVersionOrHigher('8') ||
+ goog.userAgent.OPERA && goog.userAgent.isVersionOrHigher('9.5') ||
+ goog.userAgent.WEBKIT && goog.userAgent.isVersionOrHigher('528'),
+
+ /**
+ * Whether HTML5 network events fire on document.body, or otherwise the
+ * window.
+ */
+ HTML5_NETWORK_EVENTS_FIRE_ON_BODY:
+ goog.userAgent.GECKO && !goog.userAgent.isVersionOrHigher('8') ||
+ goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('9'),
+
+ /**
+ * Whether touch is enabled in the browser.
+ */
+ TOUCH_ENABLED:
+ ('ontouchstart' in goog.global ||
+ !!(goog.global['document'] &&
+ document.documentElement &&
+ 'ontouchstart' in document.documentElement) ||
+ // IE10 uses non-standard touch events, so it has a different check.
+ !!(goog.global['navigator'] &&
+ goog.global['navigator']['msMaxTouchPoints']))
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/events/event.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/event.js
new file mode 100644
index 0000000..0e55fdc
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/event.js
@@ -0,0 +1,161 @@
+// Copyright 2005 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview A base class for event objects.
+ *
+ */
+
+
+goog.provide('goog.events.Event');
+goog.provide('goog.events.EventLike');
+
+/**
+ * goog.events.Event no longer depends on goog.Disposable. Keep requiring
+ * goog.Disposable here to not break projects which assume this dependency.
+ * @suppress {extraRequire}
+ */
+goog.require('goog.Disposable');
+goog.require('goog.events.EventId');
+
+
+/**
+ * A typedef for event like objects that are dispatchable via the
+ * goog.events.dispatchEvent function. strings are treated as the type for a
+ * goog.events.Event. Objects are treated as an extension of a new
+ * goog.events.Event with the type property of the object being used as the type
+ * of the Event.
+ * @typedef {string|Object|goog.events.Event|goog.events.EventId}
+ */
+goog.events.EventLike;
+
+
+
+/**
+ * A base class for event objects, so that they can support preventDefault and
+ * stopPropagation.
+ *
+ * @param {string|!goog.events.EventId} type Event Type.
+ * @param {Object=} opt_target Reference to the object that is the target of
+ * this event. It has to implement the {@code EventTarget} interface
+ * declared at {@link http://developer.mozilla.org/en/DOM/EventTarget}.
+ * @constructor
+ */
+goog.events.Event = function(type, opt_target) {
+ /**
+ * Event type.
+ * @type {string}
+ */
+ this.type = type instanceof goog.events.EventId ? String(type) : type;
+
+ /**
+ * TODO(user): The type should probably be
+ * EventTarget|goog.events.EventTarget.
+ *
+ * Target of the event.
+ * @type {Object|undefined}
+ */
+ this.target = opt_target;
+
+ /**
+ * Object that had the listener attached.
+ * @type {Object|undefined}
+ */
+ this.currentTarget = this.target;
+
+ /**
+ * Whether to cancel the event in internal capture/bubble processing for IE.
+ * @type {boolean}
+ * @public
+ * @suppress {underscore|visibility} Technically public, but referencing this
+ * outside this package is strongly discouraged.
+ */
+ this.propagationStopped_ = false;
+
+ /**
+ * Whether the default action has been prevented.
+ * This is a property to match the W3C specification at
+ * {@link http://www.w3.org/TR/DOM-Level-3-Events/
+ * #events-event-type-defaultPrevented}.
+ * Must be treated as read-only outside the class.
+ * @type {boolean}
+ */
+ this.defaultPrevented = false;
+
+ /**
+ * Return value for in internal capture/bubble processing for IE.
+ * @type {boolean}
+ * @public
+ * @suppress {underscore|visibility} Technically public, but referencing this
+ * outside this package is strongly discouraged.
+ */
+ this.returnValue_ = true;
+};
+
+
+/**
+ * For backwards compatibility (goog.events.Event used to inherit
+ * goog.Disposable).
+ * @deprecated Events don't need to be disposed.
+ */
+goog.events.Event.prototype.disposeInternal = function() {
+};
+
+
+/**
+ * For backwards compatibility (goog.events.Event used to inherit
+ * goog.Disposable).
+ * @deprecated Events don't need to be disposed.
+ */
+goog.events.Event.prototype.dispose = function() {
+};
+
+
+/**
+ * Stops event propagation.
+ */
+goog.events.Event.prototype.stopPropagation = function() {
+ this.propagationStopped_ = true;
+};
+
+
+/**
+ * Prevents the default action, for example a link redirecting to a url.
+ */
+goog.events.Event.prototype.preventDefault = function() {
+ this.defaultPrevented = true;
+ this.returnValue_ = false;
+};
+
+
+/**
+ * Stops the propagation of the event. It is equivalent to
+ * {@code e.stopPropagation()}, but can be used as the callback argument of
+ * {@link goog.events.listen} without declaring another function.
+ * @param {!goog.events.Event} e An event.
+ */
+goog.events.Event.stopPropagation = function(e) {
+ e.stopPropagation();
+};
+
+
+/**
+ * Prevents the default action. It is equivalent to
+ * {@code e.preventDefault()}, but can be used as the callback argument of
+ * {@link goog.events.listen} without declaring another function.
+ * @param {!goog.events.Event} e An event.
+ */
+goog.events.Event.preventDefault = function(e) {
+ e.preventDefault();
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/events/eventhandler.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/eventhandler.js
new file mode 100644
index 0000000..70071f9
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/eventhandler.js
@@ -0,0 +1,461 @@
+// Copyright 2005 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Class to create objects which want to handle multiple events
+ * and have their listeners easily cleaned up via a dispose method.
+ *
+ * Example:
+ * <pre>
+ * function Something() {
+ * Something.base(this);
+ *
+ * ... set up object ...
+ *
+ * // Add event listeners
+ * this.listen(this.starEl, goog.events.EventType.CLICK, this.handleStar);
+ * this.listen(this.headerEl, goog.events.EventType.CLICK, this.expand);
+ * this.listen(this.collapseEl, goog.events.EventType.CLICK, this.collapse);
+ * this.listen(this.infoEl, goog.events.EventType.MOUSEOVER, this.showHover);
+ * this.listen(this.infoEl, goog.events.EventType.MOUSEOUT, this.hideHover);
+ * }
+ * goog.inherits(Something, goog.events.EventHandler);
+ *
+ * Something.prototype.disposeInternal = function() {
+ * Something.base(this, 'disposeInternal');
+ * goog.dom.removeNode(this.container);
+ * };
+ *
+ *
+ * // Then elsewhere:
+ *
+ * var activeSomething = null;
+ * function openSomething() {
+ * activeSomething = new Something();
+ * }
+ *
+ * function closeSomething() {
+ * if (activeSomething) {
+ * activeSomething.dispose(); // Remove event listeners
+ * activeSomething = null;
+ * }
+ * }
+ * </pre>
+ *
+ */
+
+goog.provide('goog.events.EventHandler');
+
+goog.require('goog.Disposable');
+goog.require('goog.events');
+goog.require('goog.object');
+
+goog.forwardDeclare('goog.events.EventWrapper');
+
+
+
+/**
+ * Super class for objects that want to easily manage a number of event
+ * listeners. It allows a short cut to listen and also provides a quick way
+ * to remove all events listeners belonging to this object.
+ * @param {SCOPE=} opt_scope Object in whose scope to call the listeners.
+ * @constructor
+ * @extends {goog.Disposable}
+ * @template SCOPE
+ */
+goog.events.EventHandler = function(opt_scope) {
+ goog.Disposable.call(this);
+ // TODO(user): Rename this to this.scope_ and fix the classes in google3
+ // that access this private variable. :(
+ this.handler_ = opt_scope;
+
+ /**
+ * Keys for events that are being listened to.
+ * @type {!Object.<!goog.events.Key>}
+ * @private
+ */
+ this.keys_ = {};
+};
+goog.inherits(goog.events.EventHandler, goog.Disposable);
+
+
+/**
+ * Utility array used to unify the cases of listening for an array of types
+ * and listening for a single event, without using recursion or allocating
+ * an array each time.
+ * @type {!Array.<string>}
+ * @const
+ * @private
+ */
+goog.events.EventHandler.typeArray_ = [];
+
+
+/**
+ * Listen to an event on a Listenable. If the function is omitted then the
+ * EventHandler's handleEvent method will be used.
+ * @param {goog.events.ListenableType} src Event source.
+ * @param {string|Array.<string>|
+ * !goog.events.EventId.<EVENTOBJ>|!Array.<!goog.events.EventId.<EVENTOBJ>>}
+ * type Event type to listen for or array of event types.
+ * @param {function(this:SCOPE, EVENTOBJ):?|{handleEvent:function(?):?}|null=}
+ * opt_fn Optional callback function to be used as the listener or an object
+ * with handleEvent function.
+ * @param {boolean=} opt_capture Optional whether to use capture phase.
+ * @return {!goog.events.EventHandler.<SCOPE>} This object, allowing for
+ * chaining of calls.
+ * @template EVENTOBJ
+ */
+goog.events.EventHandler.prototype.listen = function(
+ src, type, opt_fn, opt_capture) {
+ return this.listen_(src, type, opt_fn, opt_capture);
+};
+
+
+/**
+ * Listen to an event on a Listenable. If the function is omitted then the
+ * EventHandler's handleEvent method will be used.
+ * @param {goog.events.ListenableType} src Event source.
+ * @param {string|Array.<string>|
+ * !goog.events.EventId.<EVENTOBJ>|!Array.<!goog.events.EventId.<EVENTOBJ>>}
+ * type Event type to listen for or array of event types.
+ * @param {function(this:T, EVENTOBJ):?|{handleEvent:function(this:T, ?):?}|
+ * null|undefined} fn Optional callback function to be used as the
+ * listener or an object with handleEvent function.
+ * @param {boolean|undefined} capture Optional whether to use capture phase.
+ * @param {T} scope Object in whose scope to call the listener.
+ * @return {!goog.events.EventHandler.<SCOPE>} This object, allowing for
+ * chaining of calls.
+ * @template T,EVENTOBJ
+ */
+goog.events.EventHandler.prototype.listenWithScope = function(
+ src, type, fn, capture, scope) {
+ // TODO(user): Deprecate this function.
+ return this.listen_(src, type, fn, capture, scope);
+};
+
+
+/**
+ * Listen to an event on a Listenable. If the function is omitted then the
+ * EventHandler's handleEvent method will be used.
+ * @param {goog.events.ListenableType} src Event source.
+ * @param {string|Array.<string>|
+ * !goog.events.EventId.<EVENTOBJ>|!Array.<!goog.events.EventId.<EVENTOBJ>>}
+ * type Event type to listen for or array of event types.
+ * @param {function(EVENTOBJ):?|{handleEvent:function(?):?}|null=} opt_fn
+ * Optional callback function to be used as the listener or an object with
+ * handleEvent function.
+ * @param {boolean=} opt_capture Optional whether to use capture phase.
+ * @param {Object=} opt_scope Object in whose scope to call the listener.
+ * @return {!goog.events.EventHandler.<SCOPE>} This object, allowing for
+ * chaining of calls.
+ * @template EVENTOBJ
+ * @private
+ */
+goog.events.EventHandler.prototype.listen_ = function(src, type, opt_fn,
+ opt_capture,
+ opt_scope) {
+ if (!goog.isArray(type)) {
+ if (type) {
+ goog.events.EventHandler.typeArray_[0] = type.toString();
+ }
+ type = goog.events.EventHandler.typeArray_;
+ }
+ for (var i = 0; i < type.length; i++) {
+ var listenerObj = goog.events.listen(
+ src, type[i], opt_fn || this.handleEvent,
+ opt_capture || false,
+ opt_scope || this.handler_ || this);
+
+ if (!listenerObj) {
+ // When goog.events.listen run on OFF_AND_FAIL or OFF_AND_SILENT
+ // (goog.events.CaptureSimulationMode) in IE8-, it will return null
+ // value.
+ return this;
+ }
+
+ var key = listenerObj.key;
+ this.keys_[key] = listenerObj;
+ }
+
+ return this;
+};
+
+
+/**
+ * Listen to an event on a Listenable. If the function is omitted, then the
+ * EventHandler's handleEvent method will be used. After the event has fired the
+ * event listener is removed from the target. If an array of event types is
+ * provided, each event type will be listened to once.
+ * @param {goog.events.ListenableType} src Event source.
+ * @param {string|Array.<string>|
+ * !goog.events.EventId.<EVENTOBJ>|!Array.<!goog.events.EventId.<EVENTOBJ>>}
+ * type Event type to listen for or array of event types.
+ * @param {function(this:SCOPE, EVENTOBJ):?|{handleEvent:function(?):?}|null=} opt_fn
+ * Optional callback function to be used as the listener or an object with
+ * handleEvent function.
+ * @param {boolean=} opt_capture Optional whether to use capture phase.
+ * @return {!goog.events.EventHandler.<SCOPE>} This object, allowing for
+ * chaining of calls.
+ * @template EVENTOBJ
+ */
+goog.events.EventHandler.prototype.listenOnce = function(
+ src, type, opt_fn, opt_capture) {
+ // TODO(user): Remove the opt_scope from this function and then
+ // templatize it.
+ return this.listenOnce_(src, type, opt_fn, opt_capture);
+};
+
+
+/**
+ * Listen to an event on a Listenable. If the function is omitted, then the
+ * EventHandler's handleEvent method will be used. After the event has fired the
+ * event listener is removed from the target. If an array of event types is
+ * provided, each event type will be listened to once.
+ * @param {goog.events.ListenableType} src Event source.
+ * @param {string|Array.<string>|
+ * !goog.events.EventId.<EVENTOBJ>|!Array.<!goog.events.EventId.<EVENTOBJ>>}
+ * type Event type to listen for or array of event types.
+ * @param {function(this:T, EVENTOBJ):?|{handleEvent:function(this:T, ?):?}|
+ * null|undefined} fn Optional callback function to be used as the
+ * listener or an object with handleEvent function.
+ * @param {boolean|undefined} capture Optional whether to use capture phase.
+ * @param {T} scope Object in whose scope to call the listener.
+ * @return {!goog.events.EventHandler.<SCOPE>} This object, allowing for
+ * chaining of calls.
+ * @template T,EVENTOBJ
+ */
+goog.events.EventHandler.prototype.listenOnceWithScope = function(
+ src, type, fn, capture, scope) {
+ // TODO(user): Deprecate this function.
+ return this.listenOnce_(src, type, fn, capture, scope);
+};
+
+
+/**
+ * Listen to an event on a Listenable. If the function is omitted, then the
+ * EventHandler's handleEvent method will be used. After the event has fired
+ * the event listener is removed from the target. If an array of event types is
+ * provided, each event type will be listened to once.
+ * @param {goog.events.ListenableType} src Event source.
+ * @param {string|Array.<string>|
+ * !goog.events.EventId.<EVENTOBJ>|!Array.<!goog.events.EventId.<EVENTOBJ>>}
+ * type Event type to listen for or array of event types.
+ * @param {function(EVENTOBJ):?|{handleEvent:function(?):?}|null=} opt_fn
+ * Optional callback function to be used as the listener or an object with
+ * handleEvent function.
+ * @param {boolean=} opt_capture Optional whether to use capture phase.
+ * @param {Object=} opt_scope Object in whose scope to call the listener.
+ * @return {!goog.events.EventHandler.<SCOPE>} This object, allowing for
+ * chaining of calls.
+ * @template EVENTOBJ
+ * @private
+ */
+goog.events.EventHandler.prototype.listenOnce_ = function(
+ src, type, opt_fn, opt_capture, opt_scope) {
+ if (goog.isArray(type)) {
+ for (var i = 0; i < type.length; i++) {
+ this.listenOnce_(src, type[i], opt_fn, opt_capture, opt_scope);
+ }
+ } else {
+ var listenerObj = goog.events.listenOnce(
+ src, type, opt_fn || this.handleEvent, opt_capture,
+ opt_scope || this.handler_ || this);
+ if (!listenerObj) {
+ // When goog.events.listen run on OFF_AND_FAIL or OFF_AND_SILENT
+ // (goog.events.CaptureSimulationMode) in IE8-, it will return null
+ // value.
+ return this;
+ }
+
+ var key = listenerObj.key;
+ this.keys_[key] = listenerObj;
+ }
+
+ return this;
+};
+
+
+/**
+ * Adds an event listener with a specific event wrapper on a DOM Node or an
+ * object that has implemented {@link goog.events.EventTarget}. A listener can
+ * only be added once to an object.
+ *
+ * @param {EventTarget|goog.events.EventTarget} src The node to listen to
+ * events on.
+ * @param {goog.events.EventWrapper} wrapper Event wrapper to use.
+ * @param {function(this:SCOPE, ?):?|{handleEvent:function(?):?}|null} listener
+ * Callback method, or an object with a handleEvent function.
+ * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
+ * false).
+ * @return {!goog.events.EventHandler.<SCOPE>} This object, allowing for
+ * chaining of calls.
+ */
+goog.events.EventHandler.prototype.listenWithWrapper = function(
+ src, wrapper, listener, opt_capt) {
+ // TODO(user): Remove the opt_scope from this function and then
+ // templatize it.
+ return this.listenWithWrapper_(src, wrapper, listener, opt_capt);
+};
+
+
+/**
+ * Adds an event listener with a specific event wrapper on a DOM Node or an
+ * object that has implemented {@link goog.events.EventTarget}. A listener can
+ * only be added once to an object.
+ *
+ * @param {EventTarget|goog.events.EventTarget} src The node to listen to
+ * events on.
+ * @param {goog.events.EventWrapper} wrapper Event wrapper to use.
+ * @param {function(this:T, ?):?|{handleEvent:function(this:T, ?):?}|null}
+ * listener Optional callback function to be used as the
+ * listener or an object with handleEvent function.
+ * @param {boolean|undefined} capture Optional whether to use capture phase.
+ * @param {T} scope Object in whose scope to call the listener.
+ * @return {!goog.events.EventHandler.<SCOPE>} This object, allowing for
+ * chaining of calls.
+ * @template T
+ */
+goog.events.EventHandler.prototype.listenWithWrapperAndScope = function(
+ src, wrapper, listener, capture, scope) {
+ // TODO(user): Deprecate this function.
+ return this.listenWithWrapper_(src, wrapper, listener, capture, scope);
+};
+
+
+/**
+ * Adds an event listener with a specific event wrapper on a DOM Node or an
+ * object that has implemented {@link goog.events.EventTarget}. A listener can
+ * only be added once to an object.
+ *
+ * @param {EventTarget|goog.events.EventTarget} src The node to listen to
+ * events on.
+ * @param {goog.events.EventWrapper} wrapper Event wrapper to use.
+ * @param {function(?):?|{handleEvent:function(?):?}|null} listener Callback
+ * method, or an object with a handleEvent function.
+ * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
+ * false).
+ * @param {Object=} opt_scope Element in whose scope to call the listener.
+ * @return {!goog.events.EventHandler.<SCOPE>} This object, allowing for
+ * chaining of calls.
+ * @private
+ */
+goog.events.EventHandler.prototype.listenWithWrapper_ = function(
+ src, wrapper, listener, opt_capt, opt_scope) {
+ wrapper.listen(src, listener, opt_capt, opt_scope || this.handler_ || this,
+ this);
+ return this;
+};
+
+
+/**
+ * @return {number} Number of listeners registered by this handler.
+ */
+goog.events.EventHandler.prototype.getListenerCount = function() {
+ var count = 0;
+ for (var key in this.keys_) {
+ if (Object.prototype.hasOwnProperty.call(this.keys_, key)) {
+ count++;
+ }
+ }
+ return count;
+};
+
+
+/**
+ * Unlistens on an event.
+ * @param {goog.events.ListenableType} src Event source.
+ * @param {string|Array.<string>|
+ * !goog.events.EventId.<EVENTOBJ>|!Array.<!goog.events.EventId.<EVENTOBJ>>}
+ * type Event type or array of event types to unlisten to.
+ * @param {function(EVENTOBJ):?|{handleEvent:function(?):?}|null=} opt_fn
+ * Optional callback function to be used as the listener or an object with
+ * handleEvent function.
+ * @param {boolean=} opt_capture Optional whether to use capture phase.
+ * @param {Object=} opt_scope Object in whose scope to call the listener.
+ * @return {!goog.events.EventHandler} This object, allowing for chaining of
+ * calls.
+ * @template EVENTOBJ
+ */
+goog.events.EventHandler.prototype.unlisten = function(src, type, opt_fn,
+ opt_capture,
+ opt_scope) {
+ if (goog.isArray(type)) {
+ for (var i = 0; i < type.length; i++) {
+ this.unlisten(src, type[i], opt_fn, opt_capture, opt_scope);
+ }
+ } else {
+ var listener = goog.events.getListener(src, type,
+ opt_fn || this.handleEvent,
+ opt_capture, opt_scope || this.handler_ || this);
+
+ if (listener) {
+ goog.events.unlistenByKey(listener);
+ delete this.keys_[listener.key];
+ }
+ }
+
+ return this;
+};
+
+
+/**
+ * Removes an event listener which was added with listenWithWrapper().
+ *
+ * @param {EventTarget|goog.events.EventTarget} src The target to stop
+ * listening to events on.
+ * @param {goog.events.EventWrapper} wrapper Event wrapper to use.
+ * @param {function(?):?|{handleEvent:function(?):?}|null} listener The
+ * listener function to remove.
+ * @param {boolean=} opt_capt In DOM-compliant browsers, this determines
+ * whether the listener is fired during the capture or bubble phase of the
+ * event.
+ * @param {Object=} opt_scope Element in whose scope to call the listener.
+ * @return {!goog.events.EventHandler} This object, allowing for chaining of
+ * calls.
+ */
+goog.events.EventHandler.prototype.unlistenWithWrapper = function(src, wrapper,
+ listener, opt_capt, opt_scope) {
+ wrapper.unlisten(src, listener, opt_capt,
+ opt_scope || this.handler_ || this, this);
+ return this;
+};
+
+
+/**
+ * Unlistens to all events.
+ */
+goog.events.EventHandler.prototype.removeAll = function() {
+ goog.object.forEach(this.keys_, goog.events.unlistenByKey);
+ this.keys_ = {};
+};
+
+
+/**
+ * Disposes of this EventHandler and removes all listeners that it registered.
+ * @override
+ * @protected
+ */
+goog.events.EventHandler.prototype.disposeInternal = function() {
+ goog.events.EventHandler.superClass_.disposeInternal.call(this);
+ this.removeAll();
+};
+
+
+/**
+ * Default event handler
+ * @param {goog.events.Event} e Event object.
+ */
+goog.events.EventHandler.prototype.handleEvent = function(e) {
+ throw Error('EventHandler.handleEvent not implemented');
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/events/eventid.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/eventid.js
new file mode 100644
index 0000000..f7ea4dd
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/eventid.js
@@ -0,0 +1,47 @@
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+goog.provide('goog.events.EventId');
+
+
+
+/**
+ * A templated class that is used when registering for events. Typical usage:
+ * <code>
+ * /** @type {goog.events.EventId.<MyEventObj>}
+ * var myEventId = new goog.events.EventId(
+ * goog.events.getUniqueId(('someEvent'));
+ *
+ * // No need to cast or declare here since the compiler knows the correct
+ * // type of 'evt' (MyEventObj).
+ * something.listen(myEventId, function(evt) {});
+ * </code>
+ *
+ * @param {string} eventId
+ * @template T
+ * @constructor
+ * @struct
+ * @final
+ */
+goog.events.EventId = function(eventId) {
+ /** @const */ this.id = eventId;
+};
+
+
+/**
+ * @override
+ */
+goog.events.EventId.prototype.toString = function() {
+ return this.id;
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/events/events.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/events.js
new file mode 100644
index 0000000..e39d3bd
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/events.js
@@ -0,0 +1,995 @@
+// Copyright 2005 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview An event manager for both native browser event
+ * targets and custom JavaScript event targets
+ * ({@code goog.events.Listenable}). This provides an abstraction
+ * over browsers' event systems.
+ *
+ * It also provides a simulation of W3C event model's capture phase in
+ * Internet Explorer (IE 8 and below). Caveat: the simulation does not
+ * interact well with listeners registered directly on the elements
+ * (bypassing goog.events) or even with listeners registered via
+ * goog.events in a separate JS binary. In these cases, we provide
+ * no ordering guarantees.
+ *
+ * The listeners will receive a "patched" event object. Such event object
+ * contains normalized values for certain event properties that differs in
+ * different browsers.
+ *
+ * Example usage:
+ * <pre>
+ * goog.events.listen(myNode, 'click', function(e) { alert('woo') });
+ * goog.events.listen(myNode, 'mouseover', mouseHandler, true);
+ * goog.events.unlisten(myNode, 'mouseover', mouseHandler, true);
+ * goog.events.removeAll(myNode);
+ * </pre>
+ *
+ * in IE and event object patching]
+ *
+ * @see ../demos/events.html
+ * @see ../demos/event-propagation.html
+ * @see ../demos/stopevent.html
+ */
+
+// IMPLEMENTATION NOTES:
+// goog.events stores an auxiliary data structure on each EventTarget
+// source being listened on. This allows us to take advantage of GC,
+// having the data structure GC'd when the EventTarget is GC'd. This
+// GC behavior is equivalent to using W3C DOM Events directly.
+
+goog.provide('goog.events');
+goog.provide('goog.events.CaptureSimulationMode');
+goog.provide('goog.events.Key');
+goog.provide('goog.events.ListenableType');
+
+goog.require('goog.asserts');
+goog.require('goog.debug.entryPointRegistry');
+goog.require('goog.events.BrowserEvent');
+goog.require('goog.events.BrowserFeature');
+goog.require('goog.events.Listenable');
+goog.require('goog.events.ListenerMap');
+
+goog.forwardDeclare('goog.debug.ErrorHandler');
+goog.forwardDeclare('goog.events.EventWrapper');
+
+
+/**
+ * @typedef {number|goog.events.ListenableKey}
+ */
+goog.events.Key;
+
+
+/**
+ * @typedef {EventTarget|goog.events.Listenable}
+ */
+goog.events.ListenableType;
+
+
+/**
+ * Property name on a native event target for the listener map
+ * associated with the event target.
+ * @const
+ * @private
+ */
+goog.events.LISTENER_MAP_PROP_ = 'closure_lm_' + ((Math.random() * 1e6) | 0);
+
+
+/**
+ * String used to prepend to IE event types.
+ * @const
+ * @private
+ */
+goog.events.onString_ = 'on';
+
+
+/**
+ * Map of computed "on<eventname>" strings for IE event types. Caching
+ * this removes an extra object allocation in goog.events.listen which
+ * improves IE6 performance.
+ * @const
+ * @dict
+ * @private
+ */
+goog.events.onStringMap_ = {};
+
+
+/**
+ * @enum {number} Different capture simulation mode for IE8-.
+ */
+goog.events.CaptureSimulationMode = {
+ /**
+ * Does not perform capture simulation. Will asserts in IE8- when you
+ * add capture listeners.
+ */
+ OFF_AND_FAIL: 0,
+
+ /**
+ * Does not perform capture simulation, silently ignore capture
+ * listeners.
+ */
+ OFF_AND_SILENT: 1,
+
+ /**
+ * Performs capture simulation.
+ */
+ ON: 2
+};
+
+
+/**
+ * @define {number} The capture simulation mode for IE8-. By default,
+ * this is ON.
+ */
+goog.define('goog.events.CAPTURE_SIMULATION_MODE', 2);
+
+
+/**
+ * Estimated count of total native listeners.
+ * @private {number}
+ */
+goog.events.listenerCountEstimate_ = 0;
+
+
+/**
+ * Adds an event listener for a specific event on a native event
+ * target (such as a DOM element) or an object that has implemented
+ * {@link goog.events.Listenable}. A listener can only be added once
+ * to an object and if it is added again the key for the listener is
+ * returned. Note that if the existing listener is a one-off listener
+ * (registered via listenOnce), it will no longer be a one-off
+ * listener after a call to listen().
+ *
+ * @param {EventTarget|goog.events.Listenable} src The node to listen
+ * to events on.
+ * @param {string|Array.<string>|
+ * !goog.events.EventId.<EVENTOBJ>|!Array.<!goog.events.EventId.<EVENTOBJ>>}
+ * type Event type or array of event types.
+ * @param {function(this:T, EVENTOBJ):?|{handleEvent:function(?):?}|null}
+ * listener Callback method, or an object with a handleEvent function.
+ * WARNING: passing an Object is now softly deprecated.
+ * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
+ * false).
+ * @param {T=} opt_handler Element in whose scope to call the listener.
+ * @return {goog.events.Key} Unique key for the listener.
+ * @template T,EVENTOBJ
+ */
+goog.events.listen = function(src, type, listener, opt_capt, opt_handler) {
+ if (goog.isArray(type)) {
+ for (var i = 0; i < type.length; i++) {
+ goog.events.listen(src, type[i], listener, opt_capt, opt_handler);
+ }
+ return null;
+ }
+
+ listener = goog.events.wrapListener(listener);
+ if (goog.events.Listenable.isImplementedBy(src)) {
+ return src.listen(
+ /** @type {string|!goog.events.EventId} */ (type),
+ listener, opt_capt, opt_handler);
+ } else {
+ return goog.events.listen_(
+ /** @type {EventTarget} */ (src),
+ /** @type {string|!goog.events.EventId} */ (type),
+ listener, /* callOnce */ false, opt_capt, opt_handler);
+ }
+};
+
+
+/**
+ * Adds an event listener for a specific event on a native event
+ * target. A listener can only be added once to an object and if it
+ * is added again the key for the listener is returned.
+ *
+ * Note that a one-off listener will not change an existing listener,
+ * if any. On the other hand a normal listener will change existing
+ * one-off listener to become a normal listener.
+ *
+ * @param {EventTarget} src The node to listen to events on.
+ * @param {string|!goog.events.EventId} type Event type.
+ * @param {!Function} listener Callback function.
+ * @param {boolean} callOnce Whether the listener is a one-off
+ * listener or otherwise.
+ * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
+ * false).
+ * @param {Object=} opt_handler Element in whose scope to call the listener.
+ * @return {goog.events.ListenableKey} Unique key for the listener.
+ * @private
+ */
+goog.events.listen_ = function(
+ src, type, listener, callOnce, opt_capt, opt_handler) {
+ if (!type) {
+ throw Error('Invalid event type');
+ }
+
+ var capture = !!opt_capt;
+ if (capture && !goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT) {
+ if (goog.events.CAPTURE_SIMULATION_MODE ==
+ goog.events.CaptureSimulationMode.OFF_AND_FAIL) {
+ goog.asserts.fail('Can not register capture listener in IE8-.');
+ return null;
+ } else if (goog.events.CAPTURE_SIMULATION_MODE ==
+ goog.events.CaptureSimulationMode.OFF_AND_SILENT) {
+ return null;
+ }
+ }
+
+ var listenerMap = goog.events.getListenerMap_(src);
+ if (!listenerMap) {
+ src[goog.events.LISTENER_MAP_PROP_] = listenerMap =
+ new goog.events.ListenerMap(src);
+ }
+
+ var listenerObj = listenerMap.add(
+ type, listener, callOnce, opt_capt, opt_handler);
+
+ // If the listenerObj already has a proxy, it has been set up
+ // previously. We simply return.
+ if (listenerObj.proxy) {
+ return listenerObj;
+ }
+
+ var proxy = goog.events.getProxy();
+ listenerObj.proxy = proxy;
+
+ proxy.src = src;
+ proxy.listener = listenerObj;
+
+ // Attach the proxy through the browser's API
+ if (src.addEventListener) {
+ src.addEventListener(type.toString(), proxy, capture);
+ } else {
+ // The else above used to be else if (src.attachEvent) and then there was
+ // another else statement that threw an exception warning the developer
+ // they made a mistake. This resulted in an extra object allocation in IE6
+ // due to a wrapper object that had to be implemented around the element
+ // and so was removed.
+ src.attachEvent(goog.events.getOnString_(type.toString()), proxy);
+ }
+
+ goog.events.listenerCountEstimate_++;
+ return listenerObj;
+};
+
+
+/**
+ * Helper function for returning a proxy function.
+ * @return {!Function} A new or reused function object.
+ */
+goog.events.getProxy = function() {
+ var proxyCallbackFunction = goog.events.handleBrowserEvent_;
+ // Use a local var f to prevent one allocation.
+ var f = goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT ?
+ function(eventObject) {
+ return proxyCallbackFunction.call(f.src, f.listener, eventObject);
+ } :
+ function(eventObject) {
+ var v = proxyCallbackFunction.call(f.src, f.listener, eventObject);
+ // NOTE(user): In IE, we hack in a capture phase. However, if
+ // there is inline event handler which tries to prevent default (for
+ // example <a href="..." onclick="return false">...</a>) in a
+ // descendant element, the prevent default will be overridden
+ // by this listener if this listener were to return true. Hence, we
+ // return undefined.
+ if (!v) return v;
+ };
+ return f;
+};
+
+
+/**
+ * Adds an event listener for a specific event on a native event
+ * target (such as a DOM element) or an object that has implemented
+ * {@link goog.events.Listenable}. After the event has fired the event
+ * listener is removed from the target.
+ *
+ * If an existing listener already exists, listenOnce will do
+ * nothing. In particular, if the listener was previously registered
+ * via listen(), listenOnce() will not turn the listener into a
+ * one-off listener. Similarly, if there is already an existing
+ * one-off listener, listenOnce does not modify the listeners (it is
+ * still a once listener).
+ *
+ * @param {EventTarget|goog.events.Listenable} src The node to listen
+ * to events on.
+ * @param {string|Array.<string>|
+ * !goog.events.EventId.<EVENTOBJ>|!Array.<!goog.events.EventId.<EVENTOBJ>>}
+ * type Event type or array of event types.
+ * @param {function(this:T, EVENTOBJ):?|{handleEvent:function(?):?}|null}
+ * listener Callback method.
+ * @param {boolean=} opt_capt Fire in capture phase?.
+ * @param {T=} opt_handler Element in whose scope to call the listener.
+ * @return {goog.events.Key} Unique key for the listener.
+ * @template T,EVENTOBJ
+ */
+goog.events.listenOnce = function(src, type, listener, opt_capt, opt_handler) {
+ if (goog.isArray(type)) {
+ for (var i = 0; i < type.length; i++) {
+ goog.events.listenOnce(src, type[i], listener, opt_capt, opt_handler);
+ }
+ return null;
+ }
+
+ listener = goog.events.wrapListener(listener);
+ if (goog.events.Listenable.isImplementedBy(src)) {
+ return src.listenOnce(
+ /** @type {string|!goog.events.EventId} */ (type),
+ listener, opt_capt, opt_handler);
+ } else {
+ return goog.events.listen_(
+ /** @type {EventTarget} */ (src),
+ /** @type {string|!goog.events.EventId} */ (type),
+ listener, /* callOnce */ true, opt_capt, opt_handler);
+ }
+};
+
+
+/**
+ * Adds an event listener with a specific event wrapper on a DOM Node or an
+ * object that has implemented {@link goog.events.Listenable}. A listener can
+ * only be added once to an object.
+ *
+ * @param {EventTarget|goog.events.Listenable} src The target to
+ * listen to events on.
+ * @param {goog.events.EventWrapper} wrapper Event wrapper to use.
+ * @param {function(this:T, ?):?|{handleEvent:function(?):?}|null} listener
+ * Callback method, or an object with a handleEvent function.
+ * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
+ * false).
+ * @param {T=} opt_handler Element in whose scope to call the listener.
+ * @template T
+ */
+goog.events.listenWithWrapper = function(src, wrapper, listener, opt_capt,
+ opt_handler) {
+ wrapper.listen(src, listener, opt_capt, opt_handler);
+};
+
+
+/**
+ * Removes an event listener which was added with listen().
+ *
+ * @param {EventTarget|goog.events.Listenable} src The target to stop
+ * listening to events on.
+ * @param {string|Array.<string>|
+ * !goog.events.EventId.<EVENTOBJ>|!Array.<!goog.events.EventId.<EVENTOBJ>>}
+ * type Event type or array of event types to unlisten to.
+ * @param {function(?):?|{handleEvent:function(?):?}|null} listener The
+ * listener function to remove.
+ * @param {boolean=} opt_capt In DOM-compliant browsers, this determines
+ * whether the listener is fired during the capture or bubble phase of the
+ * event.
+ * @param {Object=} opt_handler Element in whose scope to call the listener.
+ * @return {?boolean} indicating whether the listener was there to remove.
+ * @template EVENTOBJ
+ */
+goog.events.unlisten = function(src, type, listener, opt_capt, opt_handler) {
+ if (goog.isArray(type)) {
+ for (var i = 0; i < type.length; i++) {
+ goog.events.unlisten(src, type[i], listener, opt_capt, opt_handler);
+ }
+ return null;
+ }
+
+ listener = goog.events.wrapListener(listener);
+ if (goog.events.Listenable.isImplementedBy(src)) {
+ return src.unlisten(
+ /** @type {string|!goog.events.EventId} */ (type),
+ listener, opt_capt, opt_handler);
+ }
+
+ if (!src) {
+ // TODO(user): We should tighten the API to only accept
+ // non-null objects, or add an assertion here.
+ return false;
+ }
+
+ var capture = !!opt_capt;
+ var listenerMap = goog.events.getListenerMap_(
+ /** @type {EventTarget} */ (src));
+ if (listenerMap) {
+ var listenerObj = listenerMap.getListener(
+ /** @type {string|!goog.events.EventId} */ (type),
+ listener, capture, opt_handler);
+ if (listenerObj) {
+ return goog.events.unlistenByKey(listenerObj);
+ }
+ }
+
+ return false;
+};
+
+
+/**
+ * Removes an event listener which was added with listen() by the key
+ * returned by listen().
+ *
+ * @param {goog.events.Key} key The key returned by listen() for this
+ * event listener.
+ * @return {boolean} indicating whether the listener was there to remove.
+ */
+goog.events.unlistenByKey = function(key) {
+ // TODO(user): Remove this check when tests that rely on this
+ // are fixed.
+ if (goog.isNumber(key)) {
+ return false;
+ }
+
+ var listener = /** @type {goog.events.ListenableKey} */ (key);
+ if (!listener || listener.removed) {
+ return false;
+ }
+
+ var src = listener.src;
+ if (goog.events.Listenable.isImplementedBy(src)) {
+ return src.unlistenByKey(listener);
+ }
+
+ var type = listener.type;
+ var proxy = listener.proxy;
+ if (src.removeEventListener) {
+ src.removeEventListener(type, proxy, listener.capture);
+ } else if (src.detachEvent) {
+ src.detachEvent(goog.events.getOnString_(type), proxy);
+ }
+ goog.events.listenerCountEstimate_--;
+
+ var listenerMap = goog.events.getListenerMap_(
+ /** @type {EventTarget} */ (src));
+ // TODO(user): Try to remove this conditional and execute the
+ // first branch always. This should be safe.
+ if (listenerMap) {
+ listenerMap.removeByKey(listener);
+ if (listenerMap.getTypeCount() == 0) {
+ // Null the src, just because this is simple to do (and useful
+ // for IE <= 7).
+ listenerMap.src = null;
+ // We don't use delete here because IE does not allow delete
+ // on a window object.
+ src[goog.events.LISTENER_MAP_PROP_] = null;
+ }
+ } else {
+ listener.markAsRemoved();
+ }
+
+ return true;
+};
+
+
+/**
+ * Removes an event listener which was added with listenWithWrapper().
+ *
+ * @param {EventTarget|goog.events.Listenable} src The target to stop
+ * listening to events on.
+ * @param {goog.events.EventWrapper} wrapper Event wrapper to use.
+ * @param {function(?):?|{handleEvent:function(?):?}|null} listener The
+ * listener function to remove.
+ * @param {boolean=} opt_capt In DOM-compliant browsers, this determines
+ * whether the listener is fired during the capture or bubble phase of the
+ * event.
+ * @param {Object=} opt_handler Element in whose scope to call the listener.
+ */
+goog.events.unlistenWithWrapper = function(src, wrapper, listener, opt_capt,
+ opt_handler) {
+ wrapper.unlisten(src, listener, opt_capt, opt_handler);
+};
+
+
+/**
+ * Removes all listeners from an object. You can also optionally
+ * remove listeners of a particular type.
+ *
+ * @param {Object|undefined} obj Object to remove listeners from. Must be an
+ * EventTarget or a goog.events.Listenable.
+ * @param {string|!goog.events.EventId=} opt_type Type of event to remove.
+ * Default is all types.
+ * @return {number} Number of listeners removed.
+ */
+goog.events.removeAll = function(obj, opt_type) {
+ // TODO(user): Change the type of obj to
+ // (!EventTarget|!goog.events.Listenable).
+
+ if (!obj) {
+ return 0;
+ }
+
+ if (goog.events.Listenable.isImplementedBy(obj)) {
+ return obj.removeAllListeners(opt_type);
+ }
+
+ var listenerMap = goog.events.getListenerMap_(
+ /** @type {EventTarget} */ (obj));
+ if (!listenerMap) {
+ return 0;
+ }
+
+ var count = 0;
+ var typeStr = opt_type && opt_type.toString();
+ for (var type in listenerMap.listeners) {
+ if (!typeStr || type == typeStr) {
+ // Clone so that we don't need to worry about unlistenByKey
+ // changing the content of the ListenerMap.
+ var listeners = listenerMap.listeners[type].concat();
+ for (var i = 0; i < listeners.length; ++i) {
+ if (goog.events.unlistenByKey(listeners[i])) {
+ ++count;
+ }
+ }
+ }
+ }
+ return count;
+};
+
+
+/**
+ * Removes all native listeners registered via goog.events. Native
+ * listeners are listeners on native browser objects (such as DOM
+ * elements). In particular, goog.events.Listenable and
+ * goog.events.EventTarget listeners will NOT be removed.
+ * @return {number} Number of listeners removed.
+ * @deprecated This doesn't do anything, now that Closure no longer
+ * stores a central listener registry.
+ */
+goog.events.removeAllNativeListeners = function() {
+ goog.events.listenerCountEstimate_ = 0;
+ return 0;
+};
+
+
+/**
+ * Gets the listeners for a given object, type and capture phase.
+ *
+ * @param {Object} obj Object to get listeners for.
+ * @param {string|!goog.events.EventId} type Event type.
+ * @param {boolean} capture Capture phase?.
+ * @return {Array.<goog.events.Listener>} Array of listener objects.
+ */
+goog.events.getListeners = function(obj, type, capture) {
+ if (goog.events.Listenable.isImplementedBy(obj)) {
+ return obj.getListeners(type, capture);
+ } else {
+ if (!obj) {
+ // TODO(user): We should tighten the API to accept
+ // !EventTarget|goog.events.Listenable, and add an assertion here.
+ return [];
+ }
+
+ var listenerMap = goog.events.getListenerMap_(
+ /** @type {EventTarget} */ (obj));
+ return listenerMap ? listenerMap.getListeners(type, capture) : [];
+ }
+};
+
+
+/**
+ * Gets the goog.events.Listener for the event or null if no such listener is
+ * in use.
+ *
+ * @param {EventTarget|goog.events.Listenable} src The target from
+ * which to get listeners.
+ * @param {?string|!goog.events.EventId.<EVENTOBJ>} type The type of the event.
+ * @param {function(EVENTOBJ):?|{handleEvent:function(?):?}|null} listener The
+ * listener function to get.
+ * @param {boolean=} opt_capt In DOM-compliant browsers, this determines
+ * whether the listener is fired during the
+ * capture or bubble phase of the event.
+ * @param {Object=} opt_handler Element in whose scope to call the listener.
+ * @return {goog.events.ListenableKey} the found listener or null if not found.
+ * @template EVENTOBJ
+ */
+goog.events.getListener = function(src, type, listener, opt_capt, opt_handler) {
+ // TODO(user): Change type from ?string to string, or add assertion.
+ type = /** @type {string} */ (type);
+ listener = goog.events.wrapListener(listener);
+ var capture = !!opt_capt;
+ if (goog.events.Listenable.isImplementedBy(src)) {
+ return src.getListener(type, listener, capture, opt_handler);
+ }
+
+ if (!src) {
+ // TODO(user): We should tighten the API to only accept
+ // non-null objects, or add an assertion here.
+ return null;
+ }
+
+ var listenerMap = goog.events.getListenerMap_(
+ /** @type {EventTarget} */ (src));
+ if (listenerMap) {
+ return listenerMap.getListener(type, listener, capture, opt_handler);
+ }
+ return null;
+};
+
+
+/**
+ * Returns whether an event target has any active listeners matching the
+ * specified signature. If either the type or capture parameters are
+ * unspecified, the function will match on the remaining criteria.
+ *
+ * @param {EventTarget|goog.events.Listenable} obj Target to get
+ * listeners for.
+ * @param {string|!goog.events.EventId=} opt_type Event type.
+ * @param {boolean=} opt_capture Whether to check for capture or bubble-phase
+ * listeners.
+ * @return {boolean} Whether an event target has one or more listeners matching
+ * the requested type and/or capture phase.
+ */
+goog.events.hasListener = function(obj, opt_type, opt_capture) {
+ if (goog.events.Listenable.isImplementedBy(obj)) {
+ return obj.hasListener(opt_type, opt_capture);
+ }
+
+ var listenerMap = goog.events.getListenerMap_(
+ /** @type {EventTarget} */ (obj));
+ return !!listenerMap && listenerMap.hasListener(opt_type, opt_capture);
+};
+
+
+/**
+ * Provides a nice string showing the normalized event objects public members
+ * @param {Object} e Event Object.
+ * @return {string} String of the public members of the normalized event object.
+ */
+goog.events.expose = function(e) {
+ var str = [];
+ for (var key in e) {
+ if (e[key] && e[key].id) {
+ str.push(key + ' = ' + e[key] + ' (' + e[key].id + ')');
+ } else {
+ str.push(key + ' = ' + e[key]);
+ }
+ }
+ return str.join('\n');
+};
+
+
+/**
+ * Returns a string with on prepended to the specified type. This is used for IE
+ * which expects "on" to be prepended. This function caches the string in order
+ * to avoid extra allocations in steady state.
+ * @param {string} type Event type.
+ * @return {string} The type string with 'on' prepended.
+ * @private
+ */
+goog.events.getOnString_ = function(type) {
+ if (type in goog.events.onStringMap_) {
+ return goog.events.onStringMap_[type];
+ }
+ return goog.events.onStringMap_[type] = goog.events.onString_ + type;
+};
+
+
+/**
+ * Fires an object's listeners of a particular type and phase
+ *
+ * @param {Object} obj Object whose listeners to call.
+ * @param {string|!goog.events.EventId} type Event type.
+ * @param {boolean} capture Which event phase.
+ * @param {Object} eventObject Event object to be passed to listener.
+ * @return {boolean} True if all listeners returned true else false.
+ */
+goog.events.fireListeners = function(obj, type, capture, eventObject) {
+ if (goog.events.Listenable.isImplementedBy(obj)) {
+ return obj.fireListeners(type, capture, eventObject);
+ }
+
+ return goog.events.fireListeners_(obj, type, capture, eventObject);
+};
+
+
+/**
+ * Fires an object's listeners of a particular type and phase.
+ * @param {Object} obj Object whose listeners to call.
+ * @param {string|!goog.events.EventId} type Event type.
+ * @param {boolean} capture Which event phase.
+ * @param {Object} eventObject Event object to be passed to listener.
+ * @return {boolean} True if all listeners returned true else false.
+ * @private
+ */
+goog.events.fireListeners_ = function(obj, type, capture, eventObject) {
+ var retval = 1;
+
+ var listenerMap = goog.events.getListenerMap_(
+ /** @type {EventTarget} */ (obj));
+ if (listenerMap) {
+ // TODO(user): Original code avoids array creation when there
+ // is no listener, so we do the same. If this optimization turns
+ // out to be not required, we can replace this with
+ // listenerMap.getListeners(type, capture) instead, which is simpler.
+ var listenerArray = listenerMap.listeners[type.toString()];
+ if (listenerArray) {
+ listenerArray = listenerArray.concat();
+ for (var i = 0; i < listenerArray.length; i++) {
+ var listener = listenerArray[i];
+ // We might not have a listener if the listener was removed.
+ if (listener && listener.capture == capture && !listener.removed) {
+ retval &=
+ goog.events.fireListener(listener, eventObject) !== false;
+ }
+ }
+ }
+ }
+ return Boolean(retval);
+};
+
+
+/**
+ * Fires a listener with a set of arguments
+ *
+ * @param {goog.events.Listener} listener The listener object to call.
+ * @param {Object} eventObject The event object to pass to the listener.
+ * @return {boolean} Result of listener.
+ */
+goog.events.fireListener = function(listener, eventObject) {
+ var listenerFn = listener.listener;
+ var listenerHandler = listener.handler || listener.src;
+
+ if (listener.callOnce) {
+ goog.events.unlistenByKey(listener);
+ }
+ return listenerFn.call(listenerHandler, eventObject);
+};
+
+
+/**
+ * Gets the total number of listeners currently in the system.
+ * @return {number} Number of listeners.
+ * @deprecated This returns estimated count, now that Closure no longer
+ * stores a central listener registry. We still return an estimation
+ * to keep existing listener-related tests passing. In the near future,
+ * this function will be removed.
+ */
+goog.events.getTotalListenerCount = function() {
+ return goog.events.listenerCountEstimate_;
+};
+
+
+/**
+ * Dispatches an event (or event like object) and calls all listeners
+ * listening for events of this type. The type of the event is decided by the
+ * type property on the event object.
+ *
+ * If any of the listeners returns false OR calls preventDefault then this
+ * function will return false. If one of the capture listeners calls
+ * stopPropagation, then the bubble listeners won't fire.
+ *
+ * @param {goog.events.Listenable} src The event target.
+ * @param {goog.events.EventLike} e Event object.
+ * @return {boolean} If anyone called preventDefault on the event object (or
+ * if any of the handlers returns false) this will also return false.
+ * If there are no handlers, or if all handlers return true, this returns
+ * true.
+ */
+goog.events.dispatchEvent = function(src, e) {
+ goog.asserts.assert(
+ goog.events.Listenable.isImplementedBy(src),
+ 'Can not use goog.events.dispatchEvent with ' +
+ 'non-goog.events.Listenable instance.');
+ return src.dispatchEvent(e);
+};
+
+
+/**
+ * Installs exception protection for the browser event entry point using the
+ * given error handler.
+ *
+ * @param {goog.debug.ErrorHandler} errorHandler Error handler with which to
+ * protect the entry point.
+ */
+goog.events.protectBrowserEventEntryPoint = function(errorHandler) {
+ goog.events.handleBrowserEvent_ = errorHandler.protectEntryPoint(
+ goog.events.handleBrowserEvent_);
+};
+
+
+/**
+ * Handles an event and dispatches it to the correct listeners. This
+ * function is a proxy for the real listener the user specified.
+ *
+ * @param {goog.events.Listener} listener The listener object.
+ * @param {Event=} opt_evt Optional event object that gets passed in via the
+ * native event handlers.
+ * @return {boolean} Result of the event handler.
+ * @this {EventTarget} The object or Element that fired the event.
+ * @private
+ */
+goog.events.handleBrowserEvent_ = function(listener, opt_evt) {
+ if (listener.removed) {
+ return true;
+ }
+
+ // Synthesize event propagation if the browser does not support W3C
+ // event model.
+ if (!goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT) {
+ var ieEvent = opt_evt ||
+ /** @type {Event} */ (goog.getObjectByName('window.event'));
+ var evt = new goog.events.BrowserEvent(ieEvent, this);
+ var retval = true;
+
+ if (goog.events.CAPTURE_SIMULATION_MODE ==
+ goog.events.CaptureSimulationMode.ON) {
+ // If we have not marked this event yet, we should perform capture
+ // simulation.
+ if (!goog.events.isMarkedIeEvent_(ieEvent)) {
+ goog.events.markIeEvent_(ieEvent);
+
+ var ancestors = [];
+ for (var parent = evt.currentTarget; parent;
+ parent = parent.parentNode) {
+ ancestors.push(parent);
+ }
+
+ // Fire capture listeners.
+ var type = listener.type;
+ for (var i = ancestors.length - 1; !evt.propagationStopped_ && i >= 0;
+ i--) {
+ evt.currentTarget = ancestors[i];
+ retval &= goog.events.fireListeners_(ancestors[i], type, true, evt);
+ }
+
+ // Fire bubble listeners.
+ //
+ // We can technically rely on IE to perform bubble event
+ // propagation. However, it turns out that IE fires events in
+ // opposite order of attachEvent registration, which broke
+ // some code and tests that rely on the order. (While W3C DOM
+ // Level 2 Events TR leaves the event ordering unspecified,
+ // modern browsers and W3C DOM Level 3 Events Working Draft
+ // actually specify the order as the registration order.)
+ for (var i = 0; !evt.propagationStopped_ && i < ancestors.length; i++) {
+ evt.currentTarget = ancestors[i];
+ retval &= goog.events.fireListeners_(ancestors[i], type, false, evt);
+ }
+ }
+ } else {
+ retval = goog.events.fireListener(listener, evt);
+ }
+ return retval;
+ }
+
+ // Otherwise, simply fire the listener.
+ return goog.events.fireListener(
+ listener, new goog.events.BrowserEvent(opt_evt, this));
+};
+
+
+/**
+ * This is used to mark the IE event object so we do not do the Closure pass
+ * twice for a bubbling event.
+ * @param {Event} e The IE browser event.
+ * @private
+ */
+goog.events.markIeEvent_ = function(e) {
+ // Only the keyCode and the returnValue can be changed. We use keyCode for
+ // non keyboard events.
+ // event.returnValue is a bit more tricky. It is undefined by default. A
+ // boolean false prevents the default action. In a window.onbeforeunload and
+ // the returnValue is non undefined it will be alerted. However, we will only
+ // modify the returnValue for keyboard events. We can get a problem if non
+ // closure events sets the keyCode or the returnValue
+
+ var useReturnValue = false;
+
+ if (e.keyCode == 0) {
+ // We cannot change the keyCode in case that srcElement is input[type=file].
+ // We could test that that is the case but that would allocate 3 objects.
+ // If we use try/catch we will only allocate extra objects in the case of a
+ // failure.
+ /** @preserveTry */
+ try {
+ e.keyCode = -1;
+ return;
+ } catch (ex) {
+ useReturnValue = true;
+ }
+ }
+
+ if (useReturnValue ||
+ /** @type {boolean|undefined} */ (e.returnValue) == undefined) {
+ e.returnValue = true;
+ }
+};
+
+
+/**
+ * This is used to check if an IE event has already been handled by the Closure
+ * system so we do not do the Closure pass twice for a bubbling event.
+ * @param {Event} e The IE browser event.
+ * @return {boolean} True if the event object has been marked.
+ * @private
+ */
+goog.events.isMarkedIeEvent_ = function(e) {
+ return e.keyCode < 0 || e.returnValue != undefined;
+};
+
+
+/**
+ * Counter to create unique event ids.
+ * @private {number}
+ */
+goog.events.uniqueIdCounter_ = 0;
+
+
+/**
+ * Creates a unique event id.
+ *
+ * @param {string} identifier The identifier.
+ * @return {string} A unique identifier.
+ * @idGenerator
+ */
+goog.events.getUniqueId = function(identifier) {
+ return identifier + '_' + goog.events.uniqueIdCounter_++;
+};
+
+
+/**
+ * @param {EventTarget} src The source object.
+ * @return {goog.events.ListenerMap} A listener map for the given
+ * source object, or null if none exists.
+ * @private
+ */
+goog.events.getListenerMap_ = function(src) {
+ var listenerMap = src[goog.events.LISTENER_MAP_PROP_];
+ // IE serializes the property as well (e.g. when serializing outer
+ // HTML). So we must check that the value is of the correct type.
+ return listenerMap instanceof goog.events.ListenerMap ? listenerMap : null;
+};
+
+
+/**
+ * Expando property for listener function wrapper for Object with
+ * handleEvent.
+ * @const
+ * @private
+ */
+goog.events.LISTENER_WRAPPER_PROP_ = '__closure_events_fn_' +
+ ((Math.random() * 1e9) >>> 0);
+
+
+/**
+ * @param {Object|Function} listener The listener function or an
+ * object that contains handleEvent method.
+ * @return {!Function} Either the original function or a function that
+ * calls obj.handleEvent. If the same listener is passed to this
+ * function more than once, the same function is guaranteed to be
+ * returned.
+ */
+goog.events.wrapListener = function(listener) {
+ goog.asserts.assert(listener, 'Listener can not be null.');
+
+ if (goog.isFunction(listener)) {
+ return listener;
+ }
+
+ goog.asserts.assert(
+ listener.handleEvent, 'An object listener must have handleEvent method.');
+ if (!listener[goog.events.LISTENER_WRAPPER_PROP_]) {
+ listener[goog.events.LISTENER_WRAPPER_PROP_] =
+ function(e) { return listener.handleEvent(e); };
+ }
+ return listener[goog.events.LISTENER_WRAPPER_PROP_];
+};
+
+
+// Register the browser event handler as an entry point, so that
+// it can be monitored for exception handling, etc.
+goog.debug.entryPointRegistry.register(
+ /**
+ * @param {function(!Function): !Function} transformer The transforming
+ * function.
+ */
+ function(transformer) {
+ goog.events.handleBrowserEvent_ = transformer(
+ goog.events.handleBrowserEvent_);
+ });
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/events/eventtarget.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/eventtarget.js
new file mode 100644
index 0000000..172c412
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/eventtarget.js
@@ -0,0 +1,395 @@
+// Copyright 2005 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview A disposable implementation of a custom
+ * listenable/event target. See also: documentation for
+ * {@code goog.events.Listenable}.
+ *
+ * @author arv@google.com (Erik Arvidsson) [Original implementation]
+ * @author pupius@google.com (Daniel Pupius) [Port to use goog.events]
+ * @see ../demos/eventtarget.html
+ * @see goog.events.Listenable
+ */
+
+goog.provide('goog.events.EventTarget');
+
+goog.require('goog.Disposable');
+goog.require('goog.asserts');
+goog.require('goog.events');
+goog.require('goog.events.Event');
+goog.require('goog.events.Listenable');
+goog.require('goog.events.ListenerMap');
+goog.require('goog.object');
+
+
+
+/**
+ * An implementation of {@code goog.events.Listenable} with full W3C
+ * EventTarget-like support (capture/bubble mechanism, stopping event
+ * propagation, preventing default actions).
+ *
+ * You may subclass this class to turn your class into a Listenable.
+ *
+ * Unless propagation is stopped, an event dispatched by an
+ * EventTarget will bubble to the parent returned by
+ * {@code getParentEventTarget}. To set the parent, call
+ * {@code setParentEventTarget}. Subclasses that don't support
+ * changing the parent can override the setter to throw an error.
+ *
+ * Example usage:
+ * <pre>
+ * var source = new goog.events.EventTarget();
+ * function handleEvent(e) {
+ * alert('Type: ' + e.type + '; Target: ' + e.target);
+ * }
+ * source.listen('foo', handleEvent);
+ * // Or: goog.events.listen(source, 'foo', handleEvent);
+ * ...
+ * source.dispatchEvent('foo'); // will call handleEvent
+ * ...
+ * source.unlisten('foo', handleEvent);
+ * // Or: goog.events.unlisten(source, 'foo', handleEvent);
+ * </pre>
+ *
+ * @constructor
+ * @extends {goog.Disposable}
+ * @implements {goog.events.Listenable}
+ */
+goog.events.EventTarget = function() {
+ goog.Disposable.call(this);
+
+ /**
+ * Maps of event type to an array of listeners.
+ * @private {!goog.events.ListenerMap}
+ */
+ this.eventTargetListeners_ = new goog.events.ListenerMap(this);
+
+ /**
+ * The object to use for event.target. Useful when mixing in an
+ * EventTarget to another object.
+ * @private {!Object}
+ */
+ this.actualEventTarget_ = this;
+
+ /**
+ * Parent event target, used during event bubbling.
+ *
+ * TODO(user): Change this to goog.events.Listenable. This
+ * currently breaks people who expect getParentEventTarget to return
+ * goog.events.EventTarget.
+ *
+ * @private {goog.events.EventTarget}
+ */
+ this.parentEventTarget_ = null;
+};
+goog.inherits(goog.events.EventTarget, goog.Disposable);
+goog.events.Listenable.addImplementation(goog.events.EventTarget);
+
+
+/**
+ * An artificial cap on the number of ancestors you can have. This is mainly
+ * for loop detection.
+ * @const {number}
+ * @private
+ */
+goog.events.EventTarget.MAX_ANCESTORS_ = 1000;
+
+
+/**
+ * Returns the parent of this event target to use for bubbling.
+ *
+ * @return {goog.events.EventTarget} The parent EventTarget or null if
+ * there is no parent.
+ * @override
+ */
+goog.events.EventTarget.prototype.getParentEventTarget = function() {
+ return this.parentEventTarget_;
+};
+
+
+/**
+ * Sets the parent of this event target to use for capture/bubble
+ * mechanism.
+ * @param {goog.events.EventTarget} parent Parent listenable (null if none).
+ */
+goog.events.EventTarget.prototype.setParentEventTarget = function(parent) {
+ this.parentEventTarget_ = parent;
+};
+
+
+/**
+ * Adds an event listener to the event target. The same handler can only be
+ * added once per the type. Even if you add the same handler multiple times
+ * using the same type then it will only be called once when the event is
+ * dispatched.
+ *
+ * @param {string} type The type of the event to listen for.
+ * @param {function(?):?|{handleEvent:function(?):?}|null} handler The function
+ * to handle the event. The handler can also be an object that implements
+ * the handleEvent method which takes the event object as argument.
+ * @param {boolean=} opt_capture In DOM-compliant browsers, this determines
+ * whether the listener is fired during the capture or bubble phase
+ * of the event.
+ * @param {Object=} opt_handlerScope Object in whose scope to call
+ * the listener.
+ * @deprecated Use {@code #listen} instead, when possible. Otherwise, use
+ * {@code goog.events.listen} if you are passing Object
+ * (instead of Function) as handler.
+ */
+goog.events.EventTarget.prototype.addEventListener = function(
+ type, handler, opt_capture, opt_handlerScope) {
+ goog.events.listen(this, type, handler, opt_capture, opt_handlerScope);
+};
+
+
+/**
+ * Removes an event listener from the event target. The handler must be the
+ * same object as the one added. If the handler has not been added then
+ * nothing is done.
+ *
+ * @param {string} type The type of the event to listen for.
+ * @param {function(?):?|{handleEvent:function(?):?}|null} handler The function
+ * to handle the event. The handler can also be an object that implements
+ * the handleEvent method which takes the event object as argument.
+ * @param {boolean=} opt_capture In DOM-compliant browsers, this determines
+ * whether the listener is fired during the capture or bubble phase
+ * of the event.
+ * @param {Object=} opt_handlerScope Object in whose scope to call
+ * the listener.
+ * @deprecated Use {@code #unlisten} instead, when possible. Otherwise, use
+ * {@code goog.events.unlisten} if you are passing Object
+ * (instead of Function) as handler.
+ */
+goog.events.EventTarget.prototype.removeEventListener = function(
+ type, handler, opt_capture, opt_handlerScope) {
+ goog.events.unlisten(this, type, handler, opt_capture, opt_handlerScope);
+};
+
+
+/** @override */
+goog.events.EventTarget.prototype.dispatchEvent = function(e) {
+ this.assertInitialized_();
+
+ var ancestorsTree, ancestor = this.getParentEventTarget();
+ if (ancestor) {
+ ancestorsTree = [];
+ var ancestorCount = 1;
+ for (; ancestor; ancestor = ancestor.getParentEventTarget()) {
+ ancestorsTree.push(ancestor);
+ goog.asserts.assert(
+ (++ancestorCount < goog.events.EventTarget.MAX_ANCESTORS_),
+ 'infinite loop');
+ }
+ }
+
+ return goog.events.EventTarget.dispatchEventInternal_(
+ this.actualEventTarget_, e, ancestorsTree);
+};
+
+
+/**
+ * Removes listeners from this object. Classes that extend EventTarget may
+ * need to override this method in order to remove references to DOM Elements
+ * and additional listeners.
+ * @override
+ */
+goog.events.EventTarget.prototype.disposeInternal = function() {
+ goog.events.EventTarget.superClass_.disposeInternal.call(this);
+
+ this.removeAllListeners();
+ this.parentEventTarget_ = null;
+};
+
+
+/** @override */
+goog.events.EventTarget.prototype.listen = function(
+ type, listener, opt_useCapture, opt_listenerScope) {
+ this.assertInitialized_();
+ return this.eventTargetListeners_.add(
+ String(type), listener, false /* callOnce */, opt_useCapture,
+ opt_listenerScope);
+};
+
+
+/** @override */
+goog.events.EventTarget.prototype.listenOnce = function(
+ type, listener, opt_useCapture, opt_listenerScope) {
+ return this.eventTargetListeners_.add(
+ String(type), listener, true /* callOnce */, opt_useCapture,
+ opt_listenerScope);
+};
+
+
+/** @override */
+goog.events.EventTarget.prototype.unlisten = function(
+ type, listener, opt_useCapture, opt_listenerScope) {
+ return this.eventTargetListeners_.remove(
+ String(type), listener, opt_useCapture, opt_listenerScope);
+};
+
+
+/** @override */
+goog.events.EventTarget.prototype.unlistenByKey = function(key) {
+ return this.eventTargetListeners_.removeByKey(key);
+};
+
+
+/** @override */
+goog.events.EventTarget.prototype.removeAllListeners = function(opt_type) {
+ // TODO(user): Previously, removeAllListeners can be called on
+ // uninitialized EventTarget, so we preserve that behavior. We
+ // should remove this when usages that rely on that fact are purged.
+ if (!this.eventTargetListeners_) {
+ return 0;
+ }
+ return this.eventTargetListeners_.removeAll(opt_type);
+};
+
+
+/** @override */
+goog.events.EventTarget.prototype.fireListeners = function(
+ type, capture, eventObject) {
+ // TODO(user): Original code avoids array creation when there
+ // is no listener, so we do the same. If this optimization turns
+ // out to be not required, we can replace this with
+ // getListeners(type, capture) instead, which is simpler.
+ var listenerArray = this.eventTargetListeners_.listeners[String(type)];
+ if (!listenerArray) {
+ return true;
+ }
+ listenerArray = listenerArray.concat();
+
+ var rv = true;
+ for (var i = 0; i < listenerArray.length; ++i) {
+ var listener = listenerArray[i];
+ // We might not have a listener if the listener was removed.
+ if (listener && !listener.removed && listener.capture == capture) {
+ var listenerFn = listener.listener;
+ var listenerHandler = listener.handler || listener.src;
+
+ if (listener.callOnce) {
+ this.unlistenByKey(listener);
+ }
+ rv = listenerFn.call(listenerHandler, eventObject) !== false && rv;
+ }
+ }
+
+ return rv && eventObject.returnValue_ != false;
+};
+
+
+/** @override */
+goog.events.EventTarget.prototype.getListeners = function(type, capture) {
+ return this.eventTargetListeners_.getListeners(String(type), capture);
+};
+
+
+/** @override */
+goog.events.EventTarget.prototype.getListener = function(
+ type, listener, capture, opt_listenerScope) {
+ return this.eventTargetListeners_.getListener(
+ String(type), listener, capture, opt_listenerScope);
+};
+
+
+/** @override */
+goog.events.EventTarget.prototype.hasListener = function(
+ opt_type, opt_capture) {
+ var id = goog.isDef(opt_type) ? String(opt_type) : undefined;
+ return this.eventTargetListeners_.hasListener(id, opt_capture);
+};
+
+
+/**
+ * Sets the target to be used for {@code event.target} when firing
+ * event. Mainly used for testing. For example, see
+ * {@code goog.testing.events.mixinListenable}.
+ * @param {!Object} target The target.
+ */
+goog.events.EventTarget.prototype.setTargetForTesting = function(target) {
+ this.actualEventTarget_ = target;
+};
+
+
+/**
+ * Asserts that the event target instance is initialized properly.
+ * @private
+ */
+goog.events.EventTarget.prototype.assertInitialized_ = function() {
+ goog.asserts.assert(
+ this.eventTargetListeners_,
+ 'Event target is not initialized. Did you call the superclass ' +
+ '(goog.events.EventTarget) constructor?');
+};
+
+
+/**
+ * Dispatches the given event on the ancestorsTree.
+ *
+ * @param {!Object} target The target to dispatch on.
+ * @param {goog.events.Event|Object|string} e The event object.
+ * @param {Array.<goog.events.Listenable>=} opt_ancestorsTree The ancestors
+ * tree of the target, in reverse order from the closest ancestor
+ * to the root event target. May be null if the target has no ancestor.
+ * @return {boolean} If anyone called preventDefault on the event object (or
+ * if any of the listeners returns false) this will also return false.
+ * @private
+ */
+goog.events.EventTarget.dispatchEventInternal_ = function(
+ target, e, opt_ancestorsTree) {
+ var type = e.type || /** @type {string} */ (e);
+
+ // If accepting a string or object, create a custom event object so that
+ // preventDefault and stopPropagation work with the event.
+ if (goog.isString(e)) {
+ e = new goog.events.Event(e, target);
+ } else if (!(e instanceof goog.events.Event)) {
+ var oldEvent = e;
+ e = new goog.events.Event(type, target);
+ goog.object.extend(e, oldEvent);
+ } else {
+ e.target = e.target || target;
+ }
+
+ var rv = true, currentTarget;
+
+ // Executes all capture listeners on the ancestors, if any.
+ if (opt_ancestorsTree) {
+ for (var i = opt_ancestorsTree.length - 1; !e.propagationStopped_ && i >= 0;
+ i--) {
+ currentTarget = e.currentTarget = opt_ancestorsTree[i];
+ rv = currentTarget.fireListeners(type, true, e) && rv;
+ }
+ }
+
+ // Executes capture and bubble listeners on the target.
+ if (!e.propagationStopped_) {
+ currentTarget = e.currentTarget = target;
+ rv = currentTarget.fireListeners(type, true, e) && rv;
+ if (!e.propagationStopped_) {
+ rv = currentTarget.fireListeners(type, false, e) && rv;
+ }
+ }
+
+ // Executes all bubble listeners on the ancestors, if any.
+ if (opt_ancestorsTree) {
+ for (i = 0; !e.propagationStopped_ && i < opt_ancestorsTree.length; i++) {
+ currentTarget = e.currentTarget = opt_ancestorsTree[i];
+ rv = currentTarget.fireListeners(type, false, e) && rv;
+ }
+ }
+
+ return rv;
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/events/eventtype.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/eventtype.js
new file mode 100644
index 0000000..37ee9c8
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/eventtype.js
@@ -0,0 +1,226 @@
+// Copyright 2010 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Event Types.
+ *
+ * @author arv@google.com (Erik Arvidsson)
+ * @author mirkov@google.com (Mirko Visontai)
+ */
+
+
+goog.provide('goog.events.EventType');
+
+goog.require('goog.userAgent');
+
+
+/**
+ * Returns a prefixed event name for the current browser.
+ * @param {string} eventName The name of the event.
+ * @return {string} The prefixed event name.
+ * @suppress {missingRequire|missingProvide}
+ * @private
+ */
+goog.events.getVendorPrefixedName_ = function(eventName) {
+ return goog.userAgent.WEBKIT ? 'webkit' + eventName :
+ (goog.userAgent.OPERA ? 'o' + eventName.toLowerCase() :
+ eventName.toLowerCase());
+};
+
+
+/**
+ * Constants for event names.
+ * @enum {string}
+ */
+goog.events.EventType = {
+ // Mouse events
+ CLICK: 'click',
+ RIGHTCLICK: 'rightclick',
+ DBLCLICK: 'dblclick',
+ MOUSEDOWN: 'mousedown',
+ MOUSEUP: 'mouseup',
+ MOUSEOVER: 'mouseover',
+ MOUSEOUT: 'mouseout',
+ MOUSEMOVE: 'mousemove',
+ MOUSEENTER: 'mouseenter',
+ MOUSELEAVE: 'mouseleave',
+ // Select start is non-standard.
+ // See http://msdn.microsoft.com/en-us/library/ie/ms536969(v=vs.85).aspx.
+ SELECTSTART: 'selectstart', // IE, Safari, Chrome
+
+ // Key events
+ KEYPRESS: 'keypress',
+ KEYDOWN: 'keydown',
+ KEYUP: 'keyup',
+
+ // Focus
+ BLUR: 'blur',
+ FOCUS: 'focus',
+ DEACTIVATE: 'deactivate', // IE only
+ // NOTE: The following two events are not stable in cross-browser usage.
+ // WebKit and Opera implement DOMFocusIn/Out.
+ // IE implements focusin/out.
+ // Gecko implements neither see bug at
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=396927.
+ // The DOM Events Level 3 Draft deprecates DOMFocusIn in favor of focusin:
+ // http://dev.w3.org/2006/webapi/DOM-Level-3-Events/html/DOM3-Events.html
+ // You can use FOCUS in Capture phase until implementations converge.
+ FOCUSIN: goog.userAgent.IE ? 'focusin' : 'DOMFocusIn',
+ FOCUSOUT: goog.userAgent.IE ? 'focusout' : 'DOMFocusOut',
+
+ // Forms
+ CHANGE: 'change',
+ SELECT: 'select',
+ SUBMIT: 'submit',
+ INPUT: 'input',
+ PROPERTYCHANGE: 'propertychange', // IE only
+
+ // Drag and drop
+ DRAGSTART: 'dragstart',
+ DRAG: 'drag',
+ DRAGENTER: 'dragenter',
+ DRAGOVER: 'dragover',
+ DRAGLEAVE: 'dragleave',
+ DROP: 'drop',
+ DRAGEND: 'dragend',
+
+ // WebKit touch events.
+ TOUCHSTART: 'touchstart',
+ TOUCHMOVE: 'touchmove',
+ TOUCHEND: 'touchend',
+ TOUCHCANCEL: 'touchcancel',
+
+ // Misc
+ BEFOREUNLOAD: 'beforeunload',
+ CONSOLEMESSAGE: 'consolemessage',
+ CONTEXTMENU: 'contextmenu',
+ DOMCONTENTLOADED: 'DOMContentLoaded',
+ ERROR: 'error',
+ HELP: 'help',
+ LOAD: 'load',
+ LOSECAPTURE: 'losecapture',
+ ORIENTATIONCHANGE: 'orientationchange',
+ READYSTATECHANGE: 'readystatechange',
+ RESIZE: 'resize',
+ SCROLL: 'scroll',
+ UNLOAD: 'unload',
+
+ // HTML 5 History events
+ // See http://www.w3.org/TR/html5/history.html#event-definitions
+ HASHCHANGE: 'hashchange',
+ PAGEHIDE: 'pagehide',
+ PAGESHOW: 'pageshow',
+ POPSTATE: 'popstate',
+
+ // Copy and Paste
+ // Support is limited. Make sure it works on your favorite browser
+ // before using.
+ // http://www.quirksmode.org/dom/events/cutcopypaste.html
+ COPY: 'copy',
+ PASTE: 'paste',
+ CUT: 'cut',
+ BEFORECOPY: 'beforecopy',
+ BEFORECUT: 'beforecut',
+ BEFOREPASTE: 'beforepaste',
+
+ // HTML5 online/offline events.
+ // http://www.w3.org/TR/offline-webapps/#related
+ ONLINE: 'online',
+ OFFLINE: 'offline',
+
+ // HTML 5 worker events
+ MESSAGE: 'message',
+ CONNECT: 'connect',
+
+ // CSS animation events.
+ /** @suppress {missingRequire} */
+ ANIMATIONSTART: goog.events.getVendorPrefixedName_('AnimationStart'),
+ /** @suppress {missingRequire} */
+ ANIMATIONEND: goog.events.getVendorPrefixedName_('AnimationEnd'),
+ /** @suppress {missingRequire} */
+ ANIMATIONITERATION: goog.events.getVendorPrefixedName_('AnimationIteration'),
+
+ // CSS transition events. Based on the browser support described at:
+ // https://developer.mozilla.org/en/css/css_transitions#Browser_compatibility
+ /** @suppress {missingRequire} */
+ TRANSITIONEND: goog.events.getVendorPrefixedName_('TransitionEnd'),
+
+ // W3C Pointer Events
+ // http://www.w3.org/TR/pointerevents/
+ POINTERDOWN: 'pointerdown',
+ POINTERUP: 'pointerup',
+ POINTERCANCEL: 'pointercancel',
+ POINTERMOVE: 'pointermove',
+ POINTEROVER: 'pointerover',
+ POINTEROUT: 'pointerout',
+ POINTERENTER: 'pointerenter',
+ POINTERLEAVE: 'pointerleave',
+ GOTPOINTERCAPTURE: 'gotpointercapture',
+ LOSTPOINTERCAPTURE: 'lostpointercapture',
+
+ // IE specific events.
+ // See http://msdn.microsoft.com/en-us/library/ie/hh772103(v=vs.85).aspx
+ // Note: these events will be supplanted in IE11.
+ MSGESTURECHANGE: 'MSGestureChange',
+ MSGESTUREEND: 'MSGestureEnd',
+ MSGESTUREHOLD: 'MSGestureHold',
+ MSGESTURESTART: 'MSGestureStart',
+ MSGESTURETAP: 'MSGestureTap',
+ MSGOTPOINTERCAPTURE: 'MSGotPointerCapture',
+ MSINERTIASTART: 'MSInertiaStart',
+ MSLOSTPOINTERCAPTURE: 'MSLostPointerCapture',
+ MSPOINTERCANCEL: 'MSPointerCancel',
+ MSPOINTERDOWN: 'MSPointerDown',
+ MSPOINTERENTER: 'MSPointerEnter',
+ MSPOINTERHOVER: 'MSPointerHover',
+ MSPOINTERLEAVE: 'MSPointerLeave',
+ MSPOINTERMOVE: 'MSPointerMove',
+ MSPOINTEROUT: 'MSPointerOut',
+ MSPOINTEROVER: 'MSPointerOver',
+ MSPOINTERUP: 'MSPointerUp',
+
+ // Native IMEs/input tools events.
+ TEXTINPUT: 'textinput',
+ COMPOSITIONSTART: 'compositionstart',
+ COMPOSITIONUPDATE: 'compositionupdate',
+ COMPOSITIONEND: 'compositionend',
+
+ // Webview tag events
+ // See http://developer.chrome.com/dev/apps/webview_tag.html
+ EXIT: 'exit',
+ LOADABORT: 'loadabort',
+ LOADCOMMIT: 'loadcommit',
+ LOADREDIRECT: 'loadredirect',
+ LOADSTART: 'loadstart',
+ LOADSTOP: 'loadstop',
+ RESPONSIVE: 'responsive',
+ SIZECHANGED: 'sizechanged',
+ UNRESPONSIVE: 'unresponsive',
+
+ // HTML5 Page Visibility API. See details at
+ // {@code goog.labs.dom.PageVisibilityMonitor}.
+ VISIBILITYCHANGE: 'visibilitychange',
+
+ // LocalStorage event.
+ STORAGE: 'storage',
+
+ // DOM Level 2 mutation events (deprecated).
+ DOMSUBTREEMODIFIED: 'DOMSubtreeModified',
+ DOMNODEINSERTED: 'DOMNodeInserted',
+ DOMNODEREMOVED: 'DOMNodeRemoved',
+ DOMNODEREMOVEDFROMDOCUMENT: 'DOMNodeRemovedFromDocument',
+ DOMNODEINSERTEDINTODOCUMENT: 'DOMNodeInsertedIntoDocument',
+ DOMATTRMODIFIED: 'DOMAttrModified',
+ DOMCHARACTERDATAMODIFIED: 'DOMCharacterDataModified'
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/events/eventwrapper.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/eventwrapper.js
new file mode 100644
index 0000000..c556bc1
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/eventwrapper.js
@@ -0,0 +1,68 @@
+// Copyright 2009 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Definition of the goog.events.EventWrapper interface.
+ *
+ * @author eae@google.com (Emil A Eklund)
+ */
+
+goog.provide('goog.events.EventWrapper');
+
+
+
+/**
+ * Interface for event wrappers.
+ * @interface
+ */
+goog.events.EventWrapper = function() {
+};
+
+
+/**
+ * Adds an event listener using the wrapper on a DOM Node or an object that has
+ * implemented {@link goog.events.EventTarget}. A listener can only be added
+ * once to an object.
+ *
+ * @param {EventTarget|goog.events.EventTarget} src The node to listen to
+ * events on.
+ * @param {Function|Object} listener Callback method, or an object with a
+ * handleEvent function.
+ * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
+ * false).
+ * @param {Object=} opt_scope Element in whose scope to call the listener.
+ * @param {goog.events.EventHandler=} opt_eventHandler Event handler to add
+ * listener to.
+ */
+goog.events.EventWrapper.prototype.listen = function(src, listener, opt_capt,
+ opt_scope, opt_eventHandler) {
+};
+
+
+/**
+ * Removes an event listener added using goog.events.EventWrapper.listen.
+ *
+ * @param {EventTarget|goog.events.EventTarget} src The node to remove listener
+ * from.
+ * @param {Function|Object} listener Callback method, or an object with a
+ * handleEvent function.
+ * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
+ * false).
+ * @param {Object=} opt_scope Element in whose scope to call the listener.
+ * @param {goog.events.EventHandler=} opt_eventHandler Event handler to remove
+ * listener from.
+ */
+goog.events.EventWrapper.prototype.unlisten = function(src, listener, opt_capt,
+ opt_scope, opt_eventHandler) {
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/events/keycodes.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/keycodes.js
new file mode 100644
index 0000000..d0ad82c
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/keycodes.js
@@ -0,0 +1,415 @@
+// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Constant declarations for common key codes.
+ *
+ * @author eae@google.com (Emil A Eklund)
+ * @see ../demos/keyhandler.html
+ */
+
+goog.provide('goog.events.KeyCodes');
+
+goog.require('goog.userAgent');
+
+
+/**
+ * Key codes for common characters.
+ *
+ * This list is not localized and therefore some of the key codes are not
+ * correct for non US keyboard layouts. See comments below.
+ *
+ * @enum {number}
+ */
+goog.events.KeyCodes = {
+ WIN_KEY_FF_LINUX: 0,
+ MAC_ENTER: 3,
+ BACKSPACE: 8,
+ TAB: 9,
+ NUM_CENTER: 12, // NUMLOCK on FF/Safari Mac
+ ENTER: 13,
+ SHIFT: 16,
+ CTRL: 17,
+ ALT: 18,
+ PAUSE: 19,
+ CAPS_LOCK: 20,
+ ESC: 27,
+ SPACE: 32,
+ PAGE_UP: 33, // also NUM_NORTH_EAST
+ PAGE_DOWN: 34, // also NUM_SOUTH_EAST
+ END: 35, // also NUM_SOUTH_WEST
+ HOME: 36, // also NUM_NORTH_WEST
+ LEFT: 37, // also NUM_WEST
+ UP: 38, // also NUM_NORTH
+ RIGHT: 39, // also NUM_EAST
+ DOWN: 40, // also NUM_SOUTH
+ PRINT_SCREEN: 44,
+ INSERT: 45, // also NUM_INSERT
+ DELETE: 46, // also NUM_DELETE
+ ZERO: 48,
+ ONE: 49,
+ TWO: 50,
+ THREE: 51,
+ FOUR: 52,
+ FIVE: 53,
+ SIX: 54,
+ SEVEN: 55,
+ EIGHT: 56,
+ NINE: 57,
+ FF_SEMICOLON: 59, // Firefox (Gecko) fires this for semicolon instead of 186
+ FF_EQUALS: 61, // Firefox (Gecko) fires this for equals instead of 187
+ FF_DASH: 173, // Firefox (Gecko) fires this for dash instead of 189
+ QUESTION_MARK: 63, // needs localization
+ A: 65,
+ B: 66,
+ C: 67,
+ D: 68,
+ E: 69,
+ F: 70,
+ G: 71,
+ H: 72,
+ I: 73,
+ J: 74,
+ K: 75,
+ L: 76,
+ M: 77,
+ N: 78,
+ O: 79,
+ P: 80,
+ Q: 81,
+ R: 82,
+ S: 83,
+ T: 84,
+ U: 85,
+ V: 86,
+ W: 87,
+ X: 88,
+ Y: 89,
+ Z: 90,
+ META: 91, // WIN_KEY_LEFT
+ WIN_KEY_RIGHT: 92,
+ CONTEXT_MENU: 93,
+ NUM_ZERO: 96,
+ NUM_ONE: 97,
+ NUM_TWO: 98,
+ NUM_THREE: 99,
+ NUM_FOUR: 100,
+ NUM_FIVE: 101,
+ NUM_SIX: 102,
+ NUM_SEVEN: 103,
+ NUM_EIGHT: 104,
+ NUM_NINE: 105,
+ NUM_MULTIPLY: 106,
+ NUM_PLUS: 107,
+ NUM_MINUS: 109,
+ NUM_PERIOD: 110,
+ NUM_DIVISION: 111,
+ F1: 112,
+ F2: 113,
+ F3: 114,
+ F4: 115,
+ F5: 116,
+ F6: 117,
+ F7: 118,
+ F8: 119,
+ F9: 120,
+ F10: 121,
+ F11: 122,
+ F12: 123,
+ NUMLOCK: 144,
+ SCROLL_LOCK: 145,
+
+ // OS-specific media keys like volume controls and browser controls.
+ FIRST_MEDIA_KEY: 166,
+ LAST_MEDIA_KEY: 183,
+
+ SEMICOLON: 186, // needs localization
+ DASH: 189, // needs localization
+ EQUALS: 187, // needs localization
+ COMMA: 188, // needs localization
+ PERIOD: 190, // needs localization
+ SLASH: 191, // needs localization
+ APOSTROPHE: 192, // needs localization
+ TILDE: 192, // needs localization
+ SINGLE_QUOTE: 222, // needs localization
+ OPEN_SQUARE_BRACKET: 219, // needs localization
+ BACKSLASH: 220, // needs localization
+ CLOSE_SQUARE_BRACKET: 221, // needs localization
+ WIN_KEY: 224,
+ MAC_FF_META: 224, // Firefox (Gecko) fires this for the meta key instead of 91
+ MAC_WK_CMD_LEFT: 91, // WebKit Left Command key fired, same as META
+ MAC_WK_CMD_RIGHT: 93, // WebKit Right Command key fired, different from META
+ WIN_IME: 229,
+
+ // We've seen users whose machines fire this keycode at regular one
+ // second intervals. The common thread among these users is that
+ // they're all using Dell Inspiron laptops, so we suspect that this
+ // indicates a hardware/bios problem.
+ // http://en.community.dell.com/support-forums/laptop/f/3518/p/19285957/19523128.aspx
+ PHANTOM: 255
+};
+
+
+/**
+ * Returns true if the event contains a text modifying key.
+ * @param {goog.events.BrowserEvent} e A key event.
+ * @return {boolean} Whether it's a text modifying key.
+ */
+goog.events.KeyCodes.isTextModifyingKeyEvent = function(e) {
+ if (e.altKey && !e.ctrlKey ||
+ e.metaKey ||
+ // Function keys don't generate text
+ e.keyCode >= goog.events.KeyCodes.F1 &&
+ e.keyCode <= goog.events.KeyCodes.F12) {
+ return false;
+ }
+
+ // The following keys are quite harmless, even in combination with
+ // CTRL, ALT or SHIFT.
+ switch (e.keyCode) {
+ case goog.events.KeyCodes.ALT:
+ case goog.events.KeyCodes.CAPS_LOCK:
+ case goog.events.KeyCodes.CONTEXT_MENU:
+ case goog.events.KeyCodes.CTRL:
+ case goog.events.KeyCodes.DOWN:
+ case goog.events.KeyCodes.END:
+ case goog.events.KeyCodes.ESC:
+ case goog.events.KeyCodes.HOME:
+ case goog.events.KeyCodes.INSERT:
+ case goog.events.KeyCodes.LEFT:
+ case goog.events.KeyCodes.MAC_FF_META:
+ case goog.events.KeyCodes.META:
+ case goog.events.KeyCodes.NUMLOCK:
+ case goog.events.KeyCodes.NUM_CENTER:
+ case goog.events.KeyCodes.PAGE_DOWN:
+ case goog.events.KeyCodes.PAGE_UP:
+ case goog.events.KeyCodes.PAUSE:
+ case goog.events.KeyCodes.PHANTOM:
+ case goog.events.KeyCodes.PRINT_SCREEN:
+ case goog.events.KeyCodes.RIGHT:
+ case goog.events.KeyCodes.SCROLL_LOCK:
+ case goog.events.KeyCodes.SHIFT:
+ case goog.events.KeyCodes.UP:
+ case goog.events.KeyCodes.WIN_KEY:
+ case goog.events.KeyCodes.WIN_KEY_RIGHT:
+ return false;
+ case goog.events.KeyCodes.WIN_KEY_FF_LINUX:
+ return !goog.userAgent.GECKO;
+ default:
+ return e.keyCode < goog.events.KeyCodes.FIRST_MEDIA_KEY ||
+ e.keyCode > goog.events.KeyCodes.LAST_MEDIA_KEY;
+ }
+};
+
+
+/**
+ * Returns true if the key fires a keypress event in the current browser.
+ *
+ * Accoridng to MSDN [1] IE only fires keypress events for the following keys:
+ * - Letters: A - Z (uppercase and lowercase)
+ * - Numerals: 0 - 9
+ * - Symbols: ! @ # $ % ^ & * ( ) _ - + = < [ ] { } , . / ? \ | ' ` " ~
+ * - System: ESC, SPACEBAR, ENTER
+ *
+ * That's not entirely correct though, for instance there's no distinction
+ * between upper and lower case letters.
+ *
+ * [1] http://msdn2.microsoft.com/en-us/library/ms536939(VS.85).aspx)
+ *
+ * Safari is similar to IE, but does not fire keypress for ESC.
+ *
+ * Additionally, IE6 does not fire keydown or keypress events for letters when
+ * the control or alt keys are held down and the shift key is not. IE7 does
+ * fire keydown in these cases, though, but not keypress.
+ *
+ * @param {number} keyCode A key code.
+ * @param {number=} opt_heldKeyCode Key code of a currently-held key.
+ * @param {boolean=} opt_shiftKey Whether the shift key is held down.
+ * @param {boolean=} opt_ctrlKey Whether the control key is held down.
+ * @param {boolean=} opt_altKey Whether the alt key is held down.
+ * @return {boolean} Whether it's a key that fires a keypress event.
+ */
+goog.events.KeyCodes.firesKeyPressEvent = function(keyCode, opt_heldKeyCode,
+ opt_shiftKey, opt_ctrlKey, opt_altKey) {
+ if (!goog.userAgent.IE &&
+ !(goog.userAgent.WEBKIT && goog.userAgent.isVersionOrHigher('525'))) {
+ return true;
+ }
+
+ if (goog.userAgent.MAC && opt_altKey) {
+ return goog.events.KeyCodes.isCharacterKey(keyCode);
+ }
+
+ // Alt but not AltGr which is represented as Alt+Ctrl.
+ if (opt_altKey && !opt_ctrlKey) {
+ return false;
+ }
+
+ // Saves Ctrl or Alt + key for IE and WebKit 525+, which won't fire keypress.
+ // Non-IE browsers and WebKit prior to 525 won't get this far so no need to
+ // check the user agent.
+ if (goog.isNumber(opt_heldKeyCode)) {
+ opt_heldKeyCode = goog.events.KeyCodes.normalizeKeyCode(opt_heldKeyCode);
+ }
+ if (!opt_shiftKey &&
+ (opt_heldKeyCode == goog.events.KeyCodes.CTRL ||
+ opt_heldKeyCode == goog.events.KeyCodes.ALT ||
+ goog.userAgent.MAC &&
+ opt_heldKeyCode == goog.events.KeyCodes.META)) {
+ return false;
+ }
+
+ // Some keys with Ctrl/Shift do not issue keypress in WEBKIT.
+ if (goog.userAgent.WEBKIT && opt_ctrlKey && opt_shiftKey) {
+ switch (keyCode) {
+ case goog.events.KeyCodes.BACKSLASH:
+ case goog.events.KeyCodes.OPEN_SQUARE_BRACKET:
+ case goog.events.KeyCodes.CLOSE_SQUARE_BRACKET:
+ case goog.events.KeyCodes.TILDE:
+ case goog.events.KeyCodes.SEMICOLON:
+ case goog.events.KeyCodes.DASH:
+ case goog.events.KeyCodes.EQUALS:
+ case goog.events.KeyCodes.COMMA:
+ case goog.events.KeyCodes.PERIOD:
+ case goog.events.KeyCodes.SLASH:
+ case goog.events.KeyCodes.APOSTROPHE:
+ case goog.events.KeyCodes.SINGLE_QUOTE:
+ return false;
+ }
+ }
+
+ // When Ctrl+<somekey> is held in IE, it only fires a keypress once, but it
+ // continues to fire keydown events as the event repeats.
+ if (goog.userAgent.IE && opt_ctrlKey && opt_heldKeyCode == keyCode) {
+ return false;
+ }
+
+ switch (keyCode) {
+ case goog.events.KeyCodes.ENTER:
+ return true;
+ case goog.events.KeyCodes.ESC:
+ return !goog.userAgent.WEBKIT;
+ }
+
+ return goog.events.KeyCodes.isCharacterKey(keyCode);
+};
+
+
+/**
+ * Returns true if the key produces a character.
+ * This does not cover characters on non-US keyboards (Russian, Hebrew, etc.).
+ *
+ * @param {number} keyCode A key code.
+ * @return {boolean} Whether it's a character key.
+ */
+goog.events.KeyCodes.isCharacterKey = function(keyCode) {
+ if (keyCode >= goog.events.KeyCodes.ZERO &&
+ keyCode <= goog.events.KeyCodes.NINE) {
+ return true;
+ }
+
+ if (keyCode >= goog.events.KeyCodes.NUM_ZERO &&
+ keyCode <= goog.events.KeyCodes.NUM_MULTIPLY) {
+ return true;
+ }
+
+ if (keyCode >= goog.events.KeyCodes.A &&
+ keyCode <= goog.events.KeyCodes.Z) {
+ return true;
+ }
+
+ // Safari sends zero key code for non-latin characters.
+ if (goog.userAgent.WEBKIT && keyCode == 0) {
+ return true;
+ }
+
+ switch (keyCode) {
+ case goog.events.KeyCodes.SPACE:
+ case goog.events.KeyCodes.QUESTION_MARK:
+ case goog.events.KeyCodes.NUM_PLUS:
+ case goog.events.KeyCodes.NUM_MINUS:
+ case goog.events.KeyCodes.NUM_PERIOD:
+ case goog.events.KeyCodes.NUM_DIVISION:
+ case goog.events.KeyCodes.SEMICOLON:
+ case goog.events.KeyCodes.FF_SEMICOLON:
+ case goog.events.KeyCodes.DASH:
+ case goog.events.KeyCodes.EQUALS:
+ case goog.events.KeyCodes.FF_EQUALS:
+ case goog.events.KeyCodes.COMMA:
+ case goog.events.KeyCodes.PERIOD:
+ case goog.events.KeyCodes.SLASH:
+ case goog.events.KeyCodes.APOSTROPHE:
+ case goog.events.KeyCodes.SINGLE_QUOTE:
+ case goog.events.KeyCodes.OPEN_SQUARE_BRACKET:
+ case goog.events.KeyCodes.BACKSLASH:
+ case goog.events.KeyCodes.CLOSE_SQUARE_BRACKET:
+ return true;
+ default:
+ return false;
+ }
+};
+
+
+/**
+ * Normalizes key codes from OS/Browser-specific value to the general one.
+ * @param {number} keyCode The native key code.
+ * @return {number} The normalized key code.
+ */
+goog.events.KeyCodes.normalizeKeyCode = function(keyCode) {
+ if (goog.userAgent.GECKO) {
+ return goog.events.KeyCodes.normalizeGeckoKeyCode(keyCode);
+ } else if (goog.userAgent.MAC && goog.userAgent.WEBKIT) {
+ return goog.events.KeyCodes.normalizeMacWebKitKeyCode(keyCode);
+ } else {
+ return keyCode;
+ }
+};
+
+
+/**
+ * Normalizes key codes from their Gecko-specific value to the general one.
+ * @param {number} keyCode The native key code.
+ * @return {number} The normalized key code.
+ */
+goog.events.KeyCodes.normalizeGeckoKeyCode = function(keyCode) {
+ switch (keyCode) {
+ case goog.events.KeyCodes.FF_EQUALS:
+ return goog.events.KeyCodes.EQUALS;
+ case goog.events.KeyCodes.FF_SEMICOLON:
+ return goog.events.KeyCodes.SEMICOLON;
+ case goog.events.KeyCodes.FF_DASH:
+ return goog.events.KeyCodes.DASH;
+ case goog.events.KeyCodes.MAC_FF_META:
+ return goog.events.KeyCodes.META;
+ case goog.events.KeyCodes.WIN_KEY_FF_LINUX:
+ return goog.events.KeyCodes.WIN_KEY;
+ default:
+ return keyCode;
+ }
+};
+
+
+/**
+ * Normalizes key codes from their Mac WebKit-specific value to the general one.
+ * @param {number} keyCode The native key code.
+ * @return {number} The normalized key code.
+ */
+goog.events.KeyCodes.normalizeMacWebKitKeyCode = function(keyCode) {
+ switch (keyCode) {
+ case goog.events.KeyCodes.MAC_WK_CMD_RIGHT: // 93
+ return goog.events.KeyCodes.META; // 91
+ default:
+ return keyCode;
+ }
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/events/keyhandler.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/keyhandler.js
new file mode 100644
index 0000000..6a7662f
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/keyhandler.js
@@ -0,0 +1,556 @@
+// Copyright 2007 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview This file contains a class for working with keyboard events
+ * that repeat consistently across browsers and platforms. It also unifies the
+ * key code so that it is the same in all browsers and platforms.
+ *
+ * Different web browsers have very different keyboard event handling. Most
+ * importantly is that only certain browsers repeat keydown events:
+ * IE, Opera, FF/Win32, and Safari 3 repeat keydown events.
+ * FF/Mac and Safari 2 do not.
+ *
+ * For the purposes of this code, "Safari 3" means WebKit 525+, when WebKit
+ * decided that they should try to match IE's key handling behavior.
+ * Safari 3.0.4, which shipped with Leopard (WebKit 523), has the
+ * Safari 2 behavior.
+ *
+ * Firefox, Safari, Opera prevent on keypress
+ *
+ * IE prevents on keydown
+ *
+ * Firefox does not fire keypress for shift, ctrl, alt
+ * Firefox does fire keydown for shift, ctrl, alt, meta
+ * Firefox does not repeat keydown for shift, ctrl, alt, meta
+ *
+ * Firefox does not fire keypress for up and down in an input
+ *
+ * Opera fires keypress for shift, ctrl, alt, meta
+ * Opera does not repeat keypress for shift, ctrl, alt, meta
+ *
+ * Safari 2 and 3 do not fire keypress for shift, ctrl, alt
+ * Safari 2 does not fire keydown for shift, ctrl, alt
+ * Safari 3 *does* fire keydown for shift, ctrl, alt
+ *
+ * IE provides the keycode for keyup/down events and the charcode (in the
+ * keycode field) for keypress.
+ *
+ * Mozilla provides the keycode for keyup/down and the charcode for keypress
+ * unless it's a non text modifying key in which case the keycode is provided.
+ *
+ * Safari 3 provides the keycode and charcode for all events.
+ *
+ * Opera provides the keycode for keyup/down event and either the charcode or
+ * the keycode (in the keycode field) for keypress events.
+ *
+ * Firefox x11 doesn't fire keydown events if a another key is already held down
+ * until the first key is released. This can cause a key event to be fired with
+ * a keyCode for the first key and a charCode for the second key.
+ *
+ * Safari in keypress
+ *
+ * charCode keyCode which
+ * ENTER: 13 13 13
+ * F1: 63236 63236 63236
+ * F8: 63243 63243 63243
+ * ...
+ * p: 112 112 112
+ * P: 80 80 80
+ *
+ * Firefox, keypress:
+ *
+ * charCode keyCode which
+ * ENTER: 0 13 13
+ * F1: 0 112 0
+ * F8: 0 119 0
+ * ...
+ * p: 112 0 112
+ * P: 80 0 80
+ *
+ * Opera, Mac+Win32, keypress:
+ *
+ * charCode keyCode which
+ * ENTER: undefined 13 13
+ * F1: undefined 112 0
+ * F8: undefined 119 0
+ * ...
+ * p: undefined 112 112
+ * P: undefined 80 80
+ *
+ * IE7, keydown
+ *
+ * charCode keyCode which
+ * ENTER: undefined 13 undefined
+ * F1: undefined 112 undefined
+ * F8: undefined 119 undefined
+ * ...
+ * p: undefined 80 undefined
+ * P: undefined 80 undefined
+ *
+ * @author arv@google.com (Erik Arvidsson)
+ * @author eae@google.com (Emil A Eklund)
+ * @see ../demos/keyhandler.html
+ */
+
+goog.provide('goog.events.KeyEvent');
+goog.provide('goog.events.KeyHandler');
+goog.provide('goog.events.KeyHandler.EventType');
+
+goog.require('goog.events');
+goog.require('goog.events.BrowserEvent');
+goog.require('goog.events.EventTarget');
+goog.require('goog.events.EventType');
+goog.require('goog.events.KeyCodes');
+goog.require('goog.userAgent');
+
+
+
+/**
+ * A wrapper around an element that you want to listen to keyboard events on.
+ * @param {Element|Document=} opt_element The element or document to listen on.
+ * @param {boolean=} opt_capture Whether to listen for browser events in
+ * capture phase (defaults to false).
+ * @constructor
+ * @extends {goog.events.EventTarget}
+ * @final
+ */
+goog.events.KeyHandler = function(opt_element, opt_capture) {
+ goog.events.EventTarget.call(this);
+
+ if (opt_element) {
+ this.attach(opt_element, opt_capture);
+ }
+};
+goog.inherits(goog.events.KeyHandler, goog.events.EventTarget);
+
+
+/**
+ * This is the element that we will listen to the real keyboard events on.
+ * @type {Element|Document|null}
+ * @private
+ */
+goog.events.KeyHandler.prototype.element_ = null;
+
+
+/**
+ * The key for the key press listener.
+ * @type {goog.events.Key}
+ * @private
+ */
+goog.events.KeyHandler.prototype.keyPressKey_ = null;
+
+
+/**
+ * The key for the key down listener.
+ * @type {goog.events.Key}
+ * @private
+ */
+goog.events.KeyHandler.prototype.keyDownKey_ = null;
+
+
+/**
+ * The key for the key up listener.
+ * @type {goog.events.Key}
+ * @private
+ */
+goog.events.KeyHandler.prototype.keyUpKey_ = null;
+
+
+/**
+ * Used to detect keyboard repeat events.
+ * @private
+ * @type {number}
+ */
+goog.events.KeyHandler.prototype.lastKey_ = -1;
+
+
+/**
+ * Keycode recorded for key down events. As most browsers don't report the
+ * keycode in the key press event we need to record it in the key down phase.
+ * @private
+ * @type {number}
+ */
+goog.events.KeyHandler.prototype.keyCode_ = -1;
+
+
+/**
+ * Alt key recorded for key down events. FF on Mac does not report the alt key
+ * flag in the key press event, we need to record it in the key down phase.
+ * @type {boolean}
+ * @private
+ */
+goog.events.KeyHandler.prototype.altKey_ = false;
+
+
+/**
+ * Enum type for the events fired by the key handler
+ * @enum {string}
+ */
+goog.events.KeyHandler.EventType = {
+ KEY: 'key'
+};
+
+
+/**
+ * An enumeration of key codes that Safari 2 does incorrectly
+ * @type {Object}
+ * @private
+ */
+goog.events.KeyHandler.safariKey_ = {
+ '3': goog.events.KeyCodes.ENTER, // 13
+ '12': goog.events.KeyCodes.NUMLOCK, // 144
+ '63232': goog.events.KeyCodes.UP, // 38
+ '63233': goog.events.KeyCodes.DOWN, // 40
+ '63234': goog.events.KeyCodes.LEFT, // 37
+ '63235': goog.events.KeyCodes.RIGHT, // 39
+ '63236': goog.events.KeyCodes.F1, // 112
+ '63237': goog.events.KeyCodes.F2, // 113
+ '63238': goog.events.KeyCodes.F3, // 114
+ '63239': goog.events.KeyCodes.F4, // 115
+ '63240': goog.events.KeyCodes.F5, // 116
+ '63241': goog.events.KeyCodes.F6, // 117
+ '63242': goog.events.KeyCodes.F7, // 118
+ '63243': goog.events.KeyCodes.F8, // 119
+ '63244': goog.events.KeyCodes.F9, // 120
+ '63245': goog.events.KeyCodes.F10, // 121
+ '63246': goog.events.KeyCodes.F11, // 122
+ '63247': goog.events.KeyCodes.F12, // 123
+ '63248': goog.events.KeyCodes.PRINT_SCREEN, // 44
+ '63272': goog.events.KeyCodes.DELETE, // 46
+ '63273': goog.events.KeyCodes.HOME, // 36
+ '63275': goog.events.KeyCodes.END, // 35
+ '63276': goog.events.KeyCodes.PAGE_UP, // 33
+ '63277': goog.events.KeyCodes.PAGE_DOWN, // 34
+ '63289': goog.events.KeyCodes.NUMLOCK, // 144
+ '63302': goog.events.KeyCodes.INSERT // 45
+};
+
+
+/**
+ * An enumeration of key identifiers currently part of the W3C draft for DOM3
+ * and their mappings to keyCodes.
+ * http://www.w3.org/TR/DOM-Level-3-Events/keyset.html#KeySet-Set
+ * This is currently supported in Safari and should be platform independent.
+ * @type {Object}
+ * @private
+ */
+goog.events.KeyHandler.keyIdentifier_ = {
+ 'Up': goog.events.KeyCodes.UP, // 38
+ 'Down': goog.events.KeyCodes.DOWN, // 40
+ 'Left': goog.events.KeyCodes.LEFT, // 37
+ 'Right': goog.events.KeyCodes.RIGHT, // 39
+ 'Enter': goog.events.KeyCodes.ENTER, // 13
+ 'F1': goog.events.KeyCodes.F1, // 112
+ 'F2': goog.events.KeyCodes.F2, // 113
+ 'F3': goog.events.KeyCodes.F3, // 114
+ 'F4': goog.events.KeyCodes.F4, // 115
+ 'F5': goog.events.KeyCodes.F5, // 116
+ 'F6': goog.events.KeyCodes.F6, // 117
+ 'F7': goog.events.KeyCodes.F7, // 118
+ 'F8': goog.events.KeyCodes.F8, // 119
+ 'F9': goog.events.KeyCodes.F9, // 120
+ 'F10': goog.events.KeyCodes.F10, // 121
+ 'F11': goog.events.KeyCodes.F11, // 122
+ 'F12': goog.events.KeyCodes.F12, // 123
+ 'U+007F': goog.events.KeyCodes.DELETE, // 46
+ 'Home': goog.events.KeyCodes.HOME, // 36
+ 'End': goog.events.KeyCodes.END, // 35
+ 'PageUp': goog.events.KeyCodes.PAGE_UP, // 33
+ 'PageDown': goog.events.KeyCodes.PAGE_DOWN, // 34
+ 'Insert': goog.events.KeyCodes.INSERT // 45
+};
+
+
+/**
+ * If true, the KeyEvent fires on keydown. Otherwise, it fires on keypress.
+ *
+ * @type {boolean}
+ * @private
+ */
+goog.events.KeyHandler.USES_KEYDOWN_ = goog.userAgent.IE ||
+ goog.userAgent.WEBKIT && goog.userAgent.isVersionOrHigher('525');
+
+
+/**
+ * If true, the alt key flag is saved during the key down and reused when
+ * handling the key press. FF on Mac does not set the alt flag in the key press
+ * event.
+ * @type {boolean}
+ * @private
+ */
+goog.events.KeyHandler.SAVE_ALT_FOR_KEYPRESS_ = goog.userAgent.MAC &&
+ goog.userAgent.GECKO;
+
+
+/**
+ * Records the keycode for browsers that only returns the keycode for key up/
+ * down events. For browser/key combinations that doesn't trigger a key pressed
+ * event it also fires the patched key event.
+ * @param {goog.events.BrowserEvent} e The key down event.
+ * @private
+ */
+goog.events.KeyHandler.prototype.handleKeyDown_ = function(e) {
+ // Ctrl-Tab and Alt-Tab can cause the focus to be moved to another window
+ // before we've caught a key-up event. If the last-key was one of these we
+ // reset the state.
+ if (goog.userAgent.WEBKIT) {
+ if (this.lastKey_ == goog.events.KeyCodes.CTRL && !e.ctrlKey ||
+ this.lastKey_ == goog.events.KeyCodes.ALT && !e.altKey ||
+ goog.userAgent.MAC &&
+ this.lastKey_ == goog.events.KeyCodes.META && !e.metaKey) {
+ this.lastKey_ = -1;
+ this.keyCode_ = -1;
+ }
+ }
+
+ if (this.lastKey_ == -1) {
+ if (e.ctrlKey && e.keyCode != goog.events.KeyCodes.CTRL) {
+ this.lastKey_ = goog.events.KeyCodes.CTRL;
+ } else if (e.altKey && e.keyCode != goog.events.KeyCodes.ALT) {
+ this.lastKey_ = goog.events.KeyCodes.ALT;
+ } else if (e.metaKey && e.keyCode != goog.events.KeyCodes.META) {
+ this.lastKey_ = goog.events.KeyCodes.META;
+ }
+ }
+
+ if (goog.events.KeyHandler.USES_KEYDOWN_ &&
+ !goog.events.KeyCodes.firesKeyPressEvent(e.keyCode,
+ this.lastKey_, e.shiftKey, e.ctrlKey, e.altKey)) {
+ this.handleEvent(e);
+ } else {
+ this.keyCode_ = goog.events.KeyCodes.normalizeKeyCode(e.keyCode);
+ if (goog.events.KeyHandler.SAVE_ALT_FOR_KEYPRESS_) {
+ this.altKey_ = e.altKey;
+ }
+ }
+};
+
+
+/**
+ * Resets the stored previous values. Needed to be called for webkit which will
+ * not generate a key up for meta key operations. This should only be called
+ * when having finished with repeat key possiblities.
+ */
+goog.events.KeyHandler.prototype.resetState = function() {
+ this.lastKey_ = -1;
+ this.keyCode_ = -1;
+};
+
+
+/**
+ * Clears the stored previous key value, resetting the key repeat status. Uses
+ * -1 because the Safari 3 Windows beta reports 0 for certain keys (like Home
+ * and End.)
+ * @param {goog.events.BrowserEvent} e The keyup event.
+ * @private
+ */
+goog.events.KeyHandler.prototype.handleKeyup_ = function(e) {
+ this.resetState();
+ this.altKey_ = e.altKey;
+};
+
+
+/**
+ * Handles the events on the element.
+ * @param {goog.events.BrowserEvent} e The keyboard event sent from the
+ * browser.
+ */
+goog.events.KeyHandler.prototype.handleEvent = function(e) {
+ var be = e.getBrowserEvent();
+ var keyCode, charCode;
+ var altKey = be.altKey;
+
+ // IE reports the character code in the keyCode field for keypress events.
+ // There are two exceptions however, Enter and Escape.
+ if (goog.userAgent.IE && e.type == goog.events.EventType.KEYPRESS) {
+ keyCode = this.keyCode_;
+ charCode = keyCode != goog.events.KeyCodes.ENTER &&
+ keyCode != goog.events.KeyCodes.ESC ?
+ be.keyCode : 0;
+
+ // Safari reports the character code in the keyCode field for keypress
+ // events but also has a charCode field.
+ } else if (goog.userAgent.WEBKIT &&
+ e.type == goog.events.EventType.KEYPRESS) {
+ keyCode = this.keyCode_;
+ charCode = be.charCode >= 0 && be.charCode < 63232 &&
+ goog.events.KeyCodes.isCharacterKey(keyCode) ?
+ be.charCode : 0;
+
+ // Opera reports the keycode or the character code in the keyCode field.
+ } else if (goog.userAgent.OPERA) {
+ keyCode = this.keyCode_;
+ charCode = goog.events.KeyCodes.isCharacterKey(keyCode) ?
+ be.keyCode : 0;
+
+ // Mozilla reports the character code in the charCode field.
+ } else {
+ keyCode = be.keyCode || this.keyCode_;
+ charCode = be.charCode || 0;
+ if (goog.events.KeyHandler.SAVE_ALT_FOR_KEYPRESS_) {
+ altKey = this.altKey_;
+ }
+ // On the Mac, shift-/ triggers a question mark char code and no key code
+ // (normalized to WIN_KEY), so we synthesize the latter.
+ if (goog.userAgent.MAC &&
+ charCode == goog.events.KeyCodes.QUESTION_MARK &&
+ keyCode == goog.events.KeyCodes.WIN_KEY) {
+ keyCode = goog.events.KeyCodes.SLASH;
+ }
+ }
+
+ keyCode = goog.events.KeyCodes.normalizeKeyCode(keyCode);
+ var key = keyCode;
+ var keyIdentifier = be.keyIdentifier;
+
+ // Correct the key value for certain browser-specific quirks.
+ if (keyCode) {
+ if (keyCode >= 63232 && keyCode in goog.events.KeyHandler.safariKey_) {
+ // NOTE(nicksantos): Safari 3 has fixed this problem,
+ // this is only needed for Safari 2.
+ key = goog.events.KeyHandler.safariKey_[keyCode];
+ } else {
+
+ // Safari returns 25 for Shift+Tab instead of 9.
+ if (keyCode == 25 && e.shiftKey) {
+ key = 9;
+ }
+ }
+ } else if (keyIdentifier &&
+ keyIdentifier in goog.events.KeyHandler.keyIdentifier_) {
+ // This is needed for Safari Windows because it currently doesn't give a
+ // keyCode/which for non printable keys.
+ key = goog.events.KeyHandler.keyIdentifier_[keyIdentifier];
+ }
+
+ // If we get the same keycode as a keydown/keypress without having seen a
+ // keyup event, then this event was caused by key repeat.
+ var repeat = key == this.lastKey_;
+ this.lastKey_ = key;
+
+ var event = new goog.events.KeyEvent(key, charCode, repeat, be);
+ event.altKey = altKey;
+ this.dispatchEvent(event);
+};
+
+
+/**
+ * Returns the element listened on for the real keyboard events.
+ * @return {Element|Document|null} The element listened on for the real
+ * keyboard events.
+ */
+goog.events.KeyHandler.prototype.getElement = function() {
+ return this.element_;
+};
+
+
+/**
+ * Adds the proper key event listeners to the element.
+ * @param {Element|Document} element The element to listen on.
+ * @param {boolean=} opt_capture Whether to listen for browser events in
+ * capture phase (defaults to false).
+ */
+goog.events.KeyHandler.prototype.attach = function(element, opt_capture) {
+ if (this.keyUpKey_) {
+ this.detach();
+ }
+
+ this.element_ = element;
+
+ this.keyPressKey_ = goog.events.listen(this.element_,
+ goog.events.EventType.KEYPRESS,
+ this,
+ opt_capture);
+
+ // Most browsers (Safari 2 being the notable exception) doesn't include the
+ // keyCode in keypress events (IE has the char code in the keyCode field and
+ // Mozilla only included the keyCode if there's no charCode). Thus we have to
+ // listen for keydown to capture the keycode.
+ this.keyDownKey_ = goog.events.listen(this.element_,
+ goog.events.EventType.KEYDOWN,
+ this.handleKeyDown_,
+ opt_capture,
+ this);
+
+
+ this.keyUpKey_ = goog.events.listen(this.element_,
+ goog.events.EventType.KEYUP,
+ this.handleKeyup_,
+ opt_capture,
+ this);
+};
+
+
+/**
+ * Removes the listeners that may exist.
+ */
+goog.events.KeyHandler.prototype.detach = function() {
+ if (this.keyPressKey_) {
+ goog.events.unlistenByKey(this.keyPressKey_);
+ goog.events.unlistenByKey(this.keyDownKey_);
+ goog.events.unlistenByKey(this.keyUpKey_);
+ this.keyPressKey_ = null;
+ this.keyDownKey_ = null;
+ this.keyUpKey_ = null;
+ }
+ this.element_ = null;
+ this.lastKey_ = -1;
+ this.keyCode_ = -1;
+};
+
+
+/** @override */
+goog.events.KeyHandler.prototype.disposeInternal = function() {
+ goog.events.KeyHandler.superClass_.disposeInternal.call(this);
+ this.detach();
+};
+
+
+
+/**
+ * This class is used for the goog.events.KeyHandler.EventType.KEY event and
+ * it overrides the key code with the fixed key code.
+ * @param {number} keyCode The adjusted key code.
+ * @param {number} charCode The unicode character code.
+ * @param {boolean} repeat Whether this event was generated by keyboard repeat.
+ * @param {Event} browserEvent Browser event object.
+ * @constructor
+ * @extends {goog.events.BrowserEvent}
+ * @final
+ */
+goog.events.KeyEvent = function(keyCode, charCode, repeat, browserEvent) {
+ goog.events.BrowserEvent.call(this, browserEvent);
+ this.type = goog.events.KeyHandler.EventType.KEY;
+
+ /**
+ * Keycode of key press.
+ * @type {number}
+ */
+ this.keyCode = keyCode;
+
+ /**
+ * Unicode character code.
+ * @type {number}
+ */
+ this.charCode = charCode;
+
+ /**
+ * True if this event was generated by keyboard auto-repeat (i.e., the user is
+ * holding the key down.)
+ * @type {boolean}
+ */
+ this.repeat = repeat;
+};
+goog.inherits(goog.events.KeyEvent, goog.events.BrowserEvent);
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/events/listenable.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/listenable.js
new file mode 100644
index 0000000..6483f26
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/listenable.js
@@ -0,0 +1,334 @@
+// Copyright 2012 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview An interface for a listenable JavaScript object.
+ */
+
+goog.provide('goog.events.Listenable');
+goog.provide('goog.events.ListenableKey');
+
+/** @suppress {extraRequire} */
+goog.require('goog.events.EventId');
+
+
+
+/**
+ * A listenable interface. A listenable is an object with the ability
+ * to dispatch/broadcast events to "event listeners" registered via
+ * listen/listenOnce.
+ *
+ * The interface allows for an event propagation mechanism similar
+ * to one offered by native browser event targets, such as
+ * capture/bubble mechanism, stopping propagation, and preventing
+ * default actions. Capture/bubble mechanism depends on the ancestor
+ * tree constructed via {@code #getParentEventTarget}; this tree
+ * must be directed acyclic graph. The meaning of default action(s)
+ * in preventDefault is specific to a particular use case.
+ *
+ * Implementations that do not support capture/bubble or can not have
+ * a parent listenable can simply not implement any ability to set the
+ * parent listenable (and have {@code #getParentEventTarget} return
+ * null).
+ *
+ * Implementation of this class can be used with or independently from
+ * goog.events.
+ *
+ * Implementation must call {@code #addImplementation(implClass)}.
+ *
+ * @interface
+ * @see goog.events
+ * @see http://www.w3.org/TR/DOM-Level-2-Events/events.html
+ */
+goog.events.Listenable = function() {};
+
+
+/**
+ * An expando property to indicate that an object implements
+ * goog.events.Listenable.
+ *
+ * See addImplementation/isImplementedBy.
+ *
+ * @type {string}
+ * @const
+ */
+goog.events.Listenable.IMPLEMENTED_BY_PROP =
+ 'closure_listenable_' + ((Math.random() * 1e6) | 0);
+
+
+/**
+ * Marks a given class (constructor) as an implementation of
+ * Listenable, do that we can query that fact at runtime. The class
+ * must have already implemented the interface.
+ * @param {!Function} cls The class constructor. The corresponding
+ * class must have already implemented the interface.
+ */
+goog.events.Listenable.addImplementation = function(cls) {
+ cls.prototype[goog.events.Listenable.IMPLEMENTED_BY_PROP] = true;
+};
+
+
+/**
+ * @param {Object} obj The object to check.
+ * @return {boolean} Whether a given instance implements Listenable. The
+ * class/superclass of the instance must call addImplementation.
+ */
+goog.events.Listenable.isImplementedBy = function(obj) {
+ return !!(obj && obj[goog.events.Listenable.IMPLEMENTED_BY_PROP]);
+};
+
+
+/**
+ * Adds an event listener. A listener can only be added once to an
+ * object and if it is added again the key for the listener is
+ * returned. Note that if the existing listener is a one-off listener
+ * (registered via listenOnce), it will no longer be a one-off
+ * listener after a call to listen().
+ *
+ * @param {string|!goog.events.EventId.<EVENTOBJ>} type The event type id.
+ * @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener Callback
+ * method.
+ * @param {boolean=} opt_useCapture Whether to fire in capture phase
+ * (defaults to false).
+ * @param {SCOPE=} opt_listenerScope Object in whose scope to call the
+ * listener.
+ * @return {goog.events.ListenableKey} Unique key for the listener.
+ * @template SCOPE,EVENTOBJ
+ */
+goog.events.Listenable.prototype.listen;
+
+
+/**
+ * Adds an event listener that is removed automatically after the
+ * listener fired once.
+ *
+ * If an existing listener already exists, listenOnce will do
+ * nothing. In particular, if the listener was previously registered
+ * via listen(), listenOnce() will not turn the listener into a
+ * one-off listener. Similarly, if there is already an existing
+ * one-off listener, listenOnce does not modify the listeners (it is
+ * still a once listener).
+ *
+ * @param {string|!goog.events.EventId.<EVENTOBJ>} type The event type id.
+ * @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener Callback
+ * method.
+ * @param {boolean=} opt_useCapture Whether to fire in capture phase
+ * (defaults to false).
+ * @param {SCOPE=} opt_listenerScope Object in whose scope to call the
+ * listener.
+ * @return {goog.events.ListenableKey} Unique key for the listener.
+ * @template SCOPE,EVENTOBJ
+ */
+goog.events.Listenable.prototype.listenOnce;
+
+
+/**
+ * Removes an event listener which was added with listen() or listenOnce().
+ *
+ * @param {string|!goog.events.EventId.<EVENTOBJ>} type The event type id.
+ * @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener Callback
+ * method.
+ * @param {boolean=} opt_useCapture Whether to fire in capture phase
+ * (defaults to false).
+ * @param {SCOPE=} opt_listenerScope Object in whose scope to call
+ * the listener.
+ * @return {boolean} Whether any listener was removed.
+ * @template SCOPE,EVENTOBJ
+ */
+goog.events.Listenable.prototype.unlisten;
+
+
+/**
+ * Removes an event listener which was added with listen() by the key
+ * returned by listen().
+ *
+ * @param {goog.events.ListenableKey} key The key returned by
+ * listen() or listenOnce().
+ * @return {boolean} Whether any listener was removed.
+ */
+goog.events.Listenable.prototype.unlistenByKey;
+
+
+/**
+ * Dispatches an event (or event like object) and calls all listeners
+ * listening for events of this type. The type of the event is decided by the
+ * type property on the event object.
+ *
+ * If any of the listeners returns false OR calls preventDefault then this
+ * function will return false. If one of the capture listeners calls
+ * stopPropagation, then the bubble listeners won't fire.
+ *
+ * @param {goog.events.EventLike} e Event object.
+ * @return {boolean} If anyone called preventDefault on the event object (or
+ * if any of the listeners returns false) this will also return false.
+ */
+goog.events.Listenable.prototype.dispatchEvent;
+
+
+/**
+ * Removes all listeners from this listenable. If type is specified,
+ * it will only remove listeners of the particular type. otherwise all
+ * registered listeners will be removed.
+ *
+ * @param {string=} opt_type Type of event to remove, default is to
+ * remove all types.
+ * @return {number} Number of listeners removed.
+ */
+goog.events.Listenable.prototype.removeAllListeners;
+
+
+/**
+ * Returns the parent of this event target to use for capture/bubble
+ * mechanism.
+ *
+ * NOTE(user): The name reflects the original implementation of
+ * custom event target ({@code goog.events.EventTarget}). We decided
+ * that changing the name is not worth it.
+ *
+ * @return {goog.events.Listenable} The parent EventTarget or null if
+ * there is no parent.
+ */
+goog.events.Listenable.prototype.getParentEventTarget;
+
+
+/**
+ * Fires all registered listeners in this listenable for the given
+ * type and capture mode, passing them the given eventObject. This
+ * does not perform actual capture/bubble. Only implementors of the
+ * interface should be using this.
+ *
+ * @param {string|!goog.events.EventId.<EVENTOBJ>} type The type of the
+ * listeners to fire.
+ * @param {boolean} capture The capture mode of the listeners to fire.
+ * @param {EVENTOBJ} eventObject The event object to fire.
+ * @return {boolean} Whether all listeners succeeded without
+ * attempting to prevent default behavior. If any listener returns
+ * false or called goog.events.Event#preventDefault, this returns
+ * false.
+ * @template EVENTOBJ
+ */
+goog.events.Listenable.prototype.fireListeners;
+
+
+/**
+ * Gets all listeners in this listenable for the given type and
+ * capture mode.
+ *
+ * @param {string|!goog.events.EventId} type The type of the listeners to fire.
+ * @param {boolean} capture The capture mode of the listeners to fire.
+ * @return {!Array.<goog.events.ListenableKey>} An array of registered
+ * listeners.
+ * @template EVENTOBJ
+ */
+goog.events.Listenable.prototype.getListeners;
+
+
+/**
+ * Gets the goog.events.ListenableKey for the event or null if no such
+ * listener is in use.
+ *
+ * @param {string|!goog.events.EventId.<EVENTOBJ>} type The name of the event
+ * without the 'on' prefix.
+ * @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener The
+ * listener function to get.
+ * @param {boolean} capture Whether the listener is a capturing listener.
+ * @param {SCOPE=} opt_listenerScope Object in whose scope to call the
+ * listener.
+ * @return {goog.events.ListenableKey} the found listener or null if not found.
+ * @template SCOPE,EVENTOBJ
+ */
+goog.events.Listenable.prototype.getListener;
+
+
+/**
+ * Whether there is any active listeners matching the specified
+ * signature. If either the type or capture parameters are
+ * unspecified, the function will match on the remaining criteria.
+ *
+ * @param {string|!goog.events.EventId.<EVENTOBJ>=} opt_type Event type.
+ * @param {boolean=} opt_capture Whether to check for capture or bubble
+ * listeners.
+ * @return {boolean} Whether there is any active listeners matching
+ * the requested type and/or capture phase.
+ * @template EVENTOBJ
+ */
+goog.events.Listenable.prototype.hasListener;
+
+
+
+/**
+ * An interface that describes a single registered listener.
+ * @interface
+ */
+goog.events.ListenableKey = function() {};
+
+
+/**
+ * Counter used to create a unique key
+ * @type {number}
+ * @private
+ */
+goog.events.ListenableKey.counter_ = 0;
+
+
+/**
+ * Reserves a key to be used for ListenableKey#key field.
+ * @return {number} A number to be used to fill ListenableKey#key
+ * field.
+ */
+goog.events.ListenableKey.reserveKey = function() {
+ return ++goog.events.ListenableKey.counter_;
+};
+
+
+/**
+ * The source event target.
+ * @type {!(Object|goog.events.Listenable|goog.events.EventTarget)}
+ */
+goog.events.ListenableKey.prototype.src;
+
+
+/**
+ * The event type the listener is listening to.
+ * @type {string}
+ */
+goog.events.ListenableKey.prototype.type;
+
+
+/**
+ * The listener function.
+ * @type {function(?):?|{handleEvent:function(?):?}|null}
+ */
+goog.events.ListenableKey.prototype.listener;
+
+
+/**
+ * Whether the listener works on capture phase.
+ * @type {boolean}
+ */
+goog.events.ListenableKey.prototype.capture;
+
+
+/**
+ * The 'this' object for the listener function's scope.
+ * @type {Object}
+ */
+goog.events.ListenableKey.prototype.handler;
+
+
+/**
+ * A globally unique number to identify the key.
+ * @type {number}
+ */
+goog.events.ListenableKey.prototype.key;
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/events/listener.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/listener.js
new file mode 100644
index 0000000..60c7370
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/listener.js
@@ -0,0 +1,131 @@
+// Copyright 2005 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Listener object.
+ * @see ../demos/events.html
+ */
+
+goog.provide('goog.events.Listener');
+
+goog.require('goog.events.ListenableKey');
+
+
+
+/**
+ * Simple class that stores information about a listener
+ * @param {!Function} listener Callback function.
+ * @param {Function} proxy Wrapper for the listener that patches the event.
+ * @param {EventTarget|goog.events.Listenable} src Source object for
+ * the event.
+ * @param {string} type Event type.
+ * @param {boolean} capture Whether in capture or bubble phase.
+ * @param {Object=} opt_handler Object in whose context to execute the callback.
+ * @implements {goog.events.ListenableKey}
+ * @constructor
+ */
+goog.events.Listener = function(
+ listener, proxy, src, type, capture, opt_handler) {
+ if (goog.events.Listener.ENABLE_MONITORING) {
+ this.creationStack = new Error().stack;
+ }
+
+ /**
+ * Callback function.
+ * @type {Function}
+ */
+ this.listener = listener;
+
+ /**
+ * A wrapper over the original listener. This is used solely to
+ * handle native browser events (it is used to simulate the capture
+ * phase and to patch the event object).
+ * @type {Function}
+ */
+ this.proxy = proxy;
+
+ /**
+ * Object or node that callback is listening to
+ * @type {EventTarget|goog.events.Listenable}
+ */
+ this.src = src;
+
+ /**
+ * The event type.
+ * @const {string}
+ */
+ this.type = type;
+
+ /**
+ * Whether the listener is being called in the capture or bubble phase
+ * @const {boolean}
+ */
+ this.capture = !!capture;
+
+ /**
+ * Optional object whose context to execute the listener in
+ * @type {Object|undefined}
+ */
+ this.handler = opt_handler;
+
+ /**
+ * The key of the listener.
+ * @const {number}
+ * @override
+ */
+ this.key = goog.events.ListenableKey.reserveKey();
+
+ /**
+ * Whether to remove the listener after it has been called.
+ * @type {boolean}
+ */
+ this.callOnce = false;
+
+ /**
+ * Whether the listener has been removed.
+ * @type {boolean}
+ */
+ this.removed = false;
+};
+
+
+/**
+ * @define {boolean} Whether to enable the monitoring of the
+ * goog.events.Listener instances. Switching on the monitoring is only
+ * recommended for debugging because it has a significant impact on
+ * performance and memory usage. If switched off, the monitoring code
+ * compiles down to 0 bytes.
+ */
+goog.define('goog.events.Listener.ENABLE_MONITORING', false);
+
+
+/**
+ * If monitoring the goog.events.Listener instances is enabled, stores the
+ * creation stack trace of the Disposable instance.
+ * @type {string}
+ */
+goog.events.Listener.prototype.creationStack;
+
+
+/**
+ * Marks this listener as removed. This also remove references held by
+ * this listener object (such as listener and event source).
+ */
+goog.events.Listener.prototype.markAsRemoved = function() {
+ this.removed = true;
+ this.listener = null;
+ this.proxy = null;
+ this.src = null;
+ this.handler = null;
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/events/listenermap.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/listenermap.js
new file mode 100644
index 0000000..8930eb6
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/listenermap.js
@@ -0,0 +1,308 @@
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview A map of listeners that provides utility functions to
+ * deal with listeners on an event target. Used by
+ * {@code goog.events.EventTarget}.
+ *
+ * WARNING: Do not use this class from outside goog.events package.
+ *
+ * @visibility {//closure/goog/bin/sizetests:__pkg__}
+ * @visibility {//closure/goog/events:__pkg__}
+ * @visibility {//closure/goog/labs/events:__pkg__}
+ */
+
+goog.provide('goog.events.ListenerMap');
+
+goog.require('goog.array');
+goog.require('goog.events.Listener');
+goog.require('goog.object');
+
+
+
+/**
+ * Creates a new listener map.
+ * @param {EventTarget|goog.events.Listenable} src The src object.
+ * @constructor
+ * @final
+ */
+goog.events.ListenerMap = function(src) {
+ /** @type {EventTarget|goog.events.Listenable} */
+ this.src = src;
+
+ /**
+ * Maps of event type to an array of listeners.
+ * @type {Object.<string, !Array.<!goog.events.Listener>>}
+ */
+ this.listeners = {};
+
+ /**
+ * The count of types in this map that have registered listeners.
+ * @private {number}
+ */
+ this.typeCount_ = 0;
+};
+
+
+/**
+ * @return {number} The count of event types in this map that actually
+ * have registered listeners.
+ */
+goog.events.ListenerMap.prototype.getTypeCount = function() {
+ return this.typeCount_;
+};
+
+
+/**
+ * @return {number} Total number of registered listeners.
+ */
+goog.events.ListenerMap.prototype.getListenerCount = function() {
+ var count = 0;
+ for (var type in this.listeners) {
+ count += this.listeners[type].length;
+ }
+ return count;
+};
+
+
+/**
+ * Adds an event listener. A listener can only be added once to an
+ * object and if it is added again the key for the listener is
+ * returned.
+ *
+ * Note that a one-off listener will not change an existing listener,
+ * if any. On the other hand a normal listener will change existing
+ * one-off listener to become a normal listener.
+ *
+ * @param {string|!goog.events.EventId} type The listener event type.
+ * @param {!Function} listener This listener callback method.
+ * @param {boolean} callOnce Whether the listener is a one-off
+ * listener.
+ * @param {boolean=} opt_useCapture The capture mode of the listener.
+ * @param {Object=} opt_listenerScope Object in whose scope to call the
+ * listener.
+ * @return {goog.events.ListenableKey} Unique key for the listener.
+ */
+goog.events.ListenerMap.prototype.add = function(
+ type, listener, callOnce, opt_useCapture, opt_listenerScope) {
+ var typeStr = type.toString();
+ var listenerArray = this.listeners[typeStr];
+ if (!listenerArray) {
+ listenerArray = this.listeners[typeStr] = [];
+ this.typeCount_++;
+ }
+
+ var listenerObj;
+ var index = goog.events.ListenerMap.findListenerIndex_(
+ listenerArray, listener, opt_useCapture, opt_listenerScope);
+ if (index > -1) {
+ listenerObj = listenerArray[index];
+ if (!callOnce) {
+ // Ensure that, if there is an existing callOnce listener, it is no
+ // longer a callOnce listener.
+ listenerObj.callOnce = false;
+ }
+ } else {
+ listenerObj = new goog.events.Listener(
+ listener, null, this.src, typeStr, !!opt_useCapture, opt_listenerScope);
+ listenerObj.callOnce = callOnce;
+ listenerArray.push(listenerObj);
+ }
+ return listenerObj;
+};
+
+
+/**
+ * Removes a matching listener.
+ * @param {string|!goog.events.EventId} type The listener event type.
+ * @param {!Function} listener This listener callback method.
+ * @param {boolean=} opt_useCapture The capture mode of the listener.
+ * @param {Object=} opt_listenerScope Object in whose scope to call the
+ * listener.
+ * @return {boolean} Whether any listener was removed.
+ */
+goog.events.ListenerMap.prototype.remove = function(
+ type, listener, opt_useCapture, opt_listenerScope) {
+ var typeStr = type.toString();
+ if (!(typeStr in this.listeners)) {
+ return false;
+ }
+
+ var listenerArray = this.listeners[typeStr];
+ var index = goog.events.ListenerMap.findListenerIndex_(
+ listenerArray, listener, opt_useCapture, opt_listenerScope);
+ if (index > -1) {
+ var listenerObj = listenerArray[index];
+ listenerObj.markAsRemoved();
+ goog.array.removeAt(listenerArray, index);
+ if (listenerArray.length == 0) {
+ delete this.listeners[typeStr];
+ this.typeCount_--;
+ }
+ return true;
+ }
+ return false;
+};
+
+
+/**
+ * Removes the given listener object.
+ * @param {goog.events.ListenableKey} listener The listener to remove.
+ * @return {boolean} Whether the listener is removed.
+ */
+goog.events.ListenerMap.prototype.removeByKey = function(listener) {
+ var type = listener.type;
+ if (!(type in this.listeners)) {
+ return false;
+ }
+
+ var removed = goog.array.remove(this.listeners[type], listener);
+ if (removed) {
+ listener.markAsRemoved();
+ if (this.listeners[type].length == 0) {
+ delete this.listeners[type];
+ this.typeCount_--;
+ }
+ }
+ return removed;
+};
+
+
+/**
+ * Removes all listeners from this map. If opt_type is provided, only
+ * listeners that match the given type are removed.
+ * @param {string|!goog.events.EventId=} opt_type Type of event to remove.
+ * @return {number} Number of listeners removed.
+ */
+goog.events.ListenerMap.prototype.removeAll = function(opt_type) {
+ var typeStr = opt_type && opt_type.toString();
+ var count = 0;
+ for (var type in this.listeners) {
+ if (!typeStr || type == typeStr) {
+ var listenerArray = this.listeners[type];
+ for (var i = 0; i < listenerArray.length; i++) {
+ ++count;
+ listenerArray[i].markAsRemoved();
+ }
+ delete this.listeners[type];
+ this.typeCount_--;
+ }
+ }
+ return count;
+};
+
+
+/**
+ * Gets all listeners that match the given type and capture mode. The
+ * returned array is a copy (but the listener objects are not).
+ * @param {string|!goog.events.EventId} type The type of the listeners
+ * to retrieve.
+ * @param {boolean} capture The capture mode of the listeners to retrieve.
+ * @return {!Array.<goog.events.ListenableKey>} An array of matching
+ * listeners.
+ */
+goog.events.ListenerMap.prototype.getListeners = function(type, capture) {
+ var listenerArray = this.listeners[type.toString()];
+ var rv = [];
+ if (listenerArray) {
+ for (var i = 0; i < listenerArray.length; ++i) {
+ var listenerObj = listenerArray[i];
+ if (listenerObj.capture == capture) {
+ rv.push(listenerObj);
+ }
+ }
+ }
+ return rv;
+};
+
+
+/**
+ * Gets the goog.events.ListenableKey for the event or null if no such
+ * listener is in use.
+ *
+ * @param {string|!goog.events.EventId} type The type of the listener
+ * to retrieve.
+ * @param {!Function} listener The listener function to get.
+ * @param {boolean} capture Whether the listener is a capturing listener.
+ * @param {Object=} opt_listenerScope Object in whose scope to call the
+ * listener.
+ * @return {goog.events.ListenableKey} the found listener or null if not found.
+ */
+goog.events.ListenerMap.prototype.getListener = function(
+ type, listener, capture, opt_listenerScope) {
+ var listenerArray = this.listeners[type.toString()];
+ var i = -1;
+ if (listenerArray) {
+ i = goog.events.ListenerMap.findListenerIndex_(
+ listenerArray, listener, capture, opt_listenerScope);
+ }
+ return i > -1 ? listenerArray[i] : null;
+};
+
+
+/**
+ * Whether there is a matching listener. If either the type or capture
+ * parameters are unspecified, the function will match on the
+ * remaining criteria.
+ *
+ * @param {string|!goog.events.EventId=} opt_type The type of the listener.
+ * @param {boolean=} opt_capture The capture mode of the listener.
+ * @return {boolean} Whether there is an active listener matching
+ * the requested type and/or capture phase.
+ */
+goog.events.ListenerMap.prototype.hasListener = function(
+ opt_type, opt_capture) {
+ var hasType = goog.isDef(opt_type);
+ var typeStr = hasType ? opt_type.toString() : '';
+ var hasCapture = goog.isDef(opt_capture);
+
+ return goog.object.some(
+ this.listeners, function(listenerArray, type) {
+ for (var i = 0; i < listenerArray.length; ++i) {
+ if ((!hasType || listenerArray[i].type == typeStr) &&
+ (!hasCapture || listenerArray[i].capture == opt_capture)) {
+ return true;
+ }
+ }
+
+ return false;
+ });
+};
+
+
+/**
+ * Finds the index of a matching goog.events.Listener in the given
+ * listenerArray.
+ * @param {!Array.<!goog.events.Listener>} listenerArray Array of listener.
+ * @param {!Function} listener The listener function.
+ * @param {boolean=} opt_useCapture The capture flag for the listener.
+ * @param {Object=} opt_listenerScope The listener scope.
+ * @return {number} The index of the matching listener within the
+ * listenerArray.
+ * @private
+ */
+goog.events.ListenerMap.findListenerIndex_ = function(
+ listenerArray, listener, opt_useCapture, opt_listenerScope) {
+ for (var i = 0; i < listenerArray.length; ++i) {
+ var listenerObj = listenerArray[i];
+ if (!listenerObj.removed &&
+ listenerObj.listener == listener &&
+ listenerObj.capture == !!opt_useCapture &&
+ listenerObj.handler == opt_listenerScope) {
+ return i;
+ }
+ }
+ return -1;
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/functions/functions.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/functions/functions.js
new file mode 100644
index 0000000..0c70f01
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/functions/functions.js
@@ -0,0 +1,311 @@
+// Copyright 2008 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Utilities for creating functions. Loosely inspired by the
+ * java classes: http://goo.gl/GM0Hmu and http://goo.gl/6k7nI8.
+ *
+ * @author nicksantos@google.com (Nick Santos)
+ */
+
+
+goog.provide('goog.functions');
+
+
+/**
+ * Creates a function that always returns the same value.
+ * @param {T} retValue The value to return.
+ * @return {function():T} The new function.
+ * @template T
+ */
+goog.functions.constant = function(retValue) {
+ return function() {
+ return retValue;
+ };
+};
+
+
+/**
+ * Always returns false.
+ * @type {function(...): boolean}
+ */
+goog.functions.FALSE = goog.functions.constant(false);
+
+
+/**
+ * Always returns true.
+ * @type {function(...): boolean}
+ */
+goog.functions.TRUE = goog.functions.constant(true);
+
+
+/**
+ * Always returns NULL.
+ * @type {function(...): null}
+ */
+goog.functions.NULL = goog.functions.constant(null);
+
+
+/**
+ * A simple function that returns the first argument of whatever is passed
+ * into it.
+ * @param {T=} opt_returnValue The single value that will be returned.
+ * @param {...*} var_args Optional trailing arguments. These are ignored.
+ * @return {T} The first argument passed in, or undefined if nothing was passed.
+ * @template T
+ */
+goog.functions.identity = function(opt_returnValue, var_args) {
+ return opt_returnValue;
+};
+
+
+/**
+ * Creates a function that always throws an error with the given message.
+ * @param {string} message The error message.
+ * @return {!Function} The error-throwing function.
+ */
+goog.functions.error = function(message) {
+ return function() {
+ throw Error(message);
+ };
+};
+
+
+/**
+ * Creates a function that throws the given object.
+ * @param {*} err An object to be thrown.
+ * @return {!Function} The error-throwing function.
+ */
+goog.functions.fail = function(err) {
+ return function() {
+ throw err;
+ }
+};
+
+
+/**
+ * Given a function, create a function that keeps opt_numArgs arguments and
+ * silently discards all additional arguments.
+ * @param {Function} f The original function.
+ * @param {number=} opt_numArgs The number of arguments to keep. Defaults to 0.
+ * @return {!Function} A version of f that only keeps the first opt_numArgs
+ * arguments.
+ */
+goog.functions.lock = function(f, opt_numArgs) {
+ opt_numArgs = opt_numArgs || 0;
+ return function() {
+ return f.apply(this, Array.prototype.slice.call(arguments, 0, opt_numArgs));
+ };
+};
+
+
+/**
+ * Creates a function that returns its nth argument.
+ * @param {number} n The position of the return argument.
+ * @return {!Function} A new function.
+ */
+goog.functions.nth = function(n) {
+ return function() {
+ return arguments[n];
+ };
+};
+
+
+/**
+ * Given a function, create a new function that swallows its return value
+ * and replaces it with a new one.
+ * @param {Function} f A function.
+ * @param {T} retValue A new return value.
+ * @return {function(...[?]):T} A new function.
+ * @template T
+ */
+goog.functions.withReturnValue = function(f, retValue) {
+ return goog.functions.sequence(f, goog.functions.constant(retValue));
+};
+
+
+/**
+ * Creates the composition of the functions passed in.
+ * For example, (goog.functions.compose(f, g))(a) is equivalent to f(g(a)).
+ * @param {function(...[?]):T} fn The final function.
+ * @param {...Function} var_args A list of functions.
+ * @return {function(...[?]):T} The composition of all inputs.
+ * @template T
+ */
+goog.functions.compose = function(fn, var_args) {
+ var functions = arguments;
+ var length = functions.length;
+ return function() {
+ var result;
+ if (length) {
+ result = functions[length - 1].apply(this, arguments);
+ }
+
+ for (var i = length - 2; i >= 0; i--) {
+ result = functions[i].call(this, result);
+ }
+ return result;
+ };
+};
+
+
+/**
+ * Creates a function that calls the functions passed in in sequence, and
+ * returns the value of the last function. For example,
+ * (goog.functions.sequence(f, g))(x) is equivalent to f(x),g(x).
+ * @param {...Function} var_args A list of functions.
+ * @return {!Function} A function that calls all inputs in sequence.
+ */
+goog.functions.sequence = function(var_args) {
+ var functions = arguments;
+ var length = functions.length;
+ return function() {
+ var result;
+ for (var i = 0; i < length; i++) {
+ result = functions[i].apply(this, arguments);
+ }
+ return result;
+ };
+};
+
+
+/**
+ * Creates a function that returns true if each of its components evaluates
+ * to true. The components are evaluated in order, and the evaluation will be
+ * short-circuited as soon as a function returns false.
+ * For example, (goog.functions.and(f, g))(x) is equivalent to f(x) && g(x).
+ * @param {...Function} var_args A list of functions.
+ * @return {function(...[?]):boolean} A function that ANDs its component
+ * functions.
+ */
+goog.functions.and = function(var_args) {
+ var functions = arguments;
+ var length = functions.length;
+ return function() {
+ for (var i = 0; i < length; i++) {
+ if (!functions[i].apply(this, arguments)) {
+ return false;
+ }
+ }
+ return true;
+ };
+};
+
+
+/**
+ * Creates a function that returns true if any of its components evaluates
+ * to true. The components are evaluated in order, and the evaluation will be
+ * short-circuited as soon as a function returns true.
+ * For example, (goog.functions.or(f, g))(x) is equivalent to f(x) || g(x).
+ * @param {...Function} var_args A list of functions.
+ * @return {function(...[?]):boolean} A function that ORs its component
+ * functions.
+ */
+goog.functions.or = function(var_args) {
+ var functions = arguments;
+ var length = functions.length;
+ return function() {
+ for (var i = 0; i < length; i++) {
+ if (functions[i].apply(this, arguments)) {
+ return true;
+ }
+ }
+ return false;
+ };
+};
+
+
+/**
+ * Creates a function that returns the Boolean opposite of a provided function.
+ * For example, (goog.functions.not(f))(x) is equivalent to !f(x).
+ * @param {!Function} f The original function.
+ * @return {function(...[?]):boolean} A function that delegates to f and returns
+ * opposite.
+ */
+goog.functions.not = function(f) {
+ return function() {
+ return !f.apply(this, arguments);
+ };
+};
+
+
+/**
+ * Generic factory function to construct an object given the constructor
+ * and the arguments. Intended to be bound to create object factories.
+ *
+ * Callers should cast the result to the appropriate type for proper type
+ * checking by the compiler.
+ * @param {!Function} constructor The constructor for the Object.
+ * @param {...*} var_args The arguments to be passed to the constructor.
+ * @return {!Object} A new instance of the class given in {@code constructor}.
+ */
+goog.functions.create = function(constructor, var_args) {
+ /**
+ * @constructor
+ * @final
+ */
+ var temp = function() {};
+ temp.prototype = constructor.prototype;
+
+ // obj will have constructor's prototype in its chain and
+ // 'obj instanceof constructor' will be true.
+ var obj = new temp();
+
+ // obj is initialized by constructor.
+ // arguments is only array-like so lacks shift(), but can be used with
+ // the Array prototype function.
+ constructor.apply(obj, Array.prototype.slice.call(arguments, 1));
+ return obj;
+};
+
+
+/**
+ * @define {boolean} Whether the return value cache should be used.
+ * This should only be used to disable caches when testing.
+ */
+goog.define('goog.functions.CACHE_RETURN_VALUE', true);
+
+
+/**
+ * Gives a wrapper function that caches the return value of a parameterless
+ * function when first called.
+ *
+ * When called for the first time, the given function is called and its
+ * return value is cached (thus this is only appropriate for idempotent
+ * functions). Subsequent calls will return the cached return value. This
+ * allows the evaluation of expensive functions to be delayed until first used.
+ *
+ * To cache the return values of functions with parameters, see goog.memoize.
+ *
+ * @param {!function():T} fn A function to lazily evaluate.
+ * @return {!function():T} A wrapped version the function.
+ * @template T
+ */
+goog.functions.cacheReturnValue = function(fn) {
+ var called = false;
+ var value;
+
+ return function() {
+ if (!goog.functions.CACHE_RETURN_VALUE) {
+ return fn();
+ }
+
+ if (!called) {
+ value = fn();
+ called = true;
+ }
+
+ return value;
+ }
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/i18n/bidi.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/i18n/bidi.js
new file mode 100644
index 0000000..f984c28
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/i18n/bidi.js
@@ -0,0 +1,883 @@
+// Copyright 2007 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Utility functions for supporting Bidi issues.
+ */
+
+
+/**
+ * Namespace for bidi supporting functions.
+ */
+goog.provide('goog.i18n.bidi');
+goog.provide('goog.i18n.bidi.Dir');
+goog.provide('goog.i18n.bidi.DirectionalString');
+goog.provide('goog.i18n.bidi.Format');
+
+
+/**
+ * @define {boolean} FORCE_RTL forces the {@link goog.i18n.bidi.IS_RTL} constant
+ * to say that the current locale is a RTL locale. This should only be used
+ * if you want to override the default behavior for deciding whether the
+ * current locale is RTL or not.
+ *
+ * {@see goog.i18n.bidi.IS_RTL}
+ */
+goog.define('goog.i18n.bidi.FORCE_RTL', false);
+
+
+/**
+ * Constant that defines whether or not the current locale is a RTL locale.
+ * If {@link goog.i18n.bidi.FORCE_RTL} is not true, this constant will default
+ * to check that {@link goog.LOCALE} is one of a few major RTL locales.
+ *
+ * <p>This is designed to be a maximally efficient compile-time constant. For
+ * example, for the default goog.LOCALE, compiling
+ * "if (goog.i18n.bidi.IS_RTL) alert('rtl') else {}" should produce no code. It
+ * is this design consideration that limits the implementation to only
+ * supporting a few major RTL locales, as opposed to the broader repertoire of
+ * something like goog.i18n.bidi.isRtlLanguage.
+ *
+ * <p>Since this constant refers to the directionality of the locale, it is up
+ * to the caller to determine if this constant should also be used for the
+ * direction of the UI.
+ *
+ * {@see goog.LOCALE}
+ *
+ * @type {boolean}
+ *
+ * TODO(user): write a test that checks that this is a compile-time constant.
+ */
+goog.i18n.bidi.IS_RTL = goog.i18n.bidi.FORCE_RTL ||
+ (
+ (goog.LOCALE.substring(0, 2).toLowerCase() == 'ar' ||
+ goog.LOCALE.substring(0, 2).toLowerCase() == 'fa' ||
+ goog.LOCALE.substring(0, 2).toLowerCase() == 'he' ||
+ goog.LOCALE.substring(0, 2).toLowerCase() == 'iw' ||
+ goog.LOCALE.substring(0, 2).toLowerCase() == 'ps' ||
+ goog.LOCALE.substring(0, 2).toLowerCase() == 'sd' ||
+ goog.LOCALE.substring(0, 2).toLowerCase() == 'ug' ||
+ goog.LOCALE.substring(0, 2).toLowerCase() == 'ur' ||
+ goog.LOCALE.substring(0, 2).toLowerCase() == 'yi') &&
+ (goog.LOCALE.length == 2 ||
+ goog.LOCALE.substring(2, 3) == '-' ||
+ goog.LOCALE.substring(2, 3) == '_')
+ ) || (
+ goog.LOCALE.length >= 3 &&
+ goog.LOCALE.substring(0, 3).toLowerCase() == 'ckb' &&
+ (goog.LOCALE.length == 3 ||
+ goog.LOCALE.substring(3, 4) == '-' ||
+ goog.LOCALE.substring(3, 4) == '_')
+ );
+
+
+/**
+ * Unicode formatting characters and directionality string constants.
+ * @enum {string}
+ */
+goog.i18n.bidi.Format = {
+ /** Unicode "Left-To-Right Embedding" (LRE) character. */
+ LRE: '\u202A',
+ /** Unicode "Right-To-Left Embedding" (RLE) character. */
+ RLE: '\u202B',
+ /** Unicode "Pop Directional Formatting" (PDF) character. */
+ PDF: '\u202C',
+ /** Unicode "Left-To-Right Mark" (LRM) character. */
+ LRM: '\u200E',
+ /** Unicode "Right-To-Left Mark" (RLM) character. */
+ RLM: '\u200F'
+};
+
+
+/**
+ * Directionality enum.
+ * @enum {number}
+ */
+goog.i18n.bidi.Dir = {
+ /**
+ * Left-to-right.
+ */
+ LTR: 1,
+
+ /**
+ * Right-to-left.
+ */
+ RTL: -1,
+
+ /**
+ * Neither left-to-right nor right-to-left.
+ */
+ NEUTRAL: 0,
+
+ /**
+ * A historical misnomer for NEUTRAL.
+ * @deprecated For "neutral", use NEUTRAL; for "unknown", use null.
+ */
+ UNKNOWN: 0
+};
+
+
+/**
+ * 'right' string constant.
+ * @type {string}
+ */
+goog.i18n.bidi.RIGHT = 'right';
+
+
+/**
+ * 'left' string constant.
+ * @type {string}
+ */
+goog.i18n.bidi.LEFT = 'left';
+
+
+/**
+ * 'left' if locale is RTL, 'right' if not.
+ * @type {string}
+ */
+goog.i18n.bidi.I18N_RIGHT = goog.i18n.bidi.IS_RTL ? goog.i18n.bidi.LEFT :
+ goog.i18n.bidi.RIGHT;
+
+
+/**
+ * 'right' if locale is RTL, 'left' if not.
+ * @type {string}
+ */
+goog.i18n.bidi.I18N_LEFT = goog.i18n.bidi.IS_RTL ? goog.i18n.bidi.RIGHT :
+ goog.i18n.bidi.LEFT;
+
+
+/**
+ * Convert a directionality given in various formats to a goog.i18n.bidi.Dir
+ * constant. Useful for interaction with different standards of directionality
+ * representation.
+ *
+ * @param {goog.i18n.bidi.Dir|number|boolean|null} givenDir Directionality given
+ * in one of the following formats:
+ * 1. A goog.i18n.bidi.Dir constant.
+ * 2. A number (positive = LTR, negative = RTL, 0 = neutral).
+ * 3. A boolean (true = RTL, false = LTR).
+ * 4. A null for unknown directionality.
+ * @param {boolean=} opt_noNeutral Whether a givenDir of zero or
+ * goog.i18n.bidi.Dir.NEUTRAL should be treated as null, i.e. unknown, in
+ * order to preserve legacy behavior.
+ * @return {?goog.i18n.bidi.Dir} A goog.i18n.bidi.Dir constant matching the
+ * given directionality. If given null, returns null (i.e. unknown).
+ */
+goog.i18n.bidi.toDir = function(givenDir, opt_noNeutral) {
+ if (typeof givenDir == 'number') {
+ // This includes the non-null goog.i18n.bidi.Dir case.
+ return givenDir > 0 ? goog.i18n.bidi.Dir.LTR :
+ givenDir < 0 ? goog.i18n.bidi.Dir.RTL :
+ opt_noNeutral ? null : goog.i18n.bidi.Dir.NEUTRAL;
+ } else if (givenDir == null) {
+ return null;
+ } else {
+ // Must be typeof givenDir == 'boolean'.
+ return givenDir ? goog.i18n.bidi.Dir.RTL : goog.i18n.bidi.Dir.LTR;
+ }
+};
+
+
+/**
+ * A practical pattern to identify strong LTR characters. This pattern is not
+ * theoretically correct according to the Unicode standard. It is simplified for
+ * performance and small code size.
+ * @type {string}
+ * @private
+ */
+goog.i18n.bidi.ltrChars_ =
+ 'A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF' +
+ '\u200E\u2C00-\uFB1C\uFE00-\uFE6F\uFEFD-\uFFFF';
+
+
+/**
+ * A practical pattern to identify strong RTL character. This pattern is not
+ * theoretically correct according to the Unicode standard. It is simplified
+ * for performance and small code size.
+ * @type {string}
+ * @private
+ */
+goog.i18n.bidi.rtlChars_ = '\u0591-\u07FF\u200F\uFB1D-\uFDFF\uFE70-\uFEFC';
+
+
+/**
+ * Simplified regular expression for an HTML tag (opening or closing) or an HTML
+ * escape. We might want to skip over such expressions when estimating the text
+ * directionality.
+ * @type {RegExp}
+ * @private
+ */
+goog.i18n.bidi.htmlSkipReg_ = /<[^>]*>|&[^;]+;/g;
+
+
+/**
+ * Returns the input text with spaces instead of HTML tags or HTML escapes, if
+ * opt_isStripNeeded is true. Else returns the input as is.
+ * Useful for text directionality estimation.
+ * Note: the function should not be used in other contexts; it is not 100%
+ * correct, but rather a good-enough implementation for directionality
+ * estimation purposes.
+ * @param {string} str The given string.
+ * @param {boolean=} opt_isStripNeeded Whether to perform the stripping.
+ * Default: false (to retain consistency with calling functions).
+ * @return {string} The given string cleaned of HTML tags / escapes.
+ * @private
+ */
+goog.i18n.bidi.stripHtmlIfNeeded_ = function(str, opt_isStripNeeded) {
+ return opt_isStripNeeded ? str.replace(goog.i18n.bidi.htmlSkipReg_, '') :
+ str;
+};
+
+
+/**
+ * Regular expression to check for RTL characters.
+ * @type {RegExp}
+ * @private
+ */
+goog.i18n.bidi.rtlCharReg_ = new RegExp('[' + goog.i18n.bidi.rtlChars_ + ']');
+
+
+/**
+ * Regular expression to check for LTR characters.
+ * @type {RegExp}
+ * @private
+ */
+goog.i18n.bidi.ltrCharReg_ = new RegExp('[' + goog.i18n.bidi.ltrChars_ + ']');
+
+
+/**
+ * Test whether the given string has any RTL characters in it.
+ * @param {string} str The given string that need to be tested.
+ * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
+ * Default: false.
+ * @return {boolean} Whether the string contains RTL characters.
+ */
+goog.i18n.bidi.hasAnyRtl = function(str, opt_isHtml) {
+ return goog.i18n.bidi.rtlCharReg_.test(goog.i18n.bidi.stripHtmlIfNeeded_(
+ str, opt_isHtml));
+};
+
+
+/**
+ * Test whether the given string has any RTL characters in it.
+ * @param {string} str The given string that need to be tested.
+ * @return {boolean} Whether the string contains RTL characters.
+ * @deprecated Use hasAnyRtl.
+ */
+goog.i18n.bidi.hasRtlChar = goog.i18n.bidi.hasAnyRtl;
+
+
+/**
+ * Test whether the given string has any LTR characters in it.
+ * @param {string} str The given string that need to be tested.
+ * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
+ * Default: false.
+ * @return {boolean} Whether the string contains LTR characters.
+ */
+goog.i18n.bidi.hasAnyLtr = function(str, opt_isHtml) {
+ return goog.i18n.bidi.ltrCharReg_.test(goog.i18n.bidi.stripHtmlIfNeeded_(
+ str, opt_isHtml));
+};
+
+
+/**
+ * Regular expression pattern to check if the first character in the string
+ * is LTR.
+ * @type {RegExp}
+ * @private
+ */
+goog.i18n.bidi.ltrRe_ = new RegExp('^[' + goog.i18n.bidi.ltrChars_ + ']');
+
+
+/**
+ * Regular expression pattern to check if the first character in the string
+ * is RTL.
+ * @type {RegExp}
+ * @private
+ */
+goog.i18n.bidi.rtlRe_ = new RegExp('^[' + goog.i18n.bidi.rtlChars_ + ']');
+
+
+/**
+ * Check if the first character in the string is RTL or not.
+ * @param {string} str The given string that need to be tested.
+ * @return {boolean} Whether the first character in str is an RTL char.
+ */
+goog.i18n.bidi.isRtlChar = function(str) {
+ return goog.i18n.bidi.rtlRe_.test(str);
+};
+
+
+/**
+ * Check if the first character in the string is LTR or not.
+ * @param {string} str The given string that need to be tested.
+ * @return {boolean} Whether the first character in str is an LTR char.
+ */
+goog.i18n.bidi.isLtrChar = function(str) {
+ return goog.i18n.bidi.ltrRe_.test(str);
+};
+
+
+/**
+ * Check if the first character in the string is neutral or not.
+ * @param {string} str The given string that need to be tested.
+ * @return {boolean} Whether the first character in str is a neutral char.
+ */
+goog.i18n.bidi.isNeutralChar = function(str) {
+ return !goog.i18n.bidi.isLtrChar(str) && !goog.i18n.bidi.isRtlChar(str);
+};
+
+
+/**
+ * Regular expressions to check if a piece of text is of LTR directionality
+ * on first character with strong directionality.
+ * @type {RegExp}
+ * @private
+ */
+goog.i18n.bidi.ltrDirCheckRe_ = new RegExp(
+ '^[^' + goog.i18n.bidi.rtlChars_ + ']*[' + goog.i18n.bidi.ltrChars_ + ']');
+
+
+/**
+ * Regular expressions to check if a piece of text is of RTL directionality
+ * on first character with strong directionality.
+ * @type {RegExp}
+ * @private
+ */
+goog.i18n.bidi.rtlDirCheckRe_ = new RegExp(
+ '^[^' + goog.i18n.bidi.ltrChars_ + ']*[' + goog.i18n.bidi.rtlChars_ + ']');
+
+
+/**
+ * Check whether the first strongly directional character (if any) is RTL.
+ * @param {string} str String being checked.
+ * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
+ * Default: false.
+ * @return {boolean} Whether RTL directionality is detected using the first
+ * strongly-directional character method.
+ */
+goog.i18n.bidi.startsWithRtl = function(str, opt_isHtml) {
+ return goog.i18n.bidi.rtlDirCheckRe_.test(goog.i18n.bidi.stripHtmlIfNeeded_(
+ str, opt_isHtml));
+};
+
+
+/**
+ * Check whether the first strongly directional character (if any) is RTL.
+ * @param {string} str String being checked.
+ * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
+ * Default: false.
+ * @return {boolean} Whether RTL directionality is detected using the first
+ * strongly-directional character method.
+ * @deprecated Use startsWithRtl.
+ */
+goog.i18n.bidi.isRtlText = goog.i18n.bidi.startsWithRtl;
+
+
+/**
+ * Check whether the first strongly directional character (if any) is LTR.
+ * @param {string} str String being checked.
+ * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
+ * Default: false.
+ * @return {boolean} Whether LTR directionality is detected using the first
+ * strongly-directional character method.
+ */
+goog.i18n.bidi.startsWithLtr = function(str, opt_isHtml) {
+ return goog.i18n.bidi.ltrDirCheckRe_.test(goog.i18n.bidi.stripHtmlIfNeeded_(
+ str, opt_isHtml));
+};
+
+
+/**
+ * Check whether the first strongly directional character (if any) is LTR.
+ * @param {string} str String being checked.
+ * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
+ * Default: false.
+ * @return {boolean} Whether LTR directionality is detected using the first
+ * strongly-directional character method.
+ * @deprecated Use startsWithLtr.
+ */
+goog.i18n.bidi.isLtrText = goog.i18n.bidi.startsWithLtr;
+
+
+/**
+ * Regular expression to check if a string looks like something that must
+ * always be LTR even in RTL text, e.g. a URL. When estimating the
+ * directionality of text containing these, we treat these as weakly LTR,
+ * like numbers.
+ * @type {RegExp}
+ * @private
+ */
+goog.i18n.bidi.isRequiredLtrRe_ = /^http:\/\/.*/;
+
+
+/**
+ * Check whether the input string either contains no strongly directional
+ * characters or looks like a url.
+ * @param {string} str String being checked.
+ * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
+ * Default: false.
+ * @return {boolean} Whether neutral directionality is detected.
+ */
+goog.i18n.bidi.isNeutralText = function(str, opt_isHtml) {
+ str = goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml);
+ return goog.i18n.bidi.isRequiredLtrRe_.test(str) ||
+ !goog.i18n.bidi.hasAnyLtr(str) && !goog.i18n.bidi.hasAnyRtl(str);
+};
+
+
+/**
+ * Regular expressions to check if the last strongly-directional character in a
+ * piece of text is LTR.
+ * @type {RegExp}
+ * @private
+ */
+goog.i18n.bidi.ltrExitDirCheckRe_ = new RegExp(
+ '[' + goog.i18n.bidi.ltrChars_ + '][^' + goog.i18n.bidi.rtlChars_ + ']*$');
+
+
+/**
+ * Regular expressions to check if the last strongly-directional character in a
+ * piece of text is RTL.
+ * @type {RegExp}
+ * @private
+ */
+goog.i18n.bidi.rtlExitDirCheckRe_ = new RegExp(
+ '[' + goog.i18n.bidi.rtlChars_ + '][^' + goog.i18n.bidi.ltrChars_ + ']*$');
+
+
+/**
+ * Check if the exit directionality a piece of text is LTR, i.e. if the last
+ * strongly-directional character in the string is LTR.
+ * @param {string} str String being checked.
+ * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
+ * Default: false.
+ * @return {boolean} Whether LTR exit directionality was detected.
+ */
+goog.i18n.bidi.endsWithLtr = function(str, opt_isHtml) {
+ return goog.i18n.bidi.ltrExitDirCheckRe_.test(
+ goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml));
+};
+
+
+/**
+ * Check if the exit directionality a piece of text is LTR, i.e. if the last
+ * strongly-directional character in the string is LTR.
+ * @param {string} str String being checked.
+ * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
+ * Default: false.
+ * @return {boolean} Whether LTR exit directionality was detected.
+ * @deprecated Use endsWithLtr.
+ */
+goog.i18n.bidi.isLtrExitText = goog.i18n.bidi.endsWithLtr;
+
+
+/**
+ * Check if the exit directionality a piece of text is RTL, i.e. if the last
+ * strongly-directional character in the string is RTL.
+ * @param {string} str String being checked.
+ * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
+ * Default: false.
+ * @return {boolean} Whether RTL exit directionality was detected.
+ */
+goog.i18n.bidi.endsWithRtl = function(str, opt_isHtml) {
+ return goog.i18n.bidi.rtlExitDirCheckRe_.test(
+ goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml));
+};
+
+
+/**
+ * Check if the exit directionality a piece of text is RTL, i.e. if the last
+ * strongly-directional character in the string is RTL.
+ * @param {string} str String being checked.
+ * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
+ * Default: false.
+ * @return {boolean} Whether RTL exit directionality was detected.
+ * @deprecated Use endsWithRtl.
+ */
+goog.i18n.bidi.isRtlExitText = goog.i18n.bidi.endsWithRtl;
+
+
+/**
+ * A regular expression for matching right-to-left language codes.
+ * See {@link #isRtlLanguage} for the design.
+ * @type {RegExp}
+ * @private
+ */
+goog.i18n.bidi.rtlLocalesRe_ = new RegExp(
+ '^(ar|ckb|dv|he|iw|fa|nqo|ps|sd|ug|ur|yi|' +
+ '.*[-_](Arab|Hebr|Thaa|Nkoo|Tfng))' +
+ '(?!.*[-_](Latn|Cyrl)($|-|_))($|-|_)',
+ 'i');
+
+
+/**
+ * Check if a BCP 47 / III language code indicates an RTL language, i.e. either:
+ * - a language code explicitly specifying one of the right-to-left scripts,
+ * e.g. "az-Arab", or<p>
+ * - a language code specifying one of the languages normally written in a
+ * right-to-left script, e.g. "fa" (Farsi), except ones explicitly specifying
+ * Latin or Cyrillic script (which are the usual LTR alternatives).<p>
+ * The list of right-to-left scripts appears in the 100-199 range in
+ * http://www.unicode.org/iso15924/iso15924-num.html, of which Arabic and
+ * Hebrew are by far the most widely used. We also recognize Thaana, N'Ko, and
+ * Tifinagh, which also have significant modern usage. The rest (Syriac,
+ * Samaritan, Mandaic, etc.) seem to have extremely limited or no modern usage
+ * and are not recognized to save on code size.
+ * The languages usually written in a right-to-left script are taken as those
+ * with Suppress-Script: Hebr|Arab|Thaa|Nkoo|Tfng in
+ * http://www.iana.org/assignments/language-subtag-registry,
+ * as well as Central (or Sorani) Kurdish (ckb), Sindhi (sd) and Uyghur (ug).
+ * Other subtags of the language code, e.g. regions like EG (Egypt), are
+ * ignored.
+ * @param {string} lang BCP 47 (a.k.a III) language code.
+ * @return {boolean} Whether the language code is an RTL language.
+ */
+goog.i18n.bidi.isRtlLanguage = function(lang) {
+ return goog.i18n.bidi.rtlLocalesRe_.test(lang);
+};
+
+
+/**
+ * Regular expression for bracket guard replacement in html.
+ * @type {RegExp}
+ * @private
+ */
+goog.i18n.bidi.bracketGuardHtmlRe_ =
+ /(\(.*?\)+)|(\[.*?\]+)|(\{.*?\}+)|(&lt;.*?(&gt;)+)/g;
+
+
+/**
+ * Regular expression for bracket guard replacement in text.
+ * @type {RegExp}
+ * @private
+ */
+goog.i18n.bidi.bracketGuardTextRe_ =
+ /(\(.*?\)+)|(\[.*?\]+)|(\{.*?\}+)|(<.*?>+)/g;
+
+
+/**
+ * Apply bracket guard using html span tag. This is to address the problem of
+ * messy bracket display frequently happens in RTL layout.
+ * @param {string} s The string that need to be processed.
+ * @param {boolean=} opt_isRtlContext specifies default direction (usually
+ * direction of the UI).
+ * @return {string} The processed string, with all bracket guarded.
+ */
+goog.i18n.bidi.guardBracketInHtml = function(s, opt_isRtlContext) {
+ var useRtl = opt_isRtlContext === undefined ?
+ goog.i18n.bidi.hasAnyRtl(s) : opt_isRtlContext;
+ if (useRtl) {
+ return s.replace(goog.i18n.bidi.bracketGuardHtmlRe_,
+ '<span dir=rtl>$&</span>');
+ }
+ return s.replace(goog.i18n.bidi.bracketGuardHtmlRe_,
+ '<span dir=ltr>$&</span>');
+};
+
+
+/**
+ * Apply bracket guard using LRM and RLM. This is to address the problem of
+ * messy bracket display frequently happens in RTL layout.
+ * This version works for both plain text and html. But it does not work as
+ * good as guardBracketInHtml in some cases.
+ * @param {string} s The string that need to be processed.
+ * @param {boolean=} opt_isRtlContext specifies default direction (usually
+ * direction of the UI).
+ * @return {string} The processed string, with all bracket guarded.
+ */
+goog.i18n.bidi.guardBracketInText = function(s, opt_isRtlContext) {
+ var useRtl = opt_isRtlContext === undefined ?
+ goog.i18n.bidi.hasAnyRtl(s) : opt_isRtlContext;
+ var mark = useRtl ? goog.i18n.bidi.Format.RLM : goog.i18n.bidi.Format.LRM;
+ return s.replace(goog.i18n.bidi.bracketGuardTextRe_, mark + '$&' + mark);
+};
+
+
+/**
+ * Enforce the html snippet in RTL directionality regardless overall context.
+ * If the html piece was enclosed by tag, dir will be applied to existing
+ * tag, otherwise a span tag will be added as wrapper. For this reason, if
+ * html snippet start with with tag, this tag must enclose the whole piece. If
+ * the tag already has a dir specified, this new one will override existing
+ * one in behavior (tested on FF and IE).
+ * @param {string} html The string that need to be processed.
+ * @return {string} The processed string, with directionality enforced to RTL.
+ */
+goog.i18n.bidi.enforceRtlInHtml = function(html) {
+ if (html.charAt(0) == '<') {
+ return html.replace(/<\w+/, '$& dir=rtl');
+ }
+ // '\n' is important for FF so that it won't incorrectly merge span groups
+ return '\n<span dir=rtl>' + html + '</span>';
+};
+
+
+/**
+ * Enforce RTL on both end of the given text piece using unicode BiDi formatting
+ * characters RLE and PDF.
+ * @param {string} text The piece of text that need to be wrapped.
+ * @return {string} The wrapped string after process.
+ */
+goog.i18n.bidi.enforceRtlInText = function(text) {
+ return goog.i18n.bidi.Format.RLE + text + goog.i18n.bidi.Format.PDF;
+};
+
+
+/**
+ * Enforce the html snippet in RTL directionality regardless overall context.
+ * If the html piece was enclosed by tag, dir will be applied to existing
+ * tag, otherwise a span tag will be added as wrapper. For this reason, if
+ * html snippet start with with tag, this tag must enclose the whole piece. If
+ * the tag already has a dir specified, this new one will override existing
+ * one in behavior (tested on FF and IE).
+ * @param {string} html The string that need to be processed.
+ * @return {string} The processed string, with directionality enforced to RTL.
+ */
+goog.i18n.bidi.enforceLtrInHtml = function(html) {
+ if (html.charAt(0) == '<') {
+ return html.replace(/<\w+/, '$& dir=ltr');
+ }
+ // '\n' is important for FF so that it won't incorrectly merge span groups
+ return '\n<span dir=ltr>' + html + '</span>';
+};
+
+
+/**
+ * Enforce LTR on both end of the given text piece using unicode BiDi formatting
+ * characters LRE and PDF.
+ * @param {string} text The piece of text that need to be wrapped.
+ * @return {string} The wrapped string after process.
+ */
+goog.i18n.bidi.enforceLtrInText = function(text) {
+ return goog.i18n.bidi.Format.LRE + text + goog.i18n.bidi.Format.PDF;
+};
+
+
+/**
+ * Regular expression to find dimensions such as "padding: .3 0.4ex 5px 6;"
+ * @type {RegExp}
+ * @private
+ */
+goog.i18n.bidi.dimensionsRe_ =
+ /:\s*([.\d][.\w]*)\s+([.\d][.\w]*)\s+([.\d][.\w]*)\s+([.\d][.\w]*)/g;
+
+
+/**
+ * Regular expression for left.
+ * @type {RegExp}
+ * @private
+ */
+goog.i18n.bidi.leftRe_ = /left/gi;
+
+
+/**
+ * Regular expression for right.
+ * @type {RegExp}
+ * @private
+ */
+goog.i18n.bidi.rightRe_ = /right/gi;
+
+
+/**
+ * Placeholder regular expression for swapping.
+ * @type {RegExp}
+ * @private
+ */
+goog.i18n.bidi.tempRe_ = /%%%%/g;
+
+
+/**
+ * Swap location parameters and 'left'/'right' in CSS specification. The
+ * processed string will be suited for RTL layout. Though this function can
+ * cover most cases, there are always exceptions. It is suggested to put
+ * those exceptions in separate group of CSS string.
+ * @param {string} cssStr CSS spefication string.
+ * @return {string} Processed CSS specification string.
+ */
+goog.i18n.bidi.mirrorCSS = function(cssStr) {
+ return cssStr.
+ // reverse dimensions
+ replace(goog.i18n.bidi.dimensionsRe_, ':$1 $4 $3 $2').
+ replace(goog.i18n.bidi.leftRe_, '%%%%'). // swap left and right
+ replace(goog.i18n.bidi.rightRe_, goog.i18n.bidi.LEFT).
+ replace(goog.i18n.bidi.tempRe_, goog.i18n.bidi.RIGHT);
+};
+
+
+/**
+ * Regular expression for hebrew double quote substitution, finding quote
+ * directly after hebrew characters.
+ * @type {RegExp}
+ * @private
+ */
+goog.i18n.bidi.doubleQuoteSubstituteRe_ = /([\u0591-\u05f2])"/g;
+
+
+/**
+ * Regular expression for hebrew single quote substitution, finding quote
+ * directly after hebrew characters.
+ * @type {RegExp}
+ * @private
+ */
+goog.i18n.bidi.singleQuoteSubstituteRe_ = /([\u0591-\u05f2])'/g;
+
+
+/**
+ * Replace the double and single quote directly after a Hebrew character with
+ * GERESH and GERSHAYIM. In such case, most likely that's user intention.
+ * @param {string} str String that need to be processed.
+ * @return {string} Processed string with double/single quote replaced.
+ */
+goog.i18n.bidi.normalizeHebrewQuote = function(str) {
+ return str.
+ replace(goog.i18n.bidi.doubleQuoteSubstituteRe_, '$1\u05f4').
+ replace(goog.i18n.bidi.singleQuoteSubstituteRe_, '$1\u05f3');
+};
+
+
+/**
+ * Regular expression to split a string into "words" for directionality
+ * estimation based on relative word counts.
+ * @type {RegExp}
+ * @private
+ */
+goog.i18n.bidi.wordSeparatorRe_ = /\s+/;
+
+
+/**
+ * Regular expression to check if a string contains any numerals. Used to
+ * differentiate between completely neutral strings and those containing
+ * numbers, which are weakly LTR.
+ * @type {RegExp}
+ * @private
+ */
+goog.i18n.bidi.hasNumeralsRe_ = /\d/;
+
+
+/**
+ * This constant controls threshold of RTL directionality.
+ * @type {number}
+ * @private
+ */
+goog.i18n.bidi.rtlDetectionThreshold_ = 0.40;
+
+
+/**
+ * Estimates the directionality of a string based on relative word counts.
+ * If the number of RTL words is above a certain percentage of the total number
+ * of strongly directional words, returns RTL.
+ * Otherwise, if any words are strongly or weakly LTR, returns LTR.
+ * Otherwise, returns UNKNOWN, which is used to mean "neutral".
+ * Numbers are counted as weakly LTR.
+ * @param {string} str The string to be checked.
+ * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
+ * Default: false.
+ * @return {goog.i18n.bidi.Dir} Estimated overall directionality of {@code str}.
+ */
+goog.i18n.bidi.estimateDirection = function(str, opt_isHtml) {
+ var rtlCount = 0;
+ var totalCount = 0;
+ var hasWeaklyLtr = false;
+ var tokens = goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml).
+ split(goog.i18n.bidi.wordSeparatorRe_);
+ for (var i = 0; i < tokens.length; i++) {
+ var token = tokens[i];
+ if (goog.i18n.bidi.startsWithRtl(token)) {
+ rtlCount++;
+ totalCount++;
+ } else if (goog.i18n.bidi.isRequiredLtrRe_.test(token)) {
+ hasWeaklyLtr = true;
+ } else if (goog.i18n.bidi.hasAnyLtr(token)) {
+ totalCount++;
+ } else if (goog.i18n.bidi.hasNumeralsRe_.test(token)) {
+ hasWeaklyLtr = true;
+ }
+ }
+
+ return totalCount == 0 ?
+ (hasWeaklyLtr ? goog.i18n.bidi.Dir.LTR : goog.i18n.bidi.Dir.NEUTRAL) :
+ (rtlCount / totalCount > goog.i18n.bidi.rtlDetectionThreshold_ ?
+ goog.i18n.bidi.Dir.RTL : goog.i18n.bidi.Dir.LTR);
+};
+
+
+/**
+ * Check the directionality of a piece of text, return true if the piece of
+ * text should be laid out in RTL direction.
+ * @param {string} str The piece of text that need to be detected.
+ * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
+ * Default: false.
+ * @return {boolean} Whether this piece of text should be laid out in RTL.
+ */
+goog.i18n.bidi.detectRtlDirectionality = function(str, opt_isHtml) {
+ return goog.i18n.bidi.estimateDirection(str, opt_isHtml) ==
+ goog.i18n.bidi.Dir.RTL;
+};
+
+
+/**
+ * Sets text input element's directionality and text alignment based on a
+ * given directionality. Does nothing if the given directionality is unknown or
+ * neutral.
+ * @param {Element} element Input field element to set directionality to.
+ * @param {goog.i18n.bidi.Dir|number|boolean|null} dir Desired directionality,
+ * given in one of the following formats:
+ * 1. A goog.i18n.bidi.Dir constant.
+ * 2. A number (positive = LRT, negative = RTL, 0 = neutral).
+ * 3. A boolean (true = RTL, false = LTR).
+ * 4. A null for unknown directionality.
+ */
+goog.i18n.bidi.setElementDirAndAlign = function(element, dir) {
+ if (element) {
+ dir = goog.i18n.bidi.toDir(dir);
+ if (dir) {
+ element.style.textAlign =
+ dir == goog.i18n.bidi.Dir.RTL ?
+ goog.i18n.bidi.RIGHT : goog.i18n.bidi.LEFT;
+ element.dir = dir == goog.i18n.bidi.Dir.RTL ? 'rtl' : 'ltr';
+ }
+ }
+};
+
+
+
+/**
+ * Strings that have an (optional) known direction.
+ *
+ * Implementations of this interface are string-like objects that carry an
+ * attached direction, if known.
+ * @interface
+ */
+goog.i18n.bidi.DirectionalString = function() {};
+
+
+/**
+ * Interface marker of the DirectionalString interface.
+ *
+ * This property can be used to determine at runtime whether or not an object
+ * implements this interface. All implementations of this interface set this
+ * property to {@code true}.
+ * @type {boolean}
+ */
+goog.i18n.bidi.DirectionalString.prototype.
+ implementsGoogI18nBidiDirectionalString;
+
+
+/**
+ * Retrieves this object's known direction (if any).
+ * @return {?goog.i18n.bidi.Dir} The known direction. Null if unknown.
+ */
+goog.i18n.bidi.DirectionalString.prototype.getDirection;
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/iter/iter.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/iter/iter.js
new file mode 100644
index 0000000..b7744d5
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/iter/iter.js
@@ -0,0 +1,1314 @@
+// Copyright 2007 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Python style iteration utilities.
+ * @author arv@google.com (Erik Arvidsson)
+ */
+
+
+goog.provide('goog.iter');
+goog.provide('goog.iter.Iterable');
+goog.provide('goog.iter.Iterator');
+goog.provide('goog.iter.StopIteration');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.functions');
+goog.require('goog.math');
+
+
+/**
+ * @typedef {goog.iter.Iterator|{length:number}|{__iterator__}}
+ */
+goog.iter.Iterable;
+
+
+// For script engines that already support iterators.
+if ('StopIteration' in goog.global) {
+ /**
+ * Singleton Error object that is used to terminate iterations.
+ * @type {Error}
+ */
+ goog.iter.StopIteration = goog.global['StopIteration'];
+} else {
+ /**
+ * Singleton Error object that is used to terminate iterations.
+ * @type {Error}
+ * @suppress {duplicate}
+ */
+ goog.iter.StopIteration = Error('StopIteration');
+}
+
+
+
+/**
+ * Class/interface for iterators. An iterator needs to implement a {@code next}
+ * method and it needs to throw a {@code goog.iter.StopIteration} when the
+ * iteration passes beyond the end. Iterators have no {@code hasNext} method.
+ * It is recommended to always use the helper functions to iterate over the
+ * iterator or in case you are only targeting JavaScript 1.7 for in loops.
+ * @constructor
+ * @template VALUE
+ */
+goog.iter.Iterator = function() {};
+
+
+/**
+ * Returns the next value of the iteration. This will throw the object
+ * {@see goog.iter#StopIteration} when the iteration passes the end.
+ * @return {VALUE} Any object or value.
+ */
+goog.iter.Iterator.prototype.next = function() {
+ throw goog.iter.StopIteration;
+};
+
+
+/**
+ * Returns the {@code Iterator} object itself. This is used to implement
+ * the iterator protocol in JavaScript 1.7
+ * @param {boolean=} opt_keys Whether to return the keys or values. Default is
+ * to only return the values. This is being used by the for-in loop (true)
+ * and the for-each-in loop (false). Even though the param gives a hint
+ * about what the iterator will return there is no guarantee that it will
+ * return the keys when true is passed.
+ * @return {!goog.iter.Iterator.<VALUE>} The object itself.
+ */
+goog.iter.Iterator.prototype.__iterator__ = function(opt_keys) {
+ return this;
+};
+
+
+/**
+ * Returns an iterator that knows how to iterate over the values in the object.
+ * @param {goog.iter.Iterator.<VALUE>|goog.iter.Iterable} iterable If the
+ * object is an iterator it will be returned as is. If the object has an
+ * {@code __iterator__} method that will be called to get the value
+ * iterator. If the object is an array-like object we create an iterator
+ * for that.
+ * @return {!goog.iter.Iterator.<VALUE>} An iterator that knows how to iterate
+ * over the values in {@code iterable}.
+ * @template VALUE
+ */
+goog.iter.toIterator = function(iterable) {
+ if (iterable instanceof goog.iter.Iterator) {
+ return iterable;
+ }
+ if (typeof iterable.__iterator__ == 'function') {
+ return iterable.__iterator__(false);
+ }
+ if (goog.isArrayLike(iterable)) {
+ var i = 0;
+ var newIter = new goog.iter.Iterator;
+ newIter.next = function() {
+ while (true) {
+ if (i >= iterable.length) {
+ throw goog.iter.StopIteration;
+ }
+ // Don't include deleted elements.
+ if (!(i in iterable)) {
+ i++;
+ continue;
+ }
+ return iterable[i++];
+ }
+ };
+ return newIter;
+ }
+
+
+ // TODO(arv): Should we fall back on goog.structs.getValues()?
+ throw Error('Not implemented');
+};
+
+
+/**
+ * Calls a function for each element in the iterator with the element of the
+ * iterator passed as argument.
+ *
+ * @param {goog.iter.Iterator.<VALUE>|goog.iter.Iterable} iterable The iterator
+ * to iterate over. If the iterable is an object {@code toIterator} will be
+ * called on it.
+ * @param {function(this:THIS,VALUE,undefined,goog.iter.Iterator.<VALUE>)|
+ * function(this:THIS,number,undefined,goog.iter.Iterator.<VALUE>)} f
+ * The function to call for every element. This function takes 3 arguments
+ * (the element, undefined, and the iterator) and the return value is
+ * irrelevant. The reason for passing undefined as the second argument is
+ * so that the same function can be used in {@see goog.array#forEach} as
+ * well as others.
+ * @param {THIS=} opt_obj The object to be used as the value of 'this' within
+ * {@code f}.
+ * @template THIS, VALUE
+ */
+goog.iter.forEach = function(iterable, f, opt_obj) {
+ if (goog.isArrayLike(iterable)) {
+ /** @preserveTry */
+ try {
+ // NOTES: this passes the index number to the second parameter
+ // of the callback contrary to the documentation above.
+ goog.array.forEach(/** @type {goog.array.ArrayLike} */(iterable), f,
+ opt_obj);
+ } catch (ex) {
+ if (ex !== goog.iter.StopIteration) {
+ throw ex;
+ }
+ }
+ } else {
+ iterable = goog.iter.toIterator(iterable);
+ /** @preserveTry */
+ try {
+ while (true) {
+ f.call(opt_obj, iterable.next(), undefined, iterable);
+ }
+ } catch (ex) {
+ if (ex !== goog.iter.StopIteration) {
+ throw ex;
+ }
+ }
+ }
+};
+
+
+/**
+ * Calls a function for every element in the iterator, and if the function
+ * returns true adds the element to a new iterator.
+ *
+ * @param {goog.iter.Iterator.<VALUE>|goog.iter.Iterable} iterable The iterator
+ * to iterate over.
+ * @param {
+ * function(this:THIS,VALUE,undefined,goog.iter.Iterator.<VALUE>):boolean} f
+ * The function to call for every element. This function takes 3 arguments
+ * (the element, undefined, and the iterator) and should return a boolean.
+ * If the return value is true the element will be included in the returned
+ * iterator. If it is false the element is not included.
+ * @param {THIS=} opt_obj The object to be used as the value of 'this' within
+ * {@code f}.
+ * @return {!goog.iter.Iterator.<VALUE>} A new iterator in which only elements
+ * that passed the test are present.
+ * @template THIS, VALUE
+ */
+goog.iter.filter = function(iterable, f, opt_obj) {
+ var iterator = goog.iter.toIterator(iterable);
+ var newIter = new goog.iter.Iterator;
+ newIter.next = function() {
+ while (true) {
+ var val = iterator.next();
+ if (f.call(opt_obj, val, undefined, iterator)) {
+ return val;
+ }
+ }
+ };
+ return newIter;
+};
+
+
+/**
+ * Calls a function for every element in the iterator, and if the function
+ * returns false adds the element to a new iterator.
+ *
+ * @param {goog.iter.Iterator.<VALUE>|goog.iter.Iterable} iterable The iterator
+ * to iterate over.
+ * @param {
+ * function(this:THIS,VALUE,undefined,goog.iter.Iterator.<VALUE>):boolean} f
+ * The function to call for every element. This function takes 3 arguments
+ * (the element, undefined, and the iterator) and should return a boolean.
+ * If the return value is false the element will be included in the returned
+ * iterator. If it is true the element is not included.
+ * @param {THIS=} opt_obj The object to be used as the value of 'this' within
+ * {@code f}.
+ * @return {!goog.iter.Iterator.<VALUE>} A new iterator in which only elements
+ * that did not pass the test are present.
+ * @template THIS, VALUE
+ */
+goog.iter.filterFalse = function(iterable, f, opt_obj) {
+ return goog.iter.filter(iterable, goog.functions.not(f), opt_obj);
+};
+
+
+/**
+ * Creates a new iterator that returns the values in a range. This function
+ * can take 1, 2 or 3 arguments:
+ * <pre>
+ * range(5) same as range(0, 5, 1)
+ * range(2, 5) same as range(2, 5, 1)
+ * </pre>
+ *
+ * @param {number} startOrStop The stop value if only one argument is provided.
+ * The start value if 2 or more arguments are provided. If only one
+ * argument is used the start value is 0.
+ * @param {number=} opt_stop The stop value. If left out then the first
+ * argument is used as the stop value.
+ * @param {number=} opt_step The number to increment with between each call to
+ * next. This can be negative.
+ * @return {!goog.iter.Iterator.<number>} A new iterator that returns the values
+ * in the range.
+ */
+goog.iter.range = function(startOrStop, opt_stop, opt_step) {
+ var start = 0;
+ var stop = startOrStop;
+ var step = opt_step || 1;
+ if (arguments.length > 1) {
+ start = startOrStop;
+ stop = opt_stop;
+ }
+ if (step == 0) {
+ throw Error('Range step argument must not be zero');
+ }
+
+ var newIter = new goog.iter.Iterator;
+ newIter.next = function() {
+ if (step > 0 && start >= stop || step < 0 && start <= stop) {
+ throw goog.iter.StopIteration;
+ }
+ var rv = start;
+ start += step;
+ return rv;
+ };
+ return newIter;
+};
+
+
+/**
+ * Joins the values in a iterator with a delimiter.
+ * @param {goog.iter.Iterator.<VALUE>|goog.iter.Iterable} iterable The iterator
+ * to get the values from.
+ * @param {string} deliminator The text to put between the values.
+ * @return {string} The joined value string.
+ * @template VALUE
+ */
+goog.iter.join = function(iterable, deliminator) {
+ return goog.iter.toArray(iterable).join(deliminator);
+};
+
+
+/**
+ * For every element in the iterator call a function and return a new iterator
+ * with that value.
+ *
+ * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} iterable The
+ * iterator to iterate over.
+ * @param {
+ * function(this:THIS,VALUE,undefined,!goog.iter.Iterator.<VALUE>):RESULT} f
+ * The function to call for every element. This function takes 3 arguments
+ * (the element, undefined, and the iterator) and should return a new value.
+ * @param {THIS=} opt_obj The object to be used as the value of 'this' within
+ * {@code f}.
+ * @return {!goog.iter.Iterator.<RESULT>} A new iterator that returns the
+ * results of applying the function to each element in the original
+ * iterator.
+ * @template THIS, VALUE, RESULT
+ */
+goog.iter.map = function(iterable, f, opt_obj) {
+ var iterator = goog.iter.toIterator(iterable);
+ var newIter = new goog.iter.Iterator;
+ newIter.next = function() {
+ var val = iterator.next();
+ return f.call(opt_obj, val, undefined, iterator);
+ };
+ return newIter;
+};
+
+
+/**
+ * Passes every element of an iterator into a function and accumulates the
+ * result.
+ *
+ * @param {goog.iter.Iterator.<VALUE>|goog.iter.Iterable} iterable The iterator
+ * to iterate over.
+ * @param {function(this:THIS,VALUE,VALUE):VALUE} f The function to call for
+ * every element. This function takes 2 arguments (the function's previous
+ * result or the initial value, and the value of the current element).
+ * function(previousValue, currentElement) : newValue.
+ * @param {VALUE} val The initial value to pass into the function on the first
+ * call.
+ * @param {THIS=} opt_obj The object to be used as the value of 'this' within
+ * f.
+ * @return {VALUE} Result of evaluating f repeatedly across the values of
+ * the iterator.
+ * @template THIS, VALUE
+ */
+goog.iter.reduce = function(iterable, f, val, opt_obj) {
+ var rval = val;
+ goog.iter.forEach(iterable, function(val) {
+ rval = f.call(opt_obj, rval, val);
+ });
+ return rval;
+};
+
+
+/**
+ * Goes through the values in the iterator. Calls f for each of these, and if
+ * any of them returns true, this returns true (without checking the rest). If
+ * all return false this will return false.
+ *
+ * @param {goog.iter.Iterator.<VALUE>|goog.iter.Iterable} iterable The iterator
+ * object.
+ * @param {
+ * function(this:THIS,VALUE,undefined,goog.iter.Iterator.<VALUE>):boolean} f
+ * The function to call for every value. This function takes 3 arguments
+ * (the value, undefined, and the iterator) and should return a boolean.
+ * @param {THIS=} opt_obj The object to be used as the value of 'this' within
+ * {@code f}.
+ * @return {boolean} true if any value passes the test.
+ * @template THIS, VALUE
+ */
+goog.iter.some = function(iterable, f, opt_obj) {
+ iterable = goog.iter.toIterator(iterable);
+ /** @preserveTry */
+ try {
+ while (true) {
+ if (f.call(opt_obj, iterable.next(), undefined, iterable)) {
+ return true;
+ }
+ }
+ } catch (ex) {
+ if (ex !== goog.iter.StopIteration) {
+ throw ex;
+ }
+ }
+ return false;
+};
+
+
+/**
+ * Goes through the values in the iterator. Calls f for each of these and if any
+ * of them returns false this returns false (without checking the rest). If all
+ * return true this will return true.
+ *
+ * @param {goog.iter.Iterator.<VALUE>|goog.iter.Iterable} iterable The iterator
+ * object.
+ * @param {
+ * function(this:THIS,VALUE,undefined,goog.iter.Iterator.<VALUE>):boolean} f
+ * The function to call for every value. This function takes 3 arguments
+ * (the value, undefined, and the iterator) and should return a boolean.
+ * @param {THIS=} opt_obj The object to be used as the value of 'this' within
+ * {@code f}.
+ * @return {boolean} true if every value passes the test.
+ * @template THIS, VALUE
+ */
+goog.iter.every = function(iterable, f, opt_obj) {
+ iterable = goog.iter.toIterator(iterable);
+ /** @preserveTry */
+ try {
+ while (true) {
+ if (!f.call(opt_obj, iterable.next(), undefined, iterable)) {
+ return false;
+ }
+ }
+ } catch (ex) {
+ if (ex !== goog.iter.StopIteration) {
+ throw ex;
+ }
+ }
+ return true;
+};
+
+
+/**
+ * Takes zero or more iterables and returns one iterator that will iterate over
+ * them in the order chained.
+ * @param {...!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} var_args Any
+ * number of iterable objects.
+ * @return {!goog.iter.Iterator.<VALUE>} Returns a new iterator that will
+ * iterate over all the given iterables' contents.
+ * @template VALUE
+ */
+goog.iter.chain = function(var_args) {
+ var iterator = goog.iter.toIterator(arguments);
+ var iter = new goog.iter.Iterator();
+ var current = null;
+
+ iter.next = function() {
+ while (true) {
+ if (current == null) {
+ var it = iterator.next();
+ current = goog.iter.toIterator(it);
+ }
+ try {
+ return current.next();
+ } catch (ex) {
+ if (ex !== goog.iter.StopIteration) {
+ throw ex;
+ }
+ current = null;
+ }
+ }
+ };
+
+ return iter;
+};
+
+
+/**
+ * Takes a single iterable containing zero or more iterables and returns one
+ * iterator that will iterate over each one in the order given.
+ * @see http://docs.python.org/2/library/itertools.html#itertools.chain.from_iterable
+ * @param {goog.iter.Iterable} iterable The iterable of iterables to chain.
+ * @return {!goog.iter.Iterator.<VALUE>} Returns a new iterator that will
+ * iterate over all the contents of the iterables contained within
+ * {@code iterable}.
+ * @template VALUE
+ */
+goog.iter.chainFromIterable = function(iterable) {
+ return goog.iter.chain.apply(undefined, iterable);
+};
+
+
+/**
+ * Builds a new iterator that iterates over the original, but skips elements as
+ * long as a supplied function returns true.
+ * @param {goog.iter.Iterator.<VALUE>|goog.iter.Iterable} iterable The iterator
+ * object.
+ * @param {
+ * function(this:THIS,VALUE,undefined,goog.iter.Iterator.<VALUE>):boolean} f
+ * The function to call for every value. This function takes 3 arguments
+ * (the value, undefined, and the iterator) and should return a boolean.
+ * @param {THIS=} opt_obj The object to be used as the value of 'this' within
+ * {@code f}.
+ * @return {!goog.iter.Iterator.<VALUE>} A new iterator that drops elements from
+ * the original iterator as long as {@code f} is true.
+ * @template THIS, VALUE
+ */
+goog.iter.dropWhile = function(iterable, f, opt_obj) {
+ var iterator = goog.iter.toIterator(iterable);
+ var newIter = new goog.iter.Iterator;
+ var dropping = true;
+ newIter.next = function() {
+ while (true) {
+ var val = iterator.next();
+ if (dropping && f.call(opt_obj, val, undefined, iterator)) {
+ continue;
+ } else {
+ dropping = false;
+ }
+ return val;
+ }
+ };
+ return newIter;
+};
+
+
+/**
+ * Builds a new iterator that iterates over the original, but only as long as a
+ * supplied function returns true.
+ * @param {goog.iter.Iterator.<VALUE>|goog.iter.Iterable} iterable The iterator
+ * object.
+ * @param {
+ * function(this:THIS,VALUE,undefined,goog.iter.Iterator.<VALUE>):boolean} f
+ * The function to call for every value. This function takes 3 arguments
+ * (the value, undefined, and the iterator) and should return a boolean.
+ * @param {THIS=} opt_obj This is used as the 'this' object in f when called.
+ * @return {!goog.iter.Iterator.<VALUE>} A new iterator that keeps elements in
+ * the original iterator as long as the function is true.
+ * @template THIS, VALUE
+ */
+goog.iter.takeWhile = function(iterable, f, opt_obj) {
+ var iterator = goog.iter.toIterator(iterable);
+ var newIter = new goog.iter.Iterator;
+ var taking = true;
+ newIter.next = function() {
+ while (true) {
+ if (taking) {
+ var val = iterator.next();
+ if (f.call(opt_obj, val, undefined, iterator)) {
+ return val;
+ } else {
+ taking = false;
+ }
+ } else {
+ throw goog.iter.StopIteration;
+ }
+ }
+ };
+ return newIter;
+};
+
+
+/**
+ * Converts the iterator to an array
+ * @param {goog.iter.Iterator.<VALUE>|goog.iter.Iterable} iterable The iterator
+ * to convert to an array.
+ * @return {!Array.<VALUE>} An array of the elements the iterator iterates over.
+ * @template VALUE
+ */
+goog.iter.toArray = function(iterable) {
+ // Fast path for array-like.
+ if (goog.isArrayLike(iterable)) {
+ return goog.array.toArray(/** @type {!goog.array.ArrayLike} */(iterable));
+ }
+ iterable = goog.iter.toIterator(iterable);
+ var array = [];
+ goog.iter.forEach(iterable, function(val) {
+ array.push(val);
+ });
+ return array;
+};
+
+
+/**
+ * Iterates over two iterables and returns true if they contain the same
+ * sequence of elements and have the same length.
+ * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} iterable1 The first
+ * iterable object.
+ * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} iterable2 The second
+ * iterable object.
+ * @return {boolean} true if the iterables contain the same sequence of elements
+ * and have the same length.
+ * @template VALUE
+ */
+goog.iter.equals = function(iterable1, iterable2) {
+ var fillValue = {};
+ var pairs = goog.iter.zipLongest(fillValue, iterable1, iterable2);
+ return goog.iter.every(pairs, function(pair) {
+ return pair[0] == pair[1];
+ });
+};
+
+
+/**
+ * Advances the iterator to the next position, returning the given default value
+ * instead of throwing an exception if the iterator has no more entries.
+ * @param {goog.iter.Iterator.<VALUE>|goog.iter.Iterable} iterable The iterable
+ * object.
+ * @param {VALUE} defaultValue The value to return if the iterator is empty.
+ * @return {VALUE} The next item in the iteration, or defaultValue if the
+ * iterator was empty.
+ * @template VALUE
+ */
+goog.iter.nextOrValue = function(iterable, defaultValue) {
+ try {
+ return goog.iter.toIterator(iterable).next();
+ } catch (e) {
+ if (e != goog.iter.StopIteration) {
+ throw e;
+ }
+ return defaultValue;
+ }
+};
+
+
+/**
+ * Cartesian product of zero or more sets. Gives an iterator that gives every
+ * combination of one element chosen from each set. For example,
+ * ([1, 2], [3, 4]) gives ([1, 3], [1, 4], [2, 3], [2, 4]).
+ * @see http://docs.python.org/library/itertools.html#itertools.product
+ * @param {...!goog.array.ArrayLike.<VALUE>} var_args Zero or more sets, as
+ * arrays.
+ * @return {!goog.iter.Iterator.<!Array.<VALUE>>} An iterator that gives each
+ * n-tuple (as an array).
+ * @template VALUE
+ */
+goog.iter.product = function(var_args) {
+ var someArrayEmpty = goog.array.some(arguments, function(arr) {
+ return !arr.length;
+ });
+
+ // An empty set in a cartesian product gives an empty set.
+ if (someArrayEmpty || !arguments.length) {
+ return new goog.iter.Iterator();
+ }
+
+ var iter = new goog.iter.Iterator();
+ var arrays = arguments;
+
+ // The first indices are [0, 0, ...]
+ var indicies = goog.array.repeat(0, arrays.length);
+
+ iter.next = function() {
+
+ if (indicies) {
+ var retVal = goog.array.map(indicies, function(valueIndex, arrayIndex) {
+ return arrays[arrayIndex][valueIndex];
+ });
+
+ // Generate the next-largest indices for the next call.
+ // Increase the rightmost index. If it goes over, increase the next
+ // rightmost (like carry-over addition).
+ for (var i = indicies.length - 1; i >= 0; i--) {
+ // Assertion prevents compiler warning below.
+ goog.asserts.assert(indicies);
+ if (indicies[i] < arrays[i].length - 1) {
+ indicies[i]++;
+ break;
+ }
+
+ // We're at the last indices (the last element of every array), so
+ // the iteration is over on the next call.
+ if (i == 0) {
+ indicies = null;
+ break;
+ }
+ // Reset the index in this column and loop back to increment the
+ // next one.
+ indicies[i] = 0;
+ }
+ return retVal;
+ }
+
+ throw goog.iter.StopIteration;
+ };
+
+ return iter;
+};
+
+
+/**
+ * Create an iterator to cycle over the iterable's elements indefinitely.
+ * For example, ([1, 2, 3]) would return : 1, 2, 3, 1, 2, 3, ...
+ * @see: http://docs.python.org/library/itertools.html#itertools.cycle.
+ * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} iterable The
+ * iterable object.
+ * @return {!goog.iter.Iterator.<VALUE>} An iterator that iterates indefinitely
+ * over the values in {@code iterable}.
+ * @template VALUE
+ */
+goog.iter.cycle = function(iterable) {
+ var baseIterator = goog.iter.toIterator(iterable);
+
+ // We maintain a cache to store the iterable elements as we iterate
+ // over them. The cache is used to return elements once we have
+ // iterated over the iterable once.
+ var cache = [];
+ var cacheIndex = 0;
+
+ var iter = new goog.iter.Iterator();
+
+ // This flag is set after the iterable is iterated over once
+ var useCache = false;
+
+ iter.next = function() {
+ var returnElement = null;
+
+ // Pull elements off the original iterator if not using cache
+ if (!useCache) {
+ try {
+ // Return the element from the iterable
+ returnElement = baseIterator.next();
+ cache.push(returnElement);
+ return returnElement;
+ } catch (e) {
+ // If an exception other than StopIteration is thrown
+ // or if there are no elements to iterate over (the iterable was empty)
+ // throw an exception
+ if (e != goog.iter.StopIteration || goog.array.isEmpty(cache)) {
+ throw e;
+ }
+ // set useCache to true after we know that a 'StopIteration' exception
+ // was thrown and the cache is not empty (to handle the 'empty iterable'
+ // use case)
+ useCache = true;
+ }
+ }
+
+ returnElement = cache[cacheIndex];
+ cacheIndex = (cacheIndex + 1) % cache.length;
+
+ return returnElement;
+ };
+
+ return iter;
+};
+
+
+/**
+ * Creates an iterator that counts indefinitely from a starting value.
+ * @see http://docs.python.org/2/library/itertools.html#itertools.count
+ * @param {number=} opt_start The starting value. Default is 0.
+ * @param {number=} opt_step The number to increment with between each call to
+ * next. Negative and floating point numbers are allowed. Default is 1.
+ * @return {!goog.iter.Iterator.<number>} A new iterator that returns the values
+ * in the series.
+ */
+goog.iter.count = function(opt_start, opt_step) {
+ var counter = opt_start || 0;
+ var step = goog.isDef(opt_step) ? opt_step : 1;
+ var iter = new goog.iter.Iterator();
+
+ iter.next = function() {
+ var returnValue = counter;
+ counter += step;
+ return returnValue;
+ };
+
+ return iter;
+};
+
+
+/**
+ * Creates an iterator that returns the same object or value repeatedly.
+ * @param {VALUE} value Any object or value to repeat.
+ * @return {!goog.iter.Iterator.<VALUE>} A new iterator that returns the
+ * repeated value.
+ * @template VALUE
+ */
+goog.iter.repeat = function(value) {
+ var iter = new goog.iter.Iterator();
+
+ iter.next = goog.functions.constant(value);
+
+ return iter;
+};
+
+
+/**
+ * Creates an iterator that returns running totals from the numbers in
+ * {@code iterable}. For example, the array {@code [1, 2, 3, 4, 5]} yields
+ * {@code 1 -> 3 -> 6 -> 10 -> 15}.
+ * @see http://docs.python.org/3.2/library/itertools.html#itertools.accumulate
+ * @param {!goog.iter.Iterable.<number>} iterable The iterable of numbers to
+ * accumulate.
+ * @return {!goog.iter.Iterator.<number>} A new iterator that returns the
+ * numbers in the series.
+ */
+goog.iter.accumulate = function(iterable) {
+ var iterator = goog.iter.toIterator(iterable);
+ var total = 0;
+ var iter = new goog.iter.Iterator();
+
+ iter.next = function() {
+ total += iterator.next();
+ return total;
+ };
+
+ return iter;
+};
+
+
+/**
+ * Creates an iterator that returns arrays containing the ith elements from the
+ * provided iterables. The returned arrays will be the same size as the number
+ * of iterables given in {@code var_args}. Once the shortest iterable is
+ * exhausted, subsequent calls to {@code next()} will throw
+ * {@code goog.iter.StopIteration}.
+ * @see http://docs.python.org/2/library/itertools.html#itertools.izip
+ * @param {...!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} var_args Any
+ * number of iterable objects.
+ * @return {!goog.iter.Iterator.<!Array.<VALUE>>} A new iterator that returns
+ * arrays of elements from the provided iterables.
+ * @template VALUE
+ */
+goog.iter.zip = function(var_args) {
+ var args = arguments;
+ var iter = new goog.iter.Iterator();
+
+ if (args.length > 0) {
+ var iterators = goog.array.map(args, goog.iter.toIterator);
+ iter.next = function() {
+ var arr = goog.array.map(iterators, function(it) {
+ return it.next();
+ });
+ return arr;
+ };
+ }
+
+ return iter;
+};
+
+
+/**
+ * Creates an iterator that returns arrays containing the ith elements from the
+ * provided iterables. The returned arrays will be the same size as the number
+ * of iterables given in {@code var_args}. Shorter iterables will be extended
+ * with {@code fillValue}. Once the longest iterable is exhausted, subsequent
+ * calls to {@code next()} will throw {@code goog.iter.StopIteration}.
+ * @see http://docs.python.org/2/library/itertools.html#itertools.izip_longest
+ * @param {VALUE} fillValue The object or value used to fill shorter iterables.
+ * @param {...!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} var_args Any
+ * number of iterable objects.
+ * @return {!goog.iter.Iterator.<!Array.<VALUE>>} A new iterator that returns
+ * arrays of elements from the provided iterables.
+ * @template VALUE
+ */
+goog.iter.zipLongest = function(fillValue, var_args) {
+ var args = goog.array.slice(arguments, 1);
+ var iter = new goog.iter.Iterator();
+
+ if (args.length > 0) {
+ var iterators = goog.array.map(args, goog.iter.toIterator);
+
+ iter.next = function() {
+ var iteratorsHaveValues = false; // false when all iterators are empty.
+ var arr = goog.array.map(iterators, function(it) {
+ var returnValue;
+ try {
+ returnValue = it.next();
+ // Iterator had a value, so we've not exhausted the iterators.
+ // Set flag accordingly.
+ iteratorsHaveValues = true;
+ } catch (ex) {
+ if (ex !== goog.iter.StopIteration) {
+ throw ex;
+ }
+ returnValue = fillValue;
+ }
+ return returnValue;
+ });
+
+ if (!iteratorsHaveValues) {
+ throw goog.iter.StopIteration;
+ }
+ return arr;
+ };
+ }
+
+ return iter;
+};
+
+
+/**
+ * Creates an iterator that filters {@code iterable} based on a series of
+ * {@code selectors}. On each call to {@code next()}, one item is taken from
+ * both the {@code iterable} and {@code selectors} iterators. If the item from
+ * {@code selectors} evaluates to true, the item from {@code iterable} is given.
+ * Otherwise, it is skipped. Once either {@code iterable} or {@code selectors}
+ * is exhausted, subsequent calls to {@code next()} will throw
+ * {@code goog.iter.StopIteration}.
+ * @see http://docs.python.org/2/library/itertools.html#itertools.compress
+ * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} iterable The
+ * iterable to filter.
+ * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} selectors An
+ * iterable of items to be evaluated in a boolean context to determine if
+ * the corresponding element in {@code iterable} should be included in the
+ * result.
+ * @return {!goog.iter.Iterator.<VALUE>} A new iterator that returns the
+ * filtered values.
+ * @template VALUE
+ */
+goog.iter.compress = function(iterable, selectors) {
+ var selectorIterator = goog.iter.toIterator(selectors);
+
+ return goog.iter.filter(iterable, function() {
+ return !!selectorIterator.next();
+ });
+};
+
+
+
+/**
+ * Implements the {@code goog.iter.groupBy} iterator.
+ * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} iterable The
+ * iterable to group.
+ * @param {function(...[VALUE]): KEY=} opt_keyFunc Optional function for
+ * determining the key value for each group in the {@code iterable}. Default
+ * is the identity function.
+ * @constructor
+ * @extends {goog.iter.Iterator.<!Array>}
+ * @template KEY, VALUE
+ * @private
+ */
+goog.iter.GroupByIterator_ = function(iterable, opt_keyFunc) {
+
+ /**
+ * The iterable to group, coerced to an iterator.
+ * @type {!goog.iter.Iterator}
+ */
+ this.iterator = goog.iter.toIterator(iterable);
+
+ /**
+ * A function for determining the key value for each element in the iterable.
+ * If no function is provided, the identity function is used and returns the
+ * element unchanged.
+ * @type {function(...[VALUE]): KEY}
+ */
+ this.keyFunc = opt_keyFunc || goog.functions.identity;
+
+ /**
+ * The target key for determining the start of a group.
+ * @type {KEY}
+ */
+ this.targetKey;
+
+ /**
+ * The current key visited during iteration.
+ * @type {KEY}
+ */
+ this.currentKey;
+
+ /**
+ * The current value being added to the group.
+ * @type {VALUE}
+ */
+ this.currentValue;
+};
+goog.inherits(goog.iter.GroupByIterator_, goog.iter.Iterator);
+
+
+/** @override */
+goog.iter.GroupByIterator_.prototype.next = function() {
+ while (this.currentKey == this.targetKey) {
+ this.currentValue = this.iterator.next(); // Exits on StopIteration
+ this.currentKey = this.keyFunc(this.currentValue);
+ }
+ this.targetKey = this.currentKey;
+ return [this.currentKey, this.groupItems_(this.targetKey)];
+};
+
+
+/**
+ * Performs the grouping of objects using the given key.
+ * @param {KEY} targetKey The target key object for the group.
+ * @return {!Array.<VALUE>} An array of grouped objects.
+ * @private
+ */
+goog.iter.GroupByIterator_.prototype.groupItems_ = function(targetKey) {
+ var arr = [];
+ while (this.currentKey == targetKey) {
+ arr.push(this.currentValue);
+ try {
+ this.currentValue = this.iterator.next();
+ } catch (ex) {
+ if (ex !== goog.iter.StopIteration) {
+ throw ex;
+ }
+ break;
+ }
+ this.currentKey = this.keyFunc(this.currentValue);
+ }
+ return arr;
+};
+
+
+/**
+ * Creates an iterator that returns arrays containing elements from the
+ * {@code iterable} grouped by a key value. For iterables with repeated
+ * elements (i.e. sorted according to a particular key function), this function
+ * has a {@code uniq}-like effect. For example, grouping the array:
+ * {@code [A, B, B, C, C, A]} produces
+ * {@code [A, [A]], [B, [B, B]], [C, [C, C]], [A, [A]]}.
+ * @see http://docs.python.org/2/library/itertools.html#itertools.groupby
+ * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} iterable The
+ * iterable to group.
+ * @param {function(...[VALUE]): KEY=} opt_keyFunc Optional function for
+ * determining the key value for each group in the {@code iterable}. Default
+ * is the identity function.
+ * @return {!goog.iter.Iterator.<!Array>} A new iterator that returns arrays of
+ * consecutive key and groups.
+ * @template KEY, VALUE
+ */
+goog.iter.groupBy = function(iterable, opt_keyFunc) {
+ return new goog.iter.GroupByIterator_(iterable, opt_keyFunc);
+};
+
+
+/**
+ * Gives an iterator that gives the result of calling the given function
+ * <code>f</code> with the arguments taken from the next element from
+ * <code>iterable</code> (the elements are expected to also be iterables).
+ *
+ * Similar to {@see goog.iter#map} but allows the function to accept multiple
+ * arguments from the iterable.
+ *
+ * @param {!goog.iter.Iterable.<!goog.iter.Iterable>} iterable The iterable of
+ * iterables to iterate over.
+ * @param {function(this:THIS,...[*]):RESULT} f The function to call for every
+ * element. This function takes N+2 arguments, where N represents the
+ * number of items from the next element of the iterable. The two
+ * additional arguments passed to the function are undefined and the
+ * iterator itself. The function should return a new value.
+ * @param {THIS=} opt_obj The object to be used as the value of 'this' within
+ * {@code f}.
+ * @return {!goog.iter.Iterator.<RESULT>} A new iterator that returns the
+ * results of applying the function to each element in the original
+ * iterator.
+ * @template THIS, RESULT
+ */
+goog.iter.starMap = function(iterable, f, opt_obj) {
+ var iterator = goog.iter.toIterator(iterable);
+ var iter = new goog.iter.Iterator();
+
+ iter.next = function() {
+ var args = goog.iter.toArray(iterator.next());
+ return f.apply(opt_obj, goog.array.concat(args, undefined, iterator));
+ };
+
+ return iter;
+};
+
+
+/**
+ * Returns an array of iterators each of which can iterate over the values in
+ * {@code iterable} without advancing the others.
+ * @see http://docs.python.org/2/library/itertools.html#itertools.tee
+ * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} iterable The
+ * iterable to tee.
+ * @param {number=} opt_num The number of iterators to create. Default is 2.
+ * @return {!Array.<goog.iter.Iterator.<VALUE>>} An array of iterators.
+ * @template VALUE
+ */
+goog.iter.tee = function(iterable, opt_num) {
+ var iterator = goog.iter.toIterator(iterable);
+ var num = goog.isNumber(opt_num) ? opt_num : 2;
+ var buffers = goog.array.map(goog.array.range(num), function() {
+ return [];
+ });
+
+ var addNextIteratorValueToBuffers = function() {
+ var val = iterator.next();
+ goog.array.forEach(buffers, function(buffer) {
+ buffer.push(val);
+ });
+ };
+
+ var createIterator = function(buffer) {
+ // Each tee'd iterator has an associated buffer (initially empty). When a
+ // tee'd iterator's buffer is empty, it calls
+ // addNextIteratorValueToBuffers(), adding the next value to all tee'd
+ // iterators' buffers, and then returns that value. This allows each
+ // iterator to be advanced independently.
+ var iter = new goog.iter.Iterator();
+
+ iter.next = function() {
+ if (goog.array.isEmpty(buffer)) {
+ addNextIteratorValueToBuffers();
+ }
+ goog.asserts.assert(!goog.array.isEmpty(buffer));
+ return buffer.shift();
+ };
+
+ return iter;
+ };
+
+ return goog.array.map(buffers, createIterator);
+};
+
+
+/**
+ * Creates an iterator that returns arrays containing a count and an element
+ * obtained from the given {@code iterable}.
+ * @see http://docs.python.org/2/library/functions.html#enumerate
+ * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} iterable The
+ * iterable to enumerate.
+ * @param {number=} opt_start Optional starting value. Default is 0.
+ * @return {!goog.iter.Iterator.<!Array>} A new iterator containing count/item
+ * pairs.
+ * @template VALUE
+ */
+goog.iter.enumerate = function(iterable, opt_start) {
+ return goog.iter.zip(goog.iter.count(opt_start), iterable);
+};
+
+
+/**
+ * Creates an iterator that returns the first {@code limitSize} elements from an
+ * iterable. If this number is greater than the number of elements in the
+ * iterable, all the elements are returned.
+ * @see http://goo.gl/V0sihp Inspired by the limit iterator in Guava.
+ * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} iterable The
+ * iterable to limit.
+ * @param {number} limitSize The maximum number of elements to return.
+ * @return {!goog.iter.Iterator.<VALUE>} A new iterator containing
+ * {@code limitSize} elements.
+ * @template VALUE
+ */
+goog.iter.limit = function(iterable, limitSize) {
+ goog.asserts.assert(goog.math.isInt(limitSize) && limitSize >= 0);
+
+ var iterator = goog.iter.toIterator(iterable);
+
+ var iter = new goog.iter.Iterator();
+ var remaining = limitSize;
+
+ iter.next = function() {
+ if (remaining-- > 0) {
+ return iterator.next();
+ }
+ throw goog.iter.StopIteration;
+ };
+
+ return iter;
+};
+
+
+/**
+ * Creates an iterator that is advanced {@code count} steps ahead. Consumed
+ * values are silently discarded. If {@code count} is greater than the number
+ * of elements in {@code iterable}, an empty iterator is returned. Subsequent
+ * calls to {@code next()} will throw {@code goog.iter.StopIteration}.
+ * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} iterable The
+ * iterable to consume.
+ * @param {number} count The number of elements to consume from the iterator.
+ * @return {!goog.iter.Iterator.<VALUE>} An iterator advanced zero or more steps
+ * ahead.
+ * @template VALUE
+ */
+goog.iter.consume = function(iterable, count) {
+ goog.asserts.assert(goog.math.isInt(count) && count >= 0);
+
+ var iterator = goog.iter.toIterator(iterable);
+
+ while (count-- > 0) {
+ goog.iter.nextOrValue(iterator, null);
+ }
+
+ return iterator;
+};
+
+
+/**
+ * Creates an iterator that returns a range of elements from an iterable.
+ * Similar to {@see goog.array#slice} but does not support negative indexes.
+ * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} iterable The
+ * iterable to slice.
+ * @param {number} start The index of the first element to return.
+ * @param {number=} opt_end The index after the last element to return. If
+ * defined, must be greater than or equal to {@code start}.
+ * @return {!goog.iter.Iterator.<VALUE>} A new iterator containing a slice of
+ * the original.
+ * @template VALUE
+ */
+goog.iter.slice = function(iterable, start, opt_end) {
+ goog.asserts.assert(goog.math.isInt(start) && start >= 0);
+
+ var iterator = goog.iter.consume(iterable, start);
+
+ if (goog.isNumber(opt_end)) {
+ goog.asserts.assert(
+ goog.math.isInt(/** @type {number} */ (opt_end)) && opt_end >= start);
+ iterator = goog.iter.limit(iterator, opt_end - start /* limitSize */);
+ }
+
+ return iterator;
+};
+
+
+/**
+ * Checks an array for duplicate elements.
+ * @param {Array.<VALUE>|goog.array.ArrayLike} arr The array to check for
+ * duplicates.
+ * @return {boolean} True, if the array contains duplicates, false otherwise.
+ * @private
+ * @template VALUE
+ */
+// TODO(user): Consider moving this into goog.array as a public function.
+goog.iter.hasDuplicates_ = function(arr) {
+ var deduped = [];
+ goog.array.removeDuplicates(arr, deduped);
+ return arr.length != deduped.length;
+};
+
+
+/**
+ * Creates an iterator that returns permutations of elements in
+ * {@code iterable}.
+ *
+ * Permutations are obtained by taking the Cartesian product of
+ * {@code opt_length} iterables and filtering out those with repeated
+ * elements. For example, the permutations of {@code [1,2,3]} are
+ * {@code [[1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1]]}.
+ * @see http://docs.python.org/2/library/itertools.html#itertools.permutations
+ * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} iterable The
+ * iterable from which to generate permutations.
+ * @param {number=} opt_length Length of each permutation. If omitted, defaults
+ * to the length of {@code iterable}.
+ * @return {!goog.iter.Iterator.<!Array.<VALUE>>} A new iterator containing the
+ * permutations of {@code iterable}.
+ * @template VALUE
+ */
+goog.iter.permutations = function(iterable, opt_length) {
+ var elements = goog.iter.toArray(iterable);
+ var length = goog.isNumber(opt_length) ? opt_length : elements.length;
+
+ var sets = goog.array.repeat(elements, length);
+ var product = goog.iter.product.apply(undefined, sets);
+
+ return goog.iter.filter(product, function(arr) {
+ return !goog.iter.hasDuplicates_(arr);
+ });
+};
+
+
+/**
+ * Creates an iterator that returns combinations of elements from
+ * {@code iterable}.
+ *
+ * Combinations are obtained by taking the {@see goog.iter#permutations} of
+ * {@code iterable} and filtering those whose elements appear in the order they
+ * are encountered in {@code iterable}. For example, the 3-length combinations
+ * of {@code [0,1,2,3]} are {@code [[0,1,2], [0,1,3], [0,2,3], [1,2,3]]}.
+ * @see http://docs.python.org/2/library/itertools.html#itertools.combinations
+ * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} iterable The
+ * iterable from which to generate combinations.
+ * @param {number} length The length of each combination.
+ * @return {!goog.iter.Iterator.<!Array.<VALUE>>} A new iterator containing
+ * combinations from the {@code iterable}.
+ * @template VALUE
+ */
+goog.iter.combinations = function(iterable, length) {
+ var elements = goog.iter.toArray(iterable);
+ var indexes = goog.iter.range(elements.length);
+ var indexIterator = goog.iter.permutations(indexes, length);
+ // sortedIndexIterator will now give arrays of with the given length that
+ // indicate what indexes into "elements" should be returned on each iteration.
+ var sortedIndexIterator = goog.iter.filter(indexIterator, function(arr) {
+ return goog.array.isSorted(arr);
+ });
+
+ var iter = new goog.iter.Iterator();
+
+ function getIndexFromElements(index) {
+ return elements[index];
+ }
+
+ iter.next = function() {
+ return goog.array.map(
+ /** @type {!Array.<number>} */
+ (sortedIndexIterator.next()), getIndexFromElements);
+ };
+
+ return iter;
+};
+
+
+/**
+ * Creates an iterator that returns combinations of elements from
+ * {@code iterable}, with repeated elements possible.
+ *
+ * Combinations are obtained by taking the Cartesian product of {@code length}
+ * iterables and filtering those whose elements appear in the order they are
+ * encountered in {@code iterable}. For example, the 2-length combinations of
+ * {@code [1,2,3]} are {@code [[1,1], [1,2], [1,3], [2,2], [2,3], [3,3]]}.
+ * @see http://docs.python.org/2/library/itertools.html#itertools.combinations_with_replacement
+ * @see http://en.wikipedia.org/wiki/Combination#Number_of_combinations_with_repetition
+ * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} iterable The
+ * iterable to combine.
+ * @param {number} length The length of each combination.
+ * @return {!goog.iter.Iterator.<!Array.<VALUE>>} A new iterator containing
+ * combinations from the {@code iterable}.
+ * @template VALUE
+ */
+goog.iter.combinationsWithReplacement = function(iterable, length) {
+ var elements = goog.iter.toArray(iterable);
+ var indexes = goog.array.range(elements.length);
+ var sets = goog.array.repeat(indexes, length);
+ var indexIterator = goog.iter.product.apply(undefined, sets);
+ // sortedIndexIterator will now give arrays of with the given length that
+ // indicate what indexes into "elements" should be returned on each iteration.
+ var sortedIndexIterator = goog.iter.filter(indexIterator, function(arr) {
+ return goog.array.isSorted(arr);
+ });
+
+ var iter = new goog.iter.Iterator();
+
+ function getIndexFromElements(index) {
+ return elements[index];
+ }
+
+ iter.next = function() {
+ return goog.array.map(
+ /** @type {!Array.<number>} */
+ (sortedIndexIterator.next()), getIndexFromElements);
+ };
+
+ return iter;
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/labs/useragent/browser.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/labs/useragent/browser.js
new file mode 100644
index 0000000..2a53f12
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/labs/useragent/browser.js
@@ -0,0 +1,271 @@
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Closure user agent detection (Browser).
+ * @see <a href="http://www.useragentstring.com/">User agent strings</a>
+ * For more information on rendering engine, platform, or device see the other
+ * sub-namespaces in goog.labs.userAgent, goog.labs.userAgent.platform,
+ * goog.labs.userAgent.device respectively.)
+ *
+ */
+
+goog.provide('goog.labs.userAgent.browser');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.labs.userAgent.util');
+goog.require('goog.string');
+
+
+/**
+ * @return {boolean} Whether the user's browser is Opera.
+ * @private
+ */
+goog.labs.userAgent.browser.matchOpera_ = function() {
+ return goog.labs.userAgent.util.matchUserAgent('Opera') ||
+ goog.labs.userAgent.util.matchUserAgent('OPR');
+};
+
+
+/**
+ * @return {boolean} Whether the user's browser is IE.
+ * @private
+ */
+goog.labs.userAgent.browser.matchIE_ = function() {
+ return goog.labs.userAgent.util.matchUserAgent('Trident') ||
+ goog.labs.userAgent.util.matchUserAgent('MSIE');
+};
+
+
+/**
+ * @return {boolean} Whether the user's browser is Firefox.
+ * @private
+ */
+goog.labs.userAgent.browser.matchFirefox_ = function() {
+ return goog.labs.userAgent.util.matchUserAgent('Firefox');
+};
+
+
+/**
+ * @return {boolean} Whether the user's browser is Safari.
+ * @private
+ */
+goog.labs.userAgent.browser.matchSafari_ = function() {
+ return goog.labs.userAgent.util.matchUserAgent('Safari') &&
+ !goog.labs.userAgent.util.matchUserAgent('Chrome') &&
+ !goog.labs.userAgent.util.matchUserAgent('CriOS') &&
+ !goog.labs.userAgent.util.matchUserAgent('Android');
+};
+
+
+/**
+ * @return {boolean} Whether the user's browser is Chrome.
+ * @private
+ */
+goog.labs.userAgent.browser.matchChrome_ = function() {
+ return goog.labs.userAgent.util.matchUserAgent('Chrome') ||
+ goog.labs.userAgent.util.matchUserAgent('CriOS');
+};
+
+
+/**
+ * @return {boolean} Whether the user's browser is the Android browser.
+ * @private
+ */
+goog.labs.userAgent.browser.matchAndroidBrowser_ = function() {
+ return goog.labs.userAgent.util.matchUserAgent('Android') &&
+ !goog.labs.userAgent.util.matchUserAgent('Chrome') &&
+ !goog.labs.userAgent.util.matchUserAgent('CriOS');
+};
+
+
+/**
+ * @return {boolean} Whether the user's browser is Opera.
+ */
+goog.labs.userAgent.browser.isOpera = goog.labs.userAgent.browser.matchOpera_;
+
+
+/**
+ * @return {boolean} Whether the user's browser is IE.
+ */
+goog.labs.userAgent.browser.isIE = goog.labs.userAgent.browser.matchIE_;
+
+
+/**
+ * @return {boolean} Whether the user's browser is Firefox.
+ */
+goog.labs.userAgent.browser.isFirefox =
+ goog.labs.userAgent.browser.matchFirefox_;
+
+
+/**
+ * @return {boolean} Whether the user's browser is Safari.
+ */
+goog.labs.userAgent.browser.isSafari =
+ goog.labs.userAgent.browser.matchSafari_;
+
+
+/**
+ * @return {boolean} Whether the user's browser is Chrome.
+ */
+goog.labs.userAgent.browser.isChrome =
+ goog.labs.userAgent.browser.matchChrome_;
+
+
+/**
+ * @return {boolean} Whether the user's browser is the Android browser.
+ */
+goog.labs.userAgent.browser.isAndroidBrowser =
+ goog.labs.userAgent.browser.matchAndroidBrowser_;
+
+
+/**
+ * For more information, see:
+ * http://docs.aws.amazon.com/silk/latest/developerguide/user-agent.html
+ * @return {boolean} Whether the user's browser is Silk.
+ */
+goog.labs.userAgent.browser.isSilk = function() {
+ return goog.labs.userAgent.util.matchUserAgent('Silk');
+};
+
+
+/**
+ * @return {string} The browser version or empty string if version cannot be
+ * determined. Note that for Internet Explorer, this returns the version of
+ * the browser, not the version of the rendering engine. (IE 8 in
+ * compatibility mode will return 8.0 rather than 7.0. To determine the
+ * rendering engine version, look at document.documentMode instead. See
+ * http://msdn.microsoft.com/en-us/library/cc196988(v=vs.85).aspx for more
+ * details.)
+ */
+goog.labs.userAgent.browser.getVersion = function() {
+ var userAgentString = goog.labs.userAgent.util.getUserAgent();
+ // Special case IE since IE's version is inside the parenthesis and
+ // without the '/'.
+ if (goog.labs.userAgent.browser.isIE()) {
+ return goog.labs.userAgent.browser.getIEVersion_(userAgentString);
+ }
+
+ if (goog.labs.userAgent.browser.isOpera()) {
+ return goog.labs.userAgent.browser.getOperaVersion_(userAgentString);
+ }
+
+ var versionTuples =
+ goog.labs.userAgent.util.extractVersionTuples(userAgentString);
+ return goog.labs.userAgent.browser.getVersionFromTuples_(versionTuples);
+};
+
+
+/**
+ * @param {string|number} version The version to check.
+ * @return {boolean} Whether the browser version is higher or the same as the
+ * given version.
+ */
+goog.labs.userAgent.browser.isVersionOrHigher = function(version) {
+ return goog.string.compareVersions(goog.labs.userAgent.browser.getVersion(),
+ version) >= 0;
+};
+
+
+/**
+ * Determines IE version. More information:
+ * http://msdn.microsoft.com/en-us/library/ie/bg182625(v=vs.85).aspx#uaString
+ * http://msdn.microsoft.com/en-us/library/hh869301(v=vs.85).aspx
+ * http://blogs.msdn.com/b/ie/archive/2010/03/23/introducing-ie9-s-user-agent-string.aspx
+ * http://blogs.msdn.com/b/ie/archive/2009/01/09/the-internet-explorer-8-user-agent-string-updated-edition.aspx
+ *
+ * @param {string} userAgent the User-Agent.
+ * @return {string}
+ * @private
+ */
+goog.labs.userAgent.browser.getIEVersion_ = function(userAgent) {
+ // IE11 may identify itself as MSIE 9.0 or MSIE 10.0 due to an IE 11 upgrade
+ // bug. Example UA:
+ // Mozilla/5.0 (MSIE 9.0; Windows NT 6.1; WOW64; Trident/7.0; rv:11.0)
+ // like Gecko.
+ // See http://www.whatismybrowser.com/developers/unknown-user-agent-fragments.
+ var rv = /rv: *([\d\.]*)/.exec(userAgent);
+ if (rv && rv[1]) {
+ return rv[1];
+ }
+
+ var version = '';
+ var msie = /MSIE +([\d\.]+)/.exec(userAgent);
+ if (msie && msie[1]) {
+ // IE in compatibility mode usually identifies itself as MSIE 7.0; in this
+ // case, use the Trident version to determine the version of IE. For more
+ // details, see the links above.
+ var tridentVersion = /Trident\/(\d.\d)/.exec(userAgent);
+ if (msie[1] == '7.0') {
+ if (tridentVersion && tridentVersion[1]) {
+ switch (tridentVersion[1]) {
+ case '4.0':
+ version = '8.0';
+ break;
+ case '5.0':
+ version = '9.0';
+ break;
+ case '6.0':
+ version = '10.0';
+ break;
+ case '7.0':
+ version = '11.0';
+ break;
+ }
+ } else {
+ version = '7.0';
+ }
+ } else {
+ version = msie[1];
+ }
+ }
+ return version;
+};
+
+
+/**
+ * Determines Opera version. More information:
+ * http://my.opera.com/ODIN/blog/2013/07/15/opera-user-agent-strings-opera-15-and-beyond
+ *
+ * @param {string} userAgent The User-Agent.
+ * @return {string}
+ * @private
+ */
+goog.labs.userAgent.browser.getOperaVersion_ = function(userAgent) {
+ var versionTuples =
+ goog.labs.userAgent.util.extractVersionTuples(userAgent);
+ var lastTuple = goog.array.peek(versionTuples);
+ if (lastTuple[0] == 'OPR' && lastTuple[1]) {
+ return lastTuple[1];
+ }
+
+ return goog.labs.userAgent.browser.getVersionFromTuples_(versionTuples);
+};
+
+
+/**
+ * Nearly all User-Agents start with Mozilla/N.0. This looks at the second tuple
+ * for the actual browser version number.
+ * @param {!Array.<!Array.<string>>} versionTuples
+ * @return {string} The version or empty string if it cannot be determined.
+ * @private
+ */
+goog.labs.userAgent.browser.getVersionFromTuples_ = function(versionTuples) {
+ // versionTuples[2] (The first X/Y tuple after the parenthesis) contains the
+ // browser version number.
+ goog.asserts.assert(versionTuples.length > 2,
+ 'Couldn\'t extract version tuple from user agent string');
+ return versionTuples[2] && versionTuples[2][1] ? versionTuples[2][1] : '';
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/labs/useragent/engine.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/labs/useragent/engine.js
new file mode 100644
index 0000000..ab8346c
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/labs/useragent/engine.js
@@ -0,0 +1,130 @@
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Closure user agent detection.
+ * @see http://en.wikipedia.org/wiki/User_agent
+ * For more information on browser brand, platform, or device see the other
+ * sub-namespaces in goog.labs.userAgent (browser, platform, and device).
+ *
+ */
+
+goog.provide('goog.labs.userAgent.engine');
+
+goog.require('goog.array');
+goog.require('goog.labs.userAgent.util');
+goog.require('goog.string');
+
+
+/**
+ * @return {boolean} Whether the rendering engine is Presto.
+ */
+goog.labs.userAgent.engine.isPresto = function() {
+ return goog.labs.userAgent.util.matchUserAgent('Presto');
+};
+
+
+/**
+ * @return {boolean} Whether the rendering engine is Trident.
+ */
+goog.labs.userAgent.engine.isTrident = function() {
+ // IE only started including the Trident token in IE8.
+ return goog.labs.userAgent.util.matchUserAgent('Trident') ||
+ goog.labs.userAgent.util.matchUserAgent('MSIE');
+};
+
+
+/**
+ * @return {boolean} Whether the rendering engine is WebKit.
+ */
+goog.labs.userAgent.engine.isWebKit = function() {
+ return goog.labs.userAgent.util.matchUserAgentIgnoreCase('WebKit');
+};
+
+
+/**
+ * @return {boolean} Whether the rendering engine is Gecko.
+ */
+goog.labs.userAgent.engine.isGecko = function() {
+ return goog.labs.userAgent.util.matchUserAgent('Gecko') &&
+ !goog.labs.userAgent.engine.isWebKit() &&
+ !goog.labs.userAgent.engine.isTrident();
+};
+
+
+/**
+ * @return {string} The rendering engine's version or empty string if version
+ * can't be determined.
+ */
+goog.labs.userAgent.engine.getVersion = function() {
+ var userAgentString = goog.labs.userAgent.util.getUserAgent();
+ if (userAgentString) {
+ var tuples = goog.labs.userAgent.util.extractVersionTuples(
+ userAgentString);
+
+ var engineTuple = tuples[1];
+ if (engineTuple) {
+ // In Gecko, the version string is either in the browser info or the
+ // Firefox version. See Gecko user agent string reference:
+ // http://goo.gl/mULqa
+ if (engineTuple[0] == 'Gecko') {
+ return goog.labs.userAgent.engine.getVersionForKey_(
+ tuples, 'Firefox');
+ }
+
+ return engineTuple[1];
+ }
+
+ // IE has only one version identifier, and the Trident version is
+ // specified in the parenthetical.
+ var browserTuple = tuples[0];
+ var info;
+ if (browserTuple && (info = browserTuple[2])) {
+ var match = /Trident\/([^\s;]+)/.exec(info);
+ if (match) {
+ return match[1];
+ }
+ }
+ }
+ return '';
+};
+
+
+/**
+ * @param {string|number} version The version to check.
+ * @return {boolean} Whether the rendering engine version is higher or the same
+ * as the given version.
+ */
+goog.labs.userAgent.engine.isVersionOrHigher = function(version) {
+ return goog.string.compareVersions(goog.labs.userAgent.engine.getVersion(),
+ version) >= 0;
+};
+
+
+/**
+ * @param {!Array.<!Array.<string>>} tuples Version tuples.
+ * @param {string} key The key to look for.
+ * @return {string} The version string of the given key, if present.
+ * Otherwise, the empty string.
+ * @private
+ */
+goog.labs.userAgent.engine.getVersionForKey_ = function(tuples, key) {
+ // TODO(nnaze): Move to util if useful elsewhere.
+
+ var pair = goog.array.find(tuples, function(pair) {
+ return key == pair[0];
+ });
+
+ return pair && pair[1] || '';
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/labs/useragent/util.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/labs/useragent/util.js
new file mode 100644
index 0000000..c3665c3
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/labs/useragent/util.js
@@ -0,0 +1,154 @@
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Utilities used by goog.labs.userAgent tools. These functions
+ * should not be used outside of goog.labs.userAgent.*.
+ *
+ * @visibility {//closure/goog/bin/sizetests:__pkg__}
+ * @visibility {//closure/goog/dom:__subpackages__}
+ * @visibility {//closure/goog/style:__pkg__}
+ * @visibility {//closure/goog/testing:__pkg__}
+ * @visibility {//closure/goog/useragent:__subpackages__}
+ * @visibility {//testing/puppet/modules:__pkg__} *
+ *
+ * @author nnaze@google.com (Nathan Naze)
+ */
+
+goog.provide('goog.labs.userAgent.util');
+
+goog.require('goog.string');
+
+
+/**
+ * Gets the native userAgent string from navigator if it exists.
+ * If navigator or navigator.userAgent string is missing, returns an empty
+ * string.
+ * @return {string}
+ * @private
+ */
+goog.labs.userAgent.util.getNativeUserAgentString_ = function() {
+ var navigator = goog.labs.userAgent.util.getNavigator_();
+ if (navigator) {
+ var userAgent = navigator.userAgent;
+ if (userAgent) {
+ return userAgent;
+ }
+ }
+ return '';
+};
+
+
+/**
+ * Getter for the native navigator.
+ * This is a separate function so it can be stubbed out in testing.
+ * @return {Navigator}
+ * @private
+ */
+goog.labs.userAgent.util.getNavigator_ = function() {
+ return goog.global.navigator;
+};
+
+
+/**
+ * A possible override for applications which wish to not check
+ * navigator.userAgent but use a specified value for detection instead.
+ * @private {string}
+ */
+goog.labs.userAgent.util.userAgent_ =
+ goog.labs.userAgent.util.getNativeUserAgentString_();
+
+
+/**
+ * Applications may override browser detection on the built in
+ * navigator.userAgent object by setting this string. Set to null to use the
+ * browser object instead.
+ * @param {?string=} opt_userAgent The User-Agent override.
+ */
+goog.labs.userAgent.util.setUserAgent = function(opt_userAgent) {
+ goog.labs.userAgent.util.userAgent_ = opt_userAgent ||
+ goog.labs.userAgent.util.getNativeUserAgentString_();
+};
+
+
+/**
+ * @return {string} The user agent string.
+ */
+goog.labs.userAgent.util.getUserAgent = function() {
+ return goog.labs.userAgent.util.userAgent_;
+};
+
+
+/**
+ * @param {string} str
+ * @return {boolean} Whether the user agent contains the given string, ignoring
+ * case.
+ */
+goog.labs.userAgent.util.matchUserAgent = function(str) {
+ var userAgent = goog.labs.userAgent.util.getUserAgent();
+ return goog.string.contains(userAgent, str);
+};
+
+
+/**
+ * @param {string} str
+ * @return {boolean} Whether the user agent contains the given string.
+ */
+goog.labs.userAgent.util.matchUserAgentIgnoreCase = function(str) {
+ var userAgent = goog.labs.userAgent.util.getUserAgent();
+ return goog.string.caseInsensitiveContains(userAgent, str);
+};
+
+
+/**
+ * Parses the user agent into tuples for each section.
+ * @param {string} userAgent
+ * @return {!Array.<!Array.<string>>} Tuples of key, version, and the contents
+ * of the parenthetical.
+ */
+goog.labs.userAgent.util.extractVersionTuples = function(userAgent) {
+ // Matches each section of a user agent string.
+ // Example UA:
+ // Mozilla/5.0 (iPad; U; CPU OS 3_2_1 like Mac OS X; en-us)
+ // AppleWebKit/531.21.10 (KHTML, like Gecko) Mobile/7B405
+ // This has three version tuples: Mozilla, AppleWebKit, and Mobile.
+
+ var versionRegExp = new RegExp(
+ // Key. Note that a key may have a space.
+ // (i.e. 'Mobile Safari' in 'Mobile Safari/5.0')
+ '(\\w[\\w ]+)' +
+
+ '/' + // slash
+ '([^\\s]+)' + // version (i.e. '5.0b')
+ '\\s*' + // whitespace
+ '(?:\\((.*?)\\))?', // parenthetical info. parentheses not matched.
+ 'g');
+
+ var data = [];
+ var match;
+
+ // Iterate and collect the version tuples. Each iteration will be the
+ // next regex match.
+ while (match = versionRegExp.exec(userAgent)) {
+ data.push([
+ match[1], // key
+ match[2], // value
+ // || undefined as this is not undefined in IE7 and IE8
+ match[3] || undefined // info
+ ]);
+ }
+
+ return data;
+};
+
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/log/log.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/log/log.js
new file mode 100644
index 0000000..b93d835
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/log/log.js
@@ -0,0 +1,197 @@
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Basic strippable logging definitions.
+ * @see http://go/closurelogging
+ *
+ * @author johnlenz@google.com (John Lenz)
+ */
+
+goog.provide('goog.log');
+goog.provide('goog.log.Level');
+goog.provide('goog.log.LogRecord');
+goog.provide('goog.log.Logger');
+
+goog.require('goog.debug');
+goog.require('goog.debug.LogManager');
+goog.require('goog.debug.LogRecord');
+goog.require('goog.debug.Logger');
+
+
+/** @define {boolean} Whether logging is enabled. */
+goog.define('goog.log.ENABLED', goog.debug.LOGGING_ENABLED);
+
+
+/** @const */
+goog.log.ROOT_LOGGER_NAME = goog.debug.Logger.ROOT_LOGGER_NAME;
+
+
+
+/**
+ * @constructor
+ * @final
+ */
+goog.log.Logger = goog.debug.Logger;
+
+
+
+/**
+ * @constructor
+ * @final
+ */
+goog.log.Level = goog.debug.Logger.Level;
+
+
+
+/**
+ * @constructor
+ * @final
+ */
+goog.log.LogRecord = goog.debug.LogRecord;
+
+
+/**
+ * Finds or creates a logger for a named subsystem. If a logger has already been
+ * created with the given name it is returned. Otherwise a new logger is
+ * created. If a new logger is created its log level will be configured based
+ * on the goog.debug.LogManager configuration and it will configured to also
+ * send logging output to its parent's handlers.
+ * @see goog.debug.LogManager
+ *
+ * @param {string} name A name for the logger. This should be a dot-separated
+ * name and should normally be based on the package name or class name of
+ * the subsystem, such as goog.net.BrowserChannel.
+ * @param {goog.log.Level=} opt_level If provided, override the
+ * default logging level with the provided level.
+ * @return {goog.log.Logger} The named logger or null if logging is disabled.
+ */
+goog.log.getLogger = function(name, opt_level) {
+ if (goog.log.ENABLED) {
+ var logger = goog.debug.LogManager.getLogger(name);
+ if (opt_level && logger) {
+ logger.setLevel(opt_level);
+ }
+ return logger;
+ } else {
+ return null;
+ }
+};
+
+
+// TODO(johnlenz): try to tighten the types to these functions.
+/**
+ * Adds a handler to the logger. This doesn't use the event system because
+ * we want to be able to add logging to the event system.
+ * @param {goog.log.Logger} logger
+ * @param {Function} handler Handler function to add.
+ */
+goog.log.addHandler = function(logger, handler) {
+ if (goog.log.ENABLED && logger) {
+ logger.addHandler(handler);
+ }
+};
+
+
+/**
+ * Removes a handler from the logger. This doesn't use the event system because
+ * we want to be able to add logging to the event system.
+ * @param {goog.log.Logger} logger
+ * @param {Function} handler Handler function to remove.
+ * @return {boolean} Whether the handler was removed.
+ */
+goog.log.removeHandler = function(logger, handler) {
+ if (goog.log.ENABLED && logger) {
+ return logger.removeHandler(handler);
+ } else {
+ return false;
+ }
+};
+
+
+/**
+ * Logs a message. If the logger is currently enabled for the
+ * given message level then the given message is forwarded to all the
+ * registered output Handler objects.
+ * @param {goog.log.Logger} logger
+ * @param {goog.log.Level} level One of the level identifiers.
+ * @param {goog.debug.Loggable} msg The message to log.
+ * @param {Error|Object=} opt_exception An exception associated with the
+ * message.
+ */
+goog.log.log = function(logger, level, msg, opt_exception) {
+ if (goog.log.ENABLED && logger) {
+ logger.log(level, msg, opt_exception);
+ }
+};
+
+
+/**
+ * Logs a message at the Level.SEVERE level.
+ * If the logger is currently enabled for the given message level then the
+ * given message is forwarded to all the registered output Handler objects.
+ * @param {goog.log.Logger} logger
+ * @param {goog.debug.Loggable} msg The message to log.
+ * @param {Error=} opt_exception An exception associated with the message.
+ */
+goog.log.error = function(logger, msg, opt_exception) {
+ if (goog.log.ENABLED && logger) {
+ logger.severe(msg, opt_exception);
+ }
+};
+
+
+/**
+ * Logs a message at the Level.WARNING level.
+ * If the logger is currently enabled for the given message level then the
+ * given message is forwarded to all the registered output Handler objects.
+ * @param {goog.log.Logger} logger
+ * @param {goog.debug.Loggable} msg The message to log.
+ * @param {Error=} opt_exception An exception associated with the message.
+ */
+goog.log.warning = function(logger, msg, opt_exception) {
+ if (goog.log.ENABLED && logger) {
+ logger.warning(msg, opt_exception);
+ }
+};
+
+
+/**
+ * Logs a message at the Level.INFO level.
+ * If the logger is currently enabled for the given message level then the
+ * given message is forwarded to all the registered output Handler objects.
+ * @param {goog.log.Logger} logger
+ * @param {goog.debug.Loggable} msg The message to log.
+ * @param {Error=} opt_exception An exception associated with the message.
+ */
+goog.log.info = function(logger, msg, opt_exception) {
+ if (goog.log.ENABLED && logger) {
+ logger.info(msg, opt_exception);
+ }
+};
+
+
+/**
+ * Logs a message at the Level.Fine level.
+ * If the logger is currently enabled for the given message level then the
+ * given message is forwarded to all the registered output Handler objects.
+ * @param {goog.log.Logger} logger
+ * @param {goog.debug.Loggable} msg The message to log.
+ * @param {Error=} opt_exception An exception associated with the message.
+ */
+goog.log.fine = function(logger, msg, opt_exception) {
+ if (goog.log.ENABLED && logger) {
+ logger.fine(msg, opt_exception);
+ }
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/math/box.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/math/box.js
new file mode 100644
index 0000000..bae54e4a
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/math/box.js
@@ -0,0 +1,388 @@
+// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview A utility class for representing a numeric box.
+ */
+
+
+goog.provide('goog.math.Box');
+
+goog.require('goog.math.Coordinate');
+
+
+
+/**
+ * Class for representing a box. A box is specified as a top, right, bottom,
+ * and left. A box is useful for representing margins and padding.
+ *
+ * This class assumes 'screen coordinates': larger Y coordinates are further
+ * from the top of the screen.
+ *
+ * @param {number} top Top.
+ * @param {number} right Right.
+ * @param {number} bottom Bottom.
+ * @param {number} left Left.
+ * @constructor
+ */
+goog.math.Box = function(top, right, bottom, left) {
+ /**
+ * Top
+ * @type {number}
+ */
+ this.top = top;
+
+ /**
+ * Right
+ * @type {number}
+ */
+ this.right = right;
+
+ /**
+ * Bottom
+ * @type {number}
+ */
+ this.bottom = bottom;
+
+ /**
+ * Left
+ * @type {number}
+ */
+ this.left = left;
+};
+
+
+/**
+ * Creates a Box by bounding a collection of goog.math.Coordinate objects
+ * @param {...goog.math.Coordinate} var_args Coordinates to be included inside
+ * the box.
+ * @return {!goog.math.Box} A Box containing all the specified Coordinates.
+ */
+goog.math.Box.boundingBox = function(var_args) {
+ var box = new goog.math.Box(arguments[0].y, arguments[0].x,
+ arguments[0].y, arguments[0].x);
+ for (var i = 1; i < arguments.length; i++) {
+ var coord = arguments[i];
+ box.top = Math.min(box.top, coord.y);
+ box.right = Math.max(box.right, coord.x);
+ box.bottom = Math.max(box.bottom, coord.y);
+ box.left = Math.min(box.left, coord.x);
+ }
+ return box;
+};
+
+
+/**
+ * @return {number} width The width of this Box.
+ */
+goog.math.Box.prototype.getWidth = function() {
+ return this.right - this.left;
+};
+
+
+/**
+ * @return {number} height The height of this Box.
+ */
+goog.math.Box.prototype.getHeight = function() {
+ return this.bottom - this.top;
+};
+
+
+/**
+ * Creates a copy of the box with the same dimensions.
+ * @return {!goog.math.Box} A clone of this Box.
+ */
+goog.math.Box.prototype.clone = function() {
+ return new goog.math.Box(this.top, this.right, this.bottom, this.left);
+};
+
+
+if (goog.DEBUG) {
+ /**
+ * Returns a nice string representing the box.
+ * @return {string} In the form (50t, 73r, 24b, 13l).
+ * @override
+ */
+ goog.math.Box.prototype.toString = function() {
+ return '(' + this.top + 't, ' + this.right + 'r, ' + this.bottom + 'b, ' +
+ this.left + 'l)';
+ };
+}
+
+
+/**
+ * Returns whether the box contains a coordinate or another box.
+ *
+ * @param {goog.math.Coordinate|goog.math.Box} other A Coordinate or a Box.
+ * @return {boolean} Whether the box contains the coordinate or other box.
+ */
+goog.math.Box.prototype.contains = function(other) {
+ return goog.math.Box.contains(this, other);
+};
+
+
+/**
+ * Expands box with the given margins.
+ *
+ * @param {number|goog.math.Box} top Top margin or box with all margins.
+ * @param {number=} opt_right Right margin.
+ * @param {number=} opt_bottom Bottom margin.
+ * @param {number=} opt_left Left margin.
+ * @return {!goog.math.Box} A reference to this Box.
+ */
+goog.math.Box.prototype.expand = function(top, opt_right, opt_bottom,
+ opt_left) {
+ if (goog.isObject(top)) {
+ this.top -= top.top;
+ this.right += top.right;
+ this.bottom += top.bottom;
+ this.left -= top.left;
+ } else {
+ this.top -= top;
+ this.right += opt_right;
+ this.bottom += opt_bottom;
+ this.left -= opt_left;
+ }
+
+ return this;
+};
+
+
+/**
+ * Expand this box to include another box.
+ * NOTE(user): This is used in code that needs to be very fast, please don't
+ * add functionality to this function at the expense of speed (variable
+ * arguments, accepting multiple argument types, etc).
+ * @param {goog.math.Box} box The box to include in this one.
+ */
+goog.math.Box.prototype.expandToInclude = function(box) {
+ this.left = Math.min(this.left, box.left);
+ this.top = Math.min(this.top, box.top);
+ this.right = Math.max(this.right, box.right);
+ this.bottom = Math.max(this.bottom, box.bottom);
+};
+
+
+/**
+ * Compares boxes for equality.
+ * @param {goog.math.Box} a A Box.
+ * @param {goog.math.Box} b A Box.
+ * @return {boolean} True iff the boxes are equal, or if both are null.
+ */
+goog.math.Box.equals = function(a, b) {
+ if (a == b) {
+ return true;
+ }
+ if (!a || !b) {
+ return false;
+ }
+ return a.top == b.top && a.right == b.right &&
+ a.bottom == b.bottom && a.left == b.left;
+};
+
+
+/**
+ * Returns whether a box contains a coordinate or another box.
+ *
+ * @param {goog.math.Box} box A Box.
+ * @param {goog.math.Coordinate|goog.math.Box} other A Coordinate or a Box.
+ * @return {boolean} Whether the box contains the coordinate or other box.
+ */
+goog.math.Box.contains = function(box, other) {
+ if (!box || !other) {
+ return false;
+ }
+
+ if (other instanceof goog.math.Box) {
+ return other.left >= box.left && other.right <= box.right &&
+ other.top >= box.top && other.bottom <= box.bottom;
+ }
+
+ // other is a Coordinate.
+ return other.x >= box.left && other.x <= box.right &&
+ other.y >= box.top && other.y <= box.bottom;
+};
+
+
+/**
+ * Returns the relative x position of a coordinate compared to a box. Returns
+ * zero if the coordinate is inside the box.
+ *
+ * @param {goog.math.Box} box A Box.
+ * @param {goog.math.Coordinate} coord A Coordinate.
+ * @return {number} The x position of {@code coord} relative to the nearest
+ * side of {@code box}, or zero if {@code coord} is inside {@code box}.
+ */
+goog.math.Box.relativePositionX = function(box, coord) {
+ if (coord.x < box.left) {
+ return coord.x - box.left;
+ } else if (coord.x > box.right) {
+ return coord.x - box.right;
+ }
+ return 0;
+};
+
+
+/**
+ * Returns the relative y position of a coordinate compared to a box. Returns
+ * zero if the coordinate is inside the box.
+ *
+ * @param {goog.math.Box} box A Box.
+ * @param {goog.math.Coordinate} coord A Coordinate.
+ * @return {number} The y position of {@code coord} relative to the nearest
+ * side of {@code box}, or zero if {@code coord} is inside {@code box}.
+ */
+goog.math.Box.relativePositionY = function(box, coord) {
+ if (coord.y < box.top) {
+ return coord.y - box.top;
+ } else if (coord.y > box.bottom) {
+ return coord.y - box.bottom;
+ }
+ return 0;
+};
+
+
+/**
+ * Returns the distance between a coordinate and the nearest corner/side of a
+ * box. Returns zero if the coordinate is inside the box.
+ *
+ * @param {goog.math.Box} box A Box.
+ * @param {goog.math.Coordinate} coord A Coordinate.
+ * @return {number} The distance between {@code coord} and the nearest
+ * corner/side of {@code box}, or zero if {@code coord} is inside
+ * {@code box}.
+ */
+goog.math.Box.distance = function(box, coord) {
+ var x = goog.math.Box.relativePositionX(box, coord);
+ var y = goog.math.Box.relativePositionY(box, coord);
+ return Math.sqrt(x * x + y * y);
+};
+
+
+/**
+ * Returns whether two boxes intersect.
+ *
+ * @param {goog.math.Box} a A Box.
+ * @param {goog.math.Box} b A second Box.
+ * @return {boolean} Whether the boxes intersect.
+ */
+goog.math.Box.intersects = function(a, b) {
+ return (a.left <= b.right && b.left <= a.right &&
+ a.top <= b.bottom && b.top <= a.bottom);
+};
+
+
+/**
+ * Returns whether two boxes would intersect with additional padding.
+ *
+ * @param {goog.math.Box} a A Box.
+ * @param {goog.math.Box} b A second Box.
+ * @param {number} padding The additional padding.
+ * @return {boolean} Whether the boxes intersect.
+ */
+goog.math.Box.intersectsWithPadding = function(a, b, padding) {
+ return (a.left <= b.right + padding && b.left <= a.right + padding &&
+ a.top <= b.bottom + padding && b.top <= a.bottom + padding);
+};
+
+
+/**
+ * Rounds the fields to the next larger integer values.
+ *
+ * @return {!goog.math.Box} This box with ceil'd fields.
+ */
+goog.math.Box.prototype.ceil = function() {
+ this.top = Math.ceil(this.top);
+ this.right = Math.ceil(this.right);
+ this.bottom = Math.ceil(this.bottom);
+ this.left = Math.ceil(this.left);
+ return this;
+};
+
+
+/**
+ * Rounds the fields to the next smaller integer values.
+ *
+ * @return {!goog.math.Box} This box with floored fields.
+ */
+goog.math.Box.prototype.floor = function() {
+ this.top = Math.floor(this.top);
+ this.right = Math.floor(this.right);
+ this.bottom = Math.floor(this.bottom);
+ this.left = Math.floor(this.left);
+ return this;
+};
+
+
+/**
+ * Rounds the fields to nearest integer values.
+ *
+ * @return {!goog.math.Box} This box with rounded fields.
+ */
+goog.math.Box.prototype.round = function() {
+ this.top = Math.round(this.top);
+ this.right = Math.round(this.right);
+ this.bottom = Math.round(this.bottom);
+ this.left = Math.round(this.left);
+ return this;
+};
+
+
+/**
+ * Translates this box by the given offsets. If a {@code goog.math.Coordinate}
+ * is given, then the left and right values are translated by the coordinate's
+ * x value and the top and bottom values are translated by the coordinate's y
+ * value. Otherwise, {@code tx} and {@code opt_ty} are used to translate the x
+ * and y dimension values.
+ *
+ * @param {number|goog.math.Coordinate} tx The value to translate the x
+ * dimension values by or the the coordinate to translate this box by.
+ * @param {number=} opt_ty The value to translate y dimension values by.
+ * @return {!goog.math.Box} This box after translating.
+ */
+goog.math.Box.prototype.translate = function(tx, opt_ty) {
+ if (tx instanceof goog.math.Coordinate) {
+ this.left += tx.x;
+ this.right += tx.x;
+ this.top += tx.y;
+ this.bottom += tx.y;
+ } else {
+ this.left += tx;
+ this.right += tx;
+ if (goog.isNumber(opt_ty)) {
+ this.top += opt_ty;
+ this.bottom += opt_ty;
+ }
+ }
+ return this;
+};
+
+
+/**
+ * Scales this coordinate by the given scale factors. The x and y dimension
+ * values are scaled by {@code sx} and {@code opt_sy} respectively.
+ * If {@code opt_sy} is not given, then {@code sx} is used for both x and y.
+ *
+ * @param {number} sx The scale factor to use for the x dimension.
+ * @param {number=} opt_sy The scale factor to use for the y dimension.
+ * @return {!goog.math.Box} This box after scaling.
+ */
+goog.math.Box.prototype.scale = function(sx, opt_sy) {
+ var sy = goog.isNumber(opt_sy) ? opt_sy : sx;
+ this.left *= sx;
+ this.right *= sx;
+ this.top *= sy;
+ this.bottom *= sy;
+ return this;
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/math/coordinate.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/math/coordinate.js
new file mode 100644
index 0000000..bfd1d0c
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/math/coordinate.js
@@ -0,0 +1,267 @@
+// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview A utility class for representing two-dimensional positions.
+ */
+
+
+goog.provide('goog.math.Coordinate');
+
+goog.require('goog.math');
+
+
+
+/**
+ * Class for representing coordinates and positions.
+ * @param {number=} opt_x Left, defaults to 0.
+ * @param {number=} opt_y Top, defaults to 0.
+ * @constructor
+ */
+goog.math.Coordinate = function(opt_x, opt_y) {
+ /**
+ * X-value
+ * @type {number}
+ */
+ this.x = goog.isDef(opt_x) ? opt_x : 0;
+
+ /**
+ * Y-value
+ * @type {number}
+ */
+ this.y = goog.isDef(opt_y) ? opt_y : 0;
+};
+
+
+/**
+ * Returns a new copy of the coordinate.
+ * @return {!goog.math.Coordinate} A clone of this coordinate.
+ */
+goog.math.Coordinate.prototype.clone = function() {
+ return new goog.math.Coordinate(this.x, this.y);
+};
+
+
+if (goog.DEBUG) {
+ /**
+ * Returns a nice string representing the coordinate.
+ * @return {string} In the form (50, 73).
+ * @override
+ */
+ goog.math.Coordinate.prototype.toString = function() {
+ return '(' + this.x + ', ' + this.y + ')';
+ };
+}
+
+
+/**
+ * Compares coordinates for equality.
+ * @param {goog.math.Coordinate} a A Coordinate.
+ * @param {goog.math.Coordinate} b A Coordinate.
+ * @return {boolean} True iff the coordinates are equal, or if both are null.
+ */
+goog.math.Coordinate.equals = function(a, b) {
+ if (a == b) {
+ return true;
+ }
+ if (!a || !b) {
+ return false;
+ }
+ return a.x == b.x && a.y == b.y;
+};
+
+
+/**
+ * Returns the distance between two coordinates.
+ * @param {!goog.math.Coordinate} a A Coordinate.
+ * @param {!goog.math.Coordinate} b A Coordinate.
+ * @return {number} The distance between {@code a} and {@code b}.
+ */
+goog.math.Coordinate.distance = function(a, b) {
+ var dx = a.x - b.x;
+ var dy = a.y - b.y;
+ return Math.sqrt(dx * dx + dy * dy);
+};
+
+
+/**
+ * Returns the magnitude of a coordinate.
+ * @param {!goog.math.Coordinate} a A Coordinate.
+ * @return {number} The distance between the origin and {@code a}.
+ */
+goog.math.Coordinate.magnitude = function(a) {
+ return Math.sqrt(a.x * a.x + a.y * a.y);
+};
+
+
+/**
+ * Returns the angle from the origin to a coordinate.
+ * @param {!goog.math.Coordinate} a A Coordinate.
+ * @return {number} The angle, in degrees, clockwise from the positive X
+ * axis to {@code a}.
+ */
+goog.math.Coordinate.azimuth = function(a) {
+ return goog.math.angle(0, 0, a.x, a.y);
+};
+
+
+/**
+ * Returns the squared distance between two coordinates. Squared distances can
+ * be used for comparisons when the actual value is not required.
+ *
+ * Performance note: eliminating the square root is an optimization often used
+ * in lower-level languages, but the speed difference is not nearly as
+ * pronounced in JavaScript (only a few percent.)
+ *
+ * @param {!goog.math.Coordinate} a A Coordinate.
+ * @param {!goog.math.Coordinate} b A Coordinate.
+ * @return {number} The squared distance between {@code a} and {@code b}.
+ */
+goog.math.Coordinate.squaredDistance = function(a, b) {
+ var dx = a.x - b.x;
+ var dy = a.y - b.y;
+ return dx * dx + dy * dy;
+};
+
+
+/**
+ * Returns the difference between two coordinates as a new
+ * goog.math.Coordinate.
+ * @param {!goog.math.Coordinate} a A Coordinate.
+ * @param {!goog.math.Coordinate} b A Coordinate.
+ * @return {!goog.math.Coordinate} A Coordinate representing the difference
+ * between {@code a} and {@code b}.
+ */
+goog.math.Coordinate.difference = function(a, b) {
+ return new goog.math.Coordinate(a.x - b.x, a.y - b.y);
+};
+
+
+/**
+ * Returns the sum of two coordinates as a new goog.math.Coordinate.
+ * @param {!goog.math.Coordinate} a A Coordinate.
+ * @param {!goog.math.Coordinate} b A Coordinate.
+ * @return {!goog.math.Coordinate} A Coordinate representing the sum of the two
+ * coordinates.
+ */
+goog.math.Coordinate.sum = function(a, b) {
+ return new goog.math.Coordinate(a.x + b.x, a.y + b.y);
+};
+
+
+/**
+ * Rounds the x and y fields to the next larger integer values.
+ * @return {!goog.math.Coordinate} This coordinate with ceil'd fields.
+ */
+goog.math.Coordinate.prototype.ceil = function() {
+ this.x = Math.ceil(this.x);
+ this.y = Math.ceil(this.y);
+ return this;
+};
+
+
+/**
+ * Rounds the x and y fields to the next smaller integer values.
+ * @return {!goog.math.Coordinate} This coordinate with floored fields.
+ */
+goog.math.Coordinate.prototype.floor = function() {
+ this.x = Math.floor(this.x);
+ this.y = Math.floor(this.y);
+ return this;
+};
+
+
+/**
+ * Rounds the x and y fields to the nearest integer values.
+ * @return {!goog.math.Coordinate} This coordinate with rounded fields.
+ */
+goog.math.Coordinate.prototype.round = function() {
+ this.x = Math.round(this.x);
+ this.y = Math.round(this.y);
+ return this;
+};
+
+
+/**
+ * Translates this box by the given offsets. If a {@code goog.math.Coordinate}
+ * is given, then the x and y values are translated by the coordinate's x and y.
+ * Otherwise, x and y are translated by {@code tx} and {@code opt_ty}
+ * respectively.
+ * @param {number|goog.math.Coordinate} tx The value to translate x by or the
+ * the coordinate to translate this coordinate by.
+ * @param {number=} opt_ty The value to translate y by.
+ * @return {!goog.math.Coordinate} This coordinate after translating.
+ */
+goog.math.Coordinate.prototype.translate = function(tx, opt_ty) {
+ if (tx instanceof goog.math.Coordinate) {
+ this.x += tx.x;
+ this.y += tx.y;
+ } else {
+ this.x += tx;
+ if (goog.isNumber(opt_ty)) {
+ this.y += opt_ty;
+ }
+ }
+ return this;
+};
+
+
+/**
+ * Scales this coordinate by the given scale factors. The x and y values are
+ * scaled by {@code sx} and {@code opt_sy} respectively. If {@code opt_sy}
+ * is not given, then {@code sx} is used for both x and y.
+ * @param {number} sx The scale factor to use for the x dimension.
+ * @param {number=} opt_sy The scale factor to use for the y dimension.
+ * @return {!goog.math.Coordinate} This coordinate after scaling.
+ */
+goog.math.Coordinate.prototype.scale = function(sx, opt_sy) {
+ var sy = goog.isNumber(opt_sy) ? opt_sy : sx;
+ this.x *= sx;
+ this.y *= sy;
+ return this;
+};
+
+
+/**
+ * Rotates this coordinate clockwise about the origin (or, optionally, the given
+ * center) by the given angle, in radians.
+ * @param {number} radians The angle by which to rotate this coordinate
+ * clockwise about the given center, in radians.
+ * @param {!goog.math.Coordinate=} opt_center The center of rotation. Defaults
+ * to (0, 0) if not given.
+ */
+goog.math.Coordinate.prototype.rotateRadians = function(radians, opt_center) {
+ var center = opt_center || new goog.math.Coordinate(0, 0);
+
+ var x = this.x;
+ var y = this.y;
+ var cos = Math.cos(radians);
+ var sin = Math.sin(radians);
+
+ this.x = (x - center.x) * cos - (y - center.y) * sin + center.x;
+ this.y = (x - center.x) * sin + (y - center.y) * cos + center.y;
+};
+
+
+/**
+ * Rotates this coordinate clockwise about the origin (or, optionally, the given
+ * center) by the given angle, in degrees.
+ * @param {number} degrees The angle by which to rotate this coordinate
+ * clockwise about the given center, in degrees.
+ * @param {!goog.math.Coordinate=} opt_center The center of rotation. Defaults
+ * to (0, 0) if not given.
+ */
+goog.math.Coordinate.prototype.rotateDegrees = function(degrees, opt_center) {
+ this.rotateRadians(goog.math.toRadians(degrees), opt_center);
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/math/math.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/math/math.js
new file mode 100644
index 0000000..ba789dc8
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/math/math.js
@@ -0,0 +1,435 @@
+// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Additional mathematical functions.
+ */
+
+goog.provide('goog.math');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+
+
+/**
+ * Returns a random integer greater than or equal to 0 and less than {@code a}.
+ * @param {number} a The upper bound for the random integer (exclusive).
+ * @return {number} A random integer N such that 0 <= N < a.
+ */
+goog.math.randomInt = function(a) {
+ return Math.floor(Math.random() * a);
+};
+
+
+/**
+ * Returns a random number greater than or equal to {@code a} and less than
+ * {@code b}.
+ * @param {number} a The lower bound for the random number (inclusive).
+ * @param {number} b The upper bound for the random number (exclusive).
+ * @return {number} A random number N such that a <= N < b.
+ */
+goog.math.uniformRandom = function(a, b) {
+ return a + Math.random() * (b - a);
+};
+
+
+/**
+ * Takes a number and clamps it to within the provided bounds.
+ * @param {number} value The input number.
+ * @param {number} min The minimum value to return.
+ * @param {number} max The maximum value to return.
+ * @return {number} The input number if it is within bounds, or the nearest
+ * number within the bounds.
+ */
+goog.math.clamp = function(value, min, max) {
+ return Math.min(Math.max(value, min), max);
+};
+
+
+/**
+ * The % operator in JavaScript returns the remainder of a / b, but differs from
+ * some other languages in that the result will have the same sign as the
+ * dividend. For example, -1 % 8 == -1, whereas in some other languages
+ * (such as Python) the result would be 7. This function emulates the more
+ * correct modulo behavior, which is useful for certain applications such as
+ * calculating an offset index in a circular list.
+ *
+ * @param {number} a The dividend.
+ * @param {number} b The divisor.
+ * @return {number} a % b where the result is between 0 and b (either 0 <= x < b
+ * or b < x <= 0, depending on the sign of b).
+ */
+goog.math.modulo = function(a, b) {
+ var r = a % b;
+ // If r and b differ in sign, add b to wrap the result to the correct sign.
+ return (r * b < 0) ? r + b : r;
+};
+
+
+/**
+ * Performs linear interpolation between values a and b. Returns the value
+ * between a and b proportional to x (when x is between 0 and 1. When x is
+ * outside this range, the return value is a linear extrapolation).
+ * @param {number} a A number.
+ * @param {number} b A number.
+ * @param {number} x The proportion between a and b.
+ * @return {number} The interpolated value between a and b.
+ */
+goog.math.lerp = function(a, b, x) {
+ return a + x * (b - a);
+};
+
+
+/**
+ * Tests whether the two values are equal to each other, within a certain
+ * tolerance to adjust for floating point errors.
+ * @param {number} a A number.
+ * @param {number} b A number.
+ * @param {number=} opt_tolerance Optional tolerance range. Defaults
+ * to 0.000001. If specified, should be greater than 0.
+ * @return {boolean} Whether {@code a} and {@code b} are nearly equal.
+ */
+goog.math.nearlyEquals = function(a, b, opt_tolerance) {
+ return Math.abs(a - b) <= (opt_tolerance || 0.000001);
+};
+
+
+// TODO(user): Rename to normalizeAngle, retaining old name as deprecated
+// alias.
+/**
+ * Normalizes an angle to be in range [0-360). Angles outside this range will
+ * be normalized to be the equivalent angle with that range.
+ * @param {number} angle Angle in degrees.
+ * @return {number} Standardized angle.
+ */
+goog.math.standardAngle = function(angle) {
+ return goog.math.modulo(angle, 360);
+};
+
+
+/**
+ * Normalizes an angle to be in range [0-2*PI). Angles outside this range will
+ * be normalized to be the equivalent angle with that range.
+ * @param {number} angle Angle in radians.
+ * @return {number} Standardized angle.
+ */
+goog.math.standardAngleInRadians = function(angle) {
+ return goog.math.modulo(angle, 2 * Math.PI);
+};
+
+
+/**
+ * Converts degrees to radians.
+ * @param {number} angleDegrees Angle in degrees.
+ * @return {number} Angle in radians.
+ */
+goog.math.toRadians = function(angleDegrees) {
+ return angleDegrees * Math.PI / 180;
+};
+
+
+/**
+ * Converts radians to degrees.
+ * @param {number} angleRadians Angle in radians.
+ * @return {number} Angle in degrees.
+ */
+goog.math.toDegrees = function(angleRadians) {
+ return angleRadians * 180 / Math.PI;
+};
+
+
+/**
+ * For a given angle and radius, finds the X portion of the offset.
+ * @param {number} degrees Angle in degrees (zero points in +X direction).
+ * @param {number} radius Radius.
+ * @return {number} The x-distance for the angle and radius.
+ */
+goog.math.angleDx = function(degrees, radius) {
+ return radius * Math.cos(goog.math.toRadians(degrees));
+};
+
+
+/**
+ * For a given angle and radius, finds the Y portion of the offset.
+ * @param {number} degrees Angle in degrees (zero points in +X direction).
+ * @param {number} radius Radius.
+ * @return {number} The y-distance for the angle and radius.
+ */
+goog.math.angleDy = function(degrees, radius) {
+ return radius * Math.sin(goog.math.toRadians(degrees));
+};
+
+
+/**
+ * Computes the angle between two points (x1,y1) and (x2,y2).
+ * Angle zero points in the +X direction, 90 degrees points in the +Y
+ * direction (down) and from there we grow clockwise towards 360 degrees.
+ * @param {number} x1 x of first point.
+ * @param {number} y1 y of first point.
+ * @param {number} x2 x of second point.
+ * @param {number} y2 y of second point.
+ * @return {number} Standardized angle in degrees of the vector from
+ * x1,y1 to x2,y2.
+ */
+goog.math.angle = function(x1, y1, x2, y2) {
+ return goog.math.standardAngle(goog.math.toDegrees(Math.atan2(y2 - y1,
+ x2 - x1)));
+};
+
+
+/**
+ * Computes the difference between startAngle and endAngle (angles in degrees).
+ * @param {number} startAngle Start angle in degrees.
+ * @param {number} endAngle End angle in degrees.
+ * @return {number} The number of degrees that when added to
+ * startAngle will result in endAngle. Positive numbers mean that the
+ * direction is clockwise. Negative numbers indicate a counter-clockwise
+ * direction.
+ * The shortest route (clockwise vs counter-clockwise) between the angles
+ * is used.
+ * When the difference is 180 degrees, the function returns 180 (not -180)
+ * angleDifference(30, 40) is 10, and angleDifference(40, 30) is -10.
+ * angleDifference(350, 10) is 20, and angleDifference(10, 350) is -20.
+ */
+goog.math.angleDifference = function(startAngle, endAngle) {
+ var d = goog.math.standardAngle(endAngle) -
+ goog.math.standardAngle(startAngle);
+ if (d > 180) {
+ d = d - 360;
+ } else if (d <= -180) {
+ d = 360 + d;
+ }
+ return d;
+};
+
+
+/**
+ * Returns the sign of a number as per the "sign" or "signum" function.
+ * @param {number} x The number to take the sign of.
+ * @return {number} -1 when negative, 1 when positive, 0 when 0.
+ */
+goog.math.sign = function(x) {
+ return x == 0 ? 0 : (x < 0 ? -1 : 1);
+};
+
+
+/**
+ * JavaScript implementation of Longest Common Subsequence problem.
+ * http://en.wikipedia.org/wiki/Longest_common_subsequence
+ *
+ * Returns the longest possible array that is subarray of both of given arrays.
+ *
+ * @param {Array.<Object>} array1 First array of objects.
+ * @param {Array.<Object>} array2 Second array of objects.
+ * @param {Function=} opt_compareFn Function that acts as a custom comparator
+ * for the array ojects. Function should return true if objects are equal,
+ * otherwise false.
+ * @param {Function=} opt_collectorFn Function used to decide what to return
+ * as a result subsequence. It accepts 2 arguments: index of common element
+ * in the first array and index in the second. The default function returns
+ * element from the first array.
+ * @return {!Array.<Object>} A list of objects that are common to both arrays
+ * such that there is no common subsequence with size greater than the
+ * length of the list.
+ */
+goog.math.longestCommonSubsequence = function(
+ array1, array2, opt_compareFn, opt_collectorFn) {
+
+ var compare = opt_compareFn || function(a, b) {
+ return a == b;
+ };
+
+ var collect = opt_collectorFn || function(i1, i2) {
+ return array1[i1];
+ };
+
+ var length1 = array1.length;
+ var length2 = array2.length;
+
+ var arr = [];
+ for (var i = 0; i < length1 + 1; i++) {
+ arr[i] = [];
+ arr[i][0] = 0;
+ }
+
+ for (var j = 0; j < length2 + 1; j++) {
+ arr[0][j] = 0;
+ }
+
+ for (i = 1; i <= length1; i++) {
+ for (j = 1; j <= length2; j++) {
+ if (compare(array1[i - 1], array2[j - 1])) {
+ arr[i][j] = arr[i - 1][j - 1] + 1;
+ } else {
+ arr[i][j] = Math.max(arr[i - 1][j], arr[i][j - 1]);
+ }
+ }
+ }
+
+ // Backtracking
+ var result = [];
+ var i = length1, j = length2;
+ while (i > 0 && j > 0) {
+ if (compare(array1[i - 1], array2[j - 1])) {
+ result.unshift(collect(i - 1, j - 1));
+ i--;
+ j--;
+ } else {
+ if (arr[i - 1][j] > arr[i][j - 1]) {
+ i--;
+ } else {
+ j--;
+ }
+ }
+ }
+
+ return result;
+};
+
+
+/**
+ * Returns the sum of the arguments.
+ * @param {...number} var_args Numbers to add.
+ * @return {number} The sum of the arguments (0 if no arguments were provided,
+ * {@code NaN} if any of the arguments is not a valid number).
+ */
+goog.math.sum = function(var_args) {
+ return /** @type {number} */ (goog.array.reduce(arguments,
+ function(sum, value) {
+ return sum + value;
+ }, 0));
+};
+
+
+/**
+ * Returns the arithmetic mean of the arguments.
+ * @param {...number} var_args Numbers to average.
+ * @return {number} The average of the arguments ({@code NaN} if no arguments
+ * were provided or any of the arguments is not a valid number).
+ */
+goog.math.average = function(var_args) {
+ return goog.math.sum.apply(null, arguments) / arguments.length;
+};
+
+
+/**
+ * Returns the unbiased sample variance of the arguments. For a definition,
+ * see e.g. http://en.wikipedia.org/wiki/Variance
+ * @param {...number} var_args Number samples to analyze.
+ * @return {number} The unbiased sample variance of the arguments (0 if fewer
+ * than two samples were provided, or {@code NaN} if any of the samples is
+ * not a valid number).
+ */
+goog.math.sampleVariance = function(var_args) {
+ var sampleSize = arguments.length;
+ if (sampleSize < 2) {
+ return 0;
+ }
+
+ var mean = goog.math.average.apply(null, arguments);
+ var variance = goog.math.sum.apply(null, goog.array.map(arguments,
+ function(val) {
+ return Math.pow(val - mean, 2);
+ })) / (sampleSize - 1);
+
+ return variance;
+};
+
+
+/**
+ * Returns the sample standard deviation of the arguments. For a definition of
+ * sample standard deviation, see e.g.
+ * http://en.wikipedia.org/wiki/Standard_deviation
+ * @param {...number} var_args Number samples to analyze.
+ * @return {number} The sample standard deviation of the arguments (0 if fewer
+ * than two samples were provided, or {@code NaN} if any of the samples is
+ * not a valid number).
+ */
+goog.math.standardDeviation = function(var_args) {
+ return Math.sqrt(goog.math.sampleVariance.apply(null, arguments));
+};
+
+
+/**
+ * Returns whether the supplied number represents an integer, i.e. that is has
+ * no fractional component. No range-checking is performed on the number.
+ * @param {number} num The number to test.
+ * @return {boolean} Whether {@code num} is an integer.
+ */
+goog.math.isInt = function(num) {
+ return isFinite(num) && num % 1 == 0;
+};
+
+
+/**
+ * Returns whether the supplied number is finite and not NaN.
+ * @param {number} num The number to test.
+ * @return {boolean} Whether {@code num} is a finite number.
+ */
+goog.math.isFiniteNumber = function(num) {
+ return isFinite(num) && !isNaN(num);
+};
+
+
+/**
+ * Returns the precise value of floor(log10(num)).
+ * Simpler implementations didn't work because of floating point rounding
+ * errors. For example
+ * <ul>
+ * <li>Math.floor(Math.log(num) / Math.LN10) is off by one for num == 1e+3.
+ * <li>Math.floor(Math.log(num) * Math.LOG10E) is off by one for num == 1e+15.
+ * <li>Math.floor(Math.log10(num)) is off by one for num == 1e+15 - 1.
+ * </ul>
+ * @param {number} num A floating point number.
+ * @return {number} Its logarithm to base 10 rounded down to the nearest
+ * integer if num > 0. -Infinity if num == 0. NaN if num < 0.
+ */
+goog.math.log10Floor = function(num) {
+ if (num > 0) {
+ var x = Math.round(Math.log(num) * Math.LOG10E);
+ return x - (parseFloat('1e' + x) > num);
+ }
+ return num == 0 ? -Infinity : NaN;
+};
+
+
+/**
+ * A tweaked variant of {@code Math.floor} which tolerates if the passed number
+ * is infinitesimally smaller than the closest integer. It often happens with
+ * the results of floating point calculations because of the finite precision
+ * of the intermediate results. For example {@code Math.floor(Math.log(1000) /
+ * Math.LN10) == 2}, not 3 as one would expect.
+ * @param {number} num A number.
+ * @param {number=} opt_epsilon An infinitesimally small positive number, the
+ * rounding error to tolerate.
+ * @return {number} The largest integer less than or equal to {@code num}.
+ */
+goog.math.safeFloor = function(num, opt_epsilon) {
+ goog.asserts.assert(!goog.isDef(opt_epsilon) || opt_epsilon > 0);
+ return Math.floor(num + (opt_epsilon || 2e-15));
+};
+
+
+/**
+ * A tweaked variant of {@code Math.ceil}. See {@code goog.math.safeFloor} for
+ * details.
+ * @param {number} num A number.
+ * @param {number=} opt_epsilon An infinitesimally small positive number, the
+ * rounding error to tolerate.
+ * @return {number} The smallest integer greater than or equal to {@code num}.
+ */
+goog.math.safeCeil = function(num, opt_epsilon) {
+ goog.asserts.assert(!goog.isDef(opt_epsilon) || opt_epsilon > 0);
+ return Math.ceil(num - (opt_epsilon || 2e-15));
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/math/rect.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/math/rect.js
new file mode 100644
index 0000000..008918b
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/math/rect.js
@@ -0,0 +1,463 @@
+// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview A utility class for representing rectangles.
+ */
+
+goog.provide('goog.math.Rect');
+
+goog.require('goog.math.Box');
+goog.require('goog.math.Coordinate');
+goog.require('goog.math.Size');
+
+
+
+/**
+ * Class for representing rectangular regions.
+ * @param {number} x Left.
+ * @param {number} y Top.
+ * @param {number} w Width.
+ * @param {number} h Height.
+ * @constructor
+ */
+goog.math.Rect = function(x, y, w, h) {
+ /** @type {number} */
+ this.left = x;
+
+ /** @type {number} */
+ this.top = y;
+
+ /** @type {number} */
+ this.width = w;
+
+ /** @type {number} */
+ this.height = h;
+};
+
+
+/**
+ * @return {!goog.math.Rect} A new copy of this Rectangle.
+ */
+goog.math.Rect.prototype.clone = function() {
+ return new goog.math.Rect(this.left, this.top, this.width, this.height);
+};
+
+
+/**
+ * Returns a new Box object with the same position and dimensions as this
+ * rectangle.
+ * @return {!goog.math.Box} A new Box representation of this Rectangle.
+ */
+goog.math.Rect.prototype.toBox = function() {
+ var right = this.left + this.width;
+ var bottom = this.top + this.height;
+ return new goog.math.Box(this.top,
+ right,
+ bottom,
+ this.left);
+};
+
+
+/**
+ * Creates a new Rect object with the same position and dimensions as a given
+ * Box. Note that this is only the inverse of toBox if left/top are defined.
+ * @param {goog.math.Box} box A box.
+ * @return {!goog.math.Rect} A new Rect initialized with the box's position
+ * and size.
+ */
+goog.math.Rect.createFromBox = function(box) {
+ return new goog.math.Rect(box.left, box.top,
+ box.right - box.left, box.bottom - box.top);
+};
+
+
+if (goog.DEBUG) {
+ /**
+ * Returns a nice string representing size and dimensions of rectangle.
+ * @return {string} In the form (50, 73 - 75w x 25h).
+ * @override
+ */
+ goog.math.Rect.prototype.toString = function() {
+ return '(' + this.left + ', ' + this.top + ' - ' + this.width + 'w x ' +
+ this.height + 'h)';
+ };
+}
+
+
+/**
+ * Compares rectangles for equality.
+ * @param {goog.math.Rect} a A Rectangle.
+ * @param {goog.math.Rect} b A Rectangle.
+ * @return {boolean} True iff the rectangles have the same left, top, width,
+ * and height, or if both are null.
+ */
+goog.math.Rect.equals = function(a, b) {
+ if (a == b) {
+ return true;
+ }
+ if (!a || !b) {
+ return false;
+ }
+ return a.left == b.left && a.width == b.width &&
+ a.top == b.top && a.height == b.height;
+};
+
+
+/**
+ * Computes the intersection of this rectangle and the rectangle parameter. If
+ * there is no intersection, returns false and leaves this rectangle as is.
+ * @param {goog.math.Rect} rect A Rectangle.
+ * @return {boolean} True iff this rectangle intersects with the parameter.
+ */
+goog.math.Rect.prototype.intersection = function(rect) {
+ var x0 = Math.max(this.left, rect.left);
+ var x1 = Math.min(this.left + this.width, rect.left + rect.width);
+
+ if (x0 <= x1) {
+ var y0 = Math.max(this.top, rect.top);
+ var y1 = Math.min(this.top + this.height, rect.top + rect.height);
+
+ if (y0 <= y1) {
+ this.left = x0;
+ this.top = y0;
+ this.width = x1 - x0;
+ this.height = y1 - y0;
+
+ return true;
+ }
+ }
+ return false;
+};
+
+
+/**
+ * Returns the intersection of two rectangles. Two rectangles intersect if they
+ * touch at all, for example, two zero width and height rectangles would
+ * intersect if they had the same top and left.
+ * @param {goog.math.Rect} a A Rectangle.
+ * @param {goog.math.Rect} b A Rectangle.
+ * @return {goog.math.Rect} A new intersection rect (even if width and height
+ * are 0), or null if there is no intersection.
+ */
+goog.math.Rect.intersection = function(a, b) {
+ // There is no nice way to do intersection via a clone, because any such
+ // clone might be unnecessary if this function returns null. So, we duplicate
+ // code from above.
+
+ var x0 = Math.max(a.left, b.left);
+ var x1 = Math.min(a.left + a.width, b.left + b.width);
+
+ if (x0 <= x1) {
+ var y0 = Math.max(a.top, b.top);
+ var y1 = Math.min(a.top + a.height, b.top + b.height);
+
+ if (y0 <= y1) {
+ return new goog.math.Rect(x0, y0, x1 - x0, y1 - y0);
+ }
+ }
+ return null;
+};
+
+
+/**
+ * Returns whether two rectangles intersect. Two rectangles intersect if they
+ * touch at all, for example, two zero width and height rectangles would
+ * intersect if they had the same top and left.
+ * @param {goog.math.Rect} a A Rectangle.
+ * @param {goog.math.Rect} b A Rectangle.
+ * @return {boolean} Whether a and b intersect.
+ */
+goog.math.Rect.intersects = function(a, b) {
+ return (a.left <= b.left + b.width && b.left <= a.left + a.width &&
+ a.top <= b.top + b.height && b.top <= a.top + a.height);
+};
+
+
+/**
+ * Returns whether a rectangle intersects this rectangle.
+ * @param {goog.math.Rect} rect A rectangle.
+ * @return {boolean} Whether rect intersects this rectangle.
+ */
+goog.math.Rect.prototype.intersects = function(rect) {
+ return goog.math.Rect.intersects(this, rect);
+};
+
+
+/**
+ * Computes the difference regions between two rectangles. The return value is
+ * an array of 0 to 4 rectangles defining the remaining regions of the first
+ * rectangle after the second has been subtracted.
+ * @param {goog.math.Rect} a A Rectangle.
+ * @param {goog.math.Rect} b A Rectangle.
+ * @return {!Array.<!goog.math.Rect>} An array with 0 to 4 rectangles which
+ * together define the difference area of rectangle a minus rectangle b.
+ */
+goog.math.Rect.difference = function(a, b) {
+ var intersection = goog.math.Rect.intersection(a, b);
+ if (!intersection || !intersection.height || !intersection.width) {
+ return [a.clone()];
+ }
+
+ var result = [];
+
+ var top = a.top;
+ var height = a.height;
+
+ var ar = a.left + a.width;
+ var ab = a.top + a.height;
+
+ var br = b.left + b.width;
+ var bb = b.top + b.height;
+
+ // Subtract off any area on top where A extends past B
+ if (b.top > a.top) {
+ result.push(new goog.math.Rect(a.left, a.top, a.width, b.top - a.top));
+ top = b.top;
+ // If we're moving the top down, we also need to subtract the height diff.
+ height -= b.top - a.top;
+ }
+ // Subtract off any area on bottom where A extends past B
+ if (bb < ab) {
+ result.push(new goog.math.Rect(a.left, bb, a.width, ab - bb));
+ height = bb - top;
+ }
+ // Subtract any area on left where A extends past B
+ if (b.left > a.left) {
+ result.push(new goog.math.Rect(a.left, top, b.left - a.left, height));
+ }
+ // Subtract any area on right where A extends past B
+ if (br < ar) {
+ result.push(new goog.math.Rect(br, top, ar - br, height));
+ }
+
+ return result;
+};
+
+
+/**
+ * Computes the difference regions between this rectangle and {@code rect}. The
+ * return value is an array of 0 to 4 rectangles defining the remaining regions
+ * of this rectangle after the other has been subtracted.
+ * @param {goog.math.Rect} rect A Rectangle.
+ * @return {!Array.<!goog.math.Rect>} An array with 0 to 4 rectangles which
+ * together define the difference area of rectangle a minus rectangle b.
+ */
+goog.math.Rect.prototype.difference = function(rect) {
+ return goog.math.Rect.difference(this, rect);
+};
+
+
+/**
+ * Expand this rectangle to also include the area of the given rectangle.
+ * @param {goog.math.Rect} rect The other rectangle.
+ */
+goog.math.Rect.prototype.boundingRect = function(rect) {
+ // We compute right and bottom before we change left and top below.
+ var right = Math.max(this.left + this.width, rect.left + rect.width);
+ var bottom = Math.max(this.top + this.height, rect.top + rect.height);
+
+ this.left = Math.min(this.left, rect.left);
+ this.top = Math.min(this.top, rect.top);
+
+ this.width = right - this.left;
+ this.height = bottom - this.top;
+};
+
+
+/**
+ * Returns a new rectangle which completely contains both input rectangles.
+ * @param {goog.math.Rect} a A rectangle.
+ * @param {goog.math.Rect} b A rectangle.
+ * @return {goog.math.Rect} A new bounding rect, or null if either rect is
+ * null.
+ */
+goog.math.Rect.boundingRect = function(a, b) {
+ if (!a || !b) {
+ return null;
+ }
+
+ var clone = a.clone();
+ clone.boundingRect(b);
+
+ return clone;
+};
+
+
+/**
+ * Tests whether this rectangle entirely contains another rectangle or
+ * coordinate.
+ *
+ * @param {goog.math.Rect|goog.math.Coordinate} another The rectangle or
+ * coordinate to test for containment.
+ * @return {boolean} Whether this rectangle contains given rectangle or
+ * coordinate.
+ */
+goog.math.Rect.prototype.contains = function(another) {
+ if (another instanceof goog.math.Rect) {
+ return this.left <= another.left &&
+ this.left + this.width >= another.left + another.width &&
+ this.top <= another.top &&
+ this.top + this.height >= another.top + another.height;
+ } else { // (another instanceof goog.math.Coordinate)
+ return another.x >= this.left &&
+ another.x <= this.left + this.width &&
+ another.y >= this.top &&
+ another.y <= this.top + this.height;
+ }
+};
+
+
+/**
+ * @param {!goog.math.Coordinate} point A coordinate.
+ * @return {number} The squared distance between the point and the closest
+ * point inside the rectangle. Returns 0 if the point is inside the
+ * rectangle.
+ */
+goog.math.Rect.prototype.squaredDistance = function(point) {
+ var dx = point.x < this.left ?
+ this.left - point.x : Math.max(point.x - (this.left + this.width), 0);
+ var dy = point.y < this.top ?
+ this.top - point.y : Math.max(point.y - (this.top + this.height), 0);
+ return dx * dx + dy * dy;
+};
+
+
+/**
+ * @param {!goog.math.Coordinate} point A coordinate.
+ * @return {number} The distance between the point and the closest point
+ * inside the rectangle. Returns 0 if the point is inside the rectangle.
+ */
+goog.math.Rect.prototype.distance = function(point) {
+ return Math.sqrt(this.squaredDistance(point));
+};
+
+
+/**
+ * @return {!goog.math.Size} The size of this rectangle.
+ */
+goog.math.Rect.prototype.getSize = function() {
+ return new goog.math.Size(this.width, this.height);
+};
+
+
+/**
+ * @return {!goog.math.Coordinate} A new coordinate for the top-left corner of
+ * the rectangle.
+ */
+goog.math.Rect.prototype.getTopLeft = function() {
+ return new goog.math.Coordinate(this.left, this.top);
+};
+
+
+/**
+ * @return {!goog.math.Coordinate} A new coordinate for the center of the
+ * rectangle.
+ */
+goog.math.Rect.prototype.getCenter = function() {
+ return new goog.math.Coordinate(
+ this.left + this.width / 2, this.top + this.height / 2);
+};
+
+
+/**
+ * @return {!goog.math.Coordinate} A new coordinate for the bottom-right corner
+ * of the rectangle.
+ */
+goog.math.Rect.prototype.getBottomRight = function() {
+ return new goog.math.Coordinate(
+ this.left + this.width, this.top + this.height);
+};
+
+
+/**
+ * Rounds the fields to the next larger integer values.
+ * @return {!goog.math.Rect} This rectangle with ceil'd fields.
+ */
+goog.math.Rect.prototype.ceil = function() {
+ this.left = Math.ceil(this.left);
+ this.top = Math.ceil(this.top);
+ this.width = Math.ceil(this.width);
+ this.height = Math.ceil(this.height);
+ return this;
+};
+
+
+/**
+ * Rounds the fields to the next smaller integer values.
+ * @return {!goog.math.Rect} This rectangle with floored fields.
+ */
+goog.math.Rect.prototype.floor = function() {
+ this.left = Math.floor(this.left);
+ this.top = Math.floor(this.top);
+ this.width = Math.floor(this.width);
+ this.height = Math.floor(this.height);
+ return this;
+};
+
+
+/**
+ * Rounds the fields to nearest integer values.
+ * @return {!goog.math.Rect} This rectangle with rounded fields.
+ */
+goog.math.Rect.prototype.round = function() {
+ this.left = Math.round(this.left);
+ this.top = Math.round(this.top);
+ this.width = Math.round(this.width);
+ this.height = Math.round(this.height);
+ return this;
+};
+
+
+/**
+ * Translates this rectangle by the given offsets. If a
+ * {@code goog.math.Coordinate} is given, then the left and top values are
+ * translated by the coordinate's x and y values. Otherwise, top and left are
+ * translated by {@code tx} and {@code opt_ty} respectively.
+ * @param {number|goog.math.Coordinate} tx The value to translate left by or the
+ * the coordinate to translate this rect by.
+ * @param {number=} opt_ty The value to translate top by.
+ * @return {!goog.math.Rect} This rectangle after translating.
+ */
+goog.math.Rect.prototype.translate = function(tx, opt_ty) {
+ if (tx instanceof goog.math.Coordinate) {
+ this.left += tx.x;
+ this.top += tx.y;
+ } else {
+ this.left += tx;
+ if (goog.isNumber(opt_ty)) {
+ this.top += opt_ty;
+ }
+ }
+ return this;
+};
+
+
+/**
+ * Scales this rectangle by the given scale factors. The left and width values
+ * are scaled by {@code sx} and the top and height values are scaled by
+ * {@code opt_sy}. If {@code opt_sy} is not given, then all fields are scaled
+ * by {@code sx}.
+ * @param {number} sx The scale factor to use for the x dimension.
+ * @param {number=} opt_sy The scale factor to use for the y dimension.
+ * @return {!goog.math.Rect} This rectangle after scaling.
+ */
+goog.math.Rect.prototype.scale = function(sx, opt_sy) {
+ var sy = goog.isNumber(opt_sy) ? opt_sy : sx;
+ this.left *= sx;
+ this.width *= sx;
+ this.top *= sy;
+ this.height *= sy;
+ return this;
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/math/size.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/math/size.js
new file mode 100644
index 0000000..1b3841b
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/math/size.js
@@ -0,0 +1,206 @@
+// Copyright 2007 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview A utility class for representing two-dimensional sizes.
+ */
+
+
+goog.provide('goog.math.Size');
+
+
+
+/**
+ * Class for representing sizes consisting of a width and height. Undefined
+ * width and height support is deprecated and results in compiler warning.
+ * @param {number} width Width.
+ * @param {number} height Height.
+ * @constructor
+ */
+goog.math.Size = function(width, height) {
+ /**
+ * Width
+ * @type {number}
+ */
+ this.width = width;
+
+ /**
+ * Height
+ * @type {number}
+ */
+ this.height = height;
+};
+
+
+/**
+ * Compares sizes for equality.
+ * @param {goog.math.Size} a A Size.
+ * @param {goog.math.Size} b A Size.
+ * @return {boolean} True iff the sizes have equal widths and equal
+ * heights, or if both are null.
+ */
+goog.math.Size.equals = function(a, b) {
+ if (a == b) {
+ return true;
+ }
+ if (!a || !b) {
+ return false;
+ }
+ return a.width == b.width && a.height == b.height;
+};
+
+
+/**
+ * @return {!goog.math.Size} A new copy of the Size.
+ */
+goog.math.Size.prototype.clone = function() {
+ return new goog.math.Size(this.width, this.height);
+};
+
+
+if (goog.DEBUG) {
+ /**
+ * Returns a nice string representing size.
+ * @return {string} In the form (50 x 73).
+ * @override
+ */
+ goog.math.Size.prototype.toString = function() {
+ return '(' + this.width + ' x ' + this.height + ')';
+ };
+}
+
+
+/**
+ * @return {number} The longer of the two dimensions in the size.
+ */
+goog.math.Size.prototype.getLongest = function() {
+ return Math.max(this.width, this.height);
+};
+
+
+/**
+ * @return {number} The shorter of the two dimensions in the size.
+ */
+goog.math.Size.prototype.getShortest = function() {
+ return Math.min(this.width, this.height);
+};
+
+
+/**
+ * @return {number} The area of the size (width * height).
+ */
+goog.math.Size.prototype.area = function() {
+ return this.width * this.height;
+};
+
+
+/**
+ * @return {number} The perimeter of the size (width + height) * 2.
+ */
+goog.math.Size.prototype.perimeter = function() {
+ return (this.width + this.height) * 2;
+};
+
+
+/**
+ * @return {number} The ratio of the size's width to its height.
+ */
+goog.math.Size.prototype.aspectRatio = function() {
+ return this.width / this.height;
+};
+
+
+/**
+ * @return {boolean} True if the size has zero area, false if both dimensions
+ * are non-zero numbers.
+ */
+goog.math.Size.prototype.isEmpty = function() {
+ return !this.area();
+};
+
+
+/**
+ * Clamps the width and height parameters upward to integer values.
+ * @return {!goog.math.Size} This size with ceil'd components.
+ */
+goog.math.Size.prototype.ceil = function() {
+ this.width = Math.ceil(this.width);
+ this.height = Math.ceil(this.height);
+ return this;
+};
+
+
+/**
+ * @param {!goog.math.Size} target The target size.
+ * @return {boolean} True if this Size is the same size or smaller than the
+ * target size in both dimensions.
+ */
+goog.math.Size.prototype.fitsInside = function(target) {
+ return this.width <= target.width && this.height <= target.height;
+};
+
+
+/**
+ * Clamps the width and height parameters downward to integer values.
+ * @return {!goog.math.Size} This size with floored components.
+ */
+goog.math.Size.prototype.floor = function() {
+ this.width = Math.floor(this.width);
+ this.height = Math.floor(this.height);
+ return this;
+};
+
+
+/**
+ * Rounds the width and height parameters to integer values.
+ * @return {!goog.math.Size} This size with rounded components.
+ */
+goog.math.Size.prototype.round = function() {
+ this.width = Math.round(this.width);
+ this.height = Math.round(this.height);
+ return this;
+};
+
+
+/**
+ * Scales this size by the given scale factors. The width and height are scaled
+ * by {@code sx} and {@code opt_sy} respectively. If {@code opt_sy} is not
+ * given, then {@code sx} is used for both the width and height.
+ * @param {number} sx The scale factor to use for the width.
+ * @param {number=} opt_sy The scale factor to use for the height.
+ * @return {!goog.math.Size} This Size object after scaling.
+ */
+goog.math.Size.prototype.scale = function(sx, opt_sy) {
+ var sy = goog.isNumber(opt_sy) ? opt_sy : sx;
+ this.width *= sx;
+ this.height *= sy;
+ return this;
+};
+
+
+/**
+ * Uniformly scales the size to fit inside the dimensions of a given size. The
+ * original aspect ratio will be preserved.
+ *
+ * This function assumes that both Sizes contain strictly positive dimensions.
+ * @param {!goog.math.Size} target The target size.
+ * @return {!goog.math.Size} This Size object, after optional scaling.
+ */
+goog.math.Size.prototype.scaleToFit = function(target) {
+ var s = this.aspectRatio() > target.aspectRatio() ?
+ target.width / this.width :
+ target.height / this.height;
+
+ return this.scale(s);
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/net/jsloader.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/net/jsloader.js
new file mode 100644
index 0000000..26b025f
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/net/jsloader.js
@@ -0,0 +1,367 @@
+// Copyright 2011 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview A utility to load JavaScript files via DOM script tags.
+ * Refactored from goog.net.Jsonp. Works cross-domain.
+ *
+ */
+
+goog.provide('goog.net.jsloader');
+goog.provide('goog.net.jsloader.Error');
+goog.provide('goog.net.jsloader.ErrorCode');
+goog.provide('goog.net.jsloader.Options');
+
+goog.require('goog.array');
+goog.require('goog.async.Deferred');
+goog.require('goog.debug.Error');
+goog.require('goog.dom');
+goog.require('goog.dom.TagName');
+
+
+/**
+ * The name of the property of goog.global under which the JavaScript
+ * verification object is stored by the loaded script.
+ * @type {string}
+ * @private
+ */
+goog.net.jsloader.GLOBAL_VERIFY_OBJS_ = 'closure_verification';
+
+
+/**
+ * The default length of time, in milliseconds, we are prepared to wait for a
+ * load request to complete.
+ * @type {number}
+ */
+goog.net.jsloader.DEFAULT_TIMEOUT = 5000;
+
+
+/**
+ * Optional parameters for goog.net.jsloader.send.
+ * timeout: The length of time, in milliseconds, we are prepared to wait
+ * for a load request to complete. Default it 5 seconds.
+ * document: The HTML document under which to load the JavaScript. Default is
+ * the current document.
+ * cleanupWhenDone: If true clean up the script tag after script completes to
+ * load. This is important if you just want to read data from the JavaScript
+ * and then throw it away. Default is false.
+ *
+ * @typedef {{
+ * timeout: (number|undefined),
+ * document: (HTMLDocument|undefined),
+ * cleanupWhenDone: (boolean|undefined)
+ * }}
+ */
+goog.net.jsloader.Options;
+
+
+/**
+ * Scripts (URIs) waiting to be loaded.
+ * @type {Array.<string>}
+ * @private
+ */
+goog.net.jsloader.scriptsToLoad_ = [];
+
+
+/**
+ * Loads and evaluates the JavaScript files at the specified URIs, guaranteeing
+ * the order of script loads.
+ *
+ * Because we have to load the scripts in serial (load script 1, exec script 1,
+ * load script 2, exec script 2, and so on), this will be slower than doing
+ * the network fetches in parallel.
+ *
+ * If you need to load a large number of scripts but dependency order doesn't
+ * matter, you should just call goog.net.jsloader.load N times.
+ *
+ * If you need to load a large number of scripts on the same domain,
+ * you may want to use goog.module.ModuleLoader.
+ *
+ * @param {Array.<string>} uris The URIs to load.
+ * @param {goog.net.jsloader.Options=} opt_options Optional parameters. See
+ * goog.net.jsloader.options documentation for details.
+ */
+goog.net.jsloader.loadMany = function(uris, opt_options) {
+ // Loading the scripts in serial introduces asynchronosity into the flow.
+ // Therefore, there are race conditions where client A can kick off the load
+ // sequence for client B, even though client A's scripts haven't all been
+ // loaded yet.
+ //
+ // To work around this issue, all module loads share a queue.
+ if (!uris.length) {
+ return;
+ }
+
+ var isAnotherModuleLoading = goog.net.jsloader.scriptsToLoad_.length;
+ goog.array.extend(goog.net.jsloader.scriptsToLoad_, uris);
+ if (isAnotherModuleLoading) {
+ // jsloader is still loading some other scripts.
+ // In order to prevent the race condition noted above, we just add
+ // these URIs to the end of the scripts' queue and return.
+ return;
+ }
+
+ uris = goog.net.jsloader.scriptsToLoad_;
+ var popAndLoadNextScript = function() {
+ var uri = uris.shift();
+ var deferred = goog.net.jsloader.load(uri, opt_options);
+ if (uris.length) {
+ deferred.addBoth(popAndLoadNextScript);
+ }
+ };
+ popAndLoadNextScript();
+};
+
+
+/**
+ * Loads and evaluates a JavaScript file.
+ * When the script loads, a user callback is called.
+ * It is the client's responsibility to verify that the script ran successfully.
+ *
+ * @param {string} uri The URI of the JavaScript.
+ * @param {goog.net.jsloader.Options=} opt_options Optional parameters. See
+ * goog.net.jsloader.Options documentation for details.
+ * @return {!goog.async.Deferred} The deferred result, that may be used to add
+ * callbacks and/or cancel the transmission.
+ * The error callback will be called with a single goog.net.jsloader.Error
+ * parameter.
+ */
+goog.net.jsloader.load = function(uri, opt_options) {
+ var options = opt_options || {};
+ var doc = options.document || document;
+
+ var script = goog.dom.createElement(goog.dom.TagName.SCRIPT);
+ var request = {script_: script, timeout_: undefined};
+ var deferred = new goog.async.Deferred(goog.net.jsloader.cancel_, request);
+
+ // Set a timeout.
+ var timeout = null;
+ var timeoutDuration = goog.isDefAndNotNull(options.timeout) ?
+ options.timeout : goog.net.jsloader.DEFAULT_TIMEOUT;
+ if (timeoutDuration > 0) {
+ timeout = window.setTimeout(function() {
+ goog.net.jsloader.cleanup_(script, true);
+ deferred.errback(new goog.net.jsloader.Error(
+ goog.net.jsloader.ErrorCode.TIMEOUT,
+ 'Timeout reached for loading script ' + uri));
+ }, timeoutDuration);
+ request.timeout_ = timeout;
+ }
+
+ // Hang the user callback to be called when the script completes to load.
+ // NOTE(user): This callback will be called in IE even upon error. In any
+ // case it is the client's responsibility to verify that the script ran
+ // successfully.
+ script.onload = script.onreadystatechange = function() {
+ if (!script.readyState || script.readyState == 'loaded' ||
+ script.readyState == 'complete') {
+ var removeScriptNode = options.cleanupWhenDone || false;
+ goog.net.jsloader.cleanup_(script, removeScriptNode, timeout);
+ deferred.callback(null);
+ }
+ };
+
+ // Add an error callback.
+ // NOTE(user): Not supported in IE.
+ script.onerror = function() {
+ goog.net.jsloader.cleanup_(script, true, timeout);
+ deferred.errback(new goog.net.jsloader.Error(
+ goog.net.jsloader.ErrorCode.LOAD_ERROR,
+ 'Error while loading script ' + uri));
+ };
+
+ // Add the script element to the document.
+ goog.dom.setProperties(script, {
+ 'type': 'text/javascript',
+ 'charset': 'UTF-8',
+ // NOTE(user): Safari never loads the script if we don't set
+ // the src attribute before appending.
+ 'src': uri
+ });
+ var scriptParent = goog.net.jsloader.getScriptParentElement_(doc);
+ scriptParent.appendChild(script);
+
+ return deferred;
+};
+
+
+/**
+ * Loads a JavaScript file and verifies it was evaluated successfully, using a
+ * verification object.
+ * The verification object is set by the loaded JavaScript at the end of the
+ * script.
+ * We verify this object was set and return its value in the success callback.
+ * If the object is not defined we trigger an error callback.
+ *
+ * @param {string} uri The URI of the JavaScript.
+ * @param {string} verificationObjName The name of the verification object that
+ * the loaded script should set.
+ * @param {goog.net.jsloader.Options} options Optional parameters. See
+ * goog.net.jsloader.Options documentation for details.
+ * @return {!goog.async.Deferred} The deferred result, that may be used to add
+ * callbacks and/or cancel the transmission.
+ * The success callback will be called with a single parameter containing
+ * the value of the verification object.
+ * The error callback will be called with a single goog.net.jsloader.Error
+ * parameter.
+ */
+goog.net.jsloader.loadAndVerify = function(uri, verificationObjName, options) {
+ // Define the global objects variable.
+ if (!goog.global[goog.net.jsloader.GLOBAL_VERIFY_OBJS_]) {
+ goog.global[goog.net.jsloader.GLOBAL_VERIFY_OBJS_] = {};
+ }
+ var verifyObjs = goog.global[goog.net.jsloader.GLOBAL_VERIFY_OBJS_];
+
+ // Verify that the expected object does not exist yet.
+ if (goog.isDef(verifyObjs[verificationObjName])) {
+ // TODO(user): Error or reset variable?
+ return goog.async.Deferred.fail(new goog.net.jsloader.Error(
+ goog.net.jsloader.ErrorCode.VERIFY_OBJECT_ALREADY_EXISTS,
+ 'Verification object ' + verificationObjName + ' already defined.'));
+ }
+
+ // Send request to load the JavaScript.
+ var sendDeferred = goog.net.jsloader.load(uri, options);
+
+ // Create a deferred object wrapping the send result.
+ var deferred = new goog.async.Deferred(
+ goog.bind(sendDeferred.cancel, sendDeferred));
+
+ // Call user back with object that was set by the script.
+ sendDeferred.addCallback(function() {
+ var result = verifyObjs[verificationObjName];
+ if (goog.isDef(result)) {
+ deferred.callback(result);
+ delete verifyObjs[verificationObjName];
+ } else {
+ // Error: script was not loaded properly.
+ deferred.errback(new goog.net.jsloader.Error(
+ goog.net.jsloader.ErrorCode.VERIFY_ERROR,
+ 'Script ' + uri + ' loaded, but verification object ' +
+ verificationObjName + ' was not defined.'));
+ }
+ });
+
+ // Pass error to new deferred object.
+ sendDeferred.addErrback(function(error) {
+ if (goog.isDef(verifyObjs[verificationObjName])) {
+ delete verifyObjs[verificationObjName];
+ }
+ deferred.errback(error);
+ });
+
+ return deferred;
+};
+
+
+/**
+ * Gets the DOM element under which we should add new script elements.
+ * How? Take the first head element, and if not found take doc.documentElement,
+ * which always exists.
+ *
+ * @param {!HTMLDocument} doc The relevant document.
+ * @return {!Element} The script parent element.
+ * @private
+ */
+goog.net.jsloader.getScriptParentElement_ = function(doc) {
+ var headElements = doc.getElementsByTagName(goog.dom.TagName.HEAD);
+ if (!headElements || goog.array.isEmpty(headElements)) {
+ return doc.documentElement;
+ } else {
+ return headElements[0];
+ }
+};
+
+
+/**
+ * Cancels a given request.
+ * @this {{script_: Element, timeout_: number}} The request context.
+ * @private
+ */
+goog.net.jsloader.cancel_ = function() {
+ var request = this;
+ if (request && request.script_) {
+ var scriptNode = request.script_;
+ if (scriptNode && scriptNode.tagName == 'SCRIPT') {
+ goog.net.jsloader.cleanup_(scriptNode, true, request.timeout_);
+ }
+ }
+};
+
+
+/**
+ * Removes the script node and the timeout.
+ *
+ * @param {Node} scriptNode The node to be cleaned up.
+ * @param {boolean} removeScriptNode If true completely remove the script node.
+ * @param {?number=} opt_timeout The timeout handler to cleanup.
+ * @private
+ */
+goog.net.jsloader.cleanup_ = function(scriptNode, removeScriptNode,
+ opt_timeout) {
+ if (goog.isDefAndNotNull(opt_timeout)) {
+ goog.global.clearTimeout(opt_timeout);
+ }
+
+ scriptNode.onload = goog.nullFunction;
+ scriptNode.onerror = goog.nullFunction;
+ scriptNode.onreadystatechange = goog.nullFunction;
+
+ // Do this after a delay (removing the script node of a running script can
+ // confuse older IEs).
+ if (removeScriptNode) {
+ window.setTimeout(function() {
+ goog.dom.removeNode(scriptNode);
+ }, 0);
+ }
+};
+
+
+/**
+ * Possible error codes for jsloader.
+ * @enum {number}
+ */
+goog.net.jsloader.ErrorCode = {
+ LOAD_ERROR: 0,
+ TIMEOUT: 1,
+ VERIFY_ERROR: 2,
+ VERIFY_OBJECT_ALREADY_EXISTS: 3
+};
+
+
+
+/**
+ * A jsloader error.
+ *
+ * @param {goog.net.jsloader.ErrorCode} code The error code.
+ * @param {string=} opt_message Additional message.
+ * @constructor
+ * @extends {goog.debug.Error}
+ * @final
+ */
+goog.net.jsloader.Error = function(code, opt_message) {
+ var msg = 'Jsloader error (code #' + code + ')';
+ if (opt_message) {
+ msg += ': ' + opt_message;
+ }
+ goog.net.jsloader.Error.base(this, 'constructor', msg);
+
+ /**
+ * The code for this error.
+ *
+ * @type {goog.net.jsloader.ErrorCode}
+ */
+ this.code = code;
+};
+goog.inherits(goog.net.jsloader.Error, goog.debug.Error);
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/object/object.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/object/object.js
new file mode 100644
index 0000000..e181ffb
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/object/object.js
@@ -0,0 +1,637 @@
+// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Utilities for manipulating objects/maps/hashes.
+ */
+
+goog.provide('goog.object');
+
+
+/**
+ * Calls a function for each element in an object/map/hash.
+ *
+ * @param {Object.<K,V>} obj The object over which to iterate.
+ * @param {function(this:T,V,?,Object.<K,V>):?} f The function to call
+ * for every element. This function takes 3 arguments (the element, the
+ * index and the object) and the return value is ignored.
+ * @param {T=} opt_obj This is used as the 'this' object within f.
+ * @template T,K,V
+ */
+goog.object.forEach = function(obj, f, opt_obj) {
+ for (var key in obj) {
+ f.call(opt_obj, obj[key], key, obj);
+ }
+};
+
+
+/**
+ * Calls a function for each element in an object/map/hash. If that call returns
+ * true, adds the element to a new object.
+ *
+ * @param {Object.<K,V>} obj The object over which to iterate.
+ * @param {function(this:T,V,?,Object.<K,V>):boolean} f The function to call
+ * for every element. This
+ * function takes 3 arguments (the element, the index and the object)
+ * and should return a boolean. If the return value is true the
+ * element is added to the result object. If it is false the
+ * element is not included.
+ * @param {T=} opt_obj This is used as the 'this' object within f.
+ * @return {!Object.<K,V>} a new object in which only elements that passed the
+ * test are present.
+ * @template T,K,V
+ */
+goog.object.filter = function(obj, f, opt_obj) {
+ var res = {};
+ for (var key in obj) {
+ if (f.call(opt_obj, obj[key], key, obj)) {
+ res[key] = obj[key];
+ }
+ }
+ return res;
+};
+
+
+/**
+ * For every element in an object/map/hash calls a function and inserts the
+ * result into a new object.
+ *
+ * @param {Object.<K,V>} obj The object over which to iterate.
+ * @param {function(this:T,V,?,Object.<K,V>):R} f The function to call
+ * for every element. This function
+ * takes 3 arguments (the element, the index and the object)
+ * and should return something. The result will be inserted
+ * into a new object.
+ * @param {T=} opt_obj This is used as the 'this' object within f.
+ * @return {!Object.<K,R>} a new object with the results from f.
+ * @template T,K,V,R
+ */
+goog.object.map = function(obj, f, opt_obj) {
+ var res = {};
+ for (var key in obj) {
+ res[key] = f.call(opt_obj, obj[key], key, obj);
+ }
+ return res;
+};
+
+
+/**
+ * Calls a function for each element in an object/map/hash. If any
+ * call returns true, returns true (without checking the rest). If
+ * all calls return false, returns false.
+ *
+ * @param {Object.<K,V>} obj The object to check.
+ * @param {function(this:T,V,?,Object.<K,V>):boolean} f The function to
+ * call for every element. This function
+ * takes 3 arguments (the element, the index and the object) and should
+ * return a boolean.
+ * @param {T=} opt_obj This is used as the 'this' object within f.
+ * @return {boolean} true if any element passes the test.
+ * @template T,K,V
+ */
+goog.object.some = function(obj, f, opt_obj) {
+ for (var key in obj) {
+ if (f.call(opt_obj, obj[key], key, obj)) {
+ return true;
+ }
+ }
+ return false;
+};
+
+
+/**
+ * Calls a function for each element in an object/map/hash. If
+ * all calls return true, returns true. If any call returns false, returns
+ * false at this point and does not continue to check the remaining elements.
+ *
+ * @param {Object.<K,V>} obj The object to check.
+ * @param {?function(this:T,V,?,Object.<K,V>):boolean} f The function to
+ * call for every element. This function
+ * takes 3 arguments (the element, the index and the object) and should
+ * return a boolean.
+ * @param {T=} opt_obj This is used as the 'this' object within f.
+ * @return {boolean} false if any element fails the test.
+ * @template T,K,V
+ */
+goog.object.every = function(obj, f, opt_obj) {
+ for (var key in obj) {
+ if (!f.call(opt_obj, obj[key], key, obj)) {
+ return false;
+ }
+ }
+ return true;
+};
+
+
+/**
+ * Returns the number of key-value pairs in the object map.
+ *
+ * @param {Object} obj The object for which to get the number of key-value
+ * pairs.
+ * @return {number} The number of key-value pairs in the object map.
+ */
+goog.object.getCount = function(obj) {
+ // JS1.5 has __count__ but it has been deprecated so it raises a warning...
+ // in other words do not use. Also __count__ only includes the fields on the
+ // actual object and not in the prototype chain.
+ var rv = 0;
+ for (var key in obj) {
+ rv++;
+ }
+ return rv;
+};
+
+
+/**
+ * Returns one key from the object map, if any exists.
+ * For map literals the returned key will be the first one in most of the
+ * browsers (a know exception is Konqueror).
+ *
+ * @param {Object} obj The object to pick a key from.
+ * @return {string|undefined} The key or undefined if the object is empty.
+ */
+goog.object.getAnyKey = function(obj) {
+ for (var key in obj) {
+ return key;
+ }
+};
+
+
+/**
+ * Returns one value from the object map, if any exists.
+ * For map literals the returned value will be the first one in most of the
+ * browsers (a know exception is Konqueror).
+ *
+ * @param {Object.<K,V>} obj The object to pick a value from.
+ * @return {V|undefined} The value or undefined if the object is empty.
+ * @template K,V
+ */
+goog.object.getAnyValue = function(obj) {
+ for (var key in obj) {
+ return obj[key];
+ }
+};
+
+
+/**
+ * Whether the object/hash/map contains the given object as a value.
+ * An alias for goog.object.containsValue(obj, val).
+ *
+ * @param {Object.<K,V>} obj The object in which to look for val.
+ * @param {V} val The object for which to check.
+ * @return {boolean} true if val is present.
+ * @template K,V
+ */
+goog.object.contains = function(obj, val) {
+ return goog.object.containsValue(obj, val);
+};
+
+
+/**
+ * Returns the values of the object/map/hash.
+ *
+ * @param {Object.<K,V>} obj The object from which to get the values.
+ * @return {!Array.<V>} The values in the object/map/hash.
+ * @template K,V
+ */
+goog.object.getValues = function(obj) {
+ var res = [];
+ var i = 0;
+ for (var key in obj) {
+ res[i++] = obj[key];
+ }
+ return res;
+};
+
+
+/**
+ * Returns the keys of the object/map/hash.
+ *
+ * @param {Object} obj The object from which to get the keys.
+ * @return {!Array.<string>} Array of property keys.
+ */
+goog.object.getKeys = function(obj) {
+ var res = [];
+ var i = 0;
+ for (var key in obj) {
+ res[i++] = key;
+ }
+ return res;
+};
+
+
+/**
+ * Get a value from an object multiple levels deep. This is useful for
+ * pulling values from deeply nested objects, such as JSON responses.
+ * Example usage: getValueByKeys(jsonObj, 'foo', 'entries', 3)
+ *
+ * @param {!Object} obj An object to get the value from. Can be array-like.
+ * @param {...(string|number|!Array.<number|string>)} var_args A number of keys
+ * (as strings, or numbers, for array-like objects). Can also be
+ * specified as a single array of keys.
+ * @return {*} The resulting value. If, at any point, the value for a key
+ * is undefined, returns undefined.
+ */
+goog.object.getValueByKeys = function(obj, var_args) {
+ var isArrayLike = goog.isArrayLike(var_args);
+ var keys = isArrayLike ? var_args : arguments;
+
+ // Start with the 2nd parameter for the variable parameters syntax.
+ for (var i = isArrayLike ? 0 : 1; i < keys.length; i++) {
+ obj = obj[keys[i]];
+ if (!goog.isDef(obj)) {
+ break;
+ }
+ }
+
+ return obj;
+};
+
+
+/**
+ * Whether the object/map/hash contains the given key.
+ *
+ * @param {Object} obj The object in which to look for key.
+ * @param {*} key The key for which to check.
+ * @return {boolean} true If the map contains the key.
+ */
+goog.object.containsKey = function(obj, key) {
+ return key in obj;
+};
+
+
+/**
+ * Whether the object/map/hash contains the given value. This is O(n).
+ *
+ * @param {Object.<K,V>} obj The object in which to look for val.
+ * @param {V} val The value for which to check.
+ * @return {boolean} true If the map contains the value.
+ * @template K,V
+ */
+goog.object.containsValue = function(obj, val) {
+ for (var key in obj) {
+ if (obj[key] == val) {
+ return true;
+ }
+ }
+ return false;
+};
+
+
+/**
+ * Searches an object for an element that satisfies the given condition and
+ * returns its key.
+ * @param {Object.<K,V>} obj The object to search in.
+ * @param {function(this:T,V,string,Object.<K,V>):boolean} f The
+ * function to call for every element. Takes 3 arguments (the value,
+ * the key and the object) and should return a boolean.
+ * @param {T=} opt_this An optional "this" context for the function.
+ * @return {string|undefined} The key of an element for which the function
+ * returns true or undefined if no such element is found.
+ * @template T,K,V
+ */
+goog.object.findKey = function(obj, f, opt_this) {
+ for (var key in obj) {
+ if (f.call(opt_this, obj[key], key, obj)) {
+ return key;
+ }
+ }
+ return undefined;
+};
+
+
+/**
+ * Searches an object for an element that satisfies the given condition and
+ * returns its value.
+ * @param {Object.<K,V>} obj The object to search in.
+ * @param {function(this:T,V,string,Object.<K,V>):boolean} f The function
+ * to call for every element. Takes 3 arguments (the value, the key
+ * and the object) and should return a boolean.
+ * @param {T=} opt_this An optional "this" context for the function.
+ * @return {V} The value of an element for which the function returns true or
+ * undefined if no such element is found.
+ * @template T,K,V
+ */
+goog.object.findValue = function(obj, f, opt_this) {
+ var key = goog.object.findKey(obj, f, opt_this);
+ return key && obj[key];
+};
+
+
+/**
+ * Whether the object/map/hash is empty.
+ *
+ * @param {Object} obj The object to test.
+ * @return {boolean} true if obj is empty.
+ */
+goog.object.isEmpty = function(obj) {
+ for (var key in obj) {
+ return false;
+ }
+ return true;
+};
+
+
+/**
+ * Removes all key value pairs from the object/map/hash.
+ *
+ * @param {Object} obj The object to clear.
+ */
+goog.object.clear = function(obj) {
+ for (var i in obj) {
+ delete obj[i];
+ }
+};
+
+
+/**
+ * Removes a key-value pair based on the key.
+ *
+ * @param {Object} obj The object from which to remove the key.
+ * @param {*} key The key to remove.
+ * @return {boolean} Whether an element was removed.
+ */
+goog.object.remove = function(obj, key) {
+ var rv;
+ if ((rv = key in obj)) {
+ delete obj[key];
+ }
+ return rv;
+};
+
+
+/**
+ * Adds a key-value pair to the object. Throws an exception if the key is
+ * already in use. Use set if you want to change an existing pair.
+ *
+ * @param {Object.<K,V>} obj The object to which to add the key-value pair.
+ * @param {string} key The key to add.
+ * @param {V} val The value to add.
+ * @template K,V
+ */
+goog.object.add = function(obj, key, val) {
+ if (key in obj) {
+ throw Error('The object already contains the key "' + key + '"');
+ }
+ goog.object.set(obj, key, val);
+};
+
+
+/**
+ * Returns the value for the given key.
+ *
+ * @param {Object.<K,V>} obj The object from which to get the value.
+ * @param {string} key The key for which to get the value.
+ * @param {R=} opt_val The value to return if no item is found for the given
+ * key (default is undefined).
+ * @return {V|R|undefined} The value for the given key.
+ * @template K,V,R
+ */
+goog.object.get = function(obj, key, opt_val) {
+ if (key in obj) {
+ return obj[key];
+ }
+ return opt_val;
+};
+
+
+/**
+ * Adds a key-value pair to the object/map/hash.
+ *
+ * @param {Object.<K,V>} obj The object to which to add the key-value pair.
+ * @param {string} key The key to add.
+ * @param {V} value The value to add.
+ * @template K,V
+ */
+goog.object.set = function(obj, key, value) {
+ obj[key] = value;
+};
+
+
+/**
+ * Adds a key-value pair to the object/map/hash if it doesn't exist yet.
+ *
+ * @param {Object.<K,V>} obj The object to which to add the key-value pair.
+ * @param {string} key The key to add.
+ * @param {V} value The value to add if the key wasn't present.
+ * @return {V} The value of the entry at the end of the function.
+ * @template K,V
+ */
+goog.object.setIfUndefined = function(obj, key, value) {
+ return key in obj ? obj[key] : (obj[key] = value);
+};
+
+
+/**
+ * Does a flat clone of the object.
+ *
+ * @param {Object.<K,V>} obj Object to clone.
+ * @return {!Object.<K,V>} Clone of the input object.
+ * @template K,V
+ */
+goog.object.clone = function(obj) {
+ // We cannot use the prototype trick because a lot of methods depend on where
+ // the actual key is set.
+
+ var res = {};
+ for (var key in obj) {
+ res[key] = obj[key];
+ }
+ return res;
+ // We could also use goog.mixin but I wanted this to be independent from that.
+};
+
+
+/**
+ * Clones a value. The input may be an Object, Array, or basic type. Objects and
+ * arrays will be cloned recursively.
+ *
+ * WARNINGS:
+ * <code>goog.object.unsafeClone</code> does not detect reference loops. Objects
+ * that refer to themselves will cause infinite recursion.
+ *
+ * <code>goog.object.unsafeClone</code> is unaware of unique identifiers, and
+ * copies UIDs created by <code>getUid</code> into cloned results.
+ *
+ * @param {*} obj The value to clone.
+ * @return {*} A clone of the input value.
+ */
+goog.object.unsafeClone = function(obj) {
+ var type = goog.typeOf(obj);
+ if (type == 'object' || type == 'array') {
+ if (obj.clone) {
+ return obj.clone();
+ }
+ var clone = type == 'array' ? [] : {};
+ for (var key in obj) {
+ clone[key] = goog.object.unsafeClone(obj[key]);
+ }
+ return clone;
+ }
+
+ return obj;
+};
+
+
+/**
+ * Returns a new object in which all the keys and values are interchanged
+ * (keys become values and values become keys). If multiple keys map to the
+ * same value, the chosen transposed value is implementation-dependent.
+ *
+ * @param {Object} obj The object to transpose.
+ * @return {!Object} The transposed object.
+ */
+goog.object.transpose = function(obj) {
+ var transposed = {};
+ for (var key in obj) {
+ transposed[obj[key]] = key;
+ }
+ return transposed;
+};
+
+
+/**
+ * The names of the fields that are defined on Object.prototype.
+ * @type {Array.<string>}
+ * @private
+ */
+goog.object.PROTOTYPE_FIELDS_ = [
+ 'constructor',
+ 'hasOwnProperty',
+ 'isPrototypeOf',
+ 'propertyIsEnumerable',
+ 'toLocaleString',
+ 'toString',
+ 'valueOf'
+];
+
+
+/**
+ * Extends an object with another object.
+ * This operates 'in-place'; it does not create a new Object.
+ *
+ * Example:
+ * var o = {};
+ * goog.object.extend(o, {a: 0, b: 1});
+ * o; // {a: 0, b: 1}
+ * goog.object.extend(o, {b: 2, c: 3});
+ * o; // {a: 0, b: 2, c: 3}
+ *
+ * @param {Object} target The object to modify. Existing properties will be
+ * overwritten if they are also present in one of the objects in
+ * {@code var_args}.
+ * @param {...Object} var_args The objects from which values will be copied.
+ */
+goog.object.extend = function(target, var_args) {
+ var key, source;
+ for (var i = 1; i < arguments.length; i++) {
+ source = arguments[i];
+ for (key in source) {
+ target[key] = source[key];
+ }
+
+ // For IE the for-in-loop does not contain any properties that are not
+ // enumerable on the prototype object (for example isPrototypeOf from
+ // Object.prototype) and it will also not include 'replace' on objects that
+ // extend String and change 'replace' (not that it is common for anyone to
+ // extend anything except Object).
+
+ for (var j = 0; j < goog.object.PROTOTYPE_FIELDS_.length; j++) {
+ key = goog.object.PROTOTYPE_FIELDS_[j];
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
+ target[key] = source[key];
+ }
+ }
+ }
+};
+
+
+/**
+ * Creates a new object built from the key-value pairs provided as arguments.
+ * @param {...*} var_args If only one argument is provided and it is an array
+ * then this is used as the arguments, otherwise even arguments are used as
+ * the property names and odd arguments are used as the property values.
+ * @return {!Object} The new object.
+ * @throws {Error} If there are uneven number of arguments or there is only one
+ * non array argument.
+ */
+goog.object.create = function(var_args) {
+ var argLength = arguments.length;
+ if (argLength == 1 && goog.isArray(arguments[0])) {
+ return goog.object.create.apply(null, arguments[0]);
+ }
+
+ if (argLength % 2) {
+ throw Error('Uneven number of arguments');
+ }
+
+ var rv = {};
+ for (var i = 0; i < argLength; i += 2) {
+ rv[arguments[i]] = arguments[i + 1];
+ }
+ return rv;
+};
+
+
+/**
+ * Creates a new object where the property names come from the arguments but
+ * the value is always set to true
+ * @param {...*} var_args If only one argument is provided and it is an array
+ * then this is used as the arguments, otherwise the arguments are used
+ * as the property names.
+ * @return {!Object} The new object.
+ */
+goog.object.createSet = function(var_args) {
+ var argLength = arguments.length;
+ if (argLength == 1 && goog.isArray(arguments[0])) {
+ return goog.object.createSet.apply(null, arguments[0]);
+ }
+
+ var rv = {};
+ for (var i = 0; i < argLength; i++) {
+ rv[arguments[i]] = true;
+ }
+ return rv;
+};
+
+
+/**
+ * Creates an immutable view of the underlying object, if the browser
+ * supports immutable objects.
+ *
+ * In default mode, writes to this view will fail silently. In strict mode,
+ * they will throw an error.
+ *
+ * @param {!Object.<K,V>} obj An object.
+ * @return {!Object.<K,V>} An immutable view of that object, or the
+ * original object if this browser does not support immutables.
+ * @template K,V
+ */
+goog.object.createImmutableView = function(obj) {
+ var result = obj;
+ if (Object.isFrozen && !Object.isFrozen(obj)) {
+ result = Object.create(obj);
+ Object.freeze(result);
+ }
+ return result;
+};
+
+
+/**
+ * @param {!Object} obj An object.
+ * @return {boolean} Whether this is an immutable view of the object.
+ */
+goog.object.isImmutableView = function(obj) {
+ return !!Object.isFrozen && Object.isFrozen(obj);
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/positioning/positioning.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/positioning/positioning.js
new file mode 100644
index 0000000..0d5c40c
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/positioning/positioning.js
@@ -0,0 +1,557 @@
+// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Common positioning code.
+ *
+ */
+
+goog.provide('goog.positioning');
+goog.provide('goog.positioning.Corner');
+goog.provide('goog.positioning.CornerBit');
+goog.provide('goog.positioning.Overflow');
+goog.provide('goog.positioning.OverflowStatus');
+
+goog.require('goog.asserts');
+goog.require('goog.dom');
+goog.require('goog.dom.TagName');
+goog.require('goog.math.Box');
+goog.require('goog.math.Coordinate');
+goog.require('goog.math.Size');
+goog.require('goog.style');
+goog.require('goog.style.bidi');
+
+
+/**
+ * Enum for representing an element corner for positioning the popup.
+ *
+ * The START constants map to LEFT if element directionality is left
+ * to right and RIGHT if the directionality is right to left.
+ * Likewise END maps to RIGHT or LEFT depending on the directionality.
+ *
+ * @enum {number}
+ */
+goog.positioning.Corner = {
+ TOP_LEFT: 0,
+ TOP_RIGHT: 2,
+ BOTTOM_LEFT: 1,
+ BOTTOM_RIGHT: 3,
+ TOP_START: 4,
+ TOP_END: 6,
+ BOTTOM_START: 5,
+ BOTTOM_END: 7
+};
+
+
+/**
+ * Enum for bits in the {@see goog.positioning.Corner) bitmap.
+ *
+ * @enum {number}
+ */
+goog.positioning.CornerBit = {
+ BOTTOM: 1,
+ RIGHT: 2,
+ FLIP_RTL: 4
+};
+
+
+/**
+ * Enum for representing position handling in cases where the element would be
+ * positioned outside the viewport.
+ *
+ * @enum {number}
+ */
+goog.positioning.Overflow = {
+ /** Ignore overflow */
+ IGNORE: 0,
+
+ /** Try to fit horizontally in the viewport at all costs. */
+ ADJUST_X: 1,
+
+ /** If the element can't fit horizontally, report positioning failure. */
+ FAIL_X: 2,
+
+ /** Try to fit vertically in the viewport at all costs. */
+ ADJUST_Y: 4,
+
+ /** If the element can't fit vertically, report positioning failure. */
+ FAIL_Y: 8,
+
+ /** Resize the element's width to fit in the viewport. */
+ RESIZE_WIDTH: 16,
+
+ /** Resize the element's height to fit in the viewport. */
+ RESIZE_HEIGHT: 32,
+
+ /**
+ * If the anchor goes off-screen in the x-direction, position the movable
+ * element off-screen. Otherwise, try to fit horizontally in the viewport.
+ */
+ ADJUST_X_EXCEPT_OFFSCREEN: 64 | 1,
+
+ /**
+ * If the anchor goes off-screen in the y-direction, position the movable
+ * element off-screen. Otherwise, try to fit vertically in the viewport.
+ */
+ ADJUST_Y_EXCEPT_OFFSCREEN: 128 | 4
+};
+
+
+/**
+ * Enum for representing the outcome of a positioning call.
+ *
+ * @enum {number}
+ */
+goog.positioning.OverflowStatus = {
+ NONE: 0,
+ ADJUSTED_X: 1,
+ ADJUSTED_Y: 2,
+ WIDTH_ADJUSTED: 4,
+ HEIGHT_ADJUSTED: 8,
+ FAILED_LEFT: 16,
+ FAILED_RIGHT: 32,
+ FAILED_TOP: 64,
+ FAILED_BOTTOM: 128,
+ FAILED_OUTSIDE_VIEWPORT: 256
+};
+
+
+/**
+ * Shorthand to check if a status code contains any fail code.
+ * @type {number}
+ */
+goog.positioning.OverflowStatus.FAILED =
+ goog.positioning.OverflowStatus.FAILED_LEFT |
+ goog.positioning.OverflowStatus.FAILED_RIGHT |
+ goog.positioning.OverflowStatus.FAILED_TOP |
+ goog.positioning.OverflowStatus.FAILED_BOTTOM |
+ goog.positioning.OverflowStatus.FAILED_OUTSIDE_VIEWPORT;
+
+
+/**
+ * Shorthand to check if horizontal positioning failed.
+ * @type {number}
+ */
+goog.positioning.OverflowStatus.FAILED_HORIZONTAL =
+ goog.positioning.OverflowStatus.FAILED_LEFT |
+ goog.positioning.OverflowStatus.FAILED_RIGHT;
+
+
+/**
+ * Shorthand to check if vertical positioning failed.
+ * @type {number}
+ */
+goog.positioning.OverflowStatus.FAILED_VERTICAL =
+ goog.positioning.OverflowStatus.FAILED_TOP |
+ goog.positioning.OverflowStatus.FAILED_BOTTOM;
+
+
+/**
+ * Positions a movable element relative to an anchor element. The caller
+ * specifies the corners that should touch. This functions then moves the
+ * movable element accordingly.
+ *
+ * @param {Element} anchorElement The element that is the anchor for where
+ * the movable element should position itself.
+ * @param {goog.positioning.Corner} anchorElementCorner The corner of the
+ * anchorElement for positioning the movable element.
+ * @param {Element} movableElement The element to move.
+ * @param {goog.positioning.Corner} movableElementCorner The corner of the
+ * movableElement that that should be positioned adjacent to the anchor
+ * element.
+ * @param {goog.math.Coordinate=} opt_offset An offset specified in pixels.
+ * After the normal positioning algorithm is applied, the offset is then
+ * applied. Positive coordinates move the popup closer to the center of the
+ * anchor element. Negative coordinates move the popup away from the center
+ * of the anchor element.
+ * @param {goog.math.Box=} opt_margin A margin specified in pixels.
+ * After the normal positioning algorithm is applied and any offset, the
+ * margin is then applied. Positive coordinates move the popup away from the
+ * spot it was positioned towards its center. Negative coordinates move it
+ * towards the spot it was positioned away from its center.
+ * @param {?number=} opt_overflow Overflow handling mode. Defaults to IGNORE if
+ * not specified. Bitmap, {@see goog.positioning.Overflow}.
+ * @param {goog.math.Size=} opt_preferredSize The preferred size of the
+ * movableElement.
+ * @param {goog.math.Box=} opt_viewport Box object describing the dimensions of
+ * the viewport. The viewport is specified relative to offsetParent of
+ * {@code movableElement}. In other words, the viewport can be thought of as
+ * describing a "position: absolute" element contained in the offsetParent.
+ * It defaults to visible area of nearest scrollable ancestor of
+ * {@code movableElement} (see {@code goog.style.getVisibleRectForElement}).
+ * @return {goog.positioning.OverflowStatus} Status bitmap,
+ * {@see goog.positioning.OverflowStatus}.
+ */
+goog.positioning.positionAtAnchor = function(anchorElement,
+ anchorElementCorner,
+ movableElement,
+ movableElementCorner,
+ opt_offset,
+ opt_margin,
+ opt_overflow,
+ opt_preferredSize,
+ opt_viewport) {
+
+ goog.asserts.assert(movableElement);
+ var movableParentTopLeft =
+ goog.positioning.getOffsetParentPageOffset(movableElement);
+
+ // Get the visible part of the anchor element. anchorRect is
+ // relative to anchorElement's page.
+ var anchorRect = goog.positioning.getVisiblePart_(anchorElement);
+
+ // Translate anchorRect to be relative to movableElement's page.
+ goog.style.translateRectForAnotherFrame(
+ anchorRect,
+ goog.dom.getDomHelper(anchorElement),
+ goog.dom.getDomHelper(movableElement));
+
+ // Offset based on which corner of the element we want to position against.
+ var corner = goog.positioning.getEffectiveCorner(anchorElement,
+ anchorElementCorner);
+ // absolutePos is a candidate position relative to the
+ // movableElement's window.
+ var absolutePos = new goog.math.Coordinate(
+ corner & goog.positioning.CornerBit.RIGHT ?
+ anchorRect.left + anchorRect.width : anchorRect.left,
+ corner & goog.positioning.CornerBit.BOTTOM ?
+ anchorRect.top + anchorRect.height : anchorRect.top);
+
+ // Translate absolutePos to be relative to the offsetParent.
+ absolutePos =
+ goog.math.Coordinate.difference(absolutePos, movableParentTopLeft);
+
+ // Apply offset, if specified
+ if (opt_offset) {
+ absolutePos.x += (corner & goog.positioning.CornerBit.RIGHT ? -1 : 1) *
+ opt_offset.x;
+ absolutePos.y += (corner & goog.positioning.CornerBit.BOTTOM ? -1 : 1) *
+ opt_offset.y;
+ }
+
+ // Determine dimension of viewport.
+ var viewport;
+ if (opt_overflow) {
+ if (opt_viewport) {
+ viewport = opt_viewport;
+ } else {
+ viewport = goog.style.getVisibleRectForElement(movableElement);
+ if (viewport) {
+ viewport.top -= movableParentTopLeft.y;
+ viewport.right -= movableParentTopLeft.x;
+ viewport.bottom -= movableParentTopLeft.y;
+ viewport.left -= movableParentTopLeft.x;
+ }
+ }
+ }
+
+ return goog.positioning.positionAtCoordinate(absolutePos,
+ movableElement,
+ movableElementCorner,
+ opt_margin,
+ viewport,
+ opt_overflow,
+ opt_preferredSize);
+};
+
+
+/**
+ * Calculates the page offset of the given element's
+ * offsetParent. This value can be used to translate any x- and
+ * y-offset relative to the page to an offset relative to the
+ * offsetParent, which can then be used directly with as position
+ * coordinate for {@code positionWithCoordinate}.
+ * @param {!Element} movableElement The element to calculate.
+ * @return {!goog.math.Coordinate} The page offset, may be (0, 0).
+ */
+goog.positioning.getOffsetParentPageOffset = function(movableElement) {
+ // Ignore offset for the BODY element unless its position is non-static.
+ // For cases where the offset parent is HTML rather than the BODY (such as in
+ // IE strict mode) there's no need to get the position of the BODY as it
+ // doesn't affect the page offset.
+ var movableParentTopLeft;
+ var parent = movableElement.offsetParent;
+ if (parent) {
+ var isBody = parent.tagName == goog.dom.TagName.HTML ||
+ parent.tagName == goog.dom.TagName.BODY;
+ if (!isBody ||
+ goog.style.getComputedPosition(parent) != 'static') {
+ // Get the top-left corner of the parent, in page coordinates.
+ movableParentTopLeft = goog.style.getPageOffset(parent);
+
+ if (!isBody) {
+ movableParentTopLeft = goog.math.Coordinate.difference(
+ movableParentTopLeft,
+ new goog.math.Coordinate(goog.style.bidi.getScrollLeft(parent),
+ parent.scrollTop));
+ }
+ }
+ }
+
+ return movableParentTopLeft || new goog.math.Coordinate();
+};
+
+
+/**
+ * Returns intersection of the specified element and
+ * goog.style.getVisibleRectForElement for it.
+ *
+ * @param {Element} el The target element.
+ * @return {!goog.math.Rect} Intersection of getVisibleRectForElement
+ * and the current bounding rectangle of the element. If the
+ * intersection is empty, returns the bounding rectangle.
+ * @private
+ */
+goog.positioning.getVisiblePart_ = function(el) {
+ var rect = goog.style.getBounds(el);
+ var visibleBox = goog.style.getVisibleRectForElement(el);
+ if (visibleBox) {
+ rect.intersection(goog.math.Rect.createFromBox(visibleBox));
+ }
+ return rect;
+};
+
+
+/**
+ * Positions the specified corner of the movable element at the
+ * specified coordinate.
+ *
+ * @param {goog.math.Coordinate} absolutePos The coordinate to position the
+ * element at.
+ * @param {Element} movableElement The element to be positioned.
+ * @param {goog.positioning.Corner} movableElementCorner The corner of the
+ * movableElement that that should be positioned.
+ * @param {goog.math.Box=} opt_margin A margin specified in pixels.
+ * After the normal positioning algorithm is applied and any offset, the
+ * margin is then applied. Positive coordinates move the popup away from the
+ * spot it was positioned towards its center. Negative coordinates move it
+ * towards the spot it was positioned away from its center.
+ * @param {goog.math.Box=} opt_viewport Box object describing the dimensions of
+ * the viewport. Required if opt_overflow is specified.
+ * @param {?number=} opt_overflow Overflow handling mode. Defaults to IGNORE if
+ * not specified, {@see goog.positioning.Overflow}.
+ * @param {goog.math.Size=} opt_preferredSize The preferred size of the
+ * movableElement. Defaults to the current size.
+ * @return {goog.positioning.OverflowStatus} Status bitmap.
+ */
+goog.positioning.positionAtCoordinate = function(absolutePos,
+ movableElement,
+ movableElementCorner,
+ opt_margin,
+ opt_viewport,
+ opt_overflow,
+ opt_preferredSize) {
+ absolutePos = absolutePos.clone();
+ var status = goog.positioning.OverflowStatus.NONE;
+
+ // Offset based on attached corner and desired margin.
+ var corner = goog.positioning.getEffectiveCorner(movableElement,
+ movableElementCorner);
+ var elementSize = goog.style.getSize(movableElement);
+ var size = opt_preferredSize ? opt_preferredSize.clone() :
+ elementSize.clone();
+
+ if (opt_margin || corner != goog.positioning.Corner.TOP_LEFT) {
+ if (corner & goog.positioning.CornerBit.RIGHT) {
+ absolutePos.x -= size.width + (opt_margin ? opt_margin.right : 0);
+ } else if (opt_margin) {
+ absolutePos.x += opt_margin.left;
+ }
+ if (corner & goog.positioning.CornerBit.BOTTOM) {
+ absolutePos.y -= size.height + (opt_margin ? opt_margin.bottom : 0);
+ } else if (opt_margin) {
+ absolutePos.y += opt_margin.top;
+ }
+ }
+
+ // Adjust position to fit inside viewport.
+ if (opt_overflow) {
+ status = opt_viewport ?
+ goog.positioning.adjustForViewport_(
+ absolutePos, size, opt_viewport, opt_overflow) :
+ goog.positioning.OverflowStatus.FAILED_OUTSIDE_VIEWPORT;
+ if (status & goog.positioning.OverflowStatus.FAILED) {
+ return status;
+ }
+ }
+
+ goog.style.setPosition(movableElement, absolutePos);
+ if (!goog.math.Size.equals(elementSize, size)) {
+ goog.style.setBorderBoxSize(movableElement, size);
+ }
+
+ return status;
+};
+
+
+/**
+ * Adjusts the position and/or size of an element, identified by its position
+ * and size, to fit inside the viewport. If the position or size of the element
+ * is adjusted the pos or size objects, respectively, are modified.
+ *
+ * @param {goog.math.Coordinate} pos Position of element, updated if the
+ * position is adjusted.
+ * @param {goog.math.Size} size Size of element, updated if the size is
+ * adjusted.
+ * @param {goog.math.Box} viewport Bounding box describing the viewport.
+ * @param {number} overflow Overflow handling mode,
+ * {@see goog.positioning.Overflow}.
+ * @return {goog.positioning.OverflowStatus} Status bitmap,
+ * {@see goog.positioning.OverflowStatus}.
+ * @private
+ */
+goog.positioning.adjustForViewport_ = function(pos, size, viewport, overflow) {
+ var status = goog.positioning.OverflowStatus.NONE;
+
+ var ADJUST_X_EXCEPT_OFFSCREEN =
+ goog.positioning.Overflow.ADJUST_X_EXCEPT_OFFSCREEN;
+ var ADJUST_Y_EXCEPT_OFFSCREEN =
+ goog.positioning.Overflow.ADJUST_Y_EXCEPT_OFFSCREEN;
+ if ((overflow & ADJUST_X_EXCEPT_OFFSCREEN) == ADJUST_X_EXCEPT_OFFSCREEN &&
+ (pos.x < viewport.left || pos.x >= viewport.right)) {
+ overflow &= ~goog.positioning.Overflow.ADJUST_X;
+ }
+ if ((overflow & ADJUST_Y_EXCEPT_OFFSCREEN) == ADJUST_Y_EXCEPT_OFFSCREEN &&
+ (pos.y < viewport.top || pos.y >= viewport.bottom)) {
+ overflow &= ~goog.positioning.Overflow.ADJUST_Y;
+ }
+
+ // Left edge outside viewport, try to move it.
+ if (pos.x < viewport.left && overflow & goog.positioning.Overflow.ADJUST_X) {
+ pos.x = viewport.left;
+ status |= goog.positioning.OverflowStatus.ADJUSTED_X;
+ }
+
+ // Left edge inside and right edge outside viewport, try to resize it.
+ if (pos.x < viewport.left &&
+ pos.x + size.width > viewport.right &&
+ overflow & goog.positioning.Overflow.RESIZE_WIDTH) {
+ size.width = Math.max(
+ size.width - ((pos.x + size.width) - viewport.right), 0);
+ status |= goog.positioning.OverflowStatus.WIDTH_ADJUSTED;
+ }
+
+ // Right edge outside viewport, try to move it.
+ if (pos.x + size.width > viewport.right &&
+ overflow & goog.positioning.Overflow.ADJUST_X) {
+ pos.x = Math.max(viewport.right - size.width, viewport.left);
+ status |= goog.positioning.OverflowStatus.ADJUSTED_X;
+ }
+
+ // Left or right edge still outside viewport, fail if the FAIL_X option was
+ // specified, ignore it otherwise.
+ if (overflow & goog.positioning.Overflow.FAIL_X) {
+ status |= (pos.x < viewport.left ?
+ goog.positioning.OverflowStatus.FAILED_LEFT : 0) |
+ (pos.x + size.width > viewport.right ?
+ goog.positioning.OverflowStatus.FAILED_RIGHT : 0);
+ }
+
+ // Top edge outside viewport, try to move it.
+ if (pos.y < viewport.top && overflow & goog.positioning.Overflow.ADJUST_Y) {
+ pos.y = viewport.top;
+ status |= goog.positioning.OverflowStatus.ADJUSTED_Y;
+ }
+
+ // Bottom edge inside and top edge outside viewport, try to resize it.
+ if (pos.y <= viewport.top &&
+ pos.y + size.height < viewport.bottom &&
+ overflow & goog.positioning.Overflow.RESIZE_HEIGHT) {
+ size.height = Math.max(size.height - (viewport.top - pos.y), 0);
+ pos.y = viewport.top;
+ status |= goog.positioning.OverflowStatus.HEIGHT_ADJUSTED;
+ }
+
+ // Top edge inside and bottom edge outside viewport, try to resize it.
+ if (pos.y >= viewport.top &&
+ pos.y + size.height > viewport.bottom &&
+ overflow & goog.positioning.Overflow.RESIZE_HEIGHT) {
+ size.height = Math.max(
+ size.height - ((pos.y + size.height) - viewport.bottom), 0);
+ status |= goog.positioning.OverflowStatus.HEIGHT_ADJUSTED;
+ }
+
+ // Bottom edge outside viewport, try to move it.
+ if (pos.y + size.height > viewport.bottom &&
+ overflow & goog.positioning.Overflow.ADJUST_Y) {
+ pos.y = Math.max(viewport.bottom - size.height, viewport.top);
+ status |= goog.positioning.OverflowStatus.ADJUSTED_Y;
+ }
+
+ // Top or bottom edge still outside viewport, fail if the FAIL_Y option was
+ // specified, ignore it otherwise.
+ if (overflow & goog.positioning.Overflow.FAIL_Y) {
+ status |= (pos.y < viewport.top ?
+ goog.positioning.OverflowStatus.FAILED_TOP : 0) |
+ (pos.y + size.height > viewport.bottom ?
+ goog.positioning.OverflowStatus.FAILED_BOTTOM : 0);
+ }
+
+ return status;
+};
+
+
+/**
+ * Returns an absolute corner (top/bottom left/right) given an absolute
+ * or relative (top/bottom start/end) corner and the direction of an element.
+ * Absolute corners remain unchanged.
+ * @param {Element} element DOM element to test for RTL direction.
+ * @param {goog.positioning.Corner} corner The popup corner used for
+ * positioning.
+ * @return {goog.positioning.Corner} Effective corner.
+ */
+goog.positioning.getEffectiveCorner = function(element, corner) {
+ return /** @type {goog.positioning.Corner} */ (
+ (corner & goog.positioning.CornerBit.FLIP_RTL &&
+ goog.style.isRightToLeft(element) ?
+ corner ^ goog.positioning.CornerBit.RIGHT :
+ corner
+ ) & ~goog.positioning.CornerBit.FLIP_RTL);
+};
+
+
+/**
+ * Returns the corner opposite the given one horizontally.
+ * @param {goog.positioning.Corner} corner The popup corner used to flip.
+ * @return {goog.positioning.Corner} The opposite corner horizontally.
+ */
+goog.positioning.flipCornerHorizontal = function(corner) {
+ return /** @type {goog.positioning.Corner} */ (corner ^
+ goog.positioning.CornerBit.RIGHT);
+};
+
+
+/**
+ * Returns the corner opposite the given one vertically.
+ * @param {goog.positioning.Corner} corner The popup corner used to flip.
+ * @return {goog.positioning.Corner} The opposite corner vertically.
+ */
+goog.positioning.flipCornerVertical = function(corner) {
+ return /** @type {goog.positioning.Corner} */ (corner ^
+ goog.positioning.CornerBit.BOTTOM);
+};
+
+
+/**
+ * Returns the corner opposite the given one horizontally and vertically.
+ * @param {goog.positioning.Corner} corner The popup corner used to flip.
+ * @return {goog.positioning.Corner} The opposite corner horizontally and
+ * vertically.
+ */
+goog.positioning.flipCorner = function(corner) {
+ return /** @type {goog.positioning.Corner} */ (corner ^
+ goog.positioning.CornerBit.BOTTOM ^
+ goog.positioning.CornerBit.RIGHT);
+};
+
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/promise/promise.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/promise/promise.js
new file mode 100644
index 0000000..1c9ef5c
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/promise/promise.js
@@ -0,0 +1,985 @@
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+goog.provide('goog.Promise');
+
+goog.require('goog.Thenable');
+goog.require('goog.asserts');
+goog.require('goog.async.run');
+goog.require('goog.async.throwException');
+goog.require('goog.debug.Error');
+goog.require('goog.promise.Resolver');
+
+
+
+/**
+ * Promises provide a result that may be resolved asynchronously. A Promise may
+ * be resolved by being fulfilled or rejected with a value, which will be known
+ * as the fulfillment value or the rejection reason. Whether fulfilled or
+ * rejected, the Promise result is immutable once it is set.
+ *
+ * Promises may represent results of any type, including undefined. Rejection
+ * reasons are typically Errors, but may also be of any type. Closure Promises
+ * allow for optional type annotations that enforce that fulfillment values are
+ * of the appropriate types at compile time.
+ *
+ * The result of a Promise is accessible by calling {@code then} and registering
+ * {@code onFulfilled} and {@code onRejected} callbacks. Once the Promise
+ * resolves, the relevant callbacks are invoked with the fulfillment value or
+ * rejection reason as argument. Callbacks are always invoked in the order they
+ * were registered, even when additional {@code then} calls are made from inside
+ * another callback. A callback is always run asynchronously sometime after the
+ * scope containing the registering {@code then} invocation has returned.
+ *
+ * If a Promise is resolved with another Promise, the first Promise will block
+ * until the second is resolved, and then assumes the same result as the second
+ * Promise. This allows Promises to depend on the results of other Promises,
+ * linking together multiple asynchronous operations.
+ *
+ * This implementation is compatible with the Promises/A+ specification and
+ * passes that specification's conformance test suite. A Closure Promise may be
+ * resolved with a Promise instance (or sufficiently compatible Promise-like
+ * object) created by other Promise implementations. From the specification,
+ * Promise-like objects are known as "Thenables".
+ *
+ * @see http://promisesaplus.com/
+ *
+ * @param {function(
+ * this:RESOLVER_CONTEXT,
+ * function((TYPE|IThenable.<TYPE>|Thenable)=),
+ * function(*)): void} resolver
+ * Initialization function that is invoked immediately with {@code resolve}
+ * and {@code reject} functions as arguments. The Promise is resolved or
+ * rejected with the first argument passed to either function.
+ * @param {RESOLVER_CONTEXT=} opt_context An optional context for executing the
+ * resolver function. If unspecified, the resolver function will be executed
+ * in the default scope.
+ * @constructor
+ * @struct
+ * @final
+ * @implements {goog.Thenable.<TYPE>}
+ * @template TYPE,RESOLVER_CONTEXT
+ */
+goog.Promise = function(resolver, opt_context) {
+ /**
+ * The internal state of this Promise. Either PENDING, FULFILLED, REJECTED, or
+ * BLOCKED.
+ * @private {goog.Promise.State_}
+ */
+ this.state_ = goog.Promise.State_.PENDING;
+
+ /**
+ * The resolved result of the Promise. Immutable once set with either a
+ * fulfillment value or rejection reason.
+ * @private {*}
+ */
+ this.result_ = undefined;
+
+ /**
+ * For Promises created by calling {@code then()}, the originating parent.
+ * @private {goog.Promise}
+ */
+ this.parent_ = null;
+
+ /**
+ * The list of {@code onFulfilled} and {@code onRejected} callbacks added to
+ * this Promise by calls to {@code then()}.
+ * @private {Array.<goog.Promise.CallbackEntry_>}
+ */
+ this.callbackEntries_ = null;
+
+ /**
+ * Whether the Promise is in the queue of Promises to execute.
+ * @private {boolean}
+ */
+ this.executing_ = false;
+
+ if (goog.Promise.UNHANDLED_REJECTION_DELAY > 0) {
+ /**
+ * A timeout ID used when the {@code UNHANDLED_REJECTION_DELAY} is greater
+ * than 0 milliseconds. The ID is set when the Promise is rejected, and
+ * cleared only if an {@code onRejected} callback is invoked for the
+ * Promise (or one of its descendants) before the delay is exceeded.
+ *
+ * If the rejection is not handled before the timeout completes, the
+ * rejection reason is passed to the unhandled rejection handler.
+ * @private {number}
+ */
+ this.unhandledRejectionId_ = 0;
+ } else if (goog.Promise.UNHANDLED_REJECTION_DELAY == 0) {
+ /**
+ * When the {@code UNHANDLED_REJECTION_DELAY} is set to 0 milliseconds, a
+ * boolean that is set if the Promise is rejected, and reset to false if an
+ * {@code onRejected} callback is invoked for the Promise (or one of its
+ * descendants). If the rejection is not handled before the next timestep,
+ * the rejection reason is passed to the unhandled rejection handler.
+ * @private {boolean}
+ */
+ this.hadUnhandledRejection_ = false;
+ }
+
+ if (goog.Promise.LONG_STACK_TRACES) {
+ /**
+ * A list of stack trace frames pointing to the locations where this Promise
+ * was created or had callbacks added to it. Saved to add additional context
+ * to stack traces when an exception is thrown.
+ * @private {!Array.<string>}
+ */
+ this.stack_ = [];
+ this.addStackTrace_(new Error('created'));
+
+ /**
+ * Index of the most recently executed stack frame entry.
+ * @private {number}
+ */
+ this.currentStep_ = 0;
+ }
+
+ try {
+ var self = this;
+ resolver.call(
+ opt_context,
+ function(value) {
+ self.resolve_(goog.Promise.State_.FULFILLED, value);
+ },
+ function(reason) {
+ self.resolve_(goog.Promise.State_.REJECTED, reason);
+ });
+ } catch (e) {
+ this.resolve_(goog.Promise.State_.REJECTED, e);
+ }
+};
+
+
+/**
+ * @define {boolean} Whether traces of {@code then} calls should be included in
+ * exceptions thrown
+ */
+goog.define('goog.Promise.LONG_STACK_TRACES', false);
+
+
+/**
+ * @define {number} The delay in milliseconds before a rejected Promise's reason
+ * is passed to the rejection handler. By default, the rejection handler
+ * rethrows the rejection reason so that it appears in the developer console or
+ * {@code window.onerror} handler.
+ *
+ * Rejections are rethrown as quickly as possible by default. A negative value
+ * disables rejection handling entirely.
+ */
+goog.define('goog.Promise.UNHANDLED_REJECTION_DELAY', 0);
+
+
+/**
+ * The possible internal states for a Promise. These states are not directly
+ * observable to external callers.
+ * @enum {number}
+ * @private
+ */
+goog.Promise.State_ = {
+ /** The Promise is waiting for resolution. */
+ PENDING: 0,
+
+ /** The Promise is blocked waiting for the result of another Thenable. */
+ BLOCKED: 1,
+
+ /** The Promise has been resolved with a fulfillment value. */
+ FULFILLED: 2,
+
+ /** The Promise has been resolved with a rejection reason. */
+ REJECTED: 3
+};
+
+
+/**
+ * Typedef for entries in the callback chain. Each call to {@code then},
+ * {@code thenCatch}, or {@code thenAlways} creates an entry containing the
+ * functions that may be invoked once the Promise is resolved.
+ *
+ * @typedef {{
+ * child: goog.Promise,
+ * onFulfilled: function(*),
+ * onRejected: function(*)
+ * }}
+ * @private
+ */
+goog.Promise.CallbackEntry_;
+
+
+/**
+ * @param {(TYPE|goog.Thenable.<TYPE>|Thenable)=} opt_value
+ * @return {!goog.Promise.<TYPE>} A new Promise that is immediately resolved
+ * with the given value.
+ * @template TYPE
+ */
+goog.Promise.resolve = function(opt_value) {
+ return new goog.Promise(function(resolve, reject) {
+ resolve(opt_value);
+ });
+};
+
+
+/**
+ * @param {*=} opt_reason
+ * @return {!goog.Promise} A new Promise that is immediately rejected with the
+ * given reason.
+ */
+goog.Promise.reject = function(opt_reason) {
+ return new goog.Promise(function(resolve, reject) {
+ reject(opt_reason);
+ });
+};
+
+
+/**
+ * @param {!Array.<!(goog.Thenable.<TYPE>|Thenable)>} promises
+ * @return {!goog.Promise.<TYPE>} A Promise that receives the result of the
+ * first Promise (or Promise-like) input to complete.
+ * @template TYPE
+ */
+goog.Promise.race = function(promises) {
+ return new goog.Promise(function(resolve, reject) {
+ if (!promises.length) {
+ resolve(undefined);
+ }
+ for (var i = 0, promise; promise = promises[i]; i++) {
+ promise.then(resolve, reject);
+ }
+ });
+};
+
+
+/**
+ * @param {!Array.<!(goog.Thenable.<TYPE>|Thenable)>} promises
+ * @return {!goog.Promise.<!Array.<TYPE>>} A Promise that receives a list of
+ * every fulfilled value once every input Promise (or Promise-like) is
+ * successfully fulfilled, or is rejected by the first rejection result.
+ * @template TYPE
+ */
+goog.Promise.all = function(promises) {
+ return new goog.Promise(function(resolve, reject) {
+ var toFulfill = promises.length;
+ var values = [];
+
+ if (!toFulfill) {
+ resolve(values);
+ return;
+ }
+
+ var onFulfill = function(index, value) {
+ toFulfill--;
+ values[index] = value;
+ if (toFulfill == 0) {
+ resolve(values);
+ }
+ };
+
+ var onReject = function(reason) {
+ reject(reason);
+ };
+
+ for (var i = 0, promise; promise = promises[i]; i++) {
+ promise.then(goog.partial(onFulfill, i), onReject);
+ }
+ });
+};
+
+
+/**
+ * @param {!Array.<!(goog.Thenable.<TYPE>|Thenable)>} promises
+ * @return {!goog.Promise.<TYPE>} A Promise that receives the value of the first
+ * input to be fulfilled, or is rejected with a list of every rejection
+ * reason if all inputs are rejected.
+ * @template TYPE
+ */
+goog.Promise.firstFulfilled = function(promises) {
+ return new goog.Promise(function(resolve, reject) {
+ var toReject = promises.length;
+ var reasons = [];
+
+ if (!toReject) {
+ resolve(undefined);
+ return;
+ }
+
+ var onFulfill = function(value) {
+ resolve(value);
+ };
+
+ var onReject = function(index, reason) {
+ toReject--;
+ reasons[index] = reason;
+ if (toReject == 0) {
+ reject(reasons);
+ }
+ };
+
+ for (var i = 0, promise; promise = promises[i]; i++) {
+ promise.then(onFulfill, goog.partial(onReject, i));
+ }
+ });
+};
+
+
+/**
+ * @return {!goog.promise.Resolver.<TYPE>} Resolver wrapping the promise and its
+ * resolve / reject functions. Resolving or rejecting the resolver
+ * resolves or rejects the promise.
+ * @template TYPE
+ */
+goog.Promise.withResolver = function() {
+ var resolve, reject;
+ var promise = new goog.Promise(function(rs, rj) {
+ resolve = rs;
+ reject = rj;
+ });
+ return new goog.Promise.Resolver_(promise, resolve, reject);
+};
+
+
+/**
+ * Adds callbacks that will operate on the result of the Promise, returning a
+ * new child Promise.
+ *
+ * If the Promise is fulfilled, the {@code onFulfilled} callback will be invoked
+ * with the fulfillment value as argument, and the child Promise will be
+ * fulfilled with the return value of the callback. If the callback throws an
+ * exception, the child Promise will be rejected with the thrown value instead.
+ *
+ * If the Promise is rejected, the {@code onRejected} callback will be invoked
+ * with the rejection reason as argument, and the child Promise will be resolved
+ * with the return value or rejected with the thrown value of the callback.
+ *
+ * @override
+ */
+goog.Promise.prototype.then = function(
+ opt_onFulfilled, opt_onRejected, opt_context) {
+
+ if (opt_onFulfilled != null) {
+ goog.asserts.assertFunction(opt_onFulfilled,
+ 'opt_onFulfilled should be a function.');
+ }
+ if (opt_onRejected != null) {
+ goog.asserts.assertFunction(opt_onRejected,
+ 'opt_onRejected should be a function. Did you pass opt_context ' +
+ 'as the second argument instead of the third?');
+ }
+
+ if (goog.Promise.LONG_STACK_TRACES) {
+ this.addStackTrace_(new Error('then'));
+ }
+
+ return this.addChildPromise_(
+ goog.isFunction(opt_onFulfilled) ? opt_onFulfilled : null,
+ goog.isFunction(opt_onRejected) ? opt_onRejected : null,
+ opt_context);
+};
+goog.Thenable.addImplementation(goog.Promise);
+
+
+/**
+ * Adds a callback that will be invoked whether the Promise is fulfilled or
+ * rejected. The callback receives no argument, and no new child Promise is
+ * created. This is useful for ensuring that cleanup takes place after certain
+ * asynchronous operations. Callbacks added with {@code thenAlways} will be
+ * executed in the same order with other calls to {@code then},
+ * {@code thenAlways}, or {@code thenCatch}.
+ *
+ * Since it does not produce a new child Promise, cancellation propagation is
+ * not prevented by adding callbacks with {@code thenAlways}. A Promise that has
+ * a cleanup handler added with {@code thenAlways} will be canceled if all of
+ * its children created by {@code then} (or {@code thenCatch}) are canceled.
+ *
+ * @param {function(this:THIS): void} onResolved A function that will be invoked
+ * when the Promise is resolved.
+ * @param {THIS=} opt_context An optional context object that will be the
+ * execution context for the callbacks. By default, functions are executed
+ * in the global scope.
+ * @return {!goog.Promise.<TYPE>} This Promise, for chaining additional calls.
+ * @template THIS
+ */
+goog.Promise.prototype.thenAlways = function(onResolved, opt_context) {
+ if (goog.Promise.LONG_STACK_TRACES) {
+ this.addStackTrace_(new Error('thenAlways'));
+ }
+
+ var callback = function() {
+ try {
+ // Ensure that no arguments are passed to onResolved.
+ onResolved.call(opt_context);
+ } catch (err) {
+ goog.Promise.handleRejection_.call(null, err);
+ }
+ };
+
+ this.addCallbackEntry_({
+ child: null,
+ onRejected: callback,
+ onFulfilled: callback
+ });
+ return this;
+};
+
+
+/**
+ * Adds a callback that will be invoked only if the Promise is rejected. This
+ * is equivalent to {@code then(null, onRejected)}.
+ *
+ * @param {!function(this:THIS, *): *} onRejected A function that will be
+ * invoked with the rejection reason if the Promise is rejected.
+ * @param {THIS=} opt_context An optional context object that will be the
+ * execution context for the callbacks. By default, functions are executed
+ * in the global scope.
+ * @return {!goog.Promise} A new Promise that will receive the result of the
+ * callback.
+ * @template THIS
+ */
+goog.Promise.prototype.thenCatch = function(onRejected, opt_context) {
+ if (goog.Promise.LONG_STACK_TRACES) {
+ this.addStackTrace_(new Error('thenCatch'));
+ }
+ return this.addChildPromise_(null, onRejected, opt_context);
+};
+
+
+/**
+ * Cancels the Promise if it is still pending by rejecting it with a cancel
+ * Error. No action is performed if the Promise is already resolved.
+ *
+ * All child Promises of the canceled Promise will be rejected with the same
+ * cancel error, as with normal Promise rejection. If the Promise to be canceled
+ * is the only child of a pending Promise, the parent Promise will also be
+ * canceled. Cancellation may propagate upward through multiple generations.
+ *
+ * @param {string=} opt_message An optional debugging message for describing the
+ * cancellation reason.
+ */
+goog.Promise.prototype.cancel = function(opt_message) {
+ if (this.state_ == goog.Promise.State_.PENDING) {
+ goog.async.run(function() {
+ var err = new goog.Promise.CancellationError(opt_message);
+ this.cancelInternal_(err);
+ }, this);
+ }
+};
+
+
+/**
+ * Cancels this Promise with the given error.
+ *
+ * @param {!Error} err The cancellation error.
+ * @private
+ */
+goog.Promise.prototype.cancelInternal_ = function(err) {
+ if (this.state_ == goog.Promise.State_.PENDING) {
+ if (this.parent_) {
+ // Cancel the Promise and remove it from the parent's child list.
+ this.parent_.cancelChild_(this, err);
+ } else {
+ this.resolve_(goog.Promise.State_.REJECTED, err);
+ }
+ }
+};
+
+
+/**
+ * Cancels a child Promise from the list of callback entries. If the Promise has
+ * not already been resolved, reject it with a cancel error. If there are no
+ * other children in the list of callback entries, propagate the cancellation
+ * by canceling this Promise as well.
+ *
+ * @param {!goog.Promise} childPromise The Promise to cancel.
+ * @param {!Error} err The cancel error to use for rejecting the Promise.
+ * @private
+ */
+goog.Promise.prototype.cancelChild_ = function(childPromise, err) {
+ if (!this.callbackEntries_) {
+ return;
+ }
+ var childCount = 0;
+ var childIndex = -1;
+
+ // Find the callback entry for the childPromise, and count whether there are
+ // additional child Promises.
+ for (var i = 0, entry; entry = this.callbackEntries_[i]; i++) {
+ var child = entry.child;
+ if (child) {
+ childCount++;
+ if (child == childPromise) {
+ childIndex = i;
+ }
+ if (childIndex >= 0 && childCount > 1) {
+ break;
+ }
+ }
+ }
+
+ // If the child Promise was the only child, cancel this Promise as well.
+ // Otherwise, reject only the child Promise with the cancel error.
+ if (childIndex >= 0) {
+ if (this.state_ == goog.Promise.State_.PENDING && childCount == 1) {
+ this.cancelInternal_(err);
+ } else {
+ var callbackEntry = this.callbackEntries_.splice(childIndex, 1)[0];
+ this.executeCallback_(
+ callbackEntry, goog.Promise.State_.REJECTED, err);
+ }
+ }
+};
+
+
+/**
+ * Adds a callback entry to the current Promise, and schedules callback
+ * execution if the Promise has already been resolved.
+ *
+ * @param {goog.Promise.CallbackEntry_} callbackEntry Record containing
+ * {@code onFulfilled} and {@code onRejected} callbacks to execute after
+ * the Promise is resolved.
+ * @private
+ */
+goog.Promise.prototype.addCallbackEntry_ = function(callbackEntry) {
+ if ((!this.callbackEntries_ || !this.callbackEntries_.length) &&
+ (this.state_ == goog.Promise.State_.FULFILLED ||
+ this.state_ == goog.Promise.State_.REJECTED)) {
+ this.scheduleCallbacks_();
+ }
+ if (!this.callbackEntries_) {
+ this.callbackEntries_ = [];
+ }
+ this.callbackEntries_.push(callbackEntry);
+};
+
+
+/**
+ * Creates a child Promise and adds it to the callback entry list. The result of
+ * the child Promise is determined by the state of the parent Promise and the
+ * result of the {@code onFulfilled} or {@code onRejected} callbacks as
+ * specified in the Promise resolution procedure.
+ *
+ * @see http://promisesaplus.com/#the__method
+ *
+ * @param {?function(this:THIS, TYPE):
+ * (RESULT|goog.Promise.<RESULT>|Thenable)} onFulfilled A callback that
+ * will be invoked if the Promise is fullfilled, or null.
+ * @param {?function(this:THIS, *): *} onRejected A callback that will be
+ * invoked if the Promise is rejected, or null.
+ * @param {THIS=} opt_context An optional execution context for the callbacks.
+ * in the default calling context.
+ * @return {!goog.Promise} The child Promise.
+ * @template RESULT,THIS
+ * @private
+ */
+goog.Promise.prototype.addChildPromise_ = function(
+ onFulfilled, onRejected, opt_context) {
+
+ var callbackEntry = {
+ child: null,
+ onFulfilled: null,
+ onRejected: null
+ };
+
+ callbackEntry.child = new goog.Promise(function(resolve, reject) {
+ // Invoke onFulfilled, or resolve with the parent's value if absent.
+ callbackEntry.onFulfilled = onFulfilled ? function(value) {
+ try {
+ var result = onFulfilled.call(opt_context, value);
+ resolve(result);
+ } catch (err) {
+ reject(err);
+ }
+ } : resolve;
+
+ // Invoke onRejected, or reject with the parent's reason if absent.
+ callbackEntry.onRejected = onRejected ? function(reason) {
+ try {
+ var result = onRejected.call(opt_context, reason);
+ if (!goog.isDef(result) &&
+ reason instanceof goog.Promise.CancellationError) {
+ // Propagate cancellation to children if no other result is returned.
+ reject(reason);
+ } else {
+ resolve(result);
+ }
+ } catch (err) {
+ reject(err);
+ }
+ } : reject;
+ });
+
+ callbackEntry.child.parent_ = this;
+ this.addCallbackEntry_(
+ /** @type {goog.Promise.CallbackEntry_} */ (callbackEntry));
+ return callbackEntry.child;
+};
+
+
+/**
+ * Unblocks the Promise and fulfills it with the given value.
+ *
+ * @param {TYPE} value
+ * @private
+ */
+goog.Promise.prototype.unblockAndFulfill_ = function(value) {
+ goog.asserts.assert(this.state_ == goog.Promise.State_.BLOCKED);
+ this.state_ = goog.Promise.State_.PENDING;
+ this.resolve_(goog.Promise.State_.FULFILLED, value);
+};
+
+
+/**
+ * Unblocks the Promise and rejects it with the given rejection reason.
+ *
+ * @param {*} reason
+ * @private
+ */
+goog.Promise.prototype.unblockAndReject_ = function(reason) {
+ goog.asserts.assert(this.state_ == goog.Promise.State_.BLOCKED);
+ this.state_ = goog.Promise.State_.PENDING;
+ this.resolve_(goog.Promise.State_.REJECTED, reason);
+};
+
+
+/**
+ * Attempts to resolve a Promise with a given resolution state and value. This
+ * is a no-op if the given Promise has already been resolved.
+ *
+ * If the given result is a Thenable (such as another Promise), the Promise will
+ * be resolved with the same state and result as the Thenable once it is itself
+ * resolved.
+ *
+ * If the given result is not a Thenable, the Promise will be fulfilled or
+ * rejected with that result based on the given state.
+ *
+ * @see http://promisesaplus.com/#the_promise_resolution_procedure
+ *
+ * @param {goog.Promise.State_} state
+ * @param {*} x The result to apply to the Promise.
+ * @private
+ */
+goog.Promise.prototype.resolve_ = function(state, x) {
+ if (this.state_ != goog.Promise.State_.PENDING) {
+ return;
+ }
+
+ if (this == x) {
+ state = goog.Promise.State_.REJECTED;
+ x = new TypeError('Promise cannot resolve to itself');
+
+ } else if (goog.Thenable.isImplementedBy(x)) {
+ x = /** @type {!goog.Thenable} */ (x);
+ this.state_ = goog.Promise.State_.BLOCKED;
+ x.then(this.unblockAndFulfill_, this.unblockAndReject_, this);
+ return;
+
+ } else if (goog.isObject(x)) {
+ try {
+ var then = x['then'];
+ if (goog.isFunction(then)) {
+ this.tryThen_(x, then);
+ return;
+ }
+ } catch (e) {
+ state = goog.Promise.State_.REJECTED;
+ x = e;
+ }
+ }
+
+ this.result_ = x;
+ this.state_ = state;
+ this.scheduleCallbacks_();
+
+ if (state == goog.Promise.State_.REJECTED &&
+ !(x instanceof goog.Promise.CancellationError)) {
+ goog.Promise.addUnhandledRejection_(this, x);
+ }
+};
+
+
+/**
+ * Attempts to call the {@code then} method on an object in the hopes that it is
+ * a Promise-compatible instance. This allows interoperation between different
+ * Promise implementations, however a non-compliant object may cause a Promise
+ * to hang indefinitely. If the {@code then} method throws an exception, the
+ * dependent Promise will be rejected with the thrown value.
+ *
+ * @see http://promisesaplus.com/#point-70
+ *
+ * @param {Thenable} thenable An object with a {@code then} method that may be
+ * compatible with the Promise/A+ specification.
+ * @param {!Function} then The {@code then} method of the Thenable object.
+ * @private
+ */
+goog.Promise.prototype.tryThen_ = function(thenable, then) {
+ this.state_ = goog.Promise.State_.BLOCKED;
+ var promise = this;
+ var called = false;
+
+ var resolve = function(value) {
+ if (!called) {
+ called = true;
+ promise.unblockAndFulfill_(value);
+ }
+ };
+
+ var reject = function(reason) {
+ if (!called) {
+ called = true;
+ promise.unblockAndReject_(reason);
+ }
+ };
+
+ try {
+ then.call(thenable, resolve, reject);
+ } catch (e) {
+ reject(e);
+ }
+};
+
+
+/**
+ * Executes the pending callbacks of a resolved Promise after a timeout.
+ *
+ * Section 2.2.4 of the Promises/A+ specification requires that Promise
+ * callbacks must only be invoked from a call stack that only contains Promise
+ * implementation code, which we accomplish by invoking callback execution after
+ * a timeout. If {@code startExecution_} is called multiple times for the same
+ * Promise, the callback chain will be evaluated only once. Additional callbacks
+ * may be added during the evaluation phase, and will be executed in the same
+ * event loop.
+ *
+ * All Promises added to the waiting list during the same browser event loop
+ * will be executed in one batch to avoid using a separate timeout per Promise.
+ *
+ * @private
+ */
+goog.Promise.prototype.scheduleCallbacks_ = function() {
+ if (!this.executing_) {
+ this.executing_ = true;
+ goog.async.run(this.executeCallbacks_, this);
+ }
+};
+
+
+/**
+ * Executes all pending callbacks for this Promise.
+ *
+ * @private
+ */
+goog.Promise.prototype.executeCallbacks_ = function() {
+ while (this.callbackEntries_ && this.callbackEntries_.length) {
+ var entries = this.callbackEntries_;
+ this.callbackEntries_ = [];
+
+ for (var i = 0; i < entries.length; i++) {
+ if (goog.Promise.LONG_STACK_TRACES) {
+ this.currentStep_++;
+ }
+ this.executeCallback_(entries[i], this.state_, this.result_);
+ }
+ }
+ this.executing_ = false;
+};
+
+
+/**
+ * Executes a pending callback for this Promise. Invokes an {@code onFulfilled}
+ * or {@code onRejected} callback based on the resolved state of the Promise.
+ *
+ * @param {!goog.Promise.CallbackEntry_} callbackEntry An entry containing the
+ * onFulfilled and/or onRejected callbacks for this step.
+ * @param {goog.Promise.State_} state The resolution status of the Promise,
+ * either FULFILLED or REJECTED.
+ * @param {*} result The resolved result of the Promise.
+ * @private
+ */
+goog.Promise.prototype.executeCallback_ = function(
+ callbackEntry, state, result) {
+ if (state == goog.Promise.State_.FULFILLED) {
+ callbackEntry.onFulfilled(result);
+ } else {
+ this.removeUnhandledRejection_();
+ callbackEntry.onRejected(result);
+ }
+};
+
+
+/**
+ * Records a stack trace entry for functions that call {@code then} or the
+ * Promise constructor. May be disabled by unsetting {@code LONG_STACK_TRACES}.
+ *
+ * @param {!Error} err An Error object created by the calling function for
+ * providing a stack trace.
+ * @private
+ */
+goog.Promise.prototype.addStackTrace_ = function(err) {
+ if (goog.Promise.LONG_STACK_TRACES && goog.isString(err.stack)) {
+ // Extract the third line of the stack trace, which is the entry for the
+ // user function that called into Promise code.
+ var trace = err.stack.split('\n', 4)[3];
+ var message = err.message;
+
+ // Pad the message to align the traces.
+ message += Array(11 - message.length).join(' ');
+ this.stack_.push(message + trace);
+ }
+};
+
+
+/**
+ * Adds extra stack trace information to an exception for the list of
+ * asynchronous {@code then} calls that have been run for this Promise. Stack
+ * trace information is recorded in {@see #addStackTrace_}, and appended to
+ * rethrown errors when {@code LONG_STACK_TRACES} is enabled.
+ *
+ * @param {*} err An unhandled exception captured during callback execution.
+ * @private
+ */
+goog.Promise.prototype.appendLongStack_ = function(err) {
+ if (goog.Promise.LONG_STACK_TRACES &&
+ err && goog.isString(err.stack) && this.stack_.length) {
+ var longTrace = ['Promise trace:'];
+
+ for (var promise = this; promise; promise = promise.parent_) {
+ for (var i = this.currentStep_; i >= 0; i--) {
+ longTrace.push(promise.stack_[i]);
+ }
+ longTrace.push('Value: ' +
+ '[' + (promise.state_ == goog.Promise.State_.REJECTED ?
+ 'REJECTED' : 'FULFILLED') + '] ' +
+ '<' + String(promise.result_) + '>');
+ }
+ err.stack += '\n\n' + longTrace.join('\n');
+ }
+};
+
+
+/**
+ * Marks this rejected Promise as having being handled. Also marks any parent
+ * Promises in the rejected state as handled. The rejection handler will no
+ * longer be invoked for this Promise (if it has not been called already).
+ *
+ * @private
+ */
+goog.Promise.prototype.removeUnhandledRejection_ = function() {
+ if (goog.Promise.UNHANDLED_REJECTION_DELAY > 0) {
+ for (var p = this; p && p.unhandledRejectionId_; p = p.parent_) {
+ goog.global.clearTimeout(p.unhandledRejectionId_);
+ p.unhandledRejectionId_ = 0;
+ }
+ } else if (goog.Promise.UNHANDLED_REJECTION_DELAY == 0) {
+ for (var p = this; p && p.hadUnhandledRejection_; p = p.parent_) {
+ p.hadUnhandledRejection_ = false;
+ }
+ }
+};
+
+
+/**
+ * Marks this rejected Promise as unhandled. If no {@code onRejected} callback
+ * is called for this Promise before the {@code UNHANDLED_REJECTION_DELAY}
+ * expires, the reason will be passed to the unhandled rejection handler. The
+ * handler typically rethrows the rejection reason so that it becomes visible in
+ * the developer console.
+ *
+ * @param {!goog.Promise} promise The rejected Promise.
+ * @param {*} reason The Promise rejection reason.
+ * @private
+ */
+goog.Promise.addUnhandledRejection_ = function(promise, reason) {
+ if (goog.Promise.UNHANDLED_REJECTION_DELAY > 0) {
+ promise.unhandledRejectionId_ = goog.global.setTimeout(function() {
+ promise.appendLongStack_(reason);
+ goog.Promise.handleRejection_.call(null, reason);
+ }, goog.Promise.UNHANDLED_REJECTION_DELAY);
+
+ } else if (goog.Promise.UNHANDLED_REJECTION_DELAY == 0) {
+ promise.hadUnhandledRejection_ = true;
+ goog.async.run(function() {
+ if (promise.hadUnhandledRejection_) {
+ promise.appendLongStack_(reason);
+ goog.Promise.handleRejection_.call(null, reason);
+ }
+ });
+ }
+};
+
+
+/**
+ * A method that is invoked with the rejection reasons for Promises that are
+ * rejected but have no {@code onRejected} callbacks registered yet.
+ * @type {function(*)}
+ * @private
+ */
+goog.Promise.handleRejection_ = goog.async.throwException;
+
+
+/**
+ * Sets a handler that will be called with reasons from unhandled rejected
+ * Promises. If the rejected Promise (or one of its descendants) has an
+ * {@code onRejected} callback registered, the rejection will be considered
+ * handled, and the rejection handler will not be called.
+ *
+ * By default, unhandled rejections are rethrown so that the error may be
+ * captured by the developer console or a {@code window.onerror} handler.
+ *
+ * @param {function(*)} handler A function that will be called with reasons from
+ * rejected Promises. Defaults to {@code goog.async.throwException}.
+ */
+goog.Promise.setUnhandledRejectionHandler = function(handler) {
+ goog.Promise.handleRejection_ = handler;
+};
+
+
+
+/**
+ * Error used as a rejection reason for canceled Promises.
+ *
+ * @param {string=} opt_message
+ * @constructor
+ * @extends {goog.debug.Error}
+ * @final
+ */
+goog.Promise.CancellationError = function(opt_message) {
+ goog.Promise.CancellationError.base(this, 'constructor', opt_message);
+};
+goog.inherits(goog.Promise.CancellationError, goog.debug.Error);
+
+
+/** @override */
+goog.Promise.CancellationError.prototype.name = 'cancel';
+
+
+
+/**
+ * Internal implementation of the resolver interface.
+ *
+ * @param {!goog.Promise.<TYPE>} promise
+ * @param {function((TYPE|goog.Promise.<TYPE>|Thenable)=)} resolve
+ * @param {function(*): void} reject
+ * @implements {goog.promise.Resolver.<TYPE>}
+ * @final @struct
+ * @constructor
+ * @private
+ * @template TYPE
+ */
+goog.Promise.Resolver_ = function(promise, resolve, reject) {
+ /** @const */
+ this.promise = promise;
+
+ /** @const */
+ this.resolve = resolve;
+
+ /** @const */
+ this.reject = reject;
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/promise/resolver.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/promise/resolver.js
new file mode 100644
index 0000000..e27c300
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/promise/resolver.js
@@ -0,0 +1,48 @@
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+goog.provide('goog.promise.Resolver');
+
+
+
+/**
+ * Resolver interface for promises. The resolver is a convenience interface that
+ * bundles the promise and its associated resolve and reject functions together,
+ * for cases where the resolver needs to be persisted internally.
+ *
+ * @interface
+ * @template TYPE
+ */
+goog.promise.Resolver = function() {};
+
+
+/**
+ * The promise that created this resolver.
+ * @const {!goog.Promise.<TYPE>}
+ */
+goog.promise.Resolver.prototype.promise;
+
+
+/**
+ * Resolves this resolver with the specified value.
+ * @const {function((TYPE|goog.Promise.<TYPE>|Thenable)=)}
+ */
+goog.promise.Resolver.prototype.resolve;
+
+
+/**
+ * Rejects this resolver with the specified reason.
+ * @const {function(*): void}
+ */
+goog.promise.Resolver.prototype.reject;
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/promise/thenable.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/promise/thenable.js
new file mode 100644
index 0000000..dde8ec8
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/promise/thenable.js
@@ -0,0 +1,111 @@
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+goog.provide('goog.Thenable');
+
+
+
+/**
+ * Provides a more strict interface for Thenables in terms of
+ * http://promisesaplus.com for interop with {@see goog.Promise}.
+ *
+ * @interface
+ * @extends {IThenable.<TYPE>}
+ * @template TYPE
+ */
+goog.Thenable = function() {};
+
+
+/**
+ * Adds callbacks that will operate on the result of the Thenable, returning a
+ * new child Promise.
+ *
+ * If the Thenable is fulfilled, the {@code onFulfilled} callback will be
+ * invoked with the fulfillment value as argument, and the child Promise will
+ * be fulfilled with the return value of the callback. If the callback throws
+ * an exception, the child Promise will be rejected with the thrown value
+ * instead.
+ *
+ * If the Thenable is rejected, the {@code onRejected} callback will be invoked
+ * with the rejection reason as argument, and the child Promise will be rejected
+ * with the return value of the callback or thrown value.
+ *
+ * @param {?(function(this:THIS, TYPE):
+ * (RESULT|IThenable.<RESULT>|Thenable))=} opt_onFulfilled A
+ * function that will be invoked with the fulfillment value if the Promise
+ * is fullfilled.
+ * @param {?(function(this:THIS, *): *)=} opt_onRejected A function that will
+ * be invoked with the rejection reason if the Promise is rejected.
+ * @param {THIS=} opt_context An optional context object that will be the
+ * execution context for the callbacks. By default, functions are executed
+ * with the default this.
+ * @return {!goog.Promise.<RESULT>} A new Promise that will receive the result
+ * of the fulfillment or rejection callback.
+ * @template RESULT,THIS
+ */
+goog.Thenable.prototype.then = function(opt_onFulfilled, opt_onRejected,
+ opt_context) {};
+
+
+/**
+ * An expando property to indicate that an object implements
+ * {@code goog.Thenable}.
+ *
+ * {@see addImplementation}.
+ *
+ * @const
+ */
+goog.Thenable.IMPLEMENTED_BY_PROP = '$goog_Thenable';
+
+
+/**
+ * Marks a given class (constructor) as an implementation of Thenable, so
+ * that we can query that fact at runtime. The class must have already
+ * implemented the interface.
+ * Exports a 'then' method on the constructor prototype, so that the objects
+ * also implement the extern {@see goog.Thenable} interface for interop with
+ * other Promise implementations.
+ * @param {function(new:goog.Thenable,...[?])} ctor The class constructor. The
+ * corresponding class must have already implemented the interface.
+ */
+goog.Thenable.addImplementation = function(ctor) {
+ goog.exportProperty(ctor.prototype, 'then', ctor.prototype.then);
+ if (COMPILED) {
+ ctor.prototype[goog.Thenable.IMPLEMENTED_BY_PROP] = true;
+ } else {
+ // Avoids dictionary access in uncompiled mode.
+ ctor.prototype.$goog_Thenable = true;
+ }
+};
+
+
+/**
+ * @param {*} object
+ * @return {boolean} Whether a given instance implements {@code goog.Thenable}.
+ * The class/superclass of the instance must call {@code addImplementation}.
+ */
+goog.Thenable.isImplementedBy = function(object) {
+ if (!object) {
+ return false;
+ }
+ try {
+ if (COMPILED) {
+ return !!object[goog.Thenable.IMPLEMENTED_BY_PROP];
+ }
+ return !!object.$goog_Thenable;
+ } catch (e) {
+ // Property access seems to be forbidden.
+ return false;
+ }
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/reflect/reflect.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/reflect/reflect.js
new file mode 100644
index 0000000..df09e15
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/reflect/reflect.js
@@ -0,0 +1,77 @@
+// Copyright 2009 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Useful compiler idioms.
+ *
+ */
+
+goog.provide('goog.reflect');
+
+
+/**
+ * Syntax for object literal casts.
+ * @see http://go/jscompiler-renaming
+ * @see http://code.google.com/p/closure-compiler/wiki/
+ * ExperimentalTypeBasedPropertyRenaming
+ *
+ * Use this if you have an object literal whose keys need to have the same names
+ * as the properties of some class even after they are renamed by the compiler.
+ *
+ * @param {!Function} type Type to cast to.
+ * @param {Object} object Object literal to cast.
+ * @return {Object} The object literal.
+ */
+goog.reflect.object = function(type, object) {
+ return object;
+};
+
+
+/**
+ * To assert to the compiler that an operation is needed when it would
+ * otherwise be stripped. For example:
+ * <code>
+ * // Force a layout
+ * goog.reflect.sinkValue(dialog.offsetHeight);
+ * </code>
+ * @type {!Function}
+ */
+goog.reflect.sinkValue = function(x) {
+ goog.reflect.sinkValue[' '](x);
+ return x;
+};
+
+
+/**
+ * The compiler should optimize this function away iff no one ever uses
+ * goog.reflect.sinkValue.
+ */
+goog.reflect.sinkValue[' '] = goog.nullFunction;
+
+
+/**
+ * Check if a property can be accessed without throwing an exception.
+ * @param {Object} obj The owner of the property.
+ * @param {string} prop The property name.
+ * @return {boolean} Whether the property is accessible. Will also return true
+ * if obj is null.
+ */
+goog.reflect.canAccessProperty = function(obj, prop) {
+ /** @preserveTry */
+ try {
+ goog.reflect.sinkValue(obj[prop]);
+ return true;
+ } catch (e) {}
+ return false;
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/string/string.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/string/string.js
new file mode 100644
index 0000000..90ee58c
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/string/string.js
@@ -0,0 +1,1476 @@
+// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Utilities for string manipulation.
+ */
+
+
+/**
+ * Namespace for string utilities
+ */
+goog.provide('goog.string');
+goog.provide('goog.string.Unicode');
+
+
+/**
+ * @define {boolean} Enables HTML escaping of lowercase letter "e" which helps
+ * with detection of double-escaping as this letter is frequently used.
+ */
+goog.define('goog.string.DETECT_DOUBLE_ESCAPING', false);
+
+
+/**
+ * Common Unicode string characters.
+ * @enum {string}
+ */
+goog.string.Unicode = {
+ NBSP: '\xa0'
+};
+
+
+/**
+ * Fast prefix-checker.
+ * @param {string} str The string to check.
+ * @param {string} prefix A string to look for at the start of {@code str}.
+ * @return {boolean} True if {@code str} begins with {@code prefix}.
+ */
+goog.string.startsWith = function(str, prefix) {
+ return str.lastIndexOf(prefix, 0) == 0;
+};
+
+
+/**
+ * Fast suffix-checker.
+ * @param {string} str The string to check.
+ * @param {string} suffix A string to look for at the end of {@code str}.
+ * @return {boolean} True if {@code str} ends with {@code suffix}.
+ */
+goog.string.endsWith = function(str, suffix) {
+ var l = str.length - suffix.length;
+ return l >= 0 && str.indexOf(suffix, l) == l;
+};
+
+
+/**
+ * Case-insensitive prefix-checker.
+ * @param {string} str The string to check.
+ * @param {string} prefix A string to look for at the end of {@code str}.
+ * @return {boolean} True if {@code str} begins with {@code prefix} (ignoring
+ * case).
+ */
+goog.string.caseInsensitiveStartsWith = function(str, prefix) {
+ return goog.string.caseInsensitiveCompare(
+ prefix, str.substr(0, prefix.length)) == 0;
+};
+
+
+/**
+ * Case-insensitive suffix-checker.
+ * @param {string} str The string to check.
+ * @param {string} suffix A string to look for at the end of {@code str}.
+ * @return {boolean} True if {@code str} ends with {@code suffix} (ignoring
+ * case).
+ */
+goog.string.caseInsensitiveEndsWith = function(str, suffix) {
+ return goog.string.caseInsensitiveCompare(
+ suffix, str.substr(str.length - suffix.length, suffix.length)) == 0;
+};
+
+
+/**
+ * Case-insensitive equality checker.
+ * @param {string} str1 First string to check.
+ * @param {string} str2 Second string to check.
+ * @return {boolean} True if {@code str1} and {@code str2} are the same string,
+ * ignoring case.
+ */
+goog.string.caseInsensitiveEquals = function(str1, str2) {
+ return str1.toLowerCase() == str2.toLowerCase();
+};
+
+
+/**
+ * Does simple python-style string substitution.
+ * subs("foo%s hot%s", "bar", "dog") becomes "foobar hotdog".
+ * @param {string} str The string containing the pattern.
+ * @param {...*} var_args The items to substitute into the pattern.
+ * @return {string} A copy of {@code str} in which each occurrence of
+ * {@code %s} has been replaced an argument from {@code var_args}.
+ */
+goog.string.subs = function(str, var_args) {
+ var splitParts = str.split('%s');
+ var returnString = '';
+
+ var subsArguments = Array.prototype.slice.call(arguments, 1);
+ while (subsArguments.length &&
+ // Replace up to the last split part. We are inserting in the
+ // positions between split parts.
+ splitParts.length > 1) {
+ returnString += splitParts.shift() + subsArguments.shift();
+ }
+
+ return returnString + splitParts.join('%s'); // Join unused '%s'
+};
+
+
+/**
+ * Converts multiple whitespace chars (spaces, non-breaking-spaces, new lines
+ * and tabs) to a single space, and strips leading and trailing whitespace.
+ * @param {string} str Input string.
+ * @return {string} A copy of {@code str} with collapsed whitespace.
+ */
+goog.string.collapseWhitespace = function(str) {
+ // Since IE doesn't include non-breaking-space (0xa0) in their \s character
+ // class (as required by section 7.2 of the ECMAScript spec), we explicitly
+ // include it in the regexp to enforce consistent cross-browser behavior.
+ return str.replace(/[\s\xa0]+/g, ' ').replace(/^\s+|\s+$/g, '');
+};
+
+
+/**
+ * Checks if a string is empty or contains only whitespaces.
+ * @param {string} str The string to check.
+ * @return {boolean} True if {@code str} is empty or whitespace only.
+ */
+goog.string.isEmpty = function(str) {
+ // testing length == 0 first is actually slower in all browsers (about the
+ // same in Opera).
+ // Since IE doesn't include non-breaking-space (0xa0) in their \s character
+ // class (as required by section 7.2 of the ECMAScript spec), we explicitly
+ // include it in the regexp to enforce consistent cross-browser behavior.
+ return /^[\s\xa0]*$/.test(str);
+};
+
+
+/**
+ * Checks if a string is null, undefined, empty or contains only whitespaces.
+ * @param {*} str The string to check.
+ * @return {boolean} True if{@code str} is null, undefined, empty, or
+ * whitespace only.
+ */
+goog.string.isEmptySafe = function(str) {
+ return goog.string.isEmpty(goog.string.makeSafe(str));
+};
+
+
+/**
+ * Checks if a string is all breaking whitespace.
+ * @param {string} str The string to check.
+ * @return {boolean} Whether the string is all breaking whitespace.
+ */
+goog.string.isBreakingWhitespace = function(str) {
+ return !/[^\t\n\r ]/.test(str);
+};
+
+
+/**
+ * Checks if a string contains all letters.
+ * @param {string} str string to check.
+ * @return {boolean} True if {@code str} consists entirely of letters.
+ */
+goog.string.isAlpha = function(str) {
+ return !/[^a-zA-Z]/.test(str);
+};
+
+
+/**
+ * Checks if a string contains only numbers.
+ * @param {*} str string to check. If not a string, it will be
+ * casted to one.
+ * @return {boolean} True if {@code str} is numeric.
+ */
+goog.string.isNumeric = function(str) {
+ return !/[^0-9]/.test(str);
+};
+
+
+/**
+ * Checks if a string contains only numbers or letters.
+ * @param {string} str string to check.
+ * @return {boolean} True if {@code str} is alphanumeric.
+ */
+goog.string.isAlphaNumeric = function(str) {
+ return !/[^a-zA-Z0-9]/.test(str);
+};
+
+
+/**
+ * Checks if a character is a space character.
+ * @param {string} ch Character to check.
+ * @return {boolean} True if {code ch} is a space.
+ */
+goog.string.isSpace = function(ch) {
+ return ch == ' ';
+};
+
+
+/**
+ * Checks if a character is a valid unicode character.
+ * @param {string} ch Character to check.
+ * @return {boolean} True if {code ch} is a valid unicode character.
+ */
+goog.string.isUnicodeChar = function(ch) {
+ return ch.length == 1 && ch >= ' ' && ch <= '~' ||
+ ch >= '\u0080' && ch <= '\uFFFD';
+};
+
+
+/**
+ * Takes a string and replaces newlines with a space. Multiple lines are
+ * replaced with a single space.
+ * @param {string} str The string from which to strip newlines.
+ * @return {string} A copy of {@code str} stripped of newlines.
+ */
+goog.string.stripNewlines = function(str) {
+ return str.replace(/(\r\n|\r|\n)+/g, ' ');
+};
+
+
+/**
+ * Replaces Windows and Mac new lines with unix style: \r or \r\n with \n.
+ * @param {string} str The string to in which to canonicalize newlines.
+ * @return {string} {@code str} A copy of {@code} with canonicalized newlines.
+ */
+goog.string.canonicalizeNewlines = function(str) {
+ return str.replace(/(\r\n|\r|\n)/g, '\n');
+};
+
+
+/**
+ * Normalizes whitespace in a string, replacing all whitespace chars with
+ * a space.
+ * @param {string} str The string in which to normalize whitespace.
+ * @return {string} A copy of {@code str} with all whitespace normalized.
+ */
+goog.string.normalizeWhitespace = function(str) {
+ return str.replace(/\xa0|\s/g, ' ');
+};
+
+
+/**
+ * Normalizes spaces in a string, replacing all consecutive spaces and tabs
+ * with a single space. Replaces non-breaking space with a space.
+ * @param {string} str The string in which to normalize spaces.
+ * @return {string} A copy of {@code str} with all consecutive spaces and tabs
+ * replaced with a single space.
+ */
+goog.string.normalizeSpaces = function(str) {
+ return str.replace(/\xa0|[ \t]+/g, ' ');
+};
+
+
+/**
+ * Removes the breaking spaces from the left and right of the string and
+ * collapses the sequences of breaking spaces in the middle into single spaces.
+ * The original and the result strings render the same way in HTML.
+ * @param {string} str A string in which to collapse spaces.
+ * @return {string} Copy of the string with normalized breaking spaces.
+ */
+goog.string.collapseBreakingSpaces = function(str) {
+ return str.replace(/[\t\r\n ]+/g, ' ').replace(
+ /^[\t\r\n ]+|[\t\r\n ]+$/g, '');
+};
+
+
+/**
+ * Trims white spaces to the left and right of a string.
+ * @param {string} str The string to trim.
+ * @return {string} A trimmed copy of {@code str}.
+ */
+goog.string.trim = function(str) {
+ // Since IE doesn't include non-breaking-space (0xa0) in their \s character
+ // class (as required by section 7.2 of the ECMAScript spec), we explicitly
+ // include it in the regexp to enforce consistent cross-browser behavior.
+ return str.replace(/^[\s\xa0]+|[\s\xa0]+$/g, '');
+};
+
+
+/**
+ * Trims whitespaces at the left end of a string.
+ * @param {string} str The string to left trim.
+ * @return {string} A trimmed copy of {@code str}.
+ */
+goog.string.trimLeft = function(str) {
+ // Since IE doesn't include non-breaking-space (0xa0) in their \s character
+ // class (as required by section 7.2 of the ECMAScript spec), we explicitly
+ // include it in the regexp to enforce consistent cross-browser behavior.
+ return str.replace(/^[\s\xa0]+/, '');
+};
+
+
+/**
+ * Trims whitespaces at the right end of a string.
+ * @param {string} str The string to right trim.
+ * @return {string} A trimmed copy of {@code str}.
+ */
+goog.string.trimRight = function(str) {
+ // Since IE doesn't include non-breaking-space (0xa0) in their \s character
+ // class (as required by section 7.2 of the ECMAScript spec), we explicitly
+ // include it in the regexp to enforce consistent cross-browser behavior.
+ return str.replace(/[\s\xa0]+$/, '');
+};
+
+
+/**
+ * A string comparator that ignores case.
+ * -1 = str1 less than str2
+ * 0 = str1 equals str2
+ * 1 = str1 greater than str2
+ *
+ * @param {string} str1 The string to compare.
+ * @param {string} str2 The string to compare {@code str1} to.
+ * @return {number} The comparator result, as described above.
+ */
+goog.string.caseInsensitiveCompare = function(str1, str2) {
+ var test1 = String(str1).toLowerCase();
+ var test2 = String(str2).toLowerCase();
+
+ if (test1 < test2) {
+ return -1;
+ } else if (test1 == test2) {
+ return 0;
+ } else {
+ return 1;
+ }
+};
+
+
+/**
+ * Regular expression used for splitting a string into substrings of fractional
+ * numbers, integers, and non-numeric characters.
+ * @type {RegExp}
+ * @private
+ */
+goog.string.numerateCompareRegExp_ = /(\.\d+)|(\d+)|(\D+)/g;
+
+
+/**
+ * String comparison function that handles numbers in a way humans might expect.
+ * Using this function, the string "File 2.jpg" sorts before "File 10.jpg". The
+ * comparison is mostly case-insensitive, though strings that are identical
+ * except for case are sorted with the upper-case strings before lower-case.
+ *
+ * This comparison function is significantly slower (about 500x) than either
+ * the default or the case-insensitive compare. It should not be used in
+ * time-critical code, but should be fast enough to sort several hundred short
+ * strings (like filenames) with a reasonable delay.
+ *
+ * @param {string} str1 The string to compare in a numerically sensitive way.
+ * @param {string} str2 The string to compare {@code str1} to.
+ * @return {number} less than 0 if str1 < str2, 0 if str1 == str2, greater than
+ * 0 if str1 > str2.
+ */
+goog.string.numerateCompare = function(str1, str2) {
+ if (str1 == str2) {
+ return 0;
+ }
+ if (!str1) {
+ return -1;
+ }
+ if (!str2) {
+ return 1;
+ }
+
+ // Using match to split the entire string ahead of time turns out to be faster
+ // for most inputs than using RegExp.exec or iterating over each character.
+ var tokens1 = str1.toLowerCase().match(goog.string.numerateCompareRegExp_);
+ var tokens2 = str2.toLowerCase().match(goog.string.numerateCompareRegExp_);
+
+ var count = Math.min(tokens1.length, tokens2.length);
+
+ for (var i = 0; i < count; i++) {
+ var a = tokens1[i];
+ var b = tokens2[i];
+
+ // Compare pairs of tokens, returning if one token sorts before the other.
+ if (a != b) {
+
+ // Only if both tokens are integers is a special comparison required.
+ // Decimal numbers are sorted as strings (e.g., '.09' < '.1').
+ var num1 = parseInt(a, 10);
+ if (!isNaN(num1)) {
+ var num2 = parseInt(b, 10);
+ if (!isNaN(num2) && num1 - num2) {
+ return num1 - num2;
+ }
+ }
+ return a < b ? -1 : 1;
+ }
+ }
+
+ // If one string is a substring of the other, the shorter string sorts first.
+ if (tokens1.length != tokens2.length) {
+ return tokens1.length - tokens2.length;
+ }
+
+ // The two strings must be equivalent except for case (perfect equality is
+ // tested at the head of the function.) Revert to default ASCII-betical string
+ // comparison to stablize the sort.
+ return str1 < str2 ? -1 : 1;
+};
+
+
+/**
+ * URL-encodes a string
+ * @param {*} str The string to url-encode.
+ * @return {string} An encoded copy of {@code str} that is safe for urls.
+ * Note that '#', ':', and other characters used to delimit portions
+ * of URLs *will* be encoded.
+ */
+goog.string.urlEncode = function(str) {
+ return encodeURIComponent(String(str));
+};
+
+
+/**
+ * URL-decodes the string. We need to specially handle '+'s because
+ * the javascript library doesn't convert them to spaces.
+ * @param {string} str The string to url decode.
+ * @return {string} The decoded {@code str}.
+ */
+goog.string.urlDecode = function(str) {
+ return decodeURIComponent(str.replace(/\+/g, ' '));
+};
+
+
+/**
+ * Converts \n to <br>s or <br />s.
+ * @param {string} str The string in which to convert newlines.
+ * @param {boolean=} opt_xml Whether to use XML compatible tags.
+ * @return {string} A copy of {@code str} with converted newlines.
+ */
+goog.string.newLineToBr = function(str, opt_xml) {
+ return str.replace(/(\r\n|\r|\n)/g, opt_xml ? '<br />' : '<br>');
+};
+
+
+/**
+ * Escapes double quote '"' and single quote '\'' characters in addition to
+ * '&', '<', and '>' so that a string can be included in an HTML tag attribute
+ * value within double or single quotes.
+ *
+ * It should be noted that > doesn't need to be escaped for the HTML or XML to
+ * be valid, but it has been decided to escape it for consistency with other
+ * implementations.
+ *
+ * With goog.string.DETECT_DOUBLE_ESCAPING, this function escapes also the
+ * lowercase letter "e".
+ *
+ * NOTE(user):
+ * HtmlEscape is often called during the generation of large blocks of HTML.
+ * Using statics for the regular expressions and strings is an optimization
+ * that can more than half the amount of time IE spends in this function for
+ * large apps, since strings and regexes both contribute to GC allocations.
+ *
+ * Testing for the presence of a character before escaping increases the number
+ * of function calls, but actually provides a speed increase for the average
+ * case -- since the average case often doesn't require the escaping of all 4
+ * characters and indexOf() is much cheaper than replace().
+ * The worst case does suffer slightly from the additional calls, therefore the
+ * opt_isLikelyToContainHtmlChars option has been included for situations
+ * where all 4 HTML entities are very likely to be present and need escaping.
+ *
+ * Some benchmarks (times tended to fluctuate +-0.05ms):
+ * FireFox IE6
+ * (no chars / average (mix of cases) / all 4 chars)
+ * no checks 0.13 / 0.22 / 0.22 0.23 / 0.53 / 0.80
+ * indexOf 0.08 / 0.17 / 0.26 0.22 / 0.54 / 0.84
+ * indexOf + re test 0.07 / 0.17 / 0.28 0.19 / 0.50 / 0.85
+ *
+ * An additional advantage of checking if replace actually needs to be called
+ * is a reduction in the number of object allocations, so as the size of the
+ * application grows the difference between the various methods would increase.
+ *
+ * @param {string} str string to be escaped.
+ * @param {boolean=} opt_isLikelyToContainHtmlChars Don't perform a check to see
+ * if the character needs replacing - use this option if you expect each of
+ * the characters to appear often. Leave false if you expect few html
+ * characters to occur in your strings, such as if you are escaping HTML.
+ * @return {string} An escaped copy of {@code str}.
+ */
+goog.string.htmlEscape = function(str, opt_isLikelyToContainHtmlChars) {
+
+ if (opt_isLikelyToContainHtmlChars) {
+ str = str.replace(goog.string.AMP_RE_, '&amp;')
+ .replace(goog.string.LT_RE_, '&lt;')
+ .replace(goog.string.GT_RE_, '&gt;')
+ .replace(goog.string.QUOT_RE_, '&quot;')
+ .replace(goog.string.SINGLE_QUOTE_RE_, '&#39;')
+ .replace(goog.string.NULL_RE_, '&#0;');
+ if (goog.string.DETECT_DOUBLE_ESCAPING) {
+ str = str.replace(goog.string.E_RE_, '&#101;');
+ }
+ return str;
+
+ } else {
+ // quick test helps in the case when there are no chars to replace, in
+ // worst case this makes barely a difference to the time taken
+ if (!goog.string.ALL_RE_.test(str)) return str;
+
+ // str.indexOf is faster than regex.test in this case
+ if (str.indexOf('&') != -1) {
+ str = str.replace(goog.string.AMP_RE_, '&amp;');
+ }
+ if (str.indexOf('<') != -1) {
+ str = str.replace(goog.string.LT_RE_, '&lt;');
+ }
+ if (str.indexOf('>') != -1) {
+ str = str.replace(goog.string.GT_RE_, '&gt;');
+ }
+ if (str.indexOf('"') != -1) {
+ str = str.replace(goog.string.QUOT_RE_, '&quot;');
+ }
+ if (str.indexOf('\'') != -1) {
+ str = str.replace(goog.string.SINGLE_QUOTE_RE_, '&#39;');
+ }
+ if (str.indexOf('\x00') != -1) {
+ str = str.replace(goog.string.NULL_RE_, '&#0;');
+ }
+ if (goog.string.DETECT_DOUBLE_ESCAPING && str.indexOf('e') != -1) {
+ str = str.replace(goog.string.E_RE_, '&#101;');
+ }
+ return str;
+ }
+};
+
+
+/**
+ * Regular expression that matches an ampersand, for use in escaping.
+ * @const {!RegExp}
+ * @private
+ */
+goog.string.AMP_RE_ = /&/g;
+
+
+/**
+ * Regular expression that matches a less than sign, for use in escaping.
+ * @const {!RegExp}
+ * @private
+ */
+goog.string.LT_RE_ = /</g;
+
+
+/**
+ * Regular expression that matches a greater than sign, for use in escaping.
+ * @const {!RegExp}
+ * @private
+ */
+goog.string.GT_RE_ = />/g;
+
+
+/**
+ * Regular expression that matches a double quote, for use in escaping.
+ * @const {!RegExp}
+ * @private
+ */
+goog.string.QUOT_RE_ = /"/g;
+
+
+/**
+ * Regular expression that matches a single quote, for use in escaping.
+ * @const {!RegExp}
+ * @private
+ */
+goog.string.SINGLE_QUOTE_RE_ = /'/g;
+
+
+/**
+ * Regular expression that matches null character, for use in escaping.
+ * @const {!RegExp}
+ * @private
+ */
+goog.string.NULL_RE_ = /\x00/g;
+
+
+/**
+ * Regular expression that matches a lowercase letter "e", for use in escaping.
+ * @const {!RegExp}
+ * @private
+ */
+goog.string.E_RE_ = /e/g;
+
+
+/**
+ * Regular expression that matches any character that needs to be escaped.
+ * @const {!RegExp}
+ * @private
+ */
+goog.string.ALL_RE_ = (goog.string.DETECT_DOUBLE_ESCAPING ?
+ /[\x00&<>"'e]/ :
+ /[\x00&<>"']/);
+
+
+/**
+ * Unescapes an HTML string.
+ *
+ * @param {string} str The string to unescape.
+ * @return {string} An unescaped copy of {@code str}.
+ */
+goog.string.unescapeEntities = function(str) {
+ if (goog.string.contains(str, '&')) {
+ // We are careful not to use a DOM if we do not have one. We use the []
+ // notation so that the JSCompiler will not complain about these objects and
+ // fields in the case where we have no DOM.
+ if ('document' in goog.global) {
+ return goog.string.unescapeEntitiesUsingDom_(str);
+ } else {
+ // Fall back on pure XML entities
+ return goog.string.unescapePureXmlEntities_(str);
+ }
+ }
+ return str;
+};
+
+
+/**
+ * Unescapes a HTML string using the provided document.
+ *
+ * @param {string} str The string to unescape.
+ * @param {!Document} document A document to use in escaping the string.
+ * @return {string} An unescaped copy of {@code str}.
+ */
+goog.string.unescapeEntitiesWithDocument = function(str, document) {
+ if (goog.string.contains(str, '&')) {
+ return goog.string.unescapeEntitiesUsingDom_(str, document);
+ }
+ return str;
+};
+
+
+/**
+ * Unescapes an HTML string using a DOM to resolve non-XML, non-numeric
+ * entities. This function is XSS-safe and whitespace-preserving.
+ * @private
+ * @param {string} str The string to unescape.
+ * @param {Document=} opt_document An optional document to use for creating
+ * elements. If this is not specified then the default window.document
+ * will be used.
+ * @return {string} The unescaped {@code str} string.
+ */
+goog.string.unescapeEntitiesUsingDom_ = function(str, opt_document) {
+ var seen = {'&amp;': '&', '&lt;': '<', '&gt;': '>', '&quot;': '"'};
+ var div;
+ if (opt_document) {
+ div = opt_document.createElement('div');
+ } else {
+ div = goog.global.document.createElement('div');
+ }
+ // Match as many valid entity characters as possible. If the actual entity
+ // happens to be shorter, it will still work as innerHTML will return the
+ // trailing characters unchanged. Since the entity characters do not include
+ // open angle bracket, there is no chance of XSS from the innerHTML use.
+ // Since no whitespace is passed to innerHTML, whitespace is preserved.
+ return str.replace(goog.string.HTML_ENTITY_PATTERN_, function(s, entity) {
+ // Check for cached entity.
+ var value = seen[s];
+ if (value) {
+ return value;
+ }
+ // Check for numeric entity.
+ if (entity.charAt(0) == '#') {
+ // Prefix with 0 so that hex entities (e.g. &#x10) parse as hex numbers.
+ var n = Number('0' + entity.substr(1));
+ if (!isNaN(n)) {
+ value = String.fromCharCode(n);
+ }
+ }
+ // Fall back to innerHTML otherwise.
+ if (!value) {
+ // Append a non-entity character to avoid a bug in Webkit that parses
+ // an invalid entity at the end of innerHTML text as the empty string.
+ div.innerHTML = s + ' ';
+ // Then remove the trailing character from the result.
+ value = div.firstChild.nodeValue.slice(0, -1);
+ }
+ // Cache and return.
+ return seen[s] = value;
+ });
+};
+
+
+/**
+ * Unescapes XML entities.
+ * @private
+ * @param {string} str The string to unescape.
+ * @return {string} An unescaped copy of {@code str}.
+ */
+goog.string.unescapePureXmlEntities_ = function(str) {
+ return str.replace(/&([^;]+);/g, function(s, entity) {
+ switch (entity) {
+ case 'amp':
+ return '&';
+ case 'lt':
+ return '<';
+ case 'gt':
+ return '>';
+ case 'quot':
+ return '"';
+ default:
+ if (entity.charAt(0) == '#') {
+ // Prefix with 0 so that hex entities (e.g. &#x10) parse as hex.
+ var n = Number('0' + entity.substr(1));
+ if (!isNaN(n)) {
+ return String.fromCharCode(n);
+ }
+ }
+ // For invalid entities we just return the entity
+ return s;
+ }
+ });
+};
+
+
+/**
+ * Regular expression that matches an HTML entity.
+ * See also HTML5: Tokenization / Tokenizing character references.
+ * @private
+ * @type {!RegExp}
+ */
+goog.string.HTML_ENTITY_PATTERN_ = /&([^;\s<&]+);?/g;
+
+
+/**
+ * Do escaping of whitespace to preserve spatial formatting. We use character
+ * entity #160 to make it safer for xml.
+ * @param {string} str The string in which to escape whitespace.
+ * @param {boolean=} opt_xml Whether to use XML compatible tags.
+ * @return {string} An escaped copy of {@code str}.
+ */
+goog.string.whitespaceEscape = function(str, opt_xml) {
+ // This doesn't use goog.string.preserveSpaces for backwards compatibility.
+ return goog.string.newLineToBr(str.replace(/ /g, ' &#160;'), opt_xml);
+};
+
+
+/**
+ * Preserve spaces that would be otherwise collapsed in HTML by replacing them
+ * with non-breaking space Unicode characters.
+ * @param {string} str The string in which to preserve whitespace.
+ * @return {string} A copy of {@code str} with preserved whitespace.
+ */
+goog.string.preserveSpaces = function(str) {
+ return str.replace(/(^|[\n ]) /g, '$1' + goog.string.Unicode.NBSP);
+};
+
+
+/**
+ * Strip quote characters around a string. The second argument is a string of
+ * characters to treat as quotes. This can be a single character or a string of
+ * multiple character and in that case each of those are treated as possible
+ * quote characters. For example:
+ *
+ * <pre>
+ * goog.string.stripQuotes('"abc"', '"`') --> 'abc'
+ * goog.string.stripQuotes('`abc`', '"`') --> 'abc'
+ * </pre>
+ *
+ * @param {string} str The string to strip.
+ * @param {string} quoteChars The quote characters to strip.
+ * @return {string} A copy of {@code str} without the quotes.
+ */
+goog.string.stripQuotes = function(str, quoteChars) {
+ var length = quoteChars.length;
+ for (var i = 0; i < length; i++) {
+ var quoteChar = length == 1 ? quoteChars : quoteChars.charAt(i);
+ if (str.charAt(0) == quoteChar && str.charAt(str.length - 1) == quoteChar) {
+ return str.substring(1, str.length - 1);
+ }
+ }
+ return str;
+};
+
+
+/**
+ * Truncates a string to a certain length and adds '...' if necessary. The
+ * length also accounts for the ellipsis, so a maximum length of 10 and a string
+ * 'Hello World!' produces 'Hello W...'.
+ * @param {string} str The string to truncate.
+ * @param {number} chars Max number of characters.
+ * @param {boolean=} opt_protectEscapedCharacters Whether to protect escaped
+ * characters from being cut off in the middle.
+ * @return {string} The truncated {@code str} string.
+ */
+goog.string.truncate = function(str, chars, opt_protectEscapedCharacters) {
+ if (opt_protectEscapedCharacters) {
+ str = goog.string.unescapeEntities(str);
+ }
+
+ if (str.length > chars) {
+ str = str.substring(0, chars - 3) + '...';
+ }
+
+ if (opt_protectEscapedCharacters) {
+ str = goog.string.htmlEscape(str);
+ }
+
+ return str;
+};
+
+
+/**
+ * Truncate a string in the middle, adding "..." if necessary,
+ * and favoring the beginning of the string.
+ * @param {string} str The string to truncate the middle of.
+ * @param {number} chars Max number of characters.
+ * @param {boolean=} opt_protectEscapedCharacters Whether to protect escaped
+ * characters from being cutoff in the middle.
+ * @param {number=} opt_trailingChars Optional number of trailing characters to
+ * leave at the end of the string, instead of truncating as close to the
+ * middle as possible.
+ * @return {string} A truncated copy of {@code str}.
+ */
+goog.string.truncateMiddle = function(str, chars,
+ opt_protectEscapedCharacters, opt_trailingChars) {
+ if (opt_protectEscapedCharacters) {
+ str = goog.string.unescapeEntities(str);
+ }
+
+ if (opt_trailingChars && str.length > chars) {
+ if (opt_trailingChars > chars) {
+ opt_trailingChars = chars;
+ }
+ var endPoint = str.length - opt_trailingChars;
+ var startPoint = chars - opt_trailingChars;
+ str = str.substring(0, startPoint) + '...' + str.substring(endPoint);
+ } else if (str.length > chars) {
+ // Favor the beginning of the string:
+ var half = Math.floor(chars / 2);
+ var endPos = str.length - half;
+ half += chars % 2;
+ str = str.substring(0, half) + '...' + str.substring(endPos);
+ }
+
+ if (opt_protectEscapedCharacters) {
+ str = goog.string.htmlEscape(str);
+ }
+
+ return str;
+};
+
+
+/**
+ * Special chars that need to be escaped for goog.string.quote.
+ * @private
+ * @type {Object}
+ */
+goog.string.specialEscapeChars_ = {
+ '\0': '\\0',
+ '\b': '\\b',
+ '\f': '\\f',
+ '\n': '\\n',
+ '\r': '\\r',
+ '\t': '\\t',
+ '\x0B': '\\x0B', // '\v' is not supported in JScript
+ '"': '\\"',
+ '\\': '\\\\'
+};
+
+
+/**
+ * Character mappings used internally for goog.string.escapeChar.
+ * @private
+ * @type {Object}
+ */
+goog.string.jsEscapeCache_ = {
+ '\'': '\\\''
+};
+
+
+/**
+ * Encloses a string in double quotes and escapes characters so that the
+ * string is a valid JS string.
+ * @param {string} s The string to quote.
+ * @return {string} A copy of {@code s} surrounded by double quotes.
+ */
+goog.string.quote = function(s) {
+ s = String(s);
+ if (s.quote) {
+ return s.quote();
+ } else {
+ var sb = ['"'];
+ for (var i = 0; i < s.length; i++) {
+ var ch = s.charAt(i);
+ var cc = ch.charCodeAt(0);
+ sb[i + 1] = goog.string.specialEscapeChars_[ch] ||
+ ((cc > 31 && cc < 127) ? ch : goog.string.escapeChar(ch));
+ }
+ sb.push('"');
+ return sb.join('');
+ }
+};
+
+
+/**
+ * Takes a string and returns the escaped string for that character.
+ * @param {string} str The string to escape.
+ * @return {string} An escaped string representing {@code str}.
+ */
+goog.string.escapeString = function(str) {
+ var sb = [];
+ for (var i = 0; i < str.length; i++) {
+ sb[i] = goog.string.escapeChar(str.charAt(i));
+ }
+ return sb.join('');
+};
+
+
+/**
+ * Takes a character and returns the escaped string for that character. For
+ * example escapeChar(String.fromCharCode(15)) -> "\\x0E".
+ * @param {string} c The character to escape.
+ * @return {string} An escaped string representing {@code c}.
+ */
+goog.string.escapeChar = function(c) {
+ if (c in goog.string.jsEscapeCache_) {
+ return goog.string.jsEscapeCache_[c];
+ }
+
+ if (c in goog.string.specialEscapeChars_) {
+ return goog.string.jsEscapeCache_[c] = goog.string.specialEscapeChars_[c];
+ }
+
+ var rv = c;
+ var cc = c.charCodeAt(0);
+ if (cc > 31 && cc < 127) {
+ rv = c;
+ } else {
+ // tab is 9 but handled above
+ if (cc < 256) {
+ rv = '\\x';
+ if (cc < 16 || cc > 256) {
+ rv += '0';
+ }
+ } else {
+ rv = '\\u';
+ if (cc < 4096) { // \u1000
+ rv += '0';
+ }
+ }
+ rv += cc.toString(16).toUpperCase();
+ }
+
+ return goog.string.jsEscapeCache_[c] = rv;
+};
+
+
+/**
+ * Takes a string and creates a map (Object) in which the keys are the
+ * characters in the string. The value for the key is set to true. You can
+ * then use goog.object.map or goog.array.map to change the values.
+ * @param {string} s The string to build the map from.
+ * @return {!Object} The map of characters used.
+ */
+// TODO(arv): It seems like we should have a generic goog.array.toMap. But do
+// we want a dependency on goog.array in goog.string?
+goog.string.toMap = function(s) {
+ var rv = {};
+ for (var i = 0; i < s.length; i++) {
+ rv[s.charAt(i)] = true;
+ }
+ return rv;
+};
+
+
+/**
+ * Determines whether a string contains a substring.
+ * @param {string} str The string to search.
+ * @param {string} subString The substring to search for.
+ * @return {boolean} Whether {@code str} contains {@code subString}.
+ */
+goog.string.contains = function(str, subString) {
+ return str.indexOf(subString) != -1;
+};
+
+
+/**
+ * Determines whether a string contains a substring, ignoring case.
+ * @param {string} str The string to search.
+ * @param {string} subString The substring to search for.
+ * @return {boolean} Whether {@code str} contains {@code subString}.
+ */
+goog.string.caseInsensitiveContains = function(str, subString) {
+ return goog.string.contains(str.toLowerCase(), subString.toLowerCase());
+};
+
+
+/**
+ * Returns the non-overlapping occurrences of ss in s.
+ * If either s or ss evalutes to false, then returns zero.
+ * @param {string} s The string to look in.
+ * @param {string} ss The string to look for.
+ * @return {number} Number of occurrences of ss in s.
+ */
+goog.string.countOf = function(s, ss) {
+ return s && ss ? s.split(ss).length - 1 : 0;
+};
+
+
+/**
+ * Removes a substring of a specified length at a specific
+ * index in a string.
+ * @param {string} s The base string from which to remove.
+ * @param {number} index The index at which to remove the substring.
+ * @param {number} stringLength The length of the substring to remove.
+ * @return {string} A copy of {@code s} with the substring removed or the full
+ * string if nothing is removed or the input is invalid.
+ */
+goog.string.removeAt = function(s, index, stringLength) {
+ var resultStr = s;
+ // If the index is greater or equal to 0 then remove substring
+ if (index >= 0 && index < s.length && stringLength > 0) {
+ resultStr = s.substr(0, index) +
+ s.substr(index + stringLength, s.length - index - stringLength);
+ }
+ return resultStr;
+};
+
+
+/**
+ * Removes the first occurrence of a substring from a string.
+ * @param {string} s The base string from which to remove.
+ * @param {string} ss The string to remove.
+ * @return {string} A copy of {@code s} with {@code ss} removed or the full
+ * string if nothing is removed.
+ */
+goog.string.remove = function(s, ss) {
+ var re = new RegExp(goog.string.regExpEscape(ss), '');
+ return s.replace(re, '');
+};
+
+
+/**
+ * Removes all occurrences of a substring from a string.
+ * @param {string} s The base string from which to remove.
+ * @param {string} ss The string to remove.
+ * @return {string} A copy of {@code s} with {@code ss} removed or the full
+ * string if nothing is removed.
+ */
+goog.string.removeAll = function(s, ss) {
+ var re = new RegExp(goog.string.regExpEscape(ss), 'g');
+ return s.replace(re, '');
+};
+
+
+/**
+ * Escapes characters in the string that are not safe to use in a RegExp.
+ * @param {*} s The string to escape. If not a string, it will be casted
+ * to one.
+ * @return {string} A RegExp safe, escaped copy of {@code s}.
+ */
+goog.string.regExpEscape = function(s) {
+ return String(s).replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1').
+ replace(/\x08/g, '\\x08');
+};
+
+
+/**
+ * Repeats a string n times.
+ * @param {string} string The string to repeat.
+ * @param {number} length The number of times to repeat.
+ * @return {string} A string containing {@code length} repetitions of
+ * {@code string}.
+ */
+goog.string.repeat = function(string, length) {
+ return new Array(length + 1).join(string);
+};
+
+
+/**
+ * Pads number to given length and optionally rounds it to a given precision.
+ * For example:
+ * <pre>padNumber(1.25, 2, 3) -> '01.250'
+ * padNumber(1.25, 2) -> '01.25'
+ * padNumber(1.25, 2, 1) -> '01.3'
+ * padNumber(1.25, 0) -> '1.25'</pre>
+ *
+ * @param {number} num The number to pad.
+ * @param {number} length The desired length.
+ * @param {number=} opt_precision The desired precision.
+ * @return {string} {@code num} as a string with the given options.
+ */
+goog.string.padNumber = function(num, length, opt_precision) {
+ var s = goog.isDef(opt_precision) ? num.toFixed(opt_precision) : String(num);
+ var index = s.indexOf('.');
+ if (index == -1) {
+ index = s.length;
+ }
+ return goog.string.repeat('0', Math.max(0, length - index)) + s;
+};
+
+
+/**
+ * Returns a string representation of the given object, with
+ * null and undefined being returned as the empty string.
+ *
+ * @param {*} obj The object to convert.
+ * @return {string} A string representation of the {@code obj}.
+ */
+goog.string.makeSafe = function(obj) {
+ return obj == null ? '' : String(obj);
+};
+
+
+/**
+ * Concatenates string expressions. This is useful
+ * since some browsers are very inefficient when it comes to using plus to
+ * concat strings. Be careful when using null and undefined here since
+ * these will not be included in the result. If you need to represent these
+ * be sure to cast the argument to a String first.
+ * For example:
+ * <pre>buildString('a', 'b', 'c', 'd') -> 'abcd'
+ * buildString(null, undefined) -> ''
+ * </pre>
+ * @param {...*} var_args A list of strings to concatenate. If not a string,
+ * it will be casted to one.
+ * @return {string} The concatenation of {@code var_args}.
+ */
+goog.string.buildString = function(var_args) {
+ return Array.prototype.join.call(arguments, '');
+};
+
+
+/**
+ * Returns a string with at least 64-bits of randomness.
+ *
+ * Doesn't trust Javascript's random function entirely. Uses a combination of
+ * random and current timestamp, and then encodes the string in base-36 to
+ * make it shorter.
+ *
+ * @return {string} A random string, e.g. sn1s7vb4gcic.
+ */
+goog.string.getRandomString = function() {
+ var x = 2147483648;
+ return Math.floor(Math.random() * x).toString(36) +
+ Math.abs(Math.floor(Math.random() * x) ^ goog.now()).toString(36);
+};
+
+
+/**
+ * Compares two version numbers.
+ *
+ * @param {string|number} version1 Version of first item.
+ * @param {string|number} version2 Version of second item.
+ *
+ * @return {number} 1 if {@code version1} is higher.
+ * 0 if arguments are equal.
+ * -1 if {@code version2} is higher.
+ */
+goog.string.compareVersions = function(version1, version2) {
+ var order = 0;
+ // Trim leading and trailing whitespace and split the versions into
+ // subversions.
+ var v1Subs = goog.string.trim(String(version1)).split('.');
+ var v2Subs = goog.string.trim(String(version2)).split('.');
+ var subCount = Math.max(v1Subs.length, v2Subs.length);
+
+ // Iterate over the subversions, as long as they appear to be equivalent.
+ for (var subIdx = 0; order == 0 && subIdx < subCount; subIdx++) {
+ var v1Sub = v1Subs[subIdx] || '';
+ var v2Sub = v2Subs[subIdx] || '';
+
+ // Split the subversions into pairs of numbers and qualifiers (like 'b').
+ // Two different RegExp objects are needed because they are both using
+ // the 'g' flag.
+ var v1CompParser = new RegExp('(\\d*)(\\D*)', 'g');
+ var v2CompParser = new RegExp('(\\d*)(\\D*)', 'g');
+ do {
+ var v1Comp = v1CompParser.exec(v1Sub) || ['', '', ''];
+ var v2Comp = v2CompParser.exec(v2Sub) || ['', '', ''];
+ // Break if there are no more matches.
+ if (v1Comp[0].length == 0 && v2Comp[0].length == 0) {
+ break;
+ }
+
+ // Parse the numeric part of the subversion. A missing number is
+ // equivalent to 0.
+ var v1CompNum = v1Comp[1].length == 0 ? 0 : parseInt(v1Comp[1], 10);
+ var v2CompNum = v2Comp[1].length == 0 ? 0 : parseInt(v2Comp[1], 10);
+
+ // Compare the subversion components. The number has the highest
+ // precedence. Next, if the numbers are equal, a subversion without any
+ // qualifier is always higher than a subversion with any qualifier. Next,
+ // the qualifiers are compared as strings.
+ order = goog.string.compareElements_(v1CompNum, v2CompNum) ||
+ goog.string.compareElements_(v1Comp[2].length == 0,
+ v2Comp[2].length == 0) ||
+ goog.string.compareElements_(v1Comp[2], v2Comp[2]);
+ // Stop as soon as an inequality is discovered.
+ } while (order == 0);
+ }
+
+ return order;
+};
+
+
+/**
+ * Compares elements of a version number.
+ *
+ * @param {string|number|boolean} left An element from a version number.
+ * @param {string|number|boolean} right An element from a version number.
+ *
+ * @return {number} 1 if {@code left} is higher.
+ * 0 if arguments are equal.
+ * -1 if {@code right} is higher.
+ * @private
+ */
+goog.string.compareElements_ = function(left, right) {
+ if (left < right) {
+ return -1;
+ } else if (left > right) {
+ return 1;
+ }
+ return 0;
+};
+
+
+/**
+ * Maximum value of #goog.string.hashCode, exclusive. 2^32.
+ * @type {number}
+ * @private
+ */
+goog.string.HASHCODE_MAX_ = 0x100000000;
+
+
+/**
+ * String hash function similar to java.lang.String.hashCode().
+ * The hash code for a string is computed as
+ * s[0] * 31 ^ (n - 1) + s[1] * 31 ^ (n - 2) + ... + s[n - 1],
+ * where s[i] is the ith character of the string and n is the length of
+ * the string. We mod the result to make it between 0 (inclusive) and 2^32
+ * (exclusive).
+ * @param {string} str A string.
+ * @return {number} Hash value for {@code str}, between 0 (inclusive) and 2^32
+ * (exclusive). The empty string returns 0.
+ */
+goog.string.hashCode = function(str) {
+ var result = 0;
+ for (var i = 0; i < str.length; ++i) {
+ result = 31 * result + str.charCodeAt(i);
+ // Normalize to 4 byte range, 0 ... 2^32.
+ result %= goog.string.HASHCODE_MAX_;
+ }
+ return result;
+};
+
+
+/**
+ * The most recent unique ID. |0 is equivalent to Math.floor in this case.
+ * @type {number}
+ * @private
+ */
+goog.string.uniqueStringCounter_ = Math.random() * 0x80000000 | 0;
+
+
+/**
+ * Generates and returns a string which is unique in the current document.
+ * This is useful, for example, to create unique IDs for DOM elements.
+ * @return {string} A unique id.
+ */
+goog.string.createUniqueString = function() {
+ return 'goog_' + goog.string.uniqueStringCounter_++;
+};
+
+
+/**
+ * Converts the supplied string to a number, which may be Infinity or NaN.
+ * This function strips whitespace: (toNumber(' 123') === 123)
+ * This function accepts scientific notation: (toNumber('1e1') === 10)
+ *
+ * This is better than Javascript's built-in conversions because, sadly:
+ * (Number(' ') === 0) and (parseFloat('123a') === 123)
+ *
+ * @param {string} str The string to convert.
+ * @return {number} The number the supplied string represents, or NaN.
+ */
+goog.string.toNumber = function(str) {
+ var num = Number(str);
+ if (num == 0 && goog.string.isEmpty(str)) {
+ return NaN;
+ }
+ return num;
+};
+
+
+/**
+ * Returns whether the given string is lower camel case (e.g. "isFooBar").
+ *
+ * Note that this assumes the string is entirely letters.
+ * @see http://en.wikipedia.org/wiki/CamelCase#Variations_and_synonyms
+ *
+ * @param {string} str String to test.
+ * @return {boolean} Whether the string is lower camel case.
+ */
+goog.string.isLowerCamelCase = function(str) {
+ return /^[a-z]+([A-Z][a-z]*)*$/.test(str);
+};
+
+
+/**
+ * Returns whether the given string is upper camel case (e.g. "FooBarBaz").
+ *
+ * Note that this assumes the string is entirely letters.
+ * @see http://en.wikipedia.org/wiki/CamelCase#Variations_and_synonyms
+ *
+ * @param {string} str String to test.
+ * @return {boolean} Whether the string is upper camel case.
+ */
+goog.string.isUpperCamelCase = function(str) {
+ return /^([A-Z][a-z]*)+$/.test(str);
+};
+
+
+/**
+ * Converts a string from selector-case to camelCase (e.g. from
+ * "multi-part-string" to "multiPartString"), useful for converting
+ * CSS selectors and HTML dataset keys to their equivalent JS properties.
+ * @param {string} str The string in selector-case form.
+ * @return {string} The string in camelCase form.
+ */
+goog.string.toCamelCase = function(str) {
+ return String(str).replace(/\-([a-z])/g, function(all, match) {
+ return match.toUpperCase();
+ });
+};
+
+
+/**
+ * Converts a string from camelCase to selector-case (e.g. from
+ * "multiPartString" to "multi-part-string"), useful for converting JS
+ * style and dataset properties to equivalent CSS selectors and HTML keys.
+ * @param {string} str The string in camelCase form.
+ * @return {string} The string in selector-case form.
+ */
+goog.string.toSelectorCase = function(str) {
+ return String(str).replace(/([A-Z])/g, '-$1').toLowerCase();
+};
+
+
+/**
+ * Converts a string into TitleCase. First character of the string is always
+ * capitalized in addition to the first letter of every subsequent word.
+ * Words are delimited by one or more whitespaces by default. Custom delimiters
+ * can optionally be specified to replace the default, which doesn't preserve
+ * whitespace delimiters and instead must be explicitly included if needed.
+ *
+ * Default delimiter => " ":
+ * goog.string.toTitleCase('oneTwoThree') => 'OneTwoThree'
+ * goog.string.toTitleCase('one two three') => 'One Two Three'
+ * goog.string.toTitleCase(' one two ') => ' One Two '
+ * goog.string.toTitleCase('one_two_three') => 'One_two_three'
+ * goog.string.toTitleCase('one-two-three') => 'One-two-three'
+ *
+ * Custom delimiter => "_-.":
+ * goog.string.toTitleCase('oneTwoThree', '_-.') => 'OneTwoThree'
+ * goog.string.toTitleCase('one two three', '_-.') => 'One two three'
+ * goog.string.toTitleCase(' one two ', '_-.') => ' one two '
+ * goog.string.toTitleCase('one_two_three', '_-.') => 'One_Two_Three'
+ * goog.string.toTitleCase('one-two-three', '_-.') => 'One-Two-Three'
+ * goog.string.toTitleCase('one...two...three', '_-.') => 'One...Two...Three'
+ * goog.string.toTitleCase('one. two. three', '_-.') => 'One. two. three'
+ * goog.string.toTitleCase('one-two.three', '_-.') => 'One-Two.Three'
+ *
+ * @param {string} str String value in camelCase form.
+ * @param {string=} opt_delimiters Custom delimiter character set used to
+ * distinguish words in the string value. Each character represents a
+ * single delimiter. When provided, default whitespace delimiter is
+ * overridden and must be explicitly included if needed.
+ * @return {string} String value in TitleCase form.
+ */
+goog.string.toTitleCase = function(str, opt_delimiters) {
+ var delimiters = goog.isString(opt_delimiters) ?
+ goog.string.regExpEscape(opt_delimiters) : '\\s';
+
+ // For IE8, we need to prevent using an empty character set. Otherwise,
+ // incorrect matching will occur.
+ delimiters = delimiters ? '|[' + delimiters + ']+' : '';
+
+ var regexp = new RegExp('(^' + delimiters + ')([a-z])', 'g');
+ return str.replace(regexp, function(all, p1, p2) {
+ return p1 + p2.toUpperCase();
+ });
+};
+
+
+/**
+ * Parse a string in decimal or hexidecimal ('0xFFFF') form.
+ *
+ * To parse a particular radix, please use parseInt(string, radix) directly. See
+ * https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/parseInt
+ *
+ * This is a wrapper for the built-in parseInt function that will only parse
+ * numbers as base 10 or base 16. Some JS implementations assume strings
+ * starting with "0" are intended to be octal. ES3 allowed but discouraged
+ * this behavior. ES5 forbids it. This function emulates the ES5 behavior.
+ *
+ * For more information, see Mozilla JS Reference: http://goo.gl/8RiFj
+ *
+ * @param {string|number|null|undefined} value The value to be parsed.
+ * @return {number} The number, parsed. If the string failed to parse, this
+ * will be NaN.
+ */
+goog.string.parseInt = function(value) {
+ // Force finite numbers to strings.
+ if (isFinite(value)) {
+ value = String(value);
+ }
+
+ if (goog.isString(value)) {
+ // If the string starts with '0x' or '-0x', parse as hex.
+ return /^\s*-?0x/i.test(value) ?
+ parseInt(value, 16) : parseInt(value, 10);
+ }
+
+ return NaN;
+};
+
+
+/**
+ * Splits a string on a separator a limited number of times.
+ *
+ * This implementation is more similar to Python or Java, where the limit
+ * parameter specifies the maximum number of splits rather than truncating
+ * the number of results.
+ *
+ * See http://docs.python.org/2/library/stdtypes.html#str.split
+ * See JavaDoc: http://goo.gl/F2AsY
+ * See Mozilla reference: http://goo.gl/dZdZs
+ *
+ * @param {string} str String to split.
+ * @param {string} separator The separator.
+ * @param {number} limit The limit to the number of splits. The resulting array
+ * will have a maximum length of limit+1. Negative numbers are the same
+ * as zero.
+ * @return {!Array.<string>} The string, split.
+ */
+
+goog.string.splitLimit = function(str, separator, limit) {
+ var parts = str.split(separator);
+ var returnVal = [];
+
+ // Only continue doing this while we haven't hit the limit and we have
+ // parts left.
+ while (limit > 0 && parts.length) {
+ returnVal.push(parts.shift());
+ limit--;
+ }
+
+ // If there are remaining parts, append them to the end.
+ if (parts.length) {
+ returnVal.push(parts.join(separator));
+ }
+
+ return returnVal;
+};
+
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/structs/collection.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/structs/collection.js
new file mode 100644
index 0000000..86d008d
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/structs/collection.js
@@ -0,0 +1,56 @@
+// Copyright 2011 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Defines the collection interface.
+ *
+ * @author nnaze@google.com (Nathan Naze)
+ */
+
+goog.provide('goog.structs.Collection');
+
+
+
+/**
+ * An interface for a collection of values.
+ * @interface
+ * @template T
+ */
+goog.structs.Collection = function() {};
+
+
+/**
+ * @param {T} value Value to add to the collection.
+ */
+goog.structs.Collection.prototype.add;
+
+
+/**
+ * @param {T} value Value to remove from the collection.
+ */
+goog.structs.Collection.prototype.remove;
+
+
+/**
+ * @param {T} value Value to find in the collection.
+ * @return {boolean} Whether the collection contains the specified value.
+ */
+goog.structs.Collection.prototype.contains;
+
+
+/**
+ * @return {number} The number of values stored in the collection.
+ */
+goog.structs.Collection.prototype.getCount;
+
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/structs/map.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/structs/map.js
new file mode 100644
index 0000000..6db80fb
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/structs/map.js
@@ -0,0 +1,461 @@
+// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Datastructure: Hash Map.
+ *
+ * @author arv@google.com (Erik Arvidsson)
+ * @author jonp@google.com (Jon Perlow) Optimized for IE6
+ *
+ * This file contains an implementation of a Map structure. It implements a lot
+ * of the methods used in goog.structs so those functions work on hashes. This
+ * is best suited for complex key types. For simple keys such as numbers and
+ * strings, and where special names like __proto__ are not a concern, consider
+ * using the lighter-weight utilities in goog.object.
+ */
+
+
+goog.provide('goog.structs.Map');
+
+goog.require('goog.iter.Iterator');
+goog.require('goog.iter.StopIteration');
+goog.require('goog.object');
+
+
+
+/**
+ * Class for Hash Map datastructure.
+ * @param {*=} opt_map Map or Object to initialize the map with.
+ * @param {...*} var_args If 2 or more arguments are present then they
+ * will be used as key-value pairs.
+ * @constructor
+ * @template K, V
+ */
+goog.structs.Map = function(opt_map, var_args) {
+
+ /**
+ * Underlying JS object used to implement the map.
+ * @private {!Object}
+ */
+ this.map_ = {};
+
+ /**
+ * An array of keys. This is necessary for two reasons:
+ * 1. Iterating the keys using for (var key in this.map_) allocates an
+ * object for every key in IE which is really bad for IE6 GC perf.
+ * 2. Without a side data structure, we would need to escape all the keys
+ * as that would be the only way we could tell during iteration if the
+ * key was an internal key or a property of the object.
+ *
+ * This array can contain deleted keys so it's necessary to check the map
+ * as well to see if the key is still in the map (this doesn't require a
+ * memory allocation in IE).
+ * @private {!Array.<string>}
+ */
+ this.keys_ = [];
+
+ /**
+ * The number of key value pairs in the map.
+ * @private {number}
+ */
+ this.count_ = 0;
+
+ /**
+ * Version used to detect changes while iterating.
+ * @private {number}
+ */
+ this.version_ = 0;
+
+ var argLength = arguments.length;
+
+ if (argLength > 1) {
+ if (argLength % 2) {
+ throw Error('Uneven number of arguments');
+ }
+ for (var i = 0; i < argLength; i += 2) {
+ this.set(arguments[i], arguments[i + 1]);
+ }
+ } else if (opt_map) {
+ this.addAll(/** @type {Object} */ (opt_map));
+ }
+};
+
+
+/**
+ * @return {number} The number of key-value pairs in the map.
+ */
+goog.structs.Map.prototype.getCount = function() {
+ return this.count_;
+};
+
+
+/**
+ * Returns the values of the map.
+ * @return {!Array.<V>} The values in the map.
+ */
+goog.structs.Map.prototype.getValues = function() {
+ this.cleanupKeysArray_();
+
+ var rv = [];
+ for (var i = 0; i < this.keys_.length; i++) {
+ var key = this.keys_[i];
+ rv.push(this.map_[key]);
+ }
+ return rv;
+};
+
+
+/**
+ * Returns the keys of the map.
+ * @return {!Array.<string>} Array of string values.
+ */
+goog.structs.Map.prototype.getKeys = function() {
+ this.cleanupKeysArray_();
+ return /** @type {!Array.<string>} */ (this.keys_.concat());
+};
+
+
+/**
+ * Whether the map contains the given key.
+ * @param {*} key The key to check for.
+ * @return {boolean} Whether the map contains the key.
+ */
+goog.structs.Map.prototype.containsKey = function(key) {
+ return goog.structs.Map.hasKey_(this.map_, key);
+};
+
+
+/**
+ * Whether the map contains the given value. This is O(n).
+ * @param {V} val The value to check for.
+ * @return {boolean} Whether the map contains the value.
+ */
+goog.structs.Map.prototype.containsValue = function(val) {
+ for (var i = 0; i < this.keys_.length; i++) {
+ var key = this.keys_[i];
+ if (goog.structs.Map.hasKey_(this.map_, key) && this.map_[key] == val) {
+ return true;
+ }
+ }
+ return false;
+};
+
+
+/**
+ * Whether this map is equal to the argument map.
+ * @param {goog.structs.Map} otherMap The map against which to test equality.
+ * @param {function(V, V): boolean=} opt_equalityFn Optional equality function
+ * to test equality of values. If not specified, this will test whether
+ * the values contained in each map are identical objects.
+ * @return {boolean} Whether the maps are equal.
+ */
+goog.structs.Map.prototype.equals = function(otherMap, opt_equalityFn) {
+ if (this === otherMap) {
+ return true;
+ }
+
+ if (this.count_ != otherMap.getCount()) {
+ return false;
+ }
+
+ var equalityFn = opt_equalityFn || goog.structs.Map.defaultEquals;
+
+ this.cleanupKeysArray_();
+ for (var key, i = 0; key = this.keys_[i]; i++) {
+ if (!equalityFn(this.get(key), otherMap.get(key))) {
+ return false;
+ }
+ }
+
+ return true;
+};
+
+
+/**
+ * Default equality test for values.
+ * @param {*} a The first value.
+ * @param {*} b The second value.
+ * @return {boolean} Whether a and b reference the same object.
+ */
+goog.structs.Map.defaultEquals = function(a, b) {
+ return a === b;
+};
+
+
+/**
+ * @return {boolean} Whether the map is empty.
+ */
+goog.structs.Map.prototype.isEmpty = function() {
+ return this.count_ == 0;
+};
+
+
+/**
+ * Removes all key-value pairs from the map.
+ */
+goog.structs.Map.prototype.clear = function() {
+ this.map_ = {};
+ this.keys_.length = 0;
+ this.count_ = 0;
+ this.version_ = 0;
+};
+
+
+/**
+ * Removes a key-value pair based on the key. This is O(logN) amortized due to
+ * updating the keys array whenever the count becomes half the size of the keys
+ * in the keys array.
+ * @param {*} key The key to remove.
+ * @return {boolean} Whether object was removed.
+ */
+goog.structs.Map.prototype.remove = function(key) {
+ if (goog.structs.Map.hasKey_(this.map_, key)) {
+ delete this.map_[key];
+ this.count_--;
+ this.version_++;
+
+ // clean up the keys array if the threshhold is hit
+ if (this.keys_.length > 2 * this.count_) {
+ this.cleanupKeysArray_();
+ }
+
+ return true;
+ }
+ return false;
+};
+
+
+/**
+ * Cleans up the temp keys array by removing entries that are no longer in the
+ * map.
+ * @private
+ */
+goog.structs.Map.prototype.cleanupKeysArray_ = function() {
+ if (this.count_ != this.keys_.length) {
+ // First remove keys that are no longer in the map.
+ var srcIndex = 0;
+ var destIndex = 0;
+ while (srcIndex < this.keys_.length) {
+ var key = this.keys_[srcIndex];
+ if (goog.structs.Map.hasKey_(this.map_, key)) {
+ this.keys_[destIndex++] = key;
+ }
+ srcIndex++;
+ }
+ this.keys_.length = destIndex;
+ }
+
+ if (this.count_ != this.keys_.length) {
+ // If the count still isn't correct, that means we have duplicates. This can
+ // happen when the same key is added and removed multiple times. Now we have
+ // to allocate one extra Object to remove the duplicates. This could have
+ // been done in the first pass, but in the common case, we can avoid
+ // allocating an extra object by only doing this when necessary.
+ var seen = {};
+ var srcIndex = 0;
+ var destIndex = 0;
+ while (srcIndex < this.keys_.length) {
+ var key = this.keys_[srcIndex];
+ if (!(goog.structs.Map.hasKey_(seen, key))) {
+ this.keys_[destIndex++] = key;
+ seen[key] = 1;
+ }
+ srcIndex++;
+ }
+ this.keys_.length = destIndex;
+ }
+};
+
+
+/**
+ * Returns the value for the given key. If the key is not found and the default
+ * value is not given this will return {@code undefined}.
+ * @param {*} key The key to get the value for.
+ * @param {DEFAULT=} opt_val The value to return if no item is found for the
+ * given key, defaults to undefined.
+ * @return {V|DEFAULT} The value for the given key.
+ * @template DEFAULT
+ */
+goog.structs.Map.prototype.get = function(key, opt_val) {
+ if (goog.structs.Map.hasKey_(this.map_, key)) {
+ return this.map_[key];
+ }
+ return opt_val;
+};
+
+
+/**
+ * Adds a key-value pair to the map.
+ * @param {*} key The key.
+ * @param {V} value The value to add.
+ * @return {*} Some subclasses return a value.
+ */
+goog.structs.Map.prototype.set = function(key, value) {
+ if (!(goog.structs.Map.hasKey_(this.map_, key))) {
+ this.count_++;
+ this.keys_.push(key);
+ // Only change the version if we add a new key.
+ this.version_++;
+ }
+ this.map_[key] = value;
+};
+
+
+/**
+ * Adds multiple key-value pairs from another goog.structs.Map or Object.
+ * @param {Object} map Object containing the data to add.
+ */
+goog.structs.Map.prototype.addAll = function(map) {
+ var keys, values;
+ if (map instanceof goog.structs.Map) {
+ keys = map.getKeys();
+ values = map.getValues();
+ } else {
+ keys = goog.object.getKeys(map);
+ values = goog.object.getValues(map);
+ }
+ // we could use goog.array.forEach here but I don't want to introduce that
+ // dependency just for this.
+ for (var i = 0; i < keys.length; i++) {
+ this.set(keys[i], values[i]);
+ }
+};
+
+
+/**
+ * Calls the given function on each entry in the map.
+ * @param {function(this:T, V, K, goog.structs.Map.<K,V>)} f
+ * @param {T=} opt_obj The value of "this" inside f.
+ * @template T
+ */
+goog.structs.Map.prototype.forEach = function(f, opt_obj) {
+ var keys = this.getKeys();
+ for (var i = 0; i < keys.length; i++) {
+ var key = keys[i];
+ var value = this.get(key);
+ f.call(opt_obj, value, key, this);
+ }
+};
+
+
+/**
+ * Clones a map and returns a new map.
+ * @return {!goog.structs.Map} A new map with the same key-value pairs.
+ */
+goog.structs.Map.prototype.clone = function() {
+ return new goog.structs.Map(this);
+};
+
+
+/**
+ * Returns a new map in which all the keys and values are interchanged
+ * (keys become values and values become keys). If multiple keys map to the
+ * same value, the chosen transposed value is implementation-dependent.
+ *
+ * It acts very similarly to {goog.object.transpose(Object)}.
+ *
+ * @return {!goog.structs.Map} The transposed map.
+ */
+goog.structs.Map.prototype.transpose = function() {
+ var transposed = new goog.structs.Map();
+ for (var i = 0; i < this.keys_.length; i++) {
+ var key = this.keys_[i];
+ var value = this.map_[key];
+ transposed.set(value, key);
+ }
+
+ return transposed;
+};
+
+
+/**
+ * @return {!Object} Object representation of the map.
+ */
+goog.structs.Map.prototype.toObject = function() {
+ this.cleanupKeysArray_();
+ var obj = {};
+ for (var i = 0; i < this.keys_.length; i++) {
+ var key = this.keys_[i];
+ obj[key] = this.map_[key];
+ }
+ return obj;
+};
+
+
+/**
+ * Returns an iterator that iterates over the keys in the map. Removal of keys
+ * while iterating might have undesired side effects.
+ * @return {!goog.iter.Iterator} An iterator over the keys in the map.
+ */
+goog.structs.Map.prototype.getKeyIterator = function() {
+ return this.__iterator__(true);
+};
+
+
+/**
+ * Returns an iterator that iterates over the values in the map. Removal of
+ * keys while iterating might have undesired side effects.
+ * @return {!goog.iter.Iterator} An iterator over the values in the map.
+ */
+goog.structs.Map.prototype.getValueIterator = function() {
+ return this.__iterator__(false);
+};
+
+
+/**
+ * Returns an iterator that iterates over the values or the keys in the map.
+ * This throws an exception if the map was mutated since the iterator was
+ * created.
+ * @param {boolean=} opt_keys True to iterate over the keys. False to iterate
+ * over the values. The default value is false.
+ * @return {!goog.iter.Iterator} An iterator over the values or keys in the map.
+ */
+goog.structs.Map.prototype.__iterator__ = function(opt_keys) {
+ // Clean up keys to minimize the risk of iterating over dead keys.
+ this.cleanupKeysArray_();
+
+ var i = 0;
+ var keys = this.keys_;
+ var map = this.map_;
+ var version = this.version_;
+ var selfObj = this;
+
+ var newIter = new goog.iter.Iterator;
+ newIter.next = function() {
+ while (true) {
+ if (version != selfObj.version_) {
+ throw Error('The map has changed since the iterator was created');
+ }
+ if (i >= keys.length) {
+ throw goog.iter.StopIteration;
+ }
+ var key = keys[i++];
+ return opt_keys ? key : map[key];
+ }
+ };
+ return newIter;
+};
+
+
+/**
+ * Safe way to test for hasOwnProperty. It even allows testing for
+ * 'hasOwnProperty'.
+ * @param {Object} obj The object to test for presence of the given key.
+ * @param {*} key The key to check for.
+ * @return {boolean} Whether the object has the key.
+ * @private
+ */
+goog.structs.Map.hasKey_ = function(obj, key) {
+ return Object.prototype.hasOwnProperty.call(obj, key);
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/structs/set.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/structs/set.js
new file mode 100644
index 0000000..8f68298
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/structs/set.js
@@ -0,0 +1,280 @@
+// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Datastructure: Set.
+ *
+ * @author arv@google.com (Erik Arvidsson)
+ * @author pallosp@google.com (Peter Pallos)
+ *
+ * This class implements a set data structure. Adding and removing is O(1). It
+ * supports both object and primitive values. Be careful because you can add
+ * both 1 and new Number(1), because these are not the same. You can even add
+ * multiple new Number(1) because these are not equal.
+ */
+
+
+goog.provide('goog.structs.Set');
+
+goog.require('goog.structs');
+goog.require('goog.structs.Collection');
+goog.require('goog.structs.Map');
+
+
+
+/**
+ * A set that can contain both primitives and objects. Adding and removing
+ * elements is O(1). Primitives are treated as identical if they have the same
+ * type and convert to the same string. Objects are treated as identical only
+ * if they are references to the same object. WARNING: A goog.structs.Set can
+ * contain both 1 and (new Number(1)), because they are not the same. WARNING:
+ * Adding (new Number(1)) twice will yield two distinct elements, because they
+ * are two different objects. WARNING: Any object that is added to a
+ * goog.structs.Set will be modified! Because goog.getUid() is used to
+ * identify objects, every object in the set will be mutated.
+ * @param {Array.<T>|Object.<?,T>=} opt_values Initial values to start with.
+ * @constructor
+ * @implements {goog.structs.Collection.<T>}
+ * @final
+ * @template T
+ */
+goog.structs.Set = function(opt_values) {
+ this.map_ = new goog.structs.Map;
+ if (opt_values) {
+ this.addAll(opt_values);
+ }
+};
+
+
+/**
+ * Obtains a unique key for an element of the set. Primitives will yield the
+ * same key if they have the same type and convert to the same string. Object
+ * references will yield the same key only if they refer to the same object.
+ * @param {*} val Object or primitive value to get a key for.
+ * @return {string} A unique key for this value/object.
+ * @private
+ */
+goog.structs.Set.getKey_ = function(val) {
+ var type = typeof val;
+ if (type == 'object' && val || type == 'function') {
+ return 'o' + goog.getUid(/** @type {Object} */ (val));
+ } else {
+ return type.substr(0, 1) + val;
+ }
+};
+
+
+/**
+ * @return {number} The number of elements in the set.
+ * @override
+ */
+goog.structs.Set.prototype.getCount = function() {
+ return this.map_.getCount();
+};
+
+
+/**
+ * Add a primitive or an object to the set.
+ * @param {T} element The primitive or object to add.
+ * @override
+ */
+goog.structs.Set.prototype.add = function(element) {
+ this.map_.set(goog.structs.Set.getKey_(element), element);
+};
+
+
+/**
+ * Adds all the values in the given collection to this set.
+ * @param {Array.<T>|goog.structs.Collection.<T>|Object.<?,T>} col A collection
+ * containing the elements to add.
+ */
+goog.structs.Set.prototype.addAll = function(col) {
+ var values = goog.structs.getValues(col);
+ var l = values.length;
+ for (var i = 0; i < l; i++) {
+ this.add(values[i]);
+ }
+};
+
+
+/**
+ * Removes all values in the given collection from this set.
+ * @param {Array.<T>|goog.structs.Collection.<T>|Object.<?,T>} col A collection
+ * containing the elements to remove.
+ */
+goog.structs.Set.prototype.removeAll = function(col) {
+ var values = goog.structs.getValues(col);
+ var l = values.length;
+ for (var i = 0; i < l; i++) {
+ this.remove(values[i]);
+ }
+};
+
+
+/**
+ * Removes the given element from this set.
+ * @param {T} element The primitive or object to remove.
+ * @return {boolean} Whether the element was found and removed.
+ * @override
+ */
+goog.structs.Set.prototype.remove = function(element) {
+ return this.map_.remove(goog.structs.Set.getKey_(element));
+};
+
+
+/**
+ * Removes all elements from this set.
+ */
+goog.structs.Set.prototype.clear = function() {
+ this.map_.clear();
+};
+
+
+/**
+ * Tests whether this set is empty.
+ * @return {boolean} True if there are no elements in this set.
+ */
+goog.structs.Set.prototype.isEmpty = function() {
+ return this.map_.isEmpty();
+};
+
+
+/**
+ * Tests whether this set contains the given element.
+ * @param {T} element The primitive or object to test for.
+ * @return {boolean} True if this set contains the given element.
+ * @override
+ */
+goog.structs.Set.prototype.contains = function(element) {
+ return this.map_.containsKey(goog.structs.Set.getKey_(element));
+};
+
+
+/**
+ * Tests whether this set contains all the values in a given collection.
+ * Repeated elements in the collection are ignored, e.g. (new
+ * goog.structs.Set([1, 2])).containsAll([1, 1]) is True.
+ * @param {goog.structs.Collection.<T>|Object} col A collection-like object.
+ * @return {boolean} True if the set contains all elements.
+ */
+goog.structs.Set.prototype.containsAll = function(col) {
+ return goog.structs.every(col, this.contains, this);
+};
+
+
+/**
+ * Finds all values that are present in both this set and the given collection.
+ * @param {Array.<S>|Object.<?,S>} col A collection.
+ * @return {!goog.structs.Set.<T|S>} A new set containing all the values
+ * (primitives or objects) present in both this set and the given
+ * collection.
+ * @template S
+ */
+goog.structs.Set.prototype.intersection = function(col) {
+ var result = new goog.structs.Set();
+
+ var values = goog.structs.getValues(col);
+ for (var i = 0; i < values.length; i++) {
+ var value = values[i];
+ if (this.contains(value)) {
+ result.add(value);
+ }
+ }
+
+ return result;
+};
+
+
+/**
+ * Finds all values that are present in this set and not in the given
+ * collection.
+ * @param {Array.<T>|goog.structs.Collection.<T>|Object.<?,T>} col A collection.
+ * @return {!goog.structs.Set} A new set containing all the values
+ * (primitives or objects) present in this set but not in the given
+ * collection.
+ */
+goog.structs.Set.prototype.difference = function(col) {
+ var result = this.clone();
+ result.removeAll(col);
+ return result;
+};
+
+
+/**
+ * Returns an array containing all the elements in this set.
+ * @return {!Array.<T>} An array containing all the elements in this set.
+ */
+goog.structs.Set.prototype.getValues = function() {
+ return this.map_.getValues();
+};
+
+
+/**
+ * Creates a shallow clone of this set.
+ * @return {!goog.structs.Set.<T>} A new set containing all the same elements as
+ * this set.
+ */
+goog.structs.Set.prototype.clone = function() {
+ return new goog.structs.Set(this);
+};
+
+
+/**
+ * Tests whether the given collection consists of the same elements as this set,
+ * regardless of order, without repetition. Primitives are treated as equal if
+ * they have the same type and convert to the same string; objects are treated
+ * as equal if they are references to the same object. This operation is O(n).
+ * @param {goog.structs.Collection.<T>|Object} col A collection.
+ * @return {boolean} True if the given collection consists of the same elements
+ * as this set, regardless of order, without repetition.
+ */
+goog.structs.Set.prototype.equals = function(col) {
+ return this.getCount() == goog.structs.getCount(col) && this.isSubsetOf(col);
+};
+
+
+/**
+ * Tests whether the given collection contains all the elements in this set.
+ * Primitives are treated as equal if they have the same type and convert to the
+ * same string; objects are treated as equal if they are references to the same
+ * object. This operation is O(n).
+ * @param {goog.structs.Collection.<T>|Object} col A collection.
+ * @return {boolean} True if this set is a subset of the given collection.
+ */
+goog.structs.Set.prototype.isSubsetOf = function(col) {
+ var colCount = goog.structs.getCount(col);
+ if (this.getCount() > colCount) {
+ return false;
+ }
+ // TODO(user) Find the minimal collection size where the conversion makes
+ // the contains() method faster.
+ if (!(col instanceof goog.structs.Set) && colCount > 5) {
+ // Convert to a goog.structs.Set so that goog.structs.contains runs in
+ // O(1) time instead of O(n) time.
+ col = new goog.structs.Set(col);
+ }
+ return goog.structs.every(this, function(value) {
+ return goog.structs.contains(col, value);
+ });
+};
+
+
+/**
+ * Returns an iterator that iterates over the elements in this set.
+ * @param {boolean=} opt_keys This argument is ignored.
+ * @return {!goog.iter.Iterator} An iterator over the elements in this set.
+ */
+goog.structs.Set.prototype.__iterator__ = function(opt_keys) {
+ return this.map_.__iterator__(false);
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/structs/structs.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/structs/structs.js
new file mode 100644
index 0000000..edf9359
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/structs/structs.js
@@ -0,0 +1,354 @@
+// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Generics method for collection-like classes and objects.
+ *
+ * @author arv@google.com (Erik Arvidsson)
+ *
+ * This file contains functions to work with collections. It supports using
+ * Map, Set, Array and Object and other classes that implement collection-like
+ * methods.
+ */
+
+
+goog.provide('goog.structs');
+
+goog.require('goog.array');
+goog.require('goog.object');
+
+
+// We treat an object as a dictionary if it has getKeys or it is an object that
+// isn't arrayLike.
+
+
+/**
+ * Returns the number of values in the collection-like object.
+ * @param {Object} col The collection-like object.
+ * @return {number} The number of values in the collection-like object.
+ */
+goog.structs.getCount = function(col) {
+ if (typeof col.getCount == 'function') {
+ return col.getCount();
+ }
+ if (goog.isArrayLike(col) || goog.isString(col)) {
+ return col.length;
+ }
+ return goog.object.getCount(col);
+};
+
+
+/**
+ * Returns the values of the collection-like object.
+ * @param {Object} col The collection-like object.
+ * @return {!Array} The values in the collection-like object.
+ */
+goog.structs.getValues = function(col) {
+ if (typeof col.getValues == 'function') {
+ return col.getValues();
+ }
+ if (goog.isString(col)) {
+ return col.split('');
+ }
+ if (goog.isArrayLike(col)) {
+ var rv = [];
+ var l = col.length;
+ for (var i = 0; i < l; i++) {
+ rv.push(col[i]);
+ }
+ return rv;
+ }
+ return goog.object.getValues(col);
+};
+
+
+/**
+ * Returns the keys of the collection. Some collections have no notion of
+ * keys/indexes and this function will return undefined in those cases.
+ * @param {Object} col The collection-like object.
+ * @return {!Array|undefined} The keys in the collection.
+ */
+goog.structs.getKeys = function(col) {
+ if (typeof col.getKeys == 'function') {
+ return col.getKeys();
+ }
+ // if we have getValues but no getKeys we know this is a key-less collection
+ if (typeof col.getValues == 'function') {
+ return undefined;
+ }
+ if (goog.isArrayLike(col) || goog.isString(col)) {
+ var rv = [];
+ var l = col.length;
+ for (var i = 0; i < l; i++) {
+ rv.push(i);
+ }
+ return rv;
+ }
+
+ return goog.object.getKeys(col);
+};
+
+
+/**
+ * Whether the collection contains the given value. This is O(n) and uses
+ * equals (==) to test the existence.
+ * @param {Object} col The collection-like object.
+ * @param {*} val The value to check for.
+ * @return {boolean} True if the map contains the value.
+ */
+goog.structs.contains = function(col, val) {
+ if (typeof col.contains == 'function') {
+ return col.contains(val);
+ }
+ if (typeof col.containsValue == 'function') {
+ return col.containsValue(val);
+ }
+ if (goog.isArrayLike(col) || goog.isString(col)) {
+ return goog.array.contains(/** @type {Array} */ (col), val);
+ }
+ return goog.object.containsValue(col, val);
+};
+
+
+/**
+ * Whether the collection is empty.
+ * @param {Object} col The collection-like object.
+ * @return {boolean} True if empty.
+ */
+goog.structs.isEmpty = function(col) {
+ if (typeof col.isEmpty == 'function') {
+ return col.isEmpty();
+ }
+
+ // We do not use goog.string.isEmpty because here we treat the string as
+ // collection and as such even whitespace matters
+
+ if (goog.isArrayLike(col) || goog.isString(col)) {
+ return goog.array.isEmpty(/** @type {Array} */ (col));
+ }
+ return goog.object.isEmpty(col);
+};
+
+
+/**
+ * Removes all the elements from the collection.
+ * @param {Object} col The collection-like object.
+ */
+goog.structs.clear = function(col) {
+ // NOTE(arv): This should not contain strings because strings are immutable
+ if (typeof col.clear == 'function') {
+ col.clear();
+ } else if (goog.isArrayLike(col)) {
+ goog.array.clear(/** @type {goog.array.ArrayLike} */ (col));
+ } else {
+ goog.object.clear(col);
+ }
+};
+
+
+/**
+ * Calls a function for each value in a collection. The function takes
+ * three arguments; the value, the key and the collection.
+ *
+ * NOTE: This will be deprecated soon! Please use a more specific method if
+ * possible, e.g. goog.array.forEach, goog.object.forEach, etc.
+ *
+ * @param {S} col The collection-like object.
+ * @param {function(this:T,?,?,S):?} f The function to call for every value.
+ * This function takes
+ * 3 arguments (the value, the key or undefined if the collection has no
+ * notion of keys, and the collection) and the return value is irrelevant.
+ * @param {T=} opt_obj The object to be used as the value of 'this'
+ * within {@code f}.
+ * @template T,S
+ */
+goog.structs.forEach = function(col, f, opt_obj) {
+ if (typeof col.forEach == 'function') {
+ col.forEach(f, opt_obj);
+ } else if (goog.isArrayLike(col) || goog.isString(col)) {
+ goog.array.forEach(/** @type {Array} */ (col), f, opt_obj);
+ } else {
+ var keys = goog.structs.getKeys(col);
+ var values = goog.structs.getValues(col);
+ var l = values.length;
+ for (var i = 0; i < l; i++) {
+ f.call(opt_obj, values[i], keys && keys[i], col);
+ }
+ }
+};
+
+
+/**
+ * Calls a function for every value in the collection. When a call returns true,
+ * adds the value to a new collection (Array is returned by default).
+ *
+ * @param {S} col The collection-like object.
+ * @param {function(this:T,?,?,S):boolean} f The function to call for every
+ * value. This function takes
+ * 3 arguments (the value, the key or undefined if the collection has no
+ * notion of keys, and the collection) and should return a Boolean. If the
+ * return value is true the value is added to the result collection. If it
+ * is false the value is not included.
+ * @param {T=} opt_obj The object to be used as the value of 'this'
+ * within {@code f}.
+ * @return {!Object|!Array} A new collection where the passed values are
+ * present. If col is a key-less collection an array is returned. If col
+ * has keys and values a plain old JS object is returned.
+ * @template T,S
+ */
+goog.structs.filter = function(col, f, opt_obj) {
+ if (typeof col.filter == 'function') {
+ return col.filter(f, opt_obj);
+ }
+ if (goog.isArrayLike(col) || goog.isString(col)) {
+ return goog.array.filter(/** @type {!Array} */ (col), f, opt_obj);
+ }
+
+ var rv;
+ var keys = goog.structs.getKeys(col);
+ var values = goog.structs.getValues(col);
+ var l = values.length;
+ if (keys) {
+ rv = {};
+ for (var i = 0; i < l; i++) {
+ if (f.call(opt_obj, values[i], keys[i], col)) {
+ rv[keys[i]] = values[i];
+ }
+ }
+ } else {
+ // We should not use goog.array.filter here since we want to make sure that
+ // the index is undefined as well as make sure that col is passed to the
+ // function.
+ rv = [];
+ for (var i = 0; i < l; i++) {
+ if (f.call(opt_obj, values[i], undefined, col)) {
+ rv.push(values[i]);
+ }
+ }
+ }
+ return rv;
+};
+
+
+/**
+ * Calls a function for every value in the collection and adds the result into a
+ * new collection (defaults to creating a new Array).
+ *
+ * @param {S} col The collection-like object.
+ * @param {function(this:T,?,?,S):V} f The function to call for every value.
+ * This function takes 3 arguments (the value, the key or undefined if the
+ * collection has no notion of keys, and the collection) and should return
+ * something. The result will be used as the value in the new collection.
+ * @param {T=} opt_obj The object to be used as the value of 'this'
+ * within {@code f}.
+ * @return {!Object.<V>|!Array.<V>} A new collection with the new values. If
+ * col is a key-less collection an array is returned. If col has keys and
+ * values a plain old JS object is returned.
+ * @template T,S,V
+ */
+goog.structs.map = function(col, f, opt_obj) {
+ if (typeof col.map == 'function') {
+ return col.map(f, opt_obj);
+ }
+ if (goog.isArrayLike(col) || goog.isString(col)) {
+ return goog.array.map(/** @type {!Array} */ (col), f, opt_obj);
+ }
+
+ var rv;
+ var keys = goog.structs.getKeys(col);
+ var values = goog.structs.getValues(col);
+ var l = values.length;
+ if (keys) {
+ rv = {};
+ for (var i = 0; i < l; i++) {
+ rv[keys[i]] = f.call(opt_obj, values[i], keys[i], col);
+ }
+ } else {
+ // We should not use goog.array.map here since we want to make sure that
+ // the index is undefined as well as make sure that col is passed to the
+ // function.
+ rv = [];
+ for (var i = 0; i < l; i++) {
+ rv[i] = f.call(opt_obj, values[i], undefined, col);
+ }
+ }
+ return rv;
+};
+
+
+/**
+ * Calls f for each value in a collection. If any call returns true this returns
+ * true (without checking the rest). If all returns false this returns false.
+ *
+ * @param {S} col The collection-like object.
+ * @param {function(this:T,?,?,S):boolean} f The function to call for every
+ * value. This function takes 3 arguments (the value, the key or undefined
+ * if the collection has no notion of keys, and the collection) and should
+ * return a boolean.
+ * @param {T=} opt_obj The object to be used as the value of 'this'
+ * within {@code f}.
+ * @return {boolean} True if any value passes the test.
+ * @template T,S
+ */
+goog.structs.some = function(col, f, opt_obj) {
+ if (typeof col.some == 'function') {
+ return col.some(f, opt_obj);
+ }
+ if (goog.isArrayLike(col) || goog.isString(col)) {
+ return goog.array.some(/** @type {!Array} */ (col), f, opt_obj);
+ }
+ var keys = goog.structs.getKeys(col);
+ var values = goog.structs.getValues(col);
+ var l = values.length;
+ for (var i = 0; i < l; i++) {
+ if (f.call(opt_obj, values[i], keys && keys[i], col)) {
+ return true;
+ }
+ }
+ return false;
+};
+
+
+/**
+ * Calls f for each value in a collection. If all calls return true this return
+ * true this returns true. If any returns false this returns false at this point
+ * and does not continue to check the remaining values.
+ *
+ * @param {S} col The collection-like object.
+ * @param {function(this:T,?,?,S):boolean} f The function to call for every
+ * value. This function takes 3 arguments (the value, the key or
+ * undefined if the collection has no notion of keys, and the collection)
+ * and should return a boolean.
+ * @param {T=} opt_obj The object to be used as the value of 'this'
+ * within {@code f}.
+ * @return {boolean} True if all key-value pairs pass the test.
+ * @template T,S
+ */
+goog.structs.every = function(col, f, opt_obj) {
+ if (typeof col.every == 'function') {
+ return col.every(f, opt_obj);
+ }
+ if (goog.isArrayLike(col) || goog.isString(col)) {
+ return goog.array.every(/** @type {!Array} */ (col), f, opt_obj);
+ }
+ var keys = goog.structs.getKeys(col);
+ var values = goog.structs.getValues(col);
+ var l = values.length;
+ for (var i = 0; i < l; i++) {
+ if (!f.call(opt_obj, values[i], keys && keys[i], col)) {
+ return false;
+ }
+ }
+ return true;
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/style/bidi.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/style/bidi.js
new file mode 100644
index 0000000..2d5c7c5
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/style/bidi.js
@@ -0,0 +1,184 @@
+// Copyright 2012 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Bidi utility functions.
+ *
+ */
+
+goog.provide('goog.style.bidi');
+
+goog.require('goog.dom');
+goog.require('goog.style');
+goog.require('goog.userAgent');
+
+
+/**
+ * Returns the normalized scrollLeft position for a scrolled element.
+ * @param {Element} element The scrolled element.
+ * @return {number} The number of pixels the element is scrolled. 0 indicates
+ * that the element is not scrolled at all (which, in general, is the
+ * left-most position in ltr and the right-most position in rtl).
+ */
+goog.style.bidi.getScrollLeft = function(element) {
+ var isRtl = goog.style.isRightToLeft(element);
+ if (isRtl && goog.userAgent.GECKO) {
+ // ScrollLeft starts at 0 and then goes negative as the element is scrolled
+ // towards the left.
+ return -element.scrollLeft;
+ } else if (isRtl &&
+ !(goog.userAgent.IE && goog.userAgent.isVersionOrHigher('8'))) {
+ // ScrollLeft starts at the maximum positive value and decreases towards
+ // 0 as the element is scrolled towards the left. However, for overflow
+ // visible, there is no scrollLeft and the value always stays correctly at 0
+ var overflowX = goog.style.getComputedOverflowX(element);
+ if (overflowX == 'visible') {
+ return element.scrollLeft;
+ } else {
+ return element.scrollWidth - element.clientWidth - element.scrollLeft;
+ }
+ }
+ // ScrollLeft behavior is identical in rtl and ltr, it starts at 0 and
+ // increases as the element is scrolled away from the start.
+ return element.scrollLeft;
+};
+
+
+/**
+ * Returns the "offsetStart" of an element, analagous to offsetLeft but
+ * normalized for right-to-left environments and various browser
+ * inconsistencies. This value returned can always be passed to setScrollOffset
+ * to scroll to an element's left edge in a left-to-right offsetParent or
+ * right edge in a right-to-left offsetParent.
+ *
+ * For example, here offsetStart is 10px in an LTR environment and 5px in RTL:
+ *
+ * <pre>
+ * | xxxxxxxxxx |
+ * ^^^^^^^^^^ ^^^^ ^^^^^
+ * 10px elem 5px
+ * </pre>
+ *
+ * If an element is positioned before the start of its offsetParent, the
+ * startOffset may be negative. This can be used with setScrollOffset to
+ * reliably scroll to an element:
+ *
+ * <pre>
+ * var scrollOffset = goog.style.bidi.getOffsetStart(element);
+ * goog.style.bidi.setScrollOffset(element.offsetParent, scrollOffset);
+ * </pre>
+ *
+ * @see setScrollOffset
+ *
+ * @param {Element} element The element for which we need to determine the
+ * offsetStart position.
+ * @return {number} The offsetStart for that element.
+ */
+goog.style.bidi.getOffsetStart = function(element) {
+ var offsetLeftForReal = element.offsetLeft;
+
+ // The element might not have an offsetParent.
+ // For example, the node might not be attached to the DOM tree,
+ // and position:fixed children do not have an offset parent.
+ // Just try to do the best we can with what we have.
+ var bestParent = element.offsetParent;
+
+ if (!bestParent && goog.style.getComputedPosition(element) == 'fixed') {
+ bestParent = goog.dom.getOwnerDocument(element).documentElement;
+ }
+
+ // Just give up in this case.
+ if (!bestParent) {
+ return offsetLeftForReal;
+ }
+
+ if (goog.userAgent.GECKO) {
+ // When calculating an element's offsetLeft, Firefox erroneously subtracts
+ // the border width from the actual distance. So we need to add it back.
+ var borderWidths = goog.style.getBorderBox(bestParent);
+ offsetLeftForReal += borderWidths.left;
+ } else if (goog.userAgent.isDocumentModeOrHigher(8) &&
+ !goog.userAgent.isDocumentModeOrHigher(9)) {
+ // When calculating an element's offsetLeft, IE8/9-Standards Mode
+ // erroneously adds the border width to the actual distance. So we need to
+ // subtract it.
+ var borderWidths = goog.style.getBorderBox(bestParent);
+ offsetLeftForReal -= borderWidths.left;
+ }
+
+ if (goog.style.isRightToLeft(bestParent)) {
+ // Right edge of the element relative to the left edge of its parent.
+ var elementRightOffset = offsetLeftForReal + element.offsetWidth;
+
+ // Distance from the parent's right edge to the element's right edge.
+ return bestParent.clientWidth - elementRightOffset;
+ }
+
+ return offsetLeftForReal;
+};
+
+
+/**
+ * Sets the element's scrollLeft attribute so it is correctly scrolled by
+ * offsetStart pixels. This takes into account whether the element is RTL and
+ * the nuances of different browsers. To scroll to the "beginning" of an
+ * element use getOffsetStart to obtain the element's offsetStart value and then
+ * pass the value to setScrollOffset.
+ * @see getOffsetStart
+ * @param {Element} element The element to set scrollLeft on.
+ * @param {number} offsetStart The number of pixels to scroll the element.
+ * If this value is < 0, 0 is used.
+ */
+goog.style.bidi.setScrollOffset = function(element, offsetStart) {
+ offsetStart = Math.max(offsetStart, 0);
+ // In LTR and in "mirrored" browser RTL (such as IE), we set scrollLeft to
+ // the number of pixels to scroll.
+ // Otherwise, in RTL, we need to account for different browser behavior.
+ if (!goog.style.isRightToLeft(element)) {
+ element.scrollLeft = offsetStart;
+ } else if (goog.userAgent.GECKO) {
+ // Negative scroll-left positions in RTL.
+ element.scrollLeft = -offsetStart;
+ } else if (!(goog.userAgent.IE && goog.userAgent.isVersionOrHigher('8'))) {
+ // Take the current scrollLeft value and move to the right by the
+ // offsetStart to get to the left edge of the element, and then by
+ // the clientWidth of the element to get to the right edge.
+ element.scrollLeft =
+ element.scrollWidth - offsetStart - element.clientWidth;
+ } else {
+ element.scrollLeft = offsetStart;
+ }
+};
+
+
+/**
+ * Sets the element's left style attribute in LTR or right style attribute in
+ * RTL. Also clears the left attribute in RTL and the right attribute in LTR.
+ * @param {Element} elem The element to position.
+ * @param {number} left The left position in LTR; will be set as right in RTL.
+ * @param {?number} top The top position. If null only the left/right is set.
+ * @param {boolean} isRtl Whether we are in RTL mode.
+ */
+goog.style.bidi.setPosition = function(elem, left, top, isRtl) {
+ if (!goog.isNull(top)) {
+ elem.style.top = top + 'px';
+ }
+ if (isRtl) {
+ elem.style.right = left + 'px';
+ elem.style.left = '';
+ } else {
+ elem.style.left = left + 'px';
+ elem.style.right = '';
+ }
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/style/style.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/style/style.js
new file mode 100644
index 0000000..2b5baac
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/style/style.js
@@ -0,0 +1,2105 @@
+// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Utilities for element styles.
+ *
+ * @see ../demos/inline_block_quirks.html
+ * @see ../demos/inline_block_standards.html
+ * @see ../demos/style_viewport.html
+ */
+
+goog.provide('goog.style');
+
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.dom');
+goog.require('goog.dom.NodeType');
+goog.require('goog.dom.vendor');
+goog.require('goog.math.Box');
+goog.require('goog.math.Coordinate');
+goog.require('goog.math.Rect');
+goog.require('goog.math.Size');
+goog.require('goog.object');
+goog.require('goog.string');
+goog.require('goog.userAgent');
+
+
+/**
+ * @define {boolean} Whether we know at compile time that
+ * getBoundingClientRect() is present and bug-free on the browser.
+ */
+goog.define('goog.style.GET_BOUNDING_CLIENT_RECT_ALWAYS_EXISTS', false);
+
+
+/**
+ * Sets a style value on an element.
+ *
+ * This function is not indended to patch issues in the browser's style
+ * handling, but to allow easy programmatic access to setting dash-separated
+ * style properties. An example is setting a batch of properties from a data
+ * object without overwriting old styles. When possible, use native APIs:
+ * elem.style.propertyKey = 'value' or (if obliterating old styles is fine)
+ * elem.style.cssText = 'property1: value1; property2: value2'.
+ *
+ * @param {Element} element The element to change.
+ * @param {string|Object} style If a string, a style name. If an object, a hash
+ * of style names to style values.
+ * @param {string|number|boolean=} opt_value If style was a string, then this
+ * should be the value.
+ */
+goog.style.setStyle = function(element, style, opt_value) {
+ if (goog.isString(style)) {
+ goog.style.setStyle_(element, opt_value, style);
+ } else {
+ goog.object.forEach(style, goog.partial(goog.style.setStyle_, element));
+ }
+};
+
+
+/**
+ * Sets a style value on an element, with parameters swapped to work with
+ * {@code goog.object.forEach()}. Prepends a vendor-specific prefix when
+ * necessary.
+ * @param {Element} element The element to change.
+ * @param {string|number|boolean|undefined} value Style value.
+ * @param {string} style Style name.
+ * @private
+ */
+goog.style.setStyle_ = function(element, value, style) {
+ var propertyName = goog.style.getVendorJsStyleName_(element, style);
+
+ if (propertyName) {
+ element.style[propertyName] = value;
+ }
+};
+
+
+/**
+ * Returns the style property name in camel-case. If it does not exist and a
+ * vendor-specific version of the property does exist, then return the vendor-
+ * specific property name instead.
+ * @param {Element} element The element to change.
+ * @param {string} style Style name.
+ * @return {string} Vendor-specific style.
+ * @private
+ */
+goog.style.getVendorJsStyleName_ = function(element, style) {
+ var camelStyle = goog.string.toCamelCase(style);
+
+ if (element.style[camelStyle] === undefined) {
+ var prefixedStyle = goog.dom.vendor.getVendorJsPrefix() +
+ goog.string.toTitleCase(camelStyle);
+
+ if (element.style[prefixedStyle] !== undefined) {
+ return prefixedStyle;
+ }
+ }
+
+ return camelStyle;
+};
+
+
+/**
+ * Returns the style property name in CSS notation. If it does not exist and a
+ * vendor-specific version of the property does exist, then return the vendor-
+ * specific property name instead.
+ * @param {Element} element The element to change.
+ * @param {string} style Style name.
+ * @return {string} Vendor-specific style.
+ * @private
+ */
+goog.style.getVendorStyleName_ = function(element, style) {
+ var camelStyle = goog.string.toCamelCase(style);
+
+ if (element.style[camelStyle] === undefined) {
+ var prefixedStyle = goog.dom.vendor.getVendorJsPrefix() +
+ goog.string.toTitleCase(camelStyle);
+
+ if (element.style[prefixedStyle] !== undefined) {
+ return goog.dom.vendor.getVendorPrefix() + '-' + style;
+ }
+ }
+
+ return style;
+};
+
+
+/**
+ * Retrieves an explicitly-set style value of a node. This returns '' if there
+ * isn't a style attribute on the element or if this style property has not been
+ * explicitly set in script.
+ *
+ * @param {Element} element Element to get style of.
+ * @param {string} property Property to get, css-style (if you have a camel-case
+ * property, use element.style[style]).
+ * @return {string} Style value.
+ */
+goog.style.getStyle = function(element, property) {
+ // element.style is '' for well-known properties which are unset.
+ // For for browser specific styles as 'filter' is undefined
+ // so we need to return '' explicitly to make it consistent across
+ // browsers.
+ var styleValue = element.style[goog.string.toCamelCase(property)];
+
+ // Using typeof here because of a bug in Safari 5.1, where this value
+ // was undefined, but === undefined returned false.
+ if (typeof(styleValue) !== 'undefined') {
+ return styleValue;
+ }
+
+ return element.style[goog.style.getVendorJsStyleName_(element, property)] ||
+ '';
+};
+
+
+/**
+ * Retrieves a computed style value of a node. It returns empty string if the
+ * value cannot be computed (which will be the case in Internet Explorer) or
+ * "none" if the property requested is an SVG one and it has not been
+ * explicitly set (firefox and webkit).
+ *
+ * @param {Element} element Element to get style of.
+ * @param {string} property Property to get (camel-case).
+ * @return {string} Style value.
+ */
+goog.style.getComputedStyle = function(element, property) {
+ var doc = goog.dom.getOwnerDocument(element);
+ if (doc.defaultView && doc.defaultView.getComputedStyle) {
+ var styles = doc.defaultView.getComputedStyle(element, null);
+ if (styles) {
+ // element.style[..] is undefined for browser specific styles
+ // as 'filter'.
+ return styles[property] || styles.getPropertyValue(property) || '';
+ }
+ }
+
+ return '';
+};
+
+
+/**
+ * Gets the cascaded style value of a node, or null if the value cannot be
+ * computed (only Internet Explorer can do this).
+ *
+ * @param {Element} element Element to get style of.
+ * @param {string} style Property to get (camel-case).
+ * @return {string} Style value.
+ */
+goog.style.getCascadedStyle = function(element, style) {
+ // TODO(nicksantos): This should be documented to return null. #fixTypes
+ return element.currentStyle ? element.currentStyle[style] : null;
+};
+
+
+/**
+ * Cross-browser pseudo get computed style. It returns the computed style where
+ * available. If not available it tries the cascaded style value (IE
+ * currentStyle) and in worst case the inline style value. It shouldn't be
+ * called directly, see http://wiki/Main/ComputedStyleVsCascadedStyle for
+ * discussion.
+ *
+ * @param {Element} element Element to get style of.
+ * @param {string} style Property to get (must be camelCase, not css-style.).
+ * @return {string} Style value.
+ * @private
+ */
+goog.style.getStyle_ = function(element, style) {
+ return goog.style.getComputedStyle(element, style) ||
+ goog.style.getCascadedStyle(element, style) ||
+ (element.style && element.style[style]);
+};
+
+
+/**
+ * Retrieves the computed value of the box-sizing CSS attribute.
+ * Browser support: http://caniuse.com/css3-boxsizing.
+ * @param {!Element} element The element whose box-sizing to get.
+ * @return {?string} 'content-box', 'border-box' or 'padding-box'. null if
+ * box-sizing is not supported (IE7 and below).
+ */
+goog.style.getComputedBoxSizing = function(element) {
+ return goog.style.getStyle_(element, 'boxSizing') ||
+ goog.style.getStyle_(element, 'MozBoxSizing') ||
+ goog.style.getStyle_(element, 'WebkitBoxSizing') || null;
+};
+
+
+/**
+ * Retrieves the computed value of the position CSS attribute.
+ * @param {Element} element The element to get the position of.
+ * @return {string} Position value.
+ */
+goog.style.getComputedPosition = function(element) {
+ return goog.style.getStyle_(element, 'position');
+};
+
+
+/**
+ * Retrieves the computed background color string for a given element. The
+ * string returned is suitable for assigning to another element's
+ * background-color, but is not guaranteed to be in any particular string
+ * format. Accessing the color in a numeric form may not be possible in all
+ * browsers or with all input.
+ *
+ * If the background color for the element is defined as a hexadecimal value,
+ * the resulting string can be parsed by goog.color.parse in all supported
+ * browsers.
+ *
+ * Whether named colors like "red" or "lightblue" get translated into a
+ * format which can be parsed is browser dependent. Calling this function on
+ * transparent elements will return "transparent" in most browsers or
+ * "rgba(0, 0, 0, 0)" in WebKit.
+ * @param {Element} element The element to get the background color of.
+ * @return {string} The computed string value of the background color.
+ */
+goog.style.getBackgroundColor = function(element) {
+ return goog.style.getStyle_(element, 'backgroundColor');
+};
+
+
+/**
+ * Retrieves the computed value of the overflow-x CSS attribute.
+ * @param {Element} element The element to get the overflow-x of.
+ * @return {string} The computed string value of the overflow-x attribute.
+ */
+goog.style.getComputedOverflowX = function(element) {
+ return goog.style.getStyle_(element, 'overflowX');
+};
+
+
+/**
+ * Retrieves the computed value of the overflow-y CSS attribute.
+ * @param {Element} element The element to get the overflow-y of.
+ * @return {string} The computed string value of the overflow-y attribute.
+ */
+goog.style.getComputedOverflowY = function(element) {
+ return goog.style.getStyle_(element, 'overflowY');
+};
+
+
+/**
+ * Retrieves the computed value of the z-index CSS attribute.
+ * @param {Element} element The element to get the z-index of.
+ * @return {string|number} The computed value of the z-index attribute.
+ */
+goog.style.getComputedZIndex = function(element) {
+ return goog.style.getStyle_(element, 'zIndex');
+};
+
+
+/**
+ * Retrieves the computed value of the text-align CSS attribute.
+ * @param {Element} element The element to get the text-align of.
+ * @return {string} The computed string value of the text-align attribute.
+ */
+goog.style.getComputedTextAlign = function(element) {
+ return goog.style.getStyle_(element, 'textAlign');
+};
+
+
+/**
+ * Retrieves the computed value of the cursor CSS attribute.
+ * @param {Element} element The element to get the cursor of.
+ * @return {string} The computed string value of the cursor attribute.
+ */
+goog.style.getComputedCursor = function(element) {
+ return goog.style.getStyle_(element, 'cursor');
+};
+
+
+/**
+ * Retrieves the computed value of the CSS transform attribute.
+ * @param {Element} element The element to get the transform of.
+ * @return {string} The computed string representation of the transform matrix.
+ */
+goog.style.getComputedTransform = function(element) {
+ var property = goog.style.getVendorStyleName_(element, 'transform');
+ return goog.style.getStyle_(element, property) ||
+ goog.style.getStyle_(element, 'transform');
+};
+
+
+/**
+ * Sets the top/left values of an element. If no unit is specified in the
+ * argument then it will add px. The second argument is required if the first
+ * argument is a string or number and is ignored if the first argument
+ * is a coordinate.
+ * @param {Element} el Element to move.
+ * @param {string|number|goog.math.Coordinate} arg1 Left position or coordinate.
+ * @param {string|number=} opt_arg2 Top position.
+ */
+goog.style.setPosition = function(el, arg1, opt_arg2) {
+ var x, y;
+ var buggyGeckoSubPixelPos = goog.userAgent.GECKO &&
+ (goog.userAgent.MAC || goog.userAgent.X11) &&
+ goog.userAgent.isVersionOrHigher('1.9');
+
+ if (arg1 instanceof goog.math.Coordinate) {
+ x = arg1.x;
+ y = arg1.y;
+ } else {
+ x = arg1;
+ y = opt_arg2;
+ }
+
+ // Round to the nearest pixel for buggy sub-pixel support.
+ el.style.left = goog.style.getPixelStyleValue_(
+ /** @type {number|string} */ (x), buggyGeckoSubPixelPos);
+ el.style.top = goog.style.getPixelStyleValue_(
+ /** @type {number|string} */ (y), buggyGeckoSubPixelPos);
+};
+
+
+/**
+ * Gets the offsetLeft and offsetTop properties of an element and returns them
+ * in a Coordinate object
+ * @param {Element} element Element.
+ * @return {!goog.math.Coordinate} The position.
+ */
+goog.style.getPosition = function(element) {
+ return new goog.math.Coordinate(element.offsetLeft, element.offsetTop);
+};
+
+
+/**
+ * Returns the viewport element for a particular document
+ * @param {Node=} opt_node DOM node (Document is OK) to get the viewport element
+ * of.
+ * @return {Element} document.documentElement or document.body.
+ */
+goog.style.getClientViewportElement = function(opt_node) {
+ var doc;
+ if (opt_node) {
+ doc = goog.dom.getOwnerDocument(opt_node);
+ } else {
+ doc = goog.dom.getDocument();
+ }
+
+ // In old IE versions the document.body represented the viewport
+ if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9) &&
+ !goog.dom.getDomHelper(doc).isCss1CompatMode()) {
+ return doc.body;
+ }
+ return doc.documentElement;
+};
+
+
+/**
+ * Calculates the viewport coordinates relative to the page/document
+ * containing the node. The viewport may be the browser viewport for
+ * non-iframe document, or the iframe container for iframe'd document.
+ * @param {!Document} doc The document to use as the reference point.
+ * @return {!goog.math.Coordinate} The page offset of the viewport.
+ */
+goog.style.getViewportPageOffset = function(doc) {
+ var body = doc.body;
+ var documentElement = doc.documentElement;
+ var scrollLeft = body.scrollLeft || documentElement.scrollLeft;
+ var scrollTop = body.scrollTop || documentElement.scrollTop;
+ return new goog.math.Coordinate(scrollLeft, scrollTop);
+};
+
+
+/**
+ * Gets the client rectangle of the DOM element.
+ *
+ * getBoundingClientRect is part of a new CSS object model draft (with a
+ * long-time presence in IE), replacing the error-prone parent offset
+ * computation and the now-deprecated Gecko getBoxObjectFor.
+ *
+ * This utility patches common browser bugs in getBoundingClientRect. It
+ * will fail if getBoundingClientRect is unsupported.
+ *
+ * If the element is not in the DOM, the result is undefined, and an error may
+ * be thrown depending on user agent.
+ *
+ * @param {!Element} el The element whose bounding rectangle is being queried.
+ * @return {Object} A native bounding rectangle with numerical left, top,
+ * right, and bottom. Reported by Firefox to be of object type ClientRect.
+ * @private
+ */
+goog.style.getBoundingClientRect_ = function(el) {
+ var rect;
+ try {
+ rect = el.getBoundingClientRect();
+ } catch (e) {
+ // In IE < 9, calling getBoundingClientRect on an orphan element raises an
+ // "Unspecified Error". All other browsers return zeros.
+ return {'left': 0, 'top': 0, 'right': 0, 'bottom': 0};
+ }
+
+ // Patch the result in IE only, so that this function can be inlined if
+ // compiled for non-IE.
+ if (goog.userAgent.IE && el.ownerDocument.body) {
+
+ // In IE, most of the time, 2 extra pixels are added to the top and left
+ // due to the implicit 2-pixel inset border. In IE6/7 quirks mode and
+ // IE6 standards mode, this border can be overridden by setting the
+ // document element's border to zero -- thus, we cannot rely on the
+ // offset always being 2 pixels.
+
+ // In quirks mode, the offset can be determined by querying the body's
+ // clientLeft/clientTop, but in standards mode, it is found by querying
+ // the document element's clientLeft/clientTop. Since we already called
+ // getBoundingClientRect we have already forced a reflow, so it is not
+ // too expensive just to query them all.
+
+ // See: http://msdn.microsoft.com/en-us/library/ms536433(VS.85).aspx
+ var doc = el.ownerDocument;
+ rect.left -= doc.documentElement.clientLeft + doc.body.clientLeft;
+ rect.top -= doc.documentElement.clientTop + doc.body.clientTop;
+ }
+ return /** @type {Object} */ (rect);
+};
+
+
+/**
+ * Returns the first parent that could affect the position of a given element.
+ * @param {Element} element The element to get the offset parent for.
+ * @return {Element} The first offset parent or null if one cannot be found.
+ */
+goog.style.getOffsetParent = function(element) {
+ // element.offsetParent does the right thing in IE7 and below. In other
+ // browsers it only includes elements with position absolute, relative or
+ // fixed, not elements with overflow set to auto or scroll.
+ if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(8)) {
+ return element.offsetParent;
+ }
+
+ var doc = goog.dom.getOwnerDocument(element);
+ var positionStyle = goog.style.getStyle_(element, 'position');
+ var skipStatic = positionStyle == 'fixed' || positionStyle == 'absolute';
+ for (var parent = element.parentNode; parent && parent != doc;
+ parent = parent.parentNode) {
+ positionStyle =
+ goog.style.getStyle_(/** @type {!Element} */ (parent), 'position');
+ skipStatic = skipStatic && positionStyle == 'static' &&
+ parent != doc.documentElement && parent != doc.body;
+ if (!skipStatic && (parent.scrollWidth > parent.clientWidth ||
+ parent.scrollHeight > parent.clientHeight ||
+ positionStyle == 'fixed' ||
+ positionStyle == 'absolute' ||
+ positionStyle == 'relative')) {
+ return /** @type {!Element} */ (parent);
+ }
+ }
+ return null;
+};
+
+
+/**
+ * Calculates and returns the visible rectangle for a given element. Returns a
+ * box describing the visible portion of the nearest scrollable offset ancestor.
+ * Coordinates are given relative to the document.
+ *
+ * @param {Element} element Element to get the visible rect for.
+ * @return {goog.math.Box} Bounding elementBox describing the visible rect or
+ * null if scrollable ancestor isn't inside the visible viewport.
+ */
+goog.style.getVisibleRectForElement = function(element) {
+ var visibleRect = new goog.math.Box(0, Infinity, Infinity, 0);
+ var dom = goog.dom.getDomHelper(element);
+ var body = dom.getDocument().body;
+ var documentElement = dom.getDocument().documentElement;
+ var scrollEl = dom.getDocumentScrollElement();
+
+ // Determine the size of the visible rect by climbing the dom accounting for
+ // all scrollable containers.
+ for (var el = element; el = goog.style.getOffsetParent(el); ) {
+ // clientWidth is zero for inline block elements in IE.
+ // on WEBKIT, body element can have clientHeight = 0 and scrollHeight > 0
+ if ((!goog.userAgent.IE || el.clientWidth != 0) &&
+ (!goog.userAgent.WEBKIT || el.clientHeight != 0 || el != body) &&
+ // body may have overflow set on it, yet we still get the entire
+ // viewport. In some browsers, el.offsetParent may be
+ // document.documentElement, so check for that too.
+ (el != body && el != documentElement &&
+ goog.style.getStyle_(el, 'overflow') != 'visible')) {
+ var pos = goog.style.getPageOffset(el);
+ var client = goog.style.getClientLeftTop(el);
+ pos.x += client.x;
+ pos.y += client.y;
+
+ visibleRect.top = Math.max(visibleRect.top, pos.y);
+ visibleRect.right = Math.min(visibleRect.right,
+ pos.x + el.clientWidth);
+ visibleRect.bottom = Math.min(visibleRect.bottom,
+ pos.y + el.clientHeight);
+ visibleRect.left = Math.max(visibleRect.left, pos.x);
+ }
+ }
+
+ // Clip by window's viewport.
+ var scrollX = scrollEl.scrollLeft, scrollY = scrollEl.scrollTop;
+ visibleRect.left = Math.max(visibleRect.left, scrollX);
+ visibleRect.top = Math.max(visibleRect.top, scrollY);
+ var winSize = dom.getViewportSize();
+ visibleRect.right = Math.min(visibleRect.right, scrollX + winSize.width);
+ visibleRect.bottom = Math.min(visibleRect.bottom, scrollY + winSize.height);
+ return visibleRect.top >= 0 && visibleRect.left >= 0 &&
+ visibleRect.bottom > visibleRect.top &&
+ visibleRect.right > visibleRect.left ?
+ visibleRect : null;
+};
+
+
+/**
+ * Calculate the scroll position of {@code container} with the minimum amount so
+ * that the content and the borders of the given {@code element} become visible.
+ * If the element is bigger than the container, its top left corner will be
+ * aligned as close to the container's top left corner as possible.
+ *
+ * @param {Element} element The element to make visible.
+ * @param {Element} container The container to scroll.
+ * @param {boolean=} opt_center Whether to center the element in the container.
+ * Defaults to false.
+ * @return {!goog.math.Coordinate} The new scroll position of the container,
+ * in form of goog.math.Coordinate(scrollLeft, scrollTop).
+ */
+goog.style.getContainerOffsetToScrollInto =
+ function(element, container, opt_center) {
+ // Absolute position of the element's border's top left corner.
+ var elementPos = goog.style.getPageOffset(element);
+ // Absolute position of the container's border's top left corner.
+ var containerPos = goog.style.getPageOffset(container);
+ var containerBorder = goog.style.getBorderBox(container);
+ // Relative pos. of the element's border box to the container's content box.
+ var relX = elementPos.x - containerPos.x - containerBorder.left;
+ var relY = elementPos.y - containerPos.y - containerBorder.top;
+ // How much the element can move in the container, i.e. the difference between
+ // the element's bottom-right-most and top-left-most position where it's
+ // fully visible.
+ var spaceX = container.clientWidth - element.offsetWidth;
+ var spaceY = container.clientHeight - element.offsetHeight;
+
+ var scrollLeft = container.scrollLeft;
+ var scrollTop = container.scrollTop;
+ if (opt_center) {
+ // All browsers round non-integer scroll positions down.
+ scrollLeft += relX - spaceX / 2;
+ scrollTop += relY - spaceY / 2;
+ } else {
+ // This formula was designed to give the correct scroll values in the
+ // following cases:
+ // - element is higher than container (spaceY < 0) => scroll down by relY
+ // - element is not higher that container (spaceY >= 0):
+ // - it is above container (relY < 0) => scroll up by abs(relY)
+ // - it is below container (relY > spaceY) => scroll down by relY - spaceY
+ // - it is in the container => don't scroll
+ scrollLeft += Math.min(relX, Math.max(relX - spaceX, 0));
+ scrollTop += Math.min(relY, Math.max(relY - spaceY, 0));
+ }
+ return new goog.math.Coordinate(scrollLeft, scrollTop);
+};
+
+
+/**
+ * Changes the scroll position of {@code container} with the minimum amount so
+ * that the content and the borders of the given {@code element} become visible.
+ * If the element is bigger than the container, its top left corner will be
+ * aligned as close to the container's top left corner as possible.
+ *
+ * @param {Element} element The element to make visible.
+ * @param {Element} container The container to scroll.
+ * @param {boolean=} opt_center Whether to center the element in the container.
+ * Defaults to false.
+ */
+goog.style.scrollIntoContainerView = function(element, container, opt_center) {
+ var offset =
+ goog.style.getContainerOffsetToScrollInto(element, container, opt_center);
+ container.scrollLeft = offset.x;
+ container.scrollTop = offset.y;
+};
+
+
+/**
+ * Returns clientLeft (width of the left border and, if the directionality is
+ * right to left, the vertical scrollbar) and clientTop as a coordinate object.
+ *
+ * @param {Element} el Element to get clientLeft for.
+ * @return {!goog.math.Coordinate} Client left and top.
+ */
+goog.style.getClientLeftTop = function(el) {
+ // NOTE(eae): Gecko prior to 1.9 doesn't support clientTop/Left, see
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=111207
+ if (goog.userAgent.GECKO && !goog.userAgent.isVersionOrHigher('1.9')) {
+ var left = parseFloat(goog.style.getComputedStyle(el, 'borderLeftWidth'));
+ if (goog.style.isRightToLeft(el)) {
+ var scrollbarWidth = el.offsetWidth - el.clientWidth - left -
+ parseFloat(goog.style.getComputedStyle(el, 'borderRightWidth'));
+ left += scrollbarWidth;
+ }
+ return new goog.math.Coordinate(left,
+ parseFloat(goog.style.getComputedStyle(el, 'borderTopWidth')));
+ }
+
+ return new goog.math.Coordinate(el.clientLeft, el.clientTop);
+};
+
+
+/**
+ * Returns a Coordinate object relative to the top-left of the HTML document.
+ * Implemented as a single function to save having to do two recursive loops in
+ * opera and safari just to get both coordinates. If you just want one value do
+ * use goog.style.getPageOffsetLeft() and goog.style.getPageOffsetTop(), but
+ * note if you call both those methods the tree will be analysed twice.
+ *
+ * @param {Element} el Element to get the page offset for.
+ * @return {!goog.math.Coordinate} The page offset.
+ */
+goog.style.getPageOffset = function(el) {
+ var box, doc = goog.dom.getOwnerDocument(el);
+ var positionStyle = goog.style.getStyle_(el, 'position');
+ // TODO(gboyer): Update the jsdoc in a way that doesn't break the universe.
+ goog.asserts.assertObject(el, 'Parameter is required');
+
+ // NOTE(eae): Gecko pre 1.9 normally use getBoxObjectFor to calculate the
+ // position. When invoked for an element with position absolute and a negative
+ // position though it can be off by one. Therefor the recursive implementation
+ // is used in those (relatively rare) cases.
+ var BUGGY_GECKO_BOX_OBJECT =
+ !goog.style.GET_BOUNDING_CLIENT_RECT_ALWAYS_EXISTS &&
+ goog.userAgent.GECKO && doc.getBoxObjectFor &&
+ !el.getBoundingClientRect && positionStyle == 'absolute' &&
+ (box = doc.getBoxObjectFor(el)) && (box.screenX < 0 || box.screenY < 0);
+
+ // NOTE(arv): If element is hidden (display none or disconnected or any the
+ // ancestors are hidden) we get (0,0) by default but we still do the
+ // accumulation of scroll position.
+
+ // TODO(arv): Should we check if the node is disconnected and in that case
+ // return (0,0)?
+
+ var pos = new goog.math.Coordinate(0, 0);
+ var viewportElement = goog.style.getClientViewportElement(doc);
+ if (el == viewportElement) {
+ // viewport is always at 0,0 as that defined the coordinate system for this
+ // function - this avoids special case checks in the code below
+ return pos;
+ }
+
+ // IE, Gecko 1.9+, and most modern WebKit.
+ if (goog.style.GET_BOUNDING_CLIENT_RECT_ALWAYS_EXISTS ||
+ el.getBoundingClientRect) {
+ box = goog.style.getBoundingClientRect_(el);
+ // Must add the scroll coordinates in to get the absolute page offset
+ // of element since getBoundingClientRect returns relative coordinates to
+ // the viewport.
+ var scrollCoord = goog.dom.getDomHelper(doc).getDocumentScroll();
+ pos.x = box.left + scrollCoord.x;
+ pos.y = box.top + scrollCoord.y;
+
+ // Gecko prior to 1.9.
+ } else if (doc.getBoxObjectFor && !BUGGY_GECKO_BOX_OBJECT) {
+ // Gecko ignores the scroll values for ancestors, up to 1.9. See:
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=328881 and
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=330619
+
+ box = doc.getBoxObjectFor(el);
+ // TODO(user): Fix the off-by-one error when window is scrolled down
+ // or right more than 1 pixel. The viewport offset does not move in lock
+ // step with the window scroll; it moves in increments of 2px and at
+ // somewhat random intervals.
+ var vpBox = doc.getBoxObjectFor(viewportElement);
+ pos.x = box.screenX - vpBox.screenX;
+ pos.y = box.screenY - vpBox.screenY;
+
+ // Safari, Opera and Camino up to 1.0.4.
+ } else {
+ var parent = el;
+ do {
+ pos.x += parent.offsetLeft;
+ pos.y += parent.offsetTop;
+ // For safari/chrome, we need to add parent's clientLeft/Top as well.
+ if (parent != el) {
+ pos.x += parent.clientLeft || 0;
+ pos.y += parent.clientTop || 0;
+ }
+ // In Safari when hit a position fixed element the rest of the offsets
+ // are not correct.
+ if (goog.userAgent.WEBKIT &&
+ goog.style.getComputedPosition(parent) == 'fixed') {
+ pos.x += doc.body.scrollLeft;
+ pos.y += doc.body.scrollTop;
+ break;
+ }
+ parent = parent.offsetParent;
+ } while (parent && parent != el);
+
+ // Opera & (safari absolute) incorrectly account for body offsetTop.
+ if (goog.userAgent.OPERA || (goog.userAgent.WEBKIT &&
+ positionStyle == 'absolute')) {
+ pos.y -= doc.body.offsetTop;
+ }
+
+ for (parent = el; (parent = goog.style.getOffsetParent(parent)) &&
+ parent != doc.body && parent != viewportElement; ) {
+ pos.x -= parent.scrollLeft;
+ // Workaround for a bug in Opera 9.2 (and earlier) where table rows may
+ // report an invalid scroll top value. The bug was fixed in Opera 9.5
+ // however as that version supports getBoundingClientRect it won't
+ // trigger this code path. https://bugs.opera.com/show_bug.cgi?id=249965
+ if (!goog.userAgent.OPERA || parent.tagName != 'TR') {
+ pos.y -= parent.scrollTop;
+ }
+ }
+ }
+
+ return pos;
+};
+
+
+/**
+ * Returns the left coordinate of an element relative to the HTML document
+ * @param {Element} el Elements.
+ * @return {number} The left coordinate.
+ */
+goog.style.getPageOffsetLeft = function(el) {
+ return goog.style.getPageOffset(el).x;
+};
+
+
+/**
+ * Returns the top coordinate of an element relative to the HTML document
+ * @param {Element} el Elements.
+ * @return {number} The top coordinate.
+ */
+goog.style.getPageOffsetTop = function(el) {
+ return goog.style.getPageOffset(el).y;
+};
+
+
+/**
+ * Returns a Coordinate object relative to the top-left of an HTML document
+ * in an ancestor frame of this element. Used for measuring the position of
+ * an element inside a frame relative to a containing frame.
+ *
+ * @param {Element} el Element to get the page offset for.
+ * @param {Window} relativeWin The window to measure relative to. If relativeWin
+ * is not in the ancestor frame chain of the element, we measure relative to
+ * the top-most window.
+ * @return {!goog.math.Coordinate} The page offset.
+ */
+goog.style.getFramedPageOffset = function(el, relativeWin) {
+ var position = new goog.math.Coordinate(0, 0);
+
+ // Iterate up the ancestor frame chain, keeping track of the current window
+ // and the current element in that window.
+ var currentWin = goog.dom.getWindow(goog.dom.getOwnerDocument(el));
+ var currentEl = el;
+ do {
+ // if we're at the top window, we want to get the page offset.
+ // if we're at an inner frame, we only want to get the window position
+ // so that we can determine the actual page offset in the context of
+ // the outer window.
+ var offset = currentWin == relativeWin ?
+ goog.style.getPageOffset(currentEl) :
+ goog.style.getClientPositionForElement_(
+ goog.asserts.assert(currentEl));
+
+ position.x += offset.x;
+ position.y += offset.y;
+ } while (currentWin && currentWin != relativeWin &&
+ (currentEl = currentWin.frameElement) &&
+ (currentWin = currentWin.parent));
+
+ return position;
+};
+
+
+/**
+ * Translates the specified rect relative to origBase page, for newBase page.
+ * If origBase and newBase are the same, this function does nothing.
+ *
+ * @param {goog.math.Rect} rect The source rectangle relative to origBase page,
+ * and it will have the translated result.
+ * @param {goog.dom.DomHelper} origBase The DomHelper for the input rectangle.
+ * @param {goog.dom.DomHelper} newBase The DomHelper for the resultant
+ * coordinate. This must be a DOM for an ancestor frame of origBase
+ * or the same as origBase.
+ */
+goog.style.translateRectForAnotherFrame = function(rect, origBase, newBase) {
+ if (origBase.getDocument() != newBase.getDocument()) {
+ var body = origBase.getDocument().body;
+ var pos = goog.style.getFramedPageOffset(body, newBase.getWindow());
+
+ // Adjust Body's margin.
+ pos = goog.math.Coordinate.difference(pos, goog.style.getPageOffset(body));
+
+ if (goog.userAgent.IE && !origBase.isCss1CompatMode()) {
+ pos = goog.math.Coordinate.difference(pos, origBase.getDocumentScroll());
+ }
+
+ rect.left += pos.x;
+ rect.top += pos.y;
+ }
+};
+
+
+/**
+ * Returns the position of an element relative to another element in the
+ * document. A relative to B
+ * @param {Element|Event|goog.events.Event} a Element or mouse event whose
+ * position we're calculating.
+ * @param {Element|Event|goog.events.Event} b Element or mouse event position
+ * is relative to.
+ * @return {!goog.math.Coordinate} The relative position.
+ */
+goog.style.getRelativePosition = function(a, b) {
+ var ap = goog.style.getClientPosition(a);
+ var bp = goog.style.getClientPosition(b);
+ return new goog.math.Coordinate(ap.x - bp.x, ap.y - bp.y);
+};
+
+
+/**
+ * Returns the position of the event or the element's border box relative to
+ * the client viewport.
+ * @param {!Element} el Element whose position to get.
+ * @return {!goog.math.Coordinate} The position.
+ * @private
+ */
+goog.style.getClientPositionForElement_ = function(el) {
+ var pos;
+ if (goog.style.GET_BOUNDING_CLIENT_RECT_ALWAYS_EXISTS ||
+ el.getBoundingClientRect) {
+ // IE, Gecko 1.9+, and most modern WebKit
+ var box = goog.style.getBoundingClientRect_(el);
+ pos = new goog.math.Coordinate(box.left, box.top);
+ } else {
+ var scrollCoord = goog.dom.getDomHelper(el).getDocumentScroll();
+ var pageCoord = goog.style.getPageOffset(el);
+ pos = new goog.math.Coordinate(
+ pageCoord.x - scrollCoord.x,
+ pageCoord.y - scrollCoord.y);
+ }
+
+ // Gecko below version 12 doesn't add CSS translation to the client position
+ // (using either getBoundingClientRect or getBoxOffsetFor) so we need to do
+ // so manually.
+ if (goog.userAgent.GECKO && !goog.userAgent.isVersionOrHigher(12)) {
+ return goog.math.Coordinate.sum(pos, goog.style.getCssTranslation(el));
+ } else {
+ return pos;
+ }
+};
+
+
+/**
+ * Returns the position of the event or the element's border box relative to
+ * the client viewport.
+ * @param {Element|Event|goog.events.Event} el Element or a mouse / touch event.
+ * @return {!goog.math.Coordinate} The position.
+ */
+goog.style.getClientPosition = function(el) {
+ goog.asserts.assert(el);
+ if (el.nodeType == goog.dom.NodeType.ELEMENT) {
+ return goog.style.getClientPositionForElement_(
+ /** @type {!Element} */ (el));
+ } else {
+ var isAbstractedEvent = goog.isFunction(el.getBrowserEvent);
+ var be = /** @type {!goog.events.BrowserEvent} */ (el);
+ var targetEvent = el;
+
+ if (el.targetTouches) {
+ targetEvent = el.targetTouches[0];
+ } else if (isAbstractedEvent && be.getBrowserEvent().targetTouches) {
+ targetEvent = be.getBrowserEvent().targetTouches[0];
+ }
+
+ return new goog.math.Coordinate(
+ targetEvent.clientX,
+ targetEvent.clientY);
+ }
+};
+
+
+/**
+ * Moves an element to the given coordinates relative to the client viewport.
+ * @param {Element} el Absolutely positioned element to set page offset for.
+ * It must be in the document.
+ * @param {number|goog.math.Coordinate} x Left position of the element's margin
+ * box or a coordinate object.
+ * @param {number=} opt_y Top position of the element's margin box.
+ */
+goog.style.setPageOffset = function(el, x, opt_y) {
+ // Get current pageoffset
+ var cur = goog.style.getPageOffset(el);
+
+ if (x instanceof goog.math.Coordinate) {
+ opt_y = x.y;
+ x = x.x;
+ }
+
+ // NOTE(arv): We cannot allow strings for x and y. We could but that would
+ // require us to manually transform between different units
+
+ // Work out deltas
+ var dx = x - cur.x;
+ var dy = opt_y - cur.y;
+
+ // Set position to current left/top + delta
+ goog.style.setPosition(el, el.offsetLeft + dx, el.offsetTop + dy);
+};
+
+
+/**
+ * Sets the width/height values of an element. If an argument is numeric,
+ * or a goog.math.Size is passed, it is assumed to be pixels and will add
+ * 'px' after converting it to an integer in string form. (This just sets the
+ * CSS width and height properties so it might set content-box or border-box
+ * size depending on the box model the browser is using.)
+ *
+ * @param {Element} element Element to set the size of.
+ * @param {string|number|goog.math.Size} w Width of the element, or a
+ * size object.
+ * @param {string|number=} opt_h Height of the element. Required if w is not a
+ * size object.
+ */
+goog.style.setSize = function(element, w, opt_h) {
+ var h;
+ if (w instanceof goog.math.Size) {
+ h = w.height;
+ w = w.width;
+ } else {
+ if (opt_h == undefined) {
+ throw Error('missing height argument');
+ }
+ h = opt_h;
+ }
+
+ goog.style.setWidth(element, /** @type {string|number} */ (w));
+ goog.style.setHeight(element, /** @type {string|number} */ (h));
+};
+
+
+/**
+ * Helper function to create a string to be set into a pixel-value style
+ * property of an element. Can round to the nearest integer value.
+ *
+ * @param {string|number} value The style value to be used. If a number,
+ * 'px' will be appended, otherwise the value will be applied directly.
+ * @param {boolean} round Whether to round the nearest integer (if property
+ * is a number).
+ * @return {string} The string value for the property.
+ * @private
+ */
+goog.style.getPixelStyleValue_ = function(value, round) {
+ if (typeof value == 'number') {
+ value = (round ? Math.round(value) : value) + 'px';
+ }
+
+ return value;
+};
+
+
+/**
+ * Set the height of an element. Sets the element's style property.
+ * @param {Element} element Element to set the height of.
+ * @param {string|number} height The height value to set. If a number, 'px'
+ * will be appended, otherwise the value will be applied directly.
+ */
+goog.style.setHeight = function(element, height) {
+ element.style.height = goog.style.getPixelStyleValue_(height, true);
+};
+
+
+/**
+ * Set the width of an element. Sets the element's style property.
+ * @param {Element} element Element to set the width of.
+ * @param {string|number} width The width value to set. If a number, 'px'
+ * will be appended, otherwise the value will be applied directly.
+ */
+goog.style.setWidth = function(element, width) {
+ element.style.width = goog.style.getPixelStyleValue_(width, true);
+};
+
+
+/**
+ * Gets the height and width of an element, even if its display is none.
+ *
+ * Specifically, this returns the height and width of the border box,
+ * irrespective of the box model in effect.
+ *
+ * Note that this function does not take CSS transforms into account. Please see
+ * {@code goog.style.getTransformedSize}.
+ * @param {Element} element Element to get size of.
+ * @return {!goog.math.Size} Object with width/height properties.
+ */
+goog.style.getSize = function(element) {
+ return goog.style.evaluateWithTemporaryDisplay_(
+ goog.style.getSizeWithDisplay_, /** @type {!Element} */ (element));
+};
+
+
+/**
+ * Call {@code fn} on {@code element} such that {@code element}'s dimensions are
+ * accurate when it's passed to {@code fn}.
+ * @param {function(!Element): T} fn Function to call with {@code element} as
+ * an argument after temporarily changing {@code element}'s display such
+ * that its dimensions are accurate.
+ * @param {!Element} element Element (which may have display none) to use as
+ * argument to {@code fn}.
+ * @return {T} Value returned by calling {@code fn} with {@code element}.
+ * @template T
+ * @private
+ */
+goog.style.evaluateWithTemporaryDisplay_ = function(fn, element) {
+ if (goog.style.getStyle_(element, 'display') != 'none') {
+ return fn(element);
+ }
+
+ var style = element.style;
+ var originalDisplay = style.display;
+ var originalVisibility = style.visibility;
+ var originalPosition = style.position;
+
+ style.visibility = 'hidden';
+ style.position = 'absolute';
+ style.display = 'inline';
+
+ var retVal = fn(element);
+
+ style.display = originalDisplay;
+ style.position = originalPosition;
+ style.visibility = originalVisibility;
+
+ return retVal;
+};
+
+
+/**
+ * Gets the height and width of an element when the display is not none.
+ * @param {Element} element Element to get size of.
+ * @return {!goog.math.Size} Object with width/height properties.
+ * @private
+ */
+goog.style.getSizeWithDisplay_ = function(element) {
+ var offsetWidth = element.offsetWidth;
+ var offsetHeight = element.offsetHeight;
+ var webkitOffsetsZero =
+ goog.userAgent.WEBKIT && !offsetWidth && !offsetHeight;
+ if ((!goog.isDef(offsetWidth) || webkitOffsetsZero) &&
+ element.getBoundingClientRect) {
+ // Fall back to calling getBoundingClientRect when offsetWidth or
+ // offsetHeight are not defined, or when they are zero in WebKit browsers.
+ // This makes sure that we return for the correct size for SVG elements, but
+ // will still return 0 on Webkit prior to 534.8, see
+ // http://trac.webkit.org/changeset/67252.
+ var clientRect = goog.style.getBoundingClientRect_(element);
+ return new goog.math.Size(clientRect.right - clientRect.left,
+ clientRect.bottom - clientRect.top);
+ }
+ return new goog.math.Size(offsetWidth, offsetHeight);
+};
+
+
+/**
+ * Gets the height and width of an element, post transform, even if its display
+ * is none.
+ *
+ * This is like {@code goog.style.getSize}, except:
+ * <ol>
+ * <li>Takes webkitTransforms such as rotate and scale into account.
+ * <li>Will return null if {@code element} doesn't respond to
+ * {@code getBoundingClientRect}.
+ * <li>Currently doesn't make sense on non-WebKit browsers which don't support
+ * webkitTransforms.
+ * </ol>
+ * @param {!Element} element Element to get size of.
+ * @return {goog.math.Size} Object with width/height properties.
+ */
+goog.style.getTransformedSize = function(element) {
+ if (!element.getBoundingClientRect) {
+ return null;
+ }
+
+ var clientRect = goog.style.evaluateWithTemporaryDisplay_(
+ goog.style.getBoundingClientRect_, element);
+ return new goog.math.Size(clientRect.right - clientRect.left,
+ clientRect.bottom - clientRect.top);
+};
+
+
+/**
+ * Returns a bounding rectangle for a given element in page space.
+ * @param {Element} element Element to get bounds of. Must not be display none.
+ * @return {!goog.math.Rect} Bounding rectangle for the element.
+ */
+goog.style.getBounds = function(element) {
+ var o = goog.style.getPageOffset(element);
+ var s = goog.style.getSize(element);
+ return new goog.math.Rect(o.x, o.y, s.width, s.height);
+};
+
+
+/**
+ * Converts a CSS selector in the form style-property to styleProperty.
+ * @param {*} selector CSS Selector.
+ * @return {string} Camel case selector.
+ * @deprecated Use goog.string.toCamelCase instead.
+ */
+goog.style.toCamelCase = function(selector) {
+ return goog.string.toCamelCase(String(selector));
+};
+
+
+/**
+ * Converts a CSS selector in the form styleProperty to style-property.
+ * @param {string} selector Camel case selector.
+ * @return {string} Selector cased.
+ * @deprecated Use goog.string.toSelectorCase instead.
+ */
+goog.style.toSelectorCase = function(selector) {
+ return goog.string.toSelectorCase(selector);
+};
+
+
+/**
+ * Gets the opacity of a node (x-browser). This gets the inline style opacity
+ * of the node, and does not take into account the cascaded or the computed
+ * style for this node.
+ * @param {Element} el Element whose opacity has to be found.
+ * @return {number|string} Opacity between 0 and 1 or an empty string {@code ''}
+ * if the opacity is not set.
+ */
+goog.style.getOpacity = function(el) {
+ var style = el.style;
+ var result = '';
+ if ('opacity' in style) {
+ result = style.opacity;
+ } else if ('MozOpacity' in style) {
+ result = style.MozOpacity;
+ } else if ('filter' in style) {
+ var match = style.filter.match(/alpha\(opacity=([\d.]+)\)/);
+ if (match) {
+ result = String(match[1] / 100);
+ }
+ }
+ return result == '' ? result : Number(result);
+};
+
+
+/**
+ * Sets the opacity of a node (x-browser).
+ * @param {Element} el Elements whose opacity has to be set.
+ * @param {number|string} alpha Opacity between 0 and 1 or an empty string
+ * {@code ''} to clear the opacity.
+ */
+goog.style.setOpacity = function(el, alpha) {
+ var style = el.style;
+ if ('opacity' in style) {
+ style.opacity = alpha;
+ } else if ('MozOpacity' in style) {
+ style.MozOpacity = alpha;
+ } else if ('filter' in style) {
+ // TODO(arv): Overwriting the filter might have undesired side effects.
+ if (alpha === '') {
+ style.filter = '';
+ } else {
+ style.filter = 'alpha(opacity=' + alpha * 100 + ')';
+ }
+ }
+};
+
+
+/**
+ * Sets the background of an element to a transparent image in a browser-
+ * independent manner.
+ *
+ * This function does not support repeating backgrounds or alternate background
+ * positions to match the behavior of Internet Explorer. It also does not
+ * support sizingMethods other than crop since they cannot be replicated in
+ * browsers other than Internet Explorer.
+ *
+ * @param {Element} el The element to set background on.
+ * @param {string} src The image source URL.
+ */
+goog.style.setTransparentBackgroundImage = function(el, src) {
+ var style = el.style;
+ // It is safe to use the style.filter in IE only. In Safari 'filter' is in
+ // style object but access to style.filter causes it to throw an exception.
+ // Note: IE8 supports images with an alpha channel.
+ if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('8')) {
+ // See TODO in setOpacity.
+ style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(' +
+ 'src="' + src + '", sizingMethod="crop")';
+ } else {
+ // Set style properties individually instead of using background shorthand
+ // to prevent overwriting a pre-existing background color.
+ style.backgroundImage = 'url(' + src + ')';
+ style.backgroundPosition = 'top left';
+ style.backgroundRepeat = 'no-repeat';
+ }
+};
+
+
+/**
+ * Clears the background image of an element in a browser independent manner.
+ * @param {Element} el The element to clear background image for.
+ */
+goog.style.clearTransparentBackgroundImage = function(el) {
+ var style = el.style;
+ if ('filter' in style) {
+ // See TODO in setOpacity.
+ style.filter = '';
+ } else {
+ // Set style properties individually instead of using background shorthand
+ // to prevent overwriting a pre-existing background color.
+ style.backgroundImage = 'none';
+ }
+};
+
+
+/**
+ * Shows or hides an element from the page. Hiding the element is done by
+ * setting the display property to "none", removing the element from the
+ * rendering hierarchy so it takes up no space. To show the element, the default
+ * inherited display property is restored (defined either in stylesheets or by
+ * the browser's default style rules.)
+ *
+ * Caveat 1: if the inherited display property for the element is set to "none"
+ * by the stylesheets, that is the property that will be restored by a call to
+ * showElement(), effectively toggling the display between "none" and "none".
+ *
+ * Caveat 2: if the element display style is set inline (by setting either
+ * element.style.display or a style attribute in the HTML), a call to
+ * showElement will clear that setting and defer to the inherited style in the
+ * stylesheet.
+ * @param {Element} el Element to show or hide.
+ * @param {*} display True to render the element in its default style,
+ * false to disable rendering the element.
+ * @deprecated Use goog.style.setElementShown instead.
+ */
+goog.style.showElement = function(el, display) {
+ goog.style.setElementShown(el, display);
+};
+
+
+/**
+ * Shows or hides an element from the page. Hiding the element is done by
+ * setting the display property to "none", removing the element from the
+ * rendering hierarchy so it takes up no space. To show the element, the default
+ * inherited display property is restored (defined either in stylesheets or by
+ * the browser's default style rules).
+ *
+ * Caveat 1: if the inherited display property for the element is set to "none"
+ * by the stylesheets, that is the property that will be restored by a call to
+ * setElementShown(), effectively toggling the display between "none" and
+ * "none".
+ *
+ * Caveat 2: if the element display style is set inline (by setting either
+ * element.style.display or a style attribute in the HTML), a call to
+ * setElementShown will clear that setting and defer to the inherited style in
+ * the stylesheet.
+ * @param {Element} el Element to show or hide.
+ * @param {*} isShown True to render the element in its default style,
+ * false to disable rendering the element.
+ */
+goog.style.setElementShown = function(el, isShown) {
+ el.style.display = isShown ? '' : 'none';
+};
+
+
+/**
+ * Test whether the given element has been shown or hidden via a call to
+ * {@link #setElementShown}.
+ *
+ * Note this is strictly a companion method for a call
+ * to {@link #setElementShown} and the same caveats apply; in particular, this
+ * method does not guarantee that the return value will be consistent with
+ * whether or not the element is actually visible.
+ *
+ * @param {Element} el The element to test.
+ * @return {boolean} Whether the element has been shown.
+ * @see #setElementShown
+ */
+goog.style.isElementShown = function(el) {
+ return el.style.display != 'none';
+};
+
+
+/**
+ * Installs the styles string into the window that contains opt_element. If
+ * opt_element is null, the main window is used.
+ * @param {string} stylesString The style string to install.
+ * @param {Node=} opt_node Node whose parent document should have the
+ * styles installed.
+ * @return {Element|StyleSheet} The style element created.
+ */
+goog.style.installStyles = function(stylesString, opt_node) {
+ var dh = goog.dom.getDomHelper(opt_node);
+ var styleSheet = null;
+
+ // IE < 11 requires createStyleSheet. Note that doc.createStyleSheet will be
+ // undefined as of IE 11.
+ var doc = dh.getDocument();
+ if (goog.userAgent.IE && doc.createStyleSheet) {
+ styleSheet = doc.createStyleSheet();
+ goog.style.setStyles(styleSheet, stylesString);
+ } else {
+ var head = dh.getElementsByTagNameAndClass('head')[0];
+
+ // In opera documents are not guaranteed to have a head element, thus we
+ // have to make sure one exists before using it.
+ if (!head) {
+ var body = dh.getElementsByTagNameAndClass('body')[0];
+ head = dh.createDom('head');
+ body.parentNode.insertBefore(head, body);
+ }
+ styleSheet = dh.createDom('style');
+ // NOTE(user): Setting styles after the style element has been appended
+ // to the head results in a nasty Webkit bug in certain scenarios. Please
+ // refer to https://bugs.webkit.org/show_bug.cgi?id=26307 for additional
+ // details.
+ goog.style.setStyles(styleSheet, stylesString);
+ dh.appendChild(head, styleSheet);
+ }
+ return styleSheet;
+};
+
+
+/**
+ * Removes the styles added by {@link #installStyles}.
+ * @param {Element|StyleSheet} styleSheet The value returned by
+ * {@link #installStyles}.
+ */
+goog.style.uninstallStyles = function(styleSheet) {
+ var node = styleSheet.ownerNode || styleSheet.owningElement ||
+ /** @type {Element} */ (styleSheet);
+ goog.dom.removeNode(node);
+};
+
+
+/**
+ * Sets the content of a style element. The style element can be any valid
+ * style element. This element will have its content completely replaced by
+ * the new stylesString.
+ * @param {Element|StyleSheet} element A stylesheet element as returned by
+ * installStyles.
+ * @param {string} stylesString The new content of the stylesheet.
+ */
+goog.style.setStyles = function(element, stylesString) {
+ if (goog.userAgent.IE && goog.isDef(element.cssText)) {
+ // Adding the selectors individually caused the browser to hang if the
+ // selector was invalid or there were CSS comments. Setting the cssText of
+ // the style node works fine and ignores CSS that IE doesn't understand.
+ // However IE >= 11 doesn't support cssText any more, so we make sure that
+ // cssText is a defined property and otherwise fall back to innerHTML.
+ element.cssText = stylesString;
+ } else {
+ element.innerHTML = stylesString;
+ }
+};
+
+
+/**
+ * Sets 'white-space: pre-wrap' for a node (x-browser).
+ *
+ * There are as many ways of specifying pre-wrap as there are browsers.
+ *
+ * CSS3/IE8: white-space: pre-wrap;
+ * Mozilla: white-space: -moz-pre-wrap;
+ * Opera: white-space: -o-pre-wrap;
+ * IE6/7: white-space: pre; word-wrap: break-word;
+ *
+ * @param {Element} el Element to enable pre-wrap for.
+ */
+goog.style.setPreWrap = function(el) {
+ var style = el.style;
+ if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('8')) {
+ style.whiteSpace = 'pre';
+ style.wordWrap = 'break-word';
+ } else if (goog.userAgent.GECKO) {
+ style.whiteSpace = '-moz-pre-wrap';
+ } else {
+ style.whiteSpace = 'pre-wrap';
+ }
+};
+
+
+/**
+ * Sets 'display: inline-block' for an element (cross-browser).
+ * @param {Element} el Element to which the inline-block display style is to be
+ * applied.
+ * @see ../demos/inline_block_quirks.html
+ * @see ../demos/inline_block_standards.html
+ */
+goog.style.setInlineBlock = function(el) {
+ var style = el.style;
+ // Without position:relative, weirdness ensues. Just accept it and move on.
+ style.position = 'relative';
+
+ if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('8')) {
+ // IE8 supports inline-block so fall through to the else
+ // Zoom:1 forces hasLayout, display:inline gives inline behavior.
+ style.zoom = '1';
+ style.display = 'inline';
+ } else if (goog.userAgent.GECKO) {
+ // Pre-Firefox 3, Gecko doesn't support inline-block, but -moz-inline-box
+ // is close enough.
+ style.display = goog.userAgent.isVersionOrHigher('1.9a') ? 'inline-block' :
+ '-moz-inline-box';
+ } else {
+ // Opera, Webkit, and Safari seem to do OK with the standard inline-block
+ // style.
+ style.display = 'inline-block';
+ }
+};
+
+
+/**
+ * Returns true if the element is using right to left (rtl) direction.
+ * @param {Element} el The element to test.
+ * @return {boolean} True for right to left, false for left to right.
+ */
+goog.style.isRightToLeft = function(el) {
+ return 'rtl' == goog.style.getStyle_(el, 'direction');
+};
+
+
+/**
+ * The CSS style property corresponding to an element being
+ * unselectable on the current browser platform (null if none).
+ * Opera and IE instead use a DOM attribute 'unselectable'.
+ * @type {?string}
+ * @private
+ */
+goog.style.unselectableStyle_ =
+ goog.userAgent.GECKO ? 'MozUserSelect' :
+ goog.userAgent.WEBKIT ? 'WebkitUserSelect' :
+ null;
+
+
+/**
+ * Returns true if the element is set to be unselectable, false otherwise.
+ * Note that on some platforms (e.g. Mozilla), even if an element isn't set
+ * to be unselectable, it will behave as such if any of its ancestors is
+ * unselectable.
+ * @param {Element} el Element to check.
+ * @return {boolean} Whether the element is set to be unselectable.
+ */
+goog.style.isUnselectable = function(el) {
+ if (goog.style.unselectableStyle_) {
+ return el.style[goog.style.unselectableStyle_].toLowerCase() == 'none';
+ } else if (goog.userAgent.IE || goog.userAgent.OPERA) {
+ return el.getAttribute('unselectable') == 'on';
+ }
+ return false;
+};
+
+
+/**
+ * Makes the element and its descendants selectable or unselectable. Note
+ * that on some platforms (e.g. Mozilla), even if an element isn't set to
+ * be unselectable, it will behave as such if any of its ancestors is
+ * unselectable.
+ * @param {Element} el The element to alter.
+ * @param {boolean} unselectable Whether the element and its descendants
+ * should be made unselectable.
+ * @param {boolean=} opt_noRecurse Whether to only alter the element's own
+ * selectable state, and leave its descendants alone; defaults to false.
+ */
+goog.style.setUnselectable = function(el, unselectable, opt_noRecurse) {
+ // TODO(attila): Do we need all of TR_DomUtil.makeUnselectable() in Closure?
+ var descendants = !opt_noRecurse ? el.getElementsByTagName('*') : null;
+ var name = goog.style.unselectableStyle_;
+ if (name) {
+ // Add/remove the appropriate CSS style to/from the element and its
+ // descendants.
+ var value = unselectable ? 'none' : '';
+ el.style[name] = value;
+ if (descendants) {
+ for (var i = 0, descendant; descendant = descendants[i]; i++) {
+ descendant.style[name] = value;
+ }
+ }
+ } else if (goog.userAgent.IE || goog.userAgent.OPERA) {
+ // Toggle the 'unselectable' attribute on the element and its descendants.
+ var value = unselectable ? 'on' : '';
+ el.setAttribute('unselectable', value);
+ if (descendants) {
+ for (var i = 0, descendant; descendant = descendants[i]; i++) {
+ descendant.setAttribute('unselectable', value);
+ }
+ }
+ }
+};
+
+
+/**
+ * Gets the border box size for an element.
+ * @param {Element} element The element to get the size for.
+ * @return {!goog.math.Size} The border box size.
+ */
+goog.style.getBorderBoxSize = function(element) {
+ return new goog.math.Size(element.offsetWidth, element.offsetHeight);
+};
+
+
+/**
+ * Sets the border box size of an element. This is potentially expensive in IE
+ * if the document is CSS1Compat mode
+ * @param {Element} element The element to set the size on.
+ * @param {goog.math.Size} size The new size.
+ */
+goog.style.setBorderBoxSize = function(element, size) {
+ var doc = goog.dom.getOwnerDocument(element);
+ var isCss1CompatMode = goog.dom.getDomHelper(doc).isCss1CompatMode();
+
+ if (goog.userAgent.IE &&
+ (!isCss1CompatMode || !goog.userAgent.isVersionOrHigher('8'))) {
+ var style = element.style;
+ if (isCss1CompatMode) {
+ var paddingBox = goog.style.getPaddingBox(element);
+ var borderBox = goog.style.getBorderBox(element);
+ style.pixelWidth = size.width - borderBox.left - paddingBox.left -
+ paddingBox.right - borderBox.right;
+ style.pixelHeight = size.height - borderBox.top - paddingBox.top -
+ paddingBox.bottom - borderBox.bottom;
+ } else {
+ style.pixelWidth = size.width;
+ style.pixelHeight = size.height;
+ }
+ } else {
+ goog.style.setBoxSizingSize_(element, size, 'border-box');
+ }
+};
+
+
+/**
+ * Gets the content box size for an element. This is potentially expensive in
+ * all browsers.
+ * @param {Element} element The element to get the size for.
+ * @return {!goog.math.Size} The content box size.
+ */
+goog.style.getContentBoxSize = function(element) {
+ var doc = goog.dom.getOwnerDocument(element);
+ var ieCurrentStyle = goog.userAgent.IE && element.currentStyle;
+ if (ieCurrentStyle &&
+ goog.dom.getDomHelper(doc).isCss1CompatMode() &&
+ ieCurrentStyle.width != 'auto' && ieCurrentStyle.height != 'auto' &&
+ !ieCurrentStyle.boxSizing) {
+ // If IE in CSS1Compat mode than just use the width and height.
+ // If we have a boxSizing then fall back on measuring the borders etc.
+ var width = goog.style.getIePixelValue_(element, ieCurrentStyle.width,
+ 'width', 'pixelWidth');
+ var height = goog.style.getIePixelValue_(element, ieCurrentStyle.height,
+ 'height', 'pixelHeight');
+ return new goog.math.Size(width, height);
+ } else {
+ var borderBoxSize = goog.style.getBorderBoxSize(element);
+ var paddingBox = goog.style.getPaddingBox(element);
+ var borderBox = goog.style.getBorderBox(element);
+ return new goog.math.Size(borderBoxSize.width -
+ borderBox.left - paddingBox.left -
+ paddingBox.right - borderBox.right,
+ borderBoxSize.height -
+ borderBox.top - paddingBox.top -
+ paddingBox.bottom - borderBox.bottom);
+ }
+};
+
+
+/**
+ * Sets the content box size of an element. This is potentially expensive in IE
+ * if the document is BackCompat mode.
+ * @param {Element} element The element to set the size on.
+ * @param {goog.math.Size} size The new size.
+ */
+goog.style.setContentBoxSize = function(element, size) {
+ var doc = goog.dom.getOwnerDocument(element);
+ var isCss1CompatMode = goog.dom.getDomHelper(doc).isCss1CompatMode();
+ if (goog.userAgent.IE &&
+ (!isCss1CompatMode || !goog.userAgent.isVersionOrHigher('8'))) {
+ var style = element.style;
+ if (isCss1CompatMode) {
+ style.pixelWidth = size.width;
+ style.pixelHeight = size.height;
+ } else {
+ var paddingBox = goog.style.getPaddingBox(element);
+ var borderBox = goog.style.getBorderBox(element);
+ style.pixelWidth = size.width + borderBox.left + paddingBox.left +
+ paddingBox.right + borderBox.right;
+ style.pixelHeight = size.height + borderBox.top + paddingBox.top +
+ paddingBox.bottom + borderBox.bottom;
+ }
+ } else {
+ goog.style.setBoxSizingSize_(element, size, 'content-box');
+ }
+};
+
+
+/**
+ * Helper function that sets the box sizing as well as the width and height
+ * @param {Element} element The element to set the size on.
+ * @param {goog.math.Size} size The new size to set.
+ * @param {string} boxSizing The box-sizing value.
+ * @private
+ */
+goog.style.setBoxSizingSize_ = function(element, size, boxSizing) {
+ var style = element.style;
+ if (goog.userAgent.GECKO) {
+ style.MozBoxSizing = boxSizing;
+ } else if (goog.userAgent.WEBKIT) {
+ style.WebkitBoxSizing = boxSizing;
+ } else {
+ // Includes IE8 and Opera 9.50+
+ style.boxSizing = boxSizing;
+ }
+
+ // Setting this to a negative value will throw an exception on IE
+ // (and doesn't do anything different than setting it to 0).
+ style.width = Math.max(size.width, 0) + 'px';
+ style.height = Math.max(size.height, 0) + 'px';
+};
+
+
+/**
+ * IE specific function that converts a non pixel unit to pixels.
+ * @param {Element} element The element to convert the value for.
+ * @param {string} value The current value as a string. The value must not be
+ * ''.
+ * @param {string} name The CSS property name to use for the converstion. This
+ * should be 'left', 'top', 'width' or 'height'.
+ * @param {string} pixelName The CSS pixel property name to use to get the
+ * value in pixels.
+ * @return {number} The value in pixels.
+ * @private
+ */
+goog.style.getIePixelValue_ = function(element, value, name, pixelName) {
+ // Try if we already have a pixel value. IE does not do half pixels so we
+ // only check if it matches a number followed by 'px'.
+ if (/^\d+px?$/.test(value)) {
+ return parseInt(value, 10);
+ } else {
+ var oldStyleValue = element.style[name];
+ var oldRuntimeValue = element.runtimeStyle[name];
+ // set runtime style to prevent changes
+ element.runtimeStyle[name] = element.currentStyle[name];
+ element.style[name] = value;
+ var pixelValue = element.style[pixelName];
+ // restore
+ element.style[name] = oldStyleValue;
+ element.runtimeStyle[name] = oldRuntimeValue;
+ return pixelValue;
+ }
+};
+
+
+/**
+ * Helper function for getting the pixel padding or margin for IE.
+ * @param {Element} element The element to get the padding for.
+ * @param {string} propName The property name.
+ * @return {number} The pixel padding.
+ * @private
+ */
+goog.style.getIePixelDistance_ = function(element, propName) {
+ var value = goog.style.getCascadedStyle(element, propName);
+ return value ?
+ goog.style.getIePixelValue_(element, value, 'left', 'pixelLeft') : 0;
+};
+
+
+/**
+ * Gets the computed paddings or margins (on all sides) in pixels.
+ * @param {Element} element The element to get the padding for.
+ * @param {string} stylePrefix Pass 'padding' to retrieve the padding box,
+ * or 'margin' to retrieve the margin box.
+ * @return {!goog.math.Box} The computed paddings or margins.
+ * @private
+ */
+goog.style.getBox_ = function(element, stylePrefix) {
+ if (goog.userAgent.IE) {
+ var left = goog.style.getIePixelDistance_(element, stylePrefix + 'Left');
+ var right = goog.style.getIePixelDistance_(element, stylePrefix + 'Right');
+ var top = goog.style.getIePixelDistance_(element, stylePrefix + 'Top');
+ var bottom = goog.style.getIePixelDistance_(
+ element, stylePrefix + 'Bottom');
+ return new goog.math.Box(top, right, bottom, left);
+ } else {
+ // On non-IE browsers, getComputedStyle is always non-null.
+ var left = /** @type {string} */ (
+ goog.style.getComputedStyle(element, stylePrefix + 'Left'));
+ var right = /** @type {string} */ (
+ goog.style.getComputedStyle(element, stylePrefix + 'Right'));
+ var top = /** @type {string} */ (
+ goog.style.getComputedStyle(element, stylePrefix + 'Top'));
+ var bottom = /** @type {string} */ (
+ goog.style.getComputedStyle(element, stylePrefix + 'Bottom'));
+
+ // NOTE(arv): Gecko can return floating point numbers for the computed
+ // style values.
+ return new goog.math.Box(parseFloat(top),
+ parseFloat(right),
+ parseFloat(bottom),
+ parseFloat(left));
+ }
+};
+
+
+/**
+ * Gets the computed paddings (on all sides) in pixels.
+ * @param {Element} element The element to get the padding for.
+ * @return {!goog.math.Box} The computed paddings.
+ */
+goog.style.getPaddingBox = function(element) {
+ return goog.style.getBox_(element, 'padding');
+};
+
+
+/**
+ * Gets the computed margins (on all sides) in pixels.
+ * @param {Element} element The element to get the margins for.
+ * @return {!goog.math.Box} The computed margins.
+ */
+goog.style.getMarginBox = function(element) {
+ return goog.style.getBox_(element, 'margin');
+};
+
+
+/**
+ * A map used to map the border width keywords to a pixel width.
+ * @type {Object}
+ * @private
+ */
+goog.style.ieBorderWidthKeywords_ = {
+ 'thin': 2,
+ 'medium': 4,
+ 'thick': 6
+};
+
+
+/**
+ * Helper function for IE to get the pixel border.
+ * @param {Element} element The element to get the pixel border for.
+ * @param {string} prop The part of the property name.
+ * @return {number} The value in pixels.
+ * @private
+ */
+goog.style.getIePixelBorder_ = function(element, prop) {
+ if (goog.style.getCascadedStyle(element, prop + 'Style') == 'none') {
+ return 0;
+ }
+ var width = goog.style.getCascadedStyle(element, prop + 'Width');
+ if (width in goog.style.ieBorderWidthKeywords_) {
+ return goog.style.ieBorderWidthKeywords_[width];
+ }
+ return goog.style.getIePixelValue_(element, width, 'left', 'pixelLeft');
+};
+
+
+/**
+ * Gets the computed border widths (on all sides) in pixels
+ * @param {Element} element The element to get the border widths for.
+ * @return {!goog.math.Box} The computed border widths.
+ */
+goog.style.getBorderBox = function(element) {
+ if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9)) {
+ var left = goog.style.getIePixelBorder_(element, 'borderLeft');
+ var right = goog.style.getIePixelBorder_(element, 'borderRight');
+ var top = goog.style.getIePixelBorder_(element, 'borderTop');
+ var bottom = goog.style.getIePixelBorder_(element, 'borderBottom');
+ return new goog.math.Box(top, right, bottom, left);
+ } else {
+ // On non-IE browsers, getComputedStyle is always non-null.
+ var left = /** @type {string} */ (
+ goog.style.getComputedStyle(element, 'borderLeftWidth'));
+ var right = /** @type {string} */ (
+ goog.style.getComputedStyle(element, 'borderRightWidth'));
+ var top = /** @type {string} */ (
+ goog.style.getComputedStyle(element, 'borderTopWidth'));
+ var bottom = /** @type {string} */ (
+ goog.style.getComputedStyle(element, 'borderBottomWidth'));
+
+ return new goog.math.Box(parseFloat(top),
+ parseFloat(right),
+ parseFloat(bottom),
+ parseFloat(left));
+ }
+};
+
+
+/**
+ * Returns the font face applied to a given node. Opera and IE should return
+ * the font actually displayed. Firefox returns the author's most-preferred
+ * font (whether the browser is capable of displaying it or not.)
+ * @param {Element} el The element whose font family is returned.
+ * @return {string} The font family applied to el.
+ */
+goog.style.getFontFamily = function(el) {
+ var doc = goog.dom.getOwnerDocument(el);
+ var font = '';
+ // The moveToElementText method from the TextRange only works if the element
+ // is attached to the owner document.
+ if (doc.body.createTextRange && goog.dom.contains(doc, el)) {
+ var range = doc.body.createTextRange();
+ range.moveToElementText(el);
+ /** @preserveTry */
+ try {
+ font = range.queryCommandValue('FontName');
+ } catch (e) {
+ // This is a workaround for a awkward exception.
+ // On some IE, there is an exception coming from it.
+ // The error description from this exception is:
+ // This window has already been registered as a drop target
+ // This is bogus description, likely due to a bug in ie.
+ font = '';
+ }
+ }
+ if (!font) {
+ // Note if for some reason IE can't derive FontName with a TextRange, we
+ // fallback to using currentStyle
+ font = goog.style.getStyle_(el, 'fontFamily');
+ }
+
+ // Firefox returns the applied font-family string (author's list of
+ // preferred fonts.) We want to return the most-preferred font, in lieu of
+ // the *actually* applied font.
+ var fontsArray = font.split(',');
+ if (fontsArray.length > 1) font = fontsArray[0];
+
+ // Sanitize for x-browser consistency:
+ // Strip quotes because browsers aren't consistent with how they're
+ // applied; Opera always encloses, Firefox sometimes, and IE never.
+ return goog.string.stripQuotes(font, '"\'');
+};
+
+
+/**
+ * Regular expression used for getLengthUnits.
+ * @type {RegExp}
+ * @private
+ */
+goog.style.lengthUnitRegex_ = /[^\d]+$/;
+
+
+/**
+ * Returns the units used for a CSS length measurement.
+ * @param {string} value A CSS length quantity.
+ * @return {?string} The units of measurement.
+ */
+goog.style.getLengthUnits = function(value) {
+ var units = value.match(goog.style.lengthUnitRegex_);
+ return units && units[0] || null;
+};
+
+
+/**
+ * Map of absolute CSS length units
+ * @type {Object}
+ * @private
+ */
+goog.style.ABSOLUTE_CSS_LENGTH_UNITS_ = {
+ 'cm' : 1,
+ 'in' : 1,
+ 'mm' : 1,
+ 'pc' : 1,
+ 'pt' : 1
+};
+
+
+/**
+ * Map of relative CSS length units that can be accurately converted to px
+ * font-size values using getIePixelValue_. Only units that are defined in
+ * relation to a font size are convertible (%, small, etc. are not).
+ * @type {Object}
+ * @private
+ */
+goog.style.CONVERTIBLE_RELATIVE_CSS_UNITS_ = {
+ 'em' : 1,
+ 'ex' : 1
+};
+
+
+/**
+ * Returns the font size, in pixels, of text in an element.
+ * @param {Element} el The element whose font size is returned.
+ * @return {number} The font size (in pixels).
+ */
+goog.style.getFontSize = function(el) {
+ var fontSize = goog.style.getStyle_(el, 'fontSize');
+ var sizeUnits = goog.style.getLengthUnits(fontSize);
+ if (fontSize && 'px' == sizeUnits) {
+ // NOTE(user): This could be parseFloat instead, but IE doesn't return
+ // decimal fractions in getStyle_ and Firefox reports the fractions, but
+ // ignores them when rendering. Interestingly enough, when we force the
+ // issue and size something to e.g., 50% of 25px, the browsers round in
+ // opposite directions with Firefox reporting 12px and IE 13px. I punt.
+ return parseInt(fontSize, 10);
+ }
+
+ // In IE, we can convert absolute length units to a px value using
+ // goog.style.getIePixelValue_. Units defined in relation to a font size
+ // (em, ex) are applied relative to the element's parentNode and can also
+ // be converted.
+ if (goog.userAgent.IE) {
+ if (sizeUnits in goog.style.ABSOLUTE_CSS_LENGTH_UNITS_) {
+ return goog.style.getIePixelValue_(el,
+ fontSize,
+ 'left',
+ 'pixelLeft');
+ } else if (el.parentNode &&
+ el.parentNode.nodeType == goog.dom.NodeType.ELEMENT &&
+ sizeUnits in goog.style.CONVERTIBLE_RELATIVE_CSS_UNITS_) {
+ // Check the parent size - if it is the same it means the relative size
+ // value is inherited and we therefore don't want to count it twice. If
+ // it is different, this element either has explicit style or has a CSS
+ // rule applying to it.
+ var parentElement = /** @type {Element} */ (el.parentNode);
+ var parentSize = goog.style.getStyle_(parentElement, 'fontSize');
+ return goog.style.getIePixelValue_(parentElement,
+ fontSize == parentSize ?
+ '1em' : fontSize,
+ 'left',
+ 'pixelLeft');
+ }
+ }
+
+ // Sometimes we can't cleanly find the font size (some units relative to a
+ // node's parent's font size are difficult: %, smaller et al), so we create
+ // an invisible, absolutely-positioned span sized to be the height of an 'M'
+ // rendered in its parent's (i.e., our target element's) font size. This is
+ // the definition of CSS's font size attribute.
+ var sizeElement = goog.dom.createDom(
+ 'span',
+ {'style': 'visibility:hidden;position:absolute;' +
+ 'line-height:0;padding:0;margin:0;border:0;height:1em;'});
+ goog.dom.appendChild(el, sizeElement);
+ fontSize = sizeElement.offsetHeight;
+ goog.dom.removeNode(sizeElement);
+
+ return fontSize;
+};
+
+
+/**
+ * Parses a style attribute value. Converts CSS property names to camel case.
+ * @param {string} value The style attribute value.
+ * @return {!Object} Map of CSS properties to string values.
+ */
+goog.style.parseStyleAttribute = function(value) {
+ var result = {};
+ goog.array.forEach(value.split(/\s*;\s*/), function(pair) {
+ var keyValue = pair.split(/\s*:\s*/);
+ if (keyValue.length == 2) {
+ result[goog.string.toCamelCase(keyValue[0].toLowerCase())] = keyValue[1];
+ }
+ });
+ return result;
+};
+
+
+/**
+ * Reverse of parseStyleAttribute; that is, takes a style object and returns the
+ * corresponding attribute value. Converts camel case property names to proper
+ * CSS selector names.
+ * @param {Object} obj Map of CSS properties to values.
+ * @return {string} The style attribute value.
+ */
+goog.style.toStyleAttribute = function(obj) {
+ var buffer = [];
+ goog.object.forEach(obj, function(value, key) {
+ buffer.push(goog.string.toSelectorCase(key), ':', value, ';');
+ });
+ return buffer.join('');
+};
+
+
+/**
+ * Sets CSS float property on an element.
+ * @param {Element} el The element to set float property on.
+ * @param {string} value The value of float CSS property to set on this element.
+ */
+goog.style.setFloat = function(el, value) {
+ el.style[goog.userAgent.IE ? 'styleFloat' : 'cssFloat'] = value;
+};
+
+
+/**
+ * Gets value of explicitly-set float CSS property on an element.
+ * @param {Element} el The element to get float property of.
+ * @return {string} The value of explicitly-set float CSS property on this
+ * element.
+ */
+goog.style.getFloat = function(el) {
+ return el.style[goog.userAgent.IE ? 'styleFloat' : 'cssFloat'] || '';
+};
+
+
+/**
+ * Returns the scroll bar width (represents the width of both horizontal
+ * and vertical scroll).
+ *
+ * @param {string=} opt_className An optional class name (or names) to apply
+ * to the invisible div created to measure the scrollbar. This is necessary
+ * if some scrollbars are styled differently than others.
+ * @return {number} The scroll bar width in px.
+ */
+goog.style.getScrollbarWidth = function(opt_className) {
+ // Add two hidden divs. The child div is larger than the parent and
+ // forces scrollbars to appear on it.
+ // Using overflow:scroll does not work consistently with scrollbars that
+ // are styled with ::-webkit-scrollbar.
+ var outerDiv = goog.dom.createElement('div');
+ if (opt_className) {
+ outerDiv.className = opt_className;
+ }
+ outerDiv.style.cssText = 'overflow:auto;' +
+ 'position:absolute;top:0;width:100px;height:100px';
+ var innerDiv = goog.dom.createElement('div');
+ goog.style.setSize(innerDiv, '200px', '200px');
+ outerDiv.appendChild(innerDiv);
+ goog.dom.appendChild(goog.dom.getDocument().body, outerDiv);
+ var width = outerDiv.offsetWidth - outerDiv.clientWidth;
+ goog.dom.removeNode(outerDiv);
+ return width;
+};
+
+
+/**
+ * Regular expression to extract x and y translation components from a CSS
+ * transform Matrix representation.
+ *
+ * @type {!RegExp}
+ * @const
+ * @private
+ */
+goog.style.MATRIX_TRANSLATION_REGEX_ =
+ new RegExp('matrix\\([0-9\\.\\-]+, [0-9\\.\\-]+, ' +
+ '[0-9\\.\\-]+, [0-9\\.\\-]+, ' +
+ '([0-9\\.\\-]+)p?x?, ([0-9\\.\\-]+)p?x?\\)');
+
+
+/**
+ * Returns the x,y translation component of any CSS transforms applied to the
+ * element, in pixels.
+ *
+ * @param {!Element} element The element to get the translation of.
+ * @return {!goog.math.Coordinate} The CSS translation of the element in px.
+ */
+goog.style.getCssTranslation = function(element) {
+ var transform = goog.style.getComputedTransform(element);
+ if (!transform) {
+ return new goog.math.Coordinate(0, 0);
+ }
+ var matches = transform.match(goog.style.MATRIX_TRANSLATION_REGEX_);
+ if (!matches) {
+ return new goog.math.Coordinate(0, 0);
+ }
+ return new goog.math.Coordinate(parseFloat(matches[1]),
+ parseFloat(matches[2]));
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/testing/watchers.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/testing/watchers.js
new file mode 100644
index 0000000..a69dc3e
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/testing/watchers.js
@@ -0,0 +1,46 @@
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Simple notifiers for the Closure testing framework.
+ *
+ * @author johnlenz@google.com (John Lenz)
+ */
+
+goog.provide('goog.testing.watchers');
+
+
+/** @private {!Array.<function()>} */
+goog.testing.watchers.resetWatchers_ = [];
+
+
+/**
+ * Fires clock reset watching functions.
+ */
+goog.testing.watchers.signalClockReset = function() {
+ var watchers = goog.testing.watchers.resetWatchers_;
+ for (var i = 0; i < watchers.length; i++) {
+ goog.testing.watchers.resetWatchers_[i]();
+ }
+};
+
+
+/**
+ * Enqueues a function to be called when the clock used for setTimeout is reset.
+ * @param {function()} fn
+ */
+goog.testing.watchers.watchClockReset = function(fn) {
+ goog.testing.watchers.resetWatchers_.push(fn);
+};
+
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/timer/timer.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/timer/timer.js
new file mode 100644
index 0000000..ccee170
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/timer/timer.js
@@ -0,0 +1,292 @@
+// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview A timer class to which other classes and objects can
+ * listen on. This is only an abstraction above setInterval.
+ *
+ * @see ../demos/timers.html
+ */
+
+goog.provide('goog.Timer');
+
+goog.require('goog.events.EventTarget');
+
+
+
+/**
+ * Class for handling timing events.
+ *
+ * @param {number=} opt_interval Number of ms between ticks (Default: 1ms).
+ * @param {Object=} opt_timerObject An object that has setTimeout, setInterval,
+ * clearTimeout and clearInterval (eg Window).
+ * @constructor
+ * @extends {goog.events.EventTarget}
+ */
+goog.Timer = function(opt_interval, opt_timerObject) {
+ goog.events.EventTarget.call(this);
+
+ /**
+ * Number of ms between ticks
+ * @type {number}
+ * @private
+ */
+ this.interval_ = opt_interval || 1;
+
+ /**
+ * An object that implements setTimeout, setInterval, clearTimeout and
+ * clearInterval. We default to the window object. Changing this on
+ * goog.Timer.prototype changes the object for all timer instances which can
+ * be useful if your environment has some other implementation of timers than
+ * the window object.
+ * @type {Object}
+ * @private
+ */
+ this.timerObject_ = opt_timerObject || goog.Timer.defaultTimerObject;
+
+ /**
+ * Cached tick_ bound to the object for later use in the timer.
+ * @type {Function}
+ * @private
+ */
+ this.boundTick_ = goog.bind(this.tick_, this);
+
+ /**
+ * Firefox browser often fires the timer event sooner
+ * (sometimes MUCH sooner) than the requested timeout. So we
+ * compare the time to when the event was last fired, and
+ * reschedule if appropriate. See also goog.Timer.intervalScale
+ * @type {number}
+ * @private
+ */
+ this.last_ = goog.now();
+};
+goog.inherits(goog.Timer, goog.events.EventTarget);
+
+
+/**
+ * Maximum timeout value.
+ *
+ * Timeout values too big to fit into a signed 32-bit integer may cause
+ * overflow in FF, Safari, and Chrome, resulting in the timeout being
+ * scheduled immediately. It makes more sense simply not to schedule these
+ * timeouts, since 24.8 days is beyond a reasonable expectation for the
+ * browser to stay open.
+ *
+ * @type {number}
+ * @private
+ */
+goog.Timer.MAX_TIMEOUT_ = 2147483647;
+
+
+/**
+ * Whether this timer is enabled
+ * @type {boolean}
+ */
+goog.Timer.prototype.enabled = false;
+
+
+/**
+ * An object that implements setTimout, setInterval, clearTimeout and
+ * clearInterval. We default to the global object. Changing
+ * goog.Timer.defaultTimerObject changes the object for all timer instances
+ * which can be useful if your environment has some other implementation of
+ * timers you'd like to use.
+ * @type {Object}
+ */
+goog.Timer.defaultTimerObject = goog.global;
+
+
+/**
+ * A variable that controls the timer error correction. If the
+ * timer is called before the requested interval times
+ * intervalScale, which often happens on mozilla, the timer is
+ * rescheduled. See also this.last_
+ * @type {number}
+ */
+goog.Timer.intervalScale = 0.8;
+
+
+/**
+ * Variable for storing the result of setInterval
+ * @type {?number}
+ * @private
+ */
+goog.Timer.prototype.timer_ = null;
+
+
+/**
+ * Gets the interval of the timer.
+ * @return {number} interval Number of ms between ticks.
+ */
+goog.Timer.prototype.getInterval = function() {
+ return this.interval_;
+};
+
+
+/**
+ * Sets the interval of the timer.
+ * @param {number} interval Number of ms between ticks.
+ */
+goog.Timer.prototype.setInterval = function(interval) {
+ this.interval_ = interval;
+ if (this.timer_ && this.enabled) {
+ // Stop and then start the timer to reset the interval.
+ this.stop();
+ this.start();
+ } else if (this.timer_) {
+ this.stop();
+ }
+};
+
+
+/**
+ * Callback for the setTimeout used by the timer
+ * @private
+ */
+goog.Timer.prototype.tick_ = function() {
+ if (this.enabled) {
+ var elapsed = goog.now() - this.last_;
+ if (elapsed > 0 &&
+ elapsed < this.interval_ * goog.Timer.intervalScale) {
+ this.timer_ = this.timerObject_.setTimeout(this.boundTick_,
+ this.interval_ - elapsed);
+ return;
+ }
+
+ // Prevents setInterval from registering a duplicate timeout when called
+ // in the timer event handler.
+ if (this.timer_) {
+ this.timerObject_.clearTimeout(this.timer_);
+ this.timer_ = null;
+ }
+
+ this.dispatchTick();
+ // The timer could be stopped in the timer event handler.
+ if (this.enabled) {
+ this.timer_ = this.timerObject_.setTimeout(this.boundTick_,
+ this.interval_);
+ this.last_ = goog.now();
+ }
+ }
+};
+
+
+/**
+ * Dispatches the TICK event. This is its own method so subclasses can override.
+ */
+goog.Timer.prototype.dispatchTick = function() {
+ this.dispatchEvent(goog.Timer.TICK);
+};
+
+
+/**
+ * Starts the timer.
+ */
+goog.Timer.prototype.start = function() {
+ this.enabled = true;
+
+ // If there is no interval already registered, start it now
+ if (!this.timer_) {
+ // IMPORTANT!
+ // window.setInterval in FireFox has a bug - it fires based on
+ // absolute time, rather than on relative time. What this means
+ // is that if a computer is sleeping/hibernating for 24 hours
+ // and the timer interval was configured to fire every 1000ms,
+ // then after the PC wakes up the timer will fire, in rapid
+ // succession, 3600*24 times.
+ // This bug is described here and is already fixed, but it will
+ // take time to propagate, so for now I am switching this over
+ // to setTimeout logic.
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=376643
+ //
+ this.timer_ = this.timerObject_.setTimeout(this.boundTick_,
+ this.interval_);
+ this.last_ = goog.now();
+ }
+};
+
+
+/**
+ * Stops the timer.
+ */
+goog.Timer.prototype.stop = function() {
+ this.enabled = false;
+ if (this.timer_) {
+ this.timerObject_.clearTimeout(this.timer_);
+ this.timer_ = null;
+ }
+};
+
+
+/** @override */
+goog.Timer.prototype.disposeInternal = function() {
+ goog.Timer.superClass_.disposeInternal.call(this);
+ this.stop();
+ delete this.timerObject_;
+};
+
+
+/**
+ * Constant for the timer's event type
+ * @type {string}
+ */
+goog.Timer.TICK = 'tick';
+
+
+/**
+ * Calls the given function once, after the optional pause.
+ *
+ * The function is always called asynchronously, even if the delay is 0. This
+ * is a common trick to schedule a function to run after a batch of browser
+ * event processing.
+ *
+ * @param {function(this:SCOPE)|{handleEvent:function()}|null} listener Function
+ * or object that has a handleEvent method.
+ * @param {number=} opt_delay Milliseconds to wait; default is 0.
+ * @param {SCOPE=} opt_handler Object in whose scope to call the listener.
+ * @return {number} A handle to the timer ID.
+ * @template SCOPE
+ */
+goog.Timer.callOnce = function(listener, opt_delay, opt_handler) {
+ if (goog.isFunction(listener)) {
+ if (opt_handler) {
+ listener = goog.bind(listener, opt_handler);
+ }
+ } else if (listener && typeof listener.handleEvent == 'function') {
+ // using typeof to prevent strict js warning
+ listener = goog.bind(listener.handleEvent, listener);
+ } else {
+ throw Error('Invalid listener argument');
+ }
+
+ if (opt_delay > goog.Timer.MAX_TIMEOUT_) {
+ // Timeouts greater than MAX_INT return immediately due to integer
+ // overflow in many browsers. Since MAX_INT is 24.8 days, just don't
+ // schedule anything at all.
+ return -1;
+ } else {
+ return goog.Timer.defaultTimerObject.setTimeout(
+ listener, opt_delay || 0);
+ }
+};
+
+
+/**
+ * Clears a timeout initiated by callOnce
+ * @param {?number} timerId a timer ID.
+ */
+goog.Timer.clear = function(timerId) {
+ goog.Timer.defaultTimerObject.clearTimeout(timerId);
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/component.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/component.js
new file mode 100644
index 0000000..fd8656b
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/component.js
@@ -0,0 +1,1291 @@
+// Copyright 2007 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Abstract class for all UI components. This defines the standard
+ * design pattern that all UI components should follow.
+ *
+ * @see ../demos/samplecomponent.html
+ * @see http://code.google.com/p/closure-library/wiki/IntroToComponents
+ */
+
+goog.provide('goog.ui.Component');
+goog.provide('goog.ui.Component.Error');
+goog.provide('goog.ui.Component.EventType');
+goog.provide('goog.ui.Component.State');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.dom');
+goog.require('goog.dom.NodeType');
+goog.require('goog.events.EventHandler');
+goog.require('goog.events.EventTarget');
+goog.require('goog.object');
+goog.require('goog.style');
+goog.require('goog.ui.IdGenerator');
+
+
+
+/**
+ * Default implementation of UI component.
+ *
+ * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper.
+ * @constructor
+ * @extends {goog.events.EventTarget}
+ */
+goog.ui.Component = function(opt_domHelper) {
+ goog.events.EventTarget.call(this);
+ /**
+ * DomHelper used to interact with the document, allowing components to be
+ * created in a different window.
+ * @protected {!goog.dom.DomHelper}
+ * @suppress {underscore|visibility}
+ */
+ this.dom_ = opt_domHelper || goog.dom.getDomHelper();
+
+ /**
+ * Whether the component is rendered right-to-left. Right-to-left is set
+ * lazily when {@link #isRightToLeft} is called the first time, unless it has
+ * been set by calling {@link #setRightToLeft} explicitly.
+ * @private {?boolean}
+ */
+ this.rightToLeft_ = goog.ui.Component.defaultRightToLeft_;
+
+ /**
+ * Unique ID of the component, lazily initialized in {@link
+ * goog.ui.Component#getId} if needed. This property is strictly private and
+ * must not be accessed directly outside of this class!
+ * @private {?string}
+ */
+ this.id_ = null;
+
+ /**
+ * Whether the component is in the document.
+ * @private {boolean}
+ */
+ this.inDocument_ = false;
+
+ // TODO(attila): Stop referring to this private field in subclasses.
+ /**
+ * The DOM element for the component.
+ * @private {Element}
+ */
+ this.element_ = null;
+
+ /**
+ * Event handler.
+ * TODO(user): rename it to handler_ after all component subclasses in
+ * inside Google have been cleaned up.
+ * Code search: http://go/component_code_search
+ * @private {goog.events.EventHandler|undefined}
+ */
+ this.googUiComponentHandler_ = void 0;
+
+ /**
+ * Arbitrary data object associated with the component. Such as meta-data.
+ * @private {*}
+ */
+ this.model_ = null;
+
+ /**
+ * Parent component to which events will be propagated. This property is
+ * strictly private and must not be accessed directly outside of this class!
+ * @private {goog.ui.Component?}
+ */
+ this.parent_ = null;
+
+ /**
+ * Array of child components. Lazily initialized on first use. Must be kept
+ * in sync with {@code childIndex_}. This property is strictly private and
+ * must not be accessed directly outside of this class!
+ * @private {Array.<goog.ui.Component>?}
+ */
+ this.children_ = null;
+
+ /**
+ * Map of child component IDs to child components. Used for constant-time
+ * random access to child components by ID. Lazily initialized on first use.
+ * Must be kept in sync with {@code children_}. This property is strictly
+ * private and must not be accessed directly outside of this class!
+ *
+ * We use a plain Object, not a {@link goog.structs.Map}, for simplicity.
+ * This means components can't have children with IDs such as 'constructor' or
+ * 'valueOf', but this shouldn't really be an issue in practice, and if it is,
+ * we can always fix it later without changing the API.
+ *
+ * @private {Object}
+ */
+ this.childIndex_ = null;
+
+ /**
+ * Flag used to keep track of whether a component decorated an already
+ * existing element or whether it created the DOM itself.
+ *
+ * If an element is decorated, dispose will leave the node in the document.
+ * It is up to the app to remove the node.
+ *
+ * If an element was rendered, dispose will remove the node automatically.
+ *
+ * @private {boolean}
+ */
+ this.wasDecorated_ = false;
+};
+goog.inherits(goog.ui.Component, goog.events.EventTarget);
+
+
+/**
+ * @define {boolean} Whether to support calling decorate with an element that is
+ * not yet in the document. If true, we check if the element is in the
+ * document, and avoid calling enterDocument if it isn't. If false, we
+ * maintain legacy behavior (always call enterDocument from decorate).
+ */
+goog.define('goog.ui.Component.ALLOW_DETACHED_DECORATION', false);
+
+
+/**
+ * Generator for unique IDs.
+ * @type {goog.ui.IdGenerator}
+ * @private
+ */
+goog.ui.Component.prototype.idGenerator_ = goog.ui.IdGenerator.getInstance();
+
+
+// TODO(gboyer): See if we can remove this and just check goog.i18n.bidi.IS_RTL.
+/**
+ * @define {number} Defines the default BIDI directionality.
+ * 0: Unknown.
+ * 1: Left-to-right.
+ * -1: Right-to-left.
+ */
+goog.define('goog.ui.Component.DEFAULT_BIDI_DIR', 0);
+
+
+/**
+ * The default right to left value.
+ * @type {?boolean}
+ * @private
+ */
+goog.ui.Component.defaultRightToLeft_ =
+ (goog.ui.Component.DEFAULT_BIDI_DIR == 1) ? false :
+ (goog.ui.Component.DEFAULT_BIDI_DIR == -1) ? true : null;
+
+
+/**
+ * Common events fired by components so that event propagation is useful. Not
+ * all components are expected to dispatch or listen for all event types.
+ * Events dispatched before a state transition should be cancelable to prevent
+ * the corresponding state change.
+ * @enum {string}
+ */
+goog.ui.Component.EventType = {
+ /** Dispatched before the component becomes visible. */
+ BEFORE_SHOW: 'beforeshow',
+
+ /**
+ * Dispatched after the component becomes visible.
+ * NOTE(user): For goog.ui.Container, this actually fires before containers
+ * are shown. Use goog.ui.Container.EventType.AFTER_SHOW if you want an event
+ * that fires after a goog.ui.Container is shown.
+ */
+ SHOW: 'show',
+
+ /** Dispatched before the component becomes hidden. */
+ HIDE: 'hide',
+
+ /** Dispatched before the component becomes disabled. */
+ DISABLE: 'disable',
+
+ /** Dispatched before the component becomes enabled. */
+ ENABLE: 'enable',
+
+ /** Dispatched before the component becomes highlighted. */
+ HIGHLIGHT: 'highlight',
+
+ /** Dispatched before the component becomes un-highlighted. */
+ UNHIGHLIGHT: 'unhighlight',
+
+ /** Dispatched before the component becomes activated. */
+ ACTIVATE: 'activate',
+
+ /** Dispatched before the component becomes deactivated. */
+ DEACTIVATE: 'deactivate',
+
+ /** Dispatched before the component becomes selected. */
+ SELECT: 'select',
+
+ /** Dispatched before the component becomes un-selected. */
+ UNSELECT: 'unselect',
+
+ /** Dispatched before a component becomes checked. */
+ CHECK: 'check',
+
+ /** Dispatched before a component becomes un-checked. */
+ UNCHECK: 'uncheck',
+
+ /** Dispatched before a component becomes focused. */
+ FOCUS: 'focus',
+
+ /** Dispatched before a component becomes blurred. */
+ BLUR: 'blur',
+
+ /** Dispatched before a component is opened (expanded). */
+ OPEN: 'open',
+
+ /** Dispatched before a component is closed (collapsed). */
+ CLOSE: 'close',
+
+ /** Dispatched after a component is moused over. */
+ ENTER: 'enter',
+
+ /** Dispatched after a component is moused out of. */
+ LEAVE: 'leave',
+
+ /** Dispatched after the user activates the component. */
+ ACTION: 'action',
+
+ /** Dispatched after the external-facing state of a component is changed. */
+ CHANGE: 'change'
+};
+
+
+/**
+ * Errors thrown by the component.
+ * @enum {string}
+ */
+goog.ui.Component.Error = {
+ /**
+ * Error when a method is not supported.
+ */
+ NOT_SUPPORTED: 'Method not supported',
+
+ /**
+ * Error when the given element can not be decorated.
+ */
+ DECORATE_INVALID: 'Invalid element to decorate',
+
+ /**
+ * Error when the component is already rendered and another render attempt is
+ * made.
+ */
+ ALREADY_RENDERED: 'Component already rendered',
+
+ /**
+ * Error when an attempt is made to set the parent of a component in a way
+ * that would result in an inconsistent object graph.
+ */
+ PARENT_UNABLE_TO_BE_SET: 'Unable to set parent component',
+
+ /**
+ * Error when an attempt is made to add a child component at an out-of-bounds
+ * index. We don't support sparse child arrays.
+ */
+ CHILD_INDEX_OUT_OF_BOUNDS: 'Child component index out of bounds',
+
+ /**
+ * Error when an attempt is made to remove a child component from a component
+ * other than its parent.
+ */
+ NOT_OUR_CHILD: 'Child is not in parent component',
+
+ /**
+ * Error when an operation requiring DOM interaction is made when the
+ * component is not in the document
+ */
+ NOT_IN_DOCUMENT: 'Operation not supported while component is not in document',
+
+ /**
+ * Error when an invalid component state is encountered.
+ */
+ STATE_INVALID: 'Invalid component state'
+};
+
+
+/**
+ * Common component states. Components may have distinct appearance depending
+ * on what state(s) apply to them. Not all components are expected to support
+ * all states.
+ * @enum {number}
+ */
+goog.ui.Component.State = {
+ /**
+ * Union of all supported component states.
+ */
+ ALL: 0xFF,
+
+ /**
+ * Component is disabled.
+ * @see goog.ui.Component.EventType.DISABLE
+ * @see goog.ui.Component.EventType.ENABLE
+ */
+ DISABLED: 0x01,
+
+ /**
+ * Component is highlighted.
+ * @see goog.ui.Component.EventType.HIGHLIGHT
+ * @see goog.ui.Component.EventType.UNHIGHLIGHT
+ */
+ HOVER: 0x02,
+
+ /**
+ * Component is active (or "pressed").
+ * @see goog.ui.Component.EventType.ACTIVATE
+ * @see goog.ui.Component.EventType.DEACTIVATE
+ */
+ ACTIVE: 0x04,
+
+ /**
+ * Component is selected.
+ * @see goog.ui.Component.EventType.SELECT
+ * @see goog.ui.Component.EventType.UNSELECT
+ */
+ SELECTED: 0x08,
+
+ /**
+ * Component is checked.
+ * @see goog.ui.Component.EventType.CHECK
+ * @see goog.ui.Component.EventType.UNCHECK
+ */
+ CHECKED: 0x10,
+
+ /**
+ * Component has focus.
+ * @see goog.ui.Component.EventType.FOCUS
+ * @see goog.ui.Component.EventType.BLUR
+ */
+ FOCUSED: 0x20,
+
+ /**
+ * Component is opened (expanded). Applies to tree nodes, menu buttons,
+ * submenus, zippys (zippies?), etc.
+ * @see goog.ui.Component.EventType.OPEN
+ * @see goog.ui.Component.EventType.CLOSE
+ */
+ OPENED: 0x40
+};
+
+
+/**
+ * Static helper method; returns the type of event components are expected to
+ * dispatch when transitioning to or from the given state.
+ * @param {goog.ui.Component.State} state State to/from which the component
+ * is transitioning.
+ * @param {boolean} isEntering Whether the component is entering or leaving the
+ * state.
+ * @return {goog.ui.Component.EventType} Event type to dispatch.
+ */
+goog.ui.Component.getStateTransitionEvent = function(state, isEntering) {
+ switch (state) {
+ case goog.ui.Component.State.DISABLED:
+ return isEntering ? goog.ui.Component.EventType.DISABLE :
+ goog.ui.Component.EventType.ENABLE;
+ case goog.ui.Component.State.HOVER:
+ return isEntering ? goog.ui.Component.EventType.HIGHLIGHT :
+ goog.ui.Component.EventType.UNHIGHLIGHT;
+ case goog.ui.Component.State.ACTIVE:
+ return isEntering ? goog.ui.Component.EventType.ACTIVATE :
+ goog.ui.Component.EventType.DEACTIVATE;
+ case goog.ui.Component.State.SELECTED:
+ return isEntering ? goog.ui.Component.EventType.SELECT :
+ goog.ui.Component.EventType.UNSELECT;
+ case goog.ui.Component.State.CHECKED:
+ return isEntering ? goog.ui.Component.EventType.CHECK :
+ goog.ui.Component.EventType.UNCHECK;
+ case goog.ui.Component.State.FOCUSED:
+ return isEntering ? goog.ui.Component.EventType.FOCUS :
+ goog.ui.Component.EventType.BLUR;
+ case goog.ui.Component.State.OPENED:
+ return isEntering ? goog.ui.Component.EventType.OPEN :
+ goog.ui.Component.EventType.CLOSE;
+ default:
+ // Fall through.
+ }
+
+ // Invalid state.
+ throw Error(goog.ui.Component.Error.STATE_INVALID);
+};
+
+
+/**
+ * Set the default right-to-left value. This causes all component's created from
+ * this point foward to have the given value. This is useful for cases where
+ * a given page is always in one directionality, avoiding unnecessary
+ * right to left determinations.
+ * @param {?boolean} rightToLeft Whether the components should be rendered
+ * right-to-left. Null iff components should determine their directionality.
+ */
+goog.ui.Component.setDefaultRightToLeft = function(rightToLeft) {
+ goog.ui.Component.defaultRightToLeft_ = rightToLeft;
+};
+
+
+/**
+ * Gets the unique ID for the instance of this component. If the instance
+ * doesn't already have an ID, generates one on the fly.
+ * @return {string} Unique component ID.
+ */
+goog.ui.Component.prototype.getId = function() {
+ return this.id_ || (this.id_ = this.idGenerator_.getNextUniqueId());
+};
+
+
+/**
+ * Assigns an ID to this component instance. It is the caller's responsibility
+ * to guarantee that the ID is unique. If the component is a child of a parent
+ * component, then the parent component's child index is updated to reflect the
+ * new ID; this may throw an error if the parent already has a child with an ID
+ * that conflicts with the new ID.
+ * @param {string} id Unique component ID.
+ */
+goog.ui.Component.prototype.setId = function(id) {
+ if (this.parent_ && this.parent_.childIndex_) {
+ // Update the parent's child index.
+ goog.object.remove(this.parent_.childIndex_, this.id_);
+ goog.object.add(this.parent_.childIndex_, id, this);
+ }
+
+ // Update the component ID.
+ this.id_ = id;
+};
+
+
+/**
+ * Gets the component's element.
+ * @return {Element} The element for the component.
+ */
+goog.ui.Component.prototype.getElement = function() {
+ return this.element_;
+};
+
+
+/**
+ * Gets the component's element. This differs from getElement in that
+ * it assumes that the element exists (i.e. the component has been
+ * rendered/decorated) and will cause an assertion error otherwise (if
+ * assertion is enabled).
+ * @return {!Element} The element for the component.
+ */
+goog.ui.Component.prototype.getElementStrict = function() {
+ var el = this.element_;
+ goog.asserts.assert(
+ el, 'Can not call getElementStrict before rendering/decorating.');
+ return el;
+};
+
+
+/**
+ * Sets the component's root element to the given element. Considered
+ * protected and final.
+ *
+ * This should generally only be called during createDom. Setting the element
+ * does not actually change which element is rendered, only the element that is
+ * associated with this UI component.
+ *
+ * This should only be used by subclasses and its associated renderers.
+ *
+ * @param {Element} element Root element for the component.
+ */
+goog.ui.Component.prototype.setElementInternal = function(element) {
+ this.element_ = element;
+};
+
+
+/**
+ * Returns an array of all the elements in this component's DOM with the
+ * provided className.
+ * @param {string} className The name of the class to look for.
+ * @return {!goog.array.ArrayLike} The items found with the class name provided.
+ */
+goog.ui.Component.prototype.getElementsByClass = function(className) {
+ return this.element_ ?
+ this.dom_.getElementsByClass(className, this.element_) : [];
+};
+
+
+/**
+ * Returns the first element in this component's DOM with the provided
+ * className.
+ * @param {string} className The name of the class to look for.
+ * @return {Element} The first item with the class name provided.
+ */
+goog.ui.Component.prototype.getElementByClass = function(className) {
+ return this.element_ ?
+ this.dom_.getElementByClass(className, this.element_) : null;
+};
+
+
+/**
+ * Similar to {@code getElementByClass} except that it expects the
+ * element to be present in the dom thus returning a required value. Otherwise,
+ * will assert.
+ * @param {string} className The name of the class to look for.
+ * @return {!Element} The first item with the class name provided.
+ */
+goog.ui.Component.prototype.getRequiredElementByClass = function(className) {
+ var el = this.getElementByClass(className);
+ goog.asserts.assert(el, 'Expected element in component with class: %s',
+ className);
+ return el;
+};
+
+
+/**
+ * Returns the event handler for this component, lazily created the first time
+ * this method is called.
+ * @return {!goog.events.EventHandler.<T>} Event handler for this component.
+ * @protected
+ * @this T
+ * @template T
+ */
+goog.ui.Component.prototype.getHandler = function() {
+ if (!this.googUiComponentHandler_) {
+ this.googUiComponentHandler_ = new goog.events.EventHandler(this);
+ }
+ return this.googUiComponentHandler_;
+};
+
+
+/**
+ * Sets the parent of this component to use for event bubbling. Throws an error
+ * if the component already has a parent or if an attempt is made to add a
+ * component to itself as a child. Callers must use {@code removeChild}
+ * or {@code removeChildAt} to remove components from their containers before
+ * calling this method.
+ * @see goog.ui.Component#removeChild
+ * @see goog.ui.Component#removeChildAt
+ * @param {goog.ui.Component} parent The parent component.
+ */
+goog.ui.Component.prototype.setParent = function(parent) {
+ if (this == parent) {
+ // Attempting to add a child to itself is an error.
+ throw Error(goog.ui.Component.Error.PARENT_UNABLE_TO_BE_SET);
+ }
+
+ if (parent && this.parent_ && this.id_ && this.parent_.getChild(this.id_) &&
+ this.parent_ != parent) {
+ // This component is already the child of some parent, so it should be
+ // removed using removeChild/removeChildAt first.
+ throw Error(goog.ui.Component.Error.PARENT_UNABLE_TO_BE_SET);
+ }
+
+ this.parent_ = parent;
+ goog.ui.Component.superClass_.setParentEventTarget.call(this, parent);
+};
+
+
+/**
+ * Returns the component's parent, if any.
+ * @return {goog.ui.Component?} The parent component.
+ */
+goog.ui.Component.prototype.getParent = function() {
+ return this.parent_;
+};
+
+
+/**
+ * Overrides {@link goog.events.EventTarget#setParentEventTarget} to throw an
+ * error if the parent component is set, and the argument is not the parent.
+ * @override
+ */
+goog.ui.Component.prototype.setParentEventTarget = function(parent) {
+ if (this.parent_ && this.parent_ != parent) {
+ throw Error(goog.ui.Component.Error.NOT_SUPPORTED);
+ }
+ goog.ui.Component.superClass_.setParentEventTarget.call(this, parent);
+};
+
+
+/**
+ * Returns the dom helper that is being used on this component.
+ * @return {!goog.dom.DomHelper} The dom helper used on this component.
+ */
+goog.ui.Component.prototype.getDomHelper = function() {
+ return this.dom_;
+};
+
+
+/**
+ * Determines whether the component has been added to the document.
+ * @return {boolean} TRUE if rendered. Otherwise, FALSE.
+ */
+goog.ui.Component.prototype.isInDocument = function() {
+ return this.inDocument_;
+};
+
+
+/**
+ * Creates the initial DOM representation for the component. The default
+ * implementation is to set this.element_ = div.
+ */
+goog.ui.Component.prototype.createDom = function() {
+ this.element_ = this.dom_.createElement('div');
+};
+
+
+/**
+ * Renders the component. If a parent element is supplied, the component's
+ * element will be appended to it. If there is no optional parent element and
+ * the element doesn't have a parentNode then it will be appended to the
+ * document body.
+ *
+ * If this component has a parent component, and the parent component is
+ * not in the document already, then this will not call {@code enterDocument}
+ * on this component.
+ *
+ * Throws an Error if the component is already rendered.
+ *
+ * @param {Element=} opt_parentElement Optional parent element to render the
+ * component into.
+ */
+goog.ui.Component.prototype.render = function(opt_parentElement) {
+ this.render_(opt_parentElement);
+};
+
+
+/**
+ * Renders the component before another element. The other element should be in
+ * the document already.
+ *
+ * Throws an Error if the component is already rendered.
+ *
+ * @param {Node} sibling Node to render the component before.
+ */
+goog.ui.Component.prototype.renderBefore = function(sibling) {
+ this.render_(/** @type {Element} */ (sibling.parentNode),
+ sibling);
+};
+
+
+/**
+ * Renders the component. If a parent element is supplied, the component's
+ * element will be appended to it. If there is no optional parent element and
+ * the element doesn't have a parentNode then it will be appended to the
+ * document body.
+ *
+ * If this component has a parent component, and the parent component is
+ * not in the document already, then this will not call {@code enterDocument}
+ * on this component.
+ *
+ * Throws an Error if the component is already rendered.
+ *
+ * @param {Element=} opt_parentElement Optional parent element to render the
+ * component into.
+ * @param {Node=} opt_beforeNode Node before which the component is to
+ * be rendered. If left out the node is appended to the parent element.
+ * @private
+ */
+goog.ui.Component.prototype.render_ = function(opt_parentElement,
+ opt_beforeNode) {
+ if (this.inDocument_) {
+ throw Error(goog.ui.Component.Error.ALREADY_RENDERED);
+ }
+
+ if (!this.element_) {
+ this.createDom();
+ }
+
+ if (opt_parentElement) {
+ opt_parentElement.insertBefore(this.element_, opt_beforeNode || null);
+ } else {
+ this.dom_.getDocument().body.appendChild(this.element_);
+ }
+
+ // If this component has a parent component that isn't in the document yet,
+ // we don't call enterDocument() here. Instead, when the parent component
+ // enters the document, the enterDocument() call will propagate to its
+ // children, including this one. If the component doesn't have a parent
+ // or if the parent is already in the document, we call enterDocument().
+ if (!this.parent_ || this.parent_.isInDocument()) {
+ this.enterDocument();
+ }
+};
+
+
+/**
+ * Decorates the element for the UI component. If the element is in the
+ * document, the enterDocument method will be called.
+ *
+ * If goog.ui.Component.ALLOW_DETACHED_DECORATION is false, the caller must
+ * pass an element that is in the document.
+ *
+ * @param {Element} element Element to decorate.
+ */
+goog.ui.Component.prototype.decorate = function(element) {
+ if (this.inDocument_) {
+ throw Error(goog.ui.Component.Error.ALREADY_RENDERED);
+ } else if (element && this.canDecorate(element)) {
+ this.wasDecorated_ = true;
+
+ // Set the DOM helper of the component to match the decorated element.
+ var doc = goog.dom.getOwnerDocument(element);
+ if (!this.dom_ || this.dom_.getDocument() != doc) {
+ this.dom_ = goog.dom.getDomHelper(element);
+ }
+
+ // Call specific component decorate logic.
+ this.decorateInternal(element);
+
+ // If supporting detached decoration, check that element is in doc.
+ if (!goog.ui.Component.ALLOW_DETACHED_DECORATION ||
+ goog.dom.contains(doc, element)) {
+ this.enterDocument();
+ }
+ } else {
+ throw Error(goog.ui.Component.Error.DECORATE_INVALID);
+ }
+};
+
+
+/**
+ * Determines if a given element can be decorated by this type of component.
+ * This method should be overridden by inheriting objects.
+ * @param {Element} element Element to decorate.
+ * @return {boolean} True if the element can be decorated, false otherwise.
+ */
+goog.ui.Component.prototype.canDecorate = function(element) {
+ return true;
+};
+
+
+/**
+ * @return {boolean} Whether the component was decorated.
+ */
+goog.ui.Component.prototype.wasDecorated = function() {
+ return this.wasDecorated_;
+};
+
+
+/**
+ * Actually decorates the element. Should be overridden by inheriting objects.
+ * This method can assume there are checks to ensure the component has not
+ * already been rendered have occurred and that enter document will be called
+ * afterwards. This method is considered protected.
+ * @param {Element} element Element to decorate.
+ * @protected
+ */
+goog.ui.Component.prototype.decorateInternal = function(element) {
+ this.element_ = element;
+};
+
+
+/**
+ * Called when the component's element is known to be in the document. Anything
+ * using document.getElementById etc. should be done at this stage.
+ *
+ * If the component contains child components, this call is propagated to its
+ * children.
+ */
+goog.ui.Component.prototype.enterDocument = function() {
+ this.inDocument_ = true;
+
+ // Propagate enterDocument to child components that have a DOM, if any.
+ // If a child was decorated before entering the document (permitted when
+ // goog.ui.Component.ALLOW_DETACHED_DECORATION is true), its enterDocument
+ // will be called here.
+ this.forEachChild(function(child) {
+ if (!child.isInDocument() && child.getElement()) {
+ child.enterDocument();
+ }
+ });
+};
+
+
+/**
+ * Called by dispose to clean up the elements and listeners created by a
+ * component, or by a parent component/application who has removed the
+ * component from the document but wants to reuse it later.
+ *
+ * If the component contains child components, this call is propagated to its
+ * children.
+ *
+ * It should be possible for the component to be rendered again once this method
+ * has been called.
+ */
+goog.ui.Component.prototype.exitDocument = function() {
+ // Propagate exitDocument to child components that have been rendered, if any.
+ this.forEachChild(function(child) {
+ if (child.isInDocument()) {
+ child.exitDocument();
+ }
+ });
+
+ if (this.googUiComponentHandler_) {
+ this.googUiComponentHandler_.removeAll();
+ }
+
+ this.inDocument_ = false;
+};
+
+
+/**
+ * Disposes of the component. Calls {@code exitDocument}, which is expected to
+ * remove event handlers and clean up the component. Propagates the call to
+ * the component's children, if any. Removes the component's DOM from the
+ * document unless it was decorated.
+ * @override
+ * @protected
+ */
+goog.ui.Component.prototype.disposeInternal = function() {
+ if (this.inDocument_) {
+ this.exitDocument();
+ }
+
+ if (this.googUiComponentHandler_) {
+ this.googUiComponentHandler_.dispose();
+ delete this.googUiComponentHandler_;
+ }
+
+ // Disposes of the component's children, if any.
+ this.forEachChild(function(child) {
+ child.dispose();
+ });
+
+ // Detach the component's element from the DOM, unless it was decorated.
+ if (!this.wasDecorated_ && this.element_) {
+ goog.dom.removeNode(this.element_);
+ }
+
+ this.children_ = null;
+ this.childIndex_ = null;
+ this.element_ = null;
+ this.model_ = null;
+ this.parent_ = null;
+
+ goog.ui.Component.superClass_.disposeInternal.call(this);
+};
+
+
+/**
+ * Helper function for subclasses that gets a unique id for a given fragment,
+ * this can be used by components to generate unique string ids for DOM
+ * elements.
+ * @param {string} idFragment A partial id.
+ * @return {string} Unique element id.
+ */
+goog.ui.Component.prototype.makeId = function(idFragment) {
+ return this.getId() + '.' + idFragment;
+};
+
+
+/**
+ * Makes a collection of ids. This is a convenience method for makeId. The
+ * object's values are the id fragments and the new values are the generated
+ * ids. The key will remain the same.
+ * @param {Object} object The object that will be used to create the ids.
+ * @return {!Object} An object of id keys to generated ids.
+ */
+goog.ui.Component.prototype.makeIds = function(object) {
+ var ids = {};
+ for (var key in object) {
+ ids[key] = this.makeId(object[key]);
+ }
+ return ids;
+};
+
+
+/**
+ * Returns the model associated with the UI component.
+ * @return {*} The model.
+ */
+goog.ui.Component.prototype.getModel = function() {
+ return this.model_;
+};
+
+
+/**
+ * Sets the model associated with the UI component.
+ * @param {*} obj The model.
+ */
+goog.ui.Component.prototype.setModel = function(obj) {
+ this.model_ = obj;
+};
+
+
+/**
+ * Helper function for returning the fragment portion of an id generated using
+ * makeId().
+ * @param {string} id Id generated with makeId().
+ * @return {string} Fragment.
+ */
+goog.ui.Component.prototype.getFragmentFromId = function(id) {
+ return id.substring(this.getId().length + 1);
+};
+
+
+/**
+ * Helper function for returning an element in the document with a unique id
+ * generated using makeId().
+ * @param {string} idFragment The partial id.
+ * @return {Element} The element with the unique id, or null if it cannot be
+ * found.
+ */
+goog.ui.Component.prototype.getElementByFragment = function(idFragment) {
+ if (!this.inDocument_) {
+ throw Error(goog.ui.Component.Error.NOT_IN_DOCUMENT);
+ }
+ return this.dom_.getElement(this.makeId(idFragment));
+};
+
+
+/**
+ * Adds the specified component as the last child of this component. See
+ * {@link goog.ui.Component#addChildAt} for detailed semantics.
+ *
+ * @see goog.ui.Component#addChildAt
+ * @param {goog.ui.Component} child The new child component.
+ * @param {boolean=} opt_render If true, the child component will be rendered
+ * into the parent.
+ */
+goog.ui.Component.prototype.addChild = function(child, opt_render) {
+ // TODO(gboyer): addChildAt(child, this.getChildCount(), false) will
+ // reposition any already-rendered child to the end. Instead, perhaps
+ // addChild(child, false) should never reposition the child; instead, clients
+ // that need the repositioning will use addChildAt explicitly. Right now,
+ // clients can get around this by calling addChild before calling decorate.
+ this.addChildAt(child, this.getChildCount(), opt_render);
+};
+
+
+/**
+ * Adds the specified component as a child of this component at the given
+ * 0-based index.
+ *
+ * Both {@code addChild} and {@code addChildAt} assume the following contract
+ * between parent and child components:
+ * <ul>
+ * <li>the child component's element must be a descendant of the parent
+ * component's element, and
+ * <li>the DOM state of the child component must be consistent with the DOM
+ * state of the parent component (see {@code isInDocument}) in the
+ * steady state -- the exception is to addChildAt(child, i, false) and
+ * then immediately decorate/render the child.
+ * </ul>
+ *
+ * In particular, {@code parent.addChild(child)} will throw an error if the
+ * child component is already in the document, but the parent isn't.
+ *
+ * Clients of this API may call {@code addChild} and {@code addChildAt} with
+ * {@code opt_render} set to true. If {@code opt_render} is true, calling these
+ * methods will automatically render the child component's element into the
+ * parent component's element. If the parent does not yet have an element, then
+ * {@code createDom} will automatically be invoked on the parent before
+ * rendering the child.
+ *
+ * Invoking {@code parent.addChild(child, true)} will throw an error if the
+ * child component is already in the document, regardless of the parent's DOM
+ * state.
+ *
+ * If {@code opt_render} is true and the parent component is not already
+ * in the document, {@code enterDocument} will not be called on this component
+ * at this point.
+ *
+ * Finally, this method also throws an error if the new child already has a
+ * different parent, or the given index is out of bounds.
+ *
+ * @see goog.ui.Component#addChild
+ * @param {goog.ui.Component} child The new child component.
+ * @param {number} index 0-based index at which the new child component is to be
+ * added; must be between 0 and the current child count (inclusive).
+ * @param {boolean=} opt_render If true, the child component will be rendered
+ * into the parent.
+ * @return {void} Nada.
+ */
+goog.ui.Component.prototype.addChildAt = function(child, index, opt_render) {
+ goog.asserts.assert(!!child, 'Provided element must not be null.');
+
+ if (child.inDocument_ && (opt_render || !this.inDocument_)) {
+ // Adding a child that's already in the document is an error, except if the
+ // parent is also in the document and opt_render is false (e.g. decorate()).
+ throw Error(goog.ui.Component.Error.ALREADY_RENDERED);
+ }
+
+ if (index < 0 || index > this.getChildCount()) {
+ // Allowing sparse child arrays would lead to strange behavior, so we don't.
+ throw Error(goog.ui.Component.Error.CHILD_INDEX_OUT_OF_BOUNDS);
+ }
+
+ // Create the index and the child array on first use.
+ if (!this.childIndex_ || !this.children_) {
+ this.childIndex_ = {};
+ this.children_ = [];
+ }
+
+ // Moving child within component, remove old reference.
+ if (child.getParent() == this) {
+ goog.object.set(this.childIndex_, child.getId(), child);
+ goog.array.remove(this.children_, child);
+
+ // Add the child to this component. goog.object.add() throws an error if
+ // a child with the same ID already exists.
+ } else {
+ goog.object.add(this.childIndex_, child.getId(), child);
+ }
+
+ // Set the parent of the child to this component. This throws an error if
+ // the child is already contained by another component.
+ child.setParent(this);
+ goog.array.insertAt(this.children_, child, index);
+
+ if (child.inDocument_ && this.inDocument_ && child.getParent() == this) {
+ // Changing the position of an existing child, move the DOM node.
+ var contentElement = this.getContentElement();
+ contentElement.insertBefore(child.getElement(),
+ (contentElement.childNodes[index] || null));
+
+ } else if (opt_render) {
+ // If this (parent) component doesn't have a DOM yet, call createDom now
+ // to make sure we render the child component's element into the correct
+ // parent element (otherwise render_ with a null first argument would
+ // render the child into the document body, which is almost certainly not
+ // what we want).
+ if (!this.element_) {
+ this.createDom();
+ }
+ // Render the child into the parent at the appropriate location. Note that
+ // getChildAt(index + 1) returns undefined if inserting at the end.
+ // TODO(attila): We should have a renderer with a renderChildAt API.
+ var sibling = this.getChildAt(index + 1);
+ // render_() calls enterDocument() if the parent is already in the document.
+ child.render_(this.getContentElement(), sibling ? sibling.element_ : null);
+ } else if (this.inDocument_ && !child.inDocument_ && child.element_ &&
+ child.element_.parentNode &&
+ // Under some circumstances, IE8 implicitly creates a Document Fragment
+ // for detached nodes, so ensure the parent is an Element as it should be.
+ child.element_.parentNode.nodeType == goog.dom.NodeType.ELEMENT) {
+ // We don't touch the DOM, but if the parent is in the document, and the
+ // child element is in the document but not marked as such, then we call
+ // enterDocument on the child.
+ // TODO(gboyer): It would be nice to move this condition entirely, but
+ // there's a large risk of breaking existing applications that manually
+ // append the child to the DOM and then call addChild.
+ child.enterDocument();
+ }
+};
+
+
+/**
+ * Returns the DOM element into which child components are to be rendered,
+ * or null if the component itself hasn't been rendered yet. This default
+ * implementation returns the component's root element. Subclasses with
+ * complex DOM structures must override this method.
+ * @return {Element} Element to contain child elements (null if none).
+ */
+goog.ui.Component.prototype.getContentElement = function() {
+ return this.element_;
+};
+
+
+/**
+ * Returns true if the component is rendered right-to-left, false otherwise.
+ * The first time this function is invoked, the right-to-left rendering property
+ * is set if it has not been already.
+ * @return {boolean} Whether the control is rendered right-to-left.
+ */
+goog.ui.Component.prototype.isRightToLeft = function() {
+ if (this.rightToLeft_ == null) {
+ this.rightToLeft_ = goog.style.isRightToLeft(this.inDocument_ ?
+ this.element_ : this.dom_.getDocument().body);
+ }
+ return /** @type {boolean} */(this.rightToLeft_);
+};
+
+
+/**
+ * Set is right-to-left. This function should be used if the component needs
+ * to know the rendering direction during dom creation (i.e. before
+ * {@link #enterDocument} is called and is right-to-left is set).
+ * @param {boolean} rightToLeft Whether the component is rendered
+ * right-to-left.
+ */
+goog.ui.Component.prototype.setRightToLeft = function(rightToLeft) {
+ if (this.inDocument_) {
+ throw Error(goog.ui.Component.Error.ALREADY_RENDERED);
+ }
+ this.rightToLeft_ = rightToLeft;
+};
+
+
+/**
+ * Returns true if the component has children.
+ * @return {boolean} True if the component has children.
+ */
+goog.ui.Component.prototype.hasChildren = function() {
+ return !!this.children_ && this.children_.length != 0;
+};
+
+
+/**
+ * Returns the number of children of this component.
+ * @return {number} The number of children.
+ */
+goog.ui.Component.prototype.getChildCount = function() {
+ return this.children_ ? this.children_.length : 0;
+};
+
+
+/**
+ * Returns an array containing the IDs of the children of this component, or an
+ * empty array if the component has no children.
+ * @return {!Array.<string>} Child component IDs.
+ */
+goog.ui.Component.prototype.getChildIds = function() {
+ var ids = [];
+
+ // We don't use goog.object.getKeys(this.childIndex_) because we want to
+ // return the IDs in the correct order as determined by this.children_.
+ this.forEachChild(function(child) {
+ // addChild()/addChildAt() guarantee that the child array isn't sparse.
+ ids.push(child.getId());
+ });
+
+ return ids;
+};
+
+
+/**
+ * Returns the child with the given ID, or null if no such child exists.
+ * @param {string} id Child component ID.
+ * @return {goog.ui.Component?} The child with the given ID; null if none.
+ */
+goog.ui.Component.prototype.getChild = function(id) {
+ // Use childIndex_ for O(1) access by ID.
+ return (this.childIndex_ && id) ? /** @type {goog.ui.Component} */ (
+ goog.object.get(this.childIndex_, id)) || null : null;
+};
+
+
+/**
+ * Returns the child at the given index, or null if the index is out of bounds.
+ * @param {number} index 0-based index.
+ * @return {goog.ui.Component?} The child at the given index; null if none.
+ */
+goog.ui.Component.prototype.getChildAt = function(index) {
+ // Use children_ for access by index.
+ return this.children_ ? this.children_[index] || null : null;
+};
+
+
+/**
+ * Calls the given function on each of this component's children in order. If
+ * {@code opt_obj} is provided, it will be used as the 'this' object in the
+ * function when called. The function should take two arguments: the child
+ * component and its 0-based index. The return value is ignored.
+ * @param {function(this:T,?,number):?} f The function to call for every
+ * child component; should take 2 arguments (the child and its index).
+ * @param {T=} opt_obj Used as the 'this' object in f when called.
+ * @template T
+ */
+goog.ui.Component.prototype.forEachChild = function(f, opt_obj) {
+ if (this.children_) {
+ goog.array.forEach(this.children_, f, opt_obj);
+ }
+};
+
+
+/**
+ * Returns the 0-based index of the given child component, or -1 if no such
+ * child is found.
+ * @param {goog.ui.Component?} child The child component.
+ * @return {number} 0-based index of the child component; -1 if not found.
+ */
+goog.ui.Component.prototype.indexOfChild = function(child) {
+ return (this.children_ && child) ? goog.array.indexOf(this.children_, child) :
+ -1;
+};
+
+
+/**
+ * Removes the given child from this component, and returns it. Throws an error
+ * if the argument is invalid or if the specified child isn't found in the
+ * parent component. The argument can either be a string (interpreted as the
+ * ID of the child component to remove) or the child component itself.
+ *
+ * If {@code opt_unrender} is true, calls {@link goog.ui.component#exitDocument}
+ * on the removed child, and subsequently detaches the child's DOM from the
+ * document. Otherwise it is the caller's responsibility to clean up the child
+ * component's DOM.
+ *
+ * @see goog.ui.Component#removeChildAt
+ * @param {string|goog.ui.Component|null} child The ID of the child to remove,
+ * or the child component itself.
+ * @param {boolean=} opt_unrender If true, calls {@code exitDocument} on the
+ * removed child component, and detaches its DOM from the document.
+ * @return {goog.ui.Component} The removed component, if any.
+ */
+goog.ui.Component.prototype.removeChild = function(child, opt_unrender) {
+ if (child) {
+ // Normalize child to be the object and id to be the ID string. This also
+ // ensures that the child is really ours.
+ var id = goog.isString(child) ? child : child.getId();
+ child = this.getChild(id);
+
+ if (id && child) {
+ goog.object.remove(this.childIndex_, id);
+ goog.array.remove(this.children_, child);
+
+ if (opt_unrender) {
+ // Remove the child component's DOM from the document. We have to call
+ // exitDocument first (see documentation).
+ child.exitDocument();
+ if (child.element_) {
+ goog.dom.removeNode(child.element_);
+ }
+ }
+
+ // Child's parent must be set to null after exitDocument is called
+ // so that the child can unlisten to its parent if required.
+ child.setParent(null);
+ }
+ }
+
+ if (!child) {
+ throw Error(goog.ui.Component.Error.NOT_OUR_CHILD);
+ }
+
+ return /** @type {goog.ui.Component} */(child);
+};
+
+
+/**
+ * Removes the child at the given index from this component, and returns it.
+ * Throws an error if the argument is out of bounds, or if the specified child
+ * isn't found in the parent. See {@link goog.ui.Component#removeChild} for
+ * detailed semantics.
+ *
+ * @see goog.ui.Component#removeChild
+ * @param {number} index 0-based index of the child to remove.
+ * @param {boolean=} opt_unrender If true, calls {@code exitDocument} on the
+ * removed child component, and detaches its DOM from the document.
+ * @return {goog.ui.Component} The removed component, if any.
+ */
+goog.ui.Component.prototype.removeChildAt = function(index, opt_unrender) {
+ // removeChild(null) will throw error.
+ return this.removeChild(this.getChildAt(index), opt_unrender);
+};
+
+
+/**
+ * Removes every child component attached to this one and returns them.
+ *
+ * @see goog.ui.Component#removeChild
+ * @param {boolean=} opt_unrender If true, calls {@link #exitDocument} on the
+ * removed child components, and detaches their DOM from the document.
+ * @return {!Array.<goog.ui.Component>} The removed components if any.
+ */
+goog.ui.Component.prototype.removeChildren = function(opt_unrender) {
+ var removedChildren = [];
+ while (this.hasChildren()) {
+ removedChildren.push(this.removeChildAt(0, opt_unrender));
+ }
+ return removedChildren;
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/container.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/container.js
new file mode 100644
index 0000000..b33947b
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/container.js
@@ -0,0 +1,1324 @@
+// Copyright 2007 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Base class for containers that host {@link goog.ui.Control}s,
+ * such as menus and toolbars. Provides default keyboard and mouse event
+ * handling and child management, based on a generalized version of
+ * {@link goog.ui.Menu}.
+ *
+ * @author attila@google.com (Attila Bodis)
+ * @see ../demos/container.html
+ */
+// TODO(attila): Fix code/logic duplication between this and goog.ui.Control.
+// TODO(attila): Maybe pull common stuff all the way up into Component...?
+
+goog.provide('goog.ui.Container');
+goog.provide('goog.ui.Container.EventType');
+goog.provide('goog.ui.Container.Orientation');
+
+goog.require('goog.a11y.aria');
+goog.require('goog.a11y.aria.State');
+goog.require('goog.asserts');
+goog.require('goog.dom');
+goog.require('goog.events.EventType');
+goog.require('goog.events.KeyCodes');
+goog.require('goog.events.KeyHandler');
+goog.require('goog.object');
+goog.require('goog.style');
+goog.require('goog.ui.Component');
+goog.require('goog.ui.ContainerRenderer');
+goog.require('goog.ui.Control');
+
+
+
+/**
+ * Base class for containers. Extends {@link goog.ui.Component} by adding
+ * the following:
+ * <ul>
+ * <li>a {@link goog.events.KeyHandler}, to simplify keyboard handling,
+ * <li>a pluggable <em>renderer</em> framework, to simplify the creation of
+ * containers without the need to subclass this class,
+ * <li>methods to manage child controls hosted in the container,
+ * <li>default mouse and keyboard event handling methods.
+ * </ul>
+ * @param {?goog.ui.Container.Orientation=} opt_orientation Container
+ * orientation; defaults to {@code VERTICAL}.
+ * @param {goog.ui.ContainerRenderer=} opt_renderer Renderer used to render or
+ * decorate the container; defaults to {@link goog.ui.ContainerRenderer}.
+ * @param {goog.dom.DomHelper=} opt_domHelper DOM helper, used for document
+ * interaction.
+ * @extends {goog.ui.Component}
+ * @constructor
+ */
+goog.ui.Container = function(opt_orientation, opt_renderer, opt_domHelper) {
+ goog.ui.Component.call(this, opt_domHelper);
+ this.renderer_ = opt_renderer || goog.ui.ContainerRenderer.getInstance();
+ this.orientation_ = opt_orientation || this.renderer_.getDefaultOrientation();
+};
+goog.inherits(goog.ui.Container, goog.ui.Component);
+goog.tagUnsealableClass(goog.ui.Container);
+
+
+/**
+ * Container-specific events.
+ * @enum {string}
+ */
+goog.ui.Container.EventType = {
+ /**
+ * Dispatched after a goog.ui.Container becomes visible. Non-cancellable.
+ * NOTE(user): This event really shouldn't exist, because the
+ * goog.ui.Component.EventType.SHOW event should behave like this one. But the
+ * SHOW event for containers has been behaving as other components'
+ * BEFORE_SHOW event for a long time, and too much code relies on that old
+ * behavior to fix it now.
+ */
+ AFTER_SHOW: 'aftershow',
+
+ /**
+ * Dispatched after a goog.ui.Container becomes invisible. Non-cancellable.
+ */
+ AFTER_HIDE: 'afterhide'
+};
+
+
+/**
+ * Container orientation constants.
+ * @enum {string}
+ */
+goog.ui.Container.Orientation = {
+ HORIZONTAL: 'horizontal',
+ VERTICAL: 'vertical'
+};
+
+
+/**
+ * Allows an alternative element to be set to receive key events, otherwise
+ * defers to the renderer's element choice.
+ * @type {Element|undefined}
+ * @private
+ */
+goog.ui.Container.prototype.keyEventTarget_ = null;
+
+
+/**
+ * Keyboard event handler.
+ * @type {goog.events.KeyHandler?}
+ * @private
+ */
+goog.ui.Container.prototype.keyHandler_ = null;
+
+
+/**
+ * Renderer for the container. Defaults to {@link goog.ui.ContainerRenderer}.
+ * @type {goog.ui.ContainerRenderer?}
+ * @private
+ */
+goog.ui.Container.prototype.renderer_ = null;
+
+
+/**
+ * Container orientation; determines layout and default keyboard navigation.
+ * @type {?goog.ui.Container.Orientation}
+ * @private
+ */
+goog.ui.Container.prototype.orientation_ = null;
+
+
+/**
+ * Whether the container is set to be visible. Defaults to true.
+ * @type {boolean}
+ * @private
+ */
+goog.ui.Container.prototype.visible_ = true;
+
+
+/**
+ * Whether the container is enabled and reacting to keyboard and mouse events.
+ * Defaults to true.
+ * @type {boolean}
+ * @private
+ */
+goog.ui.Container.prototype.enabled_ = true;
+
+
+/**
+ * Whether the container supports keyboard focus. Defaults to true. Focusable
+ * containers have a {@code tabIndex} and can be navigated to via the keyboard.
+ * @type {boolean}
+ * @private
+ */
+goog.ui.Container.prototype.focusable_ = true;
+
+
+/**
+ * The 0-based index of the currently highlighted control in the container
+ * (-1 if none).
+ * @type {number}
+ * @private
+ */
+goog.ui.Container.prototype.highlightedIndex_ = -1;
+
+
+/**
+ * The currently open (expanded) control in the container (null if none).
+ * @type {goog.ui.Control?}
+ * @private
+ */
+goog.ui.Container.prototype.openItem_ = null;
+
+
+/**
+ * Whether the mouse button is held down. Defaults to false. This flag is set
+ * when the user mouses down over the container, and remains set until they
+ * release the mouse button.
+ * @type {boolean}
+ * @private
+ */
+goog.ui.Container.prototype.mouseButtonPressed_ = false;
+
+
+/**
+ * Whether focus of child components should be allowed. Only effective if
+ * focusable_ is set to false.
+ * @type {boolean}
+ * @private
+ */
+goog.ui.Container.prototype.allowFocusableChildren_ = false;
+
+
+/**
+ * Whether highlighting a child component should also open it.
+ * @type {boolean}
+ * @private
+ */
+goog.ui.Container.prototype.openFollowsHighlight_ = true;
+
+
+/**
+ * Map of DOM IDs to child controls. Each key is the DOM ID of a child
+ * control's root element; each value is a reference to the child control
+ * itself. Used for looking up the child control corresponding to a DOM
+ * node in O(1) time.
+ * @type {Object}
+ * @private
+ */
+goog.ui.Container.prototype.childElementIdMap_ = null;
+
+
+// Event handler and renderer management.
+
+
+/**
+ * Returns the DOM element on which the container is listening for keyboard
+ * events (null if none).
+ * @return {Element} Element on which the container is listening for key
+ * events.
+ */
+goog.ui.Container.prototype.getKeyEventTarget = function() {
+ // Delegate to renderer, unless we've set an explicit target.
+ return this.keyEventTarget_ || this.renderer_.getKeyEventTarget(this);
+};
+
+
+/**
+ * Attaches an element on which to listen for key events.
+ * @param {Element|undefined} element The element to attach, or null/undefined
+ * to attach to the default element.
+ */
+goog.ui.Container.prototype.setKeyEventTarget = function(element) {
+ if (this.focusable_) {
+ var oldTarget = this.getKeyEventTarget();
+ var inDocument = this.isInDocument();
+
+ this.keyEventTarget_ = element;
+ var newTarget = this.getKeyEventTarget();
+
+ if (inDocument) {
+ // Unlisten for events on the old key target. Requires us to reset
+ // key target state temporarily.
+ this.keyEventTarget_ = oldTarget;
+ this.enableFocusHandling_(false);
+ this.keyEventTarget_ = element;
+
+ // Listen for events on the new key target.
+ this.getKeyHandler().attach(newTarget);
+ this.enableFocusHandling_(true);
+ }
+ } else {
+ throw Error('Can\'t set key event target for container ' +
+ 'that doesn\'t support keyboard focus!');
+ }
+};
+
+
+/**
+ * Returns the keyboard event handler for this container, lazily created the
+ * first time this method is called. The keyboard event handler listens for
+ * keyboard events on the container's key event target, as determined by its
+ * renderer.
+ * @return {!goog.events.KeyHandler} Keyboard event handler for this container.
+ */
+goog.ui.Container.prototype.getKeyHandler = function() {
+ return this.keyHandler_ ||
+ (this.keyHandler_ = new goog.events.KeyHandler(this.getKeyEventTarget()));
+};
+
+
+/**
+ * Returns the renderer used by this container to render itself or to decorate
+ * an existing element.
+ * @return {goog.ui.ContainerRenderer} Renderer used by the container.
+ */
+goog.ui.Container.prototype.getRenderer = function() {
+ return this.renderer_;
+};
+
+
+/**
+ * Registers the given renderer with the container. Changing renderers after
+ * the container has already been rendered or decorated is an error.
+ * @param {goog.ui.ContainerRenderer} renderer Renderer used by the container.
+ */
+goog.ui.Container.prototype.setRenderer = function(renderer) {
+ if (this.getElement()) {
+ // Too late.
+ throw Error(goog.ui.Component.Error.ALREADY_RENDERED);
+ }
+
+ this.renderer_ = renderer;
+};
+
+
+// Standard goog.ui.Component implementation.
+
+
+/**
+ * Creates the container's DOM.
+ * @override
+ */
+goog.ui.Container.prototype.createDom = function() {
+ // Delegate to renderer.
+ this.setElementInternal(this.renderer_.createDom(this));
+};
+
+
+/**
+ * Returns the DOM element into which child components are to be rendered,
+ * or null if the container itself hasn't been rendered yet. Overrides
+ * {@link goog.ui.Component#getContentElement} by delegating to the renderer.
+ * @return {Element} Element to contain child elements (null if none).
+ * @override
+ */
+goog.ui.Container.prototype.getContentElement = function() {
+ // Delegate to renderer.
+ return this.renderer_.getContentElement(this.getElement());
+};
+
+
+/**
+ * Returns true if the given element can be decorated by this container.
+ * Overrides {@link goog.ui.Component#canDecorate}.
+ * @param {Element} element Element to decorate.
+ * @return {boolean} True iff the element can be decorated.
+ * @override
+ */
+goog.ui.Container.prototype.canDecorate = function(element) {
+ // Delegate to renderer.
+ return this.renderer_.canDecorate(element);
+};
+
+
+/**
+ * Decorates the given element with this container. Overrides {@link
+ * goog.ui.Component#decorateInternal}. Considered protected.
+ * @param {Element} element Element to decorate.
+ * @override
+ */
+goog.ui.Container.prototype.decorateInternal = function(element) {
+ // Delegate to renderer.
+ this.setElementInternal(this.renderer_.decorate(this, element));
+ // Check whether the decorated element is explicitly styled to be invisible.
+ if (element.style.display == 'none') {
+ this.visible_ = false;
+ }
+};
+
+
+/**
+ * Configures the container after its DOM has been rendered, and sets up event
+ * handling. Overrides {@link goog.ui.Component#enterDocument}.
+ * @override
+ */
+goog.ui.Container.prototype.enterDocument = function() {
+ goog.ui.Container.superClass_.enterDocument.call(this);
+
+ this.forEachChild(function(child) {
+ if (child.isInDocument()) {
+ this.registerChildId_(child);
+ }
+ }, this);
+
+ var elem = this.getElement();
+
+ // Call the renderer's initializeDom method to initialize the container's DOM.
+ this.renderer_.initializeDom(this);
+
+ // Initialize visibility (opt_force = true, so we don't dispatch events).
+ this.setVisible(this.visible_, true);
+
+ // Handle events dispatched by child controls.
+ this.getHandler().
+ listen(this, goog.ui.Component.EventType.ENTER,
+ this.handleEnterItem).
+ listen(this, goog.ui.Component.EventType.HIGHLIGHT,
+ this.handleHighlightItem).
+ listen(this, goog.ui.Component.EventType.UNHIGHLIGHT,
+ this.handleUnHighlightItem).
+ listen(this, goog.ui.Component.EventType.OPEN, this.handleOpenItem).
+ listen(this, goog.ui.Component.EventType.CLOSE, this.handleCloseItem).
+
+ // Handle mouse events.
+ listen(elem, goog.events.EventType.MOUSEDOWN, this.handleMouseDown).
+ listen(goog.dom.getOwnerDocument(elem), goog.events.EventType.MOUSEUP,
+ this.handleDocumentMouseUp).
+
+ // Handle mouse events on behalf of controls in the container.
+ listen(elem, [
+ goog.events.EventType.MOUSEDOWN,
+ goog.events.EventType.MOUSEUP,
+ goog.events.EventType.MOUSEOVER,
+ goog.events.EventType.MOUSEOUT,
+ goog.events.EventType.CONTEXTMENU
+ ], this.handleChildMouseEvents);
+
+ // If the container is focusable, set up keyboard event handling.
+ if (this.isFocusable()) {
+ this.enableFocusHandling_(true);
+ }
+};
+
+
+/**
+ * Sets up listening for events applicable to focusable containers.
+ * @param {boolean} enable Whether to enable or disable focus handling.
+ * @private
+ */
+goog.ui.Container.prototype.enableFocusHandling_ = function(enable) {
+ var handler = this.getHandler();
+ var keyTarget = this.getKeyEventTarget();
+ if (enable) {
+ handler.
+ listen(keyTarget, goog.events.EventType.FOCUS, this.handleFocus).
+ listen(keyTarget, goog.events.EventType.BLUR, this.handleBlur).
+ listen(this.getKeyHandler(), goog.events.KeyHandler.EventType.KEY,
+ this.handleKeyEvent);
+ } else {
+ handler.
+ unlisten(keyTarget, goog.events.EventType.FOCUS, this.handleFocus).
+ unlisten(keyTarget, goog.events.EventType.BLUR, this.handleBlur).
+ unlisten(this.getKeyHandler(), goog.events.KeyHandler.EventType.KEY,
+ this.handleKeyEvent);
+ }
+};
+
+
+/**
+ * Cleans up the container before its DOM is removed from the document, and
+ * removes event handlers. Overrides {@link goog.ui.Component#exitDocument}.
+ * @override
+ */
+goog.ui.Container.prototype.exitDocument = function() {
+ // {@link #setHighlightedIndex} has to be called before
+ // {@link goog.ui.Component#exitDocument}, otherwise it has no effect.
+ this.setHighlightedIndex(-1);
+
+ if (this.openItem_) {
+ this.openItem_.setOpen(false);
+ }
+
+ this.mouseButtonPressed_ = false;
+
+ goog.ui.Container.superClass_.exitDocument.call(this);
+};
+
+
+/** @override */
+goog.ui.Container.prototype.disposeInternal = function() {
+ goog.ui.Container.superClass_.disposeInternal.call(this);
+
+ if (this.keyHandler_) {
+ this.keyHandler_.dispose();
+ this.keyHandler_ = null;
+ }
+
+ this.keyEventTarget_ = null;
+ this.childElementIdMap_ = null;
+ this.openItem_ = null;
+ this.renderer_ = null;
+};
+
+
+// Default event handlers.
+
+
+/**
+ * Handles ENTER events raised by child controls when they are navigated to.
+ * @param {goog.events.Event} e ENTER event to handle.
+ * @return {boolean} Whether to prevent handleMouseOver from handling
+ * the event.
+ */
+goog.ui.Container.prototype.handleEnterItem = function(e) {
+ // Allow the Control to highlight itself.
+ return true;
+};
+
+
+/**
+ * Handles HIGHLIGHT events dispatched by items in the container when
+ * they are highlighted.
+ * @param {goog.events.Event} e Highlight event to handle.
+ */
+goog.ui.Container.prototype.handleHighlightItem = function(e) {
+ var index = this.indexOfChild(/** @type {goog.ui.Control} */ (e.target));
+ if (index > -1 && index != this.highlightedIndex_) {
+ var item = this.getHighlighted();
+ if (item) {
+ // Un-highlight previously highlighted item.
+ item.setHighlighted(false);
+ }
+
+ this.highlightedIndex_ = index;
+ item = this.getHighlighted();
+
+ if (this.isMouseButtonPressed()) {
+ // Activate item when mouse button is pressed, to allow MacOS-style
+ // dragging to choose menu items. Although this should only truly
+ // happen if the highlight is due to mouse movements, there is little
+ // harm in doing it for keyboard or programmatic highlights.
+ item.setActive(true);
+ }
+
+ // Update open item if open item needs follow highlight.
+ if (this.openFollowsHighlight_ &&
+ this.openItem_ && item != this.openItem_) {
+ if (item.isSupportedState(goog.ui.Component.State.OPENED)) {
+ item.setOpen(true);
+ } else {
+ this.openItem_.setOpen(false);
+ }
+ }
+ }
+
+ var element = this.getElement();
+ goog.asserts.assert(element,
+ 'The DOM element for the container cannot be null.');
+ if (e.target.getElement() != null) {
+ goog.a11y.aria.setState(element,
+ goog.a11y.aria.State.ACTIVEDESCENDANT,
+ e.target.getElement().id);
+ }
+};
+
+
+/**
+ * Handles UNHIGHLIGHT events dispatched by items in the container when
+ * they are unhighlighted.
+ * @param {goog.events.Event} e Unhighlight event to handle.
+ */
+goog.ui.Container.prototype.handleUnHighlightItem = function(e) {
+ if (e.target == this.getHighlighted()) {
+ this.highlightedIndex_ = -1;
+ }
+ var element = this.getElement();
+ goog.asserts.assert(element,
+ 'The DOM element for the container cannot be null.');
+ // Setting certain ARIA attributes to empty strings is problematic.
+ // Just remove the attribute instead.
+ goog.a11y.aria.removeState(element, goog.a11y.aria.State.ACTIVEDESCENDANT);
+};
+
+
+/**
+ * Handles OPEN events dispatched by items in the container when they are
+ * opened.
+ * @param {goog.events.Event} e Open event to handle.
+ */
+goog.ui.Container.prototype.handleOpenItem = function(e) {
+ var item = /** @type {goog.ui.Control} */ (e.target);
+ if (item && item != this.openItem_ && item.getParent() == this) {
+ if (this.openItem_) {
+ this.openItem_.setOpen(false);
+ }
+ this.openItem_ = item;
+ }
+};
+
+
+/**
+ * Handles CLOSE events dispatched by items in the container when they are
+ * closed.
+ * @param {goog.events.Event} e Close event to handle.
+ */
+goog.ui.Container.prototype.handleCloseItem = function(e) {
+ if (e.target == this.openItem_) {
+ this.openItem_ = null;
+ }
+};
+
+
+/**
+ * Handles mousedown events over the container. The default implementation
+ * sets the "mouse button pressed" flag and, if the container is focusable,
+ * grabs keyboard focus.
+ * @param {goog.events.BrowserEvent} e Mousedown event to handle.
+ */
+goog.ui.Container.prototype.handleMouseDown = function(e) {
+ if (this.enabled_) {
+ this.setMouseButtonPressed(true);
+ }
+
+ var keyTarget = this.getKeyEventTarget();
+ if (keyTarget && goog.dom.isFocusableTabIndex(keyTarget)) {
+ // The container is configured to receive keyboard focus.
+ keyTarget.focus();
+ } else {
+ // The control isn't configured to receive keyboard focus; prevent it
+ // from stealing focus or destroying the selection.
+ e.preventDefault();
+ }
+};
+
+
+/**
+ * Handles mouseup events over the document. The default implementation
+ * clears the "mouse button pressed" flag.
+ * @param {goog.events.BrowserEvent} e Mouseup event to handle.
+ */
+goog.ui.Container.prototype.handleDocumentMouseUp = function(e) {
+ this.setMouseButtonPressed(false);
+};
+
+
+/**
+ * Handles mouse events originating from nodes belonging to the controls hosted
+ * in the container. Locates the child control based on the DOM node that
+ * dispatched the event, and forwards the event to the control for handling.
+ * @param {goog.events.BrowserEvent} e Mouse event to handle.
+ */
+goog.ui.Container.prototype.handleChildMouseEvents = function(e) {
+ var control = this.getOwnerControl(/** @type {Node} */ (e.target));
+ if (control) {
+ // Child control identified; forward the event.
+ switch (e.type) {
+ case goog.events.EventType.MOUSEDOWN:
+ control.handleMouseDown(e);
+ break;
+ case goog.events.EventType.MOUSEUP:
+ control.handleMouseUp(e);
+ break;
+ case goog.events.EventType.MOUSEOVER:
+ control.handleMouseOver(e);
+ break;
+ case goog.events.EventType.MOUSEOUT:
+ control.handleMouseOut(e);
+ break;
+ case goog.events.EventType.CONTEXTMENU:
+ control.handleContextMenu(e);
+ break;
+ }
+ }
+};
+
+
+/**
+ * Returns the child control that owns the given DOM node, or null if no such
+ * control is found.
+ * @param {Node} node DOM node whose owner is to be returned.
+ * @return {goog.ui.Control?} Control hosted in the container to which the node
+ * belongs (if found).
+ * @protected
+ */
+goog.ui.Container.prototype.getOwnerControl = function(node) {
+ // Ensure that this container actually has child controls before
+ // looking up the owner.
+ if (this.childElementIdMap_) {
+ var elem = this.getElement();
+ // See http://b/2964418 . IE9 appears to evaluate '!=' incorrectly, so
+ // using '!==' instead.
+ // TODO(user): Possibly revert this change if/when IE9 fixes the issue.
+ while (node && node !== elem) {
+ var id = node.id;
+ if (id in this.childElementIdMap_) {
+ return this.childElementIdMap_[id];
+ }
+ node = node.parentNode;
+ }
+ }
+ return null;
+};
+
+
+/**
+ * Handles focus events raised when the container's key event target receives
+ * keyboard focus.
+ * @param {goog.events.BrowserEvent} e Focus event to handle.
+ */
+goog.ui.Container.prototype.handleFocus = function(e) {
+ // No-op in the base class.
+};
+
+
+/**
+ * Handles blur events raised when the container's key event target loses
+ * keyboard focus. The default implementation clears the highlight index.
+ * @param {goog.events.BrowserEvent} e Blur event to handle.
+ */
+goog.ui.Container.prototype.handleBlur = function(e) {
+ this.setHighlightedIndex(-1);
+ this.setMouseButtonPressed(false);
+ // If the container loses focus, and one of its children is open, close it.
+ if (this.openItem_) {
+ this.openItem_.setOpen(false);
+ }
+};
+
+
+/**
+ * Attempts to handle a keyboard event, if the control is enabled, by calling
+ * {@link handleKeyEventInternal}. Considered protected; should only be used
+ * within this package and by subclasses.
+ * @param {goog.events.KeyEvent} e Key event to handle.
+ * @return {boolean} Whether the key event was handled.
+ */
+goog.ui.Container.prototype.handleKeyEvent = function(e) {
+ if (this.isEnabled() && this.isVisible() &&
+ (this.getChildCount() != 0 || this.keyEventTarget_) &&
+ this.handleKeyEventInternal(e)) {
+ e.preventDefault();
+ e.stopPropagation();
+ return true;
+ }
+ return false;
+};
+
+
+/**
+ * Attempts to handle a keyboard event; returns true if the event was handled,
+ * false otherwise. If the container is enabled, and a child is highlighted,
+ * calls the child control's {@code handleKeyEvent} method to give the control
+ * a chance to handle the event first.
+ * @param {goog.events.KeyEvent} e Key event to handle.
+ * @return {boolean} Whether the event was handled by the container (or one of
+ * its children).
+ */
+goog.ui.Container.prototype.handleKeyEventInternal = function(e) {
+ // Give the highlighted control the chance to handle the key event.
+ var highlighted = this.getHighlighted();
+ if (highlighted && typeof highlighted.handleKeyEvent == 'function' &&
+ highlighted.handleKeyEvent(e)) {
+ return true;
+ }
+
+ // Give the open control the chance to handle the key event.
+ if (this.openItem_ && this.openItem_ != highlighted &&
+ typeof this.openItem_.handleKeyEvent == 'function' &&
+ this.openItem_.handleKeyEvent(e)) {
+ return true;
+ }
+
+ // Do not handle the key event if any modifier key is pressed.
+ if (e.shiftKey || e.ctrlKey || e.metaKey || e.altKey) {
+ return false;
+ }
+
+ // Either nothing is highlighted, or the highlighted control didn't handle
+ // the key event, so attempt to handle it here.
+ switch (e.keyCode) {
+ case goog.events.KeyCodes.ESC:
+ if (this.isFocusable()) {
+ this.getKeyEventTarget().blur();
+ } else {
+ return false;
+ }
+ break;
+
+ case goog.events.KeyCodes.HOME:
+ this.highlightFirst();
+ break;
+
+ case goog.events.KeyCodes.END:
+ this.highlightLast();
+ break;
+
+ case goog.events.KeyCodes.UP:
+ if (this.orientation_ == goog.ui.Container.Orientation.VERTICAL) {
+ this.highlightPrevious();
+ } else {
+ return false;
+ }
+ break;
+
+ case goog.events.KeyCodes.LEFT:
+ if (this.orientation_ == goog.ui.Container.Orientation.HORIZONTAL) {
+ if (this.isRightToLeft()) {
+ this.highlightNext();
+ } else {
+ this.highlightPrevious();
+ }
+ } else {
+ return false;
+ }
+ break;
+
+ case goog.events.KeyCodes.DOWN:
+ if (this.orientation_ == goog.ui.Container.Orientation.VERTICAL) {
+ this.highlightNext();
+ } else {
+ return false;
+ }
+ break;
+
+ case goog.events.KeyCodes.RIGHT:
+ if (this.orientation_ == goog.ui.Container.Orientation.HORIZONTAL) {
+ if (this.isRightToLeft()) {
+ this.highlightPrevious();
+ } else {
+ this.highlightNext();
+ }
+ } else {
+ return false;
+ }
+ break;
+
+ default:
+ return false;
+ }
+
+ return true;
+};
+
+
+// Child component management.
+
+
+/**
+ * Creates a DOM ID for the child control and registers it to an internal
+ * hash table to be able to find it fast by id.
+ * @param {goog.ui.Component} child The child control. Its root element has
+ * to be created yet.
+ * @private
+ */
+goog.ui.Container.prototype.registerChildId_ = function(child) {
+ // Map the DOM ID of the control's root element to the control itself.
+ var childElem = child.getElement();
+
+ // If the control's root element doesn't have a DOM ID assign one.
+ var id = childElem.id || (childElem.id = child.getId());
+
+ // Lazily create the child element ID map on first use.
+ if (!this.childElementIdMap_) {
+ this.childElementIdMap_ = {};
+ }
+ this.childElementIdMap_[id] = child;
+};
+
+
+/**
+ * Adds the specified control as the last child of this container. See
+ * {@link goog.ui.Container#addChildAt} for detailed semantics.
+ * @param {goog.ui.Component} child The new child control.
+ * @param {boolean=} opt_render Whether the new child should be rendered
+ * immediately after being added (defaults to false).
+ * @override
+ */
+goog.ui.Container.prototype.addChild = function(child, opt_render) {
+ goog.asserts.assertInstanceof(child, goog.ui.Control,
+ 'The child of a container must be a control');
+ goog.ui.Container.superClass_.addChild.call(this, child, opt_render);
+};
+
+
+/**
+ * Overrides {@link goog.ui.Container#getChild} to make it clear that it
+ * only returns {@link goog.ui.Control}s.
+ * @param {string} id Child component ID.
+ * @return {goog.ui.Control} The child with the given ID; null if none.
+ * @override
+ */
+goog.ui.Container.prototype.getChild;
+
+
+/**
+ * Overrides {@link goog.ui.Container#getChildAt} to make it clear that it
+ * only returns {@link goog.ui.Control}s.
+ * @param {number} index 0-based index.
+ * @return {goog.ui.Control} The child with the given ID; null if none.
+ * @override
+ */
+goog.ui.Container.prototype.getChildAt;
+
+
+/**
+ * Adds the control as a child of this container at the given 0-based index.
+ * Overrides {@link goog.ui.Component#addChildAt} by also updating the
+ * container's highlight index. Since {@link goog.ui.Component#addChild} uses
+ * {@link #addChildAt} internally, we only need to override this method.
+ * @param {goog.ui.Component} control New child.
+ * @param {number} index Index at which the new child is to be added.
+ * @param {boolean=} opt_render Whether the new child should be rendered
+ * immediately after being added (defaults to false).
+ * @override
+ */
+goog.ui.Container.prototype.addChildAt = function(control, index, opt_render) {
+ // Make sure the child control dispatches HIGHLIGHT, UNHIGHLIGHT, OPEN, and
+ // CLOSE events, and that it doesn't steal keyboard focus.
+ control.setDispatchTransitionEvents(goog.ui.Component.State.HOVER, true);
+ control.setDispatchTransitionEvents(goog.ui.Component.State.OPENED, true);
+ if (this.isFocusable() || !this.isFocusableChildrenAllowed()) {
+ control.setSupportedState(goog.ui.Component.State.FOCUSED, false);
+ }
+
+ // Disable mouse event handling by child controls.
+ control.setHandleMouseEvents(false);
+
+ // Let the superclass implementation do the work.
+ goog.ui.Container.superClass_.addChildAt.call(this, control, index,
+ opt_render);
+
+ if (control.isInDocument() && this.isInDocument()) {
+ this.registerChildId_(control);
+ }
+
+ // Update the highlight index, if needed.
+ if (index <= this.highlightedIndex_) {
+ this.highlightedIndex_++;
+ }
+};
+
+
+/**
+ * Removes a child control. Overrides {@link goog.ui.Component#removeChild} by
+ * updating the highlight index. Since {@link goog.ui.Component#removeChildAt}
+ * uses {@link #removeChild} internally, we only need to override this method.
+ * @param {string|goog.ui.Component} control The ID of the child to remove, or
+ * the control itself.
+ * @param {boolean=} opt_unrender Whether to call {@code exitDocument} on the
+ * removed control, and detach its DOM from the document (defaults to
+ * false).
+ * @return {goog.ui.Control} The removed control, if any.
+ * @override
+ */
+goog.ui.Container.prototype.removeChild = function(control, opt_unrender) {
+ control = goog.isString(control) ? this.getChild(control) : control;
+
+ if (control) {
+ var index = this.indexOfChild(control);
+ if (index != -1) {
+ if (index == this.highlightedIndex_) {
+ control.setHighlighted(false);
+ this.highlightedIndex_ = -1;
+ } else if (index < this.highlightedIndex_) {
+ this.highlightedIndex_--;
+ }
+ }
+
+ // Remove the mapping from the child element ID map.
+ var childElem = control.getElement();
+ if (childElem && childElem.id && this.childElementIdMap_) {
+ goog.object.remove(this.childElementIdMap_, childElem.id);
+ }
+ }
+
+ control = /** @type {goog.ui.Control} */ (
+ goog.ui.Container.superClass_.removeChild.call(this, control,
+ opt_unrender));
+
+ // Re-enable mouse event handling (in case the control is reused elsewhere).
+ control.setHandleMouseEvents(true);
+
+ return control;
+};
+
+
+// Container state management.
+
+
+/**
+ * Returns the container's orientation.
+ * @return {?goog.ui.Container.Orientation} Container orientation.
+ */
+goog.ui.Container.prototype.getOrientation = function() {
+ return this.orientation_;
+};
+
+
+/**
+ * Sets the container's orientation.
+ * @param {goog.ui.Container.Orientation} orientation Container orientation.
+ */
+// TODO(attila): Do we need to support containers with dynamic orientation?
+goog.ui.Container.prototype.setOrientation = function(orientation) {
+ if (this.getElement()) {
+ // Too late.
+ throw Error(goog.ui.Component.Error.ALREADY_RENDERED);
+ }
+
+ this.orientation_ = orientation;
+};
+
+
+/**
+ * Returns true if the container's visibility is set to visible, false if
+ * it is set to hidden. A container that is set to hidden is guaranteed
+ * to be hidden from the user, but the reverse isn't necessarily true.
+ * A container may be set to visible but can otherwise be obscured by another
+ * element, rendered off-screen, or hidden using direct CSS manipulation.
+ * @return {boolean} Whether the container is set to be visible.
+ */
+goog.ui.Container.prototype.isVisible = function() {
+ return this.visible_;
+};
+
+
+/**
+ * Shows or hides the container. Does nothing if the container already has
+ * the requested visibility. Otherwise, dispatches a SHOW or HIDE event as
+ * appropriate, giving listeners a chance to prevent the visibility change.
+ * @param {boolean} visible Whether to show or hide the container.
+ * @param {boolean=} opt_force If true, doesn't check whether the container
+ * already has the requested visibility, and doesn't dispatch any events.
+ * @return {boolean} Whether the visibility was changed.
+ */
+goog.ui.Container.prototype.setVisible = function(visible, opt_force) {
+ if (opt_force || (this.visible_ != visible && this.dispatchEvent(visible ?
+ goog.ui.Component.EventType.SHOW : goog.ui.Component.EventType.HIDE))) {
+ this.visible_ = visible;
+
+ var elem = this.getElement();
+ if (elem) {
+ goog.style.setElementShown(elem, visible);
+ if (this.isFocusable()) {
+ // Enable keyboard access only for enabled & visible containers.
+ this.renderer_.enableTabIndex(this.getKeyEventTarget(),
+ this.enabled_ && this.visible_);
+ }
+ if (!opt_force) {
+ this.dispatchEvent(this.visible_ ?
+ goog.ui.Container.EventType.AFTER_SHOW :
+ goog.ui.Container.EventType.AFTER_HIDE);
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+};
+
+
+/**
+ * Returns true if the container is enabled, false otherwise.
+ * @return {boolean} Whether the container is enabled.
+ */
+goog.ui.Container.prototype.isEnabled = function() {
+ return this.enabled_;
+};
+
+
+/**
+ * Enables/disables the container based on the {@code enable} argument.
+ * Dispatches an {@code ENABLED} or {@code DISABLED} event prior to changing
+ * the container's state, which may be caught and canceled to prevent the
+ * container from changing state. Also enables/disables child controls.
+ * @param {boolean} enable Whether to enable or disable the container.
+ */
+goog.ui.Container.prototype.setEnabled = function(enable) {
+ if (this.enabled_ != enable && this.dispatchEvent(enable ?
+ goog.ui.Component.EventType.ENABLE :
+ goog.ui.Component.EventType.DISABLE)) {
+ if (enable) {
+ // Flag the container as enabled first, then update children. This is
+ // because controls can't be enabled if their parent is disabled.
+ this.enabled_ = true;
+ this.forEachChild(function(child) {
+ // Enable child control unless it is flagged.
+ if (child.wasDisabled) {
+ delete child.wasDisabled;
+ } else {
+ child.setEnabled(true);
+ }
+ });
+ } else {
+ // Disable children first, then flag the container as disabled. This is
+ // because controls can't be disabled if their parent is already disabled.
+ this.forEachChild(function(child) {
+ // Disable child control, or flag it if it's already disabled.
+ if (child.isEnabled()) {
+ child.setEnabled(false);
+ } else {
+ child.wasDisabled = true;
+ }
+ });
+ this.enabled_ = false;
+ this.setMouseButtonPressed(false);
+ }
+
+ if (this.isFocusable()) {
+ // Enable keyboard access only for enabled & visible components.
+ this.renderer_.enableTabIndex(this.getKeyEventTarget(),
+ enable && this.visible_);
+ }
+ }
+};
+
+
+/**
+ * Returns true if the container is focusable, false otherwise. The default
+ * is true. Focusable containers always have a tab index and allocate a key
+ * handler to handle keyboard events while focused.
+ * @return {boolean} Whether the component is focusable.
+ */
+goog.ui.Container.prototype.isFocusable = function() {
+ return this.focusable_;
+};
+
+
+/**
+ * Sets whether the container is focusable. The default is true. Focusable
+ * containers always have a tab index and allocate a key handler to handle
+ * keyboard events while focused.
+ * @param {boolean} focusable Whether the component is to be focusable.
+ */
+goog.ui.Container.prototype.setFocusable = function(focusable) {
+ if (focusable != this.focusable_ && this.isInDocument()) {
+ this.enableFocusHandling_(focusable);
+ }
+ this.focusable_ = focusable;
+ if (this.enabled_ && this.visible_) {
+ this.renderer_.enableTabIndex(this.getKeyEventTarget(), focusable);
+ }
+};
+
+
+/**
+ * Returns true if the container allows children to be focusable, false
+ * otherwise. Only effective if the container is not focusable.
+ * @return {boolean} Whether children should be focusable.
+ */
+goog.ui.Container.prototype.isFocusableChildrenAllowed = function() {
+ return this.allowFocusableChildren_;
+};
+
+
+/**
+ * Sets whether the container allows children to be focusable, false
+ * otherwise. Only effective if the container is not focusable.
+ * @param {boolean} focusable Whether the children should be focusable.
+ */
+goog.ui.Container.prototype.setFocusableChildrenAllowed = function(focusable) {
+ this.allowFocusableChildren_ = focusable;
+};
+
+
+/**
+ * @return {boolean} Whether highlighting a child component should also open it.
+ */
+goog.ui.Container.prototype.isOpenFollowsHighlight = function() {
+ return this.openFollowsHighlight_;
+};
+
+
+/**
+ * Sets whether highlighting a child component should also open it.
+ * @param {boolean} follow Whether highlighting a child component also opens it.
+ */
+goog.ui.Container.prototype.setOpenFollowsHighlight = function(follow) {
+ this.openFollowsHighlight_ = follow;
+};
+
+
+// Highlight management.
+
+
+/**
+ * Returns the index of the currently highlighted item (-1 if none).
+ * @return {number} Index of the currently highlighted item.
+ */
+goog.ui.Container.prototype.getHighlightedIndex = function() {
+ return this.highlightedIndex_;
+};
+
+
+/**
+ * Highlights the item at the given 0-based index (if any). If another item
+ * was previously highlighted, it is un-highlighted.
+ * @param {number} index Index of item to highlight (-1 removes the current
+ * highlight).
+ */
+goog.ui.Container.prototype.setHighlightedIndex = function(index) {
+ var child = this.getChildAt(index);
+ if (child) {
+ child.setHighlighted(true);
+ } else if (this.highlightedIndex_ > -1) {
+ this.getHighlighted().setHighlighted(false);
+ }
+};
+
+
+/**
+ * Highlights the given item if it exists and is a child of the container;
+ * otherwise un-highlights the currently highlighted item.
+ * @param {goog.ui.Control} item Item to highlight.
+ */
+goog.ui.Container.prototype.setHighlighted = function(item) {
+ this.setHighlightedIndex(this.indexOfChild(item));
+};
+
+
+/**
+ * Returns the currently highlighted item (if any).
+ * @return {goog.ui.Control?} Highlighted item (null if none).
+ */
+goog.ui.Container.prototype.getHighlighted = function() {
+ return this.getChildAt(this.highlightedIndex_);
+};
+
+
+/**
+ * Highlights the first highlightable item in the container
+ */
+goog.ui.Container.prototype.highlightFirst = function() {
+ this.highlightHelper(function(index, max) {
+ return (index + 1) % max;
+ }, this.getChildCount() - 1);
+};
+
+
+/**
+ * Highlights the last highlightable item in the container.
+ */
+goog.ui.Container.prototype.highlightLast = function() {
+ this.highlightHelper(function(index, max) {
+ index--;
+ return index < 0 ? max - 1 : index;
+ }, 0);
+};
+
+
+/**
+ * Highlights the next highlightable item (or the first if nothing is currently
+ * highlighted).
+ */
+goog.ui.Container.prototype.highlightNext = function() {
+ this.highlightHelper(function(index, max) {
+ return (index + 1) % max;
+ }, this.highlightedIndex_);
+};
+
+
+/**
+ * Highlights the previous highlightable item (or the last if nothing is
+ * currently highlighted).
+ */
+goog.ui.Container.prototype.highlightPrevious = function() {
+ this.highlightHelper(function(index, max) {
+ index--;
+ return index < 0 ? max - 1 : index;
+ }, this.highlightedIndex_);
+};
+
+
+/**
+ * Helper function that manages the details of moving the highlight among
+ * child controls in response to keyboard events.
+ * @param {function(number, number) : number} fn Function that accepts the
+ * current and maximum indices, and returns the next index to check.
+ * @param {number} startIndex Start index.
+ * @return {boolean} Whether the highlight has changed.
+ * @protected
+ */
+goog.ui.Container.prototype.highlightHelper = function(fn, startIndex) {
+ // If the start index is -1 (meaning there's nothing currently highlighted),
+ // try starting from the currently open item, if any.
+ var curIndex = startIndex < 0 ?
+ this.indexOfChild(this.openItem_) : startIndex;
+ var numItems = this.getChildCount();
+
+ curIndex = fn.call(this, curIndex, numItems);
+ var visited = 0;
+ while (visited <= numItems) {
+ var control = this.getChildAt(curIndex);
+ if (control && this.canHighlightItem(control)) {
+ this.setHighlightedIndexFromKeyEvent(curIndex);
+ return true;
+ }
+ visited++;
+ curIndex = fn.call(this, curIndex, numItems);
+ }
+ return false;
+};
+
+
+/**
+ * Returns whether the given item can be highlighted.
+ * @param {goog.ui.Control} item The item to check.
+ * @return {boolean} Whether the item can be highlighted.
+ * @protected
+ */
+goog.ui.Container.prototype.canHighlightItem = function(item) {
+ return item.isVisible() && item.isEnabled() &&
+ item.isSupportedState(goog.ui.Component.State.HOVER);
+};
+
+
+/**
+ * Helper method that sets the highlighted index to the given index in response
+ * to a keyboard event. The base class implementation simply calls the
+ * {@link #setHighlightedIndex} method, but subclasses can override this
+ * behavior as needed.
+ * @param {number} index Index of item to highlight.
+ * @protected
+ */
+goog.ui.Container.prototype.setHighlightedIndexFromKeyEvent = function(index) {
+ this.setHighlightedIndex(index);
+};
+
+
+/**
+ * Returns the currently open (expanded) control in the container (null if
+ * none).
+ * @return {goog.ui.Control?} The currently open control.
+ */
+goog.ui.Container.prototype.getOpenItem = function() {
+ return this.openItem_;
+};
+
+
+/**
+ * Returns true if the mouse button is pressed, false otherwise.
+ * @return {boolean} Whether the mouse button is pressed.
+ */
+goog.ui.Container.prototype.isMouseButtonPressed = function() {
+ return this.mouseButtonPressed_;
+};
+
+
+/**
+ * Sets or clears the "mouse button pressed" flag.
+ * @param {boolean} pressed Whether the mouse button is presed.
+ */
+goog.ui.Container.prototype.setMouseButtonPressed = function(pressed) {
+ this.mouseButtonPressed_ = pressed;
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/containerrenderer.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/containerrenderer.js
new file mode 100644
index 0000000..9ea3b36
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/containerrenderer.js
@@ -0,0 +1,374 @@
+// Copyright 2008 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Base class for container renderers.
+ *
+ * @author attila@google.com (Attila Bodis)
+ */
+
+goog.provide('goog.ui.ContainerRenderer');
+
+goog.require('goog.a11y.aria');
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.dom.NodeType');
+goog.require('goog.dom.classlist');
+goog.require('goog.string');
+goog.require('goog.style');
+goog.require('goog.ui.registry');
+goog.require('goog.userAgent');
+
+
+
+/**
+ * Default renderer for {@link goog.ui.Container}. Can be used as-is, but
+ * subclasses of Container will probably want to use renderers specifically
+ * tailored for them by extending this class.
+ * @param {string=} opt_ariaRole Optional ARIA role used for the element.
+ * @constructor
+ */
+goog.ui.ContainerRenderer = function(opt_ariaRole) {
+ // By default, the ARIA role is unspecified.
+ /** @private {string|undefined} */
+ this.ariaRole_ = opt_ariaRole;
+};
+goog.addSingletonGetter(goog.ui.ContainerRenderer);
+
+
+/**
+ * Constructs a new renderer and sets the CSS class that the renderer will use
+ * as the base CSS class to apply to all elements rendered by that renderer.
+ * An example to use this function using a menu is:
+ *
+ * <pre>
+ * var myCustomRenderer = goog.ui.ContainerRenderer.getCustomRenderer(
+ * goog.ui.MenuRenderer, 'my-special-menu');
+ * var newMenu = new goog.ui.Menu(opt_domHelper, myCustomRenderer);
+ * </pre>
+ *
+ * Your styles for the menu can now be:
+ * <pre>
+ * .my-special-menu { }
+ * </pre>
+ *
+ * <em>instead</em> of
+ * <pre>
+ * .CSS_MY_SPECIAL_MENU .goog-menu { }
+ * </pre>
+ *
+ * You would want to use this functionality when you want an instance of a
+ * component to have specific styles different than the other components of the
+ * same type in your application. This avoids using descendant selectors to
+ * apply the specific styles to this component.
+ *
+ * @param {Function} ctor The constructor of the renderer you want to create.
+ * @param {string} cssClassName The name of the CSS class for this renderer.
+ * @return {goog.ui.ContainerRenderer} An instance of the desired renderer with
+ * its getCssClass() method overridden to return the supplied custom CSS
+ * class name.
+ */
+goog.ui.ContainerRenderer.getCustomRenderer = function(ctor, cssClassName) {
+ var renderer = new ctor();
+
+ /**
+ * Returns the CSS class to be applied to the root element of components
+ * rendered using this renderer.
+ * @return {string} Renderer-specific CSS class.
+ */
+ renderer.getCssClass = function() {
+ return cssClassName;
+ };
+
+ return renderer;
+};
+
+
+/**
+ * Default CSS class to be applied to the root element of containers rendered
+ * by this renderer.
+ * @type {string}
+ */
+goog.ui.ContainerRenderer.CSS_CLASS = goog.getCssName('goog-container');
+
+
+/**
+ * Returns the ARIA role to be applied to the container.
+ * See http://wiki/Main/ARIA for more info.
+ * @return {undefined|string} ARIA role.
+ */
+goog.ui.ContainerRenderer.prototype.getAriaRole = function() {
+ return this.ariaRole_;
+};
+
+
+/**
+ * Enables or disables the tab index of the element. Only elements with a
+ * valid tab index can receive focus.
+ * @param {Element} element Element whose tab index is to be changed.
+ * @param {boolean} enable Whether to add or remove the element's tab index.
+ */
+goog.ui.ContainerRenderer.prototype.enableTabIndex = function(element, enable) {
+ if (element) {
+ element.tabIndex = enable ? 0 : -1;
+ }
+};
+
+
+/**
+ * Creates and returns the container's root element. The default
+ * simply creates a DIV and applies the renderer's own CSS class name to it.
+ * To be overridden in subclasses.
+ * @param {goog.ui.Container} container Container to render.
+ * @return {Element} Root element for the container.
+ */
+goog.ui.ContainerRenderer.prototype.createDom = function(container) {
+ return container.getDomHelper().createDom('div',
+ this.getClassNames(container).join(' '));
+};
+
+
+/**
+ * Returns the DOM element into which child components are to be rendered,
+ * or null if the container hasn't been rendered yet.
+ * @param {Element} element Root element of the container whose content element
+ * is to be returned.
+ * @return {Element} Element to contain child elements (null if none).
+ */
+goog.ui.ContainerRenderer.prototype.getContentElement = function(element) {
+ return element;
+};
+
+
+/**
+ * Default implementation of {@code canDecorate}; returns true if the element
+ * is a DIV, false otherwise.
+ * @param {Element} element Element to decorate.
+ * @return {boolean} Whether the renderer can decorate the element.
+ */
+goog.ui.ContainerRenderer.prototype.canDecorate = function(element) {
+ return element.tagName == 'DIV';
+};
+
+
+/**
+ * Default implementation of {@code decorate} for {@link goog.ui.Container}s.
+ * Decorates the element with the container, and attempts to decorate its child
+ * elements. Returns the decorated element.
+ * @param {goog.ui.Container} container Container to decorate the element.
+ * @param {Element} element Element to decorate.
+ * @return {!Element} Decorated element.
+ */
+goog.ui.ContainerRenderer.prototype.decorate = function(container, element) {
+ // Set the container's ID to the decorated element's DOM ID, if any.
+ if (element.id) {
+ container.setId(element.id);
+ }
+
+ // Configure the container's state based on the CSS class names it has.
+ var baseClass = this.getCssClass();
+ var hasBaseClass = false;
+ var classNames = goog.dom.classlist.get(element);
+ if (classNames) {
+ goog.array.forEach(classNames, function(className) {
+ if (className == baseClass) {
+ hasBaseClass = true;
+ } else if (className) {
+ this.setStateFromClassName(container, className, baseClass);
+ }
+ }, this);
+ }
+
+ if (!hasBaseClass) {
+ // Make sure the container's root element has the renderer's own CSS class.
+ goog.dom.classlist.add(element, baseClass);
+ }
+
+ // Decorate the element's children, if applicable. This should happen after
+ // the container's own state has been initialized, since how children are
+ // decorated may depend on the state of the container.
+ this.decorateChildren(container, this.getContentElement(element));
+
+ return element;
+};
+
+
+/**
+ * Sets the container's state based on the given CSS class name, encountered
+ * during decoration. CSS class names that don't represent container states
+ * are ignored. Considered protected; subclasses should override this method
+ * to support more states and CSS class names.
+ * @param {goog.ui.Container} container Container to update.
+ * @param {string} className CSS class name.
+ * @param {string} baseClass Base class name used as the root of state-specific
+ * class names (typically the renderer's own class name).
+ * @protected
+ */
+goog.ui.ContainerRenderer.prototype.setStateFromClassName = function(container,
+ className, baseClass) {
+ if (className == goog.getCssName(baseClass, 'disabled')) {
+ container.setEnabled(false);
+ } else if (className == goog.getCssName(baseClass, 'horizontal')) {
+ container.setOrientation(goog.ui.Container.Orientation.HORIZONTAL);
+ } else if (className == goog.getCssName(baseClass, 'vertical')) {
+ container.setOrientation(goog.ui.Container.Orientation.VERTICAL);
+ }
+};
+
+
+/**
+ * Takes a container and an element that may contain child elements, decorates
+ * the child elements, and adds the corresponding components to the container
+ * as child components. Any non-element child nodes (e.g. empty text nodes
+ * introduced by line breaks in the HTML source) are removed from the element.
+ * @param {goog.ui.Container} container Container whose children are to be
+ * discovered.
+ * @param {Element} element Element whose children are to be decorated.
+ * @param {Element=} opt_firstChild the first child to be decorated.
+ */
+goog.ui.ContainerRenderer.prototype.decorateChildren = function(container,
+ element, opt_firstChild) {
+ if (element) {
+ var node = opt_firstChild || element.firstChild, next;
+ // Tag soup HTML may result in a DOM where siblings have different parents.
+ while (node && node.parentNode == element) {
+ // Get the next sibling here, since the node may be replaced or removed.
+ next = node.nextSibling;
+ if (node.nodeType == goog.dom.NodeType.ELEMENT) {
+ // Decorate element node.
+ var child = this.getDecoratorForChild(/** @type {Element} */(node));
+ if (child) {
+ // addChild() may need to look at the element.
+ child.setElementInternal(/** @type {Element} */(node));
+ // If the container is disabled, mark the child disabled too. See
+ // bug 1263729. Note that this must precede the call to addChild().
+ if (!container.isEnabled()) {
+ child.setEnabled(false);
+ }
+ container.addChild(child);
+ child.decorate(/** @type {Element} */(node));
+ }
+ } else if (!node.nodeValue || goog.string.trim(node.nodeValue) == '') {
+ // Remove empty text node, otherwise madness ensues (e.g. controls that
+ // use goog-inline-block will flicker and shift on hover on Gecko).
+ element.removeChild(node);
+ }
+ node = next;
+ }
+ }
+};
+
+
+/**
+ * Inspects the element, and creates an instance of {@link goog.ui.Control} or
+ * an appropriate subclass best suited to decorate it. Returns the control (or
+ * null if no suitable class was found). This default implementation uses the
+ * element's CSS class to find the appropriate control class to instantiate.
+ * May be overridden in subclasses.
+ * @param {Element} element Element to decorate.
+ * @return {goog.ui.Control?} A new control suitable to decorate the element
+ * (null if none).
+ */
+goog.ui.ContainerRenderer.prototype.getDecoratorForChild = function(element) {
+ return /** @type {goog.ui.Control} */ (
+ goog.ui.registry.getDecorator(element));
+};
+
+
+/**
+ * Initializes the container's DOM when the container enters the document.
+ * Called from {@link goog.ui.Container#enterDocument}.
+ * @param {goog.ui.Container} container Container whose DOM is to be initialized
+ * as it enters the document.
+ */
+goog.ui.ContainerRenderer.prototype.initializeDom = function(container) {
+ var elem = container.getElement();
+ goog.asserts.assert(elem, 'The container DOM element cannot be null.');
+ // Make sure the container's element isn't selectable. On Gecko, recursively
+ // marking each child element unselectable is expensive and unnecessary, so
+ // only mark the root element unselectable.
+ goog.style.setUnselectable(elem, true, goog.userAgent.GECKO);
+
+ // IE doesn't support outline:none, so we have to use the hideFocus property.
+ if (goog.userAgent.IE) {
+ elem.hideFocus = true;
+ }
+
+ // Set the ARIA role.
+ var ariaRole = this.getAriaRole();
+ if (ariaRole) {
+ goog.a11y.aria.setRole(elem, ariaRole);
+ }
+};
+
+
+/**
+ * Returns the element within the container's DOM that should receive keyboard
+ * focus (null if none). The default implementation returns the container's
+ * root element.
+ * @param {goog.ui.Container} container Container whose key event target is
+ * to be returned.
+ * @return {Element} Key event target (null if none).
+ */
+goog.ui.ContainerRenderer.prototype.getKeyEventTarget = function(container) {
+ return container.getElement();
+};
+
+
+/**
+ * Returns the CSS class to be applied to the root element of containers
+ * rendered using this renderer.
+ * @return {string} Renderer-specific CSS class.
+ */
+goog.ui.ContainerRenderer.prototype.getCssClass = function() {
+ return goog.ui.ContainerRenderer.CSS_CLASS;
+};
+
+
+/**
+ * Returns all CSS class names applicable to the given container, based on its
+ * state. The array of class names returned includes the renderer's own CSS
+ * class, followed by a CSS class indicating the container's orientation,
+ * followed by any state-specific CSS classes.
+ * @param {goog.ui.Container} container Container whose CSS classes are to be
+ * returned.
+ * @return {!Array.<string>} Array of CSS class names applicable to the
+ * container.
+ */
+goog.ui.ContainerRenderer.prototype.getClassNames = function(container) {
+ var baseClass = this.getCssClass();
+ var isHorizontal =
+ container.getOrientation() == goog.ui.Container.Orientation.HORIZONTAL;
+ var classNames = [
+ baseClass,
+ (isHorizontal ?
+ goog.getCssName(baseClass, 'horizontal') :
+ goog.getCssName(baseClass, 'vertical'))
+ ];
+ if (!container.isEnabled()) {
+ classNames.push(goog.getCssName(baseClass, 'disabled'));
+ }
+ return classNames;
+};
+
+
+/**
+ * Returns the default orientation of containers rendered or decorated by this
+ * renderer. The base class implementation returns {@code VERTICAL}.
+ * @return {goog.ui.Container.Orientation} Default orientation for containers
+ * created or decorated by this renderer.
+ */
+goog.ui.ContainerRenderer.prototype.getDefaultOrientation = function() {
+ return goog.ui.Container.Orientation.VERTICAL;
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/control.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/control.js
new file mode 100644
index 0000000..a258c26
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/control.js
@@ -0,0 +1,1388 @@
+// Copyright 2007 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Base class for UI controls such as buttons, menus, menu items,
+ * toolbar buttons, etc. The implementation is based on a generalized version
+ * of {@link goog.ui.MenuItem}.
+ * TODO(attila): If the renderer framework works well, pull it into Component.
+ *
+ * @author attila@google.com (Attila Bodis)
+ * @see ../demos/control.html
+ * @see http://code.google.com/p/closure-library/wiki/IntroToControls
+ */
+
+goog.provide('goog.ui.Control');
+
+goog.require('goog.array');
+goog.require('goog.dom');
+goog.require('goog.events.Event');
+goog.require('goog.events.EventType');
+goog.require('goog.events.KeyCodes');
+goog.require('goog.events.KeyHandler');
+goog.require('goog.string');
+goog.require('goog.ui.Component');
+/** @suppress {extraRequire} */
+goog.require('goog.ui.ControlContent');
+goog.require('goog.ui.ControlRenderer');
+goog.require('goog.ui.decorate');
+goog.require('goog.ui.registry');
+goog.require('goog.userAgent');
+
+
+
+/**
+ * Base class for UI controls. Extends {@link goog.ui.Component} by adding
+ * the following:
+ * <ul>
+ * <li>a {@link goog.events.KeyHandler}, to simplify keyboard handling,
+ * <li>a pluggable <em>renderer</em> framework, to simplify the creation of
+ * simple controls without the need to subclass this class,
+ * <li>the notion of component <em>content</em>, like a text caption or DOM
+ * structure displayed in the component (e.g. a button label),
+ * <li>getter and setter for component content, as well as a getter and
+ * setter specifically for caption text (for convenience),
+ * <li>support for hiding/showing the component,
+ <li>fine-grained control over supported states and state transition
+ events, and
+ * <li>default mouse and keyboard event handling.
+ * </ul>
+ * This class has sufficient built-in functionality for most simple UI controls.
+ * All controls dispatch SHOW, HIDE, ENTER, LEAVE, and ACTION events on show,
+ * hide, mouseover, mouseout, and user action, respectively. Additional states
+ * are also supported. See closure/demos/control.html
+ * for example usage.
+ * @param {goog.ui.ControlContent=} opt_content Text caption or DOM structure
+ * to display as the content of the control (if any).
+ * @param {goog.ui.ControlRenderer=} opt_renderer Renderer used to render or
+ * decorate the component; defaults to {@link goog.ui.ControlRenderer}.
+ * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper, used for
+ * document interaction.
+ * @constructor
+ * @extends {goog.ui.Component}
+ */
+goog.ui.Control = function(opt_content, opt_renderer, opt_domHelper) {
+ goog.ui.Component.call(this, opt_domHelper);
+ this.renderer_ = opt_renderer ||
+ goog.ui.registry.getDefaultRenderer(this.constructor);
+ this.setContentInternal(goog.isDef(opt_content) ? opt_content : null);
+};
+goog.inherits(goog.ui.Control, goog.ui.Component);
+goog.tagUnsealableClass(goog.ui.Control);
+
+
+// Renderer registry.
+// TODO(attila): Refactor existing usages inside Google in a follow-up CL.
+
+
+/**
+ * Maps a CSS class name to a function that returns a new instance of
+ * {@link goog.ui.Control} or a subclass thereof, suitable to decorate
+ * an element that has the specified CSS class. UI components that extend
+ * {@link goog.ui.Control} and want {@link goog.ui.Container}s to be able
+ * to discover and decorate elements using them should register a factory
+ * function via this API.
+ * @param {string} className CSS class name.
+ * @param {Function} decoratorFunction Function that takes no arguments and
+ * returns a new instance of a control to decorate an element with the
+ * given class.
+ * @deprecated Use {@link goog.ui.registry.setDecoratorByClassName} instead.
+ */
+goog.ui.Control.registerDecorator = goog.ui.registry.setDecoratorByClassName;
+
+
+/**
+ * Takes an element and returns a new instance of {@link goog.ui.Control}
+ * or a subclass, suitable to decorate it (based on the element's CSS class).
+ * @param {Element} element Element to decorate.
+ * @return {goog.ui.Control?} New control instance to decorate the element
+ * (null if none).
+ * @deprecated Use {@link goog.ui.registry.getDecorator} instead.
+ */
+goog.ui.Control.getDecorator =
+ /** @type {function(Element): goog.ui.Control} */ (
+ goog.ui.registry.getDecorator);
+
+
+/**
+ * Takes an element, and decorates it with a {@link goog.ui.Control} instance
+ * if a suitable decorator is found.
+ * @param {Element} element Element to decorate.
+ * @return {goog.ui.Control?} New control instance that decorates the element
+ * (null if none).
+ * @deprecated Use {@link goog.ui.decorate} instead.
+ */
+goog.ui.Control.decorate = /** @type {function(Element): goog.ui.Control} */ (
+ goog.ui.decorate);
+
+
+/**
+ * Renderer associated with the component.
+ * @type {goog.ui.ControlRenderer|undefined}
+ * @private
+ */
+goog.ui.Control.prototype.renderer_;
+
+
+/**
+ * Text caption or DOM structure displayed in the component.
+ * @type {goog.ui.ControlContent}
+ * @private
+ */
+goog.ui.Control.prototype.content_ = null;
+
+
+/**
+ * Current component state; a bit mask of {@link goog.ui.Component.State}s.
+ * @type {number}
+ * @private
+ */
+goog.ui.Control.prototype.state_ = 0x00;
+
+
+/**
+ * A bit mask of {@link goog.ui.Component.State}s this component supports.
+ * @type {number}
+ * @private
+ */
+goog.ui.Control.prototype.supportedStates_ =
+ goog.ui.Component.State.DISABLED |
+ goog.ui.Component.State.HOVER |
+ goog.ui.Component.State.ACTIVE |
+ goog.ui.Component.State.FOCUSED;
+
+
+/**
+ * A bit mask of {@link goog.ui.Component.State}s for which this component
+ * provides default event handling. For example, a component that handles
+ * the HOVER state automatically will highlight itself on mouseover, whereas
+ * a component that doesn't handle HOVER automatically will only dispatch
+ * ENTER and LEAVE events but not call {@link setHighlighted} on itself.
+ * By default, components provide default event handling for all states.
+ * Controls hosted in containers (e.g. menu items in a menu, or buttons in a
+ * toolbar) will typically want to have their container manage their highlight
+ * state. Selectable controls managed by a selection model will also typically
+ * want their selection state to be managed by the model.
+ * @type {number}
+ * @private
+ */
+goog.ui.Control.prototype.autoStates_ = goog.ui.Component.State.ALL;
+
+
+/**
+ * A bit mask of {@link goog.ui.Component.State}s for which this component
+ * dispatches state transition events. Because events are expensive, the
+ * default behavior is to not dispatch any state transition events at all.
+ * Use the {@link #setDispatchTransitionEvents} API to request transition
+ * events as needed. Subclasses may enable transition events by default.
+ * Controls hosted in containers or managed by a selection model will typically
+ * want to dispatch transition events.
+ * @type {number}
+ * @private
+ */
+goog.ui.Control.prototype.statesWithTransitionEvents_ = 0x00;
+
+
+/**
+ * Component visibility.
+ * @type {boolean}
+ * @private
+ */
+goog.ui.Control.prototype.visible_ = true;
+
+
+/**
+ * Keyboard event handler.
+ * @type {goog.events.KeyHandler}
+ * @private
+ */
+goog.ui.Control.prototype.keyHandler_;
+
+
+/**
+ * Additional class name(s) to apply to the control's root element, if any.
+ * @type {Array.<string>?}
+ * @private
+ */
+goog.ui.Control.prototype.extraClassNames_ = null;
+
+
+/**
+ * Whether the control should listen for and handle mouse events; defaults to
+ * true.
+ * @type {boolean}
+ * @private
+ */
+goog.ui.Control.prototype.handleMouseEvents_ = true;
+
+
+/**
+ * Whether the control allows text selection within its DOM. Defaults to false.
+ * @type {boolean}
+ * @private
+ */
+goog.ui.Control.prototype.allowTextSelection_ = false;
+
+
+/**
+ * The control's preferred ARIA role.
+ * @type {?goog.a11y.aria.Role}
+ * @private
+ */
+goog.ui.Control.prototype.preferredAriaRole_ = null;
+
+
+// Event handler and renderer management.
+
+
+/**
+ * Returns true if the control is configured to handle its own mouse events,
+ * false otherwise. Controls not hosted in {@link goog.ui.Container}s have
+ * to handle their own mouse events, but controls hosted in containers may
+ * allow their parent to handle mouse events on their behalf. Considered
+ * protected; should only be used within this package and by subclasses.
+ * @return {boolean} Whether the control handles its own mouse events.
+ */
+goog.ui.Control.prototype.isHandleMouseEvents = function() {
+ return this.handleMouseEvents_;
+};
+
+
+/**
+ * Enables or disables mouse event handling for the control. Containers may
+ * use this method to disable mouse event handling in their child controls.
+ * Considered protected; should only be used within this package and by
+ * subclasses.
+ * @param {boolean} enable Whether to enable or disable mouse event handling.
+ */
+goog.ui.Control.prototype.setHandleMouseEvents = function(enable) {
+ if (this.isInDocument() && enable != this.handleMouseEvents_) {
+ // Already in the document; need to update event handler.
+ this.enableMouseEventHandling_(enable);
+ }
+ this.handleMouseEvents_ = enable;
+};
+
+
+/**
+ * Returns the DOM element on which the control is listening for keyboard
+ * events (null if none).
+ * @return {Element} Element on which the control is listening for key
+ * events.
+ */
+goog.ui.Control.prototype.getKeyEventTarget = function() {
+ // Delegate to renderer.
+ return this.renderer_.getKeyEventTarget(this);
+};
+
+
+/**
+ * Returns the keyboard event handler for this component, lazily created the
+ * first time this method is called. Considered protected; should only be
+ * used within this package and by subclasses.
+ * @return {!goog.events.KeyHandler} Keyboard event handler for this component.
+ * @protected
+ */
+goog.ui.Control.prototype.getKeyHandler = function() {
+ return this.keyHandler_ || (this.keyHandler_ = new goog.events.KeyHandler());
+};
+
+
+/**
+ * Returns the renderer used by this component to render itself or to decorate
+ * an existing element.
+ * @return {goog.ui.ControlRenderer|undefined} Renderer used by the component
+ * (undefined if none).
+ */
+goog.ui.Control.prototype.getRenderer = function() {
+ return this.renderer_;
+};
+
+
+/**
+ * Registers the given renderer with the component. Changing renderers after
+ * the component has entered the document is an error.
+ * @param {goog.ui.ControlRenderer} renderer Renderer used by the component.
+ * @throws {Error} If the control is already in the document.
+ */
+goog.ui.Control.prototype.setRenderer = function(renderer) {
+ if (this.isInDocument()) {
+ // Too late.
+ throw Error(goog.ui.Component.Error.ALREADY_RENDERED);
+ }
+
+ if (this.getElement()) {
+ // The component has already been rendered, but isn't yet in the document.
+ // Replace the renderer and delete the current DOM, so it can be re-rendered
+ // using the new renderer the next time someone calls render().
+ this.setElementInternal(null);
+ }
+
+ this.renderer_ = renderer;
+};
+
+
+// Support for additional styling.
+
+
+/**
+ * Returns any additional class name(s) to be applied to the component's
+ * root element, or null if no extra class names are needed.
+ * @return {Array.<string>?} Additional class names to be applied to
+ * the component's root element (null if none).
+ */
+goog.ui.Control.prototype.getExtraClassNames = function() {
+ return this.extraClassNames_;
+};
+
+
+/**
+ * Adds the given class name to the list of classes to be applied to the
+ * component's root element.
+ * @param {string} className Additional class name to be applied to the
+ * component's root element.
+ */
+goog.ui.Control.prototype.addClassName = function(className) {
+ if (className) {
+ if (this.extraClassNames_) {
+ if (!goog.array.contains(this.extraClassNames_, className)) {
+ this.extraClassNames_.push(className);
+ }
+ } else {
+ this.extraClassNames_ = [className];
+ }
+ this.renderer_.enableExtraClassName(this, className, true);
+ }
+};
+
+
+/**
+ * Removes the given class name from the list of classes to be applied to
+ * the component's root element.
+ * @param {string} className Class name to be removed from the component's root
+ * element.
+ */
+goog.ui.Control.prototype.removeClassName = function(className) {
+ if (className && this.extraClassNames_ &&
+ goog.array.remove(this.extraClassNames_, className)) {
+ if (this.extraClassNames_.length == 0) {
+ this.extraClassNames_ = null;
+ }
+ this.renderer_.enableExtraClassName(this, className, false);
+ }
+};
+
+
+/**
+ * Adds or removes the given class name to/from the list of classes to be
+ * applied to the component's root element.
+ * @param {string} className CSS class name to add or remove.
+ * @param {boolean} enable Whether to add or remove the class name.
+ */
+goog.ui.Control.prototype.enableClassName = function(className, enable) {
+ if (enable) {
+ this.addClassName(className);
+ } else {
+ this.removeClassName(className);
+ }
+};
+
+
+// Standard goog.ui.Component implementation.
+
+
+/**
+ * Creates the control's DOM. Overrides {@link goog.ui.Component#createDom} by
+ * delegating DOM manipulation to the control's renderer.
+ * @override
+ */
+goog.ui.Control.prototype.createDom = function() {
+ var element = this.renderer_.createDom(this);
+ this.setElementInternal(element);
+
+ // Initialize ARIA role.
+ this.renderer_.setAriaRole(element, this.getPreferredAriaRole());
+
+ // Initialize text selection.
+ if (!this.isAllowTextSelection()) {
+ // The renderer is assumed to create selectable elements. Since making
+ // elements unselectable is expensive, only do it if needed (bug 1037090).
+ this.renderer_.setAllowTextSelection(element, false);
+ }
+
+ // Initialize visibility.
+ if (!this.isVisible()) {
+ // The renderer is assumed to create visible elements. Since hiding
+ // elements can be expensive, only do it if needed (bug 1037105).
+ this.renderer_.setVisible(element, false);
+ }
+};
+
+
+/**
+ * Returns the control's preferred ARIA role. This can be used by a control to
+ * override the role that would be assigned by the renderer. This is useful in
+ * cases where a different ARIA role is appropriate for a control because of the
+ * context in which it's used. E.g., a {@link goog.ui.MenuButton} added to a
+ * {@link goog.ui.Select} should have an ARIA role of LISTBOX and not MENUITEM.
+ * @return {?goog.a11y.aria.Role} This control's preferred ARIA role or null if
+ * no preferred ARIA role is set.
+ */
+goog.ui.Control.prototype.getPreferredAriaRole = function() {
+ return this.preferredAriaRole_;
+};
+
+
+/**
+ * Sets the control's preferred ARIA role. This can be used to override the role
+ * that would be assigned by the renderer. This is useful in cases where a
+ * different ARIA role is appropriate for a control because of the
+ * context in which it's used. E.g., a {@link goog.ui.MenuButton} added to a
+ * {@link goog.ui.Select} should have an ARIA role of LISTBOX and not MENUITEM.
+ * @param {goog.a11y.aria.Role} role This control's preferred ARIA role.
+ */
+goog.ui.Control.prototype.setPreferredAriaRole = function(role) {
+ this.preferredAriaRole_ = role;
+};
+
+
+/**
+ * Returns the DOM element into which child components are to be rendered,
+ * or null if the control itself hasn't been rendered yet. Overrides
+ * {@link goog.ui.Component#getContentElement} by delegating to the renderer.
+ * @return {Element} Element to contain child elements (null if none).
+ * @override
+ */
+goog.ui.Control.prototype.getContentElement = function() {
+ // Delegate to renderer.
+ return this.renderer_.getContentElement(this.getElement());
+};
+
+
+/**
+ * Returns true if the given element can be decorated by this component.
+ * Overrides {@link goog.ui.Component#canDecorate}.
+ * @param {Element} element Element to decorate.
+ * @return {boolean} Whether the element can be decorated by this component.
+ * @override
+ */
+goog.ui.Control.prototype.canDecorate = function(element) {
+ // Controls support pluggable renderers; delegate to the renderer.
+ return this.renderer_.canDecorate(element);
+};
+
+
+/**
+ * Decorates the given element with this component. Overrides {@link
+ * goog.ui.Component#decorateInternal} by delegating DOM manipulation
+ * to the control's renderer.
+ * @param {Element} element Element to decorate.
+ * @protected
+ * @override
+ */
+goog.ui.Control.prototype.decorateInternal = function(element) {
+ element = this.renderer_.decorate(this, element);
+ this.setElementInternal(element);
+
+ // Initialize ARIA role.
+ this.renderer_.setAriaRole(element, this.getPreferredAriaRole());
+
+ // Initialize text selection.
+ if (!this.isAllowTextSelection()) {
+ // Decorated elements are assumed to be selectable. Since making elements
+ // unselectable is expensive, only do it if needed (bug 1037090).
+ this.renderer_.setAllowTextSelection(element, false);
+ }
+
+ // Initialize visibility based on the decorated element's styling.
+ this.visible_ = element.style.display != 'none';
+};
+
+
+/**
+ * Configures the component after its DOM has been rendered, and sets up event
+ * handling. Overrides {@link goog.ui.Component#enterDocument}.
+ * @override
+ */
+goog.ui.Control.prototype.enterDocument = function() {
+ goog.ui.Control.superClass_.enterDocument.call(this);
+
+ // Call the renderer's initializeDom method to configure properties of the
+ // control's DOM that can only be done once it's in the document.
+ this.renderer_.initializeDom(this);
+
+ // Initialize event handling if at least one state other than DISABLED is
+ // supported.
+ if (this.supportedStates_ & ~goog.ui.Component.State.DISABLED) {
+ // Initialize mouse event handling if the control is configured to handle
+ // its own mouse events. (Controls hosted in containers don't need to
+ // handle their own mouse events.)
+ if (this.isHandleMouseEvents()) {
+ this.enableMouseEventHandling_(true);
+ }
+
+ // Initialize keyboard event handling if the control is focusable and has
+ // a key event target. (Controls hosted in containers typically aren't
+ // focusable, allowing their container to handle keyboard events for them.)
+ if (this.isSupportedState(goog.ui.Component.State.FOCUSED)) {
+ var keyTarget = this.getKeyEventTarget();
+ if (keyTarget) {
+ var keyHandler = this.getKeyHandler();
+ keyHandler.attach(keyTarget);
+ this.getHandler().
+ listen(keyHandler, goog.events.KeyHandler.EventType.KEY,
+ this.handleKeyEvent).
+ listen(keyTarget, goog.events.EventType.FOCUS,
+ this.handleFocus).
+ listen(keyTarget, goog.events.EventType.BLUR,
+ this.handleBlur);
+ }
+ }
+ }
+};
+
+
+/**
+ * Enables or disables mouse event handling on the control.
+ * @param {boolean} enable Whether to enable mouse event handling.
+ * @private
+ */
+goog.ui.Control.prototype.enableMouseEventHandling_ = function(enable) {
+ var handler = this.getHandler();
+ var element = this.getElement();
+ if (enable) {
+ handler.
+ listen(element, goog.events.EventType.MOUSEOVER, this.handleMouseOver).
+ listen(element, goog.events.EventType.MOUSEDOWN, this.handleMouseDown).
+ listen(element, goog.events.EventType.MOUSEUP, this.handleMouseUp).
+ listen(element, goog.events.EventType.MOUSEOUT, this.handleMouseOut);
+ if (this.handleContextMenu != goog.nullFunction) {
+ handler.listen(element, goog.events.EventType.CONTEXTMENU,
+ this.handleContextMenu);
+ }
+ if (goog.userAgent.IE) {
+ handler.listen(element, goog.events.EventType.DBLCLICK,
+ this.handleDblClick);
+ }
+ } else {
+ handler.
+ unlisten(element, goog.events.EventType.MOUSEOVER,
+ this.handleMouseOver).
+ unlisten(element, goog.events.EventType.MOUSEDOWN,
+ this.handleMouseDown).
+ unlisten(element, goog.events.EventType.MOUSEUP, this.handleMouseUp).
+ unlisten(element, goog.events.EventType.MOUSEOUT, this.handleMouseOut);
+ if (this.handleContextMenu != goog.nullFunction) {
+ handler.unlisten(element, goog.events.EventType.CONTEXTMENU,
+ this.handleContextMenu);
+ }
+ if (goog.userAgent.IE) {
+ handler.unlisten(element, goog.events.EventType.DBLCLICK,
+ this.handleDblClick);
+ }
+ }
+};
+
+
+/**
+ * Cleans up the component before its DOM is removed from the document, and
+ * removes event handlers. Overrides {@link goog.ui.Component#exitDocument}
+ * by making sure that components that are removed from the document aren't
+ * focusable (i.e. have no tab index).
+ * @override
+ */
+goog.ui.Control.prototype.exitDocument = function() {
+ goog.ui.Control.superClass_.exitDocument.call(this);
+ if (this.keyHandler_) {
+ this.keyHandler_.detach();
+ }
+ if (this.isVisible() && this.isEnabled()) {
+ this.renderer_.setFocusable(this, false);
+ }
+};
+
+
+/** @override */
+goog.ui.Control.prototype.disposeInternal = function() {
+ goog.ui.Control.superClass_.disposeInternal.call(this);
+ if (this.keyHandler_) {
+ this.keyHandler_.dispose();
+ delete this.keyHandler_;
+ }
+ delete this.renderer_;
+ this.content_ = null;
+ this.extraClassNames_ = null;
+};
+
+
+// Component content management.
+
+
+/**
+ * Returns the text caption or DOM structure displayed in the component.
+ * @return {goog.ui.ControlContent} Text caption or DOM structure
+ * comprising the component's contents.
+ */
+goog.ui.Control.prototype.getContent = function() {
+ return this.content_;
+};
+
+
+/**
+ * Sets the component's content to the given text caption, element, or array of
+ * nodes. (If the argument is an array of nodes, it must be an actual array,
+ * not an array-like object.)
+ * @param {goog.ui.ControlContent} content Text caption or DOM
+ * structure to set as the component's contents.
+ */
+goog.ui.Control.prototype.setContent = function(content) {
+ // Controls support pluggable renderers; delegate to the renderer.
+ this.renderer_.setContent(this.getElement(), content);
+
+ // setContentInternal needs to be after the renderer, since the implementation
+ // may depend on the content being in the DOM.
+ this.setContentInternal(content);
+};
+
+
+/**
+ * Sets the component's content to the given text caption, element, or array
+ * of nodes. Unlike {@link #setContent}, doesn't modify the component's DOM.
+ * Called by renderers during element decoration.
+ *
+ * This should only be used by subclasses and its associated renderers.
+ *
+ * @param {goog.ui.ControlContent} content Text caption or DOM structure
+ * to set as the component's contents.
+ */
+goog.ui.Control.prototype.setContentInternal = function(content) {
+ this.content_ = content;
+};
+
+
+/**
+ * @return {string} Text caption of the control or empty string if none.
+ */
+goog.ui.Control.prototype.getCaption = function() {
+ var content = this.getContent();
+ if (!content) {
+ return '';
+ }
+ var caption =
+ goog.isString(content) ? content :
+ goog.isArray(content) ? goog.array.map(content,
+ goog.dom.getRawTextContent).join('') :
+ goog.dom.getTextContent(/** @type {!Node} */ (content));
+ return goog.string.collapseBreakingSpaces(caption);
+};
+
+
+/**
+ * Sets the text caption of the component.
+ * @param {string} caption Text caption of the component.
+ */
+goog.ui.Control.prototype.setCaption = function(caption) {
+ this.setContent(caption);
+};
+
+
+// Component state management.
+
+
+/** @override */
+goog.ui.Control.prototype.setRightToLeft = function(rightToLeft) {
+ // The superclass implementation ensures the control isn't in the document.
+ goog.ui.Control.superClass_.setRightToLeft.call(this, rightToLeft);
+
+ var element = this.getElement();
+ if (element) {
+ this.renderer_.setRightToLeft(element, rightToLeft);
+ }
+};
+
+
+/**
+ * Returns true if the control allows text selection within its DOM, false
+ * otherwise. Controls that disallow text selection have the appropriate
+ * unselectable styling applied to their elements. Note that controls hosted
+ * in containers will report that they allow text selection even if their
+ * container disallows text selection.
+ * @return {boolean} Whether the control allows text selection.
+ */
+goog.ui.Control.prototype.isAllowTextSelection = function() {
+ return this.allowTextSelection_;
+};
+
+
+/**
+ * Allows or disallows text selection within the control's DOM.
+ * @param {boolean} allow Whether the control should allow text selection.
+ */
+goog.ui.Control.prototype.setAllowTextSelection = function(allow) {
+ this.allowTextSelection_ = allow;
+
+ var element = this.getElement();
+ if (element) {
+ this.renderer_.setAllowTextSelection(element, allow);
+ }
+};
+
+
+/**
+ * Returns true if the component's visibility is set to visible, false if
+ * it is set to hidden. A component that is set to hidden is guaranteed
+ * to be hidden from the user, but the reverse isn't necessarily true.
+ * A component may be set to visible but can otherwise be obscured by another
+ * element, rendered off-screen, or hidden using direct CSS manipulation.
+ * @return {boolean} Whether the component is visible.
+ */
+goog.ui.Control.prototype.isVisible = function() {
+ return this.visible_;
+};
+
+
+/**
+ * Shows or hides the component. Does nothing if the component already has
+ * the requested visibility. Otherwise, dispatches a SHOW or HIDE event as
+ * appropriate, giving listeners a chance to prevent the visibility change.
+ * When showing a component that is both enabled and focusable, ensures that
+ * its key target has a tab index. When hiding a component that is enabled
+ * and focusable, blurs its key target and removes its tab index.
+ * @param {boolean} visible Whether to show or hide the component.
+ * @param {boolean=} opt_force If true, doesn't check whether the component
+ * already has the requested visibility, and doesn't dispatch any events.
+ * @return {boolean} Whether the visibility was changed.
+ */
+goog.ui.Control.prototype.setVisible = function(visible, opt_force) {
+ if (opt_force || (this.visible_ != visible && this.dispatchEvent(visible ?
+ goog.ui.Component.EventType.SHOW : goog.ui.Component.EventType.HIDE))) {
+ var element = this.getElement();
+ if (element) {
+ this.renderer_.setVisible(element, visible);
+ }
+ if (this.isEnabled()) {
+ this.renderer_.setFocusable(this, visible);
+ }
+ this.visible_ = visible;
+ return true;
+ }
+ return false;
+};
+
+
+/**
+ * Returns true if the component is enabled, false otherwise.
+ * @return {boolean} Whether the component is enabled.
+ */
+goog.ui.Control.prototype.isEnabled = function() {
+ return !this.hasState(goog.ui.Component.State.DISABLED);
+};
+
+
+/**
+ * Returns true if the control has a parent that is itself disabled, false
+ * otherwise.
+ * @return {boolean} Whether the component is hosted in a disabled container.
+ * @private
+ */
+goog.ui.Control.prototype.isParentDisabled_ = function() {
+ var parent = this.getParent();
+ return !!parent && typeof parent.isEnabled == 'function' &&
+ !parent.isEnabled();
+};
+
+
+/**
+ * Enables or disables the component. Does nothing if this state transition
+ * is disallowed. If the component is both visible and focusable, updates its
+ * focused state and tab index as needed. If the component is being disabled,
+ * ensures that it is also deactivated and un-highlighted first. Note that the
+ * component's enabled/disabled state is "locked" as long as it is hosted in a
+ * {@link goog.ui.Container} that is itself disabled; this is to prevent clients
+ * from accidentally re-enabling a control that is in a disabled container.
+ * @param {boolean} enable Whether to enable or disable the component.
+ * @see #isTransitionAllowed
+ */
+goog.ui.Control.prototype.setEnabled = function(enable) {
+ if (!this.isParentDisabled_() &&
+ this.isTransitionAllowed(goog.ui.Component.State.DISABLED, !enable)) {
+ if (!enable) {
+ this.setActive(false);
+ this.setHighlighted(false);
+ }
+ if (this.isVisible()) {
+ this.renderer_.setFocusable(this, enable);
+ }
+ this.setState(goog.ui.Component.State.DISABLED, !enable);
+ }
+};
+
+
+/**
+ * Returns true if the component is currently highlighted, false otherwise.
+ * @return {boolean} Whether the component is highlighted.
+ */
+goog.ui.Control.prototype.isHighlighted = function() {
+ return this.hasState(goog.ui.Component.State.HOVER);
+};
+
+
+/**
+ * Highlights or unhighlights the component. Does nothing if this state
+ * transition is disallowed.
+ * @param {boolean} highlight Whether to highlight or unhighlight the component.
+ * @see #isTransitionAllowed
+ */
+goog.ui.Control.prototype.setHighlighted = function(highlight) {
+ if (this.isTransitionAllowed(goog.ui.Component.State.HOVER, highlight)) {
+ this.setState(goog.ui.Component.State.HOVER, highlight);
+ }
+};
+
+
+/**
+ * Returns true if the component is active (pressed), false otherwise.
+ * @return {boolean} Whether the component is active.
+ */
+goog.ui.Control.prototype.isActive = function() {
+ return this.hasState(goog.ui.Component.State.ACTIVE);
+};
+
+
+/**
+ * Activates or deactivates the component. Does nothing if this state
+ * transition is disallowed.
+ * @param {boolean} active Whether to activate or deactivate the component.
+ * @see #isTransitionAllowed
+ */
+goog.ui.Control.prototype.setActive = function(active) {
+ if (this.isTransitionAllowed(goog.ui.Component.State.ACTIVE, active)) {
+ this.setState(goog.ui.Component.State.ACTIVE, active);
+ }
+};
+
+
+/**
+ * Returns true if the component is selected, false otherwise.
+ * @return {boolean} Whether the component is selected.
+ */
+goog.ui.Control.prototype.isSelected = function() {
+ return this.hasState(goog.ui.Component.State.SELECTED);
+};
+
+
+/**
+ * Selects or unselects the component. Does nothing if this state transition
+ * is disallowed.
+ * @param {boolean} select Whether to select or unselect the component.
+ * @see #isTransitionAllowed
+ */
+goog.ui.Control.prototype.setSelected = function(select) {
+ if (this.isTransitionAllowed(goog.ui.Component.State.SELECTED, select)) {
+ this.setState(goog.ui.Component.State.SELECTED, select);
+ }
+};
+
+
+/**
+ * Returns true if the component is checked, false otherwise.
+ * @return {boolean} Whether the component is checked.
+ */
+goog.ui.Control.prototype.isChecked = function() {
+ return this.hasState(goog.ui.Component.State.CHECKED);
+};
+
+
+/**
+ * Checks or unchecks the component. Does nothing if this state transition
+ * is disallowed.
+ * @param {boolean} check Whether to check or uncheck the component.
+ * @see #isTransitionAllowed
+ */
+goog.ui.Control.prototype.setChecked = function(check) {
+ if (this.isTransitionAllowed(goog.ui.Component.State.CHECKED, check)) {
+ this.setState(goog.ui.Component.State.CHECKED, check);
+ }
+};
+
+
+/**
+ * Returns true if the component is styled to indicate that it has keyboard
+ * focus, false otherwise. Note that {@code isFocused()} returning true
+ * doesn't guarantee that the component's key event target has keyborad focus,
+ * only that it is styled as such.
+ * @return {boolean} Whether the component is styled to indicate as having
+ * keyboard focus.
+ */
+goog.ui.Control.prototype.isFocused = function() {
+ return this.hasState(goog.ui.Component.State.FOCUSED);
+};
+
+
+/**
+ * Applies or removes styling indicating that the component has keyboard focus.
+ * Note that unlike the other "set" methods, this method is called as a result
+ * of the component's element having received or lost keyboard focus, not the
+ * other way around, so calling {@code setFocused(true)} doesn't guarantee that
+ * the component's key event target has keyboard focus, only that it is styled
+ * as such.
+ * @param {boolean} focused Whether to apply or remove styling to indicate that
+ * the component's element has keyboard focus.
+ */
+goog.ui.Control.prototype.setFocused = function(focused) {
+ if (this.isTransitionAllowed(goog.ui.Component.State.FOCUSED, focused)) {
+ this.setState(goog.ui.Component.State.FOCUSED, focused);
+ }
+};
+
+
+/**
+ * Returns true if the component is open (expanded), false otherwise.
+ * @return {boolean} Whether the component is open.
+ */
+goog.ui.Control.prototype.isOpen = function() {
+ return this.hasState(goog.ui.Component.State.OPENED);
+};
+
+
+/**
+ * Opens (expands) or closes (collapses) the component. Does nothing if this
+ * state transition is disallowed.
+ * @param {boolean} open Whether to open or close the component.
+ * @see #isTransitionAllowed
+ */
+goog.ui.Control.prototype.setOpen = function(open) {
+ if (this.isTransitionAllowed(goog.ui.Component.State.OPENED, open)) {
+ this.setState(goog.ui.Component.State.OPENED, open);
+ }
+};
+
+
+/**
+ * Returns the component's state as a bit mask of {@link
+ * goog.ui.Component.State}s.
+ * @return {number} Bit mask representing component state.
+ */
+goog.ui.Control.prototype.getState = function() {
+ return this.state_;
+};
+
+
+/**
+ * Returns true if the component is in the specified state, false otherwise.
+ * @param {goog.ui.Component.State} state State to check.
+ * @return {boolean} Whether the component is in the given state.
+ */
+goog.ui.Control.prototype.hasState = function(state) {
+ return !!(this.state_ & state);
+};
+
+
+/**
+ * Sets or clears the given state on the component, and updates its styling
+ * accordingly. Does nothing if the component is already in the correct state
+ * or if it doesn't support the specified state. Doesn't dispatch any state
+ * transition events; use advisedly.
+ * @param {goog.ui.Component.State} state State to set or clear.
+ * @param {boolean} enable Whether to set or clear the state (if supported).
+ */
+goog.ui.Control.prototype.setState = function(state, enable) {
+ if (this.isSupportedState(state) && enable != this.hasState(state)) {
+ // Delegate actual styling to the renderer, since it is DOM-specific.
+ this.renderer_.setState(this, state, enable);
+ this.state_ = enable ? this.state_ | state : this.state_ & ~state;
+ }
+};
+
+
+/**
+ * Sets the component's state to the state represented by a bit mask of
+ * {@link goog.ui.Component.State}s. Unlike {@link #setState}, doesn't
+ * update the component's styling, and doesn't reject unsupported states.
+ * Called by renderers during element decoration. Considered protected;
+ * should only be used within this package and by subclasses.
+ *
+ * This should only be used by subclasses and its associated renderers.
+ *
+ * @param {number} state Bit mask representing component state.
+ */
+goog.ui.Control.prototype.setStateInternal = function(state) {
+ this.state_ = state;
+};
+
+
+/**
+ * Returns true if the component supports the specified state, false otherwise.
+ * @param {goog.ui.Component.State} state State to check.
+ * @return {boolean} Whether the component supports the given state.
+ */
+goog.ui.Control.prototype.isSupportedState = function(state) {
+ return !!(this.supportedStates_ & state);
+};
+
+
+/**
+ * Enables or disables support for the given state. Disabling support
+ * for a state while the component is in that state is an error.
+ * @param {goog.ui.Component.State} state State to support or de-support.
+ * @param {boolean} support Whether the component should support the state.
+ * @throws {Error} If disabling support for a state the control is currently in.
+ */
+goog.ui.Control.prototype.setSupportedState = function(state, support) {
+ if (this.isInDocument() && this.hasState(state) && !support) {
+ // Since we hook up event handlers in enterDocument(), this is an error.
+ throw Error(goog.ui.Component.Error.ALREADY_RENDERED);
+ }
+
+ if (!support && this.hasState(state)) {
+ // We are removing support for a state that the component is currently in.
+ this.setState(state, false);
+ }
+
+ this.supportedStates_ = support ?
+ this.supportedStates_ | state : this.supportedStates_ & ~state;
+};
+
+
+/**
+ * Returns true if the component provides default event handling for the state,
+ * false otherwise.
+ * @param {goog.ui.Component.State} state State to check.
+ * @return {boolean} Whether the component provides default event handling for
+ * the state.
+ */
+goog.ui.Control.prototype.isAutoState = function(state) {
+ return !!(this.autoStates_ & state) && this.isSupportedState(state);
+};
+
+
+/**
+ * Enables or disables automatic event handling for the given state(s).
+ * @param {number} states Bit mask of {@link goog.ui.Component.State}s for which
+ * default event handling is to be enabled or disabled.
+ * @param {boolean} enable Whether the component should provide default event
+ * handling for the state(s).
+ */
+goog.ui.Control.prototype.setAutoStates = function(states, enable) {
+ this.autoStates_ = enable ?
+ this.autoStates_ | states : this.autoStates_ & ~states;
+};
+
+
+/**
+ * Returns true if the component is set to dispatch transition events for the
+ * given state, false otherwise.
+ * @param {goog.ui.Component.State} state State to check.
+ * @return {boolean} Whether the component dispatches transition events for
+ * the state.
+ */
+goog.ui.Control.prototype.isDispatchTransitionEvents = function(state) {
+ return !!(this.statesWithTransitionEvents_ & state) &&
+ this.isSupportedState(state);
+};
+
+
+/**
+ * Enables or disables transition events for the given state(s). Controls
+ * handle state transitions internally by default, and only dispatch state
+ * transition events if explicitly requested to do so by calling this method.
+ * @param {number} states Bit mask of {@link goog.ui.Component.State}s for
+ * which transition events should be enabled or disabled.
+ * @param {boolean} enable Whether transition events should be enabled.
+ */
+goog.ui.Control.prototype.setDispatchTransitionEvents = function(states,
+ enable) {
+ this.statesWithTransitionEvents_ = enable ?
+ this.statesWithTransitionEvents_ | states :
+ this.statesWithTransitionEvents_ & ~states;
+};
+
+
+/**
+ * Returns true if the transition into or out of the given state is allowed to
+ * proceed, false otherwise. A state transition is allowed under the following
+ * conditions:
+ * <ul>
+ * <li>the component supports the state,
+ * <li>the component isn't already in the target state,
+ * <li>either the component is configured not to dispatch events for this
+ * state transition, or a transition event was dispatched and wasn't
+ * canceled by any event listener, and
+ * <li>the component hasn't been disposed of
+ * </ul>
+ * Considered protected; should only be used within this package and by
+ * subclasses.
+ * @param {goog.ui.Component.State} state State to/from which the control is
+ * transitioning.
+ * @param {boolean} enable Whether the control is entering or leaving the state.
+ * @return {boolean} Whether the state transition is allowed to proceed.
+ * @protected
+ */
+goog.ui.Control.prototype.isTransitionAllowed = function(state, enable) {
+ return this.isSupportedState(state) &&
+ this.hasState(state) != enable &&
+ (!(this.statesWithTransitionEvents_ & state) || this.dispatchEvent(
+ goog.ui.Component.getStateTransitionEvent(state, enable))) &&
+ !this.isDisposed();
+};
+
+
+// Default event handlers, to be overridden in subclasses.
+
+
+/**
+ * Handles mouseover events. Dispatches an ENTER event; if the event isn't
+ * canceled, the component is enabled, and it supports auto-highlighting,
+ * highlights the component. Considered protected; should only be used
+ * within this package and by subclasses.
+ * @param {goog.events.BrowserEvent} e Mouse event to handle.
+ */
+goog.ui.Control.prototype.handleMouseOver = function(e) {
+ // Ignore mouse moves between descendants.
+ if (!goog.ui.Control.isMouseEventWithinElement_(e, this.getElement()) &&
+ this.dispatchEvent(goog.ui.Component.EventType.ENTER) &&
+ this.isEnabled() &&
+ this.isAutoState(goog.ui.Component.State.HOVER)) {
+ this.setHighlighted(true);
+ }
+};
+
+
+/**
+ * Handles mouseout events. Dispatches a LEAVE event; if the event isn't
+ * canceled, and the component supports auto-highlighting, deactivates and
+ * un-highlights the component. Considered protected; should only be used
+ * within this package and by subclasses.
+ * @param {goog.events.BrowserEvent} e Mouse event to handle.
+ */
+goog.ui.Control.prototype.handleMouseOut = function(e) {
+ if (!goog.ui.Control.isMouseEventWithinElement_(e, this.getElement()) &&
+ this.dispatchEvent(goog.ui.Component.EventType.LEAVE)) {
+ if (this.isAutoState(goog.ui.Component.State.ACTIVE)) {
+ // Deactivate on mouseout; otherwise we lose track of the mouse button.
+ this.setActive(false);
+ }
+ if (this.isAutoState(goog.ui.Component.State.HOVER)) {
+ this.setHighlighted(false);
+ }
+ }
+};
+
+
+/**
+ * Handles contextmenu events.
+ * @param {goog.events.BrowserEvent} e Event to handle.
+ */
+goog.ui.Control.prototype.handleContextMenu = goog.nullFunction;
+
+
+/**
+ * Checks if a mouse event (mouseover or mouseout) occured below an element.
+ * @param {goog.events.BrowserEvent} e Mouse event (should be mouseover or
+ * mouseout).
+ * @param {Element} elem The ancestor element.
+ * @return {boolean} Whether the event has a relatedTarget (the element the
+ * mouse is coming from) and it's a descendent of elem.
+ * @private
+ */
+goog.ui.Control.isMouseEventWithinElement_ = function(e, elem) {
+ // If relatedTarget is null, it means there was no previous element (e.g.
+ // the mouse moved out of the window). Assume this means that the mouse
+ // event was not within the element.
+ return !!e.relatedTarget && goog.dom.contains(elem, e.relatedTarget);
+};
+
+
+/**
+ * Handles mousedown events. If the component is enabled, highlights and
+ * activates it. If the component isn't configured for keyboard access,
+ * prevents it from receiving keyboard focus. Considered protected; should
+ * only be used within this package and by subclasses.
+ * @param {goog.events.Event} e Mouse event to handle.
+ */
+goog.ui.Control.prototype.handleMouseDown = function(e) {
+ if (this.isEnabled()) {
+ // Highlight enabled control on mousedown, regardless of the mouse button.
+ if (this.isAutoState(goog.ui.Component.State.HOVER)) {
+ this.setHighlighted(true);
+ }
+
+ // For the left button only, activate the control, and focus its key event
+ // target (if supported).
+ if (e.isMouseActionButton()) {
+ if (this.isAutoState(goog.ui.Component.State.ACTIVE)) {
+ this.setActive(true);
+ }
+ if (this.renderer_.isFocusable(this)) {
+ this.getKeyEventTarget().focus();
+ }
+ }
+ }
+
+ // Cancel the default action unless the control allows text selection.
+ if (!this.isAllowTextSelection() && e.isMouseActionButton()) {
+ e.preventDefault();
+ }
+};
+
+
+/**
+ * Handles mouseup events. If the component is enabled, highlights it. If
+ * the component has previously been activated, performs its associated action
+ * by calling {@link performActionInternal}, then deactivates it. Considered
+ * protected; should only be used within this package and by subclasses.
+ * @param {goog.events.Event} e Mouse event to handle.
+ */
+goog.ui.Control.prototype.handleMouseUp = function(e) {
+ if (this.isEnabled()) {
+ if (this.isAutoState(goog.ui.Component.State.HOVER)) {
+ this.setHighlighted(true);
+ }
+ if (this.isActive() &&
+ this.performActionInternal(e) &&
+ this.isAutoState(goog.ui.Component.State.ACTIVE)) {
+ this.setActive(false);
+ }
+ }
+};
+
+
+/**
+ * Handles dblclick events. Should only be registered if the user agent is
+ * IE. If the component is enabled, performs its associated action by calling
+ * {@link performActionInternal}. This is used to allow more performant
+ * buttons in IE. In IE, no mousedown event is fired when that mousedown will
+ * trigger a dblclick event. Because of this, a user clicking quickly will
+ * only cause ACTION events to fire on every other click. This is a workaround
+ * to generate ACTION events for every click. Unfortunately, this workaround
+ * won't ever trigger the ACTIVE state. This is roughly the same behaviour as
+ * if this were a 'button' element with a listener on mouseup. Considered
+ * protected; should only be used within this package and by subclasses.
+ * @param {goog.events.Event} e Mouse event to handle.
+ */
+goog.ui.Control.prototype.handleDblClick = function(e) {
+ if (this.isEnabled()) {
+ this.performActionInternal(e);
+ }
+};
+
+
+/**
+ * Performs the appropriate action when the control is activated by the user.
+ * The default implementation first updates the checked and selected state of
+ * controls that support them, then dispatches an ACTION event. Considered
+ * protected; should only be used within this package and by subclasses.
+ * @param {goog.events.Event} e Event that triggered the action.
+ * @return {boolean} Whether the action is allowed to proceed.
+ * @protected
+ */
+goog.ui.Control.prototype.performActionInternal = function(e) {
+ if (this.isAutoState(goog.ui.Component.State.CHECKED)) {
+ this.setChecked(!this.isChecked());
+ }
+ if (this.isAutoState(goog.ui.Component.State.SELECTED)) {
+ this.setSelected(true);
+ }
+ if (this.isAutoState(goog.ui.Component.State.OPENED)) {
+ this.setOpen(!this.isOpen());
+ }
+
+ var actionEvent = new goog.events.Event(goog.ui.Component.EventType.ACTION,
+ this);
+ if (e) {
+ actionEvent.altKey = e.altKey;
+ actionEvent.ctrlKey = e.ctrlKey;
+ actionEvent.metaKey = e.metaKey;
+ actionEvent.shiftKey = e.shiftKey;
+ actionEvent.platformModifierKey = e.platformModifierKey;
+ }
+ return this.dispatchEvent(actionEvent);
+};
+
+
+/**
+ * Handles focus events on the component's key event target element. If the
+ * component is focusable, updates its state and styling to indicate that it
+ * now has keyboard focus. Considered protected; should only be used within
+ * this package and by subclasses. <b>Warning:</b> IE dispatches focus and
+ * blur events asynchronously!
+ * @param {goog.events.Event} e Focus event to handle.
+ */
+goog.ui.Control.prototype.handleFocus = function(e) {
+ if (this.isAutoState(goog.ui.Component.State.FOCUSED)) {
+ this.setFocused(true);
+ }
+};
+
+
+/**
+ * Handles blur events on the component's key event target element. Always
+ * deactivates the component. In addition, if the component is focusable,
+ * updates its state and styling to indicate that it no longer has keyboard
+ * focus. Considered protected; should only be used within this package and
+ * by subclasses. <b>Warning:</b> IE dispatches focus and blur events
+ * asynchronously!
+ * @param {goog.events.Event} e Blur event to handle.
+ */
+goog.ui.Control.prototype.handleBlur = function(e) {
+ if (this.isAutoState(goog.ui.Component.State.ACTIVE)) {
+ this.setActive(false);
+ }
+ if (this.isAutoState(goog.ui.Component.State.FOCUSED)) {
+ this.setFocused(false);
+ }
+};
+
+
+/**
+ * Attempts to handle a keyboard event, if the component is enabled and visible,
+ * by calling {@link handleKeyEventInternal}. Considered protected; should only
+ * be used within this package and by subclasses.
+ * @param {goog.events.KeyEvent} e Key event to handle.
+ * @return {boolean} Whether the key event was handled.
+ */
+goog.ui.Control.prototype.handleKeyEvent = function(e) {
+ if (this.isVisible() && this.isEnabled() &&
+ this.handleKeyEventInternal(e)) {
+ e.preventDefault();
+ e.stopPropagation();
+ return true;
+ }
+ return false;
+};
+
+
+/**
+ * Attempts to handle a keyboard event; returns true if the event was handled,
+ * false otherwise. Considered protected; should only be used within this
+ * package and by subclasses.
+ * @param {goog.events.KeyEvent} e Key event to handle.
+ * @return {boolean} Whether the key event was handled.
+ * @protected
+ */
+goog.ui.Control.prototype.handleKeyEventInternal = function(e) {
+ return e.keyCode == goog.events.KeyCodes.ENTER &&
+ this.performActionInternal(e);
+};
+
+
+// Register the default renderer for goog.ui.Controls.
+goog.ui.registry.setDefaultRenderer(goog.ui.Control, goog.ui.ControlRenderer);
+
+
+// Register a decorator factory function for goog.ui.Controls.
+goog.ui.registry.setDecoratorByClassName(goog.ui.ControlRenderer.CSS_CLASS,
+ function() {
+ return new goog.ui.Control(null);
+ });
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/controlcontent.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/controlcontent.js
new file mode 100644
index 0000000..c6f6571
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/controlcontent.js
@@ -0,0 +1,28 @@
+// Copyright 2009 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Type declaration for control content.
+ *
+ * @author nicksantos@google.com (Nick Santos)
+ */
+goog.provide('goog.ui.ControlContent');
+
+
+/**
+ * Type declaration for text caption or DOM structure to be used as the content
+ * of {@link goog.ui.Control}s.
+ * @typedef {string|Node|Array.<Node>|NodeList}
+ */
+goog.ui.ControlContent;
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/controlrenderer.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/controlrenderer.js
new file mode 100644
index 0000000..e16d1a2
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/controlrenderer.js
@@ -0,0 +1,927 @@
+// Copyright 2008 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Base class for control renderers.
+ * TODO(attila): If the renderer framework works well, pull it into Component.
+ *
+ * @author attila@google.com (Attila Bodis)
+ */
+
+goog.provide('goog.ui.ControlRenderer');
+
+goog.require('goog.a11y.aria');
+goog.require('goog.a11y.aria.Role');
+goog.require('goog.a11y.aria.State');
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.dom');
+goog.require('goog.dom.classlist');
+goog.require('goog.object');
+goog.require('goog.string');
+goog.require('goog.style');
+goog.require('goog.ui.Component');
+goog.require('goog.userAgent');
+
+
+
+/**
+ * Default renderer for {@link goog.ui.Control}s. Can be used as-is, but
+ * subclasses of Control will probably want to use renderers specifically
+ * tailored for them by extending this class. Controls that use renderers
+ * delegate one or more of the following API methods to the renderer:
+ * <ul>
+ * <li>{@code createDom} - renders the DOM for the component
+ * <li>{@code canDecorate} - determines whether an element can be decorated
+ * by the component
+ * <li>{@code decorate} - decorates an existing element with the component
+ * <li>{@code setState} - updates the appearance of the component based on
+ * its state
+ * <li>{@code getContent} - returns the component's content
+ * <li>{@code setContent} - sets the component's content
+ * </ul>
+ * Controls are stateful; renderers, on the other hand, should be stateless and
+ * reusable.
+ * @constructor
+ */
+goog.ui.ControlRenderer = function() {
+};
+goog.addSingletonGetter(goog.ui.ControlRenderer);
+goog.tagUnsealableClass(goog.ui.ControlRenderer);
+
+
+/**
+ * Constructs a new renderer and sets the CSS class that the renderer will use
+ * as the base CSS class to apply to all elements rendered by that renderer.
+ * An example to use this function using a color palette:
+ *
+ * <pre>
+ * var myCustomRenderer = goog.ui.ControlRenderer.getCustomRenderer(
+ * goog.ui.PaletteRenderer, 'my-special-palette');
+ * var newColorPalette = new goog.ui.ColorPalette(
+ * colors, myCustomRenderer, opt_domHelper);
+ * </pre>
+ *
+ * Your CSS can look like this now:
+ * <pre>
+ * .my-special-palette { }
+ * .my-special-palette-table { }
+ * .my-special-palette-cell { }
+ * etc.
+ * </pre>
+ *
+ * <em>instead</em> of
+ * <pre>
+ * .CSS_MY_SPECIAL_PALETTE .goog-palette { }
+ * .CSS_MY_SPECIAL_PALETTE .goog-palette-table { }
+ * .CSS_MY_SPECIAL_PALETTE .goog-palette-cell { }
+ * etc.
+ * </pre>
+ *
+ * You would want to use this functionality when you want an instance of a
+ * component to have specific styles different than the other components of the
+ * same type in your application. This avoids using descendant selectors to
+ * apply the specific styles to this component.
+ *
+ * @param {Function} ctor The constructor of the renderer you are trying to
+ * create.
+ * @param {string} cssClassName The name of the CSS class for this renderer.
+ * @return {goog.ui.ControlRenderer} An instance of the desired renderer with
+ * its getCssClass() method overridden to return the supplied custom CSS
+ * class name.
+ */
+goog.ui.ControlRenderer.getCustomRenderer = function(ctor, cssClassName) {
+ var renderer = new ctor();
+
+ /**
+ * Returns the CSS class to be applied to the root element of components
+ * rendered using this renderer.
+ * @return {string} Renderer-specific CSS class.
+ */
+ renderer.getCssClass = function() {
+ return cssClassName;
+ };
+
+ return renderer;
+};
+
+
+/**
+ * Default CSS class to be applied to the root element of components rendered
+ * by this renderer.
+ * @type {string}
+ */
+goog.ui.ControlRenderer.CSS_CLASS = goog.getCssName('goog-control');
+
+
+/**
+ * Array of arrays of CSS classes that we want composite classes added and
+ * removed for in IE6 and lower as a workaround for lack of multi-class CSS
+ * selector support.
+ *
+ * Subclasses that have accompanying CSS requiring this workaround should define
+ * their own static IE6_CLASS_COMBINATIONS constant and override
+ * getIe6ClassCombinations to return it.
+ *
+ * For example, if your stylesheet uses the selector .button.collapse-left
+ * (and is compiled to .button_collapse-left for the IE6 version of the
+ * stylesheet,) you should include ['button', 'collapse-left'] in this array
+ * and the class button_collapse-left will be applied to the root element
+ * whenever both button and collapse-left are applied individually.
+ *
+ * Members of each class name combination will be joined with underscores in the
+ * order that they're defined in the array. You should alphabetize them (for
+ * compatibility with the CSS compiler) unless you are doing something special.
+ * @type {Array.<Array.<string>>}
+ */
+goog.ui.ControlRenderer.IE6_CLASS_COMBINATIONS = [];
+
+
+/**
+ * Map of component states to corresponding ARIA attributes. Since the mapping
+ * of component states to ARIA attributes is neither component- nor
+ * renderer-specific, this is a static property of the renderer class, and is
+ * initialized on first use.
+ * @type {Object.<goog.ui.Component.State, goog.a11y.aria.State>}
+ * @private
+ * @const
+ */
+goog.ui.ControlRenderer.ARIA_ATTRIBUTE_MAP_;
+
+
+/**
+ * Map of certain ARIA states to ARIA roles that support them. Used for checked
+ * and selected Component states because they are used on Components with ARIA
+ * roles that do not support the corresponding ARIA state.
+ * @private {!Object.<goog.a11y.aria.Role, goog.a11y.aria.State>}
+ * @const
+ */
+goog.ui.ControlRenderer.TOGGLE_ARIA_STATE_MAP_ = goog.object.create(
+ goog.a11y.aria.Role.BUTTON, goog.a11y.aria.State.PRESSED,
+ goog.a11y.aria.Role.CHECKBOX, goog.a11y.aria.State.CHECKED,
+ goog.a11y.aria.Role.MENU_ITEM, goog.a11y.aria.State.SELECTED,
+ goog.a11y.aria.Role.MENU_ITEM_CHECKBOX, goog.a11y.aria.State.CHECKED,
+ goog.a11y.aria.Role.MENU_ITEM_RADIO, goog.a11y.aria.State.CHECKED,
+ goog.a11y.aria.Role.RADIO, goog.a11y.aria.State.CHECKED,
+ goog.a11y.aria.Role.TAB, goog.a11y.aria.State.SELECTED,
+ goog.a11y.aria.Role.TREEITEM, goog.a11y.aria.State.SELECTED);
+
+
+/**
+ * Returns the ARIA role to be applied to the control.
+ * See http://wiki/Main/ARIA for more info.
+ * @return {goog.a11y.aria.Role|undefined} ARIA role.
+ */
+goog.ui.ControlRenderer.prototype.getAriaRole = function() {
+ // By default, the ARIA role is unspecified.
+ return undefined;
+};
+
+
+/**
+ * Returns the control's contents wrapped in a DIV, with the renderer's own
+ * CSS class and additional state-specific classes applied to it.
+ * @param {goog.ui.Control} control Control to render.
+ * @return {Element} Root element for the control.
+ */
+goog.ui.ControlRenderer.prototype.createDom = function(control) {
+ // Create and return DIV wrapping contents.
+ var element = control.getDomHelper().createDom(
+ 'div', this.getClassNames(control).join(' '), control.getContent());
+
+ this.setAriaStates(control, element);
+ return element;
+};
+
+
+/**
+ * Takes the control's root element and returns the parent element of the
+ * control's contents. Since by default controls are rendered as a single
+ * DIV, the default implementation returns the element itself. Subclasses
+ * with more complex DOM structures must override this method as needed.
+ * @param {Element} element Root element of the control whose content element
+ * is to be returned.
+ * @return {Element} The control's content element.
+ */
+goog.ui.ControlRenderer.prototype.getContentElement = function(element) {
+ return element;
+};
+
+
+/**
+ * Updates the control's DOM by adding or removing the specified class name
+ * to/from its root element. May add additional combined classes as needed in
+ * IE6 and lower. Because of this, subclasses should use this method when
+ * modifying class names on the control's root element.
+ * @param {goog.ui.Control|Element} control Control instance (or root element)
+ * to be updated.
+ * @param {string} className CSS class name to add or remove.
+ * @param {boolean} enable Whether to add or remove the class name.
+ */
+goog.ui.ControlRenderer.prototype.enableClassName = function(control,
+ className, enable) {
+ var element = /** @type {Element} */ (
+ control.getElement ? control.getElement() : control);
+ if (element) {
+ var classNames = [className];
+
+ // For IE6, we need to enable any combined classes involving this class
+ // as well.
+ // TODO(user): Remove this as IE6 is no longer in use.
+ if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('7')) {
+ classNames = this.getAppliedCombinedClassNames_(
+ goog.dom.classlist.get(element), className);
+ classNames.push(className);
+ }
+
+ goog.dom.classlist.enableAll(element, classNames, enable);
+ }
+};
+
+
+/**
+ * Updates the control's DOM by adding or removing the specified extra class
+ * name to/from its element.
+ * @param {goog.ui.Control} control Control to be updated.
+ * @param {string} className CSS class name to add or remove.
+ * @param {boolean} enable Whether to add or remove the class name.
+ */
+goog.ui.ControlRenderer.prototype.enableExtraClassName = function(control,
+ className, enable) {
+ // The base class implementation is trivial; subclasses should override as
+ // needed.
+ this.enableClassName(control, className, enable);
+};
+
+
+/**
+ * Returns true if this renderer can decorate the element, false otherwise.
+ * The default implementation always returns true.
+ * @param {Element} element Element to decorate.
+ * @return {boolean} Whether the renderer can decorate the element.
+ */
+goog.ui.ControlRenderer.prototype.canDecorate = function(element) {
+ return true;
+};
+
+
+/**
+ * Default implementation of {@code decorate} for {@link goog.ui.Control}s.
+ * Initializes the control's ID, content, and state based on the ID of the
+ * element, its child nodes, and its CSS classes, respectively. Returns the
+ * element.
+ * @param {goog.ui.Control} control Control instance to decorate the element.
+ * @param {Element} element Element to decorate.
+ * @return {Element} Decorated element.
+ */
+goog.ui.ControlRenderer.prototype.decorate = function(control, element) {
+ // Set the control's ID to the decorated element's DOM ID, if any.
+ if (element.id) {
+ control.setId(element.id);
+ }
+
+ // Set the control's content to the decorated element's content.
+ var contentElem = this.getContentElement(element);
+ if (contentElem && contentElem.firstChild) {
+ control.setContentInternal(contentElem.firstChild.nextSibling ?
+ goog.array.clone(contentElem.childNodes) : contentElem.firstChild);
+ } else {
+ control.setContentInternal(null);
+ }
+
+ // Initialize the control's state based on the decorated element's CSS class.
+ // This implementation is optimized to minimize object allocations, string
+ // comparisons, and DOM access.
+ var state = 0x00;
+ var rendererClassName = this.getCssClass();
+ var structuralClassName = this.getStructuralCssClass();
+ var hasRendererClassName = false;
+ var hasStructuralClassName = false;
+ var hasCombinedClassName = false;
+ var classNames = goog.array.toArray(goog.dom.classlist.get(element));
+ goog.array.forEach(classNames, function(className) {
+ if (!hasRendererClassName && className == rendererClassName) {
+ hasRendererClassName = true;
+ if (structuralClassName == rendererClassName) {
+ hasStructuralClassName = true;
+ }
+ } else if (!hasStructuralClassName && className == structuralClassName) {
+ hasStructuralClassName = true;
+ } else {
+ state |= this.getStateFromClass(className);
+ }
+ }, this);
+ control.setStateInternal(state);
+
+ // Make sure the element has the renderer's CSS classes applied, as well as
+ // any extra class names set on the control.
+ if (!hasRendererClassName) {
+ classNames.push(rendererClassName);
+ if (structuralClassName == rendererClassName) {
+ hasStructuralClassName = true;
+ }
+ }
+ if (!hasStructuralClassName) {
+ classNames.push(structuralClassName);
+ }
+ var extraClassNames = control.getExtraClassNames();
+ if (extraClassNames) {
+ classNames.push.apply(classNames, extraClassNames);
+ }
+
+ // For IE6, rewrite all classes on the decorated element if any combined
+ // classes apply.
+ if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('7')) {
+ var combinedClasses = this.getAppliedCombinedClassNames_(
+ classNames);
+ if (combinedClasses.length > 0) {
+ classNames.push.apply(classNames, combinedClasses);
+ hasCombinedClassName = true;
+ }
+ }
+
+ // Only write to the DOM if new class names had to be added to the element.
+ if (!hasRendererClassName || !hasStructuralClassName ||
+ extraClassNames || hasCombinedClassName) {
+ goog.dom.classlist.set(element, classNames.join(' '));
+ }
+
+ this.setAriaStates(control, element);
+ return element;
+};
+
+
+/**
+ * Initializes the control's DOM by configuring properties that can only be set
+ * after the DOM has entered the document. This implementation sets up BiDi
+ * and keyboard focus. Called from {@link goog.ui.Control#enterDocument}.
+ * @param {goog.ui.Control} control Control whose DOM is to be initialized
+ * as it enters the document.
+ */
+goog.ui.ControlRenderer.prototype.initializeDom = function(control) {
+ // Initialize render direction (BiDi). We optimize the left-to-right render
+ // direction by assuming that elements are left-to-right by default, and only
+ // updating their styling if they are explicitly set to right-to-left.
+ if (control.isRightToLeft()) {
+ this.setRightToLeft(control.getElement(), true);
+ }
+
+ // Initialize keyboard focusability (tab index). We assume that components
+ // aren't focusable by default (i.e have no tab index), and only touch the
+ // DOM if the component is focusable, enabled, and visible, and therefore
+ // needs a tab index.
+ if (control.isEnabled()) {
+ this.setFocusable(control, control.isVisible());
+ }
+};
+
+
+/**
+ * Sets the element's ARIA role.
+ * @param {Element} element Element to update.
+ * @param {?goog.a11y.aria.Role=} opt_preferredRole The preferred ARIA role.
+ */
+goog.ui.ControlRenderer.prototype.setAriaRole = function(element,
+ opt_preferredRole) {
+ var ariaRole = opt_preferredRole || this.getAriaRole();
+ if (ariaRole) {
+ goog.asserts.assert(element,
+ 'The element passed as a first parameter cannot be null.');
+ var currentRole = goog.a11y.aria.getRole(element);
+ if (ariaRole == currentRole) {
+ return;
+ }
+ goog.a11y.aria.setRole(element, ariaRole);
+ }
+};
+
+
+/**
+ * Sets the element's ARIA attributes, including distinguishing between
+ * universally supported ARIA properties and ARIA states that are only
+ * supported by certain ARIA roles. Only attributes which are initialized to be
+ * true will be set.
+ * @param {!goog.ui.Control} control Control whose ARIA state will be updated.
+ * @param {!Element} element Element whose ARIA state is to be updated.
+ */
+goog.ui.ControlRenderer.prototype.setAriaStates = function(control, element) {
+ goog.asserts.assert(control);
+ goog.asserts.assert(element);
+
+ if (!control.isVisible()) {
+ goog.a11y.aria.setState(
+ element, goog.a11y.aria.State.HIDDEN, !control.isVisible());
+ }
+ if (!control.isEnabled()) {
+ this.updateAriaState(
+ element, goog.ui.Component.State.DISABLED, !control.isEnabled());
+ }
+ if (control.isSupportedState(goog.ui.Component.State.SELECTED)) {
+ this.updateAriaState(
+ element, goog.ui.Component.State.SELECTED, control.isSelected());
+ }
+ if (control.isSupportedState(goog.ui.Component.State.CHECKED)) {
+ this.updateAriaState(
+ element, goog.ui.Component.State.CHECKED, control.isChecked());
+ }
+ if (control.isSupportedState(goog.ui.Component.State.OPENED)) {
+ this.updateAriaState(
+ element, goog.ui.Component.State.OPENED, control.isOpen());
+ }
+};
+
+
+/**
+ * Allows or disallows text selection within the control's DOM.
+ * @param {Element} element The control's root element.
+ * @param {boolean} allow Whether the element should allow text selection.
+ */
+goog.ui.ControlRenderer.prototype.setAllowTextSelection = function(element,
+ allow) {
+ // On all browsers other than IE and Opera, it isn't necessary to recursively
+ // apply unselectable styling to the element's children.
+ goog.style.setUnselectable(element, !allow,
+ !goog.userAgent.IE && !goog.userAgent.OPERA);
+};
+
+
+/**
+ * Applies special styling to/from the control's element if it is rendered
+ * right-to-left, and removes it if it is rendered left-to-right.
+ * @param {Element} element The control's root element.
+ * @param {boolean} rightToLeft Whether the component is rendered
+ * right-to-left.
+ */
+goog.ui.ControlRenderer.prototype.setRightToLeft = function(element,
+ rightToLeft) {
+ this.enableClassName(element,
+ goog.getCssName(this.getStructuralCssClass(), 'rtl'), rightToLeft);
+};
+
+
+/**
+ * Returns true if the control's key event target supports keyboard focus
+ * (based on its {@code tabIndex} attribute), false otherwise.
+ * @param {goog.ui.Control} control Control whose key event target is to be
+ * checked.
+ * @return {boolean} Whether the control's key event target is focusable.
+ */
+goog.ui.ControlRenderer.prototype.isFocusable = function(control) {
+ var keyTarget;
+ if (control.isSupportedState(goog.ui.Component.State.FOCUSED) &&
+ (keyTarget = control.getKeyEventTarget())) {
+ return goog.dom.isFocusableTabIndex(keyTarget);
+ }
+ return false;
+};
+
+
+/**
+ * Updates the control's key event target to make it focusable or non-focusable
+ * via its {@code tabIndex} attribute. Does nothing if the control doesn't
+ * support the {@code FOCUSED} state, or if it has no key event target.
+ * @param {goog.ui.Control} control Control whose key event target is to be
+ * updated.
+ * @param {boolean} focusable Whether to enable keyboard focus support on the
+ * control's key event target.
+ */
+goog.ui.ControlRenderer.prototype.setFocusable = function(control, focusable) {
+ var keyTarget;
+ if (control.isSupportedState(goog.ui.Component.State.FOCUSED) &&
+ (keyTarget = control.getKeyEventTarget())) {
+ if (!focusable && control.isFocused()) {
+ // Blur before hiding. Note that IE calls onblur handlers asynchronously.
+ try {
+ keyTarget.blur();
+ } catch (e) {
+ // TODO(user|user): Find out why this fails on IE.
+ }
+ // The blur event dispatched by the key event target element when blur()
+ // was called on it should have been handled by the control's handleBlur()
+ // method, so at this point the control should no longer be focused.
+ // However, blur events are unreliable on IE and FF3, so if at this point
+ // the control is still focused, we trigger its handleBlur() method
+ // programmatically.
+ if (control.isFocused()) {
+ control.handleBlur(null);
+ }
+ }
+ // Don't overwrite existing tab index values unless needed.
+ if (goog.dom.isFocusableTabIndex(keyTarget) != focusable) {
+ goog.dom.setFocusableTabIndex(keyTarget, focusable);
+ }
+ }
+};
+
+
+/**
+ * Shows or hides the element.
+ * @param {Element} element Element to update.
+ * @param {boolean} visible Whether to show the element.
+ */
+goog.ui.ControlRenderer.prototype.setVisible = function(element, visible) {
+ // The base class implementation is trivial; subclasses should override as
+ // needed. It should be possible to do animated reveals, for example.
+ goog.style.setElementShown(element, visible);
+ if (element) {
+ goog.a11y.aria.setState(element, goog.a11y.aria.State.HIDDEN, !visible);
+ }
+};
+
+
+/**
+ * Updates the appearance of the control in response to a state change.
+ * @param {goog.ui.Control} control Control instance to update.
+ * @param {goog.ui.Component.State} state State to enable or disable.
+ * @param {boolean} enable Whether the control is entering or exiting the state.
+ */
+goog.ui.ControlRenderer.prototype.setState = function(control, state, enable) {
+ var element = control.getElement();
+ if (element) {
+ var className = this.getClassForState(state);
+ if (className) {
+ this.enableClassName(control, className, enable);
+ }
+ this.updateAriaState(element, state, enable);
+ }
+};
+
+
+/**
+ * Updates the element's ARIA (accessibility) attributes , including
+ * distinguishing between universally supported ARIA properties and ARIA states
+ * that are only supported by certain ARIA roles.
+ * @param {Element} element Element whose ARIA state is to be updated.
+ * @param {goog.ui.Component.State} state Component state being enabled or
+ * disabled.
+ * @param {boolean} enable Whether the state is being enabled or disabled.
+ * @protected
+ */
+goog.ui.ControlRenderer.prototype.updateAriaState = function(element, state,
+ enable) {
+ // Ensure the ARIA attribute map exists.
+ if (!goog.ui.ControlRenderer.ARIA_ATTRIBUTE_MAP_) {
+ goog.ui.ControlRenderer.ARIA_ATTRIBUTE_MAP_ = goog.object.create(
+ goog.ui.Component.State.DISABLED, goog.a11y.aria.State.DISABLED,
+ goog.ui.Component.State.SELECTED, goog.a11y.aria.State.SELECTED,
+ goog.ui.Component.State.CHECKED, goog.a11y.aria.State.CHECKED,
+ goog.ui.Component.State.OPENED, goog.a11y.aria.State.EXPANDED);
+ }
+ goog.asserts.assert(element,
+ 'The element passed as a first parameter cannot be null.');
+ var ariaAttr = goog.ui.ControlRenderer.getAriaStateForAriaRole_(
+ element, goog.ui.ControlRenderer.ARIA_ATTRIBUTE_MAP_[state]);
+ if (ariaAttr) {
+ goog.a11y.aria.setState(element, ariaAttr, enable);
+ }
+};
+
+
+/**
+ * Returns the appropriate ARIA attribute based on ARIA role if the ARIA
+ * attribute is an ARIA state.
+ * @param {!Element} element The element from which to get the ARIA role for
+ * matching ARIA state.
+ * @param {goog.a11y.aria.State} attr The ARIA attribute to check to see if it
+ * can be applied to the given ARIA role.
+ * @return {goog.a11y.aria.State} An ARIA attribute that can be applied to the
+ * given ARIA role.
+ * @private
+ */
+goog.ui.ControlRenderer.getAriaStateForAriaRole_ = function(element, attr) {
+ var role = goog.a11y.aria.getRole(element);
+ if (!role) {
+ return attr;
+ }
+ role = /** @type {goog.a11y.aria.Role} */ (role);
+ var matchAttr = goog.ui.ControlRenderer.TOGGLE_ARIA_STATE_MAP_[role] || attr;
+ return goog.ui.ControlRenderer.isAriaState_(attr) ? matchAttr : attr;
+};
+
+
+/**
+ * Determines if the given ARIA attribute is an ARIA property or ARIA state.
+ * @param {goog.a11y.aria.State} attr The ARIA attribute to classify.
+ * @return {boolean} If the ARIA attribute is an ARIA state.
+ * @private
+ */
+goog.ui.ControlRenderer.isAriaState_ = function(attr) {
+ return attr == goog.a11y.aria.State.CHECKED ||
+ attr == goog.a11y.aria.State.SELECTED;
+};
+
+
+/**
+ * Takes a control's root element, and sets its content to the given text
+ * caption or DOM structure. The default implementation replaces the children
+ * of the given element. Renderers that create more complex DOM structures
+ * must override this method accordingly.
+ * @param {Element} element The control's root element.
+ * @param {goog.ui.ControlContent} content Text caption or DOM structure to be
+ * set as the control's content. The DOM nodes will not be cloned, they
+ * will only moved under the content element of the control.
+ */
+goog.ui.ControlRenderer.prototype.setContent = function(element, content) {
+ var contentElem = this.getContentElement(element);
+ if (contentElem) {
+ goog.dom.removeChildren(contentElem);
+ if (content) {
+ if (goog.isString(content)) {
+ goog.dom.setTextContent(contentElem, content);
+ } else {
+ var childHandler = function(child) {
+ if (child) {
+ var doc = goog.dom.getOwnerDocument(contentElem);
+ contentElem.appendChild(goog.isString(child) ?
+ doc.createTextNode(child) : child);
+ }
+ };
+ if (goog.isArray(content)) {
+ // Array of nodes.
+ goog.array.forEach(content, childHandler);
+ } else if (goog.isArrayLike(content) && !('nodeType' in content)) {
+ // NodeList. The second condition filters out TextNode which also has
+ // length attribute but is not array like. The nodes have to be cloned
+ // because childHandler removes them from the list during iteration.
+ goog.array.forEach(goog.array.clone(/** @type {NodeList} */(content)),
+ childHandler);
+ } else {
+ // Node or string.
+ childHandler(content);
+ }
+ }
+ }
+ }
+};
+
+
+/**
+ * Returns the element within the component's DOM that should receive keyboard
+ * focus (null if none). The default implementation returns the control's root
+ * element.
+ * @param {goog.ui.Control} control Control whose key event target is to be
+ * returned.
+ * @return {Element} The key event target.
+ */
+goog.ui.ControlRenderer.prototype.getKeyEventTarget = function(control) {
+ return control.getElement();
+};
+
+
+// CSS class name management.
+
+
+/**
+ * Returns the CSS class name to be applied to the root element of all
+ * components rendered or decorated using this renderer. The class name
+ * is expected to uniquely identify the renderer class, i.e. no two
+ * renderer classes are expected to share the same CSS class name.
+ * @return {string} Renderer-specific CSS class name.
+ */
+goog.ui.ControlRenderer.prototype.getCssClass = function() {
+ return goog.ui.ControlRenderer.CSS_CLASS;
+};
+
+
+/**
+ * Returns an array of combinations of classes to apply combined class names for
+ * in IE6 and below. See {@link IE6_CLASS_COMBINATIONS} for more detail. This
+ * method doesn't reference {@link IE6_CLASS_COMBINATIONS} so that it can be
+ * compiled out, but subclasses should return their IE6_CLASS_COMBINATIONS
+ * static constant instead.
+ * @return {Array.<Array.<string>>} Array of class name combinations.
+ */
+goog.ui.ControlRenderer.prototype.getIe6ClassCombinations = function() {
+ return [];
+};
+
+
+/**
+ * Returns the name of a DOM structure-specific CSS class to be applied to the
+ * root element of all components rendered or decorated using this renderer.
+ * Unlike the class name returned by {@link #getCssClass}, the structural class
+ * name may be shared among different renderers that generate similar DOM
+ * structures. The structural class name also serves as the basis of derived
+ * class names used to identify and style structural elements of the control's
+ * DOM, as well as the basis for state-specific class names. The default
+ * implementation returns the same class name as {@link #getCssClass}, but
+ * subclasses are expected to override this method as needed.
+ * @return {string} DOM structure-specific CSS class name (same as the renderer-
+ * specific CSS class name by default).
+ */
+goog.ui.ControlRenderer.prototype.getStructuralCssClass = function() {
+ return this.getCssClass();
+};
+
+
+/**
+ * Returns all CSS class names applicable to the given control, based on its
+ * state. The return value is an array of strings containing
+ * <ol>
+ * <li>the renderer-specific CSS class returned by {@link #getCssClass},
+ * followed by
+ * <li>the structural CSS class returned by {@link getStructuralCssClass} (if
+ * different from the renderer-specific CSS class), followed by
+ * <li>any state-specific classes returned by {@link #getClassNamesForState},
+ * followed by
+ * <li>any extra classes returned by the control's {@code getExtraClassNames}
+ * method and
+ * <li>for IE6 and lower, additional combined classes from
+ * {@link getAppliedCombinedClassNames_}.
+ * </ol>
+ * Since all controls have at least one renderer-specific CSS class name, this
+ * method is guaranteed to return an array of at least one element.
+ * @param {goog.ui.Control} control Control whose CSS classes are to be
+ * returned.
+ * @return {!Array.<string>} Array of CSS class names applicable to the control.
+ * @protected
+ */
+goog.ui.ControlRenderer.prototype.getClassNames = function(control) {
+ var cssClass = this.getCssClass();
+
+ // Start with the renderer-specific class name.
+ var classNames = [cssClass];
+
+ // Add structural class name, if different.
+ var structuralCssClass = this.getStructuralCssClass();
+ if (structuralCssClass != cssClass) {
+ classNames.push(structuralCssClass);
+ }
+
+ // Add state-specific class names, if any.
+ var classNamesForState = this.getClassNamesForState(control.getState());
+ classNames.push.apply(classNames, classNamesForState);
+
+ // Add extra class names, if any.
+ var extraClassNames = control.getExtraClassNames();
+ if (extraClassNames) {
+ classNames.push.apply(classNames, extraClassNames);
+ }
+
+ // Add composite classes for IE6 support
+ if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('7')) {
+ classNames.push.apply(classNames,
+ this.getAppliedCombinedClassNames_(classNames));
+ }
+
+ return classNames;
+};
+
+
+/**
+ * Returns an array of all the combined class names that should be applied based
+ * on the given list of classes. Checks the result of
+ * {@link getIe6ClassCombinations} for any combinations that have all
+ * members contained in classes. If a combination matches, the members are
+ * joined with an underscore (in order), and added to the return array.
+ *
+ * If opt_includedClass is provided, return only the combined classes that have
+ * all members contained in classes AND include opt_includedClass as well.
+ * opt_includedClass is added to classes as well.
+ * @param {goog.array.ArrayLike.<string>} classes Array-like thing of classes to
+ * return matching combined classes for.
+ * @param {?string=} opt_includedClass If provided, get only the combined
+ * classes that include this one.
+ * @return {!Array.<string>} Array of combined class names that should be
+ * applied.
+ * @private
+ */
+goog.ui.ControlRenderer.prototype.getAppliedCombinedClassNames_ = function(
+ classes, opt_includedClass) {
+ var toAdd = [];
+ if (opt_includedClass) {
+ classes = classes.concat([opt_includedClass]);
+ }
+ goog.array.forEach(this.getIe6ClassCombinations(), function(combo) {
+ if (goog.array.every(combo, goog.partial(goog.array.contains, classes)) &&
+ (!opt_includedClass || goog.array.contains(combo, opt_includedClass))) {
+ toAdd.push(combo.join('_'));
+ }
+ });
+ return toAdd;
+};
+
+
+/**
+ * Takes a bit mask of {@link goog.ui.Component.State}s, and returns an array
+ * of the appropriate class names representing the given state, suitable to be
+ * applied to the root element of a component rendered using this renderer, or
+ * null if no state-specific classes need to be applied. This default
+ * implementation uses the renderer's {@link getClassForState} method to
+ * generate each state-specific class.
+ * @param {number} state Bit mask of component states.
+ * @return {!Array.<string>} Array of CSS class names representing the given
+ * state.
+ * @protected
+ */
+goog.ui.ControlRenderer.prototype.getClassNamesForState = function(state) {
+ var classNames = [];
+ while (state) {
+ // For each enabled state, push the corresponding CSS class name onto
+ // the classNames array.
+ var mask = state & -state; // Least significant bit
+ classNames.push(this.getClassForState(
+ /** @type {goog.ui.Component.State} */ (mask)));
+ state &= ~mask;
+ }
+ return classNames;
+};
+
+
+/**
+ * Takes a single {@link goog.ui.Component.State}, and returns the
+ * corresponding CSS class name (null if none).
+ * @param {goog.ui.Component.State} state Component state.
+ * @return {string|undefined} CSS class representing the given state (undefined
+ * if none).
+ * @protected
+ */
+goog.ui.ControlRenderer.prototype.getClassForState = function(state) {
+ if (!this.classByState_) {
+ this.createClassByStateMap_();
+ }
+ return this.classByState_[state];
+};
+
+
+/**
+ * Takes a single CSS class name which may represent a component state, and
+ * returns the corresponding component state (0x00 if none).
+ * @param {string} className CSS class name, possibly representing a component
+ * state.
+ * @return {goog.ui.Component.State} state Component state corresponding
+ * to the given CSS class (0x00 if none).
+ * @protected
+ */
+goog.ui.ControlRenderer.prototype.getStateFromClass = function(className) {
+ if (!this.stateByClass_) {
+ this.createStateByClassMap_();
+ }
+ var state = parseInt(this.stateByClass_[className], 10);
+ return /** @type {goog.ui.Component.State} */ (isNaN(state) ? 0x00 : state);
+};
+
+
+/**
+ * Creates the lookup table of states to classes, used during state changes.
+ * @private
+ */
+goog.ui.ControlRenderer.prototype.createClassByStateMap_ = function() {
+ var baseClass = this.getStructuralCssClass();
+
+ // This ensures space-separated css classnames are not allowed, which some
+ // ControlRenderers had been doing. See http://b/13694665.
+ var isValidClassName = !goog.string.contains(
+ goog.string.normalizeWhitespace(baseClass), ' ');
+ goog.asserts.assert(isValidClassName,
+ 'ControlRenderer has an invalid css class: \'' + baseClass + '\'');
+
+ /**
+ * Map of component states to state-specific structural class names,
+ * used when changing the DOM in response to a state change. Precomputed
+ * and cached on first use to minimize object allocations and string
+ * concatenation.
+ * @type {Object}
+ * @private
+ */
+ this.classByState_ = goog.object.create(
+ goog.ui.Component.State.DISABLED, goog.getCssName(baseClass, 'disabled'),
+ goog.ui.Component.State.HOVER, goog.getCssName(baseClass, 'hover'),
+ goog.ui.Component.State.ACTIVE, goog.getCssName(baseClass, 'active'),
+ goog.ui.Component.State.SELECTED, goog.getCssName(baseClass, 'selected'),
+ goog.ui.Component.State.CHECKED, goog.getCssName(baseClass, 'checked'),
+ goog.ui.Component.State.FOCUSED, goog.getCssName(baseClass, 'focused'),
+ goog.ui.Component.State.OPENED, goog.getCssName(baseClass, 'open'));
+};
+
+
+/**
+ * Creates the lookup table of classes to states, used during decoration.
+ * @private
+ */
+goog.ui.ControlRenderer.prototype.createStateByClassMap_ = function() {
+ // We need the classByState_ map so we can transpose it.
+ if (!this.classByState_) {
+ this.createClassByStateMap_();
+ }
+
+ /**
+ * Map of state-specific structural class names to component states,
+ * used during element decoration. Precomputed and cached on first use
+ * to minimize object allocations and string concatenation.
+ * @type {Object}
+ * @private
+ */
+ this.stateByClass_ = goog.object.transpose(this.classByState_);
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/decorate.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/decorate.js
new file mode 100644
index 0000000..0cf9c2d
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/decorate.js
@@ -0,0 +1,38 @@
+// Copyright 2008 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Provides a function that decorates an element based on its CSS
+ * class name.
+ * @author attila@google.com (Attila Bodis)
+ */
+
+goog.provide('goog.ui.decorate');
+
+goog.require('goog.ui.registry');
+
+
+/**
+ * Decorates the element with a suitable {@link goog.ui.Component} instance, if
+ * a matching decorator is found.
+ * @param {Element} element Element to decorate.
+ * @return {goog.ui.Component?} New component instance, decorating the element.
+ */
+goog.ui.decorate = function(element) {
+ var decorator = goog.ui.registry.getDecorator(element);
+ if (decorator) {
+ decorator.decorate(element);
+ }
+ return decorator;
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/idgenerator.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/idgenerator.js
new file mode 100644
index 0000000..c018a385
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/idgenerator.js
@@ -0,0 +1,48 @@
+// Copyright 2008 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Generator for unique element IDs.
+ *
+ */
+
+goog.provide('goog.ui.IdGenerator');
+
+
+
+/**
+ * Creates a new id generator.
+ * @constructor
+ * @final
+ */
+goog.ui.IdGenerator = function() {
+};
+goog.addSingletonGetter(goog.ui.IdGenerator);
+
+
+/**
+ * Next unique ID to use
+ * @type {number}
+ * @private
+ */
+goog.ui.IdGenerator.prototype.nextId_ = 0;
+
+
+/**
+ * Gets the next unique ID.
+ * @return {string} The next unique identifier.
+ */
+goog.ui.IdGenerator.prototype.getNextUniqueId = function() {
+ return ':' + (this.nextId_++).toString(36);
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/menuseparatorrenderer.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/menuseparatorrenderer.js
new file mode 100644
index 0000000..7e4dd5a
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/menuseparatorrenderer.js
@@ -0,0 +1,113 @@
+// Copyright 2008 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Renderer for {@link goog.ui.MenuSeparator}s.
+ *
+ * @author attila@google.com (Attila Bodis)
+ */
+
+goog.provide('goog.ui.MenuSeparatorRenderer');
+
+goog.require('goog.dom');
+goog.require('goog.dom.classes');
+goog.require('goog.ui.ControlContent');
+goog.require('goog.ui.ControlRenderer');
+
+
+
+/**
+ * Renderer for menu separators.
+ * @constructor
+ * @extends {goog.ui.ControlRenderer}
+ */
+goog.ui.MenuSeparatorRenderer = function() {
+ goog.ui.ControlRenderer.call(this);
+};
+goog.inherits(goog.ui.MenuSeparatorRenderer, goog.ui.ControlRenderer);
+goog.addSingletonGetter(goog.ui.MenuSeparatorRenderer);
+
+
+/**
+ * Default CSS class to be applied to the root element of components rendered
+ * by this renderer.
+ * @type {string}
+ */
+goog.ui.MenuSeparatorRenderer.CSS_CLASS = goog.getCssName('goog-menuseparator');
+
+
+/**
+ * Returns an empty, styled menu separator DIV. Overrides {@link
+ * goog.ui.ControlRenderer#createDom}.
+ * @param {goog.ui.Control} separator goog.ui.Separator to render.
+ * @return {Element} Root element for the separator.
+ * @override
+ */
+goog.ui.MenuSeparatorRenderer.prototype.createDom = function(separator) {
+ return separator.getDomHelper().createDom('div', this.getCssClass());
+};
+
+
+/**
+ * Takes an existing element, and decorates it with the separator. Overrides
+ * {@link goog.ui.ControlRenderer#decorate}.
+ * @param {goog.ui.Control} separator goog.ui.MenuSeparator to decorate the
+ * element.
+ * @param {Element} element Element to decorate.
+ * @return {Element} Decorated element.
+ * @override
+ */
+goog.ui.MenuSeparatorRenderer.prototype.decorate = function(separator,
+ element) {
+ // Normally handled in the superclass. But we don't call the superclass.
+ if (element.id) {
+ separator.setId(element.id);
+ }
+
+ if (element.tagName == 'HR') {
+ // Replace HR with separator.
+ var hr = element;
+ element = this.createDom(separator);
+ goog.dom.insertSiblingBefore(element, hr);
+ goog.dom.removeNode(hr);
+ } else {
+ goog.dom.classes.add(element, this.getCssClass());
+ }
+ return element;
+};
+
+
+/**
+ * Overrides {@link goog.ui.ControlRenderer#setContent} to do nothing, since
+ * separators are empty.
+ * @param {Element} separator The separator's root element.
+ * @param {goog.ui.ControlContent} content Text caption or DOM structure to be
+ * set as the separators's content (ignored).
+ * @override
+ */
+goog.ui.MenuSeparatorRenderer.prototype.setContent = function(separator,
+ content) {
+ // Do nothing. Separators are empty.
+};
+
+
+/**
+ * Returns the CSS class to be applied to the root element of components
+ * rendered using this renderer.
+ * @return {string} Renderer-specific CSS class.
+ * @override
+ */
+goog.ui.MenuSeparatorRenderer.prototype.getCssClass = function() {
+ return goog.ui.MenuSeparatorRenderer.CSS_CLASS;
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/registry.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/registry.js
new file mode 100644
index 0000000..91257dc
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/registry.js
@@ -0,0 +1,172 @@
+// Copyright 2008 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Global renderer and decorator registry.
+ * @author attila@google.com (Attila Bodis)
+ */
+
+goog.provide('goog.ui.registry');
+
+goog.require('goog.asserts');
+goog.require('goog.dom.classlist');
+
+
+/**
+ * Given a {@link goog.ui.Component} constructor, returns an instance of its
+ * default renderer. If the default renderer is a singleton, returns the
+ * singleton instance; otherwise returns a new instance of the renderer class.
+ * @param {Function} componentCtor Component constructor function (for example
+ * {@code goog.ui.Button}).
+ * @return {goog.ui.ControlRenderer?} Renderer instance (for example the
+ * singleton instance of {@code goog.ui.ButtonRenderer}), or null if
+ * no default renderer was found.
+ */
+goog.ui.registry.getDefaultRenderer = function(componentCtor) {
+ // Locate the default renderer based on the constructor's unique ID. If no
+ // renderer is registered for this class, walk up the superClass_ chain.
+ var key;
+ /** @type {Function|undefined} */ var rendererCtor;
+ while (componentCtor) {
+ key = goog.getUid(componentCtor);
+ if ((rendererCtor = goog.ui.registry.defaultRenderers_[key])) {
+ break;
+ }
+ componentCtor = componentCtor.superClass_ ?
+ componentCtor.superClass_.constructor : null;
+ }
+
+ // If the renderer has a static getInstance method, return the singleton
+ // instance; otherwise create and return a new instance.
+ if (rendererCtor) {
+ return goog.isFunction(rendererCtor.getInstance) ?
+ rendererCtor.getInstance() : new rendererCtor();
+ }
+
+ return null;
+};
+
+
+/**
+ * Sets the default renderer for the given {@link goog.ui.Component}
+ * constructor.
+ * @param {Function} componentCtor Component constructor function (for example
+ * {@code goog.ui.Button}).
+ * @param {Function} rendererCtor Renderer constructor function (for example
+ * {@code goog.ui.ButtonRenderer}).
+ * @throws {Error} If the arguments aren't functions.
+ */
+goog.ui.registry.setDefaultRenderer = function(componentCtor, rendererCtor) {
+ // In this case, explicit validation has negligible overhead (since each
+ // renderer is only registered once), and helps catch subtle bugs.
+ if (!goog.isFunction(componentCtor)) {
+ throw Error('Invalid component class ' + componentCtor);
+ }
+ if (!goog.isFunction(rendererCtor)) {
+ throw Error('Invalid renderer class ' + rendererCtor);
+ }
+
+ // Map the component constructor's unique ID to the renderer constructor.
+ var key = goog.getUid(componentCtor);
+ goog.ui.registry.defaultRenderers_[key] = rendererCtor;
+};
+
+
+/**
+ * Returns the {@link goog.ui.Component} instance created by the decorator
+ * factory function registered for the given CSS class name, or null if no
+ * decorator factory function was found.
+ * @param {string} className CSS class name.
+ * @return {goog.ui.Component?} Component instance.
+ */
+goog.ui.registry.getDecoratorByClassName = function(className) {
+ return className in goog.ui.registry.decoratorFunctions_ ?
+ goog.ui.registry.decoratorFunctions_[className]() : null;
+};
+
+
+/**
+ * Maps a CSS class name to a function that returns a new instance of
+ * {@link goog.ui.Component} or a subclass, suitable to decorate an element
+ * that has the specified CSS class.
+ * @param {string} className CSS class name.
+ * @param {Function} decoratorFn No-argument function that returns a new
+ * instance of a {@link goog.ui.Component} to decorate an element.
+ * @throws {Error} If the class name or the decorator function is invalid.
+ */
+goog.ui.registry.setDecoratorByClassName = function(className, decoratorFn) {
+ // In this case, explicit validation has negligible overhead (since each
+ // decorator is only registered once), and helps catch subtle bugs.
+ if (!className) {
+ throw Error('Invalid class name ' + className);
+ }
+ if (!goog.isFunction(decoratorFn)) {
+ throw Error('Invalid decorator function ' + decoratorFn);
+ }
+
+ goog.ui.registry.decoratorFunctions_[className] = decoratorFn;
+};
+
+
+/**
+ * Returns an instance of {@link goog.ui.Component} or a subclass suitable to
+ * decorate the given element, based on its CSS class.
+ *
+ * TODO(nnaze): Type of element should be {!Element}.
+ *
+ * @param {Element} element Element to decorate.
+ * @return {goog.ui.Component?} Component to decorate the element (null if
+ * none).
+ */
+goog.ui.registry.getDecorator = function(element) {
+ var decorator;
+ goog.asserts.assert(element);
+ var classNames = goog.dom.classlist.get(element);
+ for (var i = 0, len = classNames.length; i < len; i++) {
+ if ((decorator = goog.ui.registry.getDecoratorByClassName(classNames[i]))) {
+ return decorator;
+ }
+ }
+ return null;
+};
+
+
+/**
+ * Resets the global renderer and decorator registry.
+ */
+goog.ui.registry.reset = function() {
+ goog.ui.registry.defaultRenderers_ = {};
+ goog.ui.registry.decoratorFunctions_ = {};
+};
+
+
+/**
+ * Map of {@link goog.ui.Component} constructor unique IDs to the constructors
+ * of their default {@link goog.ui.Renderer}s.
+ * @type {Object}
+ * @private
+ */
+goog.ui.registry.defaultRenderers_ = {};
+
+
+/**
+ * Map of CSS class names to registry factory functions. The keys are
+ * class names. The values are function objects that return new instances
+ * of {@link goog.ui.registry} or one of its subclasses, suitable to
+ * decorate elements marked with the corresponding CSS class. Used by
+ * containers while decorating their children.
+ * @type {Object}
+ * @private
+ */
+goog.ui.registry.decoratorFunctions_ = {};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/separator.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/separator.js
new file mode 100644
index 0000000..b1ffb69
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/separator.js
@@ -0,0 +1,80 @@
+// Copyright 2007 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview A class for representing a separator, with renderers for both
+ * horizontal (menu) and vertical (toolbar) separators.
+ *
+ * @author attila@google.com (Attila Bodis)
+ */
+
+goog.provide('goog.ui.Separator');
+
+goog.require('goog.a11y.aria');
+goog.require('goog.asserts');
+goog.require('goog.ui.Component.State');
+goog.require('goog.ui.Control');
+goog.require('goog.ui.MenuSeparatorRenderer');
+goog.require('goog.ui.registry');
+
+
+
+/**
+ * Class representing a separator. Although it extends {@link goog.ui.Control},
+ * the Separator class doesn't allocate any event handlers, nor does it change
+ * its appearance on mouseover, etc.
+ * @param {goog.ui.MenuSeparatorRenderer=} opt_renderer Renderer to render or
+ * decorate the separator; defaults to {@link goog.ui.MenuSeparatorRenderer}.
+ * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper, used for
+ * document interaction.
+ * @constructor
+ * @extends {goog.ui.Control}
+ */
+goog.ui.Separator = function(opt_renderer, opt_domHelper) {
+ goog.ui.Control.call(this, null, opt_renderer ||
+ goog.ui.MenuSeparatorRenderer.getInstance(), opt_domHelper);
+
+ this.setSupportedState(goog.ui.Component.State.DISABLED, false);
+ this.setSupportedState(goog.ui.Component.State.HOVER, false);
+ this.setSupportedState(goog.ui.Component.State.ACTIVE, false);
+ this.setSupportedState(goog.ui.Component.State.FOCUSED, false);
+
+ // Separators are always considered disabled.
+ this.setStateInternal(goog.ui.Component.State.DISABLED);
+};
+goog.inherits(goog.ui.Separator, goog.ui.Control);
+
+
+/**
+ * Configures the component after its DOM has been rendered. Overrides
+ * {@link goog.ui.Control#enterDocument} by making sure no event handler
+ * is allocated.
+ * @override
+ */
+goog.ui.Separator.prototype.enterDocument = function() {
+ goog.ui.Separator.superClass_.enterDocument.call(this);
+ var element = this.getElement();
+ goog.asserts.assert(element,
+ 'The DOM element for the separator cannot be null.');
+ goog.a11y.aria.setRole(element, 'separator');
+};
+
+
+// Register a decorator factory function for goog.ui.MenuSeparators.
+goog.ui.registry.setDecoratorByClassName(
+ goog.ui.MenuSeparatorRenderer.CSS_CLASS,
+ function() {
+ // Separator defaults to using MenuSeparatorRenderer.
+ return new goog.ui.Separator();
+ });
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/uri/utils.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/uri/utils.js
new file mode 100644
index 0000000..425661e
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/uri/utils.js
@@ -0,0 +1,1076 @@
+// Copyright 2008 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Simple utilities for dealing with URI strings.
+ *
+ * This is intended to be a lightweight alternative to constructing goog.Uri
+ * objects. Whereas goog.Uri adds several kilobytes to the binary regardless
+ * of how much of its functionality you use, this is designed to be a set of
+ * mostly-independent utilities so that the compiler includes only what is
+ * necessary for the task. Estimated savings of porting is 5k pre-gzip and
+ * 1.5k post-gzip. To ensure the savings remain, future developers should
+ * avoid adding new functionality to existing functions, but instead create
+ * new ones and factor out shared code.
+ *
+ * Many of these utilities have limited functionality, tailored to common
+ * cases. The query parameter utilities assume that the parameter keys are
+ * already encoded, since most keys are compile-time alphanumeric strings. The
+ * query parameter mutation utilities also do not tolerate fragment identifiers.
+ *
+ * By design, these functions can be slower than goog.Uri equivalents.
+ * Repeated calls to some of functions may be quadratic in behavior for IE,
+ * although the effect is somewhat limited given the 2kb limit.
+ *
+ * One advantage of the limited functionality here is that this approach is
+ * less sensitive to differences in URI encodings than goog.Uri, since these
+ * functions modify the strings in place, rather than decoding and
+ * re-encoding.
+ *
+ * Uses features of RFC 3986 for parsing/formatting URIs:
+ * http://www.ietf.org/rfc/rfc3986.txt
+ *
+ * @author gboyer@google.com (Garrett Boyer) - The "lightened" design.
+ * @author msamuel@google.com (Mike Samuel) - Domain knowledge and regexes.
+ */
+
+goog.provide('goog.uri.utils');
+goog.provide('goog.uri.utils.ComponentIndex');
+goog.provide('goog.uri.utils.QueryArray');
+goog.provide('goog.uri.utils.QueryValue');
+goog.provide('goog.uri.utils.StandardQueryParam');
+
+goog.require('goog.asserts');
+goog.require('goog.string');
+goog.require('goog.userAgent');
+
+
+/**
+ * Character codes inlined to avoid object allocations due to charCode.
+ * @enum {number}
+ * @private
+ */
+goog.uri.utils.CharCode_ = {
+ AMPERSAND: 38,
+ EQUAL: 61,
+ HASH: 35,
+ QUESTION: 63
+};
+
+
+/**
+ * Builds a URI string from already-encoded parts.
+ *
+ * No encoding is performed. Any component may be omitted as either null or
+ * undefined.
+ *
+ * @param {?string=} opt_scheme The scheme such as 'http'.
+ * @param {?string=} opt_userInfo The user name before the '@'.
+ * @param {?string=} opt_domain The domain such as 'www.google.com', already
+ * URI-encoded.
+ * @param {(string|number|null)=} opt_port The port number.
+ * @param {?string=} opt_path The path, already URI-encoded. If it is not
+ * empty, it must begin with a slash.
+ * @param {?string=} opt_queryData The URI-encoded query data.
+ * @param {?string=} opt_fragment The URI-encoded fragment identifier.
+ * @return {string} The fully combined URI.
+ */
+goog.uri.utils.buildFromEncodedParts = function(opt_scheme, opt_userInfo,
+ opt_domain, opt_port, opt_path, opt_queryData, opt_fragment) {
+ var out = '';
+
+ if (opt_scheme) {
+ out += opt_scheme + ':';
+ }
+
+ if (opt_domain) {
+ out += '//';
+
+ if (opt_userInfo) {
+ out += opt_userInfo + '@';
+ }
+
+ out += opt_domain;
+
+ if (opt_port) {
+ out += ':' + opt_port;
+ }
+ }
+
+ if (opt_path) {
+ out += opt_path;
+ }
+
+ if (opt_queryData) {
+ out += '?' + opt_queryData;
+ }
+
+ if (opt_fragment) {
+ out += '#' + opt_fragment;
+ }
+
+ return out;
+};
+
+
+/**
+ * A regular expression for breaking a URI into its component parts.
+ *
+ * {@link http://www.ietf.org/rfc/rfc3986.txt} says in Appendix B
+ * As the "first-match-wins" algorithm is identical to the "greedy"
+ * disambiguation method used by POSIX regular expressions, it is natural and
+ * commonplace to use a regular expression for parsing the potential five
+ * components of a URI reference.
+ *
+ * The following line is the regular expression for breaking-down a
+ * well-formed URI reference into its components.
+ *
+ * <pre>
+ * ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
+ * 12 3 4 5 6 7 8 9
+ * </pre>
+ *
+ * The numbers in the second line above are only to assist readability; they
+ * indicate the reference points for each subexpression (i.e., each paired
+ * parenthesis). We refer to the value matched for subexpression <n> as $<n>.
+ * For example, matching the above expression to
+ * <pre>
+ * http://www.ics.uci.edu/pub/ietf/uri/#Related
+ * </pre>
+ * results in the following subexpression matches:
+ * <pre>
+ * $1 = http:
+ * $2 = http
+ * $3 = //www.ics.uci.edu
+ * $4 = www.ics.uci.edu
+ * $5 = /pub/ietf/uri/
+ * $6 = <undefined>
+ * $7 = <undefined>
+ * $8 = #Related
+ * $9 = Related
+ * </pre>
+ * where <undefined> indicates that the component is not present, as is the
+ * case for the query component in the above example. Therefore, we can
+ * determine the value of the five components as
+ * <pre>
+ * scheme = $2
+ * authority = $4
+ * path = $5
+ * query = $7
+ * fragment = $9
+ * </pre>
+ *
+ * The regular expression has been modified slightly to expose the
+ * userInfo, domain, and port separately from the authority.
+ * The modified version yields
+ * <pre>
+ * $1 = http scheme
+ * $2 = <undefined> userInfo -\
+ * $3 = www.ics.uci.edu domain | authority
+ * $4 = <undefined> port -/
+ * $5 = /pub/ietf/uri/ path
+ * $6 = <undefined> query without ?
+ * $7 = Related fragment without #
+ * </pre>
+ * @type {!RegExp}
+ * @private
+ */
+goog.uri.utils.splitRe_ = new RegExp(
+ '^' +
+ '(?:' +
+ '([^:/?#.]+)' + // scheme - ignore special characters
+ // used by other URL parts such as :,
+ // ?, /, #, and .
+ ':)?' +
+ '(?://' +
+ '(?:([^/?#]*)@)?' + // userInfo
+ '([^/#?]*?)' + // domain
+ '(?::([0-9]+))?' + // port
+ '(?=[/#?]|$)' + // authority-terminating character
+ ')?' +
+ '([^?#]+)?' + // path
+ '(?:\\?([^#]*))?' + // query
+ '(?:#(.*))?' + // fragment
+ '$');
+
+
+/**
+ * The index of each URI component in the return value of goog.uri.utils.split.
+ * @enum {number}
+ */
+goog.uri.utils.ComponentIndex = {
+ SCHEME: 1,
+ USER_INFO: 2,
+ DOMAIN: 3,
+ PORT: 4,
+ PATH: 5,
+ QUERY_DATA: 6,
+ FRAGMENT: 7
+};
+
+
+/**
+ * Splits a URI into its component parts.
+ *
+ * Each component can be accessed via the component indices; for example:
+ * <pre>
+ * goog.uri.utils.split(someStr)[goog.uri.utils.CompontentIndex.QUERY_DATA];
+ * </pre>
+ *
+ * @param {string} uri The URI string to examine.
+ * @return {!Array.<string|undefined>} Each component still URI-encoded.
+ * Each component that is present will contain the encoded value, whereas
+ * components that are not present will be undefined or empty, depending
+ * on the browser's regular expression implementation. Never null, since
+ * arbitrary strings may still look like path names.
+ */
+goog.uri.utils.split = function(uri) {
+ goog.uri.utils.phishingProtection_();
+
+ // See @return comment -- never null.
+ return /** @type {!Array.<string|undefined>} */ (
+ uri.match(goog.uri.utils.splitRe_));
+};
+
+
+/**
+ * Safari has a nasty bug where if you have an http URL with a username, e.g.,
+ * http://evil.com%2F@google.com/
+ * Safari will report that window.location.href is
+ * http://evil.com/google.com/
+ * so that anyone who tries to parse the domain of that URL will get
+ * the wrong domain. We've seen exploits where people use this to trick
+ * Safari into loading resources from evil domains.
+ *
+ * To work around this, we run a little "Safari phishing check", and throw
+ * an exception if we see this happening.
+ *
+ * There is no convenient place to put this check. We apply it to
+ * anyone doing URI parsing on Webkit. We're not happy about this, but
+ * it fixes the problem.
+ *
+ * This should be removed once Safari fixes their bug.
+ *
+ * Exploit reported by Masato Kinugawa.
+ *
+ * @type {boolean}
+ * @private
+ */
+goog.uri.utils.needsPhishingProtection_ = goog.userAgent.WEBKIT;
+
+
+/**
+ * Check to see if the user is being phished.
+ * @private
+ */
+goog.uri.utils.phishingProtection_ = function() {
+ if (goog.uri.utils.needsPhishingProtection_) {
+ // Turn protection off, so that we don't recurse.
+ goog.uri.utils.needsPhishingProtection_ = false;
+
+ // Use quoted access, just in case the user isn't using location externs.
+ var location = goog.global['location'];
+ if (location) {
+ var href = location['href'];
+ if (href) {
+ var domain = goog.uri.utils.getDomain(href);
+ if (domain && domain != location['hostname']) {
+ // Phishing attack
+ goog.uri.utils.needsPhishingProtection_ = true;
+ throw Error();
+ }
+ }
+ }
+ }
+};
+
+
+/**
+ * @param {?string} uri A possibly null string.
+ * @return {?string} The string URI-decoded, or null if uri is null.
+ * @private
+ */
+goog.uri.utils.decodeIfPossible_ = function(uri) {
+ return uri && decodeURIComponent(uri);
+};
+
+
+/**
+ * Gets a URI component by index.
+ *
+ * It is preferred to use the getPathEncoded() variety of functions ahead,
+ * since they are more readable.
+ *
+ * @param {goog.uri.utils.ComponentIndex} componentIndex The component index.
+ * @param {string} uri The URI to examine.
+ * @return {?string} The still-encoded component, or null if the component
+ * is not present.
+ * @private
+ */
+goog.uri.utils.getComponentByIndex_ = function(componentIndex, uri) {
+ // Convert undefined, null, and empty string into null.
+ return goog.uri.utils.split(uri)[componentIndex] || null;
+};
+
+
+/**
+ * @param {string} uri The URI to examine.
+ * @return {?string} The protocol or scheme, or null if none. Does not
+ * include trailing colons or slashes.
+ */
+goog.uri.utils.getScheme = function(uri) {
+ return goog.uri.utils.getComponentByIndex_(
+ goog.uri.utils.ComponentIndex.SCHEME, uri);
+};
+
+
+/**
+ * Gets the effective scheme for the URL. If the URL is relative then the
+ * scheme is derived from the page's location.
+ * @param {string} uri The URI to examine.
+ * @return {string} The protocol or scheme, always lower case.
+ */
+goog.uri.utils.getEffectiveScheme = function(uri) {
+ var scheme = goog.uri.utils.getScheme(uri);
+ if (!scheme && self.location) {
+ var protocol = self.location.protocol;
+ scheme = protocol.substr(0, protocol.length - 1);
+ }
+ // NOTE: When called from a web worker in Firefox 3.5, location maybe null.
+ // All other browsers with web workers support self.location from the worker.
+ return scheme ? scheme.toLowerCase() : '';
+};
+
+
+/**
+ * @param {string} uri The URI to examine.
+ * @return {?string} The user name still encoded, or null if none.
+ */
+goog.uri.utils.getUserInfoEncoded = function(uri) {
+ return goog.uri.utils.getComponentByIndex_(
+ goog.uri.utils.ComponentIndex.USER_INFO, uri);
+};
+
+
+/**
+ * @param {string} uri The URI to examine.
+ * @return {?string} The decoded user info, or null if none.
+ */
+goog.uri.utils.getUserInfo = function(uri) {
+ return goog.uri.utils.decodeIfPossible_(
+ goog.uri.utils.getUserInfoEncoded(uri));
+};
+
+
+/**
+ * @param {string} uri The URI to examine.
+ * @return {?string} The domain name still encoded, or null if none.
+ */
+goog.uri.utils.getDomainEncoded = function(uri) {
+ return goog.uri.utils.getComponentByIndex_(
+ goog.uri.utils.ComponentIndex.DOMAIN, uri);
+};
+
+
+/**
+ * @param {string} uri The URI to examine.
+ * @return {?string} The decoded domain, or null if none.
+ */
+goog.uri.utils.getDomain = function(uri) {
+ return goog.uri.utils.decodeIfPossible_(goog.uri.utils.getDomainEncoded(uri));
+};
+
+
+/**
+ * @param {string} uri The URI to examine.
+ * @return {?number} The port number, or null if none.
+ */
+goog.uri.utils.getPort = function(uri) {
+ // Coerce to a number. If the result of getComponentByIndex_ is null or
+ // non-numeric, the number coersion yields NaN. This will then return
+ // null for all non-numeric cases (though also zero, which isn't a relevant
+ // port number).
+ return Number(goog.uri.utils.getComponentByIndex_(
+ goog.uri.utils.ComponentIndex.PORT, uri)) || null;
+};
+
+
+/**
+ * @param {string} uri The URI to examine.
+ * @return {?string} The path still encoded, or null if none. Includes the
+ * leading slash, if any.
+ */
+goog.uri.utils.getPathEncoded = function(uri) {
+ return goog.uri.utils.getComponentByIndex_(
+ goog.uri.utils.ComponentIndex.PATH, uri);
+};
+
+
+/**
+ * @param {string} uri The URI to examine.
+ * @return {?string} The decoded path, or null if none. Includes the leading
+ * slash, if any.
+ */
+goog.uri.utils.getPath = function(uri) {
+ return goog.uri.utils.decodeIfPossible_(goog.uri.utils.getPathEncoded(uri));
+};
+
+
+/**
+ * @param {string} uri The URI to examine.
+ * @return {?string} The query data still encoded, or null if none. Does not
+ * include the question mark itself.
+ */
+goog.uri.utils.getQueryData = function(uri) {
+ return goog.uri.utils.getComponentByIndex_(
+ goog.uri.utils.ComponentIndex.QUERY_DATA, uri);
+};
+
+
+/**
+ * @param {string} uri The URI to examine.
+ * @return {?string} The fragment identifier, or null if none. Does not
+ * include the hash mark itself.
+ */
+goog.uri.utils.getFragmentEncoded = function(uri) {
+ // The hash mark may not appear in any other part of the URL.
+ var hashIndex = uri.indexOf('#');
+ return hashIndex < 0 ? null : uri.substr(hashIndex + 1);
+};
+
+
+/**
+ * @param {string} uri The URI to examine.
+ * @param {?string} fragment The encoded fragment identifier, or null if none.
+ * Does not include the hash mark itself.
+ * @return {string} The URI with the fragment set.
+ */
+goog.uri.utils.setFragmentEncoded = function(uri, fragment) {
+ return goog.uri.utils.removeFragment(uri) + (fragment ? '#' + fragment : '');
+};
+
+
+/**
+ * @param {string} uri The URI to examine.
+ * @return {?string} The decoded fragment identifier, or null if none. Does
+ * not include the hash mark.
+ */
+goog.uri.utils.getFragment = function(uri) {
+ return goog.uri.utils.decodeIfPossible_(
+ goog.uri.utils.getFragmentEncoded(uri));
+};
+
+
+/**
+ * Extracts everything up to the port of the URI.
+ * @param {string} uri The URI string.
+ * @return {string} Everything up to and including the port.
+ */
+goog.uri.utils.getHost = function(uri) {
+ var pieces = goog.uri.utils.split(uri);
+ return goog.uri.utils.buildFromEncodedParts(
+ pieces[goog.uri.utils.ComponentIndex.SCHEME],
+ pieces[goog.uri.utils.ComponentIndex.USER_INFO],
+ pieces[goog.uri.utils.ComponentIndex.DOMAIN],
+ pieces[goog.uri.utils.ComponentIndex.PORT]);
+};
+
+
+/**
+ * Extracts the path of the URL and everything after.
+ * @param {string} uri The URI string.
+ * @return {string} The URI, starting at the path and including the query
+ * parameters and fragment identifier.
+ */
+goog.uri.utils.getPathAndAfter = function(uri) {
+ var pieces = goog.uri.utils.split(uri);
+ return goog.uri.utils.buildFromEncodedParts(null, null, null, null,
+ pieces[goog.uri.utils.ComponentIndex.PATH],
+ pieces[goog.uri.utils.ComponentIndex.QUERY_DATA],
+ pieces[goog.uri.utils.ComponentIndex.FRAGMENT]);
+};
+
+
+/**
+ * Gets the URI with the fragment identifier removed.
+ * @param {string} uri The URI to examine.
+ * @return {string} Everything preceding the hash mark.
+ */
+goog.uri.utils.removeFragment = function(uri) {
+ // The hash mark may not appear in any other part of the URL.
+ var hashIndex = uri.indexOf('#');
+ return hashIndex < 0 ? uri : uri.substr(0, hashIndex);
+};
+
+
+/**
+ * Ensures that two URI's have the exact same domain, scheme, and port.
+ *
+ * Unlike the version in goog.Uri, this checks protocol, and therefore is
+ * suitable for checking against the browser's same-origin policy.
+ *
+ * @param {string} uri1 The first URI.
+ * @param {string} uri2 The second URI.
+ * @return {boolean} Whether they have the same domain and port.
+ */
+goog.uri.utils.haveSameDomain = function(uri1, uri2) {
+ var pieces1 = goog.uri.utils.split(uri1);
+ var pieces2 = goog.uri.utils.split(uri2);
+ return pieces1[goog.uri.utils.ComponentIndex.DOMAIN] ==
+ pieces2[goog.uri.utils.ComponentIndex.DOMAIN] &&
+ pieces1[goog.uri.utils.ComponentIndex.SCHEME] ==
+ pieces2[goog.uri.utils.ComponentIndex.SCHEME] &&
+ pieces1[goog.uri.utils.ComponentIndex.PORT] ==
+ pieces2[goog.uri.utils.ComponentIndex.PORT];
+};
+
+
+/**
+ * Asserts that there are no fragment or query identifiers, only in uncompiled
+ * mode.
+ * @param {string} uri The URI to examine.
+ * @private
+ */
+goog.uri.utils.assertNoFragmentsOrQueries_ = function(uri) {
+ // NOTE: would use goog.asserts here, but jscompiler doesn't know that
+ // indexOf has no side effects.
+ if (goog.DEBUG && (uri.indexOf('#') >= 0 || uri.indexOf('?') >= 0)) {
+ throw Error('goog.uri.utils: Fragment or query identifiers are not ' +
+ 'supported: [' + uri + ']');
+ }
+};
+
+
+/**
+ * Supported query parameter values by the parameter serializing utilities.
+ *
+ * If a value is null or undefined, the key-value pair is skipped, as an easy
+ * way to omit parameters conditionally. Non-array parameters are converted
+ * to a string and URI encoded. Array values are expanded into multiple
+ * &key=value pairs, with each element stringized and URI-encoded.
+ *
+ * @typedef {*}
+ */
+goog.uri.utils.QueryValue;
+
+
+/**
+ * An array representing a set of query parameters with alternating keys
+ * and values.
+ *
+ * Keys are assumed to be URI encoded already and live at even indices. See
+ * goog.uri.utils.QueryValue for details on how parameter values are encoded.
+ *
+ * Example:
+ * <pre>
+ * var data = [
+ * // Simple param: ?name=BobBarker
+ * 'name', 'BobBarker',
+ * // Conditional param -- may be omitted entirely.
+ * 'specialDietaryNeeds', hasDietaryNeeds() ? getDietaryNeeds() : null,
+ * // Multi-valued param: &house=LosAngeles&house=NewYork&house=null
+ * 'house', ['LosAngeles', 'NewYork', null]
+ * ];
+ * </pre>
+ *
+ * @typedef {!Array.<string|goog.uri.utils.QueryValue>}
+ */
+goog.uri.utils.QueryArray;
+
+
+/**
+ * Appends a URI and query data in a string buffer with special preconditions.
+ *
+ * Internal implementation utility, performing very few object allocations.
+ *
+ * @param {!Array.<string|undefined>} buffer A string buffer. The first element
+ * must be the base URI, and may have a fragment identifier. If the array
+ * contains more than one element, the second element must be an ampersand,
+ * and may be overwritten, depending on the base URI. Undefined elements
+ * are treated as empty-string.
+ * @return {string} The concatenated URI and query data.
+ * @private
+ */
+goog.uri.utils.appendQueryData_ = function(buffer) {
+ if (buffer[1]) {
+ // At least one query parameter was added. We need to check the
+ // punctuation mark, which is currently an ampersand, and also make sure
+ // there aren't any interfering fragment identifiers.
+ var baseUri = /** @type {string} */ (buffer[0]);
+ var hashIndex = baseUri.indexOf('#');
+ if (hashIndex >= 0) {
+ // Move the fragment off the base part of the URI into the end.
+ buffer.push(baseUri.substr(hashIndex));
+ buffer[0] = baseUri = baseUri.substr(0, hashIndex);
+ }
+ var questionIndex = baseUri.indexOf('?');
+ if (questionIndex < 0) {
+ // No question mark, so we need a question mark instead of an ampersand.
+ buffer[1] = '?';
+ } else if (questionIndex == baseUri.length - 1) {
+ // Question mark is the very last character of the existing URI, so don't
+ // append an additional delimiter.
+ buffer[1] = undefined;
+ }
+ }
+
+ return buffer.join('');
+};
+
+
+/**
+ * Appends key=value pairs to an array, supporting multi-valued objects.
+ * @param {string} key The key prefix.
+ * @param {goog.uri.utils.QueryValue} value The value to serialize.
+ * @param {!Array.<string>} pairs The array to which the 'key=value' strings
+ * should be appended.
+ * @private
+ */
+goog.uri.utils.appendKeyValuePairs_ = function(key, value, pairs) {
+ if (goog.isArray(value)) {
+ // Convince the compiler it's an array.
+ goog.asserts.assertArray(value);
+ for (var j = 0; j < value.length; j++) {
+ // Convert to string explicitly, to short circuit the null and array
+ // logic in this function -- this ensures that null and undefined get
+ // written as literal 'null' and 'undefined', and arrays don't get
+ // expanded out but instead encoded in the default way.
+ goog.uri.utils.appendKeyValuePairs_(key, String(value[j]), pairs);
+ }
+ } else if (value != null) {
+ // Skip a top-level null or undefined entirely.
+ pairs.push('&', key,
+ // Check for empty string. Zero gets encoded into the url as literal
+ // strings. For empty string, skip the equal sign, to be consistent
+ // with UriBuilder.java.
+ value === '' ? '' : '=',
+ goog.string.urlEncode(value));
+ }
+};
+
+
+/**
+ * Builds a buffer of query data from a sequence of alternating keys and values.
+ *
+ * @param {!Array.<string|undefined>} buffer A string buffer to append to. The
+ * first element appended will be an '&', and may be replaced by the caller.
+ * @param {goog.uri.utils.QueryArray|Arguments} keysAndValues An array with
+ * alternating keys and values -- see the typedef.
+ * @param {number=} opt_startIndex A start offset into the arary, defaults to 0.
+ * @return {!Array.<string|undefined>} The buffer argument.
+ * @private
+ */
+goog.uri.utils.buildQueryDataBuffer_ = function(
+ buffer, keysAndValues, opt_startIndex) {
+ goog.asserts.assert(Math.max(keysAndValues.length - (opt_startIndex || 0),
+ 0) % 2 == 0, 'goog.uri.utils: Key/value lists must be even in length.');
+
+ for (var i = opt_startIndex || 0; i < keysAndValues.length; i += 2) {
+ goog.uri.utils.appendKeyValuePairs_(
+ keysAndValues[i], keysAndValues[i + 1], buffer);
+ }
+
+ return buffer;
+};
+
+
+/**
+ * Builds a query data string from a sequence of alternating keys and values.
+ * Currently generates "&key&" for empty args.
+ *
+ * @param {goog.uri.utils.QueryArray} keysAndValues Alternating keys and
+ * values. See the typedef.
+ * @param {number=} opt_startIndex A start offset into the arary, defaults to 0.
+ * @return {string} The encoded query string, in the form 'a=1&b=2'.
+ */
+goog.uri.utils.buildQueryData = function(keysAndValues, opt_startIndex) {
+ var buffer = goog.uri.utils.buildQueryDataBuffer_(
+ [], keysAndValues, opt_startIndex);
+ buffer[0] = ''; // Remove the leading ampersand.
+ return buffer.join('');
+};
+
+
+/**
+ * Builds a buffer of query data from a map.
+ *
+ * @param {!Array.<string|undefined>} buffer A string buffer to append to. The
+ * first element appended will be an '&', and may be replaced by the caller.
+ * @param {Object.<goog.uri.utils.QueryValue>} map An object where keys are
+ * URI-encoded parameter keys, and the values conform to the contract
+ * specified in the goog.uri.utils.QueryValue typedef.
+ * @return {!Array.<string|undefined>} The buffer argument.
+ * @private
+ */
+goog.uri.utils.buildQueryDataBufferFromMap_ = function(buffer, map) {
+ for (var key in map) {
+ goog.uri.utils.appendKeyValuePairs_(key, map[key], buffer);
+ }
+
+ return buffer;
+};
+
+
+/**
+ * Builds a query data string from a map.
+ * Currently generates "&key&" for empty args.
+ *
+ * @param {Object} map An object where keys are URI-encoded parameter keys,
+ * and the values are arbitrary types or arrays. Keys with a null value
+ * are dropped.
+ * @return {string} The encoded query string, in the form 'a=1&b=2'.
+ */
+goog.uri.utils.buildQueryDataFromMap = function(map) {
+ var buffer = goog.uri.utils.buildQueryDataBufferFromMap_([], map);
+ buffer[0] = '';
+ return buffer.join('');
+};
+
+
+/**
+ * Appends URI parameters to an existing URI.
+ *
+ * The variable arguments may contain alternating keys and values. Keys are
+ * assumed to be already URI encoded. The values should not be URI-encoded,
+ * and will instead be encoded by this function.
+ * <pre>
+ * appendParams('http://www.foo.com?existing=true',
+ * 'key1', 'value1',
+ * 'key2', 'value?willBeEncoded',
+ * 'key3', ['valueA', 'valueB', 'valueC'],
+ * 'key4', null);
+ * result: 'http://www.foo.com?existing=true&' +
+ * 'key1=value1&' +
+ * 'key2=value%3FwillBeEncoded&' +
+ * 'key3=valueA&key3=valueB&key3=valueC'
+ * </pre>
+ *
+ * A single call to this function will not exhibit quadratic behavior in IE,
+ * whereas multiple repeated calls may, although the effect is limited by
+ * fact that URL's generally can't exceed 2kb.
+ *
+ * @param {string} uri The original URI, which may already have query data.
+ * @param {...(goog.uri.utils.QueryArray|string|goog.uri.utils.QueryValue)} var_args
+ * An array or argument list conforming to goog.uri.utils.QueryArray.
+ * @return {string} The URI with all query parameters added.
+ */
+goog.uri.utils.appendParams = function(uri, var_args) {
+ return goog.uri.utils.appendQueryData_(
+ arguments.length == 2 ?
+ goog.uri.utils.buildQueryDataBuffer_([uri], arguments[1], 0) :
+ goog.uri.utils.buildQueryDataBuffer_([uri], arguments, 1));
+};
+
+
+/**
+ * Appends query parameters from a map.
+ *
+ * @param {string} uri The original URI, which may already have query data.
+ * @param {Object} map An object where keys are URI-encoded parameter keys,
+ * and the values are arbitrary types or arrays. Keys with a null value
+ * are dropped.
+ * @return {string} The new parameters.
+ */
+goog.uri.utils.appendParamsFromMap = function(uri, map) {
+ return goog.uri.utils.appendQueryData_(
+ goog.uri.utils.buildQueryDataBufferFromMap_([uri], map));
+};
+
+
+/**
+ * Appends a single URI parameter.
+ *
+ * Repeated calls to this can exhibit quadratic behavior in IE6 due to the
+ * way string append works, though it should be limited given the 2kb limit.
+ *
+ * @param {string} uri The original URI, which may already have query data.
+ * @param {string} key The key, which must already be URI encoded.
+ * @param {*=} opt_value The value, which will be stringized and encoded
+ * (assumed not already to be encoded). If omitted, undefined, or null, the
+ * key will be added as a valueless parameter.
+ * @return {string} The URI with the query parameter added.
+ */
+goog.uri.utils.appendParam = function(uri, key, opt_value) {
+ var paramArr = [uri, '&', key];
+ if (goog.isDefAndNotNull(opt_value)) {
+ paramArr.push('=', goog.string.urlEncode(opt_value));
+ }
+ return goog.uri.utils.appendQueryData_(paramArr);
+};
+
+
+/**
+ * Finds the next instance of a query parameter with the specified name.
+ *
+ * Does not instantiate any objects.
+ *
+ * @param {string} uri The URI to search. May contain a fragment identifier
+ * if opt_hashIndex is specified.
+ * @param {number} startIndex The index to begin searching for the key at. A
+ * match may be found even if this is one character after the ampersand.
+ * @param {string} keyEncoded The URI-encoded key.
+ * @param {number} hashOrEndIndex Index to stop looking at. If a hash
+ * mark is present, it should be its index, otherwise it should be the
+ * length of the string.
+ * @return {number} The position of the first character in the key's name,
+ * immediately after either a question mark or a dot.
+ * @private
+ */
+goog.uri.utils.findParam_ = function(
+ uri, startIndex, keyEncoded, hashOrEndIndex) {
+ var index = startIndex;
+ var keyLength = keyEncoded.length;
+
+ // Search for the key itself and post-filter for surronuding punctuation,
+ // rather than expensively building a regexp.
+ while ((index = uri.indexOf(keyEncoded, index)) >= 0 &&
+ index < hashOrEndIndex) {
+ var precedingChar = uri.charCodeAt(index - 1);
+ // Ensure that the preceding character is '&' or '?'.
+ if (precedingChar == goog.uri.utils.CharCode_.AMPERSAND ||
+ precedingChar == goog.uri.utils.CharCode_.QUESTION) {
+ // Ensure the following character is '&', '=', '#', or NaN
+ // (end of string).
+ var followingChar = uri.charCodeAt(index + keyLength);
+ if (!followingChar ||
+ followingChar == goog.uri.utils.CharCode_.EQUAL ||
+ followingChar == goog.uri.utils.CharCode_.AMPERSAND ||
+ followingChar == goog.uri.utils.CharCode_.HASH) {
+ return index;
+ }
+ }
+ index += keyLength + 1;
+ }
+
+ return -1;
+};
+
+
+/**
+ * Regular expression for finding a hash mark or end of string.
+ * @type {RegExp}
+ * @private
+ */
+goog.uri.utils.hashOrEndRe_ = /#|$/;
+
+
+/**
+ * Determines if the URI contains a specific key.
+ *
+ * Performs no object instantiations.
+ *
+ * @param {string} uri The URI to process. May contain a fragment
+ * identifier.
+ * @param {string} keyEncoded The URI-encoded key. Case-sensitive.
+ * @return {boolean} Whether the key is present.
+ */
+goog.uri.utils.hasParam = function(uri, keyEncoded) {
+ return goog.uri.utils.findParam_(uri, 0, keyEncoded,
+ uri.search(goog.uri.utils.hashOrEndRe_)) >= 0;
+};
+
+
+/**
+ * Gets the first value of a query parameter.
+ * @param {string} uri The URI to process. May contain a fragment.
+ * @param {string} keyEncoded The URI-encoded key. Case-sensitive.
+ * @return {?string} The first value of the parameter (URI-decoded), or null
+ * if the parameter is not found.
+ */
+goog.uri.utils.getParamValue = function(uri, keyEncoded) {
+ var hashOrEndIndex = uri.search(goog.uri.utils.hashOrEndRe_);
+ var foundIndex = goog.uri.utils.findParam_(
+ uri, 0, keyEncoded, hashOrEndIndex);
+
+ if (foundIndex < 0) {
+ return null;
+ } else {
+ var endPosition = uri.indexOf('&', foundIndex);
+ if (endPosition < 0 || endPosition > hashOrEndIndex) {
+ endPosition = hashOrEndIndex;
+ }
+ // Progress forth to the end of the "key=" or "key&" substring.
+ foundIndex += keyEncoded.length + 1;
+ // Use substr, because it (unlike substring) will return empty string
+ // if foundIndex > endPosition.
+ return goog.string.urlDecode(
+ uri.substr(foundIndex, endPosition - foundIndex));
+ }
+};
+
+
+/**
+ * Gets all values of a query parameter.
+ * @param {string} uri The URI to process. May contain a framgnet.
+ * @param {string} keyEncoded The URI-encoded key. Case-snsitive.
+ * @return {!Array.<string>} All URI-decoded values with the given key.
+ * If the key is not found, this will have length 0, but never be null.
+ */
+goog.uri.utils.getParamValues = function(uri, keyEncoded) {
+ var hashOrEndIndex = uri.search(goog.uri.utils.hashOrEndRe_);
+ var position = 0;
+ var foundIndex;
+ var result = [];
+
+ while ((foundIndex = goog.uri.utils.findParam_(
+ uri, position, keyEncoded, hashOrEndIndex)) >= 0) {
+ // Find where this parameter ends, either the '&' or the end of the
+ // query parameters.
+ position = uri.indexOf('&', foundIndex);
+ if (position < 0 || position > hashOrEndIndex) {
+ position = hashOrEndIndex;
+ }
+
+ // Progress forth to the end of the "key=" or "key&" substring.
+ foundIndex += keyEncoded.length + 1;
+ // Use substr, because it (unlike substring) will return empty string
+ // if foundIndex > position.
+ result.push(goog.string.urlDecode(uri.substr(
+ foundIndex, position - foundIndex)));
+ }
+
+ return result;
+};
+
+
+/**
+ * Regexp to find trailing question marks and ampersands.
+ * @type {RegExp}
+ * @private
+ */
+goog.uri.utils.trailingQueryPunctuationRe_ = /[?&]($|#)/;
+
+
+/**
+ * Removes all instances of a query parameter.
+ * @param {string} uri The URI to process. Must not contain a fragment.
+ * @param {string} keyEncoded The URI-encoded key.
+ * @return {string} The URI with all instances of the parameter removed.
+ */
+goog.uri.utils.removeParam = function(uri, keyEncoded) {
+ var hashOrEndIndex = uri.search(goog.uri.utils.hashOrEndRe_);
+ var position = 0;
+ var foundIndex;
+ var buffer = [];
+
+ // Look for a query parameter.
+ while ((foundIndex = goog.uri.utils.findParam_(
+ uri, position, keyEncoded, hashOrEndIndex)) >= 0) {
+ // Get the portion of the query string up to, but not including, the ?
+ // or & starting the parameter.
+ buffer.push(uri.substring(position, foundIndex));
+ // Progress to immediately after the '&'. If not found, go to the end.
+ // Avoid including the hash mark.
+ position = Math.min((uri.indexOf('&', foundIndex) + 1) || hashOrEndIndex,
+ hashOrEndIndex);
+ }
+
+ // Append everything that is remaining.
+ buffer.push(uri.substr(position));
+
+ // Join the buffer, and remove trailing punctuation that remains.
+ return buffer.join('').replace(
+ goog.uri.utils.trailingQueryPunctuationRe_, '$1');
+};
+
+
+/**
+ * Replaces all existing definitions of a parameter with a single definition.
+ *
+ * Repeated calls to this can exhibit quadratic behavior due to the need to
+ * find existing instances and reconstruct the string, though it should be
+ * limited given the 2kb limit. Consider using appendParams to append multiple
+ * parameters in bulk.
+ *
+ * @param {string} uri The original URI, which may already have query data.
+ * @param {string} keyEncoded The key, which must already be URI encoded.
+ * @param {*} value The value, which will be stringized and encoded (assumed
+ * not already to be encoded).
+ * @return {string} The URI with the query parameter added.
+ */
+goog.uri.utils.setParam = function(uri, keyEncoded, value) {
+ return goog.uri.utils.appendParam(
+ goog.uri.utils.removeParam(uri, keyEncoded), keyEncoded, value);
+};
+
+
+/**
+ * Generates a URI path using a given URI and a path with checks to
+ * prevent consecutive "//". The baseUri passed in must not contain
+ * query or fragment identifiers. The path to append may not contain query or
+ * fragment identifiers.
+ *
+ * @param {string} baseUri URI to use as the base.
+ * @param {string} path Path to append.
+ * @return {string} Updated URI.
+ */
+goog.uri.utils.appendPath = function(baseUri, path) {
+ goog.uri.utils.assertNoFragmentsOrQueries_(baseUri);
+
+ // Remove any trailing '/'
+ if (goog.string.endsWith(baseUri, '/')) {
+ baseUri = baseUri.substr(0, baseUri.length - 1);
+ }
+ // Remove any leading '/'
+ if (goog.string.startsWith(path, '/')) {
+ path = path.substr(1);
+ }
+ return goog.string.buildString(baseUri, '/', path);
+};
+
+
+/**
+ * Replaces the path.
+ * @param {string} uri URI to use as the base.
+ * @param {string} path New path.
+ * @return {string} Updated URI.
+ */
+goog.uri.utils.setPath = function(uri, path) {
+ // Add any missing '/'.
+ if (!goog.string.startsWith(path, '/')) {
+ path = '/' + path;
+ }
+ var parts = goog.uri.utils.split(uri);
+ return goog.uri.utils.buildFromEncodedParts(
+ parts[goog.uri.utils.ComponentIndex.SCHEME],
+ parts[goog.uri.utils.ComponentIndex.USER_INFO],
+ parts[goog.uri.utils.ComponentIndex.DOMAIN],
+ parts[goog.uri.utils.ComponentIndex.PORT],
+ path,
+ parts[goog.uri.utils.ComponentIndex.QUERY_DATA],
+ parts[goog.uri.utils.ComponentIndex.FRAGMENT]);
+};
+
+
+/**
+ * Standard supported query parameters.
+ * @enum {string}
+ */
+goog.uri.utils.StandardQueryParam = {
+
+ /** Unused parameter for unique-ifying. */
+ RANDOM: 'zx'
+};
+
+
+/**
+ * Sets the zx parameter of a URI to a random value.
+ * @param {string} uri Any URI.
+ * @return {string} That URI with the "zx" parameter added or replaced to
+ * contain a random string.
+ */
+goog.uri.utils.makeUnique = function(uri) {
+ return goog.uri.utils.setParam(uri,
+ goog.uri.utils.StandardQueryParam.RANDOM, goog.string.getRandomString());
+};
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/useragent/useragent.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/useragent/useragent.js
new file mode 100644
index 0000000..4220217
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/useragent/useragent.js
@@ -0,0 +1,553 @@
+// Copyright 2006 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Rendering engine detection.
+ * @see <a href="http://www.useragentstring.com/">User agent strings</a>
+ * For information on the browser brand (such as Safari versus Chrome), see
+ * goog.userAgent.product.
+ * @see ../demos/useragent.html
+ */
+
+goog.provide('goog.userAgent');
+
+goog.require('goog.labs.userAgent.browser');
+goog.require('goog.labs.userAgent.engine');
+goog.require('goog.labs.userAgent.util');
+goog.require('goog.string');
+
+
+/**
+ * @define {boolean} Whether we know at compile-time that the browser is IE.
+ */
+goog.define('goog.userAgent.ASSUME_IE', false);
+
+
+/**
+ * @define {boolean} Whether we know at compile-time that the browser is GECKO.
+ */
+goog.define('goog.userAgent.ASSUME_GECKO', false);
+
+
+/**
+ * @define {boolean} Whether we know at compile-time that the browser is WEBKIT.
+ */
+goog.define('goog.userAgent.ASSUME_WEBKIT', false);
+
+
+/**
+ * @define {boolean} Whether we know at compile-time that the browser is a
+ * mobile device running WebKit e.g. iPhone or Android.
+ */
+goog.define('goog.userAgent.ASSUME_MOBILE_WEBKIT', false);
+
+
+/**
+ * @define {boolean} Whether we know at compile-time that the browser is OPERA.
+ */
+goog.define('goog.userAgent.ASSUME_OPERA', false);
+
+
+/**
+ * @define {boolean} Whether the
+ * {@code goog.userAgent.isVersionOrHigher}
+ * function will return true for any version.
+ */
+goog.define('goog.userAgent.ASSUME_ANY_VERSION', false);
+
+
+/**
+ * Whether we know the browser engine at compile-time.
+ * @type {boolean}
+ * @private
+ */
+goog.userAgent.BROWSER_KNOWN_ =
+ goog.userAgent.ASSUME_IE ||
+ goog.userAgent.ASSUME_GECKO ||
+ goog.userAgent.ASSUME_MOBILE_WEBKIT ||
+ goog.userAgent.ASSUME_WEBKIT ||
+ goog.userAgent.ASSUME_OPERA;
+
+
+/**
+ * Returns the userAgent string for the current browser.
+ *
+ * @return {string} The userAgent string.
+ */
+goog.userAgent.getUserAgentString = function() {
+ return goog.labs.userAgent.util.getUserAgent();
+};
+
+
+/**
+ * TODO(nnaze): Change type to "Navigator" and update compilation targets.
+ * @return {Object} The native navigator object.
+ */
+goog.userAgent.getNavigator = function() {
+ // Need a local navigator reference instead of using the global one,
+ // to avoid the rare case where they reference different objects.
+ // (in a WorkerPool, for example).
+ return goog.global['navigator'] || null;
+};
+
+
+/**
+ * Whether the user agent is Opera.
+ * @type {boolean}
+ */
+goog.userAgent.OPERA = goog.userAgent.BROWSER_KNOWN_ ?
+ goog.userAgent.ASSUME_OPERA :
+ goog.labs.userAgent.browser.isOpera();
+
+
+/**
+ * Whether the user agent is Internet Explorer.
+ * @type {boolean}
+ */
+goog.userAgent.IE = goog.userAgent.BROWSER_KNOWN_ ?
+ goog.userAgent.ASSUME_IE :
+ goog.labs.userAgent.browser.isIE();
+
+
+/**
+ * Whether the user agent is Gecko. Gecko is the rendering engine used by
+ * Mozilla, Firefox, and others.
+ * @type {boolean}
+ */
+goog.userAgent.GECKO = goog.userAgent.BROWSER_KNOWN_ ?
+ goog.userAgent.ASSUME_GECKO :
+ goog.labs.userAgent.engine.isGecko();
+
+
+/**
+ * Whether the user agent is WebKit. WebKit is the rendering engine that
+ * Safari, Android and others use.
+ * @type {boolean}
+ */
+goog.userAgent.WEBKIT = goog.userAgent.BROWSER_KNOWN_ ?
+ goog.userAgent.ASSUME_WEBKIT || goog.userAgent.ASSUME_MOBILE_WEBKIT :
+ goog.labs.userAgent.engine.isWebKit();
+
+
+/**
+ * Whether the user agent is running on a mobile device.
+ *
+ * This is a separate function so that the logic can be tested.
+ *
+ * TODO(nnaze): Investigate swapping in goog.labs.userAgent.device.isMobile().
+ *
+ * @return {boolean} Whether the user agent is running on a mobile device.
+ * @private
+ */
+goog.userAgent.isMobile_ = function() {
+ return goog.userAgent.WEBKIT &&
+ goog.labs.userAgent.util.matchUserAgent('Mobile');
+};
+
+
+/**
+ * Whether the user agent is running on a mobile device.
+ *
+ * TODO(nnaze): Consider deprecating MOBILE when labs.userAgent
+ * is promoted as the gecko/webkit logic is likely inaccurate.
+ *
+ * @type {boolean}
+ */
+goog.userAgent.MOBILE = goog.userAgent.ASSUME_MOBILE_WEBKIT ||
+ goog.userAgent.isMobile_();
+
+
+/**
+ * Used while transitioning code to use WEBKIT instead.
+ * @type {boolean}
+ * @deprecated Use {@link goog.userAgent.product.SAFARI} instead.
+ * TODO(nicksantos): Delete this from goog.userAgent.
+ */
+goog.userAgent.SAFARI = goog.userAgent.WEBKIT;
+
+
+/**
+ * @return {string} the platform (operating system) the user agent is running
+ * on. Default to empty string because navigator.platform may not be defined
+ * (on Rhino, for example).
+ * @private
+ */
+goog.userAgent.determinePlatform_ = function() {
+ var navigator = goog.userAgent.getNavigator();
+ return navigator && navigator.platform || '';
+};
+
+
+/**
+ * The platform (operating system) the user agent is running on. Default to
+ * empty string because navigator.platform may not be defined (on Rhino, for
+ * example).
+ * @type {string}
+ */
+goog.userAgent.PLATFORM = goog.userAgent.determinePlatform_();
+
+
+/**
+ * @define {boolean} Whether the user agent is running on a Macintosh operating
+ * system.
+ */
+goog.define('goog.userAgent.ASSUME_MAC', false);
+
+
+/**
+ * @define {boolean} Whether the user agent is running on a Windows operating
+ * system.
+ */
+goog.define('goog.userAgent.ASSUME_WINDOWS', false);
+
+
+/**
+ * @define {boolean} Whether the user agent is running on a Linux operating
+ * system.
+ */
+goog.define('goog.userAgent.ASSUME_LINUX', false);
+
+
+/**
+ * @define {boolean} Whether the user agent is running on a X11 windowing
+ * system.
+ */
+goog.define('goog.userAgent.ASSUME_X11', false);
+
+
+/**
+ * @define {boolean} Whether the user agent is running on Android.
+ */
+goog.define('goog.userAgent.ASSUME_ANDROID', false);
+
+
+/**
+ * @define {boolean} Whether the user agent is running on an iPhone.
+ */
+goog.define('goog.userAgent.ASSUME_IPHONE', false);
+
+
+/**
+ * @define {boolean} Whether the user agent is running on an iPad.
+ */
+goog.define('goog.userAgent.ASSUME_IPAD', false);
+
+
+/**
+ * @type {boolean}
+ * @private
+ */
+goog.userAgent.PLATFORM_KNOWN_ =
+ goog.userAgent.ASSUME_MAC ||
+ goog.userAgent.ASSUME_WINDOWS ||
+ goog.userAgent.ASSUME_LINUX ||
+ goog.userAgent.ASSUME_X11 ||
+ goog.userAgent.ASSUME_ANDROID ||
+ goog.userAgent.ASSUME_IPHONE ||
+ goog.userAgent.ASSUME_IPAD;
+
+
+/**
+ * Initialize the goog.userAgent constants that define which platform the user
+ * agent is running on.
+ * @private
+ */
+goog.userAgent.initPlatform_ = function() {
+ /**
+ * Whether the user agent is running on a Macintosh operating system.
+ * @type {boolean}
+ * @private
+ */
+ goog.userAgent.detectedMac_ = goog.string.contains(goog.userAgent.PLATFORM,
+ 'Mac');
+
+ /**
+ * Whether the user agent is running on a Windows operating system.
+ * @type {boolean}
+ * @private
+ */
+ goog.userAgent.detectedWindows_ = goog.string.contains(
+ goog.userAgent.PLATFORM, 'Win');
+
+ /**
+ * Whether the user agent is running on a Linux operating system.
+ * @type {boolean}
+ * @private
+ */
+ goog.userAgent.detectedLinux_ = goog.string.contains(goog.userAgent.PLATFORM,
+ 'Linux');
+
+ /**
+ * Whether the user agent is running on a X11 windowing system.
+ * @type {boolean}
+ * @private
+ */
+ goog.userAgent.detectedX11_ = !!goog.userAgent.getNavigator() &&
+ goog.string.contains(goog.userAgent.getNavigator()['appVersion'] || '',
+ 'X11');
+
+ // Need user agent string for Android/IOS detection
+ var ua = goog.userAgent.getUserAgentString();
+
+ /**
+ * Whether the user agent is running on Android.
+ * @type {boolean}
+ * @private
+ */
+ goog.userAgent.detectedAndroid_ = !!ua &&
+ goog.string.contains(ua, 'Android');
+
+ /**
+ * Whether the user agent is running on an iPhone.
+ * @type {boolean}
+ * @private
+ */
+ goog.userAgent.detectedIPhone_ = !!ua && goog.string.contains(ua, 'iPhone');
+
+ /**
+ * Whether the user agent is running on an iPad.
+ * @type {boolean}
+ * @private
+ */
+ goog.userAgent.detectedIPad_ = !!ua && goog.string.contains(ua, 'iPad');
+};
+
+
+if (!goog.userAgent.PLATFORM_KNOWN_) {
+ goog.userAgent.initPlatform_();
+}
+
+
+/**
+ * Whether the user agent is running on a Macintosh operating system.
+ * @type {boolean}
+ */
+goog.userAgent.MAC = goog.userAgent.PLATFORM_KNOWN_ ?
+ goog.userAgent.ASSUME_MAC : goog.userAgent.detectedMac_;
+
+
+/**
+ * Whether the user agent is running on a Windows operating system.
+ * @type {boolean}
+ */
+goog.userAgent.WINDOWS = goog.userAgent.PLATFORM_KNOWN_ ?
+ goog.userAgent.ASSUME_WINDOWS : goog.userAgent.detectedWindows_;
+
+
+/**
+ * Whether the user agent is running on a Linux operating system.
+ * @type {boolean}
+ */
+goog.userAgent.LINUX = goog.userAgent.PLATFORM_KNOWN_ ?
+ goog.userAgent.ASSUME_LINUX : goog.userAgent.detectedLinux_;
+
+
+/**
+ * Whether the user agent is running on a X11 windowing system.
+ * @type {boolean}
+ */
+goog.userAgent.X11 = goog.userAgent.PLATFORM_KNOWN_ ?
+ goog.userAgent.ASSUME_X11 : goog.userAgent.detectedX11_;
+
+
+/**
+ * Whether the user agent is running on Android.
+ * @type {boolean}
+ */
+goog.userAgent.ANDROID = goog.userAgent.PLATFORM_KNOWN_ ?
+ goog.userAgent.ASSUME_ANDROID : goog.userAgent.detectedAndroid_;
+
+
+/**
+ * Whether the user agent is running on an iPhone.
+ * @type {boolean}
+ */
+goog.userAgent.IPHONE = goog.userAgent.PLATFORM_KNOWN_ ?
+ goog.userAgent.ASSUME_IPHONE : goog.userAgent.detectedIPhone_;
+
+
+/**
+ * Whether the user agent is running on an iPad.
+ * @type {boolean}
+ */
+goog.userAgent.IPAD = goog.userAgent.PLATFORM_KNOWN_ ?
+ goog.userAgent.ASSUME_IPAD : goog.userAgent.detectedIPad_;
+
+
+/**
+ * @return {string} The string that describes the version number of the user
+ * agent.
+ * @private
+ */
+goog.userAgent.determineVersion_ = function() {
+ // All browsers have different ways to detect the version and they all have
+ // different naming schemes.
+
+ // version is a string rather than a number because it may contain 'b', 'a',
+ // and so on.
+ var version = '', re;
+
+ if (goog.userAgent.OPERA && goog.global['opera']) {
+ var operaVersion = goog.global['opera'].version;
+ return goog.isFunction(operaVersion) ? operaVersion() : operaVersion;
+ }
+
+ if (goog.userAgent.GECKO) {
+ re = /rv\:([^\);]+)(\)|;)/;
+ } else if (goog.userAgent.IE) {
+ re = /\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/;
+ } else if (goog.userAgent.WEBKIT) {
+ // WebKit/125.4
+ re = /WebKit\/(\S+)/;
+ }
+
+ if (re) {
+ var arr = re.exec(goog.userAgent.getUserAgentString());
+ version = arr ? arr[1] : '';
+ }
+
+ if (goog.userAgent.IE) {
+ // IE9 can be in document mode 9 but be reporting an inconsistent user agent
+ // version. If it is identifying as a version lower than 9 we take the
+ // documentMode as the version instead. IE8 has similar behavior.
+ // It is recommended to set the X-UA-Compatible header to ensure that IE9
+ // uses documentMode 9.
+ var docMode = goog.userAgent.getDocumentMode_();
+ if (docMode > parseFloat(version)) {
+ return String(docMode);
+ }
+ }
+
+ return version;
+};
+
+
+/**
+ * @return {number|undefined} Returns the document mode (for testing).
+ * @private
+ */
+goog.userAgent.getDocumentMode_ = function() {
+ // NOTE(user): goog.userAgent may be used in context where there is no DOM.
+ var doc = goog.global['document'];
+ return doc ? doc['documentMode'] : undefined;
+};
+
+
+/**
+ * The version of the user agent. This is a string because it might contain
+ * 'b' (as in beta) as well as multiple dots.
+ * @type {string}
+ */
+goog.userAgent.VERSION = goog.userAgent.determineVersion_();
+
+
+/**
+ * Compares two version numbers.
+ *
+ * @param {string} v1 Version of first item.
+ * @param {string} v2 Version of second item.
+ *
+ * @return {number} 1 if first argument is higher
+ * 0 if arguments are equal
+ * -1 if second argument is higher.
+ * @deprecated Use goog.string.compareVersions.
+ */
+goog.userAgent.compare = function(v1, v2) {
+ return goog.string.compareVersions(v1, v2);
+};
+
+
+/**
+ * Cache for {@link goog.userAgent.isVersionOrHigher}.
+ * Calls to compareVersions are surprisingly expensive and, as a browser's
+ * version number is unlikely to change during a session, we cache the results.
+ * @const
+ * @private
+ */
+goog.userAgent.isVersionOrHigherCache_ = {};
+
+
+/**
+ * Whether the user agent version is higher or the same as the given version.
+ * NOTE: When checking the version numbers for Firefox and Safari, be sure to
+ * use the engine's version, not the browser's version number. For example,
+ * Firefox 3.0 corresponds to Gecko 1.9 and Safari 3.0 to Webkit 522.11.
+ * Opera and Internet Explorer versions match the product release number.<br>
+ * @see <a href="http://en.wikipedia.org/wiki/Safari_version_history">
+ * Webkit</a>
+ * @see <a href="http://en.wikipedia.org/wiki/Gecko_engine">Gecko</a>
+ *
+ * @param {string|number} version The version to check.
+ * @return {boolean} Whether the user agent version is higher or the same as
+ * the given version.
+ */
+goog.userAgent.isVersionOrHigher = function(version) {
+ return goog.userAgent.ASSUME_ANY_VERSION ||
+ goog.userAgent.isVersionOrHigherCache_[version] ||
+ (goog.userAgent.isVersionOrHigherCache_[version] =
+ goog.string.compareVersions(goog.userAgent.VERSION, version) >= 0);
+};
+
+
+/**
+ * Deprecated alias to {@code goog.userAgent.isVersionOrHigher}.
+ * @param {string|number} version The version to check.
+ * @return {boolean} Whether the user agent version is higher or the same as
+ * the given version.
+ * @deprecated Use goog.userAgent.isVersionOrHigher().
+ */
+goog.userAgent.isVersion = goog.userAgent.isVersionOrHigher;
+
+
+/**
+ * Whether the IE effective document mode is higher or the same as the given
+ * document mode version.
+ * NOTE: Only for IE, return false for another browser.
+ *
+ * @param {number} documentMode The document mode version to check.
+ * @return {boolean} Whether the IE effective document mode is higher or the
+ * same as the given version.
+ */
+goog.userAgent.isDocumentModeOrHigher = function(documentMode) {
+ return goog.userAgent.IE && goog.userAgent.DOCUMENT_MODE >= documentMode;
+};
+
+
+/**
+ * Deprecated alias to {@code goog.userAgent.isDocumentModeOrHigher}.
+ * @param {number} version The version to check.
+ * @return {boolean} Whether the IE effective document mode is higher or the
+ * same as the given version.
+ * @deprecated Use goog.userAgent.isDocumentModeOrHigher().
+ */
+goog.userAgent.isDocumentMode = goog.userAgent.isDocumentModeOrHigher;
+
+
+/**
+ * For IE version < 7, documentMode is undefined, so attempt to use the
+ * CSS1Compat property to see if we are in standards mode. If we are in
+ * standards mode, treat the browser version as the document mode. Otherwise,
+ * IE is emulating version 5.
+ * @type {number|undefined}
+ * @const
+ */
+goog.userAgent.DOCUMENT_MODE = (function() {
+ var doc = goog.global['document'];
+ if (!doc || !goog.userAgent.IE) {
+ return undefined;
+ }
+ var mode = goog.userAgent.getDocumentMode_();
+ return mode || (doc['compatMode'] == 'CSS1Compat' ?
+ parseInt(goog.userAgent.VERSION, 10) : 5);
+})();
diff --git a/third_party/google_input_tools/third_party/closure_library/third_party/closure/goog/mochikit/async/deferred.js b/third_party/google_input_tools/third_party/closure_library/third_party/closure/goog/mochikit/async/deferred.js
new file mode 100644
index 0000000..de32629
--- /dev/null
+++ b/third_party/google_input_tools/third_party/closure_library/third_party/closure/goog/mochikit/async/deferred.js
@@ -0,0 +1,922 @@
+// Copyright 2007 Bob Ippolito. All Rights Reserved.
+// Modifications Copyright 2009 The Closure Library Authors. All Rights
+// Reserved.
+
+/**
+ * @license Portions of this code are from MochiKit, received by
+ * The Closure Authors under the MIT license. All other code is Copyright
+ * 2005-2009 The Closure Authors. All Rights Reserved.
+ */
+
+/**
+ * @fileoverview Classes for tracking asynchronous operations and handling the
+ * results. The Deferred object here is patterned after the Deferred object in
+ * the Twisted python networking framework.
+ *
+ * See: http://twistedmatrix.com/projects/core/documentation/howto/defer.html
+ *
+ * Based on the Dojo code which in turn is based on the MochiKit code.
+ *
+ */
+
+goog.provide('goog.async.Deferred');
+goog.provide('goog.async.Deferred.AlreadyCalledError');
+goog.provide('goog.async.Deferred.CanceledError');
+
+goog.require('goog.Promise');
+goog.require('goog.Thenable');
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.debug.Error');
+
+
+
+/**
+ * A Deferred represents the result of an asynchronous operation. A Deferred
+ * instance has no result when it is created, and is "fired" (given an initial
+ * result) by calling {@code callback} or {@code errback}.
+ *
+ * Once fired, the result is passed through a sequence of callback functions
+ * registered with {@code addCallback} or {@code addErrback}. The functions may
+ * mutate the result before it is passed to the next function in the sequence.
+ *
+ * Callbacks and errbacks may be added at any time, including after the Deferred
+ * has been "fired". If there are no pending actions in the execution sequence
+ * of a fired Deferred, any new callback functions will be called with the last
+ * computed result. Adding a callback function is the only way to access the
+ * result of the Deferred.
+ *
+ * If a Deferred operation is canceled, an optional user-provided cancellation
+ * function is invoked which may perform any special cleanup, followed by firing
+ * the Deferred's errback sequence with a {@code CanceledError}. If the
+ * Deferred has already fired, cancellation is ignored.
+ *
+ * Deferreds may be templated to a specific type they produce using generics
+ * with syntax such as:
+ * <code>
+ * /** @type {goog.async.Deferred.<string>} *&#47;
+ * var d = new goog.async.Deferred();
+ * // Compiler can infer that foo is a string.
+ * d.addCallback(function(foo) {...});
+ * d.callback('string'); // Checked to be passed a string
+ * </code>
+ * Since deferreds are often used to produce different values across a chain,
+ * the type information is not propagated across chains, but rather only
+ * associated with specifically cast objects.
+ *
+ * @param {Function=} opt_onCancelFunction A function that will be called if the
+ * Deferred is canceled. If provided, this function runs before the
+ * Deferred is fired with a {@code CanceledError}.
+ * @param {Object=} opt_defaultScope The default object context to call
+ * callbacks and errbacks in.
+ * @constructor
+ * @implements {goog.Thenable.<VALUE>}
+ * @template VALUE
+ */
+goog.async.Deferred = function(opt_onCancelFunction, opt_defaultScope) {
+ /**
+ * Entries in the sequence are arrays containing a callback, an errback, and
+ * an optional scope. The callback or errback in an entry may be null.
+ * @type {!Array.<!Array>}
+ * @private
+ */
+ this.sequence_ = [];
+
+ /**
+ * Optional function that will be called if the Deferred is canceled.
+ * @type {Function|undefined}
+ * @private
+ */
+ this.onCancelFunction_ = opt_onCancelFunction;
+
+ /**
+ * The default scope to execute callbacks and errbacks in.
+ * @type {Object}
+ * @private
+ */
+ this.defaultScope_ = opt_defaultScope || null;
+
+ /**
+ * Whether the Deferred has been fired.
+ * @type {boolean}
+ * @private
+ */
+ this.fired_ = false;
+
+ /**
+ * Whether the last result in the execution sequence was an error.
+ * @type {boolean}
+ * @private
+ */
+ this.hadError_ = false;
+
+ /**
+ * The current Deferred result, updated as callbacks and errbacks are
+ * executed.
+ * @type {*}
+ * @private
+ */
+ this.result_ = undefined;
+
+ /**
+ * Whether the Deferred is blocked waiting on another Deferred to fire. If a
+ * callback or errback returns a Deferred as a result, the execution sequence
+ * is blocked until that Deferred result becomes available.
+ * @type {boolean}
+ * @private
+ */
+ this.blocked_ = false;
+
+ /**
+ * Whether this Deferred is blocking execution of another Deferred. If this
+ * instance was returned as a result in another Deferred's execution
+ * sequence,that other Deferred becomes blocked until this instance's
+ * execution sequence completes. No additional callbacks may be added to a
+ * Deferred once it is blocking another instance.
+ * @type {boolean}
+ * @private
+ */
+ this.blocking_ = false;
+
+ /**
+ * Whether the Deferred has been canceled without having a custom cancel
+ * function.
+ * @type {boolean}
+ * @private
+ */
+ this.silentlyCanceled_ = false;
+
+ /**
+ * If an error is thrown during Deferred execution with no errback to catch
+ * it, the error is rethrown after a timeout. Reporting the error after a
+ * timeout allows execution to continue in the calling context (empty when
+ * no error is scheduled).
+ * @type {number}
+ * @private
+ */
+ this.unhandledErrorId_ = 0;
+
+ /**
+ * If this Deferred was created by branch(), this will be the "parent"
+ * Deferred.
+ * @type {goog.async.Deferred}
+ * @private
+ */
+ this.parent_ = null;
+
+ /**
+ * The number of Deferred objects that have been branched off this one. This
+ * will be decremented whenever a branch is fired or canceled.
+ * @type {number}
+ * @private
+ */
+ this.branches_ = 0;
+
+ if (goog.async.Deferred.LONG_STACK_TRACES) {
+ /**
+ * Holds the stack trace at time of deferred creation if the JS engine
+ * provides the Error.captureStackTrace API.
+ * @private {?string}
+ */
+ this.constructorStack_ = null;
+ if (Error.captureStackTrace) {
+ var target = { stack: '' };
+ Error.captureStackTrace(target, goog.async.Deferred);
+ // Check if Error.captureStackTrace worked. It fails in gjstest.
+ if (typeof target.stack == 'string') {
+ // Remove first line and force stringify to prevent memory leak due to
+ // holding on to actual stack frames.
+ this.constructorStack_ = target.stack.replace(/^[^\n]*\n/, '');
+ }
+ }
+ }
+};
+
+
+/**
+ * @define {boolean} Whether unhandled errors should always get rethrown to the
+ * global scope. Defaults to the value of goog.DEBUG.
+ */
+goog.define('goog.async.Deferred.STRICT_ERRORS', false);
+
+
+/**
+ * @define {boolean} Whether to attempt to make stack traces long. Defaults to
+ * the value of goog.DEBUG.
+ */
+goog.define('goog.async.Deferred.LONG_STACK_TRACES', false);
+
+
+/**
+ * Cancels a Deferred that has not yet been fired, or is blocked on another
+ * deferred operation. If this Deferred is waiting for a blocking Deferred to
+ * fire, the blocking Deferred will also be canceled.
+ *
+ * If this Deferred was created by calling branch() on a parent Deferred with
+ * opt_propagateCancel set to true, the parent may also be canceled. If
+ * opt_deepCancel is set, cancel() will be called on the parent (as well as any
+ * other ancestors if the parent is also a branch). If one or more branches were
+ * created with opt_propagateCancel set to true, the parent will be canceled if
+ * cancel() is called on all of those branches.
+ *
+ * @param {boolean=} opt_deepCancel If true, cancels this Deferred's parent even
+ * if cancel() hasn't been called on some of the parent's branches. Has no
+ * effect on a branch without opt_propagateCancel set to true.
+ */
+goog.async.Deferred.prototype.cancel = function(opt_deepCancel) {
+ if (!this.hasFired()) {
+ if (this.parent_) {
+ // Get rid of the parent reference before potentially running the parent's
+ // canceler function to ensure that this cancellation isn't
+ // double-counted.
+ var parent = this.parent_;
+ delete this.parent_;
+ if (opt_deepCancel) {
+ parent.cancel(opt_deepCancel);
+ } else {
+ parent.branchCancel_();
+ }
+ }
+
+ if (this.onCancelFunction_) {
+ // Call in user-specified scope.
+ this.onCancelFunction_.call(this.defaultScope_, this);
+ } else {
+ this.silentlyCanceled_ = true;
+ }
+ if (!this.hasFired()) {
+ this.errback(new goog.async.Deferred.CanceledError(this));
+ }
+ } else if (this.result_ instanceof goog.async.Deferred) {
+ this.result_.cancel();
+ }
+};
+
+
+/**
+ * Handle a single branch being canceled. Once all branches are canceled, this
+ * Deferred will be canceled as well.
+ *
+ * @private
+ */
+goog.async.Deferred.prototype.branchCancel_ = function() {
+ this.branches_--;
+ if (this.branches_ <= 0) {
+ this.cancel();
+ }
+};
+
+
+/**
+ * Called after a blocking Deferred fires. Unblocks this Deferred and resumes
+ * its execution sequence.
+ *
+ * @param {boolean} isSuccess Whether the result is a success or an error.
+ * @param {*} res The result of the blocking Deferred.
+ * @private
+ */
+goog.async.Deferred.prototype.continue_ = function(isSuccess, res) {
+ this.blocked_ = false;
+ this.updateResult_(isSuccess, res);
+};
+
+
+/**
+ * Updates the current result based on the success or failure of the last action
+ * in the execution sequence.
+ *
+ * @param {boolean} isSuccess Whether the new result is a success or an error.
+ * @param {*} res The result.
+ * @private
+ */
+goog.async.Deferred.prototype.updateResult_ = function(isSuccess, res) {
+ this.fired_ = true;
+ this.result_ = res;
+ this.hadError_ = !isSuccess;
+ this.fire_();
+};
+
+
+/**
+ * Verifies that the Deferred has not yet been fired.
+ *
+ * @private
+ * @throws {Error} If this has already been fired.
+ */
+goog.async.Deferred.prototype.check_ = function() {
+ if (this.hasFired()) {
+ if (!this.silentlyCanceled_) {
+ throw new goog.async.Deferred.AlreadyCalledError(this);
+ }
+ this.silentlyCanceled_ = false;
+ }
+};
+
+
+/**
+ * Fire the execution sequence for this Deferred by passing the starting result
+ * to the first registered callback.
+ * @param {VALUE=} opt_result The starting result.
+ */
+goog.async.Deferred.prototype.callback = function(opt_result) {
+ this.check_();
+ this.assertNotDeferred_(opt_result);
+ this.updateResult_(true /* isSuccess */, opt_result);
+};
+
+
+/**
+ * Fire the execution sequence for this Deferred by passing the starting error
+ * result to the first registered errback.
+ * @param {*=} opt_result The starting error.
+ */
+goog.async.Deferred.prototype.errback = function(opt_result) {
+ this.check_();
+ this.assertNotDeferred_(opt_result);
+ this.makeStackTraceLong_(opt_result);
+ this.updateResult_(false /* isSuccess */, opt_result);
+};
+
+
+/**
+ * Attempt to make the error's stack trace be long in that it contains the
+ * stack trace from the point where the deferred was created on top of the
+ * current stack trace to give additional context.
+ * @param {*} error
+ * @private
+ */
+goog.async.Deferred.prototype.makeStackTraceLong_ = function(error) {
+ if (!goog.async.Deferred.LONG_STACK_TRACES) {
+ return;
+ }
+ if (this.constructorStack_ && goog.isObject(error) && error.stack &&
+ // Stack looks like it was system generated. See
+ // https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
+ (/^[^\n]+(\n [^\n]+)+/).test(error.stack)) {
+ error.stack = error.stack + '\nDEFERRED OPERATION:\n' +
+ this.constructorStack_;
+ }
+};
+
+
+/**
+ * Asserts that an object is not a Deferred.
+ * @param {*} obj The object to test.
+ * @throws {Error} Throws an exception if the object is a Deferred.
+ * @private
+ */
+goog.async.Deferred.prototype.assertNotDeferred_ = function(obj) {
+ goog.asserts.assert(
+ !(obj instanceof goog.async.Deferred),
+ 'An execution sequence may not be initiated with a blocking Deferred.');
+};
+
+
+/**
+ * Register a callback function to be called with a successful result. If no
+ * value is returned by the callback function, the result value is unchanged. If
+ * a new value is returned, it becomes the Deferred result and will be passed to
+ * the next callback in the execution sequence.
+ *
+ * If the function throws an error, the error becomes the new result and will be
+ * passed to the next errback in the execution chain.
+ *
+ * If the function returns a Deferred, the execution sequence will be blocked
+ * until that Deferred fires. Its result will be passed to the next callback (or
+ * errback if it is an error result) in this Deferred's execution sequence.
+ *
+ * @param {!function(this:T,VALUE):?} cb The function to be called with a
+ * successful result.
+ * @param {T=} opt_scope An optional scope to call the callback in.
+ * @return {!goog.async.Deferred} This Deferred.
+ * @template T
+ */
+goog.async.Deferred.prototype.addCallback = function(cb, opt_scope) {
+ return this.addCallbacks(cb, null, opt_scope);
+};
+
+
+/**
+ * Register a callback function to be called with an error result. If no value
+ * is returned by the function, the error result is unchanged. If a new error
+ * value is returned or thrown, that error becomes the Deferred result and will
+ * be passed to the next errback in the execution sequence.
+ *
+ * If the errback function handles the error by returning a non-error value,
+ * that result will be passed to the next normal callback in the sequence.
+ *
+ * If the function returns a Deferred, the execution sequence will be blocked
+ * until that Deferred fires. Its result will be passed to the next callback (or
+ * errback if it is an error result) in this Deferred's execution sequence.
+ *
+ * @param {!function(this:T,?):?} eb The function to be called on an
+ * unsuccessful result.
+ * @param {T=} opt_scope An optional scope to call the errback in.
+ * @return {!goog.async.Deferred.<VALUE>} This Deferred.
+ * @template T
+ */
+goog.async.Deferred.prototype.addErrback = function(eb, opt_scope) {
+ return this.addCallbacks(null, eb, opt_scope);
+};
+
+
+/**
+ * Registers one function as both a callback and errback.
+ *
+ * @param {!function(this:T,?):?} f The function to be called on any result.
+ * @param {T=} opt_scope An optional scope to call the function in.
+ * @return {!goog.async.Deferred} This Deferred.
+ * @template T
+ */
+goog.async.Deferred.prototype.addBoth = function(f, opt_scope) {
+ return this.addCallbacks(f, f, opt_scope);
+};
+
+
+/**
+ * Registers a callback function and an errback function at the same position
+ * in the execution sequence. Only one of these functions will execute,
+ * depending on the error state during the execution sequence.
+ *
+ * NOTE: This is not equivalent to {@code def.addCallback().addErrback()}! If
+ * the callback is invoked, the errback will be skipped, and vice versa.
+ *
+ * @param {(function(this:T,VALUE):?)|null} cb The function to be called on a
+ * successful result.
+ * @param {(function(this:T,?):?)|null} eb The function to be called on an
+ * unsuccessful result.
+ * @param {T=} opt_scope An optional scope to call the functions in.
+ * @return {!goog.async.Deferred} This Deferred.
+ * @template T
+ */
+goog.async.Deferred.prototype.addCallbacks = function(cb, eb, opt_scope) {
+ goog.asserts.assert(!this.blocking_, 'Blocking Deferreds can not be re-used');
+ this.sequence_.push([cb, eb, opt_scope]);
+ if (this.hasFired()) {
+ this.fire_();
+ }
+ return this;
+};
+
+
+/**
+ * Implements {@see goog.Thenable} for seamless integration with
+ * {@see goog.Promise}.
+ * Deferred results are mutable and may represent multiple values over
+ * their lifetime. Calling {@code then} on a Deferred returns a Promise
+ * with the result of the Deferred at that point in its callback chain.
+ * Note that if the Deferred result is never mutated, and only
+ * {@code then} calls are made, the Deferred will behave like a Promise.
+ *
+ * @override
+ */
+goog.async.Deferred.prototype.then = function(opt_onFulfilled, opt_onRejected,
+ opt_context) {
+ var resolve, reject;
+ var promise = new goog.Promise(function(res, rej) {
+ // Copying resolvers to outer scope, so that they are available when the
+ // deferred callback fires (which may be synchronous).
+ resolve = res;
+ reject = rej;
+ });
+ this.addCallbacks(resolve, function(reason) {
+ if (reason instanceof goog.async.Deferred.CanceledError) {
+ promise.cancel();
+ } else {
+ reject(reason);
+ }
+ });
+ return promise.then(opt_onFulfilled, opt_onRejected, opt_context);
+};
+goog.Thenable.addImplementation(goog.async.Deferred);
+
+
+/**
+ * Links another Deferred to the end of this Deferred's execution sequence. The
+ * result of this execution sequence will be passed as the starting result for
+ * the chained Deferred, invoking either its first callback or errback.
+ *
+ * @param {!goog.async.Deferred} otherDeferred The Deferred to chain.
+ * @return {!goog.async.Deferred} This Deferred.
+ */
+goog.async.Deferred.prototype.chainDeferred = function(otherDeferred) {
+ this.addCallbacks(
+ otherDeferred.callback, otherDeferred.errback, otherDeferred);
+ return this;
+};
+
+
+/**
+ * Makes this Deferred wait for another Deferred's execution sequence to
+ * complete before continuing.
+ *
+ * This is equivalent to adding a callback that returns {@code otherDeferred},
+ * but doesn't prevent additional callbacks from being added to
+ * {@code otherDeferred}.
+ *
+ * @param {!goog.async.Deferred|!goog.Thenable} otherDeferred The Deferred
+ * to wait for.
+ * @return {!goog.async.Deferred} This Deferred.
+ */
+goog.async.Deferred.prototype.awaitDeferred = function(otherDeferred) {
+ if (!(otherDeferred instanceof goog.async.Deferred)) {
+ // The Thenable case.
+ return this.addCallback(function() {
+ return otherDeferred;
+ });
+ }
+ return this.addCallback(goog.bind(otherDeferred.branch, otherDeferred));
+};
+
+
+/**
+ * Creates a branch off this Deferred's execution sequence, and returns it as a
+ * new Deferred. The branched Deferred's starting result will be shared with the
+ * parent at the point of the branch, even if further callbacks are added to the
+ * parent.
+ *
+ * All branches at the same stage in the execution sequence will receive the
+ * same starting value.
+ *
+ * @param {boolean=} opt_propagateCancel If cancel() is called on every child
+ * branch created with opt_propagateCancel, the parent will be canceled as
+ * well.
+ * @return {!goog.async.Deferred.<VALUE>} A Deferred that will be started with
+ * the computed result from this stage in the execution sequence.
+ */
+goog.async.Deferred.prototype.branch = function(opt_propagateCancel) {
+ var d = new goog.async.Deferred();
+ this.chainDeferred(d);
+ if (opt_propagateCancel) {
+ d.parent_ = this;
+ this.branches_++;
+ }
+ return d;
+};
+
+
+/**
+ * @return {boolean} Whether the execution sequence has been started on this
+ * Deferred by invoking {@code callback} or {@code errback}.
+ */
+goog.async.Deferred.prototype.hasFired = function() {
+ return this.fired_;
+};
+
+
+/**
+ * @param {*} res The latest result in the execution sequence.
+ * @return {boolean} Whether the current result is an error that should cause
+ * the next errback to fire. May be overridden by subclasses to handle
+ * special error types.
+ * @protected
+ */
+goog.async.Deferred.prototype.isError = function(res) {
+ return res instanceof Error;
+};
+
+
+/**
+ * @return {boolean} Whether an errback exists in the remaining sequence.
+ * @private
+ */
+goog.async.Deferred.prototype.hasErrback_ = function() {
+ return goog.array.some(this.sequence_, function(sequenceRow) {
+ // The errback is the second element in the array.
+ return goog.isFunction(sequenceRow[1]);
+ });
+};
+
+
+/**
+ * Exhausts the execution sequence while a result is available. The result may
+ * be modified by callbacks or errbacks, and execution will block if the
+ * returned result is an incomplete Deferred.
+ *
+ * @private
+ */
+goog.async.Deferred.prototype.fire_ = function() {
+ if (this.unhandledErrorId_ && this.hasFired() && this.hasErrback_()) {
+ // It is possible to add errbacks after the Deferred has fired. If a new
+ // errback is added immediately after the Deferred encountered an unhandled
+ // error, but before that error is rethrown, the error is unscheduled.
+ goog.async.Deferred.unscheduleError_(this.unhandledErrorId_);
+ this.unhandledErrorId_ = 0;
+ }
+
+ if (this.parent_) {
+ this.parent_.branches_--;
+ delete this.parent_;
+ }
+
+ var res = this.result_;
+ var unhandledException = false;
+ var isNewlyBlocked = false;
+
+ while (this.sequence_.length && !this.blocked_) {
+ var sequenceEntry = this.sequence_.shift();
+
+ var callback = sequenceEntry[0];
+ var errback = sequenceEntry[1];
+ var scope = sequenceEntry[2];
+
+ var f = this.hadError_ ? errback : callback;
+ if (f) {
+ /** @preserveTry */
+ try {
+ var ret = f.call(scope || this.defaultScope_, res);
+
+ // If no result, then use previous result.
+ if (goog.isDef(ret)) {
+ // Bubble up the error as long as the return value hasn't changed.
+ this.hadError_ = this.hadError_ && (ret == res || this.isError(ret));
+ this.result_ = res = ret;
+ }
+
+ if (goog.Thenable.isImplementedBy(res)) {
+ isNewlyBlocked = true;
+ this.blocked_ = true;
+ }
+
+ } catch (ex) {
+ res = ex;
+ this.hadError_ = true;
+ this.makeStackTraceLong_(res);
+
+ if (!this.hasErrback_()) {
+ // If an error is thrown with no additional errbacks in the queue,
+ // prepare to rethrow the error.
+ unhandledException = true;
+ }
+ }
+ }
+ }
+
+ this.result_ = res;
+
+ if (isNewlyBlocked) {
+ var onCallback = goog.bind(this.continue_, this, true /* isSuccess */);
+ var onErrback = goog.bind(this.continue_, this, false /* isSuccess */);
+
+ if (res instanceof goog.async.Deferred) {
+ res.addCallbacks(onCallback, onErrback);
+ res.blocking_ = true;
+ } else {
+ res.then(onCallback, onErrback);
+ }
+ } else if (goog.async.Deferred.STRICT_ERRORS && this.isError(res) &&
+ !(res instanceof goog.async.Deferred.CanceledError)) {
+ this.hadError_ = true;
+ unhandledException = true;
+ }
+
+ if (unhandledException) {
+ // Rethrow the unhandled error after a timeout. Execution will continue, but
+ // the error will be seen by global handlers and the user. The throw will
+ // be canceled if another errback is appended before the timeout executes.
+ // The error's original stack trace is preserved where available.
+ this.unhandledErrorId_ = goog.async.Deferred.scheduleError_(res);
+ }
+};
+
+
+/**
+ * Creates a Deferred that has an initial result.
+ *
+ * @param {*=} opt_result The result.
+ * @return {!goog.async.Deferred} The new Deferred.
+ */
+goog.async.Deferred.succeed = function(opt_result) {
+ var d = new goog.async.Deferred();
+ d.callback(opt_result);
+ return d;
+};
+
+
+/**
+ * Creates a Deferred that fires when the given promise resolves.
+ * Use only during migration to Promises.
+ *
+ * @param {!goog.Promise.<T>} promise
+ * @return {!goog.async.Deferred.<T>} The new Deferred.
+ * @template T
+ */
+goog.async.Deferred.fromPromise = function(promise) {
+ var d = new goog.async.Deferred();
+ d.callback();
+ d.addCallback(function() {
+ return promise;
+ });
+ return d;
+};
+
+
+/**
+ * Creates a Deferred that has an initial error result.
+ *
+ * @param {*} res The error result.
+ * @return {!goog.async.Deferred} The new Deferred.
+ */
+goog.async.Deferred.fail = function(res) {
+ var d = new goog.async.Deferred();
+ d.errback(res);
+ return d;
+};
+
+
+/**
+ * Creates a Deferred that has already been canceled.
+ *
+ * @return {!goog.async.Deferred} The new Deferred.
+ */
+goog.async.Deferred.canceled = function() {
+ var d = new goog.async.Deferred();
+ d.cancel();
+ return d;
+};
+
+
+/**
+ * Normalizes values that may or may not be Deferreds.
+ *
+ * If the input value is a Deferred, the Deferred is branched (so the original
+ * execution sequence is not modified) and the input callback added to the new
+ * branch. The branch is returned to the caller.
+ *
+ * If the input value is not a Deferred, the callback will be executed
+ * immediately and an already firing Deferred will be returned to the caller.
+ *
+ * In the following (contrived) example, if <code>isImmediate</code> is true
+ * then 3 is alerted immediately, otherwise 6 is alerted after a 2-second delay.
+ *
+ * <pre>
+ * var value;
+ * if (isImmediate) {
+ * value = 3;
+ * } else {
+ * value = new goog.async.Deferred();
+ * setTimeout(function() { value.callback(6); }, 2000);
+ * }
+ *
+ * var d = goog.async.Deferred.when(value, alert);
+ * </pre>
+ *
+ * @param {*} value Deferred or normal value to pass to the callback.
+ * @param {!function(this:T, ?):?} callback The callback to execute.
+ * @param {T=} opt_scope An optional scope to call the callback in.
+ * @return {!goog.async.Deferred} A new Deferred that will call the input
+ * callback with the input value.
+ * @template T
+ */
+goog.async.Deferred.when = function(value, callback, opt_scope) {
+ if (value instanceof goog.async.Deferred) {
+ return value.branch(true).addCallback(callback, opt_scope);
+ } else {
+ return goog.async.Deferred.succeed(value).addCallback(callback, opt_scope);
+ }
+};
+
+
+
+/**
+ * An error sub class that is used when a Deferred has already been called.
+ * @param {!goog.async.Deferred} deferred The Deferred.
+ *
+ * @constructor
+ * @extends {goog.debug.Error}
+ */
+goog.async.Deferred.AlreadyCalledError = function(deferred) {
+ goog.debug.Error.call(this);
+
+ /**
+ * The Deferred that raised this error.
+ * @type {goog.async.Deferred}
+ */
+ this.deferred = deferred;
+};
+goog.inherits(goog.async.Deferred.AlreadyCalledError, goog.debug.Error);
+
+
+/** @override */
+goog.async.Deferred.AlreadyCalledError.prototype.message =
+ 'Deferred has already fired';
+
+
+/** @override */
+goog.async.Deferred.AlreadyCalledError.prototype.name = 'AlreadyCalledError';
+
+
+
+/**
+ * An error sub class that is used when a Deferred is canceled.
+ *
+ * @param {!goog.async.Deferred} deferred The Deferred object.
+ * @constructor
+ * @extends {goog.debug.Error}
+ */
+goog.async.Deferred.CanceledError = function(deferred) {
+ goog.debug.Error.call(this);
+
+ /**
+ * The Deferred that raised this error.
+ * @type {goog.async.Deferred}
+ */
+ this.deferred = deferred;
+};
+goog.inherits(goog.async.Deferred.CanceledError, goog.debug.Error);
+
+
+/** @override */
+goog.async.Deferred.CanceledError.prototype.message = 'Deferred was canceled';
+
+
+/** @override */
+goog.async.Deferred.CanceledError.prototype.name = 'CanceledError';
+
+
+
+/**
+ * Wrapper around errors that are scheduled to be thrown by failing deferreds
+ * after a timeout.
+ *
+ * @param {*} error Error from a failing deferred.
+ * @constructor
+ * @final
+ * @private
+ * @struct
+ */
+goog.async.Deferred.Error_ = function(error) {
+ /** @const @private {number} */
+ this.id_ = goog.global.setTimeout(goog.bind(this.throwError, this), 0);
+
+ /** @const @private {*} */
+ this.error_ = error;
+};
+
+
+/**
+ * Actually throws the error and removes it from the list of pending
+ * deferred errors.
+ */
+goog.async.Deferred.Error_.prototype.throwError = function() {
+ goog.asserts.assert(goog.async.Deferred.errorMap_[this.id_],
+ 'Cannot throw an error that is not scheduled.');
+ delete goog.async.Deferred.errorMap_[this.id_];
+ throw this.error_;
+};
+
+
+/**
+ * Resets the error throw timer.
+ */
+goog.async.Deferred.Error_.prototype.resetTimer = function() {
+ goog.global.clearTimeout(this.id_);
+};
+
+
+/**
+ * Map of unhandled errors scheduled to be rethrown in a future timestep.
+ * @private {!Object.<number|string, goog.async.Deferred.Error_>}
+ */
+goog.async.Deferred.errorMap_ = {};
+
+
+/**
+ * Schedules an error to be thrown after a delay.
+ * @param {*} error Error from a failing deferred.
+ * @return {number} Id of the error.
+ * @private
+ */
+goog.async.Deferred.scheduleError_ = function(error) {
+ var deferredError = new goog.async.Deferred.Error_(error);
+ goog.async.Deferred.errorMap_[deferredError.id_] = deferredError;
+ return deferredError.id_;
+};
+
+
+/**
+ * Unschedules an error from being thrown.
+ * @param {number} id Id of the deferred error to unschedule.
+ * @private
+ */
+goog.async.Deferred.unscheduleError_ = function(id) {
+ var error = goog.async.Deferred.errorMap_[id];
+ if (error) {
+ error.resetTimer();
+ delete goog.async.Deferred.errorMap_[id];
+ }
+};
+
+
+/**
+ * Asserts that there are no pending deferred errors. If there are any
+ * scheduled errors, one will be thrown immediately to make this function fail.
+ */
+goog.async.Deferred.assertNoErrors = function() {
+ var map = goog.async.Deferred.errorMap_;
+ for (var key in map) {
+ var error = map[key];
+ error.resetTimer();
+ error.throwError();
+ }
+};
diff --git a/third_party/google_input_tools/update.py b/third_party/google_input_tools/update.py
new file mode 100755
index 0000000..ce0e79f
--- /dev/null
+++ b/third_party/google_input_tools/update.py
@@ -0,0 +1,243 @@
+#!/usr/bin/python
+# 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.
+
+import logging
+import optparse
+import os
+import re
+import shutil
+import sys
+
+_BASE_REGEX_STRING = '^\s*goog\.%s\(\s*[\'"](.+)[\'"]\s*\)'
+require_regex = re.compile(_BASE_REGEX_STRING % 'require')
+provide_regex = re.compile(_BASE_REGEX_STRING % 'provide')
+
+# Entry-points required to build a virtual keyboard.
+namespaces = [
+ 'i18n.input.chrome.inputview.Controller',
+ 'i18n.input.chrome.inputview.content.compact.letter',
+ 'i18n.input.chrome.inputview.content.compact.util',
+ 'i18n.input.chrome.inputview.content.compact.symbol',
+ 'i18n.input.chrome.inputview.content.compact.more',
+ 'i18n.input.chrome.inputview.content.compact.numberpad',
+ 'i18n.input.chrome.inputview.content.ContextlayoutUtil',
+ 'i18n.input.chrome.inputview.content.util',
+ 'i18n.input.chrome.inputview.EmojiType',
+ 'i18n.input.chrome.inputview.layouts.CompactSpaceRow',
+ 'i18n.input.chrome.inputview.layouts.RowsOf101',
+ 'i18n.input.chrome.inputview.layouts.RowsOf102',
+ 'i18n.input.chrome.inputview.layouts.RowsOfCompact',
+ 'i18n.input.chrome.inputview.layouts.RowsOfJP',
+ 'i18n.input.chrome.inputview.layouts.SpaceRow',
+ 'i18n.input.chrome.inputview.layouts.util',
+ 'i18n.input.hwt.util']
+
+
+def ProcessFile(filename):
+ """Extracts provided and required namespaces.
+
+ Description:
+ Scans Javascript file for provied and required namespaces.
+
+ Args:
+ filename: name of the file to process.
+
+ Returns:
+ Pair of lists, where the first list contains namespaces provided by the file
+ and the second contains a list of requirements.
+ """
+ provides = []
+ requires = []
+ file_handle = open(filename, 'r')
+ try:
+ for line in file_handle:
+ if re.match(require_regex, line):
+ requires.append(re.search(require_regex, line).group(1))
+ if re.match(provide_regex, line):
+ provides.append(re.search(provide_regex, line).group(1))
+ finally:
+ file_handle.close()
+ return provides, requires
+
+
+def ExpandDirectories(refs):
+ """Expands any directory references into inputs.
+
+ Description:
+ Looks for any directories in the provided references. Found directories
+ are recursively searched for .js files.
+
+ Args:
+ refs: a list of directories.
+
+ Returns:
+ Pair of maps, where the first maps each namepace to the filename that
+ provides the namespace, and the second maps a filename to prerequisite
+ namespaces.
+ """
+ providers = {}
+ requirements = {}
+ for ref in refs:
+ if os.path.isdir(ref):
+ for (root, dirs, files) in os.walk(ref):
+ for name in files:
+ if name.endswith('js'):
+ filename = os.path.join(root, name)
+ provides, requires = ProcessFile(filename)
+ for p in provides:
+ providers[p] = filename
+ requirements[filename] = []
+ for r in requires:
+ requirements[filename].append(r)
+ return providers, requirements
+
+
+def ExtractDependencies(namespace, providers, requirements, dependencies):
+ """Recursively extracts all dependencies for a namespace.
+ Description:
+ Recursively extracts all dependencies for a namespace.
+
+ Args:
+ namespace: The namespace to process.
+ providers: Mapping of namespace to filename that provides the namespace.
+ requireemnts: Mapping of filename to a list of prerequisite namespaces.
+ Returns:
+ """
+ if namespace in providers:
+ filename = providers[namespace]
+ if not filename in dependencies:
+ for ns in requirements[filename]:
+ ExtractDependencies(ns, providers, requirements, dependencies)
+ dependencies.add(filename)
+
+
+def HomeDir():
+ """Resolves the user's home directory."""
+
+ return os.path.expanduser('~')
+
+
+def ExpandPathRelativeToHome(path):
+ """Resolves a path that is relative to the home directory.
+
+ Args:
+ path: Relative path.
+
+ Returns:
+ Resolved path.
+ """
+
+ return os.path.join(os.path.expanduser('~'), path)
+
+
+def GetGoogleInputToolsSandboxFromOptions(options):
+ """Generate the input-input-tools path from the --input flag.
+
+ Args:
+ options: Flags to update.py.
+ Returns:
+ Path to the google-input-tools sandbox.
+ """
+
+ path = options.input
+ if not path:
+ path = ExpandPathRelativeToHome('google-input-tools')
+ print 'Unspecified path for google-input-tools. Defaulting to %s' % path
+ return path
+
+
+def GetClosureLibrarySandboxFromOptions(options):
+ """Generate the closure-library path from the --input flag.
+
+ Args:
+ options: Flags to update.py.
+ Returns:
+ Path to the closure-library sandbox.
+ """
+
+ path = options.lib
+ if not path:
+ path = ExpandPathRelativeToHome('closure-library')
+ print 'Unspecified path for closure-library. Defaulting to %s' % path
+ return path
+
+
+def CopyFile(source, target):
+ """Copies a file from the source to the target location.
+
+ Args:
+ source: Path to the source file to copy.
+ target: Path to the target location to copy the file.
+ """
+ print '%s --> %s' % (source, target)
+ if not os.path.exists(os.path.dirname(target)):
+ os.makedirs(os.path.dirname(target))
+ shutil.copy(source, target)
+
+
+def UpdateFile(filename, input_source, closure_source):
+ """Updates files in third_party/google_input_tools.
+ Args:
+ filename: The file to update.
+ input_source: Root of the google_input_tools sandbox.
+ closure_source: Root of the closure_library sandbox.
+ """
+ target = ''
+ if filename.startswith(input_source):
+ target = os.path.join('src', filename[len(input_source)+1:])
+ elif filename.startswith(closure_source):
+ target = os.path.join('third_party/closure_library', \
+ filename[len(closure_source)+1:])
+ if len(target) > 0:
+ CopyFile(filename, target)
+
+
+def main():
+ """The entrypoint for this script."""
+
+ logging.basicConfig(format='update.py: %(message)s', level=logging.INFO)
+
+ usage = 'usage: %prog [options] arg'
+ parser = optparse.OptionParser(usage)
+ parser.add_option('-i',
+ '--input',
+ dest='input',
+ action='append',
+ help='Path to the google-input-tools sandbox.')
+ parser.add_option('-l',
+ '--lib',
+ dest='lib',
+ action='store',
+ help='Path to the closure-library sandbox.')
+
+ (options, args) = parser.parse_args()
+
+ input_path = GetGoogleInputToolsSandboxFromOptions(options)
+ closure_library_path = GetClosureLibrarySandboxFromOptions(options)
+
+ print 'iput_path = %s' % input_path
+ print 'closure_library_path = %s' % closure_library_path
+
+ if not os.path.isdir(input_path):
+ print 'Could not find google-input-tools sandbox.'
+ exit(1)
+ if not os.path.isdir(closure_library_path):
+ print 'Could not find closure-library sandbox.'
+ exit(1)
+
+ (providers, requirements) = \
+ ExpandDirectories([os.path.join(input_path, 'chrome'),
+ closure_library_path])
+
+ dependencies = set()
+
+ for name in namespaces:
+ ExtractDependencies(name, providers, requirements, dependencies)
+
+ for name in dependencies:
+ UpdateFile(name, input_path, closure_library_path)
+
+if __name__ == '__main__':
+ main()