aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPhil Crosby2012-06-10 23:52:43 -0700
committerPhil Crosby2012-06-12 22:00:35 -0700
commitf27180aefbc90c3bb3847faf68e58b440768e713 (patch)
treeed39fe4b2e2661a9defb4091952b0bc5bcca3935
parente8468e12f867cb71eb0d2b50c4904a1236b50e47 (diff)
downloadvimium-f27180aefbc90c3bb3847faf68e58b440768e713.tar.bz2
port dom_utils.js to coffeescript
-rw-r--r--.gitignore3
-rw-r--r--content_scripts/link_hints.js16
-rw-r--r--content_scripts/vimium_frontend.js22
-rw-r--r--lib/dom_utils.coffee105
-rw-r--r--lib/dom_utils.js121
5 files changed, 126 insertions, 141 deletions
diff --git a/.gitignore b/.gitignore
index d3f647cf..fce4a6c8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,8 @@
background_scripts/completion.js
background_scripts/commands.js
+background_scripts/settings.js
tests/completion_test.js
tests/test_helper.js
tests/utils_test.js
-background_scripts/settings.js
lib/clipboard.js
+lib/dom_utils.js
diff --git a/content_scripts/link_hints.js b/content_scripts/link_hints.js
index 7d6b431f..8ca3675b 100644
--- a/content_scripts/link_hints.js
+++ b/content_scripts/link_hints.js
@@ -34,7 +34,7 @@ var linkHints = {
* The final expression will be something like "//button | //xhtml:button | ..."
* We use translate() instead of lower-case() because Chrome only supports XPath 1.0.
*/
- clickableElementsXPath: domUtils.makeXPath(["a", "area[@href]", "textarea", "button", "select",
+ clickableElementsXPath: DomUtils.makeXPath(["a", "area[@href]", "textarea", "button", "select",
"input[not(@type='hidden' or @disabled or @readonly)]",
"*[@onclick or @tabindex or @role='link' or @role='button' or contains(@class, 'button') or " +
"@contenteditable='' or translate(@contenteditable, 'TRUE', 'true')='true']"]),
@@ -72,7 +72,7 @@ var linkHints = {
this.linkActivator = function(link) {
// When "clicking" on a link, dispatch the event with the appropriate meta key (CMD on Mac, CTRL on windows)
// to open it in a new tab if necessary.
- domUtils.simulateClick(link, { metaKey: platform == "Mac", ctrlKey: platform != "Mac" });
+ DomUtils.simulateClick(link, { metaKey: platform == "Mac", ctrlKey: platform != "Mac" });
}
} else if (copyLinkUrl) {
HUD.show("Copy link URL to Clipboard");
@@ -84,7 +84,7 @@ var linkHints = {
// When we're opening the link in the current tab, don't navigate to the selected link immediately;
// we want to give the user some time to notice which link has received focus.
this.linkActivator = function(link) {
- setTimeout(domUtils.simulateClick.bind(domUtils, link), 400);
+ setTimeout(DomUtils.simulateClick.bind(DomUtils, link), 400);
}
}
},
@@ -120,14 +120,14 @@ var linkHints = {
* of digits needed to enumerate all of the links on screen.
*/
getVisibleClickableElements: function() {
- var resultSet = domUtils.evaluateXPath(this.clickableElementsXPath, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
+ var resultSet = DomUtils.evaluateXPath(this.clickableElementsXPath, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
var visibleElements = [];
// Find all visible clickable elements.
for (var i = 0, count = resultSet.snapshotLength; i < count; i++) {
var element = resultSet.snapshotItem(i);
- var clientRect = domUtils.getVisibleClientRect(element, clientRect);
+ var clientRect = DomUtils.getVisibleClientRect(element, clientRect);
if (clientRect !== null)
visibleElements.push({element: element, rect: clientRect});
@@ -200,14 +200,14 @@ var linkHints = {
activateLink: function(matchedLink, delay) {
this.delayMode = true;
var clickEl = matchedLink.clickableItem;
- if (domUtils.isSelectable(clickEl)) {
- domUtils.simulateSelect(clickEl);
+ if (DomUtils.isSelectable(clickEl)) {
+ DomUtils.simulateSelect(clickEl);
this.deactivateMode(delay, function() { linkHints.delayMode = false; });
} else {
// TODO figure out which other input elements should not receive focus
if (clickEl.nodeName.toLowerCase() === 'input' && clickEl.type !== 'button')
clickEl.focus();
- domUtils.flashRect(matchedLink.rect);
+ DomUtils.flashRect(matchedLink.rect);
this.linkActivator(clickEl);
if (this.shouldOpenWithQueue) {
this.deactivateMode(delay, function() {
diff --git a/content_scripts/vimium_frontend.js b/content_scripts/vimium_frontend.js
index 31bb2b44..c1fbee1b 100644
--- a/content_scripts/vimium_frontend.js
+++ b/content_scripts/vimium_frontend.js
@@ -35,7 +35,7 @@ var textInputXPath = (function() {
"(" + textInputTypes.map(function(type) {return '@type="' + type + '"'}).join(" or ") + "or not(@type))" +
" and not(@disabled or @readonly)]",
"textarea", "*[@contenteditable='' or translate(@contenteditable, 'TRUE', 'true')='true']"];
- return domUtils.makeXPath(inputElements);
+ return DomUtils.makeXPath(inputElements);
})();
/**
@@ -166,7 +166,7 @@ function initializePreDomReady() {
} else if (port.name == "setScrollPosition") {
port.onMessage.addListener(function(args) {
if (args.scrollX > 0 || args.scrollY > 0) {
- domUtils.documentReady(function() { window.scrollBy(args.scrollX, args.scrollY); });
+ DomUtils.documentReady(function() { window.scrollBy(args.scrollX, args.scrollY); });
}
});
} else if (port.name == "returnCurrentTabUrl") {
@@ -332,7 +332,7 @@ function scrollLeft() { scrollActivatedElementBy("x", -1 * settings.get("scrollS
function scrollRight() { scrollActivatedElementBy("x", parseFloat(settings.get("scrollStepSize"))); }
function focusInput(count) {
- var results = domUtils.evaluateXPath(textInputXPath, XPathResult.ORDERED_NODE_ITERATOR_TYPE);
+ var results = DomUtils.evaluateXPath(textInputXPath, XPathResult.ORDERED_NODE_ITERATOR_TYPE);
var lastInputBox;
var i = 0;
@@ -341,7 +341,7 @@ function focusInput(count) {
var currentInputBox = results.iterateNext();
if (!currentInputBox) { break; }
- if (domUtils.getVisibleClientRect(currentInputBox) === null)
+ if (DomUtils.getVisibleClientRect(currentInputBox) === null)
continue;
lastInputBox = currentInputBox;
@@ -800,9 +800,9 @@ function selectFoundInputElement() {
// instead. however, since the last focused element might not be the one currently pointed to by find (e.g.
// the current one might be disabled and therefore unable to receive focus), we use the approximate
// heuristic of checking that the last anchor node is an ancestor of our element.
- if (findModeQueryHasResults && domUtils.isSelectable(document.activeElement) &&
+ if (findModeQueryHasResults && DomUtils.isSelectable(document.activeElement) &&
isDOMDescendant(findModeAnchorNode, document.activeElement)) {
- domUtils.simulateSelect(document.activeElement);
+ DomUtils.simulateSelect(document.activeElement);
// the element has already received focus via find(), so invoke insert mode manually
enterInsertModeWithoutShowingIndicator(document.activeElement);
}
@@ -839,14 +839,14 @@ function findAndFocus(backwards) {
// if we have found an input element via 'n', pressing <esc> immediately afterwards sends us into insert
// mode
- var elementCanTakeInput = domUtils.isSelectable(document.activeElement) &&
+ var elementCanTakeInput = DomUtils.isSelectable(document.activeElement) &&
isDOMDescendant(findModeAnchorNode, document.activeElement);
if (elementCanTakeInput) {
handlerStack.push({
keydown: function(event) {
handlerStack.pop();
if (isEscape(event)) {
- domUtils.simulateSelect(document.activeElement);
+ DomUtils.simulateSelect(document.activeElement);
enterInsertModeWithoutShowingIndicator(document.activeElement);
return false; // we have 'consumed' this event, so do not propagate
}
@@ -880,7 +880,7 @@ function followLink(linkElement) {
// calls, like the 'more' button on GitHub's newsfeed.
linkElement.scrollIntoView();
linkElement.focus();
- domUtils.simulateClick(linkElement);
+ DomUtils.simulateClick(linkElement);
}
}
@@ -891,8 +891,8 @@ function followLink(linkElement) {
* next big thing', and 'more' over 'nextcompany', even if 'next' occurs before 'more' in :linkStrings.
*/
function findAndFollowLink(linkStrings) {
- var linksXPath = domUtils.makeXPath(["a", "*[@onclick or @role='link' or contains(@class, 'button')]"]);
- var links = domUtils.evaluateXPath(linksXPath, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
+ var linksXPath = DomUtils.makeXPath(["a", "*[@onclick or @role='link' or contains(@class, 'button')]"]);
+ var links = DomUtils.evaluateXPath(linksXPath, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
var candidateLinks = [];
// at the end of this loop, candidateLinks will contain all visible links that match our patterns
diff --git a/lib/dom_utils.coffee b/lib/dom_utils.coffee
new file mode 100644
index 00000000..0ce9c50c
--- /dev/null
+++ b/lib/dom_utils.coffee
@@ -0,0 +1,105 @@
+DomUtils =
+ #
+ # Runs :callback if the DOM has loaded, otherwise runs it on load
+ #
+ documentReady: (->
+ loaded = false
+ window.addEventListener("DOMContentLoaded", -> loaded = true)
+ (callback) -> if loaded then callback() else window.addEventListener("DOMContentLoaded", callback)
+ )()
+
+ #
+ # Takes an array of XPath selectors, adds the necessary namespaces (currently only XHTML), and applies them
+ # to the document root. The namespaceResolver in evaluateXPath should be kept in sync with the namespaces
+ # here.
+ #
+ makeXPath: (elementArray) ->
+ xpath = []
+ for i of elementArray
+ xpath.push("//" + elementArray[i], "//xhtml:" + elementArray[i])
+ xpath.join(" | ")
+
+ evaluateXPath: (xpath, resultType) ->
+ namespaceResolver = (namespace) ->
+ if (namespace == "xhtml") then "http://www.w3.org/1999/xhtml" else null
+ document.evaluate(xpath, document.documentElement, namespaceResolver, resultType, null)
+
+ #
+ # Returns the first visible clientRect of an element if it exists. Otherwise it returns null.
+ #
+ getVisibleClientRect: (element) ->
+ # Note: this call will be expensive if we modify the DOM in between calls.
+ clientRects = element.getClientRects()
+ clientRectsLength = clientRects.length
+
+ for i in [0...clientRectsLength]
+ if (clientRects[i].top < -2 || clientRects[i].top >= window.innerHeight - 4 ||
+ clientRects[i].left < -2 || clientRects[i].left >= window.innerWidth - 4)
+ continue
+
+ if (clientRects[i].width < 3 || clientRects[i].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 clientRects[i]
+
+ for i in [0...clientRectsLength]
+ # If the link has zero dimensions, it may be wrapping visible
+ # but floated elements. Check for this.
+ if (clientRects[i].width == 0 || clientRects[i].height == 0)
+ childrenCount = element.children.length
+ for j in [0...childrenCount]
+ computedStyle = window.getComputedStyle(element.children[j], null)
+ # Ignore child elements which are not floated and not absolutely positioned for parent elements with zero width/height
+ if (computedStyle.getPropertyValue('float') == 'none' && computedStyle.getPropertyValue('position') != 'absolute')
+ continue
+ childClientRect = this.getVisibleClientRect(element.children[j])
+ if (childClientRect == null)
+ continue
+ return childClientRect
+ null
+
+ #
+ # Selectable means the element has a text caret; this is not the same as "focusable".
+ #
+ isSelectable: (element) ->
+ selectableTypes = ["search", "text", "password"]
+ (element.nodeName.toLowerCase() == "input" && selectableTypes.indexOf(element.type) >= 0) ||
+ element.nodeName.toLowerCase() == "textarea"
+
+ simulateSelect: (element) ->
+ element.focus()
+ # When focusing a textbox, put the selection caret at the end of the textbox's contents.
+ element.setSelectionRange(element.value.length, element.value.length)
+
+ simulateClick: (element, modifiers) ->
+ modifiers ||= {}
+
+ eventSequence = ["mouseover", "mousedown", "mouseup", "click"]
+ for event in eventSequence
+ mouseEvent = document.createEvent("MouseEvents")
+ mouseEvent.initMouseEvent(event, true, true, window, 1, 0, 0, 0, 0, modifiers.ctrlKey, false, false,
+ modifiers.metaKey, 0, null)
+ # Debugging note: Firefox will not execute the element's default action if we dispatch this click event,
+ # but Webkit will. Dispatching a click on an input box does not seem to focus it; we do that separately
+ element.dispatchEvent(mouseEvent)
+
+ # momentarily flash a rectangular border to give user some visual feedback
+ flashRect: (rect) ->
+ flashEl = document.createElement("div")
+ flashEl.id = "vimiumFlash"
+ flashEl.className = "vimiumReset"
+ flashEl.style.left = rect.left + window.scrollX + "px"
+ flashEl.style.top = rect.top + window.scrollY + "px"
+ flashEl.style.width = rect.width + "px"
+ flashEl.style.height = rect.height + "px"
+ document.body.appendChild(flashEl)
+ setTimeout((-> flashEl.parentNode.removeChild(flashEl)), 400)
+
+root = exports ? window
+root.DomUtils = DomUtils
diff --git a/lib/dom_utils.js b/lib/dom_utils.js
deleted file mode 100644
index 4ab92682..00000000
--- a/lib/dom_utils.js
+++ /dev/null
@@ -1,121 +0,0 @@
-var domUtils = {
- /**
- * Runs :callback if the DOM has loaded, otherwise runs it on load
- */
- documentReady: (function() {
- var loaded = false;
- window.addEventListener("DOMContentLoaded", function() { loaded = true; });
- return function(callback) {
- if (loaded)
- callback();
- else
- window.addEventListener("DOMContentLoaded", callback);
- };
- })(),
-
- /*
- * Takes an array of XPath selectors, adds the necessary namespaces (currently only XHTML), and applies them
- * to the document root. The namespaceResolver in evaluateXPath should be kept in sync with the namespaces
- * here.
- */
- makeXPath: function(elementArray) {
- var xpath = [];
- for (var i in elementArray)
- xpath.push("//" + elementArray[i], "//xhtml:" + elementArray[i]);
- return xpath.join(" | ");
- },
-
- evaluateXPath: function(xpath, resultType) {
- function namespaceResolver(namespace) {
- return namespace == "xhtml" ? "http://www.w3.org/1999/xhtml" : null;
- }
- return document.evaluate(xpath, document.documentElement, namespaceResolver, resultType, null);
- },
-
- /**
- * Returns the first visible clientRect of an element if it exists. Otherwise it returns null.
- */
- getVisibleClientRect: function(element) {
- // Note: this call will be expensive if we modify the DOM in between calls.
- var clientRects = element.getClientRects();
- var clientRectsLength = clientRects.length;
-
- for (var i = 0; i < clientRectsLength; i++) {
- if (clientRects[i].top < -2 || clientRects[i].top >= window.innerHeight - 4 ||
- clientRects[i].left < -2 || clientRects[i].left >= window.innerWidth - 4)
- continue;
-
- if (clientRects[i].width < 3 || clientRects[i].height < 3)
- continue;
-
- // eliminate invisible elements (see test_harnesses/visibility_test.html)
- var computedStyle = window.getComputedStyle(element, null);
- if (computedStyle.getPropertyValue('visibility') != 'visible' ||
- computedStyle.getPropertyValue('display') == 'none')
- continue;
-
- return clientRects[i];
- }
-
- for (var i = 0; i < clientRectsLength; i++) {
- // If the link has zero dimensions, it may be wrapping visible
- // but floated elements. Check for this.
- if (clientRects[i].width == 0 || clientRects[i].height == 0) {
- for (var j = 0, childrenCount = element.children.length; j < childrenCount; j++) {
- var computedStyle = window.getComputedStyle(element.children[j], null);
- // Ignore child elements which are not floated and not absolutely positioned for parent elements with zero width/height
- if (computedStyle.getPropertyValue('float') == 'none' && computedStyle.getPropertyValue('position') != 'absolute')
- continue;
- var childClientRect = this.getVisibleClientRect(element.children[j]);
- if (childClientRect === null)
- continue;
- return childClientRect;
- }
- }
- };
- return null;
- },
-
- /*
- * Selectable means the element has a text caret; this is not the same as "focusable".
- */
- isSelectable: function(element) {
- var selectableTypes = ["search", "text", "password"];
- return (element.nodeName.toLowerCase() == "input" && selectableTypes.indexOf(element.type) >= 0) ||
- element.nodeName.toLowerCase() == "textarea";
- },
-
- simulateSelect: function(element) {
- element.focus();
- // When focusing a textbox, put the selection caret at the end of the textbox's contents.
- element.setSelectionRange(element.value.length, element.value.length);
- },
-
- simulateClick: function(element, modifiers) {
- modifiers = modifiers || {};
-
- var eventSequence = [ "mouseover", "mousedown", "mouseup", "click" ];
- for (var i = 0; i < eventSequence.length; i++) {
- var event = document.createEvent("MouseEvents");
- event.initMouseEvent(eventSequence[i], true, true, window, 1, 0, 0, 0, 0, modifiers.ctrlKey, false, false,
- modifiers.metaKey, 0, null);
- // Debugging note: Firefox will not execute the element's default action if we dispatch this click event,
- // but Webkit will. Dispatching a click on an input box does not seem to focus it; we do that separately
- element.dispatchEvent(event);
- }
- },
-
- // momentarily flash a rectangular border to give user some visual feedback
- flashRect: function(rect) {
- var flashEl = document.createElement("div");
- flashEl.id = "vimiumFlash";
- flashEl.className = "vimiumReset";
- flashEl.style.left = rect.left + window.scrollX + "px";
- flashEl.style.top = rect.top + window.scrollY + "px";
- flashEl.style.width = rect.width + "px";
- flashEl.style.height = rect.height + "px";
- document.body.appendChild(flashEl);
- setTimeout(function() { flashEl.parentNode.removeChild(flashEl); }, 400);
- },
-
-};