mobile/android/chrome/content/Linkify.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/mobile/android/chrome/content/Linkify.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,108 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +const LINKIFY_TIMEOUT = 0;
     1.9 +
    1.10 +function Linkifier() {
    1.11 +  this._linkifyTimer = null;
    1.12 +  this._phoneRegex = /(?:\s|^)[\+]?(\(?\d{1,8}\)?)?([- ]+\(?\d{1,8}\)?)+( ?(x|ext) ?\d{1,3})?(?:\s|$)/g;
    1.13 +}
    1.14 +
    1.15 +Linkifier.prototype = {
    1.16 +  _buildAnchor : function(aDoc, aNumberText) {
    1.17 +    let anchorNode = aDoc.createElement("a");
    1.18 +    let cleanedText = "";
    1.19 +    for (let i = 0; i < aNumberText.length; i++) {
    1.20 +      let c = aNumberText.charAt(i);
    1.21 +      if ((c >= '0' && c <= '9') || c == '+')  //assuming there is only the leading '+'.
    1.22 +        cleanedText += c;
    1.23 +    }
    1.24 +    anchorNode.setAttribute("href", "tel:" + cleanedText);
    1.25 +    let nodeText = aDoc.createTextNode(aNumberText);
    1.26 +    anchorNode.appendChild(nodeText);
    1.27 +    return anchorNode;
    1.28 +  },
    1.29 +
    1.30 +  _linkifyNodeNumbers : function(aNodeToProcess, aDoc) {
    1.31 +    let parent = aNodeToProcess.parentNode;
    1.32 +    let nodeText = aNodeToProcess.nodeValue;
    1.33 +
    1.34 +    // Replacing the original text node with a sequence of
    1.35 +    // |text before number|anchor with number|text after number nodes.
    1.36 +    // Each step a couple of (optional) text node and anchor node are appended.
    1.37 +    let anchorNode = null;
    1.38 +    let m = null;
    1.39 +    let startIndex = 0;
    1.40 +    let prevNode = null;
    1.41 +    while (m = this._phoneRegex.exec(nodeText)) {
    1.42 +      anchorNode = this._buildAnchor(aDoc, nodeText.substr(m.index, m[0].length));
    1.43 +
    1.44 +      let textExistsBeforeNumber = (m.index > startIndex);
    1.45 +      let nodeToAdd = null;
    1.46 +      if (textExistsBeforeNumber)
    1.47 +        nodeToAdd = aDoc.createTextNode(nodeText.substr(startIndex, m.index - startIndex));
    1.48 +      else
    1.49 +        nodeToAdd = anchorNode;
    1.50 +
    1.51 +      if (!prevNode) // first time, need to replace the whole node with the first new one.
    1.52 +        parent.replaceChild(nodeToAdd, aNodeToProcess);
    1.53 +      else
    1.54 +        parent.insertBefore(nodeToAdd, prevNode.nextSibling); //inserts after.
    1.55 +
    1.56 +      if (textExistsBeforeNumber) // if we added the text node before the anchor, we still need to add the anchor node.
    1.57 +        parent.insertBefore(anchorNode, nodeToAdd.nextSibling);
    1.58 +
    1.59 +      // next nodes need to be appended to this node.
    1.60 +      prevNode = anchorNode; 
    1.61 +      startIndex = m.index + m[0].length;
    1.62 +    }
    1.63 +
    1.64 +    // if some text is remaining after the last anchor.
    1.65 +    if (startIndex > 0 && startIndex < nodeText.length) {
    1.66 +      let lastNode = aDoc.createTextNode(nodeText.substr(startIndex));
    1.67 +      parent.insertBefore(lastNode, prevNode.nextSibling);
    1.68 +      return lastNode;
    1.69 +    }
    1.70 +    return anchorNode;
    1.71 +  },
    1.72 +
    1.73 +  linkifyNumbers: function(aDoc) {
    1.74 +    // Removing any installed timer in case the page has changed and a previous timer is still running.
    1.75 +    if (this._linkifyTimer) {
    1.76 +      clearTimeout(this._linkifyTimer);
    1.77 +      this._linkifyTimer = null;
    1.78 +    }
    1.79 +
    1.80 +    let filterNode = function (node) {
    1.81 +      if (node.parentNode.tagName != 'A' &&
    1.82 +         node.parentNode.tagName != 'SCRIPT' &&
    1.83 +         node.parentNode.tagName != 'NOSCRIPT' &&
    1.84 +         node.parentNode.tagName != 'STYLE' &&
    1.85 +         node.parentNode.tagName != 'APPLET' &&
    1.86 +         node.parentNode.tagName != 'TEXTAREA')
    1.87 +        return NodeFilter.FILTER_ACCEPT;
    1.88 +      else
    1.89 +        return NodeFilter.FILTER_REJECT;
    1.90 +    }
    1.91 +
    1.92 +    let nodeWalker = aDoc.createTreeWalker(aDoc.body, NodeFilter.SHOW_TEXT, filterNode, false);
    1.93 +    let parseNode = function() {
    1.94 +      let node = nodeWalker.nextNode();
    1.95 +      if (!node) {
    1.96 +        this._linkifyTimer = null;
    1.97 +        return;
    1.98 +      }
    1.99 +      let lastAddedNode = this._linkifyNodeNumbers(node, aDoc);
   1.100 +      // we assign a different timeout whether the node was processed or not.
   1.101 +      if (lastAddedNode) {
   1.102 +        nodeWalker.currentNode = lastAddedNode;
   1.103 +        this._linkifyTimer = setTimeout(parseNode, LINKIFY_TIMEOUT);
   1.104 +      } else {
   1.105 +        this._linkifyTimer = setTimeout(parseNode, LINKIFY_TIMEOUT);
   1.106 +      }
   1.107 +    }.bind(this);
   1.108 +
   1.109 +    this._linkifyTimer = setTimeout(parseNode, LINKIFY_TIMEOUT); 
   1.110 +  }
   1.111 +};

mercurial