Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
michael@0 | 3 | * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | const LINKIFY_TIMEOUT = 0; |
michael@0 | 6 | |
michael@0 | 7 | function Linkifier() { |
michael@0 | 8 | this._linkifyTimer = null; |
michael@0 | 9 | this._phoneRegex = /(?:\s|^)[\+]?(\(?\d{1,8}\)?)?([- ]+\(?\d{1,8}\)?)+( ?(x|ext) ?\d{1,3})?(?:\s|$)/g; |
michael@0 | 10 | } |
michael@0 | 11 | |
michael@0 | 12 | Linkifier.prototype = { |
michael@0 | 13 | _buildAnchor : function(aDoc, aNumberText) { |
michael@0 | 14 | let anchorNode = aDoc.createElement("a"); |
michael@0 | 15 | let cleanedText = ""; |
michael@0 | 16 | for (let i = 0; i < aNumberText.length; i++) { |
michael@0 | 17 | let c = aNumberText.charAt(i); |
michael@0 | 18 | if ((c >= '0' && c <= '9') || c == '+') //assuming there is only the leading '+'. |
michael@0 | 19 | cleanedText += c; |
michael@0 | 20 | } |
michael@0 | 21 | anchorNode.setAttribute("href", "tel:" + cleanedText); |
michael@0 | 22 | let nodeText = aDoc.createTextNode(aNumberText); |
michael@0 | 23 | anchorNode.appendChild(nodeText); |
michael@0 | 24 | return anchorNode; |
michael@0 | 25 | }, |
michael@0 | 26 | |
michael@0 | 27 | _linkifyNodeNumbers : function(aNodeToProcess, aDoc) { |
michael@0 | 28 | let parent = aNodeToProcess.parentNode; |
michael@0 | 29 | let nodeText = aNodeToProcess.nodeValue; |
michael@0 | 30 | |
michael@0 | 31 | // Replacing the original text node with a sequence of |
michael@0 | 32 | // |text before number|anchor with number|text after number nodes. |
michael@0 | 33 | // Each step a couple of (optional) text node and anchor node are appended. |
michael@0 | 34 | let anchorNode = null; |
michael@0 | 35 | let m = null; |
michael@0 | 36 | let startIndex = 0; |
michael@0 | 37 | let prevNode = null; |
michael@0 | 38 | while (m = this._phoneRegex.exec(nodeText)) { |
michael@0 | 39 | anchorNode = this._buildAnchor(aDoc, nodeText.substr(m.index, m[0].length)); |
michael@0 | 40 | |
michael@0 | 41 | let textExistsBeforeNumber = (m.index > startIndex); |
michael@0 | 42 | let nodeToAdd = null; |
michael@0 | 43 | if (textExistsBeforeNumber) |
michael@0 | 44 | nodeToAdd = aDoc.createTextNode(nodeText.substr(startIndex, m.index - startIndex)); |
michael@0 | 45 | else |
michael@0 | 46 | nodeToAdd = anchorNode; |
michael@0 | 47 | |
michael@0 | 48 | if (!prevNode) // first time, need to replace the whole node with the first new one. |
michael@0 | 49 | parent.replaceChild(nodeToAdd, aNodeToProcess); |
michael@0 | 50 | else |
michael@0 | 51 | parent.insertBefore(nodeToAdd, prevNode.nextSibling); //inserts after. |
michael@0 | 52 | |
michael@0 | 53 | if (textExistsBeforeNumber) // if we added the text node before the anchor, we still need to add the anchor node. |
michael@0 | 54 | parent.insertBefore(anchorNode, nodeToAdd.nextSibling); |
michael@0 | 55 | |
michael@0 | 56 | // next nodes need to be appended to this node. |
michael@0 | 57 | prevNode = anchorNode; |
michael@0 | 58 | startIndex = m.index + m[0].length; |
michael@0 | 59 | } |
michael@0 | 60 | |
michael@0 | 61 | // if some text is remaining after the last anchor. |
michael@0 | 62 | if (startIndex > 0 && startIndex < nodeText.length) { |
michael@0 | 63 | let lastNode = aDoc.createTextNode(nodeText.substr(startIndex)); |
michael@0 | 64 | parent.insertBefore(lastNode, prevNode.nextSibling); |
michael@0 | 65 | return lastNode; |
michael@0 | 66 | } |
michael@0 | 67 | return anchorNode; |
michael@0 | 68 | }, |
michael@0 | 69 | |
michael@0 | 70 | linkifyNumbers: function(aDoc) { |
michael@0 | 71 | // Removing any installed timer in case the page has changed and a previous timer is still running. |
michael@0 | 72 | if (this._linkifyTimer) { |
michael@0 | 73 | clearTimeout(this._linkifyTimer); |
michael@0 | 74 | this._linkifyTimer = null; |
michael@0 | 75 | } |
michael@0 | 76 | |
michael@0 | 77 | let filterNode = function (node) { |
michael@0 | 78 | if (node.parentNode.tagName != 'A' && |
michael@0 | 79 | node.parentNode.tagName != 'SCRIPT' && |
michael@0 | 80 | node.parentNode.tagName != 'NOSCRIPT' && |
michael@0 | 81 | node.parentNode.tagName != 'STYLE' && |
michael@0 | 82 | node.parentNode.tagName != 'APPLET' && |
michael@0 | 83 | node.parentNode.tagName != 'TEXTAREA') |
michael@0 | 84 | return NodeFilter.FILTER_ACCEPT; |
michael@0 | 85 | else |
michael@0 | 86 | return NodeFilter.FILTER_REJECT; |
michael@0 | 87 | } |
michael@0 | 88 | |
michael@0 | 89 | let nodeWalker = aDoc.createTreeWalker(aDoc.body, NodeFilter.SHOW_TEXT, filterNode, false); |
michael@0 | 90 | let parseNode = function() { |
michael@0 | 91 | let node = nodeWalker.nextNode(); |
michael@0 | 92 | if (!node) { |
michael@0 | 93 | this._linkifyTimer = null; |
michael@0 | 94 | return; |
michael@0 | 95 | } |
michael@0 | 96 | let lastAddedNode = this._linkifyNodeNumbers(node, aDoc); |
michael@0 | 97 | // we assign a different timeout whether the node was processed or not. |
michael@0 | 98 | if (lastAddedNode) { |
michael@0 | 99 | nodeWalker.currentNode = lastAddedNode; |
michael@0 | 100 | this._linkifyTimer = setTimeout(parseNode, LINKIFY_TIMEOUT); |
michael@0 | 101 | } else { |
michael@0 | 102 | this._linkifyTimer = setTimeout(parseNode, LINKIFY_TIMEOUT); |
michael@0 | 103 | } |
michael@0 | 104 | }.bind(this); |
michael@0 | 105 | |
michael@0 | 106 | this._linkifyTimer = setTimeout(parseNode, LINKIFY_TIMEOUT); |
michael@0 | 107 | } |
michael@0 | 108 | }; |