Changeset 224758 in webkit


Ignore:
Timestamp:
Nov 13, 2017, 10:32:00 AM (8 years ago)
Author:
achristensen@apple.com
Message:

Make DocumentLoader::willSendRequest asynchronous
https://bugs.webkit.org/show_bug.cgi?id=179549

Reviewed by Tim Horton.

No change in behavior, except now redirects will wait for PolicyChecker::checkNavigationPolicy's completion handler.
Before, they would just continue in DocumentLoader::redirectReceived hoping the client had responded to
decidePolicyForNavigationAction synchronously or that the client would have been ok with continuing.

  • loader/DocumentLoader.cpp:

(WebCore::DocumentLoader::redirectReceived):
(WebCore::DocumentLoader::willSendRequest):
(WebCore::DocumentLoader::startLoadingMainResource):

  • loader/DocumentLoader.h:
  • loader/FrameLoader.cpp:

(WebCore::FrameLoader::loadURL):
(WebCore::FrameLoader::loadWithDocumentLoader):

  • loader/PolicyChecker.cpp:

(WebCore::PolicyChecker::checkNavigationPolicy):

  • loader/PolicyChecker.h:
Location:
trunk/Source/WebCore
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebCore/ChangeLog

    r224755 r224758  
     12017-11-13  Alex Christensen  <achristensen@webkit.org>
     2
     3        Make DocumentLoader::willSendRequest asynchronous
     4        https://bugs.webkit.org/show_bug.cgi?id=179549
     5
     6        Reviewed by Tim Horton.
     7
     8        No change in behavior, except now redirects will wait for PolicyChecker::checkNavigationPolicy's completion handler.
     9        Before, they would just continue in DocumentLoader::redirectReceived hoping the client had responded to
     10        decidePolicyForNavigationAction synchronously or that the client would have been ok with continuing.
     11
     12        * loader/DocumentLoader.cpp:
     13        (WebCore::DocumentLoader::redirectReceived):
     14        (WebCore::DocumentLoader::willSendRequest):
     15        (WebCore::DocumentLoader::startLoadingMainResource):
     16        * loader/DocumentLoader.h:
     17        * loader/FrameLoader.cpp:
     18        (WebCore::FrameLoader::loadURL):
     19        (WebCore::FrameLoader::loadWithDocumentLoader):
     20        * loader/PolicyChecker.cpp:
     21        (WebCore::PolicyChecker::checkNavigationPolicy):
     22        * loader/PolicyChecker.h:
     23
    1242017-11-13  Wenson Hsieh  <wenson_hsieh@apple.com>
    225
  • trunk/Source/WebCore/loader/DocumentLoader.cpp

    r224699 r224758  
    471471{
    472472    ASSERT_UNUSED(resource, &resource == m_mainResource);
    473     willSendRequest(request, redirectResponse);
    474     completionHandler(WTFMove(request));
    475 }
    476 
    477 void DocumentLoader::willSendRequest(ResourceRequest& newRequest, const ResourceResponse& redirectResponse)
     473    willSendRequest(WTFMove(request), redirectResponse, WTFMove(completionHandler));
     474}
     475
     476void DocumentLoader::willSendRequest(ResourceRequest&& newRequest, const ResourceResponse& redirectResponse, CompletionHandler<void(ResourceRequest&&)>&& completionHandler)
    478477{
    479478    // Note that there are no asserts here as there are for the other callbacks. This is due to the
     
    486485    if (!frameLoader()->checkIfFormActionAllowedByCSP(newRequest.url(), didReceiveRedirectResponse)) {
    487486        cancelMainResourceLoad(frameLoader()->cancelledError(newRequest));
    488         return;
     487        return completionHandler(WTFMove(newRequest));
    489488    }
    490489
     
    497496            FrameLoader::reportLocalLoadFailed(m_frame, newRequest.url().string());
    498497            cancelMainResourceLoad(frameLoader()->cancelledError(newRequest));
    499             return;
     498            return completionHandler(WTFMove(newRequest));
    500499        }
    501500        if (!portAllowed(newRequest.url())) {
    502501            FrameLoader::reportBlockedPortFailed(m_frame, newRequest.url().string());
    503502            cancelMainResourceLoad(frameLoader()->blockedError(newRequest));
    504             return;
     503            return completionHandler(WTFMove(newRequest));
    505504        }
    506505        timing().addRedirect(redirectResponse.url(), newRequest.url());
     
    531530        if (!m_frame->loader().mixedContentChecker().canDisplayInsecureContent(m_frame->document()->securityOrigin(), MixedContentChecker::ContentType::Active, newRequest.url(), MixedContentChecker::AlwaysDisplayInNonStrictMode::Yes)) {
    532531            cancelMainResourceLoad(frameLoader()->cancelledError(newRequest));
    533             return;
     532            return completionHandler(WTFMove(newRequest));
    534533        }
    535534        if (!frameLoader()->mixedContentChecker().canDisplayInsecureContent(topFrame.document()->securityOrigin(), MixedContentChecker::ContentType::Active, newRequest.url())) {
    536535            cancelMainResourceLoad(frameLoader()->cancelledError(newRequest));
    537             return;
     536            return completionHandler(WTFMove(newRequest));
    538537        }
    539538    }
     
    541540#if ENABLE(CONTENT_FILTERING)
    542541    if (m_contentFilter && !m_contentFilter->continueAfterWillSendRequest(newRequest, redirectResponse))
    543         return;
     542        return completionHandler(WTFMove(newRequest));
    544543#endif
    545544
     
    562561    // synchronously for these redirect cases.
    563562    if (!didReceiveRedirectResponse)
    564         return;
     563        return completionHandler(WTFMove(newRequest));
    565564
    566565    ASSERT(!m_waitingForNavigationPolicy);
    567566    m_waitingForNavigationPolicy = true;
    568     frameLoader()->policyChecker().checkNavigationPolicy(newRequest, didReceiveRedirectResponse, [this, protectedThis = makeRef(*this)] (const ResourceRequest& request, FormState*, bool shouldContinue) {
     567    frameLoader()->policyChecker().checkNavigationPolicy(ResourceRequest(newRequest), didReceiveRedirectResponse, [this, protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)] (ResourceRequest&& request, FormState*, bool shouldContinue) mutable {
    569568        continueAfterNavigationPolicy(request, shouldContinue);
     569        completionHandler(WTFMove(request));
    570570    });
    571571}
     
    14541454    ASSERT(timing().fetchStart());
    14551455
    1456     Ref<DocumentLoader> protectedThis(*this); // willSendRequest() may deallocate the provisional loader (which may be us) if it cancels the load.
    1457     willSendRequest(m_request, ResourceResponse());
    1458 
    1459     // willSendRequest() may lead to our Frame being detached or cancelling the load via nulling the ResourceRequest.
    1460     if (!m_frame || m_request.isNull()) {
    1461         RELEASE_LOG_IF_ALLOWED("startLoadingMainResource: Load canceled after willSendRequest (frame = %p, main = %d)", m_frame, m_frame ? m_frame->isMainFrame() : false);
    1462         return;
    1463     }
    1464 
    1465     m_applicationCacheHost->maybeLoadMainResource(m_request, m_substituteData);
    1466 
    1467     if (m_substituteData.isValid() && m_frame->page()) {
    1468         RELEASE_LOG_IF_ALLOWED("startLoadingMainResource: Returning cached main resource (frame = %p, main = %d)", m_frame, m_frame->isMainFrame());
    1469         m_identifierForLoadWithoutResourceLoader = m_frame->page()->progress().createUniqueIdentifier();
    1470         frameLoader()->notifier().assignIdentifierToInitialRequest(m_identifierForLoadWithoutResourceLoader, this, m_request);
    1471         frameLoader()->notifier().dispatchWillSendRequest(this, m_identifierForLoadWithoutResourceLoader, m_request, ResourceResponse());
    1472         handleSubstituteDataLoadSoon();
    1473         return;
    1474     }
    1475 
    1476     ResourceRequest request(m_request);
    1477     request.setRequester(ResourceRequest::Requester::Main);
    1478     // If this is a reload the cache layer might have made the previous request conditional. DocumentLoader can't handle 304 responses itself.
    1479     request.makeUnconditional();
    1480 
    1481     RELEASE_LOG_IF_ALLOWED("startLoadingMainResource: Starting load (frame = %p, main = %d)", m_frame, m_frame->isMainFrame());
    1482 
    1483     static NeverDestroyed<ResourceLoaderOptions> mainResourceLoadOptions(SendCallbacks, SniffContent, BufferData, StoredCredentialsPolicy::Use, ClientCredentialPolicy::MayAskClientForCredentials, FetchOptions::Credentials::Include, SkipSecurityCheck, FetchOptions::Mode::NoCors, IncludeCertificateInfo, ContentSecurityPolicyImposition::SkipPolicyCheck, DefersLoadingPolicy::AllowDefersLoading, CachingPolicy::AllowCaching);
    1484     CachedResourceRequest mainResourceRequest(ResourceRequest(request), mainResourceLoadOptions);
    1485     if (!m_frame->isMainFrame() && m_frame->document()) {
    1486         // If we are loading the main resource of a subframe, use the cache partition of the main document.
    1487         mainResourceRequest.setDomainForCachePartition(*m_frame->document());
    1488     } else {
    1489         auto origin = SecurityOrigin::create(request.url());
    1490         origin->setStorageBlockingPolicy(frameLoader()->frame().settings().storageBlockingPolicy());
    1491         mainResourceRequest.setDomainForCachePartition(origin->domainForCachePartition());
    1492     }
    1493     m_mainResource = m_cachedResourceLoader->requestMainResource(WTFMove(mainResourceRequest)).valueOr(nullptr);
     1456    willSendRequest(ResourceRequest(m_request), ResourceResponse(), [this, protectedThis = makeRef(*this)] (ResourceRequest&& request) {
     1457        m_request = request;
     1458
     1459        // willSendRequest() may lead to our Frame being detached or cancelling the load via nulling the ResourceRequest.
     1460        if (!m_frame || m_request.isNull()) {
     1461            RELEASE_LOG_IF_ALLOWED("startLoadingMainResource: Load canceled after willSendRequest (frame = %p, main = %d)", m_frame, m_frame ? m_frame->isMainFrame() : false);
     1462            return;
     1463        }
     1464
     1465        m_applicationCacheHost->maybeLoadMainResource(m_request, m_substituteData);
     1466
     1467        if (m_substituteData.isValid() && m_frame->page()) {
     1468            RELEASE_LOG_IF_ALLOWED("startLoadingMainResource: Returning cached main resource (frame = %p, main = %d)", m_frame, m_frame->isMainFrame());
     1469            m_identifierForLoadWithoutResourceLoader = m_frame->page()->progress().createUniqueIdentifier();
     1470            frameLoader()->notifier().assignIdentifierToInitialRequest(m_identifierForLoadWithoutResourceLoader, this, m_request);
     1471            frameLoader()->notifier().dispatchWillSendRequest(this, m_identifierForLoadWithoutResourceLoader, m_request, ResourceResponse());
     1472            handleSubstituteDataLoadSoon();
     1473            return;
     1474        }
     1475
     1476        request.setRequester(ResourceRequest::Requester::Main);
     1477        // If this is a reload the cache layer might have made the previous request conditional. DocumentLoader can't handle 304 responses itself.
     1478        request.makeUnconditional();
     1479
     1480        RELEASE_LOG_IF_ALLOWED("startLoadingMainResource: Starting load (frame = %p, main = %d)", m_frame, m_frame->isMainFrame());
     1481
     1482        static NeverDestroyed<ResourceLoaderOptions> mainResourceLoadOptions(SendCallbacks, SniffContent, BufferData, StoredCredentialsPolicy::Use, ClientCredentialPolicy::MayAskClientForCredentials, FetchOptions::Credentials::Include, SkipSecurityCheck, FetchOptions::Mode::NoCors, IncludeCertificateInfo, ContentSecurityPolicyImposition::SkipPolicyCheck, DefersLoadingPolicy::AllowDefersLoading, CachingPolicy::AllowCaching);
     1483        CachedResourceRequest mainResourceRequest(ResourceRequest(request), mainResourceLoadOptions);
     1484        if (!m_frame->isMainFrame() && m_frame->document()) {
     1485            // If we are loading the main resource of a subframe, use the cache partition of the main document.
     1486            mainResourceRequest.setDomainForCachePartition(*m_frame->document());
     1487        } else {
     1488            auto origin = SecurityOrigin::create(request.url());
     1489            origin->setStorageBlockingPolicy(frameLoader()->frame().settings().storageBlockingPolicy());
     1490            mainResourceRequest.setDomainForCachePartition(origin->domainForCachePartition());
     1491        }
     1492        m_mainResource = m_cachedResourceLoader->requestMainResource(WTFMove(mainResourceRequest)).valueOr(nullptr);
    14941493
    14951494#if ENABLE(CONTENT_EXTENSIONS)
    1496     if (m_mainResource && m_mainResource->errorOccurred() && m_frame->page() && m_mainResource->resourceError().domain() == ContentExtensions::WebKitContentBlockerDomain) {
    1497         RELEASE_LOG_IF_ALLOWED("startLoadingMainResource: Blocked by content blocker error (frame = %p, main = %d)", m_frame, m_frame->isMainFrame());
    1498         cancelMainResourceLoad(frameLoader()->blockedByContentBlockerError(m_request));
    1499         return;
    1500     }
    1501 #endif
    1502 
    1503     if (!m_mainResource) {
    1504         if (!m_request.url().isValid()) {
    1505             RELEASE_LOG_IF_ALLOWED("startLoadingMainResource: Unable to load main resource, URL is invalid (frame = %p, main = %d)", m_frame, m_frame->isMainFrame());
    1506             cancelMainResourceLoad(frameLoader()->client().cannotShowURLError(m_request));
     1495        if (m_mainResource && m_mainResource->errorOccurred() && m_frame->page() && m_mainResource->resourceError().domain() == ContentExtensions::WebKitContentBlockerDomain) {
     1496            RELEASE_LOG_IF_ALLOWED("startLoadingMainResource: Blocked by content blocker error (frame = %p, main = %d)", m_frame, m_frame->isMainFrame());
     1497            cancelMainResourceLoad(frameLoader()->blockedByContentBlockerError(m_request));
    15071498            return;
    15081499        }
    1509 
    1510         RELEASE_LOG_IF_ALLOWED("startLoadingMainResource: Unable to load main resource, returning empty document (frame = %p, main = %d)", m_frame, m_frame->isMainFrame());
    1511 
    1512         setRequest(ResourceRequest());
    1513         // If the load was aborted by clearing m_request, it's possible the ApplicationCacheHost
    1514         // is now in a state where starting an empty load will be inconsistent. Replace it with
    1515         // a new ApplicationCacheHost.
    1516         m_applicationCacheHost = std::make_unique<ApplicationCacheHost>(*this);
    1517         maybeLoadEmpty();
    1518         return;
    1519     }
    1520 
    1521     if (!mainResourceLoader()) {
    1522         m_identifierForLoadWithoutResourceLoader = m_frame->page()->progress().createUniqueIdentifier();
    1523         frameLoader()->notifier().assignIdentifierToInitialRequest(m_identifierForLoadWithoutResourceLoader, this, request);
    1524         frameLoader()->notifier().dispatchWillSendRequest(this, m_identifierForLoadWithoutResourceLoader, request, ResourceResponse());
    1525     }
    1526 
    1527     becomeMainResourceClient();
    1528 
    1529     // A bunch of headers are set when the underlying ResourceLoader is created, and m_request needs to include those.
    1530     if (mainResourceLoader())
    1531         request = mainResourceLoader()->originalRequest();
    1532     // If there was a fragment identifier on m_request, the cache will have stripped it. m_request should include
    1533     // the fragment identifier, so add that back in.
    1534     if (equalIgnoringFragmentIdentifier(m_request.url(), request.url()))
    1535         request.setURL(m_request.url());
    1536     setRequest(request);
     1500#endif
     1501
     1502        if (!m_mainResource) {
     1503            if (!m_request.url().isValid()) {
     1504                RELEASE_LOG_IF_ALLOWED("startLoadingMainResource: Unable to load main resource, URL is invalid (frame = %p, main = %d)", m_frame, m_frame->isMainFrame());
     1505                cancelMainResourceLoad(frameLoader()->client().cannotShowURLError(m_request));
     1506                return;
     1507            }
     1508
     1509            RELEASE_LOG_IF_ALLOWED("startLoadingMainResource: Unable to load main resource, returning empty document (frame = %p, main = %d)", m_frame, m_frame->isMainFrame());
     1510
     1511            setRequest(ResourceRequest());
     1512            // If the load was aborted by clearing m_request, it's possible the ApplicationCacheHost
     1513            // is now in a state where starting an empty load will be inconsistent. Replace it with
     1514            // a new ApplicationCacheHost.
     1515            m_applicationCacheHost = std::make_unique<ApplicationCacheHost>(*this);
     1516            maybeLoadEmpty();
     1517            return;
     1518        }
     1519
     1520        if (!mainResourceLoader()) {
     1521            m_identifierForLoadWithoutResourceLoader = m_frame->page()->progress().createUniqueIdentifier();
     1522            frameLoader()->notifier().assignIdentifierToInitialRequest(m_identifierForLoadWithoutResourceLoader, this, request);
     1523            frameLoader()->notifier().dispatchWillSendRequest(this, m_identifierForLoadWithoutResourceLoader, request, ResourceResponse());
     1524        }
     1525
     1526        becomeMainResourceClient();
     1527
     1528        // A bunch of headers are set when the underlying ResourceLoader is created, and m_request needs to include those.
     1529        if (mainResourceLoader())
     1530            request = mainResourceLoader()->originalRequest();
     1531        // If there was a fragment identifier on m_request, the cache will have stripped it. m_request should include
     1532        // the fragment identifier, so add that back in.
     1533        if (equalIgnoringFragmentIdentifier(m_request.url(), request.url()))
     1534            request.setURL(m_request.url());
     1535        setRequest(request);
     1536    });
    15371537}
    15381538
  • trunk/Source/WebCore/loader/DocumentLoader.h

    r224699 r224758  
    325325#endif
    326326
    327     void willSendRequest(ResourceRequest&, const ResourceResponse&);
     327    void willSendRequest(ResourceRequest&&, const ResourceResponse&, CompletionHandler<void(ResourceRequest&&)>&&);
    328328    void finishedLoading();
    329329    void mainReceivedError(const ResourceError&);
  • trunk/Source/WebCore/loader/FrameLoader.cpp

    r224684 r224758  
    13131313        policyChecker().stopCheck();
    13141314        policyChecker().setLoadType(newLoadType);
    1315         policyChecker().checkNavigationPolicy(request, false /* didReceiveRedirectResponse */, oldDocumentLoader.get(), formState, [this] (const ResourceRequest& request, FormState*, bool shouldContinue) {
     1315        policyChecker().checkNavigationPolicy(WTFMove(request), false /* didReceiveRedirectResponse */, oldDocumentLoader.get(), formState, [this] (const ResourceRequest& request, FormState*, bool shouldContinue) {
    13161316            continueFragmentScrollAfterNavigationPolicy(request, shouldContinue);
    13171317        });
     
    14731473        oldDocumentLoader->setLastCheckedRequest(ResourceRequest());
    14741474        policyChecker().stopCheck();
    1475         policyChecker().checkNavigationPolicy(loader->request(), false /* didReceiveRedirectResponse */, oldDocumentLoader.get(), formState, [this] (const ResourceRequest& request, FormState*, bool shouldContinue) {
     1475        policyChecker().checkNavigationPolicy(ResourceRequest(loader->request()), false /* didReceiveRedirectResponse */, oldDocumentLoader.get(), formState, [this] (const ResourceRequest& request, FormState*, bool shouldContinue) {
    14761476            continueFragmentScrollAfterNavigationPolicy(request, shouldContinue);
    14771477        });
     
    15001500    }
    15011501
    1502     policyChecker().checkNavigationPolicy(loader->request(), false /* didReceiveRedirectResponse */, loader, formState, [this, allowNavigationToInvalidURL] (const ResourceRequest& request, FormState* formState, bool shouldContinue) {
     1502    policyChecker().checkNavigationPolicy(ResourceRequest(loader->request()), false /* didReceiveRedirectResponse */, loader, formState, [this, allowNavigationToInvalidURL] (const ResourceRequest& request, FormState* formState, bool shouldContinue) {
    15031503        continueLoadAfterNavigationPolicy(request, formState, shouldContinue, allowNavigationToInvalidURL);
    15041504    });
  • trunk/Source/WebCore/loader/PolicyChecker.cpp

    r224699 r224758  
    7777}
    7878
    79 void PolicyChecker::checkNavigationPolicy(const ResourceRequest& newRequest, bool didReceiveRedirectResponse, NavigationPolicyDecisionFunction&& function)
    80 {
    81     checkNavigationPolicy(newRequest, didReceiveRedirectResponse, m_frame.loader().activeDocumentLoader(), nullptr, WTFMove(function));
    82 }
    83 
    84 void PolicyChecker::checkNavigationPolicy(const ResourceRequest& request, bool didReceiveRedirectResponse, DocumentLoader* loader, FormState* formState, NavigationPolicyDecisionFunction&& function)
     79void PolicyChecker::checkNavigationPolicy(ResourceRequest&& newRequest, bool didReceiveRedirectResponse, NavigationPolicyDecisionFunction&& function)
     80{
     81    checkNavigationPolicy(WTFMove(newRequest), didReceiveRedirectResponse, m_frame.loader().activeDocumentLoader(), nullptr, WTFMove(function));
     82}
     83
     84void PolicyChecker::checkNavigationPolicy(ResourceRequest&& request, bool didReceiveRedirectResponse, DocumentLoader* loader, FormState* formState, NavigationPolicyDecisionFunction&& function)
    8585{
    8686    NavigationAction action = loader->triggeringAction();
     
    9393    // This avoids confusion on the part of the client.
    9494    if (equalIgnoringHeaderFields(request, loader->lastCheckedRequest()) || (!request.isNull() && request.url().isEmpty())) {
    95         function(request, nullptr, true);
    96         loader->setLastCheckedRequest(ResourceRequest(request));
     95        function(ResourceRequest(request), nullptr, true);
     96        loader->setLastCheckedRequest(WTFMove(request));
    9797        return;
    9898    }
     
    108108        if (isBackForwardLoadType(m_loadType))
    109109            m_loadType = FrameLoadType::Reload;
    110         function(request, nullptr, shouldContinue);
     110        function(WTFMove(request), nullptr, shouldContinue);
    111111        return;
    112112    }
     
    118118            m_frame.ownerElement()->dispatchEvent(Event::create(eventNames().loadEvent, false, false));
    119119        }
    120         function(request, nullptr, false);
     120        function(WTFMove(request), nullptr, false);
    121121        return;
    122122    }
     
    127127    // Always allow QuickLook-generated URLs based on the protocol scheme.
    128128    if (!request.isNull() && isQuickLookPreviewURL(request.url()))
    129         return function(request, formState, true);
     129        return function(WTFMove(request), formState, true);
    130130#endif
    131131
     
    158158                return function({ }, nullptr, false);
    159159            }
    160             return function(request, formState.get(), true);
     160            return function(WTFMove(request), formState.get(), true);
    161161        }
    162162        ASSERT_NOT_REACHED();
  • trunk/Source/WebCore/loader/PolicyChecker.h

    r222472 r224758  
    5252
    5353using NewWindowPolicyDecisionFunction = WTF::CompletionHandler<void(const ResourceRequest&, FormState*, const String& frameName, const NavigationAction&, bool shouldContinue)>;
    54 using NavigationPolicyDecisionFunction = WTF::CompletionHandler<void(const ResourceRequest&, FormState*, bool shouldContinue)>;
     54using NavigationPolicyDecisionFunction = WTF::CompletionHandler<void(ResourceRequest&&, FormState*, bool shouldContinue)>;
    5555
    5656class PolicyChecker {
     
    6060    explicit PolicyChecker(Frame&);
    6161
    62     void checkNavigationPolicy(const ResourceRequest&, bool didReceiveRedirectResponse, DocumentLoader*, FormState*, NavigationPolicyDecisionFunction&&);
    63     void checkNavigationPolicy(const ResourceRequest&, bool didReceiveRedirectResponse, NavigationPolicyDecisionFunction&&);
     62    void checkNavigationPolicy(ResourceRequest&&, bool didReceiveRedirectResponse, DocumentLoader*, FormState*, NavigationPolicyDecisionFunction&&);
     63    void checkNavigationPolicy(ResourceRequest&&, bool didReceiveRedirectResponse, NavigationPolicyDecisionFunction&&);
    6464    void checkNewWindowPolicy(NavigationAction&&, const ResourceRequest&, FormState*, const String& frameName, NewWindowPolicyDecisionFunction&&);
    6565
Note: See TracChangeset for help on using the changeset viewer.