| Index: chrome/browser/resources/access_chromevox/common/selection_util.js
|
| ===================================================================
|
| --- chrome/browser/resources/access_chromevox/common/selection_util.js (revision 0)
|
| +++ chrome/browser/resources/access_chromevox/common/selection_util.js (revision 0)
|
| @@ -0,0 +1,490 @@
|
| +// Copyright (c) 2011 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +/**
|
| + * @fileoverview A collection of JavaScript utilities used to improve selection
|
| + * at different granularities.
|
| + */
|
| +
|
| +
|
| +goog.provide('cvox.SelectionUtil');
|
| +
|
| +goog.require('cvox.DomUtil');
|
| +goog.require('cvox.XpathUtil');
|
| +
|
| +/**
|
| + * Utilities for improving selection.
|
| + * @constructor
|
| + */
|
| +cvox.SelectionUtil = function() {};
|
| +
|
| +/**
|
| + * Checks if a given node is a descendant of another given node.
|
| + *
|
| + * @param {Node} nod The candidate descendant.
|
| + * @param {Node} bound The candidate parent.
|
| + * @return {boolean} True if nod is a descendant of bound.
|
| + */
|
| +cvox.SelectionUtil.isWithinBound = function(nod, bound) {
|
| + var parent = nod;
|
| + while (parent != null) {
|
| + if (parent.isSameNode(bound)) {
|
| + return true;
|
| + } else {
|
| + parent = parent.parentNode;
|
| + }
|
| + }
|
| + return false;
|
| +};
|
| +
|
| +/**
|
| + * Cleans up a paragraph selection acquired by extending forward.
|
| + * In this context, a paragraph selection is 'clean' when the focus
|
| + * node (the end of the selection) is not on a text node.
|
| + * @param {Selection} sel The paragraph-length selection.
|
| + * @return {boolean} True if the selection has been cleaned.
|
| + * False if the selection cannot be cleaned without invalid extension.
|
| + */
|
| +cvox.SelectionUtil.cleanUpParagraphForward = function(sel) {
|
| + var expand = true;
|
| +
|
| + // nodeType:3 == TEXT_NODE
|
| + while (sel.focusNode.nodeType == 3) {
|
| + // Ending with a text node, which is incorrect. Keep extending forward.
|
| + var fnode = sel.focusNode;
|
| + var foffset = sel.focusOffset;
|
| +
|
| + sel.modify('extend', 'forward', 'sentence');
|
| + if ((fnode == sel.focusNode) && (foffset == sel.focusOffset)) {
|
| + // Nothing more to be done, cannot extend forward further.
|
| + return false;
|
| + }
|
| + }
|
| +
|
| + return true;
|
| +};
|
| +
|
| +/**
|
| + * Cleans up a paragraph selection acquired by extending backward.
|
| + * In this context, a paragraph selection is 'clean' when the focus
|
| + * node (the end of the selection) is not on a text node.
|
| + * @param {Selection} sel The paragraph-length selection.
|
| + * @return {boolean} True if the selection has been cleaned.
|
| + * False if the selection cannot be cleaned without invalid extension.
|
| + */
|
| +cvox.SelectionUtil.cleanUpParagraphBack = function(sel) {
|
| + var expand = true;
|
| +
|
| + var fnode;
|
| + var foffset;
|
| +
|
| + // nodeType:3 == TEXT_NODE
|
| + while (sel.focusNode.nodeType == 3) {
|
| + // Ending with a text node, which is incorrect. Keep extending backward.
|
| + fnode = sel.focusNode;
|
| + foffset = sel.focusOffset;
|
| +
|
| + sel.modify('extend', 'backward', 'sentence');
|
| +
|
| + if ((fnode == sel.focusNode) && (foffset == sel.focusOffset)) {
|
| + // Nothing more to be done, cannot extend backward further.
|
| + return true;
|
| + }
|
| + }
|
| +
|
| + return true;
|
| +};
|
| +
|
| +/**
|
| + * Cleans up a sentence selection by extending forward.
|
| + * In this context, a sentence selection is 'clean' when the focus
|
| + * node (the end of the selection) is either:
|
| + * - not on a text node
|
| + * - on a text node that ends with a period or a space
|
| + * @param {Selection} sel The sentence-length selection.
|
| + * @return {boolean} True if the selection has been cleaned.
|
| + * False if the selection cannot be cleaned without invalid extension.
|
| + */
|
| +cvox.SelectionUtil.cleanUpSentence = function(sel) {
|
| + var expand = true;
|
| + var lastSelection;
|
| + var lastSelectionOffset;
|
| +
|
| + while (expand) {
|
| +
|
| + // nodeType:3 == TEXT_NODE
|
| + if (sel.focusNode.nodeType == 3) {
|
| + // The focus node is of type text, check end for period
|
| +
|
| + var fnode = sel.focusNode;
|
| + var foffset = sel.focusOffset;
|
| +
|
| + if (sel.rangeCount > 0 && sel.getRangeAt(0).endOffset > 0) {
|
| + if (fnode.substringData(sel.getRangeAt(0).endOffset - 1, 1) == '.') {
|
| + // Text node ends with period.
|
| + return true;
|
| + } else if (fnode.substringData(sel.getRangeAt(0).endOffset - 1, 1) ==
|
| + ' ') {
|
| + // Text node ends with space.
|
| + return true;
|
| + } else {
|
| + // Text node does not end with period or space. Extend forward.
|
| + sel.modify('extend', 'forward', 'sentence');
|
| +
|
| + if ((fnode == sel.focusNode) && (foffset == sel.focusOffset)) {
|
| + // Nothing more to be done, cannot extend forward any further.
|
| + return false;
|
| + }
|
| + }
|
| + } else {
|
| + return true;
|
| + }
|
| + } else {
|
| + // Focus node is not text node, no further cleaning required.
|
| + return true;
|
| + }
|
| + }
|
| +
|
| + return true;
|
| +};
|
| +
|
| +/**
|
| + * Finds the starting position (height from top and left width) of a
|
| + * selection in a document.
|
| + * @param {Selection} sel The selection.
|
| + * @return {Array} The coordinates [top, left] of the selection.
|
| + */
|
| +cvox.SelectionUtil.findSelPosition = function(sel) {
|
| + if (sel.rangeCount == 0) {
|
| + return [0, 0];
|
| + }
|
| +
|
| + var clientRect = sel.getRangeAt(0).getBoundingClientRect();
|
| + var top = window.pageYOffset + clientRect.top;
|
| + var left = window.pageXOffset + clientRect.left;
|
| + return [top, left];
|
| +};
|
| +
|
| +/**
|
| + * Calculates the horizontal and vertical position of a node
|
| + * @param {Node} targetNode The node.
|
| + * @return {Array} The coordinates [top, left] of the node.
|
| + */
|
| +cvox.SelectionUtil.findTopLeftPosition = function(targetNode) {
|
| + var left = 0;
|
| + var top = 0;
|
| + var obj = targetNode;
|
| +
|
| + if (obj.offsetParent) {
|
| + left = obj.offsetLeft;
|
| + top = obj.offsetTop;
|
| + obj = obj.offsetParent;
|
| +
|
| + while (obj !== null) {
|
| + left += obj.offsetLeft;
|
| + top += obj.offsetTop;
|
| + obj = obj.offsetParent;
|
| + }
|
| + }
|
| +
|
| + return [top, left];
|
| +};
|
| +
|
| +
|
| +/**
|
| + * Checks the contents of a selection for meaningful content.
|
| + * @param {Selection} sel The selection.
|
| + * @return {boolean} True if the selection is valid. False if the selection
|
| + * contains only whitespace or is an empty string.
|
| + */
|
| +cvox.SelectionUtil.isSelectionValid = function(sel) {
|
| + var regExpWhiteSpace = new RegExp(/^\s+$/);
|
| + return (! ((regExpWhiteSpace.test(sel.toString())) ||
|
| + (sel.toString() == '')));
|
| +};
|
| +
|
| +
|
| +/**
|
| + * Scrolls the selection into view if it is out of view in the current window.
|
| + * Inspired by workaround for already-on-screen elements @
|
| + * http://
|
| + * www.performantdesign.com/2009/08/2/scrollintoview-but-only-if-out-of-view/
|
| + * @param {Selection} sel The selection to be scrolled into view.
|
| + */
|
| +cvox.SelectionUtil.scrollToSelection = function(sel) {
|
| + if (sel.rangeCount == 0) {
|
| + return;
|
| + }
|
| +
|
| + var pos = cvox.SelectionUtil.findSelPosition(sel);
|
| + var top = pos[0];
|
| + var left = pos[1];
|
| +
|
| + var scrolledVertically = window.pageYOffset ||
|
| + document.documentElement.scrollTop ||
|
| + document.body.scrollTop;
|
| + var pageHeight = window.innerHeight ||
|
| + document.documentElement.clientHeight || document.body.clientHeight;
|
| + var pageWidth = window.innerWidth ||
|
| + document.documentElement.innerWidth || document.body.clientWidth;
|
| +
|
| + if (left < pageWidth) {
|
| + left = 0;
|
| + }
|
| +
|
| + // window.scroll puts specified pixel in upper left of window
|
| + if ((scrolledVertically + pageHeight) < top) {
|
| + // Align with bottom of page
|
| + var diff = top - pageHeight;
|
| + window.scroll(left, diff + 100);
|
| + } else if (top < scrolledVertically) {
|
| + // Align with top of page
|
| + window.scroll(left, top - 100);
|
| + }
|
| +};
|
| +
|
| +/**
|
| + * This is from https://developer.mozilla.org/en/Whitespace_in_the_DOM
|
| + * Determine whether a node's text content is entirely whitespace.
|
| + *
|
| + * Throughout, whitespace is defined as one of the characters
|
| + * "\t" TAB \u0009
|
| + * "\n" LF \u000A
|
| + * "\r" CR \u000D
|
| + * " " SPC \u0020
|
| + *
|
| + * This does not use Javascript's "\s" because that includes non-breaking
|
| + * spaces (and also some other characters).
|
| + *
|
| + * @param {Node} node A node implementing the |CharacterData| interface (i.e.,
|
| + * a |Text|, |Comment|, or |CDATASection| node.
|
| + * @return {boolean} True if all of the text content of |node| is whitespace,
|
| + * otherwise false.
|
| + */
|
| +cvox.SelectionUtil.isAllWs = function(node) {
|
| + // Use ECMA-262 Edition 3 String and RegExp features
|
| + return !(/[^\t\n\r ]/.test(node.data));
|
| +};
|
| +
|
| +
|
| +/**
|
| + * This is from https://developer.mozilla.org/en/Whitespace_in_the_DOM
|
| + * Determine if a node should be ignored by the iterator functions.
|
| + *
|
| + * @param {Node} node An object implementing the DOM1 |Node| interface.
|
| + * @return {boolean} True if the node is:
|
| + * 1) A |Text| node that is all whitespace
|
| + * 2) A |Comment| node
|
| + * and otherwise false.
|
| + */
|
| +
|
| +cvox.SelectionUtil.isIgnorable = function(node) {
|
| + return (node.nodeType == 8) || // A comment node
|
| + ((node.nodeType == 3) &&
|
| + cvox.SelectionUtil.isAllWs(node)); // a text node, all ws
|
| +};
|
| +
|
| +/**
|
| + * This is from https://developer.mozilla.org/en/Whitespace_in_the_DOM
|
| + * Version of |previousSibling| that skips nodes that are entirely
|
| + * whitespace or comments. (Normally |previousSibling| is a property
|
| + * of all DOM nodes that gives the sibling node, the node that is
|
| + * a child of the same parent, that occurs immediately before the
|
| + * reference node.)
|
| + *
|
| + * @param {Node} sib The reference node.
|
| + * @return {Node} Either:
|
| + * 1) The closest previous sibling to |sib| that is not
|
| + * ignorable according to |isIgnorable|, or
|
| + * 2) null if no such node exists.
|
| + */
|
| +cvox.SelectionUtil.nodeBefore = function(sib) {
|
| + while ((sib = sib.previousSibling)) {
|
| + if (!cvox.SelectionUtil.isIgnorable(sib)) {
|
| + return sib;
|
| + }
|
| + }
|
| + return null;
|
| +};
|
| +
|
| +/**
|
| + * This is from https://developer.mozilla.org/en/Whitespace_in_the_DOM
|
| + * Version of |nextSibling| that skips nodes that are entirely
|
| + * whitespace or comments.
|
| + *
|
| + * @param {Node} sib The reference node.
|
| + * @return {Node} Either:
|
| + * 1) The closest next sibling to |sib| that is not
|
| + * ignorable according to |isIgnorable|, or
|
| + * 2) null if no such node exists.
|
| + */
|
| +cvox.SelectionUtil.nodeAfter = function(sib) {
|
| + while ((sib = sib.nextSibling)) {
|
| + if (!cvox.SelectionUtil.isIgnorable(sib)) {
|
| + return sib;
|
| + }
|
| + }
|
| + return null;
|
| +};
|
| +
|
| +/**
|
| + * This is from https://developer.mozilla.org/en/Whitespace_in_the_DOM
|
| + * Version of |lastChild| that skips nodes that are entirely
|
| + * whitespace or comments. (Normally |lastChild| is a property
|
| + * of all DOM nodes that gives the last of the nodes contained
|
| + * directly in the reference node.)
|
| + *
|
| + * @param {Node} par The reference node.
|
| + * @return {Node} Either:
|
| + * 1) The last child of |sib| that is not
|
| + * ignorable according to |isIgnorable|, or
|
| + * 2) null if no such node exists.
|
| + */
|
| +cvox.SelectionUtil.lastChildNode = function(par) {
|
| + var res = par.lastChild;
|
| + while (res) {
|
| + if (!cvox.SelectionUtil.isIgnorable(res)) {
|
| + return res;
|
| + }
|
| + res = res.previousSibling;
|
| + }
|
| + return null;
|
| +};
|
| +
|
| +/**
|
| + * This is from https://developer.mozilla.org/en/Whitespace_in_the_DOM
|
| + * Version of |firstChild| that skips nodes that are entirely
|
| + * whitespace and comments.
|
| + *
|
| + * @param {Node} par The reference node.
|
| + * @return {Node} Either:
|
| + * 1) The first child of |sib| that is not
|
| + * ignorable according to |isIgnorable|, or
|
| + * 2) null if no such node exists.
|
| + */
|
| +cvox.SelectionUtil.firstChildNode = function(par) {
|
| + var res = par.firstChild;
|
| + while (res) {
|
| + if (!cvox.SelectionUtil.isIgnorable(res)) {
|
| + return res;
|
| + }
|
| + res = res.nextSibling;
|
| + }
|
| + return null;
|
| +};
|
| +
|
| +/**
|
| + * This is from https://developer.mozilla.org/en/Whitespace_in_the_DOM
|
| + * Version of |data| that doesn't include whitespace at the beginning
|
| + * and end and normalizes all whitespace to a single space. (Normally
|
| + * |data| is a property of text nodes that gives the text of the node.)
|
| + *
|
| + * @param {Node} txt The text node whose data should be returned.
|
| + * @return {string} A string giving the contents of the text node with
|
| + * whitespace collapsed.
|
| + */
|
| +cvox.SelectionUtil.dataOf = function(txt) {
|
| + var data = txt.data;
|
| + // Use ECMA-262 Edition 3 String and RegExp features
|
| + data = data.replace(/[\t\n\r ]+/g, ' ');
|
| + if (data.charAt(0) == ' ') {
|
| + data = data.substring(1, data.length);
|
| + }
|
| + if (data.charAt(data.length - 1) == ' ') {
|
| + data = data.substring(0, data.length - 1);
|
| + }
|
| + return data;
|
| +};
|
| +
|
| +/**
|
| + * Returns true if the selection has content from at least one node
|
| + * that has the specified tagName.
|
| + *
|
| + * @param {Selection} sel The selection.
|
| + * @param {string} tagName Tagname that the selection should be checked for.
|
| + * @return {boolean} True if the selection has content from at least one node
|
| + * with the specified tagName.
|
| + */
|
| +cvox.SelectionUtil.hasContentWithTag = function(sel, tagName) {
|
| + if (!sel || !sel.anchorNode || !sel.focusNode) {
|
| + return false;
|
| + }
|
| + if (sel.anchorNode.tagName && (sel.anchorNode.tagName == tagName)) {
|
| + return true;
|
| + }
|
| + if (sel.focusNode.tagName && (sel.focusNode.tagName == tagName)) {
|
| + return true;
|
| + }
|
| + if (sel.anchorNode.parentNode.tagName &&
|
| + (sel.anchorNode.parentNode.tagName == tagName)) {
|
| + return true;
|
| + }
|
| + if (sel.focusNode.parentNode.tagName &&
|
| + (sel.focusNode.parentNode.tagName == tagName)) {
|
| + return true;
|
| + }
|
| + var docFrag = sel.getRangeAt(0).cloneContents();
|
| + var span = document.createElement('span');
|
| + span.appendChild(docFrag);
|
| + return (span.getElementsByTagName(tagName).length > 0);
|
| +};
|
| +
|
| +/**
|
| + * Selects text within a text node.
|
| + *
|
| + * Note that the input node MUST be of type TEXT; otherwise, the offset
|
| + * count would not mean # of characters - this is because of the way Range
|
| + * works in JavaScript.
|
| + *
|
| + * @param {Node} textNode The text node to select text within.
|
| + * @param {number} start The start of the selection.
|
| + * @param {number} end The end of the selection.
|
| + */
|
| +cvox.SelectionUtil.selectText = function(textNode, start, end) {
|
| + var newRange = document.createRange();
|
| + newRange.setStart(textNode, start);
|
| + newRange.setEnd(textNode, end);
|
| + var sel = window.getSelection();
|
| + sel.removeAllRanges();
|
| + sel.addRange(newRange);
|
| +};
|
| +
|
| +/**
|
| + * Selects all the text in a given node.
|
| + *
|
| + * @param {Node} node The target node.
|
| + */
|
| +cvox.SelectionUtil.selectAllTextInNode = function(node) {
|
| + var newRange = document.createRange();
|
| + newRange.setStart(node, 0);
|
| + newRange.setEndAfter(node);
|
| + var sel = window.getSelection();
|
| + sel.removeAllRanges();
|
| + sel.addRange(newRange);
|
| +};
|
| +
|
| +/**
|
| + * Retrieves all the text within a selection.
|
| + *
|
| + * Note that this can be different than simply using the string from
|
| + * window.getSelection() as this will account for IMG nodes, etc.
|
| + *
|
| + * @return {string} The string of text contained in the current selection.
|
| + */
|
| +cvox.SelectionUtil.getText = function() {
|
| + var text = '';
|
| + var sel = window.getSelection();
|
| + if (cvox.SelectionUtil.hasContentWithTag(sel, 'IMG')) {
|
| + var docFrag = sel.getRangeAt(0).cloneContents();
|
| + var span = document.createElement('span');
|
| + span.appendChild(docFrag);
|
| + var leafNodes = cvox.XpathUtil.getLeafNodes(span);
|
| + for (var i = 0, node; node = leafNodes[i]; i++) {
|
| + text = text + ' ' + cvox.DomUtil.getText(node);
|
| + }
|
| + } else {
|
| + text = text + sel;
|
| + }
|
| + return text;
|
| +};
|
|
|
| Property changes on: chrome/browser/resources/access_chromevox/common/selection_util.js
|
| ___________________________________________________________________
|
| Added: svn:executable
|
| + *
|
| Added: svn:eol-style
|
| + LF
|
|
|
|
|