Safari doesn't cache resources across different domains
Asked Answered
F

4

8

Let’s say we have several different websites: website1.com, website2.com, website3.com. We use jQuery on all of them and include it from CDN like googleapis.com. The expected behavior from a browser would be to cache it once and use it for all other websites. Chrome seems to do it, but Safari downloads jQuery for every domain.

Example

  1. With the given JS code below open nytimes.com, bbc.com and dw.de in Chrome.
  2. Append jQuery on the first website and look at the Network tab of your DevTools. It will say that it got jQuery.
  3. Now open any other website and append jQuery again — the answer will be “from cache”.

However, Safari will say it’s loading jQuery for every domain, but try to open any webpage on one of the domains and append the script again — you will see that now it says it got jQuery from cache. So it looks like it caches data for a domain, even if it has already downloaded a resource from the exact URL for another domain.

Is this assumption correct and if so, how to fix it?

Code you can copy/paste:

setTimeout(function() {
    var SCRIPT_SRC = '//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js';

    var s = document.createElement('script');
    s.type = 'text/javascript';
    s.async = true;
    s.src = SCRIPT_SRC;
    var x = document.getElementsByTagName('script')[0];
    x.parentNode.insertBefore(s, x);
}, 0);

UPD: Tested it with a static image.

test.com, test2.com and test3.com have <img src="http://image.com/image.jpg" />. In all browsers except for Safari access log shows only one — first — request for the image. Safari gets the image for every new domain (but not a subdomain).

Forging answered 7/7, 2014 at 17:45 Comment(8)
I'm not sure I'd trust tests like this where you're injecting jQuery manually. The browser may handle those differently than if they were included by the page itself.Denigrate
@Denigrate What if it would be js code that asynchronously appends jQuery? This appending through the DevTools shouldn't be any different.Forging
It would not be surprising to me if the cache was sometimes bypassed when manipulating via the webdev tools. I'd suggest removing that potential cause prior to assuming it to be a problem. You could probably even test it out on the StackExchange network - go to a few sites and see if the shared static assets are cached cross-domain.Denigrate
I made a quick test with a local domain and real "script" tag. It works for subdomains like test1.localtest.me, test2.localtest.me, etc., but breaks for localhost or 127.0.0.1, even though they ask for the same resourceForging
I wouldn't be surprised if browsers handle 127.0.0.1/localhost differently.Denigrate
Tried real IP (192...) too, with the same result.Forging
Try a non-private space IP.Denigrate
Tested with several hosts now – no IPs and localhosts this time. The same results. It uses cache for subdomains but not for other domains.Forging
B
7

I've noticed this too, and I suspect it is for privacy reasons.

By default, Safari blocks third-party cookies. A third party cookie is a cookie set on b.com on for a resource that is requested by a.com. This can be used, for example, to track people across domains. You can have a script on b.com that is requested by a.com and by c.com. b.com can insert a unique client ID into this script based on a third-party cookie, so that a.com and c.com can track that this is the same person.

Safari blocks this behavior. If b.com sets a cookie for a resource requested by a.com, Safari will box that cookie so it is only sent to b.com for more requests by a.com. It will not be sent to b.com for requests by c.com.

Now enter caching and specifically the Etag header. An Etag is an arbitrary string (usually a hash of the file) that can be used to determine if the requested resource has changed since the person requested it last. This is normally a good thing. It saves re-sending the entire file if it is has not changed.

However, because an Etag is an arbitrary string, b.com can set it to include a client ID. This is called Etag tracking. It allows tracking a person across domains in almost exactly the same way as cookies do.


Summary: By not sharing the cache across domains, Safari protects you from cross-domain Etag tracking.

Bashemeth answered 4/5, 2016 at 3:24 Comment(2)
This should be an accepted answer. Just augmented it a little bitXanthophyll
@ffeast If you felt strongly enough to comment, why didn't you upvote the answer to show your support?Nguyen
L
1

This is by design, something the Safari team call Intelligent Tracking Protection - https://webkit.org/blog/7675/intelligent-tracking-prevention/ - and the cache is double-keyed based on document origin and third-party origin

Based on research using HTTP Archive data and the Yahoo / Facebook studies on cache-lifetimes I doubt shared caching of jQuery etc is effective - not enough sites use the same versions of the libraries, and the libraries don't live in cache for very long – so Safari's behaviour helps prevent tracking, while not really affecting performance

Lollop answered 11/9, 2018 at 10:15 Comment(1)
Chrome has something similar included now since 2020: developers.google.com/web/updates/2020/10/…Unsound
S
0

Rather than simply adding a DOM element, you could try using XMLHTTPRequest. It lets you define custom headers -- one of which is Cache-Control.

Give this a shot, it should override whatever's going on at the browser level:

(function () {

    var newRequest = function() {
        return (window.XMLHttpRequest) ? new XMLHttpRequest() : new ActiveXObject( 'MsXml2.XmlHttp' );
    }

    var loadScript = function(url) {

        var http = new newRequest();

        http.onReadyStateChange = function() {
            if (http.readyState === 4) {
                if (http.status === 200 || http.status === 304) {
                    appendToPage(http.responseText);
                }
            }
        }

        // This is where you set your cache
        http.setRequestHeader( 'Cache-Control', 'max-age=0' )// <-- change this to a value larger than 0

        http.open('GET', url, true);
        http.send(null);
    }

    var appendToPage = function(source) {

        if (source === null) return false;

        var head = document.getElementsByTagName('head')[0];

        var script = document.createElement('script');
            script.language = 'javascript';
            script.type  = 'text/javascript';
            script.defer = true;
            script.text  = source;

        head.appendChild(script);
    }

    loadScript( '//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js' );
})();

Note: Safari has had some issues with caching in the past. However, from what I understand it was mostly about serving stale content -- not the other way around.

Subtemperate answered 17/7, 2014 at 6:55 Comment(0)
W
-1

Here are some suggestions :

  1. Have you checked if "disable cache" option is disabled ?
  2. Are you looking for HTTP status code in the network dev panel ?
  3. Have you tried capturing traffic with tools like WireShark ?

Best regards.

Welfarism answered 5/1, 2016 at 1:58 Comment(1)
On which OS are you testing ?Welfarism

© 2022 - 2024 — McMap. All rights reserved.