Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Automated g4 rollback

*** Reason for rollback ***

Rolling the original change forward

*** Original change description ***

Automated g4 rollback

*** Reason for rollback ***

I can consistently make IE8 segfault after this change

*** Original change description ***

Generalize the the special handling of HR tags in
goog.editor.range.placeCursorNextTo() to better handle IMG tags and
other no-child-allowed Element nodes. This logic now takes effect
for all browsers, rather than just IE/IE9.
The the original behavior caused scrolling issues
when inserting images or emoji in gmail, on IE.
Also c

... description truncated by g4 rollback ...

R=nicksantos
DELTA=208 (167 added, 18 deleted, 23 changed)


Revision created by MOE tool push_codebase.
MOE_MIGRATION=6098


git-svn-id: http://closure-library.googlecode.com/svn/trunk@2424 0b95b8e8-c90f-11de-9d4f-f947ee5921c8
  • Loading branch information...
commit bd2a9d2545634ff983f6fcb9a83dc8dad98fca83 1 parent a90ff85
jamisong@google.com authored
2  closure/goog/deps.js
@@ -568,7 +568,7 @@ goog.addDependency('testing/continuationtestcase.js', ['goog.testing.Continuatio
568 568 goog.addDependency('testing/deferredtestcase.js', ['goog.testing.DeferredTestCase'], ['goog.async.Deferred', 'goog.testing.AsyncTestCase', 'goog.testing.TestCase']);
569 569 goog.addDependency('testing/dom.js', ['goog.testing.dom'], ['goog.dom', 'goog.dom.NodeIterator', 'goog.dom.NodeType', 'goog.dom.TagIterator', 'goog.dom.TagName', 'goog.dom.classes', 'goog.iter', 'goog.object', 'goog.string', 'goog.style', 'goog.testing.asserts', 'goog.userAgent']);
570 570 goog.addDependency('testing/editor/dom.js', ['goog.testing.editor.dom'], ['goog.dom.NodeType', 'goog.dom.TagIterator', 'goog.dom.TagWalkType', 'goog.iter', 'goog.string', 'goog.testing.asserts']);
571   -goog.addDependency('testing/editor/fieldmock.js', ['goog.testing.editor.FieldMock'], ['goog.dom', 'goog.dom.Range', 'goog.editor.Field', 'goog.testing.LooseMock']);
  571 +goog.addDependency('testing/editor/fieldmock.js', ['goog.testing.editor.FieldMock'], ['goog.dom', 'goog.dom.Range', 'goog.editor.Field', 'goog.testing.LooseMock', 'goog.testing.mockmatchers']);
572 572 goog.addDependency('testing/editor/testhelper.js', ['goog.testing.editor.TestHelper'], ['goog.Disposable', 'goog.dom', 'goog.dom.Range', 'goog.editor.BrowserFeature', 'goog.editor.node', 'goog.testing.dom']);
573 573 goog.addDependency('testing/events/eventobserver.js', ['goog.testing.events.EventObserver'], ['goog.array']);
574 574 goog.addDependency('testing/events/events.js', ['goog.testing.events', 'goog.testing.events.Event'], ['goog.events', 'goog.events.BrowserEvent', 'goog.events.BrowserEvent.MouseButton', 'goog.events.BrowserFeature', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.object', 'goog.style', 'goog.userAgent']);
18 closure/goog/editor/field.js
@@ -2331,6 +2331,24 @@ goog.editor.Field.prototype.placeCursorAtStartOrEnd_ = function(isStart) {
2331 2331
2332 2332
2333 2333 /**
  2334 + * Restore a saved range, and set the focus on the field.
  2335 + * If no range is specified, we simply set the focus.
  2336 + * @param {goog.dom.SavedRange=} opt_range A previously saved selected range.
  2337 + */
  2338 +goog.editor.Field.prototype.restoreSavedRange = function(opt_range) {
  2339 + if (goog.userAgent.IE) {
  2340 + this.focus();
  2341 + }
  2342 + if (opt_range) {
  2343 + opt_range.restore();
  2344 + }
  2345 + if (!goog.userAgent.IE) {
  2346 + this.focus();
  2347 + }
  2348 +};
  2349 +
  2350 +
  2351 +/**
2334 2352 * Makes a field editable.
2335 2353 *
2336 2354 * @param {string=} opt_iframeSrc URL to set the iframe src to if necessary.
54 closure/goog/editor/field_test.js
@@ -1014,6 +1014,45 @@ function doTestPlaceCursorAtStart(opt_html, opt_parentId) {
1014 1014 }
1015 1015
1016 1016
  1017 +/**
  1018 + * Verify that restoreSavedRange() restores the range and sets the focus.
  1019 + */
  1020 +function testRestoreSavedRange() {
  1021 + var editableField = new FieldConstructor('testField', document);
  1022 + editableField.makeEditable();
  1023 +
  1024 +
  1025 + // Create another node to take the focus later.
  1026 + var doc = goog.dom.getOwnerDocument(editableField.getElement());
  1027 + var otherElem = doc.createElement('div');
  1028 + otherElem.tabIndex = '1'; // Make it focusable.
  1029 + editableField.getElement().parentNode.appendChild(otherElem);
  1030 +
  1031 + // Initially place selection not at the start of the editable field.
  1032 + var textNode = editableField.getElement().firstChild;
  1033 + var range = goog.dom.Range.createFromNodes(textNode, 1, textNode, 2);
  1034 + range.select();
  1035 + var savedRange = goog.editor.range.saveUsingNormalizedCarets(range);
  1036 +
  1037 + // Change range to be a simple cursor at start, and move focus away.
  1038 + editableField.placeCursorAtStart();
  1039 + otherElem.focus();
  1040 +
  1041 + editableField.restoreSavedRange(savedRange);
  1042 +
  1043 + // Verify that we have focus and the range is restored.
  1044 + assertEquals('Field should be focused',
  1045 + editableField.getElement(),
  1046 + goog.dom.getActiveElement(doc));
  1047 + var newRange = editableField.getRange();
  1048 + assertEquals('Range startNode', textNode, newRange.getStartNode());
  1049 + assertEquals('Range startOffset', 1, newRange.getStartOffset());
  1050 + assertEquals('Range endNode', textNode, newRange.getEndNode());
  1051 + assertEquals('Range endOffset', 2, newRange.getEndOffset());
  1052 +
  1053 +}
  1054 +
  1055 +
1017 1056 function testPlaceCursorAtStart() {
1018 1057 doTestPlaceCursorAtStart();
1019 1058 }
@@ -1062,8 +1101,10 @@ function doTestPlaceCursorAtEnd(opt_html, opt_parentId, opt_offset) {
1062 1101
1063 1102 // We check whether getAttribute exist because textNode may be an actual
1064 1103 // TextNode, which does not have getAttribute.
1065   - if (textNode && textNode.getAttribute &&
1066   - textNode.getAttribute('_moz_editor_bogus_node')) {
  1104 +
  1105 + var hasBogusNode = textNode && textNode.getAttribute &&
  1106 + textNode.getAttribute('_moz_editor_bogus_node');
  1107 + if (hasBogusNode) {
1067 1108 // At least in FF >= 6, assigning '' to innerHTML of a contentEditable
1068 1109 // element will results in textNode being modified into:
1069 1110 // <br _moz_editor_bogus_node="TRUE" _moz_dirty=""> instead of nulling
@@ -1079,8 +1120,13 @@ function doTestPlaceCursorAtEnd(opt_html, opt_parentId, opt_offset) {
1079 1120 var offset = goog.isDefAndNotNull(opt_offset) ?
1080 1121 opt_offset :
1081 1122 textNode ? endNode.nodeValue.length : endNode.childNodes.length - 1;
1082   - assertEquals('The range should end at the ending of the node',
1083   - offset, range.getEndOffset());
  1123 + if (hasBogusNode) {
  1124 + assertEquals('The range should end at the ending of the bogus node ' +
  1125 + 'added by FF', offset + 1, range.getEndOffset());
  1126 + } else {
  1127 + assertEquals('The range should end at the ending of the node',
  1128 + offset, range.getEndOffset());
  1129 + }
1084 1130 }
1085 1131
1086 1132
7 closure/goog/editor/plugins/abstractdialogplugin.js
@@ -240,11 +240,8 @@ goog.editor.plugins.AbstractDialogPlugin.prototype.handleAfterHide = function(
240 240 */
241 241 goog.editor.plugins.AbstractDialogPlugin.prototype.restoreOriginalSelection =
242 242 function() {
243   - if (this.savedRange_) {
244   - this.savedRange_.restore();
245   - this.savedRange_ = null;
246   - }
247   - this.getFieldObject().focus();
  243 + this.getFieldObject().restoreSavedRange(this.savedRange_);
  244 + this.savedRange_ = null;
248 245 };
249 246
250 247
47 closure/goog/editor/range.js
@@ -187,17 +187,7 @@ goog.editor.range.placeCursorNextTo = function(node, toLeft) {
187 187 var offset = goog.array.indexOf(parent.childNodes, node) +
188 188 (toLeft ? 0 : 1);
189 189 var point = goog.editor.range.Point.createDeepestPoint(
190   - parent, offset, toLeft);
191   - // NOTE: It's for fixing bug that selecting HR tag breaks
192   - // the cursor position In IE9. See http://b/6040468.
193   - if (goog.userAgent.IE && goog.userAgent.isVersion('9') &&
194   - point.node.nodeType == goog.dom.NodeType.ELEMENT &&
195   - point.node.tagName == goog.dom.TagName.HR) {
196   - var hr = point.node;
197   - point.node = hr.parentNode;
198   - point.offset = goog.array.indexOf(point.node.childNodes, hr) +
199   - (toLeft ? 0 : 1);
200   - }
  190 + parent, offset, toLeft, true);
201 191 var range = goog.dom.Range.createCaret(point.node, point.offset);
202 192 range.select();
203 193 return range;
@@ -513,25 +503,39 @@ goog.editor.range.Point.prototype.getParentPoint = function() {
513 503 * By default, we trend rightward. If this parameter is true, then we
514 504 * trend leftward. The tendency to fall rightward by default is for
515 505 * consistency with other range APIs (like placeCursorNextTo).
  506 + * @param {boolean=} opt_stopOnChildlessElement If true, and we encounter
  507 + * a Node which is an Element that cannot have children, we return a Point
  508 + * based on its parent rather than that Node itself.
516 509 * @return {goog.editor.range.Point} A new point.
517 510 */
518 511 goog.editor.range.Point.createDeepestPoint =
519   - function(node, offset, opt_trendLeft) {
  512 + function(node, offset, opt_trendLeft, opt_stopOnChildlessElement) {
520 513 while (node.nodeType == goog.dom.NodeType.ELEMENT) {
521 514 var child = node.childNodes[offset];
522 515 if (!child && !node.lastChild) {
523 516 break;
524   - }
525   - if (child) {
  517 + } else if (child) {
526 518 var prevSibling = child.previousSibling;
527 519 if (opt_trendLeft && prevSibling) {
  520 + if (opt_stopOnChildlessElement &&
  521 + goog.editor.range.Point.isTerminalElement_(prevSibling)) {
  522 + break;
  523 + }
528 524 node = prevSibling;
529 525 offset = goog.editor.node.getLength(node);
530 526 } else {
  527 + if (opt_stopOnChildlessElement &&
  528 + goog.editor.range.Point.isTerminalElement_(child)) {
  529 + break;
  530 + }
531 531 node = child;
532 532 offset = 0;
533 533 }
534 534 } else {
  535 + if (opt_stopOnChildlessElement &&
  536 + goog.editor.range.Point.isTerminalElement_(node.lastChild)) {
  537 + break;
  538 + }
535 539 node = node.lastChild;
536 540 offset = goog.editor.node.getLength(node);
537 541 }
@@ -542,6 +546,21 @@ goog.editor.range.Point.createDeepestPoint =
542 546
543 547
544 548 /**
  549 + * Return true if the specified node is an Element that is not expected to have
  550 + * children. The createDeepestPoint() method should not traverse into
  551 + * such elements.
  552 + * @param {Node} node .
  553 + * @return {boolean} True if the node is an Element that does not contain
  554 + * child nodes (e.g. BR, IMG).
  555 + * @private
  556 + */
  557 +goog.editor.range.Point.isTerminalElement_ = function(node) {
  558 + return (node.nodeType == goog.dom.NodeType.ELEMENT &&
  559 + !goog.dom.canHaveChildren(node));
  560 +};
  561 +
  562 +
  563 +/**
545 564 * Construct a point at the very end of the given node.
546 565 * @param {Node} node The node to create a point for.
547 566 * @return {goog.editor.range.Point} A new point.
83 closure/goog/editor/range_test.html
@@ -379,13 +379,9 @@
379 379 assertEquals(2, children.length);
380 380 var node = children[0];
381 381 var range = goog.editor.range.placeCursorNextTo(node, true);
382   - if (goog.userAgent.IE && goog.userAgent.isVersion('9')) {
383   - assertEquals(div, range.getStartNode());
384   - assertEquals(0, range.getStartOffset());
385   - } else {
386   - assertEquals(children[0], range.getStartNode());
387   - assertEquals(0, range.getStartOffset());
388   - }
  382 +
  383 + assertEquals(div, range.getStartNode());
  384 + assertEquals(0, range.getStartOffset());
389 385 }
390 386
391 387 function testPlaceCursorNextTo_rightOfHr() {
@@ -395,13 +391,72 @@
395 391 assertEquals(2, children.length);
396 392 var node = children[1];
397 393 var range = goog.editor.range.placeCursorNextTo(node, false);
398   - if (goog.userAgent.IE && goog.userAgent.isVersion('9')) {
399   - assertEquals(div, range.getStartNode());
400   - assertEquals(1, range.getStartOffset());
401   - } else {
402   - assertEquals(children[1], range.getStartNode());
403   - assertEquals(0, range.getStartOffset());
404   - }
  394 +
  395 + assertEquals(div, range.getStartNode());
  396 + assertEquals(2, range.getStartOffset());
  397 +}
  398 +
  399 +function testPlaceCursorNextTo_rightOfImg() {
  400 + var div = $('parentNode');
  401 + div.innerHTML =
  402 + 'aaa<img src="https://www.google.com/images/srpr/logo3w.png">bbb';
  403 + var children = div.childNodes;
  404 + assertEquals(3, children.length);
  405 + var imgNode = children[1];
  406 + var range = goog.editor.range.placeCursorNextTo(imgNode, false);
  407 +
  408 + assertEquals('range node should be the right sibling of img tag',
  409 + children[2], range.getStartNode());
  410 + assertEquals(0, range.getStartOffset());
  411 +
  412 +}
  413 +
  414 +function testPlaceCursorNextTo_rightOfImgAtEnd() {
  415 + var div = $('parentNode');
  416 + div.innerHTML =
  417 + 'aaa<img src="https://www.google.com/images/srpr/logo3w.png">';
  418 + var children = div.childNodes;
  419 + assertEquals(2, children.length);
  420 + var imgNode = children[1];
  421 + var range = goog.editor.range.placeCursorNextTo(imgNode, false);
  422 +
  423 + assertEquals('range node should be the parent of img',
  424 + div, range.getStartNode());
  425 + assertEquals('offset should be right after the img tag',
  426 + 2, range.getStartOffset());
  427 +
  428 +}
  429 +
  430 +function testPlaceCursorNextTo_leftOfImg() {
  431 + var div = $('parentNode');
  432 + div.innerHTML =
  433 + '<img src="https://www.google.com/images/srpr/logo3w.png">xxx';
  434 + var children = div.childNodes;
  435 + assertEquals(2, children.length);
  436 + var imgNode = children[0];
  437 + var range = goog.editor.range.placeCursorNextTo(imgNode, true);
  438 +
  439 + assertEquals('range node should be the parent of img',
  440 + div, range.getStartNode());
  441 + assertEquals('offset should point to the img tag',
  442 + 0, range.getStartOffset());
  443 +}
  444 +
  445 +function testPlaceCursorNextTo_rightOfFirstOfTwoImgTags() {
  446 + var div = $('parentNode');
  447 + div.innerHTML =
  448 + 'aaa<img src="https://www.google.com/images/srpr/logo3w.png">' +
  449 + '<img src="https://www.google.com/images/srpr/logo3w.png">';
  450 + var children = div.childNodes;
  451 + assertEquals(3, children.length);
  452 + var imgNode = children[1]; // First of two IMG nodes
  453 + var range = goog.editor.range.placeCursorNextTo(imgNode, false);
  454 +
  455 + assertEquals('range node should be the parent of img instead of ' +
  456 + 'node with innerHTML=' + range.getStartNode().innerHTML,
  457 + div, range.getStartNode());
  458 + assertEquals('offset should be right after the img tag',
  459 + 2, range.getStartOffset());
405 460 }
406 461
407 462 function testGetDeepEndPoint() {
9 closure/goog/testing/editor/fieldmock.js
@@ -24,6 +24,7 @@ goog.require('goog.dom');
24 24 goog.require('goog.dom.Range');
25 25 goog.require('goog.editor.Field');
26 26 goog.require('goog.testing.LooseMock');
  27 +goog.require('goog.testing.mockmatchers');
27 28
28 29
29 30
@@ -68,6 +69,14 @@ goog.testing.editor.FieldMock =
68 69 this.$anyTimes();
69 70 this.$returns(0);
70 71
  72 + this.restoreSavedRange(goog.testing.mockmatchers.ignoreArgument);
  73 + this.$anyTimes();
  74 + this.$does(function(range) {
  75 + if (range) {
  76 + range.restore();
  77 + }
  78 + this.focus();
  79 + });
71 80
72 81 // These methods cannot be set on the prototype, because the prototype
73 82 // gets stepped on by the mock framework.

0 comments on commit bd2a9d2

Please sign in to comment.
Something went wrong with that request. Please try again.