1

I'm trying to get the contents of the last HTML comment on the page -- it contains some session information written by the web application server. It is written right before the </html> tag. After reading this question (Selecting HTML Comments with jQuery), we've come up with something that works, but not consistently:

var commentString = $("*").contents().filter(function(){
     return this.nodeType == 8 ? this.nodeValue.indexOf("Expected Comment Text") > -1: false;
})[0].nodeValue;

The problem is that evidently if the page contains an iframe (from a different domain), the * selector will hit it and then we get stopped by the cross-origin policy. We can change this selector to $("body") and it appears to work in most browsers, but this appears to rely on the browser moving the comment inside the body node, and I'm not convinced that this will universally be the case.

We can use jQuery 1.7.2 if necessary (but no plugins), and good browser support (IE < 9) is important.

Would it be best to traverse the DOM without jQuery "in reverse" until we hit a nodeType === 8? That way it wouldn't matter if the browser puts the comment node as a child of <html> or <body> as long as its the last comment in the document node tree. What would this look like? (My DOM-traversal-fu is no good.)

Community
  • 1
  • 1
NReilingh
  • 1,730
  • 17
  • 32
  • If it is your own server, why are you trying to get it this way client side? Why not server side instead? BTW, what is your use case? – A. Wolff Dec 05 '15 at 19:13
  • @A.Wolff It's not my own server! :-) It's a software product where customization is possible at the template level. We need that session information for a customization. Believe me, there are plenty more reasonable solutions to this problem if we could modify server-side, but we can't. – NReilingh Dec 05 '15 at 19:17

2 Answers2

1

Why don't you use

$("*").not("iframe")

as a selector, if rest of the code workd fine and iframe is the only element causing issue?

var commentString = $("*").not("iframe").contents().filter(function(){
     return this.nodeType == 8 ? this.nodeValue.indexOf("Expected Comment Text") > -1: false;
})[0].nodeValue;
void
  • 36,090
  • 8
  • 62
  • 107
1

There's quite a lot of overhead if you go with the kind of solution you're attempting.

Just walk the DOM by making a simple, reusable function.

function walk(node, cb) {
  cb(node);

  if (node.childNodes) {
    for (var i = 0; i < node.childNodes.length; i++) {
      walk(node.childNodes[i], cb);
    }
  }
}

// And use it like this:

walk(document, function(node) {
  if (node.nodeType === 8) {
    // do something with comment
    alert(node.data);
  }
});
<div>
  foo
  <iframe src="http://www.linuxmint.com"></iframe>
  <!-- some comment -->
</div>

Much more efficient and flexible this way.


Here's another variation on the walk function, that doesn't use a counter loop.

function walk(node, cb) {
  cb(node);

  if (node = node.firstChild) {
    do walk(node, cb);
    while (node = node.nextSibling)
  }
}

A little shorter and cleaner this way.


If you're looking to return a single node, then you can have your callback as well as the recursive call return a value, and then return immediately when a value is actually returned.

As per your comments, this version walks backwards through the DOM.

function walk(node, cb) {
  var res = cb(node);
  if (res)
    return res;

  if (node = node.lastChild) {
    do if (res = walk(node, cb))
      return res;
    while (node = node.previousSibling)
  }
}

var result = walk(document, function(node) {
  if (node.nodeType === 8) {
    return node;
  }
});

if (result)
  alert(result.data);
<div>
  foo
  <!-- Some other comment -->
  <iframe src="http://www.linuxmint.com"></iframe>
  <!-- Success! - Expected Comment Text -->
</div>
  • Ooh, this is cool. I'd like to just get the last comment on the page, ignoring any others. It looks like I can do lastChild/previousSibling to go in reverse order, and then break in the if statement of the call? – NReilingh Dec 05 '15 at 19:30
  • Hmm... in order to do that, there needs to be a way to send a return up the recursion chain. – NReilingh Dec 05 '15 at 19:37
  • 1
    @NReilingh: Are you searching for only one comment? If so, you can have the callback return a value, and at the point where it is called, if a value was returned, have it return immediately. Then do the same where `walk()` is invoked recursively. I'll add another solution at the bottom. –  Dec 05 '15 at 19:39
  • 1
    Brilliant -- this is exactly what I had in mind. (In this use case, the "lastness" of the comment is actually a bit more reliable than filtering on its content...) – NReilingh Dec 05 '15 at 19:54
  • @NReilingh: Glad it helped. –  Dec 05 '15 at 19:57