最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

CSSJavaScripthacking: Detect :visited styling on a link *without* checking it directly OR do it faster than me - Stack Overflow

programmeradmin2浏览0评论

This is for research purposes on

Consider the following code:

<style>
  div.csshistory a { display: none; color: #00ff00;}
  div.csshistory a:visited { display: inline; color: #ff0000;}
</style>

<div id="batch" class="csshistory">
  <a id="1" href="">anything you want here</a>
  <a id="2" href="">anything you want here</a>
  [etc * ~2000]
</div>

My goal is to detect whether foo has been rendered using the :visited styling.

  1. I want to detect whether foo is visited without directly looking at $('1').getComputedStyle (or in Internet Explorer, currentStyle), or any other direct method on that element.

    The purpose of this is to get around a potential browser restriction that would prevent direct inspection of the style of visited links.

    For instance, maybe you can put a sub-element in the <a> tag, or check the styling of the text directly; etc. Any method that does not directly or indierctly rely on $('1').anything is acceptable. Doing something clever with the child or parent is probably necessary.

    Note that for the purposes of this point only, the scenario is that the browser will lie to JavaScript about all properties of the <a> element (but not others), and that it will only render color: in :visited. Therefore, methods that rely on e.g. text size or background-image will not meet this requirement.

  2. I want to improve the speed of my current scraping methods.

    The majority of time (at least with the jQuery method in Firefox) is spent on document.body.appendChild(batch), so finding a way to improve that call would probably most effective.

    See and for current speed test results.

The methods I am currently using can be seen at .js

To summarize for tl;dr, they are:

  1. set color or display on :visited per above, and check each one directly w/ getComputedStyle
  2. put the ID of the link (plus a space) inside the <a> tag, and using jQuery's :visible selector, extract only the visible text (= the visited link IDs)

FWIW, I'm a white hat, and I'm doing this in consultation with the EFF and some other fairly well known security researchers.

If you contribute a new method or speedup, you'll get thanked at (if you want to be :-P), and potentially in a future published paper.

ETA: The bounty will be rewarded only for suggestions that

  • can, on Firefox, avoid the hypothetical restriction described in point 1 above, or
  • perform at least 10% faster, on any browser for which I have sufficient current data, than my best performing methods listed in the graph at

In case more than one suggestion fits either criterion, the one that does best wins.

ETA 2: I've added width-based variants of two previous-best test methods (reuse_noinsert, best on Firefox/Mozilla, and mass_insert, its very close competitor). Please visit several times from different browsers; I'll automatically get the speed test results, so we'll find out if it's better than the previous methods, and if so by how much. Thanks!

ETA 3: Current tests indicate a speed savings using offsetWidth (rather than getCalculatedStyle/currentStyle) of ~2ms (1.8%) in Chrome and ~24ms (4.3%) in Firefox, which isn't the 10% I wanted for a solid bounty win. Got an idea how to eke out the rest of that 10%?

This is for research purposes on http://cssfingerprint.com

Consider the following code:

<style>
  div.csshistory a { display: none; color: #00ff00;}
  div.csshistory a:visited { display: inline; color: #ff0000;}
</style>

<div id="batch" class="csshistory">
  <a id="1" href="http://foo.com">anything you want here</a>
  <a id="2" href="http://bar.com">anything you want here</a>
  [etc * ~2000]
</div>

My goal is to detect whether foo has been rendered using the :visited styling.

  1. I want to detect whether foo.com is visited without directly looking at $('1').getComputedStyle (or in Internet Explorer, currentStyle), or any other direct method on that element.

    The purpose of this is to get around a potential browser restriction that would prevent direct inspection of the style of visited links.

    For instance, maybe you can put a sub-element in the <a> tag, or check the styling of the text directly; etc. Any method that does not directly or indierctly rely on $('1').anything is acceptable. Doing something clever with the child or parent is probably necessary.

    Note that for the purposes of this point only, the scenario is that the browser will lie to JavaScript about all properties of the <a> element (but not others), and that it will only render color: in :visited. Therefore, methods that rely on e.g. text size or background-image will not meet this requirement.

  2. I want to improve the speed of my current scraping methods.

    The majority of time (at least with the jQuery method in Firefox) is spent on document.body.appendChild(batch), so finding a way to improve that call would probably most effective.

    See http://cssfingerprint.com/about and http://cssfingerprint.com/results for current speed test results.

The methods I am currently using can be seen at http://github.com/saizai/cssfingerprint/blob/master/public/javascripts/history_scrape.js

To summarize for tl;dr, they are:

  1. set color or display on :visited per above, and check each one directly w/ getComputedStyle
  2. put the ID of the link (plus a space) inside the <a> tag, and using jQuery's :visible selector, extract only the visible text (= the visited link IDs)

FWIW, I'm a white hat, and I'm doing this in consultation with the EFF and some other fairly well known security researchers.

If you contribute a new method or speedup, you'll get thanked at http://cssfingerprint.com/about (if you want to be :-P), and potentially in a future published paper.

ETA: The bounty will be rewarded only for suggestions that

  • can, on Firefox, avoid the hypothetical restriction described in point 1 above, or
  • perform at least 10% faster, on any browser for which I have sufficient current data, than my best performing methods listed in the graph at http://cssfingerprint.com/about

In case more than one suggestion fits either criterion, the one that does best wins.

ETA 2: I've added width-based variants of two previous-best test methods (reuse_noinsert, best on Firefox/Mozilla, and mass_insert, its very close competitor). Please visit http://cssfingerprint.com several times from different browsers; I'll automatically get the speed test results, so we'll find out if it's better than the previous methods, and if so by how much. Thanks!

ETA 3: Current tests indicate a speed savings using offsetWidth (rather than getCalculatedStyle/currentStyle) of ~2ms (1.8%) in Chrome and ~24ms (4.3%) in Firefox, which isn't the 10% I wanted for a solid bounty win. Got an idea how to eke out the rest of that 10%?

Share Improve this question edited Aug 30, 2014 at 22:33 AstroCB 12.4k20 gold badges58 silver badges74 bronze badges asked Mar 7, 2010 at 2:01 SaiSai 7,2097 gold badges43 silver badges55 bronze badges 7
  • 2 Checked out the site. That's extremely cool and kind of scary. Wow. – Cam Commented Mar 7, 2010 at 2:16
  • @incrediman: Thanks. "Extremely cool and kind of scary" is my favorite kind of hack. :-) – Sai Commented Mar 7, 2010 at 2:59
  • Have you seen this: ha.ckers.org/weird/CSS-history.cgi ? I just found it, and it seems interesting because it doesn't rely on javascript. – Cam Commented Mar 7, 2010 at 3:25
  • That's actually linked to in my <noscript> segment. :-P Unfortunately, it would probably be orders of magnitude slower than my current implementation (whose max local speed is ~3.4 million URLs per minute), because it requires the browser to make an independent call for every hit. OTOH, I haven't actually tested it (yet?)... – Sai Commented Mar 7, 2010 at 4:11
  • @incrediman: I've added a bunch of links to cssfingerprint.com/about so that it'll be clearer. – Sai Commented Mar 7, 2010 at 4:54
 |  Show 2 more comments

4 Answers 4

Reset to default 3

[new update]

If you wanted the results just for visual presentation then the fastest method would be to use CSS counter..

CSS:

body{
    counter-reset: visited_counter;
}

a:visited{
    counter-increment: visited_counter;
}

#results:before{
    content:counter(visited_counter);
}

This would add the number of visited links before the element with id 'results'.

Unfortunately there is no way to access it from JavaScript, you can only display it..


[initial answer]

You are aware that jQuery supports the :visited selector directly right?

Like $('a:visited')

[update]

As an alternative, you could apply a CSS property that does not rely to the getComputedStyle to retrieve..

Like a:visited{height:1px;display:block;} and then check for offsetHeight.

  1. add a child inside the anchor (for example a span)
  2. use color : inherit
  3. detect the color of the child (JS)

caveat: afaik it won't work on lte ie7

for lte ie7 ull have to

  • add visibility : hidden on a:visited and visibility : inherit on the child
  • check the visibility of the child using javascript (hidden = visited)

A similar idea, but sidestepping .getComputedStyle():

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
    <head>
        <title></title>
        <meta http-equiv="Content-Type" content="text/html;charset=utf-8">

        <style type="text/css">
            a:visited { display: inline-block; font-family: monospace; }
            body { font-family: sans-serif; }
        </style>

        <script type="text/javascript">
            function test() {
                var visited = document.getElementById("v").childNodes[1].firstChild.clientWidth;
                var unvisited = document.getElementById("u").childNodes[1].firstChild.clientWidth;
                var rows = document.getElementsByTagName("tr");

                for (var i = 1, length = rows.length; i < length; i++) {
                    var row = rows[i];
                    var link = row.childNodes[1].firstChild;
                    var width = link.clientWidth;

                    row.firstChild.appendChild(document.createTextNode(link.href));
                    row.childNodes[2].appendChild(document.createTextNode(width === visited ? "yes" : (width === unvisited ? "no" : "unknown")));
                }
            }
        </script>
    </head>

    <body onload="test()">
        <table>
            <tr><th>url</th><th>link</th><th>visited?</th></tr>
            <tr id="u"><td></td><td><a href="http://invalid_host..mplx/">l</a></td><td></td>
            <tr id="v"><td></td><td><a href="css-snoop.html">l</a></td><td></td>
            <tr><td></td><td><a href="http://stackoverflow.com/">l</a></td><td></td>
            <tr><td></td><td><a href="http://www.dell.com/">l</a></td><td></td>
        </table>
    </body>
</html>

The trick, of course, is ensuring that visited and unvisited links have different widths (here, by using sans-serf vs. monospace fonts) and setting them to inline-block so that their widths can be accessed via clientWidth. Tested to work on FF3.6, IE7, Chrome 4, and Opera 10.

In my tests, accessing clientWidth was consistently faster than anything which relied on computed styles (sometimes by as much as ~40%, but widely varying).

(Oh, and apologies for the <body onload="..."> nonsense; it's been too long since I tried to do events in IE without a framework and I got tired of fighting it.)

Since all versions of IE (Yes, even version 8 if you enable quirks) support CSS expressions the color property is still unsafe. You could probably speed up IE testing with this (untested):

a:visited { color: expression( arrVisited.push(this.href) ); }

Also this isn't really covered by your question but you can of course set properties in child nodes very easily to initiate detection and any solution would have to prevent that too:

a.google:visited span { background-image: url(http://example.com/visited/google); }

You need to protect adjacent siblings too, not just descendants:

a.google:visited + span { }

Also untested but you could probably do a heavy speedup using the content property to modify the DOM and then some XPath to find the new nodes.

a.google:visited:before {content: "visited"; visibility: hidden;}

XPath:

visited links = document.evaluate('//a[text()="visited"]')

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论