Skip to content
This repository has been archived by the owner on Aug 1, 2024. It is now read-only.

Commit

Permalink
RELNOTES[NEW]: Fix goog.editor.Field so handleChanges is called when …
Browse files Browse the repository at this point in the history
…content is changed on mobile devices.

PiperOrigin-RevId: 436459407
Change-Id: I62b265e64b1c9441406b8f02498a47d82e25e3a3
  • Loading branch information
Closure Team authored and copybara-github committed Mar 22, 2022
1 parent e9d43fc commit 605aa47
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 11 deletions.
1 change: 1 addition & 0 deletions closure/goog/editor/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ closure_js_library(
"//closure/goog/html:safehtml",
"//closure/goog/html:safestylesheet",
"//closure/goog/html:trustedresourceurl",
"//closure/goog/labs/useragent:platform",
"//closure/goog/log",
"//closure/goog/object",
"//closure/goog/reflect",
Expand Down
21 changes: 16 additions & 5 deletions closure/goog/editor/field.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ goog.require('goog.functions');
goog.require('goog.html.SafeHtml');
goog.require('goog.html.SafeStyleSheet');
goog.require('goog.html.legacyconversions');
goog.require('goog.labs.userAgent.platform');
goog.require('goog.log');
goog.require('goog.log.Level');
goog.require('goog.string');
Expand Down Expand Up @@ -686,12 +687,18 @@ goog.editor.Field.CTRL_KEYS_CAUSING_CHANGES_ = {
88: true // X
};

if (goog.userAgent.WINDOWS && !goog.userAgent.GECKO) {
if ((goog.userAgent.WINDOWS || goog.labs.userAgent.platform.isAndroid()) &&
!goog.userAgent.GECKO) {
// In IE and Webkit, input from IME (Input Method Editor) does not generate a
// keypress event so we have to rely on the keydown event. This way we have
// false positives while the user is using keyboard to select the
// character to input, but it is still better than the false negatives
// that ignores user's final input at all.
// The same phenomina happen on android devices - no KeyPress events are
// emitted, and all KeyDown events have no useful charCode or other
// identifying information (see
// https://bugs.chromium.org/p/chromium/issues/detail?id=118639 for
// background, but it's considered WAI by various Input Method experts).
goog.editor.Field.KEYS_CAUSING_CHANGES_[229] = true; // from IME;
}

Expand Down Expand Up @@ -894,6 +901,10 @@ goog.editor.Field.prototype.setupChangeListeners_ = function() {
this.addListener(goog.events.EventType.KEYPRESS, this.handleKeyPress_);
this.addListener(goog.events.EventType.KEYUP, this.handleKeyUp_);

// Handles changes from non-keyboard forms of input. Such as choosing a
// spellcheck suggestion.
this.addListener(goog.events.EventType.INPUT, this.handleChange);

this.selectionChangeTimer_ = new goog.async.Delay(
this.handleSelectionChangeTimer_,
goog.editor.Field.SELECTION_CHANGE_FREQUENCY_, this);
Expand Down Expand Up @@ -1092,7 +1103,7 @@ goog.editor.Field.prototype.handleBeforeChangeKeyEvent_ = function(e) {

// TODO(arv): Del at end of field or backspace at beginning should be
// ignored.
this.gotGeneratingKey_ = e.charCode ||
this.gotGeneratingKey_ = !!e.charCode ||
goog.editor.Field.isGeneratingKey_(e, goog.userAgent.GECKO);
if (this.gotGeneratingKey_) {
this.dispatchBeforeChange();
Expand Down Expand Up @@ -2125,7 +2136,7 @@ goog.editor.Field.prototype.handleMouseUp_ = function(e) {
*
* Do NOT just get the innerHTML of a field directly--there's a lot of
* processing that needs to happen.
* @return {string} The scrubbed contents of the field.
* @return {string} The scrubbed contents of the field.
*/
goog.editor.Field.prototype.getCleanContents = function() {
'use strict';
Expand All @@ -2139,7 +2150,7 @@ goog.editor.Field.prototype.getCleanContents = function() {
if (!elem) {
goog.log.log(
this.logger, goog.log.Level.SHOUT,
"Couldn't get the field element to read the contents");
'Couldn\'t get the field element to read the contents');
}
return elem.innerHTML;
}
Expand Down Expand Up @@ -2192,7 +2203,7 @@ goog.editor.Field.prototype.setSafeHtml = function(
addParas, html, opt_dontFireDelayedChange, opt_applyLorem) {
'use strict';
if (this.isLoading()) {
goog.log.error(this.logger, "Can't set html while loading Trogedit");
goog.log.error(this.logger, 'Can\'t set html while loading Trogedit');
return;
}

Expand Down
104 changes: 98 additions & 6 deletions closure/goog/editor/field_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@ const Plugin = goog.require('goog.editor.Plugin');
const Range = goog.require('goog.dom.Range');
const SafeHtml = goog.require('goog.html.SafeHtml');
const TagName = goog.require('goog.dom.TagName');
const TestEvent = goog.require('goog.testing.events.Event');
const classlist = goog.require('goog.dom.classlist');
const editorRange = goog.require('goog.editor.range');
const events = goog.require('goog.events');
const functions = goog.require('goog.functions');
const googDom = goog.require('goog.dom');
const platform = goog.require('goog.labs.userAgent.platform');
const recordFunction = goog.require('goog.testing.recordFunction');
const testSuite = goog.require('goog.testing.testSuite');
const testingDom = goog.require('goog.testing.dom');
Expand Down Expand Up @@ -181,7 +183,8 @@ function doTestPlaceCursorAtStart(html = undefined, parentId = undefined) {

let startNode = parentId ?
editableField.getEditableDomHelper().getElement(parentId).firstChild :
textNode ? textNode : editableField.getElement();
textNode ? textNode :
editableField.getElement();
assertEquals(
'The range should start at the specified expected node', startNode,
range.getStartNode());
Expand Down Expand Up @@ -239,13 +242,14 @@ function doTestPlaceCursorAtEnd(

const endNode = parentId ?
editableField.getEditableDomHelper().getElement(parentId).lastChild :
textNode ? textNode : editableField.getElement();
textNode ? textNode :
editableField.getElement();
assertEquals(
'The range should end at the specified expected node', endNode,
range.getEndNode());
const offset = (opt_offset != null) ?
opt_offset :
textNode ? endNode.nodeValue.length : endNode.childNodes.length - 1;
const offset = (opt_offset != null) ? opt_offset :
textNode ? endNode.nodeValue.length :
endNode.childNodes.length - 1;
if (hasBogusNode) {
assertEquals(
'The range should end at the ending of the bogus node ' +
Expand Down Expand Up @@ -1425,13 +1429,101 @@ testSuite({

assertTrue(Field.isGeneratingKey_(regularKeyEvent, true));
assertFalse(Field.isGeneratingKey_(ctrlKeyEvent, true));
if (userAgent.WINDOWS && !userAgent.GECKO) {
if ((userAgent.WINDOWS || platform.isAndroid()) && !userAgent.GECKO) {
assertTrue(Field.isGeneratingKey_(imeKeyEvent, false));
} else {
assertFalse(Field.isGeneratingKey_(imeKeyEvent, false));
}
},

testRegularKeyDispatchesDelayedChange() {
if (userAgent.GECKO) {
// Gecko based browsers handle changes via mutation events
return;
}
if (userAgent.WINDOWS || platform.isAndroid()) {
// Windows and Android platforms do not emit events with 'regular' key
// codes.
return;
}
const editableField = new FieldConstructor('testField');
const clock = new MockClock(true);
const delayedChanges = recordFunction();

editableField.makeEditable();
events.listen(editableField, Field.EventType.DELAYEDCHANGE, delayedChanges);

testingEvents.fireKeySequence(editableField.getElement(), KeyCodes.A);
clock.tick(1000);

if (!(userAgent.WINDOWS || platform.isAndroid()) || userAgent.GECKO) {
assertEquals(
'Delayed change event should\'ve been dispatched', 1,
delayedChanges.getCallCount());
}

clock.dispose();
editableField.dispose();
},

testImeKeyDispatchesDelayedChange() {
if (BrowserFeature.USE_MUTATION_EVENTS) {
// Gecko based browsers handle changes via mutation events
return;
}
if (!(userAgent.WINDOWS || platform.isAndroid())) {
// Only Windows and Android platforms emit these IME-specific events.
return;
}
const editableField = new FieldConstructor('testField');
const clock = new MockClock(true);
const delayedChanges = recordFunction();

editableField.makeEditable();
events.listen(editableField, Field.EventType.DELAYEDCHANGE, delayedChanges);

testingEvents.fireKeySequence(editableField.getElement(), KeyCodes.WIN_IME);
clock.tick(1000);

assertEquals(
'Delayed change event should\'ve been dispatched', 1,
delayedChanges.getCallCount());


clock.dispose();
editableField.dispose();
},

testInputEventDispatchesDelayedChange() {
if (BrowserFeature.USE_MUTATION_EVENTS) {
// Gecko based browsers handle changes via mutation events
return;
}
const editableField = new FieldConstructor('testField');
const clock = new MockClock(true);
const delayedChanges = recordFunction();

editableField.makeEditable();
events.listen(editableField, Field.EventType.DELAYEDCHANGE, delayedChanges);

// Non-typing changes on some devices rely on emitting an INPUT event, such
// as:
// - swipe-typing a word (on iOS)
// - accepting a word prediction (on iOS)
// - using speech to text (on iOS)
// - accepting a spellcheck suggestion (on iOS, Android or Desktop)
testingEvents.fireBrowserEvent(
new TestEvent(EventType.INPUT, editableField.getElement()));
clock.tick(1000);

assertEquals(
'Delayed change event should\'ve been dispatched', 1,
delayedChanges.getCallCount());

clock.dispose();
editableField.dispose();
},

testSetEditableClassName() {
const element = googDom.getElement('testField');
const editableField = new FieldConstructor('testField');
Expand Down

0 comments on commit 605aa47

Please sign in to comment.