Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(571)

Unified Diff: Source/core/fetch/ResourceFetcher.cpp

Issue 1170503003: Remove resource type-specific fetching logic from ResourceFetcher (Closed) Base URL: svn://svn.chromium.org/blink/trunk
Patch Set: Null-check Document::loader() before calling startPreload() Created 5 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « Source/core/fetch/ResourceFetcher.h ('k') | Source/core/fetch/ResourceFetcherTest.cpp » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: Source/core/fetch/ResourceFetcher.cpp
diff --git a/Source/core/fetch/ResourceFetcher.cpp b/Source/core/fetch/ResourceFetcher.cpp
index dc146e52c7777576225c3f6d8eb58bacd94bf2b7..b73d00bb0108bab15312312a89e8d62ccd563327 100644
--- a/Source/core/fetch/ResourceFetcher.cpp
+++ b/Source/core/fetch/ResourceFetcher.cpp
@@ -28,21 +28,13 @@
#include "core/fetch/ResourceFetcher.h"
#include "bindings/core/v8/V8DOMActivityLogger.h"
-#include "core/fetch/CSSStyleSheetResource.h"
#include "core/fetch/CrossOriginAccessControl.h"
-#include "core/fetch/DocumentResource.h"
#include "core/fetch/FetchContext.h"
#include "core/fetch/FetchInitiatorTypeNames.h"
-#include "core/fetch/FontResource.h"
-#include "core/fetch/ImageResource.h"
#include "core/fetch/MemoryCache.h"
-#include "core/fetch/RawResource.h"
#include "core/fetch/ResourceLoader.h"
#include "core/fetch/ResourceLoaderSet.h"
-#include "core/fetch/ScriptResource.h"
-#include "core/fetch/SubstituteData.h"
#include "core/fetch/UniqueIdentifier.h"
-#include "core/fetch/XSLStyleSheetResource.h"
#include "core/timing/ResourceTimingInfo.h"
#include "platform/Logging.h"
#include "platform/RuntimeEnabledFeatures.h"
@@ -64,40 +56,6 @@ using blink::WebURLRequest;
namespace blink {
-static Resource* createResource(Resource::Type type, const ResourceRequest& request, const String& charset)
-{
- switch (type) {
- case Resource::Image:
- return new ImageResource(request);
- case Resource::CSSStyleSheet:
- return new CSSStyleSheetResource(request, charset);
- case Resource::Script:
- return ScriptResource::create(request, charset).leakPtr();
- case Resource::SVGDocument:
- return new DocumentResource(request, Resource::SVGDocument);
- case Resource::Font:
- return new FontResource(request);
- case Resource::MainResource:
- case Resource::Raw:
- case Resource::TextTrack:
- case Resource::Media:
- return new RawResource(request, type);
- case Resource::XSLStyleSheet:
- return new XSLStyleSheetResource(request, charset);
- case Resource::LinkPrefetch:
- return new Resource(request, Resource::LinkPrefetch);
- case Resource::LinkSubresource:
- return new Resource(request, Resource::LinkSubresource);
- case Resource::LinkPreload:
- return new Resource(request, Resource::LinkPreload);
- case Resource::ImportResource:
- return new RawResource(request, type);
- }
-
- ASSERT_NOT_REACHED();
- return 0;
-}
-
ResourceLoadPriority ResourceFetcher::loadPriority(Resource::Type type, const FetchRequest& request)
{
// TODO(yoav): Change it here so that priority can be changed even after it was resolved.
@@ -143,29 +101,6 @@ ResourceLoadPriority ResourceFetcher::loadPriority(Resource::Type type, const Fe
return ResourceLoadPriorityUnresolved;
}
-static Resource* resourceFromDataURIRequest(const ResourceRequest& request, const ResourceLoaderOptions& resourceOptions, const String& cacheIdentifier)
-{
- const KURL& url = request.url();
- ASSERT(url.protocolIsData());
-
- WebString mimetype;
- WebString charset;
- RefPtr<SharedBuffer> data = PassRefPtr<SharedBuffer>(Platform::current()->parseDataURL(url, mimetype, charset));
- if (!data)
- return nullptr;
- ResourceResponse response(url, mimetype, data->size(), charset, String());
-
- Resource* resource = createResource(Resource::Image, request, charset);
- resource->setOptions(resourceOptions);
- // FIXME: We should provide a body stream here.
- resource->responseReceived(response, nullptr);
- if (data->size())
- resource->setResourceBuffer(data);
- resource->setCacheIdentifier(cacheIdentifier);
- resource->finish();
- return resource;
-}
-
static void populateResourceTiming(ResourceTimingInfo* info, Resource* resource, bool clearLoadTimings)
{
info->setInitialRequest(resource->resourceRequest());
@@ -247,159 +182,6 @@ Resource* ResourceFetcher::cachedResource(const KURL& resourceURL) const
return m_documentResources.get(url).get();
}
-ResourcePtr<Resource> ResourceFetcher::fetchSynchronously(FetchRequest& request)
-{
- request.mutableResourceRequest().setTimeoutInterval(10);
- ResourceLoaderOptions options(request.options());
- options.synchronousPolicy = RequestSynchronously;
- request.setOptions(options);
- return requestResource(Resource::Raw, request);
-}
-
-ResourcePtr<ImageResource> ResourceFetcher::fetchImage(FetchRequest& request)
-{
- if (request.resourceRequest().requestContext() == WebURLRequest::RequestContextUnspecified)
- request.mutableResourceRequest().setRequestContext(WebURLRequest::RequestContextImage);
- if (context().pageDismissalEventBeingDispatched()) {
- KURL requestURL = request.resourceRequest().url();
- if (requestURL.isValid() && context().canRequest(Resource::Image, request.resourceRequest(), requestURL, request.options(), request.forPreload(), request.originRestriction()))
- context().sendImagePing(requestURL);
- return 0;
- }
-
- if (request.resourceRequest().url().protocolIsData())
- preCacheDataURIImage(request);
-
- if (clientDefersImage(request.resourceRequest().url()))
- request.setDefer(FetchRequest::DeferredByClient);
- ResourcePtr<Resource> resource = requestResource(Resource::Image, request);
- return resource && resource->type() == Resource::Image ? toImageResource(resource) : 0;
-}
-
-void ResourceFetcher::preCacheDataURIImage(const FetchRequest& request)
-{
- const KURL& url = request.resourceRequest().url();
- ASSERT(url.protocolIsData());
-
- const String cacheIdentifier = getCacheIdentifier();
- if (memoryCache()->resourceForURL(url, cacheIdentifier))
- return;
-
- if (Resource* resource = resourceFromDataURIRequest(request.resourceRequest(), request.options(), cacheIdentifier)) {
- memoryCache()->add(resource);
- scheduleDocumentResourcesGC();
- }
-}
-
-ResourcePtr<FontResource> ResourceFetcher::fetchFont(FetchRequest& request)
-{
- ASSERT(request.resourceRequest().frameType() == WebURLRequest::FrameTypeNone);
- request.mutableResourceRequest().setRequestContext(WebURLRequest::RequestContextFont);
- return toFontResource(requestResource(Resource::Font, request));
-}
-
-ResourcePtr<RawResource> ResourceFetcher::fetchImport(FetchRequest& request)
-{
- ASSERT(request.resourceRequest().frameType() == WebURLRequest::FrameTypeNone);
- request.mutableResourceRequest().setRequestContext(WebURLRequest::RequestContextImport);
- return toRawResource(requestResource(Resource::ImportResource, request));
-}
-
-ResourcePtr<CSSStyleSheetResource> ResourceFetcher::fetchCSSStyleSheet(FetchRequest& request)
-{
- ASSERT(request.resourceRequest().frameType() == WebURLRequest::FrameTypeNone);
- request.mutableResourceRequest().setRequestContext(WebURLRequest::RequestContextStyle);
- return toCSSStyleSheetResource(requestResource(Resource::CSSStyleSheet, request));
-}
-
-ResourcePtr<ScriptResource> ResourceFetcher::fetchScript(FetchRequest& request)
-{
- ASSERT(request.resourceRequest().frameType() == WebURLRequest::FrameTypeNone);
- request.mutableResourceRequest().setRequestContext(WebURLRequest::RequestContextScript);
- return toScriptResource(requestResource(Resource::Script, request));
-}
-
-ResourcePtr<XSLStyleSheetResource> ResourceFetcher::fetchXSLStyleSheet(FetchRequest& request)
-{
- ASSERT(RuntimeEnabledFeatures::xsltEnabled());
- request.mutableResourceRequest().setRequestContext(WebURLRequest::RequestContextXSLT);
- return toXSLStyleSheetResource(requestResource(Resource::XSLStyleSheet, request));
-}
-
-ResourcePtr<DocumentResource> ResourceFetcher::fetchSVGDocument(FetchRequest& request)
-{
- ASSERT(request.resourceRequest().frameType() == WebURLRequest::FrameTypeNone);
- request.mutableResourceRequest().setRequestContext(WebURLRequest::RequestContextImage);
- return toDocumentResource(requestResource(Resource::SVGDocument, request));
-}
-
-ResourcePtr<Resource> ResourceFetcher::fetchLinkResource(Resource::Type type, FetchRequest& request)
-{
- ASSERT(type == Resource::LinkPrefetch || type == Resource::LinkSubresource);
- ASSERT(request.resourceRequest().frameType() == WebURLRequest::FrameTypeNone);
- request.mutableResourceRequest().setRequestContext(type == Resource::LinkPrefetch ? WebURLRequest::RequestContextPrefetch : WebURLRequest::RequestContextSubresource);
- return requestResource(type, request);
-}
-
-ResourcePtr<Resource> ResourceFetcher::fetchLinkPreloadResource(Resource::Type type, FetchRequest& request)
-{
- // TODO(yoav): Enforce a LinkPreload context here, once we know we're adding one - https://github.com/whatwg/fetch/issues/36
- ASSERT(request.resourceRequest().frameType() == WebURLRequest::FrameTypeNone);
- determineRequestContext(request.mutableResourceRequest(), type);
- return requestResource(type, request);
-}
-
-ResourcePtr<RawResource> ResourceFetcher::fetchRawResource(FetchRequest& request)
-{
- ASSERT(request.resourceRequest().frameType() == WebURLRequest::FrameTypeNone);
- ASSERT(request.resourceRequest().requestContext() != WebURLRequest::RequestContextUnspecified);
- return toRawResource(requestResource(Resource::Raw, request));
-}
-
-ResourcePtr<RawResource> ResourceFetcher::fetchMainResource(FetchRequest& request, const SubstituteData& substituteData)
-{
- ASSERT(request.resourceRequest().frameType() != WebURLRequest::FrameTypeNone);
- ASSERT(request.resourceRequest().requestContext() == WebURLRequest::RequestContextForm || request.resourceRequest().requestContext() == WebURLRequest::RequestContextFrame || request.resourceRequest().requestContext() == WebURLRequest::RequestContextHyperlink || request.resourceRequest().requestContext() == WebURLRequest::RequestContextIframe || request.resourceRequest().requestContext() == WebURLRequest::RequestContextInternal || request.resourceRequest().requestContext() == WebURLRequest::RequestContextLocation);
-
- if (substituteData.isValid())
- preCacheSubstituteDataForMainResource(request, substituteData);
- return toRawResource(requestResource(Resource::MainResource, request));
-}
-
-ResourcePtr<RawResource> ResourceFetcher::fetchMedia(FetchRequest& request)
-{
- ASSERT(request.resourceRequest().frameType() == WebURLRequest::FrameTypeNone);
- ASSERT(request.resourceRequest().requestContext() == WebURLRequest::RequestContextAudio || request.resourceRequest().requestContext() == WebURLRequest::RequestContextVideo);
- return toRawResource(requestResource(Resource::Media, request));
-}
-
-ResourcePtr<RawResource> ResourceFetcher::fetchTextTrack(FetchRequest& request)
-{
- ASSERT(request.resourceRequest().frameType() == WebURLRequest::FrameTypeNone);
- request.mutableResourceRequest().setRequestContext(WebURLRequest::RequestContextTrack);
- return toRawResource(requestResource(Resource::TextTrack, request));
-}
-
-void ResourceFetcher::preCacheSubstituteDataForMainResource(const FetchRequest& request, const SubstituteData& substituteData)
-{
- const String cacheIdentifier = getCacheIdentifier();
- const KURL& url = request.url();
- if (Resource* oldResource = memoryCache()->resourceForURL(url, cacheIdentifier))
- memoryCache()->remove(oldResource);
-
- ResourceResponse response(url, substituteData.mimeType(), substituteData.content()->size(), substituteData.textEncoding(), emptyString());
- ResourcePtr<Resource> resource = createResource(Resource::MainResource, request.resourceRequest(), substituteData.textEncoding());
- resource->setNeedsSynchronousCacheHit(substituteData.forceSynchronousLoad());
- resource->setOptions(request.options());
- resource->setDataBufferingPolicy(BufferData);
- resource->responseReceived(response, nullptr);
- if (substituteData.content()->size())
- resource->setResourceBuffer(substituteData.content());
- resource->setCacheIdentifier(cacheIdentifier);
- resource->finish();
- memoryCache()->add(resource.get());
-}
-
bool ResourceFetcher::canAccessResource(Resource* resource, SecurityOrigin* sourceOrigin, const KURL& url, AccessControlLoggingDecision logErrorsDecision) const
{
// Redirects can change the response URL different from one of request.
@@ -415,8 +197,7 @@ bool ResourceFetcher::canAccessResource(Resource* resource, SecurityOrigin* sour
String errorDescription;
if (!resource->passesAccessControlCheck(sourceOrigin, errorDescription)) {
- if (resource->type() == Resource::Font)
- toFontResource(resource)->setCORSFailed();
+ resource->setCORSFailed();
if (!forPreload && (logErrorsDecision == ShouldLogAccessControlErrors)) {
String resourceType = Resource::resourceTypeToString(resource->type(), resource->options().initiatorInfo);
context().addConsoleMessage(resourceType + " from origin '" + SecurityOrigin::create(url)->toString() + "' has been blocked from loading by Cross-Origin Resource Sharing policy: " + errorDescription);
@@ -469,19 +250,19 @@ void ResourceFetcher::requestLoadStarted(Resource* resource, const FetchRequest&
m_validatedURLs.add(request.resourceRequest().url());
}
-ResourcePtr<Resource> ResourceFetcher::requestResource(Resource::Type type, FetchRequest& request)
+ResourcePtr<Resource> ResourceFetcher::requestResource(FetchRequest& request, const ResourceFactory& factory)
{
- ASSERT(request.options().synchronousPolicy == RequestAsynchronously || type == Resource::Raw);
+ ASSERT(request.options().synchronousPolicy == RequestAsynchronously || factory.type() == Resource::Raw);
TRACE_EVENT0("blink", "ResourceFetcher::requestResource");
context().upgradeInsecureRequest(request);
context().addClientHintsIfNecessary(request);
- context().addCSPHeaderIfNecessary(type, request);
+ context().addCSPHeaderIfNecessary(factory.type(), request);
KURL url = request.resourceRequest().url();
- WTF_LOG(ResourceLoading, "ResourceFetcher::requestResource '%s', charset '%s', priority=%d, forPreload=%u, type=%s", url.elidedString().latin1().data(), request.charset().latin1().data(), request.priority(), request.forPreload(), ResourceTypeName(type));
+ WTF_LOG(ResourceLoading, "ResourceFetcher::requestResource '%s', charset '%s', priority=%d, forPreload=%u, type=%s", url.elidedString().latin1().data(), request.charset().latin1().data(), request.priority(), request.forPreload(), ResourceTypeName(factory.type()));
// If only the fragment identifiers differ, it is the same resource.
url = MemoryCache::removeFragmentIdentifierIfNeeded(url);
@@ -489,7 +270,7 @@ ResourcePtr<Resource> ResourceFetcher::requestResource(Resource::Type type, Fetc
if (!url.isValid())
return nullptr;
- if (!context().canRequest(type, request.resourceRequest(), url, request.options(), request.forPreload(), request.originRestriction()))
+ if (!context().canRequest(factory.type(), request.resourceRequest(), url, request.options(), request.forPreload(), request.originRestriction()))
return nullptr;
context().dispatchWillRequestResource(&request);
@@ -503,7 +284,7 @@ ResourcePtr<Resource> ResourceFetcher::requestResource(Resource::Type type, Fetc
if (activityLogger) {
Vector<String> argv;
- argv.append(Resource::resourceTypeToString(type, request.options().initiatorInfo));
+ argv.append(Resource::resourceTypeToString(factory.type(), request.options().initiatorInfo));
argv.append(url);
activityLogger->logEvent("blinkRequestResource", argv.size(), argv.data());
}
@@ -512,16 +293,16 @@ ResourcePtr<Resource> ResourceFetcher::requestResource(Resource::Type type, Fetc
// See if we can use an existing resource from the cache.
ResourcePtr<Resource> resource = memoryCache()->resourceForURL(url, getCacheIdentifier());
- const RevalidationPolicy policy = determineRevalidationPolicy(type, request, resource.get());
+ const RevalidationPolicy policy = determineRevalidationPolicy(factory.type(), request, resource.get());
switch (policy) {
case Reload:
memoryCache()->remove(resource.get());
// Fall through
case Load:
- resource = createResourceForLoading(type, request, request.charset());
+ resource = createResourceForLoading(request, request.charset(), factory);
break;
case Revalidate:
- resource = createResourceForRevalidation(request, resource.get());
+ resource = createResourceForRevalidation(request, resource.get(), factory);
break;
case Use:
memoryCache()->updateForAccess(resource.get());
@@ -530,6 +311,10 @@ ResourcePtr<Resource> ResourceFetcher::requestResource(Resource::Type type, Fetc
if (!resource)
return nullptr;
+ if (resource->type() != factory.type()) {
+ ASSERT(request.forPreload());
+ return nullptr;
+ }
if (!resource->hasClients())
m_deadStatsRecorder.update(policy);
@@ -538,7 +323,7 @@ ResourcePtr<Resource> ResourceFetcher::requestResource(Resource::Type type, Fetc
resource->setIdentifier(createUniqueIdentifier());
if (!request.forPreload() || policy != Use) {
- ResourceLoadPriority priority = loadPriority(type, request);
+ ResourceLoadPriority priority = loadPriority(factory.type(), request);
// When issuing another request for a resource that is already in-flight make
// sure to not demote the priority of the in-flight request. If the new request
// isn't at the same priority as the in-flight request, only allow promotions.
@@ -551,7 +336,7 @@ ResourcePtr<Resource> ResourceFetcher::requestResource(Resource::Type type, Fetc
}
if (resourceNeedsLoad(resource.get(), request, policy)) {
- if (!context().shouldLoadNewResource(type)) {
+ if (!context().shouldLoadNewResource(factory.type())) {
if (memoryCache()->contains(resource.get()))
memoryCache()->remove(resource.get());
return nullptr;
@@ -578,7 +363,7 @@ ResourcePtr<Resource> ResourceFetcher::requestResource(Resource::Type type, Fetc
// resources, we should be sure to understand the implications for memory
// use.
// Remove main resource from cache to prevent reuse.
- if (type == Resource::MainResource) {
+ if (factory.type() == Resource::MainResource) {
ASSERT(policy != Use || context().hasSubstituteData());
ASSERT(policy != Revalidate);
memoryCache()->remove(resource.get());
@@ -618,7 +403,7 @@ void ResourceFetcher::addAdditionalRequestHeaders(ResourceRequest& request, Reso
context().addAdditionalRequestHeaders(request, (type == Resource::MainResource) ? FetchMainResource : FetchSubresource);
}
-ResourcePtr<Resource> ResourceFetcher::createResourceForRevalidation(const FetchRequest& request, Resource* resource)
+ResourcePtr<Resource> ResourceFetcher::createResourceForRevalidation(const FetchRequest& request, Resource* resource, const ResourceFactory& factory)
{
ASSERT(resource);
ASSERT(memoryCache()->contains(resource));
@@ -648,7 +433,7 @@ ResourcePtr<Resource> ResourceFetcher::createResourceForRevalidation(const Fetch
revalidatingRequest.setHTTPHeaderField("Resource-Freshness", AtomicString(String::format("max-age=%.0lf,stale-while-revalidate=%.0lf,age=%.0lf", resource->freshnessLifetime(), stalenessLifetime, resource->currentAge())));
}
- ResourcePtr<Resource> newResource = createResource(resource->type(), revalidatingRequest, resource->encoding());
+ ResourcePtr<Resource> newResource = factory.create(revalidatingRequest, resource->encoding());
WTF_LOG(ResourceLoading, "Resource %p created to revalidate %p", newResource.get(), resource);
newResource->setResourceToRevalidate(resource);
@@ -659,15 +444,15 @@ ResourcePtr<Resource> ResourceFetcher::createResourceForRevalidation(const Fetch
return newResource;
}
-ResourcePtr<Resource> ResourceFetcher::createResourceForLoading(Resource::Type type, FetchRequest& request, const String& charset)
+ResourcePtr<Resource> ResourceFetcher::createResourceForLoading(FetchRequest& request, const String& charset, const ResourceFactory& factory)
{
const String cacheIdentifier = getCacheIdentifier();
ASSERT(!memoryCache()->resourceForURL(request.resourceRequest().url(), cacheIdentifier));
WTF_LOG(ResourceLoading, "Loading Resource for '%s'.", request.resourceRequest().url().elidedString().latin1().data());
- addAdditionalRequestHeaders(request.mutableResourceRequest(), type);
- ResourcePtr<Resource> resource = createResource(type, request.resourceRequest(), charset);
+ addAdditionalRequestHeaders(request.mutableResourceRequest(), factory.type());
+ ResourcePtr<Resource> resource = factory.create(request.resourceRequest(), charset);
resource->setCacheIdentifier(cacheIdentifier);
memoryCache()->add(resource.get());
@@ -935,39 +720,16 @@ int ResourceFetcher::requestCount() const
return m_loaders ? m_loaders->size() : 0;
}
-void ResourceFetcher::preload(Resource::Type type, FetchRequest& request, const String& charset)
+void ResourceFetcher::preloadStarted(Resource* resource)
{
- // Ensure main resources aren't preloaded, since the cache can't actually reuse the preload.
- if (type == Resource::MainResource)
- return;
-
- ASSERT(type == Resource::Script || type == Resource::CSSStyleSheet || type == Resource::Image);
-
- String encoding;
- if (type == Resource::Script || type == Resource::CSSStyleSheet) {
- encoding = charset.isEmpty() ? context().charset() : charset;
-
- // RequestContext for Resource::Image is set in fetchImage below.
- determineRequestContext(request.mutableResourceRequest(), type);
- }
-
- request.setCharset(encoding);
- request.setForPreload(true);
-
- ResourcePtr<Resource> resource;
- // Loading images involves several special cases, so use dedicated fetch method instead.
- if (type == Resource::Image)
- resource = fetchImage(request);
- else
- resource = requestResource(type, request);
- if (!resource || (m_preloads && m_preloads->contains(resource.get())))
+ if (m_preloads && m_preloads->contains(resource))
return;
- TRACE_EVENT_ASYNC_STEP_INTO0("blink.net", "Resource", resource.get(), "Preload");
+ TRACE_EVENT_ASYNC_STEP_INTO0("blink.net", "Resource", resource, "Preload");
resource->increasePreloadCount();
if (!m_preloads)
m_preloads = adoptPtr(new ListHashSet<Resource*>);
- m_preloads->add(resource.get());
+ m_preloads->add(resource);
#if PRELOAD_DEBUG
printf("PRELOADING %s\n", resource->url().string().latin1().data());
@@ -1182,8 +944,7 @@ bool ResourceFetcher::canAccessRedirect(Resource* resource, ResourceRequest& req
String errorMessage;
StoredCredentials withCredentials = resource->lastResourceRequest().allowStoredCredentials() ? AllowStoredCredentials : DoNotAllowStoredCredentials;
if (!CrossOriginAccessControl::handleRedirect(sourceOrigin, request, redirectResponse, withCredentials, options, errorMessage)) {
- if (resource->type() == Resource::Font)
- toFontResource(resource)->setCORSFailed();
+ resource->setCORSFailed();
context().addConsoleMessage(errorMessage);
return false;
}
« no previous file with comments | « Source/core/fetch/ResourceFetcher.h ('k') | Source/core/fetch/ResourceFetcherTest.cpp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698