Index: third_party/google_input_tools/third_party/closure_library/closure/goog/ui/control.js |
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 |
index a258c26dda56e4e4b44340c03cefd456548823b6..c9382f3d871415bbbdc0355bbee57b3d47428873 100644 |
--- 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 |
@@ -25,9 +25,12 @@ |
goog.provide('goog.ui.Control'); |
+goog.require('goog.Disposable'); |
goog.require('goog.array'); |
goog.require('goog.dom'); |
+goog.require('goog.events.BrowserEvent'); |
goog.require('goog.events.Event'); |
+goog.require('goog.events.EventHandler'); |
goog.require('goog.events.EventType'); |
goog.require('goog.events.KeyCodes'); |
goog.require('goog.events.KeyHandler'); |
@@ -77,6 +80,12 @@ goog.ui.Control = function(opt_content, opt_renderer, opt_domHelper) { |
this.renderer_ = opt_renderer || |
goog.ui.registry.getDefaultRenderer(this.constructor); |
this.setContentInternal(goog.isDef(opt_content) ? opt_content : null); |
+ |
+ /** @private {?string} The control's aria-label. */ |
+ this.ariaLabel_ = null; |
+ |
+ /** @private {goog.ui.Control.IeMouseEventSequenceSimulator_} */ |
+ this.ieMouseEventSequenceSimulator_; |
}; |
goog.inherits(goog.ui.Control, goog.ui.Component); |
goog.tagUnsealableClass(goog.ui.Control); |
@@ -212,7 +221,7 @@ goog.ui.Control.prototype.keyHandler_; |
/** |
* Additional class name(s) to apply to the control's root element, if any. |
- * @type {Array.<string>?} |
+ * @type {Array<string>?} |
* @private |
*/ |
goog.ui.Control.prototype.extraClassNames_ = null; |
@@ -339,7 +348,7 @@ goog.ui.Control.prototype.setRenderer = function(renderer) { |
/** |
* 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 |
+ * @return {Array<string>?} Additional class names to be applied to |
* the component's root element (null if none). |
*/ |
goog.ui.Control.prototype.getExtraClassNames = function() { |
@@ -458,6 +467,30 @@ goog.ui.Control.prototype.setPreferredAriaRole = function(role) { |
/** |
+ * Gets the control's aria label. |
+ * @return {?string} This control's aria label. |
+ */ |
+goog.ui.Control.prototype.getAriaLabel = function() { |
+ return this.ariaLabel_; |
+}; |
+ |
+ |
+/** |
+ * Sets the control's aria label. This can be used to assign aria label to the |
+ * element after it is rendered. |
+ * @param {string} label The string to set as the aria label for this control. |
+ * No escaping is done on this value. |
+ */ |
+goog.ui.Control.prototype.setAriaLabel = function(label) { |
+ this.ariaLabel_ = label; |
+ var element = this.getElement(); |
+ if (element) { |
+ this.renderer_.setAriaLabel(element, label); |
+ } |
+}; |
+ |
+ |
+/** |
* 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. |
@@ -518,6 +551,9 @@ goog.ui.Control.prototype.decorateInternal = function(element) { |
goog.ui.Control.prototype.enterDocument = function() { |
goog.ui.Control.superClass_.enterDocument.call(this); |
+ // Call the renderer's setAriaStates method to set element's aria attributes. |
+ this.renderer_.setAriaStates(this, this.getElementStrict()); |
+ |
// 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); |
@@ -574,6 +610,11 @@ goog.ui.Control.prototype.enableMouseEventHandling_ = function(enable) { |
if (goog.userAgent.IE) { |
handler.listen(element, goog.events.EventType.DBLCLICK, |
this.handleDblClick); |
+ if (!this.ieMouseEventSequenceSimulator_) { |
+ this.ieMouseEventSequenceSimulator_ = |
+ new goog.ui.Control.IeMouseEventSequenceSimulator_(this); |
+ this.registerDisposable(this.ieMouseEventSequenceSimulator_); |
+ } |
} |
} else { |
handler. |
@@ -590,6 +631,8 @@ goog.ui.Control.prototype.enableMouseEventHandling_ = function(enable) { |
if (goog.userAgent.IE) { |
handler.unlisten(element, goog.events.EventType.DBLCLICK, |
this.handleDblClick); |
+ goog.dispose(this.ieMouseEventSequenceSimulator_); |
+ this.ieMouseEventSequenceSimulator_ = null; |
} |
} |
}; |
@@ -623,6 +666,7 @@ goog.ui.Control.prototype.disposeInternal = function() { |
delete this.renderer_; |
this.content_ = null; |
this.extraClassNames_ = null; |
+ this.ieMouseEventSequenceSimulator_ = null; |
}; |
@@ -824,7 +868,7 @@ goog.ui.Control.prototype.setEnabled = function(enable) { |
if (this.isVisible()) { |
this.renderer_.setFocusable(this, enable); |
} |
- this.setState(goog.ui.Component.State.DISABLED, !enable); |
+ this.setState(goog.ui.Component.State.DISABLED, !enable, true); |
} |
}; |
@@ -996,8 +1040,13 @@ goog.ui.Control.prototype.hasState = function(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). |
+ * @param {boolean=} opt_calledFrom Prevents looping with setEnabled. |
*/ |
-goog.ui.Control.prototype.setState = function(state, enable) { |
+goog.ui.Control.prototype.setState = function(state, enable, opt_calledFrom) { |
+ if (!opt_calledFrom && state == goog.ui.Component.State.DISABLED) { |
+ this.setEnabled(!enable); |
+ return; |
+ } |
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); |
@@ -1224,7 +1273,7 @@ goog.ui.Control.prototype.handleMouseDown = function(e) { |
if (this.isAutoState(goog.ui.Component.State.ACTIVE)) { |
this.setActive(true); |
} |
- if (this.renderer_.isFocusable(this)) { |
+ if (this.renderer_ && this.renderer_.isFocusable(this)) { |
this.getKeyEventTarget().focus(); |
} |
} |
@@ -1386,3 +1435,110 @@ goog.ui.registry.setDecoratorByClassName(goog.ui.ControlRenderer.CSS_CLASS, |
function() { |
return new goog.ui.Control(null); |
}); |
+ |
+ |
+ |
+/** |
+ * A singleton that helps goog.ui.Control instances play well with screen |
+ * readers. It necessitated by shortcomings in IE, and need not be |
+ * instantiated in any other browser. |
+ * |
+ * In most cases, a click on a goog.ui.Control results in a sequence of events: |
+ * MOUSEDOWN, MOUSEUP and CLICK. UI controls rely on this sequence since most |
+ * behavior is trigged by MOUSEDOWN and MOUSEUP. But when IE is used with some |
+ * traditional screen readers (JAWS, NVDA and perhaps others), IE only sends |
+ * the CLICK event, resulting in the control being unresponsive. This class |
+ * monitors the sequence of these events, and if it detects a CLICK event not |
+ * not preceded by a MOUSEUP event, directly calls the control's event handlers |
+ * for MOUSEDOWN, then MOUSEUP. While the resulting sequence is different from |
+ * the norm (the CLICK comes first instead of last), testing thus far shows |
+ * the resulting behavior to be correct. |
+ * |
+ * See http://goo.gl/qvQR4C for more details. |
+ * |
+ * @param {!goog.ui.Control} control |
+ * @constructor |
+ * @extends {goog.Disposable} |
+ * @private |
+ */ |
+goog.ui.Control.IeMouseEventSequenceSimulator_ = function(control) { |
+ goog.ui.Control.IeMouseEventSequenceSimulator_.base(this, 'constructor'); |
+ |
+ /** @private {goog.ui.Control}*/ |
+ this.control_ = control; |
+ |
+ /** @private {boolean} */ |
+ this.clickExpected_ = false; |
+ |
+ /** @private @const */ |
+ this.handler_ = new goog.events.EventHandler(this); |
+ this.registerDisposable(this.handler_); |
+ |
+ var element = this.control_.getElementStrict(); |
+ this.handler_. |
+ listen(element, goog.events.EventType.MOUSEDOWN, this.handleMouseDown_). |
+ listen(element, goog.events.EventType.MOUSEUP, this.handleMouseUp_). |
+ listen(element, goog.events.EventType.CLICK, this.handleClick_); |
+}; |
+goog.inherits(goog.ui.Control.IeMouseEventSequenceSimulator_, goog.Disposable); |
+ |
+ |
+/** @private */ |
+goog.ui.Control.IeMouseEventSequenceSimulator_.prototype.handleMouseDown_ = |
+ function() { |
+ this.clickExpected_ = false; |
+}; |
+ |
+ |
+/** @private */ |
+goog.ui.Control.IeMouseEventSequenceSimulator_.prototype.handleMouseUp_ = |
+ function() { |
+ this.clickExpected_ = true; |
+}; |
+ |
+ |
+/** |
+ * @param {!goog.events.Event} e |
+ * @private |
+ */ |
+goog.ui.Control.IeMouseEventSequenceSimulator_.prototype.handleClick_ = |
+ function(e) { |
+ if (this.clickExpected_) { |
+ // This is the end of a normal click sequence: mouse-down, mouse-up, click. |
+ // Assume appropriate actions have already been performed. |
+ this.clickExpected_ = false; |
+ return; |
+ } |
+ |
+ // For click events not part of a normal sequence, similate the mouse-down and |
+ // mouse-up events by creating synthetic events for each and directly invoke |
+ // the corresponding event listeners in order. |
+ |
+ var browserEvent = /** @type {goog.events.BrowserEvent} */ (e); |
+ |
+ var event = browserEvent.getBrowserEvent(); |
+ var origEventButton = event.button; |
+ var origEventType = event.type; |
+ |
+ event.button = goog.events.BrowserEvent.MouseButton.LEFT; |
+ |
+ event.type = goog.events.EventType.MOUSEDOWN; |
+ this.control_.handleMouseDown( |
+ new goog.events.BrowserEvent(event, browserEvent.currentTarget)); |
+ |
+ event.type = goog.events.EventType.MOUSEUP; |
+ this.control_.handleMouseUp( |
+ new goog.events.BrowserEvent(event, browserEvent.currentTarget)); |
+ |
+ // Restore original values for click handlers that have not yet been invoked. |
+ event.button = origEventButton; |
+ event.type = origEventType; |
+}; |
+ |
+ |
+/** @override */ |
+goog.ui.Control.IeMouseEventSequenceSimulator_.prototype.disposeInternal = |
+ function() { |
+ this.control_ = null; |
+ goog.ui.Control.IeMouseEventSequenceSimulator_.base(this, 'disposeInternal'); |
+}; |