Esanders has uploaded a new change for review.
https://gerrit.wikimedia.org/r/169696
Change subject: Replace native range rectangle hacks with RangeFix library
......................................................................
Replace native range rectangle hacks with RangeFix library
Change-Id: I7f6cb6cbdd2b4385fb8ca95c03795cbdd929a860
---
M .docs/eg-iframe.html
M .jshintrc
M build/modules.json
M demos/ve/desktop.html
M demos/ve/mobile.html
A lib/rangefix/rangefix.js
M src/ce/ve.ce.Surface.js
M tests/index.html
8 files changed, 174 insertions(+), 81 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/VisualEditor/VisualEditor
refs/changes/96/169696/1
diff --git a/.docs/eg-iframe.html b/.docs/eg-iframe.html
index a1caf86..484566b 100644
--- a/.docs/eg-iframe.html
+++ b/.docs/eg-iframe.html
@@ -95,6 +95,9 @@
<!-- unicodejs -->
<script src="../lib/unicodejs/unicodejs.js"></script>
+ <!-- rangefix -->
+ <script src="../lib/rangefix/rangefix.js"></script>
+
<!-- visualEditor.base.build -->
<script src="../src/ve.js"></script>
<script src="../src/ve.track.js"></script>
diff --git a/.jshintrc b/.jshintrc
index 88b70cd..d8ff63f 100644
--- a/.jshintrc
+++ b/.jshintrc
@@ -21,6 +21,7 @@
"OO": false,
"QUnit": false,
"unicodeJS": false,
+ "RangeFix": false,
"ve": false
}
}
diff --git a/build/modules.json b/build/modules.json
index 902118e..9f77435 100644
--- a/build/modules.json
+++ b/build/modules.json
@@ -62,6 +62,11 @@
"lib/unicodejs/unicodejs.js"
]
},
+ "rangefix": {
+ "scripts": [
+ "lib/rangefix/rangefix.js"
+ ]
+ },
"jquery.i18n": {
"scripts": [
"lib/jquery.i18n/src/jquery.i18n.js",
@@ -123,7 +128,8 @@
"src/init/ve.init.Target.js"
],
"dependencies": [
- "unicodejs"
+ "unicodejs",
+ "rangefix"
]
},
"visualEditor.core": {
diff --git a/demos/ve/desktop.html b/demos/ve/desktop.html
index c2dbdbb..503b6aa 100644
--- a/demos/ve/desktop.html
+++ b/demos/ve/desktop.html
@@ -105,6 +105,9 @@
<!-- unicodejs -->
<script src="../../lib/unicodejs/unicodejs.js"></script>
+ <!-- rangefix -->
+ <script src="../../lib/rangefix/rangefix.js"></script>
+
<!-- visualEditor.base.build -->
<script src="../../src/ve.js"></script>
<script src="../../src/ve.debug.js"></script>
diff --git a/demos/ve/mobile.html b/demos/ve/mobile.html
index e2ddd95..b4c6f6f 100644
--- a/demos/ve/mobile.html
+++ b/demos/ve/mobile.html
@@ -106,6 +106,9 @@
<!-- unicodejs -->
<script src="../../lib/unicodejs/unicodejs.js"></script>
+ <!-- rangefix -->
+ <script src="../../lib/rangefix/rangefix.js"></script>
+
<!-- visualEditor.base.build -->
<script src="../../src/ve.js"></script>
<script src="../../src/ve.debug.js"></script>
diff --git a/lib/rangefix/rangefix.js b/lib/rangefix/rangefix.js
new file mode 100644
index 0000000..1b0c5fb
--- /dev/null
+++ b/lib/rangefix/rangefix.js
@@ -0,0 +1,144 @@
+/*!
+ * RangeFix v0.1.0
+ * https://github.com/edg2s/range-get-client-rects
+ *
+ * Copyright 2014 Ed Sanders.
+ * Released under the MIT license
+ */
+( function () {
+
+ var isBroken,
+ rangeFix = {};
+
+ /**
+ * Check if the bug is present in the native function
+ *
+ * Constructs two lines of text and creates a range between them.
+ * Broken browsers will return three rectangles instead of two.
+ *
+ * @private
+ * @return {booean} The bug is present
+ */
+ function isGetClientRectsBroken() {
+ if ( isBroken === undefined ) {
+ var p1 = document.createElement( 'p' ),
+ p2 = document.createElement( 'p' ),
+ t1 = document.createTextNode( 'aa' ),
+ t2 = document.createTextNode( 'aa' ),
+ range = document.createRange();
+
+ p1.appendChild( t1 );
+ p2.appendChild( t2 );
+
+ document.body.appendChild( p1 );
+ document.body.appendChild( p2 );
+
+ range.setStart( t1, 1 );
+ range.setEnd( t2, 1 );
+ isBroken = range.getClientRects().length > 2;
+
+ document.body.removeChild( p1 );
+ document.body.removeChild( p2 );
+ }
+ return isBroken;
+ }
+
+ /**
+ * Get client rectangles from a range
+ *
+ * @param {Range} range Range
+ * @return {ClientRectList|ClientRect[]} ClientRectList or list of
ClientRect objects describing range
+ */
+ rangeFix.getClientRects = function ( range ) {
+ if ( !isGetClientRectsBroken() ) {
+ return range.getClientRects();
+ }
+
+ // Chrome gets the end container rects wrong when spanning
+ // nodes so we need to traverse up the tree from the
endContainer until
+ // we reach the common ancestor, then we can add on from start
to where
+ // we got up to
+ // https://code.google.com/p/chromium/issues/detail?id=324437
+ var rects = [],
+ endContainer = range.endContainer,
+ endOffset = range.endOffset,
+ partialRange = document.createRange();
+
+ while ( endContainer !== range.commonAncestorContainer ) {
+ partialRange.setStart( endContainer, 0 );
+ partialRange.setEnd( endContainer, endOffset );
+
+ Array.prototype.push.apply( rects,
partialRange.getClientRects() );
+
+ endOffset = Array.prototype.indexOf.call(
endContainer.parentNode.childNodes, endContainer );
+ endContainer = endContainer.parentNode;
+ }
+
+ // Once we've reached the common ancestor, add on the range
from the
+ // original start position to where we ended up.
+ partialRange = range.cloneRange();
+ partialRange.setEnd( endContainer, endOffset );
+ Array.prototype.push.apply( rects,
partialRange.getClientRects() );
+ return rects;
+ };
+
+ /**
+ * Get bounding rectangle from a range
+ *
+ * @param {Range} range Range
+ * @return {ClientRect|Object|null} ClientRect or ClientRect-like
object describing
+ * bounding rectangle, or null if not
computable
+ */
+ rangeFix.getBoundingClientRect = function ( range ) {
+ var i, l, boundingRect,
+ rects = this.getClientRects( range ),
+ nativeBoundingRect = range.getBoundingClientRect();
+
+ if ( rects.length === 0 ) {
+ // If there are no rects return null, otherwise we'll
fall through to
+ // getBoundingClientRect, which in Chrome becomes
[0,0,0,0].
+ return null;
+ }
+
+ if ( !isGetClientRectsBroken() ) {
+ return range.getBoundingClientRect();
+ }
+
+ // When nativeRange is a collapsed cursor at the end of a line
or
+ // the start of a line, the bounding rect is [0,0,0,0] in
Chrome.
+ // getClientRects returns two rects, one correct, and one at the
+ // end of the next line / start of the previous line. We can't
tell
+ // here which one to use so just pick the first. This matches
+ // Firefox's behaviour, which tells you the cursor is at the end
+ // of the previous line when it is at the start of the line.
+ // See
https://code.google.com/p/chromium/issues/detail?id=426017
+ if ( nativeBoundingRect.width === 0 &&
nativeBoundingRect.height === 0 ) {
+ return rects[0];
+ }
+
+ for ( i = 0, l = rects.length; i < l; i++ ) {
+ if ( !boundingRect ) {
+ boundingRect = {
+ left: rects[i].left,
+ top: rects[i].top,
+ right: rects[i].right,
+ bottom: rects[i].bottom
+ };
+ } else {
+ boundingRect.left = Math.min(
boundingRect.left, rects[i].left );
+ boundingRect.top = Math.min( boundingRect.top,
rects[i].top );
+ boundingRect.right = Math.max(
boundingRect.right, rects[i].right );
+ boundingRect.bottom = Math.max(
boundingRect.bottom, rects[i].bottom );
+ }
+ }
+ if ( boundingRect ) {
+ boundingRect.width = boundingRect.right -
boundingRect.left;
+ boundingRect.height = boundingRect.bottom -
boundingRect.top;
+ }
+ return boundingRect;
+ };
+
+ // Expose
+ window.RangeFix = rangeFix;
+
+} )();
diff --git a/src/ce/ve.ce.Surface.js b/src/ce/ve.ce.Surface.js
index bd092f3..95fc13e 100644
--- a/src/ce/ve.ce.Surface.js
+++ b/src/ce/ve.ce.Surface.js
@@ -377,7 +377,6 @@
*/
ve.ce.Surface.prototype.getSelectionRects = function ( selection ) {
var i, l, range, nativeRange, surfaceRect, focusedNode,
- endContainer, endOffset, partialRange,
rects = [],
relativeRects = [];
@@ -402,31 +401,7 @@
// * in Firefox on page load when the address bar is still focused
// * in empty paragraphs
try {
- // This block should just be rects =
nativeRange.getClientRects(), but
- // Chrome gets the end container rects wrong when spanning
- // nodes so we need to traverse up the tree from the
endContainer until
- // we reach the common ancestor, then we can add on from start
to where
- // we got up to
- // https://code.google.com/p/chromium/issues/detail?id=324437
- endContainer = nativeRange.endContainer;
- endOffset = nativeRange.endOffset;
- partialRange = document.createRange();
-
- while ( endContainer !== nativeRange.commonAncestorContainer ) {
- partialRange.setStart( endContainer, 0 );
- partialRange.setEnd( endContainer, endOffset );
-
- rects = rects.concat( $.makeArray(
partialRange.getClientRects() ) );
-
- endOffset = ve.indexOf( endContainer,
endContainer.parentNode.childNodes );
- endContainer = endContainer.parentNode;
- }
-
- // Once we've reached the common ancestor, add on the range
from the
- // original start position to where we ended up.
- partialRange = nativeRange.cloneRange();
- partialRange.setEnd( endContainer, endOffset );
- rects = rects.concat( $.makeArray(
partialRange.getClientRects() ) );
+ rects = RangeFix.getClientRects( nativeRange );
if ( !rects.length ) {
throw new Error( 'getClientRects returned empty list' );
}
@@ -499,7 +474,15 @@
return null;
}
- boundingRect = this.getNativeRangeBoundingClientRect( nativeRange ) ||
this.getClientRectFromNode();
+ try {
+ boundingRect = RangeFix.getBoundingClientRect( nativeRange );
+ if ( !boundingRect ) {
+ throw new Error( 'getBoundingClientRect returned null'
);
+ }
+ } catch ( e ) {
+ boundingRect = this.getClientRectFromNode();
+ }
+
surfaceRect = this.getSurface().getBoundingClientRect();
if ( !boundingRect || !surfaceRect ) {
return null;
@@ -2805,59 +2788,6 @@
nativeRange.setEnd( rangeSelection.end.node,
rangeSelection.end.offset );
}
return nativeRange;
-};
-
-/**
- * Get bounding client rect of a native range
- *
- * Works around lots of browser bugs in Range#getBoundingClientRect
- *
- * @param {Range} nativeRange Native range to get the bounding client rect of
- * @return {ClientRect|null} Client rectangle of the native selection, or null
if there was a problem
- */
-ve.ce.Surface.prototype.getNativeRangeBoundingClientRect = function (
nativeRange ) {
- var rects, boundingRect;
-
- if ( !nativeRange ) {
- return null;
- }
-
- try {
- rects = nativeRange.getClientRects();
- if ( rects.length === 0 ) {
- // If there are no rects return null, otherwise we'll
fall through to
- // getBoundingClientRect, which in Chrome becomes
[0,0,0,0].
- return null;
- } else if ( rects.length === 1 ) {
- // Try the zeroth rect first as Chrome sometimes
returns a rectangle
- // full of zeros for getBoundingClientRect when the
cursor is collapsed.
- // We could test for this failure and fall back to
rects[0], except for the
- // fact that the bounding rect is 1px bigger than
rects[0], so cursoring across
- // a link causes a verticle wobble as it alternately
breaks and unbreaks.
- // See
https://code.google.com/p/chromium/issues/detail?id=238976
- return rects[0];
- } else {
- boundingRect = nativeRange.getBoundingClientRect();
- if ( boundingRect.width === 0 && boundingRect.height
=== 0 ) {
- // ... and we save the best bug until last:
- // When nativeRange is a collapsed cursor at
the end of a line or
- // the start of a line, the bounding rect is
[0,0,0,0] in Chrome.
- // getClientRects returns two rects, one
correct, and one at the
- // end of the next line / start of the previous
line. We can't tell
- // here which one to use so just pick the
first. This matches
- // Firefox's behaviour, which tells you the
cursor is at the end
- // of the previous line when it is at the start
of the line.
- // See
https://code.google.com/p/chromium/issues/detail?id=426017
- return rects[0];
- } else {
- // After three browser bugs it's finally safe
to try the bounding rect.
- return boundingRect;
- }
- }
- } catch ( e ) {
- return null;
- }
-
};
/**
diff --git a/tests/index.html b/tests/index.html
index 3e8c942..42e0262 100644
--- a/tests/index.html
+++ b/tests/index.html
@@ -60,6 +60,9 @@
<!-- unicodejs -->
<script src="../lib/unicodejs/unicodejs.js"></script>
+ <!-- rangefix -->
+ <script src="../lib/rangefix/rangefix.js"></script>
+
<!-- visualEditor.base.build -->
<script src="../src/ve.js"></script>
<script src="../src/ve.track.js"></script>
--
To view, visit https://gerrit.wikimedia.org/r/169696
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I7f6cb6cbdd2b4385fb8ca95c03795cbdd929a860
Gerrit-PatchSet: 1
Gerrit-Project: VisualEditor/VisualEditor
Gerrit-Branch: master
Gerrit-Owner: Esanders <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits