Index: ios/chrome/browser/find_in_page/resources/find_in_page.js |
diff --git a/ios/chrome/browser/find_in_page/resources/find_in_page.js b/ios/chrome/browser/find_in_page/resources/find_in_page.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..c1075824a5ac77237c54e9e345ffd54324d21e5f |
--- /dev/null |
+++ b/ios/chrome/browser/find_in_page/resources/find_in_page.js |
@@ -0,0 +1,964 @@ |
+// Copyright 2012 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. |
+ |
+/** |
+ * This file is lifted from the GoogleMobile find tool. |
+ * |
+ * @fileoverview A find in page tool. It scans the DOM for elements with the |
+ * text being search for, and wraps them with a span that highlights them. |
+ * |
+ * @author bmcmahan@google.com (Benjamin McMahan) |
+ * |
+ */ |
+ |
+/** |
+ * Namespace for this file. Depends on __gCrWeb having already been injected. |
+ */ |
+__gCrWeb['findInPage'] = {}; |
+ |
+/** |
+ * Index of the current highlighted choice. -1 means none. |
+ * @type {number} |
+ */ |
+__gCrWeb['findInPage']['index'] = -1; |
+ |
+/** |
+ * The list of found searches in span form. |
+ * @type {Array.<Element>} |
+ */ |
+__gCrWeb['findInPage']['spans'] = []; |
+ |
+/** |
+ * The list of frame documents. |
+ * TODO(justincohen): x-domain frames won't work. |
+ * @type {Array.<Document>} |
+ */ |
+__gCrWeb['findInPage'].frameDocs = []; |
+ |
+/** |
+ * Associate array to stash element styles while the element is highlighted. |
+ * @type {Object.<Element,Object<string,string>>} |
+ */ |
+__gCrWeb['findInPage'].savedElementStyles = {}; |
+ |
+/** |
+ * The style DOM element that we add. |
+ * @type {Element} |
+ */ |
+__gCrWeb['findInPage'].style = null; |
+ |
+/** |
+ * Width we expect the page to be. For example (320/480) for iphone, |
+ * (1024/768) for ipad. |
+ * @type {number} |
+ */ |
+__gCrWeb['findInPage'].pageWidth = 320; |
+ |
+/** |
+ * Height we expect the page to be. |
+ * @type {number} |
+ */ |
+__gCrWeb['findInPage'].pageHeight = 480; |
+ |
+/** |
+ * Maximum number of visible elements to count |
+ * @type {number} |
+ */ |
+__gCrWeb['findInPage'].maxVisibleElements = 100; |
+ |
+/** |
+ * A search is in progress. |
+ * @type {boolean} |
+ */ |
+__gCrWeb['findInPage'].searchInProgress = false; |
+ |
+/** |
+ * Node names that are not going to be processed. |
+ * @type {Object} |
+ */ |
+__gCrWeb['findInPage'].ignoreNodeNames = { |
+ 'SCRIPT': 1, |
+ 'STYLE': 1, |
+ 'EMBED': 1, |
+ 'OBJECT': 1 |
+}; |
+ |
+/** |
+ * Class name of CSS element. |
+ * @type {string} |
+ */ |
+__gCrWeb['findInPage'].CSS_CLASS_NAME = 'find_in_page'; |
+ |
+/** |
+ * ID of CSS style. |
+ * @type {string} |
+ */ |
+__gCrWeb['findInPage'].CSS_STYLE_ID = '__gCrWeb.findInPageStyle'; |
+ |
+/** |
+ * Result passed back to app to indicate no results for the query. |
+ * @type {string} |
+ */ |
+__gCrWeb['findInPage'].NO_RESULTS = '[0,[0,0,0]]'; |
+ |
+/** |
+ * Regex to escape regex special characters in a string. |
+ * @type {RegExp} |
+ */ |
+__gCrWeb['findInPage'].REGEX_ESCAPER = /([.?*+^$[\]\\(){}|-])/g; |
+ |
+__gCrWeb['findInPage'].getCurrentSpan = function() { |
+ return __gCrWeb['findInPage']['spans'][__gCrWeb['findInPage']['index']]; |
+}; |
+ |
+/** |
+ * Creates the regex needed to find the text. |
+ * @param {string} findText Phrase to look for. |
+ * @param {boolean} opt_split True to split up the phrase. |
+ * @return {RegExp} regex needed to find the text. |
+ */ |
+__gCrWeb['findInPage'].getRegex = function(findText, opt_split) { |
+ var regexString = ''; |
+ if (opt_split) { |
+ var words = []; |
+ var split = findText.split(' '); |
+ for (var i = 0; i < split.length; i++) { |
+ words.push(__gCrWeb['findInPage'].escapeRegex(split[i])); |
+ } |
+ var joinedWords = words.join('|'); |
+ regexString = '(' + |
+ // Match at least one word. |
+ '\\b(?:' + joinedWords + ')' + |
+ // Include zero or more additional words separated by whitespace. |
+ '(?:\\s*\\b(?:' + joinedWords + '))*' + |
+ ')'; |
+ } else { |
+ regexString = '(' + __gCrWeb['findInPage'].escapeRegex(findText) + ')'; |
+ } |
+ return new RegExp(regexString, 'ig'); |
+}; |
+ |
+/** |
+ * Get current timestamp. |
+ * @return {number} timestamp. |
+ */ |
+__gCrWeb['findInPage'].time = function() { |
+ return (new Date).getTime(); |
+}; |
+ |
+/** |
+ * After |timeCheck| iterations, return true if |now| - |start| is greater than |
+ * |timeout|. |
+ * @return {boolean} Find in page needs to return. |
+ */ |
+__gCrWeb['findInPage'].overTime = function() { |
+ return (__gCrWeb['findInPage'].time() - __gCrWeb['findInPage'].startTime > |
+ __gCrWeb['findInPage'].timeout); |
+}; |
+ |
+/** |
+ * Looks for a phrase in the DOM. |
+ * @param {string} findText Phrase to look for like "ben franklin". |
+ * @param {boolean} opt_split True to split up the words and look for any |
+ * of them. False to require the full phrase to be there. |
+ * Undefined will try the full phrase, and if nothing is found do the split. |
+ * @param {number} timeout Maximum time to run. |
+ * @return {number} How many results there are in the page. |
+ */ |
+__gCrWeb['findInPage']['highlightWord'] = |
+ function(findText, opt_split, timeout) { |
+ if (__gCrWeb['findInPage']['spans'] && |
+ __gCrWeb['findInPage']['spans'].length) { |
+ // Clean up a previous run. |
+ __gCrWeb['findInPage']['clearHighlight'](); |
+ } |
+ if (!findText || !findText.replace(/\u00a0|\s/g, '')) { |
+ // No searching for emptyness. |
+ return __gCrWeb['findInPage'].NO_RESULTS; |
+ } |
+ |
+ // Store all DOM modifications to do them in a tight loop at once. |
+ __gCrWeb['findInPage'].replacements = []; |
+ |
+ // Node is what we are currently looking at. |
+ __gCrWeb['findInPage'].node = document.body; |
+ |
+ // Holds what nodes we have not processed yet. |
+ __gCrWeb['findInPage'].stack = []; |
+ |
+ // Push frames into stack too. |
+ for (var i = __gCrWeb['findInPage'].frameDocs.length - 1; i >= 0; i--) { |
+ var doc = __gCrWeb['findInPage'].frameDocs[i]; |
+ __gCrWeb['findInPage'].stack.push(doc); |
+ } |
+ |
+ // Number of visible elements found. |
+ __gCrWeb['findInPage'].visibleFound = 0; |
+ |
+ // Index tracking variables so search can be broken up into multiple calls. |
+ __gCrWeb['findInPage'].visibleIndex = 0; |
+ __gCrWeb['findInPage'].replacementsIndex = 0; |
+ __gCrWeb['findInPage'].replacementNewNodesIndex = 0; |
+ |
+ __gCrWeb['findInPage'].regex = |
+ __gCrWeb['findInPage'].getRegex(findText, opt_split); |
+ |
+ __gCrWeb['findInPage'].searchInProgress = true; |
+ |
+ return __gCrWeb['findInPage']['pumpSearch'](timeout); |
+}; |
+ |
+/** |
+ * Break up find in page DOM regex, DOM manipulation and visibility check |
+ * into sections that can be stopped and restarted later. Because the js runs |
+ * in the main UI thread, anything over timeout will cause the UI to lock up. |
+ * @param {number} timeout Only run find in page until timeout. |
+ * @return {boolean} Whether find in page completed. |
+ */ |
+__gCrWeb['findInPage']['pumpSearch'] = function(timeout) { |
+ var opt_split = false; |
+ // TODO(justincohen): It would be better if this DCHECKed. |
+ if (__gCrWeb['findInPage'].searchInProgress == false) |
+ return __gCrWeb['findInPage'].NO_RESULTS; |
+ |
+ __gCrWeb['findInPage'].timeout = timeout; |
+ __gCrWeb['findInPage'].startTime = __gCrWeb['findInPage'].time(); |
+ |
+ var regex = __gCrWeb['findInPage'].regex; |
+ // Go through every node in DFS fashion. |
+ while (__gCrWeb['findInPage'].node) { |
+ var node = __gCrWeb['findInPage'].node; |
+ var children = node.childNodes; |
+ if (children && children.length) { |
+ // add all (reasonable) children |
+ for (var i = children.length - 1; i >= 0; --i) { |
+ var child = children[i]; |
+ if ((child.nodeType == 1 || child.nodeType == 3) && |
+ !__gCrWeb['findInPage'].ignoreNodeNames[child.nodeName]) { |
+ __gCrWeb['findInPage'].stack.push(children[i]); |
+ } |
+ } |
+ } |
+ if (node.nodeType == 3 && node.parentNode) { |
+ var strIndex = 0; |
+ var nodes = []; |
+ var match; |
+ while (match = regex.exec(node.textContent)) { |
+ try { |
+ var matchText = match[0]; |
+ |
+ // If there is content before this match, add it to a new text node. |
+ if (match.index > 0) { |
+ var nodeSubstr = node.textContent.substring(strIndex, |
+ match.index); |
+ nodes.push(node.ownerDocument.createTextNode(nodeSubstr)); |
+ } |
+ |
+ // Now create our matched element. |
+ var element = node.ownerDocument.createElement('chrome_find'); |
+ element.setAttribute('class', __gCrWeb['findInPage'].CSS_CLASS_NAME); |
+ element.innerHTML = __gCrWeb['findInPage'].escapeHTML(matchText); |
+ nodes.push(element); |
+ |
+ strIndex = match.index + matchText.length; |
+ } catch (e) { |
+ // Do nothing. |
+ } |
+ } |
+ if (nodes.length) { |
+ // Add any text after our matches to a new text node. |
+ if (strIndex < node.textContent.length) { |
+ var substr = node.textContent.substring(strIndex, |
+ node.textContent.length); |
+ nodes.push(node.ownerDocument.createTextNode(substr)); |
+ } |
+ __gCrWeb['findInPage'].replacements.push( |
+ {oldNode: node, newNodes: nodes}); |
+ regex.lastIndex = 0; |
+ } |
+ |
+ } |
+ |
+ if (__gCrWeb['findInPage'].overTime()) |
+ return '[false]'; |
+ |
+ if (__gCrWeb['findInPage'].stack.length > 0) { |
+ __gCrWeb['findInPage'].node = __gCrWeb['findInPage'].stack.pop(); |
+ } else { |
+ __gCrWeb['findInPage'].node = null; |
+ } |
+ } |
+ |
+ // Insert each of the replacement nodes into the old node's parent, then |
+ // remove the old node. |
+ var replacements = __gCrWeb['findInPage'].replacements; |
+ |
+ // Last position in replacements array. |
+ var rIndex = __gCrWeb['findInPage'].replacementsIndex; |
+ var rMax = replacements.length; |
+ for (; rIndex < rMax; rIndex++) { |
+ var replacement = replacements[rIndex]; |
+ var parent = replacement.oldNode.parentNode; |
+ if (parent == null) |
+ continue; |
+ var rNodesMax = replacement.newNodes.length; |
+ for (var rNodesIndex = __gCrWeb['findInPage'].replacementNewNodesIndex; |
+ rNodesIndex < rNodesMax; rNodesIndex++) { |
+ if (__gCrWeb['findInPage'].overTime()) { |
+ __gCrWeb['findInPage'].replacementsIndex = rIndex; |
+ __gCrWeb['findInPage'].replacementNewNodesIndex = rNodesIndex; |
+ return __gCrWeb.stringify([false]); |
+ } |
+ parent.insertBefore(replacement.newNodes[rNodesIndex], |
+ replacement.oldNode); |
+ } |
+ parent.removeChild(replacement.oldNode); |
+ __gCrWeb['findInPage'].replacementNewNodesIndex = 0; |
+ } |
+ // Save last position in replacements array. |
+ __gCrWeb['findInPage'].replacementsIndex = rIndex; |
+ |
+ __gCrWeb['findInPage']['spans'] = |
+ __gCrWeb['findInPage'].getAllElementsByClassName( |
+ __gCrWeb['findInPage'].CSS_CLASS_NAME); |
+ |
+ // Count visible elements. |
+ var max = __gCrWeb['findInPage']['spans'].length; |
+ var maxVisible = __gCrWeb['findInPage'].maxVisibleElements; |
+ for (var index = __gCrWeb['findInPage'].visibleIndex; index < max; index++) { |
+ var elem = __gCrWeb['findInPage']['spans'][index]; |
+ if (__gCrWeb['findInPage'].overTime()) { |
+ __gCrWeb['findInPage'].visibleIndex = index; |
+ return __gCrWeb.stringify([false]); |
+ } |
+ |
+ // Stop after |maxVisible| elements. |
+ if (__gCrWeb['findInPage'].visibleFound > maxVisible) { |
+ __gCrWeb['findInPage']['spans'][index].visibleIndex = maxVisible; |
+ continue; |
+ } |
+ |
+ if (__gCrWeb['findInPage'].isVisible(elem)) { |
+ __gCrWeb['findInPage'].visibleFound++; |
+ __gCrWeb['findInPage']['spans'][index].visibleIndex = |
+ __gCrWeb['findInPage'].visibleFound; |
+ } |
+ } |
+ |
+ __gCrWeb['findInPage'].searchInProgress = false; |
+ |
+ var pos; |
+ // Try again flow. |
+ // If opt_split is true, we are done since we won't do any better. |
+ // If opt_split is false, they were explicit about wanting the full thing |
+ // so do not try with a split. |
+ // If opt_split is undefined and we did not find an answer, go ahead and try |
+ // splitting the terms. |
+ if (__gCrWeb['findInPage']['spans'].length == 0 && opt_split === undefined) { |
+ // Try to be more aggressive: |
+ return __gCrWeb['findInPage']['highlightWord'](findText, true); |
+ } else { |
+ pos = __gCrWeb['findInPage']['goNext'](); |
+ if (pos) { |
+ return '[' + __gCrWeb['findInPage'].visibleFound + ',' + pos + ']'; |
+ } else if (opt_split === undefined) { |
+ // Nothing visible, go ahead and be more aggressive. |
+ return __gCrWeb['findInPage']['highlightWord'](findText, true); |
+ } else { |
+ return __gCrWeb['findInPage'].NO_RESULTS; |
+ } |
+ } |
+}; |
+ |
+/** |
+ * Converts a node list to an array. |
+ * @param {object} nodeList DOM node list. |
+ * @return {object} array. |
+ */ |
+__gCrWeb['findInPage'].toArray = function(nodeList) { |
+ var array = []; |
+ for (var i = 0; i < nodeList.length; i++) |
+ array[i] = nodeList[i]; |
+ return array; |
+}; |
+ |
+/** |
+ * Return all elements of class name, spread out over various frames. |
+ * @param {string} name of class. |
+ * @return {object} array of elements matching class name. |
+ */ |
+__gCrWeb['findInPage'].getAllElementsByClassName = function(name) { |
+ var nodeList = document.getElementsByClassName(name); |
+ var elements = __gCrWeb['findInPage'].toArray(nodeList); |
+ for (var i = __gCrWeb['findInPage'].frameDocs.length - 1; i >= 0; i--) { |
+ var doc = __gCrWeb['findInPage'].frameDocs[i]; |
+ nodeList = doc.getElementsByClassName(name); |
+ elements = elements.concat(__gCrWeb['findInPage'].toArray(nodeList)); |
+ } |
+ return elements; |
+}; |
+ |
+/** |
+ * Removes all currently highlighted spans. |
+ * Note: It does not restore previous state, just removes the class name. |
+ */ |
+__gCrWeb['findInPage']['clearHighlight'] = function() { |
+ if (__gCrWeb['findInPage']['index'] >= 0) { |
+ __gCrWeb['findInPage'].removeSelectHighlight( |
+ __gCrWeb['findInPage'].getCurrentSpan()); |
+ } |
+ // Store all DOM modifications to do them in a tight loop. |
+ var modifications = []; |
+ var length = __gCrWeb['findInPage']['spans'].length; |
+ var prevParent = null; |
+ for (var i = length - 1; i >= 0; i--) { |
+ var elem = __gCrWeb['findInPage']['spans'][i]; |
+ var parentNode = elem.parentNode; |
+ // Safari has an occasional |elem.innerText| bug that drops the trailing |
+ // space. |elem.innerText| would be more correct in this situation, but |
+ // since we only allow text in this element, grabbing the HTML value should |
+ // not matter. |
+ var nodeText = elem.innerHTML; |
+ // If this element has the same parent as the previous, check if we should |
+ // add this node to the previous one. |
+ if (prevParent && prevParent.isSameNode(parentNode) && |
+ elem.nextSibling.isSameNode( |
+ __gCrWeb['findInPage']['spans'][i + 1].previousSibling)) { |
+ var prevMod = modifications[modifications.length - 1]; |
+ prevMod.nodesToRemove.push(elem); |
+ var elemText = elem.innerText; |
+ if (elem.previousSibling) { |
+ prevMod.nodesToRemove.push(elem.previousSibling); |
+ elemText = elem.previousSibling.textContent + elemText; |
+ } |
+ prevMod.replacement.textContent = |
+ elemText + prevMod.replacement.textContent; |
+ } |
+ else { // Element isn't attached to previous, so create a new modification. |
+ var nodesToRemove = [elem]; |
+ if (elem.previousSibling && elem.previousSibling.nodeType == 3) { |
+ nodesToRemove.push(elem.previousSibling); |
+ nodeText = elem.previousSibling.textContent + nodeText; |
+ } |
+ if (elem.nextSibling && elem.nextSibling.nodeType == 3) { |
+ nodesToRemove.push(elem.nextSibling); |
+ nodeText = nodeText + elem.nextSibling.textContent; |
+ } |
+ var textNode = elem.ownerDocument.createTextNode(nodeText); |
+ modifications.push({nodesToRemove: nodesToRemove, replacement: textNode}); |
+ } |
+ prevParent = parentNode; |
+ } |
+ var numMods = modifications.length; |
+ for (i = numMods - 1; i >= 0; i--) { |
+ var mod = modifications[i]; |
+ for (var j = 0; j < mod.nodesToRemove.length; j++) { |
+ var existing = mod.nodesToRemove[j]; |
+ if (j == 0) { |
+ existing.parentNode.replaceChild(mod.replacement, existing); |
+ } else { |
+ existing.parentNode.removeChild(existing); |
+ } |
+ } |
+ } |
+ |
+ __gCrWeb['findInPage']['spans'] = []; |
+ __gCrWeb['findInPage']['index'] = -1; |
+}; |
+ |
+/** |
+ * Increments the index of the current highlighted span or, if the index is |
+ * already at the end, sets it to the index of the first span in the page. |
+ */ |
+__gCrWeb['findInPage']['incrementIndex'] = function() { |
+ if (__gCrWeb['findInPage']['index'] >= |
+ __gCrWeb['findInPage']['spans'].length - 1) { |
+ __gCrWeb['findInPage']['index'] = 0; |
+ } else { |
+ __gCrWeb['findInPage']['index']++; |
+ } |
+}; |
+ |
+/** |
+ * Switches to the next result, animating a little highlight in the process. |
+ * @return {string} JSON encoded array of coordinates to scroll to, or blank if |
+ * nothing happened. |
+ */ |
+__gCrWeb['findInPage']['goNext'] = function() { |
+ if (!__gCrWeb['findInPage']['spans'] || |
+ __gCrWeb['findInPage']['spans'].length == 0) { |
+ return ''; |
+ } |
+ if (__gCrWeb['findInPage']['index'] >= 0) { |
+ // Remove previous highlight. |
+ __gCrWeb['findInPage'].removeSelectHighlight( |
+ __gCrWeb['findInPage'].getCurrentSpan()); |
+ } |
+ // Iterate through to the next index, but because they might not be visible, |
+ // keep trying until you find one that is. Make sure we don't loop forever by |
+ // stopping on what we are currently highlighting. |
+ var oldIndex = __gCrWeb['findInPage']['index']; |
+ __gCrWeb['findInPage']['incrementIndex'](); |
+ while (!__gCrWeb['findInPage'].isVisible( |
+ __gCrWeb['findInPage'].getCurrentSpan())) { |
+ if (oldIndex === __gCrWeb['findInPage']['index']) { |
+ // Checked all spans but didn't find anything else visible. |
+ return ''; |
+ } |
+ __gCrWeb['findInPage']['incrementIndex'](); |
+ if (0 === __gCrWeb['findInPage']['index'] && oldIndex < 0) { |
+ // Didn't find anything visible and haven't highlighted anything yet. |
+ return ''; |
+ } |
+ } |
+ // Return scroll dimensions. |
+ return __gCrWeb['findInPage'].findScrollDimensions(); |
+}; |
+ |
+/** |
+ * Decrements the index of the current highlighted span or, if the index is |
+ * already at the beginning, sets it to the index of the last span in the page. |
+ */ |
+__gCrWeb['findInPage']['decrementIndex'] = function() { |
+ if (__gCrWeb['findInPage']['index'] <= 0) { |
+ __gCrWeb['findInPage']['index'] = |
+ __gCrWeb['findInPage']['spans'].length - 1; |
+ } else { |
+ __gCrWeb['findInPage']['index']--; |
+ } |
+}; |
+ |
+/** |
+ * Switches to the previous result, animating a little highlight in the process. |
+ * @return {string} JSON encoded array of coordinates to scroll to, or blank if |
+ * nothing happened. |
+ */ |
+__gCrWeb['findInPage']['goPrev'] = function() { |
+ if (!__gCrWeb['findInPage']['spans'] || |
+ __gCrWeb['findInPage']['spans'].length == 0) { |
+ return ''; |
+ } |
+ if (__gCrWeb['findInPage']['index'] >= 0) { |
+ // Remove previous highlight. |
+ __gCrWeb['findInPage'].removeSelectHighlight( |
+ __gCrWeb['findInPage'].getCurrentSpan()); |
+ } |
+ // Iterate through to the next index, but because they might not be visible, |
+ // keep trying until you find one that is. Make sure we don't loop forever by |
+ // stopping on what we are currently highlighting. |
+ var old = __gCrWeb['findInPage']['index']; |
+ __gCrWeb['findInPage']['decrementIndex'](); |
+ while (!__gCrWeb['findInPage'].isVisible( |
+ __gCrWeb['findInPage'].getCurrentSpan())) { |
+ __gCrWeb['findInPage']['decrementIndex'](); |
+ if (old == __gCrWeb['findInPage']['index']) { |
+ // Checked all spans but didn't find anything. |
+ return ''; |
+ } |
+ } |
+ |
+ // Return scroll dimensions. |
+ return __gCrWeb['findInPage'].findScrollDimensions(); |
+}; |
+ |
+/** |
+ * Adds the special highlighting to the result at the index. |
+ * @param {number} opt_index Index to replace __gCrWeb['findInPage']['index'] |
+ * with. |
+ */ |
+__gCrWeb['findInPage'].addHighlightToIndex = function(opt_index) { |
+ if (opt_index !== undefined) { |
+ __gCrWeb['findInPage']['index'] = opt_index; |
+ } |
+ __gCrWeb['findInPage'].addSelectHighlight( |
+ __gCrWeb['findInPage'].getCurrentSpan()); |
+}; |
+ |
+/** |
+ * Updates the elements style, while saving the old style in |
+ * __gCrWeb['findInPage'].savedElementStyles. |
+ * @param {Element} element Element to update. |
+ * @param {string} style Name of the style to update. |
+ * @param {string} value New style value. |
+ */ |
+__gCrWeb['findInPage'].updateElementStyle = function(element, style, value) { |
+ if (!__gCrWeb['findInPage'].savedElementStyles[element]) { |
+ __gCrWeb['findInPage'].savedElementStyles[element] = {}; |
+ } |
+ // We need to keep the original style setting for this element, so if we've |
+ // already saved a value for this style don't update it. |
+ if (!__gCrWeb['findInPage'].savedElementStyles[element][style]) { |
+ __gCrWeb['findInPage'].savedElementStyles[element][style] = |
+ element.style[style]; |
+ } |
+ element.style[style] = value; |
+}; |
+ |
+/** |
+ * Adds selected highlight style to the specified element. |
+ * @param {Element} element Element to highlight. |
+ */ |
+__gCrWeb['findInPage'].addSelectHighlight = function(element) { |
+ element.className = (element.className || '') + ' findysel'; |
+}; |
+ |
+/** |
+ * Removes selected highlight style from the specified element. |
+ * @param {Element} element Element to remove highlighting from. |
+ */ |
+__gCrWeb['findInPage'].removeSelectHighlight = function(element) { |
+ element.className = (element.className || '').replace(/\sfindysel/g, ''); |
+ |
+ // Restore any styles we may have saved when adding the select highlighting. |
+ var savedStyles = __gCrWeb['findInPage'].savedElementStyles[element]; |
+ if (savedStyles) { |
+ for (var style in savedStyles) { |
+ element.style[style] = savedStyles[style]; |
+ } |
+ delete __gCrWeb['findInPage'].savedElementStyles[element]; |
+ } |
+}; |
+ |
+/** |
+ * Normalize coordinates according to the current document dimensions. Don't go |
+ * too far off the screen in either direction. Try to center if possible. |
+ * @param {Element} elem Element to find normalized coordinates for. |
+ * @return {Array.<number>} Normalized coordinates. |
+ */ |
+__gCrWeb['findInPage'].getNormalizedCoordinates = function(elem) { |
+ var fip = __gCrWeb['findInPage']; |
+ var pos = fip.findAbsolutePosition(elem); |
+ var maxX = |
+ Math.max(fip.getBodyWidth(), pos[0] + elem.offsetWidth); |
+ var maxY = |
+ Math.max(fip.getBodyHeight(), pos[1] + elem.offsetHeight); |
+ // Don't go too far off the screen in either direction. Try to center if |
+ // possible. |
+ var xPos = Math.max(0, |
+ Math.min(maxX - window.innerWidth, |
+ pos[0] - (window.innerWidth / 2))); |
+ var yPos = Math.max(0, |
+ Math.min(maxY - window.innerHeight, |
+ pos[1] - (window.innerHeight / 2))); |
+ return [xPos, yPos]; |
+}; |
+ |
+/** |
+ * Scale coordinates according to the width of the screen, in case the screen |
+ * is zoomed out. |
+ * @param {Array.<number>} coordinates Coordinates to scale. |
+ * @return {Array.<number>} Scaled coordinates. |
+ */ |
+__gCrWeb['findInPage'].scaleCoordinates = function(coordinates) { |
+ var scaleFactor = __gCrWeb['findInPage'].pageWidth / window.innerWidth; |
+ return [coordinates[0] * scaleFactor, coordinates[1] * scaleFactor]; |
+}; |
+ |
+/** |
+ * Finds the position of the result and scrolls to it. |
+ * @param {number} opt_index Index to replace __gCrWeb['findInPage']['index'] |
+ * with. |
+ * @return {string} JSON encoded array of the scroll coordinates "[x, y]". |
+ */ |
+__gCrWeb['findInPage'].findScrollDimensions = function(opt_index) { |
+ if (opt_index !== undefined) { |
+ __gCrWeb['findInPage']['index'] = opt_index; |
+ } |
+ var elem = __gCrWeb['findInPage'].getCurrentSpan(); |
+ if (!elem) { |
+ return ''; |
+ } |
+ var normalized = __gCrWeb['findInPage'].getNormalizedCoordinates(elem); |
+ var xPos = normalized[0]; |
+ var yPos = normalized[1]; |
+ |
+ // Perform the scroll. |
+ //window.scrollTo(xPos, yPos); |
+ |
+ if (xPos < window.pageXOffset || |
+ xPos >= (window.pageXOffset + window.innerWidth) || |
+ yPos < window.pageYOffset || |
+ yPos >= (window.pageYOffset + window.innerHeight)) { |
+ // If it's off the screen. Wait a bit to start the highlight animation so |
+ // that scrolling can get there first. |
+ window.setTimeout(__gCrWeb['findInPage'].addHighlightToIndex, 250); |
+ } else { |
+ __gCrWeb['findInPage'].addHighlightToIndex(); |
+ } |
+ var scaled = __gCrWeb['findInPage'].scaleCoordinates(normalized); |
+ var index = __gCrWeb['findInPage'].getCurrentSpan().visibleIndex; |
+ scaled.unshift(index); |
+ return __gCrWeb.stringify(scaled); |
+}; |
+ |
+/** |
+ * Initialize the __gCrWeb['findInPage'] module. |
+ * @param {number} width Width of page. |
+ * @param {number} height Height of page. |
+ |
+ */ |
+__gCrWeb['findInPage']['init'] = function(width, height) { |
+ if (__gCrWeb['findInPage'].hasInitialized) { |
+ return; |
+ } |
+ __gCrWeb['findInPage'].pageWidth = width; |
+ __gCrWeb['findInPage'].pageHeight = height; |
+ __gCrWeb['findInPage'].frameDocs = __gCrWeb['findInPage'].frameDocuments(); |
+ __gCrWeb['findInPage'].enable(); |
+ __gCrWeb['findInPage'].hasInitialized = true; |
+}; |
+ |
+/** |
+ * When the GSA app detects a zoom change, we need to update our css. |
+ * @param {number} width Width of page. |
+ * @param {number} height Height of page. |
+ */ |
+__gCrWeb['findInPage']['fixZoom'] = function(width, height) { |
+ __gCrWeb['findInPage'].pageWidth = width; |
+ __gCrWeb['findInPage'].pageHeight = height; |
+ if (__gCrWeb['findInPage'].style) { |
+ __gCrWeb['findInPage'].removeStyle(); |
+ __gCrWeb['findInPage'].addStyle(); |
+ } |
+}; |
+ |
+/** |
+ * Enable the __gCrWeb['findInPage'] module. |
+ * Mainly just adds the style for the classes. |
+ */ |
+__gCrWeb['findInPage'].enable = function() { |
+ if (__gCrWeb['findInPage'].style) { |
+ // Already enabled. |
+ return; |
+ } |
+ __gCrWeb['findInPage'].addStyle(); |
+}; |
+ |
+/** |
+ * Gets the scale ratio between the application window and the web document. |
+ * @return {number} Scale. |
+ */ |
+__gCrWeb['findInPage'].getPageScale = function() { |
+ return (__gCrWeb['findInPage'].pageWidth / |
+ __gCrWeb['findInPage'].getBodyWidth()); |
+}; |
+ |
+/** |
+ * Maximum padding added to a highlighted item when selected. |
+ * @type {number} |
+ */ |
+__gCrWeb['findInPage'].MAX_HIGHLIGHT_PADDING = 10; |
+ |
+/** |
+ * Adds the appropriate style element to the page. |
+ */ |
+__gCrWeb['findInPage'].addStyle = function() { |
+ __gCrWeb['findInPage'].addDocumentStyle(document); |
+ for (var i = __gCrWeb['findInPage'].frameDocs.length - 1; i >= 0; i--) { |
+ var doc = __gCrWeb['findInPage'].frameDocs[i]; |
+ __gCrWeb['findInPage'].addDocumentStyle(doc); |
+ } |
+}; |
+ |
+__gCrWeb['findInPage'].addDocumentStyle = function(thisDocument) { |
+ var styleContent = []; |
+ function addCSSRule(name, style) { |
+ styleContent.push(name, '{', style, '}'); |
+ }; |
+ var scale = __gCrWeb['findInPage'].getPageScale(); |
+ var zoom = (1.0 / scale); |
+ var left = ((1 - scale) / 2 * 100); |
+ addCSSRule('.' + __gCrWeb['findInPage'].CSS_CLASS_NAME, |
+ 'background-color:#ffff00 !important;' + |
+ 'padding:0px;margin:0px;' + |
+ 'overflow:visible !important;'); |
+ addCSSRule('.findysel', |
+ 'background-color:#ff9632 !important;' + |
+ 'padding:0px;margin:0px;' + |
+ 'overflow:visible !important;'); |
+ __gCrWeb['findInPage'].style = thisDocument.createElement('style'); |
+ __gCrWeb['findInPage'].style.id = __gCrWeb['findInPage'].CSS_STYLE_ID; |
+ __gCrWeb['findInPage'].style.setAttribute('type', 'text/css'); |
+ __gCrWeb['findInPage'].style.appendChild( |
+ thisDocument.createTextNode(styleContent.join(''))); |
+ thisDocument.body.appendChild(__gCrWeb['findInPage'].style); |
+}; |
+ |
+/** |
+ * Removes the style element from the page. |
+ */ |
+__gCrWeb['findInPage'].removeStyle = function() { |
+ if (__gCrWeb['findInPage'].style) { |
+ __gCrWeb['findInPage'].removeDocumentStyle(document); |
+ for (var i = __gCrWeb['findInPage'].frameDocs.length - 1; i >= 0; i--) { |
+ var doc = __gCrWeb['findInPage'].frameDocs[i]; |
+ __gCrWeb['findInPage'].removeDocumentStyle(doc); |
+ } |
+ __gCrWeb['findInPage'].style = null; |
+ } |
+}; |
+ |
+__gCrWeb['findInPage'].removeDocumentStyle = function(thisDocument) { |
+ var style = thisDocument.getElementById(__gCrWeb['findInPage'].CSS_STYLE_ID); |
+ thisDocument.body.removeChild(style); |
+}; |
+ |
+/** |
+ * Disables the __gCrWeb['findInPage'] module. |
+ * Basically just removes the style and class names. |
+ */ |
+__gCrWeb['findInPage']['disable'] = function() { |
+ if (__gCrWeb['findInPage'].style) { |
+ __gCrWeb['findInPage'].removeStyle(); |
+ window.setTimeout(__gCrWeb['findInPage']['clearHighlight'], 0); |
+ } |
+ __gCrWeb['findInPage'].hasInitialized = false; |
+}; |
+ |
+/** |
+* Returns the width of the document.body. Sometimes though the body lies to |
+* try to make the page not break rails, so attempt to find those as well. |
+* An example: wikipedia pages for the ipad. |
+* @return {number} Width of the document body. |
+*/ |
+__gCrWeb['findInPage'].getBodyWidth = function() { |
+ var body = document.body; |
+ var documentElement = document.documentElement; |
+ return Math.max(body.scrollWidth, documentElement.scrollWidth, |
+ body.offsetWidth, documentElement.offsetWidth, |
+ body.clientWidth, documentElement.clientWidth); |
+}; |
+ |
+/** |
+ * Returns the height of the document.body. Sometimes though the body lies to |
+ * try to make the page not break rails, so attempt to find those as well. |
+ * An example: wikipedia pages for the ipad. |
+ * @return {number} Height of the document body. |
+ */ |
+__gCrWeb['findInPage'].getBodyHeight = function() { |
+ var body = document.body; |
+ var documentElement = document.documentElement; |
+ return Math.max(body.scrollHeight, documentElement.scrollHeight, |
+ body.offsetHeight, documentElement.offsetHeight, |
+ body.clientHeight, documentElement.clientHeight); |
+}; |
+ |
+/** |
+ * Helper function that determines if an element is visible. |
+ * @param {Element} elem Element to check. |
+ * @return {boolean} Whether elem is visible or not. |
+ */ |
+__gCrWeb['findInPage'].isVisible = function(elem) { |
+ if (!elem) { |
+ return false; |
+ } |
+ var top = 0; |
+ var left = 0; |
+ var bottom = Infinity; |
+ var right = Infinity; |
+ |
+ var originalElement = elem; |
+ var nextOffsetParent = originalElement.offsetParent; |
+ var computedStyle = |
+ elem.ownerDocument.defaultView.getComputedStyle(elem, null); |
+ |
+ // We are currently handling all scrolling through the app, which means we can |
+ // only scroll the window, not any scrollable containers in the DOM itself. So |
+ // for now this function returns false if the element is scrolled outside the |
+ // viewable area of its ancestors. |
+ // TODO (jonwall): handle scrolling within the DOM. |
+ var pageHeight = __gCrWeb['findInPage'].getBodyHeight(); |
+ var pageWidth = __gCrWeb['findInPage'].getBodyWidth(); |
+ |
+ while (elem && elem.nodeName != 'BODY') { |
+ if (elem.style.display === 'none' || |
+ elem.style.visibility === 'hidden' || |
+ elem.style.opacity === 0 || |
+ computedStyle.display === 'none' || |
+ computedStyle.visibility === 'hidden' || |
+ computedStyle.opacity === 0) { |
+ return false; |
+ } |
+ |
+ // For the original element and all ancestor offsetParents, trim down the |
+ // visible area of the original element. |
+ if (elem.isSameNode(originalElement) || elem.isSameNode(nextOffsetParent)) { |
+ var visible = elem.getBoundingClientRect(); |
+ if (elem.style.overflow === 'hidden' && |
+ (visible.width === 0 || visible.height === 0)) |
+ return false; |
+ |
+ top = Math.max(top, visible.top + window.pageYOffset); |
+ bottom = Math.min(bottom, visible.bottom + window.pageYOffset); |
+ left = Math.max(left, visible.left + window.pageXOffset); |
+ right = Math.min(right, visible.right + window.pageXOffset); |
+ |
+ // The element is not within the original viewport. |
+ var notWithinViewport = top < 0 || left < 0; |
+ |
+ // The element is flowing off the boundary of the page. Note this is |
+ // not comparing to the size of the window, but the calculated offset |
+ // size of the document body. This can happen if the element is within |
+ // a scrollable container in the page. |
+ var offPage = right > pageWidth || bottom > pageHeight; |
+ if (notWithinViewport || offPage) { |
+ return false; |
+ } |
+ nextOffsetParent = elem.offsetParent; |
+ } |
+ |
+ elem = elem.parentNode; |
+ computedStyle = elem.ownerDocument.defaultView.getComputedStyle(elem, null); |
+ } |
+ return true; |
+}; |
+ |
+/** |
+ * Helper function to find the absolute position of an element on the page. |
+ * @param {Element} elem Element to check. |
+ * @return {Array.<number>} [x, y] positions. |
+ */ |
+__gCrWeb['findInPage'].findAbsolutePosition = function(elem) { |
+ var boundingRect = elem.getBoundingClientRect(); |
+ return [boundingRect.left + window.pageXOffset, |
+ boundingRect.top + window.pageYOffset]; |
+}; |
+ |
+/** |
+ * @param {string} text Text to escape. |
+ * @return {string} escaped text. |
+ */ |
+__gCrWeb['findInPage'].escapeHTML = function(text) { |
+ var unusedDiv = document.createElement('div'); |
+ unusedDiv.innerText = text; |
+ return unusedDiv.innerHTML; |
+}; |
+ |
+/** |
+ * Escapes regexp special characters. |
+ * @param {string} text Text to escape. |
+ * @return {string} escaped text. |
+ */ |
+__gCrWeb['findInPage'].escapeRegex = function(text) { |
+ return text.replace(__gCrWeb['findInPage'].REGEX_ESCAPER, '\\$1'); |
+}; |
+ |
+/** |
+ * Gather all iframes in the main window. |
+ * @return {Array.<Document>} frames. |
+ */ |
+__gCrWeb['findInPage'].frameDocuments = function() { |
+ var windowsToSearch = [window]; |
+ var documents = []; |
+ while (windowsToSearch.length != 0) { |
+ var win = windowsToSearch.pop(); |
+ for (var i = win.frames.length - 1; i >= 0; i--) { |
+ if (win.frames[i].document) { |
+ documents.push(win.frames[i].document); |
+ windowsToSearch.push(win.frames[i]); |
+ } |
+ } |
+ } |
+ return documents; |
+}; |