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