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

Reply via email to