Changeset 249056 in webkit


Ignore:
Timestamp:
Aug 23, 2019 11:14:43 AM (5 years ago)
Author:
commit-queue@webkit.org
Message:

Support ITP on a per-session basis (198923)
https://bugs.webkit.org/show_bug.cgi?id=198923

Patch by Kate Cheney <Kate Cheney> on 2019-08-23
Reviewed by Chris Dumez.

Source/WebCore:

This patch updated the data structure used to collect resource load
statistics in order to support ITP data collection on a per session
basis. Each sessionID is stored as a key-value pair with its own map
of ResourceLoadStatistics.

It also updated the statisticsForURL function call to perform lookups
of URL data based on sessionID.

  • loader/ResourceLoadObserver.cpp:

(WebCore::ResourceLoadObserver::setStatisticsUpdatedCallback):
(WebCore::ResourceLoadObserver::shouldLog const):
(WebCore::ResourceLoadObserver::logSubresourceLoading):
(WebCore::ResourceLoadObserver::logWebSocketLoading):
(WebCore::ResourceLoadObserver::logUserInteractionWithReducedTimeResolution):
(WebCore::ResourceLoadObserver::logFontLoad):
(WebCore::ResourceLoadObserver::logCanvasRead):
(WebCore::ResourceLoadObserver::logCanvasWriteOrMeasure):
(WebCore::ResourceLoadObserver::logNavigatorAPIAccessed):
(WebCore::ResourceLoadObserver::logScreenAPIAccessed):
(WebCore::ResourceLoadObserver::ensureResourceStatisticsForRegistrableDomain):
(WebCore::ResourceLoadObserver::statisticsForURL):
(WebCore::ResourceLoadObserver::takeStatistics):
(WebCore::ResourceLoadObserver::clearState):

  • loader/ResourceLoadObserver.h:
  • testing/Internals.cpp:

(WebCore::Internals::resourceLoadStatisticsForURL):

Source/WebKit:

The original implementation of resourceLoadStatisticsUpdated
did not allow for ITP on a per session basis due to the sessionID
not being passed to the resourceLoadStatisticsUpdated function.
This patch allows access of the correct networkSession by passing
all resourceLoadStatistics in a new data structure of key-value
pairs, where the sessionID is the key.

  • NetworkProcess/NetworkConnectionToWebProcess.cpp:

(WebKit::NetworkConnectionToWebProcess::resourceLoadStatisticsUpdated):

  • NetworkProcess/NetworkConnectionToWebProcess.h:
  • WebProcess/WebProcess.cpp:
Location:
trunk/Source
Files:
9 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebCore/ChangeLog

    r249047 r249056  
     12019-08-23  Kate Cheney  <katherine_cheney@apple.com>
     2
     3        Support ITP on a per-session basis (198923)
     4        https://bugs.webkit.org/show_bug.cgi?id=198923
     5
     6        Reviewed by Chris Dumez.
     7
     8        This patch updated the data structure used to collect resource load
     9        statistics in order to support ITP data collection on a per session
     10        basis. Each sessionID is stored as a key-value pair with its own map
     11        of ResourceLoadStatistics.
     12
     13        It also updated the statisticsForURL function call to perform lookups
     14        of URL data based on sessionID.
     15
     16        * loader/ResourceLoadObserver.cpp:
     17        (WebCore::ResourceLoadObserver::setStatisticsUpdatedCallback):
     18        (WebCore::ResourceLoadObserver::shouldLog const):
     19        (WebCore::ResourceLoadObserver::logSubresourceLoading):
     20        (WebCore::ResourceLoadObserver::logWebSocketLoading):
     21        (WebCore::ResourceLoadObserver::logUserInteractionWithReducedTimeResolution):
     22        (WebCore::ResourceLoadObserver::logFontLoad):
     23        (WebCore::ResourceLoadObserver::logCanvasRead):
     24        (WebCore::ResourceLoadObserver::logCanvasWriteOrMeasure):
     25        (WebCore::ResourceLoadObserver::logNavigatorAPIAccessed):
     26        (WebCore::ResourceLoadObserver::logScreenAPIAccessed):
     27        (WebCore::ResourceLoadObserver::ensureResourceStatisticsForRegistrableDomain):
     28        (WebCore::ResourceLoadObserver::statisticsForURL):
     29        (WebCore::ResourceLoadObserver::takeStatistics):
     30        (WebCore::ResourceLoadObserver::clearState):
     31        * loader/ResourceLoadObserver.h:
     32        * testing/Internals.cpp:
     33        (WebCore::Internals::resourceLoadStatisticsForURL):
     34
    1352019-08-23  Simon Fraser  <simon.fraser@apple.com>
    236
  • trunk/Source/WebCore/loader/ResourceLoadObserver.cpp

    r245796 r249056  
    5555}
    5656
    57 void ResourceLoadObserver::setStatisticsUpdatedCallback(WTF::Function<void(Vector<ResourceLoadStatistics>&&)>&& notificationCallback)
     57void ResourceLoadObserver::setStatisticsUpdatedCallback(Function<void(PerSessionResourceLoadData&&)>&& notificationCallback)
    5858{
    5959    ASSERT(!m_notificationCallback);
     
    6161}
    6262
    63 void ResourceLoadObserver::setRequestStorageAccessUnderOpenerCallback(WTF::Function<void(PAL::SessionID sessionID, const RegistrableDomain& domainInNeedOfStorageAccess, PageIdentifier openerPageID, const RegistrableDomain& openerDomain)>&& callback)
     63void ResourceLoadObserver::setRequestStorageAccessUnderOpenerCallback(Function<void(PAL::SessionID sessionID, const RegistrableDomain& domainInNeedOfStorageAccess, PageIdentifier openerPageID, const RegistrableDomain& openerDomain)>&& callback)
    6464{
    6565    ASSERT(!m_requestStorageAccessUnderOpenerCallback);
     
    9696}
    9797
    98 bool ResourceLoadObserver::shouldLog(bool usesEphemeralSession) const
    99 {
    100     return DeprecatedGlobalSettings::resourceLoadStatisticsEnabled() && !usesEphemeralSession && m_notificationCallback;
     98bool ResourceLoadObserver::shouldLog(PAL::SessionID sessionID) const
     99{
     100    return DeprecatedGlobalSettings::resourceLoadStatisticsEnabled() && !sessionID.isEphemeral() && m_notificationCallback;
    101101}
    102102
     
    109109
    110110    auto* page = frame->page();
    111     if (!page || !shouldLog(page->usesEphemeralSession()))
     111    if (!page || !shouldLog(page->sessionID()))
    112112        return;
    113113
     
    131131
    132132    {
    133         auto& targetStatistics = ensureResourceStatisticsForRegistrableDomain(targetDomain);
     133        auto& targetStatistics = ensureResourceStatisticsForRegistrableDomain(page->sessionID(), targetDomain);
    134134        auto lastSeen = ResourceLoadStatistics::reduceTimeResolution(WallTime::now());
    135135        targetStatistics.lastSeen = lastSeen;
     
    140140
    141141    if (isRedirect) {
    142         auto& redirectingOriginStatistics = ensureResourceStatisticsForRegistrableDomain(redirectedFromDomain);
     142        auto& redirectingOriginStatistics = ensureResourceStatisticsForRegistrableDomain(page->sessionID(), redirectedFromDomain);
    143143        redirectingOriginStatistics.subresourceUniqueRedirectsTo.add(targetDomain);
    144         auto& targetStatistics = ensureResourceStatisticsForRegistrableDomain(targetDomain);
     144        auto& targetStatistics = ensureResourceStatisticsForRegistrableDomain(page->sessionID(), targetDomain);
    145145        targetStatistics.subresourceUniqueRedirectsFrom.add(redirectedFromDomain);
    146146
     
    151151void ResourceLoadObserver::logWebSocketLoading(const URL& targetURL, const URL& mainFrameURL, PAL::SessionID sessionID)
    152152{
    153     if (!shouldLog(sessionID.isEphemeral()))
     153    if (!shouldLog(sessionID))
    154154        return;
    155155
     
    168168    auto lastSeen = ResourceLoadStatistics::reduceTimeResolution(WallTime::now());
    169169
    170     auto& targetStatistics = ensureResourceStatisticsForRegistrableDomain(targetDomain);
     170    auto& targetStatistics = ensureResourceStatisticsForRegistrableDomain(sessionID, targetDomain);
    171171    targetStatistics.lastSeen = lastSeen;
    172172    targetStatistics.subresourceUnderTopFrameDomains.add(topFrameDomain);
     
    177177void ResourceLoadObserver::logUserInteractionWithReducedTimeResolution(const Document& document)
    178178{
    179     if (!document.sessionID().isValid() || !shouldLog(document.sessionID().isEphemeral()))
     179    if (!document.sessionID().isValid() || !shouldLog(document.sessionID()))
    180180        return;
    181181
     
    192192    m_lastReportedUserInteractionMap.set(topFrameDomain, newTime);
    193193
    194     auto& statistics = ensureResourceStatisticsForRegistrableDomain(topFrameDomain);
     194    auto& statistics = ensureResourceStatisticsForRegistrableDomain(document.sessionID(), topFrameDomain);
    195195    statistics.hadUserInteraction = true;
    196196    statistics.lastSeen = newTime;
     
    252252{
    253253#if ENABLE(WEB_API_STATISTICS)
    254     if (!shouldLog(document.sessionID().isEphemeral()))
     254    if (!shouldLog(document.sessionID()))
    255255        return;
    256256    RegistrableDomain registrableDomain { document.url() };
    257     auto& statistics = ensureResourceStatisticsForRegistrableDomain(registrableDomain);
     257    auto& statistics = ensureResourceStatisticsForRegistrableDomain(document.sessionID, registrableDomain);
    258258    bool shouldCallNotificationCallback = false;
    259259    if (!loadStatus) {
     
    279279{
    280280#if ENABLE(WEB_API_STATISTICS)
    281     if (!shouldLog(document.sessionID().isEphemeral()))
     281    if (!shouldLog(document.sessionID()))
    282282        return;
    283283    RegistrableDomain registrableDomain { document.url() };
    284     auto& statistics = ensureResourceStatisticsForRegistrableDomain(registrableDomain);
     284    auto& statistics = ensureResourceStatisticsForRegistrableDomain(document.sessionID(), registrableDomain);
    285285    RegistrableDomain mainFrameRegistrableDomain { document.topDocument().url() };
    286286    statistics.canvasActivityRecord.wasDataRead = true;
     
    295295{
    296296#if ENABLE(WEB_API_STATISTICS)
    297     if (!shouldLog(document.sessionID().isEphemeral()))
     297    if (!shouldLog(document.sessionID()))
    298298        return;
    299299    RegistrableDomain registrableDomain { document.url() };
    300     auto& statistics = ensureResourceStatisticsForRegistrableDomain(registrableDomain);
     300    auto& statistics = ensureResourceStatisticsForRegistrableDomain(document.sessionID, registrableDomain);
    301301    bool shouldCallNotificationCallback = false;
    302302    RegistrableDomain mainFrameRegistrableDomain { document.topDocument().url() };
     
    316316{
    317317#if ENABLE(WEB_API_STATISTICS)
    318     if (!shouldLog(document.sessionID().isEphemeral()))
     318    if (!shouldLog(document.sessionID()))
    319319        return;
    320320    RegistrableDomain registrableDomain { document.url() };
    321     auto& statistics = ensureResourceStatisticsForRegistrableDomain(registrableDomain);
     321    auto& statistics = ensureResourceStatisticsForRegistrableDomain(document.sessionID, registrableDomain);
    322322    bool shouldCallNotificationCallback = false;
    323323    if (!statistics.navigatorFunctionsAccessed.contains(functionName)) {
     
    339339{
    340340#if ENABLE(WEB_API_STATISTICS)
    341     if (!shouldLog(document.sessionID().isEphemeral()))
     341    if (!shouldLog(document.sessionID()))
    342342        return;
    343343    RegistrableDomain registrableDomain { document.url() };
    344     auto& statistics = ensureResourceStatisticsForRegistrableDomain(registrableDomain);
     344    auto& statistics = ensureResourceStatisticsForRegistrableDomain(document.sessionID, registrableDomain);
    345345    bool shouldCallNotificationCallback = false;
    346346    if (!statistics.screenFunctionsAccessed.contains(functionName)) {
     
    359359}
    360360   
    361 ResourceLoadStatistics& ResourceLoadObserver::ensureResourceStatisticsForRegistrableDomain(const RegistrableDomain& domain)
    362 {
    363     auto addResult = m_resourceStatisticsMap.ensure(domain, [&domain] {
     361ResourceLoadStatistics& ResourceLoadObserver::ensureResourceStatisticsForRegistrableDomain(PAL::SessionID sessionID, const RegistrableDomain& domain)
     362{
     363    auto addResult = m_perSessionResourceStatisticsMap.ensure(sessionID, [] {
     364        return makeUnique<HashMap<RegistrableDomain, ResourceLoadStatistics>>();
     365    });
     366
     367    auto addDomainResult = addResult.iterator->value->ensure(domain, [&domain] {
    364368        return ResourceLoadStatistics(domain);
    365369    });
    366     return addResult.iterator->value;
     370    return addDomainResult.iterator->value;
    367371}
    368372
     
    372376}
    373377
    374 String ResourceLoadObserver::statisticsForURL(const URL& url)
    375 {
    376     auto iter = m_resourceStatisticsMap.find(RegistrableDomain { url });
    377     if (iter == m_resourceStatisticsMap.end())
     378String ResourceLoadObserver::statisticsForURL(PAL::SessionID sessionID, const URL& url)
     379{
     380    auto* resourceStatisticsByDomain = m_perSessionResourceStatisticsMap.get(sessionID);
     381    if (!resourceStatisticsByDomain)
    378382        return emptyString();
    379383
     384    auto iter = resourceStatisticsByDomain->find(RegistrableDomain { url });
     385    if (iter == resourceStatisticsByDomain->end())
     386        return emptyString();
     387
    380388    return makeString("Statistics for ", url.host().toString(), ":\n", iter->value.toString());
    381389}
    382390
    383 Vector<ResourceLoadStatistics> ResourceLoadObserver::takeStatistics()
    384 {
    385     Vector<ResourceLoadStatistics> statistics;
    386     statistics.reserveInitialCapacity(m_resourceStatisticsMap.size());
    387     for (auto& statistic : m_resourceStatisticsMap.values())
    388         statistics.uncheckedAppend(WTFMove(statistic));
    389 
    390     m_resourceStatisticsMap.clear();
    391 
    392     return statistics;
     391auto ResourceLoadObserver::takeStatistics() -> PerSessionResourceLoadData
     392{
     393    PerSessionResourceLoadData perSessionStatistics;
     394
     395    for (auto& iter : m_perSessionResourceStatisticsMap) {
     396        Vector<ResourceLoadStatistics> statistics;
     397        statistics.reserveInitialCapacity(iter.value->size());
     398
     399        for (auto& statistic : iter.value->values())
     400            statistics.uncheckedAppend(WTFMove(statistic));
     401
     402        perSessionStatistics.append(std::make_pair(iter.key, WTFMove(statistics)));
     403    }
     404   
     405    m_perSessionResourceStatisticsMap.clear();
     406    return perSessionStatistics;
    393407}
    394408
    395409void ResourceLoadObserver::clearState()
    396410{
    397     m_resourceStatisticsMap.clear();
     411    m_perSessionResourceStatisticsMap.clear();
    398412    m_lastReportedUserInteractionMap.clear();
    399413}
  • trunk/Source/WebCore/loader/ResourceLoadObserver.h

    r245796 r249056  
    6060    friend class WTF::NeverDestroyed<ResourceLoadObserver>;
    6161public:
     62    using PerSessionResourceLoadData = Vector<std::pair<PAL::SessionID, Vector<ResourceLoadStatistics>>>;
    6263    WEBCORE_EXPORT static ResourceLoadObserver& shared();
    6364
     
    7273    void logScreenAPIAccessed(const Document&, const ResourceLoadStatistics::ScreenAPI);
    7374
    74     WEBCORE_EXPORT String statisticsForURL(const URL&);
     75    WEBCORE_EXPORT String statisticsForURL(PAL::SessionID, const URL&);
    7576
    76     WEBCORE_EXPORT void setStatisticsUpdatedCallback(WTF::Function<void(Vector<ResourceLoadStatistics>&&)>&&);
     77    WEBCORE_EXPORT void setStatisticsUpdatedCallback(Function<void(PerSessionResourceLoadData&&)>&&);
    7778    WEBCORE_EXPORT void setRequestStorageAccessUnderOpenerCallback(Function<void(PAL::SessionID, const RegistrableDomain&, PageIdentifier, const RegistrableDomain&)>&&);
    7879    WEBCORE_EXPORT void setLogUserInteractionNotificationCallback(Function<void(PAL::SessionID, const RegistrableDomain&)>&&);
     
    9091
    9192private:
    92     bool shouldLog(bool usesEphemeralSession) const;
    93     ResourceLoadStatistics& ensureResourceStatisticsForRegistrableDomain(const RegistrableDomain&);
     93    bool shouldLog(PAL::SessionID) const;
     94    ResourceLoadStatistics& ensureResourceStatisticsForRegistrableDomain(PAL::SessionID, const RegistrableDomain&);
    9495
    95     Vector<ResourceLoadStatistics> takeStatistics();
     96    PerSessionResourceLoadData takeStatistics();
    9697
    9798#if ENABLE(RESOURCE_LOAD_STATISTICS)
     
    99100#endif
    100101
    101     HashMap<RegistrableDomain, ResourceLoadStatistics> m_resourceStatisticsMap;
     102    HashMap<PAL::SessionID, std::unique_ptr<HashMap<RegistrableDomain, ResourceLoadStatistics>>> m_perSessionResourceStatisticsMap;
    102103    HashMap<RegistrableDomain, WTF::WallTime> m_lastReportedUserInteractionMap;
    103     Function<void(Vector<ResourceLoadStatistics>&&)> m_notificationCallback;
     104    Function<void(PerSessionResourceLoadData)> m_notificationCallback;
    104105    Function<void(PAL::SessionID, const RegistrableDomain&, PageIdentifier, const RegistrableDomain&)> m_requestStorageAccessUnderOpenerCallback;
    105106    Function<void(PAL::SessionID, const RegistrableDomain&)> m_logUserInteractionNotificationCallback;
  • trunk/Source/WebCore/testing/Internals.cpp

    r248846 r249056  
    44354435String Internals::resourceLoadStatisticsForURL(const DOMURL& url)
    44364436{
    4437     return ResourceLoadObserver::shared().statisticsForURL(url.href());
     4437    auto* document = contextDocument();
     4438    if (!document)
     4439        return emptyString();
     4440
     4441    return ResourceLoadObserver::shared().statisticsForURL(document->sessionID(), url.href());
    44384442}
    44394443
  • trunk/Source/WebKit/ChangeLog

    r249051 r249056  
     12019-08-23  Kate Cheney  <katherine_cheney@apple.com>
     2
     3        Support ITP on a per-session basis (198923)
     4        https://bugs.webkit.org/show_bug.cgi?id=198923
     5
     6        Reviewed by Chris Dumez.
     7
     8        The original implementation of resourceLoadStatisticsUpdated
     9        did not allow for ITP on a per session basis due to the sessionID
     10        not being passed to the resourceLoadStatisticsUpdated function.
     11        This patch allows access of the correct networkSession by passing
     12        all resourceLoadStatistics in a new data structure of key-value
     13        pairs, where the sessionID is the key.
     14
     15        * NetworkProcess/NetworkConnectionToWebProcess.cpp:
     16        (WebKit::NetworkConnectionToWebProcess::resourceLoadStatisticsUpdated):
     17        * NetworkProcess/NetworkConnectionToWebProcess.h:
     18        * WebProcess/WebProcess.cpp:
     19
    1202019-08-23  Russell Epstein  <repstein@apple.com>
    221
  • trunk/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp

    r248854 r249056  
    6363#include <WebCore/DocumentStorageAccess.h>
    6464#include <WebCore/NetworkStorageSession.h>
     65#include <WebCore/ResourceLoadObserver.h>
    6566#include <WebCore/ResourceLoadStatistics.h>
    6667#include <WebCore/ResourceRequest.h>
     
    713714}
    714715
    715 void NetworkConnectionToWebProcess::resourceLoadStatisticsUpdated(Vector<WebCore::ResourceLoadStatistics>&& statistics)
    716 {
    717     auto* networkSession = networkProcess().networkSessionByConnection(connection());
    718     if (!networkSession)
    719         return;
    720 
    721     if (auto* resourceLoadStatistics = networkSession->resourceLoadStatistics())
    722         resourceLoadStatistics->resourceLoadStatisticsUpdated(WTFMove(statistics));
     716void NetworkConnectionToWebProcess::resourceLoadStatisticsUpdated(ResourceLoadObserver::PerSessionResourceLoadData&& statistics)
     717{
     718    for (auto& iter : statistics) {
     719        if (auto* networkSession = networkProcess().networkSession(iter.first)) {
     720            if (auto* resourceLoadStatistics = networkSession->resourceLoadStatistics())
     721                resourceLoadStatistics->resourceLoadStatisticsUpdated(WTFMove(iter.second));
     722        }
     723    }
    723724}
    724725
  • trunk/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.h

    r248734 r249056  
    4040#include <WebCore/ProcessIdentifier.h>
    4141#include <WebCore/RegistrableDomain.h>
     42#include <WebCore/ResourceLoadObserver.h>
    4243#include <wtf/RefCounted.h>
    4344
     
    223224    void logSubresourceLoading(PAL::SessionID, const RegistrableDomain& targetDomain, const RegistrableDomain& topFrameDomain, WallTime lastSeen);
    224225    void logSubresourceRedirect(PAL::SessionID, const RegistrableDomain& sourceDomain, const RegistrableDomain& targetDomain);
    225     void resourceLoadStatisticsUpdated(Vector<WebCore::ResourceLoadStatistics>&&);
     226    void resourceLoadStatisticsUpdated(WebCore::ResourceLoadObserver::PerSessionResourceLoadData&&);
    226227    void hasStorageAccess(PAL::SessionID, const RegistrableDomain& subFrameDomain, const RegistrableDomain& topFrameDomain, WebCore::FrameIdentifier, WebCore::PageIdentifier, CompletionHandler<void(bool)>&&);
    227228    void requestStorageAccess(PAL::SessionID, const RegistrableDomain& subFrameDomain, const RegistrableDomain& topFrameDomain, WebCore::FrameIdentifier, WebCore::PageIdentifier, CompletionHandler<void(WebCore::StorageAccessWasGranted, WebCore::StorageAccessPromptWasShown)>&&);
  • trunk/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.messages.in

    r248734 r249056  
    6565    LogSubresourceLoading(PAL::SessionID sessionID, WebCore::RegistrableDomain targetDomain, WebCore::RegistrableDomain topFrameDomain, WallTime lastSeen)
    6666    LogSubresourceRedirect(PAL::SessionID sessionID, WebCore::RegistrableDomain sourceDomain, WebCore::RegistrableDomain targetDomain)
    67     ResourceLoadStatisticsUpdated(Vector<WebCore::ResourceLoadStatistics> statistics)
     67    ResourceLoadStatisticsUpdated(Vector<std::pair<PAL::SessionID, Vector<WebCore::ResourceLoadStatistics>>> statistics)
    6868    HasStorageAccess(PAL::SessionID sessionID, WebCore::RegistrableDomain subFrameDomain, WebCore::RegistrableDomain topFrameDomain, WebCore::FrameIdentifier frameID, WebCore::PageIdentifier pageID) -> (bool hasStorageAccess) Async
    6969    RequestStorageAccess(PAL::SessionID sessionID, WebCore::RegistrableDomain subFrameDomain, WebCore::RegistrableDomain topFrameDomain, WebCore::FrameIdentifier frameID, WebCore::PageIdentifier pageID) -> (enum:bool WebCore::StorageAccessWasGranted wasGranted, enum:bool WebCore::StorageAccessPromptWasShown promptWasShown) Async
  • trunk/Source/WebKit/WebProcess/WebProcess.cpp

    r248846 r249056  
    218218
    219219#if ENABLE(RESOURCE_LOAD_STATISTICS)
    220     ResourceLoadObserver::shared().setStatisticsUpdatedCallback([this] (Vector<ResourceLoadStatistics>&& statistics) {
     220    ResourceLoadObserver::shared().setStatisticsUpdatedCallback([this] (auto&& statistics) {
    221221        ensureNetworkProcessConnection().connection().send(Messages::NetworkConnectionToWebProcess::ResourceLoadStatisticsUpdated(WTFMove(statistics)), 0);
    222222    });
Note: See TracChangeset for help on using the changeset viewer.