From a199335790aec50cf3ed7cc27c5b407875c37107 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Wed, 17 Dec 2014 10:15:54 +0000 Subject: Use the DOM rather than XPath to detect clickable elements --- lib/dom_utils.coffee | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) (limited to 'lib') diff --git a/lib/dom_utils.coffee b/lib/dom_utils.coffee index a0ac0bd3..26fa9b81 100644 --- a/lib/dom_utils.coffee +++ b/lib/dom_utils.coffee @@ -41,6 +41,42 @@ DomUtils = if (namespace == "xhtml") then "http://www.w3.org/1999/xhtml" else null document.evaluate(xpath, document.documentElement, namespaceResolver, resultType, null) + # + # Returns all the clickable element children of contextNode. This also can include contextNode itself. + # + getClickableElements: (contextNode = document.documentElement) -> + elements = Array::slice.call(contextNode?.getElementsByTagName "*") + elements.unshift contextNode # Check the contextNode as well. + clickableElements = [] + for element in elements + isClickable = false + tagName = element.tagName.toLowerCase() + isClickable = (-> + if element.hasAttribute "onclick" + true + else if element.hasAttribute "tabindex" + true + else if element.getAttribute "role" in ["button", "link"] + true + else if element.getAttribute("class")?.toLowerCase().indexOf("button") >= 0 + true + else if element.getAttribute("contentEditable")?.toLowerCase() in ["", "contentEditable", "true"] + true + else if tagName == "a" + true + else if tagName == "area" + element.hasAttribute "href" + else if (tagName == "input" and DomUtils.isSelectable element) or tagName == "textarea" + not (element.disabled or element.hasAttribute "readonly") + else if (tagName == "input" and element.getAttribute("type")?.toLowerCase() != "hidden") or + tagName in ["button", "select"] + not element.disabled + else + false + )() + clickableElements.push element if isClickable + clickableElements + # # Returns the first visible clientRect of an element if it exists. Otherwise it returns null. # -- cgit v1.2.3 From c80ad2c367f873f2b2547b60cebe49715a85ffe4 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Wed, 17 Dec 2014 10:40:02 +0000 Subject: Treat area elements as being at the point of their img element --- lib/dom_utils.coffee | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/dom_utils.coffee b/lib/dom_utils.coffee index 26fa9b81..152a378e 100644 --- a/lib/dom_utils.coffee +++ b/lib/dom_utils.coffee @@ -64,8 +64,13 @@ DomUtils = true else if tagName == "a" true - else if tagName == "area" - element.hasAttribute "href" + else if tagName == "img" + mapName = element.getAttribute "usemap" + if mapName + map = document.querySelector(mapName.replace /^#/, "") + areas = Array::slice.call(map.getElementsByTagName "area") + elements.concat areas + false else if (tagName == "input" and DomUtils.isSelectable element) or tagName == "textarea" not (element.disabled or element.hasAttribute "readonly") else if (tagName == "input" and element.getAttribute("type")?.toLowerCase() != "hidden") or -- cgit v1.2.3 From 8c8ec835d673f0ec1cce242cf26cca077c845064 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Wed, 17 Dec 2014 11:08:07 +0000 Subject: Use element.readOnly instead of getAttribute "readonly" --- lib/dom_utils.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/dom_utils.coffee b/lib/dom_utils.coffee index 152a378e..46bf3639 100644 --- a/lib/dom_utils.coffee +++ b/lib/dom_utils.coffee @@ -72,7 +72,7 @@ DomUtils = elements.concat areas false else if (tagName == "input" and DomUtils.isSelectable element) or tagName == "textarea" - not (element.disabled or element.hasAttribute "readonly") + not (element.disabled or element.readOnly) else if (tagName == "input" and element.getAttribute("type")?.toLowerCase() != "hidden") or tagName in ["button", "select"] not element.disabled -- cgit v1.2.3 From c7e2f1cdef2d5e99761d7bb8ecbad91f89de6958 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Wed, 17 Dec 2014 11:12:58 +0000 Subject: Inline DomUtils.getClickableElements --- lib/dom_utils.coffee | 41 ----------------------------------------- 1 file changed, 41 deletions(-) (limited to 'lib') diff --git a/lib/dom_utils.coffee b/lib/dom_utils.coffee index 46bf3639..a0ac0bd3 100644 --- a/lib/dom_utils.coffee +++ b/lib/dom_utils.coffee @@ -41,47 +41,6 @@ DomUtils = if (namespace == "xhtml") then "http://www.w3.org/1999/xhtml" else null document.evaluate(xpath, document.documentElement, namespaceResolver, resultType, null) - # - # Returns all the clickable element children of contextNode. This also can include contextNode itself. - # - getClickableElements: (contextNode = document.documentElement) -> - elements = Array::slice.call(contextNode?.getElementsByTagName "*") - elements.unshift contextNode # Check the contextNode as well. - clickableElements = [] - for element in elements - isClickable = false - tagName = element.tagName.toLowerCase() - isClickable = (-> - if element.hasAttribute "onclick" - true - else if element.hasAttribute "tabindex" - true - else if element.getAttribute "role" in ["button", "link"] - true - else if element.getAttribute("class")?.toLowerCase().indexOf("button") >= 0 - true - else if element.getAttribute("contentEditable")?.toLowerCase() in ["", "contentEditable", "true"] - true - else if tagName == "a" - true - else if tagName == "img" - mapName = element.getAttribute "usemap" - if mapName - map = document.querySelector(mapName.replace /^#/, "") - areas = Array::slice.call(map.getElementsByTagName "area") - elements.concat areas - false - else if (tagName == "input" and DomUtils.isSelectable element) or tagName == "textarea" - not (element.disabled or element.readOnly) - else if (tagName == "input" and element.getAttribute("type")?.toLowerCase() != "hidden") or - tagName in ["button", "select"] - not element.disabled - else - false - )() - clickableElements.push element if isClickable - clickableElements - # # Returns the first visible clientRect of an element if it exists. Otherwise it returns null. # -- cgit v1.2.3 From 5f9290693ab0f35c46cea6cea0a9f5c06b4ee0ad Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Wed, 17 Dec 2014 12:27:40 +0000 Subject: Combine rectangle calculation and clickable element detection --- lib/dom_utils.coffee | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) (limited to 'lib') diff --git a/lib/dom_utils.coffee b/lib/dom_utils.coffee index a0ac0bd3..3d7e805f 100644 --- a/lib/dom_utils.coffee +++ b/lib/dom_utils.coffee @@ -90,6 +90,34 @@ DomUtils = return childClientRect null + getClientRectsForAreas: (imgClientRect, areas) -> + rects = [] + for area in areas + coords = area.coords.split(",").map((coord) -> parseInt(coord, 10)) + shape = area.shape.toLowerCase() + if shape == "rect" + [x1, y1, x2, y2] = coords + else if shape == "circle" + [x, y, r] = coords + x1 = x - r + x2 = x + r + y1 = y - r + y2 = y + r + else # For polygons and unknown shapes, don't return a rectangle. + # TODO(mrmr1993): revisit this. + continue + + rect = + top: imgClientRect.top + y1 + left: imgClientRect.left + x1 + right: imgClientRect.left + x2 + bottom: imgClientRect.top + y2 + width: x2 - x1 + height: y2 - y1 + + rects.push {element: area, rect: rect} unless isNaN rect.top + rects + # # Selectable means that we should use the simulateSelect method to activate the element instead of a click. # -- cgit v1.2.3 From 62686e83d690919f00afe1ac7f5955cecb1d2b2f Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Wed, 17 Dec 2014 12:31:09 +0000 Subject: Try to make image map rectangles work better --- lib/dom_utils.coffee | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'lib') diff --git a/lib/dom_utils.coffee b/lib/dom_utils.coffee index 3d7e805f..842dda0f 100644 --- a/lib/dom_utils.coffee +++ b/lib/dom_utils.coffee @@ -95,17 +95,18 @@ DomUtils = for area in areas coords = area.coords.split(",").map((coord) -> parseInt(coord, 10)) shape = area.shape.toLowerCase() - if shape == "rect" + if shape == "rect" or coords.length == 4 [x1, y1, x2, y2] = coords - else if shape == "circle" + else if shape == "circle" or coords.length == 3 [x, y, r] = coords x1 = x - r x2 = x + r y1 = y - r y2 = y + r - else # For polygons and unknown shapes, don't return a rectangle. - # TODO(mrmr1993): revisit this. - continue + else + # Just consider the rectangle surrounding the first two points in a polygon. It's possible to do + # something more sophisticated, but likely not worth the effort. + [x1, y1, x2, y2] = coords rect = top: imgClientRect.top + y1 -- cgit v1.2.3 From c2ab9aaa27b5fbf1f065743772c6f04dd3c5f39d Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Wed, 17 Dec 2014 12:39:22 +0000 Subject: Don't show link hints for offscreen image maps --- lib/dom_utils.coffee | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) (limited to 'lib') diff --git a/lib/dom_utils.coffee b/lib/dom_utils.coffee index 842dda0f..8ade58bb 100644 --- a/lib/dom_utils.coffee +++ b/lib/dom_utils.coffee @@ -52,18 +52,9 @@ DomUtils = } for clientRect in element.getClientRects()) for clientRect in clientRects - if (clientRect.top < 0) - clientRect.height += clientRect.top - clientRect.top = 0 + clientRect = @cropRectToVisible clientRect - if (clientRect.left < 0) - clientRect.width += clientRect.left - clientRect.left = 0 - - if (clientRect.top >= window.innerHeight - 4 || clientRect.left >= window.innerWidth - 4) - continue - - if (clientRect.width < 3 || clientRect.height < 3) + if (!clientRect || clientRect.width < 3 || clientRect.height < 3) continue # eliminate invisible elements (see test_harnesses/visibility_test.html) @@ -90,6 +81,21 @@ DomUtils = return childClientRect null + cropRectToVisible: (rect) -> + if (rect.top < 0) + rect.height += rect.top + rect.top = 0 + + if (rect.left < 0) + rect.width += rect.left + rect.left = 0 + + if (rect.top >= window.innerHeight - 4 || rect.left >= window.innerWidth - 4) + null + else + rect + + getClientRectsForAreas: (imgClientRect, areas) -> rects = [] for area in areas @@ -108,7 +114,7 @@ DomUtils = # something more sophisticated, but likely not worth the effort. [x1, y1, x2, y2] = coords - rect = + rect = @cropRectToVisible top: imgClientRect.top + y1 left: imgClientRect.left + x1 right: imgClientRect.left + x2 @@ -116,7 +122,7 @@ DomUtils = width: x2 - x1 height: y2 - y1 - rects.push {element: area, rect: rect} unless isNaN rect.top + rects.push {element: area, rect: rect} unless not rect or isNaN rect.top rects # -- cgit v1.2.3 From 833942ae06f680bc1949a7bced4719b707950568 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Wed, 17 Dec 2014 12:47:38 +0000 Subject: Stop ignoring clickable opacity: none; elements Some websites (notably Facebook) use `opacity: none;` to show an image in the place of a less-customisable element (eg. an ``). To not show link hints for such transparent elements is confusing and often the wrong thing to do. --- lib/dom_utils.coffee | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/dom_utils.coffee b/lib/dom_utils.coffee index 8ade58bb..1e2cc812 100644 --- a/lib/dom_utils.coffee +++ b/lib/dom_utils.coffee @@ -60,8 +60,7 @@ DomUtils = # eliminate invisible elements (see test_harnesses/visibility_test.html) computedStyle = window.getComputedStyle(element, null) if (computedStyle.getPropertyValue('visibility') != 'visible' || - computedStyle.getPropertyValue('display') == 'none' || - computedStyle.getPropertyValue('opacity') == '0') + computedStyle.getPropertyValue('display') == 'none') continue return clientRect -- cgit v1.2.3 From 158b3f09fd222b0e93510dc17521833de73bcf88 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Wed, 17 Dec 2014 13:49:29 +0000 Subject: Unify two loops into one --- lib/dom_utils.coffee | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) (limited to 'lib') diff --git a/lib/dom_utils.coffee b/lib/dom_utils.coffee index 1e2cc812..aaa93923 100644 --- a/lib/dom_utils.coffee +++ b/lib/dom_utils.coffee @@ -51,20 +51,6 @@ DomUtils = width: clientRect.width, height: clientRect.height } for clientRect in element.getClientRects()) - for clientRect in clientRects - clientRect = @cropRectToVisible clientRect - - if (!clientRect || clientRect.width < 3 || clientRect.height < 3) - continue - - # eliminate invisible elements (see test_harnesses/visibility_test.html) - computedStyle = window.getComputedStyle(element, null) - if (computedStyle.getPropertyValue('visibility') != 'visible' || - computedStyle.getPropertyValue('display') == 'none') - continue - - return clientRect - for clientRect in clientRects # If the link has zero dimensions, it may be wrapping visible # but floated elements. Check for this. @@ -78,6 +64,21 @@ DomUtils = childClientRect = @getVisibleClientRect(child) continue if (childClientRect == null) return childClientRect + + else + clientRect = @cropRectToVisible clientRect + + if (!clientRect || clientRect.width < 3 || clientRect.height < 3) + continue + + # eliminate invisible elements (see test_harnesses/visibility_test.html) + computedStyle = window.getComputedStyle(element, null) + if (computedStyle.getPropertyValue('visibility') != 'visible' || + computedStyle.getPropertyValue('display') == 'none') + continue + + return clientRect + null cropRectToVisible: (rect) -> -- cgit v1.2.3 From 855e9a4e19ab0926f5531c37272f00a715f45ed8 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Thu, 18 Dec 2014 10:33:09 +0000 Subject: Remove overlapping rects from link hints --- lib/dom_utils.coffee | 9 ++------ lib/utils.coffee | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 7 deletions(-) (limited to 'lib') diff --git a/lib/dom_utils.coffee b/lib/dom_utils.coffee index aaa93923..7e19a7fc 100644 --- a/lib/dom_utils.coffee +++ b/lib/dom_utils.coffee @@ -114,13 +114,8 @@ DomUtils = # something more sophisticated, but likely not worth the effort. [x1, y1, x2, y2] = coords - rect = @cropRectToVisible - top: imgClientRect.top + y1 - left: imgClientRect.left + x1 - right: imgClientRect.left + x2 - bottom: imgClientRect.top + y2 - width: x2 - x1 - height: y2 - y1 + rect = Utils.shiftRect (Utils.createRect x1, y1, x2, y2), imgClientRect.left, imgClientRect.top + rect = @cropRectToVisible rect rects.push {element: area, rect: rect} unless not rect or isNaN rect.top rects diff --git a/lib/utils.coffee b/lib/utils.coffee index b7f8731a..6cc45f32 100644 --- a/lib/utils.coffee +++ b/lib/utils.coffee @@ -136,6 +136,66 @@ Utils = # locale-sensitive uppercase detection hasUpperCase: (s) -> s.toLowerCase() != s + # Create a rect given the top left and bottom right corners. + createRect: (x1, y1, x2, y2) -> + bottom: y2 + top: y1 + left: x1 + right: x2 + width: x2 - x1 + height: y2 - y1 + + # Translate a rect by x horizontally and y vertically. + shiftRect: (rect, x, y) -> + bottom: rect.bottom + y + top: rect.top + y + left: rect.left + x + right: rect.right + x + width: rect.width + height: rect.height + + # Subtract rect2 from rect1, returning an array of rects which are in rect1 but not rect2. + subtractRect: (rect1, rect2_) -> + # Bound rect2 by rect1 + rect2 = {} + rect2 = @createRect( + Math.max(rect1.left, rect2_.left), + Math.max(rect1.top, rect2_.top), + Math.min(rect1.right, rect2_.right), + Math.min(rect1.bottom, rect2_.bottom) + ) + + # If bounding rect2 has made the width or height negative, rect1 does not contain rect2. + return [rect1] if rect2.width < 0 or rect2.height < 0 + + # + # All the possible rects, in the order + # +-+-+-+ + # |1|2|3| + # +-+-+-+ + # |4| |5| + # +-+-+-+ + # |6|7|8| + # +-+-+-+ + # where the outer rectangle is rect1 and the inner rectangle is rect 2. Note that the rects may be of + # width or height 0. + # + rects = [ + # Top row. + @createRect rect1.left, rect1.top, rect2.left, rect2.top + @createRect rect2.left, rect1.top, rect2.right, rect2.top + @createRect rect2.right, rect1.top, rect1.right, rect2.top + # Middle row. + @createRect rect1.left, rect2.top, rect2.left, rect2.bottom + @createRect rect2.right, rect2.top, rect1.right, rect2.bottom + # Bottom row. + @createRect rect1.left, rect2.bottom, rect2.left, rect1.bottom + @createRect rect2.left, rect2.bottom, rect2.right, rect1.bottom + @createRect rect2.right, rect2.bottom, rect1.right, rect1.bottom + ] + + rects.filter (rect) -> rect.height > 0 and rect.width > 0 + # This creates a new function out of an existing function, where the new function takes fewer arguments. This # allows us to pass around functions instead of functions + a partial list of arguments. Function::curry = -> -- cgit v1.2.3 From 9c9c48598534c2a0cd8aec28a4a806d74f28e090 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Thu, 18 Dec 2014 11:56:53 +0000 Subject: Move rect functions to their own file --- lib/dom_utils.coffee | 3 +-- lib/rect.coffee | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/utils.coffee | 60 ------------------------------------------------ 3 files changed, 65 insertions(+), 62 deletions(-) create mode 100644 lib/rect.coffee (limited to 'lib') diff --git a/lib/dom_utils.coffee b/lib/dom_utils.coffee index 7e19a7fc..ebbed006 100644 --- a/lib/dom_utils.coffee +++ b/lib/dom_utils.coffee @@ -95,7 +95,6 @@ DomUtils = else rect - getClientRectsForAreas: (imgClientRect, areas) -> rects = [] for area in areas @@ -114,7 +113,7 @@ DomUtils = # something more sophisticated, but likely not worth the effort. [x1, y1, x2, y2] = coords - rect = Utils.shiftRect (Utils.createRect x1, y1, x2, y2), imgClientRect.left, imgClientRect.top + rect = Rect.translate (Rect.create x1, y1, x2, y2), imgClientRect.left, imgClientRect.top rect = @cropRectToVisible rect rects.push {element: area, rect: rect} unless not rect or isNaN rect.top diff --git a/lib/rect.coffee b/lib/rect.coffee new file mode 100644 index 00000000..67c9de7c --- /dev/null +++ b/lib/rect.coffee @@ -0,0 +1,64 @@ +# Commands for manipulating rects. +Rect = + # Create a rect given the top left and bottom right corners. + create: (x1, y1, x2, y2) -> + bottom: y2 + top: y1 + left: x1 + right: x2 + width: x2 - x1 + height: y2 - y1 + + # Translate a rect by x horizontally and y vertically. + translate: (rect, x, y) -> + bottom: rect.bottom + y + top: rect.top + y + left: rect.left + x + right: rect.right + x + width: rect.width + height: rect.height + + # Subtract rect2 from rect1, returning an array of rects which are in rect1 but not rect2. + subtract: (rect1, rect2_) -> + # Bound rect2 by rect1 + rect2 = {} + rect2 = @create( + Math.max(rect1.left, rect2_.left), + Math.max(rect1.top, rect2_.top), + Math.min(rect1.right, rect2_.right), + Math.min(rect1.bottom, rect2_.bottom) + ) + + # If bounding rect2 has made the width or height negative, rect1 does not contain rect2. + return [rect1] if rect2.width < 0 or rect2.height < 0 + + # + # All the possible rects, in the order + # +-+-+-+ + # |1|2|3| + # +-+-+-+ + # |4| |5| + # +-+-+-+ + # |6|7|8| + # +-+-+-+ + # where the outer rectangle is rect1 and the inner rectangle is rect 2. Note that the rects may be of + # width or height 0. + # + rects = [ + # Top row. + @create rect1.left, rect1.top, rect2.left, rect2.top + @create rect2.left, rect1.top, rect2.right, rect2.top + @create rect2.right, rect1.top, rect1.right, rect2.top + # Middle row. + @create rect1.left, rect2.top, rect2.left, rect2.bottom + @create rect2.right, rect2.top, rect1.right, rect2.bottom + # Bottom row. + @create rect1.left, rect2.bottom, rect2.left, rect1.bottom + @create rect2.left, rect2.bottom, rect2.right, rect1.bottom + @create rect2.right, rect2.bottom, rect1.right, rect1.bottom + ] + + rects.filter (rect) -> rect.height > 0 and rect.width > 0 + +root = exports ? window +root.Rect = Rect diff --git a/lib/utils.coffee b/lib/utils.coffee index 6cc45f32..b7f8731a 100644 --- a/lib/utils.coffee +++ b/lib/utils.coffee @@ -136,66 +136,6 @@ Utils = # locale-sensitive uppercase detection hasUpperCase: (s) -> s.toLowerCase() != s - # Create a rect given the top left and bottom right corners. - createRect: (x1, y1, x2, y2) -> - bottom: y2 - top: y1 - left: x1 - right: x2 - width: x2 - x1 - height: y2 - y1 - - # Translate a rect by x horizontally and y vertically. - shiftRect: (rect, x, y) -> - bottom: rect.bottom + y - top: rect.top + y - left: rect.left + x - right: rect.right + x - width: rect.width - height: rect.height - - # Subtract rect2 from rect1, returning an array of rects which are in rect1 but not rect2. - subtractRect: (rect1, rect2_) -> - # Bound rect2 by rect1 - rect2 = {} - rect2 = @createRect( - Math.max(rect1.left, rect2_.left), - Math.max(rect1.top, rect2_.top), - Math.min(rect1.right, rect2_.right), - Math.min(rect1.bottom, rect2_.bottom) - ) - - # If bounding rect2 has made the width or height negative, rect1 does not contain rect2. - return [rect1] if rect2.width < 0 or rect2.height < 0 - - # - # All the possible rects, in the order - # +-+-+-+ - # |1|2|3| - # +-+-+-+ - # |4| |5| - # +-+-+-+ - # |6|7|8| - # +-+-+-+ - # where the outer rectangle is rect1 and the inner rectangle is rect 2. Note that the rects may be of - # width or height 0. - # - rects = [ - # Top row. - @createRect rect1.left, rect1.top, rect2.left, rect2.top - @createRect rect2.left, rect1.top, rect2.right, rect2.top - @createRect rect2.right, rect1.top, rect1.right, rect2.top - # Middle row. - @createRect rect1.left, rect2.top, rect2.left, rect2.bottom - @createRect rect2.right, rect2.top, rect1.right, rect2.bottom - # Bottom row. - @createRect rect1.left, rect2.bottom, rect2.left, rect1.bottom - @createRect rect2.left, rect2.bottom, rect2.right, rect1.bottom - @createRect rect2.right, rect2.bottom, rect1.right, rect1.bottom - ] - - rects.filter (rect) -> rect.height > 0 and rect.width > 0 - # This creates a new function out of an existing function, where the new function takes fewer arguments. This # allows us to pass around functions instead of functions + a partial list of arguments. Function::curry = -> -- cgit v1.2.3 From 91bb7d7b85df3b90882e92aeae2fa2021f61733e Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Thu, 18 Dec 2014 12:59:35 +0000 Subject: Add tests for lib/rect --- lib/rect.coffee | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) (limited to 'lib') diff --git a/lib/rect.coffee b/lib/rect.coffee index 67c9de7c..adc1fc36 100644 --- a/lib/rect.coffee +++ b/lib/rect.coffee @@ -9,8 +9,16 @@ Rect = width: x2 - x1 height: y2 - y1 + copy: (rect) -> + bottom: rect.bottom + top: rect.top + left: rect.left + right: rect.right + width: rect.width + height: rect.height + # Translate a rect by x horizontally and y vertically. - translate: (rect, x, y) -> + translate: (rect, x = 0, y = 0) -> bottom: rect.bottom + y top: rect.top + y left: rect.left + x @@ -19,18 +27,17 @@ Rect = height: rect.height # Subtract rect2 from rect1, returning an array of rects which are in rect1 but not rect2. - subtract: (rect1, rect2_) -> + subtract: (rect1, rect2) -> # Bound rect2 by rect1 - rect2 = {} rect2 = @create( - Math.max(rect1.left, rect2_.left), - Math.max(rect1.top, rect2_.top), - Math.min(rect1.right, rect2_.right), - Math.min(rect1.bottom, rect2_.bottom) + Math.max(rect1.left, rect2.left), + Math.max(rect1.top, rect2.top), + Math.min(rect1.right, rect2.right), + Math.min(rect1.bottom, rect2.bottom) ) # If bounding rect2 has made the width or height negative, rect1 does not contain rect2. - return [rect1] if rect2.width < 0 or rect2.height < 0 + return [Rect.copy rect1] if rect2.width < 0 or rect2.height < 0 # # All the possible rects, in the order @@ -60,5 +67,16 @@ Rect = rects.filter (rect) -> rect.height > 0 and rect.width > 0 + contains: (rect1, rect2) -> + rect1.right > rect2.left and + rect1.left < rect2.right and + rect1.bottom > rect2.top and + rect1.top < rect2.bottom + + equals: (rect1, rect2) -> + for property in ["top", "bottom", "left", "right", "width", "height"] + return false if rect1[property] != rect2[property] + true + root = exports ? window root.Rect = Rect -- cgit v1.2.3 From ef863e5748c088f80ec9a0ffcaa06201c42e6c98 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Thu, 18 Dec 2014 13:23:25 +0000 Subject: Make some minor changes/tweaks to rect handling in dom_utils --- lib/dom_utils.coffee | 46 +++++++++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 19 deletions(-) (limited to 'lib') diff --git a/lib/dom_utils.coffee b/lib/dom_utils.coffee index ebbed006..df1db3b9 100644 --- a/lib/dom_utils.coffee +++ b/lib/dom_utils.coffee @@ -68,8 +68,7 @@ DomUtils = else clientRect = @cropRectToVisible clientRect - if (!clientRect || clientRect.width < 3 || clientRect.height < 3) - continue + continue unless clientRect # eliminate invisible elements (see test_harnesses/visibility_test.html) computedStyle = window.getComputedStyle(element, null) @@ -81,33 +80,42 @@ DomUtils = null + # + # Bounds the rect by the current viewport dimensions. If the rect is offscreen or has a height or width < 3 + # then null is returned instead of a rect. + # cropRectToVisible: (rect) -> - if (rect.top < 0) - rect.height += rect.top - rect.top = 0 - - if (rect.left < 0) - rect.width += rect.left - rect.left = 0 - - if (rect.top >= window.innerHeight - 4 || rect.left >= window.innerWidth - 4) + boundedRect = Rect.create( + Math.max(rect.left, 0), + Math.max(rect.top, 0), + Math.min(rect.right, window.innerWidth), + Math.min(rect.bottom, window.innerHeight) + ) + if boundedRect.width < 3 or boundedRect.height < 3 null else - rect + boundedRect + # + # Get the client rects for the elements in a based on the position of the element using + # the map. Returns an array of rects. + # getClientRectsForAreas: (imgClientRect, areas) -> rects = [] for area in areas coords = area.coords.split(",").map((coord) -> parseInt(coord, 10)) shape = area.shape.toLowerCase() - if shape == "rect" or coords.length == 4 + if shape in ["rect", "rectangle"] # "rectangle" is an IE non-standard. [x1, y1, x2, y2] = coords - else if shape == "circle" or coords.length == 3 + else if shape in ["circle", "circ"] # "circ" is an IE non-standard. [x, y, r] = coords - x1 = x - r - x2 = x + r - y1 = y - r - y2 = y + r + diff = r / Math.sqrt 2 # Gives us an inner square + x1 = x - diff + x2 = x + diff + y1 = y - diff + y2 = y + diff + else if shape == "default" + [x1, y1, x2, y2] = [0, 0, imgClientRect.width, imgClientRect.height] else # Just consider the rectangle surrounding the first two points in a polygon. It's possible to do # something more sophisticated, but likely not worth the effort. @@ -116,7 +124,7 @@ DomUtils = rect = Rect.translate (Rect.create x1, y1, x2, y2), imgClientRect.left, imgClientRect.top rect = @cropRectToVisible rect - rects.push {element: area, rect: rect} unless not rect or isNaN rect.top + rects.push {element: area, rect: rect} if rect and not isNaN rect.top rects # -- cgit v1.2.3 From 845fd65e1c1a52329352f5068e3c7f0ef7b26154 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Thu, 18 Dec 2014 13:25:21 +0000 Subject: Use Rect.copy instead of literal member by member copy of a rect --- lib/dom_utils.coffee | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/dom_utils.coffee b/lib/dom_utils.coffee index df1db3b9..7fd126b8 100644 --- a/lib/dom_utils.coffee +++ b/lib/dom_utils.coffee @@ -46,10 +46,7 @@ DomUtils = # getVisibleClientRect: (element) -> # Note: this call will be expensive if we modify the DOM in between calls. - clientRects = ({ - top: clientRect.top, right: clientRect.right, bottom: clientRect.bottom, left: clientRect.left, - width: clientRect.width, height: clientRect.height - } for clientRect in element.getClientRects()) + clientRects = (Rect.copy clientRect for clientRect in element.getClientRects()) for clientRect in clientRects # If the link has zero dimensions, it may be wrapping visible -- cgit v1.2.3 From 56ba56622c529d570285f42732f4f1ed29830987 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Mon, 22 Dec 2014 11:51:06 +0000 Subject: Support small s with link hints This is primarily to deal with our calculated rects being too small for the `` on http://www.mapsofindia.com/worldmap/clickable-world-map.html --- lib/dom_utils.coffee | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'lib') diff --git a/lib/dom_utils.coffee b/lib/dom_utils.coffee index 7fd126b8..8bb099a1 100644 --- a/lib/dom_utils.coffee +++ b/lib/dom_utils.coffee @@ -59,13 +59,13 @@ DomUtils = continue if (computedStyle.getPropertyValue('float') == 'none' && computedStyle.getPropertyValue('position') != 'absolute') childClientRect = @getVisibleClientRect(child) - continue if (childClientRect == null) + continue if clientRect == null return childClientRect else clientRect = @cropRectToVisible clientRect - continue unless clientRect + continue if clientRect == null or clientRect.width < 3 or clientRect.height < 3 # eliminate invisible elements (see test_harnesses/visibility_test.html) computedStyle = window.getComputedStyle(element, null) @@ -83,12 +83,12 @@ DomUtils = # cropRectToVisible: (rect) -> boundedRect = Rect.create( - Math.max(rect.left, 0), - Math.max(rect.top, 0), - Math.min(rect.right, window.innerWidth), - Math.min(rect.bottom, window.innerHeight) + Math.max(rect.left, 0) + Math.max(rect.top, 0) + rect.right + rect.bottom ) - if boundedRect.width < 3 or boundedRect.height < 3 + if boundedRect.top >= window.innerHeight - 4 or boundedRect.left >= window.innerWidth - 4 null else boundedRect -- cgit v1.2.3 From e79537231f35215339c04d1e01347974bd4bd810 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Mon, 22 Dec 2014 12:33:14 +0000 Subject: Correct a typo, add some tests for consistency --- lib/dom_utils.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/dom_utils.coffee b/lib/dom_utils.coffee index 8bb099a1..2447b4bb 100644 --- a/lib/dom_utils.coffee +++ b/lib/dom_utils.coffee @@ -59,7 +59,7 @@ DomUtils = continue if (computedStyle.getPropertyValue('float') == 'none' && computedStyle.getPropertyValue('position') != 'absolute') childClientRect = @getVisibleClientRect(child) - continue if clientRect == null + continue if childClientRect == null or childClientRect.width < 3 or childClientRect.height < 3 return childClientRect else -- cgit v1.2.3