Index: htdocs/css/timeline.css
===================================================================
--- htdocs/css/timeline.css	(revision 4444)
+++ htdocs/css/timeline.css	(working copy)
@@ -16,13 +16,10 @@
  background: 3px 3px no-repeat;
  border: none;
  color: #000;
- padding: 0 4px 2px 22px;
+ padding: 0 4px 0 22px;
 }
-dt>:link, dt>:visited {
- /* Hide from IE/Win */
- background-position: 3px 4px;
- display: block;
-}
+dt:hover { background-color: #eed; }
+
 dt :link:hover, dt :visited:hover { background-color: #eed; color: #000 }
 dt em {
  border-bottom: 1px dotted #bbb;
@@ -39,13 +36,13 @@
 }
 
 /* Apply icon background-image twice to avoid hover-flicker in IE/Win */
-dt.changeset, dt.changeset a { background-image: url(../changeset.png) !important }
-dt.newticket, dt.newticket a { background-image: url(../newticket.png) !important }
-dt.editedticket, dt.editedticket a { background-image: url(../editedticket.png) !important }
-dt.closedticket, dt.closedticket a { background-image: url(../closedticket.png) !important }
-dt.wiki, dt.wiki a { background-image: url(../wiki.png) !important }
-dt.milestone, dt.milestone a { background-image: url(../milestone.png) !important }
-dt.attachment, dt.attachment a { background-image: url(../attachment.png) !important }
+dt.changeset a { background-image: url(../changeset.png) !important }
+dt.newticket a { background-image: url(../newticket.png) !important }
+dt.editedticket a { background-image: url(../editedticket.png) !important }
+dt.closedticket a { background-image: url(../closedticket.png) !important }
+dt.wiki a { background-image: url(../wiki.png) !important }
+dt.milestone a { background-image: url(../milestone.png) !important }
+dt.attachment a { background-image: url(../attachment.png) !important }
 
 /* styles for the 'changeset_long_messages' option */
 dd.changeset p { margin: 0; padding: 0 }
Index: trac/web/api.py
===================================================================
--- trac/web/api.py	(revision 4444)
+++ trac/web/api.py	(working copy)
@@ -22,6 +22,7 @@
 import os
 from StringIO import StringIO
 import sys
+from time import time
 import urlparse
 
 from trac.core import Interface
@@ -346,7 +347,7 @@
             self.write(data)
         raise RequestDone
 
-    def send_file(self, path, mimetype=None):
+    def send_file(self, path, mimetype=None, expires=None):
         """Send a local file to the browser.
         
         This method includes the "Last-Modified", "Content-Type" and
@@ -374,6 +375,9 @@
         self.send_header('Content-Type', mimetype)
         self.send_header('Content-Length', stat.st_size)
         self.send_header('Last-Modified', last_modified)
+        if expires:
+            exp = datetime.fromtimestamp(time()+expires, localtz)
+            self.send_header('Expires', http_date(exp))
         self.end_headers()
 
         if self.method != 'HEAD':
Index: trac/web/chrome.py
===================================================================
--- trac/web/chrome.py	(revision 4444)
+++ trac/web/chrome.py	(working copy)
@@ -276,7 +276,9 @@
                 path = os.path.normpath(os.path.join(dir, filename))
                 assert os.path.commonprefix([dir, path]) == dir
                 if os.path.isfile(path):
-                    req.send_file(path, mimeview.get_mimetype(path))
+                    req.send_file(path, mimeview.get_mimetype(path),
+                                  path[:-4] in ('.png', '.gif', '.jpg') and
+                                               2592000 or None)
 
         self.log.warning('File %s not found in any of %s', filename, dirs)
         raise HTTPNotFound('File %s not found', filename)

