From 2bad4d921c6995217a1c104322ec161f80f758bb Mon Sep 17 00:00:00 2001 From: Dan Vanderkam Date: Thu, 16 Aug 2012 15:22:21 -0400 Subject: [PATCH] Fix Issue 361: Moving the mouse over an iframe breaks range selector interaction --- dygraph-range-selector.js | 6 ++++ dygraph-utils.js | 65 ++++++++++++++++++++++++++++++++++++++++++ dygraph.js | 7 +++++ tests/iframe.html | 72 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 150 insertions(+) create mode 100644 tests/iframe.html diff --git a/dygraph-range-selector.js b/dygraph-range-selector.js index 0342ea7..e2973b0 100644 --- a/dygraph-range-selector.js +++ b/dygraph-range-selector.js @@ -179,6 +179,10 @@ DygraphRangeSelector.prototype.initInteraction_ = function() { var isPanning = false; var dynamic = !this.isMobileDevice_ && !this.isUsingExcanvas_; + // We cover iframes during mouse interactions. See comments in + // dygraph-utils.js for more info on why this is a good idea. + var tarp = new Dygraph.IFrameTarp(); + // functions, defined below. Defining them this way (rather than with // "function foo() {...}" makes JSHint happy. var toXDataWindow, onZoomStart, onZoom, onZoomEnd, doZoom, isMouseInPanZone, @@ -212,6 +216,7 @@ DygraphRangeSelector.prototype.initInteraction_ = function() { self.dygraph_.addEvent(topElem, 'mousemove', onZoom); self.dygraph_.addEvent(topElem, 'mouseup', onZoomEnd); self.fgcanvas_.style.cursor = 'col-resize'; + tarp.cover(); return true; }; @@ -256,6 +261,7 @@ DygraphRangeSelector.prototype.initInteraction_ = function() { return false; } isZooming = false; + tarp.uncover(); Dygraph.removeEvent(topElem, 'mousemove', onZoom); Dygraph.removeEvent(topElem, 'mouseup', onZoomEnd); self.fgcanvas_.style.cursor = 'default'; diff --git a/dygraph-utils.js b/dygraph-utils.js index dd8c620..707bbca 100644 --- a/dygraph-utils.js +++ b/dygraph-utils.js @@ -1015,3 +1015,68 @@ Dygraph.Circles = { ctx.stroke(); } }; + +/** + * To create a "drag" interaction, you typically register a mousedown event + * handler on the element where the drag begins. In that handler, you register a + * mouseup handler on the window to determine when the mouse is released, + * wherever that release happens. This works well, except when the user releases + * the mouse over an off-domain iframe. In that case, the mouseup event is + * handled by the iframe and never bubbles up to the window handler. + * + * To deal with this issue, we cover iframes with high z-index divs to make sure + * they don't capture mouseup. + * + * Usage: + * element.addEventListener('mousedown', function() { + * var tarper = new Dygraph.IFrameTarp(); + * tarper.cover(); + * var mouseUpHandler = function() { + * ... + * window.removeEventListener(mouseUpHandler); + * tarper.uncover(); + * }; + * window.addEventListener('mouseup', mouseUpHandler); + * }; + * + * + * @constructor + */ +Dygraph.IFrameTarp = function() { + this.tarps = []; +}; + +/** + * Find all the iframes in the document and cover them with high z-index + * transparent divs. + */ +Dygraph.IFrameTarp.prototype.cover = function() { + var iframes = document.getElementsByTagName("iframe"); + for (var i = 0; i < iframes.length; i++) { + var iframe = iframes[i]; + var x = Dygraph.findPosX(iframe), + y = Dygraph.findPosY(iframe), + width = iframe.offsetWidth, + height = iframe.offsetHeight; + + var div = document.createElement("div"); + div.style.position = "absolute"; + div.style.left = x + 'px'; + div.style.top = y + 'px'; + div.style.width = width + 'px'; + div.style.height = height + 'px'; + div.style.zIndex = 999; + document.body.appendChild(div); + this.tarps.push(div); + } +}; + +/** + * Remove all the iframe covers. You should call this in a mouseup handler. + */ +Dygraph.IFrameTarp.prototype.uncover = function() { + for (var i = 0; i < this.tarps.length; i++) { + this.tarps[i].parentNode.removeChild(this.tarps[i]); + } + this.tarps = []; +}; diff --git a/dygraph.js b/dygraph.js index 64ca4b6..c1764a1 100644 --- a/dygraph.js +++ b/dygraph.js @@ -1247,6 +1247,10 @@ Dygraph.prototype.createDragInterface_ = function() { boundedDates: null, // [minDate, maxDate] boundedValues: null, // [[minValue, maxValue] ...] + // We cover iframes during mouse interactions. See comments in + // dygraph-utils.js for more info on why this is a good idea. + tarp: new Dygraph.IFrameTarp(), + // contextB is the same thing as this context object but renamed. initializeMouseDown: function(event, g, contextB) { // prevents mouse drags from selecting page text. @@ -1262,6 +1266,7 @@ Dygraph.prototype.createDragInterface_ = function() { contextB.dragStartX = g.dragGetX_(event, contextB); contextB.dragStartY = g.dragGetY_(event, contextB); contextB.cancelNextDblclick = false; + contextB.tarp.cover(); } }; @@ -1301,6 +1306,8 @@ Dygraph.prototype.createDragInterface_ = function() { delete self.axes_[i].dragValueRange; } } + + context.tarp.uncover(); }; this.addEvent(window, 'mouseup', this.mouseUpHandler_); diff --git a/tests/iframe.html b/tests/iframe.html new file mode 100644 index 0000000..7dbcf6f --- /dev/null +++ b/tests/iframe.html @@ -0,0 +1,72 @@ + + + + + demo + + + + + + +
+
+ + +

Click on various places in the chart, then drag the mouse over the iframe + and release it. When you mouse back over the charts, strange things will + happen.

+ + + + -- 2.7.4