FileReader is an HTML5 API that allows you to read files from JavaScript. This document describes how the API works. For how to use FileReader, Exploring the FileSystem APIs is a good tutorial. Disclaimer: the document was originally written in the early January 2012. The contents will be deprecated as Chromium code evolves. Entering the mazeWe'll read through large pieces of code used to read files with FileReader, aiming to understand how the whole thing works. You may wonder how come it is difficult to read files. Shouldn't it be as easy as calling fread() or something? Not really. First of all, no file operations are allowed in the renderer process, so we'll need to forward file read requests to the browser process. This part, IPC, is fairly complex, so we'll skip the IPC part. We'll also skip JavaScript binding part.In the renderer processSuppose readAsText() is called from JavaScript code on a file in a HTML5 file system, what's gonna happen in the renderer process?Lots of things happen in the JavaScript binding layer but we skip this part as mentioned. Eventually, we'll be calling to FileReader::readAsText(), the C++ counterpart of FileReader.readAsText(), in the WebKit code base: http://trac.webkit.org/browser/trunk/Source/WebCore/fileapi/FileReader.cpp void FileReader::readAsText(Blob* blob, const String& encoding, ExceptionCode& ec) { .. m_encoding = encoding; readInternal(blob, FileReaderLoader::ReadAsText, ec); } void FileReader::readInternal(Blob* blob, FileReaderLoader::ReadType type, ExceptionCode& ec) { .. m_loader = adoptPtr(new FileReaderLoader(m_readType, this)); m_loader->setEncoding(m_encoding); m_loader->setDataType(m_blob->type()); m_loader->start(scriptExecutionContext(), m_blob.get()); } m_loader here is a FileReaderLoader, so we are now at: http://trac.webkit.org/browser/trunk/Source/WebCore/fileapi/FileReaderLoader.cpp void FileReaderLoader::start(ScriptExecutionContext* scriptExecutionContext, Blob* blob) { // The blob is read by routing through the request handling layer given a temporary public url. m_urlForReading = BlobURL::createPublicURL(scriptExecutionContext->securityOrigin()); if (m_urlForReading.isEmpty()) { failed(FileError::SECURITY_ERR); return; } .. if (m_client) m_loader = ThreadableLoader::create(scriptExecutionContext, this, request, options); .. } By the way, The blob URL looks like "blob:http%3A%2F%2Fwww.example.com/66504542-7552-45d3-abfa-7865235427be". m_client here is not NULL, so we are calling to ThreadableLoader::create(): http://trac.webkit.org/browser/trunk/Source/WebCore/loader/ThreadableLoader.cpp PassRefPtr ThreadableLoader::create(ScriptExecutionContext* context, ThreadableLoaderClient* client, const ResourceRequest& request, const ThreadableLoaderOptions& options) { .. return DocumentThreadableLoader::create(static_cast(context), client, request, options); } In the worker context (i.e. HTML5's Web Worker), a different code path is used, but here, we assume we are not in the worker context so DocumentThreadableLoader::create() is called: http://trac.webkit.org/browser/trunk/Source/WebCore/loader/DocumentThreadableLoader.cpp PassRefPtr DocumentThreadableLoader::create(Document* document, ThreadableLoaderClient* client, const ResourceRequest& request, const ThreadableLoaderOptions& options) { RefPtr loader = adoptRef(new DocumentThreadableLoader(document, client, LoadAsynchronously, request, options)); if (!loader->m_resource) loader = 0; return loader.release(); } Be sure to remember that LoadAsynchronously is set here. The constructor looks like: DocumentThreadableLoader::DocumentThreadableLoader(Document* document, ThreadableLoaderClient* client, BlockingBehavior blockingBehavior, const ResourceRequest& request, const ThreadableLoaderOptions& options) : m_client(client) , m_document(document) , m_options(options) , m_sameOriginRequest(securityOrigin()->canRequest(request.url())) , m_async(blockingBehavior == LoadAsynchronously) #if ENABLE(INSPECTOR) , m_preflightRequestIdentifier(0) #endif { .. if (m_sameOriginRequest || m_options.crossOriginRequestPolicy == AllowCrossOriginRequests) { loadRequest(request, DoSecurityCheck); return; } } Lots of things happen in the constructor, but here, a call to loadRequest() is what we are looking for: void DocumentThreadableLoader::loadRequest(const ResourceRequest& request, SecurityCheckPolicy securityCheck) { ... if (m_async) { m_resource = m_document->cachedResourceLoader()->requestRawResource(newRequest, options); if (m_resource) m_resource->addClient(this); return; } .. The code path for asynchronous operations is chosen here, as we set LoadAsynchronously before. Here, cachedResourceLoader() returns CachedResourceLoader, and the requestRawResource() looks like: http://trac.webkit.org/browser/trunk/Source/WebCore/loader/cache/CachedResourceLoader.cpp CachedRawResource* CachedResourceLoader::requestRawResource(ResourceRequest& request, const ResourceLoaderOptions& options) { return static_cast(requestResource(CachedResource::RawResource, request, String(), options, ResourceLoadPriorityUnresolved, false)); } CachedResource* CachedResourceLoader::requestResource(CachedResource::Type type, ResourceRequest& request, const String& charset, const ResourceLoaderOptions& options, ResourceLoadPriority priority, bool forPreload) { .. switch (determineRevalidationPolicy(type, request, forPreload, resource)) { case Load: resource = loadResource(type, request, charset, priority, options); break; .. } This function does a lot of things, but we'll eventually be calling loadResource() as we are now trying to load a file. We are now at: CachedResource* CachedResourceLoader::loadResource(CachedResource::Type type, ResourceRequest& request, const String& charset, ResourceLoadPriority priority, const ResourceLoaderOptions& options) { .. CachedResource* resource = createResource(type, request, charset); .. resource->setLoadPriority(priority); resource->load(this, options); .. } resource here is a CachedResource, hence we are now at: http://trac.webkit.org/browser/trunk/Source/WebCore/loader/cache/CachedResource.cpp void CachedResource::load(CachedResourceLoader* cachedResourceLoader, const ResourceLoaderOptions& options) { .. m_loader = resourceLoadScheduler()->scheduleSubresourceLoad(cachedResourceLoader->document()->frame(), this, m_resourceRequest, m_resourceRequest.priority(), options); .. } Lots of things happen again in this function, but eventually we'll be calling to resourceLoadScheduler(): http://trac.webkit.org/browser/trunk/Source/WebCore/loader/ResourceLoadScheduler.cpp ResourceLoadScheduler* resourceLoadScheduler() { ASSERT(isMainThread()); DEFINE_STATIC_LOCAL(ResourceLoadScheduler, resourceLoadScheduler, ()); return &resourceLoadScheduler; } As shown, this just returns a single ton of ResourceLoadScheduler, and scheduleSubresourceLoad() looks like: PassRefPtr ResourceLoadScheduler::scheduleSubresourceLoad(Frame* frame, CachedResource* resource, const ResourceRequest& request, ResourceLoadPriority priority, const ResourceLoaderOptions& options) { RefPtr loader = SubresourceLoader::create(frame, resource, request, options); if (loader) scheduleLoad(loader.get(), priority); return loader.release(); } void ResourceLoadScheduler::scheduleLoad(ResourceLoader* resourceLoader, ResourceLoadPriority priority) { .. if (priority > ResourceLoadPriorityLow || !resourceLoader->url().protocolInHTTPFamily() || (priority == ResourceLoadPriorityLow && !hadRequests)) { // Try to request important resources immediately. servePendingRequests(host, priority); return; } Here, priority is ResourceLoadPriorityMedium. Why? because the default priority for http://trac.webkit.org/browser/trunk/Source/WebCore/loader/cache/CachedResource.cpp static ResourceLoadPriority defaultPriorityForResourceType(CachedResource::Type type) { switch (type) { .. case CachedResource::RawResource: return ResourceLoadPriorityMedium; Anyway, we are now calling to servePendingRequests() which looks like: void ResourceLoadScheduler::servePendingRequests(HostInformation* host, ResourceLoadPriority minimumPriority) { .. for (int priority = ResourceLoadPriorityHighest; priority >= minimumPriority; --priority) { .. while (!requestsPending.isEmpty()) { .. resourceLoader->start(); } } } Again, lots of things happen in this function, eventually, we'll be calling to ResourceLoader::start(): http://trac.webkit.org/browser/trunk/Source/WebCore/loader/ResourceLoader.cpp void ResourceLoader::start() { .. if (!m_reachedTerminalState) m_handle = ResourceHandle::create(m_frame->loader()->networkingContext(), m_request, this, m_defersLoading, m_options.sniffContent == SniffContent); } Now we are creating yet another indirection, which is ResourceHandle: http://trac.webkit.org/browser/trunk/Source/WebKit/chromium/src/ResourceHandle.cpp PassRefPtr ResourceHandle::create(NetworkingContext* context, const ResourceRequest& request, ResourceHandleClient* client, bool defersLoading, bool shouldContentSniff) { RefPtr newHandle = adoptRef(new ResourceHandle( request, client, defersLoading, shouldContentSniff)); if (newHandle->start(context)) return newHandle.release(); return 0; } The ResourceHandle::start() looks lke: bool ResourceHandle::start(NetworkingContext* context) { d->start(); return true; } Wait! What's 'd'? Well, this is a member variable, which is a pointer to a ResourceHandleInternal object. Regardless of whether the variable name is good or not, we are now at: void ResourceHandleInternal::start() { .. m_loader->loadAsynchronously(wrappedRequest, this); } Here, m_loader is an object of WebURLLoaderImpl http://src.chromium.org/viewvc/chrome/trunk/src/webkit/glue/weburlloader_impl.cc?view=log void WebURLLoaderImpl::loadAsynchronously(const WebURLRequest& request, WebURLLoaderClient* client) { DCHECK(!context_->client()); context_->set_client(client); context_->Start(request, NULL, platform_); } Here, context_ is a WebURLLoaderImpl::Context object. By the way, did you noticed that we are now in Chromim's tree, not in WebKit? Start() looks like: void WebURLLoaderImpl::Context::Start( const WebURLRequest& request, ResourceLoaderBridge::SyncLoadResponse* sync_load_response, WebKitPlatformSupportImpl* platform) { .. return dispatcher_->message_sender()->Send( new ResourceHostMsg_RequestResource(routing_id_, request_id_, request_)); } This function is huge, but we'll be eventually issuing an IPC call named ResourceHostMsg_RequestResource. As mentioned before, we'll skip the IPC part. In case you are interested, message_sender() is an object of RenderThreadImpl. You can continue reading through the IPC code as a homework. Now in the browser processSo far, we've seen lots of things happened in the renderer process. We are now in the browser proces. The ResourceHostMsg_RequestResource IPC request is handled here:http://src.chromium.org/viewvc/chrome/trunk/src/content/browser/renderer_host/resource_dispatcher_host.h?view=markup bool ResourceDispatcherHost::OnMessageReceived(const IPC::Message& message, ResourceMessageFilter* filter, bool* message_was_ok) { filter_ = filter; bool handled = true; IPC_BEGIN_MESSAGE_MAP_EX(ResourceDispatcherHost, message, *message_was_ok) IPC_MESSAGE_HANDLER(ResourceHostMsg_RequestResource, OnRequestResource) Which dispatches the request to: void ResourceDispatcherHost::OnRequestResource( const IPC::Message& message, int request_id, const ResourceHostMsg_Request& request_data) { BeginRequest(request_id, request_data, NULL, message.routing_id()); } void ResourceDispatcherHost::BeginRequest( int request_id, const ResourceHostMsg_Request& request_data, IPC::Message* sync_result, // only valid for sync int route_id) { .. if (deferred_request) { .. } else { BeginRequestInternal(request); } } BeginRequest() is more than 200-line long, and not only that, it's calling to a helper function which looks like: void ResourceDispatcherHost::BeginRequestInternal(net::URLRequest* request) { .. if (!defer_start) InsertIntoResourceQueue(request, *info); Now we are inserting the request to a queue: void ResourceDispatcherHost::InsertIntoResourceQueue( net::URLRequest* request, const ResourceDispatcherHostRequestInfo& request_info) { resource_queue_.AddRequest(request, request_info); // Make sure we have the load state monitor running if (!update_load_states_timer_.IsRunning()) { update_load_states_timer_.Start(FROM_HERE, TimeDelta::FromMilliseconds(kUpdateLoadStatesIntervalMsec), this, &ResourceDispatcherHost::UpdateLoadStates); } } The requests in the queue will eventually be processed: http://src.chromium.org/viewvc/chrome/trunk/src/content/browser/renderer_host/resource_queue.cc?view=markup void ResourceQueue::AddRequest( net::URLRequest* request, const ResourceDispatcherHostRequestInfo& request_info) { .. if (interested_delegates.empty()) { request->Start(); return; } .. } Here, request is a URLRequest with StartJob() function looking like: http://src.chromium.org/viewvc/chrome/trunk/src/net/url_request/url_request.cc?view=markup void URLRequest::StartJob(URLRequestJob* job) { .. job_->Start(); } Here, job_ is a BlobURLRequestJob object, and Start() looks like: http://src.chromium.org/viewvc/chrome/trunk/src/webkit/blob/blob_url_request_job.cc?view=markup void BlobURLRequestJob::Start() { // Continue asynchronously. MessageLoop::current()->PostTask( FROM_HERE, base::Bind(&BlobURLRequestJob::DidStart, weak_factory_.GetWeakPtr())); } void BlobURLRequestJob::DidStart() { // We only support GET request per the spec. if (request()->method() != "GET") { NotifyFailure(net::ERR_METHOD_NOT_SUPPORTED); return; } // If the blob data is not present, bail out. if (!blob_data_) { NotifyFailure(net::ERR_FILE_NOT_FOUND); return; } CountSize(); } CountSize() sounds like a simple function that just counts things, but in reality, it does a lot more than that: void BlobURLRequestJob::CountSize() { for (; item_index_ < blob_data_->items().size(); ++item_index_) { .. // If there is a file item, do the resolving. if (item.type == BlobData::TYPE_FILE) { ResolveFile(item.file_path); return; } .. } Now we are reading with a File type blob, so ResolveFile() is called: void BlobURLRequestJob::ResolveFile(const FilePath& file_path) { base::FileUtilProxy::GetFileInfo( file_thread_proxy_, file_path, base::Bind(&BlobURLRequestJob::DidResolve, weak_factory_.GetWeakPtr())); } GetFileInfo() is called in the FILE thread, which looks like: http://src.chromium.org/viewvc/chrome/trunk/src/base/file_util_proxy.cc?view=markup // Retrieves the information about a file. It is invalid to pass NULL for the // callback. bool FileUtilProxy::GetFileInfo( scoped_refptr message_loop_proxy, const FilePath& file_path, const GetFileInfoCallback& callback) { GetFileInfoHelper* helper = new GetFileInfoHelper; return message_loop_proxy->PostTaskAndReply( FROM_HERE, Bind(&GetFileInfoHelper::RunWorkForFilePath, Unretained(helper), file_path), Bind(&GetFileInfoHelper::Reply, Owned(helper), callback)); } class GetFileInfoHelper { public: void RunWorkForFilePath(const FilePath& file_path) { if (!file_util::PathExists(file_path)) { error_ = PLATFORM_FILE_ERROR_NOT_FOUND; return; } if (!file_util::GetFileInfo(file_path, &file_info_)) error_ = PLATFORM_FILE_ERROR_FAILED; } void Reply(const FileUtilProxy::GetFileInfoCallback& callback) { if (!callback.is_null()) { callback.Run(error_, file_info_); } } Here, we get the file info with file_util::GetFileInfo() in the FILE thread, and calling the callback function in the original thread, which is IO thread. The callback is BlobURLRequestJob::DidResolve(): void BlobURLRequestJob::DidResolve(base::PlatformFileError rv, const base::PlatformFileInfo& file_info) { .. // Continue counting the size for the remaining items. item_index_++; CountSize(); } Which again calls CountSize() to continue the business, which ends with a call to NotifySuccess(): void BlobURLRequestJob::CountSize() { .. NotifySuccess(); } void BlobURLRequestJob::NotifySuccess() { .. HeadersCompleted(status_code, status_text); } void BlobURLRequestJob::HeadersCompleted(int status_code, const std::string& status_text) { .. NotifyHeadersComplete(); } NotifyHeadersComplete is defined in the base class, which is URLRequestJob: void URLRequestJob::NotifyHeadersComplete() { .. request_->NotifyResponseStarted(); } Here, request_ is a URLRequest, void URLRequest::NotifyResponseStarted() { .. delegate_->OnResponseStarted(this); } Here, delegate_ is an object of ResourceDispatcherHost and the function looks like: void ResourceDispatcherHost::OnResponseStarted(net::URLRequest* request) { .. StartReading(request); .. } void ResourceDispatcherHost::StartReading(net::URLRequest* request) { // Start reading. int bytes_read = 0; if (Read(request, &bytes_read)) { .. } bool ResourceDispatcherHost::Read(net::URLRequest* request, int* bytes_read) { .. return request->Read(buf, buf_size, bytes_read); } We are now back to URLRequest again: bool URLRequest::Read(IOBuffer* dest, int dest_size, int* bytes_read) { .. bool rv = job_->Read(dest, dest_size, bytes_read); .. return rv; } Here, job_ is URLRequestObject, so we are back to again: bool URLRequestJob::Read(IOBuffer* buf, int buf_size, int *bytes_read) { .. if (!filter_.get()) { rv = ReadRawDataHelper(buf, buf_size, bytes_read); bool URLRequestJob::ReadRawDataHelper(IOBuffer* buf, int buf_size, int* bytes_read) { .. bool rv = ReadRawData(buf, buf_size, bytes_read); } ReadRawData() is implemented in a sub class, which is BlobURLRequestJob here: bool BlobURLRequestJob::ReadRawData(net::IOBuffer* dest, int dest_size, int* bytes_read) { .. return ReadLoop(bytes_read); } bool BlobURLRequestJob::ReadLoop(int* bytes_read) { // Read until we encounter an error or could not get the data immediately. while (remaining_bytes_ > 0 && read_buf_remaining_bytes_ > 0) { if (!ReadItem()) return false; } *bytes_read = ReadCompleted(); return true; } Blobs are chunked to multiple items, so we read through all items with a loop, by calling to ReadItem() repeatedly: bool BlobURLRequestJob::ReadItem() { .. switch (item.type) { case BlobData::TYPE_DATA: return ReadBytes(item); case BlobData::TYPE_FILE: return DispatchReadFile(item); default: DCHECK(false); return false; } } bool BlobURLRequestJob::DispatchReadFile(const BlobData::Item& item) { // If the stream already exists, keep reading from it. if (stream_ != NULL) return ReadFile(item); base::FileUtilProxy::CreateOrOpen( file_thread_proxy_, item.file_path, kFileOpenFlags, base::Bind(&BlobURLRequestJob::DidOpen, weak_factory_.GetWeakPtr())); SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0)); return false; } First time, DispatchReadFile() is called, stream_ is NULL, so FileUtilProxy::CreateOrOpen() is called to open a file via a callback function, and then read: void BlobURLRequestJob::DidOpen(base::PlatformFileError rv, base::PassPlatformFile file, bool created) { .. ReadFile(item); } Finally we are reading that file!bool BlobURLRequestJob::ReadFile(const BlobData::Item& item) { .. // Start the asynchronous reading. int rv = stream_->Read(read_buf_->data() + read_buf_offset_, bytes_to_read_, base::Bind(&BlobURLRequestJob::DidRead, base::Unretained(this))); Finally, we've reached at the point that reads contents of the file! Here, we are using net::FileStream::Read() in an asynchronous fashion, on the IO thread. Back to the renderer processThe browser process is now ready to return the response to the renderer process. We omit the details here, but the browser process returns the response by issuing three IPC calls:
Let's start from the step 2, as the step 1 is not so interesting. The message is first handled at: http://src.chromium.org/viewvc/chrome/trunk/src/content/common/resource_dispatcher.cc?view=markup void ResourceDispatcher::DispatchMessage(const IPC::Message& message) { IPC_BEGIN_MESSAGE_MAP(ResourceDispatcher, message) .. IPC_MESSAGE_HANDLER(ResourceMsg_DataReceived, OnReceivedData) .. IPC_END_MESSAGE_MAP() } Which calls to void ResourceDispatcher::OnReceivedData(const IPC::Message& message, int request_id, base::SharedMemoryHandle shm_handle, int data_len, int encoded_data_length) { .. if (data_len > 0 && shared_mem.Map(data_len)) { const char* data = static_cast(shared_mem.memory()); request_info->peer->OnReceivedData(data, data_len, encoded_data_length); } } As shown, data is transferred via shared memory, pointed by shared_mem.memory(). Here, peer is WebURLLoaderImpl::Context, hence OnReceivedData() looks like: void WebURLLoaderImpl::Context::OnReceivedData(const char* data, int data_length, int encoded_data_length) { .. if (ftp_listing_delegate_.get()) { // The FTP listing delegate will make the appropriate calls to // client_->didReceiveData and client_->didReceiveResponse. ftp_listing_delegate_->OnReceivedData(data, data_length); } else if (multipart_delegate_.get()) { // The multipart delegate will make the appropriate calls to // client_->didReceiveData and client_->didReceiveResponse. multipart_delegate_->OnReceivedData(data, data_length, encoded_data_length); } else { client_->didReceiveData(loader_, data, data_length, encoded_data_length); } } Here, client is a ResourceLoader: http://trac.webkit.org/browser/trunk/Source/WebCore/loader/ResourceLoader.cpp void ResourceLoader::didReceiveData(ResourceHandle*, const char* data, int length, int encodedDataLength) { InspectorInstrumentationCookie cookie = InspectorInstrumentation::willReceiveResourceData(m_frame.get(), identifier()); didReceiveData(data, length, encodedDataLength, false); InspectorInstrumentation::didReceiveResourceData(cookie); } didReceiveData() is implemented in a sub class, which is SubresourceLoader: http://trac.webkit.org/browser/trunk/Source/WebCore/loader/SubresourceLoader.cpp void SubresourceLoader::didReceiveData(const char* data, int length, long long encodedDataLength, bool allAtOnce) { .. if (!m_loadingMultipartContent) sendDataToResource(data, length); } void SubresourceLoader::sendDataToResource(const char* data, int length) { .. if (m_loadingMultipartContent || !resourceData()) { RefPtr copiedData = SharedBuffer::create(data, length); m_resource->data(copiedData.release(), m_loadingMultipartContent); } else a m_resource->data(resourceData(), false); } Here, m_resource is CachedResource. Do you remember we saw this class before? data() looks like: void CachedResource::data(PassRefPtr, bool allDataReceived) { if (!allDataReceived) return; setLoading(false); checkNotify(); } First time it's called, allDataReceived is false.The function is then called again, once the renderer receives ResourceMsg_RequestCompletel. Hence, checkNotify() will be called: void CachedResource::checkNotify() { if (isLoading()) return; CachedResourceClientWalker w(m_clients); while (CachedResourceClient* c = w.next()) c->notifyFinished(this); } Here, c is DocumentThreadableLoader (we saw this before btw), and notifyFinished() looks like: void DocumentThreadableLoader::notifyFinished(CachedResource* resource) { .. if (m_actualRequest) { ASSERT(!m_sameOriginRequest); ASSERT(m_options.crossOriginRequestPolicy == UseAccessControl); preflightSuccess(); } else m_client->didFinishLoading(identifier, finishTime); } Here, m_client is a FileReaderLoader object: void FileReaderLoader::didFinishLoading(unsigned long, double) { cleanup(); if (m_client) m_client->didFinishLoading(); } And m_client here is a FileReader object. Do you remember that FileReader is where we started? The function looks like: void FileReader::didFinishLoading() { m_state = DONE; fireEvent(eventNames().loadEvent); fireEvent(eventNames().loadendEvent); } void FileReader::fireEvent(const AtomicString& type) { dispatchEvent(ProgressEvent::create(type, true, m_loader ? m_loader->bytesLoaded() : 0, m_loader ? m_loader->totalBytes() : 0)); } dispatchEvent() is implemented in the base class, which is EventTarget: http://trac.webkit.org/browser/trunk/Source/WebCore/dom/EventTarget.cpp bool EventTarget::dispatchEvent(PassRefPtr event) { event->setTarget(this); event->setCurrentTarget(this); event->setEventPhase(Event::AT_TARGET); bool defaultPrevented = fireEventListeners(event.get()); event->setEventPhase(0); return defaultPrevented; } bool EventTarget::fireEventListeners(Event* event) { .. if (listenerVector) fireEventListeners(event, d, *listenerVector); } void EventTarget::fireEventListeners(Event* event, EventTargetData* d, EventListenerVector& entry) { .. for ( ; i < end; ++i) { .. registeredListener.listener->handleEvent(scriptExecutionContext(), event); } .. } Here, listener is a V8AbstractEventListener object, which looks like: void V8AbstractEventListener::handleEvent(ScriptExecutionContext* context, Event* event) { .. invokeEventHandler(context, event, jsEvent); } Woohoo, we are now in V8, the entrance to the JavaScript world. Back to the JavaScript worldFinally onload event is delivered to the onload handler associated with a FileReader object in JavaScript, and your JavaScript code receives the file contents, and we are done. |
